Repository: tetratelabs/wazero
Pull request #1632 (2023-08-16 02:45:58)

wazevo: adds support for call_indirect
#1496 


Patch:
diff --git a/internal/engine/wazevo/backend/compiler.go b/internal/engine/wazevo/backend/compiler.go
index 2c927a58..0e752a81 100644
--- a/internal/engine/wazevo/backend/compiler.go
+++ b/internal/engine/wazevo/backend/compiler.go
@@ -187,10 +187,10 @@ func (c *compiler) assignVirtualRegisters() {
 
 	need := len(refCounts)
 	if need >= len(c.ssaValueToVRegs) {
-		c.ssaValueToVRegs = append(c.ssaValueToVRegs, make([]regalloc.VReg, need)...)
+		c.ssaValueToVRegs = append(c.ssaValueToVRegs, make([]regalloc.VReg, need+1)...)
 	}
 	if need >= len(c.ssaValueDefinitions) {
-		c.ssaValueDefinitions = append(c.ssaValueDefinitions, make([]SSAValueDefinition, need)...)
+		c.ssaValueDefinitions = append(c.ssaValueDefinitions, make([]SSAValueDefinition, need+1)...)
 	}
 
 	for blk := builder.BlockIteratorReversePostOrderBegin(); blk != nil; blk = builder.BlockIteratorReversePostOrderNext() {
diff --git a/internal/engine/wazevo/backend/isa/arm64/instr.go b/internal/engine/wazevo/backend/isa/arm64/instr.go
index cdf5b365..c318a442 100644
--- a/internal/engine/wazevo/backend/isa/arm64/instr.go
+++ b/internal/engine/wazevo/backend/isa/arm64/instr.go
@@ -898,7 +898,7 @@ func (i *instruction) String() (str string) {
 	case loadAddr:
 		panic("TODO")
 	case exitSequence:
-		str = fmt.Sprintf("exit_sequence %s", formatVRegSized(i.rn.nr(), 32))
+		str = fmt.Sprintf("exit_sequence %s", formatVRegSized(i.rn.nr(), 64))
 	case udf:
 		str = "udf"
 	default:
diff --git a/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go b/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go
index 59cd01eb..111df9e2 100644
--- a/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go
+++ b/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go
@@ -138,6 +138,17 @@ func (i *instruction) encode(c backend.Compiler) {
 			i.u3 == 1,
 			rn == sp,
 		))
+	case aluRRRShift:
+		r, amt, sop := i.rm.sr()
+		c.Emit4Bytes(encodeAluRRRShift(
+			aluOp(i.u1),
+			regNumberInEncoding[i.rd.realReg()],
+			regNumberInEncoding[i.rn.realReg()],
+			regNumberInEncoding[r.RealReg()],
+			uint32(amt),
+			sop,
+			i.u3 == 1,
+		))
 	case aluRRBitmaskImm:
 		c.Emit4Bytes(encodeAluBitmaskImmediate(
 			aluOp(i.u1),
@@ -560,6 +571,41 @@ func encodeAluRRImm12(op aluOp, rd, rn uint32, imm12 uint16, shiftBit byte, _64b
 	return _31to24<<24 | uint32(shiftBit)<<22 | uint32(imm12&0b111111111111)<<10 | rn<<5 | rd
 }
 
+// encodeAluRRR encodes as Data Processing (shifted register), depending on aluOp.
+// https://developer.arm.com/documentation/ddi0596/2020-12/Index-by-Encoding/Data-Processing----Register?lang=en#addsub_shift
+func encodeAluRRRShift(op aluOp, rd, rn, rm, amount uint32, shiftOp shiftOp, _64bit bool) uint32 {
+	var _31to24 uint32
+	switch op {
+	case aluOpAdd:
+		_31to24 = 0b00001011
+	case aluOpAddS:
+		_31to24 = 0b00101011
+	case aluOpSub:
+		_31to24 = 0b01001011
+	case aluOpSubS:
+		_31to24 = 0b01101011
+	default:
+		panic(op.String())
+	}
+
+	if _64bit {
+		_31to24 |= 0b1 << 7
+	}
+
+	var shift uint32
+	switch shiftOp {
+	case shiftOpLSL:
+		shift = 0b00
+	case shiftOpLSR:
+		shift = 0b01
+	case shiftOpASR:
+		shift = 0b10
+	default:
+		panic(shiftOp.String())
+	}
+	return _31to24<<24 | shift<<22 | rm<<16 | (amount << 10) | (rn << 5) | rd
+}
+
 // encodeAluRRR encodes as Data Processing (register), depending on aluOp.
 // https://developer.arm.com/documentation/ddi0596/2020-12/Index-by-Encoding/Data-Processing----Register?lang=en
 func encodeAluRRR(op aluOp, rd, rn, rm uint32, _64bit, isRnSp bool) uint32 {
diff --git a/internal/engine/wazevo/backend/isa/arm64/lower_instr.go b/internal/engine/wazevo/backend/isa/arm64/lower_instr.go
index 2398eaae..30d38ce9 100644
--- a/internal/engine/wazevo/backend/isa/arm64/lower_instr.go
+++ b/internal/engine/wazevo/backend/isa/arm64/lower_instr.go
@@ -130,9 +130,9 @@ func (m *machine) LowerInstr(instr *ssa.Instruction) {
 	case ssa.OpcodeExitWithCode:
 		execCtx, code := instr.ExitWithCodeData()
 		m.lowerExitWithCode(m.compiler.VRegOf(execCtx), code)
-	case ssa.OpcodeExitIfNotZeroWithCode:
-		execCtx, c, code := instr.ExitIfNotZeroWithCodeData()
-		m.lowerExitIfNotZeroWithCode(m.compiler.VRegOf(execCtx), c, code)
+	case ssa.OpcodeExitIfTrueWithCode:
+		execCtx, c, code := instr.ExitIfTrueWithCodeData()
+		m.lowerExitIfTrueWithCode(m.compiler.VRegOf(execCtx), c, code)
 	case ssa.OpcodeStore, ssa.OpcodeIstore8, ssa.OpcodeIstore16, ssa.OpcodeIstore32:
 		m.lowerStore(instr)
 	case ssa.OpcodeLoad:
@@ -341,11 +341,11 @@ func (m *machine) lowerExitWithCode(execCtxVReg regalloc.VReg, code wazevoapi.Ex
 	m.insert(exitSeq)
 }
 
-func (m *machine) lowerExitIfNotZeroWithCode(execCtxVReg regalloc.VReg, cond ssa.Value, code wazevoapi.ExitCode) {
+func (m *machine) lowerExitIfTrueWithCode(execCtxVReg regalloc.VReg, cond ssa.Value, code wazevoapi.ExitCode) {
 	condDef := m.compiler.ValueDefinition(cond)
 	if !m.compiler.MatchInstr(condDef, ssa.OpcodeIcmp) {
 		// We can have general case just like cachine.LowerConditionalBranch.
-		panic("TODO: OpcodeExitIfNotZeroWithCode must come after Icmp at the moment")
+		panic("TODO: OpcodeExitIfTrueWithCode must come after Icmp at the moment")
 	}
 	m.compiler.MarkLowered(condDef.Instr)
 
@@ -378,7 +378,7 @@ func (m *machine) lowerExitIfNotZeroWithCode(execCtxVReg regalloc.VReg, cond ssa
 
 	// We have to skip the entire exit sequence if the condition is false.
 	cbr := m.allocateInstr()
-	cbr.asCondBr(cc.asCond(), invalidLabel, false /* ignored */)
+	cbr.asCondBr(cc.invert().asCond(), invalidLabel, false /* ignored */)
 	cbr.condBrOffsetResolve(exitWithCodeEncodingSize + 4 /* br offset is from the beginning of this instruction */)
 	m.insert(cbr)
 	m.lowerExitWithCode(execCtxVReg, code)
diff --git a/internal/engine/wazevo/backend/isa/arm64/lower_instr_operands.go b/internal/engine/wazevo/backend/isa/arm64/lower_instr_operands.go
index a2607c4a..cbd82fb0 100644
--- a/internal/engine/wazevo/backend/isa/arm64/lower_instr_operands.go
+++ b/internal/engine/wazevo/backend/isa/arm64/lower_instr_operands.go
@@ -80,9 +80,6 @@ func (o operand) er() (r regalloc.VReg, eop extendOp, to byte) {
 
 // operandSR encodes the given VReg as an operand of operandKindSR.
 func operandSR(r regalloc.VReg, amt byte, sop shiftOp) operand {
-	if sop != shiftOpLSL {
-		panic("TODO: do we need to support other shift operations?")
-	}
 	return operand{kind: operandKindSR, data: uint64(r), data2: uint64(amt)<<32 | uint64(sop)}
 }
 
diff --git a/internal/engine/wazevo/call_engine.go b/internal/engine/wazevo/call_engine.go
index f5da650c..62a3ff71 100644
--- a/internal/engine/wazevo/call_engine.go
+++ b/internal/engine/wazevo/call_engine.go
@@ -162,6 +162,12 @@ func (c *callEngine) CallWithStack(ctx context.Context, paramResultStack []uint6
 			f.Call(ctx, mod, c.execCtx.goFunctionCallStack[:])
 			c.execCtx.exitCode = wazevoapi.ExitCodeOK
 			afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, c.execCtx.stackPointerBeforeGoCall)
+		case wazevoapi.ExitCodeTableOutOfBounds:
+			return wasmruntime.ErrRuntimeInvalidTableAccess
+		case wazevoapi.ExitCodeIndirectCallNullPointer:
+			return wasmruntime.ErrRuntimeInvalidTableAccess
+		case wazevoapi.ExitCodeIndirectCallTypeMismatch:
+			return wasmruntime.ErrRuntimeIndirectCallTypeMismatch
 		default:
 			panic("BUG")
 		}
diff --git a/internal/engine/wazevo/frontend/lower.go b/internal/engine/wazevo/frontend/lower.go
index 277ae1d1..b1284910 100644
--- a/internal/engine/wazevo/frontend/lower.go
+++ b/internal/engine/wazevo/frontend/lower.go
@@ -144,9 +144,13 @@ func (c *Compiler) lowerBody(entryBlk ssa.BasicBlock) {
 	}
 }
 
+func (c *Compiler) state() *loweringState {
+	return &c.loweringState
+}
+
 func (c *Compiler) lowerOpcode(op wasm.Opcode) {
 	builder := c.ssaBuilder
-	state := &c.loweringState
+	state := c.state()
 	switch op {
 	case wasm.OpcodeI32Const:
 		c := c.readI32s()
@@ -564,10 +568,10 @@ func (c *Compiler) lowerOpcode(op wasm.Opcode) {
 
 		// Check for out of bounds memory access: `memLen >= baseAddrPlusCeil`.
 		cmp := builder.AllocateInstruction()
-		cmp.AsIcmp(memLen, baseAddrPlusCeil.Return(), ssa.IntegerCmpCondUnsignedGreaterThanOrEqual)
+		cmp.AsIcmp(memLen, baseAddrPlusCeil.Return(), ssa.IntegerCmpCondUnsignedLessThan)
 		builder.InsertInstruction(cmp)
 		exitIfNZ := builder.AllocateInstruction()
-		exitIfNZ.AsExitIfNotZeroWithCode(c.execCtxPtrValue, cmp.Return(), wazevoapi.ExitCodeMemoryOutOfBounds)
+		exitIfNZ.AsExitIfTrueWithCode(c.execCtxPtrValue, cmp.Return(), wazevoapi.ExitCodeMemoryOutOfBounds)
 		builder.InsertInstruction(exitIfNZ)
 
 		// Load the value from memBase + extBaseAddr.
@@ -842,6 +846,14 @@ func (c *Compiler) lowerOpcode(op wasm.Opcode) {
 		builder.InsertInstruction(exit)
 		state.unreachable = true
 
+	case wasm.OpcodeCallIndirect:
+		typeIndex := c.readI32u()
+		tableIndex := c.readI32u()
+		if state.unreachable {
+			return
+		}
+		c.lowerCallIndirect(typeIndex, tableIndex)
+
 	case wasm.OpcodeCall:
 		fnIndex := c.readI32u()
 		if state.unreachable {
@@ -889,7 +901,7 @@ func (c *Compiler) lowerOpcode(op wasm.Opcode) {
 			// This case we have to read the address of the imported function from the module context.
 			moduleCtx := c.moduleCtxPtrValue
 			loadFuncPtr, loadModuleCtxPtr := builder.AllocateInstruction(), builder.AllocateInstruction()
-			funcPtrOffset, moduleCtxPtrOffset := c.offset.ImportedFunctionOffset(fnIndex)
+			funcPtrOffset, moduleCtxPtrOffset, _ := c.offset.ImportedFunctionOffset(fnIndex)
 			loadFuncPtr.AsLoad(moduleCtx, funcPtrOffset.U32(), ssa.TypeI64)
 			loadModuleCtxPtr.AsLoad(moduleCtx, moduleCtxPtrOffset.U32(), ssa.TypeI64)
 			builder.InsertInstruction(loadFuncPtr)
@@ -918,6 +930,135 @@ func (c *Compiler) lowerOpcode(op wasm.Opcode) {
 	}
 }
 
+const (
+	tableInstanceBaseAddressOffset = 0
+	tableInstanceLenOffset         = tableInstanceBaseAddressOffset + 8
+)
+
+func (c *Compiler) lowerCallIndirect(typeIndex, tableIndex uint32) {
+	builder := c.ssaBuilder
+	state := c.state()
+
+	targetOffsetInTable := state.pop()
+
+	// Load the table.
+	tableOffset := c.offset.TableOffset(int(tableIndex))
+	loadTableInstancePtr := builder.AllocateInstruction()
+	loadTableInstancePtr.AsLoad(c.moduleCtxPtrValue, tableOffset.U32(), ssa.TypeI64)
+	builder.InsertInstruction(loadTableInstancePtr)
+	tableInstancePtr := loadTableInstancePtr.Return()
+
+	// Load the table's length.
+	loadTableLen := builder.AllocateInstruction()
+	loadTableLen.AsLoad(tableInstancePtr, tableInstanceLenOffset, ssa.TypeI32)
+	builder.InsertInstruction(loadTableLen)
+	tableLen := loadTableLen.Return()
+
+	// Compare the length and the target, and trap if out of bounds.
+	checkOOB := builder.AllocateInstruction()
+	checkOOB.AsIcmp(targetOffsetInTable, tableLen, ssa.IntegerCmpCondUnsignedGreaterThanOrEqual)
+	builder.InsertInstruction(checkOOB)
+	exitIfOOB := builder.AllocateInstruction()
+	exitIfOOB.AsExitIfTrueWithCode(c.execCtxPtrValue, checkOOB.Return(), wazevoapi.ExitCodeTableOutOfBounds)
+	builder.InsertInstruction(exitIfOOB)
+
+	// Get the base address of wasm.TableInstance.References.
+	loadTableBaseAddress := builder.AllocateInstruction()
+	loadTableBaseAddress.AsLoad(tableInstancePtr, tableInstanceBaseAddressOffset, ssa.TypeI64)
+	builder.InsertInstruction(loadTableBaseAddress)
+	tableBase := loadTableBaseAddress.Return()
+
+	// Calculate the address of the target function. First we need to multiply targetOffsetInTable by 8 (pointer size).
+	multiplyBy8 := builder.AllocateInstruction()
+	three := builder.AllocateInstruction()
+	three.AsIconst64(3)
+	builder.InsertInstruction(three)
+	multiplyBy8.AsIshl(targetOffsetInTable, three.Return())
+	builder.InsertInstruction(multiplyBy8)
+	targetOffsetInTableMultipliedBy8 := multiplyBy8.Return()
+	// Then add the multiplied value to the base which results in the address of the target function (*wazevo.functionInstance)
+	calcFunctionInstancePtrAddressInTable := builder.AllocateInstruction()
+	calcFunctionInstancePtrAddressInTable.AsIadd(tableBase, targetOffsetInTableMultipliedBy8)
+	builder.InsertInstruction(calcFunctionInstancePtrAddressInTable)
+	functionInstancePtrAddress := calcFunctionInstancePtrAddressInTable.Return()
+	loadFunctionInstancePtr := builder.AllocateInstruction()
+	loadFunctionInstancePtr.AsLoad(functionInstancePtrAddress, 0, ssa.TypeI64)
+	builder.InsertInstruction(loadFunctionInstancePtr)
+	functionInstancePtr := loadFunctionInstancePtr.Return()
+
+	// Check if it is not the null pointer.
+	zero := builder.AllocateInstruction()
+	zero.AsIconst64(0)
+	builder.InsertInstruction(zero)
+	checkNull := builder.AllocateInstruction()
+	checkNull.AsIcmp(functionInstancePtr, zero.Return(), ssa.IntegerCmpCondEqual)
+	builder.InsertInstruction(checkNull)
+	exitIfNull := builder.AllocateInstruction()
+	exitIfNull.AsExitIfTrueWithCode(c.execCtxPtrValue, checkNull.Return(), wazevoapi.ExitCodeIndirectCallNullPointer)
+	builder.InsertInstruction(exitIfNull)
+
+	// We need to do the type check. First, load the target function instance's typeID.
+	loadTypeID := builder.AllocateInstruction()
+	loadTypeID.AsLoad(functionInstancePtr, wazevoapi.FunctionInstanceTypeIDOffset, ssa.TypeI32)
+	builder.InsertInstruction(loadTypeID)
+	actualTypeID := loadTypeID.Return()
+
+	// Next, we load the expected TypeID:
+	loadTypeIDsBegin := builder.AllocateInstruction()
+	loadTypeIDsBegin.AsLoad(c.moduleCtxPtrValue, c.offset.TypeIDs1stElement.U32(), ssa.TypeI64)
+	builder.InsertInstruction(loadTypeIDsBegin)
+	typeIDsBegin := loadTypeIDsBegin.Return()
+
+	loadExpectedTypeID := builder.AllocateInstruction()
+	loadExpectedTypeID.AsLoad(typeIDsBegin, uint32(typeIndex)*4 /* size of wasm.FunctionTypeID */, ssa.TypeI32)
+	builder.InsertInstruction(loadExpectedTypeID)
+	expectedTypeID := loadExpectedTypeID.Return()
+
+	// Check if the type ID matches.
+	checkTypeID := builder.AllocateInstruction()
+	checkTypeID.AsIcmp(actualTypeID, expectedTypeID, ssa.IntegerCmpCondNotEqual)
+	builder.InsertInstruction(checkTypeID)
+	exitIfNotMatch := builder.AllocateInstruction()
+	exitIfNotMatch.AsExitIfTrueWithCode(c.execCtxPtrValue, checkTypeID.Return(), wazevoapi.ExitCodeIndirectCallTypeMismatch)
+	builder.InsertInstruction(exitIfNotMatch)
+
+	// Now ready to call the function. Load the executable and moduleContextOpaquePtr from the function instance.
+	loadExecutablePtr := builder.AllocateInstruction()
+	loadExecutablePtr.AsLoad(functionInstancePtr, wazevoapi.FunctionInstanceExecutableOffset, ssa.TypeI64)
+	builder.InsertInstruction(loadExecutablePtr)
+	executablePtr := loadExecutablePtr.Return()
+	loadModuleContextOpaquePtr := builder.AllocateInstruction()
+	loadModuleContextOpaquePtr.AsLoad(functionInstancePtr, wazevoapi.FunctionInstanceModuleContextOpaquePtrOffset, ssa.TypeI64)
+	builder.InsertInstruction(loadModuleContextOpaquePtr)
+	moduleContextOpaquePtr := loadModuleContextOpaquePtr.Return()
+
+	// TODO: reuse slice?
+	typ := &c.m.TypeSection[typeIndex]
+	argN := len(typ.Params)
+	args := make([]ssa.Value, argN+2)
+	args[0] = c.execCtxPtrValue
+	args[1] = moduleContextOpaquePtr
+	state.nPopInto(argN, args[2:])
+
+	// Before transfer the control to the callee, we have to store the current module's moduleContextPtr
+	// into execContext.callerModuleContextPtr in case when the callee is a Go function.
+	c.storeCallerModuleContext()
+
+	call := builder.AllocateInstruction()
+	call.AsCallIndirect(executablePtr, c.signatures[typ], args)
+	builder.InsertInstruction(call)
+
+	first, rest := call.Returns()
+	if first.Valid() {
+		state.push(first)
+	}
+	for _, v := range rest {
+		state.push(v)
+	}
+
+	c.reloadAfterCall()
+}
+
 func (c *Compiler) reloadAfterCall() {
 	// Note that when these are not used in the following instructions, they will be optimized out.
 	// So in any ways, we define them!
@@ -1051,7 +1192,7 @@ func (c *Compiler) getMemoryLenValue(forceReload bool) ssa.Value {
 }
 
 func (c *Compiler) insertIcmp(cond ssa.IntegerCmpCond) {
-	state, builder := &c.loweringState, c.ssaBuilder
+	state, builder := c.state(), c.ssaBuilder
 	y, x := state.pop(), state.pop()
 	cmp := builder.AllocateInstruction()
 	cmp.AsIcmp(x, y, cond)
@@ -1061,7 +1202,7 @@ func (c *Compiler) insertIcmp(cond ssa.IntegerCmpCond) {
 }
 
 func (c *Compiler) insertFcmp(cond ssa.FloatCmpCond) {
-	state, builder := &c.loweringState, c.ssaBuilder
+	state, builder := c.state(), c.ssaBuilder
 	y, x := state.pop(), state.pop()
 	cmp := builder.AllocateInstruction()
 	cmp.AsFcmp(x, y, cond)
@@ -1120,7 +1261,7 @@ func (c *Compiler) readF64() float64 {
 
 // readBlockType reads the block type from the current position of the bytecode reader.
 func (c *Compiler) readBlockType() *wasm.FunctionType {
-	state := &c.loweringState
+	state := c.state()
 
 	c.br.Reset(c.wasmFunctionBody[state.pc+1:])
 	bt, num, err := wasm.DecodeBlockType(c.m.TypeSection, c.br, api.CoreFeaturesV2)
@@ -1133,7 +1274,7 @@ func (c *Compiler) readBlockType() *wasm.FunctionType {
 }
 
 func (c *Compiler) readMemArg() (align, offset uint32) {
-	state := &c.loweringState
+	state := c.state()
 
 	align, num, err := leb128.LoadUint32(c.wasmFunctionBody[state.pc+1:])
 	if err != nil {
@@ -1159,7 +1300,7 @@ func (c *Compiler) insertJumpToBlock(args []ssa.Value, targetBlk ssa.BasicBlock)
 }
 
 func (c *Compiler) insertIntegerExtend(signed bool, from, to byte) {
-	state := &c.loweringState
+	state := c.state()
 	builder := c.ssaBuilder
 	v := state.pop()
 	extend := builder.AllocateInstruction()
diff --git a/internal/engine/wazevo/module_engine.go b/internal/engine/wazevo/module_engine.go
index d0f97dca..1c4fc622 100644
--- a/internal/engine/wazevo/module_engine.go
+++ b/internal/engine/wazevo/module_engine.go
@@ -13,10 +13,17 @@ type (
 	// moduleEngine implements wasm.ModuleEngine.
 	moduleEngine struct {
 		// opaquePtr equals &opaque[0].
-		opaquePtr *byte
-		parent    *compiledModule
-		module    *wasm.ModuleInstance
-		opaque    moduleContextOpaque
+		opaquePtr              *byte
+		parent                 *compiledModule
+		module                 *wasm.ModuleInstance
+		opaque                 moduleContextOpaque
+		localFunctionInstances []*functionInstance
+	}
+
+	functionInstance struct {
+		executable             *byte
+		moduleContextOpaquePtr *byte
+		typeID                 wasm.FunctionTypeID
 	}
 
 	// moduleContextOpaque is the opaque byte slice of Module instance specific contents whose size
@@ -31,11 +38,10 @@ type (
 	// 	    localMemoryLength                         uint64               (optional)
 	// 	    importedMemoryInstance                    *wasm.MemoryInstance (optional)
 	// 	    importedMemoryOwnerOpaqueCtx              *byte                (optional)
-	// 	    importedFunctions [importedFunctions] struct { the total size depends on # of imported functions.
-	// 	        executable      *byte
-	// 	        opaqueCtx       *moduleContextOpaque
-	// 	    }
-	//      globals                                    []*wasm.GlobalInstance (optional)
+	// 	    importedFunctions                         [# of importedFunctions]functionInstance
+	//      globals                                   []*wasm.GlobalInstance (optional)
+	//      typeIDsBegin                              &wasm.ModuleInstance.TypeIDs[0]  (optional)
+	//      tables                                    []*wasm.TableInstance  (optional)
 	// 	    TODO: add more fields, like tables, etc.
 	// 	}
 	//
@@ -71,11 +77,21 @@ func (m *moduleEngine) setupOpaque() {
 
 	if globalOffset := offsets.GlobalsBegin; globalOffset >= 0 {
 		for _, g := range inst.Globals {
-			b := uint64(uintptr(unsafe.Pointer(g)))
-			binary.LittleEndian.PutUint64(opaque[globalOffset:], b)
+			binary.LittleEndian.PutUint64(opaque[globalOffset:], uint64(uintptr(unsafe.Pointer(g))))
 			globalOffset += 8
 		}
 	}
+
+	if tableOffset := offsets.TablesBegin; tableOffset >= 0 {
+		// First we write the first element's address of typeIDs.
+		binary.LittleEndian.PutUint64(opaque[offsets.TypeIDs1stElement:], uint64(uintptr(unsafe.Pointer(&inst.TypeIDs[0]))))
+
+		// Then we write the table addresses.
+		for _, table := range inst.Tables {
+			binary.LittleEndian.PutUint64(opaque[tableOffset:], uint64(uintptr(unsafe.Pointer(table))))
+			tableOffset += 8
+		}
+	}
 }
 
 // NewFunction implements wasm.ModuleEngine.
@@ -110,14 +126,38 @@ func (m *moduleEngine) NewFunction(index wasm.Index) api.Function {
 
 // ResolveImportedFunction implements wasm.ModuleEngine.
 func (m *moduleEngine) ResolveImportedFunction(index, indexInImportedModule wasm.Index, importedModuleEngine wasm.ModuleEngine) {
-	ptr, moduleCtx := m.parent.offsets.ImportedFunctionOffset(index)
+	executableOffset, moduleCtxOffset, typeIDOffset := m.parent.offsets.ImportedFunctionOffset(index)
 	importedME := importedModuleEngine.(*moduleEngine)
 
 	offset := importedME.parent.functionOffsets[indexInImportedModule]
+	typeID := getTypeIDOf(indexInImportedModule, importedME.module)
 	// When calling imported function from the machine code, we need to skip the Go preamble.
 	executable := &importedME.parent.executable[offset.offset+offset.goPreambleSize]
-	binary.LittleEndian.PutUint64(m.opaque[ptr:], uint64(uintptr(unsafe.Pointer(executable))))
-	binary.LittleEndian.PutUint64(m.opaque[moduleCtx:], uint64(uintptr(unsafe.Pointer(importedME.opaquePtr))))
+	// Write functionInstance.
+	binary.LittleEndian.PutUint64(m.opaque[executableOffset:], uint64(uintptr(unsafe.Pointer(executable))))
+	binary.LittleEndian.PutUint64(m.opaque[moduleCtxOffset:], uint64(uintptr(unsafe.Pointer(importedME.opaquePtr))))
+	binary.LittleEndian.PutUint64(m.opaque[typeIDOffset:], uint64(typeID))
+}
+
+func getTypeIDOf(funcIndex wasm.Index, m *wasm.ModuleInstance) wasm.FunctionTypeID {
+	source := m.Source
+
+	var typeIndex wasm.Index
+	if funcIndex >= source.ImportFunctionCount {
+		funcIndex -= source.ImportFunctionCount
+		typeIndex = source.FunctionSection[funcIndex]
+	} else {
+		var cnt wasm.Index
+		for i := range source.ImportSection {
+			if source.ImportSection[i].Type == wasm.ExternTypeFunc {
+				if cnt == funcIndex {
+					return m.TypeIDs[source.ImportSection[i].DescFunc]
+				}
+				cnt++
+			}
+		}
+	}
+	return m.TypeIDs[typeIndex]
 }
 
 // ResolveImportedMemory implements wasm.ModuleEngine.
@@ -143,10 +183,27 @@ func (m *moduleEngine) DoneInstantiation() {
 	}
 }
 
+// FunctionInstanceReference implements wasm.ModuleEngine.
+func (m *moduleEngine) FunctionInstanceReference(funcIndex wasm.Index) wasm.Reference {
+	if funcIndex < m.module.Source.ImportFunctionCount {
+		begin, _, _ := m.parent.offsets.ImportedFunctionOffset(funcIndex)
+		return uintptr(unsafe.Pointer(&m.opaque[begin]))
+	}
+
+	p := m.parent
+	executable := &p.executable[p.functionOffsets[funcIndex].offset]
+	typeID := m.module.TypeIDs[m.module.Source.FunctionSection[funcIndex]]
+
+	lf := &functionInstance{
+		executable:             executable,
+		moduleContextOpaquePtr: m.opaquePtr,
+		typeID:                 typeID,
+	}
+	m.localFunctionInstances = append(m.localFunctionInstances, lf)
+	return uintptr(unsafe.Pointer(lf))
+}
+
 // LookupFunction implements wasm.ModuleEngine.
 func (m *moduleEngine) LookupFunction(t *wasm.TableInstance, typeId wasm.FunctionTypeID, tableOffset wasm.Index) (api.Function, error) {
 	panic("TODO")
 }
-
-// FunctionInstanceReference implements wasm.ModuleEngine.
-func (m *moduleEngine) FunctionInstanceReference(funcIndex wasm.Index) wasm.Reference { panic("TODO") }
diff --git a/internal/engine/wazevo/ssa/instructions.go b/internal/engine/wazevo/ssa/instructions.go
index 405a11de..0ab771b6 100644
--- a/internal/engine/wazevo/ssa/instructions.go
+++ b/internal/engine/wazevo/ssa/instructions.go
@@ -136,8 +136,8 @@ const (
 	// OpcodeExitWithCode exit the execution immediately.
 	OpcodeExitWithCode
 
-	// OpcodeExitIfNotZeroWithCode exits the execution immediately if the value `c` is not zero.
-	OpcodeExitIfNotZeroWithCode
+	// OpcodeExitIfTrueWithCode exits the execution immediately if the value `c` is not zero.
+	OpcodeExitIfTrueWithCode
 
 	// OpcodeReturn returns from the function: `return rvalues`.
 	OpcodeReturn
@@ -830,42 +830,42 @@ const (
 // instructionSideEffects provides the info to determine if an instruction has side effects.
 // Instructions with side effects must not be eliminated regardless whether the result is used or not.
 var instructionSideEffects = [opcodeEnd]sideEffect{
-	OpcodeUndefined:             sideEffectTrue,
-	OpcodeJump:                  sideEffectTrue,
-	OpcodeIconst:                sideEffectFalse,
-	OpcodeCall:                  sideEffectTrue,
-	OpcodeCallIndirect:          sideEffectTrue,
-	OpcodeIadd:                  sideEffectFalse,
-	OpcodeImul:                  sideEffectFalse,
-	OpcodeIsub:                  sideEffectFalse,
-	OpcodeIcmp:                  sideEffectFalse,
-	OpcodeFcmp:                  sideEffectFalse,
-	OpcodeFadd:                  sideEffectFalse,
-	OpcodeLoad:                  sideEffectFalse,
-	OpcodeUload8:                sideEffectFalse,
-	OpcodeUload16:               sideEffectFalse,
-	OpcodeUload32:               sideEffectFalse,
-	OpcodeSload8:                sideEffectFalse,
-	OpcodeSload16:               sideEffectFalse,
-	OpcodeSload32:               sideEffectFalse,
-	OpcodeSExtend:               sideEffectFalse,
-	OpcodeUExtend:               sideEffectFalse,
-	OpcodeFsub:                  sideEffectFalse,
-	OpcodeF32const:              sideEffectFalse,
-	OpcodeF64const:              sideEffectFalse,
-	OpcodeIshl:                  sideEffectFalse,
-	OpcodeSshr:                  sideEffectFalse,
-	OpcodeUshr:                  sideEffectFalse,
-	OpcodeStore:                 sideEffectTrue,
-	OpcodeExitWithCode:          sideEffectTrue,
-	OpcodeExitIfNotZeroWithCode: sideEffectTrue,
-	OpcodeReturn:                sideEffectTrue,
-	OpcodeBrz:                   sideEffectTrue,
-	OpcodeBrnz:                  sideEffectTrue,
-	OpcodeFdiv:                  sideEffectFalse,
-	OpcodeFmul:                  sideEffectFalse,
-	OpcodeFmax:                  sideEffectFalse,
-	OpcodeFmin:                  sideEffectFalse,
+	OpcodeUndefined:          sideEffectTrue,
+	OpcodeJump:               sideEffectTrue,
+	OpcodeIconst:             sideEffectFalse,
+	OpcodeCall:               sideEffectTrue,
+	OpcodeCallIndirect:       sideEffectTrue,
+	OpcodeIadd:               sideEffectFalse,
+	OpcodeImul:               sideEffectFalse,
+	OpcodeIsub:               sideEffectFalse,
+	OpcodeIcmp:               sideEffectFalse,
+	OpcodeFcmp:               sideEffectFalse,
+	OpcodeFadd:               sideEffectFalse,
+	OpcodeLoad:               sideEffectFalse,
+	OpcodeUload8:             sideEffectFalse,
+	OpcodeUload16:            sideEffectFalse,
+	OpcodeUload32:            sideEffectFalse,
+	OpcodeSload8:             sideEffectFalse,
+	OpcodeSload16:            sideEffectFalse,
+	OpcodeSload32:            sideEffectFalse,
+	OpcodeSExtend:            sideEffectFalse,
+	OpcodeUExtend:            sideEffectFalse,
+	OpcodeFsub:               sideEffectFalse,
+	OpcodeF32const:           sideEffectFalse,
+	OpcodeF64const:           sideEffectFalse,
+	OpcodeIshl:               sideEffectFalse,
+	OpcodeSshr:               sideEffectFalse,
+	OpcodeUshr:               sideEffectFalse,
+	OpcodeStore:              sideEffectTrue,
+	OpcodeExitWithCode:       sideEffectTrue,
+	OpcodeExitIfTrueWithCode: sideEffectTrue,
+	OpcodeReturn:             sideEffectTrue,
+	OpcodeBrz:                sideEffectTrue,
+	OpcodeBrnz:               sideEffectTrue,
+	OpcodeFdiv:               sideEffectFalse,
+	OpcodeFmul:               sideEffectFalse,
+	OpcodeFmax:               sideEffectFalse,
+	OpcodeFmin:               sideEffectFalse,
 }
 
 // HasSideEffects returns true if this instruction has side effects.
@@ -919,32 +919,32 @@ var instructionReturnTypes = [opcodeEnd]returnTypesFn{
 		}
 		return
 	},
-	OpcodeLoad:                  returnTypesFnSingle,
-	OpcodeIadd:                  returnTypesFnSingle,
-	OpcodeIsub:                  returnTypesFnSingle,
-	OpcodeImul:                  returnTypesFnSingle,
-	OpcodeIcmp:                  returnTypesFnI32,
-	OpcodeFcmp:                  returnTypesFnI32,
-	OpcodeFadd:                  returnTypesFnSingle,
-	OpcodeFsub:                  returnTypesFnSingle,
-	OpcodeFdiv:                  returnTypesFnSingle,
-	OpcodeFmul:                  returnTypesFnSingle,
-	OpcodeFmax:                  returnTypesFnSingle,
-	OpcodeFmin:                  returnTypesFnSingle,
-	OpcodeF32const:              returnTypesFnF32,
-	OpcodeF64const:              returnTypesFnF64,
-	OpcodeStore:                 returnTypesFnNoReturns,
-	OpcodeExitWithCode:          returnTypesFnNoReturns,
-	OpcodeExitIfNotZeroWithCode: returnTypesFnNoReturns,
-	OpcodeReturn:                returnTypesFnNoReturns,
-	OpcodeBrz:                   returnTypesFnNoReturns,
-	OpcodeBrnz:                  returnTypesFnNoReturns,
-	OpcodeUload8:                returnTypesFnSingle,
-	OpcodeUload16:               returnTypesFnSingle,
-	OpcodeUload32:               returnTypesFnSingle,
-	OpcodeSload8:                returnTypesFnSingle,
-	OpcodeSload16:               returnTypesFnSingle,
-	OpcodeSload32:               returnTypesFnSingle,
+	OpcodeLoad:               returnTypesFnSingle,
+	OpcodeIadd:               returnTypesFnSingle,
+	OpcodeIsub:               returnTypesFnSingle,
+	OpcodeImul:               returnTypesFnSingle,
+	OpcodeIcmp:               returnTypesFnI32,
+	OpcodeFcmp:               returnTypesFnI32,
+	OpcodeFadd:               returnTypesFnSingle,
+	OpcodeFsub:               returnTypesFnSingle,
+	OpcodeFdiv:               returnTypesFnSingle,
+	OpcodeFmul:               returnTypesFnSingle,
+	OpcodeFmax:               returnTypesFnSingle,
+	OpcodeFmin:               returnTypesFnSingle,
+	OpcodeF32const:           returnTypesFnF32,
+	OpcodeF64const:           returnTypesFnF64,
+	OpcodeStore:              returnTypesFnNoReturns,
+	OpcodeExitWithCode:       returnTypesFnNoReturns,
+	OpcodeExitIfTrueWithCode: returnTypesFnNoReturns,
+	OpcodeReturn:             returnTypesFnNoReturns,
+	OpcodeBrz:                returnTypesFnNoReturns,
+	OpcodeBrnz:               returnTypesFnNoReturns,
+	OpcodeUload8:             returnTypesFnSingle,
+	OpcodeUload16:            returnTypesFnSingle,
+	OpcodeUload32:            returnTypesFnSingle,
+	OpcodeSload8:             returnTypesFnSingle,
+	OpcodeSload16:            returnTypesFnSingle,
+	OpcodeSload32:            returnTypesFnSingle,
 }
 
 // AsLoad initializes this instruction as a store instruction with OpcodeLoad.
@@ -1160,9 +1160,9 @@ func (i *Instruction) AsExitWithCode(ctx Value, code wazevoapi.ExitCode) {
 	i.u64 = uint64(code)
 }
 
-// AsExitIfNotZeroWithCode initializes this instruction as a trap instruction with OpcodeExitIfNotZeroWithCode.
-func (i *Instruction) AsExitIfNotZeroWithCode(ctx, c Value, code wazevoapi.ExitCode) {
-	i.opcode = OpcodeExitIfNotZeroWithCode
+// AsExitIfTrueWithCode initializes this instruction as a trap instruction with OpcodeExitIfTrueWithCode.
+func (i *Instruction) AsExitIfTrueWithCode(ctx, c Value, code wazevoapi.ExitCode) {
+	i.opcode = OpcodeExitIfTrueWithCode
 	i.v = ctx
 	i.v2 = c
 	i.u64 = uint64(code)
@@ -1173,8 +1173,8 @@ func (i *Instruction) ExitWithCodeData() (ctx Value, code wazevoapi.ExitCode) {
 	return i.v, wazevoapi.ExitCode(i.u64)
 }
 
-// ExitIfNotZeroWithCodeData returns the context and exit code of OpcodeExitWithCode.
-func (i *Instruction) ExitIfNotZeroWithCodeData() (ctx, c Value, code wazevoapi.ExitCode) {
+// ExitIfTrueWithCodeData returns the context and exit code of OpcodeExitWithCode.
+func (i *Instruction) ExitIfTrueWithCodeData() (ctx, c Value, code wazevoapi.ExitCode) {
 	return i.v, i.v2, wazevoapi.ExitCode(i.u64)
 }
 
@@ -1333,7 +1333,7 @@ func (i *Instruction) Format(b Builder) string {
 	switch i.opcode {
 	case OpcodeExitWithCode:
 		instSuffix = fmt.Sprintf(" %s, %s", i.v.Format(b), wazevoapi.ExitCode(i.u64))
-	case OpcodeExitIfNotZeroWithCode:
+	case OpcodeExitIfTrueWithCode:
 		instSuffix = fmt.Sprintf(" %s, %s, %s", i.v2.Format(b), i.v.Format(b), wazevoapi.ExitCode(i.u64))
 	case OpcodeIadd, OpcodeIsub, OpcodeImul, OpcodeFadd, OpcodeFsub, OpcodeFmin, OpcodeFmax, OpcodeFdiv, OpcodeFmul:
 		instSuffix = fmt.Sprintf(" %s, %s", i.v.Format(b), i.v2.Format(b))
@@ -1471,8 +1471,8 @@ func (o Opcode) String() (ret string) {
 		return "BrTable"
 	case OpcodeExitWithCode:
 		return "Exit"
-	case OpcodeExitIfNotZeroWithCode:
-		return "ExitIfNotZero"
+	case OpcodeExitIfTrueWithCode:
+		return "ExitIfTrue"
 	case OpcodeReturn:
 		return "Return"
 	case OpcodeCall:
diff --git a/internal/engine/wazevo/wazevoapi/exitcode.go b/internal/engine/wazevo/wazevoapi/exitcode.go
index 608650d4..0afdd8c0 100644
--- a/internal/engine/wazevo/wazevoapi/exitcode.go
+++ b/internal/engine/wazevo/wazevoapi/exitcode.go
@@ -13,7 +13,9 @@ const (
 	ExitCodeCallGoModuleFunction
 	// ExitCodeCallGoFunction is an exit code for a call to an api.GoFunction.
 	ExitCodeCallGoFunction
-
+	ExitCodeTableOutOfBounds
+	ExitCodeIndirectCallNullPointer
+	ExitCodeIndirectCallTypeMismatch
 	exitCodeMax
 )
 
@@ -34,6 +36,12 @@ func (e ExitCode) String() string {
 		return "unreachable"
 	case ExitCodeMemoryOutOfBounds:
 		return "memory_out_of_bounds"
+	case ExitCodeTableOutOfBounds:
+		return "table_out_of_bounds"
+	case ExitCodeIndirectCallNullPointer:
+		return "indirect_call_null_pointer"
+	case ExitCodeIndirectCallTypeMismatch:
+		return "indirect_call_type_mismatch"
 	}
 	panic("TODO")
 }
diff --git a/internal/engine/wazevo/wazevoapi/offsetdata.go b/internal/engine/wazevo/wazevoapi/offsetdata.go
index d9538970..c4737008 100644
--- a/internal/engine/wazevo/wazevoapi/offsetdata.go
+++ b/internal/engine/wazevo/wazevoapi/offsetdata.go
@@ -2,6 +2,17 @@ package wazevoapi
 
 import "github.com/tetratelabs/wazero/internal/wasm"
 
+const (
+	// FunctionInstanceSize is the size of wazevo.functionInstance.
+	FunctionInstanceSize = 24
+	// FunctionInstanceExecutableOffset is an offset of `executable` field in wazevo.functionInstance
+	FunctionInstanceExecutableOffset = 0
+	// FunctionInstanceModuleContextOpaquePtrOffset is an offset of `moduleContextOpaquePtr` field in wazevo.functionInstance
+	FunctionInstanceModuleContextOpaquePtrOffset = 8
+	// FunctionInstanceTypeIDOffset is an offset of `typeID` field in wazevo.functionInstance
+	FunctionInstanceTypeIDOffset = 16
+)
+
 var ExecutionContextOffsets = ExecutionContextOffsetData{
 	ExitCodeOffset:                          0,
 	CallerModuleContextPtr:                  8,
@@ -57,13 +68,18 @@ type ModuleContextOffsetData struct {
 	LocalMemoryBegin,
 	ImportedMemoryBegin,
 	ImportedFunctionsBegin,
-	GlobalsBegin Offset
+	GlobalsBegin,
+	TypeIDs1stElement,
+	TablesBegin Offset
 }
 
 // ImportedFunctionOffset returns an offset of the i-th imported function.
-func (m *ModuleContextOffsetData) ImportedFunctionOffset(i wasm.Index) (ptr, moduleCtx Offset) {
-	base := m.ImportedFunctionsBegin + Offset(i)*16
-	return base, base + 8
+// Each item is stored as wazevo.functionInstance whose size matches FunctionInstanceSize.
+func (m *ModuleContextOffsetData) ImportedFunctionOffset(i wasm.Index) (
+	executableOffset, moduleCtxOffset, typeIDOffset Offset,
+) {
+	base := m.ImportedFunctionsBegin + Offset(i)*FunctionInstanceSize
+	return base, base + 8, base + 16
 }
 
 // GlobalInstanceOffset returns an offset of the i-th global instance.
@@ -102,6 +118,11 @@ func (m *ModuleContextOffsetData) LocalMemoryLen() Offset {
 	return -1
 }
 
+// TableOffset returns an offset of the i-th table instance.
+func (m *ModuleContextOffsetData) TableOffset(tableIndex int) Offset {
+	return m.TablesBegin + Offset(tableIndex)*8
+}
+
 // NewModuleContextOffsetData creates a ModuleContextOffsetData determining the structure of moduleContextOpaque for the given Module.
 // The structure is described in the comment of wazevo.moduleContextOpaque.
 func NewModuleContextOffsetData(m *wasm.Module) ModuleContextOffsetData {
@@ -133,8 +154,8 @@ func NewModuleContextOffsetData(m *wasm.Module) ModuleContextOffsetData {
 
 	if m.ImportFunctionCount > 0 {
 		ret.ImportedFunctionsBegin = offset
-		// Each function consists of the pointer to the executable and the pointer to its moduleContextOpaque (16 bytes).
-		size := int(m.ImportFunctionCount) * 16
+		// Each function is stored wazevo.functionInstance.
+		size := int(m.ImportFunctionCount) * FunctionInstanceSize
 		offset += Offset(size)
 	} else {
 		ret.ImportedFunctionsBegin = -1
@@ -148,6 +169,18 @@ func NewModuleContextOffsetData(m *wasm.Module) ModuleContextOffsetData {
 		ret.GlobalsBegin = -1
 	}
 
+	if tables := len(m.TableSection) + int(m.ImportTableCount); tables > 0 {
+		ret.TypeIDs1stElement = offset
+		offset += 8 // First element of TypeIDs.
+
+		ret.TablesBegin = offset
+		// Pointers to *wasm.TableInstance.
+		offset += Offset(tables) * 8
+	} else {
+		ret.TypeIDs1stElement = -1
+		ret.TablesBegin = -1
+	}
+
 	ret.TotalSize = int(offset)
 	return ret
 }


Test patch:
diff --git a/internal/engine/wazevo/backend/backend_test.go b/internal/engine/wazevo/backend/backend_test.go
index 63f1ec4a..71ffa0d5 100644
--- a/internal/engine/wazevo/backend/backend_test.go
+++ b/internal/engine/wazevo/backend/backend_test.go
@@ -231,7 +231,7 @@ L3 (SSA Block: blk1):
 L2 (SSA Block: blk2):
 	movz x27, #0x3, LSL 0
 	str w27, [x0?]
-	exit_sequence w0?
+	exit_sequence x0?
 `,
 			afterFinalizeARM64: `
 L1 (SSA Block: blk0):
@@ -244,7 +244,7 @@ L3 (SSA Block: blk1):
 L2 (SSA Block: blk2):
 	movz x27, #0x3, LSL 0
 	str w27, [x0]
-	exit_sequence w0
+	exit_sequence x0
 `,
 		},
 		{
@@ -1891,7 +1891,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x0?]
-	exit_sequence w0?
+	exit_sequence x0?
 	ldr x8?, [x1?, #0x8]
 	add x11?, x8?, x4?
 	ldr w10?, [x11?]
@@ -1908,7 +1908,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x0]
-	exit_sequence w0
+	exit_sequence x0
 	ldr x9, [x1, #0x8]
 	add x8, x9, x8
 	ldr w0, [x8]
@@ -1930,7 +1930,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x0?]
-	exit_sequence w0?
+	exit_sequence x0?
 	ldr x8?, [x1?, #0x8]
 	add x200?, x8?, x4?
 	ldr w10?, [x200?]
@@ -1940,7 +1940,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x0?]
-	exit_sequence w0?
+	exit_sequence x0?
 	add x199?, x8?, x12?
 	ldr x16?, [x199?]
 	uxtw x18?, w2?
@@ -1949,7 +1949,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x0?]
-	exit_sequence w0?
+	exit_sequence x0?
 	add x198?, x8?, x18?
 	ldr s22?, [x198?]
 	uxtw x24?, w2?
@@ -1958,7 +1958,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x0?]
-	exit_sequence w0?
+	exit_sequence x0?
 	add x197?, x8?, x24?
 	ldr d28?, [x197?]
 	uxtw x30?, w2?
@@ -1967,7 +1967,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x0?]
-	exit_sequence w0?
+	exit_sequence x0?
 	add x196?, x8?, x30?
 	ldr w34?, [x196?, #0xf]
 	uxtw x36?, w2?
@@ -1976,7 +1976,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x0?]
-	exit_sequence w0?
+	exit_sequence x0?
 	add x195?, x8?, x36?
 	ldr x40?, [x195?, #0xf]
 	uxtw x42?, w2?
@@ -1985,7 +1985,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x0?]
-	exit_sequence w0?
+	exit_sequence x0?
 	add x194?, x8?, x42?
 	ldr s46?, [x194?, #0xf]
 	uxtw x48?, w2?
@@ -1994,7 +1994,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x0?]
-	exit_sequence w0?
+	exit_sequence x0?
 	add x193?, x8?, x48?
 	ldr d52?, [x193?, #0xf]
 	uxtw x54?, w2?
@@ -2003,7 +2003,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x0?]
-	exit_sequence w0?
+	exit_sequence x0?
 	add x192?, x8?, x54?
 	ldrsb w58?, [x192?]
 	uxtw x60?, w2?
@@ -2012,7 +2012,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x0?]
-	exit_sequence w0?
+	exit_sequence x0?
 	add x191?, x8?, x60?
 	ldrsb w64?, [x191?, #0xf]
 	uxtw x66?, w2?
@@ -2021,7 +2021,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x0?]
-	exit_sequence w0?
+	exit_sequence x0?
 	add x190?, x8?, x66?
 	ldrb w70?, [x190?]
 	uxtw x72?, w2?
@@ -2030,7 +2030,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x0?]
-	exit_sequence w0?
+	exit_sequence x0?
 	add x189?, x8?, x72?
 	ldrb w76?, [x189?, #0xf]
 	uxtw x78?, w2?
@@ -2039,7 +2039,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x0?]
-	exit_sequence w0?
+	exit_sequence x0?
 	add x188?, x8?, x78?
 	ldrsh w82?, [x188?]
 	uxtw x84?, w2?
@@ -2048,7 +2048,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x0?]
-	exit_sequence w0?
+	exit_sequence x0?
 	add x187?, x8?, x84?
 	ldrsh w88?, [x187?, #0xf]
 	uxtw x90?, w2?
@@ -2057,7 +2057,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x0?]
-	exit_sequence w0?
+	exit_sequence x0?
 	add x186?, x8?, x90?
 	ldrh w94?, [x186?]
 	uxtw x96?, w2?
@@ -2066,7 +2066,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x0?]
-	exit_sequence w0?
+	exit_sequence x0?
 	add x185?, x8?, x96?
 	ldrh w100?, [x185?, #0xf]
 	uxtw x102?, w2?
@@ -2075,7 +2075,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x0?]
-	exit_sequence w0?
+	exit_sequence x0?
 	add x184?, x8?, x102?
 	ldrsb w106?, [x184?]
 	uxtw x108?, w2?
@@ -2084,7 +2084,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x0?]
-	exit_sequence w0?
+	exit_sequence x0?
 	add x183?, x8?, x108?
 	ldrsb w112?, [x183?, #0xf]
 	uxtw x114?, w2?
@@ -2093,7 +2093,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x0?]
-	exit_sequence w0?
+	exit_sequence x0?
 	add x182?, x8?, x114?
 	ldrb w118?, [x182?]
 	uxtw x120?, w2?
@@ -2102,7 +2102,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x0?]
-	exit_sequence w0?
+	exit_sequence x0?
 	add x181?, x8?, x120?
 	ldrb w124?, [x181?, #0xf]
 	uxtw x126?, w2?
@@ -2111,7 +2111,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x0?]
-	exit_sequence w0?
+	exit_sequence x0?
 	add x180?, x8?, x126?
 	ldrsh w130?, [x180?]
 	uxtw x132?, w2?
@@ -2120,7 +2120,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x0?]
-	exit_sequence w0?
+	exit_sequence x0?
 	add x179?, x8?, x132?
 	ldrsh w136?, [x179?, #0xf]
 	uxtw x138?, w2?
@@ -2129,7 +2129,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x0?]
-	exit_sequence w0?
+	exit_sequence x0?
 	add x178?, x8?, x138?
 	ldrh w142?, [x178?]
 	uxtw x144?, w2?
@@ -2138,7 +2138,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x0?]
-	exit_sequence w0?
+	exit_sequence x0?
 	add x177?, x8?, x144?
 	ldrh w148?, [x177?, #0xf]
 	uxtw x150?, w2?
@@ -2147,7 +2147,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x0?]
-	exit_sequence w0?
+	exit_sequence x0?
 	add x176?, x8?, x150?
 	ldrs w154?, [x176?]
 	uxtw x156?, w2?
@@ -2156,7 +2156,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x0?]
-	exit_sequence w0?
+	exit_sequence x0?
 	add x175?, x8?, x156?
 	ldrs w160?, [x175?, #0xf]
 	uxtw x162?, w2?
@@ -2165,7 +2165,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x0?]
-	exit_sequence w0?
+	exit_sequence x0?
 	add x174?, x8?, x162?
 	ldr w166?, [x174?]
 	uxtw x168?, w2?
@@ -2174,7 +2174,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x0?]
-	exit_sequence w0?
+	exit_sequence x0?
 	add x173?, x8?, x168?
 	ldr w172?, [x173?, #0xf]
 	str x172?, [#ret_space, #0x78]
@@ -2229,7 +2229,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x8]
-	exit_sequence w8
+	exit_sequence x8
 	ldr x10, [x1, #0x8]
 	add x11, x10, x11
 	ldr w0, [x11]
@@ -2239,7 +2239,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x8]
-	exit_sequence w8
+	exit_sequence x8
 	add x11, x10, x12
 	ldr x1, [x11]
 	uxtw x12, w2
@@ -2248,7 +2248,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x8]
-	exit_sequence w8
+	exit_sequence x8
 	add x11, x10, x12
 	ldr s0, [x11]
 	uxtw x12, w2
@@ -2257,7 +2257,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x8]
-	exit_sequence w8
+	exit_sequence x8
 	add x11, x10, x12
 	ldr d1, [x11]
 	uxtw x12, w2
@@ -2266,7 +2266,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x8]
-	exit_sequence w8
+	exit_sequence x8
 	add x11, x10, x12
 	ldr w11, [x11, #0xf]
 	uxtw x13, w2
@@ -2275,7 +2275,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x8]
-	exit_sequence w8
+	exit_sequence x8
 	add x12, x10, x13
 	ldr x3, [x12, #0xf]
 	uxtw x13, w2
@@ -2284,7 +2284,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x8]
-	exit_sequence w8
+	exit_sequence x8
 	add x12, x10, x13
 	ldr s2, [x12, #0xf]
 	uxtw x13, w2
@@ -2293,7 +2293,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x8]
-	exit_sequence w8
+	exit_sequence x8
 	add x12, x10, x13
 	ldr d3, [x12, #0xf]
 	uxtw x13, w2
@@ -2302,7 +2302,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x8]
-	exit_sequence w8
+	exit_sequence x8
 	add x12, x10, x13
 	ldrsb w4, [x12]
 	uxtw x13, w2
@@ -2311,7 +2311,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x8]
-	exit_sequence w8
+	exit_sequence x8
 	add x12, x10, x13
 	ldrsb w5, [x12, #0xf]
 	uxtw x13, w2
@@ -2320,7 +2320,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x8]
-	exit_sequence w8
+	exit_sequence x8
 	add x12, x10, x13
 	ldrb w6, [x12]
 	uxtw x13, w2
@@ -2329,7 +2329,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x8]
-	exit_sequence w8
+	exit_sequence x8
 	add x12, x10, x13
 	ldrb w7, [x12, #0xf]
 	uxtw x13, w2
@@ -2338,7 +2338,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x8]
-	exit_sequence w8
+	exit_sequence x8
 	add x12, x10, x13
 	ldrsh w12, [x12]
 	uxtw x14, w2
@@ -2347,7 +2347,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x8]
-	exit_sequence w8
+	exit_sequence x8
 	add x13, x10, x14
 	ldrsh w13, [x13, #0xf]
 	uxtw x15, w2
@@ -2356,7 +2356,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x8]
-	exit_sequence w8
+	exit_sequence x8
 	add x14, x10, x15
 	ldrh w14, [x14]
 	uxtw x16, w2
@@ -2365,7 +2365,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x8]
-	exit_sequence w8
+	exit_sequence x8
 	add x15, x10, x16
 	ldrh w15, [x15, #0xf]
 	uxtw x17, w2
@@ -2374,7 +2374,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x8]
-	exit_sequence w8
+	exit_sequence x8
 	add x16, x10, x17
 	ldrsb w16, [x16]
 	uxtw x18, w2
@@ -2383,7 +2383,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x8]
-	exit_sequence w8
+	exit_sequence x8
 	add x17, x10, x18
 	ldrsb w17, [x17, #0xf]
 	uxtw x19, w2
@@ -2392,7 +2392,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x8]
-	exit_sequence w8
+	exit_sequence x8
 	add x18, x10, x19
 	ldrb w18, [x18]
 	uxtw x20, w2
@@ -2401,7 +2401,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x8]
-	exit_sequence w8
+	exit_sequence x8
 	add x19, x10, x20
 	ldrb w19, [x19, #0xf]
 	uxtw x21, w2
@@ -2410,7 +2410,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x8]
-	exit_sequence w8
+	exit_sequence x8
 	add x20, x10, x21
 	ldrsh w20, [x20]
 	uxtw x22, w2
@@ -2419,7 +2419,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x8]
-	exit_sequence w8
+	exit_sequence x8
 	add x21, x10, x22
 	ldrsh w21, [x21, #0xf]
 	uxtw x23, w2
@@ -2428,7 +2428,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x8]
-	exit_sequence w8
+	exit_sequence x8
 	add x22, x10, x23
 	ldrh w22, [x22]
 	uxtw x24, w2
@@ -2437,7 +2437,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x8]
-	exit_sequence w8
+	exit_sequence x8
 	add x23, x10, x24
 	ldrh w23, [x23, #0xf]
 	uxtw x25, w2
@@ -2446,7 +2446,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x8]
-	exit_sequence w8
+	exit_sequence x8
 	add x24, x10, x25
 	ldrs w24, [x24]
 	uxtw x26, w2
@@ -2455,7 +2455,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x8]
-	exit_sequence w8
+	exit_sequence x8
 	add x25, x10, x26
 	ldrs w25, [x25, #0xf]
 	uxtw x28, w2
@@ -2464,7 +2464,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x8]
-	exit_sequence w8
+	exit_sequence x8
 	add x26, x10, x28
 	ldr w26, [x26]
 	uxtw x29, w2
@@ -2473,7 +2473,7 @@ L1 (SSA Block: blk0):
 	b.hs #0x20
 	movz x27, #0x4, LSL 0
 	str w27, [x8]
-	exit_sequence w8
+	exit_sequence x8
 	add x8, x10, x29
 	ldr w8, [x8, #0xf]
 	str x8, [sp, #0x128]
diff --git a/internal/engine/wazevo/backend/isa/arm64/abi_go_call_test.go b/internal/engine/wazevo/backend/isa/arm64/abi_go_call_test.go
index ec5bfa44..e23b38e8 100644
--- a/internal/engine/wazevo/backend/isa/arm64/abi_go_call_test.go
+++ b/internal/engine/wazevo/backend/isa/arm64/abi_go_call_test.go
@@ -76,7 +76,7 @@ func TestMachine_CompileGoFunctionTrampoline(t *testing.T) {
 	str x27, [x0, #0x38]
 	adr x27, #0x1c
 	str x27, [x0, #0x30]
-	exit_sequence w0
+	exit_sequence x0
 	ldr x18, [x0, #0x50]
 	ldr x19, [x0, #0x60]
 	ldr x20, [x0, #0x70]
@@ -160,7 +160,7 @@ func TestMachine_CompileGoFunctionTrampoline(t *testing.T) {
 	str x27, [x0, #0x38]
 	adr x27, #0x1c
 	str x27, [x0, #0x30]
-	exit_sequence w0
+	exit_sequence x0
 	ldr x18, [x0, #0x50]
 	ldr x19, [x0, #0x60]
 	ldr x20, [x0, #0x70]
@@ -231,7 +231,7 @@ func TestMachine_CompileGoFunctionTrampoline(t *testing.T) {
 	str x27, [x0, #0x38]
 	adr x27, #0x1c
 	str x27, [x0, #0x30]
-	exit_sequence w0
+	exit_sequence x0
 	ldr x18, [x0, #0x50]
 	ldr x19, [x0, #0x60]
 	ldr x20, [x0, #0x70]
diff --git a/internal/engine/wazevo/backend/isa/arm64/instr_encoding_test.go b/internal/engine/wazevo/backend/isa/arm64/instr_encoding_test.go
index 40a148e7..953fb861 100644
--- a/internal/engine/wazevo/backend/isa/arm64/instr_encoding_test.go
+++ b/internal/engine/wazevo/backend/isa/arm64/instr_encoding_test.go
@@ -17,6 +17,78 @@ func TestInstruction_encode(t *testing.T) {
 		setup func(*instruction)
 		want  string
 	}{
+		{want: "5b28030b", setup: func(i *instruction) {
+			i.asALU(aluOpAdd, operandNR(tmpRegVReg), operandNR(x2VReg), operandSR(x3VReg, 10, shiftOpLSL), false)
+		}},
+		{want: "5b28038b", setup: func(i *instruction) {
+			i.asALU(aluOpAdd, operandNR(tmpRegVReg), operandNR(x2VReg), operandSR(x3VReg, 10, shiftOpLSL), true)
+		}},
+		{want: "5b28032b", setup: func(i *instruction) {
+			i.asALU(aluOpAddS, operandNR(tmpRegVReg), operandNR(x2VReg), operandSR(x3VReg, 10, shiftOpLSL), false)
+		}},
+		{want: "5b2803ab", setup: func(i *instruction) {
+			i.asALU(aluOpAddS, operandNR(tmpRegVReg), operandNR(x2VReg), operandSR(x3VReg, 10, shiftOpLSL), true)
+		}},
+		{want: "5b28430b", setup: func(i *instruction) {
+			i.asALU(aluOpAdd, operandNR(tmpRegVReg), operandNR(x2VReg), operandSR(x3VReg, 10, shiftOpLSR), false)
+		}},
+		{want: "5b28438b", setup: func(i *instruction) {
+			i.asALU(aluOpAdd, operandNR(tmpRegVReg), operandNR(x2VReg), operandSR(x3VReg, 10, shiftOpLSR), true)
+		}},
+		{want: "5b28432b", setup: func(i *instruction) {
+			i.asALU(aluOpAddS, operandNR(tmpRegVReg), operandNR(x2VReg), operandSR(x3VReg, 10, shiftOpLSR), false)
+		}},
+		{want: "5b2843ab", setup: func(i *instruction) {
+			i.asALU(aluOpAddS, operandNR(tmpRegVReg), operandNR(x2VReg), operandSR(x3VReg, 10, shiftOpLSR), true)
+		}},
+		{want: "5b28830b", setup: func(i *instruction) {
+			i.asALU(aluOpAdd, operandNR(tmpRegVReg), operandNR(x2VReg), operandSR(x3VReg, 10, shiftOpASR), false)
+		}},
+		{want: "5b28838b", setup: func(i *instruction) {
+			i.asALU(aluOpAdd, operandNR(tmpRegVReg), operandNR(x2VReg), operandSR(x3VReg, 10, shiftOpASR), true)
+		}},
+		{want: "5b28832b", setup: func(i *instruction) {
+			i.asALU(aluOpAddS, operandNR(tmpRegVReg), operandNR(x2VReg), operandSR(x3VReg, 10, shiftOpASR), false)
+		}},
+		{want: "5b2883ab", setup: func(i *instruction) {
+			i.asALU(aluOpAddS, operandNR(tmpRegVReg), operandNR(x2VReg), operandSR(x3VReg, 10, shiftOpASR), true)
+		}},
+		{want: "5b28034b", setup: func(i *instruction) {
+			i.asALU(aluOpSub, operandNR(tmpRegVReg), operandNR(x2VReg), operandSR(x3VReg, 10, shiftOpLSL), false)
+		}},
+		{want: "5b2803cb", setup: func(i *instruction) {
+			i.asALU(aluOpSub, operandNR(tmpRegVReg), operandNR(x2VReg), operandSR(x3VReg, 10, shiftOpLSL), true)
+		}},
+		{want: "5b28036b", setup: func(i *instruction) {
+			i.asALU(aluOpSubS, operandNR(tmpRegVReg), operandNR(x2VReg), operandSR(x3VReg, 10, shiftOpLSL), false)
+		}},
+		{want: "5b2803eb", setup: func(i *instruction) {
+			i.asALU(aluOpSubS, operandNR(tmpRegVReg), operandNR(x2VReg), operandSR(x3VReg, 10, shiftOpLSL), true)
+		}},
+		{want: "5b28434b", setup: func(i *instruction) {
+			i.asALU(aluOpSub, operandNR(tmpRegVReg), operandNR(x2VReg), operandSR(x3VReg, 10, shiftOpLSR), false)
+		}},
+		{want: "5b2843cb", setup: func(i *instruction) {
+			i.asALU(aluOpSub, operandNR(tmpRegVReg), operandNR(x2VReg), operandSR(x3VReg, 10, shiftOpLSR), true)
+		}},
+		{want: "5b28436b", setup: func(i *instruction) {
+			i.asALU(aluOpSubS, operandNR(tmpRegVReg), operandNR(x2VReg), operandSR(x3VReg, 10, shiftOpLSR), false)
+		}},
+		{want: "5b2843eb", setup: func(i *instruction) {
+			i.asALU(aluOpSubS, operandNR(tmpRegVReg), operandNR(x2VReg), operandSR(x3VReg, 10, shiftOpLSR), true)
+		}},
+		{want: "5b28834b", setup: func(i *instruction) {
+			i.asALU(aluOpSub, operandNR(tmpRegVReg), operandNR(x2VReg), operandSR(x3VReg, 10, shiftOpASR), false)
+		}},
+		{want: "5b2883cb", setup: func(i *instruction) {
+			i.asALU(aluOpSub, operandNR(tmpRegVReg), operandNR(x2VReg), operandSR(x3VReg, 10, shiftOpASR), true)
+		}},
+		{want: "5b28836b", setup: func(i *instruction) {
+			i.asALU(aluOpSubS, operandNR(tmpRegVReg), operandNR(x2VReg), operandSR(x3VReg, 10, shiftOpASR), false)
+		}},
+		{want: "5b2883eb", setup: func(i *instruction) {
+			i.asALU(aluOpSubS, operandNR(tmpRegVReg), operandNR(x2VReg), operandSR(x3VReg, 10, shiftOpASR), true)
+		}},
 		{want: "60033fd6", setup: func(i *instruction) {
 			i.asCallIndirect(tmpRegVReg, nil)
 		}},
diff --git a/internal/engine/wazevo/backend/isa/arm64/machine_pro_epi_logue_test.go b/internal/engine/wazevo/backend/isa/arm64/machine_pro_epi_logue_test.go
index a5fc2a38..a7a2520d 100644
--- a/internal/engine/wazevo/backend/isa/arm64/machine_pro_epi_logue_test.go
+++ b/internal/engine/wazevo/backend/isa/arm64/machine_pro_epi_logue_test.go
@@ -223,7 +223,7 @@ func TestMachine_insertStackBoundsCheck(t *testing.T) {
 	str x27, [x0, #0x40]
 	adr x27, #0x1c
 	str x27, [x0, #0x30]
-	exit_sequence w0
+	exit_sequence x0
 	ldr x1, [x0, #0x50]
 	ldr x2, [x0, #0x60]
 	ldr x3, [x0, #0x70]
@@ -321,7 +321,7 @@ func TestMachine_insertStackBoundsCheck(t *testing.T) {
 	str x27, [x0, #0x40]
 	adr x27, #0x1c
 	str x27, [x0, #0x30]
-	exit_sequence w0
+	exit_sequence x0
 	ldr x1, [x0, #0x50]
 	ldr x2, [x0, #0x60]
 	ldr x3, [x0, #0x70]
diff --git a/internal/engine/wazevo/e2e_test.go b/internal/engine/wazevo/e2e_test.go
index 11b01783..0f933997 100644
--- a/internal/engine/wazevo/e2e_test.go
+++ b/internal/engine/wazevo/e2e_test.go
@@ -153,6 +153,18 @@ func TestE2E(t *testing.T) {
 			m:        testcases.ImportedMemoryGrow.Module,
 			calls:    []callCase{{expResults: []uint64{1, 1, 11, 11}}},
 		},
+		{
+			name: "call_indirect",
+			m:    testcases.CallIndirect.Module,
+			// parameter == table offset.
+			calls: []callCase{
+				{params: []uint64{0}, expErr: "indirect call type mismatch"},
+				{params: []uint64{1}, expResults: []uint64{10}},
+				{params: []uint64{2}, expErr: "indirect call type mismatch"},
+				{params: []uint64{10}, expErr: "invalid table access"},             // Null pointer.
+				{params: []uint64{math.MaxUint32}, expErr: "invalid table access"}, // Out of bounds.
+			},
+		},
 	} {
 		tc := tc
 		t.Run(tc.name, func(t *testing.T) {
diff --git a/internal/engine/wazevo/frontend/frontend_test.go b/internal/engine/wazevo/frontend/frontend_test.go
index ba5f17c8..37f990f0 100644
--- a/internal/engine/wazevo/frontend/frontend_test.go
+++ b/internal/engine/wazevo/frontend/frontend_test.go
@@ -869,8 +869,8 @@ blk0: (exec_ctx:i64, module_ctx:i64, v2:i32)
 	v4:i64 = UExtend v2, 32->64
 	v5:i64 = Uload32 module_ctx, 0x10
 	v6:i64 = Iadd v4, v3
-	v7:i32 = Icmp ge_u, v5, v6
-	ExitIfNotZero v7, exec_ctx, memory_out_of_bounds
+	v7:i32 = Icmp lt_u, v5, v6
+	ExitIfTrue v7, exec_ctx, memory_out_of_bounds
 	v8:i64 = Load module_ctx, 0x8
 	v9:i64 = Iadd v8, v4
 	v10:i32 = Load v9, 0x0
@@ -904,8 +904,8 @@ blk3: (v7:i32) <-- (blk1,blk2)
 	v9:i64 = UExtend v7, 32->64
 	v10:i64 = Uload32 module_ctx, 0x10
 	v11:i64 = Iadd v9, v8
-	v12:i32 = Icmp ge_u, v10, v11
-	ExitIfNotZero v12, exec_ctx, memory_out_of_bounds
+	v12:i32 = Icmp lt_u, v10, v11
+	ExitIfTrue v12, exec_ctx, memory_out_of_bounds
 	v13:i64 = Load module_ctx, 0x8
 	v14:i64 = Iadd v13, v9
 	v15:i32 = Load v14, 0x0
@@ -934,8 +934,8 @@ blk3: () <-- (blk1,blk2)
 	v9:i64 = UExtend v2, 32->64
 	v10:i64 = Uload32 module_ctx, 0x10
 	v11:i64 = Iadd v9, v8
-	v12:i32 = Icmp ge_u, v10, v11
-	ExitIfNotZero v12, exec_ctx, memory_out_of_bounds
+	v12:i32 = Icmp lt_u, v10, v11
+	ExitIfTrue v12, exec_ctx, memory_out_of_bounds
 	v13:i64 = Load module_ctx, 0x8
 	v14:i64 = Iadd v13, v9
 	v15:i32 = Load v14, 0x0
@@ -964,198 +964,198 @@ blk0: (exec_ctx:i64, module_ctx:i64, v2:i32)
 	v4:i64 = UExtend v2, 32->64
 	v5:i64 = Uload32 module_ctx, 0x10
 	v6:i64 = Iadd v4, v3
-	v7:i32 = Icmp ge_u, v5, v6
-	ExitIfNotZero v7, exec_ctx, memory_out_of_bounds
+	v7:i32 = Icmp lt_u, v5, v6
+	ExitIfTrue v7, exec_ctx, memory_out_of_bounds
 	v8:i64 = Load module_ctx, 0x8
 	v9:i64 = Iadd v8, v4
 	v10:i32 = Load v9, 0x0
 	v11:i64 = Iconst_64 0x8
 	v12:i64 = UExtend v2, 32->64
 	v13:i64 = Iadd v12, v11
-	v14:i32 = Icmp ge_u, v5, v13
-	ExitIfNotZero v14, exec_ctx, memory_out_of_bounds
+	v14:i32 = Icmp lt_u, v5, v13
+	ExitIfTrue v14, exec_ctx, memory_out_of_bounds
 	v15:i64 = Iadd v8, v12
 	v16:i64 = Load v15, 0x0
 	v17:i64 = Iconst_64 0x4
 	v18:i64 = UExtend v2, 32->64
 	v19:i64 = Iadd v18, v17
-	v20:i32 = Icmp ge_u, v5, v19
-	ExitIfNotZero v20, exec_ctx, memory_out_of_bounds
+	v20:i32 = Icmp lt_u, v5, v19
+	ExitIfTrue v20, exec_ctx, memory_out_of_bounds
 	v21:i64 = Iadd v8, v18
 	v22:f32 = Load v21, 0x0
 	v23:i64 = Iconst_64 0x8
 	v24:i64 = UExtend v2, 32->64
 	v25:i64 = Iadd v24, v23
-	v26:i32 = Icmp ge_u, v5, v25
-	ExitIfNotZero v26, exec_ctx, memory_out_of_bounds
+	v26:i32 = Icmp lt_u, v5, v25
+	ExitIfTrue v26, exec_ctx, memory_out_of_bounds
 	v27:i64 = Iadd v8, v24
 	v28:f64 = Load v27, 0x0
 	v29:i64 = Iconst_64 0x13
 	v30:i64 = UExtend v2, 32->64
 	v31:i64 = Iadd v30, v29
-	v32:i32 = Icmp ge_u, v5, v31
-	ExitIfNotZero v32, exec_ctx, memory_out_of_bounds
+	v32:i32 = Icmp lt_u, v5, v31
+	ExitIfTrue v32, exec_ctx, memory_out_of_bounds
 	v33:i64 = Iadd v8, v30
 	v34:i32 = Load v33, 0xf
 	v35:i64 = Iconst_64 0x17
 	v36:i64 = UExtend v2, 32->64
 	v37:i64 = Iadd v36, v35
-	v38:i32 = Icmp ge_u, v5, v37
-	ExitIfNotZero v38, exec_ctx, memory_out_of_bounds
+	v38:i32 = Icmp lt_u, v5, v37
+	ExitIfTrue v38, exec_ctx, memory_out_of_bounds
 	v39:i64 = Iadd v8, v36
 	v40:i64 = Load v39, 0xf
 	v41:i64 = Iconst_64 0x13
 	v42:i64 = UExtend v2, 32->64
 	v43:i64 = Iadd v42, v41
-	v44:i32 = Icmp ge_u, v5, v43
-	ExitIfNotZero v44, exec_ctx, memory_out_of_bounds
+	v44:i32 = Icmp lt_u, v5, v43
+	ExitIfTrue v44, exec_ctx, memory_out_of_bounds
 	v45:i64 = Iadd v8, v42
 	v46:f32 = Load v45, 0xf
 	v47:i64 = Iconst_64 0x17
 	v48:i64 = UExtend v2, 32->64
 	v49:i64 = Iadd v48, v47
-	v50:i32 = Icmp ge_u, v5, v49
-	ExitIfNotZero v50, exec_ctx, memory_out_of_bounds
+	v50:i32 = Icmp lt_u, v5, v49
+	ExitIfTrue v50, exec_ctx, memory_out_of_bounds
 	v51:i64 = Iadd v8, v48
 	v52:f64 = Load v51, 0xf
 	v53:i64 = Iconst_64 0x1
 	v54:i64 = UExtend v2, 32->64
 	v55:i64 = Iadd v54, v53
-	v56:i32 = Icmp ge_u, v5, v55
-	ExitIfNotZero v56, exec_ctx, memory_out_of_bounds
+	v56:i32 = Icmp lt_u, v5, v55
+	ExitIfTrue v56, exec_ctx, memory_out_of_bounds
 	v57:i64 = Iadd v8, v54
 	v58:i32 = Sload8 v57, 0x0
 	v59:i64 = Iconst_64 0x10
 	v60:i64 = UExtend v2, 32->64
 	v61:i64 = Iadd v60, v59
-	v62:i32 = Icmp ge_u, v5, v61
-	ExitIfNotZero v62, exec_ctx, memory_out_of_bounds
+	v62:i32 = Icmp lt_u, v5, v61
+	ExitIfTrue v62, exec_ctx, memory_out_of_bounds
 	v63:i64 = Iadd v8, v60
 	v64:i32 = Sload8 v63, 0xf
 	v65:i64 = Iconst_64 0x1
 	v66:i64 = UExtend v2, 32->64
 	v67:i64 = Iadd v66, v65
-	v68:i32 = Icmp ge_u, v5, v67
-	ExitIfNotZero v68, exec_ctx, memory_out_of_bounds
+	v68:i32 = Icmp lt_u, v5, v67
+	ExitIfTrue v68, exec_ctx, memory_out_of_bounds
 	v69:i64 = Iadd v8, v66
 	v70:i32 = Uload8 v69, 0x0
 	v71:i64 = Iconst_64 0x10
 	v72:i64 = UExtend v2, 32->64
 	v73:i64 = Iadd v72, v71
-	v74:i32 = Icmp ge_u, v5, v73
-	ExitIfNotZero v74, exec_ctx, memory_out_of_bounds
+	v74:i32 = Icmp lt_u, v5, v73
+	ExitIfTrue v74, exec_ctx, memory_out_of_bounds
 	v75:i64 = Iadd v8, v72
 	v76:i32 = Uload8 v75, 0xf
 	v77:i64 = Iconst_64 0x2
 	v78:i64 = UExtend v2, 32->64
 	v79:i64 = Iadd v78, v77
-	v80:i32 = Icmp ge_u, v5, v79
-	ExitIfNotZero v80, exec_ctx, memory_out_of_bounds
+	v80:i32 = Icmp lt_u, v5, v79
+	ExitIfTrue v80, exec_ctx, memory_out_of_bounds
 	v81:i64 = Iadd v8, v78
 	v82:i32 = Sload16 v81, 0x0
 	v83:i64 = Iconst_64 0x11
 	v84:i64 = UExtend v2, 32->64
 	v85:i64 = Iadd v84, v83
-	v86:i32 = Icmp ge_u, v5, v85
-	ExitIfNotZero v86, exec_ctx, memory_out_of_bounds
+	v86:i32 = Icmp lt_u, v5, v85
+	ExitIfTrue v86, exec_ctx, memory_out_of_bounds
 	v87:i64 = Iadd v8, v84
 	v88:i32 = Sload16 v87, 0xf
 	v89:i64 = Iconst_64 0x2
 	v90:i64 = UExtend v2, 32->64
 	v91:i64 = Iadd v90, v89
-	v92:i32 = Icmp ge_u, v5, v91
-	ExitIfNotZero v92, exec_ctx, memory_out_of_bounds
+	v92:i32 = Icmp lt_u, v5, v91
+	ExitIfTrue v92, exec_ctx, memory_out_of_bounds
 	v93:i64 = Iadd v8, v90
 	v94:i32 = Uload16 v93, 0x0
 	v95:i64 = Iconst_64 0x11
 	v96:i64 = UExtend v2, 32->64
 	v97:i64 = Iadd v96, v95
-	v98:i32 = Icmp ge_u, v5, v97
-	ExitIfNotZero v98, exec_ctx, memory_out_of_bounds
+	v98:i32 = Icmp lt_u, v5, v97
+	ExitIfTrue v98, exec_ctx, memory_out_of_bounds
 	v99:i64 = Iadd v8, v96
 	v100:i32 = Uload16 v99, 0xf
 	v101:i64 = Iconst_64 0x1
 	v102:i64 = UExtend v2, 32->64
 	v103:i64 = Iadd v102, v101
-	v104:i32 = Icmp ge_u, v5, v103
-	ExitIfNotZero v104, exec_ctx, memory_out_of_bounds
+	v104:i32 = Icmp lt_u, v5, v103
+	ExitIfTrue v104, exec_ctx, memory_out_of_bounds
 	v105:i64 = Iadd v8, v102
 	v106:i64 = Sload8 v105, 0x0
 	v107:i64 = Iconst_64 0x10
 	v108:i64 = UExtend v2, 32->64
 	v109:i64 = Iadd v108, v107
-	v110:i32 = Icmp ge_u, v5, v109
-	ExitIfNotZero v110, exec_ctx, memory_out_of_bounds
+	v110:i32 = Icmp lt_u, v5, v109
+	ExitIfTrue v110, exec_ctx, memory_out_of_bounds
 	v111:i64 = Iadd v8, v108
 	v112:i64 = Sload8 v111, 0xf
 	v113:i64 = Iconst_64 0x1
 	v114:i64 = UExtend v2, 32->64
 	v115:i64 = Iadd v114, v113
-	v116:i32 = Icmp ge_u, v5, v115
-	ExitIfNotZero v116, exec_ctx, memory_out_of_bounds
+	v116:i32 = Icmp lt_u, v5, v115
+	ExitIfTrue v116, exec_ctx, memory_out_of_bounds
 	v117:i64 = Iadd v8, v114
 	v118:i64 = Uload8 v117, 0x0
 	v119:i64 = Iconst_64 0x10
 	v120:i64 = UExtend v2, 32->64
 	v121:i64 = Iadd v120, v119
-	v122:i32 = Icmp ge_u, v5, v121
-	ExitIfNotZero v122, exec_ctx, memory_out_of_bounds
+	v122:i32 = Icmp lt_u, v5, v121
+	ExitIfTrue v122, exec_ctx, memory_out_of_bounds
 	v123:i64 = Iadd v8, v120
 	v124:i64 = Uload8 v123, 0xf
 	v125:i64 = Iconst_64 0x2
 	v126:i64 = UExtend v2, 32->64
 	v127:i64 = Iadd v126, v125
-	v128:i32 = Icmp ge_u, v5, v127
-	ExitIfNotZero v128, exec_ctx, memory_out_of_bounds
+	v128:i32 = Icmp lt_u, v5, v127
+	ExitIfTrue v128, exec_ctx, memory_out_of_bounds
 	v129:i64 = Iadd v8, v126
 	v130:i64 = Sload16 v129, 0x0
 	v131:i64 = Iconst_64 0x11
 	v132:i64 = UExtend v2, 32->64
 	v133:i64 = Iadd v132, v131
-	v134:i32 = Icmp ge_u, v5, v133
-	ExitIfNotZero v134, exec_ctx, memory_out_of_bounds
+	v134:i32 = Icmp lt_u, v5, v133
+	ExitIfTrue v134, exec_ctx, memory_out_of_bounds
 	v135:i64 = Iadd v8, v132
 	v136:i64 = Sload16 v135, 0xf
 	v137:i64 = Iconst_64 0x2
 	v138:i64 = UExtend v2, 32->64
 	v139:i64 = Iadd v138, v137
-	v140:i32 = Icmp ge_u, v5, v139
-	ExitIfNotZero v140, exec_ctx, memory_out_of_bounds
+	v140:i32 = Icmp lt_u, v5, v139
+	ExitIfTrue v140, exec_ctx, memory_out_of_bounds
 	v141:i64 = Iadd v8, v138
 	v142:i64 = Uload16 v141, 0x0
 	v143:i64 = Iconst_64 0x11
 	v144:i64 = UExtend v2, 32->64
 	v145:i64 = Iadd v144, v143
-	v146:i32 = Icmp ge_u, v5, v145
-	ExitIfNotZero v146, exec_ctx, memory_out_of_bounds
+	v146:i32 = Icmp lt_u, v5, v145
+	ExitIfTrue v146, exec_ctx, memory_out_of_bounds
 	v147:i64 = Iadd v8, v144
 	v148:i64 = Uload16 v147, 0xf
 	v149:i64 = Iconst_64 0x4
 	v150:i64 = UExtend v2, 32->64
 	v151:i64 = Iadd v150, v149
-	v152:i32 = Icmp ge_u, v5, v151
-	ExitIfNotZero v152, exec_ctx, memory_out_of_bounds
+	v152:i32 = Icmp lt_u, v5, v151
+	ExitIfTrue v152, exec_ctx, memory_out_of_bounds
 	v153:i64 = Iadd v8, v150
 	v154:i64 = Sload32 v153, 0x0
 	v155:i64 = Iconst_64 0x13
 	v156:i64 = UExtend v2, 32->64
 	v157:i64 = Iadd v156, v155
-	v158:i32 = Icmp ge_u, v5, v157
-	ExitIfNotZero v158, exec_ctx, memory_out_of_bounds
+	v158:i32 = Icmp lt_u, v5, v157
+	ExitIfTrue v158, exec_ctx, memory_out_of_bounds
 	v159:i64 = Iadd v8, v156
 	v160:i64 = Sload32 v159, 0xf
 	v161:i64 = Iconst_64 0x4
 	v162:i64 = UExtend v2, 32->64
 	v163:i64 = Iadd v162, v161
-	v164:i32 = Icmp ge_u, v5, v163
-	ExitIfNotZero v164, exec_ctx, memory_out_of_bounds
+	v164:i32 = Icmp lt_u, v5, v163
+	ExitIfTrue v164, exec_ctx, memory_out_of_bounds
 	v165:i64 = Iadd v8, v162
 	v166:i64 = Uload32 v165, 0x0
 	v167:i64 = Iconst_64 0x13
 	v168:i64 = UExtend v2, 32->64
 	v169:i64 = Iadd v168, v167
-	v170:i32 = Icmp ge_u, v5, v169
-	ExitIfNotZero v170, exec_ctx, memory_out_of_bounds
+	v170:i32 = Icmp lt_u, v5, v169
+	ExitIfTrue v170, exec_ctx, memory_out_of_bounds
 	v171:i64 = Iadd v8, v168
 	v172:i64 = Uload32 v171, 0xf
 	Jump blk_ret, v10, v16, v22, v28, v34, v40, v46, v52, v58, v64, v70, v76, v82, v88, v94, v100, v106, v112, v118, v124, v130, v136, v142, v148, v154, v160, v166, v172
@@ -1365,6 +1365,37 @@ blk0: (exec_ctx:i64, module_ctx:i64)
 	v11:i64 = Load exec_ctx, 0x48
 	v12:i32 = CallIndirect v11:sig1, exec_ctx, v10
 	Jump blk_ret, v4, v9, v12
+`,
+		},
+		{
+			name: "call_indirect", m: testcases.CallIndirect.Module,
+			exp: `
+signatures:
+	sig2: i64i64_i32
+
+blk0: (exec_ctx:i64, module_ctx:i64, v2:i32)
+	v3:i64 = Load module_ctx, 0x10
+	v4:i32 = Load v3, 0x8
+	v5:i32 = Icmp ge_u, v2, v4
+	ExitIfTrue v5, exec_ctx, table_out_of_bounds
+	v6:i64 = Load v3, 0x0
+	v7:i64 = Iconst_64 0x3
+	v8:i32 = Ishl v2, v7
+	v9:i64 = Iadd v6, v8
+	v10:i64 = Load v9, 0x0
+	v11:i64 = Iconst_64 0x0
+	v12:i32 = Icmp eq, v10, v11
+	ExitIfTrue v12, exec_ctx, indirect_call_null_pointer
+	v13:i32 = Load v10, 0x10
+	v14:i64 = Load module_ctx, 0x8
+	v15:i32 = Load v14, 0x8
+	v16:i32 = Icmp neq, v13, v15
+	ExitIfTrue v16, exec_ctx, indirect_call_type_mismatch
+	v17:i64 = Load v10, 0x0
+	v18:i64 = Load v10, 0x8
+	Store module_ctx, exec_ctx, 0x8
+	v19:i32 = CallIndirect v17:sig2, exec_ctx, v18
+	Jump blk_ret, v19
 `,
 		},
 	} {
diff --git a/internal/engine/wazevo/frontend/lower_test.go b/internal/engine/wazevo/frontend/lower_test.go
index 8fa928b1..268915ff 100644
--- a/internal/engine/wazevo/frontend/lower_test.go
+++ b/internal/engine/wazevo/frontend/lower_test.go
@@ -8,9 +8,11 @@ import (
 	"github.com/tetratelabs/wazero/internal/wasm"
 )
 
-func TestGlobalInstanceValueOffset(t *testing.T) {
+func Test_Offsets(t *testing.T) {
 	var globalInstance wasm.GlobalInstance
 	require.Equal(t, int(unsafe.Offsetof(globalInstance.Val)), globalInstanceValueOffset)
 	var memInstance wasm.MemoryInstance
 	require.Equal(t, int(unsafe.Offsetof(memInstance.Buffer)), memoryInstanceBufOffset)
+	var tableInstance wasm.TableInstance
+	require.Equal(t, int(unsafe.Offsetof(tableInstance.References)), tableInstanceBaseAddressOffset)
 }
diff --git a/internal/engine/wazevo/module_engine_test.go b/internal/engine/wazevo/module_engine_test.go
index 3e2b529a..3fa6675b 100644
--- a/internal/engine/wazevo/module_engine_test.go
+++ b/internal/engine/wazevo/module_engine_test.go
@@ -22,6 +22,7 @@ func TestModuleEngine_setupOpaque(t *testing.T) {
 				LocalMemoryBegin:       10,
 				ImportedMemoryBegin:    -1,
 				ImportedFunctionsBegin: -1,
+				TablesBegin:            -1,
 				GlobalsBegin:           -1,
 			},
 			m: &wasm.ModuleInstance{MemoryInstance: &wasm.MemoryInstance{
@@ -33,6 +34,7 @@ func TestModuleEngine_setupOpaque(t *testing.T) {
 				LocalMemoryBegin:       -1,
 				ImportedMemoryBegin:    30,
 				GlobalsBegin:           -1,
+				TablesBegin:            -1,
 				ImportedFunctionsBegin: -1,
 			},
 			m: &wasm.ModuleInstance{MemoryInstance: &wasm.MemoryInstance{
@@ -45,9 +47,12 @@ func TestModuleEngine_setupOpaque(t *testing.T) {
 				ImportedMemoryBegin:    -1,
 				ImportedFunctionsBegin: -1,
 				GlobalsBegin:           30,
+				TablesBegin:            100,
 			},
 			m: &wasm.ModuleInstance{
 				Globals: []*wasm.GlobalInstance{{}, {}, {}, {}, {}, {}},
+				Tables:  []*wasm.TableInstance{{}, {}, {}},
+				TypeIDs: make([]wasm.FunctionTypeID, 50),
 			},
 		},
 	} {
@@ -91,6 +96,17 @@ func TestModuleEngine_setupOpaque(t *testing.T) {
 					require.Equal(t, expPtr, actualPtr)
 				}
 			}
+			if tc.offset.TablesBegin >= 0 {
+				typeIDsPtr := uintptr(binary.LittleEndian.Uint64(m.opaque[int(tc.offset.TypeIDs1stElement):]))
+				expPtr := uintptr(unsafe.Pointer(&tc.m.TypeIDs[0]))
+				require.Equal(t, expPtr, typeIDsPtr)
+
+				for i, table := range tc.m.Tables {
+					actualPtr := uintptr(binary.LittleEndian.Uint64(m.opaque[int(tc.offset.TablesBegin)+8*i:]))
+					expPtr := uintptr(unsafe.Pointer(table))
+					require.Equal(t, expPtr, actualPtr)
+				}
+			}
 		})
 	}
 }
@@ -108,6 +124,10 @@ func TestModuleEngine_ResolveImportedFunction(t *testing.T) {
 			executable:      make([]byte, 1000),
 			functionOffsets: []compiledFunctionOffset{{offset: 1, goPreambleSize: 4}, {offset: 5, goPreambleSize: 4}, {offset: 10, goPreambleSize: 4}},
 		},
+		module: &wasm.ModuleInstance{
+			TypeIDs: []wasm.FunctionTypeID{0, 0, 0, 0, 111, 222, 333},
+			Source:  &wasm.Module{FunctionSection: []wasm.Index{4, 5, 6}},
+		},
 	}
 	im2 := &moduleEngine{
 		opaquePtr: &op2,
@@ -115,6 +135,10 @@ func TestModuleEngine_ResolveImportedFunction(t *testing.T) {
 			executable:      make([]byte, 1000),
 			functionOffsets: []compiledFunctionOffset{{offset: 50, goPreambleSize: 4}},
 		},
+		module: &wasm.ModuleInstance{
+			TypeIDs: []wasm.FunctionTypeID{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 999},
+			Source:  &wasm.Module{FunctionSection: []wasm.Index{10}},
+		},
 	}
 
 	m.ResolveImportedFunction(0, 0, im1)
@@ -122,22 +146,61 @@ func TestModuleEngine_ResolveImportedFunction(t *testing.T) {
 	m.ResolveImportedFunction(2, 2, im1)
 	m.ResolveImportedFunction(3, 1, im1)
 
-	for _, tc := range []struct {
+	for i, tc := range []struct {
 		index      int
 		op         *byte
 		executable *byte
+		expTypeID  wasm.FunctionTypeID
 	}{
-		{index: 0, op: &op1, executable: &im1.parent.executable[1+4]},
-		{index: 1, op: &op2, executable: &im2.parent.executable[50+4]},
-		{index: 2, op: &op1, executable: &im1.parent.executable[10+4]},
-		{index: 3, op: &op1, executable: &im1.parent.executable[5+4]},
+		{index: 0, op: &op1, executable: &im1.parent.executable[1+4], expTypeID: 111},
+		{index: 1, op: &op2, executable: &im2.parent.executable[50+4], expTypeID: 999},
+		{index: 2, op: &op1, executable: &im1.parent.executable[10+4], expTypeID: 333},
+		{index: 3, op: &op1, executable: &im1.parent.executable[5+4], expTypeID: 222},
 	} {
-		buf := m.opaque[begin+16*tc.index:]
-		actualExecutable := binary.LittleEndian.Uint64(buf)
-		actualOpaquePtr := binary.LittleEndian.Uint64(buf[8:])
-		expExecutable := uint64(uintptr(unsafe.Pointer(tc.executable)))
-		expOpaquePtr := uint64(uintptr(unsafe.Pointer(tc.op)))
-		require.Equal(t, expExecutable, actualExecutable)
-		require.Equal(t, expOpaquePtr, actualOpaquePtr)
+		t.Run(strconv.Itoa(i), func(t *testing.T) {
+			buf := m.opaque[begin+wazevoapi.FunctionInstanceSize*tc.index:]
+			actualExecutable := binary.LittleEndian.Uint64(buf)
+			actualOpaquePtr := binary.LittleEndian.Uint64(buf[8:])
+			actualTypeID := binary.LittleEndian.Uint64(buf[16:])
+			expExecutable := uint64(uintptr(unsafe.Pointer(tc.executable)))
+			expOpaquePtr := uint64(uintptr(unsafe.Pointer(tc.op)))
+			require.Equal(t, expExecutable, actualExecutable)
+			require.Equal(t, expOpaquePtr, actualOpaquePtr)
+			require.Equal(t, uint64(tc.expTypeID), actualTypeID)
+		})
 	}
 }
+
+func Test_functionInstance_offsets(t *testing.T) {
+	var fi functionInstance
+	require.Equal(t, wazevoapi.FunctionInstanceSize, int(unsafe.Sizeof(fi)))
+	require.Equal(t, wazevoapi.FunctionInstanceExecutableOffset, int(unsafe.Offsetof(fi.executable)))
+	require.Equal(t, wazevoapi.FunctionInstanceModuleContextOpaquePtrOffset, int(unsafe.Offsetof(fi.moduleContextOpaquePtr)))
+	require.Equal(t, wazevoapi.FunctionInstanceTypeIDOffset, int(unsafe.Offsetof(fi.typeID)))
+
+	m := wazevoapi.ModuleContextOffsetData{ImportedFunctionsBegin: 100}
+	ptr, moduleCtx, typeID := m.ImportedFunctionOffset(10)
+	require.Equal(t, 100+10*wazevoapi.FunctionInstanceSize, int(ptr))
+	require.Equal(t, moduleCtx, ptr+8)
+	require.Equal(t, typeID, ptr+16)
+}
+
+func Test_getTypeIDOf(t *testing.T) {
+	m := &wasm.ModuleInstance{
+		TypeIDs: []wasm.FunctionTypeID{111, 222, 333, 444},
+		Source: &wasm.Module{
+			ImportFunctionCount: 1,
+			ImportSection: []wasm.Import{
+				{Type: wasm.ExternTypeMemory},
+				{Type: wasm.ExternTypeTable},
+				{Type: wasm.ExternTypeFunc, DescFunc: 3},
+			},
+			FunctionSection: []wasm.Index{2, 1, 0},
+		},
+	}
+
+	require.Equal(t, wasm.FunctionTypeID(444), getTypeIDOf(0, m))
+	require.Equal(t, wasm.FunctionTypeID(333), getTypeIDOf(1, m))
+	require.Equal(t, wasm.FunctionTypeID(222), getTypeIDOf(2, m))
+	require.Equal(t, wasm.FunctionTypeID(111), getTypeIDOf(3, m))
+}
diff --git a/internal/engine/wazevo/testcases/testcases.go b/internal/engine/wazevo/testcases/testcases.go
index cba795e4..e6728358 100644
--- a/internal/engine/wazevo/testcases/testcases.go
+++ b/internal/engine/wazevo/testcases/testcases.go
@@ -1101,6 +1101,32 @@ var (
 			DataSection: []wasm.DataSegment{{OffsetExpression: constExprI32(0), Init: maskedBuf(int(wasm.MemoryPageSize))}},
 		},
 	}
+
+	CallIndirect = TestCase{
+		Module: &wasm.Module{
+			TypeSection:     []wasm.FunctionType{i32_i32, {}, v_i32, v_i32i32},
+			ExportSection:   []wasm.Export{{Name: ExportName, Type: wasm.ExternTypeFunc, Index: 0}},
+			FunctionSection: []wasm.Index{0, 1, 2, 3},
+			TableSection:    []wasm.Table{{Type: wasm.RefTypeFuncref, Min: 1000}},
+			ElementSection: []wasm.ElementSegment{
+				{
+					OffsetExpr: constExprI32(0), TableIndex: 0, Type: wasm.RefTypeFuncref, Mode: wasm.ElementModeActive,
+					// Set the function 1, 2, 3 at the beginning of the table.
+					Init: []wasm.Index{1, 2, 3},
+				},
+			},
+			CodeSection: []wasm.Code{
+				{Body: []byte{
+					wasm.OpcodeLocalGet, 0,
+					wasm.OpcodeCallIndirect, 2, 0, // Expecting type 2 (v_i32), in tables[0]
+					wasm.OpcodeEnd,
+				}},
+				{Body: []byte{wasm.OpcodeEnd}},
+				{Body: []byte{wasm.OpcodeI32Const, 10, wasm.OpcodeEnd}},
+				{Body: []byte{wasm.OpcodeI32Const, 1, wasm.OpcodeI32Const, 1, wasm.OpcodeEnd}},
+			},
+		},
+	}
 )
 
 type TestCase struct {
diff --git a/internal/engine/wazevo/wazevoapi/offsetdata_test.go b/internal/engine/wazevo/wazevoapi/offsetdata_test.go
index ef2e2f74..1081585d 100644
--- a/internal/engine/wazevo/wazevoapi/offsetdata_test.go
+++ b/internal/engine/wazevo/wazevoapi/offsetdata_test.go
@@ -21,6 +21,8 @@ func TestNewModuleContextOffsetData(t *testing.T) {
 				ImportedMemoryBegin:    -1,
 				ImportedFunctionsBegin: -1,
 				GlobalsBegin:           -1,
+				TypeIDs1stElement:      -1,
+				TablesBegin:            -1,
 				TotalSize:              8,
 			},
 		},
@@ -32,6 +34,8 @@ func TestNewModuleContextOffsetData(t *testing.T) {
 				ImportedMemoryBegin:    -1,
 				ImportedFunctionsBegin: -1,
 				GlobalsBegin:           -1,
+				TypeIDs1stElement:      -1,
+				TablesBegin:            -1,
 				TotalSize:              24,
 			},
 		},
@@ -43,6 +47,8 @@ func TestNewModuleContextOffsetData(t *testing.T) {
 				ImportedMemoryBegin:    8,
 				ImportedFunctionsBegin: -1,
 				GlobalsBegin:           -1,
+				TypeIDs1stElement:      -1,
+				TablesBegin:            -1,
 				TotalSize:              24,
 			},
 		},
@@ -54,7 +60,9 @@ func TestNewModuleContextOffsetData(t *testing.T) {
 				ImportedMemoryBegin:    -1,
 				ImportedFunctionsBegin: 8,
 				GlobalsBegin:           -1,
-				TotalSize:              168,
+				TypeIDs1stElement:      -1,
+				TablesBegin:            -1,
+				TotalSize:              10*FunctionInstanceSize + 8,
 			},
 		},
 		{
@@ -65,14 +73,18 @@ func TestNewModuleContextOffsetData(t *testing.T) {
 				ImportedMemoryBegin:    8,
 				ImportedFunctionsBegin: 24,
 				GlobalsBegin:           -1,
-				TotalSize:              184,
+				TypeIDs1stElement:      -1,
+				TablesBegin:            -1,
+				TotalSize:              10*FunctionInstanceSize + 24,
 			},
 		},
 		{
-			name: "local mem / imported func / globals",
+			name: "local mem / imported func / globals / tables",
 			m: &wasm.Module{
 				ImportGlobalCount:   10,
 				ImportFunctionCount: 10,
+				ImportTableCount:    5,
+				TableSection:        make([]wasm.Table, 10),
 				MemorySection:       &wasm.Memory{},
 				GlobalSection:       make([]wasm.Global, 20),
 			},
@@ -80,8 +92,10 @@ func TestNewModuleContextOffsetData(t *testing.T) {
 				LocalMemoryBegin:       8,
 				ImportedMemoryBegin:    -1,
 				ImportedFunctionsBegin: 24,
-				GlobalsBegin:           24 + 16*10,
-				TotalSize:              24 + 16*10 + 8*30,
+				GlobalsBegin:           24 + 10*FunctionInstanceSize,
+				TypeIDs1stElement:      24 + 10*FunctionInstanceSize + 8*30,
+				TablesBegin:            24 + 10*FunctionInstanceSize + 8*30 + 8,
+				TotalSize:              24 + 10*FunctionInstanceSize + 8*30 + 8 + 8*15,
 			},
 		},
 	} {