Skip to content

Devirtualization with multiple guesses: JIT - #86809

Merged
EgorBo merged 11 commits into
dotnet:mainfrom
EgorBo:multiple-gdv-jit
Jun 1, 2023
Merged

Devirtualization with multiple guesses: JIT#86809
EgorBo merged 11 commits into
dotnet:mainfrom
EgorBo:multiple-gdv-jit

Conversation

@EgorBo

@EgorBoEgorBo commented May 26, 2023

Copy link
Copy Markdown
Member

Contributes to #86769

This PR enables GDV with multiple candidates for JIT. (#86551 did the initial infra and enabled that for NativeAOT)
Example:

publicinterfaceIValue{intGetValue();}publicclassMyClass1:IValue{publicintGetValue()=>10;}publicclassMyClass2:IValue{publicintGetValue()=>50;}publicclassMyClass3:IValue{publicintGetValue()=>100;}publicclassProgram{publicstaticvoidMain(string[]args){for(inti=0;i<200;i++){Test(newMyClass1());// MyClass1 has the highest likelihoodTest(newMyClass1());Test(newMyClass2());Test(newMyClass3());Thread.Sleep(16);}}[MethodImpl(MethodImplOptions.NoInlining)]staticintTest(IValuevalue)=>value.GetValue();}

JIT's codegen for Test:

; Assembly listing for method Program:Test(IValue):intsubrsp,40movrax,0x7FFBDC9C2000cmp qword ptr [rcx],rax ;; is it MyClass1 (50%) ?jne SHORT G_M7592_IG04moveax,10jmp SHORT G_M7592_IG07G_M7592_IG04:movrax,0x7FFBDC9C21C8cmp qword ptr [rcx],rax ;; is it MyClass2 (25%) ?jne SHORT G_M7592_IG06moveax,50jmp SHORT G_M7592_IG07G_M7592_IG06:movrax,0x7FFBDC9C2390cmp qword ptr [rcx],rax ;; is it MyClass3 (25%) ?jne SHORT G_M7592_IG08moveax,100G_M7592_IG07:addrsp,40retG_M7592_IG08:movr11,0x7FFBDC0C0048 ;; cold virtual fallback (0%)call[r11]IValue:GetValue():int:thisjmp SHORT G_M7592_IG07; Total bytes of code 88

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

@EgorBo
EgorBo marked this pull request as draft May 26, 2023 19:03
@ghostghost added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label May 26, 2023
@ghostghost assigned EgorBoMay 26, 2023
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

Issue Details

This PR enables GDV with multiple candidates for JIT. (did the initial infra and enabled that for NativeAOT)
Example:

publicinterfaceIValue{intGetValue();}publicclassMyClass1:IValue{publicintGetValue()=>10;}publicclassMyClass2:IValue{publicintGetValue()=>50;}publicclassMyClass3:IValue{publicintGetValue()=>100;}publicclassProgram{publicstaticvoidMain(string[]args){for(inti=0;i<200;i++){Test(newMyClass1());Test(newMyClass1());Test(newMyClass2());Test(newMyClass3());Thread.Sleep(16);}}[MethodImpl(MethodImplOptions.NoInlining)]staticintTest(IValuevalue)=>value.GetValue();}

JIT's codegen for Test:

; Assembly listing for method Program:Test(IValue):intsubrsp,40movr11, qword ptr [rcx]movrax,0x7FFBC9FE9C28 ;; is it MyClass1?cmpr11,raxjne SHORT G_M7592_IG04moveax,10jmp SHORT G_M7592_IG09G_M7592_IG04: movrax,0x7FFBC9FE9DF0 ;; is it MyClass2?cmpr11,raxjne SHORT G_M7592_IG06moveax,50jmp SHORT G_M7592_IG09G_M7592_IG06: movrax,0x7FFBC9FE9FB8 ;; is it MyClass3?cmpr11,raxjne SHORT G_M7592_IG08moveax,100jmp SHORT G_M7592_IG09G_M7592_IG08: movr11,0x7FFBC9400310 ;; virtual fallbackcall[r11]IValue:GetValue():int:thisG_M7592_IG09: nopaddrsp,40ret; Total bytes of code 92

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

Author:EgorBo
Assignees:EgorBo
Labels:

area-CodeGen-coreclr

Milestone:-

@EgorBoEgorBo mentioned this pull request May 26, 2023
14 tasks
@EgorBo

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-coreclr pgo, runtime-coreclr pgostress, runtime-coreclr libraries-pgo

@azure-pipelines

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

@EgorBo

Copy link
Copy Markdown
MemberAuthor

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

@azure-pipelines

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

@EgorBo
EgorBo marked this pull request as ready for review May 26, 2023 21:26
@EgorBo

Copy link
Copy Markdown
MemberAuthor

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

@azure-pipelines

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

@EgorBo

EgorBo commented May 27, 2023

Copy link
Copy Markdown
MemberAuthor

@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.

@EgorBo

Copy link
Copy Markdown
MemberAuthor

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

@azure-pipelines

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

@EgorBo

EgorBo commented May 28, 2023

Copy link
Copy Markdown
MemberAuthor

Stats for the minimal-api (TodosApp) NativeAOT + Exact classes:
829 calls are expanded with 2 or 3 type checks
+80.5Kb size regression (+0.3% size increase) - doesn't look bad for 26.6Mb app

@EgorBo

Copy link
Copy Markdown
MemberAuthor

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%

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.

Note with an 8 entry reservoir the smallest value we'll ever see is 12.5%.

@EgorBoEgorBoMay 31, 2023

Copy link
Copy Markdown
MemberAuthor

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?

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.

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.

Copy link
Copy Markdown
MemberAuthor

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();

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.

I don't understand this change

@EgorBoEgorBoMay 31, 2023

Copy link
Copy Markdown
MemberAuthor

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

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.

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.

Copy link
Copy Markdown
MemberAuthor

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();

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.

Ditto here

if ((inlineInfo != nullptr) && (inlineInfo->exactContextHnd != nullptr))
{
printf(" (exactContextHnd=0x%p)", dspPtr(inlineInfo->exactContextHnd));
}

Copy link
Copy Markdown
MemberAuthor

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.

@EgorBo

Copy link
Copy Markdown
MemberAuthor

@EgorBo
EgorBo merged commit 89c96dc into dotnet:mainJun 1, 2023
@EgorBo
EgorBo deleted the multiple-gdv-jit branch June 1, 2023 22:36
@ghostghost locked as resolved and limited conversation to collaborators Jul 2, 2023
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 SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@EgorBo@AndyAyersMS
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Devirtualization with multiple guesses: JIT by EgorBo · Pull Request #86809 · dotnet/runtime · GitHub
Skip to content

Devirtualization with multiple guesses: JIT - #86809

Merged
EgorBo merged 11 commits into
dotnet:mainfrom
EgorBo:multiple-gdv-jit
Jun 1, 2023
Merged

Devirtualization with multiple guesses: JIT#86809
EgorBo merged 11 commits into
dotnet:mainfrom
EgorBo:multiple-gdv-jit

Conversation

@EgorBo

@EgorBoEgorBo commented May 26, 2023

Copy link
Copy Markdown
Member

Contributes to #86769

This PR enables GDV with multiple candidates for JIT. (#86551 did the initial infra and enabled that for NativeAOT)
Example:

publicinterfaceIValue{intGetValue();}publicclassMyClass1:IValue{publicintGetValue()=>10;}publicclassMyClass2:IValue{publicintGetValue()=>50;}publicclassMyClass3:IValue{publicintGetValue()=>100;}publicclassProgram{publicstaticvoidMain(string[]args){for(inti=0;i<200;i++){Test(newMyClass1());// MyClass1 has the highest likelihoodTest(newMyClass1());Test(newMyClass2());Test(newMyClass3());Thread.Sleep(16);}}[MethodImpl(MethodImplOptions.NoInlining)]staticintTest(IValuevalue)=>value.GetValue();}

JIT's codegen for Test:

; Assembly listing for method Program:Test(IValue):intsubrsp,40movrax,0x7FFBDC9C2000cmp qword ptr [rcx],rax ;; is it MyClass1 (50%) ?jne SHORT G_M7592_IG04moveax,10jmp SHORT G_M7592_IG07G_M7592_IG04:movrax,0x7FFBDC9C21C8cmp qword ptr [rcx],rax ;; is it MyClass2 (25%) ?jne SHORT G_M7592_IG06moveax,50jmp SHORT G_M7592_IG07G_M7592_IG06:movrax,0x7FFBDC9C2390cmp qword ptr [rcx],rax ;; is it MyClass3 (25%) ?jne SHORT G_M7592_IG08moveax,100G_M7592_IG07:addrsp,40retG_M7592_IG08:movr11,0x7FFBDC0C0048 ;; cold virtual fallback (0%)call[r11]IValue:GetValue():int:thisjmp SHORT G_M7592_IG07; Total bytes of code 88

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

@EgorBo
EgorBo marked this pull request as draft May 26, 2023 19:03
@ghostghost added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label May 26, 2023
@ghostghost assigned EgorBoMay 26, 2023
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

Issue Details

This PR enables GDV with multiple candidates for JIT. (did the initial infra and enabled that for NativeAOT)
Example:

publicinterfaceIValue{intGetValue();}publicclassMyClass1:IValue{publicintGetValue()=>10;}publicclassMyClass2:IValue{publicintGetValue()=>50;}publicclassMyClass3:IValue{publicintGetValue()=>100;}publicclassProgram{publicstaticvoidMain(string[]args){for(inti=0;i<200;i++){Test(newMyClass1());Test(newMyClass1());Test(newMyClass2());Test(newMyClass3());Thread.Sleep(16);}}[MethodImpl(MethodImplOptions.NoInlining)]staticintTest(IValuevalue)=>value.GetValue();}

JIT's codegen for Test:

; Assembly listing for method Program:Test(IValue):intsubrsp,40movr11, qword ptr [rcx]movrax,0x7FFBC9FE9C28 ;; is it MyClass1?cmpr11,raxjne SHORT G_M7592_IG04moveax,10jmp SHORT G_M7592_IG09G_M7592_IG04: movrax,0x7FFBC9FE9DF0 ;; is it MyClass2?cmpr11,raxjne SHORT G_M7592_IG06moveax,50jmp SHORT G_M7592_IG09G_M7592_IG06: movrax,0x7FFBC9FE9FB8 ;; is it MyClass3?cmpr11,raxjne SHORT G_M7592_IG08moveax,100jmp SHORT G_M7592_IG09G_M7592_IG08: movr11,0x7FFBC9400310 ;; virtual fallbackcall[r11]IValue:GetValue():int:thisG_M7592_IG09: nopaddrsp,40ret; Total bytes of code 92

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

Author:EgorBo
Assignees:EgorBo
Labels:

area-CodeGen-coreclr

Milestone:-

@EgorBoEgorBo mentioned this pull request May 26, 2023
14 tasks
@EgorBo

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-coreclr pgo, runtime-coreclr pgostress, runtime-coreclr libraries-pgo

@azure-pipelines

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

@EgorBo

Copy link
Copy Markdown
MemberAuthor

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

@azure-pipelines

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

@EgorBo
EgorBo marked this pull request as ready for review May 26, 2023 21:26
@EgorBo

Copy link
Copy Markdown
MemberAuthor

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

@azure-pipelines

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

@EgorBo

EgorBo commented May 27, 2023

Copy link
Copy Markdown
MemberAuthor

@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.

@EgorBo

Copy link
Copy Markdown
MemberAuthor

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

@azure-pipelines

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

@EgorBo

EgorBo commented May 28, 2023

Copy link
Copy Markdown
MemberAuthor

Stats for the minimal-api (TodosApp) NativeAOT + Exact classes:
829 calls are expanded with 2 or 3 type checks
+80.5Kb size regression (+0.3% size increase) - doesn't look bad for 26.6Mb app

@EgorBo

Copy link
Copy Markdown
MemberAuthor

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%

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.

Note with an 8 entry reservoir the smallest value we'll ever see is 12.5%.

@EgorBoEgorBoMay 31, 2023

Copy link
Copy Markdown
MemberAuthor

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?

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.

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.

Copy link
Copy Markdown
MemberAuthor

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();

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.

I don't understand this change

@EgorBoEgorBoMay 31, 2023

Copy link
Copy Markdown
MemberAuthor

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

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.

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.

Copy link
Copy Markdown
MemberAuthor

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();

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.

Ditto here

if ((inlineInfo != nullptr) && (inlineInfo->exactContextHnd != nullptr))
{
printf(" (exactContextHnd=0x%p)", dspPtr(inlineInfo->exactContextHnd));
}

Copy link
Copy Markdown
MemberAuthor

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.

@EgorBo

Copy link
Copy Markdown
MemberAuthor

@EgorBo
EgorBo merged commit 89c96dc into dotnet:mainJun 1, 2023
@EgorBo
EgorBo deleted the multiple-gdv-jit branch June 1, 2023 22:36
@ghostghost locked as resolved and limited conversation to collaborators Jul 2, 2023
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 SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@EgorBo@AndyAyersMS
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Devirtualization with multiple guesses: JIT by EgorBo · Pull Request #86809 · dotnet/runtime · GitHub
Skip to content

Devirtualization with multiple guesses: JIT - #86809

Merged
EgorBo merged 11 commits into
dotnet:mainfrom
EgorBo:multiple-gdv-jit
Jun 1, 2023
Merged

Devirtualization with multiple guesses: JIT#86809
EgorBo merged 11 commits into
dotnet:mainfrom
EgorBo:multiple-gdv-jit

Conversation

@EgorBo

@EgorBoEgorBo commented May 26, 2023

Copy link
Copy Markdown
Member

Contributes to #86769

This PR enables GDV with multiple candidates for JIT. (#86551 did the initial infra and enabled that for NativeAOT)
Example:

publicinterfaceIValue{intGetValue();}publicclassMyClass1:IValue{publicintGetValue()=>10;}publicclassMyClass2:IValue{publicintGetValue()=>50;}publicclassMyClass3:IValue{publicintGetValue()=>100;}publicclassProgram{publicstaticvoidMain(string[]args){for(inti=0;i<200;i++){Test(newMyClass1());// MyClass1 has the highest likelihoodTest(newMyClass1());Test(newMyClass2());Test(newMyClass3());Thread.Sleep(16);}}[MethodImpl(MethodImplOptions.NoInlining)]staticintTest(IValuevalue)=>value.GetValue();}

JIT's codegen for Test:

; Assembly listing for method Program:Test(IValue):intsubrsp,40movrax,0x7FFBDC9C2000cmp qword ptr [rcx],rax ;; is it MyClass1 (50%) ?jne SHORT G_M7592_IG04moveax,10jmp SHORT G_M7592_IG07G_M7592_IG04:movrax,0x7FFBDC9C21C8cmp qword ptr [rcx],rax ;; is it MyClass2 (25%) ?jne SHORT G_M7592_IG06moveax,50jmp SHORT G_M7592_IG07G_M7592_IG06:movrax,0x7FFBDC9C2390cmp qword ptr [rcx],rax ;; is it MyClass3 (25%) ?jne SHORT G_M7592_IG08moveax,100G_M7592_IG07:addrsp,40retG_M7592_IG08:movr11,0x7FFBDC0C0048 ;; cold virtual fallback (0%)call[r11]IValue:GetValue():int:thisjmp SHORT G_M7592_IG07; Total bytes of code 88

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

@EgorBo
EgorBo marked this pull request as draft May 26, 2023 19:03
@ghostghost added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label May 26, 2023
@ghostghost assigned EgorBoMay 26, 2023
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

Issue Details

This PR enables GDV with multiple candidates for JIT. (did the initial infra and enabled that for NativeAOT)
Example:

publicinterfaceIValue{intGetValue();}publicclassMyClass1:IValue{publicintGetValue()=>10;}publicclassMyClass2:IValue{publicintGetValue()=>50;}publicclassMyClass3:IValue{publicintGetValue()=>100;}publicclassProgram{publicstaticvoidMain(string[]args){for(inti=0;i<200;i++){Test(newMyClass1());Test(newMyClass1());Test(newMyClass2());Test(newMyClass3());Thread.Sleep(16);}}[MethodImpl(MethodImplOptions.NoInlining)]staticintTest(IValuevalue)=>value.GetValue();}

JIT's codegen for Test:

; Assembly listing for method Program:Test(IValue):intsubrsp,40movr11, qword ptr [rcx]movrax,0x7FFBC9FE9C28 ;; is it MyClass1?cmpr11,raxjne SHORT G_M7592_IG04moveax,10jmp SHORT G_M7592_IG09G_M7592_IG04: movrax,0x7FFBC9FE9DF0 ;; is it MyClass2?cmpr11,raxjne SHORT G_M7592_IG06moveax,50jmp SHORT G_M7592_IG09G_M7592_IG06: movrax,0x7FFBC9FE9FB8 ;; is it MyClass3?cmpr11,raxjne SHORT G_M7592_IG08moveax,100jmp SHORT G_M7592_IG09G_M7592_IG08: movr11,0x7FFBC9400310 ;; virtual fallbackcall[r11]IValue:GetValue():int:thisG_M7592_IG09: nopaddrsp,40ret; Total bytes of code 92

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

Author:EgorBo
Assignees:EgorBo
Labels:

area-CodeGen-coreclr

Milestone:-

@EgorBoEgorBo mentioned this pull request May 26, 2023
14 tasks
@EgorBo

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-coreclr pgo, runtime-coreclr pgostress, runtime-coreclr libraries-pgo

@azure-pipelines

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

@EgorBo

Copy link
Copy Markdown
MemberAuthor

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

@azure-pipelines

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

@EgorBo
EgorBo marked this pull request as ready for review May 26, 2023 21:26
@EgorBo

Copy link
Copy Markdown
MemberAuthor

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

@azure-pipelines

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

@EgorBo

EgorBo commented May 27, 2023

Copy link
Copy Markdown
MemberAuthor

@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.

@EgorBo

Copy link
Copy Markdown
MemberAuthor

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

@azure-pipelines

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

@EgorBo

EgorBo commented May 28, 2023

Copy link
Copy Markdown
MemberAuthor

Stats for the minimal-api (TodosApp) NativeAOT + Exact classes:
829 calls are expanded with 2 or 3 type checks
+80.5Kb size regression (+0.3% size increase) - doesn't look bad for 26.6Mb app

@EgorBo

Copy link
Copy Markdown
MemberAuthor

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%

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.

Note with an 8 entry reservoir the smallest value we'll ever see is 12.5%.

@EgorBoEgorBoMay 31, 2023

Copy link
Copy Markdown
MemberAuthor

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?

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.

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.

Copy link
Copy Markdown
MemberAuthor

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();

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.

I don't understand this change

@EgorBoEgorBoMay 31, 2023

Copy link
Copy Markdown
MemberAuthor

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

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.

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.

Copy link
Copy Markdown
MemberAuthor

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();

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.

Ditto here

if ((inlineInfo != nullptr) && (inlineInfo->exactContextHnd != nullptr))
{
printf(" (exactContextHnd=0x%p)", dspPtr(inlineInfo->exactContextHnd));
}

Copy link
Copy Markdown
MemberAuthor

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.

@EgorBo

Copy link
Copy Markdown
MemberAuthor

@EgorBo
EgorBo merged commit 89c96dc into dotnet:mainJun 1, 2023
@EgorBo
EgorBo deleted the multiple-gdv-jit branch June 1, 2023 22:36
@ghostghost locked as resolved and limited conversation to collaborators Jul 2, 2023
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 SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@EgorBo@AndyAyersMS
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Devirtualization with multiple guesses: JIT by EgorBo · Pull Request #86809 · dotnet/runtime · GitHub
Skip to content

Devirtualization with multiple guesses: JIT - #86809

Merged
EgorBo merged 11 commits into
dotnet:mainfrom
EgorBo:multiple-gdv-jit
Jun 1, 2023
Merged

Devirtualization with multiple guesses: JIT#86809
EgorBo merged 11 commits into
dotnet:mainfrom
EgorBo:multiple-gdv-jit

Conversation

@EgorBo

@EgorBoEgorBo commented May 26, 2023

Copy link
Copy Markdown
Member

Contributes to #86769

This PR enables GDV with multiple candidates for JIT. (#86551 did the initial infra and enabled that for NativeAOT)
Example:

publicinterfaceIValue{intGetValue();}publicclassMyClass1:IValue{publicintGetValue()=>10;}publicclassMyClass2:IValue{publicintGetValue()=>50;}publicclassMyClass3:IValue{publicintGetValue()=>100;}publicclassProgram{publicstaticvoidMain(string[]args){for(inti=0;i<200;i++){Test(newMyClass1());// MyClass1 has the highest likelihoodTest(newMyClass1());Test(newMyClass2());Test(newMyClass3());Thread.Sleep(16);}}[MethodImpl(MethodImplOptions.NoInlining)]staticintTest(IValuevalue)=>value.GetValue();}

JIT's codegen for Test:

; Assembly listing for method Program:Test(IValue):intsubrsp,40movrax,0x7FFBDC9C2000cmp qword ptr [rcx],rax ;; is it MyClass1 (50%) ?jne SHORT G_M7592_IG04moveax,10jmp SHORT G_M7592_IG07G_M7592_IG04:movrax,0x7FFBDC9C21C8cmp qword ptr [rcx],rax ;; is it MyClass2 (25%) ?jne SHORT G_M7592_IG06moveax,50jmp SHORT G_M7592_IG07G_M7592_IG06:movrax,0x7FFBDC9C2390cmp qword ptr [rcx],rax ;; is it MyClass3 (25%) ?jne SHORT G_M7592_IG08moveax,100G_M7592_IG07:addrsp,40retG_M7592_IG08:movr11,0x7FFBDC0C0048 ;; cold virtual fallback (0%)call[r11]IValue:GetValue():int:thisjmp SHORT G_M7592_IG07; Total bytes of code 88

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

@EgorBo
EgorBo marked this pull request as draft May 26, 2023 19:03
@ghostghost added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label May 26, 2023
@ghostghost assigned EgorBoMay 26, 2023
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

Issue Details

This PR enables GDV with multiple candidates for JIT. (did the initial infra and enabled that for NativeAOT)
Example:

publicinterfaceIValue{intGetValue();}publicclassMyClass1:IValue{publicintGetValue()=>10;}publicclassMyClass2:IValue{publicintGetValue()=>50;}publicclassMyClass3:IValue{publicintGetValue()=>100;}publicclassProgram{publicstaticvoidMain(string[]args){for(inti=0;i<200;i++){Test(newMyClass1());Test(newMyClass1());Test(newMyClass2());Test(newMyClass3());Thread.Sleep(16);}}[MethodImpl(MethodImplOptions.NoInlining)]staticintTest(IValuevalue)=>value.GetValue();}

JIT's codegen for Test:

; Assembly listing for method Program:Test(IValue):intsubrsp,40movr11, qword ptr [rcx]movrax,0x7FFBC9FE9C28 ;; is it MyClass1?cmpr11,raxjne SHORT G_M7592_IG04moveax,10jmp SHORT G_M7592_IG09G_M7592_IG04: movrax,0x7FFBC9FE9DF0 ;; is it MyClass2?cmpr11,raxjne SHORT G_M7592_IG06moveax,50jmp SHORT G_M7592_IG09G_M7592_IG06: movrax,0x7FFBC9FE9FB8 ;; is it MyClass3?cmpr11,raxjne SHORT G_M7592_IG08moveax,100jmp SHORT G_M7592_IG09G_M7592_IG08: movr11,0x7FFBC9400310 ;; virtual fallbackcall[r11]IValue:GetValue():int:thisG_M7592_IG09: nopaddrsp,40ret; Total bytes of code 92

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

Author:EgorBo
Assignees:EgorBo
Labels:

area-CodeGen-coreclr

Milestone:-

@EgorBoEgorBo mentioned this pull request May 26, 2023
14 tasks
@EgorBo

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-coreclr pgo, runtime-coreclr pgostress, runtime-coreclr libraries-pgo

@azure-pipelines

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

@EgorBo

Copy link
Copy Markdown
MemberAuthor

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

@azure-pipelines

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

@EgorBo
EgorBo marked this pull request as ready for review May 26, 2023 21:26
@EgorBo

Copy link
Copy Markdown
MemberAuthor

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

@azure-pipelines

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

@EgorBo

EgorBo commented May 27, 2023

Copy link
Copy Markdown
MemberAuthor

@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.

@EgorBo

Copy link
Copy Markdown
MemberAuthor

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

@azure-pipelines

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

@EgorBo

EgorBo commented May 28, 2023

Copy link
Copy Markdown
MemberAuthor

Stats for the minimal-api (TodosApp) NativeAOT + Exact classes:
829 calls are expanded with 2 or 3 type checks
+80.5Kb size regression (+0.3% size increase) - doesn't look bad for 26.6Mb app

@EgorBo

Copy link
Copy Markdown
MemberAuthor

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%

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.

Note with an 8 entry reservoir the smallest value we'll ever see is 12.5%.

@EgorBoEgorBoMay 31, 2023

Copy link
Copy Markdown
MemberAuthor

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?

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.

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.

Copy link
Copy Markdown
MemberAuthor

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();

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.

I don't understand this change

@EgorBoEgorBoMay 31, 2023

Copy link
Copy Markdown
MemberAuthor

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

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.

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.

Copy link
Copy Markdown
MemberAuthor

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();

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.

Ditto here

if ((inlineInfo != nullptr) && (inlineInfo->exactContextHnd != nullptr))
{
printf(" (exactContextHnd=0x%p)", dspPtr(inlineInfo->exactContextHnd));
}

Copy link
Copy Markdown
MemberAuthor

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.

@EgorBo

Copy link
Copy Markdown
MemberAuthor

@EgorBo
EgorBo merged commit 89c96dc into dotnet:mainJun 1, 2023
@EgorBo
EgorBo deleted the multiple-gdv-jit branch June 1, 2023 22:36
@ghostghost locked as resolved and limited conversation to collaborators Jul 2, 2023
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 SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@EgorBo@AndyAyersMS
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Devirtualization with multiple guesses: JIT by EgorBo · Pull Request #86809 · dotnet/runtime · GitHub
Skip to content

Devirtualization with multiple guesses: JIT - #86809

Merged
EgorBo merged 11 commits into
dotnet:mainfrom
EgorBo:multiple-gdv-jit
Jun 1, 2023
Merged

Devirtualization with multiple guesses: JIT#86809
EgorBo merged 11 commits into
dotnet:mainfrom
EgorBo:multiple-gdv-jit

Conversation

@EgorBo

@EgorBoEgorBo commented May 26, 2023

Copy link
Copy Markdown
Member

Contributes to #86769

This PR enables GDV with multiple candidates for JIT. (#86551 did the initial infra and enabled that for NativeAOT)
Example:

publicinterfaceIValue{intGetValue();}publicclassMyClass1:IValue{publicintGetValue()=>10;}publicclassMyClass2:IValue{publicintGetValue()=>50;}publicclassMyClass3:IValue{publicintGetValue()=>100;}publicclassProgram{publicstaticvoidMain(string[]args){for(inti=0;i<200;i++){Test(newMyClass1());// MyClass1 has the highest likelihoodTest(newMyClass1());Test(newMyClass2());Test(newMyClass3());Thread.Sleep(16);}}[MethodImpl(MethodImplOptions.NoInlining)]staticintTest(IValuevalue)=>value.GetValue();}

JIT's codegen for Test:

; Assembly listing for method Program:Test(IValue):intsubrsp,40movrax,0x7FFBDC9C2000cmp qword ptr [rcx],rax ;; is it MyClass1 (50%) ?jne SHORT G_M7592_IG04moveax,10jmp SHORT G_M7592_IG07G_M7592_IG04:movrax,0x7FFBDC9C21C8cmp qword ptr [rcx],rax ;; is it MyClass2 (25%) ?jne SHORT G_M7592_IG06moveax,50jmp SHORT G_M7592_IG07G_M7592_IG06:movrax,0x7FFBDC9C2390cmp qword ptr [rcx],rax ;; is it MyClass3 (25%) ?jne SHORT G_M7592_IG08moveax,100G_M7592_IG07:addrsp,40retG_M7592_IG08:movr11,0x7FFBDC0C0048 ;; cold virtual fallback (0%)call[r11]IValue:GetValue():int:thisjmp SHORT G_M7592_IG07; Total bytes of code 88

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

@EgorBo
EgorBo marked this pull request as draft May 26, 2023 19:03
@ghostghost added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label May 26, 2023
@ghostghost assigned EgorBoMay 26, 2023
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

Issue Details

This PR enables GDV with multiple candidates for JIT. (did the initial infra and enabled that for NativeAOT)
Example:

publicinterfaceIValue{intGetValue();}publicclassMyClass1:IValue{publicintGetValue()=>10;}publicclassMyClass2:IValue{publicintGetValue()=>50;}publicclassMyClass3:IValue{publicintGetValue()=>100;}publicclassProgram{publicstaticvoidMain(string[]args){for(inti=0;i<200;i++){Test(newMyClass1());Test(newMyClass1());Test(newMyClass2());Test(newMyClass3());Thread.Sleep(16);}}[MethodImpl(MethodImplOptions.NoInlining)]staticintTest(IValuevalue)=>value.GetValue();}

JIT's codegen for Test:

; Assembly listing for method Program:Test(IValue):intsubrsp,40movr11, qword ptr [rcx]movrax,0x7FFBC9FE9C28 ;; is it MyClass1?cmpr11,raxjne SHORT G_M7592_IG04moveax,10jmp SHORT G_M7592_IG09G_M7592_IG04: movrax,0x7FFBC9FE9DF0 ;; is it MyClass2?cmpr11,raxjne SHORT G_M7592_IG06moveax,50jmp SHORT G_M7592_IG09G_M7592_IG06: movrax,0x7FFBC9FE9FB8 ;; is it MyClass3?cmpr11,raxjne SHORT G_M7592_IG08moveax,100jmp SHORT G_M7592_IG09G_M7592_IG08: movr11,0x7FFBC9400310 ;; virtual fallbackcall[r11]IValue:GetValue():int:thisG_M7592_IG09: nopaddrsp,40ret; Total bytes of code 92

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

Author:EgorBo
Assignees:EgorBo
Labels:

area-CodeGen-coreclr

Milestone:-

@EgorBoEgorBo mentioned this pull request May 26, 2023
14 tasks
@EgorBo

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-coreclr pgo, runtime-coreclr pgostress, runtime-coreclr libraries-pgo

@azure-pipelines

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

@EgorBo

Copy link
Copy Markdown
MemberAuthor

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

@azure-pipelines

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

@EgorBo
EgorBo marked this pull request as ready for review May 26, 2023 21:26
@EgorBo

Copy link
Copy Markdown
MemberAuthor

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

@azure-pipelines

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

@EgorBo

EgorBo commented May 27, 2023

Copy link
Copy Markdown
MemberAuthor

@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.

@EgorBo

Copy link
Copy Markdown
MemberAuthor

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

@azure-pipelines

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

@EgorBo

EgorBo commented May 28, 2023

Copy link
Copy Markdown
MemberAuthor

Stats for the minimal-api (TodosApp) NativeAOT + Exact classes:
829 calls are expanded with 2 or 3 type checks
+80.5Kb size regression (+0.3% size increase) - doesn't look bad for 26.6Mb app

@EgorBo

Copy link
Copy Markdown
MemberAuthor

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%

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.

Note with an 8 entry reservoir the smallest value we'll ever see is 12.5%.

@EgorBoEgorBoMay 31, 2023

Copy link
Copy Markdown
MemberAuthor

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?

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.

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.

Copy link
Copy Markdown
MemberAuthor

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();

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.

I don't understand this change

@EgorBoEgorBoMay 31, 2023

Copy link
Copy Markdown
MemberAuthor

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

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.

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.

Copy link
Copy Markdown
MemberAuthor

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();

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.

Ditto here

if ((inlineInfo != nullptr) && (inlineInfo->exactContextHnd != nullptr))
{
printf(" (exactContextHnd=0x%p)", dspPtr(inlineInfo->exactContextHnd));
}

Copy link
Copy Markdown
MemberAuthor

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.

@EgorBo

Copy link
Copy Markdown
MemberAuthor

@EgorBo
EgorBo merged commit 89c96dc into dotnet:mainJun 1, 2023
@EgorBo
EgorBo deleted the multiple-gdv-jit branch June 1, 2023 22:36
@ghostghost locked as resolved and limited conversation to collaborators Jul 2, 2023
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 SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@EgorBo@AndyAyersMS
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Devirtualization with multiple guesses: JIT by EgorBo · Pull Request #86809 · dotnet/runtime · GitHub
Skip to content

Devirtualization with multiple guesses: JIT - #86809

Merged
EgorBo merged 11 commits into
dotnet:mainfrom
EgorBo:multiple-gdv-jit
Jun 1, 2023
Merged

Devirtualization with multiple guesses: JIT#86809
EgorBo merged 11 commits into
dotnet:mainfrom
EgorBo:multiple-gdv-jit

Conversation

@EgorBo

@EgorBoEgorBo commented May 26, 2023

Copy link
Copy Markdown
Member

Contributes to #86769

This PR enables GDV with multiple candidates for JIT. (#86551 did the initial infra and enabled that for NativeAOT)
Example:

publicinterfaceIValue{intGetValue();}publicclassMyClass1:IValue{publicintGetValue()=>10;}publicclassMyClass2:IValue{publicintGetValue()=>50;}publicclassMyClass3:IValue{publicintGetValue()=>100;}publicclassProgram{publicstaticvoidMain(string[]args){for(inti=0;i<200;i++){Test(newMyClass1());// MyClass1 has the highest likelihoodTest(newMyClass1());Test(newMyClass2());Test(newMyClass3());Thread.Sleep(16);}}[MethodImpl(MethodImplOptions.NoInlining)]staticintTest(IValuevalue)=>value.GetValue();}

JIT's codegen for Test:

; Assembly listing for method Program:Test(IValue):intsubrsp,40movrax,0x7FFBDC9C2000cmp qword ptr [rcx],rax ;; is it MyClass1 (50%) ?jne SHORT G_M7592_IG04moveax,10jmp SHORT G_M7592_IG07G_M7592_IG04:movrax,0x7FFBDC9C21C8cmp qword ptr [rcx],rax ;; is it MyClass2 (25%) ?jne SHORT G_M7592_IG06moveax,50jmp SHORT G_M7592_IG07G_M7592_IG06:movrax,0x7FFBDC9C2390cmp qword ptr [rcx],rax ;; is it MyClass3 (25%) ?jne SHORT G_M7592_IG08moveax,100G_M7592_IG07:addrsp,40retG_M7592_IG08:movr11,0x7FFBDC0C0048 ;; cold virtual fallback (0%)call[r11]IValue:GetValue():int:thisjmp SHORT G_M7592_IG07; Total bytes of code 88

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

@EgorBo
EgorBo marked this pull request as draft May 26, 2023 19:03
@ghostghost added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label May 26, 2023
@ghostghost assigned EgorBoMay 26, 2023
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

Issue Details

This PR enables GDV with multiple candidates for JIT. (did the initial infra and enabled that for NativeAOT)
Example:

publicinterfaceIValue{intGetValue();}publicclassMyClass1:IValue{publicintGetValue()=>10;}publicclassMyClass2:IValue{publicintGetValue()=>50;}publicclassMyClass3:IValue{publicintGetValue()=>100;}publicclassProgram{publicstaticvoidMain(string[]args){for(inti=0;i<200;i++){Test(newMyClass1());Test(newMyClass1());Test(newMyClass2());Test(newMyClass3());Thread.Sleep(16);}}[MethodImpl(MethodImplOptions.NoInlining)]staticintTest(IValuevalue)=>value.GetValue();}

JIT's codegen for Test:

; Assembly listing for method Program:Test(IValue):intsubrsp,40movr11, qword ptr [rcx]movrax,0x7FFBC9FE9C28 ;; is it MyClass1?cmpr11,raxjne SHORT G_M7592_IG04moveax,10jmp SHORT G_M7592_IG09G_M7592_IG04: movrax,0x7FFBC9FE9DF0 ;; is it MyClass2?cmpr11,raxjne SHORT G_M7592_IG06moveax,50jmp SHORT G_M7592_IG09G_M7592_IG06: movrax,0x7FFBC9FE9FB8 ;; is it MyClass3?cmpr11,raxjne SHORT G_M7592_IG08moveax,100jmp SHORT G_M7592_IG09G_M7592_IG08: movr11,0x7FFBC9400310 ;; virtual fallbackcall[r11]IValue:GetValue():int:thisG_M7592_IG09: nopaddrsp,40ret; Total bytes of code 92

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

Author:EgorBo
Assignees:EgorBo
Labels:

area-CodeGen-coreclr

Milestone:-

@EgorBoEgorBo mentioned this pull request May 26, 2023
14 tasks
@EgorBo

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-coreclr pgo, runtime-coreclr pgostress, runtime-coreclr libraries-pgo

@azure-pipelines

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

@EgorBo

Copy link
Copy Markdown
MemberAuthor

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

@azure-pipelines

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

@EgorBo
EgorBo marked this pull request as ready for review May 26, 2023 21:26
@EgorBo

Copy link
Copy Markdown
MemberAuthor

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

@azure-pipelines

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

@EgorBo

EgorBo commented May 27, 2023

Copy link
Copy Markdown
MemberAuthor

@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.

@EgorBo

Copy link
Copy Markdown
MemberAuthor

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

@azure-pipelines

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

@EgorBo

EgorBo commented May 28, 2023

Copy link
Copy Markdown
MemberAuthor

Stats for the minimal-api (TodosApp) NativeAOT + Exact classes:
829 calls are expanded with 2 or 3 type checks
+80.5Kb size regression (+0.3% size increase) - doesn't look bad for 26.6Mb app

@EgorBo

Copy link
Copy Markdown
MemberAuthor

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%

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.

Note with an 8 entry reservoir the smallest value we'll ever see is 12.5%.

@EgorBoEgorBoMay 31, 2023

Copy link
Copy Markdown
MemberAuthor

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?

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.

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.

Copy link
Copy Markdown
MemberAuthor

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();

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.

I don't understand this change

@EgorBoEgorBoMay 31, 2023

Copy link
Copy Markdown
MemberAuthor

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

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.

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.

Copy link
Copy Markdown
MemberAuthor

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();

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.

Ditto here

if ((inlineInfo != nullptr) && (inlineInfo->exactContextHnd != nullptr))
{
printf(" (exactContextHnd=0x%p)", dspPtr(inlineInfo->exactContextHnd));
}

Copy link
Copy Markdown
MemberAuthor

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.

@EgorBo

Copy link
Copy Markdown
MemberAuthor

@EgorBo
EgorBo merged commit 89c96dc into dotnet:mainJun 1, 2023
@EgorBo
EgorBo deleted the multiple-gdv-jit branch June 1, 2023 22:36
@ghostghost locked as resolved and limited conversation to collaborators Jul 2, 2023
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 SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@EgorBo@AndyAyersMS
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Devirtualization with multiple guesses: JIT by EgorBo · Pull Request #86809 · dotnet/runtime · GitHub
Skip to content

Devirtualization with multiple guesses: JIT - #86809

Merged
EgorBo merged 11 commits into
dotnet:mainfrom
EgorBo:multiple-gdv-jit
Jun 1, 2023
Merged

Devirtualization with multiple guesses: JIT#86809
EgorBo merged 11 commits into
dotnet:mainfrom
EgorBo:multiple-gdv-jit

Conversation

@EgorBo

@EgorBoEgorBo commented May 26, 2023

Copy link
Copy Markdown
Member

Contributes to #86769

This PR enables GDV with multiple candidates for JIT. (#86551 did the initial infra and enabled that for NativeAOT)
Example:

publicinterfaceIValue{intGetValue();}publicclassMyClass1:IValue{publicintGetValue()=>10;}publicclassMyClass2:IValue{publicintGetValue()=>50;}publicclassMyClass3:IValue{publicintGetValue()=>100;}publicclassProgram{publicstaticvoidMain(string[]args){for(inti=0;i<200;i++){Test(newMyClass1());// MyClass1 has the highest likelihoodTest(newMyClass1());Test(newMyClass2());Test(newMyClass3());Thread.Sleep(16);}}[MethodImpl(MethodImplOptions.NoInlining)]staticintTest(IValuevalue)=>value.GetValue();}

JIT's codegen for Test:

; Assembly listing for method Program:Test(IValue):intsubrsp,40movrax,0x7FFBDC9C2000cmp qword ptr [rcx],rax ;; is it MyClass1 (50%) ?jne SHORT G_M7592_IG04moveax,10jmp SHORT G_M7592_IG07G_M7592_IG04:movrax,0x7FFBDC9C21C8cmp qword ptr [rcx],rax ;; is it MyClass2 (25%) ?jne SHORT G_M7592_IG06moveax,50jmp SHORT G_M7592_IG07G_M7592_IG06:movrax,0x7FFBDC9C2390cmp qword ptr [rcx],rax ;; is it MyClass3 (25%) ?jne SHORT G_M7592_IG08moveax,100G_M7592_IG07:addrsp,40retG_M7592_IG08:movr11,0x7FFBDC0C0048 ;; cold virtual fallback (0%)call[r11]IValue:GetValue():int:thisjmp SHORT G_M7592_IG07; Total bytes of code 88

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

@EgorBo
EgorBo marked this pull request as draft May 26, 2023 19:03
@ghostghost added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label May 26, 2023
@ghostghost assigned EgorBoMay 26, 2023
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

Issue Details

This PR enables GDV with multiple candidates for JIT. (did the initial infra and enabled that for NativeAOT)
Example:

publicinterfaceIValue{intGetValue();}publicclassMyClass1:IValue{publicintGetValue()=>10;}publicclassMyClass2:IValue{publicintGetValue()=>50;}publicclassMyClass3:IValue{publicintGetValue()=>100;}publicclassProgram{publicstaticvoidMain(string[]args){for(inti=0;i<200;i++){Test(newMyClass1());Test(newMyClass1());Test(newMyClass2());Test(newMyClass3());Thread.Sleep(16);}}[MethodImpl(MethodImplOptions.NoInlining)]staticintTest(IValuevalue)=>value.GetValue();}

JIT's codegen for Test:

; Assembly listing for method Program:Test(IValue):intsubrsp,40movr11, qword ptr [rcx]movrax,0x7FFBC9FE9C28 ;; is it MyClass1?cmpr11,raxjne SHORT G_M7592_IG04moveax,10jmp SHORT G_M7592_IG09G_M7592_IG04: movrax,0x7FFBC9FE9DF0 ;; is it MyClass2?cmpr11,raxjne SHORT G_M7592_IG06moveax,50jmp SHORT G_M7592_IG09G_M7592_IG06: movrax,0x7FFBC9FE9FB8 ;; is it MyClass3?cmpr11,raxjne SHORT G_M7592_IG08moveax,100jmp SHORT G_M7592_IG09G_M7592_IG08: movr11,0x7FFBC9400310 ;; virtual fallbackcall[r11]IValue:GetValue():int:thisG_M7592_IG09: nopaddrsp,40ret; Total bytes of code 92

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

Author:EgorBo
Assignees:EgorBo
Labels:

area-CodeGen-coreclr

Milestone:-

@EgorBoEgorBo mentioned this pull request May 26, 2023
14 tasks
@EgorBo

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-coreclr pgo, runtime-coreclr pgostress, runtime-coreclr libraries-pgo

@azure-pipelines

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

@EgorBo

Copy link
Copy Markdown
MemberAuthor

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

@azure-pipelines

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

@EgorBo
EgorBo marked this pull request as ready for review May 26, 2023 21:26
@EgorBo

Copy link
Copy Markdown
MemberAuthor

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

@azure-pipelines

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

@EgorBo

EgorBo commented May 27, 2023

Copy link
Copy Markdown
MemberAuthor

@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.

@EgorBo

Copy link
Copy Markdown
MemberAuthor

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

@azure-pipelines

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

@EgorBo

EgorBo commented May 28, 2023

Copy link
Copy Markdown
MemberAuthor

Stats for the minimal-api (TodosApp) NativeAOT + Exact classes:
829 calls are expanded with 2 or 3 type checks
+80.5Kb size regression (+0.3% size increase) - doesn't look bad for 26.6Mb app

@EgorBo

Copy link
Copy Markdown
MemberAuthor

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%

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.

Note with an 8 entry reservoir the smallest value we'll ever see is 12.5%.

@EgorBoEgorBoMay 31, 2023

Copy link
Copy Markdown
MemberAuthor

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?

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.

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.

Copy link
Copy Markdown
MemberAuthor

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();

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.

I don't understand this change

@EgorBoEgorBoMay 31, 2023

Copy link
Copy Markdown
MemberAuthor

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

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.

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.

Copy link
Copy Markdown
MemberAuthor

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();

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.

Ditto here

if ((inlineInfo != nullptr) && (inlineInfo->exactContextHnd != nullptr))
{
printf(" (exactContextHnd=0x%p)", dspPtr(inlineInfo->exactContextHnd));
}

Copy link
Copy Markdown
MemberAuthor

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.

@EgorBo

Copy link
Copy Markdown
MemberAuthor

@EgorBo
EgorBo merged commit 89c96dc into dotnet:mainJun 1, 2023
@EgorBo
EgorBo deleted the multiple-gdv-jit branch June 1, 2023 22:36
@ghostghost locked as resolved and limited conversation to collaborators Jul 2, 2023
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 SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@EgorBo@AndyAyersMS
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Devirtualization with multiple guesses: JIT by EgorBo · Pull Request #86809 · dotnet/runtime · GitHub
Skip to content

Devirtualization with multiple guesses: JIT - #86809

Merged
EgorBo merged 11 commits into
dotnet:mainfrom
EgorBo:multiple-gdv-jit
Jun 1, 2023
Merged

Devirtualization with multiple guesses: JIT#86809
EgorBo merged 11 commits into
dotnet:mainfrom
EgorBo:multiple-gdv-jit

Conversation

@EgorBo

@EgorBoEgorBo commented May 26, 2023

Copy link
Copy Markdown
Member

Contributes to #86769

This PR enables GDV with multiple candidates for JIT. (#86551 did the initial infra and enabled that for NativeAOT)
Example:

publicinterfaceIValue{intGetValue();}publicclassMyClass1:IValue{publicintGetValue()=>10;}publicclassMyClass2:IValue{publicintGetValue()=>50;}publicclassMyClass3:IValue{publicintGetValue()=>100;}publicclassProgram{publicstaticvoidMain(string[]args){for(inti=0;i<200;i++){Test(newMyClass1());// MyClass1 has the highest likelihoodTest(newMyClass1());Test(newMyClass2());Test(newMyClass3());Thread.Sleep(16);}}[MethodImpl(MethodImplOptions.NoInlining)]staticintTest(IValuevalue)=>value.GetValue();}

JIT's codegen for Test:

; Assembly listing for method Program:Test(IValue):intsubrsp,40movrax,0x7FFBDC9C2000cmp qword ptr [rcx],rax ;; is it MyClass1 (50%) ?jne SHORT G_M7592_IG04moveax,10jmp SHORT G_M7592_IG07G_M7592_IG04:movrax,0x7FFBDC9C21C8cmp qword ptr [rcx],rax ;; is it MyClass2 (25%) ?jne SHORT G_M7592_IG06moveax,50jmp SHORT G_M7592_IG07G_M7592_IG06:movrax,0x7FFBDC9C2390cmp qword ptr [rcx],rax ;; is it MyClass3 (25%) ?jne SHORT G_M7592_IG08moveax,100G_M7592_IG07:addrsp,40retG_M7592_IG08:movr11,0x7FFBDC0C0048 ;; cold virtual fallback (0%)call[r11]IValue:GetValue():int:thisjmp SHORT G_M7592_IG07; Total bytes of code 88

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

@EgorBo
EgorBo marked this pull request as draft May 26, 2023 19:03
@ghostghost added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label May 26, 2023
@ghostghost assigned EgorBoMay 26, 2023
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

Issue Details

This PR enables GDV with multiple candidates for JIT. (did the initial infra and enabled that for NativeAOT)
Example:

publicinterfaceIValue{intGetValue();}publicclassMyClass1:IValue{publicintGetValue()=>10;}publicclassMyClass2:IValue{publicintGetValue()=>50;}publicclassMyClass3:IValue{publicintGetValue()=>100;}publicclassProgram{publicstaticvoidMain(string[]args){for(inti=0;i<200;i++){Test(newMyClass1());Test(newMyClass1());Test(newMyClass2());Test(newMyClass3());Thread.Sleep(16);}}[MethodImpl(MethodImplOptions.NoInlining)]staticintTest(IValuevalue)=>value.GetValue();}

JIT's codegen for Test:

; Assembly listing for method Program:Test(IValue):intsubrsp,40movr11, qword ptr [rcx]movrax,0x7FFBC9FE9C28 ;; is it MyClass1?cmpr11,raxjne SHORT G_M7592_IG04moveax,10jmp SHORT G_M7592_IG09G_M7592_IG04: movrax,0x7FFBC9FE9DF0 ;; is it MyClass2?cmpr11,raxjne SHORT G_M7592_IG06moveax,50jmp SHORT G_M7592_IG09G_M7592_IG06: movrax,0x7FFBC9FE9FB8 ;; is it MyClass3?cmpr11,raxjne SHORT G_M7592_IG08moveax,100jmp SHORT G_M7592_IG09G_M7592_IG08: movr11,0x7FFBC9400310 ;; virtual fallbackcall[r11]IValue:GetValue():int:thisG_M7592_IG09: nopaddrsp,40ret; Total bytes of code 92

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

Author:EgorBo
Assignees:EgorBo
Labels:

area-CodeGen-coreclr

Milestone:-

@EgorBoEgorBo mentioned this pull request May 26, 2023
14 tasks
@EgorBo

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-coreclr pgo, runtime-coreclr pgostress, runtime-coreclr libraries-pgo

@azure-pipelines

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

@EgorBo

Copy link
Copy Markdown
MemberAuthor

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

@azure-pipelines

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

@EgorBo
EgorBo marked this pull request as ready for review May 26, 2023 21:26
@EgorBo

Copy link
Copy Markdown
MemberAuthor

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

@azure-pipelines

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

@EgorBo

EgorBo commented May 27, 2023

Copy link
Copy Markdown
MemberAuthor

@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.

@EgorBo

Copy link
Copy Markdown
MemberAuthor

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

@azure-pipelines

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

@EgorBo

EgorBo commented May 28, 2023

Copy link
Copy Markdown
MemberAuthor

Stats for the minimal-api (TodosApp) NativeAOT + Exact classes:
829 calls are expanded with 2 or 3 type checks
+80.5Kb size regression (+0.3% size increase) - doesn't look bad for 26.6Mb app

@EgorBo

Copy link
Copy Markdown
MemberAuthor

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%

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.

Note with an 8 entry reservoir the smallest value we'll ever see is 12.5%.

@EgorBoEgorBoMay 31, 2023

Copy link
Copy Markdown
MemberAuthor

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?

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.

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.

Copy link
Copy Markdown
MemberAuthor

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();

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.

I don't understand this change

@EgorBoEgorBoMay 31, 2023

Copy link
Copy Markdown
MemberAuthor

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

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.

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.

Copy link
Copy Markdown
MemberAuthor

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();

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.

Ditto here

if ((inlineInfo != nullptr) && (inlineInfo->exactContextHnd != nullptr))
{
printf(" (exactContextHnd=0x%p)", dspPtr(inlineInfo->exactContextHnd));
}

Copy link
Copy Markdown
MemberAuthor

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.

@EgorBo

Copy link
Copy Markdown
MemberAuthor

@EgorBo
EgorBo merged commit 89c96dc into dotnet:mainJun 1, 2023
@EgorBo
EgorBo deleted the multiple-gdv-jit branch June 1, 2023 22:36
@ghostghost locked as resolved and limited conversation to collaborators Jul 2, 2023
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 SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@EgorBo@AndyAyersMS