[llvm] [InstCombine] Canonicalize `switch(X^C)` expressions to `switch(X)` (PR #143677)

Antonio Frighetto via llvm-commits llvm-commits at lists.llvm.org
Tue Aug 26 08:05:33 PDT 2025


https://github.com/antoniofrighetto updated https://github.com/llvm/llvm-project/pull/143677

>From b92864781a5229c6e8400beb90966cf0412d2afe Mon Sep 17 00:00:00 2001
From: Antonio Frighetto <me at antoniofrighetto.com>
Date: Tue, 26 Aug 2025 16:48:05 +0200
Subject: [PATCH 1/2] [InstCombine] Introduce tests for PR143677 (NFC)

---
 .../test/Transforms/InstCombine/switch-xor.ll | 155 ++++++++++++++++++
 1 file changed, 155 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..113434553ca47
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/switch-xor.ll
@@ -0,0 +1,155 @@
+; 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
+}
+
+; Negative tests.
+
+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
+}
+
+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
+}
+
+define i1 @test_switch_with_xor_by_int_min(i64 %x) {
+; CHECK-LABEL: define i1 @test_switch_with_xor_by_int_min(
+; CHECK-SAME: i64 [[X:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[XOR:%.*]] = xor i64 [[X]], -9223372036854775808
+; CHECK-NEXT:    switch i64 [[XOR]], label %[[SW_DEFAULT:.*]] [
+; CHECK-NEXT:      i64 1, label %[[SW_BB:.*]]
+; CHECK-NEXT:      i64 2, label %[[SW_BB]]
+; CHECK-NEXT:      i64 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 i64 %x, -9223372036854775808
+  switch i64 %xor, label %sw.default [
+  i64 1, label %sw.bb
+  i64 2, label %sw.bb
+  i64 3, label %sw.bb
+  ]
+
+sw.bb:                                            ; preds = %entry, %entry, %entry
+  ret i1 true
+
+sw.default:                                       ; preds = %entry
+  ret i1 false
+}
+
+define i1 @test_switch_with_xor_by_int_max(i64 %x) {
+; CHECK-LABEL: define i1 @test_switch_with_xor_by_int_max(
+; CHECK-SAME: i64 [[X:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[XOR:%.*]] = xor i64 [[X]], 9223372036854775807
+; CHECK-NEXT:    switch i64 [[XOR]], label %[[SW_DEFAULT:.*]] [
+; CHECK-NEXT:      i64 1, label %[[SW_BB:.*]]
+; CHECK-NEXT:      i64 2, label %[[SW_BB]]
+; CHECK-NEXT:      i64 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 i64 %x, 9223372036854775807
+  switch i64 %xor, label %sw.default [
+  i64 1, label %sw.bb
+  i64 2, label %sw.bb
+  i64 3, label %sw.bb
+  ]
+
+sw.bb:                                            ; preds = %entry, %entry, %entry
+  ret i1 true
+
+sw.default:                                       ; preds = %entry
+  ret i1 false
+}
+
+declare void @opaque(i32)

>From 28f403a1283ce174210a7585c4da71749bacda7e Mon Sep 17 00:00:00 2001
From: Antonio Frighetto <me at antoniofrighetto.com>
Date: Tue, 26 Aug 2025 16:48:28 +0200
Subject: [PATCH 2/2] =?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      | 45 +++++++++++--------
 .../Transforms/InstCombine/narrow-switch.ll   |  9 ++--
 .../test/Transforms/InstCombine/switch-xor.ll |  9 ++--
 3 files changed, 34 insertions(+), 29 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp b/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
index 5ee3bb1abe86e..aac4a37f5640c 100644
--- a/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
@@ -4229,26 +4229,33 @@ 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 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 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))) &&
+        !CondOpC->isMinSignedValue() && !CondOpC->isMaxSignedValue())
+      // Change 'switch (X^C) case Case:' into 'switch (X) case Case^C'.
+      // Prevent creation of large case values by excluding extremes.
+      return [](const APInt &Case, const APInt &C) { return Case ^ C; };
+
+    return std::nullopt;
+  };
+
+  // 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));
     }
     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 113434553ca47..dce4c3e050cdb 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



More information about the llvm-commits mailing list