diff --git a/src/coreclr/jit/fgopt.cpp b/src/coreclr/jit/fgopt.cpp index 59f326b0fc13a..6e4a62975d5e7 100644 --- a/src/coreclr/jit/fgopt.cpp +++ b/src/coreclr/jit/fgopt.cpp @@ -3220,7 +3220,9 @@ bool Compiler::fgBlockIsGoodTailDuplicationCandidate(BasicBlock* target, unsigne { *lclNum = BAD_VAR_NUM; - // Here we are looking for blocks with a single statement feeding a conditional branch. + // Here we are looking for small blocks where a local live-into the block + // ultimately feeds a simple conditional branch. + // // These blocks are small, and when duplicated onto the tail of blocks that end in // assignments, there is a high probability of the branch completely going away. // @@ -3237,22 +3239,29 @@ bool Compiler::fgBlockIsGoodTailDuplicationCandidate(BasicBlock* target, unsigne return false; } - Statement* stmt = target->FirstNonPhiDef(); + Statement* lastStmt = target->lastStmt(); + Statement* firstStmt = target->FirstNonPhiDef(); - if (stmt != target->lastStmt()) + // We currently allow just one statement aside from the branch. + // + if ((firstStmt != lastStmt) && (firstStmt->GetNextStmt() != lastStmt)) { return false; } - GenTree* tree = stmt->GetRootNode(); + JITDUMP("*** first stmt in " FMT_BB "\n", target->bbNum); + + // Verify the branch is just a simple local compare. + // + GenTree* const lastTree = lastStmt->GetRootNode(); - if (tree->gtOper != GT_JTRUE) + if (lastTree->gtOper != GT_JTRUE) { return false; } // must be some kind of relational operator - GenTree* const cond = tree->AsOp()->gtOp1; + GenTree* const cond = lastTree->AsOp()->gtOp1; if (!cond->OperIsCompare()) { return false; @@ -3314,6 +3323,112 @@ bool Compiler::fgBlockIsGoodTailDuplicationCandidate(BasicBlock* target, unsigne return false; } + // If there's no second statement, we're good. + // + if (lastStmt == firstStmt) + { + JITDUMP("--- yea\n"); + return true; + } + + JITDUMP("*** second stmt in " FMT_BB "\n", target->bbNum); + + // Otherwise check the first stmt. + // Verify the branch is just a simple local compare. + // + GenTree* const firstTree = firstStmt->GetRootNode(); + + if (firstTree->gtOper != GT_ASG) + { + JITDUMP("--- not asg\n"); + return false; + } + + GenTree* const lhs = firstTree->AsOp()->gtOp1; + if (!lhs->OperIs(GT_LCL_VAR)) + { + JITDUMP("--- not asg lcl\n"); + return false; + } + + const unsigned lhsLcl = lhs->AsLclVarCommon()->GetLclNum(); + if (lhsLcl != *lclNum) + { + JITDUMP("--- not asg V%02u = binop\n", *lclNum); + return false; + } + + // Could allow unary here too... + // + GenTree* const rhs = firstTree->AsOp()->gtOp2; + if (!rhs->OperIsBinary()) + { + JITDUMP("--- not asg lcl = binop\n"); + return false; + } + + // op1 must be some combinations of casts of local or constant + // (or unary) + op1 = rhs->AsOp()->gtOp1; + while (op1->gtOper == GT_CAST) + { + op1 = op1->AsOp()->gtOp1; + } + + if (!op1->IsLocal() && !op1->OperIsConst()) + { + JITDUMP("--- not asg lcl = binop(lcl/cns, ...)\n"); + return false; + } + + // op2 must be some combinations of casts of local or constant + // (or unary) + op2 = rhs->AsOp()->gtOp2; + while (op2->gtOper == GT_CAST) + { + op2 = op2->AsOp()->gtOp1; + } + + if (!op2->IsLocal() && !op2->OperIsConst()) + { + JITDUMP("--- not asg lcl = binop(lcl/cns, lcl/cns)\n"); + return false; + } + + // Tree must have one constant and one local, or be comparing + // the same local to itself. + lcl1 = BAD_VAR_NUM; + lcl2 = BAD_VAR_NUM; + + if (op1->IsLocal()) + { + lcl1 = op1->AsLclVarCommon()->GetLclNum(); + } + + if (op2->IsLocal()) + { + lcl2 = op2->AsLclVarCommon()->GetLclNum(); + } + + if ((lcl1 != BAD_VAR_NUM) && op2->OperIsConst()) + { + *lclNum = lcl1; + } + else if ((lcl2 != BAD_VAR_NUM) && op1->OperIsConst()) + { + *lclNum = lcl2; + } + else if ((lcl1 != BAD_VAR_NUM) && (lcl1 == lcl2)) + { + *lclNum = lcl1; + } + else + { + return false; + } + + JITDUMP(" -- yeah, new lcl is V%02u\n", *lclNum); + return true; } @@ -3333,6 +3448,8 @@ bool Compiler::fgBlockIsGoodTailDuplicationCandidate(BasicBlock* target, unsigne // bool Compiler::fgOptimizeUncondBranchToSimpleCond(BasicBlock* block, BasicBlock* target) { + JITDUMP("Considering uncond to cond " FMT_BB " -> " FMT_BB "\n", block->bbNum, target->bbNum); + if (!BasicBlock::sameEHRegion(block, target)) { return false; @@ -3360,23 +3477,35 @@ bool Compiler::fgOptimizeUncondBranchToSimpleCond(BasicBlock* block, BasicBlock* // backend always calls `fgUpdateFlowGraph` with `doTailDuplication` set to false. assert(!block->IsLIR()); - Statement* stmt = target->FirstNonPhiDef(); - assert(stmt == target->lastStmt()); - // Duplicate the target block at the end of this block - GenTree* cloned = gtCloneExpr(stmt->GetRootNode()); - noway_assert(cloned); - Statement* jmpStmt = gtNewStmt(cloned); + // + for (Statement* stmt : target->NonPhiStatements()) + { + GenTree* clone = gtCloneExpr(stmt->GetRootNode()); + noway_assert(clone); + Statement* cloneStmt = gtNewStmt(clone); + + if (fgStmtListThreaded) + { + gtSetStmtInfo(cloneStmt); + } + fgInsertStmtAtEnd(block, cloneStmt); + } + + // Fix up block's flow + // block->bbJumpKind = BBJ_COND; block->bbJumpDest = target->bbJumpDest; fgAddRefPred(block->bbJumpDest, block); fgRemoveRefPred(target, block); // add an unconditional block after this block to jump to the target block's fallthrough block + // BasicBlock* next = fgNewBBafter(BBJ_ALWAYS, block, true); // The new block 'next' will inherit its weight from 'block' + // next->inheritWeight(block); next->bbJumpDest = target->bbNext; fgAddRefPred(next, block); @@ -3384,15 +3513,7 @@ bool Compiler::fgOptimizeUncondBranchToSimpleCond(BasicBlock* block, BasicBlock* JITDUMP("fgOptimizeUncondBranchToSimpleCond(from " FMT_BB " to cond " FMT_BB "), created new uncond " FMT_BB "\n", block->bbNum, target->bbNum, next->bbNum); - JITDUMP(" expecting opts to key off V%02u, added cloned compare [%06u] to " FMT_BB "\n", lclNum, - dspTreeID(cloned), block->bbNum); - - if (fgStmtListThreaded) - { - gtSetStmtInfo(jmpStmt); - } - - fgInsertStmtAtEnd(block, jmpStmt); + JITDUMP(" expecting opts to key off V%02u in " FMT_BB "\n", lclNum, block->bbNum); return true; }