Convert async context helpers from getAsyncInfo method handles to JIT helpers#125079
Convert async context helpers from getAsyncInfo method handles to JIT helpers#125079jtschuster wants to merge 3 commits intodotnet:mainfrom
Conversation
… helpers
Replace the method handle fields in CORINFO_ASYNC_INFO for async context
helpers (CaptureContexts, RestoreContexts, CaptureExecutionContext,
RestoreExecutionContext, RestoreContextsOnSuspension,
CaptureContinuationContext) with proper CORINFO_HELP_* JIT helpers.
The previous approach passed method handles via getAsyncInfo and used
gtNewCallNode(CT_USER_FUNC, ...) with getFunctionEntryPoint/setEntryPoint
in crossgen2. This created MethodEntrypoint nodes in the R2R dependency
graph, which made previously-uncompilable methods compilable and exposed
a bug where IL 'jmp' instructions generate R2R indirect jumps that the
delay-load thunk cannot resolve.
Using JIT helpers avoids this — helpers go through the ReadyToRun helper
fixup table and don't add method entry points to the dependency graph.
Changes:
- Add 6 new CORINFO_HELP_ASYNC_* helpers and DYNAMICJITHELPER entries
- Add corresponding ReadyToRun helper IDs (0x123-0x128) and renumber
AllocContinuation helpers to 0x120-0x122 for sequential layout
- Convert JIT async.cpp to use gtNewHelperCallNode
- Convert interpreter to use INTOP_CALL_HELPER_V_SS/SSS for
CaptureContexts/RestoreContexts, and resolve suspend data method
handles at compile time via getHelperFtn to avoid GC-unsafe
CoreLibBinder lookups at runtime
- Add helper mappings in crossgen2, NativeAOT, and R2RDump
- Remove method handle fields from CORINFO_ASYNC_INFO and all places
that populate/serialize them (VM, managed JitInterface, SuperPMI)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR converts the JIT’s async context save/restore plumbing from passing AsyncHelpers.* method handles via getAsyncInfo to using dedicated CORINFO_HELP_ASYNC_* JIT helpers, avoiding unwanted MethodEntrypoint R2R dependencies and the associated jmp/delay-load thunk resolution issue described in the PR.
Changes:
- Introduces six new async-related
CORINFO_HELP_ASYNC_*helpers and wires them through ReadyToRun helper IDs and DynamicJitHelper mappings. - Updates CoreCLR JIT (
async.cpp) and the interpreter to emit helper calls instead of user-function calls through method handles. - Removes async helper method-handle fields from
CORINFO_ASYNC_INFOand updates VM, tooling, and SuperPMI serialization accordingly.
Reviewed changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/coreclr/vm/jitinterface.cpp | Stops populating removed async helper method handles in getAsyncInfo. |
| src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp | Removes recording/replay of async helper method handles. |
| src/coreclr/tools/superpmi/superpmi-shared/agnostic.h | Updates SuperPMI agnostic async info struct to match new layout. |
| src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs | Maps new CorInfoHelpFunc values to R2R helper IDs. |
| src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.cs | Adds textual decoding for new async R2R helper IDs. |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs | Maps new CorInfoHelpFunc values to R2R helper IDs for crossgen2. |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs | Removes unnecessary AsyncHelpers metadata rooting previously needed for getAsyncInfo method handles. |
| src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs | Switches async infra dependency rooting from method entrypoints to helper entrypoints. |
| src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/JitHelper.cs | Adds ReadyToRun helper -> CoreLib entrypoint mapping for new async helpers. |
| src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs | Removes async helper method-handle fields from managed CORINFO_ASYNC_INFO. |
| src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs | Stops filling removed async helper method-handle fields in managed getAsyncInfo. |
| src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs | Adds six new CORINFO_HELP_ASYNC_* values. |
| src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs | Adds new async R2R helper IDs and renumbers continuation alloc helpers. |
| src/coreclr/jit/utils.cpp | Defines helper call properties for the new async helpers. |
| src/coreclr/jit/async.cpp | Uses gtNewHelperCallNode for async context capture/restore instead of user-func calls. |
| src/coreclr/interpreter/compiler.cpp | Uses interpreter “call helper” opcodes for context capture/restore; resolves helper method handles at compile time for suspend/resume paths. |
| src/coreclr/inc/readytorunhelpers.h | Registers new async helpers in the ReadyToRun helper mapping list. |
| src/coreclr/inc/readytorun.h | Adds new async ReadyToRun helper IDs and renumbers existing continuation alloc helper IDs. |
| src/coreclr/inc/jithelpers.h | Adds DYNAMICJITHELPER entries for the new async helpers. |
| src/coreclr/inc/corinfo.h | Adds new helper enums and removes async helper method-handle fields from CORINFO_ASYNC_INFO. |
src/coreclr/inc/readytorun.h
Outdated
| READYTORUN_HELPER_AllocContinuation = 0x120, | ||
| READYTORUN_HELPER_AllocContinuationClass = 0x121, | ||
| READYTORUN_HELPER_AllocContinuationMethod = 0x122, | ||
| READYTORUN_HELPER_AsyncCaptureContexts = 0x123, | ||
| READYTORUN_HELPER_AsyncRestoreContexts = 0x124, | ||
| READYTORUN_HELPER_AsyncCaptureExecCtx = 0x125, | ||
| READYTORUN_HELPER_AsyncRestoreExecCtx = 0x126, | ||
| READYTORUN_HELPER_AsyncRestoreCtxOnSusp = 0x127, | ||
| READYTORUN_HELPER_AsyncCaptureContinCtx = 0x128, |
There was a problem hiding this comment.
Renumbering existing ReadyToRunHelper values (AllocContinuation* moved from 0x113–0x115 to 0x120–0x122) is a ReadyToRun file-format compatibility break: older R2R images containing helper fixups with the previous IDs will no longer map in MapReadyToRunHelper() and can fail at runtime. Keep existing numeric IDs stable and only append new helper IDs (or add explicit backward-compat handling for the old values) instead of renumbering.
src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs
Outdated
Show resolved
Hide resolved
src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs
Outdated
Show resolved
Hide resolved
| CORINFO_CALL_INFO callInfo = {}; | ||
| callInfo.hMethod = captureCall->gtCallMethHnd; | ||
| callInfo.methodFlags = info.compCompHnd->getMethodAttribs(callInfo.hMethod); | ||
| impMarkInlineCandidate(captureCall, MAKE_METHODCONTEXT(callInfo.hMethod), false, &callInfo, compInlineContext); |
There was a problem hiding this comment.
Can you please open a follow-up issue about reallowing these helpers to be inlined?
Why are not regular method calls exposed to this bug? Should we fix this bug instead? |
I believe it's due to the expectations that |
|
It sounds like the problem solved by |
janvorli
left a comment
There was a problem hiding this comment.
Interpreter compiler changes LGTM.
| READYTORUN_HELPER_InitClass = 0x116, | ||
| READYTORUN_HELPER_InitInstClass = 0x117, | ||
|
|
||
| READYTORUN_HELPER_AllocContinuation = 0x120, |
There was a problem hiding this comment.
Is there any reason for skipping some values of the enum?
There was a problem hiding this comment.
Pre-existing scheme - it gives us a chance to keep one-off additions logically together.
Setting |
|
I created a couple alternatives to explore if we want to avoid using JIT helpers.
|
|
I'm also exploring if it be viable to have |
|
I would probably suggest going with jtschuster#7 and then fixing JMP in the backend (can be done as a separate change). Alternatively we can abort R2R when we see Another option if the I would not suggest returning tokens from |
Splits off work from #124203
Replace the method handle fields in CORINFO_ASYNC_INFO for async context helpers (CaptureContexts, RestoreContexts, CaptureExecutionContext, RestoreExecutionContext, RestoreContextsOnSuspension, CaptureContinuationContext) with proper CORINFO_HELP_* JIT helpers.
The previous approach passed method handles via getAsyncInfo and used gtNewCallNode(CT_USER_FUNC, ...) with getFunctionEntryPoint/setEntryPoint in crossgen2. This created MethodEntrypoint nodes in the R2R dependency graph, which made previously-uncompilable methods compilable and exposed a bug where IL 'jmp' instructions generate R2R indirect jumps that the delay-load thunk cannot resolve.
Using JIT helpers avoids this — helpers go through the ReadyToRun helper fixup table and don't add method entry points to the dependency graph.
Changes: