Skip to content

JIT: Add guarded devirtualization support for generic virtual methods - #132361

Open
hez2010 wants to merge 7 commits into
dotnet:mainfrom
hez2010:gvm-gdv
Open

JIT: Add guarded devirtualization support for generic virtual methods#132361
hez2010 wants to merge 7 commits into
dotnet:mainfrom
hez2010:gvm-gdv

Conversation

@hez2010

@hez2010 hez2010 commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Add guarded devirtualization support for generic virtual methods. And avoid splitting CORINFO_HELP_VIRTUAL_FUNC_PTR when creating the check so that we only perform the lookup on the fallback path.

This approach profiles both the class and the method (instantiation) for GVMs, where method profiling is necessary for shared generic virtual methods GDV because we can have different instantiations through a single shared call site. Then while creating the GDV check, check both the class and instantiation.

For simplicity, this profiles independent class and method histograms. But this can lose the correlation between the two values. For example, suppose a call site observes only these combinations:

  • A with I.M<C1>: 50%
  • B with I.M<C2>: 50%

The resulting profiles are:

  • Class: 50% A, 50% B
  • Method: 50% I.M<C1>, 50% I.M<C2>

Nothing prevents the JIT from choosing A and I.M<C2> for the GDV check, which will never succeed and will result in a fast path that is never going to be hit.

Ideally, we should profile a correlated pair of <class handle, method handle> instead of profiling them separately, but we don't have the infra today. This won't be a problem for non-shared GVMs though.

Example:

using System.Runtime.CompilerServices;

class Program
{
    private readonly IFoo _foo = new FooImpl();

    static void Main(string[] args)
    {
        var program = new Program();
        for (int i = 0; i < 1000; i++)
        {
            Test(program._foo, "Test");
            Test(program._foo, new Bar());
            Test(program._foo, 123);
            Thread.Sleep(1);
        }
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    static void Test<T>(IFoo foo, T value)
    {
        foo.Foo(value);
    }
}

interface IFoo
{
    void Foo<T>(T value);
}

class Bar;

class FooImpl : IFoo
{
    [MethodImpl(MethodImplOptions.NoInlining)]
    private static void Use<T>(T value)
    {
    }

