Skip to content

JIT: fix bit-test switch lowering when the bit table is inverted - #131736

Merged
EgorBo merged 2 commits into
dotnet:mainfrom
EgorBo:fix-switch-bittest-invert
Aug 4, 2026
Merged

JIT: fix bit-test switch lowering when the bit table is inverted#131736
EgorBo merged 2 commits into
dotnet:mainfrom
EgorBo:fix-switch-bittest-invert

Conversation

@EgorBo

@EgorBo EgorBo commented Aug 3, 2026

Copy link
Copy Markdown
Member

Fixes #131716

TryLowerSwitchToBitTest inverts the bit table on xarch when its upper 32 bits are all set, so that it still fits in a 32 bit immediate, and swaps the two targets. The swap only updated bbCase0/bbCase1, but everything after it consumes case0Edge/case1Edge - so SetCond wired the JCC to the wrong successor, and the dup-count fixup decremented bbRefs on the wrong blocks. Regressed by #116933, which replaced the fgRemoveAllRefPreds/fgAddRefPred pair (which re-derived the edges from the swapped blocks) with in-place dup-count adjustment.

Fix: swap the edges and derive the blocks afterwards.

The inversion can only trigger with exactly 64 bit table entries — below that, bits above bitCount are zero, so ~bitTable exceeds UINT32_MAX. That is why only 64-case switches were affected.

Before (64 cases, indices 1-3 -> 111):

mov      eax, 14
bt       rax, rcx
jae      SHORT G_M14444_IG06   ; wrong: bit set falls through to 222

After:

mov      eax, 14
bt       rax, rcx
jb       SHORT G_M14444_IG06   ; bit set -> 111

No SPMI asm diffs on benchmarks.run, libraries.pmi and libraries.crossgen2 (~660K contexts).

TryLowerSwitchToBitTest inverts the bit table on xarch when its upper 32
bits are all set, so that it still fits in a 32 bit immediate, and swaps
the two targets. The swap only updated bbCase0/bbCase1, but the code that
follows consumes case0Edge/case1Edge, so the JCC was wired to the wrong
successor and the block ref counts were decremented on the wrong blocks.

Swap the edges instead and derive the blocks afterwards.

The inversion can only trigger with exactly 64 bit table entries: below
that, the bits above bitCount are zero, so ~bitTable exceeds UINT32_MAX.

Fixes dotnet#131716

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 3c492a8b-6607-48dc-8a2e-98128e3197b0
Copilot AI review requested due to automatic review settings August 3, 2026 12:49
@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 Aug 3, 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.

Copilot AI 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

Fixes a correctness issue in CoreCLR JIT switch-to-bit-test lowering on xarch when the bit table is inverted to fit a 32-bit immediate, ensuring the conditional branch and ref-count adjustments use the correct successor edges after inversion.

Changes:

  • Update TryLowerSwitchToBitTest to swap case0Edge/case1Edge (not just destination blocks) when inverting the bit table, and derive bbCase0/bbCase1 after the swap.
  • Add a JIT regression test covering the “exactly 64 cases + inverted bit table” scenario and a non-inverted control case.
  • Include the new regression test in the Regression_ro_2 test project.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
src/coreclr/jit/lower.cpp Fixes edge swapping during inverted bit-table lowering so SetCond and dup-count fixups target the correct successors.
src/tests/JIT/Regression/JitBlue/Runtime_131716/Runtime_131716.cs Adds a regression test that reproduces the inverted-bit-table 64-case switch shape and validates correct dispatch.
src/tests/JIT/Regression/Regression_ro_2.csproj Adds the new regression test source file to the project build.

…vert

# Conflicts:
#	src/tests/JIT/Regression/Regression_ro_2.csproj
Copilot AI review requested due to automatic review settings August 3, 2026 13:04

Copilot AI 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

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

@EgorBo
EgorBo merged commit 891f0e6 into dotnet:main Aug 4, 2026
138 of 140 checks passed
@dotnet-milestone-bot dotnet-milestone-bot Bot added this to the 11.0-rc1 milestone Aug 4, 2026
JulieLeeMSFT pushed a commit that referenced this pull request Aug 4, 2026
…s inverted (#131781)

Backport of #131736 to release/10.0


## Customer Impact

- [x] Customer reported
- [ ] Found internally

Reported in #131716. Wrong code on x64: a `switch` with exactly 64 cases
and two distinct targets branches to the *opposite* target.
`TryLowerSwitchToBitTest` inverts the bit table when its upper 32 bits
are all set (so the table still fits a 32-bit immediate) and swaps the
two targets, but the swap only updated `bbCase0`/`bbCase1` while the
code after it consumes `case0Edge`/`case1Edge`. So `SetCond` wired the
`JCC` to the wrong successor and the dup-count fixup decremented
`bbRefs` on the wrong blocks.

This shape is very easy to hit from Roslyn's `async` state-machine
dispatch: an `async IAsyncEnumerable<T>`/`async` method with 61
awaits+yields produces exactly a 64-entry, two-target jump table. The
reporter saw the iterator body re-entered millions of times (hang) and
frameless `NullReferenceException`s on captured variables. Reproduces at
Tier0, MinOpts and fully optimized code.

## Regression

- [x] Yes
- [ ] No

Introduced in .NET 10 by #116933, which replaced the
`fgRemoveAllRefPreds`/`fgAddRefPred` pair (that re-derived the edges
from the swapped blocks) with in-place dup-count adjustment, leaving the
edge swap behind.

## Testing

Added regression test `Runtime_131716`, covering both the inverted
(64-case) and non-inverted bit table shapes. Fails without the fix,
passes with it. The original PR also ran SPMI: no asm diffs on
benchmarks.run, libraries.pmi and libraries.crossgen2 (~660K contexts).

## Risk

Low.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 3c492a8b-6607-48dc-8a2e-98128e3197b0
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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JIT x64: wrong code for async-iterator state dispatch when the switch has exactly 64 cases — bit-test lowering picks the wrong branch target

3 participants