Repository: tetratelabs/wazero
Pull request #1906 (2024-01-08 22:06:52)

wazevo(amd64): scaffolds instructions + constant lowering 


Patch:
diff --git a/internal/engine/wazevo/backend/compiler.go b/internal/engine/wazevo/backend/compiler.go
index c66b5ed3..cca9e52c 100644
--- a/internal/engine/wazevo/backend/compiler.go
+++ b/internal/engine/wazevo/backend/compiler.go
@@ -97,9 +97,15 @@ type Compiler interface {
 	// SourceOffsetInfo returns the source offset information for the current buffer offset.
 	SourceOffsetInfo() []SourceOffsetInfo
 
+	// EmitByte appends a byte to the buffer. Used during the code emission.
+	EmitByte(b byte)
+
 	// Emit4Bytes appends 4 bytes to the buffer. Used during the code emission.
 	Emit4Bytes(b uint32)
 
+	// Emit8Bytes appends 8 bytes to the buffer. Used during the code emission.
+	Emit8Bytes(b uint64)
+
 	// GetFunctionABI returns the ABI information for the given signature.
 	GetFunctionABI(sig *ssa.Signature) *FunctionABI
 }
@@ -380,11 +386,21 @@ func (c *compiler) AddRelocationInfo(funcRef ssa.FuncRef) {
 	})
 }
 
-// Emit4Bytes implements Compiler.Add4Bytes.
+// Emit8Bytes implements Compiler.Emit8Bytes.
+func (c *compiler) Emit8Bytes(b uint64) {
+	c.buf = append(c.buf, byte(b), byte(b>>8), byte(b>>16), byte(b>>24), byte(b>>32), byte(b>>40), byte(b>>48), byte(b>>56))
+}
+
+// Emit4Bytes implements Compiler.Emit4Bytes.
 func (c *compiler) Emit4Bytes(b uint32) {
 	c.buf = append(c.buf, byte(b), byte(b>>8), byte(b>>16), byte(b>>24))
 }
 
