') + ')', '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('^' + ".*" + ', '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" + ', '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('^' + ".*" + ', '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); } })(); })(); JIT: Produce less convoluted IR for boolean `isinst` checks by jakobbotsch · Pull Request #103391 · dotnet/runtime · GitHub
Skip to content

JIT: Produce less convoluted IR for boolean isinst checks - #103391

Merged
jakobbotsch merged 6 commits into
dotnet:mainfrom
jakobbotsch:boolean-isinst
Jun 18, 2024
Merged

JIT: Produce less convoluted IR for boolean isinst checks#103391
jakobbotsch merged 6 commits into
dotnet:mainfrom
jakobbotsch:boolean-isinst

Conversation

@jakobbotsch

@jakobbotschjakobbotsch commented Jun 13, 2024

Copy link
Copy Markdown
Member

Currently the IR for boolean isinst checks ends up being something like (typecheck(x) ? x : null) != null, which the JIT ends up having a hard time clean up early. With object stack allocation this pattern usually leads to unnecessary address exposure.

This adds a simple pattern match during import to produce less convoluted IR in the common cases where the isinst is just used as a boolean check.

Example from #36649:

[MethodImpl(MethodImplOptions.NoInlining)]publicstaticboolIs_Slow(objectobj)=>objisint;
 ; Method C:Is_Slow(System.Object):ubyte (FullOpts)
G_M38459_IG01: ;; offset=0x0000
;; size=0 bbWeight=1 PerfScore 0.00
G_M38459_IG02: ;; offset=0x0000
test rcx, rcx
je SHORT G_M38459_IG05
;; size=5 bbWeight=1 PerfScore 1.25
G_M38459_IG03: ;; offset=0x0005
mov rax, 0x7FFB89FA1C00 ; System.Int32
cmp qword ptr [rcx], rax
jne SHORT G_M38459_IG05
;; size=15 bbWeight=0.25 PerfScore 1.06
G_M38459_IG04: ;; offset=0x0014
+ mov eax, 1
jmp SHORT G_M38459_IG06
- ;; size=2 bbWeight=0.12 PerfScore 0.25+ ;; size=7 bbWeight=0.12 PerfScore 0.28-G_M38459_IG05: ;; offset=0x0016- xor rcx, rcx+G_M38459_IG05: ;; offset=0x001B+ xor eax, eax
;; size=2 bbWeight=0.25 PerfScore 0.06
-G_M38459_IG06: ;; offset=0x0018- test rcx, rcx- setne al- movzx rax, al- ;; size=9 bbWeight=1 PerfScore 1.50--G_M38459_IG07: ;; offset=0x0021+G_M38459_IG06: ;; offset=0x001D
ret ;; size=1 bbWeight=1 PerfScore 1.00
-; Total bytes of code: 34+; Total bytes of code: 30

Fix#36649

Currently the IR for boolean `isinst` checks ends up being something
like `(typecheck(x) ? x : null) != null`, which the JIT ends up having a
hard time clean up early. With object stack allocation this pattern
usually leads to unnecessary address exposure.
This adds a simple pattern match during import to produce less
convoluted IR in the common cases where the `isinst` is just used as a
boolean check.
@ghostghost added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jun 13, 2024
Comment threadsrc/coreclr/jit/importer.cpp
@EgorBo

Copy link
Copy Markdown
Member

Should close #36649

@jakobbotsch

jakobbotsch commented Jun 13, 2024

Copy link
Copy Markdown
MemberAuthor

Should close #36649

Looks like it, your Is_Slow example from there:

[MethodImpl(MethodImplOptions.NoInlining)]publicstaticboolIs_Slow(objectobj)=>objisint;

Base:

; Method C:Is_Slow(System.Object):ubyte (FullOpts)G_M38459_IG01: ;; offset=0x0000 ;; size=0 bbWeight=1 PerfScore 0.00G_M38459_IG02: ;; offset=0x0000testrcx,rcxje SHORT G_M38459_IG05 ;; size=5 bbWeight=1 PerfScore 1.25G_M38459_IG03: ;; offset=0x0005movrax,0x7FFB89FA1C00 ; System.Int32cmp qword ptr [rcx],raxjne SHORT G_M38459_IG05 ;; size=15 bbWeight=0.25 PerfScore 1.06G_M38459_IG04: ;; offset=0x0014jmp SHORT G_M38459_IG06 ;; size=2 bbWeight=0.12 PerfScore 0.25G_M38459_IG05: ;; offset=0x0016xorrcx,rcx ;; size=2 bbWeight=0.25 PerfScore 0.06G_M38459_IG06: ;; offset=0x0018testrcx,rcx setne almovzxrax,al ;; size=9 bbWeight=1 PerfScore 1.50G_M38459_IG07: ;; offset=0x0021ret ;; size=1 bbWeight=1 PerfScore 1.00; Total bytes of code: 34

Diff:

; Method C:Is_Slow(System.Object):ubyte (FullOpts)G_M38459_IG01: ;; offset=0x0000 ;; size=0 bbWeight=1 PerfScore 0.00G_M38459_IG02: ;; offset=0x0000testrcx,rcxje SHORT G_M38459_IG05 ;; size=5 bbWeight=1 PerfScore 1.25G_M38459_IG03: ;; offset=0x0005movrax,0x7FFB89FC1C00 ; System.Int32cmp qword ptr [rcx],raxjne SHORT G_M38459_IG05 ;; size=15 bbWeight=0.25 PerfScore 1.06G_M38459_IG04: ;; offset=0x0014moveax,1jmp SHORT G_M38459_IG06 ;; size=7 bbWeight=0.12 PerfScore 0.28G_M38459_IG05: ;; offset=0x001Bxoreax,eax ;; size=2 bbWeight=0.25 PerfScore 0.06G_M38459_IG06: ;; offset=0x001Dret ;; size=1 bbWeight=1 PerfScore 1.00; Total bytes of code: 30

@jakobbotsch

Copy link
Copy Markdown
MemberAuthor

/azp run runtime

@azure-pipelines

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

Comment threadsrc/coreclr/jit/importer.cpp Outdated
@jakobbotsch

jakobbotsch commented Jun 17, 2024

Copy link
Copy Markdown
MemberAuthor

Overall the IR created by this PR is not really any less convoluted than the existing IR. Instead of (x != null ? x.mt == expectedMT ? x : null : null) != null, we create (x != null ? x.mt == expectedMT ? 1 : 0 : 0) != 0.
I initially tried to create something like (x != null ? x.mt == expectedMT : 0) != 0, but that resulted in quite mixed diffs.
Even so, it still seems to help in some of the reported cases, and it will help for #103361 as well when x is stack allocated.

Of course it would be better if we could create the more natural control flow, but that's hard given the representation through QMARKs (when we are doing late expansion we create the more natural flow directly, but IIRC @EgorBo left the early expansion here in place because it is still more beneficial).

@EgorBo

Copy link
Copy Markdown
Member

but IIRC @EgorBo left the early expansion here in place because it is still more beneficial).

I initially hoped we'd enable JitOptRepeat and I could move the late-cast expansion to the JitOptRepeat loop and the 2nd iteration of it would clean most of the regressions I hit just fine 😞

@jakobbotsch
jakobbotsch marked this pull request as ready for review June 17, 2024 13:49
@jakobbotsch

Copy link
Copy Markdown
MemberAuthor

cc @dotnet/jit-contrib PTAL @EgorBo@AndyAyersMS

Diffs

Comment threadsrc/coreclr/jit/importer.cpp Outdated
if (*booleanCheck)
{
GenTreeOp* condMT = gtNewOperNode(GT_NE, TYP_INT, gtNewMethodTableLookup(op1Clone), op2);
GenTreeOp* condNull = gtNewOperNode(GT_EQ, TYP_INT, gtClone(op1), gtNewNull());

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.

nit: looks like these two branches have a lot of common code, e.g. condMT and condNull are the same.

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.

Deduplicated a bit of it

@EgorBoEgorBo left a comment

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.

LGTM, minor clean up comment, probably, not worth re-running CI for

@jakobbotsch
jakobbotsch merged commit b2ccd98 into dotnet:mainJun 18, 2024
@jakobbotsch
jakobbotsch deleted the boolean-isinst branch June 18, 2024 11:31
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 19, 2024
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.

Unoptimal codegen for "obj is T" with T being struct/sealed

2 participants

@jakobbotsch@EgorBo