Skip to content

Emit Async Methods in crossgen2#124203

Open
jtschuster wants to merge 88 commits intodotnet:mainfrom
jtschuster:runtime16
Open

Emit Async Methods in crossgen2#124203
jtschuster wants to merge 88 commits intodotnet:mainfrom
jtschuster:runtime16

Conversation

@jtschuster
Copy link
Member

@jtschuster jtschuster commented Feb 9, 2026

Emit async methods, and their resumption stubs into ReadyToRun images. Also compiles and emits async thunks for task-returning methods.

Signatures for async methods are emitted in the InstanceMethod table, with an additional ENCODE_METHOD_SIG_AsyncVariant (0x100) flag.

Resumption stubs are encoded as precode fixups for the async method with the "signature" being the RVA of the start point of the code. When the fixups are resolved, a DynamicMethodDesc / ILStub is created to represent the resumption and enable GC and unwind info to be resolved. Async methods which do not await and do not need resumption stubs won't have a resumption stub fixup.

The resumption stub MethodDescs are created following the existing pattern for ILStubs, but set the code to the R2R code rather than a precode thunk.

@jtschuster
Copy link
Member Author

/azp run runtime-coreclr crossgen2

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds initial support for emitting and consuming runtime-async-related methods in crossgen2 ReadyToRun (R2R) images, including new signature flags and runtime-side lookup/GC-walk plumbing for async variants and resumption stubs.

Changes:

  • Enables runtime-async=on feature flagging for selected source and test projects, and adds a dedicated CI leg to run R2R + runtime-async library tests.
  • Extends R2R method signature encoding/decoding to represent async variants and resumption stubs, and updates crossgen2 tooling/readers to surface these modifiers.
  • Adds runtime support for locating and registering resumption stub entrypoints so stack walking/GC can associate R2R resumption stubs with a MethodDesc.

Reviewed changes

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

Show a summary per file
File Description
src/libraries/Directory.Build.targets Enables preview features + runtime-async=on for source projects when UseRuntimeAsync=true.
eng/testing/tests.targets Adjusts runtime-async enablement for tests and adds TestRuntimeAsync override knob.
eng/pipelines/coreclr/crossgen2.yml Adds a new CI test matrix leg for R2R + runtime-async libraries testing.
src/coreclr/inc/corcompile.h Adds ENCODE_METHOD_SIG_ResumptionStub flag for method signature encoding.
src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs Adds managed enum flag READYTORUN_METHOD_SIG_ResumptionStub.
src/coreclr/vm/zapsig.cpp Reads new ResumptionStub flag during method sig decode (currently unused in logic).
src/coreclr/vm/stackwalk.cpp Relaxes a debug assert for unwind table registration to accommodate async R2R scenarios.
src/coreclr/vm/readytoruninfo.h Declares runtime API to look up resumption stub entrypoints for async variants.
src/coreclr/vm/readytoruninfo.cpp Implements resumption stub lookup and registers R2R-backed stub MethodDesc for GC stack walks.
src/coreclr/vm/method.hpp Extends async lookup enum and dynamic IL stub types to represent R2R resumption stubs.
src/coreclr/vm/methodtable.cpp Adds an AsyncResumptionStub lookup path (currently duplicative of the existing slow path).
src/coreclr/vm/ilstubcache.h Declares helper to create a DynamicMethodDesc wrapper around precompiled (R2R) stub code.
src/coreclr/vm/ilstubcache.cpp Implements creation of an R2R-backed IL-stub MethodDesc with native entrypoint set directly.
src/coreclr/vm/jitinterface.cpp Tweaks READYTORUN_HELPER handling (includes an unexpected printf).
src/coreclr/inc/readytorunhelpers.h Adds mapping for READYTORUN_HELPER_ThrowExact.
src/coreclr/inc/readytorun.h Clarifies formatting/commenting for async continuation helpers.
src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.cs Shows [RESUME] in method display and improves BadImageFormatException message.
src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunReader.cs Tracks async/resume modifiers when parsing instance method + PGO sections.
src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunMethod.cs Stores/display method modifiers (async/resume) in signature string.
src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/PgoInfoKey.cs Includes modifiers in PGO key signature string generation.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj Adds AsyncMethodVariant.Mangling.cs to the build.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/IL/ReadyToRunILProvider.cs Broadens IL provisioning to handle async variants and resumption stubs.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunTableManager.cs Categorizes async variants/resumption stubs with instantiated methods for table emission.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs Avoids inlining async call/thunk methods and force-adds required async metadata references once.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunSymbolNodeFactory.cs Minor whitespace/style fix.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/SignatureBuilder.cs Emits ResumptionStub flag and hashes resumption stubs with their async variant method signature.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ModuleTokenResolver.cs Adjusts method token resolution and adds field token resolution helper.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodFixupSignature.cs Ensures async variants/resumption stubs aren’t incorrectly optimized as ordinary defs.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/InstanceEntryPointTableNode.cs Handles async variants/resumption stubs in instantiated entrypoint table emission.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/InliningInfoNode.cs Skips emitting inlining info for async thunks and avoids work for methods with no inlinees.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ExceptionInfoLookupTableNode.cs Skips EH-info table processing for resumption stubs.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypeFixupSignature.cs Adjusts GC map encoding size computation (currently incorrect for non-64-bit pointer sizes).
src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs Updates diagnostic naming and hashing; marks token generation on emit.
src/coreclr/tools/Common/TypeSystem/IL/InstantiatedMethodIL.cs Relaxes a Debug.Assert to accommodate non-standard owning method relationships.
src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs Enables READYTORUN path to provide an async resumption stub and relaxes a debug assert for async variants.
src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Async.cs Tracks continuation types as valid types.
src/coreclr/tools/Common/Compiler/AsyncMethodVariant.cs Treats resumption stubs as async thunks for compilation decisions.
Comments suppressed due to low confidence (1)

