Fix up hijacking on x64 platforms in presence of runtime async - #122631

Merged
MichalStrehovsky merged 2 commits into
dotnet:mainfrom
MichalStrehovsky:hijackx64
Dec 22, 2025
Merged

Fix up hijacking on x64 platforms in presence of runtime async#122631
MichalStrehovsky merged 2 commits into
dotnet:mainfrom
MichalStrehovsky:hijackx64

Conversation

@MichalStrehovsky

Copy link
Copy Markdown
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
Copy Markdown
Contributor

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

Comment threadsrc/coreclr/nativeaot/Runtime/amd64/GcProbe.asm
Comment threadsrc/coreclr/nativeaot/Runtime/amd64/GcProbe.asm

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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.

FileDescription
src/coreclr/nativeaot/Runtime/unix/unixasmmacrosamd64.incAdds PTFF_SAVE_RCX constant definition for Unix x64 platforms
src/coreclr/nativeaot/Runtime/amd64/GcProbe.asmUpdates Windows x64 hijacking code to preserve RCX, changes bitmask register from RCX to R8, adjusts stack offsets
src/coreclr/nativeaot/Runtime/amd64/GcProbe.SUpdates 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.incAdds PTFF_SAVE_RCX constant definition for Windows x64 platforms

Comment threadsrc/coreclr/nativeaot/Runtime/amd64/GcProbe.asm Outdated
@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-nativeaot-outerloop

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@VSadov

Copy link
Copy Markdown
Member

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.

Comment threadsrc/coreclr/nativeaot/Runtime/amd64/GcProbe.S Outdated
@VSadov

Copy link
Copy Markdown
Member

LGTM. Are you going to do the same for other platforms too?

@am11

am11 commented Dec 20, 2025

Copy link
Copy Markdown
Member

Are you going to do the same for other platforms too?

Others are blocked on #121871. Would be nice to prioritize that so we can start testing async everywhere earlier in .NET 11 development cycle.

Co-authored-by: Vladimir Sadov <vsadov@microsoft.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

LGTM. Are you going to do the same for other platforms too?

Andy was suggesting I don't do all the runtime async work, so not going to work on this for now. I needed x64 in #122526 because all the crashing was causing too much noise and I didn't know if we have any other bugs.

Others are blocked on #121871. Would be nice to prioritize that so we can start testing async everywhere earlier in .NET 11 development cycle.

Reporting the register doesn't need to wait for that one. We report the extra register no matter if the called method is async or not (or has a return value at all). The other platforms will probably look the same - report extra register no matter what. If the assembly is not right, it's going to crash without runtime async too.

@am11

am11 commented Dec 22, 2025

Copy link
Copy Markdown
Member

Reporting the register doesn't need to wait for that one.

Yup, no problem implementing them in code but I meant testing won't be possible as non-x64 64-bit platforms seem to be having problem during native link step (with runtime-async:on).

@MichalStrehovsky
MichalStrehovsky merged commit c591f97 into dotnet:mainDec 22, 2025
98 checks passed
@MichalStrehovsky
MichalStrehovsky deleted the hijackx64 branch December 22, 2025 22:13
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jan 22, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@MichalStrehovsky@VSadov@am11
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Fix up hijacking on x64 platforms in presence of runtime async - #122631

Merged
MichalStrehovsky merged 2 commits into
dotnet:mainfrom
MichalStrehovsky:hijackx64
Dec 22, 2025
Merged

Fix up hijacking on x64 platforms in presence of runtime async#122631
MichalStrehovsky merged 2 commits into
dotnet:mainfrom
MichalStrehovsky:hijackx64

Conversation

@MichalStrehovsky

Copy link
Copy Markdown
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
Copy Markdown
Contributor

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

Comment threadsrc/coreclr/nativeaot/Runtime/amd64/GcProbe.asm
Comment threadsrc/coreclr/nativeaot/Runtime/amd64/GcProbe.asm

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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.

FileDescription
src/coreclr/nativeaot/Runtime/unix/unixasmmacrosamd64.incAdds PTFF_SAVE_RCX constant definition for Unix x64 platforms
src/coreclr/nativeaot/Runtime/amd64/GcProbe.asmUpdates Windows x64 hijacking code to preserve RCX, changes bitmask register from RCX to R8, adjusts stack offsets
src/coreclr/nativeaot/Runtime/amd64/GcProbe.SUpdates 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.incAdds PTFF_SAVE_RCX constant definition for Windows x64 platforms

