wa-lang.org/wazero@v1.0.2/internal/integration_test/engine/adhoc_test.go (about) 1 package adhoc 2 3 import ( 4 "context" 5 _ "embed" 6 "math" 7 "strconv" 8 "testing" 9 "unsafe" 10 11 "wa-lang.org/wazero" 12 "wa-lang.org/wazero/api" 13 "wa-lang.org/wazero/internal/platform" 14 "wa-lang.org/wazero/internal/testing/require" 15 "wa-lang.org/wazero/internal/wasm" 16 "wa-lang.org/wazero/internal/wasm/binary" 17 "wa-lang.org/wazero/sys" 18 ) 19 20 // testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors. 21 var testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary") 22 23 const ( 24 i32, i64 = wasm.ValueTypeI32, wasm.ValueTypeI64 25 ) 26 27 var memoryCapacityPages = uint32(2) 28 29 var moduleConfig = wazero.NewModuleConfig() 30 31 var tests = map[string]func(t *testing.T, r wazero.Runtime){ 32 "huge stack": testHugeStack, 33 "unreachable": testUnreachable, 34 "recursive entry": testRecursiveEntry, 35 "host func memory": testHostFuncMemory, 36 "host function with context parameter": testHostFunctionContextParameter, 37 "host function with nested context": testNestedGoContext, 38 "host function with numeric parameter": testHostFunctionNumericParameter, 39 "close module with in-flight calls": testCloseInFlight, 40 "multiple instantiation from same source": testMultipleInstantiation, 41 "exported function that grows memory": testMemOps, 42 "import functions with reference type in signature": testReftypeImports, 43 "overflow integer addition": testOverflow, 44 "un-signed extend global": testGlobalExtend, 45 } 46 47 func TestEngineCompiler(t *testing.T) { 48 if !platform.CompilerSupported() { 49 t.Skip() 50 } 51 runAllTests(t, tests, wazero.NewRuntimeConfigCompiler()) 52 } 53 54 func TestEngineInterpreter(t *testing.T) { 55 runAllTests(t, tests, wazero.NewRuntimeConfigInterpreter()) 56 } 57 58 func runAllTests(t *testing.T, tests map[string]func(t *testing.T, r wazero.Runtime), config wazero.RuntimeConfig) { 59 for name, testf := range tests { 60 name := name // pin 61 testf := testf // pin 62 t.Run(name, func(t *testing.T) { 63 t.Parallel() 64 testf(t, wazero.NewRuntimeWithConfig(testCtx, config)) 65 }) 66 } 67 } 68 69 var ( 70 //go:embed testdata/unreachable.wasm 71 unreachableWasm []byte 72 //go:embed testdata/recursive.wasm 73 recursiveWasm []byte 74 //go:embed testdata/host_memory.wasm 75 hostMemoryWasm []byte 76 //go:embed testdata/hugestack.wasm 77 hugestackWasm []byte 78 //go:embed testdata/memory.wasm 79 memoryWasm []byte 80 //go:embed testdata/reftype_imports.wasm 81 reftypeImportsWasm []byte 82 //go:embed testdata/overflow.wasm 83 overflowWasm []byte 84 //go:embed testdata/global_extend.wasm 85 globalExtendWasm []byte 86 ) 87 88 func testReftypeImports(t *testing.T, r wazero.Runtime) { 89 type dog struct { 90 name string 91 } 92 93 hostObj := &dog{name: "hello"} 94 host, err := r.NewHostModuleBuilder("host"). 95 NewFunctionBuilder(). 96 WithFunc(func(ctx context.Context, externrefFromRefNull uintptr) uintptr { 97 require.Zero(t, externrefFromRefNull) 98 return uintptr(unsafe.Pointer(hostObj)) 99 }). 100 Export("externref"). 101 Instantiate(testCtx, r) 102 require.NoError(t, err) 103 defer host.Close(testCtx) 104 105 module, err := r.InstantiateModuleFromBinary(testCtx, reftypeImportsWasm) 106 require.NoError(t, err) 107 defer module.Close(testCtx) 108 109 actual, err := module.ExportedFunction("get_externref_by_host").Call(testCtx) 110 require.NoError(t, err) 111 112 // Verifies that the returned raw uintptr is the same as the one for the host object. 113 require.Equal(t, uintptr(unsafe.Pointer(hostObj)), uintptr(actual[0])) 114 } 115 116 func testHugeStack(t *testing.T, r wazero.Runtime) { 117 module, err := r.InstantiateModuleFromBinary(testCtx, hugestackWasm) 118 require.NoError(t, err) 119 defer module.Close(testCtx) 120 121 fn := module.ExportedFunction("main") 122 require.NotNil(t, fn) 123 124 res, err := fn.Call(testCtx, 0, 0, 0, 0, 0, 0) // params ignored by wasm 125 require.NoError(t, err) 126 127 const resultNumInUint64 = 180 128 require.Equal(t, resultNumInUint64, len(res)) 129 for i := uint64(1); i <= resultNumInUint64; i++ { 130 require.Equal(t, i, res[i-1]) 131 } 132 } 133 134 // testOverflow ensures that adding one into the maximum integer results in the 135 // minimum one. See #636. 136 func testOverflow(t *testing.T, r wazero.Runtime) { 137 module, err := r.InstantiateModuleFromBinary(testCtx, overflowWasm) 138 require.NoError(t, err) 139 defer module.Close(testCtx) 140 141 for _, name := range []string{"i32", "i64"} { 142 i32 := module.ExportedFunction(name) 143 require.NotNil(t, i32) 144 145 res, err := i32.Call(testCtx) 146 require.NoError(t, err) 147 148 require.Equal(t, uint64(1), res[0]) 149 } 150 } 151 152 // testGlobalExtend ensures that un-signed extension of i32 globals must be zero extended. See #656. 153 func testGlobalExtend(t *testing.T, r wazero.Runtime) { 154 module, err := r.InstantiateModuleFromBinary(testCtx, globalExtendWasm) 155 require.NoError(t, err) 156 defer module.Close(testCtx) 157 158 extend := module.ExportedFunction("extend") 159 require.NotNil(t, extend) 160 161 res, err := extend.Call(testCtx) 162 require.NoError(t, err) 163 164 require.Equal(t, uint64(0xffff_ffff), res[0]) 165 } 166 167 func testUnreachable(t *testing.T, r wazero.Runtime) { 168 callUnreachable := func() { 169 panic("panic in host function") 170 } 171 172 _, err := r.NewHostModuleBuilder("host"). 173 NewFunctionBuilder().WithFunc(callUnreachable).Export("cause_unreachable"). 174 Instantiate(testCtx, r) 175 require.NoError(t, err) 176 177 module, err := r.InstantiateModuleFromBinary(testCtx, unreachableWasm) 178 require.NoError(t, err) 179 defer module.Close(testCtx) 180 181 _, err = module.ExportedFunction("main").Call(testCtx) 182 exp := `panic in host function (recovered by wazero) 183 wasm stack trace: 184 host.cause_unreachable() 185 .two() 186 .one() 187 .main()` 188 require.Equal(t, exp, err.Error()) 189 } 190 191 func testRecursiveEntry(t *testing.T, r wazero.Runtime) { 192 hostfunc := func(ctx context.Context, mod api.Module) { 193 _, err := mod.ExportedFunction("called_by_host_func").Call(testCtx) 194 require.NoError(t, err) 195 } 196 197 _, err := r.NewHostModuleBuilder("env"). 198 NewFunctionBuilder().WithFunc(hostfunc).Export("host_func"). 199 Instantiate(testCtx, r) 200 require.NoError(t, err) 201 202 module, err := r.InstantiateModuleFromBinary(testCtx, recursiveWasm) 203 require.NoError(t, err) 204 defer module.Close(testCtx) 205 206 _, err = module.ExportedFunction("main").Call(testCtx, 1) 207 require.NoError(t, err) 208 } 209 210 // testHostFuncMemory ensures that host functions can see the callers' memory 211 func testHostFuncMemory(t *testing.T, r wazero.Runtime) { 212 var memory *wasm.MemoryInstance 213 storeInt := func(ctx context.Context, m api.Module, offset uint32, val uint64) uint32 { 214 if !m.Memory().WriteUint64Le(ctx, offset, val) { 215 return 1 216 } 217 // sneak a reference to the memory, so we can check it later 218 memory = m.Memory().(*wasm.MemoryInstance) 219 return 0 220 } 221 222 host, err := r.NewHostModuleBuilder(""). 223 NewFunctionBuilder().WithFunc(storeInt).Export("store_int"). 224 Instantiate(testCtx, r) 225 require.NoError(t, err) 226 defer host.Close(testCtx) 227 228 module, err := r.InstantiateModuleFromBinary(testCtx, hostMemoryWasm) 229 require.NoError(t, err) 230 defer module.Close(testCtx) 231 232 // Call store_int and ensure it didn't return an error code. 233 fn := module.ExportedFunction("store_int") 234 results, err := fn.Call(testCtx, 1, math.MaxUint64) 235 require.NoError(t, err) 236 require.Equal(t, uint64(0), results[0]) 237 238 // Since offset=1 and val=math.MaxUint64, we expect to have written exactly 8 bytes, with all bits set, at index 1. 239 require.Equal(t, []byte{0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0}, memory.Buffer[0:10]) 240 } 241 242 // testNestedGoContext ensures context is updated when a function calls another. 243 func testNestedGoContext(t *testing.T, r wazero.Runtime) { 244 nestedCtx := context.WithValue(context.Background(), struct{}{}, "nested") 245 246 importedName := t.Name() + "-imported" 247 importingName := t.Name() + "-importing" 248 249 var importing api.Module 250 251 imported, err := r.NewHostModuleBuilder(importedName). 252 NewFunctionBuilder(). 253 WithFunc(func(ctx context.Context, p uint32) uint32 { 254 // We expect the initial context, testCtx, to be overwritten by "outer" when it called this. 255 require.Equal(t, nestedCtx, ctx) 256 return p + 1 257 }). 258 Export("inner"). 259 NewFunctionBuilder(). 260 WithFunc(func(ctx context.Context, module api.Module, p uint32) uint32 { 261 require.Equal(t, testCtx, ctx) 262 results, err := module.ExportedFunction("inner").Call(nestedCtx, uint64(p)) 263 require.NoError(t, err) 264 return uint32(results[0]) + 1 265 }). 266 Export("outer"). 267 Instantiate(testCtx, r) 268 require.NoError(t, err) 269 defer imported.Close(testCtx) 270 271 // Instantiate a module that uses Wasm code to call the host function. 272 importing, err = r.InstantiateModuleFromBinary(testCtx, callOuterInnerWasm(t, importedName, importingName)) 273 require.NoError(t, err) 274 defer importing.Close(testCtx) 275 276 input := uint64(math.MaxUint32 - 2) // We expect two calls where each increment by one. 277 results, err := importing.ExportedFunction("call->outer").Call(testCtx, input) 278 require.NoError(t, err) 279 require.Equal(t, uint64(math.MaxUint32), results[0]) 280 } 281 282 // testHostFunctionContextParameter ensures arg0 is optionally a context. 283 func testHostFunctionContextParameter(t *testing.T, r wazero.Runtime) { 284 importedName := t.Name() + "-imported" 285 importingName := t.Name() + "-importing" 286 287 var importing api.Module 288 fns := map[string]interface{}{ 289 "ctx": func(ctx context.Context, p uint32) uint32 { 290 require.Equal(t, testCtx, ctx) 291 return p + 1 292 }, 293 "ctx mod": func(ctx context.Context, module api.Module, p uint32) uint32 { 294 require.Equal(t, importing, module) 295 return p + 1 296 }, 297 } 298 299 for test := range fns { 300 t.Run(test, func(t *testing.T) { 301 imported, err := r.NewHostModuleBuilder(importedName). 302 NewFunctionBuilder().WithFunc(fns[test]).Export("return_input"). 303 Instantiate(testCtx, r) 304 require.NoError(t, err) 305 defer imported.Close(testCtx) 306 307 // Instantiate a module that uses Wasm code to call the host function. 308 importing, err = r.InstantiateModuleFromBinary(testCtx, 309 callReturnImportWasm(t, importedName, importingName, i32)) 310 require.NoError(t, err) 311 defer importing.Close(testCtx) 312 313 results, err := importing.ExportedFunction("call_return_input").Call(testCtx, math.MaxUint32-1) 314 require.NoError(t, err) 315 require.Equal(t, uint64(math.MaxUint32), results[0]) 316 }) 317 } 318 } 319 320 // testHostFunctionNumericParameter ensures numeric parameters aren't corrupted 321 func testHostFunctionNumericParameter(t *testing.T, r wazero.Runtime) { 322 importedName := t.Name() + "-imported" 323 importingName := t.Name() + "-importing" 324 325 fns := map[string]interface{}{ 326 "i32": func(ctx context.Context, p uint32) uint32 { 327 return p + 1 328 }, 329 "i64": func(ctx context.Context, p uint64) uint64 { 330 return p + 1 331 }, 332 "f32": func(ctx context.Context, p float32) float32 { 333 return p + 1 334 }, 335 "f64": func(ctx context.Context, p float64) float64 { 336 return p + 1 337 }, 338 } 339 340 for _, test := range []struct { 341 name string 342 vt wasm.ValueType 343 input, expected uint64 344 }{ 345 { 346 name: "i32", 347 vt: i32, 348 input: math.MaxUint32 - 1, 349 expected: math.MaxUint32, 350 }, 351 { 352 name: "i64", 353 vt: i64, 354 input: math.MaxUint64 - 1, 355 expected: math.MaxUint64, 356 }, 357 { 358 name: "f32", 359 vt: wasm.ValueTypeF32, 360 input: api.EncodeF32(math.MaxFloat32 - 1), 361 expected: api.EncodeF32(math.MaxFloat32), 362 }, 363 { 364 name: "f64", 365 vt: wasm.ValueTypeF64, 366 input: api.EncodeF64(math.MaxFloat64 - 1), 367 expected: api.EncodeF64(math.MaxFloat64), 368 }, 369 } { 370 t.Run(test.name, func(t *testing.T) { 371 imported, err := r.NewHostModuleBuilder(importedName). 372 NewFunctionBuilder().WithFunc(fns[test.name]).Export("return_input"). 373 Instantiate(testCtx, r) 374 require.NoError(t, err) 375 defer imported.Close(testCtx) 376 377 // Instantiate a module that uses Wasm code to call the host function. 378 importing, err := r.InstantiateModuleFromBinary(testCtx, 379 callReturnImportWasm(t, importedName, importingName, test.vt)) 380 require.NoError(t, err) 381 defer importing.Close(testCtx) 382 383 results, err := importing.ExportedFunction("call_return_input").Call(testCtx, test.input) 384 require.NoError(t, err) 385 require.Equal(t, test.expected, results[0]) 386 }) 387 } 388 } 389 390 func callReturnImportWasm(t *testing.T, importedModule, importingModule string, vt wasm.ValueType) []byte { 391 // test an imported function by re-exporting it 392 module := &wasm.Module{ 393 TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{vt}, Results: []wasm.ValueType{vt}}}, 394 // (import "%[2]s" "return_input" (func $return_input (param i32) (result i32))) 395 ImportSection: []*wasm.Import{ 396 {Module: importedModule, Name: "return_input", Type: wasm.ExternTypeFunc, DescFunc: 0}, 397 }, 398 FunctionSection: []wasm.Index{0}, 399 ExportSection: []*wasm.Export{ 400 // (export "return_input" (func $return_input)) 401 {Name: "return_input", Type: wasm.ExternTypeFunc, Index: 0}, 402 // (export "call_return_input" (func $call_return_input)) 403 {Name: "call_return_input", Type: wasm.ExternTypeFunc, Index: 1}, 404 }, 405 // (func $call_return_input (param i32) (result i32) local.get 0 call $return_input) 406 CodeSection: []*wasm.Code{ 407 {Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeCall, 0, wasm.OpcodeEnd}}, 408 }, 409 NameSection: &wasm.NameSection{ 410 ModuleName: importingModule, 411 FunctionNames: wasm.NameMap{ 412 {Index: 0, Name: "return_input"}, 413 {Index: 1, Name: "call_return_input"}, 414 }, 415 }, 416 } 417 require.NoError(t, module.Validate(api.CoreFeaturesV2)) 418 return binary.EncodeModule(module) 419 } 420 421 func callOuterInnerWasm(t *testing.T, importedModule, importingModule string) []byte { 422 module := &wasm.Module{ 423 TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32}, Results: []wasm.ValueType{i32}}}, 424 // (import "%[2]s" "outer" (func $outer (param i32) (result i32))) 425 // (import "%[2]s" "inner" (func $inner (param i32) (result i32))) 426 ImportSection: []*wasm.Import{ 427 {Module: importedModule, Name: "outer", Type: wasm.ExternTypeFunc, DescFunc: 0}, 428 {Module: importedModule, Name: "inner", Type: wasm.ExternTypeFunc, DescFunc: 0}, 429 }, 430 FunctionSection: []wasm.Index{0, 0}, 431 ExportSection: []*wasm.Export{ 432 // (export "call->outer" (func $call_outer)) 433 {Name: "call->outer", Type: wasm.ExternTypeFunc, Index: 2}, 434 // (export "inner" (func $call_inner)) 435 {Name: "inner", Type: wasm.ExternTypeFunc, Index: 3}, 436 }, 437 CodeSection: []*wasm.Code{ 438 // (func $call_outer (param i32) (result i32) local.get 0 call $outer) 439 {Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeCall, 0, wasm.OpcodeEnd}}, 440 // (func $call_inner (param i32) (result i32) local.get 0 call $inner) 441 {Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeCall, 1, wasm.OpcodeEnd}}, 442 }, 443 NameSection: &wasm.NameSection{ 444 ModuleName: importingModule, 445 FunctionNames: wasm.NameMap{ 446 {Index: 0, Name: "outer"}, 447 {Index: 1, Name: "inner"}, 448 {Index: 2, Name: "call_outer"}, 449 {Index: 3, Name: "call_inner"}, 450 }, 451 }, 452 } 453 require.NoError(t, module.Validate(api.CoreFeaturesV2)) 454 return binary.EncodeModule(module) 455 } 456 457 func testCloseInFlight(t *testing.T, r wazero.Runtime) { 458 tests := []struct { 459 name, function string 460 closeImporting, closeImported uint32 461 closeImportingCode, closeImportedCode bool 462 }{ 463 { // e.g. WASI proc_exit or AssemblyScript abort handler. 464 name: "importing", 465 function: "call_return_input", 466 closeImporting: 1, 467 }, 468 // TODO: A module that re-exports a function (ex "return_input") can call it after it is closed! 469 { // e.g. A function that stops the runtime. 470 name: "both", 471 function: "call_return_input", 472 closeImporting: 1, 473 closeImported: 2, 474 }, 475 { // e.g. WASI proc_exit or AssemblyScript abort handler. 476 name: "importing", 477 function: "call_return_input", 478 closeImporting: 1, 479 closeImportedCode: true, 480 }, 481 { // e.g. WASI proc_exit or AssemblyScript abort handler. 482 name: "importing", 483 function: "call_return_input", 484 closeImporting: 1, 485 closeImportedCode: true, 486 closeImportingCode: true, 487 }, 488 // TODO: A module that re-exports a function (ex "return_input") can call it after it is closed! 489 { // e.g. A function that stops the runtime. 490 name: "both", 491 function: "call_return_input", 492 closeImporting: 1, 493 closeImported: 2, 494 closeImportingCode: true, 495 }, 496 } 497 for _, tt := range tests { 498 tc := tt 499 500 t.Run(tc.name, func(t *testing.T) { 501 var importingCode, importedCode wazero.CompiledModule 502 var imported, importing api.Module 503 var err error 504 closeAndReturn := func(ctx context.Context, x uint32) uint32 { 505 if tc.closeImporting != 0 { 506 require.NoError(t, importing.CloseWithExitCode(ctx, tc.closeImporting)) 507 } 508 if tc.closeImported != 0 { 509 require.NoError(t, imported.CloseWithExitCode(ctx, tc.closeImported)) 510 } 511 if tc.closeImportedCode { 512 importedCode.Close(testCtx) 513 } 514 if tc.closeImportingCode { 515 importingCode.Close(testCtx) 516 } 517 return x 518 } 519 520 // Create the host module, which exports the function that closes the importing module. 521 importedCode, err = r.NewHostModuleBuilder(t.Name() + "-imported"). 522 NewFunctionBuilder().WithFunc(closeAndReturn).Export("return_input"). 523 Compile(testCtx) 524 require.NoError(t, err) 525 526 imported, err = r.InstantiateModule(testCtx, importedCode, moduleConfig) 527 require.NoError(t, err) 528 defer imported.Close(testCtx) 529 530 // Import that module. 531 binary := callReturnImportWasm(t, imported.Name(), t.Name()+"-importing", i32) 532 importingCode, err = r.CompileModule(testCtx, binary) 533 require.NoError(t, err) 534 535 importing, err = r.InstantiateModule(testCtx, importingCode, moduleConfig) 536 require.NoError(t, err) 537 defer importing.Close(testCtx) 538 539 var expectedErr error 540 if tc.closeImported != 0 && tc.closeImporting != 0 { 541 // When both modules are closed, importing is the better one to choose in the error message. 542 expectedErr = sys.NewExitError(importing.Name(), tc.closeImporting) 543 } else if tc.closeImported != 0 { 544 expectedErr = sys.NewExitError(imported.Name(), tc.closeImported) 545 } else if tc.closeImporting != 0 { 546 expectedErr = sys.NewExitError(importing.Name(), tc.closeImporting) 547 } else { 548 t.Fatal("invalid test case") 549 } 550 551 // Functions that return after being closed should have an exit error. 552 _, err = importing.ExportedFunction(tc.function).Call(testCtx, 5) 553 require.Equal(t, expectedErr, err) 554 }) 555 } 556 } 557 558 func testMemOps(t *testing.T, r wazero.Runtime) { 559 // Instantiate a module that manages its memory 560 mod, err := r.InstantiateModuleFromBinary(testCtx, memoryWasm) 561 require.NoError(t, err) 562 defer mod.Close(testCtx) 563 564 // Check the export worked 565 require.Equal(t, mod.Memory(), mod.ExportedMemory("memory")) 566 memory := mod.Memory() 567 568 sizeFn, storeFn, growFn := mod.ExportedFunction("size"), mod.ExportedFunction("store"), mod.ExportedFunction("grow") 569 570 // Check the size command worked 571 results, err := sizeFn.Call(testCtx) 572 require.NoError(t, err) 573 require.Zero(t, results[0]) 574 require.Zero(t, memory.Size(testCtx)) 575 576 // Any offset should be out of bounds error even when it is less than memory capacity(=memoryCapacityPages). 577 _, err = storeFn.Call(testCtx, wasm.MemoryPagesToBytesNum(memoryCapacityPages)-8) 578 require.Error(t, err) // Out of bounds error. 579 580 // Try to grow the memory by one page 581 results, err = growFn.Call(testCtx, 1) 582 require.NoError(t, err) 583 require.Zero(t, results[0]) // should succeed and return the old size in pages. 584 585 // Any offset larger than the current size should be out of bounds error even when it is less than memory capacity. 586 _, err = storeFn.Call(testCtx, wasm.MemoryPagesToBytesNum(memoryCapacityPages)-8) 587 require.Error(t, err) // Out of bounds error. 588 589 // Check the size command works! 590 results, err = sizeFn.Call(testCtx) 591 require.NoError(t, err) 592 require.Equal(t, uint64(1), results[0]) // 1 page 593 require.Equal(t, uint32(65536), memory.Size(testCtx)) // 64KB 594 595 // Grow again so that the memory size matches memory capacity. 596 results, err = growFn.Call(testCtx, 1) 597 require.NoError(t, err) 598 require.Equal(t, uint64(1), results[0]) 599 600 // Verify the size matches cap. 601 results, err = sizeFn.Call(testCtx) 602 require.NoError(t, err) 603 require.Equal(t, uint64(memoryCapacityPages), results[0]) 604 605 // Now the store instruction at the memory capcity bound should succeed. 606 _, err = storeFn.Call(testCtx, wasm.MemoryPagesToBytesNum(memoryCapacityPages)-8) // i64.store needs 8 bytes from offset. 607 require.NoError(t, err) 608 } 609 610 func testMultipleInstantiation(t *testing.T, r wazero.Runtime) { 611 bin := binary.EncodeModule(&wasm.Module{ 612 TypeSection: []*wasm.FunctionType{{}}, 613 FunctionSection: []wasm.Index{0}, 614 MemorySection: &wasm.Memory{Min: 1, Cap: 1, Max: 1, IsMaxEncoded: true}, 615 CodeSection: []*wasm.Code{{ 616 Body: []byte{ 617 wasm.OpcodeI32Const, 1, // i32.const 1 ;; memory offset 618 wasm.OpcodeI64Const, 0xe8, 0x7, // i64.const 1000 ;; expected value 619 wasm.OpcodeI64Store, 0x3, 0x0, // i64.store 620 wasm.OpcodeEnd, 621 }, 622 }}, 623 ExportSection: []*wasm.Export{{Name: "store"}}, 624 }) 625 compiled, err := r.CompileModule(testCtx, bin) 626 require.NoError(t, err) 627 defer compiled.Close(testCtx) 628 629 // Instantiate multiple modules with the same source (*CompiledModule). 630 for i := 0; i < 100; i++ { 631 module, err := r.InstantiateModule(testCtx, compiled, wazero.NewModuleConfig().WithName(strconv.Itoa(i))) 632 require.NoError(t, err) 633 defer module.Close(testCtx) 634 635 // Ensure that compilation cache doesn't cause race on memory instance. 636 before, ok := module.Memory().ReadUint64Le(testCtx, 1) 637 require.True(t, ok) 638 // Value must be zero as the memory must not be affected by the previously instantiated modules. 639 require.Zero(t, before) 640 641 f := module.ExportedFunction("store") 642 require.NotNil(t, f) 643 644 _, err = f.Call(testCtx) 645 require.NoError(t, err) 646 647 // After the call, the value must be set properly. 648 after, ok := module.Memory().ReadUint64Le(testCtx, 1) 649 require.True(t, ok) 650 require.Equal(t, uint64(1000), after) 651 } 652 }