Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
11870a2
[LV] Vectorize select min/max index.
fhahn Jun 8, 2025
2688d03
!fixup address review comments, thanks
fhahn Jul 22, 2025
26ebf5a
!fixup remove stray new line
fhahn Aug 1, 2025
af2ba25
Merge remote-tracking branch 'origin/main' into lv-find-min-max-index
fhahn Sep 11, 2025
2871d6c
!fixup fix build after merge
fhahn Sep 11, 2025
ae18690
Merge remote-tracking branch 'origin/main' into lv-find-min-max-index…
fhahn Sep 11, 2025
4305caf
Merge remote-tracking branch 'origin/main' into lv-find-min-max-index…
fhahn Sep 11, 2025
98fb1f7
!fixup detect multi-use min/max recurrences.
fhahn Sep 11, 2025
dc59607
Merge remote-tracking branch 'origin/main' into lv-find-min-max-index
fhahn Oct 2, 2025
6134ef1
Merge remote-tracking branch 'origin/main' into lv-find-min-max-index
fhahn Oct 2, 2025
ad99496
!fixup add additional uses.
fhahn Oct 2, 2025
5b65693
Merge remote-tracking branch 'origin/main' into lv-find-min-max-index
fhahn Oct 6, 2025
fabcf69
Merge remote-tracking branch 'origin/main' into lv-find-min-max-index
fhahn Oct 12, 2025
844c2c2
Merge remote-tracking branch 'origin/main' into lv-find-min-max-index
fhahn Oct 26, 2025
df7e6b8
!fixup replace MultiUse kinds with boolean flag
fhahn Oct 26, 2025
5e209db
Merge branch 'main' into lv-find-min-max-index
fhahn Nov 3, 2025
127da7d
Merge remote-tracking branch 'origin/main' into lv-find-min-max-index
fhahn Nov 11, 2025
603c47c
Merge remote-tracking branch 'origin/main' into lv-find-min-max-index
fhahn Nov 19, 2025
76e661a
!fixup address comments, thanks
fhahn Nov 19, 2025
d74939e
Merge remote-tracking branch 'origin/main' into lv-find-min-max-index
fhahn Nov 20, 2025
a057dd3
!fixup address comments, thanks
fhahn Nov 20, 2025
c351e55
Merge remote-tracking branch 'origin/main' into lv-find-min-max-index
fhahn Nov 21, 2025
fd90ad9
!fixup address comments, thanks
fhahn Nov 21, 2025
2fd21ec
Merge remote-tracking branch 'origin/main' into lv-find-min-max-index
fhahn Nov 21, 2025
da55075
!fixup address latest comments, thanks
fhahn Nov 21, 2025
5a442b2
!fixup add argmin/argmax tests with fmin/fmax.
fhahn Nov 21, 2025
a78311d
Merge remote-tracking branch 'origin/main' into lv-find-min-max-index
fhahn Nov 22, 2025
87325fd
!fixup address latest comments, thanks
fhahn Nov 22, 2025
918f079
Merge remote-tracking branch 'origin/main' into lv-find-min-max-index
fhahn Nov 24, 2025
6cc3953
!fixup address latest comments, thanks
fhahn Nov 24, 2025
3cedf8a
Merge remote-tracking branch 'origin/main' into lv-find-min-max-index
fhahn Nov 25, 2025
b1ff1a4
!fixup address comments, thanks
fhahn Nov 25, 2025
895baa8
Merge remote-tracking branch 'origin/main' into lv-find-min-max-index
fhahn Nov 26, 2025
a073c9b
!fixup
fhahn Nov 27, 2025
6d8e164
Merge remote-tracking branch 'origin/main' into lv-find-min-max-index…
fhahn Nov 27, 2025
0671371
Step
fhahn Nov 27, 2025
450d6a0
[VPlan] Use m_Intrinsic to match assumes/noalias_scope_decl (NFC).
fhahn Nov 27, 2025
cb25d0c
[VPlan] Add matcher
fhahn Nov 27, 2025
7d34974
Fix crash
fhahn Nov 27, 2025
7710b71
Merge remote-tracking branch 'origin/main' into lv-find-min-max-index…
fhahn Nov 27, 2025
7e2d9c3
Merge remote-tracking branch 'origin/main' into lv-find-min-max-index…
fhahn Nov 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion llvm/include/llvm/Analysis/IVDescriptors.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,17 @@ class RecurrenceDescriptor {
RecurKind K, FastMathFlags FMF, Instruction *ExactFP,
Type *RT, bool Signed, bool Ordered,
SmallPtrSetImpl<Instruction *> &CI,
unsigned MinWidthCastToRecurTy)
unsigned MinWidthCastToRecurTy,
bool PhiHasUsesOutsideReductionChain = false)
: IntermediateStore(Store), StartValue(Start), LoopExitInstr(Exit),
Kind(K), FMF(FMF), ExactFPMathInst(ExactFP), RecurrenceType(RT),
IsSigned(Signed), IsOrdered(Ordered),
PhiHasUsesOutsideReductionChain(PhiHasUsesOutsideReductionChain),
MinWidthCastToRecurrenceType(MinWidthCastToRecurTy) {
CastInsts.insert_range(CI);
assert(
(!PhiHasUsesOutsideReductionChain || isMinMaxRecurrenceKind(K)) &&
"Only min/max recurrences are allowed to have multiple uses currently");
}

/// This POD struct holds information about a potential recurrence operation.
Expand Down Expand Up @@ -339,6 +344,13 @@ class RecurrenceDescriptor {
/// Expose an ordered FP reduction to the instance users.
bool isOrdered() const { return IsOrdered; }

/// Returns true if the reduction PHI has any uses outside the reduction
/// chain. This is relevant for min/max reductions that are part of a
/// FindLastIV pattern.
bool hasUsesOutsideReductionChain() const {
return PhiHasUsesOutsideReductionChain;
}

/// Attempts to find a chain of operations from Phi to LoopExitInst that can
/// be treated as a set of reductions instructions for in-loop reductions.
LLVM_ABI SmallVector<Instruction *, 4> getReductionOpChain(PHINode *Phi,
Expand Down Expand Up @@ -376,6 +388,10 @@ class RecurrenceDescriptor {
// Currently only a non-reassociative FAdd can be considered in-order,
// if it is also the only FAdd in the PHI's use chain.
bool IsOrdered = false;
// True if the reduction PHI has in-loop users outside the reduction chain.
// This is relevant for min/max reductions that are part of a FindLastIV
// pattern.
bool PhiHasUsesOutsideReductionChain = false;
// Instructions used for type-promoting the recurrence.
SmallPtrSet<Instruction *, 8> CastInsts;
// The minimum width used by the recurrence.
Expand Down
51 changes: 51 additions & 0 deletions llvm/lib/Analysis/IVDescriptors.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,52 @@ static bool checkOrderedReduction(RecurKind Kind, Instruction *ExactFPMathInst,
return true;
}

/// Returns true if \p Phi is a min/max reduction matching \p Kind where \p Phi
/// is used outside the reduction chain. This is common for loops selecting the
/// index of a minimum/maximum value (argmin/argmax).
static bool isMinMaxReductionPhiWithUsersOutsideReductionChain(
PHINode *Phi, RecurKind Kind, Loop *TheLoop, RecurrenceDescriptor &RedDes) {
BasicBlock *Latch = TheLoop->getLoopLatch();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe need to add assertion to check the Phi has 2 incoming values, one is from latch (already checked), and another is from preheader.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done thanks

if (!Latch)
return false;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return false;
return false;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added, thansk


assert(Phi->getNumIncomingValues() == 2 && "phi must have 2 incoming values");
Value *Inc = Phi->getIncomingValueForBlock(Latch);
if (Phi->hasOneUse() || !Inc->hasOneUse() ||
!RecurrenceDescriptor::isIntMinMaxRecurrenceKind(Kind))
return false;

Value *A, *B;
bool IsMinMax = [&]() {
switch (Kind) {
case RecurKind::UMax:
return match(Inc, m_UMax(m_Value(A), m_Value(B)));
case RecurKind::UMin:
return match(Inc, m_UMin(m_Value(A), m_Value(B)));
case RecurKind::SMax:
return match(Inc, m_SMax(m_Value(A), m_Value(B)));
case RecurKind::SMin:
return match(Inc, m_SMin(m_Value(A), m_Value(B)));
default:
llvm_unreachable("all min/max kinds must be handled");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
llvm_unreachable("all min/max kinds must be handled");
return false; // Only min/max are handled.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This checks for completeness, all integer min/max should be handled, and unreachable here should make it slightly easier to catch missing cases together with the check above

}
}();
if (!IsMinMax)
return false;

if (A == B || (A != Phi && B != Phi))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: can be written as !((A == Phi) ^ (B == Phi)) or ((A == Phi) == (B == Phi)), but probably clearer as written.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left it as-is for now, thanks

return false;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This checks that one of the multiple users of Phi is Inc, what about checking that another user is in the loop (rather than live-out)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other users will be checked in VPlan currently.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, but the name of the function and its documentation specifically claim it has a user in the loop. Better rename isMinMaxReductionPhiWithUsersOutsideReductionChain() instead, and update documentation?

Note that every reduction surely has users outside its chain, but they typically use the final post-updated value rather than the intermediate phi value. So handleMultiUseReductions() might also be better named, given that every reduction is essentially multi-use somewhere along its chain.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, should be updated ,thanks


SmallPtrSet<Instruction *, 4> CastInsts;
Value *RdxStart = Phi->getIncomingValueForBlock(TheLoop->getLoopPreheader());
RedDes =
RecurrenceDescriptor(RdxStart, /*Exit=*/nullptr, /*Store=*/nullptr, Kind,
FastMathFlags(), /*ExactFP=*/nullptr, Phi->getType(),
/*Signed=*/false, /*Ordered=*/false, CastInsts,
/*MinWidthCastToRecurTy=*/-1U, /*PhiMultiUse=*/true);
return true;
}

bool RecurrenceDescriptor::AddReductionVar(
PHINode *Phi, RecurKind Kind, Loop *TheLoop, FastMathFlags FuncFMF,
RecurrenceDescriptor &RedDes, DemandedBits *DB, AssumptionCache *AC,
Expand All @@ -227,6 +273,11 @@ bool RecurrenceDescriptor::AddReductionVar(
if (Phi->getParent() != TheLoop->getHeader())
return false;

// Check for min/max reduction variables that feed other users in the loop.
if (isMinMaxReductionPhiWithUsersOutsideReductionChain(Phi, Kind, TheLoop,
RedDes))
return true;

// Obtain the reduction start value from the value that comes from the loop
// preheader.
Value *RdxStart = Phi->getIncomingValueForBlock(TheLoop->getLoopPreheader());
Expand Down
2 changes: 2 additions & 0 deletions llvm/lib/Transforms/Utils/LoopUnroll.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1254,6 +1254,8 @@ llvm::canParallelizeReductionWhenUnrolling(PHINode &Phi, Loop *L,
/*DemandedBits=*/nullptr,
/*AC=*/nullptr, /*DT=*/nullptr, SE))
return std::nullopt;
if (RdxDesc.hasUsesOutsideReductionChain())
return std::nullopt;
RecurKind RK = RdxDesc.getRecurrenceKind();
// Skip unsupported reductions.
// TODO: Handle additional reductions, including min-max reductions.
Expand Down
5 changes: 5 additions & 0 deletions llvm/lib/Transforms/Vectorize/LoopVectorizationLegality.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -877,6 +877,11 @@ bool LoopVectorizationLegality::canVectorizeInstr(Instruction &I) {
Requirements->addExactFPMathInst(RedDes.getExactFPMathInst());
AllowedExit.insert(RedDes.getLoopExitInstr());
Reductions[Phi] = RedDes;
assert((!RedDes.hasUsesOutsideReductionChain() ||
RecurrenceDescriptor::isMinMaxRecurrenceKind(
RedDes.getRecurrenceKind())) &&
"Only min/max recurrences are allowed to have multiple uses "
"currently");
return true;
}

Expand Down
20 changes: 16 additions & 4 deletions llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6593,6 +6593,11 @@ void LoopVectorizationCostModel::collectInLoopReductions() {
PHINode *Phi = Reduction.first;
const RecurrenceDescriptor &RdxDesc = Reduction.second;

// Multi-use reductions (e.g., used in FindLastIV patterns) are handled
// separately and should not be considered for in-loop reductions.
if (RdxDesc.hasUsesOutsideReductionChain())
continue;

// We don't collect reductions that are type promoted (yet).
if (RdxDesc.getRecurrenceType() != Phi->getType())
continue;
Expand Down Expand Up @@ -7998,9 +8003,10 @@ void VPRecipeBuilder::collectScaledReductions(VFRange &Range) {
MapVector<Instruction *,
SmallVector<std::pair<PartialReductionChain, unsigned>>>
ChainsByPhi;
for (const auto &[Phi, RdxDesc] : Legal->getReductionVars())
getScaledReductions(Phi, RdxDesc.getLoopExitInstr(), Range,
ChainsByPhi[Phi]);
for (const auto &[Phi, RdxDesc] : Legal->getReductionVars()) {
if (Instruction *RdxExitInstr = RdxDesc.getLoopExitInstr())
getScaledReductions(Phi, RdxExitInstr, Range, ChainsByPhi[Phi]);
Comment on lines +8007 to +8008
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: may be good to wrap loop body in brackets.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done thanks

}

// A partial reduction is invalid if any of its extends are used by
// something that isn't another partial reduction. This is because the
Expand Down Expand Up @@ -8221,7 +8227,8 @@ VPRecipeBase *VPRecipeBuilder::tryToCreateWidenRecipe(VPSingleDefRecipe *R,
PhiRecipe = new VPReductionPHIRecipe(
Phi, RdxDesc.getRecurrenceKind(), *StartV,
getReductionStyle(UseInLoopReduction, UseOrderedReductions,
ScaleFactor));
ScaleFactor),
RdxDesc.hasUsesOutsideReductionChain());
} else {
// TODO: Currently fixed-order recurrences are modeled as chains of
// first-order recurrences. If there are no users of the intermediate
Expand Down Expand Up @@ -8555,6 +8562,11 @@ VPlanPtr LoopVectorizationPlanner::tryToBuildVPlanWithVPRecipes(
// Adjust the recipes for any inloop reductions.
adjustRecipesForReductions(Plan, RecipeBuilder, Range.Start);

// Apply mandatory transformation to handle reductions with multiple in-loop
// uses if possible, bail out otherwise.
if (!VPlanTransforms::runPass(VPlanTransforms::handleMultiUseReductions,
*Plan))
return nullptr;
// Apply mandatory transformation to handle FP maxnum/minnum reduction with
// NaNs if possible, bail out otherwise.
if (!VPlanTransforms::runPass(VPlanTransforms::handleMaxMinNumReductions,
Expand Down
24 changes: 20 additions & 4 deletions llvm/lib/Transforms/Vectorize/VPlan.h
Original file line number Diff line number Diff line change
Expand Up @@ -2071,6 +2071,9 @@ class LLVM_ABI_FOR_TEST VPHeaderPHIRecipe : public VPSingleDefRecipe,
static inline bool classof(const VPValue *V) {
return isa<VPHeaderPHIRecipe>(V->getDefiningRecipe());
}
static inline bool classof(const VPSingleDefRecipe *R) {
return isa<VPHeaderPHIRecipe>(static_cast<const VPRecipeBase *>(R));
}

/// Generate the phi nodes.
void execute(VPTransformState &State) override = 0;
Expand Down Expand Up @@ -2136,7 +2139,7 @@ class VPWidenInductionRecipe : public VPHeaderPHIRecipe {
return R && classof(R);
}

static inline bool classof(const VPHeaderPHIRecipe *R) {
static inline bool classof(const VPSingleDefRecipe *R) {
return classof(static_cast<const VPRecipeBase *>(R));
}

Expand Down Expand Up @@ -2432,19 +2435,27 @@ class VPReductionPHIRecipe : public VPHeaderPHIRecipe,

ReductionStyle Style;

/// The phi is part of a multi-use reduction (e.g., used in FindLastIV
/// patterns for argmin/argmax).
/// TODO: Also support cases where the phi itself has a single use, but its
/// compare has multiple uses.
bool HasUsesOutsideReductionChain;

public:
/// Create a new VPReductionPHIRecipe for the reduction \p Phi.
VPReductionPHIRecipe(PHINode *Phi, RecurKind Kind, VPValue &Start,
ReductionStyle Style)
ReductionStyle Style,
bool HasUsesOutsideReductionChain = false)
: VPHeaderPHIRecipe(VPDef::VPReductionPHISC, Phi, &Start), Kind(Kind),
Style(Style) {}
Style(Style),
HasUsesOutsideReductionChain(HasUsesOutsideReductionChain) {}

~VPReductionPHIRecipe() override = default;

VPReductionPHIRecipe *clone() override {
auto *R = new VPReductionPHIRecipe(
dyn_cast_or_null<PHINode>(getUnderlyingValue()), getRecurrenceKind(),
*getOperand(0), Style);
*getOperand(0), Style, HasUsesOutsideReductionChain);
R->addOperand(getBackedgeValue());
return R;
}
Expand Down Expand Up @@ -2481,6 +2492,11 @@ class VPReductionPHIRecipe : public VPHeaderPHIRecipe,
/// Returns true if the reduction outputs a vector with a scaled down VF.
bool isPartialReduction() const { return getVFScaleFactor() > 1; }

/// Returns true, if the phi is part of a multi-use reduction.
bool hasUsesOutsideReductionChain() const {
return HasUsesOutsideReductionChain;
}

/// Returns true if the recipe only uses the first lane of operand \p Op.
bool usesFirstLaneOnly(const VPValue *Op) const override {
assert(is_contained(operands(), Op) &&
Expand Down
Loading
Loading