Repository navigation
Optimize constant-range patterns whose Boolean result is consumed by a switch expression #135630
Description
Description
Constant-range patterns can produce less efficient native code than an equivalent subtraction-based helper when their Boolean result is consumed by a switch expression.
This is related to:
- Reddit: IL optimization on ranges not applied.
- JIT: Why are ranche checks not optimized #135418, fixed by JIT: Fold unsigned constant range tests #135421, which addresses the standalone
uintrange-check example.
The remaining cases in the original Reddit parser were reproduced with a JIT containing that fix:
public static string? Validate(string iban, string pattern)
{
for (int i = 0; i < iban.Length; i++)
{
uint c = iban[i];
bool match = pattern[i] switch
{
'n' => c is >= '0' and <= '9',
'a' => c is >= 'A' and <= 'Z',
'c' => c is >= '0' and <= '9' or >= 'A' and <= 'Z',
var t => t == c,
};
if (!match)
{
return null;
}
}
return iban;
}This issue concerns the single-range expressions in the 'n' and 'a' arms, not optimization of the two disjoint ranges in the 'c' arm.
In the 'c' arm, the fix optimizes the digit range, but the letter range still uses separate comparisons because its lower-bound failure proceeds to checking digits while its upper-bound failure rejects the character.
An equivalent helper for these constant bounds is:
private static bool IsBetween(uint value, uint lower, uint upper)
=> unchecked(value - lower) <= upper - lower;Replacing the 'n' and 'a' arm expressions with calls to this helper allows each range check to use a subtraction and one unsigned comparison. Where profitable, the relational patterns should produce similarly efficient native code without requiring manual arithmetic.
Configuration
- Windows 11, x64.
- Application compiled in Release with SDK
11.0.100-rc.1.26425.128, targetingnet11.0. - Executed on an isolated main-line runtime pack,
12.0.0-alpha.1.26509.106(dotnet/dotnet commit4fd6fbb479abbacd43b32fdc0ef6f68414cc028d), with its JIT replaced by the Release x64 JIT from #135421's CI build.
Regression?
Not known to be a regression.
Data
With the fixed JIT:
| Case | Observed native code |
|---|---|
Standalone bool IsDigit(uint c) => c is >= '0' and <= '9' |
Subtraction and one unsigned comparison |
Parser's 'n' arm |
Separate comparisons against 48 and 57 |
Parser's 'a' arm |
Separate comparisons against 65 and 90 |
Parser's 'c' digit range |
Subtraction and comparison against 9 |
Parser's 'c' letter range |
Separate comparisons |
For example, the numeric arm in warmed-up Tier1 code still contains:
cmp r10d, 48
jb BelowRange
cmp r10d, 57
setbe r11b
movzx r11, r11bThe standalone digit predicate instead contains:
sub ecx, 48
cmp ecx, 9
setbe al
movzx rax, al
retAnalysis
The IL produced by Roslyn differs in how the Boolean result is consumed.
For the 'n' arm, the captured IL has the following shape. Labels and local names below are symbolic, and unrelated switch arms are omitted:
ldloc c
ldc.i4.s 48
blt.un.s BelowRange
ldloc c
ldc.i4.s 57
cgt.un
ldc.i4.0
ceq
br.s StoreResult
BelowRange:
ldc.i4.0
StoreResult:
stloc match
br.s TestResult
// Other switch arms also assign match.
TestResult:
ldloc match
brtrue.s Continue
ldnull
retThe lower-bound comparison branches. The upper-bound comparison produces a Boolean value, which is stored as the switch result. A later instruction branches on that stored result. The 'a' arm has the same structure with bounds 65 and 90.
By comparison, the standalone predicate addressed by #135418:
public static bool IsDigit(uint c)
=> c is >= '0' and <= '9';has this IL:
ldarg.0
ldc.i4.s 48
blt.un.s ReturnFalse
ldarg.0
ldc.i4.s 57
cgt.un
ldc.i4.0
ceq
ret
ReturnFalse:
ldc.i4.0
retBoth forms contain a branch for the lower bound and a value-producing comparison for the upper bound. The fixed example returns the Boolean directly; the parser stores it as a switch-expression result and tests it afterward.
Note
This issue was drafted and its reproduction investigated with GitHub Copilot.
Activity
dotnet-policy-service commentedon Oct 12, 2026
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.