+// EmitByte implements Compiler.EmitByte.
+func (c *compiler) EmitByte(b byte) {
+	c.buf = append(c.buf, b)
+}
+
 // Buf implements Compiler.Buf.
 func (c *compiler) Buf() []byte {
 	return c.buf
diff --git a/internal/engine/wazevo/backend/isa/amd64/instr.go b/internal/engine/wazevo/backend/isa/amd64/instr.go
index a444c322..abc33a15 100644
--- a/internal/engine/wazevo/backend/isa/amd64/instr.go
+++ b/internal/engine/wazevo/backend/isa/amd64/instr.go
@@ -1,12 +1,19 @@
 package amd64
 
 import (
+	"fmt"
+
+	"github.com/tetratelabs/wazero/internal/engine/wazevo/backend"
 	"github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc"
 )
 
 type instruction struct {
 	kind                instructionKind
 	prev, next          *instruction
+	abi                 *backend.FunctionABI
+	op1, op2            operand
+	u1                  uint64
+	b1                  bool
 	addedBeforeRegAlloc bool
 }
 
@@ -20,13 +27,120 @@ func (i *instruction) Prev() regalloc.Instr {
 	return i.prev
 }
 
+// IsCall implements regalloc.Instr.
+func (i *instruction) IsCall() bool { return i.kind == call }
+
+// IsIndirectCall implements regalloc.Instr.
+func (i *instruction) IsIndirectCall() bool { return i.kind == callIndirect }
+
+// IsReturn implements regalloc.Instr.
+func (i *instruction) IsReturn() bool { return i.kind == ret }
+
 // AddedBeforeRegAlloc implements regalloc.Instr.
 func (i *instruction) AddedBeforeRegAlloc() bool { return i.addedBeforeRegAlloc }
 
 // String implements regalloc.Instr.
 func (i *instruction) String() string {
-	// TODO implement me
-	panic("implement me")
+	switch i.kind {
+	case nop0:
+		return "nop"
+	case ret:
+		return "ret"
+	case imm:
+		if i.b1 {
+			return fmt.Sprintf("movabsq $%d, %s", int64(i.u1), i.op1.format(true))
+		} else {
+			return fmt.Sprintf("movl $%d, %s", int32(i.u1), i.op1.format(false))
+		}
+	case aluRmiR:
+		return fmt.Sprintf("%s %s, %s", aluRmiROpcode(i.u1), i.op1.format(i.b1), i.op2.format(i.b1))
+	case movRR:
+		if i.b1 {
+			return fmt.Sprintf("movq %s, %s", i.op1.format(true), i.op2.format(true))
+		} else {
+			return fmt.Sprintf("movl %s, %s", i.op1.format(false), i.op2.format(false))
+		}
+	case xmmRmR:
+		return fmt.Sprintf("%s %s, %s", sseOpcode(i.u1), i.op1.format(i.b1), i.op2.format(i.b1))
+	case gprToXmm:
+		return fmt.Sprintf("%s %s, %s", sseOpcode(i.u1), i.op1.format(i.b1), i.op2.format(i.b1))
+	case xmmUnaryRmR:
+		return fmt.Sprintf("%s %s, %s", sseOpcode(i.u1), i.op1.format(i.b1), i.op2.format(i.b1))
+	case unaryRmR:
+		panic("TODO")
+	case not:
+		panic("TODO")
+	case neg:
+		panic("TODO")
+	case div:
+		panic("TODO")
+	case mulHi:
+		panic("TODO")
+	case checkedDivOrRemSeq:
+		panic("TODO")
+	case signExtendData:
+		panic("TODO")
+	case movzxRmR:
+		panic("TODO")
+	case mov64MR:
+		panic("TODO")
+	case loadEffectiveAddress:
+		panic("TODO")
+	case movsxRmR:
+		panic("TODO")
+	case movRM:
+		panic("TODO")
+	case shiftR:
+		panic("TODO")
+	case xmmRmiReg:
+		panic("TODO")
+	case cmpRmiR:
+		panic("TODO")
+	case setcc:
+		panic("TODO")
+	case cmove:
+		panic("TODO")
+	case push64:
+		panic("TODO")
+	case pop64:
+		panic("TODO")
+	case xmmMovRM:
+		panic("TODO")
+	case xmmLoadConst:
+		panic("TODO")
+	case xmmToGpr:
+		panic("TODO")
+	case cvtUint64ToFloatSeq:
+		panic("TODO")
+	case cvtFloatToSintSeq:
+		panic("TODO")
+	case cvtFloatToUintSeq:
+		panic("TODO")
+	case xmmMinMaxSeq:
+		panic("TODO")
+	case xmmCmove:
+		panic("TODO")
+	case xmmCmpRmR:
+		panic("TODO")
+	case xmmRmRImm:
+		panic("TODO")
+	case jmpKnown:
+		panic("TODO")
+	case jmpIf:
+		panic("TODO")
+	case jmpCond:
+		panic("TODO")
+	case jmpTableSeq:
+		panic("TODO")
+	case jmpUnknown:
+		panic("TODO")
+	case trapIf:
+		panic("TODO")
+	case ud2:
+		panic("TODO")
+	default:
+		panic(fmt.Sprintf("BUG: %v", i.kind))
+	}
 }
 
 // Defs implements regalloc.Instr.
@@ -59,24 +173,6 @@ func (i *instruction) IsCopy() bool {
 	panic("implement me")
 }
 
-// IsCall implements regalloc.Instr.
-func (i *instruction) IsCall() bool {
-	// TODO implement me
-	panic("implement me")
-}
-
-// IsIndirectCall implements regalloc.Instr.
-func (i *instruction) IsIndirectCall() bool {
-	// TODO implement me
-	panic("implement me")
-}
-
-// IsReturn implements regalloc.Instr.
-func (i *instruction) IsReturn() bool {
-	// TODO implement me
-	panic("implement me")
-}
-
 func resetInstruction(i *instruction) {
 	*i = instruction{}
 }
@@ -97,4 +193,857 @@ type instructionKind int
 
 const (
 	nop0 instructionKind = iota + 1
+
+	// Integer arithmetic/bit-twiddling: (add sub and or xor mul, etc.) (32 64) (reg addr imm) reg
+	aluRmiR
+
+	// Instructions on GPR that only read src and defines dst (dst is not modified): bsr, etc.
+	unaryRmR
+
+	// Bitwise not
+	not
+
+	// Integer negation
+	neg
+
+	// Integer quotient and remainder: (div idiv) $rax $rdx (reg addr)
+	div
+
+	// The high bits (RDX) of a (un)signed multiply: RDX:RAX := RAX * rhs.
+	mulHi
+
+	// A synthetic sequence to implement the right inline checks for remainder and division,
+	// assuming the dividend is in %rax.
+	// Puts the result back into %rax if is_div, %rdx if !is_div, to mimic what the div
+	// instruction does.
+	// The generated code sequence is described in the emit's function match arm for this
+	// instruction.
+	///
+	// Note: %rdx is marked as modified by this instruction, to avoid an early clobber problem
+	// with the temporary and divisor registers. Make sure to zero %rdx right before this
+	// instruction, or you might run into regalloc failures where %rdx is live before its first
+	// def!
+	checkedDivOrRemSeq
+
+	// Do a sign-extend based on the sign of the value in rax into rdx: (cwd cdq cqo)
+	// or al into ah: (cbw)
+	signExtendData
+
+	// Constant materialization: (imm32 imm64) reg.
+	// Either: movl $imm32, %reg32 or movabsq $imm64, %reg64.
+	imm
+
+	// GPR to GPR move: mov (64 32) reg reg.
+	movRR
+
+	// Zero-extended loads, except for 64 bits: movz (bl bq wl wq lq) addr reg.
+	// Note that the lq variant doesn't really exist since the default zero-extend rule makes it
+	// unnecessary. For that case we emit the equivalent "movl AM, reg32".
+	movzxRmR
+
+	// A plain 64-bit integer load, since MovZX_RM_R can't represent that.
+	mov64MR
+
+	// Loads the memory address of addr into dst.
+	loadEffectiveAddress
+
+	// Sign-extended loads and moves: movs (bl bq wl wq lq) addr reg.
+	movsxRmR
+
+	// Integer stores: mov (b w l q) reg addr.
+	movRM
+
+	// Arithmetic shifts: (shl shr sar) (b w l q) imm reg.
+	shiftR
+
+	// Arithmetic SIMD shifts.
+	xmmRmiReg
+
+	// Integer comparisons/tests: cmp or test (b w l q) (reg addr imm) reg.
+	cmpRmiR
+
+	// Materializes the requested condition code in the destination reg.
+	setcc
+
+	// Integer conditional move.
+	// Overwrites the destination register.
+	cmove
+
+	// pushq (reg addr imm)
+	push64
+
+	// popq reg
+	pop64
+
+	// XMM (scalar or vector) binary op: (add sub and or xor mul adc? sbb?) (32 64) (reg addr) reg
+	xmmRmR
+
+	// XMM (scalar or vector) unary op: mov between XMM registers (32 64) (reg addr) reg.
+	//
+	// This differs from xmmRmR in that the dst register of xmmUnaryRmR is not used in the
+	// computation of the instruction dst value and so does not have to be a previously valid
+	// value. This is characteristic of mov instructions.
+	xmmUnaryRmR
+
+	// XMM (scalar or vector) unary op (from xmm to reg/mem): stores, movd, movq
+	xmmMovRM
+
+	// XMM (vector) unary op (to move a constant value into an xmm register): movups
+	xmmLoadConst
+
+	// XMM (scalar) unary op (from xmm to integer reg): movd, movq, cvtts{s,d}2si
+	xmmToGpr
+
+	// XMM (scalar) unary op (from integer to float reg): movd, movq, cvtsi2s{s,d}
+	gprToXmm
+
+	// Converts an unsigned int64 to a float32/float64.
+	cvtUint64ToFloatSeq
+
+	// Converts a scalar xmm to a signed int32/int64.
+	cvtFloatToSintSeq
+
+	// Converts a scalar xmm to an unsigned int32/int64.
+	cvtFloatToUintSeq
+
+	// A sequence to compute min/max with the proper NaN semantics for xmm registers.
+	xmmMinMaxSeq
+
+	// XMM (scalar) conditional move.
+	// Overwrites the destination register if cc is set.
+	xmmCmove
+
+	// Float comparisons/tests: cmp (b w l q) (reg addr imm) reg.
+	xmmCmpRmR
+
+	// A binary XMM instruction with an 8-bit immediate: e.g. cmp (ps pd) imm (reg addr) reg
+	xmmRmRImm
+
+	// Direct call: call simm32.
+	call
+
+	// Indirect call: callq (reg mem).
+	callIndirect
+
+	// Return.
+	ret
+
+	// Jump to a known target: jmp simm32.
+	jmpKnown
+
+	// One-way conditional branch: jcond cond target.
+	///
+	// This instruction is useful when we have conditional jumps depending on more than two
+	// conditions, see for instance the lowering of Brz/brnz with Fcmp inputs.
+	///
+	// A note of caution: in contexts where the branch target is another block, this has to be the
+	// same successor as the one specified in the terminator branch of the current block.
+	// Otherwise, this might confuse register allocation by creating new invisible edges.
+	jmpIf
+
+	// Two-way conditional branch: jcond cond target target.
+	// Emitted as a compound sequence; the MachBuffer will shrink it as appropriate.
+	jmpCond
+
+	// Jump-table sequence, as one compound instruction (see note in lower.rs for rationale).
+	// The generated code sequence is described in the emit's function match arm for this
+	// instruction.
+	// See comment in lowering about the temporaries signedness.
+	jmpTableSeq
+
+	// Indirect jump: jmpq (reg mem).
+	jmpUnknown
+
+	// Traps if the condition code is set.
+	trapIf
+
+	// An instruction that will always trigger the illegal instruction exception.
+	ud2
+)
+
+func (k instructionKind) String() string {
+	switch k {
+	case nop0:
+		return "nop"
+	case ret:
+		return "ret"
+	case imm:
+		return "imm"
+	case aluRmiR:
+		return "aluRmiR"
+	case movRR:
+		return "movRR"
+	case xmmRmR:
+		return "xmmRmR"
+	case gprToXmm:
+		return "gprToXmm"
+	case xmmUnaryRmR:
+		return "xmmUnaryRmR"
+	case unaryRmR:
+		return "unaryRmR"
+	case not:
+		return "not"
+	case neg:
+		return "neg"
+	case div:
+		return "div"
+	case mulHi:
+		return "mulHi"
+	case checkedDivOrRemSeq:
+		return "checkedDivOrRemSeq"
+	case signExtendData:
+		return "signExtendData"
+	case movzxRmR:
+		return "movzxRmR"
+	case mov64MR:
+		return "mov64MR"
+	case loadEffectiveAddress:
+		return "loadEffectiveAddress"
+	case movsxRmR:
+		return "movsxRmR"
+	case movRM:
+		return "movRM"
+	case shiftR:
+		return "shiftR"
+	case xmmRmiReg:
+		return "xmmRmiReg"
+	case cmpRmiR:
+		return "cmpRmiR"
+	case setcc:
+		return "setcc"
+	case cmove:
+		return "cmove"
+	case push64:
+		return "push64"
+	case pop64:
+		return "pop64"
+	case xmmMovRM:
+		return "xmmMovRM"
+	case xmmLoadConst:
+		return "xmmLoadConst"
+	case xmmToGpr:
+		return "xmmToGpr"
+	case cvtUint64ToFloatSeq:
+		return "cvtUint64ToFloatSeq"
+	case cvtFloatToSintSeq:
+		return "cvtFloatToSintSeq"
+	case cvtFloatToUintSeq:
+		return "cvtFloatToUintSeq"
+	case xmmMinMaxSeq:
+		return "xmmMinMaxSeq"
+	case xmmCmove:
+		return "xmmCmove"
+	case xmmCmpRmR:
+		return "xmmCmpRmR"
+	case xmmRmRImm:
+		return "xmmRmRImm"
+	case jmpKnown:
+		return "jmpKnown"
+	case jmpIf:
+		return "jmpIf"
+	case jmpCond:
+		return "jmpCond"
+	case jmpTableSeq:
+		return "jmpTableSeq"
+	case jmpUnknown:
+		return "jmpUnknown"
+	case trapIf:
+		return "trapIf"
+	case ud2:
+		return "ud2"
+	default:
+		panic("BUG")
+	}
+}
+
+type aluRmiROpcode byte
+
+const (
+	aluRmiROpcodeAdd aluRmiROpcode = iota + 1
+	aluRmiROpcodeSub
+	aluRmiROpcodeAnd
+	aluRmiROpcodeOr
+	aluRmiROpcodeXor
+	aluRmiROpcodeMul
+)
+
+func (a aluRmiROpcode) String() string {
+	switch a {
+	case aluRmiROpcodeAdd:
+		return "add"
+	case aluRmiROpcodeSub:
+		return "sub"
+	case aluRmiROpcodeAnd:
+		return "and"
+	case aluRmiROpcodeOr:
+		return "or"
+	case aluRmiROpcodeXor:
+		return "xor"
+	case aluRmiROpcodeMul:
+		return "mul"
+	default:
+		panic("BUG")
+	}
+}
+
+func (i *instruction) asRet(abi *backend.FunctionABI) *instruction {
+	i.kind = ret
+	i.abi = abi
+	return i
+}
+
+func (i *instruction) asImm(dst regalloc.VReg, value uint64, _64 bool) *instruction {
+	i.kind = imm
+	i.op1 = newOperandReg(dst)
+	i.u1 = value
+	i.b1 = _64
+	return i
+}
+
+func (i *instruction) asAluRmiR(op aluRmiROpcode, rm operand, rd regalloc.VReg, _64 bool) *instruction {
+	if rm.kind != operandKindReg && rm.kind != operandKindMem && rm.kind != operandImm32 {
+		panic("BUG")
+	}
+	i.kind = aluRmiR
+	i.op1 = rm
+	i.op2 = newOperandReg(rd)
+	i.u1 = uint64(op)
+	i.b1 = _64
+	return i
+}
+
+func (i *instruction) asXmmRmR(op sseOpcode, rm operand, rd regalloc.VReg, _64 bool) *instruction {
+	if rm.kind != operandKindReg && rm.kind != operandKindMem {
+		panic("BUG")
+	}
+	i.kind = xmmRmR
+	i.op1 = rm
+	i.op2 = newOperandReg(rd)
+	i.u1 = uint64(op)
+	i.b1 = _64
+	return i
+}
+
+func (i *instruction) asGprToXmm(op sseOpcode, rm operand, rd regalloc.VReg, _64 bool) *instruction {
+	if rm.kind != operandKindReg && rm.kind != operandKindMem {
+		panic("BUG")
+	}
+	i.kind = gprToXmm
+	i.op1 = rm
+	i.op2 = newOperandReg(rd)
+	i.u1 = uint64(op)
+	i.b1 = _64
+	return i
+}
+
+func (i *instruction) asMovRR(rm, rd regalloc.VReg, _64 bool) *instruction {
+	i.kind = movRR
+	i.op1 = newOperandReg(rm)
+	i.op2 = newOperandReg(rd)
+	i.b1 = _64
+	return i
+}
+
+func (i *instruction) asXmmUnaryRmR(op sseOpcode, rm operand, rd regalloc.VReg, _64 bool) *instruction {
+	if rm.kind != operandKindReg && rm.kind != operandKindMem {
+		panic("BUG")
+	}
+	i.kind = xmmUnaryRmR
+	i.op1 = rm
+	i.op2 = newOperandReg(rd)
+	i.u1 = uint64(op)
+	i.b1 = _64
+	return i
+}
+
+type sseOpcode byte
+
+const (
+	sseOpcodeAddps sseOpcode = iota + 1
+	sseOpcodeAddpd
+	sseOpcodeAddss
+	sseOpcodeAddsd
+	sseOpcodeAndps
+	sseOpcodeAndpd
+	sseOpcodeAndnps
+	sseOpcodeAndnpd
+	sseOpcodeComiss
+	sseOpcodeComisd
+	sseOpcodeCmpps
+	sseOpcodeCmppd
+	sseOpcodeCmpss
+	sseOpcodeCmpsd
+	sseOpcodeCvtdq2ps
+	sseOpcodeCvtsd2ss
+	sseOpcodeCvtsd2si
+	sseOpcodeCvtsi2ss
+	sseOpcodeCvtsi2sd
+	sseOpcodeCvtss2si
+	sseOpcodeCvtss2sd
+	sseOpcodeCvttps2dq
+	sseOpcodeCvttss2si
+	sseOpcodeCvttsd2si
+	sseOpcodeDivps
+	sseOpcodeDivpd
+	sseOpcodeDivss
+	sseOpcodeDivsd
+	sseOpcodeInsertps
+	sseOpcodeMaxps
+	sseOpcodeMaxpd
+	sseOpcodeMaxss
+	sseOpcodeMaxsd
+	sseOpcodeMinps
+	sseOpcodeMinpd
+	sseOpcodeMinss
+	sseOpcodeMinsd
+	sseOpcodeMovaps
+	sseOpcodeMovapd
+	sseOpcodeMovd
+	sseOpcodeMovdqa
+	sseOpcodeMovdqu
+	sseOpcodeMovlhps
+	sseOpcodeMovmskps
+	sseOpcodeMovmskpd
+	sseOpcodeMovq
+	sseOpcodeMovss
+	sseOpcodeMovsd
+	sseOpcodeMovups
+	sseOpcodeMovupd
+	sseOpcodeMulps
+	sseOpcodeMulpd
+	sseOpcodeMulss
+	sseOpcodeMulsd
+	sseOpcodeOrps
+	sseOpcodeOrpd
+	sseOpcodePabsb
+	sseOpcodePabsw
+	sseOpcodePabsd
+	sseOpcodePackssdw
+	sseOpcodePacksswb
+	sseOpcodePackusdw
+	sseOpcodePackuswb
+	sseOpcodePaddb
+	sseOpcodePaddd
+	sseOpcodePaddq
+	sseOpcodePaddw
+	sseOpcodePaddsb
+	sseOpcodePaddsw
+	sseOpcodePaddusb
+	sseOpcodePaddusw
+	sseOpcodePalignr
+	sseOpcodePand
+	sseOpcodePandn
+	sseOpcodePavgb
+	sseOpcodePavgw
+	sseOpcodePcmpeqb
+	sseOpcodePcmpeqw
+	sseOpcodePcmpeqd
+	sseOpcodePcmpeqq
+	sseOpcodePcmpgtb
+	sseOpcodePcmpgtw
+	sseOpcodePcmpgtd
+	sseOpcodePcmpgtq
+	sseOpcodePextrb
+	sseOpcodePextrw
+	sseOpcodePextrd
+	sseOpcodePinsrb
+	sseOpcodePinsrw
+	sseOpcodePinsrd
+	sseOpcodePmaddwd
+	sseOpcodePmaxsb
+	sseOpcodePmaxsw
+	sseOpcodePmaxsd
+	sseOpcodePmaxub
+	sseOpcodePmaxuw
+	sseOpcodePmaxud
+	sseOpcodePminsb
+	sseOpcodePminsw
+	sseOpcodePminsd
+	sseOpcodePminub
+	sseOpcodePminuw
+	sseOpcodePminud
+	sseOpcodePmovmskb
+	sseOpcodePmovsxbd
+	sseOpcodePmovsxbw
+	sseOpcodePmovsxbq
+	sseOpcodePmovsxwd
+	sseOpcodePmovsxwq
+	sseOpcodePmovsxdq
+	sseOpcodePmovzxbd
+	sseOpcodePmovzxbw
+	sseOpcodePmovzxbq
+	sseOpcodePmovzxwd
+	sseOpcodePmovzxwq
+	sseOpcodePmovzxdq
+	sseOpcodePmulld
+	sseOpcodePmullw
+	sseOpcodePmuludq
+	sseOpcodePor
+	sseOpcodePshufb
+	sseOpcodePshufd
+	sseOpcodePsllw
+	sseOpcodePslld
+	sseOpcodePsllq
+	sseOpcodePsraw
+	sseOpcodePsrad
+	sseOpcodePsrlw
+	sseOpcodePsrld
+	sseOpcodePsrlq
+	sseOpcodePsubb
+	sseOpcodePsubd
+	sseOpcodePsubq
+	sseOpcodePsubw
+	sseOpcodePsubsb
+	sseOpcodePsubsw
+	sseOpcodePsubusb
+	sseOpcodePsubusw
+	sseOpcodePtest
+	sseOpcodePunpckhbw
+	sseOpcodePunpcklbw
+	sseOpcodePxor
+	sseOpcodeRcpss
+	sseOpcodeRoundps
+	sseOpcodeRoundpd
+	sseOpcodeRoundss
+	sseOpcodeRoundsd
+	sseOpcodeRsqrtss
+	sseOpcodeSqrtps
+	sseOpcodeSqrtpd
+	sseOpcodeSqrtss
+	sseOpcodeSqrtsd
+	sseOpcodeSubps
+	sseOpcodeSubpd
+	sseOpcodeSubss
+	sseOpcodeSubsd
+	sseOpcodeUcomiss
+	sseOpcodeUcomisd
+	sseOpcodeXorps
+	sseOpcodeXorpd
 )
+
+func (s sseOpcode) String() string {
+	switch s {
+	case sseOpcodeAddps:
+		return "addps"
+	case sseOpcodeAddpd:
+		return "addpd"
+	case sseOpcodeAddss:
+		return "addss"
+	case sseOpcodeAddsd:
+		return "addsd"
+	case sseOpcodeAndps:
+		return "andps"
+	case sseOpcodeAndpd:
+		return "andpd"
+	case sseOpcodeAndnps:
+		return "andnps"
+	case sseOpcodeAndnpd:
+		return "andnpd"
+	case sseOpcodeComiss:
+		return "comiss"
+	case sseOpcodeComisd:
+		return "comisd"
+	case sseOpcodeCmpps:
+		return "cmpps"
+	case sseOpcodeCmppd:
+		return "cmppd"
+	case sseOpcodeCmpss:
+		return "cmpss"
+	case sseOpcodeCmpsd:
+		return "cmpsd"
+	case sseOpcodeCvtdq2ps:
+		return "cvtdq2ps"
+	case sseOpcodeCvtsd2ss:
+		return "cvtsd2ss"
+	case sseOpcodeCvtsd2si:
+		return "cvtsd2si"
+	case sseOpcodeCvtsi2ss:
+		return "cvtsi2ss"
+	case sseOpcodeCvtsi2sd:
+		return "cvtsi2sd"
+	case sseOpcodeCvtss2si:
+		return "cvtss2si"
+	case sseOpcodeCvtss2sd:
+		return "cvtss2sd"
+	case sseOpcodeCvttps2dq:
+		return "cvttps2dq"
+	case sseOpcodeCvttss2si:
+		return "cvttss2si"
+	case sseOpcodeCvttsd2si:
+		return "cvttsd2si"
+	case sseOpcodeDivps:
+		return "divps"
+	case sseOpcodeDivpd:
+		return "divpd"
+	case sseOpcodeDivss:
+		return "divss"
+	case sseOpcodeDivsd:
+		return "divsd"
+	case sseOpcodeInsertps:
+		return "insertps"
+	case sseOpcodeMaxps:
+		return "maxps"
+	case sseOpcodeMaxpd:
+		return "maxpd"
+	case sseOpcodeMaxss:
+		return "maxss"
+	case sseOpcodeMaxsd:
+		return "maxsd"
+	case sseOpcodeMinps:
+		return "minps"
+	case sseOpcodeMinpd:
+		return "minpd"
+	case sseOpcodeMinss:
+		return "minss"
+	case sseOpcodeMinsd:
+		return "minsd"
+	case sseOpcodeMovaps:
+		return "movaps"
+	case sseOpcodeMovapd:
+		return "movapd"
+	case sseOpcodeMovd:
+		return "movd"
+	case sseOpcodeMovdqa:
+		return "movdqa"
+	case sseOpcodeMovdqu:
+		return "movdqu"
+	case sseOpcodeMovlhps:
+		return "movlhps"
+	case sseOpcodeMovmskps:
+		return "movmskps"
+	case sseOpcodeMovmskpd:
+		return "movmskpd"
+	case sseOpcodeMovq:
+		return "movq"
+	case sseOpcodeMovss:
+		return "movss"
+	case sseOpcodeMovsd:
+		return "movsd"
+	case sseOpcodeMovups:
+		return "movups"
+	case sseOpcodeMovupd:
+		return "movupd"
+	case sseOpcodeMulps:
+		return "mulps"
+	case sseOpcodeMulpd:
+		return "mulpd"
+	case sseOpcodeMulss:
+		return "mulss"
+	case sseOpcodeMulsd:
+		return "mulsd"
+	case sseOpcodeOrps:
+		return "orps"
+	case sseOpcodeOrpd:
+		return "orpd"
+	case sseOpcodePabsb:
+		return "pabsb"
+	case sseOpcodePabsw:
+		return "pabsw"
+	case sseOpcodePabsd:
+		return "pabsd"
+	case sseOpcodePackssdw:
+		return "packssdw"
+	case sseOpcodePacksswb:
+		return "packsswb"
+	case sseOpcodePackusdw:
+		return "packusdw"
+	case sseOpcodePackuswb:
+		return "packuswb"
+	case sseOpcodePaddb:
+		return "paddb"
+	case sseOpcodePaddd:
+		return "paddd"
+	case sseOpcodePaddq:
+		return "paddq"
+	case sseOpcodePaddw:
+		return "paddw"
+	case sseOpcodePaddsb:
+		return "paddsb"
+	case sseOpcodePaddsw:
+		return "paddsw"
+	case sseOpcodePaddusb:
+		return "paddusb"
+	case sseOpcodePaddusw:
+		return "paddusw"
+	case sseOpcodePalignr:
+		return "palignr"
+	case sseOpcodePand:
+		return "pand"
+	case sseOpcodePandn:
+		return "pandn"
+	case sseOpcodePavgb:
+		return "pavgb"
+	case sseOpcodePavgw:
+		return "pavgw"
+	case sseOpcodePcmpeqb:
+		return "pcmpeqb"
+	case sseOpcodePcmpeqw:
+		return "pcmpeqw"
+	case sseOpcodePcmpeqd:
+		return "pcmpeqd"
+	case sseOpcodePcmpeqq:
+		return "pcmpeqq"
+	case sseOpcodePcmpgtb:
+		return "pcmpgtb"
+	case sseOpcodePcmpgtw:
+		return "pcmpgtw"
+	case sseOpcodePcmpgtd:
+		return "pcmpgtd"
+	case sseOpcodePcmpgtq:
+		return "pcmpgtq"
+	case sseOpcodePextrb:
+		return "pextrb"
+	case sseOpcodePextrw:
+		return "pextrw"
+	case sseOpcodePextrd:
+		return "pextrd"
+	case sseOpcodePinsrb:
+		return "pinsrb"
+	case sseOpcodePinsrw:
+		return "pinsrw"
+	case sseOpcodePinsrd:
+		return "pinsrd"
+	case sseOpcodePmaddwd:
+		return "pmaddwd"
+	case sseOpcodePmaxsb:
+		return "pmaxsb"
+	case sseOpcodePmaxsw:
+		return "pmaxsw"
+	case sseOpcodePmaxsd:
+		return "pmaxsd"
+	case sseOpcodePmaxub:
+		return "pmaxub"
+	case sseOpcodePmaxuw:
+		return "pmaxuw"
+	case sseOpcodePmaxud:
+		return "pmaxud"
+	case sseOpcodePminsb:
+		return "pminsb"
+	case sseOpcodePminsw:
+		return "pminsw"
+	case sseOpcodePminsd:
+		return "pminsd"
+	case sseOpcodePminub:
+		return "pminub"
+	case sseOpcodePminuw:
+		return "pminuw"
+	case sseOpcodePminud:
+		return "pminud"
+	case sseOpcodePmovmskb:
+		return "pmovmskb"
+	case sseOpcodePmovsxbd:
+		return "pmovsxbd"
+	case sseOpcodePmovsxbw:
+		return "pmovsxbw"
+	case sseOpcodePmovsxbq:
+		return "pmovsxbq"
+	case sseOpcodePmovsxwd:
+		return "pmovsxwd"
+	case sseOpcodePmovsxwq:
+		return "pmovsxwq"
+	case sseOpcodePmovsxdq:
+		return "pmovsxdq"
+	case sseOpcodePmovzxbd:
+		return "pmovzxbd"
+	case sseOpcodePmovzxbw:
+		return "pmovzxbw"
+	case sseOpcodePmovzxbq:
+		return "pmovzxbq"
+	case sseOpcodePmovzxwd:
+		return "pmovzxwd"
+	case sseOpcodePmovzxwq:
+		return "pmovzxwq"
+	case sseOpcodePmovzxdq:
+		return "pmovzxdq"
+	case sseOpcodePmulld:
+		return "pmulld"
+	case sseOpcodePmullw:
+		return "pmullw"
+	case sseOpcodePmuludq:
+		return "pmuludq"
+	case sseOpcodePor:
+		return "por"
+	case sseOpcodePshufb:
+		return "pshufb"
+	case sseOpcodePshufd:
+		return "pshufd"
+	case sseOpcodePsllw:
+		return "psllw"
+	case sseOpcodePslld:
+		return "pslld"
+	case sseOpcodePsllq:
+		return "psllq"
+	case sseOpcodePsraw:
+		return "psraw"
+	case sseOpcodePsrad:
+		return "psrad"
+	case sseOpcodePsrlw:
+		return "psrlw"
+	case sseOpcodePsrld:
+		return "psrld"
+	case sseOpcodePsrlq:
+		return "psrlq"
+	case sseOpcodePsubb:
+		return "psubb"
+	case sseOpcodePsubd:
+		return "psubd"
+	case sseOpcodePsubq:
+		return "psubq"
+	case sseOpcodePsubw:
+		return "psubw"
+	case sseOpcodePsubsb:
+		return "psubsb"
+	case sseOpcodePsubsw:
+		return "psubsw"
+	case sseOpcodePsubusb:
+		return "psubusb"
+	case sseOpcodePsubusw:
+		return "psubusw"
+	case sseOpcodePtest:
+		return "ptest"
+	case sseOpcodePunpckhbw:
+		return "punpckhbw"
+	case sseOpcodePunpcklbw:
+		return "punpcklbw"
+	case sseOpcodePxor:
+		return "pxor"
+	case sseOpcodeRcpss:
+		return "rcpss"
+	case sseOpcodeRoundps:
+		return "roundps"
+	case sseOpcodeRoundpd:
+		return "roundpd"
+	case sseOpcodeRoundss:
+		return "roundss"
+	case sseOpcodeRoundsd:
+		return "roundsd"
+	case sseOpcodeRsqrtss:
+		return "rsqrtss"
+	case sseOpcodeSqrtps:
+		return "sqrtps"
+	case sseOpcodeSqrtpd:
+		return "sqrtpd"
+	case sseOpcodeSqrtss:
+		return "sqrtss"
+	case sseOpcodeSqrtsd:
+		return "sqrtsd"
+	case sseOpcodeSubps:
+		return "subps"
+	case sseOpcodeSubpd:
+		return "subpd"
+	case sseOpcodeSubss:
+		return "subss"
+	case sseOpcodeSubsd:
+		return "subsd"
+	case sseOpcodeUcomiss:
+		return "ucomiss"
+	case sseOpcodeUcomisd:
+		return "ucomisd"
+	case sseOpcodeXorps:
+		return "xorps"
+	case sseOpcodeXorpd:
+		return "xorpd"
+	default:
+		panic("BUG")
+	}
+}
diff --git a/internal/engine/wazevo/backend/isa/amd64/instr_encoding.go b/internal/engine/wazevo/backend/isa/amd64/instr_encoding.go
new file mode 100644
index 00000000..f24c27dc
--- /dev/null
+++ b/internal/engine/wazevo/backend/isa/amd64/instr_encoding.go
@@ -0,0 +1,609 @@
+package amd64
+
+import (
+	"fmt"
+
+	"github.com/tetratelabs/wazero/internal/engine/wazevo/backend"
+)
+
+// Encode implements backend.Machine Encode.
+func (m *machine) Encode() {
+	m.encode(m.ectx.RootInstr)
+}
+
+func (m *machine) encode(root *instruction) {
+	for cur := root; cur != nil; cur = cur.next {
+		cur.encode(m.c)
+	}
+}
+
+func (i *instruction) encode(c backend.Compiler) {
+	switch i.kind {
+	case nop0:
+	case ret:
+		c.EmitByte(0xc3)
+	case imm:
+		dst := regEncodings[i.op1.r.RealReg()]
+		con := i.u1
+		if i.b1 { // 64 bit.
+			if lower32willSignExtendTo64(con) {
+				// Sign extend mov(imm32).
+				encodeRegReg(c,
+					legacyPrefixesNone,
+					0xc7, 1,
+					0,
+					dst,
+					rexInfo(0).setW(),
+				)
+				c.Emit4Bytes(uint32(con))
+			} else {
+				c.EmitByte(rexEncodingW | dst.rexBit())
+				c.EmitByte(0xb8 | dst.encoding())
+				c.Emit8Bytes(con)
+			}
+		} else {
+			if dst.rexBit() > 0 {
+				c.EmitByte(rexEncodingDefault | 0x1)
+			}
+			c.EmitByte(0xb8 | dst.encoding())
+			c.Emit4Bytes(uint32(con))
+		}
+
+	case aluRmiR:
+		panic("TODO")
+
+	case movRR:
+		src := regEncodings[i.op1.r.RealReg()]
+		dst := regEncodings[i.op2.r.RealReg()]
+		var rex rexInfo
+		if i.b1 {
+			rex = rex.setW()
+		} else {
+			rex = rex.clearW()
+		}
+		encodeRegReg(c, legacyPrefixesNone, 0x89, 1, src, dst, rex)
+
+	case xmmRmR:
+		op := sseOpcode(i.u1)
+		var legPrex legacyPrefixes
+		var opcode uint32
+		var opcodeNum uint32
+		switch op {
+		case sseOpcodeAddps:
+			legPrex, opcode, opcodeNum = legacyPrefixesNone, 0x0F58, 2
+		case sseOpcodeAddpd:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F58, 2
+		case sseOpcodeAddss:
+			legPrex, opcode, opcodeNum = legacyPrefixes0xF3, 0x0F58, 2
+		case sseOpcodeAddsd:
+			legPrex, opcode, opcodeNum = legacyPrefixes0xF2, 0x0F58, 2
+		case sseOpcodeAndps:
+			legPrex, opcode, opcodeNum = legacyPrefixesNone, 0x0F54, 2
+		case sseOpcodeAndpd:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F54, 2
+		case sseOpcodeAndnps:
+			legPrex, opcode, opcodeNum = legacyPrefixesNone, 0x0F55, 2
+		case sseOpcodeAndnpd:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F55, 2
+		case sseOpcodeCvttps2dq:
+			legPrex, opcode, opcodeNum = legacyPrefixes0xF3, 0x0F5B, 2
+		case sseOpcodeCvtdq2ps:
+			legPrex, opcode, opcodeNum = legacyPrefixesNone, 0x0F5B, 2
+		case sseOpcodeDivps:
+			legPrex, opcode, opcodeNum = legacyPrefixesNone, 0x0F5E, 2
+		case sseOpcodeDivpd:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F5E, 2
+		case sseOpcodeDivss:
+			legPrex, opcode, opcodeNum = legacyPrefixes0xF3, 0x0F5E, 2
+		case sseOpcodeDivsd:
+			legPrex, opcode, opcodeNum = legacyPrefixes0xF2, 0x0F5E, 2
+		case sseOpcodeMaxps:
+			legPrex, opcode, opcodeNum = legacyPrefixesNone, 0x0F5F, 2
+		case sseOpcodeMaxpd:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F5F, 2
+		case sseOpcodeMaxss:
+			legPrex, opcode, opcodeNum = legacyPrefixes0xF3, 0x0F5F, 2
+		case sseOpcodeMaxsd:
+			legPrex, opcode, opcodeNum = legacyPrefixes0xF2, 0x0F5F, 2
+		case sseOpcodeMinps:
+			legPrex, opcode, opcodeNum = legacyPrefixesNone, 0x0F5D, 2
+		case sseOpcodeMinpd:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F5D, 2
+		case sseOpcodeMinss:
+			legPrex, opcode, opcodeNum = legacyPrefixes0xF3, 0x0F5D, 2
+		case sseOpcodeMinsd:
+			legPrex, opcode, opcodeNum = legacyPrefixes0xF2, 0x0F5D, 2
+		case sseOpcodeMovlhps:
+			legPrex, opcode, opcodeNum = legacyPrefixesNone, 0x0F16, 2
+		case sseOpcodeMovsd:
+			legPrex, opcode, opcodeNum = legacyPrefixes0xF2, 0x0F10, 2
+		case sseOpcodeMulps:
+			legPrex, opcode, opcodeNum = legacyPrefixesNone, 0x0F59, 2
+		case sseOpcodeMulpd:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F59, 2
+		case sseOpcodeMulss:
+			legPrex, opcode, opcodeNum = legacyPrefixes0xF3, 0x0F59, 2
+		case sseOpcodeMulsd:
+			legPrex, opcode, opcodeNum = legacyPrefixes0xF2, 0x0F59, 2
+		case sseOpcodeOrpd:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F56, 2
+		case sseOpcodeOrps:
+			legPrex, opcode, opcodeNum = legacyPrefixesNone, 0x0F56, 2
+		case sseOpcodePackssdw:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F6B, 2
+		case sseOpcodePacksswb:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F63, 2
+		case sseOpcodePackusdw:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F382B, 3
+		case sseOpcodePackuswb:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F67, 2
+		case sseOpcodePaddb:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FFC, 2
+		case sseOpcodePaddd:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FFE, 2
+		case sseOpcodePaddq:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FD4, 2
+		case sseOpcodePaddw:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FFD, 2
+		case sseOpcodePaddsb:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FEC, 2
+		case sseOpcodePaddsw:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FED, 2
+		case sseOpcodePaddusb:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FDC, 2
+		case sseOpcodePaddusw:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FDD, 2
+		case sseOpcodePand:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FDB, 2
+		case sseOpcodePandn:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FDF, 2
+		case sseOpcodePavgb:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FE0, 2
+		case sseOpcodePavgw:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FE3, 2
+		case sseOpcodePcmpeqb:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F74, 2
+		case sseOpcodePcmpeqw:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F75, 2
+		case sseOpcodePcmpeqd:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F76, 2
+		case sseOpcodePcmpeqq:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3829, 3
+		case sseOpcodePcmpgtb:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F64, 2
+		case sseOpcodePcmpgtw:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F65, 2
+		case sseOpcodePcmpgtd:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F66, 2
+		case sseOpcodePcmpgtq:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3837, 3
+		case sseOpcodePmaddwd:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FF5, 2
+		case sseOpcodePmaxsb:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F383C, 3
+		case sseOpcodePmaxsw:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FEE, 2
+		case sseOpcodePmaxsd:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F383D, 3
+		case sseOpcodePmaxub:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FDE, 2
+		case sseOpcodePmaxuw:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F383E, 3
+		case sseOpcodePmaxud:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F383F, 3
+		case sseOpcodePminsb:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3838, 3
+		case sseOpcodePminsw:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FEA, 2
+		case sseOpcodePminsd:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3839, 3
+		case sseOpcodePminub:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FDA, 2
+		case sseOpcodePminuw:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F383A, 3
+		case sseOpcodePminud:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F383B, 3
+		case sseOpcodePmulld:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3840, 3
+		case sseOpcodePmullw:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FD5, 2
+		case sseOpcodePmuludq:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FF4, 2
+		case sseOpcodePor:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FEB, 2
+		case sseOpcodePshufb:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3800, 3
+		case sseOpcodePsubb:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FF8, 2
+		case sseOpcodePsubd:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FFA, 2
+		case sseOpcodePsubq:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FFB, 2
+		case sseOpcodePsubw:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FF9, 2
+		case sseOpcodePsubsb:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FE8, 2
+		case sseOpcodePsubsw:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FE9, 2
+		case sseOpcodePsubusb:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FD8, 2
+		case sseOpcodePsubusw:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FD9, 2
+		case sseOpcodePunpckhbw:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F68, 2
+		case sseOpcodePunpcklbw:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F60, 2
+		case sseOpcodePxor:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0FEF, 2
+		case sseOpcodeSubps:
+			legPrex, opcode, opcodeNum = legacyPrefixesNone, 0x0F5C, 2
+		case sseOpcodeSubpd:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F5C, 2
+		case sseOpcodeSubss:
+			legPrex, opcode, opcodeNum = legacyPrefixes0xF3, 0x0F5C, 2
+		case sseOpcodeSubsd:
+			legPrex, opcode, opcodeNum = legacyPrefixes0xF2, 0x0F5C, 2
+		case sseOpcodeXorps:
+			legPrex, opcode, opcodeNum = legacyPrefixesNone, 0x0F57, 2
+		case sseOpcodeXorpd:
+			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F57, 2
+		default:
+			panic(fmt.Sprintf("Unsupported sseOpcode: %s", op))
+		}
+
+		op1 := i.op1
+		if op1.kind == operandKindReg {
+			src := regEncodings[op1.r.RealReg()]
+			dst := regEncodings[i.op2.r.RealReg()]
+			encodeRegReg(c, legPrex, opcode, opcodeNum, dst, src, rexInfo(0).clearW())
+		} else if i.op1.kind == operandKindMem {
+			panic("TODO")
+		} else {
+			panic("BUG: invalid operand kind")
+		}
+
+	case gprToXmm:
+		var legPrefix legacyPrefixes
+		var opcode uint32
+		const opcodeNum = 2
+		switch sseOpcode(i.u1) {
+		case sseOpcodeMovd, sseOpcodeMovq:
+			legPrefix, opcode = legacyPrefixes0x66, 0x0f6e
+		case sseOpcodeCvtsi2ss:
+			legPrefix, opcode = legacyPrefixes0xF3, 0x0f2a
+		case sseOpcodeCvtsi2sd:
+			legPrefix, opcode = legacyPrefixes0xF2, 0x0f2a
+		default:
+			panic(fmt.Sprintf("Unsupported sseOpcode: %s", sseOpcode(i.u1)))
+		}
+
+		var rex rexInfo
+		if i.b1 {
+			rex = rex.setW()
+		} else {
+			rex = rex.clearW()
+		}
+
+		op1 := i.op1
+		if op1.kind == operandKindReg {
+			src := regEncodings[op1.r.RealReg()]
+			dst := regEncodings[i.op2.r.RealReg()]
+			encodeRegReg(c, legPrefix, opcode, opcodeNum, dst, src, rex)
+		} else if i.op1.kind == operandKindMem {
+			panic("TODO")
+		} else {
+			panic("BUG: invalid operand kind")
+		}
+
+	case xmmUnaryRmR:
+		var prefix legacyPrefixes
+		var opcode uint32
+		var opcodeNum uint32
+		op := sseOpcode(i.u1)
+		switch op {
+		case sseOpcodeCvtss2sd:
+			prefix, opcode, opcodeNum = legacyPrefixes0xF3, 0x0F5A, 2
+		case sseOpcodeCvtsd2ss:
+			prefix, opcode, opcodeNum = legacyPrefixes0xF2, 0x0F5A, 2
+		case sseOpcodeMovaps:
+			prefix, opcode, opcodeNum = legacyPrefixesNone, 0x0F28, 2
+		case sseOpcodeMovapd:
+			prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0F28, 2
+		case sseOpcodeMovdqa:
+			prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0F6F, 2
+		case sseOpcodeMovdqu:
+			prefix, opcode, opcodeNum = legacyPrefixes0xF3, 0x0F6F, 2
+		case sseOpcodeMovsd:
+			prefix, opcode, opcodeNum = legacyPrefixes0xF2, 0x0F10, 2
+		case sseOpcodeMovss:
+			prefix, opcode, opcodeNum = legacyPrefixes0xF3, 0x0F10, 2
+		case sseOpcodeMovups:
+			prefix, opcode, opcodeNum = legacyPrefixesNone, 0x0F10, 2
+		case sseOpcodeMovupd:
+			prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0F10, 2
+		case sseOpcodePabsb:
+			prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0F381C, 3
+		case sseOpcodePabsw:
+			prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0F381D, 3
+		case sseOpcodePabsd:
+			prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0F381E, 3
+		case sseOpcodePmovsxbd:
+			prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3821, 3
+		case sseOpcodePmovsxbw:
+			prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3820, 3
+		case sseOpcodePmovsxbq:
+			prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3822, 3
+		case sseOpcodePmovsxwd:
+			prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3823, 3
+		case sseOpcodePmovsxwq:
+			prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3824, 3
+		case sseOpcodePmovsxdq:
+			prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3825, 3
+		case sseOpcodePmovzxbd:
+			prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3831, 3
+		case sseOpcodePmovzxbw:
+			prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3830, 3
+		case sseOpcodePmovzxbq:
+			prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3832, 3
+		case sseOpcodePmovzxwd:
+			prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3833, 3
+		case sseOpcodePmovzxwq:
+			prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3834, 3
+		case sseOpcodePmovzxdq:
+			prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3835, 3
+		case sseOpcodeSqrtps:
+			prefix, opcode, opcodeNum = legacyPrefixesNone, 0x0F51, 2
+		case sseOpcodeSqrtpd:
+			prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0F51, 2
+		case sseOpcodeSqrtss:
+			prefix, opcode, opcodeNum = legacyPrefixes0xF3, 0x0F51, 2
+		case sseOpcodeSqrtsd:
+			prefix, opcode, opcodeNum = legacyPrefixes0xF2, 0x0F51, 2
+		default:
+			panic(fmt.Sprintf("Unsupported sseOpcode: %s", op))
+		}
+
+		op1 := i.op1
+		if op1.kind == operandKindReg {
+			src := regEncodings[op1.r.RealReg()]
+			dst := regEncodings[i.op2.r.RealReg()]
+			encodeRegReg(c, prefix, opcode, opcodeNum, dst, src, rexInfo(0).clearW())
+		} else if i.op1.kind == operandKindMem {
+			panic("TODO")
+		} else {
+			panic("BUG: invalid operand kind")
+		}
+
+	case unaryRmR:
+		panic("TODO")
+	case not:
+		panic("TODO")
+	case neg:
+		panic("TODO")
+	case div:
+		panic("TODO")
+	case mulHi:
+		panic("TODO")
+	case checkedDivOrRemSeq:
+		panic("TODO")
+	case signExtendData:
+		panic("TODO")
+	case movzxRmR:
+		panic("TODO")
+	case mov64MR:
+		panic("TODO")
+	case loadEffectiveAddress:
+		panic("TODO")
+	case movsxRmR:
+		panic("TODO")
+	case movRM:
+		panic("TODO")
+	case shiftR:
+		panic("TODO")
+	case xmmRmiReg:
+		panic("TODO")
+	case cmpRmiR:
+		panic("TODO")
+	case setcc:
+		panic("TODO")
+	case cmove:
+		panic("TODO")
+	case push64:
+		panic("TODO")
+	case pop64:
+		panic("TODO")
+	case xmmMovRM:
+		panic("TODO")
+	case xmmLoadConst:
+		panic("TODO")
+	case xmmToGpr:
+		panic("TODO")
+	case cvtUint64ToFloatSeq:
+		panic("TODO")
+	case cvtFloatToSintSeq:
+		panic("TODO")
+	case cvtFloatToUintSeq:
+		panic("TODO")
+	case xmmMinMaxSeq:
+		panic("TODO")
+	case xmmCmove:
+		panic("TODO")
+	case xmmCmpRmR:
+		panic("TODO")
+	case xmmRmRImm:
+		panic("TODO")
+	case jmpKnown:
+		panic("TODO")
+	case jmpIf:
+		panic("TODO")
+	case jmpCond:
+		panic("TODO")
+	case jmpTableSeq:
+		panic("TODO")
+	case jmpUnknown:
+		panic("TODO")
+	case trapIf:
+		panic("TODO")
+	case ud2:
+		panic("TODO")
+	default:
+		panic(fmt.Sprintf("TODO: %v", i.kind))
+	}
+}
+
+func encodeRegReg(
+	c backend.Compiler,
+	legPrefixes legacyPrefixes,
+	opcodes uint32,
+	opcodeNum uint32,
+	r regEnc,
+	rm regEnc,
+	rex rexInfo,
+) {
+	legPrefixes.encode(c)
+	rex.encode(c, r, rm)
+
+	for opcodeNum > 0 {
+		opcodeNum--
+		c.EmitByte(byte((opcodes >> (opcodeNum << 3)) & 0xff))
+	}
+	c.EmitByte(encodeModRM(3, r.encoding(), rm.encoding()))
+}
+
+func encodeModRM(mod byte, reg byte, rm byte) byte {
+	return mod<<6 | reg<<3 | rm
+}
+
+const (
+	rexEncodingDefault byte = 0x40
+	rexEncodingW            = rexEncodingDefault | 0x08
+)
+
+// rexInfo is a bit set to indicate:
+//
+//	0x01: W bit must be cleared.
+//	0x02: REX prefix must be emitted.
+type rexInfo byte
+
+func (ri rexInfo) setW() rexInfo {
+	return ri | 0x01
+}
+
+func (ri rexInfo) clearW() rexInfo { //nolint
+	return ri & 0x02
+}
+
+func (ri rexInfo) always() rexInfo { //nolint
+	return ri | 0x02
+}
+
+func (ri rexInfo) notAlways() rexInfo { //nolint
+	return ri & 0x01
+}
+
+func (ri rexInfo) encode(c backend.Compiler, encR regEnc, encRM regEnc) {
+	var w byte = 0
+	if ri&0x01 != 0 {
+		w = 0x01
+	}
+	r := encR.rexBit()
+	b := encRM.rexBit()
+	rex := rexEncodingDefault | w<<3 | r<<2 | b
+	if rex != rexEncodingDefault || ri&0x02 == 1 {
+		c.EmitByte(rex)
+	}
+}
+
+func (ri rexInfo) encodeForIndex(c backend.Compiler, encR regEnc, encIndex regEnc, encBase regEnc) { //nolint
+	var w byte = 0
+	if ri&0x01 != 0 {
+		w = 0x01
+	}
+	r := encR.rexBit()
+	x := encIndex.rexBit()
+	b := encBase.rexBit()
+	rex := byte(0x40) | w<<3 | r<<2 | x<<1 | b
+	if rex != 0x40 || ri&0x02 == 1 {
+		c.EmitByte(rex)
+	}
+}
+
+type regEnc byte
+
+func (r regEnc) rexBit() byte {
+	return byte(r) >> 3
+}
+
+func (r regEnc) encoding() byte {
+	return byte(r) & 0x07
+}
+
+var regEncodings = [...]regEnc{
+	rax:   0b000,
+	rcx:   0b001,
+	rdx:   0b010,
+	rbx:   0b011,
+	rsp:   0b100,
+	rbp:   0b101,
+	rsi:   0b110,
+	rdi:   0b111,
+	r8:    0b1000,
+	r9:    0b1001,
+	r10:   0b1010,
+	r11:   0b1011,
+	r12:   0b1100,
+	r13:   0b1101,
+	r14:   0b1110,
+	r15:   0b1111,
+	xmm0:  0b000,
+	xmm1:  0b001,
+	xmm2:  0b010,
+	xmm3:  0b011,
+	xmm4:  0b100,
+	xmm5:  0b101,
+	xmm6:  0b110,
+	xmm7:  0b111,
+	xmm8:  0b1000,
+	xmm9:  0b1001,
+	xmm10: 0b1010,
+	xmm11: 0b1011,
+	xmm12: 0b1100,
+	xmm13: 0b1101,
+	xmm14: 0b1110,
+	xmm15: 0b1111,
+}
+
+type legacyPrefixes byte
+
+const (
+	legacyPrefixesNone legacyPrefixes = iota
+	legacyPrefixes0x66
+	legacyPrefixes0xF0
+	legacyPrefixes0x660xF0
+	legacyPrefixes0xF2
+	legacyPrefixes0xF3
+)
+
+func (p legacyPrefixes) encode(c backend.Compiler) {
+	switch p {
+	case legacyPrefixesNone:
+	case legacyPrefixes0x66:
+		c.EmitByte(0x66)
+	case legacyPrefixes0xF0:
+		c.EmitByte(0xf0)
+	case legacyPrefixes0x660xF0:
+		c.EmitByte(0x66)
+		c.EmitByte(0xf0)
+	case legacyPrefixes0xF2:
+		c.EmitByte(0xf2)
+	case legacyPrefixes0xF3:
+		c.EmitByte(0xf3)
+	default:
+		panic("BUG: invalid legacy prefix")
+	}
+}
+
+func lower32willSignExtendTo64(x uint64) bool {
+	xs := int64(x)
+	return xs == int64(uint64(int32(xs)))
+}
diff --git a/internal/engine/wazevo/backend/isa/amd64/lower_constant.go b/internal/engine/wazevo/backend/isa/amd64/lower_constant.go
new file mode 100644
index 00000000..b2305da8
--- /dev/null
+++ b/internal/engine/wazevo/backend/isa/amd64/lower_constant.go
@@ -0,0 +1,57 @@
+package amd64
+
+import (
+	"github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc"
+	"github.com/tetratelabs/wazero/internal/engine/wazevo/ssa"
+)
+
+// InsertLoadConstant implements backend.Machine.
+func (m *machine) InsertLoadConstant(instr *ssa.Instruction, vr regalloc.VReg) {
+	val := instr.Return()
+	valType := val.Type()
+	v := instr.ConstantVal()
+
+	bits := valType.Bits()
+	if bits < 64 { // Clear the redundant bits just in case it's unexpectedly sign-extended, etc.
+		v = v & ((1 << valType.Bits()) - 1)
+	}
+
+	switch valType {
+	case ssa.TypeF32, ssa.TypeF64:
+		m.lowerFconst(vr, v, bits == 64)
+	case ssa.TypeI32, ssa.TypeI64:
+		m.lowerIconst(vr, v, bits == 64)
+	default:
+		panic("TODO")
+	}
+}
+
+func (m *machine) lowerFconst(dst regalloc.VReg, c uint64, _64 bool) {
+	if c == 0 {
+		xor := m.allocateInstr().asXmmRmR(sseOpcodeXorpd, operand{kind: operandKindReg, r: dst}, dst, _64)
+		m.insert(xor)
+	} else {
+		var tmpType ssa.Type
+		if _64 {
+			tmpType = ssa.TypeI64
+		} else {
+			tmpType = ssa.TypeI32
+		}
+		tmpInt := m.c.AllocateVReg(tmpType)
+		loadToGP := m.allocateInstr().asImm(tmpInt, c, _64)
+		m.insert(loadToGP)
+
+		movToXmm := m.allocateInstr().asGprToXmm(sseOpcodeMovq, operand{kind: operandKindReg, r: tmpInt}, dst, _64)
+		m.insert(movToXmm)
+	}
+}
+
+func (m *machine) lowerIconst(dst regalloc.VReg, c uint64, _64 bool) {
+	i := m.allocateInstr()
+	if c == 0 {
+		i.asAluRmiR(aluRmiROpcodeXor, operand{kind: operandKindReg, r: dst}, dst, _64)
+	} else {
+		i.asImm(dst, c, _64)
+	}
+	m.insert(i)
+}
diff --git a/internal/engine/wazevo/backend/isa/amd64/machine.go b/internal/engine/wazevo/backend/isa/amd64/machine.go
index 3eb0fec6..354118fe 100644
--- a/internal/engine/wazevo/backend/isa/amd64/machine.go
+++ b/internal/engine/wazevo/backend/isa/amd64/machine.go
@@ -2,6 +2,8 @@ package amd64
 
 import (
 	"context"
+	"fmt"
+	"strings"
 
 	"github.com/tetratelabs/wazero/internal/engine/wazevo/backend"
 	"github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc"
@@ -23,20 +25,22 @@ func NewBackend() backend.Machine {
 	}
 }
 
-// machine implements backend.Machine for amd64.
-type machine struct {
-	c                        backend.Compiler
-	ectx                     *backend.ExecutableContextT[instruction]
-	stackBoundsCheckDisabled bool
+type (
+	// machine implements backend.Machine for amd64.
+	machine struct {
+		c                        backend.Compiler
+		ectx                     *backend.ExecutableContextT[instruction]
+		stackBoundsCheckDisabled bool
 
-	regAlloc        regalloc.Allocator
-	regAllocFn      *backend.RegAllocFunction[*instruction, *machine]
-	regAllocStarted bool
+		regAlloc        regalloc.Allocator
+		regAllocFn      *backend.RegAllocFunction[*instruction, *machine]
+		regAllocStarted bool
 
-	spillSlotSize int64
-	currentABI    *backend.FunctionABI
-	clobberedRegs []regalloc.VReg
-}
+		spillSlotSize int64
+		currentABI    *backend.FunctionABI
+		clobberedRegs []regalloc.VReg
+	}
+)
 
 // Reset implements backend.Machine.
 func (m *machine) Reset() {
@@ -75,6 +79,12 @@ func (m *machine) RegAlloc() {
 	m.spillSlotSize = (m.spillSlotSize + 15) &^ 15
 }
 
+// InsertReturn implements backend.Machine.
+func (m *machine) InsertReturn() {
+	i := m.allocateInstr().asRet(m.currentABI)
+	m.insert(i)
+}
+
 // LowerSingleBranch implements backend.Machine.
 func (m *machine) LowerSingleBranch(b *ssa.Instruction) {
 	// TODO implement me
@@ -95,26 +105,57 @@ func (m *machine) LowerInstr(instruction *ssa.Instruction) {
 
 // InsertMove implements backend.Machine.
 func (m *machine) InsertMove(dst, src regalloc.VReg, typ ssa.Type) {
-	// TODO implement me
-	panic("implement me")
-}
-
-// InsertReturn implements backend.Machine.
-func (m *machine) InsertReturn() {
-	// TODO implement me
-	panic("implement me")
-}
-
-// InsertLoadConstant implements backend.Machine.
-func (m *machine) InsertLoadConstant(instr *ssa.Instruction, vr regalloc.VReg) {
-	// TODO implement me
-	panic("implement me")
+	switch typ {
+	case ssa.TypeI32, ssa.TypeI64:
+		i := m.allocateInstr().asMovRR(src, dst, typ.Bits() == 64)
+		m.insert(i)
+	case ssa.TypeF32, ssa.TypeF64, ssa.TypeV128:
+		var op sseOpcode
+		switch typ {
+		case ssa.TypeF32:
+			op = sseOpcodeMovss
+		case ssa.TypeF64:
+			op = sseOpcodeMovsd
+		case ssa.TypeV128:
+			op = sseOpcodeMovdqa
+		}
+		i := m.allocateInstr().asXmmUnaryRmR(op, operand{kind: operandKindReg, r: src}, dst, typ.Bits() == 64)
+		m.insert(i)
+	default:
+		panic("BUG")
+	}
 }
 
 // Format implements backend.Machine.
 func (m *machine) Format() string {
-	// TODO implement me
-	panic("implement me")
+	ectx := m.ectx
+	begins := map[*instruction]backend.Label{}
+	for l, pos := range ectx.LabelPositions {
+		begins[pos.Begin] = l
+	}
+
+	irBlocks := map[backend.Label]ssa.BasicBlockID{}
+	for i, l := range ectx.SsaBlockIDToLabels {
+		irBlocks[l] = ssa.BasicBlockID(i)
+	}
+
+	var lines []string
+	for cur := ectx.RootInstr; cur != nil; cur = cur.next {
+		if l, ok := begins[cur]; ok {
+			var labelStr string
+			if blkID, ok := irBlocks[l]; ok {
+				labelStr = fmt.Sprintf("%s (SSA Block: %s):", l, blkID)
+			} else {
+				labelStr = fmt.Sprintf("%s:", l)
+			}
+			lines = append(lines, labelStr)
+		}
+		if cur.kind == nop0 {
+			continue
+		}
+		lines = append(lines, "\t"+cur.String())
+	}
+	return "\n" + strings.Join(lines, "\n") + "\n"
 }
 
 // SetupPrologue implements backend.Machine.
@@ -141,12 +182,6 @@ func (m *machine) ResolveRelocations(refToBinaryOffset map[ssa.FuncRef]int, bina
 	panic("implement me")
 }
 
-// Encode implements backend.Machine.
-func (m *machine) Encode() {
-	// TODO implement me
-	panic("implement me")
-}
-
 // CompileGoFunctionTrampoline implements backend.Machine.
 func (m *machine) CompileGoFunctionTrampoline(exitCode wazevoapi.ExitCode, sig *ssa.Signature, needModuleContextPtr bool) []byte {
 	// TODO implement me
@@ -164,3 +199,17 @@ func (m *machine) CompileEntryPreamble(signature *ssa.Signature) []byte {
 	// TODO implement me
 	panic("implement me")
 }
+
+// allocateInstr allocates an instruction.
+func (m *machine) allocateInstr() *instruction {
+	instr := m.ectx.InstructionPool.Allocate()
+	if !m.regAllocStarted {
+		instr.addedBeforeRegAlloc = true
+	}
+	return instr
+}
+
+func (m *machine) insert(i *instruction) {
+	ectx := m.ectx
+	ectx.PendingInstructions = append(ectx.PendingInstructions, i)
+}
diff --git a/internal/engine/wazevo/backend/isa/amd64/operands.go b/internal/engine/wazevo/backend/isa/amd64/operands.go
new file mode 100644
index 00000000..feba2aa1
--- /dev/null
+++ b/internal/engine/wazevo/backend/isa/amd64/operands.go
@@ -0,0 +1,78 @@
+package amd64
+
+import (
+	"fmt"
+
+	"github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc"
+)
+
+type operand struct {
+	kind  operandKind
+	r     regalloc.VReg
+	imm32 uint32
+	amode amode
+}
+
+type operandKind byte
+
+const (
+	// operandKindReg is an operand which is an integer Register.
+	operandKindReg operandKind = iota + 1
+
+	// operandKindMem is an operand which is either an integer Register or a value in Memory.  This can denote an 8, 16,
+	// 32, 64, or 128 bit value.
+	operandKindMem
+
+	// operandKindRegMemImm is either an integer Register, a value in Memory or an Immediate.
+	operandImm32
+)
+
+func (o *operand) format(_64 bool) string {
+	switch o.kind {
+	case operandKindReg:
+		return formatVRegSized(o.r, _64)
+	case operandKindMem:
+		return o.amode.String()
+	case operandImm32:
+		return fmt.Sprintf("$%d", o.imm32)
+	default:
+		panic("BUG: invalid operand kind")
+	}
+}
+
+func newOperandReg(r regalloc.VReg) operand {
+	return operand{kind: operandKindReg, r: r}
+}
+
+// nolint
+type amode struct {
+	kind  amodeKind
+	imm32 uint32
+
+	// For amodeRegRegShit:
+
+	base  regalloc.VReg
+	index regalloc.VReg
+	shift byte // 0, 1, 2, 3
+}
+
+type amodeKind byte
+
+const (
+
+	// immediate sign-extended and a Register.
+	amodeImmReg amodeKind = iota + 1
+
+	// sign-extend-32-to-64(Immediate) + Register1 + (Register2 << Shift)
+	amodeRegRegShit
+)
+
+func (a *amode) String() string {
+	switch a.kind {
+	case amodeImmReg:
+		panic("TODO")
+	case amodeRegRegShit:
+		panic("TODO")
+	}
+	panic("BUG: invalid amode kind")
+}
diff --git a/internal/engine/wazevo/backend/isa/amd64/reg.go b/internal/engine/wazevo/backend/isa/amd64/reg.go
index 4b1abe67..c47343a2 100644
--- a/internal/engine/wazevo/backend/isa/amd64/reg.go
+++ b/internal/engine/wazevo/backend/isa/amd64/reg.go
@@ -1,6 +1,8 @@
 package amd64
 
 import (
+	"fmt"
+
 	"github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc"
 )
 
@@ -147,3 +149,37 @@ var regNames = [...]string{
 	xmm14: "xmm14",
 	xmm15: "xmm15",
 }
+
+func formatVRegSized(r regalloc.VReg, _64 bool) string {
+	if r.IsRealReg() {
+		if r.RegType() == regalloc.RegTypeInt {
+			rr := r.RealReg()
+			orig := regNames[rr]
+			if rr <= rdi {
+				if _64 {
+					return "%" + orig
+				} else {
+					return "%e" + orig[1:]
+				}
+			} else {
+				if _64 {
+					return "%" + orig
+				} else {
+					return "%" + orig + "d"
+				}
+			}
+		} else {
+			return "%" + regNames[r.RealReg()]
+		}
+	} else {
+		if r.RegType() == regalloc.RegTypeInt {
+			if _64 {
+				return fmt.Sprintf("%%r%d?", r.ID())
+			} else {
+				return fmt.Sprintf("%%r%dd?", r.ID())
+			}
+		} else {
+			return fmt.Sprintf("%%xmm%d?", r.ID())
+		}
+	}
+}


Test patch:
diff --git a/internal/engine/wazevo/backend/isa/amd64/instr_encoding_test.go b/internal/engine/wazevo/backend/isa/amd64/instr_encoding_test.go
new file mode 100644
index 00000000..7f6f2a11
--- /dev/null
+++ b/internal/engine/wazevo/backend/isa/amd64/instr_encoding_test.go
@@ -0,0 +1,898 @@
+package amd64
+
+import (
+	"encoding/hex"
+	"testing"
+
+	"github.com/tetratelabs/wazero/internal/testing/require"
+)
+
+func TestInstruction_format_encode(t *testing.T) {
+	for _, tc := range []struct {
+		setup      func(*instruction)
+		want       string
+		wantFormat string
+	}{
+		{
+			setup:      func(i *instruction) { i.asRet(nil) },
+			wantFormat: "ret",
+			want:       "c3",
+		},
+		{
+			setup:      func(i *instruction) { i.asImm(r14VReg, 1234567, false) },
+			wantFormat: "movl $1234567, %r14d",
+			want:       "41be87d61200",
+		},
+		{
+			setup: func(i *instruction) {
+				v := -126
+				i.asImm(r14VReg, uint64(int64(v)), false)
+			},
+			wantFormat: "movl $-126, %r14d",
+			want:       "41be82ffffff",
+		},
+		{
+			setup:      func(i *instruction) { i.asImm(r14VReg, 0x200000000, true) },
+			want:       "49be0000000002000000",
+			wantFormat: "movabsq $8589934592, %r14",
+		},
+		{
+			setup: func(i *instruction) {
+				v := -126
+				i.asImm(r14VReg, uint64(int64(v)), true)
+			},
+			want:       "49c7c682ffffff",
+			wantFormat: "movabsq $-126, %r14",
+		},
+		{
+			setup:      func(i *instruction) { i.asImm(rcxVReg, 1234567, false) },
+			wantFormat: "movl $1234567, %ecx",
+			want:       "b987d61200",
+		},
+		{
+			setup: func(i *instruction) {
+				v := -126
+				i.asImm(rcxVReg, uint64(int64(v)), false)
+			},
+			wantFormat: "movl $-126, %ecx",
+			want:       "b982ffffff",
+		},
+		{
+			setup:      func(i *instruction) { i.asImm(rcxVReg, 0x200000000, true) },
+			want:       "48b90000000002000000",
+			wantFormat: "movabsq $8589934592, %rcx",
+		},
+		{
+			setup: func(i *instruction) {
+				v := -126
+				i.asImm(rcxVReg, uint64(int64(v)), true)
+			},
+			want:       "48c7c182ffffff",
+			wantFormat: "movabsq $-126, %rcx",
+		},
+		{
+			setup:      func(i *instruction) { i.asMovRR(raxVReg, rdiVReg, false) },
+			want:       "89c7",
+			wantFormat: "movl %eax, %edi",
+		},
+		{
+			setup:      func(i *instruction) { i.asMovRR(raxVReg, r15VReg, false) },
+			want:       "4189c7",
+			wantFormat: "movl %eax, %r15d",
+		},
+		{
+			setup:      func(i *instruction) { i.asMovRR(r14VReg, r15VReg, false) },
+			want:       "4589f7",
+			wantFormat: "movl %r14d, %r15d",
+		},
+		{
+			setup:      func(i *instruction) { i.asMovRR(raxVReg, rcxVReg, true) },
+			want:       "4889c1",
+			wantFormat: "movq %rax, %rcx",
+		},
+		{
+			setup:      func(i *instruction) { i.asMovRR(raxVReg, r15VReg, true) },
+			want:       "4989c7",
+			wantFormat: "movq %rax, %r15",
+		},
+		{
+			setup:      func(i *instruction) { i.asMovRR(r11VReg, r12VReg, true) },
+			want:       "4d89dc",
+			wantFormat: "movq %r11, %r12",
+		},
+		// addss
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodeAddss, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "f30f58c1",
+			wantFormat: "addss %xmm1, %xmm0",
+		},
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodeAddss, newOperandReg(xmm13VReg), xmm11VReg, false) },
+			want:       "f3450f58dd",
+			wantFormat: "addss %xmm13, %xmm11",
+		},
+		// addsd
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodeAddsd, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "f20f58c1",
+			wantFormat: "addsd %xmm1, %xmm0",
+		},
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodeAddsd, newOperandReg(xmm13VReg), xmm11VReg, false) },
+			want:       "f2450f58dd",
+			wantFormat: "addsd %xmm13, %xmm11",
+		},
+		// addps
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodeAddps, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "0f58c1",
+			wantFormat: "addps %xmm1, %xmm0",
+		},
+		// addpd
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodeAddpd, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f58c1",
+			wantFormat: "addpd %xmm1, %xmm0",
+		},
+		// addss
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodeAddss, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "f30f58c1",
+			wantFormat: "addss %xmm1, %xmm0",
+		},
+		// addsd
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodeAddsd, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "f20f58c1",
+			wantFormat: "addsd %xmm1, %xmm0",
+		},
+		// andps
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodeAndps, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "0f54c1",
+			wantFormat: "andps %xmm1, %xmm0",
+		},
+		// andpd
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodeAndpd, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f54c1",
+			wantFormat: "andpd %xmm1, %xmm0",
+		},
+		// andnps
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodeAndnps, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "0f55c1",
+			wantFormat: "andnps %xmm1, %xmm0",
+		},
+		// andnpd
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodeAndnpd, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f55c1",
+			wantFormat: "andnpd %xmm1, %xmm0",
+		},
+		// cvttps2dq
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodeCvttps2dq, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "f30f5bc1",
+			wantFormat: "cvttps2dq %xmm1, %xmm0",
+		},
+		// cvtdq2ps
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodeCvtdq2ps, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "0f5bc1",
+			wantFormat: "cvtdq2ps %xmm1, %xmm0",
+		},
+		// divps
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodeDivps, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "0f5ec1",
+			wantFormat: "divps %xmm1, %xmm0",
+		},
+		// divpd
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodeDivpd, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f5ec1",
+			wantFormat: "divpd %xmm1, %xmm0",
+		},
+		// divss
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodeDivss, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "f30f5ec1",
+			wantFormat: "divss %xmm1, %xmm0",
+		},
+		// divsd
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodeDivsd, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "f20f5ec1",
+			wantFormat: "divsd %xmm1, %xmm0",
+		},
+		// maxps
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodeMaxps, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "0f5fc1",
+			wantFormat: "maxps %xmm1, %xmm0",
+		},
+		// maxpd
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodeMaxpd, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f5fc1",
+			wantFormat: "maxpd %xmm1, %xmm0",
+		},
+		// maxss
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodeMaxss, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "f30f5fc1",
+			wantFormat: "maxss %xmm1, %xmm0",
+		},
+		// maxsd
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodeMaxsd, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "f20f5fc1",
+			wantFormat: "maxsd %xmm1, %xmm0",
+		},
+		// minps
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodeMinps, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "0f5dc1",
+			wantFormat: "minps %xmm1, %xmm0",
+		},
+		// paddsw
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePaddsw, newOperandReg(xmm7VReg), xmm6VReg, true) },
+			want:       "660fedf7",
+			wantFormat: "paddsw %xmm7, %xmm6",
+		},
+		// paddusb
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePaddusb, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660fdcc1",
+			wantFormat: "paddusb %xmm1, %xmm0",
+		},
+		// paddusw
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePaddusw, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660fddc1",
+			wantFormat: "paddusw %xmm1, %xmm0",
+		},
+		// pand
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePand, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660fdbc1",
+			wantFormat: "pand %xmm1, %xmm0",
+		},
+		// pandn
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePandn, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660fdfc1",
+			wantFormat: "pandn %xmm1, %xmm0",
+		},
+		// pavgb
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePavgb, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660fe0c1",
+			wantFormat: "pavgb %xmm1, %xmm0",
+		},
+		// pavgw
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePavgw, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660fe3c1",
+			wantFormat: "pavgw %xmm1, %xmm0",
+		},
+		// pcmpeqb
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePcmpeqb, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f74c1",
+			wantFormat: "pcmpeqb %xmm1, %xmm0",
+		},
+		// pcmpeqw
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePcmpeqw, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f75c1",
+			wantFormat: "pcmpeqw %xmm1, %xmm0",
+		},
+		// pcmpeqd
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePcmpeqd, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f76c1",
+			wantFormat: "pcmpeqd %xmm1, %xmm0",
+		},
+		// pcmpeqq
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePcmpeqq, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f3829c1",
+			wantFormat: "pcmpeqq %xmm1, %xmm0",
+		},
+		// pcmpgtb
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePcmpgtb, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f64c1",
+			wantFormat: "pcmpgtb %xmm1, %xmm0",
+		},
+		// pcmpgtw
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePcmpgtw, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f65c1",
+			wantFormat: "pcmpgtw %xmm1, %xmm0",
+		},
+		// pcmpgtd
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePcmpgtd, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f66c1",
+			wantFormat: "pcmpgtd %xmm1, %xmm0",
+		},
+		// pcmpgtq
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePcmpgtq, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f3837c1",
+			wantFormat: "pcmpgtq %xmm1, %xmm0",
+		},
+		// pmaddwd
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePmaddwd, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660ff5c1",
+			wantFormat: "pmaddwd %xmm1, %xmm0",
+		},
+		// pmaxsb
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePmaxsb, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f383cc1",
+			wantFormat: "pmaxsb %xmm1, %xmm0",
+		},
+		// pmaxsw
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePmaxsw, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660feec1",
+			wantFormat: "pmaxsw %xmm1, %xmm0",
+		},
+		// pmaxsd
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePmaxsd, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f383dc1",
+			wantFormat: "pmaxsd %xmm1, %xmm0",
+		},
+		// pmaxub
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePmaxub, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660fdec1",
+			wantFormat: "pmaxub %xmm1, %xmm0",
+		},
+		// pmaxuw
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePmaxuw, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f383ec1",
+			wantFormat: "pmaxuw %xmm1, %xmm0",
+		},
+		// pmaxud
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePmaxud, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f383fc1",
+			wantFormat: "pmaxud %xmm1, %xmm0",
+		},
+		// pminsb
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePminsb, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f3838c1",
+			wantFormat: "pminsb %xmm1, %xmm0",
+		},
+		// pminsw
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePminsw, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660feac1",
+			wantFormat: "pminsw %xmm1, %xmm0",
+		},
+		// pminsd
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePminsd, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f3839c1",
+			wantFormat: "pminsd %xmm1, %xmm0",
+		},
+		// pminub
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePminub, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660fdac1",
+			wantFormat: "pminub %xmm1, %xmm0",
+		},
+		// pminuw
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePminuw, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f383ac1",
+			wantFormat: "pminuw %xmm1, %xmm0",
+		},
+		// pminud
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePminud, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f383bc1",
+			wantFormat: "pminud %xmm1, %xmm0",
+		},
+		// pmulld
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePmulld, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f3840c1",
+			wantFormat: "pmulld %xmm1, %xmm0",
+		},
+		// pmullw
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePmullw, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660fd5c1",
+			wantFormat: "pmullw %xmm1, %xmm0",
+		},
+		// pmuludq
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePmuludq, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660ff4c1",
+			wantFormat: "pmuludq %xmm1, %xmm0",
+		},
+		// por
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePor, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660febc1",
+			wantFormat: "por %xmm1, %xmm0",
+		},
+		// pshufb
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePshufb, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f3800c1",
+			wantFormat: "pshufb %xmm1, %xmm0",
+		},
+		// psubb
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePsubb, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660ff8c1",
+			wantFormat: "psubb %xmm1, %xmm0",
+		},
+		// psubd
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePsubd, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660ffac1",
+			wantFormat: "psubd %xmm1, %xmm0",
+		},
+		// psubq
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePsubq, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660ffbc1",
+			wantFormat: "psubq %xmm1, %xmm0",
+		},
+		// psubw
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePsubw, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660ff9c1",
+			wantFormat: "psubw %xmm1, %xmm0",
+		},
+		// psubsb
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePsubsb, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660fe8c1",
+			wantFormat: "psubsb %xmm1, %xmm0",
+		},
+		// psubsw
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePsubsw, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660fe9c1",
+			wantFormat: "psubsw %xmm1, %xmm0",
+		},
+		// psubusb
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePsubusb, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660fd8c1",
+			wantFormat: "psubusb %xmm1, %xmm0",
+		},
+		// psubusw
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePsubusw, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660fd9c1",
+			wantFormat: "psubusw %xmm1, %xmm0",
+		},
+		// punpckhbw
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePunpckhbw, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f68c1",
+			wantFormat: "punpckhbw %xmm1, %xmm0",
+		},
+		// punpcklbw
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePunpcklbw, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f60c1",
+			wantFormat: "punpcklbw %xmm1, %xmm0",
+		},
+		// pxor
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePxor, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660fefc1",
+			wantFormat: "pxor %xmm1, %xmm0",
+		},
+		// subps
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodeSubps, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "0f5cc1",
+			wantFormat: "subps %xmm1, %xmm0",
+		},
+		// subpd
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodeSubpd, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f5cc1",
+			wantFormat: "subpd %xmm1, %xmm0",
+		},
+		// subss
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodeSubss, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "f30f5cc1",
+			wantFormat: "subss %xmm1, %xmm0",
+		},
+		// subsd
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodeSubsd, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "f20f5cc1",
+			wantFormat: "subsd %xmm1, %xmm0",
+		},
+		// xorps
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodeXorps, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "0f57c1",
+			wantFormat: "xorps %xmm1, %xmm0",
+		},
+		// xorpd
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodeXorpd, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f57c1",
+			wantFormat: "xorpd %xmm1, %xmm0",
+		},
+		// minpd
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodeMinpd, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f5dc1",
+			wantFormat: "minpd %xmm1, %xmm0",
+		},
+		// minss
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodeMinss, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "f30f5dc1",
+			wantFormat: "minss %xmm1, %xmm0",
+		},
+		// minsd
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodeMinsd, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "f20f5dc1",
+			wantFormat: "minsd %xmm1, %xmm0",
+		},
+		// movlhps
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodeMovlhps, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "0f16c1",
+			wantFormat: "movlhps %xmm1, %xmm0",
+		},
+		// movsd
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodeMovsd, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "f20f10c1",
+			wantFormat: "movsd %xmm1, %xmm0",
+		},
+		// mulps
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodeMulps, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "0f59c1",
+			wantFormat: "mulps %xmm1, %xmm0",
+		},
+		// mulpd
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodeMulpd, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f59c1",
+			wantFormat: "mulpd %xmm1, %xmm0",
+		},
+		// mulss
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodeMulss, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "f30f59c1",
+			wantFormat: "mulss %xmm1, %xmm0",
+		},
+		// mulsd
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodeMulsd, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "f20f59c1",
+			wantFormat: "mulsd %xmm1, %xmm0",
+		},
+		// orpd
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodeOrpd, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f56c1",
+			wantFormat: "orpd %xmm1, %xmm0",
+		},
+		// orps
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodeOrps, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "0f56c1",
+			wantFormat: "orps %xmm1, %xmm0",
+		},
+		// packssdw
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePackssdw, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f6bc1",
+			wantFormat: "packssdw %xmm1, %xmm0",
+		},
+		// packsswb
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePacksswb, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f63c1",
+			wantFormat: "packsswb %xmm1, %xmm0",
+		},
+		// packusdw
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePackusdw, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f382bc1",
+			wantFormat: "packusdw %xmm1, %xmm0",
+		},
+		// packuswb
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePackuswb, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f67c1",
+			wantFormat: "packuswb %xmm1, %xmm0",
+		},
+		// paddb
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePaddb, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660ffcc1",
+			wantFormat: "paddb %xmm1, %xmm0",
+		},
+		// paddd
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePaddd, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660ffec1",
+			wantFormat: "paddd %xmm1, %xmm0",
+		},
+		// paddq
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePaddq, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660fd4c1",
+			wantFormat: "paddq %xmm1, %xmm0",
+		},
+		// paddw
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePaddw, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660ffdc1",
+			wantFormat: "paddw %xmm1, %xmm0",
+		},
+		// paddsb
+		{
+			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodePaddsb, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660fecc1",
+			wantFormat: "paddsb %xmm1, %xmm0",
+		},
+		// cvtss2sd
+		{
+			setup:      func(i *instruction) { i.asXmmUnaryRmR(sseOpcodeCvtss2sd, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "f30f5ac1",
+			wantFormat: "cvtss2sd %xmm1, %xmm0",
+		},
+		// cvtsd2ss
+		{
+			setup:      func(i *instruction) { i.asXmmUnaryRmR(sseOpcodeCvtsd2ss, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "f20f5ac1",
+			wantFormat: "cvtsd2ss %xmm1, %xmm0",
+		},
+		// movaps
+		{
+			setup:      func(i *instruction) { i.asXmmUnaryRmR(sseOpcodeMovaps, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "0f28c1",
+			wantFormat: "movaps %xmm1, %xmm0",
+		},
+		// movapd
+		{
+			setup:      func(i *instruction) { i.asXmmUnaryRmR(sseOpcodeMovapd, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f28c1",
+			wantFormat: "movapd %xmm1, %xmm0",
+		},
+		// movdqa
+		{
+			setup:      func(i *instruction) { i.asXmmUnaryRmR(sseOpcodeMovdqa, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f6fc1",
+			wantFormat: "movdqa %xmm1, %xmm0",
+		},
+		// movdqu
+		{
+			setup:      func(i *instruction) { i.asXmmUnaryRmR(sseOpcodeMovdqu, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "f30f6fc1",
+			wantFormat: "movdqu %xmm1, %xmm0",
+		},
+		// movsd
+		{
+			setup:      func(i *instruction) { i.asXmmUnaryRmR(sseOpcodeMovsd, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "f20f10c1",
+			wantFormat: "movsd %xmm1, %xmm0",
+		},
+		// movss
+		{
+			setup:      func(i *instruction) { i.asXmmUnaryRmR(sseOpcodeMovss, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "f30f10c1",
+			wantFormat: "movss %xmm1, %xmm0",
+		},
+		// movups
+		{
+			setup:      func(i *instruction) { i.asXmmUnaryRmR(sseOpcodeMovups, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "0f10c1",
+			wantFormat: "movups %xmm1, %xmm0",
+		},
+		// movupd
+		{
+			setup:      func(i *instruction) { i.asXmmUnaryRmR(sseOpcodeMovupd, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f10c1",
+			wantFormat: "movupd %xmm1, %xmm0",
+		},
+		// pabsb
+		{
+			setup:      func(i *instruction) { i.asXmmUnaryRmR(sseOpcodePabsb, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f381cc1",
+			wantFormat: "pabsb %xmm1, %xmm0",
+		},
+		// pabsw
+		{
+			setup:      func(i *instruction) { i.asXmmUnaryRmR(sseOpcodePabsw, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f381dc1",
+			wantFormat: "pabsw %xmm1, %xmm0",
+		},
+		// pabsd
+		{
+			setup:      func(i *instruction) { i.asXmmUnaryRmR(sseOpcodePabsd, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f381ec1",
+			wantFormat: "pabsd %xmm1, %xmm0",
+		},
+		// pmovsxbd
+		{
+			setup:      func(i *instruction) { i.asXmmUnaryRmR(sseOpcodePmovsxbd, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f3821c1",
+			wantFormat: "pmovsxbd %xmm1, %xmm0",
+		},
+		// pmovsxbw
+		{
+			setup:      func(i *instruction) { i.asXmmUnaryRmR(sseOpcodePmovsxbw, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f3820c1",
+			wantFormat: "pmovsxbw %xmm1, %xmm0",
+		},
+		// pmovsxbq
+		{
+			setup:      func(i *instruction) { i.asXmmUnaryRmR(sseOpcodePmovsxbq, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f3822c1",
+			wantFormat: "pmovsxbq %xmm1, %xmm0",
+		},
+		// pmovsxwd
+		{
+			setup:      func(i *instruction) { i.asXmmUnaryRmR(sseOpcodePmovsxwd, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f3823c1",
+			wantFormat: "pmovsxwd %xmm1, %xmm0",
+		},
+		// pmovsxwq
+		{
+			setup:      func(i *instruction) { i.asXmmUnaryRmR(sseOpcodePmovsxwq, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f3824c1",
+			wantFormat: "pmovsxwq %xmm1, %xmm0",
+		},
+		// pmovsxdq
+		{
+			setup:      func(i *instruction) { i.asXmmUnaryRmR(sseOpcodePmovsxdq, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f3825c1",
+			wantFormat: "pmovsxdq %xmm1, %xmm0",
+		},
+		// pmovzxbd
+		{
+			setup:      func(i *instruction) { i.asXmmUnaryRmR(sseOpcodePmovzxbd, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f3831c1",
+			wantFormat: "pmovzxbd %xmm1, %xmm0",
+		},
+		// pmovzxbw
+		{
+			setup:      func(i *instruction) { i.asXmmUnaryRmR(sseOpcodePmovzxbw, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f3830c1",
+			wantFormat: "pmovzxbw %xmm1, %xmm0",
+		},
+		// pmovzxbq
+		{
+			setup:      func(i *instruction) { i.asXmmUnaryRmR(sseOpcodePmovzxbq, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f3832c1",
+			wantFormat: "pmovzxbq %xmm1, %xmm0",
+		},
+		// pmovzxwd
+		{
+			setup:      func(i *instruction) { i.asXmmUnaryRmR(sseOpcodePmovzxwd, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f3833c1",
+			wantFormat: "pmovzxwd %xmm1, %xmm0",
+		},
+		// pmovzxwq
+		{
+			setup:      func(i *instruction) { i.asXmmUnaryRmR(sseOpcodePmovzxwq, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f3834c1",
+			wantFormat: "pmovzxwq %xmm1, %xmm0",
+		},
+		// pmovzxdq
+		{
+			setup:      func(i *instruction) { i.asXmmUnaryRmR(sseOpcodePmovzxdq, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f3835c1",
+			wantFormat: "pmovzxdq %xmm1, %xmm0",
+		},
+		// sqrtps
+		{
+			setup:      func(i *instruction) { i.asXmmUnaryRmR(sseOpcodeSqrtps, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "0f51c1",
+			wantFormat: "sqrtps %xmm1, %xmm0",
+		},
+		// sqrtpd
+		{
+			setup:      func(i *instruction) { i.asXmmUnaryRmR(sseOpcodeSqrtpd, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "660f51c1",
+			wantFormat: "sqrtpd %xmm1, %xmm0",
+		},
+		// sqrtss
+		{
+			setup:      func(i *instruction) { i.asXmmUnaryRmR(sseOpcodeSqrtss, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "f30f51c1",
+			wantFormat: "sqrtss %xmm1, %xmm0",
+		},
+		// sqrtsd
+		{
+			setup:      func(i *instruction) { i.asXmmUnaryRmR(sseOpcodeSqrtsd, newOperandReg(xmm1VReg), xmm0VReg, true) },
+			want:       "f20f51c1",
+			wantFormat: "sqrtsd %xmm1, %xmm0",
+		},
+		{
+			setup:      func(i *instruction) { i.asXmmUnaryRmR(sseOpcodeSqrtsd, newOperandReg(xmm15VReg), xmm0VReg, true) },
+			want:       "f2410f51c7",
+			wantFormat: "sqrtsd %xmm15, %xmm0",
+		},
+		{
+			setup:      func(i *instruction) { i.asXmmUnaryRmR(sseOpcodeSqrtsd, newOperandReg(xmm1VReg), xmm15VReg, true) },
+			want:       "f2440f51f9",
+			wantFormat: "sqrtsd %xmm1, %xmm15",
+		},
+		{
+			setup:      func(i *instruction) { i.asXmmUnaryRmR(sseOpcodeSqrtsd, newOperandReg(xmm11VReg), xmm15VReg, true) },
+			want:       "f2450f51fb",
+			wantFormat: "sqrtsd %xmm11, %xmm15",
+		},
+		// movd
+		{
+			setup:      func(i *instruction) { i.asGprToXmm(sseOpcodeMovd, newOperandReg(rdiVReg), xmm0VReg, false) },
+			want:       "660f6ec7",
+			wantFormat: "movd %edi, %xmm0",
+		},
+		// movq
+		{
+			setup:      func(i *instruction) { i.asGprToXmm(sseOpcodeMovq, newOperandReg(rcxVReg), xmm0VReg, true) },
+			want:       "66480f6ec1",
+			wantFormat: "movq %rcx, %xmm0",
+		},
+		// cvtsi2ss
+		{
+			setup:      func(i *instruction) { i.asGprToXmm(sseOpcodeCvtsi2ss, newOperandReg(rdiVReg), xmm0VReg, true) },
+			want:       "f3480f2ac7",
+			wantFormat: "cvtsi2ss %rdi, %xmm0",
+		},
+		// cvtsi2sd
+		{
+			setup:      func(i *instruction) { i.asGprToXmm(sseOpcodeCvtsi2sd, newOperandReg(rdiVReg), xmm0VReg, true) },
+			want:       "f2480f2ac7",
+			wantFormat: "cvtsi2sd %rdi, %xmm0",
+		},
+		{
+			setup:      func(i *instruction) { i.asGprToXmm(sseOpcodeCvtsi2sd, newOperandReg(rdiVReg), xmm0VReg, false) },
+			want:       "f20f2ac7",
+			wantFormat: "cvtsi2sd %edi, %xmm0",
+		},
+	} {
+		tc := tc
+		t.Run(tc.wantFormat, func(t *testing.T) {
+			i := &instruction{}
+			tc.setup(i)
+
+			require.Equal(t, tc.wantFormat, i.String())
+
+			mc := &mockCompiler{}
+			m := &machine{c: mc}
+			i.encode(m.c)
+			require.Equal(t, tc.want, hex.EncodeToString(mc.buf))
+
+			// TODO: verify the size of the encoded instructions.
+			//var actualSize int
+			//for cur := i; cur != nil; cur = cur.next {
+			//	actualSize += int(cur.size())
+			//}
+			//require.Equal(t, len(tc.want)/2, actualSize)
+		})
+	}
+}
diff --git a/internal/engine/wazevo/backend/isa/amd64/reg_test.go b/internal/engine/wazevo/backend/isa/amd64/reg_test.go
new file mode 100644
index 00000000..da7b5bdd
--- /dev/null
+++ b/internal/engine/wazevo/backend/isa/amd64/reg_test.go
@@ -0,0 +1,36 @@
+package amd64
+
+import (
+	"testing"
+
+	"github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc"
+	"github.com/tetratelabs/wazero/internal/testing/require"
+)
+
+func Test_formatVRegSize(t *testing.T) {
+	tests := []struct {
+		r   regalloc.VReg
+		_64 bool
+		exp string
+	}{
+		// Real registers.
+		{r: raxVReg, _64: true, exp: "%rax"},
+		{r: raxVReg, _64: false, exp: "%eax"},
+		{r: r15VReg, _64: true, exp: "%r15"},
+		{r: r15VReg, _64: false, exp: "%r15d"},
+		{r: xmm0VReg, _64: true, exp: "%xmm0"},
+		{r: xmm0VReg, _64: false, exp: "%xmm0"},
+		{r: xmm15VReg, _64: true, exp: "%xmm15"},
+		{r: xmm15VReg, _64: false, exp: "%xmm15"},
+		// Non-real registers.
+		{r: regalloc.VReg(5555).SetRegType(regalloc.RegTypeInt), _64: true, exp: "%r5555?"},
+		{r: regalloc.VReg(5555).SetRegType(regalloc.RegTypeInt), _64: false, exp: "%r5555d?"},
+		{r: regalloc.VReg(123).SetRegType(regalloc.RegTypeFloat), _64: true, exp: "%xmm123?"},
+		{r: regalloc.VReg(432).SetRegType(regalloc.RegTypeFloat), _64: false, exp: "%xmm432?"},
+	}
+	for _, tt := range tests {
+		t.Run(tt.exp, func(t *testing.T) {
+			require.Equal(t, tt.exp, formatVRegSized(tt.r, tt._64))
+		})
+	}
+}
diff --git a/internal/engine/wazevo/backend/isa/amd64/util_test.go b/internal/engine/wazevo/backend/isa/amd64/util_test.go
new file mode 100644
index 00000000..8d2d220e
--- /dev/null
+++ b/internal/engine/wazevo/backend/isa/amd64/util_test.go
@@ -0,0 +1,125 @@
+package amd64
+
+import (
+	"context"
+
+	"github.com/tetratelabs/wazero/internal/engine/wazevo/backend"
+	"github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc"
+	"github.com/tetratelabs/wazero/internal/engine/wazevo/ssa"
+)
+
+// mockCompiler implements backend.Compiler for testing.
+type mockCompiler struct {
+	currentGID  ssa.InstructionGroupID
+	vRegCounter int
+	vRegMap     map[ssa.Value]regalloc.VReg
+	definitions map[ssa.Value]*backend.SSAValueDefinition
+	sigs        map[ssa.SignatureID]*ssa.Signature
+	typeOf      map[regalloc.VRegID]ssa.Type
+	relocs      []backend.RelocationInfo
+	buf         []byte
+}
+
+func (m *mockCompiler) GetFunctionABI(sig *ssa.Signature) *backend.FunctionABI {
+	// TODO implement me
+	panic("implement me")
+}
+
+func (m *mockCompiler) SSABuilder() ssa.Builder { return nil }
+
+func (m *mockCompiler) LoopNestingForestRoots() []ssa.BasicBlock { panic("TODO") }
+
+func (m *mockCompiler) SourceOffsetInfo() []backend.SourceOffsetInfo { return nil }
+
+func (m *mockCompiler) AddSourceOffsetInfo(int64, ssa.SourceOffset) {}
+
+func (m *mockCompiler) AddRelocationInfo(funcRef ssa.FuncRef) {
+	m.relocs = append(m.relocs, backend.RelocationInfo{FuncRef: funcRef, Offset: int64(len(m.buf))})
+}
+
+func (m *mockCompiler) Emit4Bytes(b uint32) {
+	m.buf = append(m.buf, byte(b), byte(b>>8), byte(b>>16), byte(b>>24))
+}
+
+func (m *mockCompiler) EmitByte(b byte) {
+	m.buf = append(m.buf, b)
+}
+
+func (m *mockCompiler) Emit8Bytes(b uint64) {
+	m.buf = append(m.buf, byte(b), byte(b>>8), byte(b>>16), byte(b>>24), byte(b>>32), byte(b>>40), byte(b>>48), byte(b>>56))
+}
+
+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.ID()]
+}
+func (m *mockCompiler) Finalize(context.Context) {}
+func (m *mockCompiler) RegAlloc()                {}
+func (m *mockCompiler) Lower()                   {}
+func (m *mockCompiler) Format() string           { return "" }
+func (m *mockCompiler) Init()                    {}
+
+func newMockCompilationContext() *mockCompiler { //nolint
+	return &mockCompiler{
+		vRegMap:     make(map[ssa.Value]regalloc.VReg),
+		definitions: make(map[ssa.Value]*backend.SSAValueDefinition),
+		typeOf:      map[regalloc.VRegID]ssa.Type{},
+	}
+}
+
+// ResolveSignature implements backend.Compiler.
+func (m *mockCompiler) ResolveSignature(id ssa.SignatureID) *ssa.Signature {
+	return m.sigs[id]
+}
+
+// AllocateVReg implements backend.Compiler.
+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.ID()] = typ
+	return ret
+}
+
+// ValueDefinition implements backend.Compiler.
+func (m *mockCompiler) ValueDefinition(value ssa.Value) *backend.SSAValueDefinition {
+	definition, exists := m.definitions[value]
+	if !exists {
+		return nil
+	}
+	return definition
+}
+
+// VRegOf implements backend.Compiler.
+func (m *mockCompiler) VRegOf(value ssa.Value) regalloc.VReg {
+	vReg, exists := m.vRegMap[value]
+	if !exists {
+		panic("Value does not exist")
+	}
+	return vReg
+}
+
+// MatchInstr implements backend.Compiler.
+func (m *mockCompiler) MatchInstr(def *backend.SSAValueDefinition, opcode ssa.Opcode) bool {
+	instr := def.Instr
+	return def.IsFromInstr() &&
+		instr.Opcode() == opcode &&
+		instr.GroupID() == m.currentGID &&
+		def.RefCount < 2
+}
+
+// MatchInstrOneOf implements backend.Compiler.
+func (m *mockCompiler) MatchInstrOneOf(def *backend.SSAValueDefinition, opcodes []ssa.Opcode) ssa.Opcode {
+	for _, opcode := range opcodes {
+		if m.MatchInstr(def, opcode) {
+			return opcode
+		}
+	}
+	return ssa.OpcodeInvalid
+}
+
+// Compile implements backend.Compiler.
+func (m *mockCompiler) Compile(context.Context) (_ []byte, _ []backend.RelocationInfo, _ error) {
+	return
+}
diff --git a/internal/engine/wazevo/backend/isa/arm64/util_test.go b/internal/engine/wazevo/backend/isa/arm64/util_test.go
index 869495f1..6dd7862c 100644
--- a/internal/engine/wazevo/backend/isa/arm64/util_test.go
+++ b/internal/engine/wazevo/backend/isa/arm64/util_test.go
@@ -82,6 +82,14 @@ func (m *mockCompiler) Emit4Bytes(b uint32) {
 	m.buf = append(m.buf, byte(b), byte(b>>8), byte(b>>16), byte(b>>24))
 }
 
+func (m *mockCompiler) EmitByte(b byte) {
+	m.buf = append(m.buf, b)
+}
+
+func (m *mockCompiler) Emit8Bytes(b uint64) {
+	m.buf = append(m.buf, byte(b), byte(b>>8), byte(b>>16), byte(b>>24), byte(b>>32), byte(b>>40), byte(b>>48), byte(b>>56))
+}
+
 func (m *mockCompiler) Encode()     {}
 func (m *mockCompiler) Buf() []byte { return m.buf }
 func (m *mockCompiler) TypeOf(v regalloc.VReg) (ret ssa.Type) {