Repository: tetratelabs/wazero
Pull request #2019 (2024-02-06 19:04:16)

wazevo(amd64): efficient instruction/operand/amode data structure


Patch:
diff --git a/internal/engine/wazevo/backend/isa/amd64/abi.go b/internal/engine/wazevo/backend/isa/amd64/abi.go
index e9e3c838..5cf410cd 100644
--- a/internal/engine/wazevo/backend/isa/amd64/abi.go
+++ b/internal/engine/wazevo/backend/isa/amd64/abi.go
@@ -91,7 +91,7 @@ func (m *machine) LowerParams(args []ssa.Value) {
 
 			// Load the value from the arg stack slot above the current RBP.
 			load := m.allocateInstr()
-			mem := newOperandMem(newAmodeImmRBPReg(uint32(arg.Offset + 16)))
+			mem := newOperandMem(m.newAmodeImmRBPReg(uint32(arg.Offset + 16)))
 			switch arg.Type {
 			case ssa.TypeI32:
 				load.asMovzxRmR(extModeLQ, mem, reg)
@@ -156,7 +156,7 @@ func (m *machine) LowerReturns(rets []ssa.Value) {
 
 			// Store the value to the return stack slot above the current RBP.
 			store := m.allocateInstr()
-			mem := newOperandMem(newAmodeImmRBPReg(uint32(a.ArgStackSize + 16 + r.Offset)))
+			mem := newOperandMem(m.newAmodeImmRBPReg(uint32(a.ArgStackSize + 16 + r.Offset)))
 			switch r.Type {
 			case ssa.TypeI32:
 				store.asMovRM(reg, mem, 4)
diff --git a/internal/engine/wazevo/backend/isa/amd64/abi_entry_preamble.go b/internal/engine/wazevo/backend/isa/amd64/abi_entry_preamble.go
index 6866fabe..18116b15 100644
--- a/internal/engine/wazevo/backend/isa/amd64/abi_entry_preamble.go
+++ b/internal/engine/wazevo/backend/isa/amd64/abi_entry_preamble.go
@@ -125,7 +125,7 @@ func (m *machine) move64(src, dst regalloc.VReg, prev *instruction) *instruction
 }
 
 func (m *machine) loadOrStore64AtExecutionCtx(execCtx regalloc.VReg, offset wazevoapi.Offset, r regalloc.VReg, store bool, prev *instruction) *instruction {
-	mem := newOperandMem(newAmodeImmReg(offset.U32(), execCtx))
+	mem := newOperandMem(m.newAmodeImmReg(offset.U32(), execCtx))
 	instr := m.allocateInstr()
 	if store {
 		instr.asMovRM(r, mem, 8)
@@ -158,7 +158,7 @@ func (m *machine) goEntryPreamblePassArg(cur *instruction, paramSlicePtr regallo
 	}
 
 	load := m.allocateInstr()
-	a := newOperandMem(newAmodeImmReg(offsetInParamSlice, paramSlicePtr))
+	a := newOperandMem(m.newAmodeImmReg(offsetInParamSlice, paramSlicePtr))
 	switch arg.Type {
 	case ssa.TypeI32:
 		load.asMovzxRmR(extModeLQ, a, dst)
@@ -176,7 +176,7 @@ func (m *machine) goEntryPreamblePassArg(cur *instruction, paramSlicePtr regallo
 	if arg.Kind == backend.ABIArgKindStack {
 		// Store back to the stack.
 		store := m.allocateInstr()
-		a := newOperandMem(newAmodeImmReg(uint32(arg.Offset), rspVReg))
+		a := newOperandMem(m.newAmodeImmReg(uint32(arg.Offset), rspVReg))
 		switch arg.Type {
 		case ssa.TypeI32:
 			store.asMovRM(dst, a, 4)
@@ -200,7 +200,7 @@ func (m *machine) goEntryPreamblePassResult(cur *instruction, resultSlicePtr reg
 		// Load the value to the temporary.
 		load := m.allocateInstr()
 		offset := resultStackSlotBeginOffset + uint32(result.Offset)
-		a := newOperandMem(newAmodeImmReg(offset, rspVReg))
+		a := newOperandMem(m.newAmodeImmReg(offset, rspVReg))
 		switch result.Type {
 		case ssa.TypeI32:
 			r = tmpIntReg
@@ -226,7 +226,7 @@ func (m *machine) goEntryPreamblePassResult(cur *instruction, resultSlicePtr reg
 	}
 
 	store := m.allocateInstr()
-	a := newOperandMem(newAmodeImmReg(offsetInResultSlice, resultSlicePtr))
+	a := newOperandMem(m.newAmodeImmReg(offsetInResultSlice, resultSlicePtr))
 	switch result.Type {
 	case ssa.TypeI32:
 		store.asMovRM(r, a, 4)
diff --git a/internal/engine/wazevo/backend/isa/amd64/abi_go_call.go b/internal/engine/wazevo/backend/isa/amd64/abi_go_call.go
index 73e6798a..b1fb32dd 100644
--- a/internal/engine/wazevo/backend/isa/amd64/abi_go_call.go
+++ b/internal/engine/wazevo/backend/isa/amd64/abi_go_call.go
@@ -56,7 +56,7 @@ func (m *machine) CompileGoFunctionTrampoline(exitCode wazevoapi.ExitCode, sig *
 
 	if needModuleContextPtr {
 		moduleCtrPtr := rbxVReg // Module context is always the second argument.
-		mem := newAmodeImmReg(
+		mem := m.newAmodeImmReg(
 			wazevoapi.ExecutionContextOffsetGoFunctionCallCalleeModuleContextOpaque.U32(),
 			execCtrPtr)
 		store := m.allocateInstr().asMovRM(moduleCtrPtr, newOperandMem(mem), 8)
@@ -104,7 +104,7 @@ func (m *machine) CompileGoFunctionTrampoline(exitCode wazevoapi.ExitCode, sig *
 			} else {
 				v = xmm15VReg
 			}
-			mem := newOperandMem(newAmodeImmReg(uint32(arg.Offset+16 /* to skip caller_rbp and ret_addr */), rbpVReg))
+			mem := newOperandMem(m.newAmodeImmReg(uint32(arg.Offset+16 /* to skip caller_rbp and ret_addr */), rbpVReg))
 			load := m.allocateInstr()
 			switch arg.Type {
 			case ssa.TypeI32:
@@ -124,7 +124,7 @@ func (m *machine) CompileGoFunctionTrampoline(exitCode wazevoapi.ExitCode, sig *
 		}
 
 		store := m.allocateInstr()
-		mem := newOperandMem(newAmodeImmReg(uint32(offsetInGoSlice), rspVReg))
+		mem := newOperandMem(m.newAmodeImmReg(uint32(offsetInGoSlice), rspVReg))
 		switch arg.Type {
 		case ssa.TypeI32:
 			store.asMovRM(v, mem, 4)
@@ -213,7 +213,7 @@ func (m *machine) CompileGoFunctionTrampoline(exitCode wazevoapi.ExitCode, sig *
 		}
 
 		load := m.allocateInstr()
-		mem := newOperandMem(newAmodeImmReg(uint32(offsetInGoSlice), rspVReg))
+		mem := newOperandMem(m.newAmodeImmReg(uint32(offsetInGoSlice), rspVReg))
 		switch r.Type {
 		case ssa.TypeI32:
 			load.asMovzxRmR(extModeLQ, mem, v)
@@ -238,7 +238,7 @@ func (m *machine) CompileGoFunctionTrampoline(exitCode wazevoapi.ExitCode, sig *
 		if !isRegResult {
 			// We need to store it back to the result slot above rbp.
 			store := m.allocateInstr()
-			mem := newOperandMem(newAmodeImmReg(uint32(abi.ArgStackSize+r.Offset+16 /* to skip caller_rbp and ret_addr */), rbpVReg))
+			mem := newOperandMem(m.newAmodeImmReg(uint32(abi.ArgStackSize+r.Offset+16 /* to skip caller_rbp and ret_addr */), rbpVReg))
 			switch r.Type {
 			case ssa.TypeI32:
 				store.asMovRM(v, mem, 4)
@@ -263,7 +263,7 @@ func (m *machine) CompileGoFunctionTrampoline(exitCode wazevoapi.ExitCode, sig *
 	if argOverlapWithExecCtxOffset >= 0 {
 		// At this point execCtt is not used anymore, so we can finally store the
 		// result to the register which overlaps with the execution context pointer.
-		mem := newOperandMem(newAmodeImmReg(uint32(argOverlapWithExecCtxOffset), rspVReg))
+		mem := newOperandMem(m.newAmodeImmReg(uint32(argOverlapWithExecCtxOffset), rspVReg))
 		load := m.allocateInstr().asMov64MR(mem, execCtrPtr)
 		cur = linkInstr(cur, load)
 	}
@@ -280,7 +280,7 @@ func (m *machine) saveRegistersInExecutionContext(cur *instruction, execCtx rega
 	offset := wazevoapi.ExecutionContextOffsetSavedRegistersBegin.I64()
 	for _, v := range regs {
 		store := m.allocateInstr()
-		mem := newOperandMem(newAmodeImmReg(uint32(offset), execCtx))
+		mem := newOperandMem(m.newAmodeImmReg(uint32(offset), execCtx))
 		switch v.RegType() {
 		case regalloc.RegTypeInt:
 			store.asMovRM(v, mem, 8)
@@ -299,7 +299,7 @@ func (m *machine) restoreRegistersInExecutionContext(cur *instruction, execCtx r
 	offset := wazevoapi.ExecutionContextOffsetSavedRegistersBegin.I64()
 	for _, v := range regs {
 		load := m.allocateInstr()
-		mem := newOperandMem(newAmodeImmReg(uint32(offset), execCtx))
+		mem := newOperandMem(m.newAmodeImmReg(uint32(offset), execCtx))
 		switch v.RegType() {
 		case regalloc.RegTypeInt:
 			load.asMov64MR(mem, v)
@@ -321,17 +321,17 @@ func (m *machine) storeReturnAddressAndExit(cur *instruction, execCtx regalloc.V
 	ripReg := r12VReg // Callee saved which is already saved.
 	saveRip := m.allocateInstr().asMovRM(
 		ripReg,
-		newOperandMem(newAmodeImmReg(wazevoapi.ExecutionContextOffsetGoCallReturnAddress.U32(), execCtx)),
+		newOperandMem(m.newAmodeImmReg(wazevoapi.ExecutionContextOffsetGoCallReturnAddress.U32(), execCtx)),
 		8,
 	)
 	cur = linkInstr(cur, saveRip)
 
-	exit := m.allocateInstr().asExitSeq(execCtx)
+	exit := m.allocateExitSeq(execCtx)
 	cur = linkInstr(cur, exit)
 
 	nop, l := m.allocateBrTarget()
 	cur = linkInstr(cur, nop)
-	readRip.asLEA(newAmodeRipRelative(l), ripReg)
+	readRip.asLEA(m.newAmodeRipRelative(l), ripReg)
 	return cur
 }
 
@@ -402,7 +402,7 @@ func (m *machine) insertStackBoundsCheck(requiredStackSize int64, cur *instructi
 	// .cont:
 	cur = m.addRSP(-int32(requiredStackSize), cur)
 	cur = linkInstr(cur, m.allocateInstr().asCmpRmiR(true,
-		newOperandMem(newAmodeImmReg(wazevoapi.ExecutionContextOffsetStackBottomPtr.U32(), raxVReg)),
+		newOperandMem(m.newAmodeImmReg(wazevoapi.ExecutionContextOffsetStackBottomPtr.U32(), raxVReg)),
 		rspVReg, true))
 
 	ja := m.allocateInstr()
@@ -417,11 +417,11 @@ func (m *machine) insertStackBoundsCheck(requiredStackSize int64, cur *instructi
 	cur = linkInstr(cur, m.allocateInstr().asImm(r15VReg, uint64(requiredStackSize), true))
 	// Set the required size in the execution context.
 	cur = linkInstr(cur, m.allocateInstr().asMovRM(r15VReg,
-		newOperandMem(newAmodeImmReg(wazevoapi.ExecutionContextOffsetStackGrowRequiredSize.U32(), raxVReg)), 8))
+		newOperandMem(m.newAmodeImmReg(wazevoapi.ExecutionContextOffsetStackGrowRequiredSize.U32(), raxVReg)), 8))
 	// Restore the temporary.
 	cur = linkInstr(cur, m.allocateInstr().asPop64(r15VReg))
 	// Call the Go function to grow the stack.
-	cur = linkInstr(cur, m.allocateInstr().asCallIndirect(newOperandMem(newAmodeImmReg(
+	cur = linkInstr(cur, m.allocateInstr().asCallIndirect(newOperandMem(m.newAmodeImmReg(
 		wazevoapi.ExecutionContextOffsetStackGrowCallTrampolineAddress.U32(), raxVReg)), nil))
 	// Jump to the continuation.
 	jmpToCont := m.allocateInstr()
diff --git a/internal/engine/wazevo/backend/isa/amd64/instr.go b/internal/engine/wazevo/backend/isa/amd64/instr.go
index c8259754..a86af5d1 100644
--- a/internal/engine/wazevo/backend/isa/amd64/instr.go
+++ b/internal/engine/wazevo/backend/isa/amd64/instr.go
@@ -2,7 +2,6 @@ package amd64
 
 import (
 	"fmt"
-	"strings"
 
 	"github.com/tetratelabs/wazero/internal/engine/wazevo/backend"
 	"github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc"
@@ -14,10 +13,9 @@ type instruction struct {
 	abi                 *backend.FunctionABI
 	op1, op2            operand
 	u1, u2              uint64
-	b1, b2, b3          bool
+	b1                  bool
 	addedBeforeRegAlloc bool
 	kind                instructionKind
-	targets             []uint32
 }
 
 // Next implements regalloc.Instr.
@@ -216,11 +214,7 @@ func (i *instruction) String() string {
 	case jmpIf:
 		return fmt.Sprintf("j%s %s", cond(i.u1), i.op1.format(true))
 	case jmpTableIsland:
-		labels := make([]string, len(i.targets))
-		for index, l := range i.targets {
-			labels[index] = backend.Label(l).String()
-		}
-		return fmt.Sprintf("jump_table_island [%s]", strings.Join(labels, ", "))
+		return fmt.Sprintf("jump_table_island: jmp_table_index=%d", i.u1)
 	case exitSequence:
 		return fmt.Sprintf("exit_sequence %s", i.op1.format(true))
 	case ud2:
@@ -240,24 +234,24 @@ func (i *instruction) String() string {
 	case zeros:
 		return fmt.Sprintf("xor %s, %s", i.op2.format(true), i.op2.format(true))
 	case fcvtToSintSequence:
-		cmpOp, truncOp, execCtx, src, dst, tmpGp, tmpGp2, tmpXmm, _, dst64, sat := i.fcvtToSintSequenceData()
+		execCtx, src, tmpGp, tmpGp2, tmpXmm, src64, dst64, sat := i.fcvtToSintSequenceData()
 		return fmt.Sprintf(
-			"fcvtToSintSequence %s, %s, execCtx=%s, src=%s, dst=%s, tmpGp=%s, tmpGp2=%s, tmpXmm=%s, sat=%v",
-			cmpOp, truncOp, formatVRegSized(execCtx, true),
-			formatVRegSized(src, true), formatVRegSized(dst, dst64),
+			"fcvtToSintSequence execCtx=%s, src=%s, tmpGp=%s, tmpGp2=%s, tmpXmm=%s, src64=%v, dst64=%v, sat=%v",
+			formatVRegSized(execCtx, true),
+			formatVRegSized(src, true),
 			formatVRegSized(tmpGp, true),
 			formatVRegSized(tmpGp2, true),
-			formatVRegSized(tmpXmm, true), sat)
+			formatVRegSized(tmpXmm, true), src64, dst64, sat)
 	case fcvtToUintSequence:
-		subOp, cmpOp, truncOp, execCtx, src, dst, tmpGp, tmpGp2, tmpXmm, tmpXmm2, _, dst64, sat := i.fcvtToUintSequenceData()
+		execCtx, src, tmpGp, tmpGp2, tmpXmm, tmpXmm2, src64, dst64, sat := i.fcvtToUintSequenceData()
 		return fmt.Sprintf(
-			"fcvtToUintSequence %s, %s, %s, execCtx=%s, src=%s, dst=%s, tmpGp=%s, tmpGp2=%s, tmpXmm=%s, tmpXmm2=%s, sat=%v",
-			subOp, cmpOp, truncOp, formatVRegSized(execCtx, true),
-			formatVRegSized(src, true), formatVRegSized(dst, dst64),
+			"fcvtToUintSequence execCtx=%s, src=%s, tmpGp=%s, tmpGp2=%s, tmpXmm=%s, tmpXmm2=%s, src64=%v, dst64=%v, sat=%v",
+			formatVRegSized(execCtx, true),
+			formatVRegSized(src, true),
 			formatVRegSized(tmpGp, true),
 			formatVRegSized(tmpGp2, true),
 			formatVRegSized(tmpXmm, true),
-			formatVRegSized(tmpXmm2, true), sat)
+			formatVRegSized(tmpXmm2, true), src64, dst64, sat)
 	case idivRemSequence:
 		execCtx, divisor, tmpGp, isDiv, signed, _64 := i.idivRemSequenceData()
 		return fmt.Sprintf("idivRemSequence execCtx=%s, divisor=%s, tmpGp=%s, isDiv=%v, signed=%v, _64=%v",
@@ -277,18 +271,9 @@ func (i *instruction) Defs(regs *[]regalloc.VReg) []regalloc.VReg {
 	switch dk := defKinds[i.kind]; dk {
 	case defKindNone:
 	case defKindOp2:
-		if !i.op2.r.Valid() {
-			panic("BUG " + i.op2.format(true) + " invalid in " + i.String())
-		}
-		*regs = append(*regs, i.op2.r)
+		*regs = append(*regs, i.op2.reg())
 	case defKindCall:
 		*regs = append(*regs, i.abi.RetRealRegs...)
-	case defKindFcvtToSintSequence:
-		_, _, _, _, dst, _, _, _, _, _, _ := i.fcvtToSintSequenceData()
-		*regs = append(*regs, dst)
-	case defKindFcvtToUintSequence:
-		_, _, _, _, _, dst, _, _, _, _, _, _, _ := i.fcvtToUintSequenceData()
-		*regs = append(*regs, dst)
 	case defKindDivRem:
 		_, _, _, isDiv, _, _ := i.idivRemSequenceData()
 		if isDiv {
@@ -316,9 +301,9 @@ func (i *instruction) Uses(regs *[]regalloc.VReg) []regalloc.VReg {
 		// the source operand (op1) can be imm32, reg or mem.
 		switch opAny.kind {
 		case operandKindReg:
-			*regs = append(*regs, opAny.r)
+			*regs = append(*regs, opAny.reg())
 		case operandKindMem:
-			opAny.amode.uses(regs)
+			opAny.addressMode().uses(regs)
 		case operandKindImm32:
 		default:
 			panic(fmt.Sprintf("BUG: invalid operand: %s", i))
@@ -326,14 +311,14 @@ func (i *instruction) Uses(regs *[]regalloc.VReg) []regalloc.VReg {
 		if opReg.kind != operandKindReg {
 			panic(fmt.Sprintf("BUG: invalid operand: %s", i))
 		}
-		*regs = append(*regs, opReg.r)
+		*regs = append(*regs, opReg.reg())
 	case useKindOp1:
 		op := i.op1
 		switch op.kind {
 		case operandKindReg:
-			*regs = append(*regs, op.r)
+			*regs = append(*regs, op.reg())
 		case operandKindMem:
-			op.amode.uses(regs)
+			op.addressMode().uses(regs)
 		case operandKindImm32, operandKindLabel:
 		default:
 			panic(fmt.Sprintf("BUG: invalid operand: %s", i))
@@ -342,9 +327,9 @@ func (i *instruction) Uses(regs *[]regalloc.VReg) []regalloc.VReg {
 		op := i.op1
 		switch op.kind {
 		case operandKindReg:
-			*regs = append(*regs, op.r)
+			*regs = append(*regs, op.reg())
 		case operandKindMem:
-			op.amode.uses(regs)
+			op.addressMode().uses(regs)
 		default:
 			panic(fmt.Sprintf("BUG: invalid operand: %s", i))
 		}
@@ -352,10 +337,10 @@ func (i *instruction) Uses(regs *[]regalloc.VReg) []regalloc.VReg {
 	case useKindCall:
 		*regs = append(*regs, i.abi.ArgRealRegs...)
 	case useKindFcvtToSintSequence:
-		_, _, execCtx, src, _, tmpGp, tmpGp2, tmpXmm, _, _, _ := i.fcvtToSintSequenceData()
+		execCtx, src, tmpGp, tmpGp2, tmpXmm, _, _, _ := i.fcvtToSintSequenceData()
 		*regs = append(*regs, execCtx, src, tmpGp, tmpGp2, tmpXmm)
 	case useKindFcvtToUintSequence:
-		_, _, _, execCtx, src, _, tmpGp, tmpGp2, tmpXmm, tmpXmm2, _, _, _ := i.fcvtToUintSequenceData()
+		execCtx, src, tmpGp, tmpGp2, tmpXmm, tmpXmm2, _, _, _ := i.fcvtToUintSequenceData()
 		*regs = append(*regs, execCtx, src, tmpGp, tmpGp2, tmpXmm, tmpXmm2)
 	case useKindDivRem:
 		execCtx, divisor, tmpGp, _, _, _ := i.idivRemSequenceData()
@@ -378,9 +363,9 @@ func (i *instruction) AssignUse(index int, v regalloc.VReg) {
 		op := &i.op1
 		switch op.kind {
 		case operandKindReg:
-			op.r = v
+			op.setReg(v)
 		case operandKindMem:
-			op.amode.assignUses(index, v)
+			op.addressMode().assignUses(index, v)
 		default:
 			panic("BUG")
 		}
@@ -392,36 +377,24 @@ func (i *instruction) AssignUse(index int, v regalloc.VReg) {
 		switch op.kind {
 		case operandKindReg:
 			if index == 0 {
-				if op.r.IsRealReg() {
-					panic("BUG already assigned: " + i.String())
-				}
-				op.r = v
+				op.setReg(v)
 			} else if index == 1 {
-				if opMustBeReg.r.IsRealReg() {
-					panic("BUG already assigned: " + i.String())
-				}
-				opMustBeReg.r = v
+				opMustBeReg.setReg(v)
 			} else {
 				panic("BUG")
 			}
 		case operandKindMem:
-			nregs := op.amode.nregs()
+			nregs := op.addressMode().nregs()
 			if index < nregs {
-				op.amode.assignUses(index, v)
+				op.addressMode().assignUses(index, v)
 			} else if index == nregs {
-				if opMustBeReg.r.IsRealReg() {
-					panic("BUG already assigned: " + i.String())
-				}
-				opMustBeReg.r = v
+				opMustBeReg.setReg(v)
 			} else {
 				panic("BUG")
 			}
 		case operandKindImm32:
 			if index == 0 {
-				if opMustBeReg.r.IsRealReg() {
-					panic("BUG already assigned: " + i.String())
-				}
-				opMustBeReg.r = v
+				opMustBeReg.setReg(v)
 			} else {
 				panic("BUG")
 			}
@@ -435,44 +408,41 @@ func (i *instruction) AssignUse(index int, v regalloc.VReg) {
 			if index != 0 {
 				panic("BUG")
 			}
-			if op.r.IsRealReg() {
-				panic("BUG already assigned: " + i.String())
-			}
-			op.r = v
+			op.setReg(v)
 		case operandKindMem:
-			op.amode.assignUses(index, v)
+			op.addressMode().assignUses(index, v)
 		default:
 			panic(fmt.Sprintf("BUG: invalid operand: %s", i))
 		}
 	case useKindFcvtToSintSequence:
 		switch index {
 		case 0:
-			i.op1.amode.base = v
+			i.op1.addressMode().base = v
 		case 1:
-			i.op1.amode.index = v
+			i.op1.addressMode().index = v
 		case 2:
-			i.op2.amode.base = v
+			i.op2.addressMode().base = v
 		case 3:
-			i.op2.amode.index = v
+			i.op2.addressMode().index = v
 		case 4:
-			i.op2.r = v
+			i.u1 = uint64(v)
 		default:
 			panic("BUG")
 		}
 	case useKindFcvtToUintSequence:
 		switch index {
 		case 0:
-			i.op1.amode.base = v
+			i.op1.addressMode().base = v
 		case 1:
-			i.op1.amode.index = v
+			i.op1.addressMode().index = v
 		case 2:
-			i.op2.amode.base = v
+			i.op2.addressMode().base = v
 		case 3:
-			i.u2 = uint64(v)
+			i.op2.addressMode().index = v
 		case 4:
-			i.op2.amode.index = v
+			i.u1 = uint64(v)
 		case 5:
-			i.op2.r = v
+			i.u2 = uint64(v)
 		default:
 			panic("BUG")
 		}
@@ -487,11 +457,11 @@ func (i *instruction) AssignUse(index int, v regalloc.VReg) {
 				panic("BUG")
 			}
 		case 2:
-			i.op1.amode.base = v
+			i.op1.setReg(v)
 		case 3:
-			i.op1.amode.index = v
+			i.op2.setReg(v)
 		case 4:
-			i.op2.amode.base = v
+			i.u1 = uint64(v)
 		default:
 			panic("BUG")
 		}
@@ -505,12 +475,7 @@ func (i *instruction) AssignDef(reg regalloc.VReg) {
 	switch dk := defKinds[i.kind]; dk {
 	case defKindNone:
 	case defKindOp2:
-		if !i.op2.r.Valid() {
-			panic("BUG already assigned" + i.String())
-		}
-		i.op2.r = reg
-	case defKindFcvtToSintSequence, defKindFcvtToUintSequence:
-		i.op1.r = reg
+		i.op2.setReg(reg)
 	default:
 		panic(fmt.Sprintf("BUG: invalid defKind \"%s\" for %s", dk, i))
 	}
@@ -738,17 +703,20 @@ const (
 	instrMax
 )
 
-func (i *instruction) asIdivRemSequence(
-	execCtx, divisor, tmpGp regalloc.VReg,
-	isDiv, signed, _64 bool,
-) *instruction {
+func (i *instruction) asIdivRemSequence(execCtx, divisor, tmpGp regalloc.VReg, isDiv, signed, _64 bool) *instruction {
 	i.kind = idivRemSequence
-	i.op1.amode.base = execCtx
-	i.op1.amode.index = divisor
-	i.op2.amode.base = tmpGp
-	i.b1 = isDiv
-	i.b2 = signed
-	i.b3 = _64
+	i.op1 = newOperandReg(execCtx)
+	i.op2 = newOperandReg(divisor)
+	i.u1 = uint64(tmpGp)
+	if isDiv {
+		i.u2 |= 1
+	}
+	if signed {
+		i.u2 |= 2
+	}
+	if _64 {
+		i.u2 |= 4
+	}
 	return i
 }
 
@@ -758,7 +726,7 @@ func (i *instruction) idivRemSequenceData() (
 	if i.kind != idivRemSequence {
 		panic("BUG")
 	}
-	return i.op1.amode.base, i.op1.amode.index, i.op2.amode.base, i.b1, i.b2, i.b3
+	return i.op1.reg(), i.op2.reg(), regalloc.VReg(i.u1), i.u2&1 != 0, i.u2&2 != 0, i.u2&4 != 0
 }
 
 func (i *instruction) asXmmCMov(cc cond, x operand, rd regalloc.VReg, size byte) *instruction {
@@ -776,64 +744,88 @@ func (i *instruction) asDefineUninitializedReg(r regalloc.VReg) *instruction {
 	return i
 }
 
-func (i *instruction) asFcvtToUintSequence(
-	subOp, cmpOp, truncOp sseOpcode,
-	execCtx, src, dst, tmpGp, tmpGp2, tmpXmm, tmpXmm2 regalloc.VReg,
+func (m *machine) allocateFcvtToUintSequence(
+	execCtx, src, tmpGp, tmpGp2, tmpXmm, tmpXmm2 regalloc.VReg,
 	src64, dst64, sat bool,
 ) *instruction {
-	i.u1 = uint64(subOp) | uint64(cmpOp)<<8 | uint64(truncOp)<<16
+	i := m.allocateInstr()
 	i.kind = fcvtToUintSequence
-	i.op1.amode.base = execCtx
-	i.op1.amode.index = src
-	i.op1.r = dst
-	i.op2.amode.base = tmpGp
-	i.op2.amode.index = tmpXmm
-	i.op2.r = tmpXmm2
-	i.u2 = uint64(tmpGp2)
-	i.b1 = src64
-	i.b2 = dst64
-	i.b3 = sat
+	op1a := m.amodePool.Allocate()
+	op2a := m.amodePool.Allocate()
+	i.op1 = newOperandMem(op1a)
+	i.op2 = newOperandMem(op2a)
+	if src64 {
+		op1a.imm32 = 1
+	} else {
+		op1a.imm32 = 0
+	}
+	if dst64 {
+		op1a.imm32 |= 2
+	}
+	if sat {
+		op1a.imm32 |= 4
+	}
+
+	op1a.base = execCtx
+	op1a.index = src
+	op2a.base = tmpGp
+	op2a.index = tmpGp2
+	i.u1 = uint64(tmpXmm)
+	i.u2 = uint64(tmpXmm2)
 	return i
 }
 
 func (i *instruction) fcvtToUintSequenceData() (
-	subOp, cmpOp, truncOp sseOpcode, execCtx, src, dst, tmpGp, tmpGp2, tmpXmm, tmpXmm2 regalloc.VReg, src64, dst64, sat bool,
+	execCtx, src, tmpGp, tmpGp2, tmpXmm, tmpXmm2 regalloc.VReg, src64, dst64, sat bool,
 ) {
 	if i.kind != fcvtToUintSequence {
 		panic("BUG")
 	}
-	return sseOpcode(i.u1), sseOpcode(i.u1 >> 8), sseOpcode(i.u1 >> 16), i.op1.amode.base, i.op1.amode.index,
-		i.op1.r, i.op2.amode.base, regalloc.VReg(i.u2), i.op2.amode.index, i.op2.r, i.b1, i.b2, i.b3
+	op1a := i.op1.addressMode()
+	op2a := i.op2.addressMode()
+	return op1a.base, op1a.index, op2a.base, op2a.index, regalloc.VReg(i.u1), regalloc.VReg(i.u2),
+		op1a.imm32&1 != 0, op1a.imm32&2 != 0, op1a.imm32&4 != 0
 }
 
-func (i *instruction) asFcvtToSintSequence(
-	cmpOp, truncOp sseOpcode,
-	execCtx, src, dst, tmpGp, tmpGp2, tmpXmm regalloc.VReg,
+func (m *machine) allocateFcvtToSintSequence(
+	execCtx, src, tmpGp, tmpGp2, tmpXmm regalloc.VReg,
 	src64, dst64, sat bool,
 ) *instruction {
-	i.u1 = uint64(cmpOp)
-	i.u2 = uint64(truncOp)
+	i := m.allocateInstr()
 	i.kind = fcvtToSintSequence
-	i.op1.amode.base = execCtx
-	i.op1.amode.index = src
-	i.op1.r = dst
-	i.op2.amode.base = tmpGp
-	i.op2.amode.index = tmpGp2
-	i.op2.r = tmpXmm
-	i.b1 = src64
-	i.b2 = dst64
-	i.b3 = sat
+	op1a := m.amodePool.Allocate()
+	op2a := m.amodePool.Allocate()
+	i.op1 = newOperandMem(op1a)
+	i.op2 = newOperandMem(op2a)
+	op1a.base = execCtx
+	op1a.index = src
+	op2a.base = tmpGp
+	op2a.index = tmpGp2
+	i.u1 = uint64(tmpXmm)
+	if src64 {
+		i.u2 = 1
+	} else {
+		i.u2 = 0
+	}
+	if dst64 {
+		i.u2 |= 2
+	}
+	if sat {
+		i.u2 |= 4
+	}
 	return i
 }
 
 func (i *instruction) fcvtToSintSequenceData() (
-	cmpOp, truncOp sseOpcode, execCtx, src, dst, tmpGp, tmpGp2, tmpXmm regalloc.VReg, src64, dst64, sat bool,
+	execCtx, src, tmpGp, tmpGp2, tmpXmm regalloc.VReg, src64, dst64, sat bool,
 ) {
 	if i.kind != fcvtToSintSequence {
 		panic("BUG")
 	}
-	return sseOpcode(i.u1), sseOpcode(i.u2), i.op1.amode.base, i.op1.amode.index,
-		i.op1.r, i.op2.amode.base, i.op2.amode.index, i.op2.r, i.b1, i.b2, i.b3
+	op1a := i.op1.addressMode()
+	op2a := i.op2.addressMode()
+	return op1a.base, op1a.index, op2a.base, op2a.index, regalloc.VReg(i.u1),
+		i.u2&1 != 0, i.u2&2 != 0, i.u2&4 != 0
 }
 
 func (k instructionKind) String() string {
@@ -976,9 +968,12 @@ func (i *instruction) asJmpIf(cond cond, target operand) *instruction {
 	return i
 }
 
-func (i *instruction) asJmpTableSequence(targets []uint32) *instruction {
+// asJmpTableSequence is used to emit the jump table.
+// targetSliceIndex is the index of the target slice in machine.jmpTableTargets.
+func (i *instruction) asJmpTableSequence(targetSliceIndex int, targetCount int) *instruction {
 	i.kind = jmpTableIsland
-	i.targets = targets
+	i.u1 = uint64(targetSliceIndex)
+	i.u2 = uint64(targetCount)
 	return i
 }
 
@@ -997,7 +992,7 @@ func (i *instruction) jmpLabel() backend.Label {
 	}
 }
 
-func (i *instruction) asLEA(a amode, rd regalloc.VReg) *instruction {
+func (i *instruction) asLEA(a *amode, rd regalloc.VReg) *instruction {
 	i.kind = lea
 	i.op1 = newOperandMem(a)
 	i.op2 = newOperandReg(rd)
@@ -1265,9 +1260,12 @@ func (i *instruction) asCmove(c cond, rm operand, rd regalloc.VReg, _64 bool) *i
 	return i
 }
 
-func (i *instruction) asExitSeq(execCtx regalloc.VReg) *instruction {
+func (m *machine) allocateExitSeq(execCtx regalloc.VReg) *instruction {
+	i := m.allocateInstr()
 	i.kind = exitSequence
 	i.op1 = newOperandReg(execCtx)
+	// Allocate the address mode that will be used in encoding the exit sequence.
+	i.op2 = newOperandMem(m.amodePool.Allocate())
 	return i
 }
 
@@ -1928,8 +1926,6 @@ const (
 	defKindOp2
 	defKindCall
 	defKindDivRem
-	defKindFcvtToSintSequence
-	defKindFcvtToUintSequence
 )
 
 var defKinds = [instrMax]defKind{
@@ -1965,9 +1961,9 @@ var defKinds = [instrMax]defKind{
 	setcc:                  defKindOp2,
 	zeros:                  defKindOp2,
 	sourceOffsetInfo:       defKindNone,
-	fcvtToSintSequence:     defKindFcvtToSintSequence,
+	fcvtToSintSequence:     defKindNone,
 	defineUninitializedReg: defKindOp2,
-	fcvtToUintSequence:     defKindFcvtToUintSequence,
+	fcvtToUintSequence:     defKindNone,
 	xmmCMov:                defKindOp2,
 	idivRemSequence:        defKindDivRem,
 }
diff --git a/internal/engine/wazevo/backend/isa/amd64/instr_encoding.go b/internal/engine/wazevo/backend/isa/amd64/instr_encoding.go
index c590dda4..d37ee4e9 100644
--- a/internal/engine/wazevo/backend/isa/amd64/instr_encoding.go
+++ b/internal/engine/wazevo/backend/isa/amd64/instr_encoding.go
@@ -15,7 +15,7 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 	case ret:
 		encodeRet(c)
 	case imm:
-		dst := regEncodings[i.op2.r.RealReg()]
+		dst := regEncodings[i.op2.reg().RealReg()]
 		con := i.u1
 		if i.b1 { // 64 bit.
 			if lower32willSignExtendTo64(con) {
@@ -49,7 +49,7 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 			rex = rex.clearW()
 		}
 
-		dst := regEncodings[i.op2.r.RealReg()]
+		dst := regEncodings[i.op2.reg().RealReg()]
 
 		aluOp := aluRmiROpcode(i.u1)
 		if aluOp == aluRmiROpcodeMul {
@@ -57,13 +57,13 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 			const regMemOpc, regMemOpcNum = 0x0FAF, 2
 			switch op1.kind {
 			case operandKindReg:
-				src := regEncodings[op1.r.RealReg()]
+				src := regEncodings[op1.reg().RealReg()]
 				encodeRegReg(c, legacyPrefixesNone, regMemOpc, regMemOpcNum, dst, src, rex)
 			case operandKindMem:
-				m := i.op1.amode
+				m := i.op1.addressMode()
 				encodeRegMem(c, legacyPrefixesNone, regMemOpc, regMemOpcNum, dst, m, rex)
 			case operandKindImm32:
-				imm8 := lower8willSignExtendTo32(op1.imm32)
+				imm8 := lower8willSignExtendTo32(op1.imm32())
 				var opc uint32
 				if imm8 {
 					opc = 0x6b
@@ -72,9 +72,9 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 				}
 				encodeRegReg(c, legacyPrefixesNone, opc, 1, dst, dst, rex)
 				if imm8 {
-					c.EmitByte(byte(op1.imm32))
+					c.EmitByte(byte(op1.imm32()))
 				} else {
-					c.Emit4Bytes(op1.imm32)
+					c.Emit4Bytes(op1.imm32())
 				}
 			default:
 				panic("BUG: invalid operand kind")
@@ -100,13 +100,13 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 			op1 := i.op1
 			switch op1.kind {
 			case operandKindReg:
-				src := regEncodings[op1.r.RealReg()]
+				src := regEncodings[op1.reg().RealReg()]
 				encodeRegReg(c, legacyPrefixesNone, opcR, opcodeNum, src, dst, rex)
 			case operandKindMem:
-				m := i.op1.amode
+				m := i.op1.addressMode()
 				encodeRegMem(c, legacyPrefixesNone, opcM, opcodeNum, dst, m, rex)
 			case operandKindImm32:
-				imm8 := lower8willSignExtendTo32(op1.imm32)
+				imm8 := lower8willSignExtendTo32(op1.imm32())
 				var opc uint32
 				if imm8 {
 					opc = 0x83
@@ -115,9 +115,9 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 				}
 				encodeRegReg(c, legacyPrefixesNone, opc, opcodeNum, regEnc(subOpcImm), dst, rex)
 				if imm8 {
-					c.EmitByte(byte(op1.imm32))
+					c.EmitByte(byte(op1.imm32()))
 				} else {
-					c.Emit4Bytes(op1.imm32)
+					c.Emit4Bytes(op1.imm32())
 				}
 			default:
 				panic("BUG: invalid operand kind")
@@ -125,8 +125,8 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 		}
 
 	case movRR:
-		src := regEncodings[i.op1.r.RealReg()]
-		dst := regEncodings[i.op2.r.RealReg()]
+		src := regEncodings[i.op1.reg().RealReg()]
+		dst := regEncodings[i.op2.reg().RealReg()]
 		var rex rexInfo
 		if i.b1 {
 			rex = rex.setW()
@@ -323,15 +323,15 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 			panic(fmt.Sprintf("Unsupported sseOpcode: %s", op))
 		}
 
-		dst := regEncodings[i.op2.r.RealReg()]
+		dst := regEncodings[i.op2.reg().RealReg()]
 
 		rex := rexInfo(0).clearW()
 		op1 := i.op1
 		if op1.kind == operandKindReg {
-			src := regEncodings[op1.r.RealReg()]
+			src := regEncodings[op1.reg().RealReg()]
 			encodeRegReg(c, legPrex, opcode, opcodeNum, dst, src, rex)
 		} else if i.op1.kind == operandKindMem {
-			m := i.op1.amode
+			m := i.op1.addressMode()
 			encodeRegMem(c, legPrex, opcode, opcodeNum, dst, m, rex)
 		} else {
 			panic("BUG: invalid operand kind")
@@ -358,14 +358,14 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 		} else {
 			rex = rex.clearW()
 		}
-		dst := regEncodings[i.op2.r.RealReg()]
+		dst := regEncodings[i.op2.reg().RealReg()]
 
 		op1 := i.op1
 		if op1.kind == operandKindReg {
-			src := regEncodings[op1.r.RealReg()]
+			src := regEncodings[op1.reg().RealReg()]
 			encodeRegReg(c, legPrefix, opcode, opcodeNum, dst, src, rex)
 		} else if i.op1.kind == operandKindMem {
-			m := i.op1.amode
+			m := i.op1.addressMode()
 			encodeRegMem(c, legPrefix, opcode, opcodeNum, dst, m, rex)
 		} else {
 			panic("BUG: invalid operand kind")
@@ -443,15 +443,15 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 			panic(fmt.Sprintf("Unsupported sseOpcode: %s", op))
 		}
 
-		dst := regEncodings[i.op2.r.RealReg()]
+		dst := regEncodings[i.op2.reg().RealReg()]
 
 		rex := rexInfo(0).clearW()
 		op1 := i.op1
 		if op1.kind == operandKindReg {
-			src := regEncodings[op1.r.RealReg()]
+			src := regEncodings[op1.reg().RealReg()]
 			encodeRegReg(c, prefix, opcode, opcodeNum, dst, src, rex)
 		} else if i.op1.kind == operandKindMem {
-			m := i.op1.amode
+			m := i.op1.addressMode()
 			encodeRegMem(c, prefix, opcode, opcodeNum, dst, m, rex)
 		} else {
 			panic("BUG: invalid operand kind")
@@ -473,13 +473,13 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 			prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0f3a0b, 3
 		}
 		rex := rexInfo(0).clearW()
-		dst := regEncodings[i.op2.r.RealReg()]
+		dst := regEncodings[i.op2.reg().RealReg()]
 		op1 := i.op1
 		if op1.kind == operandKindReg {
-			src := regEncodings[op1.r.RealReg()]
+			src := regEncodings[op1.reg().RealReg()]
 			encodeRegReg(c, prefix, opcode, opcodeNum, dst, src, rex)
 		} else if i.op1.kind == operandKindMem {
-			m := i.op1.amode
+			m := i.op1.addressMode()
 			encodeRegMem(c, prefix, opcode, opcodeNum, dst, m, rex)
 		} else {
 			panic("BUG: invalid operand kind")
@@ -508,7 +508,7 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 			panic(fmt.Sprintf("Unsupported unaryRmROpcode: %s", op))
 		}
 
-		dst := regEncodings[i.op2.r.RealReg()]
+		dst := regEncodings[i.op2.reg().RealReg()]
 
 		rex := rexInfo(0)
 		if i.b1 { // 64 bit.
@@ -518,10 +518,10 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 		}
 		op1 := i.op1
 		if op1.kind == operandKindReg {
-			src := regEncodings[op1.r.RealReg()]
+			src := regEncodings[op1.reg().RealReg()]
 			encodeRegReg(c, prefix, opcode, opcodeNum, dst, src, rex)
 		} else if i.op1.kind == operandKindMem {
-			m := i.op1.amode
+			m := i.op1.addressMode()
 			encodeRegMem(c, prefix, opcode, opcodeNum, dst, m, rex)
 		} else {
 			panic("BUG: invalid operand kind")
@@ -529,7 +529,7 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 
 	case not:
 		var prefix legacyPrefixes
-		src := regEncodings[i.op1.r.RealReg()]
+		src := regEncodings[i.op1.reg().RealReg()]
 		rex := rexInfo(0)
 		if i.b1 { // 64 bit.
 			rex = rexInfo(0).setW()
@@ -541,7 +541,7 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 
 	case neg:
 		var prefix legacyPrefixes
-		src := regEncodings[i.op1.r.RealReg()]
+		src := regEncodings[i.op1.reg().RealReg()]
 		rex := rexInfo(0)
 		if i.b1 { // 64 bit.
 			rex = rexInfo(0).setW()
@@ -567,10 +567,10 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 
 		divisor := i.op1
 		if divisor.kind == operandKindReg {
-			src := regEncodings[divisor.r.RealReg()]
+			src := regEncodings[divisor.reg().RealReg()]
 			encodeEncEnc(c, legacyPrefixesNone, 0xf7, 1, subopcode, uint8(src), rex)
 		} else if divisor.kind == operandKindMem {
-			m := divisor.amode
+			m := divisor.addressMode()
 			encodeEncMem(c, legacyPrefixesNone, 0xf7, 1, subopcode, m, rex)
 		} else {
 			panic("BUG: invalid operand kind")
@@ -598,10 +598,10 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 		// dst_hi is implicitly rdx.
 		src2 := i.op1
 		if src2.kind == operandKindReg {
-			src := regEncodings[src2.r.RealReg()]
+			src := regEncodings[src2.reg().RealReg()]
 			encodeEncEnc(c, prefix, 0xf7, 1, subopcode, uint8(src), rex)
 		} else if src2.kind == operandKindMem {
-			m := src2.amode
+			m := src2.addressMode()
 			encodeEncMem(c, prefix, 0xf7, 1, subopcode, m, rex)
 		} else {
 			panic("BUG: invalid operand kind")
@@ -657,10 +657,10 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 		}
 
 		op := i.op1
-		dst := regEncodings[i.op2.r.RealReg()]
+		dst := regEncodings[i.op2.reg().RealReg()]
 		switch op.kind {
 		case operandKindReg:
-			src := regEncodings[op.r.RealReg()]
+			src := regEncodings[op.reg().RealReg()]
 			if ext == extModeBL || ext == extModeBQ {
 				// Some destinations must be encoded with REX.R = 1.
 				if e := src.encoding(); e >= 4 && e <= 7 {
@@ -669,25 +669,25 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 			}
 			encodeRegReg(c, legacyPrefixesNone, opcode, opcodeNum, dst, src, rex)
 		case operandKindMem:
-			m := op.amode
+			m := op.addressMode()
 			encodeRegMem(c, legacyPrefixesNone, opcode, opcodeNum, dst, m, rex)
 		default:
 			panic("BUG: invalid operand kind")
 		}
 
 	case mov64MR:
-		m := i.op1.amode
-		encodeLoad64(c, m, i.op2.r.RealReg())
+		m := i.op1.addressMode()
+		encodeLoad64(c, m, i.op2.reg().RealReg())
 
 	case lea:
 		needsLabelResolution = true
-		a := i.op1.amode
-		dst := regEncodings[i.op2.r.RealReg()]
+		a := i.op1.addressMode()
+		dst := regEncodings[i.op2.reg().RealReg()]
 		encodeRegMem(c, legacyPrefixesNone, 0x8d, 1, dst, a, rexInfo(0).setW())
 
 	case movRM:
-		m := i.op2.amode
-		src := regEncodings[i.op1.r.RealReg()]
+		m := i.op2.addressMode()
+		src := regEncodings[i.op1.reg().RealReg()]
 
 		var rex rexInfo
 		switch i.u1 {
@@ -707,7 +707,7 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 		}
 
 	case shiftR:
-		src := regEncodings[i.op2.r.RealReg()]
+		src := regEncodings[i.op2.reg().RealReg()]
 		amount := i.op1
 
 		var opcode uint32
@@ -721,7 +721,7 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 
 		switch amount.kind {
 		case operandKindReg:
-			if amount.r != rcxVReg {
+			if amount.reg() != rcxVReg {
 				panic("BUG: invalid reg operand: must be rcx")
 			}
 			opcode, prefix = 0xd3, legacyPrefixesNone
@@ -729,14 +729,14 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 		case operandKindImm32:
 			opcode, prefix = 0xc1, legacyPrefixesNone
 			encodeEncEnc(c, prefix, opcode, 1, uint8(i.u1), uint8(src), rex)
-			c.EmitByte(byte(amount.imm32))
+			c.EmitByte(byte(amount.imm32()))
 		default:
 			panic("BUG: invalid operand kind")
 		}
 	case xmmRmiReg:
 		const legPrefix = legacyPrefixes0x66
 		rex := rexInfo(0).clearW()
-		dst := regEncodings[i.op2.r.RealReg()]
+		dst := regEncodings[i.op2.reg().RealReg()]
 
 		var opcode uint32
 		var regDigit uint8
@@ -766,7 +766,7 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 			}
 
 			encodeEncEnc(c, legPrefix, opcode, 2, regDigit, uint8(dst), rex)
-			imm32 := op1.imm32
+			imm32 := op1.imm32()
 			if imm32 > 0xff&imm32 {
 				panic("immediate value does not fit 1 byte")
 			}
@@ -794,10 +794,10 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 			}
 
 			if op1.kind == operandKindReg {
-				reg := regEncodings[op1.r.RealReg()]
+				reg := regEncodings[op1.reg().RealReg()]
 				encodeRegReg(c, legPrefix, opcode, 2, dst, reg, rex)
 			} else if op1.kind == operandKindMem {
-				m := op1.amode
+				m := op1.addressMode()
 				encodeRegMem(c, legPrefix, opcode, 2, dst, m, rex)
 			} else {
 				panic("BUG: invalid operand kind")
@@ -814,11 +814,11 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 		} else {
 			rex = rex.clearW()
 		}
-		dst := regEncodings[i.op2.r.RealReg()]
+		dst := regEncodings[i.op2.reg().RealReg()]
 		op1 := i.op1
 		switch op1.kind {
 		case operandKindReg:
-			reg := regEncodings[op1.r.RealReg()]
+			reg := regEncodings[op1.reg().RealReg()]
 			if isCmp {
 				opcode = 0x39
 			} else {
@@ -833,11 +833,11 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 			} else {
 				opcode = 0x85
 			}
-			m := op1.amode
+			m := op1.addressMode()
 			encodeRegMem(c, legacyPrefixesNone, opcode, 1, dst, m, rex)
 
 		case operandKindImm32:
-			imm32 := op1.imm32
+			imm32 := op1.imm32()
 			useImm8 := isCmp && lower8willSignExtendTo32(imm32)
 			var subopcode uint8
 
@@ -861,13 +861,13 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 		}
 	case setcc:
 		cc := cond(i.u1)
-		dst := regEncodings[i.op2.r.RealReg()]
+		dst := regEncodings[i.op2.reg().RealReg()]
 		rex := rexInfo(0).clearW().always()
 		opcode := uint32(0x0f90) + uint32(cc)
 		encodeEncEnc(c, legacyPrefixesNone, opcode, 2, 0, uint8(dst), rex)
 	case cmove:
 		cc := cond(i.u1)
-		dst := regEncodings[i.op2.r.RealReg()]
+		dst := regEncodings[i.op2.reg().RealReg()]
 		rex := rexInfo(0)
 		if i.b1 { // 64 bit.
 			rex = rex.setW()
@@ -878,10 +878,10 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 		src := i.op1
 		switch src.kind {
 		case operandKindReg:
-			srcReg := regEncodings[src.r.RealReg()]
+			srcReg := regEncodings[src.reg().RealReg()]
 			encodeRegReg(c, legacyPrefixesNone, opcode, 2, dst, srcReg, rex)
 		case operandKindMem:
-			m := src.amode
+			m := src.addressMode()
 			encodeRegMem(c, legacyPrefixesNone, opcode, 2, dst, m, rex)
 		default:
 			panic("BUG: invalid operand kind")
@@ -891,25 +891,25 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 
 		switch op.kind {
 		case operandKindReg:
-			dst := regEncodings[op.r.RealReg()]
+			dst := regEncodings[op.reg().RealReg()]
 			if dst.rexBit() > 0 {
 				c.EmitByte(rexEncodingDefault | 0x1)
 			}
 			c.EmitByte(0x50 | dst.encoding())
 		case operandKindMem:
-			m := op.amode
+			m := op.addressMode()
 			encodeRegMem(
 				c, legacyPrefixesNone, 0xff, 1, regEnc(6), m, rexInfo(0).clearW(),
 			)
 		case operandKindImm32:
 			c.EmitByte(0x68)
-			c.Emit4Bytes(op.imm32)
+			c.Emit4Bytes(op.imm32())
 		default:
 			panic("BUG: invalid operand kind")
 		}
 
 	case pop64:
-		dst := regEncodings[i.op1.r.RealReg()]
+		dst := regEncodings[i.op1.reg().RealReg()]
 		if dst.rexBit() > 0 {
 			c.EmitByte(rexEncodingDefault | 0x1)
 		}
@@ -940,8 +940,8 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 			panic(fmt.Sprintf("Unsupported sseOpcode: %s", sseOpcode(i.u1)))
 		}
 
-		dst := regEncodings[i.op1.r.RealReg()]
-		encodeRegMem(c, legPrefix, opcode, opcodeNum, dst, i.op2.amode, rexInfo(0).clearW())
+		dst := regEncodings[i.op1.reg().RealReg()]
+		encodeRegMem(c, legPrefix, opcode, opcodeNum, dst, i.op2.addressMode(), rexInfo(0).clearW())
 	case xmmLoadConst:
 		panic("TODO")
 	case xmmToGpr:
@@ -972,8 +972,8 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 		} else {
 			rex = rex.clearW()
 		}
-		src := regEncodings[i.op1.r.RealReg()]
-		dst := regEncodings[i.op2.r.RealReg()]
+		src := regEncodings[i.op1.reg().RealReg()]
+		dst := regEncodings[i.op2.reg().RealReg()]
 		if argSwap {
 			src, dst = dst, src
 		}
@@ -1011,15 +1011,15 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 			panic(fmt.Sprintf("Unsupported sseOpcode: %s", op))
 		}
 
-		dst := regEncodings[i.op2.r.RealReg()]
+		dst := regEncodings[i.op2.reg().RealReg()]
 		op1 := i.op1
 		switch op1.kind {
 		case operandKindReg:
-			reg := regEncodings[op1.r.RealReg()]
+			reg := regEncodings[op1.reg().RealReg()]
 			encodeRegReg(c, prefix, opcode, opcodeNum, dst, reg, rex)
 
 		case operandKindMem:
-			m := op1.amode
+			m := op1.addressMode()
 			encodeRegMem(c, prefix, opcode, opcodeNum, dst, m, rex)
 
 		default:
@@ -1040,16 +1040,16 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 			fallthrough
 		case operandKindImm32:
 			c.EmitByte(0xe9)
-			c.Emit4Bytes(op.imm32)
+			c.Emit4Bytes(op.imm32())
 		case operandKindMem:
-			m := op.amode
+			m := op.addressMode()
 			encodeRegMem(c,
 				legacyPrefixesNone,
 				regMemOpcode, regMemOpcodeNum,
 				regMemSubOpcode, m, rexInfo(0).clearW(),
 			)
 		case operandKindReg:
-			r := op.r.RealReg()
+			r := op.reg().RealReg()
 			encodeRegReg(
 				c,
 				legacyPrefixesNone,
@@ -1070,23 +1070,29 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 		case operandKindImm32:
 			c.EmitByte(0x0f)
 			c.EmitByte(0x80 | cond(i.u1).encoding())
-			c.Emit4Bytes(op.imm32)
+			c.Emit4Bytes(op.imm32())
 		default:
 			panic("BUG: invalid operand kind")
 		}
 
 	case jmpTableIsland:
 		needsLabelResolution = true
-		for range i.targets {
+		for tc := uint64(0); tc < i.u2; tc++ {
 			c.Emit8Bytes(0)
 		}
 
 	case exitSequence:
-		execCtx := i.op1.r
+		execCtx := i.op1.reg()
+		allocatedAmode := i.op2.addressMode()
 
 		// Restore the RBP, RSP, and return to the Go code:
-		encodeLoad64(c, newAmodeImmReg(wazevoapi.ExecutionContextOffsetOriginalFramePointer.U32(), execCtx), rbp)
-		encodeLoad64(c, newAmodeImmReg(wazevoapi.ExecutionContextOffsetOriginalStackPointer.U32(), execCtx), rsp)
+		*allocatedAmode = amode{
+			kind: amodeImmReg, base: execCtx,
+			imm32: wazevoapi.ExecutionContextOffsetOriginalFramePointer.U32(),
+		}
+		encodeLoad64(c, allocatedAmode, rbp)
+		allocatedAmode.imm32 = wazevoapi.ExecutionContextOffsetOriginalStackPointer.U32()
+		encodeLoad64(c, allocatedAmode, rsp)
 		encodeRet(c)
 
 	case ud2:
@@ -1109,7 +1115,7 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 		rex := rexInfo(0).clearW()
 		switch op.kind {
 		case operandKindReg:
-			dst := regEncodings[op.r.RealReg()]
+			dst := regEncodings[op.reg().RealReg()]
 			encodeRegReg(c,
 				legacyPrefixesNone,
 				opcode, opcodeNum,
@@ -1118,7 +1124,7 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 				rex,
 			)
 		case operandKindMem:
-			m := op.amode
+			m := op.addressMode()
 			encodeRegMem(c,
 				legacyPrefixesNone,
 				opcode, opcodeNum,
@@ -1136,13 +1142,13 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 		c.Emit8Bytes(hi)
 
 	case xchg:
-		r1, r2 := i.op1.r.RealReg(), i.op2.r.RealReg()
+		r1, r2 := i.op1.reg().RealReg(), i.op2.reg().RealReg()
 
 		enc1, enc2 := regEncodings[r1], regEncodings[r2]
 		encodeRegReg(c, legacyPrefixesNone, 0x87, 1, enc2, enc1, rexInfo(0).setW())
 
 	case zeros:
-		r := i.op2.r
+		r := i.op2.reg()
 		if r.RegType() == regalloc.RegTypeInt {
 			i.asAluRmiR(aluRmiROpcodeXor, newOperandReg(r), r, true)
 		} else {
@@ -1156,7 +1162,7 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 	return
 }
 
-func encodeLoad64(c backend.Compiler, m amode, rd regalloc.RealReg) {
+func encodeLoad64(c backend.Compiler, m *amode, rd regalloc.RealReg) {
 	dst := regEncodings[rd]
 	encodeRegMem(c, legacyPrefixesNone, 0x8b, 1, dst, m, rexInfo(0).setW())
 }
@@ -1205,13 +1211,13 @@ func encodeSIB(shift byte, encIndex byte, encBase byte) byte {
 }
 
 func encodeRegMem(
-	c backend.Compiler, legPrefixes legacyPrefixes, opcodes uint32, opcodeNum uint32, r regEnc, m amode, rex rexInfo,
+	c backend.Compiler, legPrefixes legacyPrefixes, opcodes uint32, opcodeNum uint32, r regEnc, m *amode, rex rexInfo,
 ) {
 	encodeEncMem(c, legPrefixes, opcodes, opcodeNum, uint8(r), m, rex)
 }
 
 func encodeEncMem(
-	c backend.Compiler, legPrefixes legacyPrefixes, opcodes uint32, opcodeNum uint32, r uint8, m amode, rex rexInfo,
+	c backend.Compiler, legPrefixes legacyPrefixes, opcodes uint32, opcodeNum uint32, r uint8, m *amode, rex rexInfo,
 ) {
 	legPrefixes.encode(c)
 
diff --git a/internal/engine/wazevo/backend/isa/amd64/lower_mem.go b/internal/engine/wazevo/backend/isa/amd64/lower_mem.go
index 298ad90c..bee673d2 100644
--- a/internal/engine/wazevo/backend/isa/amd64/lower_mem.go
+++ b/internal/engine/wazevo/backend/isa/amd64/lower_mem.go
@@ -21,7 +21,7 @@ func (a addend) String() string {
 }
 
 // lowerToAddressMode converts a pointer to an addressMode that can be used as an operand for load/store instructions.
-func (m *machine) lowerToAddressMode(ptr ssa.Value, offsetBase uint32) (am amode) {
+func (m *machine) lowerToAddressMode(ptr ssa.Value, offsetBase uint32) (am *amode) {
 	def := m.c.ValueDefinition(ptr)
 
 	if offsetBase&0x80000000 != 0 {
@@ -35,9 +35,9 @@ func (m *machine) lowerToAddressMode(ptr ssa.Value, offsetBase uint32) (am amode
 		offsetBaseReg := m.c.AllocateVReg(ssa.TypeI64)
 		m.lowerIconst(offsetBaseReg, uint64(off64), true)
 		if a.r != regalloc.VRegInvalid {
-			return newAmodeRegRegShift(0, offsetBaseReg, a.r, a.shift)
+			return m.newAmodeRegRegShift(0, offsetBaseReg, a.r, a.shift)
 		} else {
-			return newAmodeImmReg(0, offsetBaseReg)
+			return m.newAmodeImmReg(0, offsetBaseReg)
 		}
 	}
 
@@ -57,19 +57,19 @@ func (m *machine) lowerToAddressMode(ptr ssa.Value, offsetBase uint32) (am amode
 			if a.shift != 0 {
 				tmpReg := m.c.AllocateVReg(ssa.TypeI64)
 				m.lowerIconst(tmpReg, 0, true)
-				return newAmodeRegRegShift(offsetBase, tmpReg, a.r, a.shift)
+				return m.newAmodeRegRegShift(offsetBase, tmpReg, a.r, a.shift)
 			}
-			return newAmodeImmReg(offsetBase, a.r)
+			return m.newAmodeImmReg(offsetBase, a.r)
 		} else {
 			off64 := a.off + int64(offsetBase)
 			tmpReg := m.c.AllocateVReg(ssa.TypeI64)
 			m.lowerIconst(tmpReg, uint64(off64), true)
-			return newAmodeImmReg(0, tmpReg)
+			return m.newAmodeImmReg(0, tmpReg)
 		}
 	}
 }
 
-func (m *machine) lowerAddendsToAmode(x, y addend, offBase uint32) amode {
+func (m *machine) lowerAddendsToAmode(x, y addend, offBase uint32) *amode {
 	if x.r != regalloc.VRegInvalid && x.off != 0 || y.r != regalloc.VRegInvalid && y.off != 0 {
 		panic("invalid input")
 	}
@@ -105,13 +105,13 @@ func (m *machine) lowerAddendsToAmode(x, y addend, offBase uint32) amode {
 			shifted.asShiftR(shiftROpShiftLeft, newOperandImm32(uint32(x.shift)), x.r, true)
 			m.insert(shifted)
 
-			return newAmodeRegRegShift(u32, x.r, y.r, y.shift)
+			return m.newAmodeRegRegShift(u32, x.r, y.r, y.shift)
 		case x.shift != 0 && y.shift == 0:
 			// Swap base and index.
 			x, y = y, x
 			fallthrough
 		default:
-			return newAmodeRegRegShift(u32, x.r, y.r, y.shift)
+			return m.newAmodeRegRegShift(u32, x.r, y.r, y.shift)
 		}
 	case x.r == regalloc.VRegInvalid && y.r != regalloc.VRegInvalid:
 		x, y = y, x
@@ -120,13 +120,13 @@ func (m *machine) lowerAddendsToAmode(x, y addend, offBase uint32) amode {
 		if x.shift != 0 {
 			zero := m.c.AllocateVReg(ssa.TypeI64)
 			m.lowerIconst(zero, 0, true)
-			return newAmodeRegRegShift(u32, zero, x.r, x.shift)
+			return m.newAmodeRegRegShift(u32, zero, x.r, x.shift)
 		}
-		return newAmodeImmReg(u32, x.r)
+		return m.newAmodeImmReg(u32, x.r)
 	default: // Both are invalid: use the offset.
 		tmpReg := m.c.AllocateVReg(ssa.TypeI64)
 		m.lowerIconst(tmpReg, u64, true)
-		return newAmodeImmReg(0, tmpReg)
+		return m.newAmodeImmReg(0, tmpReg)
 	}
 }
 
@@ -139,7 +139,8 @@ func (m *machine) lowerAddend(x *backend.SSAValueDefinition) addend {
 	if op != ssa.OpcodeInvalid && op != ssa.OpcodeIadd {
 		return m.lowerAddendFromInstr(x.Instr)
 	}
-	return addend{m.getOperand_Reg(x).r, 0, 0}
+	p := m.getOperand_Reg(x)
+	return addend{p.reg(), 0, 0}
 }
 
 // lowerAddendFromInstr takes an instruction returns a Vreg and an offset that can be used in an address mode.
@@ -168,16 +169,19 @@ func (m *machine) lowerAddendFromInstr(instr *ssa.Instruction) addend {
 		case constInst && op == ssa.OpcodeUExtend:
 			return addend{regalloc.VRegInvalid, int64(int32(inputDef.Instr.ConstantVal())), 0} // sign-extend!
 		default:
-			return addend{m.getOperand_Reg(inputDef).r, 0, 0}
+			r := m.getOperand_Reg(inputDef)
+			return addend{r.reg(), 0, 0}
 		}
 	case ssa.OpcodeIshl:
 		// If the addend is a shift, we can only handle it if the shift amount is a constant.
 		x, amount := instr.Arg2()
 		amountDef := m.c.ValueDefinition(amount)
 		if amountDef.IsFromInstr() && amountDef.Instr.Constant() && amountDef.Instr.ConstantVal() <= 3 {
-			return addend{m.getOperand_Reg(m.c.ValueDefinition(x)).r, 0, uint8(amountDef.Instr.ConstantVal())}
+			r := m.getOperand_Reg(m.c.ValueDefinition(x))
+			return addend{r.reg(), 0, uint8(amountDef.Instr.ConstantVal())}
 		}
-		return addend{m.getOperand_Reg(m.c.ValueDefinition(x)).r, 0, 0}
+		r := m.getOperand_Reg(m.c.ValueDefinition(x))
+		return addend{r.reg(), 0, 0}
 	}
 	panic("BUG: invalid opcode")
 }
diff --git a/internal/engine/wazevo/backend/isa/amd64/machine.go b/internal/engine/wazevo/backend/isa/amd64/machine.go
index bb87944b..ca9b38dc 100644
--- a/internal/engine/wazevo/backend/isa/amd64/machine.go
+++ b/internal/engine/wazevo/backend/isa/amd64/machine.go
@@ -27,6 +27,7 @@ func NewBackend() backend.Machine {
 		cpuFeatures: platform.CpuFeatures,
 		regAlloc:    regalloc.NewAllocator(regInfo),
 		spillSlots:  map[regalloc.VRegID]int64{},
+		amodePool:   wazevoapi.NewPool[amode](resetAmode),
 	}
 }
 
@@ -37,6 +38,8 @@ type (
 		ectx                     *backend.ExecutableContextT[instruction]
 		stackBoundsCheckDisabled bool
 
+		amodePool wazevoapi.Pool[amode]
+
 		cpuFeatures platform.CpuFeatureFlags
 
 		regAlloc        regalloc.Allocator
@@ -51,6 +54,8 @@ type (
 		maxRequiredStackSizeForCalls int64
 
 		labelResolutionPends []labelResolutionPend
+
+		jmpTableTargets [][]uint32
 	}
 
 	labelResolutionPend struct {
@@ -81,6 +86,9 @@ func (m *machine) Reset() {
 
 	m.spillSlotSize = 0
 	m.maxRequiredStackSizeForCalls = 0
+
+	m.amodePool.Reset()
+	m.jmpTableTargets = m.jmpTableTargets[:0]
 }
 
 // ExecutableContext implements backend.Machine.
@@ -144,11 +152,22 @@ func (m *machine) LowerSingleBranch(b *ssa.Instruction) {
 	}
 }
 
+func (m *machine) addJmpTableTarget(targets []ssa.BasicBlock) (index int) {
+	// TODO: reuse the slice!
+	labels := make([]uint32, len(targets))
+	for j, target := range targets {
+		labels[j] = uint32(m.ectx.GetOrAllocateSSABlockLabel(target))
+	}
+	index = len(m.jmpTableTargets)
+	m.jmpTableTargets = append(m.jmpTableTargets, labels)
+	return
+}
+
 var condBranchMatches = [...]ssa.Opcode{ssa.OpcodeIcmp, ssa.OpcodeFcmp}
 
 func (m *machine) lowerBrTable(index ssa.Value, targets []ssa.BasicBlock) {
 	_v := m.getOperand_Reg(m.c.ValueDefinition(index))
-	v := m.copyToTmp(_v.r)
+	v := m.copyToTmp(_v.reg())
 
 	// First, we need to do the bounds check.
 	maxIndex := m.c.AllocateVReg(ssa.TypeI32)
@@ -168,7 +187,7 @@ func (m *machine) lowerBrTable(index ssa.Value, targets []ssa.BasicBlock) {
 	// Then add the target's offset into jmpTableAddr.
 	loadTargetOffsetFromJmpTable := m.allocateInstr().asAluRmiR(aluRmiROpcodeAdd,
 		// Shift by 3 because each entry is 8 bytes.
-		newOperandMem(newAmodeRegRegShift(0, addr, v, 3)), addr, true)
+		newOperandMem(m.newAmodeRegRegShift(0, addr, v, 3)), addr, true)
 	m.insert(loadTargetOffsetFromJmpTable)
 
 	// Now ready to jump.
@@ -177,16 +196,11 @@ func (m *machine) lowerBrTable(index ssa.Value, targets []ssa.BasicBlock) {
 
 	jmpTableBegin, jmpTableBeginLabel := m.allocateBrTarget()
 	m.insert(jmpTableBegin)
-	leaJmpTableAddr.asLEA(newAmodeRipRelative(jmpTableBeginLabel), addr)
+	leaJmpTableAddr.asLEA(m.newAmodeRipRelative(jmpTableBeginLabel), addr)
 
 	jmpTable := m.allocateInstr()
-	// TODO: reuse the slice!
-	labels := make([]uint32, len(targets))
-	for j, target := range targets {
-		labels[j] = uint32(m.ectx.GetOrAllocateSSABlockLabel(target))
-	}
-
-	jmpTable.asJmpTableSequence(labels)
+	targetSliceIndex := m.addJmpTableTarget(targets)
+	jmpTable.asJmpTableSequence(targetSliceIndex, len(targets))
 	m.insert(jmpTable)
 }
 
@@ -263,7 +277,7 @@ func (m *machine) LowerConditionalBranch(b *ssa.Instruction) {
 		}
 
 		// Perform test %v, %v to set the flag.
-		cmp := m.allocateInstr().asCmpRmiR(false, v, v.r, false)
+		cmp := m.allocateInstr().asCmpRmiR(false, v, v.reg(), false)
 		m.insert(cmp)
 		m.insert(m.allocateInstr().asJmpIf(cc, newOperandLabel(target)))
 	}
@@ -359,14 +373,14 @@ func (m *machine) LowerInstr(instr *ssa.Instruction) {
 		rn := m.getOperand_Reg(m.c.ValueDefinition(x))
 		rd := m.c.VRegOf(instr.Return())
 		ctxVReg := m.c.VRegOf(ctx)
-		m.lowerFcvtToSint(ctxVReg, rn.r, rd, x.Type() == ssa.TypeF64,
+		m.lowerFcvtToSint(ctxVReg, rn.reg(), rd, x.Type() == ssa.TypeF64,
 			instr.Return().Type().Bits() == 64, op == ssa.OpcodeFcvtToSintSat)
 	case ssa.OpcodeFcvtToUint, ssa.OpcodeFcvtToUintSat:
 		x, ctx := instr.Arg2()
 		rn := m.getOperand_Reg(m.c.ValueDefinition(x))
 		rd := m.c.VRegOf(instr.Return())
 		ctxVReg := m.c.VRegOf(ctx)
-		m.lowerFcvtToUint(ctxVReg, rn.r, rd, x.Type() == ssa.TypeF64,
+		m.lowerFcvtToUint(ctxVReg, rn.reg(), rd, x.Type() == ssa.TypeF64,
 			instr.Return().Type().Bits() == 64, op == ssa.OpcodeFcvtToUintSat)
 	case ssa.OpcodeFcvtFromSint:
 		x := instr.Arg()
@@ -492,8 +506,8 @@ func (m *machine) lowerSelect(x, y, cval, ret ssa.Value) {
 		cond = condFromSSAIntCmpCond(cc)
 		icmp.Lowered()
 	default: // TODO: match ssa.OpcodeFcmp for optimization, but seems a bit complex.
-		cv := m.getOperand_Reg(cvalDef).r
-		test := m.allocateInstr().asCmpRmiR(false, newOperandReg(cv), cv, false)
+		cv := m.getOperand_Reg(cvalDef)
+		test := m.allocateInstr().asCmpRmiR(false, cv, cv.reg(), false)
 		m.insert(test)
 		cond = condNZ
 	}
@@ -504,7 +518,7 @@ func (m *machine) lowerSelect(x, y, cval, ret ssa.Value) {
 		tmp := m.c.AllocateVReg(typ)
 		switch yo.kind {
 		case operandKindReg:
-			mov.asMovRR(yo.r, tmp, _64)
+			mov.asMovRR(yo.reg(), tmp, _64)
 		case operandKindMem:
 			if _64 {
 				mov.asMov64MR(yo, tmp)
@@ -542,7 +556,7 @@ func (m *machine) lowerSelect(x, y, cval, ret ssa.Value) {
 
 func (m *machine) lowerXmmCmovAfterRegAlloc(i *instruction) {
 	x := i.op1
-	rd := i.op2.r
+	rd := i.op2.reg()
 	cond := cond(i.u1)
 
 	jcc := m.allocateInstr()
@@ -613,7 +627,7 @@ func (m *machine) lowerVconst(res ssa.Value, lo, hi uint64) {
 
 	islandAddr := m.c.AllocateVReg(ssa.TypeI64)
 	lea := m.allocateInstr()
-	load := m.allocateInstr().asXmmUnaryRmR(sseOpcodeMovdqu, newOperandMem(newAmodeImmReg(0, islandAddr)), dst)
+	load := m.allocateInstr().asXmmUnaryRmR(sseOpcodeMovdqu, newOperandMem(m.newAmodeImmReg(0, islandAddr)), dst)
 	jmp := m.allocateInstr()
 
 	constLabelNop, constLabel := m.allocateBrTarget()
@@ -634,7 +648,7 @@ func (m *machine) lowerVconst(res ssa.Value, lo, hi uint64) {
 	m.insert(constIsland)
 	m.insert(afterLoadNop)
 
-	lea.asLEA(newAmodeRipRelative(constLabel), islandAddr)
+	lea.asLEA(m.newAmodeRipRelative(constLabel), islandAddr)
 	jmp.asJmp(newOperandLabel(afterLoadLabel))
 }
 
@@ -660,7 +674,7 @@ func (m *machine) lowerCtz(instr *ssa.Instruction) {
 
 		// First, we have to check if the target is non-zero.
 		test := m.allocateInstr()
-		test.asCmpRmiR(false, rm, rm.r, _64)
+		test.asCmpRmiR(false, rm, rm.reg(), _64)
 		m.insert(test)
 
 		jmpNz := m.allocateInstr()
@@ -714,7 +728,7 @@ func (m *machine) lowerClz(instr *ssa.Instruction) {
 		// First, we have to check if the rm is non-zero as BSR is undefined
 		// on zero. See https://www.felixcloutier.com/x86/bsr.
 		test := m.allocateInstr()
-		test.asCmpRmiR(false, rm, rm.r, _64)
+		test.asCmpRmiR(false, rm, rm.reg(), _64)
 		m.insert(test)
 
 		jmpNz := m.allocateInstr()
@@ -833,18 +847,18 @@ func (m *machine) lowerExitIfTrueWithCode(execCtx regalloc.VReg, cond ssa.Value,
 func (m *machine) allocateExitInstructions(execCtx, exitCodeReg regalloc.VReg) (saveRsp, saveRbp, setExitCode *instruction) {
 	saveRsp = m.allocateInstr().asMovRM(
 		rspVReg,
-		newOperandMem(newAmodeImmReg(wazevoapi.ExecutionContextOffsetStackPointerBeforeGoCall.U32(), execCtx)),
+		newOperandMem(m.newAmodeImmReg(wazevoapi.ExecutionContextOffsetStackPointerBeforeGoCall.U32(), execCtx)),
 		8,
 	)
 
 	saveRbp = m.allocateInstr().asMovRM(
 		rbpVReg,
-		newOperandMem(newAmodeImmReg(wazevoapi.ExecutionContextOffsetFramePointerBeforeGoCall.U32(), execCtx)),
+		newOperandMem(m.newAmodeImmReg(wazevoapi.ExecutionContextOffsetFramePointerBeforeGoCall.U32(), execCtx)),
 		8,
 	)
 	setExitCode = m.allocateInstr().asMovRM(
 		exitCodeReg,
-		newOperandMem(newAmodeImmReg(wazevoapi.ExecutionContextOffsetExitCodeOffset.U32(), execCtx)),
+		newOperandMem(m.newAmodeImmReg(wazevoapi.ExecutionContextOffsetExitCodeOffset.U32(), execCtx)),
 		4,
 	)
 	return
@@ -866,18 +880,18 @@ func (m *machine) lowerExitWithCode(execCtx regalloc.VReg, code wazevoapi.ExitCo
 	ripReg := rbpVReg
 	saveRip := m.allocateInstr().asMovRM(
 		ripReg,
-		newOperandMem(newAmodeImmReg(wazevoapi.ExecutionContextOffsetGoCallReturnAddress.U32(), execCtx)),
+		newOperandMem(m.newAmodeImmReg(wazevoapi.ExecutionContextOffsetGoCallReturnAddress.U32(), execCtx)),
 		8,
 	)
 	m.insert(saveRip)
 
 	// Finally exit.
-	exitSq := m.allocateInstr().asExitSeq(execCtx)
+	exitSq := m.allocateExitSeq(execCtx)
 	m.insert(exitSq)
 
 	// Insert the label for the return address.
 	nop, l := m.allocateBrTarget()
-	readRip.asLEA(newAmodeRipRelative(l), ripReg)
+	readRip.asLEA(m.newAmodeRipRelative(l), ripReg)
 	m.insert(nop)
 	return l
 }
@@ -899,7 +913,7 @@ func (m *machine) lowerAluRmiROp(si *ssa.Instruction, op aluRmiROpcode) {
 
 	// rn is being overwritten, so we first copy its value to a temp register,
 	// in case it is referenced again later.
-	tmp := m.copyToTmp(rn.r)
+	tmp := m.copyToTmp(rn.reg())
 
 	alu := m.allocateInstr()
 	alu.asAluRmiR(op, rm, tmp, _64)
@@ -924,12 +938,12 @@ func (m *machine) lowerShiftR(si *ssa.Instruction, op shiftROp) {
 
 	// rx is being overwritten, so we first copy its value to a temp register,
 	// in case it is referenced again later.
-	tmpDst := m.copyToTmp(rx.r)
+	tmpDst := m.copyToTmp(rx.reg())
 
-	if opAmt.r != regalloc.VRegInvalid {
+	if opAmt.kind == operandKindReg {
 		// If opAmt is a register we must copy its value to rcx,
 		// because shiftR encoding mandates that the shift amount is in rcx.
-		m.copyTo(opAmt.r, rcxVReg)
+		m.copyTo(opAmt.reg(), rcxVReg)
 
 		alu := m.allocateInstr()
 		alu.asShiftR(op, newOperandReg(rcxVReg), tmpDst, _64)
@@ -988,7 +1002,7 @@ func (m *machine) lowerXmmRmR(instr *ssa.Instruction) {
 
 	// rm is being overwritten, so we first copy its value to a temp register,
 	// in case it is referenced again later.
-	tmp := m.copyToTmp(rm.r)
+	tmp := m.copyToTmp(rm.reg())
 
 	xmm := m.allocateInstr().asXmmRmR(op, rn, tmp)
 	m.insert(xmm)
@@ -1063,15 +1077,15 @@ func (m *machine) lowerStore(si *ssa.Instruction) {
 	store := m.allocateInstr()
 	switch value.Type() {
 	case ssa.TypeI32:
-		store.asMovRM(rm.r, mem, storeSizeInBits/8)
+		store.asMovRM(rm.reg(), mem, storeSizeInBits/8)
 	case ssa.TypeI64:
-		store.asMovRM(rm.r, mem, storeSizeInBits/8)
+		store.asMovRM(rm.reg(), mem, storeSizeInBits/8)
 	case ssa.TypeF32:
-		store.asXmmMovRM(sseOpcodeMovss, rm.r, mem)
+		store.asXmmMovRM(sseOpcodeMovss, rm.reg(), mem)
 	case ssa.TypeF64:
-		store.asXmmMovRM(sseOpcodeMovsd, rm.r, mem)
+		store.asXmmMovRM(sseOpcodeMovsd, rm.reg(), mem)
 	case ssa.TypeV128:
-		store.asXmmMovRM(sseOpcodeMovdqu, rm.r, mem)
+		store.asXmmMovRM(sseOpcodeMovdqu, rm.reg(), mem)
 	default:
 		panic("BUG")
 	}
@@ -1141,7 +1155,7 @@ func (m *machine) callerGenVRegToFunctionArg(a *backend.FunctionABI, argIndex in
 		m.InsertMove(arg.Reg, reg, arg.Type)
 	} else {
 		store := m.allocateInstr()
-		mem := newOperandMem(newAmodeImmReg(
+		mem := newOperandMem(m.newAmodeImmReg(
 			// -stackSlotSize because the stack pointer is not yet decreased.
 			uint32(arg.Offset-stackSlotSize), rspVReg))
 		switch arg.Type {
@@ -1168,7 +1182,7 @@ func (m *machine) callerGenFunctionReturnVReg(a *backend.FunctionABI, retIndex i
 		m.InsertMove(reg, r.Reg, r.Type)
 	} else {
 		load := m.allocateInstr()
-		mem := newOperandMem(newAmodeImmReg(
+		mem := newOperandMem(m.newAmodeImmReg(
 			// -stackSlotSize because the stack pointer is not yet decreased.
 			uint32(a.ArgStackSize+r.Offset-stackSlotSize), rspVReg))
 		switch r.Type {
@@ -1325,7 +1339,8 @@ func (m *machine) Encode(context.Context) {
 		case jmpTableIsland:
 			tableBegin := p.instrOffset
 			// Each entry is the offset from the beginning of the jmpTableIsland instruction in 8 bytes.
-			for i, l := range p.instr.targets {
+			targets := m.jmpTableTargets[p.instr.u1]
+			for i, l := range targets {
 				targetOffset := ectx.LabelPositions[backend.Label(l)].BinaryOffset
 				jmpOffset := targetOffset - tableBegin
 				binary.LittleEndian.PutUint64(buf[tableBegin+int64(i)*8:], uint64(jmpOffset))
@@ -1354,7 +1369,7 @@ func (m *machine) ResolveRelocations(refToBinaryOffset map[ssa.FuncRef]int, bina
 func (m *machine) lowerIcmpToFlag(xd, yd *backend.SSAValueDefinition, _64 bool) {
 	x := m.getOperand_Reg(xd)
 	y := m.getOperand_Mem_Imm32_Reg(yd)
-	cmp := m.allocateInstr().asCmpRmiR(true, y, x.r, _64)
+	cmp := m.allocateInstr().asCmpRmiR(true, y, x.reg(), _64)
 	m.insert(cmp)
 }
 
@@ -1388,7 +1403,7 @@ func (m *machine) lowerFcmpToFlags(instr *ssa.Instruction) (f1, f2 cond, and boo
 
 	xr := m.getOperand_Reg(m.c.ValueDefinition(x))
 	yr := m.getOperand_Mem_Reg(m.c.ValueDefinition(y))
-	m.insert(m.allocateInstr().asXmmCmpRmR(opc, yr, xr.r))
+	m.insert(m.allocateInstr().asXmmCmpRmR(opc, yr, xr.reg()))
 	return
 }
 
@@ -1478,10 +1493,10 @@ func (m *machine) lowerIDivRem(si *ssa.Instruction, isDiv bool, signed bool) {
 	ctxVReg := m.c.VRegOf(execCtx)
 	tmpGp := m.c.AllocateVReg(si.Return().Type())
 
-	m.copyTo(dividend.r, raxVReg)
+	m.copyTo(dividend.reg(), raxVReg)
 	m.insert(m.allocateInstr().asDefineUninitializedReg(rdxVReg))
 	m.insert(m.allocateInstr().asDefineUninitializedReg(tmpGp))
-	seq := m.allocateInstr().asIdivRemSequence(ctxVReg, divisor.r, tmpGp, isDiv, signed, x.Type().Bits() == 64)
+	seq := m.allocateInstr().asIdivRemSequence(ctxVReg, divisor.reg(), tmpGp, isDiv, signed, x.Type().Bits() == 64)
 	m.insert(seq)
 	rd := m.c.VRegOf(si.Return())
 	if isDiv {
@@ -1637,7 +1652,7 @@ func (m *machine) lowerFminFmax(instr *ssa.Instruction) {
 	rn := m.getOperand_Mem_Reg(yDef)
 	rd := m.c.VRegOf(instr.Return())
 
-	tmp := m.copyToTmp(rm.r)
+	tmp := m.copyToTmp(rm.reg())
 
 	// Check if this is (either x1 or x2 is NaN) or (x1 equals x2) case.
 	cmp := m.allocateInstr()
@@ -1774,13 +1789,13 @@ func (m *machine) lowerBitcast(instr *ssa.Instruction) {
 	rd := m.c.VRegOf(instr.Return())
 	switch {
 	case srcTyp == ssa.TypeF32 && dstTyp == ssa.TypeI32:
-		cvt := m.allocateInstr().asXmmToGpr(sseOpcodeMovd, rn.r, rd, false)
+		cvt := m.allocateInstr().asXmmToGpr(sseOpcodeMovd, rn.reg(), rd, false)
 		m.insert(cvt)
 	case srcTyp == ssa.TypeI32 && dstTyp == ssa.TypeF32:
 		cvt := m.allocateInstr().asGprToXmm(sseOpcodeMovd, rn, rd, false)
 		m.insert(cvt)
 	case srcTyp == ssa.TypeF64 && dstTyp == ssa.TypeI64:
-		cvt := m.allocateInstr().asXmmToGpr(sseOpcodeMovq, rn.r, rd, true)
+		cvt := m.allocateInstr().asXmmToGpr(sseOpcodeMovq, rn.reg(), rd, true)
 		m.insert(cvt)
 	case srcTyp == ssa.TypeI64 && dstTyp == ssa.TypeF64:
 		cvt := m.allocateInstr().asGprToXmm(sseOpcodeMovq, rn, rd, true)
@@ -1803,6 +1818,12 @@ func (m *machine) lowerFcvtToSint(ctxVReg, rn, rd regalloc.VReg, src64, dst64, s
 	m.insert(m.allocateInstr().asDefineUninitializedReg(tmpGp))
 	m.insert(m.allocateInstr().asDefineUninitializedReg(tmpGp2))
 
+	m.insert(m.allocateFcvtToSintSequence(ctxVReg, rn, tmpGp, tmpGp2, tmpXmm, src64, dst64, sat))
+	m.copyTo(tmpGp, rd)
+}
+
+func (m *machine) lowerFcvtToSintSequenceAfterRegalloc(i *instruction) {
+	execCtx, src, tmpGp, tmpGp2, tmpXmm, src64, dst64, sat := i.fcvtToSintSequenceData()
 	var cmpOp, truncOp sseOpcode
 	if src64 {
 		cmpOp, truncOp = sseOpcodeUcomisd, sseOpcodeCvttsd2si
@@ -1810,12 +1831,6 @@ func (m *machine) lowerFcvtToSint(ctxVReg, rn, rd regalloc.VReg, src64, dst64, s
 		cmpOp, truncOp = sseOpcodeUcomiss, sseOpcodeCvttss2si
 	}
 
-	seq := m.allocateInstr().asFcvtToSintSequence(cmpOp, truncOp, ctxVReg, rn, rd, tmpGp, tmpGp2, tmpXmm, src64, dst64, sat)
-	m.insert(seq)
-}
-
-func (m *machine) lowerFcvtToSintSequenceAfterRegalloc(i *instruction) {
-	cmpOp, truncOp, execCtx, src, dst, tmpGp, tmpGp2, tmpXmm, src64, dst64, sat := i.fcvtToSintSequenceData()
 	trunc := m.allocateInstr()
 	trunc.asXmmToGpr(truncOp, src, tmpGp, dst64)
 	m.insert(trunc)
@@ -1933,17 +1948,9 @@ func (m *machine) lowerFcvtToSintSequenceAfterRegalloc(i *instruction) {
 	}
 
 	m.insert(doneTarget)
-	m.copyTo(tmpGp, dst)
 }
 
 func (m *machine) lowerFcvtToUint(ctxVReg, rn, rd regalloc.VReg, src64, dst64, sat bool) {
-	var subOp, cmpOp, truncOp sseOpcode
-	if src64 {
-		subOp, cmpOp, truncOp = sseOpcodeSubsd, sseOpcodeUcomisd, sseOpcodeCvttsd2si
-	} else {
-		subOp, cmpOp, truncOp = sseOpcodeSubss, sseOpcodeUcomiss, sseOpcodeCvttss2si
-	}
-
 	tmpXmm, tmpXmm2 := m.c.AllocateVReg(ssa.TypeF64), m.c.AllocateVReg(ssa.TypeF64)
 	m.insert(m.allocateInstr().asDefineUninitializedReg(tmpXmm))
 	m.insert(m.allocateInstr().asDefineUninitializedReg(tmpXmm2))
@@ -1951,13 +1958,21 @@ func (m *machine) lowerFcvtToUint(ctxVReg, rn, rd regalloc.VReg, src64, dst64, s
 	m.insert(m.allocateInstr().asDefineUninitializedReg(tmpGp))
 	m.insert(m.allocateInstr().asDefineUninitializedReg(tmpGp2))
 
-	m.insert(m.allocateInstr().asFcvtToUintSequence(
-		subOp, cmpOp, truncOp, ctxVReg, rn, rd, tmpGp, tmpGp2, tmpXmm, tmpXmm2, src64, dst64, sat,
+	m.insert(m.allocateFcvtToUintSequence(
+		ctxVReg, rn, tmpGp, tmpGp2, tmpXmm, tmpXmm2, src64, dst64, sat,
 	))
+	m.copyTo(tmpGp, rd)
 }
 
 func (m *machine) lowerFcvtToUintSequenceAfterRegalloc(i *instruction) {
-	subOp, cmpOp, truncOp, execCtx, src, dst, tmpGp, tmpGp2, tmpXmm, tmpXmm2, src64, dst64, sat := i.fcvtToUintSequenceData()
+	execCtx, src, tmpGp, tmpGp2, tmpXmm, tmpXmm2, src64, dst64, sat := i.fcvtToUintSequenceData()
+
+	var subOp, cmpOp, truncOp sseOpcode
+	if src64 {
+		subOp, cmpOp, truncOp = sseOpcodeSubsd, sseOpcodeUcomisd, sseOpcodeCvttsd2si
+	} else {
+		subOp, cmpOp, truncOp = sseOpcodeSubss, sseOpcodeUcomiss, sseOpcodeCvttss2si
+	}
 
 	doneTarget, done := m.allocateBrTarget()
 
@@ -2104,7 +2119,6 @@ func (m *machine) lowerFcvtToUintSequenceAfterRegalloc(i *instruction) {
 	m.insert(add)
 
 	m.insert(doneTarget)
-	m.copyTo(tmpGp, dst)
 }
 
 func (m *machine) lowerFcvtFromSint(rn, rd operand, src64, dst64 bool) {
@@ -2116,7 +2130,7 @@ func (m *machine) lowerFcvtFromSint(rn, rd operand, src64, dst64 bool) {
 	}
 
 	trunc := m.allocateInstr()
-	trunc.asGprToXmm(op, rn, rd.r, src64)
+	trunc.asGprToXmm(op, rn, rd.reg(), src64)
 	m.insert(trunc)
 }
 
@@ -2141,7 +2155,7 @@ func (m *machine) lowerFcvtFromUint(rn, rd operand, src64, dst64 bool) {
 	//
 	if !src64 {
 		cvt := m.allocateInstr()
-		cvt.asGprToXmm(op, rn, rd.r, true)
+		cvt.asGprToXmm(op, rn, rd.reg(), true)
 		m.insert(cvt)
 		return
 	}
@@ -2158,7 +2172,7 @@ func (m *machine) lowerFcvtFromUint(rn, rd operand, src64, dst64 bool) {
 
 	// Check if the most significant bit (sign bit) is set.
 	test := m.allocateInstr()
-	test.asCmpRmiR(false, rn, rn.r, src64)
+	test.asCmpRmiR(false, rn, rn.reg(), src64)
 	m.insert(test)
 
 	// Jump if the sign bit is set.
@@ -2189,12 +2203,12 @@ func (m *machine) lowerFcvtFromUint(rn, rd operand, src64, dst64 bool) {
 	// 	   addsd    %xmm0, %xmm0
 	m.insert(ifSignTarget)
 
-	tmp := m.copyToTmp(rn.r)
+	tmp := m.copyToTmp(rn.reg())
 	shr := m.allocateInstr()
 	shr.asShiftR(shiftROpShiftRightLogical, newOperandImm32(1), tmp, src64)
 	m.insert(shr)
 
-	tmp2 := m.copyToTmp(rn.r)
+	tmp2 := m.copyToTmp(rn.reg())
 	and := m.allocateInstr()
 	and.asAluRmiR(aluRmiROpcodeAnd, newOperandImm32(1), tmp2, src64)
 	m.insert(and)
@@ -2216,7 +2230,7 @@ func (m *machine) lowerFcvtFromUint(rn, rd operand, src64, dst64 bool) {
 	m.insert(addsd)
 
 	m.insert(endTarget)
-	m.copyTo(tmpXmm, rd.r)
+	m.copyTo(tmpXmm, rd.reg())
 }
 
 func (m *machine) lowerVanyTrue(instr *ssa.Instruction) {
@@ -2227,7 +2241,7 @@ func (m *machine) lowerVanyTrue(instr *ssa.Instruction) {
 	tmp := m.c.AllocateVReg(ssa.TypeI32)
 
 	cmp := m.allocateInstr()
-	cmp.asXmmCmpRmR(sseOpcodePtest, rm, rm.r)
+	cmp.asXmmCmpRmR(sseOpcodePtest, rm, rm.reg())
 	m.insert(cmp)
 
 	setcc := m.allocateInstr()
@@ -2293,7 +2307,7 @@ func (m *machine) lowerVhighBits(instr *ssa.Instruction) {
 	switch lane {
 	case ssa.VecLaneI8x16:
 		mov := m.allocateInstr()
-		mov.asXmmToGpr(sseOpcodePmovmskb, rm.r, rd, false)
+		mov.asXmmToGpr(sseOpcodePmovmskb, rm.reg(), rd, false)
 		m.insert(mov)
 
 	case ssa.VecLaneI16x8:
@@ -2317,7 +2331,7 @@ func (m *machine) lowerVhighBits(instr *ssa.Instruction) {
 		// See https://www.felixcloutier.com/x86/packsswb:packssdw for detail.
 		//
 		// Therefore, v.register ends up having i-th and (i+8)-th bit set if i-th lane is negative (for i in 0..8).
-		tmp := m.copyToTmp(rm.r)
+		tmp := m.copyToTmp(rm.reg())
 		res := m.c.AllocateVReg(ssa.TypeI32)
 
 		pak := m.allocateInstr()
@@ -2337,12 +2351,12 @@ func (m *machine) lowerVhighBits(instr *ssa.Instruction) {
 
 	case ssa.VecLaneI32x4:
 		mov := m.allocateInstr()
-		mov.asXmmToGpr(sseOpcodeMovmskps, rm.r, rd, true)
+		mov.asXmmToGpr(sseOpcodeMovmskps, rm.reg(), rd, true)
 		m.insert(mov)
 
 	case ssa.VecLaneI64x2:
 		mov := m.allocateInstr()
-		mov.asXmmToGpr(sseOpcodeMovmskpd, rm.r, rd, true)
+		mov.asXmmToGpr(sseOpcodeMovmskpd, rm.reg(), rd, true)
 		m.insert(mov)
 	}
 }
@@ -2353,7 +2367,7 @@ func (m *machine) lowerVbnot(instr *ssa.Instruction) {
 	rm := m.getOperand_Reg(xDef)
 	rd := m.c.VRegOf(instr.Return())
 
-	tmp := m.copyToTmp(rm.r)
+	tmp := m.copyToTmp(rm.reg())
 	tmp2 := m.c.AllocateVReg(ssa.TypeV128)
 
 	// Ensure tmp2 is considered defined by regalloc.
@@ -2379,7 +2393,7 @@ func (m *machine) lowerVbBinOp(instr *ssa.Instruction, op sseOpcode) {
 	rm, rn := m.getOperand_Reg(xDef), m.getOperand_Reg(yDef)
 	rd := m.c.VRegOf(instr.Return())
 
-	tmp := m.copyToTmp(rm.r)
+	tmp := m.copyToTmp(rm.reg())
 
 	// op between rn, rm.
 	binOp := m.allocateInstr()
@@ -2396,7 +2410,7 @@ func (m *machine) lowerVbandnot(instr *ssa.Instruction, op sseOpcode) {
 	rm, rn := m.getOperand_Reg(xDef), m.getOperand_Reg(yDef)
 	rd := m.c.VRegOf(instr.Return())
 
-	tmp := m.copyToTmp(rn.r)
+	tmp := m.copyToTmp(rn.reg())
 
 	// pandn between rn, rm.
 	pand := m.allocateInstr()
@@ -2414,8 +2428,8 @@ func (m *machine) lowerVbitselect(instr *ssa.Instruction) {
 	creg := m.getOperand_Reg(m.c.ValueDefinition(c))
 	rd := m.c.VRegOf(instr.Return())
 
-	tmpC := m.copyToTmp(creg.r)
-	tmpX := m.copyToTmp(rm.r)
+	tmpC := m.copyToTmp(creg.reg())
+	tmpX := m.copyToTmp(rm.reg())
 
 	// And between c, x (overwrites x).
 	pand := m.allocateInstr()
diff --git a/internal/engine/wazevo/backend/isa/amd64/machine_pro_epi_logue.go b/internal/engine/wazevo/backend/isa/amd64/machine_pro_epi_logue.go
index b7a86782..9c78d7bb 100644
--- a/internal/engine/wazevo/backend/isa/amd64/machine_pro_epi_logue.go
+++ b/internal/engine/wazevo/backend/isa/amd64/machine_pro_epi_logue.go
@@ -82,7 +82,7 @@ func (m *machine) setupPrologue() {
 				// Push the XMM register is not supported by the PUSH instruction.
 				cur = m.addRSP(-16, cur)
 				push := m.allocateInstr().asXmmMovRM(
-					sseOpcodeMovdqu, r, newOperandMem(newAmodeImmReg(0, rspVReg)),
+					sseOpcodeMovdqu, r, newOperandMem(m.newAmodeImmReg(0, rspVReg)),
 				)
 				cur = linkInstr(cur, push)
 			}
@@ -191,7 +191,7 @@ func (m *machine) postRegAlloc() {
 		}
 
 		// Removes the redundant copy instruction.
-		if cur.IsCopy() && cur.op1.r.RealReg() == cur.op2.r.RealReg() {
+		if cur.IsCopy() && cur.op1.reg().RealReg() == cur.op2.reg().RealReg() {
 			prev, next := cur.prev, cur.next
 			// Remove the copy instruction.
 			prev.next = next
@@ -264,7 +264,7 @@ func (m *machine) setupEpilogueAfter(cur *instruction) {
 			} else {
 				// Pop the XMM register is not supported by the POP instruction.
 				pop := m.allocateInstr().asXmmUnaryRmR(
-					sseOpcodeMovdqu, newOperandMem(newAmodeImmReg(0, rspVReg)), r,
+					sseOpcodeMovdqu, newOperandMem(m.newAmodeImmReg(0, rspVReg)), r,
 				)
 				cur = linkInstr(cur, pop)
 				cur = m.addRSP(16, cur)
diff --git a/internal/engine/wazevo/backend/isa/amd64/machine_regalloc.go b/internal/engine/wazevo/backend/isa/amd64/machine_regalloc.go
index aa7f87eb..e2d01362 100644
--- a/internal/engine/wazevo/backend/isa/amd64/machine_regalloc.go
+++ b/internal/engine/wazevo/backend/isa/amd64/machine_regalloc.go
@@ -43,7 +43,7 @@ func (m *machine) InsertStoreRegisterAt(v regalloc.VReg, instr *instruction, aft
 
 	offsetFromSP := m.getVRegSpillSlotOffsetFromSP(v.ID(), typ.Size())
 	store := m.allocateInstr()
-	mem := newOperandMem(newAmodeImmReg(uint32(offsetFromSP), rspVReg))
+	mem := newOperandMem(m.newAmodeImmReg(uint32(offsetFromSP), rspVReg))
 	switch typ {
 	case ssa.TypeI32:
 		store.asMovRM(v, mem, 4)
@@ -78,7 +78,7 @@ func (m *machine) InsertReloadRegisterAt(v regalloc.VReg, instr *instruction, af
 	// Load the value to the temporary.
 	load := m.allocateInstr()
 	offsetFromSP := m.getVRegSpillSlotOffsetFromSP(v.ID(), typ.Size())
-	a := newOperandMem(newAmodeImmReg(uint32(offsetFromSP), rspVReg))
+	a := newOperandMem(m.newAmodeImmReg(uint32(offsetFromSP), rspVReg))
 	switch typ {
 	case ssa.TypeI32:
 		load.asMovzxRmR(extModeLQ, a, v)
diff --git a/internal/engine/wazevo/backend/isa/amd64/operands.go b/internal/engine/wazevo/backend/isa/amd64/operands.go
index cd157ef6..2efc9860 100644
--- a/internal/engine/wazevo/backend/isa/amd64/operands.go
+++ b/internal/engine/wazevo/backend/isa/amd64/operands.go
@@ -2,17 +2,17 @@ package amd64
 
 import (
 	"fmt"
+	"unsafe"
 
 	"github.com/tetratelabs/wazero/internal/engine/wazevo/backend"
 	"github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc"
 	"github.com/tetratelabs/wazero/internal/engine/wazevo/ssa"
+	"github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi"
 )
 
 type operand struct {
-	kind  operandKind
-	r     regalloc.VReg
-	imm32 uint32
-	amode amode
+	kind operandKind
+	data uint64
 }
 
 type operandKind byte
@@ -53,44 +53,65 @@ func (o operandKind) String() string {
 func (o *operand) format(_64 bool) string {
 	switch o.kind {
 	case operandKindReg:
-		return formatVRegSized(o.r, _64)
+		return formatVRegSized(o.reg(), _64)
 	case operandKindMem:
-		return o.amode.String()
+		return o.addressMode().String()
 	case operandKindImm32:
-		return fmt.Sprintf("$%d", int32(o.imm32))
+		return fmt.Sprintf("$%d", int32(o.imm32()))
 	case operandKindLabel:
-		return backend.Label(o.imm32).String()
+		return backend.Label(o.imm32()).String()
 	default:
 		panic(fmt.Sprintf("BUG: invalid operand: %s", o.kind))
 	}
 }
 
+//go:inline
+func (o *operand) reg() regalloc.VReg {
+	return regalloc.VReg(o.data)
+}
+
+//go:inline
+func (o *operand) setReg(r regalloc.VReg) {
+	o.data = uint64(r)
+}
+
+//go:inline
+func (o *operand) addressMode() *amode {
+	return wazevoapi.PtrFromUintptr[amode](uintptr(o.data))
+}
+
+//go:inline
+func (o *operand) imm32() uint32 {
+	return uint32(o.data)
+}
+
 func (o *operand) label() backend.Label {
 	switch o.kind {
 	case operandKindLabel:
-		return backend.Label(o.imm32)
+		return backend.Label(o.data)
 	case operandKindMem:
-		if o.amode.kind == amodeRipRelative {
-			return o.amode.label
+		mem := o.addressMode()
+		if mem.kind == amodeRipRelative {
+			return mem.label
 		}
 	}
 	panic("BUG: invalid operand kind")
 }
 
 func newOperandLabel(label backend.Label) operand {
-	return operand{kind: operandKindLabel, imm32: uint32(label), r: regalloc.VRegInvalid}
+	return operand{kind: operandKindLabel, data: uint64(label)}
 }
 
 func newOperandReg(r regalloc.VReg) operand {
-	return operand{kind: operandKindReg, r: r}
+	return operand{kind: operandKindReg, data: uint64(r)}
 }
 
 func newOperandImm32(imm32 uint32) operand {
-	return operand{kind: operandKindImm32, imm32: imm32, r: regalloc.VRegInvalid}
+	return operand{kind: operandKindImm32, data: uint64(imm32)}
 }
 
-func newOperandMem(amode amode) operand {
-	return operand{kind: operandKindMem, amode: amode, r: regalloc.VRegInvalid}
+func newOperandMem(amode *amode) operand {
+	return operand{kind: operandKindMem, data: uint64(uintptr(unsafe.Pointer(amode)))}
 }
 
 // amode is a memory operand (addressing mode).
@@ -109,6 +130,8 @@ type amode struct {
 	label backend.Label
 }
 
+func resetAmode(am *amode) {}
+
 type amodeKind byte
 
 const (
@@ -176,19 +199,25 @@ func (a *amode) assignUses(i int, reg regalloc.VReg) {
 	}
 }
 
-func newAmodeImmReg(imm32 uint32, base regalloc.VReg) amode {
-	return amode{kind: amodeImmReg, imm32: imm32, base: base}
+func (m *machine) newAmodeImmReg(imm32 uint32, base regalloc.VReg) *amode {
+	ret := m.amodePool.Allocate()
+	*ret = amode{kind: amodeImmReg, imm32: imm32, base: base}
+	return ret
 }
 
-func newAmodeImmRBPReg(imm32 uint32) amode {
-	return amode{kind: amodeImmRBP, imm32: imm32, base: rbpVReg}
+func (m *machine) newAmodeImmRBPReg(imm32 uint32) *amode {
+	ret := m.amodePool.Allocate()
+	*ret = amode{kind: amodeImmRBP, imm32: imm32, base: rbpVReg}
+	return ret
 }
 
-func newAmodeRegRegShift(imm32 uint32, base, index regalloc.VReg, shift byte) amode {
+func (m *machine) newAmodeRegRegShift(imm32 uint32, base, index regalloc.VReg, shift byte) *amode {
 	if shift > 3 {
 		panic(fmt.Sprintf("BUG: invalid shift (must be 3>=): %d", shift))
 	}
-	return amode{kind: amodeRegRegShift, imm32: imm32, base: base, index: index, shift: shift}
+	ret := m.amodePool.Allocate()
+	*ret = amode{kind: amodeRegRegShift, imm32: imm32, base: base, index: index, shift: shift}
+	return ret
 }
 
 func (a *amode) resolveRipRelative(imm32 uint32) {
@@ -199,11 +228,10 @@ func (a *amode) resolveRipRelative(imm32 uint32) {
 	a.label = backend.LabelInvalid
 }
 
-func newAmodeRipRelative(label backend.Label) amode {
-	if label == backend.LabelInvalid {
-		panic("BUG: invalid label")
-	}
-	return amode{kind: amodeRipRelative, label: label}
+func (m *machine) newAmodeRipRelative(label backend.Label) *amode {
+	ret := m.amodePool.Allocate()
+	*ret = amode{kind: amodeRipRelative, label: label}
+	return ret
 }
 
 // String implements fmt.Stringer.
diff --git a/internal/engine/wazevo/module_engine.go b/internal/engine/wazevo/module_engine.go
index 9aee16f2..62a8b433 100644
--- a/internal/engine/wazevo/module_engine.go
+++ b/internal/engine/wazevo/module_engine.go
@@ -309,25 +309,13 @@ func (m *moduleEngine) LookupFunction(t *wasm.TableInstance, typeId wasm.Functio
 		panic(wasmruntime.ErrRuntimeInvalidTableAccess)
 	}
 
-	tf := functionFromUintptr(rawPtr)
+	tf := wazevoapi.PtrFromUintptr[functionInstance](rawPtr)
 	if tf.typeID != typeId {
 		panic(wasmruntime.ErrRuntimeIndirectCallTypeMismatch)
 	}
 	return moduleInstanceFromOpaquePtr(tf.moduleContextOpaquePtr), tf.indexInModule
 }
 
-// functionFromUintptr resurrects the original *function from the given uintptr
-// which comes from either funcref table or OpcodeRefFunc instruction.
-func functionFromUintptr(ptr uintptr) *functionInstance {
-	// Wraps ptrs as the double pointer in order to avoid the unsafe access as detected by race detector.
-	//
-	// For example, if we have (*function)(unsafe.Pointer(ptr)) instead, then the race detector's "checkptr"
-	// subroutine wanrs as "checkptr: pointer arithmetic result points to invalid allocation"
-	// https://github.com/golang/go/blob/1ce7fcf139417d618c2730010ede2afb41664211/src/runtime/checkptr.go#L69
-	var wrapped *uintptr = &ptr
-	return *(**functionInstance)(unsafe.Pointer(wrapped))
-}
-
 func moduleInstanceFromOpaquePtr(ptr *byte) *wasm.ModuleInstance {
 	return *(**wasm.ModuleInstance)(unsafe.Pointer(ptr))
 }
diff --git a/internal/engine/wazevo/wazevoapi/ptr.go b/internal/engine/wazevo/wazevoapi/ptr.go
new file mode 100644
index 00000000..f21e1a5d
--- /dev/null
+++ b/internal/engine/wazevo/wazevoapi/ptr.go
@@ -0,0 +1,15 @@
+package wazevoapi
+
+import "unsafe"
+
+// PtrFromUintptr resurrects the original *T from the given uintptr.
+// The caller of this function MUST be sure that ptr is valid.
+func PtrFromUintptr[T any](ptr uintptr) *T {
+	// Wraps ptrs as the double pointer in order to avoid the unsafe access as detected by race detector.
+	//
+	// For example, if we have (*function)(unsafe.Pointer(ptr)) instead, then the race detector's "checkptr"
+	// subroutine wanrs as "checkptr: pointer arithmetic result points to invalid allocation"
+	// https://github.com/golang/go/blob/1ce7fcf139417d618c2730010ede2afb41664211/src/runtime/checkptr.go#L69
+	var wrapped *uintptr = &ptr
+	return *(**T)(unsafe.Pointer(wrapped))
+}


Test patch:
diff --git a/internal/engine/wazevo/backend/isa/amd64/instr_encoding_test.go b/internal/engine/wazevo/backend/isa/amd64/instr_encoding_test.go
index 50a32412..a34b37fd 100644
--- a/internal/engine/wazevo/backend/isa/amd64/instr_encoding_test.go
+++ b/internal/engine/wazevo/backend/isa/amd64/instr_encoding_test.go
@@ -2,12 +2,20 @@ package amd64
 
 import (
 	"encoding/hex"
+	"runtime"
 	"testing"
 
 	"github.com/tetratelabs/wazero/internal/testing/require"
 )
 
 func TestInstruction_format_encode(t *testing.T) {
+	_, _, m := newSetupWithMockContext()
+	defer func() { runtime.KeepAlive(m) }()
+	newAmodeImmReg := m.newAmodeImmReg
+	newAmodeRegRegShift := m.newAmodeRegRegShift
+	newAmodeRipRelative := m.newAmodeRipRelative
+	allocateExitSeq := m.allocateExitSeq
+
 	for _, tc := range []struct {
 		setup      func(*instruction)
 		want       string
@@ -3096,7 +3104,7 @@ func TestInstruction_format_encode(t *testing.T) {
 			wantFormat: "cmovnlel 123(%rax), %esi",
 		},
 		{
-			setup: func(i *instruction) { i.asExitSeq(r15VReg) },
+			setup: func(i *instruction) { *i = *allocateExitSeq(r15VReg) },
 			// movq 0x10(%r15), %rbp
 			// movq 0x18(%r15), %rsp
 			// retq
diff --git a/internal/engine/wazevo/backend/isa/amd64/instr_test.go b/internal/engine/wazevo/backend/isa/amd64/instr_test.go
index 633e6fb5..3b89a23a 100644
--- a/internal/engine/wazevo/backend/isa/amd64/instr_test.go
+++ b/internal/engine/wazevo/backend/isa/amd64/instr_test.go
@@ -23,7 +23,8 @@ func TestMachine_lowerAluRmiROp_Uses_AssignUse(t *testing.T) {
 		{
 			name: "mem_reg",
 			instr: func(i *instruction) {
-				i.asAluRmiR(aluRmiROpcodeAdd, newOperandMem(newAmodeImmReg(123, vr0)), vr1, false)
+				_, _, m := newSetupWithMockContext()
+				i.asAluRmiR(aluRmiROpcodeAdd, newOperandMem(m.newAmodeImmReg(123, vr0)), vr1, false)
 			},
 			expected: "add 123(%rax), %ecx",
 		},
diff --git a/internal/engine/wazevo/backend/isa/amd64/lower_mem_test.go b/internal/engine/wazevo/backend/isa/amd64/lower_mem_test.go
index 0138cd4c..7f6d7460 100644
--- a/internal/engine/wazevo/backend/isa/amd64/lower_mem_test.go
+++ b/internal/engine/wazevo/backend/isa/amd64/lower_mem_test.go
@@ -1,6 +1,7 @@
 package amd64
 
 import (
+	"runtime"
 	"strings"
 	"testing"
 
@@ -11,12 +12,19 @@ import (
 )
 
 func TestMachine_lowerToAddressMode(t *testing.T) {
+	_, _, m := newSetupWithMockContext()
+	defer func() {
+		runtime.KeepAlive(m)
+	}()
+	newAmodeImmReg := m.newAmodeImmReg
+	newAmodeRegRegShift := m.newAmodeRegRegShift
+
 	nextVReg := regalloc.VReg(100).SetRegType(regalloc.RegTypeInt)
 	for _, tc := range []struct {
 		name  string
 		in    func(*mockCompiler, ssa.Builder, *machine) (ptr ssa.Value, offset uint32)
 		insts []string
-		am    amode
+		am    *amode
 	}{
 		{
 			name: "iadd const, const; offset != 0",
@@ -224,6 +232,13 @@ func TestMachine_lowerAddendFromInstr(t *testing.T) {
 }
 
 func TestMachine_lowerAddendsToAmode(t *testing.T) {
+	_, _, m := newSetupWithMockContext()
+	defer func() {
+		runtime.KeepAlive(m)
+	}()
+	newAmodeImmReg := m.newAmodeImmReg
+	newAmodeRegRegShift := m.newAmodeRegRegShift
+
 	x1, x2 := raxVReg, rcxVReg
 
 	nextVReg, nextNextVReg := regalloc.VReg(100).SetRegType(regalloc.RegTypeInt), regalloc.VReg(101).SetRegType(regalloc.RegTypeInt)
@@ -232,7 +247,7 @@ func TestMachine_lowerAddendsToAmode(t *testing.T) {
 		name   string
 		x, y   addend
 		offset uint32
-		exp    amode
+		exp    *amode
 		insts  []string
 	}{
 		{
diff --git a/internal/engine/wazevo/backend/isa/amd64/machine_regalloc_test.go b/internal/engine/wazevo/backend/isa/amd64/machine_regalloc_test.go
index f86d17b8..f89abb2b 100644
--- a/internal/engine/wazevo/backend/isa/amd64/machine_regalloc_test.go
+++ b/internal/engine/wazevo/backend/isa/amd64/machine_regalloc_test.go
@@ -217,7 +217,7 @@ func TestMachineSwap(t *testing.T) {
 				r15VReg.ID(): ssa.TypeI64, raxVReg.ID(): ssa.TypeI64,
 				xmm1VReg.ID(): ssa.TypeF64, xmm12VReg.ID(): ssa.TypeF64,
 			}
-			cur, i2 := m.allocateInstr().asUD2(), m.allocateInstr().asExitSeq(rsiVReg)
+			cur, i2 := m.allocateInstr().asUD2(), m.allocateExitSeq(rsiVReg)
 			cur.next = i2
 			i2.prev = cur
 
diff --git a/internal/engine/wazevo/backend/isa/amd64/machine_test.go b/internal/engine/wazevo/backend/isa/amd64/machine_test.go
index 6160cafd..5d03eb50 100644
--- a/internal/engine/wazevo/backend/isa/amd64/machine_test.go
+++ b/internal/engine/wazevo/backend/isa/amd64/machine_test.go
@@ -2,6 +2,7 @@ package amd64
 
 import (
 	"fmt"
+	"runtime"
 	"strings"
 	"testing"
 
@@ -139,6 +140,12 @@ func TestMachine_getOperand_Imm32_Reg(t *testing.T) {
 }
 
 func Test_machine_getOperand_Mem_Imm32_Reg(t *testing.T) {
+	_, _, m := newSetupWithMockContext()
+	defer func() {
+		runtime.KeepAlive(m)
+	}()
+	newAmodeImmReg := m.newAmodeImmReg
+
 	for _, tc := range []struct {
 		name         string
 		setup        func(*mockCompiler, ssa.Builder, *machine) *backend.SSAValueDefinition
@@ -260,7 +267,11 @@ func Test_machine_getOperand_Mem_Imm32_Reg(t *testing.T) {
 			ctx, b, m := newSetupWithMockContext()
 			def := tc.setup(ctx, b, m)
 			actual := m.getOperand_Mem_Imm32_Reg(def)
-			require.Equal(t, tc.exp, actual)
+			if tc.exp.kind == operandKindMem {
+				require.Equal(t, tc.exp.addressMode(), actual.addressMode())
+			} else {
+				require.Equal(t, tc.exp, actual)
+			}
 			require.Equal(t, strings.Join(tc.instructions, "\n"), formatEmittedInstructionsInCurrentBlock(m))
 		})
 	}