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
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
Actual
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).
The VN relop based copy assertion prop path in
optAssertionProp_RelOpretargetsop1's lclNum/ssaNum toop2's local but leavesop1's node type alone, producingNE(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
bis44,lenis300; theb != lenbranch is taken, so(b == len)is staticallyfalseandrmust be0.Expected
Actual
Disasm (
DOTNET_JitDisasm=Test),V03isint loc0:Notes
NE(LCL_VAR ubyte V03, LCL_VAR int V03)—GenTree::Comparewon't fold identical operands whose types differ, so the node reaches codegen.op1's type (e.g.op1->ChangeType(op2->TypeGet())), or bail out when the types differ.try/catchand second store tolenonly forceV03to bedo-not-enregso the narrow load becomes a visible memory access; the malformed IR is produced without them too.main-only (theoptAssertionProp_RelOpcode is old, so some upstream change now lets the small-typedLCL_VARreach assertion prop uncast).