Repository: tetratelabs/wazero
Pull request #894 (2022-12-07 01:30:05)

ModuleInstance.Functions/Exports as non-pointer slice 
Ref #889 

Here's a benchmark result for `BenchmarkInitialization` with [libcre2.so](https://github.com/anuraaga/re2-go/blob/main/wasm/libcre2.so) binary by @anuraaga 

```
$ benchstat old_re2.txt new_re2.txt
name                                    old time/op    new time/op    delta
Initialization/interpreter-10              152µs ± 3%     139µs ± 2%   -8.39%  (p=0.008 n=5+5)
Initialization/interpreter-multiple-10    62.4µs ± 2%    74.4µs ±38%     ~     (p=0.730 n=4+5)
Initialization/compiler-10                 197µs ±24%     170µs ± 4%  -13.51%  (p=0.008 n=5+5)
Initialization/compiler-multiple-10       58.2µs ±16%    44.2µs ±14%  -24.01%  (p=0.016 n=5+5)

name                                    old alloc/op   new alloc/op   delta
Initialization/interpreter-10              630kB ± 0%     553kB ± 0%  -12.28%  (p=0.008 n=5+5)
Initialization/interpreter-multiple-10     631kB ± 0%     554kB ± 0%  -12.25%  (p=0.008 n=5+5)
Initialization/compiler-10                 543kB ± 0%     509kB ± 0%   -6.21%  (p=0.008 n=5+5)
Initialization/compiler-multiple-10        544kB ± 0%     510kB ± 0%   -6.21%  (p=0.008 n=5+5)

name                                    old allocs/op  new allocs/op  delta
Initialization/interpreter-10              1.92k ± 0%     1.88k ± 0%   -1.83%  (p=0.008 n=5+5)
Initialization/interpreter-multiple-10     1.92k ± 0%     1.89k ± 0%   -1.82%  (p=0.000 n=5+4)
Initialization/compiler-10                  62.0 ± 0%      40.0 ± 0%  -35.48%  (p=0.008 n=5+5)
Initialization/compiler-multiple-10         69.0 ± 0%      47.0 ± 0%  -31.88%  (p=0.008 n=5+5)

```
### `old_re2.txt`

```
goos: darwin
goarch: arm64
pkg: github.com/tetratelabs/wazero/internal/integration_test/bench
BenchmarkInitialization/interpreter-10         	    7048	    149845 ns/op	  630451 B/op	    1915 allocs/op
BenchmarkInitialization/interpreter-multiple-10         	   16717	     61864 ns/op	  631379 B/op	    1922 allocs/op
BenchmarkInitialization/compiler-10                     	    7623	    244489 ns/op	  542738 B/op	      62 allocs/op
BenchmarkInitialization/compiler-multiple-10            	   28168	     65375 ns/op	  543597 B/op	      69 allocs/op
PASS
ok  	github.com/tetratelabs/wazero/internal/integration_test/bench	12.556s
goos: darwin
goarch: arm64
pkg: github.com/tetratelabs/wazero/internal/integration_test/bench
BenchmarkInitialization/interpreter-10         	    6861	    149691 ns/op	  630450 B/op	    1915 allocs/op
BenchmarkInitialization/interpreter-multiple-10         	   16336	     63583 ns/op	  631306 B/op	    1922 allocs/op
BenchmarkInitialization/compiler-10                     	    6507	    179949 ns/op	  542747 B/op	      62 allocs/op
BenchmarkInitialization/compiler-multiple-10            	   27397	     58223 ns/op	  543572 B/op	      69 allocs/op
PASS
ok  	github.com/tetratelabs/wazero/internal/integration_test/bench	12.309s
goos: darwin
goarch: arm64
pkg: github.com/tetratelabs/wazero/internal/integration_test/bench
BenchmarkInitialization/interpreter-10         	    6944	    149922 ns/op	  630450 B/op	    1915 allocs/op
BenchmarkInitialization/interpreter-multiple-10         	   18090	     61727 ns/op	  631365 B/op	    1922 allocs/op
BenchmarkInitialization/compiler-10                     	    8559	    181302 ns/op	  542739 B/op	      62 allocs/op
BenchmarkInitialization/compiler-multiple-10            	   28434	     59833 ns/op	  543567 B/op	      69 allocs/op
PASS
ok  	github.com/tetratelabs/wazero/internal/integration_test/bench	11.860s
goos: darwin
goarch: arm64
pkg: github.com/tetratelabs/wazero/internal/integration_test/bench
BenchmarkInitialization/interpreter-10         	    6868	    156870 ns/op	  630453 B/op	    1915 allocs/op
BenchmarkInitialization/interpreter-multiple-10         	   17752	     62528 ns/op	  631370 B/op	    1922 allocs/op
BenchmarkInitialization/compiler-10                     	    6874	    191044 ns/op	  542742 B/op	      62 allocs/op
BenchmarkInitialization/compiler-multiple-10            	   30200	     58636 ns/op	  543610 B/op	      69 allocs/op
PASS
ok  	github.com/tetratelabs/wazero/internal/integration_test/bench	11.311s
goos: darwin
goarch: arm64
pkg: github.com/tetratelabs/wazero/internal/integration_test/bench
BenchmarkInitialization/interpreter-10         	    6967	    151888 ns/op	  630452 B/op	    1915 allocs/op
BenchmarkInitialization/interpreter-multiple-10         	   15666	     83863 ns/op	  631391 B/op	    1922 allocs/op
BenchmarkInitialization/compiler-10                     	    5769	    186195 ns/op	  542755 B/op	      62 allocs/op
BenchmarkInitialization/compiler-multiple-10            	   23019	     48930 ns/op	  543521 B/op	      69 allocs/op
PASS
ok  	github.com/tetratelabs/wazero/internal/integration_test/bench	9.294s

```


### `new_re2.txt`

```
goos: darwin
goarch: arm64
pkg: github.com/tetratelabs/wazero/internal/integration_test/bench
BenchmarkInitialization/interpreter-10         	   43489	     25071 ns/op	  141474 B/op	      85 allocs/op
BenchmarkInitialization/interpreter-multiple-10         	  103897	     11115 ns/op	  142588 B/op	      92 allocs/op
BenchmarkInitialization/compiler-10                     	   36500	     33019 ns/op	  144560 B/op	      35 allocs/op
BenchmarkInitialization/compiler-multiple-10            	  101678	     18000 ns/op	  145272 B/op	      41 allocs/op
PASS
ok  	github.com/tetratelabs/wazero/internal/integration_test/bench	6.615s
goos: darwin
goarch: arm64
pkg: github.com/tetratelabs/wazero/internal/integration_test/bench
BenchmarkInitialization/interpreter-10         	   51102	     31718 ns/op	  141474 B/op	      85 allocs/op
BenchmarkInitialization/interpreter-multiple-10         	   91940	     13639 ns/op	  142592 B/op	      92 allocs/op
BenchmarkInitialization/compiler-10                     	   30066	     36717 ns/op	  144560 B/op	      35 allocs/op
BenchmarkInitialization/compiler-multiple-10            	   97819	     15121 ns/op	  145301 B/op	      41 allocs/op
PASS
ok  	github.com/tetratelabs/wazero/internal/integration_test/bench	6.837s
goos: darwin
goarch: arm64
pkg: github.com/tetratelabs/wazero/internal/integration_test/bench
BenchmarkInitialization/interpreter-10         	   53106	     21618 ns/op	  141473 B/op	      85 allocs/op
BenchmarkInitialization/interpreter-multiple-10         	  108909	     10744 ns/op	  142691 B/op	      92 allocs/op
BenchmarkInitialization/compiler-10                     	   36554	     33729 ns/op	  144559 B/op	      35 allocs/op
BenchmarkInitialization/compiler-multiple-10            	  103538	     17683 ns/op	  145264 B/op	      41 allocs/op
PASS
ok  	github.com/tetratelabs/wazero/internal/integration_test/bench	6.637s
goos: darwin
goarch: arm64
pkg: github.com/tetratelabs/wazero/internal/integration_test/bench
BenchmarkInitialization/interpreter-10         	   54764	     21388 ns/op	  141473 B/op	      85 allocs/op
BenchmarkInitialization/interpreter-multiple-10         	  107838	     10712 ns/op	  142695 B/op	      92 allocs/op
BenchmarkInitialization/compiler-10                     	   35791	     32797 ns/op	  144560 B/op	      35 allocs/op
BenchmarkInitialization/compiler-multiple-10            	   98738	     16353 ns/op	  145272 B/op	      41 allocs/op
PASS
ok  	github.com/tetratelabs/wazero/internal/integration_test/bench	6.384s
goos: darwin
goarch: arm64
pkg: github.com/tetratelabs/wazero/internal/integration_test/bench
BenchmarkInitialization/interpreter-10         	   54931	     22534 ns/op	  141473 B/op	      85 allocs/op
BenchmarkInitialization/interpreter-multiple-10         	  107678	     10865 ns/op	  142697 B/op	      92 allocs/op
BenchmarkInitialization/compiler-10                     	   36722	     32671 ns/op	  144559 B/op	      35 allocs/op
BenchmarkInitialization/compiler-multiple-10            	   91105	     14615 ns/op	  145270 B/op	      41 allocs/op
PASS
ok  	github.com/tetratelabs/wazero/internal/integration_test/bench	7.212s
```


