Repository: tetratelabs/wazero
Pull request #817 (2022-09-29 02:43:13)

Adds MemoryDefinition to CompiledModule and Memory
It is more often the case that projects are enabling a freestanding target, and that may or may not have an exporting memory depending on how that's interpreted. This adds the ability to inspect memories similar to how you can already inspect compiled code prior to instantiation. For example, you can enforce an ABI constraint that "memory" must be exported even if WASI is not in use.

Here's an example of validating both a function and a memory are exported prior to instantiating the guest.
```go
const (
	FuncHandle = "handle"
	Memory = "memory"
)

// validateGuestModule ensures the guest exports features the host ABI requires.
func validateGuestModule(guest wazero.CompiledModule) error {
	if handle, ok := guest.ExportedFunctions()[FuncHandle]; !ok {
		return fmt.Errorf("wasm: guest doesn't export func[%s]", FuncHandle)
	} else if len(handle.ParamTypes()) != 0 || len(handle.ResultTypes()) != 0 {
		return fmt.Errorf("wasm: guest exports the wrong signature for func[%s]. should be nullary", FuncHandle)
	} else if _, ok = guest.ExportedMemories()[Memory]; !ok {
		return fmt.Errorf("wasm: guest doesn't export memory[%s]", Memory)
	}
	return nil
}
```

See https://github.com/tinygo-org/tinygo/pull/3072

cc @hunjixin


Patch:
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/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/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/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/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}
 


Test patch:
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/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_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_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_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)
 		})
 	}
 }