[llvm] [SimplifyCFG] Simplify switch with implicit default (PR #95665)

via llvm-commits llvm-commits at lists.llvm.org
Sun Jun 16 04:22:45 PDT 2024


https://github.com/YanWQ-monad updated https://github.com/llvm/llvm-project/pull/95665

>From 24e531ca4f2405eac815407417347452bbc91d52 Mon Sep 17 00:00:00 2001
From: YanWQ-monad <YanWQmonad at gmail.com>
Date: Sat, 15 Jun 2024 23:24:11 +0800
Subject: [PATCH 1/9] [SimplifyCFG] Pre-commit: add tests

---
 .../switch-with-implicit-default.ll           | 374 ++++++++++++++++++
 1 file changed, 374 insertions(+)
 create mode 100644 llvm/test/Transforms/SimplifyCFG/switch-with-implicit-default.ll

diff --git a/llvm/test/Transforms/SimplifyCFG/switch-with-implicit-default.ll b/llvm/test/Transforms/SimplifyCFG/switch-with-implicit-default.ll
new file mode 100644
index 0000000000000..867f28c0242e2
--- /dev/null
+++ b/llvm/test/Transforms/SimplifyCFG/switch-with-implicit-default.ll
@@ -0,0 +1,374 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt < %s -passes=simplifycfg -simplifycfg-require-and-preserve-domtree=1 -S | FileCheck %s
+
+declare void @func0()
+declare void @func1()
+declare void @func2()
+declare void @unreachable()
+
+define void @positive(i32 %x) {
+; CHECK-LABEL: define void @positive(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[C:%.*]] = icmp ult i32 [[X]], 3
+; CHECK-NEXT:    [[V:%.*]] = select i1 [[C]], i32 [[X]], i32 1
+; CHECK-NEXT:    switch i32 [[V]], label %[[DEFAULT:.*]] [
+; CHECK-NEXT:      i32 0, label %[[CASE0:.*]]
+; CHECK-NEXT:      i32 1, label %[[CASE1:.*]]
+; CHECK-NEXT:      i32 2, label %[[CASE2:.*]]
+; CHECK-NEXT:    ]
+; CHECK:       [[DEFAULT]]:
+; CHECK-NEXT:    tail call void @unreachable()
+; CHECK-NEXT:    br label %[[END:.*]]
+; CHECK:       [[CASE0]]:
+; CHECK-NEXT:    tail call void @func0()
+; CHECK-NEXT:    br label %[[END]]
+; CHECK:       [[CASE1]]:
+; CHECK-NEXT:    tail call void @func1()
+; CHECK-NEXT:    br label %[[END]]
+; CHECK:       [[CASE2]]:
+; CHECK-NEXT:    tail call void @func2()
+; CHECK-NEXT:    br label %[[END]]
+; CHECK:       [[END]]:
+; CHECK-NEXT:    ret void
+;
+  %c = icmp ult i32 %x, 3
+  %v = select i1 %c, i32 %x, i32 1
+  switch i32 %v, label %default [
+  i32 0, label %case0
+  i32 1, label %case1
+  i32 2, label %case2
+  ]
+
+default:
+  tail call void @unreachable()
+  br label %end
+
+case0:
+  tail call void @func0()
+  br label %end
+
+case1:
+  tail call void @func1()
+  br label %end
+
+case2:
+  tail call void @func2()
+  br label %end
+
+end:
+  ret void
+}
+
+define void @positive_manual_default_out_of_cases(i32 %x) {
+; CHECK-LABEL: define void @positive_manual_default_out_of_cases(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[C:%.*]] = icmp ult i32 [[X]], 3
+; CHECK-NEXT:    [[V:%.*]] = select i1 [[C]], i32 [[X]], i32 7
+; CHECK-NEXT:    switch i32 [[V]], label %[[DEFAULT:.*]] [
+; CHECK-NEXT:      i32 0, label %[[CASE0:.*]]
+; CHECK-NEXT:      i32 1, label %[[CASE1:.*]]
+; CHECK-NEXT:      i32 2, label %[[CASE2:.*]]
+; CHECK-NEXT:    ]
+; CHECK:       [[DEFAULT]]:
+; CHECK-NEXT:    tail call void @unreachable()
+; CHECK-NEXT:    br label %[[END:.*]]
+; CHECK:       [[CASE0]]:
+; CHECK-NEXT:    tail call void @func0()
+; CHECK-NEXT:    br label %[[END]]
+; CHECK:       [[CASE1]]:
+; CHECK-NEXT:    tail call void @func1()
+; CHECK-NEXT:    br label %[[END]]
+; CHECK:       [[CASE2]]:
+; CHECK-NEXT:    tail call void @func2()
+; CHECK-NEXT:    br label %[[END]]
+; CHECK:       [[END]]:
+; CHECK-NEXT:    ret void
+;
+  %c = icmp ult i32 %x, 3
+  %v = select i1 %c, i32 %x, i32 7
+  switch i32 %v, label %default [
+  i32 0, label %case0
+  i32 1, label %case1
+  i32 2, label %case2
+  ]
+
+default:
+  tail call void @unreachable()
+  br label %end
+
+case0:
+  tail call void @func0()
+  br label %end
+
+case1:
+  tail call void @func1()
+  br label %end
+
+case2:
+  tail call void @func2()
+  br label %end
+
+end:
+  ret void
+}
+
+
+define void @wrong_icmp(i32 %x) {
+; CHECK-LABEL: define void @wrong_icmp(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[C:%.*]] = icmp ugt i32 [[X]], 2
+; CHECK-NEXT:    [[V:%.*]] = select i1 [[C]], i32 [[X]], i32 1
+; CHECK-NEXT:    switch i32 [[V]], label %[[DEFAULT:.*]] [
+; CHECK-NEXT:      i32 0, label %[[CASE0:.*]]
+; CHECK-NEXT:      i32 1, label %[[CASE1:.*]]
+; CHECK-NEXT:      i32 2, label %[[CASE2:.*]]
+; CHECK-NEXT:    ]
+; CHECK:       [[DEFAULT]]:
+; CHECK-NEXT:    tail call void @unreachable()
+; CHECK-NEXT:    br label %[[END:.*]]
+; CHECK:       [[CASE0]]:
+; CHECK-NEXT:    tail call void @func0()
+; CHECK-NEXT:    br label %[[END]]
+; CHECK:       [[CASE1]]:
+; CHECK-NEXT:    tail call void @func1()
+; CHECK-NEXT:    br label %[[END]]
+; CHECK:       [[CASE2]]:
+; CHECK-NEXT:    tail call void @func2()
+; CHECK-NEXT:    br label %[[END]]
+; CHECK:       [[END]]:
+; CHECK-NEXT:    ret void
+;
+  %c = icmp ugt i32 %x, 2
+  %v = select i1 %c, i32 %x, i32 1
+  switch i32 %v, label %default [
+  i32 0, label %case0
+  i32 1, label %case1
+  i32 2, label %case2
+  ]
+
+default:
+  tail call void @unreachable()
+  br label %end
+
+case0:
+  tail call void @func0()
+  br label %end
+
+case1:
+  tail call void @func1()
+  br label %end
+
+case2:
+  tail call void @func2()
+  br label %end
+
+end:
+  ret void
+}
+
+define void @outer_icmp_too_wide(i32 %x) {
+; CHECK-LABEL: define void @outer_icmp_too_wide(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[C:%.*]] = icmp ult i32 [[X]], 4
+; CHECK-NEXT:    [[V:%.*]] = select i1 [[C]], i32 [[X]], i32 1
+; CHECK-NEXT:    switch i32 [[V]], label %[[DEFAULT:.*]] [
+; CHECK-NEXT:      i32 0, label %[[CASE0:.*]]
+; CHECK-NEXT:      i32 1, label %[[CASE1:.*]]
+; CHECK-NEXT:      i32 2, label %[[CASE2:.*]]
+; CHECK-NEXT:    ]
+; CHECK:       [[DEFAULT]]:
+; CHECK-NEXT:    tail call void @unreachable()
+; CHECK-NEXT:    br label %[[END:.*]]
+; CHECK:       [[CASE0]]:
+; CHECK-NEXT:    tail call void @func0()
+; CHECK-NEXT:    br label %[[END]]
+; CHECK:       [[CASE1]]:
+; CHECK-NEXT:    tail call void @func1()
+; CHECK-NEXT:    br label %[[END]]
+; CHECK:       [[CASE2]]:
+; CHECK-NEXT:    tail call void @func2()
+; CHECK-NEXT:    br label %[[END]]
+; CHECK:       [[END]]:
+; CHECK-NEXT:    ret void
+;
+  %c = icmp ult i32 %x, 4
+  %v = select i1 %c, i32 %x, i32 1
+  switch i32 %v, label %default [
+  i32 0, label %case0
+  i32 1, label %case1
+  i32 2, label %case2
+  ]
+
+default:
+  tail call void @unreachable()
+  br label %end
+
+case0:
+  tail call void @func0()
+  br label %end
+
+case1:
+  tail call void @func1()
+  br label %end
+
+case2:
+  tail call void @func2()
+  br label %end
+
+end:
+  ret void
+}
+
+define void @outer_icmp_too_narrow(i32 %x) {
+; CHECK-LABEL: define void @outer_icmp_too_narrow(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[C:%.*]] = icmp ult i32 [[X]], 2
+; CHECK-NEXT:    [[V:%.*]] = select i1 [[C]], i32 [[X]], i32 1
+; CHECK-NEXT:    switch i32 [[V]], label %[[DEFAULT:.*]] [
+; CHECK-NEXT:      i32 0, label %[[CASE0:.*]]
+; CHECK-NEXT:      i32 1, label %[[CASE1:.*]]
+; CHECK-NEXT:      i32 2, label %[[CASE2:.*]]
+; CHECK-NEXT:    ]
+; CHECK:       [[DEFAULT]]:
+; CHECK-NEXT:    tail call void @unreachable()
+; CHECK-NEXT:    br label %[[END:.*]]
+; CHECK:       [[CASE0]]:
+; CHECK-NEXT:    tail call void @func0()
+; CHECK-NEXT:    br label %[[END]]
+; CHECK:       [[CASE1]]:
+; CHECK-NEXT:    tail call void @func1()
+; CHECK-NEXT:    br label %[[END]]
+; CHECK:       [[CASE2]]:
+; CHECK-NEXT:    tail call void @func2()
+; CHECK-NEXT:    br label %[[END]]
+; CHECK:       [[END]]:
+; CHECK-NEXT:    ret void
+;
+  %c = icmp ult i32 %x, 2
+  %v = select i1 %c, i32 %x, i32 1
+  switch i32 %v, label %default [
+  i32 0, label %case0
+  i32 1, label %case1
+  i32 2, label %case2
+  ]
+
+default:
+  tail call void @unreachable()
+  br label %end
+
+case0:
+  tail call void @func0()
+  br label %end
+
+case1:
+  tail call void @func1()
+  br label %end
+
+case2:
+  tail call void @func2()
+  br label %end
+
+end:
+  ret void
+}
+
+define void @switch_cases_not_continuous_1(i32 %x) {
+; CHECK-LABEL: define void @switch_cases_not_continuous_1(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[C:%.*]] = icmp ult i32 [[X]], 3
+; CHECK-NEXT:    [[V:%.*]] = select i1 [[C]], i32 [[X]], i32 2
+; CHECK-NEXT:    switch i32 [[V]], label %[[DEFAULT:.*]] [
+; CHECK-NEXT:      i32 0, label %[[CASE0:.*]]
+; CHECK-NEXT:      i32 2, label %[[CASE2:.*]]
+; CHECK-NEXT:    ]
+; CHECK:       [[DEFAULT]]:
+; CHECK-NEXT:    tail call void @unreachable()
+; CHECK-NEXT:    br label %[[END:.*]]
+; CHECK:       [[CASE0]]:
+; CHECK-NEXT:    tail call void @func0()
+; CHECK-NEXT:    br label %[[END]]
+; CHECK:       [[CASE2]]:
+; CHECK-NEXT:    tail call void @func2()
+; CHECK-NEXT:    br label %[[END]]
+; CHECK:       [[END]]:
+; CHECK-NEXT:    ret void
+;
+  %c = icmp ult i32 %x, 3
+  %v = select i1 %c, i32 %x, i32 2
+  switch i32 %v, label %default [
+  i32 0, label %case0
+  i32 2, label %case2
+  ]
+
+default:
+  tail call void @unreachable()
+  br label %end
+
+case0:
+  tail call void @func0()
+  br label %end
+
+case1:
+  tail call void @func1()
+  br label %end
+
+case2:
+  tail call void @func2()
+  br label %end
+
+end:
+  ret void
+}
+
+define void @switch_cases_not_continuous_2(i32 %x) {
+; CHECK-LABEL: define void @switch_cases_not_continuous_2(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[C:%.*]] = icmp ult i32 [[X]], 3
+; CHECK-NEXT:    [[V:%.*]] = select i1 [[C]], i32 [[X]], i32 2
+; CHECK-NEXT:    switch i32 [[V]], label %[[DEFAULT:.*]] [
+; CHECK-NEXT:      i32 0, label %[[CASE0:.*]]
+; CHECK-NEXT:      i32 2, label %[[CASE2:.*]]
+; CHECK-NEXT:      i32 3, label %[[CASE1:.*]]
+; CHECK-NEXT:    ]
+; CHECK:       [[DEFAULT]]:
+; CHECK-NEXT:    tail call void @unreachable()
+; CHECK-NEXT:    br label %[[END:.*]]
+; CHECK:       [[CASE0]]:
+; CHECK-NEXT:    tail call void @func0()
+; CHECK-NEXT:    br label %[[END]]
+; CHECK:       [[CASE1]]:
+; CHECK-NEXT:    tail call void @func1()
+; CHECK-NEXT:    br label %[[END]]
+; CHECK:       [[CASE2]]:
+; CHECK-NEXT:    tail call void @func2()
+; CHECK-NEXT:    br label %[[END]]
+; CHECK:       [[END]]:
+; CHECK-NEXT:    ret void
+;
+  %c = icmp ult i32 %x, 3
+  %v = select i1 %c, i32 %x, i32 2
+  switch i32 %v, label %default [
+  i32 0, label %case0
+  i32 2, label %case2
+  i32 3, label %case1
+  ]
+
+default:
+  tail call void @unreachable()
+  br label %end
+
+case0:
+  tail call void @func0()
+  br label %end
+
+case1:
+  tail call void @func1()
+  br label %end
+
+case2:
+  tail call void @func2()
+  br label %end
+
+end:
+  ret void
+}

>From 9eff08dba645864fe31f40af6ae3221e26188323 Mon Sep 17 00:00:00 2001
From: YanWQ-monad <YanWQmonad at gmail.com>
Date: Sat, 15 Jun 2024 23:26:40 +0800
Subject: [PATCH 2/9] [SimplifyCFG] Simplify switch with implicit default

---
 llvm/lib/Transforms/Utils/SimplifyCFG.cpp     | 45 +++++++++++++++++++
 .../switch-with-implicit-default.ll           | 10 ++---
 2 files changed, 48 insertions(+), 7 deletions(-)

diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index bccfb80f2b6cb..7a16b714e5324 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -7104,6 +7104,48 @@ static bool simplifySwitchOfPowersOfTwo(SwitchInst *SI, IRBuilder<> &Builder,
   return true;
 }
 
+// Try to fold switch with manually selecting default branch
+// For example, for the switch
+//   switch (v) { case 0: case 1: case 2: ... case MaxC: default: }
+//     (continuous cases value from 0 to MaxC)
+//   and, v = select (x u<= MaxC), x, AnotherC.
+// so "AnotherC" is the de-facto default branch, and it will be folded to
+//   switch (x) { case 0: case 1: case 2: ... case MaxC:  default -> AnotherC: }
+static bool simplifySwitchWithImplicitDefault(SwitchInst *SI) {
+  Value *Cond;
+  ConstantInt *Bound, *DefaultCase;
+  ICmpInst::Predicate Pred;
+  if (!(match(SI->getCondition(),
+              m_Select(m_ICmp(Pred, m_Value(Cond), m_ConstantInt(Bound)),
+                       m_Deferred(Cond), m_ConstantInt(DefaultCase))) &&
+        Pred == CmpInst::ICMP_ULT))
+    return false;
+
+  // In an ideal situation, the range of switch cases is continuous,
+  // and should match the range of ICmp. That is,
+  //   MaxCase (declared below) + 1 = SI->getNumCases() = OuterRange.Upper.
+  // Checking it partially here can be a fast-fail path
+  if (Bound->getValue() != SI->getNumCases())
+    return false;
+
+  SmallVector<uint64_t, 4> Values;
+  for (const auto &Case : SI->cases()) {
+    uint64_t CaseValue = Case.getCaseValue()->getValue().getZExtValue();
+    Values.push_back(CaseValue);
+  }
+  llvm::sort(Values);
+
+  // The cases should be continuous from 0 to some value
+  if (!(*Values.begin() == 0 && *Values.rbegin() == SI->getNumCases() - 1))
+    return false;
+
+  SI->setCondition(Cond);
+  if (auto It = SI->findCaseValue(DefaultCase); It != SI->case_end())
+    SI->setDefaultDest(It->getCaseSuccessor());
+
+  return true;
+}
+
 bool SimplifyCFGOpt::simplifySwitch(SwitchInst *SI, IRBuilder<> &Builder) {
   BasicBlock *BB = SI->getParent();
 
@@ -7142,6 +7184,9 @@ bool SimplifyCFGOpt::simplifySwitch(SwitchInst *SI, IRBuilder<> &Builder) {
   if (Options.ForwardSwitchCondToPhi && ForwardSwitchConditionToPHI(SI))
     return requestResimplify();
 
+  if (simplifySwitchWithImplicitDefault(SI))
+    return requestResimplify();
+
   // The conversion from switch to lookup tables results in difficult-to-analyze
   // code and makes pruning branches much harder. This is a problem if the
   // switch expression itself can still be restricted as a result of inlining or
diff --git a/llvm/test/Transforms/SimplifyCFG/switch-with-implicit-default.ll b/llvm/test/Transforms/SimplifyCFG/switch-with-implicit-default.ll
index 867f28c0242e2..0107cdc70c95d 100644
--- a/llvm/test/Transforms/SimplifyCFG/switch-with-implicit-default.ll
+++ b/llvm/test/Transforms/SimplifyCFG/switch-with-implicit-default.ll
@@ -11,17 +11,13 @@ define void @positive(i32 %x) {
 ; CHECK-SAME: i32 [[X:%.*]]) {
 ; CHECK-NEXT:    [[C:%.*]] = icmp ult i32 [[X]], 3
 ; CHECK-NEXT:    [[V:%.*]] = select i1 [[C]], i32 [[X]], i32 1
-; CHECK-NEXT:    switch i32 [[V]], label %[[DEFAULT:.*]] [
+; CHECK-NEXT:    switch i32 [[X]], label %[[CASE1:.*]] [
 ; CHECK-NEXT:      i32 0, label %[[CASE0:.*]]
-; CHECK-NEXT:      i32 1, label %[[CASE1:.*]]
 ; CHECK-NEXT:      i32 2, label %[[CASE2:.*]]
 ; CHECK-NEXT:    ]
-; CHECK:       [[DEFAULT]]:
-; CHECK-NEXT:    tail call void @unreachable()
-; CHECK-NEXT:    br label %[[END:.*]]
 ; CHECK:       [[CASE0]]:
 ; CHECK-NEXT:    tail call void @func0()
-; CHECK-NEXT:    br label %[[END]]
+; CHECK-NEXT:    br label %[[END:.*]]
 ; CHECK:       [[CASE1]]:
 ; CHECK-NEXT:    tail call void @func1()
 ; CHECK-NEXT:    br label %[[END]]
@@ -64,7 +60,7 @@ define void @positive_manual_default_out_of_cases(i32 %x) {
 ; CHECK-SAME: i32 [[X:%.*]]) {
 ; CHECK-NEXT:    [[C:%.*]] = icmp ult i32 [[X]], 3
 ; CHECK-NEXT:    [[V:%.*]] = select i1 [[C]], i32 [[X]], i32 7
-; CHECK-NEXT:    switch i32 [[V]], label %[[DEFAULT:.*]] [
+; CHECK-NEXT:    switch i32 [[X]], label %[[DEFAULT:.*]] [
 ; CHECK-NEXT:      i32 0, label %[[CASE0:.*]]
 ; CHECK-NEXT:      i32 1, label %[[CASE1:.*]]
 ; CHECK-NEXT:      i32 2, label %[[CASE2:.*]]

>From 753c1c3bf4b3e1bd3617a605ed86fb34d3863172 Mon Sep 17 00:00:00 2001
From: YanWQ-monad <YanWQmonad at gmail.com>
Date: Sun, 16 Jun 2024 00:44:17 +0800
Subject: [PATCH 3/9] Fix typo

---
 llvm/lib/Transforms/Utils/SimplifyCFG.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index 7a16b714e5324..2a0e1f64a398c 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -7104,7 +7104,7 @@ static bool simplifySwitchOfPowersOfTwo(SwitchInst *SI, IRBuilder<> &Builder,
   return true;
 }
 
-// Try to fold switch with manually selecting default branch
+// Try to fold switch with manually selected default branch.
 // For example, for the switch
 //   switch (v) { case 0: case 1: case 2: ... case MaxC: default: }
 //     (continuous cases value from 0 to MaxC)

>From e69f264d41e02bee1804050146116c4cda7a23c8 Mon Sep 17 00:00:00 2001
From: YanWQ-monad <YanWQmonad at gmail.com>
Date: Sun, 16 Jun 2024 00:45:39 +0800
Subject: [PATCH 4/9] Refactor `if` with de morgan

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

diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index 2a0e1f64a398c..a0c2d6ea90c94 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -7115,10 +7115,10 @@ static bool simplifySwitchWithImplicitDefault(SwitchInst *SI) {
   Value *Cond;
   ConstantInt *Bound, *DefaultCase;
   ICmpInst::Predicate Pred;
-  if (!(match(SI->getCondition(),
-              m_Select(m_ICmp(Pred, m_Value(Cond), m_ConstantInt(Bound)),
-                       m_Deferred(Cond), m_ConstantInt(DefaultCase))) &&
-        Pred == CmpInst::ICMP_ULT))
+  if (!match(SI->getCondition(),
+             m_Select(m_ICmp(Pred, m_Value(Cond), m_ConstantInt(Bound)),
+                      m_Deferred(Cond), m_ConstantInt(DefaultCase))) ||
+      Pred != CmpInst::ICMP_ULT)
     return false;
 
   // In an ideal situation, the range of switch cases is continuous,
@@ -7136,7 +7136,7 @@ static bool simplifySwitchWithImplicitDefault(SwitchInst *SI) {
   llvm::sort(Values);
 
   // The cases should be continuous from 0 to some value
-  if (!(*Values.begin() == 0 && *Values.rbegin() == SI->getNumCases() - 1))
+  if (*Values.begin() != 0 || *Values.rbegin() != SI->getNumCases() - 1)
     return false;
 
   SI->setCondition(Cond);

>From 7e92dd6940238bea5f6330ae45b1fd54bf79c9fa Mon Sep 17 00:00:00 2001
From: YanWQ-monad <YanWQmonad at gmail.com>
Date: Sun, 16 Jun 2024 00:55:52 +0800
Subject: [PATCH 5/9] [SimplifyCFG] Use `APInt` to handle `i128` properly

---
 llvm/lib/Transforms/Utils/SimplifyCFG.cpp     | 13 +++--
 .../switch-with-implicit-default.ll           | 49 +++++++++++++++++++
 2 files changed, 55 insertions(+), 7 deletions(-)

diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index a0c2d6ea90c94..67fd22bf09cb1 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -7128,15 +7128,14 @@ static bool simplifySwitchWithImplicitDefault(SwitchInst *SI) {
   if (Bound->getValue() != SI->getNumCases())
     return false;
 
-  SmallVector<uint64_t, 4> Values;
-  for (const auto &Case : SI->cases()) {
-    uint64_t CaseValue = Case.getCaseValue()->getValue().getZExtValue();
-    Values.push_back(CaseValue);
-  }
-  llvm::sort(Values);
+  SmallVector<APInt, 4> Values;
+  for (const auto &Case : SI->cases())
+    Values.push_back(Case.getCaseValue()->getValue());
+  llvm::sort(Values,
+             [](const APInt &lhs, const APInt &rhs) { return lhs.ult(rhs); });
 
   // The cases should be continuous from 0 to some value
-  if (*Values.begin() != 0 || *Values.rbegin() != SI->getNumCases() - 1)
+  if (!Values.begin()->isZero() || *Values.rbegin() != SI->getNumCases() - 1)
     return false;
 
   SI->setCondition(Cond);
diff --git a/llvm/test/Transforms/SimplifyCFG/switch-with-implicit-default.ll b/llvm/test/Transforms/SimplifyCFG/switch-with-implicit-default.ll
index 0107cdc70c95d..a9aa6f814b59c 100644
--- a/llvm/test/Transforms/SimplifyCFG/switch-with-implicit-default.ll
+++ b/llvm/test/Transforms/SimplifyCFG/switch-with-implicit-default.ll
@@ -55,6 +55,55 @@ end:
   ret void
 }
 
+define void @positive_with_i128(i128 %x) {
+; CHECK-LABEL: define void @positive_with_i128(
+; CHECK-SAME: i128 [[X:%.*]]) {
+; CHECK-NEXT:    [[C:%.*]] = icmp ult i128 [[X]], 3
+; CHECK-NEXT:    [[V:%.*]] = select i1 [[C]], i128 [[X]], i128 1
+; CHECK-NEXT:    switch i128 [[X]], label %[[CASE1:.*]] [
+; CHECK-NEXT:      i128 0, label %[[CASE0:.*]]
+; CHECK-NEXT:      i128 2, label %[[CASE2:.*]]
+; CHECK-NEXT:    ]
+; CHECK:       [[CASE0]]:
+; CHECK-NEXT:    tail call void @func0()
+; CHECK-NEXT:    br label %[[END:.*]]
+; CHECK:       [[CASE1]]:
+; CHECK-NEXT:    tail call void @func1()
+; CHECK-NEXT:    br label %[[END]]
+; CHECK:       [[CASE2]]:
+; CHECK-NEXT:    tail call void @func2()
+; CHECK-NEXT:    br label %[[END]]
+; CHECK:       [[END]]:
+; CHECK-NEXT:    ret void
+;
+  %c = icmp ult i128 %x, 3
+  %v = select i1 %c, i128 %x, i128 1
+  switch i128 %v, label %default [
+  i128 0, label %case0
+  i128 1, label %case1
+  i128 2, label %case2
+  ]
+
+default:
+  tail call void @unreachable()
+  br label %end
+
+case0:
+  tail call void @func0()
+  br label %end
+
+case1:
+  tail call void @func1()
+  br label %end
+
+case2:
+  tail call void @func2()
+  br label %end
+
+end:
+  ret void
+}
+
 define void @positive_manual_default_out_of_cases(i32 %x) {
 ; CHECK-LABEL: define void @positive_manual_default_out_of_cases(
 ; CHECK-SAME: i32 [[X:%.*]]) {

>From 7bdf41dc089d2f9e16a1a1e60312cfa092193afd Mon Sep 17 00:00:00 2001
From: YanWQ-monad <YanWQmonad at gmail.com>
Date: Sun, 16 Jun 2024 14:30:32 +0800
Subject: [PATCH 6/9] [SimplifyCFG] Don't use `llvm::sort` to get min/max value

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

diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index 67fd22bf09cb1..289be2808195b 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -7128,14 +7128,17 @@ static bool simplifySwitchWithImplicitDefault(SwitchInst *SI) {
   if (Bound->getValue() != SI->getNumCases())
     return false;
 
-  SmallVector<APInt, 4> Values;
-  for (const auto &Case : SI->cases())
-    Values.push_back(Case.getCaseValue()->getValue());
-  llvm::sort(Values,
-             [](const APInt &lhs, const APInt &rhs) { return lhs.ult(rhs); });
+  APInt Min = SI->case_begin()->getCaseValue()->getValue(), Max = Min;
+  for (const auto &Case : SI->cases()) {
+    const APInt &CaseVal = Case.getCaseValue()->getValue();
+    if (CaseVal.ult(Min))
+      Min = CaseVal;
+    if (CaseVal.ugt(Max))
+      Max = CaseVal;
+  }
 
   // The cases should be continuous from 0 to some value
-  if (!Values.begin()->isZero() || *Values.rbegin() != SI->getNumCases() - 1)
+  if (!Min.isZero() || Max != SI->getNumCases() - 1)
     return false;
 
   SI->setCondition(Cond);

>From 7c0ea4de1487deb7dd992c2b89027bc72dd250b3 Mon Sep 17 00:00:00 2001
From: YanWQ-monad <YanWQmonad at gmail.com>
Date: Sun, 16 Jun 2024 14:40:55 +0800
Subject: [PATCH 7/9] [SimplifyCFG] Also match `icmp ule`

---
 llvm/lib/Transforms/Utils/SimplifyCFG.cpp     | 10 ++--
 .../switch-with-implicit-default.ll           | 49 +++++++++++++++++++
 2 files changed, 56 insertions(+), 3 deletions(-)

diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index 289be2808195b..2ceb5677eb124 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -7117,15 +7117,19 @@ static bool simplifySwitchWithImplicitDefault(SwitchInst *SI) {
   ICmpInst::Predicate Pred;
   if (!match(SI->getCondition(),
              m_Select(m_ICmp(Pred, m_Value(Cond), m_ConstantInt(Bound)),
-                      m_Deferred(Cond), m_ConstantInt(DefaultCase))) ||
-      Pred != CmpInst::ICMP_ULT)
+                      m_Deferred(Cond), m_ConstantInt(DefaultCase))))
+    return false;
+
+  ConstantRange OuterRange =
+      ConstantRange::makeExactICmpRegion(Pred, Bound->getValue());
+  if (!OuterRange.getLower().isZero())
     return false;
 
   // In an ideal situation, the range of switch cases is continuous,
   // and should match the range of ICmp. That is,
   //   MaxCase (declared below) + 1 = SI->getNumCases() = OuterRange.Upper.
   // Checking it partially here can be a fast-fail path
-  if (Bound->getValue() != SI->getNumCases())
+  if (OuterRange.getUpper() != SI->getNumCases())
     return false;
 
   APInt Min = SI->case_begin()->getCaseValue()->getValue(), Max = Min;
diff --git a/llvm/test/Transforms/SimplifyCFG/switch-with-implicit-default.ll b/llvm/test/Transforms/SimplifyCFG/switch-with-implicit-default.ll
index a9aa6f814b59c..11645a9396ee1 100644
--- a/llvm/test/Transforms/SimplifyCFG/switch-with-implicit-default.ll
+++ b/llvm/test/Transforms/SimplifyCFG/switch-with-implicit-default.ll
@@ -55,6 +55,55 @@ end:
   ret void
 }
 
+define void @positive_with_ule(i32 %x) {
+; CHECK-LABEL: define void @positive_with_ule(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[C:%.*]] = icmp ule i32 [[X]], 2
+; CHECK-NEXT:    [[V:%.*]] = select i1 [[C]], i32 [[X]], i32 1
+; CHECK-NEXT:    switch i32 [[X]], label %[[CASE1:.*]] [
+; CHECK-NEXT:      i32 0, label %[[CASE0:.*]]
+; CHECK-NEXT:      i32 2, label %[[CASE2:.*]]
+; CHECK-NEXT:    ]
+; CHECK:       [[CASE0]]:
+; CHECK-NEXT:    tail call void @func0()
+; CHECK-NEXT:    br label %[[END:.*]]
+; CHECK:       [[CASE1]]:
+; CHECK-NEXT:    tail call void @func1()
+; CHECK-NEXT:    br label %[[END]]
+; CHECK:       [[CASE2]]:
+; CHECK-NEXT:    tail call void @func2()
+; CHECK-NEXT:    br label %[[END]]
+; CHECK:       [[END]]:
+; CHECK-NEXT:    ret void
+;
+  %c = icmp ule i32 %x, 2
+  %v = select i1 %c, i32 %x, i32 1
+  switch i32 %v, label %default [
+  i32 0, label %case0
+  i32 1, label %case1
+  i32 2, label %case2
+  ]
+
+default:
+  tail call void @unreachable()
+  br label %end
+
+case0:
+  tail call void @func0()
+  br label %end
+
+case1:
+  tail call void @func1()
+  br label %end
+
+case2:
+  tail call void @func2()
+  br label %end
+
+end:
+  ret void
+}
+
 define void @positive_with_i128(i128 %x) {
 ; CHECK-LABEL: define void @positive_with_i128(
 ; CHECK-SAME: i128 [[X:%.*]]) {

>From 4ef626c4a8c2ad92309c9ae8effe94860c9847f2 Mon Sep 17 00:00:00 2001
From: YanWQ-monad <YanWQmonad at gmail.com>
Date: Sun, 16 Jun 2024 15:04:36 +0800
Subject: [PATCH 8/9] [SimplifyCFG] Check unreachable default for more
 flexibility

---
 llvm/lib/Transforms/Utils/SimplifyCFG.cpp     | 27 ++++++----
 .../switch-with-implicit-default.ll           | 49 +++++++++++++++++++
 2 files changed, 66 insertions(+), 10 deletions(-)

diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index 2ceb5677eb124..c1ba45f86702a 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -7125,11 +7125,14 @@ static bool simplifySwitchWithImplicitDefault(SwitchInst *SI) {
   if (!OuterRange.getLower().isZero())
     return false;
 
-  // In an ideal situation, the range of switch cases is continuous,
-  // and should match the range of ICmp. That is,
+  bool HasDefault =
+      !isa<UnreachableInst>(SI->getDefaultDest()->getFirstNonPHIOrDbg());
+
+  // In an ideal situation, if default is reachable, the range of switch cases
+  // should be continuous, and should match the range of ICmp. That is,
   //   MaxCase (declared below) + 1 = SI->getNumCases() = OuterRange.Upper.
   // Checking it partially here can be a fast-fail path
-  if (OuterRange.getUpper() != SI->getNumCases())
+  if (HasDefault && OuterRange.getUpper() != SI->getNumCases())
     return false;
 
   APInt Min = SI->case_begin()->getCaseValue()->getValue(), Max = Min;
@@ -7141,15 +7144,19 @@ static bool simplifySwitchWithImplicitDefault(SwitchInst *SI) {
       Max = CaseVal;
   }
 
-  // The cases should be continuous from 0 to some value
-  if (!Min.isZero() || Max != SI->getNumCases() - 1)
-    return false;
+  // If the switch has default, the cases should be continuous from 0 to some
+  // value. Otherwise, check whether all the cases satisfy the predicate, that
+  // is, all the cases are in the range `OuterRange`.
+  if ((HasDefault && Min.isZero() && Max == SI->getNumCases() - 1) ||
+      (!HasDefault && Max.ult(OuterRange.getUpper()))) {
+    SI->setCondition(Cond);
+    if (auto It = SI->findCaseValue(DefaultCase); It != SI->case_end())
+      SI->setDefaultDest(It->getCaseSuccessor());
 
-  SI->setCondition(Cond);
-  if (auto It = SI->findCaseValue(DefaultCase); It != SI->case_end())
-    SI->setDefaultDest(It->getCaseSuccessor());
+    return true;
+  }
 
-  return true;
+  return false;
 }
 
 bool SimplifyCFGOpt::simplifySwitch(SwitchInst *SI, IRBuilder<> &Builder) {
diff --git a/llvm/test/Transforms/SimplifyCFG/switch-with-implicit-default.ll b/llvm/test/Transforms/SimplifyCFG/switch-with-implicit-default.ll
index 11645a9396ee1..d8844bca557f3 100644
--- a/llvm/test/Transforms/SimplifyCFG/switch-with-implicit-default.ll
+++ b/llvm/test/Transforms/SimplifyCFG/switch-with-implicit-default.ll
@@ -4,6 +4,7 @@
 declare void @func0()
 declare void @func1()
 declare void @func2()
+declare void @func3()
 declare void @unreachable()
 
 define void @positive(i32 %x) {
@@ -55,6 +56,54 @@ end:
   ret void
 }
 
+define void @positive_with_hole(i32 %x) {
+; CHECK-LABEL: define void @positive_with_hole(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[C:%.*]] = icmp ult i32 [[X]], 5
+; CHECK-NEXT:    [[V:%.*]] = select i1 [[C]], i32 [[X]], i32 1
+; CHECK-NEXT:    switch i32 [[X]], label %[[CASE1:.*]] [
+; CHECK-NEXT:      i32 3, label %[[CASE3:.*]]
+; CHECK-NEXT:      i32 2, label %[[CASE2:.*]]
+; CHECK-NEXT:    ]
+; CHECK:       [[CASE1]]:
+; CHECK-NEXT:    tail call void @func1()
+; CHECK-NEXT:    br label %[[END:.*]]
+; CHECK:       [[CASE2]]:
+; CHECK-NEXT:    tail call void @func2()
+; CHECK-NEXT:    br label %[[END]]
+; CHECK:       [[CASE3]]:
+; CHECK-NEXT:    tail call void @func3()
+; CHECK-NEXT:    br label %[[END]]
+; CHECK:       [[END]]:
+; CHECK-NEXT:    ret void
+;
+  %c = icmp ult i32 %x, 5
+  %v = select i1 %c, i32 %x, i32 1
+  switch i32 %v, label %default [
+  i32 1, label %case1
+  i32 2, label %case2
+  i32 3, label %case3
+  ]
+
+default:
+  unreachable
+
+case1:
+  tail call void @func1()
+  br label %end
+
+case2:
+  tail call void @func2()
+  br label %end
+
+case3:
+  tail call void @func3()
+  br label %end
+
+end:
+  ret void
+}
+
 define void @positive_with_ule(i32 %x) {
 ; CHECK-LABEL: define void @positive_with_ule(
 ; CHECK-SAME: i32 [[X:%.*]]) {

>From e259ac543fceeb876fa1ff1741ee5e96d0b2fd85 Mon Sep 17 00:00:00 2001
From: YanWQ-monad <YanWQmonad at gmail.com>
Date: Sun, 16 Jun 2024 19:22:15 +0800
Subject: [PATCH 9/9] [SimplifyCFG] Use `defaultDestUndefined`

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

diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index c1ba45f86702a..a5b1fbac49fac 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -7125,8 +7125,7 @@ static bool simplifySwitchWithImplicitDefault(SwitchInst *SI) {
   if (!OuterRange.getLower().isZero())
     return false;
 
-  bool HasDefault =
-      !isa<UnreachableInst>(SI->getDefaultDest()->getFirstNonPHIOrDbg());
+  bool HasDefault = !SI->defaultDestUndefined();
 
   // In an ideal situation, if default is reachable, the range of switch cases
   // should be continuous, and should match the range of ICmp. That is,



More information about the llvm-commits mailing list