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

Drew Kersnar via llvm-commits llvm-commits at lists.llvm.org
Mon Jun 8 12:53:44 PDT 2026


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

>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/7] 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/7] [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

>From e84d0f0aefe2805497bd2e69447cfe6520917ef4 Mon Sep 17 00:00:00 2001
From: Drew Kersnar <dkersnar at nvidia.com>
Date: Wed, 20 May 2026 15:47:13 +0000
Subject: [PATCH 3/7] Reviewer feedback

---
 llvm/lib/Transforms/Utils/SimplifyCFG.cpp | 53 +++++++++++++++--------
 1 file changed, 35 insertions(+), 18 deletions(-)

diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index 2ef57437c1de8..5a82eb2470b2a 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -7582,7 +7582,13 @@ static bool reduceSwitchRange(SwitchInst *SI, IRBuilder<> &Builder,
   if (isSwitchDense(Values))
     return false;
 
-  auto TryBase = [&](int64_t CandidateBase, unsigned &CandidateShift) {
+  struct RangeReduction {
+    int64_t Base;
+    unsigned Shift;
+  };
+
+  auto TryGetRangeReductionForBase = [&](int64_t CandidateBase)
+      -> std::optional<RangeReduction> {
     SmallVector<int64_t, 4> ReducedValues;
     ReducedValues.reserve(Values.size());
     uint64_t ReducedBits = 0;
@@ -7592,25 +7598,34 @@ static bool reduceSwitchRange(SwitchInst *SI, IRBuilder<> &Builder,
       ReducedValues.push_back((int64_t)Reduced);
     }
 
-    CandidateShift = llvm::countr_zero(ReducedBits);
+    unsigned CandidateShift = llvm::countr_zero(ReducedBits);
     if (CandidateShift >= 64)
-      return false;
+      return std::nullopt;
     if (CandidateShift > 0)
       for (auto &V : ReducedValues)
         V = (int64_t)((uint64_t)V >> CandidateShift);
 
-    return isSwitchDense(ReducedValues);
+    if (!isSwitchDense(ReducedValues))
+      return std::nullopt;
+
+    return RangeReduction{CandidateBase, CandidateShift};
+  };
+
+  auto GetRangeReduction = [&]() -> std::optional<RangeReduction> {
+    // 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 LocalMin = Values[0];
+    if (LocalMin >= 0) {
+      if (auto Reduction = TryGetRangeReductionForBase(0))
+        return Reduction;
+    }
+    return TryGetRangeReductionForBase(LocalMin);
   };
 
-  // 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];
-  unsigned Shift = 0;
-  if (Base >= 0 && TryBase(0, Shift))
-    Base = 0;
-  else if (!TryBase(Base, Shift))
+  std::optional<RangeReduction> Reduction = GetRangeReduction();
+  if (!Reduction)
     return false;
 
   // The obvious transform is to shift the switch condition right and emit a
@@ -7626,17 +7641,19 @@ static bool reduceSwitchRange(SwitchInst *SI, IRBuilder<> &Builder,
   auto *Ty = cast<IntegerType>(SI->getCondition()->getType());
   Builder.SetInsertPoint(SI);
   Value *Sub = SI->getCondition();
-  if (Base != 0)
-    Sub = Builder.CreateSub(Sub, ConstantInt::getSigned(Ty, Base));
+  if (Reduction->Base != 0)
+    Sub = Builder.CreateSub(Sub, ConstantInt::getSigned(Ty, Reduction->Base));
   Value *Rot = Builder.CreateIntrinsic(
       Ty, Intrinsic::fshl,
-      {Sub, Sub, ConstantInt::get(Ty, Ty->getBitWidth() - Shift)});
+      {Sub, Sub, ConstantInt::get(Ty, Ty->getBitWidth() - Reduction->Shift)});
   SI->replaceUsesOfWith(SI->getCondition(), Rot);
 
   for (auto Case : SI->cases()) {
     auto *Orig = Case.getCaseValue();
-    auto Sub = Orig->getValue() - APInt(Ty->getBitWidth(), Base, true);
-    Case.setValue(cast<ConstantInt>(ConstantInt::get(Ty, Sub.lshr(Shift))));
+    auto Sub =
+        Orig->getValue() - APInt(Ty->getBitWidth(), Reduction->Base, true);
+    Case.setValue(
+        cast<ConstantInt>(ConstantInt::get(Ty, Sub.lshr(Reduction->Shift))));
   }
   return true;
 }

>From be6c5edf226b547b29b8283c8d56276cdf5307a4 Mon Sep 17 00:00:00 2001
From: Drew Kersnar <dkersnar at nvidia.com>
Date: Wed, 20 May 2026 16:26:51 +0000
Subject: [PATCH 4/7] Fix formatting

---
 llvm/lib/Transforms/Utils/SimplifyCFG.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index 5a82eb2470b2a..ce1819242a7c0 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -7587,8 +7587,8 @@ static bool reduceSwitchRange(SwitchInst *SI, IRBuilder<> &Builder,
     unsigned Shift;
   };
 
-  auto TryGetRangeReductionForBase = [&](int64_t CandidateBase)
-      -> std::optional<RangeReduction> {
+  auto TryGetRangeReductionForBase =
+      [&](int64_t CandidateBase) -> std::optional<RangeReduction> {
     SmallVector<int64_t, 4> ReducedValues;
     ReducedValues.reserve(Values.size());
     uint64_t ReducedBits = 0;

>From 8667e836d6cffcef842bbee12b8e0512ab906938 Mon Sep 17 00:00:00 2001
From: Drew Kersnar <dkersnar at nvidia.com>
Date: Fri, 22 May 2026 16:53:56 +0000
Subject: [PATCH 5/7] Address reviewer feedback

---
 llvm/lib/Transforms/Utils/SimplifyCFG.cpp     | 110 ++++++++++--------
 .../Transforms/SimplifyCFG/rangereduce.ll     |  19 +--
 2 files changed, 69 insertions(+), 60 deletions(-)

diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index ce1819242a7c0..20f8573a9a487 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -7058,6 +7058,40 @@ static bool isSwitchDense(ArrayRef<int64_t> Values) {
   return isSwitchDense(Values.size(), Range);
 }
 
+static std::optional<unsigned>
+getDenseSwitchRangeReductionShift(ArrayRef<int64_t> Values, int64_t Base) {
+  assert(llvm::all_of(Values, [Base](int64_t V) { return V >= Base; }) &&
+         "Base must not exceed any switch case value");
+  assert(Values.size() > 1 && "expected multiple switch cases");
+
+  // First, transform the values by subtracting Base.
+  SmallVector<int64_t, 4> ReducedValues(Values);
+  uint64_t ReducedValuesOr = 0;
+  for (auto &V : ReducedValues) {
+    uint64_t Reduced = (uint64_t)V - (uint64_t)Base;
+    ReducedValuesOr |= Reduced;
+    V = (int64_t)Reduced;
+  }
+
+  // Conceptually, the reduced values are non-negative distances from Base.
+  // Since the rest of the transform is bitwise only, treat them as unsigned
+  // bit patterns from here.
+
+  // countr_zero(0) returns 64. As Values is guaranteed to have more than
+  // one element and LLVM disallows duplicate cases, ReducedValuesOr will
+  // have at least one bit set, so Shift will be less than 64.
+  unsigned Shift = llvm::countr_zero(ReducedValuesOr);
+  assert(Shift < 64);
+  if (Shift > 0)
+    for (auto &V : ReducedValues)
+      V = (int64_t)((uint64_t)V >> Shift);
+
+  if (!isSwitchDense(ReducedValues))
+    return std::nullopt;
+
+  return Shift;
+}
+
 /// Determine whether a lookup table should be built for this switch, based on
 /// the number of cases, size of the table, and the types of the results.
 // TODO: We could support larger than legal types by limiting based on the
@@ -7582,50 +7616,23 @@ static bool reduceSwitchRange(SwitchInst *SI, IRBuilder<> &Builder,
   if (isSwitchDense(Values))
     return false;
 
-  struct RangeReduction {
-    int64_t Base;
-    unsigned Shift;
-  };
-
-  auto TryGetRangeReductionForBase =
-      [&](int64_t CandidateBase) -> std::optional<RangeReduction> {
-    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);
-    }
-
-    unsigned CandidateShift = llvm::countr_zero(ReducedBits);
-    if (CandidateShift >= 64)
-      return std::nullopt;
-    if (CandidateShift > 0)
-      for (auto &V : ReducedValues)
-        V = (int64_t)((uint64_t)V >> CandidateShift);
-
-    if (!isSwitchDense(ReducedValues))
-      return std::nullopt;
-
-    return RangeReduction{CandidateBase, CandidateShift};
-  };
-
-  auto GetRangeReduction = [&]() -> std::optional<RangeReduction> {
-    // 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 LocalMin = Values[0];
-    if (LocalMin >= 0) {
-      if (auto Reduction = TryGetRangeReductionForBase(0))
-        return Reduction;
-    }
-    return TryGetRangeReductionForBase(LocalMin);
-  };
-
-  std::optional<RangeReduction> Reduction = GetRangeReduction();
-  if (!Reduction)
+  // Find a Base and corresponding Shift that results in a dense switch range.
+  // Values[0] is the local minimum.
+  int64_t Base = Values[0];
+  std::optional<unsigned> Shift;
+  // 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.
+  if (Base >= 0) {
+    Shift = getDenseSwitchRangeReductionShift(Values, /*Base=*/0);
+    if (Shift)
+      Base = 0;
+  }
+  // Otherwise, try the local minimum as the base.
+  if (!Shift)
+    Shift = getDenseSwitchRangeReductionShift(Values, Base);
+
+  if (!Shift)
     return false;
 
   // The obvious transform is to shift the switch condition right and emit a
@@ -7637,23 +7644,24 @@ static bool reduceSwitchRange(SwitchInst *SI, IRBuilder<> &Builder,
   // shift and puts the shifted-off bits in the uppermost bits. If any of these
   // are nonzero then the switch condition will be very large and will hit the
   // default case.
+  //
+  // This transform can be done speculatively because it is so cheap - it
+  // results in a single rotate operation being inserted.
 
   auto *Ty = cast<IntegerType>(SI->getCondition()->getType());
   Builder.SetInsertPoint(SI);
   Value *Sub = SI->getCondition();
-  if (Reduction->Base != 0)
-    Sub = Builder.CreateSub(Sub, ConstantInt::getSigned(Ty, Reduction->Base));
+  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() - Reduction->Shift)});
+      {Sub, Sub, ConstantInt::get(Ty, Ty->getBitWidth() - *Shift)});
   SI->replaceUsesOfWith(SI->getCondition(), Rot);
 
   for (auto Case : SI->cases()) {
     auto *Orig = Case.getCaseValue();
-    auto Sub =
-        Orig->getValue() - APInt(Ty->getBitWidth(), Reduction->Base, true);
-    Case.setValue(
-        cast<ConstantInt>(ConstantInt::get(Ty, Sub.lshr(Reduction->Shift))));
+    auto Sub = Orig->getValue() - APInt(Ty->getBitWidth(), Base, true);
+    Case.setValue(cast<ConstantInt>(ConstantInt::get(Ty, Sub.lshr(*Shift))));
   }
   return true;
 }
