[llvm] [InstCombine] Canonicalize `switch(X^C)` expressions to `switch(X)` (PR #143677)
Antonio Frighetto via llvm-commits
llvm-commits at lists.llvm.org
Fri Jun 13 01:51:54 PDT 2025
https://github.com/antoniofrighetto updated https://github.com/llvm/llvm-project/pull/143677
>From c13a97987620a90d93074281ef76fd3794e3b54b Mon Sep 17 00:00:00 2001
From: Antonio Frighetto <me at antoniofrighetto.com>
Date: Wed, 11 Jun 2025 12:04:12 +0200
Subject: [PATCH 1/4] [InstCombine] Introduce tests (NFC)
---
.../test/Transforms/InstCombine/switch-xor.ll | 60 +++++++++++++++++++
1 file changed, 60 insertions(+)
create mode 100644 llvm/test/Transforms/InstCombine/switch-xor.ll
diff --git a/llvm/test/Transforms/InstCombine/switch-xor.ll b/llvm/test/Transforms/InstCombine/switch-xor.ll
new file mode 100644
index 0000000000000..c1b055c1e2c0f
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/switch-xor.ll
@@ -0,0 +1,60 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+define i1 @test_switch_with_xor(i32 %x) {
+; CHECK-LABEL: define i1 @test_switch_with_xor(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[XOR:%.*]] = xor i32 [[X]], 2
+; CHECK-NEXT: switch i32 [[XOR]], label %[[SW_DEFAULT:.*]] [
+; CHECK-NEXT: i32 1, label %[[SW_BB:.*]]
+; CHECK-NEXT: i32 2, label %[[SW_BB]]
+; CHECK-NEXT: i32 3, label %[[SW_BB]]
+; CHECK-NEXT: ]
+; CHECK: [[SW_BB]]:
+; CHECK-NEXT: ret i1 true
+; CHECK: [[SW_DEFAULT]]:
+; CHECK-NEXT: ret i1 false
+;
+entry:
+ %xor = xor i32 %x, 2
+ switch i32 %xor, label %sw.default [
+ i32 1, label %sw.bb
+ i32 2, label %sw.bb
+ i32 3, label %sw.bb
+ ]
+
+sw.bb:
+ ret i1 true
+sw.default:
+ ret i1 false
+}
+
+define i1 @test_switch_with_xor_nonconstant_ops(i32 %x, i32 %y) {
+; CHECK-LABEL: define i1 @test_switch_with_xor_nonconstant_ops(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[XOR:%.*]] = xor i32 [[X]], [[Y]]
+; CHECK-NEXT: switch i32 [[XOR]], label %[[SW_DEFAULT:.*]] [
+; CHECK-NEXT: i32 1, label %[[SW_BB:.*]]
+; CHECK-NEXT: i32 2, label %[[SW_BB]]
+; CHECK-NEXT: i32 3, label %[[SW_BB]]
+; CHECK-NEXT: ]
+; CHECK: [[SW_BB]]:
+; CHECK-NEXT: ret i1 true
+; CHECK: [[SW_DEFAULT]]:
+; CHECK-NEXT: ret i1 false
+;
+entry:
+ %xor = xor i32 %x, %y
+ switch i32 %xor, label %sw.default [
+ i32 1, label %sw.bb
+ i32 2, label %sw.bb
+ i32 3, label %sw.bb
+ ]
+
+sw.bb:
+ ret i1 true
+sw.default:
+ ret i1 false
+}
>From 3933698e9b2e9b6b9deb5fe55ccd169280fb99eb Mon Sep 17 00:00:00 2001
From: Antonio Frighetto <me at antoniofrighetto.com>
Date: Wed, 11 Jun 2025 12:06:20 +0200
Subject: [PATCH 2/4] =?UTF-8?q?[InstCombine]=C2=A0Canonicalize=20`switch(X?=
=?UTF-8?q?^C)`=20to=20`switch(X)`?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
`switch(X^C)` expressions can be folded to `switch(X)`. Minor
opportunity to generalize simplifications in `visitSwitchInst`
via an inverse function helper as well.
Proof: https://alive2.llvm.org/ce/z/TMRy_3.
---
.../InstCombine/InstructionCombining.cpp | 41 ++++++++++---------
.../Transforms/InstCombine/narrow-switch.ll | 9 ++--
.../test/Transforms/InstCombine/switch-xor.ll | 9 ++--
3 files changed, 30 insertions(+), 29 deletions(-)
diff --git a/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp b/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
index e261807bbc035..fb5e6981a4398 100644
--- a/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
@@ -3878,26 +3878,29 @@ static Value *simplifySwitchOnSelectUsingRanges(SwitchInst &SI,
Instruction *InstCombinerImpl::visitSwitchInst(SwitchInst &SI) {
Value *Cond = SI.getCondition();
Value *Op0;
- ConstantInt *AddRHS;
- if (match(Cond, m_Add(m_Value(Op0), m_ConstantInt(AddRHS)))) {
- // Change 'switch (X+4) case 1:' into 'switch (X) case -3'.
- for (auto Case : SI.cases()) {
- Constant *NewCase = ConstantExpr::getSub(Case.getCaseValue(), AddRHS);
- assert(isa<ConstantInt>(NewCase) &&
- "Result of expression should be constant");
- Case.setValue(cast<ConstantInt>(NewCase));
- }
- return replaceOperand(SI, 0, Op0);
- }
+ const APInt *CondOpC;
+ using InvertFn = std::function<APInt(const APInt &Case, const APInt &C)>;
- ConstantInt *SubLHS;
- if (match(Cond, m_Sub(m_ConstantInt(SubLHS), m_Value(Op0)))) {
- // Change 'switch (1-X) case 1:' into 'switch (X) case 0'.
- for (auto Case : SI.cases()) {
- Constant *NewCase = ConstantExpr::getSub(SubLHS, Case.getCaseValue());
- assert(isa<ConstantInt>(NewCase) &&
- "Result of expression should be constant");
- Case.setValue(cast<ConstantInt>(NewCase));
+ auto MaybeInvertible = [&](Value *Cond) -> std::optional<InvertFn> {
+ if (match(Cond, m_Add(m_Value(Op0), m_APInt(CondOpC))))
+ // Change 'switch (X+C) case A:' into 'switch (X) case C-A'.
+ return [](const APInt &C, const APInt &Case) { return C - Case; };
+
+ if (match(Cond, m_Sub(m_APInt(CondOpC), m_Value(Op0))))
+ // Change 'switch (C-X) case A:' into 'switch (X) case A-C'.
+ return [](const APInt &C, const APInt &Case) { return Case - C; };
+
+ if (match(Cond, m_Xor(m_Value(Op0), m_APInt(CondOpC))))
+ // Change 'switch (X^C) case A:' into 'switch (X) case A^C'.
+ return [](const APInt &C, const APInt &Case) { return C ^ Case; };
+
+ return std::nullopt;
+ };
+
+ if (auto InvertFn = MaybeInvertible(Cond)) {
+ for (auto &Case : SI.cases()) {
+ const APInt &New = (*InvertFn)(Case.getCaseValue()->getValue(), *CondOpC);
+ Case.setValue(ConstantInt::get(SI.getContext(), New));
}
return replaceOperand(SI, 0, Op0);
}
diff --git a/llvm/test/Transforms/InstCombine/narrow-switch.ll b/llvm/test/Transforms/InstCombine/narrow-switch.ll
index 90f56a61fa410..22859c90f8b78 100644
--- a/llvm/test/Transforms/InstCombine/narrow-switch.ll
+++ b/llvm/test/Transforms/InstCombine/narrow-switch.ll
@@ -235,11 +235,10 @@ define i32 @trunc32to16(i32 %a0) #0 {
; ALL-NEXT: [[RETVAL:%.*]] = alloca i32, align 4
; ALL-NEXT: [[XOR:%.*]] = lshr i32 [[A0]], 16
; ALL-NEXT: [[TMP0:%.*]] = trunc nuw i32 [[XOR]] to i16
-; ALL-NEXT: [[TRUNC:%.*]] = xor i16 [[TMP0]], 15784
-; ALL-NEXT: switch i16 [[TRUNC]], label %[[SW_EPILOG:.*]] [
-; ALL-NEXT: i16 63, label %[[SW_BB:.*]]
-; ALL-NEXT: i16 1, label %[[SW_BB1:.*]]
-; ALL-NEXT: i16 100, label %[[SW_BB2:.*]]
+; ALL-NEXT: switch i16 [[TMP0]], label %[[SW_EPILOG:.*]] [
+; ALL-NEXT: i16 15767, label %[[SW_BB:.*]]
+; ALL-NEXT: i16 15785, label %[[SW_BB1:.*]]
+; ALL-NEXT: i16 15820, label %[[SW_BB2:.*]]
; ALL-NEXT: ]
; ALL: [[SW_BB]]:
; ALL-NEXT: store i32 90, ptr [[RETVAL]], align 4
diff --git a/llvm/test/Transforms/InstCombine/switch-xor.ll b/llvm/test/Transforms/InstCombine/switch-xor.ll
index c1b055c1e2c0f..a7b65e406dfa2 100644
--- a/llvm/test/Transforms/InstCombine/switch-xor.ll
+++ b/llvm/test/Transforms/InstCombine/switch-xor.ll
@@ -5,11 +5,10 @@ define i1 @test_switch_with_xor(i32 %x) {
; CHECK-LABEL: define i1 @test_switch_with_xor(
; CHECK-SAME: i32 [[X:%.*]]) {
; CHECK-NEXT: [[ENTRY:.*:]]
-; CHECK-NEXT: [[XOR:%.*]] = xor i32 [[X]], 2
-; CHECK-NEXT: switch i32 [[XOR]], label %[[SW_DEFAULT:.*]] [
-; CHECK-NEXT: i32 1, label %[[SW_BB:.*]]
-; CHECK-NEXT: i32 2, label %[[SW_BB]]
-; CHECK-NEXT: i32 3, label %[[SW_BB]]
+; CHECK-NEXT: switch i32 [[X]], label %[[SW_DEFAULT:.*]] [
+; CHECK-NEXT: i32 3, label %[[SW_BB:.*]]
+; CHECK-NEXT: i32 0, label %[[SW_BB]]
+; CHECK-NEXT: i32 1, label %[[SW_BB]]
; CHECK-NEXT: ]
; CHECK: [[SW_BB]]:
; CHECK-NEXT: ret i1 true
>From cbee75e64f594b049d4c007a5566063f8aa375e4 Mon Sep 17 00:00:00 2001
From: Antonio Frighetto <me at antoniofrighetto.com>
Date: Fri, 13 Jun 2025 09:58:24 +0200
Subject: [PATCH 3/4] !fixup respect operands order
---
.../Transforms/InstCombine/InstructionCombining.cpp | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp b/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
index fb5e6981a4398..9847750de625a 100644
--- a/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
@@ -3883,16 +3883,16 @@ Instruction *InstCombinerImpl::visitSwitchInst(SwitchInst &SI) {
auto MaybeInvertible = [&](Value *Cond) -> std::optional<InvertFn> {
if (match(Cond, m_Add(m_Value(Op0), m_APInt(CondOpC))))
- // Change 'switch (X+C) case A:' into 'switch (X) case C-A'.
- return [](const APInt &C, const APInt &Case) { return C - Case; };
+ // Change 'switch (X+C) case Case:' into 'switch (X) case Case-C'.
+ return [](const APInt &Case, const APInt &C) { return Case - C; };
if (match(Cond, m_Sub(m_APInt(CondOpC), m_Value(Op0))))
- // Change 'switch (C-X) case A:' into 'switch (X) case A-C'.
- return [](const APInt &C, const APInt &Case) { return Case - C; };
+ // Change 'switch (C-X) case Case:' into 'switch (X) case C-Case'.
+ return [](const APInt &Case, const APInt &C) { return C - Case; };
if (match(Cond, m_Xor(m_Value(Op0), m_APInt(CondOpC))))
- // Change 'switch (X^C) case A:' into 'switch (X) case A^C'.
- return [](const APInt &C, const APInt &Case) { return C ^ Case; };
+ // Change 'switch (X^C) case Case:' into 'switch (X) case Case^C'.
+ return [](const APInt &Case, const APInt &C) { return Case ^ C; };
return std::nullopt;
};
>From f61aa1ecf04d5f310932f6b6142614842cd89ba9 Mon Sep 17 00:00:00 2001
From: Antonio Frighetto <me at antoniofrighetto.com>
Date: Fri, 13 Jun 2025 10:48:47 +0200
Subject: [PATCH 4/4] !fixup condition has one use
---
.../InstCombine/InstructionCombining.cpp | 4 ++-
.../test/Transforms/InstCombine/switch-xor.ll | 33 +++++++++++++++++++
2 files changed, 36 insertions(+), 1 deletion(-)
diff --git a/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp b/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
index 9847750de625a..7303c8003f175 100644
--- a/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
@@ -3897,7 +3897,9 @@ Instruction *InstCombinerImpl::visitSwitchInst(SwitchInst &SI) {
return std::nullopt;
};
- if (auto InvertFn = MaybeInvertible(Cond)) {
+ // Attempt to invert and simplify the switch condition, as long as the
+ // condition is not used further, as it may be unprofitable otherwise.
+ if (auto InvertFn = MaybeInvertible(Cond); InvertFn && Cond->hasOneUse()) {
for (auto &Case : SI.cases()) {
const APInt &New = (*InvertFn)(Case.getCaseValue()->getValue(), *CondOpC);
Case.setValue(ConstantInt::get(SI.getContext(), New));
diff --git a/llvm/test/Transforms/InstCombine/switch-xor.ll b/llvm/test/Transforms/InstCombine/switch-xor.ll
index a7b65e406dfa2..036e1df05f9e3 100644
--- a/llvm/test/Transforms/InstCombine/switch-xor.ll
+++ b/llvm/test/Transforms/InstCombine/switch-xor.ll
@@ -57,3 +57,36 @@ sw.bb:
sw.default:
ret i1 false
}
+
+define i1 @test_switch_with_xor_condition_multiple_uses(i32 %x) {
+; CHECK-LABEL: define i1 @test_switch_with_xor_condition_multiple_uses(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[XOR:%.*]] = xor i32 [[X]], 2
+; CHECK-NEXT: call void @opaque(i32 [[XOR]])
+; CHECK-NEXT: switch i32 [[XOR]], label %[[SW_DEFAULT:.*]] [
+; CHECK-NEXT: i32 1, label %[[SW_BB:.*]]
+; CHECK-NEXT: i32 2, label %[[SW_BB]]
+; CHECK-NEXT: i32 3, label %[[SW_BB]]
+; CHECK-NEXT: ]
+; CHECK: [[SW_BB]]:
+; CHECK-NEXT: ret i1 true
+; CHECK: [[SW_DEFAULT]]:
+; CHECK-NEXT: ret i1 false
+;
+entry:
+ %xor = xor i32 %x, 2
+ call void @opaque(i32 %xor)
+ switch i32 %xor, label %sw.default [
+ i32 1, label %sw.bb
+ i32 2, label %sw.bb
+ i32 3, label %sw.bb
+ ]
+
+sw.bb:
+ ret i1 true
+sw.default:
+ ret i1 false
+}
+
+declare void @opaque(i32)
More information about the llvm-commits
mailing list