-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Look through async thunks in RuntimeHelpers.PrepareMethod
#123838
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
This function should JIT the method that has the user IL.
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
Updates CoreCLR’s ReflectionInvocation_PrepareMethod path to avoid JIT-preparing runtime async thunk methods, and instead prepare the associated variant that owns the user IL.
Changes:
- Detect async thunk
MethodDescs inPrepareMethodHelper. - Redirect preparation to the async variant via
GetAsyncVariant()prior to prestub/JIT.
| if (pMD->IsAsyncThunkMethod()) | ||
| { | ||
| pMD = pMD->GetAsyncVariant(); | ||
| } |
Copilot
AI
Jan 31, 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.
Reassigning pMD to the async variant before the prestub/wrapper-stub logic can skip preparing the original MethodDesc when the incoming method is a wrapper stub (e.g., value-type unboxing stubs can carry async method data/flags). That can change PrepareMethodHelper behavior (notably for ReflectionInvocation_PrepareDelegate) by no longer calling prestub on the wrapper stub the caller will actually invoke.
Consider preserving the original pMD for the existing prestub + IsWrapperStub() handling, and only resolving/jitting the async variant for the wrapped target (after unwrapping), so both the wrapper stub and the user-IL owning variant get prepared.
Also, GetAsyncVariant() defaults allowInstParam to TRUE, while ReflectionInvocation_PrepareMethod resolves the associated MD with allowInstParam = FALSE; it’s likely safer to pass FALSE here as well to avoid ending up preparing an instantiating-stub variant unexpectedly.
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.
Maybe do it like this to avoid compiling wrapper stub for the async variant? I think the wrapper stub for the async variant is less likely to be ever used.
if (pMD->IsWrapperStub())
{
if (pMD->ShouldCallPrestub())
pMD->DoPrestub(NULL);
pMD = pMD->GetWrappedMethodDesc();
}
if (pMD->IsAsyncThunk())
{
if (pMD->ShouldCallPrestub())
pMD->DoPrestub(NULL);
pMD = pMD->GetAsyncVariant();
}
if (pMD->ShouldCallPrestub())
pMD->DoPrestub(NULL);
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.
That seems better. Used that.
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.
Maybe do it like this to avoid compiling wrapper stub for the async variant? I think the wrapper stub for the async variant is less likely to be ever used.
I don't have a strong opinion, but godbolt is relying on PrepareMethod to inspect the JIT disasm of assemblies. I'm not sure in such use cases whether users want/should see the JIT assembly code of async wrappers in addition to the real async variant.
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.
PrepareMethod triggers JITing of the generic wrapper stubs today. Do users see the disassembly for those?
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.
Yeah. Users are seeing disassembly for async wrapper stubs today, but I think users should see the async variants, or both async wrapper stubs and the async variants.
Example: https://godbolt.org/z/cqdP7GMqT
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.
Yes, I agree. With this change, they should see both.
RuntimeHelpers.PrepareMethodRuntimeHelpers.PrepareMethod
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 1 out of 1 changed files in this pull request and generated 1 comment.
| if (pMD->IsAsyncThunkMethod()) | ||
| { | ||
| if (pMD->ShouldCallPrestub()) | ||
| pMD->DoPrestub(NULL); | ||
| pMD = pMD->GetAsyncVariant(); | ||
| } |
Copilot
AI
Jan 31, 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 behavior that looks through async thunk methods is not covered by an automated regression test. Please add a test that uses a runtime-async method ([RuntimeAsyncMethodGeneration(true)]) and verifies that calling RuntimeHelpers.PrepareMethod on the reflection-exposed MethodHandle causes the underlying user-IL variant (not just the Task-returning thunk) to be jitted/prepared (e.g., via JIT/ETW/EventPipe events or another observable signal).
This function should JIT the method that has the user IL. For runtime async functions reflection exposes the Task-returning variant which is normally a thunk, so treat these transparently.