Skip to content

JIT: Graph-based loop inversion #116017

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

Merged
merged 28 commits into from
Jun 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
a0e3e4d
JIT: Make loop inversion graph based
jakobbotsch Oct 29, 2024
5df0255
Remove debug code
jakobbotsch Oct 29, 2024
b75aea7
Fix release build
jakobbotsch Oct 29, 2024
d75f60e
Avoid inverting already-inverted loops, duplicate weight manipulation…
jakobbotsch Oct 30, 2024
ddc0ea2
Add a couple of quirks
jakobbotsch Oct 30, 2024
99a4321
Run jit-format
jakobbotsch Oct 30, 2024
757d14c
Add a metric for loops inverted
jakobbotsch Oct 30, 2024
2c82f09
Merge branch 'main' of github.com:dotnet/runtime into port-loop-inver…
jakobbotsch Jan 6, 2025
e0074ff
Reuse preheader for zero-trip test
jakobbotsch Jan 6, 2025
1931c5a
Compact latch block if possible
jakobbotsch Jan 6, 2025
06854ab
Run jit-format
jakobbotsch Jan 6, 2025
6c24c53
Remove quirks
jakobbotsch Jan 6, 2025
f59243f
Merge branch 'main' of github.com:dotnet/runtime into port-loop-inver…
jakobbotsch Jan 7, 2025
1437856
Merge from main; fix profile maintenance
amanasifkhalid Mar 18, 2025
b79fc7a
Leave fgRenumberBlocks in for now
amanasifkhalid Mar 18, 2025
8a9a547
Simplify profile maintenance
amanasifkhalid Mar 19, 2025
fe411d2
Remove fgRenumberBlocks call
amanasifkhalid Mar 20, 2025
41a6246
Merge branch 'main' into loop-inversion-graph-based
amanasifkhalid Mar 20, 2025
7d1e7c8
Fix fgOptimizeBranch
amanasifkhalid Mar 20, 2025
0f12f5d
Fix another spot in fgOptimizeBranch
amanasifkhalid Mar 20, 2025
effd81e
Merge branch 'main' into loop-inversion-graph-based
amanasifkhalid Mar 25, 2025
af6f31c
Merge from main
amanasifkhalid May 27, 2025
d7ec36d
Move phase back
amanasifkhalid May 27, 2025
5a4cec5
Merge branch 'main' into loop-inversion-graph-based
amanasifkhalid May 27, 2025
7becde0
Add size restriction
amanasifkhalid May 29, 2025
f44e50d
Merge from main
amanasifkhalid Jun 3, 2025
9c2b945
Refactor loop size calculation, and add inversion size limit
amanasifkhalid Jun 3, 2025
5299576
Fix merge conflict
amanasifkhalid Jun 3, 2025
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
5 changes: 3 additions & 2 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -7074,10 +7074,11 @@ class Compiler

bool optCanonicalizeExits(FlowGraphNaturalLoop* loop);
bool optCanonicalizeExit(FlowGraphNaturalLoop* loop, BasicBlock* exit);

bool optLoopComplexityExceeds(FlowGraphNaturalLoop* loop, unsigned limit);

PhaseStatus optCloneLoops();
PhaseStatus optRangeCheckCloning();
bool optShouldCloneLoop(FlowGraphNaturalLoop* loop, LoopCloneContext* context);
void optCloneLoop(FlowGraphNaturalLoop* loop, LoopCloneContext* context);
PhaseStatus optUnrollLoops(); // Unrolls loops (needs to have cost info)
bool optTryUnrollLoop(FlowGraphNaturalLoop* loop, bool* changedIR);
Expand Down Expand Up @@ -7132,7 +7133,7 @@ class Compiler

OptInvertCountTreeInfoType optInvertCountTreeInfo(GenTree* tree);

bool optInvertWhileLoop(BasicBlock* block);
bool optTryInvertWhileLoop(FlowGraphNaturalLoop* loop);
bool optIfConvert(BasicBlock* block);

private:
Expand Down
7 changes: 6 additions & 1 deletion src/coreclr/jit/fgdiagnostic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4733,7 +4733,12 @@ void Compiler::fgDebugCheckLoops()
loop->VisitRegularExitBlocks([=](BasicBlock* exit) {
for (BasicBlock* pred : exit->PredBlocks())
{
assert(loop->ContainsBlock(pred));
if (!loop->ContainsBlock(pred))
{
JITDUMP("Loop " FMT_LP " exit " FMT_BB " has non-loop predecessor " FMT_BB "\n",
loop->GetIndex(), exit->bbNum, pred->bbNum);
assert(!"Loop exit has non-loop predecessor");
}
}
return BasicBlockVisit::Continue;
});
Expand Down
15 changes: 6 additions & 9 deletions src/coreclr/jit/fgopt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2661,9 +2661,10 @@ bool Compiler::fgOptimizeBranch(BasicBlock* bJump)
}
#endif // DEBUG

// Computing the duplication cost may have triggered node reordering, so return true to indicate we modified IR
if (costIsTooHigh)
{
return false;
return true;
}

