[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 00:58:49 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/3] [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/3] =?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/3] !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;
   };



More information about the llvm-commits mailing list