From 4a60a8f4eba710eae13a411b4ddacf44ca9141b3 Mon Sep 17 00:00:00 2001 From: Takeshi Yoneda Date: Wed, 7 Dec 2022 12:45:06 +0900 Subject: [PATCH] ModuleInstance.Functions/Exports as non-pointer slice (#894) Signed-off-by: Takeshi Yoneda --- .../compiler/compiler_controlflow_test.go | 2 +- internal/engine/compiler/engine.go | 23 ++-- internal/engine/interpreter/interpreter.go | 24 ++--- .../engine/interpreter/interpreter_test.go | 3 +- internal/integration_test/bench/bench_test.go | 2 +- .../bench/hostfunc_bench_test.go | 24 ++--- internal/testing/enginetest/enginetest.go | 78 +++++++------- internal/wasm/call_context.go | 24 +++-- internal/wasm/engine.go | 5 +- internal/wasm/module.go | 18 ++-- internal/wasm/module_test.go | 6 +- internal/wasm/store.go | 56 ++++------ internal/wasm/store_test.go | 102 +++++++++++------- internal/wasm/table_test.go | 27 ++--- runtime_test.go | 2 +- 15 files changed, 201 insertions(+), 195 deletions(-) diff --git a/internal/engine/compiler/compiler_controlflow_test.go b/internal/engine/compiler/compiler_controlflow_test.go index 32eb223b..5a6b3e69 100644 --- a/internal/engine/compiler/compiler_controlflow_test.go +++ b/internal/engine/compiler/compiler_controlflow_test.go @@ -841,7 +841,7 @@ func TestCompiler_compileCall(t *testing.T) { moduleInstanceAddress: uintptr(unsafe.Pointer(env.moduleInstance)), }) env.module().Functions = append(env.module().Functions, - &wasm.FunctionInstance{Type: targetFunctionType, Idx: index}) + wasm.FunctionInstance{Type: targetFunctionType, Idx: index}) } // Now we start building the caller's code. diff --git a/internal/engine/compiler/engine.go b/internal/engine/compiler/engine.go index 25030269..c7ecb74a 100644 --- a/internal/engine/compiler/engine.go +++ b/internal/engine/compiler/engine.go @@ -49,8 +49,6 @@ type ( // as the underlying memory region is accessed by assembly directly by using // codesElement0Address. functions []*function - - importedFunctionCount uint32 } // callEngine holds context per moduleEngine.Call, and shared across all the @@ -545,15 +543,14 @@ func (e *engine) CompileModule(ctx context.Context, module *wasm.Module, listene } // NewModuleEngine implements the same method as documented on wasm.Engine. -func (e *engine) NewModuleEngine(name string, module *wasm.Module, importedFunctions, moduleFunctions []*wasm.FunctionInstance) (wasm.ModuleEngine, error) { - imported := len(importedFunctions) +func (e *engine) NewModuleEngine(name string, module *wasm.Module, functions []wasm.FunctionInstance) (wasm.ModuleEngine, error) { me := &moduleEngine{ - name: name, - functions: make([]*function, imported+len(moduleFunctions)), - importedFunctionCount: uint32(imported), + name: name, + functions: make([]*function, len(functions)), } - for i, f := range importedFunctions { + imported := int(module.ImportFuncCount()) + for i, f := range functions[:imported] { cf := f.Module.Engine.(*moduleEngine).functions[f.Idx] me.functions[i] = cf } @@ -567,12 +564,12 @@ func (e *engine) NewModuleEngine(name string, module *wasm.Module, importedFunct // TODO: Ideally we only have one []function slice, instead of a memory store and a slice of pointers. // This will require refactoring in assembly, so for now we simply have two slices. - functionsMem := make([]function, len(moduleFunctions)) + functionsMem := make([]function, len(functions[imported:])) for i, c := range codes { - f := moduleFunctions[i] - function := c.createFunction(f) - functionsMem[i] = function - me.functions[imported+i] = &functionsMem[i] + offset := imported + i + f := &functions[offset] + functionsMem[i] = c.createFunction(f) + me.functions[offset] = &functionsMem[i] } return me, nil } diff --git a/internal/engine/interpreter/interpreter.go b/internal/engine/interpreter/interpreter.go index 1ae92e94..1a858b9a 100644 --- a/internal/engine/interpreter/interpreter.go +++ b/internal/engine/interpreter/interpreter.go @@ -78,8 +78,7 @@ type moduleEngine struct { functions []*function // parentEngine holds *engine from which this module engine is created from. - parentEngine *engine - importedFunctionCount uint32 + parentEngine *engine } // callEngine holds context per moduleEngine.Call, and shared across all the @@ -263,17 +262,17 @@ func (e *engine) CompileModule(ctx context.Context, module *wasm.Module, listene } // NewModuleEngine implements the same method as documented on wasm.Engine. -func (e *engine) NewModuleEngine(name string, module *wasm.Module, importedFunctions, moduleFunctions []*wasm.FunctionInstance) (wasm.ModuleEngine, error) { - imported := uint32(len(importedFunctions)) +func (e *engine) NewModuleEngine(name string, module *wasm.Module, functions []wasm.FunctionInstance) (wasm.ModuleEngine, error) { me := &moduleEngine{ - name: name, - parentEngine: e, - importedFunctionCount: imported, + name: name, + parentEngine: e, + functions: make([]*function, len(functions)), } - for _, f := range importedFunctions { + imported := int(module.ImportFuncCount()) + for i, f := range functions[:imported] { cf := f.Module.Engine.(*moduleEngine).functions[f.Idx] - me.functions = append(me.functions, cf) + me.functions[i] = cf } codes, ok := e.getCodes(module) @@ -282,9 +281,10 @@ func (e *engine) NewModuleEngine(name string, module *wasm.Module, importedFunct } for i, c := range codes { - f := moduleFunctions[i] - insntantiatedcode := c.instantiate(f) - me.functions = append(me.functions, insntantiatedcode) + offset := i + imported + f := &functions[offset] + inst := c.instantiate(f) + me.functions[offset] = inst } return me, nil } diff --git a/internal/engine/interpreter/interpreter_test.go b/internal/engine/interpreter/interpreter_test.go index 2b856fe8..8ea6d0dd 100644 --- a/internal/engine/interpreter/interpreter_test.go +++ b/internal/engine/interpreter/interpreter_test.go @@ -495,8 +495,7 @@ func TestInterpreter_Compile(t *testing.T) { e := et.NewEngine(api.CoreFeaturesV1).(*engine) _, err := e.NewModuleEngine("foo", &wasm.Module{}, - nil, // imports - nil, // moduleFunctions + nil, // functions ) require.EqualError(t, err, "source module for foo must be compiled before instantiation") }) diff --git a/internal/integration_test/bench/bench_test.go b/internal/integration_test/bench/bench_test.go index 58db9c76..34c8f82e 100644 --- a/internal/integration_test/bench/bench_test.go +++ b/internal/integration_test/bench/bench_test.go @@ -51,7 +51,7 @@ func BenchmarkInitialization(b *testing.B) { runInitializationConcurrentBench(b, r) }) - if runtime.GOARCH == "amd64" || runtime.GOARCH == "arm64" { + if platform.CompilerSupported() { b.Run("compiler", func(b *testing.B) { r := createRuntime(b, wazero.NewRuntimeConfigCompiler()) runInitializationBench(b, r) diff --git a/internal/integration_test/bench/hostfunc_bench_test.go b/internal/integration_test/bench/hostfunc_bench_test.go index 310cf3a2..b4dd2fc4 100644 --- a/internal/integration_test/bench/hostfunc_bench_test.go +++ b/internal/integration_test/bench/hostfunc_bench_test.go @@ -122,7 +122,8 @@ func TestBenchmarkFunctionCall(t *testing.T) { } func getCallEngine(m *wasm.ModuleInstance, name string) (ce wasm.CallEngine, err error) { - f := m.Exports[name].Function + exp := m.Exports[name] + f := &m.Functions[exp.Index] if f == nil { err = fmt.Errorf("%s not found", name) return @@ -185,16 +186,16 @@ func setupHostCallBench(requireNoError func(error)) *wasm.ModuleInstance { hostModule.BuildFunctionDefinitions() host := &wasm.ModuleInstance{Name: "host", TypeIDs: []wasm.FunctionTypeID{0}} - host.Functions = host.BuildFunctions(hostModule) + host.Functions = host.BuildFunctions(hostModule, nil) host.BuildExports(hostModule.ExportSection) - goFn := host.Exports["go"].Function - goReflectFn := host.Exports["go-reflect"].Function - wasnFn := host.Exports["wasm"].Function + goFn := &host.Functions[host.Exports["go"].Index] + goReflectFn := &host.Functions[host.Exports["go-reflect"].Index] + wasnFn := &host.Functions[host.Exports["wasm"].Index] err := eng.CompileModule(testCtx, hostModule, nil) requireNoError(err) - hostME, err := eng.NewModuleEngine(host.Name, hostModule, nil, host.Functions) + hostME, err := eng.NewModuleEngine(host.Name, hostModule, host.Functions) requireNoError(err) linkModuleToEngine(host, hostME) @@ -209,9 +210,9 @@ func setupHostCallBench(requireNoError func(error)) *wasm.ModuleInstance { }, FunctionSection: []wasm.Index{0, 0, 0}, ExportSection: []*wasm.Export{ - {Name: callGoHostName, Type: wasm.ExternTypeFunc, Index: 2}, - {Name: callGoReflectHostName, Type: wasm.ExternTypeFunc, Index: 3}, - {Name: callWasmHostName, Type: wasm.ExternTypeFunc, Index: 4}, + {Name: callGoHostName, Type: wasm.ExternTypeFunc, Index: 3}, + {Name: callGoReflectHostName, Type: wasm.ExternTypeFunc, Index: 4}, + {Name: callWasmHostName, Type: wasm.ExternTypeFunc, Index: 5}, }, CodeSection: []*wasm.Code{ {Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeCall, 0, wasm.OpcodeEnd}}, // Calling the index 0 = host.go. @@ -228,11 +229,10 @@ func setupHostCallBench(requireNoError func(error)) *wasm.ModuleInstance { requireNoError(err) importing := &wasm.ModuleInstance{TypeIDs: []wasm.FunctionTypeID{0}} - importingFunctions := importing.BuildFunctions(importingModule) - importing.Functions = append([]*wasm.FunctionInstance{goFn, wasnFn}, importingFunctions...) + importingFunctions := importing.BuildFunctions(importingModule, []*wasm.FunctionInstance{goFn, goReflectFn, wasnFn}) importing.BuildExports(importingModule.ExportSection) - importingMe, err := eng.NewModuleEngine(importing.Name, importingModule, []*wasm.FunctionInstance{goFn, goReflectFn, wasnFn}, importingFunctions) + importingMe, err := eng.NewModuleEngine(importing.Name, importingModule, importingFunctions) requireNoError(err) linkModuleToEngine(importing, importingMe) diff --git a/internal/testing/enginetest/enginetest.go b/internal/testing/enginetest/enginetest.go index c7d8dde2..22956896 100644 --- a/internal/testing/enginetest/enginetest.go +++ b/internal/testing/enginetest/enginetest.go @@ -59,7 +59,7 @@ func RunTestEngine_NewModuleEngine(t *testing.T, et EngineTester) { e := et.NewEngine(api.CoreFeaturesV1) t.Run("error before instantiation", func(t *testing.T) { - _, err := e.NewModuleEngine("mymod", &wasm.Module{}, nil, nil) + _, err := e.NewModuleEngine("mymod", &wasm.Module{}, nil) require.EqualError(t, err, "source module for mymod must be compiled before instantiation") }) @@ -67,7 +67,7 @@ func RunTestEngine_NewModuleEngine(t *testing.T, et EngineTester) { m := &wasm.Module{} err := e.CompileModule(testCtx, m, nil) require.NoError(t, err) - me, err := e.NewModuleEngine(t.Name(), m, nil, nil) + me, err := e.NewModuleEngine(t.Name(), m, nil) require.NoError(t, err) require.Equal(t, t.Name(), me.Name()) }) @@ -100,15 +100,15 @@ func RunTestModuleEngine_Call(t *testing.T, et EngineTester) { // To use the function, we first need to add it to a module. module := &wasm.ModuleInstance{Name: t.Name(), TypeIDs: []wasm.FunctionTypeID{0}} - module.Functions = module.BuildFunctions(m) + module.Functions = module.BuildFunctions(m, nil) // Compile the module - me, err := e.NewModuleEngine(module.Name, m, nil, module.Functions) + me, err := e.NewModuleEngine(module.Name, m, module.Functions) require.NoError(t, err) linkModuleToEngine(module, me) // Ensure the base case doesn't fail: A single parameter should work as that matches the function signature. - fn := module.Functions[0] + fn := &module.Functions[0] ce, err := me.NewCallEngine(module.CallCtx, fn) require.NoError(t, err) @@ -156,9 +156,9 @@ func RunTestModuleEngine_LookupFunction(t *testing.T, et EngineTester) { {Min: 2, References: make([]wasm.Reference, 2), Type: wasm.RefTypeExternref}, {Min: 10, References: make([]wasm.Reference, 10), Type: wasm.RefTypeFuncref}, } - m.Functions = m.BuildFunctions(mod) + m.Functions = m.BuildFunctions(mod, nil) - me, err := e.NewModuleEngine(m.Name, mod, nil, m.Functions) + me, err := e.NewModuleEngine(m.Name, mod, m.Functions) require.NoError(t, err) linkModuleToEngine(m, me) @@ -225,12 +225,12 @@ func runTestModuleEngine_Call_HostFn_Mem(t *testing.T, et EngineTester, readMem }{ { name: callImportReadMemName, - fn: importing.Exports[callImportReadMemName].Function, + fn: &importing.Functions[importing.Exports[callImportReadMemName].Index], expected: importingMemoryVal, }, { name: callImportCallReadMemName, - fn: importing.Exports[callImportCallReadMemName].Function, + fn: &importing.Functions[importing.Exports[callImportCallReadMemName].Index], expected: importingMemoryVal, }, } @@ -274,17 +274,17 @@ func runTestModuleEngine_Call_HostFn(t *testing.T, et EngineTester, hostDivBy *w { name: divByWasmName, module: imported.CallCtx, - fn: imported.Exports[divByWasmName].Function, + fn: &imported.Functions[imported.Exports[divByWasmName].Index], }, { name: callDivByGoName, module: imported.CallCtx, - fn: imported.Exports[callDivByGoName].Function, + fn: &imported.Functions[imported.Exports[callDivByGoName].Index], }, { name: callImportCallDivByGoName, module: importing.CallCtx, - fn: importing.Exports[callImportCallDivByGoName].Function, + fn: &importing.Functions[importing.Exports[callImportCallDivByGoName].Index], }, } for _, tt := range tests { @@ -329,21 +329,21 @@ func RunTestModuleEngine_Call_Errors(t *testing.T, et EngineTester) { name: "wasm function not enough parameters", input: []uint64{}, module: imported.CallCtx, - fn: imported.Exports[divByWasmName].Function, + fn: &imported.Functions[imported.Exports[divByWasmName].Index], expectedErr: `expected 1 params, but passed 0`, }, { name: "wasm function too many parameters", input: []uint64{1, 2}, module: imported.CallCtx, - fn: imported.Exports[divByWasmName].Function, + fn: &imported.Functions[imported.Exports[divByWasmName].Index], expectedErr: `expected 1 params, but passed 2`, }, { name: "wasm function panics with wasmruntime.Error", input: []uint64{0}, module: imported.CallCtx, - fn: imported.Exports[divByWasmName].Function, + fn: &imported.Functions[imported.Exports[divByWasmName].Index], expectedErr: `wasm error: integer divide by zero wasm stack trace: imported.div_by.wasm(i32) i32`, @@ -352,7 +352,7 @@ wasm stack trace: name: "wasm calls host function that panics", input: []uint64{math.MaxUint32}, module: imported.CallCtx, - fn: imported.Exports[callDivByGoName].Function, + fn: &imported.Functions[imported.Exports[callDivByGoName].Index], expectedErr: `host-function panic (recovered by wazero) wasm stack trace: host.div_by.go(i32) i32 @@ -362,7 +362,7 @@ wasm stack trace: name: "wasm calls imported wasm that calls host function panics with runtime.Error", input: []uint64{0}, module: importing.CallCtx, - fn: importing.Exports[callImportCallDivByGoName].Function, + fn: &importing.Functions[importing.Exports[callImportCallDivByGoName].Index], expectedErr: `runtime error: integer divide by zero (recovered by wazero) wasm stack trace: host.div_by.go(i32) i32 @@ -373,7 +373,7 @@ wasm stack trace: name: "wasm calls imported wasm that calls host function that panics", input: []uint64{math.MaxUint32}, module: importing.CallCtx, - fn: importing.Exports[callImportCallDivByGoName].Function, + fn: &importing.Functions[importing.Exports[callImportCallDivByGoName].Index], expectedErr: `host-function panic (recovered by wazero) wasm stack trace: host.div_by.go(i32) i32 @@ -384,7 +384,7 @@ wasm stack trace: name: "wasm calls imported wasm calls host function panics with runtime.Error", input: []uint64{0}, module: importing.CallCtx, - fn: importing.Exports[callImportCallDivByGoName].Function, + fn: &importing.Functions[importing.Exports[callImportCallDivByGoName].Index], expectedErr: `runtime error: integer divide by zero (recovered by wazero) wasm stack trace: host.div_by.go(i32) i32 @@ -474,12 +474,12 @@ func RunTestModuleEngine_Memory(t *testing.T, et EngineTester) { var memory api.Memory = module.Memory // To use functions, we need to instantiate them (associate them with a ModuleInstance). - module.Functions = module.BuildFunctions(m) + module.Functions = module.BuildFunctions(m, nil) module.BuildExports(m.ExportSection) - grow, init := module.Functions[0], module.Functions[1] + grow, init := &module.Functions[0], &module.Functions[1] // Compile the module - me, err := e.NewModuleEngine(module.Name, m, nil, module.Functions) + me, err := e.NewModuleEngine(module.Name, m, module.Functions) require.NoError(t, err) linkModuleToEngine(module, me) @@ -606,11 +606,11 @@ func setupCallTests(t *testing.T, e wasm.Engine, divBy *wasm.Code, fnlf experime err := e.CompileModule(testCtx, hostModule, lns) require.NoError(t, err) host := &wasm.ModuleInstance{Name: hostModule.NameSection.ModuleName, TypeIDs: []wasm.FunctionTypeID{0}} - host.Functions = host.BuildFunctions(hostModule) + host.Functions = host.BuildFunctions(hostModule, nil) host.BuildExports(hostModule.ExportSection) - hostFn := host.Exports[divByGoName].Function + hostFn := &host.Functions[host.Exports[divByGoName].Index] - hostME, err := e.NewModuleEngine(host.Name, hostModule, nil, host.Functions) + hostME, err := e.NewModuleEngine(host.Name, hostModule, host.Functions) require.NoError(t, err) linkModuleToEngine(host, hostME) @@ -642,13 +642,13 @@ func setupCallTests(t *testing.T, e wasm.Engine, divBy *wasm.Code, fnlf experime require.NoError(t, err) imported := &wasm.ModuleInstance{Name: importedModule.NameSection.ModuleName, TypeIDs: []wasm.FunctionTypeID{0}} - importedFunctions := imported.BuildFunctions(importedModule) - imported.Functions = append([]*wasm.FunctionInstance{hostFn}, importedFunctions...) + importedFunctions := imported.BuildFunctions(importedModule, []*wasm.FunctionInstance{hostFn}) + imported.Functions = importedFunctions imported.BuildExports(importedModule.ExportSection) - callHostFn := imported.Exports[callDivByGoName].Function + callHostFn := &imported.Functions[imported.Exports[callDivByGoName].Index] // Compile the imported module - importedMe, err := e.NewModuleEngine(imported.Name, importedModule, []*wasm.FunctionInstance{hostFn}, importedFunctions) + importedMe, err := e.NewModuleEngine(imported.Name, importedModule, importedFunctions) require.NoError(t, err) linkModuleToEngine(imported, importedMe) @@ -676,12 +676,12 @@ func setupCallTests(t *testing.T, e wasm.Engine, divBy *wasm.Code, fnlf experime // Add the exported function. importing := &wasm.ModuleInstance{Name: importingModule.NameSection.ModuleName, TypeIDs: []wasm.FunctionTypeID{0}} - importingFunctions := importing.BuildFunctions(importingModule) - importing.Functions = append([]*wasm.FunctionInstance{callHostFn}, importingFunctions...) + importingFunctions := importing.BuildFunctions(importingModule, []*wasm.FunctionInstance{callHostFn}) + importing.Functions = importingFunctions importing.BuildExports(importingModule.ExportSection) // Compile the importing module - importingMe, err := e.NewModuleEngine(importing.Name, importingModule, []*wasm.FunctionInstance{callHostFn}, importingFunctions) + importingMe, err := e.NewModuleEngine(importing.Name, importingModule, importingFunctions) require.NoError(t, err) linkModuleToEngine(importing, importingMe) @@ -724,12 +724,12 @@ func setupCallMemTests(t *testing.T, e wasm.Engine, readMem *wasm.Code, fnlf exp err := e.CompileModule(testCtx, hostModule, nil) require.NoError(t, err) host := &wasm.ModuleInstance{Name: hostModule.NameSection.ModuleName, TypeIDs: []wasm.FunctionTypeID{0}} - host.Functions = host.BuildFunctions(hostModule) + host.Functions = host.BuildFunctions(hostModule, nil) host.BuildExports(hostModule.ExportSection) - readMemFn := host.Exports[readMemName].Function - callReadMemFn := host.Exports[callReadMemName].Function + readMemFn := &host.Functions[host.Exports[readMemName].Index] + callReadMemFn := &host.Functions[host.Exports[callReadMemName].Index] - hostME, err := e.NewModuleEngine(host.Name, hostModule, nil, host.Functions) + hostME, err := e.NewModuleEngine(host.Name, hostModule, host.Functions) require.NoError(t, err) linkModuleToEngine(host, hostME) @@ -766,13 +766,13 @@ func setupCallMemTests(t *testing.T, e wasm.Engine, readMem *wasm.Code, fnlf exp // Add the exported function. importing := &wasm.ModuleInstance{Name: importingModule.NameSection.ModuleName, TypeIDs: []wasm.FunctionTypeID{0}} - importingFunctions := importing.BuildFunctions(importingModule) + importingFunctions := importing.BuildFunctions(importingModule, []*wasm.FunctionInstance{readMemFn, callReadMemFn}) // Note: adds imported functions readMemFn and callReadMemFn at index 0 and 1. - importing.Functions = append([]*wasm.FunctionInstance{callReadMemFn, readMemFn}, importingFunctions...) + importing.Functions = importingFunctions importing.BuildExports(importingModule.ExportSection) // Compile the importing module - importingMe, err := e.NewModuleEngine(importing.Name, importingModule, []*wasm.FunctionInstance{readMemFn, callReadMemFn}, importingFunctions) + importingMe, err := e.NewModuleEngine(importing.Name, importingModule, importingFunctions) require.NoError(t, err) linkModuleToEngine(importing, importingMe) diff --git a/internal/wasm/call_context.go b/internal/wasm/call_context.go index 638883dc..2c012c11 100644 --- a/internal/wasm/call_context.go +++ b/internal/wasm/call_context.go @@ -127,11 +127,12 @@ func (m *CallContext) Memory() api.Memory { // ExportedMemory implements the same method as documented on api.Module. func (m *CallContext) ExportedMemory(name string) api.Memory { - exp, err := m.module.getExport(name, ExternTypeMemory) + _, err := m.module.getExport(name, ExternTypeMemory) if err != nil { return nil } - return exp.Memory + // We Assume that we have at most one memory. + return m.memory } // ExportedFunction implements the same method as documented on api.Module. @@ -141,7 +142,7 @@ func (m *CallContext) ExportedFunction(name string) api.Function { return nil } - return m.function(exp.Function) + return m.function(&m.module.Functions[exp.Index]) } // Module is exposed for emscripten. @@ -153,7 +154,7 @@ func (m *CallContext) Function(funcIdx Index) api.Function { if uint32(len(m.module.Functions)) < funcIdx { return nil } - return m.function(m.module.Functions[funcIdx]) + return m.function(&m.module.Functions[funcIdx]) } func (m *CallContext) function(f *FunctionInstance) api.Function { @@ -218,19 +219,20 @@ func (m *CallContext) ExportedGlobal(name string) api.Global { if err != nil { return nil } - if exp.Global.Type.Mutable { - return &mutableGlobal{exp.Global} + g := m.module.Globals[exp.Index] + if g.Type.Mutable { + return &mutableGlobal{g} } - valType := exp.Global.Type.ValType + valType := g.Type.ValType switch valType { case ValueTypeI32: - return globalI32(exp.Global.Val) + return globalI32(g.Val) case ValueTypeI64: - return globalI64(exp.Global.Val) + return globalI64(g.Val) case ValueTypeF32: - return globalF32(exp.Global.Val) + return globalF32(g.Val) case ValueTypeF64: - return globalF64(exp.Global.Val) + return globalF64(g.Val) default: panic(fmt.Errorf("BUG: unknown value type %X", valType)) } diff --git a/internal/wasm/engine.go b/internal/wasm/engine.go index eec6c15a..01f137e3 100644 --- a/internal/wasm/engine.go +++ b/internal/wasm/engine.go @@ -24,12 +24,11 @@ type Engine interface { // // * name is the name the module was instantiated with used for error handling. // * module is the source module from which moduleFunctions are instantiated. This is used for caching. - // * importedFunctions: functions this module imports, already compiled in this engine. - // * moduleFunctions: functions declared in this module that must be compiled. + // * functions: the list of FunctionInstance which exists in this module, including the imported ones. // // Note: Input parameters must be pre-validated with wasm.Module Validate, to ensure no fields are invalid // due to reasons such as out-of-bounds. - NewModuleEngine(name string, module *Module, importedFunctions, moduleFunctions []*FunctionInstance) (ModuleEngine, error) + NewModuleEngine(name string, module *Module, functions []FunctionInstance) (ModuleEngine, error) } // ModuleEngine implements function calls for a given module. diff --git a/internal/wasm/module.go b/internal/wasm/module.go index 16a015e2..0b6392b0 100644 --- a/internal/wasm/module.go +++ b/internal/wasm/module.go @@ -604,19 +604,22 @@ func (m *Module) buildGlobals(importedGlobals []*GlobalInstance, funcRefResolver // - This relies on data generated by Module.BuildFunctionDefinitions. // - This is exported for tests that don't call Instantiate, notably only // enginetest.go. -func (m *ModuleInstance) BuildFunctions(mod *Module) (fns []*FunctionInstance) { +func (m *ModuleInstance) BuildFunctions(mod *Module, importedFunctions []*FunctionInstance) (fns []FunctionInstance) { importCount := mod.ImportFuncCount() - // TODO: Ideally we only have one []FunctionInstance slice, instead of a memory store and a slice of pointers. - // This will require refactoring in assembly, so for now we simply have two slices. - fnsMem := make([]FunctionInstance, len(mod.FunctionSection)) - fns = make([]*FunctionInstance, len(mod.FunctionSection)) + fns = make([]FunctionInstance, importCount+uint32(len(mod.FunctionSection))) + m.Functions = fns + + for i, imp := range importedFunctions { + fns[i] = *imp + } for i, section := range mod.FunctionSection { + offset := uint32(i) + importCount code := mod.CodeSection[i] - d := mod.FunctionDefinitionSection[uint32(i)+importCount] + d := mod.FunctionDefinitionSection[offset] // This object is only referenced from a slice. Instead of creating a heap object // here and storing a pointer, we store the struct directly in the slice. This // reduces the number of heap objects which improves GC performance. - fnsMem[i] = FunctionInstance{ + fns[offset] = FunctionInstance{ IsHostFunction: code.IsHostFunction, LocalTypes: code.LocalTypes, Body: code.Body, @@ -627,7 +630,6 @@ func (m *ModuleInstance) BuildFunctions(mod *Module) (fns []*FunctionInstance) { Type: d.funcType, Definition: d, } - fns[i] = &fnsMem[i] } return } diff --git a/internal/wasm/module_test.go b/internal/wasm/module_test.go index 1ab2d8b2..6a2b7830 100644 --- a/internal/wasm/module_test.go +++ b/internal/wasm/module_test.go @@ -827,9 +827,9 @@ func TestModule_buildFunctions(t *testing.T) { // Note: This only returns module-defined functions, not imported ones. That's why the index starts with 1, not 0. instance := &ModuleInstance{Name: "counter", TypeIDs: []FunctionTypeID{0}} - instance.BuildFunctions(m) - for i, f := range instance.Functions { - require.Equal(t, i, f.Definition.Index()) + instance.BuildFunctions(m, nil) + for i, f := range instance.Functions[1:] { + require.Equal(t, uint32(i+1), f.Definition.Index()) require.Equal(t, nopCode.Body, f.Body) } } diff --git a/internal/wasm/store.go b/internal/wasm/store.go index 12753731..d4ee2cb0 100644 --- a/internal/wasm/store.go +++ b/internal/wasm/store.go @@ -54,8 +54,8 @@ type ( // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-moduleinst ModuleInstance struct { Name string - Exports map[string]*ExportInstance - Functions []*FunctionInstance + Exports map[string]ExportInstance + Functions []FunctionInstance Globals []*GlobalInstance // Memory is set when Module.MemorySection had a memory, regardless of whether it was exported. Memory *MemoryInstance @@ -95,11 +95,8 @@ type ( // // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-exportinst ExportInstance struct { - Type ExternType - Function *FunctionInstance - Global *GlobalInstance - Memory *MemoryInstance - Table *TableInstance + Type ExternType + Index Index } // FunctionInstance represents a function instance in a Store. @@ -161,8 +158,7 @@ type ( const maximumFunctionTypes = 1 << 27 // addSections adds section elements to the ModuleInstance -func (m *ModuleInstance) addSections(module *Module, importedFunctions, functions []*FunctionInstance, - importedGlobals, globals []*GlobalInstance, tables []*TableInstance, memory, importedMemory *MemoryInstance, +func (m *ModuleInstance) addSections(module *Module, importedGlobals, globals []*GlobalInstance, tables []*TableInstance, memory, importedMemory *MemoryInstance, types []*FunctionType, ) { m.Types = types @@ -170,7 +166,6 @@ func (m *ModuleInstance) addSections(module *Module, importedFunctions, function for i, t := range types { m.TypeIDIndex[t.string] = m.TypeIDs[i] } - m.Functions = append(importedFunctions, functions...) m.Globals = append(importedGlobals, globals...) m.Tables = tables @@ -225,23 +220,10 @@ func (m *ModuleInstance) applyTableInits(tables []*TableInstance, tableInits []t } func (m *ModuleInstance) BuildExports(exports []*Export) { - m.Exports = make(map[string]*ExportInstance, len(exports)) + m.Exports = make(map[string]ExportInstance, len(exports)) for _, exp := range exports { - index := exp.Index - var ei *ExportInstance - switch exp.Type { - case ExternTypeFunc: - ei = &ExportInstance{Type: exp.Type, Function: m.Functions[index]} - case ExternTypeGlobal: - ei = &ExportInstance{Type: exp.Type, Global: m.Globals[index]} - case ExternTypeMemory: - ei = &ExportInstance{Type: exp.Type, Memory: m.Memory} - case ExternTypeTable: - ei = &ExportInstance{Type: exp.Type, Table: m.Tables[index]} - } - // We already validated the duplicates during module validation phase. - m.Exports[exp.Name] = ei + m.Exports[exp.Name] = ExportInstance{Type: exp.Type, Index: exp.Index} } } @@ -279,13 +261,13 @@ func (m *ModuleInstance) applyData(data []*DataSegment) error { } // GetExport returns an export of the given name and type or errs if not exported or the wrong type. -func (m *ModuleInstance) getExport(name string, et ExternType) (*ExportInstance, error) { +func (m *ModuleInstance) getExport(name string, et ExternType) (ExportInstance, error) { exp, ok := m.Exports[name] if !ok { - return nil, fmt.Errorf("%q is not exported in module %q", name, m.Name) + return ExportInstance{}, fmt.Errorf("%q is not exported in module %q", name, m.Name) } if exp.Type != et { - return nil, fmt.Errorf("export %q in module %q is a %s, not a %s", name, m.Name, ExternTypeName(exp.Type), ExternTypeName(et)) + return ExportInstance{}, fmt.Errorf("export %q in module %q is a %s, not a %s", name, m.Name, ExternTypeName(exp.Type), ExternTypeName(et)) } return exp, nil } @@ -380,10 +362,10 @@ func (s *Store) instantiate( } m := &ModuleInstance{Name: name, TypeIDs: typeIDs} - functions := m.BuildFunctions(module) + functions := m.BuildFunctions(module, importedFunctions) // Plus, we are ready to compile functions. - m.Engine, err = s.Engine.NewModuleEngine(name, module, importedFunctions, functions) + m.Engine, err = s.Engine.NewModuleEngine(name, module, functions) if err != nil { return nil, err } @@ -391,7 +373,7 @@ func (s *Store) instantiate( globals, memory := module.buildGlobals(importedGlobals, m.Engine.FunctionInstanceReference), module.buildMemory() // Now we have all instances from imports and local ones, so ready to create a new ModuleInstance. - m.addSections(module, importedFunctions, functions, importedGlobals, globals, tables, importedMemory, memory, module.TypeSection) + m.addSections(module, importedGlobals, globals, tables, importedMemory, memory, module.TypeSection) // As of reference types proposal, data segment validation must happen after instantiation, // and the side effect must persist even if there's out of bounds error after instantiation. @@ -419,7 +401,7 @@ func (s *Store) instantiate( // Execute the start function. if module.StartSection != nil { funcIdx := *module.StartSection - f := m.Functions[funcIdx] + f := &m.Functions[funcIdx] ce, err := f.Module.Engine.NewCallEngine(callCtx, f) if err != nil { @@ -452,7 +434,7 @@ func resolveImports(module *Module, modules map[string]*ModuleInstance) ( return } - var imported *ExportInstance + var imported ExportInstance imported, err = m.getExport(i.Name, i.Type) if err != nil { return @@ -467,7 +449,7 @@ func resolveImports(module *Module, modules map[string]*ModuleInstance) ( return } expectedType := module.TypeSection[i.DescFunc] - importedFunction := imported.Function + importedFunction := &m.Functions[imported.Index] d := importedFunction.Definition if !expectedType.EqualsSignature(d.ParamTypes(), d.ResultTypes()) { @@ -479,7 +461,7 @@ func resolveImports(module *Module, modules map[string]*ModuleInstance) ( importedFunctions = append(importedFunctions, importedFunction) case ExternTypeTable: expected := i.DescTable - importedTable := imported.Table + importedTable := m.Tables[imported.Index] if expected.Type != importedTable.Type { err = errorInvalidImport(i, idx, fmt.Errorf("table type mismatch: %s != %s", RefTypeName(expected.Type), RefTypeName(importedTable.Type))) @@ -503,7 +485,7 @@ func resolveImports(module *Module, modules map[string]*ModuleInstance) ( importedTables = append(importedTables, importedTable) case ExternTypeMemory: expected := i.DescMem - importedMemory = imported.Memory + importedMemory = m.Memory if expected.Min > memoryBytesNumToPages(uint64(len(importedMemory.Buffer))) { err = errorMinSizeMismatch(i, idx, expected.Min, importedMemory.Min) @@ -516,7 +498,7 @@ func resolveImports(module *Module, modules map[string]*ModuleInstance) ( } case ExternTypeGlobal: expected := i.DescGlobal - importedGlobal := imported.Global + importedGlobal := m.Globals[imported.Index] if expected.Mutable != importedGlobal.Type.Mutable { err = errorInvalidImport(i, idx, fmt.Errorf("mutability mismatch: %t != %t", diff --git a/internal/wasm/store_test.go b/internal/wasm/store_test.go index 6043a343..dfa4ef97 100644 --- a/internal/wasm/store_test.go +++ b/internal/wasm/store_test.go @@ -380,7 +380,7 @@ func (e *mockEngine) CompiledModuleCount() uint32 { return 0 } func (e *mockEngine) DeleteCompiledModule(*Module) {} // NewModuleEngine implements the same method as documented on wasm.Engine. -func (e *mockEngine) NewModuleEngine(_ string, _ *Module, _, _ []*FunctionInstance) (ModuleEngine, error) { +func (e *mockEngine) NewModuleEngine(_ string, _ *Module, _ []FunctionInstance) (ModuleEngine, error) { if e.shouldCompileFail { return nil, fmt.Errorf("some engine creation error") } @@ -630,27 +630,26 @@ func Test_resolveImports(t *testing.T) { }) t.Run("export instance not found", func(t *testing.T) { modules := map[string]*ModuleInstance{ - moduleName: {Exports: map[string]*ExportInstance{}, Name: moduleName}, + moduleName: {Exports: map[string]ExportInstance{}, Name: moduleName}, } _, _, _, _, err := resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: "unknown"}}}, modules) require.EqualError(t, err, "\"unknown\" is not exported in module \"test\"") }) t.Run("func", func(t *testing.T) { t.Run("ok", func(t *testing.T) { - f := &FunctionInstance{ - Definition: &FunctionDefinition{funcType: &FunctionType{Results: []ValueType{ValueTypeF32}}}, - } - g := &FunctionInstance{ - Definition: &FunctionDefinition{funcType: &FunctionType{Results: []ValueType{ValueTypeI32}}}, + externMod := &ModuleInstance{ + Functions: []FunctionInstance{ + {Definition: &FunctionDefinition{funcType: &FunctionType{Results: []ValueType{ValueTypeF32}}}}, + {Definition: &FunctionDefinition{funcType: &FunctionType{Results: []ValueType{ValueTypeI32}}}}, + }, + Exports: map[string]ExportInstance{ + name: {Type: ExternTypeFunc, Index: 0}, + "": {Type: ExternTypeFunc, Index: 1}, + }, + Name: moduleName, } modules := map[string]*ModuleInstance{ - moduleName: { - Exports: map[string]*ExportInstance{ - name: {Function: f}, - "": {Function: g}, - }, - Name: moduleName, - }, + moduleName: externMod, } m := &Module{ TypeSection: []*FunctionType{{Results: []ValueType{ValueTypeF32}}, {Results: []ValueType{ValueTypeI32}}}, @@ -661,22 +660,25 @@ func Test_resolveImports(t *testing.T) { } functions, _, _, _, err := resolveImports(m, modules) require.NoError(t, err) - require.True(t, functionsContain(functions, f), "expected to find %v in %v", f, functions) - require.True(t, functionsContain(functions, g), "expected to find %v in %v", g, functions) + require.True(t, functionsContain(functions, &externMod.Functions[0]), "expected to find %v in %v", &externMod.Functions[0], functions) + require.True(t, functionsContain(functions, &externMod.Functions[1]), "expected to find %v in %v", &externMod.Functions[1], functions) }) t.Run("type out of range", func(t *testing.T) { modules := map[string]*ModuleInstance{ - moduleName: {Exports: map[string]*ExportInstance{name: {}}, Name: moduleName}, + moduleName: {Exports: map[string]ExportInstance{name: {}}, Name: moduleName}, } _, _, _, _, err := resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeFunc, DescFunc: 100}}}, modules) require.EqualError(t, err, "import[0] func[test.target]: function type out of range") }) t.Run("signature mismatch", func(t *testing.T) { - modules := map[string]*ModuleInstance{ - moduleName: {Exports: map[string]*ExportInstance{name: { - Function: &FunctionInstance{Definition: &FunctionDefinition{funcType: &FunctionType{}}}, - }}, Name: moduleName}, + externMod := &ModuleInstance{ + Functions: []FunctionInstance{{Definition: &FunctionDefinition{funcType: &FunctionType{}}}}, + Exports: map[string]ExportInstance{ + name: {Type: ExternTypeFunc, Index: 0}, + }, + Name: moduleName, } + modules := map[string]*ModuleInstance{moduleName: externMod} m := &Module{ TypeSection: []*FunctionType{{Results: []ValueType{ValueTypeF32}}}, ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeFunc, DescFunc: 0}}, @@ -689,7 +691,10 @@ func Test_resolveImports(t *testing.T) { t.Run("ok", func(t *testing.T) { g := &GlobalInstance{Type: &GlobalType{ValType: ValueTypeI32}} modules := map[string]*ModuleInstance{ - moduleName: {Exports: map[string]*ExportInstance{name: {Type: ExternTypeGlobal, Global: g}}, Name: moduleName}, + moduleName: { + Globals: []*GlobalInstance{g}, + Exports: map[string]ExportInstance{name: {Type: ExternTypeGlobal, Index: 0}}, Name: moduleName, + }, } _, globals, _, _, err := resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeGlobal, DescGlobal: g.Type}}}, modules) require.NoError(t, err) @@ -697,20 +702,28 @@ func Test_resolveImports(t *testing.T) { }) t.Run("mutability mismatch", func(t *testing.T) { modules := map[string]*ModuleInstance{ - moduleName: {Exports: map[string]*ExportInstance{name: { - Type: ExternTypeGlobal, - Global: &GlobalInstance{Type: &GlobalType{Mutable: false}}, - }}, Name: moduleName}, + moduleName: { + Globals: []*GlobalInstance{{Type: &GlobalType{Mutable: false}}}, + Exports: map[string]ExportInstance{name: { + Type: ExternTypeGlobal, + Index: 0, + }}, + Name: moduleName, + }, } _, _, _, _, err := resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeGlobal, DescGlobal: &GlobalType{Mutable: true}}}}, modules) require.EqualError(t, err, "import[0] global[test.target]: mutability mismatch: true != false") }) t.Run("type mismatch", func(t *testing.T) { modules := map[string]*ModuleInstance{ - moduleName: {Exports: map[string]*ExportInstance{name: { - Type: ExternTypeGlobal, - Global: &GlobalInstance{Type: &GlobalType{ValType: ValueTypeI32}}, - }}, Name: moduleName}, + moduleName: { + Globals: []*GlobalInstance{{Type: &GlobalType{ValType: ValueTypeI32}}}, + Exports: map[string]ExportInstance{name: { + Type: ExternTypeGlobal, + Index: 0, + }}, + Name: moduleName, + }, } _, _, _, _, err := resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeGlobal, DescGlobal: &GlobalType{ValType: ValueTypeF64}}}}, modules) require.EqualError(t, err, "import[0] global[test.target]: value type mismatch: f64 != i32") @@ -721,10 +734,13 @@ func Test_resolveImports(t *testing.T) { max := uint32(10) memoryInst := &MemoryInstance{Max: max} modules := map[string]*ModuleInstance{ - moduleName: {Exports: map[string]*ExportInstance{name: { - Type: ExternTypeMemory, + moduleName: { Memory: memoryInst, - }}, Name: moduleName}, + Exports: map[string]ExportInstance{name: { + Type: ExternTypeMemory, + }}, + Name: moduleName, + }, } _, _, _, memory, err := resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeMemory, DescMem: &Memory{Max: max}}}}, modules) require.NoError(t, err) @@ -733,10 +749,13 @@ func Test_resolveImports(t *testing.T) { t.Run("minimum size mismatch", func(t *testing.T) { importMemoryType := &Memory{Min: 2, Cap: 2} modules := map[string]*ModuleInstance{ - moduleName: {Exports: map[string]*ExportInstance{name: { - Type: ExternTypeMemory, + moduleName: { Memory: &MemoryInstance{Min: importMemoryType.Min - 1, Cap: 2}, - }}, Name: moduleName}, + Exports: map[string]ExportInstance{name: { + Type: ExternTypeMemory, + }}, + Name: moduleName, + }, } _, _, _, _, err := resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeMemory, DescMem: importMemoryType}}}, modules) require.EqualError(t, err, "import[0] memory[test.target]: minimum size mismatch: 2 > 1") @@ -745,10 +764,13 @@ func Test_resolveImports(t *testing.T) { max := uint32(10) importMemoryType := &Memory{Max: max} modules := map[string]*ModuleInstance{ - moduleName: {Exports: map[string]*ExportInstance{name: { - Type: ExternTypeMemory, + moduleName: { Memory: &MemoryInstance{Max: MemoryLimitPages}, - }}, Name: moduleName}, + Exports: map[string]ExportInstance{name: { + Type: ExternTypeMemory, + }}, + Name: moduleName, + }, } _, _, _, _, err := resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeMemory, DescMem: importMemoryType}}}, modules) require.EqualError(t, err, "import[0] memory[test.target]: maximum size mismatch: 10 < 65536") @@ -862,7 +884,7 @@ func TestModuleInstance_applyTableInits(t *testing.T) { }) t.Run("funcref", func(t *testing.T) { e := &mockEngine{} - me, err := e.NewModuleEngine("", nil, nil, nil) + me, err := e.NewModuleEngine("", nil, nil) me.(*mockModuleEngine).functionRefs = map[Index]Reference{0: 0xa, 1: 0xaa, 2: 0xaaa, 3: 0xaaaa} require.NoError(t, err) m := &ModuleInstance{Engine: me} diff --git a/internal/wasm/table_test.go b/internal/wasm/table_test.go index 72409522..20b68299 100644 --- a/internal/wasm/table_test.go +++ b/internal/wasm/table_test.go @@ -21,10 +21,11 @@ func Test_resolveImports_table(t *testing.T) { max := uint32(10) tableInst := &TableInstance{Max: &max} modules := map[string]*ModuleInstance{ - moduleName: {Exports: map[string]*ExportInstance{name: { - Type: ExternTypeTable, - Table: tableInst, - }}, Name: moduleName}, + moduleName: { + Tables: []*TableInstance{tableInst}, + Exports: map[string]ExportInstance{name: {Type: ExternTypeTable, Index: 0}}, + Name: moduleName, + }, } _, _, tables, _, err := resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeTable, DescTable: &Table{Max: &max}}}}, modules) require.NoError(t, err) @@ -34,10 +35,11 @@ func Test_resolveImports_table(t *testing.T) { t.Run("minimum size mismatch", func(t *testing.T) { importTableType := &Table{Min: 2} modules := map[string]*ModuleInstance{ - moduleName: {Exports: map[string]*ExportInstance{name: { - Type: ExternTypeTable, - Table: &TableInstance{Min: importTableType.Min - 1}, - }}, Name: moduleName}, + moduleName: { + Tables: []*TableInstance{{Min: importTableType.Min - 1}}, + Exports: map[string]ExportInstance{name: {Type: ExternTypeTable}}, + Name: moduleName, + }, } _, _, _, _, err := resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeTable, DescTable: importTableType}}}, modules) require.EqualError(t, err, "import[0] table[test.target]: minimum size mismatch: 2 > 1") @@ -46,10 +48,11 @@ func Test_resolveImports_table(t *testing.T) { max := uint32(10) importTableType := &Table{Max: &max} modules := map[string]*ModuleInstance{ - moduleName: {Exports: map[string]*ExportInstance{name: { - Type: ExternTypeTable, - Table: &TableInstance{Min: importTableType.Min - 1}, - }}, Name: moduleName}, + moduleName: { + Tables: []*TableInstance{{Min: importTableType.Min - 1}}, + Exports: map[string]ExportInstance{name: {Type: ExternTypeTable}}, + Name: moduleName, + }, } _, _, _, _, err := resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeTable, DescTable: importTableType}}}, modules) require.EqualError(t, err, "import[0] table[test.target]: maximum size mismatch: 10, but actual has no max") diff --git a/runtime_test.go b/runtime_test.go index 76a401bd..bea08e80 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -664,6 +664,6 @@ func (e *mockEngine) DeleteCompiledModule(module *wasm.Module) { } // NewModuleEngine implements the same method as documented on wasm.Engine. -func (e *mockEngine) NewModuleEngine(_ string, _ *wasm.Module, _, _ []*wasm.FunctionInstance) (wasm.ModuleEngine, error) { +func (e *mockEngine) NewModuleEngine(_ string, _ *wasm.Module, _ []wasm.FunctionInstance) (wasm.ModuleEngine, error) { return nil, nil }