Repository: tetratelabs/wazero
Pull request #1419 (2023-05-01 02:18:17)

Reduces FunctionDefinition usages
This refactors interpreter/compiler around the FunctionDefinition usage,
and reduces the usage and memory footprint during installation.

```
goos: darwin
goarch: arm64
pkg: github.com/tetratelabs/wazero/internal/integration_test/bench
                                       │   old.txt    │              new.txt              │
                                       │    sec/op    │   sec/op     vs base              │
Initialization/interpreter-10            14.08µ ±  1%   14.09µ ± 1%       ~ (p=0.832 n=7)
Initialization/interpreter-multiple-10   17.28µ ±  3%   16.58µ ± 3%  -4.03% (p=0.004 n=7)
Initialization/compiler-10               15.95µ ± 11%   15.48µ ± 6%       ~ (p=0.073 n=7)
Initialization/compiler-multiple-10      15.21µ ±  6%   14.67µ ± 6%       ~ (p=0.053 n=7)
Compilation/with_extern_cache-10         114.5µ ±  1%   114.4µ ± 1%       ~ (p=0.902 n=7)
Compilation/without_extern_cache-10      1.274m ±  1%   1.275m ± 1%       ~ (p=0.318 n=7)
Compilation/interpreter-10               432.0µ ±  1%   433.8µ ± 1%       ~ (p=0.318 n=7)
Compilation_sqlite3/compiler-10          177.2m ±  1%   177.3m ± 1%       ~ (p=0.902 n=7)
Compilation_sqlite3/interpreter-10       60.77m ±  0%   60.55m ± 1%       ~ (p=0.456 n=7)
geomean                                  324.6µ         320.9µ       -1.17%

                                       │   old.txt    │               new.txt                │
                                       │     B/op     │     B/op      vs base                │
Initialization/interpreter-10            132.3Ki ± 0%   132.3Ki ± 0%       ~ (p=1.000 n=7) ¹
Initialization/interpreter-multiple-10   132.4Ki ± 0%   132.4Ki ± 0%       ~ (p=1.000 n=7)
Initialization/compiler-10               132.3Ki ± 0%   131.5Ki ± 0%  -0.66% (p=0.001 n=7)
Initialization/compiler-multiple-10      132.3Ki ± 0%   131.5Ki ± 0%  -0.66% (p=0.001 n=7)
Compilation/with_extern_cache-10         36.92Ki ± 0%   36.92Ki ± 0%       ~ (p=0.596 n=7)
Compilation/without_extern_cache-10      661.9Ki ± 0%   661.9Ki ± 0%       ~ (p=1.000 n=7)
Compilation/interpreter-10               487.6Ki ± 0%   487.6Ki ± 0%       ~ (p=0.833 n=7)
Compilation_sqlite3/compiler-10          54.32Mi ± 0%   54.32Mi ± 0%  -0.01% (p=0.001 n=7)
Compilation_sqlite3/interpreter-10       51.75Mi ± 0%   51.75Mi ± 0%       ~ (p=1.000 n=7)
geomean                                  604.5Ki        603.6Ki       -0.15%
¹ all samples are equal

                                       │   old.txt   │               new.txt               │
                                       │  allocs/op  │  allocs/op   vs base                │
Initialization/interpreter-10             23.00 ± 0%    23.00 ± 0%       ~ (p=1.000 n=7) ¹
Initialization/interpreter-multiple-10    23.00 ± 0%    23.00 ± 0%       ~ (p=1.000 n=7) ¹
Initialization/compiler-10                23.00 ± 0%    23.00 ± 0%       ~ (p=1.000 n=7) ¹
Initialization/compiler-multiple-10       23.00 ± 0%    23.00 ± 0%       ~ (p=1.000 n=7) ¹
Compilation/with_extern_cache-10          351.0 ± 0%    351.0 ± 0%       ~ (p=1.000 n=7) ¹
Compilation/without_extern_cache-10       821.0 ± 0%    821.0 ± 0%       ~ (p=1.000 n=7) ¹
Compilation/interpreter-10                489.0 ± 0%    489.0 ± 0%       ~ (p=1.000 n=7) ¹
Compilation_sqlite3/compiler-10          26.06k ± 0%   26.05k ± 0%  -0.02% (p=0.019 n=7)
Compilation_sqlite3/interpreter-10       13.90k ± 0%   13.90k ± 0%       ~ (p=0.884 n=7)
geomean                                   289.5         289.5       -0.00%
¹ all samples are equal

```


