[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