[llvm] [SimplifyCFG] Improve reduceSwitchRange to avoid unnecessary subtractions (PR #198374)

Drew Kersnar via llvm-commits llvm-commits at lists.llvm.org
Mon May 18 12:01:30 PDT 2026


https://github.com/dakersnar created https://github.com/llvm/llvm-project/pull/198374

Dense switch cases can sometimes be achieved with only a shift, avoiding a subtract. Change reduceSwitchRange to check for such cases before falling back to using a subtract.

The changes to test9 demonstrates that for the purpose of lookup-table lowering (triggered by the switch-to-lookup flag), the switch range still ends up normalized to start at 0, the subtraction just happens after the fshl.

test10 demonstrates where this change is useful, resulting in a dense switch case with one less subtract.

test11 demonstrates cases where the subtract is still needed in order to form a dense switch case.

>From 19bb66626f27847d5591f541ca75c98bf3b04970 Mon Sep 17 00:00:00 2001
From: Drew Kersnar <dkersnar at nvidia.com>
Date: Wed, 13 May 2026 16:24:24 +0000
Subject: [PATCH 1/2] Add new test without changes

---
 .../Transforms/SimplifyCFG/rangereduce.ll     | 104 ++++++++++++++++++
 1 file changed, 104 insertions(+)

diff --git a/llvm/test/Transforms/SimplifyCFG/rangereduce.ll b/llvm/test/Transforms/SimplifyCFG/rangereduce.ll
index 5444a730bab1c..690543faf5644 100644
--- a/llvm/test/Transforms/SimplifyCFG/rangereduce.ll
+++ b/llvm/test/Transforms/SimplifyCFG/rangereduce.ll
@@ -317,6 +317,110 @@ three:
   ret i32 99783
 }
 
