Skip to content

JIT: Simplify if-conversion#125347

Merged
jakobbotsch merged 9 commits intodotnet:mainfrom
BoyBaykiller:simplify-if-conv
Apr 15, 2026
Merged

JIT: Simplify if-conversion#125347
jakobbotsch merged 9 commits intodotnet:mainfrom
BoyBaykiller:simplify-if-conv

Conversation

@BoyBaykiller
Copy link
Copy Markdown
Contributor

@BoyBaykiller BoyBaykiller commented Mar 9, 2026

Zero-diff change that simplifies if-conversion based on the assumption that there are no empty blocks inside the Then and Else case. See: #125072 (comment)

@BoyBaykiller BoyBaykiller changed the title JIT: Simplify if conv JIT: Simplify if-conversion Mar 9, 2026
@github-actions github-actions Bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Mar 9, 2026
@dotnet-policy-service dotnet-policy-service Bot added the community-contribution Indicates that the PR has been added by a community member label Mar 9, 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.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 simplifies the CoreCLR JIT if-conversion implementation by collapsing the prior multi-block flow search into a single, direct-flow check, under the assumption that Then/Else paths do not contain empty/chained blocks.

Changes:

  • Replace the prior bounded “walk” of Then/Else chains with a single IfConvertCheckFlow() check.
  • Simplify statement validation to operate on a single block (ignoring NOPs) rather than scanning a block chain to m_finalBlock.
  • Simplify debug dumping and block removal to target only the identified Then/Else blocks.

Comment thread src/coreclr/jit/ifconversion.cpp
@BoyBaykiller
Copy link
Copy Markdown
Contributor Author

BoyBaykiller commented Mar 12, 2026

@a74nh @jakobbotsch What do you think? I guess the main question is whether this is a reasonable assumption to make in the first place:

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

Meaning no flow through empty blocks inside Then/Else cases.
So CheckFlow becomes just https://github.com/BoyBaykiller/runtime/blob/5c1616c5d263ec7ef07a1da4b8bc8224cdf3b97c/src/coreclr/jit/ifconversion.cpp#L73-L101. No diffs. I think cases where previous phases produce empty blocks aren't amendable to if-conversion anyway.

I simplified:

  • CheckFlow
  • CheckStmts
  • Dump
  • removeBlocks

@a74nh
Copy link
Copy Markdown
Contributor

a74nh commented Mar 13, 2026

@a74nh @jakobbotsch What do you think? I guess the main question is whether this is a reasonable assumption to make in the first place:

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

The original assumption was that the graph potentially could contain multiple blocks with a straight line flow between them. What could give that scenario?

  • reaching a max number of statements in a block? I'm not sure if there even is a limit
  • if a previous optimisation left the graph that way (for example, if conversion prior to your fix). But that's for the previous optimisation to be fixed up.
  • Nested if then, eg if (x) { if(y) {...} else {...} } else { ... }. But if conversion can't optimise that scenario anyway.
  • if (x && y && z) isn't quite the same scenario, but that will already be optimised by optimise bools into CCMPs that can then be if converted.

No diffs

Even if it's not a 100% correct assumption, sounds like we're still catching everything of relevance with your change.

@BoyBaykiller
Copy link
Copy Markdown
Contributor Author

Nested if then, eg if (x) { if(y) {...} else {...} } else { ... }. But if conversion can't optimise that scenario anyway.

Do you mean something like this. Because it does get recognized. We just bail on profitability for the outer one:
Skipping if-conversion that will evaluate RHS unconditionally at costs 9,1 .

int a74nh(bool x, bool y)
{
    if (x)
    {
        if (y)
        {
            return 1;
        }
        else
        {
            return 2;
        }
    }
    else
    {
        return 3;
    }
}
       mov      eax, 3
       mov      ecx, 2
       mov      r10d, 1
       test     r8b, r8b
       cmovne   ecx, r10d
       test     dl, dl
       cmovne   eax, ecx
						;; size=28 bbWeight=1 PerfScore 1.75
G_M25408_IG03:  ;; offset=0x001C
       ret      