/* Looks good - duplicate the conditional block */
Expand All @@ -2677,12 +2678,7 @@ bool Compiler::fgOptimizeBranch(BasicBlock* bJump)
{
// Clone/substitute the expression.
Statement* stmt = gtCloneStmt(curStmt);

// cloneExpr doesn't handle everything.
if (stmt == nullptr)
{
return false;
}
assert(stmt != nullptr);

if (fgNodeThreading == NodeThreading::AllTrees)
{
Expand Down Expand Up @@ -2713,9 +2709,10 @@ bool Compiler::fgOptimizeBranch(BasicBlock* bJump)
condTree = condTree->gtGetOp1();

// This condTree has to be a RelOp comparison.
if (condTree->OperIsCompare() == false)
// If not, return true since we created new nodes.
if (!condTree->OperIsCompare())
{
return false;
return true;
}

// Join the two linked lists.
Expand Down
4 changes: 3 additions & 1 deletion src/coreclr/jit/jitconfigvalues.h
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,9 @@ OPT_CONFIG_INTEGER(JitDoOptimizeIVs, "JitDoOptimizeIVs", 1) // Perform optim
OPT_CONFIG_INTEGER(JitDoEarlyProp, "JitDoEarlyProp", 1) // Perform Early Value Propagation
OPT_CONFIG_INTEGER(JitDoLoopHoisting, "JitDoLoopHoisting", 1) // Perform loop hoisting on loop invariant values
OPT_CONFIG_INTEGER(JitDoLoopInversion, "JitDoLoopInversion", 1) // Perform loop inversion on "for/while" loops
OPT_CONFIG_INTEGER(JitDoRangeAnalysis, "JitDoRangeAnalysis", 1) // Perform range check analysis
RELEASE_CONFIG_INTEGER(JitLoopInversionSizeLimit, "JitLoopInversionSizeLimit", 100) // limit inversion to loops with no
// more than this many tree nodes
OPT_CONFIG_INTEGER(JitDoRangeAnalysis, "JitDoRangeAnalysis", 1) // Perform range check analysis
OPT_CONFIG_INTEGER(JitDoVNBasedDeadStoreRemoval, "JitDoVNBasedDeadStoreRemoval", 1) // Perform VN-based dead store
// removal
OPT_CONFIG_INTEGER(JitDoRedundantBranchOpts, "JitDoRedundantBranchOpts", 1) // Perform redundant branch optimizations
Expand Down
67 changes: 10 additions & 57 deletions src/coreclr/jit/loopcloning.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1815,60 +1815,6 @@ void Compiler::optPerformStaticOptimizations(FlowGraphNaturalLoop* loop,
}
}

//------------------------------------------------------------------------
// optShouldCloneLoop: Decide if a loop that can be cloned should be cloned.
//
// Arguments:
// loop - the current loop for which the optimizations are performed.
// context - data structure where all loop cloning info is kept.
//
// Returns:
// true if expected performance gain from cloning is worth the potential
// size increase.
//
// Remarks:
// This is a simple-minded heuristic meant to avoid "runaway" cloning
// where large loops are cloned.
//
// We estimate the size cost of cloning by summing up the number of
// tree nodes in all statements in all blocks in the loop.
//
// This value is compared to a hard-coded threshold, and if bigger,
// then the method returns false.
//
bool Compiler::optShouldCloneLoop(FlowGraphNaturalLoop* loop, LoopCloneContext* context)
{
// See if loop size exceeds the limit.
//
const int sizeConfig = JitConfig.JitCloneLoopsSizeLimit();
unsigned const sizeLimit = (sizeConfig >= 0) ? (unsigned)sizeConfig : UINT_MAX;
unsigned size = 0;

BasicBlockVisit result = loop->VisitLoopBlocks([&](BasicBlock* block) {
assert(sizeLimit >= size);
unsigned const slack = sizeLimit - size;
unsigned blockSize = 0;
if (block->ComplexityExceeds(this, slack, &blockSize))
{
return BasicBlockVisit::Abort;
}

size += blockSize;
return BasicBlockVisit::Continue;
});

if (result == BasicBlockVisit::Abort)
{
JITDUMP("Loop cloning: rejecting loop " FMT_LP ": exceeds size limit %u\n", loop->GetIndex(), sizeLimit);
return false;
}

JITDUMP("Loop cloning: loop " FMT_LP ": size %u does not exceed size limit %u\n", loop->GetIndex(), size,
sizeLimit);

return true;
}

//----------------------------------------------------------------------------
// optIsLoopClonable: Determine whether this loop can be cloned.
//
Expand Down Expand Up @@ -3131,8 +3077,9 @@ PhaseStatus Compiler::optCloneLoops()
}
else
{
bool allTrue = false;
bool anyFalse = false;
bool allTrue = false;
bool anyFalse = false;
const int sizeLimit = JitConfig.JitCloneLoopsSizeLimit();
context.EvaluateConditions(loop->GetIndex(), &allTrue, &anyFalse DEBUGARG(verbose));
if (anyFalse)
{
Expand All @@ -3149,7 +3096,13 @@ PhaseStatus Compiler::optCloneLoops()
// No need to clone.
context.CancelLoopOptInfo(loop->GetIndex());
}
else if (!optShouldCloneLoop(loop, &context))
// This is a simple-minded heuristic meant to avoid "runaway" cloning
// where large loops are cloned.
// We estimate the size cost of cloning by summing up the number of
// tree nodes in all statements in all blocks in the loop.
// This value is compared to a hard-coded threshold, and if bigger,
// then the method returns false.
else if ((sizeLimit >= 0) && optLoopComplexityExceeds(loop, (unsigned)sizeLimit))
{
context.CancelLoopOptInfo(loop->GetIndex());
}
Expand Down
Loading
Loading