Repository: tetratelabs/wazero (go)

Issue:
A host function that calls another host function loses access to the memory
**Describe the bug**
So I'm not entirely sure this is a bug report or a feature request. I came across this issue while implementing C++ exceptions support for Emscripten in Wazero. Apparently it's possible that Emscripten uses the generated invoke_xxx methods (which is a host function) to call another host function, without a "proxy" through the guest. I then found out that it's also possible to reproduce it without using C++ exceptions. 

This issue causes the second host function not to have access to the memory, because the second host function is being called by invoke_xxx, which is in the env module, and receives the env `api.Module`, while the invoke_xxx is also in the env module, it does receive the main application memory, and this is also mentioned in the comments of `GoModuleFunction`:

```
// This function can be non-deterministic or cause side effects. It also
// has special properties not defined in the WebAssembly Core specification.
// Notably, this uses the caller's memory (via Module.Memory). See
// https://www.w3.org/TR/wasm-core-1/#host-functions%E2%91%A0
```

While it does make sense to give this the caller's memory (if we didn't, the host functions would be way less usable), but in my case, this means that the second host function call does not have access to the memory, just because it was called from another host method and not from the guest, while it would have access to it if it would have been called by a "proxy" function in the guest. 

I'm not sure what the best solution to this would be. Perhaps it would be possible to have an option to override the moduleInstance or memoryInstance in `m.Engine.LookupFunction` or `f.CallWithStack` so that we can use it in `InvokeFunc.Call()` to maintain access to the memory when doing host to host calls.

Given the sample code in the next block, the following two things will happen:
 - invoke_i (host)
 - you_could_say_im_a_host_function_proxy (guest)
 - you_could_say_im_a_host_function (host)
 - m.Memory().ReadByte(200)

Works :tada: 

 - invoke_i (host)
 - you_could_say_im_a_host_function (host)
 - m.Memory().ReadByte(200)

Breaks :bomb: (`failed: runtime error: invalid memory address or nil pointer dereference`)

**To Reproduce**

<details>

<summary>C code compiled with Emscripten</summary>

```c
#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>

extern int you_could_say_im_a_host_function();

void jmpfunction(jmp_buf env_buf) {
   longjmp(env_buf, 42);
}

int you_could_say_im_a_host_function_proxy() {
  return you_could_say_im_a_host_function();
}

int main () {
   int val;
   jmp_buf env_buffer;

   val = setjmp( env_buffer );

   if( val != 0 ) {
      printf("Returned from a longjmp() with value = %d\n", val);
      you_could_say_im_a_host_function_proxy();
      you_could_say_im_a_host_function();
      exit(0);
   }

   printf("Jump function call\n");
   jmpfunction(env_buffer);

   return(0);
}

```
</details>

`emcc -sERROR_ON_UNDEFINED_SYMBOLS=0 -sSUPPORT_LONGJMP=emscripten -g invoke_host.c -o invoke_host.wasm`

<details>

<summary>Go code with the host function implemented</summary>

```go
package main

import (
	"context"
	_ "embed"
	"github.com/tetratelabs/wazero"
	"github.com/tetratelabs/wazero/api"
	"github.com/tetratelabs/wazero/experimental"
	"github.com/tetratelabs/wazero/experimental/logging"
	"github.com/tetratelabs/wazero/imports/emscripten"
	"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
	"log"
	"os"
)

//go:embed invoke_host.wasm
var wasm []byte

func main() {
	ctx := context.WithValue(context.Background(), experimental.FunctionListenerFactoryKey{}, logging.NewLoggingListenerFactory(os.Stdout))
	r := wazero.NewRuntime(ctx)

	defer r.Close(ctx)

	if _, err := wasi_snapshot_preview1.Instantiate(ctx, r); err != nil {
		log.Fatal(err)
	}

	compiledModule, err := r.CompileModule(ctx, wasm)
	if err != nil {
		log.Fatal(err)
	}

	functionExporter, err := emscripten.NewFunctionExporterForModule(compiledModule)
	if err != nil {
		log.Fatal(err)
	}

	envModule := r.NewHostModuleBuilder("env")
	functionExporter.ExportFunctions(envModule)
	envModule.NewFunctionBuilder().WithGoModuleFunction(api.GoModuleFunc(func(ctx context.Context, m api.Module, stack []uint64) {
		m.Memory().ReadByte(200)
		stack[0] = 42
	}), []api.ValueType{}, []api.ValueType{api.ValueTypeI32}).Export("you_could_say_im_a_host_function")
	_, err = envModule.Instantiate(ctx)
	if err != nil {
		log.Fatal(err)
	}

	moduleConfig := wazero.NewModuleConfig().
		WithStartFunctions("_start").
		WithStdout(os.Stdout).
		WithStderr(os.Stderr)

	_, err = r.InstantiateModule(ctx, compiledModule, moduleConfig)
	if err != nil {
		log.Fatal(err)
	}
}
```

