Skip to content

Conversation

@hez2010
Copy link
Contributor

@hez2010 hez2010 commented Jan 18, 2026

Previously in #122023, we hit an issue with GVM devirtualization when the devirtualized target is a shared generic method. GVM calls are imported with a runtime lookup that is specific to the base method. After devirtualization, the call requires the instantiation argument for the implementing method, and the existing lookup cannot be reused.

This PR unblocks devirtualization for shared generic targets by ensuring the call receives the correct instantiation parameter for the devirtualized method:

  • The multiple ad-hoc flags in dvInfo now have been unified into a single instParamLookup

  • When the target does not require a runtime lookup, we already know the exact generic context. We pass the instantiating stub as the inst param (shared with the existing array interface devirtualization path).

  • When the target requires a runtime lookup, we now introduced a new DictionaryEntryKind::DevirtualizedMethodDescSlot, and pass it to the instParamLookup so that later the VM knows that it needs to encode the class token from the devirtualized method instead of the original token.

To make this work for late devirtualization as well which runs as a post-import phase, impTokenToHandle has been changed so that after importation completes, runtime lookup trees are built using a way that doesn't append statements.

Also due to the instParamLookup change I implement the support for R2R as well.

Example:

IVritualGenericInterface i = new Processor();
Test(i, "test");

VirtualGenericClass c = new Processor();
Test(c, "test");

static void Test<T>(IVritualGenericInterface ifce, T item) where T : notnull
{
    ifce.Process(item);
}

static void Test<T>(VirtualGenericClass baseClass, T item) where T : notnull
{
    baseClass.Process(item);
}

public class Processor : VirtualGenericClass, IVritualGenericInterface
{
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public override void Process<T>(T item)
    {
        Console.WriteLine(typeof(T));
        Console.WriteLine(item.ToString());
    }
}

Codegen diff:

  G_M27646_IG01:
-       push     rdi
        push     rsi
        push     rbx
-       sub      rsp, 32
+       sub      rsp, 40
-                                                ;; size=7 bbWeight=1 PerfScore 3.25
+                                                ;; size=6 bbWeight=1 PerfScore 2.25
 G_M27646_IG02:
-       mov      rbx, 0xD1FFAB1E      ; Program+Processor
+       mov      rbx, 0xD1FFAB1E      ; 'System.String'
        mov      rcx, rbx
-       call     CORINFO_HELP_NEWSFAST
+       call     [System.Console:WriteLine(System.Object)]
-       mov      rsi, rax
+       mov      rsi, 0xD1FFAB1E      ; 'test'
-       mov      rdi, 0xD1FFAB1E      ; 'test'
        mov      rcx, rsi
-       mov      rdx, 0xD1FFAB1E      ; Program+IVritualGenericInterface
+       call     [System.Console:WriteLine(System.String)]
-       mov      r8, 0xD1FFAB1E      ; token handle
-       call     CORINFO_HELP_VIRTUAL_FUNC_PTR
+       mov      rcx, rbx
+       call     [System.Console:WriteLine(System.Object)]
        mov      rcx, rsi
-       mov      rdx, rdi
+       call     [System.Console:WriteLine(System.String)]
-       call     rax
+       nop
-       mov      rcx, rbx
+                                                ;; size=57 bbWeight=1 PerfScore 13.75
-       call     CORINFO_HELP_NEWSFAST
+G_M27646_IG03:
-       mov      rbx, rax
+       add      rsp, 40
-       mov      rcx, rbx
+       pop      rbx
-       mov      rdx, 0xD1FFAB1E      ; Program+VirtualGenericClass
+       pop      rsi
-       mov      r8, 0xD1FFAB1E      ; token handle
+       ret
-       call     CORINFO_HELP_VIRTUAL_FUNC_PTR
+                                                ;; size=7 bbWeight=1 PerfScore 2.25
-       mov      rcx, rbx
-       mov      rdx, rdi
-       call     rax
-       nop
-                                                ;; size=115 bbWeight=1 PerfScore 14.25
-G_M27646_IG03:
-       add      rsp, 32
-       pop      rbx
-       pop      rsi
-       pop      rdi
-       ret
-                                                ;; size=8 bbWeight=1 PerfScore 2.75

Contributes to #112596

Copilot AI review requested due to automatic review settings January 18, 2026 11:52
@github-actions github-actions bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jan 18, 2026
@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Jan 18, 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 enables JIT devirtualization for shared generic virtual methods (GVM) that don't require runtime lookups. Previously, all shared GVMs were blocked from devirtualization due to concerns about having the right generic context. This change unblocks devirtualization when the instantiating stub doesn't need a runtime lookup, by checking for the presence of a GT_RUNTIMELOOKUP node before proceeding.

Changes:

  • Introduced needsMethodContext flag to track when a method context is needed for devirtualization
  • For shared generic methods, obtain the instantiating stub and set needsMethodContext = true
  • Unified handling of array interface and generic virtual method devirtualization paths
  • Added runtime lookup check in the JIT to bail out when a lookup is needed but context is unavailable

Reviewed changes

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

File Description
src/coreclr/vm/jitinterface.cpp Added logic to detect shared generic methods and obtain instantiating stubs, unified array and GVM devirtualization handling
src/coreclr/jit/importercalls.cpp Updated assertions to allow GVM in AOT scenarios, added runtime lookup check to prevent devirtualization when context is unavailable

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@hez2010
Copy link
Contributor Author

hez2010 commented Jan 18, 2026

@MihuBot

@hez2010
Copy link
Contributor Author

hez2010 commented Jan 18, 2026

Failures seem to be caused by missing context during spmi replay. Otherwise all tests are passing.

Copilot AI review requested due to automatic review settings February 1, 2026 18:06
@hez2010 hez2010 changed the title JIT: Devirtualize shared GVM that doesn't need a runtime lookup JIT: Devirtualize shared generic virtual methods Feb 1, 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

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

@hez2010 hez2010 marked this pull request as draft February 1, 2026 18:23
Copilot AI review requested due to automatic review settings February 1, 2026 19:06
Copilot AI review requested due to automatic review settings February 10, 2026 17:25
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

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

Copilot AI review requested due to automatic review settings February 10, 2026 17:49
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

Copilot reviewed 17 out of 17 changed files in this pull request and generated 2 comments.

Copilot AI review requested due to automatic review settings February 10, 2026 19:12
@hez2010
Copy link
Contributor Author

hez2010 commented Feb 10, 2026

I realised that the existing runtime lookup tests are not actually doing runtime lookup, they are const lookup.
So I updated the runtime lookup tests to make them really perform a runtime lookup.

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

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

Comment on lines +8861 to +8864
// If we don't know the array type exactly we may have the wrong interface type here.
// Bail out.
//
if (!isExact)
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

The needsCompileTimeLookup path unconditionally bails out when !isExact with an "Array interface devirt" message, but needsCompileTimeLookup can now also be true for shared generic virtual method devirtualization (not just array-interface devirt). This can prevent devirtualization in cases where objClassIsFinal/derivedMethodIsFinal would otherwise allow it. Consider gating the !isExact bailout (and the diagnostic text) to the array-interface scenario only, and allow the GVM path to proceed without requiring isExact.

Suggested change
// If we don't know the array type exactly we may have the wrong interface type here.
// Bail out.
//
if (!isExact)
// For array interface devirtualization, if we don't know the array type exactly
// we may have the wrong interface type here. Bail out.
//
if (!call->IsGenericVirtual(this) && !isExact)

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings February 10, 2026 20:25
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

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

Copilot AI review requested due to automatic review settings February 11, 2026 09:26
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

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

impl.IsCanonicalMethod(CanonicalFormKind.Specific))
{
#if READYTORUN
MethodWithToken originalImplWithToken = new MethodWithToken(originalImpl, resolver.GetModuleTokenForMethod(originalImpl.GetTypicalMethodDefinition(), allowDynamicallyCreatedReference: false, throwIfNotFound: false), null, false, null, null);
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

In the const instParamLookup path, originalImplWithToken is created with GetModuleTokenForMethod(... throwIfNotFound: false) but the returned ModuleToken isn’t checked for IsNull before constructing MethodWithToken / creating the ReadyToRunHelperId.MethodHandle node. If the method isn’t representable in the R2R image, this can lead to asserts/NREs rather than gracefully failing devirtualization. Consider using throwIfNotFound: true here, or checking IsNull and returning CORINFO_DEVIRTUALIZATION_FAILED_DECL_NOT_REPRESENTABLE (or the appropriate detail) before building the helper node.

Suggested change
MethodWithToken originalImplWithToken = new MethodWithToken(originalImpl, resolver.GetModuleTokenForMethod(originalImpl.GetTypicalMethodDefinition(), allowDynamicallyCreatedReference: false, throwIfNotFound: false), null, false, null, null);
var originalImplModuleToken = resolver.GetModuleTokenForMethod(originalImpl.GetTypicalMethodDefinition(), allowDynamicallyCreatedReference: false, throwIfNotFound: false);
if (originalImplModuleToken.IsNull)
{
info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_DECL_NOT_REPRESENTABLE;
return false;
}
MethodWithToken originalImplWithToken = new MethodWithToken(originalImpl, originalImplModuleToken, null, false, null, null);

Copilot uses AI. Check for mistakes.
@hez2010
Copy link
Contributor Author

hez2010 commented Feb 11, 2026

Test failures seem unrelated.
@jakobbotsch Can I have another pri1 test run?

@jakobbotsch
Copy link
Member

/azp run runtime-coreclr outerloop

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

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

Labels

area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants