Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JIT: Canonicalize loop exits #98096

Merged
merged 7 commits into from
Feb 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/coreclr/jit/compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5035,7 +5035,7 @@ void Compiler::compCompile(void** methodCodePtr, uint32_t* methodCodeSize, JitFl
}
}

optLoopsRequirePreHeaders = false;
optLoopsCanonical = false;

#ifdef DEBUG
DoPhase(this, PHASE_STRESS_SPLIT_TREE, &Compiler::StressSplitTree);
Expand Down
17 changes: 15 additions & 2 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -2184,6 +2184,9 @@ class FlowGraphNaturalLoop
template<typename TFunc>
BasicBlockVisit VisitLoopBlocksLexical(TFunc func);

template<typename TFunc>
BasicBlockVisit VisitRegularExitBlocks(TFunc func);

BasicBlock* GetLexicallyTopMostBlock();
BasicBlock* GetLexicallyBottomMostBlock();

Expand Down Expand Up @@ -4971,7 +4974,11 @@ class Compiler
FlowGraphDominatorTree* m_domTree;
BlockReachabilitySets* m_reachabilitySets;

bool optLoopsRequirePreHeaders; // Do we require that all loops (in m_loops) have pre-headers?
// Do we require loops to be in canonical form? The canonical form ensures that:
// 1. All loops have preheaders (single entry blocks that always enter the loop)
// 2. All loop exits where bbIsHandlerBeg(exit) is false have only loop predecessors.
//
bool optLoopsCanonical;
unsigned optNumNaturalLoopsFound; // Number of natural loops found in the loop finding phase

bool fgBBVarSetsInited;
Expand Down Expand Up @@ -5906,7 +5913,7 @@ class Compiler

PhaseStatus fgCanonicalizeFirstBB();

void fgSetEHRegionForNewPreheader(BasicBlock* preheader);
void fgSetEHRegionForNewPreheaderOrExit(BasicBlock* preheader);

void fgUnreachableBlock(BasicBlock* block);

Expand Down Expand Up @@ -6785,13 +6792,19 @@ class Compiler

void optFindLoops();
bool optCanonicalizeLoops();

void optCompactLoops();
void optCompactLoop(FlowGraphNaturalLoop* loop);
BasicBlock* optFindLoopCompactionInsertionPoint(FlowGraphNaturalLoop* loop, BasicBlock* top);
BasicBlock* optTryAdvanceLoopCompactionInsertionPoint(FlowGraphNaturalLoop* loop, BasicBlock* insertionPoint, BasicBlock* top, BasicBlock* bottom);
bool optCreatePreheader(FlowGraphNaturalLoop* loop);
void optSetPreheaderWeight(FlowGraphNaturalLoop* loop, BasicBlock* preheader);

bool optCanonicalizeExits(FlowGraphNaturalLoop* loop);
bool optCanonicalizeExit(FlowGraphNaturalLoop* loop, BasicBlock* exit);
weight_t optEstimateEdgeLikelihood(BasicBlock* from, BasicBlock* to, bool* fromProfile);
void optSetExitWeight(FlowGraphNaturalLoop* loop, BasicBlock* exit);

PhaseStatus optCloneLoops();
void optCloneLoop(FlowGraphNaturalLoop* loop, LoopCloneContext* context);
PhaseStatus optUnrollLoops(); // Unrolls loops (needs to have cost info)
Expand Down
54 changes: 54 additions & 0 deletions src/coreclr/jit/compiler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4969,6 +4969,60 @@ BasicBlockVisit FlowGraphNaturalLoop::VisitLoopBlocksLexical(TFunc func)
return BasicBlockVisit::Continue;
}

