Skip to content

JIT: don't use a jump-threaded block's sharpened predicate for dominator-based inference - #132281

Merged
EgorBo merged 1 commit into
dotnet:mainfrom
EgorBo:fix-130700-rbo-stale-predicate
Aug 14, 2026
Merged

EgorBo merged 1 commit into
dotnet:mainfrom
EgorBo:fix-130700-rbo-stale-predicate

Conversation

@EgorBo

@EgorBo EgorBo commented Aug 13, 2026 •

Copy link
Copy Markdown
Member

Fixes #130700 bug

When RBO jump threads through a block, it reroutes some of the block's preds directly to the block's successors. If the block is left with a single (ambiguous) pred, optJumpThreadCore sharpens the block's predicate VN to the value flowing in from that pred. The preds that were rerouted, however, were classified against the old VN, so the sharpened predicate does not hold on the paths that now bypass the block.

Dominator info is not updated as we thread, so the bypassed block still looks like a dominator of its successors, and optRedundantBranch could use its sharpened predicate to fold a branch in a block that is also reachable via the rerouted edges. In the reported case this removed the null check on an isinst result:

       mov      r14, rbx                       ; r14 = obj (non-null)
       cmp      qword ptr [r14], <MergeHierarchy MT>
       jne      SHORT G_M49973_IG17
       xor      r14, r14                       ; obj is MergeHierarchy => (obj as MergeFile) == null
       mov      dword ptr [rbp+0x20], 0        ; MemberType = MergeFile  <-- wrong arm, null check gone
       mov      rbx, gword ptr [r14+0x08]      ; <-- NullReferenceException

Fix: flag such blocks with BBF_STALE_PREDICATE and skip them in the two dominator-based inference walks in this phase.

No SPMI asm diffs

…tor-based inference

When RBO jump threads through a block, it reroutes some of the block's preds
directly to the block's successors. If the block is left with a single
(ambiguous) pred, optJumpThreadCore sharpens the block's predicate VN to the
value flowing in from that pred. The preds that were rerouted, however, were
classified against the *old* VN, so the sharpened predicate does not hold on
the paths that now bypass the block.

Dominator info is not updated as we thread, so the bypassed block still looks
like a dominator of its successors, and optRedundantBranch could use its
sharpened predicate to fold a branch in a block that is also reachable via the
rerouted edges. In the reported case this removed the null check on an isinst
result, so an "is MergeFile" arm was entered with a null value.

Flag such blocks with BBF_STALE_PREDICATE and skip them in the two
dominator-based inference walks in this phase.

Fixes dotnet#130700

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 049f4632-06bf-47b3-810f-437d77d5938d
Copilot AI lite review requested due to automatic review settings August 13, 2026 16:04
@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 13, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 5 pipeline(s).
11 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@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 JIT miscompilation hazard in Redundant Branch Optimization (RBO) where jump-threading can “sharpen” a block’s predicate VN in a way that no longer holds on all paths (due to rerouted edges), yet dominator-based inference can still incorrectly treat it as a dominating predicate.

Changes:

  • Introduces a new BasicBlock flag (BBF_STALE_PREDICATE) to mark predicates that became path-specific after jump threading.
  • Updates RBO dominator-based inference to ignore (or bail out on) dominators marked with BBF_STALE_PREDICATE, and clears the flag at the end of the phase when changes occur.
  • Adds a regression test (Runtime_130700) configured to reproduce the issue under tiered compilation + PGO.

Reviewed changes

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

Show a summary per file
File Description
src/coreclr/jit/redundantbranchopts.cpp Marks sharpened-predicate blocks as stale and prevents dominator-based inference from using those stale predicates during RBO.
src/coreclr/jit/block.h Adds the BBF_STALE_PREDICATE flag definition.
src/coreclr/jit/block.cpp Adds display support for the new block flag in debug dumps.
src/tests/JIT/Regression/JitBlue/Runtime_130700/Runtime_130700.cs New regression test exercising the problematic control-flow + inference pattern.
src/tests/JIT/Regression/JitBlue/Runtime_130700/Runtime_130700.csproj Test project wiring, including process isolation and enabling tiered compilation + PGO via environment variables.

