Optimize runtime async suspend/resume machinery - #127336

Merged
jakobbotsch merged 8 commits into
dotnet:mainfrom
jakobbotsch:unsafify-runtime-async
Apr 25, 2026
Merged

Optimize runtime async suspend/resume machinery#127336
jakobbotsch merged 8 commits into
dotnet:mainfrom
jakobbotsch:unsafify-runtime-async

Conversation

@jakobbotsch

@jakobbotschjakobbotsch commented Apr 23, 2026

Copy link
Copy Markdown
Member

Several optimizations around suspension/resumption:

  • Reduce number of TLS accesses by storing Thread.CurrentThread inside RuntimeAsyncAwaitState, and only accessing RuntimeAsyncAwaitState
  • Remove a number of write barriers by moving TLS object fields into a ref struct. Allocate this ref struct on the stack in the two places that initiate runtime async chains: task-returning thunks and DispatchContinuations. Keep a pointer to this in the TLS.
  • Use Unsafe in a couple of places to avoid unnecessary cast checks on the hot path

For a suspension heavy benchmark this improves performance by around 17%.

Example benchmark
usingSystem;usingSystem.Diagnostics;usingSystem.Runtime.CompilerServices;usingSystem.Threading;usingSystem.Threading.Tasks;namespaceOSRPerf;publicclassProgram{staticvoidMain(){NullAwaiterna=newNullAwaiter();for(inti=0;i<10;i++){for(intj=0;j<500;j++){Taskt=Foo(20,na);while(!t.IsCompleted){na.Continue();}}Thread.Sleep(100);}for(inti=0;i<50;i++){Taskt=Foo(10_000_000,na);while(!t.IsCompleted){na.Continue();}}}staticints_value;staticasyncTaskFoo(intn,NullAwaiterna){for(inti=0;i<n;i++){s_value+=i;}Stopwatchtimer=Stopwatch.StartNew();for(inti=0;i<n;i++){awaitna;}if(n>1000)Console.WriteLine("Took {0:F1} ms",timer.Elapsed.TotalMilliseconds);}privateclassNullAwaiter:ICriticalNotifyCompletion{publicActionContinue;publicNullAwaiterGetAwaiter()=>this;publicboolIsCompleted=>false;publicvoidGetResult(){}publicvoidUnsafeOnCompleted(Actioncontinuation){Continue=continuation;}publicvoidOnCompleted(Actioncontinuation){thrownewNotImplementedException();}}}

Before: Took 350.3 ms
After: Took 291.3 ms

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

Several optimizations around suspension/resumption:
- Reduce number of TLS accesses by storing `Thread.CurrentThread` and
`&AsyncDispatcherInfo.t_current` inside `RuntimeAsyncAwaitState`, and
only accessing `RuntimeAsyncAwaitState`
- Remove a number of write barriers by moving TLS object fields into a
`ref struct`. Allocate this ref struct on the stack in the two places
that initiate runtime async chains: task-returning thunks and
`DispatchContinuations`. Keep a pointer to this in the TLS.
- Use `Unsafe` in a couple of places to avoid unnecessary cast checks on
the hot path
For a suspension heavy benchmark this improves performance by around
25%.
Comment on lines +248 to +252
// This relies on coreclr and NAOT runtimes always storing ThreadStatic pointer fields in a pinned fashion.
fixed (AsyncDispatcherInfo** pCurrentDispatcherInfo = &AsyncDispatcherInfo.t_current)
{
return CurrentDispatcherInfo = pCurrentDispatcherInfo;
}

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

@jkotas Is this a correct assumption?

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.

AFAIR you need FixedAddressValueType for it to be guaranteed (but that didnt work for non valuetypes iirc).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this a correct assumption?

I do not think it is a valid assumption for NAOT - the thread static block is not allocated as pinned here:

returnRuntimeImports.RhNewObject((MethodTable*)gcDesc);

Is it not possible to pass this around as a byref?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

No, this pointer is being stored inside a struct allocated in TLS.
I am not totally sure if we are going to be accessing this field in NativeAOT though. Probably for Environment.StackTrace support?
We can store the dispatcher information directly inside RuntimeAsyncAwaitState, or add an object wrapper to give it an extra level of indirection. But those options would require diagnostic changes.

Maybe I just revert this and live with the one extra TLS access.

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.

If that's only needed for StackTrace, would the optimization be possible at least when StackTraceSupport=false? Or would it still not be viable because you still need to be able to get the full stacktrace even without metadata?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I don't see much (if any) benefit from avoiding this particular TLS lookup in the benchmark. I've just removed it.

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 optimizes CoreCLR “runtime async” suspension/resumption by reducing TLS traffic, minimizing write barriers on hot paths, and consolidating context-handling work into new helpers used by the JIT’s async transformation.

Changes:

  • Refactors runtime-async state to cache TLS-derived values and to move notifier/context references into a stack-allocated “stack state” accessed via the thread-static await state.
  • Extends CORINFO_ASYNC_INFO and JIT/EE plumbing with new “finish suspension” helper method handles, and updates the JIT async transform to use them.
  • Updates task-returning thunk emission (VM + ILCompiler stubs) and related tooling (SuperPMI, R2R/AOT scanners) to match the new runtime-async APIs.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.csExposes InstanceIsFlowSuppressed for optimized flow-suppression checks.
src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.csUpdates leaf await helpers to use stack-backed runtime-async state.
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.csIntroduces stack-based runtime-async state, new finish-suspension helpers, and updates dispatch/suspension logic.
src/coreclr/inc/corinfo.hExtends CORINFO_ASYNC_INFO with new helper method handles.
src/coreclr/vm/jitinterface.cppPopulates new async helper handles in CEEInfo::getAsyncInfo.
src/coreclr/vm/metasig.hAdds metasigs for updated thunk finalization signatures.
src/coreclr/vm/corelib.hUpdates CoreLib binder entries for new helpers, thunk signatures, and runtime-async nested types/field.
src/coreclr/vm/asyncthunks.cppUpdates task-returning thunk IL emission to push/pop the new await state and pass it to finalizers.
src/coreclr/jit/async.hDeclares new JIT helper routines to finish suspension context handling.
src/coreclr/jit/async.cppReworks suspension context handling to use new finish-suspension helpers and adjusts capture/restore sequence.
src/coreclr/tools/superpmi/superpmi-shared/agnostic.hExtends SuperPMI agnostic async-info struct for new handles.
src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cppRecords/replays new async-info handles.
src/coreclr/tools/Common/JitInterface/CorInfoTypes.csExtends managed projection of CORINFO_ASYNC_INFO.
src/coreclr/tools/Common/JitInterface/CorInfoImpl.csEmits new helper handles for the managed JIT interface implementation.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.csAdds R2R references to new finish-suspension helpers.
src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.csEnsures AOT scanning adds dependencies on new finish-suspension helpers.
src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.csUpdates IL stub emission for task-returning thunks to use new await-state push/pop + finalizer signatures.

CopilotAI review requested due to automatic review settings April 23, 2026 18:51

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 optimizes CoreCLR “runtime async” suspend/resume paths by reducing TLS accesses, lowering GC write barrier traffic, and avoiding some hot-path cast checks. It does so by introducing a stack-allocated state container that’s referenced via a per-thread TLS struct and by updating the thunk emitters to pass the TLS state byref.

Changes:

  • Introduces a stack-allocated RuntimeAsyncStackState and threads it through runtime-async chains via RuntimeAsyncAwaitState.Push/Pop.
  • Updates runtime-emitted task-returning thunks (VM + IL emitter) to initialize/teardown the new TLS stack state and pass ref RuntimeAsyncAwaitState into finalize helpers.
  • Switches a few hot-path casts to Unsafe.As and refactors await helpers to write notifier/context data into the stack state.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
FileDescription
src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.csUpdates public await helper intrinsics to write notifier into stack state via TLS.
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.csReplaces ExecutionAndSyncBlockStore with stack state + TLS Push/Pop, updates dispatch/finalize/handle-suspend flow.
src/coreclr/vm/metasig.hAdds metasigs for finalize helpers that now take ref RuntimeAsyncAwaitState.
src/coreclr/vm/corelib.hUpdates CoreLib binder entries for new TLS field, nested types, and finalize helper signatures.
src/coreclr/vm/asyncthunks.cppUpdates IL stub emission to Push/Pop TLS stack state and pass ref state into finalize helpers.
src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.csMirrors VM thunk emission changes in the managed IL emitter.

CopilotAI review requested due to automatic review settings April 24, 2026 08:21

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 optimizes CoreCLR “runtime async” suspension/resumption by reducing TLS lookups and write barriers, primarily by introducing a stack-allocated async state block that’s referenced from TLS during async-chain execution.

Changes:

  • Rework async await state handling to route notifier/context storage through a stack-allocated RuntimeAsyncStackState linked via TLS RuntimeAsyncAwaitState.
  • Update CoreCLR async thunk IL emission (VM + managed emitter) to Push/Pop the new TLS state and to pass the TLS state byref into Finalize*ReturningThunk.
  • Adjust CoreLib binder/metasig definitions to match the new helper signatures and types.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.csUpdate leaf await helpers to write notifier data into stack state via t_runtimeAsyncAwaitState.StackState.
src/coreclr/vm/metasig.hAdd new metasigs for Finalize*ReturningThunk(ref RuntimeAsyncAwaitState) (Task/ValueTask, generic and non-generic).
src/coreclr/vm/corelib.hUpdate binder definitions: remove ExecutionAndSyncBlockStore, add TLS field and nested async state types/methods.
src/coreclr/vm/asyncthunks.cppUpdate VM-emitted task-returning thunk IL to initialize and push/pop the new runtime async state and pass it to finalizers.
src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.csMirror VM thunk emission updates in the managed IL emitter (push/pop + updated finalizer signatures).
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.csImplement new stack/TLS async state structs, update dispatch/suspension logic, and adjust hot-path casts using Unsafe.

@jakobbotsch
jakobbotsch marked this pull request as ready for review April 24, 2026 15:17
@jakobbotsch

Copy link
Copy Markdown
MemberAuthor

PTAL @VSadov

@jakobbotsch
jakobbotsch requested a review from VSadovApril 24, 2026 15:17

@VSadovVSadov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM. Thanks!

CopilotAI review requested due to automatic review settings April 25, 2026 13:02

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 optimizes CoreCLR’s runtime-async suspend/resume path by reducing TLS traffic and GC write barriers, primarily by moving per-suspension state into a stack-allocated ref struct and caching Thread.CurrentThread in the TLS state.

Changes:

  • Introduce stack-allocated runtime-async state (RuntimeAsyncStackState) and keep only a pointer to it in TLS (RuntimeAsyncAwaitState).
  • Update task-returning thunk emission (VM + managed typesystem) to Push/Pop runtime-async state and pass ref RuntimeAsyncAwaitState into finalization helpers.
  • Update CoreLib binder signatures (corelib.h / metasig.h) to match the new helper method signatures and new nested types/fields.

Reviewed changes

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

Show a summary per file
FileDescription
src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.csSwitch await helpers to use stack-state via t_runtimeAsyncAwaitState.StackState and reduce TLS accesses.
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.csAdd RuntimeAsyncStackState + new TLS layout; adjust suspension/dispatch/finalization paths; use Unsafe.As for hot casts.
src/coreclr/vm/asyncthunks.cppUpdate VM-emitted task-returning thunk IL to Push/Pop runtime-async state and call new finalize signatures.
src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.csMirror VM thunk emission changes in the managed typesystem IL stub emitter.
src/coreclr/vm/metasig.hAdd metasig variants for finalize helpers that take ref RuntimeAsyncAwaitState.
src/coreclr/vm/corelib.hBind new nested types/field and update method signatures used by the VM binder.

