github.com/tetratelabs/wazero@v1.2.1/internal/testing/enginetest/enginetest.go (about) 1 // Package enginetest contains tests common to any wasm.Engine implementation. Defining these as top-level 2 // functions is less burden than copy/pasting the implementations, while still allowing test caching to operate. 3 // 4 // In simplest case, dispatch: 5 // 6 // func TestModuleEngine_Call(t *testing.T) { 7 // enginetest.RunTestModuleEngineCall(t, NewEngine) 8 // } 9 // 10 // Some tests using the Compiler Engine may need to guard as they use compiled features: 11 // 12 // func TestModuleEngine_Call(t *testing.T) { 13 // requireSupportedOSArch(t) 14 // enginetest.RunTestModuleEngineCall(t, NewEngine) 15 // } 16 // 17 // Note: These tests intentionally avoid using wasm.Store as it is important to know both the dependencies and 18 // the capabilities at the wasm.Engine abstraction. 19 package enginetest 20 21 import ( 22 "context" 23 "debug/dwarf" 24 "errors" 25 "math" 26 "strings" 27 "testing" 28 29 "github.com/tetratelabs/wazero/api" 30 "github.com/tetratelabs/wazero/experimental" 31 "github.com/tetratelabs/wazero/internal/leb128" 32 "github.com/tetratelabs/wazero/internal/testing/require" 33 "github.com/tetratelabs/wazero/internal/u64" 34 "github.com/tetratelabs/wazero/internal/wasm" 35 "github.com/tetratelabs/wazero/internal/wasmdebug" 36 "github.com/tetratelabs/wazero/internal/wasmruntime" 37 ) 38 39 const ( 40 i32, i64 = wasm.ValueTypeI32, wasm.ValueTypeI64 41 ) 42 43 // testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors. 44 var testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary") 45 46 type EngineTester interface { 47 NewEngine(enabledFeatures api.CoreFeatures) wasm.Engine 48 49 ListenerFactory() experimental.FunctionListenerFactory 50 } 51 52 // RunTestEngineMemoryGrowInRecursiveCall ensures that it's safe to grow memory in the recursive Wasm calls. 53 func RunTestEngineMemoryGrowInRecursiveCall(t *testing.T, et EngineTester) { 54 enabledFeatures := api.CoreFeaturesV1 55 e := et.NewEngine(enabledFeatures) 56 s := wasm.NewStore(enabledFeatures, e) 57 58 const hostModuleName = "env" 59 const hostFnName = "grow_memory" 60 var growFn api.Function 61 hm, err := wasm.NewHostModule( 62 hostModuleName, 63 []string{hostFnName}, 64 map[string]*wasm.HostFunc{ 65 hostFnName: { 66 ExportName: hostFnName, 67 Code: wasm.Code{GoFunc: func() { 68 // Does the recursive call into Wasm, which grows memory. 69 _, err := growFn.Call(context.Background()) 70 require.NoError(t, err) 71 }}, 72 }, 73 }, 74 enabledFeatures, 75 ) 76 require.NoError(t, err) 77 78 err = s.Engine.CompileModule(testCtx, hm, nil, false) 79 require.NoError(t, err) 80 81 typeIDs, err := s.GetFunctionTypeIDs(hm.TypeSection) 82 require.NoError(t, err) 83 84 _, err = s.Instantiate(testCtx, hm, hostModuleName, nil, typeIDs) 85 require.NoError(t, err) 86 87 m := &wasm.Module{ 88 ImportFunctionCount: 1, 89 TypeSection: []wasm.FunctionType{{Params: []wasm.ValueType{}, Results: []wasm.ValueType{}}}, 90 FunctionSection: []wasm.Index{0, 0}, 91 CodeSection: []wasm.Code{ 92 { 93 Body: []byte{ 94 // Calls the imported host function, which in turn calls the next in-Wasm function recursively. 95 wasm.OpcodeCall, 0, 96 // Access the memory and this should succeed as we already had memory grown at this point. 97 wasm.OpcodeI32Const, 0, 98 wasm.OpcodeI32Load, 0x2, 0x0, 99 wasm.OpcodeDrop, 100 wasm.OpcodeEnd, 101 }, 102 }, 103 { 104 // Grows memory by 1 page. 105 Body: []byte{wasm.OpcodeI32Const, 1, wasm.OpcodeMemoryGrow, wasm.OpcodeDrop, wasm.OpcodeEnd}, 106 }, 107 }, 108 MemorySection: &wasm.Memory{Max: 1000}, 109 ImportSection: []wasm.Import{{Module: hostModuleName, Name: hostFnName, DescFunc: 0}}, 110 ImportPerModule: map[string][]*wasm.Import{hostModuleName: {{Module: hostModuleName, Name: hostFnName, DescFunc: 0}}}, 111 } 112 m.BuildMemoryDefinitions() 113 114 err = s.Engine.CompileModule(testCtx, m, nil, false) 115 require.NoError(t, err) 116 117 typeIDs, err = s.GetFunctionTypeIDs(m.TypeSection) 118 require.NoError(t, err) 119 120 inst, err := s.Instantiate(testCtx, m, t.Name(), nil, typeIDs) 121 require.NoError(t, err) 122 123 growFn = inst.Engine.NewFunction(2) 124 _, err = inst.Engine.NewFunction(1).Call(context.Background()) 125 require.NoError(t, err) 126 } 127 128 func RunTestEngineNewModuleEngine(t *testing.T, et EngineTester) { 129 e := et.NewEngine(api.CoreFeaturesV1) 130 131 t.Run("error before instantiation", func(t *testing.T) { 132 _, err := e.NewModuleEngine(&wasm.Module{}, nil) 133 require.EqualError(t, err, "source module must be compiled before instantiation") 134 }) 135 } 136 137 func RunTestModuleEngineCall(t *testing.T, et EngineTester) { 138 e := et.NewEngine(api.CoreFeaturesV2) 139 140 // Define a basic function which defines two parameters and two results. 141 // This is used to test results when incorrect arity is used. 142 m := &wasm.Module{ 143 TypeSection: []wasm.FunctionType{ 144 { 145 Params: []wasm.ValueType{i64, i64}, 146 Results: []wasm.ValueType{i64, i64}, 147 ParamNumInUint64: 2, 148 ResultNumInUint64: 2, 149 }, 150 }, 151 FunctionSection: []wasm.Index{0}, 152 CodeSection: []wasm.Code{ 153 {Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeLocalGet, 1, wasm.OpcodeEnd}}, 154 }, 155 } 156 157 listeners := buildFunctionListeners(et.ListenerFactory(), m) 158 err := e.CompileModule(testCtx, m, listeners, false) 159 require.NoError(t, err) 160 161 // To use the function, we first need to add it to a module. 162 module := &wasm.ModuleInstance{ModuleName: t.Name(), TypeIDs: []wasm.FunctionTypeID{0}} 163 164 // Compile the module 165 me, err := e.NewModuleEngine(m, module) 166 require.NoError(t, err) 167 linkModuleToEngine(module, me) 168 169 // Ensure the base case doesn't fail: A single parameter should work as that matches the function signature. 170 const funcIndex = 0 171 ce := me.NewFunction(funcIndex) 172 173 results, err := ce.Call(testCtx, 1, 2) 174 require.NoError(t, err) 175 require.Equal(t, []uint64{1, 2}, results) 176 177 t.Run("errs when not enough parameters", func(t *testing.T) { 178 ce := me.NewFunction(funcIndex) 179 _, err = ce.Call(testCtx) 180 require.EqualError(t, err, "expected 2 params, but passed 0") 181 }) 182 183 t.Run("errs when too many parameters", func(t *testing.T) { 184 ce := me.NewFunction(funcIndex) 185 _, err = ce.Call(testCtx, 1, 2, 3) 186 require.EqualError(t, err, "expected 2 params, but passed 3") 187 }) 188 } 189 190 func RunTestModuleEngineCallWithStack(t *testing.T, et EngineTester) { 191 e := et.NewEngine(api.CoreFeaturesV2) 192 193 // Define a basic function which defines two parameters and two results. 194 // This is used to test results when incorrect arity is used. 195 m := &wasm.Module{ 196 TypeSection: []wasm.FunctionType{ 197 { 198 Params: []wasm.ValueType{i64, i64}, 199 Results: []wasm.ValueType{i64, i64}, 200 ParamNumInUint64: 2, 201 ResultNumInUint64: 2, 202 }, 203 }, 204 FunctionSection: []wasm.Index{0}, 205 CodeSection: []wasm.Code{ 206 {Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeLocalGet, 1, wasm.OpcodeEnd}}, 207 }, 208 } 209 210 listeners := buildFunctionListeners(et.ListenerFactory(), m) 211 err := e.CompileModule(testCtx, m, listeners, false) 212 require.NoError(t, err) 213 214 // To use the function, we first need to add it to a module. 215 module := &wasm.ModuleInstance{ModuleName: t.Name(), TypeIDs: []wasm.FunctionTypeID{0}} 216 217 // Compile the module 218 me, err := e.NewModuleEngine(m, module) 219 require.NoError(t, err) 220 linkModuleToEngine(module, me) 221 222 // Ensure the base case doesn't fail: A single parameter should work as that matches the function signature. 223 const funcIndex = 0 224 ce := me.NewFunction(funcIndex) 225 226 stack := []uint64{1, 2} 227 err = ce.CallWithStack(testCtx, stack) 228 require.NoError(t, err) 229 require.Equal(t, []uint64{1, 2}, stack) 230 231 t.Run("errs when not enough parameters", func(t *testing.T) { 232 ce := me.NewFunction(funcIndex) 233 err = ce.CallWithStack(testCtx, nil) 234 require.EqualError(t, err, "need 2 params, but stack size is 0") 235 }) 236 } 237 238 func RunTestModuleEngineLookupFunction(t *testing.T, et EngineTester) { 239 e := et.NewEngine(api.CoreFeaturesV1) 240 241 mod := &wasm.Module{ 242 TypeSection: []wasm.FunctionType{{}, {Params: []wasm.ValueType{wasm.ValueTypeV128}}}, 243 FunctionSection: []wasm.Index{0, 0, 0}, 244 CodeSection: []wasm.Code{ 245 { 246 Body: []byte{wasm.OpcodeEnd}, 247 }, {Body: []byte{wasm.OpcodeEnd}}, {Body: []byte{wasm.OpcodeEnd}}, 248 }, 249 } 250 251 err := e.CompileModule(testCtx, mod, nil, false) 252 require.NoError(t, err) 253 m := &wasm.ModuleInstance{ 254 TypeIDs: []wasm.FunctionTypeID{0, 1}, 255 } 256 m.Tables = []*wasm.TableInstance{ 257 {Min: 2, References: make([]wasm.Reference, 2), Type: wasm.RefTypeFuncref}, 258 {Min: 2, References: make([]wasm.Reference, 2), Type: wasm.RefTypeExternref}, 259 {Min: 10, References: make([]wasm.Reference, 10), Type: wasm.RefTypeFuncref}, 260 } 261 262 me, err := e.NewModuleEngine(mod, m) 263 require.NoError(t, err) 264 linkModuleToEngine(m, me) 265 266 t.Run("null reference", func(t *testing.T) { 267 _, err := me.LookupFunction(m.Tables[0], m.TypeIDs[0], 0) // offset 0 is not initialized yet. 268 require.Equal(t, wasmruntime.ErrRuntimeInvalidTableAccess, err) 269 _, err = me.LookupFunction(m.Tables[0], m.TypeIDs[0], 1) // offset 1 is not initialized yet. 270 require.Equal(t, wasmruntime.ErrRuntimeInvalidTableAccess, err) 271 }) 272 273 m.Tables[0].References[0] = me.FunctionInstanceReference(2) 274 m.Tables[0].References[1] = me.FunctionInstanceReference(0) 275 276 t.Run("initialized", func(t *testing.T) { 277 f1, err := me.LookupFunction(m.Tables[0], m.TypeIDs[0], 0) // offset 0 is now initialized. 278 require.NoError(t, err) 279 require.Equal(t, wasm.Index(2), f1.Definition().Index()) 280 f2, err := me.LookupFunction(m.Tables[0], m.TypeIDs[0], 1) // offset 1 is now initialized. 281 require.NoError(t, err) 282 require.Equal(t, wasm.Index(0), f2.Definition().Index()) 283 }) 284 285 t.Run("out of range", func(t *testing.T) { 286 _, err := me.LookupFunction(m.Tables[0], m.TypeIDs[0], 100 /* out of range */) 287 require.Equal(t, wasmruntime.ErrRuntimeInvalidTableAccess, err) 288 }) 289 290 t.Run("access to externref table", func(t *testing.T) { 291 _, err := me.LookupFunction(m.Tables[1], /* table[1] has externref type. */ 292 m.TypeIDs[0], 0) 293 require.Equal(t, wasmruntime.ErrRuntimeInvalidTableAccess, err) 294 }) 295 296 t.Run("access to externref table", func(t *testing.T) { 297 _, err := me.LookupFunction(m.Tables[0], /* type mismatch */ 298 m.TypeIDs[1], 0) 299 require.Equal(t, wasmruntime.ErrRuntimeIndirectCallTypeMismatch, err) 300 }) 301 302 m.Tables[2].References[0] = me.FunctionInstanceReference(1) 303 m.Tables[2].References[5] = me.FunctionInstanceReference(2) 304 t.Run("initialized - tables[2]", func(t *testing.T) { 305 f1, err := me.LookupFunction(m.Tables[2], m.TypeIDs[0], 0) 306 require.NoError(t, err) 307 require.Equal(t, wasm.Index(1), f1.Definition().Index()) 308 f2, err := me.LookupFunction(m.Tables[2], m.TypeIDs[0], 5) 309 require.NoError(t, err) 310 require.Equal(t, wasm.Index(2), f2.Definition().Index()) 311 }) 312 } 313 314 func runTestModuleEngineCallHostFnMem(t *testing.T, et EngineTester, readMem *wasm.Code) { 315 e := et.NewEngine(api.CoreFeaturesV1) 316 defer e.Close() 317 importing := setupCallMemTests(t, e, readMem) 318 319 importingMemoryVal := uint64(6) 320 importing.MemoryInstance = &wasm.MemoryInstance{Buffer: u64.LeBytes(importingMemoryVal), Min: 1, Cap: 1, Max: 1} 321 322 tests := []struct { 323 name string 324 fn wasm.Index 325 expected uint64 326 }{ 327 { 328 name: callImportReadMemName, 329 fn: importing.Exports[callImportReadMemName].Index, 330 expected: importingMemoryVal, 331 }, 332 } 333 for _, tt := range tests { 334 tc := tt 335 336 t.Run(tc.name, func(t *testing.T) { 337 ce := importing.Engine.NewFunction(tc.fn) 338 339 results, err := ce.Call(testCtx) 340 require.NoError(t, err) 341 require.Equal(t, tc.expected, results[0]) 342 }) 343 } 344 } 345 346 func RunTestModuleEngineCallHostFn(t *testing.T, et EngineTester) { 347 t.Run("wasm", func(t *testing.T) { 348 runTestModuleEngineCallHostFn(t, et, hostDivByWasm) 349 }) 350 t.Run("go", func(t *testing.T) { 351 runTestModuleEngineCallHostFn(t, et, &hostDivByGo) 352 runTestModuleEngineCallHostFnMem(t, et, &hostReadMemGo) 353 }) 354 } 355 356 func runTestModuleEngineCallHostFn(t *testing.T, et EngineTester, hostDivBy *wasm.Code) { 357 e := et.NewEngine(api.CoreFeaturesV1) 358 defer e.Close() 359 360 imported, importing := setupCallTests(t, e, hostDivBy, et.ListenerFactory()) 361 362 // Ensure the base case doesn't fail: A single parameter should work as that matches the function signature. 363 tests := []struct { 364 name string 365 module *wasm.ModuleInstance 366 fn wasm.Index 367 }{ 368 { 369 name: divByWasmName, 370 module: imported, 371 fn: imported.Exports[divByWasmName].Index, 372 }, 373 { 374 name: callDivByGoName, 375 module: imported, 376 fn: imported.Exports[callDivByGoName].Index, 377 }, 378 { 379 name: callImportCallDivByGoName, 380 module: importing, 381 fn: importing.Exports[callImportCallDivByGoName].Index, 382 }, 383 } 384 for _, tt := range tests { 385 tc := tt 386 387 t.Run(tc.name, func(t *testing.T) { 388 f := tc.fn 389 390 ce := tc.module.Engine.NewFunction(f) 391 392 results, err := ce.Call(testCtx, 1) 393 require.NoError(t, err) 394 require.Equal(t, uint64(1), results[0]) 395 396 results2, err := ce.Call(testCtx, 1) 397 require.NoError(t, err) 398 require.Equal(t, results, results2) 399 400 // Ensure the result slices are unique 401 results[0] = 255 402 require.Equal(t, uint64(1), results2[0]) 403 }) 404 } 405 } 406 407 func RunTestModuleEngine_Call_Errors(t *testing.T, et EngineTester) { 408 e := et.NewEngine(api.CoreFeaturesV1) 409 defer e.Close() 410 411 imported, importing := setupCallTests(t, e, &hostDivByGo, et.ListenerFactory()) 412 413 tests := []struct { 414 name string 415 module *wasm.ModuleInstance 416 fn wasm.Index 417 input []uint64 418 expectedErr string 419 }{ 420 { 421 name: "wasm function not enough parameters", 422 input: []uint64{}, 423 module: imported, 424 fn: imported.Exports[divByWasmName].Index, 425 expectedErr: `expected 1 params, but passed 0`, 426 }, 427 { 428 name: "wasm function too many parameters", 429 input: []uint64{1, 2}, 430 module: imported, 431 fn: imported.Exports[divByWasmName].Index, 432 expectedErr: `expected 1 params, but passed 2`, 433 }, 434 { 435 name: "wasm function panics with wasmruntime.Error", 436 input: []uint64{0}, 437 module: imported, 438 fn: imported.Exports[divByWasmName].Index, 439 expectedErr: `wasm error: integer divide by zero 440 wasm stack trace: 441 imported.div_by.wasm(i32) i32`, 442 }, 443 { 444 name: "wasm calls host function that panics", 445 input: []uint64{math.MaxUint32}, 446 module: imported, 447 fn: imported.Exports[callDivByGoName].Index, 448 expectedErr: `host-function panic (recovered by wazero) 449 wasm stack trace: 450 host.div_by.go(i32) i32 451 imported.call->div_by.go(i32) i32`, 452 }, 453 { 454 name: "wasm calls imported wasm that calls host function panics with runtime.Error", 455 input: []uint64{0}, 456 module: importing, 457 fn: importing.Exports[callImportCallDivByGoName].Index, 458 expectedErr: `runtime error: integer divide by zero (recovered by wazero) 459 wasm stack trace: 460 host.div_by.go(i32) i32 461 imported.call->div_by.go(i32) i32 462 importing.call_import->call->div_by.go(i32) i32`, 463 }, 464 { 465 name: "wasm calls imported wasm that calls host function that panics", 466 input: []uint64{math.MaxUint32}, 467 module: importing, 468 fn: importing.Exports[callImportCallDivByGoName].Index, 469 expectedErr: `host-function panic (recovered by wazero) 470 wasm stack trace: 471 host.div_by.go(i32) i32 472 imported.call->div_by.go(i32) i32 473 importing.call_import->call->div_by.go(i32) i32`, 474 }, 475 { 476 name: "wasm calls imported wasm calls host function panics with runtime.Error", 477 input: []uint64{0}, 478 module: importing, 479 fn: importing.Exports[callImportCallDivByGoName].Index, 480 expectedErr: `runtime error: integer divide by zero (recovered by wazero) 481 wasm stack trace: 482 host.div_by.go(i32) i32 483 imported.call->div_by.go(i32) i32 484 importing.call_import->call->div_by.go(i32) i32`, 485 }, 486 } 487 for _, tt := range tests { 488 tc := tt 489 t.Run(tc.name, func(t *testing.T) { 490 ce := tc.module.Engine.NewFunction(tc.fn) 491 492 _, err := ce.Call(testCtx, tc.input...) 493 require.NotNil(t, err) 494 495 errStr := err.Error() 496 // If this faces a Go runtime error, the error includes the Go stack trace which makes the test unstable, 497 // so we trim them here. 498 if index := strings.Index(errStr, wasmdebug.GoRuntimeErrorTracePrefix); index > -1 { 499 errStr = strings.TrimSpace(errStr[:index]) 500 } 501 require.Equal(t, errStr, tc.expectedErr) 502 503 // Ensure the module still works 504 results, err := ce.Call(testCtx, 1) 505 require.NoError(t, err) 506 require.Equal(t, uint64(1), results[0]) 507 }) 508 } 509 } 510 511 // RunTestModuleEngineBeforeListenerStackIterator tests that the StackIterator provided by the Engine to the Before hook 512 // of the listener is properly able to walk the stack. As an example, it 513 // validates that the following call stack is properly walked: 514 // 515 // 1. f1(2,3,4) [no return, no local] 516 // 2. calls f2(no arg) [1 return, 1 local] 517 // 3. calls f3(5) [1 return, no local] 518 // 4. calls f4(6) [1 return, HOST] 519 func RunTestModuleEngineBeforeListenerStackIterator(t *testing.T, et EngineTester) { 520 e := et.NewEngine(api.CoreFeaturesV2) 521 522 type stackEntry struct { 523 debugName string 524 args []uint64 525 } 526 527 expectedCallstacks := [][]stackEntry{ 528 { // when calling f1 529 {debugName: "whatever.f1", args: []uint64{2, 3, 4}}, 530 }, 531 { // when calling f2 532 {debugName: "whatever.f2", args: []uint64{}}, 533 {debugName: "whatever.f1", args: []uint64{2, 3, 4}}, 534 }, 535 { // when calling f3 536 {debugName: "whatever.f3", args: []uint64{5}}, 537 {debugName: "whatever.f2", args: []uint64{}}, 538 {debugName: "whatever.f1", args: []uint64{2, 3, 4}}, 539 }, 540 { // when calling f4 541 {debugName: "whatever.f4", args: []uint64{6}}, 542 {debugName: "whatever.f3", args: []uint64{5}}, 543 {debugName: "whatever.f2", args: []uint64{}}, 544 {debugName: "whatever.f1", args: []uint64{2, 3, 4}}, 545 }, 546 } 547 548 fnListener := &fnListener{ 549 beforeFn: func(ctx context.Context, mod api.Module, def api.FunctionDefinition, params []uint64, si experimental.StackIterator) { 550 require.True(t, len(expectedCallstacks) > 0) 551 expectedCallstack := expectedCallstacks[0] 552 for si.Next() { 553 require.True(t, len(expectedCallstack) > 0) 554 require.Equal(t, expectedCallstack[0].debugName, si.Function().Definition().DebugName()) 555 require.Equal(t, expectedCallstack[0].args, si.Parameters()) 556 expectedCallstack = expectedCallstack[1:] 557 } 558 require.Equal(t, 0, len(expectedCallstack)) 559 expectedCallstacks = expectedCallstacks[1:] 560 }, 561 } 562 563 functionTypes := []wasm.FunctionType{ 564 // f1 type 565 { 566 Params: []api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, 567 ParamNumInUint64: 3, 568 Results: []api.ValueType{}, 569 ResultNumInUint64: 0, 570 }, 571 // f2 type 572 { 573 Params: []api.ValueType{}, 574 ParamNumInUint64: 0, 575 Results: []api.ValueType{api.ValueTypeI32}, 576 ResultNumInUint64: 1, 577 }, 578 // f3 type 579 { 580 Params: []api.ValueType{api.ValueTypeI32}, 581 ParamNumInUint64: 1, 582 Results: []api.ValueType{api.ValueTypeI32}, 583 ResultNumInUint64: 1, 584 }, 585 // f4 type 586 { 587 Params: []api.ValueType{api.ValueTypeI32}, 588 ParamNumInUint64: 1, 589 Results: []api.ValueType{api.ValueTypeI32}, 590 ResultNumInUint64: 1, 591 }, 592 } 593 594 hostgofn := wasm.MustParseGoReflectFuncCode(func(x int32) int32 { 595 return x + 100 596 }) 597 598 m := &wasm.Module{ 599 TypeSection: functionTypes, 600 FunctionSection: []wasm.Index{0, 1, 2, 3}, 601 NameSection: &wasm.NameSection{ 602 ModuleName: "whatever", 603 FunctionNames: wasm.NameMap{ 604 {Index: wasm.Index(0), Name: "f1"}, 605 {Index: wasm.Index(1), Name: "f2"}, 606 {Index: wasm.Index(2), Name: "f3"}, 607 {Index: wasm.Index(3), Name: "f4"}, 608 }, 609 }, 610 CodeSection: []wasm.Code{ 611 { // f1 612 Body: []byte{ 613 wasm.OpcodeI32Const, 0, // reserve return for f2 614 wasm.OpcodeCall, 615 1, // call f2 616 wasm.OpcodeEnd, 617 }, 618 }, 619 { // f2 620 LocalTypes: []wasm.ValueType{wasm.ValueTypeI32}, 621 Body: []byte{ 622 wasm.OpcodeI32Const, 42, // local for f2 623 wasm.OpcodeLocalSet, 0, 624 wasm.OpcodeI32Const, 5, // argument of f3 625 wasm.OpcodeCall, 626 2, // call f3 627 wasm.OpcodeEnd, 628 }, 629 }, 630 { // f3 631 Body: []byte{ 632 wasm.OpcodeI32Const, 6, 633 wasm.OpcodeCall, 634 3, // call host function 635 wasm.OpcodeEnd, 636 }, 637 }, 638 // f4 [host function] 639 hostgofn, 640 }, 641 ExportSection: []wasm.Export{ 642 {Name: "f1", Type: wasm.ExternTypeFunc, Index: 0}, 643 }, 644 ID: wasm.ModuleID{0}, 645 } 646 647 listeners := buildFunctionListeners(fnListener, m) 648 err := e.CompileModule(testCtx, m, listeners, false) 649 require.NoError(t, err) 650 651 module := &wasm.ModuleInstance{ 652 ModuleName: t.Name(), 653 TypeIDs: []wasm.FunctionTypeID{0, 1, 2, 3}, 654 Exports: exportMap(m), 655 } 656 657 me, err := e.NewModuleEngine(m, module) 658 require.NoError(t, err) 659 linkModuleToEngine(module, me) 660 661 initCallEngine := me.NewFunction(0) // f1 662 _, err = initCallEngine.Call(testCtx, 2, 3, 4) 663 require.NoError(t, err) 664 require.Equal(t, 0, len(expectedCallstacks)) 665 } 666 667 // This tests that the Globals provided by the Engine to the Before hook of the 668 // listener is properly able to read the values of the globals. 669 func RunTestModuleEngineBeforeListenerGlobals(t *testing.T, et EngineTester) { 670 e := et.NewEngine(api.CoreFeaturesV2) 671 672 type globals struct { 673 values []uint64 674 types []api.ValueType 675 } 676 677 expectedGlobals := []globals{ 678 {values: []uint64{100, 200}, types: []api.ValueType{api.ValueTypeI32, api.ValueTypeI32}}, 679 {values: []uint64{42, 11}, types: []api.ValueType{api.ValueTypeI32, api.ValueTypeI32}}, 680 } 681 682 fnListener := &fnListener{ 683 beforeFn: func(ctx context.Context, mod api.Module, def api.FunctionDefinition, params []uint64, si experimental.StackIterator) { 684 require.True(t, len(expectedGlobals) > 0) 685 686 imod := mod.(experimental.InternalModule) 687 expected := expectedGlobals[0] 688 689 require.Equal(t, len(expected.values), imod.NumGlobal()) 690 for i := 0; i < imod.NumGlobal(); i++ { 691 global := imod.Global(i) 692 require.Equal(t, expected.types[i], global.Type()) 693 require.Equal(t, expected.values[i], global.Get()) 694 } 695 696 expectedGlobals = expectedGlobals[1:] 697 }, 698 } 699 700 functionTypes := []wasm.FunctionType{ 701 // f1 type 702 { 703 Params: []api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, 704 ParamNumInUint64: 3, 705 Results: []api.ValueType{}, 706 ResultNumInUint64: 0, 707 }, 708 // f2 type 709 { 710 Params: []api.ValueType{}, 711 ParamNumInUint64: 0, 712 Results: []api.ValueType{api.ValueTypeI32}, 713 ResultNumInUint64: 1, 714 }, 715 } 716 717 m := &wasm.Module{ 718 TypeSection: functionTypes, 719 FunctionSection: []wasm.Index{0, 1}, 720 NameSection: &wasm.NameSection{ 721 ModuleName: "whatever", 722 FunctionNames: wasm.NameMap{ 723 {Index: wasm.Index(0), Name: "f1"}, 724 {Index: wasm.Index(1), Name: "f2"}, 725 }, 726 }, 727 GlobalSection: []wasm.Global{ 728 { 729 Type: wasm.GlobalType{ValType: wasm.ValueTypeI32, Mutable: true}, 730 Init: wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: leb128.EncodeInt32(100)}, 731 }, 732 { 733 Type: wasm.GlobalType{ValType: wasm.ValueTypeI32, Mutable: true}, 734 Init: wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: leb128.EncodeInt32(200)}, 735 }, 736 }, 737 CodeSection: []wasm.Code{ 738 { // f1 739 Body: []byte{ 740 wasm.OpcodeI32Const, 42, 741 wasm.OpcodeGlobalSet, 0, // store 42 in global 0 742 wasm.OpcodeI32Const, 11, 743 wasm.OpcodeGlobalSet, 1, // store 11 in global 1 744 wasm.OpcodeI32Const, 0, // reserve return for f2 745 wasm.OpcodeCall, 746 1, // call f2 747 wasm.OpcodeEnd, 748 }, 749 }, 750 { // f2 751 LocalTypes: []wasm.ValueType{wasm.ValueTypeI32}, 752 Body: []byte{ 753 wasm.OpcodeI32Const, 42, // local for f2 754 wasm.OpcodeLocalSet, 0, 755 wasm.OpcodeEnd, 756 }, 757 }, 758 }, 759 ExportSection: []wasm.Export{ 760 {Name: "f1", Type: wasm.ExternTypeFunc, Index: 0}, 761 }, 762 ID: wasm.ModuleID{0}, 763 } 764 765 listeners := buildFunctionListeners(fnListener, m) 766 err := e.CompileModule(testCtx, m, listeners, false) 767 require.NoError(t, err) 768 769 module := &wasm.ModuleInstance{ 770 ModuleName: t.Name(), 771 TypeIDs: []wasm.FunctionTypeID{0, 1, 2, 3}, 772 Exports: exportMap(m), 773 Globals: []*wasm.GlobalInstance{ 774 {Val: 100, Type: wasm.GlobalType{ValType: wasm.ValueTypeI32, Mutable: true}}, 775 {Val: 200, Type: wasm.GlobalType{ValType: wasm.ValueTypeI32, Mutable: true}}, 776 }, 777 } 778 779 me, err := e.NewModuleEngine(m, module) 780 require.NoError(t, err) 781 linkModuleToEngine(module, me) 782 783 initCallEngine := me.NewFunction(0) // f1 784 _, err = initCallEngine.Call(testCtx, 2, 3, 4) 785 require.NoError(t, err) 786 require.True(t, len(expectedGlobals) == 0) 787 } 788 789 type fnListener struct { 790 beforeFn func(context.Context, api.Module, api.FunctionDefinition, []uint64, experimental.StackIterator) 791 afterFn func(context.Context, api.Module, api.FunctionDefinition, []uint64) 792 abortFn func(context.Context, api.Module, api.FunctionDefinition, any) 793 } 794 795 func (f *fnListener) NewFunctionListener(api.FunctionDefinition) experimental.FunctionListener { 796 return f 797 } 798 799 func (f *fnListener) Before(ctx context.Context, mod api.Module, def api.FunctionDefinition, params []uint64, stackIterator experimental.StackIterator) { 800 if f.beforeFn != nil { 801 f.beforeFn(ctx, mod, def, params, stackIterator) 802 } 803 } 804 805 func (f *fnListener) After(ctx context.Context, mod api.Module, def api.FunctionDefinition, results []uint64) { 806 if f.afterFn != nil { 807 f.afterFn(ctx, mod, def, results) 808 } 809 } 810 811 func (f *fnListener) Abort(ctx context.Context, mod api.Module, def api.FunctionDefinition, err error) { 812 if f.abortFn != nil { 813 f.abortFn(ctx, mod, def, err) 814 } 815 } 816 817 func RunTestModuleEngineStackIteratorOffset(t *testing.T, et EngineTester) { 818 e := et.NewEngine(api.CoreFeaturesV2) 819 820 type frame struct { 821 function api.FunctionDefinition 822 offset uint64 823 } 824 825 var tape [][]frame 826 827 fnListener := &fnListener{ 828 beforeFn: func(ctx context.Context, mod api.Module, def api.FunctionDefinition, params []uint64, si experimental.StackIterator) { 829 var stack []frame 830 for si.Next() { 831 fn := si.Function() 832 pc := si.ProgramCounter() 833 stack = append(stack, frame{fn.Definition(), fn.SourceOffsetForPC(pc)}) 834 } 835 tape = append(tape, stack) 836 }, 837 } 838 839 functionTypes := []wasm.FunctionType{ 840 // f1 type 841 { 842 Params: []api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, 843 ParamNumInUint64: 3, 844 Results: []api.ValueType{}, 845 ResultNumInUint64: 0, 846 }, 847 // f2 type 848 { 849 Params: []api.ValueType{}, 850 ParamNumInUint64: 0, 851 Results: []api.ValueType{api.ValueTypeI32}, 852 ResultNumInUint64: 1, 853 }, 854 // f3 type 855 { 856 Params: []api.ValueType{api.ValueTypeI32}, 857 ParamNumInUint64: 1, 858 Results: []api.ValueType{api.ValueTypeI32}, 859 ResultNumInUint64: 1, 860 }, 861 } 862 863 // Minimal DWARF info section to make debug/dwarf.New() happy. 864 // Necessary to make the compiler emit source offset maps. 865 info := []byte{ 866 0x7, 0x0, 0x0, 0x0, // length (len(info) - 4) 867 0x3, 0x0, // version (between 3 and 5 makes it easier) 868 0x0, 0x0, 0x0, 0x0, // abbrev offset 869 0x0, // asize 870 } 871 872 d, err := dwarf.New(nil, nil, nil, info, nil, nil, nil, nil) 873 if err != nil { 874 panic(err) 875 } 876 877 hostgofn := wasm.MustParseGoReflectFuncCode(func(x int32) int32 { 878 return x + 100 879 }) 880 881 m := &wasm.Module{ 882 DWARFLines: wasmdebug.NewDWARFLines(d), 883 TypeSection: functionTypes, 884 FunctionSection: []wasm.Index{0, 1, 2}, 885 NameSection: &wasm.NameSection{ 886 ModuleName: "whatever", 887 FunctionNames: wasm.NameMap{ 888 {Index: wasm.Index(0), Name: "f1"}, 889 {Index: wasm.Index(1), Name: "f2"}, 890 {Index: wasm.Index(2), Name: "f3"}, 891 }, 892 }, 893 GlobalSection: []wasm.Global{ 894 { 895 Type: wasm.GlobalType{ValType: wasm.ValueTypeI32, Mutable: true}, 896 Init: wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: leb128.EncodeInt32(100)}, 897 }, 898 { 899 Type: wasm.GlobalType{ValType: wasm.ValueTypeI32, Mutable: true}, 900 Init: wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: leb128.EncodeInt32(200)}, 901 }, 902 }, 903 CodeSection: []wasm.Code{ 904 { // f1 905 Body: []byte{ 906 wasm.OpcodeI32Const, 42, 907 wasm.OpcodeGlobalSet, 0, // store 42 in global 0 908 wasm.OpcodeI32Const, 11, 909 wasm.OpcodeGlobalSet, 1, // store 11 in global 1 910 wasm.OpcodeI32Const, 0, // reserve return for f2 911 wasm.OpcodeCall, 1, // call f2 912 wasm.OpcodeEnd, 913 }, 914 }, 915 { // f2 916 LocalTypes: []wasm.ValueType{wasm.ValueTypeI32}, 917 Body: []byte{ 918 wasm.OpcodeI32Const, 42, // local for f2 919 wasm.OpcodeLocalSet, 0, 920 wasm.OpcodeI32Const, 6, 921 wasm.OpcodeCall, 2, // call host function 922 wasm.OpcodeEnd, 923 }, 924 }, 925 // f3 926 hostgofn, 927 }, 928 ExportSection: []wasm.Export{ 929 {Name: "f1", Type: wasm.ExternTypeFunc, Index: 0}, 930 {Name: "f2", Type: wasm.ExternTypeFunc, Index: 1}, 931 {Name: "f3", Type: wasm.ExternTypeFunc, Index: 2}, 932 }, 933 ID: wasm.ModuleID{0}, 934 } 935 936 f1offset := uint64(0) 937 f2offset := f1offset + uint64(len(m.CodeSection[0].Body)) 938 f3offset := f2offset + uint64(len(m.CodeSection[1].Body)) 939 m.CodeSection[0].BodyOffsetInCodeSection = f1offset 940 m.CodeSection[1].BodyOffsetInCodeSection = f2offset 941 942 listeners := buildFunctionListeners(fnListener, m) 943 err = e.CompileModule(testCtx, m, listeners, false) 944 require.NoError(t, err) 945 946 module := &wasm.ModuleInstance{ 947 ModuleName: t.Name(), 948 TypeIDs: []wasm.FunctionTypeID{0, 1, 2}, 949 Exports: exportMap(m), 950 Globals: []*wasm.GlobalInstance{ 951 {Val: 100, Type: wasm.GlobalType{ValType: wasm.ValueTypeI32, Mutable: true}}, 952 {Val: 200, Type: wasm.GlobalType{ValType: wasm.ValueTypeI32, Mutable: true}}, 953 }, 954 Source: m, 955 } 956 957 me, err := e.NewModuleEngine(m, module) 958 require.NoError(t, err) 959 linkModuleToEngine(module, me) 960 961 initCallEngine := me.NewFunction(0) // f1 962 _, err = initCallEngine.Call(testCtx, 2, 3, 4) 963 require.NoError(t, err) 964 965 defs := module.ExportedFunctionDefinitions() 966 f1 := defs["f1"] 967 f2 := defs["f2"] 968 f3 := defs["f3"] 969 t.Logf("f1 offset: %#x", f1offset) 970 t.Logf("f2 offset: %#x", f2offset) 971 t.Logf("f3 offset: %#x", f3offset) 972 973 expectedStacks := [][]frame{ 974 { 975 {f1, f1offset + 0}, 976 }, 977 { 978 {f2, f2offset + 0}, 979 {f1, f1offset + 10}, // index of call opcode in f1's code 980 }, 981 { 982 {f3, 0}, // host functions don't have a wasm code offset 983 {f2, f2offset + 6}, // index of call opcode in f2's code 984 {f1, f1offset + 10}, // index of call opcode in f1's code 985 }, 986 } 987 988 for si, stack := range tape { 989 t.Log("Recorded stack", si, ":") 990 require.True(t, len(expectedStacks) > 0, "more recorded stacks than expected stacks") 991 expectedStack := expectedStacks[0] 992 expectedStacks = expectedStacks[1:] 993 for fi, frame := range stack { 994 t.Logf("\t%d -> %s :: %#x", fi, frame.function.Name(), frame.offset) 995 require.True(t, len(expectedStack) > 0, "more frames in stack than expected") 996 expectedFrame := expectedStack[0] 997 expectedStack = expectedStack[1:] 998 require.Equal(t, expectedFrame, frame) 999 } 1000 require.Zero(t, len(expectedStack), "expected more frames in stack") 1001 } 1002 require.Zero(t, len(expectedStacks), "expected more stacks") 1003 } 1004 1005 // RunTestModuleEngineMemory shows that the byte slice returned from api.Memory Read is not a copy, rather a re-slice 1006 // of the underlying memory. This allows both host and Wasm to see each other's writes, unless one side changes the 1007 // capacity of the slice. 1008 // 1009 // Known cases that change the slice capacity: 1010 // * Host code calls append on a byte slice returned by api.Memory Read 1011 // * Wasm code calls wasm.OpcodeMemoryGrowName and this changes the capacity (by default, it will). 1012 func RunTestModuleEngineMemory(t *testing.T, et EngineTester) { 1013 e := et.NewEngine(api.CoreFeaturesV2) 1014 1015 wasmPhrase := "Well, that'll be the day when you say goodbye." 1016 wasmPhraseSize := uint32(len(wasmPhrase)) 1017 1018 // Define a basic function which defines one parameter. This is used to test results when incorrect arity is used. 1019 one := uint32(1) 1020 m := &wasm.Module{ 1021 TypeSection: []wasm.FunctionType{{Params: []api.ValueType{api.ValueTypeI32}, ParamNumInUint64: 1}, {}}, 1022 FunctionSection: []wasm.Index{0, 1}, 1023 MemorySection: &wasm.Memory{Min: 1, Cap: 1, Max: 2}, 1024 DataSection: []wasm.DataSegment{ 1025 { 1026 Passive: true, 1027 Init: []byte(wasmPhrase), 1028 }, 1029 }, 1030 DataCountSection: &one, 1031 CodeSection: []wasm.Code{ 1032 {Body: []byte{ // "grow" 1033 wasm.OpcodeLocalGet, 0, // how many pages to grow (param) 1034 wasm.OpcodeMemoryGrow, 0, // memory index zero 1035 wasm.OpcodeDrop, // drop the previous page count (or -1 if grow failed) 1036 wasm.OpcodeEnd, 1037 }}, 1038 {Body: []byte{ // "init" 1039 wasm.OpcodeI32Const, 0, // target offset 1040 wasm.OpcodeI32Const, 0, // source offset 1041 wasm.OpcodeI32Const, byte(wasmPhraseSize), // len 1042 wasm.OpcodeMiscPrefix, wasm.OpcodeMiscMemoryInit, 0, 0, // segment 0, memory 0 1043 wasm.OpcodeEnd, 1044 }}, 1045 }, 1046 ExportSection: []wasm.Export{ 1047 {Name: "grow", Type: wasm.ExternTypeFunc, Index: 0}, 1048 {Name: "init", Type: wasm.ExternTypeFunc, Index: 1}, 1049 }, 1050 } 1051 listeners := buildFunctionListeners(et.ListenerFactory(), m) 1052 1053 err := e.CompileModule(testCtx, m, listeners, false) 1054 require.NoError(t, err) 1055 1056 // Assign memory to the module instance 1057 module := &wasm.ModuleInstance{ 1058 ModuleName: t.Name(), 1059 MemoryInstance: wasm.NewMemoryInstance(m.MemorySection), 1060 DataInstances: []wasm.DataInstance{m.DataSection[0].Init}, 1061 TypeIDs: []wasm.FunctionTypeID{0, 1}, 1062 } 1063 memory := module.MemoryInstance 1064 1065 // To use functions, we need to instantiate them (associate them with a ModuleInstance). 1066 module.Exports = exportMap(m) 1067 const grow, init = 0, 1 1068 1069 // Compile the module 1070 me, err := e.NewModuleEngine(m, module) 1071 require.NoError(t, err) 1072 linkModuleToEngine(module, me) 1073 1074 buf, ok := memory.Read(0, wasmPhraseSize) 1075 require.True(t, ok) 1076 require.Equal(t, make([]byte, wasmPhraseSize), buf) 1077 1078 // Initialize the memory using Wasm. This copies the test phrase. 1079 initCallEngine := me.NewFunction(init) 1080 _, err = initCallEngine.Call(testCtx) 1081 require.NoError(t, err) 1082 1083 // We expect the same []byte read earlier to now include the phrase in wasm. 1084 require.Equal(t, wasmPhrase, string(buf)) 1085 1086 hostPhrase := "Goodbye, cruel world. I'm off to join the circus." // Intentionally slightly longer. 1087 hostPhraseSize := uint32(len(hostPhrase)) 1088 1089 // Copy over the buffer, which should stop at the current length. 1090 copy(buf, hostPhrase) 1091 require.Equal(t, "Goodbye, cruel world. I'm off to join the circ", string(buf)) 1092 1093 // The underlying memory should be updated. This proves that Memory.Read returns a re-slice, not a copy, and that 1094 // programs can rely on this (for example, to update shared state in Wasm and view that in Go and visa versa). 1095 buf2, ok := memory.Read(0, wasmPhraseSize) 1096 require.True(t, ok) 1097 require.Equal(t, buf, buf2) 1098 1099 // Now, append to the buffer we got from Wasm. As this changes capacity, it should result in a new byte slice. 1100 buf = append(buf, 'u', 's', '.') 1101 require.Equal(t, hostPhrase, string(buf)) 1102 1103 // To prove the above, we re-read the memory and should not see the appended bytes (rather zeros instead). 1104 buf2, ok = memory.Read(0, hostPhraseSize) 1105 require.True(t, ok) 1106 hostPhraseTruncated := "Goodbye, cruel world. I'm off to join the circ" + string([]byte{0, 0, 0}) 1107 require.Equal(t, hostPhraseTruncated, string(buf2)) 1108 1109 // Now, we need to prove the other direction, that when Wasm changes the capacity, the host's buffer is unaffected. 1110 growCallEngine := me.NewFunction(grow) 1111 _, err = growCallEngine.Call(testCtx, 1) 1112 require.NoError(t, err) 1113 1114 // The host buffer should still contain the same bytes as before grow 1115 require.Equal(t, hostPhraseTruncated, string(buf2)) 1116 1117 // Re-initialize the memory in wasm, which overwrites the region. 1118 initCallEngine2 := me.NewFunction(init) 1119 _, err = initCallEngine2.Call(testCtx) 1120 require.NoError(t, err) 1121 1122 // The host was not affected because it is a different slice due to "memory.grow" affecting the underlying memory. 1123 require.Equal(t, hostPhraseTruncated, string(buf2)) 1124 } 1125 1126 const ( 1127 divByWasmName = "div_by.wasm" 1128 divByGoName = "div_by.go" 1129 callDivByGoName = "call->" + divByGoName 1130 callImportCallDivByGoName = "call_import->" + callDivByGoName 1131 ) 1132 1133 func divByGo(d uint32) uint32 { 1134 if d == math.MaxUint32 { 1135 panic(errors.New("host-function panic")) 1136 } 1137 return 1 / d // go panics if d == 0 1138 } 1139 1140 var hostDivByGo = wasm.MustParseGoReflectFuncCode(divByGo) 1141 1142 // (func (export "div_by.wasm") (param i32) (result i32) (i32.div_u (i32.const 1) (local.get 0))) 1143 var ( 1144 divByWasm = []byte{wasm.OpcodeI32Const, 1, wasm.OpcodeLocalGet, 0, wasm.OpcodeI32DivU, wasm.OpcodeEnd} 1145 hostDivByWasm = &wasm.Code{Body: divByWasm} 1146 ) 1147 1148 const ( 1149 readMemName = "read_mem" 1150 callImportReadMemName = "call_import->read_mem" 1151 ) 1152 1153 func readMemGo(_ context.Context, m api.Module) uint64 { 1154 ret, ok := m.Memory().ReadUint64Le(0) 1155 if !ok { 1156 panic("couldn't read memory") 1157 } 1158 return ret 1159 } 1160 1161 var hostReadMemGo = wasm.MustParseGoReflectFuncCode(readMemGo) 1162 1163 func setupCallTests(t *testing.T, e wasm.Engine, divBy *wasm.Code, fnlf experimental.FunctionListenerFactory) (*wasm.ModuleInstance, *wasm.ModuleInstance) { 1164 ft := wasm.FunctionType{Params: []wasm.ValueType{i32}, Results: []wasm.ValueType{i32}, ParamNumInUint64: 1, ResultNumInUint64: 1} 1165 1166 divByName := divByWasmName 1167 if divBy.GoFunc != nil { 1168 divByName = divByGoName 1169 } 1170 hostModule := &wasm.Module{ 1171 TypeSection: []wasm.FunctionType{ft}, 1172 FunctionSection: []wasm.Index{0}, 1173 CodeSection: []wasm.Code{*divBy}, 1174 ExportSection: []wasm.Export{{Name: divByGoName, Type: wasm.ExternTypeFunc, Index: 0}}, 1175 NameSection: &wasm.NameSection{ 1176 ModuleName: "host", 1177 FunctionNames: wasm.NameMap{{Index: wasm.Index(0), Name: divByName}}, 1178 }, 1179 ID: wasm.ModuleID{0}, 1180 } 1181 lns := buildFunctionListeners(fnlf, hostModule) 1182 err := e.CompileModule(testCtx, hostModule, lns, false) 1183 require.NoError(t, err) 1184 host := &wasm.ModuleInstance{ModuleName: hostModule.NameSection.ModuleName, TypeIDs: []wasm.FunctionTypeID{0}} 1185 host.Exports = exportMap(hostModule) 1186 1187 hostME, err := e.NewModuleEngine(hostModule, host) 1188 require.NoError(t, err) 1189 linkModuleToEngine(host, hostME) 1190 1191 importedModule := &wasm.Module{ 1192 ImportFunctionCount: 1, 1193 ImportSection: []wasm.Import{{}}, 1194 TypeSection: []wasm.FunctionType{ft}, 1195 FunctionSection: []wasm.Index{0, 0}, 1196 CodeSection: []wasm.Code{ 1197 {Body: divByWasm}, 1198 {Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeCall, byte(0), // Calling imported host function ^. 1199 wasm.OpcodeEnd}}, 1200 }, 1201 ExportSection: []wasm.Export{ 1202 {Name: divByWasmName, Type: wasm.ExternTypeFunc, Index: 1}, 1203 {Name: callDivByGoName, Type: wasm.ExternTypeFunc, Index: 2}, 1204 }, 1205 NameSection: &wasm.NameSection{ 1206 ModuleName: "imported", 1207 FunctionNames: wasm.NameMap{ 1208 {Index: wasm.Index(1), Name: divByWasmName}, 1209 {Index: wasm.Index(2), Name: callDivByGoName}, 1210 }, 1211 }, 1212 ID: wasm.ModuleID{1}, 1213 } 1214 lns = buildFunctionListeners(fnlf, importedModule) 1215 err = e.CompileModule(testCtx, importedModule, lns, false) 1216 require.NoError(t, err) 1217 1218 imported := &wasm.ModuleInstance{ 1219 ModuleName: importedModule.NameSection.ModuleName, TypeIDs: []wasm.FunctionTypeID{0}, 1220 } 1221 imported.Exports = exportMap(importedModule) 1222 1223 // Compile the imported module 1224 importedMe, err := e.NewModuleEngine(importedModule, imported) 1225 require.NoError(t, err) 1226 linkModuleToEngine(imported, importedMe) 1227 importedMe.ResolveImportedFunction(0, 0, hostME) 1228 1229 // To test stack traces, call the same function from another module 1230 importingModule := &wasm.Module{ 1231 ImportFunctionCount: 1, 1232 TypeSection: []wasm.FunctionType{ft}, 1233 ImportSection: []wasm.Import{{}}, 1234 FunctionSection: []wasm.Index{0}, 1235 CodeSection: []wasm.Code{ 1236 {Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeCall, 0 /* only one imported function */, wasm.OpcodeEnd}}, 1237 }, 1238 ExportSection: []wasm.Export{ 1239 {Name: callImportCallDivByGoName, Type: wasm.ExternTypeFunc, Index: 1}, 1240 }, 1241 NameSection: &wasm.NameSection{ 1242 ModuleName: "importing", 1243 FunctionNames: wasm.NameMap{{Index: wasm.Index(1), Name: callImportCallDivByGoName}}, 1244 }, 1245 ID: wasm.ModuleID{2}, 1246 } 1247 lns = buildFunctionListeners(fnlf, importingModule) 1248 err = e.CompileModule(testCtx, importingModule, lns, false) 1249 require.NoError(t, err) 1250 1251 // Add the exported function. 1252 importing := &wasm.ModuleInstance{ModuleName: importingModule.NameSection.ModuleName, TypeIDs: []wasm.FunctionTypeID{0}} 1253 importing.Exports = exportMap(importingModule) 1254 1255 // Compile the importing module 1256 importingMe, err := e.NewModuleEngine(importingModule, importing) 1257 require.NoError(t, err) 1258 linkModuleToEngine(importing, importingMe) 1259 importingMe.ResolveImportedFunction(0, 2, importedMe) 1260 return imported, importing 1261 } 1262 1263 func setupCallMemTests(t *testing.T, e wasm.Engine, readMem *wasm.Code) *wasm.ModuleInstance { 1264 ft := wasm.FunctionType{Results: []wasm.ValueType{i64}, ResultNumInUint64: 1} 1265 1266 hostModule := &wasm.Module{ 1267 TypeSection: []wasm.FunctionType{ft}, 1268 FunctionSection: []wasm.Index{0}, 1269 CodeSection: []wasm.Code{*readMem}, 1270 ExportSection: []wasm.Export{ 1271 {Name: readMemName, Type: wasm.ExternTypeFunc, Index: 0}, 1272 }, 1273 NameSection: &wasm.NameSection{ 1274 ModuleName: "host", 1275 FunctionNames: wasm.NameMap{{Index: 0, Name: readMemName}}, 1276 }, 1277 ID: wasm.ModuleID{0}, 1278 } 1279 err := e.CompileModule(testCtx, hostModule, nil, false) 1280 require.NoError(t, err) 1281 host := &wasm.ModuleInstance{ModuleName: hostModule.NameSection.ModuleName, TypeIDs: []wasm.FunctionTypeID{0}} 1282 host.Exports = exportMap(hostModule) 1283 1284 hostMe, err := e.NewModuleEngine(hostModule, host) 1285 require.NoError(t, err) 1286 linkModuleToEngine(host, hostMe) 1287 1288 importingModule := &wasm.Module{ 1289 ImportFunctionCount: 1, 1290 TypeSection: []wasm.FunctionType{ft}, 1291 ImportSection: []wasm.Import{ 1292 // Placeholder for two import functions from `importedModule`. 1293 {Type: wasm.ExternTypeFunc, DescFunc: 0}, 1294 }, 1295 FunctionSection: []wasm.Index{0}, 1296 ExportSection: []wasm.Export{ 1297 {Name: callImportReadMemName, Type: wasm.ExternTypeFunc, Index: 1}, 1298 }, 1299 CodeSection: []wasm.Code{ 1300 {Body: []byte{wasm.OpcodeCall, 0, wasm.OpcodeEnd}}, // Calling the index 1 = readMemFn. 1301 }, 1302 NameSection: &wasm.NameSection{ 1303 ModuleName: "importing", 1304 FunctionNames: wasm.NameMap{ 1305 {Index: 2, Name: callImportReadMemName}, 1306 }, 1307 }, 1308 // Indicates that this module has a memory so that compilers are able to assembe memory-related initialization. 1309 MemorySection: &wasm.Memory{Min: 1}, 1310 ID: wasm.ModuleID{1}, 1311 } 1312 err = e.CompileModule(testCtx, importingModule, nil, false) 1313 require.NoError(t, err) 1314 1315 // Add the exported function. 1316 importing := &wasm.ModuleInstance{ModuleName: importingModule.NameSection.ModuleName, TypeIDs: []wasm.FunctionTypeID{0}} 1317 // Note: adds imported functions readMemFn and callReadMemFn at index 0 and 1. 1318 importing.Exports = exportMap(importingModule) 1319 1320 // Compile the importing module 1321 importingMe, err := e.NewModuleEngine(importingModule, importing) 1322 require.NoError(t, err) 1323 linkModuleToEngine(importing, importingMe) 1324 importingMe.ResolveImportedFunction(0, 0, hostMe) 1325 return importing 1326 } 1327 1328 // linkModuleToEngine assigns fields that wasm.Store would on instantiation. These include fields both interpreter and 1329 // Compiler needs as well as fields only needed by Compiler. 1330 // 1331 // Note: This sets fields that are not needed in the interpreter, but are required by code compiled by Compiler. If a new 1332 // test here passes in the interpreter and segmentation faults in Compiler, check for a new field offset or a change in Compiler 1333 // (e.g. compiler.TestVerifyOffsetValue). It is possible for all other tests to pass as that field is implicitly set by 1334 // wasm.Store: store isn't used here for unit test precision. 1335 func linkModuleToEngine(module *wasm.ModuleInstance, me wasm.ModuleEngine) { 1336 module.Engine = me // for Compiler, links the module to the module-engine compiled from it (moduleInstanceEngineOffset). 1337 } 1338 1339 func buildFunctionListeners(factory experimental.FunctionListenerFactory, m *wasm.Module) []experimental.FunctionListener { 1340 if factory == nil || len(m.FunctionSection) == 0 { 1341 return nil 1342 } 1343 listeners := make([]experimental.FunctionListener, len(m.FunctionSection)) 1344 importCount := m.ImportFunctionCount 1345 for i := 0; i < len(listeners); i++ { 1346 listeners[i] = factory.NewFunctionListener(m.FunctionDefinition(uint32(i) + importCount)) 1347 } 1348 return listeners 1349 } 1350 1351 func exportMap(m *wasm.Module) map[string]*wasm.Export { 1352 ret := make(map[string]*wasm.Export, len(m.ExportSection)) 1353 for i := range m.ExportSection { 1354 exp := &m.ExportSection[i] 1355 ret[exp.Name] = exp 1356 } 1357 return ret 1358 }