Add runtime async support for saving and reusing continuation instances#125556
Add runtime async support for saving and reusing continuation instances#125556jakobbotsch wants to merge 17 commits intodotnet:mainfrom
Conversation
This reverts commit 8e54df1.
There was a problem hiding this comment.
Pull request overview
This PR introduces an optimization for CoreCLR “runtime async” methods by enabling a single shared continuation layout per async method and reusing the same continuation instance across multiple suspension points, reducing allocations and GC pressure. It updates the continuation flags contract to encode per-suspension-point field offsets (notably for return storage) in Continuation.Flags.
Changes:
- JIT: Build shared continuation layouts across all suspension points and optionally reuse continuation instances (
JitAsyncReuseContinuations). - ABI/contract: Redefine continuation flags to encode exception/context/result slot indices via bitfields.
- BCL: Update
AsyncHelperscontinuation field accessors to decode indices from flags.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/coreclr/vm/object.h | Adjusts continuation object helpers used by the runtime/interpreter to locate data/exception storage. |
| src/coreclr/vm/interpexec.cpp | Updates interpreter suspend/resume handling to use new continuation flag semantics. |
| src/coreclr/jit/jitstd/vector.h | Adds data() const overload needed by new JIT code. |
| src/coreclr/jit/jitconfigvalues.h | Adds JitAsyncReuseContinuations config switch. |
| src/coreclr/jit/async.h | Refactors async transformation to build per-state layouts and shared layout support types. |
| src/coreclr/jit/async.cpp | Implements shared layout creation, continuation reuse logic, and index encoding into flags. |
| src/coreclr/interpreter/compiler.cpp | Updates interpreter continuation layout/flags creation for new encoding scheme. |
| src/coreclr/inc/corinfo.h | Redefines CorInfoContinuationFlags to include index bitfield definitions. |
| src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs | Updates managed Continuation to decode exception/context/result locations from flags. |
Comments suppressed due to low confidence (1)
src/coreclr/vm/interpexec.cpp:4430
- Exception handling on interpreter resumption is gated on a placeholder flag literal (567), and GetExceptionObjectStorage currently returns the wrong slot. This will skip exception rethrow or read garbage. Update to the new index-based encoding (compare decoded exception index against the sentinel mask) and use the decoded offset when loading the exception object.
if (pAsyncSuspendData->flags & /*CORINFO_CONTINUATION_HAS_EXCEPTION */ 567)
{
// Throw exception if needed
OBJECTREF exception = *continuation->GetExceptionObjectStorage();
if (exception != NULL)
{
src/coreclr/vm/interpexec.cpp
Outdated
| if (pAsyncSuspendData->flags & /*CORINFO_CONTINUATION_HAS_CONTINUATION_CONTEXT*/ 123) | ||
| { | ||
| MethodDesc *captureSyncContextMethod = pAsyncSuspendData->captureSyncContextMethod; | ||
| int32_t *flagsAddress = continuation->GetFlagsAddress(); | ||
| size_t continuationOffset = OFFSETOF__CORINFO_Continuation__data; | ||
| uint8_t *pContinuationData = (uint8_t*)OBJECTREFToObject(continuation) + continuationOffset; | ||
| if (pAsyncSuspendData->flags & CORINFO_CONTINUATION_HAS_EXCEPTION) | ||
| if (pAsyncSuspendData->flags & /*CORINFO_CONTINUATION_HAS_EXCEPTION */ 456) | ||
| { | ||
| pContinuationData += sizeof(OBJECTREF); | ||
| } |
src/coreclr/interpreter/compiler.cpp
Outdated
| @@ -5949,7 +5949,7 @@ void InterpCompiler::EmitSuspend(const CORINFO_CALL_INFO &callInfo, Continuation | |||
|
|
|||
| if (needsEHHandling) | |||
| { | |||
| flags |= CORINFO_CONTINUATION_HAS_EXCEPTION; | |||
| //flags |= CORINFO_CONTINUATION_HAS_EXCEPTION; | |||
| } | |||
|
|
|||
| suspendData->flags = (CorInfoContinuationFlags)flags; | |||
There was a problem hiding this comment.
Pull request overview
This PR updates the runtime async continuation model to support a single shared continuation layout across suspension points and (optionally) reuse a single continuation instance to reduce allocations/GC pressure.
Changes:
- Reworks continuation
Flagsto encode slot indices (exception/context/result) instead of simple presence bits, and updates VM + BCL consumers accordingly. - Introduces JIT infrastructure to build per-suspension sub-layouts, optionally merge them into a shared layout, and enable continuation reuse via a new config knob.
- Updates interpreter suspension metadata generation to use the new index-encoding scheme.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/coreclr/vm/object.h | Computes result/exception storage addresses using index fields encoded in Flags. |
| src/coreclr/vm/interpexec.cpp | Decodes continuation-context index from flags and uses new exception-storage accessor. |
| src/coreclr/jit/jitstd/vector.h | Adds data() const to support const access patterns. |
| src/coreclr/jit/jitconfigvalues.h | Adds JitAsyncReuseContinuations config (default enabled). |
| src/coreclr/jit/async.h | Adds layout builder/state tracking types and shared-layout support plumbing. |
| src/coreclr/jit/async.cpp | Implements shared-layout creation, per-state suspend/resume emission, and continuation reuse logic. |
| src/coreclr/interpreter/compiler.cpp | Encodes exception/context/result indices into interpreter async suspend flags. |
| src/coreclr/inc/corinfo.h | Redefines CorInfoContinuationFlags to include index bitfields for slots. |
| src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs | Updates managed continuation flag decoding and storage offset computation to match new encoding. |
This adds support for generating a single shared continuation layout for each runtime async method. The shared continuation layout is compatible with the state that needs to be stored for all suspension points in the function. For that reason it uses more memory than the previous separated continuation layouts.
The benefit is that once a single layout is compatible with all suspension points we can reuse the same continuation instance every time we suspend. That means a single runtime async function only ends up allocating one continuation instance.
On suspension heavy benchmarks this improves performance by about 15% by significantly reducing the amount of garbage generated.
A complication arises for return values. Before this change the continuation object always stored its single possible return value in a known location, and resumption stubs would propagate return values into the caller's continuation at that location. With this change the continuation stores space for all possible types of return values, and the offset to store at changes for every suspension point. To handle that we now encode the offset in
Continuation.Flags.Based on #125497