</details>

Full sample zip: 
[invoke_host.zip](https://github.com/tetratelabs/wazero/files/12327066/invoke_host.zip)


**Environment (please complete the relevant information):**
- Go version: 1.20.2
- wazero Version: v1.4.0
- Host architecture: amd64
- Runtime mode: both

**Additional context**

This was also discussed a bit on Slack: https://gophers.slack.com/archives/C040AKTNTE0/p1691699092428169

Pull request description:
Lifts LookupFunction into ModuleInstance from ModuleEngine
Fixes #1626 

Patch:
diff --git a/internal/emscripten/emscripten.go b/internal/emscripten/emscripten.go
index 3262e670..e6aa79f0 100644
--- a/internal/emscripten/emscripten.go
+++ b/internal/emscripten/emscripten.go
@@ -106,10 +106,7 @@ 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
-	f, err := m.Engine.LookupFunction(t, typeID, tableOffset)
-	if err != nil {
-		panic(err)
-	}
+	f := m.LookupFunction(t, typeID, tableOffset)
 
 	// The Go implementation below mimics the Emscripten JS behaviour to support
 	// longjmps from indirect function calls. The implementation of these
diff --git a/internal/engine/compiler/engine.go b/internal/engine/compiler/engine.go
index 77df6994..f8b76fa4 100644
--- a/internal/engine/compiler/engine.go
+++ b/internal/engine/compiler/engine.go
@@ -681,24 +681,20 @@ func (e *moduleEngine) newFunction(f *function) 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) (f api.Function, err error) {
+func (e *moduleEngine) LookupFunction(t *wasm.TableInstance, typeId wasm.FunctionTypeID, tableOffset wasm.Index) (*wasm.ModuleInstance, wasm.Index) {
 	if tableOffset >= uint32(len(t.References)) || t.Type != wasm.RefTypeFuncref {
-		err = wasmruntime.ErrRuntimeInvalidTableAccess
-		return
+		panic(wasmruntime.ErrRuntimeInvalidTableAccess)
 	}
 	rawPtr := t.References[tableOffset]
 	if rawPtr == 0 {
-		err = wasmruntime.ErrRuntimeInvalidTableAccess
-		return
+		panic(wasmruntime.ErrRuntimeInvalidTableAccess)
 	}
 
 	tf := functionFromUintptr(rawPtr)
 	if tf.typeID != typeId {
-		err = wasmruntime.ErrRuntimeIndirectCallTypeMismatch
-		return
+		panic(wasmruntime.ErrRuntimeIndirectCallTypeMismatch)
 	}
-	f = e.newFunction(tf)
-	return
+	return tf.moduleInstance, tf.parent.index
 }
 
 // functionFromUintptr resurrects the original *function from the given uintptr
diff --git a/internal/engine/interpreter/interpreter.go b/internal/engine/interpreter/interpreter.go
index f4a79b71..8f0db3c7 100644
--- a/internal/engine/interpreter/interpreter.go
+++ b/internal/engine/interpreter/interpreter.go
@@ -462,25 +462,20 @@ 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) (f api.Function, err error) {
+func (e *moduleEngine) LookupFunction(t *wasm.TableInstance, typeId wasm.FunctionTypeID, tableOffset wasm.Index) (*wasm.ModuleInstance, wasm.Index) {
 	if tableOffset >= uint32(len(t.References)) {
-		err = wasmruntime.ErrRuntimeInvalidTableAccess
-		return
+		panic(wasmruntime.ErrRuntimeInvalidTableAccess)
 	}
 	rawPtr := t.References[tableOffset]
 	if rawPtr == 0 {
-		err = wasmruntime.ErrRuntimeInvalidTableAccess
-		return
+		panic(wasmruntime.ErrRuntimeInvalidTableAccess)
 	}
 
 	tf := functionFromUintptr(rawPtr)
 	if tf.typeID != typeId {
-		err = wasmruntime.ErrRuntimeIndirectCallTypeMismatch
-		return
+		panic(wasmruntime.ErrRuntimeIndirectCallTypeMismatch)
 	}
-
-	f = e.newCallEngine(tf)
-	return
+	return tf.moduleInstance, tf.parent.index
 }
 
 // Definition implements the same method as documented on api.Function.
diff --git a/internal/engine/wazevo/module_engine.go b/internal/engine/wazevo/module_engine.go
index 0bf801f1..6eaa560d 100644
--- a/internal/engine/wazevo/module_engine.go
+++ b/internal/engine/wazevo/module_engine.go
@@ -206,6 +206,6 @@ func (m *moduleEngine) FunctionInstanceReference(funcIndex wasm.Index) wasm.Refe
 }
 
 // LookupFunction implements wasm.ModuleEngine.
