1 package compiler
2
3 import (
4 "context"
5 "errors"
6 "fmt"
7 "runtime"
8 "testing"
9 "unsafe"
10
11 "github.com/tetratelabs/wazero/api"
12 "github.com/tetratelabs/wazero/experimental"
13 "github.com/tetratelabs/wazero/internal/bitpack"
14 "github.com/tetratelabs/wazero/internal/platform"
15 "github.com/tetratelabs/wazero/internal/testing/require"
16 "github.com/tetratelabs/wazero/internal/wasm"
17 )
18
19
20 var testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary")
21
22
23 func requireSupportedOSArch(t *testing.T) {
24 if !platform.CompilerSupported() {
25 t.Skip()
26 }
27 }
28
29 type fakeFinalizer map[*compiledModule]func(module *compiledModule)
30
31 func (f fakeFinalizer) setFinalizer(obj interface{}, finalizer interface{}) {
32 cf := obj.(*compiledModule)
33 if _, ok := f[cf]; ok {
34 panic(fmt.Sprintf("BUG: %v already had its finalizer set", cf))
35 }
36 f[cf] = finalizer.(func(*compiledModule))
37 }
38
39 func TestCompiler_CompileModule(t *testing.T) {
40 t.Run("ok", func(t *testing.T) {
41 e := NewEngine(testCtx, api.CoreFeaturesV1, nil).(*engine)
42 ff := fakeFinalizer{}
43 e.setFinalizer = ff.setFinalizer
44
45 okModule := &wasm.Module{
46 TypeSection: []wasm.FunctionType{{}},
47 FunctionSection: []wasm.Index{0, 0, 0, 0},
48 CodeSection: []wasm.Code{
49 {Body: []byte{wasm.OpcodeEnd}},
50 {Body: []byte{wasm.OpcodeEnd}},
51 {Body: []byte{wasm.OpcodeEnd}},
52 {Body: []byte{wasm.OpcodeEnd}},
53 },
54 ID: wasm.ModuleID{},
55 }
56
57 err := e.CompileModule(testCtx, okModule, nil, false)
58 require.NoError(t, err)
59
60
61 err = e.CompileModule(testCtx, okModule, nil, false)
62 require.NoError(t, err)
63
64 compiled, ok := e.codes[okModule.ID]
65 require.True(t, ok)
66 require.Equal(t, len(okModule.FunctionSection), len(compiled.functions))
67
68
69 for k, v := range ff {
70 v(k)
71 }
72 })
73
74 t.Run("fail", func(t *testing.T) {
75 errModule := &wasm.Module{
76 TypeSection: []wasm.FunctionType{{}},
77 FunctionSection: []wasm.Index{0, 0, 0},
78 CodeSection: []wasm.Code{
79 {Body: []byte{wasm.OpcodeEnd}},
80 {Body: []byte{wasm.OpcodeEnd}},
81 {Body: []byte{wasm.OpcodeCall}},
82 },
83 ID: wasm.ModuleID{},
84 }
85
86 e := NewEngine(testCtx, api.CoreFeaturesV1, nil).(*engine)
87 err := e.CompileModule(testCtx, errModule, nil, false)
88 require.EqualError(t, err, "failed to lower func[2]: handling instruction: apply stack failed for call: reading immediates: EOF")
89
90
91 _, ok := e.codes[errModule.ID]
92 require.False(t, ok)
93 })
94 }
95
96 func TestCompiler_Releasecode_Panic(t *testing.T) {
97 captured := require.CapturePanic(func() {
98 releaseCompiledModule(&compiledModule{
99 compiledCode: &compiledCode{
100 executable: makeCodeSegment(1, 2),
101 },
102 })
103 })
104 require.Contains(t, captured.Error(), "compiler: failed to munmap code segment")
105 }
106
107
108
109
110 func TestCompiler_SliceAllocatedOnHeap(t *testing.T) {
111 enabledFeatures := api.CoreFeaturesV1
112 e := newEngine(enabledFeatures, nil)
113 s := wasm.NewStore(enabledFeatures, e)
114
115 const hostModuleName = "env"
116 const hostFnName = "grow_and_shrink_goroutine_stack"
117 hostFn := func() {
118
119
120 callNum := 1000
121 var growGoroutineStack func()
122 growGoroutineStack = func() {
123 if callNum != 0 {
124 callNum--
125 growGoroutineStack()
126 }
127 }
128 growGoroutineStack()
129
130
131
132 runtime.GC()
133 }
134 hm, err := wasm.NewHostModule(
135 hostModuleName,
136 []string{hostFnName},
137 map[string]*wasm.HostFunc{hostFnName: {ExportName: hostFnName, Code: wasm.Code{GoFunc: hostFn}}},
138 enabledFeatures,
139 )
140 require.NoError(t, err)
141
142 err = s.Engine.CompileModule(testCtx, hm, nil, false)
143 require.NoError(t, err)
144
145 typeIDs, err := s.GetFunctionTypeIDs(hm.TypeSection)
146 require.NoError(t, err)
147
148 _, err = s.Instantiate(testCtx, hm, hostModuleName, nil, typeIDs)
149 require.NoError(t, err)
150
151 const stackCorruption = "value_stack_corruption"
152 const callStackCorruption = "call_stack_corruption"
153 const expectedReturnValue = 0x1
154 m := &wasm.Module{
155 ImportFunctionCount: 1,
156 TypeSection: []wasm.FunctionType{
157 {Params: []wasm.ValueType{}, Results: []wasm.ValueType{wasm.ValueTypeI32}, ResultNumInUint64: 1},
158 {Params: []wasm.ValueType{}, Results: []wasm.ValueType{}},
159 },
160 FunctionSection: []wasm.Index{
161 wasm.Index(0),
162 wasm.Index(0),
163 wasm.Index(0),
164 },
165 CodeSection: []wasm.Code{
166 {
167
168 Body: []byte{
169 wasm.OpcodeCall, 0,
170
171
172
173 wasm.OpcodeI32Const, expectedReturnValue,
174 wasm.OpcodeEnd,
175 },
176 },
177 {
178
179 Body: []byte{
180 wasm.OpcodeCall, 3,
181
182
183
184
185
186 wasm.OpcodeCall, 0,
187 wasm.OpcodeI32Const, expectedReturnValue,
188 wasm.OpcodeEnd,
189 },
190 },
191 {Body: []byte{wasm.OpcodeCall, 0, wasm.OpcodeEnd}},
192 },
193 ImportSection: []wasm.Import{{Module: hostModuleName, Name: hostFnName, DescFunc: 1}},
194 ImportPerModule: map[string][]*wasm.Import{
195 hostModuleName: {{Module: hostModuleName, Name: hostFnName, DescFunc: 1}},
196 },
197 ExportSection: []wasm.Export{
198 {Type: wasm.ExternTypeFunc, Index: 1, Name: stackCorruption},
199 {Type: wasm.ExternTypeFunc, Index: 2, Name: callStackCorruption},
200 },
201 Exports: map[string]*wasm.Export{
202 stackCorruption: {Type: wasm.ExternTypeFunc, Index: 1, Name: stackCorruption},
203 callStackCorruption: {Type: wasm.ExternTypeFunc, Index: 2, Name: callStackCorruption},
204 },
205 ID: wasm.ModuleID{1},
206 }
207
208 err = s.Engine.CompileModule(testCtx, m, nil, false)
209 require.NoError(t, err)
210
211 typeIDs, err = s.GetFunctionTypeIDs(m.TypeSection)
212 require.NoError(t, err)
213
214 mi, err := s.Instantiate(testCtx, m, t.Name(), nil, typeIDs)
215 require.NoError(t, err)
216
217 for _, fnName := range []string{stackCorruption, callStackCorruption} {
218 fnName := fnName
219 t.Run(fnName, func(t *testing.T) {
220 ret, err := mi.ExportedFunction(fnName).Call(testCtx)
221 require.NoError(t, err)
222
223 require.Equal(t, uint32(expectedReturnValue), uint32(ret[0]))
224 })
225 }
226 }
227
228 func TestCallEngine_builtinFunctionTableGrow(t *testing.T) {
229 ce := &callEngine{
230 stack: []uint64{
231 0xff,
232 1,
233
234
235 0xffffffff << 32,
236 },
237 stackContext: stackContext{stackPointer: 3},
238 }
239
240 table := &wasm.TableInstance{References: []wasm.Reference{}, Min: 10}
241 ce.builtinFunctionTableGrow([]*wasm.TableInstance{table})
242
243 require.Equal(t, 1, len(table.References))
244 require.Equal(t, uintptr(0xff), table.References[0])
245 }
246
247 func ptrAsUint64(f *function) uint64 {
248 return uint64(uintptr(unsafe.Pointer(f)))
249 }
250
251 func TestCallEngine_deferredOnCall(t *testing.T) {
252 s := &wasm.Module{
253 FunctionSection: []wasm.Index{0, 1, 2},
254 CodeSection: []wasm.Code{{}, {}, {}},
255 TypeSection: []wasm.FunctionType{{}, {}, {}},
256 }
257 f1 := &function{
258 funcType: &wasm.FunctionType{ParamNumInUint64: 2},
259 parent: &compiledFunction{parent: &compiledCode{source: s}, index: 0},
260 }
261 f2 := &function{
262 funcType: &wasm.FunctionType{ParamNumInUint64: 2, ResultNumInUint64: 3},
263 parent: &compiledFunction{parent: &compiledCode{source: s}, index: 1},
264 }
265 f3 := &function{
266 funcType: &wasm.FunctionType{ResultNumInUint64: 1},
267 parent: &compiledFunction{parent: &compiledCode{source: s}, index: 2},
268 }
269
270 ce := &callEngine{
271 stack: []uint64{
272 0xff, 0xff,
273 0, 0, 0, 0,
274 0xcc, 0xcc,
275
276 0xaa, 0xaa, 0xdeadbeaf,
277 0, 0, ptrAsUint64(f1), 0,
278 0xcc, 0xcc, 0xcc,
279
280 0xdeadbeaf,
281 0, 8 << 3, ptrAsUint64(f2), 0,
282 },
283 stackContext: stackContext{
284 stackBasePointerInBytes: 18 << 3,
285 stackPointer: 0xff,
286 },
287 moduleContext: moduleContext{
288 fn: f3,
289 moduleInstance: nil,
290 },
291 }
292
293 beforeRecoverStack := ce.stack
294
295 err := ce.deferredOnCall(context.Background(), &wasm.ModuleInstance{}, errors.New("some error"))
296 require.EqualError(t, err, `some error (recovered by wazero)
297 wasm stack trace:
298 .$2()
299 .$1()
300 .$0()`)
301
302
303
304 require.Equal(t, uint64(0), ce.stackBasePointerInBytes)
305 require.Equal(t, uint64(0), ce.stackPointer)
306 require.Equal(t, nil, ce.moduleInstance)
307 require.Equal(t, beforeRecoverStack, ce.stack)
308
309
310
311 runtime.KeepAlive(f1)
312 runtime.KeepAlive(f2)
313 runtime.KeepAlive(f3)
314 }
315
316 func TestCallEngine_initializeStack(t *testing.T) {
317 const i32 = wasm.ValueTypeI32
318 const stackSize = 10
319 const initialVal = ^uint64(0)
320 tests := []struct {
321 name string
322 funcType *wasm.FunctionType
323 args []uint64
324 expStackPointer uint64
325 expStack [stackSize]uint64
326 }{
327 {
328 name: "no param/result",
329 funcType: &wasm.FunctionType{},
330 expStackPointer: callFrameDataSizeInUint64,
331 expStack: [stackSize]uint64{
332 0, 0, 0,
333 initialVal, initialVal, initialVal, initialVal, initialVal, initialVal, initialVal,
334 },
335 },
336 {
337 name: "no result",
338 funcType: &wasm.FunctionType{
339 Params: []wasm.ValueType{i32, i32},
340 ParamNumInUint64: 2,
341 },
342 args: []uint64{0xdeadbeaf, 0xdeadbeaf},
343 expStackPointer: callFrameDataSizeInUint64 + 2,
344 expStack: [stackSize]uint64{
345 0xdeadbeaf, 0xdeadbeaf,
346 0, 0, 0,
347 initialVal, initialVal, initialVal, initialVal, initialVal,
348 },
349 },
350 {
351 name: "no param",
352 funcType: &wasm.FunctionType{
353 Results: []wasm.ValueType{i32, i32, i32},
354 ResultNumInUint64: 3,
355 },
356 expStackPointer: callFrameDataSizeInUint64 + 3,
357 expStack: [stackSize]uint64{
358 initialVal, initialVal, initialVal,
359 0, 0, 0,
360 initialVal, initialVal, initialVal, initialVal,
361 },
362 },
363 {
364 name: "params > results",
365 funcType: &wasm.FunctionType{
366 Params: []wasm.ValueType{i32, i32, i32, i32, i32},
367 ParamNumInUint64: 5,
368 Results: []wasm.ValueType{i32, i32, i32},
369 ResultNumInUint64: 3,
370 },
371 args: []uint64{0xdeafbeaf, 0xdeafbeaf, 0xdeafbeaf, 0xdeafbeaf, 0xdeafbeaf},
372 expStackPointer: callFrameDataSizeInUint64 + 5,
373 expStack: [stackSize]uint64{
374 0xdeafbeaf, 0xdeafbeaf, 0xdeafbeaf, 0xdeafbeaf, 0xdeafbeaf,
375 0, 0, 0,
376 initialVal, initialVal,
377 },
378 },
379 {
380 name: "params == results",
381 funcType: &wasm.FunctionType{
382 Params: []wasm.ValueType{i32, i32, i32, i32, i32},
383 ParamNumInUint64: 5,
384 Results: []wasm.ValueType{i32, i32, i32, i32, i32},
385 ResultNumInUint64: 5,
386 },
387 args: []uint64{0xdeafbeaf, 0xdeafbeaf, 0xdeafbeaf, 0xdeafbeaf, 0xdeafbeaf},
388 expStackPointer: callFrameDataSizeInUint64 + 5,
389 expStack: [stackSize]uint64{
390 0xdeafbeaf, 0xdeafbeaf, 0xdeafbeaf, 0xdeafbeaf, 0xdeafbeaf,
391 0, 0, 0,
392 initialVal, initialVal,
393 },
394 },
395 {
396 name: "params < results",
397 funcType: &wasm.FunctionType{
398 Params: []wasm.ValueType{i32, i32, i32},
399 ParamNumInUint64: 3,
400 Results: []wasm.ValueType{i32, i32, i32, i32, i32},
401 ResultNumInUint64: 5,
402 },
403 args: []uint64{0xdeafbeaf, 0xdeafbeaf, 0xdeafbeaf},
404 expStackPointer: callFrameDataSizeInUint64 + 5,
405 expStack: [stackSize]uint64{
406 0xdeafbeaf, 0xdeafbeaf, 0xdeafbeaf,
407 initialVal, initialVal,
408 0, 0, 0,
409 initialVal, initialVal,
410 },
411 },
412 }
413
414 for _, tc := range tests {
415 tc := tc
416 t.Run(tc.name, func(t *testing.T) {
417 initialStack := make([]uint64, stackSize)
418 for i := range initialStack {
419 initialStack[i] = initialVal
420 }
421 ce := &callEngine{stack: initialStack}
422 ce.initializeStack(tc.funcType, tc.args)
423 require.Equal(t, tc.expStackPointer, ce.stackPointer)
424 require.Equal(t, tc.expStack[:], ce.stack)
425 })
426 }
427 }
428
429 func Test_callFrameOffset(t *testing.T) {
430 require.Equal(t, 1, callFrameOffset(&wasm.FunctionType{ParamNumInUint64: 0, ResultNumInUint64: 1}))
431 require.Equal(t, 10, callFrameOffset(&wasm.FunctionType{ParamNumInUint64: 5, ResultNumInUint64: 10}))
432 require.Equal(t, 100, callFrameOffset(&wasm.FunctionType{ParamNumInUint64: 50, ResultNumInUint64: 100}))
433 require.Equal(t, 1, callFrameOffset(&wasm.FunctionType{ParamNumInUint64: 1, ResultNumInUint64: 0}))
434 require.Equal(t, 10, callFrameOffset(&wasm.FunctionType{ParamNumInUint64: 10, ResultNumInUint64: 5}))
435 require.Equal(t, 100, callFrameOffset(&wasm.FunctionType{ParamNumInUint64: 100, ResultNumInUint64: 50}))
436 }
437
438 type stackEntry struct {
439 def api.FunctionDefinition
440 }
441
442 func assertStackIterator(t *testing.T, it experimental.StackIterator, expected []stackEntry) {
443 var actual []stackEntry
444 for it.Next() {
445 actual = append(actual, stackEntry{def: it.Function().Definition()})
446 }
447 require.Equal(t, expected, actual)
448 }
449
450 func TestCallEngine_builtinFunctionFunctionListenerBefore(t *testing.T) {
451 currentContext := context.Background()
452
453 f := &function{
454 funcType: &wasm.FunctionType{ParamNumInUint64: 3},
455 parent: &compiledFunction{
456 listener: mockListener{
457 before: func(ctx context.Context, _ api.Module, def api.FunctionDefinition, params []uint64, stackIterator experimental.StackIterator) {
458 require.Equal(t, currentContext, ctx)
459 require.Equal(t, []uint64{2, 3, 4}, params)
460 assertStackIterator(t, stackIterator, []stackEntry{{def: def}})
461 },
462 },
463 index: 0,
464 parent: &compiledCode{source: &wasm.Module{
465 FunctionSection: []wasm.Index{0},
466 CodeSection: []wasm.Code{{}},
467 TypeSection: []wasm.FunctionType{{}},
468 }},
469 },
470 }
471 ce := &callEngine{
472 stack: []uint64{0, 1, 2, 3, 4, 0, 0, 0},
473 stackContext: stackContext{stackBasePointerInBytes: 16},
474 }
475 ce.builtinFunctionFunctionListenerBefore(currentContext, &wasm.ModuleInstance{}, f)
476 }
477
478 func TestCallEngine_builtinFunctionFunctionListenerAfter(t *testing.T) {
479 currentContext := context.Background()
480 f := &function{
481 funcType: &wasm.FunctionType{ResultNumInUint64: 1},
482 parent: &compiledFunction{
483 listener: mockListener{
484 after: func(ctx context.Context, mod api.Module, def api.FunctionDefinition, results []uint64) {
485 require.Equal(t, currentContext, ctx)
486 require.Equal(t, []uint64{5}, results)
487 },
488 },
489 index: 0,
490 parent: &compiledCode{source: &wasm.Module{
491 FunctionSection: []wasm.Index{0},
492 CodeSection: []wasm.Code{{}},
493 TypeSection: []wasm.FunctionType{{}},
494 }},
495 },
496 }
497
498 ce := &callEngine{
499 stack: []uint64{0, 1, 2, 3, 4, 5},
500 stackContext: stackContext{stackBasePointerInBytes: 40},
501 }
502 ce.builtinFunctionFunctionListenerAfter(currentContext, &wasm.ModuleInstance{}, f)
503 }
504
505 type mockListener struct {
506 before func(context.Context, api.Module, api.FunctionDefinition, []uint64, experimental.StackIterator)
507 after func(context.Context, api.Module, api.FunctionDefinition, []uint64)
508 abort func(context.Context, api.Module, api.FunctionDefinition, error)
509 }
510
511 func (m mockListener) Before(ctx context.Context, mod api.Module, def api.FunctionDefinition, params []uint64, stackIterator experimental.StackIterator) {
512 if m.before != nil {
513 m.before(ctx, mod, def, params, stackIterator)
514 }
515 }
516
517 func (m mockListener) After(ctx context.Context, mod api.Module, def api.FunctionDefinition, results []uint64) {
518 if m.after != nil {
519 m.after(ctx, mod, def, results)
520 }
521 }
522
523 func (m mockListener) Abort(ctx context.Context, mod api.Module, def api.FunctionDefinition, err error) {
524 if m.abort != nil {
525 m.abort(ctx, mod, def, err)
526 }
527 }
528
529 func TestFunction_getSourceOffsetInWasmBinary(t *testing.T) {
530 tests := []struct {
531 name string
532 pc, exp uint64
533 codeInitialAddress uintptr
534 srcMap sourceOffsetMap
535 }{
536 {name: "not found", srcMap: sourceOffsetMap{}},
537 {
538 name: "first IR",
539 pc: 4000,
540 codeInitialAddress: 3999,
541 srcMap: sourceOffsetMap{
542 irOperationOffsetsInNativeBinary: bitpack.NewOffsetArray([]uint64{
543 0 , 5, 8, 15,
544 }),
545 irOperationSourceOffsetsInWasmBinary: bitpack.NewOffsetArray([]uint64{
546 10, 100, 800, 12344,
547 }),
548 },
549 exp: 10,
550 },
551 {
552 name: "middle",
553 pc: 100,
554 codeInitialAddress: 90,
555 srcMap: sourceOffsetMap{
556 irOperationOffsetsInNativeBinary: bitpack.NewOffsetArray([]uint64{
557 0, 5, 8 , 15,
558 }),
559 irOperationSourceOffsetsInWasmBinary: bitpack.NewOffsetArray([]uint64{
560 10, 100, 800, 12344,
561 }),
562 },
563 exp: 800,
564 },
565 {
566 name: "last",
567 pc: 9999,
568 codeInitialAddress: 8999,
569 srcMap: sourceOffsetMap{
570 irOperationOffsetsInNativeBinary: bitpack.NewOffsetArray([]uint64{
571 0, 5, 8, 15,
572 }),
573 irOperationSourceOffsetsInWasmBinary: bitpack.NewOffsetArray([]uint64{
574 10, 100, 800, 12344,
575 }),
576 },
577 exp: 12344,
578 },
579 }
580
581 for _, tc := range tests {
582 tc := tc
583 t.Run(tc.name, func(t *testing.T) {
584 f := function{
585 parent: &compiledFunction{sourceOffsetMap: tc.srcMap},
586 codeInitialAddress: tc.codeInitialAddress,
587 }
588
589 actual := f.getSourceOffsetInWasmBinary(tc.pc)
590 require.Equal(t, tc.exp, actual)
591 })
592 }
593 }
594
View as plain text