-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Emit Async Methods in crossgen2 #124203
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Emit Async Methods in crossgen2 #124203
Conversation
- Add UseRuntimeAsync property for source projects - Add TestRuntimeAsync property for test projects - Enable RuntimeAsync with crossgen2 - Add pipeline leg for runtimeasync tests with crossgen2
|
/azp run runtime-coreclr crossgen2 |
|
Azure Pipelines successfully started running 1 pipeline(s). |
There was a problem hiding this 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=onfeature 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
printffrom 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 theprintfand 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:
src/coreclr/vm/methodtable.cpp
Outdated
| if (asyncVariantLookup == AsyncVariantLookup::AsyncResumptionStub) | ||
| { | ||
| mdMethodDef tkMethod = pDefMD->GetMemberDef(); | ||
| Module* mod = pDefMD->GetModule(); | ||
| bool isAsyncVariantMethod = pDefMD->IsAsyncVariantMethod(); |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new AsyncResumptionStub branch duplicates the existing slow-path lookup later in this method (both linearly scan for the method with the opposite IsAsyncVariantMethod() value). This makes the new enum value effectively redundant and adds maintenance overhead; consider removing the branch or making it resumption-stub-specific.
| bool isAsyncVariant = (methodFlags & ENCODE_METHOD_SIG_AsyncVariant) != 0; | ||
| bool isResumptionStub = (methodFlags & ENCODE_METHOD_SIG_ResumptionStub) != 0; | ||
|
|
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isResumptionStub is computed but never used. If the ResumptionStub flag is intended to affect method decoding, the flag currently has no effect and will also trigger unused-local warnings in builds that treat warnings as errors. Either remove the local or incorporate the flag into the FindOrCreateAssociatedMethodDesc lookup (e.g., select an appropriate AsyncVariantLookup value when the signature requests a resumption stub).
...tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypeFixupSignature.cs
Outdated
Show resolved
Hide resolved
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
|
/azp run runtime-coreclr crossgen2 |
|
Azure Pipelines successfully started running 1 pipeline(s). |
There was a problem hiding this 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 32 out of 33 changed files in this pull request and generated 6 comments.
| default: | ||
| STRESS_LOG1(LF_ZAP, LL_WARNING, "Unknown READYTORUN_HELPER %d\n", helperNum); | ||
| printf("Unknown READYTORUN_HELPER %d", helperNum); | ||
| _ASSERTE(!"Unknown READYTORUN_HELPER"); | ||
| return FALSE; |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid calling printf from the VM here. This will write to stdout in production builds and can interfere with test output/logging. Prefer existing runtime logging mechanisms (e.g., STRESS_LOG, LOG, or EEMessageBox/DbgAssertDialog depending on intent) and let the _ASSERTE remain the debug-time signal.
| Debug.Assert(!_sortedMethods); | ||
| MethodDesc method = methodNode.Method; | ||
| EcmaModule module = (EcmaModule)((EcmaMethod)method.GetTypicalMethodDefinition().GetPrimaryMethodDesc()).Module; | ||
| EcmaModule module = (EcmaModule)((EcmaMethod)method.GetPrimaryMethodDesc().GetTypicalMethodDefinition().GetTypicalMethodDefinition()).Module; |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This expression redundantly calls GetTypicalMethodDefinition() twice. It’s clearer (and avoids implying different semantics) to call it once before casting to EcmaMethod/getting the module.
| EcmaModule module = (EcmaModule)((EcmaMethod)method.GetPrimaryMethodDesc().GetTypicalMethodDefinition().GetTypicalMethodDefinition()).Module; | |
| MethodDesc typicalMethodDefinition = method.GetPrimaryMethodDesc().GetTypicalMethodDefinition(); | |
| EcmaModule module = (EcmaModule)((EcmaMethod)typicalMethodDefinition).Module; |
| static MethodDesc* CreateR2RBackedILStub( | ||
| LoaderAllocator* pAllocator, | ||
| MethodTable* pMT, | ||
| PCODE r2rEntryPoint, | ||
| DWORD stubType, // DynamicMethodDesc::ILStubType | ||
| PCCOR_SIGNATURE pSig, | ||
| DWORD cbSig, | ||
| BOOL isAsync, | ||
| AllocMemTracker* pamTracker); |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
stubType is declared as a DWORD even though the comment says it must be a DynamicMethodDesc::ILStubType. Consider changing the parameter type to the actual enum to make invalid values impossible at compile time and avoid accidental misuse at call sites.
| pMD->GetLoaderAllocator(), | ||
| pStubMT, | ||
| stubEntryPoint, | ||
| DynamicMethodDesc::StubAsyncResume, |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This creates an R2R-backed async resumption stub MethodDesc, but it uses DynamicMethodDesc::StubAsyncResume as the stub type. Since a dedicated StubAsyncResumeR2R enum value was introduced, the wrapper should use that value so the runtime can distinguish IL-based stubs from R2R-backed ones (and any future logic keyed off the stub type behaves correctly).
| DynamicMethodDesc::StubAsyncResume, | |
| DynamicMethodDesc::StubAsyncResumeR2R, |
| using ILCompiler.DependencyAnalysisFramework; | ||
| using ILCompiler.Reflection.ReadyToRun; | ||
| using Internal.TypeSystem.Ecma; | ||
| using System.Linq; |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
using System.Linq; appears unused in this file (no LINQ extension methods referenced). If this project treats warnings as errors, this can break the build; please remove the unused using or add the missing LINQ usage if intended.
| using System.Linq; |
| { | ||
| // Keep in sync with CorInfoImpl.getAsyncInfo() | ||
| DefType continuation = TypeSystemContext.ContinuationType; | ||
| var runtimeHelpers = TypeSystemContext.SystemModule.GetKnownType("System.Runtime.CompilerServices"u8, "RuntimeHelpers"u8); |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
runtimeHelpers is declared but never used. If this project treats warnings as errors, this will break the build; please remove it or use it as intended.
| var runtimeHelpers = TypeSystemContext.SystemModule.GetKnownType("System.Runtime.CompilerServices"u8, "RuntimeHelpers"u8); |
This will throw out valid methods that don't await, but I don't think we have the resumption stub resolution perfect yet.
|
/azp run runtime-coreclr crossgen2 |
|
Azure Pipelines successfully started running 1 pipeline(s). |
Emit async methods, and their resumption stubs into readytorun images. Also compiles and emits Async thunks for Task-returning methods.
Code for async methods and resumption stubs are emitted in the InstanceMethod table, with an additional ENCODE_METHOD_SIG_ResumptionStub (0x100) or ENCODE_METHOD_SIG_ResumptionStub (0x200) flag, respectively. Aside from the flags, the encoded signatures should be identical so that searching for a resumption stub can be done with the async variant. They also have identical VersionResilient hash codes for this purpose.
Resumption stubs are not the subject of any fixups, so in order to create MethodDescs and resolve GC info, the resumption stubs are searched for when an async variant method is resolved by
GetEntryPoint(). It's not trivial to use existing fixup mechanisms for resumption stubs because the jit expects the entrypoint address in the resumption stub table (which resides in the .text section), while fixups require an additional level of indirection. We could work around this in crossgen or the jit.The resumption stub MethodDescs are created following the existing pattern for ILStubs, but set the code to the R2R code rather than the precode thunk.