From da3aa7a5ad4609457cb685cc44686e0bba1796ff Mon Sep 17 00:00:00 2001 From: Crypt Keeper <64215+codefromthecrypt@users.noreply.github.com> Date: Sat, 31 Dec 2022 13:11:37 +0800 Subject: [PATCH] Adds ExportedFunctionDefinitions and ExportedMemoryDefinitions (#986) This adds ExportedFunctionDefinitions and ExportedMemoryDefinitions to api.Module so that those who can't access CompileModule can see them. Fixes #839 Signed-off-by: Adrian Cole --- api/wasm.go | 11 ++++ .../integration_test/spectest/spectest.go | 3 ++ internal/wasm/call_context.go | 27 ++++++++++ internal/wasm/module.go | 1 + internal/wasm/module_test.go | 7 ++- internal/wasm/store_test.go | 51 ++++++++++--------- runtime_test.go | 9 ++++ 7 files changed, 85 insertions(+), 24 deletions(-) diff --git a/api/wasm.go b/api/wasm.go index a45dcdbf..6be8d243 100644 --- a/api/wasm.go +++ b/api/wasm.go @@ -147,6 +147,10 @@ type Module interface { // ExportedFunction returns a function exported from this module or nil if it wasn't. ExportedFunction(name string) Function + // ExportedFunctionDefinitions returns all the exported function + // definitions in this module, keyed on export name. + ExportedFunctionDefinitions() map[string]FunctionDefinition + // TODO: Table // ExportedMemory returns a memory exported from this module or nil if it wasn't. @@ -157,6 +161,13 @@ type Module interface { // See https://github.com/WebAssembly/WASI/blob/snapshot-01/design/application-abi.md#current-unstable-abi ExportedMemory(name string) Memory + // ExportedMemoryDefinitions returns all the exported memory definitions + // in this module, keyed on export name. + // + // Note: As of WebAssembly Core Specification 2.0, there can be at most one + // memory. + ExportedMemoryDefinitions() map[string]MemoryDefinition + // ExportedGlobal a global exported from this module or nil if it wasn't. ExportedGlobal(name string) Global diff --git a/internal/integration_test/spectest/spectest.go b/internal/integration_test/spectest/spectest.go index d588b609..71aec8e8 100644 --- a/internal/integration_test/spectest/spectest.go +++ b/internal/integration_test/spectest/spectest.go @@ -362,6 +362,7 @@ func addSpectestModule(t *testing.T, ctx context.Context, s *wasm.Store, ns *was mod.ExportSection = append(mod.ExportSection, &wasm.Export{Name: "table", Index: 0, Type: wasm.ExternTypeTable}) maybeSetMemoryCap(mod) + mod.BuildMemoryDefinitions() mod.BuildFunctionDefinitions() err = mod.Validate(enabledFeatures) @@ -432,6 +433,7 @@ func Run(t *testing.T, testDataFS embed.FS, ctx context.Context, newEngine func( } maybeSetMemoryCap(mod) + mod.BuildMemoryDefinitions() mod.BuildFunctionDefinitions() err = s.Engine.CompileModule(ctx, mod, nil) require.NoError(t, err, msg) @@ -603,6 +605,7 @@ func requireInstantiationError(t *testing.T, ctx context.Context, s *wasm.Store, mod.AssignModuleID(buf) maybeSetMemoryCap(mod) + mod.BuildMemoryDefinitions() mod.BuildFunctionDefinitions() err = s.Engine.CompileModule(ctx, mod, nil) if err != nil { diff --git a/internal/wasm/call_context.go b/internal/wasm/call_context.go index 7e70838e..bb8b2620 100644 --- a/internal/wasm/call_context.go +++ b/internal/wasm/call_context.go @@ -135,6 +135,21 @@ func (m *CallContext) ExportedMemory(name string) api.Memory { return m.memory } +// ExportedMemoryDefinitions implements the same method as documented on +// api.Module. +func (m *CallContext) ExportedMemoryDefinitions() map[string]api.MemoryDefinition { + // Special case as we currently only support one memory. + if mem := m.module.Memory; mem != nil { + // Now, find out if it is exported + for name, exp := range m.module.Exports { + if exp.Type == ExternTypeMemory { + return map[string]api.MemoryDefinition{name: mem.definition} + } + } + } + return map[string]api.MemoryDefinition{} +} + // ExportedFunction implements the same method as documented on api.Module. func (m *CallContext) ExportedFunction(name string) api.Function { exp, err := m.module.getExport(name, ExternTypeFunc) @@ -145,6 +160,18 @@ func (m *CallContext) ExportedFunction(name string) api.Function { return m.function(&m.module.Functions[exp.Index]) } +// ExportedFunctionDefinitions implements the same method as documented on +// api.Module. +func (m *CallContext) ExportedFunctionDefinitions() map[string]api.FunctionDefinition { + result := map[string]api.FunctionDefinition{} + for name, exp := range m.module.Exports { + if exp.Type == ExternTypeFunc { + result[name] = m.module.Functions[exp.Index].Definition + } + } + return result +} + // Module is exposed for emscripten. func (m *CallContext) Module() *ModuleInstance { return m.module diff --git a/internal/wasm/module.go b/internal/wasm/module.go index 6d20d2ff..8eb3011d 100644 --- a/internal/wasm/module.go +++ b/internal/wasm/module.go @@ -657,6 +657,7 @@ func (m *Module) buildMemory() (mem *MemoryInstance) { memSec := m.MemorySection if memSec != nil { mem = NewMemoryInstance(memSec) + mem.definition = m.MemoryDefinitionSection[0] } return } diff --git a/internal/wasm/module_test.go b/internal/wasm/module_test.go index 9839ac9f..6bf582f4 100644 --- a/internal/wasm/module_test.go +++ b/internal/wasm/module_test.go @@ -842,10 +842,15 @@ func TestModule_buildMemoryInstance(t *testing.T) { t.Run("non-nil", func(t *testing.T) { min := uint32(1) max := uint32(10) - m := Module{MemorySection: &Memory{Min: min, Cap: min, Max: max}} + mDef := &MemoryDefinition{moduleName: "foo"} + m := Module{ + MemorySection: &Memory{Min: min, Cap: min, Max: max}, + MemoryDefinitionSection: []*MemoryDefinition{mDef}, + } mem := m.buildMemory() require.Equal(t, min, mem.Min) require.Equal(t, max, mem.Max) + require.Equal(t, mDef, mem.definition) }) } diff --git a/internal/wasm/store_test.go b/internal/wasm/store_test.go index e0c95d12..95017b5f 100644 --- a/internal/wasm/store_test.go +++ b/internal/wasm/store_test.go @@ -29,33 +29,35 @@ func TestModuleInstance_Memory(t *testing.T) { input: &Module{}, }, { - name: "memory not exported", - input: &Module{MemorySection: &Memory{Min: 1, Cap: 1}}, - }, - { - name: "memory not exported, one page", - input: &Module{MemorySection: &Memory{Min: 1, Cap: 1}}, + name: "memory not exported, one page", + input: &Module{ + MemorySection: &Memory{Min: 1, Cap: 1}, + MemoryDefinitionSection: []*MemoryDefinition{{}}, + }, }, { name: "memory exported, different name", input: &Module{ - MemorySection: &Memory{Min: 1, Cap: 1}, - ExportSection: []*Export{{Type: ExternTypeMemory, Name: "momory", Index: 0}}, + MemorySection: &Memory{Min: 1, Cap: 1}, + MemoryDefinitionSection: []*MemoryDefinition{{}}, + ExportSection: []*Export{{Type: ExternTypeMemory, Name: "momory", Index: 0}}, }, }, { name: "memory exported, but zero length", input: &Module{ - MemorySection: &Memory{}, - ExportSection: []*Export{{Type: ExternTypeMemory, Name: "memory", Index: 0}}, + MemorySection: &Memory{}, + MemoryDefinitionSection: []*MemoryDefinition{{}}, + ExportSection: []*Export{{Type: ExternTypeMemory, Name: "memory", Index: 0}}, }, expected: true, }, { name: "memory exported, one page", input: &Module{ - MemorySection: &Memory{Min: 1, Cap: 1}, - ExportSection: []*Export{{Type: ExternTypeMemory, Name: "memory", Index: 0}}, + MemorySection: &Memory{Min: 1, Cap: 1}, + MemoryDefinitionSection: []*MemoryDefinition{{}}, + ExportSection: []*Export{{Type: ExternTypeMemory, Name: "memory", Index: 0}}, }, expected: true, expectedLen: 65536, @@ -63,8 +65,9 @@ func TestModuleInstance_Memory(t *testing.T) { { name: "memory exported, two pages", input: &Module{ - MemorySection: &Memory{Min: 2, Cap: 2}, - ExportSection: []*Export{{Type: ExternTypeMemory, Name: "memory", Index: 0}}, + MemorySection: &Memory{Min: 2, Cap: 2}, + MemoryDefinitionSection: []*MemoryDefinition{{}}, + ExportSection: []*Export{{Type: ExternTypeMemory, Name: "memory", Index: 0}}, }, expected: true, expectedLen: 65536 * 2, @@ -151,11 +154,12 @@ func TestStore_CloseWithExitCode(t *testing.T) { require.NoError(t, err) m2, err := s.Instantiate(testCtx, ns, &Module{ - TypeSection: []*FunctionType{v_v}, - ImportSection: []*Import{{Type: ExternTypeFunc, Module: importedModuleName, Name: "fn", DescFunc: 0}}, - MemorySection: &Memory{Min: 1, Cap: 1}, - GlobalSection: []*Global{{Type: &GlobalType{}, Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: const1}}}, - TableSection: []*Table{{Min: 10}}, + TypeSection: []*FunctionType{v_v}, + ImportSection: []*Import{{Type: ExternTypeFunc, Module: importedModuleName, Name: "fn", DescFunc: 0}}, + MemorySection: &Memory{Min: 1, Cap: 1}, + MemoryDefinitionSection: []*MemoryDefinition{{}}, + GlobalSection: []*Global{{Type: &GlobalType{}, Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: const1}}}, + TableSection: []*Table{{Min: 10}}, }, importingModuleName, nil) require.NoError(t, err) @@ -191,10 +195,11 @@ func TestStore_hammer(t *testing.T) { require.True(t, ok) importingModule := &Module{ - TypeSection: []*FunctionType{v_v}, - FunctionSection: []uint32{0}, - CodeSection: []*Code{{Body: []byte{OpcodeEnd}}}, - MemorySection: &Memory{Min: 1, Cap: 1}, + TypeSection: []*FunctionType{v_v}, + FunctionSection: []uint32{0}, + CodeSection: []*Code{{Body: []byte{OpcodeEnd}}}, + MemorySection: &Memory{Min: 1, Cap: 1}, + MemoryDefinitionSection: []*MemoryDefinition{{}}, GlobalSection: []*Global{{ Type: &GlobalType{ValType: ValueTypeI32}, Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(1)}, diff --git a/runtime_test.go b/runtime_test.go index 310cfc1c..8c749bb4 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -225,8 +225,13 @@ func TestModule_Memory(t *testing.T) { mem := module.ExportedMemory("memory") if tc.expected { require.Equal(t, tc.expectedLen, mem.Size()) + defs := module.ExportedMemoryDefinitions() + require.Equal(t, 1, len(defs)) + def := defs["memory"] + require.Equal(t, tc.expectedLen>>16, def.Min()) } else { require.Nil(t, mem) + require.Zero(t, len(module.ExportedMemoryDefinitions())) } }) } @@ -525,7 +530,11 @@ func TestRuntime_CloseWithExitCode(t *testing.T) { require.NoError(t, err) func1 := m1.ExportedFunction("func") + require.Equal(t, map[string]api.FunctionDefinition{"func": func1.Definition()}, + m1.ExportedFunctionDefinitions()) func2 := m2.ExportedFunction("func") + require.Equal(t, map[string]api.FunctionDefinition{"func": func2.Definition()}, + m2.ExportedFunctionDefinitions()) // Modules not closed so calls succeed