@EgorBo

EgorBo commented Aug 13, 2026

Copy link
Copy Markdown
Member Author

@AndyAyersMS Could you please take a look? jump threading bug. I tried to make the fix surgical for a potential backport. No diffs cc @dotnet/jit-contrib

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

We could probably scrutinize PHIs vs preds and reach the same conclusion, but a flag seems reasonable too.

@EgorBo

EgorBo commented Aug 14, 2026

Copy link
Copy Markdown
Member Author

/ba-g unrelated failures

@EgorBo
EgorBo enabled auto-merge (squash) August 14, 2026 18:36
@EgorBo

EgorBo commented Aug 14, 2026

Copy link
Copy Markdown
Member Author

/ba-g unrelated

@EgorBo
EgorBo merged commit 7d2ff70 into dotnet:main Aug 14, 2026
136 of 140 checks passed
@dotnet-milestone-bot dotnet-milestone-bot Bot added this to the 12.0-preview1 milestone Aug 17, 2026
JulieLeeMSFT pushed a commit that referenced this pull request Sep 1, 2026
…cate for dominator-based inference (#132629)

Backport of #132281 to release/10.0

## Customer Impact

- [x] Customer reported
- [ ] Found internally

Reported in #130700. Silent bad codegen leading to a
`NullReferenceException` in optimized Release runs only;
`DOTNET_TieredPGO=0` or `[MethodImpl(MethodImplOptions.NoOptimization)]`
worked around it.

When RBO jump threads through a block, it reroutes some of the block's
preds directly to the block's successors. If the block is left with a
single (ambiguous) pred, `optJumpThreadCore` sharpens the block's
predicate VN to the value flowing in from that pred. The rerouted preds
were classified against the *old* VN, so the sharpened predicate does
not hold on the paths that now bypass the block. Dominator info is not
updated as we thread, so the bypassed block still looks like a dominator
of its successors, and `optRedundantBranch` could use its sharpened
predicate to fold a branch in a block also reachable via the rerouted
edges. In the reported case this removed the null check on an `isinst`
result:

```asm
       mov      r14, rbx                       ; r14 = obj (non-null)
       cmp      qword ptr [r14], <MergeHierarchy MT>
       jne      SHORT G_M49973_IG17
       xor      r14, r14                       ; obj is MergeHierarchy => (obj as MergeFile) == null
       mov      dword ptr [rbp+0x20], 0        ; MemberType = MergeFile  <-- wrong arm, null check gone
       mov      rbx, gword ptr [r14+0x08]      ; <-- NullReferenceException
```

Fix: flag such blocks with `BBF_STALE_PREDICATE` and skip them in
dominator-based inference in this phase.

## Regression

- [ ] Yes
- [x] No

Long-standing issue in RBO jump threading, not a .NET 10 regression.

## Testing

Regression test `Runtime_130700` from the original PR is included. The
original PR reported no SPMI asm diffs.

## Risk

Low. The change only makes RBO decline to infer from a block whose
predicate it had already narrowed to a single path, so it can only
remove unsound optimizations.

## Notes on the backport

Not a clean cherry-pick. `main` applies the new `BBF_STALE_PREDICATE`
check in two dominator-based inference walks; release/10.0 has only one,
since `optRedundantDominatingBranch` was added to `main` after the 10.0
branch. That hunk is therefore omitted, and the flag uses
`MAKE_BBFLAG(37)` (the next free bit on this branch) instead of 39. The
remaining three hunks — setting the flag in `optJumpThreadCore`,
checking it in `optRedundantBranch`, and clearing it at the end of
`optRedundantBranches` — are applied verbatim.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ec11ff08-3e62-4386-a86e-51f868acffe1
@github-actions github-actions Bot locked and limited conversation to collaborators Sep 18, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

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.

Dynamic PGO miscompilation: elided null check produces NullReferenceException

3 participants