Skip to content

Commit 6268bb9

Browse files
authored
JIT: remove fallthrough limitations from redundant branch optimizer (#97722)
Now that `BBJ_COND` no longer has fallthrough semantics, remove a bunch of code that would limit redundant branch jump threading when one of the predecessors was fallthrough. Mitigate TP impact somewhat. Since jump threading is now more effective it can remove all preds of a BBJ_COND, and there's no point in retrying RBO from such blocks. Contributes to #93020.
1 parent cd5e3b0 commit 6268bb9

File tree

1 file changed

+19
-172
lines changed

1 file changed

+19
-172
lines changed

src/coreclr/jit/redundantbranchopts.cpp

Lines changed: 19 additions & 172 deletions
Original file line numberDiff line numberDiff line change
@@ -47,43 +47,43 @@ PhaseStatus Compiler::optRedundantBranches()
4747
{
4848
bool madeChangesThisBlock = m_compiler->optRedundantRelop(block);
4949

50-
BasicBlock* const bbNext = block->GetFalseTarget();
51-
BasicBlock* const bbJump = block->GetTrueTarget();
50+
BasicBlock* const bbFalse = block->GetFalseTarget();
51+
BasicBlock* const bbTrue = block->GetTrueTarget();
5252

5353
madeChangesThisBlock |= m_compiler->optRedundantBranch(block);
5454

55-
// If we modified some flow out of block but it's still
55+
// If we modified some flow out of block but it's still referenced and
5656
// a BBJ_COND, retry; perhaps one of the later optimizations
5757
// we can do has enabled one of the earlier optimizations.
5858
//
59-
if (madeChangesThisBlock && block->KindIs(BBJ_COND))
59+
if (madeChangesThisBlock && block->KindIs(BBJ_COND) && (block->countOfInEdges() > 0))
6060
{
6161
JITDUMP("Will retry RBO in " FMT_BB " after partial optimization\n", block->bbNum);
6262
madeChangesThisBlock |= m_compiler->optRedundantBranch(block);
6363
}
6464

65-
// It's possible that the changed flow into bbNext or bbJump may unblock
65+
// It's possible that the changed flow into bbFalse or bbTrue may unblock
6666
// further optimizations there.
6767
//
6868
// Note this misses cascading retries, consider reworking the overall
6969
// strategy here to iterate until closure.
7070
//
71-
if (madeChangesThisBlock && (bbNext->countOfInEdges() == 0))
71+
if (madeChangesThisBlock && (bbFalse->countOfInEdges() == 0))
7272
{
73-
for (BasicBlock* succ : bbNext->Succs())
73+
for (BasicBlock* succ : bbFalse->Succs())
7474
{
7575
JITDUMP("Will retry RBO in " FMT_BB "; pred " FMT_BB " now unreachable\n", succ->bbNum,
76-
bbNext->bbNum);
76+
bbFalse->bbNum);
7777
m_compiler->optRedundantBranch(succ);
7878
}
7979
}
8080

81-
if (madeChangesThisBlock && (bbJump->countOfInEdges() == 0))
81+
if (madeChangesThisBlock && (bbTrue->countOfInEdges() == 0))
8282
{
83-
for (BasicBlock* succ : bbJump->Succs())
83+
for (BasicBlock* succ : bbTrue->Succs())
8484
{
8585
JITDUMP("Will retry RBO in " FMT_BB "; pred " FMT_BB " now unreachable\n", succ->bbNum,
86-
bbNext->bbNum);
86+
bbFalse->bbNum);
8787
m_compiler->optRedundantBranch(succ);
8888
}
8989
}
@@ -828,21 +828,21 @@ bool Compiler::optRedundantBranch(BasicBlock* const block)
828828
}
829829
else if (trueReaches && !falseReaches && rii.canInferFromTrue)
830830
{
831-
// Taken jump in dominator reaches, fall through doesn't; relop must be true/false.
831+
// True path in dominator reaches, false path doesn't; relop must be true/false.
832832
//
833833
const bool relopIsTrue = rii.reverseSense ^ (domIsSameRelop | domIsInferredRelop);
834-
JITDUMP("Jump successor " FMT_BB " of " FMT_BB " reaches, relop [%06u] must be %s\n",
834+
JITDUMP("True successor " FMT_BB " of " FMT_BB " reaches, relop [%06u] must be %s\n",
835835
domBlock->GetTrueTarget()->bbNum, domBlock->bbNum, dspTreeID(tree),
836836
relopIsTrue ? "true" : "false");
837837
relopValue = relopIsTrue ? 1 : 0;
838838
break;
839839
}
840840
else if (falseReaches && !trueReaches && rii.canInferFromFalse)
841841
{
842-
// Fall through from dominator reaches, taken jump doesn't; relop must be false/true.
842+
// False path from dominator reaches, true path doesn't; relop must be false/true.
843843
//
844844
const bool relopIsFalse = rii.reverseSense ^ (domIsSameRelop | domIsInferredRelop);
845-
JITDUMP("Fall through successor " FMT_BB " of " FMT_BB " reaches, relop [%06u] must be %s\n",
845+
JITDUMP("False successor " FMT_BB " of " FMT_BB " reaches, relop [%06u] must be %s\n",
846846
domBlock->GetFalseTarget()->bbNum, domBlock->bbNum, dspTreeID(tree),
847847
relopIsFalse ? "false" : "true");
848848
relopValue = relopIsFalse ? 0 : 1;
@@ -942,7 +942,6 @@ struct JumpThreadInfo
942942
: m_block(block)
943943
, m_trueTarget(block->GetTrueTarget())
944944
, m_falseTarget(block->GetFalseTarget())
945-
, m_fallThroughPred(nullptr)
946945
, m_ambiguousVNBlock(nullptr)
947946
, m_truePreds(BlockSetOps::MakeEmpty(comp))
948947
, m_ambiguousPreds(BlockSetOps::MakeEmpty(comp))
@@ -961,8 +960,6 @@ struct JumpThreadInfo
961960
BasicBlock* const m_trueTarget;
962961
// Block successor if predicate is false
963962
BasicBlock* const m_falseTarget;
964-
// Unique pred that falls through to block, if any
965-
BasicBlock* m_fallThroughPred;
966963
// Block that brings in the ambiguous VN
967964
BasicBlock* m_ambiguousVNBlock;
968965
// Pred blocks for which the predicate will be true
@@ -1239,35 +1236,9 @@ bool Compiler::optJumpThreadDom(BasicBlock* const block, BasicBlock* const domBl
12391236
// * It's also possible that the pred is a switch; we will treat switch
12401237
// preds as ambiguous as well.
12411238
//
1242-
// * We note if there is an un-ambiguous pred that falls through to block.
1243-
// This is the "fall through pred", and the (true/false) pred set it belongs to
1244-
// is the "fall through set".
1245-
//
1246-
// Now for some case analysis.
1247-
//
1248-
// (1) If we have both an ambiguous pred and a fall through pred, we treat
1249-
// the fall through pred as an ambiguous pred (we can't reroute its flow to
1250-
// avoid block, and we need to keep block intact), and jump thread the other
1251-
// preds per (2) below.
1252-
//
1253-
// (2) If we have an ambiguous pred and no fall through, we reroute the true and
1254-
// false preds to branch to the true and false successors, respectively.
1255-
//
1256-
// (3) If we don't have an ambiguous pred and don't have a fall through pred,
1257-
// we choose one of the pred sets to be treated as if it was the fall through set.
1258-
// For now the choice is arbitrary, so we chose the true preds, and proceed
1259-
// per (4) below.
1260-
//
1261-
// (4) If we don't have an ambiguous pred, and we have a fall through, we leave
1262-
// all preds in the fall through set alone -- they continue branching to block.
1263-
// We modify block to branch to the appropriate successor for the fall through set.
1264-
// Note block will be empty other than phis and the branch, so this is ok.
1265-
// The preds in the other set target the other successor.
1266-
//
1267-
// The goal of the above is to maximize the number of cases where we jump thread,
1268-
// and to maximize the number of jump threads that reuse the original block. This
1269-
// latter should prove useful in subsequent work, where we aim to enable jump
1270-
// threading in cases where block has side effects.
1239+
// If there are ambiguous preds they will continue to flow into the
1240+
// unaltered block, while true and false preds will flow to the appropriate
1241+
// successors directly.
12711242
//
12721243
BasicBlock* const domTrueSuccessor = domIsSameRelop ? domBlock->GetTrueTarget() : domBlock->GetFalseTarget();
12731244
BasicBlock* const domFalseSuccessor = domIsSameRelop ? domBlock->GetFalseTarget() : domBlock->GetTrueTarget();
@@ -1337,15 +1308,6 @@ bool Compiler::optJumpThreadDom(BasicBlock* const block, BasicBlock* const domBl
13371308
jti.m_numFalsePreds++;
13381309
JITDUMP(FMT_BB " is a false pred\n", predBlock->bbNum);
13391310
}
1340-
1341-
// Note if the true or false pred is the fall through pred.
1342-
//
1343-
if (predBlock->NextIs(block))
1344-
{
1345-
JITDUMP(FMT_BB " is the fall-through pred\n", predBlock->bbNum);
1346-
assert(jti.m_fallThroughPred == nullptr);
1347-
jti.m_fallThroughPred = predBlock;
1348-
}
13491311
}
13501312

13511313
// Do the optimization.
@@ -1597,15 +1559,6 @@ bool Compiler::optJumpThreadPhi(BasicBlock* block, GenTree* tree, ValueNum treeN
15971559

15981560
continue;
15991561
}
1600-
1601-
// Note if the true or false pred is the fall through pred.
1602-
//
1603-
if (predBlock->NextIs(block))
1604-
{
1605-
JITDUMP(FMT_BB " is the fall-through pred\n", predBlock->bbNum);
1606-
assert(jti.m_fallThroughPred == nullptr);
1607-
jti.m_fallThroughPred = predBlock;
1608-
}
16091562
}
16101563

16111564
// Do the optimization.
@@ -1638,102 +1591,10 @@ bool Compiler::optJumpThreadCore(JumpThreadInfo& jti)
16381591
return false;
16391592
}
16401593

1641-
if ((jti.m_numAmbiguousPreds > 0) && (jti.m_fallThroughPred != nullptr))
1642-
{
1643-
// TODO: Simplify jti.m_fallThroughPred logic, now that implicit fallthrough is disallowed.
1644-
const bool fallThroughIsTruePred = BlockSetOps::IsMember(this, jti.m_truePreds, jti.m_fallThroughPred->bbNum);
1645-
const bool predJumpsToNext = jti.m_fallThroughPred->KindIs(BBJ_ALWAYS) && jti.m_fallThroughPred->JumpsToNext();
1646-
1647-
if (predJumpsToNext && ((fallThroughIsTruePred && (jti.m_numFalsePreds == 0)) ||
1648-
(!fallThroughIsTruePred && (jti.m_numTruePreds == 0))))
1649-
{
1650-
JITDUMP(FMT_BB " has ambiguous preds and a (%s) fall through pred and no (%s) preds.\n"
1651-
"Fall through pred " FMT_BB " is BBJ_ALWAYS\n",
1652-
jti.m_block->bbNum, fallThroughIsTruePred ? "true" : "false",
1653-
fallThroughIsTruePred ? "false" : "true", jti.m_fallThroughPred->bbNum);
1654-
1655-
assert(jti.m_fallThroughPred->TargetIs(jti.m_block));
1656-
}
1657-
else
1658-
{
1659-
// Treat the fall through pred as an ambiguous pred.
1660-
JITDUMP(FMT_BB " has both ambiguous preds and a fall through pred\n", jti.m_block->bbNum);
1661-
JITDUMP("Treating fall through pred " FMT_BB " as an ambiguous pred\n", jti.m_fallThroughPred->bbNum);
1662-
1663-
if (fallThroughIsTruePred)
1664-
{
1665-
BlockSetOps::RemoveElemD(this, jti.m_truePreds, jti.m_fallThroughPred->bbNum);
1666-
assert(jti.m_numTruePreds > 0);
1667-
jti.m_numTruePreds--;
1668-
}
1669-
else
1670-
{
1671-
assert(jti.m_numFalsePreds > 0);
1672-
jti.m_numFalsePreds--;
1673-
}
1674-
1675-
assert(!(BlockSetOps::IsMember(this, jti.m_ambiguousPreds, jti.m_fallThroughPred->bbNum)));
1676-
BlockSetOps::AddElemD(this, jti.m_ambiguousPreds, jti.m_fallThroughPred->bbNum);
1677-
jti.m_numAmbiguousPreds++;
1678-
}
1679-
1680-
jti.m_fallThroughPred = nullptr;
1681-
}
1682-
1683-
// There still should be at least one pred that can bypass block.
1684-
//
1685-
if ((jti.m_numTruePreds == 0) && (jti.m_numFalsePreds == 0))
1686-
{
1687-
// This is possible, but also should be rare.
1688-
//
1689-
JITDUMP(FMT_BB " now only has ambiguous preds, not jump threading\n", jti.m_block->bbNum);
1690-
return false;
1691-
}
1692-
1693-
// Determine if either set of preds will route via block.
1694-
//
1695-
bool truePredsWillReuseBlock = false;
1696-
bool falsePredsWillReuseBlock = false;
1697-
1698-
if (jti.m_fallThroughPred != nullptr)
1699-
{
1700-
assert(jti.m_numAmbiguousPreds == 0);
1701-
truePredsWillReuseBlock = BlockSetOps::IsMember(this, jti.m_truePreds, jti.m_fallThroughPred->bbNum);
1702-
falsePredsWillReuseBlock = !truePredsWillReuseBlock;
1703-
}
1704-
else if (jti.m_numAmbiguousPreds == 0)
1705-
{
1706-
truePredsWillReuseBlock = true;
1707-
falsePredsWillReuseBlock = !truePredsWillReuseBlock;
1708-
}
1709-
1710-
assert(!(truePredsWillReuseBlock && falsePredsWillReuseBlock));
1711-
17121594
// We should be good to go
17131595
//
17141596
JITDUMP("Optimizing via jump threading\n");
17151597

1716-
// Fix block, if we're reusing it.
1717-
//
1718-
if (truePredsWillReuseBlock)
1719-
{
1720-
Statement* const lastStmt = jti.m_block->lastStmt();
1721-
fgRemoveStmt(jti.m_block, lastStmt);
1722-
JITDUMP(" repurposing " FMT_BB " to always jump to " FMT_BB "\n", jti.m_block->bbNum, jti.m_trueTarget->bbNum);
1723-
fgRemoveRefPred(jti.m_falseTarget, jti.m_block);
1724-
jti.m_block->SetKind(BBJ_ALWAYS);
1725-
}
1726-
else if (falsePredsWillReuseBlock)
1727-
{
1728-
Statement* const lastStmt = jti.m_block->lastStmt();
1729-
fgRemoveStmt(jti.m_block, lastStmt);
1730-
JITDUMP(" repurposing " FMT_BB " to always jump to " FMT_BB "\n", jti.m_block->bbNum,
1731-
jti.m_falseTarget->bbNum);
1732-
fgRemoveRefPred(jti.m_trueTarget, jti.m_block);
1733-
jti.m_block->SetKindAndTarget(BBJ_ALWAYS, jti.m_falseTarget);
1734-
jti.m_block->SetFlags(BBF_NONE_QUIRK);
1735-
}
1736-
17371598
// Now reroute the flow from the predecessors.
17381599
// If this pred is in the set that will reuse block, do nothing.
17391600
// Else revise pred to branch directly to the appropriate successor of block.
@@ -1749,22 +1610,8 @@ bool Compiler::optJumpThreadCore(JumpThreadInfo& jti)
17491610

17501611
const bool isTruePred = BlockSetOps::IsMember(this, jti.m_truePreds, predBlock->bbNum);
17511612

1752-
// Do we need to alter flow from this pred?
1613+
// Jump to the appropriate successor.
17531614
//
1754-
if ((isTruePred && truePredsWillReuseBlock) || (!isTruePred && falsePredsWillReuseBlock))
1755-
{
1756-
// No, we can leave as is.
1757-
//
1758-
JITDUMP("%s pred " FMT_BB " will continue to target " FMT_BB "\n", isTruePred ? "true" : "false",
1759-
predBlock->bbNum, jti.m_block->bbNum);
1760-
continue;
1761-
}
1762-
1763-
// Yes, we need to jump to the appropriate successor.
1764-
// Note we should not be altering flow for the fall-through pred.
1765-
//
1766-
assert(predBlock != jti.m_fallThroughPred);
1767-
17681615
if (isTruePred)
17691616
{
17701617
JITDUMP("Jump flow from pred " FMT_BB " -> " FMT_BB

0 commit comments

Comments
 (0)