-func (m *moduleEngine) LookupFunction(t *wasm.TableInstance, typeId wasm.FunctionTypeID, tableOffset wasm.Index) (api.Function, error) {
+func (m *moduleEngine) LookupFunction(t *wasm.TableInstance, typeId wasm.FunctionTypeID, tableOffset wasm.Index) (*wasm.ModuleInstance, wasm.Index) {
 	panic("TODO")
 }
diff --git a/internal/wasm/engine.go b/internal/wasm/engine.go
index 26d196b0..3d483807 100644
--- a/internal/wasm/engine.go
+++ b/internal/wasm/engine.go
@@ -51,8 +51,8 @@ type ModuleEngine interface {
 	// ResolveImportedMemory is called when this module imports a memory from another module.
 	ResolveImportedMemory(importedModuleEngine ModuleEngine)
 
-	// 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)
+	// LookupFunction returns the FunctionModule and the Index of the function in the returned ModuleInstance at the given offset in the table.
+	LookupFunction(t *TableInstance, typeId FunctionTypeID, tableOffset Index) (*ModuleInstance, Index)
 
 	// 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/module_instance_lookup.go b/internal/wasm/module_instance_lookup.go
new file mode 100644
index 00000000..442d26a2
--- /dev/null
+++ b/internal/wasm/module_instance_lookup.go
@@ -0,0 +1,73 @@
+package wasm
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/tetratelabs/wazero/api"
+	"github.com/tetratelabs/wazero/internal/internalapi"
+)
+
+// LookupFunction looks up the table by the given index, and returns the api.Function implementation if found,
+// otherwise this panics according to the same semantics as call_indirect instruction.
+// Currently, this is only used by emscripten which needs to do call_indirect-like operation in the host function.
+func (m *ModuleInstance) LookupFunction(t *TableInstance, typeId FunctionTypeID, tableOffset Index) api.Function {
+	fm, index := m.Engine.LookupFunction(t, typeId, tableOffset)
+	if source := fm.Source; source.IsHostModule {
+		// This case, the found function is a host function stored in the table. Generally, Engine.NewFunction are only
+		// responsible for calling Wasm-defined functions (not designed for calling Go functions!). Hence we need to wrap
+		// the host function as a special case.
+		def := &source.FunctionDefinitionSection[index]
+		goF := source.CodeSection[index].GoFunc
+		switch typed := goF.(type) {
+		case api.GoFunction:
+			// GoFunction doesn't need looked up module.
+			return &lookedUpGoFunction{def: def, g: goFunctionAsGoModuleFunction(typed)}
+		case api.GoModuleFunction:
+			return &lookedUpGoFunction{def: def, lookedUpModule: m, g: typed}
+		default:
+			panic(fmt.Sprintf("unexpected GoFunc type: %T", goF))
+		}
+	} else {
+		return fm.Engine.NewFunction(index)
+	}
+}
+
+// lookedUpGoFunction implements lookedUpGoModuleFunction.
+type lookedUpGoFunction struct {
+	internalapi.WazeroOnly
+	def *FunctionDefinition
+	// lookedUpModule is the *ModuleInstance from which this Go function is looked up, i.e. owner of the table.
+	lookedUpModule *ModuleInstance
+	g              api.GoModuleFunction
+}
+
+// goFunctionAsGoModuleFunction converts api.GoFunction to api.GoModuleFunction which ignores the api.Module argument.
+func goFunctionAsGoModuleFunction(g api.GoFunction) api.GoModuleFunction {
+	return api.GoModuleFunc(func(ctx context.Context, _ api.Module, stack []uint64) {
+		g.Call(ctx, stack)
+	})
+}
+
+// Definition implements api.Function.
+func (l *lookedUpGoFunction) Definition() api.FunctionDefinition { return l.def }
+
+// Call implements api.Function.
+func (l *lookedUpGoFunction) Call(ctx context.Context, params ...uint64) ([]uint64, error) {
+	typ := l.def.Functype
+	stackSize := typ.ParamNumInUint64
+	rn := typ.ResultNumInUint64
+	if rn > stackSize {
+		stackSize = rn
+	}
+	stack := make([]uint64, stackSize)
+	copy(stack, params)
+	return stack[:rn], l.CallWithStack(ctx, stack)
+}
+
+// CallWithStack implements api.Function.
+func (l *lookedUpGoFunction) CallWithStack(ctx context.Context, stack []uint64) error {
+	// The Go host function always needs to access caller's module, in this case the one holding the table.
+	l.g.Call(ctx, l.lookedUpModule, stack)
+	return nil
+}


Test patch:
diff --git a/internal/testing/enginetest/enginetest.go b/internal/testing/enginetest/enginetest.go
index b9dbbb77..edd80527 100644
--- a/internal/testing/enginetest/enginetest.go
+++ b/internal/testing/enginetest/enginetest.go
@@ -252,6 +252,7 @@ func RunTestModuleEngineLookupFunction(t *testing.T, et EngineTester) {
 	require.NoError(t, err)
 	m := &wasm.ModuleInstance{
 		TypeIDs: []wasm.FunctionTypeID{0, 1},
+		Source:  mod,
 	}
 	m.Tables = []*wasm.TableInstance{
 		{Min: 2, References: make([]wasm.Reference, 2), Type: wasm.RefTypeFuncref},
@@ -264,9 +265,13 @@ func RunTestModuleEngineLookupFunction(t *testing.T, et EngineTester) {
 	linkModuleToEngine(m, me)
 
 	t.Run("null reference", func(t *testing.T) {
-		_, err := me.LookupFunction(m.Tables[0], m.TypeIDs[0], 0) // offset 0 is not initialized yet.
+		err = require.CapturePanic(func() {
+			m.LookupFunction(m.Tables[0], m.TypeIDs[0], 0) // offset 0 is not initialized yet.
+		})
 		require.Equal(t, wasmruntime.ErrRuntimeInvalidTableAccess, err)
-		_, err = me.LookupFunction(m.Tables[0], m.TypeIDs[0], 1) // offset 1 is not initialized yet.
+		err = require.CapturePanic(func() {
+			m.LookupFunction(m.Tables[0], m.TypeIDs[0], 1) // offset 1 is not initialized yet.
+		})
 		require.Equal(t, wasmruntime.ErrRuntimeInvalidTableAccess, err)
 	})
 
@@ -274,39 +279,41 @@ func RunTestModuleEngineLookupFunction(t *testing.T, et EngineTester) {
 	m.Tables[0].References[1] = me.FunctionInstanceReference(0)
 
 	t.Run("initialized", func(t *testing.T) {
-		f1, err := me.LookupFunction(m.Tables[0], m.TypeIDs[0], 0) // offset 0 is now initialized.
-		require.NoError(t, err)
+		f1 := m.LookupFunction(m.Tables[0], m.TypeIDs[0], 0) // offset 0 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)
+		f2 := m.LookupFunction(m.Tables[0], m.TypeIDs[0], 1) // offset 1 is now initialized.
 		require.Equal(t, wasm.Index(0), f2.Definition().Index())
 	})
 
 	t.Run("out of range", func(t *testing.T) {
-		_, err := me.LookupFunction(m.Tables[0], m.TypeIDs[0], 100 /* out of range */)
+		err = require.CapturePanic(func() {
+			me.LookupFunction(m.Tables[0], m.TypeIDs[0], 100 /* out of range */)
+		})
 		require.Equal(t, wasmruntime.ErrRuntimeInvalidTableAccess, err)
 	})
 
 	t.Run("access to externref table", func(t *testing.T) {
-		_, err := me.LookupFunction(m.Tables[1], /* table[1] has externref type. */
-			m.TypeIDs[0], 0)
+		err := require.CapturePanic(func() {
+			m.LookupFunction(m.Tables[1], /* table[1] has externref type. */
+				m.TypeIDs[0], 0)
+		})
 		require.Equal(t, wasmruntime.ErrRuntimeInvalidTableAccess, err)
 	})
 
 	t.Run("access to externref table", func(t *testing.T) {
-		_, err := me.LookupFunction(m.Tables[0], /* type mismatch */
-			m.TypeIDs[1], 0)
+		err = require.CapturePanic(func() {
+			me.LookupFunction(m.Tables[0], /* type mismatch */
+				m.TypeIDs[1], 0)
+		})
 		require.Equal(t, wasmruntime.ErrRuntimeIndirectCallTypeMismatch, err)
 	})
 
 	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) {
-		f1, err := me.LookupFunction(m.Tables[2], m.TypeIDs[0], 0)
-		require.NoError(t, err)
+		f1 := m.LookupFunction(m.Tables[2], m.TypeIDs[0], 0)
 		require.Equal(t, wasm.Index(1), f1.Definition().Index())
-		f2, err := me.LookupFunction(m.Tables[2], m.TypeIDs[0], 5)
-		require.NoError(t, err)
+		f2 := m.LookupFunction(m.Tables[2], m.TypeIDs[0], 5)
 		require.Equal(t, wasm.Index(2), f2.Definition().Index())
 	})
 }
diff --git a/internal/wasm/module_instance_lookup_test.go b/internal/wasm/module_instance_lookup_test.go
new file mode 100644
index 00000000..3e8c839e
--- /dev/null
+++ b/internal/wasm/module_instance_lookup_test.go
@@ -0,0 +1,96 @@
+package wasm
+
+import (
+	"context"
+	"math"
+	"testing"
+
+	"github.com/tetratelabs/wazero/api"
+	"github.com/tetratelabs/wazero/internal/testing/require"
+)
+
+func TestModuleInstance_LookupFunction(t *testing.T) {
+	var called int
+	hostModule := &Module{
+		IsHostModule: true,
+		CodeSection: []Code{
+			{GoFunc: api.GoFunc(func(context.Context, []uint64) {
+				called++
+			})},
+			{GoFunc: api.GoModuleFunc(func(context.Context, api.Module, []uint64) {
+				called++
+			})},
+		},
+		FunctionDefinitionSection: []FunctionDefinition{{}, {}},
+	}
+
+	me := &mockModuleEngine{
+		lookupEntries: map[Index]mockModuleEngineLookupEntry{
+			0: {m: &ModuleInstance{Source: hostModule}, index: 0},
+			1: {m: &ModuleInstance{Source: hostModule}, index: 1},
+		},
+	}
+	m := &ModuleInstance{Engine: me}
+
+	t.Run("host", func(t *testing.T) {
+		gf, ok := m.LookupFunction(nil, 0, 0).(*lookedUpGoFunction)
+		require.True(t, ok)
+		require.Nil(t, gf.lookedUpModule) // GoFunction doesn't need looked up module.
+		require.Equal(t, &hostModule.FunctionDefinitionSection[0], gf.def)
+		err := gf.CallWithStack(context.Background(), nil)
+		require.NoError(t, err)
+
+		gmf, ok := m.LookupFunction(nil, 0, 1).(*lookedUpGoFunction)
+		require.True(t, ok)
+		require.Equal(t, m, gmf.lookedUpModule)
+		require.Equal(t, hostModule.CodeSection[1].GoFunc, gmf.g)
+		require.Equal(t, &hostModule.FunctionDefinitionSection[1], gmf.def)
+		err = gmf.CallWithStack(context.Background(), nil)
+		require.NoError(t, err)
+
+		require.Equal(t, 2, called)
+	})
+
+	t.Run("wasm", func(t *testing.T) {
+		me.lookupEntries[2] = mockModuleEngineLookupEntry{
+			m: &ModuleInstance{
+				Source: &Module{},
+				Engine: me,
+			},
+			index: 100,
+		}
+		wf, ok := m.LookupFunction(nil, 0, 2).(*mockCallEngine)
+		require.True(t, ok)
+		require.Equal(t, Index(100), wf.index)
+	})
+}
+
+func Test_lookedUpGoModuleFunction(t *testing.T) {
+	def := &FunctionDefinition{
+		Functype: &FunctionType{
+			ParamNumInUint64: 2, ResultNumInUint64: 1,
+			Params: []ValueType{ValueTypeI64, ValueTypeF64}, Results: []ValueType{ValueTypeI64},
+		},
+	}
+
+	expMod := &ModuleInstance{}
+	var called bool
+	l := &lookedUpGoFunction{
+		lookedUpModule: expMod,
+		def:            def,
+		g: api.GoModuleFunc(
+			func(ctx context.Context, mod api.Module, stack []uint64) {
+				require.Equal(t, expMod, mod)
+				require.Equal(t, []uint64{math.MaxUint64, math.Float64bits(math.Pi)}, stack)
+				stack[0] = 1
+				called = true
+			},
+		),
+	}
+	require.Equal(t, l.Definition().(*FunctionDefinition), l.def)
+	result, err := l.Call(context.Background(), math.MaxUint64, math.Float64bits(math.Pi))
+	require.NoError(t, err)
+	require.True(t, called)
+	require.Equal(t, []uint64{1}, result)
+	require.Equal(t, 1, len(result))
+}
diff --git a/internal/wasm/store_test.go b/internal/wasm/store_test.go
index 195095fb..00b418f0 100644
--- a/internal/wasm/store_test.go
+++ b/internal/wasm/store_test.go
@@ -413,6 +413,12 @@ type mockModuleEngine struct {
 	functionRefs         map[Index]Reference
 	resolveImportsCalled map[Index]Index
 	importedMemModEngine ModuleEngine
+	lookupEntries        map[Index]mockModuleEngineLookupEntry
+}
+
+type mockModuleEngineLookupEntry struct {
+	m     *ModuleInstance
+	index Index
 }
 
 type mockCallEngine struct {
@@ -436,8 +442,11 @@ 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) (api.Function, error) {
-	return nil, nil
+func (e *mockModuleEngine) LookupFunction(_ *TableInstance, _ FunctionTypeID, offset Index) (*ModuleInstance, Index) {
+	if entry, ok := e.lookupEntries[offset]; ok {
+		return entry.m, entry.index
+	}
+	return nil, 0
 }
 
 // CompiledModuleCount implements the same method as documented on wasm.Engine.