Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 0fae96e

Browse files
authored
JIT: Finally chain merging (#8810)
Add an optimization that performs a specialized tail merge for chains of callfinallys. These can arise from try-finallys where there are multiple exit points from the try that have the same continuation. When the jit generates a callfinally, it does so without considering whether some previously generated callfinally might invoke the same handler and then continue on to the same continuation. This optimization looks for callfinallys that invoke the same handler and have the same continuation and effectively merges them into one canonical callfinally. This optimization is done bottom-up (from outermost handler invocation to innermost) to allow the merging done at an outer levels to enable merging at inner levels. This optimization saves code size and also results in more compact EH reporting, since the callfinallies are reported to the runtime as duplicate EH regions. When run upstream of finally cloning, finally chain merging also allows more of the paths exiting the try to exit via the clone. Also fix an issue in block ref count maintenance during empty try removal that caused an assert during finally chain merging.
1 parent 995b869 commit 0fae96e

File tree

4 files changed

+302
-1
lines changed

4 files changed

+302
-1
lines changed

src/jit/compiler.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3562,12 +3562,18 @@ class Compiler
35623562

35633563
void fgRemoveEmptyFinally();
35643564

3565+
void fgMergeFinallyChains();
3566+
35653567
void fgCloneFinally();
35663568

35673569
void fgCleanupContinuation(BasicBlock* continuation);
35683570

35693571
void fgUpdateFinallyTargetFlags();
35703572

3573+
bool fgRetargetBranchesToCanonicalCallFinally(BasicBlock* block,
3574+
BasicBlock* handler,
3575+
BlockToBlockMap& continuationMap);
3576+
35713577
GenTreePtr fgGetCritSectOfStaticMethod();
35723578

35733579
#if !defined(_TARGET_X86_)

src/jit/compphases.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ CompPhaseNameMacro(PHASE_MORPH_INLINE, "Morph - Inlining",
3030
CompPhaseNameMacro(PHASE_MORPH_IMPBYREF, "Morph - ByRefs", "MOR-BYREF",false, -1, false)
3131
CompPhaseNameMacro(PHASE_EMPTY_TRY, "Remove empty try", "EMPTYTRY", false, -1, false)
3232
CompPhaseNameMacro(PHASE_EMPTY_FINALLY, "Remove empty finally", "EMPTYFIN", false, -1, false)
33+
CompPhaseNameMacro(PHASE_MERGE_FINALLY_CHAINS, "Merge callfinally chains", "MRGCFCHN", false, -1, false)
3334
CompPhaseNameMacro(PHASE_CLONE_FINALLY, "Clone finally", "CLONEFIN", false, -1, false)
3435
CompPhaseNameMacro(PHASE_STR_ADRLCL, "Morph - Structs/AddrExp", "MOR-STRAL",false, -1, false)
3536
CompPhaseNameMacro(PHASE_MORPH_GLOBAL, "Morph - Global", "MOR-GLOB", false, -1, false)

src/jit/flowgraph.cpp

Lines changed: 291 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22539,6 +22539,14 @@ void Compiler::fgRemoveEmptyFinally()
2253922539
{
2254022540
JITDUMP("\n*************** In fgRemoveEmptyFinally()\n");
2254122541

22542+
#if FEATURE_EH_FUNCLETS
22543+
// We need to do this transformation before funclets are created.
22544+
assert(!fgFuncletsCreated);
22545+
#endif // FEATURE_EH_FUNCLETS
22546+
22547+
// Assume we don't need to update the bbPreds lists.
22548+
assert(!fgComputePredsDone);
22549+
2254222550
if (compHndBBtabCount == 0)
2254322551
{
2254422552
JITDUMP("No EH in this method, nothing to remove.\n");
@@ -22768,6 +22776,14 @@ void Compiler::fgRemoveEmptyTry()
2276822776
{
2276922777
JITDUMP("\n*************** In fgRemoveEmptyTry()\n");
2277022778

22779+
#if FEATURE_EH_FUNCLETS
22780+
// We need to do this transformation before funclets are created.
22781+
assert(!fgFuncletsCreated);
22782+
#endif // FEATURE_EH_FUNCLETS
22783+
22784+
// Assume we don't need to update the bbPreds lists.
22785+
assert(!fgComputePredsDone);
22786+
2277122787
#ifdef FEATURE_CORECLR
2277222788
bool enableRemoveEmptyTry = true;
2277322789
#else
@@ -23022,6 +23038,7 @@ void Compiler::fgRemoveEmptyTry()
2302223038
fgRemoveStmt(block, finallyRet);
2302323039
block->bbJumpKind = BBJ_ALWAYS;
2302423040
block->bbJumpDest = continuation;
23041+
fgAddRefPred(continuation, block);
2302523042
}
2302623043
}
2302723044
}
@@ -23087,6 +23104,14 @@ void Compiler::fgCloneFinally()
2308723104
{
2308823105
JITDUMP("\n*************** In fgCloneFinally()\n");
2308923106

23107+
#if FEATURE_EH_FUNCLETS
23108+
// We need to do this transformation before funclets are created.
23109+
assert(!fgFuncletsCreated);
23110+
#endif // FEATURE_EH_FUNCLETS
23111+
23112+
// Assume we don't need to update the bbPreds lists.
23113+
assert(!fgComputePredsDone);
23114+
2309023115
#ifdef FEATURE_CORECLR
2309123116
bool enableCloning = true;
2309223117
#else
@@ -23591,7 +23616,7 @@ void Compiler::fgCloneFinally()
2359123616
BasicBlock* firstClonedBlock = blockMap[firstBlock];
2359223617
firstClonedBlock->bbCatchTyp = BBCT_NONE;
2359323618

23594-
// Cleanup the contination
23619+
// Cleanup the continuation
2359523620
fgCleanupContinuation(normalCallFinallyReturn);
2359623621

2359723622
// Todo -- mark cloned blocks as a cloned finally....
@@ -23900,6 +23925,271 @@ void Compiler::fgUpdateFinallyTargetFlags()
2390023925
#endif // FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_)
2390123926
}
2390223927

23928+
//------------------------------------------------------------------------
23929+
// fgMergeFinallyChains: tail merge finally invocations
23930+
//
23931+
// Notes:
23932+
//
23933+
// Looks for common suffixes in chains of finally invocations
23934+
// (callfinallys) and merges them. These typically arise from
23935+
// try-finallys where there are multiple exit points in the try
23936+
// that have the same target.
23937+
23938+
void Compiler::fgMergeFinallyChains()
23939+
{
23940+
JITDUMP("\n*************** In fgMergeFinallyChains()\n");
23941+
23942+
#if FEATURE_EH_FUNCLETS
23943+
// We need to do this transformation before funclets are created.
23944+
assert(!fgFuncletsCreated);
23945+
#endif // FEATURE_EH_FUNCLETS
23946+
23947+
// Assume we don't need to update the bbPreds lists.
23948+
assert(!fgComputePredsDone);
23949+
23950+
if (compHndBBtabCount == 0)
23951+
{
23952+
JITDUMP("No EH in this method, nothing to merge.\n");
23953+
return;
23954+
}
23955+
23956+
if (opts.MinOpts())
23957+
{
23958+
JITDUMP("Method compiled with minOpts, no merging.\n");
23959+
return;
23960+
}
23961+
23962+
if (opts.compDbgCode)
23963+
{
23964+
JITDUMP("Method compiled with debug codegen, no merging.\n");
23965+
return;
23966+
}
23967+
23968+
#if !FEATURE_EH_FUNCLETS
23969+
// For non-funclet models (x86) the callfinallys may contain
23970+
// statements and the continuations contain GT_END_LFINs. So no
23971+
// merging is possible until the GT_END_LFIN blocks can be merged
23972+
// and merging is not safe unless the callfinally blocks are split.
23973+
JITDUMP("EH using non-funclet model; merging not yet implemented.\n");
23974+
return;
23975+
#endif // !FEATURE_EH_FUNCLETS
23976+
23977+
#if !FEATURE_EH_CALLFINALLY_THUNKS
23978+
// For non-thunk EH models (arm32) the callfinallys may contain
23979+
// statements, and merging is not safe unless the callfinally
23980+
// blocks are split.
23981+
JITDUMP("EH using non-callfinally thunk model; merging not yet implemented.\n");
23982+
return;
23983+
#endif
23984+
23985+
#ifdef DEBUG
23986+
if (verbose)
23987+
{
23988+
printf("\n*************** Before fgMergeFinallyChains()\n");
23989+
fgDispBasicBlocks();
23990+
fgDispHandlerTab();
23991+
printf("\n");
23992+
}
23993+
#endif // DEBUG
23994+
23995+
// Look for finallys.
23996+
bool hasFinally = false;
23997+
for (unsigned XTnum = 0; XTnum < compHndBBtabCount; XTnum++)
23998+
{
23999+
EHblkDsc* const HBtab = &compHndBBtab[XTnum];
24000+
24001+
// Check if this is a try/finally.
24002+
if (HBtab->HasFinallyHandler())
24003+
{
24004+
hasFinally = true;
24005+
break;
24006+
}
24007+
}
24008+
24009+
if (!hasFinally)
24010+
{
24011+
JITDUMP("Method does not have any try-finallys; no merging.\n");
24012+
return;
24013+
}
24014+
24015+
// Process finallys from outside in, merging as we go. This gives
24016+
// us the desired bottom-up tail merge order for callfinally
24017+
// chains: outer merges may enable inner merges.
24018+
bool canMerge = false;
24019+
bool didMerge = false;
24020+
BlockToBlockMap continuationMap(getAllocator());
24021+
24022+
// Note XTnum is signed here so we can count down.
24023+
for (int XTnum = compHndBBtabCount - 1; XTnum >= 0; XTnum--)
24024+
{
24025+
EHblkDsc* const HBtab = &compHndBBtab[XTnum];
24026+
24027+
// Screen out non-finallys
24028+
if (!HBtab->HasFinallyHandler())
24029+
{
24030+
continue;
24031+
}
24032+
24033+
JITDUMP("Examining callfinallys for EH#%d.\n", XTnum);
24034+
24035+
// Find all the callfinallys that invoke this finally.
24036+
BasicBlock* firstCallFinallyRangeBlock = nullptr;
24037+
BasicBlock* endCallFinallyRangeBlock = nullptr;
24038+
ehGetCallFinallyBlockRange(XTnum, &firstCallFinallyRangeBlock, &endCallFinallyRangeBlock);
24039+
24040+
// Clear out any stale entries in the continuation map
24041+
continuationMap.RemoveAll();
24042+
24043+
// Build a map from each continuation to the "canonical"
24044+
// callfinally for that continuation.
24045+
unsigned callFinallyCount = 0;
24046+
BasicBlock* const beginHandlerBlock = HBtab->ebdHndBeg;
24047+
24048+
for (BasicBlock* currentBlock = firstCallFinallyRangeBlock; currentBlock != endCallFinallyRangeBlock;
24049+
currentBlock = currentBlock->bbNext)
24050+
{
24051+
// Ignore "retless" callfinallys (where the finally doesn't return).
24052+
if (currentBlock->isBBCallAlwaysPair() && (currentBlock->bbJumpDest == beginHandlerBlock))
24053+
{
24054+
// The callfinally must be empty, so that we can
24055+
// safely retarget anything that branches here to
24056+
// another callfinally with the same contiuation.
24057+
assert(currentBlock->isEmpty());
24058+
24059+
// This callfinally invokes the finally for this try.
24060+
callFinallyCount++;
24061+
24062+
// Locate the continuation
24063+
BasicBlock* const leaveBlock = currentBlock->bbNext;
24064+
BasicBlock* const continuationBlock = leaveBlock->bbJumpDest;
24065+
24066+
// If this is the first time we've seen this
24067+
// continuation, register this callfinally as the
24068+
// canonical one.
24069+
if (!continuationMap.Lookup(continuationBlock))
24070+
{
24071+
continuationMap.Set(continuationBlock, currentBlock);
24072+
}
24073+
}
24074+
}
24075+
24076+
// Now we've seen all the callfinallys and their continuations.
24077+
JITDUMP("EH#%i has %u callfinallys, %u continuations\n", XTnum, callFinallyCount, continuationMap.GetCount());
24078+
24079+
// If there are more callfinallys than continuations, some of the
24080+
// callfinallys must share a continuation, and we can merge them.
24081+
const bool tryMerge = callFinallyCount > continuationMap.GetCount();
24082+
24083+
if (!tryMerge)
24084+
{
24085+
JITDUMP("EH#%i does not have any mergeable callfinallys\n", XTnum);
24086+
continue;
24087+
}
24088+
24089+
canMerge = true;
24090+
24091+
// Walk the callfinally region, looking for blocks that jump
24092+
// to a callfinally that invokes this try's finally, and make
24093+
// sure they all jump to the appropriate canonical
24094+
// callfinally.
24095+
for (BasicBlock* currentBlock = firstCallFinallyRangeBlock; currentBlock != endCallFinallyRangeBlock;
24096+
currentBlock = currentBlock->bbNext)
24097+
{
24098+
bool merged = fgRetargetBranchesToCanonicalCallFinally(currentBlock, beginHandlerBlock, continuationMap);
24099+
didMerge = didMerge || merged;
24100+
}
24101+
}
24102+
24103+
if (!canMerge)
24104+
{
24105+
JITDUMP("Method had try-finallys, but did not have any mergeable finally chains.\n");
24106+
}
24107+
else
24108+
{
24109+
assert(didMerge);
24110+
JITDUMP("Method had try-finallys and some callfinally merges were performed.\n");
24111+
24112+
#if DEBUG
24113+
24114+
if (verbose)
24115+
{
24116+
printf("\n*************** After fgMergeFinallyChains()\n");
24117+
fgDispBasicBlocks();
24118+
fgDispHandlerTab();
24119+
printf("\n");
24120+
}
24121+
24122+
#endif // DEBUG
24123+
}
24124+
}
24125+
24126+
//------------------------------------------------------------------------
24127+
// fgRetargetBranchesToCanonicalCallFinally: find non-canonical callfinally
24128+
// invocations and make them canonical.
24129+
//
24130+
// Arguments:
24131+
// block -- block to examine for call finally invocation
24132+
// handler -- start of the finally region for the try
24133+
// continuationMap -- map giving the canonical callfinally for
24134+
// each continuation
24135+
//
24136+
// Returns:
24137+
// true iff the block's branch was retargeted.
24138+
24139+
bool Compiler::fgRetargetBranchesToCanonicalCallFinally(BasicBlock* block,
24140+
BasicBlock* handler,
24141+
BlockToBlockMap& continuationMap)
24142+
{
24143+
// We expect callfinallys to be invoked by a BBJ_ALWAYS at this
24144+
// stage in compilation.
24145+
if (block->bbJumpKind != BBJ_ALWAYS)
24146+
{
24147+
// Possible paranoia assert here -- no flow successor of
24148+
// this block should be a callfinally for this try.
24149+
return false;
24150+
}
24151+
24152+
// Screen out cases that are not callfinallys to the right
24153+
// handler.
24154+
BasicBlock* const callFinally = block->bbJumpDest;
24155+
24156+
if (!callFinally->isBBCallAlwaysPair())
24157+
{
24158+
return false;
24159+
}
24160+
24161+
if (callFinally->bbJumpDest != handler)
24162+
{
24163+
return false;
24164+
}
24165+
24166+
// Ok, this is a callfinally that invokes the right handler.
24167+
// Get its continuation.
24168+
BasicBlock* const leaveBlock = callFinally->bbNext;
24169+
BasicBlock* const continuationBlock = leaveBlock->bbJumpDest;
24170+
24171+
// Find the canonical callfinally for that continuation.
24172+
BasicBlock* const canonicalCallFinally = continuationMap[continuationBlock];
24173+
assert(canonicalCallFinally != nullptr);
24174+
24175+
// If the block already jumps to the canoncial call finally, no work needed.
24176+
if (block->bbJumpDest == canonicalCallFinally)
24177+
{
24178+
return false;
24179+
}
24180+
24181+
// Else, retarget it so that it does...
24182+
JITDUMP("Redirecting branch in BB%02u from BB%02u to BB%02u.\n", block->bbNum, callFinally->bbNum,
24183+
canonicalCallFinally->bbNum);
24184+
24185+
block->bbJumpDest = canonicalCallFinally;
24186+
fgAddRefPred(canonicalCallFinally, block);
24187+
assert(callFinally->bbRefs > 0);
24188+
fgRemoveRefPred(callFinally, block);
24189+
24190+
return true;
24191+
}
24192+
2390324193
// FatCalliTransformer transforms calli that can use fat function pointer.
2390424194
// Fat function pointer is pointer with the second least significant bit set,
2390524195
// if the bit is set, the pointer (after clearing the bit) actually points to

src/jit/morph.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16972,6 +16972,10 @@ void Compiler::fgMorph()
1697216972

1697316973
EndPhase(PHASE_EMPTY_FINALLY);
1697416974

16975+
fgMergeFinallyChains();
16976+
16977+
EndPhase(PHASE_MERGE_FINALLY_CHAINS);
16978+
1697516979
fgCloneFinally();
1697616980

1697716981
EndPhase(PHASE_CLONE_FINALLY);

0 commit comments

Comments
 (0)