Repository: tetratelabs/wazero
Pull request #1971 (2024-01-29 17:51:42)

wazevo(amd64): float rounding, min/max


Patch:
diff --git a/cmd/wazero/wazero.go b/cmd/wazero/wazero.go
index 6bb6bb0e..45cfc5f1 100644
--- a/cmd/wazero/wazero.go
+++ b/cmd/wazero/wazero.go
@@ -275,6 +275,9 @@ func doRun(args []string, stdOut io.Writer, stdErr logging.Writer) int {
 	if useInterpreter {
 		rtc = wazero.NewRuntimeConfigInterpreter()
 	} else if useOptimizingCompiler {
+		if runtime.GOARCH != "arm64" {
+			panic("UseOptimizingCompiler is only supported on arm64")
+		}
 		rtc = opt.NewRuntimeConfigOptimizingCompiler()
 	} else {
 		rtc = wazero.NewRuntimeConfig()
diff --git a/experimental/opt/opt.go b/experimental/opt/opt.go
index ecffb001..fa796731 100644
--- a/experimental/opt/opt.go
+++ b/experimental/opt/opt.go
@@ -1,8 +1,6 @@
 package opt
 
 import (
-	"runtime"
-
 	"github.com/tetratelabs/wazero"
 )
 
@@ -14,9 +12,6 @@ type enabler interface {
 
 // NewRuntimeConfigOptimizingCompiler returns a new RuntimeConfig with the optimizing compiler enabled.
 func NewRuntimeConfigOptimizingCompiler() wazero.RuntimeConfig {
-	if runtime.GOARCH != "arm64" {
-		panic("UseOptimizingCompiler is only supported on arm64")
-	}
 	c := wazero.NewRuntimeConfig()
 	c.(enabler).EnableOptimizingCompiler()
 	return c
diff --git a/internal/engine/wazevo/backend/isa/amd64/instr.go b/internal/engine/wazevo/backend/isa/amd64/instr.go
index b34e8ffb..e0988450 100644
--- a/internal/engine/wazevo/backend/isa/amd64/instr.go
+++ b/internal/engine/wazevo/backend/isa/amd64/instr.go
@@ -69,6 +69,8 @@ func (i *instruction) String() string {
 		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(false), i.op2.format(false))
+	case xmmUnaryRmRImm:
+		return fmt.Sprintf("%s $%d, %s, %s", sseOpcode(i.u1), roundingMode(i.u2), i.op1.format(false), i.op2.format(false))
 	case unaryRmR:
 		var suffix string
 		if i.b1 {
@@ -178,7 +180,7 @@ func (i *instruction) String() string {
 		}
 		return fmt.Sprintf("%s%s %s, %s", op, suffix, i.op1.format(i.b1), i.op2.format(i.b1))
 	case setcc:
-		return fmt.Sprintf("set%s %s", cond(i.u1), i.op1.format(true))
+		return fmt.Sprintf("set%s %s", cond(i.u1), i.op2.format(true))
 	case cmove:
 		var suffix string
 		if i.b1 {
@@ -205,10 +207,8 @@ func (i *instruction) String() string {
 		panic("TODO")
 	case xmmMinMaxSeq:
 		panic("TODO")
-	case xmmCmove:
-		panic("TODO")
 	case xmmCmpRmR:
-		panic("TODO")
+		return fmt.Sprintf("%s %s, %s", sseOpcode(i.u1), i.op1.format(false), i.op2.format(false))
 	case xmmRmRImm:
 		panic("TODO")
 	case jmp:
@@ -235,6 +235,8 @@ func (i *instruction) String() string {
 		return fmt.Sprintf("callq *%s", i.op1.format(true))
 	case v128ConstIsland:
 		return fmt.Sprintf("v128ConstIsland (%#x, %#x)", i.u1, i.u2)
+	case xchg:
+		return fmt.Sprintf("xchg %s, %s", i.op1.format(true), i.op2.format(true))
 	default:
 		panic(fmt.Sprintf("BUG: %d", int(i.kind)))
 	}
@@ -583,6 +585,13 @@ const (
 	// value. This is characteristic of mov instructions.
 	xmmUnaryRmR
 
+	// XMM (scalar or vector) unary op with immediate: roundss, roundsd, etc.
+	//
+	// This differs from XMM_RM_R_IMM in that the dst register of
+	// XmmUnaryRmRImm is not used in the computation of the instruction dst
+	// value and so does not have to be a previously valid value.
+	xmmUnaryRmRImm
+
 	// XMM (scalar or vector) unary op (from xmm to mem): stores, movd, movq
 	xmmMovRM
 
@@ -607,10 +616,6 @@ const (
 	// 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
 
@@ -642,6 +647,11 @@ const (
 	// An instruction that will always trigger the illegal instruction exception.
 	ud2
 
+	// xchg swaps the contents of two gp registers.
+	// The instruction doesn't make sense before register allocation, so it doensn't
+	// have useKinds and defKinds to avoid being used by the register allocator.
+	xchg
+
 	// v128ConstIsland is 16 bytes (128-bit) constant that will be loaded into an XMM.
 	v128ConstIsland
 
@@ -666,6 +676,8 @@ func (k instructionKind) String() string {
 		return "gprToXmm"
 	case xmmUnaryRmR:
 		return "xmmUnaryRmR"
+	case xmmUnaryRmRImm:
+		return "xmmUnaryRmRImm"
 	case unaryRmR:
 		return "unaryRmR"
 	case not:
@@ -718,8 +730,6 @@ func (k instructionKind) String() string {
 		return "cvtFloatToUintSeq"
 	case xmmMinMaxSeq:
 		return "xmmMinMaxSeq"
-	case xmmCmove:
-		return "xmmCmove"
 	case xmmCmpRmR:
 		return "xmmCmpRmR"
 	case xmmRmRImm:
@@ -736,6 +746,8 @@ func (k instructionKind) String() string {
 		return "v128ConstIsland"
 	case ud2:
 		return "ud2"
+	case xchg:
+		return "xchg"
 	default:
 		panic("BUG")
 	}
@@ -1012,13 +1024,13 @@ func (i *instruction) asXmmRmiReg(op sseOpcode, rm operand, rd regalloc.VReg) *i
 	return i
 }
 
-func (i *instruction) asCmpRmiR(cmp bool, rm operand, rd regalloc.VReg, _64 bool) *instruction {
+func (i *instruction) asCmpRmiR(cmp bool, rm operand, rn regalloc.VReg, _64 bool) *instruction {
 	if rm.kind != operandKindReg && rm.kind != operandKindImm32 && rm.kind != operandKindMem {
 		panic("BUG")
 	}
 	i.kind = cmpRmiR
 	i.op1 = rm
-	i.op2 = newOperandReg(rd)
+	i.op2 = newOperandReg(rn)
 	if cmp {
 		i.u1 = 1
 	}
@@ -1028,7 +1040,7 @@ func (i *instruction) asCmpRmiR(cmp bool, rm operand, rd regalloc.VReg, _64 bool
 
 func (i *instruction) asSetcc(c cond, rd regalloc.VReg) *instruction {
 	i.kind = setcc
-	i.op1 = newOperandReg(rd)
+	i.op2 = newOperandReg(rd)
 	i.u1 = uint64(c)
 	return i
 }
@@ -1059,6 +1071,29 @@ func (i *instruction) asXmmUnaryRmR(op sseOpcode, rm operand, rd regalloc.VReg)
 	return i
 }
 
+func (i *instruction) asXmmUnaryRmRImm(op sseOpcode, imm uint8, rm operand, rd regalloc.VReg) *instruction {
+	if rm.kind != operandKindReg && rm.kind != operandKindMem {
+		panic("BUG")
+	}
+	i.kind = xmmUnaryRmRImm
+	i.op1 = rm
+	i.op2 = newOperandReg(rd)
+	i.u1 = uint64(op)
+	i.u2 = uint64(imm)
+	return i
+}
+
+func (i *instruction) asXmmCmpRmR(op sseOpcode, rm operand, rd regalloc.VReg) *instruction {
+	if rm.kind != operandKindReg && rm.kind != operandKindMem {
+		panic("BUG")
+	}
+	i.kind = xmmCmpRmR
+	i.op1 = rm
+	i.op2 = newOperandReg(rd)
+	i.u1 = uint64(op)
+	return i
+}
+
 func (i *instruction) asV128ConstIsland(lo, hi uint64) *instruction {
 	i.kind = v128ConstIsland
 	i.u1 = lo
@@ -1092,6 +1127,13 @@ func (i *instruction) asPush64(op operand) *instruction {
 	return i
 }
 
+func (i *instruction) asXCHG(rm, rd regalloc.VReg) *instruction {
+	i.kind = xchg
+	i.op1 = newOperandReg(rm)
+	i.op2 = newOperandReg(rd)
+	return i
+}
+
 type unaryRmROpcode byte
 
 const (
@@ -1638,6 +1680,30 @@ func (s sseOpcode) String() string {
 	}
 }
 
+type roundingMode uint8
+
+const (
+	roundingModeNearest roundingMode = iota
+	roundingModeDown
+	roundingModeUp
+	roundingModeZero
+)
+
+func (r roundingMode) String() string {
+	switch r {
+	case roundingModeNearest:
+		return "nearest"
+	case roundingModeDown:
+		return "down"
+	case roundingModeUp:
+		return "up"
+	case roundingModeZero:
+		return "zero"
+	default:
+		panic("BUG")
+	}
+}
+
 func linkInstr(prev, next *instruction) *instruction {
 	prev.next = next
 	next.prev = prev
@@ -1667,6 +1733,8 @@ var defKinds = [instrMax]defKind{
 	imm:             defKindOp2,
 	unaryRmR:        defKindOp2,
 	xmmUnaryRmR:     defKindOp2,
+	xmmUnaryRmRImm:  defKindOp2,
+	xmmCmpRmR:       defKindOp2,
 	xmmRmR:          defKindNone,
 	mov64MR:         defKindOp2,
 	movsxRmR:        defKindOp2,
@@ -1683,6 +1751,7 @@ var defKinds = [instrMax]defKind{
 	exitSequence:    defKindNone,
 	lea:             defKindOp2,
 	v128ConstIsland: defKindNone,
+	setcc:           defKindOp2,
 }
 
 // String implements fmt.Stringer.
@@ -1734,6 +1803,8 @@ var useKinds = [instrMax]useKind{
 	imm:             useKindNone,
 	unaryRmR:        useKindOp1,
 	xmmUnaryRmR:     useKindOp1,
+	xmmUnaryRmRImm:  useKindOp1,
+	xmmCmpRmR:       useKindOp1Op2Reg,
 	xmmRmR:          useKindOp1Op2Reg,
 	mov64MR:         useKindOp1,
 	movzxRmR:        useKindOp1,
@@ -1749,6 +1820,7 @@ var useKinds = [instrMax]useKind{
 	lea:             useKindOp1,
 	v128ConstIsland: useKindNone,
 	jmpTableIsland:  useKindNone,
+	setcc:           useKindNone,
 }
 
 func (u useKind) String() string {
diff --git a/internal/engine/wazevo/backend/isa/amd64/instr_encoding.go b/internal/engine/wazevo/backend/isa/amd64/instr_encoding.go
index 54c5f9f6..deb0d4f4 100644
--- a/internal/engine/wazevo/backend/isa/amd64/instr_encoding.go
+++ b/internal/engine/wazevo/backend/isa/amd64/instr_encoding.go
@@ -457,6 +457,36 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 			panic("BUG: invalid operand kind")
 		}
 
+	case xmmUnaryRmRImm:
+		var prefix legacyPrefixes
+		var opcode uint32
+		var opcodeNum uint32
+		op := sseOpcode(i.u1)
+		switch op {
+		case sseOpcodeRoundps:
+			prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0f3a08, 3
+		case sseOpcodeRoundss:
+			prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0f3a0a, 3
+		case sseOpcodeRoundpd:
+			prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0f3a09, 3
+		case sseOpcodeRoundsd:
+			prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0f3a0b, 3
+		}
+		rex := rexInfo(0).clearW()
+		dst := regEncodings[i.op2.r.RealReg()]
+		op1 := i.op1
+		if op1.kind == operandKindReg {
+			src := regEncodings[op1.r.RealReg()]
+			encodeRegReg(c, prefix, opcode, opcodeNum, dst, src, rex)
+		} else if i.op1.kind == operandKindMem {
+			m := i.op1.amode
+			encodeRegMem(c, prefix, opcode, opcodeNum, dst, m, rex)
+		} else {
+			panic("BUG: invalid operand kind")
+		}
+
+		c.EmitByte(byte(i.u2))
+
 	case unaryRmR:
 		var prefix legacyPrefixes
 		var opcode uint32
@@ -833,7 +863,7 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 		}
 	case setcc:
 		cc := cond(i.u1)
-		dst := regEncodings[i.op1.r.RealReg()]
+		dst := regEncodings[i.op2.r.RealReg()]
 		rex := rexInfo(0).clearW().always()
 		opcode := uint32(0x0f90) + uint32(cc)
 		encodeEncEnc(c, legacyPrefixesNone, opcode, 2, 0, uint8(dst), rex)
@@ -926,10 +956,44 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 		panic("TODO")
 	case xmmMinMaxSeq:
 		panic("TODO")
-	case xmmCmove:
-		panic("TODO")
 	case xmmCmpRmR:
-		panic("TODO")
+		var prefix legacyPrefixes
+		var opcode uint32
+		var opcodeNum uint32
+		rex := rexInfo(0)
+		_64 := i.b1
+		if _64 { // 64 bit.
+			rex = rex.setW()
+		} else {
+			rex = rex.clearW()
+		}
+
+		op := sseOpcode(i.u1)
+		switch op {
+		case sseOpcodePtest:
+			prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0f3817, 3
+		case sseOpcodeUcomisd:
+			prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0f2e, 2
+		case sseOpcodeUcomiss:
+			prefix, opcode, opcodeNum = legacyPrefixesNone, 0x0f2e, 2
+		default:
+			panic(fmt.Sprintf("Unsupported sseOpcode: %s", op))
+		}
+
+		dst := regEncodings[i.op2.r.RealReg()]
+		op1 := i.op1
+		switch op1.kind {
+		case operandKindReg:
+			reg := regEncodings[op1.r.RealReg()]
+			encodeRegReg(c, prefix, opcode, opcodeNum, dst, reg, rex)
+
+		case operandKindMem:
+			m := op1.amode
+			encodeRegMem(c, prefix, opcode, opcodeNum, dst, m, rex)
+
+		default:
+			panic("BUG: invalid operand kind")
+		}
 	case xmmRmRImm:
 		panic("TODO")
 	case jmp:
@@ -1040,6 +1104,12 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 		c.Emit8Bytes(lo)
 		c.Emit8Bytes(hi)
 
+	case xchg:
+		r1, r2 := i.op1.r.RealReg(), i.op2.r.RealReg()
+
+		enc1, enc2 := regEncodings[r1], regEncodings[r2]
+		encodeRegReg(c, legacyPrefixesNone, 0x87, 1, enc2, enc1, rexInfo(0).setW())
+
 	default:
 		panic(fmt.Sprintf("TODO: %v", i.kind))
 	}
diff --git a/internal/engine/wazevo/backend/isa/amd64/machine.go b/internal/engine/wazevo/backend/isa/amd64/machine.go
index 0077592a..f0fdd225 100644
--- a/internal/engine/wazevo/backend/isa/amd64/machine.go
+++ b/internal/engine/wazevo/backend/isa/amd64/machine.go
@@ -290,6 +290,16 @@ func (m *machine) LowerInstr(instr *ssa.Instruction) {
 		m.lowerFabsFneg(instr)
 	case ssa.OpcodeFneg:
 		m.lowerFabsFneg(instr)
+	case ssa.OpcodeCeil:
+		m.lowerRound(instr, roundingModeUp)
+	case ssa.OpcodeFloor:
+		m.lowerRound(instr, roundingModeDown)
+	case ssa.OpcodeTrunc:
+		m.lowerRound(instr, roundingModeZero)
+	case ssa.OpcodeNearest:
+		m.lowerRound(instr, roundingModeNearest)
+	case ssa.OpcodeFmin, ssa.OpcodeFmax:
+		m.lowerFminFmax(instr)
 	case ssa.OpcodeSqrt:
 		m.lowerSqrt(instr)
 	case ssa.OpcodeUndefined:
@@ -315,11 +325,99 @@ func (m *machine) LowerInstr(instr *ssa.Instruction) {
 	case ssa.OpcodeSExtend, ssa.OpcodeUExtend:
 		from, to, signed := instr.ExtendData()
 		m.lowerExtend(instr.Arg(), instr.Return(), from, to, signed)
+	case ssa.OpcodeIcmp:
+		m.lowerIcmp(instr)
+	case ssa.OpcodeSelect:
+		cval, x, y := instr.SelectData()
+		m.lowerSelect(x, y, cval, instr.Return())
 	default:
 		panic("TODO: lowering " + op.String())
 	}
 }
 
+func (m *machine) lowerIcmp(instr *ssa.Instruction) {
+	x, y, c := instr.IcmpData()
+	m.lowerIcmpToFlag(m.c.ValueDefinition(x), m.c.ValueDefinition(y), x.Type() == ssa.TypeI64)
+	rd := m.c.VRegOf(instr.Return())
+	scc := m.allocateInstr().asSetcc(condFromSSAIntCmpCond(c), rd)
+	m.insert(scc)
+}
+
+func (m *machine) lowerSelect(x, y, cval, ret ssa.Value) {
+	xo, yo := m.getOperand_Mem_Reg(m.c.ValueDefinition(x)), m.getOperand_Reg(m.c.ValueDefinition(y))
+	rd := m.c.VRegOf(ret)
+
+	var cond cond
+	cvalDef := m.c.ValueDefinition(cval)
+	switch m.c.MatchInstrOneOf(cvalDef, condBranchMatches[:]) {
+	case ssa.OpcodeIcmp:
+		icmp := cvalDef.Instr
+		xc, yc, cc := icmp.IcmpData()
+		m.lowerIcmpToFlag(m.c.ValueDefinition(xc), m.c.ValueDefinition(yc), xc.Type() == ssa.TypeI64)
+		cond = condFromSSAIntCmpCond(cc)
+		icmp.Lowered()
+	default: // TODO: match ssa.OpcodeFcmp for optimization, but seems a bit complex.
+		cv := m.getOperand_Reg(cvalDef).r
+		test := m.allocateInstr().asCmpRmiR(false, newOperandReg(cv), cv, false)
+		m.insert(test)
+		cond = condNZ
+	}
+
+	if typ := x.Type(); typ.IsInt() {
+		_64 := typ.Bits() == 64
+		mov := m.allocateInstr()
+		switch yo.kind {
+		case operandKindReg:
+			mov.asMovRR(yo.r, rd, _64)
+		case operandKindMem:
+			if _64 {
+				mov.asMov64MR(yo, rd)
+			} else {
+				mov.asMovzxRmR(extModeLQ, yo, rd)
+			}
+		default:
+			panic("BUG")
+		}
+		m.insert(mov)
+		cmov := m.allocateInstr().asCmove(cond, xo, rd, _64)
+		m.insert(cmov)
+	} else {
+		mov := m.allocateInstr()
+		switch typ {
+		case ssa.TypeF32:
+			mov.asXmmUnaryRmR(sseOpcodeMovss, yo, rd)
+		case ssa.TypeF64:
+			mov.asXmmUnaryRmR(sseOpcodeMovsd, yo, rd)
+		case ssa.TypeV128:
+			mov.asXmmUnaryRmR(sseOpcodeMovdqu, yo, rd)
+		default:
+			panic("BUG")
+		}
+		m.insert(mov)
+
+		jcc := m.allocateInstr()
+		m.insert(jcc)
+
+		cmov := m.allocateInstr()
+		m.insert(cmov)
+		switch typ {
+		case ssa.TypeF32:
+			cmov.asXmmUnaryRmR(sseOpcodeMovss, xo, rd)
+		case ssa.TypeF64:
+			cmov.asXmmUnaryRmR(sseOpcodeMovsd, xo, rd)
+		case ssa.TypeV128:
+			cmov.asXmmUnaryRmR(sseOpcodeMovdqu, xo, rd)
+		default:
+			panic("BUG")
+		}
+
+		nop, end := m.allocateBrTarget()
+		m.insert(nop)
+
+		jcc.asJmpIf(cond.invert(), newOperandLabel(end))
+	}
+}
+
 func (m *machine) lowerExtend(_arg, ret ssa.Value, from, to byte, signed bool) {
 	rd := m.c.VRegOf(ret)
 	arg := m.getOperand_Mem_Reg(m.c.ValueDefinition(_arg))
@@ -506,7 +604,7 @@ func (m *machine) lowerUnaryRmR(si *ssa.Instruction, op unaryRmROpcode) {
 	_64 := x.Type().Bits() == 64
 
 	xDef := m.c.ValueDefinition(x)
-	rm := m.getOperand_Imm32_Reg(xDef)
+	rm := m.getOperand_Mem_Reg(xDef)
 	rd := m.c.VRegOf(si.Return())
 
 	instr := m.allocateInstr()
@@ -1307,3 +1405,137 @@ func (m *machine) lowerIDivRem(si *ssa.Instruction, isDiv bool, signed bool) {
 		m.copyTo(rdxVReg, rd)
 	}
 }
+
+func (m *machine) lowerRound(instr *ssa.Instruction, imm roundingMode) {
+	x := instr.Arg()
+	if !x.Type().IsFloat() {
+		panic("BUG?")
+	}
+	var op sseOpcode
+	if x.Type().Bits() == 64 {
+		op = sseOpcodeRoundsd
+	} else {
+		op = sseOpcodeRoundss
+	}
+
+	xDef := m.c.ValueDefinition(x)
+	rm := m.getOperand_Mem_Reg(xDef)
+	rd := m.c.VRegOf(instr.Return())
+
+	xmm := m.allocateInstr().asXmmUnaryRmRImm(op, uint8(imm), rm, rd)
+	m.insert(xmm)
+}
+
+func (m *machine) lowerFminFmax(instr *ssa.Instruction) {
+	x, y := instr.Arg2()
+	if !x.Type().IsFloat() {
+		panic("BUG?")
+	}
+
+	_64 := x.Type().Bits() == 64
+	isMin := instr.Opcode() == ssa.OpcodeFmin
+	var minMaxOp sseOpcode
+
+	switch {
+	case _64 && isMin:
+		minMaxOp = sseOpcodeMinpd
+	case _64 && !isMin:
+		minMaxOp = sseOpcodeMaxpd
+	case !_64 && isMin:
+		minMaxOp = sseOpcodeMinps
+	case !_64 && !isMin:
+		minMaxOp = sseOpcodeMaxps
+	}
+
+	xDef, yDef := m.c.ValueDefinition(x), m.c.ValueDefinition(y)
+	rm := m.getOperand_Reg(xDef)
+	rn := m.getOperand_Mem_Reg(yDef)
+	rd := m.c.VRegOf(instr.Return())
+
+	tmp := m.copyToTmp(rm.r)
+
+	// Check if this is (either x1 or x2 is NaN) or (x1 equals x2) case.
+	cmp := m.allocateInstr()
+	if _64 {
+		cmp.asXmmCmpRmR(sseOpcodeUcomisd, rn, tmp)
+	} else {
+		cmp.asXmmCmpRmR(sseOpcodeUcomiss, rn, tmp)
+	}
+	m.insert(cmp)
+
+	// At this point, we have the three cases of conditional flags below
+	// (See https://www.felixcloutier.com/x86/ucomiss#operation for detail.)
+	//
+	// 1) Two values are NaN-free and different: All flags are cleared.
+	// 2) Two values are NaN-free and equal: Only ZF flags is set.
+	// 3) One of Two values is NaN: ZF, PF and CF flags are set.
+
+	// Jump instruction to handle 1) case by checking the ZF flag
+	// as ZF is only set for 2) and 3) cases.
+	nanFreeOrDiffJump := m.allocateInstr()
+	m.insert(nanFreeOrDiffJump)
+
+	// Start handling 2) and 3).
+
+	// Jump if one of two values is NaN by checking the parity flag (PF).
+	ifIsNan := m.allocateInstr()
+	m.insert(ifIsNan)
+
+	// Start handling 2) NaN-free and equal.
+
+	// Before we exit this case, we have to ensure that positive zero (or negative zero for min instruction) is
+	// returned if two values are positive and negative zeros.
+	var op sseOpcode
+	switch {
+	case !_64 && isMin:
+		op = sseOpcodeOrps
+	case _64 && isMin:
+		op = sseOpcodeOrpd
+	case !_64 && !isMin:
+		op = sseOpcodeAndps
+	case _64 && !isMin:
+		op = sseOpcodeAndpd
+	}
+	orAnd := m.allocateInstr()
+	orAnd.asXmmRmR(op, rn, tmp)
+	m.insert(orAnd)
+
+	// Done, jump to end.
+	sameExitJump := m.allocateInstr()
+	m.insert(sameExitJump)
+
+	// Start handling 3) either is NaN.
+	isNanTarget, isNan := m.allocateBrTarget()
+	m.insert(isNanTarget)
+	ifIsNan.asJmpIf(condP, newOperandLabel(isNan))
+
+	// We emit the ADD instruction to produce the NaN in tmp.
+	add := m.allocateInstr()
+	if _64 {
+		add.asXmmRmR(sseOpcodeAddsd, rn, tmp)
+	} else {
+		add.asXmmRmR(sseOpcodeAddss, rn, tmp)
+	}
+	m.insert(add)
+
+	// Exit from the NaN case branch.
+	nanExitJmp := m.allocateInstr()
+	m.insert(nanExitJmp)
+
+	// Start handling 1).
+	doMinMaxTarget, doMinMax := m.allocateBrTarget()
+	m.insert(doMinMaxTarget)
+	nanFreeOrDiffJump.asJmpIf(condNZ, newOperandLabel(doMinMax))
+
+	// Now handle the NaN-free and different values case.
+	minMax := m.allocateInstr()
+	minMax.asXmmRmR(minMaxOp, rn, tmp)
+	m.insert(minMax)
+
+	endNop, end := m.allocateBrTarget()
+	m.insert(endNop)
+	nanExitJmp.asJmp(newOperandLabel(end))
+	sameExitJump.asJmp(newOperandLabel(end))
+
+	m.copyTo(tmp, rd)
+}
diff --git a/internal/engine/wazevo/backend/isa/amd64/machine_regalloc.go b/internal/engine/wazevo/backend/isa/amd64/machine_regalloc.go
index 01759d08..aa7f87eb 100644
--- a/internal/engine/wazevo/backend/isa/amd64/machine_regalloc.go
+++ b/internal/engine/wazevo/backend/isa/amd64/machine_regalloc.go
@@ -105,8 +105,29 @@ func (m *machine) ClobberedRegisters(regs []regalloc.VReg) {
 
 // Swap implements backend.RegAllocFunctionMachine.
 func (m *machine) Swap(cur *instruction, x1, x2, tmp regalloc.VReg) {
-	// TODO implement me
-	panic("implement me")
+	if x1.RegType() == regalloc.RegTypeInt {
+		prevNext := cur.next
+		xc := m.allocateInstr().asXCHG(x1, x2)
+		cur = linkInstr(cur, xc)
+		linkInstr(cur, prevNext)
+	} else {
+		if tmp.Valid() {
+			prevNext := cur.next
+			m.InsertMoveBefore(tmp, x1, prevNext)
+			m.InsertMoveBefore(x1, x2, prevNext)
+			m.InsertMoveBefore(x2, tmp, prevNext)
+		} else {
+			prevNext := cur.next
+			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().asXmmUnaryRmR(sseOpcodeMovdqa, newOperandReg(x2), x1))
+			linkInstr(cur, prevNext)
+			// Then reload the original value on x1 from stack to r2.
+			m.InsertReloadRegisterAt(x1.SetRealReg(r2), cur, true)
+		}
+	}
 }
 
 // LastInstrForInsertion implements backend.RegAllocFunctionMachine.
diff --git a/internal/engine/wazevo/wazevoapi/debug_options.go b/internal/engine/wazevo/wazevoapi/debug_options.go
index 73b69c0d..1a5c0bc3 100644
--- a/internal/engine/wazevo/wazevoapi/debug_options.go
+++ b/internal/engine/wazevo/wazevoapi/debug_options.go
@@ -196,18 +196,3 @@ func GetCurrentFunctionIndex(ctx context.Context) int {
 	ret, _ := ctx.Value(currentFunctionIndexKey{}).(int)
 	return ret
 }
-
-// ----- High Register Pressure -----
-
-type highRegisterPressureContextKey struct{}
-
-// EnableHighRegisterPressure enables the high register pressure mode.
-func EnableHighRegisterPressure(ctx context.Context) context.Context {
-	ctx = context.WithValue(ctx, highRegisterPressureContextKey{}, true)
-	return ctx
-}
-
-// IsHighRegisterPressure returns true if the current compilation is under high register pressure.
-func IsHighRegisterPressure(ctx context.Context) bool {
-	return ctx.Value(highRegisterPressureContextKey{}) != nil
-}


Test patch:
diff --git a/internal/engine/wazevo/backend/isa/amd64/instr_encoding_test.go b/internal/engine/wazevo/backend/isa/amd64/instr_encoding_test.go
index c7aefd4c..e419bd5b 100644
--- a/internal/engine/wazevo/backend/isa/amd64/instr_encoding_test.go
+++ b/internal/engine/wazevo/backend/isa/amd64/instr_encoding_test.go
@@ -1172,6 +1172,154 @@ func TestInstruction_format_encode(t *testing.T) {
 			want:       "f2450f51fb",
 			wantFormat: "sqrtsd %xmm11, %xmm15",
 		},
+		{
+			setup: func(i *instruction) {
+				i.asXmmUnaryRmRImm(sseOpcodeRoundss, uint8(roundingModeNearest), newOperandReg(xmm11VReg), xmm15VReg)
+			},
+			want:       "66450f3a0afb00",
+			wantFormat: "roundss $0, %xmm11, %xmm15",
+		},
+		{
+			setup: func(i *instruction) {
+				i.asXmmUnaryRmRImm(sseOpcodeRoundss, uint8(roundingModeDown), newOperandReg(xmm11VReg), xmm15VReg)
+			},
+			want:       "66450f3a0afb01",
+			wantFormat: "roundss $1, %xmm11, %xmm15",
+		},
+		{
+			setup: func(i *instruction) {
+				i.asXmmUnaryRmRImm(sseOpcodeRoundss, uint8(roundingModeUp), newOperandReg(xmm11VReg), xmm15VReg)
+			},
+			want:       "66450f3a0afb02",
+			wantFormat: "roundss $2, %xmm11, %xmm15",
+		},
+		{
+			setup: func(i *instruction) {
+				i.asXmmUnaryRmRImm(sseOpcodeRoundss, uint8(roundingModeZero), newOperandReg(xmm11VReg), xmm15VReg)
+			},
+			want:       "66450f3a0afb03",
+			wantFormat: "roundss $3, %xmm11, %xmm15",
+		},
+		{
+			setup: func(i *instruction) {
+				i.asXmmUnaryRmRImm(sseOpcodeRoundss, uint8(roundingModeNearest), newOperandMem(newAmodeImmReg(123, raxVReg)), xmm15VReg)
+			},
+			want:       "66440f3a0a787b00",
+			wantFormat: "roundss $0, 123(%rax), %xmm15",
+		},
+		{
+			setup: func(i *instruction) {
+				i.asXmmUnaryRmRImm(sseOpcodeRoundss, uint8(roundingModeDown), newOperandMem(newAmodeImmReg(123, raxVReg)), xmm15VReg)
+			},
+			want:       "66440f3a0a787b01",
+			wantFormat: "roundss $1, 123(%rax), %xmm15",
+		},
+		{
+			setup: func(i *instruction) {
+				i.asXmmUnaryRmRImm(sseOpcodeRoundss, uint8(roundingModeUp), newOperandMem(newAmodeImmReg(123, raxVReg)), xmm15VReg)
+			},
+			want:       "66440f3a0a787b02",
+			wantFormat: "roundss $2, 123(%rax), %xmm15",
+		},
+		{
+			setup: func(i *instruction) {
+				i.asXmmUnaryRmRImm(sseOpcodeRoundss, uint8(roundingModeZero), newOperandMem(newAmodeImmReg(123, raxVReg)), xmm15VReg)
+			},
+			want:       "66440f3a0a787b03",
+			wantFormat: "roundss $3, 123(%rax), %xmm15",
+		},
+		{
+			setup: func(i *instruction) {
+				i.asXmmUnaryRmRImm(sseOpcodeRoundsd, uint8(roundingModeNearest), newOperandReg(xmm11VReg), xmm15VReg)
+			},
+			want:       "66450f3a0bfb00",
+			wantFormat: "roundsd $0, %xmm11, %xmm15",
+		},
+		{
+			setup: func(i *instruction) {
+				i.asXmmUnaryRmRImm(sseOpcodeRoundsd, uint8(roundingModeDown), newOperandReg(xmm11VReg), xmm15VReg)
+			},
+			want:       "66450f3a0bfb01",
+			wantFormat: "roundsd $1, %xmm11, %xmm15",
+		},
+		{
+			setup: func(i *instruction) {
+				i.asXmmUnaryRmRImm(sseOpcodeRoundsd, uint8(roundingModeUp), newOperandReg(xmm11VReg), xmm15VReg)
+			},
+			want:       "66450f3a0bfb02",
+			wantFormat: "roundsd $2, %xmm11, %xmm15",
+		},
+		{
+			setup: func(i *instruction) {
+				i.asXmmUnaryRmRImm(sseOpcodeRoundsd, uint8(roundingModeZero), newOperandReg(xmm11VReg), xmm15VReg)
+			},
+			want:       "66450f3a0bfb03",
+			wantFormat: "roundsd $3, %xmm11, %xmm15",
+		},
+		{
+			setup: func(i *instruction) {
+				i.asXmmUnaryRmRImm(sseOpcodeRoundsd, uint8(roundingModeNearest), newOperandMem(newAmodeImmReg(123, raxVReg)), xmm15VReg)
+			},
+			want:       "66440f3a0b787b00",
+			wantFormat: "roundsd $0, 123(%rax), %xmm15",
+		},
+		{
+			setup: func(i *instruction) {
+				i.asXmmUnaryRmRImm(sseOpcodeRoundsd, uint8(roundingModeDown), newOperandMem(newAmodeImmReg(123, raxVReg)), xmm15VReg)
+			},
+			want:       "66440f3a0b787b01",
+			wantFormat: "roundsd $1, 123(%rax), %xmm15",
+		},
+		{
+			setup: func(i *instruction) {
+				i.asXmmUnaryRmRImm(sseOpcodeRoundsd, uint8(roundingModeUp), newOperandMem(newAmodeImmReg(123, raxVReg)), xmm15VReg)
+			},
+			want:       "66440f3a0b787b02",
+			wantFormat: "roundsd $2, 123(%rax), %xmm15",
+		},
+		{
+			setup: func(i *instruction) {
+				i.asXmmUnaryRmRImm(sseOpcodeRoundsd, uint8(roundingModeZero), newOperandMem(newAmodeImmReg(123, raxVReg)), xmm15VReg)
+			},
+			want:       "66440f3a0b787b03",
+			wantFormat: "roundsd $3, 123(%rax), %xmm15",
+		},
+		{
+			setup:      func(i *instruction) { i.asXmmCmpRmR(sseOpcodePtest, newOperandReg(xmm1VReg), xmm0VReg) },
+			want:       "660f3817c1",
+			wantFormat: "ptest %xmm1, %xmm0",
+		},
+		{
+			setup:      func(i *instruction) { i.asXmmCmpRmR(sseOpcodeUcomisd, newOperandReg(xmm1VReg), xmm0VReg) },
+			want:       "660f2ec1",
+			wantFormat: "ucomisd %xmm1, %xmm0",
+		},
+		{
+			setup:      func(i *instruction) { i.asXmmCmpRmR(sseOpcodeUcomiss, newOperandReg(xmm1VReg), xmm0VReg) },
+			want:       "0f2ec1",
+			wantFormat: "ucomiss %xmm1, %xmm0",
+		},
+		{
+			setup: func(i *instruction) {
+				i.asXmmCmpRmR(sseOpcodePtest, newOperandMem(newAmodeImmReg(123, raxVReg)), xmm0VReg)
+			},
+			want:       "660f3817407b",
+			wantFormat: "ptest 123(%rax), %xmm0",
+		},
+		{
+			setup: func(i *instruction) {
+				i.asXmmCmpRmR(sseOpcodeUcomisd, newOperandMem(newAmodeImmReg(123, raxVReg)), xmm0VReg)
+			},
+			want:       "660f2e407b",
+			wantFormat: "ucomisd 123(%rax), %xmm0",
+		},
+		{
+			setup: func(i *instruction) {
+				i.asXmmCmpRmR(sseOpcodeUcomiss, newOperandMem(newAmodeImmReg(123, raxVReg)), xmm0VReg)
+			},
+			want:       "0f2e407b",
+			wantFormat: "ucomiss 123(%rax), %xmm0",
+		},
 		// movd
 		{
 			setup:      func(i *instruction) { i.asGprToXmm(sseOpcodeMovd, newOperandReg(rdiVReg), xmm0VReg, false) },
@@ -2876,6 +3024,21 @@ func TestInstruction_format_encode(t *testing.T) {
 			want:       "eeeeeeeeffffffffbbbbbbbbaaaaaaaa",
 			wantFormat: "v128ConstIsland (0xffffffffeeeeeeee, 0xaaaaaaaabbbbbbbb)",
 		},
+		{
+			setup:      func(i *instruction) { i.asXCHG(r11VReg, r14VReg) },
+			want:       "4d87f3",
+			wantFormat: "xchg %r11, %r14",
+		},
+		{
+			setup:      func(i *instruction) { i.asXCHG(r15VReg, raxVReg) },
+			want:       "4987c7",
+			wantFormat: "xchg %r15, %rax",
+		},
+		{
+			setup:      func(i *instruction) { i.asXCHG(rbxVReg, rsiVReg) },
+			want:       "4887f3",
+			wantFormat: "xchg %rbx, %rsi",
+		},
 	} {
 		tc := tc
 		t.Run(tc.wantFormat, func(t *testing.T) {
diff --git a/internal/engine/wazevo/backend/isa/amd64/machine_regalloc_test.go b/internal/engine/wazevo/backend/isa/amd64/machine_regalloc_test.go
index 5d01c018..f86d17b8 100644
--- a/internal/engine/wazevo/backend/isa/amd64/machine_regalloc_test.go
+++ b/internal/engine/wazevo/backend/isa/amd64/machine_regalloc_test.go
@@ -160,3 +160,73 @@ func TestMachine_InsertMoveBefore(t *testing.T) {
 		})
 	}
 }
+
+func TestMachineSwap(t *testing.T) {
+	for _, tc := range []struct {
+		x1, x2, tmp regalloc.VReg
+		expected    string
+	}{
+		{
+			x1:  r15VReg,
+			x2:  raxVReg,
+			tmp: rdiVReg,
+			expected: `
+	ud2
+	xchg %r15, %rax
+	exit_sequence %rsi
+`,
+		},
+		{
+			x1: r15VReg,
+			x2: raxVReg,
+			expected: `
+	ud2
+	xchg %r15, %rax
+	exit_sequence %rsi
+`,
+		},
+		{
+			x1:  xmm1VReg,
+			x2:  xmm12VReg,
+			tmp: xmm11VReg,
+			expected: `
+	ud2
+	movdqu %xmm1, %xmm11
+	movdqu %xmm12, %xmm1
+	movdqu %xmm11, %xmm12
+	exit_sequence %rsi
+`,
+		},
+		{
+			x1: xmm1VReg,
+			x2: xmm12VReg,
+			// Tmp not given.
+			expected: `
+	ud2
+	movsd %xmm1, (%rsp)
+	movdqa %xmm12, %xmm1
+	movsd (%rsp), %xmm12
+	exit_sequence %rsi
+`,
+		},
+	} {
+		t.Run(tc.expected, func(t *testing.T) {
+			ctx, _, m := newSetupWithMockContext()
+
+			ctx.typeOf = map[regalloc.VRegID]ssa.Type{
+				r15VReg.ID(): ssa.TypeI64, raxVReg.ID(): ssa.TypeI64,
+				xmm1VReg.ID(): ssa.TypeF64, xmm12VReg.ID(): ssa.TypeF64,
+			}
+			cur, i2 := m.allocateInstr().asUD2(), m.allocateInstr().asExitSeq(rsiVReg)
+			cur.next = i2
+			i2.prev = cur
+
+			m.Swap(cur, tc.x1, tc.x2, tc.tmp)
+			m.ectx.RootInstr = cur
+
+			require.Equal(t, tc.expected, m.Format())
+
+			m.encodeWithoutSSA(cur)
+		})
+	}
+}
diff --git a/internal/engine/wazevo/backend/regalloc/regalloc_test.go b/internal/engine/wazevo/backend/regalloc/regalloc_test.go
index 37d01b80..17183d14 100644
--- a/internal/engine/wazevo/backend/regalloc/regalloc_test.go
+++ b/internal/engine/wazevo/backend/regalloc/regalloc_test.go
@@ -360,9 +360,6 @@ func TestAllocator_livenessAnalysis(t *testing.T) {
 					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",
-						blockID, exp.Format(a.regInfo), blockID, actual.Format(a.regInfo))
-
 					require.Equal(t, exp.liveOuts, actual.liveOuts, "live outs")
 					require.Equal(t, exp.liveIns, actual.liveIns, "live ins")
 				})
diff --git a/internal/engine/wazevo/e2e_test.go b/internal/engine/wazevo/e2e_test.go
index 29f786a2..d63640ac 100644
--- a/internal/engine/wazevo/e2e_test.go
+++ b/internal/engine/wazevo/e2e_test.go
@@ -13,7 +13,11 @@ import (
 	"github.com/tetratelabs/wazero/api"
 	"github.com/tetratelabs/wazero/experimental"
 	"github.com/tetratelabs/wazero/experimental/logging"
+	"github.com/tetratelabs/wazero/experimental/opt"
 	"github.com/tetratelabs/wazero/internal/engine/wazevo/testcases"
+	"github.com/tetratelabs/wazero/internal/integration_test/spectest"
+	v1 "github.com/tetratelabs/wazero/internal/integration_test/spectest/v1"
+	v2 "github.com/tetratelabs/wazero/internal/integration_test/spectest/v2"
 	"github.com/tetratelabs/wazero/internal/leb128"
 	"github.com/tetratelabs/wazero/internal/testing/binaryencoding"
 	"github.com/tetratelabs/wazero/internal/testing/dwarftestdata"
@@ -278,7 +282,8 @@ func TestE2E(t *testing.T) {
 			calls: []callCase{
 				{
 					params: []uint64{
-						math.Float64bits(25), math.Float64bits(5), uint64(math.Float32bits(25)), uint64(math.Float32bits(5)),
+						math.Float64bits(25), math.Float64bits(5), math.Float64bits(1.4),
+						uint64(math.Float32bits(25)), uint64(math.Float32bits(5)), uint64(math.Float32bits(1.4)),
 					},
 					expResults: []uint64{
 						math.Float64bits(-25),
@@ -290,6 +295,11 @@ func TestE2E(t *testing.T) {
 						math.Float64bits(125),
 						math.Float64bits(5),
 
+						math.Float64bits(1),
+						math.Float64bits(1),
+						math.Float64bits(2),
+						math.Float64bits(1),
+
 						uint64(math.Float32bits(-25)),
 						uint64(math.Float32bits(25)),
 
@@ -298,6 +308,70 @@ func TestE2E(t *testing.T) {
 						uint64(math.Float32bits(20)),
 						uint64(math.Float32bits(125)),
 						uint64(math.Float32bits(5)),
+
+						uint64(math.Float32bits(1)),
+						uint64(math.Float32bits(1)),
+						uint64(math.Float32bits(2)),
+						uint64(math.Float32bits(1)),
+					},
+				},
+			},
+		},
+		{
+			name: "min_max_float", m: testcases.MinMaxFloat.Module,
+			calls: []callCase{
+				{
+					params: []uint64{
+						math.Float64bits(25), math.Float64bits(5),
+						uint64(math.Float32bits(25)), uint64(math.Float32bits(5)),
+					},
+					expResults: []uint64{
+						math.Float64bits(5),
+						math.Float64bits(25),
+						uint64(math.Float32bits(5)),
+						uint64(math.Float32bits(25)),
+					},
+				},
+				{
+					// Left-hand side is NaN.
+					params: []uint64{
+						0x7ff8000000000001, math.Float64bits(5),
+						uint64(0x7ff80001), uint64(math.Float32bits(5)),
+					},
+					expResults: []uint64{
+						0x7ff8000000000001,
+						0x7ff8000000000001,
+
+						uint64(0x7ff80001),
+						uint64(0x7ff80001),
+					},
+				},
+				{
+					// Both NaN.
+					params: []uint64{
+						0x7ff8000000000001, 0x7ff8000000000001,
+						uint64(0x7ff80001), uint64(0x7ff80001),
+					},
+					expResults: []uint64{
+						0x7ff8000000000001,
+						0x7ff8000000000001,
+
+						uint64(0x7ff80001),
+						uint64(0x7ff80001),
+					},
+				},
+				{
+					// Negative zero and zero.
+					params: []uint64{
+						0x8000000000000000, 0,
+						uint64(0), uint64(0x80000000),
+					},
+					expResults: []uint64{
+						0x8000000000000000,
+						0,
+
+						uint64(0x80000000),
+						uint64(0),
 					},
 				},
 			},
@@ -544,7 +618,7 @@ func TestE2E(t *testing.T) {
 				t.Run(name, func(t *testing.T) {
 					cache, err := wazero.NewCompilationCacheWithDir(tmp)
 					require.NoError(t, err)
-					config := newRuntimeConfigOptimizingCompiler().WithCompilationCache(cache)
+					config := opt.NewRuntimeConfigOptimizingCompiler().WithCompilationCache(cache)
 
 					ctx := context.Background()
 					r := wazero.NewRuntimeWithConfig(ctx, config)
@@ -582,7 +656,7 @@ func TestE2E(t *testing.T) {
 								require.Equal(t, len(cc.expResults), len(result))
 								for i := range cc.expResults {
 									if cc.expResults[i] != result[i] {
-										t.Errorf("result[%d]: exp %d, got %d", i, cc.expResults[i], result[i])
+										t.Errorf("result[%d]: exp %x, got %x", i, cc.expResults[i], result[i])
 									}
 								}
 							}
@@ -609,7 +683,7 @@ func TestE2E_host_functions(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			ctx := tc.ctx
 
-			config := newRuntimeConfigOptimizingCompiler()
+			config := opt.NewRuntimeConfigOptimizingCompiler()
 
 			r := wazero.NewRuntimeWithConfig(ctx, config)
 			defer func() {
@@ -691,7 +765,7 @@ func TestE2E_host_functions(t *testing.T) {
 }
 
 func TestE2E_stores(t *testing.T) {
-	config := newRuntimeConfigOptimizingCompiler()
+	config := opt.NewRuntimeConfigOptimizingCompiler()
 
 	ctx := context.Background()
 	r := wazero.NewRuntimeWithConfig(ctx, config)
@@ -778,7 +852,7 @@ func TestE2E_reexported_memory(t *testing.T) {
 		CodeSection:       []wasm.Code{{Body: []byte{wasm.OpcodeI32Const, 10, wasm.OpcodeMemoryGrow, 0, wasm.OpcodeEnd}}},
 	}
 
-	config := newRuntimeConfigOptimizingCompiler()
+	config := opt.NewRuntimeConfigOptimizingCompiler()
 
 	ctx := context.Background()
 	r := wazero.NewRuntimeWithConfig(ctx, config)
@@ -827,7 +901,7 @@ func TestStackUnwind_panic_in_host(t *testing.T) {
 		},
 	}
 
-	config := newRuntimeConfigOptimizingCompiler()
+	config := opt.NewRuntimeConfigOptimizingCompiler()
 
 	ctx := context.Background()
 	r := wazero.NewRuntimeWithConfig(ctx, config)
@@ -877,7 +951,7 @@ func TestStackUnwind_unreachable(t *testing.T) {
 		},
 	}
 
-	config := newRuntimeConfigOptimizingCompiler()
+	config := opt.NewRuntimeConfigOptimizingCompiler()
 	ctx := context.Background()
 	r := wazero.NewRuntimeWithConfig(ctx, config)
 	defer func() {
@@ -899,7 +973,7 @@ wasm stack trace:
 
 func TestListener_local(t *testing.T) {
 	var buf bytes.Buffer
-	config := newRuntimeConfigOptimizingCompiler()
+	config := opt.NewRuntimeConfigOptimizingCompiler()
 	ctx := context.WithValue(context.Background(), experimental.FunctionListenerFactoryKey{}, logging.NewLoggingListenerFactory(&buf))
 
 	r := wazero.NewRuntimeWithConfig(ctx, config)
@@ -927,7 +1001,7 @@ func TestListener_local(t *testing.T) {
 
 func TestListener_imported(t *testing.T) {
 	var buf bytes.Buffer
-	config := newRuntimeConfigOptimizingCompiler()
+	config := opt.NewRuntimeConfigOptimizingCompiler()
 	ctx := context.WithValue(context.Background(), experimental.FunctionListenerFactoryKey{}, logging.NewLoggingListenerFactory(&buf))
 
 	r := wazero.NewRuntimeWithConfig(ctx, config)
@@ -978,7 +1052,7 @@ func TestListener_long(t *testing.T) {
 	})
 
 	var buf bytes.Buffer
-	config := newRuntimeConfigOptimizingCompiler()
+	config := opt.NewRuntimeConfigOptimizingCompiler()
 	ctx := context.WithValue(context.Background(), experimental.FunctionListenerFactoryKey{}, logging.NewLoggingListenerFactory(&buf))
 
 	r := wazero.NewRuntimeWithConfig(ctx, config)
@@ -1028,7 +1102,7 @@ func TestListener_long_as_is(t *testing.T) {
 	})
 
 	var buf bytes.Buffer
-	config := newRuntimeConfigOptimizingCompiler()
+	config := opt.NewRuntimeConfigOptimizingCompiler()
 	ctx := context.WithValue(context.Background(), experimental.FunctionListenerFactoryKey{}, logging.NewLoggingListenerFactory(&buf))
 
 	r := wazero.NewRuntimeWithConfig(ctx, config)
@@ -1077,7 +1151,7 @@ func TestListener_long_many_consts(t *testing.T) {
 	})
 
 	var buf bytes.Buffer
-	config := newRuntimeConfigOptimizingCompiler()
+	config := opt.NewRuntimeConfigOptimizingCompiler()
 	ctx := context.WithValue(context.Background(), experimental.FunctionListenerFactoryKey{}, logging.NewLoggingListenerFactory(&buf))
 
 	r := wazero.NewRuntimeWithConfig(ctx, config)
@@ -1102,7 +1176,7 @@ func TestListener_long_many_consts(t *testing.T) {
 
 // TestDWARF verifies that the DWARF based stack traces work as expected before/after compilation cache.
 func TestDWARF(t *testing.T) {
-	config := newRuntimeConfigOptimizingCompiler()
+	config := opt.NewRuntimeConfigOptimizingCompiler()
 	ctx := context.Background()
 
 	bin := dwarftestdata.ZigWasm
@@ -1135,11 +1209,191 @@ func TestDWARF(t *testing.T) {
 	require.NoError(t, err)
 }
 
-// newRuntimeConfigOptimizingCompiler is the same as opt.NewRuntimeConfigOptimizingCompiler
-// except that it doesn't check if it is on arm64 or not. Use this in tests until amd64 is completely supported.
-func newRuntimeConfigOptimizingCompiler() wazero.RuntimeConfig {
-	type enabler interface{ EnableOptimizingCompiler() }
-	c := wazero.NewRuntimeConfig()
-	c.(enabler).EnableOptimizingCompiler()
-	return c
+// TODO: delete this and rune them in internal/integration_test/spectest/v1/spec_test.go after amd64 is done.
+func TestSpectestV1(t *testing.T) {
+	if runtime.GOARCH != "amd64" {
+		return
+	}
+
+	config := opt.NewRuntimeConfigOptimizingCompiler()
+
+	for _, tc := range []struct {
+		name string
+	}{
+		{name: "address"},
+		{name: "align"},
+		{name: "br"},
+		{name: "br_if"},
+		{name: "br_table"},
+		{name: "break-drop"},
+		//{name: "block"},
+		{name: "binary"},
+		{name: "binary-leb128"},
+		{name: "call"},
+		//{name: "call_indirect"},
+		{name: "comments"},
+		{name: "custom"},
+		//{name: "conversions"},
+		{name: "const"},
+		{name: "data"},
+		//{name: "elem"},
+		//{name: "endianness"},
+		{name: "exports"},
+		//{name: "f32"},
+		//{name: "f32_bitwise"},
+		//{name: "f32_cmp"},
+		//{name: "f64"},
+		//{name: "f64_bitwise"},
+		//{name: "f64_cmp"},
+		//{name: "fac"},
+		//{name: "float_exprs"},
+		//{name: "float_literals"},
+		{name: "float_memory"},
+		//{name: "float_misc"},
+		//{name: "func"},
+		{name: "func_ptrs"},
+		{name: "forward"},
+		//{name: "globals"},
+		//{name: "if"},
+		//{name: "imports"},
+		{name: "inline-module"},
+		//{name: "i32"},
+		//{name: "i64"},
+		//{name: "int_exprs"},
+		{name: "int_literals"},
+		{name: "labels"},
+		//{name: "left-to-right"},
+		//{name: "linking"},
+		//{name: "load"},
+		//{name: "loop"},
+		//{name: "local_get"},
+		//{name: "local_set"},
+		//{name: "local_tee"},
+		//{name: "memory"},
+		{name: "memory_size"},
+		//{name: "memory_grow"},
+		{name: "memory_redundancy"},
+		{name: "memory_trap"},
+		{name: "names"},
+		//{name: "nop"},
+		{name: "return"},
+		//{name: "select"},
+		{name: "start"},
+		{name: "stack"},
+		{name: "store"},
+		//{name: "switch"},
+		{name: "skip-stack-guard-page"},
+		{name: "token"},
+		//{name: "type"},
+		//{name: "traps"},
+		{name: "unreachable"},
+		//{name: "unreached-invalid"},
+		{name: "unwind"},
+	} {
+		t.Run(tc.name, func(t *testing.T) {
+			spectest.RunCase(t, v1.Testcases, tc.name, context.Background(), config,
+				-1, 0, math.MaxInt)
+		})
+	}
+}
+
+// TODO: delete this and rune them in internal/integration_test/spectest/v2/spec_test.go after amd64 is done.
+func TestSpectestV2(t *testing.T) {
+	if runtime.GOARCH != "amd64" {
+		return
+	}
+
+	config := opt.NewRuntimeConfigOptimizingCompiler()
+
+	for _, tc := range []struct {
+		name string
+	}{
+		//{"block"},
+		//{"bulk"},
+		//{"i32"},
+		//{"i64"},
+		{"br"},
+		//{"call"},
+		//{"call_indirect"},
+		//{"conversions"},
+		//{"global"},
+		//{"if"},
+		//{"linking"},
+		//{"loop"},
+		//{"memory_copy"},
+		//{"memory_fill"},
+		//{"memory_init"},
+		{"memory_trap"},
+		{"ref_null"},
+		//{"ref_is_null"},
+		//{"ref_func"},
+		//{"table_copy"},
+		//{"table_fill"},
+		//{"table_get"},
+		//{"table_init"},
+		//{"table_set"},
+		{"table_size"},
+		//{"select"},
+		//{"simd_boolean"},
+		//{"simd_bit_shift"},
+		//{"simd_bitwise"},
+		//{"simd_const"},
+		//{"simd_conversions"},
+		//{"simd_i8x16_arith"},
+		//{"simd_i8x16_arith2"},
+		//{"simd_i8x16_sat_arith"},
+		//{"simd_i16x8_arith"},
+		//{"simd_i16x8_arith2"},
+		//{"simd_i16x8_sat_arith"},
+		//{"simd_i32x4_arith"},
+		//{"simd_i32x4_arith2"},
+		//{"simd_i64x2_arith"},
+		//{"simd_i64x2_arith2"},
+		//{"simd_i8x16_cmp"},
+		//{"simd_i16x8_cmp"},
+		//{"simd_i32x4_cmp"},
+		//{"simd_i64x2_cmp"},
+		//{"simd_f32x4"},
+		//{"simd_f64x2"},
+		//{"simd_f32x4_arith"},
+		//{"simd_f64x2_arith"},
+		//{"simd_f32x4_cmp"},
+		//{"simd_f64x2_cmp"},
+		//{"simd_f32x4_rounding"},
+		//{"simd_f64x2_rounding"},
+		//{"simd_f32x4_pmin_pmax"},
+		//{"simd_f64x2_pmin_pmax"},
+		//{"simd_i32x4_trunc_sat_f32x4"},
+		//{"simd_i32x4_trunc_sat_f64x2"},
+		//{"simd_i32x4_dot_i16x8"},
+		//{"simd_i16x8_extmul_i8x16"},
+		//{"simd_i64x2_extmul_i32x4"},
+		//{"simd_i32x4_extmul_i16x8"},
+		//{"simd_i16x8_extadd_pairwise_i8x16"},
+		//{"simd_i32x4_extadd_pairwise_i16x8"},
+		//{"simd_i16x8_q15mulr_sat_s"},
+		//{"simd_int_to_int_extend"},
+		//{"simd_load"},
+		//{"simd_load_extend"},
+		//{"simd_load_splat"},
+		//{"simd_load_zero"},
+		//{"simd_load8_lane"},
+		//{"simd_load16_lane"},
+		//{"simd_load32_lane"},
+		//{"simd_load64_lane"},
+		//{"simd_lane"},
+		//{"simd_linking"},
+		//{"simd_splat"},
+		//{"simd_store"},
+		//{"simd_store8_lane"},
+		//{"simd_store16_lane"},
+		//{"simd_store32_lane"},
+		//{"simd_store64_lane"},
+	} {
+		t.Run(tc.name, func(t *testing.T) {
+			ctx := context.Background()
+			spectest.RunCase(t, v2.Testcases, tc.name, ctx, config,
+				-1, 0, math.MaxInt)
+		})
+	}
 }
diff --git a/internal/engine/wazevo/testcases/testcases.go b/internal/engine/wazevo/testcases/testcases.go
index 809cf07a..2a15eb63 100644
--- a/internal/engine/wazevo/testcases/testcases.go
+++ b/internal/engine/wazevo/testcases/testcases.go
@@ -1088,8 +1088,11 @@ var (
 	FloatArithm = TestCase{
 		Name: "float_arithm",
 		Module: SingleFunctionModule(wasm.FunctionType{
-			Params:  []wasm.ValueType{f64, f64, f32, f32},
-			Results: []wasm.ValueType{f64, f64, f64, f64, f64, f64, f64, f32, f32, f32, f32, f32, f32, f32},
+			Params: []wasm.ValueType{f64, f64, f64, f32, f32, f32},
+			Results: []wasm.ValueType{
+				f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64,
+				f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32,
+			},
 		}, []byte{
 			wasm.OpcodeLocalGet, 0,
 			wasm.OpcodeF64Neg,
@@ -1117,33 +1120,86 @@ var (
 			wasm.OpcodeLocalGet, 1,
 			wasm.OpcodeF64Div,
 
-			// 32-bit floats.
 			wasm.OpcodeLocalGet, 2,
-			wasm.OpcodeF32Neg,
+			wasm.OpcodeF64Nearest,
 
 			wasm.OpcodeLocalGet, 2,
+			wasm.OpcodeF64Floor,
+
+			wasm.OpcodeLocalGet, 2,
+			wasm.OpcodeF64Ceil,
+
+			wasm.OpcodeLocalGet, 2,
+			wasm.OpcodeF64Trunc,
+
+			// 32-bit floats.
+			wasm.OpcodeLocalGet, 3,
+			wasm.OpcodeF32Neg,
+
+			wasm.OpcodeLocalGet, 3,
 			wasm.OpcodeF32Neg,
 			wasm.OpcodeF32Abs,
 
-			wasm.OpcodeLocalGet, 2,
+			wasm.OpcodeLocalGet, 3,
 			wasm.OpcodeF32Sqrt,
 
-			wasm.OpcodeLocalGet, 2,
 			wasm.OpcodeLocalGet, 3,
+			wasm.OpcodeLocalGet, 4,
 			wasm.OpcodeF32Add,
 
-			wasm.OpcodeLocalGet, 2,
 			wasm.OpcodeLocalGet, 3,
+			wasm.OpcodeLocalGet, 4,
 			wasm.OpcodeF32Sub,
 
-			wasm.OpcodeLocalGet, 2,
 			wasm.OpcodeLocalGet, 3,
+			wasm.OpcodeLocalGet, 4,
 			wasm.OpcodeF32Mul,
 
-			wasm.OpcodeLocalGet, 2,
 			wasm.OpcodeLocalGet, 3,
+			wasm.OpcodeLocalGet, 4,
 			wasm.OpcodeF32Div,
 
+			wasm.OpcodeLocalGet, 5,
+			wasm.OpcodeF32Nearest,
+
+			wasm.OpcodeLocalGet, 5,
+			wasm.OpcodeF32Floor,
+
+			wasm.OpcodeLocalGet, 5,
+			wasm.OpcodeF32Ceil,
+
+			wasm.OpcodeLocalGet, 5,
+			wasm.OpcodeF32Trunc,
+
+			wasm.OpcodeEnd,
+		}, []wasm.ValueType{}),
+	}
+	MinMaxFloat = TestCase{
+		Name: "min_max_float",
+		Module: SingleFunctionModule(wasm.FunctionType{
+			Params: []wasm.ValueType{f64, f64, f32, f32},
+			Results: []wasm.ValueType{
+				f64, f64,
+				f32, f32,
+			},
+		}, []byte{
+			wasm.OpcodeLocalGet, 0,
+			wasm.OpcodeLocalGet, 1,
+			wasm.OpcodeF64Min,
+
+			wasm.OpcodeLocalGet, 0,
+			wasm.OpcodeLocalGet, 1,
+			wasm.OpcodeF64Max,
+
+			// 32-bit floats.
+			wasm.OpcodeLocalGet, 2,
+			wasm.OpcodeLocalGet, 3,
+			wasm.OpcodeF32Min,
+
+			wasm.OpcodeLocalGet, 2,
+			wasm.OpcodeLocalGet, 3,
+			wasm.OpcodeF32Max,
+
 			wasm.OpcodeEnd,
 		}, []wasm.ValueType{}),
 	}