Skip to content

JIT: (bug) assertion prop retargets a small-typed LCL_VAR to an int local, emitting a truncated compare (wrong result) #133859

Description

@EgorBo

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

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).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions