Enable devirtualization of async methods in crossgen2#125420
Enable devirtualization of async methods in crossgen2#125420jtschuster wants to merge 2 commits intodotnet:mainfrom
Conversation
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>
There was a problem hiding this comment.
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
MetadataVirtualMethodAlgorithmtoAsyncAwareVirtualMethodResolutionAlgorithm. - 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. |
| [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); | ||
| } |
There was a problem hiding this comment.
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.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilerContext.cs
Outdated
Show resolved
Hide resolved
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>
Crossgen2's
CompilerTypeSystemContextwas usingMetadataVirtualMethodAlgorithm, which does not handle async variant methods. This prevented devirtualization of async virtual methods during R2R compilation. Switch toAsyncAwareVirtualMethodResolutionAlgorithm, 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 (
CallOnNewOpenInterfaceImpltest) emits a VIRTUAL_ENTRY indirect dispatch:With the fix, crossgen2 resolves the async variant through the interface and devirtualizes to a direct call:
The other calls still require runtime dispatch.
Fixes #124620