Repository: tetratelabs/wazero
Pull request #1976 (2024-01-29 21:34:07)

wazevo(amd64): support for copysign
- wazevo(amd64): float copysign



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 9af1f624..edbcaffe 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)
 
@@ -300,6 +320,8 @@ func (m *machine) LowerInstr(instr *ssa.Instruction) {
 		m.lowerRound(instr, roundingModeNearest)
 	case ssa.OpcodeFmin, ssa.OpcodeFmax:
 		m.lowerFminFmax(instr)
+	case ssa.OpcodeFcopysign:
+		m.lowerFcopysign(instr)
 	case ssa.OpcodeSqrt:
 		m.lowerSqrt(instr)
 	case ssa.OpcodeUndefined:
@@ -327,23 +349,59 @@ 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())
+	case ssa.OpcodeIreduce:
+		rn := m.getOperand_Mem_Reg(m.c.ValueDefinition(instr.Arg()))
+		retVal := instr.Return()
+		rd := m.c.VRegOf(retVal)
+
+		if retVal.Type() != ssa.TypeI32 {
+			panic("TODO?: Ireduce to non-i32")
+		}
+		m.insert(m.allocateInstr().asMovzxRmR(extModeLQ, rn, rd))
+
 	default:
 		panic("TODO: lowering " + op.String())
 	}
 }
 
+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) {
@@ -369,30 +427,33 @@ func (m *machine) lowerSelect(x, y, cval, ret ssa.Value) {
 	if typ := x.Type(); typ.IsInt() {
 		_64 := typ.Bits() == 64
 		mov := m.allocateInstr()
+		tmp := m.c.AllocateVReg(typ)
 		switch yo.kind {
 		case operandKindReg:
-			mov.asMovRR(yo.r, rd, _64)
+			mov.asMovRR(yo.r, tmp, _64)
 		case operandKindMem:
 			if _64 {
-				mov.asMov64MR(yo, rd)
+				mov.asMov64MR(yo, tmp)
 			} else {
-				mov.asMovzxRmR(extModeLQ, yo, rd)
+				mov.asMovzxRmR(extModeLQ, yo, tmp)
 			}
 		default:
 			panic("BUG")
 		}
 		m.insert(mov)
-		cmov := m.allocateInstr().asCmove(cond, xo, rd, _64)
+		cmov := m.allocateInstr().asCmove(cond, xo, tmp, _64)
 		m.insert(cmov)
+		m.insert(m.allocateInstr().asMovRR(tmp, rd, _64))
 	} else {
 		mov := m.allocateInstr()
+		tmp := m.c.AllocateVReg(typ)
 		switch typ {
 		case ssa.TypeF32:
-			mov.asXmmUnaryRmR(sseOpcodeMovss, yo, rd)
+			mov.asXmmUnaryRmR(sseOpcodeMovss, yo, tmp)
 		case ssa.TypeF64:
-			mov.asXmmUnaryRmR(sseOpcodeMovsd, yo, rd)
+			mov.asXmmUnaryRmR(sseOpcodeMovsd, yo, tmp)
 		case ssa.TypeV128:
-			mov.asXmmUnaryRmR(sseOpcodeMovdqu, yo, rd)
+			mov.asXmmUnaryRmR(sseOpcodeMovdqu, yo, tmp)
 		default:
 			panic("BUG")
 		}
@@ -405,11 +466,11 @@ func (m *machine) lowerSelect(x, y, cval, ret ssa.Value) {
 		m.insert(cmov)
 		switch typ {
 		case ssa.TypeF32:
-			cmov.asXmmUnaryRmR(sseOpcodeMovss, xo, rd)
+			cmov.asXmmUnaryRmR(sseOpcodeMovss, xo, tmp)
 		case ssa.TypeF64:
-			cmov.asXmmUnaryRmR(sseOpcodeMovsd, xo, rd)
+			cmov.asXmmUnaryRmR(sseOpcodeMovsd, xo, tmp)
 		case ssa.TypeV128:
-			cmov.asXmmUnaryRmR(sseOpcodeMovdqu, xo, rd)
+			cmov.asXmmUnaryRmR(sseOpcodeMovdqu, xo, tmp)
 		default:
 			panic("BUG")
 		}
@@ -418,6 +479,7 @@ func (m *machine) lowerSelect(x, y, cval, ret ssa.Value) {
 		m.insert(nop)
 
 		jcc.asJmpIf(cond.invert(), newOperandLabel(end))
+		m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeMovdqu, newOperandReg(tmp), rd))
 	}
 }
 
@@ -1204,6 +1266,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()
@@ -1542,3 +1638,38 @@ func (m *machine) lowerFminFmax(instr *ssa.Instruction) {
 
 	m.copyTo(tmp, rd)
 }
+
+func (m *machine) lowerFcopysign(instr *ssa.Instruction) {
+	x, y := instr.Arg2()
+	if !x.Type().IsFloat() {
+		panic("BUG")
+	}
+
+	_64 := x.Type().Bits() == 64
+
+	xDef, yDef := m.c.ValueDefinition(x), m.c.ValueDefinition(y)
+	rm := m.getOperand_Reg(xDef)
+	rn := m.getOperand_Reg(yDef)
+	rd := m.c.VRegOf(instr.Return())
+
+	// Clear the non-sign bits of src via AND with the mask.
+	var opAnd, opOr sseOpcode
+	var mask uint64
+	if _64 {
+		mask, opAnd, opOr = 0x8000000000000000, sseOpcodeAndpd, sseOpcodeOrpd
+	} else {
+		mask, opAnd, opOr = 0x80000000, sseOpcodeAndps, sseOpcodeOrps
+	}
+
+	tmp := m.c.AllocateVReg(x.Type())
+	m.lowerFconst(tmp, mask, _64)
+
+	and := m.allocateInstr().asXmmRmR(opAnd, rn, tmp)
+	m.insert(and)
+
+	// Finally, copy the sign bit of src to dst.
+	or := m.allocateInstr().asXmmRmR(opOr, rm, tmp)
+	m.insert(or)
+
+	m.copyTo(tmp, rd)
+}
diff --git a/internal/engine/wazevo/ssa/instructions.go b/internal/engine/wazevo/ssa/instructions.go
index 4dbe0883..725a7dd7 100644
--- a/internal/engine/wazevo/ssa/instructions.go
+++ b/internal/engine/wazevo/ssa/instructions.go
@@ -551,8 +551,7 @@ const (
 	// `v = bmask x`.
 	OpcodeBmask
 
-	// OpcodeIreduce ...
-	// `v = ireduce x`.
+	// OpcodeIreduce narrow the given integer: `v = Ireduce x`.
 	OpcodeIreduce
 
 	// OpcodeSnarrow converts two input vectors x, y into a smaller lane vector by narrowing each lane, signed `v = Snarrow.lane x, y`.


Test patch:
diff --git a/internal/engine/wazevo/e2e_test.go b/internal/engine/wazevo/e2e_test.go
index 9e73f8bc..7eb16667 100644
--- a/internal/engine/wazevo/e2e_test.go
+++ b/internal/engine/wazevo/e2e_test.go
@@ -300,6 +300,8 @@ func TestE2E(t *testing.T) {
 						math.Float64bits(2),
 						math.Float64bits(1),
 
+						math.Float64bits(-25),
+
 						uint64(math.Float32bits(-25)),
 						uint64(math.Float32bits(25)),
 
@@ -313,6 +315,8 @@ func TestE2E(t *testing.T) {
 						uint64(math.Float32bits(1)),
 						uint64(math.Float32bits(2)),
 						uint64(math.Float32bits(1)),
+
+						uint64(math.Float32bits(-25)),
 					},
 				},
 			},
@@ -1226,7 +1230,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 +1245,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 +1258,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: "load"},
+		{name: "loop"},
 		//{name: "local_get"},
 		//{name: "local_set"},
 		//{name: "local_tee"},
@@ -1277,11 +1281,11 @@ func TestSpectestV1(t *testing.T) {
 		{name: "names"},
 		{name: "nop"},
 		{name: "return"},
-		//{name: "select"},
+		{name: "select"},
 		{name: "start"},
 		{name: "stack"},
 		{name: "store"},
-		//{name: "switch"},
+		{name: "switch"},
 		{name: "skip-stack-guard-page"},
 		{name: "token"},
 		{name: "type"},
diff --git a/internal/engine/wazevo/testcases/testcases.go b/internal/engine/wazevo/testcases/testcases.go
index 2a15eb63..b5582f2b 100644
--- a/internal/engine/wazevo/testcases/testcases.go
+++ b/internal/engine/wazevo/testcases/testcases.go
@@ -1090,8 +1090,8 @@ var (
 		Module: SingleFunctionModule(wasm.FunctionType{
 			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,
+				f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64,
+				f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32,
 			},
 		}, []byte{
 			wasm.OpcodeLocalGet, 0,
@@ -1132,6 +1132,11 @@ var (
 			wasm.OpcodeLocalGet, 2,
 			wasm.OpcodeF64Trunc,
 
+			wasm.OpcodeLocalGet, 0,
+			wasm.OpcodeLocalGet, 1,
+			wasm.OpcodeF64Neg,
+			wasm.OpcodeF64Copysign,
+
 			// 32-bit floats.
 			wasm.OpcodeLocalGet, 3,
 			wasm.OpcodeF32Neg,
@@ -1171,6 +1176,11 @@ var (
 			wasm.OpcodeLocalGet, 5,
 			wasm.OpcodeF32Trunc,
 
+			wasm.OpcodeLocalGet, 3,
+			wasm.OpcodeLocalGet, 4,
+			wasm.OpcodeF32Neg,
+			wasm.OpcodeF32Copysign,
+
 			wasm.OpcodeEnd,
 		}, []wasm.ValueType{}),
 	}