From 4c09966554e50dad18b6cc6833343d4bb68d912a Mon Sep 17 00:00:00 2001 From: Aman Khalid Date: Thu, 2 May 2024 21:04:11 +0000 Subject: [PATCH] JIT: Implement greedy RPO-based block layout (#101473) Part of #93020. Compiler::fgDoReversePostOrderLayout reorders blocks based on a RPO of the flowgraph's successor edges. When reordering based on the RPO, we only reorder blocks within the same EH region to avoid breaking up their contiguousness. After establishing an RPO-based layout, we do another pass to move cold blocks to the ends of their regions in fgMoveColdBlocks. The "greedy" part of this layout isn't all that greedy just yet. For now, we use edge likelihoods to make placement decisions only for BBJ_COND blocks' successors. I plan to extend this greediness to other multi-successor block kinds (BBJ_SWITCH, etc) in a follow-up so we can independently evaluate the value in doing so. This new layout is disabled by default for now. --- src/coreclr/jit/block.cpp | 21 +- src/coreclr/jit/block.h | 4 +- src/coreclr/jit/compiler.h | 8 +- src/coreclr/jit/compiler.hpp | 32 +- src/coreclr/jit/fgopt.cpp | 514 +++++++++++++++++++++++++++++- src/coreclr/jit/flowgraph.cpp | 17 +- src/coreclr/jit/jitconfigvalues.h | 3 + 7 files changed, 576 insertions(+), 23 deletions(-) diff --git a/src/coreclr/jit/block.cpp b/src/coreclr/jit/block.cpp index 60dbce6aaf00a0..ecee292264ec85 100644 --- a/src/coreclr/jit/block.cpp +++ b/src/coreclr/jit/block.cpp @@ -137,14 +137,17 @@ void FlowEdge::addLikelihood(weight_t addedLikelihood) // AllSuccessorEnumerator: Construct an instance of the enumerator. // // Arguments: -// comp - Compiler instance -// block - The block whose successors are to be iterated +// comp - Compiler instance +// block - The block whose successors are to be iterated +// useProfile - If true, determines the order of successors visited using profile data // -AllSuccessorEnumerator::AllSuccessorEnumerator(Compiler* comp, BasicBlock* block) +AllSuccessorEnumerator::AllSuccessorEnumerator(Compiler* comp, BasicBlock* block, const bool useProfile /* = false */) : m_block(block) { m_numSuccs = 0; - block->VisitAllSuccs(comp, [this](BasicBlock* succ) { + block->VisitAllSuccs( + comp, + [this](BasicBlock* succ) { if (m_numSuccs < ArrLen(m_successors)) { m_successors[m_numSuccs] = succ; @@ -152,18 +155,22 @@ AllSuccessorEnumerator::AllSuccessorEnumerator(Compiler* comp, BasicBlock* block m_numSuccs++; return BasicBlockVisit::Continue; - }); + }, + useProfile); if (m_numSuccs > ArrLen(m_successors)) { m_pSuccessors = new (comp, CMK_BasicBlock) BasicBlock*[m_numSuccs]; unsigned numSuccs = 0; - block->VisitAllSuccs(comp, [this, &numSuccs](BasicBlock* succ) { + block->VisitAllSuccs( + comp, + [this, &numSuccs](BasicBlock* succ) { assert(numSuccs < m_numSuccs); m_pSuccessors[numSuccs++] = succ; return BasicBlockVisit::Continue; - }); + }, + useProfile); assert(numSuccs == m_numSuccs); } diff --git a/src/coreclr/jit/block.h b/src/coreclr/jit/block.h index 37c97bfdca6485..6ff2bb31b2a856 100644 --- a/src/coreclr/jit/block.h +++ b/src/coreclr/jit/block.h @@ -1799,7 +1799,7 @@ struct BasicBlock : private LIR::Range BasicBlockVisit VisitEHEnclosedHandlerSecondPassSuccs(Compiler* comp, TFunc func); template - BasicBlockVisit VisitAllSuccs(Compiler* comp, TFunc func); + BasicBlockVisit VisitAllSuccs(Compiler* comp, TFunc func, const bool useProfile = false); template BasicBlockVisit VisitEHSuccs(Compiler* comp, TFunc func); @@ -2497,7 +2497,7 @@ class AllSuccessorEnumerator public: // Constructs an enumerator of all `block`'s successors. - AllSuccessorEnumerator(Compiler* comp, BasicBlock* block); + AllSuccessorEnumerator(Compiler* comp, BasicBlock* block, const bool useProfile = false); // Gets the block whose successors are enumerated. BasicBlock* Block() diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index deb7b97943dc81..050f2e001ea294 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -2793,6 +2793,9 @@ class Compiler EHblkDsc* ehIsBlockHndLast(BasicBlock* block); bool ehIsBlockEHLast(BasicBlock* block); + template + void ehUpdateTryLasts(GetTryLast getTryLast, SetTryLast setTryLast); + bool ehBlockHasExnFlowDsc(BasicBlock* block); // Return the region index of the most nested EH region this block is in. @@ -6054,6 +6057,8 @@ class Compiler bool fgComputeCalledCount(weight_t returnWeight); bool fgReorderBlocks(bool useProfile); + void fgDoReversePostOrderLayout(); + void fgMoveColdBlocks(); bool fgFuncletsAreCold(); @@ -6074,9 +6079,10 @@ class Compiler PhaseStatus fgSetBlockOrder(); bool fgHasCycleWithoutGCSafePoint(); - template + template unsigned fgRunDfs(VisitPreorder assignPreorder, VisitPostorder assignPostorder, VisitEdge visitEdge); + template FlowGraphDfsTree* fgComputeDfs(); void fgInvalidateDfsTree(); diff --git a/src/coreclr/jit/compiler.hpp b/src/coreclr/jit/compiler.hpp index 022a92bd740a3f..d8844c2e35afb8 100644 --- a/src/coreclr/jit/compiler.hpp +++ b/src/coreclr/jit/compiler.hpp @@ -623,12 +623,13 @@ BasicBlockVisit BasicBlock::VisitEHSuccs(Compiler* comp, TFunc func) // Arguments: // comp - Compiler instance // func - Callback +// useProfile - If true, determines the order of successors visited using profile data // // Returns: // Whether or not the visiting was aborted. // template -BasicBlockVisit BasicBlock::VisitAllSuccs(Compiler* comp, TFunc func) +BasicBlockVisit BasicBlock::VisitAllSuccs(Compiler* comp, TFunc func, const bool useProfile /* = false */) { switch (bbKind) { @@ -662,10 +663,22 @@ BasicBlockVisit BasicBlock::VisitAllSuccs(Compiler* comp, TFunc func) return VisitEHSuccs(comp, func); case BBJ_COND: - RETURN_ON_ABORT(func(GetFalseTarget())); - - if (!TrueEdgeIs(GetFalseEdge())) + if (TrueEdgeIs(GetFalseEdge())) { + RETURN_ON_ABORT(func(GetFalseTarget())); + } + else if (useProfile && (GetTrueEdge()->getLikelihood() < GetFalseEdge()->getLikelihood())) + { + // When building an RPO-based block layout, we want to visit the unlikely successor first + // so that in the DFS computation, the likely successor will be processed right before this block, + // meaning the RPO-based layout will enable fall-through into the likely successor. + // + RETURN_ON_ABORT(func(GetTrueTarget())); + RETURN_ON_ABORT(func(GetFalseTarget())); + } + else + { + RETURN_ON_ABORT(func(GetFalseTarget())); RETURN_ON_ABORT(func(GetTrueTarget())); } @@ -696,8 +709,8 @@ BasicBlockVisit BasicBlock::VisitAllSuccs(Compiler* comp, TFunc func) // VisitRegularSuccs: Visit regular successors of this block. // // Arguments: -// comp - Compiler instance -// func - Callback +// comp - Compiler instance +// func - Callback // // Returns: // Whether or not the visiting was aborted. @@ -4745,6 +4758,7 @@ inline bool Compiler::compCanHavePatchpoints(const char** reason) // VisitPreorder - Functor type that takes a BasicBlock* and its preorder number // VisitPostorder - Functor type that takes a BasicBlock* and its postorder number // VisitEdge - Functor type that takes two BasicBlock*. +// useProfile - If true, determines order of successors visited using profile data // // Parameters: // visitPreorder - Functor to visit block in its preorder @@ -4755,7 +4769,7 @@ inline bool Compiler::compCanHavePatchpoints(const char** reason) // Returns: // Number of blocks visited. // -template +template unsigned Compiler::fgRunDfs(VisitPreorder visitPreorder, VisitPostorder visitPostorder, VisitEdge visitEdge) { BitVecTraits traits(fgBBNumMax + 1, this); @@ -4768,7 +4782,7 @@ unsigned Compiler::fgRunDfs(VisitPreorder visitPreorder, VisitPostorder visitPos auto dfsFrom = [&](BasicBlock* firstBB) { BitVecOps::AddElemD(&traits, visited, firstBB->bbNum); - blocks.Emplace(this, firstBB); + blocks.Emplace(this, firstBB, useProfile); visitPreorder(firstBB, preOrderIndex++); while (!blocks.Empty()) @@ -4780,7 +4794,7 @@ unsigned Compiler::fgRunDfs(VisitPreorder visitPreorder, VisitPostorder visitPos { if (BitVecOps::TryAddElemD(&traits, visited, succ->bbNum)) { - blocks.Emplace(this, succ); + blocks.Emplace(this, succ, useProfile); visitPreorder(succ, preOrderIndex++); } diff --git a/src/coreclr/jit/fgopt.cpp b/src/coreclr/jit/fgopt.cpp index ac008eb140f9d7..88dd717fab6934 100644 --- a/src/coreclr/jit/fgopt.cpp +++ b/src/coreclr/jit/fgopt.cpp @@ -3412,9 +3412,22 @@ bool Compiler::fgReorderBlocks(bool useProfile) } } - // If we will be reordering blocks, ensure the false target of a BBJ_COND block is its next block if (useProfile) { + if (JitConfig.JitDoReversePostOrderLayout()) + { + fgDoReversePostOrderLayout(); + fgMoveColdBlocks(); + + // Renumber blocks to facilitate LSRA's order of block visitation + // TODO: Consider removing this, and using traversal order in lSRA + // + fgRenumberBlocks(); + + return true; + } + + // We will be reordering blocks, so ensure the false target of a BBJ_COND block is its next block for (BasicBlock* block = fgFirstBB; block != nullptr; block = block->Next()) { if (block->KindIs(BBJ_COND) && !block->NextIs(block->GetFalseTarget())) @@ -4522,6 +4535,505 @@ bool Compiler::fgReorderBlocks(bool useProfile) #pragma warning(pop) #endif +//----------------------------------------------------------------------------- +// fgDoReversePostOrderLayout: Reorder blocks using a greedy RPO traversal. +// +void Compiler::fgDoReversePostOrderLayout() +{ +#ifdef DEBUG + if (verbose) + { + printf("*************** In fgDoReversePostOrderLayout()\n"); + + printf("\nInitial BasicBlocks"); + fgDispBasicBlocks(verboseTrees); + printf("\n"); + } +#endif // DEBUG + + // Compute DFS of all blocks in the method, using profile data to determine the order successors are visited in + // + FlowGraphDfsTree* const dfsTree = fgComputeDfs(); + + // Fast path: We don't have any EH regions, so just reorder the blocks + // + if (compHndBBtabCount == 0) + { + for (unsigned i = dfsTree->GetPostOrderCount() - 1; i != 0; i--) + { + BasicBlock* const block = dfsTree->GetPostOrder(i); + BasicBlock* const blockToMove = dfsTree->GetPostOrder(i - 1); + fgUnlinkBlock(blockToMove); + fgInsertBBafter(block, blockToMove); + } + + return; + } + + // The RPO will scramble the EH regions, requiring us to correct their state. + // To do so, we will need to determine the new end blocks of each region. + // + struct EHLayoutInfo + { + BasicBlock* tryRegionEnd; + BasicBlock* hndRegionEnd; + bool tryRegionInMainBody; + + // Default constructor provided so we can call ArrayStack::Emplace + // + EHLayoutInfo() = default; + }; + + ArrayStack regions(getAllocator(CMK_ArrayStack), compHndBBtabCount); + + // The RPO will break up call-finally pairs, so save them before re-ordering + // + struct CallFinallyPair + { + BasicBlock* callFinally; + BasicBlock* callFinallyRet; + + // Constructor provided so we can call ArrayStack::Emplace + // + CallFinallyPair(BasicBlock* first, BasicBlock* second) + : callFinally(first) + , callFinallyRet(second) + { + } + }; + + ArrayStack callFinallyPairs(getAllocator()); + + for (EHblkDsc* const HBtab : EHClauses(this)) + { + // Default-initialize a EHLayoutInfo for each EH clause + regions.Emplace(); + + if (HBtab->HasFinallyHandler()) + { + for (BasicBlock* const pred : HBtab->ebdHndBeg->PredBlocks()) + { + assert(pred->KindIs(BBJ_CALLFINALLY)); + if (pred->isBBCallFinallyPair()) + { + callFinallyPairs.Emplace(pred, pred->Next()); + } + } + } + } + + // Reorder blocks + // + for (unsigned i = dfsTree->GetPostOrderCount() - 1; i != 0; i--) + { + BasicBlock* const block = dfsTree->GetPostOrder(i); + BasicBlock* const blockToMove = dfsTree->GetPostOrder(i - 1); + + // Only reorder blocks within the same EH region -- we don't want to make them non-contiguous + // + if (BasicBlock::sameEHRegion(block, blockToMove)) + { + // Don't reorder EH regions with filter handlers -- we want the filter to come first + // + if (block->hasHndIndex() && ehGetDsc(block->getHndIndex())->HasFilter()) + { + continue; + } + + fgUnlinkBlock(blockToMove); + fgInsertBBafter(block, blockToMove); + } + } + + // Fix up call-finally pairs + // + for (int i = 0; i < callFinallyPairs.Height(); i++) + { + const CallFinallyPair& pair = callFinallyPairs.BottomRef(i); + fgUnlinkBlock(pair.callFinallyRet); + fgInsertBBafter(pair.callFinally, pair.callFinallyRet); + } + + // The RPO won't change the entry blocks of any EH regions, but reordering can change the last block in a region + // (for example, by pushing throw blocks unreachable via normal flow to the end of the region). + // First, determine the new EH region ends. + // + + for (BasicBlock* const block : Blocks(fgFirstBB, fgLastBBInMainFunction())) + { + if (block->hasTryIndex()) + { + EHLayoutInfo& layoutInfo = regions.BottomRef(block->getTryIndex()); + layoutInfo.tryRegionEnd = block; + layoutInfo.tryRegionInMainBody = true; + } + + if (block->hasHndIndex()) + { + regions.BottomRef(block->getHndIndex()).hndRegionEnd = block; + } + } + + for (BasicBlock* const block : Blocks(fgFirstFuncletBB)) + { + if (block->hasHndIndex()) + { + regions.BottomRef(block->getHndIndex()).hndRegionEnd = block; + } + + if (block->hasTryIndex()) + { + EHLayoutInfo& layoutInfo = regions.BottomRef(block->getTryIndex()); + + if (!layoutInfo.tryRegionInMainBody) + { + layoutInfo.tryRegionEnd = block; + } + } + } + + // Now, update the EH descriptors, starting with the try regions + // + auto getTryLast = [®ions](const unsigned index) -> BasicBlock* { + return regions.BottomRef(index).tryRegionEnd; + }; + + auto setTryLast = [®ions](const unsigned index, BasicBlock* const block) { + regions.BottomRef(index).tryRegionEnd = block; + }; + + ehUpdateTryLasts(getTryLast, setTryLast); + + // Now, do the handler regions + // + unsigned XTnum = 0; + for (EHblkDsc* const HBtab : EHClauses(this)) + { + // The end of each handler region should have been visited by iterating the blocklist above + // + BasicBlock* const hndEnd = regions.BottomRef(XTnum++).hndRegionEnd; + assert(hndEnd != nullptr); + + // Update the end pointer of this handler region to the new last block + // + HBtab->ebdHndLast = hndEnd; + const unsigned enclosingHndIndex = HBtab->ebdEnclosingHndIndex; + + // If this handler region is nested in another one, we might need to update its enclosing region's end block + // + if (enclosingHndIndex != EHblkDsc::NO_ENCLOSING_INDEX) + { + BasicBlock* const enclosingHndEnd = regions.BottomRef(enclosingHndIndex).hndRegionEnd; + assert(enclosingHndEnd != nullptr); + + // If the enclosing region ends right before the nested region begins, + // extend the enclosing region's last block to the end of the nested region. + // + BasicBlock* const hndBeg = HBtab->HasFilter() ? HBtab->ebdFilter : HBtab->ebdHndBeg; + if (enclosingHndEnd->NextIs(hndBeg)) + { + regions.BottomRef(enclosingHndIndex).hndRegionEnd = hndEnd; + } + } + } +} + +//----------------------------------------------------------------------------- +// fgMoveColdBlocks: Move rarely-run blocks to the end of their respective regions. +// +// Notes: +// Exception handlers are assumed to be cold, so we won't move blocks within them. +// +void Compiler::fgMoveColdBlocks() +{ +#ifdef DEBUG + if (verbose) + { + printf("*************** In fgMoveColdBlocks()\n"); + + printf("\nInitial BasicBlocks"); + fgDispBasicBlocks(verboseTrees); + printf("\n"); + } +#endif // DEBUG + + auto moveColdMainBlocks = [this]() { + // Find the last block in the main body that isn't part of an EH region + // + BasicBlock* lastMainBB; + for (lastMainBB = this->fgLastBBInMainFunction(); lastMainBB != nullptr; lastMainBB = lastMainBB->Prev()) + { + if (!lastMainBB->hasTryIndex() && !lastMainBB->hasHndIndex()) + { + break; + } + } + + // Nothing to do if there are two or fewer non-EH blocks + // + if ((lastMainBB == nullptr) || lastMainBB->IsFirst() || lastMainBB->PrevIs(fgFirstBB)) + { + return; + } + + // Search the main method body for rarely-run blocks to move + // + BasicBlock* prev; + for (BasicBlock* block = lastMainBB->Prev(); block != fgFirstBB; block = prev) + { + prev = block->Prev(); + + // We only want to move cold blocks. + // Also, don't consider blocks in EH regions for now; only move blocks in the main method body. + // Finally, don't move block if it is the beginning of a call-finally pair, + // as we want to keep these pairs contiguous + // (if we encounter the end of a pair below, we'll move the whole pair). + // + if (!block->isRunRarely() || block->hasTryIndex() || block->hasHndIndex() || block->isBBCallFinallyPair()) + { + continue; + } + + this->fgUnlinkBlock(block); + this->fgInsertBBafter(lastMainBB, block); + + // If block is the end of a call-finally pair, prev is the beginning of the pair. + // Move prev to before block to keep the pair contiguous. + // + if (block->KindIs(BBJ_CALLFINALLYRET)) + { + BasicBlock* const callFinally = prev; + prev = prev->Prev(); + assert(callFinally->KindIs(BBJ_CALLFINALLY)); + assert(!callFinally->HasFlag(BBF_RETLESS_CALL)); + this->fgUnlinkBlock(callFinally); + this->fgInsertBBafter(lastMainBB, callFinally); + } + } + + // We have moved all cold main blocks before lastMainBB to after lastMainBB. + // If lastMainBB itself is cold, move it to the end of the method to restore its relative ordering. + // + if (lastMainBB->isRunRarely()) + { + BasicBlock* const newLastMainBB = this->fgLastBBInMainFunction(); + if (lastMainBB != newLastMainBB) + { + BasicBlock* const prev = lastMainBB->Prev(); + this->fgUnlinkBlock(lastMainBB); + this->fgInsertBBafter(newLastMainBB, lastMainBB); + + // Call-finally check + // + if (lastMainBB->KindIs(BBJ_CALLFINALLYRET)) + { + assert(prev->KindIs(BBJ_CALLFINALLY)); + assert(!prev->HasFlag(BBF_RETLESS_CALL)); + assert(prev != newLastMainBB); + this->fgUnlinkBlock(prev); + this->fgInsertBBafter(newLastMainBB, prev); + } + } + } + }; + + moveColdMainBlocks(); + + // No EH regions + // + if (compHndBBtabCount == 0) + { + return; + } + + // We assume exception handlers are cold, so we won't bother moving blocks within them. + // We will move blocks only within try regions. + // First, determine where each try region ends, without considering nested regions. + // We will use these end blocks as insertion points. + // + BasicBlock** const tryRegionEnds = new (this, CMK_Generic) BasicBlock* [compHndBBtabCount] {}; + + for (BasicBlock* const block : Blocks(fgFirstBB, fgLastBBInMainFunction())) + { + if (block->hasTryIndex()) + { + tryRegionEnds[block->getTryIndex()] = block; + } + } + + // Search all try regions in the main method body for cold blocks to move + // + BasicBlock* prev; + for (BasicBlock* block = fgLastBBInMainFunction(); block != fgFirstBB; block = prev) + { + prev = block->Prev(); + + // Only consider rarely-run blocks in try regions. + // If we have such a block that is also part of an exception handler, don't bother moving it. + // Finally, don't move block if it is the beginning of a call-finally pair, + // as we want to keep these pairs contiguous + // (if we encounter the end of a pair below, we'll move the whole pair). + // + if (!block->hasTryIndex() || !block->isRunRarely() || block->hasHndIndex() || block->isBBCallFinallyPair()) + { + continue; + } + + const unsigned tryIndex = block->getTryIndex(); + EHblkDsc* const HBtab = ehGetDsc(tryIndex); + + // Don't move the beginning of a try region. + // Also, if this try region's entry is cold, don't bother moving its blocks. + // + if ((HBtab->ebdTryBeg == block) || (HBtab->ebdTryBeg->isRunRarely())) + { + continue; + } + + BasicBlock* const insertionPoint = tryRegionEnds[tryIndex]; + assert(insertionPoint != nullptr); + + // Don't move the end of this try region + // + if (block == insertionPoint) + { + continue; + } + + fgUnlinkBlock(block); + fgInsertBBafter(insertionPoint, block); + + // Keep call-finally pairs contiguous + // + if (block->KindIs(BBJ_CALLFINALLYRET)) + { + BasicBlock* const callFinally = prev; + prev = prev->Prev(); + assert(callFinally->KindIs(BBJ_CALLFINALLY)); + assert(!callFinally->HasFlag(BBF_RETLESS_CALL)); + fgUnlinkBlock(callFinally); + fgInsertBBafter(insertionPoint, callFinally); + } + } + + // Before updating EH descriptors, find the new try region ends + // + for (unsigned XTnum = 0; XTnum < compHndBBtabCount; XTnum++) + { + BasicBlock* const tryEnd = tryRegionEnds[XTnum]; + + // This try region isn't in the main method body + // + if (tryEnd == nullptr) + { + continue; + } + + // If we moved cold blocks to the end of this try region, + // search for the new end block + // + BasicBlock* newTryEnd = tryEnd; + for (BasicBlock* const block : Blocks(tryEnd, fgLastBBInMainFunction())) + { + if (!BasicBlock::sameTryRegion(tryEnd, block)) + { + break; + } + + newTryEnd = block; + } + + // We moved cold blocks to the end of this try region, but the old end block is cold, too. + // Move the old end block to the end of the region to preserve its relative ordering. + // + if ((tryEnd != newTryEnd) && tryEnd->isRunRarely() && !tryEnd->hasHndIndex()) + { + BasicBlock* const prev = tryEnd->Prev(); + fgUnlinkBlock(tryEnd); + fgInsertBBafter(newTryEnd, tryEnd); + + // Keep call-finally pairs contiguous + // + if (tryEnd->KindIs(BBJ_CALLFINALLYRET)) + { + assert(prev->KindIs(BBJ_CALLFINALLY)); + assert(!prev->HasFlag(BBF_RETLESS_CALL)); + fgUnlinkBlock(prev); + fgInsertBBafter(newTryEnd, prev); + } + } + else + { + // Otherwise, just update the try region end + // + tryRegionEnds[XTnum] = newTryEnd; + } + } + + // Now, update EH descriptors + // + auto getTryLast = [tryRegionEnds](const unsigned index) -> BasicBlock* { + return tryRegionEnds[index]; + }; + + auto setTryLast = [tryRegionEnds](const unsigned index, BasicBlock* const block) { + tryRegionEnds[index] = block; + }; + + ehUpdateTryLasts(getTryLast, setTryLast); +} + +//------------------------------------------------------------- +// ehUpdateTryLasts: Iterates EH descriptors, updating each try region's +// end block as determined by getTryLast. +// +// Type parameters: +// GetTryLast - Functor type that takes an EH index, +// and returns the corresponding region's new try end block +// SetTryLast - Functor type that takes an EH index and a BasicBlock*, +// and updates some internal state tracking the new try end block of each EH region +// +// Parameters: +// getTryLast - Functor to get new try end block for an EH region +// setTryLast - Functor to update the new try end block for an EH region +// +template +void Compiler::ehUpdateTryLasts(GetTryLast getTryLast, SetTryLast setTryLast) +{ + unsigned XTnum = 0; + for (EHblkDsc* const HBtab : EHClauses(this)) + { + BasicBlock* const tryEnd = getTryLast(XTnum++); + + if (tryEnd == nullptr) + { + continue; + } + + // Update the end pointer of this try region to the new last block + // + HBtab->ebdTryLast = tryEnd; + const unsigned enclosingTryIndex = HBtab->ebdEnclosingTryIndex; + + // If this try region is nested in another one, we might need to update its enclosing region's end block + // + if (enclosingTryIndex != EHblkDsc::NO_ENCLOSING_INDEX) + { + BasicBlock* const enclosingTryEnd = getTryLast(enclosingTryIndex); + + // If multiple EH descriptors map to the same try region, + // then the enclosing region's last block might be null in the table, so set it here. + // Similarly, if the enclosing region ends right before the nested region begins, + // extend the enclosing region's last block to the end of the nested region. + // + if ((enclosingTryEnd == nullptr) || enclosingTryEnd->NextIs(HBtab->ebdTryBeg)) + { + setTryLast(enclosingTryIndex, tryEnd); + } + } + } +} + //------------------------------------------------------------- // fgUpdateFlowGraphPhase: run flow graph optimization as a // phase, with no tail duplication diff --git a/src/coreclr/jit/flowgraph.cpp b/src/coreclr/jit/flowgraph.cpp index 2a185d9e47b1d1..151729b0950514 100644 --- a/src/coreclr/jit/flowgraph.cpp +++ b/src/coreclr/jit/flowgraph.cpp @@ -4029,8 +4029,8 @@ bool FlowGraphDfsTree::Contains(BasicBlock* block) const // block `descendant` // // Arguments: -// ancestor -- block that is possible ancestor -// descendant -- block that is possible descendant +// ancestor - block that is possible ancestor +// descendant - block that is possible descendant // // Returns: // True if `ancestor` is ancestor of `descendant` in the depth first spanning @@ -4049,6 +4049,9 @@ bool FlowGraphDfsTree::IsAncestor(BasicBlock* ancestor, BasicBlock* descendant) //------------------------------------------------------------------------ // fgComputeDfs: Compute a depth-first search tree for the flow graph. // +// Type parameters: +// useProfile - If true, determines order of successors visited using profile data +// // Returns: // The tree. // @@ -4056,6 +4059,7 @@ bool FlowGraphDfsTree::IsAncestor(BasicBlock* ancestor, BasicBlock* descendant) // Preorder and postorder numbers are assigned into the BasicBlock structure. // The tree returned contains a postorder of the basic blocks. // +template FlowGraphDfsTree* Compiler::fgComputeDfs() { BasicBlock** postOrder = new (this, CMK_DepthFirstSearch) BasicBlock*[fgBBcount]; @@ -4081,10 +4085,17 @@ FlowGraphDfsTree* Compiler::fgComputeDfs() } }; - unsigned numBlocks = fgRunDfs(visitPreorder, visitPostorder, visitEdge); + unsigned numBlocks = + fgRunDfs(visitPreorder, + visitPostorder, + visitEdge); return new (this, CMK_DepthFirstSearch) FlowGraphDfsTree(this, postOrder, numBlocks, hasCycle); } +// Add explicit instantiations. +template FlowGraphDfsTree* Compiler::fgComputeDfs(); +template FlowGraphDfsTree* Compiler::fgComputeDfs(); + //------------------------------------------------------------------------ // fgInvalidateDfsTree: Invalidate computed DFS tree and dependent annotations // (like loops, dominators and SSA). diff --git a/src/coreclr/jit/jitconfigvalues.h b/src/coreclr/jit/jitconfigvalues.h index 700cd33bd6bf2f..81345191ce5f88 100644 --- a/src/coreclr/jit/jitconfigvalues.h +++ b/src/coreclr/jit/jitconfigvalues.h @@ -754,6 +754,9 @@ RELEASE_CONFIG_INTEGER(JitEnablePhysicalPromotion, W("JitEnablePhysicalPromotion // Enable cross-block local assertion prop RELEASE_CONFIG_INTEGER(JitEnableCrossBlockLocalAssertionProp, W("JitEnableCrossBlockLocalAssertionProp"), 1) +// Do greedy RPO-based layout in Compiler::fgReorderBlocks. +RELEASE_CONFIG_INTEGER(JitDoReversePostOrderLayout, W("JitDoReversePostOrderLayout"), 0); + // JitFunctionFile: Name of a file that contains a list of functions. If the currently compiled function is in the // file, certain other JIT config variables will be active. If the currently compiled function is not in the file, // the specific JIT config variables will not be active.