You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This issue body is AI-generated (GitHub Copilot CLI). The investigation, reduction, and analysis are checked by me.
Description
Math.Clamp(int, int, int) throws ArgumentException from JIT-optimized code when called with a min argument computed from (~c) * constant, where c is a Compare result ({0, 1}). The actual value of min is correct in the exception message (e.g. -672479), but the JIT's min > max check still fires even though the comparison is false under signed arithmetic.
Tier0/MinOpts works correctly. FullOpts, Tier1, and Tier1+PGO all fail.
Repro
usingSystem;usingSystem.Runtime.CompilerServices;publicclassProgram{privatestaticvolatileintP1=0;privatestaticvolatileintSink;[MethodImpl(MethodImplOptions.NoInlining)]publicstaticintBug(intp1){// c ∈ {0, 1} from a Compare. With P1 = 0, c = 0.intc=(p1<0)?1:0;// The OUTER Math.Clamp is required to trigger the bug.int_=Math.Clamp(c,0,p1);// v6 = (~0) * 672479 = -672479.intv6=(~c)*672479;// INNER Clamp: should clamp 0x5756E (357230) into [-672479, 100] → 100.returnMath.Clamp(0x0005756E,v6,100);}publicstaticintMain(){intr=0;for(inti=0;i<400;i++){try{Sink=Bug(P1);}catch{}}System.Threading.Thread.Sleep(150);for(inti=0;i<400;i++){try{Sink=Bug(P1);}catch{}}System.Threading.Thread.Sleep(200);try{r=Bug(P1);Console.WriteLine($"OK: 0x{r:X8}");return0;}catch(ArgumentExceptione){Console.WriteLine($"FAIL: {e.Message}");return1;}}}
Observed (checked CoreCLR at a8b2c92ce21, osx-arm64)
-672479 > 100 is false under signed arithmetic, so Math.Clamp should not throw. Note that the value -672479 in the exception message is correct — the JIT computed v6 correctly. The bug is in the min > max comparison itself.
Regression
11.0.100-preview.3.26207.106 (shipping): OK — does not throw.
The JIT appears to be doing an unsigned comparison for min > max when one operand has a range deduced from a (~Compare) * const chain.
c ∈ {0, 1} from the ternary
~c ∈ {-1, -2} signed, or {0xFFFFFFFF, 0xFFFFFFFE} unsigned
(~c) * 672479 is computed correctly as -672479 in twos-complement
But range analysis may treat the operand as unsigned, deducing v6 ∈ [huge positive, ...]
Then the constant-folded v6 > 100 check yields true under unsigned compare
That hypothesis fits all the variants I tried:
~c - 672478 → no bug (no multiplication)
(~c) * 672479 → bug
0xA42DF / ~c → bug (exception says min = 0; division produces a different range issue)
Notes
Triggered by ReifyCs (sibling repo), seed 22004304, profile stage2_intrinsic. The original program also exercised Stage 2 multi-function composition, but the bug reproduces in a single function with no helpers (shown above).
Both Math.Clamp calls are needed; removing the outer one suppresses the bug.
(~c) is required; substituting a literal -1 or (c - 1) does not trigger.
Note
This issue body is AI-generated (GitHub Copilot CLI). The investigation, reduction, and analysis are checked by me.
Description
Math.Clamp(int, int, int)throwsArgumentExceptionfrom JIT-optimized code when called with aminargument computed from(~c) * constant, wherecis aCompareresult ({0, 1}). The actual value ofminis correct in the exception message (e.g.-672479), but the JIT'smin > maxcheck still fires even though the comparison is false under signed arithmetic.Tier0/MinOpts works correctly. FullOpts, Tier1, and Tier1+PGO all fail.
Repro
Observed (checked CoreCLR at
a8b2c92ce21, osx-arm64)DOTNET_TieredCompilation=1 DOTNET_TC_CallCountThreshold=999999(Tier0/MinOpts)OK: 0x00000064DOTNET_TieredCompilation=0(FullOpts)FAIL: '-672479' cannot be greater than 100.DOTNET_TieredCompilation=1+ warmup (Tier1)FAIL: '-672479' cannot be greater than 100.DOTNET_TieredPGO=1(Tier1+PGO)FAIL: '-672479' cannot be greater than 100.-672479 > 100is false under signed arithmetic, soMath.Clampshould not throw. Note that the value-672479in the exception message is correct — the JIT computedv6correctly. The bug is in themin > maxcomparison itself.Regression
11.0.100-preview.3.26207.106(shipping): OK — does not throw.a8b2c92ce21, May 2026): FAIL.Same regression window as #129076 and #129099.
Hypothesis
The JIT appears to be doing an unsigned comparison for
min > maxwhen one operand has a range deduced from a(~Compare) * constchain.c ∈ {0, 1}from the ternary~c ∈ {-1, -2}signed, or{0xFFFFFFFF, 0xFFFFFFFE}unsigned(~c) * 672479is computed correctly as-672479in twos-complementv6 ∈ [huge positive, ...]v6 > 100check yields true under unsigned compareThat hypothesis fits all the variants I tried:
~c - 672478→ no bug (no multiplication)(~c) * 672479→ bug0xA42DF / ~c→ bug (exception saysmin = 0; division produces a different range issue)Notes
22004304, profilestage2_intrinsic. The original program also exercised Stage 2 multi-function composition, but the bug reproduces in a single function with no helpers (shown above).Math.Clampcalls are needed; removing the outer one suppresses the bug.(~c)is required; substituting a literal-1or(c - 1)does not trigger.