Skip to content

Faulty codegen SkipLocalsInit Nullable<T> default value arm64 #113658

@NinoFloris

Description

@NinoFloris

Description

A user of Npgsql ran into garbled nullable values on his M2.

The full context can be found here npgsql/npgsql#6058 (comment)

Reproduction Steps

using System;
using System.Runtime.CompilerServices;

// Necessary
[module: SkipLocalsInit]

static class Program
{
    public static void Main()
    {
        var i = 0;
        // Let the JIT tier up.
        while (true)
        {
            var data = FaultyDefaultNullable<long?>();
            var d = data.GetValueOrDefault();
            // We need to dirty the stack and make sure this won't be optimized out by doing GC.Keepalive.
            var iObject = (object)i;
            var dataObject = (object?)data;
            var dObject = (object?)d;
            var hasValueObject = (object?)data.HasValue;
            // Alternatively
            // Console.WriteLine("{0}: `{1}` / {2}, `{3}`", i, data, d, data.HasValue);

            if (d != 0)
            {
                Console.WriteLine($"Unexpected value: {d} / {d:X} / {d:b8}");
                throw new Exception("We are here");
            }

            GC.KeepAlive(iObject);
            GC.KeepAlive(dataObject);
            GC.KeepAlive(dObject);
            GC.KeepAlive(hasValueObject);

            i++;
        }
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    static T FaultyDefaultNullable<T>()
    {
        // When T is a Nullable<T> (and only in that case), we support returning null
        if (default(T) is null && typeof(T).IsValueType)
            return default!;

        throw new InvalidOperationException("Not nullable");
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    static T OkDefaultNullable<T>() => default!;
}

Expected behavior

OkDefaultNullable

; Assembly listing for method Program:OkDefaultNullable[System.Nullable`1[long]]():System.Nullable`1[long] (Tier1)
; Emitting BLENDED_CODE for generic ARM64 - Apple
; Tier1 code
; optimized code
; optimized using Synthesized PGO
; fp based frame
; partially interruptible
; with Synthesized PGO: fgCalledCount is 100
; No PGO data

G_M000_IG01:                ;; offset=0x0000
            stp     fp, lr, [sp, #-0x10]!
            mov     fp, sp
 
G_M000_IG02:                ;; offset=0x0008
            mov     w0, wzr
            mov     x1, xzr
 
G_M000_IG03:                ;; offset=0x0010
            ldp     fp, lr, [sp], #0x10
            ret     lr
 
; Total bytes of code 24

Actual behavior

FaultyDefaultNullable

; Assembly listing for method Program:FaultyDefaultNullable[System.Nullable`1[long]]():System.Nullable`1[long] (Tier1)
; Emitting BLENDED_CODE for generic ARM64 - Apple
; Tier1 code
; optimized code
; optimized using Synthesized PGO
; fp based frame
; partially interruptible
; with Synthesized PGO: fgCalledCount is 100
; No PGO data

G_M000_IG01:                ;; offset=0x0000
            stp     fp, lr, [sp, #-0x20]!
            mov     fp, sp
 
G_M000_IG02:                ;; offset=0x0008
            mov     w0, wzr
            ldr     x1, [fp, #0x18]
 
G_M000_IG03:                ;; offset=0x0010
            ldp     fp, lr, [sp], #0x20
            ret     lr
 
; Total bytes of code 24

Regression?

This code works in .NET 8.0, starts failing in 9.0 and onwards (also tested 10.0 preview 1)

Known Workarounds

Remove SkipLocalsInit from Npgsql

Configuration

No response

Other information

No response

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