[llvm] [InstCombine] Handle switch-to-select remapping correctly (PR #213302)

via llvm-commits llvm-commits at lists.llvm.org
Fri Jul 31 09:10:57 PDT 2026


https://github.com/arrowten created https://github.com/llvm/llvm-project/pull/213302

Simplifed `switch`es whose input a select that maps one value to another. Handles both equal and non equal forms and also avoids a pointless case when it would not change anything.

Fixes #202212.

>From e9420910864651d8dc642d9a9647e17b43e8abee Mon Sep 17 00:00:00 2001
From: Ajay Wakodikar <ajaywakodikarsocial at gmail.com>
Date: Fri, 31 Jul 2026 12:00:19 -0400
Subject: [PATCH] [InstCombine] Handle switch-to-select remapping correctly

---
 .../InstCombine/InstructionCombining.cpp      |  40 ++++++
 .../InstCombine/switch-select-remap.ll        | 136 ++++++++++++++++++
 2 files changed, 176 insertions(+)
 create mode 100644 llvm/test/Transforms/InstCombine/switch-select-remap.ll

diff --git a/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp b/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
index 2663510efed35..8e1dac1241848 100644
--- a/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
@@ -4577,6 +4577,46 @@ Instruction *InstCombinerImpl::visitSwitchInst(SwitchInst &SI) {
     if (Value *V =
             simplifySwitchOnSelectUsingRanges(SI, Select, /*IsTrueArm=*/false))
       return replaceOperand(SI, 0, V);
+
+    // Fold switch(select(icmp eq X, C, K, X)) into switch(X), retargeting
+    // (or adding) the case for C to wherever K currently dispatches to:
+    //   %cmp = icmp eq T %x, C
+    //   %key = select i1 %cmp, T K, T %x
+    //   switch T %key, label %default [ T K, label %case_k ... ]
+    // becomes
+    //   switch T %x, label %default [ T C, label %case_k
+    //                                  T K, label %case_k ... ]
+    CmpPredicate Pred;
+    Value *X;
+    ConstantInt *C;
+
+    if (match(Select->getCondition(),
+              m_c_ICmp(Pred, m_Value(X), m_ConstantInt(C))) &&
+        ICmpInst::isEquality(Pred)) {
+      Value *TrueVal = Select->getTrueValue();
+      Value *FalseVal = Select->getFalseValue();
+
+      // Normalize to select(icmp eq X, C, K, X).
+      if (Pred == ICmpInst::ICMP_NE)
+        std::swap(TrueVal, FalseVal);
+      if (FalseVal == X) {
+        if (auto *K = dyn_cast<ConstantInt>(TrueVal)) {
+          // X == C is redirected to K, so case C should go where K does.
+          BasicBlock *DestFork = SI.findCaseValue(K)->getCaseSuccessor();
+          auto CaseC = SI.findCaseValue(C);
+
+          if (CaseC == SI.case_default()) {
+            if (DestFork != SI.getDefaultDest()) {
+              SwitchInstProfUpdateWrapper SIW(SI);
+              SIW.addCase(C, DestFork, std::nullopt);
+            }
+          } else {
+            CaseC->setSuccessor(DestFork);
+          }
+          return replaceOperand(SI, 0, X);
+        }
+      }
+    }
   }
 
   KnownBits Known = computeKnownBits(Cond, &SI);
diff --git a/llvm/test/Transforms/InstCombine/switch-select-remap.ll b/llvm/test/Transforms/InstCombine/switch-select-remap.ll
new file mode 100644
index 0000000000000..49f3bd121d72e
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/switch-select-remap.ll
@@ -0,0 +1,136 @@
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+; The compared value 4 has no explicit case, and the remapped value 6 maps to
+; a real (non-default) case, so switching on %x needs a new explicit case for
+; 4 pointing to bb2.
+define void @test_remap_add_case(i8 %x) {
+; CHECK-LABEL: define void @test_remap_add_case(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT:    switch i8 [[X]], label [[BB1:%.*]] [
+; CHECK-NEXT:      i8 6, label [[BB2:%.*]]
+; CHECK-NEXT:      i8 10, label [[BB3:%.*]]
+; CHECK-NEXT:      i8 4, label [[BB2]]
+; CHECK-NEXT:    ]
+;
+  %cmp = icmp eq i8 %x, 4
+  %key = select i1 %cmp, i8 6, i8 %x
+  switch i8 %key, label %bb1 [
+    i8 6, label %bb2
+    i8 10, label %bb3
+  ]
+
+bb1:
+  call void @func1()
+  unreachable
+bb2:
+  call void @func2()
+  unreachable
+bb3:
+  call void @func3()
+  unreachable
+}
+
+; The value 4 already has an explicit case pointing to bb4, but %key can never
+; actually be 4 (it's remapped to 6 whenever %x is 4), so that case is really
+; dead and should be retargeted to wherever the remapped value 6 dispatches to
+; (bb2). bb4 loses its only predecessor, but the fold doesn't clean up its now-dead
+; body itself (that's left to a follow-up DCE/SimplifyCFG run), so this
+; function needs instcombine-no-verify-fixpoint to accept the one-pass result.
+define void @test_remap_retarget_case(i8 %x) #0 {
+; CHECK-LABEL: define void @test_remap_retarget_case(
+; CHECK-SAME: i8 [[X:%.*]]) #[[ATTR0:[0-9]+]] {
+; CHECK-NEXT:    switch i8 [[X]], label [[BB1:%.*]] [
+; CHECK-NEXT:      i8 4, label [[BB2:%.*]]
+; CHECK-NEXT:      i8 6, label [[BB2]]
+; CHECK-NEXT:      i8 10, label [[BB3:%.*]]
+; CHECK-NEXT:    ]
+;
+  %cmp = icmp eq i8 %x, 4
+  %key = select i1 %cmp, i8 6, i8 %x
+  switch i8 %key, label %bb1 [
+    i8 4, label %bb4
+    i8 6, label %bb2
+    i8 10, label %bb3
+  ]
+
+bb1:
+  call void @func1()
+  unreachable
+bb2:
+  call void @func2()
+  unreachable
+bb3:
+  call void @func3()
+  unreachable
+bb4:
+  call void @func4()
+  unreachable
+}
+
+; Same remap expressed with icmp ne / select(cond, %x, 6). The remapped value 6
+; maps to a real (non-default) case, so switching on %x needs a new explicit
+; case for 4 pointing to bb2, same as test_remap_add_case but via the NE arm.
+define void @test_remap_ne_add_case(i8 %x) {
+; CHECK-LABEL: define void @test_remap_ne_add_case(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT:    switch i8 [[X]], label [[BB1:%.*]] [
+; CHECK-NEXT:      i8 6, label [[BB2:%.*]]
+; CHECK-NEXT:      i8 10, label [[BB3:%.*]]
+; CHECK-NEXT:      i8 4, label [[BB2]]
+; CHECK-NEXT:    ]
+;
+  %cmp = icmp ne i8 %x, 4
+  %key = select i1 %cmp, i8 %x, i8 6
+  switch i8 %key, label %bb1 [
+    i8 6, label %bb2
+    i8 10, label %bb3
+  ]
+
+bb1:
+  call void @func1()
+  unreachable
+bb2:
+  call void @func2()
+  unreachable
+bb3:
+  call void @func3()
+  unreachable
+}
+
+; Negative test: the select's non-constant arm (%y) doesn't match the icmp's
+; non-constant operand (%x), so this isn't a same-value remap and must not
+; be folded.
+define void @test_remap_mismatched_operand(i8 %x, i8 %y) {
+; CHECK-LABEL: define void @test_remap_mismatched_operand(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i8 [[X]], 4
+; CHECK-NEXT:    [[KEY:%.*]] = select i1 [[CMP]], i8 6, i8 [[Y]]
+; CHECK-NEXT:    switch i8 [[KEY]], label [[BB1:%.*]] [
+; CHECK-NEXT:      i8 6, label [[BB2:%.*]]
+; CHECK-NEXT:      i8 10, label [[BB3:%.*]]
+; CHECK-NEXT:    ]
+;
+  %cmp = icmp eq i8 %x, 4
+  %key = select i1 %cmp, i8 6, i8 %y
+  switch i8 %key, label %bb1 [
+    i8 6, label %bb2
+    i8 10, label %bb3
+  ]
+
+bb1:
+  call void @func1()
+  unreachable
+bb2:
+  call void @func2()
+  unreachable
+bb3:
+  call void @func3()
+  unreachable
+}
+
+declare void @func1()
+declare void @func2()
+declare void @func3()
+declare void @func4()
+
+attributes #0 = { "instcombine-no-verify-fixpoint" }



More information about the llvm-commits mailing list