') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); JIT: eliminate bounds checks for "i != arr.Length" loops by AndyAyersMS · Pull Request #129199 · dotnet/runtime · GitHub
Skip to content

JIT: eliminate bounds checks for "i != arr.Length" loops - #129199

Closed
AndyAyersMS wants to merge 2 commits into
dotnet:mainfrom
AndyAyersMS:fix-84697-ne-bce
Closed

JIT: eliminate bounds checks for "i != arr.Length" loops#129199
AndyAyersMS wants to merge 2 commits into
dotnet:mainfrom
AndyAyersMS:fix-84697-ne-bce

Conversation

@AndyAyersMS

Copy link
Copy Markdown
Member

Loops with i != end previously kept their per-iteration bounds checks while the equivalent < / > forms did not: loop cloning bailed on GT_NE, and RangeCheck couldn't refine the IV's range from a "back-edge != end" assertion.

Recognize GT_NE with stride exactly +/-1 as an increasing or decreasing loop in NaturalLoopIterInfo, extend optDeriveLoopCloningConditions to emit the right per-access and zero-trip conditions for GT_NE, and add a new "init RELOP end" cloning condition so that a misordered init (which for GT_NE would wrap the IV through the type rather than exit at the first test) falls back to the slow path. Builds on #129176 which fixes the latent decreasing-loop soundness gap.

Also extend optCreateJTrueBoundsAssertion to create CompareCheckedBound assertions for EQ/NE against a checked bound, and teach RangeCheck::MergeAssertion to tighten ranges using X != bound / X == bound. This catches NE-loop BCE in cases that fall outside loop cloning (e.g. loops with side-effecting calls in the body). For equality assertions where the non-bound side is a constant, defer to the existing LCLVAR-based equality assertion path to preserve downstream consumers that rely on it (e.g. proving "len > 0" after a "len != 0" check).

Codegen for for (int i = 0; i != src.Length; i++) sum += src[i] drops from 64 to 30 bytes.

Fixes#84697.

CopilotAI review requested due to automatic review settings June 9, 2026 23:19
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jun 9, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Updates CoreCLR JIT loop analysis, loop cloning, assertion propagation, and range analysis to enable bounds-check elimination for i != end loop forms (with strict stride constraints), aligning them more closely with equivalent < / > loops and improving both optimization and soundness.

Changes:

  • Teach NaturalLoopIterInfo to classify GT_NE loop tests as increasing/decreasing only when the IV step is exactly +1/-1.
  • Extend loop cloning (optDeriveLoopCloningConditions) to accept GT_NE tests and add the necessary entry/zero-trip and per-access guards to keep the fast clone sound.
  • Extend assertion creation + RangeCheck merging so ==/!= assertions against checked bounds can tighten IV ranges and enable BCE even when loop cloning can’t apply.
  • Add a JIT regression test covering both the optimization shape and key soundness cases.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
src/tests/JIT/Regression/JitBlue/Runtime_84697/Runtime_84697.csAdds regression coverage for i != Length loops across arrays/strings/spans and includes soundness checks for misordered init and decreasing-loop two-array scenarios.
src/coreclr/jit/flowgraph.cppExtends increasing/decreasing loop classification to recognize GT_NE only for stride ±1 to avoid non-terminating/wrapping cases.
src/coreclr/jit/loopcloning.cppEnables loop cloning reasoning for GT_NE, adds ordered entry guards and an init-vs-limit guard to prevent wraparound on the fast path, and derives per-access conditions for GT_NE.
src/coreclr/jit/assertionprop.cppAllows optCreateJTrueBoundsAssertion to create checked-bound equality assertions (EQ/NE) where useful for RangeCheck while preserving existing constant-equality assertion behavior.
src/coreclr/jit/rangecheck.cppTeaches RangeCheck to tighten ranges from X == bound / X != bound assertions where bound is a checked bound, enabling BCE in non-cloneable cases.

Comment on lines +1440 to +1443
// GT_NE loop test (stride = +/-1; see IsIncreasing/DecreasingLoop):
// For increasing: visited indices are [init..end-1] => guard end <= arrLen (same as LT)
// For decreasing: visited indices are [end+1..init] => guard end < arrLen (same as GT)
//
AndyAyersMSand others added 2 commits June 10, 2026 06:42
Loops with `i != end` previously kept their per-iteration bounds checks
while the equivalent `<` / `>` forms did not: loop cloning bailed on
GT_NE, and RangeCheck couldn't refine the IV's range from a "back-edge
!= end" assertion.
Recognize GT_NE with stride exactly +/-1 as an increasing or decreasing
loop in NaturalLoopIterInfo, extend optDeriveLoopCloningConditions to
emit the right per-access and zero-trip conditions for GT_NE, and add a
new "init RELOP end" cloning condition so that a misordered init (which
for GT_NE would wrap the IV through the type rather than exit at the
first test) falls back to the slow path. Builds on dotnet#129176 which fixes
the latent decreasing-loop soundness gap.
Also extend optCreateJTrueBoundsAssertion to create CompareCheckedBound
assertions for EQ/NE against a checked bound, and teach
RangeCheck::MergeAssertion to tighten ranges using `X != bound` /
`X == bound`. This catches NE-loop BCE in cases that fall outside loop
cloning (e.g. loops with side-effecting calls in the body). For
equality assertions where the non-bound side is a constant, defer to
the existing LCLVAR-based equality assertion path to preserve
downstream consumers that rely on it (e.g. proving "len > 0" after a
"len != 0" check).
Codegen for `for (int i = 0; i != src.Length; i++) sum += src[i]` drops
from 64 to 30 bytes.
Fixesdotnet#84697.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Apply jit-format fixes from CI and clarify the GT_NE comment in
optDeriveLoopCloningConditions to call out that the decreasing-loop
soundness relies on `ident` being the init value (per Copilot bot
review of dotnet#129199).
// [lo, bound] -> [lo, bound - 1]
// [bound, hi] -> [bound + 1, hi]
if (pRange->UpperLimit().IsBinOpArray() && (pRange->UpperLimit().vn == boundVN) &&
(pRange->UpperLimit().GetConstant() == 0))

@EgorBoEgorBoJun 10, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

presumably we don't have to check pRange->UpperLimit().GetConstant() == 0 ?
e.g. [lo, bound - 1] -> [lo, bound - 2]
ah, actually nvm, it's not correct

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

Going to split this in two..

#129268

@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 11, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Loop condition i != T.Length bounds check not eliminated

3 participants

@AndyAyersMS@EgorBo