Comment threadsrc/coreclr/nativeaot/Runtime/amd64/GcProbe.asm Outdated
@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-nativeaot-outerloop

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@VSadov

Copy link
Copy Markdown
Member

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.

Comment threadsrc/coreclr/nativeaot/Runtime/amd64/GcProbe.S Outdated
@VSadov

Copy link
Copy Markdown
Member

LGTM. Are you going to do the same for other platforms too?

@am11

am11 commented Dec 20, 2025

Copy link
Copy Markdown
Member

Are you going to do the same for other platforms too?

Others are blocked on #121871. Would be nice to prioritize that so we can start testing async everywhere earlier in .NET 11 development cycle.

Co-authored-by: Vladimir Sadov <vsadov@microsoft.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

LGTM. Are you going to do the same for other platforms too?

Andy was suggesting I don't do all the runtime async work, so not going to work on this for now. I needed x64 in #122526 because all the crashing was causing too much noise and I didn't know if we have any other bugs.

Others are blocked on #121871. Would be nice to prioritize that so we can start testing async everywhere earlier in .NET 11 development cycle.

Reporting the register doesn't need to wait for that one. We report the extra register no matter if the called method is async or not (or has a return value at all). The other platforms will probably look the same - report extra register no matter what. If the assembly is not right, it's going to crash without runtime async too.

@am11

am11 commented Dec 22, 2025

Copy link
Copy Markdown
Member

Reporting the register doesn't need to wait for that one.

Yup, no problem implementing them in code but I meant testing won't be possible as non-x64 64-bit platforms seem to be having problem during native link step (with runtime-async:on).

@MichalStrehovsky
MichalStrehovsky merged commit c591f97 into dotnet:mainDec 22, 2025
98 checks passed
@MichalStrehovsky
MichalStrehovsky deleted the hijackx64 branch December 22, 2025 22:13
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jan 22, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@MichalStrehovsky@VSadov@am11
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix up hijacking on x64 platforms in presence of runtime async - #122631

Merged
MichalStrehovsky merged 2 commits into
dotnet:mainfrom
MichalStrehovsky:hijackx64
Dec 22, 2025
Merged

Fix up hijacking on x64 platforms in presence of runtime async#122631
MichalStrehovsky merged 2 commits into
dotnet:mainfrom
MichalStrehovsky:hijackx64

Conversation

@MichalStrehovsky

Copy link
Copy Markdown
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
Copy Markdown
Contributor

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

Comment threadsrc/coreclr/nativeaot/Runtime/amd64/GcProbe.asm
Comment threadsrc/coreclr/nativeaot/Runtime/amd64/GcProbe.asm

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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.

FileDescription
src/coreclr/nativeaot/Runtime/unix/unixasmmacrosamd64.incAdds PTFF_SAVE_RCX constant definition for Unix x64 platforms
src/coreclr/nativeaot/Runtime/amd64/GcProbe.asmUpdates Windows x64 hijacking code to preserve RCX, changes bitmask register from RCX to R8, adjusts stack offsets
src/coreclr/nativeaot/Runtime/amd64/GcProbe.SUpdates 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.incAdds PTFF_SAVE_RCX constant definition for Windows x64 platforms

Comment threadsrc/coreclr/nativeaot/Runtime/amd64/GcProbe.asm Outdated
@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-nativeaot-outerloop

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@VSadov

Copy link
Copy Markdown
Member

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.

Comment threadsrc/coreclr/nativeaot/Runtime/amd64/GcProbe.S Outdated
@VSadov

Copy link
Copy Markdown
Member

LGTM. Are you going to do the same for other platforms too?

@am11

am11 commented Dec 20, 2025

Copy link
Copy Markdown
Member

Are you going to do the same for other platforms too?

Others are blocked on #121871. Would be nice to prioritize that so we can start testing async everywhere earlier in .NET 11 development cycle.

Co-authored-by: Vladimir Sadov <vsadov@microsoft.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

LGTM. Are you going to do the same for other platforms too?

