Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@MichalStrehovsky
Copy link
Member

Contributes to #122492.

(Please review as if LLM wrote this. LLM didn't write this, but there was a lot of pattern matching without understanding.)

Cc @dotnet/ilc-contrib

@dotnet-policy-service
Copy link
Contributor

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

LEAF_ENTRY RhpGcStressHijack, _TEXT
FixupHijackedCallstack
or ecx, DEFAULT_FRAME_SAVE_FLAGS + PTFF_SAVE_RAX
mov r8d, DEFAULT_FRAME_SAVE_FLAGS + PTFF_SAVE_RAX + PTFF_SAVE_RCX
Copy link
Member Author

Choose a reason for hiding this comment

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

Changed this to mov because the original or assumed FixupHijackedCallstack zeroed the register out which is subtle.

xor ecx, ecx
mov [rdx + OFFSETOF__Thread__m_ppvHijackedReturnAddressLocation], rcx
mov [rdx + OFFSETOF__Thread__m_pvHijackedReturnAddress], rcx
xor r8, r8
Copy link
Member Author

Choose a reason for hiding this comment

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

Could be r8d to match the ecx but I don't think there's advantage.

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 adds support for preserving the RCX register during GC hijacking on x64 platforms to handle async continuation return values. This is part of fixing runtime async support as mentioned in issue #122492.

Key Changes

  • Adds PTFF_SAVE_RCX flag constant to indicate RCX should be preserved during hijacking
  • Updates hijacking frame macros to save and restore RCX alongside RAX (and RDX on Unix)
  • Changes register usage from RCX to R8 for passing the register bitmask to avoid conflicts with preserved RCX
  • Adjusts stack calculations to account for the additional saved register

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
src/coreclr/nativeaot/Runtime/unix/unixasmmacrosamd64.inc Adds PTFF_SAVE_RCX constant definition for Unix x64 platforms
src/coreclr/nativeaot/Runtime/amd64/GcProbe.asm Updates Windows x64 hijacking code to preserve RCX, changes bitmask register from RCX to R8, adjusts stack offsets
src/coreclr/nativeaot/Runtime/amd64/GcProbe.S Updates Unix x64 hijacking code to preserve RCX, changes bitmask register from RCX to R8, adjusts stack offsets and alignment
src/coreclr/nativeaot/Runtime/amd64/AsmMacros.inc Adds PTFF_SAVE_RCX constant definition for Windows x64 platforms

;;
;; Register state on entry:
;; RDX: thread pointer
;; RCX: register bitmask
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

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

The comment stating "RCX: register bitmask" is outdated. The register bitmask is now passed in R8, not RCX, as seen in the code on line 150 where r8d is set to the flags and line 168 where r8 is passed to PUSH_PROBE_FRAME. This change was necessary to preserve RCX for async continuation return values. The comment should be updated to say "R8: register bitmask".

Suggested change
;; RCX: register bitmask
;; R8: register bitmask

Copilot uses AI. Check for mistakes.
@MichalStrehovsky
Copy link
Member Author

/azp run runtime-nativeaot-outerloop

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@VSadov
Copy link
Member

VSadov commented Dec 19, 2025

I was going through the JIT/VM runtime async PRs to see if anything was missed and noticed changes to hijacking. Do we need hijacking changes for native AOT?

Yes, I think so.

What registers may need updating when GC happens is encoded by the JIT for every possible GC site. But in order to update, these registers must be saved/restored in a state that GC can modify.
For asynchronous GC interrupts (i.e. signals) it is trivial since OS stores/restores the entire register set. We could, in theory do the same (capture the entire set) for hijacking, but the register sets have gotten fairly big recently and keep growing. So hijacking saves/restores just a subset that can possibly have live GC pointers. That is nonvolatile registers that survive the call + anything that a call can produce & can contain a pointer. Async adds the continuation register to the "call can produce and contain a pointer" set.

I did not look in details at the changes yet, but the LLM-like approach by analogy would totally make sense. On every platform we need to add one more register to the set that is stored/restored by the hijacking. The tracking and updating will happen by itself, but it needs a stored value to work with.

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.

2 participants