| //===-- VPlanTransforms.cpp - Utility VPlan to VPlan transforms -----------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| /// | |
| /// \file | |
| /// This file implements a set of utility VPlan to VPlan transformations. | |
| /// | |
| //===----------------------------------------------------------------------===// | |
| #include "VPlanTransforms.h" | |
| #include "VPRecipeBuilder.h" | |
| #include "VPlan.h" | |
| #include "VPlanAnalysis.h" | |
| #include "VPlanCFG.h" | |
| #include "VPlanDominatorTree.h" | |
| #include "VPlanHelpers.h" | |
| #include "VPlanPatternMatch.h" | |
| #include "VPlanUtils.h" | |
| #include "VPlanVerifier.h" | |
| #include "llvm/ADT/APInt.h" | |
| #include "llvm/ADT/PostOrderIterator.h" | |
| #include "llvm/ADT/STLExtras.h" | |
| #include "llvm/ADT/SetVector.h" | |
| #include "llvm/ADT/SmallPtrSet.h" | |
| #include "llvm/ADT/TypeSwitch.h" | |
| #include "llvm/Analysis/IVDescriptors.h" | |
| #include "llvm/Analysis/InstSimplifyFolder.h" | |
| #include "llvm/Analysis/Loads.h" | |
| #include "llvm/Analysis/LoopAccessAnalysis.h" | |
| #include "llvm/Analysis/LoopInfo.h" | |
| #include "llvm/Analysis/MemoryLocation.h" | |
| #include "llvm/Analysis/ScalarEvolutionPatternMatch.h" | |
| #include "llvm/Analysis/ScopedNoAliasAA.h" | |
| #include "llvm/Analysis/VectorUtils.h" | |
| #include "llvm/IR/Intrinsics.h" | |
| #include "llvm/IR/MDBuilder.h" | |
| #include "llvm/IR/Metadata.h" | |
| #include "llvm/Support/Casting.h" | |
| #include "llvm/Support/TypeSize.h" | |
| #include "llvm/Transforms/Utils/LoopUtils.h" | |
| #include "llvm/Transforms/Utils/ScalarEvolutionExpander.h" | |
| using namespace llvm; | |
| using namespace VPlanPatternMatch; | |
| using namespace SCEVPatternMatch; | |
| bool VPlanTransforms::tryToConvertVPInstructionsToVPRecipes( | |
| VPlan &Plan, const TargetLibraryInfo &TLI) { | |
| ReversePostOrderTraversal<VPBlockDeepTraversalWrapper<VPBlockBase *>> RPOT( | |
| Plan.getVectorLoopRegion()); | |
| for (VPBasicBlock *VPBB : VPBlockUtils::blocksOnly<VPBasicBlock>(RPOT)) { | |
| // Skip blocks outside region | |
| if (!VPBB->getParent()) | |
| break; | |
| VPRecipeBase *Term = VPBB->getTerminator(); | |
| auto EndIter = Term ? Term->getIterator() : VPBB->end(); | |
| // Introduce each ingredient into VPlan. | |
| for (VPRecipeBase &Ingredient : | |
| make_early_inc_range(make_range(VPBB->begin(), EndIter))) { | |
| VPValue *VPV = Ingredient.getVPSingleValue(); | |
| if (!VPV->getUnderlyingValue()) | |
| continue; | |
| Instruction *Inst = cast<Instruction>(VPV->getUnderlyingValue()); | |
| // Atomic accesses and fences have ordering/atomicity semantics that | |
| // cannot be preserved by lane-wise widening. | |
| if (isa<AtomicRMWInst, AtomicCmpXchgInst, FenceInst>(Inst)) | |
| return false; | |
| VPRecipeBase *NewRecipe = nullptr; | |
| if (auto *PhiR = dyn_cast<VPPhi>(&Ingredient)) { | |
| auto *Phi = cast<PHINode>(PhiR->getUnderlyingValue()); | |
| NewRecipe = new VPWidenPHIRecipe(PhiR->operands(), PhiR->getDebugLoc(), | |
| Phi->getName()); | |
| } else if (auto *VPI = dyn_cast<VPInstruction>(&Ingredient)) { | |
| assert(!isa<PHINode>(Inst) && "phis should be handled above"); | |
| // Create VPWidenMemoryRecipe for loads and stores. | |
| if (LoadInst *Load = dyn_cast<LoadInst>(Inst)) { | |
| NewRecipe = new VPWidenLoadRecipe( | |
| *Load, Ingredient.getOperand(0), nullptr /*Mask*/, | |
| false /*Consecutive*/, *VPI, Ingredient.getDebugLoc()); | |
| } else if (StoreInst *Store = dyn_cast<StoreInst>(Inst)) { | |
| NewRecipe = new VPWidenStoreRecipe( | |
| *Store, Ingredient.getOperand(1), Ingredient.getOperand(0), | |
| nullptr /*Mask*/, false /*Consecutive*/, *VPI, | |
| Ingredient.getDebugLoc()); | |
| } else if (GetElementPtrInst *GEP = dyn_cast<GetElementPtrInst>(Inst)) { | |
| NewRecipe = new VPWidenGEPRecipe(GEP->getSourceElementType(), | |
| Ingredient.operands(), *VPI, | |
| Ingredient.getDebugLoc(), GEP); | |
| } else if (CallInst *CI = dyn_cast<CallInst>(Inst)) { | |
| Intrinsic::ID VectorID = getVectorIntrinsicIDForCall(CI, &TLI); | |
| if (VectorID == Intrinsic::not_intrinsic) | |
| return false; | |
| // The noalias.scope.decl intrinsic declares a noalias scope that | |
| // is valid for a single iteration. Emitting it as a single-scalar | |
| // replicate would incorrectly extend the scope across multiple | |
| // original iterations packed into one vector iteration. | |
| // FIXME: If we want to vectorize this loop, then we have to drop | |
| // all the associated !alias.scope and !noalias. | |
| if (VectorID == Intrinsic::experimental_noalias_scope_decl) | |
| return false; | |
| // These intrinsics are recognized by getVectorIntrinsicIDForCall | |
| // but are not widenable. Emit them as replicate instead of widening. | |
| if (VectorID == Intrinsic::assume || | |
| VectorID == Intrinsic::lifetime_end || | |
| VectorID == Intrinsic::lifetime_start || | |
| VectorID == Intrinsic::sideeffect || | |
| VectorID == Intrinsic::pseudoprobe) { | |
| // If the operand of llvm.assume holds before vectorization, it will | |
| // also hold per lane. | |
| // llvm.pseudoprobe requires to be duplicated per lane for accurate | |
| // sample count. | |
| const bool IsSingleScalar = VectorID != Intrinsic::assume && | |
| VectorID != Intrinsic::pseudoprobe; | |
| NewRecipe = new VPReplicateRecipe(CI, Ingredient.operands(), | |
| /*IsSingleScalar=*/IsSingleScalar, | |
| /*Mask=*/nullptr, *VPI, *VPI, | |
| Ingredient.getDebugLoc()); | |
| } else { | |
| NewRecipe = new VPWidenIntrinsicRecipe( | |
| *CI, VectorID, drop_end(Ingredient.operands()), CI->getType(), | |
| VPIRFlags(*CI), *VPI, CI->getDebugLoc()); | |
| } | |
| } else if (auto *CI = dyn_cast<CastInst>(Inst)) { | |
| NewRecipe = new VPWidenCastRecipe( | |
| CI->getOpcode(), Ingredient.getOperand(0), CI->getType(), CI, | |
| VPIRFlags(*CI), VPIRMetadata(*CI)); | |
| } else { | |
| NewRecipe = new VPWidenRecipe(*Inst, Ingredient.operands(), *VPI, | |
| *VPI, Ingredient.getDebugLoc()); | |
| } | |
| } else { | |
| assert(isa<VPWidenIntOrFpInductionRecipe>(&Ingredient) && | |
| "inductions must be created earlier"); | |
| continue; | |
| } | |
| NewRecipe->insertBefore(&Ingredient); | |
| if (NewRecipe->getNumDefinedValues() == 1) | |
| VPV->replaceAllUsesWith(NewRecipe->getVPSingleValue()); | |
| else | |
| assert(NewRecipe->getNumDefinedValues() == 0 && | |
| "Only recpies with zero or one defined values expected"); | |
| Ingredient.eraseFromParent(); | |
| } | |
| } | |
| return true; | |
| } | |
| /// Helper for extra no-alias checks via known-safe recipe and SCEV. | |
| class SinkStoreInfo { | |
| SmallPtrSet<VPReplicateRecipe *, 4> ExcludeRecipes; | |
| VPReplicateRecipe &GroupLeader; | |
| PredicatedScalarEvolution *PSE = nullptr; | |
| const Loop *L = nullptr; | |
| // Return true if \p A and \p B are known to not alias for all VFs in the | |
| // plan, checked via the distance between the accesses | |
| bool isNoAliasViaDistance(VPReplicateRecipe *A, VPReplicateRecipe *B) const { | |
| if (A->getOpcode() != Instruction::Store || | |
| B->getOpcode() != Instruction::Store) | |
| return false; | |
| if (!PSE || !L) | |
| return A == B; | |
| VPValue *AddrA = A->getOperand(1); | |
| const SCEV *SCEVA = vputils::getSCEVExprForVPValue(AddrA, *PSE, L); | |
| VPValue *AddrB = B->getOperand(1); | |
| const SCEV *SCEVB = vputils::getSCEVExprForVPValue(AddrB, *PSE, L); | |
| if (isa<SCEVCouldNotCompute>(SCEVA) || isa<SCEVCouldNotCompute>(SCEVB)) | |
| return false; | |
| const APInt *Distance; | |
| ScalarEvolution &SE = *PSE->getSE(); | |
| if (!match(SE.getMinusSCEV(SCEVA, SCEVB), m_scev_APInt(Distance))) | |
| return false; | |
| const DataLayout &DL = SE.getDataLayout(); | |
| Type *TyA = A->getOperand(0)->getScalarType(); | |
| uint64_t SizeA = DL.getTypeStoreSize(TyA); | |
| Type *TyB = B->getOperand(0)->getScalarType(); | |
| uint64_t SizeB = DL.getTypeStoreSize(TyB); | |
| // Use the maximum store size to ensure no overlap from either direction. | |
| // Currently only handles fixed sizes, as it is only used for | |
| // replicating VPReplicateRecipes. | |
| uint64_t MaxStoreSize = std::max(SizeA, SizeB); | |
| auto VFs = B->getParent()->getPlan()->vectorFactors(); | |
| ElementCount MaxVF = *max_element(VFs, ElementCount::isKnownLT); | |
| if (MaxVF.isScalable()) | |
| return false; | |
| return Distance->abs().uge( | |
| MaxVF.multiplyCoefficientBy(MaxStoreSize).getFixedValue()); | |
| } | |
| public: | |
| SinkStoreInfo(ArrayRef<VPReplicateRecipe *> ExcludeRecipes, | |
| VPReplicateRecipe &GroupLeader, PredicatedScalarEvolution &PSE, | |
| const Loop &L) | |
| : ExcludeRecipes(ExcludeRecipes.begin(), ExcludeRecipes.end()), | |
| GroupLeader(GroupLeader), PSE(&PSE), L(&L) {} | |
| SinkStoreInfo(VPReplicateRecipe &GroupLeader) : GroupLeader(GroupLeader) {} | |
| /// Return true if \p R should be skipped during alias checking, either | |
| /// because it's in the exclude set or because no-alias can be proven via | |
| /// SCEV. | |
| bool shouldSkip(VPRecipeBase &R) const { | |
| auto *Store = dyn_cast<VPReplicateRecipe>(&R); | |
| return ExcludeRecipes.contains(Store) || | |
| (Store && isNoAliasViaDistance(Store, &GroupLeader)); | |
| } | |
| }; | |
| /// Check if a memory operation doesn't alias with memory operations using | |
| /// scoped noalias metadata, in blocks in the single-successor chain between \p | |
| /// FirstBB and \p LastBB. If \p SinkInfo is std::nullopt, only recipes that may | |
| /// write to memory are checked (for load hoisting). Otherwise recipes that both | |
| /// read and write memory are checked, and SCEV is used to prove no-alias | |
| /// between the group leader and other replicate recipes (for store sinking). | |
| static bool | |
| canHoistOrSinkWithNoAliasCheck(const MemoryLocation &MemLoc, | |
| VPBasicBlock *FirstBB, VPBasicBlock *LastBB, | |
| std::optional<SinkStoreInfo> SinkInfo = {}) { | |
| bool CheckReads = SinkInfo.has_value(); | |
| for (VPBasicBlock *VPBB : | |
| VPBlockUtils::blocksInSingleSuccessorChainBetween(FirstBB, LastBB)) { | |
| for (VPRecipeBase &R : *VPBB) { | |
| if (SinkInfo && SinkInfo->shouldSkip(R)) | |
| continue; | |
| // Skip recipes that don't need checking. | |
| if (!R.mayWriteToMemory() && !(CheckReads && R.mayReadFromMemory())) | |
| continue; | |
| auto Loc = vputils::getMemoryLocation(R); | |
| if (!Loc) | |
| // Conservatively assume aliasing for memory operations without | |
| // location. | |
| return false; | |
| if (ScopedNoAliasAAResult::alias(*Loc, MemLoc) != AliasResult::NoAlias) | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| /// Get the value type of the replicate load or store. \p IsLoad indicates | |
| /// whether it is a load. | |
| static Type *getLoadStoreValueType(VPReplicateRecipe *R, bool IsLoad) { | |
| return (IsLoad ? R : R->getOperand(0))->getScalarType(); | |
| } | |
| /// Collect either replicated Loads or Stores grouped by their address SCEV and | |
| /// their load-store type, in a deep-traversal of the vector loop region in \p | |
| /// Plan. | |
| template <unsigned Opcode> | |
| static SmallVector<SmallVector<VPReplicateRecipe *, 4>> | |
| collectGroupedReplicateMemOps( | |
| VPlan &Plan, PredicatedScalarEvolution &PSE, const Loop *L, | |
| function_ref<bool(VPReplicateRecipe *)> FilterFn) { | |
| static_assert(Opcode == Instruction::Load || Opcode == Instruction::Store, | |
| "Only Load and Store opcodes supported"); | |
| constexpr bool IsLoad = (Opcode == Instruction::Load); | |
| SmallDenseMap<std::pair<const SCEV *, const Type *>, | |
| SmallVector<VPReplicateRecipe *, 4>> | |
| RecipesByAddressAndType; | |
| for (VPBasicBlock *VPBB : VPBlockUtils::blocksOnly<VPBasicBlock>( | |
| vp_depth_first_deep(Plan.getVectorLoopRegion()->getEntry()))) { | |
| for (VPRecipeBase &R : *VPBB) { | |
| auto *RepR = dyn_cast<VPReplicateRecipe>(&R); | |
| if (!RepR || RepR->getOpcode() != Opcode || !FilterFn(RepR)) | |
| continue; | |
| // For loads, operand 0 is address; for stores, operand 1 is address. | |
| VPValue *Addr = RepR->getOperand(IsLoad ? 0 : 1); | |
| const Type *LoadStoreTy = getLoadStoreValueType(RepR, IsLoad); | |
| const SCEV *AddrSCEV = vputils::getSCEVExprForVPValue(Addr, PSE, L); | |
| if (!isa<SCEVCouldNotCompute>(AddrSCEV)) | |
| RecipesByAddressAndType[{AddrSCEV, LoadStoreTy}].push_back(RepR); | |
| } | |
| } | |
| auto Groups = to_vector(RecipesByAddressAndType.values()); | |
| VPDominatorTree VPDT(Plan); | |
| for (auto &Group : Groups) { | |
| // Sort mem ops by dominance order, with earliest (most dominating) first. | |
| stable_sort(Group, [&VPDT](VPReplicateRecipe *A, VPReplicateRecipe *B) { | |
| return VPDT.properlyDominates(A, B); | |
| }); | |
| } | |
| return Groups; | |
| } | |
| static bool sinkScalarOperands(VPlan &Plan) { | |
| auto Iter = vp_depth_first_deep(Plan.getEntry()); | |
| bool ScalarVFOnly = Plan.hasScalarVFOnly(); | |
| bool Changed = false; | |
| SetVector<std::pair<VPBasicBlock *, VPSingleDefRecipe *>> WorkList; | |
| auto InsertIfValidSinkCandidate = [ScalarVFOnly, &WorkList]( | |
| VPBasicBlock *SinkTo, VPValue *Op) { | |
| auto *Candidate = | |
| dyn_cast_or_null<VPSingleDefRecipe>(Op->getDefiningRecipe()); | |
| if (!Candidate) | |
| return; | |
| // We only know how to sink VPReplicateRecipes and VPScalarIVStepsRecipes | |
| // for now. | |
| if (!isa<VPReplicateRecipe, VPScalarIVStepsRecipe>(Candidate)) | |
| return; | |
| if (Candidate->getParent() == SinkTo || | |
| vputils::cannotHoistOrSinkRecipe(*Candidate, /*Sinking=*/true)) | |
| return; | |
| if (auto *RepR = dyn_cast<VPReplicateRecipe>(Candidate)) | |
| if (!ScalarVFOnly && RepR->isSingleScalar()) | |
| return; | |
| WorkList.insert({SinkTo, Candidate}); | |
| }; | |
| // First, collect the operands of all recipes in replicate blocks as seeds for | |
| // sinking. | |
| for (VPRegionBlock *VPR : VPBlockUtils::blocksOnly<VPRegionBlock>(Iter)) { | |
| VPBasicBlock *EntryVPBB = VPR->getEntryBasicBlock(); | |
| if (!VPR->isReplicator() || EntryVPBB->getSuccessors().size() != 2) | |
| continue; | |
| VPBasicBlock *VPBB = cast<VPBasicBlock>(EntryVPBB->getSuccessors().front()); | |
| if (VPBB->getSingleSuccessor() != VPR->getExitingBasicBlock()) | |
| continue; | |
| for (auto &Recipe : *VPBB) | |
| for (VPValue *Op : Recipe.operands()) | |
| InsertIfValidSinkCandidate(VPBB, Op); | |
| } | |
| // Try to sink each replicate or scalar IV steps recipe in the worklist. | |
| for (unsigned I = 0; I != WorkList.size(); ++I) { | |
| VPBasicBlock *SinkTo; | |
| VPSingleDefRecipe *SinkCandidate; | |
| std::tie(SinkTo, SinkCandidate) = WorkList[I]; | |
| // All recipe users of SinkCandidate must be in the same block SinkTo or all | |
| // users outside of SinkTo must only use the first lane of SinkCandidate. In | |
| // the latter case, we need to duplicate SinkCandidate. | |
| auto UsersOutsideSinkTo = | |
| make_filter_range(SinkCandidate->users(), [SinkTo](VPUser *U) { | |
| return cast<VPRecipeBase>(U)->getParent() != SinkTo; | |
| }); | |
| if (any_of(UsersOutsideSinkTo, [SinkCandidate](VPUser *U) { | |
| return !U->usesFirstLaneOnly(SinkCandidate); | |
| })) | |
| continue; | |
| bool NeedsDuplicating = !UsersOutsideSinkTo.empty(); | |
| if (NeedsDuplicating) { | |
| if (ScalarVFOnly) | |
| continue; | |
| VPSingleDefRecipe *Clone; | |
| if (auto *SinkCandidateRepR = | |
| dyn_cast<VPReplicateRecipe>(SinkCandidate)) { | |
| // TODO: Handle converting to uniform recipes as separate transform, | |
| // then cloning should be sufficient here. | |
| Clone = VPBuilder::createSingleScalarOp( | |
| SinkCandidateRepR->getOpcode(), SinkCandidate->operands(), | |
| /*Mask=*/nullptr, *SinkCandidateRepR, *SinkCandidateRepR, | |
| SinkCandidate->getDebugLoc(), SinkCandidate->getUnderlyingInstr()); | |
| // TODO: add ".cloned" suffix to name of Clone's VPValue. | |
| } else { | |
| Clone = SinkCandidate->clone(); | |
| } | |
| Clone->insertBefore(SinkCandidate); | |
| SinkCandidate->replaceUsesWithIf(Clone, [SinkTo](VPUser &U, unsigned) { | |
| return cast<VPRecipeBase>(&U)->getParent() != SinkTo; | |
| }); | |
| } | |
| SinkCandidate->moveBefore(*SinkTo, SinkTo->getFirstNonPhi()); | |
| for (VPValue *Op : SinkCandidate->operands()) | |
| InsertIfValidSinkCandidate(SinkTo, Op); | |
| Changed = true; | |
| } | |
| return Changed; | |
| } | |
| /// If \p R is a region with a VPBranchOnMaskRecipe in the entry block, return | |
| /// the mask. | |
| static VPValue *getPredicatedMask(VPRegionBlock *R) { | |
| auto *EntryBB = dyn_cast<VPBasicBlock>(R->getEntry()); | |
| if (!EntryBB || EntryBB->size() != 1 || | |
| !isa<VPBranchOnMaskRecipe>(EntryBB->begin())) | |
| return nullptr; | |
| return cast<VPBranchOnMaskRecipe>(&*EntryBB->begin())->getOperand(0); | |
| } | |
| /// If \p R is a triangle region, return the 'then' block of the triangle. | |
| static VPBasicBlock *getPredicatedThenBlock(VPRegionBlock *R) { | |
| auto *EntryBB = cast<VPBasicBlock>(R->getEntry()); | |
| if (EntryBB->getNumSuccessors() != 2) | |
| return nullptr; | |
| auto *Succ0 = dyn_cast<VPBasicBlock>(EntryBB->getSuccessors()[0]); | |
| auto *Succ1 = dyn_cast<VPBasicBlock>(EntryBB->getSuccessors()[1]); | |
| if (!Succ0 || !Succ1) | |
| return nullptr; | |
| if (Succ0->getNumSuccessors() + Succ1->getNumSuccessors() != 1) | |
| return nullptr; | |
| if (Succ0->getSingleSuccessor() == Succ1) | |
| return Succ0; | |
| if (Succ1->getSingleSuccessor() == Succ0) | |
| return Succ1; | |
| return nullptr; | |
| } | |
| // Merge replicate regions in their successor region, if a replicate region | |
| // is connected to a successor replicate region with the same predicate by a | |
| // single, empty VPBasicBlock. | |
| static bool mergeReplicateRegionsIntoSuccessors(VPlan &Plan) { | |
| SmallPtrSet<VPRegionBlock *, 4> TransformedRegions; | |
| // Collect replicate regions followed by an empty block, followed by another | |
| // replicate region with matching masks to process front. This is to avoid | |
| // iterator invalidation issues while merging regions. | |
| SmallVector<VPRegionBlock *, 8> WorkList; | |
| for (VPRegionBlock *Region1 : VPBlockUtils::blocksOnly<VPRegionBlock>( | |
| vp_depth_first_deep(Plan.getEntry()))) { | |
| if (!Region1->isReplicator()) | |
| continue; | |
| auto *MiddleBasicBlock = | |
| dyn_cast_or_null<VPBasicBlock>(Region1->getSingleSuccessor()); | |
| if (!MiddleBasicBlock || !MiddleBasicBlock->empty()) | |
| continue; | |
| auto *Region2 = | |
| dyn_cast_or_null<VPRegionBlock>(MiddleBasicBlock->getSingleSuccessor()); | |
| if (!Region2 || !Region2->isReplicator()) | |
| continue; | |
| VPValue *Mask1 = getPredicatedMask(Region1); | |
| VPValue *Mask2 = getPredicatedMask(Region2); | |
| if (!Mask1 || Mask1 != Mask2) | |
| continue; | |
| assert(Mask1 && Mask2 && "both region must have conditions"); | |
| WorkList.push_back(Region1); | |
| } | |
| // Move recipes from Region1 to its successor region, if both are triangles. | |
| for (VPRegionBlock *Region1 : WorkList) { | |
| if (TransformedRegions.contains(Region1)) | |
| continue; | |
| auto *MiddleBasicBlock = cast<VPBasicBlock>(Region1->getSingleSuccessor()); | |
| auto *Region2 = cast<VPRegionBlock>(MiddleBasicBlock->getSingleSuccessor()); | |
| VPBasicBlock *Then1 = getPredicatedThenBlock(Region1); | |
| VPBasicBlock *Then2 = getPredicatedThenBlock(Region2); | |
| if (!Then1 || !Then2) | |
| continue; | |
| // Note: No fusion-preventing memory dependencies are expected in either | |
| // region. Such dependencies should be rejected during earlier dependence | |
| // checks, which guarantee accesses can be re-ordered for vectorization. | |
| // | |
| // Move recipes to the successor region. | |
| for (VPRecipeBase &ToMove : make_early_inc_range(reverse(*Then1))) | |
| ToMove.moveBefore(*Then2, Then2->getFirstNonPhi()); | |
| auto *Merge1 = cast<VPBasicBlock>(Then1->getSingleSuccessor()); | |
| auto *Merge2 = cast<VPBasicBlock>(Then2->getSingleSuccessor()); | |
| // Move VPPredInstPHIRecipes from the merge block to the successor region's | |
| // merge block. Update all users inside the successor region to use the | |
| // original values. | |
| for (VPRecipeBase &Phi1ToMove : make_early_inc_range(reverse(*Merge1))) { | |
| VPValue *PredInst1 = | |
| cast<VPPredInstPHIRecipe>(&Phi1ToMove)->getOperand(0); | |
| VPValue *Phi1ToMoveV = Phi1ToMove.getVPSingleValue(); | |
| Phi1ToMoveV->replaceUsesWithIf(PredInst1, [Then2](VPUser &U, unsigned) { | |
| return cast<VPRecipeBase>(&U)->getParent() == Then2; | |
| }); | |
| // Remove phi recipes that are unused after merging the regions. | |
| if (Phi1ToMove.getVPSingleValue()->user_empty()) { | |
| Phi1ToMove.eraseFromParent(); | |
| continue; | |
| } | |
| Phi1ToMove.moveBefore(*Merge2, Merge2->begin()); | |
| } | |
| // Remove the dead recipes in Region1's entry block. | |
| for (VPRecipeBase &R : | |
| make_early_inc_range(reverse(*Region1->getEntryBasicBlock()))) | |
| R.eraseFromParent(); | |
| // Finally, remove the first region. | |
| for (VPBlockBase *Pred : make_early_inc_range(Region1->getPredecessors())) { | |
| VPBlockUtils::disconnectBlocks(Pred, Region1); | |
| VPBlockUtils::connectBlocks(Pred, MiddleBasicBlock); | |
| } | |
| VPBlockUtils::disconnectBlocks(Region1, MiddleBasicBlock); | |
| TransformedRegions.insert(Region1); | |
| } | |
| return !TransformedRegions.empty(); | |
| } | |
| static VPRegionBlock *createReplicateRegion(VPReplicateRecipe *PredRecipe, | |
| VPRegionBlock *ParentRegion, | |
| VPlan &Plan) { | |
| Instruction *Instr = PredRecipe->getUnderlyingInstr(); | |
| // Build the triangular if-then region. | |
| std::string RegionName = (Twine("pred.") + Instr->getOpcodeName()).str(); | |
| assert(Instr->getParent() && "Predicated instruction not in any basic block"); | |
| auto *BlockInMask = PredRecipe->getMask(); | |
| auto *MaskDef = BlockInMask->getDefiningRecipe(); | |
| auto *BOMRecipe = new VPBranchOnMaskRecipe( | |
| BlockInMask, MaskDef ? MaskDef->getDebugLoc() : DebugLoc::getUnknown()); | |
| auto *Entry = | |
| Plan.createVPBasicBlock(Twine(RegionName) + ".entry", BOMRecipe); | |
| // Replace predicated replicate recipe with a replicate recipe without a | |
| // mask but in the replicate region. | |
| auto *RecipeWithoutMask = new VPReplicateRecipe( | |
| PredRecipe->getUnderlyingInstr(), PredRecipe->operandsWithoutMask(), | |
| PredRecipe->isSingleScalar(), nullptr /*Mask*/, *PredRecipe, *PredRecipe, | |
| PredRecipe->getDebugLoc()); | |
| auto *Pred = | |
| Plan.createVPBasicBlock(Twine(RegionName) + ".if", RecipeWithoutMask); | |
| auto *Exiting = Plan.createVPBasicBlock(Twine(RegionName) + ".continue"); | |
| VPRegionBlock *Region = | |
| Plan.createReplicateRegion(Entry, Exiting, RegionName); | |
| // Note: first set Entry as region entry and then connect successors starting | |
| // from it in order, to propagate the "parent" of each VPBasicBlock. | |
| Region->setParent(ParentRegion); | |
| VPBlockUtils::insertTwoBlocksAfter(Pred, Exiting, Entry); | |
| VPBlockUtils::connectBlocks(Pred, Exiting); | |
| if (!PredRecipe->user_empty()) { | |
| auto *PHIRecipe = new VPPredInstPHIRecipe(RecipeWithoutMask, | |
| RecipeWithoutMask->getDebugLoc()); | |
| Exiting->appendRecipe(PHIRecipe); | |
| PredRecipe->replaceAllUsesWith(PHIRecipe); | |
| } | |
| PredRecipe->eraseFromParent(); | |
| return Region; | |
| } | |
| static void addReplicateRegions(VPlan &Plan) { | |
| SmallVector<VPReplicateRecipe *> WorkList; | |
| for (VPBasicBlock *VPBB : VPBlockUtils::blocksOnly<VPBasicBlock>( | |
| vp_depth_first_deep(Plan.getEntry()))) { | |
| for (VPRecipeBase &R : *VPBB) | |
| if (auto *RepR = dyn_cast<VPReplicateRecipe>(&R)) { | |
| if (RepR->isPredicated()) | |
| WorkList.push_back(RepR); | |
| } | |
| } | |
| unsigned BBNum = 0; | |
| for (VPReplicateRecipe *RepR : WorkList) { | |
| VPBasicBlock *CurrentBlock = RepR->getParent(); | |
| VPBasicBlock *SplitBlock = CurrentBlock->splitAt(RepR->getIterator()); | |
| BasicBlock *OrigBB = RepR->getUnderlyingInstr()->getParent(); | |
| SplitBlock->setName( | |
| OrigBB->hasName() ? OrigBB->getName() + "." + Twine(BBNum++) : ""); | |
| // Record predicated instructions for above packing optimizations. | |
| VPRegionBlock *Region = | |
| createReplicateRegion(RepR, CurrentBlock->getParent(), Plan); | |
| VPBlockUtils::insertOnEdge(CurrentBlock, SplitBlock, Region); | |
| VPRegionBlock *ParentRegion = Region->getParent(); | |
| if (ParentRegion && ParentRegion->getExiting() == CurrentBlock) | |
| ParentRegion->setExiting(SplitBlock); | |
| } | |
| } | |
| bool VPlanTransforms::mergeBlocksIntoPredecessors(VPlan &Plan) { | |
| SmallVector<VPBasicBlock *> WorkList; | |
| for (VPBasicBlock *VPBB : VPBlockUtils::blocksOnly<VPBasicBlock>( | |
| vp_depth_first_deep(Plan.getEntry()))) { | |
| // Don't fold the blocks in the skeleton of the Plan into their single | |
| // predecessors for now. | |
| // TODO: Remove restriction once more of the skeleton is modeled in VPlan. | |
| if (!VPBB->getParent()) | |
| continue; | |
| auto *PredVPBB = | |
| dyn_cast_or_null<VPBasicBlock>(VPBB->getSinglePredecessor()); | |
| if (!PredVPBB || PredVPBB->getNumSuccessors() != 1 || | |
| isa<VPIRBasicBlock>(PredVPBB)) | |
| continue; | |
| WorkList.push_back(VPBB); | |
| } | |
| for (VPBasicBlock *VPBB : WorkList) { | |
| VPBasicBlock *PredVPBB = cast<VPBasicBlock>(VPBB->getSinglePredecessor()); | |
| for (VPRecipeBase &R : make_early_inc_range(*VPBB)) | |
| R.moveBefore(*PredVPBB, PredVPBB->end()); | |
| VPBlockUtils::disconnectBlocks(PredVPBB, VPBB); | |
| auto *ParentRegion = VPBB->getParent(); | |
| if (ParentRegion && ParentRegion->getExiting() == VPBB) | |
| ParentRegion->setExiting(PredVPBB); | |
| VPBlockUtils::transferSuccessors(VPBB, PredVPBB); | |
| // VPBB is now dead and will be cleaned up when the plan gets destroyed. | |
| } | |
| return !WorkList.empty(); | |
| } | |
| void VPlanTransforms::createAndOptimizeReplicateRegions(VPlan &Plan) { | |
| // Convert masked VPReplicateRecipes to if-then region blocks. | |
| addReplicateRegions(Plan); | |
| bool ShouldSimplify = true; | |
| while (ShouldSimplify) { | |
| ShouldSimplify = sinkScalarOperands(Plan); | |
| ShouldSimplify |= mergeReplicateRegionsIntoSuccessors(Plan); | |
| ShouldSimplify |= mergeBlocksIntoPredecessors(Plan); | |
| } | |
| } | |
| /// Remove redundant casts of inductions. | |
| /// | |
| /// Such redundant casts are casts of induction variables that can be ignored, | |
| /// because we already proved that the casted phi is equal to the uncasted phi | |
| /// in the vectorized loop. There is no need to vectorize the cast - the same | |
| /// value can be used for both the phi and casts in the vector loop. | |
| static void removeRedundantInductionCasts(VPlan &Plan) { | |
| for (auto &Phi : Plan.getVectorLoopRegion()->getEntryBasicBlock()->phis()) { | |
| auto *IV = dyn_cast<VPWidenIntOrFpInductionRecipe>(&Phi); | |
| if (!IV || IV->getTruncInst()) | |
| continue; | |
| // A sequence of IR Casts has potentially been recorded for IV, which | |
| // *must be bypassed* when the IV is vectorized, because the vectorized IV | |
| // will produce the desired casted value. This sequence forms a def-use | |
| // chain and is provided in reverse order, ending with the cast that uses | |
| // the IV phi. Search for the recipe of the last cast in the chain and | |
| // replace it with the original IV. Note that only the final cast is | |
| // expected to have users outside the cast-chain and the dead casts left | |
| // over will be cleaned up later. | |
| ArrayRef<Instruction *> Casts = IV->getInductionDescriptor().getCastInsts(); | |
| VPValue *FindMyCast = IV; | |
| for (Instruction *IRCast : reverse(Casts)) { | |
| VPSingleDefRecipe *FoundUserCast = nullptr; | |
| for (auto *U : FindMyCast->users()) { | |
| auto *UserCast = dyn_cast<VPSingleDefRecipe>(U); | |
| if (UserCast && UserCast->getUnderlyingValue() == IRCast) { | |
| FoundUserCast = UserCast; | |
| break; | |
| } | |
| } | |
| // A cast recipe in the chain may have been removed by earlier DCE. | |
| if (!FoundUserCast) | |
| break; | |
| FindMyCast = FoundUserCast; | |
| } | |
| if (FindMyCast != IV) | |
| FindMyCast->replaceAllUsesWith(IV); | |
| } | |
| } | |
| static VPScalarIVStepsRecipe * | |
| createScalarIVSteps(VPlan &Plan, InductionDescriptor::InductionKind Kind, | |
| Instruction::BinaryOps InductionOpcode, | |
| FPMathOperator *FPBinOp, Instruction *TruncI, | |
| VPIRValue *StartV, VPValue *Step, DebugLoc DL, | |
| VPBuilder &Builder) { | |
| VPRegionBlock *LoopRegion = Plan.getVectorLoopRegion(); | |
| VPBasicBlock *HeaderVPBB = LoopRegion->getEntryBasicBlock(); | |
| VPValue *CanonicalIV = LoopRegion->getCanonicalIV(); | |
| VPSingleDefRecipe *BaseIV = | |
| Builder.createDerivedIV(Kind, FPBinOp, StartV, CanonicalIV, Step); | |
| // Truncate base induction if needed. | |
| Type *ResultTy = BaseIV->getScalarType(); | |
| if (TruncI) { | |
| Type *TruncTy = TruncI->getType(); | |
| assert(ResultTy->getScalarSizeInBits() > TruncTy->getScalarSizeInBits() && | |
| "Not truncating."); | |
| assert(ResultTy->isIntegerTy() && "Truncation requires an integer type"); | |
| BaseIV = Builder.createScalarCast(Instruction::Trunc, BaseIV, TruncTy, DL); | |
| ResultTy = TruncTy; | |
| } | |
| // Truncate step if needed. | |
| Type *StepTy = Step->getScalarType(); | |
| if (ResultTy != StepTy) { | |
| assert(StepTy->getScalarSizeInBits() > ResultTy->getScalarSizeInBits() && | |
| "Not truncating."); | |
| assert(StepTy->isIntegerTy() && "Truncation requires an integer type"); | |
| auto *VecPreheader = | |
| cast<VPBasicBlock>(HeaderVPBB->getSingleHierarchicalPredecessor()); | |
| VPBuilder::InsertPointGuard Guard(Builder); | |
| Builder.setInsertPoint(VecPreheader); | |
| Step = Builder.createScalarCast(Instruction::Trunc, Step, ResultTy, DL); | |
| } | |
| return Builder.createScalarIVSteps(InductionOpcode, FPBinOp, BaseIV, Step, | |
| &Plan.getVF(), DL); | |
| } | |
| void VPlanTransforms::replaceWideCanonicalIVWithWideIV( | |
| VPlan &Plan, ScalarEvolution &SE, const TargetTransformInfo &TTI, | |
| TargetTransformInfo::TargetCostKind CostKind, ElementCount VF, unsigned UF, | |
| const SmallPtrSetImpl<const Value *> &ValuesToIgnore) { | |
| VPRegionBlock *LoopRegion = Plan.getVectorLoopRegion(); | |
| if (!LoopRegion) | |
| return; | |
| auto *WideCanIV = | |
| findUserOf<VPWidenCanonicalIVRecipe>(LoopRegion->getCanonicalIV()); | |
| if (!WideCanIV) | |
| return; | |
| Type *CanIVTy = LoopRegion->getCanonicalIVType(); | |
| // Replace the wide canonical IV with a scalar-iv-steps over the canonical | |
| // IV. | |
| if (Plan.hasScalarVFOnly() || vputils::onlyFirstLaneUsed(WideCanIV)) { | |
| VPBuilder Builder(WideCanIV); | |
| WideCanIV->replaceAllUsesWith(createScalarIVSteps( | |
| Plan, InductionDescriptor::IK_IntInduction, Instruction::Add, nullptr, | |
| nullptr, Plan.getZero(CanIVTy), Plan.getConstantInt(CanIVTy, 1), | |
| WideCanIV->getDebugLoc(), Builder)); | |
| WideCanIV->eraseFromParent(); | |
| return; | |
| } | |
| if (vputils::onlyScalarValuesUsed(WideCanIV)) | |
| return; | |
| // If a canonical VPWidenIntOrFpInductionRecipe already produces vector lanes | |
| // in the header, reuse it instead of introducing another wide induction phi. | |
| VPBasicBlock *Header = LoopRegion->getEntryBasicBlock(); | |
| for (VPRecipeBase &Phi : Header->phis()) { | |
| VPWidenIntOrFpInductionRecipe *WidenIV; | |
| if (!match(&Phi, m_CanonicalWidenIV(WidenIV))) | |
| continue; | |
| // The reused wide IV feeds the header mask, whose lanes may extend past | |
| // the trip count; drop flags that only hold inside the scalar loop. | |
| WidenIV->dropPoisonGeneratingFlags(); | |
| WideCanIV->replaceAllUsesWith(WidenIV); | |
| WideCanIV->eraseFromParent(); | |
| return; | |
| } | |
| // Introduce a new VPWidenIntOrFpInductionRecipe if profitable. | |
| auto *VecTy = VectorType::get(CanIVTy, VF); | |
| InstructionCost BroadcastCost = TTI.getShuffleCost( | |
| TargetTransformInfo::SK_Broadcast, VecTy, VecTy, {}, CostKind); | |
| InstructionCost PHICost = TTI.getCFInstrCost(Instruction::PHI, CostKind); | |
| if (PHICost > BroadcastCost) | |
| return; | |
| // Bail out if the additional wide induction phi increase the expected spill | |
| // cost. | |
| VPRegisterUsage UnrolledBase = | |
| calculateRegisterUsageForPlan(Plan, VF, TTI, ValuesToIgnore)[0]; | |
| for (unsigned &NumUsers : make_second_range(UnrolledBase.MaxLocalUsers)) | |
| NumUsers *= UF; | |
| unsigned RegClass = TTI.getRegisterClassForType(/*Vector=*/true, VecTy); | |
| VPRegisterUsage Projected = UnrolledBase; | |
| Projected.MaxLocalUsers[RegClass] += TTI.getRegUsageForType(VecTy); | |
| if (Projected.spillCost(TTI, CostKind) > | |
| UnrolledBase.spillCost(TTI, CostKind)) | |
| return; | |
| InductionDescriptor ID = | |
| InductionDescriptor::getCanonicalIntInduction(CanIVTy, SE); | |
| VPValue *StepV = Plan.getConstantInt(CanIVTy, 1); | |
| auto *NewWideIV = new VPWidenIntOrFpInductionRecipe( | |
| /*IV=*/nullptr, Plan.getZero(CanIVTy), StepV, &Plan.getVF(), ID, | |
| WideCanIV->getNoWrapFlags(), WideCanIV->getDebugLoc()); | |
| NewWideIV->insertBefore(&*Header->getFirstNonPhi()); | |
| WideCanIV->replaceAllUsesWith(NewWideIV); | |
| WideCanIV->eraseFromParent(); | |
| } | |
| /// Returns true if \p R is dead and can be removed. | |
| static bool isDeadRecipe(VPRecipeBase &R) { | |
| // Do remove conditional assume instructions as their conditions may be | |
| // flattened. | |
| auto *RepR = dyn_cast<VPReplicateRecipe>(&R); | |
| bool IsConditionalAssume = RepR && RepR->isPredicated() && | |
| match(RepR, m_Intrinsic<Intrinsic::assume>()); | |
| if (IsConditionalAssume) | |
| return true; | |
| if (R.mayHaveSideEffects()) | |
| return false; | |
| // Recipe is dead if no user keeps the recipe alive. | |
| return all_of(R.definedValues(), [](VPValue *V) { return V->user_empty(); }); | |
| } | |
| void VPlanTransforms::removeDeadRecipes(VPlan &Plan) { | |
| PostOrderTraversal<VPBlockDeepTraversalWrapper<VPBlockBase *>> POT( | |
| Plan.getEntry()); | |
| for (VPBasicBlock *VPBB : VPBlockUtils::blocksOnly<VPBasicBlock>(POT)) { | |
| // The recipes in the block are processed in reverse order, to catch chains | |
| // of dead recipes. | |
| for (VPRecipeBase &R : make_early_inc_range(reverse(*VPBB))) { | |
| if (isDeadRecipe(R)) { | |
| R.eraseFromParent(); | |
| continue; | |
| } | |
| // Check if R is a dead VPPhi <-> update cycle and remove it. | |
| VPValue *Start, *Incoming; | |
| if (!match(&R, m_VPPhi(m_VPValue(Start), m_VPValue(Incoming)))) | |
| continue; | |
| auto *PhiR = cast<VPPhi>(&R); | |
| VPUser *PhiUser = PhiR->getSingleUser(); | |
| if (!PhiUser) | |
| continue; | |
| if (PhiUser != Incoming->getDefiningRecipe() || | |
| Incoming->getNumUsers() != 1) | |
| continue; | |
| PhiR->replaceAllUsesWith(Start); | |
| PhiR->eraseFromParent(); | |
| Incoming->getDefiningRecipe()->eraseFromParent(); | |
| } | |
| } | |
| } | |
| static SmallVector<VPUser *> collectUsersRecursively(VPValue *V) { | |
| SetVector<VPUser *> Users(llvm::from_range, V->users()); | |
| for (unsigned I = 0; I != Users.size(); ++I) { | |
| VPRecipeBase *Cur = cast<VPRecipeBase>(Users[I]); | |
| for (VPValue *V : Cur->definedValues()) | |
| Users.insert_range(V->users()); | |
| } | |
| return Users.takeVector(); | |
| } | |
| /// Scalarize a VPWidenPointerInductionRecipe by replacing it with a PtrAdd | |
| /// (IndStart, ScalarIVSteps (0, Step)). This is used when the recipe only | |
| /// generates scalar values. | |
| static VPValue * | |
| scalarizeVPWidenPointerInduction(VPWidenPointerInductionRecipe *PtrIV, | |
| VPlan &Plan, VPBuilder &Builder) { | |
| const InductionDescriptor &ID = PtrIV->getInductionDescriptor(); | |
| VPIRValue *StartV = Plan.getZero(ID.getStep()->getType()); | |
| VPValue *StepV = PtrIV->getOperand(1); | |
| VPScalarIVStepsRecipe *Steps = createScalarIVSteps( | |
| Plan, InductionDescriptor::IK_IntInduction, Instruction::Add, nullptr, | |
| nullptr, StartV, StepV, PtrIV->getDebugLoc(), Builder); | |
| return Builder.createPtrAdd(PtrIV->getStartValue(), Steps, | |
| PtrIV->getDebugLoc(), "next.gep"); | |
| } | |
| /// Legalize VPWidenPointerInductionRecipe, by replacing it with a PtrAdd | |
| /// (IndStart, ScalarIVSteps (0, Step)) if only its scalar values are used, as | |
| /// VPWidenPointerInductionRecipe will generate vectors only. If some users | |
| /// require vectors while other require scalars, the scalar uses need to extract | |
| /// the scalars from the generated vectors (Note that this is different to how | |
| /// int/fp inductions are handled). Legalize extract-from-ends using uniform | |
| /// VPReplicateRecipe of wide inductions to use regular VPReplicateRecipe, so | |
| /// the correct end value is available. Also optimize | |
| /// VPWidenIntOrFpInductionRecipe, if any of its users needs scalar values, by | |
| /// providing them scalar steps built on the canonical scalar IV and update the | |
| /// original IV's users. This is an optional optimization to reduce the needs of | |
| /// vector extracts. | |
| static void legalizeAndOptimizeInductions(VPlan &Plan) { | |
| VPBasicBlock *HeaderVPBB = Plan.getVectorLoopRegion()->getEntryBasicBlock(); | |
| bool HasOnlyVectorVFs = !Plan.hasScalarVFOnly(); | |
| VPBuilder Builder(HeaderVPBB, HeaderVPBB->getFirstNonPhi()); | |
| for (VPRecipeBase &Phi : HeaderVPBB->phis()) { | |
| auto *PhiR = dyn_cast<VPWidenInductionRecipe>(&Phi); | |
| if (!PhiR) | |
| continue; | |
| // Try to narrow wide and replicating recipes to uniform recipes, based on | |
| // VPlan analysis. | |
| // TODO: Apply to all recipes in the future, to replace legacy uniformity | |
| // analysis. | |
| auto Users = collectUsersRecursively(PhiR); | |
| for (VPUser *U : reverse(Users)) { | |
| auto *Def = dyn_cast<VPRecipeWithIRFlags>(U); | |
| auto *RepR = dyn_cast<VPReplicateRecipe>(U); | |
| // Skip recipes that shouldn't be narrowed. | |
| if (!Def || !isa<VPReplicateRecipe, VPWidenRecipe>(Def) || | |
| Def->user_empty() || !Def->getUnderlyingValue() || | |
| (RepR && (RepR->isSingleScalar() || RepR->isPredicated()))) | |
| continue; | |
| // Skip recipes that may have other lanes than their first used. | |
| if (!vputils::isSingleScalar(Def) && !vputils::onlyFirstLaneUsed(Def)) | |
| continue; | |
| // TODO: Support scalarizing ExtractValue. | |
| if (match(Def, | |
| m_Binary<Instruction::ExtractValue>(m_VPValue(), m_VPValue()))) | |
| continue; | |
| auto *Clone = VPBuilder::createSingleScalarOp( | |
| Def->getUnderlyingInstr()->getOpcode(), Def->operands(), | |
| /*Mask=*/nullptr, *Def, {}, DebugLoc::getUnknown(), | |
| Def->getUnderlyingInstr()); | |
| Clone->insertAfter(Def); | |
| Def->replaceAllUsesWith(Clone); | |
| } | |
| // Replace wide pointer inductions which have only their scalars used by | |
| // PtrAdd(IndStart, ScalarIVSteps (0, Step)). | |
| if (auto *PtrIV = dyn_cast<VPWidenPointerInductionRecipe>(&Phi)) { | |
| if (!Plan.hasScalarVFOnly() && | |
| !PtrIV->onlyScalarsGenerated(Plan.hasScalableVF())) | |
| continue; | |
| VPValue *PtrAdd = scalarizeVPWidenPointerInduction(PtrIV, Plan, Builder); | |
| PtrIV->replaceAllUsesWith(PtrAdd); | |
| continue; | |
| } | |
| // Replace widened induction with scalar steps for users that only use | |
| // scalars. | |
| auto *WideIV = cast<VPWidenIntOrFpInductionRecipe>(&Phi); | |
| if (HasOnlyVectorVFs && none_of(WideIV->users(), [WideIV](VPUser *U) { | |
| return U->usesScalars(WideIV); | |
| })) | |
| continue; | |
| const InductionDescriptor &ID = WideIV->getInductionDescriptor(); | |
| VPScalarIVStepsRecipe *Steps = createScalarIVSteps( | |
| Plan, ID.getKind(), ID.getInductionOpcode(), | |
| dyn_cast_or_null<FPMathOperator>(ID.getInductionBinOp()), | |
| WideIV->getTruncInst(), WideIV->getStartValue(), WideIV->getStepValue(), | |
| WideIV->getDebugLoc(), Builder); | |
| // Update scalar users of IV to use Step instead. | |
| if (!HasOnlyVectorVFs) { | |
| assert(!Plan.hasScalableVF() && | |
| "plans containing a scalar VF cannot also include scalable VFs"); | |
| WideIV->replaceAllUsesWith(Steps); | |
| } else { | |
| bool HasScalableVF = Plan.hasScalableVF(); | |
| WideIV->replaceUsesWithIf(Steps, | |
| [WideIV, HasScalableVF](VPUser &U, unsigned) { | |
| if (HasScalableVF) | |
| return U.usesFirstLaneOnly(WideIV); | |
| return U.usesScalars(WideIV); | |
| }); | |
| } | |
| } | |
| } | |
| /// Check if \p VPV is an untruncated wide induction, either before or after the | |
| /// increment. If so return the header IV (before the increment), otherwise | |
| /// return null. | |
| static VPWidenInductionRecipe * | |
| getOptimizableIVOf(VPValue *VPV, PredicatedScalarEvolution &PSE) { | |
| auto *WideIV = dyn_cast<VPWidenInductionRecipe>(VPV); | |
| if (WideIV) { | |
| // VPV itself is a wide induction, separately compute the end value for exit | |
| // users if it is not a truncated IV. | |
| auto *IntOrFpIV = dyn_cast<VPWidenIntOrFpInductionRecipe>(WideIV); | |
| return (IntOrFpIV && IntOrFpIV->getTruncInst()) ? nullptr : WideIV; | |
| } | |
| // Check if VPV is an optimizable induction increment. | |
| VPRecipeBase *Def = VPV->getDefiningRecipe(); | |
| if (!Def || Def->getNumOperands() != 2) | |
| return nullptr; | |
| WideIV = dyn_cast<VPWidenInductionRecipe>(Def->getOperand(0)); | |
| if (!WideIV) | |
| WideIV = dyn_cast<VPWidenInductionRecipe>(Def->getOperand(1)); | |
| if (!WideIV) | |
| return nullptr; | |
| auto IsWideIVInc = [&]() { | |
| auto &ID = WideIV->getInductionDescriptor(); | |
| // Check if VPV increments the induction by the induction step. | |
| VPValue *IVStep = WideIV->getStepValue(); | |
| switch (ID.getInductionOpcode()) { | |
| case Instruction::Add: | |
| return match(VPV, m_c_Add(m_Specific(WideIV), m_Specific(IVStep))); | |
| case Instruction::FAdd: | |
| return match(VPV, m_c_FAdd(m_Specific(WideIV), m_Specific(IVStep))); | |
| case Instruction::FSub: | |
| return match(VPV, m_Binary<Instruction::FSub>(m_Specific(WideIV), | |
| m_Specific(IVStep))); | |
| case Instruction::Sub: { | |
| // IVStep will be the negated step of the subtraction. Check if Step == -1 | |
| // * IVStep. | |
| VPValue *Step; | |
| if (!match(VPV, m_Sub(m_VPValue(), m_VPValue(Step)))) | |
| return false; | |
| const SCEV *IVStepSCEV = vputils::getSCEVExprForVPValue(IVStep, PSE); | |
| const SCEV *StepSCEV = vputils::getSCEVExprForVPValue(Step, PSE); | |
| ScalarEvolution &SE = *PSE.getSE(); | |
| return !isa<SCEVCouldNotCompute>(IVStepSCEV) && | |
| !isa<SCEVCouldNotCompute>(StepSCEV) && | |
| IVStepSCEV == SE.getNegativeSCEV(StepSCEV); | |
| } | |
| default: | |
| return ID.getKind() == InductionDescriptor::IK_PtrInduction && | |
| match(VPV, m_GetElementPtr(m_Specific(WideIV), | |
| m_Specific(WideIV->getStepValue()))); | |
| } | |
| llvm_unreachable("should have been covered by switch above"); | |
| }; | |
| return IsWideIVInc() ? WideIV : nullptr; | |
| } | |
| /// Attempts to optimize the induction variable exit values for users in the | |
| /// early exit block. | |
| static VPValue *optimizeEarlyExitInductionUser(VPlan &Plan, VPValue *Op, | |
| PredicatedScalarEvolution &PSE) { | |
| VPValue *Incoming, *Mask; | |
| if (!match(Op, m_ExtractLane(m_FirstActiveLane(m_VPValue(Mask)), | |
| m_VPValue(Incoming)))) | |
| return nullptr; | |
| auto *WideIV = getOptimizableIVOf(Incoming, PSE); | |
| if (!WideIV) | |
| return nullptr; | |
| auto *WideIntOrFp = dyn_cast<VPWidenIntOrFpInductionRecipe>(WideIV); | |
| if (WideIntOrFp && WideIntOrFp->getTruncInst()) | |
| return nullptr; | |
| // Calculate the final index. | |
| VPRegionBlock *LoopRegion = Plan.getVectorLoopRegion(); | |
| auto *CanonicalIV = LoopRegion->getCanonicalIV(); | |
| Type *CanonicalIVType = LoopRegion->getCanonicalIVType(); | |
| auto *ExtractR = cast<VPInstruction>(Op); | |
| VPBuilder B(ExtractR); | |
| DebugLoc DL = ExtractR->getDebugLoc(); | |
| VPValue *FirstActiveLane = B.createFirstActiveLane(Mask, DL); | |
| FirstActiveLane = B.createScalarZExtOrTrunc( | |
| FirstActiveLane, CanonicalIVType, FirstActiveLane->getScalarType(), DL); | |
| VPValue *EndValue = B.createAdd(CanonicalIV, FirstActiveLane, DL); | |
| // `getOptimizableIVOf()` always returns the pre-incremented IV, so if it | |
| // changed it means the exit is using the incremented value, so we need to | |
| // add the step. | |
| if (Incoming != WideIV) { | |
| VPValue *One = Plan.getConstantInt(CanonicalIVType, 1); | |
| EndValue = B.createAdd(EndValue, One, DL); | |
| } | |
| if (!match(WideIV, m_CanonicalWidenIV())) { | |
| const InductionDescriptor &ID = WideIV->getInductionDescriptor(); | |
| VPIRValue *Start = WideIV->getStartValue(); | |
| VPValue *Step = WideIV->getStepValue(); | |
| EndValue = B.createDerivedIV( | |
| ID.getKind(), dyn_cast_or_null<FPMathOperator>(ID.getInductionBinOp()), | |
| Start, EndValue, Step); | |
| } | |
| return EndValue; | |
| } | |
| /// Compute the end value for \p WideIV, unless it is truncated. Creates a | |
| /// VPDerivedIVRecipe for non-canonical inductions. | |
| static VPValue *tryToComputeEndValueForInduction(VPWidenInductionRecipe *WideIV, | |
| VPBuilder &VectorPHBuilder, | |
| VPValue *VectorTC) { | |
| auto *WideIntOrFp = dyn_cast<VPWidenIntOrFpInductionRecipe>(WideIV); | |
| // Truncated wide inductions resume from the last lane of their vector value | |
| // in the last vector iteration which is handled elsewhere. | |
| if (WideIntOrFp && WideIntOrFp->getTruncInst()) | |
| return nullptr; | |
| VPIRValue *Start = WideIV->getStartValue(); | |
| VPValue *Step = WideIV->getStepValue(); | |
| const InductionDescriptor &ID = WideIV->getInductionDescriptor(); | |
| VPValue *EndValue = VectorTC; | |
| if (!match(WideIV, m_CanonicalWidenIV())) { | |
| EndValue = VectorPHBuilder.createDerivedIV( | |
| ID.getKind(), dyn_cast_or_null<FPMathOperator>(ID.getInductionBinOp()), | |
| Start, VectorTC, Step); | |
| } | |
| // EndValue is derived from the vector trip count (which has the same type as | |
| // the widest induction) and thus may be wider than the induction here. | |
| Type *ScalarTypeOfWideIV = WideIV->getScalarType(); | |
| if (ScalarTypeOfWideIV != EndValue->getScalarType()) { | |
| EndValue = VectorPHBuilder.createScalarCast(Instruction::Trunc, EndValue, | |
| ScalarTypeOfWideIV, | |
| WideIV->getDebugLoc()); | |
| } | |
| return EndValue; | |
| } | |
| /// Attempts to optimize the induction variable exit values for users in the | |
| /// exit block coming from the latch in the original scalar loop. | |
| static VPValue * | |
| optimizeLatchExitInductionUser(VPlan &Plan, VPValue *Op, | |
| DenseMap<VPValue *, VPValue *> &EndValues, | |
| PredicatedScalarEvolution &PSE) { | |
| VPValue *Incoming; | |
| if (!match(Op, m_CombineOr(m_ExtractLastLaneOfLastPart(m_VPValue(Incoming)), | |
| m_ExtractLane(m_LastActiveLane(m_HeaderMask()), | |
| m_VPValue(Incoming))))) | |
| return nullptr; | |
| VPWidenInductionRecipe *WideIV = getOptimizableIVOf(Incoming, PSE); | |
| if (!WideIV) | |
| return nullptr; | |
| VPValue *EndValue = EndValues.lookup(WideIV); | |
| assert(EndValue && "Must have computed the end value up front"); | |
| // `getOptimizableIVOf()` always returns the pre-incremented IV, so if it | |
| // changed it means the exit is using the incremented value, so we don't | |
| // need to subtract the step. | |
| if (Incoming != WideIV) | |
| return EndValue; | |
| // Otherwise, subtract the step from the EndValue. | |
| auto *ExtractR = cast<VPInstruction>(Op); | |
| VPBuilder B(ExtractR); | |
| VPValue *Step = WideIV->getStepValue(); | |
| Type *ScalarTy = WideIV->getScalarType(); | |
| if (ScalarTy->isIntegerTy()) | |
| return B.createSub(EndValue, Step, DebugLoc::getUnknown(), "ind.escape"); | |
| if (ScalarTy->isPointerTy()) { | |
| Type *StepTy = Step->getScalarType(); | |
| auto *Zero = Plan.getZero(StepTy); | |
| return B.createPtrAdd(EndValue, B.createSub(Zero, Step), | |
| DebugLoc::getUnknown(), "ind.escape"); | |
| } | |
| if (ScalarTy->isFloatingPointTy()) { | |
| const auto &ID = WideIV->getInductionDescriptor(); | |
| return B.createNaryOp( | |
| ID.getInductionBinOp()->getOpcode() == Instruction::FAdd | |
| ? Instruction::FSub | |
| : Instruction::FAdd, | |
| {EndValue, Step}, {ID.getInductionBinOp()->getFastMathFlags()}); | |
| } | |
| llvm_unreachable("all possible induction types must be handled"); | |
| return nullptr; | |
| } | |
| void VPlanTransforms::optimizeInductionLiveOutUsers( | |
| VPlan &Plan, PredicatedScalarEvolution &PSE) { | |
| // Compute end values for all inductions. | |
| VPRegionBlock *VectorRegion = Plan.getVectorLoopRegion(); | |
| auto *VectorPH = cast<VPBasicBlock>(VectorRegion->getSinglePredecessor()); | |
| VPBuilder VectorPHBuilder(VectorPH, VectorPH->begin()); | |
| DenseMap<VPValue *, VPValue *> EndValues; | |
| VPValue *ResumeTC = | |
| Plan.hasTailFolded() ? Plan.getTripCount() : &Plan.getVectorTripCount(); | |
| for (auto &Phi : VectorRegion->getEntryBasicBlock()->phis()) { | |
| auto *WideIV = dyn_cast<VPWidenInductionRecipe>(&Phi); | |
| if (!WideIV) | |
| continue; | |
| if (VPValue *EndValue = | |
| tryToComputeEndValueForInduction(WideIV, VectorPHBuilder, ResumeTC)) | |
| EndValues[WideIV] = EndValue; | |
| } | |
| VPBasicBlock *MiddleVPBB = Plan.getMiddleBlock(); | |
| for (VPRecipeBase &R : make_early_inc_range(*MiddleVPBB)) { | |
| VPValue *Op; | |
| if (!match(&R, m_ExitingIVValue(m_VPValue(Op)))) | |
| continue; | |
| auto *WideIV = cast<VPWidenInductionRecipe>(Op); | |
| if (VPValue *EndValue = EndValues.lookup(WideIV)) { | |
| R.getVPSingleValue()->replaceAllUsesWith(EndValue); | |
| R.eraseFromParent(); | |
| } | |
| } | |
| // Then, optimize exit block users. | |
| for (VPIRBasicBlock *ExitVPBB : Plan.getExitBlocks()) { | |
| for (VPRecipeBase &R : ExitVPBB->phis()) { | |
| auto *ExitIRI = cast<VPIRPhi>(&R); | |
| for (auto [Idx, PredVPBB] : enumerate(ExitVPBB->getPredecessors())) { | |
| VPValue *Escape = nullptr; | |
| if (PredVPBB == MiddleVPBB) | |
| Escape = optimizeLatchExitInductionUser( | |
| Plan, ExitIRI->getOperand(Idx), EndValues, PSE); | |
| else | |
| Escape = optimizeEarlyExitInductionUser( | |
| Plan, ExitIRI->getOperand(Idx), PSE); | |
| if (Escape) | |
| ExitIRI->setOperand(Idx, Escape); | |
| } | |
| } | |
| } | |
| } | |
| /// Remove redundant ExpandSCEVRecipes in \p Plan's entry block by replacing | |
| /// them with already existing recipes expanding the same SCEV expression. | |
| static void removeRedundantExpandSCEVRecipes(VPlan &Plan) { | |
| DenseMap<const SCEV *, VPValue *> SCEV2VPV; | |
| for (VPRecipeBase &R : | |
| make_early_inc_range(*Plan.getEntry()->getEntryBasicBlock())) { | |
| auto *ExpR = dyn_cast<VPExpandSCEVRecipe>(&R); | |
| if (!ExpR) | |
| continue; | |
| const auto &[V, Inserted] = SCEV2VPV.try_emplace(ExpR->getSCEV(), ExpR); | |
| if (Inserted) | |
| continue; | |
| ExpR->replaceAllUsesWith(V->second); | |
| if (ExpR == Plan.getTripCount()) | |
| Plan.resetTripCount(V->second); | |
| ExpR->eraseFromParent(); | |
| } | |
| } | |
| static void recursivelyDeleteDeadRecipes(VPValue *V) { | |
| SmallVector<VPValue *> WorkList; | |
| SmallPtrSet<VPValue *, 8> Seen; | |
| WorkList.push_back(V); | |
| while (!WorkList.empty()) { | |
| VPValue *Cur = WorkList.pop_back_val(); | |
| if (!Seen.insert(Cur).second) | |
| continue; | |
| VPRecipeBase *R = Cur->getDefiningRecipe(); | |
| if (!R) | |
| continue; | |
| if (!isDeadRecipe(*R)) | |
| continue; | |
| append_range(WorkList, R->operands()); | |
| R->eraseFromParent(); | |
| } | |
| } | |
| /// Get any instruction opcode or intrinsic ID data embedded in recipe \p R. | |
| /// Returns an optional pair, where the first element indicates whether it is | |
| /// an intrinsic ID. | |
| static std::optional<std::pair<bool, unsigned>> | |
| getOpcodeOrIntrinsicID(const VPSingleDefRecipe *R) { | |
| if (Intrinsic::ID IID = vputils::getIntrinsicID(R)) | |
| return std::make_pair(true, IID); | |
| return TypeSwitch<const VPSingleDefRecipe *, | |
| std::optional<std::pair<bool, unsigned>>>(R) | |
| .Case<VPInstruction, VPWidenRecipe, VPWidenCastRecipe, VPWidenGEPRecipe, | |
| VPReplicateRecipe>( | |
| [](auto *I) { return std::make_pair(false, I->getOpcode()); }) | |
| .Case([](const VPWidenPHIRecipe *I) { | |
| return std::make_pair(false, Instruction::PHI); | |
| }) | |
| .Case<VPVectorPointerRecipe, VPPredInstPHIRecipe, VPScalarIVStepsRecipe>( | |
| [](auto *I) { | |
| // For recipes that do not directly map to LLVM IR instructions, | |
| // assign opcodes after the last VPInstruction opcode (which is also | |
| // after the last IR Instruction opcode), based on the VPRecipeID. | |
| return std::make_pair(false, VPInstruction::OpsEnd + 1 + | |
| I->getVPRecipeID()); | |
| }) | |
| .Default([](auto *) { return std::nullopt; }); | |
| } | |
| /// Try to fold \p R using InstSimplifyFolder. Will succeed and return a | |
| /// non-nullptr VPValue for a handled opcode or intrinsic ID if corresponding \p | |
| /// Operands are foldable live-ins. | |
| static VPIRValue *tryToFoldLiveIns(VPSingleDefRecipe &R, | |
| ArrayRef<VPValue *> Operands, | |
| const DataLayout &DL) { | |
| auto OpcodeOrIID = getOpcodeOrIntrinsicID(&R); | |
| if (!OpcodeOrIID) | |
| return nullptr; | |
| SmallVector<Value *, 4> Ops; | |
| for (VPValue *Op : Operands) { | |
| VPValue *Candidate = Op; | |
| match(Op, m_Broadcast(m_VPValue(Candidate))); | |
| if (!match(Candidate, m_LiveIn())) | |
| return nullptr; | |
| Value *V = Candidate->getUnderlyingValue(); | |
| if (!V) | |
| return nullptr; | |
| Ops.push_back(V); | |
| } | |
| VPlan &Plan = *R.getParent()->getPlan(); | |
| auto FoldToIRValue = [&]() -> Value * { | |
| InstSimplifyFolder Folder(DL); | |
| if (OpcodeOrIID->first) { | |
| // VPInstructions store the called intrinsic as last operand. | |
| if (isa<VPInstruction>(R)) | |
| Ops.pop_back(); | |
| auto *RFlags = dyn_cast<VPRecipeWithIRFlags>(&R); | |
| return Folder.FoldIntrinsic(OpcodeOrIID->second, Ops, R.getScalarType(), | |
| RFlags ? RFlags->getFastMathFlagsOrNone() | |
| : FastMathFlags()); | |
| } | |
| unsigned Opcode = OpcodeOrIID->second; | |
| if (Instruction::isBinaryOp(Opcode)) | |
| return Folder.FoldBinOp(static_cast<Instruction::BinaryOps>(Opcode), | |
| Ops[0], Ops[1]); | |
| if (Instruction::isCast(Opcode)) | |
| return Folder.FoldCast(static_cast<Instruction::CastOps>(Opcode), Ops[0], | |
| R.getVPSingleValue()->getScalarType()); | |
| switch (Opcode) { | |
| case VPInstruction::Not: | |
| return Folder.FoldBinOp(Instruction::BinaryOps::Xor, Ops[0], | |
| Constant::getAllOnesValue(Ops[0]->getType())); | |
| case Instruction::Select: | |
| return Folder.FoldSelect(Ops[0], Ops[1], Ops[2]); | |
| case Instruction::ICmp: | |
| case Instruction::FCmp: | |
| return Folder.FoldCmp(cast<VPRecipeWithIRFlags>(R).getPredicate(), Ops[0], | |
| Ops[1]); | |
| case Instruction::GetElementPtr: { | |
| auto &RFlags = cast<VPRecipeWithIRFlags>(R); | |
| auto *GEP = cast<GetElementPtrInst>(RFlags.getUnderlyingInstr()); | |
| return Folder.FoldGEP(GEP->getSourceElementType(), Ops[0], | |
| drop_begin(Ops), RFlags.getGEPNoWrapFlags()); | |
| } | |
| case VPInstruction::PtrAdd: | |
| case VPInstruction::WidePtrAdd: | |
| return Folder.FoldGEP(IntegerType::getInt8Ty(Plan.getContext()), Ops[0], | |
| Ops[1], | |
| cast<VPRecipeWithIRFlags>(R).getGEPNoWrapFlags()); | |
| // An extract of a live-in is an extract of a broadcast, so return the | |
| // broadcasted element. | |
| case Instruction::ExtractElement: | |
| assert(!Ops[0]->getType()->isVectorTy() && "Live-ins should be scalar"); | |
| return Ops[0]; | |
| } | |
| return nullptr; | |
| }; | |
| if (Value *V = FoldToIRValue()) | |
| return Plan.getOrAddLiveIn(V); | |
| return nullptr; | |
| } | |
| /// Try to simplify logical and bitwise recipes in \p Def. | |
| static bool simplifyLogicalRecipe(VPSingleDefRecipe *Def, VPBuilder &Builder, | |
| bool CanCreateNewRecipe) { | |
| VPlan *Plan = Def->getParent()->getPlan(); | |
| // Simplify (X && Y) | (X && !Y) -> X. | |
| // TODO: Split up into simpler, modular combines: (X && Y) | (X && Z) into X | |
| // && (Y | Z) and (X | !X) into true. This requires queuing newly created | |
| // recipes to be visited during simplification. | |
| VPValue *X, *Y, *Z; | |
| if (match(Def, | |
| m_c_BinaryOr(m_LogicalAnd(m_VPValue(X), m_VPValue(Y)), | |
| m_LogicalAnd(m_Deferred(X), m_Not(m_Deferred(Y)))))) { | |
| Def->replaceAllUsesWith(X); | |
| Def->eraseFromParent(); | |
| return true; | |
| } | |
| // x | AllOnes -> AllOnes | |
| if (match(Def, m_c_BinaryOr(m_VPValue(X), m_AllOnes()))) { | |
| Def->replaceAllUsesWith(Plan->getAllOnesValue(Def->getScalarType())); | |
| return true; | |
| } | |
| // x | 0 -> x | |
| if (match(Def, m_c_BinaryOr(m_VPValue(X), m_ZeroInt()))) { | |
| Def->replaceAllUsesWith(X); | |
| return true; | |
| } | |
| // x | !x -> AllOnes | |
| if (match(Def, m_c_BinaryOr(m_VPValue(X), m_Not(m_Deferred(X))))) { | |
| Def->replaceAllUsesWith(Plan->getAllOnesValue(Def->getScalarType())); | |
| return true; | |
| } | |
| // x & 0 -> 0 | |
| if (match(Def, m_c_BinaryAnd(m_VPValue(X), m_ZeroInt()))) { | |
| Def->replaceAllUsesWith(Plan->getZero(Def->getScalarType())); | |
| return true; | |
| } | |
| // x & AllOnes -> x | |
| if (match(Def, m_c_BinaryAnd(m_VPValue(X), m_AllOnes()))) { | |
| Def->replaceAllUsesWith(X); | |
| return true; | |
| } | |
| // x && false -> false | |
| if (match(Def, m_c_LogicalAnd(m_VPValue(X), m_False()))) { | |
| Def->replaceAllUsesWith(Plan->getFalse()); | |
| return true; | |
| } | |
| // x && true -> x | |
| if (match(Def, m_c_LogicalAnd(m_VPValue(X), m_True()))) { | |
| Def->replaceAllUsesWith(X); | |
| return true; | |
| } | |
| // (x && y) | (x && z) -> x && (y | z) | |
| if (CanCreateNewRecipe && | |
| match(Def, m_c_BinaryOr(m_LogicalAnd(m_VPValue(X), m_VPValue(Y)), | |
| m_LogicalAnd(m_Deferred(X), m_VPValue(Z)))) && | |
| // Simplify only if one of the operands has one use to avoid creating an | |
| // extra recipe. | |
| (!Def->getOperand(0)->hasMoreThanOneUniqueUser() || | |
| !Def->getOperand(1)->hasMoreThanOneUniqueUser())) { | |
| Def->replaceAllUsesWith( | |
| Builder.createLogicalAnd(X, Builder.createOr(Y, Z))); | |
| return true; | |
| } | |
| // x && (x && y) -> x && y | |
| if (match(Def, m_LogicalAnd(m_VPValue(X), | |
| m_LogicalAnd(m_Deferred(X), m_VPValue())))) { | |
| Def->replaceAllUsesWith(Def->getOperand(1)); | |
| return true; | |
| } | |
| // x && (y && x) -> x && y | |
| if (match(Def, m_LogicalAnd(m_VPValue(X), | |
| m_LogicalAnd(m_VPValue(Y), m_Deferred(X))))) { | |
| Def->replaceAllUsesWith(Builder.createLogicalAnd(X, Y)); | |
| return true; | |
| } | |
| // x && !x -> 0 | |
| if (match(Def, m_LogicalAnd(m_VPValue(X), m_Not(m_Deferred(X))))) { | |
| Def->replaceAllUsesWith(Plan->getFalse()); | |
| return true; | |
| } | |
| if (match(Def, m_Select(m_VPValue(), m_VPValue(X), m_Deferred(X)))) { | |
| Def->replaceAllUsesWith(X); | |
| return true; | |
| } | |
| // select c, false, true -> not c | |
| VPValue *C; | |
| if (CanCreateNewRecipe && | |
| match(Def, m_Select(m_VPValue(C), m_False(), m_True()))) { | |
| Def->replaceAllUsesWith(Builder.createNot(C)); | |
| return true; | |
| } | |
| // select !c, x, y -> select c, y, x | |
| if (match(Def, m_Select(m_Not(m_VPValue(C)), m_VPValue(X), m_VPValue(Y)))) { | |
| Def->setOperand(0, C); | |
| Def->setOperand(1, Y); | |
| Def->setOperand(2, X); | |
| return true; | |
| } | |
| // select x, (i1 y | z), y -> y | (x && z) | |
| if (CanCreateNewRecipe && | |
| match(Def, m_Select(m_VPValue(X), | |
| m_OneUse(m_c_BinaryOr(m_VPValue(Y), m_VPValue(Z))), | |
| m_Deferred(Y))) && | |
| Y->getScalarType()->isIntegerTy(1)) { | |
| Def->replaceAllUsesWith( | |
| Builder.createOr(Y, Builder.createLogicalAnd(X, Z))); | |
| return true; | |
| } | |
| return false; | |
| } | |
| /// Try to simplify VPSingleDefRecipe \p Def. | |
| static void simplifyRecipe(VPSingleDefRecipe *Def) { | |
| VPlan *Plan = Def->getParent()->getPlan(); | |
| // Simplification of live-in IR values for SingleDef recipes using | |
| // InstSimplifyFolder. | |
| const DataLayout &DL = Plan->getDataLayout(); | |
| if (VPValue *V = tryToFoldLiveIns(*Def, Def->operands(), DL)) | |
| return Def->replaceAllUsesWith(V); | |
| // Fold PredPHI LiveIn -> LiveIn. | |
| if (auto *PredPHI = dyn_cast<VPPredInstPHIRecipe>(Def)) { | |
| VPValue *Op = PredPHI->getOperand(0); | |
| if (isa<VPIRValue>(Op)) | |
| PredPHI->replaceAllUsesWith(Op); | |
| } | |
| // Drop the mask of a predicated store masked by the header mask (which is | |
| // guaranteed to be true at least for the first lane) and both the stored | |
| // value and the address are uniform across VF and UF. The header mask is | |
| // still the abstract region value here. | |
| if (auto *RepR = dyn_cast<VPReplicateRecipe>(Def); | |
| RepR && RepR->isPredicated() && RepR->getOpcode() == Instruction::Store && | |
| all_of(RepR->operandsWithoutMask(), vputils::isUniformAcrossVFsAndUFs) && | |
| match(RepR->getMask(), m_HeaderMask())) { | |
| auto *Unmasked = new VPReplicateRecipe( | |
| RepR->getUnderlyingInstr(), RepR->operandsWithoutMask(), | |
| RepR->isSingleScalar(), /*Mask=*/nullptr, *RepR, *RepR, | |
| RepR->getDebugLoc()); | |
| Unmasked->insertBefore(RepR); | |
| RepR->replaceAllUsesWith(Unmasked); | |
| RepR->eraseFromParent(); | |
| return; | |
| } | |
| VPBuilder Builder(Def); | |
| // Avoid replacing VPInstructions with underlying values with new | |
| // VPInstructions, as we would fail to create widen/replicate recpes from the | |
| // new VPInstructions without an underlying value, and miss out on some | |
| // transformations that only apply to widened/replicated recipes later, by | |
| // doing so. | |
| // TODO: We should also not replace non-VPInstructions like VPWidenRecipe with | |
| // VPInstructions without underlying values, as those will get skipped during | |
| // cost computation. | |
| bool CanCreateNewRecipe = | |
| !isa<VPInstruction>(Def) || !Def->getUnderlyingValue(); | |
| VPValue *A; | |
| if (match(Def, m_Trunc(m_ZExtOrSExt(m_VPValue(A))))) { | |
| Type *TruncTy = Def->getScalarType(); | |
| Type *ATy = A->getScalarType(); | |
| if (TruncTy == ATy) { | |
| Def->replaceAllUsesWith(A); | |
| } else { | |
| // Don't replace a non-widened cast recipe with a widened cast. | |
| if (!isa<VPWidenCastRecipe>(Def)) | |
| return; | |
| if (ATy->getScalarSizeInBits() < TruncTy->getScalarSizeInBits()) { | |
| unsigned ExtOpcode = match(Def->getOperand(0), m_SExt(m_VPValue())) | |
| ? Instruction::SExt | |
| : Instruction::ZExt; | |
| auto *Ext = Builder.createWidenCast(Instruction::CastOps(ExtOpcode), A, | |
| TruncTy); | |
| if (auto *UnderlyingExt = Def->getOperand(0)->getUnderlyingValue()) { | |
| // UnderlyingExt has distinct return type, used to retain legacy cost. | |
| Ext->setUnderlyingValue(UnderlyingExt); | |
| } | |
| Def->replaceAllUsesWith(Ext); | |
| } else if (ATy->getScalarSizeInBits() > TruncTy->getScalarSizeInBits()) { | |
| auto *Trunc = Builder.createWidenCast(Instruction::Trunc, A, TruncTy); | |
| Def->replaceAllUsesWith(Trunc); | |
| } | |
| } | |
| } | |
| if (simplifyLogicalRecipe(Def, Builder, CanCreateNewRecipe)) | |
| return; | |
| VPValue *X, *Y, *C; | |
| if (match(Def, m_c_Add(m_VPValue(A), m_ZeroInt()))) | |
| return Def->replaceAllUsesWith(A); | |
| if (match(Def, m_c_Mul(m_VPValue(A), m_One()))) | |
| return Def->replaceAllUsesWith(A); | |
| if (match(Def, m_c_Mul(m_VPValue(A), m_ZeroInt()))) | |
| return Def->replaceAllUsesWith(Plan->getZero(Def->getScalarType())); | |
| if (CanCreateNewRecipe && match(Def, m_c_Mul(m_VPValue(A), m_AllOnes()))) { | |
| // Preserve nsw from the Mul on the new Sub. | |
| VPIRFlags::WrapFlagsTy NW = { | |
| false, cast<VPRecipeWithIRFlags>(Def)->hasNoSignedWrap()}; | |
| return Def->replaceAllUsesWith(Builder.createSub( | |
| Plan->getZero(A->getScalarType()), A, Def->getDebugLoc(), "", NW)); | |
| } | |
| if (CanCreateNewRecipe && | |
| match(Def, m_c_Add(m_VPValue(X), m_Sub(m_ZeroInt(), m_VPValue(Y))))) { | |
| // Preserve nsw from the Add and the Sub, if it's present on both, on the | |
| // new Sub. | |
| VPIRFlags::WrapFlagsTy NW = { | |
| false, | |
| cast<VPRecipeWithIRFlags>(Def)->hasNoSignedWrap() && | |
| cast<VPRecipeWithIRFlags>(Def->getOperand(Def->getOperand(0) == X)) | |
| ->hasNoSignedWrap()}; | |
| return Def->replaceAllUsesWith( | |
| Builder.createSub(X, Y, Def->getDebugLoc(), "", NW)); | |
| } | |
| const APInt *APC; | |
| if (CanCreateNewRecipe && match(Def, m_c_Mul(m_VPValue(A), m_APInt(APC))) && | |
| APC->isPowerOf2()) { | |
| auto *MulR = cast<VPRecipeWithIRFlags>(Def); | |
| unsigned ShiftAmt = APC->exactLogBase2(); | |
| VPIRFlags::WrapFlagsTy NW(MulR->hasNoUnsignedWrap(), | |
| MulR->hasNoSignedWrap() && | |
| ShiftAmt != APC->getBitWidth() - 1); | |
| return Def->replaceAllUsesWith(Builder.createNaryOp( | |
| Instruction::Shl, | |
| {A, Plan->getConstantInt(APC->getBitWidth(), ShiftAmt)}, NW, | |
| Def->getDebugLoc())); | |
| } | |
| if (CanCreateNewRecipe && match(Def, m_UDiv(m_VPValue(A), m_APInt(APC))) && | |
| APC->isPowerOf2()) | |
| return Def->replaceAllUsesWith(Builder.createNaryOp( | |
| Instruction::LShr, | |
| {A, Plan->getConstantInt(APC->getBitWidth(), APC->exactLogBase2())}, | |
| *cast<VPRecipeWithIRFlags>(Def), Def->getDebugLoc())); | |
| if (match(Def, m_Not(m_VPValue(A)))) { | |
| if (match(A, m_Not(m_VPValue(A)))) | |
| return Def->replaceAllUsesWith(A); | |
| // Try to fold Not into compares by adjusting the predicate in-place. | |
| CmpPredicate Pred; | |
| if (match(A, m_Cmp(Pred, m_VPValue(), m_VPValue()))) { | |
| auto *Cmp = cast<VPRecipeWithIRFlags>(A); | |
| if (all_of(Cmp->users(), | |
| match_fn(m_CombineOr( | |
| m_Not(m_Specific(Cmp)), | |
| m_Select(m_Specific(Cmp), m_VPValue(), m_VPValue()))))) { | |
| Cmp->setPredicate(CmpInst::getInversePredicate(Pred)); | |
| for (VPUser *U : to_vector(Cmp->users())) { | |
| auto *R = cast<VPSingleDefRecipe>(U); | |
| if (match(R, m_Select(m_Specific(Cmp), m_VPValue(X), m_VPValue(Y)))) { | |
| // select (cmp pred), x, y -> select (cmp inv_pred), y, x | |
| R->setOperand(1, Y); | |
| R->setOperand(2, X); | |
| } else { | |
| // not (cmp pred) -> cmp inv_pred | |
| assert(match(R, m_Not(m_Specific(Cmp))) && "Unexpected user"); | |
| R->replaceAllUsesWith(Cmp); | |
| } | |
| } | |
| // If Cmp doesn't have a debug location, use the one from the negation, | |
| // to preserve the location. | |
| if (!Cmp->getDebugLoc() && Def->getDebugLoc()) | |
| Cmp->setDebugLoc(Def->getDebugLoc()); | |
| } | |
| } | |
| } | |
| // Fold any-of (fcmp uno %A, %A), (fcmp uno %B, %B), ... -> | |
| // any-of (fcmp uno %A, %B), ... | |
| if (match(Def, m_AnyOf())) { | |
| SmallVector<VPValue *, 4> NewOps; | |
| VPRecipeBase *UnpairedCmp = nullptr; | |
| for (VPValue *Op : Def->operands()) { | |
| VPValue *X; | |
| if (Op->getNumUsers() > 1 || | |
| !match(Op, m_SpecificCmp(CmpInst::FCMP_UNO, m_VPValue(X), | |
| m_Deferred(X)))) { | |
| NewOps.push_back(Op); | |
| } else if (!UnpairedCmp) { | |
| UnpairedCmp = Op->getDefiningRecipe(); | |
| } else { | |
| NewOps.push_back(Builder.createFCmp(CmpInst::FCMP_UNO, | |
| UnpairedCmp->getOperand(0), X)); | |
| UnpairedCmp = nullptr; | |
| } | |
| } | |
| if (UnpairedCmp) | |
| NewOps.push_back(UnpairedCmp->getVPSingleValue()); | |
| if (NewOps.size() < Def->getNumOperands()) { | |
| VPValue *NewAnyOf = Builder.createNaryOp(VPInstruction::AnyOf, NewOps); | |
| return Def->replaceAllUsesWith(NewAnyOf); | |
| } | |
| } | |
| // Fold (fcmp uno %X, %X) or (fcmp uno %Y, %Y) -> fcmp uno %X, %Y | |
| // This is useful for fmax/fmin without fast-math flags, where we need to | |
| // check if any operand is NaN. | |
| if (CanCreateNewRecipe && | |
| match(Def, m_BinaryOr(m_SpecificCmp(CmpInst::FCMP_UNO, m_VPValue(X), | |
| m_Deferred(X)), | |
| m_SpecificCmp(CmpInst::FCMP_UNO, m_VPValue(Y), | |
| m_Deferred(Y))))) { | |
| VPValue *NewCmp = Builder.createFCmp(CmpInst::FCMP_UNO, X, Y); | |
| return Def->replaceAllUsesWith(NewCmp); | |
| } | |
| // Remove redundant DerviedIVs, that is 0 + A * 1 -> A and 0 + 0 * x -> 0. | |
| if ((match(Def, m_DerivedIV(m_ZeroInt(), m_VPValue(A), m_One())) || | |
| match(Def, m_DerivedIV(m_ZeroInt(), m_ZeroInt(), m_VPValue()))) && | |
| Def->getOperand(1)->getScalarType() == Def->getScalarType()) | |
| return Def->replaceAllUsesWith(Def->getOperand(1)); | |
| if (match(Def, m_VPInstruction<VPInstruction::WideIVStep>(m_VPValue(X), | |
| m_One()))) { | |
| Type *WideStepTy = Def->getScalarType(); | |
| if (X->getScalarType() != WideStepTy) | |
| X = Builder.createWidenCast(Instruction::Trunc, X, WideStepTy); | |
| Def->replaceAllUsesWith(X); | |
| return; | |
| } | |
| // For i1 vp.merges produced by AnyOf reductions: | |
| // vp.merge true, (or x, y), x, evl -> vp.merge y, true, x, evl | |
| if (match(Def, m_Intrinsic<Intrinsic::vp_merge>(m_True(), m_VPValue(A), | |
| m_VPValue(X), m_VPValue())) && | |
| match(A, m_c_BinaryOr(m_Specific(X), m_VPValue(Y))) && | |
| Def->getScalarType()->isIntegerTy(1)) { | |
| Def->setOperand(1, Def->getOperand(0)); | |
| Def->setOperand(0, Y); | |
| return; | |
| } | |
| // Simplify MaskedCond with no block mask to its single operand. | |
| if (match(Def, m_VPInstruction<VPInstruction::MaskedCond>()) && | |
| !cast<VPInstruction>(Def)->isMasked()) | |
| return Def->replaceAllUsesWith(Def->getOperand(0)); | |
| // Look through ExtractLastLane. | |
| if (match(Def, m_ExtractLastLane(m_VPValue(A)))) { | |
| if (match(A, m_BuildVector())) { | |
| auto *BuildVector = cast<VPInstruction>(A); | |
| Def->replaceAllUsesWith( | |
| BuildVector->getOperand(BuildVector->getNumOperands() - 1)); | |
| return; | |
| } | |
| if (match(A, m_Broadcast(m_VPValue(X)))) | |
| return Def->replaceAllUsesWith(X); | |
| if (isa<VPInstruction, VPReplicateRecipe>(A) && vputils::isSingleScalar(A)) | |
| return Def->replaceAllUsesWith(A); | |
| if (Plan->hasScalarVFOnly()) | |
| return Def->replaceAllUsesWith(A); | |
| } | |
| // Look through ExtractPenultimateElement (BuildVector ....). | |
| if (match(Def, m_ExtractPenultimateElement(m_BuildVector()))) { | |
| auto *BuildVector = cast<VPInstruction>(Def->getOperand(0)); | |
| Def->replaceAllUsesWith( | |
| BuildVector->getOperand(BuildVector->getNumOperands() - 2)); | |
| return; | |
| } | |
| uint64_t Idx; | |
| if (match(Def, m_ExtractElement(m_BuildVector(), m_ConstantInt(Idx)))) { | |
| auto *BuildVector = cast<VPInstruction>(Def->getOperand(0)); | |
| Def->replaceAllUsesWith(BuildVector->getOperand(Idx)); | |
| return; | |
| } | |
| if (match(Def, m_BuildVector()) && all_equal(Def->operands())) { | |
| Def->replaceAllUsesWith( | |
| Builder.createNaryOp(VPInstruction::Broadcast, Def->getOperand(0))); | |
| return; | |
| } | |
| // Replace uses of a BuildVector by users that only use its first lane with | |
| // its first operand directly. | |
| if (match(Def, m_BuildVector())) { | |
| Def->replaceUsesWithIf(Def->getOperand(0), [Def](VPUser &U, unsigned) { | |
| return U.usesFirstLaneOnly(Def); | |
| }); | |
| } | |
| // Look through broadcast of single-scalar when used as select conditions; in | |
| // that case the scalar condition can be used directly. | |
| if (match(Def, | |
| m_Select(m_Broadcast(m_VPValue(C)), m_VPValue(), m_VPValue()))) { | |
| assert(vputils::isSingleScalar(C) && | |
| "broadcast operand must be single-scalar"); | |
| Def->setOperand(0, C); | |
| return; | |
| } | |
| if (match(Def, m_Broadcast(m_VPValue(X)))) | |
| return Def->replaceUsesWithIf( | |
| X, [Def](const VPUser &U, unsigned) { return U.usesScalars(Def); }); | |
| if (isa<VPPhi, VPWidenPHIRecipe, VPHeaderPHIRecipe>(Def)) { | |
| if (Def->getNumOperands() == 1) { | |
| Def->replaceAllUsesWith(Def->getOperand(0)); | |
| return; | |
| } | |
| if (auto *Phi = dyn_cast<VPFirstOrderRecurrencePHIRecipe>(Def)) { | |
| if (all_equal(Phi->incoming_values())) | |
| Phi->replaceAllUsesWith(Phi->getOperand(0)); | |
| } | |
| return; | |
| } | |
| VPIRValue *IRV; | |
| if (Def->getNumOperands() == 1 && | |
| match(Def, m_ComputeReductionResult(m_VPIRValue(IRV)))) | |
| return Def->replaceAllUsesWith(IRV); | |
| // Some simplifications can only be applied after unrolling. Perform them | |
| // below. | |
| if (!Plan->isUnrolled()) | |
| return; | |
| // After unrolling, extract-lane may be used to extract values from multiple | |
| // scalar sources. Only simplify when extracting from a single scalar source. | |
| VPValue *LaneToExtract; | |
| if (match(Def, m_ExtractLane(m_VPValue(LaneToExtract), m_VPValue(A)))) { | |
| // Simplify extract-lane(%lane_num, %scalar_val) -> %scalar_val. | |
| if (vputils::isSingleScalar(A)) | |
| return Def->replaceAllUsesWith(A); | |
| // Replace extract-lane(0, canonical-WIDEN-INDUCTION) with the region's | |
| // scalar canonical IV. | |
| VPWidenIntOrFpInductionRecipe *WidenIV; | |
| if (match(LaneToExtract, m_ZeroInt()) && | |
| match(A, m_CanonicalWidenIV(WidenIV))) | |
| return Def->replaceAllUsesWith(WidenIV->getRegion()->getCanonicalIV()); | |
| // Simplify extract-lane with single source to extract-element. | |
| Def->replaceAllUsesWith(Builder.createNaryOp( | |
| Instruction::ExtractElement, {A, LaneToExtract}, Def->getDebugLoc())); | |
| return; | |
| } | |
| // Look for cycles where Def is of the form: | |
| // X = phi(0, IVInc) ; used only by IVInc, or by IVInc and Inc = X + Y | |
| // IVInc = X + Step ; used by X and Def | |
| // Def = IVInc + Y | |
| // Fold the increment Y into the phi's start value, replace Def with IVInc, | |
| // and if Inc exists, replace it with X. | |
| if (match(Def, m_Add(m_Add(m_VPValue(X), m_VPValue()), m_VPValue(Y))) && | |
| isa<VPIRValue>(Y) && | |
| match(X, m_VPPhi(m_ZeroInt(), m_Specific(Def->getOperand(0))))) { | |
| auto *Phi = cast<VPPhi>(X); | |
| auto *IVInc = Def->getOperand(0); | |
| if (IVInc->getNumUsers() == 2) { | |
| // If Phi has a second user (besides IVInc's defining recipe), it must | |
| // be Inc = Phi + Y for the fold to apply. | |
| auto *Inc = dyn_cast_or_null<VPSingleDefRecipe>( | |
| findUserOf(Phi, m_Add(m_Specific(Phi), m_Specific(Y)))); | |
| if (Phi->getNumUsers() == 1 || (Phi->getNumUsers() == 2 && Inc)) { | |
| Def->replaceAllUsesWith(IVInc); | |
| if (Inc) | |
| Inc->replaceAllUsesWith(Phi); | |
| Phi->setOperand(0, Y); | |
| return; | |
| } | |
| } | |
| } | |
| // Simplify unrolled VectorPointer without offset, or with zero offset, to | |
| // just the pointer operand. | |
| if (auto *VPR = dyn_cast<VPVectorPointerRecipe>(Def)) | |
| if (!VPR->getVFxPart() || match(VPR->getVFxPart(), m_ZeroInt())) | |
| return VPR->replaceAllUsesWith(VPR->getOperand(0)); | |
| // VPScalarIVSteps after unrolling can be replaced by their start value, if | |
| // the start index is zero and only the first lane 0 is demanded. | |
| if (auto *Steps = dyn_cast<VPScalarIVStepsRecipe>(Def)) { | |
| if (!Steps->getStartIndex() && vputils::onlyFirstLaneUsed(Steps)) { | |
| Steps->replaceAllUsesWith(Steps->getOperand(0)); | |
| return; | |
| } | |
| } | |
| // Simplify redundant ReductionStartVector recipes after unrolling. | |
| VPValue *StartV; | |
| if (match(Def, m_VPInstruction<VPInstruction::ReductionStartVector>( | |
| m_VPValue(StartV), m_VPValue(), m_VPValue()))) { | |
| Def->replaceUsesWithIf(StartV, [](const VPUser &U, unsigned Idx) { | |
| auto *PhiR = dyn_cast<VPReductionPHIRecipe>(&U); | |
| return PhiR && PhiR->isInLoop(); | |
| }); | |
| return; | |
| } | |
| if (Plan->getConcreteUF() == 1 && match(Def, m_ExtractLastPart(m_VPValue(A)))) | |
| return Def->replaceAllUsesWith(A); | |
| } | |
| void VPlanTransforms::simplifyRecipes(VPlan &Plan) { | |
| ReversePostOrderTraversal<VPBlockDeepTraversalWrapper<VPBlockBase *>> RPOT( | |
| Plan.getEntry()); | |
| for (VPBasicBlock *VPBB : VPBlockUtils::blocksOnly<VPBasicBlock>(RPOT)) { | |
| for (VPRecipeBase &R : make_early_inc_range(*VPBB)) | |
| if (auto *Def = dyn_cast<VPSingleDefRecipe>(&R)) | |
| simplifyRecipe(Def); | |
| } | |
| } | |
| /// Removes the permutation pattern \p Perm from any elementwise operations | |
| /// in the plan, by constructing a new permutation via \p Build. | |
| /// e.g. binop(perm(x), perm(y)) -> perm(binop(x,y)). | |
| template <typename Match_t, typename Builder> | |
| static void pullOutPermutations(VPlan &Plan, Match_t Perm, Builder Build) { | |
| for (VPBasicBlock *VPBB : VPBlockUtils::blocksOnly<VPBasicBlock>( | |
| vp_depth_first_deep(Plan.getEntry()))) { | |
| for (VPRecipeBase &R : make_early_inc_range(*VPBB)) { | |
| auto *Def = dyn_cast<VPSingleDefRecipe>(&R); | |
| if (!Def || !vputils::isElementwise(Def)) | |
| continue; | |
| // At least one of the ops must be a permutation. | |
| if (!any_of(Def->operands(), match_fn(Perm(m_VPValue())))) | |
| continue; | |
| // All operands must be permuted or a live in (splat). | |
| if (!all_of( | |
| Def->operands(), | |
| match_fn(m_CombineOr(m_OneUse(Perm(m_VPValue())), m_LiveIn())))) | |
| continue; | |
| VPValue *X; | |
| // Remove the inner permutations. | |
| for (unsigned I = 0; I < Def->getNumOperands(); I++) | |
| if (match(Def->getOperand(I), Perm(m_VPValue(X)))) | |
| Def->setOperand(I, X); | |
| VPSingleDefRecipe *Res = Build(Def); | |
| Res->insertAfter(Def); | |
| Def->replaceUsesWithIf( | |
| Res, [&Res](VPUser &U, unsigned _) { return &U != Res; }); | |
| } | |
| } | |
| } | |
| void VPlanTransforms::simplifyReverses(VPlan &Plan) { | |
| // Pull out reverses from any elementwise op. | |
| // binop(reverse(x), reverse(y)) -> reverse(binop(x,y)) | |
| pullOutPermutations( | |
| Plan, [](const auto &X) { return m_Reverse(X); }, | |
| [](auto *X) { return new VPInstruction(VPInstruction::Reverse, X); }); | |
| // reverse(reverse(x)) -> x | |
| VPValue *X; | |
| for (VPBasicBlock *VPBB : VPBlockUtils::blocksOnly<VPBasicBlock>( | |
| vp_depth_first_deep(Plan.getEntry()))) | |
| for (VPRecipeBase &R : make_early_inc_range(*VPBB)) | |
| if (match(&R, m_Reverse(m_Reverse(m_VPValue(X))))) | |
| R.getVPSingleValue()->replaceAllUsesWith(X); | |
| } | |
| /// Reassociate (headermask && x) && y -> headermask && (x && y) to allow the | |
| /// header mask to be simplified further when tail folding, e.g. in | |
| /// optimizeEVLMasks. | |
| static void reassociateHeaderMask(VPlan &Plan) { | |
| VPValue *HeaderMask = Plan.getVectorLoopRegion()->getHeaderMask(); | |
| if (!HeaderMask) | |
| return; | |
| SmallVector<VPUser *> Worklist; | |
| for (VPUser *U : HeaderMask->users()) | |
| if (match(U, m_LogicalAnd(m_Specific(HeaderMask), m_VPValue()))) | |
| append_range(Worklist, cast<VPSingleDefRecipe>(U)->users()); | |
| while (!Worklist.empty()) { | |
| auto *R = dyn_cast<VPSingleDefRecipe>(Worklist.pop_back_val()); | |
| VPValue *X, *Y; | |
| if (!R || !match(R, m_LogicalAnd( | |
| m_LogicalAnd(m_Specific(HeaderMask), m_VPValue(X)), | |
| m_VPValue(Y)))) | |
| continue; | |
| append_range(Worklist, R->users()); | |
| VPBuilder Builder(R); | |
| R->replaceAllUsesWith( | |
| Builder.createLogicalAnd(HeaderMask, Builder.createLogicalAnd(X, Y))); | |
| } | |
| } | |
| static std::optional<Instruction::BinaryOps> | |
| getUnmaskedDivRemOpcode(Intrinsic::ID ID) { | |
| switch (ID) { | |
| case Intrinsic::masked_udiv: | |
| return Instruction::UDiv; | |
| case Intrinsic::masked_sdiv: | |
| return Instruction::SDiv; | |
| case Intrinsic::masked_urem: | |
| return Instruction::URem; | |
| case Intrinsic::masked_srem: | |
| return Instruction::SRem; | |
| default: | |
| return {}; | |
| } | |
| } | |
| static void narrowToSingleScalarRecipes(VPlan &Plan) { | |
| if (Plan.hasScalarVFOnly()) | |
| return; | |
| for (VPBasicBlock *VPBB : VPBlockUtils::blocksOnly<VPBasicBlock>( | |
| vp_depth_first_deep(Plan.getEntry()))) { | |
| for (VPRecipeBase &R : make_early_inc_range(reverse(*VPBB))) { | |
| if (!isa<VPWidenRecipe, VPWidenGEPRecipe, VPReplicateRecipe, | |
| VPWidenIntrinsicRecipe>(&R)) | |
| continue; | |
| auto *RepR = dyn_cast<VPReplicateRecipe>(&R); | |
| if (RepR && (RepR->isSingleScalar() || RepR->isPredicated())) | |
| continue; | |
| auto *RepOrWidenR = cast<VPRecipeWithIRFlags>(&R); | |
| if (RepR && RepR->getOpcode() == Instruction::Store && | |
| vputils::isSingleScalar(RepR->getOperand(1))) { | |
| auto *Clone = new VPReplicateRecipe( | |
| RepOrWidenR->getUnderlyingInstr(), RepOrWidenR->operands(), | |
| true /*IsSingleScalar*/, nullptr /*Mask*/, *RepR /*Flags*/, | |
| *RepR /*Metadata*/, RepR->getDebugLoc()); | |
| Clone->insertBefore(RepOrWidenR); | |
| VPBuilder Builder(Clone); | |
| VPValue *ExtractOp = Clone->getOperand(0); | |
| if (vputils::isUniformAcrossVFsAndUFs(RepR->getOperand(1))) | |
| ExtractOp = | |
| Builder.createNaryOp(VPInstruction::ExtractLastPart, ExtractOp); | |
| ExtractOp = | |
| Builder.createNaryOp(VPInstruction::ExtractLastLane, ExtractOp); | |
| Clone->setOperand(0, ExtractOp); | |
| RepR->eraseFromParent(); | |
| continue; | |
| } | |
| // Narrow llvm.masked.{u,s}{div,rem} intrinsics with a safe divisor. | |
| if (auto *IntrR = dyn_cast<VPWidenIntrinsicRecipe>(RepOrWidenR)) { | |
| if (!vputils::onlyFirstLaneUsed(IntrR)) | |
| continue; | |
| auto Opc = getUnmaskedDivRemOpcode(IntrR->getVectorIntrinsicID()); | |
| if (!Opc) | |
| continue; | |
| VPBuilder Builder(IntrR); | |
| VPValue *SafeDivisor = Builder.createSelect( | |
| IntrR->getOperand(2), IntrR->getOperand(1), | |
| Plan.getConstantInt(IntrR->getScalarType(), 1)); | |
| VPValue *Clone = Builder.createNaryOp( | |
| *Opc, {IntrR->getOperand(0), SafeDivisor}, | |
| VPIRFlags::getDefaultFlags(*Opc), IntrR->getDebugLoc()); | |
| IntrR->replaceAllUsesWith(Clone); | |
| IntrR->eraseFromParent(); | |
| continue; | |
| } | |
| // Skip recipes that aren't single scalars. | |
| if (!vputils::isSingleScalar(RepOrWidenR)) | |
| continue; | |
| // Predicate to check if a user of Op introduces extra broadcasts. | |
| auto IntroducesBCastOf = [](const VPValue *Op) { | |
| return [Op](const VPUser *U) { | |
| if (auto *VPI = dyn_cast<VPInstruction>(U)) { | |
| if (is_contained({VPInstruction::ExtractLastLane, | |
| VPInstruction::ExtractLastPart, | |
| VPInstruction::ExtractPenultimateElement}, | |
| VPI->getOpcode())) | |
| return false; | |
| } | |
| return !U->usesScalars(Op); | |
| }; | |
| }; | |
| if (any_of(RepOrWidenR->users(), IntroducesBCastOf(RepOrWidenR)) && | |
| none_of(RepOrWidenR->operands(), [&](VPValue *Op) { | |
| if (any_of( | |
| make_filter_range(Op->users(), not_equal_to(RepOrWidenR)), | |
| IntroducesBCastOf(Op))) | |
| return false; | |
| // Non-constant live-ins require broadcasts, while constants do not | |
| // need explicit broadcasts. | |
| bool LiveInNeedsBroadcast = | |
| isa<VPIRValue>(Op) && !isa<VPConstant>(Op); | |
| auto *OpR = dyn_cast<VPReplicateRecipe>(Op); | |
| return LiveInNeedsBroadcast || (OpR && OpR->isSingleScalar()); | |
| })) | |
| continue; | |
| auto *Clone = VPBuilder::createSingleScalarOp( | |
| getOpcodeOrIntrinsicID(RepOrWidenR)->second, RepOrWidenR->operands(), | |
| /*Mask=*/nullptr, *RepOrWidenR, {}, DebugLoc::getUnknown(), | |
| RepOrWidenR->getUnderlyingInstr()); | |
| Clone->insertBefore(RepOrWidenR); | |
| RepOrWidenR->replaceAllUsesWith(Clone); | |
| if (isDeadRecipe(*RepOrWidenR)) | |
| RepOrWidenR->eraseFromParent(); | |
| } | |
| } | |
| } | |
| /// Try to see if all of \p Blend's masks share a common value logically and'ed | |
| /// and remove it from the masks. | |
| static void removeCommonBlendMask(VPBlendRecipe *Blend) { | |
| if (Blend->isNormalized()) | |
| return; | |
| VPValue *CommonEdgeMask; | |
| if (!match(Blend->getMask(0), | |
| m_LogicalAnd(m_VPValue(CommonEdgeMask), m_VPValue()))) | |
| return; | |
| for (unsigned I = 0; I < Blend->getNumIncomingValues(); I++) | |
| if (!match(Blend->getMask(I), | |
| m_LogicalAnd(m_Specific(CommonEdgeMask), m_VPValue()))) | |
| return; | |
| for (unsigned I = 0; I < Blend->getNumIncomingValues(); I++) | |
| Blend->setMask(I, Blend->getMask(I)->getDefiningRecipe()->getOperand(1)); | |
| } | |
| /// Normalize and simplify VPBlendRecipes. Should be run after simplifyRecipes | |
| /// to make sure the masks are simplified. | |
| static void simplifyBlends(VPlan &Plan) { | |
| for (VPBasicBlock *VPBB : VPBlockUtils::blocksOnly<VPBasicBlock>( | |
| vp_depth_first_shallow(Plan.getVectorLoopRegion()->getEntry()))) { | |
| for (VPRecipeBase &R : make_early_inc_range(*VPBB)) { | |
| auto *Blend = dyn_cast<VPBlendRecipe>(&R); | |
| if (!Blend) | |
| continue; | |
| removeCommonBlendMask(Blend); | |
| // Try to remove redundant blend recipes. | |
| SmallPtrSet<VPValue *, 4> UniqueValues; | |
| if (Blend->isNormalized() || !match(Blend->getMask(0), m_False())) | |
| UniqueValues.insert(Blend->getIncomingValue(0)); | |
| for (unsigned I = 1; I != Blend->getNumIncomingValues(); ++I) | |
| if (!match(Blend->getMask(I), m_False())) | |
| UniqueValues.insert(Blend->getIncomingValue(I)); | |
| if (UniqueValues.size() == 1) { | |
| Blend->replaceAllUsesWith(*UniqueValues.begin()); | |
| Blend->eraseFromParent(); | |
| continue; | |
| } | |
| if (Blend->isNormalized()) | |
| continue; | |
| // Normalize the blend so its first incoming value is used as the initial | |
| // value with the others blended into it. | |
| unsigned StartIndex = 0; | |
| for (unsigned I = 0; I != Blend->getNumIncomingValues(); ++I) { | |
| // If a value's mask is used only by the blend then is can be deadcoded. | |
| // TODO: Find the most expensive mask that can be deadcoded, or a mask | |
| // that's used by multiple blends where it can be removed from them all. | |
| VPValue *Mask = Blend->getMask(I); | |
| if (Mask->hasOneUse() && !match(Mask, m_False())) { | |
| StartIndex = I; | |
| break; | |
| } | |
| } | |
| SmallVector<VPValue *, 4> OperandsWithMask; | |
| OperandsWithMask.push_back(Blend->getIncomingValue(StartIndex)); | |
| for (unsigned I = 0; I != Blend->getNumIncomingValues(); ++I) { | |
| if (I == StartIndex) | |
| continue; | |
| OperandsWithMask.push_back(Blend->getIncomingValue(I)); | |
| OperandsWithMask.push_back(Blend->getMask(I)); | |
| } | |
| auto *NewBlend = | |
| new VPBlendRecipe(cast_or_null<PHINode>(Blend->getUnderlyingValue()), | |
| OperandsWithMask, *Blend, Blend->getDebugLoc()); | |
| NewBlend->insertBefore(&R); | |
| VPValue *DeadMask = Blend->getMask(StartIndex); | |
| Blend->replaceAllUsesWith(NewBlend); | |
| Blend->eraseFromParent(); | |
| recursivelyDeleteDeadRecipes(DeadMask); | |
| /// Simplify BLEND %a, %b, Not(%mask) -> BLEND %b, %a, %mask. | |
| VPValue *NewMask; | |
| if (NewBlend->getNumOperands() == 3 && | |
| match(NewBlend->getMask(1), m_Not(m_VPValue(NewMask)))) { | |
| VPValue *Inc0 = NewBlend->getOperand(0); | |
| VPValue *Inc1 = NewBlend->getOperand(1); | |
| VPValue *OldMask = NewBlend->getOperand(2); | |
| NewBlend->setOperand(0, Inc1); | |
| NewBlend->setOperand(1, Inc0); | |
| NewBlend->setOperand(2, NewMask); | |
| if (OldMask->user_empty()) | |
| cast<VPInstruction>(OldMask)->eraseFromParent(); | |
| } | |
| } | |
| } | |
| } | |
| /// Optimize the width of vector induction variables in \p Plan based on a known | |
| /// constant Trip Count, \p BestVF and \p BestUF. | |
| static bool optimizeVectorInductionWidthForTCAndVFUF(VPlan &Plan, | |
| ElementCount BestVF, | |
| unsigned BestUF) { | |
| // Only proceed if we have not completely removed the vector region. | |
| if (!Plan.getVectorLoopRegion()) | |
| return false; | |
| const APInt *TC; | |
| if (!BestVF.isFixed() || !match(Plan.getTripCount(), m_APInt(TC))) | |
| return false; | |
| // Calculate the minimum power-of-2 bit width that can fit the known TC, VF | |
| // and UF. Returns at least 8. | |
| auto ComputeBitWidth = [](APInt TC, uint64_t Align) { | |
| APInt AlignedTC = | |
| Align * APIntOps::RoundingUDiv(TC, APInt(TC.getBitWidth(), Align), | |
| APInt::Rounding::UP); | |
| APInt MaxVal = AlignedTC - 1; | |
| return std::max<unsigned>(PowerOf2Ceil(MaxVal.getActiveBits()), 8); | |
| }; | |
| unsigned NewBitWidth = | |
| ComputeBitWidth(*TC, BestVF.getKnownMinValue() * BestUF); | |
| LLVMContext &Ctx = Plan.getContext(); | |
| auto *NewIVTy = IntegerType::get(Ctx, NewBitWidth); | |
| bool MadeChange = false; | |
| VPBasicBlock *HeaderVPBB = Plan.getVectorLoopRegion()->getEntryBasicBlock(); | |
| for (VPRecipeBase &Phi : HeaderVPBB->phis()) { | |
| // Currently only handle canonical IVs as it is trivial to replace the start | |
| // and stop values, and we currently only perform the optimization when the | |
| // IV has a single use. | |
| VPWidenIntOrFpInductionRecipe *WideIV; | |
| if (!match(&Phi, m_CanonicalWidenIV(WideIV))) | |
| continue; | |
| if (WideIV->hasMoreThanOneUniqueUser() || | |
| NewIVTy == WideIV->getScalarType()) | |
| continue; | |
| // Currently only handle cases where the single user is a header-mask | |
| // comparison with the backedge-taken-count. | |
| VPUser *SingleUser = WideIV->getSingleUser(); | |
| if (!SingleUser || | |
| !match(SingleUser, | |
| m_ICmp(m_Specific(WideIV), | |
| m_Broadcast(m_Specific(Plan.getBackedgeTakenCount()))))) | |
| continue; | |
| // Update IV operands and comparison bound to use new narrower type. | |
| assert(!WideIV->getTruncInst() && | |
| "canonical IV is not expected to have a truncation"); | |
| auto *NewWideIV = new VPWidenIntOrFpInductionRecipe( | |
| WideIV->getPHINode(), Plan.getZero(NewIVTy), | |
| Plan.getConstantInt(NewIVTy, 1), WideIV->getVFValue(), | |
| WideIV->getInductionDescriptor(), *WideIV, WideIV->getDebugLoc()); | |
| NewWideIV->insertBefore(WideIV); | |
| auto *NewBTC = new VPWidenCastRecipe( | |
| Instruction::Trunc, Plan.getOrCreateBackedgeTakenCount(), NewIVTy, | |
| nullptr, VPIRFlags::getDefaultFlags(Instruction::Trunc)); | |
| Plan.getVectorPreheader()->appendRecipe(NewBTC); | |
| auto *Cmp = cast<VPInstruction>(WideIV->getSingleUser()); | |
| Cmp->replaceAllUsesWith( | |
| VPBuilder(Cmp).createICmp(Cmp->getPredicate(), NewWideIV, NewBTC)); | |
| MadeChange = true; | |
| } | |
| return MadeChange; | |
| } | |
| /// Return true if \p Cond is known to be true for given \p BestVF and \p | |
| /// BestUF. | |
| static bool isConditionTrueViaVFAndUF(VPValue *Cond, VPlan &Plan, | |
| ElementCount BestVF, unsigned BestUF, | |
| PredicatedScalarEvolution &PSE) { | |
| if (match(Cond, m_BinaryOr(m_VPValue(), m_VPValue()))) | |
| return any_of(Cond->getDefiningRecipe()->operands(), [&Plan, BestVF, BestUF, | |
| &PSE](VPValue *C) { | |
| return isConditionTrueViaVFAndUF(C, Plan, BestVF, BestUF, PSE); | |
| }); | |
| auto *CanIV = Plan.getVectorLoopRegion()->getCanonicalIV(); | |
| if (!match(Cond, m_SpecificICmp( | |
| CmpInst::ICMP_EQ, | |
| m_c_Add(m_Specific(CanIV), m_Specific(&Plan.getVFxUF())), | |
| m_Specific(&Plan.getVectorTripCount())))) | |
| return false; | |
| // The compare checks CanIV + VFxUF == vector trip count. The vector trip | |
| // count is not conveniently available as SCEV so far, so we compare directly | |
| // against the original trip count. This is stricter than necessary, as we | |
| // will only return true if the trip count == vector trip count. | |
| const SCEV *VectorTripCount = | |
| vputils::getSCEVExprForVPValue(&Plan.getVectorTripCount(), PSE); | |
| if (isa<SCEVCouldNotCompute>(VectorTripCount)) | |
| VectorTripCount = vputils::getSCEVExprForVPValue(Plan.getTripCount(), PSE); | |
| assert(!isa<SCEVCouldNotCompute>(VectorTripCount) && | |
| "Trip count SCEV must be computable"); | |
| ScalarEvolution &SE = *PSE.getSE(); | |
| ElementCount NumElements = BestVF.multiplyCoefficientBy(BestUF); | |
| const SCEV *C = SE.getElementCount(VectorTripCount->getType(), NumElements); | |
| return SE.isKnownPredicate(CmpInst::ICMP_EQ, VectorTripCount, C); | |
| } | |
| /// Try to replace multiple active lane masks used for control flow with | |
| /// a single, wide active lane mask instruction followed by multiple | |
| /// extract subvector intrinsics. This applies to the active lane mask | |
| /// instructions both in the loop and in the preheader. | |
| /// Incoming values of all ActiveLaneMaskPHIs are updated to use the | |
| /// new extracts from the first active lane mask, which has it's last | |
| /// operand (multiplier) set to UF. | |
| static bool tryToReplaceALMWithWideALM(VPlan &Plan, ElementCount VF, | |
| unsigned UF) { | |
| if (!EnableWideActiveLaneMask || !VF.isVector() || UF == 1) | |
| return false; | |
| VPRegionBlock *VectorRegion = Plan.getVectorLoopRegion(); | |
| VPBasicBlock *ExitingVPBB = VectorRegion->getExitingBasicBlock(); | |
| auto *Term = &ExitingVPBB->back(); | |
| using namespace llvm::VPlanPatternMatch; | |
| if (!match(Term, m_BranchOnCond(m_Not(m_ActiveLaneMask( | |
| m_VPValue(), m_VPValue(), m_VPValue()))))) | |
| return false; | |
| auto *Header = cast<VPBasicBlock>(VectorRegion->getEntry()); | |
| LLVMContext &Ctx = Plan.getContext(); | |
| auto ExtractFromALM = [&](VPInstruction *ALM, | |
| SmallVectorImpl<VPValue *> &Extracts) { | |
| DebugLoc DL = ALM->getDebugLoc(); | |
| for (unsigned Part = 0; Part < UF; ++Part) { | |
| SmallVector<VPValue *> Ops; | |
| Ops.append({ALM, Plan.getConstantInt(64, VF.getKnownMinValue() * Part)}); | |
| auto *Ext = | |
| new VPWidenIntrinsicRecipe(Intrinsic::vector_extract, Ops, | |
| IntegerType::getInt1Ty(Ctx), {}, {}, DL); | |
| Extracts[Part] = Ext; | |
| Ext->insertAfter(ALM); | |
| } | |
| }; | |
| // Create a list of each active lane mask phi, ordered by unroll part. | |
| SmallVector<VPActiveLaneMaskPHIRecipe *> Phis(UF, nullptr); | |
| for (VPRecipeBase &R : Header->phis()) { | |
| auto *Phi = dyn_cast<VPActiveLaneMaskPHIRecipe>(&R); | |
| if (!Phi) | |
| continue; | |
| VPValue *Index = nullptr; | |
| match(Phi->getBackedgeValue(), | |
| m_ActiveLaneMask(m_VPValue(Index), m_VPValue(), m_VPValue())); | |
| assert(Index && "Expected index from ActiveLaneMask instruction"); | |
| uint64_t Part; | |
| if (match(Index, | |
| m_VPInstruction<VPInstruction::CanonicalIVIncrementForPart>( | |
| m_VPValue(), m_Mul(m_VPValue(), m_ConstantInt(Part))))) | |
| Phis[Part] = Phi; | |
| else { | |
| // Anything other than a CanonicalIVIncrementForPart is part 0 | |
| assert(!match( | |
| Index, | |
| m_VPInstruction<VPInstruction::CanonicalIVIncrementForPart>())); | |
| Phis[0] = Phi; | |
| } | |
| } | |
| assert(all_of(Phis, not_equal_to(nullptr)) && | |
| "Expected one VPActiveLaneMaskPHIRecipe for each unroll part"); | |
| auto *EntryALM = cast<VPInstruction>(Phis[0]->getStartValue()); | |
| auto *LoopALM = cast<VPInstruction>(Phis[0]->getBackedgeValue()); | |
| assert((EntryALM->getOpcode() == VPInstruction::ActiveLaneMask && | |
| LoopALM->getOpcode() == VPInstruction::ActiveLaneMask) && | |
| "Expected incoming values of Phi to be ActiveLaneMasks"); | |
| // When using wide lane masks, the return type of the get.active.lane.mask | |
| // intrinsic is VF x UF (last operand). | |
| VPValue *ALMMultiplier = Plan.getConstantInt(64, UF); | |
| EntryALM->setOperand(2, ALMMultiplier); | |
| LoopALM->setOperand(2, ALMMultiplier); | |
| // Create UF x extract vectors and insert into preheader. | |
| SmallVector<VPValue *> EntryExtracts(UF); | |
| ExtractFromALM(EntryALM, EntryExtracts); | |
| // Create UF x extract vectors and insert before the loop compare & branch, | |
| // updating the compare to use the first extract. | |
| SmallVector<VPValue *> LoopExtracts(UF); | |
| ExtractFromALM(LoopALM, LoopExtracts); | |
| VPInstruction *Not = cast<VPInstruction>(Term->getOperand(0)); | |
| Not->setOperand(0, LoopExtracts[0]); | |
| // Update the incoming values of active lane mask phis. | |
| for (unsigned Part = 0; Part < UF; ++Part) { | |
| Phis[Part]->setStartValue(EntryExtracts[Part]); | |
| Phis[Part]->setBackedgeValue(LoopExtracts[Part]); | |
| } | |
| return true; | |
| } | |
| /// Try to simplify the branch condition of \p Plan. This may restrict the | |
| /// resulting plan to \p BestVF and \p BestUF. | |
| static bool simplifyBranchConditionForVFAndUF(VPlan &Plan, ElementCount BestVF, | |
| unsigned BestUF, | |
| PredicatedScalarEvolution &PSE) { | |
| VPRegionBlock *VectorRegion = Plan.getVectorLoopRegion(); | |
| VPBasicBlock *ExitingVPBB = VectorRegion->getExitingBasicBlock(); | |
| auto *Term = &ExitingVPBB->back(); | |
| VPValue *Cond; | |
| auto m_CanIVInc = m_Add(m_VPValue(), m_Specific(&Plan.getVFxUF())); | |
| // Check if the branch condition compares the canonical IV increment (for main | |
| // loop), or the canonical IV increment plus an offset (for epilog loop). | |
| if (match(Term, m_BranchOnCount( | |
| m_CombineOr(m_CanIVInc, m_c_Add(m_CanIVInc, m_LiveIn())), | |
| m_VPValue())) || | |
| match(Term, m_BranchOnCond(m_Not(m_ActiveLaneMask( | |
| m_VPValue(), m_VPValue(), m_VPValue()))))) { | |
| // Try to simplify the branch condition if VectorTC <= VF * UF when the | |
| // latch terminator is BranchOnCount or BranchOnCond(Not(ActiveLaneMask)). | |
| const SCEV *VectorTripCount = | |
| vputils::getSCEVExprForVPValue(&Plan.getVectorTripCount(), PSE); | |
| if (isa<SCEVCouldNotCompute>(VectorTripCount)) | |
| VectorTripCount = | |
| vputils::getSCEVExprForVPValue(Plan.getTripCount(), PSE); | |
| assert(!isa<SCEVCouldNotCompute>(VectorTripCount) && | |
| "Trip count SCEV must be computable"); | |
| ScalarEvolution &SE = *PSE.getSE(); | |
| ElementCount NumElements = BestVF.multiplyCoefficientBy(BestUF); | |
| const SCEV *C = SE.getElementCount(VectorTripCount->getType(), NumElements); | |
| if (!SE.isKnownPredicate(CmpInst::ICMP_ULE, VectorTripCount, C)) | |
| return false; | |
| } else if (match(Term, m_BranchOnCond(m_VPValue(Cond))) || | |
| match(Term, m_BranchOnTwoConds(m_VPValue(), m_VPValue(Cond)))) { | |
| // For BranchOnCond, check if we can prove the condition to be true using VF | |
| // and UF. | |
| if (!isConditionTrueViaVFAndUF(Cond, Plan, BestVF, BestUF, PSE)) | |
| return false; | |
| } else { | |
| return false; | |
| } | |
| // The vector loop region only executes once. Convert terminator of the | |
| // exiting block to exit in the first iteration. | |
| if (match(Term, m_BranchOnTwoConds())) { | |
| Term->setOperand(1, Plan.getTrue()); | |
| return true; | |
| } | |
| auto *BOC = new VPInstruction(VPInstruction::BranchOnCond, Plan.getTrue(), {}, | |
| {}, Term->getDebugLoc()); | |
| ExitingVPBB->appendRecipe(BOC); | |
| Term->eraseFromParent(); | |
| return true; | |
| } | |
| /// From the definition of llvm.experimental.get.vector.length, | |
| /// VPInstruction::ExplicitVectorLength(%AVL) = %AVL when %AVL <= VF. | |
| bool VPlanTransforms::simplifyKnownEVL(VPlan &Plan, ElementCount VF, | |
| PredicatedScalarEvolution &PSE) { | |
| for (VPBasicBlock *VPBB : VPBlockUtils::blocksOnly<VPBasicBlock>( | |
| vp_depth_first_deep(Plan.getEntry()))) { | |
| for (VPRecipeBase &R : *VPBB) { | |
| VPValue *AVL; | |
| if (!match(&R, m_EVL(m_VPValue(AVL)))) | |
| continue; | |
| const SCEV *AVLSCEV = vputils::getSCEVExprForVPValue(AVL, PSE); | |
| if (isa<SCEVCouldNotCompute>(AVLSCEV)) | |
| continue; | |
| ScalarEvolution &SE = *PSE.getSE(); | |
| const SCEV *VFSCEV = SE.getElementCount(AVLSCEV->getType(), VF); | |
| if (!SE.isKnownPredicate(CmpInst::ICMP_ULE, AVLSCEV, VFSCEV)) | |
| continue; | |
| VPValue *Trunc = VPBuilder(&R).createScalarZExtOrTrunc( | |
| AVL, Type::getInt32Ty(Plan.getContext()), AVLSCEV->getType(), | |
| R.getDebugLoc()); | |
| if (Trunc != AVL) { | |
| auto *TruncR = cast<VPSingleDefRecipe>(Trunc); | |
| const DataLayout &DL = Plan.getDataLayout(); | |
| if (VPValue *Folded = tryToFoldLiveIns(*TruncR, TruncR->operands(), DL)) | |
| Trunc = Folded; | |
| } | |
| R.getVPSingleValue()->replaceAllUsesWith(Trunc); | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| void VPlanTransforms::optimizeForVFAndUF(VPlan &Plan, ElementCount BestVF, | |
| unsigned BestUF, | |
| PredicatedScalarEvolution &PSE) { | |
| assert(Plan.hasVF(BestVF) && "BestVF is not available in Plan"); | |
| assert(Plan.hasUF(BestUF) && "BestUF is not available in Plan"); | |
| bool MadeChange = tryToReplaceALMWithWideALM(Plan, BestVF, BestUF); | |
| MadeChange |= simplifyBranchConditionForVFAndUF(Plan, BestVF, BestUF, PSE); | |
| MadeChange |= optimizeVectorInductionWidthForTCAndVFUF(Plan, BestVF, BestUF); | |
| if (MadeChange) { | |
| Plan.setVF(BestVF); | |
| assert(Plan.getConcreteUF() == BestUF && "BestUF must match the Plan's UF"); | |
| } | |
| } | |
| void VPlanTransforms::clearReductionWrapFlags(VPlan &Plan) { | |
| for (VPRecipeBase &R : | |
| Plan.getVectorLoopRegion()->getEntryBasicBlock()->phis()) { | |
| auto *PhiR = dyn_cast<VPReductionPHIRecipe>(&R); | |
| if (!PhiR) | |
| continue; | |
| RecurKind RK = PhiR->getRecurrenceKind(); | |
| if (RK != RecurKind::Add && RK != RecurKind::Mul && RK != RecurKind::Sub && | |
| RK != RecurKind::AddChainWithSubs) | |
| continue; | |
| for (VPUser *U : collectUsersRecursively(PhiR)) | |
| if (auto *RecWithFlags = dyn_cast<VPRecipeWithIRFlags>(U)) { | |
| RecWithFlags->dropPoisonGeneratingFlags(); | |
| } | |
| } | |
| } | |
| namespace { | |
| struct VPCSEDenseMapInfo : public DenseMapInfo<VPSingleDefRecipe *> { | |
| /// If recipe \p R will lower to a GEP with a non-i8 source element type, | |
| /// return that source element type. | |
| static Type *getGEPSourceElementType(const VPSingleDefRecipe *R) { | |
| // All VPInstructions that lower to GEPs must have the i8 source element | |
| // type (as they are PtrAdds), so we omit it. | |
| return TypeSwitch<const VPSingleDefRecipe *, Type *>(R) | |
| .Case([](const VPReplicateRecipe *I) -> Type * { | |
| if (auto *GEP = dyn_cast<GetElementPtrInst>(I->getUnderlyingValue())) | |
| return GEP->getSourceElementType(); | |
| return nullptr; | |
| }) | |
| .Case<VPVectorPointerRecipe, VPWidenGEPRecipe>( | |
| [](auto *I) { return I->getSourceElementType(); }) | |
| .Default([](auto *) { return nullptr; }); | |
| } | |
| /// Returns true if recipe \p Def can be safely handed for CSE. | |
| static bool canHandle(const VPSingleDefRecipe *Def) { | |
| // We can extend the list of handled recipes in the future, | |
| // provided we account for the data embedded in them while checking for | |
| // equality or hashing. | |
| auto C = getOpcodeOrIntrinsicID(Def); | |
| // The issue with (Insert|Extract)Value is that the index of the | |
| // insert/extract is not a proper operand in LLVM IR, and hence also not in | |
| // VPlan. | |
| if (!C || (!C->first && (C->second == Instruction::InsertValue || | |
| C->second == Instruction::ExtractValue))) | |
| return false; | |
| // During CSE, we can only handle non-memory recipes, as memory can alias. | |
| return !Def->mayReadOrWriteMemory(); | |
| } | |
| /// Hash the underlying data of \p Def. | |
| static unsigned getHashValue(const VPSingleDefRecipe *Def) { | |
| hash_code Result = hash_combine( | |
| Def->getVPRecipeID(), getOpcodeOrIntrinsicID(Def), | |
| getGEPSourceElementType(Def), Def->getScalarType(), | |
| vputils::isSingleScalar(Def), hash_combine_range(Def->operands())); | |
| if (auto *RFlags = dyn_cast<VPRecipeWithIRFlags>(Def)) | |
| if (RFlags->hasPredicate()) | |
| return hash_combine(Result, RFlags->getPredicate()); | |
| if (auto *SIVSteps = dyn_cast<VPScalarIVStepsRecipe>(Def)) | |
| return hash_combine(Result, SIVSteps->getInductionOpcode()); | |
| return Result; | |
| } | |
| /// Check equality of underlying data of \p L and \p R. | |
| static bool isEqual(const VPSingleDefRecipe *L, const VPSingleDefRecipe *R) { | |
| if (L->getVPRecipeID() != R->getVPRecipeID() || | |
| getOpcodeOrIntrinsicID(L) != getOpcodeOrIntrinsicID(R) || | |
| getGEPSourceElementType(L) != getGEPSourceElementType(R) || | |
| vputils::isSingleScalar(L) != vputils::isSingleScalar(R) || | |
| !equal(L->operands(), R->operands())) | |
| return false; | |
| assert(getOpcodeOrIntrinsicID(L) && getOpcodeOrIntrinsicID(R) && | |
| "must have valid opcode info for both recipes"); | |
| if (auto *LFlags = dyn_cast<VPRecipeWithIRFlags>(L)) | |
| if (LFlags->hasPredicate() && | |
| LFlags->getPredicate() != | |
| cast<VPRecipeWithIRFlags>(R)->getPredicate()) | |
| return false; | |
| if (auto *LSIV = dyn_cast<VPScalarIVStepsRecipe>(L)) | |
| if (LSIV->getInductionOpcode() != | |
| cast<VPScalarIVStepsRecipe>(R)->getInductionOpcode()) | |
| return false; | |
| // Phi recipes can only be equal if they are in the same VPBB, as they | |
| // implicitly depend on their predecessors. | |
| if (isa<VPWidenPHIRecipe>(L) && L->getParent() != R->getParent()) | |
| return false; | |
| // Recipes in replicate regions implicitly depend on predicate. If either | |
| // recipe is in a replicate region, only consider them equal if both have | |
| // the same parent. | |
| const VPRegionBlock *RegionL = L->getRegion(); | |
| const VPRegionBlock *RegionR = R->getRegion(); | |
| if (((RegionL && RegionL->isReplicator()) || | |
| (RegionR && RegionR->isReplicator())) && | |
| L->getParent() != R->getParent()) | |
| return false; | |
| return L->getScalarType() == R->getScalarType(); | |
| } | |
| }; | |
| } // end anonymous namespace | |
| /// Perform a common-subexpression-elimination of VPSingleDefRecipes on the \p | |
| /// Plan. | |
| void VPlanTransforms::cse(VPlan &Plan) { | |
| VPDominatorTree VPDT(Plan); | |
| DenseMap<VPSingleDefRecipe *, VPSingleDefRecipe *, VPCSEDenseMapInfo> CSEMap; | |
| ReversePostOrderTraversal<VPBlockDeepTraversalWrapper<VPBlockBase *>> RPOT( | |
| Plan.getEntry()); | |
| for (VPBasicBlock *VPBB : VPBlockUtils::blocksOnly<VPBasicBlock>(RPOT)) { | |
| for (VPRecipeBase &R : *VPBB) { | |
| auto *Def = dyn_cast<VPSingleDefRecipe>(&R); | |
| if (!Def || !VPCSEDenseMapInfo::canHandle(Def)) | |
| continue; | |
| if (VPSingleDefRecipe *V = CSEMap.lookup(Def)) { | |
| // V must dominate Def for a valid replacement. | |
| if (!VPDT.dominates(V->getParent(), VPBB)) | |
| continue; | |
| // Only keep flags present on both V and Def. | |
| if (auto *RFlags = dyn_cast<VPRecipeWithIRFlags>(V)) | |
| RFlags->intersectFlags(*cast<VPRecipeWithIRFlags>(Def)); | |
| Def->replaceAllUsesWith(V); | |
| continue; | |
| } | |
| CSEMap[Def] = Def; | |
| } | |
| } | |
| } | |
| /// Return true if we do not know how to (mechanically) hoist or sink a | |
| /// non-memory or memory recipe \p R out of a loop region. When sinking, passing | |
| /// \p Sinking = true ensures that assumes aren't sunk. | |
| static bool cannotHoistOrSinkRecipe(VPRecipeBase &R, VPBasicBlock *FirstBB, | |
| VPBasicBlock *LastBB, | |
| bool Sinking = false) { | |
| if (!isa<VPReplicateRecipe>(R) || !R.mayReadOrWriteMemory() || | |
| match(&R, m_Intrinsic<Intrinsic::assume>())) | |
| return vputils::cannotHoistOrSinkRecipe(R, Sinking); | |
| // Check that the memory operation doesn't alias between FirstBB and LastBB. | |
| auto MemLoc = vputils::getMemoryLocation(R); | |
| // TODO: Could make use of SinkStoreInfo::isNoAliasViaDistance by collecting | |
| // stores upfront, and constructing a full SinkStoreInfo. | |
| auto SinkInfo = | |
| Sinking ? std::make_optional(SinkStoreInfo(cast<VPReplicateRecipe>(R))) | |
| : std::nullopt; | |
| return !MemLoc || | |
| !canHoistOrSinkWithNoAliasCheck(*MemLoc, FirstBB, LastBB, SinkInfo); | |
| } | |
| /// Move loop-invariant recipes out of the vector loop region in \p Plan. | |
| static void licm(VPlan &Plan) { | |
| VPBasicBlock *Preheader = Plan.getVectorPreheader(); | |
| // Hoist any loop invariant recipes from the vector loop region to the | |
| // preheader. Preform a shallow traversal of the vector loop region, to | |
| // exclude recipes in replicate regions. Since the top-level blocks in the | |
| // vector loop region are guaranteed to execute if the vector pre-header is, | |
| // we don't need to check speculation safety. | |
| VPRegionBlock *LoopRegion = Plan.getVectorLoopRegion(); | |
| assert(Preheader->getSingleSuccessor() == LoopRegion && | |
| "Expected vector prehader's successor to be the vector loop region"); | |
| for (VPBasicBlock *VPBB : VPBlockUtils::blocksOnly<VPBasicBlock>( | |
| vp_depth_first_shallow(LoopRegion->getEntry()))) { | |
| for (VPRecipeBase &R : make_early_inc_range(*VPBB)) { | |
| if (cannotHoistOrSinkRecipe(R, LoopRegion->getEntryBasicBlock(), | |
| LoopRegion->getExitingBasicBlock())) | |
| continue; | |
| if (any_of(R.operands(), [](VPValue *Op) { | |
| return !Op->isDefinedOutsideLoopRegions(); | |
| })) | |
| continue; | |
| R.moveBefore(*Preheader, Preheader->end()); | |
| } | |
| } | |
| #ifndef NDEBUG | |
| VPDominatorTree VPDT(Plan); | |
| #endif | |
| // Sink recipes with no users inside the vector loop region if all users are | |
| // in the same exit block of the region. | |
| // TODO: Extend to sink recipes from inner loops. | |
| PostOrderTraversal<VPBlockShallowTraversalWrapper<VPBlockBase *>> POT( | |
| LoopRegion->getEntry()); | |
| for (VPBasicBlock *VPBB : VPBlockUtils::blocksOnly<VPBasicBlock>(POT)) { | |
| for (VPRecipeBase &R : make_early_inc_range(reverse(*VPBB))) { | |
| if (cannotHoistOrSinkRecipe(R, LoopRegion->getEntryBasicBlock(), | |
| LoopRegion->getExitingBasicBlock(), | |
| /*Sinking=*/true)) | |
| continue; | |
| if (auto *RepR = dyn_cast<VPReplicateRecipe>(&R)) { | |
| assert(!RepR->isPredicated() && | |
| "Expected prior transformation of predicated replicates to " | |
| "replicate regions"); | |
| // narrowToSingleScalarRecipes should have already maximally narrowed | |
| // replicates to single-scalar replicates. | |
| // TODO: When unrolling, replicateByVF doesn't handle sunk | |
| // non-single-scalar replicates correctly. | |
| if (!RepR->isSingleScalar()) | |
| continue; | |
| // The pointer operand of stores must be loop-invariant. | |
| if (RepR->getOpcode() == Instruction::Store && | |
| !RepR->getOperand(1)->isDefinedOutsideLoopRegions()) | |
| continue; | |
| } | |
| [[maybe_unused]] auto *RepR = dyn_cast<VPReplicateRecipe>(&R); | |
| assert((!R.mayWriteToMemory() || | |
| (RepR && RepR->getOpcode() == Instruction::Store && | |
| RepR->getOperand(1)->isDefinedOutsideLoopRegions())) && | |
| "The only recipes that may write to memory are expected to be " | |
| "stores with invariant pointer-operand"); | |
| // TODO: Use R.definedValues() instead of casting to VPSingleDefRecipe to | |
| // support recipes with multiple defined values (e.g., interleaved loads). | |
| auto *Def = cast<VPSingleDefRecipe>(&R); | |
| // Cannot sink the recipe if the user is defined in a loop region or a | |
| // non-successor of the vector loop region. Cannot sink if user is a phi | |
| // either. | |
| VPBasicBlock *SinkBB = nullptr; | |
| if (any_of(Def->users(), [&SinkBB, &LoopRegion](VPUser *U) { | |
| auto *UserR = cast<VPRecipeBase>(U); | |
| VPBasicBlock *Parent = UserR->getParent(); | |
| // TODO: Support sinking when users are in multiple blocks. | |
| if (SinkBB && SinkBB != Parent) | |
| return true; | |
| SinkBB = Parent; | |
| // TODO: If the user is a PHI node, we should check the block of | |
| // incoming value. Support PHI node users if needed. | |
| return UserR->isPhi() || Parent->getEnclosingLoopRegion() || | |
| Parent->getSinglePredecessor() != LoopRegion; | |
| })) | |
| continue; | |
| if (!SinkBB) | |
| SinkBB = cast<VPBasicBlock>(LoopRegion->getSingleSuccessor()); | |
| // TODO: This will need to be a check instead of a assert after | |
| // conditional branches in vectorized loops are supported. | |
| assert(VPDT.properlyDominates(VPBB, SinkBB) && | |
| "Defining block must dominate sink block"); | |
| // TODO: Clone the recipe if users are on multiple exit paths, instead of | |
| // just moving. | |
| Def->moveBefore(*SinkBB, SinkBB->getFirstNonPhi()); | |
| } | |
| } | |
| } | |
| void VPlanTransforms::truncateToMinimalBitwidths( | |
| VPlan &Plan, const MapVector<Instruction *, uint64_t> &MinBWs) { | |
| if (Plan.hasScalarVFOnly()) | |
| return; | |
| // Keep track of created truncates, so they can be re-used. Note that we | |
| // cannot use RAUW after creating a new truncate, as this would could make | |
| // other uses have different types for their operands, making them invalidly | |
| // typed. | |
| DenseMap<VPValue *, VPWidenCastRecipe *> ProcessedTruncs; | |
| VPBasicBlock *PH = Plan.getVectorPreheader(); | |
| for (VPBasicBlock *VPBB : VPBlockUtils::blocksOnly<VPBasicBlock>( | |
| vp_depth_first_deep(Plan.getVectorLoopRegion()))) { | |
| for (VPRecipeBase &R : make_early_inc_range(*VPBB)) { | |
| if (!isa<VPWidenRecipe, VPWidenCastRecipe, VPReplicateRecipe, | |
| VPWidenLoadRecipe, VPWidenIntrinsicRecipe>(&R)) | |
| continue; | |
| VPValue *ResultVPV = R.getVPSingleValue(); | |
| auto *UI = cast_or_null<Instruction>(ResultVPV->getUnderlyingValue()); | |
| unsigned NewResSizeInBits = MinBWs.lookup(UI); | |
| if (!NewResSizeInBits) | |
| continue; | |
| // If the value wasn't vectorized, we must maintain the original scalar | |
| // type. Skip those here, after incrementing NumProcessedRecipes. Also | |
| // skip casts which do not need to be handled explicitly here, as | |
| // redundant casts will be removed during recipe simplification. | |
| if (isa<VPReplicateRecipe, VPWidenCastRecipe>(&R)) | |
| continue; | |
| Type *OldResTy = ResultVPV->getScalarType(); | |
| unsigned OldResSizeInBits = OldResTy->getScalarSizeInBits(); | |
| assert(OldResTy->isIntegerTy() && "only integer types supported"); | |
| (void)OldResSizeInBits; | |
| auto *NewResTy = IntegerType::get(Plan.getContext(), NewResSizeInBits); | |
| // Any wrapping introduced by shrinking this operation shouldn't be | |
| // considered undefined behavior. So, we can't unconditionally copy | |
| // arithmetic wrapping flags to VPW. | |
| if (auto *VPW = dyn_cast<VPRecipeWithIRFlags>(&R)) | |
| VPW->dropPoisonGeneratingFlags(); | |
| assert((OldResSizeInBits != NewResSizeInBits || | |
| match(&R, m_ICmp(m_VPValue(), m_VPValue()))) && | |
| "Only ICmps should not need extending the result."); | |
| assert(!isa<VPWidenStoreRecipe>(&R) && "stores cannot be narrowed"); | |
| // For loads/intrinsics we don't recreate the recipe; just wrap the | |
| // original wide result in a ZExt to OldResTy. | |
| if (isa<VPWidenLoadRecipe, VPWidenIntrinsicRecipe>(&R)) { | |
| if (OldResSizeInBits != NewResSizeInBits) { | |
| auto *Ext = VPBuilder::getToInsertAfter(&R).createWidenCast( | |
| Instruction::ZExt, ResultVPV, OldResTy); | |
| ResultVPV->replaceAllUsesWith(Ext); | |
| Ext->setOperand(0, ResultVPV); | |
| } | |
| continue; | |
| } | |
| // Shrink operands by introducing truncates as needed. | |
| unsigned StartIdx = | |
| match(&R, m_Select(m_VPValue(), m_VPValue(), m_VPValue())) ? 1 : 0; | |
| SmallVector<VPValue *> NewOperands(R.operands()); | |
| for (VPValue *&Op : drop_begin(NewOperands, StartIdx)) { | |
| unsigned OpSizeInBits = Op->getScalarType()->getScalarSizeInBits(); | |
| if (OpSizeInBits == NewResSizeInBits) | |
| continue; | |
| assert(OpSizeInBits > NewResSizeInBits && "nothing to truncate"); | |
| auto [ProcessedIter, Inserted] = ProcessedTruncs.try_emplace(Op); | |
| if (Inserted) { | |
| VPBuilder Builder; | |
| if (isa<VPIRValue>(Op)) | |
| Builder.setInsertPoint(PH); | |
| else | |
| Builder.setInsertPoint(&R); | |
| ProcessedIter->second = | |
| Builder.createWidenCast(Instruction::Trunc, Op, NewResTy); | |
| } | |
| Op = ProcessedIter->second; | |
| } | |
| auto *NWR = cast<VPWidenRecipe>(&R)->cloneWithOperands(NewOperands); | |
| NWR->insertBefore(&R); | |
| // Wrap NWR in a ZExt to preserve the original wide type for downstream | |
| // users (unless this is an ICmp, which produces i1 regardless). | |
| VPValue *Replacement = NWR->getVPSingleValue(); | |
| if (OldResSizeInBits != NewResSizeInBits) | |
| Replacement = | |
| VPBuilder::getToInsertAfter(NWR) | |
| .createWidenCast(Instruction::ZExt, Replacement, OldResTy) | |
| ->getVPSingleValue(); | |
| ResultVPV->replaceAllUsesWith(Replacement); | |
| R.eraseFromParent(); | |
| } | |
| } | |
| } | |
| bool VPlanTransforms::removeBranchOnConst(VPlan &Plan, bool OnlyLatches) { | |
| std::optional<VPDominatorTree> VPDT; | |
| if (OnlyLatches) | |
| VPDT.emplace(Plan); | |
| // Collect all blocks before modifying the CFG so we can identify unreachable | |
| // ones after constant branch removal. | |
| SmallVector<VPBlockBase *> AllBlocks(vp_depth_first_shallow(Plan.getEntry())); | |
| bool SimplifiedPhi = false; | |
| for (VPBasicBlock *VPBB : VPBlockUtils::blocksOnly<VPBasicBlock>(AllBlocks)) { | |
| VPValue *Cond; | |
| // Skip blocks that are not terminated by BranchOnCond. | |
| if (VPBB->empty() || !match(&VPBB->back(), m_BranchOnCond(m_VPValue(Cond)))) | |
| continue; | |
| if (OnlyLatches && !VPBlockUtils::isLatch(VPBB, *VPDT)) | |
| continue; | |
| assert(VPBB->getNumSuccessors() == 2 && | |
| "Two successors expected for BranchOnCond"); | |
| unsigned RemovedIdx; | |
| if (match(Cond, m_True())) | |
| RemovedIdx = 1; | |
| else if (match(Cond, m_False())) | |
| RemovedIdx = 0; | |
| else | |
| continue; | |
| VPBasicBlock *RemovedSucc = | |
| cast<VPBasicBlock>(VPBB->getSuccessors()[RemovedIdx]); | |
| assert(count(RemovedSucc->getPredecessors(), VPBB) == 1 && | |
| "There must be a single edge between VPBB and its successor"); | |
| // Values coming from VPBB into phi recipes of RemovedSucc are removed from | |
| // these recipes. | |
| auto Phis = RemovedSucc->phis(); | |
| for (VPRecipeBase &R : Phis) | |
| cast<VPPhiAccessors>(&R)->removeIncomingValueFor(VPBB); | |
| SimplifiedPhi |= !std::empty(Phis); | |
| // Disconnect blocks and remove the terminator. | |
| VPBlockUtils::disconnectBlocks(VPBB, RemovedSucc); | |
| VPBB->back().eraseFromParent(); | |
| } | |
| // Compute which blocks are still reachable from the entry after constant | |
| // branch removal. | |
| SmallPtrSet<VPBlockBase *, 16> Reachable( | |
| llvm::from_range, vp_depth_first_shallow(Plan.getEntry())); | |
| // Detach all unreachable blocks from their successors, removing their recipes | |
| // and incoming values from phi recipes. | |
| VPSymbolicValue Tmp(nullptr); | |
| for (VPBlockBase *B : AllBlocks) { | |
| if (Reachable.contains(B)) | |
| continue; | |
| for (VPBlockBase *Succ : to_vector(B->successors())) { | |
| if (auto *SuccBB = dyn_cast<VPBasicBlock>(Succ)) | |
| for (VPRecipeBase &R : SuccBB->phis()) | |
| cast<VPPhiAccessors>(&R)->removeIncomingValueFor(B); | |
| VPBlockUtils::disconnectBlocks(B, Succ); | |
| } | |
| for (VPBasicBlock *DeadBB : | |
| VPBlockUtils::blocksOnly<VPBasicBlock>(vp_depth_first_deep(B))) { | |
| for (VPRecipeBase &R : make_early_inc_range(*DeadBB)) { | |
| for (VPValue *Def : R.definedValues()) | |
| Def->replaceAllUsesWith(&Tmp); | |
| R.eraseFromParent(); | |
| } | |
| } | |
| } | |
| return SimplifiedPhi; | |
| } | |
| void VPlanTransforms::optimize(VPlan &Plan) { | |
| RUN_VPLAN_PASS(removeRedundantInductionCasts, Plan); | |
| RUN_VPLAN_PASS(reassociateHeaderMask, Plan); | |
| RUN_VPLAN_PASS(simplifyRecipes, Plan); | |
| RUN_VPLAN_PASS(removeDeadRecipes, Plan); | |
| RUN_VPLAN_PASS(simplifyBlends, Plan); | |
| RUN_VPLAN_PASS(legalizeAndOptimizeInductions, Plan); | |
| RUN_VPLAN_PASS(narrowToSingleScalarRecipes, Plan); | |
| RUN_VPLAN_PASS(removeRedundantExpandSCEVRecipes, Plan); | |
| RUN_VPLAN_PASS(reassociateHeaderMask, Plan); | |
| RUN_VPLAN_PASS(simplifyRecipes, Plan); | |
| RUN_VPLAN_PASS(removeBranchOnConst, Plan, /*OnlyLatches=*/false); | |
| RUN_VPLAN_PASS(simplifyReverses, Plan); | |
| RUN_VPLAN_PASS(removeDeadRecipes, Plan); | |
| RUN_VPLAN_PASS(createAndOptimizeReplicateRegions, Plan); | |
| RUN_VPLAN_PASS(mergeBlocksIntoPredecessors, Plan); | |
| RUN_VPLAN_PASS(licm, Plan); | |
| } | |
| // Add a VPActiveLaneMaskPHIRecipe and related recipes to \p Plan and replace | |
| // the loop terminator with a branch-on-cond recipe with the negated | |
| // active-lane-mask as operand. Note that this turns the loop into an | |
| // uncountable one. Only the existing terminator is replaced, all other existing | |
| // recipes/users remain unchanged, except for poison-generating flags being | |
| // dropped from the canonical IV increment. Return the created | |
| // VPActiveLaneMaskPHIRecipe. | |
| // | |
| // The function adds the following recipes: | |
| // | |
| // vector.ph: | |
| // %EntryInc = canonical-iv-increment-for-part CanonicalIVStart | |
| // %EntryALM = active-lane-mask %EntryInc, TC | |
| // | |
| // vector.body: | |
| // ... | |
| // %P = active-lane-mask-phi [ %EntryALM, %vector.ph ], [ %ALM, %vector.body ] | |
| // ... | |
| // %InLoopInc = canonical-iv-increment-for-part CanonicalIVIncrement | |
| // %ALM = active-lane-mask %InLoopInc, TC | |
| // %Negated = Not %ALM | |
| // branch-on-cond %Negated | |
| // | |
| static VPActiveLaneMaskPHIRecipe * | |
| addVPLaneMaskPhiAndUpdateExitBranch(VPlan &Plan) { | |
| VPRegionBlock *TopRegion = Plan.getVectorLoopRegion(); | |
| VPBasicBlock *EB = TopRegion->getExitingBasicBlock(); | |
| VPValue *StartV = Plan.getZero(TopRegion->getCanonicalIVType()); | |
| auto *CanonicalIVIncrement = TopRegion->getOrCreateCanonicalIVIncrement(); | |
| // TODO: Check if dropping the flags is needed. | |
| TopRegion->clearCanonicalIVNUW(CanonicalIVIncrement); | |
| DebugLoc DL = CanonicalIVIncrement->getDebugLoc(); | |
| // We can't use StartV directly in the ActiveLaneMask VPInstruction, since | |
| // we have to take unrolling into account. Each part needs to start at | |
| // Part * VF | |
| auto *VecPreheader = Plan.getVectorPreheader(); | |
| VPBuilder Builder(VecPreheader); | |
| // Create the ActiveLaneMask instruction using the correct start values. | |
| VPValue *TC = Plan.getTripCount(); | |
| VPValue *VF = &Plan.getVF(); | |
| auto *EntryIncrement = | |
| Builder.createOverflowingOp(VPInstruction::CanonicalIVIncrementForPart, | |
| {StartV, VF}, {}, DL, "index.part.next"); | |
| // Create the active lane mask instruction in the VPlan preheader. | |
| VPValue *ALMMultiplier = | |
| Plan.getConstantInt(TopRegion->getCanonicalIVType(), 1); | |
| auto *EntryALM = Builder.createNaryOp(VPInstruction::ActiveLaneMask, | |
| {EntryIncrement, TC, ALMMultiplier}, DL, | |
| "active.lane.mask.entry"); | |
| // Now create the ActiveLaneMaskPhi recipe in the main loop using the | |
| // preheader ActiveLaneMask instruction. | |
| auto *LaneMaskPhi = | |
| new VPActiveLaneMaskPHIRecipe(EntryALM, DebugLoc::getUnknown()); | |
| auto *HeaderVPBB = TopRegion->getEntryBasicBlock(); | |
| LaneMaskPhi->insertBefore(*HeaderVPBB, HeaderVPBB->begin()); | |
| // Create the active lane mask for the next iteration of the loop before the | |
| // original terminator. | |
| VPRecipeBase *OriginalTerminator = EB->getTerminator(); | |
| Builder.setInsertPoint(OriginalTerminator); | |
| auto *InLoopIncrement = Builder.createOverflowingOp( | |
| VPInstruction::CanonicalIVIncrementForPart, | |
| {CanonicalIVIncrement, &Plan.getVF()}, {}, DL); | |
| auto *ALM = Builder.createNaryOp(VPInstruction::ActiveLaneMask, | |
| {InLoopIncrement, TC, ALMMultiplier}, DL, | |
| "active.lane.mask.next"); | |
| LaneMaskPhi->addBackedgeValue(ALM); | |
| // Replace the original terminator with BranchOnCond. We have to invert the | |
| // mask here because a true condition means jumping to the exit block. | |
| auto *NotMask = Builder.createNot(ALM, DL); | |
| Builder.createNaryOp(VPInstruction::BranchOnCond, {NotMask}, DL); | |
| OriginalTerminator->eraseFromParent(); | |
| return LaneMaskPhi; | |
| } | |
| void VPlanTransforms::materializeHeaderMask( | |
| VPlan &Plan, bool UseActiveLaneMask, bool UseActiveLaneMaskForControlFlow) { | |
| VPRegionBlock *LoopRegion = Plan.getVectorLoopRegion(); | |
| VPValue *HeaderMask = LoopRegion->getUsedHeaderMask(); | |
| if (!HeaderMask) | |
| return; | |
| if (UseActiveLaneMaskForControlFlow) { | |
| HeaderMask->replaceAllUsesWith(addVPLaneMaskPhiAndUpdateExitBranch(Plan)); | |
| return; | |
| } | |
| VPBasicBlock *Header = LoopRegion->getEntryBasicBlock(); | |
| VPBuilder Builder(Header, Header->getFirstNonPhi()); | |
| auto *WideCanonicalIV = Builder.insert(new VPWidenCanonicalIVRecipe( | |
| LoopRegion->getCanonicalIV(), | |
| VPIRFlags::WrapFlagsTy(/*HasNUW=*/true, /*HasNSW=*/false))); | |
| VPValue *Mask; | |
| if (UseActiveLaneMask) { | |
| VPValue *ALMMultiplier = | |
| Plan.getConstantInt(LoopRegion->getCanonicalIVType(), 1); | |
| Mask = Builder.createNaryOp( | |
| VPInstruction::ActiveLaneMask, | |
| {WideCanonicalIV, Plan.getTripCount(), ALMMultiplier}, nullptr, | |
| "active.lane.mask"); | |
| } else { | |
| Mask = Builder.createICmp(CmpInst::ICMP_ULE, WideCanonicalIV, | |
| Plan.getOrCreateBackedgeTakenCount()); | |
| } | |
| HeaderMask->replaceAllUsesWith(Mask); | |
| } | |
| template <typename Op0_t, typename Op1_t> struct RemoveMask_match { | |
| Op0_t In; | |
| Op1_t &Out; | |
| RemoveMask_match(const Op0_t &In, Op1_t &Out) : In(In), Out(Out) {} | |
| template <typename OpTy> bool match(OpTy *V) const { | |
| if (m_Specific(In).match(V)) { | |
| Out = nullptr; | |
| return true; | |
| } | |
| return m_LogicalAnd(m_Specific(In), m_VPValue(Out)).match(V); | |
| } | |
| }; | |
| /// Match a specific mask \p In, or a combination of it (logical-and In, Out). | |
| /// Returns the remaining part \p Out if so, or nullptr otherwise. | |
| template <typename Op0_t, typename Op1_t> | |
| static inline RemoveMask_match<Op0_t, Op1_t> m_RemoveMask(const Op0_t &In, | |
| Op1_t &Out) { | |
| return RemoveMask_match<Op0_t, Op1_t>(In, Out); | |
| } | |
| static std::optional<Intrinsic::ID> getVPDivRemIntrinsic(Intrinsic::ID IntrID) { | |
| switch (IntrID) { | |
| case Intrinsic::masked_udiv: | |
| return Intrinsic::vp_udiv; | |
| case Intrinsic::masked_sdiv: | |
| return Intrinsic::vp_sdiv; | |
| case Intrinsic::masked_urem: | |
| return Intrinsic::vp_urem; | |
| case Intrinsic::masked_srem: | |
| return Intrinsic::vp_srem; | |
| default: | |
| return std::nullopt; | |
| } | |
| } | |
| /// Try to optimize a \p CurRecipe masked by \p HeaderMask to a corresponding | |
| /// EVL-based recipe without the header mask. Returns nullptr if no EVL-based | |
| /// recipe could be created. | |
| /// \p HeaderMask Header Mask. | |
| /// \p CurRecipe Recipe to be transform. | |
| /// \p EVL The explicit vector length parameter of vector-predication | |
| /// intrinsics. | |
| static VPRecipeBase *optimizeMaskToEVL(VPValue *HeaderMask, | |
| VPRecipeBase &CurRecipe, VPValue &EVL) { | |
| VPlan *Plan = CurRecipe.getParent()->getPlan(); | |
| DebugLoc DL = CurRecipe.getDebugLoc(); | |
| VPValue *Addr, *Mask, *EndPtr; | |
| /// Adjust any end pointers so that they point to the end of EVL lanes not VF. | |
| auto AdjustEndPtr = [&CurRecipe, &EVL](VPValue *EndPtr) { | |
| auto *EVLEndPtr = cast<VPVectorEndPointerRecipe>(EndPtr)->clone(); | |
| EVLEndPtr->insertBefore(&CurRecipe); | |
| // Cast EVL (i32) to match the VF operand's type. | |
| VPValue *EVLAsVF = VPBuilder(EVLEndPtr).createScalarZExtOrTrunc( | |
| &EVL, EVLEndPtr->getOperand(1)->getScalarType(), EVL.getScalarType(), | |
| DebugLoc::getUnknown()); | |
| EVLEndPtr->setOperand(1, EVLAsVF); | |
| return EVLEndPtr; | |
| }; | |
| auto GetVPReverse = [&CurRecipe, &EVL, Plan, | |
| DL](VPValue *V) -> VPWidenIntrinsicRecipe * { | |
| if (!V) | |
| return nullptr; | |
| auto *Reverse = new VPWidenIntrinsicRecipe( | |
| Intrinsic::experimental_vp_reverse, {V, Plan->getTrue(), &EVL}, | |
| V->getScalarType(), {}, {}, DL); | |
| Reverse->insertBefore(&CurRecipe); | |
| return Reverse; | |
| }; | |
| if (match(&CurRecipe, | |
| m_MaskedLoad(m_VPValue(Addr), m_RemoveMask(HeaderMask, Mask)))) | |
| return new VPWidenLoadEVLRecipe(cast<VPWidenLoadRecipe>(CurRecipe), Addr, | |
| EVL, Mask); | |
| if (match(&CurRecipe, | |
| m_MaskedLoad(m_VPValue(EndPtr), | |
| m_Reverse(m_RemoveMask(HeaderMask, Mask)))) && | |
| match(EndPtr, m_VecEndPtr(m_VPValue(), m_Specific(&Plan->getVF())))) { | |
| Mask = GetVPReverse(Mask); | |
| Addr = AdjustEndPtr(EndPtr); | |
| auto *LoadR = new VPWidenLoadEVLRecipe(cast<VPWidenLoadRecipe>(CurRecipe), | |
| Addr, EVL, Mask); | |
| LoadR->insertBefore(&CurRecipe); | |
| VPValue *Poison = Plan->getPoison(LoadR->getScalarType()); | |
| return new VPWidenIntrinsicRecipe(Intrinsic::vector_splice_left, | |
| {Poison, LoadR, &EVL}, | |
| LoadR->getScalarType(), {}, {}, DL); | |
| } | |
| VPValue *Stride; | |
| if (match(&CurRecipe, m_Intrinsic<Intrinsic::experimental_vp_strided_load>( | |
| m_VPValue(Addr), m_VPValue(Stride), | |
| m_RemoveMask(HeaderMask, Mask), | |
| m_TruncOrSelf(m_Specific(&Plan->getVF()))))) { | |
| if (!Mask) | |
| Mask = Plan->getTrue(); | |
| auto *NewLoad = cast<VPWidenMemIntrinsicRecipe>(&CurRecipe)->clone(); | |
| NewLoad->setOperand(2, Mask); | |
| NewLoad->setOperand(3, &EVL); | |
| return NewLoad; | |
| } | |
| VPValue *StoredVal; | |
| if (match(&CurRecipe, m_MaskedStore(m_VPValue(Addr), m_VPValue(StoredVal), | |
| m_RemoveMask(HeaderMask, Mask)))) | |
| return new VPWidenStoreEVLRecipe(cast<VPWidenStoreRecipe>(CurRecipe), Addr, | |
| StoredVal, EVL, Mask); | |
| if (match(&CurRecipe, | |
| m_MaskedStore(m_VPValue(EndPtr), m_VPValue(StoredVal), | |
| m_Reverse(m_RemoveMask(HeaderMask, Mask)))) && | |
| match(EndPtr, m_VecEndPtr(m_VPValue(), m_Specific(&Plan->getVF())))) { | |
| Mask = GetVPReverse(Mask); | |
| Addr = AdjustEndPtr(EndPtr); | |
| VPValue *Poison = Plan->getPoison(StoredVal->getScalarType()); | |
| auto *SpliceR = new VPWidenIntrinsicRecipe( | |
| Intrinsic::vector_splice_right, {StoredVal, Poison, &EVL}, | |
| StoredVal->getScalarType(), {}, {}, DL); | |
| SpliceR->insertBefore(&CurRecipe); | |
| return new VPWidenStoreEVLRecipe(cast<VPWidenStoreRecipe>(CurRecipe), Addr, | |
| SpliceR, EVL, Mask); | |
| } | |
| if (auto *Rdx = dyn_cast<VPReductionRecipe>(&CurRecipe)) | |
| if (Rdx->isConditional() && | |
| match(Rdx->getCondOp(), m_RemoveMask(HeaderMask, Mask))) | |
| return new VPReductionEVLRecipe(*Rdx, EVL, Mask); | |
| if (auto *Interleave = dyn_cast<VPInterleaveRecipe>(&CurRecipe)) | |
| if (Interleave->getMask() && | |
| match(Interleave->getMask(), m_RemoveMask(HeaderMask, Mask))) | |
| return new VPInterleaveEVLRecipe(*Interleave, EVL, Mask); | |
| VPValue *LHS, *RHS; | |
| if (match(&CurRecipe, m_SelectLike(m_RemoveMask(HeaderMask, Mask), | |
| m_VPValue(LHS), m_VPValue(RHS)))) | |
| return new VPWidenIntrinsicRecipe( | |
| Intrinsic::vp_merge, {Mask ? Mask : Plan->getTrue(), LHS, RHS, &EVL}, | |
| LHS->getScalarType(), {}, {}, DL); | |
| if (match(&CurRecipe, m_LastActiveLane(m_Specific(HeaderMask)))) { | |
| Type *Ty = CurRecipe.getVPSingleValue()->getScalarType(); | |
| VPValue *ZExt = | |
| VPBuilder(&CurRecipe) | |
| .createScalarZExtOrTrunc(&EVL, Ty, EVL.getScalarType(), DL); | |
| return new VPInstruction( | |
| Instruction::Sub, {ZExt, Plan->getConstantInt(Ty, 1)}, | |
| VPIRFlags::getDefaultFlags(Instruction::Sub), {}, DL); | |
| } | |
| // lhs | (headermask && rhs) -> vp.merge rhs, true, lhs, evl | |
| if (match(&CurRecipe, | |
| m_c_BinaryOr(m_VPValue(LHS), | |
| m_LogicalAnd(m_Specific(HeaderMask), m_VPValue(RHS))))) | |
| return new VPWidenIntrinsicRecipe(Intrinsic::vp_merge, | |
| {RHS, Plan->getTrue(), LHS, &EVL}, | |
| LHS->getScalarType(), {}, {}, DL); | |
| if (auto *IntrR = dyn_cast<VPWidenIntrinsicRecipe>(&CurRecipe)) | |
| if (auto VPID = getVPDivRemIntrinsic(IntrR->getVectorIntrinsicID())) | |
| if (match(IntrR->getOperand(2), m_RemoveMask(HeaderMask, Mask))) | |
| return new VPWidenIntrinsicRecipe(*VPID, | |
| {IntrR->getOperand(0), | |
| IntrR->getOperand(1), | |
| Mask ? Mask : Plan->getTrue(), &EVL}, | |
| IntrR->getScalarType(), {}, {}, DL); | |
| return nullptr; | |
| } | |
| /// Optimize away any EVL-based header masks to VP intrinsic based recipes. | |
| /// The transforms here need to preserve the original semantics. | |
| void VPlanTransforms::optimizeEVLMasks(VPlan &Plan) { | |
| // Find the EVL-based header mask if it exists: icmp ult step-vector, EVL | |
| VPValue *HeaderMask = nullptr, *EVL = nullptr; | |
| for (VPRecipeBase &R : *Plan.getVectorLoopRegion()->getEntryBasicBlock()) { | |
| if (match(&R, m_SpecificICmp(CmpInst::ICMP_ULT, m_StepVector(), | |
| m_VPValue(EVL))) && | |
| match(EVL, m_EVL(m_VPValue()))) { | |
| HeaderMask = R.getVPSingleValue(); | |
| break; | |
| } | |
| } | |
| if (!HeaderMask) | |
| return; | |
| SmallVector<VPRecipeBase *> OldRecipes; | |
| for (VPUser *U : collectUsersRecursively(HeaderMask)) { | |
| VPRecipeBase *R = cast<VPRecipeBase>(U); | |
| if (auto *NewR = optimizeMaskToEVL(HeaderMask, *R, *EVL)) { | |
| NewR->insertBefore(R); | |
| for (auto [Old, New] : | |
| zip_equal(R->definedValues(), NewR->definedValues())) | |
| Old->replaceAllUsesWith(New); | |
| OldRecipes.push_back(R); | |
| } | |
| } | |
| // Replace remaining (HeaderMask && Mask) with vp.merge (True, Mask, | |
| // False, EVL) | |
| for (VPUser *U : collectUsersRecursively(HeaderMask)) { | |
| VPValue *Mask; | |
| if (match(U, m_LogicalAnd(m_Specific(HeaderMask), m_VPValue(Mask)))) { | |
| auto *LogicalAnd = cast<VPInstruction>(U); | |
| auto *Merge = new VPWidenIntrinsicRecipe( | |
| Intrinsic::vp_merge, {Plan.getTrue(), Mask, Plan.getFalse(), EVL}, | |
| Mask->getScalarType(), {}, {}, LogicalAnd->getDebugLoc()); | |
| Merge->insertBefore(LogicalAnd); | |
| LogicalAnd->replaceAllUsesWith(Merge); | |
| OldRecipes.push_back(LogicalAnd); | |
| } | |
| } | |
| // Pull out left splices from any elementwise op. | |
| // binop(splice.left(poison, x, evl), live-in) | |
| // -> splice.left(poison, binop(x,live-in), evl) | |
| pullOutPermutations( | |
| Plan, | |
| [&EVL](const auto &X) { | |
| return m_Intrinsic<Intrinsic::vector_splice_left>(m_Poison(), X, | |
| m_Specific(EVL)); | |
| }, | |
| [&Plan, &EVL](auto *X) { | |
| return new VPWidenIntrinsicRecipe( | |
| Intrinsic::vector_splice_left, | |
| {Plan.getPoison(X->getScalarType()), X, EVL}, X->getScalarType(), | |
| {}, {}, X->getDebugLoc()); | |
| }); | |
| // Fold the following splice patterns: | |
| // splice.right(splice.left(poison, x, evl), poison, evl) -> x | |
| // vector.reverse(splice.left(poison, x, evl)) -> vp.reverse(x, true, evl) | |
| // splice.right(vector.reverse(x), poison, evl) -> vp.reverse(x, true, evl) | |
| for (VPUser *U : collectUsersRecursively(EVL)) { | |
| auto *R = cast<VPRecipeBase>(U); | |
| // Remove potentially dead left splices from the transform above. | |
| if (match(U, m_Intrinsic<Intrinsic::vector_splice_left>()) && | |
| R->getVPSingleValue()->getNumUsers() == 0) { | |
| OldRecipes.push_back(R); | |
| continue; | |
| } | |
| VPValue *X; | |
| if (match(U, m_Intrinsic<Intrinsic::vector_splice_right>( | |
| m_Intrinsic<Intrinsic::vector_splice_left>( | |
| m_Poison(), m_VPValue(X), m_Specific(EVL)), | |
| m_Poison(), m_Specific(EVL)))) { | |
| R->getVPSingleValue()->replaceAllUsesWith(X); | |
| OldRecipes.push_back(R); | |
| continue; | |
| } | |
| if (!match(U, | |
| m_CombineOr( | |
| m_Reverse(m_Intrinsic<Intrinsic::vector_splice_left>( | |
| m_Poison(), m_VPValue(X), m_Specific(EVL))), | |
| m_Intrinsic<Intrinsic::vector_splice_right>( | |
| m_Reverse(m_VPValue(X)), m_Poison(), m_Specific(EVL))))) | |
| continue; | |
| auto *VPReverse = new VPWidenIntrinsicRecipe( | |
| Intrinsic::experimental_vp_reverse, {X, Plan.getTrue(), EVL}, | |
| X->getScalarType(), {}, {}, R->getDebugLoc()); | |
| VPReverse->insertBefore(R); | |
| R->getVPSingleValue()->replaceAllUsesWith(VPReverse); | |
| OldRecipes.push_back(R); | |
| } | |
| for (VPRecipeBase *R : reverse(OldRecipes)) { | |
| SmallVector<VPValue *> PossiblyDead(R->operands()); | |
| R->eraseFromParent(); | |
| for (VPValue *Op : PossiblyDead) | |
| recursivelyDeleteDeadRecipes(Op); | |
| } | |
| } | |
| /// After replacing the canonical IV with a EVL-based IV, fixup recipes that use | |
| /// VF to use the EVL instead to avoid incorrect updates on the penultimate | |
| /// iteration. | |
| static void fixupVFUsersForEVL(VPlan &Plan, VPValue &EVL) { | |
| VPRegionBlock *LoopRegion = Plan.getVectorLoopRegion(); | |
| VPBasicBlock *Header = LoopRegion->getEntryBasicBlock(); | |
| // EVL is i32 but VF/VFxUF are IdxTy. Convert as needed. | |
| VPValue *EVLAsIdx = | |
| VPBuilder::getToInsertAfter(EVL.getDefiningRecipe()) | |
| .createScalarZExtOrTrunc(&EVL, Plan.getVF().getScalarType(), | |
| EVL.getScalarType(), DebugLoc::getUnknown()); | |
| assert(all_of(Plan.getVF().users(), | |
| [&Plan](VPUser *U) { | |
| auto IsAllowedUser = | |
| IsaPred<VPVectorEndPointerRecipe, VPScalarIVStepsRecipe, | |
| VPWidenIntOrFpInductionRecipe, | |
| VPWidenMemIntrinsicRecipe>; | |
| if (match(U, m_Trunc(m_Specific(&Plan.getVF())))) | |
| return all_of(cast<VPSingleDefRecipe>(U)->users(), | |
| IsAllowedUser); | |
| return IsAllowedUser(U); | |
| }) && | |
| "User of VF that we can't transform to EVL."); | |
| Plan.getVF().replaceUsesWithIf(EVLAsIdx, [](VPUser &U, unsigned Idx) { | |
| return isa<VPWidenIntOrFpInductionRecipe, VPScalarIVStepsRecipe>(U); | |
| }); | |
| assert(all_of(Plan.getVFxUF().users(), | |
| match_fn(m_CombineOr( | |
| m_c_Add(m_Specific(LoopRegion->getCanonicalIV()), | |
| m_Specific(&Plan.getVFxUF())), | |
| m_Isa<VPWidenPointerInductionRecipe>()))) && | |
| "Only users of VFxUF should be VPWidenPointerInductionRecipe and the " | |
| "increment of the canonical induction."); | |
| Plan.getVFxUF().replaceUsesWithIf(EVLAsIdx, [](VPUser &U, unsigned Idx) { | |
| // Only replace uses in VPWidenPointerInductionRecipe; The increment of the | |
| // canonical induction must not be updated. | |
| return isa<VPWidenPointerInductionRecipe>(U); | |
| }); | |
| // Create a scalar phi to track the previous EVL if fixed-order recurrence is | |
| // contained. | |
| bool ContainsFORs = | |
| any_of(Header->phis(), IsaPred<VPFirstOrderRecurrencePHIRecipe>); | |
| if (ContainsFORs) { | |
| // TODO: Use VPInstruction::ExplicitVectorLength to get maximum EVL. | |
| VPValue *MaxEVL = &Plan.getVF(); | |
| // Emit VPScalarCastRecipe in preheader if VF is not a 32 bits integer. | |
| VPBuilder Builder(LoopRegion->getPreheaderVPBB()); | |
| MaxEVL = Builder.createScalarZExtOrTrunc( | |
| MaxEVL, Type::getInt32Ty(Plan.getContext()), MaxEVL->getScalarType(), | |
| DebugLoc::getUnknown()); | |
| Builder.setInsertPoint(Header, Header->getFirstNonPhi()); | |
| VPValue *PrevEVL = Builder.createScalarPhi( | |
| {MaxEVL, &EVL}, DebugLoc::getUnknown(), "prev.evl"); | |
| for (VPBasicBlock *VPBB : VPBlockUtils::blocksOnly<VPBasicBlock>( | |
| vp_depth_first_deep(Plan.getVectorLoopRegion()->getEntry()))) { | |
| for (VPRecipeBase &R : *VPBB) { | |
| VPValue *V1, *V2; | |
| if (!match(&R, | |
| m_VPInstruction<VPInstruction::FirstOrderRecurrenceSplice>( | |
| m_VPValue(V1), m_VPValue(V2)))) | |
| continue; | |
| VPValue *Imm = Plan.getOrAddLiveIn( | |
| ConstantInt::getSigned(Type::getInt32Ty(Plan.getContext()), -1)); | |
| VPWidenIntrinsicRecipe *VPSplice = new VPWidenIntrinsicRecipe( | |
| Intrinsic::experimental_vp_splice, | |
| {V1, V2, Imm, Plan.getTrue(), PrevEVL, &EVL}, | |
| R.getVPSingleValue()->getScalarType(), {}, {}, R.getDebugLoc()); | |
| VPSplice->insertBefore(&R); | |
| R.getVPSingleValue()->replaceAllUsesWith(VPSplice); | |
| } | |
| } | |
| } | |
| VPValue *HeaderMask = LoopRegion->getHeaderMask(); | |
| if (!HeaderMask) | |
| return; | |
| // Ensure that any reduction that uses a select to mask off tail lanes does so | |
| // in the vector loop, not the middle block, since EVL tail folding can have | |
| // tail elements in the penultimate iteration. | |
| assert(all_of(*Plan.getMiddleBlock(), [&Plan, HeaderMask](VPRecipeBase &R) { | |
| if (match(&R, m_ComputeReductionResult(m_Select(m_Specific(HeaderMask), | |
| m_VPValue(), m_VPValue())))) | |
| return R.getOperand(0)->getDefiningRecipe()->getRegion() == | |
| Plan.getVectorLoopRegion(); | |
| return true; | |
| })); | |
| // Replace the abstract header mask with a mask equivalent to predicating by | |
| // EVL: icmp ult step-vector, EVL | |
| VPRecipeBase *EVLR = EVL.getDefiningRecipe(); | |
| VPBuilder Builder(EVLR->getParent(), std::next(EVLR->getIterator())); | |
| Type *EVLType = EVL.getScalarType(); | |
| VPValue *EVLMask = Builder.createICmp( | |
| CmpInst::ICMP_ULT, | |
| Builder.createNaryOp(VPInstruction::StepVector, {}, EVLType), &EVL); | |
| HeaderMask->replaceAllUsesWith(EVLMask); | |
| } | |
| /// Converts a tail folded vector loop region to step by | |
| /// VPInstruction::ExplicitVectorLength elements instead of VF elements each | |
| /// iteration. | |
| /// | |
| /// - Add a VPCurrentIterationPHIRecipe and related recipes to \p Plan and | |
| /// replaces all uses of the canonical IV except for the canonical IV | |
| /// increment with a VPCurrentIterationPHIRecipe. The canonical IV is used | |
| /// only for loop iterations counting after this transformation. | |
| /// | |
| /// - The header mask is replaced with a header mask based on the EVL. | |
| /// | |
| /// - Plans with FORs have a new phi added to keep track of the EVL of the | |
| /// previous iteration, and VPFirstOrderRecurrencePHIRecipes are replaced with | |
| /// @llvm.vp.splice. | |
| /// | |
| /// The function uses the following definitions: | |
| /// %StartV is the canonical induction start value. | |
| /// | |
| /// The function adds the following recipes: | |
| /// | |
| /// vector.ph: | |
| /// ... | |
| /// | |
| /// vector.body: | |
| /// ... | |
| /// %CurrentIter = CURRENT-ITERATION-PHI [ %StartV, %vector.ph ], | |
| /// [ %NextIter, %vector.body ] | |
| /// %AVL = phi [ trip-count, %vector.ph ], [ %NextAVL, %vector.body ] | |
| /// %VPEVL = EXPLICIT-VECTOR-LENGTH %AVL | |
| /// ... | |
| /// %OpEVL = cast i32 %VPEVL to IVSize | |
| /// %NextIter = add IVSize %OpEVL, %CurrentIter | |
| /// %NextAVL = sub IVSize nuw %AVL, %OpEVL | |
| /// ... | |
| /// | |
| /// If MaxSafeElements is provided, the function adds the following recipes: | |
| /// vector.ph: | |
| /// ... | |
| /// | |
| /// vector.body: | |
| /// ... | |
| /// %CurrentIter = CURRENT-ITERATION-PHI [ %StartV, %vector.ph ], | |
| /// [ %NextIter, %vector.body ] | |
| /// %AVL = phi [ trip-count, %vector.ph ], [ %NextAVL, %vector.body ] | |
| /// %cmp = cmp ult %AVL, MaxSafeElements | |
| /// %SAFE_AVL = select %cmp, %AVL, MaxSafeElements | |
| /// %VPEVL = EXPLICIT-VECTOR-LENGTH %SAFE_AVL | |
| /// ... | |
| /// %OpEVL = cast i32 %VPEVL to IVSize | |
| /// %NextIter = add IVSize %OpEVL, %CurrentIter | |
| /// %NextAVL = sub IVSize nuw %AVL, %OpEVL | |
| /// ... | |
| /// | |
| void VPlanTransforms::addExplicitVectorLength( | |
| VPlan &Plan, const std::optional<unsigned> &MaxSafeElements) { | |
| if (Plan.hasScalarVFOnly()) | |
| return; | |
| VPRegionBlock *LoopRegion = Plan.getVectorLoopRegion(); | |
| VPBasicBlock *Header = LoopRegion->getEntryBasicBlock(); | |
| auto *CanonicalIV = LoopRegion->getCanonicalIV(); | |
| auto *CanIVTy = LoopRegion->getCanonicalIVType(); | |
| VPValue *StartV = Plan.getZero(CanIVTy); | |
| auto *CanonicalIVIncrement = LoopRegion->getOrCreateCanonicalIVIncrement(); | |
| // Create the CurrentIteration recipe in the vector loop. | |
| auto *CurrentIteration = | |
| new VPCurrentIterationPHIRecipe(StartV, DebugLoc::getUnknown()); | |
| CurrentIteration->insertBefore(*Header, Header->begin()); | |
| VPBuilder Builder(Header, Header->getFirstNonPhi()); | |
| // Create the AVL (application vector length), starting from TC -> 0 in steps | |
| // of EVL. | |
| VPPhi *AVLPhi = Builder.createScalarPhi( | |
| {Plan.getTripCount()}, DebugLoc::getCompilerGenerated(), "avl"); | |
| VPValue *AVL = AVLPhi; | |
| if (MaxSafeElements) { | |
| // Support for MaxSafeDist for correct loop emission. | |
| VPValue *AVLSafe = Plan.getConstantInt(CanIVTy, *MaxSafeElements); | |
| VPValue *Cmp = Builder.createICmp(ICmpInst::ICMP_ULT, AVL, AVLSafe); | |
| AVL = Builder.createSelect(Cmp, AVL, AVLSafe, DebugLoc::getUnknown(), | |
| "safe_avl"); | |
| } | |
| auto *VPEVL = Builder.createNaryOp(VPInstruction::ExplicitVectorLength, AVL, | |
| DebugLoc::getUnknown(), "evl"); | |
| Builder.setInsertPoint(CanonicalIVIncrement); | |
| VPValue *OpVPEVL = VPEVL; | |
| auto *I32Ty = Type::getInt32Ty(Plan.getContext()); | |
| OpVPEVL = Builder.createScalarZExtOrTrunc( | |
| OpVPEVL, CanIVTy, I32Ty, CanonicalIVIncrement->getDebugLoc()); | |
| auto *NextIter = Builder.createAdd( | |
| OpVPEVL, CurrentIteration, CanonicalIVIncrement->getDebugLoc(), | |
| "current.iteration.next", CanonicalIVIncrement->getNoWrapFlags()); | |
| CurrentIteration->addBackedgeValue(NextIter); | |
| VPValue *NextAVL = | |
| Builder.createSub(AVLPhi, OpVPEVL, DebugLoc::getCompilerGenerated(), | |
| "avl.next", {/*NUW=*/true, /*NSW=*/false}); | |
| AVLPhi->addIncoming(NextAVL); | |
| fixupVFUsersForEVL(Plan, *VPEVL); | |
| removeDeadRecipes(Plan); | |
| // Replace all uses of the canonical IV with VPCurrentIterationPHIRecipe | |
| // except for the canonical IV increment. | |
| CanonicalIV->replaceUsesWithIf(CurrentIteration, | |
| [CanonicalIVIncrement](VPUser &U, unsigned) { | |
| return &U != CanonicalIVIncrement; | |
| }); | |
| // TODO: support unroll factor > 1. | |
| Plan.setUF(1); | |
| } | |
| void VPlanTransforms::convertToVariableLengthStep(VPlan &Plan) { | |
| // Find the vector loop entry by locating VPCurrentIterationPHIRecipe. | |
| // There should be only one VPCurrentIteration in the entire plan. | |
| VPCurrentIterationPHIRecipe *CurrentIteration = nullptr; | |
| for (VPBasicBlock *VPBB : VPBlockUtils::blocksAs<VPBasicBlock>( | |
| vp_depth_first_shallow(Plan.getEntry()))) | |
| for (VPRecipeBase &R : VPBB->phis()) | |
| if (auto *PhiR = dyn_cast<VPCurrentIterationPHIRecipe>(&R)) { | |
| assert(!CurrentIteration && | |
| "Found multiple CurrentIteration. Only one expected"); | |
| CurrentIteration = PhiR; | |
| } | |
| // Early return if it is not variable-length stepping. | |
| if (!CurrentIteration) | |
| return; | |
| VPBasicBlock *HeaderVPBB = CurrentIteration->getParent(); | |
| VPValue *CurrentIterationIncr = CurrentIteration->getBackedgeValue(); | |
| // Convert CurrentIteration to concrete recipe. | |
| auto *ScalarR = | |
| VPBuilder(CurrentIteration) | |
| .createScalarPhi( | |
| {CurrentIteration->getStartValue(), CurrentIterationIncr}, | |
| CurrentIteration->getDebugLoc(), "current.iteration.iv"); | |
| CurrentIteration->replaceAllUsesWith(ScalarR); | |
| CurrentIteration->eraseFromParent(); | |
| // Replace CanonicalIVInc with CurrentIteration increment if it exists. | |
| auto *CanonicalIV = cast<VPPhi>(&*HeaderVPBB->begin()); | |
| if (auto *CanIVInc = findUserOf( | |
| CanonicalIV, m_c_Add(m_VPValue(), m_Specific(&Plan.getVFxUF())))) { | |
| cast<VPInstruction>(CanIVInc)->replaceAllUsesWith(CurrentIterationIncr); | |
| CanIVInc->eraseFromParent(); | |
| } | |
| } | |
| void VPlanTransforms::convertEVLExitCond(VPlan &Plan) { | |
| VPRegionBlock *LoopRegion = Plan.getVectorLoopRegion(); | |
| if (!LoopRegion) | |
| return; | |
| VPBasicBlock *Header = LoopRegion->getEntryBasicBlock(); | |
| if (Header->empty()) | |
| return; | |
| // The EVL IV is always at the beginning. | |
| auto *EVLPhi = dyn_cast<VPCurrentIterationPHIRecipe>(&Header->front()); | |
| if (!EVLPhi) | |
| return; | |
| // Bail if not an EVL tail folded loop. | |
| VPValue *AVL; | |
| if (!match(EVLPhi->getBackedgeValue(), | |
| m_c_Add(m_ZExtOrSelf(m_EVL(m_VPValue(AVL))), m_Specific(EVLPhi)))) | |
| return; | |
| // The AVL may be capped to a safe distance. | |
| VPValue *SafeAVL, *UnsafeAVL; | |
| if (match(AVL, | |
| m_Select(m_SpecificICmp(CmpInst::ICMP_ULT, m_VPValue(UnsafeAVL), | |
| m_VPValue(SafeAVL)), | |
| m_Deferred(UnsafeAVL), m_Deferred(SafeAVL)))) | |
| AVL = UnsafeAVL; | |
| VPValue *AVLNext; | |
| [[maybe_unused]] bool FoundAVLNext = | |
| match(AVL, m_VPInstruction<Instruction::PHI>( | |
| m_Specific(Plan.getTripCount()), m_VPValue(AVLNext))); | |
| assert(FoundAVLNext && "Didn't find AVL backedge?"); | |
| VPBasicBlock *Latch = LoopRegion->getExitingBasicBlock(); | |
| auto *LatchBr = cast<VPInstruction>(Latch->getTerminator()); | |
| if (match(LatchBr, m_BranchOnCond(m_True()))) | |
| return; | |
| VPValue *CanIVInc; | |
| [[maybe_unused]] bool FoundIncrement = match( | |
| LatchBr, | |
| m_BranchOnCond(m_SpecificCmp(CmpInst::ICMP_EQ, m_VPValue(CanIVInc), | |
| m_Specific(&Plan.getVectorTripCount())))); | |
| assert(FoundIncrement && | |
| match(CanIVInc, m_Add(m_Specific(LoopRegion->getCanonicalIV()), | |
| m_Specific(&Plan.getVFxUF()))) && | |
| "Expected BranchOnCond with ICmp comparing CanIV + VFxUF with vector " | |
| "trip count"); | |
| Type *AVLTy = AVLNext->getScalarType(); | |
| VPBuilder Builder(LatchBr); | |
| LatchBr->setOperand( | |
| 0, Builder.createICmp(CmpInst::ICMP_EQ, AVLNext, Plan.getZero(AVLTy))); | |
| } | |
| void VPlanTransforms::replaceSymbolicStrides( | |
| VPlan &Plan, PredicatedScalarEvolution &PSE, | |
| const DenseMap<Value *, const SCEV *> &StridesMap, | |
| const VPDominatorTree &VPDT) { | |
| // Replace VPValues for known constant strides guaranteed by predicated scalar | |
| // evolution that are guaranteed to be guarded by the runtime checks; that is, | |
| // blocks dominated by the vector preheader. | |
| assert(!Plan.getVectorLoopRegion() && | |
| "expected to run before loop regions are created"); | |
| VPBlockBase *Preheader = Plan.getEntry()->getSuccessors()[1]; | |
| auto CanUseVersionedStride = [&VPDT, Preheader](VPUser &U, unsigned) { | |
| auto *R = cast<VPRecipeBase>(&U); | |
| VPBlockBase *Parent = R->getParent(); | |
| return VPDT.dominates(Preheader, Parent); | |
| }; | |
| ValueToSCEVMapTy RewriteMap; | |
| for (const SCEV *Stride : StridesMap.values()) { | |
| using namespace SCEVPatternMatch; | |
| auto *StrideV = cast<SCEVUnknown>(Stride)->getValue(); | |
| const APInt *StrideConst; | |
| if (!match(PSE.getSCEV(StrideV), m_scev_APInt(StrideConst))) | |
| // Only handle constant strides for now. | |
| continue; | |
| auto *CI = Plan.getConstantInt(*StrideConst); | |
| if (VPValue *StrideVPV = Plan.getLiveIn(StrideV)) | |
| StrideVPV->replaceUsesWithIf(CI, CanUseVersionedStride); | |
| // The versioned value may not be used in the loop directly but through a | |
| // sext/zext. Add new live-ins in those cases. | |
| for (Value *U : StrideV->users()) { | |
| if (!isa<SExtInst, ZExtInst>(U)) | |
| continue; | |
| VPValue *StrideVPV = Plan.getLiveIn(U); | |
| if (!StrideVPV) | |
| continue; | |
| unsigned BW = U->getType()->getScalarSizeInBits(); | |
| APInt C = | |
| isa<SExtInst>(U) ? StrideConst->sext(BW) : StrideConst->zext(BW); | |
| VPValue *CI = Plan.getConstantInt(C); | |
| StrideVPV->replaceUsesWithIf(CI, CanUseVersionedStride); | |
| } | |
| RewriteMap[StrideV] = PSE.getSCEV(StrideV); | |
| } | |
| for (VPRecipeBase &R : *Plan.getEntry()) { | |
| auto *ExpSCEV = dyn_cast<VPExpandSCEVRecipe>(&R); | |
| if (!ExpSCEV) | |
| continue; | |
| const SCEV *ScevExpr = ExpSCEV->getSCEV(); | |
| auto *NewSCEV = | |
| SCEVParameterRewriter::rewrite(ScevExpr, *PSE.getSE(), RewriteMap); | |
| if (NewSCEV != ScevExpr) { | |
| VPValue *NewExp = vputils::getOrCreateVPValueForSCEVExpr(Plan, NewSCEV); | |
| ExpSCEV->replaceAllUsesWith(NewExp); | |
| if (Plan.getTripCount() == ExpSCEV) | |
| Plan.resetTripCount(NewExp); | |
| } | |
| } | |
| } | |
| void VPlanTransforms::dropPoisonGeneratingRecipes(VPlan &Plan) { | |
| // Collect recipes in the backward slice of `Root` that may generate a poison | |
| // value that is used after vectorization. | |
| SmallPtrSet<VPRecipeBase *, 16> Visited; | |
| auto CollectPoisonGeneratingInstrsInBackwardSlice([&](VPRecipeBase *Root) { | |
| SmallVector<VPRecipeBase *, 16> Worklist; | |
| Worklist.push_back(Root); | |
| // Traverse the backward slice of Root through its use-def chain. | |
| while (!Worklist.empty()) { | |
| VPRecipeBase *CurRec = Worklist.pop_back_val(); | |
| if (!Visited.insert(CurRec).second) | |
| continue; | |
| // Prune search if we find another recipe generating a widen memory | |
| // instruction. Widen memory instructions involved in address computation | |
| // will lead to gather/scatter instructions, which don't need to be | |
| // handled. | |
| if (isa<VPWidenMemoryRecipe, VPInterleaveRecipe, VPScalarIVStepsRecipe, | |
| VPHeaderPHIRecipe>(CurRec)) | |
| continue; | |
| // This recipe contributes to the address computation of a widen | |
| // load/store. If the underlying instruction has poison-generating flags, | |
| // drop them directly. | |
| if (auto *RecWithFlags = dyn_cast<VPRecipeWithIRFlags>(CurRec)) { | |
| VPValue *A, *B; | |
| // Dropping disjoint from an OR may yield incorrect results, as some | |
| // analysis may have converted it to an Add implicitly (e.g. SCEV used | |
| // for dependence analysis). Instead, replace it with an equivalent Add. | |
| // This is possible as all users of the disjoint OR only access lanes | |
| // where the operands are disjoint or poison otherwise. | |
| if (match(RecWithFlags, m_BinaryOr(m_VPValue(A), m_VPValue(B))) && | |
| RecWithFlags->isDisjoint()) { | |
| VPBuilder Builder(RecWithFlags); | |
| VPInstruction *New = | |
| Builder.createAdd(A, B, RecWithFlags->getDebugLoc()); | |
| New->setUnderlyingValue(RecWithFlags->getUnderlyingValue()); | |
| RecWithFlags->replaceAllUsesWith(New); | |
| RecWithFlags->eraseFromParent(); | |
| CurRec = New; | |
| } else | |
| RecWithFlags->dropPoisonGeneratingFlags(); | |
| } else { | |
| Instruction *Instr = dyn_cast_or_null<Instruction>( | |
| CurRec->getVPSingleValue()->getUnderlyingValue()); | |
| (void)Instr; | |
| assert((!Instr || !Instr->hasPoisonGeneratingFlags()) && | |
| "found instruction with poison generating flags not covered by " | |
| "VPRecipeWithIRFlags"); | |
| } | |
| // Add new definitions to the worklist. | |
| for (VPValue *Operand : CurRec->operands()) | |
| if (VPRecipeBase *OpDef = Operand->getDefiningRecipe()) | |
| Worklist.push_back(OpDef); | |
| } | |
| }); | |
| // We want to exclude the tail folding case, as we don't need to drop flags | |
| // for operations computing the first lane in this case: the first lane of the | |
| // header mask must always be true. For reverse memory accesses, the mask is | |
| // wrapped in a Reverse, which is just a permutation of the header mask, so | |
| // peel it off before checking. The header mask is still the abstract region | |
| // value at this point (materialization happens later). | |
| auto IsNotHeaderMask = [](VPValue *Mask) { | |
| return Mask && | |
| !match(Mask, m_CombineOr(m_HeaderMask(), m_Reverse(m_HeaderMask()))); | |
| }; | |
| // Traverse all the recipes in the VPlan and collect the poison-generating | |
| // recipes in the backward slice starting at the address of a VPWidenRecipe or | |
| // VPInterleaveRecipe. | |
| auto Iter = | |
| vp_depth_first_shallow(Plan.getVectorLoopRegion()->getEntryBasicBlock()); | |
| for (VPBasicBlock *VPBB : VPBlockUtils::blocksOnly<VPBasicBlock>(Iter)) { | |
| for (VPRecipeBase &Recipe : *VPBB) { | |
| if (auto *WidenRec = dyn_cast<VPWidenMemoryRecipe>(&Recipe)) { | |
| VPRecipeBase *AddrDef = WidenRec->getAddr()->getDefiningRecipe(); | |
| if (AddrDef && WidenRec->isConsecutive() && | |
| IsNotHeaderMask(WidenRec->getMask())) | |
| CollectPoisonGeneratingInstrsInBackwardSlice(AddrDef); | |
| } else if (auto *InterleaveRec = dyn_cast<VPInterleaveRecipe>(&Recipe)) { | |
| VPRecipeBase *AddrDef = InterleaveRec->getAddr()->getDefiningRecipe(); | |
| if (AddrDef && IsNotHeaderMask(InterleaveRec->getMask())) | |
| CollectPoisonGeneratingInstrsInBackwardSlice(AddrDef); | |
| } | |
| } | |
| } | |
| } | |
| void VPlanTransforms::createInterleaveGroups( | |
| VPlan &Plan, | |
| const SmallPtrSetImpl<const InterleaveGroup<Instruction> *> | |
| &InterleaveGroups, | |
| const bool &EpilogueAllowed) { | |
| if (InterleaveGroups.empty()) | |
| return; | |
| DenseMap<Instruction *, VPWidenMemoryRecipe *> IRMemberToRecipe; | |
| for (VPBasicBlock *VPBB : | |
| VPBlockUtils::blocksOnly<VPBasicBlock>(vp_depth_first_shallow( | |
| Plan.getVectorLoopRegion()->getEntryBasicBlock()))) | |
| for (VPRecipeBase &R : make_filter_range(*VPBB, [](VPRecipeBase &R) { | |
| return isa<VPWidenMemoryRecipe>(&R); | |
| })) { | |
| auto *MemR = cast<VPWidenMemoryRecipe>(&R); | |
| IRMemberToRecipe[&MemR->getIngredient()] = MemR; | |
| } | |
| // Interleave memory: for each Interleave Group we marked earlier as relevant | |
| // for this VPlan, replace the Recipes widening its memory instructions with a | |
| // single VPInterleaveRecipe at its insertion point. | |
| VPDominatorTree VPDT(Plan); | |
| for (const auto *IG : InterleaveGroups) { | |
| VPWidenMemoryRecipe *Start = nullptr; | |
| Instruction *StartMember = nullptr; | |
| for (auto *Member : IG->members()) | |
| if (VPWidenMemoryRecipe *R = IRMemberToRecipe.lookup(Member)) { | |
| StartMember = Member; | |
| Start = R; | |
| break; | |
| } | |
| if (!StartMember) // All member recipes are dead, so the group is dead. | |
| continue; | |
| VPIRMetadata InterleaveMD(*Start); | |
| SmallVector<VPValue *, 4> StoredValues; | |
| for (unsigned I = 0; I < IG->getFactor(); ++I) { | |
| Instruction *MemberI = IG->getMember(I); | |
| if (!MemberI) | |
| continue; | |
| if (VPWidenMemoryRecipe *MemoryR = IRMemberToRecipe.lookup(MemberI)) { | |
| if (auto *StoreR = dyn_cast<VPWidenStoreRecipe>(MemoryR->getAsRecipe())) | |
| StoredValues.push_back(StoreR->getStoredValue()); | |
| InterleaveMD.intersect(*MemoryR); | |
| } else { | |
| InterleaveMD.intersect(VPIRMetadata(*MemberI)); | |
| } | |
| } | |
| bool NeedsMaskForGaps = | |
| (IG->requiresScalarEpilogue() && !EpilogueAllowed) || | |
| (!StoredValues.empty() && !IG->isFull()); | |
| Instruction *IRInsertPos = IG->getInsertPos(); | |
| auto *InsertPos = IRMemberToRecipe.lookup(IRInsertPos); | |
| if (!InsertPos) { | |
| // InsertPos member is dead: find a new member that is alive. | |
| assert(isa<VPWidenLoadRecipe>(Start->getAsRecipe()) && | |
| "Dead member in non-load group?"); | |
| InsertPos = Start; | |
| for (Instruction *Member : IG->members()) | |
| if (VPWidenMemoryRecipe *MemberR = IRMemberToRecipe.lookup(Member)) | |
| if (VPDT.properlyDominates(MemberR->getAsRecipe(), | |
| InsertPos->getAsRecipe())) | |
| InsertPos = MemberR; | |
| IRInsertPos = &InsertPos->getIngredient(); | |
| } | |
| VPRecipeBase *InsertPosR = InsertPos->getAsRecipe(); | |
| GEPNoWrapFlags NW = GEPNoWrapFlags::none(); | |
| if (auto *Gep = dyn_cast<GetElementPtrInst>( | |
| getLoadStorePointerOperand(IRInsertPos)->stripPointerCasts())) | |
| NW = Gep->getNoWrapFlags().withoutNoUnsignedWrap(); | |
| // Get or create the start address for the interleave group. | |
| VPValue *Addr = Start->getAddr(); | |
| VPRecipeBase *AddrDef = Addr->getDefiningRecipe(); | |
| if (IG->getIndex(StartMember) != 0 || | |
| (AddrDef && !VPDT.properlyDominates(AddrDef, InsertPosR))) { | |
| // Either member zero's recipe is dead, or we cannot re-use the address of | |
| // member zero because it does not dominate the insert position. Instead, | |
| // use the address of the insert position and create a PtrAdd adjusting it | |
| // to the address of member zero. | |
| // TODO: Hoist Addr's defining recipe (and any operands as needed) to | |
| // InsertPos or sink loads above zero members to join it. | |
| assert(IG->getIndex(IRInsertPos) != 0 && | |
| "index of insert position shouldn't be zero"); | |
| auto &DL = IRInsertPos->getDataLayout(); | |
| APInt Offset(32, | |
| DL.getTypeAllocSize(getLoadStoreType(IRInsertPos)) * | |
| IG->getIndex(IRInsertPos), | |
| /*IsSigned=*/true); | |
| VPValue *OffsetVPV = Plan.getConstantInt(-Offset); | |
| VPBuilder B(InsertPosR); | |
| Addr = B.createNoWrapPtrAdd(InsertPos->getAddr(), OffsetVPV, NW); | |
| } | |
| // If the group is reverse, adjust the index to refer to the last vector | |
| // lane instead of the first. We adjust the index from the first vector | |
| // lane, rather than directly getting the pointer for lane VF - 1, because | |
| // the pointer operand of the interleaved access is supposed to be uniform. | |
| if (IG->isReverse()) { | |
| auto *ReversePtr = new VPVectorEndPointerRecipe( | |
| Addr, &Plan.getVF(), getLoadStoreType(IRInsertPos), | |
| -(int64_t)IG->getFactor(), NW, InsertPosR->getDebugLoc()); | |
| ReversePtr->insertBefore(InsertPosR); | |
| Addr = ReversePtr; | |
| } | |
| auto *VPIG = new VPInterleaveRecipe( | |
| IG, Addr, StoredValues, InsertPos->getMask(), NeedsMaskForGaps, | |
| InterleaveMD, InsertPosR->getDebugLoc()); | |
| VPIG->insertBefore(InsertPosR); | |
| unsigned J = 0; | |
| for (unsigned i = 0; i < IG->getFactor(); ++i) | |
| if (Instruction *Member = IG->getMember(i)) { | |
| VPWidenMemoryRecipe *MemberR = IRMemberToRecipe.lookup(Member); | |
| if (!Member->getType()->isVoidTy()) { | |
| if (MemberR) { | |
| VPValue *OriginalV = MemberR->getAsRecipe()->getVPSingleValue(); | |
| OriginalV->replaceAllUsesWith(VPIG->getVPValue(J)); | |
| } | |
| J++; | |
| } | |
| if (MemberR) | |
| MemberR->getAsRecipe()->eraseFromParent(); | |
| } | |
| } | |
| } | |
| /// Expand a VPWidenIntOrFpInduction into executable recipes, for the initial | |
| /// value, phi and backedge value. In the following example: | |
| /// | |
| /// vector.ph: | |
| /// Successor(s): vector loop | |
| /// | |
| /// <x1> vector loop: { | |
| /// vector.body: | |
| /// WIDEN-INDUCTION %i = phi %start, %step, %vf | |
| /// ... | |
| /// EMIT branch-on-count ... | |
| /// No successors | |
| /// } | |
| /// | |
| /// WIDEN-INDUCTION will get expanded to: | |
| /// | |
| /// vector.ph: | |
| /// ... | |
| /// vp<%induction.start> = ... | |
| /// vp<%induction.increment> = ... | |
| /// | |
| /// Successor(s): vector loop | |
| /// | |
| /// <x1> vector loop: { | |
| /// vector.body: | |
| /// ir<%i> = WIDEN-PHI vp<%induction.start>, vp<%vec.ind.next> | |
| /// ... | |
| /// vp<%vec.ind.next> = add ir<%i>, vp<%induction.increment> | |
| /// EMIT branch-on-count ... | |
| /// No successors | |
| /// } | |
| static void | |
| expandVPWidenIntOrFpInduction(VPWidenIntOrFpInductionRecipe *WidenIVR) { | |
| VPlan *Plan = WidenIVR->getParent()->getPlan(); | |
| VPValue *Start = WidenIVR->getStartValue(); | |
| VPValue *Step = WidenIVR->getStepValue(); | |
| VPValue *VF = WidenIVR->getVFValue(); | |
| DebugLoc DL = WidenIVR->getDebugLoc(); | |
| // The value from the original loop to which we are mapping the new induction | |
| // variable. | |
| Type *Ty = WidenIVR->getScalarType(); | |
| const InductionDescriptor &ID = WidenIVR->getInductionDescriptor(); | |
| Instruction::BinaryOps AddOp; | |
| Instruction::BinaryOps MulOp; | |
| VPIRFlags Flags = *WidenIVR; | |
| if (ID.getKind() == InductionDescriptor::IK_IntInduction) { | |
| AddOp = Instruction::Add; | |
| MulOp = Instruction::Mul; | |
| } else { | |
| AddOp = ID.getInductionOpcode(); | |
| MulOp = Instruction::FMul; | |
| } | |
| // If the phi is truncated, truncate the start and step values. | |
| VPBuilder Builder(Plan->getVectorPreheader()); | |
| Type *StepTy = Step->getScalarType(); | |
| if (Ty->getScalarSizeInBits() < StepTy->getScalarSizeInBits()) { | |
| assert(StepTy->isIntegerTy() && "Truncation requires an integer type"); | |
| Step = Builder.createScalarCast(Instruction::Trunc, Step, Ty, DL); | |
| Start = Builder.createScalarCast(Instruction::Trunc, Start, Ty, DL); | |
| StepTy = Ty; | |
| } | |
| // Construct the initial value of the vector IV in the vector loop preheader. | |
| Type *IVIntTy = | |
| IntegerType::get(Plan->getContext(), StepTy->getScalarSizeInBits()); | |
| VPValue *Init = Builder.createNaryOp(VPInstruction::StepVector, {}, IVIntTy); | |
| if (StepTy->isFloatingPointTy()) | |
| Init = Builder.createWidenCast(Instruction::UIToFP, Init, StepTy); | |
| VPValue *SplatStart = Builder.createNaryOp(VPInstruction::Broadcast, Start); | |
| VPValue *SplatStep = Builder.createNaryOp(VPInstruction::Broadcast, Step); | |
| Init = Builder.createNaryOp(MulOp, {Init, SplatStep}, Flags); | |
| Init = Builder.createNaryOp(AddOp, {SplatStart, Init}, Flags, | |
| DebugLoc::getUnknown(), "induction"); | |
| // Create the widened phi of the vector IV. | |
| auto *WidePHI = VPBuilder(WidenIVR).createWidenPhi( | |
| Init, WidenIVR->getDebugLoc(), "vec.ind"); | |
| // Create the backedge value for the vector IV. | |
| VPValue *Inc; | |
| VPValue *Prev; | |
| // If unrolled, use the increment and prev value from the operands. | |
| if (auto *SplatVF = WidenIVR->getSplatVFValue()) { | |
| Inc = SplatVF; | |
| Prev = WidenIVR->getLastUnrolledPartOperand(); | |
| } else { | |
| // Move the insertion point after the VF definition when the VF is defined | |
| // inside a loop, such as for EVL tail-folding. | |
| if (VPRecipeBase *R = VF->getDefiningRecipe()) | |
| if (R->getParent()->getEnclosingLoopRegion()) | |
| Builder.setInsertPoint(R->getParent(), std::next(R->getIterator())); | |
| // Multiply the vectorization factor by the step using integer or | |
| // floating-point arithmetic as appropriate. | |
| if (StepTy->isFloatingPointTy()) | |
| VF = Builder.createScalarCast(Instruction::CastOps::UIToFP, VF, StepTy, | |
| DL); | |
| else | |
| VF = Builder.createScalarZExtOrTrunc(VF, StepTy, VF->getScalarType(), DL); | |
| Inc = Builder.createNaryOp(MulOp, {Step, VF}, Flags); | |
| Inc = Builder.createNaryOp(VPInstruction::Broadcast, Inc); | |
| Prev = WidePHI; | |
| } | |
| VPBasicBlock *ExitingBB = Plan->getVectorLoopRegion()->getExitingBasicBlock(); | |
| Builder.setInsertPoint(ExitingBB, ExitingBB->getTerminator()->getIterator()); | |
| auto *Next = Builder.createNaryOp(AddOp, {Prev, Inc}, Flags, | |
| WidenIVR->getDebugLoc(), "vec.ind.next"); | |
| WidePHI->addIncoming(Next); | |
| WidenIVR->replaceAllUsesWith(WidePHI); | |
| } | |
| /// Expand a VPWidenPointerInductionRecipe into executable recipes, for the | |
| /// initial value, phi and backedge value. In the following example: | |
| /// | |
| /// <x1> vector loop: { | |
| /// vector.body: | |
| /// EMIT ir<%ptr.iv> = WIDEN-POINTER-INDUCTION %start, %step, %vf | |
| /// ... | |
| /// EMIT branch-on-count ... | |
| /// } | |
| /// | |
| /// WIDEN-POINTER-INDUCTION will get expanded to: | |
| /// | |
| /// <x1> vector loop: { | |
| /// vector.body: | |
| /// EMIT-SCALAR %pointer.phi = phi %start, %ptr.ind | |
| /// EMIT %mul = mul %stepvector, %step | |
| /// EMIT %vector.gep = wide-ptradd %pointer.phi, %mul | |
| /// ... | |
| /// EMIT %ptr.ind = ptradd %pointer.phi, %vf | |
| /// EMIT branch-on-count ... | |
| /// } | |
| static void expandVPWidenPointerInduction(VPWidenPointerInductionRecipe *R) { | |
| VPlan *Plan = R->getParent()->getPlan(); | |
| VPValue *Start = R->getStartValue(); | |
| VPValue *Step = R->getStepValue(); | |
| VPValue *VF = R->getVFValue(); | |
| assert(R->getInductionDescriptor().getKind() == | |
| InductionDescriptor::IK_PtrInduction && | |
| "Not a pointer induction according to InductionDescriptor!"); | |
| assert(R->getScalarType()->isPointerTy() && "Unexpected type."); | |
| assert(!R->onlyScalarsGenerated(Plan->hasScalableVF()) && | |
| "Recipe should have been replaced"); | |
| VPBuilder Builder(R); | |
| DebugLoc DL = R->getDebugLoc(); | |
| // Build a scalar pointer phi. | |
| VPPhi *ScalarPtrPhi = Builder.createScalarPhi(Start, DL, "pointer.phi"); | |
| // Create actual address geps that use the pointer phi as base and a | |
| // vectorized version of the step value (<step*0, ..., step*N>) as offset. | |
| Builder.setInsertPoint(R->getParent(), R->getParent()->getFirstNonPhi()); | |
| Type *StepTy = Step->getScalarType(); | |
| VPValue *Offset = Builder.createNaryOp(VPInstruction::StepVector, {}, StepTy); | |
| Offset = Builder.createOverflowingOp(Instruction::Mul, {Offset, Step}); | |
| VPValue *PtrAdd = | |
| Builder.createWidePtrAdd(ScalarPtrPhi, Offset, DL, "vector.gep"); | |
| R->replaceAllUsesWith(PtrAdd); | |
| // Create the backedge value for the scalar pointer phi. | |
| VPBasicBlock *ExitingBB = Plan->getVectorLoopRegion()->getExitingBasicBlock(); | |
| Builder.setInsertPoint(ExitingBB, ExitingBB->getTerminator()->getIterator()); | |
| VF = Builder.createScalarZExtOrTrunc(VF, StepTy, VF->getScalarType(), DL); | |
| VPValue *Inc = Builder.createOverflowingOp(Instruction::Mul, {Step, VF}); | |
| VPValue *InductionGEP = | |
| Builder.createPtrAdd(ScalarPtrPhi, Inc, DL, "ptr.ind"); | |
| ScalarPtrPhi->addIncoming(InductionGEP); | |
| } | |
| /// Expand a VPDerivedIVRecipe into executable recipes. | |
| static void expandVPDerivedIV(VPDerivedIVRecipe *R) { | |
| VPBuilder Builder(R); | |
| VPValue *Start = R->getStartValue(); | |
| VPValue *Step = R->getStepValue(); | |
| VPValue *Index = R->getIndex(); | |
| Type *StepTy = Step->getScalarType(); | |
| Type *IndexTy = Index->getScalarType(); | |
| Index = StepTy->isIntegerTy() | |
| ? Builder.createScalarSExtOrTrunc( | |
| Index, StepTy, IndexTy, DebugLoc::getCompilerGenerated()) | |
| : Builder.createScalarCast(Instruction::SIToFP, Index, StepTy, | |
| DebugLoc::getCompilerGenerated()); | |
| switch (R->getInductionKind()) { | |
| case InductionDescriptor::IK_IntInduction: { | |
| assert(Index->getScalarType() == Start->getScalarType() && | |
| "Index type does not match StartValue type"); | |
| return R->replaceAllUsesWith(Builder.createAdd( | |
| Start, Builder.createOverflowingOp(Instruction::Mul, {Index, Step}))); | |
| } | |
| case InductionDescriptor::IK_PtrInduction: | |
| return R->replaceAllUsesWith(Builder.createPtrAdd( | |
| Start, Builder.createOverflowingOp(Instruction::Mul, {Index, Step}))); | |
| case InductionDescriptor::IK_FpInduction: { | |
| assert(StepTy->isFloatingPointTy() && "Expected FP Step value"); | |
| const FPMathOperator *FPBinOp = R->getFPBinOp(); | |
| assert(FPBinOp && | |
| (FPBinOp->getOpcode() == Instruction::FAdd || | |
| FPBinOp->getOpcode() == Instruction::FSub) && | |
| "Original BinOp should be defined for FP induction"); | |
| FastMathFlags FMF = FPBinOp->getFastMathFlags(); | |
| VPValue *FMul = Builder.createNaryOp(Instruction::FMul, {Step, Index}, FMF); | |
| return R->replaceAllUsesWith( | |
| Builder.createNaryOp(FPBinOp->getOpcode(), {Start, FMul}, FMF)); | |
| } | |
| case InductionDescriptor::IK_NoInduction: | |
| return; | |
| } | |
| llvm_unreachable("Unhandled induction kind"); | |
| } | |
| void VPlanTransforms::dissolveLoopRegions(VPlan &Plan) { | |
| // Replace loop regions with explicity CFG. | |
| SmallVector<VPRegionBlock *> LoopRegions; | |
| for (VPRegionBlock *R : VPBlockUtils::blocksOnly<VPRegionBlock>( | |
| vp_depth_first_deep(Plan.getEntry()))) { | |
| if (!R->isReplicator()) | |
| LoopRegions.push_back(R); | |
| } | |
| for (VPRegionBlock *R : LoopRegions) | |
| R->dissolveToCFGLoop(); | |
| } | |
| void VPlanTransforms::expandBranchOnTwoConds(VPlan &Plan) { | |
| SmallVector<VPInstruction *> WorkList; | |
| // The transform runs after dissolving loop regions, so all VPBasicBlocks | |
| // terminated with BranchOnTwoConds are reached via a shallow traversal. | |
| for (VPBasicBlock *VPBB : VPBlockUtils::blocksAs<VPBasicBlock>( | |
| vp_depth_first_shallow(Plan.getEntry()))) { | |
| if (!VPBB->empty() && match(&VPBB->back(), m_BranchOnTwoConds())) | |
| WorkList.push_back(cast<VPInstruction>(&VPBB->back())); | |
| } | |
| // Expand BranchOnTwoConds instructions into explicit CFG with two new | |
| // single-condition branches: | |
| // 1. A branch that replaces BranchOnTwoConds, jumps to the first successor if | |
| // the first condition is true, and otherwise jumps to a new interim block. | |
| // 2. A branch that ends the interim block, jumps to the second successor if | |
| // the second condition is true, and otherwise jumps to the third | |
| // successor. | |
| for (VPInstruction *Br : WorkList) { | |
| assert(Br->getNumOperands() == 2 && | |
| "BranchOnTwoConds must have exactly 2 conditions"); | |
| DebugLoc DL = Br->getDebugLoc(); | |
| VPBasicBlock *BrOnTwoCondsBB = Br->getParent(); | |
| const auto Successors = to_vector(BrOnTwoCondsBB->getSuccessors()); | |
| assert(Successors.size() == 3 && | |
| "BranchOnTwoConds must have exactly 3 successors"); | |
| for (VPBlockBase *Succ : Successors) | |
| VPBlockUtils::disconnectBlocks(BrOnTwoCondsBB, Succ); | |
| VPValue *Cond0 = Br->getOperand(0); | |
| VPValue *Cond1 = Br->getOperand(1); | |
| VPBlockBase *Succ0 = Successors[0]; | |
| VPBlockBase *Succ1 = Successors[1]; | |
| VPBlockBase *Succ2 = Successors[2]; | |
| // If the successor block for both conditions is the same, then combine the | |
| // two conditions and plant a single conditional branch. | |
| if (Succ0 == Succ1) { | |
| VPBuilder Builder(Br); | |
| VPValue *Combined = Builder.createOr(Cond0, Cond1, DL); | |
| Builder.createNaryOp(VPInstruction::BranchOnCond, {Combined}, DL); | |
| VPBlockUtils::connectBlocks(BrOnTwoCondsBB, Succ0); | |
| VPBlockUtils::connectBlocks(BrOnTwoCondsBB, Succ2); | |
| Br->eraseFromParent(); | |
| continue; | |
| } | |
| assert(!Succ0->getParent() && !Succ1->getParent() && !Succ2->getParent() && | |
| !BrOnTwoCondsBB->getParent() && "regions must already be dissolved"); | |
| VPBasicBlock *InterimBB = | |
| Plan.createVPBasicBlock(BrOnTwoCondsBB->getName() + ".interim"); | |
| VPBuilder(BrOnTwoCondsBB) | |
| .createNaryOp(VPInstruction::BranchOnCond, {Cond0}, DL); | |
| VPBlockUtils::connectBlocks(BrOnTwoCondsBB, Succ0); | |
| VPBlockUtils::connectBlocks(BrOnTwoCondsBB, InterimBB); | |
| VPBuilder(InterimBB).createNaryOp(VPInstruction::BranchOnCond, {Cond1}, DL); | |
| VPBlockUtils::connectBlocks(InterimBB, Succ1); | |
| VPBlockUtils::connectBlocks(InterimBB, Succ2); | |
| Br->eraseFromParent(); | |
| } | |
| } | |
| void VPlanTransforms::convertToConcreteRecipes(VPlan &Plan) { | |
| for (VPBasicBlock *VPBB : VPBlockUtils::blocksOnly<VPBasicBlock>( | |
| vp_depth_first_deep(Plan.getEntry()))) { | |
| for (VPRecipeBase &R : make_early_inc_range(*VPBB)) { | |
| VPBuilder Builder(&R); | |
| if (auto *WidenIVR = dyn_cast<VPWidenIntOrFpInductionRecipe>(&R)) { | |
| expandVPWidenIntOrFpInduction(WidenIVR); | |
| WidenIVR->eraseFromParent(); | |
| continue; | |
| } | |
| if (auto *WidenIVR = dyn_cast<VPWidenPointerInductionRecipe>(&R)) { | |
| // If the recipe only generates scalars, scalarize it instead of | |
| // expanding it. | |
| if (WidenIVR->onlyScalarsGenerated(Plan.hasScalableVF())) { | |
| VPValue *PtrAdd = | |
| scalarizeVPWidenPointerInduction(WidenIVR, Plan, Builder); | |
| WidenIVR->replaceAllUsesWith(PtrAdd); | |
| WidenIVR->eraseFromParent(); | |
| continue; | |
| } | |
| expandVPWidenPointerInduction(WidenIVR); | |
| WidenIVR->eraseFromParent(); | |
| continue; | |
| } | |
| if (auto *DerivedIVR = dyn_cast<VPDerivedIVRecipe>(&R)) { | |
| expandVPDerivedIV(DerivedIVR); | |
| DerivedIVR->eraseFromParent(); | |
| continue; | |
| } | |
| if (auto *WideCanIV = dyn_cast<VPWidenCanonicalIVRecipe>(&R)) { | |
| VPValue *CanIV = WideCanIV->getCanonicalIV(); | |
| Type *CanIVTy = CanIV->getScalarType(); | |
| VPValue *Step = WideCanIV->getStepValue(); | |
| if (!Step) { | |
| assert(Plan.getConcreteUF() == 1 && | |
| "Expected unroller to have materialized step for UF != 1"); | |
| Step = Plan.getZero(CanIVTy); | |
| } | |
| CanIV = Builder.createNaryOp(VPInstruction::Broadcast, CanIV); | |
| Step = Builder.createNaryOp(VPInstruction::Broadcast, Step); | |
| Step = Builder.createAdd( | |
| Step, Builder.createNaryOp(VPInstruction::StepVector, {}, CanIVTy)); | |
| VPValue *CanVecIV = | |
| Builder.createAdd(CanIV, Step, WideCanIV->getDebugLoc(), "vec.iv", | |
| WideCanIV->getNoWrapFlags()); | |
| WideCanIV->replaceAllUsesWith(CanVecIV); | |
| WideCanIV->eraseFromParent(); | |
| continue; | |
| } | |
| // Expand VPBlendRecipe into VPInstruction::Select. | |
| if (auto *Blend = dyn_cast<VPBlendRecipe>(&R)) { | |
| VPValue *Select = Blend->getIncomingValue(0); | |
| for (unsigned I = 1; I != Blend->getNumIncomingValues(); ++I) | |
| Select = Builder.createSelect(Blend->getMask(I), | |
| Blend->getIncomingValue(I), Select, | |
| R.getDebugLoc(), "predphi", *Blend); | |
| Blend->replaceAllUsesWith(Select); | |
| Blend->eraseFromParent(); | |
| continue; | |
| } | |
| if (auto *VEPR = dyn_cast<VPVectorEndPointerRecipe>(&R)) { | |
| if (!VEPR->getOffset()) { | |
| assert(Plan.getConcreteUF() == 1 && | |
| "Expected unroller to have materialized offset for UF != 1"); | |
| VEPR->materializeOffset(); | |
| } | |
| continue; | |
| } | |
| if (auto *Expr = dyn_cast<VPExpressionRecipe>(&R)) { | |
| Expr->decompose(); | |
| Expr->eraseFromParent(); | |
| continue; | |
| } | |
| // Expand LastActiveLane into Not + FirstActiveLane + Sub. | |
| auto *LastActiveL = dyn_cast<VPInstruction>(&R); | |
| if (LastActiveL && | |
| LastActiveL->getOpcode() == VPInstruction::LastActiveLane) { | |
| // Create Not(Mask) for all operands. | |
| SmallVector<VPValue *, 2> NotMasks; | |
| for (VPValue *Op : LastActiveL->operands()) { | |
| VPValue *NotMask = Builder.createNot(Op, LastActiveL->getDebugLoc()); | |
| NotMasks.push_back(NotMask); | |
| } | |
| // Create FirstActiveLane on the inverted masks. | |
| VPValue *FirstInactiveLane = Builder.createFirstActiveLane( | |
| NotMasks, LastActiveL->getDebugLoc(), "first.inactive.lane"); | |
| // Subtract 1 to get the last active lane. | |
| VPValue *One = | |
| Plan.getConstantInt(FirstInactiveLane->getScalarType(), 1); | |
| VPValue *LastLane = | |
| Builder.createSub(FirstInactiveLane, One, | |
| LastActiveL->getDebugLoc(), "last.active.lane"); | |
| LastActiveL->replaceAllUsesWith(LastLane); | |
| LastActiveL->eraseFromParent(); | |
| continue; | |
| } | |
| // Lower MaskedCond with block mask to LogicalAnd. | |
| if (match(&R, m_VPInstruction<VPInstruction::MaskedCond>())) { | |
| auto *VPI = cast<VPInstruction>(&R); | |
| assert(VPI->isMasked() && | |
| "Unmasked MaskedCond should be simplified earlier"); | |
| VPI->replaceAllUsesWith(Builder.createNaryOp( | |
| VPInstruction::LogicalAnd, {VPI->getMask(), VPI->getOperand(0)})); | |
| VPI->eraseFromParent(); | |
| continue; | |
| } | |
| // Lower CanonicalIVIncrementForPart to plain Add. | |
| if (match( | |
| &R, | |
| m_VPInstruction<VPInstruction::CanonicalIVIncrementForPart>())) { | |
| auto *VPI = cast<VPInstruction>(&R); | |
| VPValue *Add = Builder.createOverflowingOp( | |
| Instruction::Add, VPI->operands(), VPI->getNoWrapFlags(), | |
| VPI->getDebugLoc()); | |
| VPI->replaceAllUsesWith(Add); | |
| VPI->eraseFromParent(); | |
| continue; | |
| } | |
| // Lower BranchOnCount to ICmp + BranchOnCond. | |
| VPValue *IV, *TC; | |
| if (match(&R, m_BranchOnCount(m_VPValue(IV), m_VPValue(TC)))) { | |
| auto *BranchOnCountInst = cast<VPInstruction>(&R); | |
| DebugLoc DL = BranchOnCountInst->getDebugLoc(); | |
| VPValue *Cond = Builder.createICmp(CmpInst::ICMP_EQ, IV, TC, DL); | |
| Builder.createNaryOp(VPInstruction::BranchOnCond, Cond, DL); | |
| BranchOnCountInst->eraseFromParent(); | |
| continue; | |
| } | |
| VPValue *VectorStep; | |
| VPValue *ScalarStep; | |
| if (!match(&R, m_VPInstruction<VPInstruction::WideIVStep>( | |
| m_VPValue(VectorStep), m_VPValue(ScalarStep)))) | |
| continue; | |
| // Expand WideIVStep. | |
| auto *VPI = cast<VPInstruction>(&R); | |
| Type *IVTy = VPI->getScalarType(); | |
| if (VectorStep->getScalarType() != IVTy) { | |
| Instruction::CastOps CastOp = IVTy->isFloatingPointTy() | |
| ? Instruction::UIToFP | |
| : Instruction::Trunc; | |
| VectorStep = Builder.createWidenCast(CastOp, VectorStep, IVTy); | |
| } | |
| assert(!match(ScalarStep, m_One()) && "Expected non-unit scalar-step"); | |
| if (ScalarStep->getScalarType() != IVTy) { | |
| ScalarStep = | |
| Builder.createWidenCast(Instruction::Trunc, ScalarStep, IVTy); | |
| } | |
| VPIRFlags Flags; | |
| unsigned MulOpc; | |
| if (IVTy->isFloatingPointTy()) { | |
| MulOpc = Instruction::FMul; | |
| Flags = VPI->getFastMathFlagsOrNone(); | |
| } else { | |
| MulOpc = Instruction::Mul; | |
| Flags = VPIRFlags::getDefaultFlags(MulOpc); | |
| } | |
| VPInstruction *Mul = Builder.createNaryOp( | |
| MulOpc, {VectorStep, ScalarStep}, Flags, R.getDebugLoc()); | |
| VectorStep = Mul; | |
| VPI->replaceAllUsesWith(VectorStep); | |
| VPI->eraseFromParent(); | |
| } | |
| } | |
| } | |
| /// Returns the VPValue representing the uncountable exit comparison used by | |
| /// AnyOf if the recipes it depends on can be traced back to live-ins and | |
| /// the addresses (in GEP/PtrAdd form) of any (non-masked) load used in | |
| /// generating the values for the comparison. The recipes are stored in | |
| /// \p Recipes. | |
| static std::optional<VPValue *> | |
| getRecipesForUncountableExit(SmallVectorImpl<VPInstruction *> &Recipes, | |
| VPBasicBlock *LatchVPBB) { | |
| // Given a plain CFG VPlan loop with countable latch exiting block | |
| // \p LatchVPBB, we're looking to match the recipes contributing to the | |
| // uncountable exit condition comparison (here, vp<%4>) back to either | |
| // live-ins or the address nodes for the load used as part of the uncountable | |
| // exit comparison so that we can either move them within the loop, or copy | |
| // them to the preheader depending on the chosen method for dealing with | |
| // stores in uncountable exit loops. | |
| // | |
| // Currently, the address of the load is restricted to a GEP with 2 operands | |
| // and a live-in base address. This constraint may be relaxed later. | |
| // | |
| // VPlan ' for UF>=1' { | |
| // Live-in vp<%0> = VF * UF | |
| // Live-in vp<%1> = vector-trip-count | |
| // Live-in ir<20> = original trip-count | |
| // | |
| // ir-bb<entry>: | |
| // Successor(s): scalar.ph, vector.ph | |
| // | |
| // vector.ph: | |
| // Successor(s): for.body | |
| // | |
| // for.body: | |
| // EMIT vp<%2> = phi ir<0>, vp<%index.next> | |
| // EMIT-SCALAR ir<%iv> = phi [ ir<0>, vector.ph ], [ ir<%iv.next>, for.inc ] | |
| // EMIT ir<%uncountable.addr> = getelementptr inbounds nuw ir<%pred>,ir<%iv> | |
| // EMIT ir<%uncountable.val> = load ir<%uncountable.addr> | |
| // EMIT ir<%uncountable.cond> = icmp sgt ir<%uncountable.val>, ir<500> | |
| // EMIT vp<%3> = masked-cond ir<%uncountable.cond> | |
| // Successor(s): for.inc | |
| // | |
| // for.inc: | |
| // EMIT ir<%iv.next> = add nuw nsw ir<%iv>, ir<1> | |
| // EMIT ir<%countable.cond> = icmp eq ir<%iv.next>, ir<20> | |
| // EMIT vp<%index.next> = add nuw vp<%2>, vp<%0> | |
| // EMIT vp<%4> = any-of ir<%3> | |
| // EMIT vp<%5> = icmp eq vp<%index.next>, vp<%1> | |
| // EMIT branch-on-two-conds vp<%4>, vp<%5> | |
| // Successor(s): middle.block, middle.block, for.body | |
| // | |
| // middle.block: | |
| // Successor(s): ir-bb<exit>, scalar.ph | |
| // | |
| // ir-bb<exit>: | |
| // No successors | |
| // | |
| // scalar.ph: | |
| // } | |
| // Find the uncountable loop exit condition. | |
| VPValue *UncountableCondition = nullptr; | |
| if (!match(LatchVPBB->getTerminator(), | |
| m_BranchOnTwoConds(m_AnyOf(m_VPValue(UncountableCondition)), | |
| m_VPValue()))) | |
| return std::nullopt; | |
| SmallVector<VPValue *, 4> Worklist; | |
| Worklist.push_back(UncountableCondition); | |
| while (!Worklist.empty()) { | |
| VPValue *V = Worklist.pop_back_val(); | |
| // Any value defined outside the loop does not need to be copied. | |
| if (V->isDefinedOutsideLoopRegions()) | |
| continue; | |
| // FIXME: Remove the single user restriction; it's here because we're | |
| // starting with the simplest set of loops we can, and multiple | |
| // users means needing to add PHI nodes in the transform. | |
| if (V->getNumUsers() > 1) | |
| return std::nullopt; | |
| VPValue *Op1, *Op2; | |
| // Walk back through recipes until we find at least one load from memory. | |
| if (match(V, m_ICmp(m_VPValue(Op1), m_VPValue(Op2)))) { | |
| Worklist.push_back(Op1); | |
| Worklist.push_back(Op2); | |
| Recipes.push_back(cast<VPInstruction>(V->getDefiningRecipe())); | |
| } else if (match(V, m_VPInstruction<Instruction::Load>(m_VPValue(Op1)))) { | |
| VPRecipeBase *GepR = Op1->getDefiningRecipe(); | |
| // Only matching base + single offset term for now. | |
| if (GepR->getNumOperands() != 2) | |
| return std::nullopt; | |
| // Matching a GEP with a loop-invariant base ptr. | |
| if (!match(GepR, m_VPInstruction<Instruction::GetElementPtr>( | |
| m_LiveIn(), m_VPValue()))) | |
| return std::nullopt; | |
| Recipes.push_back(cast<VPInstruction>(V->getDefiningRecipe())); | |
| Recipes.push_back(cast<VPInstruction>(GepR)); | |
| } else if (match(V, m_VPInstruction<VPInstruction::MaskedCond>( | |
| m_VPValue(Op1)))) { | |
| Worklist.push_back(Op1); | |
| Recipes.push_back(cast<VPInstruction>(V->getDefiningRecipe())); | |
| } else | |
| return std::nullopt; | |
| } | |
| // If we couldn't match anything, don't return the condition. It may be | |
| // defined outside the loop. | |
| if (Recipes.empty() || none_of(Recipes, [](VPInstruction *I) { | |
| return match(I, m_VPInstruction<Instruction::GetElementPtr>()); | |
| })) | |
| return std::nullopt; | |
| return UncountableCondition; | |
| } | |
| struct EarlyExitInfo { | |
| VPBasicBlock *EarlyExitingVPBB; | |
| VPIRBasicBlock *EarlyExitVPBB; | |
| VPValue *CondToExit; | |
| }; | |
| /// Update \p Plan to mask memory operations in the loop based on whether the | |
| /// early exit is taken or not. | |
| /// | |
| /// We're currently expecting to find a loop with properties similar to the | |
| /// following: | |
| /// | |
| /// for.body: | |
| /// ir<%indvars.iv> = WIDEN-INDUCTION nuw nsw ir<0>, ir<1>, vp<%0> | |
| /// EMIT ir<%arrayidx> = getelementptr inbounds nuw ir<@c>, ir<%indvars.iv> | |
| /// EMIT-SCALAR ir<%0> = load ir<%arrayidx> | |
| /// EMIT ir<%cmp1> = icmp sgt ir<%0>, ir<5> | |
| /// EMIT vp<%1> = masked-cond ir<%cmp1> | |
| /// Successor(s): if.end | |
| /// | |
| /// if.end: | |
| /// EMIT ir<%arrayidx3> = getelementptr inbounds nuw ir<@src>, ir<%indvars.iv> | |
| /// EMIT-SCALAR ir<%2> = load ir<%arrayidx3> | |
| /// EMIT ir<%add> = add nsw ir<%2>, ir<42> | |
| /// EMIT ir<%arrayidx5> = getelementptr inbounds nuw ir<@dst>, ir<%indvars.iv> | |
| /// EMIT store ir<%add>, ir<%arrayidx5> | |
| /// EMIT ir<%indvars.iv.next> = add nuw nsw ir<%indvars.iv>, ir<1> | |
| /// EMIT vp<%3> = any-of ir<%1> | |
| /// EMIT ir<%exitcond.not> = icmp eq ir<%indvars.iv.next>, ir<10000> | |
| /// EMIT branch-on-two-conds vp<%3>, ir<%exitcond.not> | |
| /// Successor(s): middle.block, middle.block, for.body | |
| /// | |
| /// We currently expect LoopVectorizationLegality to ensure that: | |
| /// * There must also be a counted exit. We will need to support speculative | |
| /// or first-faulting loads before we can remove this restriction. | |
| /// * Any stores within the loop must not alias with the load used for the | |
| /// uncountable exit. We can relax this a bit with runtime aliasing checks. | |
| /// * Other memory operations in the loop can take place before or after the | |
| /// uncountable exit, but must also be unconditional. We need to support | |
| /// combining the conditions in VPlanPredicator. | |
| /// * The loop must have a single unconditional load contributing to the | |
| /// uncountable exit comparison, and the other term must be loop-invariant. | |
| /// Improving upon this requires work in getRecipesForUncountableExit to | |
| /// handle more complex recipe graphs. | |
| static bool handleUncountableExitsWithSideEffects( | |
| VPlan &Plan, SmallVectorImpl<EarlyExitInfo> &Exits, | |
| VPBasicBlock *HeaderVPBB, VPBasicBlock *LatchVPBB, VPBasicBlock *MiddleVPBB, | |
| Loop *TheLoop, PredicatedScalarEvolution &PSE, DominatorTree &DT, | |
| AssumptionCache *AC) { | |
| // Disconnect early exiting blocks from successors, remove branches. We | |
| // currently don't support multiple uses for recipes involved in creating | |
| // the uncountable exit condition. | |
| for (auto &Exit : Exits) { | |
| if (Exit.EarlyExitingVPBB == LatchVPBB) | |
| continue; | |
| for (VPRecipeBase &R : Exit.EarlyExitVPBB->phis()) | |
| cast<VPIRPhi>(&R)->removeIncomingValueFor(Exit.EarlyExitingVPBB); | |
| Exit.EarlyExitingVPBB->getTerminator()->eraseFromParent(); | |
| VPBlockUtils::disconnectBlocks(Exit.EarlyExitingVPBB, Exit.EarlyExitVPBB); | |
| } | |
| VPDominatorTree VPDT(Plan); | |
| // We can abandon a VPlan entirely if we return false here, so we shouldn't | |
| // crash if some earlier assumptions on scalar IR don't hold for the vplan | |
| // version of the loop. | |
| SmallVector<VPInstruction *, 8> ConditionRecipes; | |
| std::optional<VPValue *> Cond = | |
| getRecipesForUncountableExit(ConditionRecipes, LatchVPBB); | |
| if (!Cond) | |
| return false; | |
| // Find load contributing to condition. | |
| // At the moment LoopVectorizationLegality only supports a single | |
| // early-exit expression with a compare and a single load that must | |
| // be unconditional. | |
| // TODO: Support more than one load. | |
| auto *Load = | |
| find_singleton<VPInstruction>(ConditionRecipes, [](auto *I, bool _) { | |
| return match(I, m_VPInstruction<Instruction::Load>(m_VPValue())) | |
| ? I | |
| : nullptr; | |
| }); | |
| assert(Load && "Couldn't find exactly one load"); | |
| // TODO: Support conditional loads for uncountable exits. | |
| assert(VPDT.dominates(Load->getParent(), LatchVPBB) && | |
| "Uncountable exit condition load is conditional."); | |
| VPInstruction *Ptr = cast<VPInstruction>(Load->getOperand(0)); | |
| // Ensure that we are guaranteed to be able to dereference the memory used | |
| // for determining the uncountable exit for the maximum possible number of | |
| // scalar iterations of the loop. | |
| // | |
| // TODO: Support first-faulting loads in cases where we don't know whether | |
| // all possible addresses are dereferenceable. | |
| { | |
| SmallVector<const SCEVPredicate *, 4> Predicates; | |
| const SCEV *PtrSCEV = vputils::getSCEVExprForVPValue(Ptr, PSE, TheLoop); | |
| const DataLayout &DL = Plan.getDataLayout(); | |
| APInt EltSize(DL.getIndexTypeSizeInBits(Ptr->getScalarType()), | |
| DL.getTypeStoreSize(Load->getScalarType()).getFixedValue()); | |
| if (!isDereferenceableAndAlignedInLoop( | |
| PtrSCEV, cast<LoadInst>(Load->getUnderlyingInstr())->getAlign(), | |
| PSE.getSE()->getConstant(EltSize), TheLoop, *PSE.getSE(), DT, AC, | |
| &Predicates)) | |
| return false; | |
| } | |
| // Check for a single GEP for the condition load to see if we can link it to | |
| // a widen IV recipe with a step of 1; we're only interested in contiguous | |
| // accesses for the condition load right now. | |
| auto *IV = cast<VPWidenInductionRecipe>(&HeaderVPBB->front()); | |
| if (!match(IV->getStartValue(), m_SpecificInt(0)) || | |
| !match(IV->getStepValue(), m_SpecificInt(1))) | |
| return false; | |
| if (!match(Ptr, m_VPInstruction<Instruction::GetElementPtr>(m_LiveIn(), | |
| m_Specific(IV)))) | |
| return false; | |
| // We want to guarantee that the uncountable exit condition (and the mask | |
| // we will generate from it) are available for all operations in the loop | |
| // that need to be masked. If the condition recipes are not already the first | |
| // recipes in the header after the last phi, move them there. | |
| auto InsertIt = HeaderVPBB->getFirstNonPhi(); | |
| while (InsertIt != HeaderVPBB->end() && | |
| is_contained(ConditionRecipes, &*InsertIt)) { | |
| erase(ConditionRecipes, &*InsertIt); | |
| InsertIt++; | |
| } | |
| for (auto *Recipe : reverse(ConditionRecipes)) | |
| Recipe->moveBefore(*HeaderVPBB, InsertIt); | |
| // Create a mask to represent all lanes that fully execute in the vector loop, | |
| // stopping short of any early exit. | |
| VPBuilder MaskBuilder(HeaderVPBB, InsertIt); | |
| VPValue *FirstActive = MaskBuilder.createFirstActiveLane(*Cond); | |
| Type *IVScalarTy = IV->getScalarType(); | |
| Type *FirstActiveTy = FirstActive->getScalarType(); | |
| VPValue *ALMMultiplier = Plan.getConstantInt(IVScalarTy, 1); | |
| VPValue *Zero = Plan.getZero(IVScalarTy); | |
| FirstActive = MaskBuilder.createScalarZExtOrTrunc(FirstActive, IVScalarTy, | |
| FirstActiveTy, DebugLoc()); | |
| VPValue *Mask = MaskBuilder.createNaryOp(VPInstruction::ActiveLaneMask, | |
| {Zero, FirstActive, ALMMultiplier}, | |
| DebugLoc(), "uncountable.exit.mask"); | |
| // Convert all other memory operations to use the mask. | |
| for (VPBasicBlock *VPBB : vp_rpo_plain_cfg_loop_body(HeaderVPBB)) | |
| for (VPRecipeBase &R : *VPBB) | |
| if (R.mayReadOrWriteMemory() && &R != Load) { | |
| // TODO: Handle conditional memory operations in the loop. | |
| if (!VPDT.dominates(R.getParent(), LatchVPBB)) | |
| return false; | |
| cast<VPInstruction>(&R)->addMask(Mask); | |
| } | |
| // Update middle block branch to compare (IV + however many lanes were active) | |
| // against the full trip count, since we may be exiting the vector loop early. | |
| // If we didn't take an early exit, we should get the equivalent of VF from | |
| // the FirstActiveLane. | |
| assert(match(MiddleVPBB->getTerminator(), m_BranchOnCond()) && | |
| "Expected BranchOnCond terminator for MiddleVPBB"); | |
| VPBuilder MiddleBuilder(MiddleVPBB->getTerminator()); | |
| VPValue *ScalarIV = MiddleBuilder.createNaryOp(VPInstruction::ExtractLane, | |
| {Zero, IV}, DebugLoc()); | |
| VPValue *ExitIV = MiddleBuilder.createAdd(ScalarIV, FirstActive); | |
| VPValue *FullTC = | |
| MiddleBuilder.createICmp(CmpInst::ICMP_EQ, ExitIV, Plan.getTripCount()); | |
| MiddleVPBB->getTerminator()->setOperand(0, FullTC); | |
| // Update resume phi in scalar.ph. | |
| VPBasicBlock *ScalarPH = Plan.getScalarPreheader(); | |
| auto Phis = ScalarPH->phis(); | |
| // TODO: Handle more than one Phi; re-derive from IV. | |
| // TODO: Handle reductions. | |
| if (range_size(Phis) != 1) | |
| return false; | |
| VPPhi *ContinueIV = cast<VPPhi>(Phis.begin()); | |
| // Make sure we're referring to the same IV. | |
| assert( | |
| match(ContinueIV->getOperand(0), | |
| m_VPInstruction<VPInstruction::ExitingIVValue>(m_Specific(IV))) && | |
| "Continuing from different IV"); | |
| ContinueIV->setOperand(0, ExitIV); | |
| return true; | |
| } | |
| bool VPlanTransforms::handleUncountableEarlyExits( | |
| VPlan &Plan, VPBasicBlock *HeaderVPBB, VPBasicBlock *LatchVPBB, | |
| VPBasicBlock *MiddleVPBB, Loop *TheLoop, PredicatedScalarEvolution &PSE, | |
| DominatorTree &DT, AssumptionCache *AC, UncountableExitStyle Style) { | |
| #ifndef NDEBUG | |
| VPDominatorTree VPDT(Plan); | |
| #endif | |
| VPBuilder LatchBuilder(LatchVPBB->getTerminator()); | |
| SmallVector<EarlyExitInfo> Exits; | |
| for (VPIRBasicBlock *ExitBlock : Plan.getExitBlocks()) { | |
| for (VPBlockBase *Pred : to_vector(ExitBlock->getPredecessors())) { | |
| if (Pred == MiddleVPBB) | |
| continue; | |
| // Collect condition for this early exit. | |
| auto *EarlyExitingVPBB = cast<VPBasicBlock>(Pred); | |
| VPBlockBase *TrueSucc = EarlyExitingVPBB->getSuccessors()[0]; | |
| VPValue *CondOfEarlyExitingVPBB; | |
| [[maybe_unused]] bool Matched = | |
| match(EarlyExitingVPBB->getTerminator(), | |
| m_BranchOnCond(m_VPValue(CondOfEarlyExitingVPBB))); | |
| assert(Matched && "Terminator must be BranchOnCond"); | |
| // Insert the MaskedCond in the EarlyExitingVPBB so the predicator adds | |
| // the correct block mask. | |
| VPBuilder EarlyExitingBuilder(EarlyExitingVPBB->getTerminator()); | |
| auto *CondToEarlyExit = EarlyExitingBuilder.createNaryOp( | |
| VPInstruction::MaskedCond, | |
| TrueSucc == ExitBlock | |
| ? CondOfEarlyExitingVPBB | |
| : EarlyExitingBuilder.createNot(CondOfEarlyExitingVPBB)); | |
| assert((isa<VPIRValue>(CondOfEarlyExitingVPBB) || | |
| !VPDT.properlyDominates(EarlyExitingVPBB, LatchVPBB) || | |
| VPDT.properlyDominates( | |
| CondOfEarlyExitingVPBB->getDefiningRecipe()->getParent(), | |
| LatchVPBB)) && | |
| "exit condition must dominate the latch"); | |
| Exits.push_back({ | |
| EarlyExitingVPBB, | |
| ExitBlock, | |
| CondToEarlyExit, | |
| }); | |
| } | |
| } | |
| assert(!Exits.empty() && "must have at least one early exit"); | |
| // Sort exits by RPO order to get correct program order. RPO gives a | |
| // topological ordering of the CFG, ensuring upstream exits are checked | |
| // before downstream exits in the dispatch chain. | |
| ReversePostOrderTraversal<VPBlockShallowTraversalWrapper<VPBlockBase *>> RPOT( | |
| HeaderVPBB); | |
| DenseMap<VPBlockBase *, unsigned> RPOIdx; | |
| for (const auto &[Num, VPB] : enumerate(RPOT)) | |
| RPOIdx[VPB] = Num; | |
| llvm::sort(Exits, [&RPOIdx](const EarlyExitInfo &A, const EarlyExitInfo &B) { | |
| return RPOIdx[A.EarlyExitingVPBB] < RPOIdx[B.EarlyExitingVPBB]; | |
| }); | |
| #ifndef NDEBUG | |
| // After RPO sorting, verify that for any pair where one exit dominates | |
| // another, the dominating exit comes first. This is guaranteed by RPO | |
| // (topological order) and is required for the dispatch chain correctness. | |
| for (unsigned I = 0; I + 1 < Exits.size(); ++I) | |
| for (unsigned J = I + 1; J < Exits.size(); ++J) | |
| assert(!VPDT.properlyDominates(Exits[J].EarlyExitingVPBB, | |
| Exits[I].EarlyExitingVPBB) && | |
| "RPO sort must place dominating exits before dominated ones"); | |
| #endif | |
| // Build the AnyOf condition for the latch terminator using logical OR | |
| // to avoid poison propagation from later exit conditions when an earlier | |
| // exit is taken. | |
| VPValue *Combined = Exits[0].CondToExit; | |
| for (const EarlyExitInfo &Info : drop_begin(Exits)) | |
| Combined = LatchBuilder.createLogicalOr(Combined, Info.CondToExit); | |
| VPValue *IsAnyExitTaken = | |
| LatchBuilder.createNaryOp(VPInstruction::AnyOf, {Combined}); | |
| // Create a comparison for the latch exit condition and replace the | |
| // BranchOnCond with a BranchOnTwoConds. The original BranchOnCond's condition | |
| // is used as the latch-exit condition; canonical IV recipes have not been | |
| // introduced yet, so there is no BranchOnCount to derive the condition from. | |
| auto *LatchExitingBranch = cast<VPInstruction>(LatchVPBB->getTerminator()); | |
| assert(LatchExitingBranch->getOpcode() == VPInstruction::BranchOnCond && | |
| "Unexpected terminator"); | |
| VPValue *IsLatchExitTaken = LatchExitingBranch->getOperand(0); | |
| DebugLoc LatchDL = LatchExitingBranch->getDebugLoc(); | |
| LatchExitingBranch->eraseFromParent(); | |
| LatchBuilder.setInsertPoint(LatchVPBB); | |
| LatchBuilder.createNaryOp(VPInstruction::BranchOnTwoConds, | |
| {IsAnyExitTaken, IsLatchExitTaken}, LatchDL); | |
| LatchVPBB->clearSuccessors(); | |
| if (Style == UncountableExitStyle::MaskedHandleExitInScalarLoop) { | |
| // If handling the exiting lane in the scalar loop, combine the exit | |
| // conditions into a single BranchOnCond. | |
| LatchVPBB->setSuccessors({MiddleVPBB, MiddleVPBB, HeaderVPBB}); | |
| MiddleVPBB->clearPredecessors(); | |
| MiddleVPBB->setPredecessors({LatchVPBB, LatchVPBB}); | |
| return handleUncountableExitsWithSideEffects( | |
| Plan, Exits, HeaderVPBB, LatchVPBB, MiddleVPBB, TheLoop, PSE, DT, AC); | |
| } | |
| // Create the vector.early.exit blocks. | |
| SmallVector<VPBasicBlock *> VectorEarlyExitVPBBs(Exits.size()); | |
| for (unsigned Idx = 0; Idx != Exits.size(); ++Idx) { | |
| Twine BlockSuffix = Exits.size() == 1 ? "" : Twine(".") + Twine(Idx); | |
| VPBasicBlock *VectorEarlyExitVPBB = | |
| Plan.createVPBasicBlock("vector.early.exit" + BlockSuffix); | |
| VectorEarlyExitVPBBs[Idx] = VectorEarlyExitVPBB; | |
| } | |
| // Create the dispatch block (or reuse the single exit block if only one | |
| // exit). The dispatch block computes the first active lane of the combined | |
| // condition and, for multiple exits, chains through conditions to determine | |
| // which exit to take. | |
| VPBasicBlock *DispatchVPBB = | |
| Exits.size() == 1 ? VectorEarlyExitVPBBs[0] | |
| : Plan.createVPBasicBlock("vector.early.exit.check"); | |
| DispatchVPBB->setPredecessors({LatchVPBB}); | |
| LatchVPBB->setSuccessors({DispatchVPBB, MiddleVPBB, HeaderVPBB}); | |
| VPBuilder DispatchBuilder(DispatchVPBB, DispatchVPBB->begin()); | |
| VPValue *FirstActiveLane = DispatchBuilder.createFirstActiveLane( | |
| {Combined}, DebugLoc::getUnknown(), "first.active.lane"); | |
| // For each early exit, disconnect the original exiting block | |
| // (early.exiting.I) from the exit block (ir-bb<exit.I>) and route through a | |
| // new vector.early.exit block. Update ir-bb<exit.I>'s phis to extract their | |
| // values at the first active lane: | |
| // | |
| // Input: | |
| // early.exiting.I: | |
| // ... | |
| // EMIT branch-on-cond vp<%cond.I> | |
| // Successor(s): in.loop.succ, ir-bb<exit.I> | |
| // | |
| // ir-bb<exit.I>: | |
| // IR %phi = phi [ vp<%incoming.I>, early.exiting.I ], ... | |
| // | |
| // Output: | |
| // early.exiting.I: | |
| // ... | |
| // Successor(s): in.loop.succ | |
| // | |
| // vector.early.exit.I: | |
| // EMIT vp<%exit.val> = extract-lane vp<%first.lane>, vp<%incoming.I> | |
| // Successor(s): ir-bb<exit.I> | |
| // | |
| // ir-bb<exit.I>: | |
| // IR %phi = phi ... (extra operand: vp<%exit.val> from | |
| // vector.early.exit.I) | |
| // | |
| for (auto [Exit, VectorEarlyExitVPBB] : | |
| zip_equal(Exits, VectorEarlyExitVPBBs)) { | |
| auto &[EarlyExitingVPBB, EarlyExitVPBB, _] = Exit; | |
| // Adjust the phi nodes in EarlyExitVPBB. | |
| // 1. remove incoming values from EarlyExitingVPBB, | |
| // 2. extract the incoming value at FirstActiveLane | |
| // 3. add back the extracts as last operands for the phis | |
| // Then adjust the CFG, removing the edge between EarlyExitingVPBB and | |
| // EarlyExitVPBB and adding a new edge between VectorEarlyExitVPBB and | |
| // EarlyExitVPBB. The extracts at FirstActiveLane are now the incoming | |
| // values from VectorEarlyExitVPBB. | |
| for (VPRecipeBase &R : EarlyExitVPBB->phis()) { | |
| auto *ExitIRI = cast<VPIRPhi>(&R); | |
| VPValue *IncomingVal = | |
| ExitIRI->getIncomingValueForBlock(EarlyExitingVPBB); | |
| VPValue *NewIncoming = IncomingVal; | |
| if (!isa<VPIRValue>(IncomingVal)) { | |
| VPBuilder EarlyExitBuilder(VectorEarlyExitVPBB); | |
| NewIncoming = EarlyExitBuilder.createNaryOp( | |
| VPInstruction::ExtractLane, {FirstActiveLane, IncomingVal}, | |
| DebugLoc::getUnknown(), "early.exit.value"); | |
| } | |
| ExitIRI->removeIncomingValueFor(EarlyExitingVPBB); | |
| ExitIRI->addIncoming(NewIncoming); | |
| } | |
| EarlyExitingVPBB->getTerminator()->eraseFromParent(); | |
| VPBlockUtils::disconnectBlocks(EarlyExitingVPBB, EarlyExitVPBB); | |
| VPBlockUtils::connectBlocks(VectorEarlyExitVPBB, EarlyExitVPBB); | |
| } | |
| // Chain through exits: for each exit, check if its condition is true at | |
| // the first active lane. If so, take that exit; otherwise, try the next. | |
| // The last exit needs no check since it must be taken if all others fail. | |
| // | |
| // For 3 exits (cond.0, cond.1, cond.2), this creates: | |
| // | |
| // latch: | |
| // ... | |
| // EMIT vp<%combined> = logical-or vp<%cond.0>, vp<%cond.1>, vp<%cond.2> | |
| // ... | |
| // | |
| // vector.early.exit.check: | |
| // EMIT vp<%first.lane> = first-active-lane vp<%combined> | |
| // EMIT vp<%at.cond.0> = extract-lane vp<%first.lane>, vp<%cond.0> | |
| // EMIT branch-on-cond vp<%at.cond.0> | |
| // Successor(s): vector.early.exit.0, vector.early.exit.check.0 | |
| // | |
| // vector.early.exit.check.0: | |
| // EMIT vp<%at.cond.1> = extract-lane vp<%first.lane>, vp<%cond.1> | |
| // EMIT branch-on-cond vp<%at.cond.1> | |
| // Successor(s): vector.early.exit.1, vector.early.exit.2 | |
| VPBasicBlock *CurrentBB = DispatchVPBB; | |
| for (auto [I, Exit] : enumerate(ArrayRef(Exits).drop_back())) { | |
| VPValue *LaneVal = DispatchBuilder.createNaryOp( | |
| VPInstruction::ExtractLane, {FirstActiveLane, Exit.CondToExit}, | |
| DebugLoc::getUnknown(), "exit.cond.at.lane"); | |
| // For the last dispatch, branch directly to the last exit on false; | |
| // otherwise, create a new check block. | |
| bool IsLastDispatch = (I + 2 == Exits.size()); | |
| VPBasicBlock *FalseBB = | |
| IsLastDispatch ? VectorEarlyExitVPBBs.back() | |
| : Plan.createVPBasicBlock( | |
| Twine("vector.early.exit.check.") + Twine(I)); | |
| DispatchBuilder.createNaryOp(VPInstruction::BranchOnCond, {LaneVal}); | |
| CurrentBB->setSuccessors({VectorEarlyExitVPBBs[I], FalseBB}); | |
| VectorEarlyExitVPBBs[I]->setPredecessors({CurrentBB}); | |
| FalseBB->setPredecessors({CurrentBB}); | |
| CurrentBB = FalseBB; | |
| DispatchBuilder.setInsertPoint(CurrentBB); | |
| } | |
| return true; | |
| } | |
| /// This function tries convert extended in-loop reductions to | |
| /// VPExpressionRecipe and clamp the \p Range if it is beneficial and | |
| /// valid. The created recipe must be decomposed to its constituent | |
| /// recipes before execution. | |
| static VPExpressionRecipe * | |
| tryToMatchAndCreateExtendedReduction(VPReductionRecipe *Red, VPCostContext &Ctx, | |
| VFRange &Range) { | |
| Type *RedTy = Red->getScalarType(); | |
| VPValue *VecOp = Red->getVecOp(); | |
| assert(!Red->isPartialReduction() && | |
| "This path does not support partial reductions"); | |
| // Clamp the range if using extended-reduction is profitable. | |
| auto IsExtendedRedValidAndClampRange = | |
| [&](unsigned Opcode, Instruction::CastOps ExtOpc, Type *SrcTy) -> bool { | |
| return LoopVectorizationPlanner::getDecisionAndClampRange( | |
| [&](ElementCount VF) { | |
| auto *SrcVecTy = cast<VectorType>(toVectorTy(SrcTy, VF)); | |
| TTI::TargetCostKind CostKind = TTI::TCK_RecipThroughput; | |
| InstructionCost ExtRedCost = InstructionCost::getInvalid(); | |
| InstructionCost ExtCost = | |
| cast<VPWidenCastRecipe>(VecOp)->computeCost(VF, Ctx); | |
| InstructionCost RedCost = Red->computeCost(VF, Ctx); | |
| assert(!RedTy->isFloatingPointTy() && | |
| "getExtendedReductionCost only supports integer types"); | |
| ExtRedCost = Ctx.TTI.getExtendedReductionCost( | |
| Opcode, ExtOpc == Instruction::CastOps::ZExt, RedTy, SrcVecTy, | |
| Red->getFastMathFlagsOrNone(), CostKind); | |
| return ExtRedCost.isValid() && ExtRedCost < ExtCost + RedCost; | |
| }, | |
| Range); | |
| }; | |
| VPValue *A; | |
| // Match reduce(ext)). | |
| if (match(VecOp, m_Isa<VPWidenCastRecipe>(m_ZExtOrSExt(m_VPValue(A)))) && | |
| IsExtendedRedValidAndClampRange( | |
| RecurrenceDescriptor::getOpcode(Red->getRecurrenceKind()), | |
| cast<VPWidenCastRecipe>(VecOp)->getOpcode(), A->getScalarType())) | |
| return new VPExpressionRecipe(cast<VPWidenCastRecipe>(VecOp), Red); | |
| return nullptr; | |
| } | |
| /// This function tries convert extended in-loop reductions to | |
| /// VPExpressionRecipe and clamp the \p Range if it is beneficial | |
| /// and valid. The created VPExpressionRecipe must be decomposed to its | |
| /// constituent recipes before execution. Patterns of the | |
| /// VPExpressionRecipe: | |
| /// reduce.add(mul(...)), | |
| /// reduce.add(mul(ext(A), ext(B))), | |
| /// reduce.add(ext(mul(ext(A), ext(B)))). | |
| /// reduce.fadd(fmul(ext(A), ext(B))) | |
| static VPExpressionRecipe * | |
| tryToMatchAndCreateMulAccumulateReduction(VPReductionRecipe *Red, | |
| VPCostContext &Ctx, VFRange &Range) { | |
| unsigned Opcode = RecurrenceDescriptor::getOpcode(Red->getRecurrenceKind()); | |
| if (Opcode != Instruction::Add && Opcode != Instruction::Sub && | |
| Opcode != Instruction::FAdd) | |
| return nullptr; | |
| assert(!Red->isPartialReduction() && | |
| "This path does not support partial reductions"); | |
| Type *RedTy = Red->getScalarType(); | |
| // Clamp the range if using multiply-accumulate-reduction is profitable. | |
| auto IsMulAccValidAndClampRange = | |
| [&](VPWidenRecipe *Mul, VPWidenCastRecipe *Ext0, VPWidenCastRecipe *Ext1, | |
| VPWidenCastRecipe *OuterExt) -> bool { | |
| return LoopVectorizationPlanner::getDecisionAndClampRange( | |
| [&](ElementCount VF) { | |
| TTI::TargetCostKind CostKind = TTI::TCK_RecipThroughput; | |
| Type *SrcTy = Ext0 ? Ext0->getOperand(0)->getScalarType() : RedTy; | |
| InstructionCost MulAccCost; | |
| // getMulAccReductionCost for in-loop reductions does not support | |
| // mixed or floating-point extends. | |
| if (Ext0 && Ext1 && | |
| (Ext0->getOpcode() != Ext1->getOpcode() || | |
| Ext0->getOpcode() == Instruction::CastOps::FPExt)) | |
| return false; | |
| bool IsZExt = | |
| !Ext0 || Ext0->getOpcode() == Instruction::CastOps::ZExt; | |
| auto *SrcVecTy = cast<VectorType>(toVectorTy(SrcTy, VF)); | |
| MulAccCost = Ctx.TTI.getMulAccReductionCost(IsZExt, Opcode, RedTy, | |
| SrcVecTy, CostKind); | |
| InstructionCost MulCost = Mul->computeCost(VF, Ctx); | |
| InstructionCost RedCost = Red->computeCost(VF, Ctx); | |
| InstructionCost ExtCost = 0; | |
| if (Ext0) | |
| ExtCost += Ext0->computeCost(VF, Ctx); | |
| if (Ext1) | |
| ExtCost += Ext1->computeCost(VF, Ctx); | |
| if (OuterExt) | |
| ExtCost += OuterExt->computeCost(VF, Ctx); | |
| return MulAccCost.isValid() && | |
| MulAccCost < ExtCost + MulCost + RedCost; | |
| }, | |
| Range); | |
| }; | |
| VPValue *VecOp = Red->getVecOp(); | |
| VPRecipeBase *Sub = nullptr; | |
| VPValue *A, *B; | |
| VPValue *Tmp = nullptr; | |
| if (RedTy->isFloatingPointTy()) | |
| return nullptr; | |
| // Sub reductions could have a sub between the add reduction and vec op. | |
| if (match(VecOp, m_Sub(m_ZeroInt(), m_VPValue(Tmp)))) { | |
| Sub = VecOp->getDefiningRecipe(); | |
| VecOp = Tmp; | |
| } | |
| // If ValB is a constant and can be safely extended, truncate it to the same | |
| // type as ExtA's operand, then extend it to the same type as ExtA. This | |
| // creates two uniform extends that can more easily be matched by the rest of | |
| // the bundling code. The ExtB reference, ValB and operand 1 of Mul are all | |
| // replaced with the new extend of the constant. | |
| auto ExtendAndReplaceConstantOp = [](VPWidenCastRecipe *ExtA, | |
| VPWidenCastRecipe *&ExtB, VPValue *&ValB, | |
| VPWidenRecipe *Mul) { | |
| if (!ExtA || ExtB || !isa<VPIRValue>(ValB)) | |
| return; | |
| Type *NarrowTy = ExtA->getOperand(0)->getScalarType(); | |
| Instruction::CastOps ExtOpc = ExtA->getOpcode(); | |
| const APInt *Const; | |
| if (!match(ValB, m_APInt(Const)) || | |
| !llvm::canConstantBeExtended( | |
| Const, NarrowTy, TTI::getPartialReductionExtendKind(ExtOpc))) | |
| return; | |
| // The truncate ensures that the type of each extended operand is the | |
| // same, and it's been proven that the constant can be extended from | |
| // NarrowTy safely. Necessary since ExtA's extended operand would be | |
| // e.g. an i8, while the const will likely be an i32. This will be | |
| // elided by later optimisations. | |
| VPBuilder Builder(Mul); | |
| auto *Trunc = | |
| Builder.createWidenCast(Instruction::CastOps::Trunc, ValB, NarrowTy); | |
| Type *WideTy = ExtA->getScalarType(); | |
| ValB = ExtB = Builder.createWidenCast(ExtOpc, Trunc, WideTy); | |
| Mul->setOperand(1, ExtB); | |
| }; | |
| // Try to match reduce.add(mul(...)). | |
| if (match(VecOp, m_Mul(m_VPValue(A), m_VPValue(B)))) { | |
| auto *RecipeA = dyn_cast<VPWidenCastRecipe>(A); | |
| auto *RecipeB = dyn_cast<VPWidenCastRecipe>(B); | |
| auto *Mul = cast<VPWidenRecipe>(VecOp); | |
| // Convert reduce.add(mul(ext, const)) to reduce.add(mul(ext, ext(const))) | |
| ExtendAndReplaceConstantOp(RecipeA, RecipeB, B, Mul); | |
| // Match reduce.add/sub(mul(ext, ext)). | |
| if (RecipeA && RecipeB && match(RecipeA, m_ZExtOrSExt(m_VPValue())) && | |
| match(RecipeB, m_ZExtOrSExt(m_VPValue())) && | |
| IsMulAccValidAndClampRange(Mul, RecipeA, RecipeB, nullptr)) { | |
| if (Sub) | |
| return new VPExpressionRecipe(RecipeA, RecipeB, Mul, | |
| cast<VPWidenRecipe>(Sub), Red); | |
| return new VPExpressionRecipe(RecipeA, RecipeB, Mul, Red); | |
| } | |
| // TODO: Add an expression type for this variant with a negated mul | |
| if (!Sub && IsMulAccValidAndClampRange(Mul, nullptr, nullptr, nullptr)) | |
| return new VPExpressionRecipe(Mul, Red); | |
| } | |
| // TODO: Add an expression type for negated versions of other expression | |
| // variants. | |
| if (Sub) | |
| return nullptr; | |
| // Match reduce.add(ext(mul(A, B))). | |
| if (match(VecOp, m_ZExtOrSExt(m_Mul(m_VPValue(A), m_VPValue(B))))) { | |
| auto *Ext = cast<VPWidenCastRecipe>(VecOp); | |
| auto *Mul = cast<VPWidenRecipe>(Ext->getOperand(0)); | |
| auto *Ext0 = dyn_cast<VPWidenCastRecipe>(A); | |
| auto *Ext1 = dyn_cast<VPWidenCastRecipe>(B); | |
| // reduce.add(ext(mul(ext, const))) | |
| // -> reduce.add(ext(mul(ext, ext(const)))) | |
| ExtendAndReplaceConstantOp(Ext0, Ext1, B, Mul); | |
| // reduce.add(ext(mul(ext(A), ext(B)))) | |
| // -> reduce.add(mul(wider_ext(A), wider_ext(B))) | |
| // The inner extends must either have the same opcode as the outer extend or | |
| // be the same, in which case the multiply can never result in a negative | |
| // value and the outer extend can be folded away by doing wider | |
| // extends for the operands of the mul. | |
| if (Ext0 && Ext1 && | |
| (Ext->getOpcode() == Ext0->getOpcode() || Ext0 == Ext1) && | |
| Ext0->getOpcode() == Ext1->getOpcode() && | |
| IsMulAccValidAndClampRange(Mul, Ext0, Ext1, Ext) && Mul->hasOneUse()) { | |
| auto *NewExt0 = new VPWidenCastRecipe( | |
| Ext0->getOpcode(), Ext0->getOperand(0), Ext->getScalarType(), nullptr, | |
| *Ext0, *Ext0, Ext0->getDebugLoc()); | |
| NewExt0->insertBefore(Ext0); | |
| VPWidenCastRecipe *NewExt1 = NewExt0; | |
| if (Ext0 != Ext1) { | |
| NewExt1 = new VPWidenCastRecipe(Ext1->getOpcode(), Ext1->getOperand(0), | |
| Ext->getScalarType(), nullptr, *Ext1, | |
| *Ext1, Ext1->getDebugLoc()); | |
| NewExt1->insertBefore(Ext1); | |
| } | |
| auto *NewMul = Mul->cloneWithOperands({NewExt0, NewExt1}); | |
| NewMul->insertBefore(Mul); | |
| Ext->replaceAllUsesWith(NewMul); | |
| Ext->eraseFromParent(); | |
| Mul->eraseFromParent(); | |
| return new VPExpressionRecipe(NewExt0, NewExt1, NewMul, Red); | |
| } | |
| } | |
| return nullptr; | |
| } | |
| /// This function tries to create abstract recipes from the reduction recipe for | |
| /// following optimizations and cost estimation. | |
| static void tryToCreateAbstractReductionRecipe(VPReductionRecipe *Red, | |
| VPCostContext &Ctx, | |
| VFRange &Range) { | |
| // Creation of VPExpressions for partial reductions is entirely handled in | |
| // transformToPartialReduction. | |
| assert(!Red->isPartialReduction() && | |
| "This path does not support partial reductions"); | |
| VPExpressionRecipe *AbstractR = nullptr; | |
| auto IP = std::next(Red->getIterator()); | |
| auto *VPBB = Red->getParent(); | |
| if (auto *MulAcc = tryToMatchAndCreateMulAccumulateReduction(Red, Ctx, Range)) | |
| AbstractR = MulAcc; | |
| else if (auto *ExtRed = tryToMatchAndCreateExtendedReduction(Red, Ctx, Range)) | |
| AbstractR = ExtRed; | |
| // Cannot create abstract inloop reduction recipes. | |
| if (!AbstractR) | |
| return; | |
| AbstractR->insertBefore(*VPBB, IP); | |
| Red->replaceAllUsesWith(AbstractR); | |
| } | |
| void VPlanTransforms::convertToAbstractRecipes(VPlan &Plan, VPCostContext &Ctx, | |
| VFRange &Range) { | |
| for (VPBasicBlock *VPBB : VPBlockUtils::blocksOnly<VPBasicBlock>( | |
| vp_depth_first_deep(Plan.getVectorLoopRegion()))) { | |
| for (VPRecipeBase &R : make_early_inc_range(*VPBB)) { | |
| if (auto *Red = dyn_cast<VPReductionRecipe>(&R)) | |
| tryToCreateAbstractReductionRecipe(Red, Ctx, Range); | |
| } | |
| } | |
| } | |
| void VPlanTransforms::materializeBroadcasts(VPlan &Plan) { | |
| if (Plan.hasScalarVFOnly()) | |
| return; | |
| #ifndef NDEBUG | |
| VPDominatorTree VPDT(Plan); | |
| #endif | |
| SmallVector<VPValue *> VPValues; | |
| if (VPValue *BTC = Plan.getBackedgeTakenCount()) | |
| VPValues.push_back(BTC); | |
| append_range(VPValues, Plan.getLiveIns()); | |
| for (VPRecipeBase &R : *Plan.getEntry()) | |
| append_range(VPValues, R.definedValues()); | |
| auto *VectorPreheader = Plan.getVectorPreheader(); | |
| for (VPValue *VPV : VPValues) { | |
| if (vputils::onlyScalarValuesUsed(VPV) || isa<VPConstant>(VPV)) | |
| continue; | |
| // Add explicit broadcast at the insert point that dominates all users. | |
| VPBasicBlock *HoistBlock = VectorPreheader; | |
| VPBasicBlock::iterator HoistPoint = VectorPreheader->end(); | |
| for (VPUser *User : VPV->users()) { | |
| if (User->usesScalars(VPV)) | |
| continue; | |
| if (cast<VPRecipeBase>(User)->getParent() == VectorPreheader) | |
| HoistPoint = HoistBlock->begin(); | |
| else | |
| assert(VPDT.dominates(VectorPreheader, | |
| cast<VPRecipeBase>(User)->getParent()) && | |
| "All users must be in the vector preheader or dominated by it"); | |
| } | |
| VPBuilder Builder(cast<VPBasicBlock>(HoistBlock), HoistPoint); | |
| auto *Broadcast = Builder.createNaryOp(VPInstruction::Broadcast, {VPV}); | |
| VPV->replaceUsesWithIf(Broadcast, | |
| [VPV, Broadcast](VPUser &U, unsigned Idx) { | |
| return Broadcast != &U && !U.usesScalars(VPV); | |
| }); | |
| } | |
| } | |
| // Collect common metadata from a group of replicate recipes by intersecting | |
| // metadata from all recipes in the group. | |
| static VPIRMetadata getCommonMetadata(ArrayRef<VPReplicateRecipe *> Recipes) { | |
| VPIRMetadata CommonMetadata = *Recipes.front(); | |
| for (VPReplicateRecipe *Recipe : drop_begin(Recipes)) | |
| CommonMetadata.intersect(*Recipe); | |
| return CommonMetadata; | |
| } | |
| template <unsigned Opcode> | |
| static SmallVector<SmallVector<VPReplicateRecipe *, 4>> | |
| collectComplementaryPredicatedMemOps(VPlan &Plan, | |
| PredicatedScalarEvolution &PSE, | |
| const Loop *L) { | |
| static_assert(Opcode == Instruction::Load || Opcode == Instruction::Store, | |
| "Only Load and Store opcodes supported"); | |
| [[maybe_unused]] constexpr bool IsLoad = (Opcode == Instruction::Load); | |
| // For each address, collect operations with the same or complementary masks. | |
| SmallVector<SmallVector<VPReplicateRecipe *, 4>> AllGroups; | |
| auto Groups = collectGroupedReplicateMemOps<Opcode>( | |
| Plan, PSE, L, | |
| [](VPReplicateRecipe *RepR) { return RepR->isPredicated(); }); | |
| for (auto Recipes : Groups) { | |
| if (Recipes.size() < 2) | |
| continue; | |
| assert(all_equal( | |
| map_range(Recipes, bind_back<getLoadStoreValueType>(IsLoad))) && | |
| "Expected all recipes in group to have the same load-store type"); | |
| // Collect groups with the same or complementary masks. | |
| for (VPReplicateRecipe *&RecipeI : Recipes) { | |
| if (!RecipeI) | |
| continue; | |
| VPValue *MaskI = RecipeI->getMask(); | |
| SmallVector<VPReplicateRecipe *, 4> Group; | |
| Group.push_back(RecipeI); | |
| RecipeI = nullptr; | |
| // Find all operations with the same or complementary masks. | |
| bool HasComplementaryMask = false; | |
| for (VPReplicateRecipe *&RecipeJ : Recipes) { | |
| if (!RecipeJ) | |
| continue; | |
| VPValue *MaskJ = RecipeJ->getMask(); | |
| // Check if any operation in the group has a complementary mask with | |
| // another, that is M1 == NOT(M2) or M2 == NOT(M1). | |
| HasComplementaryMask |= match(MaskI, m_Not(m_Specific(MaskJ))) || | |
| match(MaskJ, m_Not(m_Specific(MaskI))); | |
| Group.push_back(RecipeJ); | |
| RecipeJ = nullptr; | |
| } | |
| if (HasComplementaryMask) { | |
| assert(Group.size() >= 2 && "must have at least 2 entries"); | |
| AllGroups.push_back(std::move(Group)); | |
| } | |
| } | |
| } | |
| return AllGroups; | |
| } | |
| // Find the recipe with minimum alignment in the group. | |
| template <typename InstType> | |
| static VPReplicateRecipe * | |
| findRecipeWithMinAlign(ArrayRef<VPReplicateRecipe *> Group) { | |
| return *min_element(Group, [](VPReplicateRecipe *A, VPReplicateRecipe *B) { | |
| return cast<InstType>(A->getUnderlyingInstr())->getAlign() < | |
| cast<InstType>(B->getUnderlyingInstr())->getAlign(); | |
| }); | |
| } | |
| void VPlanTransforms::hoistPredicatedLoads(VPlan &Plan, | |
| PredicatedScalarEvolution &PSE, | |
| const Loop *L) { | |
| auto Groups = | |
| collectComplementaryPredicatedMemOps<Instruction::Load>(Plan, PSE, L); | |
| if (Groups.empty()) | |
| return; | |
| // Process each group of loads. | |
| for (auto &Group : Groups) { | |
| // Try to use the earliest (most dominating) load to replace all others. | |
| VPReplicateRecipe *EarliestLoad = Group[0]; | |
| VPBasicBlock *FirstBB = EarliestLoad->getParent(); | |
| VPBasicBlock *LastBB = Group.back()->getParent(); | |
| // Check that the load doesn't alias with stores between first and last. | |
| auto LoadLoc = vputils::getMemoryLocation(*EarliestLoad); | |
| if (!LoadLoc || !canHoistOrSinkWithNoAliasCheck(*LoadLoc, FirstBB, LastBB)) | |
| continue; | |
| // Collect common metadata from all loads in the group. | |
| VPIRMetadata CommonMetadata = getCommonMetadata(Group); | |
| // Find the load with minimum alignment to use. | |
| auto *LoadWithMinAlign = findRecipeWithMinAlign<LoadInst>(Group); | |
| bool IsSingleScalar = EarliestLoad->isSingleScalar(); | |
| assert(all_of(Group, | |
| [IsSingleScalar](VPReplicateRecipe *R) { | |
| return R->isSingleScalar() == IsSingleScalar; | |
| }) && | |
| "all members in group must agree on IsSingleScalar"); | |
| // Create an unpredicated version of the earliest load with common | |
| // metadata. | |
| auto *UnpredicatedLoad = new VPReplicateRecipe( | |
| LoadWithMinAlign->getUnderlyingInstr(), {EarliestLoad->getOperand(0)}, | |
| IsSingleScalar, /*Mask=*/nullptr, *EarliestLoad, CommonMetadata); | |
| UnpredicatedLoad->insertBefore(EarliestLoad); | |
| // Replace all loads in the group with the unpredicated load. | |
| for (VPReplicateRecipe *Load : Group) { | |
| Load->replaceAllUsesWith(UnpredicatedLoad); | |
| Load->eraseFromParent(); | |
| } | |
| } | |
| } | |
| static bool | |
| canSinkStoreWithNoAliasCheck(ArrayRef<VPReplicateRecipe *> StoresToSink, | |
| PredicatedScalarEvolution &PSE, const Loop &L) { | |
| auto StoreLoc = vputils::getMemoryLocation(*StoresToSink.front()); | |
| if (!StoreLoc || !StoreLoc->AATags.Scope) | |
| return false; | |
| // When sinking a group of stores, all members of the group alias each other. | |
| // Skip them during the alias checks. | |
| VPBasicBlock *FirstBB = StoresToSink.front()->getParent(); | |
| VPBasicBlock *LastBB = StoresToSink.back()->getParent(); | |
| SinkStoreInfo SinkInfo(StoresToSink, *StoresToSink[0], PSE, L); | |
| return canHoistOrSinkWithNoAliasCheck(*StoreLoc, FirstBB, LastBB, SinkInfo); | |
| } | |
| void VPlanTransforms::sinkPredicatedStores(VPlan &Plan, | |
| PredicatedScalarEvolution &PSE, | |
| const Loop *L) { | |
| auto Groups = | |
| collectComplementaryPredicatedMemOps<Instruction::Store>(Plan, PSE, L); | |
| if (Groups.empty()) | |
| return; | |
| for (auto &Group : Groups) { | |
| if (!canSinkStoreWithNoAliasCheck(Group, PSE, *L)) | |
| continue; | |
| // Use the last (most dominated) store's location for the unconditional | |
| // store. | |
| VPReplicateRecipe *LastStore = Group.back(); | |
| VPBasicBlock *InsertBB = LastStore->getParent(); | |
| // Collect common alias metadata from all stores in the group. | |
| VPIRMetadata CommonMetadata = getCommonMetadata(Group); | |
| // Build select chain for stored values. | |
| VPValue *SelectedValue = Group[0]->getOperand(0); | |
| VPBuilder Builder(InsertBB, LastStore->getIterator()); | |
| bool IsSingleScalar = Group[0]->isSingleScalar(); | |
| for (unsigned I = 1; I < Group.size(); ++I) { | |
| assert(IsSingleScalar == Group[I]->isSingleScalar() && | |
| "all members in group must agree on IsSingleScalar"); | |
| VPValue *Mask = Group[I]->getMask(); | |
| VPValue *Value = Group[I]->getOperand(0); | |
| SelectedValue = Builder.createSelect(Mask, Value, SelectedValue, | |
| Group[I]->getDebugLoc()); | |
| } | |
| // Find the store with minimum alignment to use. | |
| auto *StoreWithMinAlign = findRecipeWithMinAlign<StoreInst>(Group); | |
| // Create unconditional store with selected value and common metadata. | |
| auto *UnpredicatedStore = new VPReplicateRecipe( | |
| StoreWithMinAlign->getUnderlyingInstr(), | |
| {SelectedValue, LastStore->getOperand(1)}, IsSingleScalar, | |
| /*Mask=*/nullptr, *LastStore, CommonMetadata); | |
| UnpredicatedStore->insertBefore(*InsertBB, LastStore->getIterator()); | |
| // Remove all predicated stores from the group. | |
| for (VPReplicateRecipe *Store : Group) | |
| Store->eraseFromParent(); | |
| } | |
| } | |
| void VPlanTransforms::materializeConstantVectorTripCount( | |
| VPlan &Plan, ElementCount BestVF, unsigned BestUF, | |
| PredicatedScalarEvolution &PSE) { | |
| assert(Plan.hasVF(BestVF) && "BestVF is not available in Plan"); | |
| assert(Plan.hasUF(BestUF) && "BestUF is not available in Plan"); | |
| VPValue *TC = Plan.getTripCount(); | |
| if (TC->user_empty()) | |
| return; | |
| // Skip cases for which the trip count may be non-trivial to materialize. | |
| // I.e., when a scalar tail is absent - due to tail folding, or when a scalar | |
| // tail is required. | |
| if (Plan.hasTailFolded() || !Plan.hasScalarTail() || | |
| Plan.getMiddleBlock()->getSingleSuccessor() == | |
| Plan.getScalarPreheader() || | |
| !isa<VPIRValue>(TC)) | |
| return; | |
| // Materialize vector trip counts for constants early if it can simply | |
| // be computed as (Original TC / VF * UF) * VF * UF. | |
| // TODO: Compute vector trip counts for loops requiring a scalar epilogue and | |
| // tail-folded loops. | |
| ScalarEvolution &SE = *PSE.getSE(); | |
| auto *TCScev = SE.getSCEV(TC->getLiveInIRValue()); | |
| if (!isa<SCEVConstant>(TCScev)) | |
| return; | |
| const SCEV *VFxUF = SE.getElementCount(TCScev->getType(), BestVF * BestUF); | |
| auto VecTCScev = SE.getMulExpr(SE.getUDivExpr(TCScev, VFxUF), VFxUF); | |
| if (auto *ConstVecTC = dyn_cast<SCEVConstant>(VecTCScev)) | |
| Plan.getVectorTripCount().setUnderlyingValue(ConstVecTC->getValue()); | |
| } | |
| void VPlanTransforms::materializeBackedgeTakenCount(VPlan &Plan, | |
| VPBasicBlock *VectorPH) { | |
| VPValue *BTC = Plan.getOrCreateBackedgeTakenCount(); | |
| if (BTC->user_empty()) | |
| return; | |
| VPBuilder Builder(VectorPH, VectorPH->begin()); | |
| auto *TCTy = Plan.getTripCount()->getScalarType(); | |
| auto *TCMO = | |
| Builder.createSub(Plan.getTripCount(), Plan.getConstantInt(TCTy, 1), | |
| DebugLoc::getCompilerGenerated(), "trip.count.minus.1"); | |
| BTC->replaceAllUsesWith(TCMO); | |
| } | |
| void VPlanTransforms::materializePacksAndUnpacks(VPlan &Plan) { | |
| if (Plan.hasScalarVFOnly()) | |
| return; | |
| VPRegionBlock *LoopRegion = Plan.getVectorLoopRegion(); | |
| auto VPBBsOutsideLoopRegion = VPBlockUtils::blocksOnly<VPBasicBlock>( | |
| vp_depth_first_shallow(Plan.getEntry())); | |
| auto VPBBsInsideLoopRegion = VPBlockUtils::blocksOnly<VPBasicBlock>( | |
| vp_depth_first_shallow(LoopRegion->getEntry())); | |
| // Materialize Build(Struct)Vector for all replicating VPReplicateRecipes, | |
| // VPScalarIVStepsRecipe and VPInstructions, excluding ones in replicate | |
| // regions. Those are not materialized explicitly yet. | |
| // TODO: materialize build vectors for replicating recipes in replicating | |
| // regions. | |
| for (VPBasicBlock *VPBB : | |
| concat<VPBasicBlock *>(VPBBsOutsideLoopRegion, VPBBsInsideLoopRegion)) { | |
| for (VPRecipeBase &R : make_early_inc_range(*VPBB)) { | |
| if (!isa<VPScalarIVStepsRecipe, VPReplicateRecipe, VPInstruction>(&R)) | |
| continue; | |
| auto *DefR = cast<VPSingleDefRecipe>(&R); | |
| auto UsesVectorOrInsideReplicateRegion = [DefR, LoopRegion](VPUser *U) { | |
| VPRegionBlock *ParentRegion = cast<VPRecipeBase>(U)->getRegion(); | |
| return !U->usesScalars(DefR) || ParentRegion != LoopRegion; | |
| }; | |
| if ((isa<VPReplicateRecipe>(DefR) && | |
| cast<VPReplicateRecipe>(DefR)->isSingleScalar()) || | |
| (isa<VPInstruction>(DefR) && | |
| (vputils::onlyFirstLaneUsed(DefR) || | |
| !cast<VPInstruction>(DefR)->doesGeneratePerAllLanes())) || | |
| none_of(DefR->users(), UsesVectorOrInsideReplicateRegion)) | |
| continue; | |
| Type *ScalarTy = DefR->getScalarType(); | |
| unsigned Opcode = ScalarTy->isStructTy() | |
| ? VPInstruction::BuildStructVector | |
| : VPInstruction::BuildVector; | |
| auto *BuildVector = new VPInstruction(Opcode, {DefR}); | |
| BuildVector->insertAfter(DefR); | |
| DefR->replaceUsesWithIf( | |
| BuildVector, [BuildVector, &UsesVectorOrInsideReplicateRegion]( | |
| VPUser &U, unsigned) { | |
| return &U != BuildVector && UsesVectorOrInsideReplicateRegion(&U); | |
| }); | |
| } | |
| } | |
| // Create explicit VPInstructions to convert vectors to scalars. The current | |
| // implementation is conservative - it may miss some cases that may or may not | |
| // be vector values. TODO: introduce Unpacks speculatively - remove them later | |
| // if they are known to operate on scalar values. | |
| for (VPBasicBlock *VPBB : VPBBsInsideLoopRegion) { | |
| for (VPRecipeBase &R : make_early_inc_range(*VPBB)) { | |
| if (isa<VPReplicateRecipe, VPInstruction, VPScalarIVStepsRecipe, | |
| VPDerivedIVRecipe>(&R)) | |
| continue; | |
| for (VPValue *Def : R.definedValues()) { | |
| // Skip recipes that are single-scalar. | |
| // TODO: The Defs skipped here may or may not be vector values. | |
| // Introduce Unpacks, and remove them later, if they are guaranteed to | |
| // produce scalar values. | |
| if (vputils::isSingleScalar(Def)) | |
| continue; | |
| // Only introduce an Unpack if some, but not all, users use the first | |
| // lane only. | |
| unsigned NumFirstLaneUsers = count_if(Def->users(), [&Def](VPUser *U) { | |
| return U->usesFirstLaneOnly(Def); | |
| }); | |
| if (!NumFirstLaneUsers || NumFirstLaneUsers == Def->getNumUsers()) | |
| continue; | |
| auto *Unpack = new VPInstruction(VPInstruction::Unpack, {Def}); | |
| if (R.isPhi()) | |
| Unpack->insertBefore(*VPBB, VPBB->getFirstNonPhi()); | |
| else | |
| Unpack->insertAfter(&R); | |
| Def->replaceUsesWithIf(Unpack, [&Def](VPUser &U, unsigned) { | |
| return U.usesFirstLaneOnly(Def); | |
| }); | |
| } | |
| } | |
| } | |
| } | |
| void VPlanTransforms::materializeVectorTripCount( | |
| VPlan &Plan, VPBasicBlock *VectorPHVPBB, bool TailByMasking, | |
| bool RequiresScalarEpilogue, VPValue *Step, | |
| std::optional<uint64_t> MaxRuntimeStep) { | |
| VPSymbolicValue &VectorTC = Plan.getVectorTripCount(); | |
| // There's nothing to do if there are no users of the vector trip count or its | |
| // IR value has already been set. | |
| if (VectorTC.user_empty() || VectorTC.getUnderlyingValue()) | |
| return; | |
| VPValue *TC = Plan.getTripCount(); | |
| Type *TCTy = TC->getScalarType(); | |
| VPBasicBlock::iterator InsertPt = VectorPHVPBB->begin(); | |
| if (auto *StepR = Step->getDefiningRecipe()) { | |
| assert(VPDominatorTree(Plan).dominates(StepR->getParent(), VectorPHVPBB) && | |
| "Step VPBB must dominate VectorPHVPBB"); | |
| // Insert after Step's definition to maintain valid def-use ordering. | |
| InsertPt = std::next(StepR->getIterator()); | |
| } | |
| VPBuilder Builder(VectorPHVPBB, InsertPt); | |
| // For scalable steps, if TC is a constant and is divisible by the maximum | |
| // possible runtime step, then TC % Step == 0 for all valid vscale values | |
| // and the vector trip count equals TC directly. | |
| const APInt *TCVal; | |
| if (!RequiresScalarEpilogue && match(TC, m_APInt(TCVal)) && MaxRuntimeStep && | |
| TCVal->urem(*MaxRuntimeStep) == 0) { | |
| VectorTC.replaceAllUsesWith(TC); | |
| return; | |
| } | |
| // If the tail is to be folded by masking, round the number of iterations N | |
| // up to a multiple of Step instead of rounding down. This is done by first | |
| // adding Step-1 and then rounding down. Note that it's ok if this addition | |
| // overflows: the vector induction variable will eventually wrap to zero given | |
| // that it starts at zero and its Step is a power of two; the loop will then | |
| // exit, with the last early-exit vector comparison also producing all-true. | |
| if (TailByMasking) { | |
| TC = Builder.createAdd( | |
| TC, Builder.createSub(Step, Plan.getConstantInt(TCTy, 1)), | |
| DebugLoc::getCompilerGenerated(), "n.rnd.up"); | |
| } | |
| // Now we need to generate the expression for the part of the loop that the | |
| // vectorized body will execute. This is equal to N - (N % Step) if scalar | |
| // iterations are not required for correctness, or N - Step, otherwise. Step | |
| // is equal to the vectorization factor (number of SIMD elements) times the | |
| // unroll factor (number of SIMD instructions). | |
| VPValue *R = | |
| Builder.createNaryOp(Instruction::URem, {TC, Step}, | |
| DebugLoc::getCompilerGenerated(), "n.mod.vf"); | |
| // There are cases where we *must* run at least one iteration in the remainder | |
| // loop. See the cost model for when this can happen. If the step evenly | |
| // divides the trip count, we set the remainder to be equal to the step. If | |
| // the step does not evenly divide the trip count, no adjustment is necessary | |
| // since there will already be scalar iterations. Note that the minimum | |
| // iterations check ensures that N >= Step. | |
| if (RequiresScalarEpilogue) { | |
| assert(!TailByMasking && | |
| "requiring scalar epilogue is not supported with fail folding"); | |
| VPValue *IsZero = | |
| Builder.createICmp(CmpInst::ICMP_EQ, R, Plan.getZero(TCTy)); | |
| R = Builder.createSelect(IsZero, Step, R); | |
| } | |
| VPValue *Res = | |
| Builder.createSub(TC, R, DebugLoc::getCompilerGenerated(), "n.vec"); | |
| VectorTC.replaceAllUsesWith(Res); | |
| } | |
| void VPlanTransforms::materializeFactors(VPlan &Plan, VPBasicBlock *VectorPH, | |
| ElementCount VFEC) { | |
| // If VF and VFxUF have already been materialized (no remaining users), | |
| // there's nothing more to do. | |
| if (Plan.getVF().isMaterialized()) { | |
| assert(Plan.getVFxUF().isMaterialized() && | |
| "VF and VFxUF must be materialized together"); | |
| return; | |
| } | |
| VPBuilder Builder(VectorPH, VectorPH->begin()); | |
| Type *TCTy = Plan.getTripCount()->getScalarType(); | |
| VPValue &VF = Plan.getVF(); | |
| VPValue &VFxUF = Plan.getVFxUF(); | |
| // If there are no users of the runtime VF, compute VFxUF by constant folding | |
| // the multiplication of VF and UF. | |
| if (VF.user_empty()) { | |
| VPValue *RuntimeVFxUF = | |
| Builder.createElementCount(TCTy, VFEC * Plan.getConcreteUF()); | |
| VFxUF.replaceAllUsesWith(RuntimeVFxUF); | |
| return; | |
| } | |
| // For users of the runtime VF, compute it as VF * vscale, and VFxUF as (VF * | |
| // vscale) * UF. | |
| VPValue *RuntimeVF = Builder.createElementCount(TCTy, VFEC); | |
| if (!vputils::onlyScalarValuesUsed(&VF)) { | |
| VPValue *BC = Builder.createNaryOp(VPInstruction::Broadcast, RuntimeVF); | |
| VF.replaceUsesWithIf( | |
| BC, [&VF](VPUser &U, unsigned) { return !U.usesScalars(&VF); }); | |
| } | |
| VF.replaceAllUsesWith(RuntimeVF); | |
| VPValue *MulByUF = Builder.createOverflowingOp( | |
| Instruction::Mul, | |
| {RuntimeVF, Plan.getConstantInt(TCTy, Plan.getConcreteUF())}, | |
| {true, false}); | |
| VFxUF.replaceAllUsesWith(MulByUF); | |
| } | |
| void VPlanTransforms::attachAliasMaskToHeaderMask(VPlan &Plan) { | |
| VPRegionBlock *LoopRegion = Plan.getVectorLoopRegion(); | |
| VPValue *HeaderMask = LoopRegion->getHeaderMask(); | |
| Type *I1Ty = IntegerType::getInt1Ty(Plan.getContext()); | |
| VPBuilder Builder(Plan.getVectorPreheader()); | |
| auto *AliasMask = Builder.createNaryOp( | |
| VPInstruction::IncomingAliasMask, {}, nullptr, {}, {}, | |
| DebugLoc::getUnknown(), "incoming.alias.mask", I1Ty); | |
| VPBasicBlock *Header = LoopRegion->getEntryBasicBlock(); | |
| Builder = VPBuilder(Header, Header->getFirstNonPhi()); | |
| // Update all existing users of the header mask to "HeaderMask & AliasMask". | |
| auto *ClampedHeaderMask = Builder.createAnd(HeaderMask, AliasMask); | |
| HeaderMask->replaceUsesWithIf(ClampedHeaderMask, [&](VPUser &U, unsigned) { | |
| return &U != ClampedHeaderMask; | |
| }); | |
| } | |
| VPValue * | |
| VPlanTransforms::materializeAliasMask(VPlan &Plan, VPBasicBlock *AliasCheckVPBB, | |
| ArrayRef<PointerDiffInfo> DiffChecks) { | |
| VPBuilder Builder(AliasCheckVPBB); | |
| Type *I1Ty = IntegerType::getInt1Ty(Plan.getContext()); | |
| VPValue *IncomingAliasMask = vputils::findIncomingAliasMask(Plan); | |
| assert(IncomingAliasMask && "Expected an alias mask!"); | |
| VPValue *AliasMask = nullptr; | |
| for (const PointerDiffInfo &Check : DiffChecks) { | |
| VPValue *Src = vputils::getOrCreateVPValueForSCEVExpr(Plan, Check.SrcStart); | |
| VPValue *Sink = | |
| vputils::getOrCreateVPValueForSCEVExpr(Plan, Check.SinkStart); | |
| Type *AddrType = Src->getScalarType(); | |
| // TODO: Only freeze the required pointer (not both src and sink). | |
| if (Check.NeedsFreeze) { | |
| Src = Builder.createScalarFreeze(Src, AddrType, DebugLoc::getUnknown()); | |
| Sink = Builder.createScalarFreeze(Sink, AddrType, DebugLoc::getUnknown()); | |
| } | |
| // TODO: Generate loop_dependence_raw_mask when there's a read-after-write | |
| // dependency between the source and the sink. This is not necessary for | |
| // correctness of the mask, but using the "raw" variant prevents loads | |
| // depending on the completion of stores. | |
| VPWidenIntrinsicRecipe *WARMask = Builder.insert(new VPWidenIntrinsicRecipe( | |
| Intrinsic::loop_dependence_war_mask, | |
| {Src, Sink, Plan.getConstantInt(AddrType, Check.AccessSize)}, I1Ty)); | |
| if (AliasMask) | |
| AliasMask = Builder.createAnd(AliasMask, WARMask); | |
| else | |
| AliasMask = WARMask; | |
| } | |
| Type *IVTy = Plan.getVectorLoopRegion()->getCanonicalIVType(); | |
| Type *IndexTy = Plan.getDataLayout().getIndexType(Plan.getContext(), 0); | |
| VPValue *NumActive = Builder.createNaryOp( | |
| VPInstruction::NumActiveLanes, {AliasMask}, nullptr, {}, {}, | |
| DebugLoc::getUnknown(), "num.active.lanes", IndexTy); | |
| VPValue *ClampedVF = Builder.createScalarZExtOrTrunc( | |
| NumActive, IVTy, IndexTy, DebugLoc::getCompilerGenerated()); | |
| IncomingAliasMask->replaceAllUsesWith(AliasMask); | |
| return ClampedVF; | |
| } | |
| void VPlanTransforms::materializeAliasMaskCheckBlock( | |
| VPlan &Plan, ArrayRef<PointerDiffInfo> DiffChecks, bool HasBranchWeights) { | |
| VPBasicBlock *ClampedVFCheck = | |
| Plan.createVPBasicBlock("vector.clamped.vf.check"); | |
| VPValue *ClampedVF = materializeAliasMask(Plan, ClampedVFCheck, DiffChecks); | |
| VPBuilder Builder(ClampedVFCheck); | |
| DebugLoc DL = DebugLoc::getCompilerGenerated(); | |
| Type *TCTy = Plan.getTripCount()->getScalarType(); | |
| // Check the "ClampedVF" from the alias mask is larger than one. | |
| VPValue *IsScalar = | |
| Builder.createICmp(CmpInst::ICMP_ULE, ClampedVF, | |
| Plan.getConstantInt(TCTy, 1), DL, "vf.is.scalar"); | |
| VPValue *TripCount = Plan.getTripCount(); | |
| VPValue *MaxUIntTripCount = | |
| Plan.getConstantInt(cast<IntegerType>(TCTy)->getMask()); | |
| VPValue *DistanceToMax = Builder.createSub(MaxUIntTripCount, TripCount); | |
| // For tail-folding: Don't execute the vector loop if (UMax - n) < ClampedVF. | |
| // Note: The ClampedVF may not be a power-of-two. This means the loop exit | |
| // condition (index.next == n.vec) may not be correct in the case of an | |
| // overflow. The issue is `n.vec` could be zero due to an overflow, but | |
| // index.next is not guaranteed to overflow to zero as the ClampedVF is not a | |
| // power-of-two). | |
| VPValue *TripCountCheck = Builder.createICmp( | |
| ICmpInst::ICMP_ULT, DistanceToMax, ClampedVF, DL, "vf.step.overflow"); | |
| VPValue *Cond = Builder.createOr(IsScalar, TripCountCheck, DL); | |
| attachVPCheckBlock(Plan, Cond, ClampedVFCheck, HasBranchWeights); | |
| // Materialize the trip count early as this will add a use of (VFxUF) that | |
| // needs to be replaced with the ClampedVF. | |
| materializeVectorTripCount(Plan, Plan.getVectorPreheader(), | |
| /*TailByMasking=*/true, | |
| /*RequiresScalarEpilogue=*/false, | |
| &Plan.getVFxUF()); | |
| assert(Plan.getConcreteUF() == 1 && | |
| "Clamped VF not supported with interleaving"); | |
| Plan.getVF().replaceAllUsesWith(ClampedVF); | |
| Plan.getVFxUF().replaceAllUsesWith(ClampedVF); | |
| } | |
| void VPlanTransforms::expandSCEVsToVPInstructions(VPlan &Plan, | |
| ScalarEvolution &SE) { | |
| auto *Entry = Plan.getEntry(); | |
| VPBuilder Builder(Entry, Entry->begin()); | |
| DebugLoc DL = cast<VPIRBasicBlock>(Entry) | |
| ->getIRBasicBlock() | |
| ->getTerminator() | |
| ->getDebugLoc(); | |
| VPSCEVExpander Expander(Builder, SE, DL); | |
| // Expand VPExpandSCEVRecipes to VPInstructions using VPSCEVExpander. During | |
| // the transition, unsupported VPExpandSCEVRecipes are skipped and left for | |
| // late expansion. | |
| for (VPRecipeBase &R : make_early_inc_range(*Entry)) { | |
| auto *ExpSCEV = dyn_cast<VPExpandSCEVRecipe>(&R); | |
| if (!ExpSCEV || ExpSCEV->user_empty()) | |
| continue; | |
| Builder.setInsertPoint(ExpSCEV); | |
| VPValue *Expanded = Expander.tryToExpand(ExpSCEV->getSCEV()); | |
| if (!Expanded) | |
| continue; | |
| ExpSCEV->replaceAllUsesWith(Expanded); | |
| // TripCount should not be used after expansion to VPInstructions. Reset to | |
| // poison to avoid dangling references. | |
| if (Plan.getTripCount() == ExpSCEV) | |
| Plan.resetTripCount(Plan.getPoison(ExpSCEV->getScalarType())); | |
| ExpSCEV->eraseFromParent(); | |
| } | |
| } | |
| DenseMap<const SCEV *, Value *> | |
| VPlanTransforms::expandSCEVs(VPlan &Plan, ScalarEvolution &SE) { | |
| SCEVExpander Expander(SE, "induction", /*PreserveLCSSA=*/false); | |
| auto *Entry = cast<VPIRBasicBlock>(Plan.getEntry()); | |
| BasicBlock *EntryBB = Entry->getIRBasicBlock(); | |
| DenseMap<const SCEV *, Value *> ExpandedSCEVs; | |
| // Expand remaining VPExpandSCEVRecipes to IR instructions using SCEVExpander. | |
| for (VPRecipeBase &R : make_early_inc_range(*Entry)) { | |
| auto *ExpSCEV = dyn_cast<VPExpandSCEVRecipe>(&R); | |
| if (!ExpSCEV) | |
| continue; | |
| const SCEV *Expr = ExpSCEV->getSCEV(); | |
| Value *Res = | |
| Expander.expandCodeFor(Expr, Expr->getType(), EntryBB->getTerminator()); | |
| ExpandedSCEVs[Expr] = Res; | |
| VPValue *Exp = Plan.getOrAddLiveIn(Res); | |
| ExpSCEV->replaceAllUsesWith(Exp); | |
| if (Plan.getTripCount() == ExpSCEV) | |
| Plan.resetTripCount(Exp); | |
| ExpSCEV->eraseFromParent(); | |
| } | |
| assert(none_of(*Entry, IsaPred<VPExpandSCEVRecipe>) && | |
| "all VPExpandSCEVRecipes must have been expanded"); | |
| // Add IR instructions in the entry basic block but not in the VPIRBasicBlock | |
| // to the VPIRBasicBlock. | |
| auto EI = Entry->begin(); | |
| for (Instruction &I : drop_end(*EntryBB)) { | |
| if (EI != Entry->end() && isa<VPIRInstruction>(*EI) && | |
| &cast<VPIRInstruction>(&*EI)->getInstruction() == &I) { | |
| EI++; | |
| continue; | |
| } | |
| VPIRInstruction::create(I)->insertBefore(*Entry, EI); | |
| } | |
| return ExpandedSCEVs; | |
| } | |
| /// Returns true if \p V is VPWidenLoadRecipe or VPInterleaveRecipe that can be | |
| /// converted to a narrower recipe. \p V is used by a wide recipe that feeds a | |
| /// store interleave group at index \p Idx, \p WideMember0 is the recipe feeding | |
| /// the same interleave group at index 0. A VPWidenLoadRecipe can be narrowed to | |
| /// an index-independent load if it feeds all wide ops at all indices (\p OpV | |
| /// must be the operand at index \p OpIdx for both the recipe at lane 0, \p | |
| /// WideMember0). A VPInterleaveRecipe can be narrowed to a wide load, if \p V | |
| /// is defined at \p Idx of a load interleave group. | |
| /// A live-in or recipe defined outside the loop region can be converted, if it | |
| /// is the same across all lanes, or we can create a BuildVector for it. | |
| static bool canNarrowLoad(VPSingleDefRecipe *WideMember0, unsigned OpIdx, | |
| VPValue *OpV, unsigned Idx, bool IsScalable) { | |
| VPValue *Member0Op = WideMember0->getOperand(OpIdx); | |
| if (Member0Op->isDefinedOutsideLoopRegions()) { | |
| // Operand matches Member0, broadcast across all fields for both live-ins | |
| // and recipes. | |
| if (Member0Op == OpV) | |
| return true; | |
| // Otherwise distinct per-field VPValues are assembled into a BuildVector. | |
| return !IsScalable && OpV->isDefinedOutsideLoopRegions() && | |
| OpV->getScalarType() == Member0Op->getScalarType(); | |
| } | |
| VPRecipeBase *Member0OpR = Member0Op->getDefiningRecipe(); | |
| if (auto *W = dyn_cast<VPWidenLoadRecipe>(Member0OpR)) | |
| // For scalable VFs, the narrowed plan processes vscale iterations at once, | |
| // so a shared wide load cannot be narrowed to a uniform scalar; bail out. | |
| return !IsScalable && !W->getMask() && W->isConsecutive() && | |
| Member0Op == OpV; | |
| if (auto *IR = dyn_cast<VPInterleaveRecipe>(Member0OpR)) | |
| return IR->getInterleaveGroup()->isFull() && IR->getVPValue(Idx) == OpV; | |
| return false; | |
| } | |
| static bool canNarrowOps(ArrayRef<VPValue *> Ops, bool IsScalable) { | |
| SmallVector<VPValue *> Ops0; | |
| auto *WideMember0 = dyn_cast<VPRecipeWithIRFlags>(Ops[0]); | |
| if (!WideMember0) | |
| return false; | |
| for (VPValue *V : Ops) { | |
| if (!isa<VPWidenRecipe, VPWidenCastRecipe>(V)) | |
| return false; | |
| auto *R = cast<VPRecipeWithIRFlags>(V); | |
| if (getOpcodeOrIntrinsicID(R) != getOpcodeOrIntrinsicID(WideMember0)) | |
| return false; | |
| if (R->getScalarType() != WideMember0->getScalarType()) | |
| return false; | |
| if (R->hasPredicate() && R->getPredicate() != WideMember0->getPredicate()) | |
| return false; | |
| } | |
| for (unsigned Idx = 0; Idx != WideMember0->getNumOperands(); ++Idx) { | |
| SmallVector<VPValue *> OpsI; | |
| for (VPValue *Op : Ops) | |
| OpsI.push_back(Op->getDefiningRecipe()->getOperand(Idx)); | |
| if (canNarrowOps(OpsI, IsScalable)) | |
| continue; | |
| if (any_of(enumerate(OpsI), [WideMember0, Idx, IsScalable](const auto &P) { | |
| const auto &[OpIdx, OpV] = P; | |
| return !canNarrowLoad(WideMember0, Idx, OpV, OpIdx, IsScalable); | |
| })) | |
| return false; | |
| } | |
| return true; | |
| } | |
| /// Returns VF from \p VFs if \p IR is a full interleave group with factor and | |
| /// number of members both equal to VF. The interleave group must also access | |
| /// the full vector width. | |
| static std::optional<ElementCount> | |
| isConsecutiveInterleaveGroup(VPInterleaveRecipe *InterleaveR, | |
| ArrayRef<ElementCount> VFs, | |
| const TargetTransformInfo &TTI) { | |
| if (!InterleaveR || InterleaveR->getMask()) | |
| return std::nullopt; | |
| Type *GroupElementTy = nullptr; | |
| if (InterleaveR->getStoredValues().empty()) { | |
| GroupElementTy = InterleaveR->getVPValue(0)->getScalarType(); | |
| if (!all_of(InterleaveR->definedValues(), [GroupElementTy](VPValue *Op) { | |
| return Op->getScalarType() == GroupElementTy; | |
| })) | |
| return std::nullopt; | |
| } else { | |
| GroupElementTy = InterleaveR->getStoredValues()[0]->getScalarType(); | |
| if (!all_of(InterleaveR->getStoredValues(), [GroupElementTy](VPValue *Op) { | |
| return Op->getScalarType() == GroupElementTy; | |
| })) | |
| return std::nullopt; | |
| } | |
| auto IG = InterleaveR->getInterleaveGroup(); | |
| if (IG->getFactor() != IG->getNumMembers()) | |
| return std::nullopt; | |
| auto GetVectorBitWidthForVF = [&TTI](ElementCount VF) { | |
| TypeSize Size = TTI.getRegisterBitWidth( | |
| VF.isFixed() ? TargetTransformInfo::RGK_FixedWidthVector | |
| : TargetTransformInfo::RGK_ScalableVector); | |
| assert(Size.isScalable() == VF.isScalable() && | |
| "if Size is scalable, VF must be scalable and vice versa"); | |
| return Size.getKnownMinValue(); | |
| }; | |
| for (ElementCount VF : VFs) { | |
| unsigned MinVal = VF.getKnownMinValue(); | |
| unsigned GroupSize = GroupElementTy->getScalarSizeInBits() * MinVal; | |
| if (IG->getFactor() == MinVal && GroupSize == GetVectorBitWidthForVF(VF)) | |
| return {VF}; | |
| } | |
| return std::nullopt; | |
| } | |
| /// Returns true if \p VPValue is a narrow VPValue. | |
| static bool isAlreadyNarrow(VPValue *VPV) { | |
| if (isa<VPIRValue>(VPV)) | |
| return true; | |
| auto *RepR = dyn_cast<VPReplicateRecipe>(VPV); | |
| return RepR && RepR->isSingleScalar(); | |
| } | |
| // Convert the wide recipes defining the VPValues in \p Members feeding an | |
| // interleave group to a single narrow variant. The first member is reused as | |
| // the narrowed recipe. BuildVectors for live-in operands are inserted into \p | |
| // Preheader. | |
| static VPValue *narrowInterleaveGroupOp(ArrayRef<VPValue *> Members, | |
| SmallPtrSetImpl<VPValue *> &NarrowedOps, | |
| VPBasicBlock *Preheader) { | |
| VPValue *V = Members.front(); | |
| if (NarrowedOps.contains(V)) | |
| return V; | |
| if (V->isDefinedOutsideLoopRegions()) { | |
| assert(all_of(Members, | |
| [V](VPValue *M) { | |
| return M->isDefinedOutsideLoopRegions() && | |
| M->getScalarType() == V->getScalarType(); | |
| }) && | |
| "expected distinct loop-invariant values of matching scalar type"); | |
| auto *BV = new VPInstruction(VPInstruction::BuildVector, Members); | |
| Preheader->appendRecipe(BV); | |
| NarrowedOps.insert(BV); | |
| return BV; | |
| } | |
| if (isAlreadyNarrow(V)) | |
| return V; | |
| VPRecipeBase *R = V->getDefiningRecipe(); | |
| if (isa<VPWidenRecipe, VPWidenCastRecipe>(R)) { | |
| auto *WideMember0 = cast<VPRecipeWithIRFlags>(R); | |
| for (VPValue *Member : Members.drop_front()) | |
| WideMember0->intersectFlags(*cast<VPRecipeWithIRFlags>(Member)); | |
| for (unsigned Idx = 0, E = WideMember0->getNumOperands(); Idx != E; ++Idx) { | |
| SmallVector<VPValue *> OpsI; | |
| for (VPValue *Member : Members) | |
| OpsI.push_back(Member->getDefiningRecipe()->getOperand(Idx)); | |
| WideMember0->setOperand( | |
| Idx, narrowInterleaveGroupOp(OpsI, NarrowedOps, Preheader)); | |
| } | |
| return V; | |
| } | |
| if (auto *LoadGroup = dyn_cast<VPInterleaveRecipe>(R)) { | |
| // Narrow interleave group to wide load, as transformed VPlan will only | |
| // process one original iteration. | |
| auto *LI = cast<LoadInst>(LoadGroup->getInterleaveGroup()->getInsertPos()); | |
| auto *L = new VPWidenLoadRecipe(*LI, LoadGroup->getAddr(), | |
| LoadGroup->getMask(), /*Consecutive=*/true, | |
| *LoadGroup, LoadGroup->getDebugLoc()); | |
| L->insertBefore(LoadGroup); | |
| NarrowedOps.insert(L); | |
| return L; | |
| } | |
| if (auto *RepR = dyn_cast<VPReplicateRecipe>(R)) { | |
| assert(RepR->isSingleScalar() && RepR->getOpcode() == Instruction::Load && | |
| "must be a single scalar load"); | |
| NarrowedOps.insert(RepR); | |
| return RepR; | |
| } | |
| auto *WideLoad = cast<VPWidenLoadRecipe>(R); | |
| VPValue *PtrOp = WideLoad->getAddr(); | |
| if (auto *VecPtr = dyn_cast<VPVectorPointerRecipe>(PtrOp)) | |
| PtrOp = VecPtr->getOperand(0); | |
| // Narrow wide load to uniform scalar load, as transformed VPlan will only | |
| // process one original iteration. | |
| auto *N = new VPReplicateRecipe(&WideLoad->getIngredient(), {PtrOp}, | |
| /*IsUniform*/ true, | |
| /*Mask*/ nullptr, {}, *WideLoad); | |
| N->insertBefore(WideLoad); | |
| NarrowedOps.insert(N); | |
| return N; | |
| } | |
| std::unique_ptr<VPlan> | |
| VPlanTransforms::narrowInterleaveGroups(VPlan &Plan, | |
| const TargetTransformInfo &TTI) { | |
| VPRegionBlock *VectorLoop = Plan.getVectorLoopRegion(); | |
| if (!VectorLoop) | |
| return nullptr; | |
| // Only handle single-block loops for now. | |
| if (VectorLoop->getEntryBasicBlock() != VectorLoop->getExitingBasicBlock()) | |
| return nullptr; | |
| // Skip plans when we may not be able to properly narrow. | |
| VPBasicBlock *Exiting = VectorLoop->getExitingBasicBlock(); | |
| if (!match(&Exiting->back(), m_BranchOnCount())) | |
| return nullptr; | |
| assert(match(&Exiting->back(), | |
| m_BranchOnCount(m_Add(m_VPValue(), m_Specific(&Plan.getVFxUF())), | |
| m_Specific(&Plan.getVectorTripCount()))) && | |
| "unexpected branch-on-count"); | |
| SmallVector<VPInterleaveRecipe *> StoreGroups; | |
| std::optional<ElementCount> VFToOptimize; | |
| for (auto &R : *VectorLoop->getEntryBasicBlock()) { | |
| if (isa<VPDerivedIVRecipe, VPScalarIVStepsRecipe>(&R) && | |
| vputils::onlyFirstLaneUsed(cast<VPSingleDefRecipe>(&R))) | |
| continue; | |
| // Bail out on recipes not supported at the moment: | |
| // * phi recipes other than the canonical induction | |
| // * recipes writing to memory except interleave groups | |
| // Only support plans with a canonical induction phi. | |
| if (R.isPhi()) | |
| return nullptr; | |
| auto *InterleaveR = dyn_cast<VPInterleaveRecipe>(&R); | |
| if (R.mayWriteToMemory() && !InterleaveR) | |
| return nullptr; | |
| // Bail out if any recipe defines a vector value used outside the | |
| // vector loop region. | |
| if (any_of(R.definedValues(), [&](VPValue *V) { | |
| return any_of(V->users(), [&](VPUser *U) { | |
| auto *UR = cast<VPRecipeBase>(U); | |
| return UR->getParent()->getParent() != VectorLoop; | |
| }); | |
| })) | |
| return nullptr; | |
| // All other ops are allowed, but we reject uses that cannot be converted | |
| // when checking all allowed consumers (store interleave groups) below. | |
| if (!InterleaveR) | |
| continue; | |
| // Try to find a single VF, where all interleave groups are consecutive and | |
| // saturate the full vector width. If we already have a candidate VF, check | |
| // if it is applicable for the current InterleaveR, otherwise look for a | |
| // suitable VF across the Plan's VFs. | |
| SmallVector<ElementCount> VFs = | |
| VFToOptimize ? SmallVector<ElementCount>({*VFToOptimize}) | |
| : to_vector(Plan.vectorFactors()); | |
| std::optional<ElementCount> NarrowedVF = | |
| isConsecutiveInterleaveGroup(InterleaveR, VFs, TTI); | |
| if (!NarrowedVF || (VFToOptimize && NarrowedVF != VFToOptimize)) | |
| return nullptr; | |
| VFToOptimize = NarrowedVF; | |
| // Skip read interleave groups. | |
| if (InterleaveR->getStoredValues().empty()) | |
| continue; | |
| // Narrow interleave groups, if all operands are already matching narrow | |
| // ops. | |
| auto *Member0 = InterleaveR->getStoredValues()[0]; | |
| if (isAlreadyNarrow(Member0) && | |
| all_of(InterleaveR->getStoredValues(), equal_to(Member0))) { | |
| StoreGroups.push_back(InterleaveR); | |
| continue; | |
| } | |
| // For now, we only support full interleave groups storing load interleave | |
| // groups. | |
| if (all_of(enumerate(InterleaveR->getStoredValues()), [](auto Op) { | |
| VPRecipeBase *DefR = Op.value()->getDefiningRecipe(); | |
| if (!DefR) | |
| return false; | |
| auto *IR = dyn_cast<VPInterleaveRecipe>(DefR); | |
| return IR && IR->getInterleaveGroup()->isFull() && | |
| IR->getVPValue(Op.index()) == Op.value(); | |
| })) { | |
| StoreGroups.push_back(InterleaveR); | |
| continue; | |
| } | |
| // Check if all values feeding InterleaveR are matching wide recipes, which | |
| // operands that can be narrowed. | |
| if (!canNarrowOps(InterleaveR->getStoredValues(), | |
| VFToOptimize->isScalable())) | |
| return nullptr; | |
| StoreGroups.push_back(InterleaveR); | |
| } | |
| if (StoreGroups.empty()) | |
| return nullptr; | |
| VPBasicBlock *MiddleVPBB = Plan.getMiddleBlock(); | |
| bool RequiresScalarEpilogue = | |
| MiddleVPBB->getNumSuccessors() == 1 && | |
| MiddleVPBB->getSingleSuccessor() == Plan.getScalarPreheader(); | |
| // Bail out for tail-folding (middle block with a single successor to exit). | |
| if (MiddleVPBB->getNumSuccessors() != 2 && !RequiresScalarEpilogue) | |
| return nullptr; | |
| // All interleave groups in Plan can be narrowed for VFToOptimize. Split the | |
| // original Plan into 2: a) a new clone which contains all VFs of Plan, except | |
| // VFToOptimize, and b) the original Plan with VFToOptimize as single VF. | |
| // TODO: Handle cases where only some interleave groups can be narrowed. | |
| std::unique_ptr<VPlan> NewPlan; | |
| if (size(Plan.vectorFactors()) != 1) { | |
| NewPlan = std::unique_ptr<VPlan>(Plan.duplicate()); | |
| Plan.setVF(*VFToOptimize); | |
| NewPlan->removeVF(*VFToOptimize); | |
| } | |
| // Convert InterleaveGroup \p R to a single VPWidenLoadRecipe. | |
| SmallPtrSet<VPValue *, 4> NarrowedOps; | |
| VPBasicBlock *Preheader = Plan.getVectorPreheader(); | |
| // Narrow operation tree rooted at store groups. | |
| for (auto *StoreGroup : StoreGroups) { | |
| VPValue *Res = narrowInterleaveGroupOp(StoreGroup->getStoredValues(), | |
| NarrowedOps, Preheader); | |
| auto *SI = | |
| cast<StoreInst>(StoreGroup->getInterleaveGroup()->getInsertPos()); | |
| auto *S = new VPWidenStoreRecipe(*SI, StoreGroup->getAddr(), Res, nullptr, | |
| /*Consecutive=*/true, *StoreGroup, | |
| StoreGroup->getDebugLoc()); | |
| S->insertBefore(StoreGroup); | |
| StoreGroup->eraseFromParent(); | |
| } | |
| // Adjust induction to reflect that the transformed plan only processes one | |
| // original iteration. | |
| VPInstruction *CanIVInc = vputils::findCanonicalIVIncrement(Plan); | |
| Type *CanIVTy = VectorLoop->getCanonicalIVType(); | |
| VPBasicBlock *VectorPH = Plan.getVectorPreheader(); | |
| VPBuilder PHBuilder(VectorPH, VectorPH->begin()); | |
| VPValue *UF = &Plan.getUF(); | |
| VPValue *Step; | |
| if (VFToOptimize->isScalable()) { | |
| VPValue *VScale = | |
| PHBuilder.createElementCount(CanIVTy, ElementCount::getScalable(1)); | |
| Step = PHBuilder.createOverflowingOp(Instruction::Mul, {VScale, UF}, | |
| {true, false}); | |
| Plan.getVF().replaceAllUsesWith(VScale); | |
| } else { | |
| Step = UF; | |
| Plan.getVF().replaceAllUsesWith(Plan.getConstantInt(CanIVTy, 1)); | |
| } | |
| // Materialize vector trip count with the narrowed step. | |
| materializeVectorTripCount(Plan, VectorPH, /*TailByMasking=*/false, | |
| RequiresScalarEpilogue, Step); | |
| CanIVInc->setOperand(1, Step); | |
| Plan.getVFxUF().replaceAllUsesWith(Step); | |
| removeDeadRecipes(Plan); | |
| assert(none_of(*VectorLoop->getEntryBasicBlock(), | |
| IsaPred<VPVectorPointerRecipe>) && | |
| "All VPVectorPointerRecipes should have been removed"); | |
| return NewPlan; | |
| } | |
| /// Add branch weight metadata, if the \p Plan's middle block is terminated by a | |
| /// BranchOnCond recipe. | |
| void VPlanTransforms::addBranchWeightToMiddleTerminator( | |
| VPlan &Plan, ElementCount VF, std::optional<unsigned> VScaleForTuning) { | |
| VPBasicBlock *MiddleVPBB = Plan.getMiddleBlock(); | |
| auto *MiddleTerm = | |
| dyn_cast_or_null<VPInstruction>(MiddleVPBB->getTerminator()); | |
| // Only add branch metadata if there is a (conditional) terminator. | |
| if (!MiddleTerm) | |
| return; | |
| assert(MiddleTerm->getOpcode() == VPInstruction::BranchOnCond && | |
| "must have a BranchOnCond"); | |
| // Assume that `TripCount % VectorStep ` is equally distributed. | |
| unsigned VectorStep = Plan.getConcreteUF() * VF.getKnownMinValue(); | |
| if (VF.isScalable() && VScaleForTuning.has_value()) | |
| VectorStep *= *VScaleForTuning; | |
| assert(VectorStep > 0 && "trip count should not be zero"); | |
| MDBuilder MDB(Plan.getContext()); | |
| MDNode *BranchWeights = | |
| MDB.createBranchWeights({1, VectorStep - 1}, /*IsExpected=*/false); | |
| MiddleTerm->setMetadata(LLVMContext::MD_prof, BranchWeights); | |
| } | |
| void VPlanTransforms::adjustFirstOrderRecurrenceMiddleUsers(VPlan &Plan, | |
| VFRange &Range) { | |
| VPRegionBlock *VectorRegion = Plan.getVectorLoopRegion(); | |
| auto *MiddleVPBB = Plan.getMiddleBlock(); | |
| VPBuilder MiddleBuilder(MiddleVPBB, MiddleVPBB->getFirstNonPhi()); | |
| auto IsScalableOne = [](ElementCount VF) -> bool { | |
| return VF == ElementCount::getScalable(1); | |
| }; | |
| for (auto &HeaderPhi : VectorRegion->getEntryBasicBlock()->phis()) { | |
| auto *FOR = dyn_cast<VPFirstOrderRecurrencePHIRecipe>(&HeaderPhi); | |
| if (!FOR) | |
| continue; | |
| assert(VectorRegion->getSingleSuccessor() == Plan.getMiddleBlock() && | |
| "Cannot handle loops with uncountable early exits"); | |
| // Find the existing splice for this FOR, created in | |
| // createHeaderPhiRecipes. All uses of FOR have already been replaced with | |
| // RecurSplice there; only RecurSplice itself still references FOR. | |
| auto *RecurSplice = | |
| findUserOf<VPInstruction::FirstOrderRecurrenceSplice>(FOR); | |
| assert(RecurSplice && "expected FirstOrderRecurrenceSplice"); | |
| // For VF vscale x 1, if vscale = 1, we are unable to extract the | |
| // penultimate value of the recurrence. Instead we rely on the existing | |
| // extract of the last element from the result of | |
| // VPInstruction::FirstOrderRecurrenceSplice. | |
| // TODO: Consider vscale_range info and UF. | |
| if (any_of(RecurSplice->users(), | |
| [](VPUser *U) { return !cast<VPRecipeBase>(U)->getRegion(); }) && | |
| LoopVectorizationPlanner::getDecisionAndClampRange(IsScalableOne, | |
| Range)) | |
| return; | |
| // This is the second phase of vectorizing first-order recurrences, creating | |
| // extracts for users outside the loop. An overview of the transformation is | |
| // described below. Suppose we have the following loop with some use after | |
| // the loop of the last a[i-1], | |
| // | |
| // for (int i = 0; i < n; ++i) { | |
| // t = a[i - 1]; | |
| // b[i] = a[i] - t; | |
| // } | |
| // use t; | |
| // | |
| // There is a first-order recurrence on "a". For this loop, the shorthand | |
| // scalar IR looks like: | |
| // | |
| // scalar.ph: | |
| // s.init = a[-1] | |
| // br scalar.body | |
| // | |
| // scalar.body: | |
| // i = phi [0, scalar.ph], [i+1, scalar.body] | |
| // s1 = phi [s.init, scalar.ph], [s2, scalar.body] | |
| // s2 = a[i] | |
| // b[i] = s2 - s1 | |
| // br cond, scalar.body, exit.block | |
| // | |
| // exit.block: | |
| // use = lcssa.phi [s1, scalar.body] | |
| // | |
| // In this example, s1 is a recurrence because it's value depends on the | |
| // previous iteration. In the first phase of vectorization, we created a | |
| // VPFirstOrderRecurrencePHIRecipe v1 for s1. Now we create the extracts | |
| // for users in the scalar preheader and exit block. | |
| // | |
| // vector.ph: | |
| // v_init = vector(..., ..., ..., a[-1]) | |
| // br vector.body | |
| // | |
| // vector.body | |
| // i = phi [0, vector.ph], [i+4, vector.body] | |
| // v1 = phi [v_init, vector.ph], [v2, vector.body] | |
| // v2 = a[i, i+1, i+2, i+3] | |
| // v1' = splice(v1(3), v2(0, 1, 2)) | |
| // b[i, i+1, i+2, i+3] = v2 - v1' | |
| // br cond, vector.body, middle.block | |
| // | |
| // middle.block: | |
| // vector.recur.extract.for.phi = v2(2) | |
| // vector.recur.extract = v2(3) | |
| // br cond, scalar.ph, exit.block | |
| // | |
| // scalar.ph: | |
| // scalar.recur.init = phi [vector.recur.extract, middle.block], | |
| // [s.init, otherwise] | |
| // br scalar.body | |
| // | |
| // scalar.body: | |
| // i = phi [0, scalar.ph], [i+1, scalar.body] | |
| // s1 = phi [scalar.recur.init, scalar.ph], [s2, scalar.body] | |
| // s2 = a[i] | |
| // b[i] = s2 - s1 | |
| // br cond, scalar.body, exit.block | |
| // | |
| // exit.block: | |
| // lo = lcssa.phi [s1, scalar.body], | |
| // [vector.recur.extract.for.phi, middle.block] | |
| // | |
| // Update extracts of the splice in the middle block: they extract the | |
| // penultimate element of the recurrence. | |
| for (VPRecipeBase &R : make_early_inc_range( | |
| make_range(MiddleVPBB->getFirstNonPhi(), MiddleVPBB->end()))) { | |
| if (!match(&R, m_ExtractLastLaneOfLastPart(m_Specific(RecurSplice)))) | |
| continue; | |
| auto *ExtractR = cast<VPInstruction>(&R); | |
| VPValue *PenultimateElement = MiddleBuilder.createNaryOp( | |
| VPInstruction::ExtractPenultimateElement, RecurSplice->getOperand(1), | |
| {}, "vector.recur.extract.for.phi"); | |
| for (VPUser *ExitU : to_vector(ExtractR->users())) { | |
| if (auto *ExitPhi = dyn_cast<VPIRPhi>(ExitU)) | |
| ExitPhi->replaceUsesOfWith(ExtractR, PenultimateElement); | |
| } | |
| } | |
| } | |
| } | |
| /// Check if \p V is a binary expression of a widened IV and a loop-invariant | |
| /// value. Returns the widened IV if found, nullptr otherwise. | |
| static VPWidenIntOrFpInductionRecipe *getExpressionIV(VPValue *V) { | |
| auto *BinOp = dyn_cast<VPWidenRecipe>(V); | |
| if (!BinOp || !Instruction::isBinaryOp(BinOp->getOpcode()) || | |
| Instruction::isIntDivRem(BinOp->getOpcode())) | |
| return nullptr; | |
| VPValue *WidenIVCandidate = BinOp->getOperand(0); | |
| VPValue *InvariantCandidate = BinOp->getOperand(1); | |
| if (!isa<VPWidenIntOrFpInductionRecipe>(WidenIVCandidate)) | |
| std::swap(WidenIVCandidate, InvariantCandidate); | |
| if (!InvariantCandidate->isDefinedOutsideLoopRegions()) | |
| return nullptr; | |
| return dyn_cast<VPWidenIntOrFpInductionRecipe>(WidenIVCandidate); | |
| } | |
| /// Create a scalar version of \p BinOp, with its \p WidenIV operand replaced | |
| /// by \p ScalarIV, and place it after \p ScalarIV's defining recipe. | |
| static VPValue *cloneBinOpForScalarIV(VPWidenRecipe *BinOp, VPValue *ScalarIV, | |
| VPWidenIntOrFpInductionRecipe *WidenIV) { | |
| assert(Instruction::isBinaryOp(BinOp->getOpcode()) && | |
| BinOp->getNumOperands() == 2 && "BinOp must have 2 operands"); | |
| auto *ClonedOp = BinOp->clone(); | |
| if (ClonedOp->getOperand(0) == WidenIV) { | |
| ClonedOp->setOperand(0, ScalarIV); | |
| } else { | |
| assert(ClonedOp->getOperand(1) == WidenIV && "one operand must be WideIV"); | |
| ClonedOp->setOperand(1, ScalarIV); | |
| } | |
| ClonedOp->insertAfter(ScalarIV->getDefiningRecipe()); | |
| return ClonedOp; | |
| } | |
| void VPlanTransforms::optimizeFindIVReductions(VPlan &Plan, | |
| PredicatedScalarEvolution &PSE, | |
| Loop &L) { | |
| ScalarEvolution &SE = *PSE.getSE(); | |
| VPRegionBlock *VectorLoopRegion = Plan.getVectorLoopRegion(); | |
| // Helper lambda to check if the IV range excludes the sentinel value. Try | |
| // signed first, then unsigned. Return an excluded sentinel if found, | |
| // otherwise return std::nullopt. | |
| auto CheckSentinel = [&SE](const SCEV *IVSCEV, | |
| bool UseMax) -> std::optional<APSInt> { | |
| unsigned BW = IVSCEV->getType()->getScalarSizeInBits(); | |
| for (bool Signed : {true, false}) { | |
| APSInt Sentinel = UseMax ? APSInt::getMinValue(BW, /*Unsigned=*/!Signed) | |
| : APSInt::getMaxValue(BW, /*Unsigned=*/!Signed); | |
| ConstantRange IVRange = | |
| Signed ? SE.getSignedRange(IVSCEV) : SE.getUnsignedRange(IVSCEV); | |
| if (!IVRange.contains(Sentinel)) | |
| return Sentinel; | |
| } | |
| return std::nullopt; | |
| }; | |
| VPValue *HeaderMask = VectorLoopRegion->getHeaderMask(); | |
| for (VPRecipeBase &Phi : | |
| make_early_inc_range(VectorLoopRegion->getEntryBasicBlock()->phis())) { | |
| auto *PhiR = dyn_cast<VPReductionPHIRecipe>(&Phi); | |
| if (!PhiR || !RecurrenceDescriptor::isFindLastRecurrenceKind( | |
| PhiR->getRecurrenceKind())) | |
| continue; | |
| Type *PhiTy = PhiR->getScalarType(); | |
| if (PhiTy->isPointerTy() || PhiTy->isFloatingPointTy()) | |
| continue; | |
| // If there's a header mask, the backedge select will not be the find-last | |
| // select. | |
| VPValue *BackedgeVal = PhiR->getBackedgeValue(); | |
| auto *FindLastSelect = cast<VPSingleDefRecipe>(BackedgeVal); | |
| if (HeaderMask && | |
| !match(BackedgeVal, | |
| m_Select(m_Specific(HeaderMask), | |
| m_VPSingleDefRecipe(FindLastSelect), m_Specific(PhiR)))) | |
| continue; | |
| // Get the find-last expression from the find-last select of the reduction | |
| // phi. The find-last select should be a select between the phi and the | |
| // find-last expression. | |
| VPValue *Cond, *FindLastExpression; | |
| if (!match(FindLastSelect, m_SelectLike(m_VPValue(Cond), m_Specific(PhiR), | |
| m_VPValue(FindLastExpression))) && | |
| !match(FindLastSelect, | |
| m_SelectLike(m_VPValue(Cond), m_VPValue(FindLastExpression), | |
| m_Specific(PhiR)))) | |
| continue; | |
| // Check if FindLastExpression is a simple expression of a widened IV. If | |
| // so, we can track the underlying IV instead and sink the expression. | |
| auto *IVOfExpressionToSink = getExpressionIV(FindLastExpression); | |
| const SCEV *IVSCEV = vputils::getSCEVExprForVPValue( | |
| IVOfExpressionToSink ? IVOfExpressionToSink : FindLastExpression, PSE, | |
| &L); | |
| const SCEV *Step; | |
| if (!match(IVSCEV, m_scev_AffineAddRec(m_SCEV(), m_SCEV(Step)))) { | |
| assert(!match(vputils::getSCEVExprForVPValue(FindLastExpression, PSE, &L), | |
| m_scev_AffineAddRec(m_SCEV(), m_SCEV())) && | |
| "IVOfExpressionToSink not being an AddRec must imply " | |
| "FindLastExpression not being an AddRec."); | |
| continue; | |
| } | |
| // Determine direction from SCEV step. | |
| if (!SE.isKnownNonZero(Step)) | |
| continue; | |
| // Positive step means we need UMax/SMax to find the last IV value, and | |
| // UMin/SMin otherwise. | |
| bool UseMax = SE.isKnownPositive(Step); | |
| std::optional<APSInt> SentinelVal = CheckSentinel(IVSCEV, UseMax); | |
| bool UseSigned = SentinelVal && SentinelVal->isSigned(); | |
| // Sinking an expression will disable epilogue vectorization. Only use it, | |
| // if FindLastExpression cannot be vectorized via a sentinel. Sinking may | |
| // also prevent vectorizing using a sentinel (e.g., if the expression is a | |
| // multiply or divide by large constant, respectively), which also makes | |
| // sinking undesirable. | |
| if (IVOfExpressionToSink) { | |
| const SCEV *FindLastExpressionSCEV = | |
| vputils::getSCEVExprForVPValue(FindLastExpression, PSE, &L); | |
| if (match(FindLastExpressionSCEV, | |
| m_scev_AffineAddRec(m_SCEV(), m_SCEV(Step)))) { | |
| bool NewUseMax = SE.isKnownPositive(Step); | |
| if (auto NewSentinel = | |
| CheckSentinel(FindLastExpressionSCEV, NewUseMax)) { | |
| // The original expression already has a sentinel, so prefer not | |
| // sinking to keep epilogue vectorization possible. | |
| SentinelVal = *NewSentinel; | |
| UseSigned = NewSentinel->isSigned(); | |
| UseMax = NewUseMax; | |
| IVSCEV = FindLastExpressionSCEV; | |
| IVOfExpressionToSink = nullptr; | |
| } | |
| } | |
| } | |
| // If no sentinel was found, fall back to a boolean AnyOf reduction to track | |
| // if the condition was ever true. Requires the IV to not wrap, otherwise we | |
| // cannot use min/max. | |
| if (!SentinelVal) { | |
| auto *AR = cast<SCEVAddRecExpr>(IVSCEV); | |
| if (AR->hasNoSignedWrap()) | |
| UseSigned = true; | |
| else if (AR->hasNoUnsignedWrap()) | |
| UseSigned = false; | |
| else | |
| continue; | |
| } | |
| VPInstruction *RdxResult = cast<VPInstruction>(vputils::findRecipe( | |
| BackedgeVal, | |
| match_fn(m_VPInstruction<VPInstruction::ComputeReductionResult>()))); | |
| VPValue *NewFindLastSelect = BackedgeVal; | |
| VPValue *SelectCond = Cond; | |
| if (!SentinelVal || IVOfExpressionToSink) { | |
| // When we need to create a new select, normalize the condition so that | |
| // PhiR is the last operand and include the header mask if needed. | |
| DebugLoc DL = FindLastSelect->getDefiningRecipe()->getDebugLoc(); | |
| VPBuilder LoopBuilder(FindLastSelect->getDefiningRecipe()); | |
| if (FindLastSelect->getDefiningRecipe()->getOperand(1) == PhiR) | |
| SelectCond = LoopBuilder.createNot(SelectCond); | |
| // When tail folding, mask the condition with the header mask to prevent | |
| // propagating poison from inactive lanes in the last vector iteration. | |
| if (HeaderMask) | |
| SelectCond = LoopBuilder.createLogicalAnd(HeaderMask, SelectCond); | |
| if (SelectCond != Cond || IVOfExpressionToSink) { | |
| NewFindLastSelect = LoopBuilder.createSelect( | |
| SelectCond, | |
| IVOfExpressionToSink ? IVOfExpressionToSink : FindLastExpression, | |
| PhiR, DL); | |
| } | |
| } | |
| // Create the reduction result in the middle block using sentinel directly. | |
| RecurKind MinMaxKind = | |
| UseMax ? (UseSigned ? RecurKind::SMax : RecurKind::UMax) | |
| : (UseSigned ? RecurKind::SMin : RecurKind::UMin); | |
| VPIRFlags Flags(MinMaxKind, /*IsOrdered=*/false, /*IsInLoop=*/false, | |
| FastMathFlags()); | |
| DebugLoc ExitDL = RdxResult->getDebugLoc(); | |
| VPBuilder MiddleBuilder(RdxResult); | |
| VPValue *ReducedIV = | |
| MiddleBuilder.createNaryOp(VPInstruction::ComputeReductionResult, | |
| NewFindLastSelect, Flags, ExitDL); | |
| // If IVOfExpressionToSink is an expression to sink, sink it now. | |
| VPValue *VectorRegionExitingVal = ReducedIV; | |
| if (IVOfExpressionToSink) | |
| VectorRegionExitingVal = | |
| cloneBinOpForScalarIV(cast<VPWidenRecipe>(FindLastExpression), | |
| ReducedIV, IVOfExpressionToSink); | |
| VPValue *NewRdxResult; | |
| VPValue *StartVPV = PhiR->getStartValue(); | |
| if (SentinelVal) { | |
| // Sentinel-based approach: reduce IVs with min/max, compare against | |
| // sentinel to detect if condition was ever true, select accordingly. | |
| VPValue *Sentinel = Plan.getConstantInt(*SentinelVal); | |
| auto *Cmp = MiddleBuilder.createICmp(CmpInst::ICMP_NE, ReducedIV, | |
| Sentinel, ExitDL); | |
| NewRdxResult = MiddleBuilder.createSelect(Cmp, VectorRegionExitingVal, | |
| StartVPV, ExitDL); | |
| StartVPV = Sentinel; | |
| } else { | |
| // Introduce a boolean AnyOf reduction to track if the condition was ever | |
| // true in the loop. Use it to select the initial start value, if it was | |
| // never true. | |
| auto *AnyOfPhi = new VPReductionPHIRecipe( | |
| /*Phi=*/nullptr, RecurKind::Or, *Plan.getFalse(), *Plan.getFalse(), | |
| RdxUnordered{1}, {}, /*HasUsesOutsideReductionChain=*/false); | |
| AnyOfPhi->insertAfter(PhiR); | |
| VPBuilder LoopBuilder(BackedgeVal->getDefiningRecipe()); | |
| VPValue *OrVal = LoopBuilder.createOr(AnyOfPhi, SelectCond); | |
| AnyOfPhi->setOperand(1, OrVal); | |
| NewRdxResult = MiddleBuilder.createAnyOfReduction( | |
| OrVal, VectorRegionExitingVal, StartVPV, ExitDL); | |
| // Initialize the IV reduction phi with the neutral element, not the | |
| // original start value, to ensure correct min/max reduction results. | |
| StartVPV = Plan.getOrAddLiveIn( | |
| getRecurrenceIdentity(MinMaxKind, IVSCEV->getType(), {})); | |
| } | |
| RdxResult->replaceAllUsesWith(NewRdxResult); | |
| RdxResult->eraseFromParent(); | |
| auto *NewPhiR = new VPReductionPHIRecipe( | |
| cast<PHINode>(PhiR->getUnderlyingInstr()), RecurKind::FindIV, *StartVPV, | |
| *NewFindLastSelect, RdxUnordered{1}, {}, | |
| PhiR->hasUsesOutsideReductionChain()); | |
| NewPhiR->insertBefore(PhiR); | |
| PhiR->replaceAllUsesWith(NewPhiR); | |
| PhiR->eraseFromParent(); | |
| } | |
| } | |
| namespace { | |
| using ExtendKind = TTI::PartialReductionExtendKind; | |
| struct ReductionExtend { | |
| Type *SrcType = nullptr; | |
| ExtendKind Kind = ExtendKind::PR_None; | |
| }; | |
| /// Describes the extends used to compute the extended reduction operand. | |
| /// ExtendB is optional. If ExtendB is present, ExtendsUser is a binary | |
| /// operation. | |
| struct ExtendedReductionOperand { | |
| /// The recipe that consumes the extends. | |
| VPWidenRecipe *ExtendsUser = nullptr; | |
| /// Extend descriptions (inputs to getPartialReductionCost). | |
| ReductionExtend ExtendA, ExtendB; | |
| }; | |
| /// A chain of recipes that form a partial reduction. Matches either | |
| /// reduction_bin_op (extended op, accumulator), or | |
| /// reduction_bin_op (accumulator, extended op). | |
| /// The possible forms of the "extended op" are listed in | |
| /// matchExtendedReductionOperand. | |
| struct VPPartialReductionChain { | |
| /// The top-level binary operation that forms the reduction to a scalar | |
| /// after the loop body. | |
| VPWidenRecipe *ReductionBinOp = nullptr; | |
| /// The user of the extends that is then reduced. | |
| ExtendedReductionOperand ExtendedOp; | |
| /// The recurrence kind for the entire partial reduction chain. | |
| /// This allows distinguishing between Sub and AddWithSub recurrences, | |
| /// when the ReductionBinOp is a Instruction::Sub. | |
| RecurKind RK; | |
| /// The index of the accumulator operand of ReductionBinOp. The extended op | |
| /// is `1 - AccumulatorOpIdx`. | |
| unsigned AccumulatorOpIdx; | |
| unsigned ScaleFactor; | |
| /// Optional blend to represent predication for the block that updates the | |
| /// reduction. | |
| VPBlendRecipe *Blend = nullptr; | |
| }; | |
| // Return the incoming index of the single-use value in the blend, which is | |
| // expected to be the predicated reduction update. | |
| static std::optional<unsigned> | |
| getBlendReductionUpdateValueIdx(VPBlendRecipe *Blend) { | |
| assert(Blend && !Blend->isNormalized() && | |
| Blend->getNumIncomingValues() == 2 && | |
| "Expected a non-normalized blend with two incoming values"); | |
| bool FirstIncomingHasOneUse = Blend->getIncomingValue(0)->hasOneUse(); | |
| // Only the update value should have one use (the blend). The previous | |
| // value should always have at least two uses, the blend and the reduction. | |
| if (FirstIncomingHasOneUse == Blend->getIncomingValue(1)->hasOneUse()) | |
| return std::nullopt; | |
| return FirstIncomingHasOneUse ? 0 : 1; | |
| } | |
| static VPSingleDefRecipe * | |
| optimizeExtendsForPartialReduction(VPSingleDefRecipe *Op) { | |
| // reduce.add(mul(ext(A), C)) | |
| // -> reduce.add(mul(ext(A), ext(trunc(C)))) | |
| const APInt *Const; | |
| if (match(Op, m_Mul(m_ZExtOrSExt(m_VPValue()), m_APInt(Const)))) { | |
| auto *ExtA = cast<VPWidenCastRecipe>(Op->getOperand(0)); | |
| Instruction::CastOps ExtOpc = ExtA->getOpcode(); | |
| Type *NarrowTy = ExtA->getOperand(0)->getScalarType(); | |
| if (!Op->hasOneUse() || | |
| !llvm::canConstantBeExtended( | |
| Const, NarrowTy, TTI::getPartialReductionExtendKind(ExtOpc))) | |
| return Op; | |
| VPBuilder Builder(Op); | |
| auto *Trunc = Builder.createWidenCast(Instruction::CastOps::Trunc, | |
| Op->getOperand(1), NarrowTy); | |
| Type *WideTy = ExtA->getScalarType(); | |
| Op->setOperand(1, Builder.createWidenCast(ExtOpc, Trunc, WideTy)); | |
| return Op; | |
| } | |
| // reduce.add(abs(sub(ext(A), ext(B)))) | |
| // -> reduce.add(ext(absolute-difference(A, B))) | |
| VPValue *X, *Y; | |
| if (match(Op, m_WidenIntrinsic<Intrinsic::abs>(m_Sub( | |
| m_ZExtOrSExt(m_VPValue(X)), m_ZExtOrSExt(m_VPValue(Y)))))) { | |
| auto *Sub = Op->getOperand(0)->getDefiningRecipe(); | |
| auto *Ext = cast<VPWidenCastRecipe>(Sub->getOperand(0)); | |
| assert(Ext->getOpcode() == | |
| cast<VPWidenCastRecipe>(Sub->getOperand(1))->getOpcode() && | |
| "Expected both the LHS and RHS extends to be the same"); | |
| bool IsSigned = Ext->getOpcode() == Instruction::SExt; | |
| VPBuilder Builder(Op); | |
| Type *SrcTy = X->getScalarType(); | |
| auto *FreezeX = Builder.insert(new VPWidenRecipe(Instruction::Freeze, {X})); | |
| auto *FreezeY = Builder.insert(new VPWidenRecipe(Instruction::Freeze, {Y})); | |
| auto *Max = Builder.insert( | |
| new VPWidenIntrinsicRecipe(IsSigned ? Intrinsic::smax : Intrinsic::umax, | |
| {FreezeX, FreezeY}, SrcTy)); | |
| auto *Min = Builder.insert( | |
| new VPWidenIntrinsicRecipe(IsSigned ? Intrinsic::smin : Intrinsic::umin, | |
| {FreezeX, FreezeY}, SrcTy)); | |
| auto *AbsDiff = | |
| Builder.insert(new VPWidenRecipe(Instruction::Sub, {Max, Min})); | |
| return Builder.createWidenCast(Instruction::CastOps::ZExt, AbsDiff, | |
| Op->getScalarType()); | |
| } | |
| // reduce.add(ext(mul(ext(A), ext(B)))) | |
| // -> reduce.add(mul(wider_ext(A), wider_ext(B))) | |
| // TODO: Support this optimization for float types. | |
| if (match(Op, m_ZExtOrSExt(m_Mul(m_ZExtOrSExt(m_VPValue()), | |
| m_ZExtOrSExt(m_VPValue()))))) { | |
| auto *Ext = cast<VPWidenCastRecipe>(Op); | |
| auto *Mul = cast<VPWidenRecipe>(Ext->getOperand(0)); | |
| auto *MulLHS = cast<VPWidenCastRecipe>(Mul->getOperand(0)); | |
| auto *MulRHS = cast<VPWidenCastRecipe>(Mul->getOperand(1)); | |
| if (!Mul->hasOneUse() || | |
| (Ext->getOpcode() != MulLHS->getOpcode() && MulLHS != MulRHS) || | |
| MulLHS->getOpcode() != MulRHS->getOpcode()) | |
| return Op; | |
| VPBuilder Builder(Mul); | |
| auto *NewLHS = Builder.createWidenCast( | |
| MulLHS->getOpcode(), MulLHS->getOperand(0), Ext->getScalarType()); | |
| auto *NewRHS = MulLHS == MulRHS | |
| ? NewLHS | |
| : Builder.createWidenCast(MulRHS->getOpcode(), | |
| MulRHS->getOperand(0), | |
| Ext->getScalarType()); | |
| auto *NewMul = Mul->cloneWithOperands({NewLHS, NewRHS}); | |
| Builder.insert(NewMul); | |
| Op->replaceAllUsesWith(NewMul); | |
| Op->eraseFromParent(); | |
| Mul->eraseFromParent(); | |
| return NewMul; | |
| } | |
| return Op; | |
| } | |
| static VPExpressionRecipe * | |
| createPartialReductionExpression(VPReductionRecipe *Red) { | |
| VPValue *VecOp = Red->getVecOp(); | |
| // reduce.[f]add(ext(op)) | |
| // -> VPExpressionRecipe(op, red) | |
| if (match(VecOp, m_WidenAnyExtend(m_VPValue()))) | |
| return new VPExpressionRecipe(cast<VPWidenCastRecipe>(VecOp), Red); | |
| // reduce.[f]add(neg(ext(op))) | |
| // -> VPExpressionRecipe(op, sub/neg, red) | |
| if (match(VecOp, m_AnyNeg(m_WidenAnyExtend(m_VPValue())))) { | |
| auto *Neg = cast<VPWidenRecipe>(VecOp); | |
| auto *Ext = | |
| cast<VPWidenCastRecipe>(Neg->getOperand(Neg->getNumOperands() - 1)); | |
| return new VPExpressionRecipe(Ext, Neg, Red); | |
| } | |
| // reduce.[f]add([f]mul(ext(a), ext(b))) | |
| // -> VPExpressionRecipe(a, b, mul, red) | |
| if (match(VecOp, m_FMul(m_FPExt(m_VPValue()), m_FPExt(m_VPValue()))) || | |
| match(VecOp, | |
| m_Mul(m_ZExtOrSExt(m_VPValue()), m_ZExtOrSExt(m_VPValue())))) { | |
| auto *Mul = cast<VPWidenRecipe>(VecOp); | |
| auto *ExtA = cast<VPWidenCastRecipe>(Mul->getOperand(0)); | |
| auto *ExtB = cast<VPWidenCastRecipe>(Mul->getOperand(1)); | |
| return new VPExpressionRecipe(ExtA, ExtB, Mul, Red); | |
| } | |
| // reduce.fadd(fneg(fmul(fpext(a), fpext(b)))) | |
| // -> VPExpressionRecipe(a, b, fmul, fsub, red) | |
| if (match(VecOp, | |
| m_FNeg(m_FMul(m_FPExt(m_VPValue()), m_FPExt(m_VPValue()))))) { | |
| auto *FNeg = cast<VPWidenRecipe>(VecOp); | |
| auto *FMul = cast<VPWidenRecipe>(FNeg->getOperand(0)); | |
| auto *ExtA = cast<VPWidenCastRecipe>(FMul->getOperand(0)); | |
| auto *ExtB = cast<VPWidenCastRecipe>(FMul->getOperand(1)); | |
| return new VPExpressionRecipe(ExtA, ExtB, FMul, FNeg, Red); | |
| } | |
| // reduce.add(neg(mul(ext(a), ext(b)))) | |
| // -> VPExpressionRecipe(a, b, mul, sub, red) | |
| if (match(VecOp, m_Sub(m_ZeroInt(), m_Mul(m_ZExtOrSExt(m_VPValue()), | |
| m_ZExtOrSExt(m_VPValue()))))) { | |
| auto *Sub = cast<VPWidenRecipe>(VecOp); | |
| auto *Mul = cast<VPWidenRecipe>(Sub->getOperand(1)); | |
| auto *ExtA = cast<VPWidenCastRecipe>(Mul->getOperand(0)); | |
| auto *ExtB = cast<VPWidenCastRecipe>(Mul->getOperand(1)); | |
| return new VPExpressionRecipe(ExtA, ExtB, Mul, Sub, Red); | |
| } | |
| llvm_unreachable("Unsupported expression"); | |
| } | |
| // Helper to transform a partial reduction chain into a partial reduction | |
| // recipe. Assumes profitability has been checked. | |
| static void transformToPartialReduction(const VPPartialReductionChain &Chain, | |
| VPlan &Plan, | |
| VPReductionPHIRecipe *RdxPhi) { | |
| VPWidenRecipe *WidenRecipe = Chain.ReductionBinOp; | |
| assert(WidenRecipe->getNumOperands() == 2 && "Expected binary operation"); | |
| VPValue *Accumulator = WidenRecipe->getOperand(Chain.AccumulatorOpIdx); | |
| auto *ExtendedOp = cast<VPSingleDefRecipe>( | |
| WidenRecipe->getOperand(1 - Chain.AccumulatorOpIdx)); | |
| // FIXME: Do these transforms before invoking the cost-model. | |
| ExtendedOp = optimizeExtendsForPartialReduction(ExtendedOp); | |
| // Sub-reductions can be implemented in two ways: | |
| // (1) negate the operand in the vector loop (the default way). | |
| // (2) subtract the reduced value from the init value in the middle block. | |
| // Both ways keep the reduction itself as an 'add' reduction. | |
| // | |
| // The ISD nodes for partial reductions don't support folding the | |
| // sub/negation into its operands because the following is not a valid | |
| // transformation: | |
| // sub(0, mul(ext(a), ext(b))) | |
| // -> mul(ext(a), ext(sub(0, b))) | |
| // | |
| // It's therefore better to choose option (2) such that the partial | |
| // reduction is always positive (starting at '0') and to do a final | |
| // subtract in the middle block. | |
| if ((WidenRecipe->getOpcode() == Instruction::Sub && | |
| Chain.RK != RecurKind::Sub) || | |
| (WidenRecipe->getOpcode() == Instruction::FSub && | |
| Chain.RK != RecurKind::FSub)) { | |
| VPBuilder Builder(WidenRecipe); | |
| Type *ElemTy = ExtendedOp->getScalarType(); | |
| VPWidenRecipe *NegRecipe; | |
| if (WidenRecipe->getOpcode() == Instruction::FSub) { | |
| NegRecipe = | |
| new VPWidenRecipe(Instruction::FNeg, {ExtendedOp}, VPIRFlags(), | |
| VPIRMetadata(), DebugLoc::getUnknown()); | |
| } else { | |
| auto *Zero = Plan.getZero(ElemTy); | |
| NegRecipe = | |
| new VPWidenRecipe(Instruction::Sub, {Zero, ExtendedOp}, VPIRFlags(), | |
| VPIRMetadata(), DebugLoc::getUnknown()); | |
| } | |
| Builder.insert(NegRecipe); | |
| ExtendedOp = NegRecipe; | |
| } | |
| // Check if WidenRecipe is the final result of the reduction. If so, look | |
| // through the Select recipe introduced by tail-folding, otherwise look | |
| // through any Blend recipe introduced by predication for the block. | |
| VPValue *ExitSearch = | |
| Chain.Blend ? cast<VPValue>(Chain.Blend) : cast<VPValue>(WidenRecipe); | |
| VPValue *Cond = nullptr; | |
| VPValue *ExitValue = cast_or_null<VPInstruction>( | |
| findUserOf(ExitSearch, m_Select(m_VPValue(Cond), m_Specific(ExitSearch), | |
| m_Specific(RdxPhi)))); | |
| if (Chain.Blend) { | |
| std::optional<unsigned> BlendReductionIdx = | |
| getBlendReductionUpdateValueIdx(Chain.Blend); | |
| assert(BlendReductionIdx && | |
| Chain.Blend->getIncomingValue(*BlendReductionIdx) == WidenRecipe && | |
| "Expected blend to contain the reduction update"); | |
| VPValue *BlendCond = Chain.Blend->getMask(*BlendReductionIdx); | |
| Cond = ExitValue ? VPBuilder(WidenRecipe) | |
| .createLogicalAnd(Cond, BlendCond, | |
| WidenRecipe->getDebugLoc()) | |
| : BlendCond; | |
| } | |
| bool IsLastInChain = RdxPhi->getBackedgeValue() == WidenRecipe || | |
| RdxPhi->getBackedgeValue() == ExitValue || | |
| RdxPhi->getBackedgeValue() == Chain.Blend; | |
| assert((!ExitValue || IsLastInChain) && | |
| "if we found ExitValue, it must match RdxPhi's backedge value"); | |
| Type *PhiType = RdxPhi->getScalarType(); | |
| RecurKind RdxKind = | |
| PhiType->isFloatingPointTy() ? RecurKind::FAdd : RecurKind::Add; | |
| auto *PartialRed = new VPReductionRecipe( | |
| RdxKind, | |
| RdxKind == RecurKind::FAdd ? WidenRecipe->getFastMathFlagsOrNone() | |
| : FastMathFlags(), | |
| WidenRecipe->getUnderlyingInstr(), Accumulator, ExtendedOp, Cond, | |
| RdxUnordered{/*VFScaleFactor=*/Chain.ScaleFactor}); | |
| PartialRed->insertBefore(WidenRecipe); | |
| if (ExitValue) | |
| ExitValue->replaceAllUsesWith(PartialRed); | |
| if (Chain.Blend) | |
| Chain.Blend->replaceAllUsesWith(PartialRed); | |
| WidenRecipe->replaceAllUsesWith(PartialRed); | |
| // For cost-model purposes, fold this into a VPExpression. | |
| VPExpressionRecipe *E = createPartialReductionExpression(PartialRed); | |
| E->insertBefore(WidenRecipe); | |
| PartialRed->replaceAllUsesWith(E); | |
| // We only need to update the PHI node once, which is when we find the | |
| // last reduction in the chain. | |
| if (!IsLastInChain) | |
| return; | |
| // Scale the PHI and ReductionStartVector by the VFScaleFactor | |
| assert(RdxPhi->getVFScaleFactor() == 1 && "scale factor must not be set"); | |
| RdxPhi->setVFScaleFactor(Chain.ScaleFactor); | |
| auto *StartInst = cast<VPInstruction>(RdxPhi->getStartValue()); | |
| assert(StartInst->getOpcode() == VPInstruction::ReductionStartVector); | |
| auto *NewScaleFactor = Plan.getConstantInt(32, Chain.ScaleFactor); | |
| StartInst->setOperand(2, NewScaleFactor); | |
| // If this is the last value in a sub-reduction chain, then update the PHI | |
| // node to start at `0` and update the reduction-result to subtract from | |
| // the PHI's start value. | |
| if (Chain.RK != RecurKind::Sub && Chain.RK != RecurKind::FSub) | |
| return; | |
| VPValue *OldStartValue = StartInst->getOperand(0); | |
| StartInst->setOperand(0, StartInst->getOperand(1)); | |
| // Replace reduction_result by 'sub (startval, reductionresult)'. | |
| VPInstruction *RdxResult = vputils::findComputeReductionResult(RdxPhi); | |
| assert(RdxResult && "Could not find reduction result"); | |
| VPBuilder Builder = VPBuilder::getToInsertAfter(RdxResult); | |
| unsigned SubOpc = Chain.RK == RecurKind::FSub ? Instruction::BinaryOps::FSub | |
| : Instruction::BinaryOps::Sub; | |
| VPInstruction *NewResult = Builder.createNaryOp( | |
| SubOpc, {OldStartValue, RdxResult}, VPIRFlags::getDefaultFlags(SubOpc), | |
| RdxPhi->getDebugLoc()); | |
| RdxResult->replaceUsesWithIf( | |
| NewResult, | |
| [&NewResult](VPUser &U, unsigned Idx) { return &U != NewResult; }); | |
| } | |
| /// Returns the cost of a link in a partial-reduction chain for a given VF. | |
| static InstructionCost | |
| getPartialReductionLinkCost(VPCostContext &CostCtx, | |
| const VPPartialReductionChain &Link, | |
| ElementCount VF) { | |
| Type *RdxType = Link.ReductionBinOp->getScalarType(); | |
| const ExtendedReductionOperand &ExtendedOp = Link.ExtendedOp; | |
| std::optional<unsigned> BinOpc = std::nullopt; | |
| // If ExtendB is not none, then the "ExtendsUser" is the binary operation. | |
| if (ExtendedOp.ExtendB.Kind != ExtendKind::PR_None) | |
| BinOpc = ExtendedOp.ExtendsUser->getOpcode(); | |
| std::optional<llvm::FastMathFlags> Flags; | |
| if (RdxType->isFloatingPointTy()) | |
| Flags = Link.ReductionBinOp->getFastMathFlagsOrNone(); | |
| auto GetLinkOpcode = [&Link]() -> unsigned { | |
| switch (Link.RK) { | |
| case RecurKind::Sub: | |
| return Instruction::Add; | |
| case RecurKind::FSub: | |
| return Instruction::FAdd; | |
| default: | |
| return Link.ReductionBinOp->getOpcode(); | |
| } | |
| }; | |
| return CostCtx.TTI.getPartialReductionCost( | |
| GetLinkOpcode(), ExtendedOp.ExtendA.SrcType, ExtendedOp.ExtendB.SrcType, | |
| RdxType, VF, ExtendedOp.ExtendA.Kind, ExtendedOp.ExtendB.Kind, BinOpc, | |
| CostCtx.CostKind, Flags); | |
| } | |
| static ExtendKind getPartialReductionExtendKind(VPWidenCastRecipe *Cast) { | |
| return TTI::getPartialReductionExtendKind(Cast->getOpcode()); | |
| } | |
| /// Checks if \p Op (which is an operand of \p UpdateR) is an extended reduction | |
| /// operand. This is an operand where the source of the value (e.g. a load) has | |
| /// been extended (sext, zext, or fpext) before it is used in the reduction. | |
| /// | |
| /// Possible forms matched by this function: | |
| /// - UpdateR(PrevValue, ext(...)) | |
| /// - UpdateR(PrevValue, mul(ext(...), ext(...))) | |
| /// - UpdateR(PrevValue, mul(ext(...), Constant)) | |
| /// - UpdateR(PrevValue, ext(mul(ext(...), ext(...)))) | |
| /// - UpdateR(PrevValue, ext(mul(ext(...), Constant))) | |
| /// - UpdateR(PrevValue, abs(sub(ext(...), ext(...))) | |
| /// | |
| /// Note: The second operand of UpdateR corresponds to \p Op in the examples. | |
| static std::optional<ExtendedReductionOperand> | |
| matchExtendedReductionOperand(VPWidenRecipe *UpdateR, VPValue *Op) { | |
| assert(is_contained(UpdateR->operands(), Op) && | |
| "Op should be operand of UpdateR"); | |
| // Try matching an absolute difference operand of the form | |
| // `abs(sub(ext(A), ext(B)))`. This will be later transformed into | |
| // `ext(absolute-difference(A, B))`. This allows us to perform the absolute | |
| // difference on a wider type and get the extend for "free" from the partial | |
| // reduction. | |
| VPValue *X, *Y; | |
| if (Op->hasOneUse() && | |
| match(Op, m_WidenIntrinsic<Intrinsic::abs>( | |
| m_OneUse(m_Sub(m_WidenAnyExtend(m_VPValue(X)), | |
| m_WidenAnyExtend(m_VPValue(Y))))))) { | |
| auto *Abs = cast<VPWidenIntrinsicRecipe>(Op); | |
| auto *Sub = cast<VPWidenRecipe>(Abs->getOperand(0)); | |
| auto *LHSExt = cast<VPWidenCastRecipe>(Sub->getOperand(0)); | |
| auto *RHSExt = cast<VPWidenCastRecipe>(Sub->getOperand(1)); | |
| Type *LHSInputType = X->getScalarType(); | |
| Type *RHSInputType = Y->getScalarType(); | |
| if (LHSInputType != RHSInputType || | |
| LHSExt->getOpcode() != RHSExt->getOpcode()) | |
| return std::nullopt; | |
| // Note: This is essentially the same as matching ext(...) as we will | |
| // rewrite this operand to ext(absolute-difference(A, B)). | |
| return ExtendedReductionOperand{ | |
| Sub, | |
| /*ExtendA=*/{LHSInputType, getPartialReductionExtendKind(LHSExt)}, | |
| /*ExtendB=*/{}}; | |
| } | |
| std::optional<TTI::PartialReductionExtendKind> OuterExtKind; | |
| if (match(Op, m_WidenAnyExtend(m_VPValue()))) { | |
| auto *CastRecipe = cast<VPWidenCastRecipe>(Op); | |
| VPValue *CastSource = CastRecipe->getOperand(0); | |
| OuterExtKind = getPartialReductionExtendKind(CastRecipe); | |
| if (match(CastSource, m_Mul(m_VPValue(), m_VPValue())) || | |
| match(CastSource, m_FMul(m_VPValue(), m_VPValue()))) { | |
| // Match: ext(mul(...)) | |
| // Record the outer extend kind and set `Op` to the mul. We can then match | |
| // this as a binary operation. Note: We can optimize out the outer extend | |
| // by widening the inner extends to match it. See | |
| // optimizeExtendsForPartialReduction. | |
| Op = CastSource; | |
| } else { | |
| return ExtendedReductionOperand{ | |
| UpdateR, | |
| /*ExtendA=*/{CastSource->getScalarType(), *OuterExtKind}, | |
| /*ExtendB=*/{}}; | |
| } | |
| } | |
| if (!Op->hasOneUse()) | |
| return std::nullopt; | |
| VPWidenRecipe *MulOp = dyn_cast<VPWidenRecipe>(Op); | |
| if (!MulOp || | |
| !is_contained({Instruction::Mul, Instruction::FMul}, MulOp->getOpcode())) | |
| return std::nullopt; | |
| // The rest of the matching assumes `Op` is a (possibly extended) mul | |
| // operation. | |
| VPValue *LHS = MulOp->getOperand(0); | |
| VPValue *RHS = MulOp->getOperand(1); | |
| // The LHS of the operation must always be an extend. | |
| if (!match(LHS, m_WidenAnyExtend(m_VPValue()))) | |
| return std::nullopt; | |
| auto *LHSCast = cast<VPWidenCastRecipe>(LHS); | |
| Type *LHSInputType = LHSCast->getOperand(0)->getScalarType(); | |
| ExtendKind LHSExtendKind = getPartialReductionExtendKind(LHSCast); | |
| // The RHS of the operation can be an extend or a constant integer. | |
| const APInt *RHSConst = nullptr; | |
| VPWidenCastRecipe *RHSCast = nullptr; | |
| if (match(RHS, m_WidenAnyExtend(m_VPValue()))) | |
| RHSCast = cast<VPWidenCastRecipe>(RHS); | |
| else if (!match(RHS, m_APInt(RHSConst)) || | |
| !canConstantBeExtended(RHSConst, LHSInputType, LHSExtendKind)) | |
| return std::nullopt; | |
| // The outer extend kind must match the inner extends for folding. | |
| for (VPWidenCastRecipe *Cast : {LHSCast, RHSCast}) | |
| if (Cast && OuterExtKind && | |
| getPartialReductionExtendKind(Cast) != OuterExtKind) | |
| return std::nullopt; | |
| Type *RHSInputType = LHSInputType; | |
| ExtendKind RHSExtendKind = LHSExtendKind; | |
| if (RHSCast) { | |
| RHSInputType = RHSCast->getOperand(0)->getScalarType(); | |
| RHSExtendKind = getPartialReductionExtendKind(RHSCast); | |
| } | |
| return ExtendedReductionOperand{ | |
| MulOp, {LHSInputType, LHSExtendKind}, {RHSInputType, RHSExtendKind}}; | |
| } | |
| /// Examines each operation in the reduction chain corresponding to \p RedPhiR, | |
| /// and determines if the target can use a cheaper operation with a wider | |
| /// per-iteration input VF and narrower PHI VF. If successful, returns the chain | |
| /// of operations in the reduction. | |
| static std::optional<SmallVector<VPPartialReductionChain>> | |
| getScaledReductions(VPReductionPHIRecipe *RedPhiR) { | |
| // Get the backedge value from the reduction PHI and find the | |
| // ComputeReductionResult that uses it (directly or through a select for | |
| // predicated reductions). | |
| auto *RdxResult = vputils::findComputeReductionResult(RedPhiR); | |
| if (!RdxResult) | |
| return std::nullopt; | |
| VPValue *ExitValue = RdxResult->getOperand(0); | |
| match(ExitValue, m_Select(m_VPValue(), m_VPValue(ExitValue), m_VPValue())); | |
| SmallVector<VPPartialReductionChain> Chain; | |
| RecurKind RK = RedPhiR->getRecurrenceKind(); | |
| Type *PhiType = RedPhiR->getScalarType(); | |
| TypeSize PHISize = PhiType->getPrimitiveSizeInBits(); | |
| // Work backwards from the ExitValue examining each reduction operation. | |
| VPValue *CurrentValue = ExitValue; | |
| while (CurrentValue != RedPhiR) { | |
| VPBlendRecipe *Blend = dyn_cast<VPBlendRecipe>(CurrentValue); | |
| std::optional<unsigned> BlendReductionIdx; | |
| if (Blend) { | |
| assert(!Blend->isNormalized() && "Expect Blend not to be normalized."); | |
| if (Blend->getNumIncomingValues() != 2) | |
| return std::nullopt; | |
| BlendReductionIdx = getBlendReductionUpdateValueIdx(Blend); | |
| if (!BlendReductionIdx) | |
| return std::nullopt; | |
| CurrentValue = Blend->getIncomingValue(*BlendReductionIdx); | |
| } | |
| auto *UpdateR = dyn_cast<VPWidenRecipe>(CurrentValue); | |
| if (!UpdateR || !Instruction::isBinaryOp(UpdateR->getOpcode())) | |
| return std::nullopt; | |
| VPValue *Op = UpdateR->getOperand(1); | |
| VPValue *PrevValue = UpdateR->getOperand(0); | |
| // Find the extended operand. The other operand (PrevValue) is the next link | |
| // in the reduction chain. | |
| std::optional<ExtendedReductionOperand> ExtendedOp = | |
| matchExtendedReductionOperand(UpdateR, Op); | |
| if (!ExtendedOp) { | |
| ExtendedOp = matchExtendedReductionOperand(UpdateR, PrevValue); | |
| if (!ExtendedOp) | |
| return std::nullopt; | |
| std::swap(Op, PrevValue); | |
| } | |
| // Look for VPBlend(reduce(PrevValue, Op), PrevValue), where | |
| // reduce is equal to CurrentValue. This can be lowered as | |
| // a conditional reduction by hoisting the select to the inputs. | |
| if (Blend && Blend->getIncomingValue(1 - *BlendReductionIdx) != PrevValue) | |
| return std::nullopt; | |
| Type *ExtSrcType = ExtendedOp->ExtendA.SrcType; | |
| TypeSize ExtSrcSize = ExtSrcType->getPrimitiveSizeInBits(); | |
| if (!PHISize.hasKnownScalarFactor(ExtSrcSize)) | |
| return std::nullopt; | |
| VPPartialReductionChain Link( | |
| {UpdateR, *ExtendedOp, RK, | |
| PrevValue == UpdateR->getOperand(0) ? 0U : 1U, | |
| static_cast<unsigned>(PHISize.getKnownScalarFactor(ExtSrcSize)), | |
| Blend}); | |
| Chain.push_back(Link); | |
| CurrentValue = PrevValue; | |
| } | |
| // The chain links were collected by traversing backwards from the exit value. | |
| // Reverse the chains so they are in program order. | |
| std::reverse(Chain.begin(), Chain.end()); | |
| return Chain; | |
| } | |
| } // namespace | |
| void VPlanTransforms::createPartialReductions(VPlan &Plan, | |
| VPCostContext &CostCtx, | |
| VFRange &Range) { | |
| // Find all possible valid partial reductions, grouping chains by their PHI. | |
| // This grouping allows invalidating the whole chain, if any link is not a | |
| // valid partial reduction. | |
| MapVector<VPReductionPHIRecipe *, SmallVector<VPPartialReductionChain>> | |
| ChainsByPhi; | |
| VPBasicBlock *HeaderVPBB = Plan.getVectorLoopRegion()->getEntryBasicBlock(); | |
| for (VPRecipeBase &R : HeaderVPBB->phis()) { | |
| auto *RedPhiR = dyn_cast<VPReductionPHIRecipe>(&R); | |
| if (!RedPhiR) | |
| continue; | |
| if (auto Chains = getScaledReductions(RedPhiR)) | |
| ChainsByPhi.try_emplace(RedPhiR, std::move(*Chains)); | |
| } | |
| if (ChainsByPhi.empty()) | |
| return; | |
| // Build set of partial reduction operations and blends for user validation | |
| // and a map of reduction bin ops to their scale factors for scale validation. | |
| SmallPtrSet<VPRecipeBase *, 4> PartialReductionOps; | |
| SmallPtrSet<VPBlendRecipe *, 4> PartialReductionBlends; | |
| DenseMap<VPSingleDefRecipe *, unsigned> ScaledReductionMap; | |
| for (const auto &[_, Chains] : ChainsByPhi) | |
| for (const VPPartialReductionChain &Chain : Chains) { | |
| PartialReductionOps.insert(Chain.ExtendedOp.ExtendsUser); | |
| if (Chain.Blend) | |
| PartialReductionBlends.insert(Chain.Blend); | |
| ScaledReductionMap[Chain.ReductionBinOp] = Chain.ScaleFactor; | |
| } | |
| // A partial reduction is invalid if any of its extends are used by | |
| // something that isn't another partial reduction. This is because the | |
| // extends are intended to be lowered along with the reduction itself. | |
| auto ExtendUsersValid = [&](VPValue *Ext) { | |
| return !isa<VPWidenCastRecipe>(Ext) || all_of(Ext->users(), [&](VPUser *U) { | |
| return PartialReductionOps.contains(cast<VPRecipeBase>(U)); | |
| }); | |
| }; | |
| auto IsProfitablePartialReductionChainForVF = | |
| [&](ArrayRef<VPPartialReductionChain> Chain, ElementCount VF) -> bool { | |
| InstructionCost PartialCost = 0, RegularCost = 0; | |
| // The chain is a profitable partial reduction chain if the cost of handling | |
| // the entire chain is cheaper when using partial reductions than when | |
| // handling the entire chain using regular reductions. | |
| for (const VPPartialReductionChain &Link : Chain) { | |
| const ExtendedReductionOperand &ExtendedOp = Link.ExtendedOp; | |
| InstructionCost LinkCost = getPartialReductionLinkCost(CostCtx, Link, VF); | |
| if (!LinkCost.isValid()) | |
| return false; | |
| PartialCost += LinkCost; | |
| RegularCost += Link.ReductionBinOp->computeCost(VF, CostCtx); | |
| // If ExtendB is not none, then the "ExtendsUser" is the binary operation. | |
| if (ExtendedOp.ExtendB.Kind != ExtendKind::PR_None) | |
| RegularCost += ExtendedOp.ExtendsUser->computeCost(VF, CostCtx); | |
| for (VPValue *Op : ExtendedOp.ExtendsUser->operands()) | |
| if (auto *Extend = dyn_cast<VPWidenCastRecipe>(Op)) | |
| RegularCost += Extend->computeCost(VF, CostCtx); | |
| } | |
| return PartialCost.isValid() && PartialCost < RegularCost; | |
| }; | |
| // Validate chains: check that extends are only used by partial reductions, | |
| // and that reduction bin ops are only used by other partial reductions with | |
| // matching scale factors, are outside the loop region or the select | |
| // introduced by tail-folding. Otherwise we would create users of scaled | |
| // reductions where the types of the other operands don't match. | |
| for (auto &[RedPhiR, Chains] : ChainsByPhi) { | |
| for (const VPPartialReductionChain &Chain : Chains) { | |
| if (!all_of(Chain.ExtendedOp.ExtendsUser->operands(), ExtendUsersValid)) { | |
| Chains.clear(); | |
| break; | |
| } | |
| auto UseIsValid = [&, RedPhiR = RedPhiR](VPUser *U) { | |
| if (auto *PhiR = dyn_cast<VPReductionPHIRecipe>(U)) | |
| return PhiR == RedPhiR; | |
| auto *R = cast<VPSingleDefRecipe>(U); | |
| if (auto *Blend = dyn_cast<VPBlendRecipe>(R)) | |
| return Blend == Chain.Blend || PartialReductionBlends.contains(Blend); | |
| return Chain.ScaleFactor == ScaledReductionMap.lookup_or(R, 0) || | |
| match(R, m_ComputeReductionResult( | |
| m_Specific(Chain.ReductionBinOp))) || | |
| match(R, m_Select(m_VPValue(), m_Specific(Chain.ReductionBinOp), | |
| m_Specific(RedPhiR))); | |
| }; | |
| if (!all_of(Chain.ReductionBinOp->users(), UseIsValid)) { | |
| Chains.clear(); | |
| break; | |
| } | |
| // Check if the compute-reduction-result is used by a sunk store. | |
| // TODO: Also form partial reductions in those cases. | |
| if (auto *RdxResult = vputils::findComputeReductionResult(RedPhiR)) { | |
| if (any_of(RdxResult->users(), [](VPUser *U) { | |
| auto *RepR = dyn_cast<VPReplicateRecipe>(U); | |
| return RepR && RepR->getOpcode() == Instruction::Store; | |
| })) { | |
| Chains.clear(); | |
| break; | |
| } | |
| } | |
| } | |
| // Clear the chain if it is not profitable. | |
| if (!LoopVectorizationPlanner::getDecisionAndClampRange( | |
| [&, &Chains = Chains](ElementCount VF) { | |
| return IsProfitablePartialReductionChainForVF(Chains, VF); | |
| }, | |
| Range)) | |
| Chains.clear(); | |
| } | |
| for (auto &[Phi, Chains] : ChainsByPhi) | |
| for (const VPPartialReductionChain &Chain : Chains) | |
| transformToPartialReduction(Chain, Plan, Phi); | |
| } | |
| /// If the pointer operand \p Addr of a memory access is an affine AddRec | |
| /// w.r.t. \p L with a constant stride, return the stride in units of | |
| /// \p AccessTy. Otherwise return std::nullopt. | |
| static std::optional<int64_t> getConstantStride(VPValue *Addr, Type *AccessTy, | |
| PredicatedScalarEvolution &PSE, | |
| const Loop *L) { | |
| const SCEV *AddrSCEV = vputils::getSCEVExprForVPValue(Addr, PSE, L); | |
| auto *AddRec = dyn_cast<SCEVAddRecExpr>(AddrSCEV); | |
| if (!AddRec) | |
| return {}; | |
| return getStrideFromAddRec(AddRec, L, AccessTy, /*Ptr=*/nullptr, PSE); | |
| } | |
| void VPlanTransforms::makeMemOpWideningDecisions(VPlan &Plan, VFRange &Range, | |
| VPRecipeBuilder &RecipeBuilder, | |
| VPCostContext &CostCtx) { | |
| // Collect all loads/stores first. We will start with ones having simpler | |
| // decisions followed by more complex ones that are potentially | |
| // guided/dependent on the simpler ones. | |
| SmallVector<VPInstruction *> MemOps; | |
| for (VPBasicBlock *VPBB : | |
| VPBlockUtils::blocksOnly<VPBasicBlock>(vp_depth_first_shallow( | |
| Plan.getVectorLoopRegion()->getEntryBasicBlock()))) { | |
| for (VPRecipeBase &R : *VPBB) { | |
| auto *VPI = dyn_cast<VPInstruction>(&R); | |
| if (VPI && VPI->getUnderlyingValue() && | |
| is_contained({Instruction::Load, Instruction::Store}, | |
| VPI->getOpcode())) | |
| MemOps.push_back(VPI); | |
| } | |
| } | |
| // Few helpers to process different kinds of memory operations. | |
| // To be used as argument to `VPlanTransforms::runPass` which explicitly | |
| // specified pass name, hence `VPlan &` parameter. | |
| auto ProcessSubset = [&](VPlan &, auto ProcessVPInst) { | |
| SmallVector<VPInstruction *> RemainingMemOps; | |
| for (VPInstruction *VPI : MemOps) { | |
| if (!ProcessVPInst(VPI)) | |
| RemainingMemOps.push_back(VPI); | |
| } | |
| MemOps.clear(); | |
| std::swap(MemOps, RemainingMemOps); | |
| }; | |
| auto ReplaceWith = [&](VPInstruction *VPI, VPRecipeBase *New) { | |
| New->insertBefore(VPI); | |
| if (VPI->getOpcode() == Instruction::Load) | |
| VPI->replaceAllUsesWith(New->getVPSingleValue()); | |
| VPI->eraseFromParent(); | |
| // VPI has been processed. | |
| return true; | |
| }; | |
| auto Scalarize = [&](VPInstruction *VPI) { | |
| return ReplaceWith(VPI, RecipeBuilder.handleReplication(VPI, Range)); | |
| }; | |
| VPBasicBlock *MiddleVPBB = Plan.getMiddleBlock(); | |
| VPBuilder FinalRedStoresBuilder(MiddleVPBB, MiddleVPBB->getFirstNonPhi()); | |
| VPlanTransforms::runPass( | |
| "lowerMemoryIdioms", ProcessSubset, Plan, [&](VPInstruction *VPI) { | |
| if (RecipeBuilder.replaceWithFinalIfReductionStore( | |
| VPI, FinalRedStoresBuilder)) | |
| return true; | |
| // Filter out scalar VPlan for the remaining idioms. | |
| if (LoopVectorizationPlanner::getDecisionAndClampRange( | |
| [](ElementCount VF) { return VF.isScalar(); }, Range)) | |
| return false; | |
| if (VPHistogramRecipe *Histogram = RecipeBuilder.widenIfHistogram(VPI)) | |
| return ReplaceWith(VPI, Histogram); | |
| return false; | |
| }); | |
| // Filter out scalar VPlan for the remaining memory operations. | |
| if (LoopVectorizationPlanner::getDecisionAndClampRange( | |
| [](ElementCount VF) { return VF.isScalar(); }, Range)) | |
| return; | |
| // If the instruction's allocated size doesn't equal it's type size, it | |
| // requires padding and will be scalarized. | |
| VPlanTransforms::runPass( | |
| "scalarizeMemOpsWithIrregularTypes", ProcessSubset, Plan, | |
| [&](VPInstruction *VPI) { | |
| Instruction *I = VPI->getUnderlyingInstr(); | |
| if (hasIrregularType(getLoadStoreType(I), I->getDataLayout())) | |
| return Scalarize(VPI); | |
| return false; | |
| }); | |
| if (!RecipeBuilder.prefersVectorizedAddressing()) { | |
| VPlanTransforms::runPass( | |
| "makeVPlanMemOpDecision", ProcessSubset, Plan, [&](VPInstruction *VPI) { | |
| Instruction *I = VPI->getUnderlyingInstr(); | |
| bool IsLoad = VPI->getOpcode() == Instruction::Load; | |
| if (RecipeBuilder.isPredicatedInst(I) || !IsLoad || | |
| !vputils::isUsedByLoadStoreAddress(VPI)) | |
| return false; | |
| // Scalarize loads used as addresses, matching the legacy CM. The load | |
| // is single-scalar if the pointer is loop-invariant, otherwise it is | |
| // replicated per-lane. No mask is needed as the load is not | |
| // predicated. | |
| VPValue *Ptr = VPI->getOperand(0); | |
| const SCEV *PtrSCEV = | |
| vputils::getSCEVExprForVPValue(Ptr, CostCtx.PSE, CostCtx.L); | |
| bool IsSingleScalarLoad = | |
| !isa<SCEVCouldNotCompute>(PtrSCEV) && | |
| CostCtx.PSE.getSE()->isLoopInvariant(PtrSCEV, CostCtx.L); | |
| ReplaceWith(VPI, | |
| new VPReplicateRecipe( | |
| I, Ptr, /*IsSingleScalar=*/IsSingleScalarLoad, | |
| /*Mask=*/nullptr, *VPI, *VPI, VPI->getDebugLoc())); | |
| return true; | |
| }); | |
| } | |
| // Widen unmasked unit-stride consecutive accesses, matching the legacy CM. | |
| VPlanTransforms::runPass( | |
| "widenConsecutiveMemOps", ProcessSubset, Plan, [&](VPInstruction *VPI) { | |
| Instruction *I = VPI->getUnderlyingInstr(); | |
| if (RecipeBuilder.isPredicatedInst(I)) | |
| return false; | |
| bool IsLoad = VPI->getOpcode() == Instruction::Load; | |
| VPValue *Ptr = VPI->getOperand(!IsLoad); | |
| Type *ScalarTy = | |
| IsLoad ? VPI->getScalarType() : VPI->getOperand(0)->getScalarType(); | |
| if (getConstantStride(Ptr, ScalarTy, CostCtx.PSE, CostCtx.L) != 1) | |
| return false; | |
| Type *StrideTy = | |
| Plan.getDataLayout().getIndexType(Ptr->getScalarType()); | |
| VPValue *StrideOne = Plan.getConstantInt(StrideTy, 1); | |
| auto *VectorPtr = new VPVectorPointerRecipe( | |
| Ptr, ScalarTy, StrideOne, vputils::getGEPFlagsForPtr(Ptr), | |
| VPI->getDebugLoc()); | |
| VectorPtr->insertBefore(VPI); | |
| VPRecipeBase *WidenedR; | |
| if (IsLoad) | |
| WidenedR = new VPWidenLoadRecipe(*cast<LoadInst>(I), VectorPtr, | |
| /*Mask=*/nullptr, | |
| /*Consecutive=*/true, *VPI, | |
| VPI->getDebugLoc()); | |
| else | |
| WidenedR = new VPWidenStoreRecipe( | |
| *cast<StoreInst>(I), VectorPtr, VPI->getOperand(0), | |
| /*Mask=*/nullptr, /*Consecutive=*/true, *VPI, VPI->getDebugLoc()); | |
| return ReplaceWith(VPI, WidenedR); | |
| }); | |
| VPlanTransforms::runPass("delegateMemOpWideningToLegacyCM", ProcessSubset, | |
| Plan, [&](VPInstruction *VPI) { | |
| if (VPRecipeBase *Recipe = | |
| RecipeBuilder.tryToWidenMemory(VPI, Range)) | |
| return ReplaceWith(VPI, Recipe); | |
| return Scalarize(VPI); | |
| }); | |
| } | |
| void VPlanTransforms::makeScalarizationDecisions(VPlan &Plan, VFRange &Range) { | |
| if (LoopVectorizationPlanner::getDecisionAndClampRange( | |
| [&](ElementCount VF) { return VF.isScalar(); }, Range)) | |
| return; | |
| PostOrderTraversal<VPBlockDeepTraversalWrapper<VPBlockBase *>> POT( | |
| Plan.getEntry()); | |
| for (VPBasicBlock *VPBB : VPBlockUtils::blocksOnly<VPBasicBlock>(POT)) { | |
| for (VPRecipeBase &R : make_early_inc_range(reverse(*VPBB))) { | |
| auto *VPI = dyn_cast<VPInstruction>(&R); | |
| if (!VPI) | |
| continue; | |
| auto *I = cast_or_null<Instruction>(VPI->getUnderlyingValue()); | |
| // Wouldn't be able to create a `VPReplicateRecipe` anyway. | |
| if (!I) | |
| continue; | |
| // If executing other lanes produces side-effects we can't avoid them. | |
| if (VPI->mayHaveSideEffects()) | |
| continue; | |
| // We want to drop the mask operand, verify we can safely do that. | |
| if (VPI->isMasked() && !VPI->isSafeToSpeculativelyExecute()) | |
| continue; | |
| // Avoid rewriting IV increment as that interferes with | |
| // `removeRedundantCanonicalIVs`. | |
| if (VPI->getOpcode() == Instruction::Add && | |
| any_of(VPI->operands(), IsaPred<VPWidenIntOrFpInductionRecipe>)) | |
| continue; | |
| // Other lanes are needed - can't drop them. | |
| if (!vputils::onlyFirstLaneUsed(VPI)) | |
| continue; | |
| auto *Recipe = VPBuilder::createSingleScalarOp( | |
| VPI->getOpcode(), VPI->operandsWithoutMask(), /*Mask=*/nullptr, *VPI, | |
| *VPI, VPI->getDebugLoc(), I); | |
| Recipe->insertBefore(VPI); | |
| VPI->replaceAllUsesWith(Recipe); | |
| VPI->eraseFromParent(); | |
| } | |
| } | |
| } | |
| /// Returns true if \p Info's parameter kinds are compatible with \p Args. | |
| static bool areVFParamsOk(const VFInfo &Info, ArrayRef<VPValue *> Args, | |
| PredicatedScalarEvolution &PSE, const Loop *L) { | |
| ScalarEvolution *SE = PSE.getSE(); | |
| return all_of(Info.Shape.Parameters, [&](VFParameter Param) { | |
| switch (Param.ParamKind) { | |
| case VFParamKind::Vector: | |
| case VFParamKind::GlobalPredicate: | |
| return true; | |
| case VFParamKind::OMP_Uniform: | |
| return SE->isSCEVable(Args[Param.ParamPos]->getScalarType()) && | |
| SE->isLoopInvariant( | |
| vputils::getSCEVExprForVPValue(Args[Param.ParamPos], PSE, L), | |
| L); | |
| case VFParamKind::OMP_Linear: | |
| return match(vputils::getSCEVExprForVPValue(Args[Param.ParamPos], PSE, L), | |
| m_scev_AffineAddRec( | |
| m_SCEV(), m_scev_SpecificSInt(Param.LinearStepOrPos), | |
| m_SpecificLoop(L))); | |
| default: | |
| return false; | |
| } | |
| }); | |
| } | |
| /// Find a vector variant of \p CI for \p VF, respecting \p MaskRequired. | |
| /// Returns the variant function, or nullptr. Masked variants are assumed to | |
| /// take the mask as a trailing parameter. | |
| static Function *findVectorVariant(CallInst *CI, ArrayRef<VPValue *> Args, | |
| ElementCount VF, bool MaskRequired, | |
| PredicatedScalarEvolution &PSE, | |
| const Loop *L) { | |
| if (CI->isNoBuiltin()) | |
| return nullptr; | |
| auto Mappings = VFDatabase::getMappings(*CI); | |
| const auto *It = find_if(Mappings, [&](const VFInfo &Info) { | |
| return Info.Shape.VF == VF && (!MaskRequired || Info.isMasked()) && | |
| areVFParamsOk(Info, Args, PSE, L); | |
| }); | |
| if (It == Mappings.end()) | |
| return nullptr; | |
| return CI->getModule()->getFunction(It->VectorName); | |
| } | |
| namespace { | |
| /// The outcome of choosing how to widen a call at a given VF. | |
| struct CallWideningDecision { | |
| enum class KindTy { Scalarize, Intrinsic, VectorVariant }; | |
| CallWideningDecision(KindTy Kind, Function *Variant = nullptr) | |
| : Kind(Kind), Variant(Variant) {} | |
| KindTy Kind; | |
| /// Set when Kind == VectorVariant. | |
| Function *Variant; | |
| bool operator==(const CallWideningDecision &Other) const { | |
| return Kind == Other.Kind && Variant == Other.Variant; | |
| } | |
| }; | |
| } // namespace | |
| /// Pick the cheapest widening for the call \p VPI at \p VF among scalarization, | |
| /// vector intrinsic, and vector library variant. | |
| static CallWideningDecision decideCallWidening(VPInstruction &VPI, | |
| ArrayRef<VPValue *> Ops, | |
| ElementCount VF, | |
| VPCostContext &CostCtx) { | |
| auto *CI = cast<CallInst>(VPI.getUnderlyingInstr()); | |
| // Scalar VFs and calls forced or known to scalarize always replicate. | |
| if (VF.isScalar() || CostCtx.willBeScalarized(CI, VF)) | |
| return CallWideningDecision::KindTy::Scalarize; | |
| auto *CalledFn = cast<Function>( | |
| VPI.getOperand(VPI.getNumOperandsWithoutMask() - 1)->getLiveInIRValue()); | |
| Type *ResultTy = VPI.getScalarType(); | |
| Intrinsic::ID ID = getVectorIntrinsicIDForCall(CI, &CostCtx.TLI); | |
| bool MaskRequired = CostCtx.isMaskRequired(CI); | |
| // Pseudo intrinsics (assume, lifetime, ...) are always scalarized. | |
| if (ID && VPCostContext::isFreeScalarIntrinsic(ID)) | |
| return CallWideningDecision::KindTy::Scalarize; | |
| InstructionCost ScalarCost = | |
| VPReplicateRecipe::computeCallCost(CalledFn, ResultTy, Ops, | |
| /*IsSingleScalar=*/false, VF, CostCtx); | |
| Function *VecFunc = | |
| findVectorVariant(CI, Ops, VF, MaskRequired, CostCtx.PSE, CostCtx.L); | |
| InstructionCost VecCallCost = InstructionCost::getInvalid(); | |
| if (VecFunc) | |
| VecCallCost = VPWidenCallRecipe::computeCallCost(VecFunc, CostCtx); | |
| // Prefer the intrinsic if it is at least as cheap as scalarizing and any | |
| // available vector variant. | |
| if (ID) { | |
| InstructionCost IntrinsicCost = | |
| VPWidenIntrinsicRecipe::computeCallCost(ID, Ops, VPI, VF, CostCtx); | |
| if (IntrinsicCost.isValid() && ScalarCost >= IntrinsicCost && | |
| (!VecFunc || VecCallCost >= IntrinsicCost)) | |
| return CallWideningDecision::KindTy::Intrinsic; | |
| } | |
| // Otherwise, use a vector library variant when it beats scalarizing. | |
| if (VecFunc && ScalarCost >= VecCallCost) | |
| return {CallWideningDecision::KindTy::VectorVariant, VecFunc}; | |
| return CallWideningDecision::KindTy::Scalarize; | |
| } | |
| void VPlanTransforms::makeCallWideningDecisions(VPlan &Plan, VFRange &Range, | |
| VPRecipeBuilder &RecipeBuilder, | |
| VPCostContext &CostCtx) { | |
| for (VPBasicBlock *VPBB : VPBlockUtils::blocksAs<VPBasicBlock>( | |
| vp_depth_first_shallow(Plan.getVectorLoopRegion()->getEntry()))) { | |
| for (VPRecipeBase &R : make_early_inc_range(*VPBB)) { | |
| auto *VPI = dyn_cast<VPInstruction>(&R); | |
| if (!VPI || !VPI->getUnderlyingValue() || | |
| VPI->getOpcode() != Instruction::Call) | |
| continue; | |
| auto *CI = cast<CallInst>(VPI->getUnderlyingInstr()); | |
| SmallVector<VPValue *, 4> Ops(VPI->op_begin(), | |
| VPI->op_begin() + CI->arg_size()); | |
| CallWideningDecision Decision = | |
| decideCallWidening(*VPI, Ops, Range.Start, CostCtx); | |
| LoopVectorizationPlanner::getDecisionAndClampRange( | |
| [&](ElementCount VF) { | |
| return Decision == decideCallWidening(*VPI, Ops, VF, CostCtx); | |
| }, | |
| Range); | |
| VPSingleDefRecipe *Replacement = nullptr; | |
| switch (Decision.Kind) { | |
| case CallWideningDecision::KindTy::Intrinsic: { | |
| Intrinsic::ID ID = getVectorIntrinsicIDForCall(CI, &CostCtx.TLI); | |
| Type *ResultTy = VPI->getScalarType(); | |
| Replacement = new VPWidenIntrinsicRecipe(*CI, ID, Ops, ResultTy, *VPI, | |
| *VPI, VPI->getDebugLoc()); | |
| break; | |
| } | |
| case CallWideningDecision::KindTy::VectorVariant: { | |
| // Masked variants take the mask as a trailing parameter, so they have | |
| // one more parameter than the original call's arguments. | |
| if (Decision.Variant->arg_size() > Ops.size()) { | |
| VPValue *Mask = VPI->isMasked() ? VPI->getMask() : Plan.getTrue(); | |
| Ops.push_back(Mask); | |
| } | |
| Ops.push_back(VPI->getOperand(VPI->getNumOperandsWithoutMask() - 1)); | |
| Replacement = new VPWidenCallRecipe(CI, Decision.Variant, Ops, *VPI, | |
| *VPI, VPI->getDebugLoc()); | |
| break; | |
| } | |
| case CallWideningDecision::KindTy::Scalarize: | |
| Replacement = RecipeBuilder.handleReplication(VPI, Range); | |
| break; | |
| } | |
| Replacement->insertBefore(VPI); | |
| VPI->replaceAllUsesWith(Replacement); | |
| VPI->eraseFromParent(); | |
| } | |
| } | |
| } | |
| void VPlanTransforms::convertToStridedAccesses(VPlan &Plan, | |
| PredicatedScalarEvolution &PSE, | |
| Loop &L, VPCostContext &Ctx, | |
| VFRange &Range) { | |
| if (Plan.hasScalarVFOnly()) | |
| return; | |
| VPRegionBlock *VectorLoop = Plan.getVectorLoopRegion(); | |
| VPValue *I32VF = nullptr; | |
| for (VPBasicBlock *VPBB : VPBlockUtils::blocksOnly<VPBasicBlock>( | |
| vp_depth_first_shallow(VectorLoop->getEntry()))) { | |
| for (VPRecipeBase &R : make_early_inc_range(*VPBB)) { | |
| auto *LoadR = dyn_cast<VPWidenLoadRecipe>(&R); | |
| // TODO: Support strided store. | |
| // TODO: Transform reverse access into strided access with -1 stride. | |
| // TODO: Transform gather/scatter with uniform address into strided access | |
| // with 0 stride. | |
| // TODO: Transform interleave access into multiple strided accesses. | |
| if (!LoadR || LoadR->isConsecutive()) | |
| continue; | |
| VPValue *Ptr = LoadR->getAddr(); | |
| // Check if this is a strided access by analyzing the address SCEV for an | |
| // affine addRec. | |
| const SCEV *PtrSCEV = vputils::getSCEVExprForVPValue(Ptr, PSE, &L); | |
| const SCEV *Start; | |
| const SCEVConstant *Step; | |
| // TODO: Support non-constant loop invariant stride. | |
| if (!match(PtrSCEV, | |
| m_scev_AffineAddRec(m_SCEV(Start), m_SCEVConstant(Step), | |
| m_SpecificLoop(&L)))) | |
| continue; | |
| Type *LoadTy = LoadR->getScalarType(); | |
| Align Alignment = LoadR->getAlign(); | |
| auto IsProfitable = [&](ElementCount VF) { | |
| Type *DataTy = toVectorTy(LoadTy, VF); | |
| if (!Ctx.TTI.isLegalStridedLoadStore(DataTy, Alignment)) | |
| return false; | |
| const InstructionCost CurrentCost = LoadR->computeCost(VF, Ctx); | |
| const InstructionCost StridedLoadStoreCost = | |
| VPWidenMemIntrinsicRecipe::computeMemIntrinsicCost( | |
| Intrinsic::experimental_vp_strided_load, DataTy, | |
| LoadR->isMasked(), Alignment, Ctx); | |
| return StridedLoadStoreCost < CurrentCost; | |
| }; | |
| if (!LoopVectorizationPlanner::getDecisionAndClampRange(IsProfitable, | |
| Range)) | |
| continue; | |
| // Invalidate the legacy widening decision so the cost of replaced load is | |
| // not counted during precomputeCosts. | |
| // TODO: Remove once the legacy exit cost computation is retired. | |
| for (ElementCount VF : Range) | |
| Ctx.invalidateWideningDecision(&LoadR->getIngredient(), VF); | |
| // Get VF as i32 for the vector length operand. | |
| if (!I32VF) { | |
| VPBuilder Builder(Plan.getVectorPreheader()); | |
| I32VF = Builder.createScalarZExtOrTrunc( | |
| &Plan.getVF(), Type::getInt32Ty(Plan.getContext()), | |
| Plan.getVF().getScalarType(), DebugLoc::getUnknown()); | |
| } | |
| VPBuilder Builder(LoadR); | |
| // Create the base pointer of strided access. | |
| // TODO: reuse VPDerivedIVRecipe for base pointer computation when it | |
| // supports a general VPValue as the start value. | |
| VPValue *StartVPV = | |
| VPSCEVExpander(Builder, *PSE.getSE(), LoadR->getDebugLoc()) | |
| .tryToExpand(Start); | |
| if (!StartVPV) | |
| StartVPV = VPBuilder(Plan.getEntry()).createExpandSCEV(Start); | |
| VPValue *StrideInBytes = Plan.getOrAddLiveIn(Step->getValue()); | |
| Type *IndexTy = Plan.getDataLayout().getIndexType(Ptr->getScalarType()); | |
| assert(IndexTy == StrideInBytes->getScalarType() && | |
| "Stride type from SCEV must match the index type"); | |
| VPValue *CanIV = Builder.createScalarSExtOrTrunc( | |
| VectorLoop->getCanonicalIV(), IndexTy, | |
| VectorLoop->getCanonicalIVType(), DebugLoc::getUnknown()); | |
| auto *AddRecPtr = cast<SCEVAddRecExpr>(PtrSCEV); | |
| auto *Offset = Builder.createOverflowingOp( | |
| Instruction::Mul, {CanIV, StrideInBytes}, | |
| {AddRecPtr->hasNoUnsignedWrap(), /*HasNSW=*/false}); | |
| GEPNoWrapFlags NWFlags = AddRecPtr->hasNoUnsignedWrap() | |
| ? GEPNoWrapFlags::noUnsignedWrap() | |
| : GEPNoWrapFlags::none(); | |
| VPValue *BasePtr = Builder.createNoWrapPtrAdd(StartVPV, Offset, NWFlags); | |
| // Create a new vector pointer for strided access. | |
| VPValue *NewPtr = Builder.createVectorPointer( | |
| BasePtr, Type::getInt8Ty(Plan.getContext()), StrideInBytes, NWFlags, | |
| LoadR->getDebugLoc()); | |
| VPValue *Mask = LoadR->getMask(); | |
| if (!Mask) | |
| Mask = Plan.getTrue(); | |
| auto *StridedLoad = Builder.createWidenMemIntrinsic( | |
| Intrinsic::experimental_vp_strided_load, | |
| {NewPtr, StrideInBytes, Mask, I32VF}, LoadTy, Alignment, *LoadR, | |
| LoadR->getDebugLoc()); | |
| LoadR->replaceAllUsesWith(StridedLoad); | |
| } | |
| } | |
| } |