JIT: fix switch peeling stealing the wrong operand of its compare - #132395
Open
EgorBo wants to merge 1 commit into
Open
JIT: fix switch peeling stealing the wrong operand of its compare#132395EgorBo wants to merge 1 commit into
EgorBo wants to merge 1 commit into
Conversation
|
Azure Pipelines: Successfully started running 8 pipeline(s). 8 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
Contributor
|
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch |
EgorBo
force-pushed
the
fix-switch-peel-operand-swap
branch
2 times, most recently
from
August 17, 2026 10:09
3eedfbe to
de3ab29
Compare
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes a JIT correctness issue in fgPeelSwitch where operand swapping during statement sequencing could cause the peeled switch to dispatch on the wrong value, and adds a new Arm64 LSE atomics “light-up” path via a new Lse intrinsic plus associated JIT/AOT plumbing. Also introduces a JIT regression test for the switch-peeling miscompile. (Not built/tested as part of this review.)
Changes:
- JIT: build the dominant-case compare fully before statement creation in
fgPeelSwitchto avoid operand swapping changing which operand is “stolen”. - Arm64 atomics: introduce
System.Runtime.Intrinsics.Arm.Lseand route Interlocked atomic ops throughif (Lse.IsSupported)where applicable; update JIT importer/lowering/LSRA/codegen and AOT/R2R metadata/tooling to support it. - Tests: add
Runtime_132370regression test reproducing the miscompile scenario.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/tests/JIT/Regression/JitBlue/Runtime_132370/Runtime_132370.cs | Adds regression coverage for the switch-peeling/unroll miscompile. |
| src/libraries/System.Private.CoreLib/src/System/Threading/Interlocked.cs | Adds Arm64 Lse.IsSupported fast-paths for And/Or on word-sized integers. |
| src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Arm/Lse.cs | Introduces new internal Arm64 LSE intrinsic surface used by Interlocked. |
| src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems | Includes the new Lse.cs source file in CoreLib builds. |
| src/coreclr/tools/Common/JitInterface/ThunkGenerator/InstructionSetDesc.txt | Names the ARM64 atomics instruction set implementation type as Lse. |
| src/coreclr/tools/Common/JitInterface/CorInfoInstructionSet.cs | Maps System.Runtime.Intrinsics.Arm.Lse to ARM64_Atomics and roots the type. |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunHardwareIntrinsicRootProvider.cs | Avoids rooting generic intrinsic method definitions for R2R compilation. |
| src/coreclr/System.Private.CoreLib/src/System/Threading/Interlocked.CoreCLR.cs | Adds Arm64 Lse.IsSupported light-up for key atomic Interlocked APIs (CoreCLR flavor). |
| src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Interlocked.cs | Adds Arm64 Lse.IsSupported light-up for key atomic Interlocked APIs (NativeAOT flavor). |
| src/coreclr/jit/lsraarm64.cpp | Uses per-node atomics implementation choice to adjust internal regs / lifetimes. |
| src/coreclr/jit/lowerarmarch.cpp | Disables contained immediates for atomic ops when emitting LSE forms. |
| src/coreclr/jit/jitconfigvalues.h | Adds Arm64 stress config for exercising managed atomics light-up. |
| src/coreclr/jit/importercalls.cpp | Leaves select Interlocked calls unexpanded in the “dynamic” atomics mode to enable managed light-up. |
| src/coreclr/jit/hwintrinsiclistarm64.h | Adds Arm64 Atomics intrinsic definitions (CompareAndSwap/LoadAdd/LoadClear/LoadSet/Swap). |
| src/coreclr/jit/hwintrinsicarm64.cpp | Maps Atomics intrinsics to existing atomic IR nodes and enforces type constraints. |
| src/coreclr/jit/hwintrinsic.cpp | Registers Atomics intrinsic range so they’re recognized as a real ISA group. |
| src/coreclr/jit/gentree.h | Adds GTF_ATOMIC_LSE flag to distinguish LSE-vs-LL/SC expansion. |
| src/coreclr/jit/gentree.cpp | Prevents CSE/merging of atomic nodes that differ by GTF_ATOMIC_LSE. |
| src/coreclr/jit/fgopt.cpp | Fixes fgPeelSwitch sequencing so the peeled compare steals the correct operand. |
| src/coreclr/jit/compiler.h | Introduces AtomicsImpl + per-node selection helper for atomic expansion strategy. |
| src/coreclr/jit/compiler.cpp | Implements cached atomics expansion mode selection (Lse/LlSc/Dynamic). |
| src/coreclr/jit/codegenarm64.cpp | Uses per-node atomics expansion decision to emit LSE instructions vs LL/SC loops. |
fgPeelSwitch creates "EQ(switchValue, dominantCase)" for the peeled check, puts
it into a statement and only then reattaches the switch value to the switch via
fgMakeMultiUse(&compare->gtOp1).
Creating the statement sequences the tree, and gtSetEvalOrder is free to swap the
operands of a compare - it does exactly that when op1 is a constant ("encourage
constants to be evaluated last"). The switch value is a constant whenever the
loop containing the switch was unrolled, so the switch ended up dispatching on
the dominant case constant instead of on its own value: every unrolled copy of
the switch jumped to the case 0 target, silently producing wrong results.
Build the compare completely (including the multi-use and the flag updates)
before handing it to fgNewStmtFromTree.
Fixes dotnet#132370
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: bd309226-4fb2-49aa-8586-b3dad59f3a36
EgorBo
force-pushed
the
fix-switch-peel-operand-swap
branch
from
August 17, 2026 10:28
de3ab29 to
dc550f6
Compare
This was referenced Aug 17, 2026
Open
Member
Author
|
PTAL, user-reported bug, small fix, no diffs @jakobbotsch @dotnet/jit-contrib |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
fgPeelSwitchreattached the switch value viafgMakeMultiUse(&compare->gtOp1)after the compare was put into a statement. Sequencing runsgtSetEvalOrder, which swaps a compare's operands when op1 is a constant, so the switch got the dominant case constant instead of the switch value.The switch value is a constant when the loop around the switch is unrolled, so each unrolled copy dispatched on case 0 -> wrong results.
Fix: build the compare fully before creating the statement.
No asm diffs (benchmarks.run, libraries.pmi).
Fixes #132370