-
Notifications
You must be signed in to change notification settings - Fork 5.4k
Detect and use zicond in ifconversion on riscv64 #128799
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -3150,6 +3150,61 @@ void CodeGen::genCodeForCompare(GenTreeOp* tree) | |||||
| genProduceReg(tree); | ||||||
| } | ||||||
|
|
||||||
| //------------------------------------------------------------------------ | ||||||
| // genCodeForSelect: Produce branch-free code for a GT_SELECT node using Zicond. | ||||||
| // | ||||||
| // SELECT(cond, trueVal, falseVal) => | ||||||
| // czero.nez tmp, falseVal, cond ; tmp = (cond != 0) ? 0 : falseVal | ||||||
| // czero.eqz dst, trueVal, cond ; dst = (cond == 0) ? 0 : trueVal | ||||||
| // or dst, dst, tmp | ||||||
| // | ||||||
| // Degenerate cases (one operand is the zero register) collapse to a single czero. | ||||||
| // | ||||||
| // Arguments: | ||||||
| // tree - the GT_SELECT node | ||||||
| // | ||||||
| void CodeGen::genCodeForSelect(GenTreeOp* tree) | ||||||
| { | ||||||
| assert(tree->OperIs(GT_SELECT)); | ||||||
| assert(m_compiler->compOpportunisticallyDependsOn(InstructionSet_Zicond)); | ||||||
| assert(varTypeIsIntegralOrI(tree)); | ||||||
|
|
||||||
| GenTreeConditional* sel = tree->AsConditional(); | ||||||
| GenTree* cond = sel->gtCond; | ||||||
| GenTree* trueVal = sel->gtOp1; | ||||||
| GenTree* falseVal = sel->gtOp2; | ||||||
|
|
||||||
| genConsumeRegs(cond); | ||||||
| genConsumeRegs(trueVal); | ||||||
| genConsumeRegs(falseVal); | ||||||
|
|
||||||
| regNumber targetReg = tree->GetRegNum(); | ||||||
| regNumber condReg = cond->GetRegNum(); | ||||||
| regNumber trueReg = trueVal->isContained() ? REG_ZERO : trueVal->GetRegNum(); | ||||||
| regNumber falseReg = falseVal->isContained() ? REG_ZERO : falseVal->GetRegNum(); | ||||||
|
|
||||||
| emitter* emit = GetEmitter(); | ||||||
| emitAttr attr = emitActualTypeSize(tree); | ||||||
|
|
||||||
| if (falseReg == REG_ZERO) | ||||||
| { | ||||||
| emit->emitIns_R_R_R(INS_czero_eqz, attr, targetReg, trueReg, condReg); | ||||||
| } | ||||||
| else if (trueReg == REG_ZERO) | ||||||
| { | ||||||
| emit->emitIns_R_R_R(INS_czero_nez, attr, targetReg, falseReg, condReg); | ||||||
| } | ||||||
| else | ||||||
| { | ||||||
| regNumber tmpReg = internalRegisters.Extract(tree); | ||||||
| emit->emitIns_R_R_R(INS_czero_nez, attr, tmpReg, falseReg, condReg); | ||||||
| emit->emitIns_R_R_R(INS_czero_eqz, attr, targetReg, trueReg, condReg); | ||||||
| emit->emitIns_R_R_R(INS_or, attr, targetReg, targetReg, tmpReg); | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| } | ||||||
|
|
||||||
| genProduceReg(tree); | ||||||
| } | ||||||
|
|
||||||
| //------------------------------------------------------------------------ | ||||||
| // genCodeForJumpCompare: Generates code for jmpCompare statement. | ||||||
| // | ||||||
|
|
@@ -3940,6 +3995,10 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) | |||||
| genCodeForCompare(treeNode->AsOp()); | ||||||
| break; | ||||||
|
|
||||||
| case GT_SELECT: | ||||||
| genCodeForSelect(treeNode->AsOp()); | ||||||
| break; | ||||||
|
|
||||||
| case GT_JCMP: | ||||||
| genCodeForJumpCompare(treeNode->AsOpCC()); | ||||||
| break; | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -639,8 +639,38 @@ bool OptIfConversionDsc::optIfConvert(int* pReachabilityBudget) | |||||||||||||||
| #ifdef TARGET_RISCV64 | ||||||||||||||||
| if (select->OperIs(GT_SELECT)) | ||||||||||||||||
| { | ||||||||||||||||
| JITDUMP("Skipping if-conversion that could not be optimized to ordinary operations\n"); | ||||||||||||||||
| return true; | ||||||||||||||||
| // Without Zicond, riscv64 has no native lowering for GT_SELECT - the | ||||||||||||||||
| // branchy form is kept. With Zicond the SELECT is lowered to a | ||||||||||||||||
| // czero.{eqz,nez}/or sequence, but only for integer-typed selects. | ||||||||||||||||
| bool canCodegenSelect = m_compiler->compOpportunisticallyDependsOn(InstructionSet_Zicond) && | ||||||||||||||||
| varTypeIsIntegralOrI(select->TypeGet()); | ||||||||||||||||
| if (!canCodegenSelect) | ||||||||||||||||
| { | ||||||||||||||||
| JITDUMP("Skipping if-conversion that could not be optimized to ordinary operations\n"); | ||||||||||||||||
| return true; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| // RISC-V has no flag register, so any branchless SELECT must first materialize | ||||||||||||||||
| // the condition into a 0/1 register before the czero/or sequence (>=5 insns). | ||||||||||||||||
| // When one arm is a no-op (read of an existing local) and the other is a small | ||||||||||||||||
| // immediate, the branchy form fits in ~2 insns (branch + addi), so keep it. | ||||||||||||||||
| GenTreeConditional* sel = select->AsConditional(); | ||||||||||||||||
| GenTree* selTrue = sel->gtOp1; | ||||||||||||||||
| GenTree* selFalse = sel->gtOp2; | ||||||||||||||||
|
|
||||||||||||||||
| auto isLocalNoOp = [](GenTree* n) { | ||||||||||||||||
| return n->OperIs(GT_LCL_VAR); | ||||||||||||||||
| }; | ||||||||||||||||
| auto isSmallImm = [](GenTree* n) { | ||||||||||||||||
| return n->IsIntegralConst() && (n->AsIntConCommon()->IntegralValue() >= -2048) && | ||||||||||||||||
| (n->AsIntConCommon()->IntegralValue() <= 2047); | ||||||||||||||||
| }; | ||||||||||||||||
|
Comment on lines
+664
to
+667
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||
|
|
||||||||||||||||
| if ((isLocalNoOp(selTrue) && isSmallImm(selFalse)) || (isSmallImm(selTrue) && isLocalNoOp(selFalse))) | ||||||||||||||||
| { | ||||||||||||||||
| JITDUMP("Skipping if-conversion: branchy form fits in ~2 insns; Zicond would need 5+\n"); | ||||||||||||||||
| return true; | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
| #endif | ||||||||||||||||
|
|
||||||||||||||||
|
|
||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -1283,7 +1283,61 @@ void Lowering::ContainCheckCompare(GenTreeOp* cmp) | |||||||||||||||||
| // | ||||||||||||||||||
| void Lowering::ContainCheckSelect(GenTreeOp* node) | ||||||||||||||||||
| { | ||||||||||||||||||
| noway_assert(!"GT_SELECT nodes are not supported on riscv64"); | ||||||||||||||||||
| assert(node->OperIs(GT_SELECT)); | ||||||||||||||||||
| // GT_SELECT is only produced/codegen'd on riscv64 when Zicond is available; integer-only. | ||||||||||||||||||
| assert(m_compiler->compOpportunisticallyDependsOn(InstructionSet_Zicond)); | ||||||||||||||||||
| assert(varTypeIsIntegralOrI(node)); | ||||||||||||||||||
|
|
||||||||||||||||||
| GenTreeConditional* sel = node->AsConditional(); | ||||||||||||||||||
|
|
||||||||||||||||||
| // czero.{eqz,nez} encode both polarities, so reversed relops (GE/LE and unsigned | ||||||||||||||||||
| // EQ/NE-with-zero) that would otherwise emit slt+xori can have the trailing xori | ||||||||||||||||||
| // dropped by swapping the SELECT arms and using the un-reversed compare directly. | ||||||||||||||||||
| GenTree* cond = sel->gtCond; | ||||||||||||||||||
| if (cond->OperIsCompare()) | ||||||||||||||||||
| { | ||||||||||||||||||
| GenTreeOp* relop = cond->AsOp(); | ||||||||||||||||||
| genTreeOps oper = relop->OperGet(); | ||||||||||||||||||
| // genCodeForCompare reverses GE/LE into LT/GT and emits a trailing xori; it | ||||||||||||||||||
| // also rewrites unsigned EQ(x,0)/NE(x,0) into LE/GT and reverses again. Both | ||||||||||||||||||
| // patterns end up materializing the negation as xori reg, reg, 1. | ||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
runtime/src/coreclr/jit/lowerriscv64.cpp Lines 147 to 154 in f2d5042
|
||||||||||||||||||
| bool relopIsReversed = oper == GT_GE || oper == GT_LE; | ||||||||||||||||||
| if (!relopIsReversed && relop->OperIs(GT_EQ, GT_NE)) | ||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
nit, if it's EQ or NE, then it's not LE or GE |
||||||||||||||||||
| { | ||||||||||||||||||
| GenTree* relopOp2 = relop->gtGetOp2(); | ||||||||||||||||||
| relopIsReversed = oper == GT_EQ && relopOp2->IsIntegralConst(0) && relopOp2->isContained(); | ||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could remove EQ|NE 0 and leave just |
||||||||||||||||||
| } | ||||||||||||||||||
| LIR::Use condUse; | ||||||||||||||||||
| if (relopIsReversed && BlockRange().TryGetUse(cond, &condUse) && (condUse.User() == sel)) | ||||||||||||||||||
| { | ||||||||||||||||||
| relop->SetOper(GenTree::ReverseRelop(oper)); | ||||||||||||||||||
| std::swap(sel->gtOp1, sel->gtOp2); | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| // Also fold an explicit XOR-with-1 negation produced by other lowerings. | ||||||||||||||||||
| else if (cond->OperIs(GT_XOR)) | ||||||||||||||||||
| { | ||||||||||||||||||
| GenTree* xorLhs = cond->AsOp()->gtGetOp1(); | ||||||||||||||||||
| GenTree* xorRhs = cond->AsOp()->gtGetOp2(); | ||||||||||||||||||
| LIR::Use condUse; | ||||||||||||||||||
| if (xorRhs->IsIntegralConst(1) && xorLhs->OperIsCompare() && BlockRange().TryGetUse(cond, &condUse) && | ||||||||||||||||||
| (condUse.User() == sel)) | ||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why check the user? Isn't |
||||||||||||||||||
| { | ||||||||||||||||||
| sel->gtCond = xorLhs; | ||||||||||||||||||
| std::swap(sel->gtOp1, sel->gtOp2); | ||||||||||||||||||
| BlockRange().Remove(xorRhs); | ||||||||||||||||||
| BlockRange().Remove(cond); | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| // czero.{eqz,nez} take a register source and a register condition, so the only | ||||||||||||||||||
| // contained form is an integral-zero operand that can be expressed via REG_ZERO. | ||||||||||||||||||
| GenTree* op1 = sel->gtOp1; | ||||||||||||||||||
| GenTree* op2 = sel->gtOp2; | ||||||||||||||||||
| if (op1->IsIntegralConst(0) && !op1->AsIntCon()->ImmedValNeedsReloc(m_compiler)) | ||||||||||||||||||
| MakeSrcContained(node, op1); | ||||||||||||||||||
|
Comment on lines
+1337
to
+1338
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can this happen? Isn't SELECT normalized to have a constant always on RHS? |
||||||||||||||||||
| if (op2->IsIntegralConst(0) && !op2->AsIntCon()->ImmedValNeedsReloc(m_compiler)) | ||||||||||||||||||
| MakeSrcContained(node, op2); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| //------------------------------------------------------------------------ | ||||||||||||||||||
|
|
||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -458,6 +458,19 @@ int LinearScan::BuildNode(GenTree* tree) | |
| srcCount = BuildCmp(tree); | ||
| break; | ||
|
|
||
| case GT_SELECT: | ||
| { | ||
| GenTreeConditional* sel = tree->AsConditional(); | ||
| srcCount = BuildOperandUses(sel->gtCond); | ||
| srcCount += BuildOperandUses(sel->gtOp1); | ||
| srcCount += BuildOperandUses(sel->gtOp2); | ||
| buildInternalIntRegisterDefForNode(tree); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You don't need a temp if the constant op is zero |
||
| assert(dstCount == 1); | ||
| BuildDef(tree); | ||
| buildInternalRegisterUses(); | ||
| } | ||
| break; | ||
|
|
||
| case GT_CKFINITE: | ||
| srcCount = 1; | ||
| assert(dstCount == 1); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.