[llvm] [SimplifyCFG]: Switch on umin replaces default (PR #164097)

via llvm-commits llvm-commits at lists.llvm.org
Sat Oct 18 10:46:32 PDT 2025


https://github.com/kper updated https://github.com/llvm/llvm-project/pull/164097

>From fba6d47789707e2bfb92addcd605210ce698299c Mon Sep 17 00:00:00 2001
From: Kevin Per <kevin.per at protonmail.com>
Date: Sat, 18 Oct 2025 12:26:41 +0000
Subject: [PATCH 1/3] [SimplifyCFG]: Remove unreachable when switch with umin

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

diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index d831c2737e5f8..8d3c91e69ad48 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -7540,6 +7540,62 @@ static bool reduceSwitchRange(SwitchInst *SI, IRBuilder<> &Builder,
   return true;
 }
 
+/// Tries to transform the switch when the condition is umin and a constant.
+/// In that case, the default branch can be replaced by the constant's branch.
+/// For example:
+/// switch(umin(a, 3)) {
+/// case 0:
+/// case 1:
+/// case 2:
+/// case 3:
+///   // ...
+/// default:
+///   unreachable
+/// }
+///
+/// Transforms into:
+///
+/// switch(umin(a, 3)) {
+/// case 0:
+/// case 1:
+/// case 2:
+/// default:
+///   // This is case 3
+/// }
+static bool simplifySwitchWhenUMin(SwitchInst *SI, IRBuilder<> &Builder) {
+  auto *Call = dyn_cast<IntrinsicInst>(SI->getCondition());
+
+  if (!Call)
+    return false;
+
+  if (Call->getIntrinsicID() != Intrinsic::umin)
+    return false;
+
+  if (!SI->defaultDestUnreachable())
+    return false;
+
+  // Extract the constant operand from the intrinsic.
+  auto *Constant = dyn_cast<ConstantInt>(Call->getArgOperand(1));
+
+  if (!Constant) {
+    return false;
+  }
+
+  for (auto Case = SI->case_begin(), e = SI->case_end(); Case != e; Case++) {
+    uint64_t CaseValue = Case->getCaseValue()->getValue().getZExtValue();
+
+    // We found the case which is equal to the case's umin argument.
+    // We can make the case the default case.
+    if (Constant->equalsInt(CaseValue)) {
+      SI->setDefaultDest(Case->getCaseSuccessor());
+      SI->removeCase(Case);
+      return true;
+    }
+  }
+
+  return false;
+}
+
 /// Tries to transform switch of powers of two to reduce switch range.
 /// For example, switch like:
 /// switch (C) { case 1: case 2: case 64: case 128: }
@@ -7966,6 +8022,9 @@ bool SimplifyCFGOpt::simplifySwitch(SwitchInst *SI, IRBuilder<> &Builder) {
   if (simplifyDuplicateSwitchArms(SI, DTU))
     return requestResimplify();
 
+  if (simplifySwitchWhenUMin(SI, Builder))
+    return requestResimplify();
+
   return false;
 }
 

>From c131865a96a26987aab9cc0a6c5cd116b53a58c0 Mon Sep 17 00:00:00 2001
From: Kevin Per <kevin.per at protonmail.com>
Date: Sat, 18 Oct 2025 15:35:29 +0000
Subject: [PATCH 2/3] [SimplifyCFG]: Added test

---
 .../Transforms/SimplifyCFG/switch-umin.ll     | 105 ++++++++++++++++++
 1 file changed, 105 insertions(+)
 create mode 100644 llvm/test/Transforms/SimplifyCFG/switch-umin.ll

diff --git a/llvm/test/Transforms/SimplifyCFG/switch-umin.ll b/llvm/test/Transforms/SimplifyCFG/switch-umin.ll
new file mode 100644
index 0000000000000..69f78bf377dae
--- /dev/null
+++ b/llvm/test/Transforms/SimplifyCFG/switch-umin.ll
@@ -0,0 +1,105 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -S -passes=simplifycfg < %s | FileCheck %s
+
+declare void @a()
+declare void @b()
+declare void @c()
+
+define void @switch_replace_default(i32 %x) {
+; CHECK-LABEL: define void @switch_replace_default(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[MIN:%.*]] = call i32 @llvm.umin.i32(i32 [[X]], i32 3)
+; CHECK-NEXT:    switch i32 [[MIN]], label %[[COMMON_RET:.*]] [
+; CHECK-NEXT:      i32 0, label %[[CASE0:.*]]
+; CHECK-NEXT:      i32 1, label %[[CASE1:.*]]
+; CHECK-NEXT:      i32 2, label %[[CASE2:.*]]
+; CHECK-NEXT:    ]
+; CHECK:       [[COMMON_RET]]:
+; CHECK-NEXT:    ret void
+; CHECK:       [[CASE0]]:
+; CHECK-NEXT:    call void @a()
+; CHECK-NEXT:    br label %[[COMMON_RET]]
+; CHECK:       [[CASE1]]:
+; CHECK-NEXT:    call void @b()
+; CHECK-NEXT:    br label %[[COMMON_RET]]
+; CHECK:       [[CASE2]]:
+; CHECK-NEXT:    call void @c()
+; CHECK-NEXT:    br label %[[COMMON_RET]]
+;
+  %min = call i32 @llvm.umin.i32(i32 %x, i32 3)
+  switch i32 %min, label %unreachable [
+  i32 0, label %case0
+  i32 1, label %case1
+  i32 2, label %case2
+  i32 3, label %case3
+  ]
+
+case0:
+  call void @a()
+  ret void
+
+case1:
+  call void @b()
+  ret void
+
+case2:
+  call void @c()
+  ret void
+
+case3:
+  ret void
+
+unreachable:
+  unreachable
+}
+
+define void @do_not_switch_replace_default(i32 %x, i32 %y) {
+; CHECK-LABEL: define void @do_not_switch_replace_default(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT:    [[MIN:%.*]] = call i32 @llvm.umin.i32(i32 [[X]], i32 [[Y]])
+; CHECK-NEXT:    switch i32 [[MIN]], label %[[UNREACHABLE:.*]] [
+; CHECK-NEXT:      i32 0, label %[[CASE0:.*]]
+; CHECK-NEXT:      i32 1, label %[[CASE1:.*]]
+; CHECK-NEXT:      i32 2, label %[[CASE2:.*]]
+; CHECK-NEXT:      i32 3, label %[[COMMON_RET:.*]]
+; CHECK-NEXT:    ]
+; CHECK:       [[COMMON_RET]]:
+; CHECK-NEXT:    ret void
+; CHECK:       [[CASE0]]:
+; CHECK-NEXT:    call void @a()
+; CHECK-NEXT:    br label %[[COMMON_RET]]
+; CHECK:       [[CASE1]]:
+; CHECK-NEXT:    call void @b()
+; CHECK-NEXT:    br label %[[COMMON_RET]]
+; CHECK:       [[CASE2]]:
+; CHECK-NEXT:    call void @c()
+; CHECK-NEXT:    br label %[[COMMON_RET]]
+; CHECK:       [[UNREACHABLE]]:
+; CHECK-NEXT:    unreachable
+;
+  %min = call i32 @llvm.umin.i32(i32 %x, i32 %y)
+  switch i32 %min, label %unreachable [
+  i32 0, label %case0
+  i32 1, label %case1
+  i32 2, label %case2
+  i32 3, label %case3
+  ]
+
+case0:
+  call void @a()
+  ret void
+
+case1:
+  call void @b()
+  ret void
+
+case2:
+  call void @c()
+  ret void
+
+case3:
+  ret void
+
+unreachable:
+  unreachable
+}

>From 4866e8f6666df8d88633f8363fafd30722420160 Mon Sep 17 00:00:00 2001
From: Kevin Per <kevin.per at protonmail.com>
Date: Sat, 18 Oct 2025 17:45:38 +0000
Subject: [PATCH 3/3] [SimplifyCFG]: Addressed feedback

---
 llvm/lib/Transforms/Utils/SimplifyCFG.cpp     | 42 +++++++++----------
 .../Transforms/SimplifyCFG/switch-umin.ll     |  2 +-
 2 files changed, 20 insertions(+), 24 deletions(-)

diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index 8d3c91e69ad48..ae2d216ad71ac 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -7555,45 +7555,41 @@ static bool reduceSwitchRange(SwitchInst *SI, IRBuilder<> &Builder,
 ///
 /// Transforms into:
 ///
-/// switch(umin(a, 3)) {
+/// switch(a) {
 /// case 0:
 /// case 1:
 /// case 2:
 /// default:
 ///   // This is case 3
 /// }
-static bool simplifySwitchWhenUMin(SwitchInst *SI, IRBuilder<> &Builder) {
-  auto *Call = dyn_cast<IntrinsicInst>(SI->getCondition());
-
-  if (!Call)
-    return false;
+static bool simplifySwitchWhenUMin(SwitchInst *SI, DomTreeUpdater *DTU) {
+  Value *A;
+  ConstantInt *Constant;
 
-  if (Call->getIntrinsicID() != Intrinsic::umin)
+  if (!match(SI->getCondition(), m_UMin(m_Value(A), m_ConstantInt(Constant))))
     return false;
 
   if (!SI->defaultDestUnreachable())
     return false;
 
-  // Extract the constant operand from the intrinsic.
-  auto *Constant = dyn_cast<ConstantInt>(Call->getArgOperand(1));
+  auto Case = SI->findCaseValue(Constant);
 
-  if (!Constant) {
+  // When the case value cannot be found then `findCaseValue` returns the
+  // default cause. This means that there is no `case 3:` and this
+  // simplification fails.
+  if (Case == SI->case_default())
     return false;
-  }
 
-  for (auto Case = SI->case_begin(), e = SI->case_end(); Case != e; Case++) {
-    uint64_t CaseValue = Case->getCaseValue()->getValue().getZExtValue();
+  BasicBlock *Unreachable = SI->getDefaultDest();
+  SI->setDefaultDest(Case->getCaseSuccessor());
+  SI->removeCase(Case);
+  SI->setCondition(A);
 
-    // We found the case which is equal to the case's umin argument.
-    // We can make the case the default case.
-    if (Constant->equalsInt(CaseValue)) {
-      SI->setDefaultDest(Case->getCaseSuccessor());
-      SI->removeCase(Case);
-      return true;
-    }
-  }
+  BasicBlock *BB = SI->getParent();
+  if (DTU)
+    DTU->applyUpdates({{DominatorTree::Delete, BB, Unreachable}});
 
-  return false;
+  return true;
 }
 
 /// Tries to transform switch of powers of two to reduce switch range.
@@ -8022,7 +8018,7 @@ bool SimplifyCFGOpt::simplifySwitch(SwitchInst *SI, IRBuilder<> &Builder) {
   if (simplifyDuplicateSwitchArms(SI, DTU))
     return requestResimplify();
 
-  if (simplifySwitchWhenUMin(SI, Builder))
+  if (simplifySwitchWhenUMin(SI, DTU))
     return requestResimplify();
 
   return false;
diff --git a/llvm/test/Transforms/SimplifyCFG/switch-umin.ll b/llvm/test/Transforms/SimplifyCFG/switch-umin.ll
index 69f78bf377dae..fbc4d475b0264 100644
--- a/llvm/test/Transforms/SimplifyCFG/switch-umin.ll
+++ b/llvm/test/Transforms/SimplifyCFG/switch-umin.ll
@@ -9,7 +9,7 @@ define void @switch_replace_default(i32 %x) {
 ; CHECK-LABEL: define void @switch_replace_default(
 ; CHECK-SAME: i32 [[X:%.*]]) {
 ; CHECK-NEXT:    [[MIN:%.*]] = call i32 @llvm.umin.i32(i32 [[X]], i32 3)
-; CHECK-NEXT:    switch i32 [[MIN]], label %[[COMMON_RET:.*]] [
+; CHECK-NEXT:    switch i32 [[X]], label %[[COMMON_RET:.*]] [
 ; CHECK-NEXT:      i32 0, label %[[CASE0:.*]]
 ; CHECK-NEXT:      i32 1, label %[[CASE1:.*]]
 ; CHECK-NEXT:      i32 2, label %[[CASE2:.*]]



More information about the llvm-commits mailing list