Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
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
26 changes: 24 additions & 2 deletions llvm/include/llvm/Analysis/IVDescriptors.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,13 @@ enum class RecurKind {
And, ///< Bitwise or logical AND of integers.
Xor, ///< Bitwise or logical XOR of integers.
SMin, ///< Signed integer min implemented in terms of select(cmp()).
SMinMultiUse, ///< Signed integer min implemented in terms of select(cmp()).
SMax, ///< Signed integer max implemented in terms of select(cmp()).
SMaxMultiUse, ///< Signed integer max implemented in terms of select(cmp()).
UMin, ///< Unsigned integer min implemented in terms of select(cmp()).
UMinMultiUse, ///< Unsigned integer min implemented in terms of select(cmp()).
UMax, ///< Unsigned integer max implemented in terms of select(cmp()).
UMaxMultiUse, ///< Unsigned integer max implemented in terms of select(cmp()).
FAdd, ///< Sum of floats.
FMul, ///< Product of floats.
FMin, ///< FP min implemented in terms of select(cmp()).
Expand Down Expand Up @@ -247,8 +251,26 @@ class RecurrenceDescriptor {

/// Returns true if the recurrence kind is an integer min/max kind.
static bool isIntMinMaxRecurrenceKind(RecurKind Kind) {
return Kind == RecurKind::UMin || Kind == RecurKind::UMax ||
Kind == RecurKind::SMin || Kind == RecurKind::SMax;
return Kind == RecurKind::UMin || Kind == RecurKind::UMinMultiUse ||
Kind == RecurKind::UMax || Kind == RecurKind::UMaxMultiUse ||
Kind == RecurKind::SMin || Kind == RecurKind::SMinMultiUse ||
Kind == RecurKind::SMax || Kind == RecurKind::SMaxMultiUse;
}

static RecurKind convertFromMultiUseKind(RecurKind Kind) {
switch (Kind) {
case RecurKind::UMaxMultiUse:
return RecurKind::UMax;
case RecurKind::UMinMultiUse:
return RecurKind::UMin;
case RecurKind::SMinMultiUse:
return RecurKind::SMin;
case RecurKind::SMaxMultiUse:
return RecurKind::SMax;
default:
return Kind;
}
llvm_unreachable("all cases must be handled above");
}

/// Returns true if the recurrence kind is a floating-point min/max kind.
Expand Down
39 changes: 39 additions & 0 deletions llvm/lib/Analysis/IVDescriptors.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,41 @@ static bool checkOrderedReduction(RecurKind Kind, Instruction *ExactFPMathInst,
return true;
}

static std::optional<RecurrenceDescriptor>
getMultiUseMinMax(PHINode *Phi, RecurKind Kind, Loop *TheLoop) {
BasicBlock *Latch = TheLoop->getLoopLatch();
if (!Latch)
return std::nullopt;
Value *Inc = Phi->getIncomingValueForBlock(Latch);
RecurKind RK;
if (Phi->hasOneUse() ||
!RecurrenceDescriptor::isIntMinMaxRecurrenceKind(Kind))
return std::nullopt;

Value *A, *B;
if (match(Inc, m_OneUse(m_UMin(m_Value(A), m_Value(B)))))
RK = RecurKind::UMinMultiUse;
else if (match(Inc, m_OneUse(m_UMax(m_Value(A), m_Value(B)))))
RK = RecurKind::UMaxMultiUse;
else if (match(Inc, m_OneUse(m_SMax(m_Value(A), m_Value(B)))))
RK = RecurKind::SMaxMultiUse;
else if (match(Inc, m_OneUse(m_SMin(m_Value(A), m_Value(B)))))
RK = RecurKind::SMinMultiUse;
else
return std::nullopt;

if (RecurrenceDescriptor::convertFromMultiUseKind(RK) != Kind || A == B ||
(A != Phi && B != Phi))
return std::nullopt;

SmallPtrSet<Instruction *, 4> CastInsts;
Value *RdxStart = Phi->getIncomingValueForBlock(TheLoop->getLoopPreheader());
RecurrenceDescriptor RD(RdxStart, nullptr, nullptr, RK, FastMathFlags(),
nullptr, Phi->getType(), false, false, CastInsts,
-1U);
return {RD};
}

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

if (auto RD = getMultiUseMinMax(Phi, Kind, TheLoop)) {
RedDes = *RD;
return true;
}
// Obtain the reduction start value from the value that comes from the loop
// preheader.
Value *RdxStart = Phi->getIncomingValueForBlock(TheLoop->getLoopPreheader());
Expand Down
29 changes: 24 additions & 5 deletions llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7157,6 +7157,9 @@ static void fixReductionScalarResumeWhenVectorizingEpilog(
Value *StartV = getStartValueFromReductionResult(EpiRedResult);
Value *SentinelV = EpiRedResult->getOperand(2)->getLiveInIRValue();
using namespace llvm::PatternMatch;
MainResumeValue = cast<VPInstruction>(EpiRedHeaderPhi->getStartValue())
->getOperand(0)
->getUnderlyingValue();
Value *Cmp, *OrigResumeV, *CmpOp;
[[maybe_unused]] bool IsExpectedPattern =
match(MainResumeValue,
Expand All @@ -7167,7 +7170,11 @@ static void fixReductionScalarResumeWhenVectorizingEpilog(
((CmpOp == StartV && isGuaranteedNotToBeUndefOrPoison(CmpOp))));
assert(IsExpectedPattern && "Unexpected reduction resume pattern");
MainResumeValue = OrigResumeV;
} else if (auto *VPI =
dyn_cast<VPInstruction>(EpiRedHeaderPhi->getStartValue())) {
MainResumeValue = VPI->getOperand(0)->getUnderlyingValue();
}

PHINode *MainResumePhi = cast<PHINode>(MainResumeValue);

// When fixing reductions in the epilogue loop we should already have
Expand Down Expand Up @@ -7879,6 +7886,9 @@ void VPRecipeBuilder::collectScaledReductions(VFRange &Range) {
SmallVector<std::pair<PartialReductionChain, unsigned>>
PartialReductionChains;
for (const auto &[Phi, RdxDesc] : Legal->getReductionVars()) {
if (RecurrenceDescriptor::isMinMaxRecurrenceKind(
RdxDesc.getRecurrenceKind()))
continue;
getScaledReductions(Phi, RdxDesc.getLoopExitInstr(), Range,
PartialReductionChains);
}
Expand Down Expand Up @@ -8043,9 +8053,6 @@ VPRecipeBase *VPRecipeBuilder::tryToCreateWidenRecipe(VPSingleDefRecipe *R,
return Recipe;

VPHeaderPHIRecipe *PhiRecipe = nullptr;
assert((Legal->isReductionVariable(Phi) ||
Legal->isFixedOrderRecurrence(Phi)) &&
"can only widen reductions and fixed-order recurrences here");
VPValue *StartV = Operands[0];
if (Legal->isReductionVariable(Phi)) {
const RecurrenceDescriptor &RdxDesc = Legal->getRecurrenceDescriptor(Phi);
Expand All @@ -8058,12 +8065,17 @@ VPRecipeBase *VPRecipeBuilder::tryToCreateWidenRecipe(VPSingleDefRecipe *R,
PhiRecipe = new VPReductionPHIRecipe(
Phi, RdxDesc.getRecurrenceKind(), *StartV, CM.isInLoopReduction(Phi),
CM.useOrderedReductions(RdxDesc), ScaleFactor);
} else {
} else if (Legal->isFixedOrderRecurrence(Phi)) {
// TODO: Currently fixed-order recurrences are modeled as chains of
// first-order recurrences. If there are no users of the intermediate
// recurrences in the chain, the fixed order recurrence should be modeled
// directly, enabling more efficient codegen.
PhiRecipe = new VPFirstOrderRecurrencePHIRecipe(Phi, *StartV);
} else {
// Failed to identify phi as reduction or fixed-order recurrence. Keep the
// original VPWidenPHIRecipe for now, to be legalized later if possible.
setRecipe(Phi, R);
return nullptr;
}
// Add backedge value.
PhiRecipe->addOperand(Operands[1]);
Expand Down Expand Up @@ -8344,8 +8356,11 @@ VPlanPtr LoopVectorizationPlanner::tryToBuildVPlanWithVPRecipes(

VPRecipeBase *Recipe =
RecipeBuilder.tryToCreateWidenRecipe(SingleDef, Range);
if (!Recipe)
if (!Recipe) {
if (isa<VPPhi>(SingleDef))
continue;
Recipe = RecipeBuilder.handleReplication(Instr, R.operands(), Range);
}

RecipeBuilder.setRecipe(Instr, Recipe);
if (isa<VPWidenIntOrFpInductionRecipe>(Recipe) && isa<TruncInst>(Instr)) {
Expand Down Expand Up @@ -8407,6 +8422,10 @@ VPlanPtr LoopVectorizationPlanner::tryToBuildVPlanWithVPRecipes(
// Adjust the recipes for any inloop reductions.
adjustRecipesForReductions(Plan, RecipeBuilder, Range.Start);

// Try to convert remaining VPWidenPHIRecipes to reduction recipes.
if (!VPlanTransforms::runPass(VPlanTransforms::legalizeUnclassifiedPhis,
*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
12 changes: 12 additions & 0 deletions llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24960,6 +24960,10 @@ class HorizontalReduction {
case RecurKind::FMinNum:
case RecurKind::FMaximumNum:
case RecurKind::FMinimumNum:
case RecurKind::UMaxMultiUse:
case RecurKind::UMinMultiUse:
case RecurKind::SMinMultiUse:
case RecurKind::SMaxMultiUse:
case RecurKind::None:
llvm_unreachable("Unexpected reduction kind for repeated scalar.");
}
Expand Down Expand Up @@ -25101,6 +25105,10 @@ class HorizontalReduction {
case RecurKind::FMinNum:
case RecurKind::FMaximumNum:
case RecurKind::FMinimumNum:
case RecurKind::UMaxMultiUse:
case RecurKind::UMinMultiUse:
case RecurKind::SMinMultiUse:
case RecurKind::SMaxMultiUse:
case RecurKind::None:
llvm_unreachable("Unexpected reduction kind for repeated scalar.");
}
Expand Down Expand Up @@ -25207,6 +25215,10 @@ class HorizontalReduction {
case RecurKind::FMinNum:
case RecurKind::FMaximumNum:
case RecurKind::FMinimumNum:
case RecurKind::UMaxMultiUse:
case RecurKind::UMinMultiUse:
case RecurKind::SMinMultiUse:
case RecurKind::SMaxMultiUse:
case RecurKind::None:
llvm_unreachable("Unexpected reduction kind for reused scalars.");
}
Expand Down
9 changes: 7 additions & 2 deletions llvm/lib/Transforms/Vectorize/VPlan.h
Original file line number Diff line number Diff line change
Expand Up @@ -1988,7 +1988,8 @@ class LLVM_ABI_FOR_TEST VPHeaderPHIRecipe : public VPSingleDefRecipe,
~VPHeaderPHIRecipe() override = default;

/// Method to support type inquiry through isa, cast, and dyn_cast.
static inline bool classof(const VPRecipeBase *B) {
static inline bool classof(const VPUser *U) {
auto *B = cast<VPRecipeBase>(U);
return B->getVPDefID() >= VPDef::VPFirstHeaderPHISC &&
B->getVPDefID() <= VPDef::VPLastHeaderPHISC;
}
Expand All @@ -1997,6 +1998,10 @@ class LLVM_ABI_FOR_TEST VPHeaderPHIRecipe : public VPSingleDefRecipe,
return B && B->getVPDefID() >= VPRecipeBase::VPFirstHeaderPHISC &&
B->getVPDefID() <= VPRecipeBase::VPLastHeaderPHISC;
}
static inline bool classof(const VPSingleDefRecipe *B) {
return B->getVPDefID() >= VPDef::VPFirstHeaderPHISC &&
B->getVPDefID() <= VPDef::VPLastHeaderPHISC;
}

/// Generate the phi nodes.
void execute(VPTransformState &State) override = 0;
Expand Down Expand Up @@ -2061,7 +2066,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
102 changes: 102 additions & 0 deletions llvm/lib/Transforms/Vectorize/VPlanConstruction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -952,3 +952,105 @@ bool VPlanTransforms::handleMaxMinNumReductions(VPlan &Plan) {
MiddleTerm->setOperand(0, NewCond);
return true;
}

bool VPlanTransforms::legalizeUnclassifiedPhis(VPlan &Plan) {
using namespace VPlanPatternMatch;
for (auto &PhiR : make_early_inc_range(
Plan.getVectorLoopRegion()->getEntryBasicBlock()->phis())) {
auto *MinMaxPhiR = dyn_cast<VPReductionPHIRecipe>(&PhiR);
if (!MinMaxPhiR || !RecurrenceDescriptor::isIntMinMaxRecurrenceKind(
MinMaxPhiR->getRecurrenceKind()))
continue;

RecurKind RdxKind = RecurrenceDescriptor::convertFromMultiUseKind(
MinMaxPhiR->getRecurrenceKind());
if (RdxKind == MinMaxPhiR->getRecurrenceKind())
continue;

// One user of MinMaxPhiR is MinMaxOp, the other users must be a compare
// that's part of a FindLastIV chain.
auto *MinMaxOp = cast<VPRecipeWithIRFlags>(MinMaxPhiR->getBackedgeValue());
auto MinMaxUsers = to_vector(MinMaxPhiR->users());
auto *Cmp = dyn_cast<VPRecipeWithIRFlags>(
MinMaxUsers[0] == MinMaxOp ? MinMaxUsers[1] : MinMaxUsers[0]);
Copy link
Contributor

Choose a reason for hiding this comment

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

Should check the number of phi's users, like #141467 did:

  // TODO: support min/max with 2-D indexes.
  if (!Phi->hasNUses(2))
    return false;

VPValue *CmpOpA;
VPValue *CmpOpB;
Copy link
Contributor

Choose a reason for hiding this comment

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

compress into one line, and choose the better name.

if (!Cmp || Cmp->getNumUsers() != 1 ||
!match(Cmp, m_Binary<Instruction::ICmp>(m_VPValue(CmpOpA),
m_VPValue(CmpOpB))))
return false;

// Normalize the predicate so MinMaxPhiR is on the right side.
CmpInst::Predicate Pred = Cmp->getPredicate();
if (CmpOpA == MinMaxPhiR)
Pred = CmpInst::getSwappedPredicate(Pred);

// Determine if the predicate is not strict.
bool IsNonStrictPred = ICmpInst::isLE(Pred) || ICmpInst::isGE(Pred);
// Account for a mis-match between RdxKind and the predicate.
switch (RdxKind) {
case RecurKind::UMin:
case RecurKind::SMin:
IsNonStrictPred |= ICmpInst::isGT(Pred);
break;
case RecurKind::UMax:
case RecurKind::SMax:
IsNonStrictPred |= ICmpInst::isLT(Pred);
break;
default:
llvm_unreachable("unsupported kind");
}

// TODO: Strict predicates need to find the first IV value for which the
// predicate holds, not the last.
if (Pred == CmpInst::ICMP_NE || !IsNonStrictPred)
return false;

// Cmp must be used by the select of a FindLastIV chain.
VPValue *Sel = dyn_cast<VPSingleDefRecipe>(*Cmp->user_begin());
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
VPValue *Sel = dyn_cast<VPSingleDefRecipe>(*Cmp->user_begin());
auto *Sel = dyn_cast<VPSingleDefRecipe>(*Cmp->user_begin());

VPValue *IVOp, *FindIV;
if (!Sel ||
!match(Sel,
m_Select(m_Specific(Cmp), m_VPValue(IVOp), m_VPValue(FindIV))) ||
Sel->getNumUsers() != 2 || !isa<VPWidenIntOrFpInductionRecipe>(IVOp))
return false;
auto *FindIVPhiR = dyn_cast<VPReductionPHIRecipe>(FindIV);
if (!FindIVPhiR || !RecurrenceDescriptor::isFindLastIVRecurrenceKind(
FindIVPhiR->getRecurrenceKind()))
return false;

assert(!FindIVPhiR->isInLoop() && !FindIVPhiR->isOrdered() &&
"cannot handle inloop/ordered reductions yet");

auto NewPhiR = new VPReductionPHIRecipe(
cast<PHINode>(MinMaxPhiR->getUnderlyingInstr()), RdxKind,
*MinMaxPhiR->getOperand(0), false, false, 1);
NewPhiR->insertBefore(MinMaxPhiR);
MinMaxPhiR->replaceAllUsesWith(NewPhiR);
NewPhiR->addOperand(MinMaxPhiR->getOperand(1));
MinMaxPhiR->eraseFromParent();

// The reduction using MinMaxPhiR needs adjusting to compute the correct
// result:
// 1. We need to find the last IV for which the condition based on the
// min/max recurrence is true,
// 2. Compare the partial min/max reduction result to its final value and,
// 3. Select the lanes of the partial FindLastIV reductions which
// correspond to the lanes matching the min/max reduction result.
VPInstruction *FindIVResult = cast<VPInstruction>(
*(Sel->user_begin() + (*Sel->user_begin() == FindIVPhiR ? 1 : 0)));
VPBuilder B(FindIVResult);
VPInstruction *MinMaxResult =
B.createNaryOp(VPInstruction::ComputeReductionResult,
{NewPhiR, NewPhiR->getBackedgeValue()}, VPIRFlags(), {});
NewPhiR->getBackedgeValue()->replaceUsesWithIf(
MinMaxResult, [](VPUser &U, unsigned) { return isa<VPPhi>(&U); });
auto *FinalMinMaxCmp = B.createICmp(
CmpInst::ICMP_EQ, MinMaxResult->getOperand(1), MinMaxResult);
auto *FinalIVSelect =
B.createSelect(FinalMinMaxCmp, FindIVResult->getOperand(3),
FindIVResult->getOperand(2));
FindIVResult->setOperand(3, FinalIVSelect);
}
return true;
}
5 changes: 5 additions & 0 deletions llvm/lib/Transforms/Vectorize/VPlanTransforms.h
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,11 @@ struct VPlanTransforms {
GetIntOrFpInductionDescriptor,
const TargetLibraryInfo &TLI);

/// Try to legalize unclassified phis by converting VPWidenPHIRecipes to
/// min-max reductions used by FindLastIV reductions if possible. Returns
/// false if the VPlan contains VPWidenPHIRecipes that cannot be legalized.
static bool legalizeUnclassifiedPhis(VPlan &Plan);

/// Try to have all users of fixed-order recurrences appear after the recipe
/// defining their previous value, by either sinking users or hoisting recipes
/// defining their previous value (and its operands). Then introduce
Expand Down
Loading