diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 1be19a765767c7..859e4f9000eb31 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -5384,6 +5384,8 @@ class Compiler FoldResult fgFoldConditional(BasicBlock* block); + bool fgFoldCondToReturnBlock(BasicBlock* block); + struct MorphUnreachableInfo { MorphUnreachableInfo(Compiler* comp); @@ -6225,6 +6227,8 @@ class Compiler PhaseStatus fgDetermineFirstColdBlock(); + bool fgDedupReturnComparison(BasicBlock* block); + bool fgIsForwardBranch(BasicBlock* bJump, BasicBlock* bDest, BasicBlock* bSrc = nullptr); bool fgUpdateFlowGraph(bool doTailDup = false, bool isPhase = false); diff --git a/src/coreclr/jit/fgdiagnostic.cpp b/src/coreclr/jit/fgdiagnostic.cpp index d5b12b7d8c752b..3133d8d6a70b34 100644 --- a/src/coreclr/jit/fgdiagnostic.cpp +++ b/src/coreclr/jit/fgdiagnostic.cpp @@ -3278,9 +3278,20 @@ void Compiler::fgDebugCheckTypes(GenTree* tree) assert(!"TYP_ULONG and TYP_UINT are not legal in IR"); } - if (node->OperIs(GT_NOP)) + switch (node->OperGet()) { - assert(node->TypeIs(TYP_VOID) && "GT_NOP should be TYP_VOID."); + case GT_NOP: + case GT_JTRUE: + case GT_BOUNDS_CHECK: + if (!node->TypeIs(TYP_VOID)) + { + m_compiler->gtDispTree(node); + assert("The tree is expected to be of TYP_VOID type"); + } + break; + + default: + break; } if (varTypeIsSmall(node)) diff --git a/src/coreclr/jit/fgopt.cpp b/src/coreclr/jit/fgopt.cpp index 35ed6ab2a6b0f0..c82cc4def87f7e 100644 --- a/src/coreclr/jit/fgopt.cpp +++ b/src/coreclr/jit/fgopt.cpp @@ -4259,6 +4259,64 @@ PhaseStatus Compiler::fgUpdateFlowGraphPhase() return madeChanges ? PhaseStatus::MODIFIED_EVERYTHING : PhaseStatus::MODIFIED_NOTHING; } +//------------------------------------------------------------- +// fgDedupReturnComparison: Expands BBJ_RETURN into BBJ_COND with two +// BBJ_RETURN blocks ("return true" and "return false"). Such transformation +// helps other phases to focus only on BBJ_COND (normalization). +// +// Arguments: +// block - the BBJ_RETURN block to convert into BBJ_COND +// +// Returns: +// true if the block was converted into BBJ_COND +// +bool Compiler::fgDedupReturnComparison(BasicBlock* block) +{ +#ifdef JIT32_GCENCODER + // JIT32_GCENCODER has a hard limit on the number of epilogues, let's not add more. + return false; +#endif + + assert(block->KindIs(BBJ_RETURN)); + + // We're only interested in boolean returns + if ((info.compRetType != TYP_UBYTE) || (block == genReturnBB) || (block->lastStmt() == nullptr)) + { + return false; + } + + GenTree* rootNode = block->lastStmt()->GetRootNode(); + if (!rootNode->OperIs(GT_RETURN) || !rootNode->gtGetOp1()->OperIsCmpCompare()) + { + return false; + } + + GenTree* cmp = rootNode->gtGetOp1(); + cmp->gtFlags |= (GTF_RELOP_JMP_USED | GTF_DONT_CSE); + rootNode->ChangeOper(GT_JTRUE); + rootNode->ChangeType(TYP_VOID); + + GenTree* retTrue = gtNewOperNode(GT_RETURN, TYP_INT, gtNewTrue()); + GenTree* retFalse = gtNewOperNode(GT_RETURN, TYP_INT, gtNewFalse()); + + // Create RETURN 1/0 blocks. We expect fgHeadTailMerge to handle them if there are similar returns. + DebugInfo dbgInfo = block->lastStmt()->GetDebugInfo(); + BasicBlock* retTrueBb = fgNewBBFromTreeAfter(BBJ_RETURN, block, retTrue, dbgInfo); + BasicBlock* retFalseBb = fgNewBBFromTreeAfter(BBJ_RETURN, block, retFalse, dbgInfo); + + FlowEdge* trueEdge = fgAddRefPred(retTrueBb, block); + FlowEdge* falseEdge = fgAddRefPred(retFalseBb, block); + block->SetCond(trueEdge, falseEdge); + + // We might want to instrument 'return ' too in the future. For now apply 50%/50%. + trueEdge->setLikelihood(0.5); + falseEdge->setLikelihood(0.5); + retTrueBb->inheritWeightPercentage(block, 50); + retFalseBb->inheritWeightPercentage(block, 50); + + return true; +} + //------------------------------------------------------------- // fgUpdateFlowGraph: Removes any empty blocks, unreachable blocks, and redundant jumps. // Most of those appear after dead store removal and folding of conditionals. @@ -4354,6 +4412,15 @@ bool Compiler::fgUpdateFlowGraph(bool doTailDuplication /* = false */, bool isPh bDest = nullptr; bFalseDest = nullptr; + // Expand BBJ_RETURN into BBJ_COND when doTailDuplication is enabled + if (doTailDuplication && block->KindIs(BBJ_RETURN) && fgDedupReturnComparison(block)) + { + assert(block->KindIs(BBJ_COND)); + change = true; + modified = true; + bNext = block->Next(); + } + if (block->KindIs(BBJ_ALWAYS)) { bDest = block->GetTarget(); diff --git a/src/coreclr/jit/ifconversion.cpp b/src/coreclr/jit/ifconversion.cpp index d193d133be76ff..ef81c2580a1928 100644 --- a/src/coreclr/jit/ifconversion.cpp +++ b/src/coreclr/jit/ifconversion.cpp @@ -704,9 +704,27 @@ bool OptIfConversionDsc::optIfConvert() selectType = genActualType(m_thenOperation.node); } - // Create a select node. - GenTreeConditional* select = - m_comp->gtNewConditionalNode(GT_SELECT, m_cond, selectTrueInput, selectFalseInput, selectType); + GenTree* select = nullptr; + if (selectTrueInput->TypeIs(TYP_INT) && selectFalseInput->TypeIs(TYP_INT)) + { + if (selectTrueInput->IsIntegralConst(1) && selectFalseInput->IsIntegralConst(0)) + { + // compare ? true : false --> compare + select = m_cond; + } + else if (selectTrueInput->IsIntegralConst(0) && selectFalseInput->IsIntegralConst(1)) + { + // compare ? false : true --> reversed_compare + select = m_comp->gtReverseCond(m_cond); + } + } + + if (select == nullptr) + { + // Create a select node + select = m_comp->gtNewConditionalNode(GT_SELECT, m_cond, selectTrueInput, selectFalseInput, selectType); + } + m_thenOperation.node->AddAllEffectsFlags(select); // Use the select as the source of the Then operation. diff --git a/src/coreclr/jit/optimizebools.cpp b/src/coreclr/jit/optimizebools.cpp index 4a6057e122d014..451f35241799e2 100644 --- a/src/coreclr/jit/optimizebools.cpp +++ b/src/coreclr/jit/optimizebools.cpp @@ -67,20 +67,17 @@ class OptBoolsDsc { m_b1 = b1; m_b2 = b2; - m_b3 = nullptr; m_comp = comp; } private: BasicBlock* m_b1; // The first basic block with the BBJ_COND conditional jump type - BasicBlock* m_b2; // The next basic block of m_b1. Either BBJ_COND or BBJ_RETURN type - BasicBlock* m_b3; // m_b1's target block. Null if m_b2 is not a return block. + BasicBlock* m_b2; // The next basic block of m_b1. BBJ_COND type Compiler* m_comp; // The pointer to the Compiler instance OptTestInfo m_testInfo1; // The first test info OptTestInfo m_testInfo2; // The second test info - GenTree* m_t3; // The root node of the first statement of m_b3 GenTree* m_c1; // The first operand of m_testInfo1.compTree GenTree* m_c2; // The first operand of m_testInfo2.compTree @@ -95,7 +92,6 @@ class OptBoolsDsc bool optOptimizeBoolsCondBlock(); bool optOptimizeCompareChainCondBlock(); bool optOptimizeRangeTests(); - bool optOptimizeBoolsReturnBlock(BasicBlock* b3); #ifdef DEBUG void optOptimizeBoolsGcStress(); #endif @@ -125,16 +121,6 @@ class OptBoolsDsc // B1 : brtrue(t1|t2, BX) // B3 : // -// For example, (x == 0 && y == 0 && z == 0) generates -// B1: GT_JTRUE (BBJ_COND), jump to B4 -// B2: GT_JTRUE (BBJ_COND), jump to B4 -// B3: GT_RETURN/GT_SWIFT_ERROR_RET (BBJ_RETURN) -// B4: GT_RETURN/GT_SWIFT_ERROR_RET (BBJ_RETURN) -// and B1 and B2 are folded into B1: -// B1: GT_JTRUE (BBJ_COND), jump to B4 -// B3: GT_RETURN/GT_SWIFT_ERROR_RET (BBJ_RETURN) -// B4: GT_RETURN/GT_SWIFT_ERROR_RET (BBJ_RETURN) -// // Case 2: if B2->FalseTargetIs(B1->GetTarget()), it transforms // B1 : brtrue(t1, B3) // B2 : brtrue(t2, Bx) @@ -145,12 +131,9 @@ class OptBoolsDsc // bool OptBoolsDsc::optOptimizeBoolsCondBlock() { - assert(m_b1 != nullptr && m_b2 != nullptr && m_b3 == nullptr); + assert(m_b1 != nullptr && m_b2 != nullptr); // Check if m_b1 and m_b2 jump to the same target and get back pointers to m_testInfo1 and t2 tree nodes - - m_t3 = nullptr; - // Check if m_b1 and m_b2 have the same target if (m_b1->TrueTargetIs(m_b2->GetTrueTarget())) @@ -751,7 +734,7 @@ bool FoldRangeTests(Compiler* comp, GenTreeOp* cmp1, bool cmp1IsReversed, GenTre bool OptBoolsDsc::optOptimizeRangeTests() { // At this point we have two consecutive conditional blocks (BBJ_COND): m_b1 and m_b2 - assert((m_b1 != nullptr) && (m_b2 != nullptr) && (m_b3 == nullptr)); + assert((m_b1 != nullptr) && (m_b2 != nullptr)); assert(m_b1->KindIs(BBJ_COND) && m_b2->KindIs(BBJ_COND) && m_b1->FalseTargetIs(m_b2)); if (m_b2->isRunRarely()) @@ -962,8 +945,7 @@ bool OptBoolsDsc::optOptimizeRangeTests() // bool OptBoolsDsc::optOptimizeCompareChainCondBlock() { - assert((m_b1 != nullptr) && (m_b2 != nullptr) && (m_b3 == nullptr)); - m_t3 = nullptr; + assert((m_b1 != nullptr) && (m_b2 != nullptr)); bool foundEndOfOrConditions = false; if (m_b1->FalseTargetIs(m_b2) && m_b2->FalseTargetIs(m_b1->GetTrueTarget())) @@ -1121,15 +1103,9 @@ Statement* OptBoolsDsc::optOptimizeBoolsChkBlkCond() { assert(m_b1 != nullptr && m_b2 != nullptr); - bool optReturnBlock = false; - if (m_b3 != nullptr) - { - optReturnBlock = true; - } - // Find the block conditions of m_b1 and m_b2 - if (m_b2->countOfInEdges() > 1 || (optReturnBlock && m_b3->countOfInEdges() > 1)) + if (m_b2->countOfInEdges() > 1) { return nullptr; } @@ -1150,49 +1126,7 @@ Statement* OptBoolsDsc::optOptimizeBoolsChkBlkCond() } GenTree* testTree2 = s2->GetRootNode(); - - if (!optReturnBlock) - { - assert(testTree2->OperIs(GT_JTRUE)); - } - else - { - if (!testTree2->OperIs(GT_RETURN, GT_SWIFT_ERROR_RET)) - { - return nullptr; - } - - Statement* s3 = m_b3->firstStmt(); - if (s3->GetPrevStmt() != s3) - { - return nullptr; - } - - GenTree* testTree3 = s3->GetRootNode(); - if (!testTree3->OperIs(GT_RETURN, GT_SWIFT_ERROR_RET)) - { - return nullptr; - } - - if (!varTypeIsIntegral(testTree2->TypeGet()) || !varTypeIsIntegral(testTree3->TypeGet())) - { - return nullptr; - } - - // The third block is Return with "CNS_INT int 0/1" - GenTree* const retVal = testTree3->AsOp()->GetReturnValue(); - if (!retVal->OperIs(GT_CNS_INT)) - { - return nullptr; - } - - if (!retVal->TypeIs(TYP_INT)) - { - return nullptr; - } - - m_t3 = testTree3; - } + assert(testTree2->OperIs(GT_JTRUE)); m_testInfo1.testStmt = s1; m_testInfo1.testTree = testTree1; @@ -1263,13 +1197,6 @@ bool OptBoolsDsc::optOptimizeBoolsChkTypeCostCond() void OptBoolsDsc::optOptimizeBoolsUpdateTrees() { assert(m_b1 != nullptr && m_b2 != nullptr); - - bool optReturnBlock = false; - if (m_b3 != nullptr) - { - optReturnBlock = true; - } - assert(m_cmpOp != GT_NONE && m_c1 != nullptr && m_c2 != nullptr); GenTree* cmpOp1 = m_foldOp == GT_NONE ? m_c1 : m_comp->gtNewOperNode(m_foldOp, m_foldType, m_c1, m_c2); @@ -1278,17 +1205,6 @@ void OptBoolsDsc::optOptimizeBoolsUpdateTrees() t1Comp->SetOper(m_cmpOp); t1Comp->AsOp()->gtOp1 = cmpOp1; t1Comp->AsOp()->gtOp2->gtType = m_foldType; // Could have been varTypeIsGC() - if (optReturnBlock) - { - // Update tree when m_b1 is BBJ_COND and m_b2 and m_b3 are GT_RETURN/GT_SWIFT_ERROR_RET (BBJ_RETURN) - t1Comp->AsOp()->gtOp2->AsIntCon()->gtIconVal = 0; - m_testInfo1.testTree->gtOper = m_testInfo2.testTree->OperGet(); - m_testInfo1.testTree->gtType = m_testInfo2.testTree->TypeGet(); - - // Update the return count of flow graph - assert(m_comp->fgReturnCount >= 2); - --m_comp->fgReturnCount; - } // Recost/rethread the tree if necessary // @@ -1300,15 +1216,6 @@ void OptBoolsDsc::optOptimizeBoolsUpdateTrees() /* Modify the target of the conditional jump and update bbRefs and bbPreds */ - if (optReturnBlock) - { - assert(m_b1->KindIs(BBJ_COND)); - assert(m_b2->KindIs(BBJ_RETURN)); - assert(m_b1->FalseTargetIs(m_b2)); - assert(m_b3 != nullptr); - m_b1->SetKindAndTargetEdge(BBJ_RETURN); - } - else { // Modify b1, if necessary, so it has the same // true target as b2. @@ -1386,253 +1293,8 @@ void OptBoolsDsc::optOptimizeBoolsUpdateTrees() // If m_b2 was the last block of a try or handler, update the EH table. m_comp->ehUpdateForDeletedBlock(m_b2); - if (optReturnBlock) - { - // Get rid of the third block - m_comp->fgUnlinkBlockForRemoval(m_b3); - m_b3->SetFlags(BBF_REMOVED); - // If m_b3 was the last block of a try or handler, update the EH table. - m_comp->ehUpdateForDeletedBlock(m_b3); - } - // Update IL range of first block - m_b1->bbCodeOffsEnd = optReturnBlock ? m_b3->bbCodeOffsEnd : m_b2->bbCodeOffsEnd; -} - -//----------------------------------------------------------------------------- -// optOptimizeBoolsReturnBlock: Optimize boolean when m_b1 is BBJ_COND and m_b2 and m_b3 are BBJ_RETURN -// -// Arguments: -// b3: Pointer to basic block b3 -// -// Returns: -// true if boolean optimization is done and m_b1, m_b2 and m_b3 are folded into m_b1, else false. -// -// Notes: -// m_b1, m_b2 and m_b3 of OptBoolsDsc are set on entry. -// -// if B1->TargetIs(b3), it transforms -// B1 : brtrue(t1, B3) -// B2 : ret(t2) -// B3 : ret(0) -// to -// B1 : ret((!t1) && t2) -// -// For example, (x==0 && y==0) generates: -// B1: GT_JTRUE (BBJ_COND), jumps to B3 -// B2: GT_RETURN/GT_SWIFT_ERROR (BBJ_RETURN) -// B3: GT_RETURN/GT_SWIFT_ERROR (BBJ_RETURN), -// and it is folded into -// B1: GT_RETURN/GT_SWIFT_ERROR (BBJ_RETURN) -// -bool OptBoolsDsc::optOptimizeBoolsReturnBlock(BasicBlock* b3) -{ - assert(m_b1 != nullptr && m_b2 != nullptr); - - // m_b3 is set for cond/return/return case - m_b3 = b3; - - m_sameTarget = false; - Statement* const s1 = optOptimizeBoolsChkBlkCond(); - if (s1 == nullptr) - { - return false; - } - - // Find the branch conditions of m_b1 and m_b2 - - m_c1 = optIsBoolComp(&m_testInfo1); - if (m_c1 == nullptr) - { - return false; - } - - m_c2 = optIsBoolComp(&m_testInfo2); - if (m_c2 == nullptr) - { - return false; - } - - // Find the type and cost conditions of m_testInfo1 and m_testInfo2 - - if (!optOptimizeBoolsChkTypeCostCond()) - { - return false; - } - - // Get the fold operator (m_foldOp, e.g., GT_OR/GT_AND) and - // the comparison operator (m_cmpOp, e.g., GT_EQ/GT_NE/GT_GE/GT_LT) - - var_types foldType = genActualType(m_c1->TypeGet()); - if (varTypeIsGC(foldType)) - { - foldType = TYP_I_IMPL; - } - m_foldType = foldType; - - m_foldOp = GT_NONE; - m_cmpOp = GT_NONE; - - genTreeOps foldOp; - genTreeOps cmpOp; - - ssize_t it1val = m_testInfo1.compTree->AsOp()->gtOp2->AsIntCon()->gtIconVal; - ssize_t it2val = m_testInfo2.compTree->AsOp()->gtOp2->AsIntCon()->gtIconVal; - ssize_t it3val = m_t3->AsOp()->gtOp1->AsIntCon()->gtIconVal; - - if (m_c1->gtOper == GT_LCL_VAR && m_c2->gtOper == GT_LCL_VAR && - m_c1->AsLclVarCommon()->GetLclNum() == m_c2->AsLclVarCommon()->GetLclNum()) - { - if (((m_testInfo1.compTree->gtOper == GT_LT && m_testInfo2.compTree->gtOper == GT_EQ) || - (m_testInfo1.compTree->gtOper == GT_EQ && m_testInfo2.compTree->gtOper == GT_LT)) && - it3val == 1) - { - // Case: x < 0 || x == 0 - // t1:c1<0 t2:c2==0 t3:c3==1 - // ==> true if c1<=0 - // - // Case: x == 0 || x < 0 - // t1:c1==0 t2:c2<0 t3:c3==1 - // ==> true if c1 <= 0 - cmpOp = GT_LE; - } - else if (((m_testInfo1.compTree->gtOper == GT_GT && m_testInfo2.compTree->gtOper == GT_EQ) || - (m_testInfo1.compTree->gtOper == GT_EQ && m_testInfo2.compTree->gtOper == GT_GT)) && - it3val == 1) - { - // Case: x > 0 || x == 0 - // t1:c1<0 t2:c2==0 t3:c3==1 - // ==> true if c1>=0 - // - // Case: x == 0 || x > 0 - // t1:c1==0 t2:c2>0 t3:c3==1 - // ==> true if c1 >= 0 - cmpOp = GT_GE; - } - else if (((m_testInfo1.compTree->gtOper == GT_LT && m_testInfo2.compTree->gtOper == GT_NE) || - (m_testInfo1.compTree->gtOper == GT_EQ && m_testInfo2.compTree->gtOper == GT_GE)) && - it3val == 0) - { - // Case: x >= 0 && x != 0 - // t1:c1<0 t2:c2==0 t3:c3==0 - // ==> true if c1>0 - // - // Case: x != 0 && x >= 0 - // t1:c1==0 t2:c2>=0 t3:c3==0 - // ==> true if c1>0 - cmpOp = GT_GT; - } - else if (((m_testInfo1.compTree->gtOper == GT_GT && m_testInfo2.compTree->gtOper == GT_NE) || - (m_testInfo1.compTree->gtOper == GT_EQ && m_testInfo2.compTree->gtOper == GT_LE)) && - it3val == 0) - { - // Case: x <= 0 && x != 0 - // t1:c1<0 t2:c2==0 t3:c3==0 - // ==> true if c1<0 - // - // Case: x != 0 && x <= 0 - // t1:c1==0 t2:c2<=0 t3:c3==0 - // ==> true if c1<0 - cmpOp = GT_LT; - } - else - { - return false; - } - - foldOp = GT_NONE; - } - else if ((m_testInfo1.compTree->gtOper == GT_NE && m_testInfo2.compTree->gtOper == GT_EQ) && - (it1val == 0 && it2val == 0 && it3val == 0)) - { - // Case: x == 0 && y == 0 - // t1:c1!=0 t2:c2==0 t3:c3==0 - // ==> true if (c1|c2)==0 - foldOp = GT_OR; - cmpOp = GT_EQ; - } - else if ((m_testInfo1.compTree->gtOper == GT_EQ && m_testInfo2.compTree->gtOper == GT_NE) && - (it1val == 0 && it2val == 0 && it3val == 0)) - { - // Case: x == 1 && y ==1 - // t1:c1!=1 t2:c2==1 t3:c3==0 is reversed from optIsBoolComp() to: t1:c1==0 t2:c2!=0 t3:c3==0 - // ==> true if (c1&c2)!=0 - foldOp = GT_AND; - cmpOp = GT_NE; - } - else if ((m_testInfo1.compTree->gtOper == GT_LT && m_testInfo2.compTree->gtOper == GT_GE) && - (it1val == 0 && it2val == 0 && it3val == 0) && - (!m_testInfo1.GetTestOp()->IsUnsigned() && !m_testInfo2.GetTestOp()->IsUnsigned())) - { - // Case: x >= 0 && y >= 0 - // t1:c1<0 t2:c2>=0 t3:c3==0 - // ==> true if (c1|c2)>=0 - - foldOp = GT_OR; - cmpOp = GT_GE; - } - else if ((m_testInfo1.compTree->gtOper == GT_EQ && m_testInfo2.compTree->gtOper == GT_EQ) && - (it1val == 0 && it2val == 0 && it3val == 1)) - { - // Case: x == 0 || y == 0 - // t1:c1==0 t2:c2==0 t3:c3==1 - // ==> true if (c1&c2)==0 - foldOp = GT_AND; - cmpOp = GT_EQ; - } - else if ((m_testInfo1.compTree->gtOper == GT_NE && m_testInfo2.compTree->gtOper == GT_NE) && - (it1val == 0 && it2val == 0 && it3val == 1)) - { - // Case: x == 1 || y == 1 - // t1:c1==1 t2:c2==1 t3:c3==1 is reversed from optIsBoolComp() to: t1:c1!=0 t2:c2!=0 t3:c3==1 - // ==> true if (c1|c2)!=0 - foldOp = GT_OR; - cmpOp = GT_NE; - } - else if ((m_testInfo1.compTree->gtOper == GT_LT && m_testInfo2.compTree->gtOper == GT_LT) && - (it1val == 0 && it2val == 0 && it3val == 1) && - (!m_testInfo1.GetTestOp()->IsUnsigned() && !m_testInfo2.GetTestOp()->IsUnsigned())) - { - // Case: x < 0 || y < 0 - // t1:c1<0 t2:c2<0 t3:c3==1 - // ==> true if (c1|c2)<0 - - foldOp = GT_OR; - cmpOp = GT_LT; - } - else - { - // Require NOT operation for operand(s). Do Not fold. - return false; - } - - if ((foldOp == GT_AND || (cmpOp == GT_NE && foldOp != GT_OR)) && (!m_testInfo1.isBool || !m_testInfo2.isBool)) - { - // x == 1 && y == 1: Skip cases where x or y is greater than 1, e.g., x=3, y=1 - // x == 0 || y == 0: Skip cases where x and y have opposite bits set, e.g., x=2, y=1 - // x == 1 || y == 1: Skip cases where either x or y is greater than 1, e.g., x=2, y=0 - return false; - } - - m_foldOp = foldOp; - m_cmpOp = cmpOp; - - // Now update the trees - - optOptimizeBoolsUpdateTrees(); - -#ifdef DEBUG - if (m_comp->verbose) - { - printf("Folded %sboolean conditions of " FMT_BB ", " FMT_BB " and " FMT_BB " to :\n", - m_c2->OperIsLeaf() ? "" : "non-leaf ", m_b1->bbNum, m_b2->bbNum, m_b3->bbNum); - m_comp->gtDispStmt(s1); - printf("\n"); - } -#endif - - // Return true to continue the bool optimization for the rest of the BB chain - return true; + m_b1->bbCodeOffsEnd = m_b2->bbCodeOffsEnd; } //----------------------------------------------------------------------------- @@ -1918,7 +1580,6 @@ PhaseStatus Compiler::optOptimizeBools() bool change = false; bool retry = false; unsigned numCond = 0; - unsigned numReturn = 0; unsigned numPasses = 0; unsigned stress = false; @@ -1930,6 +1591,11 @@ PhaseStatus Compiler::optOptimizeBools() for (BasicBlock* b1 = fgFirstBB; b1 != nullptr; b1 = retry ? b1 : b1->Next()) { retry = false; + if (b1->KindIs(BBJ_COND) && fgFoldCondToReturnBlock(b1)) + { + change = true; + numCond++; + } // We're only interested in conditional jumps here @@ -1987,31 +1653,6 @@ PhaseStatus Compiler::optOptimizeBools() } #endif } - else if (b2->KindIs(BBJ_RETURN)) - { - // Set b3 to b1 jump destination - BasicBlock* b3 = b1->GetTrueTarget(); - - // b3 must not be marked as BBF_DONT_REMOVE - - if (b3->HasFlag(BBF_DONT_REMOVE)) - { - continue; - } - - // b3 must be RETURN type - - if (!b3->KindIs(BBJ_RETURN)) - { - continue; - } - - if (optBoolsDsc.optOptimizeBoolsReturnBlock(b3)) - { - change = true; - numReturn++; - } - } else { #ifdef DEBUG @@ -2022,8 +1663,141 @@ PhaseStatus Compiler::optOptimizeBools() } } while (change); - JITDUMP("\noptimized %u BBJ_COND cases, %u BBJ_RETURN cases in %u passes\n", numCond, numReturn, numPasses); + JITDUMP("\noptimized %u BBJ_COND cases in %u passes\n", numCond, numPasses); - const bool modified = stress || ((numCond + numReturn) > 0); + const bool modified = stress || (numCond > 0); return modified ? PhaseStatus::MODIFIED_EVERYTHING : PhaseStatus::MODIFIED_NOTHING; } + +//------------------------------------------------------------- +// fgFoldCondToReturnBlock: Folds BBJ_COND into BBJ_RETURN +// This operation is the opposite of what fgDedupReturnComparison does. +// We don't fold such conditionals if both return blocks have multiple predecessors. +// +// Arguments: +// block - the BBJ_COND block to convert into BBJ_RETURN +// +// Returns: +// true if the block was converted into BBJ_RETURN +// +bool Compiler::fgFoldCondToReturnBlock(BasicBlock* block) +{ + bool modified = false; + + assert(block->KindIs(BBJ_COND)); + +#ifdef JIT32_GCENCODER + // JIT32_GCENCODER has a hard limit on the number of epilogues. + return modified; +#endif + + // Early out if the current method is not returning a boolean. + if ((info.compRetType != TYP_UBYTE)) + { + return modified; + } + + // Both edges must be BBJ_RETURN + BasicBlock* retFalseBb = block->GetFalseTarget(); + BasicBlock* retTrueBb = block->GetTrueTarget(); + + // Although, we might want to fold fallthrough BBJ_ALWAYS blocks first + if (fgCanCompactBlock(retTrueBb)) + { + fgCompactBlock(retTrueBb); + modified = true; + } + // By the time we get to the retFalseBb, it might be removed by fgCompactBlock() + // so we need to check if it is still valid. + if (!retFalseBb->HasFlag(BBF_REMOVED) && fgCanCompactBlock(retFalseBb)) + { + fgCompactBlock(retFalseBb); + modified = true; + } + // Same here - bail out if the block is no longer BBJ_COND after compacting. + if (!block->KindIs(BBJ_COND)) + { + return modified; + } + + retTrueBb = block->GetTrueTarget(); + retFalseBb = block->GetFalseTarget(); + if (!retTrueBb->KindIs(BBJ_RETURN) || !retFalseBb->KindIs(BBJ_RETURN) || + !BasicBlock::sameEHRegion(block, retTrueBb) || !BasicBlock::sameEHRegion(block, retFalseBb) || + (retTrueBb == genReturnBB) || (retFalseBb == genReturnBB)) + { + // Both edges must be BBJ_RETURN + return modified; + } + + // The last statement has to be either JTRUE(cond) or JTRUE(comma(cond)), + // but let's be resilient just in case. + assert(block->lastStmt() != nullptr); + GenTree* node = block->lastStmt()->GetRootNode(); + GenTree* cond = node->gtGetOp1(); + if (!cond->OperIsCompare()) + { + return modified; + } + assert(cond->TypeIs(TYP_INT)); + + if ((retTrueBb->GetUniquePred(this) == nullptr) && (retFalseBb->GetUniquePred(this) == nullptr)) + { + // Both return blocks have multiple predecessors - bail out. + // We don't want to introduce a new epilogue. + return modified; + } + + // Is block a BBJ_RETURN(1/0) ? (single statement) + auto isReturnBool = [](const BasicBlock* block, bool value) { + if (block->KindIs(BBJ_RETURN) && block->hasSingleStmt() && (block->lastStmt() != nullptr)) + { + GenTree* node = block->lastStmt()->GetRootNode(); + return node->OperIs(GT_RETURN) && node->gtGetOp1()->IsIntegralConst(value ? 1 : 0); + } + return false; + }; + + // Make sure we deal with true/false return blocks (or false/true) + bool retTrueFalse = isReturnBool(retTrueBb, true) && isReturnBool(retFalseBb, false); + bool retFalseTrue = isReturnBool(retTrueBb, false) && isReturnBool(retFalseBb, true); + if (!retTrueFalse && !retFalseTrue) + { + return modified; + } + + // Reverse the condition if we jump to "return false" on true. + if (retFalseTrue) + { + gtReverseCond(cond); + } + modified = true; + + // Decrease the weight of the return blocks since we no longer have edges to them. + // Although one might still be reachable from other blocks. + if (retTrueBb->hasProfileWeight()) + { + retTrueBb->decreaseBBProfileWeight(block->GetTrueEdge()->getLikelyWeight()); + } + if (retFalseBb->hasProfileWeight()) + { + retFalseBb->decreaseBBProfileWeight(block->GetFalseEdge()->getLikelyWeight()); + } + + // Unlink the return blocks + fgRemoveRefPred(block->GetTrueEdge()); + fgRemoveRefPred(block->GetFalseEdge()); + block->SetKindAndTargetEdge(BBJ_RETURN); + node->ChangeOper(GT_RETURN); + node->ChangeType(TYP_INT); + cond->gtFlags &= ~GTF_RELOP_JMP_USED; + + block->bbCodeOffsEnd = max(retTrueBb->bbCodeOffsEnd, retFalseBb->bbCodeOffsEnd); + gtSetStmtInfo(block->lastStmt()); + fgSetStmtSeq(block->lastStmt()); + gtUpdateStmtSideEffects(block->lastStmt()); + + JITDUMP("fgFoldCondToReturnBlock: folding " FMT_BB " from BBJ_COND into BBJ_RETURN:", block->bbNum); + DISPBLOCK(block) + return modified; +}