Repository: tetratelabs/wazero
Pull request #1973 (2024-01-29 19:46:35)

wazevo(amd64): lowering Select, Icmp


Patch:
diff --git a/internal/engine/wazevo/backend/isa/amd64/instr.go b/internal/engine/wazevo/backend/isa/amd64/instr.go
index 7136689f..4fc1516d 100644
--- a/internal/engine/wazevo/backend/isa/amd64/instr.go
+++ b/internal/engine/wazevo/backend/isa/amd64/instr.go
@@ -178,7 +178,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,8 +205,6 @@ func (i *instruction) String() string {
 		panic("TODO")
 	case xmmMinMaxSeq:
 		panic("TODO")
-	case xmmCmove:
-		panic("TODO")
 	case xmmCmpRmR:
 		panic("TODO")
 	case xmmRmRImm:
@@ -609,10 +607,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
 
@@ -725,8 +719,6 @@ func (k instructionKind) String() string {
 		return "cvtFloatToUintSeq"
 	case xmmMinMaxSeq:
 		return "xmmMinMaxSeq"
-	case xmmCmove:
-		return "xmmCmove"
 	case xmmCmpRmR:
 		return "xmmCmpRmR"
 	case xmmRmRImm:
@@ -1021,13 +1013,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
 	}
@@ -1037,7 +1029,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
 }
@@ -1699,6 +1691,7 @@ var defKinds = [instrMax]defKind{
 	exitSequence:    defKindNone,
 	lea:             defKindOp2,
 	v128ConstIsland: defKindNone,
+	setcc:           defKindOp2,
 }
 
 // String implements fmt.Stringer.
@@ -1765,6 +1758,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 1bc03ab5..86f0d49c 100644
--- a/internal/engine/wazevo/backend/isa/amd64/instr_encoding.go
+++ b/internal/engine/wazevo/backend/isa/amd64/instr_encoding.go
@@ -833,7 +833,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,8 +926,6 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 		panic("TODO")
 	case xmmMinMaxSeq:
 		panic("TODO")
-	case xmmCmove:
-		panic("TODO")
 	case xmmCmpRmR:
 		panic("TODO")
 	case xmmRmRImm:
diff --git a/internal/engine/wazevo/backend/isa/amd64/machine.go b/internal/engine/wazevo/backend/isa/amd64/machine.go
index 0077592a..e70e70b7 100644
--- a/internal/engine/wazevo/backend/isa/amd64/machine.go
+++ b/internal/engine/wazevo/backend/isa/amd64/machine.go
@@ -315,11 +315,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 +594,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()


Test patch:
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 67472c7f..3a84e04a 100644
--- a/internal/engine/wazevo/e2e_test.go
+++ b/internal/engine/wazevo/e2e_test.go
@@ -1153,13 +1153,13 @@ func TestSpectestV1(t *testing.T) {
 		{name: "address"},
 		{name: "align"},
 		{name: "br"},
-		//{name: "br_if"},
+		{name: "br_if"},
 		{name: "br_table"},
 		{name: "break-drop"},
 		//{name: "block"},
 		{name: "binary"},
 		{name: "binary-leb128"},
-		//{name: "call"},
+		{name: "call"},
 		//{name: "call_indirect"},
 		{name: "comments"},
 		{name: "custom"},
@@ -1218,7 +1218,7 @@ func TestSpectestV1(t *testing.T) {
 		//{name: "traps"},
 		{name: "unreachable"},
 		//{name: "unreached-invalid"},
-		//{name: "unwind"},
+		{name: "unwind"},
 	} {
 		t.Run(tc.name, func(t *testing.T) {
 			spectest.RunCase(t, v1.Testcases, tc.name, context.Background(), config,