Skip to content

Conversation

@jakobbotsch
Copy link
Member

The JIT team has tooling that wants to be able to invoke the JIT on methods obtained through reflection (PMI and Disasmo). But since reflection always returns the Task-returning version of runtime async methods we can never JIT the actual runtime async versions. Add a function that allows obtaining the non-thunk version of async methods to make sure we JIT the actual method we are interested in.

The function is private, but we expect to call it through reflection in those tools.

@jkotas what are your thoughts on this? Alternatively PrepareMethod could prepare both the thunk and the runtime async method, similar to how unboxing and instantiating stubs are handled.

cc @EgorBo

The JIT team has tooling that wants to be able to invoke the JIT on
methods obtained through reflection (PMI and Disasmo). But since
reflection always returns the Task-returning version of runtime async
methods we can never JIT the actual runtime async versions. Add a
function that allows obtaining the non-thunk version of async methods to
make sure we JIT the actual method we are interested in.

The function is private, but we expect to call it through reflection in
those tools.
Copilot AI review requested due to automatic review settings January 30, 2026 23:23
@github-actions github-actions bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Jan 30, 2026
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 a new private method RuntimeMethodHandle.GetParallelMethodWithUserIL to support JIT tooling (PMI and Disasmo). The method allows obtaining the non-thunk version of async methods, enabling the JIT to compile the actual runtime async implementation instead of the Task-returning wrapper that reflection normally returns.

Changes:

  • Adds a native QCall function to retrieve the async variant (non-thunk) method for async methods
  • The function is private and intended to be called via reflection from JIT tooling
  • Returns the same method if it's not an async thunk

Reviewed changes

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

File Description
src/coreclr/vm/runtimehandles.h Declares the new RuntimeMethodHandle_GetParallelMethodWithUserIL QCall function
src/coreclr/vm/runtimehandles.cpp Implements the function to return the async variant for thunks or the same method otherwise
src/coreclr/vm/qcallentrypoints.cpp Registers the new QCall entry point
src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs Adds the private managed declaration using LibraryImport

@jkotas
Copy link
Member

jkotas commented Jan 31, 2026

@jkotas what are your thoughts on this? Alternatively PrepareMethod could prepare both the thunk and the runtime async method, similar to how unboxing and instantiating stubs are handled.

PrepareMethod should always compile the user written code for the method. For runtime async methods, it should compile the async variant body.

The story with wrappers is hit or miss. The logic that controls when the reflection holds onto a wrapper vs. the actual method is internal implementation detail (see FindOrCreateAssociatedMethodDescForReflection). It is not something that user has control over, so the API does not provide guarantees about which wrapper got compiled. We can follow the current logic and compile the async thunk if the reflection happens to hold onto it. I do not think it makes sense to compile all possible wrappers.

@jakobbotsch
Copy link
Member Author

@jkotas what are your thoughts on this? Alternatively PrepareMethod could prepare both the thunk and the runtime async method, similar to how unboxing and instantiating stubs are handled.

PrepareMethod should always compile the user written code for the method. For runtime async methods, it should compile the async variant body.

The story with wrappers is hit or miss. The logic that controls when the reflection holds onto a wrapper vs. the actual method is internal implementation detail (see FindOrCreateAssociatedMethodDescForReflection). It is not something that user has control over, so the API does not provide guarantees about which wrapper got compiled. We can follow the current logic and compile the async thunk if the reflection happens to hold onto it. I do not think it makes sense to compile all possible wrappers.

Makes sense. I will open a new PR with that change.

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

Labels

needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants