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

via llvm-commits llvm-commits at lists.llvm.org
Sat Jun 15 09:01:07 PDT 2024


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

Fold the switch like
``` llvm
define void @src(i32 %x) {
  %w = icmp ult i32 %x, 3
  %v = select i1 %w, i32 %x, i32 1
  switch i32 %v, label %default [
    i32 0, label %bb0
    i32 1, label %bb1
    i32 2, label %bb2
  ]
  ; ...
}
```
to
``` llvm
define void @tgt(i32 %x) {
  switch i32 %x, label %bb1 [
    i32 0, label %bb0
    i32 1, label %bb1
    i32 2, label %bb2
  ]
  ; ...
}
```
since in `@src`, `icmp ult` is basically to test if `%x` in switch's range, and if not, choose another value as default case in the following `select`. So we can make this "manually chosen default" to switch's builtin default.

Alive2: https://alive2.llvm.org/ce/z/W_pQHw, and https://alive2.llvm.org/ce/z/Ve5ThT (corresponding to `positive_manual_default_out_of_cases` in the test case).

This pattern seems to be very common in Rust (and it's how Rust niche optimization of `enum` tag works), for example,
https://github.com/dtcxzyw/llvm-opt-benchmark/blob/1d607a918383456782039836cb014543e28ed7f9/bench/coreutils-rs/optimized/2eifmygohquvvuy.ll#L873-L882 and https://play.rust-lang.org/?version=stable&mode=release&edition=2021&gist=169a7315259359c73c461fea15fa4783.

>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/2] [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/2] [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:.*]]



More information about the llvm-commits mailing list