Skip to content

Commit 6e88cf8

Browse files
JIT: Move targets of hot jumps to create fallthrough post-RPO layout (#102927)
After establishing an RPO-based layout, we currently try to move any backward jumps up to their successors, if it is profitable to do so in spite of any fallthrough behavior lost. In #102763, we see many instances where the RPO layout fails to create fallthrough for forward jumps on the hot path, such as in cases where a block is reachable from many predecessors. This work addresses the RPO's limitations by also considering moving the targets of forward jumps (conditional and unconditional) to maximize fallthrough.
1 parent 44c519e commit 6e88cf8

File tree

2 files changed

+133
-54
lines changed

2 files changed

+133
-54
lines changed

src/coreclr/jit/compiler.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6146,7 +6146,7 @@ class Compiler
61466146
void fgMoveColdBlocks();
61476147

61486148
template <bool hasEH>
6149-
void fgMoveBackwardJumpsToSuccessors();
6149+
void fgMoveHotJumps();
61506150

61516151
bool fgFuncletsAreCold();
61526152

src/coreclr/jit/fgopt.cpp

Lines changed: 132 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -4538,114 +4538,198 @@ bool Compiler::fgReorderBlocks(bool useProfile)
45384538
#endif
45394539

45404540
//-----------------------------------------------------------------------------
4541-
// fgMoveBackwardJumpsToSuccessors: Try to move backward unconditional jumps to fall into their successors.
4541+
// fgMoveHotJumps: Try to move jumps to fall into their successors, if the jump is sufficiently hot.
45424542
//
45434543
// Template parameters:
45444544
// hasEH - If true, method has EH regions, so check that we don't try to move blocks in different regions
45454545
//
45464546
template <bool hasEH>
4547-
void Compiler::fgMoveBackwardJumpsToSuccessors()
4547+
void Compiler::fgMoveHotJumps()
45484548
{
45494549
#ifdef DEBUG
45504550
if (verbose)
45514551
{
4552-
printf("*************** In fgMoveBackwardJumpsToSuccessors()\n");
4552+
printf("*************** In fgMoveHotJumps()\n");
45534553

45544554
printf("\nInitial BasicBlocks");
45554555
fgDispBasicBlocks(verboseTrees);
45564556
printf("\n");
45574557
}
45584558
#endif // DEBUG
45594559

4560-
// Compact blocks before trying to move any jumps.
4561-
// This can unlock more opportunities for fallthrough behavior.
4560+
EnsureBasicBlockEpoch();
4561+
BlockSet visitedBlocks(BlockSetOps::MakeEmpty(this));
4562+
BlockSetOps::AddElemD(this, visitedBlocks, fgFirstBB->bbNum);
4563+
4564+
// If we have a funclet region, don't bother reordering anything in it.
45624565
//
4563-
for (BasicBlock* block = fgFirstBB; block != fgFirstFuncletBB;)
4566+
BasicBlock* next;
4567+
for (BasicBlock* block = fgFirstBB; block != fgFirstFuncletBB; block = next)
45644568
{
45654569
if (fgCanCompactBlocks(block, block->Next()))
45664570
{
4567-
// We haven't fixed EH information yet, so don't do any correctness checks here
4571+
// Compact blocks before trying to move any jumps.
4572+
// This can unlock more opportunities for fallthrough behavior.
4573+
// We haven't fixed EH information yet, so don't do any correctness checks here.
45684574
//
45694575
fgCompactBlocks(block, block->Next() DEBUGARG(/* doDebugCheck */ false));
4576+
next = block;
4577+
continue;
45704578
}
4571-
else
4572-
{
4573-
block = block->Next();
4574-
}
4575-
}
45764579

4577-
EnsureBasicBlockEpoch();
4578-
BlockSet visitedBlocks(BlockSetOps::MakeEmpty(this));
4579-
BlockSetOps::AddElemD(this, visitedBlocks, fgFirstBB->bbNum);
4580-
4581-
// Don't try to move the first block.
4582-
// Also, if we have a funclet region, don't bother reordering anything in it.
4583-
//
4584-
BasicBlock* next;
4585-
for (BasicBlock* block = fgFirstBB->Next(); block != fgFirstFuncletBB; block = next)
4586-
{
45874580
next = block->Next();
45884581
BlockSetOps::AddElemD(this, visitedBlocks, block->bbNum);
45894582

45904583
// Don't bother trying to move cold blocks
45914584
//
4592-
if (!block->KindIs(BBJ_ALWAYS) || block->isRunRarely())
4585+
if (block->isBBWeightCold(this))
45934586
{
45944587
continue;
45954588
}
45964589

4597-
// We will consider moving only backward jumps
4598-
//
4599-
BasicBlock* const target = block->GetTarget();
4600-
if ((block == target) || !BlockSetOps::IsMember(this, visitedBlocks, target->bbNum))
4590+
FlowEdge* targetEdge;
4591+
FlowEdge* unlikelyEdge;
4592+
4593+
if (block->KindIs(BBJ_ALWAYS))
4594+
{
4595+
targetEdge = block->GetTargetEdge();
4596+
unlikelyEdge = nullptr;
4597+
}
4598+
else if (block->KindIs(BBJ_COND))
46014599
{
4600+
// Consider conditional block's most likely branch for moving
4601+
//
4602+
if (block->GetTrueEdge()->getLikelihood() > 0.5)
4603+
{
4604+
targetEdge = block->GetTrueEdge();
4605+
unlikelyEdge = block->GetFalseEdge();
4606+
}
4607+
else
4608+
{
4609+
targetEdge = block->GetFalseEdge();
4610+
unlikelyEdge = block->GetTrueEdge();
4611+
}
4612+
}
4613+
else
4614+
{
4615+
// Don't consider other block kinds
4616+
//
46024617
continue;
46034618
}
46044619

4605-
if (hasEH)
4620+
BasicBlock* target = targetEdge->getDestinationBlock();
4621+
bool isBackwardJump = BlockSetOps::IsMember(this, visitedBlocks, target->bbNum);
4622+
4623+
if (isBackwardJump)
46064624
{
4607-
// Don't move blocks in different EH regions
4625+
// We don't want to change the first block, so if block is a backward jump to the first block,
4626+
// don't try moving block before it.
46084627
//
4609-
if (!BasicBlock::sameEHRegion(block, target))
4628+
if (target->IsFirst())
46104629
{
46114630
continue;
46124631
}
46134632

4614-
// block and target are in the same try/handler regions, and target is behind block,
4615-
// so block cannot possibly be the start of the region.
4616-
//
4617-
assert(!bbIsTryBeg(block) && !bbIsHandlerBeg(block));
4633+
if (block->KindIs(BBJ_COND))
4634+
{
4635+
// This could be a loop exit, so don't bother moving this block up.
4636+
// Instead, try moving the unlikely target up to create fallthrough.
4637+
//
4638+
targetEdge = unlikelyEdge;
4639+
target = targetEdge->getDestinationBlock();
4640+
isBackwardJump = BlockSetOps::IsMember(this, visitedBlocks, target->bbNum);
46184641

4619-
// Don't change the entry block of an EH region
4642+
if (isBackwardJump)
4643+
{
4644+
continue;
4645+
}
4646+
}
4647+
// Check for single-block loop case
46204648
//
4621-
if (bbIsTryBeg(target) || bbIsHandlerBeg(target))
4649+
else if (block == target)
46224650
{
46234651
continue;
46244652
}
46254653
}
46264654

4627-
// We don't want to change the first block, so if the jump target is the first block,
4628-
// don't try moving this block before it.
4629-
// Also, if the target is cold, don't bother moving this block up to it.
4655+
// Check if block already falls into target
46304656
//
4631-
if (target->IsFirst() || target->isRunRarely())
4657+
if (block->NextIs(target))
46324658
{
46334659
continue;
46344660
}
46354661

4662+
if (target->isBBWeightCold(this))
4663+
{
4664+
// If target is block's most-likely successor, and block is not rarely-run,
4665+
// perhaps the profile data is misleading, and we need to run profile repair?
4666+
//
4667+
continue;
4668+
}
4669+
4670+
if (hasEH)
4671+
{
4672+
// Don't move blocks in different EH regions
4673+
//
4674+
if (!BasicBlock::sameEHRegion(block, target))
4675+
{
4676+
continue;
4677+
}
4678+
4679+
if (isBackwardJump)
4680+
{
4681+
// block and target are in the same try/handler regions, and target is behind block,
4682+
// so block cannot possibly be the start of the region.
4683+
//
4684+
assert(!bbIsTryBeg(block) && !bbIsHandlerBeg(block));
4685+
4686+
// Don't change the entry block of an EH region
4687+
//
4688+
if (bbIsTryBeg(target) || bbIsHandlerBeg(target))
4689+
{
4690+
continue;
4691+
}
4692+
}
4693+
else
4694+
{
4695+
// block and target are in the same try/handler regions, and block is behind target,
4696+
// so target cannot possibly be the start of the region.
4697+
//
4698+
assert(!bbIsTryBeg(target) && !bbIsHandlerBeg(target));
4699+
}
4700+
}
4701+
46364702
// If moving block will break up existing fallthrough behavior into target, make sure it's worth it
46374703
//
46384704
FlowEdge* const fallthroughEdge = fgGetPredForBlock(target, target->Prev());
4639-
if ((fallthroughEdge != nullptr) &&
4640-
(fallthroughEdge->getLikelyWeight() >= block->GetTargetEdge()->getLikelyWeight()))
4705+
if ((fallthroughEdge != nullptr) && (fallthroughEdge->getLikelyWeight() >= targetEdge->getLikelyWeight()))
46414706
{
46424707
continue;
46434708
}
46444709

4645-
// Move block to before target
4646-
//
4647-
fgUnlinkBlock(block);
4648-
fgInsertBBbefore(target, block);
4710+
if (isBackwardJump)
4711+
{
4712+
// Move block to before target
4713+
//
4714+
fgUnlinkBlock(block);
4715+
fgInsertBBbefore(target, block);
4716+
}
4717+
else if (hasEH && target->isBBCallFinallyPair())
4718+
{
4719+
// target is a call-finally pair, so move the pair up to block
4720+
//
4721+
fgUnlinkRange(target, target->Next());
4722+
fgMoveBlocksAfter(target, target->Next(), block);
4723+
next = target->Next();
4724+
}
4725+
else
4726+
{
4727+
// Move target up to block
4728+
//
4729+
fgUnlinkBlock(target);
4730+
fgInsertBBafter(block, target);
4731+
next = target;
4732+
}
46494733
}
46504734
}
46514735

@@ -4681,12 +4765,7 @@ void Compiler::fgDoReversePostOrderLayout()
46814765
fgInsertBBafter(block, blockToMove);
46824766
}
46834767

4684-
// The RPO established a good base layout, but in some cases, it might produce a subpar layout for loops.
4685-
// In particular, it may place the loop head after the loop exit, creating unnecessary branches.
4686-
// Fix this by moving unconditional backward jumps up to their targets,
4687-
// increasing the likelihood that the loop exit block is the last block in the loop.
4688-
//
4689-
fgMoveBackwardJumpsToSuccessors</* hasEH */ false>();
4768+
fgMoveHotJumps</* hasEH */ false>();
46904769

46914770
return;
46924771
}
@@ -4775,7 +4854,7 @@ void Compiler::fgDoReversePostOrderLayout()
47754854
fgInsertBBafter(pair.callFinally, pair.callFinallyRet);
47764855
}
47774856

4778-
fgMoveBackwardJumpsToSuccessors</* hasEH */ true>();
4857+
fgMoveHotJumps</* hasEH */ true>();
47794858

47804859
// The RPO won't change the entry blocks of any EH regions, but reordering can change the last block in a region
47814860
// (for example, by pushing throw blocks unreachable via normal flow to the end of the region).

0 commit comments

Comments
 (0)