-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Emit task returning thunks in crossgen2 #122651
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?
Conversation
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 implements support for emitting Task-returning async method thunks in crossgen2 by encoding them as MethodSignatures with the AsyncVariant flag set. The changes introduce a new GetPrimaryMethodDesc helper method to navigate between different MethodDesc variants (async, unboxing, P/Invoke) and their primary metadata representations. The GC reference map emission is updated to account for the additional async continuation parameter.
Key changes:
- Adds
AsyncVariantflag to ReadyToRunMethodSigFlags enum (requiring uint instead of byte to accommodate 0x100 value) - Implements
GetPrimaryMethodDescextension method to resolve various MethodDesc wrappers to their primary ECMA method - Updates ArgIterator and GCRefMapBuilder to handle async continuation parameter in both managed and native code
- Modifies ReadyToRunILProvider to emit Task-returning thunks for async methods
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/coreclr/vm/frames.cpp | Adds async continuation parameter handling in FakeGcScanRoots for GC scanning |
| src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.cs | Adds display support for ASYNC flag in method signatures |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun/TypeSystem/MethodDescExtensions.cs | New file introducing GetPrimaryMethodDesc helper to unwrap MethodDesc variants |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs | Updates token resolution to use GetPrimaryMethodDesc and removes version bubble checks |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj | Adds new MethodDescExtensions.cs file to project |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun/IL/ReadyToRunILProvider.cs | Implements Task-returning async thunk emission for async methods |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunTableManager.cs | Updates module resolution to use GetPrimaryMethodDesc |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs | Updates async variant assertion to use new helper method |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs | Adds IsPrimaryMethodDesc check in debug code and minor formatting fix |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/SignatureBuilder.cs | Adds AsyncVariant flag emission in method signatures |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodFixupSignature.cs | Replaces unboxing check with IsPrimaryMethodDesc for fixup optimization |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/GCRefMapBuilder.cs | Adds async continuation parameter to GC reference map generation |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ArgIterator.cs | Implements async continuation argument offset calculation and allocation logic |
| src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs | Exposes TargetMethod property for GetPrimaryMethodDesc navigation |
| src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs | Changes ReadyToRunMethodSigFlags from byte to uint and adds AsyncVariant flag |
| src/coreclr/tools/Common/Compiler/AsyncMethodVariant.cs | Adds GetAsyncVariant and GetTargetOfAsyncVariant extension methods |
src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs
Outdated
Show resolved
Hide resolved
...lr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/GCRefMapBuilder.cs
Show resolved
Hide resolved
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.
Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.
54e7d22 to
b779849
Compare
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 16 out of 16 changed files in this pull request and generated no new comments.
|
/azp run runtime-coreclr crossgen2 outerloop |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
/azp run runtime-coreclr crossgen2 outerloop |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
/azp run runtime-coreclr crossgen2 outerloop |
|
Azure Pipelines successfully started running 1 pipeline(s). |
src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs
Outdated
Show resolved
Hide resolved
|
Azure Pipelines successfully started running 1 pipeline(s). |
| codestream.EndTry(tryFilterRegion); | ||
| } | ||
| // Catch | ||
| // Filter - check if the caught object is a System.Exception |
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.
Should we make this workaround for R2R encoding limitation specific to R2R to avoid leaving perf on the table in NAOT?
What would it take to fix the encoding limitation in R2R?
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.
Should we make this workaround for R2R encoding limitation specific to R2R to avoid leaving perf on the table in NAOT?
Good idea, I'll add an option to switch between using a filter or typed catch.
What would it take to fix the encoding limitation in R2R?
It looks like crossgen encodes EH info as CORINFO_EH_CLAUSE, which is also in a few places in the runtime.
runtime/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs
Lines 671 to 684 in cd5f273
| public struct CORINFO_EH_CLAUSE | |
| { | |
| public CORINFO_EH_CLAUSE_FLAGS Flags; | |
| public uint TryOffset; | |
| public uint TryLength; | |
| public uint HandlerOffset; | |
| public uint HandlerLength; | |
| public uint ClassTokenOrOffset; | |
| /* union | |
| { | |
| DWORD ClassToken; // use for type-based exception handlers | |
| DWORD FilterOffset; // use for filter-based exception handlers (COR_ILEXCEPTION_FILTER is set) | |
| };*/ | |
| } |
runtime/src/coreclr/inc/readytorun.h
Lines 482 to 493 in cd5f273
| struct READYTORUN_EXCEPTION_CLAUSE | |
| { | |
| CorExceptionFlag Flags; | |
| DWORD TryStartPC; | |
| DWORD TryEndPC; | |
| DWORD HandlerStartPC; | |
| DWORD HandlerEndPC; | |
| union { | |
| mdToken ClassToken; | |
| DWORD FilterOffset; | |
| }; | |
| }; |
runtime/src/coreclr/inc/corcompile.h
Lines 142 to 153 in cd5f273
| struct CORCOMPILE_EXCEPTION_CLAUSE | |
| { | |
| CorExceptionFlag Flags; | |
| DWORD TryStartPC; | |
| DWORD TryEndPC; | |
| DWORD HandlerStartPC; | |
| DWORD HandlerEndPC; | |
| union { | |
| mdToken ClassToken; | |
| DWORD FilterOffset; | |
| }; | |
| }; |
runtime/src/coreclr/inc/eexcp.h
Lines 18 to 31 in cd5f273
| struct EE_ILEXCEPTION_CLAUSE { | |
| //Flags is not marked as volatile since it is always accessed | |
| // from within a critical section | |
| CorExceptionFlag Flags; | |
| DWORD TryStartPC; | |
| DWORD TryEndPC; | |
| DWORD HandlerStartPC; | |
| DWORD HandlerEndPC; | |
| union { | |
| void* TypeHandle; | |
| mdToken ClassToken; | |
| DWORD FilterOffset; | |
| }; | |
| }; |
We could add a ModuleOverride flag or something similar for the R2R struct. Or we could make a class token of 0 represent "catch anything".
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 token in EH clause from R2R image is resolved here:
runtime/src/coreclr/vm/codeman.cpp
Lines 6494 to 6500 in 3a11eaf
| Module* pModule = pMD->GetModule(); | |
| _ASSERTE(pModule != NULL); | |
| SigTypeContext typeContext(pMD); | |
| mdToken typeTok = pEHClause->ClassToken; | |
| return ClassLoader::LoadTypeDefOrRefOrSpecThrowing(pModule, typeTok, &typeContext, | |
| ClassLoader::ReturnNullIfNotFound); |
We just need to ensure that the encoding produced by crossgen2 can be resolved in this method.
Or we could make a class token of 0 represent "catch anything".
Yes, it would be an option. There is an interesting detail in how to handle exceptions that do not inherit from System.Exception. These exceptions may or may not get wrapped as RuntimeWrappedException depending on per-assembly settings. What is the behavior we want for these stubs? Do we want them to be wrapped unconditionally to compat with async v1?
I see that you have added code in the filter to specifically handle this case. Do we have any test for it?
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.
It doesn't look like we have tests for throwing non-exceptions.
It looks like async state machines catch System.Exception, not object.
In the stubs, the catch block passes the exception to TaskFromException(), so I'd expect we should rethrow / not catch anything that isn't a System.Exception.
We also could consider a new CorExceptionFlag enum value for "catch System.Exception".
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 goal here is to match async v1 behavior. This implies that we need to test/validate the behavior of traditional async with regards to the RuntimeWrappedException behavior and match up to whatever it is (for cases where the async method is defined in an assemblies with both of the relevant options. Sounds like a test hole we need to fill.
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.
I've opened #123194 to track adding tests for this
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.
It looks like async state machines catch System.Exception, not object.
Do you mean async v1 state machines? They catch System.Exception in corelib. Corelib is marked with RuntimeCompatibilityAttribute(WrapNonExceptionThrows = true). It means they will catch non-exceptions wrapped in RuntimeWrappedException.
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.
I don't think I fully understand all the implications of WrapNonExceptionThrows on async v1 vs async v2. Do we want a passing test suite as a prereq for merging this PR? And do you have a preference for adding a CorExceptionFlag or treating ClassToken == 0 as System.Exception, or finding another solution? I lean towards using a new CorExceptionFlag for R2R.
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.
Do we want a passing test suite as a prereq for merging this PR?
David opened #123194 on it. It should not block this PR. I assume we may have problems in the existing JIT version as well.
I lean towards using a new CorExceptionFlag for R2R.
Fine with me.
|
/azp run runtime-coreclr crossgen2 outerloop |
|
Azure Pipelines successfully started running 1 pipeline(s). |
With these changes, we no longer need separate IL for nativeaot and readytorun
|
/azp run runtime-coreclr crossgen2 outerloop |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
/azp run runtime-coreclr crossgen2 outerloop |
|
Azure Pipelines successfully started running 1 pipeline(s). |
* Rework async thunks generation to cross module generics infra - Add ability for code generating an ILStub to mark that the tokens might be generated - Funnel through various details to ensure that we trigger creation of the ManifestModuleWrappedMethodIL as needed - Adjust method fixup signature generation to allow for VersionsWithMethodBody code which is in the MutableModule - Add a few todos about work we might do to allow cross module compilation of runtime async code. - Disable this for now though, since its much to complex to test without a working end-to-end scenario (See code in CrossModuleInlineableUncached * Revert logic which attempts to treat System.Exception in EH clause specially
|
/azp run runtime-coreclr crossgen2 outerloop |
|
Azure Pipelines successfully started running 1 pipeline(s). |
src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs
Outdated
Show resolved
Hide resolved
…foImpl.ReadyToRun.cs Co-authored-by: Jan Kotas <jkotas@microsoft.com>
…kReturningThunks
|
/azp run runtime-coreclr crossgen2 outerloop |
|
Azure Pipelines successfully started running 1 pipeline(s). |
Emits Task-returning async method thunks in crossgen2 as MethodSignatures with the AsyncVariant flag set
Adds
GetPrimaryMethodDescto get the most natural "primary" method desc for MethodDesc's that point to the same Ecma/Instantiated method. This is used in the places where we cast to EcmaMethod to get relevant metadata information.GCRefMap emission also needed to be updated to account for the continuation parameter. Runtime's
FakeGcScanRoots(used to verify the ReadyToRun images GCRefMap in debug config) also needed to updated to handle the continuation parameter.