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

No one assigned

    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