Repository navigation
JIT: equality-guarded derived long comparison can silently use a truncated 32-bit value on x64 #133251
Description
Description
On .NET 11 x64, the JIT can silently miscompile a long comparison when:
- A local is derived from a method argument using addition or subtraction.
- An outer equality guard establishes an exact value for the argument.
- The derived local is compared with another
longconstant inside that guard.
The optimized method below returns 3, but the correct result is 1.
This reproduces with full optimization on .NET 11 preview 7 and a recent
dotnet/runtime main build. It does not reproduce with MinOpts, on .NET 10,
or on arm64 at the same runtime commit.
Reproduction
using System;
using System.Runtime.CompilerServices;
public static class Program
{
[MethodImpl(MethodImplOptions.NoInlining)]
private static long GetX() => 9223372036854775806L;
[MethodImpl(MethodImplOptions.NoInlining)]
private static int Case(long x)
{
int result = 0;
long y = x - 16;
if (x == 9223372036854775806L)
{
result |= 1;
if (y < -2)
{
result |= 2;
}
}
return result;
}
public static int Main()
{
int result = Case(GetX());
Console.WriteLine(result);
return result == 1 ? 100 : 1;
}
}Build the program for net11.0, then disable tiering so Case is compiled
with full optimization immediately:
dotnet build -c Release
$env:DOTNET_TieredCompilation = "0"
dotnet .\bin\Release\net11.0\Repro.dllActual result
3
The process exits with code 1.
Expected result
1
The process should exit with code 100.
x is 9223372036854775806, so y is 9223372036854775790.
Therefore, y < -2 is false and only the outer branch should set a bit.
Configuration comparison
| Runtime/configuration | Result |
|---|---|
| .NET 11 preview 7 x64, full optimization | 3 (incorrect) |
Recent dotnet/runtime main x64 Checked build, full optimization |
3 (incorrect) |
Same builds with DOTNET_JitMinOpts=1 |
1 (correct) |
| .NET 10.0.5 x64 | 1 (correct) |
| .NET 10.0.11 x64 | 1 (correct) |
| arm64 Checked build at the same main commit | 1 (correct) |
Tested framework version:
Microsoft.NETCore.App 11.0.0-preview.7.26381.103
The recent Checked build was from:
d2ea726dd3d8faad797755c9fbb1ff15226e25f0
Additional investigation
The original generated campaign found 15 mismatches in 2,500 trials.
A separate sweep then generated 300 independent cases of this form using
random (guard constant, additive constant, comparison constant) triples.
On x64, 106 of the 300 cases miscompiled. Every incorrect result matched the
prediction obtained by evaluating the inner comparison as:
(long)(int)y < comparisonConstant
In the minimal reproduction, the low 32 bits of y are 0xFFFFFFEE, or
-18 when interpreted as an int. Testing -18 < -2 explains the
incorrectly taken inner branch.
This does not prove where the truncation is introduced in the JIT, but all
106 observed failures matched this behavior. Alternative predictions in
which both operands were truncated or the addition itself was performed at
32-bit width did not match the failures.
The outer x == constant guard is required. Removing it and directly
comparing the derived long produced no mismatches in the corresponding
sweep.
The same 300-case program produced zero mismatches on arm64 at commit
d2ea726dd3d8faad797755c9fbb1ff15226e25f0, suggesting the defect is
x64-specific.
Impact
This is a silent wrong-code regression in ordinary integer control flow. The
triggering source pattern is not intrinsically unusual: test an argument for
an exact value, derive a related long, and compare that value inside the
guard.
Note
This issue draft was generated with GitHub Copilot assistance.
Activity
dotnet-policy-service commentedon Sep 4, 2026
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.
AndyAyersMS commentedon Sep 4, 2026
Fixed by #132849