Andy was suggesting I don't do all the runtime async work, so not going to work on this for now. I needed x64 in #122526 because all the crashing was causing too much noise and I didn't know if we have any other bugs.

Others are blocked on #121871. Would be nice to prioritize that so we can start testing async everywhere earlier in .NET 11 development cycle.

Reporting the register doesn't need to wait for that one. We report the extra register no matter if the called method is async or not (or has a return value at all). The other platforms will probably look the same - report extra register no matter what. If the assembly is not right, it's going to crash without runtime async too.

@am11

am11 commented Dec 22, 2025

Copy link
Copy Markdown
Member

Reporting the register doesn't need to wait for that one.

Yup, no problem implementing them in code but I meant testing won't be possible as non-x64 64-bit platforms seem to be having problem during native link step (with runtime-async:on).

@MichalStrehovsky
MichalStrehovsky merged commit c591f97 into dotnet:mainDec 22, 2025
98 checks passed
@MichalStrehovsky
MichalStrehovsky deleted the hijackx64 branch December 22, 2025 22:13
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jan 22, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@MichalStrehovsky@VSadov@am11
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix up hijacking on x64 platforms in presence of runtime async - #122631

Merged
MichalStrehovsky merged 2 commits into
dotnet:mainfrom
MichalStrehovsky:hijackx64
Dec 22, 2025
Merged

Fix up hijacking on x64 platforms in presence of runtime async#122631
MichalStrehovsky merged 2 commits into
dotnet:mainfrom
MichalStrehovsky:hijackx64

Conversation

@MichalStrehovsky

Copy link
Copy Markdown
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
Copy Markdown
Contributor

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

Comment threadsrc/coreclr/nativeaot/Runtime/amd64/GcProbe.asm
Comment threadsrc/coreclr/nativeaot/Runtime/amd64/GcProbe.asm

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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.

FileDescription
src/coreclr/nativeaot/Runtime/unix/unixasmmacrosamd64.incAdds PTFF_SAVE_RCX constant definition for Unix x64 platforms
src/coreclr/nativeaot/Runtime/amd64/GcProbe.asmUpdates Windows x64 hijacking code to preserve RCX, changes bitmask register from RCX to R8, adjusts stack offsets
src/coreclr/nativeaot/Runtime/amd64/GcProbe.SUpdates 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.incAdds PTFF_SAVE_RCX constant definition for Windows x64 platforms

Comment threadsrc/coreclr/nativeaot/Runtime/amd64/GcProbe.asm Outdated
@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-nativeaot-outerloop

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@VSadov

Copy link
Copy Markdown
Member

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.

Comment threadsrc/coreclr/nativeaot/Runtime/amd64/GcProbe.S Outdated
@VSadov

Copy link
Copy Markdown
Member

LGTM. Are you going to do the same for other platforms too?

@am11

am11 commented Dec 20, 2025

Copy link
Copy Markdown
Member

Are you going to do the same for other platforms too?

Others are blocked on #121871. Would be nice to prioritize that so we can start testing async everywhere earlier in .NET 11 development cycle.

Co-authored-by: Vladimir Sadov <vsadov@microsoft.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

LGTM. Are you going to do the same for other platforms too?

Andy was suggesting I don't do all the runtime async work, so not going to work on this for now. I needed x64 in #122526 because all the crashing was causing too much noise and I didn't know if we have any other bugs.

Others are blocked on #121871. Would be nice to prioritize that so we can start testing async everywhere earlier in .NET 11 development cycle.

Reporting the register doesn't need to wait for that one. We report the extra register no matter if the called method is async or not (or has a return value at all). The other platforms will probably look the same - report extra register no matter what. If the assembly is not right, it's going to crash without runtime async too.

@am11

am11 commented Dec 22, 2025

Copy link
Copy Markdown
Member

Reporting the register doesn't need to wait for that one.

Yup, no problem implementing them in code but I meant testing won't be possible as non-x64 64-bit platforms seem to be having problem during native link step (with runtime-async:on).

@MichalStrehovsky
MichalStrehovsky merged commit c591f97 into dotnet:mainDec 22, 2025
98 checks passed
@MichalStrehovsky
MichalStrehovsky deleted the hijackx64 branch December 22, 2025 22:13
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jan 22, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@MichalStrehovsky@VSadov@am11
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Fix up hijacking on x64 platforms in presence of runtime async - #122631

