Repository: tetratelabs/wazero
Pull request #1977 (2024-01-29 23:53:49)

wazevo(amd64): support for Fcmp


Patch:
diff --git a/internal/engine/wazevo/backend/isa/amd64/cond.go b/internal/engine/wazevo/backend/isa/amd64/cond.go
index c422e72a..f26f3a03 100644
--- a/internal/engine/wazevo/backend/isa/amd64/cond.go
+++ b/internal/engine/wazevo/backend/isa/amd64/cond.go
@@ -37,6 +37,8 @@ const (
 	condLE
 	// condNLE represents (> signed) condition.
 	condNLE
+
+	condInvalid
 )
 
 func (c cond) String() string {
@@ -105,7 +107,7 @@ func condFromSSAIntCmpCond(origin ssa.IntegerCmpCond) cond {
 	}
 }
 
-func condFromSSAFloatCmpCond(origin ssa.FloatCmpCond) cond { // nolint:unused
+func condFromSSAFloatCmpCond(origin ssa.FloatCmpCond) cond {
 	switch origin {
 	case ssa.FloatCmpCondEqual:
 		return condZ
diff --git a/internal/engine/wazevo/backend/isa/amd64/machine.go b/internal/engine/wazevo/backend/isa/amd64/machine.go
index 88c4f034..b7be9650 100644
--- a/internal/engine/wazevo/backend/isa/amd64/machine.go
+++ b/internal/engine/wazevo/backend/isa/amd64/machine.go
@@ -222,7 +222,27 @@ func (m *machine) LowerConditionalBranch(b *ssa.Instruction) {
 		m.insert(m.allocateInstr().asJmpIf(cc, newOperandLabel(target)))
 		cvalDef.Instr.MarkLowered()
 	case ssa.OpcodeFcmp:
-		panic("TODO")
+		cvalInstr := cvalDef.Instr
+
+		f1, f2, and := m.lowerFcmpToFlags(cvalInstr)
+		if f2 == condInvalid {
+			m.insert(m.allocateInstr().asJmpIf(f1, newOperandLabel(target)))
+		} else {
+			jmp1, jmp2 := m.allocateInstr(), m.allocateInstr()
+			m.insert(jmp1)
+			m.insert(jmp2)
+			notTaken, notTakenLabel := m.allocateBrTarget()
+			m.insert(notTaken)
+			if and {
+				jmp1.asJmpIf(f1.invert(), newOperandLabel(notTakenLabel))
+				jmp2.asJmpIf(f2, newOperandLabel(target))
+			} else {
+				jmp1.asJmpIf(f1, newOperandLabel(target))
+				jmp2.asJmpIf(f2, newOperandLabel(target))
+			}
+		}
+
+		cvalDef.Instr.MarkLowered()
 	default:
 		v := m.getOperand_Reg(cvalDef)
 
@@ -327,6 +347,8 @@ func (m *machine) LowerInstr(instr *ssa.Instruction) {
 		m.lowerExtend(instr.Arg(), instr.Return(), from, to, signed)
 	case ssa.OpcodeIcmp:
 		m.lowerIcmp(instr)
+	case ssa.OpcodeFcmp:
+		m.lowerFcmp(instr)
 	case ssa.OpcodeSelect:
 		cval, x, y := instr.SelectData()
 		m.lowerSelect(x, y, cval, instr.Return())
@@ -345,15 +367,39 @@ func (m *machine) LowerInstr(instr *ssa.Instruction) {
 	}
 }
 
+func (m *machine) lowerFcmp(instr *ssa.Instruction) {
+	f1, f2, and := m.lowerFcmpToFlags(instr)
+	rd := m.c.VRegOf(instr.Return())
+	if f2 == condInvalid {
+		tmp := m.c.AllocateVReg(ssa.TypeI32)
+		m.insert(m.allocateInstr().asSetcc(f1, tmp))
+		// On amd64, setcc only sets the first byte of the register, so we need to zero extend it to match
+		// the semantics of Icmp that sets either 0 or 1.
+		m.insert(m.allocateInstr().asMovzxRmR(extModeBQ, newOperandReg(tmp), rd))
+	} else {
+		tmp1, tmp2 := m.c.AllocateVReg(ssa.TypeI32), m.c.AllocateVReg(ssa.TypeI32)
+		m.insert(m.allocateInstr().asSetcc(f1, tmp1))
+		m.insert(m.allocateInstr().asSetcc(f2, tmp2))
+		var op aluRmiROpcode
+		if and {
+			op = aluRmiROpcodeAnd
+		} else {
+			op = aluRmiROpcodeOr
+		}
+		m.insert(m.allocateInstr().asAluRmiR(op, newOperandReg(tmp1), tmp2, false))
+		m.insert(m.allocateInstr().asMovzxRmR(extModeBQ, newOperandReg(tmp2), rd))
+	}
+}
+
 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)
+	tmp := m.c.AllocateVReg(ssa.TypeI32)
+	m.insert(m.allocateInstr().asSetcc(condFromSSAIntCmpCond(c), tmp))
 	// On amd64, setcc only sets the first byte of the register, so we need to zero extend it to match
 	// the semantics of Icmp that sets either 0 or 1.
-	m.insert(m.allocateInstr().asMovzxRmR(extModeBQ, newOperandReg(rd), rd))
+	m.insert(m.allocateInstr().asMovzxRmR(extModeBQ, newOperandReg(tmp), rd))
 }
 
 func (m *machine) lowerSelect(x, y, cval, ret ssa.Value) {
@@ -1218,6 +1264,40 @@ func (m *machine) lowerIcmpToFlag(xd, yd *backend.SSAValueDefinition, _64 bool)
 	m.insert(cmp)
 }
 
+func (m *machine) lowerFcmpToFlags(instr *ssa.Instruction) (f1, f2 cond, and bool) {
+	x, y, c := instr.FcmpData()
+	switch c {
+	case ssa.FloatCmpCondEqual:
+		f1, f2 = condNP, condZ
+		and = true
+	case ssa.FloatCmpCondNotEqual:
+		f1, f2 = condP, condNZ
+	case ssa.FloatCmpCondLessThan:
+		f1 = condFromSSAFloatCmpCond(ssa.FloatCmpCondGreaterThan)
+		f2 = condInvalid
+		x, y = y, x
+	case ssa.FloatCmpCondLessThanOrEqual:
+		f1 = condFromSSAFloatCmpCond(ssa.FloatCmpCondGreaterThanOrEqual)
+		f2 = condInvalid
+		x, y = y, x
+	default:
+		f1 = condFromSSAFloatCmpCond(c)
+		f2 = condInvalid
+	}
+
+	var opc sseOpcode
+	if x.Type() == ssa.TypeF32 {
+		opc = sseOpcodeUcomiss
+	} else {
+		opc = sseOpcodeUcomisd
+	}
+
+	xr := m.getOperand_Reg(m.c.ValueDefinition(x))
+	yr := m.getOperand_Mem_Reg(m.c.ValueDefinition(y))
+	m.insert(m.allocateInstr().asXmmCmpRmR(opc, yr, xr.r))
+	return
+}
+
 // allocateInstr allocates an instruction.
 func (m *machine) allocateInstr() *instruction {
 	instr := m.ectx.InstructionPool.Allocate()


Test patch:
diff --git a/internal/engine/wazevo/e2e_test.go b/internal/engine/wazevo/e2e_test.go
index 7e696337..a5011578 100644
--- a/internal/engine/wazevo/e2e_test.go
+++ b/internal/engine/wazevo/e2e_test.go
@@ -1226,7 +1226,7 @@ func TestSpectestV1(t *testing.T) {
 		{name: "br_if"},
 		{name: "br_table"},
 		{name: "break-drop"},
-		//{name: "block"},
+		{name: "block"},
 		{name: "binary"},
 		{name: "binary-leb128"},
 		{name: "call"},
@@ -1241,10 +1241,10 @@ func TestSpectestV1(t *testing.T) {
 		{name: "exports"},
 		//{name: "f32"},
 		//{name: "f32_bitwise"},
-		//{name: "f32_cmp"},
+		{name: "f32_cmp"},
 		//{name: "f64"},
 		//{name: "f64_bitwise"},
-		//{name: "f64_cmp"},
+		{name: "f64_cmp"},
 		{name: "fac"},
 		//{name: "float_exprs"},
 		//{name: "float_literals"},
@@ -1254,18 +1254,18 @@ func TestSpectestV1(t *testing.T) {
 		{name: "func_ptrs"},
 		{name: "forward"},
 		{name: "globals"},
-		//{name: "if"},
+		{name: "if"},
 		//{name: "imports"},
 		{name: "inline-module"},
-		//{name: "i32"},
-		//{name: "i64"},
-		//{name: "int_exprs"},
+		{name: "i32"},
+		{name: "i64"},
+		{name: "int_exprs"},
 		{name: "int_literals"},
 		{name: "labels"},
 		//{name: "left-to-right"},
 		{name: "linking"},
 		{name: "load"},
-		//{name: "loop"},
+		{name: "loop"},
 		//{name: "local_get"},
 		//{name: "local_set"},
 		//{name: "local_tee"},