-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Conversation
@jkotas @JosephTremoulet PTAL More details in the per-commit messages. For interface calls, when jitting, this invokes the same paths in the runtime as a stub dispatch would, and so may trigger cache updates. It is not clear yet if this update is desirable or should be suppressed. The impact on the caches is similar to what would be without interface call devirtualization, and opportunities for devirtualization are still somewhat scarce, so it may not be easy to find cases where performance differs. Will post some stats shortly, and workup the desktop equivalent and run tests there too. |
Data from jit-diff on frameworks. A total of 397 interface calls were devirtualized.
Cherry-picked good looking diff where we can now dead code a runtime lookup: ; Assembly listing for method System.Collections.Concurrent.ConcurrentStack`1
; [__Canon][System.__Canon]:System.Collections.IEnumerable.GetEnumerator():ref:this
; ** Before **
; Total bytes of code 63
G_M45156_IG01:
push rsi
sub rsp, 48
mov qword ptr [rsp+28H], rcx
mov rsi, rcx
G_M45156_IG02:
mov rcx, qword ptr [rsi]
mov rdx, qword ptr [rcx+56]
mov rdx, qword ptr [rdx]
mov r11, qword ptr [rdx+40]
test r11, r11
jne SHORT G_M45156_IG03
lea rdx, [(reloc)]
call CORINFO_HELP_RUNTIMEHANDLE_CLASS
mov r11, rax
G_M45156_IG03:
mov rcx, rsi
mov rax, qword ptr [r11]
cmp dword ptr [rcx], ecx
G_M45156_IG04:
add rsp, 48
pop rsi
rex.jmp rax
; ** After **
; Devirtualized interface call to System.Collections.Generic.IEnumerable`1[__Canon]:GetEnumerator;
; now direct call to System.Collections.Concurrent.ConcurrentStack`1[__Canon][System.__Canon]:GetEnumerator [final method]
;
; Total bytes of code 38
G_M45158_IG01:
push rsi
sub rsp, 16
mov qword ptr [rsp+08H], rcx
mov rsi, rcx
G_M45158_IG02:
mov rdx, qword ptr [rsi]
mov rdx, gword ptr [rsi+8]
mov rcx, rsi
lea rax, [(reloc)]
G_M45158_IG03:
add rsp, 16
pop rsi
rex.jmp rax |
Not sure yet what is up with the failing windows pri1 tests. I don't normally run the full pri-1 suite locally. Investigating. |
src/vm/jitinterface.cpp
Outdated
// | ||
// For generic interface methods we must have an ownerType to | ||
// safely devirtualize. | ||
if (ownerType != nullptr) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May need o check here that the type implements the interface, similar to what is done for the non-interface case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that check is needed. The failing tests have instances where casts are missing and the jit ends up seeing an invalid type.
; Loader\classloader\TypeGeneratorTests\TypeGeneratorTest908\Generated908\Generated908.exe
; Framework:MethodCallingTest()
; local0 is type object
IL_0113 06 ldloc.0
IL_0114 25 dup
IL_0115 6f 28 00 00 06 callvirt 0x6000028
What is the best way to do this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using CanCastToInterface
seems to work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right.
d5d5d01
to
913795e
Compare
Updated with additional check on VM side. Full Pri1 suite passes locally. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, nothing but nits.
src/vm/jitinterface.cpp
Outdated
TypeHandle DerivedClsHnd(derivedClass); | ||
MethodTable* pDerivedMT = DerivedClsHnd.GetMethodTable(); | ||
_ASSERTE(pDerivedMT->IsRestored() && pDerivedMT->IsFullyLoaded()); | ||
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: inserted trailing whitespace
src/vm/jitinterface.cpp
Outdated
// exactly derived class. It is up to the jit to determine whether | ||
// directly calling this method is correct. | ||
pDevirtMD = pDerivedMT->GetMethodDescForSlot(slot); | ||
_ASSERTE(pDevirtMD->IsRestored()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Redundant with the assert 3 lines down; did you mean to have both?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since it is common to both virtual and interface, we only want the second one... will fix.
{ | ||
new public int F() { return 33; } | ||
override public int G() { return 22; } | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could consider adding an interface method that B explicitly implements (int Iz.H() { return 11; }
)
You could also consider adding interfaces that B implements and Z re-implements (for various combinations of implementing explicitly/name-matching-virtual/name-matching-nonvirtual/inheriting-from-B).
public static int Main() | ||
{ | ||
// B:F Z:G B:F B:G | ||
return Fx(new Z()) + Gy(new Z()) + Fx(new B()) + Gy(new B()) - 65; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we get ((Ix)new Z()).F()
as well, or is there something special about having the cast to interface happen implicitly for argument passing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll look into this. If C# can elide emitting an actual cast that version should also lead to devirtualization.
We currently lose types in checkcast. I have a partial fix worked up but need to understand (if downcasts are explicit) which casts add type info and which casts lose type info and avoid losing type info.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are some interface casts that change behaviour, like casting a dictionary to IDictionary changes the enumerator behaviour (from KeyValuePair to DictionaryEntry); may or maynot before something to watch for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@benaadams I believe that is a C# language feature. At runtime an object only has one type and casting does not alter the object in any way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like downcasts (to supertype) aren't explicit. Will update the test with some more cases, though I am not sure I will get every possible variation.
Windows debug failure was a timeout in tailcall_v4_hijacking. Windows release failures don't repro locally. Only one of the cases does any interface call devirtualization, and it looks to be correct. Hate to do this but I am going to retry.... @dotnet-bot retest Windows_NT x64 Release Priority 1 Build and Test |
Extend resolveVirtualMethod to take in the owner type needed to resolve shared generic interface calls, and implement VM support for interface call devirtualization. Add a check that the class presented by the jit implements the interface, to catch cases where the IL is missing type casts. Update jit GUID, SPMI and zap to reflect the changed signature.
Unblock VM queries where the base class is an interface (queries where the implementing class is also an interface are still blocked as no resolution is possible). Pass along the context handle that the jit got from getCallInfo.
913795e
to
4e02195
Compare
Retest was green. Updates per @JosephTremoulet comments. |
Ported changes over to destktop, initial testing looks good there too. |
{ | ||
new public int F() { return 13; } | ||
override public int G() { return 17; } | ||
int Iz.H() { return 19; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If Iz
had a couple more methods, B
and Z
could implement and re-implement them similar to how they do F
and G
(shadowing and overriding). I don't know/recall what the various C#isms map to in the MSIL for slots and overrides well enough to know if that would be testing anything novel over what you have.
Updates look good with one comment, still green from me. |
@jkotas any other feedback? |
@dotnet-bot test Windows_NT x64 corefx_baseline |
LGTM |
@RussKeldorph what is the trigger phrase for corefx testing? |
Ah, looks like what I used above worked, but the test leg seems broken.... |
@AndyAyersMS Yes, corefx jobs are broken due to 14207f4 breaking change. Apparently we have to wait until corefx ingests a coreclr with those bits before the break can be resolved. @rahku is tracking it. |
Three commits, first has VM changes, second has jit changes, third has tests.