Support devirtualization for virtual methods that require an instantiating stub - #128702
Conversation
There was a problem hiding this comment.
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 aftertokenLookupContextis set. - In the managed JIT interface (
CorInfoImpl), add parallel logic to populateinstParamLookupfor generic DIM / generic virtual / array-interface devirtualization (R2R usesTypeDictionary/MethodHandlehelpers; NativeAOT path returnsFAILED_CANONfor now), and switchtokenLookupContextto 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.
| File | Description |
|---|---|
| src/coreclr/vm/jitinterface.cpp | Allow 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.cs | Add instParamLookup population for generic DIM / GVM / array-interface cases; adjust tokenLookupContext; gate AOT vs R2R behavior. |
| src/coreclr/tools/Common/Compiler/DevirtualizationManager.cs | For 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.cs | Update stale comment numbering after removing the DIM-restriction note. |
|
@MihuBot -nuget |
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. |
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. |
|
I added several tests to cover NativeAOT's behavior of unboxing stubs + runtime lookups in a non-recursive way. |
|
/azp run runtime-coreclr outerloop, runtime-coreclr pgo, runtime-coreclr pgostress, runtime-nativeaot-outerloop |
|
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? |
|
/azp run runtime-coreclr outerloop, runtime-coreclr pgo, runtime-coreclr pgostress, runtime-nativeaot-outerloop |
|
Azure Pipelines successfully started running 4 pipeline(s). |
|
Test failures seem unrelated? |
Pri1 failures: which are #129876. NativeAOT outerloop failures are #129755 (comment) and #129832. |
|
/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. |
#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.
…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
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.
Before:
After:
Similar for R2R:
For NativeAOT:
Before:
After:
Also added a couple of tests from #43668.
Closes #9588
Contributes to #112596
/cc: @jakobbotsch @davidwrighton @MichalStrehovsky