Merged
MichalStrehovsky merged 2 commits into
dotnet:mainfrom
MichalStrehovsky:hijackx64
Dec 22, 2025
Merged

Fix up hijacking on x64 platforms in presence of runtime async#122631
MichalStrehovsky merged 2 commits into
dotnet:mainfrom
MichalStrehovsky:hijackx64

Conversation

@MichalStrehovsky

Copy link
Copy Markdown
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
Copy Markdown
Contributor

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

Comment threadsrc/coreclr/nativeaot/Runtime/amd64/GcProbe.asm
Comment threadsrc/coreclr/nativeaot/Runtime/amd64/GcProbe.asm

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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.

FileDescription
src/coreclr/nativeaot/Runtime/unix/unixasmmacrosamd64.incAdds PTFF_SAVE_RCX constant definition for Unix x64 platforms
src/coreclr/nativeaot/Runtime/amd64/GcProbe.asmUpdates Windows x64 hijacking code to preserve RCX, changes bitmask register from RCX to R8, adjusts stack offsets
src/coreclr/nativeaot/Runtime/amd64/GcProbe.SUpdates 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.incAdds PTFF_SAVE_RCX constant definition for Windows x64 platforms

Comment threadsrc/coreclr/nativeaot/Runtime/amd64/GcProbe.asm Outdated
@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-nativeaot-outerloop

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@VSadov

Copy link
Copy Markdown
Member

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.

Comment threadsrc/coreclr/nativeaot/Runtime/amd64/GcProbe.S Outdated
@VSadov

Copy link
Copy Markdown
Member

LGTM. Are you going to do the same for other platforms too?

@am11

am11 commented Dec 20, 2025

Copy link
Copy Markdown
Member

Are you going to do the same for other platforms too?

Others are blocked on #121871. Would be nice to prioritize that so we can start testing async everywhere earlier in .NET 11 development cycle.

Co-authored-by: Vladimir Sadov <vsadov@microsoft.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

LGTM. Are you going to do the same for other platforms too?

Andy was suggesting I don't do all the runtime async work, so not going to work on this for now. I needed x64 in #122526 because all the crashing was causing too much noise and I didn't know if we have any other bugs.

Others are blocked on #121871. Would be nice to prioritize that so we can start testing async everywhere earlier in .NET 11 development cycle.

Reporting the register doesn't need to wait for that one. We report the extra register no matter if the called method is async or not (or has a return value at all). The other platforms will probably look the same - report extra register no matter what. If the assembly is not right, it's going to crash without runtime async too.

@am11

am11 commented Dec 22, 2025

Copy link
Copy Markdown
Member

Reporting the register doesn't need to wait for that one.

Yup, no problem implementing them in code but I meant testing won't be possible as non-x64 64-bit platforms seem to be having problem during native link step (with runtime-async:on).

@MichalStrehovsky
MichalStrehovsky merged commit c591f97 into dotnet:mainDec 22, 2025
98 checks passed
@MichalStrehovsky
MichalStrehovsky deleted the hijackx64 branch December 22, 2025 22:13
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jan 22, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@MichalStrehovsky@VSadov@am11
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix up hijacking on x64 platforms in presence of runtime async - #122631

Merged
MichalStrehovsky merged 2 commits into
dotnet:mainfrom
MichalStrehovsky:hijackx64
Dec 22, 2025
Merged

Fix up hijacking on x64 platforms in presence of runtime async#122631
MichalStrehovsky merged 2 commits into
dotnet:mainfrom
MichalStrehovsky:hijackx64

Conversation

@MichalStrehovsky

Copy link
Copy Markdown
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
Copy Markdown
Contributor

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

Comment threadsrc/coreclr/nativeaot/Runtime/amd64/GcProbe.asm
Comment threadsrc/coreclr/nativeaot/Runtime/amd64/GcProbe.asm

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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.

FileDescription
src/coreclr/nativeaot/Runtime/unix/unixasmmacrosamd64.incAdds PTFF_SAVE_RCX constant definition for Unix x64 platforms
src/coreclr/nativeaot/Runtime/amd64/GcProbe.asmUpdates Windows x64 hijacking code to preserve RCX, changes bitmask register from RCX to R8, adjusts stack offsets
src/coreclr/nativeaot/Runtime/amd64/GcProbe.SUpdates 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.incAdds PTFF_SAVE_RCX constant definition for Windows x64 platforms

