[llvm] [InstCombine] Fold redundant select guards for ctpop (PR #195443)

Sai Sanjay Chikne via llvm-commits llvm-commits at lists.llvm.org
Sun May 3 06:07:55 PDT 2026


https://github.com/sai18022001 updated https://github.com/llvm/llvm-project/pull/195443

>From 15bb21f7c3c5ebc4501ee476f60a1e68be9167b5 Mon Sep 17 00:00:00 2001
From: Sai Sanjay Chikne <saichikne180201 at gmail.com>
Date: Sat, 2 May 2026 16:18:23 +0530
Subject: [PATCH 1/4] [InstCombine] Fold redundant select guards for ctpop

Fixes: [X86][AArch64] backend didn't revert early exit for native popcnt #194207

Folds select patterns that guard ctpop for edge cases since ctpop(0)==0,
ctpop(1)==1, and ctpop(-1)==BitWidth already make the guards redundant.
---
 .../InstCombine/InstCombineSelect.cpp         |  20 +++
 .../InstCombine/select-ctpop-fold.ll          | 117 ++++++++++++++++++
 2 files changed, 137 insertions(+)
 create mode 100644 llvm/test/Transforms/InstCombine/select-ctpop-fold.ll

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
index 82954ee6f2457..af5d9e6f482b4 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
@@ -4317,6 +4317,26 @@ Instruction *InstCombinerImpl::visitSelectInst(SelectInst &SI) {
   if (Instruction *I = canonicalizeScalarSelectOfVecs(SI, *this))
     return I;
 
+  // Fold: select (icmp ult X, 2), X, ctpop(X)  -->  ctpop(X)
+  // Fold: select (icmp eq X, -1), BitWidth, ctpop(X)  -->  ctpop(X)
+  // ctpop(0)==0, ctpop(1)==1, ctpop(-1)==BitWidth, so guards are redundant.
+  {
+    Value *X;
+    CmpPredicate CtpopPred;
+    Value *CtpopCmpLHS, *CtpopCmpRHS;
+    unsigned CtpopBitWidth = SelType->getScalarSizeInBits();
+    if (match(FalseVal, m_Intrinsic<Intrinsic::ctpop>(m_Value(X))) &&
+        match(CondVal, m_ICmp(CtpopPred, m_Value(CtpopCmpLHS), m_Value(CtpopCmpRHS)))) {
+      if (CtpopCmpLHS == X && CtpopPred == ICmpInst::ICMP_ULT &&
+          match(CtpopCmpRHS, m_SpecificInt(2)) && TrueVal == X)
+        return replaceInstUsesWith(SI, FalseVal);
+      if (CtpopCmpLHS == X && CtpopPred == ICmpInst::ICMP_EQ &&
+          match(CtpopCmpRHS, m_AllOnes()) &&
+          match(TrueVal, m_SpecificInt(CtpopBitWidth)))
+        return replaceInstUsesWith(SI, FalseVal);
+    }
+  }
+
   // If the type of select is not an integer type or if the condition and
   // the selection type are not both scalar nor both vector types, there is no
   // point in attempting to match these patterns.
diff --git a/llvm/test/Transforms/InstCombine/select-ctpop-fold.ll b/llvm/test/Transforms/InstCombine/select-ctpop-fold.ll
new file mode 100644
index 0000000000000..a2dae53a22e3f
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/select-ctpop-fold.ll
@@ -0,0 +1,117 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+;
+; Test folding of redundant early-exit guards around ctpop:
+;   select (icmp ule X, 1), X, ctpop(X)        --> ctpop(X)
+;   select (icmp eq X, -1), BitWidth, ctpop(X)  --> ctpop(X)
+;
+; These folds are unconditionally valid because ctpop already returns the
+; correct answer for edge cases: ctpop(0)==0, ctpop(1)==1, ctpop(-1)==BitWidth.
+; The guards only existed to skip slow software emulation -- they are never
+; needed for correctness and InstCombine removes them regardless of target.
+
+; RUN: opt < %s -S -passes=instcombine | FileCheck %s
+
+declare i64 @llvm.ctpop.i64(i64)
+declare i32 @llvm.ctpop.i32(i32)
+declare <4 x i32> @llvm.ctpop.v4i32(<4 x i32>)
+declare <2 x i64> @llvm.ctpop.v2i64(<2 x i64>)
+
+;------------------------------------------------------------------------------
+; Pattern 1: select (icmp ule X, 1), X, ctpop(X)  -->  ctpop(X)
+; ctpop(0)==0 and ctpop(1)==1, so the guard is always redundant.
+;------------------------------------------------------------------------------
+
+define i64 @fold_ule1_i64(i64 %x) {
+; CHECK-LABEL: @fold_ule1_i64(
+; CHECK-NEXT:    [[POP:%.*]] = call range(i64 0, 65) i64 @llvm.ctpop.i64(i64 [[X:%.*]])
+; CHECK-NEXT:    ret i64 [[POP]]
+;
+  %cmp = icmp ule i64 %x, 1
+  %pop = call i64 @llvm.ctpop.i64(i64 %x)
+  %res = select i1 %cmp, i64 %x, i64 %pop
+  ret i64 %res
+}
+
+define i32 @fold_ule1_i32(i32 %x) {
+; CHECK-LABEL: @fold_ule1_i32(
+; CHECK-NEXT:    [[POP:%.*]] = call range(i32 0, 33) i32 @llvm.ctpop.i32(i32 [[X:%.*]])
+; CHECK-NEXT:    ret i32 [[POP]]
+;
+  %cmp = icmp ule i32 %x, 1
+  %pop = call i32 @llvm.ctpop.i32(i32 %x)
+  %res = select i1 %cmp, i32 %x, i32 %pop
+  ret i32 %res
+}
+
+;------------------------------------------------------------------------------
+; Pattern 2: select (icmp eq X, -1), BitWidth, ctpop(X)  -->  ctpop(X)
+; ctpop(SIZE_MAX) == BitWidth already, so the guard is always redundant.
+;------------------------------------------------------------------------------
+
+define i64 @fold_allones_i64(i64 %x) {
+; CHECK-LABEL: @fold_allones_i64(
+; CHECK-NEXT:    [[POP:%.*]] = call range(i64 0, 65) i64 @llvm.ctpop.i64(i64 [[X:%.*]])
+; CHECK-NEXT:    ret i64 [[POP]]
+;
+  %cmp = icmp eq i64 %x, -1
+  %pop = call i64 @llvm.ctpop.i64(i64 %x)
+  %res = select i1 %cmp, i64 64, i64 %pop
+  ret i64 %res
+}
+
+define i32 @fold_allones_i32(i32 %x) {
+; CHECK-LABEL: @fold_allones_i32(
+; CHECK-NEXT:    [[POP:%.*]] = call range(i32 0, 33) i32 @llvm.ctpop.i32(i32 [[X:%.*]])
+; CHECK-NEXT:    ret i32 [[POP]]
+;
+  %cmp = icmp eq i32 %x, -1
+  %pop = call i32 @llvm.ctpop.i32(i32 %x)
+  %res = select i1 %cmp, i32 32, i32 %pop
+  ret i32 %res
+}
+
+;------------------------------------------------------------------------------
+; Pattern 3: the full mi_popcount chain -- both guards combined.
+; select (ule X,1), X, (select (eq X,-1), BitWidth, ctpop(X))
+; Both selects should fold away leaving just ctpop(X).
+;------------------------------------------------------------------------------
+
+define i64 @fold_mi_popcount_chain(i64 %x) {
+; CHECK-LABEL: @fold_mi_popcount_chain(
+; CHECK-NEXT:    [[POP:%.*]] = call range(i64 0, 65) i64 @llvm.ctpop.i64(i64 [[X:%.*]])
+; CHECK-NEXT:    ret i64 [[POP]]
+;
+  %cmp1 = icmp ule i64 %x, 1
+  %cmp2 = icmp eq i64 %x, -1
+  %pop  = call i64 @llvm.ctpop.i64(i64 %x)
+  %sel2 = select i1 %cmp2, i64 64, i64 %pop
+  %res  = select i1 %cmp1, i64 %x, i64 %sel2
+  ret i64 %res
+}
+
+;------------------------------------------------------------------------------
+; Pattern 4: vector -- fold is unconditional.
+; All SIMD lanes wait regardless, so the branch never saves work.
+;------------------------------------------------------------------------------
+
+define <4 x i32> @fold_vector_ule1(<4 x i32> %x) {
+; CHECK-LABEL: @fold_vector_ule1(
+; CHECK-NEXT:    [[POP:%.*]] = call range(i32 0, 33) <4 x i32> @llvm.ctpop.v4i32(<4 x i32> [[X:%.*]])
+; CHECK-NEXT:    ret <4 x i32> [[POP]]
+;
+  %cmp = icmp ule <4 x i32> %x, <i32 1, i32 1, i32 1, i32 1>
+  %pop = call <4 x i32> @llvm.ctpop.v4i32(<4 x i32> %x)
+  %res = select <4 x i1> %cmp, <4 x i32> %x, <4 x i32> %pop
+  ret <4 x i32> %res
+}
+
+define <2 x i64> @fold_vector_allones(<2 x i64> %x) {
+; CHECK-LABEL: @fold_vector_allones(
+; CHECK-NEXT:    [[POP:%.*]] = call range(i64 0, 65) <2 x i64> @llvm.ctpop.v2i64(<2 x i64> [[X:%.*]])
+; CHECK-NEXT:    ret <2 x i64> [[POP]]
+;
+  %cmp = icmp eq <2 x i64> %x, <i64 -1, i64 -1>
+  %pop = call <2 x i64> @llvm.ctpop.v2i64(<2 x i64> %x)
+  %res = select <2 x i1> %cmp, <2 x i64> <i64 64, i64 64>, <2 x i64> %pop
+  ret <2 x i64> %res
+}

>From 8d846f05c3fc7aea0963a94302249680d9cc3b3e Mon Sep 17 00:00:00 2001
From: Sai Sanjay Chikne <saichikne180201 at gmail.com>
Date: Sat, 2 May 2026 17:38:37 +0530
Subject: [PATCH 2/4] clang-format fix

---
 llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
index af5d9e6f482b4..53a4b3c30da88 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
@@ -4326,7 +4326,8 @@ Instruction *InstCombinerImpl::visitSelectInst(SelectInst &SI) {
     Value *CtpopCmpLHS, *CtpopCmpRHS;
     unsigned CtpopBitWidth = SelType->getScalarSizeInBits();
     if (match(FalseVal, m_Intrinsic<Intrinsic::ctpop>(m_Value(X))) &&
-        match(CondVal, m_ICmp(CtpopPred, m_Value(CtpopCmpLHS), m_Value(CtpopCmpRHS)))) {
+        match(CondVal,
+              m_ICmp(CtpopPred, m_Value(CtpopCmpLHS), m_Value(CtpopCmpRHS)))) {
       if (CtpopCmpLHS == X && CtpopPred == ICmpInst::ICMP_ULT &&
           match(CtpopCmpRHS, m_SpecificInt(2)) && TrueVal == X)
         return replaceInstUsesWith(SI, FalseVal);

>From 30605cdeffaf0bb920d8ff29f17df9db5bb405f0 Mon Sep 17 00:00:00 2001
From: Sai Sanjay Chikne <saichikne180201 at gmail.com>
Date: Sat, 2 May 2026 19:53:13 +0530
Subject: [PATCH 3/4] address review: drop allones fold, add
 dropPoisonGeneratingAnnotations

---
 .../InstCombine/InstCombineSelect.cpp         | 18 ++---
 .../InstCombine/select-ctpop-fold.ll          | 81 +++++--------------
 2 files changed, 26 insertions(+), 73 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
index 53a4b3c30da88..fd3664e0ba66f 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
@@ -4318,23 +4318,19 @@ Instruction *InstCombinerImpl::visitSelectInst(SelectInst &SI) {
     return I;
 
   // Fold: select (icmp ult X, 2), X, ctpop(X)  -->  ctpop(X)
-  // Fold: select (icmp eq X, -1), BitWidth, ctpop(X)  -->  ctpop(X)
-  // ctpop(0)==0, ctpop(1)==1, ctpop(-1)==BitWidth, so guards are redundant.
+  // ctpop(0)==0 and ctpop(1)==1, so the guard is redundant.
   {
     Value *X;
     CmpPredicate CtpopPred;
     Value *CtpopCmpLHS, *CtpopCmpRHS;
-    unsigned CtpopBitWidth = SelType->getScalarSizeInBits();
     if (match(FalseVal, m_Intrinsic<Intrinsic::ctpop>(m_Value(X))) &&
         match(CondVal,
-              m_ICmp(CtpopPred, m_Value(CtpopCmpLHS), m_Value(CtpopCmpRHS)))) {
-      if (CtpopCmpLHS == X && CtpopPred == ICmpInst::ICMP_ULT &&
-          match(CtpopCmpRHS, m_SpecificInt(2)) && TrueVal == X)
-        return replaceInstUsesWith(SI, FalseVal);
-      if (CtpopCmpLHS == X && CtpopPred == ICmpInst::ICMP_EQ &&
-          match(CtpopCmpRHS, m_AllOnes()) &&
-          match(TrueVal, m_SpecificInt(CtpopBitWidth)))
-        return replaceInstUsesWith(SI, FalseVal);
+              m_ICmp(CtpopPred, m_Value(CtpopCmpLHS), m_Value(CtpopCmpRHS))) &&
+        CtpopCmpLHS == X && CtpopPred == ICmpInst::ICMP_ULT &&
+        match(CtpopCmpRHS, m_SpecificInt(2)) && TrueVal == X) {
+      cast<Instruction>(FalseVal)->dropPoisonGeneratingAnnotations();
+      addToWorklist(cast<Instruction>(FalseVal));
+      return replaceInstUsesWith(SI, FalseVal);
     }
   }
 
diff --git a/llvm/test/Transforms/InstCombine/select-ctpop-fold.ll b/llvm/test/Transforms/InstCombine/select-ctpop-fold.ll
index a2dae53a22e3f..05eadc57a8678 100644
--- a/llvm/test/Transforms/InstCombine/select-ctpop-fold.ll
+++ b/llvm/test/Transforms/InstCombine/select-ctpop-fold.ll
@@ -1,13 +1,10 @@
 ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
 ;
-; Test folding of redundant early-exit guards around ctpop:
-;   select (icmp ule X, 1), X, ctpop(X)        --> ctpop(X)
-;   select (icmp eq X, -1), BitWidth, ctpop(X)  --> ctpop(X)
+; Test folding of redundant early-exit guard around ctpop:
+;   select (icmp ule X, 1), X, ctpop(X)  --> ctpop(X)
 ;
-; These folds are unconditionally valid because ctpop already returns the
-; correct answer for edge cases: ctpop(0)==0, ctpop(1)==1, ctpop(-1)==BitWidth.
-; The guards only existed to skip slow software emulation -- they are never
-; needed for correctness and InstCombine removes them regardless of target.
+; This fold is valid because ctpop(0)==0 and ctpop(1)==1, so the guard
+; is always redundant. The guard only existed to skip slow software emulation.
 
 ; RUN: opt < %s -S -passes=instcombine | FileCheck %s
 
@@ -44,53 +41,7 @@ define i32 @fold_ule1_i32(i32 %x) {
 }
 
 ;------------------------------------------------------------------------------
-; Pattern 2: select (icmp eq X, -1), BitWidth, ctpop(X)  -->  ctpop(X)
-; ctpop(SIZE_MAX) == BitWidth already, so the guard is always redundant.
-;------------------------------------------------------------------------------
-
-define i64 @fold_allones_i64(i64 %x) {
-; CHECK-LABEL: @fold_allones_i64(
-; CHECK-NEXT:    [[POP:%.*]] = call range(i64 0, 65) i64 @llvm.ctpop.i64(i64 [[X:%.*]])
-; CHECK-NEXT:    ret i64 [[POP]]
-;
-  %cmp = icmp eq i64 %x, -1
-  %pop = call i64 @llvm.ctpop.i64(i64 %x)
-  %res = select i1 %cmp, i64 64, i64 %pop
-  ret i64 %res
-}
-
-define i32 @fold_allones_i32(i32 %x) {
-; CHECK-LABEL: @fold_allones_i32(
-; CHECK-NEXT:    [[POP:%.*]] = call range(i32 0, 33) i32 @llvm.ctpop.i32(i32 [[X:%.*]])
-; CHECK-NEXT:    ret i32 [[POP]]
-;
-  %cmp = icmp eq i32 %x, -1
-  %pop = call i32 @llvm.ctpop.i32(i32 %x)
-  %res = select i1 %cmp, i32 32, i32 %pop
-  ret i32 %res
-}
-
-;------------------------------------------------------------------------------
-; Pattern 3: the full mi_popcount chain -- both guards combined.
-; select (ule X,1), X, (select (eq X,-1), BitWidth, ctpop(X))
-; Both selects should fold away leaving just ctpop(X).
-;------------------------------------------------------------------------------
-
-define i64 @fold_mi_popcount_chain(i64 %x) {
-; CHECK-LABEL: @fold_mi_popcount_chain(
-; CHECK-NEXT:    [[POP:%.*]] = call range(i64 0, 65) i64 @llvm.ctpop.i64(i64 [[X:%.*]])
-; CHECK-NEXT:    ret i64 [[POP]]
-;
-  %cmp1 = icmp ule i64 %x, 1
-  %cmp2 = icmp eq i64 %x, -1
-  %pop  = call i64 @llvm.ctpop.i64(i64 %x)
-  %sel2 = select i1 %cmp2, i64 64, i64 %pop
-  %res  = select i1 %cmp1, i64 %x, i64 %sel2
-  ret i64 %res
-}
-
-;------------------------------------------------------------------------------
-; Pattern 4: vector -- fold is unconditional.
+; Pattern 2: vector -- fold is unconditional.
 ; All SIMD lanes wait regardless, so the branch never saves work.
 ;------------------------------------------------------------------------------
 
@@ -105,13 +56,19 @@ define <4 x i32> @fold_vector_ule1(<4 x i32> %x) {
   ret <4 x i32> %res
 }
 
-define <2 x i64> @fold_vector_allones(<2 x i64> %x) {
-; CHECK-LABEL: @fold_vector_allones(
-; CHECK-NEXT:    [[POP:%.*]] = call range(i64 0, 65) <2 x i64> @llvm.ctpop.v2i64(<2 x i64> [[X:%.*]])
-; CHECK-NEXT:    ret <2 x i64> [[POP]]
+;------------------------------------------------------------------------------
+; Negative test: select (icmp eq X, -1), BitWidth, ctpop(X) should NOT be
+; folded here -- it is handled by foldSelectValueEquivalence, and folding
+; when ctpop has a range annotation that excludes BitWidth would be incorrect.
+;------------------------------------------------------------------------------
+
+define i32 @no_fold_allones_i32(i32 %x) {
+; CHECK-LABEL: @no_fold_allones_i32(
+; CHECK-NEXT:    [[POP:%.*]] = call range(i32 0, 33) i32 @llvm.ctpop.i32(i32 [[X:%.*]])
+; CHECK-NEXT:    ret i32 [[POP]]
 ;
-  %cmp = icmp eq <2 x i64> %x, <i64 -1, i64 -1>
-  %pop = call <2 x i64> @llvm.ctpop.v2i64(<2 x i64> %x)
-  %res = select <2 x i1> %cmp, <2 x i64> <i64 64, i64 64>, <2 x i64> %pop
-  ret <2 x i64> %res
+  %cmp = icmp eq i32 %x, -1
+  %pop = call i32 @llvm.ctpop.i32(i32 %x)
+  %res = select i1 %cmp, i32 32, i32 %pop
+  ret i32 %res
 }

>From 776f04411894f1c136ed17bf508548da0ffcab7c Mon Sep 17 00:00:00 2001
From: Sai Sanjay Chikne <saichikne180201 at gmail.com>
Date: Sun, 3 May 2026 17:24:24 +0530
Subject: [PATCH 4/4] address review: use m_SpecificICmp, add negative tests,
 drop intrinsic declares

---
 .../InstCombine/InstCombineSelect.cpp         | 21 +++----
 .../InstCombine/select-ctpop-fold.ll          | 60 ++++++++++++-------
 2 files changed, 47 insertions(+), 34 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
index fd3664e0ba66f..ff6b42a9a170b 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
@@ -4318,20 +4318,13 @@ Instruction *InstCombinerImpl::visitSelectInst(SelectInst &SI) {
     return I;
 
   // Fold: select (icmp ult X, 2), X, ctpop(X)  -->  ctpop(X)
-  // ctpop(0)==0 and ctpop(1)==1, so the guard is redundant.
-  {
-    Value *X;
-    CmpPredicate CtpopPred;
-    Value *CtpopCmpLHS, *CtpopCmpRHS;
-    if (match(FalseVal, m_Intrinsic<Intrinsic::ctpop>(m_Value(X))) &&
-        match(CondVal,
-              m_ICmp(CtpopPred, m_Value(CtpopCmpLHS), m_Value(CtpopCmpRHS))) &&
-        CtpopCmpLHS == X && CtpopPred == ICmpInst::ICMP_ULT &&
-        match(CtpopCmpRHS, m_SpecificInt(2)) && TrueVal == X) {
-      cast<Instruction>(FalseVal)->dropPoisonGeneratingAnnotations();
-      addToWorklist(cast<Instruction>(FalseVal));
-      return replaceInstUsesWith(SI, FalseVal);
-    }
+  // ctpop(0)==0 and ctpop(1)==1, so the guard is always redundant.
+  if (match(FalseVal, m_Intrinsic<Intrinsic::ctpop>(m_Specific(TrueVal))) &&
+      match(CondVal, 
+            m_SpecificICmp(ICmpInst::ICMP_ULT, m_Specific(TrueVal), m_SpecificInt(2)))) {
+    cast<Instruction>(FalseVal)->dropPoisonGeneratingAnnotations();
+    addToWorklist(cast<Instruction>(FalseVal));
+    return replaceInstUsesWith(SI, FalseVal);
   }
 
   // If the type of select is not an integer type or if the condition and
diff --git a/llvm/test/Transforms/InstCombine/select-ctpop-fold.ll b/llvm/test/Transforms/InstCombine/select-ctpop-fold.ll
index 05eadc57a8678..d42fd3605264f 100644
--- a/llvm/test/Transforms/InstCombine/select-ctpop-fold.ll
+++ b/llvm/test/Transforms/InstCombine/select-ctpop-fold.ll
@@ -6,16 +6,12 @@
 ; This fold is valid because ctpop(0)==0 and ctpop(1)==1, so the guard
 ; is always redundant. The guard only existed to skip slow software emulation.
 
+
 ; RUN: opt < %s -S -passes=instcombine | FileCheck %s
 
-declare i64 @llvm.ctpop.i64(i64)
-declare i32 @llvm.ctpop.i32(i32)
-declare <4 x i32> @llvm.ctpop.v4i32(<4 x i32>)
-declare <2 x i64> @llvm.ctpop.v2i64(<2 x i64>)
 
 ;------------------------------------------------------------------------------
-; Pattern 1: select (icmp ule X, 1), X, ctpop(X)  -->  ctpop(X)
-; ctpop(0)==0 and ctpop(1)==1, so the guard is always redundant.
+; Positive tests: select (icmp ule X, 1), X, ctpop(X)  -->  ctpop(X)
 ;------------------------------------------------------------------------------
 
 define i64 @fold_ule1_i64(i64 %x) {
@@ -40,35 +36,59 @@ define i32 @fold_ule1_i32(i32 %x) {
   ret i32 %res
 }
 
-;------------------------------------------------------------------------------
-; Pattern 2: vector -- fold is unconditional.
-; All SIMD lanes wait regardless, so the branch never saves work.
-;------------------------------------------------------------------------------
-
 define <4 x i32> @fold_vector_ule1(<4 x i32> %x) {
 ; CHECK-LABEL: @fold_vector_ule1(
 ; CHECK-NEXT:    [[POP:%.*]] = call range(i32 0, 33) <4 x i32> @llvm.ctpop.v4i32(<4 x i32> [[X:%.*]])
 ; CHECK-NEXT:    ret <4 x i32> [[POP]]
 ;
-  %cmp = icmp ule <4 x i32> %x, <i32 1, i32 1, i32 1, i32 1>
+  %cmp = icmp ule <4 x i32> %x, splat(i32 1)
   %pop = call <4 x i32> @llvm.ctpop.v4i32(<4 x i32> %x)
   %res = select <4 x i1> %cmp, <4 x i32> %x, <4 x i32> %pop
   ret <4 x i32> %res
 }
 
 ;------------------------------------------------------------------------------
-; Negative test: select (icmp eq X, -1), BitWidth, ctpop(X) should NOT be
-; folded here -- it is handled by foldSelectValueEquivalence, and folding
-; when ctpop has a range annotation that excludes BitWidth would be incorrect.
+; Negative tests: should NOT fold.
 ;------------------------------------------------------------------------------
 
-define i32 @no_fold_allones_i32(i32 %x) {
-; CHECK-LABEL: @no_fold_allones_i32(
+; Wrong predicate (ugt instead of ult/ule)
+define i32 @no_fold_wrong_pred(i32 %x) {
+; CHECK-LABEL: @no_fold_wrong_pred(
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ugt i32 [[X:%.*]], 1
+; CHECK-NEXT:    [[POP:%.*]] = call range(i32 0, 33) i32 @llvm.ctpop.i32(i32 [[X]])
+; CHECK-NEXT:    [[RES:%.*]] = select i1 [[CMP]], i32 [[X]], i32 [[POP]]
+; CHECK-NEXT:    ret i32 [[RES]]
+;
+  %cmp = icmp ugt i32 %x, 1
+  %pop = call i32 @llvm.ctpop.i32(i32 %x)
+  %res = select i1 %cmp, i32 %x, i32 %pop
+  ret i32 %res
+}
+
+; Wrong constant (ult 3 instead of ult 2)
+define i32 @no_fold_wrong_const(i32 %x) {
+; CHECK-LABEL: @no_fold_wrong_const(
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i32 [[X:%.*]], 3
+; CHECK-NEXT:    [[POP:%.*]] = call range(i32 0, 33) i32 @llvm.ctpop.i32(i32 [[X]])
+; CHECK-NEXT:    [[RES:%.*]] = select i1 [[CMP]], i32 [[X]], i32 [[POP]]
+; CHECK-NEXT:    ret i32 [[RES]]
+;
+  %cmp = icmp ult i32 %x, 3
+  %pop = call i32 @llvm.ctpop.i32(i32 %x)
+  %res = select i1 %cmp, i32 %x, i32 %pop
+  ret i32 %res
+}
+
+; Mismatched variables (condition uses Y, ctpop uses X)
+define i32 @no_fold_mismatch_var(i32 %x, i32 %y) {
+; CHECK-LABEL: @no_fold_mismatch_var(
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i32 [[Y:%.*]], 2
 ; CHECK-NEXT:    [[POP:%.*]] = call range(i32 0, 33) i32 @llvm.ctpop.i32(i32 [[X:%.*]])
-; CHECK-NEXT:    ret i32 [[POP]]
+; CHECK-NEXT:    [[RES:%.*]] = select i1 [[CMP]], i32 [[Y]], i32 [[POP]]
+; CHECK-NEXT:    ret i32 [[RES]]
 ;
-  %cmp = icmp eq i32 %x, -1
+  %cmp = icmp ult i32 %y, 2
   %pop = call i32 @llvm.ctpop.i32(i32 %x)
-  %res = select i1 %cmp, i32 32, i32 %pop
+  %res = select i1 %cmp, i32 %y, i32 %pop
   ret i32 %res
 }



More information about the llvm-commits mailing list