diff --git a/src/coreclr/jit/codegenriscv64.cpp b/src/coreclr/jit/codegenriscv64.cpp index 943cd62e137b30..a3febcdd804c7d 100644 --- a/src/coreclr/jit/codegenriscv64.cpp +++ b/src/coreclr/jit/codegenriscv64.cpp @@ -3125,35 +3125,32 @@ void CodeGen::genCkfinite(GenTree* treeNode) // void CodeGen::genCodeForCompare(GenTreeOp* tree) { + assert(tree->OperIsCmpCompare()); GenTree* op1 = tree->gtOp1; GenTree* op2 = tree->gtOp2; var_types op1Type = genActualType(op1->TypeGet()); - var_types op2Type = genActualType(op2->TypeGet()); assert(!op1->isUsedFromMemory()); assert(!op2->isUsedFromMemory()); - emitAttr cmpSize = EA_ATTR(genTypeSize(op1Type)); + emitAttr cmpSize = EA_SIZE(genTypeSize(op1Type)); assert(cmpSize == EA_4BYTE || cmpSize == EA_8BYTE); - assert(genTypeSize(op1Type) == genTypeSize(op2Type)); - emitter* emit = GetEmitter(); regNumber targetReg = tree->GetRegNum(); assert(targetReg != REG_NA); assert(!tree->TypeIs(TYP_VOID)); - assert(!op1->isContainedIntOrIImmed()); - assert(tree->OperIs(GT_LT, GT_LE, GT_EQ, GT_NE, GT_GT, GT_GE)); + bool isReversed = false; if (varTypeIsFloating(op1Type)) { - assert(!op2->isContainedIntOrIImmed()); - assert(op1Type == op2Type); + assert(!op1->isContainedIntOrIImmed() && !op2->isContainedIntOrIImmed()); + assert(op1->TypeIs(op2->TypeGet())); genTreeOps oper = tree->OperGet(); - bool isUnordered = (tree->gtFlags & GTF_RELOP_NAN_UN) != 0; - if (isUnordered) + isReversed = (tree->gtFlags & GTF_RELOP_NAN_UN) != 0; + if (isReversed) { oper = GenTree::ReverseRelop(oper); } @@ -3162,6 +3159,7 @@ void CodeGen::genCodeForCompare(GenTreeOp* tree) oper = GenTree::SwapRelop(oper); std::swap(op1, op2); } + instruction instr = INS_none; switch (oper) { @@ -3178,142 +3176,55 @@ void CodeGen::genCodeForCompare(GenTreeOp* tree) unreached(); } emit->emitIns_R_R_R(instr, cmpSize, targetReg, op1->GetRegNum(), op2->GetRegNum()); - if (isUnordered) - emit->emitIns_R_R_I(INS_xori, EA_8BYTE, targetReg, targetReg, 1); } else { - bool isUnsigned = (tree->gtFlags & GTF_UNSIGNED) != 0; - regNumber regOp1 = op1->GetRegNum(); + bool isUnsigned = tree->IsUnsigned(); - if (op2->isContainedIntOrIImmed()) + genTreeOps oper = tree->OperGet(); + if (oper == GT_EQ || oper == GT_NE) { - ssize_t imm = op2->AsIntCon()->gtIconVal; - - bool useAddSub = !(!tree->OperIs(GT_EQ, GT_NE) || (imm == -2048)); - bool useShiftRight = - !isUnsigned && ((tree->OperIs(GT_LT) && (imm == 0)) || (tree->OperIs(GT_LE) && (imm == -1))); - bool useLoadImm = isUnsigned && ((tree->OperIs(GT_LT, GT_GE) && (imm == 0)) || - (tree->OperIs(GT_LE, GT_GT) && (imm == -1))); - - if (cmpSize == EA_4BYTE) - { - if (!useAddSub && !useShiftRight && !useLoadImm) - { - regNumber tmpRegOp1 = internalRegisters.GetSingle(tree); - assert(regOp1 != tmpRegOp1); - imm = static_cast(imm); - emit->emitIns_R_R(INS_sext_w, EA_8BYTE, tmpRegOp1, regOp1); - regOp1 = tmpRegOp1; - } - } - - if (tree->OperIs(GT_EQ, GT_NE)) - { - if ((imm != 0) || (cmpSize == EA_4BYTE)) - { - instruction diff = INS_xori; - if (imm != -2048) - { - assert(useAddSub); - diff = (cmpSize == EA_4BYTE) ? INS_addiw : INS_addi; - imm = -imm; - } - emit->emitIns_R_R_I(diff, cmpSize, targetReg, regOp1, imm); - regOp1 = targetReg; - } - assert(emitter::isValidSimm12(imm)); + assert(op2->isContainedIntOrIImmed() && op2->IsIntegralConst(0)); + oper = (oper == GT_EQ) ? GT_LE : GT_GT; + isUnsigned = true; + } - if (tree->OperIs(GT_EQ)) - { - emit->emitIns_R_R_I(INS_sltiu, EA_PTRSIZE, targetReg, regOp1, 1); - } - else - { - assert(tree->OperIs(GT_NE)); - emit->emitIns_R_R_R(INS_sltu, EA_PTRSIZE, targetReg, REG_ZERO, regOp1); - } - } - else - { - assert(tree->OperIs(GT_LT, GT_LE, GT_GT, GT_GE)); - if (useLoadImm) - { - // unsigned (a <= ~0), (a >= 0) / (a > ~0), (a < 0) is always true / false - imm = tree->OperIs(GT_GE, GT_LE) ? 1 : 0; - emit->emitIns_R_R_I(INS_addi, EA_PTRSIZE, targetReg, REG_ZERO, imm); - } - else if (useShiftRight) - { - // signed (a < 0) or (a <= -1) is just the sign bit - instruction srli = (cmpSize == EA_4BYTE) ? INS_srliw : INS_srli; - emit->emitIns_R_R_I(srli, cmpSize, targetReg, regOp1, cmpSize * 8 - 1); - } - else if ((tree->OperIs(GT_GT) && (imm == 0)) || (tree->OperIs(GT_GE) && (imm == 1))) - { - instruction slt = isUnsigned ? INS_sltu : INS_slt; - emit->emitIns_R_R_R(slt, EA_PTRSIZE, targetReg, REG_ZERO, regOp1); - } - else - { - instruction slti = isUnsigned ? INS_sltiu : INS_slti; - if (tree->OperIs(GT_LE, GT_GT)) - imm += 1; - assert(emitter::isValidSimm12(imm)); - assert(!isUnsigned || (imm != 0)); // should be handled in useLoadImm + if (oper == GT_LE && op2->isContainedIntOrIImmed()) + { + oper = GT_LT; + assert(op2->AsIntCon()->gtIconVal == 0); + op2->AsIntCon()->gtIconVal = 1; + } - emit->emitIns_R_R_I(slti, EA_PTRSIZE, targetReg, regOp1, imm); + isReversed = (oper == GT_LE || oper == GT_GE); + if (isReversed) + { + oper = GenTree::ReverseRelop(oper); + } + if (oper == GT_GT) + { + INDEBUG(oper = GenTree::SwapRelop(oper)); + std::swap(op1, op2); + } + assert(oper == GT_LT); // RISC-V only has set-less-than instructions - if (tree->OperIs(GT_GT, GT_GE)) - emit->emitIns_R_R_I(INS_xori, EA_PTRSIZE, targetReg, targetReg, 1); - } - } + assert(!op1->isContainedIntOrIImmed() || op1->IsIntegralConst(0)); + regNumber reg1 = op1->isContainedIntOrIImmed() ? REG_ZERO : op1->GetRegNum(); + if (op2->isContainedIntOrIImmed()) + { + instruction slti = isUnsigned ? INS_sltiu : INS_slti; + emit->emitIns_R_R_I(slti, EA_PTRSIZE, targetReg, reg1, op2->AsIntCon()->gtIconVal); } else { - regNumber regOp2 = op2->GetRegNum(); - - if (tree->OperIs(GT_EQ, GT_NE)) - { - instruction sub = (cmpSize == EA_4BYTE) ? INS_subw : INS_sub; - emit->emitIns_R_R_R(sub, EA_PTRSIZE, targetReg, regOp1, regOp2); - if (tree->OperIs(GT_EQ)) - { - emit->emitIns_R_R_I(INS_sltiu, EA_PTRSIZE, targetReg, targetReg, 1); - } - else - { - assert(tree->OperIs(GT_NE)); - emit->emitIns_R_R_R(INS_sltu, EA_PTRSIZE, targetReg, REG_ZERO, targetReg); - } - } - else - { - assert(tree->OperIs(GT_LT, GT_LE, GT_GT, GT_GE)); - if (cmpSize == EA_4BYTE) - { - regNumber tmpRegOp1 = REG_RA; - regNumber tmpRegOp2 = internalRegisters.GetSingle(tree); - assert(regOp1 != tmpRegOp2); - assert(regOp2 != tmpRegOp2); - emit->emitIns_R_R(INS_sext_w, EA_8BYTE, tmpRegOp1, regOp1); - emit->emitIns_R_R(INS_sext_w, EA_8BYTE, tmpRegOp2, regOp2); - regOp1 = tmpRegOp1; - regOp2 = tmpRegOp2; - } - - instruction slt = isUnsigned ? INS_sltu : INS_slt; - if (tree->OperIs(GT_LE, GT_GT)) - std::swap(regOp1, regOp2); - - emit->emitIns_R_R_R(slt, EA_8BYTE, targetReg, regOp1, regOp2); - - if (tree->OperIs(GT_LE, GT_GE)) - emit->emitIns_R_R_I(INS_xori, EA_PTRSIZE, targetReg, targetReg, 1); - } + instruction slt = isUnsigned ? INS_sltu : INS_slt; + emit->emitIns_R_R_R(slt, EA_PTRSIZE, targetReg, reg1, op2->GetRegNum()); } } + if (isReversed) + emit->emitIns_R_R_I(INS_xori, EA_8BYTE, targetReg, targetReg, 1); + genProduceReg(tree); } @@ -3334,7 +3245,6 @@ void CodeGen::genCodeForJumpCompare(GenTreeOpCC* tree) assert(compiler->compCurBB->KindIs(BBJ_COND)); assert(tree->OperIs(GT_JCMP)); - assert(!varTypeIsFloating(tree)); assert(tree->TypeIs(TYP_VOID)); assert(tree->GetRegNum() == REG_NA); @@ -3342,166 +3252,48 @@ void CodeGen::genCodeForJumpCompare(GenTreeOpCC* tree) GenTree* op2 = tree->gtGetOp2(); assert(!op1->isUsedFromMemory()); assert(!op2->isUsedFromMemory()); - assert(!op1->isContainedIntOrIImmed()); - - var_types op1Type = genActualType(op1->TypeGet()); - var_types op2Type = genActualType(op2->TypeGet()); - assert(genTypeSize(op1Type) == genTypeSize(op2Type)); - - genConsumeOperands(tree); - - emitter* emit = GetEmitter(); - instruction ins = INS_invalid; - int regs = 0; + assert(!op1->isContainedIntOrIImmed() || op1->IsIntegralConst(0)); + assert(!op2->isContainedIntOrIImmed() || op2->IsIntegralConst(0)); GenCondition cond = tree->gtCondition; - - emitAttr cmpSize = EA_ATTR(genTypeSize(op1Type)); - regNumber regOp1 = op1->GetRegNum(); - - if (op2->isContainedIntOrIImmed()) + genConsumeOperands(tree); + regNumber reg1 = op1->isContainedIntOrIImmed() ? REG_ZERO : op1->GetRegNum(); + regNumber reg2 = op2->isContainedIntOrIImmed() ? REG_ZERO : op2->GetRegNum(); + if (cond.Is(GenCondition::SGT, GenCondition::UGT, GenCondition::SLE, GenCondition::ULE)) { - ssize_t imm = op2->AsIntCon()->gtIconVal; - if (imm) - { - assert(regOp1 != REG_R0); - switch (cmpSize) - { - case EA_4BYTE: - { - regNumber tmpRegOp1 = rsGetRsvdReg(); - assert(regOp1 != tmpRegOp1); - imm = static_cast(imm); - emit->emitIns_R_R(INS_sext_w, EA_8BYTE, tmpRegOp1, regOp1); - regOp1 = tmpRegOp1; - break; - } - case EA_8BYTE: - break; - default: - unreached(); - } - - GenTreeIntCon* con = op2->AsIntCon(); - - emitAttr attr = emitActualTypeSize(op2Type); - // TODO-CQ: Currently we cannot do this for all handles because of - // https://github.com/dotnet/runtime/issues/60712 - if (con->ImmedValNeedsReloc(compiler)) - { - attr = EA_SET_FLG(attr, EA_CNS_RELOC_FLG); - } - - if (op2Type == TYP_BYREF) - { - attr = EA_SET_FLG(attr, EA_BYREF_FLG); - } - - instGen_Set_Reg_To_Imm(attr, REG_RA, imm, - INS_FLAGS_DONT_CARE DEBUGARG(con->gtTargetHandle) DEBUGARG(con->gtFlags)); - regSet.verifyRegUsed(REG_RA); - regs = (int)REG_RA << 5; - } - else - { - if (cmpSize == EA_4BYTE) - { - regNumber tmpRegOp1 = rsGetRsvdReg(); - assert(regOp1 != tmpRegOp1); - emit->emitIns_R_R(INS_sext_w, EA_8BYTE, tmpRegOp1, regOp1); - regOp1 = tmpRegOp1; - } - } - - switch (cond.GetCode()) - { - case GenCondition::EQ: - regs |= ((int)regOp1); - ins = INS_beq; - break; - case GenCondition::NE: - regs |= ((int)regOp1); - ins = INS_bne; - break; - case GenCondition::UGE: - case GenCondition::SGE: - regs |= ((int)regOp1); - ins = cond.IsUnsigned() ? INS_bgeu : INS_bge; - break; - case GenCondition::UGT: - case GenCondition::SGT: - regs = imm ? ((((int)regOp1) << 5) | (int)REG_RA) : (((int)regOp1) << 5); - ins = cond.IsUnsigned() ? INS_bltu : INS_blt; - break; - case GenCondition::ULT: - case GenCondition::SLT: - regs |= ((int)regOp1); - ins = cond.IsUnsigned() ? INS_bltu : INS_blt; - break; - case GenCondition::ULE: - case GenCondition::SLE: - regs = imm ? ((((int)regOp1) << 5) | (int)REG_RA) : (((int)regOp1) << 5); - ins = cond.IsUnsigned() ? INS_bgeu : INS_bge; - break; - default: - NO_WAY("unexpected condition type"); - break; - } + cond = GenCondition::Swap(cond); + std::swap(reg1, reg2); } - else - { - regNumber regOp2 = op2->GetRegNum(); - if (cmpSize == EA_4BYTE) - { - regNumber tmpRegOp1 = REG_RA; - regNumber tmpRegOp2 = rsGetRsvdReg(); - assert(regOp1 != tmpRegOp2); - assert(regOp2 != tmpRegOp2); - emit->emitIns_R_R(INS_sext_w, EA_8BYTE, tmpRegOp1, regOp1); - emit->emitIns_R_R(INS_sext_w, EA_8BYTE, tmpRegOp2, regOp2); - regOp1 = tmpRegOp1; - regOp2 = tmpRegOp2; - } - switch (cond.GetCode()) - { - case GenCondition::EQ: - regs = (((int)regOp1) << 5) | (int)regOp2; - ins = INS_beq; - break; - case GenCondition::NE: - regs = (((int)regOp1) << 5) | (int)regOp2; - ins = INS_bne; - break; - case GenCondition::UGE: - case GenCondition::SGE: - regs = ((int)regOp1 | ((int)regOp2 << 5)); - ins = cond.IsUnsigned() ? INS_bgeu : INS_bge; - break; - case GenCondition::UGT: - case GenCondition::SGT: - regs = (((int)regOp1) << 5) | (int)regOp2; - ins = cond.IsUnsigned() ? INS_bltu : INS_blt; - break; - case GenCondition::ULT: - case GenCondition::SLT: - regs = ((int)regOp1 | ((int)regOp2 << 5)); - ins = cond.IsUnsigned() ? INS_bltu : INS_blt; - break; - case GenCondition::ULE: - case GenCondition::SLE: - regs = (((int)regOp1) << 5) | (int)regOp2; - ins = cond.IsUnsigned() ? INS_bgeu : INS_bge; - break; - default: - NO_WAY("unexpected condition type-regs"); - break; - } + instruction ins = INS_invalid; + switch (cond.GetCode()) + { + case GenCondition::EQ: + ins = INS_beq; + break; + case GenCondition::NE: + ins = INS_bne; + break; + case GenCondition::SGE: + ins = INS_bge; + break; + case GenCondition::UGE: + ins = INS_bgeu; + break; + case GenCondition::SLT: + ins = INS_blt; + break; + case GenCondition::ULT: + ins = INS_bltu; + break; + default: + NO_WAY("unexpected branch condition"); + break; } - assert(ins != INS_invalid); - assert(regs != 0); - emit->emitIns_J(ins, compiler->compCurBB->GetTrueTarget(), regs); // 5-bits; + assert(emitter::isGeneralRegisterOrR0(reg1) && emitter::isGeneralRegisterOrR0(reg2)); + int regs = (int)reg1 | (((int)reg2) << 5); + GetEmitter()->emitIns_J(ins, compiler->compCurBB->GetTrueTarget(), regs); // If we cannot fall into the false target, emit a jump to it BasicBlock* falseTarget = compiler->compCurBB->GetFalseTarget(); diff --git a/src/coreclr/jit/emitriscv64.cpp b/src/coreclr/jit/emitriscv64.cpp index edb5d94ccee070..1c19bc7e6f9030 100644 --- a/src/coreclr/jit/emitriscv64.cpp +++ b/src/coreclr/jit/emitriscv64.cpp @@ -738,7 +738,7 @@ void emitter::emitIns_R_R_I( (INS_lb <= ins && INS_lhu >= ins) || INS_ld == ins || INS_lw == ins || INS_jalr == ins || INS_fld == ins || INS_flw == ins || INS_slli_uw == ins || INS_rori == ins || INS_roriw == ins) { - assert(isGeneralRegister(reg2)); + assert(isGeneralRegisterOrR0(reg2)); code |= (reg1 & 0x1f) << 7; // rd code |= reg2 << 15; // rs1 code |= imm << 20; // imm @@ -5433,15 +5433,6 @@ regNumber emitter::emitInsTernary(instruction ins, emitAttr attr, GenTree* dst, // n * n bytes will store n bytes result emitIns_R_R_R(ins, attr, dstReg, src1Reg, src2Reg); - if ((dst->gtFlags & GTF_UNSIGNED) != 0) - { - if (attr == EA_4BYTE) - { - emitIns_R_R_I(INS_slli, EA_8BYTE, dstReg, dstReg, 32); - emitIns_R_R_I(INS_srli, EA_8BYTE, dstReg, dstReg, 32); - } - } - if (needCheckOv) { assert(tempReg != dstReg); diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index 73597ea88edffa..24a136a83d9fa6 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -4511,7 +4511,18 @@ GenTree* Lowering::LowerCompare(GenTree* cmp) cmp->gtFlags |= GTF_UNSIGNED; } } -#endif // TARGET_XARCH +#elif defined(TARGET_RISCV64) + if (varTypeUsesIntReg(cmp->gtGetOp1())) + { + if (GenTree* next = LowerSavedIntegerCompare(cmp); next != cmp) + return next; + + // Integer comparisons are full-register only. + SignExtendIfNecessary(&cmp->AsOp()->gtOp1); + SignExtendIfNecessary(&cmp->AsOp()->gtOp2); + } +#endif // TARGET_RISCV64 + ContainCheckCompare(cmp->AsOp()); return cmp->gtNext; } @@ -7789,7 +7800,7 @@ bool Lowering::LowerUnsignedDivOrMod(GenTreeOp* divMod) { divMod->ChangeOper(GT_GE); divMod->gtFlags |= GTF_UNSIGNED; - ContainCheckNode(divMod); + LowerNode(divMod); return true; } } diff --git a/src/coreclr/jit/lower.h b/src/coreclr/jit/lower.h index fb8d87b982d177..09687301a33679 100644 --- a/src/coreclr/jit/lower.h +++ b/src/coreclr/jit/lower.h @@ -156,9 +156,13 @@ class Lowering final : public Phase #ifndef TARGET_64BIT GenTree* DecomposeLongCompare(GenTree* cmp); #endif - GenTree* OptimizeConstCompare(GenTree* cmp); - GenTree* LowerCompare(GenTree* cmp); - GenTree* LowerJTrue(GenTreeOp* jtrue); + GenTree* OptimizeConstCompare(GenTree* cmp); + GenTree* LowerCompare(GenTree* cmp); + GenTree* LowerJTrue(GenTreeOp* jtrue); +#ifdef TARGET_RISCV64 + GenTree* LowerSavedIntegerCompare(GenTree* cmp); + void SignExtendIfNecessary(GenTree** arg); +#endif GenTree* LowerSelect(GenTreeConditional* cond); bool TryLowerConditionToFlagsNode(GenTree* parent, GenTree* condition, diff --git a/src/coreclr/jit/lowerriscv64.cpp b/src/coreclr/jit/lowerriscv64.cpp index 7f397d9272792f..afad339cc76a68 100644 --- a/src/coreclr/jit/lowerriscv64.cpp +++ b/src/coreclr/jit/lowerriscv64.cpp @@ -64,14 +64,6 @@ bool Lowering::IsContainableImmed(GenTree* parentNode, GenTree* childNode) const switch (parentNode->OperGet()) { - case GT_EQ: - case GT_NE: - return emitter::isValidSimm12(-immVal) || (immVal == -2048); - - case GT_LE: // a <= N -> a < N+1 - case GT_GT: // a > N -> !(a <= N) -> !(a < N+1) - immVal += 1; - FALLTHROUGH; case GT_LT: case GT_GE: case GT_ADD: @@ -79,9 +71,12 @@ bool Lowering::IsContainableImmed(GenTree* parentNode, GenTree* childNode) const case GT_OR: case GT_XOR: return emitter::isValidSimm12(immVal); - case GT_JCMP: - return true; + case GT_EQ: + case GT_NE: + case GT_GT: + case GT_LE: + case GT_JCMP: case GT_CMPXCHG: case GT_XORR: case GT_XAND: @@ -133,57 +128,158 @@ GenTree* Lowering::LowerMul(GenTreeOp* mul) // GenTree* Lowering::LowerJTrue(GenTreeOp* jtrue) { - GenTree* op = jtrue->gtGetOp1(); - GenCondition cond; - GenTree* cmpOp1; - GenTree* cmpOp2; - - assert(!op->OperIsCompare() || op->OperIsCmpCompare()); // We do not expect any other relops on RISCV64 + GenTree* cmp = jtrue->gtGetOp1(); + assert(!cmp->OperIsCompare() || cmp->OperIsCmpCompare()); // We do not expect any other relops on RISCV64 - if (op->OperIsCompare() && !varTypeIsFloating(op->gtGetOp1())) + // for RISCV64's compare and condition-branch instructions, it's very similar to the IL instructions. + jtrue->ChangeOper(GT_JCMP); + GenTreeOpCC* jcmp = jtrue->AsOpCC(); + if (cmp->OperIsCompare() && !varTypeIsFloating(cmp->gtGetOp1())) { - cond = GenCondition::FromRelop(op); - - cmpOp1 = op->gtGetOp1(); - cmpOp2 = op->gtGetOp2(); - - // We will fall through and turn this into a JCMP(op1, op2, kind), but need to remove the relop here. - BlockRange().Remove(op); + jcmp->gtCondition = GenCondition::FromIntegralRelop(cmp); + jcmp->gtOp1 = cmp->gtGetOp1(); + jcmp->gtOp2 = cmp->gtGetOp2(); + BlockRange().Remove(cmp); } else { + // branch if (cond) ---> branch if (cond != 0) GenCondition::Code code = GenCondition::NE; - if (op->OperIsCompare() && varTypeIsFloating(op->gtGetOp1()) && (op->gtFlags & GTF_RELOP_NAN_UN) != 0) + if (cmp->OperIsCompare() && varTypeIsFloating(cmp->gtGetOp1()) && (cmp->gtFlags & GTF_RELOP_NAN_UN) != 0) { // Unordered floating-point comparisons are achieved by neg'ing the ordered counterparts. Avoid that by // reversing both the FP comparison and the zero-comparison fused with the branch. - op->ChangeOper(GenTree::ReverseRelop(op->OperGet())); - op->gtFlags &= ~GTF_RELOP_NAN_UN; + cmp->ChangeOper(GenTree::ReverseRelop(cmp->OperGet())); + cmp->gtFlags &= ~GTF_RELOP_NAN_UN; code = GenCondition::EQ; } - cond = GenCondition(code); + jcmp->gtCondition = GenCondition(code); + jcmp->gtOp1 = cmp; + jcmp->gtOp2 = comp->gtNewZeroConNode(cmp->TypeGet()); + BlockRange().InsertBefore(jcmp, jcmp->gtOp2); + } - cmpOp1 = op; - cmpOp2 = comp->gtNewZeroConNode(cmpOp1->TypeGet()); + // Comparisons fused with branches don't have immediates, re-evaluate containment for 'zero' register + if (!CheckImmedAndMakeContained(jcmp, jcmp->gtOp2)) + jcmp->gtOp2->ClearContained(); - BlockRange().InsertBefore(jtrue, cmpOp2); + return jcmp->gtNext; +} - // Fall through and turn this into a JCMP(op1, 0, NE). - } +//------------------------------------------------------------------------ +// LowerSavedIntegerCompare: lowers a integer comparison saved to a register so that it matches the available +// instructions better +// +// Arguments: +// cmp - the integer comparison to lower +// +// Return Value: +// The original compare node if lowering should proceed as usual or the next node to lower if the compare node was +// changed in such a way that lowering is no longer needed. +// +GenTree* Lowering::LowerSavedIntegerCompare(GenTree* cmp) +{ + // Branches have a full range of comparisons, these transformations would be counter-productive + LIR::Use cmpUse; + if (!BlockRange().TryGetUse(cmp, &cmpUse) || cmpUse.User()->OperIs(GT_JTRUE)) + return cmp; - // for RISCV64's compare and condition-branch instructions, - // it's very similar to the IL instructions. - jtrue->ChangeOper(GT_JCMP); - jtrue->gtOp1 = cmpOp1; - jtrue->gtOp2 = cmpOp2; - jtrue->AsOpCC()->gtCondition = cond; + GenTree*& left = cmp->AsOp()->gtOp1; + GenTree*& right = cmp->AsOp()->gtOp2; + assert(cmp->OperIsCmpCompare() && varTypeUsesIntReg(left)); + + if (cmp->OperIs(GT_EQ, GT_NE) && !right->IsIntegralConst(0)) + { + // Only equality with zero is supported + // a == b ---> (a - b) == 0 + var_types type = genActualTypeIsInt(left) ? TYP_INT : TYP_I_IMPL; + genTreeOps oper = GT_SUB; + if (right->IsIntegralConst() && !right->AsIntCon()->ImmedValNeedsReloc(comp)) + { + INT64 value = right->AsIntConCommon()->IntegralValue(); + INT64 minVal = (type == TYP_INT) ? INT_MIN : SSIZE_T_MIN; + + const INT64 min12BitImm = -2048; + if (value == min12BitImm) + { + // (a - C) == 0 ---> (a ^ C) == 0 + oper = GT_XOR; + } + else if (!right->TypeIs(TYP_BYREF) && value != minVal) + { + // a - C ---> a + (-C) + oper = GT_ADD; + right->AsIntConCommon()->SetIntegralValue(-value); + } + } + left = comp->gtNewOperNode(oper, type, left, right); + right = comp->gtNewZeroConNode(type); + BlockRange().InsertBefore(cmp, left, right); + ContainCheckBinary(left->AsOp()); + } - if (cmpOp2->IsCnsIntOrI()) + if (!right->TypeIs(TYP_BYREF) && right->IsIntegralConst() && !right->AsIntConCommon()->ImmedValNeedsReloc(comp)) { - cmpOp2->SetContained(); + if (cmp->OperIs(GT_LE, GT_GE)) + { + // a <= C ---> a < C+1 + // a >= C ---> a > C-1 + INT64 value = right->AsIntConCommon()->IntegralValue(); + + bool isOverflow; + if (cmp->OperIs(GT_LE)) + { + isOverflow = genActualTypeIsInt(left) + ? CheckedOps::AddOverflows((INT32)value, (INT32)1, cmp->IsUnsigned()) + : CheckedOps::AddOverflows((INT64)value, (INT64)1, cmp->IsUnsigned()); + } + else + { + isOverflow = genActualTypeIsInt(left) + ? CheckedOps::SubOverflows((INT32)value, (INT32)1, cmp->IsUnsigned()) + : CheckedOps::SubOverflows((INT64)value, (INT64)1, cmp->IsUnsigned()); + } + if (!isOverflow) + { + right->AsIntConCommon()->SetIntegralValue(cmp->OperIs(GT_LE) ? value + 1 : value - 1); + cmp->SetOperRaw(cmp->OperIs(GT_LE) ? GT_LT : GT_GT); + } + } + + if (cmp->OperIs(GT_LT) && right->IsIntegralConst(0) && !cmp->IsUnsigned()) + { + // a < 0 (signed) ---> shift the sign bit into the lowest bit + cmp->SetOperRaw(GT_RSZ); + cmp->ChangeType(genActualType(left)); + right->AsIntConCommon()->SetIntegralValue(genTypeSize(cmp) * BITS_PER_BYTE - 1); + right->SetContained(); + return cmp->gtNext; + } } + return cmp; +} + +//------------------------------------------------------------------------ +// SignExtendIfNecessary: inserts a 32-bit sign extension unless the argument is full-register or is known to be +// implemented with a sign-extending instruction. +// +// Arguments: +// arg - the argument to sign-extend +// +void Lowering::SignExtendIfNecessary(GenTree** arg) +{ + assert(varTypeUsesIntReg(*arg)); + if (!genActualTypeIsInt(*arg)) + return; + + if ((*arg)->OperIs(GT_ADD, GT_SUB, GT_MUL, GT_MOD, GT_UMOD, GT_DIV, GT_UDIV, GT_CNS_INT)) + return; - return jtrue->gtNext; + if ((*arg)->OperIsShiftOrRotate() || (*arg)->OperIsCmpCompare() || (*arg)->OperIsAtomicOp()) + return; + + *arg = comp->gtNewCastNode(TYP_I_IMPL, *arg, false, TYP_I_IMPL); + BlockRange().InsertAfter((*arg)->gtGetOp1(), *arg); } //------------------------------------------------------------------------ @@ -1140,6 +1236,9 @@ void Lowering::ContainCheckCast(GenTreeCast* node) // void Lowering::ContainCheckCompare(GenTreeOp* cmp) { + if (cmp->gtOp1->IsIntegralConst(0) && !cmp->gtOp1->AsIntCon()->ImmedValNeedsReloc(comp)) + MakeSrcContained(cmp, cmp->gtOp1); // use 'zero' register + CheckImmedAndMakeContained(cmp, cmp->gtOp2); } diff --git a/src/coreclr/jit/lsra.cpp b/src/coreclr/jit/lsra.cpp index fa260c0e7de322..05e316ceecfe7c 100644 --- a/src/coreclr/jit/lsra.cpp +++ b/src/coreclr/jit/lsra.cpp @@ -9065,23 +9065,27 @@ void LinearScan::handleOutgoingCriticalEdges(BasicBlock* block) if (lastNode->OperIs(GT_JTRUE, GT_JCMP, GT_JTEST)) { - GenTree* op = lastNode->gtGetOp1(); - consumedRegs |= genSingleTypeRegMask(op->GetRegNum()); - - if (op->OperIs(GT_COPY)) - { - GenTree* srcOp = op->gtGetOp1(); - consumedRegs |= genSingleTypeRegMask(srcOp->GetRegNum()); - } - else if (op->IsLocal()) + assert(!lastNode->OperIs(GT_JTRUE) || !lastNode->gtGetOp1()->isContained()); + if (!lastNode->gtGetOp1()->isContained()) { - GenTreeLclVarCommon* lcl = op->AsLclVarCommon(); - terminatorNodeLclVarDsc = &compiler->lvaTable[lcl->GetLclNum()]; + GenTree* op = lastNode->gtGetOp1(); + consumedRegs |= genSingleTypeRegMask(op->GetRegNum()); + + if (op->OperIs(GT_COPY)) + { + GenTree* srcOp = op->gtGetOp1(); + consumedRegs |= genSingleTypeRegMask(srcOp->GetRegNum()); + } + else if (op->IsLocal()) + { + GenTreeLclVarCommon* lcl = op->AsLclVarCommon(); + terminatorNodeLclVarDsc = &compiler->lvaTable[lcl->GetLclNum()]; + } } if (lastNode->OperIs(GT_JCMP, GT_JTEST) && !lastNode->gtGetOp2()->isContained()) { - op = lastNode->gtGetOp2(); + GenTree* op = lastNode->gtGetOp2(); consumedRegs |= genSingleTypeRegMask(op->GetRegNum()); if (op->OperIs(GT_COPY)) diff --git a/src/coreclr/jit/lsrariscv64.cpp b/src/coreclr/jit/lsrariscv64.cpp index a2a6152309f121..3896b2108e1e1a 100644 --- a/src/coreclr/jit/lsrariscv64.cpp +++ b/src/coreclr/jit/lsrariscv64.cpp @@ -202,11 +202,6 @@ int LinearScan::BuildNode(GenTree* tree) srcCount = BuildOperandUses(tree->gtGetOp1()); break; - case GT_JTRUE: - srcCount = 0; - assert(dstCount == 0); - break; - case GT_JMP: srcCount = 0; assert(dstCount == 0); @@ -440,30 +435,6 @@ int LinearScan::BuildNode(GenTree* tree) case GT_LE: case GT_GE: case GT_GT: - { - var_types op1Type = genActualType(tree->gtGetOp1()->TypeGet()); - if (!varTypeIsFloating(op1Type)) - { - emitAttr cmpSize = EA_ATTR(genTypeSize(op1Type)); - if (cmpSize == EA_4BYTE) - { - GenTree* op2 = tree->gtGetOp2(); - - bool isUnsigned = (tree->gtFlags & GTF_UNSIGNED) != 0; - bool useAddSub = !(!tree->OperIs(GT_EQ, GT_NE) || op2->IsIntegralConst(-2048)); - bool useShiftRight = !isUnsigned && ((tree->OperIs(GT_LT) && op2->IsIntegralConst(0)) || - (tree->OperIs(GT_LE) && op2->IsIntegralConst(-1))); - bool useLoadImm = isUnsigned && ((tree->OperIs(GT_LT, GT_GE) && op2->IsIntegralConst(0)) || - (tree->OperIs(GT_LE, GT_GT) && op2->IsIntegralConst(-1))); - - if (!useAddSub && !useShiftRight && !useLoadImm) - buildInternalIntRegisterDefForNode(tree); - } - } - buildInternalRegisterUses(); - } - FALLTHROUGH; - case GT_JCMP: srcCount = BuildCmp(tree); break; diff --git a/src/tests/JIT/Directed/compare/cmp_int32.cs b/src/tests/JIT/Directed/compare/cmp_int32.cs index 7bb555b7c2e635..e1ca0c79b8aea3 100644 --- a/src/tests/JIT/Directed/compare/cmp_int32.cs +++ b/src/tests/JIT/Directed/compare/cmp_int32.cs @@ -94,6 +94,12 @@ public static class CompareTestInt [MethodImplAttribute(MethodImplOptions.NoInlining)] public static bool NeMinus2048((int, float) x) => (x.Item1 != -2048); + [MethodImplAttribute(MethodImplOptions.NoInlining)] + public static bool EqMin((int, float) x) => (x.Item1 == int.MinValue); + + [MethodImplAttribute(MethodImplOptions.NoInlining)] + public static bool NeMin((int, float) x) => (x.Item1 != int.MinValue); + [Fact] public static void Test() { @@ -130,6 +136,9 @@ public static void Test() args = new object[] {(-2048, 0f)}; Assert.True((bool)type.GetMethod("EqMinus2048").Invoke(null, args)); Assert.False((bool)type.GetMethod("NeMinus2048").Invoke(null, args)); + args = new object[] {(int.MinValue, 0f)}; + Assert.True((bool)type.GetMethod("EqMin").Invoke(null, args)); + Assert.False((bool)type.GetMethod("NeMin").Invoke(null, args)); } } @@ -217,6 +226,12 @@ public static class CompareTestUint [MethodImplAttribute(MethodImplOptions.NoInlining)] public static bool NeMinus2048((uint, float) x) => (x.Item1 != 0xFFFF_F800u); + [MethodImplAttribute(MethodImplOptions.NoInlining)] + public static bool EqMax((uint, float) x) => (x.Item1 == uint.MaxValue); + + [MethodImplAttribute(MethodImplOptions.NoInlining)] + public static bool NeMax((uint, float) x) => (x.Item1 != uint.MaxValue); + [Fact] public static void Test() { @@ -253,5 +268,8 @@ public static void Test() args = new object[] {(0xFFFF_F800u, 0f)}; Assert.True((bool)type.GetMethod("EqMinus2048").Invoke(null, args)); Assert.False((bool)type.GetMethod("NeMinus2048").Invoke(null, args)); + args = new object[] {(uint.MaxValue, 0f)}; + Assert.True((bool)type.GetMethod("EqMax").Invoke(null, args)); + Assert.False((bool)type.GetMethod("NeMax").Invoke(null, args)); } }