Comment threadsrc/coreclr/nativeaot/Runtime/amd64/GcProbe.asm Outdated
@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-nativeaot-outerloop

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@VSadov

Copy link
Copy Markdown
Member

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.

Comment threadsrc/coreclr/nativeaot/Runtime/amd64/GcProbe.S Outdated
@VSadov

Copy link
Copy Markdown
Member

LGTM. Are you going to do the same for other platforms too?

@am11

am11 commented Dec 20, 2025

Copy link
Copy Markdown
Member

Are you going to do the same for other platforms too?

Others are blocked on #121871. Would be nice to prioritize that so we can start testing async everywhere earlier in .NET 11 development cycle.

Co-authored-by: Vladimir Sadov <vsadov@microsoft.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

LGTM. Are you going to do the same for other platforms too?

Andy was suggesting I don't do all the runtime async work, so not going to work on this for now. I needed x64 in #122526 because all the crashing was causing too much noise and I didn't know if we have any other bugs.

Others are blocked on #121871. Would be nice to prioritize that so we can start testing async everywhere earlier in .NET 11 development cycle.

Reporting the register doesn't need to wait for that one. We report the extra register no matter if the called method is async or not (or has a return value at all). The other platforms will probably look the same - report extra register no matter what. If the assembly is not right, it's going to crash without runtime async too.

@am11

am11 commented Dec 22, 2025

Copy link
Copy Markdown
Member

Reporting the register doesn't need to wait for that one.

Yup, no problem implementing them in code but I meant testing won't be possible as non-x64 64-bit platforms seem to be having problem during native link step (with runtime-async:on).

@MichalStrehovsky
MichalStrehovsky merged commit c591f97 into dotnet:mainDec 22, 2025
98 checks passed
@MichalStrehovsky
MichalStrehovsky deleted the hijackx64 branch December 22, 2025 22:13
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jan 22, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@MichalStrehovsky@VSadov@am11
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix up hijacking on x64 platforms in presence of runtime async - #122631

Merged
MichalStrehovsky merged 2 commits into
dotnet:mainfrom
MichalStrehovsky:hijackx64
Dec 22, 2025
Merged

Fix up hijacking on x64 platforms in presence of runtime async#122631
MichalStrehovsky merged 2 commits into
dotnet:mainfrom
MichalStrehovsky:hijackx64

Conversation

@MichalStrehovsky

Copy link
Copy Markdown
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
Copy Markdown
Contributor

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

Comment threadsrc/coreclr/nativeaot/Runtime/amd64/GcProbe.asm
Comment threadsrc/coreclr/nativeaot/Runtime/amd64/GcProbe.asm

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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.

FileDescription
src/coreclr/nativeaot/Runtime/unix/unixasmmacrosamd64.incAdds PTFF_SAVE_RCX constant definition for Unix x64 platforms
src/coreclr/nativeaot/Runtime/amd64/GcProbe.asmUpdates Windows x64 hijacking code to preserve RCX, changes bitmask register from RCX to R8, adjusts stack offsets
src/coreclr/nativeaot/Runtime/amd64/GcProbe.SUpdates 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.incAdds PTFF_SAVE_RCX constant definition for Windows x64 platforms

Comment threadsrc/coreclr/nativeaot/Runtime/amd64/GcProbe.asm Outdated
@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-nativeaot-outerloop

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@VSadov

Copy link
Copy Markdown
Member

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.

Comment threadsrc/coreclr/nativeaot/Runtime/amd64/GcProbe.S Outdated
@VSadov

Copy link
Copy Markdown
Member

LGTM. Are you going to do the same for other platforms too?

@am11

am11 commented Dec 20, 2025

Copy link
Copy Markdown
Member

Are you going to do the same for other platforms too?

Others are blocked on #121871. Would be nice to prioritize that so we can start testing async everywhere earlier in .NET 11 development cycle.

Co-authored-by: Vladimir Sadov <vsadov@microsoft.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

LGTM. Are you going to do the same for other platforms too?

