[llvm] [InstCombine] support trunc condition in foldSelectICmpAndAnd (PR #220690)

Andreas Jonson via llvm-commits llvm-commits at lists.llvm.org
Mon Sep 7 12:01:17 PDT 2026


https://github.com/andjo403 updated https://github.com/llvm/llvm-project/pull/220690

>From e821d4aa67aa318d5371ffe8670b0918f513fafb Mon Sep 17 00:00:00 2001
From: Andreas Jonson <andjo403 at hotmail.com>
Date: Wed, 2 Sep 2026 20:14:24 +0200
Subject: [PATCH 1/2] [InstCombine] pre commit test (NFC).

---
 .../InstCombine/select-of-bittest.ll          | 76 ++++++++++++++++++-
 1 file changed, 72 insertions(+), 4 deletions(-)

diff --git a/llvm/test/Transforms/InstCombine/select-of-bittest.ll b/llvm/test/Transforms/InstCombine/select-of-bittest.ll
index 4495e7f6ed067..199f1bed339b3 100644
--- a/llvm/test/Transforms/InstCombine/select-of-bittest.ll
+++ b/llvm/test/Transforms/InstCombine/select-of-bittest.ll
@@ -3,6 +3,10 @@
 
 ; https://bugs.llvm.org/show_bug.cgi?id=36950
 
+declare void @use32(i32)
+declare void @use1(i1)
+declare void @use_vec1(<2 x i1>)
+
 ; These all should be just and+icmp, there should be no select.
 
 define i32 @and_lshr_and(i32 %arg) {
@@ -20,6 +24,40 @@ define i32 @and_lshr_and(i32 %arg) {
   ret i32 %t4
 }
 
+define i32 @and_lshr_and_ne_cond(i32 %arg) {
+; CHECK-LABEL: @and_lshr_and_ne_cond(
+; CHECK-NEXT:    [[TMP1:%.*]] = and i32 [[ARG:%.*]], 4
+; CHECK-NEXT:    [[TMP2:%.*]] = icmp ne i32 [[TMP1]], 0
+; CHECK-NEXT:    call void @use1(i1 [[TMP2]])
+; CHECK-NEXT:    [[T2:%.*]] = lshr i32 [[ARG]], 1
+; CHECK-NEXT:    [[T3:%.*]] = and i32 [[T2]], 1
+; CHECK-NEXT:    [[T4:%.*]] = select i1 [[TMP2]], i32 1, i32 [[T3]]
+; CHECK-NEXT:    ret i32 [[T4]]
+;
+  %t = and i32 %arg, 4
+  %t1 = icmp ne i32 %t, 0
+  call void @use1(i1 %t1)
+  %t2 = lshr i32 %arg, 1
+  %t3 = and i32 %t2, 1
+  %t4 = select i1 %t1, i32 1, i32 %t3
+  ret i32 %t4
+}
+
+define i32 @and_lshr_and_trunc_cond(i32 %arg) {
+; CHECK-LABEL: @and_lshr_and_trunc_cond(
+; CHECK-NEXT:    [[T1:%.*]] = trunc i32 [[ARG:%.*]] to i1
+; CHECK-NEXT:    [[T2:%.*]] = lshr i32 [[ARG]], 1
+; CHECK-NEXT:    [[T3:%.*]] = and i32 [[T2]], 1
+; CHECK-NEXT:    [[T4:%.*]] = select i1 [[T1]], i32 1, i32 [[T3]]
+; CHECK-NEXT:    ret i32 [[T4]]
+;
+  %t1 = trunc i32 %arg to i1
+  %t2 = lshr i32 %arg, 1
+  %t3 = and i32 %t2, 1
+  %t4 = select i1 %t1, i32 1, i32 %t3
+  ret i32 %t4
+}
+
 define <2 x i32> @and_lshr_and_splatvec(<2 x i32> %arg) {
 ; CHECK-LABEL: @and_lshr_and_splatvec(
 ; CHECK-NEXT:    [[TMP1:%.*]] = and <2 x i32> [[ARG:%.*]], splat (i32 3)
@@ -35,6 +73,40 @@ define <2 x i32> @and_lshr_and_splatvec(<2 x i32> %arg) {
   ret <2 x i32> %t4
 }
 
+define <2 x i32> @and_lshr_and_ne_cond_splatvec(<2 x i32> %arg) {
+; CHECK-LABEL: @and_lshr_and_ne_cond_splatvec(
+; CHECK-NEXT:    [[T:%.*]] = and <2 x i32> [[ARG:%.*]], splat (i32 4)
+; CHECK-NEXT:    [[T1:%.*]] = icmp ne <2 x i32> [[T]], zeroinitializer
+; CHECK-NEXT:    call void @use_vec1(<2 x i1> [[T1]])
+; CHECK-NEXT:    [[T2:%.*]] = lshr <2 x i32> [[ARG]], splat (i32 1)
+; CHECK-NEXT:    [[T3:%.*]] = and <2 x i32> [[T2]], splat (i32 1)
+; CHECK-NEXT:    [[T4:%.*]] = select <2 x i1> [[T1]], <2 x i32> splat (i32 1), <2 x i32> [[T3]]
+; CHECK-NEXT:    ret <2 x i32> [[T4]]
+;
+  %t = and <2 x i32> %arg, <i32 4, i32 4>
+  %t1 = icmp ne <2 x i32> %t, zeroinitializer
+  call void @use_vec1(<2 x i1> %t1)
+  %t2 = lshr <2 x i32> %arg, <i32 1, i32 1>
+  %t3 = and <2 x i32> %t2, <i32 1, i32 1>
+  %t4 = select <2 x i1> %t1, <2 x i32> <i32 1, i32 1>, <2 x i32> %t3
+  ret <2 x i32> %t4
+}
+
+define <2 x i32> @and_lshr_and_trunc_cond_splatvec(<2 x i32> %arg) {
+; CHECK-LABEL: @and_lshr_and_trunc_cond_splatvec(
+; CHECK-NEXT:    [[T1:%.*]] = trunc <2 x i32> [[ARG:%.*]] to <2 x i1>
+; CHECK-NEXT:    [[T2:%.*]] = lshr <2 x i32> [[ARG]], splat (i32 1)
+; CHECK-NEXT:    [[T3:%.*]] = and <2 x i32> [[T2]], splat (i32 1)
+; CHECK-NEXT:    [[T4:%.*]] = select <2 x i1> [[T1]], <2 x i32> splat (i32 1), <2 x i32> [[T3]]
+; CHECK-NEXT:    ret <2 x i32> [[T4]]
+;
+  %t1 = trunc <2 x i32> %arg to <2 x i1>
+  %t2 = lshr <2 x i32> %arg, <i32 1, i32 1>
+  %t3 = and <2 x i32> %t2, <i32 1, i32 1>
+  %t4 = select <2 x i1> %t1, <2 x i32> <i32 1, i32 1>, <2 x i32> %t3
+  ret <2 x i32> %t4
+}
+
 define <2 x i32> @and_lshr_and_vec_v0(<2 x i32> %arg) {
 ; CHECK-LABEL: @and_lshr_and_vec_v0(
 ; CHECK-NEXT:    [[TMP1:%.*]] = and <2 x i32> [[ARG:%.*]], <i32 3, i32 6>
@@ -449,10 +521,6 @@ define <3 x i32> @f_var3_vec_poison(<3 x i32> %arg, <3 x i32> %arg1, <3 x i32> %
 
 ; One use only.
 
-declare void @use32(i32)
-
-declare void @use1(i1)
-
 define i32 @n_var0_oneuse(i32 %arg, i32 %arg1, i32 %arg2) {
 ; CHECK-LABEL: @n_var0_oneuse(
 ; CHECK-NEXT:    [[T:%.*]] = and i32 [[ARG:%.*]], [[ARG1:%.*]]

>From 511e33831df84efff67188ac557825e8c28e402b Mon Sep 17 00:00:00 2001
From: Andreas Jonson <andjo403 at hotmail.com>
Date: Wed, 2 Sep 2026 20:14:34 +0200
Subject: [PATCH 2/2] [InstCombine] support trunc condition in
 foldSelectICmpAndAnd

---
 llvm/include/llvm/IR/PatternMatch.h           | 11 ++++
 .../InstCombine/InstCombineSelect.cpp         | 65 ++++++++++++-------
 .../InstCombine/select-of-bittest.ll          | 64 ++++++++++++------
 3 files changed, 94 insertions(+), 46 deletions(-)

diff --git a/llvm/include/llvm/IR/PatternMatch.h b/llvm/include/llvm/IR/PatternMatch.h
index 88f3989352cf6..32d5e45320463 100644
--- a/llvm/include/llvm/IR/PatternMatch.h
+++ b/llvm/include/llvm/IR/PatternMatch.h
@@ -688,6 +688,17 @@ m_SpecificInt_ICMP(ICmpInst::Predicate Predicate, const APInt &Threshold) {
   return P;
 }
 
+/// Match an integer or vector with every element comparing 'pred' (eg/ne/...)
+/// to Threshold. For vectors, this includes constants with undefined elements.
+inline cst_pred_ty<icmp_pred_with_threshold, false>
+m_SpecificInt_ICMP_ForbidPoison(ICmpInst::Predicate Predicate,
+                                const APInt &Threshold) {
+  cst_pred_ty<icmp_pred_with_threshold, false> P;
+  P.Pred = Predicate;
+  P.Thr = &Threshold;
+  return P;
+}
+
 struct is_nan {
   bool isValue(const APFloat &C) const { return C.isNaN(); }
 };
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
index 4b01334f8634c..11ae934a55403 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
@@ -749,37 +749,52 @@ static Value *foldSelectICmpMinMax(const ICmpInst *Cmp, Value *TVal,
 ///   Z may be 0 if lshr is missing.
 /// Worst-case scenario is that we will replace 5 instructions with 5 different
 /// instructions, but we got rid of select.
-static Instruction *foldSelectICmpAndAnd(Type *SelType, const ICmpInst *Cmp,
+static Instruction *foldSelectICmpAndAnd(Type *SelType, const Value *Cond,
                                          Value *TVal, Value *FVal,
                                          InstCombiner::BuilderTy &Builder) {
-  if (!(Cmp->hasOneUse() && Cmp->getOperand(0)->hasOneUse() &&
-        Cmp->getPredicate() == ICmpInst::ICMP_EQ &&
-        match(Cmp->getOperand(1), m_Zero()) && match(FVal, m_One())))
+  Value *A, *X, *Y, *Z;
+  CmpPredicate Pred;
+  unsigned NumReplaced = 1 + Cond->hasOneUse();
+  if (match(Cond, m_Trunc(m_Value(X)))) {
+    Y = ConstantInt::get(X->getType(), 1);
+    Pred = ICmpInst::ICMP_NE;
+  } else if (match(Cond,
+                   m_ICmp(Pred, m_And(m_Value(X), m_Value(Y)), m_Zero())) &&
+             ICmpInst::isEquality(Pred)) {
+    NumReplaced +=
+        Cond->hasOneUse() && cast<ICmpInst>(Cond)->getOperand(0)->hasOneUse();
+  } else
+    return nullptr;
+
+  if (Pred == ICmpInst::ICMP_NE)
+    std::swap(TVal, FVal);
+
+  if (!match(FVal, m_One()))
     return nullptr;
 
   // The TrueVal has general form of:  and %B, 1
-  Value *B;
-  if (!match(TVal, m_OneUse(m_And(m_Value(B), m_One()))))
+  if (!match(TVal, m_And(m_Value(A), m_One())))
     return nullptr;
 
-  // Where %B may be optionally shifted:  lshr %X, %Z.
-  Value *X, *Z;
-  const bool HasShift = match(B, m_OneUse(m_LShr(m_Value(X), m_Value(Z))));
+  auto TValPattern = m_CombineOr(
+      m_Deferred(X),
+      m_LShr(m_Deferred(X),
+             m_Value(Z, m_SpecificInt_ICMP_ForbidPoison(
+                            CmpInst::ICMP_ULT,
+                            APInt(SelType->getScalarSizeInBits(),
+                                  SelType->getScalarSizeInBits())))));
 
-  // The shift must be valid.
-  // TODO: This restricts the fold to constant shift amounts. Is there a way to
-  //       handle variable shifts safely? PR47012
-  if (HasShift &&
-      !match(Z, m_SpecificInt_ICMP(CmpInst::ICMP_ULT,
-                                   APInt(SelType->getScalarSizeInBits(),
-                                         SelType->getScalarSizeInBits()))))
-    return nullptr;
+  if (!match(A, TValPattern)) {
+    std::swap(X, Y);
+    if (!match(A, TValPattern))
+      return nullptr;
+  }
 
-  if (!HasShift)
-    X = B;
+  bool HasShift = A != X;
+  if (TVal->hasOneUse())
+    NumReplaced += 1 + (HasShift && A->hasOneUse());
 
-  Value *Y;
-  if (!match(Cmp->getOperand(0), m_c_And(m_Specific(X), m_Value(Y))))
+  if (NumReplaced < (4u - isa<Constant>(Y)))
     return nullptr;
 
   // ((X & Y) == 0) ? ((X >> Z) & 1) : 1 --> (X & (Y | (1 << Z))) != 0
@@ -2451,10 +2466,6 @@ Instruction *InstCombinerImpl::foldSelectInstWithICmp(SelectInst &SI,
   if (Value *V = foldSelectICmpMinMax(ICI, TrueVal, FalseVal, Builder, SQ))
     return replaceInstUsesWith(SI, V);
 
-  if (Instruction *V =
-          foldSelectICmpAndAnd(SI.getType(), ICI, TrueVal, FalseVal, Builder))
-    return V;
-
   if (Value *V = foldSelectICmpAndZeroShl(ICI, TrueVal, FalseVal, Builder))
     return replaceInstUsesWith(SI, V);
 
@@ -4916,6 +4927,10 @@ Instruction *InstCombinerImpl::visitSelectInst(SelectInst &SI) {
     if (Instruction *Result = foldSelectInstWithICmp(SI, ICI))
       return Result;
 
+  if (Instruction *V =
+          foldSelectICmpAndAnd(SelType, CondVal, TrueVal, FalseVal, Builder))
+    return V;
+
   if (Value *V = foldSelectBitTest(SI, CondVal, TrueVal, FalseVal, Builder, SQ))
     return replaceInstUsesWith(SI, V);
 
diff --git a/llvm/test/Transforms/InstCombine/select-of-bittest.ll b/llvm/test/Transforms/InstCombine/select-of-bittest.ll
index 199f1bed339b3..3715038d7fd01 100644
--- a/llvm/test/Transforms/InstCombine/select-of-bittest.ll
+++ b/llvm/test/Transforms/InstCombine/select-of-bittest.ll
@@ -29,9 +29,9 @@ define i32 @and_lshr_and_ne_cond(i32 %arg) {
 ; CHECK-NEXT:    [[TMP1:%.*]] = and i32 [[ARG:%.*]], 4
 ; CHECK-NEXT:    [[TMP2:%.*]] = icmp ne i32 [[TMP1]], 0
 ; CHECK-NEXT:    call void @use1(i1 [[TMP2]])
-; CHECK-NEXT:    [[T2:%.*]] = lshr i32 [[ARG]], 1
-; CHECK-NEXT:    [[T3:%.*]] = and i32 [[T2]], 1
-; CHECK-NEXT:    [[T4:%.*]] = select i1 [[TMP2]], i32 1, i32 [[T3]]
+; CHECK-NEXT:    [[TMP3:%.*]] = and i32 [[ARG]], 6
+; CHECK-NEXT:    [[TMP4:%.*]] = icmp ne i32 [[TMP3]], 0
+; CHECK-NEXT:    [[T4:%.*]] = zext i1 [[TMP4]] to i32
 ; CHECK-NEXT:    ret i32 [[T4]]
 ;
   %t = and i32 %arg, 4
@@ -45,10 +45,9 @@ define i32 @and_lshr_and_ne_cond(i32 %arg) {
 
 define i32 @and_lshr_and_trunc_cond(i32 %arg) {
 ; CHECK-LABEL: @and_lshr_and_trunc_cond(
-; CHECK-NEXT:    [[T1:%.*]] = trunc i32 [[ARG:%.*]] to i1
-; CHECK-NEXT:    [[T2:%.*]] = lshr i32 [[ARG]], 1
-; CHECK-NEXT:    [[T3:%.*]] = and i32 [[T2]], 1
-; CHECK-NEXT:    [[T4:%.*]] = select i1 [[T1]], i32 1, i32 [[T3]]
+; CHECK-NEXT:    [[TMP1:%.*]] = and i32 [[ARG:%.*]], 3
+; CHECK-NEXT:    [[TMP2:%.*]] = icmp ne i32 [[TMP1]], 0
+; CHECK-NEXT:    [[T4:%.*]] = zext i1 [[TMP2]] to i32
 ; CHECK-NEXT:    ret i32 [[T4]]
 ;
   %t1 = trunc i32 %arg to i1
@@ -78,9 +77,9 @@ define <2 x i32> @and_lshr_and_ne_cond_splatvec(<2 x i32> %arg) {
 ; CHECK-NEXT:    [[T:%.*]] = and <2 x i32> [[ARG:%.*]], splat (i32 4)
 ; CHECK-NEXT:    [[T1:%.*]] = icmp ne <2 x i32> [[T]], zeroinitializer
 ; CHECK-NEXT:    call void @use_vec1(<2 x i1> [[T1]])
-; CHECK-NEXT:    [[T2:%.*]] = lshr <2 x i32> [[ARG]], splat (i32 1)
-; CHECK-NEXT:    [[T3:%.*]] = and <2 x i32> [[T2]], splat (i32 1)
-; CHECK-NEXT:    [[T4:%.*]] = select <2 x i1> [[T1]], <2 x i32> splat (i32 1), <2 x i32> [[T3]]
+; CHECK-NEXT:    [[TMP1:%.*]] = and <2 x i32> [[ARG]], splat (i32 6)
+; CHECK-NEXT:    [[TMP2:%.*]] = icmp ne <2 x i32> [[TMP1]], zeroinitializer
+; CHECK-NEXT:    [[T4:%.*]] = zext <2 x i1> [[TMP2]] to <2 x i32>
 ; CHECK-NEXT:    ret <2 x i32> [[T4]]
 ;
   %t = and <2 x i32> %arg, <i32 4, i32 4>
@@ -94,10 +93,9 @@ define <2 x i32> @and_lshr_and_ne_cond_splatvec(<2 x i32> %arg) {
 
 define <2 x i32> @and_lshr_and_trunc_cond_splatvec(<2 x i32> %arg) {
 ; CHECK-LABEL: @and_lshr_and_trunc_cond_splatvec(
-; CHECK-NEXT:    [[T1:%.*]] = trunc <2 x i32> [[ARG:%.*]] to <2 x i1>
-; CHECK-NEXT:    [[T2:%.*]] = lshr <2 x i32> [[ARG]], splat (i32 1)
-; CHECK-NEXT:    [[T3:%.*]] = and <2 x i32> [[T2]], splat (i32 1)
-; CHECK-NEXT:    [[T4:%.*]] = select <2 x i1> [[T1]], <2 x i32> splat (i32 1), <2 x i32> [[T3]]
+; CHECK-NEXT:    [[TMP1:%.*]] = and <2 x i32> [[ARG:%.*]], splat (i32 3)
+; CHECK-NEXT:    [[TMP2:%.*]] = icmp ne <2 x i32> [[TMP1]], zeroinitializer
+; CHECK-NEXT:    [[T4:%.*]] = zext <2 x i1> [[TMP2]] to <2 x i32>
 ; CHECK-NEXT:    ret <2 x i32> [[T4]]
 ;
   %t1 = trunc <2 x i32> %arg to <2 x i1>
@@ -154,9 +152,11 @@ define <2 x i32> @and_lshr_and_vec_v2(<2 x i32> %arg) {
 
 define <3 x i32> @and_lshr_and_vec_poison(<3 x i32> %arg) {
 ; CHECK-LABEL: @and_lshr_and_vec_poison(
-; CHECK-NEXT:    [[TMP1:%.*]] = and <3 x i32> [[ARG:%.*]], <i32 3, i32 poison, i32 3>
-; CHECK-NEXT:    [[TMP2:%.*]] = icmp ne <3 x i32> [[TMP1]], zeroinitializer
-; CHECK-NEXT:    [[T4:%.*]] = zext <3 x i1> [[TMP2]] to <3 x i32>
+; CHECK-NEXT:    [[T:%.*]] = and <3 x i32> [[ARG:%.*]], <i32 1, i32 poison, i32 1>
+; CHECK-NEXT:    [[T1:%.*]] = icmp eq <3 x i32> [[T]], <i32 0, i32 poison, i32 0>
+; CHECK-NEXT:    [[T2:%.*]] = lshr <3 x i32> [[ARG]], <i32 1, i32 poison, i32 1>
+; CHECK-NEXT:    [[T3:%.*]] = and <3 x i32> [[T2]], <i32 1, i32 poison, i32 1>
+; CHECK-NEXT:    [[T4:%.*]] = select <3 x i1> [[T1]], <3 x i32> [[T3]], <3 x i32> <i32 1, i32 poison, i32 1>
 ; CHECK-NEXT:    ret <3 x i32> [[T4]]
 ;
   %t = and <3 x i32> %arg, <i32 1, i32 poison, i32 1>
@@ -294,10 +294,11 @@ define <2 x i32> @f_var0_vec(<2 x i32> %arg, <2 x i32> %arg1) {
 
 define <3 x i32> @f_var0_vec_poison(<3 x i32> %arg, <3 x i32> %arg1) {
 ; CHECK-LABEL: @f_var0_vec_poison(
-; CHECK-NEXT:    [[TMP1:%.*]] = or <3 x i32> [[ARG1:%.*]], <i32 2, i32 poison, i32 2>
-; CHECK-NEXT:    [[TMP2:%.*]] = and <3 x i32> [[ARG:%.*]], [[TMP1]]
-; CHECK-NEXT:    [[TMP3:%.*]] = icmp ne <3 x i32> [[TMP2]], zeroinitializer
-; CHECK-NEXT:    [[T5:%.*]] = zext <3 x i1> [[TMP3]] to <3 x i32>
+; CHECK-NEXT:    [[TMP2:%.*]] = and <3 x i32> [[ARG:%.*]], [[TMP1:%.*]]
+; CHECK-NEXT:    [[T2:%.*]] = icmp eq <3 x i32> [[TMP2]], <i32 0, i32 poison, i32 0>
+; CHECK-NEXT:    [[T3:%.*]] = lshr <3 x i32> [[ARG]], <i32 1, i32 poison, i32 1>
+; CHECK-NEXT:    [[T4:%.*]] = and <3 x i32> [[T3]], <i32 1, i32 poison, i32 1>
+; CHECK-NEXT:    [[T5:%.*]] = select <3 x i1> [[T2]], <3 x i32> [[T4]], <3 x i32> <i32 1, i32 poison, i32 1>
 ; CHECK-NEXT:    ret <3 x i32> [[T5]]
 ;
   %t = and <3 x i32> %arg, %arg1
@@ -567,6 +568,27 @@ define i32 @n_var1_oneuse(i32 %arg, i32 %arg1) {
   ret i32 %t4
 }
 
+define i32 @lshr_oneuse_and_multiuse(i32 %arg) {
+; CHECK-LABEL: @lshr_oneuse_and_multiuse(
+; CHECK-NEXT:    [[T:%.*]] = and i32 [[ARG:%.*]], 5
+; CHECK-NEXT:    [[T3:%.*]] = icmp eq i32 [[T]], 0
+; CHECK-NEXT:    [[TMP1:%.*]] = lshr i32 [[ARG]], 1
+; CHECK-NEXT:    [[DOTLOBIT:%.*]] = and i32 [[TMP1]], 1
+; CHECK-NEXT:    [[T_LOBIT:%.*]] = select i1 [[T3]], i32 [[DOTLOBIT]], i32 1
+; CHECK-NEXT:    call void @use32(i32 [[T]])
+; CHECK-NEXT:    call void @use32(i32 [[DOTLOBIT]])
+; CHECK-NEXT:    ret i32 [[T_LOBIT]]
+;
+  %t = and i32 %arg, 5
+  %t3 = icmp eq i32 %t, 0
+  %t4 = lshr i32 %arg, 1
+  %t5 = and i32 %t4, 1
+  %t6 = select i1 %t3, i32 %t5, i32 1
+  call void @use32(i32 %t)
+  call void @use32(i32 %t5)
+  ret i32 %t6
+}
+
 ; Different variables are used
 
 define i32 @n0(i32 %arg, i32 %arg1) {



More information about the llvm-commits mailing list