Repository: tetratelabs/wazero
Pull request #436 (2022-04-04 02:16:13)

Adds base call error tests
This adds tests that pass without changing deferred error handling.
There are some tests that don't pass even without deferred error
handling. I'll add those in a separate PR.



Patch:
diff --git a/internal/wasm/host.go b/internal/wasm/host.go
index ec6216ff..8427ec5b 100644
--- a/internal/wasm/host.go
+++ b/internal/wasm/host.go
@@ -93,11 +93,11 @@ func (m *Module) buildHostFunctionInstances() (functions []*FunctionInstance) {
 	for idx, typeIndex := range m.FunctionSection {
 		fn := m.HostFunctionSection[idx]
 		f := &FunctionInstance{
-			Name:   functionNames[idx].Name,
-			Kind:   kind(fn.Type()),
-			Type:   m.TypeSection[typeIndex],
-			GoFunc: fn,
-			Index:  Index(idx),
+			DebugName: functionNames[idx].Name,
+			Kind:      kind(fn.Type()),
+			Type:      m.TypeSection[typeIndex],
+			GoFunc:    fn,
+			Index:     Index(idx),
 		}
 		functions = append(functions, f)
 	}
diff --git a/internal/wasm/interpreter/interpreter.go b/internal/wasm/interpreter/interpreter.go
index a39000b1..e295ef91 100644
--- a/internal/wasm/interpreter/interpreter.go
+++ b/internal/wasm/interpreter/interpreter.go
@@ -11,9 +11,10 @@ import (
 	"sync"
 	"sync/atomic"
 
+	"github.com/tetratelabs/wazero/internal/buildoptions"
 	"github.com/tetratelabs/wazero/internal/moremath"
 	"github.com/tetratelabs/wazero/internal/wasm"
-	"github.com/tetratelabs/wazero/internal/wasm/buildoptions"
+	"github.com/tetratelabs/wazero/internal/wasmruntime"
 	"github.com/tetratelabs/wazero/internal/wazeroir"
 	"github.com/tetratelabs/wazero/sys"
 )
@@ -124,7 +125,7 @@ func (ce *callEngine) drop(r *wazeroir.InclusiveRange) {
 
 func (ce *callEngine) pushFrame(frame *callFrame) {
 	if callStackCeiling <= len(ce.frames) {
-		panic(wasm.ErrRuntimeCallStackOverflow)
+		panic(wasmruntime.ErrRuntimeCallStackOverflow)
 	}
 	ce.frames = append(ce.frames, frame)
 }
@@ -173,7 +174,7 @@ func (e *engine) NewModuleEngine(name string, importedFunctions, moduleFunctions
 	for idx, f := range importedFunctions {
 		cf, ok := e.getCompiledFunction(f)
 		if !ok {
-			return nil, fmt.Errorf("import[%d] func[%s.%s]: uncompiled", idx, f.Module.Name, f.Name)
+			return nil, fmt.Errorf("import[%d] func[%s]: uncompiled", idx, f.DebugName)
 		}
 		me.compiledFunctions = append(me.compiledFunctions, cf)
 	}
@@ -532,16 +533,13 @@ func (me *moduleEngine) Call(ctx *wasm.ModuleContext, f *wasm.FunctionInstance,
 			traces := make([]string, len(ce.frames))
 			for i := 0; i < len(traces); i++ {
 				frame := ce.popFrame()
-				name := frame.f.funcInstance.Name
+				name := frame.f.funcInstance.DebugName
 				// TODO: include DWARF symbols. See #58
 				traces[i] = fmt.Sprintf("\t%d: %s", i, name)
 			}
 
 			runtimeErr, ok := v.(error)
 			if ok {
-				if runtimeErr.Error() == "runtime error: integer divide by zero" {
-					runtimeErr = wasm.ErrRuntimeIntegerDivideByZero
-				}
 				err = fmt.Errorf("wasm runtime error: %w", runtimeErr)
 			} else {
 				err = fmt.Errorf("wasm runtime error: %v", v)
@@ -646,7 +644,7 @@ func (ce *callEngine) callNativeFunc(ctx *wasm.ModuleContext, f *compiledFunctio
 		// how the stack is modified, etc.
 		switch op.kind {
 		case wazeroir.OperationKindUnreachable:
-			panic(wasm.ErrRuntimeUnreachable)
+			panic(wasmruntime.ErrRuntimeUnreachable)
 		case wazeroir.OperationKindBr:
 			{
 				frame.pc = op.us[0]
@@ -686,13 +684,13 @@ func (ce *callEngine) callNativeFunc(ctx *wasm.ModuleContext, f *compiledFunctio
 			{
 				offset := ce.pop()
 				if offset >= uint64(len(table.Table)) {
-					panic(wasm.ErrRuntimeInvalidTableAccess)
+					panic(wasmruntime.ErrRuntimeInvalidTableAccess)
 				}
 				targetCompiledFunction, ok := table.Table[offset].(*compiledFunction)
 				if !ok {
-					panic(wasm.ErrRuntimeInvalidTableAccess)
+					panic(wasmruntime.ErrRuntimeInvalidTableAccess)
 				} else if uint64(targetCompiledFunction.funcInstance.TypeID) != op.us[1] {
-					panic(wasm.ErrRuntimeIndirectCallTypeMismatch)
+					panic(wasmruntime.ErrRuntimeIndirectCallTypeMismatch)
 				}
 
 				// Call in.
@@ -747,12 +745,12 @@ func (ce *callEngine) callNativeFunc(ctx *wasm.ModuleContext, f *compiledFunctio
 				switch wazeroir.UnsignedType(op.b1) {
 				case wazeroir.UnsignedTypeI32, wazeroir.UnsignedTypeF32:
 					if uint64(len(memoryInst.Buffer)) < base+4 {
-						panic(wasm.ErrRuntimeOutOfBoundsMemoryAccess)
+						panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
 					}
 					ce.push(uint64(binary.LittleEndian.Uint32(memoryInst.Buffer[base:])))
 				case wazeroir.UnsignedTypeI64, wazeroir.UnsignedTypeF64:
 					if uint64(len(memoryInst.Buffer)) < base+8 {
-						panic(wasm.ErrRuntimeOutOfBoundsMemoryAccess)
+						panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
 					}
 					ce.push(binary.LittleEndian.Uint64(memoryInst.Buffer[base:]))
 				}
@@ -762,7 +760,7 @@ func (ce *callEngine) callNativeFunc(ctx *wasm.ModuleContext, f *compiledFunctio
 			{
 				base := op.us[1] + ce.pop()
 				if uint64(len(memoryInst.Buffer)) < base+1 {
-					panic(wasm.ErrRuntimeOutOfBoundsMemoryAccess)
+					panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
 				}
 				switch wazeroir.SignedInt(op.b1) {
 				case wazeroir.SignedInt32, wazeroir.SignedInt64:
@@ -776,7 +774,7 @@ func (ce *callEngine) callNativeFunc(ctx *wasm.ModuleContext, f *compiledFunctio
 			{
 				base := op.us[1] + ce.pop()
 				if uint64(len(memoryInst.Buffer)) < base+2 {
-					panic(wasm.ErrRuntimeOutOfBoundsMemoryAccess)
+					panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
 				}
 				switch wazeroir.SignedInt(op.b1) {
 				case wazeroir.SignedInt32, wazeroir.SignedInt64:
@@ -790,7 +788,7 @@ func (ce *callEngine) callNativeFunc(ctx *wasm.ModuleContext, f *compiledFunctio
 			{
 				base := op.us[1] + ce.pop()
 				if uint64(len(memoryInst.Buffer)) < base+4 {
-					panic(wasm.ErrRuntimeOutOfBoundsMemoryAccess)
+					panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
 				}
 				if op.b1 == 1 {
 					ce.push(uint64(int32(binary.LittleEndian.Uint32(memoryInst.Buffer[base:]))))
@@ -806,12 +804,12 @@ func (ce *callEngine) callNativeFunc(ctx *wasm.ModuleContext, f *compiledFunctio
 				switch wazeroir.UnsignedType(op.b1) {
 				case wazeroir.UnsignedTypeI32, wazeroir.UnsignedTypeF32:
 					if uint64(len(memoryInst.Buffer)) < base+4 {
-						panic(wasm.ErrRuntimeOutOfBoundsMemoryAccess)
+						panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
 					}
 					binary.LittleEndian.PutUint32(memoryInst.Buffer[base:], uint32(val))
 				case wazeroir.UnsignedTypeI64, wazeroir.UnsignedTypeF64:
 					if uint64(len(memoryInst.Buffer)) < base+8 {
-						panic(wasm.ErrRuntimeOutOfBoundsMemoryAccess)
+						panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
 					}
 					binary.LittleEndian.PutUint64(memoryInst.Buffer[base:], val)
 				}
@@ -822,7 +820,7 @@ func (ce *callEngine) callNativeFunc(ctx *wasm.ModuleContext, f *compiledFunctio
 				val := byte(ce.pop())
 				base := op.us[1] + ce.pop()
 				if uint64(len(memoryInst.Buffer)) < base+1 {
-					panic(wasm.ErrRuntimeOutOfBoundsMemoryAccess)
+					panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
 				}
 				memoryInst.Buffer[base] = val
 				frame.pc++
@@ -832,7 +830,7 @@ func (ce *callEngine) callNativeFunc(ctx *wasm.ModuleContext, f *compiledFunctio
 				val := uint16(ce.pop())
 				base := op.us[1] + ce.pop()
 				if uint64(len(memoryInst.Buffer)) < base+2 {
-					panic(wasm.ErrRuntimeOutOfBoundsMemoryAccess)
+					panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
 				}
 				binary.LittleEndian.PutUint16(memoryInst.Buffer[base:], val)
 				frame.pc++
@@ -842,7 +840,7 @@ func (ce *callEngine) callNativeFunc(ctx *wasm.ModuleContext, f *compiledFunctio
 				val := uint32(ce.pop())
 				base := op.us[1] + ce.pop()
 				if uint64(len(memoryInst.Buffer)) < base+4 {
-					panic(wasm.ErrRuntimeOutOfBoundsMemoryAccess)
+					panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
 				}
 				binary.LittleEndian.PutUint32(memoryInst.Buffer[base:], val)
 				frame.pc++
@@ -1105,61 +1103,76 @@ func (ce *callEngine) callNativeFunc(ctx *wasm.ModuleContext, f *compiledFunctio
 			}
 		case wazeroir.OperationKindDiv:
 			{
-				switch wazeroir.SignedType(op.b1) {
+				// If an integer, check we won't divide by zero.
+				t := wazeroir.SignedType(op.b1)
+				v2, v1 := ce.pop(), ce.pop()
+				switch t {
+				case wazeroir.SignedTypeFloat32, wazeroir.SignedTypeFloat64: // not integers
+				default:
+					if v2 == 0 {
+						panic(wasmruntime.ErrRuntimeIntegerDivideByZero)
+					}
+				}
+
+				switch t {
 				case wazeroir.SignedTypeInt32:
-					v2 := int32(ce.pop())
-					v1 := int32(ce.pop())
-					if v1 == math.MinInt32 && v2 == -1 {
-						panic(wasm.ErrRuntimeIntegerOverflow)
+					d := int32(v2)
+					n := int32(v1)
+					if n == math.MinInt32 && d == -1 {
+						panic(wasmruntime.ErrRuntimeIntegerOverflow)
 					}
-					ce.push(uint64(uint32(v1 / v2)))
+					ce.push(uint64(uint32(n / d)))
 				case wazeroir.SignedTypeInt64:
-					v2 := int64(ce.pop())
-					v1 := int64(ce.pop())
-					if v1 == math.MinInt64 && v2 == -1 {
-						panic(wasm.ErrRuntimeIntegerOverflow)
+					d := int64(v2)
+					n := int64(v1)
+					if n == math.MinInt64 && d == -1 {
+						panic(wasmruntime.ErrRuntimeIntegerOverflow)
 					}
-					ce.push(uint64(v1 / v2))
+					ce.push(uint64(n / d))
 				case wazeroir.SignedTypeUint32:
-					v2 := uint32(ce.pop())
-					v1 := uint32(ce.pop())
-					ce.push(uint64(v1 / v2))
+					d := uint32(v2)
+					n := uint32(v1)
+					ce.push(uint64(n / d))
 				case wazeroir.SignedTypeUint64:
-					v2 := ce.pop()
-					v1 := ce.pop()
-					ce.push(v1 / v2)
+					d := v2
+					n := v1
+					ce.push(n / d)
 				case wazeroir.SignedTypeFloat32:
-					v2 := ce.pop()
-					v1 := ce.pop()
-					v := math.Float32frombits(uint32(v1)) / math.Float32frombits(uint32(v2))
+					d := v2
+					n := v1
+					v := math.Float32frombits(uint32(n)) / math.Float32frombits(uint32(d))
 					ce.push(uint64(math.Float32bits(v)))
 				case wazeroir.SignedTypeFloat64:
-					v2 := ce.pop()
-					v1 := ce.pop()
-					v := math.Float64frombits(v1) / math.Float64frombits(v2)
-					ce.push(uint64(math.Float64bits(v)))
+					d := v2
+					n := v1
+					v := math.Float64frombits(n) / math.Float64frombits(d)
+					ce.push(math.Float64bits(v))
 				}
 				frame.pc++
 			}
 		case wazeroir.OperationKindRem:
 			{
+				v2, v1 := ce.pop(), ce.pop()
+				if v2 == 0 {
+					panic(wasmruntime.ErrRuntimeIntegerDivideByZero)
+				}
 				switch wazeroir.SignedInt(op.b1) {
 				case wazeroir.SignedInt32:
-					v2 := int32(ce.pop())
-					v1 := int32(ce.pop())
-					ce.push(uint64(uint32(v1 % v2)))
+					d := int32(v2)
+					n := int32(v1)
+					ce.push(uint64(uint32(n % d)))
 				case wazeroir.SignedInt64:
-					v2 := int64(ce.pop())
-					v1 := int64(ce.pop())
-					ce.push(uint64(v1 % v2))
+					d := int64(v2)
+					n := int64(v1)
+					ce.push(uint64(n % d))
 				case wazeroir.SignedUint32:
-					v2 := uint32(ce.pop())
-					v1 := uint32(ce.pop())
-					ce.push(uint64(v1 % v2))
+					d := uint32(v2)
+					n := uint32(v1)
+					ce.push(uint64(n % d))
 				case wazeroir.SignedUint64:
-					v2 := ce.pop()
-					v1 := ce.pop()
-					ce.push(v1 % v2)
+					d := v2
+					n := v1
+					ce.push(n % d)
 				}
 				frame.pc++
 			}
@@ -1408,39 +1421,39 @@ func (ce *callEngine) callNativeFunc(ctx *wasm.ModuleContext, f *compiledFunctio
 					case wazeroir.SignedInt32:
 						v := math.Trunc(float64(math.Float32frombits(uint32(ce.pop()))))
 						if math.IsNaN(v) { // NaN cannot be compared with themselves, so we have to use IsNaN
-							panic(wasm.ErrRuntimeInvalidConversionToInteger)
+							panic(wasmruntime.ErrRuntimeInvalidConversionToInteger)
 						} else if v < math.MinInt32 || v > math.MaxInt32 {
-							panic(wasm.ErrRuntimeIntegerOverflow)
+							panic(wasmruntime.ErrRuntimeIntegerOverflow)
 						}
 						ce.push(uint64(int32(v)))
 					case wazeroir.SignedInt64:
 						v := math.Trunc(float64(math.Float32frombits(uint32(ce.pop()))))
 						res := int64(v)
 						if math.IsNaN(v) { // NaN cannot be compared with themselves, so we have to use IsNaN
-							panic(wasm.ErrRuntimeInvalidConversionToInteger)
+							panic(wasmruntime.ErrRuntimeInvalidConversionToInteger)
 						} else if v < math.MinInt64 || v >= math.MaxInt64 {
 							// Note: math.MaxInt64 is rounded up to math.MaxInt64+1 in 64-bit float representation,
 							// and that's why we use '>=' not '>' to check overflow.
-							panic(wasm.ErrRuntimeIntegerOverflow)
+							panic(wasmruntime.ErrRuntimeIntegerOverflow)
 						}
 						ce.push(uint64(res))
 					case wazeroir.SignedUint32:
 						v := math.Trunc(float64(math.Float32frombits(uint32(ce.pop()))))
 						if math.IsNaN(v) { // NaN cannot be compared with themselves, so we have to use IsNaN
-							panic(wasm.ErrRuntimeInvalidConversionToInteger)
+							panic(wasmruntime.ErrRuntimeInvalidConversionToInteger)
 						} else if v < 0 || v > math.MaxUint32 {
-							panic(wasm.ErrRuntimeIntegerOverflow)
+							panic(wasmruntime.ErrRuntimeIntegerOverflow)
 						}
 						ce.push(uint64(uint32(v)))
 					case wazeroir.SignedUint64:
 						v := math.Trunc(float64(math.Float32frombits(uint32(ce.pop()))))
 						res := uint64(v)
 						if math.IsNaN(v) { // NaN cannot be compared with themselves, so we have to use IsNaN
-							panic(wasm.ErrRuntimeInvalidConversionToInteger)
+							panic(wasmruntime.ErrRuntimeInvalidConversionToInteger)
 						} else if v < 0 || v >= math.MaxUint64 {
 							// Note: math.MaxUint64 is rounded up to math.MaxUint64+1 in 64-bit float representation,
 							// and that's why we use '>=' not '>' to check overflow.
-							panic(wasm.ErrRuntimeIntegerOverflow)
+							panic(wasmruntime.ErrRuntimeIntegerOverflow)
 						}
 						ce.push(res)
 					}
@@ -1450,39 +1463,39 @@ func (ce *callEngine) callNativeFunc(ctx *wasm.ModuleContext, f *compiledFunctio
 					case wazeroir.SignedInt32:
 						v := math.Trunc(math.Float64frombits(ce.pop()))
 						if math.IsNaN(v) { // NaN cannot be compared with themselves, so we have to use IsNaN
-							panic(wasm.ErrRuntimeInvalidConversionToInteger)
+							panic(wasmruntime.ErrRuntimeInvalidConversionToInteger)
 						} else if v < math.MinInt32 || v > math.MaxInt32 {
-							panic(wasm.ErrRuntimeIntegerOverflow)
+							panic(wasmruntime.ErrRuntimeIntegerOverflow)
 						}
 						ce.push(uint64(int32(v)))
 					case wazeroir.SignedInt64:
 						v := math.Trunc(math.Float64frombits(ce.pop()))
 						res := int64(v)
 						if math.IsNaN(v) { // NaN cannot be compared with themselves, so we have to use IsNaN
-							panic(wasm.ErrRuntimeInvalidConversionToInteger)
+							panic(wasmruntime.ErrRuntimeInvalidConversionToInteger)
 						} else if v < math.MinInt64 || v >= math.MaxInt64 {
 							// Note: math.MaxInt64 is rounded up to math.MaxInt64+1 in 64-bit float representation,
 							// and that's why we use '>=' not '>' to check overflow.
-							panic(wasm.ErrRuntimeIntegerOverflow)
+							panic(wasmruntime.ErrRuntimeIntegerOverflow)
 						}
 						ce.push(uint64(res))
 					case wazeroir.SignedUint32:
 						v := math.Trunc(math.Float64frombits(ce.pop()))
 						if math.IsNaN(v) { // NaN cannot be compared with themselves, so we have to use IsNaN
-							panic(wasm.ErrRuntimeInvalidConversionToInteger)
+							panic(wasmruntime.ErrRuntimeInvalidConversionToInteger)
 						} else if v < 0 || v > math.MaxUint32 {
-							panic(wasm.ErrRuntimeIntegerOverflow)
+							panic(wasmruntime.ErrRuntimeIntegerOverflow)
 						}
 						ce.push(uint64(uint32(v)))
 					case wazeroir.SignedUint64:
 						v := math.Trunc(math.Float64frombits(ce.pop()))
 						res := uint64(v)
 						if math.IsNaN(v) { // NaN cannot be compared with themselves, so we have to use IsNaN
-							panic(wasm.ErrRuntimeInvalidConversionToInteger)
+							panic(wasmruntime.ErrRuntimeInvalidConversionToInteger)
 						} else if v < 0 || v >= math.MaxUint64 {
 							// Note: math.MaxUint64 is rounded up to math.MaxUint64+1 in 64-bit float representation,
 							// and that's why we use '>=' not '>' to check overflow.
-							panic(wasm.ErrRuntimeIntegerOverflow)
+							panic(wasmruntime.ErrRuntimeIntegerOverflow)
 						}
 						ce.push(res)
 					}
diff --git a/internal/wasm/jit/engine.go b/internal/wasm/jit/engine.go
index 7a5058cf..a3f0f5d7 100644
--- a/internal/wasm/jit/engine.go
+++ b/internal/wasm/jit/engine.go
@@ -11,8 +11,9 @@ import (
 	"sync/atomic"
 	"unsafe"
 
+	"github.com/tetratelabs/wazero/internal/buildoptions"
 	"github.com/tetratelabs/wazero/internal/wasm"
-	"github.com/tetratelabs/wazero/internal/wasm/buildoptions"
+	"github.com/tetratelabs/wazero/internal/wasmruntime"
 	"github.com/tetratelabs/wazero/internal/wazeroir"
 	"github.com/tetratelabs/wazero/sys"
 )
@@ -299,19 +300,19 @@ func (s jitCallStatusCode) causePanic() {
 	var err error
 	switch s {
 	case jitCallStatusIntegerOverflow:
-		err = wasm.ErrRuntimeIntegerOverflow
+		err = wasmruntime.ErrRuntimeIntegerOverflow
 	case jitCallStatusIntegerDivisionByZero:
-		err = wasm.ErrRuntimeIntegerDivideByZero
+		err = wasmruntime.ErrRuntimeIntegerDivideByZero
 	case jitCallStatusCodeInvalidFloatToIntConversion:
-		err = wasm.ErrRuntimeInvalidConversionToInteger
+		err = wasmruntime.ErrRuntimeInvalidConversionToInteger
 	case jitCallStatusCodeUnreachable:
-		err = wasm.ErrRuntimeUnreachable
+		err = wasmruntime.ErrRuntimeUnreachable
 	case jitCallStatusCodeMemoryOutOfBounds:
-		err = wasm.ErrRuntimeOutOfBoundsMemoryAccess
+		err = wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess
 	case jitCallStatusCodeInvalidTableAccess:
-		err = wasm.ErrRuntimeInvalidTableAccess
+		err = wasmruntime.ErrRuntimeInvalidTableAccess
 	case jitCallStatusCodeTypeMismatchOnIndirectCall:
-		err = wasm.ErrRuntimeIndirectCallTypeMismatch
+		err = wasmruntime.ErrRuntimeIndirectCallTypeMismatch
 	}
 	panic(err)
 }
@@ -334,7 +335,7 @@ func (s jitCallStatusCode) String() (ret string) {
 func (c *callFrame) String() string {
 	return fmt.Sprintf(
 		"[%s: return address=0x%x, return stack base pointer=%d]",
-		c.compiledFunction.source.Name, c.returnAddress, c.returnStackBasePointer,
+		c.compiledFunction.source.DebugName, c.returnAddress, c.returnStackBasePointer,
 	)
 }
 
@@ -368,7 +369,7 @@ func (e *engine) NewModuleEngine(name string, importedFunctions, moduleFunctions
 	for idx, f := range importedFunctions {
 		cf, ok := e.getCompiledFunction(f)
 		if !ok {
-			return nil, fmt.Errorf("import[%d] func[%s.%s]: uncompiled", idx, f.Module.Name, f.Name)
+			return nil, fmt.Errorf("import[%d] func[%s]: uncompiled", idx, f.DebugName)
 		}
 		me.compiledFunctions = append(me.compiledFunctions, cf)
 	}
@@ -495,9 +496,14 @@ func (me *moduleEngine) Call(ctx *wasm.ModuleContext, f *wasm.FunctionInstance,
 			}
 
 			var frames []string
+			// Handle edge-case where the host function is called directly by Go.
+			if ce.globalContext.callFrameStackPointer == 0 {
+				fn := compiled.source
+				frames = append(frames, fmt.Sprintf("\t%d: %s", 0, fn.DebugName))
+			}
 			for i := uint64(0); i < ce.globalContext.callFrameStackPointer; i++ {
 				f := ce.callFrameStack[ce.globalContext.callFrameStackPointer-1-i].compiledFunction
-				frames = append(frames, fmt.Sprintf("\t%d: %s", i, f.source.Name))
+				frames = append(frames, fmt.Sprintf("\t%d: %s", i, f.source.DebugName))
 				// TODO: include DWARF symbols. See #58
 			}
 
@@ -629,7 +635,8 @@ func (ce *callEngine) callFrameTop() *callFrame {
 }
 
 func (ce *callEngine) callFrameAt(depth uint64) *callFrame {
-	return &ce.callFrameStack[ce.globalContext.callFrameStackPointer-1-depth]
+	idx := ce.globalContext.callFrameStackPointer - 1 - depth
+	return &ce.callFrameStack[idx]
 }
 
 func (ce *callEngine) valueStackTopIndex() uint64 {
@@ -796,7 +803,7 @@ var callStackCeiling = uint64(buildoptions.CallStackCeiling)
 
 func (ce *callEngine) builtinFunctionGrowCallFrameStack() {
 	if callStackCeiling < uint64(len(ce.callFrameStack)+1) {
-		panic(wasm.ErrRuntimeCallStackOverflow)
+		panic(wasmruntime.ErrRuntimeCallStackOverflow)
 	}
 
 	// Double the callstack slice length.
diff --git a/internal/wasm/jit/jit_impl_amd64.go b/internal/wasm/jit/jit_impl_amd64.go
index f6558e9f..707a7672 100644
--- a/internal/wasm/jit/jit_impl_amd64.go
+++ b/internal/wasm/jit/jit_impl_amd64.go
@@ -14,8 +14,8 @@ import (
 
 	"github.com/tetratelabs/wazero/internal/asm"
 	amd64 "github.com/tetratelabs/wazero/internal/asm/amd64"
+	"github.com/tetratelabs/wazero/internal/buildoptions"
 	"github.com/tetratelabs/wazero/internal/wasm"
-	"github.com/tetratelabs/wazero/internal/wasm/buildoptions"
 	"github.com/tetratelabs/wazero/internal/wazeroir"
 )
 
diff --git a/internal/wasm/module.go b/internal/wasm/module.go
index b280c133..d3341761 100644
--- a/internal/wasm/module.go
+++ b/internal/wasm/module.go
@@ -465,7 +465,7 @@ func (m *Module) buildFunctions() (functions []*FunctionInstance) {
 	importCount := m.ImportFuncCount()
 	n, nLen := 0, len(functionNames)
 	for codeIndex, typeIndex := range m.FunctionSection {
-		funcIdx := importCount + uint32(len(functions))
+		funcIdx := importCount + uint32(codeIndex)
 		// Seek to see if there's a better name than "unknown"
 		name := "unknown"
 		for ; n < nLen; n++ {
@@ -479,7 +479,7 @@ func (m *Module) buildFunctions() (functions []*FunctionInstance) {
 		}
 
 		f := &FunctionInstance{
-			Name:       name,
+			DebugName:  name,
 			Kind:       FunctionKindWasm,
 			Type:       m.TypeSection[typeIndex],
 			Body:       m.CodeSection[codeIndex].Body,
diff --git a/internal/wasm/store.go b/internal/wasm/store.go
index 323a5a6d..d34af01a 100644
--- a/internal/wasm/store.go
+++ b/internal/wasm/store.go
@@ -87,13 +87,8 @@ type (
 	// FunctionInstance represents a function instance in a Store.
 	// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#function-instances%E2%91%A0
 	FunctionInstance struct {
-		// Name is for debugging purpose, and is used to argument the stack traces.
-		//
-		// When GoFunc is not nil, this returns dot-delimited parameters given to
-		// Store.NewHostModule. Ex. something.realistic
-		//
-		// Otherwise, this is the corresponding value in NameSection.FunctionNames or "unknown" if unavailable.
-		Name string
+		// DebugName is for debugging purpose, and is used to augment the stack traces.
+		DebugName string
 
 		// Kind describes how this function should be called.
 		Kind FunctionKind
@@ -120,7 +115,7 @@ type (
 		// TypeID is assigned by a store for FunctionType.
 		TypeID FunctionTypeID
 
-		// Index holds the index of this function instance in Module.
+		// Index holds the index of this function instance in the function index namespace (beginning with imports).
 		Index Index
 	}
 
diff --git a/internal/wasm/errors.go b/internal/wasmruntime/errors.go
similarity index 51%
rename from internal/wasm/errors.go
rename to internal/wasmruntime/errors.go
index c0df2708..031777c4 100644
--- a/internal/wasm/errors.go
+++ b/internal/wasmruntime/errors.go
@@ -1,31 +1,44 @@
-package wasm
+// Package wasmruntime contains internal symbols shared between modules for error handling.
+// Note: This is named wasmruntime to avoid conflicts with the normal go module.
+// Note: This only imports "api" as importing "wasm" would create a cyclic dependency.
+package wasmruntime
 
-import "errors"
-
-// All the errors are returned by Engine during the execution of Wasm functions,
-// and they indicate that the Wasm virtual machine's state is unrecoverable.
 var (
 	// ErrRuntimeCallStackOverflow indicates that there are too many function calls,
 	// and the Engine terminated the execution.
-	ErrRuntimeCallStackOverflow = errors.New("callstack overflow")
+	ErrRuntimeCallStackOverflow = New("callstack overflow")
 	// ErrRuntimeInvalidConversionToInteger indicates the Wasm function tries to
 	// convert NaN floating point value to integers during trunc variant instructions.
-	ErrRuntimeInvalidConversionToInteger = errors.New("invalid conversion to integer")
+	ErrRuntimeInvalidConversionToInteger = New("invalid conversion to integer")
 	// ErrRuntimeIntegerOverflow indicates that an integer arithmetic resulted in
 	// overflow value. For example, when the program tried to truncate a float value
 	// which doesn't fit in the range of target integer.
-	ErrRuntimeIntegerOverflow = errors.New("integer overflow")
+	ErrRuntimeIntegerOverflow = New("integer overflow")
 	// ErrRuntimeIntegerDivideByZero indicates that an integer div or rem instructions
 	// was executed with 0 as the divisor.
-	ErrRuntimeIntegerDivideByZero = errors.New("integer divide by zero")
+	ErrRuntimeIntegerDivideByZero = New("integer divide by zero")
 	// ErrRuntimeUnreachable means "unreachable" instruction was executed by the program.
-	ErrRuntimeUnreachable = errors.New("unreachable")
+	ErrRuntimeUnreachable = New("unreachable")
 	// ErrRuntimeOutOfBoundsMemoryAccess indicates that the program tried to access the
 	// region beyond the linear memory.
-	ErrRuntimeOutOfBoundsMemoryAccess = errors.New("out of bounds memory access")
+	ErrRuntimeOutOfBoundsMemoryAccess = New("out of bounds memory access")
 	// ErrRuntimeInvalidTableAccess means either offset to the table was out of bounds of table, or
 	// the target element in the table was uninitialized during call_indirect instruction.
-	ErrRuntimeInvalidTableAccess = errors.New("invalid table access")
+	ErrRuntimeInvalidTableAccess = New("invalid table access")
 	// ErrRuntimeIndirectCallTypeMismatch indicates that the type check failed during call_indirect.
-	ErrRuntimeIndirectCallTypeMismatch = errors.New("indirect call type mismatch")
+	ErrRuntimeIndirectCallTypeMismatch = New("indirect call type mismatch")
 )
+
+// Error is returned by a wasm.Engine during the execution of Wasm functions, and they indicate that the Wasm runtime
+// state is unrecoverable.
+type Error struct {
+	s string
+}
+
+func New(text string) *Error {
+	return &Error{s: text}
+}
+
+func (e *Error) Error() string {
+	return e.s
+}
diff --git a/internal/wazeroir/compiler.go b/internal/wazeroir/compiler.go
index 89c3ed44..69dd900f 100644
--- a/internal/wazeroir/compiler.go
+++ b/internal/wazeroir/compiler.go
@@ -8,9 +8,9 @@ import (
 	"os"
 	"strings"
 
+	"github.com/tetratelabs/wazero/internal/buildoptions"
 	"github.com/tetratelabs/wazero/internal/leb128"
 	"github.com/tetratelabs/wazero/internal/wasm"
-	"github.com/tetratelabs/wazero/internal/wasm/buildoptions"
 )
 
 type controlFrameKind byte


Test patch:
diff --git a/internal/wasm/buildoptions/debug_disabled.go b/internal/buildoptions/debug_disabled.go
similarity index 100%
rename from internal/wasm/buildoptions/debug_disabled.go
rename to internal/buildoptions/debug_disabled.go
diff --git a/internal/wasm/buildoptions/debug_enabled.go b/internal/buildoptions/debug_enabled.go
similarity index 100%
rename from internal/wasm/buildoptions/debug_enabled.go
rename to internal/buildoptions/debug_enabled.go
diff --git a/internal/wasm/buildoptions/stackoverflow.go b/internal/buildoptions/stackoverflow.go
similarity index 100%
rename from internal/wasm/buildoptions/stackoverflow.go
rename to internal/buildoptions/stackoverflow.go
diff --git a/internal/testing/enginetest/enginetest.go b/internal/testing/enginetest/enginetest.go
index 29242c8a..a90c2fdd 100644
--- a/internal/testing/enginetest/enginetest.go
+++ b/internal/testing/enginetest/enginetest.go
@@ -18,6 +18,8 @@ package enginetest
 
 import (
 	"context"
+	"errors"
+	"math"
 	"reflect"
 	"testing"
 
@@ -49,7 +51,6 @@ func RunTestModuleEngine_Call(t *testing.T, et EngineTester) {
 	// Define a basic function which defines one parameter. This is used to test results when incorrect arity is used.
 	i64 := wasm.ValueTypeI64
 	fn := &wasm.FunctionInstance{
-		Name: "fn",
 		Kind: wasm.FunctionKindWasm,
 		Type: &wasm.FunctionType{Params: []wasm.ValueType{i64}, Results: []wasm.ValueType{i64}},
 		Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeEnd},
@@ -57,18 +58,16 @@ 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()}
-	addFunction(module, fn)
+	addFunction(module, "fn", fn)
 
 	// Compile the module
 	me, err := e.NewModuleEngine(module.Name, nil, module.Functions, nil, nil)
 	require.NoError(t, err)
 	defer closeModuleEngineWithExitCode(t, me, 0)
-
-	// Create a call context which links the module to the module-engine compiled from it.
-	ctx := newModuleContext(module, me)
+	linkModuleToEngine(module, me)
 
 	// Ensure the base case doesn't fail: A single parameter should work as that matches the function signature.
-	results, err := me.Call(ctx, fn, 3)
+	results, err := me.Call(module.Ctx, fn, 3)
 	require.NoError(t, err)
 	require.Equal(t, uint64(3), results[0])
 
@@ -105,10 +104,10 @@ func RunTestEngine_NewModuleEngine_InitTable(t *testing.T, et EngineTester) {
 		table := &wasm.TableInstance{Min: 2, Table: make([]interface{}, 2)}
 		var importedFunctions []*wasm.FunctionInstance
 		moduleFunctions := []*wasm.FunctionInstance{
-			{Name: "1", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
-			{Name: "2", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
-			{Name: "3", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
-			{Name: "4", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
+			{DebugName: "1", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
+			{DebugName: "2", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
+			{DebugName: "3", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
+			{DebugName: "4", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
 		}
 		tableInit := map[wasm.Index]wasm.Index{0: 2}
 
@@ -124,10 +123,10 @@ func RunTestEngine_NewModuleEngine_InitTable(t *testing.T, et EngineTester) {
 	t.Run("imported function", func(t *testing.T) {
 		table := &wasm.TableInstance{Min: 2, Table: make([]interface{}, 2)}
 		importedFunctions := []*wasm.FunctionInstance{
-			{Name: "1", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
-			{Name: "2", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
-			{Name: "3", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
-			{Name: "4", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
+			{DebugName: "1", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
+			{DebugName: "2", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
+			{DebugName: "3", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
+			{DebugName: "4", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
 		}
 		var moduleFunctions []*wasm.FunctionInstance
 		tableInit := map[wasm.Index]wasm.Index{0: 2}
@@ -149,16 +148,16 @@ func RunTestEngine_NewModuleEngine_InitTable(t *testing.T, et EngineTester) {
 	t.Run("mixed functions", func(t *testing.T) {
 		table := &wasm.TableInstance{Min: 2, Table: make([]interface{}, 2)}
 		importedFunctions := []*wasm.FunctionInstance{
-			{Name: "1", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
-			{Name: "2", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
-			{Name: "3", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
-			{Name: "4", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
+			{DebugName: "1", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
+			{DebugName: "2", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
+			{DebugName: "3", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
+			{DebugName: "4", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
 		}
 		moduleFunctions := []*wasm.FunctionInstance{
-			{Name: "1", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
-			{Name: "2", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
-			{Name: "3", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
-			{Name: "4", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
+			{DebugName: "1", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
+			{DebugName: "2", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
+			{DebugName: "3", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
+			{DebugName: "4", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
 		}
 		tableInit := map[wasm.Index]wasm.Index{0: 0, 1: 4}
 
@@ -177,7 +176,7 @@ func RunTestEngine_NewModuleEngine_InitTable(t *testing.T, et EngineTester) {
 	})
 }
 
-func RunTestModuleEngine_Call_HostFn(t *testing.T, et EngineTester) {
+func runTestModuleEngine_Call_HostFn_ModuleContext(t *testing.T, et EngineTester) {
 	memory := &wasm.MemoryInstance{}
 	var ctxMemory api.Memory
 	hostFn := reflect.ValueOf(func(ctx api.Module, v uint64) uint64 {
@@ -204,6 +203,7 @@ func RunTestModuleEngine_Call_HostFn(t *testing.T, et EngineTester) {
 
 	me, err := e.NewModuleEngine(t.Name(), nil, module.Functions, nil, nil)
 	require.NoError(t, err)
+	defer closeModuleEngineWithExitCode(t, me, 0)
 
 	t.Run("defaults to module memory when call stack empty", func(t *testing.T) {
 		// When calling a host func directly, there may be no stack. This ensures the module's memory is used.
@@ -212,36 +212,232 @@ func RunTestModuleEngine_Call_HostFn(t *testing.T, et EngineTester) {
 		require.Equal(t, uint64(3), results[0])
 		require.Same(t, memory, ctxMemory)
 	})
+}
 
-	t.Run("errs when not enough parameters", func(t *testing.T) {
-		_, err := me.Call(modCtx, f)
-		require.EqualError(t, err, "expected 1 params, but passed 0")
-	})
+func RunTestModuleEngine_Call_HostFn(t *testing.T, et EngineTester) {
+	runTestModuleEngine_Call_HostFn_ModuleContext(t, et) // TODO: refactor to use the same test interface.
 
-	t.Run("errs when too many parameters", func(t *testing.T) {
-		_, err := me.Call(modCtx, f, 1, 2)
-		require.EqualError(t, err, "expected 1 params, but passed 2")
-	})
+	e := et.NewEngine()
+
+	imported, importedMe := setupCallTests(t, e)
+	defer closeModuleEngineWithExitCode(t, importedMe, 0)
+
+	// Ensure the base case doesn't fail: A single parameter should work as that matches the function signature.
+	tests := []struct {
+		name string
+		me   wasm.ModuleEngine
+		fn   *wasm.FunctionInstance
+	}{
+		{
+			name: wasmFnName,
+			me:   importedMe,
+			fn:   imported.Exports[wasmFnName].Function,
+		},
+		{
+			name: hostFnName,
+			me:   importedMe,
+			fn:   imported.Exports[hostFnName].Function,
+		},
+		{
+			name: callHostFnName,
+			me:   importedMe,
+			fn:   imported.Exports[callHostFnName].Function,
+		},
+	}
+	for _, tt := range tests {
+		tc := tt
+
+		t.Run(tc.name, func(t *testing.T) {
+			f := tc.fn
+			m := f.Module
+			results, err := m.Engine.Call(m.Ctx, f, 1)
+			require.NoError(t, err)
+			require.Equal(t, uint64(1), results[0])
+		})
+	}
 }
 
-// addFunction assigns and adds a function to the module.
-func addFunction(module *wasm.ModuleInstance, fn *wasm.FunctionInstance) {
-	module.Functions = append(module.Functions, fn)
-	// This link is essential for all engines. For example, functions call other functions defined in the same module.
-	fn.Module = module
+func RunTestModuleEngine_Call_Errors(t *testing.T, et EngineTester) {
+	e := et.NewEngine()
+
+	imported, importedMe := setupCallTests(t, e)
+	defer closeModuleEngineWithExitCode(t, importedMe, 0)
+
+	tests := []struct {
+		name        string
+		me          wasm.ModuleEngine
+		fn          *wasm.FunctionInstance
+		input       []uint64
+		expectedErr string
+	}{
+		{
+			name:        "host function not enough parameters",
+			input:       []uint64{},
+			me:          importedMe,
+			fn:          imported.Exports[hostFnName].Function,
+			expectedErr: `expected 1 params, but passed 0`,
+		},
+		{
+			name:        "host function too many parameters",
+			input:       []uint64{1, 2},
+			me:          importedMe,
+			fn:          imported.Exports[hostFnName].Function,
+			expectedErr: `expected 1 params, but passed 2`,
+		},
+		{
+			name:        "wasm function not enough parameters",
+			input:       []uint64{},
+			me:          importedMe,
+			fn:          imported.Exports[wasmFnName].Function,
+			expectedErr: `expected 1 params, but passed 0`,
+		},
+		{
+			name:        "wasm function too many parameters",
+			input:       []uint64{1, 2},
+			me:          importedMe,
+			fn:          imported.Exports[wasmFnName].Function,
+			expectedErr: `expected 1 params, but passed 2`,
+		},
+		{
+			name:  "wasm function panics with wasmruntime.Error",
+			input: []uint64{0},
+			me:    importedMe,
+			fn:    imported.Exports[wasmFnName].Function,
+			expectedErr: `wasm runtime error: integer divide by zero
+wasm backtrace:
+	0: wasm_div_by`,
+		},
+		{
+			name:  "host function that panics",
+			input: []uint64{math.MaxUint32},
+			me:    importedMe,
+			fn:    imported.Exports[hostFnName].Function,
+			expectedErr: `wasm runtime error: host-function panic
+wasm backtrace:
+	0: host_div_by`,
+		},
+		{
+			name:  "host function panics with runtime.Error",
+			input: []uint64{0},
+			me:    importedMe,
+			fn:    imported.Exports[hostFnName].Function, // TODO: This should be a normal runtime error
+			expectedErr: `wasm runtime error: runtime error: integer divide by zero
+wasm backtrace:
+	0: host_div_by`,
+		},
+		{
+			name:  "wasm calls host function that panics",
+			input: []uint64{math.MaxUint32},
+			me:    importedMe,
+			fn:    imported.Exports[callHostFnName].Function,
+			expectedErr: `wasm runtime error: host-function panic
+wasm backtrace:
+	0: host_div_by
+	1: call->host_div_by`,
+		},
+		{
+			name:  "wasm calls imported wasm that calls host function panics with runtime.Error",
+			input: []uint64{0},
+			me:    importedMe,
+			fn:    imported.Exports[callHostFnName].Function,
+			expectedErr: `wasm runtime error: runtime error: integer divide by zero
+wasm backtrace:
+	0: host_div_by
+	1: call->host_div_by`,
+		},
+	}
+	for _, tt := range tests {
+		tc := tt
+		t.Run(tc.name, func(t *testing.T) {
+			f := tc.fn
+			m := f.Module
+			_, err := m.Engine.Call(m.Ctx, f, tc.input...)
+			require.EqualError(t, err, tc.expectedErr)
+
+			// Ensure the module still works
+			ret, err := m.Engine.Call(m.Ctx, f, 1)
+			require.NoError(t, err)
+			require.Equal(t, uint64(1), ret[0])
+		})
+	}
+}
+
+const (
+	wasmFnName     = "wasm_div_by"
+	hostFnName     = "host_div_by"
+	callHostFnName = "call->" + hostFnName
+)
+
+// (func (export "wasm_div_by") (param i32) (result i32) (i32.div_u (i32.const 1) (local.get 0)))
+var wasmFnBody = []byte{wasm.OpcodeI32Const, 1, wasm.OpcodeLocalGet, 0, wasm.OpcodeI32DivU, wasm.OpcodeEnd}
+
+func divBy(d uint32) uint32 {
+	if d == math.MaxUint32 {
+		panic(errors.New("host-function panic"))
+	}
+	return 1 / d // go panics if d == 0
+}
+
+func setupCallTests(t *testing.T, e wasm.Engine) (*wasm.ModuleInstance, wasm.ModuleEngine) {
+	i32 := wasm.ValueTypeI32
+	ft := &wasm.FunctionType{Params: []wasm.ValueType{i32}, Results: []wasm.ValueType{i32}}
+	wasmFn := &wasm.FunctionInstance{
+		Kind:  wasm.FunctionKindWasm,
+		Type:  ft,
+		Body:  wasmFnBody,
+		Index: 0,
+	}
+	hostFnVal := reflect.ValueOf(divBy)
+	hostFn := &wasm.FunctionInstance{
+		Kind:   wasm.FunctionKindGoNoContext,
+		Type:   ft,
+		GoFunc: &hostFnVal,
+		Index:  1,
+	}
+	callHostFn := &wasm.FunctionInstance{
+		Kind:  wasm.FunctionKindWasm,
+		Type:  ft,
+		Body:  []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeCall, byte(hostFn.Index), wasm.OpcodeEnd},
+		Index: 2,
+	}
+
+	// To use the function, we first need to add it to a module.
+	imported := &wasm.ModuleInstance{Name: t.Name()}
+	addFunction(imported, wasmFnName, wasmFn)
+	addFunction(imported, hostFnName, hostFn)
+	addFunction(imported, callHostFnName, callHostFn)
+
+	// Compile the imported module
+	importedMe, err := e.NewModuleEngine(imported.Name, nil, imported.Functions, nil, nil)
+	require.NoError(t, err)
+	linkModuleToEngine(imported, importedMe)
+
+	return imported, importedMe
 }
 
-// newModuleContext creates an wasm.ModuleContext for unit tests.
+// linkModuleToEngine assigns fields that wasm.Store would on instantiation. These includes fields both interpreter and
+// JIT needs as well as fields only needed by JIT.
 //
 // Note: This sets fields that are not needed in the interpreter, but are required by code compiled by JIT. If a new
 // test here passes in the interpreter and segmentation faults in JIT, check for a new field offset or a change in JIT
 // (ex. jit.TestVerifyOffsetValue). It is possible for all other tests to pass as that field is implicitly set by
 // wasm.Store: store isn't used here for unit test precision.
-func newModuleContext(module *wasm.ModuleInstance, engine wasm.ModuleEngine) *wasm.ModuleContext {
-	// moduleInstanceEngineOffset
-	module.Engine = engine
+func linkModuleToEngine(module *wasm.ModuleInstance, me wasm.ModuleEngine) {
+	module.Engine = me // for JIT, links the module to the module-engine compiled from it (moduleInstanceEngineOffset).
 	// callEngineModuleContextModuleInstanceAddressOffset
-	return wasm.NewModuleContext(context.Background(), nil, module, nil)
+	module.Ctx = wasm.NewModuleContext(context.Background(), nil, module, nil)
+}
+
+// addFunction assigns and adds a function to the module.
+func addFunction(module *wasm.ModuleInstance, funcName string, fn *wasm.FunctionInstance) {
+	fn.DebugName = funcName
+	module.Functions = append(module.Functions, fn)
+	if module.Exports == nil {
+		module.Exports = map[string]*wasm.ExportInstance{}
+	}
+	module.Exports[funcName] = &wasm.ExportInstance{Type: wasm.ExternTypeFunc, Function: fn}
+	// This link is essential for all engines. For example, functions call other functions defined in the same module.
+	fn.Module = module
 }
 
 // closeModuleEngineWithExitCode allows unit tests to check `CloseWithExitCode` didn't err.
diff --git a/internal/wasm/interpreter/interpreter_test.go b/internal/wasm/interpreter/interpreter_test.go
index 95bae339..4e6f2f55 100644
--- a/internal/wasm/interpreter/interpreter_test.go
+++ b/internal/wasm/interpreter/interpreter_test.go
@@ -8,13 +8,13 @@ import (
 
 	"github.com/stretchr/testify/require"
 
+	"github.com/tetratelabs/wazero/internal/buildoptions"
 	"github.com/tetratelabs/wazero/internal/testing/enginetest"
 	"github.com/tetratelabs/wazero/internal/wasm"
-	"github.com/tetratelabs/wazero/internal/wasm/buildoptions"
 	"github.com/tetratelabs/wazero/internal/wazeroir"
 )
 
-func TestCallEngine_PushFrame(t *testing.T) {
+func TestInterpreter_CallEngine_PushFrame(t *testing.T) {
 	f1 := &callFrame{}
 	f2 := &callFrame{}
 
@@ -28,7 +28,7 @@ func TestCallEngine_PushFrame(t *testing.T) {
 	require.Equal(t, []*callFrame{f1, f2}, vm.frames)
 }
 
-func TestCallEngine_PushFrame_StackOverflow(t *testing.T) {
+func TestInterpreter_CallEngine_PushFrame_StackOverflow(t *testing.T) {
 	defer func() { callStackCeiling = buildoptions.CallStackCeiling }()
 
 	callStackCeiling = 3
@@ -64,23 +64,27 @@ func (e engineTester) InitTable(me wasm.ModuleEngine, initTableLen uint32, initT
 	return table
 }
 
-func TestEngine_NewModuleEngine(t *testing.T) {
+func TestInterpreter_Engine_NewModuleEngine(t *testing.T) {
 	enginetest.RunTestEngine_NewModuleEngine(t, et)
 }
 
-func TestEngine_NewModuleEngine_InitTable(t *testing.T) {
+func TestInterpreter_Engine_NewModuleEngine_InitTable(t *testing.T) {
 	enginetest.RunTestEngine_NewModuleEngine_InitTable(t, et)
 }
 
-func TestModuleEngine_Call(t *testing.T) {
+func TestInterpreter_ModuleEngine_Call(t *testing.T) {
 	enginetest.RunTestModuleEngine_Call(t, et)
 }
 
-func TestModuleEngine_Call_HostFn(t *testing.T) {
+func TestInterpreter_ModuleEngine_Call_HostFn(t *testing.T) {
 	enginetest.RunTestModuleEngine_Call_HostFn(t, et)
 }
 
-func TestCallEngine_callNativeFunc_signExtend(t *testing.T) {
+func TestInterpreter_ModuleEngine_Call_Errors(t *testing.T) {
+	enginetest.RunTestModuleEngine_Call_Errors(t, et)
+}
+
+func TestInterpreter_CallEngine_callNativeFunc_signExtend(t *testing.T) {
 	translateToIROperationKind := func(op wasm.Opcode) (kind wazeroir.OperationKind) {
 		switch op {
 		case wasm.OpcodeI32Extend8S:
@@ -192,11 +196,11 @@ func TestCallEngine_callNativeFunc_signExtend(t *testing.T) {
 	})
 }
 
-func TestEngineCompile_Errors(t *testing.T) {
+func TestInterpreter_EngineCompile_Errors(t *testing.T) {
 	t.Run("invalid import", func(t *testing.T) {
 		e := et.NewEngine().(*engine)
 		_, err := e.NewModuleEngine(t.Name(),
-			[]*wasm.FunctionInstance{{Module: &wasm.ModuleInstance{Name: "uncompiled"}, Name: "fn"}},
+			[]*wasm.FunctionInstance{{Module: &wasm.ModuleInstance{Name: "uncompiled"}, DebugName: "uncompiled.fn"}},
 			nil, // moduleFunctions
 			nil, // table
 			nil, // tableInit
@@ -208,10 +212,10 @@ func TestEngineCompile_Errors(t *testing.T) {
 		e := et.NewEngine().(*engine)
 
 		importedFunctions := []*wasm.FunctionInstance{
-			{Name: "1", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
-			{Name: "2", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
-			{Name: "3", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
-			{Name: "4", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
+			{DebugName: "1", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
+			{DebugName: "2", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
+			{DebugName: "3", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
+			{DebugName: "4", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
 		}
 
 		// initialize the module-engine containing imported functions
@@ -221,9 +225,9 @@ func TestEngineCompile_Errors(t *testing.T) {
 		require.Len(t, e.compiledFunctions, len(importedFunctions))
 
 		moduleFunctions := []*wasm.FunctionInstance{
-			{Name: "ok1", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
-			{Name: "ok2", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
-			{Name: "invalid code", Type: &wasm.FunctionType{}, Body: []byte{
+			{DebugName: "ok1", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
+			{DebugName: "ok2", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
+			{DebugName: "invalid code", Type: &wasm.FunctionType{}, Body: []byte{
 				wasm.OpcodeCall, // Call instruction without immediate for call target index is invalid and should fail to compile.
 			}, Module: &wasm.ModuleInstance{}},
 		}
@@ -239,10 +243,10 @@ func TestEngineCompile_Errors(t *testing.T) {
 	})
 }
 
-func TestClose(t *testing.T) {
+func TestInterpreter_Close(t *testing.T) {
 	newFunctionInstance := func(id int) *wasm.FunctionInstance {
 		return &wasm.FunctionInstance{
-			Name: strconv.Itoa(id), Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}}
+			DebugName: strconv.Itoa(id), Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}}
 	}
 
 	for _, tc := range []struct {
diff --git a/internal/wasm/jit/engine_test.go b/internal/wasm/jit/engine_test.go
index 350d32b7..923adb27 100644
--- a/internal/wasm/jit/engine_test.go
+++ b/internal/wasm/jit/engine_test.go
@@ -16,7 +16,7 @@ import (
 )
 
 // Ensures that the offset consts do not drift when we manipulate the target structs.
-func TestVerifyOffsetValue(t *testing.T) {
+func TestJIT_VerifyOffsetValue(t *testing.T) {
 	var me moduleEngine
 	require.Equal(t, int(unsafe.Offsetof(me.compiledFunctions)), moduleEngineCompiledFunctionsOffset)
 
@@ -116,38 +116,43 @@ func (e *engineTester) InitTable(me wasm.ModuleEngine, initTableLen uint32, init
 	return table
 }
 
-func TestEngine_NewModuleEngine(t *testing.T) {
+func TestJIT_Engine_NewModuleEngine(t *testing.T) {
 	requireSupportedOSArch(t)
 	enginetest.RunTestEngine_NewModuleEngine(t, et)
 }
 
-func TestEngine_NewModuleEngine_InitTable(t *testing.T) {
+func TestJIT_Engine_NewModuleEngine_InitTable(t *testing.T) {
 	requireSupportedOSArch(t)
 	enginetest.RunTestEngine_NewModuleEngine_InitTable(t, et)
 }
 
-func TestModuleEngine_Call(t *testing.T) {
+func TestJIT_ModuleEngine_Call(t *testing.T) {
 	requireSupportedOSArch(t)
 	enginetest.RunTestModuleEngine_Call(t, et)
 }
 
-func TestModuleEngine_Call_HostFn(t *testing.T) {
+func TestJIT_ModuleEngine_Call_HostFn(t *testing.T) {
 	requireSupportedOSArch(t)
 	enginetest.RunTestModuleEngine_Call_HostFn(t, et)
 }
 
+func TestJIT_ModuleEngine_Call_Errors(t *testing.T) {
+	requireSupportedOSArch(t)
+	enginetest.RunTestModuleEngine_Call_Errors(t, et)
+}
+
 func requireSupportedOSArch(t *testing.T) {
 	if runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" {
 		t.Skip()
 	}
 }
 
-func TestEngineCompile_Errors(t *testing.T) {
+func TestJIT_EngineCompile_Errors(t *testing.T) {
 	t.Run("invalid import", func(t *testing.T) {
 		e := et.NewEngine()
 		_, err := e.NewModuleEngine(
 			t.Name(),
-			[]*wasm.FunctionInstance{{Module: &wasm.ModuleInstance{Name: "uncompiled"}, Name: "fn"}},
+			[]*wasm.FunctionInstance{{Module: &wasm.ModuleInstance{Name: "uncompiled"}, DebugName: "uncompiled.fn"}},
 			nil, // moduleFunctions
 			nil, // table
 			nil, // tableInit
@@ -159,10 +164,10 @@ func TestEngineCompile_Errors(t *testing.T) {
 		e := et.NewEngine().(*engine)
 
 		importedFunctions := []*wasm.FunctionInstance{
-			{Name: "1", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
-			{Name: "2", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
-			{Name: "3", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
-			{Name: "4", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
+			{DebugName: "1", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
+			{DebugName: "2", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
+			{DebugName: "3", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
+			{DebugName: "4", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
 		}
 		_, err := e.NewModuleEngine(t.Name(), nil, importedFunctions, nil, nil)
 		require.NoError(t, err)
@@ -170,9 +175,9 @@ func TestEngineCompile_Errors(t *testing.T) {
 		require.Len(t, e.compiledFunctions, len(importedFunctions))
 
 		moduleFunctions := []*wasm.FunctionInstance{
-			{Name: "ok1", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
-			{Name: "ok2", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
-			{Name: "invalid code", Type: &wasm.FunctionType{}, Body: []byte{
+			{DebugName: "ok1", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
+			{DebugName: "ok2", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
+			{DebugName: "invalid code", Type: &wasm.FunctionType{}, Body: []byte{
 				wasm.OpcodeCall, // Call instruction without immediate for call target index is invalid and should fail to compile.
 			}, Module: &wasm.ModuleInstance{}},
 		}
@@ -198,13 +203,13 @@ func (f fakeFinalizer) setFinalizer(obj interface{}, finalizer interface{}) {
 	f[cf] = finalizer.(func(*compiledFunction))
 }
 
-func TestNewModuleEngine_CompiledFunctions(t *testing.T) {
+func TestJIT_NewModuleEngine_CompiledFunctions(t *testing.T) {
 	newFunctionInstance := func(id int) *wasm.FunctionInstance {
 		return &wasm.FunctionInstance{
-			Name:   strconv.Itoa(id),
-			Type:   &wasm.FunctionType{},
-			Body:   []byte{wasm.OpcodeEnd},
-			Module: &wasm.ModuleInstance{},
+			DebugName: strconv.Itoa(id),
+			Type:      &wasm.FunctionType{},
+			Body:      []byte{wasm.OpcodeEnd},
+			Module:    &wasm.ModuleInstance{},
 		}
 	}
 
@@ -269,7 +274,7 @@ func TestNewModuleEngine_CompiledFunctions(t *testing.T) {
 }
 
 // TestReleaseCompiledFunction_Panic tests that an unexpected panic has some identifying information in it.
-func TestReleaseCompiledFunction_Panic(t *testing.T) {
+func TestJIT_ReleaseCompiledFunction_Panic(t *testing.T) {
 	// capturePanic because there's no require.PanicsWithErrorPrefix
 	errMessage := capturePanic(func() {
 		releaseCompiledFunction(&compiledFunction{
@@ -277,7 +282,8 @@ func TestReleaseCompiledFunction_Panic(t *testing.T) {
 			source:      &wasm.FunctionInstance{Index: 2, Module: &wasm.ModuleInstance{Name: t.Name()}}, // for error string
 		})
 	})
-	require.Contains(t, errMessage.Error(), "jit: failed to munmap code segment for TestReleaseCompiledFunction_Panic.function[2]:")
+	require.Contains(t, errMessage.Error(),
+		fmt.Sprintf("jit: failed to munmap code segment for %[1]s.function[2]:", t.Name()))
 }
 
 // capturePanic returns an error recovered from a panic
@@ -293,10 +299,10 @@ func capturePanic(panics func()) (err error) {
 	return
 }
 
-func TestModuleEngine_Close(t *testing.T) {
+func TestJIT_ModuleEngine_Close(t *testing.T) {
 	newFunctionInstance := func(id int) *wasm.FunctionInstance {
 		return &wasm.FunctionInstance{
-			Name: strconv.Itoa(id), Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}}
+			DebugName: strconv.Itoa(id), Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}}
 	}
 
 	for _, tc := range []struct {
@@ -387,7 +393,7 @@ func TestModuleEngine_Close(t *testing.T) {
 // Ensures that value stack and call-frame stack are allocated on heap which
 // allows us to safely access to their data region from native code.
 // See comments on initialValueStackSize and initialCallFrameStackSize.
-func TestSliceAllocatedOnHeap(t *testing.T) {
+func TestJIT_SliceAllocatedOnHeap(t *testing.T) {
 	e := newEngine()
 	store := wasm.NewStore(e, wasm.Features20191205)
 
diff --git a/internal/wasm/module_test.go b/internal/wasm/module_test.go
index 9b28e5a3..6ca12278 100644
--- a/internal/wasm/module_test.go
+++ b/internal/wasm/module_test.go
@@ -692,6 +692,7 @@ func TestModule_buildGlobalInstances(t *testing.T) {
 func TestModule_buildFunctionInstances(t *testing.T) {
 	nopCode := &Code{nil, []byte{OpcodeEnd}}
 	m := Module{
+		TypeSection:   []*FunctionType{{}},
 		ImportSection: []*Import{{Type: ExternTypeFunc}},
 		NameSection: &NameSection{
 			FunctionNames: NameMap{
@@ -706,7 +707,7 @@ func TestModule_buildFunctionInstances(t *testing.T) {
 	actual := m.buildFunctions()
 	expectedNames := []string{"unknown", "two", "unknown", "four", "five"}
 	for i, f := range actual {
-		require.Equal(t, expectedNames[i], f.Name)
+		require.Equal(t, expectedNames[i], f.DebugName)
 		require.Equal(t, nopCode.Body, f.Body)
 	}
 }
diff --git a/tests/spectest/spec_test.go b/tests/spectest/spec_test.go
index bb36738b..ece5d5a9 100644
--- a/tests/spectest/spec_test.go
+++ b/tests/spectest/spec_test.go
@@ -17,6 +17,7 @@ import (
 	"github.com/tetratelabs/wazero/internal/wasm/binary"
 	"github.com/tetratelabs/wazero/internal/wasm/interpreter"
 	"github.com/tetratelabs/wazero/internal/wasm/jit"
+	"github.com/tetratelabs/wazero/internal/wasmruntime"
 )
 
 //go:embed testdata/*.wasm
@@ -166,29 +167,29 @@ func (c commandActionVal) toUint64() uint64 {
 }
 
 // expectedError returns the expected runtime error when the command type equals assert_trap
-// which expectes engines to emit the errors corresponding command.Text field.
+// which expects engines to emit the errors corresponding command.Text field.
 func (c command) expectedError() (err error) {
 	if c.CommandType != "assert_trap" {
 		panic("unreachable")
 	}
 	switch c.Text {
 	case "out of bounds memory access":
-		err = wasm.ErrRuntimeOutOfBoundsMemoryAccess
+		err = wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess
 	case "indirect call type mismatch", "indirect call":
-		err = wasm.ErrRuntimeIndirectCallTypeMismatch
+		err = wasmruntime.ErrRuntimeIndirectCallTypeMismatch
 	case "undefined element", "undefined":
-		err = wasm.ErrRuntimeInvalidTableAccess
+		err = wasmruntime.ErrRuntimeInvalidTableAccess
 	case "integer overflow":
-		err = wasm.ErrRuntimeIntegerOverflow
+		err = wasmruntime.ErrRuntimeIntegerOverflow
 	case "invalid conversion to integer":
-		err = wasm.ErrRuntimeInvalidConversionToInteger
+		err = wasmruntime.ErrRuntimeInvalidConversionToInteger
 	case "integer divide by zero":
-		err = wasm.ErrRuntimeIntegerDivideByZero
+		err = wasmruntime.ErrRuntimeIntegerDivideByZero
 	case "unreachable":
-		err = wasm.ErrRuntimeUnreachable
+		err = wasmruntime.ErrRuntimeUnreachable
 	default:
 		if strings.HasPrefix(c.Text, "uninitialized") {
-			err = wasm.ErrRuntimeInvalidTableAccess
+			err = wasmruntime.ErrRuntimeInvalidTableAccess
 		}
 	}
 	return
@@ -421,7 +422,7 @@ func runTest(t *testing.T, newEngine func() wasm.Engine) {
 								msg += " in module " + c.Action.Module
 							}
 							_, _, err := callFunction(store, moduleName, c.Action.Field, args...)
-							require.ErrorIs(t, err, wasm.ErrRuntimeCallStackOverflow, msg)
+							require.ErrorIs(t, err, wasmruntime.ErrRuntimeCallStackOverflow, msg)
 						default:
 							t.Fatalf("unsupported action type type: %v", c)
 						}