Skip to content

Modular range folds - #136994

Draft
NinoFloris wants to merge 9 commits into
dotnet:mainfrom
NinoFloris:jit/modular-range-folds
Draft

NinoFloris wants to merge 9 commits into
dotnet:mainfrom
NinoFloris:jit/modular-range-folds

Conversation

@NinoFloris

Copy link
Copy Markdown
Contributor

Follow-up to, and based on, #136926. Only the last 4 commits are relevant: 18 lines added and 31 removed.

Move the modular interval arithmetic of redundant branch opts into
modularrange.h, a ring interval that models constant comparisons of
either signedness, with symbolic upper bounds by identity. The
redundant branch optimizer is converted to construct and intersect
these intervals; it produces no code differences.
Replace GetIntersection, FoldRangeTests and FoldNeverNegativeRangeTest
with a single FoldComparisons that intersects the two tests as modular
intervals and emits the comparison form that matches the result.

The conversion keeps the previously accepted shapes only: two tests of
the same signedness on a plain local that enclose a range of at least
two non-negative values, or "X >= 0" paired with a never-negative
symbolic bound. It produces no code differences. The shapes the
interval representation makes available, equality tests, negative
bounds, offset bases and single values, are enabled separately.
…ools

Accept equality tests, offset bases, field locals, mixed signedness, overlapping ranges and single values in the range test fold. The dominant new shape is "X != a && X != a + 1", which becomes a single unsigned comparison.
The previous fold rejected negative constants. On the modular interval they are ordinary points, so "lo <= X <= hi" with a negative lower bound becomes "(X - lo) u<= hi - lo" like any other range.
SUB/CMP needs as many arithmetic instructions as CMP/CCMP. Decline the offset form of a fold when its constants cost more than the two original bounds.
@github-actions github-actions Bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Nov 5, 2026
@dotnet-policy-service dotnet-policy-service Bot added the community-contribution Indicates that the PR has been added by a community member label Nov 5, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 6 pipeline(s).
9 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

@NinoFloris
NinoFloris force-pushed the jit/modular-range-folds branch from 0aaaeab to 166e5b7 Compare November 5, 2026 04:13
The general case of the range fold lowered an interval as "(X - lower) u<= width" whatever its position on the ring. An interval that covers more than half of the ring is the complement of a small one, and one that ends near the top of the signed range is the signed test of its offset value, so the literal encoding materialized constants like 0xFFFFFFFD for "x != a && x != a + 1" and 0x7FFFFFF9 for "(uint)x < 257 || (x - 257) < 6". On arm64 the wrapped bound still encodes in a cmn while the compare stands alone, but once the compare joins a ccmp chain its 5-bit immediate forces a movn, which is where the folds grew code.

An interval has four encodings: the unsigned test of itself or of its complement, and the signed test of either, which starts the interval at the minimum value. They differ only in their constants. Price each with the cost model and take the cheapest, with the smallest constants on a tie, since those fit the most immediate forms. The two shapes above become "(x - a) u> 1" and "(x - 257) <s 6".
…ptors

The value number store and the assertion table are released after the SSA based phases, so a later phase that asks GenTree::IsNeverNegative about a local only sees the flag on its descriptor, which import and promotion set for span lengths and little else. A length read into a plain local is never negative by value number and nothing after value numbering could tell.

Add a phase at the end of the SSA section that writes such facts onto the descriptors. The first is never-negative: a local in SSA whose every definition is never negative by value number gets the flag, where the implicit initial definition counts as zero when the method zero-initializes its locals and disqualifies the local otherwise. Optimize bools, if conversion, lowering and codegen then see array and span lengths, checked bounds and the temps CSE made for them the way the value number phases did.
A value that is never negative takes only the nonnegative half of the ring, but the range fold intersected its tests over the whole ring, so a pair like "(uint)length < 257 || (length - 257) < 6" on an array length produced an interval straddling the sign boundary and a signed test with an offset. Intersect the first test's interval with the nonnegative half when the value is never negative; the pair above is then "length < 263" with no arithmetic.
@NinoFloris
NinoFloris force-pushed the jit/modular-range-folds branch from 166e5b7 to 61d264e Compare November 5, 2026 04:24

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant