Skip to content

ARM64 JIT: k % 2 reads stale flags after an allocation (cneg emitted before its cmp), wrong results on .NET 10 #135521

Description

@foffer

Description

On osx-arm64, k % 2 for a non-negative k returns -1 when the result is stored into a newly allocated object right after another field. The JIT emits the sign fix-up cneg ..., lt before the cmp that should set its flags. cneg then reads whatever flags the allocation helper left behind.

Reproduction Steps

Repro.csproj:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFrameworks>net10.0;net9.0</TargetFrameworks>
  </PropertyGroup>
</Project>

Program.cs:

using System;
using System.Runtime.InteropServices;

sealed class Item { public int A, B; }

static class Program
{
    static Item[] Fill(int n, int a)
    {
        var items = new Item[n];
        for (int k = 0; k < n; k++)
            items[k] = new Item { A = a, B = k % 2 };   // k >= 0, so B must be 0 or 1
        return items;
    }

    static int Main()
    {
        int wrong = 0, total = 0;
        for (int i = 0; i < 5000; i++)
            foreach (var item in Fill(11, i))
            {
                total++;
                if (item.B != 0 && item.B != 1) wrong++;
            }
        Console.WriteLine($"{Environment.Version} {RuntimeInformation.ProcessArchitecture}: {wrong} of {total} wrong");
        return wrong == 0 ? 100 : 1;
    }
}

Run dotnet run -c Release -f net10.0. The bug shows with default settings, and on almost every odd k with DOTNET_TieredCompilation=0.

Expected behavior

0 of 55000 wrong

Actual behavior

Runtime (osx-arm64) Default DOTNET_TieredCompilation=0
10.0.12 21067 of 55000 wrong 24902 of 55000 wrong
10.0.11 21067 24900
10.0.0 21067 24902
9.0.20 0 0

The wrong values are all -1 (odd k comes out negated). The count varies slightly between runs.

DOTNET_JitDisasm=Fill for the loop body on 10.0.12 (FullOpts):

G_M000_IG04:
            sub     x0, x21, #128
            bl      CORINFO_HELP_NEWSFAST
            and     w14, w23, #1
            cneg    w14, w14, lt          ; reads flags left by the helper call
            stp     w20, w14, [x0, #0x08]
            cmp     w23, #0               ; the compare cneg needed, emitted after it (and dead here)
            add     x14, x24, x23,  LSL #3
            mov     x15, x0
            bl      CORINFO_HELP_ASSIGN_REF
            add     w23, w23, #1
            cmp     w23, w19
            blt     G_M000_IG04

Regression?

The wrong results are new in .NET 10 (10.0.0 through 10.0.12). The misordered code is not: 9.0.20 emits the same and / cneg / stp / cmp sequence. It only gives the right answer there because its allocation helper happens to leave flags for which lt is false. So the codegen bug looks latent, and a change to the allocation helper in .NET 10 exposed it.

Known Workarounds

  • Swap the two assignments (new Item { B = k % 2, A = a }): the result is correct.
  • Compute the remainder outside the initializer, or use k & 1 when k is known to be non-negative.

Configuration

  • .NET SDK 10.0.401, runtime 10.0.12 (also 10.0.11 and 10.0.0 from the official osx-arm64 tarballs)
  • macOS 27.0.1 (26A434), Apple silicon (M-series), osx-arm64
  • Not reproduced on linux-x64 (10.0.11)
  • Not tried on linux-arm64 or win-arm64

Other information

  • It needs both stores into the new object, in that order. Without the A = a store, or with no allocation in the loop (storing into two int[]s), the result is correct. That suggests the store-pair (stp) combine is moving the cmp past the cneg.
  • First seen in real code where a lambda captured locals of the method. The lambda isn't needed to reproduce it.

Filed by an AI agent (Claude Code) on behalf of Christoffer Buusmann.

Activity

  1. added
    area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
    on Oct 9, 2026
  2. dotnet-policy-service commented on Oct 9, 2026

    @dotnet-policy-service
    Contributor

    Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
    See info in area-owners.md if you want to be subscribed.

  3. foffer commented on Oct 9, 2026

    @foffer
    Author

    I hope the above report is valuable and will help. Please let me know if you need anything further from me

  4. added this to the 12.0.0 milestone on Oct 9, 2026
  5. removed
    untriagedNew issue has not been triaged by the area owner
    on Oct 9, 2026
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