From bd8ed680e8e28f14f81f226e3268ccca007ad9d1 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Thu, 21 May 2026 13:01:33 -0700 Subject: [PATCH 1/2] JIT: skip loop inversion for loops that are already bottom-tested In optTryInvertWhileLoop, bail out when the loop's back-edge already controls loop continuation. Two shapes count: * a BBJ_COND latch that exits the loop * a BBJ_ALWAYS latch (installed by optCanonicalizeBackedges for loops that originally had multiple backedges) with an in-loop predecessor that is a BBJ_COND exiting the loop In either case the candidate condBlock is some other early-exit test; inverting it just adds a redundant test above the loop and splits the body into two branches per iteration where there was one. The existing 'already inverted' checks only catch single-block self- loops and loops whose latch test was recognized as the IV test by AnalyzeIteration. Loops with an unrecognized bottom test plus a separate early-exit BBJ_COND in the body fell through and got inverted on top of an already bottom-tested shape. PR #116104 made this more visible by always computing loop iteration estimates from profile data, which pushes more loops past the size budget threshold where inversion is attempted. Fixes #118494. --- src/coreclr/jit/optimizer.cpp | 47 +++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/coreclr/jit/optimizer.cpp b/src/coreclr/jit/optimizer.cpp index 396f89035d57cb..d0c972584f64a5 100644 --- a/src/coreclr/jit/optimizer.cpp +++ b/src/coreclr/jit/optimizer.cpp @@ -1850,6 +1850,53 @@ bool Compiler::optTryInvertWhileLoop(FlowGraphNaturalLoop* loop) return false; } + // If the loop is already bottom-tested (has a BBJ_COND latch that exits the loop), + // there is no need to invert. Also handle the canonical multi-backedge case, where + // optCanonicalizeBackedges has installed a BBJ_ALWAYS latch whose in-loop predecessors + // are the original bottom tests. + // + auto isExitingCondLatch = [&](BasicBlock* block) -> bool { + if ((block == condBlock) || !block->KindIs(BBJ_COND)) + { + return false; + } + for (FlowEdge* const exitEdge : loop->ExitEdges()) + { + if (exitEdge->getSourceBlock() == block) + { + return true; + } + } + return false; + }; + + for (FlowEdge* const backEdge : loop->BackEdges()) + { + BasicBlock* const latch = backEdge->getSourceBlock(); + + if (isExitingCondLatch(latch)) + { + JITDUMP("No loop-inversion for " FMT_LP "; latch " FMT_BB " already makes it bottom-tested\n", + loop->GetIndex(), latch->bbNum); + return false; + } + + if (latch->KindIs(BBJ_ALWAYS) && latch->isEmpty()) + { + for (FlowEdge* const predEdge : latch->PredEdges()) + { + BasicBlock* const pred = predEdge->getSourceBlock(); + if (loop->ContainsBlock(pred) && isExitingCondLatch(pred)) + { + JITDUMP("No loop-inversion for " FMT_LP "; predecessor " FMT_BB " of canonical latch " FMT_BB + " already makes it bottom-tested\n", + loop->GetIndex(), pred->bbNum, latch->bbNum); + return false; + } + } + } + } + JITDUMP("Condition in block " FMT_BB " of loop " FMT_LP " is a candidate for duplication to invert the loop\n", condBlock->bbNum, loop->GetIndex()); From c2d855e7d96141ffe8afa0b73b15473e7747c8af Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Fri, 22 May 2026 11:30:47 -0700 Subject: [PATCH 2/2] JIT: remove now-redundant IV-tested check Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/jit/optimizer.cpp | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/coreclr/jit/optimizer.cpp b/src/coreclr/jit/optimizer.cpp index d0c972584f64a5..5f4d371268bd9a 100644 --- a/src/coreclr/jit/optimizer.cpp +++ b/src/coreclr/jit/optimizer.cpp @@ -1840,15 +1840,8 @@ bool Compiler::optTryInvertWhileLoop(FlowGraphNaturalLoop* loop) // There may be multiple exits, and one of the other exits may also be a // latch. That latch could be preferable to leave (for example because it - // is an IV test). - NaturalLoopIterInfo iterInfo; - if (loop->AnalyzeIteration(&iterInfo) && - (iterInfo.TestBlock->TrueTargetIs(loop->GetHeader()) != iterInfo.TestBlock->FalseTargetIs(loop->GetHeader()))) - { - // Test block is both a latch and exit, so the loop is already inverted in a preferable way. - JITDUMP("No loop-inversion for " FMT_LP " since it is already inverted (with an IV test)\n", loop->GetIndex()); - return false; - } + // is an IV test). The general BBJ_COND-latch-that-exits check below + // subsumes the IV-test case. // If the loop is already bottom-tested (has a BBJ_COND latch that exits the loop), // there is no need to invert. Also handle the canonical multi-backedge case, where