Skip to content

[JIT] Incorrect code generation for readonly struct copy constructor on arm64 #126414

@martinpotter

Description

@martinpotter

Description

On arm64 platforms (macOS arm64, Linux arm64, and Windows arm64 confirmed), the JIT generates incorrect code when copying a readonly struct via a field-by-field copy constructor inside a class constructor. The bug causes struct fields to read garbage values instead of the actual source values.

This bug causes silent data corruption in production code. It was discovered in a real-world application where test results became incorrect after upgrading to .NET 10.

Reproduction Steps

using System.Runtime.CompilerServices;

public readonly struct Endpoint
{
    public Endpoint(Endpoint e)
    {
        A = e.A;
        B = e.B;
        C = e.C;
    }

    public Endpoint(int a, int b, int c) { A = a; B = b; C = c; }

    public int A { get; }
    public int B { get; }
    public int C { get; }
}

public sealed class Range
{
    private readonly Endpoint m_start;
    private readonly Endpoint m_end;

    // Using AggressiveOptimization to force reproduction of the bug which would eventually occur when tiered compilation optimizes the constructor
    [MethodImpl(MethodImplOptions.AggressiveOptimization)]
    public Range(Endpoint start, Endpoint end)
    {
        m_start = new Endpoint(start);  // Copy via copy constructor
        m_end = new Endpoint(end);      // Copy via copy constructor
    }

    public int Length => m_end.A - m_start.A;
}

public class Program
{
    public static int Main()
    {
        const int expected = 5759;

        for (int i = 0; i < 10000; i++)
        {
            var r = new Range(new Endpoint(100, 0, 0), new Endpoint(100 + expected, 0, 0));

            if (r.Length != expected)
            {
                Console.WriteLine($"FAIL at iteration {i}: Length={r.Length} (expected {expected}), hex=0x{r.Length:X}");
                return 1;
            }
        }

        Console.WriteLine($"PASS: 10000 iterations, Length always = {expected}");
        return 0;
    }
}

Expected behavior

PASS: 10000 iterations, Length always = 5759

Actual behavior

FAIL at iteration 0: Length=-100 (expected 5759), hex=0xFFFFFF9C

The Length property returns a garbage value instead of 5759.

Regression?

This is a regression in .NET 10. The same code works correctly on .NET 8, .NET 9 and .NET 10 prior to RC1.

Known Workarounds

DOTNET_JitEnablePostorderLocalAssertionProp=0

Configuration

.NET version: .NET 10.0.5
OS: macOS 15 or 26
Architecture: ARM64

This impacts macOS, Linux and Windows ARM64 platforms: https://github.com/martinpotter/JitBugRepro/actions/runs/23853768346

Other information

This bug was introduced with bbe64d0. Prior to, or reverting, that commit, the bug is no longer reproducible.

Metadata

Metadata

Assignees

Labels

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

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions