JIT: Fix invalid IR and weights in if-conversion - #125072

Merged
jakobbotsch merged 11 commits into
dotnet:mainfrom
BoyBaykiller:fix-invalid-ir-if-conv-124942
Mar 11, 2026
Merged

JIT: Fix invalid IR and weights in if-conversion#125072
jakobbotsch merged 11 commits into
dotnet:mainfrom
BoyBaykiller:fix-invalid-ir-if-conv-124942

Conversation

@BoyBaykiller

Copy link
Copy Markdown
Contributor

Fix#124942.

Small diffs are expected because:

  1. It allows if-conversion to recognize more cases.
    For example https://godbolt.org/z/Pdj898qTT currently still produces one jmp. But with this change it's eliminated, as the search now knows when there actually is a return block (previously it wasn't marked as such).

  2. The block weights changed. For example previously

staticintInvalidIR(boolcond){if(cond){return2;}// or else {}return4;}

produced blocks with bbWeight = 0.5, now it's 1.0.

@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Mar 2, 2026
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Mar 2, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

@BoyBaykiller
BoyBaykiller marked this pull request as draft March 2, 2026 21:06

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes CFG/IR correctness issues in CoreCLR JIT if-conversion, especially for return-based if/else patterns, and adjusts how unreachable then/else blocks are treated after conversion so the transformed IR no longer has a RETURN block with a successor.

Changes:

  • Improve DEBUG dumping for if-conversion flows pre- and post-transformation.
  • Update if-conversion CFG rewriting to correctly produce BBJ_RETURN when converting return-based branches, and to detach then/else blocks from the start block.
  • Adjust weights for then/else blocks after their incoming flow is removed.

Comment threadsrc/coreclr/jit/ifconversion.cpp Outdated
Comment threadsrc/coreclr/jit/ifconversion.cpp Outdated
@jakobbotsch
jakobbotsch requested a review from a74nhMarch 4, 2026 16:15
Comment threadsrc/coreclr/jit/ifconversion.cpp Outdated
Comment threadsrc/coreclr/jit/ifconversion.cpp

@a74nha74nh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested this with some if/then, if/then/else, if/then/else/return cases. I'm happy the IR now looks correct afterwards.

* don't dump m_finalBlock

@a74nha74nh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given removing the BB blocks isn't required, I'm happy with this.

Comment threadsrc/coreclr/jit/ifconversion.cpp Outdated
Comment threadsrc/coreclr/jit/ifconversion.cpp
@BoyBaykiller

BoyBaykiller commented Mar 7, 2026

Copy link
Copy Markdown
ContributorAuthor

Now, that if-conversion no longer produces flow through empty blocks I think we can make the following assumptions:

assert(m_startBlock->GetFalseTarget()->GetUniqueSucc() == m_finalBlock);
if (m_doElseConversion)
{
assert(m_startBlock->GetTrueTarget()->GetUniqueSucc() == m_finalBlock);
}

I've run python .\src\coreclr\scripts\superpmi.py replay on this and haven't seen any failures. This would allow us to
simplify some parts, most noticeably FindFlow().

Previously this wasn't true. There could be empty blocks after m_{then/else}Operation.Block before reaching m_finalBlock. E.g System.Net.SocketAddress:GetMaximumAddressSize() or 3039 in aspnet2.run.windows.x64.checked.mch

Conditionally executing BB03 and BB05 inside BB02
------------ BB02 [0001] [004..008) -> BB05(0.5),BB03(0.5) (cond), preds={BB01} succs={BB03,BB05}
***** BB02 [0001]
STMT00003 ( 0x004[E--] ... 0x006 )
N004 ( 5, 5) [000011] -----+----- * JTRUE void $VN.Void
N003 ( 3, 3) [000010] J----+-N--- \--* EQ int $101
N001 ( 1, 1) [000008] -----+----- +--* LCL_VAR int V00 arg0 u:1 $80
N002 ( 1, 1) [000009] -----+----- \--* CNS_INT int 2 $44
------------ BB03 [0002] [008..00D) -> BB06(1) (always), preds={BB02} succs={BB06}
***** BB03 [0002]
STMT00005 ( 0x008[E--] ... 0x00B )
N001 ( 0, 0) [000017] -----+----- * NOP void ***** BB03 [0002]
STMT00007 ( 0x027[E--] ... 0x02C )
N007 ( 6, 9) [000021] DA---+----- * STORE_LCL_VAR int V01 loc0 d:4 $VN.Void
N006 ( 6, 9) [000028] ----------- \--* SELECT int N003 ( 3, 3) [000016] J----+-N--- +--* EQ int $102
N001 ( 1, 1) [000014] -----+----- | +--* LCL_VAR int V00 arg0 u:1 (last use) $80
N002 ( 1, 1) [000015] -----+----- | \--* CNS_INT int 23 $46
N004 ( 1, 1) [000018] -----+----- +--* CNS_INT int 28 $47
N005 ( 1, 4) [000020] -----+----- \--* CNS_INT int 128 $48
***** BB03 [0002]
STMT00006 ( 0x017[E--] ... 0x01C )
N001 ( 0, 0) [000019] -----+----- * NOP void ------------ BB06 [0005] [017..01F) -> BB09(1) (always), preds={BB03} succs={BB09}
------------ BB05 [0004] [00F..017) -> BB09(1) (always), preds={BB02} succs={BB09}
***** BB05 [0004]
STMT00004 ( 0x00F[E--] ... 0x014 )
N002 ( 1, 3) [000013] DA---+----- * STORE_LCL_VAR int V01 loc0 d:2 $VN.Void
N001 ( 1, 1) [000012] -----+----- \--* CNS_INT int 16 $45

Of course if previous phases produce empty blocks inside Then/Else then we'd miss cases, but at least currently this doesn't seem to happen. I want to simplify the code using these assumptions in a follow-up PR.

@BoyBaykiller

Copy link
Copy Markdown
ContributorAuthor

I've applied jakobs suggestion to remove all Then/Else blocks.

I also got rid of the the NOP after the SELECT:

After if conversion
\------------ BB01 [0000] [000..005) -> BB04(1) (always), preds={} succs={BB04}
***** BB01 [0000]
STMT00001 ( 0x002[E--] ... 0x003 )
N001 ( 0, 0) [000005] -----+----- * NOP void ***** BB01 [0000]
STMT00005 ( 0x005[E--] ... 0x006 )
N008 ( 12, 15) [000012] DA---+----- * STORE_LCL_VAR int V01 loc0 d:3 $VN.Void
N007 ( 8, 12) [000018] ----------- \--* SELECT int N004 ( 5, 7) [000004] J----+-N--- +--* EQ int $101
N002 ( 3, 4) [000013] -----+----- | +--* CAST int <- ubyte <- int $100
N001 ( 2, 2) [000002] -----+----- | | \--* LCL_VAR int V00 arg0 u:1 (last use) $80
N003 ( 1, 2) [000003] -----+----- | \--* CNS_INT int 0 $40
N005 ( 1, 2) [000006] -----+----- +--* CNS_INT int 9 $43
N006 ( 1, 2) [000011] -----+----- \--* CNS_INT int 2 $44
- ***** BB01 [0000]- STMT00002 ( 0x009[E--] ... 0x00B )- N001 ( 0, 0) [000007] -----+----- * NOP void 

This is simply done by no longer appending the Else block. That means IfConvertJoinStmts is only called once. And since we really only want to append m_thenOperation.stmt and not the entire block (it might have NOPs), I've decided to remove that function and append m_thenOperation.stmt inline.

@BoyBaykiller

Copy link
Copy Markdown
ContributorAuthor

I now replace the JTRUE statement with the USE(SELECT(...)) instead of appending it and bashing JTRUE to NOP. This means all NOPs are gone. Ready for an other review.

@a74nh

a74nh commented Mar 9, 2026

Copy link
Copy Markdown
Contributor

With the latest version, the IR dump on my test cases look much better. Everything that was optimised becomes a single block with no junk lying around. Plus the removal of the statements and basicblocks automatically get lines added in the dump.

@jakobbotschjakobbotsch 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! Thanks for working on this.

@jakobbotsch

Copy link
Copy Markdown
Member

/ba-g Timeouts, android/tvOS failures and 404 superpmi-diffs failure

@jakobbotsch
jakobbotsch merged commit 6b49b5c into dotnet:mainMar 11, 2026
124 of 131 checks passed
CopilotAI pushed a commit that referenced this pull request Mar 13, 2026
Fix#124942.
Small diffs are expected because:
1. It allows if-conversion to recognize more cases.
For example https://godbolt.org/z/Pdj898qTT currently still produces one
jmp. But with this change it's eliminated, as the search now knows when
there actually is a return block (previously it wasn't marked as such).
2. The block weights changed. For example previously
```cs
static int InvalidIR(bool cond)
{
if (cond)
{
return 2;
}
// or else {}
return 4;
}
```
produced blocks with `bbWeight = 0.5`, now it's 1.0.
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Apr 11, 2026
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 SuperPMIcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JIT: If-conversion phase can produce questionable IR. Return block having a successor

4 participants

@BoyBaykiller@a74nh@jakobbotsch
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n 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;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

JIT: Fix invalid IR and weights in if-conversion - #125072

Merged
jakobbotsch merged 11 commits into
dotnet:mainfrom
BoyBaykiller:fix-invalid-ir-if-conv-124942
Mar 11, 2026
Merged

JIT: Fix invalid IR and weights in if-conversion#125072
jakobbotsch merged 11 commits into
dotnet:mainfrom
BoyBaykiller:fix-invalid-ir-if-conv-124942

Conversation

@BoyBaykiller

Copy link
Copy Markdown
Contributor

Fix#124942.

Small diffs are expected because:

  1. It allows if-conversion to recognize more cases.
    For example https://godbolt.org/z/Pdj898qTT currently still produces one jmp. But with this change it's eliminated, as the search now knows when there actually is a return block (previously it wasn't marked as such).

  2. The block weights changed. For example previously

staticintInvalidIR(boolcond){if(cond){return2;}// or else {}return4;}

produced blocks with bbWeight = 0.5, now it's 1.0.

@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Mar 2, 2026
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Mar 2, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

@BoyBaykiller
BoyBaykiller marked this pull request as draft March 2, 2026 21:06

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes CFG/IR correctness issues in CoreCLR JIT if-conversion, especially for return-based if/else patterns, and adjusts how unreachable then/else blocks are treated after conversion so the transformed IR no longer has a RETURN block with a successor.

Changes:

  • Improve DEBUG dumping for if-conversion flows pre- and post-transformation.
  • Update if-conversion CFG rewriting to correctly produce BBJ_RETURN when converting return-based branches, and to detach then/else blocks from the start block.
  • Adjust weights for then/else blocks after their incoming flow is removed.

Comment threadsrc/coreclr/jit/ifconversion.cpp Outdated
Comment threadsrc/coreclr/jit/ifconversion.cpp Outdated
@jakobbotsch
jakobbotsch requested a review from a74nhMarch 4, 2026 16:15
Comment threadsrc/coreclr/jit/ifconversion.cpp Outdated
Comment threadsrc/coreclr/jit/ifconversion.cpp

@a74nha74nh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested this with some if/then, if/then/else, if/then/else/return cases. I'm happy the IR now looks correct afterwards.

* don't dump m_finalBlock

@a74nha74nh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given removing the BB blocks isn't required, I'm happy with this.

Comment threadsrc/coreclr/jit/ifconversion.cpp Outdated
Comment threadsrc/coreclr/jit/ifconversion.cpp
@BoyBaykiller

BoyBaykiller commented Mar 7, 2026

Copy link
Copy Markdown
ContributorAuthor

Now, that if-conversion no longer produces flow through empty blocks I think we can make the following assumptions:

assert(m_startBlock->GetFalseTarget()->GetUniqueSucc() == m_finalBlock);
if (m_doElseConversion)
{
assert(m_startBlock->GetTrueTarget()->GetUniqueSucc() == m_finalBlock);
}

I've run python .\src\coreclr\scripts\superpmi.py replay on this and haven't seen any failures. This would allow us to
simplify some parts, most noticeably FindFlow().

Previously this wasn't true. There could be empty blocks after m_{then/else}Operation.Block before reaching m_finalBlock. E.g System.Net.SocketAddress:GetMaximumAddressSize() or 3039 in aspnet2.run.windows.x64.checked.mch

Conditionally executing BB03 and BB05 inside BB02
------------ BB02 [0001] [004..008) -> BB05(0.5),BB03(0.5) (cond), preds={BB01} succs={BB03,BB05}
***** BB02 [0001]
STMT00003 ( 0x004[E--] ... 0x006 )
N004 ( 5, 5) [000011] -----+----- * JTRUE void $VN.Void
N003 ( 3, 3) [000010] J----+-N--- \--* EQ int $101
N001 ( 1, 1) [000008] -----+----- +--* LCL_VAR int V00 arg0 u:1 $80
N002 ( 1, 1) [000009] -----+----- \--* CNS_INT int 2 $44
------------ BB03 [0002] [008..00D) -> BB06(1) (always), preds={BB02} succs={BB06}
***** BB03 [0002]
STMT00005 ( 0x008[E--] ... 0x00B )
N001 ( 0, 0) [000017] -----+----- * NOP void ***** BB03 [0002]
STMT00007 ( 0x027[E--] ... 0x02C )
N007 ( 6, 9) [000021] DA---+----- * STORE_LCL_VAR int V01 loc0 d:4 $VN.Void
N006 ( 6, 9) [000028] ----------- \--* SELECT int N003 ( 3, 3) [000016] J----+-N--- +--* EQ int $102
N001 ( 1, 1) [000014] -----+----- | +--* LCL_VAR int V00 arg0 u:1 (last use) $80
N002 ( 1, 1) [000015] -----+----- | \--* CNS_INT int 23 $46
N004 ( 1, 1) [000018] -----+----- +--* CNS_INT int 28 $47
N005 ( 1, 4) [000020] -----+----- \--* CNS_INT int 128 $48
***** BB03 [0002]
STMT00006 ( 0x017[E--] ... 0x01C )
N001 ( 0, 0) [000019] -----+----- * NOP void ------------ BB06 [0005] [017..01F) -> BB09(1) (always), preds={BB03} succs={BB09}
------------ BB05 [0004] [00F..017) -> BB09(1) (always), preds={BB02} succs={BB09}
***** BB05 [0004]
STMT00004 ( 0x00F[E--] ... 0x014 )
N002 ( 1, 3) [000013] DA---+----- * STORE_LCL_VAR int V01 loc0 d:2 $VN.Void
N001 ( 1, 1) [000012] -----+----- \--* CNS_INT int 16 $45

Of course if previous phases produce empty blocks inside Then/Else then we'd miss cases, but at least currently this doesn't seem to happen. I want to simplify the code using these assumptions in a follow-up PR.

@BoyBaykiller

Copy link
Copy Markdown
ContributorAuthor

I've applied jakobs suggestion to remove all Then/Else blocks.

I also got rid of the the NOP after the SELECT:

After if conversion
\------------ BB01 [0000] [000..005) -> BB04(1) (always), preds={} succs={BB04}
***** BB01 [0000]
STMT00001 ( 0x002[E--] ... 0x003 )
N001 ( 0, 0) [000005] -----+----- * NOP void ***** BB01 [0000]
STMT00005 ( 0x005[E--] ... 0x006 )
N008 ( 12, 15) [000012] DA---+----- * STORE_LCL_VAR int V01 loc0 d:3 $VN.Void
N007 ( 8, 12) [000018] ----------- \--* SELECT int N004 ( 5, 7) [000004] J----+-N--- +--* EQ int $101
N002 ( 3, 4) [000013] -----+----- | +--* CAST int <- ubyte <- int $100
N001 ( 2, 2) [000002] -----+----- | | \--* LCL_VAR int V00 arg0 u:1 (last use) $80
N003 ( 1, 2) [000003] -----+----- | \--* CNS_INT int 0 $40
N005 ( 1, 2) [000006] -----+----- +--* CNS_INT int 9 $43
N006 ( 1, 2) [000011] -----+----- \--* CNS_INT int 2 $44
- ***** BB01 [0000]- STMT00002 ( 0x009[E--] ... 0x00B )- N001 ( 0, 0) [000007] -----+----- * NOP void 

This is simply done by no longer appending the Else block. That means IfConvertJoinStmts is only called once. And since we really only want to append m_thenOperation.stmt and not the entire block (it might have NOPs), I've decided to remove that function and append m_thenOperation.stmt inline.

@BoyBaykiller

Copy link
Copy Markdown
ContributorAuthor

I now replace the JTRUE statement with the USE(SELECT(...)) instead of appending it and bashing JTRUE to NOP. This means all NOPs are gone. Ready for an other review.

@a74nh

a74nh commented Mar 9, 2026

Copy link
Copy Markdown
Contributor

With the latest version, the IR dump on my test cases look much better. Everything that was optimised becomes a single block with no junk lying around. Plus the removal of the statements and basicblocks automatically get lines added in the dump.

@jakobbotschjakobbotsch 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! Thanks for working on this.

@jakobbotsch

Copy link
Copy Markdown
Member

/ba-g Timeouts, android/tvOS failures and 404 superpmi-diffs failure

@jakobbotsch
jakobbotsch merged commit 6b49b5c into dotnet:mainMar 11, 2026
124 of 131 checks passed
CopilotAI pushed a commit that referenced this pull request Mar 13, 2026
Fix#124942.
Small diffs are expected because:
1. It allows if-conversion to recognize more cases.
For example https://godbolt.org/z/Pdj898qTT currently still produces one
jmp. But with this change it's eliminated, as the search now knows when
there actually is a return block (previously it wasn't marked as such).
2. The block weights changed. For example previously
```cs
static int InvalidIR(bool cond)
{
if (cond)
{
return 2;
}
// or else {}
return 4;
}
```
produced blocks with `bbWeight = 0.5`, now it's 1.0.
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Apr 11, 2026
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 SuperPMIcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JIT: If-conversion phase can produce questionable IR. Return block having a successor

4 participants

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

JIT: Fix invalid IR and weights in if-conversion - #125072

Merged
jakobbotsch merged 11 commits into
dotnet:mainfrom
BoyBaykiller:fix-invalid-ir-if-conv-124942
Mar 11, 2026
Merged

JIT: Fix invalid IR and weights in if-conversion#125072
jakobbotsch merged 11 commits into
dotnet:mainfrom
BoyBaykiller:fix-invalid-ir-if-conv-124942

Conversation

@BoyBaykiller

Copy link
Copy Markdown
Contributor

Fix#124942.

Small diffs are expected because:

  1. It allows if-conversion to recognize more cases.
    For example https://godbolt.org/z/Pdj898qTT currently still produces one jmp. But with this change it's eliminated, as the search now knows when there actually is a return block (previously it wasn't marked as such).

  2. The block weights changed. For example previously

staticintInvalidIR(boolcond){if(cond){return2;}// or else {}return4;}

produced blocks with bbWeight = 0.5, now it's 1.0.

@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Mar 2, 2026
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Mar 2, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

@BoyBaykiller
BoyBaykiller marked this pull request as draft March 2, 2026 21:06

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes CFG/IR correctness issues in CoreCLR JIT if-conversion, especially for return-based if/else patterns, and adjusts how unreachable then/else blocks are treated after conversion so the transformed IR no longer has a RETURN block with a successor.

Changes:

  • Improve DEBUG dumping for if-conversion flows pre- and post-transformation.
  • Update if-conversion CFG rewriting to correctly produce BBJ_RETURN when converting return-based branches, and to detach then/else blocks from the start block.
  • Adjust weights for then/else blocks after their incoming flow is removed.

Comment threadsrc/coreclr/jit/ifconversion.cpp Outdated
Comment threadsrc/coreclr/jit/ifconversion.cpp Outdated
@jakobbotsch
jakobbotsch requested a review from a74nhMarch 4, 2026 16:15
Comment threadsrc/coreclr/jit/ifconversion.cpp Outdated
Comment threadsrc/coreclr/jit/ifconversion.cpp

@a74nha74nh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested this with some if/then, if/then/else, if/then/else/return cases. I'm happy the IR now looks correct afterwards.

* don't dump m_finalBlock

@a74nha74nh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given removing the BB blocks isn't required, I'm happy with this.

Comment threadsrc/coreclr/jit/ifconversion.cpp Outdated
Comment threadsrc/coreclr/jit/ifconversion.cpp
@BoyBaykiller

BoyBaykiller commented Mar 7, 2026

Copy link
Copy Markdown
ContributorAuthor

Now, that if-conversion no longer produces flow through empty blocks I think we can make the following assumptions:

assert(m_startBlock->GetFalseTarget()->GetUniqueSucc() == m_finalBlock);
if (m_doElseConversion)
{
assert(m_startBlock->GetTrueTarget()->GetUniqueSucc() == m_finalBlock);
}

I've run python .\src\coreclr\scripts\superpmi.py replay on this and haven't seen any failures. This would allow us to
simplify some parts, most noticeably FindFlow().

Previously this wasn't true. There could be empty blocks after m_{then/else}Operation.Block before reaching m_finalBlock. E.g System.Net.SocketAddress:GetMaximumAddressSize() or 3039 in aspnet2.run.windows.x64.checked.mch

Conditionally executing BB03 and BB05 inside BB02
------------ BB02 [0001] [004..008) -> BB05(0.5),BB03(0.5) (cond), preds={BB01} succs={BB03,BB05}
***** BB02 [0001]
STMT00003 ( 0x004[E--] ... 0x006 )
N004 ( 5, 5) [000011] -----+----- * JTRUE void $VN.Void
N003 ( 3, 3) [000010] J----+-N--- \--* EQ int $101
N001 ( 1, 1) [000008] -----+----- +--* LCL_VAR int V00 arg0 u:1 $80
N002 ( 1, 1) [000009] -----+----- \--* CNS_INT int 2 $44
------------ BB03 [0002] [008..00D) -> BB06(1) (always), preds={BB02} succs={BB06}
***** BB03 [0002]
STMT00005 ( 0x008[E--] ... 0x00B )
N001 ( 0, 0) [000017] -----+----- * NOP void ***** BB03 [0002]
STMT00007 ( 0x027[E--] ... 0x02C )
N007 ( 6, 9) [000021] DA---+----- * STORE_LCL_VAR int V01 loc0 d:4 $VN.Void
N006 ( 6, 9) [000028] ----------- \--* SELECT int N003 ( 3, 3) [000016] J----+-N--- +--* EQ int $102
N001 ( 1, 1) [000014] -----+----- | +--* LCL_VAR int V00 arg0 u:1 (last use) $80
N002 ( 1, 1) [000015] -----+----- | \--* CNS_INT int 23 $46
N004 ( 1, 1) [000018] -----+----- +--* CNS_INT int 28 $47
N005 ( 1, 4) [000020] -----+----- \--* CNS_INT int 128 $48
***** BB03 [0002]
STMT00006 ( 0x017[E--] ... 0x01C )
N001 ( 0, 0) [000019] -----+----- * NOP void ------------ BB06 [0005] [017..01F) -> BB09(1) (always), preds={BB03} succs={BB09}
------------ BB05 [0004] [00F..017) -> BB09(1) (always), preds={BB02} succs={BB09}
***** BB05 [0004]
STMT00004 ( 0x00F[E--] ... 0x014 )
N002 ( 1, 3) [000013] DA---+----- * STORE_LCL_VAR int V01 loc0 d:2 $VN.Void
N001 ( 1, 1) [000012] -----+----- \--* CNS_INT int 16 $45

Of course if previous phases produce empty blocks inside Then/Else then we'd miss cases, but at least currently this doesn't seem to happen. I want to simplify the code using these assumptions in a follow-up PR.

@BoyBaykiller

Copy link
Copy Markdown
ContributorAuthor

I've applied jakobs suggestion to remove all Then/Else blocks.

I also got rid of the the NOP after the SELECT:

After if conversion
\------------ BB01 [0000] [000..005) -> BB04(1) (always), preds={} succs={BB04}
***** BB01 [0000]
STMT00001 ( 0x002[E--] ... 0x003 )
N001 ( 0, 0) [000005] -----+----- * NOP void ***** BB01 [0000]
STMT00005 ( 0x005[E--] ... 0x006 )
N008 ( 12, 15) [000012] DA---+----- * STORE_LCL_VAR int V01 loc0 d:3 $VN.Void
N007 ( 8, 12) [000018] ----------- \--* SELECT int N004 ( 5, 7) [000004] J----+-N--- +--* EQ int $101
N002 ( 3, 4) [000013] -----+----- | +--* CAST int <- ubyte <- int $100
N001 ( 2, 2) [000002] -----+----- | | \--* LCL_VAR int V00 arg0 u:1 (last use) $80
N003 ( 1, 2) [000003] -----+----- | \--* CNS_INT int 0 $40
N005 ( 1, 2) [000006] -----+----- +--* CNS_INT int 9 $43
N006 ( 1, 2) [000011] -----+----- \--* CNS_INT int 2 $44
- ***** BB01 [0000]- STMT00002 ( 0x009[E--] ... 0x00B )- N001 ( 0, 0) [000007] -----+----- * NOP void 

This is simply done by no longer appending the Else block. That means IfConvertJoinStmts is only called once. And since we really only want to append m_thenOperation.stmt and not the entire block (it might have NOPs), I've decided to remove that function and append m_thenOperation.stmt inline.

@BoyBaykiller

Copy link
Copy Markdown
ContributorAuthor

I now replace the JTRUE statement with the USE(SELECT(...)) instead of appending it and bashing JTRUE to NOP. This means all NOPs are gone. Ready for an other review.

@a74nh

a74nh commented Mar 9, 2026

Copy link
Copy Markdown
Contributor

With the latest version, the IR dump on my test cases look much better. Everything that was optimised becomes a single block with no junk lying around. Plus the removal of the statements and basicblocks automatically get lines added in the dump.

@jakobbotschjakobbotsch 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! Thanks for working on this.

@jakobbotsch

Copy link
Copy Markdown
Member

/ba-g Timeouts, android/tvOS failures and 404 superpmi-diffs failure

@jakobbotsch
jakobbotsch merged commit 6b49b5c into dotnet:mainMar 11, 2026
124 of 131 checks passed
CopilotAI pushed a commit that referenced this pull request Mar 13, 2026
Fix#124942.
Small diffs are expected because:
1. It allows if-conversion to recognize more cases.
For example https://godbolt.org/z/Pdj898qTT currently still produces one
jmp. But with this change it's eliminated, as the search now knows when
there actually is a return block (previously it wasn't marked as such).
2. The block weights changed. For example previously
```cs
static int InvalidIR(bool cond)
{
if (cond)
{
return 2;
}
// or else {}
return 4;
}
```
produced blocks with `bbWeight = 0.5`, now it's 1.0.
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Apr 11, 2026
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 SuperPMIcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JIT: If-conversion phase can produce questionable IR. Return block having a successor

4 participants

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

JIT: Fix invalid IR and weights in if-conversion - #125072

Merged
jakobbotsch merged 11 commits into
dotnet:mainfrom
BoyBaykiller:fix-invalid-ir-if-conv-124942
Mar 11, 2026
Merged

JIT: Fix invalid IR and weights in if-conversion#125072
jakobbotsch merged 11 commits into
dotnet:mainfrom
BoyBaykiller:fix-invalid-ir-if-conv-124942

Conversation

@BoyBaykiller

Copy link
Copy Markdown
Contributor

Fix#124942.

Small diffs are expected because:

  1. It allows if-conversion to recognize more cases.
    For example https://godbolt.org/z/Pdj898qTT currently still produces one jmp. But with this change it's eliminated, as the search now knows when there actually is a return block (previously it wasn't marked as such).

  2. The block weights changed. For example previously

staticintInvalidIR(boolcond){if(cond){return2;}// or else {}return4;}

produced blocks with bbWeight = 0.5, now it's 1.0.

@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Mar 2, 2026
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Mar 2, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

@BoyBaykiller
BoyBaykiller marked this pull request as draft March 2, 2026 21:06

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes CFG/IR correctness issues in CoreCLR JIT if-conversion, especially for return-based if/else patterns, and adjusts how unreachable then/else blocks are treated after conversion so the transformed IR no longer has a RETURN block with a successor.

Changes:

  • Improve DEBUG dumping for if-conversion flows pre- and post-transformation.
  • Update if-conversion CFG rewriting to correctly produce BBJ_RETURN when converting return-based branches, and to detach then/else blocks from the start block.
  • Adjust weights for then/else blocks after their incoming flow is removed.

Comment threadsrc/coreclr/jit/ifconversion.cpp Outdated
Comment threadsrc/coreclr/jit/ifconversion.cpp Outdated
@jakobbotsch
jakobbotsch requested a review from a74nhMarch 4, 2026 16:15
Comment threadsrc/coreclr/jit/ifconversion.cpp Outdated
Comment threadsrc/coreclr/jit/ifconversion.cpp

@a74nha74nh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested this with some if/then, if/then/else, if/then/else/return cases. I'm happy the IR now looks correct afterwards.

* don't dump m_finalBlock

@a74nha74nh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given removing the BB blocks isn't required, I'm happy with this.

Comment threadsrc/coreclr/jit/ifconversion.cpp Outdated
Comment threadsrc/coreclr/jit/ifconversion.cpp
@BoyBaykiller

BoyBaykiller commented Mar 7, 2026

Copy link
Copy Markdown
ContributorAuthor

Now, that if-conversion no longer produces flow through empty blocks I think we can make the following assumptions:

assert(m_startBlock->GetFalseTarget()->GetUniqueSucc() == m_finalBlock);
if (m_doElseConversion)
{
assert(m_startBlock->GetTrueTarget()->GetUniqueSucc() == m_finalBlock);
}

I've run python .\src\coreclr\scripts\superpmi.py replay on this and haven't seen any failures. This would allow us to
simplify some parts, most noticeably FindFlow().

Previously this wasn't true. There could be empty blocks after m_{then/else}Operation.Block before reaching m_finalBlock. E.g System.Net.SocketAddress:GetMaximumAddressSize() or 3039 in aspnet2.run.windows.x64.checked.mch

Conditionally executing BB03 and BB05 inside BB02
------------ BB02 [0001] [004..008) -> BB05(0.5),BB03(0.5) (cond), preds={BB01} succs={BB03,BB05}
***** BB02 [0001]
STMT00003 ( 0x004[E--] ... 0x006 )
N004 ( 5, 5) [000011] -----+----- * JTRUE void $VN.Void
N003 ( 3, 3) [000010] J----+-N--- \--* EQ int $101
N001 ( 1, 1) [000008] -----+----- +--* LCL_VAR int V00 arg0 u:1 $80
N002 ( 1, 1) [000009] -----+----- \--* CNS_INT int 2 $44
------------ BB03 [0002] [008..00D) -> BB06(1) (always), preds={BB02} succs={BB06}
***** BB03 [0002]
STMT00005 ( 0x008[E--] ... 0x00B )
N001 ( 0, 0) [000017] -----+----- * NOP void ***** BB03 [0002]
STMT00007 ( 0x027[E--] ... 0x02C )
N007 ( 6, 9) [000021] DA---+----- * STORE_LCL_VAR int V01 loc0 d:4 $VN.Void
N006 ( 6, 9) [000028] ----------- \--* SELECT int N003 ( 3, 3) [000016] J----+-N--- +--* EQ int $102
N001 ( 1, 1) [000014] -----+----- | +--* LCL_VAR int V00 arg0 u:1 (last use) $80
N002 ( 1, 1) [000015] -----+----- | \--* CNS_INT int 23 $46
N004 ( 1, 1) [000018] -----+----- +--* CNS_INT int 28 $47
N005 ( 1, 4) [000020] -----+----- \--* CNS_INT int 128 $48
***** BB03 [0002]
STMT00006 ( 0x017[E--] ... 0x01C )
N001 ( 0, 0) [000019] -----+----- * NOP void ------------ BB06 [0005] [017..01F) -> BB09(1) (always), preds={BB03} succs={BB09}
------------ BB05 [0004] [00F..017) -> BB09(1) (always), preds={BB02} succs={BB09}
***** BB05 [0004]
STMT00004 ( 0x00F[E--] ... 0x014 )
N002 ( 1, 3) [000013] DA---+----- * STORE_LCL_VAR int V01 loc0 d:2 $VN.Void
N001 ( 1, 1) [000012] -----+----- \--* CNS_INT int 16 $45

Of course if previous phases produce empty blocks inside Then/Else then we'd miss cases, but at least currently this doesn't seem to happen. I want to simplify the code using these assumptions in a follow-up PR.

@BoyBaykiller

Copy link
Copy Markdown
ContributorAuthor

I've applied jakobs suggestion to remove all Then/Else blocks.

I also got rid of the the NOP after the SELECT:

After if conversion
\------------ BB01 [0000] [000..005) -> BB04(1) (always), preds={} succs={BB04}
***** BB01 [0000]
STMT00001 ( 0x002[E--] ... 0x003 )
N001 ( 0, 0) [000005] -----+----- * NOP void ***** BB01 [0000]
STMT00005 ( 0x005[E--] ... 0x006 )
N008 ( 12, 15) [000012] DA---+----- * STORE_LCL_VAR int V01 loc0 d:3 $VN.Void
N007 ( 8, 12) [000018] ----------- \--* SELECT int N004 ( 5, 7) [000004] J----+-N--- +--* EQ int $101
N002 ( 3, 4) [000013] -----+----- | +--* CAST int <- ubyte <- int $100
N001 ( 2, 2) [000002] -----+----- | | \--* LCL_VAR int V00 arg0 u:1 (last use) $80
N003 ( 1, 2) [000003] -----+----- | \--* CNS_INT int 0 $40
N005 ( 1, 2) [000006] -----+----- +--* CNS_INT int 9 $43
N006 ( 1, 2) [000011] -----+----- \--* CNS_INT int 2 $44
- ***** BB01 [0000]- STMT00002 ( 0x009[E--] ... 0x00B )- N001 ( 0, 0) [000007] -----+----- * NOP void 

This is simply done by no longer appending the Else block. That means IfConvertJoinStmts is only called once. And since we really only want to append m_thenOperation.stmt and not the entire block (it might have NOPs), I've decided to remove that function and append m_thenOperation.stmt inline.

@BoyBaykiller

Copy link
Copy Markdown
ContributorAuthor

I now replace the JTRUE statement with the USE(SELECT(...)) instead of appending it and bashing JTRUE to NOP. This means all NOPs are gone. Ready for an other review.

@a74nh

a74nh commented Mar 9, 2026

Copy link
Copy Markdown
Contributor

With the latest version, the IR dump on my test cases look much better. Everything that was optimised becomes a single block with no junk lying around. Plus the removal of the statements and basicblocks automatically get lines added in the dump.

@jakobbotschjakobbotsch 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! Thanks for working on this.

@jakobbotsch

Copy link
Copy Markdown
Member

/ba-g Timeouts, android/tvOS failures and 404 superpmi-diffs failure

@jakobbotsch
jakobbotsch merged commit 6b49b5c into dotnet:mainMar 11, 2026
124 of 131 checks passed
CopilotAI pushed a commit that referenced this pull request Mar 13, 2026
Fix#124942.
Small diffs are expected because:
1. It allows if-conversion to recognize more cases.
For example https://godbolt.org/z/Pdj898qTT currently still produces one
jmp. But with this change it's eliminated, as the search now knows when
there actually is a return block (previously it wasn't marked as such).
2. The block weights changed. For example previously
```cs
static int InvalidIR(bool cond)
{
if (cond)
{
return 2;
}
// or else {}
return 4;
}
```
produced blocks with `bbWeight = 0.5`, now it's 1.0.
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Apr 11, 2026
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 SuperPMIcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JIT: If-conversion phase can produce questionable IR. Return block having a successor

4 participants

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

JIT: Fix invalid IR and weights in if-conversion - #125072

Merged
jakobbotsch merged 11 commits into
dotnet:mainfrom
BoyBaykiller:fix-invalid-ir-if-conv-124942
Mar 11, 2026
Merged

JIT: Fix invalid IR and weights in if-conversion#125072
jakobbotsch merged 11 commits into
dotnet:mainfrom
BoyBaykiller:fix-invalid-ir-if-conv-124942

Conversation

@BoyBaykiller

Copy link
Copy Markdown
Contributor

Fix#124942.

Small diffs are expected because:

  1. It allows if-conversion to recognize more cases.
    For example https://godbolt.org/z/Pdj898qTT currently still produces one jmp. But with this change it's eliminated, as the search now knows when there actually is a return block (previously it wasn't marked as such).

  2. The block weights changed. For example previously

staticintInvalidIR(boolcond){if(cond){return2;}// or else {}return4;}

produced blocks with bbWeight = 0.5, now it's 1.0.

@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Mar 2, 2026
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Mar 2, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

@BoyBaykiller
BoyBaykiller marked this pull request as draft March 2, 2026 21:06

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes CFG/IR correctness issues in CoreCLR JIT if-conversion, especially for return-based if/else patterns, and adjusts how unreachable then/else blocks are treated after conversion so the transformed IR no longer has a RETURN block with a successor.

Changes:

  • Improve DEBUG dumping for if-conversion flows pre- and post-transformation.
  • Update if-conversion CFG rewriting to correctly produce BBJ_RETURN when converting return-based branches, and to detach then/else blocks from the start block.
  • Adjust weights for then/else blocks after their incoming flow is removed.

Comment threadsrc/coreclr/jit/ifconversion.cpp Outdated
Comment threadsrc/coreclr/jit/ifconversion.cpp Outdated
@jakobbotsch
jakobbotsch requested a review from a74nhMarch 4, 2026 16:15
Comment threadsrc/coreclr/jit/ifconversion.cpp Outdated
Comment threadsrc/coreclr/jit/ifconversion.cpp

@a74nha74nh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested this with some if/then, if/then/else, if/then/else/return cases. I'm happy the IR now looks correct afterwards.

* don't dump m_finalBlock

@a74nha74nh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given removing the BB blocks isn't required, I'm happy with this.

Comment threadsrc/coreclr/jit/ifconversion.cpp Outdated
Comment threadsrc/coreclr/jit/ifconversion.cpp
@BoyBaykiller

BoyBaykiller commented Mar 7, 2026

Copy link
Copy Markdown
ContributorAuthor

Now, that if-conversion no longer produces flow through empty blocks I think we can make the following assumptions:

assert(m_startBlock->GetFalseTarget()->GetUniqueSucc() == m_finalBlock);
if (m_doElseConversion)
{
assert(m_startBlock->GetTrueTarget()->GetUniqueSucc() == m_finalBlock);
}

I've run python .\src\coreclr\scripts\superpmi.py replay on this and haven't seen any failures. This would allow us to
simplify some parts, most noticeably FindFlow().

Previously this wasn't true. There could be empty blocks after m_{then/else}Operation.Block before reaching m_finalBlock. E.g System.Net.SocketAddress:GetMaximumAddressSize() or 3039 in aspnet2.run.windows.x64.checked.mch

Conditionally executing BB03 and BB05 inside BB02
------------ BB02 [0001] [004..008) -> BB05(0.5),BB03(0.5) (cond), preds={BB01} succs={BB03,BB05}
***** BB02 [0001]
STMT00003 ( 0x004[E--] ... 0x006 )
N004 ( 5, 5) [000011] -----+----- * JTRUE void $VN.Void
N003 ( 3, 3) [000010] J----+-N--- \--* EQ int $101
N001 ( 1, 1) [000008] -----+----- +--* LCL_VAR int V00 arg0 u:1 $80
N002 ( 1, 1) [000009] -----+----- \--* CNS_INT int 2 $44
------------ BB03 [0002] [008..00D) -> BB06(1) (always), preds={BB02} succs={BB06}
***** BB03 [0002]
STMT00005 ( 0x008[E--] ... 0x00B )
N001 ( 0, 0) [000017] -----+----- * NOP void ***** BB03 [0002]
STMT00007 ( 0x027[E--] ... 0x02C )
N007 ( 6, 9) [000021] DA---+----- * STORE_LCL_VAR int V01 loc0 d:4 $VN.Void
N006 ( 6, 9) [000028] ----------- \--* SELECT int N003 ( 3, 3) [000016] J----+-N--- +--* EQ int $102
N001 ( 1, 1) [000014] -----+----- | +--* LCL_VAR int V00 arg0 u:1 (last use) $80
N002 ( 1, 1) [000015] -----+----- | \--* CNS_INT int 23 $46
N004 ( 1, 1) [000018] -----+----- +--* CNS_INT int 28 $47
N005 ( 1, 4) [000020] -----+----- \--* CNS_INT int 128 $48
***** BB03 [0002]
STMT00006 ( 0x017[E--] ... 0x01C )
N001 ( 0, 0) [000019] -----+----- * NOP void ------------ BB06 [0005] [017..01F) -> BB09(1) (always), preds={BB03} succs={BB09}
------------ BB05 [0004] [00F..017) -> BB09(1) (always), preds={BB02} succs={BB09}
***** BB05 [0004]
STMT00004 ( 0x00F[E--] ... 0x014 )
N002 ( 1, 3) [000013] DA---+----- * STORE_LCL_VAR int V01 loc0 d:2 $VN.Void
N001 ( 1, 1) [000012] -----+----- \--* CNS_INT int 16 $45

Of course if previous phases produce empty blocks inside Then/Else then we'd miss cases, but at least currently this doesn't seem to happen. I want to simplify the code using these assumptions in a follow-up PR.

@BoyBaykiller

Copy link
Copy Markdown
ContributorAuthor

I've applied jakobs suggestion to remove all Then/Else blocks.

I also got rid of the the NOP after the SELECT:

After if conversion
\------------ BB01 [0000] [000..005) -> BB04(1) (always), preds={} succs={BB04}
***** BB01 [0000]
STMT00001 ( 0x002[E--] ... 0x003 )
N001 ( 0, 0) [000005] -----+----- * NOP void ***** BB01 [0000]
STMT00005 ( 0x005[E--] ... 0x006 )
N008 ( 12, 15) [000012] DA---+----- * STORE_LCL_VAR int V01 loc0 d:3 $VN.Void
N007 ( 8, 12) [000018] ----------- \--* SELECT int N004 ( 5, 7) [000004] J----+-N--- +--* EQ int $101
N002 ( 3, 4) [000013] -----+----- | +--* CAST int <- ubyte <- int $100
N001 ( 2, 2) [000002] -----+----- | | \--* LCL_VAR int V00 arg0 u:1 (last use) $80
N003 ( 1, 2) [000003] -----+----- | \--* CNS_INT int 0 $40
N005 ( 1, 2) [000006] -----+----- +--* CNS_INT int 9 $43
N006 ( 1, 2) [000011] -----+----- \--* CNS_INT int 2 $44
- ***** BB01 [0000]- STMT00002 ( 0x009[E--] ... 0x00B )- N001 ( 0, 0) [000007] -----+----- * NOP void 

This is simply done by no longer appending the Else block. That means IfConvertJoinStmts is only called once. And since we really only want to append m_thenOperation.stmt and not the entire block (it might have NOPs), I've decided to remove that function and append m_thenOperation.stmt inline.

@BoyBaykiller

Copy link
Copy Markdown
ContributorAuthor

I now replace the JTRUE statement with the USE(SELECT(...)) instead of appending it and bashing JTRUE to NOP. This means all NOPs are gone. Ready for an other review.

@a74nh

a74nh commented Mar 9, 2026

Copy link
Copy Markdown
Contributor

With the latest version, the IR dump on my test cases look much better. Everything that was optimised becomes a single block with no junk lying around. Plus the removal of the statements and basicblocks automatically get lines added in the dump.

@jakobbotschjakobbotsch 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! Thanks for working on this.

@jakobbotsch

Copy link
Copy Markdown
Member

/ba-g Timeouts, android/tvOS failures and 404 superpmi-diffs failure

@jakobbotsch
jakobbotsch merged commit 6b49b5c into dotnet:mainMar 11, 2026
124 of 131 checks passed
CopilotAI pushed a commit that referenced this pull request Mar 13, 2026
Fix#124942.
Small diffs are expected because:
1. It allows if-conversion to recognize more cases.
For example https://godbolt.org/z/Pdj898qTT currently still produces one
jmp. But with this change it's eliminated, as the search now knows when
there actually is a return block (previously it wasn't marked as such).
2. The block weights changed. For example previously
```cs
static int InvalidIR(bool cond)
{
if (cond)
{
return 2;
}
// or else {}
return 4;
}
```
produced blocks with `bbWeight = 0.5`, now it's 1.0.
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Apr 11, 2026
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 SuperPMIcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JIT: If-conversion phase can produce questionable IR. Return block having a successor

4 participants

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

JIT: Fix invalid IR and weights in if-conversion - #125072

Merged
jakobbotsch merged 11 commits into
dotnet:mainfrom
BoyBaykiller:fix-invalid-ir-if-conv-124942
Mar 11, 2026
Merged

JIT: Fix invalid IR and weights in if-conversion#125072
jakobbotsch merged 11 commits into
dotnet:mainfrom
BoyBaykiller:fix-invalid-ir-if-conv-124942

Conversation

@BoyBaykiller

Copy link
Copy Markdown
Contributor

Fix#124942.

Small diffs are expected because:

  1. It allows if-conversion to recognize more cases.
    For example https://godbolt.org/z/Pdj898qTT currently still produces one jmp. But with this change it's eliminated, as the search now knows when there actually is a return block (previously it wasn't marked as such).

  2. The block weights changed. For example previously

staticintInvalidIR(boolcond){if(cond){return2;}// or else {}return4;}

produced blocks with bbWeight = 0.5, now it's 1.0.

@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Mar 2, 2026
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Mar 2, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

@BoyBaykiller
BoyBaykiller marked this pull request as draft March 2, 2026 21:06

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes CFG/IR correctness issues in CoreCLR JIT if-conversion, especially for return-based if/else patterns, and adjusts how unreachable then/else blocks are treated after conversion so the transformed IR no longer has a RETURN block with a successor.

Changes:

  • Improve DEBUG dumping for if-conversion flows pre- and post-transformation.
  • Update if-conversion CFG rewriting to correctly produce BBJ_RETURN when converting return-based branches, and to detach then/else blocks from the start block.
  • Adjust weights for then/else blocks after their incoming flow is removed.

Comment threadsrc/coreclr/jit/ifconversion.cpp Outdated
Comment threadsrc/coreclr/jit/ifconversion.cpp Outdated
@jakobbotsch
jakobbotsch requested a review from a74nhMarch 4, 2026 16:15
Comment threadsrc/coreclr/jit/ifconversion.cpp Outdated
Comment threadsrc/coreclr/jit/ifconversion.cpp

@a74nha74nh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested this with some if/then, if/then/else, if/then/else/return cases. I'm happy the IR now looks correct afterwards.

* don't dump m_finalBlock

@a74nha74nh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given removing the BB blocks isn't required, I'm happy with this.

Comment threadsrc/coreclr/jit/ifconversion.cpp Outdated
Comment threadsrc/coreclr/jit/ifconversion.cpp
@BoyBaykiller

BoyBaykiller commented Mar 7, 2026

Copy link
Copy Markdown
ContributorAuthor

Now, that if-conversion no longer produces flow through empty blocks I think we can make the following assumptions:

assert(m_startBlock->GetFalseTarget()->GetUniqueSucc() == m_finalBlock);
if (m_doElseConversion)
{
assert(m_startBlock->GetTrueTarget()->GetUniqueSucc() == m_finalBlock);
}

I've run python .\src\coreclr\scripts\superpmi.py replay on this and haven't seen any failures. This would allow us to
simplify some parts, most noticeably FindFlow().

Previously this wasn't true. There could be empty blocks after m_{then/else}Operation.Block before reaching m_finalBlock. E.g System.Net.SocketAddress:GetMaximumAddressSize() or 3039 in aspnet2.run.windows.x64.checked.mch

Conditionally executing BB03 and BB05 inside BB02
------------ BB02 [0001] [004..008) -> BB05(0.5),BB03(0.5) (cond), preds={BB01} succs={BB03,BB05}
***** BB02 [0001]
STMT00003 ( 0x004[E--] ... 0x006 )
N004 ( 5, 5) [000011] -----+----- * JTRUE void $VN.Void
N003 ( 3, 3) [000010] J----+-N--- \--* EQ int $101
N001 ( 1, 1) [000008] -----+----- +--* LCL_VAR int V00 arg0 u:1 $80
N002 ( 1, 1) [000009] -----+----- \--* CNS_INT int 2 $44
------------ BB03 [0002] [008..00D) -> BB06(1) (always), preds={BB02} succs={BB06}
***** BB03 [0002]
STMT00005 ( 0x008[E--] ... 0x00B )
N001 ( 0, 0) [000017] -----+----- * NOP void ***** BB03 [0002]
STMT00007 ( 0x027[E--] ... 0x02C )
N007 ( 6, 9) [000021] DA---+----- * STORE_LCL_VAR int V01 loc0 d:4 $VN.Void
N006 ( 6, 9) [000028] ----------- \--* SELECT int N003 ( 3, 3) [000016] J----+-N--- +--* EQ int $102
N001 ( 1, 1) [000014] -----+----- | +--* LCL_VAR int V00 arg0 u:1 (last use) $80
N002 ( 1, 1) [000015] -----+----- | \--* CNS_INT int 23 $46
N004 ( 1, 1) [000018] -----+----- +--* CNS_INT int 28 $47
N005 ( 1, 4) [000020] -----+----- \--* CNS_INT int 128 $48
***** BB03 [0002]
STMT00006 ( 0x017[E--] ... 0x01C )
N001 ( 0, 0) [000019] -----+----- * NOP void ------------ BB06 [0005] [017..01F) -> BB09(1) (always), preds={BB03} succs={BB09}
------------ BB05 [0004] [00F..017) -> BB09(1) (always), preds={BB02} succs={BB09}
***** BB05 [0004]
STMT00004 ( 0x00F[E--] ... 0x014 )
N002 ( 1, 3) [000013] DA---+----- * STORE_LCL_VAR int V01 loc0 d:2 $VN.Void
N001 ( 1, 1) [000012] -----+----- \--* CNS_INT int 16 $45

Of course if previous phases produce empty blocks inside Then/Else then we'd miss cases, but at least currently this doesn't seem to happen. I want to simplify the code using these assumptions in a follow-up PR.

@BoyBaykiller

Copy link
Copy Markdown
ContributorAuthor

I've applied jakobs suggestion to remove all Then/Else blocks.

I also got rid of the the NOP after the SELECT:

After if conversion
\------------ BB01 [0000] [000..005) -> BB04(1) (always), preds={} succs={BB04}
***** BB01 [0000]
STMT00001 ( 0x002[E--] ... 0x003 )
N001 ( 0, 0) [000005] -----+----- * NOP void ***** BB01 [0000]
STMT00005 ( 0x005[E--] ... 0x006 )
N008 ( 12, 15) [000012] DA---+----- * STORE_LCL_VAR int V01 loc0 d:3 $VN.Void
N007 ( 8, 12) [000018] ----------- \--* SELECT int N004 ( 5, 7) [000004] J----+-N--- +--* EQ int $101
N002 ( 3, 4) [000013] -----+----- | +--* CAST int <- ubyte <- int $100
N001 ( 2, 2) [000002] -----+----- | | \--* LCL_VAR int V00 arg0 u:1 (last use) $80
N003 ( 1, 2) [000003] -----+----- | \--* CNS_INT int 0 $40
N005 ( 1, 2) [000006] -----+----- +--* CNS_INT int 9 $43
N006 ( 1, 2) [000011] -----+----- \--* CNS_INT int 2 $44
- ***** BB01 [0000]- STMT00002 ( 0x009[E--] ... 0x00B )- N001 ( 0, 0) [000007] -----+----- * NOP void 

This is simply done by no longer appending the Else block. That means IfConvertJoinStmts is only called once. And since we really only want to append m_thenOperation.stmt and not the entire block (it might have NOPs), I've decided to remove that function and append m_thenOperation.stmt inline.

@BoyBaykiller

Copy link
Copy Markdown
ContributorAuthor

I now replace the JTRUE statement with the USE(SELECT(...)) instead of appending it and bashing JTRUE to NOP. This means all NOPs are gone. Ready for an other review.

@a74nh

a74nh commented Mar 9, 2026

Copy link
Copy Markdown
Contributor

With the latest version, the IR dump on my test cases look much better. Everything that was optimised becomes a single block with no junk lying around. Plus the removal of the statements and basicblocks automatically get lines added in the dump.

@jakobbotschjakobbotsch 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! Thanks for working on this.

@jakobbotsch

Copy link
Copy Markdown
Member

/ba-g Timeouts, android/tvOS failures and 404 superpmi-diffs failure

@jakobbotsch
jakobbotsch merged commit 6b49b5c into dotnet:mainMar 11, 2026
124 of 131 checks passed
CopilotAI pushed a commit that referenced this pull request Mar 13, 2026
Fix#124942.
Small diffs are expected because:
1. It allows if-conversion to recognize more cases.
For example https://godbolt.org/z/Pdj898qTT currently still produces one
jmp. But with this change it's eliminated, as the search now knows when
there actually is a return block (previously it wasn't marked as such).
2. The block weights changed. For example previously
```cs
static int InvalidIR(bool cond)
{
if (cond)
{
return 2;
}
// or else {}
return 4;
}
```
produced blocks with `bbWeight = 0.5`, now it's 1.0.
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Apr 11, 2026
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 SuperPMIcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JIT: If-conversion phase can produce questionable IR. Return block having a successor

4 participants

@BoyBaykiller@a74nh@jakobbotsch
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

JIT: Fix invalid IR and weights in if-conversion - #125072

Merged
jakobbotsch merged 11 commits into
dotnet:mainfrom
BoyBaykiller:fix-invalid-ir-if-conv-124942
Mar 11, 2026
Merged

JIT: Fix invalid IR and weights in if-conversion#125072
jakobbotsch merged 11 commits into
dotnet:mainfrom
BoyBaykiller:fix-invalid-ir-if-conv-124942

Conversation

@BoyBaykiller

Copy link
Copy Markdown
Contributor

Fix#124942.

Small diffs are expected because:

  1. It allows if-conversion to recognize more cases.
    For example https://godbolt.org/z/Pdj898qTT currently still produces one jmp. But with this change it's eliminated, as the search now knows when there actually is a return block (previously it wasn't marked as such).

  2. The block weights changed. For example previously

staticintInvalidIR(boolcond){if(cond){return2;}// or else {}return4;}

produced blocks with bbWeight = 0.5, now it's 1.0.

@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Mar 2, 2026
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Mar 2, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

@BoyBaykiller
BoyBaykiller marked this pull request as draft March 2, 2026 21:06

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes CFG/IR correctness issues in CoreCLR JIT if-conversion, especially for return-based if/else patterns, and adjusts how unreachable then/else blocks are treated after conversion so the transformed IR no longer has a RETURN block with a successor.

Changes:

  • Improve DEBUG dumping for if-conversion flows pre- and post-transformation.
  • Update if-conversion CFG rewriting to correctly produce BBJ_RETURN when converting return-based branches, and to detach then/else blocks from the start block.
  • Adjust weights for then/else blocks after their incoming flow is removed.

Comment threadsrc/coreclr/jit/ifconversion.cpp Outdated
Comment threadsrc/coreclr/jit/ifconversion.cpp Outdated
@jakobbotsch
jakobbotsch requested a review from a74nhMarch 4, 2026 16:15
Comment threadsrc/coreclr/jit/ifconversion.cpp Outdated
Comment threadsrc/coreclr/jit/ifconversion.cpp

@a74nha74nh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested this with some if/then, if/then/else, if/then/else/return cases. I'm happy the IR now looks correct afterwards.

* don't dump m_finalBlock

@a74nha74nh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given removing the BB blocks isn't required, I'm happy with this.

Comment threadsrc/coreclr/jit/ifconversion.cpp Outdated
Comment threadsrc/coreclr/jit/ifconversion.cpp
@BoyBaykiller

BoyBaykiller commented Mar 7, 2026

Copy link
Copy Markdown
ContributorAuthor

Now, that if-conversion no longer produces flow through empty blocks I think we can make the following assumptions:

assert(m_startBlock->GetFalseTarget()->GetUniqueSucc() == m_finalBlock);
if (m_doElseConversion)
{
assert(m_startBlock->GetTrueTarget()->GetUniqueSucc() == m_finalBlock);
}

I've run python .\src\coreclr\scripts\superpmi.py replay on this and haven't seen any failures. This would allow us to
simplify some parts, most noticeably FindFlow().

Previously this wasn't true. There could be empty blocks after m_{then/else}Operation.Block before reaching m_finalBlock. E.g System.Net.SocketAddress:GetMaximumAddressSize() or 3039 in aspnet2.run.windows.x64.checked.mch

Conditionally executing BB03 and BB05 inside BB02
------------ BB02 [0001] [004..008) -> BB05(0.5),BB03(0.5) (cond), preds={BB01} succs={BB03,BB05}
***** BB02 [0001]
STMT00003 ( 0x004[E--] ... 0x006 )
N004 ( 5, 5) [000011] -----+----- * JTRUE void $VN.Void
N003 ( 3, 3) [000010] J----+-N--- \--* EQ int $101
N001 ( 1, 1) [000008] -----+----- +--* LCL_VAR int V00 arg0 u:1 $80
N002 ( 1, 1) [000009] -----+----- \--* CNS_INT int 2 $44
------------ BB03 [0002] [008..00D) -> BB06(1) (always), preds={BB02} succs={BB06}
***** BB03 [0002]
STMT00005 ( 0x008[E--] ... 0x00B )
N001 ( 0, 0) [000017] -----+----- * NOP void ***** BB03 [0002]
STMT00007 ( 0x027[E--] ... 0x02C )
N007 ( 6, 9) [000021] DA---+----- * STORE_LCL_VAR int V01 loc0 d:4 $VN.Void
N006 ( 6, 9) [000028] ----------- \--* SELECT int N003 ( 3, 3) [000016] J----+-N--- +--* EQ int $102
N001 ( 1, 1) [000014] -----+----- | +--* LCL_VAR int V00 arg0 u:1 (last use) $80
N002 ( 1, 1) [000015] -----+----- | \--* CNS_INT int 23 $46
N004 ( 1, 1) [000018] -----+----- +--* CNS_INT int 28 $47
N005 ( 1, 4) [000020] -----+----- \--* CNS_INT int 128 $48
***** BB03 [0002]
STMT00006 ( 0x017[E--] ... 0x01C )
N001 ( 0, 0) [000019] -----+----- * NOP void ------------ BB06 [0005] [017..01F) -> BB09(1) (always), preds={BB03} succs={BB09}
------------ BB05 [0004] [00F..017) -> BB09(1) (always), preds={BB02} succs={BB09}
***** BB05 [0004]
STMT00004 ( 0x00F[E--] ... 0x014 )
N002 ( 1, 3) [000013] DA---+----- * STORE_LCL_VAR int V01 loc0 d:2 $VN.Void
N001 ( 1, 1) [000012] -----+----- \--* CNS_INT int 16 $45

Of course if previous phases produce empty blocks inside Then/Else then we'd miss cases, but at least currently this doesn't seem to happen. I want to simplify the code using these assumptions in a follow-up PR.

@BoyBaykiller

Copy link
Copy Markdown
ContributorAuthor

I've applied jakobs suggestion to remove all Then/Else blocks.

I also got rid of the the NOP after the SELECT:

After if conversion
\------------ BB01 [0000] [000..005) -> BB04(1) (always), preds={} succs={BB04}
***** BB01 [0000]
STMT00001 ( 0x002[E--] ... 0x003 )
N001 ( 0, 0) [000005] -----+----- * NOP void ***** BB01 [0000]
STMT00005 ( 0x005[E--] ... 0x006 )
N008 ( 12, 15) [000012] DA---+----- * STORE_LCL_VAR int V01 loc0 d:3 $VN.Void
N007 ( 8, 12) [000018] ----------- \--* SELECT int N004 ( 5, 7) [000004] J----+-N--- +--* EQ int $101
N002 ( 3, 4) [000013] -----+----- | +--* CAST int <- ubyte <- int $100
N001 ( 2, 2) [000002] -----+----- | | \--* LCL_VAR int V00 arg0 u:1 (last use) $80
N003 ( 1, 2) [000003] -----+----- | \--* CNS_INT int 0 $40
N005 ( 1, 2) [000006] -----+----- +--* CNS_INT int 9 $43
N006 ( 1, 2) [000011] -----+----- \--* CNS_INT int 2 $44
- ***** BB01 [0000]- STMT00002 ( 0x009[E--] ... 0x00B )- N001 ( 0, 0) [000007] -----+----- * NOP void 

This is simply done by no longer appending the Else block. That means IfConvertJoinStmts is only called once. And since we really only want to append m_thenOperation.stmt and not the entire block (it might have NOPs), I've decided to remove that function and append m_thenOperation.stmt inline.

@BoyBaykiller

Copy link
Copy Markdown
ContributorAuthor

I now replace the JTRUE statement with the USE(SELECT(...)) instead of appending it and bashing JTRUE to NOP. This means all NOPs are gone. Ready for an other review.

@a74nh

a74nh commented Mar 9, 2026

Copy link
Copy Markdown
Contributor

With the latest version, the IR dump on my test cases look much better. Everything that was optimised becomes a single block with no junk lying around. Plus the removal of the statements and basicblocks automatically get lines added in the dump.

@jakobbotschjakobbotsch 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! Thanks for working on this.

@jakobbotsch

Copy link
Copy Markdown
Member

/ba-g Timeouts, android/tvOS failures and 404 superpmi-diffs failure

@jakobbotsch
jakobbotsch merged commit 6b49b5c into dotnet:mainMar 11, 2026
124 of 131 checks passed
CopilotAI pushed a commit that referenced this pull request Mar 13, 2026
Fix#124942.
Small diffs are expected because:
1. It allows if-conversion to recognize more cases.
For example https://godbolt.org/z/Pdj898qTT currently still produces one
jmp. But with this change it's eliminated, as the search now knows when
there actually is a return block (previously it wasn't marked as such).
2. The block weights changed. For example previously
```cs
static int InvalidIR(bool cond)
{
if (cond)
{
return 2;
}
// or else {}
return 4;
}
```
produced blocks with `bbWeight = 0.5`, now it's 1.0.
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Apr 11, 2026
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 SuperPMIcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JIT: If-conversion phase can produce questionable IR. Return block having a successor

4 participants

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

JIT: Fix invalid IR and weights in if-conversion - #125072

Merged
jakobbotsch merged 11 commits into
dotnet:mainfrom
BoyBaykiller:fix-invalid-ir-if-conv-124942
Mar 11, 2026
Merged

JIT: Fix invalid IR and weights in if-conversion#125072
jakobbotsch merged 11 commits into
dotnet:mainfrom
BoyBaykiller:fix-invalid-ir-if-conv-124942

Conversation

@BoyBaykiller

Copy link
Copy Markdown
Contributor

Fix#124942.

Small diffs are expected because:

  1. It allows if-conversion to recognize more cases.
    For example https://godbolt.org/z/Pdj898qTT currently still produces one jmp. But with this change it's eliminated, as the search now knows when there actually is a return block (previously it wasn't marked as such).

  2. The block weights changed. For example previously

staticintInvalidIR(boolcond){if(cond){return2;}// or else {}return4;}

produced blocks with bbWeight = 0.5, now it's 1.0.

@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Mar 2, 2026
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Mar 2, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

@BoyBaykiller
BoyBaykiller marked this pull request as draft March 2, 2026 21:06

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes CFG/IR correctness issues in CoreCLR JIT if-conversion, especially for return-based if/else patterns, and adjusts how unreachable then/else blocks are treated after conversion so the transformed IR no longer has a RETURN block with a successor.

Changes:

  • Improve DEBUG dumping for if-conversion flows pre- and post-transformation.
  • Update if-conversion CFG rewriting to correctly produce BBJ_RETURN when converting return-based branches, and to detach then/else blocks from the start block.
  • Adjust weights for then/else blocks after their incoming flow is removed.

Comment threadsrc/coreclr/jit/ifconversion.cpp Outdated
Comment threadsrc/coreclr/jit/ifconversion.cpp Outdated
@jakobbotsch
jakobbotsch requested a review from a74nhMarch 4, 2026 16:15
Comment threadsrc/coreclr/jit/ifconversion.cpp Outdated
Comment threadsrc/coreclr/jit/ifconversion.cpp

@a74nha74nh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested this with some if/then, if/then/else, if/then/else/return cases. I'm happy the IR now looks correct afterwards.

* don't dump m_finalBlock

@a74nha74nh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given removing the BB blocks isn't required, I'm happy with this.

Comment threadsrc/coreclr/jit/ifconversion.cpp Outdated
Comment threadsrc/coreclr/jit/ifconversion.cpp
@BoyBaykiller

BoyBaykiller commented Mar 7, 2026

Copy link
Copy Markdown
ContributorAuthor

Now, that if-conversion no longer produces flow through empty blocks I think we can make the following assumptions:

assert(m_startBlock->GetFalseTarget()->GetUniqueSucc() == m_finalBlock);
if (m_doElseConversion)
{
assert(m_startBlock->GetTrueTarget()->GetUniqueSucc() == m_finalBlock);
}

I've run python .\src\coreclr\scripts\superpmi.py replay on this and haven't seen any failures. This would allow us to
simplify some parts, most noticeably FindFlow().

Previously this wasn't true. There could be empty blocks after m_{then/else}Operation.Block before reaching m_finalBlock. E.g System.Net.SocketAddress:GetMaximumAddressSize() or 3039 in aspnet2.run.windows.x64.checked.mch

Conditionally executing BB03 and BB05 inside BB02
------------ BB02 [0001] [004..008) -> BB05(0.5),BB03(0.5) (cond), preds={BB01} succs={BB03,BB05}
***** BB02 [0001]
STMT00003 ( 0x004[E--] ... 0x006 )
N004 ( 5, 5) [000011] -----+----- * JTRUE void $VN.Void
N003 ( 3, 3) [000010] J----+-N--- \--* EQ int $101
N001 ( 1, 1) [000008] -----+----- +--* LCL_VAR int V00 arg0 u:1 $80
N002 ( 1, 1) [000009] -----+----- \--* CNS_INT int 2 $44
------------ BB03 [0002] [008..00D) -> BB06(1) (always), preds={BB02} succs={BB06}
***** BB03 [0002]
STMT00005 ( 0x008[E--] ... 0x00B )
N001 ( 0, 0) [000017] -----+----- * NOP void ***** BB03 [0002]
STMT00007 ( 0x027[E--] ... 0x02C )
N007 ( 6, 9) [000021] DA---+----- * STORE_LCL_VAR int V01 loc0 d:4 $VN.Void
N006 ( 6, 9) [000028] ----------- \--* SELECT int N003 ( 3, 3) [000016] J----+-N--- +--* EQ int $102
N001 ( 1, 1) [000014] -----+----- | +--* LCL_VAR int V00 arg0 u:1 (last use) $80
N002 ( 1, 1) [000015] -----+----- | \--* CNS_INT int 23 $46
N004 ( 1, 1) [000018] -----+----- +--* CNS_INT int 28 $47
N005 ( 1, 4) [000020] -----+----- \--* CNS_INT int 128 $48
***** BB03 [0002]
STMT00006 ( 0x017[E--] ... 0x01C )
N001 ( 0, 0) [000019] -----+----- * NOP void ------------ BB06 [0005] [017..01F) -> BB09(1) (always), preds={BB03} succs={BB09}
------------ BB05 [0004] [00F..017) -> BB09(1) (always), preds={BB02} succs={BB09}
***** BB05 [0004]
STMT00004 ( 0x00F[E--] ... 0x014 )
N002 ( 1, 3) [000013] DA---+----- * STORE_LCL_VAR int V01 loc0 d:2 $VN.Void
N001 ( 1, 1) [000012] -----+----- \--* CNS_INT int 16 $45

Of course if previous phases produce empty blocks inside Then/Else then we'd miss cases, but at least currently this doesn't seem to happen. I want to simplify the code using these assumptions in a follow-up PR.

@BoyBaykiller

Copy link
Copy Markdown
ContributorAuthor

I've applied jakobs suggestion to remove all Then/Else blocks.

I also got rid of the the NOP after the SELECT:

After if conversion
\------------ BB01 [0000] [000..005) -> BB04(1) (always), preds={} succs={BB04}
***** BB01 [0000]
STMT00001 ( 0x002[E--] ... 0x003 )
N001 ( 0, 0) [000005] -----+----- * NOP void ***** BB01 [0000]
STMT00005 ( 0x005[E--] ... 0x006 )
N008 ( 12, 15) [000012] DA---+----- * STORE_LCL_VAR int V01 loc0 d:3 $VN.Void
N007 ( 8, 12) [000018] ----------- \--* SELECT int N004 ( 5, 7) [000004] J----+-N--- +--* EQ int $101
N002 ( 3, 4) [000013] -----+----- | +--* CAST int <- ubyte <- int $100
N001 ( 2, 2) [000002] -----+----- | | \--* LCL_VAR int V00 arg0 u:1 (last use) $80
N003 ( 1, 2) [000003] -----+----- | \--* CNS_INT int 0 $40
N005 ( 1, 2) [000006] -----+----- +--* CNS_INT int 9 $43
N006 ( 1, 2) [000011] -----+----- \--* CNS_INT int 2 $44
- ***** BB01 [0000]- STMT00002 ( 0x009[E--] ... 0x00B )- N001 ( 0, 0) [000007] -----+----- * NOP void 

This is simply done by no longer appending the Else block. That means IfConvertJoinStmts is only called once. And since we really only want to append m_thenOperation.stmt and not the entire block (it might have NOPs), I've decided to remove that function and append m_thenOperation.stmt inline.

@BoyBaykiller

Copy link
Copy Markdown
ContributorAuthor

I now replace the JTRUE statement with the USE(SELECT(...)) instead of appending it and bashing JTRUE to NOP. This means all NOPs are gone. Ready for an other review.

@a74nh

a74nh commented Mar 9, 2026

Copy link
Copy Markdown
Contributor

With the latest version, the IR dump on my test cases look much better. Everything that was optimised becomes a single block with no junk lying around. Plus the removal of the statements and basicblocks automatically get lines added in the dump.

@jakobbotschjakobbotsch 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! Thanks for working on this.

@jakobbotsch

Copy link
Copy Markdown
Member

/ba-g Timeouts, android/tvOS failures and 404 superpmi-diffs failure

@jakobbotsch
jakobbotsch merged commit 6b49b5c into dotnet:mainMar 11, 2026
124 of 131 checks passed
CopilotAI pushed a commit that referenced this pull request Mar 13, 2026
Fix#124942.
Small diffs are expected because:
1. It allows if-conversion to recognize more cases.
For example https://godbolt.org/z/Pdj898qTT currently still produces one
jmp. But with this change it's eliminated, as the search now knows when
there actually is a return block (previously it wasn't marked as such).
2. The block weights changed. For example previously
```cs
static int InvalidIR(bool cond)
{
if (cond)
{
return 2;
}
// or else {}
return 4;
}
```
produced blocks with `bbWeight = 0.5`, now it's 1.0.
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Apr 11, 2026
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 SuperPMIcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JIT: If-conversion phase can produce questionable IR. Return block having a successor

4 participants

@BoyBaykiller@a74nh@jakobbotsch