Skip to content

JIT: Devirtualize non-shared generic virtual methods - #122023

Merged
jakobbotsch merged 41 commits into
dotnet:mainfrom
hez2010:gvm-devirt-2
Jan 13, 2026
Merged

JIT: Devirtualize non-shared generic virtual methods#122023
jakobbotsch merged 41 commits into
dotnet:mainfrom
hez2010:gvm-devirt-2

Conversation

@hez2010

@hez2010hez2010 commented Nov 27, 2025

Copy link
Copy Markdown
Contributor

Enable devirtualization support for generic virtual methods.

When we see a base method having a method instantiation, we use FindOrCreateAssociatedMethodDesc to obtain the devirted method.

Also introduced a jit knob so that it can be turned off at any time.

AOT support is not included in this PR, which needs additional work in managed type system.

Also, if we end up with an instantiating stub (i.e. a shared generic method that requires runtime lookup), we don't have the correct generic context so we need to bail out for now.

Codegen example:

publicclassIntProcessor:VirtualGenericClass,IVritualGenericInterface{publicoverridevoidProcess<T>(Titem){Console.WriteLine(item.ToString());}}publicinterfaceIVritualGenericInterface{voidProcess<T>(Titem)whereT:notnull;}publicstaticvoidTest<T>(IVritualGenericInterfaceifce,Titem)whereT:notnull{ifce.Process(item);}publicstaticvoidTest<T>(VirtualGenericClassbaseClass,Titem)whereT:notnull{baseClass.Process(item);}staticvoidTest(){IVritualGenericInterfacei=newIntProcessor();Test(i,42);VirtualGenericClassc=newIntProcessor();Test(c,42);}

Codegen diff:

 G_M27646_IG01: ;; offset=0x0000
- push rsi
push rbx
- sub rsp, 40+ sub rsp, 32- ;; size=6 bbWeight=1 PerfScore 2.25+ ;; size=5 bbWeight=1 PerfScore 1.25-G_M27646_IG02: ;; offset=0x0006+G_M27646_IG02: ;; offset=0x0005- mov rbx, 0x7FFE0803F318 ; Program+IntProcessor+ mov rbx, 0x221CA429C38 ; 'System.Int32'
mov rcx, rbx
- call CORINFO_HELP_NEWSFAST+ call [System.Console:WriteLine(System.Object)]- mov rsi, rax+ mov ecx, 42- mov rcx, rsi+ call [System.Number:Int32ToDecStr(int):System.String]- mov rdx, 0x7FFE0803F100 ; Program+IVritualGenericInterface+ mov rcx, rax- mov r8, 0x7FFE0803F6A8 ; token handle+ call [System.Console:WriteLine(System.String)]- call CORINFO_HELP_VIRTUAL_FUNC_PTR- mov rcx, rsi- mov edx, 42- call rax
mov rcx, rbx
- call CORINFO_HELP_NEWSFAST+ call [System.Console:WriteLine(System.Object)]- mov rbx, rax+ mov ecx, 42- mov rcx, rbx+ call [System.Number:Int32ToDecStr(int):System.String]- mov rdx, 0x7FFE0803EF30 ; Program+VirtualGenericClass+ mov rcx, rax- mov r8, 0x7FFE0803F8A8 ; token handle+ call [System.Console:WriteLine(System.String)]- call CORINFO_HELP_VIRTUAL_FUNC_PTR- mov rcx, rbx- mov edx, 42- call rax
nop
- ;; size=109 bbWeight=1 PerfScore 14.00+ ;; size=69 bbWeight=1 PerfScore 20.00-G_M27646_IG03: ;; offset=0x0073+G_M27646_IG03: ;; offset=0x004A- add rsp, 40+ add rsp, 32
pop rbx
- pop rsi
ret
- ;; size=7 bbWeight=1 PerfScore 2.25+ ;; size=6 bbWeight=1 PerfScore 1.75

Contributes to #112596

cc: @dotnet/jit-contrib

CopilotAI review requested due to automatic review settings November 27, 2025 17:12
@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Nov 27, 2025
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Nov 27, 2025

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.

