[llvm] [SelectionDAG] Avoid redundant comparisons in bit-test switch lowering (PR #222626)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Sep 10 05:28:12 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-backend-aarch64
Author: Shreeyash Pandey (bojle)
<details>
<summary>Changes</summary>
Fixes https://github.com/llvm/llvm-project/issues/155953
Previously, SelectionDAG switch lowering used the same BitTestBlock::Default/fallthrough destination for both bit-test misses and failures of the bit-test’s numeric range check. This could produce redundant comparisons, such as testing x == 4 after x > 12 had already been established. The patch creates stable MBB entries for all clusters and records a separate RangeCheckDefault, allowing a range-check failure to jump directly to the first safe upper out-of-range cluster, or to the switch default, while bit-test misses retain the normal fallthrough chain. This is enabled only for zero-based bit-test ranges whose remaining clusters form an in-range prefix followed by an upper out-of-range suffix. In the reproducer, cases 7–12 form a bit-test with First = 0, so its numeric range is [0,12] (values 0–6 are holes), [4,4] is still in range, and a later cluster such as [1897,1897] is above the range and can be targeted directly.
---
Full diff: https://github.com/llvm/llvm-project/pull/222626.diff
3 Files Affected:
- (modified) llvm/include/llvm/CodeGen/SwitchLoweringUtils.h (+2-1)
- (modified) llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp (+76-15)
- (added) llvm/test/CodeGen/AArch64/switch-bittest-range-check.ll (+171)
``````````diff
diff --git a/llvm/include/llvm/CodeGen/SwitchLoweringUtils.h b/llvm/include/llvm/CodeGen/SwitchLoweringUtils.h
index 33fa71ae109ea..89f05f71d8adf 100644
--- a/llvm/include/llvm/CodeGen/SwitchLoweringUtils.h
+++ b/llvm/include/llvm/CodeGen/SwitchLoweringUtils.h
@@ -224,6 +224,7 @@ struct BitTestBlock {
bool ContiguousRange;
MachineBasicBlock *Parent;
MachineBasicBlock *Default;
+ MachineBasicBlock *RangeCheckDefault; // When outside [First, First + Range]
BitTestInfo Cases;
BranchProbability Prob;
BranchProbability DefaultProb;
@@ -234,7 +235,7 @@ struct BitTestBlock {
BitTestInfo C, BranchProbability Pr)
: First(std::move(F)), Range(std::move(R)), SValue(SV), Reg(Rg),
RegVT(RgVT), Emitted(E), ContiguousRange(CR), Parent(P), Default(D),
- Cases(std::move(C)), Prob(Pr) {}
+ RangeCheckDefault(nullptr), Cases(std::move(C)), Prob(Pr) {}
};
/// Return the range of values within a range.
diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
index f34f1c7e9c969..c89c701d9f58c 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
@@ -3352,10 +3352,12 @@ void SelectionDAGBuilder::visitBitTestHeader(BitTestBlock &B,
B.Reg = FuncInfo.CreateReg(B.RegVT);
SDValue CopyTo = DAG.getCopyToReg(getControlRoot(), dl, B.Reg, Sub);
- MachineBasicBlock* MBB = B.Cases[0].ThisBB;
+ MachineBasicBlock *MBB = B.Cases[0].ThisBB;
+ MachineBasicBlock *RangeCheckDefault =
+ B.RangeCheckDefault ? B.RangeCheckDefault : B.Default;
if (!B.FallthroughUnreachable)
- addSuccessorWithProb(SwitchBB, B.Default, B.DefaultProb);
+ addSuccessorWithProb(SwitchBB, RangeCheckDefault, B.DefaultProb);
addSuccessorWithProb(SwitchBB, MBB, B.Prob);
SwitchBB->normalizeSuccProbs();
@@ -3369,7 +3371,7 @@ void SelectionDAGBuilder::visitBitTestHeader(BitTestBlock &B,
ISD::SETUGT);
Root = DAG.getNode(ISD::BRCOND, dl, MVT::Other, Root, RangeCmp,
- DAG.getBasicBlock(B.Default));
+ DAG.getBasicBlock(RangeCheckDefault));
}
// Avoid emitting unnecessary branches to the next block.
@@ -12532,20 +12534,45 @@ void SelectionDAGBuilder::lowerWorkItem(SwitchWorkListItem W, Value *Cond,
for (CaseClusterIt I = W.FirstCluster; I <= W.LastCluster; ++I)
UnhandledProbs += I->Prob;
- MachineBasicBlock *CurMBB = W.MBB;
- for (CaseClusterIt I = W.FirstCluster, E = W.LastCluster; I <= E; ++I) {
+ // Insert bit-test blocks before the linear work-item continuation blocks so
+ // that pre-creating the latter preserves the existing layout.
+ for (CaseClusterIt I = W.FirstCluster; I <= W.LastCluster; ++I) {
+ if (I->Kind != CC_BitTests)
+ continue;
+ BitTestBlock *BTB = &SL->BitTestCases[I->BTCasesIndex];
+ for (BitTestCase &BTC : BTB->Cases)
+ CurMF->insert(BBI, BTC.ThisBB);
+ }
+
+ // Give every cluster a stable entry block. Besides making the normal
+ // fallthrough chain explicit, this lets a bit-test range failure jump over
+ // preceding clusters that cannot contain an out-of-range value.
+ SmallVector<MachineBasicBlock *, 8> ClusterMBBs;
+ ClusterMBBs.reserve(Size);
+ ClusterMBBs.push_back(W.MBB);
+ for (unsigned Index = 1; Index < Size; ++Index) {
+ MachineBasicBlock *EntryMBB =
+ CurMF->CreateMachineBasicBlock(W.MBB->getBasicBlock());
+ CurMF->insert(BBI, EntryMBB);
+ ClusterMBBs.push_back(EntryMBB);
+ }
+ if (Size > 1)
+ ExportFromCurrentBlock(Cond);
+
+ MachineBasicBlock *CurMBB = nullptr;
+ for (unsigned ClusterIndex = 0; ClusterIndex < Size; ++ClusterIndex) {
+ CaseClusterIt I = W.FirstCluster + ClusterIndex;
+ CurMBB = ClusterMBBs[ClusterIndex];
+
bool FallthroughUnreachable = false;
MachineBasicBlock *Fallthrough;
- if (I == W.LastCluster) {
+ if (ClusterIndex + 1 == Size) {
// For the last cluster, fall through to the default destination.
Fallthrough = DefaultMBB;
FallthroughUnreachable = isa<UnreachableInst>(
DefaultMBB->getBasicBlock()->getFirstNonPHIOrDbg());
} else {
- Fallthrough = CurMF->CreateMachineBasicBlock(CurMBB->getBasicBlock());
- CurMF->insert(BBI, Fallthrough);
- // Put Cond in a virtual register to make it available from the new blocks.
- ExportFromCurrentBlock(Cond);
+ Fallthrough = ClusterMBBs[ClusterIndex + 1];
}
UnhandledProbs -= I->Prob;
@@ -12615,13 +12642,48 @@ void SelectionDAGBuilder::lowerWorkItem(SwitchWorkListItem W, Value *Cond,
// FIXME: Optimize away range check based on pivot comparisons.
BitTestBlock *BTB = &SL->BitTestCases[I->BTCasesIndex];
- // The bit test blocks haven't been inserted yet; insert them here.
- for (BitTestCase &BTC : BTB->Cases)
- CurMF->insert(BBI, BTC.ThisBB);
+ // The bit-test blocks were inserted before the work-item entry
+ // blocks were created.
// Fill in fields of the BitTestBlock.
BTB->Parent = CurMBB;
BTB->Default = Fallthrough;
+ BTB->RangeCheckDefault = nullptr;
+
+ // In the zero-based form, a range failure is either below First or
+ // above First + Range. If the remaining clusters are laid out as an
+ // in-range prefix followed by an out-of-range suffix, the suffix has
+ // a stable entry block that can be used as the range-failure target.
+ // Otherwise retain the old combined continuation for now.
+ if (BTB->First.isZero()) {
+ APInt RangeEnd = BTB->First + BTB->Range;
+ unsigned FirstUpperOutOfRange = Size;
+ bool HasLowerOutOfRange = false;
+ bool HasInRangeAfterUpper = false;
+
+ for (unsigned JIndex = ClusterIndex + 1; JIndex < Size; ++JIndex) {
+ CaseClusterIt J = W.FirstCluster + JIndex;
+ bool IsBelowRange = J->High->getValue().slt(BTB->First);
+ bool IsAboveRange = J->Low->getValue().sgt(RangeEnd);
+ if (IsBelowRange) {
+ HasLowerOutOfRange = true;
+ break;
+ }
+ if (IsAboveRange) {
+ if (FirstUpperOutOfRange == Size)
+ FirstUpperOutOfRange = JIndex;
+ } else if (FirstUpperOutOfRange != Size) {
+ HasInRangeAfterUpper = true;
+ break;
+ }
+ }
+
+ if (!HasLowerOutOfRange && !HasInRangeAfterUpper) {
+ BTB->RangeCheckDefault = FirstUpperOutOfRange == Size
+ ? DefaultMBB
+ : ClusterMBBs[FirstUpperOutOfRange];
+ }
+ }
BTB->DefaultProb = UnhandledProbs;
// If the cases in bit test don't form a contiguous range, we evenly
@@ -12674,8 +12736,7 @@ void SelectionDAGBuilder::lowerWorkItem(SwitchWorkListItem W, Value *Cond,
break;
}
- }
- CurMBB = Fallthrough;
+ }
}
}
diff --git a/llvm/test/CodeGen/AArch64/switch-bittest-range-check.ll b/llvm/test/CodeGen/AArch64/switch-bittest-range-check.ll
new file mode 100644
index 0000000000000..0911fcadcde6e
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/switch-bittest-range-check.ll
@@ -0,0 +1,171 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
+; RUN: llc -mtriple=aarch64 -O2 -verify-machineinstrs < %s | FileCheck %s
+
+declare void @foo1(i32)
+declare void @foo2(i32)
+declare void @foo3(i32)
+declare void @foo4(i32)
+declare void @foo5(i32)
+
+define i32 @switch1(i32 %x) "no-jump-tables"="true" {
+; CHECK-LABEL: switch1:
+; CHECK: // %bb.0: // %entry
+; CHECK-NEXT: stp x30, x19, [sp, #-16]! // 16-byte Folded Spill
+; CHECK-NEXT: .cfi_def_cfa_offset 16
+; CHECK-NEXT: .cfi_offset w19, -8
+; CHECK-NEXT: .cfi_offset w30, -16
+; CHECK-NEXT: mov w19, w0
+; CHECK-NEXT: cmp w0, #12
+; CHECK-NEXT: mov w0, wzr
+; CHECK-NEXT: b.hi .LBB0_8
+; CHECK-NEXT: // %bb.1: // %entry
+; CHECK-NEXT: mov w9, #1152 // =0x480
+; CHECK-NEXT: lsr w9, w9, w19
+; CHECK-NEXT: tbnz w9, #0, .LBB0_6
+; CHECK-NEXT: // %bb.2: // %entry
+; CHECK-NEXT: mov w9, #2304 // =0x900
+; CHECK-NEXT: lsr w9, w9, w19
+; CHECK-NEXT: tbnz w9, #0, .LBB0_5
+; CHECK-NEXT: // %bb.3: // %entry
+; CHECK-NEXT: mov w9, #4608 // =0x1200
+; CHECK-NEXT: lsr w9, w9, w19
+; CHECK-NEXT: tbz w9, #0, .LBB0_9
+; CHECK-NEXT: // %bb.4: // %case9
+; CHECK-NEXT: mov w0, w19
+; CHECK-NEXT: bl foo3
+; CHECK-NEXT: b .LBB0_7
+; CHECK-NEXT: .LBB0_5: // %case8
+; CHECK-NEXT: mov w0, w19
+; CHECK-NEXT: bl foo2
+; CHECK-NEXT: b .LBB0_7
+; CHECK-NEXT: .LBB0_6: // %case7
+; CHECK-NEXT: mov w0, w19
+; CHECK-NEXT: bl foo1
+; CHECK-NEXT: .LBB0_7: // %return
+; CHECK-NEXT: mov w0, w19
+; CHECK-NEXT: .LBB0_8: // %return
+; CHECK-NEXT: ldp x30, x19, [sp], #16 // 16-byte Folded Reload
+; CHECK-NEXT: ret
+; CHECK-NEXT: .LBB0_9: // %entry
+; CHECK-NEXT: cmp w19, #4
+; CHECK-NEXT: b.ne .LBB0_8
+; CHECK-NEXT: // %bb.10: // %case4
+; CHECK-NEXT: mov w0, #4 // =0x4
+; CHECK-NEXT: bl foo4
+; CHECK-NEXT: mov w0, #4 // =0x4
+; CHECK-NEXT: ldp x30, x19, [sp], #16 // 16-byte Folded Reload
+; CHECK-NEXT: ret
+entry:
+ switch i32 %x, label %return [
+ i32 7, label %case7
+ i32 10, label %case7
+ i32 8, label %case8
+ i32 11, label %case8
+ i32 9, label %case9
+ i32 12, label %case9
+ i32 4, label %case4
+ ]
+case7:
+ tail call void @foo1(i32 %x)
+ br label %return
+case8:
+ tail call void @foo2(i32 %x)
+ br label %return
+case9:
+ tail call void @foo3(i32 %x)
+ br label %return
+case4:
+ tail call void @foo4(i32 4)
+ br label %return
+return:
+ %result = phi i32 [ 0, %entry ], [ 4, %case4 ], [ %x, %case9 ], [ %x, %case8 ], [ %x, %case7 ]
+ ret i32 %result
+}
+
+define i32 @switch2(i32 %x) "no-jump-tables"="true" {
+; CHECK-LABEL: switch2:
+; CHECK: // %bb.0: // %entry
+; CHECK-NEXT: stp x30, x19, [sp, #-16]! // 16-byte Folded Spill
+; CHECK-NEXT: .cfi_def_cfa_offset 16
+; CHECK-NEXT: .cfi_offset w19, -8
+; CHECK-NEXT: .cfi_offset w30, -16
+; CHECK-NEXT: cmp w0, #12
+; CHECK-NEXT: b.hi .LBB1_9
+; CHECK-NEXT: // %bb.1: // %entry
+; CHECK-NEXT: mov w8, #1152 // =0x480
+; CHECK-NEXT: lsr w8, w8, w0
+; CHECK-NEXT: tbnz w8, #0, .LBB1_6
+; CHECK-NEXT: // %bb.2: // %entry
+; CHECK-NEXT: mov w8, #2304 // =0x900
+; CHECK-NEXT: lsr w8, w8, w0
+; CHECK-NEXT: tbnz w8, #0, .LBB1_5
+; CHECK-NEXT: // %bb.3: // %entry
+; CHECK-NEXT: mov w8, #4608 // =0x1200
+; CHECK-NEXT: lsr w8, w8, w0
+; CHECK-NEXT: tbz w8, #0, .LBB1_8
+; CHECK-NEXT: // %bb.4: // %case9
+; CHECK-NEXT: mov w19, w0
+; CHECK-NEXT: bl foo3
+; CHECK-NEXT: b .LBB1_7
+; CHECK-NEXT: .LBB1_5: // %case8
+; CHECK-NEXT: mov w19, w0
+; CHECK-NEXT: bl foo2
+; CHECK-NEXT: b .LBB1_7
+; CHECK-NEXT: .LBB1_6: // %case7
+; CHECK-NEXT: mov w19, w0
+; CHECK-NEXT: bl foo1
+; CHECK-NEXT: .LBB1_7: // %case7
+; CHECK-NEXT: mov w0, w19
+; CHECK-NEXT: ldp x30, x19, [sp], #16 // 16-byte Folded Reload
+; CHECK-NEXT: ret
+; CHECK-NEXT: .LBB1_8: // %entry
+; CHECK-NEXT: cmp w0, #4
+; CHECK-NEXT: b.eq .LBB1_12
+; CHECK-NEXT: .LBB1_9: // %entry
+; CHECK-NEXT: cmp w0, #1897
+; CHECK-NEXT: b.ne .LBB1_11
+; CHECK-NEXT: // %bb.10: // %case1897
+; CHECK-NEXT: mov w0, #4 // =0x4
+; CHECK-NEXT: bl foo5
+; CHECK-NEXT: mov w0, #1897 // =0x769
+; CHECK-NEXT: ldp x30, x19, [sp], #16 // 16-byte Folded Reload
+; CHECK-NEXT: ret
+; CHECK-NEXT: .LBB1_11:
+; CHECK-NEXT: mov w0, wzr
+; CHECK-NEXT: ldp x30, x19, [sp], #16 // 16-byte Folded Reload
+; CHECK-NEXT: ret
+; CHECK-NEXT: .LBB1_12: // %case4
+; CHECK-NEXT: bl foo4
+; CHECK-NEXT: mov w0, #4 // =0x4
+; CHECK-NEXT: ldp x30, x19, [sp], #16 // 16-byte Folded Reload
+; CHECK-NEXT: ret
+entry:
+ switch i32 %x, label %return [
+ i32 7, label %case7
+ i32 10, label %case7
+ i32 8, label %case8
+ i32 11, label %case8
+ i32 9, label %case9
+ i32 12, label %case9
+ i32 4, label %case4
+ i32 1897, label %case1897
+ ]
+case7:
+ tail call void @foo1(i32 %x)
+ br label %return
+case8:
+ tail call void @foo2(i32 %x)
+ br label %return
+case9:
+ tail call void @foo3(i32 %x)
+ br label %return
+case4:
+ tail call void @foo4(i32 4)
+ br label %return
+case1897:
+ tail call void @foo5(i32 4)
+ br label %return
+return:
+ %result = phi i32 [ 0, %entry ], [ 4, %case4 ], [ 1897, %case1897 ], [ %x, %case9 ], [ %x, %case8 ], [ %x, %case7 ]
+ ret i32 %result
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/222626
More information about the llvm-commits
mailing list