src/coreclr/vm/jitinterface.cpp:14153

  • Avoid calling printf from the VM here. This will write to stdout in production and can interfere with host output; it also bypasses existing logging/diagnostics patterns already present in this block (STRESS_LOG + _ASSERTE). Please remove the printf and rely on the existing logging/assertion mechanisms (or route through the runtime logging infrastructure if an additional message is needed).
                    result = (size_t)GetEEFuncEntryPoint(DelayLoad_Helper_Obj);
                    break;

                case READYTORUN_HELPER_DelayLoad_Helper_ObjObj:

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings February 9, 2026 23:41
The AsyncHelpers prefix made the names too verbose. Revert to the
original shorter names (CORINFO_HELP_ALLOC_CONTINUATION, CORINFO_HELP_ASYNC_*,
READYTORUN_HELPER_AllocContinuation, READYTORUN_HELPER_Async*) and add a
comment above each definition section noting these are helpers for runtime
async (System.Runtime.CompilerServices.AsyncHelpers).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The PEObjectWriter previously threw NotSupportedException for ARM64
PAGEBASE_REL21 and PAGEOFFSET_12A/12L relocations with non-zero addends.
Async continuation data layouts produce relocations to fields at non-zero
offsets within the data node, requiring addend support.

Include the addend in the page/offset calculations, matching the approach
used by the ELF and Mach-O object writers. The formula is:
  ADRP:  Page(S+A) - Page(P)
  ADD/LDR: (S+A) & 0xFFF

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings February 27, 2026 18:46
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 44 out of 45 changed files in this pull request and generated no new comments.

{
throw new NotSupportedException();
}
long targetAddress = symbolImageOffset + addend;
Copy link
Member

Choose a reason for hiding this comment

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

Saw your commit about this and I am curious. Can you share some more information about it? Are these relics crossgen2 is using or ones that the JIT ends up using?

Copy link
Member Author

Choose a reason for hiding this comment

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

From what I can tell, we end up with this pattern for copying the Resume pointer from the resumption table to the continuation. IIRC, the jit calls recordRelocation and points to an offset into the RW data section it requested for the resumption stub, then crossgen converts that to an offset/addend to the MethodReadWriteDataNode.

Copy link
Member

Choose a reason for hiding this comment

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

Ok, it was not totally clear that these were the relocs we were talking about.

For PE there is no need for the JIT to create a read-write data section and emit these relocs, so generally the JIT won't do that when it knows we are targeting Windows. Is this a case where we were generating PE but targeting non-Windows? My understanding was that we had switched to use the native executable formats for each target, but I could be wrong about this.

We probably want to introduce a better mechanism for the EE to tell the JIT that it does not need to create a read-write data section for this case if we are truly still using PE outside Windows.

Copy link
Member Author

Choose a reason for hiding this comment

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

I believe we do still produce PE files for Linux.

Copilot AI review requested due to automatic review settings March 4, 2026 23:15
@jtschuster
Copy link
Member Author

/azp run runtime-coreclr crossgen2

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

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

Comments suppressed due to low confidence (1)

src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs:1349

  • Formatting: else{ is inconsistent with the surrounding style in this file/project and will likely get rewritten by dotnet-format. Use the standard else + brace spacing/newline.
            else{
                throw new RequiresRuntimeJitException(method.ToString());
            }

Copilot AI review requested due to automatic review settings March 5, 2026 00:16
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

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

Comments suppressed due to low confidence (1)

src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs:1349

  • Formatting: else{ should follow the prevailing style in this file (else on its own line, { on the next line). This keeps the code consistent and avoids style-only diffs later.
            else{
                throw new RequiresRuntimeJitException(method.ToString());
            }

Comment on lines 138 to +142
if (method.IsAsync)
{
if (!wrappedMethodIL.Initialize(_manifestMutableModule, GetMethodILForAsyncMethod(method), method, false))
Debug.Assert(NeedsTaskReturningThunk(method));
if (!wrappedMethodIL.Initialize(_manifestMutableModule, GetMethodILForAsyncMethod(method), (EcmaMethod)method, false))
{
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

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

CreateCrossModuleInlineableTokensForILBody branches on method.IsAsync before checking IsAsyncVariant(), but AsyncMethodVariant inherits MethodDelegator so IsAsync delegates to the wrapped EcmaMethod. If the wrapped method has IsAsync == true, this path will hit the Debug.Assert(NeedsTaskReturningThunk(method)) (debug) and then throw InvalidCastException on (EcmaMethod)method (release). Consider guarding the async-thunk path with method is EcmaMethod and/or moving the IsAsyncVariant() check before the IsAsync check so variants never flow into the EcmaMethod cast.

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

/azp run runtime-coreclr crossgen2

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@jtschuster
Copy link
Member Author

runtime pipeline failures look unrelated, cDAC failure also happened in the latest build on main, so also seems unrelated.

Copy link
Member Author

Choose a reason for hiding this comment

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

I believe these changes are no longer needed with getFunctionEntrypoint only succeeding on the AsyncHelper methods.


READYTORUN_HELPER_InitClass = 0x116,
READYTORUN_HELPER_InitInstClass = 0x117,

Copy link
Member Author

Choose a reason for hiding this comment

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

These should be moved back

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

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

5 participants