[llvm] [SLP] Vectorize zero-tested OR/UMax reductions (PR #205473)
via llvm-commits
llvm-commits at lists.llvm.org
Sat Jun 27 02:37:41 PDT 2026
https://github.com/ParkHanbum updated https://github.com/llvm/llvm-project/pull/205473
>From 2b40ec7dac871fe11c0614b55486f8b8fad20024 Mon Sep 17 00:00:00 2001
From: Hanbum Park <kese111 at gmail.com>
Date: Tue, 23 Jun 2026 23:36:38 +0900
Subject: [PATCH 1/3] [SLP] Vectorize zero-tested OR/UMax reductions
When a scalar OR or UMax reduction has a single eq/ne-zero use, form
an equivalent lane-wise comparison followed by an i1 OR/AND reduction.
This avoids generating an integer vector reduction only to test its
scalar result, and exposes a boolean reduction that targets can lower
directly. Model the vectorization cost as a vector compare plus the
corresponding boolean reduction.
Add regression tests for both predicates, operand orders, supported
vector widths, and non-transformable uses.
Fixes #195118
---
.../Transforms/Vectorize/SLPVectorizer.cpp | 195 +++++++++++++++++-
.../WebAssembly/or-reduction-zero-test.ll | 42 +---
.../X86/reduction-zero-test-ctpop.ll | 24 ++-
3 files changed, 213 insertions(+), 48 deletions(-)
diff --git a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
index d1fb8e2beea50..b4f2189983043 100644
--- a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
+++ b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
@@ -28713,6 +28713,19 @@ namespace {
class HorizontalReduction {
using ReductionOpsType = SmallVector<Value *, 16>;
using ReductionOpsListType = SmallVector<ReductionOpsType, 2>;
+ enum class ZeroTestLoweringKind {
+ BoolReduction,
+ Ctpop,
+ };
+ struct ZeroTestLoweringInfo {
+ ZeroTestLoweringKind Kind;
+ InstructionCost Cost;
+ };
+ struct ReductionZeroTestInfo {
+ ICmpInst *Cmp;
+ CmpPredicate Pred;
+ };
+
ReductionOpsListType ReductionOps;
/// List of possibly reduced values.
SmallVector<SmallVector<Value *>> ReducedVals;
@@ -28744,6 +28757,107 @@ class HorizontalReduction {
(match(I, m_LogicalAnd()) || match(I, m_LogicalOr()));
}
+ std::optional<ReductionZeroTestInfo> matchReductionZeroTestUse() const {
+ auto *Root = dyn_cast<Instruction>(ReductionRoot);
+ if (!Root || !Root->getType()->isIntegerTy() || !Root->hasOneUse() ||
+ (RdxKind != RecurKind::Or && RdxKind != RecurKind::UMax))
+ return std::nullopt;
+
+ auto *Cmp = dyn_cast<ICmpInst>(*Root->user_begin());
+ CmpPredicate Pred;
+ if (!Cmp || !match(Cmp, m_c_ICmp(Pred, m_Specific(Root), m_ZeroInt())) ||
+ !ICmpInst::isEquality(Pred))
+ return std::nullopt;
+
+ return ReductionZeroTestInfo{Cmp, Pred};
+ }
+
+ static InstructionCost
+ getBoolReductionZeroTestCost(const TargetTransformInfo &TTI,
+ FixedVectorType *VecTy, CmpPredicate Pred,
+ TTI::TargetCostKind CostKind) {
+ auto *CmpTy = cast<VectorType>(CmpInst::makeCmpResultType(VecTy));
+ unsigned ReductionOpcode =
+ Pred == ICmpInst::ICMP_EQ ? Instruction::And : Instruction::Or;
+ return TTI.getCmpSelInstrCost(Instruction::ICmp, VecTy, CmpTy, Pred,
+ CostKind) +
+ TTI.getArithmeticReductionCost(ReductionOpcode, CmpTy, {},
+ CostKind);
+ }
+
+ static InstructionCost
+ getCtpopZeroTestCost(const TargetTransformInfo &TTI, FixedVectorType *VecTy,
+ CmpPredicate Pred, TTI::TargetCostKind CostKind) {
+ LLVMContext &Ctx = VecTy->getContext();
+ ElementCount EC = VecTy->getElementCount();
+ if (EC.isScalable())
+ return InstructionCost::getInvalid();
+
+ auto *CmpTy = cast<VectorType>(CmpInst::makeCmpResultType(VecTy));
+ auto *MaskIntTy = IntegerType::get(Ctx, EC.getFixedValue());
+ InstructionCost CmpCost = TTI.getCmpSelInstrCost(
+ Instruction::ICmp, VecTy, CmpTy, ICmpInst::ICMP_NE, CostKind);
+ InstructionCost BitcastCost = TTI.getCastInstrCost(
+ Instruction::BitCast, MaskIntTy, CmpTy, TTI::CastContextHint::None,
+ CostKind);
+ IntrinsicCostAttributes ICA(Intrinsic::ctpop, MaskIntTy, {MaskIntTy});
+ InstructionCost CtpopCost = TTI.getIntrinsicInstrCost(ICA, CostKind);
+ InstructionCost ScalarCmpCost = TTI.getCmpSelInstrCost(
+ Instruction::ICmp, MaskIntTy, Type::getInt1Ty(Ctx), Pred, CostKind);
+ return CmpCost + BitcastCost + CtpopCost + ScalarCmpCost;
+ }
+
+ static std::optional<ZeroTestLoweringInfo>
+ getBestReductionZeroTestLowering(const TargetTransformInfo &TTI,
+ FixedVectorType *VecTy, CmpPredicate Pred,
+ TTI::TargetCostKind CostKind) {
+ if (Pred != ICmpInst::ICMP_EQ && Pred != ICmpInst::ICMP_NE)
+ return std::nullopt;
+
+ InstructionCost BoolCost =
+ getBoolReductionZeroTestCost(TTI, VecTy, Pred, CostKind);
+ InstructionCost CtpopCost =
+ getCtpopZeroTestCost(TTI, VecTy, Pred, CostKind);
+ if (!BoolCost.isValid() && !CtpopCost.isValid())
+ return std::nullopt;
+ if (!CtpopCost.isValid())
+ return ZeroTestLoweringInfo{ZeroTestLoweringKind::BoolReduction,
+ BoolCost};
+ if (!BoolCost.isValid())
+ return ZeroTestLoweringInfo{ZeroTestLoweringKind::Ctpop, CtpopCost};
+ if (CtpopCost < BoolCost)
+ return ZeroTestLoweringInfo{ZeroTestLoweringKind::Ctpop, CtpopCost};
+ return ZeroTestLoweringInfo{ZeroTestLoweringKind::BoolReduction, BoolCost};
+ }
+
+ static Value *createBoolReductionZeroTest(IRBuilderBase &Builder, Value *Vec,
+ CmpPredicate Pred) {
+ auto *VecTy = dyn_cast<VectorType>(Vec->getType());
+ if (!VecTy)
+ return nullptr;
+
+ Value *Cmp =
+ Builder.CreateICmp(Pred, Vec, Constant::getNullValue(VecTy));
+ RecurKind ReductionKind =
+ Pred == ICmpInst::ICMP_EQ ? RecurKind::And : RecurKind::Or;
+ return createSimpleReduction(Builder, Cmp, ReductionKind);
+ }
+
+ static Value *createCtpopZeroTest(IRBuilderBase &Builder, Value *Vec,
+ CmpPredicate Pred) {
+ auto *VecTy = dyn_cast<FixedVectorType>(Vec->getType());
+ if (!VecTy)
+ return nullptr;
+
+ Type *MaskIntTy =
+ Builder.getIntNTy(VecTy->getElementCount().getFixedValue());
+ Value *LaneNonZero =
+ Builder.CreateICmpNE(Vec, Constant::getNullValue(VecTy));
+ Value *MaskBits = Builder.CreateBitCast(LaneNonZero, MaskIntTy);
+ Value *Pop = Builder.CreateUnaryIntrinsic(Intrinsic::ctpop, MaskBits);
+ return Builder.CreateICmp(Pred, Pop, ConstantInt::get(MaskIntTy, 0));
+ }
+
/// Checks if instruction is associative and can be vectorized.
enum class ReductionOrdering { Unordered, Ordered, None };
ReductionOrdering RK = ReductionOrdering::None;
@@ -30224,7 +30338,59 @@ class HorizontalReduction {
}
VectorizedTree = ExtraReductions.front().second;
- ReductionRoot->replaceAllUsesWith(VectorizedTree);
+ // Fold an eq/ne-zero test of a single integer reduction into a lane-wise
+ // comparison and an i1 reduction. For example, transform:
+ // %r = call i8 @llvm.vector.reduce.or.v16i8(<16 x i8> %v)
+ // %cmp = icmp ne i8 %r, 0
+ // into:
+ // %lanes = icmp ne <16 x i8> %v, zeroinitializer
+ // %cmp = call i1 @llvm.vector.reduce.or.v16i1(<16 x i1> %lanes)
+ bool ReplacedZeroTest = false;
+ if (auto ZeroTest = matchReductionZeroTestUse()) {
+ auto *Reduction = dyn_cast<IntrinsicInst>(VectorizedTree);
+ Intrinsic::ID ReductionID = RdxKind == RecurKind::Or
+ ? Intrinsic::vector_reduce_or
+ : Intrinsic::vector_reduce_umax;
+ if (Reduction && Reduction->getIntrinsicID() == ReductionID) {
+ Value *Vec = Reduction->getArgOperand(0);
+ auto *VecTy = dyn_cast<FixedVectorType>(Vec->getType());
+ if (VecTy && VecTy->getElementType()->isIntegerTy()) {
+ if (auto Lowering = getBestReductionZeroTestLowering(
+ *TTI, VecTy, ZeroTest->Pred, TTI::TCK_RecipThroughput)) {
+ Builder.SetInsertPoint(cast<Instruction>(ReductionRoot));
+ Builder.SetCurrentDebugLocation(ZeroTest->Cmp->getDebugLoc());
+ Value *NewReduction = nullptr;
+ switch (Lowering->Kind) {
+ case ZeroTestLoweringKind::BoolReduction:
+ NewReduction =
+ createBoolReductionZeroTest(Builder, Vec, ZeroTest->Pred);
+ break;
+ case ZeroTestLoweringKind::Ctpop:
+ NewReduction = createCtpopZeroTest(Builder, Vec, ZeroTest->Pred);
+ break;
+ }
+ assert(NewReduction && "Expected valid zero-test lowering.");
+ NewReduction->takeName(ZeroTest->Cmp);
+ ZeroTest->Cmp->replaceAllUsesWith(NewReduction);
+ VectorizedTree = NewReduction;
+ salvageDebugInfo(*ZeroTest->Cmp);
+ ZeroTest->Cmp->dropAllReferences();
+ ZeroTest->Cmp->removeFromParent();
+ V.eraseInstruction(ZeroTest->Cmp);
+ V.eraseInstruction(Reduction);
+ // Reuse the count for the replaced reduction and account for the
+ // new vector compare.
+ ++NumVectorInstructions;
+ if (Lowering->Kind == ZeroTestLoweringKind::Ctpop)
+ NumVectorInstructions += 2;
+ ReplacedZeroTest = true;
+ }
+ }
+ }
+ }
+
+ if (!ReplacedZeroTest)
+ ReductionRoot->replaceAllUsesWith(VectorizedTree);
// The original scalar reduction is expected to have no remaining
// uses outside the reduction tree itself. Assert that we got this
@@ -30648,6 +30814,23 @@ class HorizontalReduction {
// 2. The storage does not have any vector with full vector use (first
// vector with full register use).
bool DoesRequireReductionOp = !AllConsts && VectorValuesAndScales.empty();
+ auto getReductionZeroTestCost = [&]() -> std::optional<InstructionCost> {
+ auto ZeroTest = matchReductionZeroTestUse();
+ // Direct generation below is limited to one vectorized reduction.
+ if (!ZeroTest || !DoesRequireReductionOp ||
+ this->ReducedVals.size() != 1 || isa<VectorType>(ScalarTy))
+ return std::nullopt;
+
+ auto *FixedVecTy = dyn_cast<FixedVectorType>(VectorTy);
+ if (!FixedVecTy || !FixedVecTy->getElementType()->isIntegerTy())
+ return std::nullopt;
+
+ auto Lowering = getBestReductionZeroTestLowering(
+ *TTI, FixedVecTy, ZeroTest->Pred, CostKind);
+ if (!Lowering)
+ return std::nullopt;
+ return Lowering->Cost;
+ };
switch (RdxKind) {
case RecurKind::Add:
case RecurKind::Mul:
@@ -30659,7 +30842,9 @@ class HorizontalReduction {
unsigned RdxOpcode = RecurrenceDescriptor::getOpcode(RdxKind);
if (!AllConsts) {
if (DoesRequireReductionOp) {
- if (auto *VecTy = dyn_cast<FixedVectorType>(ScalarTy)) {
+ if (auto ZeroTestCost = getReductionZeroTestCost()) {
+ VectorCost = *ZeroTestCost;
+ } else if (auto *VecTy = dyn_cast<FixedVectorType>(ScalarTy)) {
assert(SLPReVec && "FixedVectorType is not expected.");
unsigned ScalarTyNumElements = VecTy->getNumElements();
for (unsigned I : seq<unsigned>(ReducedVals.size())) {
@@ -30764,7 +30949,11 @@ class HorizontalReduction {
Intrinsic::ID Id = getMinMaxReductionIntrinsicOp(RdxKind);
if (!AllConsts) {
if (DoesRequireReductionOp) {
- VectorCost = TTI->getMinMaxReductionCost(Id, VectorTy, FMF, CostKind);
+ if (auto ZeroTestCost = getReductionZeroTestCost())
+ VectorCost = *ZeroTestCost;
+ else
+ VectorCost =
+ TTI->getMinMaxReductionCost(Id, VectorTy, FMF, CostKind);
} else {
// Check if the previous reduction already exists and account it as
// series of operations + single reduction.
diff --git a/llvm/test/Transforms/SLPVectorizer/WebAssembly/or-reduction-zero-test.ll b/llvm/test/Transforms/SLPVectorizer/WebAssembly/or-reduction-zero-test.ll
index b7d0c2694af7f..1ca4943ace277 100644
--- a/llvm/test/Transforms/SLPVectorizer/WebAssembly/or-reduction-zero-test.ll
+++ b/llvm/test/Transforms/SLPVectorizer/WebAssembly/or-reduction-zero-test.ll
@@ -6,25 +6,8 @@ define i1 @or_reduction_nonzero(ptr %p) {
; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR0:[0-9]+]] {
; CHECK-NEXT: [[ENTRY:.*:]]
; CHECK-NEXT: [[TMP0:%.*]] = load <16 x i8>, ptr [[P]], align 1
-; CHECK-NEXT: [[TMP18:%.*]] = shufflevector <16 x i8> [[TMP0]], <16 x i8> poison, <2 x i32> <i32 0, i32 8>
-; CHECK-NEXT: [[TMP2:%.*]] = shufflevector <16 x i8> [[TMP0]], <16 x i8> poison, <2 x i32> <i32 1, i32 9>
-; CHECK-NEXT: [[TMP3:%.*]] = or <2 x i8> [[TMP18]], [[TMP2]]
-; CHECK-NEXT: [[TMP4:%.*]] = shufflevector <16 x i8> [[TMP0]], <16 x i8> poison, <2 x i32> <i32 2, i32 10>
-; CHECK-NEXT: [[TMP5:%.*]] = shufflevector <16 x i8> [[TMP0]], <16 x i8> poison, <2 x i32> <i32 3, i32 11>
-; CHECK-NEXT: [[TMP6:%.*]] = or <2 x i8> [[TMP4]], [[TMP5]]
-; CHECK-NEXT: [[TMP7:%.*]] = shufflevector <16 x i8> [[TMP0]], <16 x i8> poison, <2 x i32> <i32 4, i32 12>
-; CHECK-NEXT: [[TMP8:%.*]] = shufflevector <16 x i8> [[TMP0]], <16 x i8> poison, <2 x i32> <i32 5, i32 13>
-; CHECK-NEXT: [[TMP9:%.*]] = or <2 x i8> [[TMP7]], [[TMP8]]
-; CHECK-NEXT: [[TMP10:%.*]] = shufflevector <16 x i8> [[TMP0]], <16 x i8> poison, <2 x i32> <i32 6, i32 14>
-; CHECK-NEXT: [[TMP11:%.*]] = shufflevector <16 x i8> [[TMP0]], <16 x i8> poison, <2 x i32> <i32 7, i32 15>
-; CHECK-NEXT: [[TMP12:%.*]] = or <2 x i8> [[TMP10]], [[TMP11]]
-; CHECK-NEXT: [[TMP13:%.*]] = or <2 x i8> [[TMP3]], [[TMP6]]
-; CHECK-NEXT: [[TMP14:%.*]] = or <2 x i8> [[TMP9]], [[TMP12]]
-; CHECK-NEXT: [[TMP15:%.*]] = or <2 x i8> [[TMP13]], [[TMP14]]
-; CHECK-NEXT: [[TMP16:%.*]] = extractelement <2 x i8> [[TMP15]], i32 0
-; CHECK-NEXT: [[TMP17:%.*]] = extractelement <2 x i8> [[TMP15]], i32 1
-; CHECK-NEXT: [[TMP1:%.*]] = or i8 [[TMP16]], [[TMP17]]
-; CHECK-NEXT: [[CMP:%.*]] = icmp ne i8 [[TMP1]], 0
+; CHECK-NEXT: [[TMP1:%.*]] = icmp ne <16 x i8> [[TMP0]], zeroinitializer
+; CHECK-NEXT: [[CMP:%.*]] = call i1 @llvm.vector.reduce.or.v16i1(<16 x i1> [[TMP1]])
; CHECK-NEXT: ret i1 [[CMP]]
;
entry:
@@ -83,25 +66,8 @@ define i1 @or_reduction_zero(ptr %p) {
; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR0]] {
; CHECK-NEXT: [[ENTRY:.*:]]
; CHECK-NEXT: [[TMP0:%.*]] = load <16 x i8>, ptr [[P]], align 1
-; CHECK-NEXT: [[TMP18:%.*]] = shufflevector <16 x i8> [[TMP0]], <16 x i8> poison, <2 x i32> <i32 0, i32 8>
-; CHECK-NEXT: [[TMP2:%.*]] = shufflevector <16 x i8> [[TMP0]], <16 x i8> poison, <2 x i32> <i32 1, i32 9>
-; CHECK-NEXT: [[TMP3:%.*]] = or <2 x i8> [[TMP18]], [[TMP2]]
-; CHECK-NEXT: [[TMP4:%.*]] = shufflevector <16 x i8> [[TMP0]], <16 x i8> poison, <2 x i32> <i32 2, i32 10>
-; CHECK-NEXT: [[TMP5:%.*]] = shufflevector <16 x i8> [[TMP0]], <16 x i8> poison, <2 x i32> <i32 3, i32 11>
-; CHECK-NEXT: [[TMP6:%.*]] = or <2 x i8> [[TMP4]], [[TMP5]]
-; CHECK-NEXT: [[TMP7:%.*]] = shufflevector <16 x i8> [[TMP0]], <16 x i8> poison, <2 x i32> <i32 4, i32 12>
-; CHECK-NEXT: [[TMP8:%.*]] = shufflevector <16 x i8> [[TMP0]], <16 x i8> poison, <2 x i32> <i32 5, i32 13>
-; CHECK-NEXT: [[TMP9:%.*]] = or <2 x i8> [[TMP7]], [[TMP8]]
-; CHECK-NEXT: [[TMP10:%.*]] = shufflevector <16 x i8> [[TMP0]], <16 x i8> poison, <2 x i32> <i32 6, i32 14>
-; CHECK-NEXT: [[TMP11:%.*]] = shufflevector <16 x i8> [[TMP0]], <16 x i8> poison, <2 x i32> <i32 7, i32 15>
-; CHECK-NEXT: [[TMP12:%.*]] = or <2 x i8> [[TMP10]], [[TMP11]]
-; CHECK-NEXT: [[TMP13:%.*]] = or <2 x i8> [[TMP3]], [[TMP6]]
-; CHECK-NEXT: [[TMP14:%.*]] = or <2 x i8> [[TMP9]], [[TMP12]]
-; CHECK-NEXT: [[TMP15:%.*]] = or <2 x i8> [[TMP13]], [[TMP14]]
-; CHECK-NEXT: [[TMP16:%.*]] = extractelement <2 x i8> [[TMP15]], i32 0
-; CHECK-NEXT: [[TMP17:%.*]] = extractelement <2 x i8> [[TMP15]], i32 1
-; CHECK-NEXT: [[TMP1:%.*]] = or i8 [[TMP16]], [[TMP17]]
-; CHECK-NEXT: [[CMP:%.*]] = icmp eq i8 [[TMP1]], 0
+; CHECK-NEXT: [[TMP1:%.*]] = icmp eq <16 x i8> [[TMP0]], zeroinitializer
+; CHECK-NEXT: [[CMP:%.*]] = call i1 @llvm.vector.reduce.and.v16i1(<16 x i1> [[TMP1]])
; CHECK-NEXT: ret i1 [[CMP]]
;
entry:
diff --git a/llvm/test/Transforms/SLPVectorizer/X86/reduction-zero-test-ctpop.ll b/llvm/test/Transforms/SLPVectorizer/X86/reduction-zero-test-ctpop.ll
index 67fd3f4acacaa..e53d48d31fb5e 100644
--- a/llvm/test/Transforms/SLPVectorizer/X86/reduction-zero-test-ctpop.ll
+++ b/llvm/test/Transforms/SLPVectorizer/X86/reduction-zero-test-ctpop.ll
@@ -5,7 +5,9 @@ define i32 @or_nonzero(ptr %p) {
; CHECK-LABEL: define i32 @or_nonzero(
; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR0:[0-9]+]] {
; CHECK-NEXT: [[INPUT:%.*]] = load <8 x i8>, ptr [[P]], align 1
-; CHECK-NEXT: [[TMP3:%.*]] = call i8 @llvm.vector.reduce.or.v8i8(<8 x i8> [[INPUT]])
+; CHECK-NEXT: [[TMP1:%.*]] = icmp ne <8 x i8> [[INPUT]], zeroinitializer
+; CHECK-NEXT: [[TMP2:%.*]] = bitcast <8 x i1> [[TMP1]] to i8
+; CHECK-NEXT: [[TMP3:%.*]] = call i8 @llvm.ctpop.i8(i8 [[TMP2]])
; CHECK-NEXT: [[CMP:%.*]] = icmp ne i8 [[TMP3]], 0
; CHECK-NEXT: [[RESULT:%.*]] = zext i1 [[CMP]] to i32
; CHECK-NEXT: ret i32 [[RESULT]]
@@ -35,8 +37,10 @@ define i32 @or_nonzero_commuted(ptr %p) {
; CHECK-LABEL: define i32 @or_nonzero_commuted(
; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR0]] {
; CHECK-NEXT: [[INPUT:%.*]] = load <8 x i8>, ptr [[P]], align 1
-; CHECK-NEXT: [[TMP1:%.*]] = call i8 @llvm.vector.reduce.or.v8i8(<8 x i8> [[INPUT]])
-; CHECK-NEXT: [[CMP:%.*]] = icmp ne i8 0, [[TMP1]]
+; CHECK-NEXT: [[TMP1:%.*]] = icmp ne <8 x i8> [[INPUT]], zeroinitializer
+; CHECK-NEXT: [[TMP2:%.*]] = bitcast <8 x i1> [[TMP1]] to i8
+; CHECK-NEXT: [[TMP3:%.*]] = call i8 @llvm.ctpop.i8(i8 [[TMP2]])
+; CHECK-NEXT: [[CMP:%.*]] = icmp ne i8 [[TMP3]], 0
; CHECK-NEXT: [[RESULT:%.*]] = zext i1 [[CMP]] to i32
; CHECK-NEXT: ret i32 [[RESULT]]
;
@@ -65,7 +69,9 @@ define i32 @or_zero(ptr %p) {
; CHECK-LABEL: define i32 @or_zero(
; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR0]] {
; CHECK-NEXT: [[INPUT:%.*]] = load <8 x i8>, ptr [[P]], align 1
-; CHECK-NEXT: [[TMP3:%.*]] = call i8 @llvm.vector.reduce.or.v8i8(<8 x i8> [[INPUT]])
+; CHECK-NEXT: [[TMP1:%.*]] = icmp ne <8 x i8> [[INPUT]], zeroinitializer
+; CHECK-NEXT: [[TMP2:%.*]] = bitcast <8 x i1> [[TMP1]] to i8
+; CHECK-NEXT: [[TMP3:%.*]] = call i8 @llvm.ctpop.i8(i8 [[TMP2]])
; CHECK-NEXT: [[CMP:%.*]] = icmp eq i8 [[TMP3]], 0
; CHECK-NEXT: [[RESULT:%.*]] = zext i1 [[CMP]] to i32
; CHECK-NEXT: ret i32 [[RESULT]]
@@ -95,7 +101,9 @@ define i32 @umax_nonzero(ptr %p) {
; CHECK-LABEL: define i32 @umax_nonzero(
; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR0]] {
; CHECK-NEXT: [[INPUT:%.*]] = load <8 x i8>, ptr [[P]], align 1
-; CHECK-NEXT: [[TMP3:%.*]] = call i8 @llvm.vector.reduce.umax.v8i8(<8 x i8> [[INPUT]])
+; CHECK-NEXT: [[TMP1:%.*]] = icmp ne <8 x i8> [[INPUT]], zeroinitializer
+; CHECK-NEXT: [[TMP2:%.*]] = bitcast <8 x i1> [[TMP1]] to i8
+; CHECK-NEXT: [[TMP3:%.*]] = call i8 @llvm.ctpop.i8(i8 [[TMP2]])
; CHECK-NEXT: [[CMP:%.*]] = icmp ne i8 [[TMP3]], 0
; CHECK-NEXT: [[RESULT:%.*]] = zext i1 [[CMP]] to i32
; CHECK-NEXT: ret i32 [[RESULT]]
@@ -132,8 +140,10 @@ define i32 @umax_zero_commuted(ptr %p) {
; CHECK-LABEL: define i32 @umax_zero_commuted(
; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR0]] {
; CHECK-NEXT: [[INPUT:%.*]] = load <8 x i8>, ptr [[P]], align 1
-; CHECK-NEXT: [[TMP1:%.*]] = call i8 @llvm.vector.reduce.umax.v8i8(<8 x i8> [[INPUT]])
-; CHECK-NEXT: [[CMP:%.*]] = icmp eq i8 0, [[TMP1]]
+; CHECK-NEXT: [[TMP1:%.*]] = icmp ne <8 x i8> [[INPUT]], zeroinitializer
+; CHECK-NEXT: [[TMP2:%.*]] = bitcast <8 x i1> [[TMP1]] to i8
+; CHECK-NEXT: [[TMP3:%.*]] = call i8 @llvm.ctpop.i8(i8 [[TMP2]])
+; CHECK-NEXT: [[CMP:%.*]] = icmp eq i8 [[TMP3]], 0
; CHECK-NEXT: [[RESULT:%.*]] = zext i1 [[CMP]] to i32
; CHECK-NEXT: ret i32 [[RESULT]]
;
>From c829c462be9a845f078f4fcb2da325f1cda9ea6b Mon Sep 17 00:00:00 2001
From: hanbeom <kese111 at gmail.com>
Date: Sat, 27 Jun 2026 18:06:33 +0900
Subject: [PATCH 2/3] formmating
---
.../Transforms/Vectorize/SLPVectorizer.cpp | 19 +++++++++----------
1 file changed, 9 insertions(+), 10 deletions(-)
diff --git a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
index b4f2189983043..2b40b189a05ff 100644
--- a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
+++ b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
@@ -28781,13 +28781,13 @@ class HorizontalReduction {
Pred == ICmpInst::ICMP_EQ ? Instruction::And : Instruction::Or;
return TTI.getCmpSelInstrCost(Instruction::ICmp, VecTy, CmpTy, Pred,
CostKind) +
- TTI.getArithmeticReductionCost(ReductionOpcode, CmpTy, {},
- CostKind);
+ TTI.getArithmeticReductionCost(ReductionOpcode, CmpTy, {}, CostKind);
}
- static InstructionCost
- getCtpopZeroTestCost(const TargetTransformInfo &TTI, FixedVectorType *VecTy,
- CmpPredicate Pred, TTI::TargetCostKind CostKind) {
+ static InstructionCost getCtpopZeroTestCost(const TargetTransformInfo &TTI,
+ FixedVectorType *VecTy,
+ CmpPredicate Pred,
+ TTI::TargetCostKind CostKind) {
LLVMContext &Ctx = VecTy->getContext();
ElementCount EC = VecTy->getElementCount();
if (EC.isScalable())
@@ -28797,9 +28797,9 @@ class HorizontalReduction {
auto *MaskIntTy = IntegerType::get(Ctx, EC.getFixedValue());
InstructionCost CmpCost = TTI.getCmpSelInstrCost(
Instruction::ICmp, VecTy, CmpTy, ICmpInst::ICMP_NE, CostKind);
- InstructionCost BitcastCost = TTI.getCastInstrCost(
- Instruction::BitCast, MaskIntTy, CmpTy, TTI::CastContextHint::None,
- CostKind);
+ InstructionCost BitcastCost =
+ TTI.getCastInstrCost(Instruction::BitCast, MaskIntTy, CmpTy,
+ TTI::CastContextHint::None, CostKind);
IntrinsicCostAttributes ICA(Intrinsic::ctpop, MaskIntTy, {MaskIntTy});
InstructionCost CtpopCost = TTI.getIntrinsicInstrCost(ICA, CostKind);
InstructionCost ScalarCmpCost = TTI.getCmpSelInstrCost(
@@ -28836,8 +28836,7 @@ class HorizontalReduction {
if (!VecTy)
return nullptr;
- Value *Cmp =
- Builder.CreateICmp(Pred, Vec, Constant::getNullValue(VecTy));
+ Value *Cmp = Builder.CreateICmp(Pred, Vec, Constant::getNullValue(VecTy));
RecurKind ReductionKind =
Pred == ICmpInst::ICMP_EQ ? RecurKind::And : RecurKind::Or;
return createSimpleReduction(Builder, Cmp, ReductionKind);
>From 75281e2a3fa0811ef7f070e39215d21cb1b0e94a Mon Sep 17 00:00:00 2001
From: hanbeom <kese111 at gmail.com>
Date: Sat, 27 Jun 2026 18:37:27 +0900
Subject: [PATCH 3/3] add comments for decribe each adding function
---
.../Transforms/Vectorize/SLPVectorizer.cpp | 53 +++++++++++++++++++
1 file changed, 53 insertions(+)
diff --git a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
index 2b40b189a05ff..8637741cce99e 100644
--- a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
+++ b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
@@ -28757,6 +28757,12 @@ class HorizontalReduction {
(match(I, m_LogicalAnd()) || match(I, m_LogicalOr()));
}
+ /// Match an OR/UMax reduction whose only use is an equality compare against
+ /// zero, for example:
+ /// %r = call iN @llvm.vector.reduce.or/umax(...)
+ /// %c = icmp eq/ne iN %r, 0
+ ///
+ /// this is an all-zero / any-nonzero test of the reduced input lanes.
std::optional<ReductionZeroTestInfo> matchReductionZeroTestUse() const {
auto *Root = dyn_cast<Instruction>(ReductionRoot);
if (!Root || !Root->getType()->isIntegerTy() || !Root->hasOneUse() ||
@@ -28772,6 +28778,12 @@ class HorizontalReduction {
return ReductionZeroTestInfo{Cmp, Pred};
}
+ /// Cost the boolean-vector form of a reduction zero-test:
+ /// %cmp = icmp eq/ne <N x Ty> %v, zeroinitializer
+ /// %r = call i1 @llvm.vector.reduce.and/or.vNi1(%cmp)
+ ///
+ /// `eq` uses reduce.and for an all-zero test, while `ne` uses reduce.or for
+ /// an any-nonzero test.
static InstructionCost
getBoolReductionZeroTestCost(const TargetTransformInfo &TTI,
FixedVectorType *VecTy, CmpPredicate Pred,
@@ -28784,6 +28796,13 @@ class HorizontalReduction {
TTI.getArithmeticReductionCost(ReductionOpcode, CmpTy, {}, CostKind);
}
+ /// Cost the fixed-width mask+ctpop form of a reduction zero-test:
+ /// %cmp = icmp ne <N x Ty> %v, zeroinitializer
+ /// %mask = bitcast <N x i1> %cmp to iN
+ /// %cnt = call iN @llvm.ctpop.iN(iN %mask)
+ /// %r = icmp eq/ne iN %cnt, 0
+ ///
+ /// `eq` tests all-zero lanes; `ne` tests any-nonzero lane.
static InstructionCost getCtpopZeroTestCost(const TargetTransformInfo &TTI,
FixedVectorType *VecTy,
CmpPredicate Pred,
@@ -28807,6 +28826,16 @@ class HorizontalReduction {
return CmpCost + BitcastCost + CtpopCost + ScalarCmpCost;
}
+ /// Choose the cheaper lowering for an OR/UMax reduction zero-test:
+ /// %r = call iN @llvm.vector.reduce.or/umax(...)
+ /// %c = icmp eq/ne iN %r, 0
+ ///
+ /// Return Ctpop when the fixed-width mask+ctpop form is valid and cheaper
+ /// than the boolean reduction form, or when boolean reduction is invalid:
+ /// %cmp = icmp ne <N x Ty> %v, zeroinitializer
+ /// %mask = bitcast <N x i1> %cmp to iN
+ /// %cnt = call iN @llvm.ctpop.iN(iN %mask)
+ /// %c = icmp eq/ne iN %cnt, 0
static std::optional<ZeroTestLoweringInfo>
getBestReductionZeroTestLowering(const TargetTransformInfo &TTI,
FixedVectorType *VecTy, CmpPredicate Pred,
@@ -28830,6 +28859,19 @@ class HorizontalReduction {
return ZeroTestLoweringInfo{ZeroTestLoweringKind::BoolReduction, BoolCost};
}
+ /// Create the boolean-reduction form of a vector zero-test.
+ /// For a vector input:
+ /// %vec = <N x Ty> ...
+ /// return:
+ /// %cmp = icmp eq/ne <N x Ty> %vec, zeroinitializer
+ /// %r = call i1 @llvm.vector.reduce.and/or.vNi1(%cmp)
+ ///
+ /// `eq` creates an all-zero test:
+ /// reduce.and(icmp eq %vec, 0)
+ /// `ne` creates an any-nonzero test:
+ /// reduce.or(icmp ne %vec, 0)
+ ///
+ /// Return nullptr if the input is not a vector.
static Value *createBoolReductionZeroTest(IRBuilderBase &Builder, Value *Vec,
CmpPredicate Pred) {
auto *VecTy = dyn_cast<VectorType>(Vec->getType());
@@ -28842,6 +28884,17 @@ class HorizontalReduction {
return createSimpleReduction(Builder, Cmp, ReductionKind);
}
+ /// Create the mask+ctpop form of a fixed-vector zero-test.
+ /// For a fixed vector input:
+ /// %vec = <N x Ty> ...
+ /// return:
+ /// %nz = icmp ne <N x Ty> %vec, zeroinitializer
+ /// %mask = bitcast <N x i1> %nz to iN
+ /// %pop = call iN @llvm.ctpop.iN(iN %mask)
+ /// %r = icmp eq/ne iN %pop, 0
+ ///
+ /// `eq` tests whether all lanes are zero; `ne` tests whether any lane is
+ /// non-zero. Return nullptr if the input is not a fixed vector.
static Value *createCtpopZeroTest(IRBuilderBase &Builder, Value *Vec,
CmpPredicate Pred) {
auto *VecTy = dyn_cast<FixedVectorType>(Vec->getType());
More information about the llvm-commits
mailing list