Skip to content

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

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

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

Conversation

@hez2010

@hez2010hez2010 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:

usingSystem.Runtime.CompilerServices;classProgram{privatereadonlyIFoo_foo=newFooImpl();staticvoidMain(string[]args){varprogram=newProgram();for(inti=0;i<1000;i++){Test(program._foo,"Test");Test(program._foo,newBar());Test(program._foo,123);Thread.Sleep(1);}}[MethodImpl(MethodImplOptions.NoInlining)]staticvoidTest<T>(IFoofoo,Tvalue){foo.Foo(value);}}interfaceIFoo{voidFoo<T>(Tvalue);}classBar;classFooImpl:IFoo{[MethodImpl(MethodImplOptions.NoInlining)]privatestaticvoidUse<T>(Tvalue){}publicvoidFoo<T>(Tvalue){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 = 40G_M30694_IG01: ;; offset=0x0000pushrsipushrbxsubrsp,40mov qword ptr [rsp+0x20],rcxmovrbx,rdxmovrsi,r8 ;; size=17 bbWeight=1 PerfScore 3.75G_M30694_IG02: ;; offset=0x0011movrdx, qword ptr [rcx+0x48]movr8, qword ptr [rdx+0x10]testr8,r8je SHORT G_M30694_IG05 ;; size=13 bbWeight=1 PerfScore 5.25G_M30694_IG03: ;; offset=0x001Emovrcx,0x7FFAF62EC3E0 ; FooImplcmp qword ptr [rbx],rcx setne clmovzxrcx,clmovrdx,0x7FFAF62EC8E0 ; IFoo:Foo[System.String](System.String):thiscmpr8,rdx setne dlmovzxrdx,dlorecx,edxjne SHORT G_M30694_IG06 ;; size=42 bbWeight=1 PerfScore 5.50G_M30694_IG04: ;; offset=0x0048movrdx,rsimovrcx,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.38G_M30694_IG05: ;; offset=0x005Dmovrdx,0x7FFAF631EC28 ; global ptrcall CORINFO_HELP_RUNTIMEHANDLE_METHODmovr8,raxjmp SHORT G_M30694_IG03 ;; size=20 bbWeight=0.20 PerfScore 0.70G_M30694_IG06: ;; offset=0x0071movrcx,rbxmovrdx,0x7FFAF62EB690 ; IFoocall CORINFO_HELP_VIRTUAL_FUNC_PTRmovrcx,rbxmovrdx,rsicallrax ;; size=26 bbWeight=0.25 PerfScore 1.25G_M30694_IG07: ;; offset=0x008Bnop ;; size=1 bbWeight=1 PerfScore 0.25G_M30694_IG08: ;; offset=0x008Caddrsp,40poprbxpoprsiret ;; 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 = 40G_M23270_IG01: ;; offset=0x0000pushrsipushrbxsubrsp,40movrbx,rcxmovesi,edx ;; size=11 bbWeight=1 PerfScore 2.75G_M23270_IG02: ;; offset=0x000Bmovrcx,0x7FFAF62EC3F0 ; FooImplcmp qword ptr [rbx],rcxjne SHORT G_M23270_IG06 ;; size=15 bbWeight=1 PerfScore 3.25G_M23270_IG03: ;; offset=0x001Amovecx,esicall[FooImpl:Use[int](int)] ;; size=8 bbWeight=0.50 PerfScore 1.62G_M23270_IG04: ;; offset=0x0022nop ;; size=1 bbWeight=1 PerfScore 0.25G_M23270_IG05: ;; offset=0x0023addrsp,40poprbxpoprsiret ;; size=7 bbWeight=1 PerfScore 2.25G_M23270_IG06: ;; offset=0x002Amovrcx,rbxmovrdx,0x7FFAF62EB590 ; IFoomovr8,0x7FFAF62ED468 ; token handlecall CORINFO_HELP_VIRTUAL_FUNC_PTRmovrcx,rbxmovedx,esicallraxjmp 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

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

CopilotAI 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-actionsgithub-actionsBot 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-servicedotnet-policy-serviceBot 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.

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

CopilotAI 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.

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

CopilotAI 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.

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

CopilotAI 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.

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

CopilotAI 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.

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

CopilotAI 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.

CopilotAI review requested due to automatic review settings August 17, 2026 13:19

CopilotAI 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.

@hez2010

hez2010 commented Aug 17, 2026

Copy link
Copy Markdown
ContributorAuthor

@jakobbotsch I think this is ready now.
cc @AndyAyersMS as well since you have worked on GDV before

Test failures seem unrelated.

@AndyAyersMS

Copy link
Copy Markdown
Member

main...AndyAyersMS:runtime:context-sensitive-pgo has support for multiple-valued samples (though note those can tear as well), you might want to do something similar.

@hez2010

Copy link
Copy Markdown
ContributorAuthor

main...AndyAyersMS:runtime:context-sensitive-pgo has support for multiple-valued samples (though note those can tear as well), you might want to do something similar.

Thanks that's helpful! Ideally I can rebase this PR on yours, or waiting for the context-sensitive pgo to land first.

But alternatively we can also land this PR first then move to context-sensitive pgo later. I think the two changes are parallel and we only need to switch the jit helper and the histogram in the later migration.

Which do you prefer?

@AndyAyersMS

Copy link
Copy Markdown
Member

main...AndyAyersMS:runtime:context-sensitive-pgo has support for multiple-valued samples (though note those can tear as well), you might want to do something similar.

Thanks that's helpful! Ideally I can rebase this PR on yours, or waiting for the context-sensitive pgo to land first.

But alternatively we can also land this PR first then move to context-sensitive pgo later. I think the two changes are parallel and we only need to switch the jit helper and the histogram in the later migration.

Which do you prefer?

I don't know when or even if I'll PR the contextual profiling. It seems tricky to figure out which bit of context is actually useful. Was hoping caller address / identity might be enough to justify the work, but I didn't find a lot of cases where it helped. For shared code it might be the generic context that is useful.

The other interesting part there is using true reservoir sampling; our current histogram probes have a recency bias as the replacement likelihood stays fixed once the table fills up. I think this is justified but perhaps it is time to do some actual measurements.

So I'd say keep this independent -- I was just pointing out you can have probes that record multiple pieces of information in a fairly natural way, as long as you don't mind having occasional torn pairs / sets. And I suppose we could fix that too, if it really mattered.

CopilotAI review requested due to automatic review settings August 19, 2026 14:55

CopilotAI 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.

CopilotAI review requested due to automatic review settings August 26, 2026 16:14

CopilotAI 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 freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMIcommunity-contributionIndicates 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

@hez2010@AndyAyersMS