[llvm] [InstCombine] Fold inner select (PR #226371)

Henry Jiang via llvm-commits llvm-commits at lists.llvm.org
Thu Sep 24 23:18:01 PDT 2026


https://github.com/mustartt created https://github.com/llvm/llvm-project/pull/226371

None

>From e279d9a08c0083ac729c1f6454f30b999ddbec67 Mon Sep 17 00:00:00 2001
From: Henry Jiang <henry_jiang2 at apple.com>
Date: Thu, 24 Sep 2026 23:15:37 -0700
Subject: [PATCH] Limited InstCombine fold

---
 .../InstCombine/InstCombineInternal.h         |   1 +
 .../InstCombine/InstCombineSelect.cpp         |  41 ++++
 .../select-arm-same-cond-select.ll            | 203 ++++++++++++++++++
 llvm/test/Transforms/InstCombine/select.ll    |   5 +-
 4 files changed, 247 insertions(+), 3 deletions(-)
 create mode 100644 llvm/test/Transforms/InstCombine/select-arm-same-cond-select.ll

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineInternal.h b/llvm/lib/Transforms/InstCombine/InstCombineInternal.h
index f2549bad3a8523..8df6233e3a05c6 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineInternal.h
+++ b/llvm/lib/Transforms/InstCombine/InstCombineInternal.h
@@ -831,6 +831,7 @@ class LLVM_LIBRARY_VISIBILITY InstCombinerImpl final
 
   bool replaceInInstruction(Value *V, Value *Old, Value *New,
                             unsigned Depth = 0);
+  Instruction *foldSelectArmOperandsOnSameCond(SelectInst &SI, bool TrueArm);
 
   Value *insertRangeTest(Value *V, const APInt &Lo, const APInt &Hi,
                          bool isSigned, bool Inside);
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
index a4061bbd607707..ab2d8a9e02431b 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
@@ -1685,6 +1685,40 @@ bool InstCombinerImpl::replaceInInstruction(Value *V, Value *Old, Value *New,
   return Changed;
 }
 
+Instruction *InstCombinerImpl::foldSelectArmOperandsOnSameCond(SelectInst &SI,
+                                                               bool TrueArm) {
+  Value *Cond = SI.getCondition();
+  unsigned ArmIdx = TrueArm ? 1 : 2;
+  auto *I = dyn_cast<Instruction>(SI.getOperand(ArmIdx));
+  if (!I || isa<PHINode>(I) ||
+      !isSafeToSpeculativelyExecuteWithVariableReplaced(I))
+    return nullptr;
+
+  if (Cond->getType()->isVectorTy() && !isNotCrossLaneOperation(I))
+    return nullptr;
+
+  auto GetPickedValue = [&](Value *V) -> Value * {
+    auto *Inner = dyn_cast<SelectInst>(V);
+    if (!Inner || Inner->getCondition() != Cond)
+      return nullptr;
+    return TrueArm ? Inner->getTrueValue() : Inner->getFalseValue();
+  };
+  if (none_of(I->operands(), [&](Value *V) { return GetPickedValue(V); }))
+    return nullptr;
+
+  for (Use &U : I->uses()) {
+    auto *User = dyn_cast<SelectInst>(U.getUser());
+    if (!User || User->getCondition() != Cond || U.getOperandNo() != ArmIdx)
+      return nullptr;
+  }
+
+  for (Use &Op : I->operands())
+    if (Value *V = GetPickedValue(Op))
+      replaceUse(Op, V);
+  Worklist.add(I);
+  return &SI;
+}
+
 /// If we have a select with an equality comparison, then we know the value in
 /// one of the arms of the select. See if substituting this value into an arm
 /// and simplifying the result yields the same value as the other arm.
@@ -4648,6 +4682,13 @@ Instruction *InstCombinerImpl::visitSelectInst(SelectInst &SI) {
       return &SI;
   }
 
+  if (!isa<Constant>(CondVal)) {
+    if (Instruction *R = foldSelectArmOperandsOnSameCond(SI, /*TrueArm=*/true))
+      return R;
+    if (Instruction *R = foldSelectArmOperandsOnSameCond(SI, /*TrueArm=*/false))
+      return R;
+  }
+
   if (Instruction *R = foldSelectOfBools(SI))
     return R;
 
diff --git a/llvm/test/Transforms/InstCombine/select-arm-same-cond-select.ll b/llvm/test/Transforms/InstCombine/select-arm-same-cond-select.ll
new file mode 100644
index 00000000000000..a557336565b599
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/select-arm-same-cond-select.ll
@@ -0,0 +1,203 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+declare void @use(i32)
+
+define { i32, i32 } @arm_multiuse_same_side(i1 %c, i32 %a, i32 %b, i32 %x, i32 %y, i32 %z) {
+; CHECK-LABEL: @arm_multiuse_same_side(
+; CHECK-NEXT:    [[S:%.*]] = select i1 [[C:%.*]], i32 [[A:%.*]], i32 [[B:%.*]]
+; CHECK-NEXT:    call void @use(i32 [[S]])
+; CHECK-NEXT:    [[V:%.*]] = mul i32 [[A]], [[X:%.*]]
+; CHECK-NEXT:    [[R0:%.*]] = select i1 [[C]], i32 [[V]], i32 [[Y:%.*]]
+; CHECK-NEXT:    [[R1:%.*]] = select i1 [[C]], i32 [[V]], i32 [[Z:%.*]]
+; CHECK-NEXT:    [[I0:%.*]] = insertvalue { i32, i32 } poison, i32 [[R0]], 0
+; CHECK-NEXT:    [[I1:%.*]] = insertvalue { i32, i32 } [[I0]], i32 [[R1]], 1
+; CHECK-NEXT:    ret { i32, i32 } [[I1]]
+;
+  %s = select i1 %c, i32 %a, i32 %b
+  call void @use(i32 %s)
+  %v = mul i32 %s, %x
+  %r0 = select i1 %c, i32 %v, i32 %y
+  %r1 = select i1 %c, i32 %v, i32 %z
+  %i0 = insertvalue { i32, i32 } poison, i32 %r0, 0
+  %i1 = insertvalue { i32, i32 } %i0, i32 %r1, 1
+  ret { i32, i32 } %i1
+}
+
+define { i32, i32 } @counter_pair(i1 %c, i32 %a, i32 %b) {
+; CHECK-LABEL: @counter_pair(
+; CHECK-NEXT:    [[S:%.*]] = select i1 [[C:%.*]], i32 [[B:%.*]], i32 [[A:%.*]]
+; CHECK-NEXT:    [[INC:%.*]] = add i32 [[S]], 1
+; CHECK-NEXT:    [[AN:%.*]] = select i1 [[C]], i32 [[A]], i32 [[INC]]
+; CHECK-NEXT:    [[BN:%.*]] = select i1 [[C]], i32 [[INC]], i32 [[B]]
+; CHECK-NEXT:    [[I0:%.*]] = insertvalue { i32, i32 } poison, i32 [[AN]], 0
+; CHECK-NEXT:    [[I1:%.*]] = insertvalue { i32, i32 } [[I0]], i32 [[BN]], 1
+; CHECK-NEXT:    ret { i32, i32 } [[I1]]
+;
+  %s = select i1 %c, i32 %b, i32 %a
+  %inc = add i32 %s, 1
+  %an = select i1 %c, i32 %a, i32 %inc
+  %bn = select i1 %c, i32 %inc, i32 %b
+  %i0 = insertvalue { i32, i32 } poison, i32 %an, 0
+  %i1 = insertvalue { i32, i32 } %i0, i32 %bn, 1
+  ret { i32, i32 } %i1
+}
+
+define { i32, i32, i32 } @counter_pair_live_inner(i1 %c, i32 %a, i32 %b) {
+; CHECK-LABEL: @counter_pair_live_inner(
+; CHECK-NEXT:    [[S:%.*]] = select i1 [[C:%.*]], i32 [[B:%.*]], i32 [[A:%.*]]
+; CHECK-NEXT:    [[INC:%.*]] = add nuw i32 [[S]], 1
+; CHECK-NEXT:    [[AN:%.*]] = select i1 [[C]], i32 [[A]], i32 [[INC]]
+; CHECK-NEXT:    [[BN:%.*]] = select i1 [[C]], i32 [[INC]], i32 [[B]]
+; CHECK-NEXT:    [[I0:%.*]] = insertvalue { i32, i32, i32 } poison, i32 [[AN]], 0
+; CHECK-NEXT:    [[I1:%.*]] = insertvalue { i32, i32, i32 } [[I0]], i32 [[BN]], 1
+; CHECK-NEXT:    [[I2:%.*]] = insertvalue { i32, i32, i32 } [[I1]], i32 [[S]], 2
+; CHECK-NEXT:    ret { i32, i32, i32 } [[I2]]
+;
+  %s = select i1 %c, i32 %b, i32 %a
+  %inc = add nuw i32 %s, 1
+  %an = select i1 %c, i32 %a, i32 %inc
+  %bn = select i1 %c, i32 %inc, i32 %b
+  %i0 = insertvalue { i32, i32, i32 } poison, i32 %an, 0
+  %i1 = insertvalue { i32, i32, i32 } %i0, i32 %bn, 1
+  %i2 = insertvalue { i32, i32, i32 } %i1, i32 %s, 2
+  ret { i32, i32, i32 } %i2
+}
+
+define { i32, i32 } @not_pair(i1 %c, i32 %a, i32 %b) {
+; CHECK-LABEL: @not_pair(
+; CHECK-NEXT:    [[S:%.*]] = select i1 [[C:%.*]], i32 [[A:%.*]], i32 [[B:%.*]]
+; CHECK-NEXT:    [[N:%.*]] = xor i32 [[S]], -1
+; CHECK-NEXT:    [[AN:%.*]] = select i1 [[C]], i32 [[N]], i32 [[A]]
+; CHECK-NEXT:    [[BN:%.*]] = select i1 [[C]], i32 [[B]], i32 [[N]]
+; CHECK-NEXT:    [[I0:%.*]] = insertvalue { i32, i32 } poison, i32 [[AN]], 0
+; CHECK-NEXT:    [[I1:%.*]] = insertvalue { i32, i32 } [[I0]], i32 [[BN]], 1
+; CHECK-NEXT:    ret { i32, i32 } [[I1]]
+;
+  %s = select i1 %c, i32 %a, i32 %b
+  %n = xor i32 %s, -1
+  %an = select i1 %c, i32 %n, i32 %a
+  %bn = select i1 %c, i32 %b, i32 %n
+  %i0 = insertvalue { i32, i32 } poison, i32 %an, 0
+  %i1 = insertvalue { i32, i32 } %i0, i32 %bn, 1
+  ret { i32, i32 } %i1
+}
+
+define { <4 x i32>, <4 x i32> } @counter_pair_vec(<4 x i1> %c, <4 x i32> %a, <4 x i32> %b) {
+; CHECK-LABEL: @counter_pair_vec(
+; CHECK-NEXT:    [[S:%.*]] = select <4 x i1> [[C:%.*]], <4 x i32> [[B:%.*]], <4 x i32> [[A:%.*]]
+; CHECK-NEXT:    [[INC:%.*]] = add <4 x i32> [[S]], splat (i32 1)
+; CHECK-NEXT:    [[AN:%.*]] = select <4 x i1> [[C]], <4 x i32> [[A]], <4 x i32> [[INC]]
+; CHECK-NEXT:    [[BN:%.*]] = select <4 x i1> [[C]], <4 x i32> [[INC]], <4 x i32> [[B]]
+; CHECK-NEXT:    [[I0:%.*]] = insertvalue { <4 x i32>, <4 x i32> } poison, <4 x i32> [[AN]], 0
+; CHECK-NEXT:    [[I1:%.*]] = insertvalue { <4 x i32>, <4 x i32> } [[I0]], <4 x i32> [[BN]], 1
+; CHECK-NEXT:    ret { <4 x i32>, <4 x i32> } [[I1]]
+;
+  %s = select <4 x i1> %c, <4 x i32> %b, <4 x i32> %a
+  %inc = add <4 x i32> %s, splat (i32 1)
+  %an = select <4 x i1> %c, <4 x i32> %a, <4 x i32> %inc
+  %bn = select <4 x i1> %c, <4 x i32> %inc, <4 x i32> %b
+  %i0 = insertvalue { <4 x i32>, <4 x i32> } poison, <4 x i32> %an, 0
+  %i1 = insertvalue { <4 x i32>, <4 x i32> } %i0, <4 x i32> %bn, 1
+  ret { <4 x i32>, <4 x i32> } %i1
+}
+
+define { i32, i32 } @opposite_sides_variable_operand(i1 %c, i32 %a, i32 %b, i32 %x) {
+; CHECK-LABEL: @opposite_sides_variable_operand(
+; CHECK-NEXT:    [[S:%.*]] = select i1 [[C:%.*]], i32 [[A:%.*]], i32 [[B:%.*]]
+; CHECK-NEXT:    [[V:%.*]] = mul i32 [[S]], [[X:%.*]]
+; CHECK-NEXT:    [[AN:%.*]] = select i1 [[C]], i32 [[V]], i32 [[A]]
+; CHECK-NEXT:    [[BN:%.*]] = select i1 [[C]], i32 [[B]], i32 [[V]]
+; CHECK-NEXT:    [[I0:%.*]] = insertvalue { i32, i32 } poison, i32 [[AN]], 0
+; CHECK-NEXT:    [[I1:%.*]] = insertvalue { i32, i32 } [[I0]], i32 [[BN]], 1
+; CHECK-NEXT:    ret { i32, i32 } [[I1]]
+;
+  %s = select i1 %c, i32 %a, i32 %b
+  %v = mul i32 %s, %x
+  %an = select i1 %c, i32 %v, i32 %a
+  %bn = select i1 %c, i32 %b, i32 %v
+  %i0 = insertvalue { i32, i32 } poison, i32 %an, 0
+  %i1 = insertvalue { i32, i32 } %i0, i32 %bn, 1
+  ret { i32, i32 } %i1
+}
+
+define i32 @arm_used_outside_select(i1 %c, i32 %a, i32 %b, i32 %x) {
+; CHECK-LABEL: @arm_used_outside_select(
+; CHECK-NEXT:    [[S:%.*]] = select i1 [[C:%.*]], i32 [[A:%.*]], i32 [[B:%.*]]
+; CHECK-NEXT:    call void @use(i32 [[S]])
+; CHECK-NEXT:    [[V:%.*]] = mul i32 [[S]], [[X:%.*]]
+; CHECK-NEXT:    call void @use(i32 [[V]])
+; CHECK-NEXT:    [[R:%.*]] = select i1 [[C]], i32 [[V]], i32 [[B]]
+; CHECK-NEXT:    ret i32 [[R]]
+;
+  %s = select i1 %c, i32 %a, i32 %b
+  call void @use(i32 %s)
+  %v = mul i32 %s, %x
+  call void @use(i32 %v)
+  %r = select i1 %c, i32 %v, i32 %b
+  ret i32 %r
+}
+
+define i32 @arm_used_by_select_on_other_cond(i1 %c, i1 %d, i32 %a, i32 %b, i32 %x) {
+; CHECK-LABEL: @arm_used_by_select_on_other_cond(
+; CHECK-NEXT:    [[S:%.*]] = select i1 [[C:%.*]], i32 [[A:%.*]], i32 [[B:%.*]]
+; CHECK-NEXT:    call void @use(i32 [[S]])
+; CHECK-NEXT:    [[V:%.*]] = mul i32 [[S]], [[X:%.*]]
+; CHECK-NEXT:    [[TMP1:%.*]] = select i1 [[D:%.*]], i1 true, i1 [[C]]
+; CHECK-NEXT:    [[Q:%.*]] = select i1 [[TMP1]], i32 [[V]], i32 [[B]]
+; CHECK-NEXT:    ret i32 [[Q]]
+;
+  %s = select i1 %c, i32 %a, i32 %b
+  call void @use(i32 %s)
+  %v = mul i32 %s, %x
+  %r = select i1 %c, i32 %v, i32 %b
+  %q = select i1 %d, i32 %v, i32 %r
+  ret i32 %q
+}
+
+define i32 @inner_select_on_other_cond(i1 %c, i1 %d, i32 %a, i32 %b, i32 %x) {
+; CHECK-LABEL: @inner_select_on_other_cond(
+; CHECK-NEXT:    [[S:%.*]] = select i1 [[D:%.*]], i32 [[A:%.*]], i32 [[B:%.*]]
+; CHECK-NEXT:    call void @use(i32 [[S]])
+; CHECK-NEXT:    [[V:%.*]] = mul i32 [[S]], [[X:%.*]]
+; CHECK-NEXT:    [[R:%.*]] = select i1 [[C:%.*]], i32 [[V]], i32 [[B]]
+; CHECK-NEXT:    ret i32 [[R]]
+;
+  %s = select i1 %d, i32 %a, i32 %b
+  call void @use(i32 %s)
+  %v = mul i32 %s, %x
+  %r = select i1 %c, i32 %v, i32 %b
+  ret i32 %r
+}
+
+define i32 @udiv_not_speculatable(i1 %c, i32 %a, i32 %x) {
+; CHECK-LABEL: @udiv_not_speculatable(
+; CHECK-NEXT:    [[S:%.*]] = select i1 [[C:%.*]], i32 [[A:%.*]], i32 1
+; CHECK-NEXT:    call void @use(i32 [[S]])
+; CHECK-NEXT:    [[V:%.*]] = udiv i32 [[X:%.*]], [[S]]
+; CHECK-NEXT:    [[R:%.*]] = select i1 [[C]], i32 [[V]], i32 0
+; CHECK-NEXT:    ret i32 [[R]]
+;
+  %s = select i1 %c, i32 %a, i32 1
+  call void @use(i32 %s)
+  %v = udiv i32 %x, %s
+  %r = select i1 %c, i32 %v, i32 0
+  ret i32 %r
+}
+
+define <4 x i32> @lane_crossing(<4 x i1> %c, <4 x i32> %a, <4 x i32> %b, <4 x i32> %x) {
+; CHECK-LABEL: @lane_crossing(
+; CHECK-NEXT:    [[S:%.*]] = select <4 x i1> [[C:%.*]], <4 x i32> [[A:%.*]], <4 x i32> [[B:%.*]]
+; CHECK-NEXT:    call void @usev(<4 x i32> [[S]])
+; CHECK-NEXT:    [[V:%.*]] = shufflevector <4 x i32> [[S]], <4 x i32> poison, <4 x i32> <i32 1, i32 0, i32 3, i32 2>
+; CHECK-NEXT:    [[R:%.*]] = select <4 x i1> [[C]], <4 x i32> [[V]], <4 x i32> [[X:%.*]]
+; CHECK-NEXT:    ret <4 x i32> [[R]]
+;
+  %s = select <4 x i1> %c, <4 x i32> %a, <4 x i32> %b
+  call void @usev(<4 x i32> %s)
+  %v = shufflevector <4 x i32> %s, <4 x i32> poison, <4 x i32> <i32 1, i32 0, i32 3, i32 2>
+  %r = select <4 x i1> %c, <4 x i32> %v, <4 x i32> %x
+  ret <4 x i32> %r
+}
+
+declare void @usev(<4 x i32>)
diff --git a/llvm/test/Transforms/InstCombine/select.ll b/llvm/test/Transforms/InstCombine/select.ll
index 203f15cd6f2fc2..133a4a614bb72e 100644
--- a/llvm/test/Transforms/InstCombine/select.ll
+++ b/llvm/test/Transforms/InstCombine/select.ll
@@ -4943,8 +4943,7 @@ define i32 @sequence_select_with_same_cond_true(i1 %c1, i1 %c2){
 define double @sequence_select_with_same_cond_double(double %a, i1 %c1, i1 %c2, double %r1, double %r2){
 ; CHECK-LABEL: define double @sequence_select_with_same_cond_double(
 ; CHECK-SAME: double [[A:%.*]], i1 [[C1:%.*]], i1 [[C2:%.*]], double [[R1:%.*]], double [[R2:%.*]]) {
-; CHECK-NEXT:    [[S1:%.*]] = select i1 [[C1]], double 1.000000e+00, double 0.000000e+00
-; CHECK-NEXT:    [[S2:%.*]] = select i1 [[C2]], double [[S1]], double 2.000000e+00
+; CHECK-NEXT:    [[S2:%.*]] = select i1 [[C2]], double 1.000000e+00, double 2.000000e+00
 ; CHECK-NEXT:    [[S3:%.*]] = select i1 [[C1]], double [[S2]], double 3.000000e+00
 ; CHECK-NEXT:    ret double [[S3]]
 ;
@@ -4961,7 +4960,7 @@ define i32 @sequence_select_with_same_cond_extra_use(i1 %c1, i1 %c2){
 ; CHECK-SAME: i1 [[C1:%.*]], i1 [[C2:%.*]]) {
 ; CHECK-NEXT:    [[S1:%.*]] = select i1 [[C1]], i32 23, i32 45
 ; CHECK-NEXT:    call void @use32(i32 [[S1]])
-; CHECK-NEXT:    [[S2:%.*]] = select i1 [[C2]], i32 666, i32 [[S1]]
+; CHECK-NEXT:    [[S2:%.*]] = select i1 [[C2]], i32 666, i32 45
 ; CHECK-NEXT:    [[S3:%.*]] = select i1 [[C1]], i32 789, i32 [[S2]]
 ; CHECK-NEXT:    ret i32 [[S3]]
 ;



More information about the llvm-commits mailing list