[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