JIT: eliminate bounds checks for "i != arr.Length" loops#129199
Draft
AndyAyersMS wants to merge 1 commit into
Draft
JIT: eliminate bounds checks for "i != arr.Length" loops#129199AndyAyersMS wants to merge 1 commit into
AndyAyersMS wants to merge 1 commit into
Conversation
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. Fixes dotnet#84697. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
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
NaturalLoopIterInfoto classifyGT_NEloop tests as increasing/decreasing only when the IV step is exactly+1/-1. - Extend loop cloning (
optDeriveLoopCloningConditions) to acceptGT_NEtests 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
| File | Description |
|---|---|
| src/tests/JIT/Regression/JitBlue/Runtime_84697/Runtime_84697.cs | Adds 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.cpp | Extends increasing/decreasing loop classification to recognize GT_NE only for stride ±1 to avoid non-terminating/wrapping cases. |
| src/coreclr/jit/loopcloning.cpp | Enables 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.cpp | Allows optCreateJTrueBoundsAssertion to create checked-bound equality assertions (EQ/NE) where useful for RangeCheck while preserving existing constant-equality assertion behavior. |
| src/coreclr/jit/rangecheck.cpp | Teaches 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) | ||
| // |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Loops with
i != endpreviously 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.