Patch:
diff --git a/internal/engine/compiler/engine.go b/internal/engine/compiler/engine.go
index 25030269..c7ecb74a 100644
--- a/internal/engine/compiler/engine.go
+++ b/internal/engine/compiler/engine.go
@@ -49,8 +49,6 @@ type (
 		// as the underlying memory region is accessed by assembly directly by using
 		// codesElement0Address.
 		functions []*function
-
-		importedFunctionCount uint32
 	}
 
 	// callEngine holds context per moduleEngine.Call, and shared across all the
@@ -545,15 +543,14 @@ func (e *engine) CompileModule(ctx context.Context, module *wasm.Module, listene
 }
 
 // NewModuleEngine implements the same method as documented on wasm.Engine.
-func (e *engine) NewModuleEngine(name string, module *wasm.Module, importedFunctions, moduleFunctions []*wasm.FunctionInstance) (wasm.ModuleEngine, error) {
-	imported := len(importedFunctions)
+func (e *engine) NewModuleEngine(name string, module *wasm.Module, functions []wasm.FunctionInstance) (wasm.ModuleEngine, error) {
 	me := &moduleEngine{
-		name:                  name,
-		functions:             make([]*function, imported+len(moduleFunctions)),
-		importedFunctionCount: uint32(imported),
+		name:      name,
+		functions: make([]*function, len(functions)),
 	}
 
-	for i, f := range importedFunctions {
+	imported := int(module.ImportFuncCount())
+	for i, f := range functions[:imported] {
 		cf := f.Module.Engine.(*moduleEngine).functions[f.Idx]
 		me.functions[i] = cf
 	}
@@ -567,12 +564,12 @@ func (e *engine) NewModuleEngine(name string, module *wasm.Module, importedFunct
 
 	// TODO: Ideally we only have one []function slice, instead of a memory store and a slice of pointers.
 	// This will require refactoring in assembly, so for now we simply have two slices.
-	functionsMem := make([]function, len(moduleFunctions))
+	functionsMem := make([]function, len(functions[imported:]))
 	for i, c := range codes {
-		f := moduleFunctions[i]
-		function := c.createFunction(f)
-		functionsMem[i] = function
-		me.functions[imported+i] = &functionsMem[i]
+		offset := imported + i
+		f := &functions[offset]
+		functionsMem[i] = c.createFunction(f)
+		me.functions[offset] = &functionsMem[i]
 	}
 	return me, nil
 }
diff --git a/internal/engine/interpreter/interpreter.go b/internal/engine/interpreter/interpreter.go
index 1ae92e94..1a858b9a 100644
--- a/internal/engine/interpreter/interpreter.go
+++ b/internal/engine/interpreter/interpreter.go
@@ -78,8 +78,7 @@ type moduleEngine struct {
 	functions []*function
 
 	// parentEngine holds *engine from which this module engine is created from.
-	parentEngine          *engine
-	importedFunctionCount uint32
+	parentEngine *engine
 }
 
 // callEngine holds context per moduleEngine.Call, and shared across all the
@@ -263,17 +262,17 @@ func (e *engine) CompileModule(ctx context.Context, module *wasm.Module, listene
 }
 
 // NewModuleEngine implements the same method as documented on wasm.Engine.
-func (e *engine) NewModuleEngine(name string, module *wasm.Module, importedFunctions, moduleFunctions []*wasm.FunctionInstance) (wasm.ModuleEngine, error) {
-	imported := uint32(len(importedFunctions))
+func (e *engine) NewModuleEngine(name string, module *wasm.Module, functions []wasm.FunctionInstance) (wasm.ModuleEngine, error) {
 	me := &moduleEngine{
-		name:                  name,
-		parentEngine:          e,
-		importedFunctionCount: imported,
+		name:         name,
+		parentEngine: e,
+		functions:    make([]*function, len(functions)),
 	}
 
-	for _, f := range importedFunctions {
+	imported := int(module.ImportFuncCount())
+	for i, f := range functions[:imported] {
 		cf := f.Module.Engine.(*moduleEngine).functions[f.Idx]
-		me.functions = append(me.functions, cf)
+		me.functions[i] = cf
 	}
 
 	codes, ok := e.getCodes(module)
@@ -282,9 +281,10 @@ func (e *engine) NewModuleEngine(name string, module *wasm.Module, importedFunct
 	}
 
 	for i, c := range codes {
-		f := moduleFunctions[i]
-		insntantiatedcode := c.instantiate(f)
-		me.functions = append(me.functions, insntantiatedcode)
+		offset := i + imported
+		f := &functions[offset]
+		inst := c.instantiate(f)
+		me.functions[offset] = inst
 	}
 	return me, nil
 }
diff --git a/internal/wasm/call_context.go b/internal/wasm/call_context.go
index 638883dc..2c012c11 100644
--- a/internal/wasm/call_context.go
+++ b/internal/wasm/call_context.go
@@ -127,11 +127,12 @@ func (m *CallContext) Memory() api.Memory {
 
 // ExportedMemory implements the same method as documented on api.Module.
 func (m *CallContext) ExportedMemory(name string) api.Memory {
-	exp, err := m.module.getExport(name, ExternTypeMemory)
+	_, err := m.module.getExport(name, ExternTypeMemory)
 	if err != nil {
 		return nil
 	}
-	return exp.Memory
+	// We Assume that we have at most one memory.
+	return m.memory
 }
 
 // ExportedFunction implements the same method as documented on api.Module.
@@ -141,7 +142,7 @@ func (m *CallContext) ExportedFunction(name string) api.Function {
 		return nil
 	}
 
-	return m.function(exp.Function)
+	return m.function(&m.module.Functions[exp.Index])
 }
 
 // Module is exposed for emscripten.
@@ -153,7 +154,7 @@ func (m *CallContext) Function(funcIdx Index) api.Function {
 	if uint32(len(m.module.Functions)) < funcIdx {
 		return nil
 	}
-	return m.function(m.module.Functions[funcIdx])
+	return m.function(&m.module.Functions[funcIdx])
 }
 
 func (m *CallContext) function(f *FunctionInstance) api.Function {
@@ -218,19 +219,20 @@ func (m *CallContext) ExportedGlobal(name string) api.Global {
 	if err != nil {
 		return nil
 	}
-	if exp.Global.Type.Mutable {
-		return &mutableGlobal{exp.Global}
+	g := m.module.Globals[exp.Index]
+	if g.Type.Mutable {
+		return &mutableGlobal{g}
 	}
-	valType := exp.Global.Type.ValType
+	valType := g.Type.ValType
 	switch valType {
 	case ValueTypeI32:
-		return globalI32(exp.Global.Val)
+		return globalI32(g.Val)
 	case ValueTypeI64:
-		return globalI64(exp.Global.Val)
+		return globalI64(g.Val)
 	case ValueTypeF32:
-		return globalF32(exp.Global.Val)
+		return globalF32(g.Val)
 	case ValueTypeF64:
-		return globalF64(exp.Global.Val)
+		return globalF64(g.Val)
 	default:
 		panic(fmt.Errorf("BUG: unknown value type %X", valType))
 	}
diff --git a/internal/wasm/engine.go b/internal/wasm/engine.go
index eec6c15a..01f137e3 100644
--- a/internal/wasm/engine.go
+++ b/internal/wasm/engine.go
@@ -24,12 +24,11 @@ type Engine interface {
 	//
 	// * name is the name the module was instantiated with used for error handling.
 	// * module is the source module from which moduleFunctions are instantiated. This is used for caching.
-	// * importedFunctions: functions this module imports, already compiled in this engine.
-	// * moduleFunctions: functions declared in this module that must be compiled.
+	// * functions: the list of FunctionInstance which exists in this module, including the imported ones.
 	//
 	// Note: Input parameters must be pre-validated with wasm.Module Validate, to ensure no fields are invalid
 	// due to reasons such as out-of-bounds.
-	NewModuleEngine(name string, module *Module, importedFunctions, moduleFunctions []*FunctionInstance) (ModuleEngine, error)
+	NewModuleEngine(name string, module *Module, functions []FunctionInstance) (ModuleEngine, error)
 }
 
 // ModuleEngine implements function calls for a given module.
diff --git a/internal/wasm/module.go b/internal/wasm/module.go
index 16a015e2..0b6392b0 100644
--- a/internal/wasm/module.go
+++ b/internal/wasm/module.go
@@ -604,19 +604,22 @@ func (m *Module) buildGlobals(importedGlobals []*GlobalInstance, funcRefResolver
 //   - This relies on data generated by Module.BuildFunctionDefinitions.
 //   - This is exported for tests that don't call Instantiate, notably only
 //     enginetest.go.
-func (m *ModuleInstance) BuildFunctions(mod *Module) (fns []*FunctionInstance) {
+func (m *ModuleInstance) BuildFunctions(mod *Module, importedFunctions []*FunctionInstance) (fns []FunctionInstance) {
 	importCount := mod.ImportFuncCount()
-	// TODO: Ideally we only have one []FunctionInstance slice, instead of a memory store and a slice of pointers.
-	// This will require refactoring in assembly, so for now we simply have two slices.
-	fnsMem := make([]FunctionInstance, len(mod.FunctionSection))
-	fns = make([]*FunctionInstance, len(mod.FunctionSection))
+	fns = make([]FunctionInstance, importCount+uint32(len(mod.FunctionSection)))
+	m.Functions = fns
+
+	for i, imp := range importedFunctions {
+		fns[i] = *imp
+	}
 	for i, section := range mod.FunctionSection {
+		offset := uint32(i) + importCount
 		code := mod.CodeSection[i]
-		d := mod.FunctionDefinitionSection[uint32(i)+importCount]
+		d := mod.FunctionDefinitionSection[offset]
 		// This object is only referenced from a slice. Instead of creating a heap object
 		// here and storing a pointer, we store the struct directly in the slice. This
 		// reduces the number of heap objects which improves GC performance.
-		fnsMem[i] = FunctionInstance{
+		fns[offset] = FunctionInstance{
 			IsHostFunction: code.IsHostFunction,
 			LocalTypes:     code.LocalTypes,
 			Body:           code.Body,
@@ -627,7 +630,6 @@ func (m *ModuleInstance) BuildFunctions(mod *Module) (fns []*FunctionInstance) {
 			Type:           d.funcType,
 			Definition:     d,
 		}
-		fns[i] = &fnsMem[i]
 	}
 	return
 }
diff --git a/internal/wasm/store.go b/internal/wasm/store.go
index 12753731..d4ee2cb0 100644
--- a/internal/wasm/store.go
+++ b/internal/wasm/store.go
@@ -54,8 +54,8 @@ type (
 	// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-moduleinst
 	ModuleInstance struct {
 		Name      string
-		Exports   map[string]*ExportInstance
-		Functions []*FunctionInstance
+		Exports   map[string]ExportInstance
+		Functions []FunctionInstance
 		Globals   []*GlobalInstance
 		// Memory is set when Module.MemorySection had a memory, regardless of whether it was exported.
 		Memory *MemoryInstance
@@ -95,11 +95,8 @@ type (
 	//
 	// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-exportinst
 	ExportInstance struct {
-		Type     ExternType
-		Function *FunctionInstance
-		Global   *GlobalInstance
-		Memory   *MemoryInstance
-		Table    *TableInstance
+		Type  ExternType
+		Index Index
 	}
 
 	// FunctionInstance represents a function instance in a Store.
@@ -161,8 +158,7 @@ type (
 const maximumFunctionTypes = 1 << 27
 
 // addSections adds section elements to the ModuleInstance
-func (m *ModuleInstance) addSections(module *Module, importedFunctions, functions []*FunctionInstance,
-	importedGlobals, globals []*GlobalInstance, tables []*TableInstance, memory, importedMemory *MemoryInstance,
+func (m *ModuleInstance) addSections(module *Module, importedGlobals, globals []*GlobalInstance, tables []*TableInstance, memory, importedMemory *MemoryInstance,
 	types []*FunctionType,
 ) {
 	m.Types = types
@@ -170,7 +166,6 @@ func (m *ModuleInstance) addSections(module *Module, importedFunctions, function
 	for i, t := range types {
 		m.TypeIDIndex[t.string] = m.TypeIDs[i]
 	}
-	m.Functions = append(importedFunctions, functions...)
 	m.Globals = append(importedGlobals, globals...)
 	m.Tables = tables
 
@@ -225,23 +220,10 @@ func (m *ModuleInstance) applyTableInits(tables []*TableInstance, tableInits []t
 }
 
 func (m *ModuleInstance) BuildExports(exports []*Export) {
-	m.Exports = make(map[string]*ExportInstance, len(exports))
+	m.Exports = make(map[string]ExportInstance, len(exports))
 	for _, exp := range exports {
-		index := exp.Index
-		var ei *ExportInstance
-		switch exp.Type {
-		case ExternTypeFunc:
-			ei = &ExportInstance{Type: exp.Type, Function: m.Functions[index]}
-		case ExternTypeGlobal:
-			ei = &ExportInstance{Type: exp.Type, Global: m.Globals[index]}
-		case ExternTypeMemory:
-			ei = &ExportInstance{Type: exp.Type, Memory: m.Memory}
-		case ExternTypeTable:
-			ei = &ExportInstance{Type: exp.Type, Table: m.Tables[index]}
-		}
-
 		// We already validated the duplicates during module validation phase.
-		m.Exports[exp.Name] = ei
+		m.Exports[exp.Name] = ExportInstance{Type: exp.Type, Index: exp.Index}
 	}
 }
 
@@ -279,13 +261,13 @@ func (m *ModuleInstance) applyData(data []*DataSegment) error {
 }
 
 // GetExport returns an export of the given name and type or errs if not exported or the wrong type.
-func (m *ModuleInstance) getExport(name string, et ExternType) (*ExportInstance, error) {
+func (m *ModuleInstance) getExport(name string, et ExternType) (ExportInstance, error) {
 	exp, ok := m.Exports[name]
 	if !ok {
-		return nil, fmt.Errorf("%q is not exported in module %q", name, m.Name)
+		return ExportInstance{}, fmt.Errorf("%q is not exported in module %q", name, m.Name)
 	}
 	if exp.Type != et {
-		return nil, fmt.Errorf("export %q in module %q is a %s, not a %s", name, m.Name, ExternTypeName(exp.Type), ExternTypeName(et))
+		return ExportInstance{}, fmt.Errorf("export %q in module %q is a %s, not a %s", name, m.Name, ExternTypeName(exp.Type), ExternTypeName(et))
 	}
 	return exp, nil
 }
@@ -380,10 +362,10 @@ func (s *Store) instantiate(
 	}
 
 	m := &ModuleInstance{Name: name, TypeIDs: typeIDs}
-	functions := m.BuildFunctions(module)
+	functions := m.BuildFunctions(module, importedFunctions)
 
 	// Plus, we are ready to compile functions.
-	m.Engine, err = s.Engine.NewModuleEngine(name, module, importedFunctions, functions)
+	m.Engine, err = s.Engine.NewModuleEngine(name, module, functions)
 	if err != nil {
 		return nil, err
 	}
@@ -391,7 +373,7 @@ func (s *Store) instantiate(
 	globals, memory := module.buildGlobals(importedGlobals, m.Engine.FunctionInstanceReference), module.buildMemory()
 
 	// Now we have all instances from imports and local ones, so ready to create a new ModuleInstance.
-	m.addSections(module, importedFunctions, functions, importedGlobals, globals, tables, importedMemory, memory, module.TypeSection)
+	m.addSections(module, importedGlobals, globals, tables, importedMemory, memory, module.TypeSection)
 
 	// As of reference types proposal, data segment validation must happen after instantiation,
 	// and the side effect must persist even if there's out of bounds error after instantiation.
@@ -419,7 +401,7 @@ func (s *Store) instantiate(
 	// Execute the start function.
 	if module.StartSection != nil {
 		funcIdx := *module.StartSection
-		f := m.Functions[funcIdx]
+		f := &m.Functions[funcIdx]
 
 		ce, err := f.Module.Engine.NewCallEngine(callCtx, f)
 		if err != nil {
@@ -452,7 +434,7 @@ func resolveImports(module *Module, modules map[string]*ModuleInstance) (
 			return
 		}
 
-		var imported *ExportInstance
+		var imported ExportInstance
 		imported, err = m.getExport(i.Name, i.Type)
 		if err != nil {
 			return
@@ -467,7 +449,7 @@ func resolveImports(module *Module, modules map[string]*ModuleInstance) (
 				return
 			}
 			expectedType := module.TypeSection[i.DescFunc]
-			importedFunction := imported.Function
+			importedFunction := &m.Functions[imported.Index]
 
 			d := importedFunction.Definition
 			if !expectedType.EqualsSignature(d.ParamTypes(), d.ResultTypes()) {
@@ -479,7 +461,7 @@ func resolveImports(module *Module, modules map[string]*ModuleInstance) (
 			importedFunctions = append(importedFunctions, importedFunction)
 		case ExternTypeTable:
 			expected := i.DescTable
-			importedTable := imported.Table
+			importedTable := m.Tables[imported.Index]
 			if expected.Type != importedTable.Type {
 				err = errorInvalidImport(i, idx, fmt.Errorf("table type mismatch: %s != %s",
 					RefTypeName(expected.Type), RefTypeName(importedTable.Type)))
@@ -503,7 +485,7 @@ func resolveImports(module *Module, modules map[string]*ModuleInstance) (
 			importedTables = append(importedTables, importedTable)
 		case ExternTypeMemory:
 			expected := i.DescMem
-			importedMemory = imported.Memory
+			importedMemory = m.Memory
 
 			if expected.Min > memoryBytesNumToPages(uint64(len(importedMemory.Buffer))) {
 				err = errorMinSizeMismatch(i, idx, expected.Min, importedMemory.Min)
@@ -516,7 +498,7 @@ func resolveImports(module *Module, modules map[string]*ModuleInstance) (
 			}
 		case ExternTypeGlobal:
 			expected := i.DescGlobal
-			importedGlobal := imported.Global
+			importedGlobal := m.Globals[imported.Index]
 
 			if expected.Mutable != importedGlobal.Type.Mutable {
 				err = errorInvalidImport(i, idx, fmt.Errorf("mutability mismatch: %t != %t",


Test patch:
diff --git a/internal/engine/compiler/compiler_controlflow_test.go b/internal/engine/compiler/compiler_controlflow_test.go
index 32eb223b..5a6b3e69 100644
--- a/internal/engine/compiler/compiler_controlflow_test.go
+++ b/internal/engine/compiler/compiler_controlflow_test.go
@@ -841,7 +841,7 @@ func TestCompiler_compileCall(t *testing.T) {
 			moduleInstanceAddress: uintptr(unsafe.Pointer(env.moduleInstance)),
 		})
 		env.module().Functions = append(env.module().Functions,
-			&wasm.FunctionInstance{Type: targetFunctionType, Idx: index})
+			wasm.FunctionInstance{Type: targetFunctionType, Idx: index})
 	}
 
 	// Now we start building the caller's code.
diff --git a/internal/engine/interpreter/interpreter_test.go b/internal/engine/interpreter/interpreter_test.go
index 2b856fe8..8ea6d0dd 100644
--- a/internal/engine/interpreter/interpreter_test.go
+++ b/internal/engine/interpreter/interpreter_test.go
@@ -495,8 +495,7 @@ func TestInterpreter_Compile(t *testing.T) {
 		e := et.NewEngine(api.CoreFeaturesV1).(*engine)
 		_, err := e.NewModuleEngine("foo",
 			&wasm.Module{},
-			nil, // imports
-			nil, // moduleFunctions
+			nil, // functions
 		)
 		require.EqualError(t, err, "source module for foo must be compiled before instantiation")
 	})
diff --git a/internal/integration_test/bench/bench_test.go b/internal/integration_test/bench/bench_test.go
index 58db9c76..34c8f82e 100644
--- a/internal/integration_test/bench/bench_test.go
+++ b/internal/integration_test/bench/bench_test.go
@@ -51,7 +51,7 @@ func BenchmarkInitialization(b *testing.B) {
 		runInitializationConcurrentBench(b, r)
 	})
 
-	if runtime.GOARCH == "amd64" || runtime.GOARCH == "arm64" {
+	if platform.CompilerSupported() {
 		b.Run("compiler", func(b *testing.B) {
 			r := createRuntime(b, wazero.NewRuntimeConfigCompiler())
 			runInitializationBench(b, r)
diff --git a/internal/integration_test/bench/hostfunc_bench_test.go b/internal/integration_test/bench/hostfunc_bench_test.go
index 310cf3a2..b4dd2fc4 100644
--- a/internal/integration_test/bench/hostfunc_bench_test.go
+++ b/internal/integration_test/bench/hostfunc_bench_test.go
@@ -122,7 +122,8 @@ func TestBenchmarkFunctionCall(t *testing.T) {
 }
 
 func getCallEngine(m *wasm.ModuleInstance, name string) (ce wasm.CallEngine, err error) {
-	f := m.Exports[name].Function
+	exp := m.Exports[name]
+	f := &m.Functions[exp.Index]
 	if f == nil {
 		err = fmt.Errorf("%s not found", name)
 		return
@@ -185,16 +186,16 @@ func setupHostCallBench(requireNoError func(error)) *wasm.ModuleInstance {
 	hostModule.BuildFunctionDefinitions()
 
 	host := &wasm.ModuleInstance{Name: "host", TypeIDs: []wasm.FunctionTypeID{0}}
-	host.Functions = host.BuildFunctions(hostModule)
+	host.Functions = host.BuildFunctions(hostModule, nil)
 	host.BuildExports(hostModule.ExportSection)
-	goFn := host.Exports["go"].Function
-	goReflectFn := host.Exports["go-reflect"].Function
-	wasnFn := host.Exports["wasm"].Function
+	goFn := &host.Functions[host.Exports["go"].Index]
+	goReflectFn := &host.Functions[host.Exports["go-reflect"].Index]
+	wasnFn := &host.Functions[host.Exports["wasm"].Index]
 
 	err := eng.CompileModule(testCtx, hostModule, nil)
 	requireNoError(err)
 
-	hostME, err := eng.NewModuleEngine(host.Name, hostModule, nil, host.Functions)
+	hostME, err := eng.NewModuleEngine(host.Name, hostModule, host.Functions)
 	requireNoError(err)
 	linkModuleToEngine(host, hostME)
 
@@ -209,9 +210,9 @@ func setupHostCallBench(requireNoError func(error)) *wasm.ModuleInstance {
 		},
 		FunctionSection: []wasm.Index{0, 0, 0},
 		ExportSection: []*wasm.Export{
-			{Name: callGoHostName, Type: wasm.ExternTypeFunc, Index: 2},
-			{Name: callGoReflectHostName, Type: wasm.ExternTypeFunc, Index: 3},
-			{Name: callWasmHostName, Type: wasm.ExternTypeFunc, Index: 4},
+			{Name: callGoHostName, Type: wasm.ExternTypeFunc, Index: 3},
+			{Name: callGoReflectHostName, Type: wasm.ExternTypeFunc, Index: 4},
+			{Name: callWasmHostName, Type: wasm.ExternTypeFunc, Index: 5},
 		},
 		CodeSection: []*wasm.Code{
 			{Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeCall, 0, wasm.OpcodeEnd}}, // Calling the index 0 = host.go.
@@ -228,11 +229,10 @@ func setupHostCallBench(requireNoError func(error)) *wasm.ModuleInstance {
 	requireNoError(err)
 
 	importing := &wasm.ModuleInstance{TypeIDs: []wasm.FunctionTypeID{0}}
-	importingFunctions := importing.BuildFunctions(importingModule)
-	importing.Functions = append([]*wasm.FunctionInstance{goFn, wasnFn}, importingFunctions...)
+	importingFunctions := importing.BuildFunctions(importingModule, []*wasm.FunctionInstance{goFn, goReflectFn, wasnFn})
 	importing.BuildExports(importingModule.ExportSection)
 
-	importingMe, err := eng.NewModuleEngine(importing.Name, importingModule, []*wasm.FunctionInstance{goFn, goReflectFn, wasnFn}, importingFunctions)
+	importingMe, err := eng.NewModuleEngine(importing.Name, importingModule, importingFunctions)
 	requireNoError(err)
 	linkModuleToEngine(importing, importingMe)
 
diff --git a/internal/testing/enginetest/enginetest.go b/internal/testing/enginetest/enginetest.go
index c7d8dde2..22956896 100644
--- a/internal/testing/enginetest/enginetest.go
+++ b/internal/testing/enginetest/enginetest.go
@@ -59,7 +59,7 @@ func RunTestEngine_NewModuleEngine(t *testing.T, et EngineTester) {
 	e := et.NewEngine(api.CoreFeaturesV1)
 
 	t.Run("error before instantiation", func(t *testing.T) {
-		_, err := e.NewModuleEngine("mymod", &wasm.Module{}, nil, nil)
+		_, err := e.NewModuleEngine("mymod", &wasm.Module{}, nil)
 		require.EqualError(t, err, "source module for mymod must be compiled before instantiation")
 	})
 
@@ -67,7 +67,7 @@ func RunTestEngine_NewModuleEngine(t *testing.T, et EngineTester) {
 		m := &wasm.Module{}
 		err := e.CompileModule(testCtx, m, nil)
 		require.NoError(t, err)
-		me, err := e.NewModuleEngine(t.Name(), m, nil, nil)
+		me, err := e.NewModuleEngine(t.Name(), m, nil)
 		require.NoError(t, err)
 		require.Equal(t, t.Name(), me.Name())
 	})
@@ -100,15 +100,15 @@ func RunTestModuleEngine_Call(t *testing.T, et EngineTester) {
 
 	// To use the function, we first need to add it to a module.
 	module := &wasm.ModuleInstance{Name: t.Name(), TypeIDs: []wasm.FunctionTypeID{0}}
-	module.Functions = module.BuildFunctions(m)
+	module.Functions = module.BuildFunctions(m, nil)
 
 	// Compile the module
-	me, err := e.NewModuleEngine(module.Name, m, nil, module.Functions)
+	me, err := e.NewModuleEngine(module.Name, m, module.Functions)
 	require.NoError(t, err)
 	linkModuleToEngine(module, me)
 
 	// Ensure the base case doesn't fail: A single parameter should work as that matches the function signature.
-	fn := module.Functions[0]
+	fn := &module.Functions[0]
 
 	ce, err := me.NewCallEngine(module.CallCtx, fn)
 	require.NoError(t, err)
@@ -156,9 +156,9 @@ func RunTestModuleEngine_LookupFunction(t *testing.T, et EngineTester) {
 		{Min: 2, References: make([]wasm.Reference, 2), Type: wasm.RefTypeExternref},
 		{Min: 10, References: make([]wasm.Reference, 10), Type: wasm.RefTypeFuncref},
 	}
-	m.Functions = m.BuildFunctions(mod)
+	m.Functions = m.BuildFunctions(mod, nil)
 
-	me, err := e.NewModuleEngine(m.Name, mod, nil, m.Functions)
+	me, err := e.NewModuleEngine(m.Name, mod, m.Functions)
 	require.NoError(t, err)
 	linkModuleToEngine(m, me)
 
@@ -225,12 +225,12 @@ func runTestModuleEngine_Call_HostFn_Mem(t *testing.T, et EngineTester, readMem
 	}{
 		{
 			name:     callImportReadMemName,
-			fn:       importing.Exports[callImportReadMemName].Function,
+			fn:       &importing.Functions[importing.Exports[callImportReadMemName].Index],
 			expected: importingMemoryVal,
 		},
 		{
 			name:     callImportCallReadMemName,
-			fn:       importing.Exports[callImportCallReadMemName].Function,
+			fn:       &importing.Functions[importing.Exports[callImportCallReadMemName].Index],
 			expected: importingMemoryVal,
 		},
 	}
@@ -274,17 +274,17 @@ func runTestModuleEngine_Call_HostFn(t *testing.T, et EngineTester, hostDivBy *w
 		{
 			name:   divByWasmName,
 			module: imported.CallCtx,
-			fn:     imported.Exports[divByWasmName].Function,
+			fn:     &imported.Functions[imported.Exports[divByWasmName].Index],
 		},
 		{
 			name:   callDivByGoName,
 			module: imported.CallCtx,
-			fn:     imported.Exports[callDivByGoName].Function,
+			fn:     &imported.Functions[imported.Exports[callDivByGoName].Index],
 		},
 		{
 			name:   callImportCallDivByGoName,
 			module: importing.CallCtx,
-			fn:     importing.Exports[callImportCallDivByGoName].Function,
+			fn:     &importing.Functions[importing.Exports[callImportCallDivByGoName].Index],
 		},
 	}
 	for _, tt := range tests {
@@ -329,21 +329,21 @@ func RunTestModuleEngine_Call_Errors(t *testing.T, et EngineTester) {
 			name:        "wasm function not enough parameters",
 			input:       []uint64{},
 			module:      imported.CallCtx,
-			fn:          imported.Exports[divByWasmName].Function,
+			fn:          &imported.Functions[imported.Exports[divByWasmName].Index],
 			expectedErr: `expected 1 params, but passed 0`,
 		},
 		{
 			name:        "wasm function too many parameters",
 			input:       []uint64{1, 2},
 			module:      imported.CallCtx,
-			fn:          imported.Exports[divByWasmName].Function,
+			fn:          &imported.Functions[imported.Exports[divByWasmName].Index],
 			expectedErr: `expected 1 params, but passed 2`,
 		},
 		{
 			name:   "wasm function panics with wasmruntime.Error",
 			input:  []uint64{0},
 			module: imported.CallCtx,
-			fn:     imported.Exports[divByWasmName].Function,
+			fn:     &imported.Functions[imported.Exports[divByWasmName].Index],
 			expectedErr: `wasm error: integer divide by zero
 wasm stack trace:
 	imported.div_by.wasm(i32) i32`,
@@ -352,7 +352,7 @@ wasm stack trace:
 			name:   "wasm calls host function that panics",
 			input:  []uint64{math.MaxUint32},
 			module: imported.CallCtx,
-			fn:     imported.Exports[callDivByGoName].Function,
+			fn:     &imported.Functions[imported.Exports[callDivByGoName].Index],
 			expectedErr: `host-function panic (recovered by wazero)
 wasm stack trace:
 	host.div_by.go(i32) i32
@@ -362,7 +362,7 @@ wasm stack trace:
 			name:   "wasm calls imported wasm that calls host function panics with runtime.Error",
 			input:  []uint64{0},
 			module: importing.CallCtx,
-			fn:     importing.Exports[callImportCallDivByGoName].Function,
+			fn:     &importing.Functions[importing.Exports[callImportCallDivByGoName].Index],
 			expectedErr: `runtime error: integer divide by zero (recovered by wazero)
 wasm stack trace:
 	host.div_by.go(i32) i32
@@ -373,7 +373,7 @@ wasm stack trace:
 			name:   "wasm calls imported wasm that calls host function that panics",
 			input:  []uint64{math.MaxUint32},
 			module: importing.CallCtx,
-			fn:     importing.Exports[callImportCallDivByGoName].Function,
+			fn:     &importing.Functions[importing.Exports[callImportCallDivByGoName].Index],
 			expectedErr: `host-function panic (recovered by wazero)
 wasm stack trace:
 	host.div_by.go(i32) i32
@@ -384,7 +384,7 @@ wasm stack trace:
 			name:   "wasm calls imported wasm calls host function panics with runtime.Error",
 			input:  []uint64{0},
 			module: importing.CallCtx,
-			fn:     importing.Exports[callImportCallDivByGoName].Function,
+			fn:     &importing.Functions[importing.Exports[callImportCallDivByGoName].Index],
 			expectedErr: `runtime error: integer divide by zero (recovered by wazero)
 wasm stack trace:
 	host.div_by.go(i32) i32
@@ -474,12 +474,12 @@ func RunTestModuleEngine_Memory(t *testing.T, et EngineTester) {
 	var memory api.Memory = module.Memory
 
 	// To use functions, we need to instantiate them (associate them with a ModuleInstance).
-	module.Functions = module.BuildFunctions(m)
+	module.Functions = module.BuildFunctions(m, nil)
 	module.BuildExports(m.ExportSection)
-	grow, init := module.Functions[0], module.Functions[1]
+	grow, init := &module.Functions[0], &module.Functions[1]
 
 	// Compile the module
-	me, err := e.NewModuleEngine(module.Name, m, nil, module.Functions)
+	me, err := e.NewModuleEngine(module.Name, m, module.Functions)
 	require.NoError(t, err)
 	linkModuleToEngine(module, me)
 
@@ -606,11 +606,11 @@ func setupCallTests(t *testing.T, e wasm.Engine, divBy *wasm.Code, fnlf experime
 	err := e.CompileModule(testCtx, hostModule, lns)
 	require.NoError(t, err)
 	host := &wasm.ModuleInstance{Name: hostModule.NameSection.ModuleName, TypeIDs: []wasm.FunctionTypeID{0}}
-	host.Functions = host.BuildFunctions(hostModule)
+	host.Functions = host.BuildFunctions(hostModule, nil)
 	host.BuildExports(hostModule.ExportSection)
-	hostFn := host.Exports[divByGoName].Function
+	hostFn := &host.Functions[host.Exports[divByGoName].Index]
 
-	hostME, err := e.NewModuleEngine(host.Name, hostModule, nil, host.Functions)
+	hostME, err := e.NewModuleEngine(host.Name, hostModule, host.Functions)
 	require.NoError(t, err)
 	linkModuleToEngine(host, hostME)
 
@@ -642,13 +642,13 @@ func setupCallTests(t *testing.T, e wasm.Engine, divBy *wasm.Code, fnlf experime
 	require.NoError(t, err)
 
 	imported := &wasm.ModuleInstance{Name: importedModule.NameSection.ModuleName, TypeIDs: []wasm.FunctionTypeID{0}}
-	importedFunctions := imported.BuildFunctions(importedModule)
-	imported.Functions = append([]*wasm.FunctionInstance{hostFn}, importedFunctions...)
+	importedFunctions := imported.BuildFunctions(importedModule, []*wasm.FunctionInstance{hostFn})
+	imported.Functions = importedFunctions
 	imported.BuildExports(importedModule.ExportSection)
-	callHostFn := imported.Exports[callDivByGoName].Function
+	callHostFn := &imported.Functions[imported.Exports[callDivByGoName].Index]
 
 	// Compile the imported module
-	importedMe, err := e.NewModuleEngine(imported.Name, importedModule, []*wasm.FunctionInstance{hostFn}, importedFunctions)
+	importedMe, err := e.NewModuleEngine(imported.Name, importedModule, importedFunctions)
 	require.NoError(t, err)
 	linkModuleToEngine(imported, importedMe)
 
@@ -676,12 +676,12 @@ func setupCallTests(t *testing.T, e wasm.Engine, divBy *wasm.Code, fnlf experime
 
 	// Add the exported function.
 	importing := &wasm.ModuleInstance{Name: importingModule.NameSection.ModuleName, TypeIDs: []wasm.FunctionTypeID{0}}
-	importingFunctions := importing.BuildFunctions(importingModule)
-	importing.Functions = append([]*wasm.FunctionInstance{callHostFn}, importingFunctions...)
+	importingFunctions := importing.BuildFunctions(importingModule, []*wasm.FunctionInstance{callHostFn})
+	importing.Functions = importingFunctions
 	importing.BuildExports(importingModule.ExportSection)
 
 	// Compile the importing module
-	importingMe, err := e.NewModuleEngine(importing.Name, importingModule, []*wasm.FunctionInstance{callHostFn}, importingFunctions)
+	importingMe, err := e.NewModuleEngine(importing.Name, importingModule, importingFunctions)
 	require.NoError(t, err)
 	linkModuleToEngine(importing, importingMe)
 
@@ -724,12 +724,12 @@ func setupCallMemTests(t *testing.T, e wasm.Engine, readMem *wasm.Code, fnlf exp
 	err := e.CompileModule(testCtx, hostModule, nil)
 	require.NoError(t, err)
 	host := &wasm.ModuleInstance{Name: hostModule.NameSection.ModuleName, TypeIDs: []wasm.FunctionTypeID{0}}
-	host.Functions = host.BuildFunctions(hostModule)
+	host.Functions = host.BuildFunctions(hostModule, nil)
 	host.BuildExports(hostModule.ExportSection)
-	readMemFn := host.Exports[readMemName].Function
-	callReadMemFn := host.Exports[callReadMemName].Function
+	readMemFn := &host.Functions[host.Exports[readMemName].Index]
+	callReadMemFn := &host.Functions[host.Exports[callReadMemName].Index]
 
-	hostME, err := e.NewModuleEngine(host.Name, hostModule, nil, host.Functions)
+	hostME, err := e.NewModuleEngine(host.Name, hostModule, host.Functions)
 	require.NoError(t, err)
 	linkModuleToEngine(host, hostME)
 
@@ -766,13 +766,13 @@ func setupCallMemTests(t *testing.T, e wasm.Engine, readMem *wasm.Code, fnlf exp
 
 	// Add the exported function.
 	importing := &wasm.ModuleInstance{Name: importingModule.NameSection.ModuleName, TypeIDs: []wasm.FunctionTypeID{0}}
-	importingFunctions := importing.BuildFunctions(importingModule)
+	importingFunctions := importing.BuildFunctions(importingModule, []*wasm.FunctionInstance{readMemFn, callReadMemFn})
 	// Note: adds imported functions readMemFn and callReadMemFn at index 0 and 1.
-	importing.Functions = append([]*wasm.FunctionInstance{callReadMemFn, readMemFn}, importingFunctions...)
+	importing.Functions = importingFunctions
 	importing.BuildExports(importingModule.ExportSection)
 
 	// Compile the importing module
-	importingMe, err := e.NewModuleEngine(importing.Name, importingModule, []*wasm.FunctionInstance{readMemFn, callReadMemFn}, importingFunctions)
+	importingMe, err := e.NewModuleEngine(importing.Name, importingModule, importingFunctions)
 	require.NoError(t, err)
 	linkModuleToEngine(importing, importingMe)
 
diff --git a/internal/wasm/module_test.go b/internal/wasm/module_test.go
index 1ab2d8b2..6a2b7830 100644
--- a/internal/wasm/module_test.go
+++ b/internal/wasm/module_test.go
@@ -827,9 +827,9 @@ func TestModule_buildFunctions(t *testing.T) {
 
 	// Note: This only returns module-defined functions, not imported ones. That's why the index starts with 1, not 0.
 	instance := &ModuleInstance{Name: "counter", TypeIDs: []FunctionTypeID{0}}
-	instance.BuildFunctions(m)
-	for i, f := range instance.Functions {
-		require.Equal(t, i, f.Definition.Index())
+	instance.BuildFunctions(m, nil)
+	for i, f := range instance.Functions[1:] {
+		require.Equal(t, uint32(i+1), f.Definition.Index())
 		require.Equal(t, nopCode.Body, f.Body)
 	}
 }
diff --git a/internal/wasm/store_test.go b/internal/wasm/store_test.go
index 6043a343..dfa4ef97 100644
--- a/internal/wasm/store_test.go
+++ b/internal/wasm/store_test.go
@@ -380,7 +380,7 @@ func (e *mockEngine) CompiledModuleCount() uint32 { return 0 }
 func (e *mockEngine) DeleteCompiledModule(*Module) {}
 
 // NewModuleEngine implements the same method as documented on wasm.Engine.
-func (e *mockEngine) NewModuleEngine(_ string, _ *Module, _, _ []*FunctionInstance) (ModuleEngine, error) {
+func (e *mockEngine) NewModuleEngine(_ string, _ *Module, _ []FunctionInstance) (ModuleEngine, error) {
 	if e.shouldCompileFail {
 		return nil, fmt.Errorf("some engine creation error")
 	}
@@ -630,27 +630,26 @@ func Test_resolveImports(t *testing.T) {
 	})
 	t.Run("export instance not found", func(t *testing.T) {
 		modules := map[string]*ModuleInstance{
-			moduleName: {Exports: map[string]*ExportInstance{}, Name: moduleName},
+			moduleName: {Exports: map[string]ExportInstance{}, Name: moduleName},
 		}
 		_, _, _, _, err := resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: "unknown"}}}, modules)
 		require.EqualError(t, err, "\"unknown\" is not exported in module \"test\"")
 	})
 	t.Run("func", func(t *testing.T) {
 		t.Run("ok", func(t *testing.T) {
-			f := &FunctionInstance{
-				Definition: &FunctionDefinition{funcType: &FunctionType{Results: []ValueType{ValueTypeF32}}},
-			}
-			g := &FunctionInstance{
-				Definition: &FunctionDefinition{funcType: &FunctionType{Results: []ValueType{ValueTypeI32}}},
+			externMod := &ModuleInstance{
+				Functions: []FunctionInstance{
+					{Definition: &FunctionDefinition{funcType: &FunctionType{Results: []ValueType{ValueTypeF32}}}},
+					{Definition: &FunctionDefinition{funcType: &FunctionType{Results: []ValueType{ValueTypeI32}}}},
+				},
+				Exports: map[string]ExportInstance{
+					name: {Type: ExternTypeFunc, Index: 0},
+					"":   {Type: ExternTypeFunc, Index: 1},
+				},
+				Name: moduleName,
 			}
 			modules := map[string]*ModuleInstance{
-				moduleName: {
-					Exports: map[string]*ExportInstance{
-						name: {Function: f},
-						"":   {Function: g},
-					},
-					Name: moduleName,
-				},
+				moduleName: externMod,
 			}
 			m := &Module{
 				TypeSection: []*FunctionType{{Results: []ValueType{ValueTypeF32}}, {Results: []ValueType{ValueTypeI32}}},
@@ -661,22 +660,25 @@ func Test_resolveImports(t *testing.T) {
 			}
 			functions, _, _, _, err := resolveImports(m, modules)
 			require.NoError(t, err)
-			require.True(t, functionsContain(functions, f), "expected to find %v in %v", f, functions)
-			require.True(t, functionsContain(functions, g), "expected to find %v in %v", g, functions)
+			require.True(t, functionsContain(functions, &externMod.Functions[0]), "expected to find %v in %v", &externMod.Functions[0], functions)
+			require.True(t, functionsContain(functions, &externMod.Functions[1]), "expected to find %v in %v", &externMod.Functions[1], functions)
 		})
 		t.Run("type out of range", func(t *testing.T) {
 			modules := map[string]*ModuleInstance{
-				moduleName: {Exports: map[string]*ExportInstance{name: {}}, Name: moduleName},
+				moduleName: {Exports: map[string]ExportInstance{name: {}}, Name: moduleName},
 			}
 			_, _, _, _, err := resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeFunc, DescFunc: 100}}}, modules)
 			require.EqualError(t, err, "import[0] func[test.target]: function type out of range")
 		})
 		t.Run("signature mismatch", func(t *testing.T) {
-			modules := map[string]*ModuleInstance{
-				moduleName: {Exports: map[string]*ExportInstance{name: {
-					Function: &FunctionInstance{Definition: &FunctionDefinition{funcType: &FunctionType{}}},
-				}}, Name: moduleName},
+			externMod := &ModuleInstance{
+				Functions: []FunctionInstance{{Definition: &FunctionDefinition{funcType: &FunctionType{}}}},
+				Exports: map[string]ExportInstance{
+					name: {Type: ExternTypeFunc, Index: 0},
+				},
+				Name: moduleName,
 			}
+			modules := map[string]*ModuleInstance{moduleName: externMod}
 			m := &Module{
 				TypeSection:   []*FunctionType{{Results: []ValueType{ValueTypeF32}}},
 				ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeFunc, DescFunc: 0}},
@@ -689,7 +691,10 @@ func Test_resolveImports(t *testing.T) {
 		t.Run("ok", func(t *testing.T) {
 			g := &GlobalInstance{Type: &GlobalType{ValType: ValueTypeI32}}
 			modules := map[string]*ModuleInstance{
-				moduleName: {Exports: map[string]*ExportInstance{name: {Type: ExternTypeGlobal, Global: g}}, Name: moduleName},
+				moduleName: {
+					Globals: []*GlobalInstance{g},
+					Exports: map[string]ExportInstance{name: {Type: ExternTypeGlobal, Index: 0}}, Name: moduleName,
+				},
 			}
 			_, globals, _, _, err := resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeGlobal, DescGlobal: g.Type}}}, modules)
 			require.NoError(t, err)
@@ -697,20 +702,28 @@ func Test_resolveImports(t *testing.T) {
 		})
 		t.Run("mutability mismatch", func(t *testing.T) {
 			modules := map[string]*ModuleInstance{
-				moduleName: {Exports: map[string]*ExportInstance{name: {
-					Type:   ExternTypeGlobal,
-					Global: &GlobalInstance{Type: &GlobalType{Mutable: false}},
-				}}, Name: moduleName},
+				moduleName: {
+					Globals: []*GlobalInstance{{Type: &GlobalType{Mutable: false}}},
+					Exports: map[string]ExportInstance{name: {
+						Type:  ExternTypeGlobal,
+						Index: 0,
+					}},
+					Name: moduleName,
+				},
 			}
 			_, _, _, _, err := resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeGlobal, DescGlobal: &GlobalType{Mutable: true}}}}, modules)
 			require.EqualError(t, err, "import[0] global[test.target]: mutability mismatch: true != false")
 		})
 		t.Run("type mismatch", func(t *testing.T) {
 			modules := map[string]*ModuleInstance{
-				moduleName: {Exports: map[string]*ExportInstance{name: {
-					Type:   ExternTypeGlobal,
-					Global: &GlobalInstance{Type: &GlobalType{ValType: ValueTypeI32}},
-				}}, Name: moduleName},
+				moduleName: {
+					Globals: []*GlobalInstance{{Type: &GlobalType{ValType: ValueTypeI32}}},
+					Exports: map[string]ExportInstance{name: {
+						Type:  ExternTypeGlobal,
+						Index: 0,
+					}},
+					Name: moduleName,
+				},
 			}
 			_, _, _, _, err := resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeGlobal, DescGlobal: &GlobalType{ValType: ValueTypeF64}}}}, modules)
 			require.EqualError(t, err, "import[0] global[test.target]: value type mismatch: f64 != i32")
@@ -721,10 +734,13 @@ func Test_resolveImports(t *testing.T) {
 			max := uint32(10)
 			memoryInst := &MemoryInstance{Max: max}
 			modules := map[string]*ModuleInstance{
-				moduleName: {Exports: map[string]*ExportInstance{name: {
-					Type:   ExternTypeMemory,
+				moduleName: {
 					Memory: memoryInst,
-				}}, Name: moduleName},
+					Exports: map[string]ExportInstance{name: {
+						Type: ExternTypeMemory,
+					}},
+					Name: moduleName,
+				},
 			}
 			_, _, _, memory, err := resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeMemory, DescMem: &Memory{Max: max}}}}, modules)
 			require.NoError(t, err)
@@ -733,10 +749,13 @@ func Test_resolveImports(t *testing.T) {
 		t.Run("minimum size mismatch", func(t *testing.T) {
 			importMemoryType := &Memory{Min: 2, Cap: 2}
 			modules := map[string]*ModuleInstance{
-				moduleName: {Exports: map[string]*ExportInstance{name: {
-					Type:   ExternTypeMemory,
+				moduleName: {
 					Memory: &MemoryInstance{Min: importMemoryType.Min - 1, Cap: 2},
-				}}, Name: moduleName},
+					Exports: map[string]ExportInstance{name: {
+						Type: ExternTypeMemory,
+					}},
+					Name: moduleName,
+				},
 			}
 			_, _, _, _, err := resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeMemory, DescMem: importMemoryType}}}, modules)
 			require.EqualError(t, err, "import[0] memory[test.target]: minimum size mismatch: 2 > 1")
@@ -745,10 +764,13 @@ func Test_resolveImports(t *testing.T) {
 			max := uint32(10)
 			importMemoryType := &Memory{Max: max}
 			modules := map[string]*ModuleInstance{
-				moduleName: {Exports: map[string]*ExportInstance{name: {
-					Type:   ExternTypeMemory,
+				moduleName: {
 					Memory: &MemoryInstance{Max: MemoryLimitPages},
-				}}, Name: moduleName},
+					Exports: map[string]ExportInstance{name: {
+						Type: ExternTypeMemory,
+					}},
+					Name: moduleName,
+				},
 			}
 			_, _, _, _, err := resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeMemory, DescMem: importMemoryType}}}, modules)
 			require.EqualError(t, err, "import[0] memory[test.target]: maximum size mismatch: 10 < 65536")
@@ -862,7 +884,7 @@ func TestModuleInstance_applyTableInits(t *testing.T) {
 	})
 	t.Run("funcref", func(t *testing.T) {
 		e := &mockEngine{}
-		me, err := e.NewModuleEngine("", nil, nil, nil)
+		me, err := e.NewModuleEngine("", nil, nil)
 		me.(*mockModuleEngine).functionRefs = map[Index]Reference{0: 0xa, 1: 0xaa, 2: 0xaaa, 3: 0xaaaa}
 		require.NoError(t, err)
 		m := &ModuleInstance{Engine: me}
diff --git a/internal/wasm/table_test.go b/internal/wasm/table_test.go
index 72409522..20b68299 100644
--- a/internal/wasm/table_test.go
+++ b/internal/wasm/table_test.go
@@ -21,10 +21,11 @@ func Test_resolveImports_table(t *testing.T) {
 		max := uint32(10)
 		tableInst := &TableInstance{Max: &max}
 		modules := map[string]*ModuleInstance{
-			moduleName: {Exports: map[string]*ExportInstance{name: {
-				Type:  ExternTypeTable,
-				Table: tableInst,
-			}}, Name: moduleName},
+			moduleName: {
+				Tables:  []*TableInstance{tableInst},
+				Exports: map[string]ExportInstance{name: {Type: ExternTypeTable, Index: 0}},
+				Name:    moduleName,
+			},
 		}
 		_, _, tables, _, err := resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeTable, DescTable: &Table{Max: &max}}}}, modules)
 		require.NoError(t, err)
@@ -34,10 +35,11 @@ func Test_resolveImports_table(t *testing.T) {
 	t.Run("minimum size mismatch", func(t *testing.T) {
 		importTableType := &Table{Min: 2}
 		modules := map[string]*ModuleInstance{
-			moduleName: {Exports: map[string]*ExportInstance{name: {
-				Type:  ExternTypeTable,
-				Table: &TableInstance{Min: importTableType.Min - 1},
-			}}, Name: moduleName},
+			moduleName: {
+				Tables:  []*TableInstance{{Min: importTableType.Min - 1}},
+				Exports: map[string]ExportInstance{name: {Type: ExternTypeTable}},
+				Name:    moduleName,
+			},
 		}
 		_, _, _, _, err := resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeTable, DescTable: importTableType}}}, modules)
 		require.EqualError(t, err, "import[0] table[test.target]: minimum size mismatch: 2 > 1")
@@ -46,10 +48,11 @@ func Test_resolveImports_table(t *testing.T) {
 		max := uint32(10)
 		importTableType := &Table{Max: &max}
 		modules := map[string]*ModuleInstance{
-			moduleName: {Exports: map[string]*ExportInstance{name: {
-				Type:  ExternTypeTable,
-				Table: &TableInstance{Min: importTableType.Min - 1},
-			}}, Name: moduleName},
+			moduleName: {
+				Tables:  []*TableInstance{{Min: importTableType.Min - 1}},
+				Exports: map[string]ExportInstance{name: {Type: ExternTypeTable}},
+				Name:    moduleName,
+			},
 		}
 		_, _, _, _, err := resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeTable, DescTable: importTableType}}}, modules)
 		require.EqualError(t, err, "import[0] table[test.target]: maximum size mismatch: 10, but actual has no max")
diff --git a/runtime_test.go b/runtime_test.go
index 76a401bd..bea08e80 100644
--- a/runtime_test.go
+++ b/runtime_test.go
@@ -664,6 +664,6 @@ func (e *mockEngine) DeleteCompiledModule(module *wasm.Module) {
 }
 
 // NewModuleEngine implements the same method as documented on wasm.Engine.
-func (e *mockEngine) NewModuleEngine(_ string, _ *wasm.Module, _, _ []*wasm.FunctionInstance) (wasm.ModuleEngine, error) {
+func (e *mockEngine) NewModuleEngine(_ string, _ *wasm.Module, _ []wasm.FunctionInstance) (wasm.ModuleEngine, error) {
 	return nil, nil
 }