[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