Skip to content

Conversation

@eduardo-vp
Copy link
Member

@eduardo-vp eduardo-vp commented Jan 23, 2026

The original PR #123057 led to some crashes in the outerloop runs so was reverted in #123474.

Given the code in

#ifdef TARGET_ARM
m_RegDisplay.pLR = (PTR_uintptr_t)PTR_HOST_MEMBER_TADDR(PInvokeTransitionFrame, pFrame, m_RIP);
if (pFrame->m_Flags & PTFF_SAVE_R4) { m_RegDisplay.pR4 = pPreservedRegsCursor++; }
if (pFrame->m_Flags & PTFF_SAVE_R5) { m_RegDisplay.pR5 = pPreservedRegsCursor++; }
if (pFrame->m_Flags & PTFF_SAVE_R6) { m_RegDisplay.pR6 = pPreservedRegsCursor++; }
if (pFrame->m_Flags & PTFF_SAVE_R7) { m_RegDisplay.pR7 = pPreservedRegsCursor++; }
if (pFrame->m_Flags & PTFF_SAVE_R8) { m_RegDisplay.pR8 = pPreservedRegsCursor++; }
if (pFrame->m_Flags & PTFF_SAVE_R9) { m_RegDisplay.pR9 = pPreservedRegsCursor++; }
if (pFrame->m_Flags & PTFF_SAVE_R10) { m_RegDisplay.pR10 = pPreservedRegsCursor++; }
if (pFrame->m_Flags & PTFF_SAVE_SP) { m_RegDisplay.SP = *pPreservedRegsCursor++; }
m_RegDisplay.pR11 = (PTR_uintptr_t) PTR_HOST_MEMBER_TADDR(PInvokeTransitionFrame, pFrame, m_FramePointer);
if (pFrame->m_Flags & PTFF_SAVE_R0) { m_RegDisplay.pR0 = pPreservedRegsCursor++; }
if (pFrame->m_Flags & PTFF_SAVE_R1) { m_RegDisplay.pR1 = pPreservedRegsCursor++; }
if (pFrame->m_Flags & PTFF_SAVE_R2) { m_RegDisplay.pR2 = pPreservedRegsCursor++; }
if (pFrame->m_Flags & PTFF_SAVE_R3) { m_RegDisplay.pR3 = pPreservedRegsCursor++; }
if (pFrame->m_Flags & PTFF_SAVE_LR) { m_RegDisplay.pLR = pPreservedRegsCursor++; }

I think the stack has r0, r1, r2 but without the PTFF_SAVE_R1 flag, r1 gets skipped and PTFF_SAVE_R2 is actually used to preserve r1 instead.

eduardo-vp and others added 2 commits January 22, 2026 19:56
…net#123057)

Flag r2 (see [async calling
convention](https://github.com/dotnet/runtime/blob/main/docs/design/coreclr/botr/clr-abi.md#returning-continuation))
during GC as it might contain an async continuation.

Contributes to dotnet#122492.

---------

Co-authored-by: Eduardo Velarde <[email protected]>
Co-authored-by: Jan Kotas <[email protected]>
Copilot AI review requested due to automatic review settings January 23, 2026 04:47
@github-actions github-actions bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Jan 23, 2026
@eduardo-vp eduardo-vp added area-NativeAOT-coreclr runtime-async and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Jan 23, 2026
@dotnet-policy-service
Copy link
Contributor

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

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes ARM32 hijacking during GC to properly preserve the async continuation register (r2). The original code only saved r0 and r1 during hijacking, but ARM32's async calling convention uses r2 to return continuations (as documented in the CLR ABI). Without this fix, async continuations could be lost during GC, leading to incorrect behavior.

Changes:

  • Added flag definitions for PTFF_SAVE_R1 and PTFF_SAVE_R2 to enable saving these registers during hijacking
  • Modified GC probe frame to save and restore r0-r2 (was r0-r1) to preserve the async continuation register
  • Updated stack frame layout calculations and alignment to accommodate the additional saved register

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
src/coreclr/nativeaot/Runtime/unix/unixasmmacrosarm.inc Added PTFF_SAVE_R1 and PTFF_SAVE_R2 flag definitions matching PInvokeTransitionFrameFlags enum
src/coreclr/nativeaot/Runtime/arm/GcProbe.S Updated PUSH_PROBE_FRAME, POP_PROBE_FRAME, and FixupHijackedCallstack macros to save/restore r0-r2, adjusted stack frame size from 88 to 96 bytes with proper 8-byte alignment, and updated RhpGcProbeHijack to set the new flags

@MichalStrehovsky
Copy link
Member

/azp run runtime-nativeaot-outerloop

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@eduardo-vp eduardo-vp changed the title Merged Fix up hijacking on arm32 (preserve async continuation register) Fix up hijacking on arm32 (preserve async continuation register) Jan 23, 2026
@jkotas
Copy link
Member

jkotas commented Jan 23, 2026

Outer loop has a lot of crashes on linux arm32 that are likely caused by the PR

@eduardo-vp
Copy link
Member Author

Outer loop has a lot of crashes on linux arm32 that are likely caused by the PR

I think that's likely. Is there a better way to check the changes than running the outerloop pipeline by any chance?

@jkotas
Copy link
Member

jkotas commented Jan 23, 2026

  • You can run NAOT-compiled linux arm32 binarier under qemu using docker (using same image as what's used in CI). For example, I have just done the following to reproduce the crash:
runfo get-helix-payload -j 201029a5-27fd-4db8-a0b2-4ae69bf23f10 -w System.Runtime.Tests -o c:\helix_payload
docker run -it --privileged --cap-add=SYS_PTRACE -v c:\helix_payload:/helix_payload mcr.microsoft.com/dotnet-buildtools/prereqs:debian-13-helix-arm32v7
cd helix_payload/workitems/System.Runtime.Tests
./System.Runtime.Tests
  • To test your local changes, you can build your changes and the failing test in arm32 cross-build image (same image as what's used by the CI): https://github.com/dotnet/runtime/blob/main/docs/workflow/using-docker.md , and then run them under docker/qemu using the flow from my previous point.
  • Getting working debugger on arm32 with qemu is close to impossible. If you really want to have a working debugger on arm32, I had the best success with a 32-bit Raspberry Pi.

@jkotas
Copy link
Member

jkotas commented Jan 23, 2026

I think the stack has r0, r1, r2 but without the PTFF_SAVE_R1 flag, r1 gets skipped and PTFF_SAVE_R2 is actually used to preserve r1 instead.

Looking at this code, is there a problem with where the alignment that gets inserted between SP and R0? I think the alignment needs to be inserted above r2, so that sp and r0 are next to each other to match this code.

@eduardo-vp
Copy link
Member Author

/azp run runtime-nativeaot-outerloop

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@jkotas
Copy link
Member

jkotas commented Jan 24, 2026

/azp run runtime-nativeaot-outerloop

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants