Skip to content

Enable devirtualization of async methods in crossgen2#125420

Open
jtschuster wants to merge 2 commits intodotnet:mainfrom
jtschuster:ResolveVirtualAsyncMethod
Open

Enable devirtualization of async methods in crossgen2#125420
jtschuster wants to merge 2 commits intodotnet:mainfrom
jtschuster:ResolveVirtualAsyncMethod

Conversation

@jtschuster
Copy link
Member

Crossgen2's CompilerTypeSystemContext was using MetadataVirtualMethodAlgorithm, which does not handle async variant methods. This prevented devirtualization of async virtual methods during R2R compilation. Switch to AsyncAwareVirtualMethodResolutionAlgorithm, matching ilc.

Add a devirtualize test for async methods exercising sealed class virtual dispatch, sealed interface dispatch, and generic constrained dispatch.

Without the fix, interface dispatch on a newobj-allocated type (CallOnNewOpenInterfaceImpl test) emits a VIRTUAL_ENTRY indirect dispatch:

   lea r11, [0x70a0]  // [ASYNC] IAsyncInterface.GetValue() (VIRTUAL_ENTRY)

With the fix, crossgen2 resolves the async variant through the interface and devirtualizes to a direct call:

   call qword ptr [0x6290]  // [ASYNC] OpenInterfaceImpl.GetValue() (METHOD_ENTRY)

The other calls still require runtime dispatch.

Fixes #124620

crossgen2's CompilerTypeSystemContext was using MetadataVirtualMethodAlgorithm,
which does not handle async variant methods. This prevented devirtualization of
async virtual methods during R2R compilation. Switch to
AsyncAwareVirtualMethodResolutionAlgorithm, matching the NativeAOT compiler.

Add a devirtualize test for async methods exercising sealed class virtual
dispatch, sealed interface dispatch, and generic constrained dispatch.

Fixes dotnet#124620

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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 fixes crossgen2 devirtualization for awaited virtual/interface methods by switching ReadyToRun’s CompilerTypeSystemContext to an async-variant-aware virtual method resolution algorithm (matching ilc). It also adds an async test intended to exercise devirtualization scenarios involving sealed dispatch and interface/generic constrained dispatch.

Changes:

  • Switch ReadyToRun’s virtual method algorithm from MetadataVirtualMethodAlgorithm to AsyncAwareVirtualMethodResolutionAlgorithm.
  • Add a new async test project covering multiple async virtual dispatch shapes (class, interface, and constrained generic calls).

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilerContext.cs Uses async-aware virtual method resolution so crossgen2 can resolve/devirtualize async variant methods.
src/tests/async/devirtualize/devirtualize.csproj Adds a new async test project for devirtualization scenarios.
src/tests/async/devirtualize/devirtualize.cs Introduces async virtual/interface dispatch scenarios meant to exercise crossgen2 devirtualization paths.

Comment on lines +132 to +143
[Fact]
public static void TestEntryPoint()
{
Assert.Equal(42, CallOnNewOpenDerived().Result);
Assert.Equal(43, CallOnNewOpenInterfaceImpl().Result);
Assert.Equal(2, CallOnSealed(new SealedDerived()).Result);
Assert.Equal(3, CallOnSealedNoYield(new SealedDerivedNoYield()).Result);
Assert.Equal(10, CallOnSealedInterface(new SealedInterfaceImpl()).Result);
Assert.Equal(11, CallOnSealedInterfaceNoYield(new SealedInterfaceImplNoYield()).Result);
Assert.Equal(10, CallOnInterface(new SealedInterfaceImpl()).Result);
Assert.Equal(11, CallOnInterface(new SealedInterfaceImplNoYield()).Result);
}
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test only asserts return values, so it will pass regardless of whether crossgen2 actually devirtualizes the awaited virtual/interface calls (the optimization doesn’t change observable behavior). To make this a regression test for the crossgen2 async-variant resolution fix, consider adding an assertion based on generated R2R artifacts (e.g., run R2RDump on the test assembly in crossgen2/R2R modes and check that the interface dispatch in CallOnNewOpenInterfaceImpl resolves to OpenInterfaceImpl.GetValue with a METHOD_ENTRY/direct call rather than an IAsyncInterface VIRTUAL_ENTRY fixup). Gate the check so it only runs when the assembly is ReadyToRun-compiled.

Copilot uses AI. Check for mistakes.
Move the field declaration from the R2R and NativeAOT partial class
files to the shared CompilerTypeSystemContext.cs, since both now use
the same AsyncAwareVirtualMethodResolutionAlgorithm type.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

Devirtualization for Async Methods in crossgen2 resolveVirtualMethod

3 participants