[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