From cd05a22df20507b8554054a282b4405b62ede63d Mon Sep 17 00:00:00 2001 From: Takeshi Yoneda Date: Wed, 22 Mar 2023 02:55:58 -0700 Subject: [PATCH] Reduces allocations during instantiation (#1267) Signed-off-by: Takeshi Yoneda --- internal/engine/compiler/engine.go | 4 +- internal/engine/compiler/engine_test.go | 1 + internal/engine/interpreter/interpreter.go | 4 +- .../bench/hostfunc_bench_test.go | 19 +- internal/testing/binaryencoding/element.go | 2 +- internal/testing/enginetest/enginetest.go | 85 +++++--- internal/wasm/binary/decoder.go | 3 +- internal/wasm/binary/decoder_test.go | 35 +++- internal/wasm/binary/element.go | 14 +- internal/wasm/binary/element_test.go | 42 ++-- internal/wasm/binary/section.go | 25 ++- internal/wasm/counts.go | 36 ---- internal/wasm/counts_test.go | 190 ------------------ internal/wasm/function_definition.go | 2 +- internal/wasm/function_definition_test.go | 8 +- internal/wasm/memory_definition.go | 2 +- internal/wasm/module.go | 55 +++-- internal/wasm/module_test.go | 169 +++++++++------- internal/wasm/store.go | 104 +++++----- internal/wasm/store_test.go | 147 +++++++------- internal/wasm/table.go | 89 ++++---- internal/wasm/table_test.go | 173 ++++++++-------- internal/wazeroir/compiler.go | 2 +- runtime.go | 2 +- 24 files changed, 529 insertions(+), 684 deletions(-) diff --git a/internal/engine/compiler/engine.go b/internal/engine/compiler/engine.go index 7bf7dffd..f3b10382 100644 --- a/internal/engine/compiler/engine.go +++ b/internal/engine/compiler/engine.go @@ -513,7 +513,7 @@ func (e *engine) CompileModule(_ context.Context, module *wasm.Module, listeners } var withGoFunc bool - importedFuncs := module.ImportFuncCount() + importedFuncs := module.ImportFunctionCount funcs := make([]*code, len(module.FunctionSection)) ln := len(listeners) cmp := newCompiler() @@ -556,7 +556,7 @@ func (e *engine) NewModuleEngine(name string, module *wasm.Module, functions []w functions: make([]function, len(functions)), } - imported := int(module.ImportFuncCount()) + imported := int(module.ImportFunctionCount) for i, f := range functions[:imported] { cf := f.Module.Engine.(*moduleEngine).functions[f.Definition.Index()] me.functions[i] = cf diff --git a/internal/engine/compiler/engine_test.go b/internal/engine/compiler/engine_test.go index 18a49499..3027ebb8 100644 --- a/internal/engine/compiler/engine_test.go +++ b/internal/engine/compiler/engine_test.go @@ -274,6 +274,7 @@ func TestCompiler_SliceAllocatedOnHeap(t *testing.T) { const callStackCorruption = "call_stack_corruption" const expectedReturnValue = 0x1 m := &wasm.Module{ + ImportFunctionCount: 1, TypeSection: []wasm.FunctionType{ {Params: []wasm.ValueType{}, Results: []wasm.ValueType{wasm.ValueTypeI32}, ResultNumInUint64: 1}, {Params: []wasm.ValueType{}, Results: []wasm.ValueType{}}, diff --git a/internal/engine/interpreter/interpreter.go b/internal/engine/interpreter/interpreter.go index 6804def0..44fe922a 100644 --- a/internal/engine/interpreter/interpreter.go +++ b/internal/engine/interpreter/interpreter.go @@ -244,7 +244,7 @@ func (e *engine) CompileModule(ctx context.Context, module *wasm.Module, listene } else { compiled, err = e.lowerIR(ir) if err != nil { - def := module.FunctionDefinitionSection[uint32(i)+module.ImportFuncCount()] + def := module.FunctionDefinitionSection[uint32(i)+module.ImportFunctionCount] return fmt.Errorf("failed to lower func[%s] to wazeroir: %w", def.DebugName(), err) } compiled.listener = lsn @@ -265,7 +265,7 @@ func (e *engine) NewModuleEngine(name string, module *wasm.Module, functions []w functions: make([]function, len(functions)), } - imported := int(module.ImportFuncCount()) + imported := int(module.ImportFunctionCount) for i, f := range functions[:imported] { cf := f.Module.Engine.(*moduleEngine).functions[f.Definition.Index()] me.functions[i] = cf diff --git a/internal/integration_test/bench/hostfunc_bench_test.go b/internal/integration_test/bench/hostfunc_bench_test.go index 69d4691d..cb2ca8ef 100644 --- a/internal/integration_test/bench/hostfunc_bench_test.go +++ b/internal/integration_test/bench/hostfunc_bench_test.go @@ -166,8 +166,11 @@ func setupHostCallBench(requireNoError func(error)) *wasm.ModuleInstance { } hostModule.BuildFunctionDefinitions() - host := &wasm.ModuleInstance{Name: "host", TypeIDs: []wasm.FunctionTypeID{0}} - host.Functions = host.BuildFunctions(hostModule, nil) + host := &wasm.ModuleInstance{ + Name: "host", TypeIDs: []wasm.FunctionTypeID{0}, + Functions: make([]wasm.FunctionInstance, len(hostModule.CodeSection)), + } + host.BuildFunctions(hostModule) host.BuildExports(hostModule.ExportSection) goFn := &host.Functions[host.Exports["go"].Index] goReflectFn := &host.Functions[host.Exports["go-reflect"].Index] @@ -182,7 +185,8 @@ func setupHostCallBench(requireNoError func(error)) *wasm.ModuleInstance { // Build the importing module. importingModule := &wasm.Module{ - TypeSection: []wasm.FunctionType{ft}, + ImportFunctionCount: 3, + TypeSection: []wasm.FunctionType{ft}, ImportSection: []wasm.Import{ // Placeholders for imports from hostModule. {Type: wasm.ExternTypeFunc}, @@ -208,11 +212,14 @@ func setupHostCallBench(requireNoError func(error)) *wasm.ModuleInstance { err = eng.CompileModule(testCtx, importingModule, nil, false) requireNoError(err) - importing := &wasm.ModuleInstance{TypeIDs: []wasm.FunctionTypeID{0}} - importingFunctions := importing.BuildFunctions(importingModule, []*wasm.FunctionInstance{goFn, goReflectFn, wasnFn}) + importing := &wasm.ModuleInstance{ + TypeIDs: []wasm.FunctionTypeID{0}, + Functions: []wasm.FunctionInstance{*goFn, *goReflectFn, *wasnFn, {}, {}, {}}, + } + importing.BuildFunctions(importingModule) importing.BuildExports(importingModule.ExportSection) - importingMe, err := eng.NewModuleEngine(importing.Name, importingModule, importingFunctions) + importingMe, err := eng.NewModuleEngine(importing.Name, importingModule, importing.Functions) requireNoError(err) linkModuleToEngine(importing, importingMe) diff --git a/internal/testing/binaryencoding/element.go b/internal/testing/binaryencoding/element.go index a238176c..5d56e6a5 100644 --- a/internal/testing/binaryencoding/element.go +++ b/internal/testing/binaryencoding/element.go @@ -28,7 +28,7 @@ func encodeElement(e *wasm.ElementSegment) (ret []byte) { ret = append(ret, encodeConstantExpression(e.OffsetExpr)...) ret = append(ret, leb128.EncodeUint32(uint32(len(e.Init)))...) for _, idx := range e.Init { - ret = append(ret, leb128.EncodeInt32(int32(*idx))...) + ret = append(ret, leb128.EncodeInt32(int32(idx))...) } } else { panic("TODO: support encoding for non-active elements in bulk-memory-operations proposal") diff --git a/internal/testing/enginetest/enginetest.go b/internal/testing/enginetest/enginetest.go index ba0e288d..fb4f7243 100644 --- a/internal/testing/enginetest/enginetest.go +++ b/internal/testing/enginetest/enginetest.go @@ -81,8 +81,9 @@ func RunTestEngine_MemoryGrowInRecursiveCall(t *testing.T, et EngineTester) { require.NoError(t, err) m := &wasm.Module{ - TypeSection: []wasm.FunctionType{{Params: []wasm.ValueType{}, Results: []wasm.ValueType{}}}, - FunctionSection: []wasm.Index{0, 0}, + ImportFunctionCount: 1, + TypeSection: []wasm.FunctionType{{Params: []wasm.ValueType{}, Results: []wasm.ValueType{}}}, + FunctionSection: []wasm.Index{0, 0}, CodeSection: []wasm.Code{ { Body: []byte{ @@ -164,8 +165,11 @@ func RunTestModuleEngine_Call(t *testing.T, et EngineTester) { require.NoError(t, err) // 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, nil) + module := &wasm.ModuleInstance{ + Name: t.Name(), TypeIDs: []wasm.FunctionTypeID{0}, + Functions: make([]wasm.FunctionInstance, len(m.FunctionSection)), + } + module.BuildFunctions(m) // Compile the module me, err := e.NewModuleEngine(module.Name, m, module.Functions) @@ -215,13 +219,16 @@ func RunTestModuleEngine_LookupFunction(t *testing.T, et EngineTester) { mod.BuildFunctionDefinitions() err := e.CompileModule(testCtx, mod, nil, false) require.NoError(t, err) - m := &wasm.ModuleInstance{TypeIDs: []wasm.FunctionTypeID{0, 1}} + m := &wasm.ModuleInstance{ + TypeIDs: []wasm.FunctionTypeID{0, 1}, + Functions: make([]wasm.FunctionInstance, len(mod.FunctionSection)), + } m.Tables = []*wasm.TableInstance{ {Min: 2, References: make([]wasm.Reference, 2), Type: wasm.RefTypeFuncref}, {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, nil) + m.BuildFunctions(mod) me, err := e.NewModuleEngine(m.Name, mod, m.Functions) require.NoError(t, err) @@ -529,11 +536,12 @@ func RunTestModuleEngine_Memory(t *testing.T, et EngineTester) { Memory: wasm.NewMemoryInstance(m.MemorySection), DataInstances: []wasm.DataInstance{m.DataSection[0].Init}, TypeIDs: []wasm.FunctionTypeID{0, 1}, + Functions: make([]wasm.FunctionInstance, len(m.FunctionSection)), } var memory api.Memory = module.Memory // To use functions, we need to instantiate them (associate them with a ModuleInstance). - module.Functions = module.BuildFunctions(m, nil) + module.BuildFunctions(m) module.BuildExports(m.ExportSection) grow, init := &module.Functions[0], &module.Functions[1] @@ -656,8 +664,11 @@ func setupCallTests(t *testing.T, e wasm.Engine, divBy *wasm.Code, fnlf experime lns := buildListeners(fnlf, hostModule) err := e.CompileModule(testCtx, hostModule, lns, false) require.NoError(t, err) - host := &wasm.ModuleInstance{Name: hostModule.NameSection.ModuleName, TypeIDs: []wasm.FunctionTypeID{0}} - host.Functions = host.BuildFunctions(hostModule, nil) + host := &wasm.ModuleInstance{ + Name: hostModule.NameSection.ModuleName, TypeIDs: []wasm.FunctionTypeID{0}, + Functions: make([]wasm.FunctionInstance, len(hostModule.FunctionSection)), + } + host.BuildFunctions(hostModule) host.BuildExports(hostModule.ExportSection) hostFn := &host.Functions[host.Exports[divByGoName].Index] @@ -666,9 +677,10 @@ func setupCallTests(t *testing.T, e wasm.Engine, divBy *wasm.Code, fnlf experime linkModuleToEngine(host, hostME) importedModule := &wasm.Module{ - ImportSection: []wasm.Import{{}}, - TypeSection: []wasm.FunctionType{ft}, - FunctionSection: []wasm.Index{0, 0}, + ImportFunctionCount: 1, + ImportSection: []wasm.Import{{}}, + TypeSection: []wasm.FunctionType{ft}, + FunctionSection: []wasm.Index{0, 0}, CodeSection: []wasm.Code{ {Body: divByWasm}, {Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeCall, byte(0), // Calling imported host function ^. @@ -692,22 +704,25 @@ func setupCallTests(t *testing.T, e wasm.Engine, divBy *wasm.Code, fnlf experime err = e.CompileModule(testCtx, importedModule, lns, false) require.NoError(t, err) - imported := &wasm.ModuleInstance{Name: importedModule.NameSection.ModuleName, TypeIDs: []wasm.FunctionTypeID{0}} - importedFunctions := imported.BuildFunctions(importedModule, []*wasm.FunctionInstance{hostFn}) - imported.Functions = importedFunctions + imported := &wasm.ModuleInstance{ + Name: importedModule.NameSection.ModuleName, TypeIDs: []wasm.FunctionTypeID{0}, + Functions: []wasm.FunctionInstance{*hostFn, {}, {}}, + } + imported.BuildFunctions(importedModule) imported.BuildExports(importedModule.ExportSection) callHostFn := &imported.Functions[imported.Exports[callDivByGoName].Index] // Compile the imported module - importedMe, err := e.NewModuleEngine(imported.Name, importedModule, importedFunctions) + importedMe, err := e.NewModuleEngine(imported.Name, importedModule, imported.Functions) require.NoError(t, err) linkModuleToEngine(imported, importedMe) // To test stack traces, call the same function from another module importingModule := &wasm.Module{ - TypeSection: []wasm.FunctionType{ft}, - ImportSection: []wasm.Import{{}}, - FunctionSection: []wasm.Index{0}, + ImportFunctionCount: 1, + TypeSection: []wasm.FunctionType{ft}, + ImportSection: []wasm.Import{{}}, + FunctionSection: []wasm.Index{0}, CodeSection: []wasm.Code{ {Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeCall, 0 /* only one imported function */, wasm.OpcodeEnd}}, }, @@ -726,13 +741,15 @@ func setupCallTests(t *testing.T, e wasm.Engine, divBy *wasm.Code, fnlf experime require.NoError(t, err) // Add the exported function. - importing := &wasm.ModuleInstance{Name: importingModule.NameSection.ModuleName, TypeIDs: []wasm.FunctionTypeID{0}} - importingFunctions := importing.BuildFunctions(importingModule, []*wasm.FunctionInstance{callHostFn}) - importing.Functions = importingFunctions + importing := &wasm.ModuleInstance{ + Name: importingModule.NameSection.ModuleName, TypeIDs: []wasm.FunctionTypeID{0}, + Functions: []wasm.FunctionInstance{*callHostFn, {}}, + } + importing.BuildFunctions(importingModule) importing.BuildExports(importingModule.ExportSection) // Compile the importing module - importingMe, err := e.NewModuleEngine(importing.Name, importingModule, importingFunctions) + importingMe, err := e.NewModuleEngine(importing.Name, importingModule, importing.Functions) require.NoError(t, err) linkModuleToEngine(importing, importingMe) @@ -762,8 +779,11 @@ func setupCallMemTests(t *testing.T, e wasm.Engine, readMem *wasm.Code, fnlf exp hostModule.BuildFunctionDefinitions() err := e.CompileModule(testCtx, hostModule, nil, false) require.NoError(t, err) - host := &wasm.ModuleInstance{Name: hostModule.NameSection.ModuleName, TypeIDs: []wasm.FunctionTypeID{0}} - host.Functions = host.BuildFunctions(hostModule, nil) + host := &wasm.ModuleInstance{ + Name: hostModule.NameSection.ModuleName, TypeIDs: []wasm.FunctionTypeID{0}, + Functions: make([]wasm.FunctionInstance, len(hostModule.FunctionSection)), + } + host.BuildFunctions(hostModule) host.BuildExports(hostModule.ExportSection) readMemFn := &host.Functions[host.Exports[readMemName].Index] @@ -772,7 +792,8 @@ func setupCallMemTests(t *testing.T, e wasm.Engine, readMem *wasm.Code, fnlf exp linkModuleToEngine(host, hostME) importingModule := &wasm.Module{ - TypeSection: []wasm.FunctionType{ft}, + ImportFunctionCount: 1, + TypeSection: []wasm.FunctionType{ft}, ImportSection: []wasm.Import{ // Placeholder for two import functions from `importedModule`. {Type: wasm.ExternTypeFunc, DescFunc: 0}, @@ -799,14 +820,16 @@ func setupCallMemTests(t *testing.T, e wasm.Engine, readMem *wasm.Code, fnlf exp require.NoError(t, err) // Add the exported function. - importing := &wasm.ModuleInstance{Name: importingModule.NameSection.ModuleName, TypeIDs: []wasm.FunctionTypeID{0}} - importingFunctions := importing.BuildFunctions(importingModule, []*wasm.FunctionInstance{readMemFn}) + importing := &wasm.ModuleInstance{ + Name: importingModule.NameSection.ModuleName, TypeIDs: []wasm.FunctionTypeID{0}, + Functions: []wasm.FunctionInstance{*readMemFn, {}}, + } + importing.BuildFunctions(importingModule) // Note: adds imported functions readMemFn and callReadMemFn at index 0 and 1. - importing.Functions = importingFunctions importing.BuildExports(importingModule.ExportSection) // Compile the importing module - importingMe, err := e.NewModuleEngine(importing.Name, importingModule, importingFunctions) + importingMe, err := e.NewModuleEngine(importing.Name, importingModule, importing.Functions) require.NoError(t, err) linkModuleToEngine(importing, importingMe) @@ -834,7 +857,7 @@ func buildListeners(factory experimental.FunctionListenerFactory, m *wasm.Module return nil } listeners := make([]experimental.FunctionListener, len(m.FunctionSection)) - importCount := m.ImportFuncCount() + importCount := m.ImportFunctionCount for i := 0; i < len(listeners); i++ { listeners[i] = factory.NewListener(&m.FunctionDefinitionSection[uint32(i)+importCount]) } diff --git a/internal/wasm/binary/decoder.go b/internal/wasm/binary/decoder.go index 6bb49414..45fe943e 100644 --- a/internal/wasm/binary/decoder.go +++ b/internal/wasm/binary/decoder.go @@ -106,7 +106,8 @@ func DecodeModule( case wasm.SectionIDType: m.TypeSection, err = decodeTypeSection(enabledFeatures, r) case wasm.SectionIDImport: - if m.ImportSection, err = decodeImportSection(r, memorySizer, memoryLimitPages, enabledFeatures); err != nil { + m.ImportSection, m.ImportFunctionCount, m.ImportGlobalCount, m.ImportMemoryCount, m.ImportTableCount, err = decodeImportSection(r, memorySizer, memoryLimitPages, enabledFeatures) + if err != nil { return nil, err // avoid re-wrapping the error. } case wasm.SectionIDFunction: diff --git a/internal/wasm/binary/decoder_test.go b/internal/wasm/binary/decoder_test.go index 4896b6ae..b1f86aac 100644 --- a/internal/wasm/binary/decoder_test.go +++ b/internal/wasm/binary/decoder_test.go @@ -41,6 +41,10 @@ func TestDecodeModule(t *testing.T) { { name: "type and import section", input: &wasm.Module{ + ImportFunctionCount: 2, + ImportTableCount: 1, + ImportMemoryCount: 1, + ImportGlobalCount: 3, TypeSection: []wasm.FunctionType{ {Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}}, {Params: []wasm.ValueType{f32, f32}, Results: []wasm.ValueType{f32}}, @@ -50,11 +54,37 @@ func TestDecodeModule(t *testing.T) { Module: "Math", Name: "Mul", Type: wasm.ExternTypeFunc, DescFunc: 1, - }, { + }, + { + Module: "foo", Name: "bar", + Type: wasm.ExternTypeTable, + DescTable: wasm.Table{Type: wasm.ValueTypeFuncref}, + }, + { Module: "Math", Name: "Add", Type: wasm.ExternTypeFunc, DescFunc: 0, }, + { + Module: "bar", Name: "mem", + Type: wasm.ExternTypeMemory, + DescMem: &wasm.Memory{IsMaxEncoded: true}, + }, + { + Module: "foo", Name: "bar2", + Type: wasm.ExternTypeGlobal, + DescGlobal: wasm.GlobalType{ValType: wasm.ValueTypeI32}, + }, + { + Module: "foo", Name: "bar3", + Type: wasm.ExternTypeGlobal, + DescGlobal: wasm.GlobalType{ValType: wasm.ValueTypeI32}, + }, + { + Module: "foo", Name: "bar4", + Type: wasm.ExternTypeGlobal, + DescGlobal: wasm.GlobalType{ValType: wasm.ValueTypeI32}, + }, }, }, }, @@ -68,7 +98,8 @@ func TestDecodeModule(t *testing.T) { { name: "type function and start section", input: &wasm.Module{ - TypeSection: []wasm.FunctionType{{}}, + ImportFunctionCount: 1, + TypeSection: []wasm.FunctionType{{}}, ImportSection: []wasm.Import{{ Module: "", Name: "hello", Type: wasm.ExternTypeFunc, diff --git a/internal/wasm/binary/element.go b/internal/wasm/binary/element.go index 056b5a11..a969fcc5 100644 --- a/internal/wasm/binary/element.go +++ b/internal/wasm/binary/element.go @@ -21,29 +21,29 @@ func ensureElementKindFuncRef(r *bytes.Reader) error { return nil } -func decodeElementInitValueVector(r *bytes.Reader) ([]*wasm.Index, error) { +func decodeElementInitValueVector(r *bytes.Reader) ([]wasm.Index, error) { vs, _, err := leb128.DecodeUint32(r) if err != nil { return nil, fmt.Errorf("get size of vector: %w", err) } - vec := make([]*wasm.Index, vs) + vec := make([]wasm.Index, vs) for i := range vec { u32, _, err := leb128.DecodeUint32(r) if err != nil { return nil, fmt.Errorf("read function index: %w", err) } - vec[i] = &u32 + vec[i] = u32 } return vec, nil } -func decodeElementConstExprVector(r *bytes.Reader, elemType wasm.RefType, enabledFeatures api.CoreFeatures) ([]*wasm.Index, error) { +func decodeElementConstExprVector(r *bytes.Reader, elemType wasm.RefType, enabledFeatures api.CoreFeatures) ([]wasm.Index, error) { vs, _, err := leb128.DecodeUint32(r) if err != nil { return nil, fmt.Errorf("failed to get the size of constexpr vector: %w", err) } - vec := make([]*wasm.Index, vs) + vec := make([]wasm.Index, vs) for i := range vec { var expr wasm.ConstantExpression err := decodeConstantExpression(r, enabledFeatures, &expr) @@ -56,13 +56,13 @@ func decodeElementConstExprVector(r *bytes.Reader, elemType wasm.RefType, enable return nil, fmt.Errorf("element type mismatch: want %s, but constexpr has funcref", wasm.RefTypeName(elemType)) } v, _, _ := leb128.LoadUint32(expr.Data) - vec[i] = &v + vec[i] = v case wasm.OpcodeRefNull: if elemType != expr.Data[0] { return nil, fmt.Errorf("element type mismatch: want %s, but constexpr has %s", wasm.RefTypeName(elemType), wasm.RefTypeName(expr.Data[0])) } - // vec[i] is already nil, so nothing to do. + vec[i] = wasm.ElementInitNullReference default: return nil, fmt.Errorf("const expr must be either ref.null or ref.func but was %s", wasm.InstructionName(expr.Opcode)) } diff --git a/internal/wasm/binary/element_test.go b/internal/wasm/binary/element_test.go index 9a3545ac..8eff438b 100644 --- a/internal/wasm/binary/element_test.go +++ b/internal/wasm/binary/element_test.go @@ -10,10 +10,6 @@ import ( "github.com/tetratelabs/wazero/internal/wasm" ) -func uint32Ptr(v uint32) *uint32 { - return &v -} - func Test_ensureElementKindFuncRef(t *testing.T) { require.NoError(t, ensureElementKindFuncRef(bytes.NewReader([]byte{0x0}))) require.Error(t, ensureElementKindFuncRef(bytes.NewReader([]byte{0x1}))) @@ -22,15 +18,15 @@ func Test_ensureElementKindFuncRef(t *testing.T) { func Test_decodeElementInitValueVector(t *testing.T) { tests := []struct { in []byte - exp []*wasm.Index + exp []wasm.Index }{ { in: []byte{0}, - exp: []*wasm.Index{}, + exp: []wasm.Index{}, }, { in: []byte{5, 1, 2, 3, 4, 5}, - exp: []*wasm.Index{uint32Ptr(1), uint32Ptr(2), uint32Ptr(3), uint32Ptr(4), uint32Ptr(5)}, + exp: []wasm.Index{1, 2, 3, 4, 5}, }, } @@ -48,12 +44,12 @@ func Test_decodeElementConstExprVector(t *testing.T) { tests := []struct { in []byte refType wasm.RefType - exp []*wasm.Index + exp []wasm.Index features api.CoreFeatures }{ { in: []byte{0}, - exp: []*wasm.Index{}, + exp: []wasm.Index{}, refType: wasm.RefTypeFuncref, features: api.CoreFeatureBulkMemoryOperations, }, @@ -63,7 +59,7 @@ func Test_decodeElementConstExprVector(t *testing.T) { wasm.OpcodeRefNull, wasm.RefTypeFuncref, wasm.OpcodeEnd, wasm.OpcodeRefFunc, 100, wasm.OpcodeEnd, }, - exp: []*wasm.Index{nil, uint32Ptr(100)}, + exp: []wasm.Index{wasm.ElementInitNullReference, 100}, refType: wasm.RefTypeFuncref, features: api.CoreFeatureBulkMemoryOperations, }, @@ -76,7 +72,7 @@ func Test_decodeElementConstExprVector(t *testing.T) { wasm.OpcodeEnd, wasm.OpcodeRefNull, wasm.RefTypeFuncref, wasm.OpcodeEnd, }, - exp: []*wasm.Index{nil, uint32Ptr(165675008), nil}, + exp: []wasm.Index{wasm.ElementInitNullReference, 165675008, wasm.ElementInitNullReference}, refType: wasm.RefTypeFuncref, features: api.CoreFeatureBulkMemoryOperations, }, @@ -86,7 +82,7 @@ func Test_decodeElementConstExprVector(t *testing.T) { wasm.OpcodeRefNull, wasm.RefTypeExternref, wasm.OpcodeEnd, wasm.OpcodeRefNull, wasm.RefTypeExternref, wasm.OpcodeEnd, }, - exp: []*wasm.Index{nil, nil}, + exp: []wasm.Index{wasm.ElementInitNullReference, wasm.ElementInitNullReference}, refType: wasm.RefTypeExternref, features: api.CoreFeatureBulkMemoryOperations, }, @@ -177,7 +173,7 @@ func TestDecodeElementSegment(t *testing.T) { }, exp: wasm.ElementSegment{ OffsetExpr: wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: []byte{1}}, - Init: []*wasm.Index{uint32Ptr(1), uint32Ptr(2), uint32Ptr(3), uint32Ptr(4), uint32Ptr(5)}, + Init: []wasm.Index{1, 2, 3, 4, 5}, Mode: wasm.ElementModeActive, Type: wasm.RefTypeFuncref, }, @@ -194,7 +190,7 @@ func TestDecodeElementSegment(t *testing.T) { }, exp: wasm.ElementSegment{ OffsetExpr: wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: []byte{0x80, 0}}, - Init: []*wasm.Index{uint32Ptr(1), uint32Ptr(2), uint32Ptr(3), uint32Ptr(4), uint32Ptr(5)}, + Init: []wasm.Index{1, 2, 3, 4, 5}, Mode: wasm.ElementModeActive, Type: wasm.RefTypeFuncref, }, @@ -209,7 +205,7 @@ func TestDecodeElementSegment(t *testing.T) { 5, 1, 2, 3, 4, 5, }, exp: wasm.ElementSegment{ - Init: []*wasm.Index{uint32Ptr(1), uint32Ptr(2), uint32Ptr(3), uint32Ptr(4), uint32Ptr(5)}, + Init: []wasm.Index{1, 2, 3, 4, 5}, Mode: wasm.ElementModePassive, Type: wasm.RefTypeFuncref, }, @@ -228,7 +224,7 @@ func TestDecodeElementSegment(t *testing.T) { }, exp: wasm.ElementSegment{ OffsetExpr: wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: []byte{0x80, 0}}, - Init: []*wasm.Index{uint32Ptr(1), uint32Ptr(2), uint32Ptr(3), uint32Ptr(4), uint32Ptr(5)}, + Init: []wasm.Index{1, 2, 3, 4, 5}, Mode: wasm.ElementModeActive, Type: wasm.RefTypeFuncref, }, @@ -247,7 +243,7 @@ func TestDecodeElementSegment(t *testing.T) { }, exp: wasm.ElementSegment{ OffsetExpr: wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: []byte{0x80, 0}}, - Init: []*wasm.Index{uint32Ptr(1), uint32Ptr(2), uint32Ptr(3), uint32Ptr(4), uint32Ptr(5)}, + Init: []wasm.Index{1, 2, 3, 4, 5}, Mode: wasm.ElementModeActive, Type: wasm.RefTypeFuncref, TableIndex: 10, @@ -277,7 +273,7 @@ func TestDecodeElementSegment(t *testing.T) { 5, 1, 2, 3, 4, 5, }, exp: wasm.ElementSegment{ - Init: []*wasm.Index{uint32Ptr(1), uint32Ptr(2), uint32Ptr(3), uint32Ptr(4), uint32Ptr(5)}, + Init: []wasm.Index{1, 2, 3, 4, 5}, Mode: wasm.ElementModeDeclarative, Type: wasm.RefTypeFuncref, }, @@ -299,7 +295,7 @@ func TestDecodeElementSegment(t *testing.T) { }, exp: wasm.ElementSegment{ OffsetExpr: wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: []byte{0x80, 1}}, - Init: []*wasm.Index{nil, uint32Ptr(165675008), nil}, + Init: []wasm.Index{wasm.ElementInitNullReference, 165675008, wasm.ElementInitNullReference}, Mode: wasm.ElementModeActive, Type: wasm.RefTypeFuncref, }, @@ -319,7 +315,7 @@ func TestDecodeElementSegment(t *testing.T) { wasm.OpcodeRefNull, wasm.RefTypeFuncref, wasm.OpcodeEnd, }, exp: wasm.ElementSegment{ - Init: []*wasm.Index{nil, uint32Ptr(165675008), nil}, + Init: []wasm.Index{wasm.ElementInitNullReference, 165675008, wasm.ElementInitNullReference}, Mode: wasm.ElementModePassive, Type: wasm.RefTypeFuncref, }, @@ -352,7 +348,7 @@ func TestDecodeElementSegment(t *testing.T) { }, exp: wasm.ElementSegment{ OffsetExpr: wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: []byte{0x80, 1}}, - Init: []*wasm.Index{nil, uint32Ptr(165675008), nil}, + Init: []wasm.Index{wasm.ElementInitNullReference, 165675008, wasm.ElementInitNullReference}, Mode: wasm.ElementModeActive, Type: wasm.RefTypeFuncref, }, @@ -376,7 +372,7 @@ func TestDecodeElementSegment(t *testing.T) { }, exp: wasm.ElementSegment{ OffsetExpr: wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: []byte{0x80, 1}}, - Init: []*wasm.Index{nil, uint32Ptr(165675008), nil}, + Init: []wasm.Index{wasm.ElementInitNullReference, 165675008, wasm.ElementInitNullReference}, Mode: wasm.ElementModeActive, Type: wasm.RefTypeFuncref, TableIndex: 10, @@ -415,7 +411,7 @@ func TestDecodeElementSegment(t *testing.T) { wasm.OpcodeEnd, }, exp: wasm.ElementSegment{ - Init: []*wasm.Index{nil, uint32Ptr(165675008)}, + Init: []wasm.Index{wasm.ElementInitNullReference, 165675008}, Mode: wasm.ElementModeDeclarative, Type: wasm.RefTypeFuncref, }, diff --git a/internal/wasm/binary/section.go b/internal/wasm/binary/section.go index 666456ba..b0fcf1e4 100644 --- a/internal/wasm/binary/section.go +++ b/internal/wasm/binary/section.go @@ -25,24 +25,37 @@ func decodeTypeSection(enabledFeatures api.CoreFeatures, r *bytes.Reader) ([]was return result, nil } +// decodeImportSection decodes the decoded import segments plus the count per wasm.ExternType. func decodeImportSection( r *bytes.Reader, memorySizer memorySizer, memoryLimitPages uint32, enabledFeatures api.CoreFeatures, -) ([]wasm.Import, error) { +) (result []wasm.Import, funcCount, globalCount, memoryCount, tableCount wasm.Index, err error) { vs, _, err := leb128.DecodeUint32(r) if err != nil { - return nil, fmt.Errorf("get size of vector: %w", err) + err = fmt.Errorf("get size of vector: %w", err) + return } - result := make([]wasm.Import, vs) + result = make([]wasm.Import, vs) for i := uint32(0); i < vs; i++ { - if err = decodeImport(r, i, memorySizer, memoryLimitPages, enabledFeatures, &result[i]); err != nil { - return nil, err + imp := &result[i] + if err = decodeImport(r, i, memorySizer, memoryLimitPages, enabledFeatures, imp); err != nil { + return + } + switch imp.Type { + case wasm.ExternTypeFunc: + funcCount++ + case wasm.ExternTypeGlobal: + globalCount++ + case wasm.ExternTypeMemory: + memoryCount++ + case wasm.ExternTypeTable: + tableCount++ } } - return result, nil + return } func decodeFunctionSection(r *bytes.Reader) ([]uint32, error) { diff --git a/internal/wasm/counts.go b/internal/wasm/counts.go index bbcc8b9c..685a4094 100644 --- a/internal/wasm/counts.go +++ b/internal/wasm/counts.go @@ -2,42 +2,6 @@ package wasm import "fmt" -// ImportFuncCount returns the possibly empty count of imported functions. This plus SectionElementCount of -// SectionIDFunction is the size of the function index. -func (m *Module) ImportFuncCount() uint32 { - return m.importCount(ExternTypeFunc) -} - -// ImportTableCount returns the possibly empty count of imported tables. This plus SectionElementCount of SectionIDTable -// is the size of the table index. -func (m *Module) ImportTableCount() uint32 { - return m.importCount(ExternTypeTable) -} - -// ImportMemoryCount returns the possibly empty count of imported memories. This plus SectionElementCount of -// SectionIDMemory is the size of the memory index. -func (m *Module) ImportMemoryCount() uint32 { - return m.importCount(ExternTypeMemory) // TODO: once validation happens on decode, this is zero or one. -} - -// ImportGlobalCount returns the possibly empty count of imported globals. This plus SectionElementCount of -// SectionIDGlobal is the size of the global index. -func (m *Module) ImportGlobalCount() uint32 { - return m.importCount(ExternTypeGlobal) -} - -// importCount returns the count of a specific type of import. This is important because it is easy to mistake the -// length of the import section with the count of a specific kind of import. -func (m *Module) importCount(et ExternType) (res uint32) { - for i := range m.ImportSection { - imp := &m.ImportSection[i] - if imp.Type == et { - res++ - } - } - return -} - // SectionElementCount returns the count of elements in a given section ID // // For example... diff --git a/internal/wasm/counts_test.go b/internal/wasm/counts_test.go index 445f62cd..6db4fdba 100644 --- a/internal/wasm/counts_test.go +++ b/internal/wasm/counts_test.go @@ -6,196 +6,6 @@ import ( "github.com/tetratelabs/wazero/internal/testing/require" ) -func TestModule_ImportFuncCount(t *testing.T) { - tests := []struct { - name string - input *Module - expected uint32 - }{ - { - name: "none", - input: &Module{}, - }, - { - name: "none with function section", - input: &Module{FunctionSection: []Index{0}}, - }, - { - name: "one", - input: &Module{ImportSection: []Import{{Type: ExternTypeFunc}}}, - expected: 1, - }, - { - name: "one with function section", - input: &Module{ImportSection: []Import{{Type: ExternTypeFunc}}, FunctionSection: []Index{0}}, - expected: 1, - }, - { - name: "one with other imports", - input: &Module{ImportSection: []Import{{Type: ExternTypeFunc}, {Type: ExternTypeMemory}}}, - expected: 1, - }, - { - name: "two", - input: &Module{ImportSection: []Import{{Type: ExternTypeFunc}, {Type: ExternTypeFunc}}}, - expected: 2, - }, - } - - for _, tt := range tests { - tc := tt - - t.Run(tc.name, func(t *testing.T) { - require.Equal(t, tc.expected, tc.input.ImportFuncCount()) - }) - } -} - -func TestModule_ImportTableCount(t *testing.T) { - tests := []struct { - name string - input *Module - expected uint32 - }{ - { - name: "none", - input: &Module{}, - }, - { - name: "none with table section", - input: &Module{TableSection: []Table{{Min: 1, Max: nil}}}, - }, - { - name: "one", - input: &Module{ImportSection: []Import{{Type: ExternTypeTable}}}, - expected: 1, - }, - { - name: "one with table section", - input: &Module{ - ImportSection: []Import{{Type: ExternTypeTable}}, - TableSection: []Table{{Min: 1, Max: nil}}, - }, - expected: 1, - }, - { - name: "one with other imports", - input: &Module{ImportSection: []Import{{Type: ExternTypeTable}, {Type: ExternTypeMemory}}}, - expected: 1, - }, - { - name: "two", - input: &Module{ImportSection: []Import{{Type: ExternTypeTable}, {Type: ExternTypeTable}}}, - expected: 2, - }, - } - - for _, tt := range tests { - tc := tt - - t.Run(tc.name, func(t *testing.T) { - require.Equal(t, tc.expected, tc.input.ImportTableCount()) - }) - } -} - -// TODO: once we fix up-front validation, this only needs to check zero or one -func TestModule_ImportMemoryCount(t *testing.T) { - tests := []struct { - name string - input *Module - expected uint32 - }{ - { - name: "none", - input: &Module{}, - }, - { - name: "none with memory section", - input: &Module{MemorySection: &Memory{Min: 1}}, - }, - { - name: "one", - input: &Module{ImportSection: []Import{{Type: ExternTypeMemory}}}, - expected: 1, - }, - { - name: "one with memory section", - input: &Module{ - ImportSection: []Import{{Type: ExternTypeMemory}}, - MemorySection: &Memory{Min: 1}, - }, - expected: 1, - }, - { - name: "one with other imports", - input: &Module{ImportSection: []Import{{Type: ExternTypeMemory}, {Type: ExternTypeTable}}}, - expected: 1, - }, - { - name: "two", - input: &Module{ImportSection: []Import{{Type: ExternTypeMemory}, {Type: ExternTypeMemory}}}, - expected: 2, - }, - } - - for _, tt := range tests { - tc := tt - - t.Run(tc.name, func(t *testing.T) { - require.Equal(t, tc.expected, tc.input.ImportMemoryCount()) - }) - } -} - -func TestModule_ImportGlobalCount(t *testing.T) { - tests := []struct { - name string - input *Module - expected uint32 - }{ - { - name: "none", - input: &Module{}, - }, - { - name: "none with global section", - input: &Module{GlobalSection: []Global{{Type: GlobalType{ValType: ValueTypeI64}}}}, - }, - { - name: "one", - input: &Module{ImportSection: []Import{{Type: ExternTypeGlobal}}}, - expected: 1, - }, - { - name: "one with global section", - input: &Module{ - ImportSection: []Import{{Type: ExternTypeGlobal}}, - GlobalSection: []Global{{Type: GlobalType{ValType: ValueTypeI64}}}, - }, - expected: 1, - }, - { - name: "one with other imports", - input: &Module{ImportSection: []Import{{Type: ExternTypeGlobal}, {Type: ExternTypeMemory}}}, - expected: 1, - }, - { - name: "two", - input: &Module{ImportSection: []Import{{Type: ExternTypeGlobal}, {Type: ExternTypeGlobal}}}, - expected: 2, - }, - } - - for _, tt := range tests { - tc := tt - - t.Run(tc.name, func(t *testing.T) { - require.Equal(t, tc.expected, tc.input.ImportGlobalCount()) - }) - } -} - func TestModule_SectionElementCount(t *testing.T) { i32, f32 := ValueTypeI32, ValueTypeF32 zero := uint32(0) diff --git a/internal/wasm/function_definition.go b/internal/wasm/function_definition.go index 31506309..433cd624 100644 --- a/internal/wasm/function_definition.go +++ b/internal/wasm/function_definition.go @@ -50,7 +50,7 @@ func (m *Module) BuildFunctionDefinitions() { resultNames = m.NameSection.ResultNames } - importCount := m.ImportFuncCount() + importCount := m.ImportFunctionCount m.FunctionDefinitionSection = make([]FunctionDefinition, importCount+uint32(len(m.FunctionSection))) importFuncIdx := Index(0) diff --git a/internal/wasm/function_definition_test.go b/internal/wasm/function_definition_test.go index a1cd0a3f..f73df87e 100644 --- a/internal/wasm/function_definition_test.go +++ b/internal/wasm/function_definition_test.go @@ -128,7 +128,8 @@ func TestModule_BuildFunctionDefinitions(t *testing.T) { { name: "with imports", m: &Module{ - ImportSection: []Import{*imp}, + ImportFunctionCount: 1, + ImportSection: []Import{*imp}, ExportSection: []Export{ {Name: "imported_function", Type: ExternTypeFunc, Index: 0}, {Name: "function_index=1", Type: ExternTypeFunc, Index: 1}, @@ -197,8 +198,9 @@ func TestModule_BuildFunctionDefinitions(t *testing.T) { { name: "with names", m: &Module{ - TypeSection: []FunctionType{v_v}, - ImportSection: []Import{{Module: "i", Name: "f", Type: ExternTypeFunc}}, + ImportFunctionCount: 1, + TypeSection: []FunctionType{v_v}, + ImportSection: []Import{{Module: "i", Name: "f", Type: ExternTypeFunc}}, NameSection: &NameSection{ ModuleName: "module", FunctionNames: NameMap{ diff --git a/internal/wasm/memory_definition.go b/internal/wasm/memory_definition.go index be497bd7..bf0acfc1 100644 --- a/internal/wasm/memory_definition.go +++ b/internal/wasm/memory_definition.go @@ -35,7 +35,7 @@ func (m *Module) BuildMemoryDefinitions() { moduleName = m.NameSection.ModuleName } - memoryCount := m.ImportMemoryCount() + memoryCount := m.ImportMemoryCount if m.MemorySection != nil { memoryCount++ } diff --git a/internal/wasm/module.go b/internal/wasm/module.go index 15e93971..e8647f00 100644 --- a/internal/wasm/module.go +++ b/internal/wasm/module.go @@ -42,6 +42,12 @@ type Module struct { // // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#import-section%E2%91%A0 ImportSection []Import + // ImportFunctionCount ImportGlobalCount ImportMemoryCount, and ImportTableCount are + // the cached import count per ExternType set during decoding. + ImportFunctionCount, + ImportGlobalCount, + ImportMemoryCount, + ImportTableCount Index // FunctionSection contains the index in TypeSection of each function defined in this module. // @@ -296,7 +302,7 @@ func (m *Module) validateGlobals(globals []GlobalType, numFuncts, maxGlobals uin // Global initialization constant expression can only reference the imported globals. // See the note on https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#constant-expressions%E2%91%A0 - importedGlobals := globals[:m.ImportGlobalCount()] + importedGlobals := globals[:m.ImportGlobalCount] for i := range m.GlobalSection { g := &m.GlobalSection[i] if err := validateConstExpression(importedGlobals, numFuncts, &g.Init, g.Type.ValType); err != nil { @@ -308,7 +314,7 @@ func (m *Module) validateGlobals(globals []GlobalType, numFuncts, maxGlobals uin func (m *Module) validateFunctions(enabledFeatures api.CoreFeatures, functions []Index, globals []GlobalType, memory *Memory, tables []Table, maximumFunctionIndex uint32) error { if uint32(len(functions)) > maximumFunctionIndex { - return fmt.Errorf("too many functions in a store") + return fmt.Errorf("too many functions in a module") } functionCount := m.SectionElementCount(SectionIDFunction) @@ -387,8 +393,8 @@ func (m *Module) declaredFunctionIndexes() (ret map[Index]struct{}, err error) { for i := range m.ElementSection { elem := &m.ElementSection[i] for _, index := range elem.Init { - if index != nil { - ret[*index] = struct{}{} + if index != ElementInitNullReference { + ret[index] = struct{}{} } } } @@ -398,7 +404,7 @@ func (m *Module) declaredFunctionIndexes() (ret map[Index]struct{}, err error) { func (m *Module) funcDesc(sectionID SectionID, sectionIndex Index) string { // Try to improve the error message by collecting any exports: var exportNames []string - funcIdx := sectionIndex + m.importCount(ExternTypeFunc) + funcIdx := sectionIndex + m.ImportFunctionCount for i := range m.ExportSection { exp := &m.ExportSection[i] if exp.Index == funcIdx && exp.Type == ExternTypeFunc { @@ -427,7 +433,7 @@ func (m *Module) validateMemory(memory *Memory, globals []GlobalType, _ api.Core // Constant expression can only reference imported globals. // https://github.com/WebAssembly/spec/blob/5900d839f38641989a9d8df2df4aee0513365d39/test/core/data.wast#L84-L91 - importedGlobals := globals[:m.ImportGlobalCount()] + importedGlobals := globals[:m.ImportGlobalCount] for i := range m.DataSection { d := &m.DataSection[i] if !d.IsPassive() { @@ -569,15 +575,15 @@ func (m *Module) validateDataCountSection() (err error) { return } -func (m *Module) buildGlobals(importedGlobals []*GlobalInstance, funcRefResolver func(funcIndex Index) Reference) (globals []*GlobalInstance) { - globals = make([]*GlobalInstance, len(m.GlobalSection)) - for i := range m.GlobalSection { - gs := &m.GlobalSection[i] - g := &GlobalInstance{Type: gs.Type} +func (m *ModuleInstance) buildGlobals(module *Module, funcRefResolver func(funcIndex Index) Reference) { + importedGlobals := m.Globals[:module.ImportGlobalCount] + for i := Index(0); i < Index(len(module.GlobalSection)); i++ { + gs := &module.GlobalSection[i] + g := &GlobalInstance{} + m.Globals[i+module.ImportGlobalCount] = g + g.Type = gs.Type g.initialize(importedGlobals, &gs.Init, funcRefResolver) - globals[i] = g } - return } // BuildFunctions generates function instances for all host or wasm-defined @@ -587,28 +593,20 @@ 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, importedFunctions []*FunctionInstance) (fns []FunctionInstance) { - importCount := mod.ImportFuncCount() - fns = make([]FunctionInstance, importCount+uint32(len(mod.FunctionSection))) - m.Functions = fns - - for i, imp := range importedFunctions { - fns[i] = *imp - } +func (m *ModuleInstance) BuildFunctions(mod *Module) { for i, section := range mod.FunctionSection { - offset := uint32(i) + importCount + offset := uint32(i) + mod.ImportFunctionCount 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. - fns[offset] = FunctionInstance{ + m.Functions[offset] = FunctionInstance{ TypeID: m.TypeIDs[section], Module: m, Type: d.funcType, Definition: d, } } - return } func paramNames(localNames IndirectNameMap, funcIdx uint32, paramLen int) []string { @@ -629,13 +627,12 @@ func paramNames(localNames IndirectNameMap, funcIdx uint32, paramLen int) []stri return nil } -func (m *Module) buildMemory() (mem *MemoryInstance) { - memSec := m.MemorySection +func (m *ModuleInstance) buildMemory(module *Module) { + memSec := module.MemorySection if memSec != nil { - mem = NewMemoryInstance(memSec) - mem.definition = &m.MemoryDefinitionSection[0] + m.Memory = NewMemoryInstance(memSec) + m.Memory.definition = &module.MemoryDefinitionSection[0] } - return } // Index is the offset in an index, not necessarily an absolute position in a Module section. This is because diff --git a/internal/wasm/module_test.go b/internal/wasm/module_test.go index 5fa11b80..8bf192f0 100644 --- a/internal/wasm/module_test.go +++ b/internal/wasm/module_test.go @@ -432,6 +432,7 @@ func TestModule_validateGlobals(t *testing.T) { }) t.Run("ok with imported global", func(t *testing.T) { m := Module{ + ImportGlobalCount: 1, GlobalSection: []Global{ { Type: GlobalType{ValType: ValueTypeI32}, @@ -464,7 +465,7 @@ func TestModule_validateFunctions(t *testing.T) { m := Module{} err := m.validateFunctions(api.CoreFeaturesV1, []uint32{1, 2, 3, 4}, nil, nil, nil, 3) require.Error(t, err) - require.EqualError(t, err, "too many functions in a store") + require.EqualError(t, err, "too many functions in a module") }) t.Run("function, but no code", func(t *testing.T) { m := Module{ @@ -509,11 +510,12 @@ func TestModule_validateFunctions(t *testing.T) { }) t.Run("in- exported after import", func(t *testing.T) { m := Module{ - TypeSection: []FunctionType{v_v}, - ImportSection: []Import{{Type: ExternTypeFunc}}, - FunctionSection: []Index{0}, - CodeSection: []Code{{Body: []byte{OpcodeF32Abs}}}, - ExportSection: []Export{{Name: "f1", Type: ExternTypeFunc, Index: 1}}, + ImportFunctionCount: 1, + TypeSection: []FunctionType{v_v}, + ImportSection: []Import{{Type: ExternTypeFunc}}, + FunctionSection: []Index{0}, + CodeSection: []Code{{Body: []byte{OpcodeF32Abs}}}, + ExportSection: []Export{{Name: "f1", Type: ExternTypeFunc, Index: 1}}, } err := m.validateFunctions(api.CoreFeaturesV1, nil, nil, nil, nil, MaximumFunctionIndex) require.Error(t, err) @@ -736,70 +738,80 @@ func TestModule_buildGlobals(t *testing.T) { const localFuncRefInstructionIndex = uint32(0xffff) minusOne := int32(-1) - m := Module{GlobalSection: []Global{ - { - Type: GlobalType{Mutable: true, ValType: ValueTypeF64}, - Init: ConstantExpression{ - Opcode: OpcodeF64Const, - Data: u64.LeBytes(api.EncodeF64(math.MaxFloat64)), - }, - }, - { - Type: GlobalType{Mutable: false, ValType: ValueTypeI32}, - Init: ConstantExpression{ - Opcode: OpcodeI32Const, - Data: leb128.EncodeInt32(math.MaxInt32), - }, - }, - { - Type: GlobalType{Mutable: false, ValType: ValueTypeI32}, - Init: ConstantExpression{ - Opcode: OpcodeI32Const, - Data: leb128.EncodeInt32(minusOne), - }, - }, - { - Type: GlobalType{Mutable: false, ValType: ValueTypeV128}, - Init: ConstantExpression{ - Opcode: OpcodeVecV128Const, - Data: []byte{ - 1, 0, 0, 0, 0, 0, 0, 0, - 2, 0, 0, 0, 0, 0, 0, 0, + m := &Module{ + ImportGlobalCount: 2, + GlobalSection: []Global{ + { + Type: GlobalType{Mutable: true, ValType: ValueTypeF64}, + Init: ConstantExpression{ + Opcode: OpcodeF64Const, + Data: u64.LeBytes(api.EncodeF64(math.MaxFloat64)), }, }, + { + Type: GlobalType{Mutable: false, ValType: ValueTypeI32}, + Init: ConstantExpression{ + Opcode: OpcodeI32Const, + Data: leb128.EncodeInt32(math.MaxInt32), + }, + }, + { + Type: GlobalType{Mutable: false, ValType: ValueTypeI32}, + Init: ConstantExpression{ + Opcode: OpcodeI32Const, + Data: leb128.EncodeInt32(minusOne), + }, + }, + { + Type: GlobalType{Mutable: false, ValType: ValueTypeV128}, + Init: ConstantExpression{ + Opcode: OpcodeVecV128Const, + Data: []byte{ + 1, 0, 0, 0, 0, 0, 0, 0, + 2, 0, 0, 0, 0, 0, 0, 0, + }, + }, + }, + { + Type: GlobalType{Mutable: false, ValType: ValueTypeExternref}, + Init: ConstantExpression{Opcode: OpcodeRefNull, Data: []byte{ValueTypeExternref}}, + }, + { + Type: GlobalType{Mutable: false, ValType: ValueTypeFuncref}, + Init: ConstantExpression{Opcode: OpcodeRefNull, Data: []byte{ValueTypeFuncref}}, + }, + { + Type: GlobalType{Mutable: false, ValType: ValueTypeFuncref}, + Init: ConstantExpression{Opcode: OpcodeRefFunc, Data: leb128.EncodeUint32(localFuncRefInstructionIndex)}, + }, + { + Type: GlobalType{Mutable: false, ValType: ValueTypeExternref}, + Init: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0}}, + }, + { + Type: GlobalType{Mutable: false, ValType: ValueTypeFuncref}, + Init: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{1}}, + }, }, - { - Type: GlobalType{Mutable: false, ValType: ValueTypeExternref}, - Init: ConstantExpression{Opcode: OpcodeRefNull, Data: []byte{ValueTypeExternref}}, - }, - { - Type: GlobalType{Mutable: false, ValType: ValueTypeFuncref}, - Init: ConstantExpression{Opcode: OpcodeRefNull, Data: []byte{ValueTypeFuncref}}, - }, - { - Type: GlobalType{Mutable: false, ValType: ValueTypeFuncref}, - Init: ConstantExpression{Opcode: OpcodeRefFunc, Data: leb128.EncodeUint32(localFuncRefInstructionIndex)}, - }, - { - Type: GlobalType{Mutable: false, ValType: ValueTypeExternref}, - Init: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0}}, - }, - { - Type: GlobalType{Mutable: false, ValType: ValueTypeFuncref}, - Init: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{1}}, - }, - }} + } imported := []*GlobalInstance{ {Type: GlobalType{ValType: ValueTypeExternref}, Val: 0x54321}, {Type: GlobalType{ValType: ValueTypeFuncref}, Val: 0x12345}, } - globals := m.buildGlobals(imported, func(funcIndex Index) Reference { + mi := &ModuleInstance{ + Globals: make([]*GlobalInstance, m.ImportGlobalCount+uint32(len(m.GlobalSection))), + } + + mi.Globals[0], mi.Globals[1] = imported[0], imported[1] + + mi.buildGlobals(m, func(funcIndex Index) Reference { require.Equal(t, localFuncRefInstructionIndex, funcIndex) return 0x99999 }) expectedGlobals := []*GlobalInstance{ + imported[0], imported[1], {Type: GlobalType{ValType: ValueTypeF64, Mutable: true}, Val: api.EncodeF64(math.MaxFloat64)}, {Type: GlobalType{ValType: ValueTypeI32, Mutable: false}, Val: uint64(int32(math.MaxInt32))}, // Higher bits are must be zeroed for i32 globals, not signed-extended. See #656. @@ -811,16 +823,17 @@ func TestModule_buildGlobals(t *testing.T) { {Type: GlobalType{ValType: ValueTypeExternref, Mutable: false}, Val: 0x54321}, {Type: GlobalType{ValType: ValueTypeFuncref, Mutable: false}, Val: 0x12345}, } - require.Equal(t, expectedGlobals, globals) + require.Equal(t, expectedGlobals, mi.Globals) } func TestModule_buildFunctions(t *testing.T) { nopCode := Code{Body: []byte{OpcodeEnd}} m := &Module{ - TypeSection: []FunctionType{v_v}, - ImportSection: []Import{{Type: ExternTypeFunc}}, - FunctionSection: []Index{0, 0, 0, 0, 0}, - CodeSection: []Code{nopCode, nopCode, nopCode, nopCode, nopCode}, + ImportFunctionCount: 1, + TypeSection: []FunctionType{v_v}, + ImportSection: []Import{{Type: ExternTypeFunc}}, + FunctionSection: []Index{0, 0, 0, 0, 0}, + CodeSection: []Code{nopCode, nopCode, nopCode, nopCode, nopCode}, FunctionDefinitionSection: []FunctionDefinition{ {index: 0, funcType: &v_v}, {index: 1, funcType: &v_v}, @@ -832,8 +845,11 @@ 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, nil) + instance := &ModuleInstance{ + Name: "counter", TypeIDs: []FunctionTypeID{0}, + Functions: make([]FunctionInstance, len(m.ImportSection)+len(m.FunctionSection)), + } + instance.BuildFunctions(m) for i, f := range instance.Functions[1:] { require.Equal(t, uint32(i+1), f.Definition.Index()) } @@ -841,19 +857,20 @@ func TestModule_buildFunctions(t *testing.T) { func TestModule_buildMemoryInstance(t *testing.T) { t.Run("nil", func(t *testing.T) { - m := Module{} - mem := m.buildMemory() - require.Nil(t, mem) + m := ModuleInstance{} + m.buildMemory(&Module{}) + require.Nil(t, m.Memory) }) t.Run("non-nil", func(t *testing.T) { min := uint32(1) max := uint32(10) mDef := MemoryDefinition{moduleName: "foo"} - m := Module{ + m := ModuleInstance{} + m.buildMemory(&Module{ MemorySection: &Memory{Min: min, Cap: min, Max: max}, MemoryDefinitionSection: []MemoryDefinition{mDef}, - } - mem := m.buildMemory() + }) + mem := m.Memory require.Equal(t, min, mem.Min) require.Equal(t, max, mem.Max) require.Equal(t, &mDef, mem.definition) @@ -932,15 +949,15 @@ func TestModule_declaredFunctionIndexes(t *testing.T) { ElementSection: []ElementSegment{ { Mode: ElementModeActive, - Init: []*Index{uint32Ptr(0), nil, uint32Ptr(5)}, + Init: []Index{0, ElementInitNullReference, 5}, }, { Mode: ElementModeDeclarative, - Init: []*Index{uint32Ptr(1), nil, uint32Ptr(5)}, + Init: []Index{1, ElementInitNullReference, 5}, }, { Mode: ElementModePassive, - Init: []*Index{uint32Ptr(5), uint32Ptr(2), nil, nil}, + Init: []Index{5, 2, ElementInitNullReference, ElementInitNullReference}, }, }, }, @@ -970,15 +987,15 @@ func TestModule_declaredFunctionIndexes(t *testing.T) { ElementSection: []ElementSegment{ { Mode: ElementModeActive, - Init: []*Index{uint32Ptr(0), nil, uint32Ptr(5)}, + Init: []Index{0, ElementInitNullReference, 5}, }, { Mode: ElementModeDeclarative, - Init: []*Index{uint32Ptr(1), nil, uint32Ptr(5)}, + Init: []Index{1, ElementInitNullReference, 5}, }, { Mode: ElementModePassive, - Init: []*Index{uint32Ptr(5), uint32Ptr(2), nil, nil}, + Init: []Index{5, 2, ElementInitNullReference, ElementInitNullReference}, }, }, }, diff --git a/internal/wasm/store.go b/internal/wasm/store.go index 98262587..2b49d565 100644 --- a/internal/wasm/store.go +++ b/internal/wasm/store.go @@ -138,20 +138,6 @@ type ( // The wazero specific limitations described at RATIONALE.md. const maximumFunctionTypes = 1 << 27 -// addSections adds section elements to the ModuleInstance -func (m *ModuleInstance) addSections(module *Module, importedGlobals, globals []*GlobalInstance, tables []*TableInstance, memory, importedMemory *MemoryInstance) { - m.Globals = append(importedGlobals, globals...) - m.Tables = tables - - if importedMemory != nil { - m.Memory = importedMemory - } else { - m.Memory = memory - } - - m.BuildExports(module.ExportSection) -} - func (m *ModuleInstance) buildElementInstances(elements []ElementSegment) { m.ElementInstances = make([]ElementInstance, len(elements)) for i, elm := range elements { @@ -162,9 +148,8 @@ func (m *ModuleInstance) buildElementInstances(elements []ElementSegment) { elemInst := &m.ElementInstances[i] elemInst.References = make([]Reference, len(inits)) elemInst.Type = RefTypeFuncref - for j, idxPtr := range inits { - if idxPtr != nil { - idx := *idxPtr + for j, idx := range inits { + if idx != ElementInitNullReference { elemInst.References[j] = m.Engine.FunctionInstanceReference(idx) } } @@ -172,12 +157,20 @@ func (m *ModuleInstance) buildElementInstances(elements []ElementSegment) { } } -func (m *ModuleInstance) applyTableInits(tables []*TableInstance, tableInits []tableInitEntry) { - for _, init := range tableInits { - table := tables[init.tableIndex] +func (m *ModuleInstance) applyElements(elems []validatedActiveElementSegment) { + for elemI := range elems { + elem := &elems[elemI] + var offset uint32 + if elem.opcode == OpcodeGlobalGet { + global := m.Globals[elem.arg] + offset = uint32(global.Val) + } else { + offset = elem.arg // constant + } + + table := m.Tables[elem.tableIndex] references := table.References - if int(init.offset)+len(init.functionIndexes) > len(references) || - int(init.offset)+init.nullExternRefCount > len(references) { + if int(offset)+len(elem.init) > len(references) { // ErrElementOffsetOutOfBounds is the error raised when the active element offset exceeds the table length. // Before CoreFeatureReferenceTypes, this was checked statically before instantiation, after the proposal, // this must be raised as runtime error (as in assert_trap in spectest), not even an instantiation error. @@ -189,13 +182,13 @@ func (m *ModuleInstance) applyTableInits(tables []*TableInstance, tableInits []t } if table.Type == RefTypeExternref { - for i := 0; i < init.nullExternRefCount; i++ { - references[init.offset+uint32(i)] = Reference(0) + for i := 0; i < len(elem.init); i++ { + references[offset+uint32(i)] = Reference(0) } } else { - for i, fnIndex := range init.functionIndexes { - if fnIndex != nil { - references[init.offset+uint32(i)] = m.Engine.FunctionInstanceReference(*fnIndex) + for i, fnIndex := range elem.init { + if fnIndex != ElementInitNullReference { + references[offset+uint32(i)] = m.Engine.FunctionInstanceReference(fnIndex) } } } @@ -327,31 +320,34 @@ func (s *Store) instantiate( modules map[string]*ModuleInstance, typeIDs []FunctionTypeID, ) (*CallContext, error) { - importedFunctions, importedGlobals, importedTables, importedMemory, err := resolveImports(module, modules) - if err != nil { + m := &ModuleInstance{Name: name, TypeIDs: typeIDs} + + m.Functions = make([]FunctionInstance, int(module.ImportFunctionCount)+len(module.FunctionSection)) + m.Tables = make([]*TableInstance, int(module.ImportTableCount)+len(module.TableSection)) + m.Globals = make([]*GlobalInstance, int(module.ImportGlobalCount)+len(module.GlobalSection)) + + if err := m.resolveImports(module, modules); err != nil { return nil, err } - tables, tableInit, err := module.buildTables(importedTables, importedGlobals, + err := m.buildTables(module, // As of reference-types proposal, boundary check must be done after instantiation. s.EnabledFeatures.IsEnabled(api.CoreFeatureReferenceTypes)) if err != nil { return nil, err } - m := &ModuleInstance{Name: name, TypeIDs: typeIDs} - functions := m.BuildFunctions(module, importedFunctions) + m.BuildFunctions(module) // Plus, we are ready to compile functions. - m.Engine, err = s.Engine.NewModuleEngine(name, module, functions) + m.Engine, err = s.Engine.NewModuleEngine(name, module, m.Functions) if err != nil { return nil, err } - 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, importedGlobals, globals, tables, importedMemory, memory) + m.buildGlobals(module, m.Engine.FunctionInstanceReference) + m.buildMemory(module) + m.BuildExports(module.ExportSection) // 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. @@ -370,7 +366,7 @@ func (s *Store) instantiate( return nil, err } - m.applyTableInits(tables, tableInit) + m.applyElements(module.validatedActiveElementSegments) // Compile the default context for calls to this module. callCtx := NewCallContext(s, m, sysCtx) @@ -398,23 +394,18 @@ func (s *Store) instantiate( return m.CallCtx, nil } -func resolveImports(module *Module, modules map[string]*ModuleInstance) ( - importedFunctions []*FunctionInstance, - importedGlobals []*GlobalInstance, - importedTables []*TableInstance, - importedMemory *MemoryInstance, - err error, -) { +func (m *ModuleInstance) resolveImports(module *Module, importedModules map[string]*ModuleInstance) (err error) { + var fs, gs, tables int for idx := range module.ImportSection { i := &module.ImportSection[idx] - m, ok := modules[i.Module] + importedModule, ok := importedModules[i.Module] if !ok { err = fmt.Errorf("module[%s] not instantiated", i.Module) return } var imported ExportInstance - imported, err = m.getExport(i.Name, i.Type) + imported, err = importedModule.getExport(i.Name, i.Type) if err != nil { return } @@ -428,7 +419,7 @@ func resolveImports(module *Module, modules map[string]*ModuleInstance) ( return } expectedType := &module.TypeSection[i.DescFunc] - importedFunction := &m.Functions[imported.Index] + importedFunction := &importedModule.Functions[imported.Index] d := importedFunction.Definition if !expectedType.EqualsSignature(d.ParamTypes(), d.ResultTypes()) { @@ -436,11 +427,11 @@ func resolveImports(module *Module, modules map[string]*ModuleInstance) ( err = errorInvalidImport(i, idx, fmt.Errorf("signature mismatch: %s != %s", expectedType, actualType)) return } - - importedFunctions = append(importedFunctions, importedFunction) + m.Functions[fs] = *importedFunction + fs++ case ExternTypeTable: expected := i.DescTable - importedTable := m.Tables[imported.Index] + importedTable := importedModule.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))) @@ -461,10 +452,11 @@ func resolveImports(module *Module, modules map[string]*ModuleInstance) ( return } } - importedTables = append(importedTables, importedTable) + m.Tables[tables] = importedTable + tables++ case ExternTypeMemory: expected := i.DescMem - importedMemory = m.Memory + importedMemory := importedModule.Memory if expected.Min > memoryBytesNumToPages(uint64(len(importedMemory.Buffer))) { err = errorMinSizeMismatch(i, idx, expected.Min, importedMemory.Min) @@ -475,9 +467,10 @@ func resolveImports(module *Module, modules map[string]*ModuleInstance) ( err = errorMaxSizeMismatch(i, idx, expected.Max, importedMemory.Max) return } + m.Memory = importedMemory case ExternTypeGlobal: expected := i.DescGlobal - importedGlobal := m.Globals[imported.Index] + importedGlobal := importedModule.Globals[imported.Index] if expected.Mutable != importedGlobal.Type.Mutable { err = errorInvalidImport(i, idx, fmt.Errorf("mutability mismatch: %t != %t", @@ -490,7 +483,8 @@ func resolveImports(module *Module, modules map[string]*ModuleInstance) ( ValueTypeName(expected.ValType), ValueTypeName(importedGlobal.Type.ValType))) return } - importedGlobals = append(importedGlobals, importedGlobal) + m.Globals[gs] = importedGlobal + gs++ } } return diff --git a/internal/wasm/store_test.go b/internal/wasm/store_test.go index b2dfcc6d..d888300b 100644 --- a/internal/wasm/store_test.go +++ b/internal/wasm/store_test.go @@ -154,6 +154,7 @@ func TestStore_CloseWithExitCode(t *testing.T) { require.NoError(t, err) m2, err := s.Instantiate(testCtx, &Module{ + ImportFunctionCount: 1, TypeSection: []FunctionType{v_v}, ImportSection: []Import{{Type: ExternTypeFunc, Module: importedModuleName, Name: "fn", DescFunc: 0}}, MemorySection: &Memory{Min: 1, Cap: 1}, @@ -194,6 +195,7 @@ func TestStore_hammer(t *testing.T) { require.True(t, ok) importingModule := &Module{ + ImportFunctionCount: 1, TypeSection: []FunctionType{v_v}, FunctionSection: []uint32{0}, CodeSection: []Code{{Body: []byte{OpcodeEnd}}}, @@ -248,6 +250,7 @@ func TestStore_hammer_close(t *testing.T) { require.True(t, ok) importingModule := &Module{ + ImportFunctionCount: 1, TypeSection: []FunctionType{v_v}, FunctionSection: []uint32{0}, CodeSection: []Code{{Body: []byte{OpcodeEnd}}}, @@ -342,8 +345,9 @@ func TestStore_Instantiate_Errors(t *testing.T) { engine.shouldCompileFail = true importingModule := &Module{ - TypeSection: []FunctionType{v_v}, - FunctionSection: []uint32{0, 0}, + ImportFunctionCount: 1, + TypeSection: []FunctionType{v_v}, + FunctionSection: []uint32{0, 0}, CodeSection: []Code{ {Body: []byte{OpcodeEnd}}, {Body: []byte{OpcodeEnd}}, @@ -371,10 +375,11 @@ func TestStore_Instantiate_Errors(t *testing.T) { startFuncIndex := uint32(1) importingModule := &Module{ - TypeSection: []FunctionType{v_v}, - FunctionSection: []uint32{0}, - CodeSection: []Code{{Body: []byte{OpcodeEnd}}}, - StartSection: &startFuncIndex, + ImportFunctionCount: 1, + TypeSection: []FunctionType{v_v}, + FunctionSection: []uint32{0}, + CodeSection: []Code{{Body: []byte{OpcodeEnd}}}, + StartSection: &startFuncIndex, ImportSection: []Import{ {Type: ExternTypeFunc, Module: importedModuleName, Name: "fn", DescFunc: 0}, }, @@ -640,14 +645,16 @@ func Test_resolveImports(t *testing.T) { t.Run("module not instantiated", func(t *testing.T) { modules := map[string]*ModuleInstance{} - _, _, _, _, err := resolveImports(&Module{ImportSection: []Import{{Module: "unknown", Name: "unknown"}}}, modules) + m := &ModuleInstance{} + err := m.resolveImports(&Module{ImportSection: []Import{{Module: "unknown", Name: "unknown"}}}, modules) require.EqualError(t, err, "module[unknown] not instantiated") }) t.Run("export instance not found", func(t *testing.T) { modules := map[string]*ModuleInstance{ moduleName: {Exports: map[string]ExportInstance{}, Name: moduleName}, } - _, _, _, _, err := resolveImports(&Module{ImportSection: []Import{{Module: moduleName, Name: "unknown"}}}, modules) + m := &ModuleInstance{} + err := m.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) { @@ -663,26 +670,30 @@ func Test_resolveImports(t *testing.T) { }, Name: moduleName, } - modules := map[string]*ModuleInstance{ + importedModules := map[string]*ModuleInstance{ moduleName: externMod, } - m := &Module{ + module := &Module{ TypeSection: []FunctionType{{Results: []ValueType{ValueTypeF32}}, {Results: []ValueType{ValueTypeI32}}}, ImportSection: []Import{ {Module: moduleName, Name: name, Type: ExternTypeFunc, DescFunc: 0}, {Module: moduleName, Name: "", Type: ExternTypeFunc, DescFunc: 1}, }, } - functions, _, _, _, err := resolveImports(m, modules) + + m := &ModuleInstance{Functions: make([]FunctionInstance, 2)} + err := m.resolveImports(module, importedModules) require.NoError(t, err) - 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) + + require.Equal(t, m.Functions[0], externMod.Functions[0]) + require.Equal(t, m.Functions[1], externMod.Functions[1]) }) t.Run("type out of range", func(t *testing.T) { modules := map[string]*ModuleInstance{ moduleName: {Exports: map[string]ExportInstance{name: {}}, Name: moduleName}, } - _, _, _, _, err := resolveImports(&Module{ImportSection: []Import{{Module: moduleName, Name: name, Type: ExternTypeFunc, DescFunc: 100}}}, modules) + m := &ModuleInstance{Functions: make([]FunctionInstance, 1)} + err := m.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) { @@ -693,30 +704,34 @@ func Test_resolveImports(t *testing.T) { }, Name: moduleName, } - modules := map[string]*ModuleInstance{moduleName: externMod} - m := &Module{ + module := &Module{ TypeSection: []FunctionType{{Results: []ValueType{ValueTypeF32}}}, ImportSection: []Import{{Module: moduleName, Name: name, Type: ExternTypeFunc, DescFunc: 0}}, } - _, _, _, _, err := resolveImports(m, modules) + + m := &ModuleInstance{Functions: make([]FunctionInstance, 1)} + err := m.resolveImports(module, map[string]*ModuleInstance{moduleName: externMod}) require.EqualError(t, err, "import[0] func[test.target]: signature mismatch: v_f32 != v_v") }) }) t.Run("global", func(t *testing.T) { t.Run("ok", func(t *testing.T) { g := &GlobalInstance{Type: GlobalType{ValType: ValueTypeI32}} - modules := map[string]*ModuleInstance{ - moduleName: { - Globals: []*GlobalInstance{g}, - Exports: map[string]ExportInstance{name: {Type: ExternTypeGlobal, Index: 0}}, Name: moduleName, + m := &ModuleInstance{Globals: make([]*GlobalInstance, 1)} + err := m.resolveImports( + &Module{ImportSection: []Import{{Module: moduleName, Name: name, Type: ExternTypeGlobal, DescGlobal: g.Type}}}, + map[string]*ModuleInstance{ + 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) - require.True(t, globalsContain(globals, g), "expected to find %v in %v", g, globals) + require.True(t, globalsContain(m.Globals, g), "expected to find %v in %v", g, m.Globals) }) t.Run("mutability mismatch", func(t *testing.T) { - modules := map[string]*ModuleInstance{ + importedModules := map[string]*ModuleInstance{ moduleName: { Globals: []*GlobalInstance{{Type: GlobalType{Mutable: false}}}, Exports: map[string]ExportInstance{name: { @@ -726,11 +741,12 @@ func Test_resolveImports(t *testing.T) { Name: moduleName, }, } - _, _, _, _, err := resolveImports(&Module{ImportSection: []Import{{Module: moduleName, Name: name, Type: ExternTypeGlobal, DescGlobal: GlobalType{Mutable: true}}}}, modules) + m := &ModuleInstance{Globals: make([]*GlobalInstance, 1)} + err := m.resolveImports(&Module{ImportSection: []Import{{Module: moduleName, Name: name, Type: ExternTypeGlobal, DescGlobal: GlobalType{Mutable: true}}}}, importedModules) 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{ + importedModules := map[string]*ModuleInstance{ moduleName: { Globals: []*GlobalInstance{{Type: GlobalType{ValType: ValueTypeI32}}}, Exports: map[string]ExportInstance{name: { @@ -740,7 +756,8 @@ func Test_resolveImports(t *testing.T) { Name: moduleName, }, } - _, _, _, _, err := resolveImports(&Module{ImportSection: []Import{{Module: moduleName, Name: name, Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeF64}}}}, modules) + m := &ModuleInstance{Globals: make([]*GlobalInstance, 1)} + err := m.resolveImports(&Module{ImportSection: []Import{{Module: moduleName, Name: name, Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeF64}}}}, importedModules) require.EqualError(t, err, "import[0] global[test.target]: value type mismatch: f64 != i32") }) }) @@ -748,7 +765,7 @@ func Test_resolveImports(t *testing.T) { t.Run("ok", func(t *testing.T) { max := uint32(10) memoryInst := &MemoryInstance{Max: max} - modules := map[string]*ModuleInstance{ + importedModules := map[string]*ModuleInstance{ moduleName: { Memory: memoryInst, Exports: map[string]ExportInstance{name: { @@ -757,13 +774,14 @@ func Test_resolveImports(t *testing.T) { Name: moduleName, }, } - _, _, _, memory, err := resolveImports(&Module{ImportSection: []Import{{Module: moduleName, Name: name, Type: ExternTypeMemory, DescMem: &Memory{Max: max}}}}, modules) + m := &ModuleInstance{} + err := m.resolveImports(&Module{ImportSection: []Import{{Module: moduleName, Name: name, Type: ExternTypeMemory, DescMem: &Memory{Max: max}}}}, importedModules) require.NoError(t, err) - require.Equal(t, memory, memoryInst) + require.Equal(t, m.Memory, memoryInst) }) t.Run("minimum size mismatch", func(t *testing.T) { importMemoryType := &Memory{Min: 2, Cap: 2} - modules := map[string]*ModuleInstance{ + importedModules := map[string]*ModuleInstance{ moduleName: { Memory: &MemoryInstance{Min: importMemoryType.Min - 1, Cap: 2}, Exports: map[string]ExportInstance{name: { @@ -772,7 +790,8 @@ func Test_resolveImports(t *testing.T) { Name: moduleName, }, } - _, _, _, _, err := resolveImports(&Module{ImportSection: []Import{{Module: moduleName, Name: name, Type: ExternTypeMemory, DescMem: importMemoryType}}}, modules) + m := &ModuleInstance{} + err := m.resolveImports(&Module{ImportSection: []Import{{Module: moduleName, Name: name, Type: ExternTypeMemory, DescMem: importMemoryType}}}, importedModules) require.EqualError(t, err, "import[0] memory[test.target]: minimum size mismatch: 2 > 1") }) t.Run("maximum size mismatch", func(t *testing.T) { @@ -787,7 +806,8 @@ func Test_resolveImports(t *testing.T) { Name: moduleName, }, } - _, _, _, _, err := resolveImports(&Module{ImportSection: []Import{{Module: moduleName, Name: name, Type: ExternTypeMemory, DescMem: importMemoryType}}}, modules) + m := &ModuleInstance{} + err := m.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") }) }) @@ -866,36 +886,27 @@ func globalsContain(globals []*GlobalInstance, want *GlobalInstance) bool { return false } -func functionsContain(functions []*FunctionInstance, want *FunctionInstance) bool { - for _, f := range functions { - if f == want { - return true - } - } - return false -} - func TestModuleInstance_applyTableInits(t *testing.T) { t.Run("extenref", func(t *testing.T) { - tables := []*TableInstance{{Type: RefTypeExternref, References: make([]Reference, 10)}} - for i := range tables[0].References { - tables[0].References[i] = 0xffff // non-null ref. - } m := &ModuleInstance{} + m.Tables = []*TableInstance{{Type: RefTypeExternref, References: make([]Reference, 10)}} + for i := range m.Tables[0].References { + m.Tables[0].References[i] = 0xffff // non-null ref. + } // This shouldn't panic. - m.applyTableInits(tables, []tableInitEntry{{offset: 100}}) - m.applyTableInits(tables, []tableInitEntry{ - {offset: 0, nullExternRefCount: 3}, - {offset: 100}, // Iteration stops at this point, so the offset:5 below shouldn't be applied. - {offset: 5, nullExternRefCount: 5}, + m.applyElements([]validatedActiveElementSegment{{arg: 100}}) + m.applyElements([]validatedActiveElementSegment{ + {arg: 0, init: make([]Index, 3)}, + {arg: 100}, // Iteration stops at this point, so the offset:5 below shouldn't be applied. + {arg: 5, init: make([]Index, 5)}, }) require.Equal(t, []Reference{0, 0, 0, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff}, - tables[0].References) - m.applyTableInits(tables, []tableInitEntry{ - {offset: 5, nullExternRefCount: 5}, + m.Tables[0].References) + m.applyElements([]validatedActiveElementSegment{ + {arg: 5, init: make([]Index, 5)}, }) - require.Equal(t, []Reference{0, 0, 0, 0xffff, 0xffff, 0, 0, 0, 0, 0}, tables[0].References) + require.Equal(t, []Reference{0, 0, 0, 0xffff, 0xffff, 0, 0, 0, 0, 0}, m.Tables[0].References) }) t.Run("funcref", func(t *testing.T) { e := &mockEngine{} @@ -904,25 +915,25 @@ func TestModuleInstance_applyTableInits(t *testing.T) { require.NoError(t, err) m := &ModuleInstance{Engine: me} - tables := []*TableInstance{{Type: RefTypeFuncref, References: make([]Reference, 10)}} - for i := range tables[0].References { - tables[0].References[i] = 0xffff // non-null ref. + m.Tables = []*TableInstance{{Type: RefTypeFuncref, References: make([]Reference, 10)}} + for i := range m.Tables[0].References { + m.Tables[0].References[i] = 0xffff // non-null ref. } // This shouldn't panic. - m.applyTableInits(tables, []tableInitEntry{{offset: 100}}) - m.applyTableInits(tables, []tableInitEntry{ - {offset: 0, functionIndexes: []*Index{uint32Ptr(0), uint32Ptr(1), uint32Ptr(2)}}, - {offset: 100}, // Iteration stops at this point, so the offset:5 below shouldn't be applied. - {offset: 5, nullExternRefCount: 5}, + m.applyElements([]validatedActiveElementSegment{{arg: 100}}) + m.applyElements([]validatedActiveElementSegment{ + {arg: 0, init: []Index{0, 1, 2}}, + {arg: 100}, // Iteration stops at this point, so the offset:5 below shouldn't be applied. + {arg: 5, init: make([]Index, 5)}, }) require.Equal(t, []Reference{0xa, 0xaa, 0xaaa, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff}, - tables[0].References) - m.applyTableInits(tables, []tableInitEntry{ - {offset: 5, functionIndexes: []*Index{uint32Ptr(0), nil, uint32Ptr(2)}}, + m.Tables[0].References) + m.applyElements([]validatedActiveElementSegment{ + {arg: 5, init: []Index{0, ElementInitNullReference, 2}}, }) require.Equal(t, []Reference{0xa, 0xaa, 0xaaa, 0xffff, 0xffff, 0xa, 0xffff, 0xaaa, 0xffff, 0xffff}, - tables[0].References) + m.Tables[0].References) }) } diff --git a/internal/wasm/table.go b/internal/wasm/table.go index e7471710..19e07985 100644 --- a/internal/wasm/table.go +++ b/internal/wasm/table.go @@ -67,7 +67,7 @@ type ElementSegment struct { // Followings are set/used regardless of the Mode. // Init indices are (nullable) table elements where each index is the function index by which the module initialize the table. - Init []*Index + Init []Index // Type holds the type of this element segment, which is the RefType in WebAssembly 2.0. Type RefType @@ -76,6 +76,13 @@ type ElementSegment struct { Mode ElementMode } +// ElementInitNullReference represents the null reference in ElementSegment's Init. +// In Wasm spec, an init item represents either Function's Index or null reference, +// and in wazero, we limit the maximum number of functions available in a module to +// MaximumFunctionIndex. Therefore, it is safe to use math.MaxUint32 to represent the null +// reference in Element segments. +const ElementInitNullReference Index = math.MaxUint32 + // IsActive returns true if the element segment is "active" mode which requires the runtime to initialize table // with the contents in .Init field. func (e *ElementSegment) IsActive() bool { @@ -132,7 +139,7 @@ type validatedActiveElementSegment struct { // init are a range of table elements whose values are positions in the function index. This range // replaces any values in TableInstance.Table at an offset arg which is a constant if opcode == OpcodeI32Const or // derived from a globalIdx if opcode == OpcodeGlobalGet - init []*Index + init []Index // tableIndex is the table's index to which this active element will be applied. tableIndex Index @@ -149,12 +156,12 @@ func (m *Module) validateTable(enabledFeatures api.CoreFeatures, tables []Table, return m.validatedActiveElementSegments, nil } - importedTableCount := m.ImportTableCount() + importedTableCount := m.ImportTableCount ret := make([]validatedActiveElementSegment, 0, m.SectionElementCount(SectionIDElement)) // Create bounds checks as these can err prior to instantiation - funcCount := m.importCount(ExternTypeFunc) + m.SectionElementCount(SectionIDFunction) + funcCount := m.ImportFunctionCount + m.SectionElementCount(SectionIDFunction) // Now, we have to figure out which table elements can be resolved before instantiation and also fail early if there // are any imported globals that are known to be invalid by their declarations. @@ -166,14 +173,14 @@ func (m *Module) validateTable(enabledFeatures api.CoreFeatures, tables []Table, if elem.Type == RefTypeFuncref { // Any offset applied is to the element, not the function index: validate here if the funcidx is sound. for ei, funcIdx := range elem.Init { - if funcIdx != nil && *funcIdx >= funcCount { - return nil, fmt.Errorf("%s[%d].init[%d] funcidx %d out of range", SectionIDName(SectionIDElement), idx, ei, *funcIdx) + if funcIdx != ElementInitNullReference && funcIdx >= funcCount { + return nil, fmt.Errorf("%s[%d].init[%d] funcidx %d out of range", SectionIDName(SectionIDElement), idx, ei, funcIdx) } } } else { for j, elem := range elem.Init { - if elem != nil { - return nil, fmt.Errorf("%s[%d].init[%d] must be ref.null but was %v", SectionIDName(SectionIDElement), idx, j, *elem) + if elem != ElementInitNullReference { + return nil, fmt.Errorf("%s[%d].init[%d] must be ref.null but was %v", SectionIDName(SectionIDElement), idx, j, elem) } } } @@ -244,69 +251,45 @@ func (m *Module) validateTable(enabledFeatures api.CoreFeatures, tables []Table, // If the result `init` is non-nil, it is the `tableInit` parameter of Engine.NewModuleEngine. // // Note: An error is only possible when an ElementSegment.OffsetExpr is out of range of the TableInstance.Min. -func (m *Module) buildTables(importedTables []*TableInstance, importedGlobals []*GlobalInstance, skipBoundCheck bool) (tables []*TableInstance, inits []tableInitEntry, err error) { - tables = importedTables - - for i := range m.TableSection { - tsec := &m.TableSection[i] +func (m *ModuleInstance) buildTables(module *Module, skipBoundCheck bool) (err error) { + idx := module.ImportTableCount + for i := range module.TableSection { + tsec := &module.TableSection[i] // The module defining the table is the one that sets its Min/Max etc. - tables = append(tables, &TableInstance{ + m.Tables[idx] = &TableInstance{ References: make([]Reference, tsec.Min), Min: tsec.Min, Max: tsec.Max, Type: tsec.Type, - }) + } + idx++ } - elementSegments := m.validatedActiveElementSegments + elementSegments := module.validatedActiveElementSegments if len(elementSegments) == 0 { return } - for elemI := range elementSegments { // Do not loop over the value since elementSegments is a slice of value. - elem := &elementSegments[elemI] - table := tables[elem.tableIndex] - var offset uint32 - if elem.opcode == OpcodeGlobalGet { - global := importedGlobals[elem.arg] - offset = uint32(global.Val) - } else { - offset = elem.arg // constant - } + if !skipBoundCheck { + for elemI := range elementSegments { // Do not loop over the value since elementSegments is a slice of value. + elem := &elementSegments[elemI] + table := m.Tables[elem.tableIndex] + var offset uint32 + if elem.opcode == OpcodeGlobalGet { + global := m.Globals[elem.arg] + offset = uint32(global.Val) + } else { + offset = elem.arg // constant + } - // Check to see if we are out-of-bounds - initCount := uint64(len(elem.init)) - if !skipBoundCheck { + // Check to see if we are out-of-bounds + initCount := uint64(len(elem.init)) if err = checkSegmentBounds(table.Min, uint64(offset)+initCount, Index(elemI)); err != nil { return } } - - if table.Type == RefTypeExternref { - inits = append(inits, tableInitEntry{ - tableIndex: elem.tableIndex, offset: offset, - // ExternRef elements are guaranteed to be all null via the validation phase. - nullExternRefCount: len(elem.init), - }) - } else { - inits = append(inits, tableInitEntry{ - tableIndex: elem.tableIndex, offset: offset, functionIndexes: elem.init, - }) - } } return } -// tableInitEntry is normalized element segment used for initializing tables. -type tableInitEntry struct { - tableIndex Index - // offset is the offset in the table from which the table is initialized by engine. - offset Index - // functionIndexes contains nullable function indexes. This is set when the target table has RefTypeFuncref. - functionIndexes []*Index - // nullExternRefCount is the number of nul reference which is the only available RefTypeExternref value in elements as of - // WebAssembly 2.0. This is set when the target table has RefTypeExternref. - nullExternRefCount int -} - // checkSegmentBounds fails if the capacity needed for an ElementSegment.Init is larger than limitsType.Min // // WebAssembly 1.0 (20191205) doesn't forbid growing to accommodate element segments, and spectests are inconsistent. diff --git a/internal/wasm/table_test.go b/internal/wasm/table_test.go index 313063f9..12c62c3c 100644 --- a/internal/wasm/table_test.go +++ b/internal/wasm/table_test.go @@ -9,8 +9,10 @@ import ( "github.com/tetratelabs/wazero/internal/testing/require" ) -func uint32Ptr(v uint32) *uint32 { - return &v +// Test_ElementInitNullReference_valid ensures it is actually safe to use ElementInitNullReference +// as a null reference, and it won't collide with the actual function Index. +func Test_ElementInitNullReference_valid(t *testing.T) { + require.True(t, MaximumFunctionIndex < ElementInitNullReference) } func Test_resolveImports_table(t *testing.T) { @@ -20,41 +22,43 @@ func Test_resolveImports_table(t *testing.T) { t.Run("ok", func(t *testing.T) { max := uint32(10) tableInst := &TableInstance{Max: &max} - modules := map[string]*ModuleInstance{ + importedModules := map[string]*ModuleInstance{ 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) + m := &ModuleInstance{Tables: make([]*TableInstance, 1)} + err := m.resolveImports(&Module{ImportSection: []Import{{Module: moduleName, Name: name, Type: ExternTypeTable, DescTable: Table{Max: &max}}}}, importedModules) require.NoError(t, err) - require.Equal(t, 1, len(tables)) - require.Equal(t, tables[0], tableInst) + require.Equal(t, m.Tables[0], tableInst) }) t.Run("minimum size mismatch", func(t *testing.T) { importTableType := Table{Min: 2} - modules := map[string]*ModuleInstance{ + importedModules := map[string]*ModuleInstance{ 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) + m := &ModuleInstance{Tables: make([]*TableInstance, 1)} + err := m.resolveImports(&Module{ImportSection: []Import{{Module: moduleName, Name: name, Type: ExternTypeTable, DescTable: importTableType}}}, importedModules) require.EqualError(t, err, "import[0] table[test.target]: minimum size mismatch: 2 > 1") }) t.Run("maximum size mismatch", func(t *testing.T) { max := uint32(10) importTableType := Table{Max: &max} - modules := map[string]*ModuleInstance{ + importedModules := map[string]*ModuleInstance{ 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) + m := &ModuleInstance{Tables: make([]*TableInstance, 1)} + err := m.resolveImports(&Module{ImportSection: []Import{{Module: moduleName, Name: name, Type: ExternTypeTable, DescTable: importTableType}}}, importedModules) require.EqualError(t, err, "import[0] table[test.target]: maximum size mismatch: 10, but actual has no max") }) } @@ -115,32 +119,33 @@ func TestModule_validateTable(t *testing.T) { ElementSection: []ElementSegment{ { OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: const0}, - Init: []*Index{uint32Ptr(0)}, + Init: []Index{0}, Type: RefTypeFuncref, }, }, }, expected: []validatedActiveElementSegment{ - {opcode: OpcodeI32Const, arg: 0, init: []*Index{uint32Ptr(0)}}, + {opcode: OpcodeI32Const, arg: 0, init: []Index{0}}, }, }, { name: "constant derived element offset - ignores min on imported table", input: &Module{ - TypeSection: []FunctionType{{}}, - ImportSection: []Import{{Type: ExternTypeTable, DescTable: Table{Type: RefTypeFuncref}}}, - FunctionSection: []Index{0}, - CodeSection: []Code{codeEnd}, + ImportTableCount: 1, + TypeSection: []FunctionType{{}}, + ImportSection: []Import{{Type: ExternTypeTable, DescTable: Table{Type: RefTypeFuncref}}}, + FunctionSection: []Index{0}, + CodeSection: []Code{codeEnd}, ElementSection: []ElementSegment{ { OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: const0}, - Init: []*Index{uint32Ptr(0)}, + Init: []Index{0}, Type: RefTypeFuncref, }, }, }, expected: []validatedActiveElementSegment{ - {opcode: OpcodeI32Const, arg: 0, init: []*Index{uint32Ptr(0)}}, + {opcode: OpcodeI32Const, arg: 0, init: []Index{0}}, }, }, { @@ -153,13 +158,13 @@ func TestModule_validateTable(t *testing.T) { ElementSection: []ElementSegment{ { OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: const0}, - Init: []*Index{uint32Ptr(0)}, + Init: []Index{0}, Type: RefTypeFuncref, }, }, }, expected: []validatedActiveElementSegment{ - {opcode: OpcodeI32Const, arg: 0, init: []*Index{uint32Ptr(0)}}, + {opcode: OpcodeI32Const, arg: 0, init: []Index{0}}, }, }, { @@ -172,13 +177,13 @@ func TestModule_validateTable(t *testing.T) { ElementSection: []ElementSegment{ { OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: const1}, - Init: []*Index{uint32Ptr(0), uint32Ptr(2)}, + Init: []Index{0, 2}, Type: RefTypeFuncref, }, }, }, expected: []validatedActiveElementSegment{ - {opcode: OpcodeI32Const, arg: 1, init: []*Index{uint32Ptr(0), uint32Ptr(2)}}, + {opcode: OpcodeI32Const, arg: 1, init: []Index{0, 2}}, }, }, { // See: https://github.com/WebAssembly/spec/issues/1427 @@ -213,13 +218,13 @@ func TestModule_validateTable(t *testing.T) { ElementSection: []ElementSegment{ { OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x0}}, - Init: []*Index{uint32Ptr(0)}, + Init: []Index{0}, Type: RefTypeFuncref, }, }, }, expected: []validatedActiveElementSegment{ - {opcode: OpcodeGlobalGet, arg: 0, init: []*Index{uint32Ptr(0)}}, + {opcode: OpcodeGlobalGet, arg: 0, init: []Index{0}}, }, }, { @@ -235,13 +240,13 @@ func TestModule_validateTable(t *testing.T) { ElementSection: []ElementSegment{ { OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x0}}, - Init: []*Index{uint32Ptr(0)}, + Init: []Index{0}, Type: RefTypeFuncref, }, }, }, expected: []validatedActiveElementSegment{ - {opcode: OpcodeGlobalGet, arg: 0, init: []*Index{uint32Ptr(0)}}, + {opcode: OpcodeGlobalGet, arg: 0, init: []Index{0}}, }, }, { @@ -257,13 +262,13 @@ func TestModule_validateTable(t *testing.T) { ElementSection: []ElementSegment{ { OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x0}}, - Init: []*Index{uint32Ptr(0)}, + Init: []Index{0}, Type: RefTypeFuncref, }, }, }, expected: []validatedActiveElementSegment{ - {opcode: OpcodeGlobalGet, arg: 0, init: []*Index{uint32Ptr(0)}}, + {opcode: OpcodeGlobalGet, arg: 0, init: []Index{0}}, }, }, { @@ -280,13 +285,13 @@ func TestModule_validateTable(t *testing.T) { ElementSection: []ElementSegment{ { OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x1}}, - Init: []*Index{uint32Ptr(0), uint32Ptr(2)}, + Init: []Index{0, 2}, Type: RefTypeFuncref, }, }, }, expected: []validatedActiveElementSegment{ - {opcode: OpcodeGlobalGet, arg: 1, init: []*Index{uint32Ptr(0), uint32Ptr(2)}}, + {opcode: OpcodeGlobalGet, arg: 1, init: []Index{0, 2}}, }, }, { @@ -303,19 +308,19 @@ func TestModule_validateTable(t *testing.T) { ElementSection: []ElementSegment{ { OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: const1}, - Init: []*Index{uint32Ptr(0), uint32Ptr(2)}, + Init: []Index{0, 2}, Type: RefTypeFuncref, }, { OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x1}}, - Init: []*Index{uint32Ptr(1), uint32Ptr(2)}, + Init: []Index{1, 2}, Type: RefTypeFuncref, }, }, }, expected: []validatedActiveElementSegment{ - {opcode: OpcodeI32Const, arg: 1, init: []*Index{uint32Ptr(0), uint32Ptr(2)}}, - {opcode: OpcodeGlobalGet, arg: 1, init: []*Index{uint32Ptr(1), uint32Ptr(2)}}, + {opcode: OpcodeI32Const, arg: 1, init: []Index{0, 2}}, + {opcode: OpcodeGlobalGet, arg: 1, init: []Index{1, 2}}, }, }, } @@ -413,7 +418,7 @@ func TestModule_validateTable_Errors(t *testing.T) { Data: leb128.EncodeUint64(math.MaxUint64), }, Type: RefTypeExternref, - Init: []*Index{uint32Ptr(0)}, + Init: []Index{0}, }, }, }, @@ -432,7 +437,7 @@ func TestModule_validateTable_Errors(t *testing.T) { Opcode: OpcodeI32Const, Data: leb128.EncodeUint64(math.MaxUint64), }, - Init: []*Index{uint32Ptr(0)}, + Init: []Index{0}, Type: RefTypeFuncref, }, }, @@ -448,7 +453,7 @@ func TestModule_validateTable_Errors(t *testing.T) { CodeSection: []Code{codeEnd}, ElementSection: []ElementSegment{ { - OffsetExpr: ConstantExpression{Opcode: OpcodeI64Const, Data: const0}, Init: []*Index{uint32Ptr(0)}, + OffsetExpr: ConstantExpression{Opcode: OpcodeI64Const, Data: const0}, Init: []Index{0}, Type: RefTypeFuncref, }, }, @@ -463,7 +468,7 @@ func TestModule_validateTable_Errors(t *testing.T) { CodeSection: []Code{codeEnd}, ElementSection: []ElementSegment{ { - OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: const0}, Init: []*Index{uint32Ptr(0)}, + OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: const0}, Init: []Index{0}, Type: RefTypeFuncref, }, }, @@ -479,7 +484,7 @@ func TestModule_validateTable_Errors(t *testing.T) { CodeSection: []Code{codeEnd}, ElementSection: []ElementSegment{ { - OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(2)}, Init: []*Index{uint32Ptr(0)}, + OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(2)}, Init: []Index{0}, Type: RefTypeFuncref, }, }, @@ -495,11 +500,11 @@ func TestModule_validateTable_Errors(t *testing.T) { CodeSection: []Code{codeEnd}, ElementSection: []ElementSegment{ { - OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: const1}, Init: []*Index{uint32Ptr(0)}, + OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: const1}, Init: []Index{0}, Type: RefTypeFuncref, }, { - OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: const1}, Init: []*Index{uint32Ptr(0), uint32Ptr(0)}, + OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: const1}, Init: []Index{0, 0}, Type: RefTypeFuncref, }, }, @@ -531,7 +536,7 @@ func TestModule_validateTable_Errors(t *testing.T) { CodeSection: []Code{codeEnd}, ElementSection: []ElementSegment{ { - OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: const1}, Init: []*Index{uint32Ptr(0), uint32Ptr(1)}, + OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: const1}, Init: []Index{0, 1}, Type: RefTypeFuncref, }, }, @@ -549,7 +554,7 @@ func TestModule_validateTable_Errors(t *testing.T) { CodeSection: []Code{codeEnd}, ElementSection: []ElementSegment{ { - OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x0}}, Init: []*Index{uint32Ptr(0)}, + OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x0}}, Init: []Index{0}, Type: RefTypeFuncref, }, }, @@ -568,7 +573,7 @@ func TestModule_validateTable_Errors(t *testing.T) { CodeSection: []Code{codeEnd}, ElementSection: []ElementSegment{ { - OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x0}}, Init: []*Index{uint32Ptr(0), uint32Ptr(1)}, + OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x0}}, Init: []Index{0, 1}, Type: RefTypeFuncref, }, }, @@ -587,7 +592,7 @@ func TestModule_validateTable_Errors(t *testing.T) { CodeSection: []Code{codeEnd}, ElementSection: []ElementSegment{ { - OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x0}}, Init: []*Index{uint32Ptr(0)}, + OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x0}}, Init: []Index{0}, Type: RefTypeFuncref, }, }, @@ -610,7 +615,7 @@ func TestModule_validateTable_Errors(t *testing.T) { Opcode: OpcodeGlobalGet, Data: leb128.EncodeUint64(math.MaxUint64), }, - Init: []*Index{uint32Ptr(0)}, + Init: []Index{0}, Type: RefTypeFuncref, }, }, @@ -627,7 +632,7 @@ func TestModule_validateTable_Errors(t *testing.T) { CodeSection: []Code{codeEnd}, ElementSection: []ElementSegment{ { - OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x0}}, Init: []*Index{uint32Ptr(0)}, + OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x0}}, Init: []Index{0}, Type: RefTypeFuncref, }, }, @@ -647,7 +652,7 @@ func TestModule_validateTable_Errors(t *testing.T) { CodeSection: []Code{codeEnd}, ElementSection: []ElementSegment{ { - OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x0}}, Init: []*Index{uint32Ptr(0)}, + OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x0}}, Init: []Index{0}, Type: RefTypeFuncref, }, }, @@ -681,7 +686,6 @@ func TestModule_buildTables(t *testing.T) { importedTables []*TableInstance importedGlobals []*GlobalInstance expectedTables []*TableInstance - expectedInit []tableInitEntry }{ { name: "empty", @@ -721,11 +725,10 @@ func TestModule_buildTables(t *testing.T) { module: &Module{ TableSection: []Table{{Min: 10, Type: RefTypeExternref}}, validatedActiveElementSegments: []validatedActiveElementSegment{ - {opcode: OpcodeI32Const, arg: 5, init: []*Index{nil, nil, nil}}, // three null refs. + {opcode: OpcodeI32Const, arg: 5, init: []Index{ElementInitNullReference, ElementInitNullReference, ElementInitNullReference}}, // three null refs. }, }, expectedTables: []*TableInstance{{References: make([]Reference, 10), Min: 10, Type: RefTypeExternref}}, - expectedInit: []tableInitEntry{{tableIndex: 0, offset: 5, nullExternRefCount: 3}}, }, { name: "constant derived element offset=0 and one index", @@ -735,11 +738,10 @@ func TestModule_buildTables(t *testing.T) { FunctionSection: []Index{0}, CodeSection: []Code{codeEnd}, validatedActiveElementSegments: []validatedActiveElementSegment{ - {opcode: OpcodeI32Const, arg: 0, init: []*Index{uint32Ptr(0)}}, + {opcode: OpcodeI32Const, arg: 0, init: []Index{0}}, }, }, expectedTables: []*TableInstance{{References: make([]Reference, 1), Min: 1}}, - expectedInit: []tableInitEntry{{tableIndex: 0, offset: 0, functionIndexes: []*Index{uint32Ptr(0)}}}, }, { name: "constant derived element offset - imported table", @@ -748,12 +750,11 @@ func TestModule_buildTables(t *testing.T) { FunctionSection: []Index{0}, CodeSection: []Code{codeEnd}, validatedActiveElementSegments: []validatedActiveElementSegment{ - {opcode: OpcodeI32Const, arg: 0, init: []*Index{uint32Ptr(0)}}, + {opcode: OpcodeI32Const, arg: 0, init: []Index{0}}, }, }, importedTables: []*TableInstance{{Min: 2}}, expectedTables: []*TableInstance{{Min: 2}}, - expectedInit: []tableInitEntry{{tableIndex: 0, offset: 0, functionIndexes: []*Index{uint32Ptr(0)}}}, }, { name: "constant derived element offset=0 and one index - imported table", @@ -763,12 +764,11 @@ func TestModule_buildTables(t *testing.T) { FunctionSection: []Index{0}, CodeSection: []Code{codeEnd}, validatedActiveElementSegments: []validatedActiveElementSegment{ - {opcode: OpcodeI32Const, arg: 0, init: []*Index{uint32Ptr(0)}}, + {opcode: OpcodeI32Const, arg: 0, init: []Index{0}}, }, }, importedTables: []*TableInstance{{Min: 1}}, expectedTables: []*TableInstance{{Min: 1}}, - expectedInit: []tableInitEntry{{tableIndex: 0, offset: 0, functionIndexes: []*Index{uint32Ptr(0)}}}, }, { name: "constant derived element offset and two indices", @@ -778,11 +778,10 @@ func TestModule_buildTables(t *testing.T) { FunctionSection: []Index{0, 0, 0, 0}, CodeSection: []Code{codeEnd, codeEnd, codeEnd, codeEnd}, validatedActiveElementSegments: []validatedActiveElementSegment{ - {opcode: OpcodeI32Const, arg: 1, init: []*Index{uint32Ptr(0), uint32Ptr(2)}}, + {opcode: OpcodeI32Const, arg: 1, init: []Index{0, 2}}, }, }, expectedTables: []*TableInstance{{References: make([]Reference, 3), Min: 3}}, - expectedInit: []tableInitEntry{{tableIndex: 0, offset: 1, functionIndexes: []*Index{uint32Ptr(0), uint32Ptr(2)}}}, }, { // See: https://github.com/WebAssembly/spec/issues/1427 name: "imported global derived element offset and no index", @@ -810,12 +809,11 @@ func TestModule_buildTables(t *testing.T) { FunctionSection: []Index{0}, CodeSection: []Code{codeEnd}, validatedActiveElementSegments: []validatedActiveElementSegment{ - {opcode: OpcodeGlobalGet, arg: 0, init: []*Index{uint32Ptr(0)}}, + {opcode: OpcodeGlobalGet, arg: 0, init: []Index{0}}, }, }, importedGlobals: []*GlobalInstance{{Type: GlobalType{ValType: ValueTypeI32}, Val: 1}}, expectedTables: []*TableInstance{{References: make([]Reference, 2), Min: 2}}, - expectedInit: []tableInitEntry{{tableIndex: 0, offset: 1, functionIndexes: []*Index{uint32Ptr(0)}}}, }, { name: "imported global derived element offset and one index - imported table", @@ -828,13 +826,12 @@ func TestModule_buildTables(t *testing.T) { FunctionSection: []Index{0}, CodeSection: []Code{codeEnd}, validatedActiveElementSegments: []validatedActiveElementSegment{ - {opcode: OpcodeGlobalGet, arg: 0, init: []*Index{uint32Ptr(0)}}, + {opcode: OpcodeGlobalGet, arg: 0, init: []Index{0}}, }, }, importedGlobals: []*GlobalInstance{{Type: GlobalType{ValType: ValueTypeI32}, Val: 1}}, importedTables: []*TableInstance{{References: make([]Reference, 2), Min: 2}}, expectedTables: []*TableInstance{{Min: 2, References: []Reference{0, 0}}}, - expectedInit: []tableInitEntry{{tableIndex: 0, offset: 1, functionIndexes: []*Index{uint32Ptr(0)}}}, }, { name: "imported global derived element offset - ignores min on imported table", @@ -847,13 +844,12 @@ func TestModule_buildTables(t *testing.T) { FunctionSection: []Index{0}, CodeSection: []Code{codeEnd}, validatedActiveElementSegments: []validatedActiveElementSegment{ - {opcode: OpcodeGlobalGet, arg: 0, init: []*Index{uint32Ptr(0)}}, + {opcode: OpcodeGlobalGet, arg: 0, init: []Index{0}}, }, }, importedGlobals: []*GlobalInstance{{Type: GlobalType{ValType: ValueTypeI32}, Val: 1}}, importedTables: []*TableInstance{{References: make([]Reference, 2), Min: 2}}, expectedTables: []*TableInstance{{Min: 2, References: []Reference{0, 0}}}, - expectedInit: []tableInitEntry{{tableIndex: 0, offset: 1, functionIndexes: []*Index{uint32Ptr(0)}}}, }, { name: "imported global derived element offset - two indices", @@ -869,18 +865,18 @@ func TestModule_buildTables(t *testing.T) { ElementSection: []ElementSegment{ { OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x0}}, - Init: []*Index{nil, uint32Ptr(2)}, + Init: []Index{ElementInitNullReference, 2}, TableIndex: 1, }, { OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x1}}, - Init: []*Index{uint32Ptr(0), uint32Ptr(2)}, + Init: []Index{0, 2}, TableIndex: 0, }, }, validatedActiveElementSegments: []validatedActiveElementSegment{ - {tableIndex: 1, opcode: OpcodeGlobalGet, arg: 0, init: []*Index{nil, uint32Ptr(2)}}, - {tableIndex: 0, opcode: OpcodeGlobalGet, arg: 1, init: []*Index{uint32Ptr(0), uint32Ptr(2)}}, + {tableIndex: 1, opcode: OpcodeGlobalGet, arg: 0, init: []Index{ElementInitNullReference, 2}}, + {tableIndex: 0, opcode: OpcodeGlobalGet, arg: 1, init: []Index{0, 2}}, }, }, importedGlobals: []*GlobalInstance{ @@ -891,10 +887,6 @@ func TestModule_buildTables(t *testing.T) { {References: make([]Reference, 3), Min: 3}, {References: make([]Reference, 100), Min: 100}, }, - expectedInit: []tableInitEntry{ - {tableIndex: 1, offset: 3, functionIndexes: []*Index{nil, uint32Ptr(2)}}, - {tableIndex: 0, offset: 1, functionIndexes: []*Index{uint32Ptr(0), uint32Ptr(2)}}, - }, }, { name: "mixed elementSegments - const before imported global", @@ -908,8 +900,8 @@ func TestModule_buildTables(t *testing.T) { FunctionSection: []Index{0, 0, 0, 0}, CodeSection: []Code{codeEnd, codeEnd, codeEnd, codeEnd}, validatedActiveElementSegments: []validatedActiveElementSegment{ - {opcode: OpcodeI32Const, arg: 1, init: []*Index{uint32Ptr(0), uint32Ptr(2)}}, - {opcode: OpcodeGlobalGet, arg: 1, init: []*Index{uint32Ptr(1), uint32Ptr(2)}}, + {opcode: OpcodeI32Const, arg: 1, init: []Index{0, 2}}, + {opcode: OpcodeGlobalGet, arg: 1, init: []Index{1, 2}}, }, }, importedGlobals: []*GlobalInstance{ @@ -917,10 +909,6 @@ func TestModule_buildTables(t *testing.T) { {Type: GlobalType{ValType: ValueTypeI32}, Val: 1}, }, expectedTables: []*TableInstance{{References: make([]Reference, 3), Min: 3}}, - expectedInit: []tableInitEntry{ - {tableIndex: 0, offset: 1, functionIndexes: []*Index{uint32Ptr(0), uint32Ptr(2)}}, - {tableIndex: 0, offset: 1, functionIndexes: []*Index{uint32Ptr(1), uint32Ptr(2)}}, - }, }, } @@ -928,11 +916,14 @@ func TestModule_buildTables(t *testing.T) { tc := tt t.Run(tc.name, func(t *testing.T) { - tables, init, err := tc.module.buildTables(tc.importedTables, tc.importedGlobals, false) + m := &ModuleInstance{ + Tables: append(tc.importedTables, make([]*TableInstance, len(tc.module.TableSection))...), + Globals: tc.importedGlobals, + } + err := m.buildTables(tc.module, false) require.NoError(t, err) - require.Equal(t, tc.expectedTables, tables) - require.Equal(t, tc.expectedInit, init) + require.Equal(t, tc.expectedTables, m.Tables) }) } } @@ -956,11 +947,11 @@ func TestModule_buildTable_Errors(t *testing.T) { ElementSection: []ElementSegment{ { OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: const0}, - Init: []*Index{uint32Ptr(0)}, + Init: []Index{0}, }, }, validatedActiveElementSegments: []validatedActiveElementSegment{ - {opcode: OpcodeI32Const, arg: 2, init: []*Index{uint32Ptr(0)}}, + {opcode: OpcodeI32Const, arg: 2, init: []Index{0}}, }, }, importedTables: []*TableInstance{{References: make([]Reference, 2), Min: 2}}, @@ -979,11 +970,11 @@ func TestModule_buildTable_Errors(t *testing.T) { ElementSection: []ElementSegment{ { OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x0}}, - Init: []*Index{uint32Ptr(0)}, + Init: []Index{0}, }, }, validatedActiveElementSegments: []validatedActiveElementSegment{ - {opcode: OpcodeGlobalGet, arg: 0, init: []*Index{uint32Ptr(0)}}, + {opcode: OpcodeGlobalGet, arg: 0, init: []Index{0}}, }, }, importedGlobals: []*GlobalInstance{{Type: GlobalType{ValType: ValueTypeI32}, Val: 2}}, @@ -1003,11 +994,11 @@ func TestModule_buildTable_Errors(t *testing.T) { ElementSection: []ElementSegment{ { OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x0}}, - Init: []*Index{uint32Ptr(0)}, + Init: []Index{0}, }, }, validatedActiveElementSegments: []validatedActiveElementSegment{ - {opcode: OpcodeGlobalGet, arg: 0, init: []*Index{uint32Ptr(0)}}, + {opcode: OpcodeGlobalGet, arg: 0, init: []Index{0}}, }, }, importedTables: []*TableInstance{{References: make([]Reference, 2), Min: 2}}, @@ -1020,7 +1011,11 @@ func TestModule_buildTable_Errors(t *testing.T) { tc := tt t.Run(tc.name, func(t *testing.T) { - _, _, err := tc.module.buildTables(tc.importedTables, tc.importedGlobals, false) + m := &ModuleInstance{ + Tables: append(tc.importedTables, make([]*TableInstance, len(tc.module.TableSection))...), + Globals: tc.importedGlobals, + } + err := m.buildTables(tc.module, false) require.EqualError(t, err, tc.expectedErr) }) } diff --git a/internal/wazeroir/compiler.go b/internal/wazeroir/compiler.go index 60a4609b..3c4d821f 100644 --- a/internal/wazeroir/compiler.go +++ b/internal/wazeroir/compiler.go @@ -307,7 +307,7 @@ func CompileFunctions(enabledFeatures api.CoreFeatures, callFrameStackSizeInUint code.LocalTypes, types, functions, globals, code.BodyOffsetInCodeSection, module.DWARFLines != nil, ensureTermination, funcTypeToSigs, controlFramesStack) if err != nil { - def := module.FunctionDefinitionSection[uint32(funcIndex)+module.ImportFuncCount()] + def := module.FunctionDefinitionSection[uint32(funcIndex)+module.ImportFunctionCount] return nil, fmt.Errorf("failed to lower func[%s] to wazeroir: %w", def.DebugName(), err) } r.Globals = globals diff --git a/runtime.go b/runtime.go index ffe8cfdb..6c5501e6 100644 --- a/runtime.go +++ b/runtime.go @@ -233,7 +233,7 @@ func buildListeners(ctx context.Context, internal *wasm.Module) ([]experimentala return nil, nil } factory := fnlf.(experimentalapi.FunctionListenerFactory) - importCount := internal.ImportFuncCount() + importCount := internal.ImportFunctionCount listeners := make([]experimentalapi.FunctionListener, len(internal.FunctionSection)) for i := 0; i < len(listeners); i++ { listeners[i] = factory.NewListener(&internal.FunctionDefinitionSection[uint32(i)+importCount])