Pull request overview

This PR enables devirtualization support for generic virtual methods in the JIT compiler, allowing calls to generic virtual methods to be devirtualized when the exact type is known at JIT time. This optimization eliminates virtual dispatch overhead and enables further optimizations like inlining.

Key changes:

  • Removes the assertion that previously blocked generic method devirtualization
  • Generalizes array interface devirtualization to support generic virtual methods by renaming wasArrayInterfaceDevirt to needsMethodContext
  • Adds runtime lookup support for generic method instantiation parameters

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 4 comments.

Show a summary per file
FileDescription
src/coreclr/vm/jitinterface.cppRemoves assertion blocking generic method devirtualization and adds logic to handle generic virtual methods using FindOrCreateAssociatedMethodDesc
src/coreclr/inc/corinfo.hRenames wasArrayInterfaceDevirt to needsMethodContext to generalize the field meaning
src/coreclr/jit/jitconfigvalues.hAdds JitEnableGenericVirtualDevirtualization configuration flag to control the feature
src/coreclr/jit/gentree.hAdds IsGenericVirtual() helper method to identify generic virtual method calls
src/coreclr/jit/gentree.cppUpdates IsDevirtualizationCandidate() to include generic virtual methods (non-AOT only)
src/coreclr/jit/importercalls.cppImplements devirtualization logic for generic virtual methods including runtime lookup handling, updates comments and variable names, and introduces DEVIRT label for control flow
src/coreclr/jit/inline.hRenames arrayInterface field to needsMethodContext in InlineCandidateInfo struct
src/coreclr/jit/indirectcalltransformer.cppUpdates to use renamed needsMethodContext field
src/coreclr/tools/Common/JitInterface/CorInfoTypes.csUpdates managed struct definition to match renamed field
src/coreclr/tools/Common/JitInterface/CorInfoImpl.csUpdates to use renamed field in managed implementation
src/coreclr/tools/superpmi/superpmi-shared/agnostic.hUpdates SuperPMI data structure with renamed field
src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cppUpdates SuperPMI recording/replay to use renamed field

Comment threadsrc/coreclr/jit/importercalls.cpp Outdated
Comment threadsrc/coreclr/jit/importercalls.cpp Outdated
Comment threadsrc/coreclr/jit/importercalls.cpp Outdated
Comment threadsrc/coreclr/jit/importercalls.cpp Outdated
@hez2010

Copy link
Copy Markdown
ContributorAuthor

@MihuBot

Comment threadsrc/coreclr/jit/importercalls.cpp Outdated
@davidwrighton

Copy link
Copy Markdown
Member

@hez2010 sorry about the delay, I got stuck in a bunch of work just before the holidays and wasn't able to do the review before vacation began.

@jakobbotsch

Copy link
Copy Markdown
Member

I'm guessing the crossgen issues are related, I don't see them on other PRs. Can you see if you can reproduce them?

@hez2010

Copy link
Copy Markdown
ContributorAuthor

I'm guessing the crossgen issues are related, I don't see them on other PRs. Can you see if you can reproduce them?

I managed to reproduce the freebsd one on my local cross-build environment. Will investigate it soon to see whether it's related or not.

@hez2010

hez2010 commented Jan 9, 2026

Copy link
Copy Markdown
ContributorAuthor

We hit the assertion here:

/home/i/runtime/src/coreclr/jit/importercalls.cpp:8666
Assertion failed 'call->IsVirtualStub()' in 'System.Threading.StackHelper:CallOnEmptyStack[System.__Canon,System.__Canon,System.__Canon,System.__Canon](System.Func`4[System.__Canon,System.__Canon,System.__Canon,System.__Canon],System.__Canon,System.__Canon,System.__Canon):System.__Canon' during 'Importation' (IL size 116; hash 0x7bf56612; FullOpts)
/home/i/runtime/src/coreclr/jit/importercalls.cpp:8666
Assertion failed 'call->IsVirtualStub()' in 'System.Threading.StackHelper:CallOnEmptyStack[System.__Canon,System.__Canon](System.Func`2[System.__Canon,System.__Canon],System.__Canon):System.__Canon' during 'Importation' (IL size 102; hash 0x657768d4; FullOpts)