//------------------------------------------------------------------------------
// FlowGraphNaturalLoop::VisitRegularExitBlocks: Visit non-handler blocks that
// are outside the loop but that may have regular predecessors inside the loop.
//
// Type parameters:
// TFunc - Callback functor type
//
// Arguments:
// func - Callback functor that takes a BasicBlock* and returns a
// BasicBlockVisit.
//
// Returns:
// BasicBlockVisit that indicated whether the visit was aborted by the
// callback or whether all blocks were visited.
//
// Remarks:
// Note that no handler begins are visited by this function, even if they
// have regular predecessors inside the loop (for example, finally handlers
// can have regular BBJ_CALLFINALLY predecessors inside the loop). This
// choice is motivated by the fact that such handlers will also show up as
// exceptional exit blocks that must always be handled specially by client
// code regardless.
//
template <typename TFunc>
BasicBlockVisit FlowGraphNaturalLoop::VisitRegularExitBlocks(TFunc func)
{
Compiler* comp = m_dfsTree->GetCompiler();

BitVecTraits traits = m_dfsTree->PostOrderTraits();
BitVec visited(BitVecOps::MakeEmpty(&traits));

for (FlowEdge* edge : ExitEdges())
{
BasicBlockVisit result = edge->getSourceBlock()->VisitRegularSuccs(comp, [&](BasicBlock* succ) {
assert(m_dfsTree->Contains(succ));
if (!comp->bbIsHandlerBeg(succ) && !ContainsBlock(succ) &&
BitVecOps::TryAddElemD(&traits, visited, succ->bbPostorderNum) &&
(func(succ) == BasicBlockVisit::Abort))
{
return BasicBlockVisit::Abort;
}

return BasicBlockVisit::Continue;
});

if (result == BasicBlockVisit::Abort)
{
return BasicBlockVisit::Abort;
}
}

return BasicBlockVisit::Continue;
}

/*****************************************************************************/
#endif //_COMPILER_HPP_
/*****************************************************************************/
10 changes: 9 additions & 1 deletion src/coreclr/jit/fgdiagnostic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4719,12 +4719,20 @@ void Compiler::fgDebugCheckLoops()
{
return;
}
if (optLoopsRequirePreHeaders)
if (optLoopsCanonical)
{
for (FlowGraphNaturalLoop* loop : m_loops->InReversePostOrder())
{
assert(loop->EntryEdges().size() == 1);
assert(loop->EntryEdge(0)->getSourceBlock()->KindIs(BBJ_ALWAYS));

loop->VisitRegularExitBlocks([=](BasicBlock* exit) {
for (BasicBlock* pred : exit->PredBlocks())
{
assert(loop->ContainsBlock(pred));
}
return BasicBlockVisit::Continue;
});
}
}
}
Expand Down
11 changes: 9 additions & 2 deletions src/coreclr/jit/loopcloning.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2969,11 +2969,18 @@ PhaseStatus Compiler::optCloneLoops()

if (optLoopsCloned > 0)
{
fgRenumberBlocks();

fgInvalidateDfsTree();
m_dfsTree = fgComputeDfs();
m_loops = FlowGraphNaturalLoops::Find(m_dfsTree);

if (optCanonicalizeLoops())
{
fgInvalidateDfsTree();
m_dfsTree = fgComputeDfs();
m_loops = FlowGraphNaturalLoops::Find(m_dfsTree);
}
Comment on lines +2976 to +2981
Copy link
Member Author

@jakobbotsch jakobbotsch Feb 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe one way to improve TP slightly would be to canonicalize exits as part of loop cloning so that we don't have to reinvoke canonicalization here. I might look into what this would save, but I would expect not that much since we're only paying here in cases where we actually did do loop cloning.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These costs are likely dwarfed by the cost of cloning, so if we improve the cloning heuristics (which I assume will mean cloning fewer big loops) we should come out ahead.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like it wouldn't be hard for cloning to also clone all its exit blocks?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would work to clone the exits, although at the tradeoff of duplicating even more IR. Not sure whether that's worth it.


fgRenumberBlocks();
}

#ifdef DEBUG
Expand Down
Loading
Loading