1 package compiler
2
3 import (
4 "fmt"
5 "strconv"
6 "testing"
7 "unsafe"
8
9 "github.com/tetratelabs/wazero/internal/asm"
10 "github.com/tetratelabs/wazero/internal/testing/require"
11 "github.com/tetratelabs/wazero/internal/wasm"
12 "github.com/tetratelabs/wazero/internal/wazeroir"
13 )
14
15 func TestCompiler_compileSignExtend(t *testing.T) {
16 type fromKind byte
17 from8, from16, from32 := fromKind(0), fromKind(1), fromKind(2)
18
19 t.Run("32bit", func(t *testing.T) {
20 tests := []struct {
21 in int32
22 expected int32
23 fromKind fromKind
24 }{
25
26 {in: 0, expected: 0, fromKind: from8},
27 {in: 0x7f, expected: 127, fromKind: from8},
28 {in: 0x80, expected: -128, fromKind: from8},
29 {in: 0xff, expected: -1, fromKind: from8},
30 {in: 0x012345_00, expected: 0, fromKind: from8},
31 {in: -19088768 , expected: -0x80, fromKind: from8},
32 {in: -1, expected: -1, fromKind: from8},
33
34
35 {in: 0, expected: 0, fromKind: from16},
36 {in: 0x7fff, expected: 32767, fromKind: from16},
37 {in: 0x8000, expected: -32768, fromKind: from16},
38 {in: 0xffff, expected: -1, fromKind: from16},
39 {in: 0x0123_0000, expected: 0, fromKind: from16},
40 {in: -19103744 , expected: -0x8000, fromKind: from16},
41 {in: -1, expected: -1, fromKind: from16},
42 }
43
44 for _, tt := range tests {
45 tc := tt
46 t.Run(fmt.Sprintf("0x%x", tc.in), func(t *testing.T) {
47 env := newCompilerEnvironment()
48 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil)
49 err := compiler.compilePreamble()
50 require.NoError(t, err)
51
52
53 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(uint32(tc.in))))
54 require.NoError(t, err)
55
56 if tc.fromKind == from8 {
57 err = compiler.compileSignExtend32From8()
58 } else {
59 err = compiler.compileSignExtend32From16()
60 }
61 require.NoError(t, err)
62
63
64
65 err = compiler.compileReturnFunction()
66 require.NoError(t, err)
67
68 code := asm.CodeSegment{}
69 defer func() { require.NoError(t, code.Unmap()) }()
70
71
72 _, err = compiler.compile(code.NextCodeSection())
73 require.NoError(t, err)
74 env.exec(code.Bytes())
75
76 require.Equal(t, uint64(1), env.stackPointer())
77 require.Equal(t, tc.expected, env.stackTopAsInt32())
78 })
79 }
80 })
81 t.Run("64bit", func(t *testing.T) {
82 tests := []struct {
83 in int64
84 expected int64
85 fromKind fromKind
86 }{
87
88 {in: 0, expected: 0, fromKind: from8},
89 {in: 0x7f, expected: 127, fromKind: from8},
90 {in: 0x80, expected: -128, fromKind: from8},
91 {in: 0xff, expected: -1, fromKind: from8},
92 {in: 0x01234567_89abcd_00, expected: 0, fromKind: from8},
93 {in: 81985529216486784 , expected: -0x80, fromKind: from8},
94 {in: -1, expected: -1, fromKind: from8},
95
96
97 {in: 0, expected: 0, fromKind: from16},
98 {in: 0x7fff, expected: 32767, fromKind: from16},
99 {in: 0x8000, expected: -32768, fromKind: from16},
100 {in: 0xffff, expected: -1, fromKind: from16},
101 {in: 0x12345678_9abc_0000, expected: 0, fromKind: from16},
102 {in: 81985529216466944 , expected: -0x8000, fromKind: from16},
103 {in: -1, expected: -1, fromKind: from16},
104
105
106 {in: 0, expected: 0, fromKind: from32},
107 {in: 0x7fff, expected: 32767, fromKind: from32},
108 {in: 0x8000, expected: 32768, fromKind: from32},
109 {in: 0xffff, expected: 65535, fromKind: from32},
110 {in: 0x7fffffff, expected: 0x7fffffff, fromKind: from32},
111 {in: 0x80000000, expected: -0x80000000, fromKind: from32},
112 {in: 0xffffffff, expected: -1, fromKind: from32},
113 {in: 0x01234567_00000000, expected: 0, fromKind: from32},
114 {in: -81985529054232576 , expected: -0x80000000, fromKind: from32},
115 {in: -1, expected: -1, fromKind: from32},
116 }
117
118 for _, tt := range tests {
119 tc := tt
120 t.Run(fmt.Sprintf("0x%x", tc.in), func(t *testing.T) {
121 env := newCompilerEnvironment()
122 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil)
123 err := compiler.compilePreamble()
124 require.NoError(t, err)
125
126
127 err = compiler.compileConstI64(operationPtr(wazeroir.NewOperationConstI64(uint64(tc.in))))
128 require.NoError(t, err)
129
130 if tc.fromKind == from8 {
131 err = compiler.compileSignExtend64From8()
132 } else if tc.fromKind == from16 {
133 err = compiler.compileSignExtend64From16()
134 } else {
135 err = compiler.compileSignExtend64From32()
136 }
137 require.NoError(t, err)
138
139
140
141 err = compiler.compileReturnFunction()
142 require.NoError(t, err)
143
144 code := asm.CodeSegment{}
145 defer func() { require.NoError(t, code.Unmap()) }()
146
147
148 _, err = compiler.compile(code.NextCodeSection())
149 require.NoError(t, err)
150 env.exec(code.Bytes())
151
152 require.Equal(t, uint64(1), env.stackPointer())
153 require.Equal(t, tc.expected, env.stackTopAsInt64())
154 })
155 }
156 })
157 }
158
159 func TestCompiler_compileMemoryCopy(t *testing.T) {
160 const checkCeil = 100
161 tests := []struct {
162 sourceOffset, destOffset, size uint32
163 requireOutOfBoundsError bool
164 }{
165 {sourceOffset: 0, destOffset: 0, size: 0},
166 {sourceOffset: 10, destOffset: 5, size: 10},
167 {sourceOffset: 10, destOffset: 9, size: 1},
168 {sourceOffset: 10, destOffset: 9, size: 2},
169 {sourceOffset: 0, destOffset: 10, size: 10},
170 {sourceOffset: 0, destOffset: 5, size: 10},
171 {sourceOffset: 9, destOffset: 10, size: 10},
172 {sourceOffset: 11, destOffset: 13, size: 4},
173 {sourceOffset: 0, destOffset: 10, size: 5},
174 {sourceOffset: 1, destOffset: 10, size: 5},
175 {sourceOffset: 0, destOffset: 10, size: 1},
176 {sourceOffset: 0, destOffset: 10, size: 0},
177 {sourceOffset: 5, destOffset: 10, size: 10},
178 {sourceOffset: 5, destOffset: 10, size: 5},
179 {sourceOffset: 5, destOffset: 10, size: 1},
180 {sourceOffset: 5, destOffset: 10, size: 0},
181 {sourceOffset: 10, destOffset: 0, size: 10},
182 {sourceOffset: 1, destOffset: 0, size: 2},
183 {sourceOffset: 1, destOffset: 0, size: 20},
184 {sourceOffset: 10, destOffset: 0, size: 5},
185 {sourceOffset: 10, destOffset: 0, size: 1},
186 {sourceOffset: 10, destOffset: 0, size: 0},
187 {sourceOffset: 0, destOffset: 50, size: 48},
188 {sourceOffset: 0, destOffset: 50, size: 49},
189 {sourceOffset: 10, destOffset: 20, size: 72},
190 {sourceOffset: 20, destOffset: 10, size: 72},
191 {sourceOffset: 19, destOffset: 18, size: 79},
192 {sourceOffset: 20, destOffset: 19, size: 79},
193 {sourceOffset: defaultMemoryPageNumInTest * wasm.MemoryPageSize, destOffset: 0, size: 1, requireOutOfBoundsError: true},
194 {sourceOffset: defaultMemoryPageNumInTest*wasm.MemoryPageSize + 1, destOffset: 0, size: 0, requireOutOfBoundsError: true},
195 {sourceOffset: 0, destOffset: defaultMemoryPageNumInTest * wasm.MemoryPageSize, size: 1, requireOutOfBoundsError: true},
196 {sourceOffset: 0, destOffset: defaultMemoryPageNumInTest*wasm.MemoryPageSize + 1, size: 0, requireOutOfBoundsError: true},
197 {sourceOffset: defaultMemoryPageNumInTest*wasm.MemoryPageSize - 99, destOffset: 0, size: 100, requireOutOfBoundsError: true},
198 {sourceOffset: 0, destOffset: defaultMemoryPageNumInTest*wasm.MemoryPageSize - 99, size: 100, requireOutOfBoundsError: true},
199 }
200
201 for i, tt := range tests {
202 tc := tt
203 t.Run(strconv.Itoa(i), func(t *testing.T) {
204 env := newCompilerEnvironment()
205 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{HasMemory: true})
206
207 err := compiler.compilePreamble()
208 require.NoError(t, err)
209
210
211 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(tc.destOffset)))
212 require.NoError(t, err)
213 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(tc.sourceOffset)))
214 require.NoError(t, err)
215 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(tc.size)))
216 require.NoError(t, err)
217
218 err = compiler.compileMemoryCopy()
219 require.NoError(t, err)
220
221 code := asm.CodeSegment{}
222 defer func() { require.NoError(t, code.Unmap()) }()
223
224
225 err = compiler.compileReturnFunction()
226 require.NoError(t, err)
227 _, err = compiler.compile(code.NextCodeSection())
228 require.NoError(t, err)
229
230
231 mem := env.memory()
232 for i := 0; i < checkCeil; i++ {
233 mem[i] = byte(i)
234 }
235
236
237 env.exec(code.Bytes())
238
239 if !tc.requireOutOfBoundsError {
240 exp := make([]byte, checkCeil)
241 for i := 0; i < checkCeil; i++ {
242 exp[i] = byte(i)
243 }
244 copy(exp[tc.destOffset:],
245 exp[tc.sourceOffset:tc.sourceOffset+tc.size])
246
247
248 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
249 require.Equal(t, exp, mem[:checkCeil])
250 } else {
251 require.Equal(t, nativeCallStatusCodeMemoryOutOfBounds, env.compilerStatus())
252 }
253 })
254 }
255 }
256
257 func TestCompiler_compileMemoryFill(t *testing.T) {
258 const checkCeil = 50
259
260 tests := []struct {
261 v, destOffset uint32
262 size uint32
263 requireOutOfBoundsError bool
264 }{
265 {v: 0, destOffset: 0, size: 25},
266 {v: 0, destOffset: 10, size: 17},
267 {v: 0, destOffset: 10, size: 15},
268 {v: 0, destOffset: 10, size: 5},
269 {v: 0, destOffset: 10, size: 1},
270 {v: 0, destOffset: 10, size: 0},
271 {v: 5, destOffset: 10, size: 27},
272 {v: 5, destOffset: 10, size: 25},
273 {v: 5, destOffset: 10, size: 21},
274 {v: 5, destOffset: 10, size: 10},
275 {v: 5, destOffset: 10, size: 5},
276 {v: 5, destOffset: 10, size: 1},
277 {v: 5, destOffset: 10, size: 0},
278 {v: 10, destOffset: 0, size: 10},
279 {v: 10, destOffset: 0, size: 5},
280 {v: 10, destOffset: 0, size: 1},
281 {v: 10, destOffset: 0, size: 0},
282 {v: 10, destOffset: defaultMemoryPageNumInTest*wasm.MemoryPageSize - 99, size: 100, requireOutOfBoundsError: true},
283 {v: 10, destOffset: defaultMemoryPageNumInTest * wasm.MemoryPageSize, size: 5, requireOutOfBoundsError: true},
284 {v: 10, destOffset: defaultMemoryPageNumInTest * wasm.MemoryPageSize, size: 1, requireOutOfBoundsError: true},
285 {v: 10, destOffset: defaultMemoryPageNumInTest*wasm.MemoryPageSize + 1, size: 0, requireOutOfBoundsError: true},
286 }
287
288 for i, tt := range tests {
289 tc := tt
290 t.Run(strconv.Itoa(i), func(t *testing.T) {
291 env := newCompilerEnvironment()
292 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{HasMemory: true})
293
294 err := compiler.compilePreamble()
295 require.NoError(t, err)
296
297
298 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(tc.destOffset)))
299 require.NoError(t, err)
300 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(tc.v)))
301 require.NoError(t, err)
302 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(tc.size)))
303 require.NoError(t, err)
304
305 err = compiler.compileMemoryFill()
306 require.NoError(t, err)
307
308 code := asm.CodeSegment{}
309 defer func() { require.NoError(t, code.Unmap()) }()
310
311
312 err = compiler.compileReturnFunction()
313 require.NoError(t, err)
314 _, err = compiler.compile(code.NextCodeSection())
315 require.NoError(t, err)
316
317
318 mem := env.memory()
319 for i := 0; i < checkCeil; i++ {
320 mem[i] = byte(i)
321 }
322
323
324 env.exec(code.Bytes())
325
326 if !tc.requireOutOfBoundsError {
327 exp := make([]byte, checkCeil)
328 for i := 0; i < checkCeil; i++ {
329 if i >= int(tc.destOffset) && i < int(tc.destOffset+tc.size) {
330 exp[i] = byte(tc.v)
331 } else {
332 exp[i] = byte(i)
333 }
334 }
335
336
337 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
338 require.Equal(t, exp, mem[:checkCeil])
339 } else {
340 require.Equal(t, nativeCallStatusCodeMemoryOutOfBounds, env.compilerStatus())
341 }
342 })
343 }
344 }
345
346 func TestCompiler_compileDataDrop(t *testing.T) {
347 origins := [][]byte{
348 {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10},
349 }
350
351 for i := 0; i < len(origins); i++ {
352 t.Run(strconv.Itoa(i), func(t *testing.T) {
353 env := newCompilerEnvironment()
354
355 env.module().DataInstances = make([][]byte, len(origins))
356 copy(env.module().DataInstances, origins)
357
358 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{
359 HasDataInstances: true,
360 })
361
362 err := compiler.compilePreamble()
363 require.NoError(t, err)
364
365 err = compiler.compileDataDrop(operationPtr(wazeroir.NewOperationDataDrop(uint32(i))))
366 require.NoError(t, err)
367
368 code := asm.CodeSegment{}
369 defer func() { require.NoError(t, code.Unmap()) }()
370
371
372 err = compiler.compileReturnFunction()
373 require.NoError(t, err)
374 _, err = compiler.compile(code.NextCodeSection())
375 require.NoError(t, err)
376
377
378 env.exec(code.Bytes())
379
380 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
381
382
383 for j := 0; j < len(origins); j++ {
384 if i == j {
385 require.Nil(t, env.module().DataInstances[j])
386 } else {
387 require.NotNil(t, env.module().DataInstances[j])
388 }
389 }
390 })
391 }
392 }
393
394 func TestCompiler_compileMemoryInit(t *testing.T) {
395 dataInstances := []wasm.DataInstance{
396 nil, {1, 2, 3, 4, 5},
397 }
398
399 tests := []struct {
400 sourceOffset, destOffset uint32
401 dataIndex uint32
402 copySize uint32
403 expOutOfBounds bool
404 }{
405 {sourceOffset: 0, destOffset: 0, copySize: 0, dataIndex: 0},
406 {sourceOffset: 0, destOffset: 0, copySize: 1, dataIndex: 0, expOutOfBounds: true},
407 {sourceOffset: 1, destOffset: 0, copySize: 0, dataIndex: 0, expOutOfBounds: true},
408 {sourceOffset: 0, destOffset: 0, copySize: 0, dataIndex: 1},
409 {sourceOffset: 0, destOffset: 0, copySize: 5, dataIndex: 1},
410 {sourceOffset: 0, destOffset: 0, copySize: 1, dataIndex: 1},
411 {sourceOffset: 0, destOffset: 0, copySize: 3, dataIndex: 1},
412 {sourceOffset: 0, destOffset: 1, copySize: 3, dataIndex: 1},
413 {sourceOffset: 0, destOffset: 7, copySize: 4, dataIndex: 1},
414 {sourceOffset: 1, destOffset: 7, copySize: 4, dataIndex: 1},
415 {sourceOffset: 4, destOffset: 7, copySize: 1, dataIndex: 1},
416 {sourceOffset: 5, destOffset: 7, copySize: 0, dataIndex: 1},
417 {sourceOffset: 0, destOffset: 7, copySize: 5, dataIndex: 1},
418 {sourceOffset: 1, destOffset: 0, copySize: 3, dataIndex: 1},
419 {sourceOffset: 0, destOffset: 1, copySize: 4, dataIndex: 1},
420 {sourceOffset: 1, destOffset: 1, copySize: 3, dataIndex: 1},
421 {sourceOffset: 0, destOffset: 10, copySize: 5, dataIndex: 1},
422 {sourceOffset: 0, destOffset: 0, copySize: 6, dataIndex: 1, expOutOfBounds: true},
423 {sourceOffset: 0, destOffset: defaultMemoryPageNumInTest * wasm.MemoryPageSize, copySize: 5, dataIndex: 1, expOutOfBounds: true},
424 {sourceOffset: 0, destOffset: defaultMemoryPageNumInTest*wasm.MemoryPageSize - 3, copySize: 5, dataIndex: 1, expOutOfBounds: true},
425 {sourceOffset: 6, destOffset: 0, copySize: 0, dataIndex: 1, expOutOfBounds: true},
426 }
427
428 for i, tt := range tests {
429 tc := tt
430 t.Run(strconv.Itoa(i), func(t *testing.T) {
431 env := newCompilerEnvironment()
432 env.module().DataInstances = dataInstances
433
434 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{
435 HasDataInstances: true, HasMemory: true,
436 })
437
438 err := compiler.compilePreamble()
439 require.NoError(t, err)
440
441
442 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(tc.destOffset)))
443 require.NoError(t, err)
444 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(tc.sourceOffset)))
445 require.NoError(t, err)
446 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(tc.copySize)))
447 require.NoError(t, err)
448
449 err = compiler.compileMemoryInit(operationPtr(wazeroir.NewOperationMemoryInit(tc.dataIndex)))
450 require.NoError(t, err)
451
452 code := asm.CodeSegment{}
453 defer func() { require.NoError(t, code.Unmap()) }()
454
455
456 err = compiler.compileReturnFunction()
457 require.NoError(t, err)
458 _, err = compiler.compile(code.NextCodeSection())
459 require.NoError(t, err)
460
461
462 env.exec(code.Bytes())
463
464 if !tc.expOutOfBounds {
465 mem := env.memory()
466 exp := make([]byte, defaultMemoryPageNumInTest*wasm.MemoryPageSize)
467 if dataInst := dataInstances[tc.dataIndex]; dataInst != nil {
468 copy(exp[tc.destOffset:], dataInst[tc.sourceOffset:tc.sourceOffset+tc.copySize])
469 }
470 require.Equal(t, exp[:20], mem[:20])
471 } else {
472 require.Equal(t, nativeCallStatusCodeMemoryOutOfBounds, env.compilerStatus())
473 }
474 })
475 }
476 }
477
478 func TestCompiler_compileElemDrop(t *testing.T) {
479 origins := []wasm.ElementInstance{{1}, {2}, {3}, {4}, {5}}
480
481 for i := 0; i < len(origins); i++ {
482 t.Run(strconv.Itoa(i), func(t *testing.T) {
483 env := newCompilerEnvironment()
484
485 insts := make([]wasm.ElementInstance, len(origins))
486 copy(insts, origins)
487 env.module().ElementInstances = insts
488
489
490 for _, inst := range insts {
491 require.NotEqual(t, 0, len(inst))
492 }
493
494 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{
495 HasElementInstances: true,
496 })
497
498 err := compiler.compilePreamble()
499 require.NoError(t, err)
500
501 err = compiler.compileElemDrop(operationPtr(wazeroir.NewOperationElemDrop(uint32(i))))
502 require.NoError(t, err)
503
504 code := asm.CodeSegment{}
505 defer func() { require.NoError(t, code.Unmap()) }()
506
507
508 err = compiler.compileReturnFunction()
509 require.NoError(t, err)
510 _, err = compiler.compile(code.NextCodeSection())
511 require.NoError(t, err)
512
513
514 env.exec(code.Bytes())
515
516 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
517
518 for j := 0; j < len(insts); j++ {
519 if i == j {
520 require.Zero(t, len(env.module().ElementInstances[j]))
521 } else {
522 require.NotEqual(t, 0, len(env.module().ElementInstances[j]))
523 }
524 }
525 })
526 }
527 }
528
529 func TestCompiler_compileTableCopy(t *testing.T) {
530 const tableSize = 100
531 tests := []struct {
532 sourceOffset, destOffset, size uint32
533 requireOutOfBoundsError bool
534 }{
535 {sourceOffset: 0, destOffset: 0, size: 0},
536 {sourceOffset: 10, destOffset: 5, size: 10},
537 {sourceOffset: 10, destOffset: 9, size: 1},
538 {sourceOffset: 10, destOffset: 9, size: 2},
539 {sourceOffset: 0, destOffset: 10, size: 10},
540 {sourceOffset: 0, destOffset: 5, size: 10},
541 {sourceOffset: 9, destOffset: 10, size: 10},
542 {sourceOffset: 11, destOffset: 13, size: 4},
543 {sourceOffset: 0, destOffset: 10, size: 5},
544 {sourceOffset: 1, destOffset: 10, size: 5},
545 {sourceOffset: 0, destOffset: 10, size: 1},
546 {sourceOffset: 0, destOffset: 10, size: 0},
547 {sourceOffset: 5, destOffset: 10, size: 10},
548 {sourceOffset: 5, destOffset: 10, size: 5},
549 {sourceOffset: 5, destOffset: 10, size: 1},
550 {sourceOffset: 5, destOffset: 10, size: 0},
551 {sourceOffset: 10, destOffset: 0, size: 10},
552 {sourceOffset: 1, destOffset: 0, size: 2},
553 {sourceOffset: 1, destOffset: 0, size: 20},
554 {sourceOffset: 10, destOffset: 0, size: 5},
555 {sourceOffset: 10, destOffset: 0, size: 1},
556 {sourceOffset: 10, destOffset: 0, size: 0},
557 {sourceOffset: tableSize, destOffset: 0, size: 1, requireOutOfBoundsError: true},
558 {sourceOffset: tableSize + 1, destOffset: 0, size: 0, requireOutOfBoundsError: true},
559 {sourceOffset: 0, destOffset: tableSize, size: 1, requireOutOfBoundsError: true},
560 {sourceOffset: 0, destOffset: tableSize + 1, size: 0, requireOutOfBoundsError: true},
561 {sourceOffset: tableSize - 99, destOffset: 0, size: 100, requireOutOfBoundsError: true},
562 {sourceOffset: 0, destOffset: tableSize - 99, size: 100, requireOutOfBoundsError: true},
563 }
564
565 for i, tt := range tests {
566 tc := tt
567 t.Run(strconv.Itoa(i), func(t *testing.T) {
568 env := newCompilerEnvironment()
569 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{HasTable: true})
570
571 err := compiler.compilePreamble()
572 require.NoError(t, err)
573
574
575 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(tc.destOffset)))
576 require.NoError(t, err)
577 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(tc.sourceOffset)))
578 require.NoError(t, err)
579 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(tc.size)))
580 require.NoError(t, err)
581
582 err = compiler.compileTableCopy(operationPtr(wazeroir.NewOperationTableCopy(0, 0)))
583 require.NoError(t, err)
584
585 code := asm.CodeSegment{}
586 defer func() { require.NoError(t, code.Unmap()) }()
587
588
589 err = compiler.compileReturnFunction()
590 require.NoError(t, err)
591 _, err = compiler.compile(code.NextCodeSection())
592 require.NoError(t, err)
593
594
595 table := make([]wasm.Reference, tableSize)
596 env.addTable(&wasm.TableInstance{References: table})
597 for i := 0; i < tableSize; i++ {
598 table[i] = uintptr(i)
599 }
600
601
602 env.exec(code.Bytes())
603
604 if !tc.requireOutOfBoundsError {
605 exp := make([]wasm.Reference, tableSize)
606 for i := 0; i < tableSize; i++ {
607 exp[i] = uintptr(i)
608 }
609 copy(exp[tc.destOffset:],
610 exp[tc.sourceOffset:tc.sourceOffset+tc.size])
611
612
613 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
614 require.Equal(t, exp, table)
615 } else {
616 require.Equal(t, nativeCallStatusCodeInvalidTableAccess, env.compilerStatus())
617 }
618 })
619 }
620 }
621
622 func TestCompiler_compileTableInit(t *testing.T) {
623 elementInstances := []wasm.ElementInstance{
624 {}, {1, 2, 3, 4, 5},
625 }
626
627 const tableSize = 100
628 tests := []struct {
629 sourceOffset, destOffset uint32
630 elemIndex uint32
631 copySize uint32
632 expOutOfBounds bool
633 }{
634 {sourceOffset: 0, destOffset: 0, copySize: 0, elemIndex: 0},
635 {sourceOffset: 0, destOffset: 0, copySize: 1, elemIndex: 0, expOutOfBounds: true},
636 {sourceOffset: 1, destOffset: 0, copySize: 0, elemIndex: 0, expOutOfBounds: true},
637 {sourceOffset: 0, destOffset: 0, copySize: 0, elemIndex: 1},
638 {sourceOffset: 0, destOffset: 0, copySize: 5, elemIndex: 1},
639 {sourceOffset: 0, destOffset: 0, copySize: 1, elemIndex: 1},
640 {sourceOffset: 0, destOffset: 0, copySize: 3, elemIndex: 1},
641 {sourceOffset: 0, destOffset: 1, copySize: 3, elemIndex: 1},
642 {sourceOffset: 0, destOffset: 7, copySize: 4, elemIndex: 1},
643 {sourceOffset: 1, destOffset: 7, copySize: 4, elemIndex: 1},
644 {sourceOffset: 4, destOffset: 7, copySize: 1, elemIndex: 1},
645 {sourceOffset: 5, destOffset: 7, copySize: 0, elemIndex: 1},
646 {sourceOffset: 0, destOffset: 7, copySize: 5, elemIndex: 1},
647 {sourceOffset: 1, destOffset: 0, copySize: 3, elemIndex: 1},
648 {sourceOffset: 0, destOffset: 1, copySize: 4, elemIndex: 1},
649 {sourceOffset: 1, destOffset: 1, copySize: 3, elemIndex: 1},
650 {sourceOffset: 0, destOffset: 10, copySize: 5, elemIndex: 1},
651 {sourceOffset: 0, destOffset: 0, copySize: 6, elemIndex: 1, expOutOfBounds: true},
652 {sourceOffset: 6, destOffset: 0, copySize: 0, elemIndex: 1, expOutOfBounds: true},
653 }
654
655 for i, tt := range tests {
656 tc := tt
657 t.Run(strconv.Itoa(i), func(t *testing.T) {
658 env := newCompilerEnvironment()
659 env.module().ElementInstances = elementInstances
660
661 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{
662 HasElementInstances: true, HasTable: true,
663 })
664
665 err := compiler.compilePreamble()
666 require.NoError(t, err)
667
668
669 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(tc.destOffset)))
670 require.NoError(t, err)
671 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(tc.sourceOffset)))
672 require.NoError(t, err)
673 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(tc.copySize)))
674 require.NoError(t, err)
675
676 err = compiler.compileTableInit(operationPtr(wazeroir.NewOperationTableInit(tc.elemIndex, 0)))
677 require.NoError(t, err)
678
679
680 table := make([]wasm.Reference, tableSize)
681 env.addTable(&wasm.TableInstance{References: table})
682 for i := 0; i < tableSize; i++ {
683 table[i] = uintptr(i)
684 }
685
686 code := asm.CodeSegment{}
687 defer func() { require.NoError(t, code.Unmap()) }()
688
689
690 err = compiler.compileReturnFunction()
691 require.NoError(t, err)
692 _, err = compiler.compile(code.NextCodeSection())
693 require.NoError(t, err)
694
695
696 env.exec(code.Bytes())
697
698 if !tc.expOutOfBounds {
699 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
700 exp := make([]wasm.Reference, tableSize)
701 for i := 0; i < tableSize; i++ {
702 exp[i] = uintptr(i)
703 }
704 if inst := elementInstances[tc.elemIndex]; inst != nil {
705 copy(exp[tc.destOffset:], inst[tc.sourceOffset:tc.sourceOffset+tc.copySize])
706 }
707 require.Equal(t, exp, table)
708 } else {
709 require.Equal(t, nativeCallStatusCodeInvalidTableAccess, env.compilerStatus())
710 }
711 })
712 }
713 }
714
715 type dog struct{ name string }
716
717 func TestCompiler_compileTableSet(t *testing.T) {
718 externDog := &dog{name: "sushi"}
719 externrefOpaque := uintptr(unsafe.Pointer(externDog))
720 funcref := &function{moduleInstance: &wasm.ModuleInstance{}}
721 funcrefOpaque := uintptr(unsafe.Pointer(funcref))
722
723 externTable := &wasm.TableInstance{Type: wasm.RefTypeExternref, References: []wasm.Reference{0, 0, externrefOpaque, 0, 0}}
724 funcrefTable := &wasm.TableInstance{Type: wasm.RefTypeFuncref, References: []wasm.Reference{0, 0, 0, 0, funcrefOpaque}}
725 tables := []*wasm.TableInstance{externTable, funcrefTable}
726
727 tests := []struct {
728 name string
729 tableIndex uint32
730 offset uint32
731 in uintptr
732 expExtern bool
733 expError bool
734 }{
735 {
736 name: "externref - non nil",
737 tableIndex: 0,
738 offset: 2,
739 in: externrefOpaque,
740 expExtern: true,
741 },
742 {
743 name: "externref - nil",
744 tableIndex: 0,
745 offset: 1,
746 in: 0,
747 expExtern: true,
748 },
749 {
750 name: "externref - out of bounds",
751 tableIndex: 0,
752 offset: 10,
753 in: 0,
754 expError: true,
755 },
756 {
757 name: "funcref - non nil",
758 tableIndex: 1,
759 offset: 4,
760 in: funcrefOpaque,
761 expExtern: false,
762 },
763 {
764 name: "funcref - nil",
765 tableIndex: 1,
766 offset: 3,
767 in: 0,
768 expExtern: false,
769 },
770 {
771 name: "funcref - out of bounds",
772 tableIndex: 1,
773 offset: 100000,
774 in: 0,
775 expError: true,
776 },
777 }
778
779 for _, tt := range tests {
780 tc := tt
781 t.Run(tc.name, func(t *testing.T) {
782 env := newCompilerEnvironment()
783
784 for _, table := range tables {
785 env.addTable(table)
786 }
787
788 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{
789 HasTable: true,
790 })
791
792 err := compiler.compilePreamble()
793 require.NoError(t, err)
794
795 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(tc.offset)))
796 require.NoError(t, err)
797
798 err = compiler.compileConstI64(operationPtr(wazeroir.NewOperationConstI64(uint64(tc.in))))
799 require.NoError(t, err)
800
801 err = compiler.compileTableSet(operationPtr(wazeroir.NewOperationTableSet(tc.tableIndex)))
802 require.NoError(t, err)
803
804 code := asm.CodeSegment{}
805 defer func() { require.NoError(t, code.Unmap()) }()
806
807
808 err = compiler.compileReturnFunction()
809 require.NoError(t, err)
810 _, err = compiler.compile(code.NextCodeSection())
811 require.NoError(t, err)
812
813
814 env.exec(code.Bytes())
815
816 if tc.expError {
817 require.Equal(t, nativeCallStatusCodeInvalidTableAccess, env.compilerStatus())
818 } else {
819 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
820 require.Equal(t, uint64(0), env.stackPointer())
821
822 if tc.expExtern {
823 actual := dogFromPtr(externTable.References[tc.offset])
824 exp := externDog
825 if tc.in == 0 {
826 exp = nil
827 }
828 require.Equal(t, exp, actual)
829 } else {
830 actual := functionFromPtr(funcrefTable.References[tc.offset])
831 exp := funcref
832 if tc.in == 0 {
833 exp = nil
834 }
835 require.Equal(t, exp, actual)
836 }
837 }
838 })
839 }
840 }
841
842
843 func dogFromPtr(ptr uintptr) *dog {
844 if ptr == 0 {
845 return nil
846 }
847 return (*dog)(unsafe.Pointer(ptr))
848 }
849
850
851 func functionFromPtr(ptr uintptr) *function {
852 if ptr == 0 {
853 return nil
854 }
855 return (*function)(unsafe.Pointer(ptr))
856 }
857
858 func TestCompiler_compileTableGet(t *testing.T) {
859 externDog := &dog{name: "sushi"}
860 externrefOpaque := uintptr(unsafe.Pointer(externDog))
861 funcref := &function{moduleInstance: &wasm.ModuleInstance{}}
862 funcrefOpaque := uintptr(unsafe.Pointer(funcref))
863 tables := []*wasm.TableInstance{
864 {Type: wasm.RefTypeExternref, References: []wasm.Reference{0, 0, externrefOpaque, 0, 0}},
865 {Type: wasm.RefTypeFuncref, References: []wasm.Reference{0, 0, 0, 0, funcrefOpaque}},
866 }
867
868 tests := []struct {
869 name string
870 tableIndex uint32
871 offset uint32
872 exp uintptr
873 expError bool
874 }{
875 {
876 name: "externref - non nil",
877 tableIndex: 0,
878 offset: 2,
879 exp: externrefOpaque,
880 },
881 {
882 name: "externref - nil",
883 tableIndex: 0,
884 offset: 4,
885 exp: 0,
886 },
887 {
888 name: "externref - out of bounds",
889 tableIndex: 0,
890 offset: 5,
891 expError: true,
892 },
893 {
894 name: "funcref - non nil",
895 tableIndex: 1,
896 offset: 4,
897 exp: funcrefOpaque,
898 },
899 {
900 name: "funcref - nil",
901 tableIndex: 1,
902 offset: 1,
903 exp: 0,
904 },
905 {
906 name: "funcref - out of bounds",
907 tableIndex: 1,
908 offset: 1000,
909 expError: true,
910 },
911 }
912
913 for _, tt := range tests {
914 tc := tt
915 t.Run(tc.name, func(t *testing.T) {
916 env := newCompilerEnvironment()
917
918 for _, table := range tables {
919 env.addTable(table)
920 }
921
922 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{
923 HasTable: true,
924 })
925
926 err := compiler.compilePreamble()
927 require.NoError(t, err)
928
929 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(tc.offset)))
930 require.NoError(t, err)
931
932 err = compiler.compileTableGet(operationPtr(wazeroir.NewOperationTableGet(tc.tableIndex)))
933 require.NoError(t, err)
934
935 code := asm.CodeSegment{}
936 defer func() { require.NoError(t, code.Unmap()) }()
937
938
939 err = compiler.compileReturnFunction()
940 require.NoError(t, err)
941 _, err = compiler.compile(code.NextCodeSection())
942 require.NoError(t, err)
943
944
945 env.exec(code.Bytes())
946
947 if tc.expError {
948 require.Equal(t, nativeCallStatusCodeInvalidTableAccess, env.compilerStatus())
949 } else {
950 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
951 require.Equal(t, uint64(1), env.stackPointer())
952 require.Equal(t, uint64(tc.exp), env.stackTopAsUint64())
953 }
954 })
955 }
956 }
957
958 func TestCompiler_compileRefFunc(t *testing.T) {
959 env := newCompilerEnvironment()
960 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{})
961
962 err := compiler.compilePreamble()
963 require.NoError(t, err)
964
965 me := env.moduleEngine()
966 const numFuncs = 20
967 for i := 0; i < numFuncs; i++ {
968 me.functions = append(me.functions, function{moduleInstance: &wasm.ModuleInstance{}})
969 }
970
971 for i := 0; i < numFuncs; i++ {
972 i := i
973 t.Run(strconv.Itoa(i), func(t *testing.T) {
974 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{})
975
976 err := compiler.compilePreamble()
977 require.NoError(t, err)
978
979 err = compiler.compileRefFunc(operationPtr(wazeroir.NewOperationRefFunc(uint32(i))))
980 require.NoError(t, err)
981
982 code := asm.CodeSegment{}
983 defer func() { require.NoError(t, code.Unmap()) }()
984
985
986 err = compiler.compileReturnFunction()
987 require.NoError(t, err)
988 _, err = compiler.compile(code.NextCodeSection())
989 require.NoError(t, err)
990
991
992 env.exec(code.Bytes())
993
994 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
995 require.Equal(t, uint64(1), env.stackPointer())
996 require.Equal(t, uintptr(unsafe.Pointer(&me.functions[i])), uintptr(env.stackTopAsUint64()))
997 })
998 }
999 }
1000
View as plain text