Skip to content

NativeAOT arm64: (int, int) value-tuple parameter used as a 64-bit array index without zero-extension, so the first store writes out of bounds #136335

Open

Description

@w2olves

Description

On osx-arm64, a NativeAOT-compiled method that takes an (int Start, int End) value tuple and writes an array in a loop starting at Start crashes with EXC_BAD_ACCESS (SIGBUS).

The tuple arrives packed in one register (x1: Start in the low 32 bits, End in the high 32 bits). The loop's first store computes its address with x1 as a 64-bit index (add x14, x0, x1, LSL #3) without zero-extending Start first. So the first write lands (End << 32) * 8 bytes past the array. Later iterations are fine, because add w1, w1, #1 clears the upper half.

The same code runs correctly under the regular runtime (JIT, tiered or fully optimized). The same loop taking two int parameters is also fine under NativeAOT.

Reproduction Steps

AotTupleIndex.csproj:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <PublishAot>true</PublishAot>
  </PropertyGroup>
</Project>

Program.cs:

using System.Runtime.CompilerServices;

string mode = args.Length > 0 ? args[0] : "tuple";
string[] states = ["a", "a", "b", "b", "b", "b", "b", "b"];
if (mode == "tuple")
{
    Repro.FillFromTuple(states, (0, 7));   // crashes under NativeAOT on osx-arm64
}
else
{
    Repro.FillFromInts(states, 0, 7);      // same loop with two int parameters: fine
}
Console.WriteLine($"{mode}: {string.Join("", states)}");

static class Repro
{
    [MethodImpl(MethodImplOptions.NoInlining)]
    public static void FillFromTuple(string[] states, (int Start, int End) run)
    {
        string state = states[run.End];
        for (int i = run.Start; i <= run.End; i++)
        {
            states[i] = state;
        }
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    public static void FillFromInts(string[] states, int start, int end)
    {
        string state = states[end];
        for (int i = start; i <= end; i++)
        {
            states[i] = state;
        }
    }
}

Then:

dotnet publish -c Release -r osx-arm64 -o out
./out/AotTupleIndex tuple   # crashes: exit 138 (SIGBUS)
./out/AotTupleIndex ints    # prints "ints: bbbbbbbb"
dotnet run -c Release -- tuple   # JIT: prints "tuple: bbbbbbbb"

Expected behavior

tuple: bbbbbbbb, the same as the ints path and the JIT.

Actual behavior

The process dies with EXC_BAD_ACCESS (SIGBUS) on the first store inside the loop.

The disassembly of FillFromTuple, from adding <IlcArg Include="--codegenopt:JitDisasm=FillFromTuple" /> to the project:

; Assembly listing for method Repro:FillFromTuple(System.String[],System.ValueTuple`2[int,int]) (FullOpts)
; Emitting BLENDED_CODE for generic ARM64 on Apple
; FullOpts code
; NativeAOT compilation
; optimized code
; fp based frame
; fully interruptible

G_M000_IG01:                ;; offset=0x0000
            sub     sp, sp, #32
            stp     fp, lr, [sp, #0x10]
            add     fp, sp, #16
            str     x1, [sp, #0x08]
 
G_M000_IG02:                ;; offset=0x0010
            ldr     w2, [sp, #0x0C]          ; End
            ldr     w3, [x0, #0x08]
            cmp     w2, w3
            bhs     G_M000_IG07
            add     x0, x0, #16
            ldr     x4, [x0, w2, UXTW #3]
            cmp     w1, w2
            bgt     G_M000_IG05
 
G_M000_IG03:                ;; offset=0x0030
            cmp     w3, w2
            ccmp    w1, #0, nc, gt
            blt     G_M000_IG06
            align   [4 bytes for IG04]
            align   [0 bytes]
            align   [0 bytes]
            align   [0 bytes]
 
G_M000_IG04:                ;; offset=0x0040
            add     x14, x0, x1,  LSL #3     ; x1 still holds End in its upper 32 bits on the first iteration
            mov     x15, x4
            bl      CORINFO_HELP_ASSIGN_REF
            add     w1, w1, #1               ; clears the upper half from the second iteration on
            cmp     w1, w2
            ble     G_M000_IG04
 
G_M000_IG05:                ;; offset=0x0058
            ldp     fp, lr, [sp, #0x10]
            add     sp, sp, #32
            ret     lr
 
G_M000_IG06:                ;; offset=0x0064
            cmp     w1, w3
            bhs     G_M000_IG07
            add     x14, x0, x1,  LSL #3     ; same in the range-checked copy of the loop
            mov     x15, x4
            bl      CORINFO_HELP_ASSIGN_REF
            add     w1, w1, #1               ; clears the upper half from the second iteration on
            cmp     w1, w2
            ble     G_M000_IG06
            b       G_M000_IG05
 
G_M000_IG07:                ;; offset=0x0088
            bl      CORINFO_HELP_RNGCHKFAIL
            brk     #0
 
; Total bytes of code 144

Regression?

Not known. Not tried on .NET 9.

Known Workarounds

Pass the two fields as separate int parameters, or use states.AsSpan(start, end - start + 1).Fill(state).

Configuration

  • ILCompiler (NativeAOT): 10.0.0, 10.0.8, 10.0.12 and 10.0.13, all crash; 10.0.13 is the latest stable.
  • RID: osx-arm64 on macOS 26.6.2 (25G83), Apple M5. linux-arm64 and win-arm64 not tried.
  • Optimisation: OptimizationPreference Size and Speed both crash.
  • These codegen options didn't help (each tried alone): JitEnablePhysicalPromotion=0, JitNoStructPromotion=1, JitEnableInductionVariableOpts=0, JitEnableStrengthReduction=0, JitDoRangeAnalysis=0, JitDoAssertionProp=0, JitEnableOptRepeat=0.

Other information

It looks like the loop's address computation uses the incoming struct register directly for the induction variable's first value, without the zero-extension that the w-register increment provides on later iterations.

Activity

  1. added
    area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
    on Oct 22, 2026
  2. dotnet-policy-service commented on Oct 22, 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. EgorBo commented on Oct 22, 2026

    @EgorBo
    Member

    It's fixed in main via #134466, I'll backport it to .NET 11.0.
    @w2olves is it acceptable for you to update to .NET 11.0 once it's released? (in a few weeks).

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