https://github.com/hez2010/runtime/blob/40d7569852bd8cecef706532d374ab0e8f7e53f2/src/coreclr/jit/importercalls.cpp#L8664-L8670

I think GVM applies the same with virtual stub, where in R2R mode, we might see GVM calls to non-virtuals. I think we should change the assertion to call->IsVirtualStub() || call->IsGenericVirtual(). What do you think? @jakobbotsch

@jakobbotsch

Copy link
Copy Markdown
Member

I think GVM applies the same with virtual stub, where in R2R mode, we might see GVM calls to non-virtuals. I think we should change the assertion to call->IsVirtualStub() || call->IsGenericVirtual(). What do you think? @jakobbotsch

Seems reasonable to me.

Comment threadsrc/coreclr/jit/importercalls.cpp Outdated
@hez2010

hez2010 commented Jan 11, 2026

Copy link
Copy Markdown
ContributorAuthor

CI is green now.
By the way, do you know why the assertion message didn't show up in the CI log before? It makes it hard to spot the cause of bug without a local repro. (I guess it's because MSBuild somehow omitted all the logs from crossgen2?)

@jakobbotsch

Copy link
Copy Markdown
Member

/azp run runtime-coreclr jitstress, runtime-coreclr outerloop

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 2 pipeline(s).

@jakobbotsch

Copy link
Copy Markdown
Member

CI is green now. By the way, do you know why the assertion message didn't show up in the CI log before? It makes it hard to spot the cause of bug without a local repro. (I guess it's because MSBuild somehow omitted all the logs from crossgen2?)

I am not sure, perhaps that is something specific to how the crossgen2 testing is done for the community targets.

I kicked off some more testing. There are some known test failures in jitstress (for async), so might take some filtering once it's done to validate things.

@hez2010

hez2010 commented Jan 12, 2026

Copy link
Copy Markdown
ContributorAuthor

I kicked off some more testing. There are some known test failures in jitstress (for async), so might take some filtering once it's done to validate things.

All failures seem to be preexisting and unrelated.

@jakobbotsch

Copy link
Copy Markdown
Member

/ba-g Failure is #122345 that is not being matched automatically

@jakobbotsch
jakobbotsch merged commit 87b470c into dotnet:mainJan 13, 2026
184 of 189 checks passed
@jakobbotsch

Copy link
Copy Markdown
Member

Thanks!

jakobbotsch pushed a commit that referenced this pull request Jan 22, 2026
We already have the method handle in CallInfo, so we don't need to rely
on the GenTree to extract the method handle for devirtualization.
Previously in #122023 we changed the JIT to only devirtualize a call
when we can get the method handle from the GenTree, which was
unnecessarily conservative especially when we already have the method
handle in CallInfo.
Fixes#123391
davidwrighton pushed a commit that referenced this pull request Feb 3, 2026
Implement the support for GVM devirtualization in managed type system.
It shares the same issue in #122023 where we are still not able to
devirt shared GVMs due to lacking a proper generic context, so let's do
the non-shared case first.
NativeAOT still not supported yet as it requires some work in the JIT to
stop spilling the helper call for fat pointers.
Contributes to #112596
/cc: @MichalStrehovsky@jakobbotsch
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
lewing pushed a commit to lewing/runtime that referenced this pull request Feb 9, 2026
Implement the support for GVM devirtualization in managed type system.
It shares the same issue in dotnet#122023 where we are still not able to
devirt shared GVMs due to lacking a proper generic context, so let's do
the non-shared case first.
NativeAOT still not supported yet as it requires some work in the JIT to
stop spilling the helper call for fat pointers.
Contributes to dotnet#112596
/cc: @MichalStrehovsky@jakobbotsch
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Feb 13, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

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.

6 participants

@hez2010@AndyAyersMS@jakobbotsch@MichalStrehovsky@davidwrighton