Comment threadsrc/coreclr/vm/asyncthunks.cpp
Comment threadsrc/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.cs
@jakobbotsch
jakobbotsch merged commit 7e17692 into dotnet:mainApr 25, 2026
159 checks passed
@jakobbotsch
jakobbotsch deleted the unsafify-runtime-async branch April 25, 2026 19:45
This was referenced Apr 28, 2026
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Apr 28, 2026
PR dotnet#127336 (Optimize runtime async suspend/resume machinery) changed
AsyncDispatcherInfo.t_current access from ldsfld/stsfld to ldsflda.
This prevents the type from being resolved by name in R2R mode, and
R2R continuation layout fixups may not trigger getContinuationType,
leaving g_pContinuationClassIfSubTypeCreated null.
Changes:
- Switch AsyncContinuation debuggee to JIT-only mode (R2RModes=Jit)
so the JIT async transformation creates continuation MTs
- Make OuterAsync use a non-tail await (use value after await)
- Hide FailFast behind an indirect call to prevent the JIT from
treating post-await code as unreachable
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Apr 29, 2026
The debuggees were being R2R-compiled with the SDK's bundled crossgen2
pack (pinned in global.json), not the crossgen2 built from the current
commit. This caused R2R/CoreLib version skew when CoreLib types/signatures
changed between SDK pinning and the current build (e.g. PR dotnet#127336
removed ExecutionAndSyncBlockStore, but SDK preview3 crossgen2 still
emits R2R thunks referencing the removed type, causing TypeLoadException
at runtime).
Changes:
- Import targetingpacks.targets in Debuggees/Directory.Build.targets so
UseLocalCrossgen2Pack=true and UpdateLocalCrossgen2Pack target redirect
ResolvedCrossgen2Pack to the locally-built crossgen2.
- Pipe the runtime configuration through DumpTests.targets so the publish
command resolves Crossgen2InBuildDir correctly when CoreCLR is built
with a different configuration than the libs (e.g. -rc checked -lc release).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb added a commit that referenced this pull request Apr 30, 2026
> [!NOTE]
> This PR was created with the assistance of GitHub Copilot
(AI-generated content).
## Problem
The `AsyncContinuationDumpTests` (and potentially other R2R dump tests)
fail in CI because the debuggee apps are R2R-compiled with the SDK's
bundled crossgen2 instead of the locally-built one. When the
locally-built runtime removes or changes types between previews (e.g. PR
#127336 removing `ExecutionAndSyncBlockStore`), the SDK's crossgen2
emits R2R thunks referencing types that no longer exist, causing
`TypeLoadException` or null globals at runtime.
## Root Cause
Two issues prevent the locally-built crossgen2 from being used:
1. **NuGet pack layout mismatch**: The SDK's
`ResolveReadyToRunCompilers` task expects crossgen2 at
`PackagePath/tools/crossgen2.exe` (NuGet pack layout), but the local
build produces a flat layout with `crossgen2.exe` at the root of
`Crossgen2InBuildDir`. `targetingpacks.targets` alone cannot fix this
because it only updates `PackageDirectory` without restructuring the
layout.
2. **Missing property propagation**: The child `dotnet publish` process
doesn't inherit `RuntimeConfiguration`, `TargetArchitecture`,
`TargetOS`, or `BuildArchitecture` from the outer build, causing
crossgen2 and runtime pack paths to resolve incorrectly (especially in
cross-build scenarios like building on linux-x64 for linux-arm).
## Fix
### `Debuggees/Directory.Build.targets`
- Import `targetingpacks.targets` for runtime/targeting pack resolution
- Import `tests.readytorun.targets` via `AfterMicrosoftNETSdkTargets` to
override `ResolveReadyToRunCompilers` after the SDK defines it, pointing
directly at the locally-built crossgen2
### `DumpTests.targets`
- Add `_DebuggeeRuntimeConfig` property (falls back from
`RuntimeConfiguration` -> `Configuration` -> `Debug`)
- Add `_DebuggeePublishProps` to propagate `RuntimeConfiguration`,
`TargetArchitecture`, `TargetOS`, and `BuildArchitecture` to child
`dotnet publish` calls
### `prepare-cdac-helix-steps.yml`
- Add `runtimeConfiguration` parameter (default: `Checked`) and pass it
to `BuildDebuggeesOnly` so crossgen2 is found under the correct
`Checked` artifacts path
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 26, 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.

6 participants

@jakobbotsch@jkotas@VSadov@Sergio0694@MichalPetryka
, '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

Optimize runtime async suspend/resume machinery - #127336

Merged
jakobbotsch merged 8 commits into
dotnet:mainfrom
jakobbotsch:unsafify-runtime-async
Apr 25, 2026
Merged

Optimize runtime async suspend/resume machinery#127336
jakobbotsch merged 8 commits into
dotnet:mainfrom
jakobbotsch:unsafify-runtime-async

Conversation

@jakobbotsch

@jakobbotschjakobbotsch commented Apr 23, 2026

Copy link
Copy Markdown
Member

Several optimizations around suspension/resumption:

  • Reduce number of TLS accesses by storing Thread.CurrentThread inside RuntimeAsyncAwaitState, and only accessing RuntimeAsyncAwaitState
  • Remove a number of write barriers by moving TLS object fields into a ref struct. Allocate this ref struct on the stack in the two places that initiate runtime async chains: task-returning thunks and DispatchContinuations. Keep a pointer to this in the TLS.
  • Use Unsafe in a couple of places to avoid unnecessary cast checks on the hot path

For a suspension heavy benchmark this improves performance by around 17%.

Example benchmark
usingSystem;usingSystem.Diagnostics;usingSystem.Runtime.CompilerServices;usingSystem.Threading;usingSystem.Threading.Tasks;namespaceOSRPerf;publicclassProgram{staticvoidMain(){NullAwaiterna=newNullAwaiter();for(inti=0;i<10;i++){for(intj=0;j<500;j++){Taskt=Foo(20,na);while(!t.IsCompleted){na.Continue();}}Thread.Sleep(100);}for(inti=0;i<50;i++){Taskt=Foo(10_000_000,na);while(!t.IsCompleted){na.Continue();}}}staticints_value;staticasyncTaskFoo(intn,NullAwaiterna){for(inti=0;i<n;i++){s_value+=i;}Stopwatchtimer=Stopwatch.StartNew();for(inti=0;i<n;i++){awaitna;}if(n>1000)Console.WriteLine("Took {0:F1} ms",timer.Elapsed.TotalMilliseconds);}privateclassNullAwaiter:ICriticalNotifyCompletion{publicActionContinue;publicNullAwaiterGetAwaiter()=>this;publicboolIsCompleted=>false;publicvoidGetResult(){}publicvoidUnsafeOnCompleted(Actioncontinuation){Continue=continuation;}publicvoidOnCompleted(Actioncontinuation){thrownewNotImplementedException();}}}

Before: Took 350.3 ms
After: Took 291.3 ms

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

Several optimizations around suspension/resumption:
- Reduce number of TLS accesses by storing `Thread.CurrentThread` and
`&AsyncDispatcherInfo.t_current` inside `RuntimeAsyncAwaitState`, and
only accessing `RuntimeAsyncAwaitState`
- Remove a number of write barriers by moving TLS object fields into a
`ref struct`. Allocate this ref struct on the stack in the two places
that initiate runtime async chains: task-returning thunks and
`DispatchContinuations`. Keep a pointer to this in the TLS.
- Use `Unsafe` in a couple of places to avoid unnecessary cast checks on
the hot path
For a suspension heavy benchmark this improves performance by around
25%.
Comment on lines +248 to +252
// This relies on coreclr and NAOT runtimes always storing ThreadStatic pointer fields in a pinned fashion.
fixed (AsyncDispatcherInfo** pCurrentDispatcherInfo = &AsyncDispatcherInfo.t_current)
{
return CurrentDispatcherInfo = pCurrentDispatcherInfo;
}

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

@jkotas Is this a correct assumption?

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.

AFAIR you need FixedAddressValueType for it to be guaranteed (but that didnt work for non valuetypes iirc).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this a correct assumption?

I do not think it is a valid assumption for NAOT - the thread static block is not allocated as pinned here:

returnRuntimeImports.RhNewObject((MethodTable*)gcDesc);

Is it not possible to pass this around as a byref?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

No, this pointer is being stored inside a struct allocated in TLS.
I am not totally sure if we are going to be accessing this field in NativeAOT though. Probably for Environment.StackTrace support?
We can store the dispatcher information directly inside RuntimeAsyncAwaitState, or add an object wrapper to give it an extra level of indirection. But those options would require diagnostic changes.

Maybe I just revert this and live with the one extra TLS access.

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.

If that's only needed for StackTrace, would the optimization be possible at least when StackTraceSupport=false? Or would it still not be viable because you still need to be able to get the full stacktrace even without metadata?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I don't see much (if any) benefit from avoiding this particular TLS lookup in the benchmark. I've just removed it.

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 optimizes CoreCLR “runtime async” suspension/resumption by reducing TLS traffic, minimizing write barriers on hot paths, and consolidating context-handling work into new helpers used by the JIT’s async transformation.

Changes:

  • Refactors runtime-async state to cache TLS-derived values and to move notifier/context references into a stack-allocated “stack state” accessed via the thread-static await state.
  • Extends CORINFO_ASYNC_INFO and JIT/EE plumbing with new “finish suspension” helper method handles, and updates the JIT async transform to use them.
  • Updates task-returning thunk emission (VM + ILCompiler stubs) and related tooling (SuperPMI, R2R/AOT scanners) to match the new runtime-async APIs.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.csExposes InstanceIsFlowSuppressed for optimized flow-suppression checks.
src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.csUpdates leaf await helpers to use stack-backed runtime-async state.
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.csIntroduces stack-based runtime-async state, new finish-suspension helpers, and updates dispatch/suspension logic.
src/coreclr/inc/corinfo.hExtends CORINFO_ASYNC_INFO with new helper method handles.
src/coreclr/vm/jitinterface.cppPopulates new async helper handles in CEEInfo::getAsyncInfo.
src/coreclr/vm/metasig.hAdds metasigs for updated thunk finalization signatures.
src/coreclr/vm/corelib.hUpdates CoreLib binder entries for new helpers, thunk signatures, and runtime-async nested types/field.
src/coreclr/vm/asyncthunks.cppUpdates task-returning thunk IL emission to push/pop the new await state and pass it to finalizers.
src/coreclr/jit/async.hDeclares new JIT helper routines to finish suspension context handling.
src/coreclr/jit/async.cppReworks suspension context handling to use new finish-suspension helpers and adjusts capture/restore sequence.
src/coreclr/tools/superpmi/superpmi-shared/agnostic.hExtends SuperPMI agnostic async-info struct for new handles.
src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cppRecords/replays new async-info handles.
src/coreclr/tools/Common/JitInterface/CorInfoTypes.csExtends managed projection of CORINFO_ASYNC_INFO.
src/coreclr/tools/Common/JitInterface/CorInfoImpl.csEmits new helper handles for the managed JIT interface implementation.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.csAdds R2R references to new finish-suspension helpers.
src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.csEnsures AOT scanning adds dependencies on new finish-suspension helpers.
src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.csUpdates IL stub emission for task-returning thunks to use new await-state push/pop + finalizer signatures.

CopilotAI review requested due to automatic review settings April 23, 2026 18:51

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 optimizes CoreCLR “runtime async” suspend/resume paths by reducing TLS accesses, lowering GC write barrier traffic, and avoiding some hot-path cast checks. It does so by introducing a stack-allocated state container that’s referenced via a per-thread TLS struct and by updating the thunk emitters to pass the TLS state byref.

Changes:

  • Introduces a stack-allocated RuntimeAsyncStackState and threads it through runtime-async chains via RuntimeAsyncAwaitState.Push/Pop.
  • Updates runtime-emitted task-returning thunks (VM + IL emitter) to initialize/teardown the new TLS stack state and pass ref RuntimeAsyncAwaitState into finalize helpers.
  • Switches a few hot-path casts to Unsafe.As and refactors await helpers to write notifier/context data into the stack state.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
FileDescription
src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.csUpdates public await helper intrinsics to write notifier into stack state via TLS.
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.csReplaces ExecutionAndSyncBlockStore with stack state + TLS Push/Pop, updates dispatch/finalize/handle-suspend flow.
src/coreclr/vm/metasig.hAdds metasigs for finalize helpers that now take ref RuntimeAsyncAwaitState.
src/coreclr/vm/corelib.hUpdates CoreLib binder entries for new TLS field, nested types, and finalize helper signatures.
src/coreclr/vm/asyncthunks.cppUpdates IL stub emission to Push/Pop TLS stack state and pass ref state into finalize helpers.
src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.csMirrors VM thunk emission changes in the managed IL emitter.

CopilotAI review requested due to automatic review settings April 24, 2026 08:21

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 optimizes CoreCLR “runtime async” suspension/resumption by reducing TLS lookups and write barriers, primarily by introducing a stack-allocated async state block that’s referenced from TLS during async-chain execution.

Changes:

  • Rework async await state handling to route notifier/context storage through a stack-allocated RuntimeAsyncStackState linked via TLS RuntimeAsyncAwaitState.
  • Update CoreCLR async thunk IL emission (VM + managed emitter) to Push/Pop the new TLS state and to pass the TLS state byref into Finalize*ReturningThunk.
  • Adjust CoreLib binder/metasig definitions to match the new helper signatures and types.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.csUpdate leaf await helpers to write notifier data into stack state via t_runtimeAsyncAwaitState.StackState.
src/coreclr/vm/metasig.hAdd new metasigs for Finalize*ReturningThunk(ref RuntimeAsyncAwaitState) (Task/ValueTask, generic and non-generic).
src/coreclr/vm/corelib.hUpdate binder definitions: remove ExecutionAndSyncBlockStore, add TLS field and nested async state types/methods.
src/coreclr/vm/asyncthunks.cppUpdate VM-emitted task-returning thunk IL to initialize and push/pop the new runtime async state and pass it to finalizers.
src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.csMirror VM thunk emission updates in the managed IL emitter (push/pop + updated finalizer signatures).
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.csImplement new stack/TLS async state structs, update dispatch/suspension logic, and adjust hot-path casts using Unsafe.

@jakobbotsch
jakobbotsch marked this pull request as ready for review April 24, 2026 15:17
@jakobbotsch

Copy link
Copy Markdown
MemberAuthor

PTAL @VSadov

@jakobbotsch
jakobbotsch requested a review from VSadovApril 24, 2026 15:17

@VSadovVSadov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM. Thanks!

CopilotAI review requested due to automatic review settings April 25, 2026 13:02

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 optimizes CoreCLR’s runtime-async suspend/resume path by reducing TLS traffic and GC write barriers, primarily by moving per-suspension state into a stack-allocated ref struct and caching Thread.CurrentThread in the TLS state.

Changes:

  • Introduce stack-allocated runtime-async state (RuntimeAsyncStackState) and keep only a pointer to it in TLS (RuntimeAsyncAwaitState).
  • Update task-returning thunk emission (VM + managed typesystem) to Push/Pop runtime-async state and pass ref RuntimeAsyncAwaitState into finalization helpers.
  • Update CoreLib binder signatures (corelib.h / metasig.h) to match the new helper method signatures and new nested types/fields.

Reviewed changes

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

Show a summary per file
FileDescription
src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.csSwitch await helpers to use stack-state via t_runtimeAsyncAwaitState.StackState and reduce TLS accesses.
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.csAdd RuntimeAsyncStackState + new TLS layout; adjust suspension/dispatch/finalization paths; use Unsafe.As for hot casts.
src/coreclr/vm/asyncthunks.cppUpdate VM-emitted task-returning thunk IL to Push/Pop runtime-async state and call new finalize signatures.
src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.csMirror VM thunk emission changes in the managed typesystem IL stub emitter.
src/coreclr/vm/metasig.hAdd metasig variants for finalize helpers that take ref RuntimeAsyncAwaitState.
src/coreclr/vm/corelib.hBind new nested types/field and update method signatures used by the VM binder.

Comment threadsrc/coreclr/vm/asyncthunks.cpp
Comment threadsrc/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.cs
@jakobbotsch
jakobbotsch merged commit 7e17692 into dotnet:mainApr 25, 2026
159 checks passed
@jakobbotsch
jakobbotsch deleted the unsafify-runtime-async branch April 25, 2026 19:45
This was referenced Apr 28, 2026
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Apr 28, 2026
PR dotnet#127336 (Optimize runtime async suspend/resume machinery) changed
AsyncDispatcherInfo.t_current access from ldsfld/stsfld to ldsflda.
This prevents the type from being resolved by name in R2R mode, and
R2R continuation layout fixups may not trigger getContinuationType,
leaving g_pContinuationClassIfSubTypeCreated null.
Changes:
- Switch AsyncContinuation debuggee to JIT-only mode (R2RModes=Jit)
so the JIT async transformation creates continuation MTs
- Make OuterAsync use a non-tail await (use value after await)
- Hide FailFast behind an indirect call to prevent the JIT from
treating post-await code as unreachable
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Apr 29, 2026
The debuggees were being R2R-compiled with the SDK's bundled crossgen2
pack (pinned in global.json), not the crossgen2 built from the current
commit. This caused R2R/CoreLib version skew when CoreLib types/signatures
changed between SDK pinning and the current build (e.g. PR dotnet#127336
removed ExecutionAndSyncBlockStore, but SDK preview3 crossgen2 still
emits R2R thunks referencing the removed type, causing TypeLoadException
at runtime).
Changes:
- Import targetingpacks.targets in Debuggees/Directory.Build.targets so
UseLocalCrossgen2Pack=true and UpdateLocalCrossgen2Pack target redirect
ResolvedCrossgen2Pack to the locally-built crossgen2.
- Pipe the runtime configuration through DumpTests.targets so the publish
command resolves Crossgen2InBuildDir correctly when CoreCLR is built
with a different configuration than the libs (e.g. -rc checked -lc release).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb added a commit that referenced this pull request Apr 30, 2026
> [!NOTE]
> This PR was created with the assistance of GitHub Copilot
(AI-generated content).
## Problem
The `AsyncContinuationDumpTests` (and potentially other R2R dump tests)
fail in CI because the debuggee apps are R2R-compiled with the SDK's
bundled crossgen2 instead of the locally-built one. When the
locally-built runtime removes or changes types between previews (e.g. PR
#127336 removing `ExecutionAndSyncBlockStore`), the SDK's crossgen2
emits R2R thunks referencing types that no longer exist, causing
`TypeLoadException` or null globals at runtime.
## Root Cause
Two issues prevent the locally-built crossgen2 from being used:
1. **NuGet pack layout mismatch**: The SDK's
`ResolveReadyToRunCompilers` task expects crossgen2 at
`PackagePath/tools/crossgen2.exe` (NuGet pack layout), but the local
build produces a flat layout with `crossgen2.exe` at the root of
`Crossgen2InBuildDir`. `targetingpacks.targets` alone cannot fix this
because it only updates `PackageDirectory` without restructuring the
layout.
2. **Missing property propagation**: The child `dotnet publish` process
doesn't inherit `RuntimeConfiguration`, `TargetArchitecture`,
`TargetOS`, or `BuildArchitecture` from the outer build, causing
crossgen2 and runtime pack paths to resolve incorrectly (especially in
cross-build scenarios like building on linux-x64 for linux-arm).
## Fix
### `Debuggees/Directory.Build.targets`
- Import `targetingpacks.targets` for runtime/targeting pack resolution
- Import `tests.readytorun.targets` via `AfterMicrosoftNETSdkTargets` to
override `ResolveReadyToRunCompilers` after the SDK defines it, pointing
directly at the locally-built crossgen2
### `DumpTests.targets`
- Add `_DebuggeeRuntimeConfig` property (falls back from
`RuntimeConfiguration` -> `Configuration` -> `Debug`)
- Add `_DebuggeePublishProps` to propagate `RuntimeConfiguration`,
`TargetArchitecture`, `TargetOS`, and `BuildArchitecture` to child
`dotnet publish` calls
### `prepare-cdac-helix-steps.yml`
- Add `runtimeConfiguration` parameter (default: `Checked`) and pass it
to `BuildDebuggeesOnly` so crossgen2 is found under the correct
`Checked` artifacts path
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 26, 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.

6 participants

@jakobbotsch@jkotas@VSadov@Sergio0694@MichalPetryka
, '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

Optimize runtime async suspend/resume machinery - #127336

Merged
jakobbotsch merged 8 commits into
dotnet:mainfrom
jakobbotsch:unsafify-runtime-async
Apr 25, 2026
Merged

Optimize runtime async suspend/resume machinery#127336
jakobbotsch merged 8 commits into
dotnet:mainfrom
jakobbotsch:unsafify-runtime-async

Conversation

@jakobbotsch

@jakobbotschjakobbotsch commented Apr 23, 2026

Copy link
Copy Markdown
Member

Several optimizations around suspension/resumption:

  • Reduce number of TLS accesses by storing Thread.CurrentThread inside RuntimeAsyncAwaitState, and only accessing RuntimeAsyncAwaitState
  • Remove a number of write barriers by moving TLS object fields into a ref struct. Allocate this ref struct on the stack in the two places that initiate runtime async chains: task-returning thunks and DispatchContinuations. Keep a pointer to this in the TLS.
  • Use Unsafe in a couple of places to avoid unnecessary cast checks on the hot path

For a suspension heavy benchmark this improves performance by around 17%.

Example benchmark
usingSystem;usingSystem.Diagnostics;usingSystem.Runtime.CompilerServices;usingSystem.Threading;usingSystem.Threading.Tasks;namespaceOSRPerf;publicclassProgram{staticvoidMain(){NullAwaiterna=newNullAwaiter();for(inti=0;i<10;i++){for(intj=0;j<500;j++){Taskt=Foo(20,na);while(!t.IsCompleted){na.Continue();}}Thread.Sleep(100);}for(inti=0;i<50;i++){Taskt=Foo(10_000_000,na);while(!t.IsCompleted){na.Continue();}}}staticints_value;staticasyncTaskFoo(intn,NullAwaiterna){for(inti=0;i<n;i++){s_value+=i;}Stopwatchtimer=Stopwatch.StartNew();for(inti=0;i<n;i++){awaitna;}if(n>1000)Console.WriteLine("Took {0:F1} ms",timer.Elapsed.TotalMilliseconds);}privateclassNullAwaiter:ICriticalNotifyCompletion{publicActionContinue;publicNullAwaiterGetAwaiter()=>this;publicboolIsCompleted=>false;publicvoidGetResult(){}publicvoidUnsafeOnCompleted(Actioncontinuation){Continue=continuation;}publicvoidOnCompleted(Actioncontinuation){thrownewNotImplementedException();}}}

Before: Took 350.3 ms
After: Took 291.3 ms

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

Several optimizations around suspension/resumption:
- Reduce number of TLS accesses by storing `Thread.CurrentThread` and
`&AsyncDispatcherInfo.t_current` inside `RuntimeAsyncAwaitState`, and
only accessing `RuntimeAsyncAwaitState`
- Remove a number of write barriers by moving TLS object fields into a
`ref struct`. Allocate this ref struct on the stack in the two places
that initiate runtime async chains: task-returning thunks and
`DispatchContinuations`. Keep a pointer to this in the TLS.
- Use `Unsafe` in a couple of places to avoid unnecessary cast checks on
the hot path
For a suspension heavy benchmark this improves performance by around
25%.
Comment on lines +248 to +252
// This relies on coreclr and NAOT runtimes always storing ThreadStatic pointer fields in a pinned fashion.
fixed (AsyncDispatcherInfo** pCurrentDispatcherInfo = &AsyncDispatcherInfo.t_current)
{
return CurrentDispatcherInfo = pCurrentDispatcherInfo;
}

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

@jkotas Is this a correct assumption?

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.

AFAIR you need FixedAddressValueType for it to be guaranteed (but that didnt work for non valuetypes iirc).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this a correct assumption?

I do not think it is a valid assumption for NAOT - the thread static block is not allocated as pinned here:

returnRuntimeImports.RhNewObject((MethodTable*)gcDesc);

Is it not possible to pass this around as a byref?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

No, this pointer is being stored inside a struct allocated in TLS.
I am not totally sure if we are going to be accessing this field in NativeAOT though. Probably for Environment.StackTrace support?
We can store the dispatcher information directly inside RuntimeAsyncAwaitState, or add an object wrapper to give it an extra level of indirection. But those options would require diagnostic changes.

Maybe I just revert this and live with the one extra TLS access.

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.

If that's only needed for StackTrace, would the optimization be possible at least when StackTraceSupport=false? Or would it still not be viable because you still need to be able to get the full stacktrace even without metadata?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I don't see much (if any) benefit from avoiding this particular TLS lookup in the benchmark. I've just removed it.

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 optimizes CoreCLR “runtime async” suspension/resumption by reducing TLS traffic, minimizing write barriers on hot paths, and consolidating context-handling work into new helpers used by the JIT’s async transformation.

Changes:

  • Refactors runtime-async state to cache TLS-derived values and to move notifier/context references into a stack-allocated “stack state” accessed via the thread-static await state.
  • Extends CORINFO_ASYNC_INFO and JIT/EE plumbing with new “finish suspension” helper method handles, and updates the JIT async transform to use them.
  • Updates task-returning thunk emission (VM + ILCompiler stubs) and related tooling (SuperPMI, R2R/AOT scanners) to match the new runtime-async APIs.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.csExposes InstanceIsFlowSuppressed for optimized flow-suppression checks.
src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.csUpdates leaf await helpers to use stack-backed runtime-async state.
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.csIntroduces stack-based runtime-async state, new finish-suspension helpers, and updates dispatch/suspension logic.
src/coreclr/inc/corinfo.hExtends CORINFO_ASYNC_INFO with new helper method handles.
src/coreclr/vm/jitinterface.cppPopulates new async helper handles in CEEInfo::getAsyncInfo.
src/coreclr/vm/metasig.hAdds metasigs for updated thunk finalization signatures.
src/coreclr/vm/corelib.hUpdates CoreLib binder entries for new helpers, thunk signatures, and runtime-async nested types/field.
src/coreclr/vm/asyncthunks.cppUpdates task-returning thunk IL emission to push/pop the new await state and pass it to finalizers.
src/coreclr/jit/async.hDeclares new JIT helper routines to finish suspension context handling.
src/coreclr/jit/async.cppReworks suspension context handling to use new finish-suspension helpers and adjusts capture/restore sequence.
src/coreclr/tools/superpmi/superpmi-shared/agnostic.hExtends SuperPMI agnostic async-info struct for new handles.
src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cppRecords/replays new async-info handles.
src/coreclr/tools/Common/JitInterface/CorInfoTypes.csExtends managed projection of CORINFO_ASYNC_INFO.
src/coreclr/tools/Common/JitInterface/CorInfoImpl.csEmits new helper handles for the managed JIT interface implementation.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.csAdds R2R references to new finish-suspension helpers.
src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.csEnsures AOT scanning adds dependencies on new finish-suspension helpers.
src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.csUpdates IL stub emission for task-returning thunks to use new await-state push/pop + finalizer signatures.

CopilotAI review requested due to automatic review settings April 23, 2026 18:51

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 optimizes CoreCLR “runtime async” suspend/resume paths by reducing TLS accesses, lowering GC write barrier traffic, and avoiding some hot-path cast checks. It does so by introducing a stack-allocated state container that’s referenced via a per-thread TLS struct and by updating the thunk emitters to pass the TLS state byref.

Changes:

  • Introduces a stack-allocated RuntimeAsyncStackState and threads it through runtime-async chains via RuntimeAsyncAwaitState.Push/Pop.
  • Updates runtime-emitted task-returning thunks (VM + IL emitter) to initialize/teardown the new TLS stack state and pass ref RuntimeAsyncAwaitState into finalize helpers.
  • Switches a few hot-path casts to Unsafe.As and refactors await helpers to write notifier/context data into the stack state.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
FileDescription
src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.csUpdates public await helper intrinsics to write notifier into stack state via TLS.
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.csReplaces ExecutionAndSyncBlockStore with stack state + TLS Push/Pop, updates dispatch/finalize/handle-suspend flow.
src/coreclr/vm/metasig.hAdds metasigs for finalize helpers that now take ref RuntimeAsyncAwaitState.
src/coreclr/vm/corelib.hUpdates CoreLib binder entries for new TLS field, nested types, and finalize helper signatures.
src/coreclr/vm/asyncthunks.cppUpdates IL stub emission to Push/Pop TLS stack state and pass ref state into finalize helpers.
src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.csMirrors VM thunk emission changes in the managed IL emitter.

CopilotAI review requested due to automatic review settings April 24, 2026 08:21

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 optimizes CoreCLR “runtime async” suspension/resumption by reducing TLS lookups and write barriers, primarily by introducing a stack-allocated async state block that’s referenced from TLS during async-chain execution.

Changes:

  • Rework async await state handling to route notifier/context storage through a stack-allocated RuntimeAsyncStackState linked via TLS RuntimeAsyncAwaitState.
  • Update CoreCLR async thunk IL emission (VM + managed emitter) to Push/Pop the new TLS state and to pass the TLS state byref into Finalize*ReturningThunk.
  • Adjust CoreLib binder/metasig definitions to match the new helper signatures and types.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.csUpdate leaf await helpers to write notifier data into stack state via t_runtimeAsyncAwaitState.StackState.
src/coreclr/vm/metasig.hAdd new metasigs for Finalize*ReturningThunk(ref RuntimeAsyncAwaitState) (Task/ValueTask, generic and non-generic).
src/coreclr/vm/corelib.hUpdate binder definitions: remove ExecutionAndSyncBlockStore, add TLS field and nested async state types/methods.
src/coreclr/vm/asyncthunks.cppUpdate VM-emitted task-returning thunk IL to initialize and push/pop the new runtime async state and pass it to finalizers.
src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.csMirror VM thunk emission updates in the managed IL emitter (push/pop + updated finalizer signatures).
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.csImplement new stack/TLS async state structs, update dispatch/suspension logic, and adjust hot-path casts using Unsafe.

@jakobbotsch
jakobbotsch marked this pull request as ready for review April 24, 2026 15:17
@jakobbotsch

Copy link
Copy Markdown
MemberAuthor

PTAL @VSadov

@jakobbotsch
jakobbotsch requested a review from VSadovApril 24, 2026 15:17

@VSadovVSadov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM. Thanks!

CopilotAI review requested due to automatic review settings April 25, 2026 13:02

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 optimizes CoreCLR’s runtime-async suspend/resume path by reducing TLS traffic and GC write barriers, primarily by moving per-suspension state into a stack-allocated ref struct and caching Thread.CurrentThread in the TLS state.

Changes:

  • Introduce stack-allocated runtime-async state (RuntimeAsyncStackState) and keep only a pointer to it in TLS (RuntimeAsyncAwaitState).
  • Update task-returning thunk emission (VM + managed typesystem) to Push/Pop runtime-async state and pass ref RuntimeAsyncAwaitState into finalization helpers.
  • Update CoreLib binder signatures (corelib.h / metasig.h) to match the new helper method signatures and new nested types/fields.

Reviewed changes

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

Show a summary per file
FileDescription
src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.csSwitch await helpers to use stack-state via t_runtimeAsyncAwaitState.StackState and reduce TLS accesses.
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.csAdd RuntimeAsyncStackState + new TLS layout; adjust suspension/dispatch/finalization paths; use Unsafe.As for hot casts.
src/coreclr/vm/asyncthunks.cppUpdate VM-emitted task-returning thunk IL to Push/Pop runtime-async state and call new finalize signatures.
src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.csMirror VM thunk emission changes in the managed typesystem IL stub emitter.
src/coreclr/vm/metasig.hAdd metasig variants for finalize helpers that take ref RuntimeAsyncAwaitState.
src/coreclr/vm/corelib.hBind new nested types/field and update method signatures used by the VM binder.

Comment threadsrc/coreclr/vm/asyncthunks.cpp
Comment threadsrc/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.cs
@jakobbotsch
jakobbotsch merged commit 7e17692 into dotnet:mainApr 25, 2026
159 checks passed
@jakobbotsch
jakobbotsch deleted the unsafify-runtime-async branch April 25, 2026 19:45
This was referenced Apr 28, 2026
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Apr 28, 2026
PR dotnet#127336 (Optimize runtime async suspend/resume machinery) changed
AsyncDispatcherInfo.t_current access from ldsfld/stsfld to ldsflda.
This prevents the type from being resolved by name in R2R mode, and
R2R continuation layout fixups may not trigger getContinuationType,
leaving g_pContinuationClassIfSubTypeCreated null.
Changes:
- Switch AsyncContinuation debuggee to JIT-only mode (R2RModes=Jit)
so the JIT async transformation creates continuation MTs
- Make OuterAsync use a non-tail await (use value after await)
- Hide FailFast behind an indirect call to prevent the JIT from
treating post-await code as unreachable
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Apr 29, 2026
The debuggees were being R2R-compiled with the SDK's bundled crossgen2
pack (pinned in global.json), not the crossgen2 built from the current
commit. This caused R2R/CoreLib version skew when CoreLib types/signatures
changed between SDK pinning and the current build (e.g. PR dotnet#127336
removed ExecutionAndSyncBlockStore, but SDK preview3 crossgen2 still
emits R2R thunks referencing the removed type, causing TypeLoadException
at runtime).
Changes:
- Import targetingpacks.targets in Debuggees/Directory.Build.targets so
UseLocalCrossgen2Pack=true and UpdateLocalCrossgen2Pack target redirect
ResolvedCrossgen2Pack to the locally-built crossgen2.
- Pipe the runtime configuration through DumpTests.targets so the publish
command resolves Crossgen2InBuildDir correctly when CoreCLR is built
with a different configuration than the libs (e.g. -rc checked -lc release).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb added a commit that referenced this pull request Apr 30, 2026
> [!NOTE]
> This PR was created with the assistance of GitHub Copilot
(AI-generated content).
## Problem
The `AsyncContinuationDumpTests` (and potentially other R2R dump tests)
fail in CI because the debuggee apps are R2R-compiled with the SDK's
bundled crossgen2 instead of the locally-built one. When the
locally-built runtime removes or changes types between previews (e.g. PR
#127336 removing `ExecutionAndSyncBlockStore`), the SDK's crossgen2
emits R2R thunks referencing types that no longer exist, causing
`TypeLoadException` or null globals at runtime.
## Root Cause
Two issues prevent the locally-built crossgen2 from being used:
1. **NuGet pack layout mismatch**: The SDK's
`ResolveReadyToRunCompilers` task expects crossgen2 at
`PackagePath/tools/crossgen2.exe` (NuGet pack layout), but the local
build produces a flat layout with `crossgen2.exe` at the root of
`Crossgen2InBuildDir`. `targetingpacks.targets` alone cannot fix this
because it only updates `PackageDirectory` without restructuring the
layout.
2. **Missing property propagation**: The child `dotnet publish` process
doesn't inherit `RuntimeConfiguration`, `TargetArchitecture`,
`TargetOS`, or `BuildArchitecture` from the outer build, causing
crossgen2 and runtime pack paths to resolve incorrectly (especially in
cross-build scenarios like building on linux-x64 for linux-arm).
## Fix
### `Debuggees/Directory.Build.targets`
- Import `targetingpacks.targets` for runtime/targeting pack resolution
- Import `tests.readytorun.targets` via `AfterMicrosoftNETSdkTargets` to
override `ResolveReadyToRunCompilers` after the SDK defines it, pointing
directly at the locally-built crossgen2
### `DumpTests.targets`
- Add `_DebuggeeRuntimeConfig` property (falls back from
`RuntimeConfiguration` -> `Configuration` -> `Debug`)
- Add `_DebuggeePublishProps` to propagate `RuntimeConfiguration`,
`TargetArchitecture`, `TargetOS`, and `BuildArchitecture` to child
`dotnet publish` calls
### `prepare-cdac-helix-steps.yml`
- Add `runtimeConfiguration` parameter (default: `Checked`) and pass it
to `BuildDebuggeesOnly` so crossgen2 is found under the correct
`Checked` artifacts path
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 26, 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.

6 participants

@jakobbotsch@jkotas@VSadov@Sergio0694@MichalPetryka
, '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

Optimize runtime async suspend/resume machinery - #127336

Merged
jakobbotsch merged 8 commits into
dotnet:mainfrom
jakobbotsch:unsafify-runtime-async
Apr 25, 2026
Merged

Optimize runtime async suspend/resume machinery#127336
jakobbotsch merged 8 commits into
dotnet:mainfrom
jakobbotsch:unsafify-runtime-async

Conversation

@jakobbotsch

@jakobbotschjakobbotsch commented Apr 23, 2026

Copy link
Copy Markdown
Member

Several optimizations around suspension/resumption:

  • Reduce number of TLS accesses by storing Thread.CurrentThread inside RuntimeAsyncAwaitState, and only accessing RuntimeAsyncAwaitState
  • Remove a number of write barriers by moving TLS object fields into a ref struct. Allocate this ref struct on the stack in the two places that initiate runtime async chains: task-returning thunks and DispatchContinuations. Keep a pointer to this in the TLS.
  • Use Unsafe in a couple of places to avoid unnecessary cast checks on the hot path

For a suspension heavy benchmark this improves performance by around 17%.

Example benchmark
usingSystem;usingSystem.Diagnostics;usingSystem.Runtime.CompilerServices;usingSystem.Threading;usingSystem.Threading.Tasks;namespaceOSRPerf;publicclassProgram{staticvoidMain(){NullAwaiterna=newNullAwaiter();for(inti=0;i<10;i++){for(intj=0;j<500;j++){Taskt=Foo(20,na);while(!t.IsCompleted){na.Continue();}}Thread.Sleep(100);}for(inti=0;i<50;i++){Taskt=Foo(10_000_000,na);while(!t.IsCompleted){na.Continue();}}}staticints_value;staticasyncTaskFoo(intn,NullAwaiterna){for(inti=0;i<n;i++){s_value+=i;}Stopwatchtimer=Stopwatch.StartNew();for(inti=0;i<n;i++){awaitna;}if(n>1000)Console.WriteLine("Took {0:F1} ms",timer.Elapsed.TotalMilliseconds);}privateclassNullAwaiter:ICriticalNotifyCompletion{publicActionContinue;publicNullAwaiterGetAwaiter()=>this;publicboolIsCompleted=>false;publicvoidGetResult(){}publicvoidUnsafeOnCompleted(Actioncontinuation){Continue=continuation;}publicvoidOnCompleted(Actioncontinuation){thrownewNotImplementedException();}}}

Before: Took 350.3 ms
After: Took 291.3 ms

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

Several optimizations around suspension/resumption:
- Reduce number of TLS accesses by storing `Thread.CurrentThread` and
`&AsyncDispatcherInfo.t_current` inside `RuntimeAsyncAwaitState`, and
only accessing `RuntimeAsyncAwaitState`
- Remove a number of write barriers by moving TLS object fields into a
`ref struct`. Allocate this ref struct on the stack in the two places
that initiate runtime async chains: task-returning thunks and
`DispatchContinuations`. Keep a pointer to this in the TLS.
- Use `Unsafe` in a couple of places to avoid unnecessary cast checks on
the hot path
For a suspension heavy benchmark this improves performance by around
25%.
Comment on lines +248 to +252
// This relies on coreclr and NAOT runtimes always storing ThreadStatic pointer fields in a pinned fashion.
fixed (AsyncDispatcherInfo** pCurrentDispatcherInfo = &AsyncDispatcherInfo.t_current)
{
return CurrentDispatcherInfo = pCurrentDispatcherInfo;
}

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

@jkotas Is this a correct assumption?

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.

AFAIR you need FixedAddressValueType for it to be guaranteed (but that didnt work for non valuetypes iirc).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this a correct assumption?

I do not think it is a valid assumption for NAOT - the thread static block is not allocated as pinned here:

returnRuntimeImports.RhNewObject((MethodTable*)gcDesc);

Is it not possible to pass this around as a byref?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

No, this pointer is being stored inside a struct allocated in TLS.
I am not totally sure if we are going to be accessing this field in NativeAOT though. Probably for Environment.StackTrace support?
We can store the dispatcher information directly inside RuntimeAsyncAwaitState, or add an object wrapper to give it an extra level of indirection. But those options would require diagnostic changes.

Maybe I just revert this and live with the one extra TLS access.

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.

If that's only needed for StackTrace, would the optimization be possible at least when StackTraceSupport=false? Or would it still not be viable because you still need to be able to get the full stacktrace even without metadata?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I don't see much (if any) benefit from avoiding this particular TLS lookup in the benchmark. I've just removed it.

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 optimizes CoreCLR “runtime async” suspension/resumption by reducing TLS traffic, minimizing write barriers on hot paths, and consolidating context-handling work into new helpers used by the JIT’s async transformation.

Changes:

  • Refactors runtime-async state to cache TLS-derived values and to move notifier/context references into a stack-allocated “stack state” accessed via the thread-static await state.
  • Extends CORINFO_ASYNC_INFO and JIT/EE plumbing with new “finish suspension” helper method handles, and updates the JIT async transform to use them.
  • Updates task-returning thunk emission (VM + ILCompiler stubs) and related tooling (SuperPMI, R2R/AOT scanners) to match the new runtime-async APIs.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.csExposes InstanceIsFlowSuppressed for optimized flow-suppression checks.
src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.csUpdates leaf await helpers to use stack-backed runtime-async state.
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.csIntroduces stack-based runtime-async state, new finish-suspension helpers, and updates dispatch/suspension logic.
src/coreclr/inc/corinfo.hExtends CORINFO_ASYNC_INFO with new helper method handles.
src/coreclr/vm/jitinterface.cppPopulates new async helper handles in CEEInfo::getAsyncInfo.
src/coreclr/vm/metasig.hAdds metasigs for updated thunk finalization signatures.
src/coreclr/vm/corelib.hUpdates CoreLib binder entries for new helpers, thunk signatures, and runtime-async nested types/field.
src/coreclr/vm/asyncthunks.cppUpdates task-returning thunk IL emission to push/pop the new await state and pass it to finalizers.
src/coreclr/jit/async.hDeclares new JIT helper routines to finish suspension context handling.
src/coreclr/jit/async.cppReworks suspension context handling to use new finish-suspension helpers and adjusts capture/restore sequence.
src/coreclr/tools/superpmi/superpmi-shared/agnostic.hExtends SuperPMI agnostic async-info struct for new handles.
src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cppRecords/replays new async-info handles.
src/coreclr/tools/Common/JitInterface/CorInfoTypes.csExtends managed projection of CORINFO_ASYNC_INFO.
src/coreclr/tools/Common/JitInterface/CorInfoImpl.csEmits new helper handles for the managed JIT interface implementation.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.csAdds R2R references to new finish-suspension helpers.
src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.csEnsures AOT scanning adds dependencies on new finish-suspension helpers.
src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.csUpdates IL stub emission for task-returning thunks to use new await-state push/pop + finalizer signatures.

CopilotAI review requested due to automatic review settings April 23, 2026 18:51

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 optimizes CoreCLR “runtime async” suspend/resume paths by reducing TLS accesses, lowering GC write barrier traffic, and avoiding some hot-path cast checks. It does so by introducing a stack-allocated state container that’s referenced via a per-thread TLS struct and by updating the thunk emitters to pass the TLS state byref.

Changes:

  • Introduces a stack-allocated RuntimeAsyncStackState and threads it through runtime-async chains via RuntimeAsyncAwaitState.Push/Pop.
  • Updates runtime-emitted task-returning thunks (VM + IL emitter) to initialize/teardown the new TLS stack state and pass ref RuntimeAsyncAwaitState into finalize helpers.
  • Switches a few hot-path casts to Unsafe.As and refactors await helpers to write notifier/context data into the stack state.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
FileDescription
src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.csUpdates public await helper intrinsics to write notifier into stack state via TLS.
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.csReplaces ExecutionAndSyncBlockStore with stack state + TLS Push/Pop, updates dispatch/finalize/handle-suspend flow.
src/coreclr/vm/metasig.hAdds metasigs for finalize helpers that now take ref RuntimeAsyncAwaitState.
src/coreclr/vm/corelib.hUpdates CoreLib binder entries for new TLS field, nested types, and finalize helper signatures.
src/coreclr/vm/asyncthunks.cppUpdates IL stub emission to Push/Pop TLS stack state and pass ref state into finalize helpers.
src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.csMirrors VM thunk emission changes in the managed IL emitter.

CopilotAI review requested due to automatic review settings April 24, 2026 08:21

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 optimizes CoreCLR “runtime async” suspension/resumption by reducing TLS lookups and write barriers, primarily by introducing a stack-allocated async state block that’s referenced from TLS during async-chain execution.

Changes:

  • Rework async await state handling to route notifier/context storage through a stack-allocated RuntimeAsyncStackState linked via TLS RuntimeAsyncAwaitState.
  • Update CoreCLR async thunk IL emission (VM + managed emitter) to Push/Pop the new TLS state and to pass the TLS state byref into Finalize*ReturningThunk.
  • Adjust CoreLib binder/metasig definitions to match the new helper signatures and types.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.csUpdate leaf await helpers to write notifier data into stack state via t_runtimeAsyncAwaitState.StackState.
src/coreclr/vm/metasig.hAdd new metasigs for Finalize*ReturningThunk(ref RuntimeAsyncAwaitState) (Task/ValueTask, generic and non-generic).
src/coreclr/vm/corelib.hUpdate binder definitions: remove ExecutionAndSyncBlockStore, add TLS field and nested async state types/methods.
src/coreclr/vm/asyncthunks.cppUpdate VM-emitted task-returning thunk IL to initialize and push/pop the new runtime async state and pass it to finalizers.
src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.csMirror VM thunk emission updates in the managed IL emitter (push/pop + updated finalizer signatures).
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.csImplement new stack/TLS async state structs, update dispatch/suspension logic, and adjust hot-path casts using Unsafe.

@jakobbotsch
jakobbotsch marked this pull request as ready for review April 24, 2026 15:17
@jakobbotsch

Copy link
Copy Markdown
MemberAuthor

PTAL @VSadov

@jakobbotsch
jakobbotsch requested a review from VSadovApril 24, 2026 15:17

@VSadovVSadov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM. Thanks!

CopilotAI review requested due to automatic review settings April 25, 2026 13:02

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 optimizes CoreCLR’s runtime-async suspend/resume path by reducing TLS traffic and GC write barriers, primarily by moving per-suspension state into a stack-allocated ref struct and caching Thread.CurrentThread in the TLS state.

Changes:

  • Introduce stack-allocated runtime-async state (RuntimeAsyncStackState) and keep only a pointer to it in TLS (RuntimeAsyncAwaitState).
  • Update task-returning thunk emission (VM + managed typesystem) to Push/Pop runtime-async state and pass ref RuntimeAsyncAwaitState into finalization helpers.
  • Update CoreLib binder signatures (corelib.h / metasig.h) to match the new helper method signatures and new nested types/fields.

Reviewed changes

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

Show a summary per file
FileDescription
src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.csSwitch await helpers to use stack-state via t_runtimeAsyncAwaitState.StackState and reduce TLS accesses.
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.csAdd RuntimeAsyncStackState + new TLS layout; adjust suspension/dispatch/finalization paths; use Unsafe.As for hot casts.
src/coreclr/vm/asyncthunks.cppUpdate VM-emitted task-returning thunk IL to Push/Pop runtime-async state and call new finalize signatures.
src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.csMirror VM thunk emission changes in the managed typesystem IL stub emitter.
src/coreclr/vm/metasig.hAdd metasig variants for finalize helpers that take ref RuntimeAsyncAwaitState.
src/coreclr/vm/corelib.hBind new nested types/field and update method signatures used by the VM binder.

Comment threadsrc/coreclr/vm/asyncthunks.cpp
Comment threadsrc/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.cs
@jakobbotsch
jakobbotsch merged commit 7e17692 into dotnet:mainApr 25, 2026
159 checks passed
@jakobbotsch
jakobbotsch deleted the unsafify-runtime-async branch April 25, 2026 19:45
This was referenced Apr 28, 2026
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Apr 28, 2026
PR dotnet#127336 (Optimize runtime async suspend/resume machinery) changed
AsyncDispatcherInfo.t_current access from ldsfld/stsfld to ldsflda.
This prevents the type from being resolved by name in R2R mode, and
R2R continuation layout fixups may not trigger getContinuationType,
leaving g_pContinuationClassIfSubTypeCreated null.
Changes:
- Switch AsyncContinuation debuggee to JIT-only mode (R2RModes=Jit)
so the JIT async transformation creates continuation MTs
- Make OuterAsync use a non-tail await (use value after await)
- Hide FailFast behind an indirect call to prevent the JIT from
treating post-await code as unreachable
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Apr 29, 2026
The debuggees were being R2R-compiled with the SDK's bundled crossgen2
pack (pinned in global.json), not the crossgen2 built from the current
commit. This caused R2R/CoreLib version skew when CoreLib types/signatures
changed between SDK pinning and the current build (e.g. PR dotnet#127336
removed ExecutionAndSyncBlockStore, but SDK preview3 crossgen2 still
emits R2R thunks referencing the removed type, causing TypeLoadException
at runtime).
Changes:
- Import targetingpacks.targets in Debuggees/Directory.Build.targets so
UseLocalCrossgen2Pack=true and UpdateLocalCrossgen2Pack target redirect
ResolvedCrossgen2Pack to the locally-built crossgen2.
- Pipe the runtime configuration through DumpTests.targets so the publish
command resolves Crossgen2InBuildDir correctly when CoreCLR is built
with a different configuration than the libs (e.g. -rc checked -lc release).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb added a commit that referenced this pull request Apr 30, 2026
> [!NOTE]
> This PR was created with the assistance of GitHub Copilot
(AI-generated content).
## Problem
The `AsyncContinuationDumpTests` (and potentially other R2R dump tests)
fail in CI because the debuggee apps are R2R-compiled with the SDK's
bundled crossgen2 instead of the locally-built one. When the
locally-built runtime removes or changes types between previews (e.g. PR
#127336 removing `ExecutionAndSyncBlockStore`), the SDK's crossgen2
emits R2R thunks referencing types that no longer exist, causing
`TypeLoadException` or null globals at runtime.
## Root Cause
Two issues prevent the locally-built crossgen2 from being used:
1. **NuGet pack layout mismatch**: The SDK's
`ResolveReadyToRunCompilers` task expects crossgen2 at
`PackagePath/tools/crossgen2.exe` (NuGet pack layout), but the local
build produces a flat layout with `crossgen2.exe` at the root of
`Crossgen2InBuildDir`. `targetingpacks.targets` alone cannot fix this
because it only updates `PackageDirectory` without restructuring the
layout.
2. **Missing property propagation**: The child `dotnet publish` process
doesn't inherit `RuntimeConfiguration`, `TargetArchitecture`,
`TargetOS`, or `BuildArchitecture` from the outer build, causing
crossgen2 and runtime pack paths to resolve incorrectly (especially in
cross-build scenarios like building on linux-x64 for linux-arm).
## Fix
### `Debuggees/Directory.Build.targets`
- Import `targetingpacks.targets` for runtime/targeting pack resolution
- Import `tests.readytorun.targets` via `AfterMicrosoftNETSdkTargets` to
override `ResolveReadyToRunCompilers` after the SDK defines it, pointing
directly at the locally-built crossgen2
### `DumpTests.targets`
- Add `_DebuggeeRuntimeConfig` property (falls back from
`RuntimeConfiguration` -> `Configuration` -> `Debug`)
- Add `_DebuggeePublishProps` to propagate `RuntimeConfiguration`,
`TargetArchitecture`, `TargetOS`, and `BuildArchitecture` to child
`dotnet publish` calls
### `prepare-cdac-helix-steps.yml`
- Add `runtimeConfiguration` parameter (default: `Checked`) and pass it
to `BuildDebuggeesOnly` so crossgen2 is found under the correct
`Checked` artifacts path
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 26, 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.

6 participants

@jakobbotsch@jkotas@VSadov@Sergio0694@MichalPetryka
, '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

Optimize runtime async suspend/resume machinery - #127336

Merged
jakobbotsch merged 8 commits into
dotnet:mainfrom
jakobbotsch:unsafify-runtime-async
Apr 25, 2026
Merged

Optimize runtime async suspend/resume machinery#127336
jakobbotsch merged 8 commits into
dotnet:mainfrom
jakobbotsch:unsafify-runtime-async

Conversation

@jakobbotsch

@jakobbotschjakobbotsch commented Apr 23, 2026

Copy link
Copy Markdown
Member

Several optimizations around suspension/resumption:

  • Reduce number of TLS accesses by storing Thread.CurrentThread inside RuntimeAsyncAwaitState, and only accessing RuntimeAsyncAwaitState
  • Remove a number of write barriers by moving TLS object fields into a ref struct. Allocate this ref struct on the stack in the two places that initiate runtime async chains: task-returning thunks and DispatchContinuations. Keep a pointer to this in the TLS.
  • Use Unsafe in a couple of places to avoid unnecessary cast checks on the hot path

For a suspension heavy benchmark this improves performance by around 17%.

Example benchmark
usingSystem;usingSystem.Diagnostics;usingSystem.Runtime.CompilerServices;usingSystem.Threading;usingSystem.Threading.Tasks;namespaceOSRPerf;publicclassProgram{staticvoidMain(){NullAwaiterna=newNullAwaiter();for(inti=0;i<10;i++){for(intj=0;j<500;j++){Taskt=Foo(20,na);while(!t.IsCompleted){na.Continue();}}Thread.Sleep(100);}for(inti=0;i<50;i++){Taskt=Foo(10_000_000,na);while(!t.IsCompleted){na.Continue();}}}staticints_value;staticasyncTaskFoo(intn,NullAwaiterna){for(inti=0;i<n;i++){s_value+=i;}Stopwatchtimer=Stopwatch.StartNew();for(inti=0;i<n;i++){awaitna;}if(n>1000)Console.WriteLine("Took {0:F1} ms",timer.Elapsed.TotalMilliseconds);}privateclassNullAwaiter:ICriticalNotifyCompletion{publicActionContinue;publicNullAwaiterGetAwaiter()=>this;publicboolIsCompleted=>false;publicvoidGetResult(){}publicvoidUnsafeOnCompleted(Actioncontinuation){Continue=continuation;}publicvoidOnCompleted(Actioncontinuation){thrownewNotImplementedException();}}}

Before: Took 350.3 ms
After: Took 291.3 ms

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

Several optimizations around suspension/resumption:
- Reduce number of TLS accesses by storing `Thread.CurrentThread` and
`&AsyncDispatcherInfo.t_current` inside `RuntimeAsyncAwaitState`, and
only accessing `RuntimeAsyncAwaitState`
- Remove a number of write barriers by moving TLS object fields into a
`ref struct`. Allocate this ref struct on the stack in the two places
that initiate runtime async chains: task-returning thunks and
`DispatchContinuations`. Keep a pointer to this in the TLS.
- Use `Unsafe` in a couple of places to avoid unnecessary cast checks on
the hot path
For a suspension heavy benchmark this improves performance by around
25%.
Comment on lines +248 to +252
// This relies on coreclr and NAOT runtimes always storing ThreadStatic pointer fields in a pinned fashion.
fixed (AsyncDispatcherInfo** pCurrentDispatcherInfo = &AsyncDispatcherInfo.t_current)
{
return CurrentDispatcherInfo = pCurrentDispatcherInfo;
}

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

@jkotas Is this a correct assumption?

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.

AFAIR you need FixedAddressValueType for it to be guaranteed (but that didnt work for non valuetypes iirc).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this a correct assumption?

I do not think it is a valid assumption for NAOT - the thread static block is not allocated as pinned here:

returnRuntimeImports.RhNewObject((MethodTable*)gcDesc);

Is it not possible to pass this around as a byref?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

No, this pointer is being stored inside a struct allocated in TLS.
I am not totally sure if we are going to be accessing this field in NativeAOT though. Probably for Environment.StackTrace support?
We can store the dispatcher information directly inside RuntimeAsyncAwaitState, or add an object wrapper to give it an extra level of indirection. But those options would require diagnostic changes.

Maybe I just revert this and live with the one extra TLS access.

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.

If that's only needed for StackTrace, would the optimization be possible at least when StackTraceSupport=false? Or would it still not be viable because you still need to be able to get the full stacktrace even without metadata?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I don't see much (if any) benefit from avoiding this particular TLS lookup in the benchmark. I've just removed it.

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 optimizes CoreCLR “runtime async” suspension/resumption by reducing TLS traffic, minimizing write barriers on hot paths, and consolidating context-handling work into new helpers used by the JIT’s async transformation.

Changes:

  • Refactors runtime-async state to cache TLS-derived values and to move notifier/context references into a stack-allocated “stack state” accessed via the thread-static await state.
  • Extends CORINFO_ASYNC_INFO and JIT/EE plumbing with new “finish suspension” helper method handles, and updates the JIT async transform to use them.
  • Updates task-returning thunk emission (VM + ILCompiler stubs) and related tooling (SuperPMI, R2R/AOT scanners) to match the new runtime-async APIs.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.csExposes InstanceIsFlowSuppressed for optimized flow-suppression checks.
src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.csUpdates leaf await helpers to use stack-backed runtime-async state.
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.csIntroduces stack-based runtime-async state, new finish-suspension helpers, and updates dispatch/suspension logic.
src/coreclr/inc/corinfo.hExtends CORINFO_ASYNC_INFO with new helper method handles.
src/coreclr/vm/jitinterface.cppPopulates new async helper handles in CEEInfo::getAsyncInfo.
src/coreclr/vm/metasig.hAdds metasigs for updated thunk finalization signatures.
src/coreclr/vm/corelib.hUpdates CoreLib binder entries for new helpers, thunk signatures, and runtime-async nested types/field.
src/coreclr/vm/asyncthunks.cppUpdates task-returning thunk IL emission to push/pop the new await state and pass it to finalizers.
src/coreclr/jit/async.hDeclares new JIT helper routines to finish suspension context handling.
src/coreclr/jit/async.cppReworks suspension context handling to use new finish-suspension helpers and adjusts capture/restore sequence.
src/coreclr/tools/superpmi/superpmi-shared/agnostic.hExtends SuperPMI agnostic async-info struct for new handles.
src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cppRecords/replays new async-info handles.
src/coreclr/tools/Common/JitInterface/CorInfoTypes.csExtends managed projection of CORINFO_ASYNC_INFO.
src/coreclr/tools/Common/JitInterface/CorInfoImpl.csEmits new helper handles for the managed JIT interface implementation.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.csAdds R2R references to new finish-suspension helpers.
src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.csEnsures AOT scanning adds dependencies on new finish-suspension helpers.
src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.csUpdates IL stub emission for task-returning thunks to use new await-state push/pop + finalizer signatures.

CopilotAI review requested due to automatic review settings April 23, 2026 18:51

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 optimizes CoreCLR “runtime async” suspend/resume paths by reducing TLS accesses, lowering GC write barrier traffic, and avoiding some hot-path cast checks. It does so by introducing a stack-allocated state container that’s referenced via a per-thread TLS struct and by updating the thunk emitters to pass the TLS state byref.

Changes:

  • Introduces a stack-allocated RuntimeAsyncStackState and threads it through runtime-async chains via RuntimeAsyncAwaitState.Push/Pop.
  • Updates runtime-emitted task-returning thunks (VM + IL emitter) to initialize/teardown the new TLS stack state and pass ref RuntimeAsyncAwaitState into finalize helpers.
  • Switches a few hot-path casts to Unsafe.As and refactors await helpers to write notifier/context data into the stack state.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
FileDescription
src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.csUpdates public await helper intrinsics to write notifier into stack state via TLS.
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.csReplaces ExecutionAndSyncBlockStore with stack state + TLS Push/Pop, updates dispatch/finalize/handle-suspend flow.
src/coreclr/vm/metasig.hAdds metasigs for finalize helpers that now take ref RuntimeAsyncAwaitState.
src/coreclr/vm/corelib.hUpdates CoreLib binder entries for new TLS field, nested types, and finalize helper signatures.
src/coreclr/vm/asyncthunks.cppUpdates IL stub emission to Push/Pop TLS stack state and pass ref state into finalize helpers.
src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.csMirrors VM thunk emission changes in the managed IL emitter.

CopilotAI review requested due to automatic review settings April 24, 2026 08:21

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 optimizes CoreCLR “runtime async” suspension/resumption by reducing TLS lookups and write barriers, primarily by introducing a stack-allocated async state block that’s referenced from TLS during async-chain execution.

Changes:

  • Rework async await state handling to route notifier/context storage through a stack-allocated RuntimeAsyncStackState linked via TLS RuntimeAsyncAwaitState.
  • Update CoreCLR async thunk IL emission (VM + managed emitter) to Push/Pop the new TLS state and to pass the TLS state byref into Finalize*ReturningThunk.
  • Adjust CoreLib binder/metasig definitions to match the new helper signatures and types.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.csUpdate leaf await helpers to write notifier data into stack state via t_runtimeAsyncAwaitState.StackState.
src/coreclr/vm/metasig.hAdd new metasigs for Finalize*ReturningThunk(ref RuntimeAsyncAwaitState) (Task/ValueTask, generic and non-generic).
src/coreclr/vm/corelib.hUpdate binder definitions: remove ExecutionAndSyncBlockStore, add TLS field and nested async state types/methods.
src/coreclr/vm/asyncthunks.cppUpdate VM-emitted task-returning thunk IL to initialize and push/pop the new runtime async state and pass it to finalizers.
src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.csMirror VM thunk emission updates in the managed IL emitter (push/pop + updated finalizer signatures).
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.csImplement new stack/TLS async state structs, update dispatch/suspension logic, and adjust hot-path casts using Unsafe.

@jakobbotsch
jakobbotsch marked this pull request as ready for review April 24, 2026 15:17
@jakobbotsch

Copy link
Copy Markdown
MemberAuthor

PTAL @VSadov

@jakobbotsch
jakobbotsch requested a review from VSadovApril 24, 2026 15:17

@VSadovVSadov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM. Thanks!

CopilotAI review requested due to automatic review settings April 25, 2026 13:02

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 optimizes CoreCLR’s runtime-async suspend/resume path by reducing TLS traffic and GC write barriers, primarily by moving per-suspension state into a stack-allocated ref struct and caching Thread.CurrentThread in the TLS state.

Changes:

  • Introduce stack-allocated runtime-async state (RuntimeAsyncStackState) and keep only a pointer to it in TLS (RuntimeAsyncAwaitState).
  • Update task-returning thunk emission (VM + managed typesystem) to Push/Pop runtime-async state and pass ref RuntimeAsyncAwaitState into finalization helpers.
  • Update CoreLib binder signatures (corelib.h / metasig.h) to match the new helper method signatures and new nested types/fields.

Reviewed changes

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

Show a summary per file
FileDescription
src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.csSwitch await helpers to use stack-state via t_runtimeAsyncAwaitState.StackState and reduce TLS accesses.
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.csAdd RuntimeAsyncStackState + new TLS layout; adjust suspension/dispatch/finalization paths; use Unsafe.As for hot casts.
src/coreclr/vm/asyncthunks.cppUpdate VM-emitted task-returning thunk IL to Push/Pop runtime-async state and call new finalize signatures.
src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.csMirror VM thunk emission changes in the managed typesystem IL stub emitter.
src/coreclr/vm/metasig.hAdd metasig variants for finalize helpers that take ref RuntimeAsyncAwaitState.
src/coreclr/vm/corelib.hBind new nested types/field and update method signatures used by the VM binder.

Comment threadsrc/coreclr/vm/asyncthunks.cpp
Comment threadsrc/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.cs
@jakobbotsch
jakobbotsch merged commit 7e17692 into dotnet:mainApr 25, 2026
159 checks passed
@jakobbotsch
jakobbotsch deleted the unsafify-runtime-async branch April 25, 2026 19:45
This was referenced Apr 28, 2026
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Apr 28, 2026
PR dotnet#127336 (Optimize runtime async suspend/resume machinery) changed
AsyncDispatcherInfo.t_current access from ldsfld/stsfld to ldsflda.
This prevents the type from being resolved by name in R2R mode, and
R2R continuation layout fixups may not trigger getContinuationType,
leaving g_pContinuationClassIfSubTypeCreated null.
Changes:
- Switch AsyncContinuation debuggee to JIT-only mode (R2RModes=Jit)
so the JIT async transformation creates continuation MTs
- Make OuterAsync use a non-tail await (use value after await)
- Hide FailFast behind an indirect call to prevent the JIT from
treating post-await code as unreachable
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Apr 29, 2026
The debuggees were being R2R-compiled with the SDK's bundled crossgen2
pack (pinned in global.json), not the crossgen2 built from the current
commit. This caused R2R/CoreLib version skew when CoreLib types/signatures
changed between SDK pinning and the current build (e.g. PR dotnet#127336
removed ExecutionAndSyncBlockStore, but SDK preview3 crossgen2 still
emits R2R thunks referencing the removed type, causing TypeLoadException
at runtime).
Changes:
- Import targetingpacks.targets in Debuggees/Directory.Build.targets so
UseLocalCrossgen2Pack=true and UpdateLocalCrossgen2Pack target redirect
ResolvedCrossgen2Pack to the locally-built crossgen2.
- Pipe the runtime configuration through DumpTests.targets so the publish
command resolves Crossgen2InBuildDir correctly when CoreCLR is built
with a different configuration than the libs (e.g. -rc checked -lc release).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb added a commit that referenced this pull request Apr 30, 2026
> [!NOTE]
> This PR was created with the assistance of GitHub Copilot
(AI-generated content).
## Problem
The `AsyncContinuationDumpTests` (and potentially other R2R dump tests)
fail in CI because the debuggee apps are R2R-compiled with the SDK's
bundled crossgen2 instead of the locally-built one. When the
locally-built runtime removes or changes types between previews (e.g. PR
#127336 removing `ExecutionAndSyncBlockStore`), the SDK's crossgen2
emits R2R thunks referencing types that no longer exist, causing
`TypeLoadException` or null globals at runtime.
## Root Cause
Two issues prevent the locally-built crossgen2 from being used:
1. **NuGet pack layout mismatch**: The SDK's
`ResolveReadyToRunCompilers` task expects crossgen2 at
`PackagePath/tools/crossgen2.exe` (NuGet pack layout), but the local
build produces a flat layout with `crossgen2.exe` at the root of
`Crossgen2InBuildDir`. `targetingpacks.targets` alone cannot fix this
because it only updates `PackageDirectory` without restructuring the
layout.
2. **Missing property propagation**: The child `dotnet publish` process
doesn't inherit `RuntimeConfiguration`, `TargetArchitecture`,
`TargetOS`, or `BuildArchitecture` from the outer build, causing
crossgen2 and runtime pack paths to resolve incorrectly (especially in
cross-build scenarios like building on linux-x64 for linux-arm).
## Fix
### `Debuggees/Directory.Build.targets`
- Import `targetingpacks.targets` for runtime/targeting pack resolution
- Import `tests.readytorun.targets` via `AfterMicrosoftNETSdkTargets` to
override `ResolveReadyToRunCompilers` after the SDK defines it, pointing
directly at the locally-built crossgen2
### `DumpTests.targets`
- Add `_DebuggeeRuntimeConfig` property (falls back from
`RuntimeConfiguration` -> `Configuration` -> `Debug`)
- Add `_DebuggeePublishProps` to propagate `RuntimeConfiguration`,
`TargetArchitecture`, `TargetOS`, and `BuildArchitecture` to child
`dotnet publish` calls
### `prepare-cdac-helix-steps.yml`
- Add `runtimeConfiguration` parameter (default: `Checked`) and pass it
to `BuildDebuggeesOnly` so crossgen2 is found under the correct
`Checked` artifacts path
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 26, 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.

6 participants

@jakobbotsch@jkotas@VSadov@Sergio0694@MichalPetryka
, '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

Optimize runtime async suspend/resume machinery - #127336

Merged
jakobbotsch merged 8 commits into
dotnet:mainfrom
jakobbotsch:unsafify-runtime-async
Apr 25, 2026
Merged

Optimize runtime async suspend/resume machinery#127336
jakobbotsch merged 8 commits into
dotnet:mainfrom
jakobbotsch:unsafify-runtime-async

Conversation

@jakobbotsch

@jakobbotschjakobbotsch commented Apr 23, 2026

Copy link
Copy Markdown
Member

Several optimizations around suspension/resumption:

  • Reduce number of TLS accesses by storing Thread.CurrentThread inside RuntimeAsyncAwaitState, and only accessing RuntimeAsyncAwaitState
  • Remove a number of write barriers by moving TLS object fields into a ref struct. Allocate this ref struct on the stack in the two places that initiate runtime async chains: task-returning thunks and DispatchContinuations. Keep a pointer to this in the TLS.
  • Use Unsafe in a couple of places to avoid unnecessary cast checks on the hot path

For a suspension heavy benchmark this improves performance by around 17%.

Example benchmark
usingSystem;usingSystem.Diagnostics;usingSystem.Runtime.CompilerServices;usingSystem.Threading;usingSystem.Threading.Tasks;namespaceOSRPerf;publicclassProgram{staticvoidMain(){NullAwaiterna=newNullAwaiter();for(inti=0;i<10;i++){for(intj=0;j<500;j++){Taskt=Foo(20,na);while(!t.IsCompleted){na.Continue();}}Thread.Sleep(100);}for(inti=0;i<50;i++){Taskt=Foo(10_000_000,na);while(!t.IsCompleted){na.Continue();}}}staticints_value;staticasyncTaskFoo(intn,NullAwaiterna){for(inti=0;i<n;i++){s_value+=i;}Stopwatchtimer=Stopwatch.StartNew();for(inti=0;i<n;i++){awaitna;}if(n>1000)Console.WriteLine("Took {0:F1} ms",timer.Elapsed.TotalMilliseconds);}privateclassNullAwaiter:ICriticalNotifyCompletion{publicActionContinue;publicNullAwaiterGetAwaiter()=>this;publicboolIsCompleted=>false;publicvoidGetResult(){}publicvoidUnsafeOnCompleted(Actioncontinuation){Continue=continuation;}publicvoidOnCompleted(Actioncontinuation){thrownewNotImplementedException();}}}

Before: Took 350.3 ms
After: Took 291.3 ms

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

Several optimizations around suspension/resumption:
- Reduce number of TLS accesses by storing `Thread.CurrentThread` and
`&AsyncDispatcherInfo.t_current` inside `RuntimeAsyncAwaitState`, and
only accessing `RuntimeAsyncAwaitState`
- Remove a number of write barriers by moving TLS object fields into a
`ref struct`. Allocate this ref struct on the stack in the two places
that initiate runtime async chains: task-returning thunks and
`DispatchContinuations`. Keep a pointer to this in the TLS.
- Use `Unsafe` in a couple of places to avoid unnecessary cast checks on
the hot path
For a suspension heavy benchmark this improves performance by around
25%.
Comment on lines +248 to +252
// This relies on coreclr and NAOT runtimes always storing ThreadStatic pointer fields in a pinned fashion.
fixed (AsyncDispatcherInfo** pCurrentDispatcherInfo = &AsyncDispatcherInfo.t_current)
{
return CurrentDispatcherInfo = pCurrentDispatcherInfo;
}

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

@jkotas Is this a correct assumption?

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.

AFAIR you need FixedAddressValueType for it to be guaranteed (but that didnt work for non valuetypes iirc).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this a correct assumption?

I do not think it is a valid assumption for NAOT - the thread static block is not allocated as pinned here:

returnRuntimeImports.RhNewObject((MethodTable*)gcDesc);

Is it not possible to pass this around as a byref?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

No, this pointer is being stored inside a struct allocated in TLS.
I am not totally sure if we are going to be accessing this field in NativeAOT though. Probably for Environment.StackTrace support?
We can store the dispatcher information directly inside RuntimeAsyncAwaitState, or add an object wrapper to give it an extra level of indirection. But those options would require diagnostic changes.

Maybe I just revert this and live with the one extra TLS access.

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.

If that's only needed for StackTrace, would the optimization be possible at least when StackTraceSupport=false? Or would it still not be viable because you still need to be able to get the full stacktrace even without metadata?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I don't see much (if any) benefit from avoiding this particular TLS lookup in the benchmark. I've just removed it.

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 optimizes CoreCLR “runtime async” suspension/resumption by reducing TLS traffic, minimizing write barriers on hot paths, and consolidating context-handling work into new helpers used by the JIT’s async transformation.

Changes:

  • Refactors runtime-async state to cache TLS-derived values and to move notifier/context references into a stack-allocated “stack state” accessed via the thread-static await state.
  • Extends CORINFO_ASYNC_INFO and JIT/EE plumbing with new “finish suspension” helper method handles, and updates the JIT async transform to use them.
  • Updates task-returning thunk emission (VM + ILCompiler stubs) and related tooling (SuperPMI, R2R/AOT scanners) to match the new runtime-async APIs.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.csExposes InstanceIsFlowSuppressed for optimized flow-suppression checks.
src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.csUpdates leaf await helpers to use stack-backed runtime-async state.
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.csIntroduces stack-based runtime-async state, new finish-suspension helpers, and updates dispatch/suspension logic.
src/coreclr/inc/corinfo.hExtends CORINFO_ASYNC_INFO with new helper method handles.
src/coreclr/vm/jitinterface.cppPopulates new async helper handles in CEEInfo::getAsyncInfo.
src/coreclr/vm/metasig.hAdds metasigs for updated thunk finalization signatures.
src/coreclr/vm/corelib.hUpdates CoreLib binder entries for new helpers, thunk signatures, and runtime-async nested types/field.
src/coreclr/vm/asyncthunks.cppUpdates task-returning thunk IL emission to push/pop the new await state and pass it to finalizers.
src/coreclr/jit/async.hDeclares new JIT helper routines to finish suspension context handling.
src/coreclr/jit/async.cppReworks suspension context handling to use new finish-suspension helpers and adjusts capture/restore sequence.
src/coreclr/tools/superpmi/superpmi-shared/agnostic.hExtends SuperPMI agnostic async-info struct for new handles.
src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cppRecords/replays new async-info handles.
src/coreclr/tools/Common/JitInterface/CorInfoTypes.csExtends managed projection of CORINFO_ASYNC_INFO.
src/coreclr/tools/Common/JitInterface/CorInfoImpl.csEmits new helper handles for the managed JIT interface implementation.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.csAdds R2R references to new finish-suspension helpers.
src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.csEnsures AOT scanning adds dependencies on new finish-suspension helpers.
src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.csUpdates IL stub emission for task-returning thunks to use new await-state push/pop + finalizer signatures.

CopilotAI review requested due to automatic review settings April 23, 2026 18:51

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 optimizes CoreCLR “runtime async” suspend/resume paths by reducing TLS accesses, lowering GC write barrier traffic, and avoiding some hot-path cast checks. It does so by introducing a stack-allocated state container that’s referenced via a per-thread TLS struct and by updating the thunk emitters to pass the TLS state byref.

Changes:

  • Introduces a stack-allocated RuntimeAsyncStackState and threads it through runtime-async chains via RuntimeAsyncAwaitState.Push/Pop.
  • Updates runtime-emitted task-returning thunks (VM + IL emitter) to initialize/teardown the new TLS stack state and pass ref RuntimeAsyncAwaitState into finalize helpers.
  • Switches a few hot-path casts to Unsafe.As and refactors await helpers to write notifier/context data into the stack state.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
FileDescription
src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.csUpdates public await helper intrinsics to write notifier into stack state via TLS.
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.csReplaces ExecutionAndSyncBlockStore with stack state + TLS Push/Pop, updates dispatch/finalize/handle-suspend flow.
src/coreclr/vm/metasig.hAdds metasigs for finalize helpers that now take ref RuntimeAsyncAwaitState.
src/coreclr/vm/corelib.hUpdates CoreLib binder entries for new TLS field, nested types, and finalize helper signatures.
src/coreclr/vm/asyncthunks.cppUpdates IL stub emission to Push/Pop TLS stack state and pass ref state into finalize helpers.
src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.csMirrors VM thunk emission changes in the managed IL emitter.

CopilotAI review requested due to automatic review settings April 24, 2026 08:21

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 optimizes CoreCLR “runtime async” suspension/resumption by reducing TLS lookups and write barriers, primarily by introducing a stack-allocated async state block that’s referenced from TLS during async-chain execution.

Changes:

  • Rework async await state handling to route notifier/context storage through a stack-allocated RuntimeAsyncStackState linked via TLS RuntimeAsyncAwaitState.
  • Update CoreCLR async thunk IL emission (VM + managed emitter) to Push/Pop the new TLS state and to pass the TLS state byref into Finalize*ReturningThunk.
  • Adjust CoreLib binder/metasig definitions to match the new helper signatures and types.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.csUpdate leaf await helpers to write notifier data into stack state via t_runtimeAsyncAwaitState.StackState.
src/coreclr/vm/metasig.hAdd new metasigs for Finalize*ReturningThunk(ref RuntimeAsyncAwaitState) (Task/ValueTask, generic and non-generic).
src/coreclr/vm/corelib.hUpdate binder definitions: remove ExecutionAndSyncBlockStore, add TLS field and nested async state types/methods.
src/coreclr/vm/asyncthunks.cppUpdate VM-emitted task-returning thunk IL to initialize and push/pop the new runtime async state and pass it to finalizers.
src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.csMirror VM thunk emission updates in the managed IL emitter (push/pop + updated finalizer signatures).
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.csImplement new stack/TLS async state structs, update dispatch/suspension logic, and adjust hot-path casts using Unsafe.

@jakobbotsch
jakobbotsch marked this pull request as ready for review April 24, 2026 15:17
@jakobbotsch

Copy link
Copy Markdown
MemberAuthor

PTAL @VSadov

@jakobbotsch
jakobbotsch requested a review from VSadovApril 24, 2026 15:17

@VSadovVSadov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM. Thanks!

CopilotAI review requested due to automatic review settings April 25, 2026 13:02

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 optimizes CoreCLR’s runtime-async suspend/resume path by reducing TLS traffic and GC write barriers, primarily by moving per-suspension state into a stack-allocated ref struct and caching Thread.CurrentThread in the TLS state.

Changes:

  • Introduce stack-allocated runtime-async state (RuntimeAsyncStackState) and keep only a pointer to it in TLS (RuntimeAsyncAwaitState).
  • Update task-returning thunk emission (VM + managed typesystem) to Push/Pop runtime-async state and pass ref RuntimeAsyncAwaitState into finalization helpers.
  • Update CoreLib binder signatures (corelib.h / metasig.h) to match the new helper method signatures and new nested types/fields.

Reviewed changes

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

Show a summary per file
FileDescription
src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.csSwitch await helpers to use stack-state via t_runtimeAsyncAwaitState.StackState and reduce TLS accesses.
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.csAdd RuntimeAsyncStackState + new TLS layout; adjust suspension/dispatch/finalization paths; use Unsafe.As for hot casts.
src/coreclr/vm/asyncthunks.cppUpdate VM-emitted task-returning thunk IL to Push/Pop runtime-async state and call new finalize signatures.
src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.csMirror VM thunk emission changes in the managed typesystem IL stub emitter.
src/coreclr/vm/metasig.hAdd metasig variants for finalize helpers that take ref RuntimeAsyncAwaitState.
src/coreclr/vm/corelib.hBind new nested types/field and update method signatures used by the VM binder.

Comment threadsrc/coreclr/vm/asyncthunks.cpp
Comment threadsrc/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.cs
@jakobbotsch
jakobbotsch merged commit 7e17692 into dotnet:mainApr 25, 2026
159 checks passed
@jakobbotsch
jakobbotsch deleted the unsafify-runtime-async branch April 25, 2026 19:45
This was referenced Apr 28, 2026
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Apr 28, 2026
PR dotnet#127336 (Optimize runtime async suspend/resume machinery) changed
AsyncDispatcherInfo.t_current access from ldsfld/stsfld to ldsflda.
This prevents the type from being resolved by name in R2R mode, and
R2R continuation layout fixups may not trigger getContinuationType,
leaving g_pContinuationClassIfSubTypeCreated null.
Changes:
- Switch AsyncContinuation debuggee to JIT-only mode (R2RModes=Jit)
so the JIT async transformation creates continuation MTs
- Make OuterAsync use a non-tail await (use value after await)
- Hide FailFast behind an indirect call to prevent the JIT from
treating post-await code as unreachable
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Apr 29, 2026
The debuggees were being R2R-compiled with the SDK's bundled crossgen2
pack (pinned in global.json), not the crossgen2 built from the current
commit. This caused R2R/CoreLib version skew when CoreLib types/signatures
changed between SDK pinning and the current build (e.g. PR dotnet#127336
removed ExecutionAndSyncBlockStore, but SDK preview3 crossgen2 still
emits R2R thunks referencing the removed type, causing TypeLoadException
at runtime).
Changes:
- Import targetingpacks.targets in Debuggees/Directory.Build.targets so
UseLocalCrossgen2Pack=true and UpdateLocalCrossgen2Pack target redirect
ResolvedCrossgen2Pack to the locally-built crossgen2.
- Pipe the runtime configuration through DumpTests.targets so the publish
command resolves Crossgen2InBuildDir correctly when CoreCLR is built
with a different configuration than the libs (e.g. -rc checked -lc release).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb added a commit that referenced this pull request Apr 30, 2026
> [!NOTE]
> This PR was created with the assistance of GitHub Copilot
(AI-generated content).
## Problem
The `AsyncContinuationDumpTests` (and potentially other R2R dump tests)
fail in CI because the debuggee apps are R2R-compiled with the SDK's
bundled crossgen2 instead of the locally-built one. When the
locally-built runtime removes or changes types between previews (e.g. PR
#127336 removing `ExecutionAndSyncBlockStore`), the SDK's crossgen2
emits R2R thunks referencing types that no longer exist, causing
`TypeLoadException` or null globals at runtime.
## Root Cause
Two issues prevent the locally-built crossgen2 from being used:
1. **NuGet pack layout mismatch**: The SDK's
`ResolveReadyToRunCompilers` task expects crossgen2 at
`PackagePath/tools/crossgen2.exe` (NuGet pack layout), but the local
build produces a flat layout with `crossgen2.exe` at the root of
`Crossgen2InBuildDir`. `targetingpacks.targets` alone cannot fix this
because it only updates `PackageDirectory` without restructuring the
layout.
2. **Missing property propagation**: The child `dotnet publish` process
doesn't inherit `RuntimeConfiguration`, `TargetArchitecture`,
`TargetOS`, or `BuildArchitecture` from the outer build, causing
crossgen2 and runtime pack paths to resolve incorrectly (especially in
cross-build scenarios like building on linux-x64 for linux-arm).
## Fix
### `Debuggees/Directory.Build.targets`
- Import `targetingpacks.targets` for runtime/targeting pack resolution
- Import `tests.readytorun.targets` via `AfterMicrosoftNETSdkTargets` to
override `ResolveReadyToRunCompilers` after the SDK defines it, pointing
directly at the locally-built crossgen2
### `DumpTests.targets`
- Add `_DebuggeeRuntimeConfig` property (falls back from
`RuntimeConfiguration` -> `Configuration` -> `Debug`)
- Add `_DebuggeePublishProps` to propagate `RuntimeConfiguration`,
`TargetArchitecture`, `TargetOS`, and `BuildArchitecture` to child
`dotnet publish` calls
### `prepare-cdac-helix-steps.yml`
- Add `runtimeConfiguration` parameter (default: `Checked`) and pass it
to `BuildDebuggeesOnly` so crossgen2 is found under the correct
`Checked` artifacts path
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 26, 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.

6 participants

@jakobbotsch@jkotas@VSadov@Sergio0694@MichalPetryka
, '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

Optimize runtime async suspend/resume machinery - #127336

Merged
jakobbotsch merged 8 commits into
dotnet:mainfrom
jakobbotsch:unsafify-runtime-async
Apr 25, 2026
Merged

Optimize runtime async suspend/resume machinery#127336
jakobbotsch merged 8 commits into
dotnet:mainfrom
jakobbotsch:unsafify-runtime-async

Conversation

@jakobbotsch

@jakobbotschjakobbotsch commented Apr 23, 2026

Copy link
Copy Markdown
Member

Several optimizations around suspension/resumption:

  • Reduce number of TLS accesses by storing Thread.CurrentThread inside RuntimeAsyncAwaitState, and only accessing RuntimeAsyncAwaitState
  • Remove a number of write barriers by moving TLS object fields into a ref struct. Allocate this ref struct on the stack in the two places that initiate runtime async chains: task-returning thunks and DispatchContinuations. Keep a pointer to this in the TLS.
  • Use Unsafe in a couple of places to avoid unnecessary cast checks on the hot path

For a suspension heavy benchmark this improves performance by around 17%.

Example benchmark
usingSystem;usingSystem.Diagnostics;usingSystem.Runtime.CompilerServices;usingSystem.Threading;usingSystem.Threading.Tasks;namespaceOSRPerf;publicclassProgram{staticvoidMain(){NullAwaiterna=newNullAwaiter();for(inti=0;i<10;i++){for(intj=0;j<500;j++){Taskt=Foo(20,na);while(!t.IsCompleted){na.Continue();}}Thread.Sleep(100);}for(inti=0;i<50;i++){Taskt=Foo(10_000_000,na);while(!t.IsCompleted){na.Continue();}}}staticints_value;staticasyncTaskFoo(intn,NullAwaiterna){for(inti=0;i<n;i++){s_value+=i;}Stopwatchtimer=Stopwatch.StartNew();for(inti=0;i<n;i++){awaitna;}if(n>1000)Console.WriteLine("Took {0:F1} ms",timer.Elapsed.TotalMilliseconds);}privateclassNullAwaiter:ICriticalNotifyCompletion{publicActionContinue;publicNullAwaiterGetAwaiter()=>this;publicboolIsCompleted=>false;publicvoidGetResult(){}publicvoidUnsafeOnCompleted(Actioncontinuation){Continue=continuation;}publicvoidOnCompleted(Actioncontinuation){thrownewNotImplementedException();}}}

Before: Took 350.3 ms
After: Took 291.3 ms

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

Several optimizations around suspension/resumption:
- Reduce number of TLS accesses by storing `Thread.CurrentThread` and
`&AsyncDispatcherInfo.t_current` inside `RuntimeAsyncAwaitState`, and
only accessing `RuntimeAsyncAwaitState`
- Remove a number of write barriers by moving TLS object fields into a
`ref struct`. Allocate this ref struct on the stack in the two places
that initiate runtime async chains: task-returning thunks and
`DispatchContinuations`. Keep a pointer to this in the TLS.
- Use `Unsafe` in a couple of places to avoid unnecessary cast checks on
the hot path
For a suspension heavy benchmark this improves performance by around
25%.
Comment on lines +248 to +252
// This relies on coreclr and NAOT runtimes always storing ThreadStatic pointer fields in a pinned fashion.
fixed (AsyncDispatcherInfo** pCurrentDispatcherInfo = &AsyncDispatcherInfo.t_current)
{
return CurrentDispatcherInfo = pCurrentDispatcherInfo;
}

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

@jkotas Is this a correct assumption?

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.

AFAIR you need FixedAddressValueType for it to be guaranteed (but that didnt work for non valuetypes iirc).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this a correct assumption?

I do not think it is a valid assumption for NAOT - the thread static block is not allocated as pinned here:

returnRuntimeImports.RhNewObject((MethodTable*)gcDesc);

Is it not possible to pass this around as a byref?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

No, this pointer is being stored inside a struct allocated in TLS.
I am not totally sure if we are going to be accessing this field in NativeAOT though. Probably for Environment.StackTrace support?
We can store the dispatcher information directly inside RuntimeAsyncAwaitState, or add an object wrapper to give it an extra level of indirection. But those options would require diagnostic changes.

Maybe I just revert this and live with the one extra TLS access.

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.

If that's only needed for StackTrace, would the optimization be possible at least when StackTraceSupport=false? Or would it still not be viable because you still need to be able to get the full stacktrace even without metadata?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I don't see much (if any) benefit from avoiding this particular TLS lookup in the benchmark. I've just removed it.

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 optimizes CoreCLR “runtime async” suspension/resumption by reducing TLS traffic, minimizing write barriers on hot paths, and consolidating context-handling work into new helpers used by the JIT’s async transformation.

Changes:

  • Refactors runtime-async state to cache TLS-derived values and to move notifier/context references into a stack-allocated “stack state” accessed via the thread-static await state.
  • Extends CORINFO_ASYNC_INFO and JIT/EE plumbing with new “finish suspension” helper method handles, and updates the JIT async transform to use them.
  • Updates task-returning thunk emission (VM + ILCompiler stubs) and related tooling (SuperPMI, R2R/AOT scanners) to match the new runtime-async APIs.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.csExposes InstanceIsFlowSuppressed for optimized flow-suppression checks.
src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.csUpdates leaf await helpers to use stack-backed runtime-async state.
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.csIntroduces stack-based runtime-async state, new finish-suspension helpers, and updates dispatch/suspension logic.
src/coreclr/inc/corinfo.hExtends CORINFO_ASYNC_INFO with new helper method handles.
src/coreclr/vm/jitinterface.cppPopulates new async helper handles in CEEInfo::getAsyncInfo.
src/coreclr/vm/metasig.hAdds metasigs for updated thunk finalization signatures.
src/coreclr/vm/corelib.hUpdates CoreLib binder entries for new helpers, thunk signatures, and runtime-async nested types/field.
src/coreclr/vm/asyncthunks.cppUpdates task-returning thunk IL emission to push/pop the new await state and pass it to finalizers.
src/coreclr/jit/async.hDeclares new JIT helper routines to finish suspension context handling.
src/coreclr/jit/async.cppReworks suspension context handling to use new finish-suspension helpers and adjusts capture/restore sequence.
src/coreclr/tools/superpmi/superpmi-shared/agnostic.hExtends SuperPMI agnostic async-info struct for new handles.
src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cppRecords/replays new async-info handles.
src/coreclr/tools/Common/JitInterface/CorInfoTypes.csExtends managed projection of CORINFO_ASYNC_INFO.
src/coreclr/tools/Common/JitInterface/CorInfoImpl.csEmits new helper handles for the managed JIT interface implementation.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.csAdds R2R references to new finish-suspension helpers.
src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.csEnsures AOT scanning adds dependencies on new finish-suspension helpers.
src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.csUpdates IL stub emission for task-returning thunks to use new await-state push/pop + finalizer signatures.

CopilotAI review requested due to automatic review settings April 23, 2026 18:51

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 optimizes CoreCLR “runtime async” suspend/resume paths by reducing TLS accesses, lowering GC write barrier traffic, and avoiding some hot-path cast checks. It does so by introducing a stack-allocated state container that’s referenced via a per-thread TLS struct and by updating the thunk emitters to pass the TLS state byref.

Changes:

  • Introduces a stack-allocated RuntimeAsyncStackState and threads it through runtime-async chains via RuntimeAsyncAwaitState.Push/Pop.
  • Updates runtime-emitted task-returning thunks (VM + IL emitter) to initialize/teardown the new TLS stack state and pass ref RuntimeAsyncAwaitState into finalize helpers.
  • Switches a few hot-path casts to Unsafe.As and refactors await helpers to write notifier/context data into the stack state.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
FileDescription
src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.csUpdates public await helper intrinsics to write notifier into stack state via TLS.
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.csReplaces ExecutionAndSyncBlockStore with stack state + TLS Push/Pop, updates dispatch/finalize/handle-suspend flow.
src/coreclr/vm/metasig.hAdds metasigs for finalize helpers that now take ref RuntimeAsyncAwaitState.
src/coreclr/vm/corelib.hUpdates CoreLib binder entries for new TLS field, nested types, and finalize helper signatures.
src/coreclr/vm/asyncthunks.cppUpdates IL stub emission to Push/Pop TLS stack state and pass ref state into finalize helpers.
src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.csMirrors VM thunk emission changes in the managed IL emitter.

CopilotAI review requested due to automatic review settings April 24, 2026 08:21

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 optimizes CoreCLR “runtime async” suspension/resumption by reducing TLS lookups and write barriers, primarily by introducing a stack-allocated async state block that’s referenced from TLS during async-chain execution.

Changes:

  • Rework async await state handling to route notifier/context storage through a stack-allocated RuntimeAsyncStackState linked via TLS RuntimeAsyncAwaitState.
  • Update CoreCLR async thunk IL emission (VM + managed emitter) to Push/Pop the new TLS state and to pass the TLS state byref into Finalize*ReturningThunk.
  • Adjust CoreLib binder/metasig definitions to match the new helper signatures and types.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.csUpdate leaf await helpers to write notifier data into stack state via t_runtimeAsyncAwaitState.StackState.
src/coreclr/vm/metasig.hAdd new metasigs for Finalize*ReturningThunk(ref RuntimeAsyncAwaitState) (Task/ValueTask, generic and non-generic).
src/coreclr/vm/corelib.hUpdate binder definitions: remove ExecutionAndSyncBlockStore, add TLS field and nested async state types/methods.
src/coreclr/vm/asyncthunks.cppUpdate VM-emitted task-returning thunk IL to initialize and push/pop the new runtime async state and pass it to finalizers.
src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.csMirror VM thunk emission updates in the managed IL emitter (push/pop + updated finalizer signatures).
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.csImplement new stack/TLS async state structs, update dispatch/suspension logic, and adjust hot-path casts using Unsafe.

@jakobbotsch
jakobbotsch marked this pull request as ready for review April 24, 2026 15:17
@jakobbotsch

Copy link
Copy Markdown
MemberAuthor

PTAL @VSadov

@jakobbotsch
jakobbotsch requested a review from VSadovApril 24, 2026 15:17

@VSadovVSadov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM. Thanks!

CopilotAI review requested due to automatic review settings April 25, 2026 13:02

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 optimizes CoreCLR’s runtime-async suspend/resume path by reducing TLS traffic and GC write barriers, primarily by moving per-suspension state into a stack-allocated ref struct and caching Thread.CurrentThread in the TLS state.

Changes:

  • Introduce stack-allocated runtime-async state (RuntimeAsyncStackState) and keep only a pointer to it in TLS (RuntimeAsyncAwaitState).
  • Update task-returning thunk emission (VM + managed typesystem) to Push/Pop runtime-async state and pass ref RuntimeAsyncAwaitState into finalization helpers.
  • Update CoreLib binder signatures (corelib.h / metasig.h) to match the new helper method signatures and new nested types/fields.

Reviewed changes

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

Show a summary per file
FileDescription
src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.csSwitch await helpers to use stack-state via t_runtimeAsyncAwaitState.StackState and reduce TLS accesses.
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.csAdd RuntimeAsyncStackState + new TLS layout; adjust suspension/dispatch/finalization paths; use Unsafe.As for hot casts.
src/coreclr/vm/asyncthunks.cppUpdate VM-emitted task-returning thunk IL to Push/Pop runtime-async state and call new finalize signatures.
src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.csMirror VM thunk emission changes in the managed typesystem IL stub emitter.
src/coreclr/vm/metasig.hAdd metasig variants for finalize helpers that take ref RuntimeAsyncAwaitState.
src/coreclr/vm/corelib.hBind new nested types/field and update method signatures used by the VM binder.

Comment threadsrc/coreclr/vm/asyncthunks.cpp
Comment threadsrc/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.cs
@jakobbotsch
jakobbotsch merged commit 7e17692 into dotnet:mainApr 25, 2026
159 checks passed
@jakobbotsch
jakobbotsch deleted the unsafify-runtime-async branch April 25, 2026 19:45
This was referenced Apr 28, 2026
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Apr 28, 2026
PR dotnet#127336 (Optimize runtime async suspend/resume machinery) changed
AsyncDispatcherInfo.t_current access from ldsfld/stsfld to ldsflda.
This prevents the type from being resolved by name in R2R mode, and
R2R continuation layout fixups may not trigger getContinuationType,
leaving g_pContinuationClassIfSubTypeCreated null.
Changes:
- Switch AsyncContinuation debuggee to JIT-only mode (R2RModes=Jit)
so the JIT async transformation creates continuation MTs
- Make OuterAsync use a non-tail await (use value after await)
- Hide FailFast behind an indirect call to prevent the JIT from
treating post-await code as unreachable
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Apr 29, 2026
The debuggees were being R2R-compiled with the SDK's bundled crossgen2
pack (pinned in global.json), not the crossgen2 built from the current
commit. This caused R2R/CoreLib version skew when CoreLib types/signatures
changed between SDK pinning and the current build (e.g. PR dotnet#127336
removed ExecutionAndSyncBlockStore, but SDK preview3 crossgen2 still
emits R2R thunks referencing the removed type, causing TypeLoadException
at runtime).
Changes:
- Import targetingpacks.targets in Debuggees/Directory.Build.targets so
UseLocalCrossgen2Pack=true and UpdateLocalCrossgen2Pack target redirect
ResolvedCrossgen2Pack to the locally-built crossgen2.
- Pipe the runtime configuration through DumpTests.targets so the publish
command resolves Crossgen2InBuildDir correctly when CoreCLR is built
with a different configuration than the libs (e.g. -rc checked -lc release).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb added a commit that referenced this pull request Apr 30, 2026
> [!NOTE]
> This PR was created with the assistance of GitHub Copilot
(AI-generated content).
## Problem
The `AsyncContinuationDumpTests` (and potentially other R2R dump tests)
fail in CI because the debuggee apps are R2R-compiled with the SDK's
bundled crossgen2 instead of the locally-built one. When the
locally-built runtime removes or changes types between previews (e.g. PR
#127336 removing `ExecutionAndSyncBlockStore`), the SDK's crossgen2
emits R2R thunks referencing types that no longer exist, causing
`TypeLoadException` or null globals at runtime.
## Root Cause
Two issues prevent the locally-built crossgen2 from being used:
1. **NuGet pack layout mismatch**: The SDK's
`ResolveReadyToRunCompilers` task expects crossgen2 at
`PackagePath/tools/crossgen2.exe` (NuGet pack layout), but the local
build produces a flat layout with `crossgen2.exe` at the root of
`Crossgen2InBuildDir`. `targetingpacks.targets` alone cannot fix this
because it only updates `PackageDirectory` without restructuring the
layout.
2. **Missing property propagation**: The child `dotnet publish` process
doesn't inherit `RuntimeConfiguration`, `TargetArchitecture`,
`TargetOS`, or `BuildArchitecture` from the outer build, causing
crossgen2 and runtime pack paths to resolve incorrectly (especially in
cross-build scenarios like building on linux-x64 for linux-arm).
## Fix
### `Debuggees/Directory.Build.targets`
- Import `targetingpacks.targets` for runtime/targeting pack resolution
- Import `tests.readytorun.targets` via `AfterMicrosoftNETSdkTargets` to
override `ResolveReadyToRunCompilers` after the SDK defines it, pointing
directly at the locally-built crossgen2
### `DumpTests.targets`
- Add `_DebuggeeRuntimeConfig` property (falls back from
`RuntimeConfiguration` -> `Configuration` -> `Debug`)
- Add `_DebuggeePublishProps` to propagate `RuntimeConfiguration`,
`TargetArchitecture`, `TargetOS`, and `BuildArchitecture` to child
`dotnet publish` calls
### `prepare-cdac-helix-steps.yml`
- Add `runtimeConfiguration` parameter (default: `Checked`) and pass it
to `BuildDebuggeesOnly` so crossgen2 is found under the correct
`Checked` artifacts path
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 26, 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.

6 participants

@jakobbotsch@jkotas@VSadov@Sergio0694@MichalPetryka
, '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

Optimize runtime async suspend/resume machinery - #127336

Merged
jakobbotsch merged 8 commits into
dotnet:mainfrom
jakobbotsch:unsafify-runtime-async
Apr 25, 2026
Merged

Optimize runtime async suspend/resume machinery#127336
jakobbotsch merged 8 commits into
dotnet:mainfrom
jakobbotsch:unsafify-runtime-async

Conversation

@jakobbotsch

@jakobbotschjakobbotsch commented Apr 23, 2026

Copy link
Copy Markdown
Member

Several optimizations around suspension/resumption:

  • Reduce number of TLS accesses by storing Thread.CurrentThread inside RuntimeAsyncAwaitState, and only accessing RuntimeAsyncAwaitState
  • Remove a number of write barriers by moving TLS object fields into a ref struct. Allocate this ref struct on the stack in the two places that initiate runtime async chains: task-returning thunks and DispatchContinuations. Keep a pointer to this in the TLS.
  • Use Unsafe in a couple of places to avoid unnecessary cast checks on the hot path

For a suspension heavy benchmark this improves performance by around 17%.

Example benchmark
usingSystem;usingSystem.Diagnostics;usingSystem.Runtime.CompilerServices;usingSystem.Threading;usingSystem.Threading.Tasks;namespaceOSRPerf;publicclassProgram{staticvoidMain(){NullAwaiterna=newNullAwaiter();for(inti=0;i<10;i++){for(intj=0;j<500;j++){Taskt=Foo(20,na);while(!t.IsCompleted){na.Continue();}}Thread.Sleep(100);}for(inti=0;i<50;i++){Taskt=Foo(10_000_000,na);while(!t.IsCompleted){na.Continue();}}}staticints_value;staticasyncTaskFoo(intn,NullAwaiterna){for(inti=0;i<n;i++){s_value+=i;}Stopwatchtimer=Stopwatch.StartNew();for(inti=0;i<n;i++){awaitna;}if(n>1000)Console.WriteLine("Took {0:F1} ms",timer.Elapsed.TotalMilliseconds);}privateclassNullAwaiter:ICriticalNotifyCompletion{publicActionContinue;publicNullAwaiterGetAwaiter()=>this;publicboolIsCompleted=>false;publicvoidGetResult(){}publicvoidUnsafeOnCompleted(Actioncontinuation){Continue=continuation;}publicvoidOnCompleted(Actioncontinuation){thrownewNotImplementedException();}}}

Before: Took 350.3 ms
After: Took 291.3 ms

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

Several optimizations around suspension/resumption:
- Reduce number of TLS accesses by storing `Thread.CurrentThread` and
`&AsyncDispatcherInfo.t_current` inside `RuntimeAsyncAwaitState`, and
only accessing `RuntimeAsyncAwaitState`
- Remove a number of write barriers by moving TLS object fields into a
`ref struct`. Allocate this ref struct on the stack in the two places
that initiate runtime async chains: task-returning thunks and
`DispatchContinuations`. Keep a pointer to this in the TLS.
- Use `Unsafe` in a couple of places to avoid unnecessary cast checks on
the hot path
For a suspension heavy benchmark this improves performance by around
25%.
Comment on lines +248 to +252
// This relies on coreclr and NAOT runtimes always storing ThreadStatic pointer fields in a pinned fashion.
fixed (AsyncDispatcherInfo** pCurrentDispatcherInfo = &AsyncDispatcherInfo.t_current)
{
return CurrentDispatcherInfo = pCurrentDispatcherInfo;
}

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

@jkotas Is this a correct assumption?

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.

AFAIR you need FixedAddressValueType for it to be guaranteed (but that didnt work for non valuetypes iirc).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this a correct assumption?

I do not think it is a valid assumption for NAOT - the thread static block is not allocated as pinned here:

returnRuntimeImports.RhNewObject((MethodTable*)gcDesc);

Is it not possible to pass this around as a byref?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

No, this pointer is being stored inside a struct allocated in TLS.
I am not totally sure if we are going to be accessing this field in NativeAOT though. Probably for Environment.StackTrace support?
We can store the dispatcher information directly inside RuntimeAsyncAwaitState, or add an object wrapper to give it an extra level of indirection. But those options would require diagnostic changes.

Maybe I just revert this and live with the one extra TLS access.

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.

If that's only needed for StackTrace, would the optimization be possible at least when StackTraceSupport=false? Or would it still not be viable because you still need to be able to get the full stacktrace even without metadata?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I don't see much (if any) benefit from avoiding this particular TLS lookup in the benchmark. I've just removed it.

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 optimizes CoreCLR “runtime async” suspension/resumption by reducing TLS traffic, minimizing write barriers on hot paths, and consolidating context-handling work into new helpers used by the JIT’s async transformation.

Changes:

  • Refactors runtime-async state to cache TLS-derived values and to move notifier/context references into a stack-allocated “stack state” accessed via the thread-static await state.
  • Extends CORINFO_ASYNC_INFO and JIT/EE plumbing with new “finish suspension” helper method handles, and updates the JIT async transform to use them.
  • Updates task-returning thunk emission (VM + ILCompiler stubs) and related tooling (SuperPMI, R2R/AOT scanners) to match the new runtime-async APIs.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.csExposes InstanceIsFlowSuppressed for optimized flow-suppression checks.
src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.csUpdates leaf await helpers to use stack-backed runtime-async state.
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.csIntroduces stack-based runtime-async state, new finish-suspension helpers, and updates dispatch/suspension logic.
src/coreclr/inc/corinfo.hExtends CORINFO_ASYNC_INFO with new helper method handles.
src/coreclr/vm/jitinterface.cppPopulates new async helper handles in CEEInfo::getAsyncInfo.
src/coreclr/vm/metasig.hAdds metasigs for updated thunk finalization signatures.
src/coreclr/vm/corelib.hUpdates CoreLib binder entries for new helpers, thunk signatures, and runtime-async nested types/field.
src/coreclr/vm/asyncthunks.cppUpdates task-returning thunk IL emission to push/pop the new await state and pass it to finalizers.
src/coreclr/jit/async.hDeclares new JIT helper routines to finish suspension context handling.
src/coreclr/jit/async.cppReworks suspension context handling to use new finish-suspension helpers and adjusts capture/restore sequence.
src/coreclr/tools/superpmi/superpmi-shared/agnostic.hExtends SuperPMI agnostic async-info struct for new handles.
src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cppRecords/replays new async-info handles.
src/coreclr/tools/Common/JitInterface/CorInfoTypes.csExtends managed projection of CORINFO_ASYNC_INFO.
src/coreclr/tools/Common/JitInterface/CorInfoImpl.csEmits new helper handles for the managed JIT interface implementation.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.csAdds R2R references to new finish-suspension helpers.
src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.csEnsures AOT scanning adds dependencies on new finish-suspension helpers.
src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.csUpdates IL stub emission for task-returning thunks to use new await-state push/pop + finalizer signatures.

CopilotAI review requested due to automatic review settings April 23, 2026 18:51

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 optimizes CoreCLR “runtime async” suspend/resume paths by reducing TLS accesses, lowering GC write barrier traffic, and avoiding some hot-path cast checks. It does so by introducing a stack-allocated state container that’s referenced via a per-thread TLS struct and by updating the thunk emitters to pass the TLS state byref.

Changes:

  • Introduces a stack-allocated RuntimeAsyncStackState and threads it through runtime-async chains via RuntimeAsyncAwaitState.Push/Pop.
  • Updates runtime-emitted task-returning thunks (VM + IL emitter) to initialize/teardown the new TLS stack state and pass ref RuntimeAsyncAwaitState into finalize helpers.
  • Switches a few hot-path casts to Unsafe.As and refactors await helpers to write notifier/context data into the stack state.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
FileDescription
src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.csUpdates public await helper intrinsics to write notifier into stack state via TLS.
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.csReplaces ExecutionAndSyncBlockStore with stack state + TLS Push/Pop, updates dispatch/finalize/handle-suspend flow.
src/coreclr/vm/metasig.hAdds metasigs for finalize helpers that now take ref RuntimeAsyncAwaitState.
src/coreclr/vm/corelib.hUpdates CoreLib binder entries for new TLS field, nested types, and finalize helper signatures.
src/coreclr/vm/asyncthunks.cppUpdates IL stub emission to Push/Pop TLS stack state and pass ref state into finalize helpers.
src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.csMirrors VM thunk emission changes in the managed IL emitter.

CopilotAI review requested due to automatic review settings April 24, 2026 08:21

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 optimizes CoreCLR “runtime async” suspension/resumption by reducing TLS lookups and write barriers, primarily by introducing a stack-allocated async state block that’s referenced from TLS during async-chain execution.

Changes:

  • Rework async await state handling to route notifier/context storage through a stack-allocated RuntimeAsyncStackState linked via TLS RuntimeAsyncAwaitState.
  • Update CoreCLR async thunk IL emission (VM + managed emitter) to Push/Pop the new TLS state and to pass the TLS state byref into Finalize*ReturningThunk.
  • Adjust CoreLib binder/metasig definitions to match the new helper signatures and types.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.csUpdate leaf await helpers to write notifier data into stack state via t_runtimeAsyncAwaitState.StackState.
src/coreclr/vm/metasig.hAdd new metasigs for Finalize*ReturningThunk(ref RuntimeAsyncAwaitState) (Task/ValueTask, generic and non-generic).
src/coreclr/vm/corelib.hUpdate binder definitions: remove ExecutionAndSyncBlockStore, add TLS field and nested async state types/methods.
src/coreclr/vm/asyncthunks.cppUpdate VM-emitted task-returning thunk IL to initialize and push/pop the new runtime async state and pass it to finalizers.
src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.csMirror VM thunk emission updates in the managed IL emitter (push/pop + updated finalizer signatures).
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.csImplement new stack/TLS async state structs, update dispatch/suspension logic, and adjust hot-path casts using Unsafe.

@jakobbotsch
jakobbotsch marked this pull request as ready for review April 24, 2026 15:17
@jakobbotsch

Copy link
Copy Markdown
MemberAuthor

PTAL @VSadov

@jakobbotsch
jakobbotsch requested a review from VSadovApril 24, 2026 15:17

@VSadovVSadov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM. Thanks!

CopilotAI review requested due to automatic review settings April 25, 2026 13:02

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 optimizes CoreCLR’s runtime-async suspend/resume path by reducing TLS traffic and GC write barriers, primarily by moving per-suspension state into a stack-allocated ref struct and caching Thread.CurrentThread in the TLS state.

Changes:

  • Introduce stack-allocated runtime-async state (RuntimeAsyncStackState) and keep only a pointer to it in TLS (RuntimeAsyncAwaitState).
  • Update task-returning thunk emission (VM + managed typesystem) to Push/Pop runtime-async state and pass ref RuntimeAsyncAwaitState into finalization helpers.
  • Update CoreLib binder signatures (corelib.h / metasig.h) to match the new helper method signatures and new nested types/fields.

Reviewed changes

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

Show a summary per file
FileDescription
src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.csSwitch await helpers to use stack-state via t_runtimeAsyncAwaitState.StackState and reduce TLS accesses.
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.csAdd RuntimeAsyncStackState + new TLS layout; adjust suspension/dispatch/finalization paths; use Unsafe.As for hot casts.
src/coreclr/vm/asyncthunks.cppUpdate VM-emitted task-returning thunk IL to Push/Pop runtime-async state and call new finalize signatures.
src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.csMirror VM thunk emission changes in the managed typesystem IL stub emitter.
src/coreclr/vm/metasig.hAdd metasig variants for finalize helpers that take ref RuntimeAsyncAwaitState.
src/coreclr/vm/corelib.hBind new nested types/field and update method signatures used by the VM binder.

Comment threadsrc/coreclr/vm/asyncthunks.cpp
Comment threadsrc/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.cs
@jakobbotsch
jakobbotsch merged commit 7e17692 into dotnet:mainApr 25, 2026
159 checks passed
@jakobbotsch
jakobbotsch deleted the unsafify-runtime-async branch April 25, 2026 19:45
This was referenced Apr 28, 2026
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Apr 28, 2026
PR dotnet#127336 (Optimize runtime async suspend/resume machinery) changed
AsyncDispatcherInfo.t_current access from ldsfld/stsfld to ldsflda.
This prevents the type from being resolved by name in R2R mode, and
R2R continuation layout fixups may not trigger getContinuationType,
leaving g_pContinuationClassIfSubTypeCreated null.
Changes:
- Switch AsyncContinuation debuggee to JIT-only mode (R2RModes=Jit)
so the JIT async transformation creates continuation MTs
- Make OuterAsync use a non-tail await (use value after await)
- Hide FailFast behind an indirect call to prevent the JIT from
treating post-await code as unreachable
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Apr 29, 2026
The debuggees were being R2R-compiled with the SDK's bundled crossgen2
pack (pinned in global.json), not the crossgen2 built from the current
commit. This caused R2R/CoreLib version skew when CoreLib types/signatures
changed between SDK pinning and the current build (e.g. PR dotnet#127336
removed ExecutionAndSyncBlockStore, but SDK preview3 crossgen2 still
emits R2R thunks referencing the removed type, causing TypeLoadException
at runtime).
Changes:
- Import targetingpacks.targets in Debuggees/Directory.Build.targets so
UseLocalCrossgen2Pack=true and UpdateLocalCrossgen2Pack target redirect
ResolvedCrossgen2Pack to the locally-built crossgen2.
- Pipe the runtime configuration through DumpTests.targets so the publish
command resolves Crossgen2InBuildDir correctly when CoreCLR is built
with a different configuration than the libs (e.g. -rc checked -lc release).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb added a commit that referenced this pull request Apr 30, 2026
> [!NOTE]
> This PR was created with the assistance of GitHub Copilot
(AI-generated content).
## Problem
The `AsyncContinuationDumpTests` (and potentially other R2R dump tests)
fail in CI because the debuggee apps are R2R-compiled with the SDK's
bundled crossgen2 instead of the locally-built one. When the
locally-built runtime removes or changes types between previews (e.g. PR
#127336 removing `ExecutionAndSyncBlockStore`), the SDK's crossgen2
emits R2R thunks referencing types that no longer exist, causing
`TypeLoadException` or null globals at runtime.
## Root Cause
Two issues prevent the locally-built crossgen2 from being used:
1. **NuGet pack layout mismatch**: The SDK's
`ResolveReadyToRunCompilers` task expects crossgen2 at
`PackagePath/tools/crossgen2.exe` (NuGet pack layout), but the local
build produces a flat layout with `crossgen2.exe` at the root of
`Crossgen2InBuildDir`. `targetingpacks.targets` alone cannot fix this
because it only updates `PackageDirectory` without restructuring the
layout.
2. **Missing property propagation**: The child `dotnet publish` process
doesn't inherit `RuntimeConfiguration`, `TargetArchitecture`,
`TargetOS`, or `BuildArchitecture` from the outer build, causing
crossgen2 and runtime pack paths to resolve incorrectly (especially in
cross-build scenarios like building on linux-x64 for linux-arm).
## Fix
### `Debuggees/Directory.Build.targets`
- Import `targetingpacks.targets` for runtime/targeting pack resolution
- Import `tests.readytorun.targets` via `AfterMicrosoftNETSdkTargets` to
override `ResolveReadyToRunCompilers` after the SDK defines it, pointing
directly at the locally-built crossgen2
### `DumpTests.targets`
- Add `_DebuggeeRuntimeConfig` property (falls back from
`RuntimeConfiguration` -> `Configuration` -> `Debug`)
- Add `_DebuggeePublishProps` to propagate `RuntimeConfiguration`,
`TargetArchitecture`, `TargetOS`, and `BuildArchitecture` to child
`dotnet publish` calls
### `prepare-cdac-helix-steps.yml`
- Add `runtimeConfiguration` parameter (default: `Checked`) and pass it
to `BuildDebuggeesOnly` so crossgen2 is found under the correct
`Checked` artifacts path
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 26, 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.

6 participants

@jakobbotsch@jkotas@VSadov@Sergio0694@MichalPetryka