+; Base=0 is profitable here: shifting out the common low zero bits produces a
+; dense range without subtracting the local minimum first.
+define void @test10(i32 %a) {
+; CHECK-LABEL: @test10(
+; CHECK-NEXT:    [[TMP1:%.*]] = sub i32 [[A:%.*]], 32
+; CHECK-NEXT:    [[TMP2:%.*]] = call i32 @llvm.fshl.i32(i32 [[TMP1]], i32 [[TMP1]], i32 27)
+; CHECK-NEXT:    switch i32 [[TMP2]], label [[COMMON_RET:%.*]] [
+; CHECK-NEXT:      i32 0, label [[ONE:%.*]]
+; CHECK-NEXT:      i32 1, label [[TWO:%.*]]
+; CHECK-NEXT:      i32 2, label [[THREE:%.*]]
+; CHECK-NEXT:      i32 3, label [[FOUR:%.*]]
+; CHECK-NEXT:    ]
+; CHECK:       common.ret:
+; CHECK-NEXT:    ret void
+; CHECK:       one:
+; CHECK-NEXT:    call void @side_effect(i32 32)
+; CHECK-NEXT:    br label [[COMMON_RET]]
+; CHECK:       two:
+; CHECK-NEXT:    call void @side_effect(i32 64)
+; CHECK-NEXT:    br label [[COMMON_RET]]
+; CHECK:       three:
+; CHECK-NEXT:    call void @side_effect(i32 96)
+; CHECK-NEXT:    br label [[COMMON_RET]]
+; CHECK:       four:
+; CHECK-NEXT:    call void @side_effect(i32 128)
+; CHECK-NEXT:    br label [[COMMON_RET]]
+;
+  switch i32 %a, label %def [
+  i32 32, label %one
+  i32 64, label %two
+  i32 96, label %three
+  i32 128, label %four
+  ]
+
+def:
+  ret void
+
+one:
+  call void @side_effect(i32 32)
+  ret void
+two:
+  call void @side_effect(i32 64)
+  ret void
+three:
+  call void @side_effect(i32 96)
+  ret void
+four:
+  call void @side_effect(i32 128)
+  ret void
+}
+
+; Base=0 is not dense after shifting, so this should still subtract the local
+; minimum before reducing the range.
+define void @test11(i32 %a) {
+; CHECK-LABEL: @test11(
+; CHECK-NEXT:    [[TMP1:%.*]] = sub i32 [[A:%.*]], 40
+; CHECK-NEXT:    [[TMP2:%.*]] = call i32 @llvm.fshl.i32(i32 [[TMP1]], i32 [[TMP1]], i32 27)
+; CHECK-NEXT:    switch i32 [[TMP2]], label [[COMMON_RET:%.*]] [
+; CHECK-NEXT:      i32 0, label [[ONE:%.*]]
+; CHECK-NEXT:      i32 1, label [[TWO:%.*]]
+; CHECK-NEXT:      i32 2, label [[THREE:%.*]]
+; CHECK-NEXT:      i32 3, label [[FOUR:%.*]]
+; CHECK-NEXT:    ]
+; CHECK:       common.ret:
+; CHECK-NEXT:    ret void
+; CHECK:       one:
+; CHECK-NEXT:    call void @side_effect(i32 40)
+; CHECK-NEXT:    br label [[COMMON_RET]]
+; CHECK:       two:
+; CHECK-NEXT:    call void @side_effect(i32 72)
+; CHECK-NEXT:    br label [[COMMON_RET]]
+; CHECK:       three:
+; CHECK-NEXT:    call void @side_effect(i32 104)
+; CHECK-NEXT:    br label [[COMMON_RET]]
+; CHECK:       four:
+; CHECK-NEXT:    call void @side_effect(i32 136)
+; CHECK-NEXT:    br label [[COMMON_RET]]
+;
+  switch i32 %a, label %def [
+  i32 40, label %one
+  i32 72, label %two
+  i32 104, label %three
+  i32 136, label %four
+  ]
+
+def:
+  ret void
+
+one:
+  call void @side_effect(i32 40)
+  ret void
+two:
+  call void @side_effect(i32 72)
+  ret void
+three:
+  call void @side_effect(i32 104)
+  ret void
+four:
+  call void @side_effect(i32 136)
+  ret void
+}
+
+declare void @side_effect(i32)
+
 !0 = !{!"function_entry_count", i32 100}
 !1 = !{!"branch_weights", i32 5, i32 7, i32 11, i32 13, i32 17}
 ;.

>From 256f866074e3a217dcbcda1078c39a826f538f5d Mon Sep 17 00:00:00 2001
From: Drew Kersnar <dkersnar at nvidia.com>
Date: Wed, 13 May 2026 17:18:00 +0000
Subject: [PATCH 2/2] [SimplifyCFG] Improve reduceSwitchRange to avoid
 unnecessary subtractions

---
 llvm/lib/Transforms/Utils/SimplifyCFG.cpp     | 57 ++++++++++---------
 .../Transforms/SimplifyCFG/rangereduce.ll     | 21 ++++---
 2 files changed, 41 insertions(+), 37 deletions(-)

diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index f1d47abe79365..2ef57437c1de8 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -7582,31 +7582,35 @@ static bool reduceSwitchRange(SwitchInst *SI, IRBuilder<> &Builder,
   if (isSwitchDense(Values))
     return false;
 
-  // First, transform the values such that they start at zero and ascend.
+  auto TryBase = [&](int64_t CandidateBase, unsigned &CandidateShift) {
+    SmallVector<int64_t, 4> ReducedValues;
+    ReducedValues.reserve(Values.size());
+    uint64_t ReducedBits = 0;
+    for (int64_t V : Values) {
+      uint64_t Reduced = (uint64_t)V - (uint64_t)CandidateBase;
+      ReducedBits |= Reduced;
+      ReducedValues.push_back((int64_t)Reduced);
+    }
+
+    CandidateShift = llvm::countr_zero(ReducedBits);
+    if (CandidateShift >= 64)
+      return false;
+    if (CandidateShift > 0)
+      for (auto &V : ReducedValues)
+        V = (int64_t)((uint64_t)V >> CandidateShift);
+
+    return isSwitchDense(ReducedValues);
+  };
+
+  // Choose the base to subtract from the switch condition. The default is the
+  // local minimum, but prefer Base=0 when the case values are still dense
+  // after shifting out their common low zero bits without subtracting a base.
+  // This avoids creating an unnecessary `(condition - local_min)` expression.
   int64_t Base = Values[0];
-  for (auto &V : Values)
-    V -= (uint64_t)(Base);
-
-  // Now we have signed numbers that have been shifted so that, given enough
-  // precision, there are no negative values. Since the rest of the transform
-  // is bitwise only, we switch now to an unsigned representation.
-
-  // This transform can be done speculatively because it is so cheap - it
-  // results in a single rotate operation being inserted.
-
-  // countTrailingZeros(0) returns 64. As Values is guaranteed to have more than
-  // one element and LLVM disallows duplicate cases, Shift is guaranteed to be
-  // less than 64.
-  unsigned Shift = 64;
-  for (auto &V : Values)
-    Shift = std::min(Shift, (unsigned)llvm::countr_zero((uint64_t)V));
-  assert(Shift < 64);
-  if (Shift > 0)
-    for (auto &V : Values)
-      V = (int64_t)((uint64_t)V >> Shift);
-
-  if (!isSwitchDense(Values))
-    // Transform didn't create a dense switch.
+  unsigned Shift = 0;
+  if (Base >= 0 && TryBase(0, Shift))
+    Base = 0;
+  else if (!TryBase(Base, Shift))
     return false;
 
   // The obvious transform is to shift the switch condition right and emit a
@@ -7621,8 +7625,9 @@ static bool reduceSwitchRange(SwitchInst *SI, IRBuilder<> &Builder,
 
   auto *Ty = cast<IntegerType>(SI->getCondition()->getType());
   Builder.SetInsertPoint(SI);
-  Value *Sub =
-      Builder.CreateSub(SI->getCondition(), ConstantInt::getSigned(Ty, Base));
+  Value *Sub = SI->getCondition();
+  if (Base != 0)
+    Sub = Builder.CreateSub(Sub, ConstantInt::getSigned(Ty, Base));
   Value *Rot = Builder.CreateIntrinsic(
       Ty, Intrinsic::fshl,
       {Sub, Sub, ConstantInt::get(Ty, Ty->getBitWidth() - Shift)});
diff --git a/llvm/test/Transforms/SimplifyCFG/rangereduce.ll b/llvm/test/Transforms/SimplifyCFG/rangereduce.ll
index 690543faf5644..5358e6ec68ddb 100644
--- a/llvm/test/Transforms/SimplifyCFG/rangereduce.ll
+++ b/llvm/test/Transforms/SimplifyCFG/rangereduce.ll
@@ -286,12 +286,12 @@ three:
 
 define i32 @test9(i32 %a) {
 ; CHECK-LABEL: @test9(
-; CHECK-NEXT:    [[TMP1:%.*]] = sub i32 [[A:%.*]], 6
-; CHECK-NEXT:    [[TMP2:%.*]] = call i32 @llvm.fshl.i32(i32 [[TMP1]], i32 [[TMP1]], i32 31)
-; CHECK-NEXT:    [[TMP3:%.*]] = icmp ult i32 [[TMP2]], 8
+; CHECK-NEXT:    [[TMP2:%.*]] = call i32 @llvm.fshl.i32(i32 [[TMP1:%.*]], i32 [[TMP1]], i32 31)
+; CHECK-NEXT:    [[SWITCH_TABLEIDX:%.*]] = sub i32 [[TMP2]], 3
+; CHECK-NEXT:    [[TMP3:%.*]] = icmp ult i32 [[SWITCH_TABLEIDX]], 8
 ; CHECK-NEXT:    br i1 [[TMP3]], label [[SWITCH_LOOKUP:%.*]], label [[COMMON_RET:%.*]]
 ; CHECK:       switch.lookup:
-; CHECK-NEXT:    [[TMP4:%.*]] = zext nneg i32 [[TMP2]] to i64
+; CHECK-NEXT:    [[TMP4:%.*]] = zext nneg i32 [[SWITCH_TABLEIDX]] to i64
 ; CHECK-NEXT:    [[SWITCH_GEP:%.*]] = getelementptr inbounds [8 x i32], ptr @switch.table.test9, i64 0, i64 [[TMP4]]
 ; CHECK-NEXT:    [[SWITCH_LOAD:%.*]] = load i32, ptr [[SWITCH_GEP]], align 4
 ; CHECK-NEXT:    br label [[COMMON_RET]]
@@ -321,13 +321,12 @@ three:
 ; dense range without subtracting the local minimum first.
 define void @test10(i32 %a) {
 ; CHECK-LABEL: @test10(
-; CHECK-NEXT:    [[TMP1:%.*]] = sub i32 [[A:%.*]], 32
-; CHECK-NEXT:    [[TMP2:%.*]] = call i32 @llvm.fshl.i32(i32 [[TMP1]], i32 [[TMP1]], i32 27)
-; CHECK-NEXT:    switch i32 [[TMP2]], label [[COMMON_RET:%.*]] [
-; CHECK-NEXT:      i32 0, label [[ONE:%.*]]
-; CHECK-NEXT:      i32 1, label [[TWO:%.*]]
-; CHECK-NEXT:      i32 2, label [[THREE:%.*]]
-; CHECK-NEXT:      i32 3, label [[FOUR:%.*]]
+; CHECK-NEXT:    [[TMP1:%.*]] = call i32 @llvm.fshl.i32(i32 [[A:%.*]], i32 [[A]], i32 27)
+; CHECK-NEXT:    switch i32 [[TMP1]], label [[COMMON_RET:%.*]] [
+; CHECK-NEXT:      i32 1, label [[ONE:%.*]]
+; CHECK-NEXT:      i32 2, label [[TWO:%.*]]
+; CHECK-NEXT:      i32 3, label [[THREE:%.*]]
+; CHECK-NEXT:      i32 4, label [[FOUR:%.*]]
 ; CHECK-NEXT:    ]
 ; CHECK:       common.ret:
 ; CHECK-NEXT:    ret void



More information about the llvm-commits mailing list