[llvm] [InstCombine] Fold icmp eq/ne X, select(icmp pred X, P, C1, C2) to set membership (PR #226756)
via llvm-commits
llvm-commits at lists.llvm.org
Sat Sep 26 22:35:48 PDT 2026
https://github.com/addmisol updated https://github.com/llvm/llvm-project/pull/226756
>From 6a43e0a11f1b27107e72f6f261ea00e32befa586 Mon Sep 17 00:00:00 2001
From: addmisol <addmisol9 at gmail.com>
Date: Sun, 27 Sep 2026 10:55:11 +0530
Subject: [PATCH 1/2] [InstCombine] Fold icmp eq/ne X, select(icmp pred X, P,
C1, C2) to set membership
Signed-off-by: addmisol <addmisol9 at gmail.com>
---
.../InstCombine/InstCombineCompares.cpp | 63 ++++++++
.../InstCombine/icmp-eq-select-self.ll | 153 ++++++++++++++++++
2 files changed, 216 insertions(+)
create mode 100644 llvm/test/Transforms/InstCombine/icmp-eq-select-self.ll
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
index 717cb65239189..e9be3730b636d 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
@@ -8105,6 +8105,69 @@ Instruction *InstCombinerImpl::visitICmpInst(ICmpInst &I) {
}
}
+ // Fold icmp eq/ne X, select(icmp pred X, P, C1, C2)
+ // When the select condition is based on X and C1/C2 are constants,
+ // we can fold this to a simple set membership test.
+ // Example: X == select(X >s 0, 2, 0) -> (X == 2) | (X == 0)
+ // This is valid when C1 satisfies the select condition (C1 >s 0) and
+ // C2 does not satisfy it (C2 <=s 0).
+ if (I.isEquality()) {
+ // Try both operand orderings for the commutative icmp eq/ne
+ for (int Swap = 0; Swap < 2; ++Swap) {
+ Value *X = Swap ? Op1 : Op0;
+ Value *Sel = Swap ? Op0 : Op1;
+
+ Value *SelCond, *TrueVal, *FalseVal;
+ if (!match(Sel, m_Select(m_Value(SelCond), m_Value(TrueVal),
+ m_Value(FalseVal))))
+ continue;
+
+ Value *CondLHS, *CondRHS;
+ CmpPredicate CondPred;
+ if (!match(SelCond, m_ICmp(CondPred, m_Value(CondLHS), m_Value(CondRHS))))
+ continue;
+
+ // Check if the select condition compares X with something
+ const APInt *C1, *C2, *P;
+ if (CondLHS == X && match(CondRHS, m_APInt(P))) {
+ // icmp pred X, P
+ } else if (CondRHS == X && match(CondLHS, m_APInt(P))) {
+ // icmp pred P, X -> swap to icmp swapped_pred X, P
+ CondPred = ICmpInst::getSwappedPredicate(CondPred);
+ } else {
+ continue;
+ }
+
+ if (!match(TrueVal, m_APInt(C1)) || !match(FalseVal, m_APInt(C2)) ||
+ !Sel->hasOneUse())
+ continue;
+
+ // Check: C1 must satisfy (C1 pred P) - true branch is taken when
+ // condition holds
+ // Check: C2 must NOT satisfy (C2 pred P) - false branch is taken
+ // when condition doesn't hold
+ bool C1SatisfiesCond = ICmpInst::compare(*C1, *P, CondPred);
+ bool C2SatisfiesCond = ICmpInst::compare(*C2, *P, CondPred);
+
+ if (C1SatisfiesCond && !C2SatisfiesCond) {
+ // Valid: X == select(cond, C1, C2) -> (X == C1) | (X == C2)
+ // X != select(cond, C1, C2) -> (X != C1) & (X != C2)
+ Type *Ty = X->getType();
+ Constant *ConstC1 = ConstantInt::get(Ty, *C1);
+ Constant *ConstC2 = ConstantInt::get(Ty, *C2);
+ if (Pred == ICmpInst::ICMP_EQ) {
+ Value *Cmp1 = Builder.CreateICmpEQ(X, ConstC1);
+ Value *Cmp2 = Builder.CreateICmpEQ(X, ConstC2);
+ return replaceInstUsesWith(I, Builder.CreateOr(Cmp1, Cmp2));
+ }
+ // ICMP_NE
+ Value *Cmp1 = Builder.CreateICmpNE(X, ConstC1);
+ Value *Cmp2 = Builder.CreateICmpNE(X, ConstC2);
+ return replaceInstUsesWith(I, Builder.CreateAnd(Cmp1, Cmp2));
+ }
+ }
+ }
+
// Fold icmp pred (select C1, TV1, FV1), (select C2, TV2, FV2)
// when all select arms are constants, via truth table.
if (Instruction *R = foldCmpSelectOfConstants(I))
diff --git a/llvm/test/Transforms/InstCombine/icmp-eq-select-self.ll b/llvm/test/Transforms/InstCombine/icmp-eq-select-self.ll
new file mode 100644
index 0000000000000..61ffac5d0192c
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/icmp-eq-select-self.ll
@@ -0,0 +1,153 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt -S -passes=instcombine < %s | FileCheck %s
+
+; Test folding: X == select(icmp pred X, P, C1, C2) -> (X == C1) | (X == C2)
+; when C1 satisfies (C1 pred P) and C2 does not satisfy (C2 pred P).
+
+; Basic test case from issue #223249
+define i1 @icmp_eq_select_sgt_zero(i32 %x) {
+; CHECK-LABEL: define i1 @icmp_eq_select_sgt_zero(
+; CHECK-NEXT: [[TMP1:%.*]] = and i32 [[X:%.*]], -3
+; CHECK-NEXT: [[R:%.*]] = icmp eq i32 [[TMP1]], 0
+; CHECK-NEXT: ret i1 [[R]]
+;
+ %cond = icmp sgt i32 %x, 0
+ %s = select i1 %cond, i32 2, i32 0
+ %r = icmp eq i32 %x, %s
+ ret i1 %r
+}
+
+; Commuted operands: select(...) == X
+define i1 @icmp_eq_select_sgt_zero_commuted(i32 %x) {
+; CHECK-LABEL: define i1 @icmp_eq_select_sgt_zero_commuted(
+; CHECK-NEXT: [[TMP1:%.*]] = and i32 [[X:%.*]], -3
+; CHECK-NEXT: [[R:%.*]] = icmp eq i32 [[TMP1]], 0
+; CHECK-NEXT: ret i1 [[R]]
+;
+ %cond = icmp sgt i32 %x, 0
+ %s = select i1 %cond, i32 2, i32 0
+ %r = icmp eq i32 %s, %x
+ ret i1 %r
+}
+
+; ne version: X != select(X >s 0, 2, 0) -> (X != 0) & (X != 2)
+define i1 @icmp_ne_select_sgt_zero(i32 %x) {
+; CHECK-LABEL: define i1 @icmp_ne_select_sgt_zero(
+; CHECK-NEXT: [[TMP1:%.*]] = and i32 [[X:%.*]], -3
+; CHECK-NEXT: [[R:%.*]] = icmp ne i32 [[TMP1]], 0
+; CHECK-NEXT: ret i1 [[R]]
+;
+ %cond = icmp sgt i32 %x, 0
+ %s = select i1 %cond, i32 2, i32 0
+ %r = icmp ne i32 %x, %s
+ ret i1 %r
+}
+
+; slt version: X == select(X <s 0, -5, 3)
+define i1 @icmp_eq_select_slt_zero(i32 %x) {
+; CHECK-LABEL: define i1 @icmp_eq_select_slt_zero(
+; CHECK-NEXT: [[TMP1:%.*]] = icmp eq i32 [[X:%.*]], -5
+; CHECK-NEXT: [[TMP2:%.*]] = icmp eq i32 [[X]], 3
+; CHECK-NEXT: [[R:%.*]] = or i1 [[TMP1]], [[TMP2]]
+; CHECK-NEXT: ret i1 [[R]]
+;
+ %cond = icmp slt i32 %x, 0
+ %s = select i1 %cond, i32 -5, i32 3
+ %r = icmp eq i32 %x, %s
+ ret i1 %r
+}
+
+; uge version: X == select(X >=u 5, 10, 2)
+define i1 @icmp_eq_select_uge(i32 %x) {
+; CHECK-LABEL: define i1 @icmp_eq_select_uge(
+; CHECK-NEXT: [[TMP1:%.*]] = and i32 [[X:%.*]], -9
+; CHECK-NEXT: [[R:%.*]] = icmp eq i32 [[TMP1]], 2
+; CHECK-NEXT: ret i1 [[R]]
+;
+ %cond = icmp uge i32 %x, 5
+ %s = select i1 %cond, i32 10, i32 2
+ %r = icmp eq i32 %x, %s
+ ret i1 %r
+}
+
+; Condition with constant on LHS: select(0 <s X, 2, 0) == X
+define i1 @icmp_eq_select_cond_reversed(i32 %x) {
+; CHECK-LABEL: define i1 @icmp_eq_select_cond_reversed(
+; CHECK-NEXT: [[TMP1:%.*]] = and i32 [[X:%.*]], -3
+; CHECK-NEXT: [[R:%.*]] = icmp eq i32 [[TMP1]], 0
+; CHECK-NEXT: ret i1 [[R]]
+;
+ %cond = icmp slt i32 0, %x
+ %s = select i1 %cond, i32 2, i32 0
+ %r = icmp eq i32 %x, %s
+ ret i1 %r
+}
+
+; Vector version
+define <2 x i1> @icmp_eq_select_sgt_zero_vec(<2 x i32> %x) {
+; CHECK-LABEL: define <2 x i1> @icmp_eq_select_sgt_zero_vec(
+; CHECK-NEXT: [[TMP1:%.*]] = and <2 x i32> [[X:%.*]], splat (i32 -3)
+; CHECK-NEXT: [[R:%.*]] = icmp eq <2 x i32> [[TMP1]], zeroinitializer
+; CHECK-NEXT: ret <2 x i1> [[R]]
+;
+ %cond = icmp sgt <2 x i32> %x, zeroinitializer
+ %s = select <2 x i1> %cond, <2 x i32> <i32 2, i32 2>, <2 x i32> zeroinitializer
+ %r = icmp eq <2 x i32> %x, %s
+ ret <2 x i1> %r
+}
+
+; Negative test: C1 does not satisfy condition (2 is not > 5)
+; Should not be folded by our pattern
+define i1 @negative_c1_not_in_range(i32 %x) {
+; CHECK-LABEL: define i1 @negative_c1_not_in_range(
+; CHECK-NEXT: [[R1:%.*]] = icmp eq i32 [[X:%.*]], 0
+; CHECK-NEXT: ret i1 [[R1]]
+;
+ %cond = icmp sgt i32 %x, 5
+ %s = select i1 %cond, i32 2, i32 0
+ %r = icmp eq i32 %x, %s
+ ret i1 %r
+}
+
+; Negative test: C2 satisfies condition (3 > 0)
+; Should not be folded by our pattern
+define i1 @negative_c2_in_range(i32 %x) {
+; CHECK-LABEL: define i1 @negative_c2_in_range(
+; CHECK-NEXT: [[R1:%.*]] = icmp eq i32 [[X:%.*]], 2
+; CHECK-NEXT: ret i1 [[R1]]
+;
+ %cond = icmp sgt i32 %x, 0
+ %s = select i1 %cond, i32 2, i32 3
+ %r = icmp eq i32 %x, %s
+ ret i1 %r
+}
+
+; Negative test: multiple uses of select
+define i1 @negative_multi_use(i32 %x, ptr %p) {
+; CHECK-LABEL: define i1 @negative_multi_use(
+; CHECK-NEXT: [[COND:%.*]] = icmp sgt i32 [[X:%.*]], 0
+; CHECK-NEXT: [[S:%.*]] = select i1 [[COND]], i32 2, i32 0
+; CHECK-NEXT: store i32 [[S]], ptr [[P:%.*]], align 4
+; CHECK-NEXT: [[R:%.*]] = icmp eq i32 [[X]], [[S]]
+; CHECK-NEXT: ret i1 [[R]]
+;
+ %cond = icmp sgt i32 %x, 0
+ %s = select i1 %cond, i32 2, i32 0
+ store i32 %s, ptr %p
+ %r = icmp eq i32 %x, %s
+ ret i1 %r
+}
+
+; Negative test: select condition doesn't involve X
+define i1 @negative_unrelated_cond(i32 %x, i32 %y) {
+; CHECK-LABEL: define i1 @negative_unrelated_cond(
+; CHECK-NEXT: [[COND:%.*]] = icmp sgt i32 [[Y:%.*]], 0
+; CHECK-NEXT: [[S:%.*]] = select i1 [[COND]], i32 2, i32 0
+; CHECK-NEXT: [[R:%.*]] = icmp eq i32 [[X:%.*]], [[S]]
+; CHECK-NEXT: ret i1 [[R]]
+;
+ %cond = icmp sgt i32 %y, 0
+ %s = select i1 %cond, i32 2, i32 0
+ %r = icmp eq i32 %x, %s
+ ret i1 %r
+}
>From 75c55ac9f1d0cd37077d1c8f8587ece0105e559b Mon Sep 17 00:00:00 2001
From: addmisol <addmisol9 at gmail.com>
Date: Sun, 27 Sep 2026 11:05:27 +0530
Subject: [PATCH 2/2] Fix: add more test cases
Signed-off-by: addmisol <addmisol9 at gmail.com>
---
.../InstCombine/icmp-eq-select-self.ll | 98 ++++++++++++++++++-
1 file changed, 97 insertions(+), 1 deletion(-)
diff --git a/llvm/test/Transforms/InstCombine/icmp-eq-select-self.ll b/llvm/test/Transforms/InstCombine/icmp-eq-select-self.ll
index 61ffac5d0192c..48974c42d906b 100644
--- a/llvm/test/Transforms/InstCombine/icmp-eq-select-self.ll
+++ b/llvm/test/Transforms/InstCombine/icmp-eq-select-self.ll
@@ -83,7 +83,7 @@ define i1 @icmp_eq_select_cond_reversed(i32 %x) {
ret i1 %r
}
-; Vector version
+; Vector version - eq
define <2 x i1> @icmp_eq_select_sgt_zero_vec(<2 x i32> %x) {
; CHECK-LABEL: define <2 x i1> @icmp_eq_select_sgt_zero_vec(
; CHECK-NEXT: [[TMP1:%.*]] = and <2 x i32> [[X:%.*]], splat (i32 -3)
@@ -96,6 +96,102 @@ define <2 x i1> @icmp_eq_select_sgt_zero_vec(<2 x i32> %x) {
ret <2 x i1> %r
}
+; Vector version - ne
+define <2 x i1> @icmp_ne_select_sgt_zero_vec(<2 x i32> %x) {
+; CHECK-LABEL: define <2 x i1> @icmp_ne_select_sgt_zero_vec(
+; CHECK-NEXT: [[TMP1:%.*]] = and <2 x i32> [[X:%.*]], splat (i32 -3)
+; CHECK-NEXT: [[R:%.*]] = icmp ne <2 x i32> [[TMP1]], zeroinitializer
+; CHECK-NEXT: ret <2 x i1> [[R]]
+;
+ %cond = icmp sgt <2 x i32> %x, zeroinitializer
+ %s = select <2 x i1> %cond, <2 x i32> <i32 2, i32 2>, <2 x i32> zeroinitializer
+ %r = icmp ne <2 x i32> %x, %s
+ ret <2 x i1> %r
+}
+
+; Vector version - commuted operands
+define <2 x i1> @icmp_eq_select_sgt_zero_vec_commuted(<2 x i32> %x) {
+; CHECK-LABEL: define <2 x i1> @icmp_eq_select_sgt_zero_vec_commuted(
+; CHECK-NEXT: [[TMP1:%.*]] = and <2 x i32> [[X:%.*]], splat (i32 -3)
+; CHECK-NEXT: [[R:%.*]] = icmp eq <2 x i32> [[TMP1]], zeroinitializer
+; CHECK-NEXT: ret <2 x i1> [[R]]
+;
+ %cond = icmp sgt <2 x i32> %x, zeroinitializer
+ %s = select <2 x i1> %cond, <2 x i32> <i32 2, i32 2>, <2 x i32> zeroinitializer
+ %r = icmp eq <2 x i32> %s, %x
+ ret <2 x i1> %r
+}
+
+; Vector version - slt predicate
+define <2 x i1> @icmp_eq_select_slt_zero_vec(<2 x i32> %x) {
+; CHECK-LABEL: define <2 x i1> @icmp_eq_select_slt_zero_vec(
+; CHECK-NEXT: [[TMP1:%.*]] = icmp eq <2 x i32> [[X:%.*]], splat (i32 -5)
+; CHECK-NEXT: [[TMP2:%.*]] = icmp eq <2 x i32> [[X]], splat (i32 3)
+; CHECK-NEXT: [[R:%.*]] = or <2 x i1> [[TMP1]], [[TMP2]]
+; CHECK-NEXT: ret <2 x i1> [[R]]
+;
+ %cond = icmp slt <2 x i32> %x, zeroinitializer
+ %s = select <2 x i1> %cond, <2 x i32> <i32 -5, i32 -5>, <2 x i32> <i32 3, i32 3>
+ %r = icmp eq <2 x i32> %x, %s
+ ret <2 x i1> %r
+}
+
+; Vector version - condition with constant on LHS (reversed)
+define <2 x i1> @icmp_eq_select_cond_reversed_vec(<2 x i32> %x) {
+; CHECK-LABEL: define <2 x i1> @icmp_eq_select_cond_reversed_vec(
+; CHECK-NEXT: [[TMP1:%.*]] = and <2 x i32> [[X:%.*]], splat (i32 -3)
+; CHECK-NEXT: [[R:%.*]] = icmp eq <2 x i32> [[TMP1]], zeroinitializer
+; CHECK-NEXT: ret <2 x i1> [[R]]
+;
+ %cond = icmp slt <2 x i32> zeroinitializer, %x
+ %s = select <2 x i1> %cond, <2 x i32> <i32 2, i32 2>, <2 x i32> zeroinitializer
+ %r = icmp eq <2 x i32> %x, %s
+ ret <2 x i1> %r
+}
+
+; Vector version - unsigned predicate (uge)
+define <2 x i1> @icmp_eq_select_uge_vec(<2 x i32> %x) {
+; CHECK-LABEL: define <2 x i1> @icmp_eq_select_uge_vec(
+; CHECK-NEXT: [[TMP1:%.*]] = and <2 x i32> [[X:%.*]], splat (i32 -9)
+; CHECK-NEXT: [[R:%.*]] = icmp eq <2 x i32> [[TMP1]], splat (i32 2)
+; CHECK-NEXT: ret <2 x i1> [[R]]
+;
+ %cond = icmp uge <2 x i32> %x, <i32 5, i32 5>
+ %s = select <2 x i1> %cond, <2 x i32> <i32 10, i32 10>, <2 x i32> <i32 2, i32 2>
+ %r = icmp eq <2 x i32> %x, %s
+ ret <2 x i1> %r
+}
+
+; Vector negative test - multi use
+define <2 x i1> @negative_multi_use_vec(<2 x i32> %x, ptr %p) {
+; CHECK-LABEL: define <2 x i1> @negative_multi_use_vec(
+; CHECK-NEXT: [[COND:%.*]] = icmp sgt <2 x i32> [[X:%.*]], zeroinitializer
+; CHECK-NEXT: [[S:%.*]] = select <2 x i1> [[COND]], <2 x i32> splat (i32 2), <2 x i32> zeroinitializer
+; CHECK-NEXT: store <2 x i32> [[S]], ptr [[P:%.*]], align 8
+; CHECK-NEXT: [[R:%.*]] = icmp eq <2 x i32> [[X]], [[S]]
+; CHECK-NEXT: ret <2 x i1> [[R]]
+;
+ %cond = icmp sgt <2 x i32> %x, zeroinitializer
+ %s = select <2 x i1> %cond, <2 x i32> <i32 2, i32 2>, <2 x i32> zeroinitializer
+ store <2 x i32> %s, ptr %p
+ %r = icmp eq <2 x i32> %x, %s
+ ret <2 x i1> %r
+}
+
+; Vector negative test - unrelated condition
+define <2 x i1> @negative_unrelated_cond_vec(<2 x i32> %x, <2 x i32> %y) {
+; CHECK-LABEL: define <2 x i1> @negative_unrelated_cond_vec(
+; CHECK-NEXT: [[COND:%.*]] = icmp sgt <2 x i32> [[Y:%.*]], zeroinitializer
+; CHECK-NEXT: [[S:%.*]] = select <2 x i1> [[COND]], <2 x i32> splat (i32 2), <2 x i32> zeroinitializer
+; CHECK-NEXT: [[R:%.*]] = icmp eq <2 x i32> [[X:%.*]], [[S]]
+; CHECK-NEXT: ret <2 x i1> [[R]]
+;
+ %cond = icmp sgt <2 x i32> %y, zeroinitializer
+ %s = select <2 x i1> %cond, <2 x i32> <i32 2, i32 2>, <2 x i32> zeroinitializer
+ %r = icmp eq <2 x i32> %x, %s
+ ret <2 x i1> %r
+}
+
; Negative test: C1 does not satisfy condition (2 is not > 5)
; Should not be folded by our pattern
define i1 @negative_c1_not_in_range(i32 %x) {
More information about the llvm-commits
mailing list