@@ -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
0 commit comments