    public void Foo<T>(T value)
    {
        Use(value);
    }
}

Codegen for Test now:

; Assembly listing for method Program:Test[System.__Canon](IFoo,System.__Canon) (Tier1)
; Emitting BLENDED_CODE for x64 + VEX on Windows
; Tier1 code
; optimized code
; rsp based frame
; partially interruptible
; 0 inlinees with PGO data; 1 single block inlinees; 0 inlinees without PGO data
; Final local variable assignments
;
;  V00 TypeCtx      [V00,T00] (  5,  4.20)    long  ->  rcx         single-def
;  V01 arg0         [V01,T02] (  5,  3.50)     ref  ->  rbx         class-hnd single-def <IFoo>
;  V02 arg1         [V02,T03] (  4,  2.50)     ref  ->  rsi         class-hnd single-def <System.__Canon>
;  V03 OutArgs      [V03    ] (  1,  1   )  struct (32) [rsp+0x00]  do-not-enreg[XS] addr-exposed "OutgoingArgSpace" <UNNAMED>
;* V04 tmp1         [V04    ] (  0,  0   )    long  ->  zero-ref    "spilling helperCall"
;  V05 tmp2         [V05,T04] (  4,  4.50)    long  ->   r8         "generic virtual method guard"
;* V06 tmp3         [V06    ] (  0,  0   )    long  ->  zero-ref    "indirect call transform spill temp"
;* V07 tmp4         [V07    ] (  0,  0   )     ref  ->  zero-ref    class-hnd exact "guarded devirt this exact temp" <FooImpl>
;  V08 rat0         [V08,T01] (  3,  5.60)    long  ->   r8         "fgMakeTemp is creating a new local variable"
;
; Lcl frame size = 40

G_M30694_IG01:  ;; offset=0x0000
       push     rsi
       push     rbx
       sub      rsp, 40
       mov      qword ptr [rsp+0x20], rcx
       mov      rbx, rdx
       mov      rsi, r8
                                                ;; size=17 bbWeight=1 PerfScore 3.75
G_M30694_IG02:  ;; offset=0x0011
       mov      rdx, qword ptr [rcx+0x48]
       mov      r8, qword ptr [rdx+0x10]
       test     r8, r8
       je       SHORT G_M30694_IG05
                                                ;; size=13 bbWeight=1 PerfScore 5.25
G_M30694_IG03:  ;; offset=0x001E
       mov      rcx, 0x7FFAF62EC3E0      ; FooImpl
       cmp      qword ptr [rbx], rcx
       setne    cl
       movzx    rcx, cl
       mov      rdx, 0x7FFAF62EC8E0      ; IFoo:Foo[System.String](System.String):this
       cmp      r8, rdx
       setne    dl
       movzx    rdx, dl
       or       ecx, edx
       jne      SHORT G_M30694_IG06
                                                ;; size=42 bbWeight=1 PerfScore 5.50
G_M30694_IG04:  ;; offset=0x0048
       mov      rdx, rsi
       mov      rcx, 0x7FFAF62ECFC0      ; FooImpl:Use[System.String](System.String)
       call     [FooImpl:Use[System.__Canon](System.__Canon)]
       jmp      SHORT G_M30694_IG07
                                                ;; size=21 bbWeight=0.25 PerfScore 1.38
G_M30694_IG05:  ;; offset=0x005D
       mov      rdx, 0x7FFAF631EC28      ; global ptr
       call     CORINFO_HELP_RUNTIMEHANDLE_METHOD
       mov      r8, rax
       jmp      SHORT G_M30694_IG03
                                                ;; size=20 bbWeight=0.20 PerfScore 0.70
G_M30694_IG06:  ;; offset=0x0071
       mov      rcx, rbx
       mov      rdx, 0x7FFAF62EB690      ; IFoo
       call     CORINFO_HELP_VIRTUAL_FUNC_PTR
       mov      rcx, rbx
       mov      rdx, rsi
       call     rax
                                                ;; size=26 bbWeight=0.25 PerfScore 1.25
G_M30694_IG07:  ;; offset=0x008B
       nop
                                                ;; size=1 bbWeight=1 PerfScore 0.25
G_M30694_IG08:  ;; offset=0x008C
       add      rsp, 40
       pop      rbx
       pop      rsi
       ret
                                                ;; size=7 bbWeight=1 PerfScore 2.25

; Total bytes of code 147, prolog size 11, PerfScore 20.32, instruction count 39, allocated bytes for code 147 (MethodHash=f2028819) for method Program:Test[System.__Canon](IFoo,System.__Canon) (Tier1)
; ============================================================

; Assembly listing for method Program:Test[int](IFoo,int) (Tier1)
; Emitting BLENDED_CODE for x64 + VEX on Windows
; Tier1 code
; optimized code
; rsp based frame
; partially interruptible
; 0 inlinees with PGO data; 1 single block inlinees; 0 inlinees without PGO data
; Final local variable assignments
;
;  V00 arg0         [V00,T00] (  5,  3   )     ref  ->  rbx         class-hnd single-def <IFoo>
;  V01 arg1         [V01,T01] (  4,  2.50)     int  ->  rsi         single-def
;  V02 OutArgs      [V02    ] (  1,  1   )  struct (32) [rsp+0x00]  do-not-enreg[XS] addr-exposed "OutgoingArgSpace" <UNNAMED>
;* V03 tmp1         [V03    ] (  0,  0   )    long  ->  zero-ref    "generic virtual method guard"
;* V04 tmp2         [V04    ] (  0,  0   )    long  ->  zero-ref    "indirect call transform spill temp"
;* V05 tmp3         [V05    ] (  0,  0   )     ref  ->  zero-ref    class-hnd exact "guarded devirt this exact temp" <FooImpl>
;
; Lcl frame size = 40

G_M23270_IG01:  ;; offset=0x0000
       push     rsi
       push     rbx
       sub      rsp, 40
       mov      rbx, rcx
       mov      esi, edx
                                                ;; size=11 bbWeight=1 PerfScore 2.75
G_M23270_IG02:  ;; offset=0x000B
       mov      rcx, 0x7FFAF62EC3F0      ; FooImpl
       cmp      qword ptr [rbx], rcx
       jne      SHORT G_M23270_IG06
                                                ;; size=15 bbWeight=1 PerfScore 3.25
G_M23270_IG03:  ;; offset=0x001A
       mov      ecx, esi
       call     [FooImpl:Use[int](int)]
                                                ;; size=8 bbWeight=0.50 PerfScore 1.62
G_M23270_IG04:  ;; offset=0x0022
       nop
                                                ;; size=1 bbWeight=1 PerfScore 0.25
G_M23270_IG05:  ;; offset=0x0023
       add      rsp, 40
       pop      rbx
       pop      rsi
       ret
                                                ;; size=7 bbWeight=1 PerfScore 2.25
G_M23270_IG06:  ;; offset=0x002A
       mov      rcx, rbx
       mov      rdx, 0x7FFAF62EB590      ; IFoo
       mov      r8, 0x7FFAF62ED468      ; token handle
       call     CORINFO_HELP_VIRTUAL_FUNC_PTR
       mov      rcx, rbx
       mov      edx, esi
       call     rax
       jmp      SHORT G_M23270_IG04
                                                ;; size=37 bbWeight=0 PerfScore 0.00

; Total bytes of code 79, prolog size 6, PerfScore 10.12, instruction count 23, allocated bytes for code 79 (MethodHash=5104a519) for method Program:Test[int](IFoo,int) (Tier1)

Contributes to #112596

cc: @dotnet/jit-contrib

Copilot AI lite review requested due to automatic review settings August 15, 2026 17:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@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 Aug 15, 2026
@dotnet-policy-service dotnet-policy-service Bot added the community-contribution Indicates that the PR has been added by a community member label Aug 15, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

Copilot AI review requested due to automatic review settings August 15, 2026 17:59

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings August 15, 2026 18:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings August 16, 2026 09:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings August 16, 2026 10:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings August 16, 2026 11:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

2 participants