Skip to content

[ARM64] R2R methods require RCPC2 without using RCPC2 instructions #133671

Closed

Description

@simonrozsival

Description

Crossgen2 adds RCPC2 dependencies to methods that contain no RCPC2 instructions, causing unnecessary R2R rejection on CPUs without RCPC2.

Context: dotnet/android#12722

Reproduction

Create Probe/Probe.csproj:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net11.0</TargetFramework>
    <PublishReadyToRun>true</PublishReadyToRun>
  </PropertyGroup>
</Project>

Add Probe/Probe.cs:

using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Explicit)]
public sealed class Fields
{
    [FieldOffset(0)]
    public volatile int Small;

    [FieldOffset(320)]
    public volatile int Large;
}

public sealed class ReferenceField
{
    public volatile object Value;
}

public static class Probe
{
    [MethodImpl(MethodImplOptions.NoInlining)]
    public static int ReadSmall(Fields value) => value.Small;

    [MethodImpl(MethodImplOptions.NoInlining)]
    public static int ReadLarge(Fields value) => value.Large;

    [MethodImpl(MethodImplOptions.NoInlining)]
    public static void WriteSmall(Fields value, int data) => value.Small = data;

    [MethodImpl(MethodImplOptions.NoInlining)]
    public static void WriteLarge(Fields value, int data) => value.Large = data;

    [MethodImpl(MethodImplOptions.NoInlining)]
    public static void WriteReference(ReferenceField value, object data) => value.Value = data;
}

Publish:

dotnet publish Probe/Probe.csproj -c Release -r linux-arm64
Disassembly and dependency graphs for stock and RCPC1-only builds

Run from the directory containing Probe/. Clean between variants to avoid reusing R2R output:

set -euo pipefail
for policy in stock rcpc-only; do
  output="$PWD/evidence/$policy"
  mkdir -p "$output"
  extra="--mapcsv;--parallelism:1;--dgmllog:$output/dependencies.dgml;--fulllog"
  extra+=";--codegenopt:JitDisasm=*;--codegenopt:JitDisasmTesting=1"
  extra+=";--codegenopt:JitDisasmWithCodeBytes=1;--codegenopt:JitStdOutFile=$output/jit-disasm.txt"
  if [ "$policy" = rcpc-only ]; then
    extra+=";--instruction-set:rcpc,-optimistic"
  fi

  escaped=${extra//;/%3B}
  escaped=${escaped//,/%2C}
  dotnet clean Probe/Probe.csproj -c Release -r linux-arm64
  dotnet publish Probe/Probe.csproj -c Release -r linux-arm64 \
    "-p:PublishReadyToRunCrossgen2ExtraArgs=$escaped" \
    -o "$output/publish"
  cp Probe/obj/Release/net11.0/linux-arm64/R2R/Probe.map.csv "$output/"
done

Actual behavior

The stock build declares RCPC2 for all five methods, but only two use it:

Method Generated code Uses RCPC2?
ReadSmall ldapur w0, [x0, #8] Yes
WriteSmall stlur w1, [x0, #8] Yes
ReadLarge add x0, x0, #328; ldapr w0, [x0] No
WriteLarge add x0, x0, #328; stlr w1, [x0] No
WriteReference Call to CORINFO_HELP_ASSIGN_REF No

ReadLarge:

stp     x29, x30, [sp, #-0x10]!
mov     x29, sp
add     x0, x0, #328
ldapr   w0, [x0]
ldp     x29, x30, [sp], #0x10
ret

Despite using only RCPC1, it has this dependency:

MethodWithGCInfo(Probe_Probe__ReadLarge)
  -> PrecodeHelperImport->SignaturePointer_ReadyToRunInstructionSets_Rcpc+Rcpc2,
     reason: classMustBeLoadedBeforeCodeIsRun

The emitted ISA signature also requires RCPC2. With --instruction-set:rcpc,-optimistic, ReadLarge, WriteLarge and WriteReference have byte-identical machine code without that dependency.

Expected behavior

ReadLarge, WriteLarge and WriteReference should not require RCPC2.

Likely cause

Lowering::TryCreateAddrMode calls compOpportunisticallyDependsOn(InstructionSet_Rcpc2) before checking whether the addressing mode is usable.

That call records the dependency, which survives when a later offset/index/containment check fails.

Here, offset 328 is outside the signed 9-bit range. The reference-store case also records RCPC2 even though codegen takes the GC write-barrier path.

Impact and environment

In a MAUI sample-content composite, 117 of 564 RCPC2-guarded bodies contain no RCPC2 instruction. The other 447 genuinely use RCPC2. Startup impact of a fix has not been measured.

Reproduced on a macOS ARM64 host, publishing a net11.0 library for linux-arm64, with:

  • .NET SDK 11.0.100-rc.1.26431.118
  • crossgen2/runtime 11.0.0-rc.1.26431.118

The same check is present in runtime main at c307f70 (source inspection only).

Investigation and discussion.

Drafted with the Copilot App.

Activity

  1. added
    area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
    on Sep 11, 2026
  2. dotnet-policy-service commented on Sep 11, 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. changed the title [-][ARM64] Spurious RCPC2 dependencies in ReadyToRun code[/-] [+][ARM64] R2R methods require RCPC2 without using RCPC2 instructions[/+] on Sep 11, 2026
  4. EgorBo commented on Sep 11, 2026

    @EgorBo
  5. JulieLeeMSFT commented on Sep 11, 2026

    @JulieLeeMSFT
  6. added and removed
    area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
    on Sep 11, 2026
  7. dotnet-policy-service commented on Sep 11, 2026

    @dotnet-policy-service
    Contributor

    Tagging subscribers to this area: @dotnet/crossgen-contrib
    See info in area-owners.md if you want to be subscribed.

  8. added
    area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
    and removed on Sep 11, 2026
  9. dotnet-policy-service commented on Sep 11, 2026

    @dotnet-policy-service
    Contributor

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

  10. jkotas commented on Sep 11, 2026

    @jkotas
    Member

    This is JIT bug in this code:

    // With RCPC2 (arm64 v8.4+) we can work with simple addressing modes like [reg + simm9]
    const bool shouldUseRcpc2 = !addrIsInReg && indir->Addr()->OperIs(GT_LEA) && !indir->HasIndex() &&
    (indir->Scale() == 1) &&
    emitter::emitIns_valid_imm_for_unscaled_ldst_offset(indir->Offset()) &&
    m_compiler->compOpportunisticallyDependsOn(InstructionSet_Rcpc2);
    if (shouldUseRcpc2)
    {
    assert(!addrIsInReg);
    switch (currentIns)
    {
    // Loads
    case INS_ldrb:
    return INS_ldapurb;
    case INS_ldrh:
    return INS_ldapurh;
    case INS_ldr:
    return INS_ldapur;
    // Stores
    case INS_strb:
    return INS_stlurb;
    case INS_strh:
    return INS_stlurh;
    case INS_str:
    return INS_stlur;
    default:
    *needsBarrier = true;
    return currentIns;
    }
    }

  11. added
    area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
    and removed
    untriagedNew issue has not been triaged by the area owner
    area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
    on Sep 26, 2026
  12. added this to the 12.0.0 milestone on Sep 26, 2026
  13. dotnet-policy-service commented on Sep 26, 2026

    @dotnet-policy-service
    Contributor

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

  14. added a commit that references this issue on Oct 5, 2026
    2a4e283
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

arch-arm64area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMIbug

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions