diff --git a/api/wasm.go b/api/wasm.go index d4aad01f..bcaf1b1f 100644 --- a/api/wasm.go +++ b/api/wasm.go @@ -179,21 +179,57 @@ type Closer interface { Close(context.Context) error } -// FunctionDefinition is a WebAssembly function exported in a module (wazero.CompiledModule). +// ExportDefinition is a WebAssembly type exported in a module +// (wazero.CompiledModule). // // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#exports%E2%91%A0 -type FunctionDefinition interface { +type ExportDefinition interface { // ModuleName is the possibly empty name of the module defining this - // function. + // export. // // Note: This may be different from Module.Name, because a compiled module // can be instantiated multiple times as different names. ModuleName() string - // Index is the position in the module's function index namespace, imports - // first. + // Index is the position in the module's index namespace, imports first. Index() uint32 + // Import returns true with the module and name when this was imported. + // Otherwise, it returns false. + // + // Note: Empty string is valid for both names in the WebAssembly Core + // Specification, so "" "" is possible. + Import() (moduleName, name string, isImport bool) + + // ExportNames include all exported names. + // + // Note: The empty name is allowed in the WebAssembly Core Specification, + // so "" is possible. + ExportNames() []string +} + +// MemoryDefinition is a WebAssembly memory exported in a module +// (wazero.CompiledModule). Units are in pages (64KB). +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#exports%E2%91%A0 +type MemoryDefinition interface { + ExportDefinition + + // Min returns the possibly zero initial count of 64KB pages. + Min() uint32 + + // Max returns the possibly zero max count of 64KB pages, or false if + // unbounded. + Max() (uint32, bool) +} + +// FunctionDefinition is a WebAssembly function exported in a module +// (wazero.CompiledModule). +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#exports%E2%91%A0 +type FunctionDefinition interface { + ExportDefinition + // Name is the module-defined name of the function, which is not necessarily // the same as its export name. Name() string @@ -215,19 +251,6 @@ type FunctionDefinition interface { // and not the imported function name. DebugName() string - // Import returns true with the module and function name when this function - // is imported. Otherwise, it returns false. - // - // Note: Empty string is valid for both the imported module and function - // name in the WebAssembly specification. - Import() (moduleName, name string, isImport bool) - - // ExportNames include all exported names for the given function. - // - // Note: The empty name is allowed in the WebAssembly specification, so "" - // is possible. - ExportNames() []string - // GoFunc is present when the function was implemented by the embedder // (ex via wazero.HostModuleBuilder) instead of a wasm binary. // @@ -326,15 +349,18 @@ type MutableGlobal interface { // // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#storage%E2%91%A0 type Memory interface { + // Definition is metadata about this memory from its defining module. + Definition() MemoryDefinition - // Size returns the size in bytes available. Ex. If the underlying memory has 1 page: 65536 + // Size returns the size in bytes available. Ex. If the underlying memory + // has 1 page: 65536 // // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#-hrefsyntax-instr-memorymathsfmemorysize%E2%91%A0 Size(context.Context) uint32 // Grow increases memory by the delta in pages (65536 bytes per page). // The return val is the previous memory size in pages, or false if the - // delta was ignored as it exceeds max memory. + // delta was ignored as it exceeds MemoryDefinition.Max. // // # Notes // diff --git a/config.go b/config.go index 5642f7d5..fe04ec38 100644 --- a/config.go +++ b/config.go @@ -168,6 +168,22 @@ type CompiledModule interface { // (api.FunctionDefinition) in this module keyed on export name. ExportedFunctions() map[string]api.FunctionDefinition + // ImportedMemories returns all the imported memories + // (api.MemoryDefinition) in this module or nil if there are none. + // + // ## Notes + // - As of WebAssembly Core Specification 2.0, there can be at most one + // memory. + // - Unlike ExportedMemories, there is no unique constraint on imports. + ImportedMemories() []api.MemoryDefinition + + // ExportedMemories returns all the exported memories + // (api.MemoryDefinition) in this module keyed on export name. + // + // Note: As of WebAssembly Core Specification 2.0, there can be at most one + // memory. + ExportedMemories() map[string]api.MemoryDefinition + // Close releases all the allocated resources for this CompiledModule. // // Note: It is safe to call Close while having outstanding calls from an @@ -175,6 +191,9 @@ type CompiledModule interface { Close(context.Context) error } +// compile-time check to ensure compiledModule implements CompiledModule +var _ CompiledModule = &compiledModule{} + type compiledModule struct { module *wasm.Module // compiledEngine holds an engine on which `module` is compiled. @@ -210,6 +229,16 @@ func (c *compiledModule) ExportedFunctions() map[string]api.FunctionDefinition { return c.module.ExportedFunctions() } +// ImportedMemories implements CompiledModule.ImportedMemories +func (c *compiledModule) ImportedMemories() []api.MemoryDefinition { + return c.module.ImportedMemories() +} + +// ExportedMemories implements CompiledModule.ExportedMemories +func (c *compiledModule) ExportedMemories() map[string]api.MemoryDefinition { + return c.module.ExportedMemories() +} + // ModuleConfig configures resources needed by functions that have low-level interactions with the host operating // system. Using this, resources such as STDIN can be isolated, so that the same module can be safely instantiated // multiple times. diff --git a/internal/engine/compiler/engine.go b/internal/engine/compiler/engine.go index 3edc94e3..74814135 100644 --- a/internal/engine/compiler/engine.go +++ b/internal/engine/compiler/engine.go @@ -661,7 +661,7 @@ func (ce *callEngine) deferredOnCall(recovered interface{}) (err error) { fn := ce.fn stackBasePointer := int(ce.stackBasePointerInBytes >> 3) for { - def := fn.source.FunctionDefinition + def := fn.source.Definition builder.AddFrame(def.DebugName(), def.ParamTypes(), def.ResultTypes()) callFrameOffset := callFrameOffset(fn.source.Type) diff --git a/internal/engine/compiler/engine_test.go b/internal/engine/compiler/engine_test.go index 9b016435..6085135f 100644 --- a/internal/engine/compiler/engine_test.go +++ b/internal/engine/compiler/engine_test.go @@ -311,16 +311,16 @@ func ptrAsUint64(f *function) uint64 { func TestCallEngine_deferredOnCall(t *testing.T) { f1 := &function{source: &wasm.FunctionInstance{ - FunctionDefinition: newMockFunctionDefinition("1"), - Type: &wasm.FunctionType{ParamNumInUint64: 2}, + Definition: newMockFunctionDefinition("1"), + Type: &wasm.FunctionType{ParamNumInUint64: 2}, }} f2 := &function{source: &wasm.FunctionInstance{ - FunctionDefinition: newMockFunctionDefinition("2"), - Type: &wasm.FunctionType{ParamNumInUint64: 2, ResultNumInUint64: 3}, + Definition: newMockFunctionDefinition("2"), + Type: &wasm.FunctionType{ParamNumInUint64: 2, ResultNumInUint64: 3}, }} f3 := &function{source: &wasm.FunctionInstance{ - FunctionDefinition: newMockFunctionDefinition("3"), - Type: &wasm.FunctionType{ResultNumInUint64: 1}, + Definition: newMockFunctionDefinition("3"), + Type: &wasm.FunctionType{ResultNumInUint64: 1}, }} ce := &callEngine{ diff --git a/internal/engine/interpreter/interpreter.go b/internal/engine/interpreter/interpreter.go index f18d1e9f..9b6ca5cb 100644 --- a/internal/engine/interpreter/interpreter.go +++ b/internal/engine/interpreter/interpreter.go @@ -813,7 +813,7 @@ func (ce *callEngine) recoverOnCall(v interface{}) (err error) { frameCount := len(ce.frames) for i := 0; i < frameCount; i++ { frame := ce.popFrame() - def := frame.f.source.FunctionDefinition + def := frame.f.source.Definition builder.AddFrame(def.DebugName(), def.ParamTypes(), def.ResultTypes()) } err = builder.FromRecovered(v) @@ -826,7 +826,7 @@ func (ce *callEngine) recoverOnCall(v interface{}) (err error) { func (ce *callEngine) callFunction(ctx context.Context, callCtx *wasm.CallContext, f *function) { if f.hostFn != nil { ce.callGoFuncWithStack(ctx, callCtx, f) - } else if lsn := f.source.FunctionListener; lsn != nil { + } else if lsn := f.source.Listener; lsn != nil { ce.callNativeFuncWithListener(ctx, callCtx, f, lsn) } else { ce.callNativeFunc(ctx, callCtx, f) @@ -835,16 +835,16 @@ func (ce *callEngine) callFunction(ctx context.Context, callCtx *wasm.CallContex func (ce *callEngine) callGoFunc(ctx context.Context, callCtx *wasm.CallContext, f *function, params []uint64) (results []uint64) { callCtx = callCtx.WithMemory(ce.callerMemory()) - if f.source.FunctionListener != nil { - ctx = f.source.FunctionListener.Before(ctx, f.source.FunctionDefinition, params) + if f.source.Listener != nil { + ctx = f.source.Listener.Before(ctx, f.source.Definition, params) } frame := &callFrame{f: f} ce.pushFrame(frame) results = wasm.CallGoFunc(ctx, callCtx, f.source, params) ce.popFrame() - if f.source.FunctionListener != nil { + if f.source.Listener != nil { // TODO: This doesn't get the error due to use of panic to propagate them. - f.source.FunctionListener.After(ctx, f.source.FunctionDefinition, nil, results) + f.source.Listener.After(ctx, f.source.Definition, nil, results) } return } @@ -4327,10 +4327,10 @@ func i32Abs(v uint32) uint32 { } func (ce *callEngine) callNativeFuncWithListener(ctx context.Context, callCtx *wasm.CallContext, f *function, fnl experimental.FunctionListener) context.Context { - ctx = fnl.Before(ctx, f.source.FunctionDefinition, ce.peekValues(len(f.source.Type.Params))) + ctx = fnl.Before(ctx, f.source.Definition, ce.peekValues(len(f.source.Type.Params))) ce.callNativeFunc(ctx, callCtx, f) // TODO: This doesn't get the error due to use of panic to propagate them. - fnl.After(ctx, f.source.FunctionDefinition, nil, ce.peekValues(len(f.source.Type.Results))) + fnl.After(ctx, f.source.Definition, nil, ce.peekValues(len(f.source.Type.Results))) return ctx } diff --git a/internal/wasm/call_context.go b/internal/wasm/call_context.go index a408882d..c6efbfb9 100644 --- a/internal/wasm/call_context.go +++ b/internal/wasm/call_context.go @@ -163,7 +163,7 @@ type function struct { // Definition implements the same method as documented on api.FunctionDefinition. func (f *function) Definition() api.FunctionDefinition { - return f.fi.FunctionDefinition + return f.fi.Definition } // Call implements the same method as documented on api.Function. @@ -180,7 +180,7 @@ type importedFn struct { // Definition implements the same method as documented on api.Function. func (f *importedFn) Definition() api.FunctionDefinition { - return f.importedFn.FunctionDefinition + return f.importedFn.Definition } // Call implements the same method as documented on api.Function. diff --git a/internal/wasm/memory.go b/internal/wasm/memory.go index 9ca5c535..36034efc 100644 --- a/internal/wasm/memory.go +++ b/internal/wasm/memory.go @@ -37,6 +37,8 @@ type MemoryInstance struct { Min, Cap, Max uint32 // mux is used to prevent overlapping calls to Grow. mux sync.RWMutex + // definition is known at compile time. + definition api.MemoryDefinition } // NewMemoryInstance creates a new instance based on the parameters in the SectionIDMemory. @@ -51,6 +53,11 @@ func NewMemoryInstance(memSec *Memory) *MemoryInstance { } } +// Definition implements the same method as documented on api.Memory. +func (m *MemoryInstance) Definition() api.MemoryDefinition { + return m.definition +} + // Size implements the same method as documented on api.Memory. func (m *MemoryInstance) Size(_ context.Context) uint32 { return m.size() diff --git a/internal/wasm/memory_definition.go b/internal/wasm/memory_definition.go new file mode 100644 index 00000000..1830553b --- /dev/null +++ b/internal/wasm/memory_definition.go @@ -0,0 +1,119 @@ +package wasm + +import "github.com/tetratelabs/wazero/api" + +// ImportedMemories implements the same method as documented on wazero.CompiledModule. +func (m *Module) ImportedMemories() (ret []api.MemoryDefinition) { + for _, d := range m.MemoryDefinitionSection { + if d.importDesc != nil { + ret = append(ret, d) + } + } + return +} + +// ExportedMemories implements the same method as documented on wazero.CompiledModule. +func (m *Module) ExportedMemories() map[string]api.MemoryDefinition { + ret := map[string]api.MemoryDefinition{} + for _, d := range m.MemoryDefinitionSection { + for _, e := range d.exportNames { + ret[e] = d + } + } + return ret +} + +// BuildMemoryDefinitions generates memory metadata that can be parsed from +// the module. This must be called after all validation. +// +// Note: This is exported for wazero.Runtime `CompileModule`. +func (m *Module) BuildMemoryDefinitions() { + var moduleName string + if m.NameSection != nil { + moduleName = m.NameSection.ModuleName + } + + memoryCount := m.ImportMemoryCount() + if m.MemorySection != nil { + memoryCount++ + } + + if memoryCount == 0 { + return + } + + m.MemoryDefinitionSection = make([]*MemoryDefinition, 0, memoryCount) + importMemIdx := Index(0) + for _, i := range m.ImportSection { + if i.Type != ExternTypeMemory { + continue + } + + m.MemoryDefinitionSection = append(m.MemoryDefinitionSection, &MemoryDefinition{ + importDesc: &[2]string{i.Module, i.Name}, + index: importMemIdx, + memory: i.DescMem, + }) + importMemIdx++ + } + + if m.MemorySection != nil { + m.MemoryDefinitionSection = append(m.MemoryDefinitionSection, &MemoryDefinition{ + index: importMemIdx, + memory: m.MemorySection, + }) + } + + for _, d := range m.MemoryDefinitionSection { + d.moduleName = moduleName + for _, e := range m.ExportSection { + if e.Type == ExternTypeMemory && e.Index == d.index { + d.exportNames = append(d.exportNames, e.Name) + } + } + } +} + +// MemoryDefinition implements api.MemoryDefinition +type MemoryDefinition struct { + moduleName string + index Index + importDesc *[2]string + exportNames []string + memory *Memory +} + +// ModuleName implements the same method as documented on api.MemoryDefinition. +func (f *MemoryDefinition) ModuleName() string { + return f.moduleName +} + +// Index implements the same method as documented on api.MemoryDefinition. +func (f *MemoryDefinition) Index() uint32 { + return f.index +} + +// Import implements the same method as documented on api.MemoryDefinition. +func (f *MemoryDefinition) Import() (moduleName, name string, isImport bool) { + if importDesc := f.importDesc; importDesc != nil { + moduleName, name, isImport = importDesc[0], importDesc[1], true + } + return +} + +// ExportNames implements the same method as documented on api.MemoryDefinition. +func (f *MemoryDefinition) ExportNames() []string { + return f.exportNames +} + +// Min implements the same method as documented on api.MemoryDefinition. +func (f *MemoryDefinition) Min() uint32 { + return f.memory.Min +} + +// Max implements the same method as documented on api.MemoryDefinition. +func (f *MemoryDefinition) Max() (max uint32, encoded bool) { + max = f.memory.Max + encoded = f.memory.IsMaxEncoded + return +} diff --git a/internal/wasm/memory_definition_test.go b/internal/wasm/memory_definition_test.go new file mode 100644 index 00000000..57319449 --- /dev/null +++ b/internal/wasm/memory_definition_test.go @@ -0,0 +1,121 @@ +package wasm + +import ( + "testing" + + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/internal/testing/require" +) + +func TestModule_BuildMemoryDefinitions(t *testing.T) { + tests := []struct { + name string + m *Module + expected []*MemoryDefinition + expectedImports []api.MemoryDefinition + expectedExports map[string]api.MemoryDefinition + }{ + { + name: "no exports", + m: &Module{}, + expectedExports: map[string]api.MemoryDefinition{}, + }, + { + name: "no memories", + m: &Module{ + ExportSection: []*Export{{Type: ExternTypeGlobal, Index: 0}}, + GlobalSection: []*Global{{}}, + }, + expectedExports: map[string]api.MemoryDefinition{}, + }, + { + name: "defines memory{0,}", + m: &Module{MemorySection: &Memory{Min: 0}}, + expected: []*MemoryDefinition{{index: 0, memory: &Memory{Min: 0}}}, + expectedExports: map[string]api.MemoryDefinition{}, + }, + { + name: "exports defined memory{2,3}", + m: &Module{ + ExportSection: []*Export{ + {Name: "memory_index=0", Type: ExternTypeMemory, Index: 0}, + {Name: "", Type: ExternTypeGlobal, Index: 0}, + }, + GlobalSection: []*Global{{}}, + MemorySection: &Memory{Min: 2, Max: 3, IsMaxEncoded: true}, + }, + expected: []*MemoryDefinition{ + { + index: 0, + exportNames: []string{"memory_index=0"}, + memory: &Memory{Min: 2, Max: 3, IsMaxEncoded: true}, + }, + }, + expectedExports: map[string]api.MemoryDefinition{ + "memory_index=0": &MemoryDefinition{ + index: 0, + exportNames: []string{"memory_index=0"}, + memory: &Memory{Min: 2, Max: 3, IsMaxEncoded: true}, + }, + }, + }, + { // NOTE: not yet supported https://github.com/WebAssembly/multi-memory + name: "exports imported memory{0,} and defined memory{2,3}", + m: &Module{ + ImportSection: []*Import{{ + Type: ExternTypeMemory, + DescMem: &Memory{Min: 0}, + }}, + ExportSection: []*Export{ + {Name: "imported_memory", Type: ExternTypeMemory, Index: 0}, + {Name: "memory_index=1", Type: ExternTypeMemory, Index: 1}, + }, + MemorySection: &Memory{Min: 2, Max: 3, IsMaxEncoded: true}, + }, + expected: []*MemoryDefinition{ + { + index: 0, + importDesc: &[2]string{"", ""}, + exportNames: []string{"imported_memory"}, + memory: &Memory{Min: 0}, + }, + { + index: 1, + exportNames: []string{"memory_index=1"}, + memory: &Memory{Min: 2, Max: 3, IsMaxEncoded: true}, + }, + }, + expectedImports: []api.MemoryDefinition{ + &MemoryDefinition{ + index: 0, + importDesc: &[2]string{"", ""}, + exportNames: []string{"imported_memory"}, + memory: &Memory{Min: 0}, + }, + }, + expectedExports: map[string]api.MemoryDefinition{ + "imported_memory": &MemoryDefinition{ + index: 0, + importDesc: &[2]string{"", ""}, + exportNames: []string{"imported_memory"}, + memory: &Memory{Min: 0}, + }, + "memory_index=1": &MemoryDefinition{ + index: 1, + exportNames: []string{"memory_index=1"}, + memory: &Memory{Min: 2, Max: 3, IsMaxEncoded: true}, + }, + }, + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + tc.m.BuildMemoryDefinitions() + require.Equal(t, tc.expected, tc.m.MemoryDefinitionSection) + require.Equal(t, tc.expectedImports, tc.m.ImportedMemories()) + require.Equal(t, tc.expectedExports, tc.m.ExportedMemories()) + }) + } +} diff --git a/internal/wasm/module.go b/internal/wasm/module.go index dcd637e7..2ab469ca 100644 --- a/internal/wasm/module.go +++ b/internal/wasm/module.go @@ -175,8 +175,11 @@ type Module struct { // ID is the sha256 value of the source wasm and is used for caching. ID ModuleID - // FunctionDefinitionSection is a wazero specific section built on Validate. + // FunctionDefinitionSection is a wazero-specific section built on Validate. FunctionDefinitionSection []*FunctionDefinition + + // MemoryDefinitionSection is a wazero-specific section built on Validate. + MemoryDefinitionSection []*MemoryDefinition } // ModuleID represents sha256 hash value uniquely assigned to Module. @@ -606,9 +609,9 @@ func (m *ModuleInstance) BuildFunctions(mod *Module, listeners []experimental.Fu f.Module = m f.Idx = d.index f.Type = d.funcType - f.FunctionDefinition = d + f.Definition = d if listeners != nil { - f.FunctionListener = listeners[i] + f.Listener = listeners[i] } } return diff --git a/internal/wasm/module_test.go b/internal/wasm/module_test.go index 3f6224d2..c1e8d744 100644 --- a/internal/wasm/module_test.go +++ b/internal/wasm/module_test.go @@ -787,7 +787,7 @@ func TestModule_buildFunctions(t *testing.T) { instance := &ModuleInstance{Name: "counter", TypeIDs: []FunctionTypeID{0}} instance.BuildFunctions(m, nil) for i, f := range instance.Functions { - require.Equal(t, i, f.FunctionDefinition.Index()) + require.Equal(t, i, f.Definition.Index()) require.Equal(t, nopCode.Body, f.Body) } } diff --git a/internal/wasm/store.go b/internal/wasm/store.go index 41c406d2..e3a510cf 100644 --- a/internal/wasm/store.go +++ b/internal/wasm/store.go @@ -140,11 +140,11 @@ type ( // Idx holds the index of this function instance in the function index namespace (beginning with imports). Idx Index - // FunctionDefinition is known at compile time. - FunctionDefinition api.FunctionDefinition + // Definition is known at compile time. + Definition api.FunctionDefinition - // FunctionListener holds a listener to notify when this function is called. - FunctionListener experimentalapi.FunctionListener + // Listener holds a listener to notify when this function is called. + Listener experimentalapi.FunctionListener } // GlobalInstance represents a global instance in a store. @@ -450,7 +450,7 @@ func resolveImports(module *Module, modules map[string]*ModuleInstance) ( expectedType := module.TypeSection[i.DescFunc] importedFunction := imported.Function - d := importedFunction.FunctionDefinition + d := importedFunction.Definition if !expectedType.EqualsSignature(d.ParamTypes(), d.ResultTypes()) { actualType := &FunctionType{Params: d.ParamTypes(), Results: d.ResultTypes()} err = errorInvalidImport(i, idx, fmt.Errorf("signature mismatch: %s != %s", expectedType, actualType)) diff --git a/internal/wasm/store_test.go b/internal/wasm/store_test.go index 35f37c00..6bde3c6f 100644 --- a/internal/wasm/store_test.go +++ b/internal/wasm/store_test.go @@ -401,7 +401,7 @@ func (e *mockModuleEngine) Close(_ context.Context) { // Call implements the same method as documented on wasm.ModuleEngine. func (ce *mockCallEngine) Call(ctx context.Context, callCtx *CallContext, _ ...uint64) (results []uint64, err error) { - if ce.callFailIndex >= 0 && ce.f.FunctionDefinition.Index() == Index(ce.callFailIndex) { + if ce.callFailIndex >= 0 && ce.f.Definition.Index() == Index(ce.callFailIndex) { err = errors.New("call failed") return } @@ -617,9 +617,9 @@ func Test_resolveImports(t *testing.T) { t.Run("func", func(t *testing.T) { t.Run("ok", func(t *testing.T) { f := &FunctionInstance{ - FunctionDefinition: &FunctionDefinition{funcType: &FunctionType{Results: []ValueType{ValueTypeF32}}}} + Definition: &FunctionDefinition{funcType: &FunctionType{Results: []ValueType{ValueTypeF32}}}} g := &FunctionInstance{ - FunctionDefinition: &FunctionDefinition{funcType: &FunctionType{Results: []ValueType{ValueTypeI32}}}} + Definition: &FunctionDefinition{funcType: &FunctionType{Results: []ValueType{ValueTypeI32}}}} modules := map[string]*ModuleInstance{ moduleName: { Exports: map[string]*ExportInstance{ @@ -651,7 +651,7 @@ func Test_resolveImports(t *testing.T) { t.Run("signature mismatch", func(t *testing.T) { modules := map[string]*ModuleInstance{ moduleName: {Exports: map[string]*ExportInstance{name: { - Function: &FunctionInstance{FunctionDefinition: &FunctionDefinition{funcType: &FunctionType{}}}, + Function: &FunctionInstance{Definition: &FunctionDefinition{funcType: &FunctionType{}}}, }}, Name: moduleName}, } m := &Module{ diff --git a/runtime.go b/runtime.go index 17d2c190..887f3c04 100644 --- a/runtime.go +++ b/runtime.go @@ -181,8 +181,9 @@ func (r *runtime) CompileModule(ctx context.Context, binary []byte) (CompiledMod internal.AssignModuleID(binary) - // Now that the module is validated, cache the function definitions. + // Now that the module is validated, cache the function and memory definitions. internal.BuildFunctionDefinitions() + internal.BuildMemoryDefinitions() c := &compiledModule{module: internal, compiledEngine: r.store.Engine} diff --git a/runtime_test.go b/runtime_test.go index a795281d..0fb2b2ff 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -36,10 +36,11 @@ func TestNewRuntimeWithConfig_version(t *testing.T) { func TestRuntime_CompileModule(t *testing.T) { tests := []struct { - name string - runtime Runtime - wasm []byte - expectedName string + name string + runtime Runtime + wasm []byte + moduleBuilder HostModuleBuilder + expected func(CompiledModule) }{ { name: "no name section", @@ -50,9 +51,70 @@ func TestRuntime_CompileModule(t *testing.T) { wasm: binaryformat.EncodeModule(&wasm.Module{NameSection: &wasm.NameSection{}}), }, { - name: "NameSection.ModuleName", - wasm: binaryformat.EncodeModule(&wasm.Module{NameSection: &wasm.NameSection{ModuleName: "test"}}), - expectedName: "test", + name: "NameSection.ModuleName", + wasm: binaryformat.EncodeModule(&wasm.Module{NameSection: &wasm.NameSection{ModuleName: "test"}}), + expected: func(compiled CompiledModule) { + require.Equal(t, "test", compiled.Name()) + }, + }, + { + name: "FunctionSection, but not exported", + wasm: binaryformat.EncodeModule(&wasm.Module{ + TypeSection: []*wasm.FunctionType{{Params: []api.ValueType{api.ValueTypeI32}}}, + FunctionSection: []wasm.Index{0}, + CodeSection: []*wasm.Code{{Body: []byte{wasm.OpcodeEnd}}}, + }), + expected: func(compiled CompiledModule) { + require.Nil(t, compiled.ImportedFunctions()) + require.Zero(t, len(compiled.ExportedFunctions())) + }, + }, + { + name: "FunctionSection exported", + wasm: binaryformat.EncodeModule(&wasm.Module{ + TypeSection: []*wasm.FunctionType{{Params: []api.ValueType{api.ValueTypeI32}}}, + FunctionSection: []wasm.Index{0}, + CodeSection: []*wasm.Code{{Body: []byte{wasm.OpcodeEnd}}}, + ExportSection: []*wasm.Export{{ + Type: wasm.ExternTypeFunc, + Name: "function", + Index: 0, + }}, + }), + expected: func(compiled CompiledModule) { + require.Nil(t, compiled.ImportedFunctions()) + f := compiled.ExportedFunctions()["function"] + require.Equal(t, []api.ValueType{api.ValueTypeI32}, f.ParamTypes()) + }, + }, + { + name: "MemorySection, but not exported", + wasm: binaryformat.EncodeModule(&wasm.Module{ + MemorySection: &wasm.Memory{Min: 2, Max: 3, IsMaxEncoded: true}, + }), + expected: func(compiled CompiledModule) { + require.Nil(t, compiled.ImportedMemories()) + require.Zero(t, len(compiled.ExportedMemories())) + }, + }, + { + name: "MemorySection exported", + wasm: binaryformat.EncodeModule(&wasm.Module{ + MemorySection: &wasm.Memory{Min: 2, Max: 3, IsMaxEncoded: true}, + ExportSection: []*wasm.Export{{ + Type: wasm.ExternTypeMemory, + Name: "memory", + Index: 0, + }}, + }), + expected: func(compiled CompiledModule) { + require.Nil(t, compiled.ImportedMemories()) + mem := compiled.ExportedMemories()["memory"] + require.Equal(t, uint32(2), mem.Min()) + max, ok := mem.Max() + require.Equal(t, uint32(3), max) + require.True(t, ok) + }, }, } @@ -65,11 +127,11 @@ func TestRuntime_CompileModule(t *testing.T) { t.Run(tc.name, func(t *testing.T) { m, err := r.CompileModule(testCtx, tc.wasm) require.NoError(t, err) - code := m.(*compiledModule) - if tc.expectedName != "" { - require.Equal(t, tc.expectedName, code.module.NameSection.ModuleName) + if tc.expected == nil { + tc.expected = func(CompiledModule) {} } - require.Equal(t, r.(*runtime).store.Engine, code.compiledEngine) + tc.expected(m) + require.Equal(t, r.(*runtime).store.Engine, m.(*compiledModule).compiledEngine) }) } }