1 package compiler
2
3 import (
4 "fmt"
5 "testing"
6 "unsafe"
7
8 "github.com/tetratelabs/wazero/internal/asm"
9 "github.com/tetratelabs/wazero/internal/testing/require"
10 "github.com/tetratelabs/wazero/internal/wasm"
11 "github.com/tetratelabs/wazero/internal/wazeroir"
12 )
13
14 func TestCompiler_compileHostFunction(t *testing.T) {
15 env := newCompilerEnvironment()
16 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil)
17
18 err := compiler.compileGoDefinedHostFunction()
19 require.NoError(t, err)
20
21
22
23 _, _, callerFuncLoc := compiler.runtimeValueLocationStack().getCallFrameLocations(&wasm.FunctionType{})
24
25 code := asm.CodeSegment{}
26 defer func() { require.NoError(t, code.Unmap()) }()
27
28
29 _, err = compiler.compile(code.NextCodeSection())
30 require.NoError(t, err)
31
32
33 f := &function{moduleInstance: &wasm.ModuleInstance{}}
34 env.stack()[callerFuncLoc.stackPointer] = uint64(uintptr(unsafe.Pointer(f)))
35 env.exec(code.Bytes())
36
37
38 require.Equal(t, nativeCallStatusCodeCallGoHostFunction, env.compilerStatus())
39
40 require.Equal(t, f.moduleInstance, env.ce.exitContext.callerModuleInstance)
41
42
43 require.NotEqual(t, uintptr(0), uintptr(env.ce.returnAddress))
44 nativecall(env.ce.returnAddress, env.callEngine(), env.module())
45
46
47 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
48 }
49
50 func TestCompiler_compileLabel(t *testing.T) {
51 label := wazeroir.NewLabel(wazeroir.LabelKindContinuation, 100)
52 for _, expectSkip := range []bool{false, true} {
53 expectSkip := expectSkip
54 t.Run(fmt.Sprintf("expect skip=%v", expectSkip), func(t *testing.T) {
55 env := newCompilerEnvironment()
56 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil)
57
58 if expectSkip {
59
60 actual := compiler.compileLabel(operationPtr(wazeroir.NewOperationLabel(label)))
61 require.True(t, actual)
62 } else {
63 err := compiler.compileBr(operationPtr(wazeroir.NewOperationBr(label)))
64 require.NoError(t, err)
65 actual := compiler.compileLabel(operationPtr(wazeroir.NewOperationLabel(label)))
66 require.False(t, actual)
67 }
68 })
69 }
70 }
71
72 func TestCompiler_compileBrIf(t *testing.T) {
73 unreachableStatus, thenLabelExitStatus, elseLabelExitStatus := nativeCallStatusCodeUnreachable, nativeCallStatusCodeUnreachable+1, nativeCallStatusCodeUnreachable+2
74 thenBranchTarget := wazeroir.NewLabel(wazeroir.LabelKindHeader, 1)
75 elseBranchTarget := wazeroir.NewLabel(wazeroir.LabelKindHeader, 2)
76
77 tests := []struct {
78 name string
79 setupFunc func(t *testing.T, compiler compilerImpl, shouldGoElse bool)
80 }{
81 {
82 name: "cond on register",
83 setupFunc: func(t *testing.T, compiler compilerImpl, shouldGoElse bool) {
84 val := uint32(1)
85 if shouldGoElse {
86 val = 0
87 }
88 err := compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(val)))
89 require.NoError(t, err)
90 },
91 },
92 {
93 name: "LS",
94 setupFunc: func(t *testing.T, compiler compilerImpl, shouldGoElse bool) {
95 x1, x2 := uint32(1), uint32(2)
96 if shouldGoElse {
97 x2, x1 = x1, x2
98 }
99 requirePushTwoInt32Consts(t, x1, x2, compiler)
100
101 err := compiler.compileLe(operationPtr(wazeroir.NewOperationLe(wazeroir.SignedTypeUint32)))
102 require.NoError(t, err)
103 },
104 },
105 {
106 name: "LE",
107 setupFunc: func(t *testing.T, compiler compilerImpl, shouldGoElse bool) {
108 x1, x2 := uint32(1), uint32(2)
109 if shouldGoElse {
110 x2, x1 = x1, x2
111 }
112 requirePushTwoInt32Consts(t, x1, x2, compiler)
113
114 err := compiler.compileLe(operationPtr(wazeroir.NewOperationLe(wazeroir.SignedTypeInt32)))
115 require.NoError(t, err)
116 },
117 },
118 {
119 name: "HS",
120 setupFunc: func(t *testing.T, compiler compilerImpl, shouldGoElse bool) {
121 x1, x2 := uint32(2), uint32(1)
122 if shouldGoElse {
123 x2, x1 = x1, x2
124 }
125 requirePushTwoInt32Consts(t, x1, x2, compiler)
126
127 err := compiler.compileGe(operationPtr(wazeroir.NewOperationGe(wazeroir.SignedTypeUint32)))
128 require.NoError(t, err)
129 },
130 },
131 {
132 name: "GE",
133 setupFunc: func(t *testing.T, compiler compilerImpl, shouldGoElse bool) {
134 x1, x2 := uint32(2), uint32(1)
135 if shouldGoElse {
136 x2, x1 = x1, x2
137 }
138 requirePushTwoInt32Consts(t, x1, x2, compiler)
139
140 err := compiler.compileGe(operationPtr(wazeroir.NewOperationGe(wazeroir.SignedTypeInt32)))
141 require.NoError(t, err)
142 },
143 },
144 {
145 name: "HI",
146 setupFunc: func(t *testing.T, compiler compilerImpl, shouldGoElse bool) {
147 x1, x2 := uint32(2), uint32(1)
148 if shouldGoElse {
149 x2, x1 = x1, x2
150 }
151 requirePushTwoInt32Consts(t, x1, x2, compiler)
152
153 err := compiler.compileGt(operationPtr(wazeroir.NewOperationGt(wazeroir.SignedTypeUint32)))
154 require.NoError(t, err)
155 },
156 },
157 {
158 name: "GT",
159 setupFunc: func(t *testing.T, compiler compilerImpl, shouldGoElse bool) {
160 x1, x2 := uint32(2), uint32(1)
161 if shouldGoElse {
162 x2, x1 = x1, x2
163 }
164 requirePushTwoInt32Consts(t, x1, x2, compiler)
165
166 err := compiler.compileGt(operationPtr(wazeroir.NewOperationGt(wazeroir.SignedTypeInt32)))
167 require.NoError(t, err)
168 },
169 },
170 {
171 name: "LO",
172 setupFunc: func(t *testing.T, compiler compilerImpl, shouldGoElse bool) {
173 x1, x2 := uint32(1), uint32(2)
174 if shouldGoElse {
175 x2, x1 = x1, x2
176 }
177 requirePushTwoInt32Consts(t, x1, x2, compiler)
178
179 err := compiler.compileLt(operationPtr(wazeroir.NewOperationLt(wazeroir.SignedTypeUint32)))
180 require.NoError(t, err)
181 },
182 },
183 {
184 name: "LT",
185 setupFunc: func(t *testing.T, compiler compilerImpl, shouldGoElse bool) {
186 x1, x2 := uint32(1), uint32(2)
187 if shouldGoElse {
188 x2, x1 = x1, x2
189 }
190 requirePushTwoInt32Consts(t, x1, x2, compiler)
191
192 err := compiler.compileLt(operationPtr(wazeroir.NewOperationLt(wazeroir.SignedTypeInt32)))
193 require.NoError(t, err)
194 },
195 },
196 {
197 name: "MI",
198 setupFunc: func(t *testing.T, compiler compilerImpl, shouldGoElse bool) {
199 x1, x2 := float32(1), float32(2)
200 if shouldGoElse {
201 x2, x1 = x1, x2
202 }
203 requirePushTwoFloat32Consts(t, x1, x2, compiler)
204
205 err := compiler.compileLt(operationPtr(wazeroir.NewOperationLt(wazeroir.SignedTypeFloat32)))
206 require.NoError(t, err)
207 },
208 },
209 {
210 name: "EQ",
211 setupFunc: func(t *testing.T, compiler compilerImpl, shouldGoElse bool) {
212 x1, x2 := uint32(1), uint32(1)
213 if shouldGoElse {
214 x2++
215 }
216 requirePushTwoInt32Consts(t, x1, x2, compiler)
217 err := compiler.compileEq(operationPtr(wazeroir.NewOperationEq(wazeroir.UnsignedTypeI32)))
218 require.NoError(t, err)
219 },
220 },
221 {
222 name: "NE",
223 setupFunc: func(t *testing.T, compiler compilerImpl, shouldGoElse bool) {
224 x1, x2 := uint32(1), uint32(2)
225 if shouldGoElse {
226 x2 = x1
227 }
228 requirePushTwoInt32Consts(t, x1, x2, compiler)
229 err := compiler.compileNe(operationPtr(wazeroir.NewOperationNe(wazeroir.UnsignedTypeI32)))
230 require.NoError(t, err)
231 },
232 },
233 }
234
235 for _, tt := range tests {
236 tc := tt
237 t.Run(tc.name, func(t *testing.T) {
238 for _, shouldGoToElse := range []bool{false, true} {
239 shouldGoToElse := shouldGoToElse
240 t.Run(fmt.Sprintf("should_goto_else=%v", shouldGoToElse), func(t *testing.T) {
241 env := newCompilerEnvironment()
242 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil)
243 err := compiler.compilePreamble()
244 require.NoError(t, err)
245
246 tc.setupFunc(t, compiler, shouldGoToElse)
247 requireRuntimeLocationStackPointerEqual(t, uint64(1), compiler)
248
249 err = compiler.compileBrIf(operationPtr(wazeroir.NewOperationBrIf(thenBranchTarget, elseBranchTarget, wazeroir.NopInclusiveRange)))
250 require.NoError(t, err)
251 compiler.compileExitFromNativeCode(unreachableStatus)
252
253
254 skip := compiler.compileLabel(operationPtr(wazeroir.NewOperationLabel(thenBranchTarget)))
255 require.False(t, skip)
256 compiler.compileExitFromNativeCode(thenLabelExitStatus)
257
258
259 skip = compiler.compileLabel(operationPtr(wazeroir.NewOperationLabel(elseBranchTarget)))
260 require.False(t, skip)
261 compiler.compileExitFromNativeCode(elseLabelExitStatus)
262
263 code := asm.CodeSegment{}
264 defer func() { require.NoError(t, code.Unmap()) }()
265
266 _, err = compiler.compile(code.NextCodeSection())
267 require.NoError(t, err)
268
269
270
271
272
273
274
275
276
277
278
279
280
281 env.exec(code.Bytes())
282 require.NotEqual(t, unreachableStatus, env.compilerStatus())
283 if shouldGoToElse {
284 require.Equal(t, elseLabelExitStatus, env.compilerStatus())
285 } else {
286 require.Equal(t, thenLabelExitStatus, env.compilerStatus())
287 }
288 })
289 }
290 })
291 }
292 }
293
294 func TestCompiler_compileBrTable(t *testing.T) {
295 requireRunAndExpectedValueReturned := func(t *testing.T, env *compilerEnv, c compilerImpl, expValue uint32) {
296
297 for returnValue := uint32(0); returnValue < 7; returnValue++ {
298 label := wazeroir.NewLabel(wazeroir.LabelKindHeader, returnValue)
299 err := c.compileBr(operationPtr(wazeroir.NewOperationBr(label)))
300 require.NoError(t, err)
301 _ = c.compileLabel(operationPtr(wazeroir.NewOperationLabel(label)))
302 _ = c.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(uint32(label.FrameID()))))
303 err = c.compileReturnFunction()
304 require.NoError(t, err)
305 }
306
307 code := asm.CodeSegment{}
308 defer func() { require.NoError(t, code.Unmap()) }()
309
310
311 _, err := c.compile(code.NextCodeSection())
312 require.NoError(t, err)
313 env.exec(code.Bytes())
314
315
316 require.Equal(t, uint64(1), env.stackPointer())
317 require.Equal(t, expValue, env.stackTopAsUint32())
318 }
319
320 getBranchLabelFromFrameID := func(frameid uint32) uint64 {
321 return uint64(wazeroir.NewLabel(wazeroir.LabelKindHeader, frameid))
322 }
323
324 tests := []struct {
325 name string
326 index int64
327 o *wazeroir.UnionOperation
328 expectedValue uint32
329 }{
330 {
331 name: "only default with index 0",
332 o: operationPtr(wazeroir.NewOperationBrTable([]uint64{
333 getBranchLabelFromFrameID(6),
334 wazeroir.NopInclusiveRange.AsU64(),
335 })),
336 index: 0,
337 expectedValue: 6,
338 },
339 {
340 name: "only default with index 100",
341 o: operationPtr(wazeroir.NewOperationBrTable([]uint64{
342 getBranchLabelFromFrameID(6),
343 wazeroir.NopInclusiveRange.AsU64(),
344 })),
345 index: 100,
346 expectedValue: 6,
347 },
348 {
349 name: "select default with targets and good index",
350 o: operationPtr(wazeroir.NewOperationBrTable([]uint64{
351 getBranchLabelFromFrameID(1),
352 wazeroir.NopInclusiveRange.AsU64(),
353 getBranchLabelFromFrameID(2),
354 wazeroir.NopInclusiveRange.AsU64(),
355 getBranchLabelFromFrameID(6),
356 wazeroir.NopInclusiveRange.AsU64(),
357 })),
358 index: 3,
359 expectedValue: 6,
360 },
361 {
362 name: "select default with targets and huge index",
363 o: operationPtr(wazeroir.NewOperationBrTable([]uint64{
364 getBranchLabelFromFrameID(1),
365 wazeroir.NopInclusiveRange.AsU64(),
366 getBranchLabelFromFrameID(2),
367 wazeroir.NopInclusiveRange.AsU64(),
368 getBranchLabelFromFrameID(6),
369 wazeroir.NopInclusiveRange.AsU64(),
370 },
371 )),
372 index: 100000,
373 expectedValue: 6,
374 },
375 {
376 name: "select first with two targets",
377 o: operationPtr(wazeroir.NewOperationBrTable([]uint64{
378 getBranchLabelFromFrameID(1),
379 wazeroir.NopInclusiveRange.AsU64(),
380 getBranchLabelFromFrameID(2),
381 wazeroir.NopInclusiveRange.AsU64(),
382 getBranchLabelFromFrameID(5),
383 wazeroir.NopInclusiveRange.AsU64(),
384 })),
385 index: 0,
386 expectedValue: 1,
387 },
388 {
389 name: "select last with two targets",
390 o: operationPtr(wazeroir.NewOperationBrTable([]uint64{
391 getBranchLabelFromFrameID(1),
392 wazeroir.NopInclusiveRange.AsU64(),
393 getBranchLabelFromFrameID(2),
394 wazeroir.NopInclusiveRange.AsU64(),
395 getBranchLabelFromFrameID(6),
396 wazeroir.NopInclusiveRange.AsU64(),
397 })),
398 index: 1,
399 expectedValue: 2,
400 },
401 {
402 name: "select first with five targets",
403 o: operationPtr(wazeroir.NewOperationBrTable([]uint64{
404 getBranchLabelFromFrameID(1),
405 wazeroir.NopInclusiveRange.AsU64(),
406 getBranchLabelFromFrameID(2),
407 wazeroir.NopInclusiveRange.AsU64(),
408 getBranchLabelFromFrameID(3),
409 wazeroir.NopInclusiveRange.AsU64(),
410 getBranchLabelFromFrameID(4),
411 wazeroir.NopInclusiveRange.AsU64(),
412 getBranchLabelFromFrameID(5),
413 wazeroir.NopInclusiveRange.AsU64(),
414 getBranchLabelFromFrameID(5),
415 wazeroir.NopInclusiveRange.AsU64(),
416 })),
417 index: 0,
418 expectedValue: 1,
419 },
420 {
421 name: "select middle with five targets",
422 o: operationPtr(wazeroir.NewOperationBrTable([]uint64{
423 getBranchLabelFromFrameID(1),
424 wazeroir.NopInclusiveRange.AsU64(),
425 getBranchLabelFromFrameID(2),
426 wazeroir.NopInclusiveRange.AsU64(),
427 getBranchLabelFromFrameID(3),
428 wazeroir.NopInclusiveRange.AsU64(),
429 getBranchLabelFromFrameID(4),
430 wazeroir.NopInclusiveRange.AsU64(),
431 getBranchLabelFromFrameID(5),
432 wazeroir.NopInclusiveRange.AsU64(),
433 getBranchLabelFromFrameID(5),
434 wazeroir.NopInclusiveRange.AsU64(),
435 })),
436 index: 2,
437 expectedValue: 3,
438 },
439 {
440 name: "select last with five targets",
441 o: operationPtr(wazeroir.NewOperationBrTable([]uint64{
442 getBranchLabelFromFrameID(1),
443 wazeroir.NopInclusiveRange.AsU64(),
444 getBranchLabelFromFrameID(2),
445 wazeroir.NopInclusiveRange.AsU64(),
446 getBranchLabelFromFrameID(3),
447 wazeroir.NopInclusiveRange.AsU64(),
448 getBranchLabelFromFrameID(4),
449 wazeroir.NopInclusiveRange.AsU64(),
450 getBranchLabelFromFrameID(5),
451 wazeroir.NopInclusiveRange.AsU64(),
452 getBranchLabelFromFrameID(5),
453 wazeroir.NopInclusiveRange.AsU64(),
454 })),
455 index: 4,
456 expectedValue: 5,
457 },
458 }
459
460 for _, tt := range tests {
461 tc := tt
462 t.Run(tc.name, func(t *testing.T) {
463 env := newCompilerEnvironment()
464 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil)
465
466 err := compiler.compilePreamble()
467 require.NoError(t, err)
468
469 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(uint32(tc.index))))
470 require.NoError(t, err)
471
472 err = compiler.compileBrTable(tc.o)
473 require.NoError(t, err)
474
475 require.Zero(t, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
476
477 requireRunAndExpectedValueReturned(t, env, compiler, tc.expectedValue)
478 })
479 }
480 }
481
482 func requirePushTwoInt32Consts(t *testing.T, x1, x2 uint32, compiler compilerImpl) {
483 err := compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(x1)))
484 require.NoError(t, err)
485 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(x2)))
486 require.NoError(t, err)
487 }
488
489 func requirePushTwoFloat32Consts(t *testing.T, x1, x2 float32, compiler compilerImpl) {
490 err := compiler.compileConstF32(operationPtr(wazeroir.NewOperationConstF32(x1)))
491 require.NoError(t, err)
492 err = compiler.compileConstF32(operationPtr(wazeroir.NewOperationConstF32(x2)))
493 require.NoError(t, err)
494 }
495
496 func TestCompiler_compileBr(t *testing.T) {
497 t.Run("return", func(t *testing.T) {
498 env := newCompilerEnvironment()
499 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil)
500 err := compiler.compilePreamble()
501 require.NoError(t, err)
502
503
504 err = compiler.compileBr(operationPtr(wazeroir.NewOperationBr(wazeroir.NewLabel(wazeroir.LabelKindReturn, 0))))
505 require.NoError(t, err)
506
507 code := asm.CodeSegment{}
508 defer func() { require.NoError(t, code.Unmap()) }()
509
510
511
512 _, err = compiler.compile(code.NextCodeSection())
513 require.NoError(t, err)
514 env.exec(code.Bytes())
515
516 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
517 })
518 t.Run("back-and-forth br", func(t *testing.T) {
519 env := newCompilerEnvironment()
520 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil)
521 err := compiler.compilePreamble()
522 require.NoError(t, err)
523
524
525 forwardLabel := wazeroir.NewLabel(wazeroir.LabelKindHeader, 0)
526 err = compiler.compileBr(operationPtr(wazeroir.NewOperationBr(forwardLabel)))
527 require.NoError(t, err)
528
529
530 compiler.compileExitFromNativeCode(nativeCallStatusCodeUnreachable)
531 require.NoError(t, err)
532
533 exitLabel := wazeroir.NewLabel(wazeroir.LabelKindHeader, 1)
534 err = compiler.compileBr(operationPtr(wazeroir.NewOperationBr(exitLabel)))
535 require.NoError(t, err)
536
537
538 skip := compiler.compileLabel(operationPtr(wazeroir.NewOperationLabel(exitLabel)))
539 require.False(t, skip)
540 compiler.compileExitFromNativeCode(nativeCallStatusCodeReturned)
541 require.NoError(t, err)
542
543
544 skip = compiler.compileLabel(operationPtr(wazeroir.NewOperationLabel(forwardLabel)))
545 require.False(t, skip)
546 err = compiler.compileBr(operationPtr(wazeroir.NewOperationBr(exitLabel)))
547 require.NoError(t, err)
548
549 code := asm.CodeSegment{}
550 defer func() { require.NoError(t, code.Unmap()) }()
551
552 _, err = compiler.compile(code.NextCodeSection())
553 require.NoError(t, err)
554
555
556
557
558
559
560
561
562
563
564
565
566
567 env.exec(code.Bytes())
568 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
569 })
570 }
571
572 func TestCompiler_compileCallIndirect(t *testing.T) {
573 t.Run("out of bounds", func(t *testing.T) {
574 env := newCompilerEnvironment()
575 env.addTable(&wasm.TableInstance{References: make([]wasm.Reference, 10)})
576 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{
577 Types: []wasm.FunctionType{{}},
578 HasTable: true,
579 })
580 err := compiler.compilePreamble()
581 require.NoError(t, err)
582
583 targetOperation := operationPtr(wazeroir.NewOperationCallIndirect(0, 0))
584
585
586 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(10)))
587 require.NoError(t, err)
588
589 err = compiler.compileCallIndirect(targetOperation)
590 require.NoError(t, err)
591
592
593 compiler.compileExitFromNativeCode(nativeCallStatusCodeUnreachable)
594
595 code := asm.CodeSegment{}
596 defer func() { require.NoError(t, code.Unmap()) }()
597
598
599 _, err = compiler.compile(code.NextCodeSection())
600 require.NoError(t, err)
601 env.exec(code.Bytes())
602
603 require.Equal(t, nativeCallStatusCodeInvalidTableAccess, env.compilerStatus())
604 })
605
606 t.Run("uninitialized", func(t *testing.T) {
607 env := newCompilerEnvironment()
608 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{
609 Types: []wasm.FunctionType{{}},
610 HasTable: true,
611 })
612 err := compiler.compilePreamble()
613 require.NoError(t, err)
614
615 targetOperation := operationPtr(wazeroir.NewOperationCallIndirect(0, 0))
616 targetOffset := operationPtr(wazeroir.NewOperationConstI32(uint32(0)))
617
618
619 table := make([]wasm.Reference, 10)
620 env.addTable(&wasm.TableInstance{References: table})
621 env.module().TypeIDs = make([]wasm.FunctionTypeID, 10)
622
623
624 err = compiler.compileConstI32(targetOffset)
625 require.NoError(t, err)
626 err = compiler.compileCallIndirect(targetOperation)
627 require.NoError(t, err)
628
629
630 compiler.compileExitFromNativeCode(nativeCallStatusCodeUnreachable)
631 require.NoError(t, err)
632
633 code := asm.CodeSegment{}
634 defer func() { require.NoError(t, code.Unmap()) }()
635
636
637 _, err = compiler.compile(code.NextCodeSection())
638 require.NoError(t, err)
639 env.exec(code.Bytes())
640
641 require.Equal(t, nativeCallStatusCodeInvalidTableAccess, env.compilerStatus())
642 })
643
644 t.Run("type not match", func(t *testing.T) {
645 env := newCompilerEnvironment()
646 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{
647 Types: []wasm.FunctionType{{}},
648 HasTable: true,
649 })
650 err := compiler.compilePreamble()
651 require.NoError(t, err)
652
653 targetOperation := operationPtr(wazeroir.NewOperationCallIndirect(0, 0))
654 targetOffset := operationPtr(wazeroir.NewOperationConstI32(uint32(0)))
655 env.module().TypeIDs = []wasm.FunctionTypeID{1000}
656
657
658 table := make([]wasm.Reference, 10)
659 env.addTable(&wasm.TableInstance{References: table})
660
661 cf := &function{typeID: 50}
662 table[0] = uintptr(unsafe.Pointer(cf))
663
664
665 err = compiler.compileConstI32(targetOffset)
666 require.NoError(t, err)
667
668
669 require.NoError(t, compiler.compileCallIndirect(targetOperation))
670
671
672 compiler.compileExitFromNativeCode(nativeCallStatusCodeUnreachable)
673 require.NoError(t, err)
674
675 code := asm.CodeSegment{}
676 defer func() { require.NoError(t, code.Unmap()) }()
677
678
679 _, err = compiler.compile(code.NextCodeSection())
680 require.NoError(t, err)
681 env.exec(code.Bytes())
682
683 require.Equal(t, nativeCallStatusCodeTypeMismatchOnIndirectCall.String(), env.compilerStatus().String())
684 })
685
686 t.Run("ok", func(t *testing.T) {
687 targetType := wasm.FunctionType{
688 Results: []wasm.ValueType{wasm.ValueTypeI32},
689 ResultNumInUint64: 1,
690 }
691 const typeIndex = 0
692 targetTypeID := wasm.FunctionTypeID(10)
693 operation := operationPtr(wazeroir.NewOperationCallIndirect(typeIndex, 0))
694
695 table := make([]wasm.Reference, 10)
696 env := newCompilerEnvironment()
697 env.addTable(&wasm.TableInstance{References: table})
698
699
700
701 env.module().TypeIDs = make([]wasm.FunctionTypeID, 100)
702 env.module().TypeIDs[typeIndex] = targetTypeID
703 env.module().Engine = &moduleEngine{functions: []function{}}
704
705 me := env.moduleEngine()
706 me.functions = make([]function, len(table))
707 for i := 0; i < len(table); i++ {
708
709
710 expectedReturnValue := uint32(i * 1000)
711
712 compiler := env.requireNewCompiler(t, &targetType, newCompiler, &wazeroir.CompilationResult{})
713 err := compiler.compilePreamble()
714 require.NoError(t, err)
715 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(expectedReturnValue)))
716 require.NoError(t, err)
717
718 requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
719
720 err = compiler.compileSet(operationPtr(wazeroir.NewOperationSet(int(compiler.runtimeValueLocationStack().sp-1), false)))
721 require.NoError(t, err)
722 err = compiler.compileReturnFunction()
723 require.NoError(t, err)
724
725 code := asm.CodeSegment{}
726 defer func() { require.NoError(t, code.Unmap()) }()
727
728 _, err = compiler.compile(code.NextCodeSection())
729 require.NoError(t, err)
730
731 makeExecutable(code.Bytes())
732
733
734
735 me.functions[i] = function{
736 codeInitialAddress: uintptr(unsafe.Pointer(&code.Bytes()[0])),
737 moduleInstance: env.moduleInstance,
738 typeID: targetTypeID,
739 }
740 table[i] = uintptr(unsafe.Pointer(&me.functions[i]))
741 }
742
743
744 for i := 1; i < len(table); i++ {
745 expectedReturnValue := uint32(i * 1000)
746 t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
747 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
748 &wazeroir.CompilationResult{
749 Types: []wasm.FunctionType{targetType},
750 HasTable: true,
751 },
752 )
753 err := compiler.compilePreamble()
754 require.NoError(t, err)
755
756
757 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(uint32(i))))
758 require.NoError(t, err)
759
760
761 requireRuntimeLocationStackPointerEqual(t, 1, compiler)
762
763 require.NoError(t, compiler.compileCallIndirect(operation))
764
765
766
767 requireRuntimeLocationStackPointerEqual(t, 1, compiler)
768
769 err = compiler.compileReturnFunction()
770 require.NoError(t, err)
771
772 code := asm.CodeSegment{}
773 defer func() { require.NoError(t, code.Unmap()) }()
774
775
776 _, err = compiler.compile(code.NextCodeSection())
777 require.NoError(t, err)
778 env.exec(code.Bytes())
779
780 require.Equal(t, nativeCallStatusCodeReturned.String(), env.compilerStatus().String())
781 require.Equal(t, uint64(1), env.stackPointer())
782 require.Equal(t, expectedReturnValue, uint32(env.ce.popValue()))
783 })
784 }
785 })
786 }
787
788
789
790 func TestCompiler_callIndirect_largeTypeIndex(t *testing.T) {
791 env := newCompilerEnvironment()
792 table := make([]wasm.Reference, 1)
793 env.addTable(&wasm.TableInstance{References: table})
794
795
796 const typeIndex, typeID = 12345, 0
797 operation := operationPtr(wazeroir.NewOperationCallIndirect(typeIndex, 0))
798 env.module().TypeIDs = make([]wasm.FunctionTypeID, typeIndex+1)
799 env.module().TypeIDs[typeIndex] = typeID
800 env.module().Engine = &moduleEngine{functions: []function{}}
801
802 types := make([]wasm.FunctionType, typeIndex+1)
803 types[typeIndex] = wasm.FunctionType{}
804
805 code1 := asm.CodeSegment{}
806 code2 := asm.CodeSegment{}
807 defer func() {
808 require.NoError(t, code1.Unmap())
809 require.NoError(t, code2.Unmap())
810 }()
811
812 me := env.moduleEngine()
813 {
814 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil)
815 err := compiler.compilePreamble()
816 require.NoError(t, err)
817 err = compiler.compileReturnFunction()
818 require.NoError(t, err)
819
820 _, err = compiler.compile(code1.NextCodeSection())
821 require.NoError(t, err)
822
823 makeExecutable(code1.Bytes())
824 f := function{
825 parent: &compiledFunction{parent: &compiledCode{executable: code1}},
826 codeInitialAddress: uintptr(unsafe.Pointer(&code1.Bytes()[0])),
827 moduleInstance: env.moduleInstance,
828 }
829 me.functions = append(me.functions, f)
830 table[0] = uintptr(unsafe.Pointer(&f))
831 }
832
833 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{
834 Types: types,
835 HasTable: true,
836 })
837 err := compiler.compilePreamble()
838 require.NoError(t, err)
839
840 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(0)))
841 require.NoError(t, err)
842
843 require.NoError(t, compiler.compileCallIndirect(operation))
844
845 err = compiler.compileReturnFunction()
846 require.NoError(t, err)
847
848
849 _, err = compiler.compile(code2.NextCodeSection())
850 require.NoError(t, err)
851 env.exec(code2.Bytes())
852 }
853
854 func TestCompiler_compileCall(t *testing.T) {
855 env := newCompilerEnvironment()
856 me := env.moduleEngine()
857 expectedValue := uint32(0)
858
859
860 const numCalls = 3
861 targetFunctionType := wasm.FunctionType{
862 Params: []wasm.ValueType{wasm.ValueTypeI32},
863 Results: []wasm.ValueType{wasm.ValueTypeI32},
864 ParamNumInUint64: 1, ResultNumInUint64: 1,
865 }
866
867 for i := 0; i < numCalls; i++ {
868
869 addTargetValue := uint32(100 + i)
870 expectedValue += addTargetValue
871 compiler := env.requireNewCompiler(t, &targetFunctionType, newCompiler, &wazeroir.CompilationResult{})
872
873 err := compiler.compilePreamble()
874 require.NoError(t, err)
875
876 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(addTargetValue)))
877 require.NoError(t, err)
878
879 err = compiler.compilePick(operationPtr(wazeroir.NewOperationPick(int(compiler.runtimeValueLocationStack().sp-1), false)))
880 require.NoError(t, err)
881
882 err = compiler.compileAdd(operationPtr(wazeroir.NewOperationAdd(wazeroir.UnsignedTypeI32)))
883 require.NoError(t, err)
884
885 err = compiler.compileSet(operationPtr(wazeroir.NewOperationSet(int(compiler.runtimeValueLocationStack().sp-1), false)))
886 require.NoError(t, err)
887
888 err = compiler.compileReturnFunction()
889 require.NoError(t, err)
890
891 code := asm.CodeSegment{}
892 defer func() { require.NoError(t, code.Unmap()) }()
893
894 _, err = compiler.compile(code.NextCodeSection())
895 require.NoError(t, err)
896
897 makeExecutable(code.Bytes())
898 me.functions = append(me.functions, function{
899 parent: &compiledFunction{parent: &compiledCode{executable: code}},
900 codeInitialAddress: uintptr(unsafe.Pointer(&code.Bytes()[0])),
901 moduleInstance: env.moduleInstance,
902 })
903 }
904
905
906 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{
907 Functions: make([]uint32, numCalls),
908 Types: []wasm.FunctionType{targetFunctionType},
909 })
910
911 err := compiler.compilePreamble()
912 require.NoError(t, err)
913
914 const initialValue = 100
915 expectedValue += initialValue
916 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(1234)))
917 require.NoError(t, err)
918 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(initialValue)))
919 require.NoError(t, err)
920
921
922 for i := 0; i < numCalls; i++ {
923 err = compiler.compileCall(operationPtr(wazeroir.NewOperationCall(1)))
924 require.NoError(t, err)
925 }
926
927
928 err = compiler.compileReturnFunction()
929 require.NoError(t, err)
930
931 code := asm.CodeSegment{}
932 defer func() { require.NoError(t, code.Unmap()) }()
933
934 _, err = compiler.compile(code.NextCodeSection())
935 require.NoError(t, err)
936 env.exec(code.Bytes())
937
938
939 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
940 require.Equal(t, uint64(0), env.stackBasePointer())
941 require.Equal(t, uint64(2), env.stackPointer())
942 require.Equal(t, expectedValue, env.stackTopAsUint32())
943 }
944
View as plain text