Andy was suggesting I don't do all the runtime async work, so not going to work on this for now. I needed x64 in #122526 because all the crashing was causing too much noise and I didn't know if we have any other bugs.

Others are blocked on #121871. Would be nice to prioritize that so we can start testing async everywhere earlier in .NET 11 development cycle.

Reporting the register doesn't need to wait for that one. We report the extra register no matter if the called method is async or not (or has a return value at all). The other platforms will probably look the same - report extra register no matter what. If the assembly is not right, it's going to crash without runtime async too.

@am11

am11 commented Dec 22, 2025

Copy link
Copy Markdown
Member

Reporting the register doesn't need to wait for that one.

Yup, no problem implementing them in code but I meant testing won't be possible as non-x64 64-bit platforms seem to be having problem during native link step (with runtime-async:on).

@MichalStrehovsky
MichalStrehovsky merged commit c591f97 into dotnet:mainDec 22, 2025
98 checks passed
@MichalStrehovsky
MichalStrehovsky deleted the hijackx64 branch December 22, 2025 22:13
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jan 22, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@MichalStrehovsky@VSadov@am11
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Fix up hijacking on x64 platforms in presence of runtime async - #122631

Merged
MichalStrehovsky merged 2 commits into
dotnet:mainfrom
MichalStrehovsky:hijackx64
Dec 22, 2025
Merged

Fix up hijacking on x64 platforms in presence of runtime async#122631
MichalStrehovsky merged 2 commits into
dotnet:mainfrom
MichalStrehovsky:hijackx64

Conversation

@MichalStrehovsky

Copy link
Copy Markdown
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
Copy Markdown
Contributor

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

Comment threadsrc/coreclr/nativeaot/Runtime/amd64/GcProbe.asm
Comment threadsrc/coreclr/nativeaot/Runtime/amd64/GcProbe.asm

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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.

FileDescription
src/coreclr/nativeaot/Runtime/unix/unixasmmacrosamd64.incAdds PTFF_SAVE_RCX constant definition for Unix x64 platforms
src/coreclr/nativeaot/Runtime/amd64/GcProbe.asmUpdates Windows x64 hijacking code to preserve RCX, changes bitmask register from RCX to R8, adjusts stack offsets
src/coreclr/nativeaot/Runtime/amd64/GcProbe.SUpdates 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.incAdds PTFF_SAVE_RCX constant definition for Windows x64 platforms

Comment threadsrc/coreclr/nativeaot/Runtime/amd64/GcProbe.asm Outdated
@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-nativeaot-outerloop

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@VSadov

Copy link
Copy Markdown
Member

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.

Comment threadsrc/coreclr/nativeaot/Runtime/amd64/GcProbe.S Outdated
@VSadov

Copy link
Copy Markdown
Member

LGTM. Are you going to do the same for other platforms too?

@am11

am11 commented Dec 20, 2025

Copy link
Copy Markdown
Member

Are you going to do the same for other platforms too?

Others are blocked on #121871. Would be nice to prioritize that so we can start testing async everywhere earlier in .NET 11 development cycle.

Co-authored-by: Vladimir Sadov <vsadov@microsoft.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

LGTM. Are you going to do the same for other platforms too?

Andy was suggesting I don't do all the runtime async work, so not going to work on this for now. I needed x64 in #122526 because all the crashing was causing too much noise and I didn't know if we have any other bugs.

Others are blocked on #121871. Would be nice to prioritize that so we can start testing async everywhere earlier in .NET 11 development cycle.

Reporting the register doesn't need to wait for that one. We report the extra register no matter if the called method is async or not (or has a return value at all). The other platforms will probably look the same - report extra register no matter what. If the assembly is not right, it's going to crash without runtime async too.

@am11

am11 commented Dec 22, 2025

Copy link
Copy Markdown
Member

Reporting the register doesn't need to wait for that one.

Yup, no problem implementing them in code but I meant testing won't be possible as non-x64 64-bit platforms seem to be having problem during native link step (with runtime-async:on).

@MichalStrehovsky
MichalStrehovsky merged commit c591f97 into dotnet:mainDec 22, 2025
98 checks passed
@MichalStrehovsky
MichalStrehovsky deleted the hijackx64 branch December 22, 2025 22:13
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jan 22, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@MichalStrehovsky@VSadov@am11