Patch:
diff --git a/internal/emscripten/emscripten.go b/internal/emscripten/emscripten.go
index 6cda874c..bfe10f2c 100644
--- a/internal/emscripten/emscripten.go
+++ b/internal/emscripten/emscripten.go
@@ -84,12 +84,12 @@ func (v *InvokeFunc) Call(ctx context.Context, mod api.Module, stack []uint64) {
 
 	// Lookup the table index we will call.
 	t := m.Tables[0] // Note: Emscripten doesn't use multiple tables
-	idx, err := m.Engine.LookupFunction(t, typeID, tableOffset)
+	f, err := m.Engine.LookupFunction(t, typeID, tableOffset)
 	if err != nil {
 		panic(err)
 	}
 
-	err = m.Engine.NewFunction(idx).CallWithStack(ctx, stack)
+	err = f.CallWithStack(ctx, stack)
 	if err != nil {
 		panic(err)
 	}
diff --git a/internal/engine/compiler/engine.go b/internal/engine/compiler/engine.go
index b6f1104a..d44ed5e0 100644
--- a/internal/engine/compiler/engine.go
+++ b/internal/engine/compiler/engine.go
@@ -272,12 +272,8 @@ type (
 		moduleInstance *wasm.ModuleInstance
 		// typeID is the corresponding wasm.FunctionTypeID for funcType.
 		typeID wasm.FunctionTypeID
-		// index is the function Index in this module.
-		index wasm.Index
 		// funcType is the function type for this function. Created during compilation.
 		funcType *wasm.FunctionType
-		// def is the api.Function for this function. Created during compilation.
-		def api.FunctionDefinition
 		// parent holds code from which this is created.
 		parent *compiledFunction
 	}
@@ -298,6 +294,7 @@ type (
 		// stackPointerCeil is the max of the stack pointer this function can reach. Lazily applied via maybeGrowStack.
 		stackPointerCeil uint64
 
+		index           wasm.Index
 		goFunc          interface{}
 		listener        experimental.FunctionListener
 		parent          *compiledModule
@@ -353,7 +350,7 @@ const (
 	functionCodeInitialAddressOffset = 0
 	functionModuleInstanceOffset     = 8
 	functionTypeIDOffset             = 16
-	functionSize                     = 56
+	functionSize                     = 40
 
 	// Offsets for wasm.ModuleInstance.
 	moduleInstanceGlobalsOffset          = 32
@@ -574,6 +571,7 @@ func (e *engine) CompileModule(_ context.Context, module *wasm.Module, listeners
 		bodies[i] = bodyCopied
 		compiledFn.listener = lsn
 		compiledFn.parent = cm
+		compiledFn.index = importedFuncs + funcIndex
 	}
 
 	var executableOffset int
@@ -628,10 +626,8 @@ func (e *engine) NewModuleEngine(module *wasm.Module, instance *wasm.ModuleInsta
 		me.functions[offset] = function{
 			codeInitialAddress: uintptr(unsafe.Pointer(&cm.executable[c.executableOffset])),
 			moduleInstance:     instance,
-			index:              wasm.Index(offset),
 			typeID:             instance.TypeIDs[typeIndex],
 			funcType:           &module.TypeSection[typeIndex],
-			def:                &module.FunctionDefinitionSection[offset],
 			parent:             c,
 		}
 	}
@@ -643,8 +639,6 @@ func (e *moduleEngine) ResolveImportedFunction(index, indexInImportedModule wasm
 	imported := importedModuleEngine.(*moduleEngine)
 	// Copies the content from the import target moduleEngine.
 	e.functions[index] = imported.functions[indexInImportedModule]
-	// Update the .index field to the value in this Module.
-	e.functions[index].index = index
 }
 
 // FunctionInstanceReference implements the same method as documented on wasm.ModuleEngine.
@@ -652,20 +646,21 @@ func (e *moduleEngine) FunctionInstanceReference(funcIndex wasm.Index) wasm.Refe
 	return uintptr(unsafe.Pointer(&e.functions[funcIndex]))
 }
 
+// NewFunction implements wasm.ModuleEngine.
 func (e *moduleEngine) NewFunction(index wasm.Index) api.Function {
-	// Note: The input parameters are pre-validated, so a compiled function is only absent on close. Updates to
-	// code on close aren't locked, neither is this read.
-	compiled := &e.functions[index]
+	return e.newFunction(&e.functions[index])
+}
 
+func (e *moduleEngine) newFunction(f *function) api.Function {
 	initStackSize := initialStackSize
-	if initialStackSize < compiled.parent.stackPointerCeil {
-		initStackSize = compiled.parent.stackPointerCeil * 2
+	if initialStackSize < f.parent.stackPointerCeil {
+		initStackSize = f.parent.stackPointerCeil * 2
 	}
-	return e.newCallEngine(initStackSize, compiled)
+	return e.newCallEngine(initStackSize, f)
 }
 
 // LookupFunction implements the same method as documented on wasm.ModuleEngine.
-func (e *moduleEngine) LookupFunction(t *wasm.TableInstance, typeId wasm.FunctionTypeID, tableOffset wasm.Index) (idx wasm.Index, err error) {
+func (e *moduleEngine) LookupFunction(t *wasm.TableInstance, typeId wasm.FunctionTypeID, tableOffset wasm.Index) (f api.Function, err error) {
 	if tableOffset >= uint32(len(t.References)) || t.Type != wasm.RefTypeFuncref {
 		err = wasmruntime.ErrRuntimeInvalidTableAccess
 		return
@@ -681,8 +676,7 @@ func (e *moduleEngine) LookupFunction(t *wasm.TableInstance, typeId wasm.Functio
 		err = wasmruntime.ErrRuntimeIndirectCallTypeMismatch
 		return
 	}
-	idx = tf.index
-
+	f = e.newFunction(tf)
 	return
 }
 
@@ -700,7 +694,12 @@ func functionFromUintptr(ptr uintptr) *function {
 
 // Definition implements the same method as documented on wasm.ModuleEngine.
 func (ce *callEngine) Definition() api.FunctionDefinition {
-	return ce.initialFn.def
+	return ce.initialFn.definition()
+}
+
+func (f *function) definition() api.FunctionDefinition {
+	compiled := f.parent
+	return &compiled.parent.source.FunctionDefinitionSection[compiled.index]
 }
 
 // Call implements the same method as documented on wasm.ModuleEngine.
@@ -830,7 +829,7 @@ func (ce *callEngine) deferredOnCall(recovered interface{}) (err error) {
 		pc := uint64(ce.returnAddress)
 		stackBasePointer := int(ce.stackBasePointerInBytes >> 3)
 		for {
-			def := fn.def
+			def := fn.definition()
 
 			// sourceInfo holds the source code information corresponding to the frame.
 			// It is not empty only when the DWARF is enabled.
@@ -1138,7 +1137,7 @@ func (si *stackIterator) Next() bool {
 
 // FunctionDefinition implements experimental.StackIterator.
 func (si *stackIterator) FunctionDefinition() api.FunctionDefinition {
-	return si.fn.def
+	return si.fn.definition()
 }
 
 // Parameters implements experimental.StackIterator.
@@ -1151,7 +1150,7 @@ func (ce *callEngine) builtinFunctionFunctionListenerBefore(ctx context.Context,
 	ce.stackIterator.reset(ce.stack, fn, base)
 
 	params := ce.stack[base : base+fn.funcType.ParamNumInUint64]
-	listerCtx := fn.parent.listener.Before(ctx, mod, fn.def, params, &ce.stackIterator)
+	listerCtx := fn.parent.listener.Before(ctx, mod, fn.definition(), params, &ce.stackIterator)
 	prevStackTop := ce.contextStack
 	ce.contextStack = &contextStack{self: ctx, prev: prevStackTop}
 
@@ -1161,7 +1160,7 @@ func (ce *callEngine) builtinFunctionFunctionListenerBefore(ctx context.Context,
 
 func (ce *callEngine) builtinFunctionFunctionListenerAfter(ctx context.Context, mod api.Module, fn *function) {
 	base := int(ce.stackBasePointerInBytes >> 3)
-	fn.parent.listener.After(ctx, mod, fn.def, nil, ce.stack[base:base+fn.funcType.ResultNumInUint64])
+	fn.parent.listener.After(ctx, mod, fn.definition(), nil, ce.stack[base:base+fn.funcType.ResultNumInUint64])
 	ce.ctx = ce.contextStack.self
 	ce.contextStack = ce.contextStack.prev
 }
diff --git a/internal/engine/compiler/engine_cache.go b/internal/engine/compiler/engine_cache.go
index 70e56dee..3509324a 100644
--- a/internal/engine/compiler/engine_cache.go
+++ b/internal/engine/compiler/engine_cache.go
@@ -86,7 +86,7 @@ func (e *engine) getCompiledModuleFromCache(module *wasm.Module) (cm *compiledMo
 	// We retrieve *code structures from `cached`.
 	var staleCache bool
 	// Note: cached.Close is ensured to be called in deserializeCodes.
-	cm, staleCache, err = deserializeCompiledModule(e.wazeroVersion, cached)
+	cm, staleCache, err = deserializeCompiledModule(e.wazeroVersion, cached, module)
 	if err != nil {
 		hit = false
 		return
@@ -129,7 +129,7 @@ func serializeCompiledModule(wazeroVersion string, cm *compiledModule) io.Reader
 	return bytes.NewReader(buf.Bytes())
 }
 
-func deserializeCompiledModule(wazeroVersion string, reader io.ReadCloser) (cm *compiledModule, staleCache bool, err error) {
+func deserializeCompiledModule(wazeroVersion string, reader io.ReadCloser, module *wasm.Module) (cm *compiledModule, staleCache bool, err error) {
 	defer reader.Close()
 	cacheHeaderSize := len(wazeroMagic) + 1 /* version size */ + len(wazeroVersion) + 1 /* ensure termination */ + 4 /* number of functions */
 
@@ -160,6 +160,8 @@ func deserializeCompiledModule(wazeroVersion string, reader io.ReadCloser) (cm *
 	functionsNum := binary.LittleEndian.Uint32(header[len(header)-4:])
 	cm = &compiledModule{functions: make([]compiledFunction, functionsNum), ensureTermination: ensureTermination}
 
+	imported := module.ImportFunctionCount
+
 	var eightBytes [8]byte
 	for i := uint32(0); i < functionsNum; i++ {
 		f := &cm.functions[i]
@@ -178,6 +180,7 @@ func deserializeCompiledModule(wazeroVersion string, reader io.ReadCloser) (cm *
 			return
 		}
 		f.executableOffset = int(offset)
+		f.index = imported + i
 	}
 
 	executableLen, err := readUint64(reader, &eightBytes)
diff --git a/internal/engine/interpreter/interpreter.go b/internal/engine/interpreter/interpreter.go
index d10f2571..0f6153f1 100644
--- a/internal/engine/interpreter/interpreter.go
+++ b/internal/engine/interpreter/interpreter.go
@@ -428,7 +428,7 @@ func (e *moduleEngine) NewFunction(index wasm.Index) (ce api.Function) {
 }
 
 // LookupFunction implements the same method as documented on wasm.ModuleEngine.
-func (e *moduleEngine) LookupFunction(t *wasm.TableInstance, typeId wasm.FunctionTypeID, tableOffset wasm.Index) (idx wasm.Index, err error) {
+func (e *moduleEngine) LookupFunction(t *wasm.TableInstance, typeId wasm.FunctionTypeID, tableOffset wasm.Index) (f api.Function, err error) {
 	if tableOffset >= uint32(len(t.References)) {
 		err = wasmruntime.ErrRuntimeInvalidTableAccess
 		return
@@ -444,7 +444,8 @@ func (e *moduleEngine) LookupFunction(t *wasm.TableInstance, typeId wasm.Functio
 		err = wasmruntime.ErrRuntimeIndirectCallTypeMismatch
 		return
 	}
-	idx = tf.index
+
+	f = e.newCallEngine(tf)
 	return
 }
 
diff --git a/internal/wasm/engine.go b/internal/wasm/engine.go
index c11e4f37..0d032432 100644
--- a/internal/wasm/engine.go
+++ b/internal/wasm/engine.go
@@ -45,8 +45,8 @@ type ModuleEngine interface {
 	//	- `importedModuleEngine` is the ModuleEngine for the imported ModuleInstance.
 	ResolveImportedFunction(index, indexInImportedModule Index, importedModuleEngine ModuleEngine)
 
-	// LookupFunction returns the index of the function in the function table.
-	LookupFunction(t *TableInstance, typeId FunctionTypeID, tableOffset Index) (Index, error)
+	// LookupFunction returns the api.Function created from the function in the function table at the given offset.
+	LookupFunction(t *TableInstance, typeId FunctionTypeID, tableOffset Index) (api.Function, error)
 
 	// FunctionInstanceReference returns Reference for the given Index for a FunctionInstance. The returned values are used by
 	// the initialization via ElementSegment.
diff --git a/internal/wasm/function_definition.go b/internal/wasm/function_definition.go
index 24bb895b..51d530a7 100644
--- a/internal/wasm/function_definition.go
+++ b/internal/wasm/function_definition.go
@@ -60,7 +60,7 @@ func (m *Module) BuildFunctionDefinitions() {
 		def := &m.FunctionDefinitionSection[importFuncIdx]
 		def.importDesc = imp
 		def.index = importFuncIdx
-		def.funcType = &m.TypeSection[imp.DescFunc]
+		def.Functype = &m.TypeSection[imp.DescFunc]
 		importFuncIdx++
 	}
 
@@ -69,7 +69,7 @@ func (m *Module) BuildFunctionDefinitions() {
 		idx := importFuncIdx + Index(codeIndex)
 		def := &m.FunctionDefinitionSection[idx]
 		def.index = idx
-		def.funcType = &m.TypeSection[typeIndex]
+		def.Functype = &m.TypeSection[typeIndex]
 		def.goFunc = code.GoFunc
 	}
 
@@ -92,9 +92,9 @@ func (m *Module) BuildFunctionDefinitions() {
 
 		d.moduleName = moduleName
 		d.name = funcName
-		d.debugName = wasmdebug.FuncName(moduleName, funcName, funcIdx)
-		d.paramNames = paramNames(localNames, funcIdx, len(d.funcType.Params))
-		d.resultNames = paramNames(resultNames, funcIdx, len(d.funcType.Results))
+		d.Debugname = wasmdebug.FuncName(moduleName, funcName, funcIdx)
+		d.paramNames = paramNames(localNames, funcIdx, len(d.Functype.Params))
+		d.resultNames = paramNames(resultNames, funcIdx, len(d.Functype.Results))
 
 		for i := range m.ExportSection {
 			e := &m.ExportSection[i]
@@ -108,12 +108,14 @@ func (m *Module) BuildFunctionDefinitions() {
 // FunctionDefinition implements api.FunctionDefinition
 type FunctionDefinition struct {
 	internalapi.WazeroOnlyType
-	moduleName  string
-	index       Index
-	name        string
-	debugName   string
-	goFunc      interface{}
-	funcType    *FunctionType
+	moduleName string
+	index      Index
+	name       string
+	// Debugname is exported for testing purpose.
+	Debugname string
+	goFunc    interface{}
+	// Functype is exported for testing purpose.
+	Functype    *FunctionType
 	importDesc  *Import
 	exportNames []string
 	paramNames  []string
@@ -137,7 +139,7 @@ func (f *FunctionDefinition) Name() string {
 
 // DebugName implements the same method as documented on api.FunctionDefinition.
 func (f *FunctionDefinition) DebugName() string {
-	return f.debugName
+	return f.Debugname
 }
 
 // Import implements the same method as documented on api.FunctionDefinition.
@@ -161,7 +163,7 @@ func (f *FunctionDefinition) GoFunction() interface{} {
 
 // ParamTypes implements api.FunctionDefinition ParamTypes.
 func (f *FunctionDefinition) ParamTypes() []ValueType {
-	return f.funcType.Params
+	return f.Functype.Params
 }
 
 // ParamNames implements the same method as documented on api.FunctionDefinition.
@@ -171,7 +173,7 @@ func (f *FunctionDefinition) ParamNames() []string {
 
 // ResultTypes implements api.FunctionDefinition ResultTypes.
 func (f *FunctionDefinition) ResultTypes() []ValueType {
-	return f.funcType.Results
+	return f.Functype.Results
 }
 
 // ResultNames implements the same method as documented on api.FunctionDefinition.
diff --git a/internal/wasm/store.go b/internal/wasm/store.go
index 2ad16b15..8134c50a 100644
--- a/internal/wasm/store.go
+++ b/internal/wasm/store.go
@@ -397,8 +397,8 @@ func (m *ModuleInstance) resolveImports(module *Module) (err error) {
 			case ExternTypeFunc:
 				expectedType := &module.TypeSection[i.DescFunc]
 				actual := &importedModule.Definitions[imported.Index]
-				if !actual.funcType.EqualsSignature(expectedType.Params, expectedType.Results) {
-					err = errorInvalidImport(i, fmt.Errorf("signature mismatch: %s != %s", expectedType, actual.funcType))
+				if !actual.Functype.EqualsSignature(expectedType.Params, expectedType.Results) {
+					err = errorInvalidImport(i, fmt.Errorf("signature mismatch: %s != %s", expectedType, actual.Functype))
 					return
 				}
 


Test patch:
diff --git a/internal/engine/compiler/engine_cache_test.go b/internal/engine/compiler/engine_cache_test.go
index cd71da41..3286b875 100644
--- a/internal/engine/compiler/engine_cache_test.go
+++ b/internal/engine/compiler/engine_cache_test.go
@@ -107,11 +107,12 @@ func TestSerializeCompiledModule(t *testing.T) {
 
 func TestDeserializeCompiledModule(t *testing.T) {
 	tests := []struct {
-		name              string
-		in                []byte
-		expCompiledModule *compiledModule
-		expStaleCache     bool
-		expErr            string
+		name                  string
+		in                    []byte
+		importedFunctionCount uint32
+		expCompiledModule     *compiledModule
+		expStaleCache         bool
+		expErr                string
 	}{
 		{
 			name:   "invalid header",
@@ -155,7 +156,7 @@ func TestDeserializeCompiledModule(t *testing.T) {
 			expCompiledModule: &compiledModule{
 				executable: []byte{1, 2, 3, 4, 5},
 				functions: []compiledFunction{
-					{executableOffset: 0, stackPointerCeil: 12345},
+					{executableOffset: 0, stackPointerCeil: 12345, index: 0},
 				},
 			},
 			expStaleCache: false,
@@ -177,7 +178,7 @@ func TestDeserializeCompiledModule(t *testing.T) {
 			expCompiledModule: &compiledModule{
 				ensureTermination: true,
 				executable:        []byte{1, 2, 3, 4, 5},
-				functions:         []compiledFunction{{executableOffset: 0, stackPointerCeil: 12345}},
+				functions:         []compiledFunction{{executableOffset: 0, stackPointerCeil: 12345, index: 0}},
 			},
 			expStaleCache: false,
 			expErr:        "",
@@ -200,11 +201,12 @@ func TestDeserializeCompiledModule(t *testing.T) {
 				u64.LeBytes(10),                       // size.
 				[]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, // machine code.
 			),
+			importedFunctionCount: 1,
 			expCompiledModule: &compiledModule{
 				executable: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
 				functions: []compiledFunction{
-					{executableOffset: 0, stackPointerCeil: 12345},
-					{executableOffset: 7, stackPointerCeil: 0xffffffff},
+					{executableOffset: 0, stackPointerCeil: 12345, index: 1},
+					{executableOffset: 7, stackPointerCeil: 0xffffffff, index: 2},
 				},
 			},
 			expStaleCache: false,
@@ -266,7 +268,8 @@ func TestDeserializeCompiledModule(t *testing.T) {
 	for _, tc := range tests {
 		tc := tc
 		t.Run(tc.name, func(t *testing.T) {
-			cm, staleCache, err := deserializeCompiledModule(testVersion, io.NopCloser(bytes.NewReader(tc.in)))
+			cm, staleCache, err := deserializeCompiledModule(testVersion, io.NopCloser(bytes.NewReader(tc.in)),
+				&wasm.Module{ImportFunctionCount: tc.importedFunctionCount})
 
 			if tc.expCompiledModule != nil {
 				require.Equal(t, len(tc.expCompiledModule.functions), len(cm.functions))
@@ -355,8 +358,8 @@ func TestEngine_getCompiledModuleFromCache(t *testing.T) {
 			expCompiledModule: &compiledModule{
 				executable: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
 				functions: []compiledFunction{
-					{stackPointerCeil: 12345, executableOffset: 0},
-					{stackPointerCeil: 0xffffffff, executableOffset: 5},
+					{stackPointerCeil: 12345, executableOffset: 0, index: 0},
+					{stackPointerCeil: 0xffffffff, executableOffset: 5, index: 1},
 				},
 				source:            nil,
 				ensureTermination: false,
diff --git a/internal/engine/compiler/engine_test.go b/internal/engine/compiler/engine_test.go
index e624ebe9..d8dbc041 100644
--- a/internal/engine/compiler/engine_test.go
+++ b/internal/engine/compiler/engine_test.go
@@ -378,20 +378,23 @@ func ptrAsUint64(f *function) uint64 {
 }
 
 func TestCallEngine_deferredOnCall(t *testing.T) {
+	vv := &wasm.FunctionType{}
+	s := &wasm.Module{
+		FunctionDefinitionSection: []wasm.FunctionDefinition{
+			{Debugname: "1", Functype: vv}, {Debugname: "2", Functype: vv}, {Debugname: "3", Functype: vv},
+		},
+	}
 	f1 := &function{
-		def:      newMockFunctionDefinition("1"),
 		funcType: &wasm.FunctionType{ParamNumInUint64: 2},
-		parent:   &compiledFunction{parent: &compiledModule{source: &wasm.Module{}}},
+		parent:   &compiledFunction{parent: &compiledModule{source: s}, index: 0},
 	}
 	f2 := &function{
-		def:      newMockFunctionDefinition("2"),
 		funcType: &wasm.FunctionType{ParamNumInUint64: 2, ResultNumInUint64: 3},
-		parent:   &compiledFunction{parent: &compiledModule{source: &wasm.Module{}}},
+		parent:   &compiledFunction{parent: &compiledModule{source: s}, index: 1},
 	}
 	f3 := &function{
-		def:      newMockFunctionDefinition("3"),
 		funcType: &wasm.FunctionType{ResultNumInUint64: 1},
-		parent:   &compiledFunction{parent: &compiledModule{source: &wasm.Module{}}},
+		parent:   &compiledFunction{parent: &compiledModule{source: s}, index: 2},
 	}
 
 	ce := &callEngine{
@@ -440,30 +443,6 @@ wasm stack trace:
 	runtime.KeepAlive(f3)
 }
 
-func newMockFunctionDefinition(name string) api.FunctionDefinition {
-	return &mockFunctionDefinition{debugName: name, FunctionDefinition: &wasm.FunctionDefinition{}}
-}
-
-type mockFunctionDefinition struct {
-	debugName string
-	*wasm.FunctionDefinition
-}
-
-// DebugName implements the same method as documented on api.FunctionDefinition.
-func (f *mockFunctionDefinition) DebugName() string {
-	return f.debugName
-}
-
-// ParamTypes implements api.FunctionDefinition ParamTypes.
-func (f *mockFunctionDefinition) ParamTypes() []wasm.ValueType {
-	return []wasm.ValueType{}
-}
-
-// ResultTypes implements api.FunctionDefinition ResultTypes.
-func (f *mockFunctionDefinition) ResultTypes() []wasm.ValueType {
-	return []wasm.ValueType{}
-}
-
 func TestCallEngine_initializeStack(t *testing.T) {
 	const i32 = wasm.ValueTypeI32
 	const stackSize = 10
@@ -602,9 +581,7 @@ func assertStackIterator(t *testing.T, it experimental.StackIterator, expected [
 func TestCallEngine_builtinFunctionFunctionListenerBefore(t *testing.T) {
 	nextContext, currentContext, prevContext := context.Background(), context.Background(), context.Background()
 
-	def := newMockFunctionDefinition("1")
 	f := &function{
-		def:      def,
 		funcType: &wasm.FunctionType{ParamNumInUint64: 3},
 		parent: &compiledFunction{
 			listener: mockListener{
@@ -615,6 +592,10 @@ func TestCallEngine_builtinFunctionFunctionListenerBefore(t *testing.T) {
 					return nextContext
 				},
 			},
+			index: 0,
+			parent: &compiledModule{source: &wasm.Module{
+				FunctionDefinitionSection: []wasm.FunctionDefinition{{}},
+			}},
 		},
 	}
 	ce := &callEngine{
@@ -632,7 +613,6 @@ func TestCallEngine_builtinFunctionFunctionListenerBefore(t *testing.T) {
 func TestCallEngine_builtinFunctionFunctionListenerAfter(t *testing.T) {
 	currentContext, prevContext := context.Background(), context.Background()
 	f := &function{
-		def:      newMockFunctionDefinition("1"),
 		funcType: &wasm.FunctionType{ResultNumInUint64: 1},
 		parent: &compiledFunction{
 			listener: mockListener{
@@ -641,6 +621,10 @@ func TestCallEngine_builtinFunctionFunctionListenerAfter(t *testing.T) {
 					require.Equal(t, []uint64{5}, resultValues)
 				},
 			},
+			index: 0,
+			parent: &compiledModule{source: &wasm.Module{
+				FunctionDefinitionSection: []wasm.FunctionDefinition{{}},
+			}},
 		},
 	}
 
diff --git a/internal/testing/enginetest/enginetest.go b/internal/testing/enginetest/enginetest.go
index d443bb37..deed42cc 100644
--- a/internal/testing/enginetest/enginetest.go
+++ b/internal/testing/enginetest/enginetest.go
@@ -283,12 +283,12 @@ func RunTestModuleEngineLookupFunction(t *testing.T, et EngineTester) {
 	m.Tables[0].References[1] = me.FunctionInstanceReference(0)
 
 	t.Run("initialized", func(t *testing.T) {
-		index, err := me.LookupFunction(m.Tables[0], m.TypeIDs[0], 0) // offset 0 is now initialized.
+		f1, err := me.LookupFunction(m.Tables[0], m.TypeIDs[0], 0) // offset 0 is now initialized.
 		require.NoError(t, err)
-		require.Equal(t, wasm.Index(2), index)
-		index, err = me.LookupFunction(m.Tables[0], m.TypeIDs[0], 1) // offset 1 is now initialized.
+		require.Equal(t, wasm.Index(2), f1.Definition().Index())
+		f2, err := me.LookupFunction(m.Tables[0], m.TypeIDs[0], 1) // offset 1 is now initialized.
 		require.NoError(t, err)
-		require.Equal(t, wasm.Index(0), index)
+		require.Equal(t, wasm.Index(0), f2.Definition().Index())
 	})
 
 	t.Run("out of range", func(t *testing.T) {
@@ -311,12 +311,12 @@ func RunTestModuleEngineLookupFunction(t *testing.T, et EngineTester) {
 	m.Tables[2].References[0] = me.FunctionInstanceReference(1)
 	m.Tables[2].References[5] = me.FunctionInstanceReference(2)
 	t.Run("initialized - tables[2]", func(t *testing.T) {
-		index, err := me.LookupFunction(m.Tables[2], m.TypeIDs[0], 0)
+		f1, err := me.LookupFunction(m.Tables[2], m.TypeIDs[0], 0)
 		require.NoError(t, err)
-		require.Equal(t, wasm.Index(1), index)
-		index, err = me.LookupFunction(m.Tables[2], m.TypeIDs[0], 5)
+		require.Equal(t, wasm.Index(1), f1.Definition().Index())
+		f2, err := me.LookupFunction(m.Tables[2], m.TypeIDs[0], 5)
 		require.NoError(t, err)
-		require.Equal(t, wasm.Index(2), index)
+		require.Equal(t, wasm.Index(2), f2.Definition().Index())
 	})
 }
 
diff --git a/internal/wasm/function_definition_test.go b/internal/wasm/function_definition_test.go
index 5eca0fac..99c71f55 100644
--- a/internal/wasm/function_definition_test.go
+++ b/internal/wasm/function_definition_test.go
@@ -40,7 +40,7 @@ func TestModule_BuildFunctionDefinitions(t *testing.T) {
 		{
 			name: "only imported functions",
 			expected: []FunctionDefinition{
-				{name: "fn", debugName: ".fn", importDesc: &Import{Module: "foo", Name: "bar", Type: ExternTypeFunc}, funcType: &FunctionType{}},
+				{name: "fn", Debugname: ".fn", importDesc: &Import{Module: "foo", Name: "bar", Type: ExternTypeFunc}, Functype: &FunctionType{}},
 			},
 			m: &Module{
 				ExportSection:       []Export{{Type: ExternTypeGlobal, Index: 0}},
@@ -52,8 +52,8 @@ func TestModule_BuildFunctionDefinitions(t *testing.T) {
 			},
 			expectedImports: []api.FunctionDefinition{
 				&FunctionDefinition{
-					name: "fn", debugName: ".fn", importDesc: &Import{Module: "foo", Name: "bar", Type: ExternTypeFunc},
-					funcType: &FunctionType{},
+					name: "fn", Debugname: ".fn", importDesc: &Import{Module: "foo", Name: "bar", Type: ExternTypeFunc},
+					Functype: &FunctionType{},
 				},
 			},
 			expectedExports: map[string]api.FunctionDefinition{},
@@ -76,9 +76,9 @@ func TestModule_BuildFunctionDefinitions(t *testing.T) {
 					index:       0,
 					name:        "fn",
 					moduleName:  "m",
-					debugName:   "m.fn",
+					Debugname:   "m.fn",
 					goFunc:      MustParseGoReflectFuncCode(fn).GoFunc,
-					funcType:    &i32_i32,
+					Functype:    &i32_i32,
 					paramNames:  []string{"x"},
 					resultNames: []string{"y"},
 				},
@@ -110,41 +110,41 @@ func TestModule_BuildFunctionDefinitions(t *testing.T) {
 			expected: []FunctionDefinition{
 				{
 					index:       0,
-					debugName:   ".$0",
+					Debugname:   ".$0",
 					exportNames: []string{"function_index=0"},
-					funcType:    &f64i32_v128i64,
+					Functype:    &f64i32_v128i64,
 				},
 				{
 					index:       1,
-					debugName:   ".$1",
+					Debugname:   ".$1",
 					exportNames: []string{"function_index=1"},
-					funcType:    &f64f32_i64,
+					Functype:    &f64f32_i64,
 				},
 				{
 					index:       2,
-					debugName:   ".$2",
+					Debugname:   ".$2",
 					exportNames: []string{"function_index=2"},
-					funcType:    &v_v,
+					Functype:    &v_v,
 				},
 			},
 			expectedExports: map[string]api.FunctionDefinition{
 				"function_index=0": &FunctionDefinition{
 					index:       0,
-					debugName:   ".$0",
+					Debugname:   ".$0",
 					exportNames: []string{"function_index=0"},
-					funcType:    &f64i32_v128i64,
+					Functype:    &f64i32_v128i64,
 				},
 				"function_index=1": &FunctionDefinition{
 					index:       1,
 					exportNames: []string{"function_index=1"},
-					debugName:   ".$1",
-					funcType:    &f64f32_i64,
+					Debugname:   ".$1",
+					Functype:    &f64f32_i64,
 				},
 				"function_index=2": &FunctionDefinition{
 					index:       2,
-					debugName:   ".$2",
+					Debugname:   ".$2",
 					exportNames: []string{"function_index=2"},
-					funcType:    &v_v,
+					Functype:    &v_v,
 				},
 			},
 		},
@@ -169,52 +169,52 @@ func TestModule_BuildFunctionDefinitions(t *testing.T) {
 			expected: []FunctionDefinition{
 				{
 					index:       0,
-					debugName:   ".$0",
+					Debugname:   ".$0",
 					importDesc:  imp,
 					exportNames: []string{"imported_function"},
-					funcType:    &f64f32_i64,
+					Functype:    &f64f32_i64,
 				},
 				{
 					index:       1,
-					debugName:   ".$1",
+					Debugname:   ".$1",
 					exportNames: []string{"function_index=1"},
-					funcType:    &f64i32_v128i64,
+					Functype:    &f64i32_v128i64,
 				},
 				{
 					index:       2,
-					debugName:   ".$2",
+					Debugname:   ".$2",
 					exportNames: []string{"function_index=2"},
-					funcType:    &v_v,
+					Functype:    &v_v,
 				},
 			},
 			expectedImports: []api.FunctionDefinition{
 				&FunctionDefinition{
 					index:       0,
-					debugName:   ".$0",
+					Debugname:   ".$0",
 					importDesc:  imp,
 					exportNames: []string{"imported_function"},
-					funcType:    &f64f32_i64,
+					Functype:    &f64f32_i64,
 				},
 			},
 			expectedExports: map[string]api.FunctionDefinition{
 				"imported_function": &FunctionDefinition{
 					index:       0,
-					debugName:   ".$0",
+					Debugname:   ".$0",
 					importDesc:  imp,
 					exportNames: []string{"imported_function"},
-					funcType:    &f64f32_i64,
+					Functype:    &f64f32_i64,
 				},
 				"function_index=1": &FunctionDefinition{
 					index:       1,
-					debugName:   ".$1",
+					Debugname:   ".$1",
 					exportNames: []string{"function_index=1"},
-					funcType:    &f64i32_v128i64,
+					Functype:    &f64i32_v128i64,
 				},
 				"function_index=2": &FunctionDefinition{
 					index:       2,
-					debugName:   ".$2",
+					Debugname:   ".$2",
 					exportNames: []string{"function_index=2"},
-					funcType:    &v_v,
+					Functype:    &v_v,
 				},
 			},
 		},
@@ -236,15 +236,15 @@ func TestModule_BuildFunctionDefinitions(t *testing.T) {
 				CodeSection:     []Code{nopCode, nopCode, nopCode, nopCode, nopCode},
 			},
 			expected: []FunctionDefinition{
-				{moduleName: "module", index: 0, debugName: "module.$0", importDesc: &Import{Module: "i", Name: "f"}, funcType: &v_v},
-				{moduleName: "module", index: 1, debugName: "module.$1", funcType: &v_v},
-				{moduleName: "module", index: 2, debugName: "module.two", funcType: &v_v, name: "two"},
-				{moduleName: "module", index: 3, debugName: "module.$3", funcType: &v_v},
-				{moduleName: "module", index: 4, debugName: "module.four", funcType: &v_v, name: "four"},
-				{moduleName: "module", index: 5, debugName: "module.five", funcType: &v_v, name: "five"},
+				{moduleName: "module", index: 0, Debugname: "module.$0", importDesc: &Import{Module: "i", Name: "f"}, Functype: &v_v},
+				{moduleName: "module", index: 1, Debugname: "module.$1", Functype: &v_v},
+				{moduleName: "module", index: 2, Debugname: "module.two", Functype: &v_v, name: "two"},
+				{moduleName: "module", index: 3, Debugname: "module.$3", Functype: &v_v},
+				{moduleName: "module", index: 4, Debugname: "module.four", Functype: &v_v, name: "four"},
+				{moduleName: "module", index: 5, Debugname: "module.five", Functype: &v_v, name: "five"},
 			},
 			expectedImports: []api.FunctionDefinition{
-				&FunctionDefinition{moduleName: "module", index: 0, debugName: "module.$0", importDesc: &Import{Module: "i", Name: "f"}, funcType: &v_v},
+				&FunctionDefinition{moduleName: "module", index: 0, Debugname: "module.$0", importDesc: &Import{Module: "i", Name: "f"}, Functype: &v_v},
 			},
 			expectedExports: map[string]api.FunctionDefinition{},
 		},
diff --git a/internal/wasm/store_test.go b/internal/wasm/store_test.go
index 135f688c..9046ab02 100644
--- a/internal/wasm/store_test.go
+++ b/internal/wasm/store_test.go
@@ -145,7 +145,7 @@ func TestStore_CloseWithExitCode(t *testing.T) {
 				FunctionSection:           []uint32{0},
 				CodeSection:               []Code{{Body: []byte{OpcodeEnd}}},
 				Exports:                   map[string]*Export{"fn": {Type: ExternTypeFunc, Name: "fn"}},
-				FunctionDefinitionSection: []FunctionDefinition{{funcType: &v_v}},
+				FunctionDefinitionSection: []FunctionDefinition{{Functype: &v_v}},
 			}, importedModuleName, nil, []FunctionTypeID{0})
 			require.NoError(t, err)
 
@@ -439,8 +439,8 @@ func (e *mockEngine) CompileModule(context.Context, *Module, []experimental.Func
 }
 
 // LookupFunction implements the same method as documented on wasm.Engine.
-func (e *mockModuleEngine) LookupFunction(*TableInstance, FunctionTypeID, Index) (Index, error) {
-	return 0, nil
+func (e *mockModuleEngine) LookupFunction(*TableInstance, FunctionTypeID, Index) (api.Function, error) {
+	return nil, nil
 }
 
 // CompiledModuleCount implements the same method as documented on wasm.Engine.
@@ -695,9 +695,9 @@ func Test_resolveImports(t *testing.T) {
 				Definitions: []FunctionDefinition{
 					{},
 					{},
-					{funcType: &FunctionType{Params: []ValueType{i32}, Results: []ValueType{ValueTypeV128}}},
+					{Functype: &FunctionType{Params: []ValueType{i32}, Results: []ValueType{ValueTypeV128}}},
 					{},
-					{funcType: &FunctionType{Params: []ValueType{ExternTypeFunc}, Results: []ValueType{}}},
+					{Functype: &FunctionType{Params: []ValueType{ExternTypeFunc}, Results: []ValueType{}}},
 				},
 			}
 			module := &Module{
@@ -729,7 +729,7 @@ func Test_resolveImports(t *testing.T) {
 				},
 				ModuleName:  moduleName,
 				TypeIDs:     []FunctionTypeID{123435},
-				Definitions: []FunctionDefinition{{funcType: &FunctionType{}}},
+				Definitions: []FunctionDefinition{{Functype: &FunctionType{}}},
 			}
 			module := &Module{
 				TypeSection: []FunctionType{{Results: []ValueType{ValueTypeF32}}},