Skip to content

Support devirtualization for virtual methods that require an instantiating stub - #128702

Merged
MichalStrehovsky merged 68 commits into
dotnet:mainfrom
hez2010:devirt-instantiating-stub-coreclr
Jun 29, 2026
Merged

Support devirtualization for virtual methods that require an instantiating stub#128702
MichalStrehovsky merged 68 commits into
dotnet:mainfrom
hez2010:devirt-instantiating-stub-coreclr

Conversation

@hez2010

@hez2010hez2010 commented May 28, 2026

Copy link
Copy Markdown
Contributor

Enable devirtualization of virtual methods that require an instantiating stub.
We have landed the refactor in the JIT so that now we are able to handle instantiating stub naturally without a JIT change.

This covers both shared generic virtual methods that don't require a runtime lookup, and generic interface methods that have a default implementation.

GVM devirt for NativeAOT requires more changes which is out of scope of this PR.

IGenericA<string>a=newAImpl();a.B("GVM");a.C("Generic DIM");interfaceIA{voidA(objecto);voidB<T>(To);}interfaceIGenericA<T>:IA{voidC(To)=>B(o);}classAImpl:IGenericA<string>{publicvoidA(objecto){Console.WriteLine(o);}publicvoidB<T>(To){Console.WriteLine(o);}}

Before:

G_M000_IG01: ;; offset=0x0000pushrbxsubrsp,32G_M000_IG02: ;; offset=0x0005movrcx,0x7FFB383AF518call CORINFO_HELP_NEWSFASTmovrbx,raxmovrcx,rbxmovrdx,0x7FFB383AF348movr8,0x7FFB383AF828call CORINFO_HELP_VIRTUAL_FUNC_PTRmovrcx,rbxmovrdx,0xA025C36200callraxmovrcx,rbxmovr11,0x7FFB380E0070movrdx,0xA025C36220call[r11]IGenericA`1[System.__Canon]:C(System.__Canon):thisnopG_M000_IG03: ;; offset=0x005Daddrsp,32poprbxret; Total bytes of code 99

After:

G_M56640_IG01: ;; offset=0x0000subrsp,40G_M56640_IG02: ;; offset=0x0004movrcx,0xA0261EE6F0 ; 'GVM'call[System.Console:WriteLine(System.Object)]movrcx,0xA0261EE710 ; 'Generic DIM'call[System.Console:WriteLine(System.Object)]nopG_M56640_IG03: ;; offset=0x0025addrsp,40ret; Total bytes of code 42

Similar for R2R:

G_M56640_IG01: ;; offset=0x0000pushrbxsubrsp,32G_M56640_IG02: ;; offset=0x0005call[CORINFO_HELP_READYTORUN_NEW]movrbx,raxmovrcx, qword ptr [(reloc 0x4000000000420148)] ; const ptrmovrcx, gword ptr [rcx]call[System.Console:WriteLine(System.Object)]movrdx, qword ptr [(reloc 0x4000000000420150)] ; const ptrmovrdx, gword ptr [rdx]movrcx,rbxlear11,[(reloc 0x40000000004200f8)] ; function addresscall[r11]IGenericA`1[System.String]:C(System.String):thisnopG_M56640_IG03: ;; offset=0x0036addrsp,32poprbxret

For NativeAOT:

