-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Devirtualization with multiple guesses: JIT #86809
Conversation
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch Issue DetailsThis PR enables GDV with multiple candidates for JIT. (did the initial infra and enabled that for NativeAOT) public interface IValue
{
int GetValue();
}
public class MyClass1 : IValue
{
public int GetValue() => 10;
}
public class MyClass2 : IValue
{
public int GetValue() => 50;
}
public class MyClass3 : IValue
{
public int GetValue() => 100;
}
public class Program
{
public static void Main(string[] args)
{
for (int i = 0; i < 200; i++)
{
Test(new MyClass1());
Test(new MyClass1());
Test(new MyClass2());
Test(new MyClass3());
Thread.Sleep(16);
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
static int Test(IValue value) => value.GetValue();
} JIT's codegen for ; Assembly listing for method Program:Test(IValue):int
sub rsp, 40
mov r11, qword ptr [rcx]
mov rax, 0x7FFBC9FE9C28 ;; is it MyClass1?
cmp r11, rax
jne SHORT G_M7592_IG04
mov eax, 10
jmp SHORT G_M7592_IG09
G_M7592_IG04:
mov rax, 0x7FFBC9FE9DF0 ;; is it MyClass2?
cmp r11, rax
jne SHORT G_M7592_IG06
mov eax, 50
jmp SHORT G_M7592_IG09
G_M7592_IG06:
mov rax, 0x7FFBC9FE9FB8 ;; is it MyClass3?
cmp r11, rax
jne SHORT G_M7592_IG08
mov eax, 100
jmp SHORT G_M7592_IG09
G_M7592_IG08:
mov r11, 0x7FFBC9400310 ;; virtual fallback
call [r11]IValue:GetValue():int:this
G_M7592_IG09:
nop
add rsp, 40
ret
; Total bytes of code 92 I will keep the number of candidates to check just
|
/azp run runtime-coreclr pgo, runtime-coreclr pgostress, runtime-coreclr libraries-pgo |
Azure Pipelines successfully started running 3 pipeline(s). |
/azp run runtime-coreclr pgo, runtime-coreclr pgostress |
Azure Pipelines successfully started running 2 pipeline(s). |
/azp run runtime-coreclr pgo, runtime-coreclr pgostress |
Azure Pipelines successfully started running 2 pipeline(s). |
@AndyAyersMS @jakobbotsch PTAL, passes all the tests. I'm going to revert the default number of candidates back to 1 (and enable multiple candidates for pgostress only) - just wanted to make sure it passes CI tests. NOTE: I recommend to review with "Hide whitespaces" option enabled. |
/azp run runtime-coreclr pgo, runtime-coreclr pgostress |
Azure Pipelines successfully started running 2 pipeline(s). |
Stats for the minimal-api (TodosApp) NativeAOT + Exact classes: |
PTAL @AndyAyersMS, this also fixed a JitDump-only assert |
} | ||
else | ||
{ | ||
// We're allowed to make more than 2 guesses - pick all types with likelihood >= 10% |
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.
Note with an 8 entry reservoir the smallest value we'll ever see is 12.5%.
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 assume it's an implementation detail JIT doesn't have to know, right? (and we might change it) Do you want me to change it to 12.5?
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.
No, just pointing out that currently you may never see a candidate that will fail this test.
Ultimately, we might want to compute the conditional probability. Say there are two candidates with likelihoods of 90 and 9. If the target is not the first candidate, the conditional probability of it being the second candidate is now 90%. (and if there was a third that was say 0.9, if it was not the first two, it is 90% likely to be that third one).
But to do this, we'd have to have more confidence in those low likelihoods.
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.
Btw, with the current logic, the 10% threshold is likely be ignored for anything more complicated than a simple method because inliner will fine it for too low block weight
|
||
if (IsChainingSupported()) | ||
{ | ||
call->SetIsGuarded(); |
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 don't understand this change
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.
@AndyAyersMS it's a theoretical case. IsGuarded is only used for GDV chaining. Imagine we have:
obj.Call1();
obj.Call2();
and Call1()
is expanded using 2 classes and chaining is disabled (since it only works with a single candidate). Call2()
is expanded with just 1 class - it will try to "chain" with the previous Call1
that doesn't support chaining.
PS: this PR is zero diffs, it doesn't yet enable multiple GDV
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.
Are you sure? I think IsGuarded
is currently just used to annotate the inlining tree and inline XML. It means that a callee was GDV and we now decided to test for it and inline it. So I think you can just call it always here.
The inline tree and XML will get confused when one call site expands to multiple candidates. We should think about how we want to express something like multi-guess GDV for those situations. But that can wait.
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're right, I've reverted that change as it's not needed
|
||
if (IsChainingSupported()) | ||
{ | ||
call->SetIsGuarded(); |
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.
Ditto here
if ((inlineInfo != nullptr) && (inlineInfo->exactContextHnd != nullptr)) | ||
{ | ||
printf(" (exactContextHnd=0x%p)", dspPtr(inlineInfo->exactContextHnd)); | ||
} |
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.
These changes are JitDump only, gtDispTree
was not aware that a call might have more than a single candidate.
…sed to produce unexpected diffs
Contributes to #86769
This PR enables GDV with multiple candidates for JIT. (#86551 did the initial infra and enabled that for NativeAOT)
Example:
JIT's codegen for
Test
:I will keep the number of candidates to check just
1
(current behavior in Main) for JIT untill I implement all work items in #86769