Repository: tetratelabs/wazero
Pull request #1829 (2023-11-07 01:52:19)

wazevo: implements linear reg alloc algorithm 
This implements the variant of the Go's register allocator as in https://github.com/golang/go/blob/master/src/cmd/compile/internal/ssa/regalloc.go

#1496 

mathetake: ok almost done finally...

Patch:
diff --git a/internal/engine/wazevo/backend/compiler.go b/internal/engine/wazevo/backend/compiler.go
index 6ddcc08b..0dd9cc2a 100644
--- a/internal/engine/wazevo/backend/compiler.go
+++ b/internal/engine/wazevo/backend/compiler.go
@@ -33,6 +33,9 @@ func newCompiler(ctx context.Context, mach Machine, builder ssa.Builder) *compil
 // Compiler is the backend of wazevo which takes ssa.Builder and Machine,
 // use the information there to emit the final machine code.
 type Compiler interface {
+	// SSABuilder returns the ssa.Builder used by this compiler.
+	SSABuilder() ssa.Builder
+
 	// Compile executes the following steps:
 	// 	1. Lower()
 	// 	2. RegAlloc()
@@ -66,9 +69,6 @@ type Compiler interface {
 	// Init initializes the internal state of the compiler for the next compilation.
 	Init()
 
-	// ResolveSignature returns the ssa.Signature of the given ssa.SignatureID.
-	ResolveSignature(id ssa.SignatureID) *ssa.Signature
-
 	// AllocateVReg allocates a new virtual register of the given type.
 	AllocateVReg(typ ssa.Type) regalloc.VReg
 
@@ -102,9 +102,6 @@ type Compiler interface {
 
 	// Emit4Bytes appends 4 bytes to the buffer. Used during the code emission.
 	Emit4Bytes(b uint32)
-
-	// LoopNestingForestRoots returns the roots of the loop nesting forest.
-	LoopNestingForestRoots() []ssa.BasicBlock
 }
 
 // RelocationInfo represents the relocation information for a call instruction.
@@ -357,9 +354,9 @@ func (c *compiler) MatchInstrOneOf(def *SSAValueDefinition, opcodes []ssa.Opcode
 	return ssa.OpcodeInvalid
 }
 
-// ResolveSignature implements Compiler.ResolveSignature.
-func (c *compiler) ResolveSignature(id ssa.SignatureID) *ssa.Signature {
-	return c.ssaBuilder.ResolveSignature(id)
+// SSABuilder implements Compiler .SSABuilder.
+func (c *compiler) SSABuilder() ssa.Builder {
+	return c.ssaBuilder
 }
 
 // AddSourceOffsetInfo implements Compiler.AddSourceOffsetInfo.
@@ -392,8 +389,3 @@ func (c *compiler) Emit4Bytes(b uint32) {
 func (c *compiler) Buf() []byte {
 	return c.buf
 }
-
-// LoopNestingForestRoots implements Compiler.LoopNestingForestRoots.
-func (c *compiler) LoopNestingForestRoots() []ssa.BasicBlock {
-	return c.ssaBuilder.LoopNestingForestRoots()
-}
diff --git a/internal/engine/wazevo/backend/isa/arm64/abi.go b/internal/engine/wazevo/backend/isa/arm64/abi.go
index 638c8239..f75aa55b 100644
--- a/internal/engine/wazevo/backend/isa/arm64/abi.go
+++ b/internal/engine/wazevo/backend/isa/arm64/abi.go
@@ -358,7 +358,7 @@ func (m *machine) lowerCall(si *ssa.Instruction) {
 	} else {
 		indirectCalleePtr, sigID, args = si.CallIndirectData()
 	}
-	calleeABI := m.getOrCreateABIImpl(m.compiler.ResolveSignature(sigID))
+	calleeABI := m.getOrCreateABIImpl(m.compiler.SSABuilder().ResolveSignature(sigID))
 
 	stackSlotSize := calleeABI.alignedArgResultStackSlotSize()
 	if m.maxRequiredStackSizeForCalls < stackSlotSize+16 {
diff --git a/internal/engine/wazevo/backend/isa/arm64/instr.go b/internal/engine/wazevo/backend/isa/arm64/instr.go
index ee364736..3f5a33ec 100644
--- a/internal/engine/wazevo/backend/isa/arm64/instr.go
+++ b/internal/engine/wazevo/backend/isa/arm64/instr.go
@@ -36,13 +36,27 @@ type (
 	instructionKind int
 )
 
+// IsCall implements regalloc.Instr IsCall.
+func (i *instruction) IsCall() bool {
+	return i.kind == call
+}
+
+// IsIndirectCall implements regalloc.Instr IsIndirectCall.
+func (i *instruction) IsIndirectCall() bool {
+	return i.kind == callInd
+}
+
+// IsReturn implements regalloc.Instr IsReturn.
+func (i *instruction) IsReturn() bool {
+	return i.kind == ret
+}
+
 type defKind byte
 
 const (
 	defKindNone defKind = iota + 1
 	defKindRD
 	defKindCall
-	defKindVecRRR
 )
 
 var defKinds = [numInstructionKinds]defKind{
@@ -112,7 +126,8 @@ var defKinds = [numInstructionKinds]defKind{
 	vecTbl:               defKindRD,
 	vecTbl2:              defKindRD,
 	vecPermute:           defKindRD,
-	vecRRR:               defKindVecRRR,
+	vecRRR:               defKindRD,
+	vecRRRRewrite:        defKindNone,
 	fpuToInt:             defKindRD,
 	intToFpu:             defKindRD,
 	cCmpImm:              defKindNone,
@@ -121,34 +136,30 @@ var defKinds = [numInstructionKinds]defKind{
 	emitSourceOffsetInfo: defKindNone,
 }
 
-// defs returns the list of regalloc.VReg that are defined by the instruction.
+// Defs returns the list of regalloc.VReg that are defined by the instruction.
 // In order to reduce the number of allocations, the caller can pass the slice to be used.
-func (i *instruction) defs(regs []regalloc.VReg) []regalloc.VReg {
+func (i *instruction) Defs(regs *[]regalloc.VReg) []regalloc.VReg {
+	*regs = (*regs)[:0]
 	switch defKinds[i.kind] {
 	case defKindNone:
 	case defKindRD:
-		regs = append(regs, i.rd.nr())
+		*regs = append(*regs, i.rd.nr())
 	case defKindCall:
-		regs = append(regs, i.abi.retRealRegs...)
-	case defKindVecRRR:
-		if vecOp(i.u1) != vecOpBsl {
-			regs = append(regs, i.rd.nr())
-		}
+		*regs = append(*regs, i.abi.retRealRegs...)
 	default:
 		panic(fmt.Sprintf("defKind for %v not defined", i))
 	}
-	return regs
+	return *regs
 }
 
-func (i *instruction) assignDef(reg regalloc.VReg) {
+// AssignDef implements regalloc.Instr AssignDef.
+func (i *instruction) AssignDef(reg regalloc.VReg) {
 	switch defKinds[i.kind] {
 	case defKindNone:
 	case defKindRD:
 		i.rd = i.rd.assignReg(reg)
 	case defKindCall:
 		panic("BUG: call instructions shouldn't be assigned")
-	case defKindVecRRR:
-		i.rd = i.rd.assignReg(reg)
 	default:
 		panic(fmt.Sprintf("defKind for %v not defined", i))
 	}
@@ -168,7 +179,7 @@ const (
 	useKindAMode
 	useKindRNAMode
 	useKindCond
-	useKindVecRRR
+	useKindVecRRRRewrite
 )
 
 var useKinds = [numInstructionKinds]useKind{
@@ -237,7 +248,8 @@ var useKinds = [numInstructionKinds]useKind{
 	vecShiftImm:          useKindRN,
 	vecTbl:               useKindRNRM,
 	vecTbl2:              useKindRNRN1RM,
-	vecRRR:               useKindVecRRR,
+	vecRRR:               useKindRNRM,
+	vecRRRRewrite:        useKindVecRRRRewrite,
 	vecPermute:           useKindRNRM,
 	fpuToInt:             useKindRN,
 	intToFpu:             useKindRN,
@@ -247,84 +259,79 @@ var useKinds = [numInstructionKinds]useKind{
 	emitSourceOffsetInfo: useKindNone,
 }
 
-// uses returns the list of regalloc.VReg that are used by the instruction.
+// Uses returns the list of regalloc.VReg that are used by the instruction.
 // In order to reduce the number of allocations, the caller can pass the slice to be used.
-func (i *instruction) uses(regs []regalloc.VReg) []regalloc.VReg {
+func (i *instruction) Uses(regs *[]regalloc.VReg) []regalloc.VReg {
+	*regs = (*regs)[:0]
 	switch useKinds[i.kind] {
 	case useKindNone:
 	case useKindRN:
 		if rn := i.rn.reg(); rn.Valid() {
-			regs = append(regs, rn)
+			*regs = append(*regs, rn)
 		}
 	case useKindRNRM:
 		if rn := i.rn.reg(); rn.Valid() {
-			regs = append(regs, rn)
+			*regs = append(*regs, rn)
 		}
 		if rm := i.rm.reg(); rm.Valid() {
-			regs = append(regs, rm)
+			*regs = append(*regs, rm)
 		}
 	case useKindRNRMRA:
 		if rn := i.rn.reg(); rn.Valid() {
-			regs = append(regs, rn)
+			*regs = append(*regs, rn)
 		}
 		if rm := i.rm.reg(); rm.Valid() {
-			regs = append(regs, rm)
+			*regs = append(*regs, rm)
 		}
 		if ra := i.ra.reg(); ra.Valid() {
-			regs = append(regs, ra)
+			*regs = append(*regs, ra)
 		}
 	case useKindRNRN1RM:
 		if rn := i.rn.reg(); rn.Valid() && rn.IsRealReg() {
 			rn1 := regalloc.FromRealReg(rn.RealReg()+1, rn.RegType())
-			regs = append(regs, rn, rn1)
+			*regs = append(*regs, rn, rn1)
 		}
 		if rm := i.rm.reg(); rm.Valid() {
-			regs = append(regs, rm)
+			*regs = append(*regs, rm)
 		}
 	case useKindRet:
-		regs = append(regs, i.abi.retRealRegs...)
+		*regs = append(*regs, i.abi.retRealRegs...)
 	case useKindAMode:
 		if amodeRN := i.amode.rn; amodeRN.Valid() {
-			regs = append(regs, amodeRN)
+			*regs = append(*regs, amodeRN)
 		}
 		if amodeRM := i.amode.rm; amodeRM.Valid() {
-			regs = append(regs, amodeRM)
+			*regs = append(*regs, amodeRM)
 		}
 	case useKindRNAMode:
-		regs = append(regs, i.rn.reg())
+		*regs = append(*regs, i.rn.reg())
 		if amodeRN := i.amode.rn; amodeRN.Valid() {
-			regs = append(regs, amodeRN)
+			*regs = append(*regs, amodeRN)
 		}
 		if amodeRM := i.amode.rm; amodeRM.Valid() {
-			regs = append(regs, amodeRM)
+			*regs = append(*regs, amodeRM)
 		}
 	case useKindCond:
 		cnd := cond(i.u1)
 		if cnd.kind() != condKindCondFlagSet {
-			regs = append(regs, cnd.register())
+			*regs = append(*regs, cnd.register())
 		}
 	case useKindCall:
-		regs = append(regs, i.abi.argRealRegs...)
+		*regs = append(*regs, i.abi.argRealRegs...)
 	case useKindCallInd:
-		regs = append(regs, i.rn.nr())
-		regs = append(regs, i.abi.argRealRegs...)
-	case useKindVecRRR:
-		if rn := i.rn.reg(); rn.Valid() {
-			regs = append(regs, rn)
-		}
-		if rm := i.rm.reg(); rm.Valid() {
-			regs = append(regs, rm)
-		}
-		if vecOp(i.u1) == vecOpBsl {
-			regs = append(regs, i.rd.reg())
-		}
+		*regs = append(*regs, i.rn.nr())
+		*regs = append(*regs, i.abi.argRealRegs...)
+	case useKindVecRRRRewrite:
+		*regs = append(*regs, i.rn.reg())
+		*regs = append(*regs, i.rm.reg())
+		*regs = append(*regs, i.rd.reg())
 	default:
 		panic(fmt.Sprintf("useKind for %v not defined", i))
 	}
-	return regs
+	return *regs
 }
 
-func (i *instruction) assignUse(index int, reg regalloc.VReg) {
+func (i *instruction) AssignUse(index int, reg regalloc.VReg) {
 	switch useKinds[i.kind] {
 	case useKindNone:
 	case useKindRN:
@@ -341,7 +348,7 @@ func (i *instruction) assignUse(index int, reg regalloc.VReg) {
 				i.rm = i.rm.assignReg(reg)
 			}
 		}
-	case useKindVecRRR:
+	case useKindVecRRRRewrite:
 		if index == 0 {
 			if rn := i.rn.reg(); rn.Valid() {
 				i.rn = i.rn.assignReg(reg)
@@ -834,9 +841,10 @@ func (i *instruction) asMove32(rd, rn regalloc.VReg) {
 	i.rn, i.rd = operandNR(rn), operandNR(rd)
 }
 
-func (i *instruction) asMove64(rd, rn regalloc.VReg) {
+func (i *instruction) asMove64(rd, rn regalloc.VReg) *instruction {
 	i.kind = mov64
 	i.rn, i.rd = operandNR(rn), operandNR(rd)
+	return i
 }
 
 func (i *instruction) asFpuMov64(rd, rn regalloc.VReg) {
@@ -844,9 +852,10 @@ func (i *instruction) asFpuMov64(rd, rn regalloc.VReg) {
 	i.rn, i.rd = operandNR(rn), operandNR(rd)
 }
 
-func (i *instruction) asFpuMov128(rd, rn regalloc.VReg) {
+func (i *instruction) asFpuMov128(rd, rn regalloc.VReg) *instruction {
 	i.kind = fpuMov128
 	i.rn, i.rd = operandNR(rn), operandNR(rd)
+	return i
 }
 
 func (i *instruction) asMovToVec(rd, rn operand, arr vecArrangement, index vecIndex) {
@@ -948,7 +957,16 @@ func (i *instruction) asVecRRR(op vecOp, rd, rn, rm operand, arr vecArrangement)
 	i.u2 = uint64(arr)
 }
 
-func (i *instruction) isCopy() bool {
+// asVecRRRRewrite encodes a vector instruction that rewrites the destination register.
+// IMPORTANT: the destination register must be already defined before this instruction.
+func (i *instruction) asVecRRRRewrite(op vecOp, rd, rn, rm operand, arr vecArrangement) {
+	i.kind = vecRRRRewrite
+	i.u1 = uint64(op)
+	i.rn, i.rd, i.rm = rn, rd, rm
+	i.u2 = uint64(arr)
+}
+
+func (i *instruction) IsCopy() bool {
 	op := i.kind
 	// We do not include mov32 as it is not a copy instruction in the sense that it does not preserve the upper 32 bits,
 	// and it is only used in the translation of IReduce, not the actual copy indeed.
@@ -1269,7 +1287,7 @@ func (i *instruction) String() (str string) {
 		)
 	case vecMiscNarrow:
 		panic("TODO")
-	case vecRRR:
+	case vecRRR, vecRRRRewrite:
 		str = fmt.Sprintf("%s %s, %s, %s",
 			vecOp(i.u1),
 			formatVRegVec(i.rd.nr(), vecArrangement(i.u2), vecIndexNone),
@@ -1556,6 +1574,11 @@ const (
 	vecMiscNarrow
 	// vecRRR represents a vector ALU operation.
 	vecRRR
+	// vecRRRRewrite is exactly the same as vecRRR except that this rewrites the destination register.
+	// For example, BSL instruction rewrites the destination register, and the existing value influences the result.
+	// Therefore, the "destination" register in vecRRRRewrite will be treated as "use" which makes the register outlive
+	// the instruction while this instruction doesn't have "def" in the context of register allocation.
+	vecRRRRewrite
 	// vecMisc represents a vector two register miscellaneous instruction.
 	vecMisc
 	// vecLanes represents a vector instruction across lanes.
diff --git a/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go b/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go
index ef5a6f12..80a8f1ef 100644
--- a/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go
+++ b/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go
@@ -356,6 +356,11 @@ func (i *instruction) encode(c backend.Compiler) {
 			i.u3 == 1,
 		))
 	case vecRRR:
+		if op := vecOp(i.u1); op == vecOpBsl || op == vecOpBit || op == vecOpUmlal {
+			panic(fmt.Sprintf("vecOp %s must use vecRRRRewrite instead of vecRRR", op.String()))
+		}
+		fallthrough
+	case vecRRRRewrite:
 		c.Emit4Bytes(encodeVecRRR(
 			vecOp(i.u1),
 			regNumberInEncoding[i.rd.realReg()],
diff --git a/internal/engine/wazevo/backend/isa/arm64/lower_instr.go b/internal/engine/wazevo/backend/isa/arm64/lower_instr.go
index 5c70ee6d..74484d1d 100644
--- a/internal/engine/wazevo/backend/isa/arm64/lower_instr.go
+++ b/internal/engine/wazevo/backend/isa/arm64/lower_instr.go
@@ -362,16 +362,16 @@ func (m *machine) LowerInstr(instr *ssa.Instruction) {
 
 		// creg is overwritten by BSL, so we need to move it to the result register before the instruction
 		// in case when it is used somewhere else.
-		rd := m.compiler.VRegOf(instr.Return())
 		mov := m.allocateInstr()
 		mov.asFpuMov128(tmp.nr(), creg.nr())
 		m.insert(mov)
 
 		ins := m.allocateInstr()
-		ins.asVecRRR(vecOpBsl, tmp, rn, rm, vecArrangement16B)
+		ins.asVecRRRRewrite(vecOpBsl, tmp, rn, rm, vecArrangement16B)
 		m.insert(ins)
 
 		mov2 := m.allocateInstr()
+		rd := m.compiler.VRegOf(instr.Return())
 		mov2.asFpuMov128(rd, tmp.nr())
 		m.insert(mov2)
 	case ssa.OpcodeVanyTrue, ssa.OpcodeVallTrue:
@@ -1071,6 +1071,8 @@ func (m *machine) lowerVIMul(rd, rn, rm operand, arr vecArrangement) {
 		tmp2 := operandNR(m.compiler.AllocateVReg(ssa.TypeV128))
 		tmp3 := operandNR(m.compiler.AllocateVReg(ssa.TypeV128))
 
+		tmpRes := operandNR(m.compiler.AllocateVReg(ssa.TypeV128))
+
 		// Following the algorithm in https://chromium-review.googlesource.com/c/v8/v8/+/1781696
 		rev64 := m.allocateInstr()
 		rev64.asVecMisc(vecOpRev64, tmp2, rm, vecArrangement4S)
@@ -1092,13 +1094,20 @@ func (m *machine) lowerVIMul(rd, rn, rm operand, arr vecArrangement) {
 		xtn2.asVecMisc(vecOpXtn, tmp3, rm, vecArrangement2S)
 		m.insert(xtn2)
 
+		// Note: do not write the result directly into result yet. This is the same reason as in bsl.
+		// In short, in UMLAL instruction, the result register is also one of the source register, and
+		// the value on the result register is significant.
 		shll := m.allocateInstr()
-		shll.asVecMisc(vecOpShll, rd, tmp2, vecArrangement2S)
+		shll.asVecMisc(vecOpShll, tmpRes, tmp2, vecArrangement2S)
 		m.insert(shll)
 
 		umlal := m.allocateInstr()
-		umlal.asVecRRR(vecOpUmlal, rd, tmp3, tmp1, vecArrangement2S)
+		umlal.asVecRRRRewrite(vecOpUmlal, tmpRes, tmp3, tmp1, vecArrangement2S)
 		m.insert(umlal)
+
+		mov := m.allocateInstr()
+		mov.asFpuMov128(rd.nr(), tmpRes.nr())
+		m.insert(mov)
 	}
 }
 
@@ -1109,7 +1118,10 @@ func (m *machine) lowerVMinMaxPseudo(instr *ssa.Instruction, max bool) {
 	rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone)
 	rm := m.getOperand_NR(m.compiler.ValueDefinition(y), extModeNone)
 
-	// TODO: this usage of tmp is weird - it should be fine directly using rd. (seems a bug in regalloc).
+	// Note: this usage of tmp is important.
+	// BSL modifies the destination register, so we need to use a temporary register so that
+	// the actual definition of the destination register happens *after* the BSL instruction.
+	// That way, we can force the spill instruction to be inserted after the BSL instruction.
 	tmp := operandNR(m.compiler.AllocateVReg(ssa.TypeV128))
 
 	fcmgt := m.allocateInstr()
@@ -1122,7 +1134,7 @@ func (m *machine) lowerVMinMaxPseudo(instr *ssa.Instruction, max bool) {
 	m.insert(fcmgt)
 
 	bsl := m.allocateInstr()
-	bsl.asVecRRR(vecOpBsl, tmp, rm, rn, vecArrangement16B)
+	bsl.asVecRRRRewrite(vecOpBsl, tmp, rm, rn, vecArrangement16B)
 	m.insert(bsl)
 
 	res := operandNR(m.compiler.VRegOf(instr.Return()))
@@ -1235,7 +1247,7 @@ func (m *machine) lowerFcopysignImpl(rd, rn, rm, tmpI, tmpF operand, _64bit bool
 	m.insert(mov)
 
 	vbit := m.allocateInstr()
-	vbit.asVecRRR(vecOpBit, tmpReg, rm, tmpF, vecArrangement8B)
+	vbit.asVecRRRRewrite(vecOpBit, tmpReg, rm, tmpF, vecArrangement8B)
 	m.insert(vbit)
 
 	movDst := m.allocateInstr()
@@ -1918,15 +1930,21 @@ func (m *machine) lowerSelectVec(rc, rn, rm, rd operand) {
 	m.insert(cset)
 
 	// Then move the bits to the result vector register.
+	tmp2 := operandNR(m.compiler.AllocateVReg(ssa.TypeV128))
 	dup := m.allocateInstr()
-	dup.asVecDup(rd, operandNR(allOnesOrZero), vecArrangement2D)
+	dup.asVecDup(tmp2, operandNR(allOnesOrZero), vecArrangement2D)
 	m.insert(dup)
 
-	// Now that `rd` has either all bits one or zero depending on `rc`,
+	// Now that `tmp2` has either all bits one or zero depending on `rc`,
 	// we can use bsl to select between `rn` and `rm`.
 	ins := m.allocateInstr()
-	ins.asVecRRR(vecOpBsl, rd, rn, rm, vecArrangement16B)
+	ins.asVecRRRRewrite(vecOpBsl, tmp2, rn, rm, vecArrangement16B)
 	m.insert(ins)
+
+	// Finally, move the result to the destination register.
+	mov2 := m.allocateInstr()
+	mov2.asFpuMov128(rd.nr(), tmp2.nr())
+	m.insert(mov2)
 }
 
 // copyToTmp copies the given regalloc.VReg to a temporary register. This is called before cbr to avoid the regalloc issue
diff --git a/internal/engine/wazevo/backend/isa/arm64/machine.go b/internal/engine/wazevo/backend/isa/arm64/machine.go
index 3663feb3..59d60e29 100644
--- a/internal/engine/wazevo/backend/isa/arm64/machine.go
+++ b/internal/engine/wazevo/backend/isa/arm64/machine.go
@@ -250,6 +250,7 @@ func resetLabelPosition(l *labelPosition) {
 	*l = labelPosition{}
 }
 
+// FlushPendingInstructions implements backend.Machine.
 func (m *machine) FlushPendingInstructions() {
 	l := len(m.pendingInstructions)
 	if l == 0 {
diff --git a/internal/engine/wazevo/backend/isa/arm64/machine_pro_epi_logue.go b/internal/engine/wazevo/backend/isa/arm64/machine_pro_epi_logue.go
index fd0e58a7..62b4e0ec 100644
--- a/internal/engine/wazevo/backend/isa/arm64/machine_pro_epi_logue.go
+++ b/internal/engine/wazevo/backend/isa/arm64/machine_pro_epi_logue.go
@@ -194,7 +194,7 @@ func (m *machine) SetupEpilogue() {
 
 		// Removes the redundant copy instruction.
 		// TODO: doing this in `SetupEpilogue` seems weird. Find a better home.
-		if cur.isCopy() && cur.rn.realReg() == cur.rd.realReg() {
+		if cur.IsCopy() && cur.rn.realReg() == cur.rd.realReg() {
 			prev, next := cur.prev, cur.next
 			// Remove the copy instruction.
 			prev.next = next
diff --git a/internal/engine/wazevo/backend/isa/arm64/machine_regalloc.go b/internal/engine/wazevo/backend/isa/arm64/machine_regalloc.go
index 6851f5b5..881d0975 100644
--- a/internal/engine/wazevo/backend/isa/arm64/machine_regalloc.go
+++ b/internal/engine/wazevo/backend/isa/arm64/machine_regalloc.go
@@ -11,58 +11,48 @@ type (
 	// regAllocFunctionImpl implements regalloc.Function.
 	regAllocFunctionImpl struct {
 		m *machine
-		// iter is the iterator for reversePostOrderBlocks.
+		// iter is the iterator for reversePostOrderBlocks
 		iter                   int
 		reversePostOrderBlocks []regAllocBlockImpl
 		// labelToRegAllocBlockIndex maps label to the index of reversePostOrderBlocks.
 		labelToRegAllocBlockIndex map[label]int
-		// vs is used for regalloc.Instr Defs() and Uses() methods, defined here for reuse.
-		vs                     []regalloc.VReg
-		loopNestingForestRoots []ssa.BasicBlock
+		loopNestingForestRoots    []ssa.BasicBlock
 	}
 
 	// regAllocBlockImpl implements regalloc.Block.
 	regAllocBlockImpl struct {
 		// f is the function this instruction belongs to. Used to reuse the regAllocFunctionImpl.predsSlice slice for Defs() and Uses().
-		f   *regAllocFunctionImpl
-		sb  ssa.BasicBlock
-		l   label
-		pos *labelPosition
-		// instrImpl is re-used for all instructions in this block.
-		instrImpl                 regAllocInstrImpl
+		f                         *regAllocFunctionImpl
+		sb                        ssa.BasicBlock
+		l                         label
+		pos                       *labelPosition
 		loopNestingForestChildren []ssa.BasicBlock
-	}
-
-	// regAllocInstrImpl implements regalloc.Instr.
-	regAllocInstrImpl struct {
-		// f is the function this instruction belongs to. Used to reuse the regAllocFunctionImpl.vs slice for Defs() and Uses().
-		f *regAllocFunctionImpl
-		i *instruction
+		cur                       *instruction
+		id                        int
+		cachedLastInstr           regalloc.Instr
 	}
 )
 
 func (f *regAllocFunctionImpl) addBlock(sb ssa.BasicBlock, l label, pos *labelPosition) {
 	i := len(f.reversePostOrderBlocks)
 	f.reversePostOrderBlocks = append(f.reversePostOrderBlocks, regAllocBlockImpl{
-		f:         f,
-		sb:        sb,
-		l:         l,
-		pos:       pos,
-		instrImpl: regAllocInstrImpl{f: f},
+		f:   f,
+		sb:  sb,
+		l:   l,
+		pos: pos,
+		id:  int(sb.ID()),
 	})
 	f.labelToRegAllocBlockIndex[l] = i
 }
 
 func (f *regAllocFunctionImpl) reset() {
 	f.reversePostOrderBlocks = f.reversePostOrderBlocks[:0]
-	f.vs = f.vs[:0]
 	f.iter = 0
 }
 
 var (
 	_ regalloc.Function = (*regAllocFunctionImpl)(nil)
 	_ regalloc.Block    = (*regAllocBlockImpl)(nil)
-	_ regalloc.Instr    = (*regAllocInstrImpl)(nil)
 )
 
 // PostOrderBlockIteratorBegin implements regalloc.Function PostOrderBlockIteratorBegin.
@@ -106,25 +96,91 @@ func (f *regAllocFunctionImpl) ClobberedRegisters(regs []regalloc.VReg) {
 // StoreRegisterBefore implements regalloc.Function StoreRegisterBefore.
 func (f *regAllocFunctionImpl) StoreRegisterBefore(v regalloc.VReg, instr regalloc.Instr) {
 	m := f.m
-	m.insertStoreRegisterAt(v, instr.(*regAllocInstrImpl).i, false)
+	m.insertStoreRegisterAt(v, instr.(*instruction), false)
+}
+
+// SwapAtEndOfBlock implements regalloc.Function SwapAtEndOfBlock.
+func (f *regAllocFunctionImpl) SwapAtEndOfBlock(x1, x2, tmp regalloc.VReg, block regalloc.Block) {
+	blk := block.(*regAllocBlockImpl)
+	cur := blk.LastInstr().(*instruction)
+	cur = cur.prev
+	f.m.swap(cur, x1, x2, tmp)
+}
+
+func (m *machine) swap(cur *instruction, x1, x2, tmp regalloc.VReg) {
+	prevNext := cur.next
+	var mov1, mov2, mov3 *instruction
+	if x1.RegType() == regalloc.RegTypeInt {
+		if !tmp.Valid() {
+			tmp = tmpRegVReg
+		}
+		mov1 = m.allocateInstr().asMove64(tmp, x1)
+		mov2 = m.allocateInstr().asMove64(x1, x2)
+		mov3 = m.allocateInstr().asMove64(x2, tmp)
+		cur = linkInstr(cur, mov1)
+		cur = linkInstr(cur, mov2)
+		cur = linkInstr(cur, mov3)
+		linkInstr(cur, prevNext)
+	} else {
+		if !tmp.Valid() {
+			r2 := x2.RealReg()
+			// Temporarily spill x1 to stack.
+			cur = m.insertStoreRegisterAt(x1, cur, true).prev
+			// Then move x2 to x1.
+			cur = linkInstr(cur, m.allocateInstr().asFpuMov128(x1, x2))
+			linkInstr(cur, prevNext)
+			// Then reload the original value on x1 from stack to r2.
+			m.insertReloadRegisterAt(x1.SetRealReg(r2), cur, true)
+		} else {
+			mov1 = m.allocateInstr().asFpuMov128(tmp, x1)
+			mov2 = m.allocateInstr().asFpuMov128(x1, x2)
+			mov3 = m.allocateInstr().asFpuMov128(x2, tmp)
+			cur = linkInstr(cur, mov1)
+			cur = linkInstr(cur, mov2)
+			cur = linkInstr(cur, mov3)
+			linkInstr(cur, prevNext)
+		}
+	}
+}
+
+// InsertMoveBefore implements regalloc.Function InsertMoveBefore.
+func (f *regAllocFunctionImpl) InsertMoveBefore(dst, src regalloc.VReg, instr regalloc.Instr) {
+	m := f.m
+
+	typ := src.RegType()
+	if typ != dst.RegType() {
+		panic("BUG: src and dst must have the same type")
+	}
+
+	mov := m.allocateInstr()
+	if typ == regalloc.RegTypeInt {
+		mov.asMove64(dst, src)
+	} else {
+		mov.asFpuMov128(dst, src)
+	}
+
+	cur := instr.(*instruction).prev
+	prevNext := cur.next
+	cur = linkInstr(cur, mov)
+	linkInstr(cur, prevNext)
 }
 
 // StoreRegisterAfter implements regalloc.Function StoreRegisterAfter.
 func (f *regAllocFunctionImpl) StoreRegisterAfter(v regalloc.VReg, instr regalloc.Instr) {
 	m := f.m
-	m.insertStoreRegisterAt(v, instr.(*regAllocInstrImpl).i, true)
+	m.insertStoreRegisterAt(v, instr.(*instruction), true)
 }
 
 // ReloadRegisterBefore implements regalloc.Function ReloadRegisterBefore.
 func (f *regAllocFunctionImpl) ReloadRegisterBefore(v regalloc.VReg, instr regalloc.Instr) {
 	m := f.m
-	m.insertReloadRegisterAt(v, instr.(*regAllocInstrImpl).i, false)
+	m.insertReloadRegisterAt(v, instr.(*instruction), false)
 }
 
 // ReloadRegisterAfter implements regalloc.Function ReloadRegisterAfter.
 func (f *regAllocFunctionImpl) ReloadRegisterAfter(v regalloc.VReg, instr regalloc.Instr) {
 	m := f.m
-	m.insertReloadRegisterAt(v, instr.(*regAllocInstrImpl).i, true)
+	m.insertReloadRegisterAt(v, instr.(*instruction), true)
 }
 
 // Done implements regalloc.Function Done.
@@ -136,7 +192,7 @@ func (f *regAllocFunctionImpl) Done() {
 
 // ID implements regalloc.Block ID.
 func (r *regAllocBlockImpl) ID() int {
-	return int(r.sb.ID())
+	return r.id
 }
 
 // Preds implements regalloc.Block Preds.
@@ -177,7 +233,7 @@ func (r *regAllocBlockImpl) LoopHeader() bool {
 
 // LoopNestingForestRoots implements regalloc.Function LoopNestingForestRoots.
 func (f *regAllocFunctionImpl) LoopNestingForestRoots() int {
-	f.loopNestingForestRoots = f.m.compiler.LoopNestingForestRoots()
+	f.loopNestingForestRoots = f.m.compiler.SSABuilder().LoopNestingForestRoots()
 	return len(f.loopNestingForestRoots)
 }
 
@@ -205,17 +261,21 @@ func (r *regAllocBlockImpl) LoopNestingForestChild(i int) regalloc.Block {
 
 // InstrIteratorBegin implements regalloc.Block InstrIteratorBegin.
 func (r *regAllocBlockImpl) InstrIteratorBegin() regalloc.Instr {
-	r.instrImpl.i = r.pos.begin
-	return &r.instrImpl
+	r.cur = r.pos.begin
+	return r.cur
 }
 
 // InstrIteratorNext implements regalloc.Block InstrIteratorNext.
 func (r *regAllocBlockImpl) InstrIteratorNext() regalloc.Instr {
 	for {
-		instr := r.instrIteratorNext()
+		if r.cur == r.pos.end {
+			return nil
+		}
+		instr := r.cur.next
+		r.cur = instr
 		if instr == nil {
 			return nil
-		} else if instr.i.addedBeforeRegAlloc {
+		} else if instr.addedBeforeRegAlloc {
 			// Only concerned about the instruction added before regalloc.
 			return instr
 		}
@@ -224,17 +284,21 @@ func (r *regAllocBlockImpl) InstrIteratorNext() regalloc.Instr {
 
 // InstrRevIteratorBegin implements regalloc.Block InstrRevIteratorBegin.
 func (r *regAllocBlockImpl) InstrRevIteratorBegin() regalloc.Instr {
-	r.instrImpl.i = r.pos.end
-	return &r.instrImpl
+	r.cur = r.pos.end
+	return r.cur
 }
 
 // InstrRevIteratorNext implements regalloc.Block InstrRevIteratorNext.
 func (r *regAllocBlockImpl) InstrRevIteratorNext() regalloc.Instr {
 	for {
-		instr := r.instrIteratorRevNext()
+		if r.cur == r.pos.begin {
+			return nil
+		}
+		instr := r.cur.prev
+		r.cur = instr
 		if instr == nil {
 			return nil
-		} else if instr.i.addedBeforeRegAlloc {
+		} else if instr.addedBeforeRegAlloc {
 			// Only concerned about the instruction added before regalloc.
 			return instr
 		}
@@ -242,63 +306,19 @@ func (r *regAllocBlockImpl) InstrRevIteratorNext() regalloc.Instr {
 }
 
 // BlockParams implements regalloc.Block BlockParams.
-func (r *regAllocBlockImpl) BlockParams() []regalloc.VReg {
+func (r *regAllocBlockImpl) BlockParams(regs *[]regalloc.VReg) []regalloc.VReg {
 	c := r.f.m.compiler
-	regs := r.f.vs[:0]
+	*regs = (*regs)[:0]
 	for i := 0; i < r.sb.Params(); i++ {
 		v := c.VRegOf(r.sb.Param(i))
-		regs = append(regs, v)
+		*regs = append(*regs, v)
 	}
-	return regs
-}
-
-func (r *regAllocBlockImpl) instrIteratorNext() *regAllocInstrImpl {
-	cur := r.instrImpl.i
-	if r.pos.end == cur {
-		return nil
-	}
-	r.instrImpl.i = cur.next
-	return &r.instrImpl
-}
-
-func (r *regAllocBlockImpl) instrIteratorRevNext() *regAllocInstrImpl {
-	cur := r.instrImpl.i
-	if r.pos.begin == cur {
-		return nil
-	}
-	r.instrImpl.i = cur.prev
-	return &r.instrImpl
+	return *regs
 }
 
 // Entry implements regalloc.Block Entry.
 func (r *regAllocBlockImpl) Entry() bool { return r.sb.EntryBlock() }
 
-// Format implements regalloc.Instr String.
-func (r *regAllocInstrImpl) String() string {
-	return r.i.String()
-}
-
-// Defs implements regalloc.Instr Defs.
-func (r *regAllocInstrImpl) Defs() []regalloc.VReg {
-	regs := r.f.vs[:0]
-	regs = r.i.defs(regs)
-	r.f.vs = regs
-	return regs
-}
-
-// Uses implements regalloc.Instr Uses.
-func (r *regAllocInstrImpl) Uses() []regalloc.VReg {
-	regs := r.f.vs[:0]
-	regs = r.i.uses(regs)
-	r.f.vs = regs
-	return regs
-}
-
-// IsCopy implements regalloc.Instr IsCopy.
-func (r *regAllocInstrImpl) IsCopy() bool {
-	return r.i.isCopy()
-}
-
 // RegisterInfo implements backend.Machine.
 func (m *machine) RegisterInfo(debug bool) *regalloc.RegisterInfo {
 	if debug {
@@ -328,32 +348,7 @@ func (m *machine) Function() regalloc.Function {
 	return &m.regAllocFn
 }
 
-// IsCall implements regalloc.Instr IsCall.
-func (r *regAllocInstrImpl) IsCall() bool {
-	return r.i.kind == call
-}
-
-// IsIndirectCall implements regalloc.Instr IsIndirectCall.
-func (r *regAllocInstrImpl) IsIndirectCall() bool {
-	return r.i.kind == callInd
-}
-
-// IsReturn implements regalloc.Instr IsReturn.
-func (r *regAllocInstrImpl) IsReturn() bool {
-	return r.i.kind == ret
-}
-
-// AssignUse implements regalloc.Instr AssignUse.
-func (r *regAllocInstrImpl) AssignUse(i int, v regalloc.VReg) {
-	r.i.assignUse(i, v)
-}
-
-// AssignDef implements regalloc.Instr AssignDef.
-func (r *regAllocInstrImpl) AssignDef(v regalloc.VReg) {
-	r.i.assignDef(v)
-}
-
-func (m *machine) insertStoreRegisterAt(v regalloc.VReg, instr *instruction, after bool) {
+func (m *machine) insertStoreRegisterAt(v regalloc.VReg, instr *instruction, after bool) *instruction {
 	if !v.IsRealReg() {
 		panic("BUG: VReg must be backed by real reg to be stored")
 	}
@@ -374,10 +369,10 @@ func (m *machine) insertStoreRegisterAt(v regalloc.VReg, instr *instruction, aft
 	store.asStore(operandNR(v), amode, typ.Bits())
 
 	cur = linkInstr(cur, store)
-	linkInstr(cur, prevNext)
+	return linkInstr(cur, prevNext)
 }
 
-func (m *machine) insertReloadRegisterAt(v regalloc.VReg, instr *instruction, after bool) {
+func (m *machine) insertReloadRegisterAt(v regalloc.VReg, instr *instruction, after bool) *instruction {
 	if !v.IsRealReg() {
 		panic("BUG: VReg must be backed by real reg to be stored")
 	}
@@ -407,5 +402,50 @@ func (m *machine) insertReloadRegisterAt(v regalloc.VReg, instr *instruction, af
 	}
 
 	cur = linkInstr(cur, load)
-	linkInstr(cur, prevNext)
+	return linkInstr(cur, prevNext)
+}
+
+// LowestCommonAncestor implements regalloc.Function LowestCommonAncestor.
+func (f *regAllocFunctionImpl) LowestCommonAncestor(blk1, blk2 regalloc.Block) regalloc.Block {
+	ret := f.m.compiler.SSABuilder().LowestCommonAncestor(blk1.(*regAllocBlockImpl).sb, blk2.(*regAllocBlockImpl).sb)
+	l := f.m.ssaBlockIDToLabels[ret.ID()]
+	index := f.labelToRegAllocBlockIndex[l]
+	return &f.reversePostOrderBlocks[index]
+}
+
+func (f *regAllocFunctionImpl) Idom(blk regalloc.Block) regalloc.Block {
+	builder := f.m.compiler.SSABuilder()
+	idom := builder.Idom(blk.(*regAllocBlockImpl).sb)
+	if idom == nil {
+		panic("BUG: idom must not be nil")
+	}
+	l := f.m.ssaBlockIDToLabels[idom.ID()]
+	index := f.labelToRegAllocBlockIndex[l]
+	return &f.reversePostOrderBlocks[index]
+}
+
+// FirstInstr implements regalloc.Block FirstInstr.
+func (r *regAllocBlockImpl) FirstInstr() regalloc.Instr {
+	return r.pos.begin
+}
+
+// LastInstr implements regalloc.Block LastInstr.
+func (r *regAllocBlockImpl) LastInstr() regalloc.Instr {
+	if r.cachedLastInstr == nil {
+		cur := r.pos.end
+		for cur.kind == nop0 {
+			cur = cur.prev
+			if cur == r.pos.begin {
+				r.cachedLastInstr = r.pos.end
+				return r.cachedLastInstr
+			}
+		}
+		switch cur.kind {
+		case br:
+			r.cachedLastInstr = cur
+		default:
+			r.cachedLastInstr = r.pos.end
+		}
+	}
+	return r.cachedLastInstr
 }
diff --git a/internal/engine/wazevo/backend/regalloc/api.go b/internal/engine/wazevo/backend/regalloc/api.go
index 4e2e6fd1..81240cfd 100644
--- a/internal/engine/wazevo/backend/regalloc/api.go
+++ b/internal/engine/wazevo/backend/regalloc/api.go
@@ -25,6 +25,21 @@ type (
 		ReversePostOrderBlockIteratorNext() Block
 		// ClobberedRegisters tell the clobbered registers by this function.
 		ClobberedRegisters([]VReg)
+		// Done tells the implementation that register allocation is done, and it can finalize the stack
+		Done()
+		// LoopNestingForestRoots returns the number of roots of the loop nesting forest in a function.
+		LoopNestingForestRoots() int
+		// LoopNestingForestRoot returns the i-th root of the loop nesting forest in a function.
+		LoopNestingForestRoot(i int) Block
+		// LowestCommonAncestor returns the lowest common ancestor of two blocks in the dominator tree.
+		LowestCommonAncestor(blk1, blk2 Block) Block
+		// Idom returns the immediate dominator of the given block.
+		Idom(blk Block) Block
+
+		// Followings are for rewriting the function.
+
+		// SwapAtEndOfBlock swaps the two virtual registers at the end of the given block.
+		SwapAtEndOfBlock(x1, x2, tmp VReg, block Block)
 		// StoreRegisterBefore inserts store instruction(s) before the given instruction for the given virtual register.
 		StoreRegisterBefore(v VReg, instr Instr)
 		// StoreRegisterAfter inserts store instruction(s) after the given instruction for the given virtual register.
@@ -33,20 +48,16 @@ type (
 		ReloadRegisterBefore(v VReg, instr Instr)
 		// ReloadRegisterAfter inserts reload instruction(s) after the given instruction for the given virtual register.
 		ReloadRegisterAfter(v VReg, instr Instr)
-		// Done tells the implementation that register allocation is done, and it can finalize the stack
-		Done()
-		// LoopNestingForestRoots returns the number of roots of the loop nesting forest in a function.
-		LoopNestingForestRoots() int
-		// LoopNestingForestRoot returns the i-th root of the loop nesting forest in a function.
-		LoopNestingForestRoot(i int) Block
+		// InsertMoveBefore inserts move instruction(s) before the given instruction for the given virtual registers.
+		InsertMoveBefore(dst, src VReg, instr Instr)
 	}
 
 	// Block is a basic block in the CFG of a function, and it consists of multiple instructions, and predecessor Block(s).
 	Block interface {
-		// ID returns the unique identifier of this block.
+		// ID returns the unique identifier of this block which is ordered in the reverse post-order traversal of the CFG.
 		ID() int
 		// BlockParams returns the virtual registers used as the parameters of this block.
-		BlockParams() []VReg
+		BlockParams(*[]VReg) []VReg
 		// InstrIteratorBegin returns the first instruction in this block. Instructions added after lowering must be skipped.
 		// Note: multiple Instr(s) will not be held at the same time, so it's safe to use the same impl for the return Instr.
 		InstrIteratorBegin() Instr
@@ -57,6 +68,14 @@ type (
 		InstrRevIteratorBegin() Instr
 		// InstrRevIteratorNext is the same as InstrIteratorNext, but in the reverse order.
 		InstrRevIteratorNext() Instr
+		// FirstInstr returns the fist instruction in this block where instructions will be inserted after it.
+		FirstInstr() Instr
+		// LastInstr returns the last instruction in this block where instructions will be inserted before it.
+		// Such insertions only happen when we need to insert spill/reload instructions to adjust the merge edges.
+		// If the very last instruction is the unconditional branching, then the returned instruction is the one before it.
+		// Note that at the time of register allocation, all the critical edges are already split, so there is no need
+		// to worry about the case where branching instruction has multiple successors.
+		LastInstr() Instr
 		// Preds returns the number of predecessors of this block in the CFG.
 		Preds() int
 		// Pred returns the i-th predecessor of this block in the CFG.
@@ -80,11 +99,10 @@ type (
 		fmt.Stringer
 
 		// Defs returns the virtual registers defined by this instruction.
-		// Note: multiple returned []VReg will not be held at the same time, so it's safe to use the same slice for this.
-		Defs() []VReg
+		Defs(*[]VReg) []VReg
 		// Uses returns the virtual registers used by this instruction.
 		// Note: multiple returned []VReg will not be held at the same time, so it's safe to use the same slice for this.
-		Uses() []VReg
+		Uses(*[]VReg) []VReg
 		// AssignUse assigns the RealReg-allocated virtual register used by this instruction at the given index.
 		AssignUse(index int, v VReg)
 		// AssignDef assigns a RealReg-allocated virtual register defined by this instruction.
diff --git a/internal/engine/wazevo/backend/regalloc/assign.go b/internal/engine/wazevo/backend/regalloc/assign.go
deleted file mode 100644
index 851236c7..00000000
--- a/internal/engine/wazevo/backend/regalloc/assign.go
+++ /dev/null
@@ -1,236 +0,0 @@
-package regalloc
-
-import (
-	"fmt"
-
-	"github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi"
-)
-
-// assignRegisters assigns real registers to virtual registers on each instruction.
-// This is called after coloring is done.
-func (a *Allocator) assignRegisters(f Function) {
-	for blk := f.ReversePostOrderBlockIteratorBegin(); blk != nil; blk = f.ReversePostOrderBlockIteratorNext() {
-		a.assignRegistersPerBlock(f, blk, a.vRegIDToNode)
-	}
-}
-
-// assignRegistersPerBlock assigns real registers to virtual registers on each instruction in a block.
-func (a *Allocator) assignRegistersPerBlock(f Function, blk Block, vRegIDToNode []*node) {
-	if wazevoapi.RegAllocLoggingEnabled {
-		fmt.Println("---------------------- assigning registers for block", blk.ID(), "----------------------")
-	}
-
-	blkID := blk.ID()
-	var pc programCounter
-	for instr := blk.InstrIteratorBegin(); instr != nil; instr = blk.InstrIteratorNext() {
-		tree := a.blockInfos[blkID].intervalMng
-		a.assignRegistersPerInstr(f, pc, instr, vRegIDToNode, tree)
-		pc += pcStride
-	}
-}
-
-func (a *Allocator) assignRegistersPerInstr(f Function, pc programCounter, instr Instr, vRegIDToNode []*node, intervalMng *intervalManager) {
-	if indirect := instr.IsIndirectCall(); instr.IsCall() || indirect {
-		intervalMng.collectActiveNodes(
-			// To find the all the live registers "after" call, we need to add pcDefOffset for search.
-			pc+pcDefOffset,
-			&a.nodes1,
-			// Only take care of non-real VRegs (e.g. VReg.IsRealReg() == false) since
-			// the real VRegs are already placed in the right registers at this point.
-			false,
-		)
-		for _, active := range a.nodes1 {
-			if r := active.r; a.regInfo.isCallerSaved(r) {
-				v := active.v.SetRealReg(r)
-				f.StoreRegisterBefore(v, instr)
-				f.ReloadRegisterAfter(v, instr)
-			}
-		}
-		if indirect {
-			// Direct function calls do not need assignment, while indirect one needs the assignment on the function pointer.
-			a.assignIndirectCall(f, instr, vRegIDToNode)
-		}
-
-		if wazevoapi.RegAllocValidationEnabled {
-			for _, def := range instr.Defs() {
-				if !def.IsRealReg() {
-					panic(fmt.Sprintf("BUG: call/indirect call instruction must define only real registers: %s", def))
-				}
-			}
-		}
-		return
-	} else if instr.IsReturn() {
-		return
-	}
-
-	usesSpills := a.vs[:0]
-	uses := instr.Uses()
-	for i, u := range uses {
-		if u.IsRealReg() {
-			continue
-		}
-		if wazevoapi.RegAllocLoggingEnabled {
-			fmt.Printf("%s uses %s(%d)\n", instr, u.RegType(), u.ID())
-		}
-		n := vRegIDToNode[u.ID()]
-		if !n.spill() {
-			instr.AssignUse(i, u.SetRealReg(n.r))
-		} else {
-			usesSpills = append(usesSpills, u)
-		}
-	}
-
-	defs := instr.Defs()
-	defSpill := VRegInvalid
-	switch len(defs) {
-	case 0:
-	case 1:
-		d := defs[0]
-		if !d.IsRealReg() {
-			if wazevoapi.RegAllocLoggingEnabled {
-				fmt.Printf("%s defines %s(%d)\n", instr, d.RegType(), d.ID())
-			}
-
-			n := vRegIDToNode[d.ID()]
-			if !n.spill() {
-				instr.AssignDef(d.SetRealReg(n.r))
-			} else {
-				defSpill = n.v
-			}
-		}
-	default:
-		panic("BUG: multiple def instructions must be special cased")
-	}
-
-	a.handleSpills(f, pc, instr, usesSpills, defSpill, intervalMng)
-	a.vs = usesSpills[:0] // for reuse.
-}
-
-func (a *Allocator) handleSpills(
-	f Function, pc programCounter, instr Instr,
-	usesSpills []VReg, defSpill VReg, intervalMng *intervalManager,
-) {
-	_usesSpills, _defSpill := len(usesSpills) > 0, defSpill.Valid()
-	switch {
-	case !_usesSpills && !_defSpill: // Nothing to do.
-	case !_usesSpills && _defSpill: // Only definition is spilled.
-		intervalMng.collectActiveNodes(pc+pcDefOffset, &a.nodes1, true)
-		a.spillHandler.init(a.nodes1, instr)
-
-		r, evictedNode := a.spillHandler.getUnusedOrEvictReg(defSpill.RegType(), a.regInfo)
-		if evictedNode != nil {
-			evictedNodeV := evictedNode.v.SetRealReg(evictedNode.assignedRealReg())
-			f.StoreRegisterBefore(evictedNodeV, instr)
-			f.ReloadRegisterAfter(evictedNodeV, instr)
-		}
-
-		defSpill = defSpill.SetRealReg(r)
-		instr.AssignDef(defSpill)
-
-		f.StoreRegisterAfter(defSpill, instr)
-
-	case _usesSpills:
-		intervalMng.collectActiveNodes(pc, &a.nodes1, true)
-		a.spillHandler.init(a.nodes1, instr)
-
-		var evicted [3]*node
-		var evictedCount int
-		for i, u := range usesSpills {
-			r, evictedNode := a.spillHandler.getUnusedOrEvictReg(u.RegType(), a.regInfo)
-			if evictedNode != nil {
-				evicted[evictedCount] = evictedNode
-				evictedCount++
-			}
-			usesSpills[i] = u.SetRealReg(r)
-		}
-
-		for i := 0; i < evictedCount; i++ {
-			evictedNode := evicted[i]
-			evictedNodeV := evictedNode.v.SetRealReg(evictedNode.assignedRealReg())
-			f.StoreRegisterBefore(evictedNodeV, instr)
-			f.ReloadRegisterAfter(evictedNodeV, instr)
-		}
-
-		for _, u := range usesSpills {
-			f.ReloadRegisterBefore(u, instr)
-		}
-
-		for useIndex, v := range instr.Uses() {
-			for _, u := range usesSpills {
-				if v.ID() == u.ID() {
-					instr.AssignUse(useIndex, u)
-				}
-			}
-		}
-
-		if _defSpill {
-			// We can reuse the register in usesSpills for the definition.
-			for _, u := range usesSpills {
-				if defSpill.RegType() == u.RegType() {
-					defSpill = defSpill.SetRealReg(u.RealReg())
-					break
-				}
-			}
-
-			if !defSpill.IsRealReg() {
-				// This case, the destination register type is different from the source registers.
-				intervalMng.collectActiveNodes(pc+pcDefOffset, &a.nodes1, true)
-				a.spillHandler.init(a.nodes1, instr)
-				r, evictedNode := a.spillHandler.getUnusedOrEvictReg(defSpill.RegType(), a.regInfo)
-				if evictedNode != nil {
-					evictedNodeV := evictedNode.v.SetRealReg(evictedNode.assignedRealReg())
-					f.StoreRegisterBefore(evictedNodeV, instr)
-					f.ReloadRegisterAfter(evictedNodeV, instr)
-				}
-				defSpill = defSpill.SetRealReg(r)
-			}
-
-			instr.AssignDef(defSpill)
-			f.StoreRegisterAfter(defSpill, instr)
-		}
-	}
-}
-
-func (a *Allocator) assignIndirectCall(f Function, instr Instr, vRegIDToNode []*node) {
-	a.nodes1 = a.nodes1[:0]
-	uses := instr.Uses()
-	if wazevoapi.RegAllocValidationEnabled {
-		var nonRealRegs int
-		for _, u := range uses {
-			if !u.IsRealReg() {
-				nonRealRegs++
-			}
-		}
-		if nonRealRegs != 1 {
-			panic(fmt.Sprintf("BUG: indirect call must have only one non-real register (for function pointer): %d", nonRealRegs))
-		}
-	}
-
-	var v VReg
-	for _, u := range uses {
-		if !u.IsRealReg() {
-			v = u
-			break
-		}
-	}
-
-	if v.RegType() != RegTypeInt {
-		panic(fmt.Sprintf("BUG: function pointer for indirect call must be an integer register: %s", v))
-	}
-
-	n := vRegIDToNode[v.ID()]
-	if n.spill() {
-		// If the function pointer is spilled, we need to reload it to a register.
-		// But at this point, all the caller-saved registers are saved, we can use a callee-saved register to reload.
-		for _, r := range a.regInfo.AllocatableRegisters[RegTypeInt] {
-			if a.regInfo.isCallerSaved(r) {
-				v = v.SetRealReg(r)
-				f.ReloadRegisterBefore(v, instr)
-				break
-			}
-		}
-	} else {
-		v = v.SetRealReg(n.r)
-	}
-	instr.AssignUse(0, v)
-}
diff --git a/internal/engine/wazevo/backend/regalloc/coloring.go b/internal/engine/wazevo/backend/regalloc/coloring.go
deleted file mode 100644
index 00f3245d..00000000
--- a/internal/engine/wazevo/backend/regalloc/coloring.go
+++ /dev/null
@@ -1,253 +0,0 @@
-package regalloc
-
-import (
-	"fmt"
-	"sort"
-
-	"github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi"
-)
-
-// buildNeighbors builds the neighbors for each node in the interference graph.
-func (a *Allocator) buildNeighbors() {
-	allocated := a.nodePool.Allocated()
-	if diff := allocated - len(a.dedup); diff > 0 {
-		a.dedup = append(a.dedup, make([]bool, diff+1)...)
-	}
-	for i := 0; i < allocated; i++ {
-		n := a.nodePool.View(i)
-		a.buildNeighborsFor(n)
-	}
-}
-
-func (a *Allocator) buildNeighborsFor(n *node) {
-	for _, r := range n.ranges {
-		// Collects all the nodes that are in the same range.
-		for _, neighbor := range r.nodes {
-			neighborID := neighbor.id
-			if neighbor.v.RegType() != n.v.RegType() {
-				continue
-			}
-			if neighbor != n && !a.dedup[neighborID] {
-				n.neighbors = append(n.neighbors, neighbor)
-				a.dedup[neighborID] = true
-			}
-		}
-
-		// And also collects all the nodes that are in the neighbor ranges.
-		for _, neighborInterval := range r.neighbors {
-			for _, neighbor := range neighborInterval.nodes {
-				if neighbor.v.RegType() != n.v.RegType() {
-					continue
-				}
-				neighborID := neighbor.id
-				if neighbor != n && !a.dedup[neighborID] {
-					n.neighbors = append(n.neighbors, neighbor)
-					a.dedup[neighborID] = true
-				}
-			}
-		}
-	}
-	// Reset for the next iteration.
-	for _, neighbor := range n.neighbors {
-		a.dedup[neighbor.id] = false
-	}
-}
-
-// coloring does the graph coloring for both RegType(s).
-// Since the graphs are disjoint per RegType, we do it by RegType separately.
-func (a *Allocator) coloring() {
-	a.collectNodesByRegType(RegTypeInt)
-	a.coloringFor(a.regInfo.AllocatableRegisters[RegTypeInt])
-	a.collectNodesByRegType(RegTypeFloat)
-	a.coloringFor(a.regInfo.AllocatableRegisters[RegTypeFloat])
-}
-
-// collectNodesByRegType collects all the nodes that are of the given register type.
-// The result is stored in Allocator.nodes1.
-func (a *Allocator) collectNodesByRegType(regType RegType) {
-	a.nodes1 = a.nodes1[:0]
-	// Gather all the nodes that are of the given register type.
-	for i := 0; i < a.nodePool.Allocated(); i++ {
-		// TODO: when we implement the coalescing, we should skip the coalesced nodes here.
-		n := a.nodePool.View(i)
-		if n.v.RegType() == regType {
-			a.nodes1 = append(a.nodes1, n)
-		}
-	}
-}
-
-// coloringFor colors the graph by the given allocatable registers. The algorithm here is called "Chaitin's Algorithm".
-//
-// This assumes that the coloring target nodes are stored at Allocator.nodes1.
-//
-// TODO: the implementation here is not optimized at all. Come back later.
-func (a *Allocator) coloringFor(allocatable []RealReg) {
-	degreeSortedNodes := a.nodes1 // We assume nodes1 holds all the nodes of the given register type.
-	// Reuses the nodes2 slice and the degrees map from the previous iteration.
-	coloringStack := a.nodes2[:0]
-
-	numAllocatable := len(allocatable)
-
-	// Initialize the degree for each node which is defined as the number of neighbors.
-	for _, n := range degreeSortedNodes {
-		n.degree = len(n.neighbors)
-	}
-
-	// Sort the nodes by the current degree.
-	sort.SliceStable(degreeSortedNodes, func(i, j int) bool {
-		return degreeSortedNodes[i].degree < degreeSortedNodes[j].degree
-	})
-
-	// First step of the algorithm:
-	// until we have removed the all the nodes:
-	//	1. pop the nodes with degree < numAllocatable.
-	//  2. if there's no node with degree < numAllocatable, spill one node.
-	popTargetQueue := a.nodes3[:0] // Only containing the nodes whose degree < numAllocatable.
-	for i := 0; i < len(degreeSortedNodes); i++ {
-		n := degreeSortedNodes[i]
-		if n.degree < numAllocatable {
-			popTargetQueue = append(popTargetQueue, n)
-			n.visited = true
-		} else {
-			break
-		}
-	}
-	total := len(degreeSortedNodes)
-	for len(coloringStack) != total {
-		if len(popTargetQueue) == 0 {
-			// If no node can be popped, it means that the graph is not colorable. We need to forcibly choose one node to pop.
-			// TODO: currently we just choose the last node. We could do this more wisely. e.g. choose the one without pre-colored neighbors etc.
-			// Swap the top node with the last node.
-			tail := len(degreeSortedNodes) - 1
-			for i := 0; i < len(degreeSortedNodes); i++ {
-				j := tail - i
-				n := degreeSortedNodes[j]
-				if !n.visited {
-					popTargetQueue = append(popTargetQueue, n)
-					n.visited = true
-					break
-				}
-			}
-		}
-
-		for len(popTargetQueue) > 0 {
-			top := popTargetQueue[0]
-			popTargetQueue = popTargetQueue[1:]
-			for _, neighbor := range top.neighbors {
-				neighbor.degree--
-				if neighbor.degree < numAllocatable {
-					if !neighbor.visited {
-						popTargetQueue = append(popTargetQueue, neighbor)
-						neighbor.visited = true
-					}
-				}
-			}
-			coloringStack = append(coloringStack, top)
-		}
-	}
-
-	// Assign colors.
-	neighborColorsSet := &a.realRegSet
-	tail := len(coloringStack) - 1
-	for i := range coloringStack {
-		n := coloringStack[tail-i]
-		if n.r != RealRegInvalid {
-			// This means the node is a pre-colored register.
-			continue
-		}
-
-		if wazevoapi.RegAllocLoggingEnabled {
-			fmt.Printf("coloring %s\n", n)
-		}
-
-		// Gather already used colors.
-		for _, neighbor := range n.neighbors {
-			if neighborColor := neighbor.r; neighborColor != RealRegInvalid {
-				neighborColorsSet[neighborColor] = true
-			}
-		}
-
-		a.assignColor(n, neighborColorsSet, allocatable)
-
-		if wazevoapi.RegAllocLoggingEnabled {
-			fmt.Printf("\tassigned color: %s\n", a.regInfo.RealRegName(n.r))
-		}
-
-		// Reset the map for the next iteration.
-		for j := range neighborColorsSet {
-			neighborColorsSet[j] = false
-		}
-	}
-
-	if wazevoapi.RegAllocValidationEnabled {
-		for _, n := range coloringStack {
-			if n.r == RealRegInvalid {
-				continue
-			}
-			for _, neighbor := range n.neighbors {
-				if n.r == neighbor.r {
-					panic(fmt.Sprintf("BUG color conflict: %s vs %s", n.v, neighbor.v))
-				}
-			}
-		}
-	}
-
-	// Reuses the slices for the next coloring.
-	a.nodes1 = degreeSortedNodes[:0]
-	a.nodes2 = coloringStack[:0]
-	a.nodes3 = popTargetQueue[:0]
-}
-
-func (a *Allocator) assignColor(n *node, neighborColorsSet *[128]bool, allocatable []RealReg) {
-	if cfv := n.copyFromVReg; cfv != nil && cfv.r != RealRegInvalid {
-		r := cfv.r
-		if a.allocatableSet[r] {
-			if !neighborColorsSet[r] {
-				n.r = r
-				a.allocatedRegSet[r] = true
-				return
-			}
-		}
-	}
-
-	if ctv := n.copyToVReg; ctv != nil && ctv.r != RealRegInvalid {
-		r := ctv.r
-		if a.allocatableSet[r] {
-			if !neighborColorsSet[r] {
-				n.r = r
-				a.allocatedRegSet[r] = true
-				return
-			}
-		}
-	}
-
-	if r := n.copyFromReal; r != RealRegInvalid {
-		if a.allocatableSet[r] {
-			if !neighborColorsSet[r] {
-				n.r = r
-				a.allocatedRegSet[r] = true
-				return
-			}
-		}
-	}
-
-	if r := n.copyToReal; r != RealRegInvalid {
-		if a.allocatableSet[r] {
-			if !neighborColorsSet[r] {
-				n.r = r
-				a.allocatedRegSet[r] = true
-				return
-			}
-		}
-	}
-
-	if n.r == RealRegInvalid {
-		for _, color := range allocatable {
-			if !neighborColorsSet[color] {
-				n.r = color
-				a.allocatedRegSet[color] = true
-				break
-			}
-		}
-	}
-}
diff --git a/internal/engine/wazevo/backend/regalloc/intervals.go b/internal/engine/wazevo/backend/regalloc/intervals.go
deleted file mode 100644
index 6f43e4b9..00000000
--- a/internal/engine/wazevo/backend/regalloc/intervals.go
+++ /dev/null
@@ -1,147 +0,0 @@
-package regalloc
-
-import (
-	"sort"
-
-	"github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi"
-)
-
-type (
-	// intervalManager manages intervals for each block.
-	intervalManager struct {
-		allocator       wazevoapi.Pool[interval]
-		intervals       map[intervalKey]*interval
-		sortedIntervals []*interval
-		collectionCur   int
-	}
-	// interval represents an interval in the block, which is a range of program counters.
-	// Each interval has a list of nodes which are live in the interval.
-	interval struct {
-		begin, end programCounter
-		// nodes are nodes which are alive in this interval.
-		nodes []*node
-		// neighbors are intervals which are adjacent to this interval.
-		neighbors []*interval
-	}
-	// intervalKey is a key for intervalManager.intervals which consists of begin and end.
-	intervalKey uint64
-)
-
-func newIntervalManager() *intervalManager {
-	return &intervalManager{
-		allocator: wazevoapi.NewPool[interval](resetIntervalTreeNode),
-		intervals: make(map[intervalKey]*interval),
-	}
-}
-
-func resetIntervalTreeNode(i *interval) {
-	i.begin = 0
-	i.end = 0
-	i.nodes = i.nodes[:0]
-	i.neighbors = i.neighbors[:0]
-}
-
-// intervalTreeNodeKey returns a key for intervalManager.intervals.
-func intervalTreeNodeKey(begin, end programCounter) intervalKey {
-	return intervalKey(begin) | intervalKey(end)<<32
-}
-
-// insert inserts a node into the interval tree.
-func (t *intervalManager) insert(n *node, begin, end programCounter) *interval {
-	key := intervalTreeNodeKey(begin, end)
-	if i, ok := t.intervals[key]; ok {
-		i.nodes = append(i.nodes, n)
-		return i
-	}
-	i := t.allocator.Allocate()
-	i.nodes = append(i.nodes, n)
-	i.begin = begin
-	i.end = end
-	t.intervals[key] = i
-	t.sortedIntervals = append(t.sortedIntervals, i) // Will be sorted later.
-	return i
-}
-
-func (t *intervalManager) reset() {
-	t.allocator.Reset()
-	t.sortedIntervals = t.sortedIntervals[:0]
-	t.intervals = make(map[intervalKey]*interval)
-	t.collectionCur = 0
-}
-
-// build is called after all the intervals are inserted. This sorts the intervals,
-// and builds the neighbor intervals for each interval.
-func (t *intervalManager) build() {
-	sort.Slice(t.sortedIntervals, func(i, j int) bool {
-		ii, ij := t.sortedIntervals[i], t.sortedIntervals[j]
-		if ii.begin == ij.begin {
-			return ii.end < ij.end
-		}
-		return ii.begin < ij.begin
-	})
-
-	var cur int
-	var existingEndMax programCounter = -1
-	for i, _interval := range t.sortedIntervals {
-		begin, end := _interval.begin, _interval.end
-		if begin > existingEndMax {
-			cur = i
-			existingEndMax = end
-		} else {
-			for j := cur; j < i; j++ {
-				existing := t.sortedIntervals[j]
-				if existing.end < begin {
-					continue
-				}
-				if existing.begin > end {
-					panic("BUG")
-				}
-				_interval.neighbors = append(_interval.neighbors, existing)
-				existing.neighbors = append(existing.neighbors, _interval)
-			}
-			if end > existingEndMax {
-				existingEndMax = end
-			}
-		}
-	}
-}
-
-// collectActiveNodes collects nodes which are alive at pc, and the result is stored in `collected`.
-// If `real` is true, only nodes which are assigned to a real register are collected.
-func (t *intervalManager) collectActiveNodes(pc programCounter, collected *[]*node, real bool) {
-	*collected = (*collected)[:0]
-
-	// Advance the collection cursor until the current interval's end is greater than pc.
-	l := len(t.sortedIntervals)
-	for cur := t.collectionCur; cur < l; cur++ {
-		curNode := t.sortedIntervals[cur]
-		if curNode.end < pc {
-			t.collectionCur++
-			continue
-		} else {
-			break
-		}
-	}
-
-	for cur := t.collectionCur; cur < l; cur++ {
-		curNode := t.sortedIntervals[cur]
-		if curNode.end < pc {
-			continue
-		} else if curNode.begin > pc {
-			break
-		}
-
-		for _, n := range curNode.nodes {
-			if real {
-				if n.assignedRealReg() == RealRegInvalid {
-					continue
-				}
-			} else {
-				if n.spill() || n.v.IsRealReg() {
-					continue
-				}
-			}
-			*collected = append(*collected, n)
-		}
-	}
-}
diff --git a/internal/engine/wazevo/backend/regalloc/reg.go b/internal/engine/wazevo/backend/regalloc/reg.go
index 6852ad80..e365936b 100644
--- a/internal/engine/wazevo/backend/regalloc/reg.go
+++ b/internal/engine/wazevo/backend/regalloc/reg.go
@@ -2,7 +2,6 @@ package regalloc
 
 import (
 	"fmt"
-	"math/bits"
 
 	"github.com/tetratelabs/wazero/internal/engine/wazevo/ssa"
 )
@@ -14,10 +13,6 @@ type VReg uint64
 // VRegID is the lower 32bit of VReg, which is the pure identifier of VReg without RealReg info.
 type VRegID uint32
 
-const (
-	MaxVRegID = ^VRegID(0)
-)
-
 // RealReg returns the RealReg of this VReg.
 func (v VReg) RealReg() RealReg {
 	return RealReg(v >> 32)
@@ -63,215 +58,6 @@ func (v VReg) Valid() bool {
 	return v.ID() != vRegIDInvalid && v.RegType() != RegTypeInvalid
 }
 
-// VRegIDMinSet is used to collect the minimum ID by type in a collection of
-// virtual registers.
-//
-// We store the min values + 1 so the zero-value of the VRegIDMinSet is valid.
-type VRegIDMinSet [NumRegType]VRegID
-
-func (mins *VRegIDMinSet) Min(t RegType) VRegID {
-	return mins[t] - 1
-}
-
-func (mins *VRegIDMinSet) Observe(v VReg) {
-	if rt, id := v.RegType(), v.ID(); id < (mins[rt] - 1) {
-		mins[rt] = id + 1
-	}
-}
-
-// VRegTable is a data structure designed for fast association of program
-// counters to virtual registers.
-type VRegTable [NumRegType]VRegTypeTable
-
-func (t *VRegTable) Contains(v VReg) bool {
-	return t[v.RegType()].Contains(v.ID())
-}
-
-// Lookup returns the program counter associated with the given virtual register.
-func (t *VRegTable) Lookup(v VReg) programCounter {
-	return t[v.RegType()].Lookup(v.ID())
-}
-
-func (t *VRegTable) Insert(v VReg, p programCounter) {
-	if v.IsRealReg() {
-		panic("BUG: cannot insert real registers in virtual register table")
-	}
-	t[v.RegType()].Insert(v.ID(), p)
-}
-
-func (t *VRegTable) Range(f func(VReg, programCounter)) {
-	for i := range t {
-		t[i].Range(func(id VRegID, p programCounter) {
-			f(VReg(id).SetRegType(RegType(i)), p)
-		})
-	}
-}
-
-func (t *VRegTable) Reset(minVRegIDs VRegIDMinSet) {
-	for i := range t {
-		t[i].Reset(minVRegIDs.Min(RegType(i)))
-	}
-}
-
-// VRegTypeTable implements a table for virtual registers of a specific type.
-//
-// The parent VRegTable uses 3 instances of this type to maintain a table for
-// each virtual register type.
-//
-// The virtual register type table uses a bitset to accelerate checking whether
-// a virtual register exists in the table. The bitset also helps identify which
-// slots in the program counter array are used, since the table is sparse and
-// some locations may not be occupied and will have the value zero even though
-// zero is a valid program counter value.
-type VRegTypeTable struct {
-	min VRegID
-	set bitset
-	pcs []programCounter
-}
-
-func (t *VRegTypeTable) Contains(id VRegID) bool {
-	return t.set.has(uint(id - t.min))
-}
-
-// Lookup returns the program counter associated with the given virtual register.
-// If the virtual register is not in the table, -1 is returned.
-func (t *VRegTypeTable) Lookup(id VRegID) programCounter {
-	if id := int(id - t.min); t.set.has(uint(id)) {
-		return t.pcs[id]
-	}
-	return -1
-}
-
-func (t *VRegTypeTable) Insert(id VRegID, p programCounter) {
-	if p < 0 {
-		panic("BUG: cannot insert negative program counter in virtual register table")
-	}
-	i := int(id - t.min)
-	if len(t.pcs) <= i {
-		t.pcs = append(t.pcs, make([]programCounter, (i+1)-len(t.pcs))...)
-	}
-	t.set.set(uint(i))
-	t.pcs[i] = p
-}
-
-func (t *VRegTypeTable) Range(f func(VRegID, programCounter)) {
-	t.set.scan(func(i uint) { f(VRegID(i)+t.min, t.pcs[i]) })
-}
-
-func (t *VRegTypeTable) Reset(minVRegID VRegID) {
-	t.min = minVRegID
-	t.set.reset()
-	t.pcs = t.pcs[:0]
-}
-
-// VRegSet is a data structure designed for fast lookup in a set of virtual
-// registers.
-//
-// The type maintains one instance of VRegTypeSet for each register type:
-// invalid, int, and float. It dispatches between the sub sets based on the
-// type of virtual registers that are inserted.
-//
-// Note that there is no way to delete entries, the sets are constructed and
-// then used until they are either reset or go unused.
-type VRegSet [NumRegType]VRegTypeSet
-
-func (s *VRegSet) Contains(v VReg) bool {
-	return s[v.RegType()].Contains(v.ID())
-}
-
-func (s *VRegSet) Insert(v VReg) {
-	if v.IsRealReg() {
-		panic("BUG: cannot insert real registers in virtual register table")
-	}
-	s[v.RegType()].Insert(v.ID())
-}
-
-func (s *VRegSet) Range(f func(VReg)) {
-	for i := range s {
-		s[i].Range(func(id VRegID) {
-			f(VReg(id).SetRegType(RegType(i)))
-		})
-	}
-}
-
-func (s *VRegSet) Reset(minVRegIDs VRegIDMinSet) {
-	for i := range s {
-		s[i].Reset(minVRegIDs.Min(RegType(i)))
-	}
-}
-
-// VRegTypeSet implements a set for virtual registers of a specific type.
-//
-// The parent VRegSet uses 3 instances of this type to maintain a table for each
-// virtual register type.
-//
-// The virtual register type set uses a bitset to minimize the memory footprint,
-// registers ids are offseted by the minimum entry, and represented by a bit bit
-// set to 1 in the bitset.
-type VRegTypeSet struct {
-	min VRegID
-	set bitset
-}
-
-func (s *VRegTypeSet) Contains(id VRegID) bool {
-	return s.set.has(uint(id - s.min))
-}
-
-func (s *VRegTypeSet) Insert(id VRegID) {
-	s.set.set(uint(id - s.min))
-}
-
-func (s *VRegTypeSet) Range(f func(VRegID)) {
-	s.set.scan(func(i uint) { f(VRegID(i) + s.min) })
-}
-
-func (s *VRegTypeSet) Reset(minVRegID VRegID) {
-	s.min = minVRegID
-	s.set.reset()
-}
-
-type bitset struct {
-	bits []uint64
-	// Most of the bitset values have short backing arrays, to reduce the memory
-	// footprint we use this buffer as backing array for storing up to 320 bits.
-	// When more bits need to be stored, the backing array are offloaded to the
-	// heap.
-	buf [5]uint64
-}
-
-func (b *bitset) reset() {
-	b.bits, b.buf = b.bits[:0], [5]uint64{}
-}
-
-func (b *bitset) scan(f func(uint)) {
-	for i, v := range b.bits {
-		for j := uint(i * 64); v != 0; j++ {
-			n := uint(bits.TrailingZeros64(v))
-			j += n
-			v >>= (n + 1)
-			f(j)
-		}
-	}
-}
-
-func (b *bitset) has(i uint) bool {
-	index, shift := i/64, i%64
-	return index < uint(len(b.bits)) && ((b.bits[index] & (1 << shift)) != 0)
-}
-
-func (b *bitset) set(i uint) {
-	index, shift := i/64, i%64
-	if index >= uint(len(b.bits)) {
-		if index < uint(len(b.buf)) {
-			b.bits = b.buf[:]
-		} else {
-			b.bits = append(b.bits, make([]uint64, (index+1)-uint(len(b.bits)))...)
-			b.buf = [5]uint64{}
-		}
-	}
-	b.bits[index] |= 1 << shift
-}
-
 // RealReg represents a physical register.
 type RealReg byte
 
diff --git a/internal/engine/wazevo/backend/regalloc/regalloc.go b/internal/engine/wazevo/backend/regalloc/regalloc.go
index 6fa6d087..59630b1f 100644
--- a/internal/engine/wazevo/backend/regalloc/regalloc.go
+++ b/internal/engine/wazevo/backend/regalloc/regalloc.go
@@ -11,7 +11,6 @@ package regalloc
 import (
 	"fmt"
 	"math"
-	"sort"
 	"strings"
 
 	"github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi"
@@ -20,13 +19,13 @@ import (
 // NewAllocator returns a new Allocator.
 func NewAllocator(allocatableRegs *RegisterInfo) Allocator {
 	a := Allocator{
-		regInfo:       allocatableRegs,
-		nodePool:      wazevoapi.NewPool[node](resetNode),
-		blockInfoPool: wazevoapi.NewPool[blockInfo](resetBlockInfo),
+		regInfo:               allocatableRegs,
+		blockLivenessDataPool: wazevoapi.NewPool[blockLivenessData](resetBlockLivenessData),
 	}
+	a.state.reset()
 	for _, regs := range allocatableRegs.AllocatableRegisters {
 		for _, r := range regs {
-			a.allocatableSet[r] = true
+			a.allocatableSet = a.allocatableSet.add(r)
 		}
 	}
 	return a
@@ -51,139 +50,307 @@ type (
 		// regInfo is static per ABI/ISA, and is initialized by the machine during Machine.PrepareRegisterAllocator.
 		regInfo *RegisterInfo
 		// allocatableSet is a set of allocatable RealReg derived from regInfo. Static per ABI/ISA.
-		allocatableSet [RealRegsNumMax]bool
-		// allocatedRegSet is a set of RealReg that are allocated during the allocation phase. This is reset per function.
-		allocatedRegSet          [RealRegsNumMax]bool
+		allocatableSet           regSet
 		allocatedCalleeSavedRegs []VReg
-		nodePool                 wazevoapi.Pool[node]
-		blockInfoPool            wazevoapi.Pool[blockInfo]
-		// vRegIDToNode maps VRegID to the node whose node.v has the VRegID.
-		vRegIDToNode [] /* VRegID to */ *node
-		blockInfos   [] /* blockID to */ *blockInfo
-		vs           []VReg
-		spillHandler spillHandler
-		// phis keeps track of the VRegs that are defined by phi functions.
-		phiBlocks []Block
-		phis      []VReg
+		blockLivenessDataPool    wazevoapi.Pool[blockLivenessData]
+		blockLivenessData        [] /* blockID to */ *blockLivenessData
+		vs                       []VReg
+		maxBlockID               int
 
 		// Followings are re-used during various places e.g. coloring.
-		realRegSet [RealRegsNumMax]bool
-		realRegs   []RealReg
-		nodes1     []*node
-		nodes2     []*node
-		nodes3     []*node
-		dedup      []bool
-		blks       []Block
+		blks             []Block
+		insts            []Instr
+		reals            []RealReg
+		currentOccupants regInUseSet
+
+		// Following two fields are updated while iterating the blocks in the reverse postorder.
+		state       state
+		blockStates [] /* blockID to */ blockState
 	}
 
-	// blockInfo is a per-block information used during the register allocation.
-	blockInfo struct {
+	// blockLivenessData is a per-block information used during the register allocation.
+	blockLivenessData struct {
+		seen     bool
 		liveOuts map[VReg]struct{}
 		liveIns  map[VReg]struct{}
-		defs     map[VReg]programCounter
-		lastUses VRegTable
-		// Pre-colored real registers can have multiple live ranges in one block.
-		realRegUses [vRegIDReservedForRealNum][]programCounter
-		realRegDefs [vRegIDReservedForRealNum][]programCounter
-		intervalMng *intervalManager
-	}
-
-	// node represents a VReg.
-	node struct {
-		id     int
-		v      VReg
-		ranges []*interval
-		// r is the real register assigned to this node. It is either a pre-colored register or a register assigned during allocation.
-		r RealReg
-		// neighbors are the nodes that this node interferes with. Such neighbors have the same RegType as this node.
-		neighbors []*node
-		// copyFromReal and copyToReal are the real registers that this node copies from/to. During the allocation phase,
-		// we try to assign the same RealReg to copyFromReal and copyToReal so that we can remove the redundant copy.
-		copyFromReal, copyToReal RealReg
-		// copyFromVReg and copyToVReg are the same as above, but for VReg not backed by real registers.
-		copyFromVReg, copyToVReg *node
-		degree                   int
-		visited                  bool
 	}
 
 	// programCounter represents an opaque index into the program which is used to represents a LiveInterval of a VReg.
 	programCounter int32
+
+	state struct {
+		argRealRegs          []VReg
+		regsInUse            regInUseSet
+		vrStates             []vrState
+		maxVRegIDEncountered int
+
+		// allocatedRegSet is a set of RealReg that are allocated during the allocation phase. This is reset per function.
+		allocatedRegSet regSet
+	}
+
+	blockState struct {
+		visited            bool
+		startFromPredIndex int
+		// startRegs is a list of RealReg that are used at the beginning of the block. This is used to fix the merge edges.
+		startRegs regInUseSet
+		// endRegs is a list of RealReg that are used at the end of the block. This is used to fix the merge edges.
+		endRegs regInUseSet
+		init    bool
+	}
+
+	vrState struct {
+		v VReg
+		r RealReg
+		// defInstr is the instruction that defines this value. If this is the phi value and not the entry block, this is nil.
+		defInstr Instr
+		// defBlk is the block that defines this value. If this is the phi value, this is the block whose arguments contain this value.
+		defBlk Block
+		// spilled is true if this value is spilled i.e. the value is reload from the stack somewhere in the program.
+		spilled bool
+		// lca = lowest common ancestor. This is the block that is the lowest common ancestor of all the blocks that
+		// reloads this value. This is used to determine the spill location. Only valid if spilled=true.
+		lca Block
+		// lastUse is the program counter of the last use of this value. This changes while iterating the block, and
+		// should not be used across the blocks as it becomes invalid.
+		lastUse programCounter
+		// isPhi is true if this is a phi value.
+		isPhi bool
+		// isArg is true if this is phi (isPhi=true) and the value is passed via a real register at the beginning of the blk.
+		regPhi RealReg
+	}
 )
 
+func (s *state) dump(info *RegisterInfo) { //nolint:unused
+	fmt.Println("\t\tstate:")
+	fmt.Println("\t\t\targRealRegs:", s.argRealRegs)
+	fmt.Println("\t\t\tregsInUse", s.regsInUse.format(info))
+	fmt.Println("\t\t\tallocatedRegSet:", s.allocatedRegSet.format(info))
+	fmt.Println("\t\t\tused:", s.regsInUse.format(info))
+	fmt.Println("\t\t\tmaxVRegIDEncountered:", s.maxVRegIDEncountered)
+	var strs []string
+	for i, v := range s.vrStates {
+		if v.r != RealRegInvalid {
+			strs = append(strs, fmt.Sprintf("(v%d: %s)", i, info.RealRegName(v.r)))
+		}
+	}
+	fmt.Println("\t\t\tvrStates:", strings.Join(strs, ", "))
+}
+
+func (s *state) reset() {
+	s.argRealRegs = s.argRealRegs[:0]
+	for i, l := 0, len(s.vrStates); i <= s.maxVRegIDEncountered && i < l; i++ {
+		s.vrStates[i].reset()
+	}
+	s.maxVRegIDEncountered = -1
+	s.allocatedRegSet = regSet(0)
+	s.regsInUse.reset()
+}
+
+func (a *Allocator) getBlockState(bID int) *blockState {
+	if bID >= len(a.blockStates) {
+		a.blockStates = append(a.blockStates, make([]blockState, (bID+1)-len(a.blockStates))...)
+		a.blockStates = a.blockStates[:cap(a.blockStates)]
+	}
+	ret := &a.blockStates[bID]
+	if !ret.init {
+		ret.reset()
+		ret.init = true
+	}
+	return ret
+}
+
+func (s *state) setVRegState(v VReg, r RealReg) {
+	id := int(v.ID())
+	if id >= len(s.vrStates) {
+		s.vrStates = append(s.vrStates, make([]vrState, id+1-len(s.vrStates))...)
+		s.vrStates = s.vrStates[:cap(s.vrStates)]
+	}
+
+	st := &s.vrStates[id]
+	st.r = r
+	st.v = v
+}
+
+func (vs *vrState) reset() {
+	vs.r = RealRegInvalid
+	vs.defInstr = nil
+	vs.defBlk = nil
+	vs.spilled = false
+	vs.lca = nil
+	vs.isPhi = false
+	vs.regPhi = RealRegInvalid
+}
+
+func (s *state) getVRegState(v VReg) *vrState {
+	id := int(v.ID())
+	if id >= len(s.vrStates) {
+		s.setVRegState(v, RealRegInvalid)
+	}
+	if s.maxVRegIDEncountered < id {
+		s.maxVRegIDEncountered = id
+	}
+	return &s.vrStates[id]
+}
+
+func (s *state) useRealReg(r RealReg, v VReg) {
+	if s.regsInUse.has(r) {
+		panic("BUG: useRealReg: the given real register is already used")
+	}
+	s.regsInUse.add(r, v)
+	s.setVRegState(v, r)
+	s.allocatedRegSet = s.allocatedRegSet.add(r)
+}
+
+func (s *state) releaseRealReg(r RealReg) {
+	current := s.regsInUse.get(r)
+	if current.Valid() {
+		s.regsInUse.remove(r)
+		s.setVRegState(current, RealRegInvalid)
+	}
+}
+
+// recordReload records that the given VReg is reloaded in the given block.
+// This is used to determine the spill location by tracking the lowest common ancestor of all the blocks that reloads the value.
+func (vs *vrState) recordReload(f Function, blk Block) {
+	vs.spilled = true
+	if vs.lca == nil {
+		if wazevoapi.RegAllocLoggingEnabled {
+			fmt.Printf("\t\tv%d is reloaded in blk%d,\n", vs.v.ID(), blk.ID())
+		}
+		vs.lca = blk
+	} else {
+		if wazevoapi.RegAllocLoggingEnabled {
+			fmt.Printf("\t\tv%d is reloaded in blk%d, lca=%d\n", vs.v.ID(), blk.ID(), vs.lca.ID())
+		}
+		vs.lca = f.LowestCommonAncestor(vs.lca, blk)
+		if wazevoapi.RegAllocLoggingEnabled {
+			fmt.Printf("updated lca=%d\n", vs.lca.ID())
+		}
+	}
+}
+
+func (s *state) findOrSpillAllocatable(a *Allocator, allocatable []RealReg, forbiddenMask regSet) (r RealReg) {
+	r = RealRegInvalid
+	var lastUseAt programCounter = math.MinInt32
+	var spillVReg VReg
+	for _, candidateReal := range allocatable {
+		if forbiddenMask.has(candidateReal) {
+			continue
+		}
+
+		using := s.regsInUse.get(candidateReal)
+		if using == VRegInvalid {
+			// This is not used at this point.
+			return candidateReal
+		}
+
+		if last := s.getVRegState(using).lastUse; last > lastUseAt {
+			lastUseAt = last
+			r = candidateReal
+			spillVReg = using
+		}
+	}
+
+	if r == RealRegInvalid {
+		panic("not found any allocatable register")
+	}
+
+	if wazevoapi.RegAllocLoggingEnabled {
+		fmt.Printf("\tspilling v%d when: %s\n", spillVReg.ID(), forbiddenMask.format(a.regInfo))
+	}
+	s.releaseRealReg(r)
+	return r
+}
+
+func (s *state) findAllocatable(allocatable []RealReg, forbiddenMask regSet) RealReg {
+	for _, r := range allocatable {
+		if !s.regsInUse.has(r) && !forbiddenMask.has(r) {
+			return r
+		}
+	}
+	return RealRegInvalid
+}
+
+func (s *state) resetAt(bs *blockState, liveIns map[VReg]struct{}) {
+	s.regsInUse.range_(func(_ RealReg, vr VReg) {
+		s.setVRegState(vr, RealRegInvalid)
+	})
+	s.regsInUse.reset()
+	bs.endRegs.range_(func(r RealReg, v VReg) {
+		if _, ok := liveIns[v]; ok {
+			s.regsInUse.add(r, v)
+			s.setVRegState(v, r)
+		}
+	})
+}
+
+func (b *blockState) reset() {
+	b.visited = false
+	b.endRegs.reset()
+	b.startRegs.reset()
+	b.startFromPredIndex = -1
+	b.init = false
+}
+
+func (b *blockState) dump(a *RegisterInfo) {
+	fmt.Println("\t\tblockState:")
+	fmt.Println("\t\t\tstartRegs:", b.startRegs.format(a))
+	fmt.Println("\t\t\tendRegs:", b.endRegs.format(a))
+	fmt.Println("\t\t\tstartFromPredIndex:", b.startFromPredIndex)
+	fmt.Println("\t\t\tvisited:", b.visited)
+	fmt.Println("\t\t\tinit:", b.init)
+}
+
 // DoAllocation performs register allocation on the given Function.
 func (a *Allocator) DoAllocation(f Function) {
 	a.livenessAnalysis(f)
-	a.buildLiveRanges(f)
-	a.buildNeighbors()
-	a.coloring()
+	a.alloc(f)
 	a.determineCalleeSavedRealRegs(f)
-	a.assignRegisters(f)
 	f.Done()
 }
 
 func (a *Allocator) determineCalleeSavedRealRegs(f Function) {
 	a.allocatedCalleeSavedRegs = a.allocatedCalleeSavedRegs[:0]
-	for i, allocated := range a.allocatedRegSet {
-		if allocated {
-			r := RealReg(i)
-			if a.regInfo.isCalleeSaved(r) {
-				a.allocatedCalleeSavedRegs = append(a.allocatedCalleeSavedRegs, a.regInfo.RealRegToVReg[r])
-			}
+	a.state.allocatedRegSet.range_(func(allocatedRealReg RealReg) {
+		if a.regInfo.isCalleeSaved(allocatedRealReg) {
+			a.allocatedCalleeSavedRegs = append(a.allocatedCalleeSavedRegs, a.regInfo.RealRegToVReg[allocatedRealReg])
 		}
-	}
-	// In order to make the output deterministic, sort it now.
-	sort.Slice(a.allocatedCalleeSavedRegs, func(i, j int) bool {
-		return a.allocatedCalleeSavedRegs[i] < a.allocatedCalleeSavedRegs[j]
 	})
 	f.ClobberedRegisters(a.allocatedCalleeSavedRegs)
 }
 
-// We assign different pc to use and def in one instruction. That way we can, for example, use the same register in
-// one instruction. E.g. add r0, r0, r0.
-const (
-	pcUseOffset = 0
-	pcDefOffset = 1
-	pcStride    = pcDefOffset + 1
-)
-
 // phiBlk returns the block that defines the given phi value, nil otherwise.
-func (a *Allocator) phiBlk(id VRegID) Block {
-	if int(id) >= len(a.phiBlocks) {
-		return nil
+func (s *state) phiBlk(v VReg) Block {
+	vs := s.getVRegState(v)
+	if vs.isPhi {
+		return vs.defBlk
 	}
-	return a.phiBlocks[id]
+	return nil
 }
 
-// liveAnalysis constructs Allocator.blockInfos.
+// liveAnalysis constructs Allocator.blockLivenessData.
 // The algorithm here is described in https://pfalcon.github.io/ssabook/latest/book-full.pdf Chapter 9.2.
 func (a *Allocator) livenessAnalysis(f Function) {
-	// First, we need to allocate blockInfos.
-	var maxBlockID int
+	// First, we need to allocate blockLivenessData.
+	s := &a.state
 	for blk := f.PostOrderBlockIteratorBegin(); blk != nil; blk = f.PostOrderBlockIteratorNext() { // Order doesn't matter.
-		info := a.allocateBlockInfo(blk.ID())
-		// If this is not the entry block, we should define phi nodes, which are not defined by instructions.
-		for _, p := range blk.BlockParams() {
-			info.defs[p] = 0 // Earliest definition is at the beginning of the block.
-			a.phis = append(a.phis, p)
-			pid := int(p.ID())
-			if diff := pid + 1 - len(a.phiBlocks); diff > 0 {
-				a.phiBlocks = append(a.phiBlocks, make([]Block, diff+1)...)
-			}
-			a.phiBlocks[pid] = blk
+		a.allocateBlockLivenessData(blk.ID())
+
+		// We should gather phi value data.
+		for _, p := range blk.BlockParams(&a.vs) {
+			vs := s.getVRegState(p)
+			vs.isPhi = true
+			vs.defBlk = blk
 		}
-		if blk.ID() > maxBlockID {
-			maxBlockID = blk.ID()
+		if blk.ID() > a.maxBlockID {
+			a.maxBlockID = blk.ID()
 		}
 	}
 
-	if maxBlockID >= len(a.dedup) {
-		a.dedup = append(a.dedup, make([]bool, maxBlockID+1)...)
-	}
-
 	// Run the Algorithm 9.2 in the bool.
 	for blk := f.PostOrderBlockIteratorBegin(); blk != nil; blk = f.PostOrderBlockIteratorNext() {
 		blkID := blk.ID()
-		info := a.allocateBlockInfo(blkID)
+		info := a.livenessDataAt(blkID)
 
 		ns := blk.Succs()
 		for i := 0; i < ns; i++ {
@@ -193,69 +360,38 @@ func (a *Allocator) livenessAnalysis(f Function) {
 			}
 
 			succID := succ.ID()
-			if !a.dedup[succID] { // This means the back edge.
+			succInfo := a.livenessDataAt(succID)
+			if !succInfo.seen { // This means the back edge.
 				continue
 			}
 
-			succInfo := a.blockInfoAt(succID)
 			for v := range succInfo.liveIns {
-				if a.phiBlk(v.ID()) != succ {
+				if s.phiBlk(v) != succ {
 					info.liveOuts[v] = struct{}{}
 					info.liveIns[v] = struct{}{}
 				}
 			}
 		}
 
-		var pc programCounter
-		var minVRegID VRegIDMinSet
-		for instr := blk.InstrIteratorBegin(); instr != nil; instr = blk.InstrIteratorNext() {
-			uses := instr.Uses()
-			for _, use := range uses {
-				if !use.IsRealReg() {
-					minVRegID.Observe(use)
-				}
-			}
-			pc += pcStride
-		}
-		info.lastUses.Reset(minVRegID)
-
 		for instr := blk.InstrRevIteratorBegin(); instr != nil; instr = blk.InstrRevIteratorNext() {
-			pc -= pcStride
+
 			var use, def VReg
-			for _, def = range instr.Defs() {
-				defID := def.ID()
-				pos := pc + pcDefOffset
-				if def.IsRealReg() {
-					info.realRegDefs[defID] = append(info.realRegDefs[defID], pos)
-				} else {
-					info.defs[def] = pos
+			for _, def = range instr.Defs(&a.vs) {
+				if !def.IsRealReg() {
 					delete(info.liveIns, def)
 				}
 			}
-			for _, use = range instr.Uses() {
-				pos := pc + pcUseOffset
-				if use.IsRealReg() {
-					id := use.ID()
-					info.realRegUses[id] = append(info.realRegUses[id], pos)
-				} else {
-					if info.lastUses.Lookup(use) < 0 {
-						info.lastUses.Insert(use, pos)
-					}
+			for _, use = range instr.Uses(&a.vs) {
+				if !use.IsRealReg() {
 					info.liveIns[use] = struct{}{}
 				}
 			}
 
-			if instr.IsCopy() {
-				a.recordCopyRelation(def, use)
-			}
-
 			// If the destination is a phi value, and ...
-			if def.Valid() && a.phiBlk(def.ID()) != nil {
+			if def.Valid() && s.phiBlk(def) != nil {
 				if use.Valid() && use.IsRealReg() {
-					// If the source is a real register, this is the beginning of the function, and
-					// therefore we need to add the definition of the real register.
-					r := use.ID()
-					info.realRegDefs[r] = append(info.realRegDefs[r], 0)
+					// If the source is a real register, this is the beginning of the function.
+					a.state.argRealRegs = append(a.state.argRealRegs, use)
 				} else {
 					// Otherwise, this is the definition of the phi value for the successor block.
 					// So we need to make it outlive the block.
@@ -263,7 +399,7 @@ func (a *Allocator) livenessAnalysis(f Function) {
 				}
 			}
 		}
-		a.dedup[blkID] = true
+		info.seen = true
 	}
 
 	nrs := f.LoopNestingForestRoots()
@@ -271,11 +407,6 @@ func (a *Allocator) livenessAnalysis(f Function) {
 		root := f.LoopNestingForestRoot(i)
 		a.loopTreeDFS(root)
 	}
-
-	// Clears the dedup array for the next function.
-	for i := 0; i <= maxBlockID; i++ {
-		a.dedup[i] = false
-	}
 }
 
 // loopTreeDFS implements the Algorithm 9.3 in the book in an iterative way.
@@ -283,15 +414,16 @@ func (a *Allocator) loopTreeDFS(entry Block) {
 	a.blks = a.blks[:0]
 	a.blks = append(a.blks, entry)
 
+	s := &a.state
 	for len(a.blks) > 0 {
 		tail := len(a.blks) - 1
 		loop := a.blks[tail]
 		a.blks = a.blks[:tail]
 		a.vs = a.vs[:0]
 
-		info := a.blockInfoAt(loop.ID())
+		info := a.livenessDataAt(loop.ID())
 		for v := range info.liveIns {
-			if a.phiBlk(v.ID()) != loop {
+			if s.phiBlk(v) != loop {
 				a.vs = append(a.vs, v)
 				info.liveOuts[v] = struct{}{}
 			}
@@ -301,7 +433,7 @@ func (a *Allocator) loopTreeDFS(entry Block) {
 		for i := 0; i < cn; i++ {
 			child := loop.LoopNestingForestChild(i)
 			childID := child.ID()
-			childInfo := a.blockInfoAt(childID)
+			childInfo := a.livenessDataAt(childID)
 			for _, v := range a.vs {
 				childInfo.liveIns[v] = struct{}{}
 				childInfo.liveOuts[v] = struct{}{}
@@ -313,211 +445,541 @@ func (a *Allocator) loopTreeDFS(entry Block) {
 	}
 }
 
-func (a *Allocator) buildLiveRanges(f Function) {
-	for blk := f.PostOrderBlockIteratorBegin(); blk != nil; blk = f.PostOrderBlockIteratorNext() { // Order doesn't matter.
-		blkID := blk.ID()
-		info := a.blockInfoAt(blkID)
-		a.buildLiveRangesForNonReals(info)
-		a.buildLiveRangesForReals(info)
-		info.intervalMng.build()
+// alloc allocates registers for the given function by iterating the blocks in the reverse postorder.
+// The algorithm here is derived from the Go compiler's allocator https://github.com/golang/go/blob/release-branch.go1.21/src/cmd/compile/internal/ssa/regalloc.go
+// In short, this is a simply linear scan register allocation where each block inherits the register allocation state from
+// one of its predecessors. Each block inherits the selected state and starts allocation from there.
+// If there's a discrepancy in the end states between predecessors, the adjustments are made to ensure consistency after allocation is done (which we call "fixing merge state").
+// The spill instructions (store into the dedicated slots) are inserted after all the allocations and fixing merge states. That is because
+// at the point, we all know where the reloads happen, and therefore we can know the best place to spill the values. More precisely,
+// the spill happens in the block that is the lowest common ancestor of all the blocks that reloads the value.
+//
+// All of these logics are almost the same as Go's compiler which has a dedicated description in the source file ^^.
+func (a *Allocator) alloc(f Function) {
+	// First we allocate each block in the reverse postorder (at least one predecessor should be allocated for each block).
+	for blk := f.ReversePostOrderBlockIteratorBegin(); blk != nil; blk = f.ReversePostOrderBlockIteratorNext() {
+		if wazevoapi.RegAllocLoggingEnabled {
+			fmt.Printf("========== allocating blk%d ========\n", blk.ID())
+		}
+		a.allocBlock(f, blk)
 	}
+	// After the allocation, we all know the start and end state of each block. So we can fix the merge states.
+	for blk := f.ReversePostOrderBlockIteratorBegin(); blk != nil; blk = f.ReversePostOrderBlockIteratorNext() {
+		a.fixMergeState(f, blk)
+	}
+	// Finally, we insert the spill instructions as we know all the places where the reloads happen.
+	a.scheduleSpills(f)
 }
 
-func (a *Allocator) buildLiveRangesForNonReals(info *blockInfo) {
-	ins, outs, defs := info.liveIns, info.liveOuts, info.defs
+func (a *Allocator) allocBlock(f Function, blk Block) {
+	bID := blk.ID()
+	liveness := a.livenessDataAt(bID)
+	s := &a.state
+	currentBlkState := a.getBlockState(bID)
 
-	// In order to do the deterministic allocation, we need to sort ins.
-	vs := a.vs[:0]
-	for v := range ins {
-		vs = append(vs, v)
+	preds := blk.Preds()
+	var predState *blockState
+	switch preds {
+	case 0: // This is the entry block.
+	case 1:
+		predID := blk.Pred(0).ID()
+		predState = a.getBlockState(predID)
+		currentBlkState.startFromPredIndex = 0
+	default:
+		// TODO: there should be some better heuristic to choose the predecessor.
+		for i := 0; i < preds; i++ {
+			predID := blk.Pred(i).ID()
+			if _predState := a.getBlockState(predID); _predState.visited {
+				predState = _predState
+				currentBlkState.startFromPredIndex = i
+				break
+			}
+		}
 	}
-	sort.SliceStable(vs, func(i, j int) bool {
-		return vs[i].ID() < vs[j].ID()
-	})
-	for _, v := range vs {
-		if v.IsRealReg() {
-			panic("BUG: real registers should not be in liveIns")
+	if predState == nil {
+		if !blk.Entry() {
+			panic(fmt.Sprintf("BUG: at lease one predecessor should be visited for blk%d", blk.ID()))
 		}
-		var begin, end programCounter
-		if _, ok := outs[v]; ok {
-			// v is live-in and live-out, so it is live-through.
-			begin, end = 0, math.MaxInt32
-		} else {
-			// v is killed at killPos.
-			begin, end = 0, info.lastUses.Lookup(v)
+		for _, u := range s.argRealRegs {
+			s.useRealReg(u.RealReg(), u)
+		}
+	} else if predState != nil {
+		if wazevoapi.RegAllocLoggingEnabled {
+			fmt.Printf("allocating blk%d starting from blk%d (on index=%d) \n",
+				bID, blk.Pred(currentBlkState.startFromPredIndex).ID(), currentBlkState.startFromPredIndex)
 		}
-		n := a.getOrAllocateNode(v)
-		intervalNode := info.intervalMng.insert(n, begin, end)
-		n.ranges = append(n.ranges, intervalNode)
+		s.resetAt(predState, liveness.liveIns)
 	}
 
-	// In order to do the deterministic allocation, we need to sort defs.
-	vs = vs[:0]
-	for v := range defs {
-		vs = append(vs, v)
-	}
-	sort.SliceStable(vs, func(i, j int) bool {
-		return vs[i].ID() < vs[j].ID()
+	s.regsInUse.range_(func(allocated RealReg, v VReg) {
+		currentBlkState.startRegs.add(allocated, v)
 	})
-	for _, v := range vs {
-		defPos := defs[v]
-		if v.IsRealReg() {
-			panic("BUG: real registers should not be in defs")
+
+	// Update the last use of each VReg.
+	var pc programCounter
+	for instr := blk.InstrIteratorBegin(); instr != nil; instr = blk.InstrIteratorNext() {
+		for _, use := range instr.Uses(&a.vs) {
+			if !use.IsRealReg() {
+				s.getVRegState(use).lastUse = pc
+			}
 		}
+		pc++
+	}
+	// Reset the last use of the liveOuts.
+	for outlive := range liveness.liveOuts {
+		s.getVRegState(outlive).lastUse = math.MaxInt32
+	}
 
-		if _, ok := ins[v]; ok {
-			// This case, phi value is coming in (used somewhere) but re-defined at the end.
-			continue
+	pc = 0
+	a.insts = a.insts[:0]
+	for instr := blk.InstrIteratorBegin(); instr != nil; instr = blk.InstrIteratorNext() {
+		if wazevoapi.RegAllocLoggingEnabled {
+			fmt.Println(instr)
 		}
 
-		var end programCounter
-		if _, ok := outs[v]; ok {
-			// v is defined here and live-out, so it is live-through.
-			end = math.MaxInt32
-		} else {
-			end = info.lastUses.Lookup(v)
-			if end == -1 {
-				// This case the defined value is not used at all.
-				end = defPos
+		var currentUsedSet regSet
+		killSet := a.reals[:0]
+
+		// Gather the set of registers that will be used in the current instruction.
+		for _, use := range instr.Uses(&a.vs) {
+			if use.IsRealReg() {
+				r := use.RealReg()
+				currentUsedSet = currentUsedSet.add(r)
+				if a.allocatableSet.has(r) {
+					killSet = append(killSet, r)
+				}
+			} else {
+				vs := s.getVRegState(use)
+				if r := vs.r; r != RealRegInvalid {
+					currentUsedSet = currentUsedSet.add(r)
+				}
+			}
+		}
+
+		for i, use := range instr.Uses(&a.vs) {
+			if !use.IsRealReg() {
+				vs := s.getVRegState(use)
+				killed := liveness.isKilledAt(vs, pc)
+				r := vs.r
+
+				if r == RealRegInvalid {
+					r = s.findOrSpillAllocatable(a, a.regInfo.AllocatableRegisters[use.RegType()], currentUsedSet)
+					vs.recordReload(f, blk)
+					f.ReloadRegisterBefore(use.SetRealReg(r), instr)
+					s.useRealReg(r, use)
+				}
+				if wazevoapi.RegAllocLoggingEnabled {
+					fmt.Printf("\ttrying to use v%v on %s\n", use.ID(), a.regInfo.RealRegName(r))
+				}
+				instr.AssignUse(i, use.SetRealReg(r))
+				currentUsedSet = currentUsedSet.add(r)
+				if killed {
+					if wazevoapi.RegAllocLoggingEnabled {
+						fmt.Printf("\tkill v%d with %s\n", use.ID(), a.regInfo.RealRegName(r))
+					}
+					killSet = append(killSet, r)
+				}
+			}
+		}
+
+		isIndirect := instr.IsIndirectCall()
+		call := instr.IsCall() || isIndirect
+		if call {
+			addr := RealRegInvalid
+			if instr.IsIndirectCall() {
+				addr = a.vs[0].RealReg()
+			}
+			a.releaseCallerSavedRegs(addr)
+		}
+
+		for _, r := range killSet {
+			s.releaseRealReg(r)
+		}
+		a.reals = killSet
+
+		defs := instr.Defs(&a.vs)
+		switch {
+		case len(defs) > 1:
+			if !call {
+				panic("only call can have multiple defs")
+			}
+			// Call's defining register are all caller-saved registers.
+			// Therefore, we can assume that all of them are allocatable.
+			for _, def := range defs {
+				s.useRealReg(def.RealReg(), def)
+			}
+		case len(defs) == 1:
+			def := defs[0]
+			if def.IsRealReg() {
+				r := def.RealReg()
+				if a.allocatableSet.has(r) {
+					if s.regsInUse.has(r) {
+						s.releaseRealReg(r)
+					}
+					s.useRealReg(r, def)
+				}
+			} else {
+				vState := s.getVRegState(def)
+				r := vState.r
+				// Allocate a new real register if `def` is not currently assigned one.
+				// It can happen when multiple instructions define the same VReg (e.g. const loads).
+				if r == RealRegInvalid {
+					if instr.IsCopy() {
+						copySrc := instr.Uses(&a.vs)[0].RealReg()
+						if a.allocatableSet.has(copySrc) && !s.regsInUse.has(copySrc) {
+							r = copySrc
+						}
+					}
+					if r == RealRegInvalid {
+						typ := def.RegType()
+						r = s.findOrSpillAllocatable(a, a.regInfo.AllocatableRegisters[typ], regSet(0))
+					}
+					s.useRealReg(r, def)
+				}
+				instr.AssignDef(def.SetRealReg(r))
+				if wazevoapi.RegAllocLoggingEnabled {
+					fmt.Printf("\tdefining v%d with %s\n", def.ID(), a.regInfo.RealRegName(r))
+				}
+				if vState.isPhi {
+					if blk.Entry() {
+						// If this is the entry block, the phi value has a unique definition.
+						vState.defInstr = instr
+					}
+					a.insts = append(a.insts, instr)
+				} else {
+					vState.defInstr = instr
+					vState.defBlk = blk
+				}
+			}
+		}
+		if wazevoapi.RegAllocLoggingEnabled {
+			fmt.Println(instr)
+		}
+		pc++
+	}
+
+	if !blk.Entry() {
+		for _, phiDefInstr := range a.insts {
+			phiDefInstr.Defs(&a.vs)
+			phi := a.vs[0]
+			if s.getVRegState(phi).r == RealRegInvalid {
+				f.StoreRegisterAfter(phi, phiDefInstr)
 			}
 		}
-		n := a.getOrAllocateNode(v)
-		intervalNode := info.intervalMng.insert(n, defPos, end)
-		n.ranges = append(n.ranges, intervalNode)
 	}
 
-	// Reuse for the next block.
-	a.vs = vs[:0]
+	s.regsInUse.range_(func(allocated RealReg, v VReg) {
+		currentBlkState.endRegs.add(allocated, v)
+	})
+
+	currentBlkState.visited = true
+	if wazevoapi.RegAllocLoggingEnabled {
+		currentBlkState.dump(a.regInfo)
+	}
 }
 
-// buildLiveRangesForReals builds live ranges for pre-colored real registers.
-func (a *Allocator) buildLiveRangesForReals(info *blockInfo) {
-	ds, us := info.realRegDefs, info.realRegUses
+func (a *Allocator) releaseCallerSavedRegs(addrReg RealReg) {
+	s := &a.state
 
-	for i := 0; i < RealRegsNumMax; i++ {
-		r := RealReg(i)
-		// Non allocation target registers are not needed here.
-		if !a.allocatableSet[r] {
+	for i := 0; i < 64; i++ {
+		allocated := RealReg(i)
+		if allocated == addrReg { // If this is the call indirect, we should not touch the addr register.
 			continue
 		}
+		if v := s.regsInUse.get(allocated); v.Valid() {
+			if v.IsRealReg() {
+				continue // This is the argument register as it's already used by VReg backed by the corresponding RealReg.
+			}
+			if !a.regInfo.isCallerSaved(allocated) {
+				// If this is not a caller-saved register, it is safe to keep it across the call.
+				continue
+			}
+			s.releaseRealReg(allocated)
+		}
+	}
+}
+
+func (a *Allocator) fixMergeState(f Function, blk Block) {
+	preds := blk.Preds()
+	if preds <= 1 {
+		return
+	}
+
+	s := &a.state
+
+	// Restores the state at the beginning of the block.
+	bID := blk.ID()
+	blkSt := a.getBlockState(bID)
+	desiredOccupants := &blkSt.startRegs
+	aliveOnRegVRegs := make(map[VReg]RealReg)
+	for i := 0; i < 64; i++ {
+		r := RealReg(i)
+		if v := blkSt.startRegs.get(r); v.Valid() {
+			aliveOnRegVRegs[v] = r
+		}
+	}
 
-		uses := us[r]
-		defs := ds[r]
-		if len(defs) != len(uses) {
-			// This is likely a bug of the Instr interface implementation and/or ABI around call instructions.
-			// E.g. call or ret instructions should specify that they use all the real registers (calling convention).
-			panic(
-				fmt.Sprintf(
-					"BUG: real register (%s) is defined and used, but the number of defs and uses are different: %d (defs) != %d (uses)",
-					a.regInfo.RealRegName(r), len(defs), len(uses),
-				),
-			)
-		} else if len(uses) == 0 {
+	if wazevoapi.RegAllocLoggingEnabled {
+		fmt.Println("fixMergeState", blk.ID(), ":", desiredOccupants.format(a.regInfo))
+	}
+
+	// Record that register-allocated phis and not.
+	for _, phi := range blk.BlockParams(&a.vs) {
+		vs := s.getVRegState(phi)
+		if r, ok := aliveOnRegVRegs[phi]; ok {
+			vs.regPhi = r
+		}
+	}
+
+	currentOccupants := &a.currentOccupants
+	for i := 0; i < preds; i++ {
+		currentOccupants.reset()
+		if i == blkSt.startFromPredIndex {
 			continue
 		}
 
-		sort.Slice(uses, func(i, j int) bool {
-			return uses[i] < uses[j]
-		})
-		sort.Slice(defs, func(i, j int) bool {
-			return defs[i] < defs[j]
-		})
+		currentOccupantsRev := make(map[VReg]RealReg)
+		pred := blk.Pred(i)
+		predSt := a.getBlockState(pred.ID())
+		for ii := 0; ii < 64; ii++ {
+			r := RealReg(ii)
+			if v := predSt.endRegs.get(r); v.Valid() {
+				if _, ok := aliveOnRegVRegs[v]; !ok {
+					continue
+				}
+				currentOccupants.add(r, v)
+				currentOccupantsRev[v] = r
+			}
+		}
+
+		s.resetAt(predSt, a.livenessDataAt(bID).liveIns)
 
-		for i := range uses {
-			n := a.allocateNode()
-			n.r = r
-			n.v = FromRealReg(r, a.regInfo.RealRegType(r))
-			defined, used := defs[i], uses[i]
-			intervalNode := info.intervalMng.insert(n, defined, used)
-			n.ranges = append(n.ranges, intervalNode)
+		// Finds the free registers if any.
+		intTmp, floatTmp := VRegInvalid, VRegInvalid
+		if intFree := s.findAllocatable(
+			a.regInfo.AllocatableRegisters[RegTypeInt], desiredOccupants.set,
+		); intFree != RealRegInvalid {
+			intTmp = FromRealReg(intFree, RegTypeInt)
+		}
+		if floatFree := s.findAllocatable(
+			a.regInfo.AllocatableRegisters[RegTypeFloat], desiredOccupants.set,
+		); floatFree != RealRegInvalid {
+			floatTmp = FromRealReg(floatFree, RegTypeFloat)
+		}
+
+		if wazevoapi.RegAllocLoggingEnabled {
+			fmt.Println("\t", pred.ID(), ":", currentOccupants.format(a.regInfo))
+		}
+
+		for ii := 0; ii < 64; ii++ {
+			r := RealReg(ii)
+			desiredVReg := desiredOccupants.get(r)
+			if !desiredVReg.Valid() {
+				continue
+			}
+
+			currentVReg := currentOccupants.get(r)
+			if desiredVReg.ID() == currentVReg.ID() {
+				continue
+			}
+
+			typ := desiredVReg.RegType()
+			var tmpRealReg VReg
+			if typ == RegTypeInt {
+				tmpRealReg = intTmp
+			} else {
+				tmpRealReg = floatTmp
+			}
+			a.reconcileEdge(f, r, pred, currentOccupants, currentOccupantsRev, currentVReg, desiredVReg, tmpRealReg, typ)
 		}
 	}
 }
 
-// Reset resets the allocator's internal state so that it can be reused.
-func (a *Allocator) Reset() {
-	a.nodePool.Reset()
-	a.blockInfos = a.blockInfos[:0]
-	a.blockInfoPool.Reset()
-	for i := range a.vRegIDToNode {
-		a.vRegIDToNode[i] = nil
+func (a *Allocator) reconcileEdge(f Function,
+	r RealReg,
+	pred Block,
+	currentOccupants *regInUseSet,
+	currentOccupantsRev map[VReg]RealReg,
+	currentVReg, desiredVReg VReg,
+	freeReg VReg,
+	typ RegType,
+) {
+	s := &a.state
+	if currentVReg.Valid() {
+		// Both are on reg.
+		er, ok := currentOccupantsRev[desiredVReg]
+		if !ok {
+			if wazevoapi.RegAllocLoggingEnabled {
+				fmt.Printf("\t\tv%d is desired to be on %s, but currently on the stack\n",
+					desiredVReg.ID(), a.regInfo.RealRegName(r),
+				)
+			}
+			// This case is that the desired value is on the stack, but currentVReg is on the target register.
+			// We need to move the current value to the stack, and reload the desired value.
+			// TODO: we can do better here.
+			f.StoreRegisterBefore(currentVReg.SetRealReg(r), pred.LastInstr())
+			delete(currentOccupantsRev, currentVReg)
+
+			s.getVRegState(desiredVReg).recordReload(f, pred)
+			f.ReloadRegisterBefore(desiredVReg.SetRealReg(r), pred.LastInstr())
+			currentOccupants.add(r, desiredVReg)
+			currentOccupantsRev[desiredVReg] = r
+			return
+		}
+
+		if wazevoapi.RegAllocLoggingEnabled {
+			fmt.Printf("\t\tv%d is desired to be on %s, but currently on %s\n",
+				desiredVReg.ID(), a.regInfo.RealRegName(r), a.regInfo.RealRegName(er),
+			)
+		}
+		f.SwapAtEndOfBlock(
+			FromRealReg(r, typ),
+			FromRealReg(er, typ),
+			freeReg,
+			pred,
+		)
+		currentOccupantsRev[desiredVReg] = r
+		currentOccupantsRev[currentVReg] = er
+		currentOccupants.add(r, desiredVReg)
+		currentOccupants.add(er, currentVReg)
+		if wazevoapi.RegAllocLoggingEnabled {
+			fmt.Printf("\t\tv%d previously on %s moved to %s\n", currentVReg.ID(), a.regInfo.RealRegName(r), a.regInfo.RealRegName(er))
+		}
+	} else {
+		// Desired is on reg, but currently the target register is not used.
+		if wazevoapi.RegAllocLoggingEnabled {
+			fmt.Printf("\t\tv%d is desired to be on %s, current not used\n",
+				desiredVReg.ID(), a.regInfo.RealRegName(r),
+			)
+		}
+		if currentReg, ok := currentOccupantsRev[desiredVReg]; ok {
+			f.InsertMoveBefore(
+				FromRealReg(r, typ),
+				desiredVReg.SetRealReg(currentReg),
+				pred.LastInstr(),
+			)
+			currentOccupants.remove(currentReg)
+		} else {
+			s.getVRegState(desiredVReg).recordReload(f, pred)
+			f.ReloadRegisterBefore(desiredVReg.SetRealReg(r), pred.LastInstr())
+		}
+		currentOccupantsRev[desiredVReg] = r
+		currentOccupants.add(r, desiredVReg)
 	}
-	for i := range a.allocatedRegSet {
-		a.allocatedRegSet[i] = false
+
+	if wazevoapi.RegAllocLoggingEnabled {
+		fmt.Println("\t", pred.ID(), ":", currentOccupants.format(a.regInfo))
 	}
+}
 
-	a.nodes1 = a.nodes1[:0]
-	a.nodes2 = a.nodes2[:0]
-	a.realRegs = a.realRegs[:0]
-	for _, phi := range a.phis {
-		a.phiBlocks[phi.ID()] = nil
+func (a *Allocator) scheduleSpills(f Function) {
+	vrStates := a.state.vrStates
+	for i := 0; i <= a.state.maxVRegIDEncountered; i++ {
+		vs := &vrStates[i]
+		if vs.spilled {
+			a.scheduleSpill(f, vs)
+		}
 	}
-	a.phis = a.phis[:0]
-	a.vs = a.vs[:0]
 }
 
-func (a *Allocator) allocateBlockInfo(blockID int) *blockInfo {
-	if blockID >= len(a.blockInfos) {
-		a.blockInfos = append(a.blockInfos, make([]*blockInfo, (blockID+1)-len(a.blockInfos))...)
+func (a *Allocator) scheduleSpill(f Function, vs *vrState) {
+	v := vs.v
+	// If the value is the phi value, we need to insert a spill before the first instruction of the defining block whose
+	// arguments contain the value.
+	if phiDefiningBlk := vs.defBlk; vs.isPhi &&
+		// Except for the entry block since the phi value is actually defined via the instruction.
+		!phiDefiningBlk.Entry() {
+		if r := vs.regPhi; r == RealRegInvalid {
+			// This case, the phi is already passed via stack performed in the code inserted in fixMergeState function.
+			if wazevoapi.RegAllocLoggingEnabled {
+				fmt.Printf("v%d is already passed via stack at blk%v\n", v.ID(), phiDefiningBlk.ID())
+				fmt.Println(vs.defInstr)
+				a.blockStates[phiDefiningBlk.ID()].dump(a.regInfo)
+			}
+		} else {
+			// Otherwise, we need to insert a spill before the first instruction of the block.
+			f.StoreRegisterAfter(v.SetRealReg(r), phiDefiningBlk.FirstInstr())
+		}
+		return
 	}
-	info := a.blockInfos[blockID]
-	if info == nil {
-		info = a.blockInfoPool.Allocate()
-		a.blockInfos[blockID] = info
+
+	pos := vs.lca
+	definingBlk := vs.defBlk
+	r := RealRegInvalid
+	if wazevoapi.RegAllocLoggingEnabled {
+		fmt.Printf("v%d is spilled in blk%d, lca=blk%d\n", v.ID(), definingBlk.ID(), pos.ID())
 	}
-	return info
-}
+	for pos != definingBlk {
+		st := a.blockStates[pos.ID()]
+		for ii := 0; ii < 64; ii++ {
+			rr := RealReg(ii)
+			if st.startRegs.get(rr) == v {
+				r = rr
+				// Already in the register, so we can place the spill at the beginning of the block.
+				break
+			}
+		}
 
-func (a *Allocator) blockInfoAt(blockID int) (info *blockInfo) {
-	info = a.blockInfos[blockID]
-	return
-}
+		if r != RealRegInvalid {
+			break
+		}
 
-// getOrAllocateNode returns a node for the given virtual register.
-// This assumes that VReg is not a real register-backed one, otherwise
-// the lookup table vRegIDToNode will be overflowed.
-func (a *Allocator) getOrAllocateNode(v VReg) (n *node) {
-	if vid := int(v.ID()); vid < len(a.vRegIDToNode) {
-		if n = a.vRegIDToNode[v.ID()]; n != nil {
-			return
+		pos = f.Idom(pos)
+	}
+
+	if pos == definingBlk {
+		defInstr := vs.defInstr
+		defInstr.Defs(&a.vs)
+		if wazevoapi.RegAllocLoggingEnabled {
+			fmt.Printf("schedule spill v%d after %v\n", v.ID(), defInstr)
 		}
+		f.StoreRegisterAfter(a.vs[0], defInstr)
 	} else {
-		a.vRegIDToNode = append(a.vRegIDToNode, make([]*node, vid+1)...)
+		// Found an ancestor block that holds the value in the register at the beginning of the block.
+		// We need to insert a spill before the last use.
+		first := pos.FirstInstr()
+		if wazevoapi.RegAllocLoggingEnabled {
+			fmt.Printf("schedule spill v%d before %v\n", v.ID(), first)
+		}
+		f.StoreRegisterAfter(v.SetRealReg(r), first)
 	}
-	n = a.allocateNode()
-	n.r = RealRegInvalid
-	n.v = v
-	a.vRegIDToNode[v.ID()] = n
-	return
 }
 
-func resetBlockInfo(i *blockInfo) {
-	if i.intervalMng == nil {
-		i.intervalMng = newIntervalManager()
-	} else {
-		i.intervalMng.reset()
+// Reset resets the allocator's internal state so that it can be reused.
+func (a *Allocator) Reset() {
+	a.state.reset()
+	for i, l := 0, len(a.blockStates); i <= a.maxBlockID && i < l; i++ {
+		a.blockLivenessData[i] = nil
+		s := &a.blockStates[i]
+		s.reset()
 	}
-	i.liveOuts = resetMap(i.liveOuts)
-	i.liveIns = resetMap(i.liveIns)
-	i.defs = resetMap(i.defs)
+	a.blockLivenessDataPool.Reset()
+
+	a.vs = a.vs[:0]
+	a.maxBlockID = -1
+}
 
-	for index := range i.realRegUses {
-		i.realRegUses[index] = i.realRegUses[index][:0]
-		i.realRegDefs[index] = i.realRegDefs[index][:0]
+func (a *Allocator) allocateBlockLivenessData(blockID int) *blockLivenessData {
+	if blockID >= len(a.blockLivenessData) {
+		a.blockLivenessData = append(a.blockLivenessData, make([]*blockLivenessData, (blockID+1)-len(a.blockLivenessData))...)
 	}
+	info := a.blockLivenessData[blockID]
+	if info == nil {
+		info = a.blockLivenessDataPool.Allocate()
+		a.blockLivenessData[blockID] = info
+	}
+	return info
 }
 
-func resetNode(n *node) {
-	n.r = RealRegInvalid
-	n.v = VRegInvalid
-	n.ranges = n.ranges[:0]
-	n.neighbors = n.neighbors[:0]
-	n.copyFromVReg = nil
-	n.copyToVReg = nil
-	n.copyFromReal = RealRegInvalid
-	n.copyToReal = RealRegInvalid
-	n.degree = 0
-	n.visited = false
+func (a *Allocator) livenessDataAt(blockID int) (info *blockLivenessData) {
+	info = a.blockLivenessData[blockID]
+	return
+}
+
+func resetBlockLivenessData(i *blockLivenessData) {
+	i.seen = false
+	i.liveOuts = resetMap(i.liveOuts)
+	i.liveIns = resetMap(i.liveIns)
 }
 
 func resetMap[K comparable, V any](m map[K]V) map[K]V {
@@ -531,64 +993,38 @@ func resetMap[K comparable, V any](m map[K]V) map[K]V {
 	return m
 }
 
-func (a *Allocator) allocateNode() (n *node) {
-	n = a.nodePool.Allocate()
-	n.id = a.nodePool.Allocated() - 1
-	return
-}
-
 // Format is for debugging.
-func (i *blockInfo) Format(ri *RegisterInfo) string {
+func (i *blockLivenessData) Format(ri *RegisterInfo) string {
 	var buf strings.Builder
-	buf.WriteString("\tliveOuts: ")
+	buf.WriteString("\t\tblockLivenessData:")
+	buf.WriteString("\n\t\t\tliveOuts: ")
 	for v := range i.liveOuts {
-		buf.WriteString(fmt.Sprintf("%v ", v))
-	}
-	buf.WriteString("\n\tliveIns: ")
-	for v := range i.liveIns {
-		buf.WriteString(fmt.Sprintf("%v ", v))
-	}
-	buf.WriteString("\n\tdefs: ")
-	for v, pos := range i.defs {
-		buf.WriteString(fmt.Sprintf("%v@%v ", v, pos))
-	}
-	buf.WriteString("\n\tlastUses: ")
-	i.lastUses.Range(func(v VReg, pos programCounter) {
-		buf.WriteString(fmt.Sprintf("%v@%v ", v, pos))
-	})
-	buf.WriteString("\n\trealRegUses: ")
-	for v, pos := range i.realRegUses {
-		if len(pos) > 0 {
-			buf.WriteString(fmt.Sprintf("%s@%v ", ri.RealRegName(RealReg(v)), pos))
+		if v.IsRealReg() {
+			buf.WriteString(fmt.Sprintf("%s ", ri.RealRegName(v.RealReg())))
+		} else {
+			buf.WriteString(fmt.Sprintf("%v ", v))
 		}
 	}
-	buf.WriteString("\n\trealRegDefs: ")
-	for v, pos := range i.realRegDefs {
-		if len(pos) > 0 {
-			buf.WriteString(fmt.Sprintf("%s@%v ", ri.RealRegName(RealReg(v)), pos))
+	buf.WriteString("\n\t\t\tliveIns: ")
+	for v := range i.liveIns {
+		if v.IsRealReg() {
+			buf.WriteString(fmt.Sprintf("%s ", ri.RealRegName(v.RealReg())))
+		} else {
+			buf.WriteString(fmt.Sprintf("%v ", v))
 		}
 	}
+	buf.WriteString(fmt.Sprintf("\n\t\t\tseen: %v", i.seen))
 	return buf.String()
 }
 
-// String implements fmt.Stringer for debugging.
-func (n *node) String() string {
-	var buf strings.Builder
-	buf.WriteString(fmt.Sprintf("v%v", n.v.ID()))
-	if n.r != RealRegInvalid {
-		buf.WriteString(fmt.Sprintf(":%v", n.r))
-	}
-	// Add neighbors
-	buf.WriteString(" neighbors[")
-	for _, n := range n.neighbors {
-		buf.WriteString(fmt.Sprintf("v%v ", n.v.ID()))
+func (i *blockLivenessData) isKilledAt(vs *vrState, pos programCounter) bool {
+	v := vs.v
+	if vs.lastUse == pos {
+		if _, ok := i.liveOuts[v]; !ok {
+			return true
+		}
 	}
-	buf.WriteString("]")
-	return buf.String()
-}
-
-func (n *node) spill() bool {
-	return n.r == RealRegInvalid
+	return false
 }
 
 func (r *RegisterInfo) isCalleeSaved(reg RealReg) bool {
@@ -598,30 +1034,3 @@ func (r *RegisterInfo) isCalleeSaved(reg RealReg) bool {
 func (r *RegisterInfo) isCallerSaved(reg RealReg) bool {
 	return r.CallerSavedRegisters[reg]
 }
-
-func (a *Allocator) recordCopyRelation(dst, src VReg) {
-	sr, dr := src.IsRealReg(), dst.IsRealReg()
-	switch {
-	case sr && dr:
-	case !sr && !dr:
-		dstN := a.getOrAllocateNode(dst)
-		srcN := a.getOrAllocateNode(src)
-		dstN.copyFromVReg = srcN
-		srcN.copyToVReg = dstN
-	case sr && !dr:
-		dstN := a.getOrAllocateNode(dst)
-		dstN.copyFromReal = src.RealReg()
-	case !sr && dr:
-		srcN := a.getOrAllocateNode(src)
-		srcN.copyToReal = dst.RealReg()
-	}
-}
-
-// assignedRealReg returns either the assigned RealReg to this node or precolored RealReg.
-func (n *node) assignedRealReg() RealReg {
-	r := n.r
-	if r != RealRegInvalid {
-		return r
-	}
-	return n.v.RealReg()
-}
diff --git a/internal/engine/wazevo/backend/regalloc/regset.go b/internal/engine/wazevo/backend/regalloc/regset.go
new file mode 100644
index 00000000..542c2a75
--- /dev/null
+++ b/internal/engine/wazevo/backend/regalloc/regset.go
@@ -0,0 +1,98 @@
+package regalloc
+
+import (
+	"fmt"
+	"strings"
+)
+
+type regSet uint64
+
+func (rs regSet) format(info *RegisterInfo) string { //nolint:unused
+	var ret []string
+	for i := 0; i < 64; i++ {
+		if rs&(1<<uint(i)) != 0 {
+			ret = append(ret, info.RealRegName(RealReg(i)))
+		}
+	}
+	return strings.Join(ret, ", ")
+}
+
+func (rs regSet) has(r RealReg) bool {
+	return rs&(1<<uint(r)) != 0
+}
+
+func (rs regSet) add(r RealReg) regSet {
+	if r >= 64 {
+		return rs
+	}
+	return rs | 1<<uint(r)
+}
+
+func (rs regSet) range_(f func(allocatedRealReg RealReg)) {
+	for i := 0; i < 64; i++ {
+		if rs&(1<<uint(i)) != 0 {
+			f(RealReg(i))
+		}
+	}
+}
+
+type regInUseSet struct {
+	set regSet
+	vrs [64]VReg
+}
+
+func (rs *regInUseSet) reset() {
+	rs.set = 0
+	for i := range rs.vrs {
+		rs.vrs[i] = VRegInvalid
+	}
+}
+
+func (rs *regInUseSet) format(info *RegisterInfo) string { //nolint:unused
+	var ret []string
+	for i := 0; i < 64; i++ {
+		if rs.set&(1<<uint(i)) != 0 {
+			vr := rs.vrs[i]
+			ret = append(ret, fmt.Sprintf("(%s->v%d)", info.RealRegName(RealReg(i)), vr.ID()))
+		}
+	}
+	return strings.Join(ret, ", ")
+}
+
+func (rs *regInUseSet) has(r RealReg) bool {
+	if r >= 64 {
+		return false
+	}
+	return rs.set&(1<<uint(r)) != 0
+}
+
+func (rs *regInUseSet) get(r RealReg) VReg {
+	if r >= 64 {
+		return VRegInvalid
+	}
+	return rs.vrs[r]
+}
+
+func (rs *regInUseSet) remove(r RealReg) {
+	if r >= 64 {
+		return
+	}
+	rs.set &= ^(1 << uint(r))
+	rs.vrs[r] = VRegInvalid
+}
+
+func (rs *regInUseSet) add(r RealReg, vr VReg) {
+	if r >= 64 {
+		return
+	}
+	rs.set |= 1 << uint(r)
+	rs.vrs[r] = vr
+}
+
+func (rs *regInUseSet) range_(f func(allocatedRealReg RealReg, vr VReg)) {
+	for i := 0; i < 64; i++ {
+		if rs.set&(1<<uint(i)) != 0 {
+			f(RealReg(i), rs.vrs[i])
+		}
+	}
+}
diff --git a/internal/engine/wazevo/backend/regalloc/spill_handler.go b/internal/engine/wazevo/backend/regalloc/spill_handler.go
deleted file mode 100644
index dc85f511..00000000
--- a/internal/engine/wazevo/backend/regalloc/spill_handler.go
+++ /dev/null
@@ -1,90 +0,0 @@
-package regalloc
-
-// spillHandler is a helper to handle the spill and reload of registers at some point in the program.
-type spillHandler struct {
-	activeRegs   map[RealReg]spillHandlerRegState
-	deleteTemp   []RealReg
-	beingUsedNow map[RealReg]struct{}
-}
-
-type spillHandlerRegState struct {
-	state int
-	node  *node
-}
-
-const (
-	spillHandlerRegStateUsed = iota
-	spillHandlerRegStateEvictable
-	spillHandlerRegStateEvicted
-	spillHandlerRegStateBeingUsedNow
-)
-
-// init initializes the spill handler with the active nodes which are the node alive at a some point in the program.
-func (s *spillHandler) init(activeNodes []*node, inst Instr) {
-	if s.beingUsedNow == nil {
-		s.beingUsedNow = make(map[RealReg]struct{})
-	} else {
-		s.deleteTemp = s.deleteTemp[:0]
-		for r := range s.beingUsedNow {
-			s.deleteTemp = append(s.deleteTemp, r)
-		}
-		for _, r := range s.deleteTemp {
-			delete(s.beingUsedNow, r)
-		}
-		for _, u := range inst.Uses() {
-			if u.IsRealReg() {
-				s.beingUsedNow[u.RealReg()] = struct{}{}
-			}
-		}
-	}
-
-	if s.activeRegs == nil {
-		s.activeRegs = make(map[RealReg]spillHandlerRegState)
-	} else {
-		s.deleteTemp = s.deleteTemp[:0]
-		for r := range s.activeRegs {
-			s.deleteTemp = append(s.deleteTemp, r)
-		}
-		for _, r := range s.deleteTemp {
-			delete(s.activeRegs, r)
-		}
-	}
-	for _, n := range activeNodes {
-		r := n.assignedRealReg()
-		if _, ok := s.beingUsedNow[r]; ok {
-			s.activeRegs[r] = spillHandlerRegState{node: n, state: spillHandlerRegStateBeingUsedNow}
-		} else {
-			s.activeRegs[r] = spillHandlerRegState{node: n, state: spillHandlerRegStateEvictable}
-		}
-	}
-}
-
-// getUnusedOrEvictReg returns an unused register of the given type or evicts a register from the active registers.
-func (s *spillHandler) getUnusedOrEvictReg(regType RegType, regInfo *RegisterInfo) (r RealReg, evicted *node) {
-	allocatables := regInfo.AllocatableRegisters[regType]
-	for _, candidate := range allocatables {
-		_, ok := s.activeRegs[candidate]
-		if !ok {
-			r = candidate
-			s.activeRegs[candidate] = spillHandlerRegState{state: spillHandlerRegStateUsed}
-			break
-		} // ok=true meaning that it is either used or evicted.
-	}
-
-	if r == RealRegInvalid {
-		// We need to evict a register from the active registers.
-		for _, candidate := range allocatables {
-			state, ok := s.activeRegs[candidate]
-			if !ok {
-				panic("BUG")
-			}
-			if state.state == spillHandlerRegStateEvictable {
-				evicted = state.node
-				r = candidate
-				s.activeRegs[candidate] = spillHandlerRegState{node: state.node, state: spillHandlerRegStateEvicted}
-				break
-			}
-		}
-	}
-	return
-}
diff --git a/internal/engine/wazevo/ssa/basic_block.go b/internal/engine/wazevo/ssa/basic_block.go
index f11347db..b85d9d26 100644
--- a/internal/engine/wazevo/ssa/basic_block.go
+++ b/internal/engine/wazevo/ssa/basic_block.go
@@ -116,6 +116,9 @@ type (
 		// reversePostOrder is used to sort all the blocks in the function in reverse post order.
 		// This is used in builder.LayoutBlocks.
 		reversePostOrder int
+
+		// child and sibling are the ones in the dominator tree.
+		child, sibling *basicBlock
 	}
 	// BasicBlockID is the unique ID of a basicBlock.
 	BasicBlockID uint32
@@ -287,6 +290,8 @@ func resetBasicBlock(bb *basicBlock) {
 	bb.reversePostOrder = -1
 	bb.loopNestingForestChildren = bb.loopNestingForestChildren[:0]
 	bb.loopHeader = false
+	bb.sibling = nil
+	bb.child = nil
 }
 
 // addPred adds a predecessor to this block specified by the branch instruction.
diff --git a/internal/engine/wazevo/ssa/builder.go b/internal/engine/wazevo/ssa/builder.go
index 11927bb0..90a83853 100644
--- a/internal/engine/wazevo/ssa/builder.go
+++ b/internal/engine/wazevo/ssa/builder.go
@@ -124,6 +124,12 @@ type Builder interface {
 
 	// LoopNestingForestRoots returns the roots of the loop nesting forest.
 	LoopNestingForestRoots() []BasicBlock
+
+	// LowestCommonAncestor returns the lowest common ancestor in the dominator tree of the given BasicBlock(s).
+	LowestCommonAncestor(blk1, blk2 BasicBlock) BasicBlock
+
+	// Idom returns the immediate dominator of the given BasicBlock.
+	Idom(blk BasicBlock) BasicBlock
 }
 
 // NewBuilder returns a new Builder implementation.
@@ -169,6 +175,7 @@ type builder struct {
 	// dominators stores the immediate dominator of each BasicBlock.
 	// The index is blockID of the BasicBlock.
 	dominators []*basicBlock
+	sparseTree dominatorSparseTree
 
 	// loopNestingForestRoots are the roots of the loop nesting forest.
 	loopNestingForestRoots []BasicBlock
@@ -311,6 +318,11 @@ func (b *builder) allocateBasicBlock() *basicBlock {
 	return blk
 }
 
+// Idom implements Builder.Idom.
+func (b *builder) Idom(blk BasicBlock) BasicBlock {
+	return b.dominators[blk.ID()]
+}
+
 // InsertInstruction implements Builder.InsertInstruction.
 func (b *builder) InsertInstruction(instr *Instruction) {
 	b.currentBB.InsertInstruction(instr)
@@ -845,6 +857,7 @@ func (b *builder) LayoutBlocks() {
 
 	// Critical edges are split, so we fix the loop nesting forest.
 	buildLoopNestingForest(b)
+	buildDominatorTree(b)
 
 	// Reuse the stack for the next iteration.
 	b.blkStack2 = uninsertedTrampolines[:0]
@@ -1046,3 +1059,8 @@ func (b *builder) InsertUndefined() {
 func (b *builder) LoopNestingForestRoots() []BasicBlock {
 	return b.loopNestingForestRoots
 }
+
+// LowestCommonAncestor implements Builder.LowestCommonAncestor.
+func (b *builder) LowestCommonAncestor(blk1, blk2 BasicBlock) BasicBlock {
+	return b.sparseTree.findLCA(blk1.ID(), blk2.ID())
+}
diff --git a/internal/engine/wazevo/ssa/pass_cfg.go b/internal/engine/wazevo/ssa/pass_cfg.go
index ae674a20..c39a75b5 100644
--- a/internal/engine/wazevo/ssa/pass_cfg.go
+++ b/internal/engine/wazevo/ssa/pass_cfg.go
@@ -2,6 +2,7 @@ package ssa
 
 import (
 	"fmt"
+	"math"
 	"strings"
 
 	"github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi"
@@ -199,3 +200,113 @@ func printLoopNestingForest(root *basicBlock, depth int) {
 		}
 	}
 }
+
+type dominatorSparseTree struct {
+	time         int
+	euler        []*basicBlock
+	first, depth []int
+	table        [][]int
+}
+
+// buildDominatorTree builds the dominator tree for the function, and constructs builder.sparseTree.
+func buildDominatorTree(b *builder) {
+	// First we materialize the children of each node in the dominator tree.
+	idoms := b.dominators
+	for _, blk := range b.reversePostOrderedBasicBlocks {
+		parent := idoms[blk.id]
+		if parent == nil {
+			panic("BUG")
+		} else if parent == blk {
+			// This is the entry block.
+			continue
+		}
+		if prev := parent.child; prev == nil {
+			parent.child = blk
+		} else {
+			parent.child = blk
+			blk.sibling = prev
+		}
+	}
+
+	// Reset the state from the previous computation.
+	n := b.basicBlocksPool.Allocated()
+	st := &b.sparseTree
+	st.euler = append(st.euler[:0], make([]*basicBlock, 2*n-1)...)
+	st.first = append(st.first[:0], make([]int, n)...)
+	for i := range st.first {
+		st.first[i] = -1
+	}
+	st.depth = append(st.depth[:0], make([]int, 2*n-1)...)
+	st.time = 0
+
+	// Start building the sparse tree.
+	st.eulerTour(b.entryBlk(), 0)
+	st.buildSparseTable()
+}
+
+func (dt *dominatorSparseTree) eulerTour(node *basicBlock, height int) {
+	if wazevoapi.SSALoggingEnabled {
+		fmt.Println(strings.Repeat("\t", height), "euler tour:", node.ID())
+	}
+	dt.euler[dt.time] = node
+	dt.depth[dt.time] = height
+	if dt.first[node.id] == -1 {
+		dt.first[node.id] = dt.time
+	}
+	dt.time++
+
+	for child := node.child; child != nil; child = child.sibling {
+		dt.eulerTour(child, height+1)
+		dt.euler[dt.time] = node // add the current node again after visiting a child
+		dt.depth[dt.time] = height
+		dt.time++
+	}
+}
+
+// buildSparseTable builds a sparse table for RMQ queries.
+func (dt *dominatorSparseTree) buildSparseTable() {
+	n := len(dt.depth)
+	k := int(math.Log2(float64(n))) + 1
+	table := dt.table
+
+	if n >= len(table) {
+		table = append(table, make([][]int, n+1)...)
+	}
+	for i := range table {
+		if len(table[i]) < k {
+			table[i] = append(table[i], make([]int, k)...)
+		}
+		table[i][0] = i
+	}
+
+	for j := 1; 1<<j <= n; j++ {
+		for i := 0; i+(1<<j)-1 < n; i++ {
+			if dt.depth[table[i][j-1]] < dt.depth[table[i+(1<<(j-1))][j-1]] {
+				table[i][j] = table[i][j-1]
+			} else {
+				table[i][j] = table[i+(1<<(j-1))][j-1]
+			}
+		}
+	}
+	dt.table = table
+}
+
+// rmq performs a range minimum query on the sparse table.
+func (dt *dominatorSparseTree) rmq(l, r int) int {
+	table := dt.table
+	depth := dt.depth
+	j := int(math.Log2(float64(r - l + 1)))
+	if depth[table[l][j]] <= depth[table[r-(1<<j)+1][j]] {
+		return table[l][j]
+	}
+	return table[r-(1<<j)+1][j]
+}
+
+// findLCA finds the LCA using the Euler tour and RMQ.
+func (dt *dominatorSparseTree) findLCA(u, v BasicBlockID) *basicBlock {
+	first := dt.first
+	if first[u] > first[v] {
+		u, v = v, u
+	}
+	return dt.euler[dt.rmq(first[u], first[v])]
+}


Test patch:
diff --git a/internal/engine/wazevo/backend/backend_test.go b/internal/engine/wazevo/backend/backend_test.go
index bb0239e2..592e20a8 100644
--- a/internal/engine/wazevo/backend/backend_test.go
+++ b/internal/engine/wazevo/backend/backend_test.go
@@ -67,14 +67,17 @@ L1 (SSA Block: blk0):
 	stp x30, xzr, [sp, #-0x10]!
 	str xzr, [sp, #-0x10]!
 	subs xzr, x4, x5
-	csel w0, w2, w3, eq
+	csel w8, w2, w3, eq
 	subs wzr, w3, wzr
-	csel x1, x4, x5, ne
+	csel x9, x4, x5, ne
 	fcmp d2, d3
 	fcsel s8, s0, s1, gt
 	fcmp s0, s1
-	fcsel d1, d2, d3, ne
+	fcsel d9, d2, d3, ne
+	mov v1.8b, v9.8b
 	mov v0.8b, v8.8b
+	mov x1, x9
+	mov x0, x8
 	add sp, sp, #0x10
 	ldr x30, [sp], #0x10
 	ret
@@ -98,10 +101,14 @@ L1 (SSA Block: blk0):
 L1 (SSA Block: blk0):
 	stp x30, xzr, [sp, #-0x10]!
 	str xzr, [sp, #-0x10]!
-	ldr d1, #8; b 16; data.f64 64.000000
-	ldr s0, #8; b 8; data.f32 32.000000
-	orr x1, xzr, #0x2
-	orr w0, wzr, #0x1
+	ldr d8, #8; b 16; data.f64 64.000000
+	mov v1.8b, v8.8b
+	ldr s8, #8; b 8; data.f32 32.000000
+	mov v0.8b, v8.8b
+	orr x8, xzr, #0x2
+	mov x1, x8
+	orr w8, wzr, #0x1
+	mov x0, x8
 	add sp, sp, #0x10
 	ldr x30, [sp], #0x10
 	ret
@@ -123,7 +130,8 @@ L1 (SSA Block: blk0):
 	stp x30, xzr, [sp, #-0x10]!
 	str xzr, [sp, #-0x10]!
 	add w8, w2, w3
-	sub w0, w8, w2
+	sub w8, w8, w2
+	mov x0, x8
 	add sp, sp, #0x10
 	ldr x30, [sp], #0x10
 	ret
@@ -160,19 +168,22 @@ L1 (SSA Block: blk0):
 	stp x30, xzr, [sp, #-0x10]!
 	str xzr, [sp, #-0x10]!
 	add x8, x2, x2
-	sub x0, x8, x2
+	sub x8, x8, x2
 	fadd s8, s0, s0
 	fsub s8, s8, s0
 	fmul s8, s8, s0
 	fdiv s8, s8, s0
 	fmax s8, s8, s0
-	fmin s0, s8, s0
-	fadd d8, d1, d1
-	fsub d8, d8, d1
-	fmul d8, d8, d1
-	fdiv d8, d8, d1
-	fmax d8, d8, d1
-	fmin d1, d8, d1
+	fmin s8, s8, s0
+	fadd d9, d1, d1
+	fsub d9, d9, d1
+	fmul d9, d9, d1
+	fdiv d9, d9, d1
+	fmax d9, d9, d1
+	fmin d9, d9, d1
+	mov v1.8b, v9.8b
+	mov v0.8b, v8.8b
+	mov x0, x8
 	add sp, sp, #0x10
 	ldr x30, [sp], #0x10
 	ret
@@ -192,7 +203,8 @@ L1 (SSA Block: blk0):
 L1 (SSA Block: blk0):
 	stp x30, xzr, [sp, #-0x10]!
 	str xzr, [sp, #-0x10]!
-	mov x1, xzr
+	mov x8, xzr
+	mov x1, x8
 	mov x0, x2
 	add sp, sp, #0x10
 	ldr x30, [sp], #0x10
@@ -469,15 +481,17 @@ L1 (SSA Block: blk0):
 	stp x30, xzr, [sp, #-0x10]!
 	str xzr, [sp, #-0x10]!
 	mov x8, xzr
-	cbz w8, #0x14 L2
+	cbz w8, #0x18 L2
 L3 (SSA Block: blk1):
-	mov x0, xzr
+	mov x8, xzr
+	mov x0, x8
 	add sp, sp, #0x10
 	ldr x30, [sp], #0x10
 	ret
 L2 (SSA Block: blk2):
 L4 (SSA Block: blk3):
-	mov x0, xzr
+	mov x8, xzr
+	mov x0, x8
 	add sp, sp, #0x10
 	ldr x30, [sp], #0x10
 	ret
@@ -562,7 +576,8 @@ L4 (SSA Block: blk5):
 L3 (SSA Block: blk4):
 L5 (SSA Block: blk3):
 L6 (SSA Block: blk2):
-	mov x0, xzr
+	mov x8, xzr
+	mov x0, x8
 	add sp, sp, #0x10
 	ldr x30, [sp], #0x10
 	ret
@@ -598,8 +613,9 @@ L4 (SSA Block: blk5):
 	ret
 L3 (SSA Block: blk4):
 L5 (SSA Block: blk3):
-	orr w2, wzr, #0x1
-	b #-0x18 (L2)
+	orr w8, wzr, #0x1
+	mov x2, x8
+	b #-0x1c (L2)
 `,
 		},
 		{
@@ -635,36 +651,35 @@ L1 (SSA Block: blk0):
 			afterFinalizeARM64: `
 L1 (SSA Block: blk0):
 	stp x30, xzr, [sp, #-0x10]!
-	sub sp, sp, #0x10
-	orr x27, xzr, #0x10
+	sub sp, sp, #0x20
+	orr x27, xzr, #0x20
 	str x27, [sp, #-0x10]!
-	mov x8, x0
-	mov x9, x1
-	str x9, [x8, #0x8]
-	mov x0, x8
-	mov x1, x9
-	str x8, [sp, #0x10]
-	str x9, [sp, #0x18]
+	str x0, [sp, #0x18]
+	str x1, [sp, #0x10]
+	str x1, [x0, #0x8]
 	bl f1
-	ldr x9, [sp, #0x18]
+	str w0, [sp, #0x20]
 	ldr x8, [sp, #0x10]
-	mov x2, x0
-	str x9, [x8, #0x8]
-	mov x0, x8
-	mov x1, x9
-	movz w3, #0x5, lsl 0
-	str x8, [sp, #0x10]
-	str x9, [sp, #0x18]
-	bl f2
 	ldr x9, [sp, #0x18]
+	str x8, [x9, #0x8]
+	mov x0, x9
+	mov x1, x8
+	ldr w10, [sp, #0x20]
+	mov x2, x10
+	movz w10, #0x5, lsl 0
+	mov x3, x10
+	bl f2
+	str w0, [sp, #0x24]
 	ldr x8, [sp, #0x10]
-	mov x2, x0
-	str x9, [x8, #0x8]
-	mov x0, x8
-	mov x1, x9
+	ldr x9, [sp, #0x18]
+	str x8, [x9, #0x8]
+	mov x0, x9
+	mov x1, x8
+	ldr w8, [sp, #0x24]
+	mov x2, x8
 	bl f3
 	add sp, sp, #0x10
-	add sp, sp, #0x10
+	add sp, sp, #0x20
 	ldr x30, [sp], #0x10
 	ret
 `,
@@ -728,19 +743,21 @@ L1 (SSA Block: blk0):
 			afterFinalizeARM64: `
 L1 (SSA Block: blk0):
 	stp x30, xzr, [sp, #-0x10]!
-	str xzr, [sp, #-0x10]!
-	mov x8, x2
-	mov x9, x3
-	mov v8.8b, v0.8b
-	mov v9.8b, v1.8b
+	sub sp, sp, #0x20
+	orr x27, xzr, #0x20
+	str x27, [sp, #-0x10]!
+	str w2, [sp, #0x10]
+	str x3, [sp, #0x14]
+	str s0, [sp, #0x1c]
+	str d1, [sp, #0x20]
 	str x1, [x0, #0x8]
-	mov x2, x8
-	mov x3, x9
-	mov v0.8b, v8.8b
-	mov v1.8b, v9.8b
+	ldr w8, [sp, #0x10]
 	mov x4, x8
+	ldr x9, [sp, #0x14]
 	mov x5, x9
+	ldr s8, [sp, #0x1c]
 	mov v2.8b, v8.8b
+	ldr d9, [sp, #0x20]
 	mov v3.8b, v9.8b
 	mov x6, x8
 	mov x7, x9
@@ -776,6 +793,7 @@ L1 (SSA Block: blk0):
 	str d9, [sp, #-0x8]
 	bl f1
 	add sp, sp, #0x10
+	add sp, sp, #0x20
 	ldr x30, [sp], #0x10
 	ret
 `,
@@ -893,54 +911,54 @@ L1 (SSA Block: blk0):
 	str x27, [sp, #-0x10]!
 	str x1, [x0, #0x8]
 	bl f1
-	ldr w20, [sp, #-0xc0]
-	ldr x19, [sp, #-0xb8]
-	ldr s19, [sp, #-0xb0]
-	ldr d18, [sp, #-0xa8]
-	ldr w17, [sp, #-0xa0]
-	ldr x16, [sp, #-0x98]
-	ldr s17, [sp, #-0x90]
-	ldr d16, [sp, #-0x88]
-	ldr w15, [sp, #-0x80]
-	ldr x14, [sp, #-0x78]
-	ldr s15, [sp, #-0x70]
-	ldr d14, [sp, #-0x68]
-	ldr w13, [sp, #-0x60]
-	ldr x12, [sp, #-0x58]
-	ldr s13, [sp, #-0x50]
-	ldr d12, [sp, #-0x48]
-	ldr w11, [sp, #-0x40]
-	ldr x10, [sp, #-0x38]
-	ldr s11, [sp, #-0x30]
-	ldr d10, [sp, #-0x28]
-	ldr w9, [sp, #-0x20]
-	ldr x8, [sp, #-0x18]
-	ldr s9, [sp, #-0x10]
-	ldr d8, [sp, #-0x8]
-	str d8, [sp, #0x118]
-	str s9, [sp, #0x110]
-	str x8, [sp, #0x108]
-	str w9, [sp, #0x100]
-	str d10, [sp, #0xf8]
-	str s11, [sp, #0xf0]
-	str x10, [sp, #0xe8]
-	str w11, [sp, #0xe0]
-	str d12, [sp, #0xd8]
-	str s13, [sp, #0xd0]
-	str x12, [sp, #0xc8]
-	str w13, [sp, #0xc0]
-	str d14, [sp, #0xb8]
-	str s15, [sp, #0xb0]
-	str x14, [sp, #0xa8]
-	str w15, [sp, #0xa0]
-	str d16, [sp, #0x98]
-	str s17, [sp, #0x90]
-	str x16, [sp, #0x88]
-	str w17, [sp, #0x80]
-	str d18, [sp, #0x78]
-	str s19, [sp, #0x70]
-	str x19, [sp, #0x68]
-	str w20, [sp, #0x60]
+	ldr w8, [sp, #-0xc0]
+	ldr x9, [sp, #-0xb8]
+	ldr s8, [sp, #-0xb0]
+	ldr d9, [sp, #-0xa8]
+	ldr w10, [sp, #-0xa0]
+	ldr x11, [sp, #-0x98]
+	ldr s10, [sp, #-0x90]
+	ldr d11, [sp, #-0x88]
+	ldr w12, [sp, #-0x80]
+	ldr x13, [sp, #-0x78]
+	ldr s12, [sp, #-0x70]
+	ldr d13, [sp, #-0x68]
+	ldr w14, [sp, #-0x60]
+	ldr x15, [sp, #-0x58]
+	ldr s14, [sp, #-0x50]
+	ldr d15, [sp, #-0x48]
+	ldr w16, [sp, #-0x40]
+	ldr x17, [sp, #-0x38]
+	ldr s16, [sp, #-0x30]
+	ldr d17, [sp, #-0x28]
+	ldr w19, [sp, #-0x20]
+	ldr x20, [sp, #-0x18]
+	ldr s18, [sp, #-0x10]
+	ldr d19, [sp, #-0x8]
+	str d19, [sp, #0x118]
+	str s18, [sp, #0x110]
+	str x20, [sp, #0x108]
+	str w19, [sp, #0x100]
+	str d17, [sp, #0xf8]
+	str s16, [sp, #0xf0]
+	str x17, [sp, #0xe8]
+	str w16, [sp, #0xe0]
+	str d15, [sp, #0xd8]
+	str s14, [sp, #0xd0]
+	str x15, [sp, #0xc8]
+	str w14, [sp, #0xc0]
+	str d13, [sp, #0xb8]
+	str s12, [sp, #0xb0]
+	str x13, [sp, #0xa8]
+	str w12, [sp, #0xa0]
+	str d11, [sp, #0x98]
+	str s10, [sp, #0x90]
+	str x11, [sp, #0x88]
+	str w10, [sp, #0x80]
+	str d9, [sp, #0x78]
+	str s8, [sp, #0x70]
+	str x9, [sp, #0x68]
+	str w8, [sp, #0x60]
 	add sp, sp, #0x10
 	ldr q19, [sp], #0x10
 	ldr q18, [sp], #0x10
@@ -978,15 +996,20 @@ L1 (SSA Block: blk0):
 L1 (SSA Block: blk0):
 	stp x30, xzr, [sp, #-0x10]!
 	str xzr, [sp, #-0x10]!
-	mov x8, x2
-	mov x9, x3
-	sxtw x0, w8
-	uxtw x1, w8
-	sxtb x2, w9
-	sxth x3, w9
-	sxtw x4, w9
-	sxtb w5, w8
-	sxth w6, w8
+	sxtw x8, w2
+	uxtw x9, w2
+	sxtb x10, w3
+	sxth x11, w3
+	sxtw x12, w3
+	sxtb w13, w2
+	sxth w14, w2
+	mov x6, x14
+	mov x5, x13
+	mov x4, x12
+	mov x3, x11
+	mov x2, x10
+	mov x1, x9
+	mov x0, x8
 	add sp, sp, #0x10
 	ldr x30, [sp], #0x10
 	ret
@@ -1024,21 +1047,26 @@ L1 (SSA Block: blk0):
 L1 (SSA Block: blk0):
 	stp x30, xzr, [sp, #-0x10]!
 	str xzr, [sp, #-0x10]!
-	mov x8, x3
-	clz w0, w2
+	clz w8, w2
 	rbit w9, w2
-	clz w1, w9
+	clz w9, w9
 	ins v8.d[0], x2
 	cnt v8.16b, v8.16b
 	uaddlv h8, v8.8b
-	mov x2, v8.d[0]
-	clz x3, x8
-	rbit x9, x8
-	clz x4, x9
-	ins v8.d[0], x8
+	mov x10, v8.d[0]
+	clz x11, x3
+	rbit x12, x3
+	clz x12, x12
+	ins v8.d[0], x3
 	cnt v8.16b, v8.16b
 	uaddlv h8, v8.8b
-	mov x5, v8.d[0]
+	mov x13, v8.d[0]
+	mov x5, x13
+	mov x4, x12
+	mov x3, x11
+	mov x2, x10
+	mov x1, x9
+	mov x0, x8
 	add sp, sp, #0x10
 	ldr x30, [sp], #0x10
 	ret
@@ -1052,36 +1080,49 @@ L1 (SSA Block: blk0):
 	orr x27, xzr, #0x20
 	sub sp, sp, x27
 	stp x30, x27, [sp, #-0x10]!
-	str xzr, [sp, #-0x10]!
+	str x19, [sp, #-0x10]!
+	str x20, [sp, #-0x10]!
+	orr x27, xzr, #0x20
+	str x27, [sp, #-0x10]!
 	fcmp s0, s1
-	cset x0, eq
+	cset x8, eq
 	fcmp s0, s1
-	cset x1, ne
+	cset x9, ne
 	fcmp s0, s1
-	cset x2, mi
+	cset x10, mi
 	fcmp s0, s1
-	cset x3, gt
+	cset x11, gt
 	fcmp s0, s1
-	cset x4, ls
+	cset x12, ls
 	fcmp s0, s1
-	cset x5, ge
+	cset x13, ge
 	fcmp d2, d3
-	cset x6, eq
+	cset x14, eq
 	fcmp d2, d3
-	cset x7, ne
+	cset x15, ne
 	fcmp d2, d3
-	cset x11, mi
+	cset x16, mi
 	fcmp d2, d3
-	cset x10, gt
+	cset x17, gt
 	fcmp d2, d3
-	cset x9, ls
+	cset x19, ls
 	fcmp d2, d3
-	cset x8, ge
-	str w8, [sp, #0x38]
-	str w9, [sp, #0x30]
-	str w10, [sp, #0x28]
-	str w11, [sp, #0x20]
+	cset x20, ge
+	str w20, [sp, #0x58]
+	str w19, [sp, #0x50]
+	str w17, [sp, #0x48]
+	str w16, [sp, #0x40]
+	mov x7, x15
+	mov x6, x14
+	mov x5, x13
+	mov x4, x12
+	mov x3, x11
+	mov x2, x10
+	mov x1, x9
+	mov x0, x8
 	add sp, sp, #0x10
+	ldr x20, [sp], #0x10
+	ldr x19, [sp], #0x10
 	ldr x30, [sp], #0x10
 	add sp, sp, #0x20
 	ret
@@ -1094,216 +1135,225 @@ L1 (SSA Block: blk0):
 L1 (SSA Block: blk0):
 	stp x30, xzr, [sp, #-0x10]!
 	str xzr, [sp, #-0x10]!
-	mov x8, x0
 	msr fpsr, xzr
-	fcvtzs x0, d0
-	mrs x10 fpsr
-	mov x9, x8
+	fcvtzs x8, d0
+	mrs x9 fpsr
+	mov x10, x0
 	mov v8.16b, v0.16b
-	subs xzr, x10, #0x1
+	subs xzr, x9, #0x1
 	b.ne #0x70, (L17)
 	fcmp d8, d8
-	mov x10, x9
+	mov x9, x10
 	b.vc #0x34, (L16)
 	movz x11, #0xc, lsl 0
-	str w11, [x10]
+	str w11, [x9]
 	mov x11, sp
-	str x11, [x10, #0x38]
+	str x11, [x9, #0x38]
 	adr x11, #0x0
-	str x11, [x10, #0x30]
-	exit_sequence x10
-L16:
-	movz x10, #0xb, lsl 0
-	str w10, [x9]
-	mov x10, sp
-	str x10, [x9, #0x38]
-	adr x10, #0x0
-	str x10, [x9, #0x30]
+	str x11, [x9, #0x30]
 	exit_sequence x9
+L16:
+	movz x9, #0xb, lsl 0
+	str w9, [x10]
+	mov x9, sp
+	str x9, [x10, #0x38]
+	adr x9, #0x0
+	str x9, [x10, #0x30]
+	exit_sequence x10
 L17:
 	msr fpsr, xzr
-	fcvtzs x1, s1
+	fcvtzs x9, s1
 	mrs x10 fpsr
-	mov x9, x8
+	mov x11, x0
 	mov v8.16b, v1.16b
 	subs xzr, x10, #0x1
 	b.ne #0x70, (L15)
 	fcmp s8, s8
-	mov x10, x9
+	mov x10, x11
 	b.vc #0x34, (L14)
-	movz x11, #0xc, lsl 0
-	str w11, [x10]
-	mov x11, sp
-	str x11, [x10, #0x38]
-	adr x11, #0x0
-	str x11, [x10, #0x30]
+	movz x12, #0xc, lsl 0
+	str w12, [x10]
+	mov x12, sp
+	str x12, [x10, #0x38]
+	adr x12, #0x0
+	str x12, [x10, #0x30]
 	exit_sequence x10
 L14:
 	movz x10, #0xb, lsl 0
-	str w10, [x9]
+	str w10, [x11]
 	mov x10, sp
-	str x10, [x9, #0x38]
+	str x10, [x11, #0x38]
 	adr x10, #0x0
-	str x10, [x9, #0x30]
-	exit_sequence x9
+	str x10, [x11, #0x30]
+	exit_sequence x11
 L15:
 	msr fpsr, xzr
-	fcvtzs w2, d0
-	mrs x10 fpsr
-	mov x9, x8
+	fcvtzs w10, d0
+	mrs x11 fpsr
+	mov x12, x0
 	mov v8.16b, v0.16b
-	subs xzr, x10, #0x1
+	subs xzr, x11, #0x1
 	b.ne #0x70, (L13)
 	fcmp d8, d8
-	mov x10, x9
+	mov x11, x12
 	b.vc #0x34, (L12)
-	movz x11, #0xc, lsl 0
-	str w11, [x10]
+	movz x13, #0xc, lsl 0
+	str w13, [x11]
+	mov x13, sp
+	str x13, [x11, #0x38]
+	adr x13, #0x0
+	str x13, [x11, #0x30]
+	exit_sequence x11
+L12:
+	movz x11, #0xb, lsl 0
+	str w11, [x12]
 	mov x11, sp
-	str x11, [x10, #0x38]
+	str x11, [x12, #0x38]
 	adr x11, #0x0
-	str x11, [x10, #0x30]
-	exit_sequence x10
-L12:
-	movz x10, #0xb, lsl 0
-	str w10, [x9]
-	mov x10, sp
-	str x10, [x9, #0x38]
-	adr x10, #0x0
-	str x10, [x9, #0x30]
-	exit_sequence x9
+	str x11, [x12, #0x30]
+	exit_sequence x12
 L13:
 	msr fpsr, xzr
-	fcvtzs w3, s1
-	mrs x10 fpsr
-	mov x9, x8
+	fcvtzs w11, s1
+	mrs x12 fpsr
+	mov x13, x0
 	mov v8.16b, v1.16b
-	subs xzr, x10, #0x1
+	subs xzr, x12, #0x1
 	b.ne #0x70, (L11)
 	fcmp s8, s8
-	mov x10, x9
+	mov x12, x13
 	b.vc #0x34, (L10)
-	movz x11, #0xc, lsl 0
-	str w11, [x10]
-	mov x11, sp
-	str x11, [x10, #0x38]
-	adr x11, #0x0
-	str x11, [x10, #0x30]
-	exit_sequence x10
+	movz x14, #0xc, lsl 0
+	str w14, [x12]
+	mov x14, sp
+	str x14, [x12, #0x38]
+	adr x14, #0x0
+	str x14, [x12, #0x30]
+	exit_sequence x12
 L10:
-	movz x10, #0xb, lsl 0
-	str w10, [x9]
-	mov x10, sp
-	str x10, [x9, #0x38]
-	adr x10, #0x0
-	str x10, [x9, #0x30]
-	exit_sequence x9
+	movz x12, #0xb, lsl 0
+	str w12, [x13]
+	mov x12, sp
+	str x12, [x13, #0x38]
+	adr x12, #0x0
+	str x12, [x13, #0x30]
+	exit_sequence x13
 L11:
 	msr fpsr, xzr
-	fcvtzu x4, d0
-	mrs x10 fpsr
-	mov x9, x8
+	fcvtzu x12, d0
+	mrs x13 fpsr
+	mov x14, x0
 	mov v8.16b, v0.16b
-	subs xzr, x10, #0x1
+	subs xzr, x13, #0x1
 	b.ne #0x70, (L9)
 	fcmp d8, d8
-	mov x10, x9
+	mov x13, x14
 	b.vc #0x34, (L8)
-	movz x11, #0xc, lsl 0
-	str w11, [x10]
-	mov x11, sp
-	str x11, [x10, #0x38]
-	adr x11, #0x0
-	str x11, [x10, #0x30]
-	exit_sequence x10
+	movz x15, #0xc, lsl 0
+	str w15, [x13]
+	mov x15, sp
+	str x15, [x13, #0x38]
+	adr x15, #0x0
+	str x15, [x13, #0x30]
+	exit_sequence x13
 L8:
-	movz x10, #0xb, lsl 0
-	str w10, [x9]
-	mov x10, sp
-	str x10, [x9, #0x38]
-	adr x10, #0x0
-	str x10, [x9, #0x30]
-	exit_sequence x9
+	movz x13, #0xb, lsl 0
+	str w13, [x14]
+	mov x13, sp
+	str x13, [x14, #0x38]
+	adr x13, #0x0
+	str x13, [x14, #0x30]
+	exit_sequence x14
 L9:
 	msr fpsr, xzr
-	fcvtzu x5, s1
-	mrs x10 fpsr
-	mov x9, x8
+	fcvtzu x13, s1
+	mrs x14 fpsr
+	mov x15, x0
 	mov v8.16b, v1.16b
-	subs xzr, x10, #0x1
+	subs xzr, x14, #0x1
 	b.ne #0x70, (L7)
 	fcmp s8, s8
-	mov x10, x9
+	mov x14, x15
 	b.vc #0x34, (L6)
-	movz x11, #0xc, lsl 0
-	str w11, [x10]
-	mov x11, sp
-	str x11, [x10, #0x38]
-	adr x11, #0x0
-	str x11, [x10, #0x30]
-	exit_sequence x10
+	movz x16, #0xc, lsl 0
+	str w16, [x14]
+	mov x16, sp
+	str x16, [x14, #0x38]
+	adr x16, #0x0
+	str x16, [x14, #0x30]
+	exit_sequence x14
 L6:
-	movz x10, #0xb, lsl 0
-	str w10, [x9]
-	mov x10, sp
-	str x10, [x9, #0x38]
-	adr x10, #0x0
-	str x10, [x9, #0x30]
-	exit_sequence x9
+	movz x14, #0xb, lsl 0
+	str w14, [x15]
+	mov x14, sp
+	str x14, [x15, #0x38]
+	adr x14, #0x0
+	str x14, [x15, #0x30]
+	exit_sequence x15
 L7:
 	msr fpsr, xzr
-	fcvtzu w6, d0
-	mrs x10 fpsr
-	mov x9, x8
+	fcvtzu w14, d0
+	mrs x15 fpsr
+	mov x16, x0
 	mov v8.16b, v0.16b
-	subs xzr, x10, #0x1
+	subs xzr, x15, #0x1
 	b.ne #0x70, (L5)
 	fcmp d8, d8
-	mov x10, x9
+	mov x15, x16
 	b.vc #0x34, (L4)
-	movz x11, #0xc, lsl 0
-	str w11, [x10]
-	mov x11, sp
-	str x11, [x10, #0x38]
-	adr x11, #0x0
-	str x11, [x10, #0x30]
-	exit_sequence x10
+	movz x17, #0xc, lsl 0
+	str w17, [x15]
+	mov x17, sp
+	str x17, [x15, #0x38]
+	adr x17, #0x0
+	str x17, [x15, #0x30]
+	exit_sequence x15
 L4:
-	movz x10, #0xb, lsl 0
-	str w10, [x9]
-	mov x10, sp
-	str x10, [x9, #0x38]
-	adr x10, #0x0
-	str x10, [x9, #0x30]
-	exit_sequence x9
+	movz x15, #0xb, lsl 0
+	str w15, [x16]
+	mov x15, sp
+	str x15, [x16, #0x38]
+	adr x15, #0x0
+	str x15, [x16, #0x30]
+	exit_sequence x16
 L5:
 	msr fpsr, xzr
-	fcvtzu w7, s1
-	mrs x9 fpsr
+	fcvtzu w15, s1
+	mrs x16 fpsr
 	mov v8.16b, v1.16b
-	subs xzr, x9, #0x1
+	subs xzr, x16, #0x1
 	b.ne #0x70, (L3)
 	fcmp s8, s8
-	mov x9, x8
+	mov x16, x0
 	b.vc #0x34, (L2)
-	movz x10, #0xc, lsl 0
-	str w10, [x9]
-	mov x10, sp
-	str x10, [x9, #0x38]
-	adr x10, #0x0
-	str x10, [x9, #0x30]
-	exit_sequence x9
+	movz x17, #0xc, lsl 0
+	str w17, [x16]
+	mov x17, sp
+	str x17, [x16, #0x38]
+	adr x17, #0x0
+	str x17, [x16, #0x30]
+	exit_sequence x16
 L2:
-	movz x9, #0xb, lsl 0
-	str w9, [x8]
-	mov x9, sp
-	str x9, [x8, #0x38]
-	adr x9, #0x0
-	str x9, [x8, #0x30]
-	exit_sequence x8
+	movz x16, #0xb, lsl 0
+	str w16, [x0]
+	mov x16, sp
+	str x16, [x0, #0x38]
+	adr x16, #0x0
+	str x16, [x0, #0x30]
+	exit_sequence x0
 L3:
-	fcvt s0, d0
-	fcvt d1, s1
+	fcvt s8, d0
+	fcvt d9, s1
+	mov v1.8b, v9.8b
+	mov v0.8b, v8.8b
+	mov x7, x15
+	mov x6, x14
+	mov x5, x13
+	mov x4, x12
+	mov x3, x11
+	mov x2, x10
+	mov x1, x9
+	mov x0, x8
 	add sp, sp, #0x10
 	ldr x30, [sp], #0x10
 	ret
@@ -1316,14 +1366,22 @@ L3:
 L1 (SSA Block: blk0):
 	stp x30, xzr, [sp, #-0x10]!
 	str xzr, [sp, #-0x10]!
-	fcvtzs x0, d0
-	fcvtzs x1, s1
-	fcvtzs w2, d0
-	fcvtzs w3, s1
-	fcvtzu x4, d0
-	fcvtzu x5, s1
-	fcvtzu w6, d0
-	fcvtzu w7, s1
+	fcvtzs x8, d0
+	fcvtzs x9, s1
+	fcvtzs w10, d0
+	fcvtzs w11, s1
+	fcvtzu x12, d0
+	fcvtzu x13, s1
+	fcvtzu w14, d0
+	fcvtzu w15, s1
+	mov x7, x15
+	mov x6, x14
+	mov x5, x13
+	mov x4, x12
+	mov x3, x11
+	mov x2, x10
+	mov x1, x9
+	mov x0, x8
 	add sp, sp, #0x10
 	ldr x30, [sp], #0x10
 	ret
@@ -1482,35 +1540,35 @@ L1 (SSA Block: blk0):
 	movz x27, #0x120, lsl 0
 	str x27, [sp, #-0x10]!
 	orr w8, wzr, #0x1
-	madd w23, w2, w8, wzr
-	orr w8, wzr, #0x2
-	madd w22, w2, w8, wzr
-	orr w8, wzr, #0x3
-	madd w21, w2, w8, wzr
-	orr w8, wzr, #0x4
-	madd w20, w2, w8, wzr
-	movz w8, #0x5, lsl 0
-	madd w19, w2, w8, wzr
-	orr w8, wzr, #0x6
-	madd w17, w2, w8, wzr
-	orr w8, wzr, #0x7
-	madd w16, w2, w8, wzr
-	orr w8, wzr, #0x8
-	madd w15, w2, w8, wzr
-	movz w8, #0x9, lsl 0
-	madd w14, w2, w8, wzr
-	movz w8, #0xa, lsl 0
-	madd w13, w2, w8, wzr
-	movz w8, #0xb, lsl 0
-	madd w12, w2, w8, wzr
-	orr w8, wzr, #0xc
-	madd w11, w2, w8, wzr
-	movz w8, #0xd, lsl 0
-	madd w10, w2, w8, wzr
-	orr w8, wzr, #0xe
-	madd w9, w2, w8, wzr
-	orr w8, wzr, #0xf
 	madd w8, w2, w8, wzr
+	orr w9, wzr, #0x2
+	madd w9, w2, w9, wzr
+	orr w10, wzr, #0x3
+	madd w10, w2, w10, wzr
+	orr w11, wzr, #0x4
+	madd w11, w2, w11, wzr
+	movz w12, #0x5, lsl 0
+	madd w12, w2, w12, wzr
+	orr w13, wzr, #0x6
+	madd w13, w2, w13, wzr
+	orr w14, wzr, #0x7
+	madd w14, w2, w14, wzr
+	orr w15, wzr, #0x8
+	madd w15, w2, w15, wzr
+	movz w16, #0x9, lsl 0
+	madd w16, w2, w16, wzr
+	movz w17, #0xa, lsl 0
+	madd w17, w2, w17, wzr
+	movz w19, #0xb, lsl 0
+	madd w19, w2, w19, wzr
+	orr w20, wzr, #0xc
+	madd w20, w2, w20, wzr
+	movz w21, #0xd, lsl 0
+	madd w21, w2, w21, wzr
+	orr w22, wzr, #0xe
+	madd w22, w2, w22, wzr
+	orr w23, wzr, #0xf
+	madd w23, w2, w23, wzr
 	orr w24, wzr, #0x10
 	madd w24, w2, w24, wzr
 	movz w25, #0x11, lsl 0
@@ -1525,47 +1583,47 @@ L1 (SSA Block: blk0):
 	add w26, w26, w29
 	add w25, w25, w26
 	add w24, w24, w25
-	add w8, w8, w24
-	add w8, w9, w8
-	add w8, w10, w8
-	add w8, w11, w8
-	add w8, w12, w8
-	add w8, w13, w8
-	add w8, w14, w8
-	add w8, w15, w8
-	add w8, w16, w8
-	add w8, w17, w8
-	add w8, w19, w8
-	add w8, w20, w8
-	add w8, w21, w8
-	add w8, w22, w8
-	add w0, w23, w8
+	add w23, w23, w24
+	add w22, w22, w23
+	add w21, w21, w22
+	add w20, w20, w21
+	add w19, w19, w20
+	add w17, w17, w19
+	add w16, w16, w17
+	add w15, w15, w16
+	add w14, w14, w15
+	add w13, w13, w14
+	add w12, w12, w13
+	add w11, w11, w12
+	add w10, w10, w11
+	add w9, w9, w10
+	add w8, w8, w9
 	ldr s8, #8; b 8; data.f32 1.000000
-	fmul s20, s0, s8
-	ldr s8, #8; b 8; data.f32 2.000000
-	fmul s19, s0, s8
-	ldr s8, #8; b 8; data.f32 3.000000
-	fmul s18, s0, s8
-	ldr s8, #8; b 8; data.f32 4.000000
-	fmul s17, s0, s8
-	ldr s8, #8; b 8; data.f32 5.000000
-	fmul s16, s0, s8
-	ldr s8, #8; b 8; data.f32 6.000000
-	fmul s15, s0, s8
-	ldr s8, #8; b 8; data.f32 7.000000
-	fmul s14, s0, s8
-	ldr s8, #8; b 8; data.f32 8.000000
-	fmul s13, s0, s8
-	ldr s8, #8; b 8; data.f32 9.000000
-	fmul s12, s0, s8
-	ldr s8, #8; b 8; data.f32 10.000000
-	fmul s11, s0, s8
-	ldr s8, #8; b 8; data.f32 11.000000
-	fmul s10, s0, s8
-	ldr s8, #8; b 8; data.f32 12.000000
-	fmul s9, s0, s8
-	ldr s8, #8; b 8; data.f32 13.000000
 	fmul s8, s0, s8
+	ldr s9, #8; b 8; data.f32 2.000000
+	fmul s9, s0, s9
+	ldr s10, #8; b 8; data.f32 3.000000
+	fmul s10, s0, s10
+	ldr s11, #8; b 8; data.f32 4.000000
+	fmul s11, s0, s11
+	ldr s12, #8; b 8; data.f32 5.000000
+	fmul s12, s0, s12
+	ldr s13, #8; b 8; data.f32 6.000000
+	fmul s13, s0, s13
+	ldr s14, #8; b 8; data.f32 7.000000
+	fmul s14, s0, s14
+	ldr s15, #8; b 8; data.f32 8.000000
+	fmul s15, s0, s15
+	ldr s16, #8; b 8; data.f32 9.000000
+	fmul s16, s0, s16
+	ldr s17, #8; b 8; data.f32 10.000000
+	fmul s17, s0, s17
+	ldr s18, #8; b 8; data.f32 11.000000
+	fmul s18, s0, s18
+	ldr s19, #8; b 8; data.f32 12.000000
+	fmul s19, s0, s19
+	ldr s20, #8; b 8; data.f32 13.000000
+	fmul s20, s0, s20
 	ldr s21, #8; b 8; data.f32 14.000000
 	fmul s21, s0, s21
 	ldr s22, #8; b 8; data.f32 15.000000
@@ -1586,19 +1644,21 @@ L1 (SSA Block: blk0):
 	fadd s23, s23, s24
 	fadd s22, s22, s23
 	fadd s21, s21, s22
-	fadd s8, s8, s21
-	fadd s8, s9, s8
-	fadd s8, s10, s8
-	fadd s8, s11, s8
-	fadd s8, s12, s8
-	fadd s8, s13, s8
-	fadd s8, s14, s8
-	fadd s8, s15, s8
-	fadd s8, s16, s8
-	fadd s8, s17, s8
-	fadd s8, s18, s8
-	fadd s8, s19, s8
-	fadd s0, s20, s8
+	fadd s20, s20, s21
+	fadd s19, s19, s20
+	fadd s18, s18, s19
+	fadd s17, s17, s18
+	fadd s16, s16, s17
+	fadd s15, s15, s16
+	fadd s14, s14, s15
+	fadd s13, s13, s14
+	fadd s12, s12, s13
+	fadd s11, s11, s12
+	fadd s10, s10, s11
+	fadd s9, s9, s10
+	fadd s8, s8, s9
+	mov v0.8b, v8.8b
+	mov x0, x8
 	add sp, sp, #0x10
 	ldr q27, [sp], #0x10
 	ldr q26, [sp], #0x10
@@ -1644,15 +1704,19 @@ L1 (SSA Block: blk0):
 			afterFinalizeARM64: `
 L1 (SSA Block: blk0):
 	stp x30, xzr, [sp, #-0x10]!
-	str xzr, [sp, #-0x10]!
-	mov x9, x2
+	sub sp, sp, #0x10
+	orr x27, xzr, #0x10
+	str x27, [sp, #-0x10]!
+	str w2, [sp, #0x10]
 	str x1, [x0, #0x8]
 	ldr x8, [x1, #0x8]
-	ldr x1, [x1, #0x10]
-	mov x2, x9
+	ldr x9, [x1, #0x10]
+	mov x1, x9
+	ldr w9, [sp, #0x10]
 	mov x3, x9
 	bl x8
 	add sp, sp, #0x10
+	add sp, sp, #0x10
 	ldr x30, [sp], #0x10
 	ret
 `,
@@ -1689,9 +1753,9 @@ L1 (SSA Block: blk0):
 	stp x30, xzr, [sp, #-0x10]!
 	str xzr, [sp, #-0x10]!
 	uxtw x8, w2
-	ldr w10, [x1, #0x10]
-	add x9, x8, #0x4
-	subs xzr, x10, x9
+	ldr w9, [x1, #0x10]
+	add x10, x8, #0x4
+	subs xzr, x9, x10
 	b.hs #0x34, (L2)
 	movz x9, #0x4, lsl 0
 	str w9, [x0]
@@ -1703,7 +1767,8 @@ L1 (SSA Block: blk0):
 L2:
 	ldr x9, [x1, #0x8]
 	add x8, x9, x8
-	ldr w0, [x8]
+	ldr w8, [x8]
+	mov x0, x8
 	add sp, sp, #0x10
 	ldr x30, [sp], #0x10
 	ret
@@ -1716,27 +1781,27 @@ L1 (SSA Block: blk0):
 	stp x30, xzr, [sp, #-0x10]!
 	str xzr, [sp, #-0x10]!
 	mov x8, xzr
-	uxtw x10, w8
-	ldr w8, [x1, #0x10]
-	add x9, x10, #0x4
-	subs xzr, x8, x9
-	mov x9, x0
+	uxtw x8, w8
+	ldr w9, [x1, #0x10]
+	add x10, x8, #0x4
+	subs xzr, x9, x10
+	mov x10, x0
 	b.hs #0x34, (L10)
 	movz x11, #0x4, lsl 0
-	str w11, [x9]
+	str w11, [x10]
 	mov x11, sp
-	str x11, [x9, #0x38]
+	str x11, [x10, #0x38]
 	adr x11, #0x0
-	str x11, [x9, #0x30]
-	exit_sequence x9
+	str x11, [x10, #0x30]
+	exit_sequence x10
 L10:
-	ldr x9, [x1, #0x8]
-	add x10, x9, x10
-	str w2, [x10]
-	orr w10, wzr, #0x8
-	uxtw x10, w10
-	add x11, x10, #0x8
-	subs xzr, x8, x11
+	ldr x10, [x1, #0x8]
+	add x8, x10, x8
+	str w2, [x8]
+	orr w8, wzr, #0x8
+	uxtw x8, w8
+	add x11, x8, #0x8
+	subs xzr, x9, x11
 	mov x11, x0
 	b.hs #0x34, (L9)
 	movz x12, #0x4, lsl 0
@@ -1747,12 +1812,12 @@ L10:
 	str x12, [x11, #0x30]
 	exit_sequence x11
 L9:
-	add x10, x9, x10
-	str x3, [x10]
-	orr w10, wzr, #0x10
-	uxtw x10, w10
-	add x11, x10, #0x4
-	subs xzr, x8, x11
+	add x8, x10, x8
+	str x3, [x8]
+	orr w8, wzr, #0x10
+	uxtw x8, w8
+	add x11, x8, #0x4
+	subs xzr, x9, x11
 	mov x11, x0
 	b.hs #0x34, (L8)
 	movz x12, #0x4, lsl 0
@@ -1763,12 +1828,12 @@ L9:
 	str x12, [x11, #0x30]
 	exit_sequence x11
 L8:
-	add x10, x9, x10
-	str s0, [x10]
-	orr w10, wzr, #0x18
-	uxtw x10, w10
-	add x11, x10, #0x8
-	subs xzr, x8, x11
+	add x8, x10, x8
+	str s0, [x8]
+	orr w8, wzr, #0x18
+	uxtw x8, w8
+	add x11, x8, #0x8
+	subs xzr, x9, x11
 	mov x11, x0
 	b.hs #0x34, (L7)
 	movz x12, #0x4, lsl 0
@@ -1779,12 +1844,12 @@ L8:
 	str x12, [x11, #0x30]
 	exit_sequence x11
 L7:
-	add x10, x9, x10
-	str d1, [x10]
-	orr w10, wzr, #0x20
-	uxtw x10, w10
-	add x11, x10, #0x1
-	subs xzr, x8, x11
+	add x8, x10, x8
+	str d1, [x8]
+	orr w8, wzr, #0x20
+	uxtw x8, w8
+	add x11, x8, #0x1
+	subs xzr, x9, x11
 	mov x11, x0
 	b.hs #0x34, (L6)
 	movz x12, #0x4, lsl 0
@@ -1795,12 +1860,12 @@ L7:
 	str x12, [x11, #0x30]
 	exit_sequence x11
 L6:
-	add x10, x9, x10
-	strb w2, [x10]
-	movz w10, #0x28, lsl 0
-	uxtw x10, w10
-	add x11, x10, #0x2
-	subs xzr, x8, x11
+	add x8, x10, x8
+	strb w2, [x8]
+	movz w8, #0x28, lsl 0
+	uxtw x8, w8
+	add x11, x8, #0x2
+	subs xzr, x9, x11
 	mov x11, x0
 	b.hs #0x34, (L5)
 	movz x12, #0x4, lsl 0
@@ -1811,12 +1876,12 @@ L6:
 	str x12, [x11, #0x30]
 	exit_sequence x11
 L5:
-	add x10, x9, x10
-	strh w2, [x10]
-	orr w10, wzr, #0x30
-	uxtw x10, w10
-	add x11, x10, #0x1
-	subs xzr, x8, x11
+	add x8, x10, x8
+	strh w2, [x8]
+	orr w8, wzr, #0x30
+	uxtw x8, w8
+	add x11, x8, #0x1
+	subs xzr, x9, x11
 	mov x11, x0
 	b.hs #0x34, (L4)
 	movz x12, #0x4, lsl 0
@@ -1827,12 +1892,12 @@ L5:
 	str x12, [x11, #0x30]
 	exit_sequence x11
 L4:
-	add x10, x9, x10
-	strb w3, [x10]
-	orr w10, wzr, #0x38
-	uxtw x10, w10
-	add x11, x10, #0x2
-	subs xzr, x8, x11
+	add x8, x10, x8
+	strb w3, [x8]
+	orr w8, wzr, #0x38
+	uxtw x8, w8
+	add x11, x8, #0x2
+	subs xzr, x9, x11
 	mov x11, x0
 	b.hs #0x34, (L3)
 	movz x12, #0x4, lsl 0
@@ -1843,22 +1908,22 @@ L4:
 	str x12, [x11, #0x30]
 	exit_sequence x11
 L3:
-	add x10, x9, x10
-	strh w3, [x10]
-	orr w10, wzr, #0x40
-	uxtw x10, w10
-	add x11, x10, #0x4
-	subs xzr, x8, x11
+	add x8, x10, x8
+	strh w3, [x8]
+	orr w8, wzr, #0x40
+	uxtw x8, w8
+	add x11, x8, #0x4
+	subs xzr, x9, x11
 	b.hs #0x34, (L2)
-	movz x8, #0x4, lsl 0
-	str w8, [x0]
-	mov x8, sp
-	str x8, [x0, #0x38]
-	adr x8, #0x0
-	str x8, [x0, #0x30]
+	movz x9, #0x4, lsl 0
+	str w9, [x0]
+	mov x9, sp
+	str x9, [x0, #0x38]
+	adr x9, #0x0
+	str x9, [x0, #0x30]
 	exit_sequence x0
 L2:
-	add x8, x9, x10
+	add x8, x10, x8
 	str w3, [x8]
 	add sp, sp, #0x10
 	ldr x30, [sp], #0x10
@@ -1908,37 +1973,41 @@ L1 (SSA Block: blk0):
 	sub sp, sp, #0x20
 	orr x27, xzr, #0x20
 	str x27, [sp, #-0x10]!
-	mov x10, x1
-	ldr x8, [x10, #0x8]
+	str x1, [sp, #0x10]
+	ldr x8, [x1, #0x8]
 	ldr w8, [x8, #0x8]
-	ldr x9, [x10, #0x10]
+	str w8, [sp, #0x2c]
+	ldr x9, [x1, #0x10]
 	ldr x9, [x9, #0x8]
-	ldr x11, [x10, #0x18]
-	ldr s0, [x11, #0x8]
-	ldr x11, [x10, #0x20]
-	ldr d1, [x11, #0x8]
-	str x10, [x0, #0x8]
-	mov x1, x10
-	str x10, [sp, #0x10]
-	str w8, [sp, #0x18]
-	str x9, [sp, #0x1c]
-	str s0, [sp, #0x24]
-	str d1, [sp, #0x28]
+	str x9, [sp, #0x24]
+	ldr x10, [x1, #0x18]
+	ldr s8, [x10, #0x8]
+	str s8, [sp, #0x20]
+	ldr x10, [x1, #0x20]
+	ldr d9, [x10, #0x8]
+	str d9, [sp, #0x18]
+	str x1, [x0, #0x8]
 	bl f1
-	ldr d1, [sp, #0x28]
-	ldr s0, [sp, #0x24]
-	ldr x9, [sp, #0x1c]
-	ldr w8, [sp, #0x18]
-	ldr x10, [sp, #0x10]
-	ldr x11, [x10, #0x8]
-	ldr w2, [x11, #0x8]
-	ldr x11, [x10, #0x10]
-	ldr x3, [x11, #0x8]
-	ldr x11, [x10, #0x18]
-	ldr s2, [x11, #0x8]
-	ldr x10, [x10, #0x20]
-	ldr d3, [x10, #0x8]
-	mov x1, x9
+	ldr x8, [sp, #0x10]
+	ldr x9, [x8, #0x8]
+	ldr w9, [x9, #0x8]
+	ldr x10, [x8, #0x10]
+	ldr x10, [x10, #0x8]
+	ldr x11, [x8, #0x18]
+	ldr s8, [x11, #0x8]
+	ldr x8, [x8, #0x20]
+	ldr d9, [x8, #0x8]
+	mov v3.8b, v9.8b
+	mov v2.8b, v8.8b
+	mov x3, x10
+	mov x2, x9
+	ldr d8, [sp, #0x18]
+	mov v1.8b, v8.8b
+	ldr s8, [sp, #0x20]
+	mov v0.8b, v8.8b
+	ldr x8, [sp, #0x24]
+	mov x1, x8
+	ldr w8, [sp, #0x2c]
 	mov x0, x8
 	add sp, sp, #0x10
 	add sp, sp, #0x20
@@ -1971,12 +2040,12 @@ L1 (SSA Block: blk0):
 L1 (SSA Block: blk0):
 	stp x30, xzr, [sp, #-0x10]!
 	str xzr, [sp, #-0x10]!
-	ldr x9, [x1, #0x8]
-	orr w8, wzr, #0x1
-	str w8, [x9, #0x8]
-	ldr x9, [x1, #0x10]
-	orr x8, xzr, #0x2
-	str x8, [x9, #0x8]
+	ldr x8, [x1, #0x8]
+	orr w9, wzr, #0x1
+	str w9, [x8, #0x8]
+	ldr x8, [x1, #0x10]
+	orr x9, xzr, #0x2
+	str x9, [x8, #0x8]
 	ldr x8, [x1, #0x18]
 	ldr s8, #8; b 8; data.f32 3.000000
 	str s8, [x8, #0x8]
@@ -2039,42 +2108,48 @@ L1 (SSA Block: blk0):
 	orr w8, wzr, #0x6
 	subs wzr, w2, w8
 	csel w8, w8, w2, hs
-	adr x27, #16; ldrsw x8, [x27, x8, UXTW 2]; add x27, x27, x8; br x27; [0x1c 0x20 0x30 0x40 0x50 0x60 0x70]
+	adr x27, #16; ldrsw x8, [x27, x8, UXTW 2]; add x27, x27, x8; br x27; [0x1c 0x20 0x34 0x48 0x5c 0x70 0x84]
 L2 (SSA Block: blk7):
-	b #0x54 (L9)
+	b #0x68 (L9)
 L3 (SSA Block: blk8):
 L10 (SSA Block: blk5):
-	orr w0, wzr, #0xc
+	orr w8, wzr, #0xc
+	mov x0, x8
 	add sp, sp, #0x10
 	ldr x30, [sp], #0x10
 	ret
 L4 (SSA Block: blk9):
 L11 (SSA Block: blk4):
-	movz w0, #0xd, lsl 0
+	movz w8, #0xd, lsl 0
+	mov x0, x8
 	add sp, sp, #0x10
 	ldr x30, [sp], #0x10
 	ret
 L5 (SSA Block: blk10):
 L12 (SSA Block: blk3):
-	orr w0, wzr, #0xe
+	orr w8, wzr, #0xe
+	mov x0, x8
 	add sp, sp, #0x10
 	ldr x30, [sp], #0x10
 	ret
 L6 (SSA Block: blk11):
 L13 (SSA Block: blk2):
-	orr w0, wzr, #0xf
+	orr w8, wzr, #0xf
+	mov x0, x8
 	add sp, sp, #0x10
 	ldr x30, [sp], #0x10
 	ret
 L7 (SSA Block: blk12):
 L14 (SSA Block: blk1):
-	orr w0, wzr, #0x10
+	orr w8, wzr, #0x10
+	mov x0, x8
 	add sp, sp, #0x10
 	ldr x30, [sp], #0x10
 	ret
 L8 (SSA Block: blk13):
 L9 (SSA Block: blk6):
-	movz w0, #0xb, lsl 0
+	movz w8, #0xb, lsl 0
+	mov x0, x8
 	add sp, sp, #0x10
 	ldr x30, [sp], #0x10
 	ret
@@ -2086,12 +2161,18 @@ L9 (SSA Block: blk6):
 			afterFinalizeARM64: `
 L1 (SSA Block: blk0):
 	stp x30, xzr, [sp, #-0x10]!
-	str xzr, [sp, #-0x10]!
+	str q29, [sp, #-0x10]!
+	str q30, [sp, #-0x10]!
+	orr x27, xzr, #0x20
+	str x27, [sp, #-0x10]!
 	mov v29.16b, v0.16b
 	mov v30.16b, v1.16b
 	ldr q8, #8; b 32; data.v128  0706050403020100 1f1e1d1c1b1a1918
-	tbl v0.16b, { v29.16b, v30.16b }, v8.16b
+	tbl v8.16b, { v29.16b, v30.16b }, v8.16b
+	mov v0.16b, v8.16b
 	add sp, sp, #0x10
+	ldr q30, [sp], #0x10
+	ldr q29, [sp], #0x10
 	ldr x30, [sp], #0x10
 	ret
 `,
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 20265a94..58242bf6 100644
--- a/internal/engine/wazevo/backend/isa/arm64/instr_encoding_test.go
+++ b/internal/engine/wazevo/backend/isa/arm64/instr_encoding_test.go
@@ -140,10 +140,10 @@ func TestInstruction_encode(t *testing.T) {
 			i.asVecPermute(vecOpZip1, operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), vecArrangement2D)
 		}},
 		{want: "411ca32e", setup: func(i *instruction) {
-			i.asVecRRR(vecOpBit, operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), vecArrangement8B)
+			i.asVecRRRRewrite(vecOpBit, operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), vecArrangement8B)
 		}},
 		{want: "411ca36e", setup: func(i *instruction) {
-			i.asVecRRR(vecOpBit, operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), vecArrangement16B)
+			i.asVecRRRRewrite(vecOpBit, operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), vecArrangement16B)
 		}},
 		{want: "411c236e", setup: func(i *instruction) {
 			i.asVecRRR(vecOpEOR, operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), vecArrangement16B)
@@ -1495,10 +1495,10 @@ func TestInstruction_encode(t *testing.T) {
 			i.asVecRRR(vecOpBic, operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), vecArrangement16B)
 		}},
 		{want: "411c632e", setup: func(i *instruction) {
-			i.asVecRRR(vecOpBsl, operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), vecArrangement8B)
+			i.asVecRRRRewrite(vecOpBsl, operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), vecArrangement8B)
 		}},
 		{want: "411c636e", setup: func(i *instruction) {
-			i.asVecRRR(vecOpBsl, operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), vecArrangement16B)
+			i.asVecRRRRewrite(vecOpBsl, operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), vecArrangement16B)
 		}},
 		{want: "4158202e", setup: func(i *instruction) {
 			i.asVecMisc(vecOpNot, operandNR(v1VReg), operandNR(v2VReg), vecArrangement8B)
diff --git a/internal/engine/wazevo/backend/isa/arm64/instr_test.go b/internal/engine/wazevo/backend/isa/arm64/instr_test.go
index 65d27287..6e4ec7ce 100644
--- a/internal/engine/wazevo/backend/isa/arm64/instr_test.go
+++ b/internal/engine/wazevo/backend/isa/arm64/instr_test.go
@@ -69,8 +69,8 @@ func TestInstruction_String(t *testing.T) {
 }
 
 func TestInstruction_isCopy(t *testing.T) {
-	require.False(t, (&instruction{kind: mov32}).isCopy())
-	require.True(t, (&instruction{kind: mov64}).isCopy())
-	require.True(t, (&instruction{kind: fpuMov64}).isCopy())
-	require.True(t, (&instruction{kind: fpuMov128}).isCopy())
+	require.False(t, (&instruction{kind: mov32}).IsCopy())
+	require.True(t, (&instruction{kind: mov64}).IsCopy())
+	require.True(t, (&instruction{kind: fpuMov64}).IsCopy())
+	require.True(t, (&instruction{kind: fpuMov128}).IsCopy())
 }
diff --git a/internal/engine/wazevo/backend/isa/arm64/lower_instr_test.go b/internal/engine/wazevo/backend/isa/arm64/lower_instr_test.go
index 2f969341..e3f97023 100644
--- a/internal/engine/wazevo/backend/isa/arm64/lower_instr_test.go
+++ b/internal/engine/wazevo/backend/isa/arm64/lower_instr_test.go
@@ -379,7 +379,7 @@ L2:
 				regalloc.VReg(2).SetRegType(regalloc.RegTypeInt),
 				regalloc.VReg(3).SetRegType(regalloc.RegTypeInt)
 			mc, _, m := newSetupWithMockContext()
-			mc.typeOf = map[regalloc.VReg]ssa.Type{execCtx: ssa.TypeI64, 2: ssa.TypeI64, 3: ssa.TypeI64}
+			mc.typeOf = map[regalloc.VRegID]ssa.Type{execCtx.ID(): ssa.TypeI64, 2: ssa.TypeI64, 3: ssa.TypeI64}
 			m.lowerIDiv(execCtx, operandNR(rd), operandNR(rn), operandNR(rm), tc._64bit, tc.signed)
 			require.Equal(t, tc.exp, "\n"+formatEmittedInstructionsInCurrentBlock(m)+"\n")
 		})
@@ -450,7 +450,7 @@ fcvtzu w1, s2
 	} {
 		t.Run(tc.name, func(t *testing.T) {
 			mc, _, m := newSetupWithMockContext()
-			mc.typeOf = map[regalloc.VReg]ssa.Type{v2VReg: ssa.TypeI64, x15VReg: ssa.TypeI64}
+			mc.typeOf = map[regalloc.VRegID]ssa.Type{v2VReg.ID(): ssa.TypeI64, x15VReg.ID(): ssa.TypeI64}
 			m.lowerFpuToInt(operandNR(x1VReg), operandNR(v2VReg), x15VReg, false, false, false, tc.nontrapping)
 			require.Equal(t, tc.expectedAsm, "\n"+formatEmittedInstructionsInCurrentBlock(m)+"\n")
 
@@ -476,10 +476,11 @@ mul v2?.4s, v2?.4s, x2.4s
 xtn v1?.2s, x2.2s
 addp v2?.4s, v2?.4s, v2?.4s
 xtn v3?.2s, x15.2s
-shll x1.2s, v2?.2s
-umlal x1.2s, v3?.2s, v1?.2s
+shll v4?.2s, v2?.2s
+umlal v4?.2s, v3?.2s, v1?.2s
+mov x1.16b, v4?.16b
 `,
-			expectedBytes: "e009a04e009ca24e4028a10e00bca04ee029a10e0138a12e0180a02e",
+			expectedBytes: "e009a04e009ca24e4028a10e00bca04ee029a10e0038a12e0080a02e011ca04e",
 		},
 		{
 			name:        "8B",
@@ -855,8 +856,9 @@ func TestMachine_lowerSelectVec(t *testing.T) {
 	require.Equal(t, `
 subs wzr, w1?, wzr
 csetm x5?, ne
-dup v4?.2d, x5?
-bsl v4?.16b, v2?.16b, v3?.16b
+dup v6?.2d, x5?
+bsl v6?.16b, v2?.16b, v3?.16b
+mov v4?.16b, v6?.16b
 `, "\n"+formatEmittedInstructionsInCurrentBlock(m)+"\n")
 }
 
diff --git a/internal/engine/wazevo/backend/isa/arm64/machine_regalloc_test.go b/internal/engine/wazevo/backend/isa/arm64/machine_regalloc_test.go
index 389dc73e..0b7e53ff 100644
--- a/internal/engine/wazevo/backend/isa/arm64/machine_regalloc_test.go
+++ b/internal/engine/wazevo/backend/isa/arm64/machine_regalloc_test.go
@@ -23,8 +23,6 @@ func TestRegAllocFunctionImpl_addBlock(t *testing.T) {
 	rb1, rb2 := &f.reversePostOrderBlocks[0], &f.reversePostOrderBlocks[1]
 	require.Equal(t, f, rb1.f)
 	require.Equal(t, f, rb2.f)
-	require.Equal(t, f, rb1.instrImpl.f)
-	require.Equal(t, f, rb2.instrImpl.f)
 
 	require.Equal(t, p1, rb1.pos)
 	require.Equal(t, p2, rb2.pos)
@@ -63,14 +61,14 @@ func TestRegAllocFunctionImpl_ReversePostOrderBlockIterator(t *testing.T) {
 func TestRegAllocFunctionImpl_ReloadRegisterAfter(t *testing.T) {
 	ctx, _, m := newSetupWithMockContext()
 
-	ctx.typeOf = map[regalloc.VReg]ssa.Type{x1VReg: ssa.TypeI64, v1VReg: ssa.TypeF64}
+	ctx.typeOf = map[regalloc.VRegID]ssa.Type{x1VReg.ID(): ssa.TypeI64, v1VReg.ID(): ssa.TypeF64}
 	i1, i2 := m.allocateNop(), m.allocateNop()
 	i1.next = i2
 	i2.prev = i1
 
 	f := &regAllocFunctionImpl{m: m}
-	f.ReloadRegisterAfter(x1VReg, &regAllocInstrImpl{i: i1})
-	f.ReloadRegisterAfter(v1VReg, &regAllocInstrImpl{i: i1})
+	f.ReloadRegisterAfter(x1VReg, i1)
+	f.ReloadRegisterAfter(v1VReg, i1)
 
 	require.NotEqual(t, i1, i2.prev)
 	require.NotEqual(t, i1.next, i2)
@@ -93,14 +91,14 @@ func TestRegAllocFunctionImpl_ReloadRegisterAfter(t *testing.T) {
 func TestRegAllocFunctionImpl_StoreRegisterBefore(t *testing.T) {
 	ctx, _, m := newSetupWithMockContext()
 
-	ctx.typeOf = map[regalloc.VReg]ssa.Type{x1VReg: ssa.TypeI64, v1VReg: ssa.TypeF64}
+	ctx.typeOf = map[regalloc.VRegID]ssa.Type{x1VReg.ID(): ssa.TypeI64, v1VReg.ID(): ssa.TypeF64}
 	i1, i2 := m.allocateNop(), m.allocateNop()
 	i1.next = i2
 	i2.prev = i1
 
 	f := &regAllocFunctionImpl{m: m}
-	f.StoreRegisterBefore(x1VReg, &regAllocInstrImpl{i: i2})
-	f.StoreRegisterBefore(v1VReg, &regAllocInstrImpl{i: i2})
+	f.StoreRegisterBefore(x1VReg, i2)
+	f.StoreRegisterBefore(v1VReg, i2)
 
 	require.NotEqual(t, i1, i2.prev)
 	require.NotEqual(t, i1.next, i2)
@@ -173,7 +171,7 @@ func TestMachine_insertStoreRegisterAt(t *testing.T) {
 					name = "before"
 				}
 				t.Run(name, func(t *testing.T) {
-					ctx.typeOf = map[regalloc.VReg]ssa.Type{x1VReg: ssa.TypeI64, v1VReg: ssa.TypeF64}
+					ctx.typeOf = map[regalloc.VRegID]ssa.Type{x1VReg.ID(): ssa.TypeI64, v1VReg.ID(): ssa.TypeF64}
 					i1, i2 := m.allocateInstr().asUDF(), m.allocateInstr().asExitSequence(x30VReg)
 					i1.next = i2
 					i2.prev = i1
@@ -246,7 +244,7 @@ func TestMachine_insertReloadRegisterAt(t *testing.T) {
 					name = "before"
 				}
 				t.Run(name, func(t *testing.T) {
-					ctx.typeOf = map[regalloc.VReg]ssa.Type{x1VReg: ssa.TypeI64, v1VReg: ssa.TypeF64}
+					ctx.typeOf = map[regalloc.VRegID]ssa.Type{x1VReg.ID(): ssa.TypeI64, v1VReg.ID(): ssa.TypeF64}
 					i1, i2 := m.allocateInstr().asUDF(), m.allocateInstr().asExitSequence(x30VReg)
 					i1.next = i2
 					i2.prev = i1
@@ -273,3 +271,76 @@ func TestRegAllocFunctionImpl_ClobberedRegisters(t *testing.T) {
 	f.ClobberedRegisters([]regalloc.VReg{v19VReg, v19VReg, v19VReg, v19VReg})
 	require.Equal(t, []regalloc.VReg{v19VReg, v19VReg, v19VReg, v19VReg}, m.clobberedRegs)
 }
+
+func TestMachine_swap(t *testing.T) {
+	for _, tc := range []struct {
+		x1, x2, tmp regalloc.VReg
+		expected    string
+	}{
+		{
+			x1:  x18VReg,
+			x2:  x19VReg,
+			tmp: x20VReg,
+			expected: `
+	udf
+	mov x20, x18
+	mov x18, x19
+	mov x19, x20
+	exit_sequence x30
+`,
+		},
+		{
+			x1: x18VReg,
+			x2: x19VReg,
+			// Tmp not given.
+			expected: `
+	udf
+	mov x27, x18
+	mov x18, x19
+	mov x19, x27
+	exit_sequence x30
+`,
+		},
+		{
+			x1:  v18VReg,
+			x2:  v19VReg,
+			tmp: v11VReg,
+			expected: `
+	udf
+	mov v11.16b, v18.16b
+	mov v18.16b, v19.16b
+	mov v19.16b, v11.16b
+	exit_sequence x30
+`,
+		},
+		{
+			x1: v18VReg,
+			x2: v19VReg,
+			// Tmp not given.
+			expected: `
+	udf
+	str d18, [sp, #0x10]
+	mov v18.16b, v19.16b
+	ldr d19, [sp, #0x10]
+	exit_sequence x30
+`,
+		},
+	} {
+		t.Run(tc.expected, func(t *testing.T) {
+			ctx, _, m := newSetupWithMockContext()
+
+			ctx.typeOf = map[regalloc.VRegID]ssa.Type{
+				x18VReg.ID(): ssa.TypeI64, x19VReg.ID(): ssa.TypeI64,
+				v18VReg.ID(): ssa.TypeF64, v19VReg.ID(): ssa.TypeF64,
+			}
+			cur, i2 := m.allocateInstr().asUDF(), m.allocateInstr().asExitSequence(x30VReg)
+			cur.next = i2
+			i2.prev = cur
+
+			m.swap(cur, tc.x1, tc.x2, tc.tmp)
+			m.rootInstr = cur
+
+			require.Equal(t, tc.expected, m.Format())
+		})
+	}
+}
diff --git a/internal/engine/wazevo/backend/isa/arm64/util_test.go b/internal/engine/wazevo/backend/isa/arm64/util_test.go
index 20f142a2..77f51de1 100644
--- a/internal/engine/wazevo/backend/isa/arm64/util_test.go
+++ b/internal/engine/wazevo/backend/isa/arm64/util_test.go
@@ -56,11 +56,13 @@ type mockCompiler struct {
 	vRegMap     map[ssa.Value]regalloc.VReg
 	definitions map[ssa.Value]*backend.SSAValueDefinition
 	sigs        map[ssa.SignatureID]*ssa.Signature
-	typeOf      map[regalloc.VReg]ssa.Type
+	typeOf      map[regalloc.VRegID]ssa.Type
 	relocs      []backend.RelocationInfo
 	buf         []byte
 }
 
+func (m *mockCompiler) SSABuilder() ssa.Builder { panic("TODO") }
+
 func (m *mockCompiler) LoopNestingForestRoots() []ssa.BasicBlock { panic("TODO") }
 
 func (m *mockCompiler) SourceOffsetInfo() []backend.SourceOffsetInfo { return nil }
@@ -78,7 +80,7 @@ func (m *mockCompiler) Emit4Bytes(b uint32) {
 func (m *mockCompiler) Encode()     {}
 func (m *mockCompiler) Buf() []byte { return m.buf }
 func (m *mockCompiler) TypeOf(v regalloc.VReg) (ret ssa.Type) {
-	return m.typeOf[v]
+	return m.typeOf[v.ID()]
 }
 func (m *mockCompiler) Finalize()      {}
 func (m *mockCompiler) RegAlloc()      {}
@@ -90,7 +92,7 @@ func newMockCompilationContext() *mockCompiler {
 	return &mockCompiler{
 		vRegMap:     make(map[ssa.Value]regalloc.VReg),
 		definitions: make(map[ssa.Value]*backend.SSAValueDefinition),
-		typeOf:      map[regalloc.VReg]ssa.Type{},
+		typeOf:      map[regalloc.VRegID]ssa.Type{},
 	}
 }
 
@@ -104,7 +106,7 @@ func (m *mockCompiler) AllocateVReg(typ ssa.Type) regalloc.VReg {
 	m.vRegCounter++
 	regType := regalloc.RegTypeOf(typ)
 	ret := regalloc.VReg(m.vRegCounter).SetRegType(regType)
-	m.typeOf[ret] = typ
+	m.typeOf[ret.ID()] = typ
 	return ret
 }
 
diff --git a/internal/engine/wazevo/backend/regalloc/api_test.go b/internal/engine/wazevo/backend/regalloc/api_test.go
index b085e1f8..6fd25e69 100644
--- a/internal/engine/wazevo/backend/regalloc/api_test.go
+++ b/internal/engine/wazevo/backend/regalloc/api_test.go
@@ -41,6 +41,14 @@ type (
 	}
 )
 
+func (m *mockFunction) LowestCommonAncestor(blk1, blk2 Block) Block { panic("TODO") }
+
+func (m *mockFunction) Idom(blk Block) Block { panic("TODO") }
+
+func (m *mockFunction) SwapAtEndOfBlock(x1, x2, tmp VReg, block Block) { panic("TODO") }
+
+func (m *mockFunction) InsertMoveBefore(dst, src VReg, instr Instr) { panic("TODO") }
+
 func newMockFunction(blocks ...*mockBlock) *mockFunction {
 	return &mockFunction{blocks: blocks}
 }
@@ -113,12 +121,12 @@ func (m *mockInstr) asCopy() *mockInstr {
 	return m
 }
 
-func (m *mockInstr) asCall() *mockInstr {
+func (m *mockInstr) asCall() *mockInstr { //nolint:unused
 	m.isCall = true
 	return m
 }
 
-func (m *mockInstr) asIndirectCall() *mockInstr {
+func (m *mockInstr) asIndirectCall() *mockInstr { //nolint:unused
 	m.isIndirect = true
 	return m
 }
@@ -234,7 +242,10 @@ func (m *mockBlock) Preds() int {
 }
 
 // BlockParams implements Block.
-func (m *mockBlock) BlockParams() []VReg { return m.blockParams }
+func (m *mockBlock) BlockParams(ret *[]VReg) []VReg {
+	*ret = append((*ret)[:0], m.blockParams...)
+	return *ret
+}
 
 func (m *mockBlock) blockParam(v VReg) {
 	m.blockParams = append(m.blockParams, v)
@@ -244,13 +255,15 @@ func (m *mockBlock) blockParam(v VReg) {
 func (m *mockBlock) Pred(i int) Block { return m._preds[i] }
 
 // Defs implements Instr.
-func (m *mockInstr) Defs() []VReg {
-	return m.defs
+func (m *mockInstr) Defs(ret *[]VReg) []VReg {
+	*ret = append((*ret)[:0], m.defs...)
+	return *ret
 }
 
 // Uses implements Instr.
-func (m *mockInstr) Uses() []VReg {
-	return m.uses
+func (m *mockInstr) Uses(ret *[]VReg) []VReg {
+	*ret = append((*ret)[:0], m.uses...)
+	return *ret
 }
 
 // IsCopy implements Instr.
@@ -314,3 +327,11 @@ func (m *mockBlock) LoopNestingForestChildren() int {
 func (m *mockBlock) LoopNestingForestChild(i int) Block {
 	return m.lnfChildren[i]
 }
+
+func (m *mockBlock) LastInstr() Instr {
+	return m.instructions[len(m.instructions)-1]
+}
+
+func (m *mockBlock) FirstInstr() Instr {
+	return m.instructions[0]
+}
diff --git a/internal/engine/wazevo/backend/regalloc/assign_test.go b/internal/engine/wazevo/backend/regalloc/assign_test.go
deleted file mode 100644
index cb5285d4..00000000
--- a/internal/engine/wazevo/backend/regalloc/assign_test.go
+++ /dev/null
@@ -1,332 +0,0 @@
-package regalloc
-
-import (
-	"testing"
-
-	"github.com/tetratelabs/wazero/internal/testing/require"
-)
-
-func TestAllocator_assignRegistersPerInstr(t *testing.T) {
-	t.Run("call", func(t *testing.T) {
-		a := NewAllocator(&RegisterInfo{CallerSavedRegisters: [RealRegsNumMax]bool{1: true, 3: true}})
-		pc := programCounter(5)
-
-		manager := newIntervalManager()
-		liveNodes := []*node{
-			{r: 1, v: 0xa},
-			{r: RealRegInvalid, v: 0xb},           // Spill. not save target.
-			{r: 2, v: FromRealReg(1, RegTypeInt)}, // Real reg-backed VReg. not save target
-			{r: 3, v: 0xc},
-			{r: 4, v: 0xd}, // real reg, but not caller saved. not save target
-		}
-		for _, n := range liveNodes {
-			manager.insert(n, 5, 20)
-		}
-		manager.build()
-		call := newMockInstr().asCall()
-		blk := newMockBlock(0, call).entry()
-		f := newMockFunction(blk)
-		a.assignRegistersPerInstr(f, pc, call, nil, manager)
-
-		require.Equal(t, 2, len(f.befores))
-		require.Equal(t, 2, len(f.afters))
-	})
-	t.Run("call_indirect/func_ptr not spilled", func(t *testing.T) {
-		a := NewAllocator(&RegisterInfo{CallerSavedRegisters: [RealRegsNumMax]bool{1: true, 3: true, 0xf: true}})
-		pc := programCounter(5)
-		functionPtrVRegID := 0x0
-		functionPtrVReg := VReg(functionPtrVRegID).SetRegType(RegTypeInt)
-		functionPtrLiveNode := &node{r: 0xf, v: functionPtrVReg}
-		manager := newIntervalManager()
-		manager.insert(functionPtrLiveNode, 4, pc)
-		liveNodes := []*node{
-			{r: 1, v: 0xa},
-			{r: 2, v: FromRealReg(1, RegTypeInt)}, // Real reg-backed VReg. not target
-			{r: 3, v: 0xc},
-			{r: 4, v: 0xd}, // real reg, but not caller saved. not save target
-		}
-		for _, n := range liveNodes {
-			manager.insert(n, 5, 20)
-		}
-		manager.build()
-		callInd := newMockInstr().asIndirectCall().use(functionPtrVReg)
-		blk := newMockBlock(0, callInd).entry()
-		f := newMockFunction(blk)
-		a.assignRegistersPerInstr(f, pc, callInd, []*node{0: functionPtrLiveNode}, manager)
-
-		require.Equal(t, 2, len(f.befores))
-		require.Equal(t, 2, len(f.afters))
-		require.True(t, callInd.uses[0].IsRealReg())
-		require.Equal(t, functionPtrVReg.SetRealReg(0xf), callInd.uses[0])
-	})
-	t.Run("call_indirect/func_ptr spilled", func(t *testing.T) {
-		a := NewAllocator(&RegisterInfo{
-			CallerSavedRegisters: [RealRegsNumMax]bool{1: true, 3: true, 0xb: true},
-			AllocatableRegisters: [3][]RealReg{RegTypeInt: {0xf, 0xb}},
-		})
-		pc := programCounter(5)
-		functionPtrVRegID := 0x0
-		functionPtrVReg := VReg(functionPtrVRegID).SetRegType(RegTypeInt)
-		liveNodes := []*node{
-			{r: 1, v: 0xa},
-			{r: 2, v: FromRealReg(1, RegTypeInt)}, // Real reg-backed VReg. not target
-			{r: 3, v: 0xc},
-			{r: 4, v: 0xd}, // real reg, but not caller saved. not save target
-		}
-		manager := newIntervalManager()
-		for _, n := range liveNodes {
-			manager.insert(n, 5, 20)
-		}
-		manager.build()
-		callInd := newMockInstr().asIndirectCall().use(functionPtrVReg)
-		blk := newMockBlock(0, callInd).entry()
-		f := newMockFunction(blk)
-		a.assignRegistersPerInstr(f, pc, callInd, []*node{
-			0: {r: RealRegInvalid},
-		}, manager)
-
-		require.Equal(t, 3, len(f.befores))
-		require.Equal(t, 2, len(f.afters))
-		require.Equal(t, callInd, f.befores[2].instr)
-		require.Equal(t, functionPtrVReg.SetRealReg(0xb), f.befores[2].v)
-		require.True(t, callInd.uses[0].IsRealReg())
-		require.Equal(t, functionPtrVReg.SetRealReg(0xb), callInd.uses[0])
-	})
-	t.Run("no spills", func(t *testing.T) {
-		r := FromRealReg(1, RegTypeInt)
-		a := NewAllocator(&RegisterInfo{})
-		instr := newMockInstr().def(4).use(r, 2, 3)
-		blk := newMockBlock(0, instr).entry()
-		f := newMockFunction(blk)
-
-		a.assignRegistersPerInstr(f, 0, instr, []*node{
-			2: {r: 22},
-			3: {r: 33},
-			4: {r: 44},
-		}, nil)
-
-		require.Equal(t, []VReg{r, VReg(2).SetRealReg(22), VReg(3).SetRealReg(33)}, instr.uses)
-		require.Equal(t, []VReg{VReg(4).SetRealReg(44)}, instr.defs)
-	})
-}
-
-func TestAllocator_handleSpills(t *testing.T) {
-	requireInsertedInst := func(t *testing.T, f *mockFunction, before bool, index int, instr Instr, reload bool, v VReg) {
-		lists := f.afters
-		if before {
-			lists = f.befores
-		}
-		actual := lists[index]
-		require.Equal(t, instr, actual.instr)
-		require.Equal(t, reload, actual.reload)
-		require.Equal(t, v, actual.v)
-	}
-
-	t.Run("no spills", func(t *testing.T) {
-		a := NewAllocator(&RegisterInfo{})
-		a.handleSpills(nil, 0, nil, nil, VRegInvalid, nil)
-	})
-	t.Run("only def / evicted / Real reg backed VReg", func(t *testing.T) {
-		const pc = 5
-		liveNodes := []*node{
-			// Real reg backed VReg.
-			{r: RealRegInvalid, v: VReg(1).SetRealReg(0xa)},
-			{r: RealReg(0xb), v: 0xa},
-			{r: RealReg(0xc), v: 0xc},
-		}
-		manager := newIntervalManager()
-		for _, n := range liveNodes {
-			manager.insert(n, pc, 20)
-		}
-		manager.build()
-		a := NewAllocator(&RegisterInfo{
-			AllocatableRegisters: [3][]RealReg{RegTypeInt: {0xa, 0xb, 0xc}}, // Only live nodes are allocatable.
-		})
-
-		f := newMockFunction(newMockBlock(0).entry())
-
-		vr := VReg(100).SetRegType(RegTypeInt)
-		instr := newMockInstr().def(vr)
-		a.handleSpills(f, pc, instr, nil, vr, manager)
-		require.Equal(t, 1, len(instr.defs))
-		require.Equal(t, RealReg(0xa), instr.defs[0].RealReg())
-
-		require.Equal(t, 1, len(f.befores))
-		requireInsertedInst(t, f, true, 0, instr, false, liveNodes[0].v)
-
-		require.Equal(t, 2, len(f.afters))
-		requireInsertedInst(t, f, false, 0, instr, true, liveNodes[0].v)
-		requireInsertedInst(t, f, false, 1, instr, false, vr.SetRealReg(0xa))
-	})
-
-	t.Run("only def / evicted", func(t *testing.T) {
-		const pc = 5
-		liveNodes := []*node{
-			{r: RealReg(0xb), v: 0xa},
-			{r: RealReg(0xc), v: 0xc},
-		}
-		manager := newIntervalManager()
-		for _, n := range liveNodes {
-			manager.insert(n, pc, 20)
-		}
-		manager.build()
-		a := NewAllocator(&RegisterInfo{
-			AllocatableRegisters: [3][]RealReg{RegTypeInt: {0xb, 0xc}}, // Only live nodes are allocatable.
-		})
-
-		f := newMockFunction(newMockBlock(0).entry())
-
-		vr := VReg(100).SetRegType(RegTypeInt)
-		instr := newMockInstr().def(vr)
-		a.handleSpills(f, pc, instr, nil, vr, manager)
-		require.Equal(t, 1, len(instr.defs))
-		require.Equal(t, RealReg(0xb), instr.defs[0].RealReg())
-
-		require.Equal(t, 1, len(f.befores))
-		requireInsertedInst(t, f, true, 0, instr, false, liveNodes[0].v.SetRealReg(0xb))
-
-		require.Equal(t, 2, len(f.afters))
-		requireInsertedInst(t, f, false, 0, instr, true, liveNodes[0].v.SetRealReg(0xb))
-		requireInsertedInst(t, f, false, 1, instr, false, vr.SetRealReg(0xb))
-	})
-
-	t.Run("only def / not evicted", func(t *testing.T) {
-		const pc = 5
-		liveNodes := []*node{
-			{r: RealReg(0xb), v: 0xa},
-			{r: RealReg(0xc), v: 0xc},
-		}
-
-		manager := newIntervalManager()
-		for _, n := range liveNodes {
-			manager.insert(n, pc, 20)
-		}
-		manager.build()
-		a := NewAllocator(&RegisterInfo{
-			AllocatableRegisters: [3][]RealReg{RegTypeInt: {0xb, 0xc, 0xf /* free */}},
-		})
-
-		f := newMockFunction(newMockBlock(0).entry())
-
-		vr := VReg(100).SetRegType(RegTypeInt)
-		instr := newMockInstr().def(vr)
-		a.handleSpills(f, pc, instr, nil, vr, manager)
-		require.Equal(t, 1, len(instr.defs))
-		require.Equal(t, RealReg(0xf), instr.defs[0].RealReg())
-
-		require.Equal(t, 0, len(f.befores))
-		require.Equal(t, 1, len(f.afters))
-		requireInsertedInst(t, f, false, 0, instr, false, vr.SetRealReg(0xf))
-	})
-
-	t.Run("uses and def / not evicted / def same type", func(t *testing.T) {
-		const pc = 5
-		liveNodes := []*node{
-			{r: RealReg(0xb), v: 0xa},
-			{r: RealReg(0xc), v: 0xc},
-		}
-
-		manager := newIntervalManager()
-		for _, n := range liveNodes {
-			manager.insert(n, pc, 20)
-		}
-		manager.build()
-		a := NewAllocator(&RegisterInfo{
-			AllocatableRegisters: [3][]RealReg{
-				RegTypeInt:   {0xb, 0xc, 0xa /* free */},
-				RegTypeFloat: {0xf /* free */},
-			},
-		})
-
-		f := newMockFunction(newMockBlock(0).entry())
-		u1, u2, u3 := VReg(100).SetRegType(RegTypeInt),
-			VReg(101).SetRegType(RegTypeInt).SetRealReg(0x88), // This one isn't spilled.
-			VReg(102).SetRegType(RegTypeFloat)
-		d1 := VReg(104).SetRegType(RegTypeFloat)
-		instr := newMockInstr().use(u1, u2, u3).def(d1)
-		a.handleSpills(f, pc, instr, []VReg{u1, u3}, d1, manager)
-		require.Equal(t, []VReg{u1.SetRealReg(0xa), u2, u3.SetRealReg(0xf)}, instr.uses)
-		require.Equal(t, []VReg{d1.SetRealReg(0xf)}, instr.defs)
-
-		require.Equal(t, 2, len(f.befores))
-		requireInsertedInst(t, f, true, 0, instr, true, u1.SetRealReg(0xa))
-		requireInsertedInst(t, f, true, 1, instr, true, u3.SetRealReg(0xf))
-
-		require.Equal(t, 1, len(f.afters))
-		requireInsertedInst(t, f, false, 0, instr, false, d1.SetRealReg(0xf))
-	})
-
-	t.Run("uses and def / not evicted / def different type", func(t *testing.T) {
-		const pc = 5
-
-		liveNodes := []*node{
-			{r: RealReg(0xb), v: 0xa},
-			{r: RealReg(0xf), v: 0xb},
-		}
-
-		manager := newIntervalManager()
-		for _, n := range liveNodes {
-			manager.insert(n, pc, 20)
-		}
-		manager.build()
-		a := NewAllocator(&RegisterInfo{
-			AllocatableRegisters: [3][]RealReg{
-				RegTypeInt:   {0xb, 0xa /* free */},
-				RegTypeFloat: {0xf},
-			},
-		})
-
-		f := newMockFunction(newMockBlock(0).entry())
-		u1 := VReg(100).SetRegType(RegTypeInt)
-		d1 := VReg(104).SetRegType(RegTypeFloat)
-		instr := newMockInstr().use(u1).def(d1)
-		a.handleSpills(f, pc, instr, []VReg{u1}, d1, manager)
-		require.Equal(t, []VReg{u1.SetRealReg(0xa)}, instr.uses)
-		require.Equal(t, []VReg{d1.SetRealReg(0xf)}, instr.defs)
-
-		require.Equal(t, 2, len(f.befores))
-		requireInsertedInst(t, f, true, 0, instr, true, u1.SetRealReg(0xa))
-		requireInsertedInst(t, f, true, 1, instr, false, liveNodes[1].v.SetRealReg(0xf))
-		require.Equal(t, 2, len(f.afters))
-		requireInsertedInst(t, f, false, 0, instr, true, liveNodes[1].v.SetRealReg(0xf))
-		requireInsertedInst(t, f, false, 1, instr, false, d1.SetRealReg(0xf))
-	})
-
-	t.Run("uses and def / evicted / def different type", func(t *testing.T) {
-		const pc = 5
-
-		liveNodes := []*node{
-			{r: RealReg(0xb), v: 0xa},
-			{r: RealReg(0xc), v: 0xa},
-			{r: RealReg(0xf), v: 0xb},
-		}
-		manager := newIntervalManager()
-		for _, n := range liveNodes {
-			manager.insert(n, pc, 20)
-		}
-		manager.build()
-		a := NewAllocator(&RegisterInfo{
-			AllocatableRegisters: [3][]RealReg{
-				RegTypeInt:   {0xb, 0xc},
-				RegTypeFloat: {0xf},
-			},
-		})
-
-		f := newMockFunction(newMockBlock(0).entry())
-		u1 := VReg(100).SetRegType(RegTypeInt)
-		d1 := VReg(104).SetRegType(RegTypeFloat)
-		instr := newMockInstr().use(u1).def(d1)
-		a.handleSpills(f, pc, instr, []VReg{u1}, d1, manager)
-		require.Equal(t, []VReg{u1.SetRealReg(0xb)}, instr.uses)
-		require.Equal(t, []VReg{d1.SetRealReg(0xf)}, instr.defs)
-
-		require.Equal(t, 3, len(f.befores))
-		requireInsertedInst(t, f, true, 0, instr, false, liveNodes[0].v.SetRealReg(0xb))
-		requireInsertedInst(t, f, true, 1, instr, true, u1.SetRealReg(0xb))
-		requireInsertedInst(t, f, true, 2, instr, false, liveNodes[2].v.SetRealReg(0xf))
-		require.Equal(t, 3, len(f.afters))
-		requireInsertedInst(t, f, false, 0, instr, true, liveNodes[0].v.SetRealReg(0xb))
-		requireInsertedInst(t, f, false, 1, instr, true, liveNodes[2].v.SetRealReg(0xf))
-		requireInsertedInst(t, f, false, 2, instr, false, d1.SetRealReg(0xf))
-	})
-}
diff --git a/internal/engine/wazevo/backend/regalloc/coloring_test.go b/internal/engine/wazevo/backend/regalloc/coloring_test.go
deleted file mode 100644
index 6f8e662a..00000000
--- a/internal/engine/wazevo/backend/regalloc/coloring_test.go
+++ /dev/null
@@ -1,308 +0,0 @@
-package regalloc
-
-import (
-	"sort"
-	"strconv"
-	"testing"
-
-	"github.com/tetratelabs/wazero/internal/testing/require"
-)
-
-func TestAllocator_collectNodesByRegType(t *testing.T) {
-	a := NewAllocator(&RegisterInfo{})
-	n1 := a.allocateNode()
-	n1.v = VReg(0).SetRegType(RegTypeInt)
-	n2 := a.allocateNode()
-	n2.v = VReg(1).SetRegType(RegTypeFloat)
-	n3 := a.allocateNode()
-	n3.v = VReg(2).SetRegType(RegTypeInt)
-	n4 := a.allocateNode()
-	n4.v = VReg(3).SetRegType(RegTypeInt)
-
-	a.collectNodesByRegType(RegTypeInt)
-	require.Equal(t, []*node{n1, n3, n4}, a.nodes1)
-	a.collectNodesByRegType(RegTypeFloat)
-	require.Equal(t, []*node{n2}, a.nodes1)
-}
-
-func TestAllocator_coloringFor(t *testing.T) {
-	addEdge := func(n1, n2 *node) {
-		n1.neighbors = append(n1.neighbors, n2)
-		n2.neighbors = append(n2.neighbors, n1)
-	}
-
-	for _, tc := range []struct {
-		name         string
-		allocatable  []RealReg
-		links        [][]int
-		expRegs      []RealReg
-		preColorRegs map[int]RealReg
-	}{
-		{
-			name:        "one nodes",
-			allocatable: []RealReg{1},
-			links:       [][]int{{}},
-			expRegs:     []RealReg{1},
-		},
-		{
-			name:        "two nodes without interference",
-			allocatable: []RealReg{1, 2},
-			links:       [][]int{{}, {}},
-			// No interference, so both can be assigned a register.
-			expRegs: []RealReg{1, 1},
-		},
-		{
-			name:        "two nodes with interference",
-			allocatable: []RealReg{1, 2},
-			links:       [][]int{{1}, {0}},
-			// Interference, so only one can be assigned a register.
-			expRegs: []RealReg{1, 2},
-		},
-		{
-			// 0 <- 1 -> 2
-			name:        "three nodes with interference but no spill",
-			allocatable: []RealReg{1, 2},
-			links:       [][]int{{}, {0, 2}, {}},
-			expRegs:     []RealReg{2, 1, 2},
-		},
-		{
-			// 0 <- 1 -> 2 (precolor)
-			name:         "three nodes with interference but no spill / precolor",
-			allocatable:  []RealReg{1, 2},
-			links:        [][]int{{}, {0, 2}, {}},
-			expRegs:      []RealReg{1, 2, 1},
-			preColorRegs: map[int]RealReg{2: 1},
-		},
-		{
-			//     0
-			//   /   \
-			//  1 --- 3
-			name:        "three nodes with interference and spill",
-			allocatable: []RealReg{RealReg(1), RealReg(2)},
-			links:       [][]int{{1, 2}, {2}, {}},
-			expRegs:     []RealReg{2, 1, RealRegInvalid},
-		},
-		{
-			//     0
-			//   /   \
-			//  1 --- 2 (precolor)
-			name:         "three nodes with interference and spill / precolor",
-			allocatable:  []RealReg{RealReg(1), RealReg(2)},
-			links:        [][]int{{1, 2}, {2}, {}},
-			expRegs:      []RealReg{RealRegInvalid, 1, 2},
-			preColorRegs: map[int]RealReg{2: 2},
-		},
-		{
-			// https://web.stanford.edu/class/archive/cs/cs143/cs143.1128/lectures/17/Slides17.pdf
-			name:        "example in page 140",
-			allocatable: []RealReg{10, 20, 30, 40},
-			links: [][]int{
-				{1, 3, 5, 6},
-				{2, 3, 4},
-				{3, 4},
-				{5, 6},
-				{5, 6},
-				{6},
-				{},
-			},
-			expRegs: []RealReg{40, 10, 20, 30, 30, 20, 10},
-		},
-		{
-			// https://web.stanford.edu/class/archive/cs/cs143/cs143.1128/lectures/17/Slides17.pdf
-			name:        "example in page 169",
-			allocatable: []RealReg{10, 20, 30},
-			links: [][]int{
-				{1, 2, 3}, {2, 3, 4, 5}, {3, 4}, {}, {5}, {}, {},
-			},
-			expRegs: []RealReg{30, RealRegInvalid, 20, 10, 10, 20, 10},
-		},
-	} {
-		tc := tc
-		t.Run(tc.name, func(t *testing.T) {
-			if tc.preColorRegs == nil {
-				tc.preColorRegs = map[int]RealReg{}
-			}
-			a := NewAllocator(&RegisterInfo{})
-			testNodes := make([]*node, 0, len(tc.expRegs))
-			for i := range tc.expRegs {
-				n := a.allocateNode()
-				n.v = VReg(i)
-				if r, ok := tc.preColorRegs[i]; ok {
-					n.r = r
-				}
-				testNodes = append(testNodes, n)
-				a.nodes1 = append(a.nodes1, n)
-			}
-			for i, links := range tc.links {
-				n1 := testNodes[i]
-				for _, link := range links {
-					n2 := testNodes[link]
-					addEdge(n1, n2)
-				}
-			}
-			a.coloringFor(tc.allocatable)
-			var actual []string
-			for _, n := range testNodes {
-				actual = append(actual, n.r.String())
-			}
-			var exp []string
-			for _, r := range tc.expRegs {
-				exp = append(exp, r.String())
-			}
-			require.Equal(t, exp, actual)
-		})
-	}
-}
-
-func TestAllocator_assignColor(t *testing.T) {
-	t.Run("copyFromVReg", func(t *testing.T) {
-		a := NewAllocator(&RegisterInfo{})
-		a.allocatableSet[10] = true
-		n := a.getOrAllocateNode(100)
-		n.copyFromVReg = &node{r: 10}
-		a.assignColor(n, &a.realRegSet, nil)
-		require.Equal(t, RealReg(10), n.r)
-		ok := a.allocatedRegSet[n.r]
-		require.True(t, ok)
-	})
-	t.Run("copyToVReg", func(t *testing.T) {
-		a := NewAllocator(&RegisterInfo{})
-		a.allocatableSet[10] = true
-		a.allocatableSet[20] = true
-		n := a.getOrAllocateNode(100)
-		n.copyFromVReg = &node{r: 10}
-		n.copyToVReg = &node{r: 20}
-		a.realRegSet[10] = true
-		a.assignColor(n, &a.realRegSet, nil)
-		require.Equal(t, RealReg(20), n.r)
-		ok := a.allocatedRegSet[n.r]
-		require.True(t, ok)
-	})
-	t.Run("copyFromReal", func(t *testing.T) {
-		a := NewAllocator(&RegisterInfo{})
-		a.allocatableSet[10] = true
-		a.allocatableSet[20] = true
-		a.allocatableSet[30] = true
-		n := a.getOrAllocateNode(100)
-		n.copyFromVReg = &node{r: 10}
-		n.copyToVReg = &node{r: 20}
-		n.copyFromReal = 30
-		a.realRegSet[10] = true
-		a.realRegSet[20] = true
-		a.assignColor(n, &a.realRegSet, nil)
-		require.Equal(t, RealReg(30), n.r)
-		ok := a.allocatedRegSet[n.r]
-		require.True(t, ok)
-	})
-	t.Run("copyToReal", func(t *testing.T) {
-		a := NewAllocator(&RegisterInfo{})
-		a.allocatableSet[10] = true
-		a.allocatableSet[20] = true
-		a.allocatableSet[30] = true
-		a.allocatableSet[40] = true
-		n := a.getOrAllocateNode(100)
-		n.copyFromVReg = &node{r: 10}
-		n.copyToVReg = &node{r: 20}
-		n.copyFromReal = 30
-		n.copyToReal = 40
-		a.realRegSet[10] = true
-		a.realRegSet[20] = true
-		a.realRegSet[30] = true
-		a.assignColor(n, &a.realRegSet, nil)
-		require.Equal(t, RealReg(40), n.r)
-		ok := a.allocatedRegSet[n.r]
-		require.True(t, ok)
-	})
-	t.Run("from allocatable sets", func(t *testing.T) {
-		a := NewAllocator(&RegisterInfo{})
-		a.allocatableSet[10] = true
-		a.allocatableSet[20] = true
-		a.allocatableSet[30] = true
-		a.allocatableSet[40] = true
-		a.allocatableSet[50] = true
-		n := a.getOrAllocateNode(100)
-		n.copyFromVReg = &node{r: 10}
-		n.copyToVReg = &node{r: 20}
-		n.copyFromReal = 30
-		n.copyToReal = 40
-		a.realRegSet[10] = true
-		a.realRegSet[20] = true
-		a.realRegSet[30] = true
-		a.realRegSet[40] = true
-		a.assignColor(n, &a.realRegSet, []RealReg{50})
-		require.Equal(t, RealReg(50), n.r)
-		ok := a.allocatedRegSet[n.r]
-		require.True(t, ok)
-	})
-}
-
-func TestAllocator_buildNeighbors(t *testing.T) {
-	a := NewAllocator(&RegisterInfo{})
-	a.dedup = make([]bool, 1000) // Enough large.
-
-	newNode := func(id int) *node {
-		return &node{id: id}
-	}
-
-	newNodes := func(ids ...int) []*node {
-		var ns []*node
-		for _, id := range ids {
-			ns = append(ns, newNode(id))
-		}
-		return ns
-	}
-
-	for i, tc := range []struct {
-		n   *node
-		exp []int
-	}{
-		{n: newNode(0)},
-		{
-			n: &node{
-				ranges: []*interval{
-					{nodes: newNodes(1, 2, 3)},
-					{nodes: newNodes(4, 5, 1, 2, 3)},
-				},
-			},
-			exp: []int{1, 2, 3, 4, 5},
-		},
-		{
-			n: &node{
-				ranges: []*interval{
-					{nodes: newNodes(1, 2, 3)},
-					{nodes: newNodes(1, 2, 3)},
-					{nodes: newNodes(1, 2, 3)},
-					{nodes: newNodes(1, 2, 3)},
-					{nodes: newNodes(4, 5, 1, 2, 3)},
-				},
-			},
-			exp: []int{1, 2, 3, 4, 5},
-		},
-		{
-			n: &node{
-				ranges: []*interval{
-					{nodes: newNodes(1, 2, 3)},
-					{nodes: newNodes(4), neighbors: []*interval{
-						{nodes: newNodes(5, 6)},
-						{nodes: newNodes(100, 200)},
-					}},
-				},
-			},
-			exp: []int{1, 2, 3, 4, 5, 6, 100, 200},
-		},
-	} {
-		tc := tc
-		t.Run(strconv.Itoa(i), func(t *testing.T) {
-			a.buildNeighborsFor(tc.n)
-			var collected []int
-			for _, nei := range tc.n.neighbors {
-				collected = append(collected, nei.id)
-			}
-			sort.Ints(collected)
-			require.Equal(t, tc.exp, collected)
-			for i := range a.dedup {
-				require.False(t, a.dedup[i]) // must be cleaned up.
-			}
-		})
-	}
-}
diff --git a/internal/engine/wazevo/backend/regalloc/intervals_test.go b/internal/engine/wazevo/backend/regalloc/intervals_test.go
deleted file mode 100644
index cec878c4..00000000
--- a/internal/engine/wazevo/backend/regalloc/intervals_test.go
+++ /dev/null
@@ -1,258 +0,0 @@
-package regalloc
-
-import (
-	"fmt"
-	"sort"
-	"testing"
-
-	"github.com/tetratelabs/wazero/internal/testing/require"
-)
-
-func TestIntervalsManager_build(t *testing.T) {
-	type (
-		intervalCase struct {
-			begin, end programCounter
-		}
-		expNeighborCase struct {
-			index     int
-			neighbors []intervalCase
-		}
-	)
-
-	for _, tc := range []struct {
-		name         string
-		intervals    []intervalCase
-		expNeighbors []expNeighborCase
-	}{
-		{
-			name:         "single",
-			intervals:    []intervalCase{{begin: 0, end: 100}},
-			expNeighbors: []expNeighborCase{{index: 0}},
-		},
-		{
-			name:         "disjoints",
-			intervals:    []intervalCase{{begin: 50, end: 100}, {begin: 1, end: 2}},
-			expNeighbors: []expNeighborCase{{index: 0}, {index: 1}},
-		},
-		{
-			name:         "disjoints duplicate",
-			intervals:    []intervalCase{{begin: 50, end: 100}, {begin: 1, end: 2}, {begin: 50, end: 100}, {begin: 1, end: 2}},
-			expNeighbors: []expNeighborCase{{index: 0}, {index: 1}, {index: 2}, {index: 3}},
-		},
-		{
-			name: "two intersecting",
-			intervals: []intervalCase{
-				{begin: 70, end: 200},
-				{begin: 50, end: 100},
-			},
-			expNeighbors: []expNeighborCase{
-				{index: 0, neighbors: []intervalCase{{begin: 50, end: 100}}},
-				{index: 1, neighbors: []intervalCase{{begin: 70, end: 200}}},
-			},
-		},
-		{
-			name: "same beginnings",
-			intervals: []intervalCase{
-				{begin: 50, end: 200},
-				{begin: 50, end: 401},
-				{begin: 50, end: 201},
-				{begin: 50, end: 302},
-			},
-			expNeighbors: []expNeighborCase{
-				{index: 0, neighbors: []intervalCase{{begin: 50, end: 201}, {begin: 50, end: 302}, {begin: 50, end: 401}}},
-				{index: 1, neighbors: []intervalCase{{begin: 50, end: 200}, {begin: 50, end: 201}, {begin: 50, end: 302}}},
-				{index: 2, neighbors: []intervalCase{{begin: 50, end: 200}, {begin: 50, end: 302}, {begin: 50, end: 401}}},
-				{index: 3, neighbors: []intervalCase{{begin: 50, end: 200}, {begin: 50, end: 201}, {begin: 50, end: 401}}},
-			},
-		},
-		{
-			name: "three intersecting",
-			intervals: []intervalCase{
-				{begin: 70, end: 200},
-				{begin: 71, end: 150},
-				{begin: 50, end: 100},
-			},
-			expNeighbors: []expNeighborCase{
-				{index: 0, neighbors: []intervalCase{{begin: 50, end: 100}, {begin: 71, end: 150}}},
-				{index: 1, neighbors: []intervalCase{{begin: 50, end: 100}, {begin: 70, end: 200}}},
-				{index: 2, neighbors: []intervalCase{{begin: 70, end: 200}, {begin: 71, end: 150}}},
-			},
-		},
-		{
-			name: "two enclosing interval",
-			intervals: []intervalCase{
-				{begin: 50, end: 100},
-				{begin: 25, end: 200},
-				{begin: 40, end: 1000},
-			},
-			expNeighbors: []expNeighborCase{
-				{index: 0, neighbors: []intervalCase{{begin: 25, end: 200}, {begin: 40, end: 1000}}},
-				{index: 1, neighbors: []intervalCase{{begin: 40, end: 1000}, {begin: 50, end: 100}}},
-				{index: 2, neighbors: []intervalCase{{begin: 25, end: 200}, {begin: 50, end: 100}}},
-			},
-		},
-	} {
-		t.Run(tc.name, func(t *testing.T) {
-			manager := newIntervalManager()
-			for i, inter := range tc.intervals {
-				n := &node{id: i, r: RealReg(1)}
-				manager.insert(n, inter.begin, inter.end)
-			}
-			manager.build()
-
-			for i, exp := range tc.expNeighbors {
-				it := tc.intervals[exp.index]
-				key := intervalTreeNodeKey(it.begin, it.end)
-
-				var found []intervalCase
-				for _, n := range manager.intervals[key].neighbors {
-					found = append(found, intervalCase{begin: n.begin, end: n.end})
-				}
-				sort.Slice(found, func(i, j int) bool {
-					return found[i].begin < found[j].begin
-				})
-				require.Equal(t, exp.neighbors, found, fmt.Sprintf("case=%d", i))
-			}
-		})
-	}
-}
-
-func TestIntervalManager_collectActiveNodes(t *testing.T) {
-	type (
-		queryCase struct {
-			query programCounter
-			exp   []int
-		}
-		intervalCase struct {
-			begin, end programCounter
-			id         int
-		}
-	)
-
-	newQueryCase := func(s programCounter, exp ...int) queryCase {
-		return queryCase{query: s, exp: exp}
-	}
-
-	for _, tc := range []struct {
-		name       string
-		intervals  []intervalCase
-		queryCases []queryCase
-	}{
-		{
-			name:      "single",
-			intervals: []intervalCase{{begin: 0, end: 100, id: 0}},
-			queryCases: []queryCase{
-				newQueryCase(0, 0),
-				newQueryCase(1, 0),
-				newQueryCase(101),
-			},
-		},
-		{
-			name:      "single/2",
-			intervals: []intervalCase{{begin: 50, end: 100, id: 0}},
-			queryCases: []queryCase{
-				newQueryCase(48),
-				newQueryCase(50, 0),
-				newQueryCase(51, 0),
-				newQueryCase(101),
-			},
-		},
-		{
-			name:      "same id for different intervals",
-			intervals: []intervalCase{{begin: 50, end: 100, id: 0xa}, {begin: 150, end: 200, id: 0xa}},
-			queryCases: []queryCase{
-				newQueryCase(0),
-				newQueryCase(50, 0xa),
-				newQueryCase(101),
-				newQueryCase(150, 0xa),
-			},
-		},
-		{
-			name:      "two disjoint intervals",
-			intervals: []intervalCase{{begin: 50, end: 100, id: 0xa}, {begin: 150, end: 200, id: 0xb}},
-			queryCases: []queryCase{
-				newQueryCase(0),
-				newQueryCase(50, 0xa),
-				newQueryCase(51, 0xa),
-				newQueryCase(101),
-				newQueryCase(150, 0xb),
-				newQueryCase(200, 0xb),
-				newQueryCase(201),
-			},
-		},
-		{
-			name:      "two intersecting intervals",
-			intervals: []intervalCase{{begin: 50, end: 100, id: 0xa}, {begin: 51, end: 200, id: 0xb}},
-			queryCases: []queryCase{
-				newQueryCase(0),
-				newQueryCase(1),
-				newQueryCase(49),
-				newQueryCase(50, 0xa),
-				newQueryCase(51, 0xa, 0xb),
-				newQueryCase(70, 0xa, 0xb),
-				newQueryCase(100, 0xa, 0xb),
-				newQueryCase(101, 0xb),
-				newQueryCase(1001),
-			},
-		},
-		{
-			name:      "two enclosing interval",
-			intervals: []intervalCase{{begin: 50, end: 100, id: 0xa}, {begin: 25, end: 200, id: 0xb}, {begin: 40, end: 1000, id: 0xc}},
-			queryCases: []queryCase{
-				newQueryCase(24),
-				newQueryCase(25, 0xb),
-				newQueryCase(39, 0xb),
-				newQueryCase(40, 0xb, 0xc),
-				newQueryCase(49, 0xb, 0xc),
-				newQueryCase(50, 0xa, 0xb, 0xc),
-				newQueryCase(51, 0xa, 0xb, 0xc),
-				newQueryCase(99, 0xa, 0xb, 0xc),
-				newQueryCase(100, 0xa, 0xb, 0xc),
-				newQueryCase(101, 0xb, 0xc),
-				newQueryCase(200, 0xb, 0xc),
-				newQueryCase(201, 0xc),
-				newQueryCase(1000, 0xc),
-				newQueryCase(1001),
-			},
-		},
-	} {
-		t.Run(tc.name, func(t *testing.T) {
-			for _, onlyReal := range []bool{false, true} {
-				t.Run(fmt.Sprintf("onlyReal=%t", onlyReal), func(t *testing.T) {
-					manager := newIntervalManager()
-					for _, inter := range tc.intervals {
-						n := &node{id: inter.id, r: RealReg(1)}
-						manager.insert(n, inter.begin, inter.end)
-						key := intervalTreeNodeKey(inter.begin, inter.end)
-						inserted := manager.intervals[key]
-
-						// They are ignored.
-						if onlyReal {
-							inserted.nodes = append(inserted.nodes, &node{v: VRegInvalid.SetRealReg(RealRegInvalid)}) // non-real reg should be ignored.
-						} else {
-							inserted.nodes = append(inserted.nodes, &node{v: FromRealReg(1, RegTypeInt)})
-							inserted.nodes = append(inserted.nodes, &node{v: FromRealReg(1, RegTypeFloat)})
-							inserted.nodes = append(inserted.nodes, &node{v: VReg(1)})
-						}
-					}
-					manager.build()
-					for _, qc := range tc.queryCases {
-						t.Run(fmt.Sprintf("%d", qc.query), func(t *testing.T) {
-							var collected []*node
-							manager.collectActiveNodes(qc.query, &collected, onlyReal)
-							require.Equal(t, len(qc.exp), len(collected))
-							var foundIDs []int
-							for _, n := range collected {
-								foundIDs = append(foundIDs, n.id)
-							}
-							sort.Slice(foundIDs, func(i, j int) bool {
-								return foundIDs[i] < foundIDs[j]
-							})
-							require.Equal(t, qc.exp, foundIDs)
-						})
-					}
-				})
-			}
-		})
-	}
-}
diff --git a/internal/engine/wazevo/backend/regalloc/reg_test.go b/internal/engine/wazevo/backend/regalloc/reg_test.go
index 2dda28a1..7c48200a 100644
--- a/internal/engine/wazevo/backend/regalloc/reg_test.go
+++ b/internal/engine/wazevo/backend/regalloc/reg_test.go
@@ -25,61 +25,3 @@ func Test_FromRealReg(t *testing.T) {
 	require.Equal(t, RealReg(5), r.RealReg())
 	require.Equal(t, VRegID(5), r.ID())
 }
-
-func TestVRegTable(t *testing.T) {
-	min := VRegIDMinSet{}
-	min.Observe(VReg(vRegIDReservedForRealNum + 2))
-	min.Observe(VReg(vRegIDReservedForRealNum + 1))
-	min.Observe(VReg(vRegIDReservedForRealNum + 0))
-
-	table := VRegTable{}
-	table.Reset(min)
-	table.Insert(VReg(vRegIDReservedForRealNum+0), 1)
-	table.Insert(VReg(vRegIDReservedForRealNum+1), 10)
-	table.Insert(VReg(vRegIDReservedForRealNum+2), 100)
-
-	vregs := map[VReg]programCounter{}
-	table.Range(func(v VReg, p programCounter) {
-		vregs[v] = p
-	})
-	require.Equal(t, 3, len(vregs))
-
-	for v, p := range vregs {
-		require.True(t, table.Contains(v))
-		require.Equal(t, p, table.Lookup(v))
-	}
-
-	table.Range(func(v VReg, p programCounter) {
-		require.Equal(t, vregs[v], p)
-		delete(vregs, v)
-	})
-	require.Equal(t, 0, len(vregs))
-}
-
-func TestVRegSet(t *testing.T) {
-	min := VRegIDMinSet{}
-	min.Observe(VReg(vRegIDReservedForRealNum + 2))
-	min.Observe(VReg(vRegIDReservedForRealNum + 1))
-	min.Observe(VReg(vRegIDReservedForRealNum + 0))
-
-	set := VRegSet{}
-	set.Reset(min)
-	set.Insert(VReg(vRegIDReservedForRealNum + 0))
-	set.Insert(VReg(vRegIDReservedForRealNum + 1))
-	set.Insert(VReg(vRegIDReservedForRealNum + 2))
-
-	vregs := map[VReg]struct{}{}
-	set.Range(func(v VReg) {
-		vregs[v] = struct{}{}
-	})
-	require.Equal(t, 3, len(vregs))
-
-	for v := range vregs {
-		require.True(t, set.Contains(v))
-	}
-
-	set.Range(func(v VReg) {
-		delete(vregs, v)
-	})
-	require.Equal(t, 0, len(vregs))
-}
diff --git a/internal/engine/wazevo/backend/regalloc/regalloc_test.go b/internal/engine/wazevo/backend/regalloc/regalloc_test.go
index 7fda48ab..37d01b80 100644
--- a/internal/engine/wazevo/backend/regalloc/regalloc_test.go
+++ b/internal/engine/wazevo/backend/regalloc/regalloc_test.go
@@ -2,27 +2,11 @@ package regalloc
 
 import (
 	"fmt"
-	"sort"
 	"testing"
 
 	"github.com/tetratelabs/wazero/internal/testing/require"
 )
 
-func makeVRegIDMinSet[T any](vregs map[VReg]T) (min VRegIDMinSet) {
-	for v := range vregs {
-		min.Observe(v)
-	}
-	return min
-}
-
-func makeVRegTable(vregs map[VReg]programCounter) (table VRegTable) {
-	table.Reset(makeVRegIDMinSet(vregs))
-	for v, p := range vregs {
-		table.Insert(v, p)
-	}
-	return table
-}
-
 func TestAllocator_livenessAnalysis(t *testing.T) {
 	const realRegID, realRegID2 = 50, 100
 	realReg, realReg2 := FromRealReg(realRegID, RegTypeInt), FromRealReg(realRegID2, RegTypeInt)
@@ -30,7 +14,7 @@ func TestAllocator_livenessAnalysis(t *testing.T) {
 	for _, tc := range []struct {
 		name  string
 		setup func() Function
-		exp   map[int]*blockInfo
+		exp   map[int]*blockLivenessData
 	}{
 		{
 			name: "single block",
@@ -42,11 +26,8 @@ func TestAllocator_livenessAnalysis(t *testing.T) {
 					).entry(),
 				)
 			},
-			exp: map[int]*blockInfo{
-				0: {
-					defs:     map[VReg]programCounter{2: pcDefOffset + pcStride, 1: pcDefOffset},
-					lastUses: makeVRegTable(map[VReg]programCounter{1: pcStride + pcUseOffset}),
-				},
+			exp: map[int]*blockLivenessData{
+				0: {},
 			},
 		},
 		{
@@ -63,16 +44,8 @@ func TestAllocator_livenessAnalysis(t *testing.T) {
 				blk.blockParam(param)
 				return newMockFunction(blk)
 			},
-			exp: map[int]*blockInfo{
-				0: {
-					defs: map[VReg]programCounter{1: 1, 2: pcDefOffset + pcStride},
-					lastUses: makeVRegTable(map[VReg]programCounter{
-						1: pcStride + pcUseOffset,
-						2: pcStride*2 + pcUseOffset,
-					}),
-					realRegUses: [vRegIDReservedForRealNum][]programCounter{10: {0}},
-					realRegDefs: [vRegIDReservedForRealNum][]programCounter{10: {pcDefOffset + pcStride*2}},
-				},
+			exp: map[int]*blockLivenessData{
+				0: {},
 			},
 		},
 		{
@@ -96,42 +69,13 @@ func TestAllocator_livenessAnalysis(t *testing.T) {
 				b1.addPred(b0)
 				return newMockFunction(b0, b1, b2)
 			},
-			exp: map[int]*blockInfo{
-				0: {
-					defs: map[VReg]programCounter{
-						1000: pcDefOffset,
-						1:    pcDefOffset,
-						2:    pcDefOffset,
-						3:    pcStride*2 + pcDefOffset,
-					},
-					lastUses: makeVRegTable(map[VReg]programCounter{
-						1000: pcStride + pcUseOffset,
-						1:    pcStride*2 + pcUseOffset,
-						2:    pcStride*2 + pcUseOffset,
-					}),
-					liveOuts: map[VReg]struct{}{3: {}},
-				},
+			exp: map[int]*blockLivenessData{
+				0: {liveOuts: map[VReg]struct{}{3: {}}},
 				1: {
 					liveIns:  map[VReg]struct{}{3: {}},
 					liveOuts: map[VReg]struct{}{3: {}, 4: {}, 5: {}},
-					lastUses: makeVRegTable(map[VReg]programCounter{
-						3: pcStride + pcUseOffset,
-					}),
-					defs: map[VReg]programCounter{
-						4: pcStride + pcDefOffset,
-						5: pcStride + pcDefOffset,
-					},
-					realRegUses: [vRegIDReservedForRealNum][]programCounter{
-						realRegID: {pcStride*2 + pcUseOffset},
-					},
-					realRegDefs: [vRegIDReservedForRealNum][]programCounter{
-						realRegID: {pcDefOffset},
-					},
-				},
-				2: {
-					liveIns:  map[VReg]struct{}{3: {}, 4: {}, 5: {}},
-					lastUses: makeVRegTable(map[VReg]programCounter{3: pcUseOffset, 4: pcUseOffset, 5: pcUseOffset}),
 				},
+				2: {liveIns: map[VReg]struct{}{3: {}, 4: {}, 5: {}}},
 			},
 		},
 		{
@@ -167,39 +111,20 @@ func TestAllocator_livenessAnalysis(t *testing.T) {
 				b2.addPred(b0)
 				return newMockFunction(b0, b1, b2, b3)
 			},
-			exp: map[int]*blockInfo{
+			exp: map[int]*blockLivenessData{
 				0: {
-					defs: map[VReg]programCounter{
-						1000: pcDefOffset,
-						1:    pcStride + pcDefOffset,
-						2:    pcStride*2 + pcDefOffset,
-					},
 					liveOuts: map[VReg]struct{}{1000: {}, 1: {}, 2: {}},
-					lastUses: makeVRegTable(nil),
 				},
 				1: {
 					liveIns:  map[VReg]struct{}{1000: {}, 1: {}},
 					liveOuts: map[VReg]struct{}{1000: {}},
-					lastUses: makeVRegTable(map[VReg]programCounter{1: pcUseOffset}),
-					realRegDefs: [vRegIDReservedForRealNum][]programCounter{
-						realRegID:  {pcDefOffset, pcStride*4 + pcDefOffset},
-						realRegID2: {pcStride*2 + pcDefOffset},
-					},
-					realRegUses: [vRegIDReservedForRealNum][]programCounter{
-						realRegID:  {pcStride + pcUseOffset, pcStride*5 + pcUseOffset},
-						realRegID2: {pcStride*3 + pcUseOffset},
-					},
 				},
 				2: {
-					liveIns:     map[VReg]struct{}{1000: {}, 2: {}},
-					liveOuts:    map[VReg]struct{}{1000: {}},
-					lastUses:    makeVRegTable(map[VReg]programCounter{2: pcUseOffset}),
-					realRegUses: [vRegIDReservedForRealNum][]programCounter{realRegID2: {pcUseOffset}},
-					realRegDefs: [vRegIDReservedForRealNum][]programCounter{},
+					liveIns:  map[VReg]struct{}{1000: {}, 2: {}},
+					liveOuts: map[VReg]struct{}{1000: {}},
 				},
 				3: {
-					liveIns:  map[VReg]struct{}{1000: {}},
-					lastUses: makeVRegTable(map[VReg]programCounter{1000: pcUseOffset}),
+					liveIns: map[VReg]struct{}{1000: {}},
 				},
 			},
 		},
@@ -234,32 +159,24 @@ func TestAllocator_livenessAnalysis(t *testing.T) {
 				b1.addPred(b0)
 				return newMockFunction(b0, b1, b2, b3, b4)
 			},
-			exp: map[int]*blockInfo{
+			exp: map[int]*blockLivenessData{
 				0: {
-					defs:     map[VReg]programCounter{1000: pcDefOffset, 2000: pcDefOffset, 3000: pcDefOffset},
 					liveOuts: map[VReg]struct{}{1000: {}, 2000: {}, 3000: {}},
-					lastUses: makeVRegTable(nil),
 				},
 				1: {
 					liveIns:  map[VReg]struct{}{2000: {}, 3000: {}},
 					liveOuts: map[VReg]struct{}{phiVReg: {}, 3000: {}},
-					defs:     map[VReg]programCounter{phiVReg: pcDefOffset},
-					lastUses: makeVRegTable(map[VReg]programCounter{2000: pcUseOffset}),
 				},
 				2: {
 					liveIns:  map[VReg]struct{}{phiVReg: {}, 3000: {}},
 					liveOuts: map[VReg]struct{}{phiVReg: {}, 3000: {}},
-					lastUses: makeVRegTable(nil),
 				},
 				3: {
 					liveIns:  map[VReg]struct{}{1000: {}, 3000: {}},
 					liveOuts: map[VReg]struct{}{phiVReg: {}, 3000: {}},
-					defs:     map[VReg]programCounter{phiVReg: pcDefOffset},
-					lastUses: makeVRegTable(map[VReg]programCounter{1000: pcUseOffset}),
 				},
 				4: {
-					liveIns:  map[VReg]struct{}{phiVReg: {}, 3000: {}},
-					lastUses: makeVRegTable(map[VReg]programCounter{phiVReg: pcUseOffset, 3000: pcUseOffset}),
+					liveIns: map[VReg]struct{}{phiVReg: {}, 3000: {}},
 				},
 			},
 		},
@@ -305,43 +222,28 @@ func TestAllocator_livenessAnalysis(t *testing.T) {
 				f.loopNestingForestRoots(b1)
 				return f
 			},
-			exp: map[int]*blockInfo{
+			exp: map[int]*blockLivenessData{
 				0: {
 					liveIns: map[VReg]struct{}{},
 					liveOuts: map[VReg]struct{}{
 						phiVReg: {},
 					},
-					defs: map[VReg]programCounter{
-						1:       pcDefOffset,
-						phiVReg: pcStride + pcDefOffset,
-					},
-					lastUses: makeVRegTable(map[VReg]programCounter{
-						1: pcStride + pcUseOffset,
-					}),
 				},
 				1: {
 					liveIns:  map[VReg]struct{}{phiVReg: {}},
 					liveOuts: map[VReg]struct{}{phiVReg: {}, 9999: {}},
-					defs:     map[VReg]programCounter{phiVReg: 0, 9999: pcDefOffset},
-					lastUses: makeVRegTable(map[VReg]programCounter{}),
 				},
 				2: {
 					liveIns:  map[VReg]struct{}{phiVReg: {}, 9999: {}},
 					liveOuts: map[VReg]struct{}{100: {}},
-					defs:     map[VReg]programCounter{100: pcDefOffset},
-					lastUses: makeVRegTable(map[VReg]programCounter{phiVReg: pcUseOffset, 9999: pcUseOffset}),
 				},
 				3: {
 					liveIns:  map[VReg]struct{}{100: {}},
 					liveOuts: map[VReg]struct{}{54321: {}},
-					defs:     map[VReg]programCounter{54321: pcDefOffset},
-					lastUses: makeVRegTable(map[VReg]programCounter{100: pcStride + pcUseOffset}),
 				},
 				4: {
 					liveIns:  map[VReg]struct{}{54321: {}},
 					liveOuts: map[VReg]struct{}{phiVReg: {}},
-					defs:     map[VReg]programCounter{phiVReg: pcDefOffset},
-					lastUses: makeVRegTable(map[VReg]programCounter{54321: pcUseOffset}),
 				},
 			},
 		},
@@ -367,36 +269,29 @@ func TestAllocator_livenessAnalysis(t *testing.T) {
 				f.loopNestingForestRoots(b2)
 				return f
 			},
-			exp: map[int]*blockInfo{
+			exp: map[int]*blockLivenessData{
 				0: {
 					liveOuts: map[VReg]struct{}{9999: {}},
-					defs:     map[VReg]programCounter{9999: pcDefOffset},
-					lastUses: makeVRegTable(nil),
 				},
 				1: {
 					liveIns:  map[VReg]struct{}{9999: {}},
 					liveOuts: map[VReg]struct{}{9999: {}},
-					lastUses: makeVRegTable(nil),
 				},
 				2: {
 					liveIns:  map[VReg]struct{}{9999: {}},
 					liveOuts: map[VReg]struct{}{9999: {}},
-					lastUses: makeVRegTable(nil),
 				},
 				3: {
-					liveIns:  map[VReg]struct{}{9999: {}},
-					lastUses: makeVRegTable(map[VReg]programCounter{9999: pcUseOffset}),
+					liveIns: map[VReg]struct{}{9999: {}},
 				},
 				4: {
 					liveIns:  map[VReg]struct{}{9999: {}},
 					liveOuts: map[VReg]struct{}{9999: {}},
-					lastUses: makeVRegTable(nil),
 				},
-				5: {lastUses: makeVRegTable(nil)},
+				5: {},
 				6: {
 					liveIns:  map[VReg]struct{}{9999: {}},
 					liveOuts: map[VReg]struct{}{9999: {}},
-					lastUses: makeVRegTable(nil),
 				},
 			},
 		},
@@ -430,34 +325,24 @@ func TestAllocator_livenessAnalysis(t *testing.T) {
 				f.loopNestingForestRoots(b1)
 				return f
 			},
-			exp: map[int]*blockInfo{
+			exp: map[int]*blockLivenessData{
 				0: {
-					defs:     map[VReg]programCounter{99999: pcDefOffset, phiVReg: pcStride + pcDefOffset},
 					liveOuts: map[VReg]struct{}{99999: {}, phiVReg: {}},
 					liveIns:  map[VReg]struct{}{111: {}},
-					lastUses: makeVRegTable(map[VReg]programCounter{111: pcStride + pcUseOffset}),
 				},
 				1: {
-					defs:     map[VReg]programCounter{phiVReg: 0},
 					liveIns:  map[VReg]struct{}{99999: {}, phiVReg: {}},
 					liveOuts: map[VReg]struct{}{99999: {}, phiVReg: {}},
-					lastUses: makeVRegTable(map[VReg]programCounter{99999: pcUseOffset}),
 				},
 				2: {
 					liveIns:  map[VReg]struct{}{99999: {}, phiVReg: {}},
 					liveOuts: map[VReg]struct{}{99999: {}, 88888: {}, phiVReg: {}},
-					defs:     map[VReg]programCounter{88888: pcDefOffset},
-					lastUses: makeVRegTable(map[VReg]programCounter{phiVReg: pcUseOffset}),
 				},
 				3: {
 					liveIns:  map[VReg]struct{}{99999: {}, phiVReg: {}, 88888: {}},
 					liveOuts: map[VReg]struct{}{99999: {}, phiVReg: {}},
-					defs:     map[VReg]programCounter{phiVReg: pcDefOffset},
-					lastUses: makeVRegTable(map[VReg]programCounter{88888: pcUseOffset}),
-				},
-				4: {
-					lastUses: makeVRegTable(nil),
 				},
+				4: {},
 			},
 		},
 	} {
@@ -470,9 +355,9 @@ func TestAllocator_livenessAnalysis(t *testing.T) {
 				},
 			})
 			a.livenessAnalysis(f)
-			for blockID := range a.blockInfos {
+			for blockID := range a.blockLivenessData {
 				t.Run(fmt.Sprintf("block_id=%d", blockID), func(t *testing.T) {
-					actual := a.blockInfos[blockID]
+					actual := a.blockLivenessData[blockID]
 					exp := tc.exp[blockID]
 					initMapInInfo(exp)
 					fmt.Printf("\n[exp for block[%d]]\n%v\n[actual for block[%d]]\n%v\n",
@@ -480,33 +365,8 @@ func TestAllocator_livenessAnalysis(t *testing.T) {
 
 					require.Equal(t, exp.liveOuts, actual.liveOuts, "live outs")
 					require.Equal(t, exp.liveIns, actual.liveIns, "live ins")
-					require.Equal(t, exp.defs, actual.defs, "defs")
-					for i := range exp.realRegUses {
-						_exp, _actual := exp.realRegUses[i], actual.realRegUses[i]
-						sort.Slice(_exp, func(i, j int) bool {
-							return _exp[i] < _exp[j]
-						})
-						sort.Slice(_actual, func(i, j int) bool {
-							return _actual[i] < _actual[j]
-						})
-						require.Equal(t, _exp, _actual, "real reg use[%d]", i)
-					}
-					for i := range exp.realRegDefs {
-						_exp, _actual := exp.realRegDefs[i], actual.realRegDefs[i]
-						sort.Slice(_exp, func(i, j int) bool {
-							return _exp[i] < _exp[j]
-						})
-						sort.Slice(_actual, func(i, j int) bool {
-							return _actual[i] < _actual[j]
-						})
-						require.Equal(t, _exp, _actual, "real defs[%d]", i)
-					}
-					require.Equal(t, exp.lastUses, actual.lastUses, "last uses")
 				})
 			}
-
-			// Sanity check: buildLiveRanges should not panic.
-			a.buildLiveRanges(f)
 		})
 	}
 }
@@ -520,73 +380,13 @@ func TestAllocator_livenessAnalysis_copy(t *testing.T) {
 	)
 	a := NewAllocator(&RegisterInfo{})
 	a.livenessAnalysis(f)
-
-	n1, n2 := a.getOrAllocateNode(1), a.getOrAllocateNode(2)
-	require.Equal(t, n2, n1.copyToVReg)
-	require.Equal(t, n2.copyFromVReg, n1)
-	require.Nil(t, n1.copyFromVReg)
-	require.Nil(t, n2.copyToVReg)
 }
 
-func TestAllocator_recordCopyRelation(t *testing.T) {
-	t.Run("real/real", func(t *testing.T) {
-		// Just ensure that it doesn't panic.
-		a := NewAllocator(&RegisterInfo{})
-		a.recordCopyRelation(FromRealReg(1, RegTypeInt), FromRealReg(2, RegTypeInt))
-	})
-	t.Run("read/virtual", func(t *testing.T) {
-		a := NewAllocator(&RegisterInfo{})
-		v100, r := VReg(100), FromRealReg(1, RegTypeInt)
-		a.recordCopyRelation(v100, r)
-
-		n := a.vRegIDToNode[100]
-		require.Nil(t, n.copyFromVReg)
-		require.Nil(t, n.copyToVReg)
-		require.Equal(t, RealRegInvalid, n.copyToReal)
-		require.Equal(t, r.RealReg(), n.copyFromReal)
-	})
-	t.Run("virtual/read", func(t *testing.T) {
-		a := NewAllocator(&RegisterInfo{})
-		v100, r := VReg(100), FromRealReg(1, RegTypeInt)
-		a.recordCopyRelation(r, v100)
-
-		n := a.vRegIDToNode[100]
-		require.Nil(t, n.copyFromVReg)
-		require.Nil(t, n.copyToVReg)
-		require.Equal(t, RealRegInvalid, n.copyFromReal)
-		require.Equal(t, r.RealReg(), n.copyToReal)
-	})
-	t.Run("virtual/virtual", func(t *testing.T) {
-		a := NewAllocator(&RegisterInfo{})
-		v100, v200 := VReg(100), VReg(200)
-		a.recordCopyRelation(v200, v100)
-
-		n100, n200 := a.vRegIDToNode[100], a.vRegIDToNode[200]
-		require.Nil(t, n100.copyFromVReg)
-		require.Nil(t, n200.copyToVReg)
-		require.Equal(t, n200, n100.copyToVReg)
-		require.Equal(t, n200.copyFromVReg, n100)
-		require.Equal(t, RealRegInvalid, n100.copyFromReal)
-		require.Equal(t, RealRegInvalid, n100.copyToReal)
-		require.Equal(t, RealRegInvalid, n200.copyFromReal)
-		require.Equal(t, RealRegInvalid, n200.copyToReal)
-	})
-}
-
-func initMapInInfo(info *blockInfo) {
+func initMapInInfo(info *blockLivenessData) {
 	if info.liveIns == nil {
 		info.liveIns = make(map[VReg]struct{})
 	}
 	if info.liveOuts == nil {
 		info.liveOuts = make(map[VReg]struct{})
 	}
-	if info.defs == nil {
-		info.defs = make(map[VReg]programCounter)
-	}
-}
-
-func TestNode_assignedRealReg(t *testing.T) {
-	require.Equal(t, RealRegInvalid, (&node{}).assignedRealReg())
-	require.Equal(t, RealReg(100), (&node{r: 100}).assignedRealReg())
-	require.Equal(t, RealReg(200), (&node{v: VReg(1).SetRealReg(200)}).assignedRealReg())
 }
diff --git a/internal/engine/wazevo/backend/regalloc/spill_handler_test.go b/internal/engine/wazevo/backend/regalloc/spill_handler_test.go
deleted file mode 100644
index d8c07a92..00000000
--- a/internal/engine/wazevo/backend/regalloc/spill_handler_test.go
+++ /dev/null
@@ -1,68 +0,0 @@
-package regalloc
-
-import (
-	"testing"
-
-	"github.com/tetratelabs/wazero/internal/testing/require"
-)
-
-func TestSpillHandler_init(t *testing.T) {
-	instr := newMockInstr().use(FromRealReg(30, RegTypeInt))
-	var s spillHandler
-	activeNodes := []*node{{r: RealReg(0)}, {r: RealReg(1)}, {r: RealReg(2)}, {v: VReg(10).SetRealReg(100)}, {v: VReg(10).SetRealReg(30)}}
-	s.init(activeNodes, instr)
-	require.Equal(t, 5, len(s.activeRegs))
-	for _, n := range activeNodes {
-		if n.r == 30 {
-			require.Equal(t, spillHandlerRegState{node: n, state: spillHandlerRegStateBeingUsedNow}, s.activeRegs[n.assignedRealReg()])
-		} else {
-			require.Equal(t, spillHandlerRegState{node: n, state: spillHandlerRegStateEvictable}, s.activeRegs[n.assignedRealReg()])
-		}
-	}
-}
-
-func TestSpillHandler_getUnusedOrEvictReg(t *testing.T) {
-	var s spillHandler
-	activeNodes := []*node{{r: RealReg(30)}, {r: RealReg(0)}, {r: RealReg(1)}, {r: RealReg(2)}}
-	instr := newMockInstr().use(FromRealReg(30, RegTypeInt))
-	s.init(activeNodes, instr)
-	require.Equal(t, 4, len(s.activeRegs))
-
-	regInfo := RegisterInfo{
-		AllocatableRegisters: [NumRegType][]RealReg{
-			RegTypeInt: {
-				RealReg(0xff), // unused.
-				RealReg(0), RealReg(1), RealReg(2),
-			},
-			RegTypeFloat: {RealReg(0xaa)},
-		},
-	}
-
-	// Get unused register.
-	r, evicted := s.getUnusedOrEvictReg(RegTypeInt, &regInfo)
-	require.Equal(t, RealReg(0xff), r)
-	require.Nil(t, evicted)
-	require.Equal(t, 5, len(s.activeRegs))
-	require.Equal(t, spillHandlerRegState{state: spillHandlerRegStateUsed}, s.activeRegs[RealReg(0xff)])
-
-	// Get unused register.
-	r, evicted = s.getUnusedOrEvictReg(RegTypeFloat, &regInfo)
-	require.Equal(t, RealReg(0xaa), r)
-	require.Nil(t, evicted)
-	require.Equal(t, 6, len(s.activeRegs))
-	require.Equal(t, spillHandlerRegState{state: spillHandlerRegStateUsed}, s.activeRegs[RealReg(0xaa)])
-
-	// Evict register.
-	r, evicted = s.getUnusedOrEvictReg(RegTypeInt, &regInfo)
-	require.Equal(t, RealReg(0), r)
-	require.Equal(t, activeNodes[1], evicted)
-	require.Equal(t, 6, len(s.activeRegs))
-	require.Equal(t, spillHandlerRegState{node: activeNodes[1], state: spillHandlerRegStateEvicted}, s.activeRegs[RealReg(0)])
-
-	// Evict again.
-	r, evicted = s.getUnusedOrEvictReg(RegTypeInt, &regInfo)
-	require.Equal(t, RealReg(1), r)
-	require.Equal(t, activeNodes[2], evicted)
-	require.Equal(t, 6, len(s.activeRegs))
-	require.Equal(t, spillHandlerRegState{node: activeNodes[2], state: spillHandlerRegStateEvicted}, s.activeRegs[RealReg(1)])
-}
diff --git a/internal/engine/wazevo/ssa/pass_cfg_test.go b/internal/engine/wazevo/ssa/pass_cfg_test.go
index 0d3ee68e..014428ed 100644
--- a/internal/engine/wazevo/ssa/pass_cfg_test.go
+++ b/internal/engine/wazevo/ssa/pass_cfg_test.go
@@ -680,3 +680,165 @@ func TestBuildLoopNestingForest(t *testing.T) {
 		})
 	}
 }
+
+func TestDominatorTree(t *testing.T) {
+	type lowestCommonAncestorCase struct {
+		a, b BasicBlockID
+		exp  BasicBlockID
+	}
+
+	for _, tc := range []struct {
+		name  string
+		edges edgesCase
+		cases []lowestCommonAncestorCase
+	}{
+		{
+			name: "linear",
+			// 0 -> 1 -> 2 -> 3 -> 4
+			edges: edgesCase{
+				0: {1},
+				1: {2},
+				2: {3},
+				3: {4},
+			},
+			cases: []lowestCommonAncestorCase{
+				{a: 0, b: 0, exp: 0},
+				{a: 0, b: 1, exp: 0},
+				{a: 0, b: 2, exp: 0},
+				{a: 0, b: 3, exp: 0},
+				{a: 0, b: 4, exp: 0},
+				{a: 1, b: 1, exp: 1},
+				{a: 1, b: 2, exp: 1},
+				{a: 1, b: 3, exp: 1},
+				{a: 1, b: 4, exp: 1},
+				{a: 2, b: 2, exp: 2},
+				{a: 2, b: 3, exp: 2},
+				{a: 2, b: 4, exp: 2},
+				{a: 3, b: 3, exp: 3},
+				{a: 3, b: 4, exp: 3},
+				{a: 4, b: 4, exp: 4},
+			},
+		},
+		{
+			//
+			//                  +-----+
+			//                  |     |
+			//                  v     |
+			//    0 ---> 1 ---> 2 --> 3 ---> 4
+			//           ^      |
+			//           |      |
+			//           +------+
+			//
+			name: "two loops",
+			edges: map[BasicBlockID][]BasicBlockID{
+				0: {1},
+				1: {2},
+				2: {1, 3},
+				3: {2, 4},
+			},
+			cases: []lowestCommonAncestorCase{
+				{a: 0, b: 0, exp: 0},
+				{a: 0, b: 1, exp: 0},
+				{a: 0, b: 2, exp: 0},
+				{a: 0, b: 3, exp: 0},
+				{a: 0, b: 4, exp: 0},
+				{a: 1, b: 1, exp: 1},
+				{a: 1, b: 2, exp: 1},
+				{a: 1, b: 3, exp: 1},
+				{a: 1, b: 4, exp: 1},
+				{a: 2, b: 2, exp: 2},
+				{a: 2, b: 3, exp: 2},
+				{a: 2, b: 4, exp: 2},
+				{a: 3, b: 3, exp: 3},
+				{a: 3, b: 4, exp: 3},
+				{a: 4, b: 4, exp: 4},
+			},
+		},
+		{
+			name: "binary",
+			edges: edgesCase{
+				0: {1, 2},
+				1: {3, 4},
+				2: {5, 6},
+				3: {},
+				4: {},
+				5: {},
+				6: {},
+			},
+			cases: []lowestCommonAncestorCase{
+				{a: 3, b: 4, exp: 1},
+				{a: 3, b: 5, exp: 0},
+				{a: 3, b: 6, exp: 0},
+				{a: 4, b: 5, exp: 0},
+				{a: 4, b: 6, exp: 0},
+				{a: 5, b: 6, exp: 2},
+				{a: 3, b: 1, exp: 1},
+				{a: 6, b: 2, exp: 2},
+			},
+		},
+		{
+			name: "complex tree",
+			edges: edgesCase{
+				0:  {1, 2},
+				1:  {3, 4, 5},
+				2:  {6, 7},
+				3:  {8, 9},
+				4:  {10},
+				6:  {11, 12, 13},
+				7:  {14},
+				12: {15},
+			},
+			cases: []lowestCommonAncestorCase{
+				{a: 8, b: 9, exp: 3},
+				{a: 10, b: 5, exp: 1},
+				{a: 11, b: 14, exp: 2},
+				{a: 15, b: 13, exp: 6},
+				{a: 8, b: 10, exp: 1},
+				{a: 9, b: 4, exp: 1},
+				{a: 15, b: 7, exp: 2},
+			},
+		},
+		{
+			name: "complex tree with single and multiple branching",
+			edges: edgesCase{
+				0:  {1, 2},
+				1:  {3},
+				2:  {4, 5},
+				3:  {6, 7, 8},
+				4:  {9, 10},
+				5:  {11},
+				8:  {12, 13},
+				9:  {14, 15},
+				10: {16},
+				13: {17, 18},
+			},
+			cases: []lowestCommonAncestorCase{
+				{a: 6, b: 7, exp: 3},
+				{a: 14, b: 16, exp: 4},
+				{a: 17, b: 18, exp: 13},
+				{a: 12, b: 18, exp: 8},
+				{a: 6, b: 12, exp: 3},
+				{a: 11, b: 9, exp: 2},
+				{a: 15, b: 11, exp: 2},
+				{a: 3, b: 10, exp: 0},
+				{a: 7, b: 13, exp: 3},
+				{a: 15, b: 16, exp: 4},
+				{a: 0, b: 17, exp: 0},
+				{a: 1, b: 18, exp: 1},
+			},
+		},
+	} {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			b := constructGraphFromEdges(tc.edges)
+			// buildDominatorTree requires passCalculateImmediateDominators to be done.
+			passCalculateImmediateDominators(b)
+			buildDominatorTree(b)
+
+			for _, c := range tc.cases {
+				exp := b.sparseTree.findLCA(c.a, c.b)
+				require.Equal(t, exp.id, c.exp, "LCA(%d, %d)", c.a, c.b)
+			}
+		})
+	}
+}
diff --git a/internal/integration_test/fuzzcases/fuzzcases_test.go b/internal/integration_test/fuzzcases/fuzzcases_test.go
index 0731e39b..f4daf903 100644
--- a/internal/integration_test/fuzzcases/fuzzcases_test.go
+++ b/internal/integration_test/fuzzcases/fuzzcases_test.go
@@ -413,7 +413,7 @@ func Test1054(t *testing.T) {
 		return
 	}
 
-	modules := make([]api.Module, 0, 2)
+	modules := make([]api.Module, 0, 3)
 	run(t, func(t *testing.T, r wazero.Runtime) {
 		mod, err := r.Instantiate(ctx, getWasmBinary(t, "1054"))
 		require.NoError(t, err)
@@ -421,10 +421,11 @@ func Test1054(t *testing.T) {
 	})
 
 	// Checks if the memory state is the same between engines.
-	require.Equal(t,
-		modules[0].Memory().(*wasm.MemoryInstance).Buffer,
-		modules[1].Memory().(*wasm.MemoryInstance).Buffer,
-	)
+	exp := modules[0].Memory().(*wasm.MemoryInstance).Buffer
+	for i := 1; i < len(modules); i++ {
+		actual := modules[i].Memory().(*wasm.MemoryInstance).Buffer
+		require.Equal(t, exp, actual)
+	}
 }
 
 // Test1777 tests that br_table with multiple args works fine even if