Before:

 G_M000_IG01: ;; offset=0x0000pushrbxsubrsp,32 G_M000_IG02: ;; offset=0x0005learcx,[(reloc 0x420c38)]call CORINFO_HELP_NEWSFASTmovrbx,raxmovrcx,rbxleardx,[(reloc 0x420c48)]call CORINFO_HELP_GVMLOOKUP_FOR_SLOTtestal,2je SHORT G_M000_IG04 G_M000_IG03: ;; offset=0x0027movrdx, qword ptr [rax+0x06]movrcx,rbxlear8, gword ptr [(reloc 0x420c68)]call[rax-0x02]jmp SHORT G_M000_IG05 G_M000_IG04: ;; offset=0x003Amovrcx,rbxleardx, gword ptr [(reloc 0x420c68)]callrax G_M000_IG05: ;; offset=0x0046movrcx,rbxlear11,[(reloc 0x420c58)]leardx, gword ptr [(reloc 0x420c70)]call[r11]IGenericA`1[System.String]:C(System.String):thisnop G_M000_IG06: ;; offset=0x005Baddrsp,32poprbxret ; Total bytes of code 97

After:

G_M56640_IG01: ;; offset=0x0000pushrdipushrsipushrbxsubrsp,32 ;; size=7 bbWeight=1 PerfScore 3.25G_M56640_IG02: ;; offset=0x0007learcx,[(reloc 0x4000000000420c30)] ; AImplcall CORINFO_HELP_NEWSFASTmovrbx,raxmovrcx,rbxleardx,[(reloc 0x4000000000420c40)] ; IA:B[System.String](System.String):thiscall CORINFO_HELP_GVMLOOKUP_FOR_SLOTmovrsi,raxmovedi,eaxandedi,2je SHORT G_M56640_IG04G_M56640_IG03: ;; offset=0x002Fmovrdx, qword ptr [rax+0x06]movrcx,rbxlear8, gword ptr [(reloc 0x4000000000420c90)] ; '"GVM"'call[rax-0x02]jmp SHORT G_M56640_IG05G_M56640_IG04: ;; offset=0x0042movrcx,rbxleardx, gword ptr [(reloc 0x4000000000420c90)] ; '"GVM"'callraxG_M56640_IG05: ;; offset=0x004Etestedi,edije SHORT G_M56640_IG07G_M56640_IG06: ;; offset=0x0052movrdx, qword ptr [rsi+0x06]movrcx,rbxlear8, gword ptr [(reloc 0x4000000000420c98)] ; '"Generic DIM"'call[rsi-0x02]jmp SHORT G_M56640_IG08G_M56640_IG07: ;; offset=0x0065movrcx,rbxleardx, gword ptr [(reloc 0x4000000000420c98)] ; '"Generic DIM"'callrsiG_M56640_IG08: ;; offset=0x0071nopG_M56640_IG09: ;; offset=0x0072addrsp,32poprbxpoprsipoprdiret; Total bytes of code 122

Also added a couple of tests from #43668.

Closes#9588
Contributes to #112596

/cc: @jakobbotsch@davidwrighton@MichalStrehovsky

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

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

Note

Copilot was unable to run its full agentic suite in this review.

Enables devirtualization of generic virtual methods and default interface methods (DIMs) on generic interfaces, by computing and passing the instantiation argument needed at runtime, instead of failing devirtualization in those cases.

Changes:

  • In the VM JIT interface, remove the early bail-out for generic DIMs and shared generic virtual methods, and instead compute instParamLookup / re-resolve via the exact (non-shared) MethodDesc when possible; defer the instantiating-stub unwrap until after tokenLookupContext is set.
  • In the managed JIT interface (CorInfoImpl), add parallel logic to populate instParamLookup for generic DIM / generic virtual / array-interface devirtualization (R2R uses TypeDictionary / MethodHandle helpers; NativeAOT path returns FAILED_CANON for now), and switch tokenLookupContext to a method context where appropriate.
  • In DevirtualizationManager, drop the unconditional generic-virtual canon bail-out and, for R2R, only fail DIM resolution on variant dispatch (allowing generic DIMs); keep the stricter pre-existing behavior for NativeAOT.

Reviewed changes

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

FileDescription
src/coreclr/vm/jitinterface.cppAllow devirt to generic DIM / shared GVM by computing exact MD and instParamLookup; move instantiating-stub unwrap after context setup.
src/coreclr/tools/Common/JitInterface/CorInfoImpl.csAdd instParamLookup population for generic DIM / GVM / array-interface cases; adjust tokenLookupContext; gate AOT vs R2R behavior.
src/coreclr/tools/Common/Compiler/DevirtualizationManager.csFor R2R, only fail DIM resolution on variant dispatch; remove unconditional shared-GVM canon bail-out.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DevirtualizationManager.csUpdate stale comment numbering after removing the DIM-restriction note.

Comment threadsrc/coreclr/vm/jitinterface.cpp Outdated
Comment threadsrc/coreclr/vm/jitinterface.cpp Outdated
Comment threadsrc/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
Comment threadsrc/coreclr/tools/Common/Compiler/DevirtualizationManager.cs Outdated
Comment threadsrc/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
Comment threadsrc/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
Comment threadsrc/coreclr/vm/jitinterface.cpp
CopilotAI review requested due to automatic review settings May 28, 2026 14:18

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

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

Comment threadsrc/coreclr/vm/jitinterface.cpp Outdated
Comment threadsrc/coreclr/tools/Common/JitInterface/CorInfoImpl.cs Outdated
Comment threadsrc/coreclr/tools/Common/JitInterface/CorInfoImpl.cs Outdated
Comment threadsrc/coreclr/tools/Common/JitInterface/CorInfoImpl.cs Outdated
Comment threadsrc/coreclr/tools/Common/JitInterface/CorInfoImpl.cs Outdated
Comment threadsrc/coreclr/tools/Common/Compiler/DevirtualizationManager.cs Outdated
CopilotAI review requested due to automatic review settings May 28, 2026 14:29

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

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

Comment threadsrc/coreclr/vm/jitinterface.cpp
Comment threadsrc/coreclr/vm/jitinterface.cpp Outdated
Comment threadsrc/coreclr/tools/Common/JitInterface/CorInfoImpl.cs Outdated
Comment threadsrc/coreclr/tools/Common/JitInterface/CorInfoImpl.cs Outdated
Comment threadsrc/coreclr/vm/jitinterface.cpp Outdated
CopilotAI review requested due to automatic review settings May 28, 2026 14:50

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

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

Comment threadsrc/coreclr/vm/jitinterface.cpp Outdated
Comment threadsrc/coreclr/tools/Common/JitInterface/CorInfoImpl.cs Outdated
Comment threadsrc/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
@hez2010

Copy link
Copy Markdown
ContributorAuthor

@MihuBot

@hez2010

Copy link
Copy Markdown
ContributorAuthor

@MihuBot -nuget

@MichalStrehovsky

Copy link
Copy Markdown
Member

@MichalStrehovsky I disabled the generic recursion test for NativeAOT in 87b4e0c due to #129855. Can you take a look?

These are not scenarios we're concerned about for native AOT, correct? Otherwise it would be nice to write it in a way that it doesn't trigger a recursion.

@hez2010

hez2010 commented Jun 26, 2026

Copy link
Copy Markdown
ContributorAuthor

These are not scenarios we're concerned about for native AOT, correct?

Right. I think this test can be disabled for NativeAOT fine. The type is a struct here is just for testing the interaction between runtime lookups and unboxing stubs, we have other cases that don't need a recursion which works fine under NativeAOT.

Otherwise it would be nice to write it in a way that it doesn't trigger a recursion.

The test here performs a runtime lookup through delegates to resolve to itself with a different instantiation to exercise whether we use the right generic context before / after devirtualization against the same method, and that's why a generic recursion is here.

CopilotAI review requested due to automatic review settings June 26, 2026 06:23

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

Copy link
Copy Markdown
ContributorAuthor

I added several tests to cover NativeAOT's behavior of unboxing stubs + runtime lookups in a non-recursive way.

@MichalStrehovskyMichalStrehovsky left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thank you!

@MichalStrehovsky

Copy link
Copy Markdown
Member

/azp run runtime-coreclr outerloop, runtime-coreclr pgo, runtime-coreclr pgostress, runtime-nativeaot-outerloop

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command.

CopilotAI review requested due to automatic review settings June 26, 2026 08:25

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 Jun 26, 2026

Copy link
Copy Markdown
ContributorAuthor

Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command.

I updated the branch to resolve this. Also fixed a copy-paste error which dismissed the approval.

@MichalStrehovsky Can you trigger the run again?

@MichalStrehovsky

Copy link
Copy Markdown
Member

/azp run runtime-coreclr outerloop, runtime-coreclr pgo, runtime-coreclr pgostress, runtime-nativeaot-outerloop

@azure-pipelines

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

@hez2010

hez2010 commented Jun 27, 2026

Copy link
Copy Markdown
ContributorAuthor

Test failures seem unrelated?

@hez2010

hez2010 commented Jun 28, 2026

Copy link
Copy Markdown
ContributorAuthor

Test failures seem unrelated?

Pri1 failures:

Error loading shared library libatomic.so.1: No such file or directory (needed by /root/helix/work/correlation/SuperFileCheck/runtimes/linux-musl-x64/native/libc++.so.1)

which are #129876.

NativeAOT outerloop failures are #129755 (comment) and #129832.

@MichalStrehovsky

Copy link
Copy Markdown
Member

/ba-g the native AOT failures are known, the owner is now known not to care about it. I have a fix in a PR.

@MichalStrehovsky
MichalStrehovsky merged commit 664063f into dotnet:mainJun 29, 2026
206 of 227 checks passed
@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview7 milestone Jun 30, 2026
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
#129729)
Fixes#129696.
When a single type load needs both an unboxing and non-unboxing
entrypoint at the same time, we'd build the same generic dictionary
twice and fail during registration. The surgical fix is to allow type
loader to do the work twice, but stop at the point where we would try to
allocate it. Runtime type loader having unboxing MethodDescs was a
mistake.
This is not a problem if we don't need both at the same time since the
all the lookups for existing dictionary already ignore the unboxing bit.
(Which is why we fail during registration in the bug - unboxing bit is
not part of the key.)
No regression test because the test is in #128702.
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
…ating stub (#128702)
Enable devirtualization of virtual methods that require an instantiating
stub.
We have landed the refactor in the JIT so that now we are able to handle
instantiating stub naturally without a JIT change.
This covers both shared generic virtual methods that don't require a
runtime lookup, and generic interface methods that have a default
implementation.
GVM devirt for NativeAOT requires more changes which is out of scope of
this PR.
```cs
IGenericA<string> a = new AImpl();
a.B("GVM");
a.C("Generic DIM");
interface IA
{
void A(object o);
void B<T>(T o);
}
interface IGenericA<T> : IA
{
void C(T o) => B(o);
}
class AImpl : IGenericA<string>
{
public void A(object o)
{
Console.WriteLine(o);
}
public void B<T>(T o)
{
Console.WriteLine(o);
}
}
```
Before:
```nasm
G_M000_IG01: ;; offset=0x0000
push rbx
sub rsp, 32
G_M000_IG02: ;; offset=0x0005
mov rcx, 0x7FFB383AF518
call CORINFO_HELP_NEWSFAST
mov rbx, rax
mov rcx, rbx
mov rdx, 0x7FFB383AF348
mov r8, 0x7FFB383AF828
call CORINFO_HELP_VIRTUAL_FUNC_PTR
mov rcx, rbx
mov rdx, 0xA025C36200
call rax
mov rcx, rbx
mov r11, 0x7FFB380E0070
mov rdx, 0xA025C36220
call [r11]IGenericA`1[System.__Canon]:C(System.__Canon):this
nop G_M000_IG03: ;; offset=0x005D
add rsp, 32
pop rbx
ret ; Total bytes of code 99
```
After:
```nasm
G_M56640_IG01: ;; offset=0x0000
sub rsp, 40
G_M56640_IG02: ;; offset=0x0004
mov rcx, 0xA0261EE6F0 ; 'GVM'
call [System.Console:WriteLine(System.Object)]
mov rcx, 0xA0261EE710 ; 'Generic DIM'
call [System.Console:WriteLine(System.Object)]
nop
G_M56640_IG03: ;; offset=0x0025
add rsp, 40
ret
; Total bytes of code 42
```
Similar for R2R:
```nasm
G_M56640_IG01: ;; offset=0x0000
push rbx
sub rsp, 32
G_M56640_IG02: ;; offset=0x0005
call [CORINFO_HELP_READYTORUN_NEW]
mov rbx, rax
mov rcx, qword ptr [(reloc 0x4000000000420148)] ; const ptr
mov rcx, gword ptr [rcx]
call [System.Console:WriteLine(System.Object)]
mov rdx, qword ptr [(reloc 0x4000000000420150)] ; const ptr
mov rdx, gword ptr [rdx]
mov rcx, rbx
lea r11, [(reloc 0x40000000004200f8)] ; function address
call [r11]IGenericA`1[System.String]:C(System.String):this
nop
G_M56640_IG03: ;; offset=0x0036
add rsp, 32
pop rbx
ret
```
For NativeAOT:
Before:
```nasm
G_M000_IG01: ;; offset=0x0000
push rbx
sub rsp, 32
G_M000_IG02: ;; offset=0x0005
lea rcx, [(reloc 0x420c38)]
call CORINFO_HELP_NEWSFAST
mov rbx, rax
mov rcx, rbx
lea rdx, [(reloc 0x420c48)]
call CORINFO_HELP_GVMLOOKUP_FOR_SLOT
test al, 2
je SHORT G_M000_IG04
G_M000_IG03: ;; offset=0x0027
mov rdx, qword ptr [rax+0x06]
mov rcx, rbx
lea r8, gword ptr [(reloc 0x420c68)]
call [rax-0x02]
jmp SHORT G_M000_IG05
G_M000_IG04: ;; offset=0x003A
mov rcx, rbx
lea rdx, gword ptr [(reloc 0x420c68)]
call rax
G_M000_IG05: ;; offset=0x0046
mov rcx, rbx
lea r11, [(reloc 0x420c58)]
lea rdx, gword ptr [(reloc 0x420c70)]
call [r11]IGenericA`1[System.String]:C(System.String):this
nop G_M000_IG06: ;; offset=0x005B
add rsp, 32
pop rbx
ret ; Total bytes of code 97
```
After:
```nasm
G_M56640_IG01: ;; offset=0x0000
push rdi
push rsi
push rbx
sub rsp, 32
;; size=7 bbWeight=1 PerfScore 3.25
G_M56640_IG02: ;; offset=0x0007
lea rcx, [(reloc 0x4000000000420c30)] ; AImpl
call CORINFO_HELP_NEWSFAST
mov rbx, rax
mov rcx, rbx
lea rdx, [(reloc 0x4000000000420c40)] ; IA:B[System.String](System.String):this
call CORINFO_HELP_GVMLOOKUP_FOR_SLOT
mov rsi, rax
mov edi, eax
and edi, 2
je SHORT G_M56640_IG04
G_M56640_IG03: ;; offset=0x002F
mov rdx, qword ptr [rax+0x06]
mov rcx, rbx
lea r8, gword ptr [(reloc 0x4000000000420c90)] ; '"GVM"'
call [rax-0x02]
jmp SHORT G_M56640_IG05
G_M56640_IG04: ;; offset=0x0042
mov rcx, rbx
lea rdx, gword ptr [(reloc 0x4000000000420c90)] ; '"GVM"'
call rax
G_M56640_IG05: ;; offset=0x004E
test edi, edi
je SHORT G_M56640_IG07
G_M56640_IG06: ;; offset=0x0052
mov rdx, qword ptr [rsi+0x06]
mov rcx, rbx
lea r8, gword ptr [(reloc 0x4000000000420c98)] ; '"Generic DIM"'
call [rsi-0x02]
jmp SHORT G_M56640_IG08
G_M56640_IG07: ;; offset=0x0065
mov rcx, rbx
lea rdx, gword ptr [(reloc 0x4000000000420c98)] ; '"Generic DIM"'
call rsi
G_M56640_IG08: ;; offset=0x0071
nop
G_M56640_IG09: ;; offset=0x0072
add rsp, 32
pop rbx
pop rsi
pop rdi
ret
; Total bytes of code 122
```
Also added a couple of tests from #43668.
Closes#9588
Contributes to #112596
/cc: @jakobbotsch@davidwrighton@MichalStrehovsky
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 30, 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.

[Default interfaces] Support for default interface method devirtualization

5 participants

@hez2010@jakobbotsch@MichalStrehovsky@davidwrighton