diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 916f277a1275c7..7214402d6f8c64 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -6128,6 +6128,7 @@ class Compiler #endif // FEATURE_EH_WINDOWS_X86 bool fgOptimizeUncondBranchToSimpleCond(BasicBlock* block, BasicBlock* target); + bool fgFoldSimpleCondByForwardSub(BasicBlock* block); bool fgBlockEndFavorsTailDuplication(BasicBlock* block, unsigned lclNum); diff --git a/src/coreclr/jit/fgopt.cpp b/src/coreclr/jit/fgopt.cpp index b258481910c29b..5431d76afc6925 100644 --- a/src/coreclr/jit/fgopt.cpp +++ b/src/coreclr/jit/fgopt.cpp @@ -1966,6 +1966,12 @@ bool Compiler::fgBlockIsGoodTailDuplicationCandidate(BasicBlock* target, unsigne return false; } + // No point duplicating this block if it would not remove (part of) the join. + if (target->TrueTargetIs(target) || target->FalseTargetIs(target)) + { + return false; + } + Statement* const lastStmt = target->lastStmt(); Statement* const firstStmt = target->FirstNonPhiDef(); @@ -2265,6 +2271,108 @@ bool Compiler::fgOptimizeUncondBranchToSimpleCond(BasicBlock* block, BasicBlock* return true; } +//------------------------------------------------------------- +// fgFoldSimpleCondByForwardSub: +// Try to refine the flow of a block that may have just been tail duplicated +// or compacted. +// +// Arguments: +// block - block that was tail duplicated or compacted +// +// Returns Value: +// true if control flow was changed +// +bool Compiler::fgFoldSimpleCondByForwardSub(BasicBlock* block) +{ + assert(block->KindIs(BBJ_COND)); + GenTree* jtrue = block->lastStmt()->GetRootNode(); + assert(jtrue->OperIs(GT_JTRUE)); + + GenTree* relop = jtrue->gtGetOp1(); + if (!relop->OperIsCompare()) + { + return false; + } + + GenTree* op1 = relop->gtGetOp1(); + GenTree* op2 = relop->gtGetOp2(); + + GenTree** lclUse; + GenTreeLclVarCommon* lcl; + + if (op1->OperIs(GT_LCL_VAR) && op2->IsIntegralConst()) + { + lclUse = &relop->AsOp()->gtOp1; + lcl = op1->AsLclVarCommon(); + } + else if (op2->OperIs(GT_LCL_VAR) && op1->IsIntegralConst()) + { + lclUse = &relop->AsOp()->gtOp2; + lcl = op2->AsLclVarCommon(); + } + else + { + return false; + } + + Statement* secondLastStmt = block->lastStmt()->GetPrevStmt(); + if ((secondLastStmt == nullptr) || (secondLastStmt == block->lastStmt())) + { + return false; + } + + GenTree* prevTree = secondLastStmt->GetRootNode(); + if (!prevTree->OperIs(GT_STORE_LCL_VAR)) + { + return false; + } + + GenTreeLclVarCommon* store = prevTree->AsLclVarCommon(); + if (store->GetLclNum() != lcl->GetLclNum()) + { + return false; + } + + if (!store->Data()->IsIntegralConst()) + { + return false; + } + + if (genActualType(store) != genActualType(store->Data()) || (genActualType(store) != genActualType(lcl))) + { + return false; + } + + JITDUMP("Forward substituting local after jump threading. Before:\n"); + DISPSTMT(block->lastStmt()); + + JITDUMP("\nAfter:\n"); + + LclVarDsc* varDsc = lvaGetDesc(lcl); + GenTree* newData = gtCloneExpr(store->Data()); + if (varTypeIsSmall(varDsc) && fgCastNeeded(store->Data(), varDsc->TypeGet())) + { + newData = gtNewCastNode(TYP_INT, newData, false, varDsc->TypeGet()); + newData = gtFoldExpr(newData); + } + + *lclUse = newData; + DISPSTMT(block->lastStmt()); + + JITDUMP("\nNow trying to fold...\n"); + jtrue->AsUnOp()->gtOp1 = gtFoldExpr(relop); + DISPSTMT(block->lastStmt()); + + Compiler::FoldResult result = fgFoldConditional(block); + if (result != Compiler::FoldResult::FOLD_DID_NOTHING) + { + assert(block->KindIs(BBJ_ALWAYS)); + return true; + } + + return false; +} + //------------------------------------------------------------- // fgRemoveConditionalJump: // Optimize a BBJ_COND block that unconditionally jumps to the same target @@ -5176,10 +5284,35 @@ bool Compiler::fgUpdateFlowGraph(bool doTailDuplication /* = false */, { assert(block->KindIs(BBJ_COND)); assert(bNext == block->Next()); - change = true; - modified = true; - bDest = block->GetTrueTarget(); - bFalseDest = block->GetFalseTarget(); + change = true; + modified = true; + + if (fgFoldSimpleCondByForwardSub(block)) + { + // It is likely another pred of the target now can + // similarly have its control flow straightened out. + // Try to compact it and repeat the optimization for + // it. + if (bDest->bbRefs == 1) + { + BasicBlock* otherPred = bDest->bbPreds->getSourceBlock(); + JITDUMP("Trying to compact last pred " FMT_BB " of " FMT_BB " that we now bypass\n", + otherPred->bbNum, bDest->bbNum); + if (fgCanCompactBlock(otherPred)) + { + fgCompactBlock(otherPred); + fgFoldSimpleCondByForwardSub(otherPred); + } + } + + assert(block->KindIs(BBJ_ALWAYS)); + bDest = block->GetTarget(); + } + else + { + bDest = block->GetTrueTarget(); + bFalseDest = block->GetFalseTarget(); + } } } diff --git a/src/coreclr/jit/switchrecognition.cpp b/src/coreclr/jit/switchrecognition.cpp index 747d046699340e..dcfb57cabf4e2a 100644 --- a/src/coreclr/jit/switchrecognition.cpp +++ b/src/coreclr/jit/switchrecognition.cpp @@ -86,7 +86,7 @@ bool IsConstantTestCondBlock(const BasicBlock* block, } // We're looking for "X EQ/NE CNS" or "CNS EQ/NE X" pattern - if (op1->IsCnsIntOrI() ^ op2->IsCnsIntOrI()) + if ((op1->IsCnsIntOrI() && !op1->IsIconHandle()) ^ (op2->IsCnsIntOrI() && !op2->IsIconHandle())) { // TODO: relax this to support any side-effect free expression if (!op1->OperIs(GT_LCL_VAR) && !op2->OperIs(GT_LCL_VAR))