Skip to content

Share modular range arithmetic across comparison optimizations - #136926

Open
NinoFloris wants to merge 2 commits into
dotnet:mainfrom
NinoFloris:jit/modular-range-conversion
Open

NinoFloris wants to merge 2 commits into
dotnet:mainfrom
NinoFloris:jit/modular-range-conversion

Conversation

@NinoFloris

@NinoFloris NinoFloris commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Adds ModularRange, an interval on the ring of integers modulo 2^n. It replaces the existing range arithmetic in RBO, and optimize bools. A signed comparison is the unsigned comparison of the value with the sign bit flipped. Either produces the same kind of interval. A comparison pair becomes one intersection instead of per-signedness case analysis, and wraparound is a property of the interval instead of a guard.

For example x >= 10 && x <= 100 is [10, 100], x != 5 is [6, 4] wrapping through zero, and intersecting [6, 4] with [7, 5] gives [7, 4], the values other than 5 and 6.

The representation follows the replaced code. It works in target_size_t, so the 32-bit JIT does no 64-bit arithmetic for it.

It is intended as a code cleanup and should produce no diffs. Each consumer keeps the shapes it accepted before:

  • optimize bools still requires two relational tests, no equalities, of the same signedness on a plain local that enclose at least two nonnegative values from both sides, or x >= 0 paired with a never-negative bound.
  • RBO still answers the same implication questions over the same constant tests.

Prospective

The expanded functionality for optimize bools (~30 lines of gates removed and a few lines added) can then be handled in a follow-up to enable:

  • equality tests, x != a && x != a + 1 and x > a && x == a + 1
  • negative bounds, lo <= x <= hi with negative lo
  • overlapping ranges, x > 10 && x > 100 to x > 100
  • single values, x >= 10 && x <= 10 to x == 10, and constant results, x >= 20 && x < 10 to false
  • offset bases, tests on x + c and x - c rather than a plain local
  • mixed signedness, a signed test paired with an unsigned one
  • LCL_FLD as the second operand
  • symbolic pairs beyond a literal x >= 0, such as x > -1 && x < length or x < 0 && x < length

The follow-up also resolves the TODO whose code this PR replaces:

if ((*pRangeStart >= *pRangeEnd) || (*pRangeStart < 0) || (*pRangeEnd < 0) || !FitsIn(type, *pRangeStart) ||
!FitsIn(type, *pRangeEnd))
{
// TODO: If ranges don't intersect we might be able to fold the condition to true/false.
// Also, check again if any of the ranges are negative (in case of overflow after normalization)
// and fits into the given type.
return false;
}

I ran into these shortcomings trying to unblock #119689.
This is the generalized form of what the experiment in #136739 needed.

Notes

Range check is not converted here. Its constant arithmetic almost fits, but two of its contracts complicate it: checked arithmetic reads a known result as proof that the operation cannot overflow, so a wrapped sum must stay unknown, and RangeOps keeps a known limit when only the other one overflows. A ring interval cannot express either, and adding those checks basically comes down to the current code.

More sharing may be possible, but I don't expect these to be zero-diff changes:

  • fgOptimizeRelationalComparisonWithFullRangeConst in morph, 108 lines.
  • fgOptimizeRelationalComparisonWithConst, 97 lines. Though it seems more geared towards optimal codegen, can make an attempt.

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.
@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 3, 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 3, 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-conversion branch from 5767fef to 283ec3e Compare November 3, 2026 20:45
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.
@NinoFloris

Copy link
Copy Markdown
Contributor Author

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