JIT: (bug) assertion prop retargets a small-typed `LCL_VAR` to an `int` local, emitting a truncated compare (wrong result)
- Dominant language
- C#
- Stars
- 18.3k
- Forks
- 5.6k
- PR merge metrics
- PR metrics pending
Description
The VN relop based copy assertion prop path in `optAssertionProp_RelOp` retargets `op1`'s lclNum/ssaNum to `op2`'s local but leaves `op1`'s node type alone, producing `NE(LCL_VAR ubyte V03, LCL_VAR int V03)`. The mismatched types stop the self-compare from folding, and codegen emits a narrow load for one side, so the compare yields the wrong answer.
### Minimal Repro
```csharp
using System;
using System.Runtime.CompilerServices;
public static class Program
{
[MethodImpl(MethodImplOptions.NoInlining)]
static void Consume(int x) { }
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
public static int Test(byte b, int[] arr, int x)
{
b = (byte)x;
int len = arr.Length;
int r = 0;
try
{
if (b != len)
{
Consume(b);
Consume(len);
r = (b == len) ? 1 : 0;
}
else
{
r = 3;
}
if (x == 12345)
{
len = 7;
}
}
catch
{
r = len;
}
return r;
}
public static int Main()
{
Console.WriteLine(Test(0, new int[300], 44));
return 100;
}
}
```
`b` is `44`, `len` is `300`; the `b != len` branch is taken, so `(b == len)` is statically `false` and `r` must be `0`.
### Expected
```
0
```
### Actual
```
1
```
Disasm (`DOTNET_JitDisasm=Test`), `V03` is `int loc0`:
```
movzx rax, byte ptr [rbp-0x0C] ; <=== narrow load of the TYP_INT local V03 (300 -> 44)
mov ecx, dword ptr [rbp-0x0C] ; <=== full load of the same local (300)
cmp eax, ecx
setne al
```
### Notes
- Malformed tree after assertion prop: `NE(LCL_VAR ubyte V03, LCL_VAR int V03)` — `GenTree::Compare` won't fold identical operands whose types differ, so the node reaches codegen.
- Fix direction: in the local/local copy path also fix up `op1`'s type (e.g. `op1->ChangeType(op2->TypeGet())`), or bail out when the types differ.
- The `try`/`catch` and second store to `len` only force `V03` to be `do-not-enreg` so the narrow load becomes a visible memory access; the malformed IR is produced without them too.
- Reproduces in Release and Checked corerun, with and without tiering; no assert fires. Does not reproduce on .NET 10.0.12, so this looks `main`-only (the `optAssertionProp_RelOp` code is old, so some upstream change now lets the small-typed `LCL_VAR` reach assertion prop uncast).
Contributor guide
Research direction
Start at optAssertionProp_RelOp and reproduce the issue with the provided Test method, checking the malformed NE(LCL_VAR ubyte V03, LCL_VAR int V03) tree. Inspect the local/local copy assertion path and verify the fix prevents the narrow load and produces the expected output 0 in Release and Checked corerun.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100