Anyway so yeah, while there will still be Then/Else cases with multiple blocks that have linear flow this PR says: "Such cases won't be amendable to if-conversion anyway so let's bail in CheckFlow()".
Previously such cases would have been considered valid flow only to bail out in CheckStmts immediately after (with #125072).

Comment thread src/coreclr/jit/ifconversion.cpp Outdated
Comment thread src/coreclr/jit/ifconversion.cpp
Comment thread src/coreclr/jit/ifconversion.cpp Outdated
* add 'assert(m_cond->OperIsCompare())'
* combine IfConvertCheckFlow + IfConvertCheckStmts into new IfConvertCheck
* get m_mainOper from m_thenOperation instead of flow check
@BoyBaykiller
Copy link
Copy Markdown
Contributor Author

@a74nh does everything look ok now or are there other changes you would like me to do?

Copy link
Copy Markdown
Contributor

@a74nh a74nh left a comment

Choose a reason for hiding this comment

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

I'm happy with this now.
Although, looks like formatting needs fixing.

@jakobbotsch jakobbotsch self-requested a review March 24, 2026 10:01
@jakobbotsch
Copy link
Copy Markdown
Member

@BoyBaykiller Can you address the formatting issue?

@BoyBaykiller BoyBaykiller reopened this Apr 9, 2026
@jakobbotsch
Copy link
Copy Markdown
Member

jakobbotsch commented Apr 9, 2026

@BoyBaykiller The jit-format job uploads a patch file when the formatting fails that you can apply locally to fix it. You can also run jit-format locally before you even commit: https://github.com/dotnet/jitutils/blob/main/doc/formatting.md

@BoyBaykiller
Copy link
Copy Markdown
Contributor Author

Thanks. I'll know that for the future. In this case vscode formatting did the job

Copy link
Copy Markdown
Member

@jakobbotsch jakobbotsch left a comment

Choose a reason for hiding this comment

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

Thanks!

@jakobbotsch jakobbotsch merged commit dea4756 into dotnet:main Apr 15, 2026
144 of 148 checks passed
EgorBo added a commit that referenced this pull request May 7, 2026
A Fuzzlyn-generated repro hits
`assert(!block->HasFlag(BBF_DONT_REMOVE))` during the JIT's *If
conversion* phase when the Then block is the start of a try region.
Bisected to dea4756 (#125347), which simplified `IfConvertCheckFlow` and
inadvertently dropped the `BasicBlock::sameEHRegion` check previously
performed by `IfConvertCheckInnerBlockFlow`. Without it, if-conversion
proceeds on a Then/Else block that begins an EH region, then trips the
assert when later removing it.

## Changes

- **`src/coreclr/jit/ifconversion.cpp`** — In
`OptIfConversionDsc::IfConvertCheckFlow`, bail when `falseBb` (Then) or
`trueBb` (Else, when present) is not in the same EH region as
`m_startBlock`. After merging `origin/main`, the Else guard uses the new
`HasElseBlock()` helper (which replaced the removed `m_doElseConversion`
member).
-
**`src/tests/JIT/Regression/JitBlue/Runtime_127446/Runtime_127446.cs`**
— Regression test using the original repro. The test source is added to
the shared merged `src/tests/JIT/Regression/Regression_ro_2.csproj`
(alongside other JitBlue regression tests); no per-test csproj or
environment variables are needed.

```cpp
if (falseBb->GetUniquePred(m_compiler) == nullptr)
{
    return false;
}

// The Then/Else blocks will be removed by if-conversion, so they must be in the same
// EH region as m_startBlock. Otherwise they may be the start of a try/handler region
// (and thus marked BBF_DONT_REMOVE), or removing them could leave dangling EH state.
if (!BasicBlock::sameEHRegion(falseBb, m_startBlock))
{
    return false;
}

m_finalBlock = HasElseBlock() ? trueBb->GetUniqueSucc() : trueBb;

if (HasElseBlock() && !BasicBlock::sameEHRegion(trueBb, m_startBlock))
{
    return false;
}
```

Verified the assert reproduces on the unfixed JIT and is gone with the
fix; the new test passes through the merged test runner
(`global::Runtime_127446.Runtime_127446.TestEntryPoint()` → Passed)
after merging the latest `origin/main`.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: EgorBo <523221+EgorBo@users.noreply.github.com>
Co-authored-by: jakobbotsch <7887810+jakobbotsch@users.noreply.github.com>
Co-authored-by: Egor Bogatov <egorbo@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants