[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