diff --git a/llvm/test/Transforms/SimplifyCFG/rangereduce.ll b/llvm/test/Transforms/SimplifyCFG/rangereduce.ll
index 5358e6ec68ddb..61ed03831724c 100644
--- a/llvm/test/Transforms/SimplifyCFG/rangereduce.ll
+++ b/llvm/test/Transforms/SimplifyCFG/rangereduce.ll
@@ -286,13 +286,13 @@ three:
 
 define i32 @test9(i32 %a) {
 ; CHECK-LABEL: @test9(
-; 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-NEXT:    [[TMP1:%.*]] = call i32 @llvm.fshl.i32(i32 [[A:%.*]], i32 [[A]], i32 31)
+; CHECK-NEXT:    [[SWITCH_TABLEIDX:%.*]] = sub i32 [[TMP1]], 3
+; CHECK-NEXT:    [[TMP2:%.*]] = icmp ult i32 [[SWITCH_TABLEIDX]], 8
+; CHECK-NEXT:    br i1 [[TMP2]], label [[SWITCH_LOOKUP:%.*]], label [[COMMON_RET:%.*]]
 ; CHECK:       switch.lookup:
-; 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:    [[TMP3:%.*]] = zext nneg i32 [[SWITCH_TABLEIDX]] to i64
+; CHECK-NEXT:    [[SWITCH_GEP:%.*]] = getelementptr inbounds [8 x i32], ptr @switch.table.test9, i64 0, i64 [[TMP3]]
 ; CHECK-NEXT:    [[SWITCH_LOAD:%.*]] = load i32, ptr [[SWITCH_GEP]], align 4
 ; CHECK-NEXT:    br label [[COMMON_RET]]
 ; CHECK:       common.ret:
@@ -318,11 +318,12 @@ three:
 }
 
 ; Base=0 is profitable here: shifting out the common low zero bits produces a
-; dense range without subtracting the local minimum first.
+; dense range without subtracting the local minimum first. SimplifyCFG does not
+; form a lookup table here, so the reduced switch is tested directly.
 define void @test10(i32 %a) {
 ; CHECK-LABEL: @test10(
-; CHECK-NEXT:    [[TMP1:%.*]] = call i32 @llvm.fshl.i32(i32 [[A:%.*]], i32 [[A]], i32 27)
-; CHECK-NEXT:    switch i32 [[TMP1]], label [[COMMON_RET:%.*]] [
+; CHECK-NEXT:    [[TMP:%.*]] = call i32 @llvm.fshl.i32(i32 [[A:%.*]], i32 [[A]], i32 27)
+; CHECK-NEXT:    switch i32 [[TMP]], label [[COMMON_RET:%.*]] [
 ; CHECK-NEXT:      i32 1, label [[ONE:%.*]]
 ; CHECK-NEXT:      i32 2, label [[TWO:%.*]]
 ; CHECK-NEXT:      i32 3, label [[THREE:%.*]]

>From 346288668524624c9d660b62391e9158a083a22e Mon Sep 17 00:00:00 2001
From: Drew Kersnar <dkersnar at nvidia.com>
Date: Tue, 2 Jun 2026 16:41:18 +0000
Subject: [PATCH 6/7] Reviewer feedback

---
 llvm/lib/Transforms/Utils/SimplifyCFG.cpp | 14 +++++---------
 1 file changed, 5 insertions(+), 9 deletions(-)

diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index 20f8573a9a487..42b5074934581 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -7060,9 +7060,9 @@ static bool isSwitchDense(ArrayRef<int64_t> Values) {
 
 static std::optional<unsigned>
 getDenseSwitchRangeReductionShift(ArrayRef<int64_t> Values, int64_t Base) {
-  assert(llvm::all_of(Values, [Base](int64_t V) { return V >= Base; }) &&
-         "Base must not exceed any switch case value");
   assert(Values.size() > 1 && "expected multiple switch cases");
+  if (!llvm::all_of(Values, [Base](int64_t V) { return V >= Base; }))
+    return std::nullopt;
 
   // First, transform the values by subtracting Base.
   SmallVector<int64_t, 4> ReducedValues(Values);
@@ -7623,13 +7623,9 @@ static bool reduceSwitchRange(SwitchInst *SI, IRBuilder<> &Builder,
   // 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.
-  if (Base >= 0) {
-    Shift = getDenseSwitchRangeReductionShift(Values, /*Base=*/0);
-    if (Shift)
-      Base = 0;
-  }
-  // Otherwise, try the local minimum as the base.
-  if (!Shift)
+  if ((Shift = getDenseSwitchRangeReductionShift(Values, /*Base=*/0)))
+    Base = 0;
+  else if (Base != 0)
     Shift = getDenseSwitchRangeReductionShift(Values, Base);
 
   if (!Shift)

>From 6a5c41b27408455509078e29082de9893fb3df8b Mon Sep 17 00:00:00 2001
From: Drew Kersnar <dkersnar at nvidia.com>
Date: Thu, 4 Jun 2026 23:07:19 +0000
Subject: [PATCH 7/7] Use the more conservative density measurement for Base=0

---
 llvm/lib/Transforms/Utils/SimplifyCFG.cpp       |  3 ++-
 llvm/test/Transforms/SimplifyCFG/rangereduce.ll | 12 ++++++------
 2 files changed, 8 insertions(+), 7 deletions(-)

diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index e783ee21fe830..0fee66d1cec88 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -7683,7 +7683,8 @@ static bool reduceSwitchRange(SwitchInst *SI, IRBuilder<> &Builder,
   // 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.
-  if ((Shift = getDenseSwitchRangeReductionShift(Values, /*Base=*/0, OptSize)))
+  if ((Shift = getDenseSwitchRangeReductionShift(Values, /*Base=*/0,
+                                                 /*OptSize=*/true)))
     Base = 0;
   else if (Base != 0)
     Shift = getDenseSwitchRangeReductionShift(Values, Base, OptSize);
diff --git a/llvm/test/Transforms/SimplifyCFG/rangereduce.ll b/llvm/test/Transforms/SimplifyCFG/rangereduce.ll
index 084e6d9a45122..fa9da7b9a28bf 100644
--- a/llvm/test/Transforms/SimplifyCFG/rangereduce.ll
+++ b/llvm/test/Transforms/SimplifyCFG/rangereduce.ll
@@ -286,13 +286,13 @@ three:
 
 define i32 @test9(i32 %a) {
 ; CHECK-LABEL: @test9(
-; CHECK-NEXT:    [[TMP1:%.*]] = call i32 @llvm.fshl.i32(i32 [[A:%.*]], i32 [[A]], i32 31)
-; CHECK-NEXT:    [[SWITCH_TABLEIDX:%.*]] = sub i32 [[TMP1]], 3
-; CHECK-NEXT:    [[TMP2:%.*]] = icmp ult i32 [[SWITCH_TABLEIDX]], 28
-; CHECK-NEXT:    br i1 [[TMP2]], label [[SWITCH_LOOKUP:%.*]], label [[COMMON_RET:%.*]]
+; 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]], 28
+; CHECK-NEXT:    br i1 [[TMP3]], label [[SWITCH_LOOKUP:%.*]], label [[COMMON_RET:%.*]]
 ; CHECK:       switch.lookup:
-; CHECK-NEXT:    [[TMP3:%.*]] = zext nneg i32 [[SWITCH_TABLEIDX]] to i64
-; CHECK-NEXT:    [[SWITCH_GEP:%.*]] = getelementptr inbounds [28 x i32], ptr @switch.table.test9, i64 0, i64 [[TMP3]]
+; CHECK-NEXT:    [[TMP4:%.*]] = zext nneg i32 [[TMP2]] to i64
+; CHECK-NEXT:    [[SWITCH_GEP:%.*]] = getelementptr inbounds [28 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]]
 ; CHECK:       common.ret:



More information about the llvm-commits mailing list