Uh oh!
There was an error while loading. Please reload this page.
Instantiating stub for devirtualized default interface method on a generic type - #43668
Instantiating stub for devirtualized default interface method on a generic type#43668Rattenkrieg wants to merge 29 commits into
Conversation
MichalStrehovsky
commented
Oct 21, 2020
Let me know if you have trouble extracting the failing tests from the CI. E.g. here is one: I don't have pointers for you - I've decided the CoreCLR type system is not something I want to be involved in after finishing the default interface methods feature and haven't looked back since. |
Rattenkrieg
commented
Oct 21, 2020
Thank you! I've managed to dig through pipelines but I'm struggling to reproduce that failure. ... and it passes 🤯 I see there is setup being done during pipeline builds:
I can't find that file in my repo/artifacts. Could there be some crucial setup steps like setting |
MichalStrehovsky
commented
Oct 21, 2020
:) Try COMPlus_TieredCompilation=0 |
Rattenkrieg
commented
Oct 22, 2020
Thank you @MichalStrehovsky ❤️ now I'm able to reproduce and have couple of failing tests under /regeressions dir. And all that issues have linked PR's submitted by you. Reading through related discussions I can feel your PTSD
|
Rattenkrieg
left a comment
There was a problem hiding this comment.
I'm done with general logic changes. The gist is:resolveVirtualMethodHelper now returns wrapped MethodDesc for devirtualized default interface method.
interfaceI<T>{stringDefaultTypeOf()=>typeof(T).Name;}classC:I<string>{}publicstaticclassProgram{[MethodImpl(MethodImplOptions.AggressiveOptimization)]staticintMain(){varc=newC();vardcs=((I<string>)c).DefaultTypeOf();if(dcs!="String")return200;return100;}}For the program above we will get I'1[[System.String]][System.String].DefaultTypeOf from pDerivedMT->GetMethodDescForInterfaceMethod(TypeHandle(pOwnerMT), pBaseMD, FALSE); Which is already an instantiating stub!
Then we going to unwrap it into I'1[[System.__Canon]][System.String].DefaultTypeOf, tell to caller that *requiresInstMethodTableArg = true and do our business in impDevirtualizeCall:
Get the instantiation param from context: GenTree* instParam = gtNewIconEmbClsHndNode(exactClassHandle);. Context here is I<string> part of ((I<string>)c).DefaultTypeOf();. Pass that param, so we will end with:
[000010] I-C-G------- * CALL nullcheck ref I`1[__Canon][System.__Canon].DefaultTypeOf (exactContextHnd=0x00007FF95AE1FE50)
[000009] ------------ this in rcx +--* LCL_VAR ref V00 loc0
[000011] H----------- arg1 \--* CNS_INT(h) long 0x7ff95ae40020 class ^-- I`1[System.String] type handle
and the following assembly would be
IN0002: 00000F call CORINFO_HELP_NEWSFASTIN0003: 000014movrsi,raxIN0004: 000017movrcx,rsiIN0005: 00001A call System.Object:.ctor():thisIN0006: 00001F movrcx,rsiIN0007: 000022movrdx,0x7FF95AE40020 # I`1[System.String] type handleIN0008: 00002C call I`1[__Canon][System.__Canon]:DefaultTypeOf():System.String:thisUh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
I forgot to change wording before pushing.
I think we should rephrase that
The method is itself generic and is shared between generic instantiations but is not itself generic
There was a problem hiding this comment.
I've delayed this check because it forbids several devirt oportunities:
interfaceI<T>{stringDefaultTypeOf()=>typeof(T).Name;}classC:I<string>{}publicstaticclassProgram{[MethodImpl(MethodImplOptions.AggressiveOptimization)]staticintMain(){varc=newC();vardcs=((I<string>)c).DefaultTypeOf();if(dcs!="String")return200;return100;}}The check above makes us return nullptr for ((I<string>)c).DefaultTypeOf(); Since it takes !pTargetMT->HasVariance() branch and we fail to comapre I<String> with I<__Canon> .
So this change should allow us to devirtualize cases like class C<T> : I<T> and class C : I<string>.
By the way cases like class C : I<int> are fine since we comparing I<Int32> with I<Int32>
Rattenkrieg
commented
Oct 27, 2020
I wonder what could be the reason behind inliner failure |
BTW all failures in checked builds are happening here runtime/src/coreclr/src/vm/methodtable.cpp Line 2119 in f5c17f1 This is because of this reordering EDIT: It appears I've messed with conditionals. Fixed in 61a7aab |
Uh oh!
There was an error while loading. Please reload this page.
davidwrighton
commented
Oct 28, 2020
Given the current state of what can be done with devirtualization in the JIT I worry that directly calling an instantiating stub may not provide much of a performance win. We'll need at least some level of performance benchmarking to indicate how much improvement this can provide. |
@davidwrighton I've named this PR in consistent way with the problem described in #9588 After spending some time on this issue it appears the title does not describe preciselly what have been done in this PR. Let me briefly explain my understaing of "instantiating/unboxing stub" first: this is purely VM concept, there is no common asm counterpart of this on the compiler side, like the ones we call "PrecodeFixup", "PreStub" etc. It is the duty of the one who generates the code to emit all necessary instructions once they have been told by VM side that I would really love to hear your criticism on stated above. For performance sake
interfaceI<T>{stringDefaultTypeOf()=>typeof(T).Name;}classC:I<string>{}publicstaticclassProgram{[MethodImpl(MethodImplOptions.AggressiveOptimization)]staticintMain(){I<string>c=newC();vardcs=c.DefaultTypeOf();if(dcs!="String")return200;return100;}}when jitted relevant parts of Main would be: 00007FFEFA6AC33A call 00007FFEFA690088 # C.ctor()00007FFEFA6AC33F movrcx,rsi00007FFEFA6AC342 movrdx,7FFEFAA10860h # I`1[[System.String]] type handle00007FFEFA6AC34C call 00007FFEFA6ABDB8 # I`1[__Canon][System.__Canon]:DefaultTypeOf()00007FFEFA6AC351 movrcx,rax00007FFEFA6AC354 movrdx,1DD3FD031A8h00007FFEFA6AC35E movrdx,qword ptr [rdx]00007FFEFA6AC361 call 00007FFEFA695978 # String equals00007FFEFA6AC366 testeax,eax00007FFEFA6AC368 je 00007FFEFA6AC375 00007FFEFA6AC36A moveax,0C8h00007FFEFA6AC36F addrsp,20h00007FFEFA6AC373 poprsi00007FFEFA6AC374 ret
00007FFEFA6ABDB8 call PrecodeFixupThunk (07FFF594EEAD0h) and after jitting of 00007FFEFA6ABDB8 jmp 00007FFEFA6AC3D0 If we replace interface with class with virtual method like that: classI<T>{publicvirtualstringDefaultTypeOf()=>typeof(T).Name;}the only difference (in case of devirt) would be the absence of type handle store in rdx (we didn't tell compiler to emit it, because methodtable of 00007FFEFA68C36F call qword ptr [7FFEFA9F0730h]For me this is clearly better than VSD in non-devirtualized interface case and just one In given example we were not able to inline due to limitations mentioned in #38477 But if we try something like that: interfaceI<T>{TGetAt(inti,T[]tx)=>tx[i];}classC:I<string>{}publicstaticclassProgram{privatestaticstring[]tx=newstring[]{"test"};[MethodImpl(MethodImplOptions.AggressiveOptimization)]staticintMain(){I<string>c=newC();vardcs=c.GetAt(0,newstring[]{"test"});if(dcs!="test")return200;return100;}}we will get inlined code IN0001: 000005movrcx,0x7FFEFA9F0858IN0002: 00000F call CORINFO_HELP_NEWSFASTIN0003: 000014movrcx,raxIN0004: 000017call System.Object:.ctor():thisIN0005: 00001C movrcx,0x7FFEFA9618E0IN0006: 000026movedx,1IN0007: 00002B call CORINFO_HELP_NEWARR_1_OBJIN0008: 000030learcx, bword ptr [rax+16]IN0009: 000034movrdx,0x284C25431A8IN000a: 00003E movrsi, gword ptr [rdx]IN000b: 000041movrdx,rsiIN000c: 000044call CORINFO_HELP_ASSIGN_REFIN000d: 000049movrcx,rsiIN000e: 00004C movrdx,rsiIN000f: 00004F call System.String:op_Inequality(System.String,System.String):boolIN0010: 000054testeax,eaxIN0011: 000056je SHORT G_M24375_IG05I'm totally ok if you still not convinced: would synthetic BDN harness be suffictient to demonstrate improvement or do I need to hack something like stuff described here then? |
Some asm diff stats:
IN0008: 000000subrsp,40IN0009: 000004xorrax,raxIN000a: 000006mov qword ptr [V00 rsp+20H],raxG_M29659_IG02: ; offs=00000BH, size=0029H, bbWeight=1 PerfScore 6.75, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, byrefIN0001: 00000Bmovrcx,0x13F900031A8 # "IFrobber<T>:Frob"IN0002: 000015movrcx, gword ptr [rcx]IN0003: 000018call System.Console:WriteLine(System.String)IN0004: 00001D movrcx,0x13F900031B0 # "IRobber<T>:Frob"IN0005: 000027movrcx, gword ptr [rcx]IN0006: 00002A call System.Console:WriteLine(System.String)IN0007: 00002F moveax,100 # 34+66G_M29659_IG03: ; offs=000034H, size=0005H, bbWeight=1 PerfScore 1.25, epilog, nogc, extendIN000b: 000034addrsp,40IN000c: 000038ret
IN0005: 000000subrsp,56IN0006: 000004xorrax,raxIN0007: 000006mov qword ptr [V05 rsp+28H],raxIN0008: 00000Bmov qword ptr [V05+0x8rsp+30H],raxG_M27646_IG02: ; offs=000010H, size=0010H, bbWeight=1 PerfScore 2.00, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, byrefIN0001: 000010learcx, bword ptr [V05 rsp+28H]IN0002: 000015movedx,42IN0003: 00001A call IM`1[Int32][System.Int32]:DefaultM(int):System.Threading.Tasks.ValueTaskIN0004: 00001F nopG_M27646_IG03: ; offs=000020H, size=0005H, bbWeight=1 PerfScore 1.25, epilog, nogc, extendIN0009: 000020addrsp,56IN000a: 000024retEDIT: to have stats reported by jit-diff properly, it has to be patched to move coreclr binaries around, see dotnet/jitutils#296 |
davidwrighton
commented
Nov 1, 2020
@Rattenkrieg I would prefer some level of actual benchmarking, but the analysis you have provided is sufficient to convince me that there is value here, and this is worth implementing. I'm currently very focused on finishing up some details around crossgen2 compilation and that will occupy me for a few more days before I can deeply analyze the correctness of this change, and probably write a few extra test cases to cover possible problems I see. (Primarily around cases where the type implements multiple generic variants of the same interface.). In addition, I'd like to see @AndyAyersMS or someone he designates take a look at the jit/jitinterface api side of this change. With this work, we are very close to supporting devirtualization of generic virtual methods, which would also be interesting, and it might be best to make the jit interface api reflect that possibility as well. |
AndyAyersMS
commented
Nov 2, 2020
This is going to take a bit of time to sort through. But some quick initial thoughts:
|
Rattenkrieg
commented
Nov 3, 2020
@AndyAyersMS I'll check params passing solution for jit-diff -fAll these improvements are due to #43668 (comment) |
Rattenkrieg
commented
Nov 3, 2020
No rush needed! I've pushed some tests I used for debugging, most of them are primitive since I was primarily interested in straightforward asm for analysis. But there are few testing "cases where the type implements multiple generic variants of the same interface". I don't propose to merge those, just added it for reference. Now I'm going to work on benchmarks, mostly for methods were reported as changed by jit-diff. |
c751e71 to
f145018CompareRattenkrieg
commented
Nov 4, 2020
Here are benchmarks made from pushed tests. Ran with resultsBenchmarkDotNet=v0.12.1.1448-nightly, OS=fedora 33
Intel Core i7-8700 CPU 3.20GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET SDK=6.0.100-alpha.1.20553.9
[Host] : .NET 6.0.0 (6.0.20.55204), X64 RyuJIT
Job-UOOGDK : .NET 6.0 (42.42.42.42424), X64 RyuJIT
Job-ENJNBQ : .NET 6.0 (42.42.42.42424), X64 RyuJIT
PowerPlanMode=00000000-0000-0000-0000-000000000000 Arguments=/p:DebugType=portable IterationTime=250.0000 ms MaxIterationCount=20 MinIterationCount=15 WarmupCount=1
|
There was a problem hiding this comment.
Be advised, although I'm not 100% sure, same below.
There was a problem hiding this comment.
Yeah, it looks like the wrong map is getting used here. Suspect the keys are conformable and don't collide so we never noticed.
cc @sandreenko
There was a problem hiding this comment.
Agree, thanks for catching this.
Rattenkrieg
commented
Nov 22, 2020
I don't know the reason behind timeouts in few tests, have everything passing on my machine. Besides that I think that I'm done with implementation. |
Format is failing on CISame on my machineWindows |
5fe23e4 to
e91c55bCompareRattenkrieg
commented
Dec 21, 2020
@davidwrighton@AndyAyersMS PTAL Initially I thought that my solution is incomplete wrt generic methods on non-generic interfaces but tests demonstrated that we never touch devirtualization for such cases. Consider: interfaceI{stringDefaultTypeOf<T>()=>typeof(T).Name;}classC:I{}publicstaticclassProgram{[MethodImpl(MethodImplOptions.AggressiveOptimization)]staticintMain(){Ic=newC();vardcs=c.DefaultTypeOf();if(dcs!="Object")return200;return100;}}produces IN0001: 000008movrdi,0x12293E518IN0002: 000012call CORINFO_HELP_NEWSFASTIN0003: 000017movrbx,raxIN0004: 00001A movrdi,rbxIN0005: 00001D call System.Object:.ctor():thisIN0006: 000022movrdi,rbxIN0007: 000025movrsi,0x12293E3C8IN0008: 00002F movrdx,0x12293E718IN0009: 000039call CORINFO_HELP_VIRTUAL_FUNC_PTRIN000a: 00003E movrdi,rbxIN000b: 000041callraxIN000c: 000043movrdi,raxIN000d: 000046movrsi,0x19B2371F0IN000e: 000050movrsi, gword ptr [rsi]IN000f: 000053call System.String:op_Inequality(System.String,System.String):boolIN0010: 000058testeax,eaxIN0011: 00005A je SHORT G_M24375_IG05If we remove DIM and let class implement interface asm stays the same. However if we remove interface, method inlines perfectly: classC{publicstringDefaultTypeOf<T>()=>typeof(T).Name;}IN0001: 000004movrdi,0x11CFFE3F8IN0002: 00000E call CORINFO_HELP_NEWSFASTIN0003: 000013movrdi,raxIN0004: 000016call System.Object:.ctor():thisIN0005: 00001Bmovrdi,0x11CDA4388IN0006: 000025call CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPEIN0007: 00002A movrdi,raxIN0008: 00002D movrax,0x11CC5A5F0IN0009: 000037call gword ptr [rax]System.RuntimeType:get_Name():System.String:thisIN000a: 000039movrdi,raxIN000b: 00003C movrsi,0x19D8FD1F0IN000c: 000046movrsi, gword ptr [rsi]IN000d: 000049call System.String:op_Inequality(System.String,System.String):boolIN000e: 00004E testeax,eaxIN000f: 000050je SHORT G_M24375_IG05But only for one generic param: classC{publicstringDefaultTypeOf<T1,T2>()=>typeof(T1).Name+typeof(T2).Name;}IN0001: 000008movrdi,0x10C68E3F8IN0002: 000012call CORINFO_HELP_NEWSFASTIN0003: 000017movrbx,raxIN0004: 00001A movrdi,rbxIN0005: 00001D call System.Object:.ctor():thisIN0006: 000022movrdi,rbxIN0007: 000025movrsi,0x10C68E608IN0008: 00002F call C:DefaultTypeOf():System.String:thisIN0009: 000034movrdi,raxIN000a: 000037movrsi,0x194F911F0IN000b: 000041movrsi, gword ptr [rsi]IN000c: 000044call System.String:op_Inequality(System.String,System.String):boolIN000d: 000049testeax,eaxIN000e: 00004B je SHORT G_M24375_IG05Have we ever considered optimizing such case? |
AndyAyersMS
commented
Dec 21, 2020
I believe all generic virtual methods are handled via the I had been thinking it wouldn't be worth addressing until after the jit is able propagate methods into |
Rattenkrieg
commented
Feb 2, 2021
Hey @davidwrighton is there still an interest from dotnet team/you in this PR? |
| public static class Program | ||
| { | ||
| [MethodImpl(MethodImplOptions.AggressiveOptimization)] | ||
| static int Main() |
There was a problem hiding this comment.
We should find a way to test this without AggressiveOptimization. Currently that will disable the Crossgen compiler and so we won't test the behavior in ahead of time compile scenarios.
| @@ -0,0 +1,31 @@ | |||
| using System; | |||
There was a problem hiding this comment.
u [](start = 0, length = 1)
All of the test files need the copyright attribution comment.
| MethodDesc *GetMethodDescForInterfaceMethod(TypeHandle ownerType, MethodDesc *pInterfaceMD, BOOL throwOnConflict); | ||
| MethodDesc *GetMethodDescForInterfaceMethod(MethodDesc *pInterfaceMD, BOOL throwOnConflict); // You can only use this one for non-generic interfaces | ||
| // ^-- I don't believe this is correct statement, we have PRECONDITION(!pInterfaceMD->HasClassOrMethodInstantiation()); which implies it can be used with generic interfaces |
There was a problem hiding this comment.
That is not the meaning of the precondition. Its actually the opposite. If the precondition isn't true, the method can't be used, and if the method is on a generic interface then HasClassOrMethodInstantiation will return true, so !HasClassOrMethodInstantiation will be false.
| // RequiresInstMethodDescArg() | ||
| // The method is itself generic and is shared between generic | ||
| // instantiations but is not itself generic. Furthermore | ||
| // instantiations but is not itself generic. WTF LOL. Furthermore |
There was a problem hiding this comment.
Words are hard, and copy/pasting is easy. The "but is not itself generic" is just wrong and should be deleted from this comment.
| // instantiations but is not itself generic. Furthermore | ||
| // instantiations but is not itself generic. WTF LOL. Furthermore | ||
| // no "this" pointer is given (e.g. a value type method), so we pass in the | ||
| // exact-instantiation method table as an extra argument. |
There was a problem hiding this comment.
method table [](start = 27, length = 12)
This should read "MethodDesc"
| MethodTable* pOwnerMT = OwnerClsHnd.GetMethodTable(); | ||
| pOwnerMT = OwnerClsHnd.GetMethodTable(); | ||
| if (!canCastStraightForward && !(pOwnerMT->IsInterface() && pObjMT->CanCastToInterface(pOwnerMT))) |
There was a problem hiding this comment.
What scenario are you addressing here?
There was a problem hiding this comment.
Sorry, I found your explanation above. If pOwnerMT isn't an interface, why don't we return false here?
In reply to: 570649611 [](ancestors = 570649611)
| false); | ||
| // Shared generic or static per-inst methods and shared methods on generic structs | ||
| // Shared generic or static per-inst methods, shared methods on generic structs and default interface methods |
There was a problem hiding this comment.
This is a good comment change.
davidwrighton
commented
Feb 5, 2021
@Rattenkrieg We are interested in this change, and based on the performance numbers I'm convinced its a nice small worthwhile change. One reason why this feature wasn't built is that it crosses feature areas and expertise of various teams, and is difficult for us to actually review for correctness. I have a few concerns
|
danmoseley
commented
Feb 10, 2021
Assigning a shepherd for this PR, feel free to change. |
davidwrighton
commented
Mar 22, 2021
@Rattenkrieg Are you able to answer my questions? If so, please let us know, as otherwise we'll be closing this PR soon. |
Rattenkrieg
commented
Mar 23, 2021
via email
Hey David! I cannot open this PR anymore, I'm getting This page is taking too long to load.
Sorry about that. Please try refreshing > and contact us if the problem persists.
Neither mobile version nor desktop one works. Seems that's because I've
accidentally merged tons of files from main while ago and github is
overwhelmed with big diffs now. So I've missed your questions, sorry.
I definitely looking to finish this PR. I guess the only option for me is
to resubmit changes with new PR and I would gladly ask you to state your
questions in the new one. …On Tue, Mar 23, 2021, 03:02 David Wrighton ***@***.***> wrote:
@Rattenkrieg <https://github.com/Rattenkrieg> Are you able to answer my
questions? If so, please let us know, as otherwise we'll be closing this PR
soon.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#43668 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/ABVALU7AGEC2BAPGLN6QY2DTE6V6NANCNFSM4SZAV26A>
.
|
@Rattenkrieg you can append |
Rattenkrieg
commented
Mar 25, 2021
Thank you @lambdageek now I'm back online. @davidwrighton I will address your feedback (hopefully) this weekend and will open a new PR with smaller carbon footprint. |
mangod9
commented
Apr 26, 2021
@Rattenkrieg can this PR be closed since you are hoping to open a new one after addressing feedback? |
Rattenkrieg
commented
Apr 27, 2021
@mangod9 sorry for inconvenience. I was planning to resubmit changes like month ago but currently I have technical issues. I'm really really looking to open new PR soon. |
mangod9
commented
Apr 27, 2021
Thanks for the update! |
Fixes#9588
I don't believe this could be that easy, otherwise why this wasn't added in dotnet/coreclr#15979 🤔
I'm struggling to find jit asm changes at the moment.
produces no diff, I've also tried with
--frameworkand--corelib.And I've also compared jit output from the following command
where XXX were
diamondshape_r.ilproj,non_virtual_calls_to_instance_methods.ilprojandsharedgenerics_d.ilprojand new project I created with the code from #39419... still no difference.
So my thoughts are: either I've missed some optimization flag or messed with setup in any other way OR we never reaching that code 🤷
Going to try tomorrow on Windows.
@MichalStrehovsky PTAL as you were the author of dotnet/coreclr#15979 and friends.