wa-lang.org/wazero@v1.0.2/runtime_test.go (about) 1 package wazero 2 3 import ( 4 "context" 5 _ "embed" 6 "errors" 7 "testing" 8 "time" 9 10 "wa-lang.org/wazero/api" 11 "wa-lang.org/wazero/experimental" 12 "wa-lang.org/wazero/internal/leb128" 13 "wa-lang.org/wazero/internal/testing/require" 14 "wa-lang.org/wazero/internal/version" 15 "wa-lang.org/wazero/internal/wasm" 16 binaryformat "wa-lang.org/wazero/internal/wasm/binary" 17 "wa-lang.org/wazero/sys" 18 ) 19 20 var ( 21 binaryNamedZero = binaryformat.EncodeModule(&wasm.Module{NameSection: &wasm.NameSection{ModuleName: "0"}}) 22 // testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors. 23 testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary") 24 ) 25 26 var _ context.Context = &HostContext{} 27 28 // HostContext contain the content will be used in host function call 29 type HostContext struct { 30 Content string 31 } 32 33 func (h *HostContext) Deadline() (deadline time.Time, ok bool) { return } 34 35 func (h *HostContext) Done() <-chan struct{} { return nil } 36 37 func (h *HostContext) Err() error { return nil } 38 39 func (h *HostContext) Value(key interface{}) interface{} { return nil } 40 41 func TestNewRuntimeWithConfig_version(t *testing.T) { 42 cfg := NewRuntimeConfig().(*runtimeConfig) 43 oldNewEngine := cfg.newEngine 44 cfg.newEngine = func(ctx context.Context, features api.CoreFeatures) wasm.Engine { 45 // Ensures that wazeroVersion is propagated to the engine. 46 v := ctx.Value(version.WazeroVersionKey{}) 47 require.NotNil(t, v) 48 require.Equal(t, wazeroVersion, v.(string)) 49 return oldNewEngine(ctx, features) 50 } 51 _ = NewRuntimeWithConfig(testCtx, cfg) 52 } 53 54 func TestRuntime_CompileModule(t *testing.T) { 55 tests := []struct { 56 name string 57 runtime Runtime 58 wasm []byte 59 moduleBuilder HostModuleBuilder 60 expected func(CompiledModule) 61 }{ 62 { 63 name: "no name section", 64 wasm: binaryformat.EncodeModule(&wasm.Module{}), 65 }, 66 { 67 name: "empty NameSection.ModuleName", 68 wasm: binaryformat.EncodeModule(&wasm.Module{NameSection: &wasm.NameSection{}}), 69 }, 70 { 71 name: "NameSection.ModuleName", 72 wasm: binaryformat.EncodeModule(&wasm.Module{NameSection: &wasm.NameSection{ModuleName: "test"}}), 73 expected: func(compiled CompiledModule) { 74 require.Equal(t, "test", compiled.Name()) 75 }, 76 }, 77 { 78 name: "FunctionSection, but not exported", 79 wasm: binaryformat.EncodeModule(&wasm.Module{ 80 TypeSection: []*wasm.FunctionType{{Params: []api.ValueType{api.ValueTypeI32}}}, 81 FunctionSection: []wasm.Index{0}, 82 CodeSection: []*wasm.Code{{Body: []byte{wasm.OpcodeEnd}}}, 83 }), 84 expected: func(compiled CompiledModule) { 85 require.Nil(t, compiled.ImportedFunctions()) 86 require.Zero(t, len(compiled.ExportedFunctions())) 87 }, 88 }, 89 { 90 name: "FunctionSection exported", 91 wasm: binaryformat.EncodeModule(&wasm.Module{ 92 TypeSection: []*wasm.FunctionType{{Params: []api.ValueType{api.ValueTypeI32}}}, 93 FunctionSection: []wasm.Index{0}, 94 CodeSection: []*wasm.Code{{Body: []byte{wasm.OpcodeEnd}}}, 95 ExportSection: []*wasm.Export{{ 96 Type: wasm.ExternTypeFunc, 97 Name: "function", 98 Index: 0, 99 }}, 100 }), 101 expected: func(compiled CompiledModule) { 102 require.Nil(t, compiled.ImportedFunctions()) 103 f := compiled.ExportedFunctions()["function"] 104 require.Equal(t, []api.ValueType{api.ValueTypeI32}, f.ParamTypes()) 105 }, 106 }, 107 { 108 name: "MemorySection, but not exported", 109 wasm: binaryformat.EncodeModule(&wasm.Module{ 110 MemorySection: &wasm.Memory{Min: 2, Max: 3, IsMaxEncoded: true}, 111 }), 112 expected: func(compiled CompiledModule) { 113 require.Nil(t, compiled.ImportedMemories()) 114 require.Zero(t, len(compiled.ExportedMemories())) 115 }, 116 }, 117 { 118 name: "MemorySection exported", 119 wasm: binaryformat.EncodeModule(&wasm.Module{ 120 MemorySection: &wasm.Memory{Min: 2, Max: 3, IsMaxEncoded: true}, 121 ExportSection: []*wasm.Export{{ 122 Type: wasm.ExternTypeMemory, 123 Name: "memory", 124 Index: 0, 125 }}, 126 }), 127 expected: func(compiled CompiledModule) { 128 require.Nil(t, compiled.ImportedMemories()) 129 mem := compiled.ExportedMemories()["memory"] 130 require.Equal(t, uint32(2), mem.Min()) 131 max, ok := mem.Max() 132 require.Equal(t, uint32(3), max) 133 require.True(t, ok) 134 }, 135 }, 136 } 137 138 r := NewRuntime(testCtx) 139 defer r.Close(testCtx) 140 141 for _, tt := range tests { 142 tc := tt 143 144 t.Run(tc.name, func(t *testing.T) { 145 m, err := r.CompileModule(testCtx, tc.wasm) 146 require.NoError(t, err) 147 if tc.expected == nil { 148 tc.expected = func(CompiledModule) {} 149 } 150 tc.expected(m) 151 require.Equal(t, r.(*runtime).store.Engine, m.(*compiledModule).compiledEngine) 152 }) 153 } 154 } 155 156 func TestRuntime_CompileModule_Errors(t *testing.T) { 157 tests := []struct { 158 name string 159 wasm []byte 160 expectedErr string 161 }{ 162 { 163 name: "nil", 164 expectedErr: "binary == nil", 165 }, 166 { 167 name: "invalid binary", 168 wasm: append(binaryformat.Magic, []byte("yolo")...), 169 expectedErr: "invalid version header", 170 }, 171 { 172 name: "memory has too many pages", 173 wasm: binaryformat.EncodeModule(&wasm.Module{MemorySection: &wasm.Memory{Min: 2, Cap: 2, Max: 70000, IsMaxEncoded: true}}), 174 expectedErr: "section memory: max 70000 pages (4 Gi) over limit of 65536 pages (4 Gi)", 175 }, 176 } 177 178 r := NewRuntime(testCtx) 179 defer r.Close(testCtx) 180 181 for _, tt := range tests { 182 tc := tt 183 184 t.Run(tc.name, func(t *testing.T) { 185 _, err := r.CompileModule(testCtx, tc.wasm) 186 require.EqualError(t, err, tc.expectedErr) 187 }) 188 } 189 } 190 191 // TestModule_Memory only covers a couple cases to avoid duplication of internal/wasm/runtime_test.go 192 func TestModule_Memory(t *testing.T) { 193 tests := []struct { 194 name string 195 wasm []byte 196 expected bool 197 expectedLen uint32 198 }{ 199 { 200 name: "no memory", 201 wasm: binaryformat.EncodeModule(&wasm.Module{}), 202 }, 203 { 204 name: "memory exported, one page", 205 wasm: binaryformat.EncodeModule(&wasm.Module{ 206 MemorySection: &wasm.Memory{Min: 1}, 207 ExportSection: []*wasm.Export{{Name: "memory", Type: api.ExternTypeMemory}}, 208 }), 209 expected: true, 210 expectedLen: 65536, 211 }, 212 } 213 214 for _, tt := range tests { 215 tc := tt 216 217 t.Run(tc.name, func(t *testing.T) { 218 r := NewRuntime(testCtx) 219 defer r.Close(testCtx) 220 221 // Instantiate the module and get the export of the above memory 222 module, err := r.InstantiateModuleFromBinary(testCtx, tc.wasm) 223 require.NoError(t, err) 224 225 mem := module.ExportedMemory("memory") 226 if tc.expected { 227 require.Equal(t, tc.expectedLen, mem.Size(testCtx)) 228 } else { 229 require.Nil(t, mem) 230 } 231 }) 232 } 233 } 234 235 // TestModule_Global only covers a couple cases to avoid duplication of internal/wasm/global_test.go 236 func TestModule_Global(t *testing.T) { 237 globalVal := int64(100) // intentionally a value that differs in signed vs unsigned encoding 238 239 tests := []struct { 240 name string 241 module *wasm.Module // module as wat doesn't yet support globals 242 expected, expectedMutable bool 243 }{ 244 { 245 name: "no global", 246 module: &wasm.Module{}, 247 }, 248 { 249 name: "global not exported", 250 module: &wasm.Module{ 251 GlobalSection: []*wasm.Global{ 252 { 253 Type: &wasm.GlobalType{ValType: wasm.ValueTypeI64, Mutable: true}, 254 Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI64Const, Data: leb128.EncodeInt64(globalVal)}, 255 }, 256 }, 257 }, 258 }, 259 { 260 name: "global exported", 261 module: &wasm.Module{ 262 GlobalSection: []*wasm.Global{ 263 { 264 Type: &wasm.GlobalType{ValType: wasm.ValueTypeI64}, 265 Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI64Const, Data: leb128.EncodeInt64(globalVal)}, 266 }, 267 }, 268 ExportSection: []*wasm.Export{ 269 {Type: wasm.ExternTypeGlobal, Name: "global"}, 270 }, 271 }, 272 expected: true, 273 }, 274 { 275 name: "global exported and mutable", 276 module: &wasm.Module{ 277 GlobalSection: []*wasm.Global{ 278 { 279 Type: &wasm.GlobalType{ValType: wasm.ValueTypeI64, Mutable: true}, 280 Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI64Const, Data: leb128.EncodeInt64(globalVal)}, 281 }, 282 }, 283 ExportSection: []*wasm.Export{ 284 {Type: wasm.ExternTypeGlobal, Name: "global"}, 285 }, 286 }, 287 expected: true, 288 expectedMutable: true, 289 }, 290 } 291 292 for _, tt := range tests { 293 tc := tt 294 295 t.Run(tc.name, func(t *testing.T) { 296 r := NewRuntime(testCtx).(*runtime) 297 defer r.Close(testCtx) 298 299 code := &compiledModule{module: tc.module} 300 301 err := r.store.Engine.CompileModule(testCtx, code.module, nil) 302 require.NoError(t, err) 303 304 // Instantiate the module and get the export of the above global 305 module, err := r.InstantiateModule(testCtx, code, NewModuleConfig()) 306 require.NoError(t, err) 307 308 global := module.ExportedGlobal("global") 309 if !tc.expected { 310 require.Nil(t, global) 311 return 312 } 313 require.Equal(t, uint64(globalVal), global.Get(testCtx)) 314 315 mutable, ok := global.(api.MutableGlobal) 316 require.Equal(t, tc.expectedMutable, ok) 317 if ok { 318 mutable.Set(testCtx, 2) 319 require.Equal(t, uint64(2), global.Get(testCtx)) 320 } 321 }) 322 } 323 } 324 325 func TestRuntime_InstantiateModule_UsesContext(t *testing.T) { 326 r := NewRuntime(testCtx) 327 defer r.Close(testCtx) 328 329 // Define a function that will be set as the start function 330 var calledStart bool 331 start := func(ctx context.Context) { 332 calledStart = true 333 require.Equal(t, testCtx, ctx) 334 } 335 336 _, err := r.NewHostModuleBuilder("env"). 337 NewFunctionBuilder().WithFunc(start).Export("start"). 338 Instantiate(testCtx, r) 339 require.NoError(t, err) 340 341 one := uint32(1) 342 binary := binaryformat.EncodeModule(&wasm.Module{ 343 TypeSection: []*wasm.FunctionType{{}}, 344 ImportSection: []*wasm.Import{{Module: "env", Name: "start", Type: wasm.ExternTypeFunc, DescFunc: 0}}, 345 FunctionSection: []wasm.Index{0}, 346 CodeSection: []*wasm.Code{ 347 {Body: []byte{wasm.OpcodeCall, 0, wasm.OpcodeEnd}}, // Call the imported env.start. 348 }, 349 StartSection: &one, 350 }) 351 352 code, err := r.CompileModule(testCtx, binary) 353 require.NoError(t, err) 354 355 // Instantiate the module, which calls the start function. This will fail if the context wasn't as intended. 356 mod, err := r.InstantiateModule(testCtx, code, NewModuleConfig()) 357 require.NoError(t, err) 358 359 require.True(t, calledStart) 360 361 // Closing the module shouldn't remove the compiler cache 362 require.NoError(t, mod.Close(testCtx)) 363 require.Equal(t, uint32(2), r.(*runtime).store.Engine.CompiledModuleCount()) 364 } 365 366 // TestRuntime_InstantiateModuleFromBinary_DoesntEnforce_Start ensures wapc-go work when modules import WASI, but don't 367 // export "_start". 368 func TestRuntime_InstantiateModuleFromBinary_DoesntEnforce_Start(t *testing.T) { 369 r := NewRuntime(testCtx) 370 defer r.Close(testCtx) 371 372 binary := binaryformat.EncodeModule(&wasm.Module{ 373 MemorySection: &wasm.Memory{Min: 1}, 374 ExportSection: []*wasm.Export{{Name: "memory", Type: wasm.ExternTypeMemory, Index: 0}}, 375 }) 376 377 mod, err := r.InstantiateModuleFromBinary(testCtx, binary) 378 require.NoError(t, err) 379 require.NoError(t, mod.Close(testCtx)) 380 } 381 382 func TestRuntime_InstantiateModuleFromBinary_ErrorOnStart(t *testing.T) { 383 tests := []struct { 384 name, wasm string 385 }{ 386 { 387 name: "_start function", 388 wasm: `(module 389 (import "" "start" (func $start)) 390 (export "_start" (func $start)) 391 )`, 392 }, 393 { 394 name: ".start function", 395 wasm: `(module 396 (import "" "start" (func $start)) 397 (start $start) 398 )`, 399 }, 400 } 401 402 for _, tt := range tests { 403 tc := tt 404 405 t.Run(tc.name, func(t *testing.T) { 406 r := NewRuntime(testCtx) 407 defer r.Close(testCtx) 408 409 start := func() { 410 panic(errors.New("ice cream")) 411 } 412 413 host, err := r.NewHostModuleBuilder(""). 414 NewFunctionBuilder().WithFunc(start).Export("start"). 415 Instantiate(testCtx, r) 416 require.NoError(t, err) 417 418 // Start the module as a WASI command. We expect it to fail. 419 _, err = r.InstantiateModuleFromBinary(testCtx, []byte(tc.wasm)) 420 require.Error(t, err) 421 422 // Close the imported module, which should remove its compiler cache. 423 require.NoError(t, host.Close(testCtx)) 424 425 // The compiler cache of the importing module should be removed on error. 426 require.Zero(t, r.(*runtime).store.Engine.CompiledModuleCount()) 427 }) 428 } 429 } 430 431 // TestRuntime_InstantiateModule_WithName tests that we can pre-validate (cache) a module and instantiate it under 432 // different names. This pattern is used in wapc-go. 433 func TestRuntime_InstantiateModule_WithName(t *testing.T) { 434 r := NewRuntime(testCtx) 435 defer r.Close(testCtx) 436 437 base, err := r.CompileModule(testCtx, binaryNamedZero) 438 require.NoError(t, err) 439 440 require.Equal(t, "0", base.(*compiledModule).module.NameSection.ModuleName) 441 442 // Use the same runtime to instantiate multiple modules 443 internal := r.(*runtime).ns 444 m1, err := r.InstantiateModule(testCtx, base, NewModuleConfig().WithName("1")) 445 require.NoError(t, err) 446 447 require.Nil(t, internal.Module("0")) 448 require.Equal(t, internal.Module("1"), m1) 449 450 m2, err := r.InstantiateModule(testCtx, base, NewModuleConfig().WithName("2")) 451 require.NoError(t, err) 452 453 require.Nil(t, internal.Module("0")) 454 require.Equal(t, internal.Module("2"), m2) 455 } 456 457 func TestRuntime_InstantiateModule_ExitError(t *testing.T) { 458 r := NewRuntime(testCtx) 459 defer r.Close(testCtx) 460 461 start := func(ctx context.Context, m api.Module) { 462 require.NoError(t, m.CloseWithExitCode(ctx, 2)) 463 } 464 465 _, err := r.NewHostModuleBuilder("env"). 466 NewFunctionBuilder().WithFunc(start).Export("exit"). 467 Instantiate(testCtx, r) 468 require.NoError(t, err) 469 470 one := uint32(1) 471 binary := binaryformat.EncodeModule(&wasm.Module{ 472 TypeSection: []*wasm.FunctionType{{}}, 473 ImportSection: []*wasm.Import{{Module: "env", Name: "exit", Type: wasm.ExternTypeFunc, DescFunc: 0}}, 474 FunctionSection: []wasm.Index{0}, 475 CodeSection: []*wasm.Code{ 476 {Body: []byte{wasm.OpcodeCall, 0, wasm.OpcodeEnd}}, // Call the imported env.start. 477 }, 478 StartSection: &one, 479 }) 480 481 code, err := r.CompileModule(testCtx, binary) 482 require.NoError(t, err) 483 484 // Instantiate the module, which calls the start function. 485 _, err = r.InstantiateModule(testCtx, code, NewModuleConfig().WithName("call-exit")) 486 487 // Ensure the exit error propagated and didn't wrap. 488 require.Equal(t, err, sys.NewExitError("call-exit", 2)) 489 } 490 491 func TestRuntime_CloseWithExitCode(t *testing.T) { 492 bin := binaryformat.EncodeModule(&wasm.Module{ 493 TypeSection: []*wasm.FunctionType{{}}, 494 FunctionSection: []wasm.Index{0}, 495 CodeSection: []*wasm.Code{{Body: []byte{wasm.OpcodeEnd}}}, 496 ExportSection: []*wasm.Export{{Type: wasm.ExternTypeFunc, Index: 0, Name: "func"}}, 497 }) 498 499 tests := []struct { 500 name string 501 exitCode uint32 502 }{ 503 { 504 name: "exit code 0", 505 exitCode: uint32(0), 506 }, 507 { 508 name: "exit code 2", 509 exitCode: uint32(2), 510 }, 511 } 512 513 for _, tt := range tests { 514 tc := tt 515 t.Run(tc.name, func(t *testing.T) { 516 r := NewRuntime(testCtx) 517 518 code, err := r.CompileModule(testCtx, bin) 519 require.NoError(t, err) 520 521 // Instantiate two modules. 522 m1, err := r.InstantiateModule(testCtx, code, NewModuleConfig().WithName("mod1")) 523 require.NoError(t, err) 524 m2, err := r.InstantiateModule(testCtx, code, NewModuleConfig().WithName("mod2")) 525 require.NoError(t, err) 526 527 func1 := m1.ExportedFunction("func") 528 func2 := m2.ExportedFunction("func") 529 530 // Modules not closed so calls succeed 531 532 _, err = func1.Call(testCtx) 533 require.NoError(t, err) 534 535 _, err = func2.Call(testCtx) 536 require.NoError(t, err) 537 538 if tc.exitCode == 0 { 539 err = r.Close(testCtx) 540 } else { 541 err = r.CloseWithExitCode(testCtx, tc.exitCode) 542 } 543 require.NoError(t, err) 544 545 // Modules closed so calls fail 546 _, err = func1.Call(testCtx) 547 require.ErrorIs(t, err, sys.NewExitError("mod1", tc.exitCode)) 548 549 _, err = func2.Call(testCtx) 550 require.ErrorIs(t, err, sys.NewExitError("mod2", tc.exitCode)) 551 }) 552 } 553 } 554 555 func TestHostFunctionWithCustomContext(t *testing.T) { 556 const fistString = "hello" 557 const secondString = "hello call" 558 hostCtx := &HostContext{fistString} 559 r := NewRuntime(hostCtx) 560 defer r.Close(hostCtx) 561 562 // Define a function that will be set as the start function 563 var calledStart bool 564 var calledCall bool 565 start := func(ctx context.Context, module api.Module) { 566 hts, ok := ctx.(*HostContext) 567 if !ok { 568 t.Fatal("decorate call context could effect host ctx cast failed, please consider it.") 569 } 570 calledStart = true 571 require.NotNil(t, hts) 572 require.Equal(t, fistString, hts.Content) 573 } 574 575 callFunc := func(ctx context.Context, module api.Module) { 576 hts, ok := ctx.(*HostContext) 577 if !ok { 578 t.Fatal("decorate call context could effect host ctx cast failed, please consider it.") 579 } 580 calledCall = true 581 require.NotNil(t, hts) 582 require.Equal(t, secondString, hts.Content) 583 } 584 585 _, err := r.NewHostModuleBuilder("env"). 586 NewFunctionBuilder().WithFunc(start).Export("host"). 587 NewFunctionBuilder().WithFunc(callFunc).Export("host2"). 588 Instantiate(hostCtx, r) 589 require.NoError(t, err) 590 591 one := uint32(0) 592 binary := binaryformat.EncodeModule(&wasm.Module{ 593 TypeSection: []*wasm.FunctionType{{}, {}}, 594 ImportSection: []*wasm.Import{ 595 {Module: "env", Name: "host", Type: wasm.ExternTypeFunc, DescFunc: 0}, 596 {Module: "env", Name: "host2", Type: wasm.ExternTypeFunc, DescFunc: 0}, 597 }, 598 FunctionSection: []wasm.Index{0, 1}, 599 CodeSection: []*wasm.Code{ 600 {Body: []byte{wasm.OpcodeCall, 0, wasm.OpcodeEnd}}, // Call the imported env.host. 601 {Body: []byte{wasm.OpcodeCall, 1, wasm.OpcodeEnd}}, // Call the imported env.host. 602 }, 603 ExportSection: []*wasm.Export{ 604 {Type: api.ExternTypeFunc, Name: "callHost", Index: uint32(3)}, 605 }, 606 StartSection: &one, 607 }) 608 609 code, err := r.CompileModule(hostCtx, binary) 610 require.NoError(t, err) 611 612 // Instantiate the module, which calls the start function. This will fail if the context wasn't as intended. 613 ins, err := r.InstantiateModule(hostCtx, code, NewModuleConfig()) 614 require.NoError(t, err) 615 require.True(t, calledStart) 616 617 // add the new context content for call with used in host function 618 hostCtx.Content = secondString 619 _, err = ins.ExportedFunction("callHost").Call(hostCtx) 620 require.NoError(t, err) 621 require.True(t, calledCall) 622 } 623 624 func TestRuntime_Close_ClosesCompiledModules(t *testing.T) { 625 engine := &mockEngine{name: "mock", cachedModules: map[*wasm.Module]struct{}{}} 626 conf := *engineLessConfig 627 conf.newEngine = func(context.Context, api.CoreFeatures) wasm.Engine { 628 return engine 629 } 630 r := NewRuntimeWithConfig(testCtx, &conf) 631 defer r.Close(testCtx) 632 633 // Normally compiled modules are closed when instantiated but this is never instantiated. 634 _, err := r.CompileModule(testCtx, binaryNamedZero) 635 require.NoError(t, err) 636 require.Equal(t, uint32(1), engine.CompiledModuleCount()) 637 638 err = r.Close(testCtx) 639 require.NoError(t, err) 640 641 // Closing the runtime should remove the compiler cache 642 require.Zero(t, engine.CompiledModuleCount()) 643 } 644 645 type mockEngine struct { 646 name string 647 cachedModules map[*wasm.Module]struct{} 648 } 649 650 // CompileModule implements the same method as documented on wasm.Engine. 651 func (e *mockEngine) CompileModule(_ context.Context, module *wasm.Module, _ []experimental.FunctionListener) error { 652 e.cachedModules[module] = struct{}{} 653 return nil 654 } 655 656 // CompiledModuleCount implements the same method as documented on wasm.Engine. 657 func (e *mockEngine) CompiledModuleCount() uint32 { 658 return uint32(len(e.cachedModules)) 659 } 660 661 // DeleteCompiledModule implements the same method as documented on wasm.Engine. 662 func (e *mockEngine) DeleteCompiledModule(module *wasm.Module) { 663 delete(e.cachedModules, module) 664 } 665 666 // NewModuleEngine implements the same method as documented on wasm.Engine. 667 func (e *mockEngine) NewModuleEngine(_ string, _ *wasm.Module, _, _ []*wasm.FunctionInstance) (wasm.ModuleEngine, error) { 668 return nil, nil 669 }