| //===- SLPVectorizer.cpp - A bottom up SLP Vectorizer ---------------------===// | |
| // | |
| // 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 | |
| // | |
| //===----------------------------------------------------------------------===// | |
| // | |
| // This pass implements the Bottom Up SLP vectorizer. It detects consecutive | |
| // stores that can be put together into vector-stores. Next, it attempts to | |
| // construct vectorizable tree using the use-def chains. If a profitable tree | |
| // was found, the SLP vectorizer performs vectorization on the tree. | |
| // | |
| // The pass is inspired by the work described in the paper: | |
| // "Loop-Aware SLP in GCC" by Ira Rosen, Dorit Nuzman, Ayal Zaks. | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "llvm/Transforms/Vectorize/SLPVectorizer.h" | |
| #include "SLPVectorizer/SLPCompatibilityAnalysis.h" | |
| #include "SLPVectorizer/SLPCostAnalysis.h" | |
| #include "SLPVectorizer/SLPMemoryUtils.h" | |
| #include "SLPVectorizer/SLPReductionUtils.h" | |
| #include "SLPVectorizer/SLPShuffleAnalysis.h" | |
| #include "SLPVectorizer/SLPTypeUtils.h" | |
| #include "SLPVectorizer/SLPUtils.h" | |
| #include "VectorizeOptions.h" | |
| #include "llvm/ADT/DenseMap.h" | |
| #include "llvm/ADT/DenseSet.h" | |
| #include "llvm/ADT/PriorityQueue.h" | |
| #include "llvm/ADT/STLExtras.h" | |
| #include "llvm/ADT/ScopeExit.h" | |
| #include "llvm/ADT/SetVector.h" | |
| #include "llvm/ADT/SmallBitVector.h" | |
| #include "llvm/ADT/SmallPtrSet.h" | |
| #include "llvm/ADT/SmallSet.h" | |
| #include "llvm/ADT/SmallString.h" | |
| #include "llvm/ADT/SmallVectorExtras.h" | |
| #include "llvm/ADT/Statistic.h" | |
| #include "llvm/ADT/iterator.h" | |
| #include "llvm/ADT/iterator_range.h" | |
| #include "llvm/Analysis/AliasAnalysis.h" | |
| #include "llvm/Analysis/AssumptionCache.h" | |
| #include "llvm/Analysis/CodeMetrics.h" | |
| #include "llvm/Analysis/ConstantFolding.h" | |
| #include "llvm/Analysis/DemandedBits.h" | |
| #include "llvm/Analysis/DomTreeUpdater.h" | |
| #include "llvm/Analysis/GlobalsModRef.h" | |
| #include "llvm/Analysis/IVDescriptors.h" | |
| #include "llvm/Analysis/Loads.h" | |
| #include "llvm/Analysis/LoopInfo.h" | |
| #include "llvm/Analysis/MemoryLocation.h" | |
| #include "llvm/Analysis/OptimizationRemarkEmitter.h" | |
| #include "llvm/Analysis/ScalarEvolution.h" | |
| #include "llvm/Analysis/ScalarEvolutionExpressions.h" | |
| #include "llvm/Analysis/TargetLibraryInfo.h" | |
| #include "llvm/Analysis/TargetTransformInfo.h" | |
| #include "llvm/Analysis/ValueTracking.h" | |
| #include "llvm/Analysis/VectorUtils.h" | |
| #include "llvm/IR/Attributes.h" | |
| #include "llvm/IR/BasicBlock.h" | |
| #include "llvm/IR/CFG.h" | |
| #include "llvm/IR/Constant.h" | |
| #include "llvm/IR/Constants.h" | |
| #include "llvm/IR/DataLayout.h" | |
| #include "llvm/IR/DerivedTypes.h" | |
| #include "llvm/IR/Dominators.h" | |
| #include "llvm/IR/Function.h" | |
| #include "llvm/IR/IRBuilder.h" | |
| #include "llvm/IR/InstrTypes.h" | |
| #include "llvm/IR/Instruction.h" | |
| #include "llvm/IR/Instructions.h" | |
| #include "llvm/IR/IntrinsicInst.h" | |
| #include "llvm/IR/Intrinsics.h" | |
| #include "llvm/IR/MDBuilder.h" | |
| #include "llvm/IR/Module.h" | |
| #include "llvm/IR/Operator.h" | |
| #include "llvm/IR/PatternMatch.h" | |
| #include "llvm/IR/ProfDataUtils.h" | |
| #include "llvm/IR/Type.h" | |
| #include "llvm/IR/Use.h" | |
| #include "llvm/IR/User.h" | |
| #include "llvm/IR/Value.h" | |
| #include "llvm/IR/ValueHandle.h" | |
| #include "llvm/IR/VectorTypeUtils.h" | |
| #ifdef EXPENSIVE_CHECKS | |
| #include "llvm/IR/Verifier.h" | |
| #endif | |
| #include "llvm/Pass.h" | |
| #include "llvm/Support/Casting.h" | |
| #include "llvm/Support/Compiler.h" | |
| #include "llvm/Support/DOTGraphTraits.h" | |
| #include "llvm/Support/Debug.h" | |
| #include "llvm/Support/DebugCounter.h" | |
| #include "llvm/Support/ErrorHandling.h" | |
| #include "llvm/Support/GraphWriter.h" | |
| #include "llvm/Support/InstructionCost.h" | |
| #include "llvm/Support/KnownBits.h" | |
| #include "llvm/Support/MathExtras.h" | |
| #include "llvm/Support/SaveAndRestore.h" | |
| #include "llvm/Support/raw_ostream.h" | |
| #include "llvm/Transforms/Utils/InjectTLIMappings.h" | |
| #include "llvm/Transforms/Utils/Local.h" | |
| #include "llvm/Transforms/Utils/LoopUtils.h" | |
| #include "llvm/Transforms/Utils/ScalarEvolutionExpander.h" | |
| #include <algorithm> | |
| #include <cassert> | |
| #include <cstdint> | |
| #include <iterator> | |
| #include <map> | |
| #include <memory> | |
| #include <optional> | |
| #include <string> | |
| #include <tuple> | |
| #include <utility> | |
| using namespace llvm; | |
| using namespace llvm::PatternMatch; | |
| using namespace slpvectorizer; | |
| using namespace std::placeholders; | |
| #define SV_NAME "slp-vectorizer" | |
| #define DEBUG_TYPE "SLP" | |
| STATISTIC(NumVectorInstructions, "Number of vector instructions generated"); | |
| STATISTIC(NumStridedStoreChains, "Number of vectorized stride stores"); | |
| STATISTIC(NumStoreChains, "Number of vector stores created"); | |
| STATISTIC(NumVectorizedStores, "Number of vectorized stores"); | |
| DEBUG_COUNTER(VectorizedGraphs, "slp-vectorized", | |
| "Controls which SLP graphs should be vectorized."); | |
| // Limit the number of alias checks. The limit is chosen so that | |
| // it has no negative effect on the llvm benchmarks. | |
| static const unsigned AliasedCheckLimit = 10; | |
| // Another limit for the alias checks: The maximum distance between load/store | |
| // instructions where alias checks are done. | |
| // This limit is useful for very large basic blocks. | |
| static const unsigned MaxMemDepDistance = 160; | |
| /// If the ScheduleRegionSizeBudget is exhausted, we allow small scheduling | |
| /// regions to be handled. | |
| static const int MinScheduleRegionSize = 16; | |
| /// If the reduction budget is exhausted, we allow small reduction trees to be | |
| /// handled. | |
| static const unsigned MinReductionTreeSize = 2048; | |
| /// Maximum allowed number of operands in the PHI nodes. | |
| static const unsigned MaxPHINumOperands = 128; | |
| namespace { | |
| /// A vectorized part of a split reduction, combined into the final reduction | |
| /// result by the horizontal reduction emitter. | |
| struct ReductionVectorPart { | |
| /// The vectorized value, tracked in case it is replaced while other parts | |
| /// are vectorized. | |
| WeakTrackingVH Vec; | |
| /// The number of times each lane is repeated in the reduction (emitted as a | |
| /// multiplication by the scale for add/fadd reductions). | |
| unsigned Scale = 1; | |
| /// Signedness of \p Vec for reductions, operating on truncated types. | |
| bool IsSigned = false; | |
| /// True if the value was already reduced in-tree. | |
| bool ReducedInTree = false; | |
| /// True if the part contribution is subtracted from (rather than added to) | |
| /// the final reduction result. Used for reassociated fadd reductions, | |
| /// flattened through fsub/fneg operations. | |
| bool Negated = false; | |
| }; | |
| } // namespace | |
| namespace { | |
| class HorizontalReduction; | |
| } // namespace | |
| /// Bottom Up SLP Vectorizer. | |
| class slpvectorizer::BoUpSLP { | |
| class TreeEntry; | |
| class ScheduleEntity; | |
| class ScheduleData; | |
| class ScheduleCopyableData; | |
| class ScheduleBundle; | |
| class ShuffleCostEstimator; | |
| class ShuffleInstructionBuilder; | |
| public: | |
| /// If we decide to generate strided load / store, this struct contains all | |
| /// the necessary info. It's fields are calculated by analyzeRtStrideCandidate | |
| /// and analyzeConstantStrideCandidate. Note that Stride can be given either | |
| /// as a SCEV or as a Value if it already exists. To get the stride in bytes, | |
| /// StrideVal (or value obtained from StrideSCEV) has to by multiplied by the | |
| /// size of element of FixedVectorType. | |
| struct StridedPtrInfo { | |
| Value *StrideVal = nullptr; | |
| const SCEV *StrideSCEV = nullptr; | |
| FixedVectorType *Ty = nullptr; | |
| }; | |
| /// Tracks the state we can represent the loads in the given sequence. | |
| enum class LoadsState { | |
| Gather, | |
| Vectorize, | |
| ScatterVectorize, | |
| StridedVectorize, | |
| CompressVectorize, | |
| BlendedLoadVectorize | |
| }; | |
| using ValueList = SmallVector<Value *, 8>; | |
| using InstrList = SmallVector<Instruction *, 16>; | |
| using ValueSet = SmallPtrSet<Value *, 16>; | |
| using StoreList = SmallVector<StoreInst *, 8>; | |
| using ExtraValueToDebugLocsMap = SmallDenseSet<Value *, 4>; | |
| using OrdersType = SmallVector<unsigned, 4>; | |
| const VectorizeOptions &Opts; | |
| BoUpSLP(const VectorizeOptions &Opts, Function *Func, ScalarEvolution *Se, | |
| TargetTransformInfo *Tti, TargetLibraryInfo *TLi, AAResults *Aa, | |
| LoopInfo *Li, DominatorTree *Dt, AssumptionCache *AC, | |
| DemandedBits *DB, const DataLayout *DL, | |
| OptimizationRemarkEmitter *ORE) | |
| : Opts(Opts), AA(Aa), BatchAA(std::in_place, *Aa), F(Func), SE(Se), | |
| TTI(Tti), TLI(TLi), LI(Li), DT(Dt), AC(AC), DB(DB), DL(DL), ORE(ORE), | |
| CostKind(getSLPCostKind(Func)), | |
| Builder(Se->getModule(), TargetFolder(*DL)) { | |
| CodeMetrics::collectEphemeralValues(F, AC, EphValues); | |
| // Use the vector register size specified by the target unless overridden | |
| // by a command-line option. | |
| // TODO: It would be better to limit the vectorization factor based on | |
| // data type rather than just register size. For example, x86 AVX has | |
| // 256-bit registers, but it does not support integer operations | |
| // at that width (that requires AVX2). | |
| MaxVecRegSize = Opts.slp_max_reg_size.value_or( | |
| TTI->getRegisterBitWidth(TargetTransformInfo::RGK_FixedWidthVector) | |
| .getFixedValue()); | |
| MinVecRegSize = | |
| Opts.slp_min_reg_size.value_or(TTI->getMinVectorRegisterBitWidth()); | |
| } | |
| /// Vectorize the tree that starts with the elements in \p VL. | |
| /// Returns the vectorized root. | |
| Value *vectorizeTree(); | |
| /// Vectorize the tree but with the list of externally used values \p | |
| /// ExternallyUsedValues. Values in this MapVector can be replaced but the | |
| /// generated extractvalue instructions. | |
| Value * | |
| vectorizeTree(const ExtraValueToDebugLocsMap &ExternallyUsedValues, | |
| Instruction *ReductionRoot = nullptr, | |
| ArrayRef<ReductionVectorPart> VectorValuesAndScales = {}); | |
| /// \returns the cost incurred by unwanted spills and fills, caused by | |
| /// holding live values over call sites. | |
| InstructionCost getSpillCost(); | |
| TargetTransformInfo::TargetCostKind getCostKind() const { return CostKind; } | |
| /// Calculates the cost of the subtrees, trims non-profitable ones and returns | |
| /// final cost. | |
| InstructionCost | |
| calculateTreeCostAndTrimNonProfitable(ArrayRef<Value *> VectorizedVals = {}, | |
| Instruction *RdxRoot = nullptr); | |
| /// Finds i1 and/or nodes whose operand nodes are booleanized wide leaves | |
| /// (one-use truncs of wide values or zero-tests of values from [0, 1]) and | |
| /// registers them in MinBWs to be emitted in the wide leaf type. | |
| void detectBooleanizedNodes(); | |
| /// \returns the vectorization cost of the subtree that starts at \p VL. | |
| /// A negative number means that this is profitable. | |
| InstructionCost getTreeCost(InstructionCost TreeCost, | |
| ArrayRef<Value *> VectorizedVals = {}, | |
| InstructionCost ReductionCost = TTI::TCC_Free, | |
| Instruction *RdxRoot = nullptr); | |
| /// Construct a vectorizable tree that starts at \p Roots, ignoring users for | |
| /// the purpose of scheduling and extraction in the \p UserIgnoreLst. | |
| void buildTree(ArrayRef<Value *> Roots, | |
| const SmallDenseSet<Value *> &UserIgnoreLst); | |
| /// Construct a vectorizable tree that starts at \p Roots. | |
| void buildTree(ArrayRef<Value *> Roots); | |
| /// Sets the narrowed reduction chain instructions, dropped together with | |
| /// the reduction. | |
| void setNarrowedChainInsts(ArrayRef<Instruction *> Insts) { | |
| NarrowedChainInsts.insert(Insts.begin(), Insts.end()); | |
| } | |
| /// Returns true if the last buildTree() observed a may-alias memory | |
| /// dependency between two distinct, range-checkable base objects, i.e. a | |
| /// dependency that could be turned into a runtime alias check. | |
| bool hasRuntimeCheckableBlockers() const { | |
| return HasRuntimeCheckableBlockers; | |
| } | |
| /// Records whether a may-alias dependency between distinct, range-checkable | |
| /// base objects has been observed, so the caller can decide to retry with | |
| /// runtime alias checks enabled. | |
| void setHasRuntimeCheckableBlockers(bool V) { | |
| HasRuntimeCheckableBlockers = V; | |
| } | |
| /// Returns true if the last buildTree() kept a may-alias memory dependency | |
| /// that is not runtime-checkable (call or a non-simple mem access). Such a | |
| /// dependency cannot be dropped, so a runtime-checks retry cannot unblock the | |
| /// region and would be pure overhead. | |
| bool hasNonCheckableMemBlocker() const { return HasNonCheckableMemBlocker; } | |
| /// Records that a non-runtime-checkable may-alias dependency was kept. | |
| void setHasNonCheckableMemBlocker(bool V) { HasNonCheckableMemBlocker = V; } | |
| /// Returns true if the current vectorization attempt may drop | |
| /// runtime-checkable may-alias dependencies and guard the region with | |
| /// runtime alias checks. | |
| bool isTryingRuntimeAliasChecks() const { return TryRuntimeAliasChecks; } | |
| /// Enables or disables dropping runtime-checkable may-alias dependencies in | |
| /// favor of runtime alias checks for the current vectorization attempt. | |
| void setTryRuntimeAliasChecks(bool V) { TryRuntimeAliasChecks = V; } | |
| /// Splits the stored values of the root stores into halves, vectorized as | |
| /// separate subtrees, while the returned guard is alive. | |
| [[nodiscard]] SaveAndRestore<bool> splitStoredValues() { | |
| return SaveAndRestore<bool>(SplitStoredValues, true); | |
| } | |
| /// Checks if the graph is built for the \p Stores and their stored values, | |
| /// looking through the vectorized cast node, are vectorized as the alternate | |
| /// node with the different opcodes in the halves. | |
| bool canSplitStoredValues(ArrayRef<Value *> Stores) const { | |
| if (VectorizableTree.empty() || getRootNode().isGather() || | |
| getRootNodeScalars() != Stores) | |
| return false; | |
| const TreeEntry *E = getOperandEntry(&getRootNode(), 0); | |
| bool IsCast = E->State == TreeEntry::Vectorize && !E->isAltShuffle() && | |
| E->getMainOp()->isCast(); | |
| if (IsCast) | |
| E = getOperandEntry(E, 0); | |
| if (E->State != TreeEntry::Vectorize || !E->isAltShuffle()) | |
| return false; | |
| auto GetOpcode = [&](Value *V) -> unsigned { | |
| V = cast<StoreInst>(V)->getValueOperand(); | |
| if (auto *CI = dyn_cast<CastInst>(V); CI && IsCast) | |
| V = CI->getOperand(0); | |
| auto *I = dyn_cast<Instruction>(V); | |
| return I ? I->getOpcode() : 0; | |
| }; | |
| unsigned Opcode0 = GetOpcode(Stores.front()); | |
| unsigned Opcode1 = GetOpcode(Stores.back()); | |
| ArrayRef<Value *> Half = Stores.take_front(Stores.size() / 2); | |
| return Opcode0 != Opcode1 && | |
| all_of(Half, [&](Value *V) { return GetOpcode(V) == Opcode0; }) && | |
| all_of(Stores.drop_front(Half.size()), | |
| [&](Value *V) { return GetOpcode(V) == Opcode1; }); | |
| } | |
| /// Resets the runtime alias check data. | |
| void resetRuntimeAliasCheckState() { | |
| HasRuntimeCheckableBlockers = false; | |
| HasNonCheckableMemBlocker = false; | |
| RTChecksFinalized = false; | |
| RTChecks.clear(); | |
| RTOrigBodyOrder.clear(); | |
| } | |
| /// Snapshots RTChecks.BB's body (non-PHI, non-alloca, non-terminator) into | |
| /// RTOrigBodyOrder in program order, for the scalar fallback. | |
| void captureRuntimeCheckBodySnapshot(); | |
| /// Returns true if \p BB satisfies the block-level preconditions for runtime | |
| /// alias check versioning (straight-line, duplicable, not a scalar fallback, | |
| /// function not optimized for size). These checks do not depend on the | |
| /// collected checks, so they can gate the (expensive) optimistic retry before | |
| /// any tree is rebuilt. | |
| bool canVersionBlockForRuntimeChecks(BasicBlock *BB) const; | |
| /// Returns true if the runtime alias checks can be safely emitted to guard | |
| /// the vectorized region. | |
| bool canVersionForRuntimeChecks(); | |
| /// Returns true if \p BB is a scalar fallback block created by runtime alias | |
| /// check versioning. | |
| bool isScalarFallbackBlock(BasicBlock *BB) const { | |
| return ScalarFallbackBlocks.contains(BB); | |
| } | |
| /// Returns true if an optimistic runtime-checks versioning attempt already | |
| /// failed for \p BB, so further retries in the same block can be skipped. | |
| bool runtimeChecksFailedForBlock(BasicBlock *BB) const { | |
| return FailedRuntimeChecksBlocks.contains(BB); | |
| } | |
| /// Records that an optimistic runtime-checks versioning attempt failed for | |
| /// \p BB. | |
| void markRuntimeChecksFailedForBlock(BasicBlock *BB) { | |
| FailedRuntimeChecksBlocks.insert(BB); | |
| } | |
| /// Returns the modeled cost of the runtime alias checks collected during the | |
| /// last (optimistic) buildTree(). | |
| InstructionCost getRuntimeChecksCost() const; | |
| /// Returns true if the last (optimistic) buildTree() collected any runtime | |
| /// alias checks that must guard the vectorized region. | |
| bool hasRuntimeAliasChecks() const { return !RTChecks.BasePairs.empty(); } | |
| /// Returns true if vectorization changed the CFG (i.e. a block was versioned | |
| /// with runtime alias checks). When true, CFG analyses must not be preserved. | |
| bool isCFGChanged() const { return CFGChanged; } | |
| TreeEntry &getRootNode() { | |
| assert(!VectorizableTree.empty() && "No graph to get the first node from"); | |
| return *VectorizableTree.front(); | |
| } | |
| const TreeEntry &getRootNode() const { | |
| assert(!VectorizableTree.empty() && "No graph to get the first node from"); | |
| return *VectorizableTree.front(); | |
| } | |
| /// Returns the scalars of the root node. | |
| ArrayRef<Value *> getRootNodeScalars() const { return getRootNode().Scalars; } | |
| /// Returns the lane the given value is vectorized to in the root node. | |
| unsigned findRootLaneForValue(Value *V) const { | |
| return getRootNode().findLaneForValue(V); | |
| } | |
| /// Returns the type/is-signed info for the root node in the graph without | |
| /// casting. | |
| std::optional<std::pair<Type *, bool>> getRootNodeTypeWithNoCast() const { | |
| const TreeEntry &Root = getRootNode(); | |
| if (Root.State != TreeEntry::Vectorize || Root.isAltShuffle() || | |
| !Root.Scalars.front()->getType()->isIntegerTy()) | |
| return std::nullopt; | |
| auto It = MinBWs.find(&Root); | |
| if (It != MinBWs.end()) | |
| return std::make_pair(IntegerType::get(Root.Scalars.front()->getContext(), | |
| It->second.first), | |
| It->second.second); | |
| if (Root.getOpcode() == Instruction::ZExt || | |
| Root.getOpcode() == Instruction::SExt) | |
| return std::make_pair(cast<CastInst>(Root.getMainOp())->getSrcTy(), | |
| Root.getOpcode() == Instruction::SExt); | |
| return std::nullopt; | |
| } | |
| /// Checks if the root graph node can be emitted with narrower bitwidth at | |
| /// codegen and returns it signedness, if so. | |
| bool isSignedMinBitwidthRootNode() const { | |
| return MinBWs.at(&getRootNode()).second; | |
| } | |
| /// Returns reduction type after minbitdth analysis. | |
| FixedVectorType *getReductionType() const { | |
| if (ReductionBitWidth == 0 || | |
| !getRootNodeScalars().front()->getType()->isIntegerTy() || | |
| ReductionBitWidth >= | |
| DL->getTypeSizeInBits(getRootNodeScalars().front()->getType())) | |
| return cast<FixedVectorType>( | |
| getWidenedType(getRootNodeScalars().front()->getType(), | |
| getRootNode().getVectorFactor())); | |
| return cast<FixedVectorType>(getWidenedType( | |
| IntegerType::get(getRootNodeScalars().front()->getContext(), | |
| ReductionBitWidth), | |
| getRootNode().getVectorFactor())); | |
| } | |
| /// Returns true if the tree results in one of the reduced bitcasts variants. | |
| bool isReducedBitcastRoot() const { | |
| return getRootNode().hasState() && | |
| (getRootNode().CombinedOp == TreeEntry::ReducedBitcast || | |
| getRootNode().CombinedOp == TreeEntry::ReducedBitcastBSwap || | |
| getRootNode().CombinedOp == TreeEntry::ReducedBitcastLoads || | |
| getRootNode().CombinedOp == TreeEntry::ReducedBitcastBSwapLoads || | |
| getRootNode().CombinedOp == TreeEntry::ReducedBitPack) && | |
| getRootNode().State == TreeEntry::Vectorize; | |
| } | |
| /// Returns true if the tree results in the reduced cmp bitcast root. | |
| bool isReducedCmpBitcastRoot() const { | |
| return getRootNode().hasState() && | |
| getRootNode().CombinedOp == TreeEntry::ReducedCmpBitcast && | |
| getRootNode().State == TreeEntry::Vectorize; | |
| } | |
| /// Returns true if the tree is a reduction tree. | |
| bool isReductionTree() const { return UserIgnoreList != nullptr; } | |
| /// Builds external uses of the vectorized scalars, i.e. the list of | |
| /// vectorized scalars to be extracted, their lanes and their scalar users. \p | |
| /// ExternallyUsedValues contains additional list of external uses to handle | |
| /// vectorization of reductions. | |
| void | |
| buildExternalUses(const ExtraValueToDebugLocsMap &ExternallyUsedValues = {}); | |
| /// Transforms graph nodes to target specific representations, if profitable. | |
| void transformNodes(); | |
| /// Clear the internal data structures that are created by 'buildTree'. | |
| void deleteTree() { | |
| VectorizableTree.clear(); | |
| ScalarToTreeEntries.clear(); | |
| DeletedNodes.clear(); | |
| TransformedToGatherNodes.clear(); | |
| OperandsToTreeEntry.clear(); | |
| ScalarsInSplitNodes.clear(); | |
| MustGather.clear(); | |
| ReassocScalarToTreeEntries.clear(); | |
| KeptReassocScalars.clear(); | |
| NonScheduledFirst.clear(); | |
| EntryToLastInstruction.clear(); | |
| LastInstructionToPos.clear(); | |
| LoadEntriesToVectorize.clear(); | |
| IsGraphTransformMode = false; | |
| GatheredLoadsEntriesFirst.reset(); | |
| SplatGatheredScalarsRoots.clear(); | |
| NumCanonicalSplatSubtreeEntries = 0; | |
| CompressEntryToData.clear(); | |
| ExternalUses.clear(); | |
| ExternalUsesAsOriginalScalar.clear(); | |
| ExternalUsesWithNonUsers.clear(); | |
| ExternalUseReplacements.clear(); | |
| RTChecks.clear(); | |
| HasRuntimeCheckableBlockers = false; | |
| HasNonCheckableMemBlocker = false; | |
| RTChecksFinalized = false; | |
| for (auto &Iter : BlocksSchedules) { | |
| BlockScheduling *BS = Iter.second.get(); | |
| BS->clear(); | |
| } | |
| MinBWs.clear(); | |
| ReductionBitWidth = 0; | |
| BaseGraphSize = 1; | |
| CastMaxMinBWSizes.reset(); | |
| ExtraBitWidthNodes.clear(); | |
| InstrElementSize.clear(); | |
| UserIgnoreList = nullptr; | |
| NarrowedChainInsts.clear(); | |
| PostponedGathers.clear(); | |
| ValueToGatherNodes.clear(); | |
| TreeEntryToStridedPtrInfoMap.clear(); | |
| CurrentLoopNest.clear(); | |
| MergedLoopBTCs.clear(); | |
| } | |
| unsigned getTreeSize() const { return VectorizableTree.size(); } | |
| /// Returns the size of the current tree, stopping at gathers | |
| /// For instance, even though we continue adding tree entries after | |
| /// a split node, this function stops counting at the split node | |
| /// since it is a gather. This matches the behavior of MinBWs | |
| /// computation. | |
| unsigned getTreeSizeExcludingGathers() const { | |
| unsigned Cnt = 0; | |
| SmallBitVector GatherTreeNodes(VectorizableTree.size(), false); | |
| for (unsigned NodeIdx : seq<unsigned>(VectorizableTree.size())) { | |
| auto &TE = VectorizableTree[NodeIdx]; | |
| if (DeletedNodes.contains(TE.get())) | |
| continue; | |
| auto IsGather = [&](TreeEntry *TE) { | |
| return TE->isGather() || TransformedToGatherNodes.contains(TE) || | |
| GatherTreeNodes.test(TE->Idx); | |
| }; | |
| if (IsGather(TE.get()) || (!TE->UserTreeIndex.UserTE && NodeIdx != 0) || | |
| (TE->UserTreeIndex.UserTE && (IsGather(TE->UserTreeIndex.UserTE)))) { | |
| GatherTreeNodes.set(NodeIdx); | |
| continue; | |
| } | |
| ++Cnt; | |
| } | |
| return Cnt; | |
| } | |
| /// Returns true if the tree gathers any scalars other than constants. | |
| bool hasNonConstantGathers() const { | |
| return any_of(VectorizableTree, [&](const std::unique_ptr<TreeEntry> &TE) { | |
| return !DeletedNodes.contains(TE.get()) && | |
| (TE->isGather() || TransformedToGatherNodes.contains(TE.get())) && | |
| !allConstant(TE->Scalars); | |
| }); | |
| } | |
| /// Returns the base graph size, before any transformations. | |
| unsigned getCanonicalGraphSize() const { return BaseGraphSize; } | |
| /// Number of tree entries that form the splat gather subtrees. | |
| unsigned getNumSplatSubtreeEntries() const { | |
| return NumCanonicalSplatSubtreeEntries; | |
| } | |
| /// Perform LICM and CSE on the newly generated gather sequences. | |
| void optimizeGatherSequence(); | |
| /// Does this non-empty order represent an identity order? Identity | |
| /// should be represented as an empty order, so this is used to | |
| /// decide if we can canonicalize a computed order. Undef elements | |
| /// (represented as size) are ignored. | |
| static bool isIdentityOrder(ArrayRef<unsigned> Order) { | |
| assert(!Order.empty() && "expected non-empty order"); | |
| const unsigned Sz = Order.size(); | |
| return all_of(enumerate(Order), [&](const auto &P) { | |
| return P.value() == P.index() || P.value() == Sz; | |
| }); | |
| } | |
| /// Checks if the specified gather tree entry \p TE can be represented as a | |
| /// shuffled vector entry + (possibly) permutation with other gathers. It | |
| /// implements the checks only for possibly ordered scalars (Loads, | |
| /// ExtractElement, ExtractValue), which can be part of the graph. | |
| /// \param TopToBottom If true, used for the whole tree rotation, false - for | |
| /// sub-tree rotations. \param IgnoreReorder true, if the order of the root | |
| /// node might be ignored. | |
| std::optional<OrdersType> findReusedOrderedScalars(const TreeEntry &TE, | |
| bool TopToBottom, | |
| bool IgnoreReorder); | |
| /// Sort loads into increasing pointers offsets to allow greater clustering. | |
| std::optional<OrdersType> findPartiallyOrderedLoads(const TreeEntry &TE); | |
| /// Gets reordering data for the given tree entry. If the entry is vectorized | |
| /// - just return ReorderIndices, otherwise check if the scalars can be | |
| /// reordered and return the most optimal order. | |
| /// \return std::nullopt if ordering is not important, empty order, if | |
| /// identity order is important, or the actual order. | |
| /// \param TopToBottom If true, include the order of vectorized stores and | |
| /// insertelement nodes, otherwise skip them. | |
| /// \param IgnoreReorder true, if the root node order can be ignored. | |
| std::optional<OrdersType> | |
| getReorderingData(const TreeEntry &TE, bool TopToBottom, bool IgnoreReorder); | |
| /// Checks if it is profitable to reorder the current tree. | |
| /// If the tree does not contain many profitable reordable nodes, better to | |
| /// skip it to save compile time. | |
| bool isProfitableToReorder() const; | |
| /// Reorders the current graph to the most profitable order starting from the | |
| /// root node to the leaf nodes. The best order is chosen only from the nodes | |
| /// of the same size (vectorization factor). Smaller nodes are considered | |
| /// parts of subgraph with smaller VF and they are reordered independently. We | |
| /// can make it because we still need to extend smaller nodes to the wider VF | |
| /// and we can merge reordering shuffles with the widening shuffles. | |
| void reorderTopToBottom(); | |
| /// Reorders the current graph to the most profitable order starting from | |
| /// leaves to the root. It allows to rotate small subgraphs and reduce the | |
| /// number of reshuffles if the leaf nodes use the same order. In this case we | |
| /// can merge the orders and just shuffle user node instead of shuffling its | |
| /// operands. Plus, even the leaf nodes have different orders, it allows to | |
| /// sink reordering in the graph closer to the root node and merge it later | |
| /// during analysis. | |
| void reorderBottomToTop(bool IgnoreReorder = false); | |
| /// Marks the schedule data of the copyable-modeled operands of \p TE for | |
| /// dependency recalculation at the next bundle scheduling. | |
| void markCopyableDepsForRecalc(TreeEntry &TE); | |
| /// \return The vector element size in bits to use when vectorizing the | |
| /// expression tree ending at \p V. If V is a store, the size is the width of | |
| /// the stored value. Otherwise, the size is the width of the largest loaded | |
| /// value reaching V. This method is used by the vectorizer to calculate | |
| /// vectorization factors. | |
| unsigned getVectorElementSize(Value *V); | |
| /// Compute the minimum type sizes required to represent the entries in a | |
| /// vectorizable tree. | |
| void computeMinimumValueSizes(); | |
| // \returns maximum vector register size as set by TTI or overridden by cl::opt. | |
| unsigned getMaxVecRegSize() const { | |
| return MaxVecRegSize; | |
| } | |
| /// \returns the -slp-threshold value, 0 if unset. | |
| int getCostThreshold() const { return Opts.slp_threshold.value_or(0); } | |
| // \returns minimum vector register size as set by cl::opt. | |
| unsigned getMinVecRegSize() const { | |
| return MinVecRegSize; | |
| } | |
| /// \returns the number of parts, the type \p VecTy is split at the codegen | |
| /// phase. The type legalization queries are repeated for the very same types | |
| /// during the analysis, so the results are cached for the function. | |
| unsigned getNumberOfParts( | |
| Type *VecTy, Type *ScalarTy, | |
| unsigned Limit = std::numeric_limits<unsigned>::max()) const { | |
| auto [It, Inserted] = | |
| NumberOfPartsCache.try_emplace(std::make_tuple(VecTy, ScalarTy, Limit)); | |
| if (Inserted) | |
| It->second = slpvectorizer::getNumberOfPartsOrRegs( | |
| /*QueryNumParts=*/true, *TTI, VecTy, ScalarTy, Opts.slp_revec, Limit); | |
| return It->second; | |
| } | |
| /// \returns the number of parts, the type \p VecTy is split at the codegen | |
| /// phase. The type legalization queries are repeated for the very same types | |
| /// during the analysis, so the results are cached for the function. | |
| unsigned getRegUsageForType( | |
| Type *VecTy, Type *ScalarTy, | |
| unsigned Limit = std::numeric_limits<unsigned>::max()) const { | |
| auto [It, Inserted] = | |
| NumberOfRegsCache.try_emplace(std::make_tuple(VecTy, ScalarTy, Limit)); | |
| if (Inserted) | |
| It->second = slpvectorizer::getNumberOfPartsOrRegs( | |
| /*QueryNumParts=*/false, *TTI, VecTy, ScalarTy, Opts.slp_revec, | |
| Limit); | |
| return It->second; | |
| } | |
| unsigned getMinVF(unsigned Sz) const { | |
| return std::max(2U, getMinVecRegSize() / Sz); | |
| } | |
| unsigned getMaximumVF(unsigned ElemWidth, unsigned Opcode) const { | |
| unsigned MaxVF = | |
| Opts.slp_max_vf.value_or(TTI->getMaximumVF(ElemWidth, Opcode)); | |
| return MaxVF ? MaxVF : UINT_MAX; | |
| } | |
| /// Check if homogeneous aggregate is isomorphic to some VectorType. | |
| /// Accepts homogeneous multidimensional aggregate of scalars/vectors like | |
| /// {[4 x i16], [4 x i16]}, { <2 x float>, <2 x float> }, | |
| /// {{{i16, i16}, {i16, i16}}, {{i16, i16}, {i16, i16}}} and so on. | |
| /// | |
| /// \returns number of elements in vector if isomorphism exists, 0 otherwise. | |
| unsigned canMapToVector(Type *T) const; | |
| /// \returns true if the vectorized insertvalue result can be stored directly | |
| /// as a vector, i.e. every insertvalue with an external user is consumed by a | |
| /// single store only. | |
| bool canVectorStoreInsertValue(const TreeEntry *E) const; | |
| /// \returns the source vector type for an InsertElement/InsertValue | |
| /// buildvector node \p E: the inserted vector type for insertelement, or a | |
| /// vector of the inserted scalar type wide enough to cover the highest | |
| /// inserted index for insertvalue. | |
| FixedVectorType *getInsertBuildVectorSrcTy(const TreeEntry *E) const; | |
| /// \returns True if the VectorizableTree is both tiny and not fully | |
| /// vectorizable. We do not vectorize such trees. | |
| bool isTreeTinyAndNotFullyVectorizable(bool ForReduction = false) const; | |
| /// Checks if the graph and all its subgraphs cannot be better vectorized. | |
| /// It may happen, if all gather nodes are loads and they cannot be | |
| /// "clusterized". In this case even subgraphs cannot be vectorized more | |
| /// effectively than the base graph. | |
| bool isTreeNotExtendable() const; | |
| bool isStridedLoad(ArrayRef<Value *> PointerOps, Type *ScalarTy, | |
| Align Alignment, const int64_t Diff, | |
| const size_t Sz) const; | |
| /// Returns true for widened strided loads, where each lane of the strided | |
| /// load covers a group of consecutive scalars. Reversed widened strided | |
| /// loads must be emitted with a positive stride and a reorder shuffle. | |
| bool isWidenedStridedLoad(const TreeEntry *E) const; | |
| /// Return true if an array of scalar loads can be replaced with a strided | |
| /// load (with constant stride). | |
| /// | |
| /// It is possible that the load gets "widened". Suppose that originally each | |
| /// load loads `k` bytes and `PointerOps` can be arranged as follows (`%s` is | |
| /// constant): %b + 0 * %s + 0 %b + 0 * %s + 1 %b + 0 * %s + 2 | |
| /// ... | |
| /// %b + 0 * %s + (w - 1) | |
| /// | |
| /// %b + 1 * %s + 0 | |
| /// %b + 1 * %s + 1 | |
| /// %b + 1 * %s + 2 | |
| /// ... | |
| /// %b + 1 * %s + (w - 1) | |
| /// ... | |
| /// | |
| /// %b + (n - 1) * %s + 0 | |
| /// %b + (n - 1) * %s + 1 | |
| /// %b + (n - 1) * %s + 2 | |
| /// ... | |
| /// %b + (n - 1) * %s + (w - 1) | |
| /// | |
| /// In this case we will generate a strided load of type `<n x (k * w)>`. | |
| /// | |
| /// \param PointerOps list of pointer arguments of loads. | |
| /// \param ElemTy original scalar type of loads. | |
| /// \param Alignment alignment of the first load. | |
| /// \param SortedIndices is the order of PointerOps as returned by | |
| /// `sortPtrAccesses` | |
| /// \param Diff Pointer difference between the lowest and the highes pointer | |
| /// in `PointerOps` as returned by `getPointersDiff`. | |
| /// \param Ptr0 first pointer in `PointersOps`. | |
| /// \param PtrN last pointer in `PointersOps`. | |
| /// \param SPtrInfo If the function return `true`, it also sets all the fields | |
| /// of `SPtrInfo` necessary to generate the strided load later. | |
| bool analyzeConstantStrideCandidate( | |
| const ArrayRef<Value *> PointerOps, Type *ElemTy, Align Alignment, | |
| const SmallVectorImpl<unsigned> &SortedIndices, const int64_t Diff, | |
| Value *Ptr0, StridedPtrInfo &SPtrInfo) const; | |
| /// Return true if an array of scalar loads can be replaced with a strided | |
| /// load (with run-time stride). | |
| /// \param PointerOps list of pointer arguments of loads. | |
| /// \param ScalarTy type of loads. | |
| /// \param CommonAlignment common alignement of loads as computed by | |
| /// `computeCommonAlignment<LoadInst>`. | |
| /// \param SortedIndicies is a list of indicies computed by this function such | |
| /// that the sequence `PointerOps[SortedIndices[0]], | |
| /// PointerOps[SortedIndicies[1]], ..., PointerOps[SortedIndices[n]]` is | |
| /// ordered by the coefficient of the stride. For example, if PointerOps is | |
| /// `%base + %stride, %base, %base + 2 * stride` the `SortedIndices` will be | |
| /// `[1, 0, 2]`. We follow the convention that if `SortedIndices` has to be | |
| /// `0, 1, 2, 3, ...` we return empty vector for `SortedIndicies`. | |
| /// \param SPtrInfo If the function return `true`, it also sets all the fields | |
| /// of `SPtrInfo` necessary to generate the strided load later. | |
| /// \param IsLoad Is this a strided load (true) or strided store (false) | |
| bool analyzeRtStrideCandidate(ArrayRef<Value *> PointerOps, Type *ScalarTy, | |
| Align CommonAlignment, | |
| SmallVectorImpl<unsigned> &SortedIndices, | |
| StridedPtrInfo &SPtrInfo, bool IsLoad) const; | |
| /// Checks if the given array of loads can be represented as a vectorized, | |
| /// scatter or just simple gather. | |
| /// \param VL list of loads. | |
| /// \param VL0 main load value. | |
| /// \param Order returned order of load instructions. | |
| /// \param PointerOps returned list of pointer operands. | |
| /// \param BestVF return best vector factor, if recursive check found better | |
| /// vectorization sequences rather than masked gather. | |
| /// \param TryRecursiveCheck used to check if long masked gather can be | |
| /// represented as a serie of loads/insert subvector, if profitable. | |
| LoadsState canVectorizeLoads(ArrayRef<Value *> VL, const Value *VL0, | |
| SmallVectorImpl<unsigned> &Order, | |
| SmallVectorImpl<Value *> &PointerOps, | |
| StridedPtrInfo &SPtrInfo, | |
| unsigned *BestVF = nullptr, | |
| bool TryRecursiveCheck = true) const; | |
| /// Checks whether some existing tree entry has scalars equal to \p VL. | |
| /// \p S is the common opcode of \p VL when one exists; an empty \p S means | |
| /// the values have no common opcode (mixed buildvector/gather candidates). | |
| bool hasSameNode(const InstructionsState &S, ArrayRef<Value *> VL) const { | |
| auto IsSame = [&](const TreeEntry *TE) { return TE->isSame(VL); }; | |
| if (S) { | |
| // Any vectorized or gather entry equal to VL must contain S.getMainOp() | |
| // (the representative instruction, which is also the recorded scalar | |
| // for copyable-elements bundles), so probing the MainOp-indexed maps | |
| // is sufficient and avoids scanning the whole tree. | |
| return any_of(getTreeEntries(S.getMainOp()), IsSame) || | |
| any_of(ValueToGatherNodes.lookup(S.getMainOp()), IsSame); | |
| } | |
| // No common opcode: only gather entries can match. Each non-constant | |
| // value in VL has to be in the gather entry's scalar list and is | |
| // therefore present in ValueToGatherNodes. Probe by VL members instead | |
| // of scanning the whole tree (O(tree) -> O(|VL|)). | |
| SmallPtrSet<const TreeEntry *, 4> Visited; | |
| for (Value *V : VL) { | |
| // Constants/poisons are not tracked in ValueToGatherNodes. | |
| if (isConstant(V)) | |
| continue; | |
| for (const TreeEntry *TE : ValueToGatherNodes.lookup(V)) { | |
| if (!Visited.insert(TE).second) | |
| continue; | |
| if (IsSame(TE)) | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| /// Registers non-vectorizable sequence of loads | |
| template <typename T> void registerNonVectorizableLoads(ArrayRef<T *> VL) { | |
| ListOfKnonwnNonVectorizableLoads.insert(hash_value(VL)); | |
| } | |
| /// Checks if the given loads sequence is known as not vectorizable | |
| template <typename T> | |
| bool areKnownNonVectorizableLoads(ArrayRef<T *> VL) const { | |
| return ListOfKnonwnNonVectorizableLoads.contains(hash_value(VL)); | |
| } | |
| OptimizationRemarkEmitter *getORE() { return ORE; } | |
| /// This structure holds any data we need about the edges being traversed | |
| /// during buildTreeRec(). We keep track of: | |
| /// (i) the user TreeEntry index, and | |
| /// (ii) the index of the edge. | |
| struct EdgeInfo { | |
| EdgeInfo() = default; | |
| EdgeInfo(TreeEntry *UserTE, unsigned EdgeIdx) | |
| : UserTE(UserTE), EdgeIdx(EdgeIdx) {} | |
| /// The user TreeEntry. | |
| TreeEntry *UserTE = nullptr; | |
| /// The operand index of the use. | |
| unsigned EdgeIdx = UINT_MAX; | |
| #ifndef NDEBUG | |
| friend inline raw_ostream &operator<<(raw_ostream &OS, | |
| const BoUpSLP::EdgeInfo &EI) { | |
| EI.dump(OS); | |
| return OS; | |
| } | |
| /// Debug print. | |
| void dump(raw_ostream &OS) const { | |
| OS << "{User:" << (UserTE ? std::to_string(UserTE->Idx) : "null") | |
| << " EdgeIdx:" << EdgeIdx << "}"; | |
| } | |
| LLVM_DUMP_METHOD void dump() const { dump(dbgs()); } | |
| #endif | |
| bool operator == (const EdgeInfo &Other) const { | |
| return UserTE == Other.UserTE && EdgeIdx == Other.EdgeIdx; | |
| } | |
| operator bool() const { return UserTE != nullptr; } | |
| }; | |
| friend struct DenseMapInfo<EdgeInfo>; | |
| /// A helper class used for scoring candidates for two consecutive lanes. | |
| class LookAheadHeuristics { | |
| const TargetLibraryInfo &TLI; | |
| const DataLayout &DL; | |
| ScalarEvolution &SE; | |
| const BoUpSLP &R; | |
| int NumLanes; // Total number of lanes (aka vectorization factor). | |
| int MaxLevel; // The maximum recursion depth for accumulating score. | |
| public: | |
| LookAheadHeuristics(const TargetLibraryInfo &TLI, const DataLayout &DL, | |
| ScalarEvolution &SE, const BoUpSLP &R, int NumLanes, | |
| int MaxLevel) | |
| : TLI(TLI), DL(DL), SE(SE), R(R), NumLanes(NumLanes), | |
| MaxLevel(MaxLevel) {} | |
| // The hard-coded scores listed here are not very important, though it shall | |
| // be higher for better matches to improve the resulting cost. When | |
| // computing the scores of matching one sub-tree with another, we are | |
| // basically counting the number of values that are matching. So even if all | |
| // scores are set to 1, we would still get a decent matching result. | |
| // However, sometimes we have to break ties. For example we may have to | |
| // choose between matching loads vs matching opcodes. This is what these | |
| // scores are helping us with: they provide the order of preference. Also, | |
| // this is important if the scalar is externally used or used in another | |
| // tree entry node in the different lane. | |
| /// Loads from consecutive memory addresses, e.g. load(A[i]), load(A[i+1]). | |
| static constexpr int ScoreConsecutiveLoads = 40; | |
| /// The same load multiple times. This should have a better score than | |
| /// `ScoreSplat` because it in x86 for a 2-lane vector we can represent it | |
| /// with `movddup (%reg), xmm0` which has a throughput of 0.5 versus 0.5 for | |
| /// a vector load and 1.0 for a broadcast. | |
| static constexpr int ScoreSplatLoads = 30; | |
| /// Loads from reversed memory addresses, e.g. load(A[i+1]), load(A[i]). | |
| static constexpr int ScoreReversedLoads = 30; | |
| /// A load candidate for masked gather. | |
| static constexpr int ScoreMaskedGatherCandidate = 10; | |
| /// ExtractElementInst from same vector and consecutive indexes. | |
| static constexpr int ScoreConsecutiveExtracts = 40; | |
| /// ExtractElementInst from same vector and reversed indices. | |
| static constexpr int ScoreReversedExtracts = 30; | |
| /// Constants. | |
| static constexpr int ScoreConstants = 15; | |
| /// Same constants. | |
| static constexpr int ScoreSameConstants = 17; | |
| /// Instructions with the same opcode. | |
| static constexpr int ScoreSameOpcode = 20; | |
| /// Instructions with alt opcodes (e.g, add + sub). | |
| static constexpr int ScoreAltOpcodes = 10; | |
| /// Identical instructions (a.k.a. splat or broadcast). | |
| static constexpr int ScoreSplat = 10; | |
| /// Matching with an undef is preferable to failing. | |
| static constexpr int ScoreUndef = 10; | |
| /// Score for failing to find a decent match. | |
| static constexpr int ScoreFail = 0; | |
| /// Score if all users are vectorized. | |
| static constexpr int ScoreAllUserVectorized = 10; | |
| /// \returns the score of placing \p V1 and \p V2 in consecutive lanes. | |
| /// \p U1 and \p U2 are the users of \p V1 and \p V2. | |
| /// Also, checks if \p V1 and \p V2 are compatible with instructions in \p | |
| /// MainAltOps. | |
| int getShallowScore(Value *V1, Value *V2, Instruction *U1, Instruction *U2, | |
| ArrayRef<Value *> MainAltOps) const { | |
| if (!isValidElementType(V1->getType(), R.Opts.slp_revec) || | |
| !isValidElementType(V2->getType(), R.Opts.slp_revec)) | |
| return LookAheadHeuristics::ScoreFail; | |
| if (V1 == V2) { | |
| if (isa<LoadInst>(V1)) { | |
| // Retruns true if the users of V1 and V2 won't need to be extracted. | |
| auto AllUsersAreInternal = [U1, U2, this](Value *V1, Value *V2) { | |
| // Bail out if we have too many uses to save compilation time. | |
| if (V1->hasNUsesOrMore(UsesLimit) || V2->hasNUsesOrMore(UsesLimit)) | |
| return false; | |
| auto AllUsersVectorized = [U1, U2, this](Value *V) { | |
| return llvm::all_of(V->users(), [U1, U2, this](Value *U) { | |
| return U == U1 || U == U2 || R.isVectorized(U); | |
| }); | |
| }; | |
| return AllUsersVectorized(V1) && AllUsersVectorized(V2); | |
| }; | |
| // A broadcast of a load can be cheaper on some targets. | |
| if (R.TTI->isLegalBroadcastLoad(V1->getType(), | |
| ElementCount::getFixed(NumLanes)) && | |
| ((int)V1->getNumUses() == NumLanes || | |
| AllUsersAreInternal(V1, V2))) | |
| return LookAheadHeuristics::ScoreSplatLoads; | |
| } | |
| if (isa<UndefValue>(V1)) | |
| return LookAheadHeuristics::ScoreUndef; | |
| if (isConstant(V1)) | |
| return LookAheadHeuristics::ScoreSameConstants; | |
| return LookAheadHeuristics::ScoreSplat; | |
| } | |
| auto CheckSameEntryOrFail = [&]() { | |
| if (ArrayRef<TreeEntry *> TEs1 = R.getTreeEntries(V1); !TEs1.empty()) { | |
| SmallPtrSet<TreeEntry *, 4> Set(llvm::from_range, TEs1); | |
| if (ArrayRef<TreeEntry *> TEs2 = R.getTreeEntries(V2); | |
| !TEs2.empty() && | |
| any_of(TEs2, [&](TreeEntry *E) { return Set.contains(E); })) | |
| return LookAheadHeuristics::ScoreSplatLoads; | |
| } | |
| return LookAheadHeuristics::ScoreFail; | |
| }; | |
| auto *LI1 = dyn_cast<LoadInst>(V1); | |
| auto *LI2 = dyn_cast<LoadInst>(V2); | |
| if (LI1 && LI2) { | |
| if (LI1->getParent() != LI2->getParent() || !LI1->isSimple() || | |
| !LI2->isSimple()) | |
| return CheckSameEntryOrFail(); | |
| std::optional<int64_t> Dist = R.getPointersDiff( | |
| LI1->getType(), LI1->getPointerOperand(), LI2->getType(), | |
| LI2->getPointerOperand(), DL, SE, /*StrictCheck=*/true); | |
| if (!Dist || *Dist == 0) { | |
| if (getUnderlyingObject(LI1->getPointerOperand()) == | |
| getUnderlyingObject(LI2->getPointerOperand()) && | |
| R.TTI->isLegalMaskedGather( | |
| getWidenedType(LI1->getType(), NumLanes), LI1->getAlign())) | |
| return LookAheadHeuristics::ScoreMaskedGatherCandidate; | |
| return CheckSameEntryOrFail(); | |
| } | |
| // The distance is too large - still may be profitable to use masked | |
| // loads/gathers. | |
| if (std::abs(*Dist) > NumLanes / 2) | |
| return LookAheadHeuristics::ScoreMaskedGatherCandidate; | |
| // This still will detect consecutive loads, but we might have "holes" | |
| // in some cases. It is ok for non-power-2 vectorization and may produce | |
| // better results. It should not affect current vectorization. | |
| return (*Dist > 0) ? LookAheadHeuristics::ScoreConsecutiveLoads | |
| : LookAheadHeuristics::ScoreReversedLoads; | |
| } | |
| auto *C1 = dyn_cast<Constant>(V1); | |
| auto *C2 = dyn_cast<Constant>(V2); | |
| if (C1 && C2) | |
| return LookAheadHeuristics::ScoreConstants; | |
| // Consider constants and buildvector compatible. | |
| if ((C1 && isa<InsertElementInst>(V2)) || | |
| (C2 && isa<InsertElementInst>(V1))) | |
| return LookAheadHeuristics::ScoreSameOpcode; | |
| // Extracts from consecutive indexes of the same vector better score as | |
| // the extracts could be optimized away. | |
| Value *EV1; | |
| ConstantInt *Ex1Idx; | |
| if (match(V1, m_ExtractElt(m_Value(EV1), m_ConstantInt(Ex1Idx)))) { | |
| // Undefs are always profitable for extractelements. | |
| // Compiler can easily combine poison and extractelement <non-poison> or | |
| // undef and extractelement <poison>. But combining undef + | |
| // extractelement <non-poison-but-may-produce-poison> requires some | |
| // extra operations. | |
| if (isa<UndefValue>(V2)) | |
| return (isa<PoisonValue>(V2) || isUndefVector(EV1).all()) | |
| ? LookAheadHeuristics::ScoreConsecutiveExtracts | |
| : LookAheadHeuristics::ScoreSameOpcode; | |
| Value *EV2 = nullptr; | |
| ConstantInt *Ex2Idx = nullptr; | |
| if (match(V2, | |
| m_ExtractElt(m_Value(EV2), m_CombineOr(m_ConstantInt(Ex2Idx), | |
| m_Undef())))) { | |
| // Undefs are always profitable for extractelements. | |
| if (!Ex2Idx) | |
| return LookAheadHeuristics::ScoreConsecutiveExtracts; | |
| if (isUndefVector(EV2).all() && EV2->getType() == EV1->getType()) | |
| return LookAheadHeuristics::ScoreConsecutiveExtracts; | |
| if (EV2 == EV1) { | |
| int Idx1 = Ex1Idx->getZExtValue(); | |
| int Idx2 = Ex2Idx->getZExtValue(); | |
| int Dist = Idx2 - Idx1; | |
| // The distance is too large - still may be profitable to use | |
| // shuffles. | |
| if (std::abs(Dist) == 0) | |
| return LookAheadHeuristics::ScoreSplat; | |
| if (std::abs(Dist) > NumLanes / 2) | |
| return LookAheadHeuristics::ScoreSameOpcode; | |
| return (Dist > 0) ? LookAheadHeuristics::ScoreConsecutiveExtracts | |
| : LookAheadHeuristics::ScoreReversedExtracts; | |
| } | |
| return LookAheadHeuristics::ScoreAltOpcodes; | |
| } | |
| return CheckSameEntryOrFail(); | |
| } | |
| auto *I1 = dyn_cast<Instruction>(V1); | |
| auto *I2 = dyn_cast<Instruction>(V2); | |
| if (I1 && I2) { | |
| if (I1->getParent() != I2->getParent()) | |
| return CheckSameEntryOrFail(); | |
| Value *V; | |
| Value *Cond; | |
| // ZExt i1 to something must be considered same opcode for select i1 | |
| // cmp, x, y | |
| // Required to better match the transformation after | |
| // BoUpSLP::matchesInversedZExtSelect analysis. | |
| if ((match(I1, m_ZExt(m_Value(V))) && | |
| match(I2, m_Select(m_Value(Cond), m_Value(), m_Value())) && | |
| V->getType() == Cond->getType()) || | |
| (match(I2, m_ZExt(m_Value(V))) && | |
| match(I1, m_Select(m_Value(Cond), m_Value(), m_Value())) && | |
| V->getType() == Cond->getType())) | |
| return LookAheadHeuristics::ScoreSameOpcode; | |
| SmallVector<Value *, 4> Ops(MainAltOps); | |
| Ops.push_back(I1); | |
| Ops.push_back(I2); | |
| InstructionsState S = getSameOpcode(Ops, TLI); | |
| // Note: Only consider instructions with <= 2 operands to avoid | |
| // complexity explosion. | |
| if (S && | |
| (S.getMainOp()->getNumOperands() <= 2 || !MainAltOps.empty() || | |
| !S.isAltShuffle()) && | |
| all_of(make_isa_range<Instruction>(Ops), [&S](Instruction *I) { | |
| return I->getNumOperands() == S.getMainOp()->getNumOperands() || | |
| (S.getOpcode() == Instruction::ICmp && isa<TruncInst>(I)); | |
| })) | |
| return S.isAltShuffle() ? LookAheadHeuristics::ScoreAltOpcodes | |
| : LookAheadHeuristics::ScoreSameOpcode; | |
| } | |
| if (I1 && isa<PoisonValue>(V2)) | |
| return LookAheadHeuristics::ScoreSameOpcode; | |
| if (isa<UndefValue>(V2)) | |
| return LookAheadHeuristics::ScoreUndef; | |
| return CheckSameEntryOrFail(); | |
| } | |
| /// Go through the operands of \p LHS and \p RHS recursively until | |
| /// MaxLevel, and return the cummulative score. \p U1 and \p U2 are | |
| /// the users of \p LHS and \p RHS (that is \p LHS and \p RHS are operands | |
| /// of \p U1 and \p U2), except at the beginning of the recursion where | |
| /// these are set to nullptr. | |
| /// | |
| /// For example: | |
| /// \verbatim | |
| /// A[0] B[0] A[1] B[1] C[0] D[0] B[1] A[1] | |
| /// \ / \ / \ / \ / | |
| /// + + + + | |
| /// G1 G2 G3 G4 | |
| /// \endverbatim | |
| /// The getScoreAtLevelRec(G1, G2) function will try to match the nodes at | |
| /// each level recursively, accumulating the score. It starts from matching | |
| /// the additions at level 0, then moves on to the loads (level 1). The | |
| /// score of G1 and G2 is higher than G1 and G3, because {A[0],A[1]} and | |
| /// {B[0],B[1]} match with LookAheadHeuristics::ScoreConsecutiveLoads, while | |
| /// {A[0],C[0]} has a score of LookAheadHeuristics::ScoreFail. | |
| /// Please note that the order of the operands does not matter, as we | |
| /// evaluate the score of all profitable combinations of operands. In | |
| /// other words the score of G1 and G4 is the same as G1 and G2. This | |
| /// heuristic is based on ideas described in: | |
| /// Look-ahead SLP: Auto-vectorization in the presence of commutative | |
| /// operations, CGO 2018 by Vasileios Porpodas, Rodrigo C. O. Rocha, | |
| /// LuÃs F. W. Góes | |
| int getScoreAtLevelRec(Value *LHS, Value *RHS, Instruction *U1, | |
| Instruction *U2, int CurrLevel, | |
| ArrayRef<Value *> MainAltOps) const { | |
| // Get the shallow score of V1 and V2. | |
| int ShallowScoreAtThisLevel = | |
| getShallowScore(LHS, RHS, U1, U2, MainAltOps); | |
| // If reached MaxLevel, | |
| // or if V1 and V2 are not instructions, | |
| // or if they are SPLAT, | |
| // or if they are not consecutive, | |
| // or if profitable to vectorize loads or extractelements, early return | |
| // the current cost. | |
| auto *I1 = dyn_cast<Instruction>(LHS); | |
| auto *I2 = dyn_cast<Instruction>(RHS); | |
| if (CurrLevel == MaxLevel || !(I1 && I2) || I1 == I2 || | |
| ShallowScoreAtThisLevel == LookAheadHeuristics::ScoreFail || | |
| (((isa<LoadInst>(I1) && isa<LoadInst>(I2)) || | |
| (I1->getNumOperands() > 2 && I2->getNumOperands() > 2) || | |
| (isa<ExtractElementInst>(I1) && isa<ExtractElementInst>(I2))) && | |
| ShallowScoreAtThisLevel)) | |
| return ShallowScoreAtThisLevel; | |
| assert(I1 && I2 && "Should have early exited."); | |
| // Contains the I2 operand indexes that got matched with I1 operands. | |
| SmallSet<unsigned, 4> Op2Used; | |
| // Recursion towards the operands of I1 and I2. We are trying all possible | |
| // operand pairs, and keeping track of the best score. | |
| if (I1->getNumOperands() != I2->getNumOperands()) | |
| return LookAheadHeuristics::ScoreSameOpcode; | |
| for (unsigned OpIdx1 = 0, NumOperands1 = I1->getNumOperands(); | |
| OpIdx1 != NumOperands1; ++OpIdx1) { | |
| // Try to pair op1I with the best operand of I2. | |
| int MaxTmpScore = 0; | |
| unsigned MaxOpIdx2 = 0; | |
| bool FoundBest = false; | |
| // If I2 is commutative try all combinations. | |
| unsigned FromIdx = isCommutative(I2) ? 0 : OpIdx1; | |
| unsigned ToIdx = isCommutative(I2) | |
| ? I2->getNumOperands() | |
| : std::min(I2->getNumOperands(), OpIdx1 + 1); | |
| assert(FromIdx <= ToIdx && "Bad index"); | |
| for (unsigned OpIdx2 = FromIdx; OpIdx2 != ToIdx; ++OpIdx2) { | |
| // Skip operands already paired with OpIdx1. | |
| if (Op2Used.count(OpIdx2)) | |
| continue; | |
| // Recursively calculate the cost at each level | |
| int TmpScore = | |
| getScoreAtLevelRec(I1->getOperand(OpIdx1), I2->getOperand(OpIdx2), | |
| I1, I2, CurrLevel + 1, {}); | |
| // Look for the best score. | |
| if (TmpScore > LookAheadHeuristics::ScoreFail && | |
| TmpScore > MaxTmpScore) { | |
| MaxTmpScore = TmpScore; | |
| MaxOpIdx2 = OpIdx2; | |
| FoundBest = true; | |
| } | |
| } | |
| if (FoundBest) { | |
| // Pair {OpIdx1, MaxOpIdx2} was found to be best. Never revisit it. | |
| Op2Used.insert(MaxOpIdx2); | |
| ShallowScoreAtThisLevel += MaxTmpScore; | |
| } | |
| } | |
| return ShallowScoreAtThisLevel; | |
| } | |
| }; | |
| /// A helper data structure to hold the operands of a vector of instructions. | |
| /// This supports a fixed vector length for all operand vectors. | |
| class VLOperands { | |
| /// For each operand we need (i) the value, and (ii) the opcode that it | |
| /// would be attached to if the expression was in a left-linearized form. | |
| /// This is required to avoid illegal operand reordering. | |
| /// For example: | |
| /// \verbatim | |
| /// 0 Op1 | |
| /// |/ | |
| /// Op1 Op2 Linearized + Op2 | |
| /// \ / ----------> |/ | |
| /// - - | |
| /// | |
| /// Op1 - Op2 (0 + Op1) - Op2 | |
| /// \endverbatim | |
| /// | |
| /// Value Op1 is attached to a '+' operation, and Op2 to a '-'. | |
| /// | |
| /// Another way to think of this is to track all the operations across the | |
| /// path from the operand all the way to the root of the tree and to | |
| /// calculate the operation that corresponds to this path. For example, the | |
| /// path from Op2 to the root crosses the RHS of the '-', therefore the | |
| /// corresponding operation is a '-' (which matches the one in the | |
| /// linearized tree, as shown above). | |
| /// | |
| /// For lack of a better term, we refer to this operation as Accumulated | |
| /// Path Operation (APO). | |
| struct OperandData { | |
| OperandData() = default; | |
| OperandData(Value *V, bool APO, bool IsUsed) | |
| : V(V), APO(APO), IsUsed(IsUsed) {} | |
| /// The operand value. | |
| Value *V = nullptr; | |
| /// TreeEntries only allow a single opcode, or an alternate sequence of | |
| /// them (e.g, +, -). Therefore, we can safely use a boolean value for the | |
| /// APO. It is set to 'true' if 'V' is attached to an inverse operation | |
| /// in the left-linearized form (e.g., Sub/Div), and 'false' otherwise | |
| /// (e.g., Add/Mul) | |
| bool APO = false; | |
| /// Helper data for the reordering function. | |
| bool IsUsed = false; | |
| }; | |
| /// During operand reordering, we are trying to select the operand at lane | |
| /// that matches best with the operand at the neighboring lane. Our | |
| /// selection is based on the type of value we are looking for. For example, | |
| /// if the neighboring lane has a load, we need to look for a load that is | |
| /// accessing a consecutive address. These strategies are summarized in the | |
| /// 'ReorderingMode' enumerator. | |
| enum class ReorderingMode { | |
| Load, ///< Matching loads to consecutive memory addresses | |
| Opcode, ///< Matching instructions based on opcode (same or alternate) | |
| Constant, ///< Matching constants | |
| Splat, ///< Matching the same instruction multiple times (broadcast) | |
| Failed, ///< We failed to create a vectorizable group | |
| }; | |
| using OperandDataVec = SmallVector<OperandData, 2>; | |
| /// A vector of operand vectors. | |
| SmallVector<OperandDataVec, 4> OpsVec; | |
| /// When VL[0] is IntrinsicInst, ArgSize is CallBase::arg_size. When VL[0] | |
| /// is not IntrinsicInst, ArgSize is User::getNumOperands. | |
| unsigned ArgSize = 0; | |
| const TargetLibraryInfo &TLI; | |
| const DataLayout &DL; | |
| ScalarEvolution &SE; | |
| const BoUpSLP &R; | |
| const Loop *L = nullptr; | |
| /// \returns the operand data at \p OpIdx and \p Lane. | |
| OperandData &getData(unsigned OpIdx, unsigned Lane) { | |
| return OpsVec[OpIdx][Lane]; | |
| } | |
| /// \returns the operand data at \p OpIdx and \p Lane. Const version. | |
| const OperandData &getData(unsigned OpIdx, unsigned Lane) const { | |
| return OpsVec[OpIdx][Lane]; | |
| } | |
| /// Clears the used flag for all entries. | |
| void clearUsed() { | |
| for (unsigned OpIdx = 0, NumOperands = getNumOperands(); | |
| OpIdx != NumOperands; ++OpIdx) | |
| for (unsigned Lane = 0, NumLanes = getNumLanes(); Lane != NumLanes; | |
| ++Lane) | |
| OpsVec[OpIdx][Lane].IsUsed = false; | |
| } | |
| /// Swap the operand at \p OpIdx1 with that one at \p OpIdx2. | |
| void swap(unsigned OpIdx1, unsigned OpIdx2, unsigned Lane) { | |
| std::swap(OpsVec[OpIdx1][Lane], OpsVec[OpIdx2][Lane]); | |
| } | |
| /// \param Lane lane of the operands under analysis. | |
| /// \param OpIdx operand index in \p Lane lane we're looking the best | |
| /// candidate for. | |
| /// \param Idx operand index of the current candidate value. | |
| /// \returns The additional score due to possible broadcasting of the | |
| /// elements in the lane. It is more profitable to have power-of-2 unique | |
| /// elements in the lane, it will be vectorized with higher probability | |
| /// after removing duplicates. Currently the SLP vectorizer supports only | |
| /// vectorization of the power-of-2 number of unique scalars. | |
| int getSplatScore(unsigned Lane, unsigned OpIdx, unsigned Idx, | |
| const SmallBitVector &UsedLanes) const { | |
| Value *IdxLaneV = getData(Idx, Lane).V; | |
| if (!isa<Instruction>(IdxLaneV) || IdxLaneV == getData(OpIdx, Lane).V || | |
| isa<ExtractElementInst>(IdxLaneV)) | |
| return 0; | |
| SmallDenseMap<Value *, unsigned, 4> Uniques; | |
| for (unsigned Ln : seq<unsigned>(getNumLanes())) { | |
| if (Ln == Lane) | |
| continue; | |
| Value *OpIdxLnV = getData(OpIdx, Ln).V; | |
| if (!isa<Instruction>(OpIdxLnV)) | |
| return 0; | |
| Uniques.try_emplace(OpIdxLnV, Ln); | |
| } | |
| unsigned UniquesCount = Uniques.size(); | |
| auto IdxIt = Uniques.find(IdxLaneV); | |
| unsigned UniquesCntWithIdxLaneV = | |
| IdxIt != Uniques.end() ? UniquesCount : UniquesCount + 1; | |
| Value *OpIdxLaneV = getData(OpIdx, Lane).V; | |
| auto OpIdxIt = Uniques.find(OpIdxLaneV); | |
| unsigned UniquesCntWithOpIdxLaneV = | |
| OpIdxIt != Uniques.end() ? UniquesCount : UniquesCount + 1; | |
| if (UniquesCntWithIdxLaneV == UniquesCntWithOpIdxLaneV) | |
| return 0; | |
| return std::min(bit_ceil(UniquesCntWithOpIdxLaneV) - | |
| UniquesCntWithOpIdxLaneV, | |
| UniquesCntWithOpIdxLaneV - | |
| bit_floor(UniquesCntWithOpIdxLaneV)) - | |
| ((IdxIt != Uniques.end() && UsedLanes.test(IdxIt->second)) | |
| ? UniquesCntWithIdxLaneV - bit_floor(UniquesCntWithIdxLaneV) | |
| : bit_ceil(UniquesCntWithIdxLaneV) - UniquesCntWithIdxLaneV); | |
| } | |
| /// \param Lane lane of the operands under analysis. | |
| /// \param OpIdx operand index in \p Lane lane we're looking the best | |
| /// candidate for. | |
| /// \param Idx operand index of the current candidate value. | |
| /// \returns The additional score for the scalar which users are all | |
| /// vectorized. | |
| int getExternalUseScore(unsigned Lane, unsigned OpIdx, unsigned Idx) const { | |
| Value *IdxLaneV = getData(Idx, Lane).V; | |
| Value *OpIdxLaneV = getData(OpIdx, Lane).V; | |
| // Do not care about number of uses for vector-like instructions | |
| // (extractelement/extractvalue with constant indices), they are extracts | |
| // themselves and already externally used. Vectorization of such | |
| // instructions does not add extra extractelement instruction, just may | |
| // remove it. | |
| if (isVectorLikeInstWithConstOps(IdxLaneV) && | |
| isVectorLikeInstWithConstOps(OpIdxLaneV)) | |
| return LookAheadHeuristics::ScoreAllUserVectorized; | |
| auto *IdxLaneI = dyn_cast<Instruction>(IdxLaneV); | |
| if (!IdxLaneI || !isa<Instruction>(OpIdxLaneV)) | |
| return 0; | |
| return R.areAllUsersVectorized(IdxLaneI) | |
| ? LookAheadHeuristics::ScoreAllUserVectorized | |
| : 0; | |
| } | |
| /// Score scaling factor for fully compatible instructions but with | |
| /// different number of external uses. Allows better selection of the | |
| /// instructions with less external uses. | |
| static constexpr int ScoreScaleFactor = 10; | |
| /// Scale factor for constants only. | |
| static constexpr int ScoreConstantScaleFactor = 6; | |
| /// \Returns the look-ahead score, which tells us how much the sub-trees | |
| /// rooted at \p LHS and \p RHS match, the more they match the higher the | |
| /// score. This helps break ties in an informed way when we cannot decide on | |
| /// the order of the operands by just considering the immediate | |
| /// predecessors. | |
| int getLookAheadScore(Value *LHS, Value *RHS, ArrayRef<Value *> MainAltOps, | |
| int Lane, unsigned OpIdx, unsigned Idx, | |
| bool &IsUsed, const SmallBitVector &UsedLanes) { | |
| LookAheadHeuristics LookAhead(TLI, DL, SE, R, getNumLanes(), | |
| R.Opts.slp_max_look_ahead_depth); | |
| // Keep track of the instruction stack as we recurse into the operands | |
| // during the look-ahead score exploration. | |
| int Score = | |
| LookAhead.getScoreAtLevelRec(LHS, RHS, /*U1=*/nullptr, /*U2=*/nullptr, | |
| /*CurrLevel=*/1, MainAltOps); | |
| if (Score) { | |
| int SplatScore = | |
| getSplatScore(Lane, OpIdx, Idx, UsedLanes) * ScoreScaleFactor; | |
| if (Score <= -SplatScore) { | |
| // Failed score. | |
| Score = 0; | |
| } else { | |
| Score += SplatScore; | |
| // Scale score to see the difference between different operands | |
| // and similar operands but all vectorized/not all vectorized | |
| // uses. It does not affect actual selection of the best | |
| // compatible operand in general, just allows to select the | |
| // operand with all vectorized uses. | |
| const int SF = (LHS == RHS && isConstant(LHS)) | |
| ? ScoreConstantScaleFactor | |
| : ScoreScaleFactor; | |
| Score *= SF; | |
| Score += getExternalUseScore(Lane, OpIdx, Idx); | |
| IsUsed = true; | |
| } | |
| } | |
| return Score; | |
| } | |
| /// Best defined scores per lanes between the passes. Used to choose the | |
| /// best operand (with the highest score) between the passes. | |
| /// The key - {Operand Index, Lane}. | |
| /// The value - the best score between the passes for the lane and the | |
| /// operand. | |
| SmallDenseMap<std::pair<unsigned, unsigned>, unsigned, 8> | |
| BestScoresPerLanes; | |
| // Search all operands in Ops[*][Lane] for the one that matches best | |
| // Ops[OpIdx][LastLane] and return its opreand index. | |
| // If no good match can be found, return std::nullopt. | |
| std::optional<unsigned> | |
| getBestOperand(unsigned OpIdx, int Lane, int LastLane, | |
| ArrayRef<ReorderingMode> ReorderingModes, | |
| ArrayRef<Value *> MainAltOps, | |
| const SmallBitVector &UsedLanes) { | |
| unsigned NumOperands = getNumOperands(); | |
| // The operand of the previous lane at OpIdx. | |
| Value *OpLastLane = getData(OpIdx, LastLane).V; | |
| // Our strategy mode for OpIdx. | |
| ReorderingMode RMode = ReorderingModes[OpIdx]; | |
| if (RMode == ReorderingMode::Failed) | |
| return std::nullopt; | |
| // The linearized opcode of the operand at OpIdx, Lane. | |
| bool OpIdxAPO = getData(OpIdx, Lane).APO; | |
| // The best operand index and its score. | |
| // Sometimes we have more than one option (e.g., Opcode and Undefs), so we | |
| // are using the score to differentiate between the two. | |
| struct BestOpData { | |
| std::optional<unsigned> Idx; | |
| unsigned Score = 0; | |
| } BestOp; | |
| BestOp.Score = | |
| BestScoresPerLanes.try_emplace(std::make_pair(OpIdx, Lane), 0) | |
| .first->second; | |
| // Track if the operand must be marked as used. If the operand is set to | |
| // Score 1 explicitly (because of non power-of-2 unique scalars, we may | |
| // want to reestimate the operands again on the following iterations). | |
| bool IsUsed = RMode == ReorderingMode::Splat || | |
| RMode == ReorderingMode::Constant || | |
| RMode == ReorderingMode::Load; | |
| // Iterate through all unused operands and look for the best. | |
| for (unsigned Idx = 0; Idx != NumOperands; ++Idx) { | |
| // Get the operand at Idx and Lane. | |
| OperandData &OpData = getData(Idx, Lane); | |
| Value *Op = OpData.V; | |
| bool OpAPO = OpData.APO; | |
| // Skip already selected operands. | |
| if (OpData.IsUsed) | |
| continue; | |
| // Skip if we are trying to move the operand to a position with a | |
| // different opcode in the linearized tree form. This would break the | |
| // semantics. | |
| if (OpAPO != OpIdxAPO) | |
| continue; | |
| // Look for an operand that matches the current mode. | |
| switch (RMode) { | |
| case ReorderingMode::Load: | |
| case ReorderingMode::Opcode: { | |
| bool LeftToRight = Lane > LastLane; | |
| Value *OpLeft = (LeftToRight) ? OpLastLane : Op; | |
| Value *OpRight = (LeftToRight) ? Op : OpLastLane; | |
| int Score = getLookAheadScore(OpLeft, OpRight, MainAltOps, Lane, | |
| OpIdx, Idx, IsUsed, UsedLanes); | |
| if (Score > static_cast<int>(BestOp.Score) || | |
| (Score > 0 && Score == static_cast<int>(BestOp.Score) && | |
| Idx == OpIdx)) { | |
| BestOp.Idx = Idx; | |
| BestOp.Score = Score; | |
| BestScoresPerLanes[std::make_pair(OpIdx, Lane)] = Score; | |
| } | |
| break; | |
| } | |
| case ReorderingMode::Constant: | |
| if (isa<Constant>(Op) || | |
| (!BestOp.Score && L && L->isLoopInvariant(Op))) { | |
| BestOp.Idx = Idx; | |
| if (isa<Constant>(Op)) { | |
| BestOp.Score = LookAheadHeuristics::ScoreConstants; | |
| BestScoresPerLanes[std::make_pair(OpIdx, Lane)] = | |
| LookAheadHeuristics::ScoreConstants; | |
| } | |
| if (isa<UndefValue>(Op) || !isa<Constant>(Op)) | |
| IsUsed = false; | |
| } | |
| break; | |
| case ReorderingMode::Splat: | |
| if (Op == OpLastLane || (!BestOp.Score && isa<Constant>(Op))) { | |
| IsUsed = Op == OpLastLane; | |
| if (Op == OpLastLane) { | |
| BestOp.Score = LookAheadHeuristics::ScoreSplat; | |
| BestScoresPerLanes[std::make_pair(OpIdx, Lane)] = | |
| LookAheadHeuristics::ScoreSplat; | |
| } | |
| BestOp.Idx = Idx; | |
| } | |
| break; | |
| case ReorderingMode::Failed: | |
| llvm_unreachable("Not expected Failed reordering mode."); | |
| } | |
| } | |
| if (BestOp.Idx) { | |
| getData(*BestOp.Idx, Lane).IsUsed = IsUsed; | |
| return BestOp.Idx; | |
| } | |
| // If we could not find a good match return std::nullopt. | |
| return std::nullopt; | |
| } | |
| /// Helper for reorderOperandVecs. | |
| /// \returns the lane that we should start reordering from. This is the one | |
| /// which has the least number of operands that can freely move about or | |
| /// less profitable because it already has the most optimal set of operands. | |
| unsigned getBestLaneToStartReordering() const { | |
| unsigned Min = UINT_MAX; | |
| unsigned SameOpNumber = 0; | |
| // std::pair<unsigned, unsigned> is used to implement a simple voting | |
| // algorithm and choose the lane with the least number of operands that | |
| // can freely move about or less profitable because it already has the | |
| // most optimal set of operands. The first unsigned is a counter for | |
| // voting, the second unsigned is the counter of lanes with instructions | |
| // with same/alternate opcodes and same parent basic block. | |
| MapVector<unsigned, std::pair<unsigned, unsigned>> HashMap; | |
| // Try to be closer to the original results, if we have multiple lanes | |
| // with same cost. If 2 lanes have the same cost, use the one with the | |
| // highest index. | |
| for (int I = getNumLanes(); I > 0; --I) { | |
| unsigned Lane = I - 1; | |
| OperandsOrderData NumFreeOpsHash = | |
| getMaxNumOperandsThatCanBeReordered(Lane); | |
| // Compare the number of operands that can move and choose the one with | |
| // the least number. | |
| if (NumFreeOpsHash.NumOfAPOs < Min) { | |
| Min = NumFreeOpsHash.NumOfAPOs; | |
| SameOpNumber = NumFreeOpsHash.NumOpsWithSameOpcodeParent; | |
| HashMap.clear(); | |
| HashMap[NumFreeOpsHash.Hash] = std::make_pair(1, Lane); | |
| } else if (NumFreeOpsHash.NumOfAPOs == Min && | |
| NumFreeOpsHash.NumOpsWithSameOpcodeParent < SameOpNumber) { | |
| // Select the most optimal lane in terms of number of operands that | |
| // should be moved around. | |
| SameOpNumber = NumFreeOpsHash.NumOpsWithSameOpcodeParent; | |
| HashMap[NumFreeOpsHash.Hash] = std::make_pair(1, Lane); | |
| } else if (NumFreeOpsHash.NumOfAPOs == Min && | |
| NumFreeOpsHash.NumOpsWithSameOpcodeParent == SameOpNumber) { | |
| auto [It, Inserted] = | |
| HashMap.try_emplace(NumFreeOpsHash.Hash, 1, Lane); | |
| if (!Inserted) | |
| ++It->second.first; | |
| } | |
| } | |
| // Select the lane with the minimum counter. | |
| unsigned BestLane = 0; | |
| unsigned CntMin = UINT_MAX; | |
| for (const auto &Data : reverse(HashMap)) { | |
| if (Data.second.first < CntMin) { | |
| CntMin = Data.second.first; | |
| BestLane = Data.second.second; | |
| } | |
| } | |
| return BestLane; | |
| } | |
| /// Data structure that helps to reorder operands. | |
| struct OperandsOrderData { | |
| /// The best number of operands with the same APOs, which can be | |
| /// reordered. | |
| unsigned NumOfAPOs = UINT_MAX; | |
| /// Number of operands with the same/alternate instruction opcode and | |
| /// parent. | |
| unsigned NumOpsWithSameOpcodeParent = 0; | |
| /// Hash for the actual operands ordering. | |
| /// Used to count operands, actually their position id and opcode | |
| /// value. It is used in the voting mechanism to find the lane with the | |
| /// least number of operands that can freely move about or less profitable | |
| /// because it already has the most optimal set of operands. Can be | |
| /// replaced with SmallVector<unsigned> instead but hash code is faster | |
| /// and requires less memory. | |
| unsigned Hash = 0; | |
| }; | |
| /// \returns the maximum number of operands that are allowed to be reordered | |
| /// for \p Lane and the number of compatible instructions(with the same | |
| /// parent/opcode). This is used as a heuristic for selecting the first lane | |
| /// to start operand reordering. | |
| OperandsOrderData getMaxNumOperandsThatCanBeReordered(unsigned Lane) const { | |
| unsigned CntTrue = 0; | |
| unsigned NumOperands = getNumOperands(); | |
| // Operands with the same APO can be reordered. We therefore need to count | |
| // how many of them we have for each APO, like this: Cnt[APO] = x. | |
| // Since we only have two APOs, namely true and false, we can avoid using | |
| // a map. Instead we can simply count the number of operands that | |
| // correspond to one of them (in this case the 'true' APO), and calculate | |
| // the other by subtracting it from the total number of operands. | |
| // Operands with the same instruction opcode and parent are more | |
| // profitable since we don't need to move them in many cases, with a high | |
| // probability such lane already can be vectorized effectively. | |
| bool AllUndefs = true; | |
| unsigned NumOpsWithSameOpcodeParent = 0; | |
| Instruction *OpcodeI = nullptr; | |
| BasicBlock *Parent = nullptr; | |
| unsigned Hash = 0; | |
| for (unsigned OpIdx = 0; OpIdx != NumOperands; ++OpIdx) { | |
| const OperandData &OpData = getData(OpIdx, Lane); | |
| if (OpData.APO) | |
| ++CntTrue; | |
| // Use Boyer-Moore majority voting for finding the majority opcode and | |
| // the number of times it occurs. | |
| if (auto *I = dyn_cast<Instruction>(OpData.V)) { | |
| if (!OpcodeI || !getSameOpcode({OpcodeI, I}, TLI) || | |
| I->getParent() != Parent) { | |
| if (NumOpsWithSameOpcodeParent == 0) { | |
| NumOpsWithSameOpcodeParent = 1; | |
| OpcodeI = I; | |
| Parent = I->getParent(); | |
| } else { | |
| --NumOpsWithSameOpcodeParent; | |
| } | |
| } else { | |
| ++NumOpsWithSameOpcodeParent; | |
| } | |
| } | |
| Hash = hash_combine( | |
| Hash, hash_value((OpIdx + 1) * (OpData.V->getValueID() + 1))); | |
| AllUndefs = AllUndefs && isa<UndefValue>(OpData.V); | |
| } | |
| if (AllUndefs) | |
| return {}; | |
| OperandsOrderData Data; | |
| Data.NumOfAPOs = std::max(CntTrue, NumOperands - CntTrue); | |
| Data.NumOpsWithSameOpcodeParent = NumOpsWithSameOpcodeParent; | |
| Data.Hash = Hash; | |
| return Data; | |
| } | |
| /// Go through the instructions in VL and append their operands. | |
| void appendOperands(ArrayRef<Value *> VL, ArrayRef<ValueList> Operands, | |
| const InstructionsState &S) { | |
| assert(!Operands.empty() && !VL.empty() && "Bad list of operands"); | |
| assert((empty() || all_of(Operands, | |
| [this](const ValueList &VL) { | |
| return VL.size() == getNumLanes(); | |
| })) && | |
| "Expected same number of lanes"); | |
| assert(S.valid() && "InstructionsState is invalid."); | |
| // IntrinsicInst::isCommutative returns true if swapping the first "two" | |
| // arguments to the intrinsic produces the same result. | |
| Instruction *MainOp = S.getMainOp(); | |
| ArgSize = getNumberOfPotentiallyCommutativeOps(MainOp); | |
| OpsVec.resize(ArgSize); | |
| unsigned NumLanes = VL.size(); | |
| for (OperandDataVec &Ops : OpsVec) | |
| Ops.resize(NumLanes); | |
| for (unsigned Lane : seq<unsigned>(NumLanes)) { | |
| // Our tree has just 3 nodes: the root and two operands. | |
| // It is therefore trivial to get the APO. We only need to check the | |
| // opcode of V and whether the operand at OpIdx is the LHS or RHS | |
| // operand. The LHS operand of both add and sub is never attached to an | |
| // inversese operation in the linearized form, therefore its APO is | |
| // false. The RHS is true only if V is an inverse operation. | |
| // Since operand reordering is performed on groups of commutative | |
| // operations or alternating sequences (e.g., +, -), we can safely tell | |
| // the inverse operations by checking commutativity. | |
| auto *I = dyn_cast<Instruction>(VL[Lane]); | |
| if (!I && isa<PoisonValue>(VL[Lane])) { | |
| for (unsigned OpIdx : seq<unsigned>(ArgSize)) | |
| OpsVec[OpIdx][Lane] = {Operands[OpIdx][Lane], true, false}; | |
| continue; | |
| } | |
| bool IsInverseOperation = false; | |
| if (S.isCopyableElement(VL[Lane])) { | |
| // The value is a copyable element. | |
| IsInverseOperation = | |
| !isCommutative(MainOp, VL[Lane], /*IsCopyable=*/true); | |
| } else { | |
| assert(I && "Expected instruction"); | |
| Instruction *SelectedOp = convertTo(I, S).first; | |
| // We cannot check commutativity by the converted instruction | |
| // (SelectedOp) because isCommutative also examines def-use | |
| // relationships. | |
| IsInverseOperation = !isCommutative(SelectedOp, I); | |
| } | |
| for (unsigned OpIdx : seq<unsigned>(ArgSize)) { | |
| bool APO = (OpIdx == 0) ? false : IsInverseOperation; | |
| OpsVec[OpIdx][Lane] = {Operands[OpIdx][Lane], APO, false}; | |
| } | |
| } | |
| } | |
| /// \returns the number of operands. | |
| unsigned getNumOperands() const { return ArgSize; } | |
| /// \returns the number of lanes. | |
| unsigned getNumLanes() const { return OpsVec[0].size(); } | |
| /// \returns the operand value at \p OpIdx and \p Lane. | |
| Value *getValue(unsigned OpIdx, unsigned Lane) const { | |
| return getData(OpIdx, Lane).V; | |
| } | |
| /// \returns true if the data structure is empty. | |
| bool empty() const { return OpsVec.empty(); } | |
| /// Clears the data. | |
| void clear() { OpsVec.clear(); } | |
| /// \Returns true if there are enough operands identical to \p Op to fill | |
| /// the whole vector (it is mixed with constants or loop invariant values). | |
| /// Note: This modifies the 'IsUsed' flag, so a cleanUsed() must follow. | |
| bool shouldBroadcast(Value *Op, unsigned OpIdx, unsigned Lane) { | |
| assert(Op == getValue(OpIdx, Lane) && | |
| "Op is expected to be getValue(OpIdx, Lane)."); | |
| // Small number of loads - try load matching. | |
| if (isa<LoadInst>(Op) && getNumLanes() == 2 && getNumOperands() == 2) | |
| return false; | |
| bool OpAPO = getData(OpIdx, Lane).APO; | |
| bool IsInvariant = L && L->isLoopInvariant(Op); | |
| unsigned Cnt = 0; | |
| for (unsigned Ln = 0, Lns = getNumLanes(); Ln != Lns; ++Ln) { | |
| if (Ln == Lane) | |
| continue; | |
| // This is set to true if we found a candidate for broadcast at Lane. | |
| bool FoundCandidate = false; | |
| for (unsigned OpI = 0, OpE = getNumOperands(); OpI != OpE; ++OpI) { | |
| OperandData &Data = getData(OpI, Ln); | |
| if (Data.APO != OpAPO || Data.IsUsed) | |
| continue; | |
| Value *OpILane = getValue(OpI, Lane); | |
| bool IsConstantOp = isa<Constant>(OpILane); | |
| // Consider the broadcast candidate if: | |
| // 1. Same value is found in one of the operands. | |
| if (Data.V == Op || | |
| // 2. The operand in the given lane is not constant but there is a | |
| // constant operand in another lane (which can be moved to the | |
| // given lane). In this case we can represent it as a simple | |
| // permutation of constant and broadcast. | |
| (!IsConstantOp && | |
| ((Lns > 2 && isa<Constant>(Data.V)) || | |
| // 2.1. If we have only 2 lanes, need to check that value in the | |
| // next lane does not build same opcode sequence. | |
| (Lns == 2 && | |
| !getSameOpcode({Op, getValue((OpI + 1) % OpE, Ln)}, TLI) && | |
| isa<Constant>(Data.V)))) || | |
| // 3. The operand in the current lane is loop invariant (can be | |
| // hoisted out) and another operand is also a loop invariant | |
| // (though not a constant). In this case the whole vector can be | |
| // hoisted out. | |
| // FIXME: need to teach the cost model about this case for better | |
| // estimation. | |
| (IsInvariant && !isa<Constant>(Data.V) && | |
| !getSameOpcode({Op, Data.V}, TLI) && | |
| L->isLoopInvariant(Data.V))) { | |
| FoundCandidate = true; | |
| Data.IsUsed = Data.V == Op; | |
| if (Data.V == Op) | |
| ++Cnt; | |
| break; | |
| } | |
| } | |
| if (!FoundCandidate) | |
| return false; | |
| } | |
| return getNumLanes() == 2 || Cnt > 1; | |
| } | |
| /// Checks if there is at least single compatible operand in lanes other | |
| /// than \p Lane, compatible with the operand \p Op. | |
| bool canBeVectorized(Instruction *Op, unsigned OpIdx, unsigned Lane) const { | |
| assert(Op == getValue(OpIdx, Lane) && | |
| "Op is expected to be getValue(OpIdx, Lane)."); | |
| bool OpAPO = getData(OpIdx, Lane).APO; | |
| for (unsigned Ln = 0, Lns = getNumLanes(); Ln != Lns; ++Ln) { | |
| if (Ln == Lane) | |
| continue; | |
| if (any_of(seq<unsigned>(getNumOperands()), [&](unsigned OpI) { | |
| const OperandData &Data = getData(OpI, Ln); | |
| if (Data.APO != OpAPO || Data.IsUsed) | |
| return true; | |
| Value *OpILn = getValue(OpI, Ln); | |
| return (L && L->isLoopInvariant(OpILn)) || | |
| (getSameOpcode({Op, OpILn}, TLI) && | |
| allSameBlock({Op, OpILn})); | |
| })) | |
| return true; | |
| } | |
| return false; | |
| } | |
| public: | |
| /// Initialize with all the operands of the instruction vector \p RootVL. | |
| VLOperands(ArrayRef<Value *> RootVL, ArrayRef<ValueList> Operands, | |
| const InstructionsState &S, const BoUpSLP &R) | |
| : TLI(*R.TLI), DL(*R.DL), SE(*R.SE), R(R), | |
| L(R.LI->getLoopFor(S.getMainOp()->getParent())) { | |
| // Append all the operands of RootVL. | |
| appendOperands(RootVL, Operands, S); | |
| } | |
| /// Initialize with flattened operand columns of an associative node. | |
| /// ArgSize is taken from \p Operands, APO is always false. | |
| VLOperands(ArrayRef<ValueList> Operands, const BasicBlock *BB, | |
| const BoUpSLP &R) | |
| : TLI(*R.TLI), DL(*R.DL), SE(*R.SE), R(R), L(R.LI->getLoopFor(BB)) { | |
| assert(!Operands.empty() && "Expected at least one operand column"); | |
| ArgSize = Operands.size(); | |
| OpsVec.resize(ArgSize); | |
| unsigned NumLanes = Operands.front().size(); | |
| for (auto [OpIdx, Ops] : enumerate(OpsVec)) { | |
| Ops.resize(NumLanes); | |
| for (unsigned Lane : seq<unsigned>(NumLanes)) | |
| Ops[Lane] = OperandData(Operands[OpIdx][Lane], /*APO=*/false, | |
| /*IsUsed=*/false); | |
| } | |
| } | |
| /// \Returns a value vector with the operands across all lanes for the | |
| /// opearnd at \p OpIdx. | |
| ValueList getVL(unsigned OpIdx) const { | |
| ValueList OpVL(OpsVec[OpIdx].size()); | |
| assert(OpsVec[OpIdx].size() == getNumLanes() && | |
| "Expected same num of lanes across all operands"); | |
| for (unsigned Lane = 0, Lanes = getNumLanes(); Lane != Lanes; ++Lane) | |
| OpVL[Lane] = OpsVec[OpIdx][Lane].V; | |
| return OpVL; | |
| } | |
| // Performs operand reordering for 2 or more operands. | |
| // The original operands are in OrigOps[OpIdx][Lane]. | |
| // The reordered operands are returned in 'SortedOps[OpIdx][Lane]'. | |
| void reorder() { | |
| unsigned NumOperands = getNumOperands(); | |
| unsigned NumLanes = getNumLanes(); | |
| // Each operand has its own mode. We are using this mode to help us select | |
| // the instructions for each lane, so that they match best with the ones | |
| // we have selected so far. | |
| SmallVector<ReorderingMode, 2> ReorderingModes(NumOperands); | |
| // This is a greedy single-pass algorithm. We are going over each lane | |
| // once and deciding on the best order right away with no back-tracking. | |
| // However, in order to increase its effectiveness, we start with the lane | |
| // that has operands that can move the least. For example, given the | |
| // following lanes: | |
| // Lane 0 : A[0] = B[0] + C[0] // Visited 3rd | |
| // Lane 1 : A[1] = C[1] - B[1] // Visited 1st | |
| // Lane 2 : A[2] = B[2] + C[2] // Visited 2nd | |
| // Lane 3 : A[3] = C[3] - B[3] // Visited 4th | |
| // we will start at Lane 1, since the operands of the subtraction cannot | |
| // be reordered. Then we will visit the rest of the lanes in a circular | |
| // fashion. That is, Lanes 2, then Lane 0, and finally Lane 3. | |
| // Find the first lane that we will start our search from. | |
| unsigned FirstLane = getBestLaneToStartReordering(); | |
| // Initialize the modes. | |
| for (unsigned OpIdx = 0; OpIdx != NumOperands; ++OpIdx) { | |
| Value *OpLane0 = getValue(OpIdx, FirstLane); | |
| // Keep track if we have instructions with all the same opcode on one | |
| // side. | |
| if (auto *OpILane0 = dyn_cast<Instruction>(OpLane0)) { | |
| // Check if OpLane0 should be broadcast. | |
| if (shouldBroadcast(OpLane0, OpIdx, FirstLane) || | |
| !canBeVectorized(OpILane0, OpIdx, FirstLane)) | |
| ReorderingModes[OpIdx] = ReorderingMode::Splat; | |
| else if (isa<LoadInst>(OpILane0)) | |
| ReorderingModes[OpIdx] = ReorderingMode::Load; | |
| else | |
| ReorderingModes[OpIdx] = ReorderingMode::Opcode; | |
| } else if (isa<Constant>(OpLane0)) { | |
| ReorderingModes[OpIdx] = ReorderingMode::Constant; | |
| } else if (isa<Argument>(OpLane0)) { | |
| // Our best hope is a Splat. It may save some cost in some cases. | |
| ReorderingModes[OpIdx] = ReorderingMode::Splat; | |
| } else { | |
| llvm_unreachable("Unexpected value kind."); | |
| } | |
| } | |
| // Check that we don't have same operands. No need to reorder if operands | |
| // are just perfect diamond or shuffled diamond match. Do not do it only | |
| // for possible broadcasts. | |
| auto &&SkipReordering = [this]() { | |
| SmallPtrSet<Value *, 4> UniqueValues; | |
| ArrayRef<OperandData> Op0 = OpsVec.front(); | |
| for (const OperandData &Data : Op0) | |
| UniqueValues.insert(Data.V); | |
| for (ArrayRef<OperandData> Op : | |
| ArrayRef(OpsVec).slice(1, getNumOperands() - 1)) { | |
| if (any_of(Op, [&UniqueValues](const OperandData &Data) { | |
| return !UniqueValues.contains(Data.V); | |
| })) | |
| return false; | |
| } | |
| return UniqueValues.size() != 2; | |
| }; | |
| // If the initial strategy fails for any of the operand indexes, then we | |
| // perform reordering again in a second pass. This helps avoid assigning | |
| // high priority to the failed strategy, and should improve reordering for | |
| // the non-failed operand indexes. | |
| for (int Pass = 0; Pass != 2; ++Pass) { | |
| // Check if no need to reorder operands since they're are perfect or | |
| // shuffled diamond match. | |
| // Need to do it to avoid extra external use cost counting for | |
| // shuffled matches, which may cause regressions. | |
| if (SkipReordering()) | |
| break; | |
| // Skip the second pass if the first pass did not fail. | |
| bool StrategyFailed = false; | |
| // Mark all operand data as free to use. | |
| clearUsed(); | |
| // We keep the original operand order for the FirstLane, so reorder the | |
| // rest of the lanes. We are visiting the nodes in a circular fashion, | |
| // using FirstLane as the center point and increasing the radius | |
| // distance. | |
| SmallVector<SmallVector<Value *, 2>> MainAltOps(NumOperands); | |
| for (unsigned I = 0; I < NumOperands; ++I) | |
| MainAltOps[I].push_back(getData(I, FirstLane).V); | |
| SmallBitVector UsedLanes(NumLanes); | |
| UsedLanes.set(FirstLane); | |
| for (unsigned Distance = 1; Distance != NumLanes; ++Distance) { | |
| // Visit the lane on the right and then the lane on the left. | |
| for (int Direction : {+1, -1}) { | |
| int Lane = FirstLane + Direction * Distance; | |
| if (Lane < 0 || Lane >= (int)NumLanes) | |
| continue; | |
| UsedLanes.set(Lane); | |
| int LastLane = Lane - Direction; | |
| assert(LastLane >= 0 && LastLane < (int)NumLanes && | |
| "Out of bounds"); | |
| // Look for a good match for each operand. | |
| for (unsigned OpIdx = 0; OpIdx != NumOperands; ++OpIdx) { | |
| // Search for the operand that matches SortedOps[OpIdx][Lane-1]. | |
| std::optional<unsigned> BestIdx = | |
| getBestOperand(OpIdx, Lane, LastLane, ReorderingModes, | |
| MainAltOps[OpIdx], UsedLanes); | |
| // By not selecting a value, we allow the operands that follow to | |
| // select a better matching value. We will get a non-null value in | |
| // the next run of getBestOperand(). | |
| if (BestIdx) { | |
| // Swap the current operand with the one returned by | |
| // getBestOperand(). | |
| swap(OpIdx, *BestIdx, Lane); | |
| } else { | |
| // Enable the second pass. | |
| StrategyFailed = true; | |
| } | |
| // Try to get the alternate opcode and follow it during analysis. | |
| if (MainAltOps[OpIdx].size() != 2) { | |
| OperandData &AltOp = getData(OpIdx, Lane); | |
| InstructionsState OpS = | |
| getSameOpcode({MainAltOps[OpIdx].front(), AltOp.V}, TLI); | |
| if (OpS && OpS.isAltShuffle()) | |
| MainAltOps[OpIdx].push_back(AltOp.V); | |
| } | |
| } | |
| } | |
| } | |
| // Skip second pass if the strategy did not fail. | |
| if (!StrategyFailed) | |
| break; | |
| } | |
| } | |
| #if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP) | |
| LLVM_DUMP_METHOD static StringRef getModeStr(ReorderingMode RMode) { | |
| switch (RMode) { | |
| case ReorderingMode::Load: | |
| return "Load"; | |
| case ReorderingMode::Opcode: | |
| return "Opcode"; | |
| case ReorderingMode::Constant: | |
| return "Constant"; | |
| case ReorderingMode::Splat: | |
| return "Splat"; | |
| case ReorderingMode::Failed: | |
| return "Failed"; | |
| } | |
| llvm_unreachable("Unimplemented Reordering Type"); | |
| } | |
| LLVM_DUMP_METHOD static raw_ostream &printMode(ReorderingMode RMode, | |
| raw_ostream &OS) { | |
| return OS << getModeStr(RMode); | |
| } | |
| /// Debug print. | |
| LLVM_DUMP_METHOD static void dumpMode(ReorderingMode RMode) { | |
| printMode(RMode, dbgs()); | |
| } | |
| friend raw_ostream &operator<<(raw_ostream &OS, ReorderingMode RMode) { | |
| return printMode(RMode, OS); | |
| } | |
| LLVM_DUMP_METHOD raw_ostream &print(raw_ostream &OS) const { | |
| const unsigned Indent = 2; | |
| unsigned Cnt = 0; | |
| for (const OperandDataVec &OpDataVec : OpsVec) { | |
| OS << "Operand " << Cnt++ << "\n"; | |
| for (const OperandData &OpData : OpDataVec) { | |
| OS.indent(Indent) << "{"; | |
| if (Value *V = OpData.V) | |
| OS << *V; | |
| else | |
| OS << "null"; | |
| OS << ", APO:" << OpData.APO << "}\n"; | |
| } | |
| OS << "\n"; | |
| } | |
| return OS; | |
| } | |
| /// Debug print. | |
| LLVM_DUMP_METHOD void dump() const { print(dbgs()); } | |
| #endif | |
| }; | |
| /// Evaluate each pair in \p Candidates and return index into \p Candidates | |
| /// for a pair which have highest score deemed to have best chance to form | |
| /// root of profitable tree to vectorize. Return std::nullopt if no candidate | |
| /// scored above the LookAheadHeuristics::ScoreFail. \param Limit Lower limit | |
| /// of the cost, considered to be good enough score. | |
| std::pair<std::optional<int>, int> | |
| findBestRootPair(ArrayRef<std::pair<Value *, Value *>> Candidates, | |
| int Limit = LookAheadHeuristics::ScoreFail) const { | |
| LookAheadHeuristics LookAhead(*TLI, *DL, *SE, *this, /*NumLanes=*/2, | |
| Opts.slp_max_root_look_ahead_depth); | |
| int BestScore = Limit; | |
| std::optional<int> Index; | |
| for (int I : seq<int>(0, Candidates.size())) { | |
| int Score = LookAhead.getScoreAtLevelRec(Candidates[I].first, | |
| Candidates[I].second, | |
| /*U1=*/nullptr, /*U2=*/nullptr, | |
| /*CurrLevel=*/1, {}); | |
| if (Score > BestScore) { | |
| BestScore = Score; | |
| Index = I; | |
| } | |
| } | |
| return std::make_pair(Index, BestScore); | |
| } | |
| /// Checks if the instruction is marked for deletion. | |
| bool isDeleted(Instruction *I) const { return DeletedInstructions.count(I); } | |
| /// Cached llvm::getPointersDiff(), see SLPAnalysisCache. | |
| std::optional<int64_t> | |
| getPointersDiff(Type *ElemTyA, Value *PtrA, Type *ElemTyB, Value *PtrB, | |
| const DataLayout &DL, ScalarEvolution &SE, | |
| bool StrictCheck = false, bool CheckType = true) const { | |
| return Cache.getPointersDiff(ElemTyA, PtrA, ElemTyB, PtrB, DL, SE, | |
| StrictCheck, CheckType); | |
| } | |
| /// Returns the cached analyses results, shared by all the trees built for | |
| /// the function. | |
| SLPAnalysisCache &getAnalysisCache() const { return Cache; } | |
| /// Checks if the value is used only by the assume-like intrinsics. | |
| bool isEphemeralValue(const Value *V) const { return EphValues.contains(V); } | |
| /// Removes an instruction from its block and eventually deletes it. | |
| /// It's like Instruction::eraseFromParent() except that the actual deletion | |
| /// is delayed until BoUpSLP is destructed. | |
| void eraseInstruction(Instruction *I) { | |
| DeletedInstructions.insert(I); | |
| } | |
| /// Remove instructions from the parent function and clear the operands of \p | |
| /// DeadVals instructions, marking for deletion trivially dead operands. | |
| template <typename T> | |
| void removeInstructionsAndOperands( | |
| ArrayRef<T *> DeadVals, | |
| ArrayRef<ReductionVectorPart> VectorValuesAndScales) { | |
| SmallVector<WeakTrackingVH> DeadInsts; | |
| for (T *V : DeadVals) { | |
| auto *I = cast<Instruction>(V); | |
| eraseInstruction(I); | |
| } | |
| DenseSet<Value *> Processed; | |
| for (T *V : DeadVals) { | |
| if (!V || !Processed.insert(V).second) | |
| continue; | |
| auto *I = cast<Instruction>(V); | |
| salvageDebugInfo(*I); | |
| ArrayRef<TreeEntry *> Entries = getTreeEntries(I); | |
| for (Use &U : I->operands()) { | |
| if (auto *OpI = dyn_cast<Instruction>(U.get()); | |
| OpI && !DeletedInstructions.contains(OpI) && OpI->hasOneUser() && | |
| wouldInstructionBeTriviallyDead(OpI, TLI) && | |
| !ExternalUseReplacements.contains(OpI) && | |
| (Entries.empty() || none_of(Entries, [&](const TreeEntry *Entry) { | |
| return Entry->VectorizedValue == OpI; | |
| }))) | |
| DeadInsts.push_back(OpI); | |
| } | |
| I->dropAllReferences(); | |
| } | |
| for (T *V : DeadVals) { | |
| auto *I = cast<Instruction>(V); | |
| if (!I->getParent()) | |
| continue; | |
| assert((I->use_empty() || all_of(I->uses(), | |
| [&](Use &U) { | |
| return isDeleted( | |
| cast<Instruction>(U.getUser())); | |
| })) && | |
| "trying to erase instruction with users."); | |
| I->removeFromParent(); | |
| SE->forgetValue(I); | |
| } | |
| // Process the dead instruction list until empty. | |
| while (!DeadInsts.empty()) { | |
| Value *V = DeadInsts.pop_back_val(); | |
| Instruction *VI = cast<Instruction>(V); | |
| if (!VI || !VI->getParent()) | |
| continue; | |
| assert(isInstructionTriviallyDead(VI, TLI) && | |
| "Live instruction found in dead worklist!"); | |
| assert(VI->use_empty() && "Instructions with uses are not dead."); | |
| // Don't lose the debug info while deleting the instructions. | |
| salvageDebugInfo(*VI); | |
| // Null out all of the instruction's operands to see if any operand | |
| // becomes dead as we go. | |
| for (Use &OpU : VI->operands()) { | |
| Value *OpV = OpU.get(); | |
| if (!OpV) | |
| continue; | |
| OpU.set(nullptr); | |
| if (!OpV->use_empty()) | |
| continue; | |
| // If the operand is an instruction that became dead as we nulled out | |
| // the operand, and if it is 'trivially' dead, delete it in a future | |
| // loop iteration. | |
| if (auto *OpI = dyn_cast<Instruction>(OpV)) | |
| if (!DeletedInstructions.contains(OpI) && | |
| !ExternalUseReplacements.contains(OpI) && | |
| (!OpI->getType()->isVectorTy() || | |
| none_of(VectorValuesAndScales, | |
| [&](const ReductionVectorPart &V) { | |
| return V.Vec == OpI; | |
| })) && | |
| isInstructionTriviallyDead(OpI, TLI)) | |
| DeadInsts.push_back(OpI); | |
| } | |
| VI->removeFromParent(); | |
| eraseInstruction(VI); | |
| SE->forgetValue(VI); | |
| } | |
| } | |
| /// Checks if the instruction was already analyzed for being possible | |
| /// reduction root. | |
| bool isAnalyzedReductionRoot(Instruction *I) const { | |
| return AnalyzedReductionsRoots.count(I); | |
| } | |
| /// Register given instruction as already analyzed for being possible | |
| /// reduction root. | |
| void analyzedReductionRoot(Instruction *I) { | |
| AnalyzedReductionsRoots.insert(I); | |
| } | |
| /// Checks if the provided list of reduced values was checked already for | |
| /// vectorization. | |
| bool areAnalyzedReductionVals(ArrayRef<Value *> VL) const { | |
| return AnalyzedReductionVals.contains(hash_value(VL)); | |
| } | |
| /// Adds the list of reduced values to list of already checked values for the | |
| /// vectorization. | |
| void analyzedReductionVals(ArrayRef<Value *> VL) { | |
| AnalyzedReductionVals.insert(hash_value(VL)); | |
| } | |
| /// Checks if the value was already a part of the analyzed vector node. | |
| bool isAnalyzedScalar(const Value *V) const { | |
| return AnalyzedScalars.contains(V); | |
| } | |
| /// Checks if the given bundle was already rejected as non-vectorizable. | |
| bool isAnalyzedBundle(ArrayRef<Value *> VL) const { | |
| return AnalyzedBundles.contains(hash_value(VL)); | |
| } | |
| /// Registers the bundle as rejected for the vectorization. | |
| void analyzedBundle(ArrayRef<Value *> VL) { | |
| AnalyzedBundles.insert(hash_value(VL)); | |
| } | |
| /// Clear the list of the analyzed reduction root instructions. | |
| void clearReductionData() { | |
| AnalyzedReductionsRoots.clear(); | |
| AnalyzedReductionVals.clear(); | |
| AnalyzedBundles.clear(); | |
| AnalyzedMinBWVals.clear(); | |
| } | |
| /// Checks if the given value is gathered in one of the nodes. | |
| bool isAnyGathered(const SmallDenseSet<Value *> &Vals) const { | |
| return any_of(MustGather, [&](Value *V) { return Vals.contains(V); }); | |
| } | |
| /// Checks if the given value is gathered in one of the nodes. | |
| bool isGathered(const Value *V) const { | |
| return MustGather.contains(V); | |
| } | |
| /// Checks if the specified value was not schedule. | |
| bool isNotScheduled(const Value *V) const { | |
| return NonScheduledFirst.contains(V); | |
| } | |
| /// Check if \p V is a peeled reassociated scalar still owned by a live | |
| /// (non-deleted, non-gathered) tree entry. | |
| bool isReassocScalarVectorized(const Value *V) const { | |
| auto It = ReassocScalarToTreeEntries.find(V); | |
| return It != ReassocScalarToTreeEntries.end() && | |
| any_of(It->second, [&](const TreeEntry *E) { | |
| return !DeletedNodes.contains(E) && | |
| !TransformedToGatherNodes.contains(E); | |
| }); | |
| } | |
| /// Check if the value is vectorized in the tree. | |
| bool isVectorized(const Value *V) const { | |
| assert(V && "V cannot be nullptr."); | |
| if (isReassocScalarVectorized(V)) | |
| return true; | |
| return any_of(getTreeEntries(V), [&](const TreeEntry *E) { | |
| return !DeletedNodes.contains(E) && !TransformedToGatherNodes.contains(E); | |
| }); | |
| } | |
| /// Returns true if the role of \p I is already decided by its user: a deleted | |
| /// user was folded into some other vector by an earlier attempt. | |
| bool hasResolvedUser(Instruction *I) const { | |
| return any_of(I->users(), [&](User *U) { | |
| auto *UI = dyn_cast<Instruction>(U); | |
| return UI && isDeleted(UI); | |
| }); | |
| } | |
| /// Checks if it is legal and profitable to build SplitVectorize node for the | |
| /// given \p VL. | |
| /// \param Op1 first homogeneous scalars. | |
| /// \param Op2 second homogeneous scalars. | |
| /// \param ReorderIndices indices to reorder the scalars. | |
| /// \returns true if the node was successfully built. | |
| bool canBuildSplitNode(ArrayRef<Value *> VL, | |
| const InstructionsState &LocalState, | |
| SmallVectorImpl<Value *> &Op1, | |
| SmallVectorImpl<Value *> &Op2, | |
| OrdersType &ReorderIndices) const; | |
| /// \returns Cast context for the given graph node. | |
| TargetTransformInfo::CastContextHint | |
| getCastContextHint(const TreeEntry &TE) const; | |
| ~BoUpSLP(); | |
| private: | |
| /// Determine if a node \p E in can be demoted to a smaller type with a | |
| /// truncation. We collect the entries that will be demoted in ToDemote. | |
| /// \param E Node for analysis | |
| /// \param ToDemote indices of the nodes to be demoted. | |
| bool collectValuesToDemote( | |
| const TreeEntry &E, bool IsProfitableToDemoteRoot, unsigned &BitWidth, | |
| SmallVectorImpl<unsigned> &ToDemote, DenseSet<const TreeEntry *> &Visited, | |
| const SmallDenseSet<unsigned, 8> &NodesToKeepBWs, unsigned &MaxDepthLevel, | |
| bool &IsProfitableToDemote, bool IsTruncRoot) const; | |
| /// Builds the list of reorderable operands on the edges \p Edges of the \p | |
| /// UserTE, which allow reordering (i.e. the operands can be reordered because | |
| /// they have only one user and reordarable). | |
| /// \param ReorderableGathers List of all gather nodes that require reordering | |
| /// (e.g., gather of extractlements or partially vectorizable loads). | |
| /// \param GatherOps List of gather operand nodes for \p UserTE that require | |
| /// reordering, subset of \p NonVectorized. | |
| void buildReorderableOperands( | |
| TreeEntry *UserTE, | |
| SmallVectorImpl<std::pair<unsigned, TreeEntry *>> &Edges, | |
| const SmallPtrSetImpl<const TreeEntry *> &ReorderableGathers, | |
| SmallVectorImpl<TreeEntry *> &GatherOps); | |
| /// Checks if the given \p TE is a gather node with clustered reused scalars | |
| /// and reorders it per given \p Mask. | |
| void reorderNodeWithReuses(TreeEntry &TE, ArrayRef<int> Mask) const; | |
| /// Checks if all users of \p I are the part of the vectorization tree. | |
| bool areAllUsersVectorized( | |
| Instruction *I, | |
| const SmallDenseSet<Value *> *VectorizedVals = nullptr) const; | |
| /// Estimates the number of scalar instructions in the tree, each weighted by | |
| /// its loop-nest trip count (nest-invariant entries are dropped when | |
| /// \p TreeLoop is non-null). | |
| uint64_t getNumScalarInsts(bool HasTreeLoop); | |
| /// Estimates the number of vector instructions (including buildvectors, | |
| /// shuffles, and, if \p CountExtracts is set, extracts) the tree produces, | |
| /// weighted like getNumScalarInsts(). | |
| uint64_t getNumVectorInsts(bool HasTreeLoop, bool CountExtracts = true); | |
| /// Returns true if the instruction-count veto is skipped for a tree costed at | |
| /// \p TreeCost because it contains a poor-throughput operation whose real | |
| /// vector savings the count check does not model. | |
| bool bypassesInstCountCheck(InstructionCost TreeCost) const; | |
| /// Collects into \p UserOps the user operations of the gathers of the scalars | |
| /// of \p E, for TTI::getBuildVectorContextHint(). Returns false if no user is | |
| /// found or some user cannot be described. | |
| bool | |
| collectGatherUserOps(const TreeEntry &E, | |
| SmallVectorImpl<TTI::BuildVectorUseOp> &UserOps) const; | |
| /// \returns the fadd/fsub user of the single-use fmul \p I, which the | |
| /// backend fuses with \p I into an fmuladd in the scalar code, and the cost | |
| /// of that fmuladd, or {nullptr, invalid cost}. If \p InTree is set, the | |
| /// users, whose fusion is accounted for by the tree, are not returned. | |
| std::pair<Instruction *, InstructionCost> | |
| getFMulFusingUser(Instruction *I, bool InTree = true) const; | |
| /// \returns true if the fadd/fsub \p U, fused with its fmul operand by the | |
| /// backend, is a vectorized tree scalar whose lane is priced as fmuladd. | |
| /// Peeled reassociated scalars are priced as plain fadd/fsub. | |
| bool isFusedVectorizedFAddSub(Instruction *U) const; | |
| /// \returns the scalar cost of the fmul lanes of \p TE that are priced as | |
| /// fused into their vectorized fadd/fsub users. If the node is turned into a | |
| /// gather, these fmuls stay scalar and lose the fusion, so the gather | |
| /// alternative must pay their full price. | |
| InstructionCost getUnfusedFMulsPenalty(const TreeEntry &TE) const; | |
| /// \returns the graph entry for the \p Idx operand of the \p E entry. | |
| const TreeEntry *getOperandEntry(const TreeEntry *E, unsigned Idx) const; | |
| TreeEntry *getOperandEntry(TreeEntry *E, unsigned Idx) { | |
| return const_cast<TreeEntry *>( | |
| getOperandEntry(const_cast<const TreeEntry *>(E), Idx)); | |
| } | |
| /// Gets the root instruction for the given node. If the node is a strided | |
| /// load/store node with the reverse order, the root instruction is the last | |
| /// one. | |
| Instruction *getRootEntryInstruction(const TreeEntry &Entry) const; | |
| /// \returns the scale of the given tree entry to the loop iteration. | |
| /// \p Scalar is the scalar value from the entry, if using the parent for the | |
| /// external use. | |
| /// \p U is the user of the vectorized value from the entry, if using the | |
| /// parent for the external use. | |
| uint64_t getScaleToLoopIterations(const TreeEntry &TE, | |
| Value *Scalar = nullptr, | |
| Instruction *U = nullptr); | |
| /// \returns the product of trip counts of the loop \p L and all of its | |
| /// enclosing loops. Unlike the state kept by getScaleToLoopIterations(), | |
| /// this helper depends only on the loop structure and is independent of | |
| /// per-entry operand invariance. Returns 1 when loop-aware cost modeling | |
| /// is disabled or \p L is null. | |
| uint64_t getLoopNestScale(const Loop *L); | |
| /// \returns a refined execution scale for a gather/buildvector tree entry | |
| /// \p TE. The scale is computed as the average of per-lane execution | |
| /// scales: each lane's scale is the loop-nest scale of the loop that | |
| /// contains the lane's defining instruction (or 1 if the lane is a | |
| /// constant / loop-invariant non-instruction value). This models the | |
| /// LICM hoisting that optimizeGatherSequence() performs after vectorization | |
| /// for inserts with loop-invariant operands. Falls back to the whole-entry | |
| /// scale when per-lane information is unavailable or the feature is off. | |
| uint64_t getGatherNodeEffectiveScale(const TreeEntry &TE, | |
| Instruction *U = nullptr); | |
| /// \returns the loop-nest execution scale of \p TE. | |
| uint64_t getEntryEffectiveScale(const TreeEntry &TE, | |
| Instruction *U = nullptr); | |
| /// Get the loop nest for the given loop \p L. | |
| ArrayRef<const Loop *> getLoopNest(const Loop *L); | |
| /// \returns the cost of the vectorizable entry. | |
| InstructionCost getEntryCost(const TreeEntry *E, | |
| ArrayRef<Value *> VectorizedVals, | |
| SmallPtrSetImpl<Value *> &CheckedExtracts); | |
| /// Estimates spill/reload cost from vector register pressure for \p E at the | |
| /// point of emitting its vector result type \p FinalVecTy. \p ScalarTy is the | |
| /// scalar/slot type used to widen into \p VecTy/\p FinalVecTy and may itself | |
| /// be a FixedVectorType in ReVec mode or an adjusted type due to MinBWs. | |
| InstructionCost | |
| getVectorSpillReloadCost(const TreeEntry *E, Type *ScalarTy, Type *VecTy, | |
| Type *FinalVecTy, | |
| const TTI::TargetCostKind CostKind) const; | |
| /// This is the recursive part of buildTree. | |
| void buildTreeRec(ArrayRef<Value *> Roots, unsigned Depth, const EdgeInfo &EI, | |
| unsigned InterleaveFactor = 0); | |
| /// \returns true if the ExtractElement/ExtractValue instructions in \p VL can | |
| /// be vectorized to use the original vector (or aggregate "bitcast" to a | |
| /// vector) and sets \p CurrentOrder to the identity permutation; otherwise | |
| /// returns false, setting \p CurrentOrder to either an empty vector or a | |
| /// non-identity permutation that allows to reuse extract instructions. | |
| /// \param ResizeAllowed indicates whether it is allowed to handle subvector | |
| /// extract order. | |
| bool canReuseExtract(ArrayRef<Value *> VL, | |
| SmallVectorImpl<unsigned> &CurrentOrder, | |
| bool ResizeAllowed = false) const; | |
| /// Vectorize a single entry in the tree. | |
| Value *vectorizeTree(TreeEntry *E); | |
| /// Vectorize a single entry in the tree, the \p Idx-th operand of the entry | |
| /// \p E. | |
| Value *vectorizeOperand(TreeEntry *E, unsigned NodeIdx); | |
| /// Create a new vector from a list of scalar values. Produces a sequence | |
| /// which exploits values reused across lanes, and arranges the inserts | |
| /// for ease of later optimization. | |
| template <typename BVTy, typename ResTy, typename... Args> | |
| ResTy processBuildVector(const TreeEntry *E, Type *ScalarTy, Args &...Params); | |
| /// Handles the gather of zero-extended sub-fields of the same wider integer | |
| /// scalar, emitted as a bitcast to the field vector plus a permutation and | |
| /// an extension to the lane type, if the gathered values match. | |
| template <typename ResTy, typename BVTy> | |
| std::optional<ResTy> processExtractedFieldsGather( | |
| BVTy &ShuffleBuilder, const TreeEntry *E, Type *ScalarTy, | |
| ArrayRef<Value *> VL, | |
| ArrayRef<std::pair<const TreeEntry *, unsigned>> SubVectors); | |
| /// Create a new vector from a list of scalar values. Produces a sequence | |
| /// which exploits values reused across lanes, and arranges the inserts | |
| /// for ease of later optimization. | |
| Value *createBuildVector(const TreeEntry *E, Type *ScalarTy); | |
| /// Returns the instruction in the bundle, which can be used as a base point | |
| /// for scheduling. Usually it is the last instruction in the bundle, except | |
| /// for the case when all operands are external (in this case, it is the first | |
| /// instruction in the list). | |
| Instruction &getLastInstructionInBundle(const TreeEntry *E); | |
| /// Tries to find extractelement instructions with constant indices from fixed | |
| /// vector type and gather such instructions into a bunch, which highly likely | |
| /// might be detected as a shuffle of 1 or 2 input vectors. If this attempt | |
| /// was successful, the matched scalars are replaced by poison values in \p VL | |
| /// for future analysis. | |
| std::optional<TargetTransformInfo::ShuffleKind> | |
| tryToGatherSingleRegisterExtractElements(MutableArrayRef<Value *> VL, | |
| SmallVectorImpl<int> &Mask) const; | |
| /// Tries to find extractelement instructions with constant indices from fixed | |
| /// vector type and gather such instructions into a bunch, which highly likely | |
| /// might be detected as a shuffle of 1 or 2 input vectors. If this attempt | |
| /// was successful, the matched scalars are replaced by poison values in \p VL | |
| /// for future analysis. | |
| SmallVector<std::optional<TargetTransformInfo::ShuffleKind>> | |
| tryToGatherExtractElements(SmallVectorImpl<Value *> &VL, | |
| SmallVectorImpl<int> &Mask, | |
| unsigned NumParts) const; | |
| /// Checks if the gathered \p VL can be represented as a single register | |
| /// shuffle(s) of previous tree entries. | |
| /// \param TE Tree entry checked for permutation. | |
| /// \param VL List of scalars (a subset of the TE scalar), checked for | |
| /// permutations. Must form single-register vector. | |
| /// \param ForOrder Tries to fetch the best candidates for ordering info. Also | |
| /// commands to build the mask using the original vector value, without | |
| /// relying on the potential reordering. | |
| /// \returns ShuffleKind, if gathered values can be represented as shuffles of | |
| /// previous tree entries. \p Part of \p Mask is filled with the shuffle mask. | |
| std::optional<TargetTransformInfo::ShuffleKind> | |
| isGatherShuffledSingleRegisterEntry( | |
| const TreeEntry *TE, ArrayRef<Value *> VL, MutableArrayRef<int> Mask, | |
| SmallVectorImpl<const TreeEntry *> &Entries, unsigned Part, bool ForOrder, | |
| unsigned SliceSize); | |
| /// Checks if the gathered \p VL can be represented as multi-register | |
| /// shuffle(s) of previous tree entries. | |
| /// \param TE Tree entry checked for permutation. | |
| /// \param VL List of scalars (a subset of the TE scalar), checked for | |
| /// permutations. | |
| /// \param ForOrder Tries to fetch the best candidates for ordering info. Also | |
| /// commands to build the mask using the original vector value, without | |
| /// relying on the potential reordering. | |
| /// \returns per-register series of ShuffleKind, if gathered values can be | |
| /// represented as shuffles of previous tree entries. \p Mask is filled with | |
| /// the shuffle mask (also on per-register base). | |
| SmallVector<std::optional<TargetTransformInfo::ShuffleKind>> | |
| isGatherShuffledEntry( | |
| const TreeEntry *TE, ArrayRef<Value *> VL, SmallVectorImpl<int> &Mask, | |
| SmallVectorImpl<SmallVector<const TreeEntry *>> &Entries, | |
| unsigned NumParts, Type *ScalarTy, bool ForOrder = false); | |
| /// \returns the cost of gathering (inserting) the values in \p VL into a | |
| /// vector. | |
| /// \param ForPoisonSrc true if initial vector is poison, false otherwise. | |
| InstructionCost getGatherCost(ArrayRef<Value *> VL, bool ForPoisonSrc, | |
| Type *ScalarTy) const; | |
| /// Set the Builder insert point to one after the last instruction in | |
| /// the bundle | |
| void setInsertPointAfterBundle(const TreeEntry *E); | |
| /// \returns a vector from a collection of scalars in \p VL. if \p Root is not | |
| /// specified, the starting vector value is poison. | |
| Value * | |
| gather(ArrayRef<Value *> VL, Value *Root, Type *ScalarTy, | |
| function_ref<Value *(Value *, Value *, ArrayRef<int>)> CreateShuffle); | |
| /// \returns whether the VectorizableTree is fully vectorizable and will | |
| /// be beneficial even the tree height is tiny. | |
| bool isFullyVectorizableTinyTree(bool ForReduction) const; | |
| /// Run through the list of all gathered loads in the graph and try to find | |
| /// vector loads/masked gathers instead of regular gathers. Later these loads | |
| /// are reshufled to build final gathered nodes. | |
| void tryToVectorizeGatheredLoads( | |
| const SmallMapVector< | |
| std::tuple<BasicBlock *, Value *, Type *>, | |
| SmallVector<SmallVector<std::pair<LoadInst *, int64_t>>>, 8> | |
| &GatheredLoads); | |
| /// Run through the gather nodes that are splats of the same instruction and | |
| /// try to vectorize the unique splatted values together as a separate | |
| /// subtree. The splat gathers are then emitted as broadcasts of the | |
| /// vectorized subtree instead of insertion sequences. | |
| void tryToVectorizeSplatGatheredScalars(); | |
| /// Helper for `findExternalStoreUsersReorderIndices()`. It iterates over the | |
| /// users of \p TE and collects the stores. It returns the map from the store | |
| /// pointers to the collected stores. | |
| SmallVector<SmallVector<StoreInst *>> | |
| collectUserStores(const BoUpSLP::TreeEntry *TE) const; | |
| /// Helper for `findExternalStoreUsersReorderIndices()`. It checks if the | |
| /// stores in \p StoresVec can form a vector instruction. If so it returns | |
| /// true and populates \p ReorderIndices with the shuffle indices of the | |
| /// stores when compared to the sorted vector. | |
| bool canFormVector(ArrayRef<StoreInst *> StoresVec, | |
| OrdersType &ReorderIndices) const; | |
| /// Iterates through the users of \p TE, looking for scalar stores that can be | |
| /// potentially vectorized in a future SLP-tree. If found, it keeps track of | |
| /// their order and builds an order index vector for each store bundle. It | |
| /// returns all these order vectors found. | |
| /// We run this after the tree has formed, otherwise we may come across user | |
| /// instructions that are not yet in the tree. | |
| SmallVector<OrdersType, 1> | |
| findExternalStoreUsersReorderIndices(TreeEntry *TE) const; | |
| /// Tries to reorder the gathering node for better vectorization | |
| /// opportunities. | |
| void reorderGatherNode(TreeEntry &TE); | |
| /// Checks if the tree represents disjoint or reduction of shl(zext, (0, 8, | |
| /// .., 56))-like pattern. | |
| /// If the int shifts unique, also strided, but not ordered, sets \p Order. | |
| /// If the node can be represented as a bitcast + bswap, sets \p IsBSwap. | |
| /// If the root nodes are loads, sets \p ForLoads to true. | |
| bool matchesShlZExt(const TreeEntry &TE, OrdersType &Order, bool &IsBSwap, | |
| bool &ForLoads) const; | |
| /// Computes the packing layout of an or reduction of masked shifted values | |
| /// packing per-lane byte fields of the scalar result (and(shl(x, s), m) per | |
| /// lane). The field analysis proves the disjointness of the fields, so the | |
| /// reduction operations are not required to be marked disjoint. | |
| std::optional<BitPackInfo> computeBitPackLayout(const TreeEntry &TE) const; | |
| /// Returns the node packed for the shl operand \p LhsTE of a bitpack root. | |
| /// If \p LhsTE is a (zext of) and(lshr/shl(x, C), mask) chain extracting | |
| /// the same bits of every lane of x, returns the node of x and sets the | |
| /// shift amounts of \p Info to the field offset in the lanes of x. | |
| /// Otherwise, returns \p LhsTE. | |
| const TreeEntry *getBitPackSource(const TreeEntry &LhsTE, | |
| BitPackInfo &Info) const; | |
| /// Checks if the tree entry is the root of such a reduction, the tree nodes | |
| /// are in the expected state and it is profitable to model as a bit pack. | |
| bool matchesBitPack(const TreeEntry &TE) const; | |
| /// If the node's lanes are a plain zext, returns the source bit width: | |
| /// compacting the vectorized lanes back to it is free. Copyable lanes force | |
| /// a blend after the extend, so the vectorized value is not a plain zext. | |
| unsigned getZExtSrcWidth(const TreeEntry &TE) const; | |
| /// Checks if the \p SelectTE matches zext+selects, which can be inversed for | |
| /// better codegen in case like zext (icmp ne), select (icmp eq), .... | |
| bool matchesInversedZExtSelect( | |
| const TreeEntry &SelectTE, | |
| SmallVectorImpl<unsigned> &InversedCmpsIndices) const; | |
| /// Checks if the tree is reduction or of bit selects, like select %cmp, <1, | |
| /// 2, 4, 8, ..>, zeroinitializer, which can be reduced just to a bitcast %cmp | |
| /// to in. | |
| bool matchesSelectOfBits(const TreeEntry &SelectTE) const; | |
| class TreeEntry { | |
| public: | |
| using VecTreeTy = SmallVector<std::unique_ptr<TreeEntry>, 8>; | |
| TreeEntry(VecTreeTy &Container) : Container(Container) {} | |
| /// \returns Common mask for reorder indices and reused scalars. | |
| SmallVector<int> getCommonMask() const { | |
| if (State == TreeEntry::SplitVectorize) | |
| return {}; | |
| SmallVector<int> Mask; | |
| inversePermutation(ReorderIndices, Mask); | |
| addMask(Mask, ReuseShuffleIndices); | |
| return Mask; | |
| } | |
| /// \returns The mask for split nodes. | |
| SmallVector<int> getSplitMask() const { | |
| assert(State == TreeEntry::SplitVectorize && !ReorderIndices.empty() && | |
| "Expected only split vectorize node."); | |
| unsigned CommonVF = std::max<unsigned>( | |
| CombinedEntriesWithIndices.back().second, | |
| Scalars.size() - CombinedEntriesWithIndices.back().second); | |
| const unsigned Scale = getNumElements(Scalars.front()->getType()); | |
| CommonVF *= Scale; | |
| SmallVector<int> Mask(getVectorFactor() * Scale, PoisonMaskElem); | |
| for (auto [Idx, I] : enumerate(ReorderIndices)) { | |
| for (unsigned K : seq<unsigned>(Scale)) { | |
| Mask[Scale * I + K] = | |
| Scale * Idx + K + | |
| (Idx >= CombinedEntriesWithIndices.back().second | |
| ? CommonVF - CombinedEntriesWithIndices.back().second * Scale | |
| : 0); | |
| } | |
| } | |
| return Mask; | |
| } | |
| /// Updates (reorders) SplitVectorize node according to the given mask \p | |
| /// Mask and order \p MaskOrder. | |
| void reorderSplitNode(unsigned Idx, ArrayRef<int> Mask, | |
| ArrayRef<int> MaskOrder); | |
| /// \returns true if the scalars in VL are equal to this entry. | |
| bool isSame(ArrayRef<Value *> VL) const { | |
| auto &&IsSame = [VL](ArrayRef<Value *> Scalars, ArrayRef<int> Mask) { | |
| if (Mask.size() != VL.size() && VL.size() == Scalars.size()) | |
| return std::equal(VL.begin(), VL.end(), Scalars.begin()); | |
| return VL.size() == Mask.size() && | |
| std::equal(VL.begin(), VL.end(), Mask.begin(), | |
| [Scalars](Value *V, int Idx) { | |
| return isa<PoisonValue>(V) || | |
| (Idx != PoisonMaskElem && V == Scalars[Idx]); | |
| }); | |
| }; | |
| if (!ReorderIndices.empty()) { | |
| // TODO: implement matching if the nodes are just reordered, still can | |
| // treat the vector as the same if the list of scalars matches VL | |
| // directly, without reordering. | |
| SmallVector<int> Mask; | |
| inversePermutation(ReorderIndices, Mask); | |
| if (VL.size() == Scalars.size()) | |
| return IsSame(Scalars, Mask); | |
| if (VL.size() == ReuseShuffleIndices.size()) { | |
| addMask(Mask, ReuseShuffleIndices); | |
| return IsSame(Scalars, Mask); | |
| } | |
| return false; | |
| } | |
| return IsSame(Scalars, ReuseShuffleIndices); | |
| } | |
| /// \returns true if current entry has same operands as \p TE. | |
| bool hasEqualOperands(const TreeEntry &TE) const { | |
| if (TE.getNumOperands() != getNumOperands()) | |
| return false; | |
| SmallBitVector Used(getNumOperands()); | |
| for (unsigned I = 0, E = getNumOperands(); I < E; ++I) { | |
| unsigned PrevCount = Used.count(); | |
| for (unsigned K = 0; K < E; ++K) { | |
| if (Used.test(K)) | |
| continue; | |
| if (getOperand(K) == TE.getOperand(I)) { | |
| Used.set(K); | |
| break; | |
| } | |
| } | |
| // Check if we actually found the matching operand. | |
| if (PrevCount == Used.count()) | |
| return false; | |
| } | |
| return true; | |
| } | |
| /// \return Final vectorization factor for the node. Defined by the total | |
| /// number of vectorized scalars, including those, used several times in the | |
| /// entry and counted in the \a ReuseShuffleIndices, if any. | |
| unsigned getVectorFactor() const { | |
| if (!ReuseShuffleIndices.empty()) | |
| return ReuseShuffleIndices.size(); | |
| return Scalars.size(); | |
| }; | |
| /// Checks if the current node is a gather node. | |
| bool isGather() const { return State == NeedToGather; } | |
| /// Checks if the current node is in reverse order. | |
| bool isReverse() const { | |
| return !ReorderIndices.empty() && isReverseOrder(ReorderIndices); | |
| } | |
| /// A vector of scalars. | |
| ValueList Scalars; | |
| /// The Scalars are vectorized into this value. It is initialized to Null. | |
| WeakTrackingVH VectorizedValue = nullptr; | |
| /// Do we need to gather this sequence or vectorize it | |
| /// (either with vector instruction or with scatter/gather | |
| /// intrinsics for store/load)? | |
| enum EntryState { | |
| Vectorize, ///< The node is regularly vectorized. | |
| ScatterVectorize, ///< Masked scatter/gather node. | |
| StridedVectorize, ///< Strided loads (and stores) | |
| ExpandVectorize, ///< Masked stores, the values are expanded into | |
| ///< a wider vector and vectorized with a mask. | |
| CompressVectorize, ///< (Masked) load with compress. | |
| BlendedLoadVectorize, ///< (Masked) loads blended via `select` from two | |
| ///< candidate base pointers. | |
| NeedToGather, ///< Gather/buildvector node. | |
| CombinedVectorize, ///< Vectorized node, combined with its user into more | |
| ///< complex node like select/cmp to minmax, mul/add to | |
| ///< fma, etc. Must be used for the following nodes in | |
| ///< the pattern, not the very first one. | |
| SplitVectorize, ///< Splits the node into 2 subnodes, vectorizes them | |
| ///< independently and then combines back. | |
| }; | |
| EntryState State; | |
| /// List of combined opcodes supported by the vectorizer. | |
| enum CombinedOpcode { | |
| NotCombinedOp = -1, | |
| MinMax = Instruction::OtherOpsEnd + 1, | |
| FMulAdd, | |
| ReducedBitcast, | |
| ReducedBitcastBSwap, | |
| ReducedBitcastLoads, | |
| ReducedBitcastBSwapLoads, | |
| ReducedBitPack, | |
| ReducedCmpBitcast, | |
| }; | |
| CombinedOpcode CombinedOp = NotCombinedOp; | |
| /// Does this sequence require some shuffling? | |
| SmallVector<int, 4> ReuseShuffleIndices; | |
| /// Does this entry require reordering? | |
| SmallVector<unsigned, 4> ReorderIndices; | |
| /// Points back to the VectorizableTree. | |
| /// | |
| /// Only used for Graphviz right now. Unfortunately GraphTrait::NodeRef has | |
| /// to be a pointer and needs to be able to initialize the child iterator. | |
| /// Thus we need a reference back to the container to translate the indices | |
| /// to entries. | |
| VecTreeTy &Container; | |
| /// The TreeEntry index containing the user of this entry. | |
| EdgeInfo UserTreeIndex; | |
| /// The index of this treeEntry in VectorizableTree. | |
| unsigned Idx = 0; | |
| /// For gather/buildvector/alt opcode nodes, which are combined from | |
| /// other nodes as a series of insertvector instructions. | |
| SmallVector<std::pair<unsigned, unsigned>, 2> CombinedEntriesWithIndices; | |
| /// For ExtractValue entries that are vectorized via the struct-call path | |
| /// (checkEVsForVecCalls succeeded during tree building), stores the common | |
| /// field-index path shared by all scalars in the bundle. Empty for all | |
| /// other entry kinds. | |
| SmallVector<unsigned, 1> StructEVIndices; | |
| private: | |
| /// The operands of each instruction in each lane Operands[op_index][lane]. | |
| /// Note: This helps avoid the replication of the code that performs the | |
| /// reordering of operands during buildTreeRec() and vectorizeTree(). | |
| SmallVector<ValueList, 2> Operands; | |
| /// Copyable elements of the entry node. | |
| SmallPtrSet<const Value *, 4> CopyableElements; | |
| /// Intermediate instructions peeled from an associative chain (e.g. the | |
| /// inner add in add(add(v0,x),v1)). Not part of Scalars. | |
| SmallVector<Value *, 4> ReassocScalars; | |
| /// Sign of each flattened operand column of a reassociated add/sub | |
| /// chain, parallel to the operand columns: a negated column is | |
| /// subtracted from the positive total. Empty when no column is negated. | |
| SmallBitVector ReassocNegatedOps; | |
| /// MainOp and AltOp are recorded inside. S should be obtained from | |
| /// newTreeEntry. | |
| InstructionsState S = InstructionsState::invalid(); | |
| /// Interleaving factor for interleaved loads Vectorize nodes. | |
| unsigned InterleaveFactor = 0; | |
| /// True if the node does not require scheduling. | |
| bool DoesNotNeedToSchedule = false; | |
| /// Set this bundle's \p OpIdx'th operand to \p OpVL. | |
| void setOperand(unsigned OpIdx, ArrayRef<Value *> OpVL) { | |
| if (Operands.size() < OpIdx + 1) | |
| Operands.resize(OpIdx + 1); | |
| assert(Operands[OpIdx].empty() && "Already resized?"); | |
| assert(OpVL.size() <= Scalars.size() && | |
| "Number of operands is greater than the number of scalars."); | |
| Operands[OpIdx].resize(OpVL.size()); | |
| copy(OpVL, Operands[OpIdx].begin()); | |
| } | |
| /// Maps values to their lanes in the node. | |
| mutable SmallDenseMap<Value *, unsigned> ValueToLane; | |
| public: | |
| /// Returns interleave factor for interleave nodes. | |
| unsigned getInterleaveFactor() const { return InterleaveFactor; } | |
| /// Sets interleaving factor for the interleaving nodes. | |
| void setInterleave(unsigned Factor) { InterleaveFactor = Factor; } | |
| /// Marks the node as one that does not require scheduling. | |
| void setDoesNotNeedToSchedule() { DoesNotNeedToSchedule = true; } | |
| /// Returns true if the node is marked as one that does not require | |
| /// scheduling. | |
| bool doesNotNeedToSchedule() const { return DoesNotNeedToSchedule; } | |
| /// Set this bundle's operands from \p Operands. | |
| void setOperands(ArrayRef<ValueList> Operands) { | |
| for (unsigned I : seq<unsigned>(Operands.size())) | |
| setOperand(I, Operands[I]); | |
| } | |
| /// Reorders operands of the node to the given mask \p Mask. | |
| void reorderOperands(ArrayRef<int> Mask) { | |
| for (ValueList &Operand : Operands) | |
| reorderScalars(Operand, Mask); | |
| } | |
| /// \returns the \p OpIdx operand of this TreeEntry. | |
| ValueList &getOperand(unsigned OpIdx) { | |
| assert(OpIdx < Operands.size() && "Off bounds"); | |
| return Operands[OpIdx]; | |
| } | |
| /// \returns the \p OpIdx operand of this TreeEntry. | |
| ArrayRef<Value *> getOperand(unsigned OpIdx) const { | |
| assert(OpIdx < Operands.size() && "Off bounds"); | |
| return Operands[OpIdx]; | |
| } | |
| /// \returns the number of operands. | |
| unsigned getNumOperands() const { return Operands.size(); } | |
| /// \return the single \p OpIdx operand. | |
| Value *getSingleOperand(unsigned OpIdx) const { | |
| assert(OpIdx < Operands.size() && "Off bounds"); | |
| assert(!Operands[OpIdx].empty() && "No operand available"); | |
| return Operands[OpIdx][0]; | |
| } | |
| /// Some of the instructions in the list have alternate opcodes. | |
| bool isAltShuffle() const { return S.isAltShuffle(); } | |
| Instruction *getMatchingMainOpOrAltOp(Instruction *I) const { | |
| return S.getMatchingMainOpOrAltOp(I); | |
| } | |
| void setOperations(const InstructionsState &S) { | |
| assert(S && "InstructionsState is invalid."); | |
| this->S = S; | |
| } | |
| Instruction *getMainOp() const { return S.getMainOp(); } | |
| Instruction *getAltOp() const { return S.getAltOp(); } | |
| /// The main/alternate opcodes for the list of instructions. | |
| unsigned getOpcode() const { return S.getOpcode(); } | |
| unsigned getAltOpcode() const { return S.getAltOpcode(); } | |
| bool hasState() const { return S.valid(); } | |
| /// Add \p V to the list of copyable elements. | |
| void addCopyableElement(Value *V) { | |
| assert(S.isCopyableElement(V) && "Not a copyable element."); | |
| CopyableElements.insert(V); | |
| } | |
| /// Returns true if \p V is a copyable element. | |
| bool isCopyableElement(Value *V) const { | |
| return CopyableElements.contains(V); | |
| } | |
| /// Checks if the value \p V is a transformed instruction, compatible either | |
| /// with main or alternate ops. | |
| bool isExpandedBinOp(Value *V) const { | |
| assert(hasState() && "InstructionsState is invalid."); | |
| if (isCopyableElement(V)) | |
| return false; | |
| return S.isExpandedBinOp(V); | |
| } | |
| /// Checks if the operand at index \p Idx of instruction \p I is an expanded | |
| /// operand. | |
| bool isExpandedOperand(Instruction *I, unsigned Idx) const { | |
| assert(hasState() && "InstructionsState is invalid."); | |
| if (isCopyableElement(I)) | |
| return false; | |
| if (!isExpandedBinOp(I)) | |
| return false; | |
| return S.isExpandedOperand(I, Idx); | |
| } | |
| /// Returns true if any scalar in the list is a copyable element. | |
| bool hasCopyableElements() const { return !CopyableElements.empty(); } | |
| /// Adds \p V to the peeled reassociated scalars. | |
| void addReassocScalar(Value *V) { ReassocScalars.push_back(V); } | |
| /// True if operands were gathered from an associative chain. | |
| bool hasReassocScalars() const { return !ReassocScalars.empty(); } | |
| /// Returns peeled reassociated scalars. | |
| ArrayRef<Value *> getReassocScalars() const { return ReassocScalars; } | |
| /// Records the signs of the flattened operand columns. | |
| void setReassocNegatedOps(const SmallBitVector &NegatedOps) { | |
| assert(NegatedOps.size() == getNumOperands() && | |
| "Signs must cover all operand columns."); | |
| ReassocNegatedOps = NegatedOps; | |
| } | |
| /// True if operand column \p Idx is subtracted rather than added. | |
| bool isReassocNegatedOp(unsigned Idx) const { | |
| return Idx < ReassocNegatedOps.size() && ReassocNegatedOps[Idx]; | |
| } | |
| /// Returns the state of the operations. | |
| const InstructionsState &getOperations() const { return S; } | |
| /// When ReuseReorderShuffleIndices is empty it just returns position of \p | |
| /// V within vector of Scalars. Otherwise, try to remap on its reuse index. | |
| unsigned findLaneForValue(Value *V) const { | |
| auto Res = ValueToLane.try_emplace(V, getVectorFactor()); | |
| if (!Res.second) | |
| return Res.first->second; | |
| unsigned &FoundLane = Res.first->getSecond(); | |
| // Poison can take any lane, match it to the lane of the first non-poison | |
| // scalar. | |
| auto IsMatch = [V](Value *S) { | |
| return isa<PoisonValue>(V) ? !isa<PoisonValue>(S) : S == V; | |
| }; | |
| for (auto *It = find_if(Scalars, IsMatch), *End = Scalars.end(); | |
| It != End; std::advance(It, 1)) { | |
| if (!IsMatch(*It)) | |
| continue; | |
| FoundLane = std::distance(Scalars.begin(), It); | |
| assert(FoundLane < Scalars.size() && "Couldn't find extract lane"); | |
| if (!ReorderIndices.empty()) | |
| FoundLane = ReorderIndices[FoundLane]; | |
| assert(FoundLane < Scalars.size() && "Couldn't find extract lane"); | |
| if (ReuseShuffleIndices.empty()) | |
| break; | |
| if (auto *RIt = find(ReuseShuffleIndices, FoundLane); | |
| RIt != ReuseShuffleIndices.end()) { | |
| FoundLane = std::distance(ReuseShuffleIndices.begin(), RIt); | |
| break; | |
| } | |
| } | |
| assert(FoundLane < getVectorFactor() && "Unable to find given value."); | |
| return FoundLane; | |
| } | |
| /// Build a shuffle mask for graph entry which represents a merge of main | |
| /// and alternate operations. | |
| void | |
| buildAltOpShuffleMask(const function_ref<bool(Instruction *)> IsAltOp, | |
| SmallVectorImpl<int> &Mask, | |
| SmallVectorImpl<Value *> *OpScalars = nullptr, | |
| SmallVectorImpl<Value *> *AltScalars = nullptr) const; | |
| /// Return true if this is a non-power-of-2 node. | |
| bool isNonPowOf2Vec() const { | |
| bool IsNonPowerOf2 = !has_single_bit(Scalars.size()); | |
| return IsNonPowerOf2; | |
| } | |
| Value *getOrdered(unsigned Idx) const { | |
| if (ReorderIndices.empty()) | |
| return Scalars[Idx]; | |
| SmallVector<int> Mask; | |
| inversePermutation(ReorderIndices, Mask); | |
| return Scalars[Mask[Idx]]; | |
| } | |
| #ifndef NDEBUG | |
| /// Debug printer. | |
| LLVM_DUMP_METHOD void dump() const { | |
| dbgs() << Idx << ".\n"; | |
| for (unsigned OpI = 0, OpE = Operands.size(); OpI != OpE; ++OpI) { | |
| dbgs() << "Operand " << OpI << ":\n"; | |
| for (const Value *V : Operands[OpI]) | |
| dbgs().indent(2) << *V << "\n"; | |
| } | |
| dbgs() << "Scalars: \n"; | |
| for (Value *V : Scalars) { | |
| dbgs().indent(2) << *V | |
| << ((S && S.isExpandedBinOp(V)) ? " [[Expanded]]\n" | |
| : "\n"); | |
| } | |
| dbgs() << "State: "; | |
| if (S && hasCopyableElements()) | |
| dbgs() << "[[Copyable]] "; | |
| switch (State) { | |
| case Vectorize: | |
| if (InterleaveFactor > 0) { | |
| dbgs() << "Vectorize with interleave factor " << InterleaveFactor | |
| << "\n"; | |
| } else { | |
| dbgs() << "Vectorize\n"; | |
| } | |
| break; | |
| case ScatterVectorize: | |
| dbgs() << "ScatterVectorize\n"; | |
| break; | |
| case StridedVectorize: | |
| dbgs() << "StridedVectorize\n"; | |
| break; | |
| case ExpandVectorize: | |
| dbgs() << "ExpandVectorize\n"; | |
| break; | |
| case CompressVectorize: | |
| dbgs() << "CompressVectorize\n"; | |
| break; | |
| case BlendedLoadVectorize: | |
| dbgs() << "BlendedLoadVectorize\n"; | |
| break; | |
| case NeedToGather: | |
| dbgs() << "NeedToGather\n"; | |
| break; | |
| case CombinedVectorize: | |
| dbgs() << "CombinedVectorize\n"; | |
| break; | |
| case SplitVectorize: | |
| dbgs() << "SplitVectorize\n"; | |
| break; | |
| } | |
| if (S) { | |
| dbgs() << "MainOp: " << *S.getMainOp() << "\n"; | |
| dbgs() << "AltOp: " << *S.getAltOp() << "\n"; | |
| } else { | |
| dbgs() << "MainOp: NULL\n"; | |
| dbgs() << "AltOp: NULL\n"; | |
| } | |
| dbgs() << "VectorizedValue: "; | |
| if (VectorizedValue) | |
| dbgs() << *VectorizedValue << "\n"; | |
| else | |
| dbgs() << "NULL\n"; | |
| dbgs() << "ReuseShuffleIndices: "; | |
| if (ReuseShuffleIndices.empty()) | |
| dbgs() << "Empty"; | |
| else | |
| for (int ReuseIdx : ReuseShuffleIndices) | |
| dbgs() << ReuseIdx << ", "; | |
| dbgs() << "\n"; | |
| dbgs() << "ReorderIndices: "; | |
| for (unsigned ReorderIdx : ReorderIndices) | |
| dbgs() << ReorderIdx << ", "; | |
| dbgs() << "\n"; | |
| dbgs() << "UserTreeIndex: "; | |
| if (UserTreeIndex) | |
| dbgs() << UserTreeIndex; | |
| else | |
| dbgs() << "<invalid>"; | |
| dbgs() << "\n"; | |
| if (!StructEVIndices.empty()) { | |
| dbgs() << "StructEVIndices: "; | |
| interleaveComma(StructEVIndices, dbgs()); | |
| dbgs() << "\n"; | |
| } | |
| if (!CombinedEntriesWithIndices.empty()) { | |
| dbgs() << "Combined entries: "; | |
| interleaveComma(CombinedEntriesWithIndices, dbgs(), [&](const auto &P) { | |
| dbgs() << "Entry index " << P.first << " with offset " << P.second; | |
| }); | |
| dbgs() << "\n"; | |
| } | |
| } | |
| #endif | |
| }; | |
| #ifndef NDEBUG | |
| void dumpTreeCosts(const TreeEntry *E, InstructionCost ReuseShuffleCost, | |
| InstructionCost VecCost, InstructionCost ScalarCost, | |
| StringRef Banner) const { | |
| dbgs() << "SLP: " << Banner << ":\n"; | |
| E->dump(); | |
| dbgs() << "SLP: Costs:\n"; | |
| dbgs() << "SLP: ReuseShuffleCost = " << ReuseShuffleCost << "\n"; | |
| dbgs() << "SLP: VectorCost = " << VecCost << "\n"; | |
| dbgs() << "SLP: ScalarCost = " << ScalarCost << "\n"; | |
| dbgs() << "SLP: ReuseShuffleCost + VecCost - ScalarCost = " | |
| << ReuseShuffleCost + VecCost - ScalarCost << "\n"; | |
| } | |
| #endif | |
| /// Create a new gather TreeEntry | |
| TreeEntry *newGatherTreeEntry(ArrayRef<Value *> VL, | |
| const InstructionsState &S, | |
| const EdgeInfo &UserTreeIdx, | |
| ArrayRef<int> ReuseShuffleIndices = {}) { | |
| auto Invalid = ScheduleBundle::invalid(); | |
| return newTreeEntry(VL, Invalid, S, UserTreeIdx, ReuseShuffleIndices); | |
| } | |
| /// Create a new VectorizableTree entry. | |
| TreeEntry *newTreeEntry(ArrayRef<Value *> VL, ScheduleBundle &Bundle, | |
| const InstructionsState &S, | |
| const EdgeInfo &UserTreeIdx, | |
| ArrayRef<int> ReuseShuffleIndices = {}, | |
| ArrayRef<unsigned> ReorderIndices = {}, | |
| unsigned InterleaveFactor = 0) { | |
| TreeEntry::EntryState EntryState = | |
| Bundle ? TreeEntry::Vectorize : TreeEntry::NeedToGather; | |
| TreeEntry *E = newTreeEntry(VL, EntryState, Bundle, S, UserTreeIdx, | |
| ReuseShuffleIndices, ReorderIndices); | |
| if (E && InterleaveFactor > 0) | |
| E->setInterleave(InterleaveFactor); | |
| return E; | |
| } | |
| TreeEntry *newTreeEntry(ArrayRef<Value *> VL, | |
| TreeEntry::EntryState EntryState, | |
| ScheduleBundle &Bundle, const InstructionsState &S, | |
| const EdgeInfo &UserTreeIdx, | |
| ArrayRef<int> ReuseShuffleIndices = {}, | |
| ArrayRef<unsigned> ReorderIndices = {}) { | |
| assert(((!Bundle && (EntryState == TreeEntry::NeedToGather || | |
| EntryState == TreeEntry::SplitVectorize)) || | |
| (Bundle && EntryState != TreeEntry::NeedToGather && | |
| EntryState != TreeEntry::SplitVectorize)) && | |
| "Need to vectorize gather entry?"); | |
| // Gathered loads still gathered? Do not create entry, use the original one. | |
| if (GatheredLoadsEntriesFirst.has_value() && | |
| EntryState == TreeEntry::NeedToGather && S && | |
| S.getOpcode() == Instruction::Load && UserTreeIdx.EdgeIdx == UINT_MAX && | |
| !UserTreeIdx.UserTE) | |
| return nullptr; | |
| VectorizableTree.push_back(std::make_unique<TreeEntry>(VectorizableTree)); | |
| TreeEntry *Last = VectorizableTree.back().get(); | |
| Last->Idx = VectorizableTree.size() - 1; | |
| Last->State = EntryState; | |
| if (UserTreeIdx.UserTE) | |
| OperandsToTreeEntry.try_emplace( | |
| std::make_pair(UserTreeIdx.UserTE, UserTreeIdx.EdgeIdx), Last); | |
| Last->ReuseShuffleIndices.append(ReuseShuffleIndices.begin(), | |
| ReuseShuffleIndices.end()); | |
| if (ReorderIndices.empty()) { | |
| Last->Scalars.assign(VL.begin(), VL.end()); | |
| if (S) | |
| Last->setOperations(S); | |
| } else { | |
| // Reorder scalars and build final mask. | |
| Last->Scalars.assign(VL.size(), nullptr); | |
| transform(ReorderIndices, Last->Scalars.begin(), | |
| [VL](unsigned Idx) -> Value * { | |
| if (Idx >= VL.size()) | |
| return UndefValue::get(VL.front()->getType()); | |
| return VL[Idx]; | |
| }); | |
| InstructionsState S = getSameOpcode(Last->Scalars, *TLI); | |
| if (S) | |
| Last->setOperations(S); | |
| Last->ReorderIndices.append(ReorderIndices.begin(), ReorderIndices.end()); | |
| } | |
| if (EntryState == TreeEntry::SplitVectorize) { | |
| assert(S && "Split nodes must have operations."); | |
| Last->setOperations(S); | |
| SmallPtrSet<Value *, 4> Processed; | |
| for (Instruction *I : make_isa_range<Instruction>(VL)) { | |
| auto It = ScalarsInSplitNodes.find(I); | |
| if (It == ScalarsInSplitNodes.end()) { | |
| ScalarsInSplitNodes.try_emplace(I).first->getSecond().push_back(Last); | |
| (void)Processed.insert(I); | |
| } else if (Processed.insert(I).second) { | |
| assert(!is_contained(It->getSecond(), Last) && | |
| "Value already associated with the node."); | |
| It->getSecond().push_back(Last); | |
| } | |
| } | |
| } else if (!Last->isGather()) { | |
| if (isa<PHINode>(S.getMainOp()) || | |
| isVectorLikeInstWithConstOps(S.getMainOp()) || | |
| (!S.areInstructionsWithCopyableElements() && | |
| doesNotNeedToSchedule(VL)) || | |
| all_of(VL, [&](Value *V) { return S.isNonSchedulable(V); })) | |
| Last->setDoesNotNeedToSchedule(); | |
| SmallPtrSet<Value *, 4> Processed; | |
| for (Value *V : make_filter_range( | |
| VL, [](Value *V) { return !isa<PoisonValue>(V); })) { | |
| if (S.isCopyableElement(V)) { | |
| Last->addCopyableElement(V); | |
| continue; | |
| } | |
| auto It = ScalarToTreeEntries.find(V); | |
| if (It == ScalarToTreeEntries.end()) { | |
| ScalarToTreeEntries.try_emplace(V).first->getSecond().push_back(Last); | |
| (void)Processed.insert(V); | |
| } else if (Processed.insert(V).second) { | |
| assert(!is_contained(It->getSecond(), Last) && | |
| "Value already associated with the node."); | |
| It->getSecond().push_back(Last); | |
| } | |
| } | |
| // Update the scheduler bundle to point to this TreeEntry. | |
| assert((!Bundle.getBundle().empty() || Last->doesNotNeedToSchedule()) && | |
| "Bundle and VL out of sync"); | |
| if (!Bundle.getBundle().empty()) { | |
| #if !defined(NDEBUG) || defined(EXPENSIVE_CHECKS) | |
| auto *BundleMember = Bundle.getBundle().begin(); | |
| SmallPtrSet<Value *, 4> Processed; | |
| for (Value *V : VL) { | |
| if (S.isNonSchedulable(V) || !Processed.insert(V).second) | |
| continue; | |
| ++BundleMember; | |
| } | |
| assert(BundleMember == Bundle.getBundle().end() && | |
| "Bundle and VL out of sync"); | |
| #endif | |
| Bundle.setTreeEntry(Last); | |
| } | |
| } else { | |
| // Build a map for gathered scalars to the nodes where they are used. | |
| bool AllConstsOrCasts = true; | |
| for (Value *V : VL) { | |
| if (S && S.areInstructionsWithCopyableElements() && | |
| S.isCopyableElement(V)) | |
| Last->addCopyableElement(V); | |
| if (!isConstant(V)) { | |
| auto *I = dyn_cast<CastInst>(V); | |
| AllConstsOrCasts &= I && I->getType()->isIntegerTy(); | |
| if (UserTreeIdx.EdgeIdx != UINT_MAX || !UserTreeIdx.UserTE || | |
| !UserTreeIdx.UserTE->isGather()) | |
| ValueToGatherNodes.try_emplace(V).first->getSecond().insert(Last); | |
| } | |
| } | |
| if (AllConstsOrCasts) | |
| CastMaxMinBWSizes = | |
| std::make_pair(std::numeric_limits<unsigned>::max(), 1); | |
| MustGather.insert_range(VL); | |
| } | |
| if (UserTreeIdx.UserTE) | |
| Last->UserTreeIndex = UserTreeIdx; | |
| return Last; | |
| } | |
| /// -- Vectorization State -- | |
| /// Holds all of the tree entries. | |
| TreeEntry::VecTreeTy VectorizableTree; | |
| #ifndef NDEBUG | |
| /// Debug printer. | |
| LLVM_DUMP_METHOD void dumpVectorizableTree() const { | |
| for (unsigned Id = 0, IdE = VectorizableTree.size(); Id != IdE; ++Id) { | |
| VectorizableTree[Id]->dump(); | |
| if (TransformedToGatherNodes.contains(VectorizableTree[Id].get())) | |
| dbgs() << "[[TRANSFORMED TO GATHER]]"; | |
| else if (DeletedNodes.contains(VectorizableTree[Id].get())) | |
| dbgs() << "[[DELETED NODE]]"; | |
| dbgs() << "\n"; | |
| } | |
| } | |
| #endif | |
| /// Get list of vector entries, associated with the value \p V. | |
| ArrayRef<TreeEntry *> getTreeEntries(const Value *V) const { | |
| assert(V && "V cannot be nullptr."); | |
| auto It = ScalarToTreeEntries.find(V); | |
| if (It == ScalarToTreeEntries.end()) | |
| return {}; | |
| return It->getSecond(); | |
| } | |
| /// Get list of split vector entries, associated with the value \p V. | |
| ArrayRef<TreeEntry *> getSplitTreeEntries(Value *V) const { | |
| assert(V && "V cannot be nullptr."); | |
| auto It = ScalarsInSplitNodes.find(V); | |
| if (It == ScalarsInSplitNodes.end()) | |
| return {}; | |
| return It->getSecond(); | |
| } | |
| /// Returns first vector node for value \p V, matching values \p VL. | |
| TreeEntry *getSameValuesTreeEntry(Value *V, ArrayRef<Value *> VL, | |
| bool SameVF = false) const { | |
| assert(V && "V cannot be nullptr."); | |
| for (TreeEntry *TE : ScalarToTreeEntries.lookup(V)) | |
| if ((!SameVF || TE->getVectorFactor() == VL.size()) && TE->isSame(VL)) | |
| return TE; | |
| return nullptr; | |
| } | |
| /// Contains all the outputs of legality analysis for a list of values to | |
| /// vectorize. | |
| class ScalarsVectorizationLegality { | |
| InstructionsState S; | |
| bool IsLegal; | |
| bool TryToFindDuplicates; | |
| bool TrySplitVectorize; | |
| public: | |
| ScalarsVectorizationLegality(InstructionsState S, bool IsLegal, | |
| bool TryToFindDuplicates = true, | |
| bool TrySplitVectorize = false) | |
| : S(S), IsLegal(IsLegal), TryToFindDuplicates(TryToFindDuplicates), | |
| TrySplitVectorize(TrySplitVectorize) { | |
| assert((!IsLegal || (S.valid() && TryToFindDuplicates)) && | |
| "Inconsistent state"); | |
| } | |
| const InstructionsState &getInstructionsState() const { return S; }; | |
| bool isLegal() const { return IsLegal; } | |
| bool tryToFindDuplicates() const { return TryToFindDuplicates; } | |
| bool trySplitVectorize() const { return TrySplitVectorize; } | |
| }; | |
| /// Checks if the specified list of the instructions/values can be vectorized | |
| /// in general. | |
| ScalarsVectorizationLegality | |
| getScalarsVectorizationLegality(ArrayRef<Value *> VL, unsigned Depth, | |
| const EdgeInfo &UserTreeIdx) const; | |
| /// Checks if the specified list of the instructions/values can be vectorized | |
| /// and fills required data before actual scheduling of the instructions. | |
| TreeEntry::EntryState getScalarsVectorizationState( | |
| const InstructionsState &S, ArrayRef<Value *> VL, | |
| bool IsScatterVectorizeUserTE, OrdersType &CurrentOrder, | |
| SmallVectorImpl<Value *> &PointerOps, StridedPtrInfo &SPtrInfo, | |
| SmallVectorImpl<int> &ReuseShuffleIndices); | |
| /// Maps a specific scalar to its tree entry(ies). | |
| SmallDenseMap<Value *, SmallVector<TreeEntry *>> ScalarToTreeEntries; | |
| /// List of deleted non-profitable nodes. | |
| SmallPtrSet<const TreeEntry *, 8> DeletedNodes; | |
| /// List of nodes, transformed to gathered, with their conservative | |
| /// gather/buildvector cost estimation. | |
| SmallDenseMap<const TreeEntry *, InstructionCost> TransformedToGatherNodes; | |
| /// Maps the operand index and entry to the corresponding tree entry. | |
| SmallDenseMap<std::pair<const TreeEntry *, unsigned>, TreeEntry *> | |
| OperandsToTreeEntry; | |
| /// Scalars, used in split vectorize nodes. | |
| SmallDenseMap<Value *, SmallVector<TreeEntry *>> ScalarsInSplitNodes; | |
| /// Maps a value to the proposed vectorizable size. | |
| SmallDenseMap<Value *, unsigned> InstrElementSize; | |
| /// A list of scalars that we found that we need to keep as scalars. | |
| ValueSet MustGather; | |
| /// Maps each peeled reassociated scalar to owning entries. Keeps them | |
| /// treated as vectorized while an owner is live. | |
| SmallDenseMap<const Value *, SmallVector<const TreeEntry *>> | |
| ReassocScalarToTreeEntries; | |
| /// Peeled reassociated scalars that must survive erasure: claimed by a | |
| /// gather node, listed in some tree entry's scalars, or feeding another | |
| /// kept scalar. | |
| SmallPtrSet<const Value *, 8> KeptReassocScalars; | |
| /// A set of first non-schedulable values. | |
| ValueSet NonScheduledFirst; | |
| /// A map between the vectorized entries and the last instructions in the | |
| /// bundles. The bundles are built in use order, not in the def order of the | |
| /// instructions. So, we cannot rely directly on the last instruction in the | |
| /// bundle being the last instruction in the program order during | |
| /// vectorization process since the basic blocks are affected, need to | |
| /// pre-gather them before. | |
| SmallDenseMap<const TreeEntry *, WeakTrackingVH> EntryToLastInstruction; | |
| /// Keeps the mapping between the last instructions and their insertion | |
| /// points, which is an instruction-after-the-last-instruction. | |
| SmallDenseMap<const Instruction *, Instruction *> LastInstructionToPos; | |
| /// List of gather nodes, depending on other gather/vector nodes, which should | |
| /// be emitted after the vector instruction emission process to correctly | |
| /// handle order of the vector instructions and shuffles. | |
| SetVector<const TreeEntry *> PostponedGathers; | |
| using ValueToGatherNodesMap = | |
| DenseMap<Value *, SmallSetVector<const TreeEntry *, 4>>; | |
| ValueToGatherNodesMap ValueToGatherNodes; | |
| SmallDenseMap<TreeEntry *, StridedPtrInfo> TreeEntryToStridedPtrInfoMap; | |
| /// A list of the load entries (node indices), which can be vectorized using | |
| /// strided or masked gather approach, but attempted to be represented as | |
| /// contiguous loads. | |
| SetVector<unsigned> LoadEntriesToVectorize; | |
| /// true if graph nodes transforming mode is on. | |
| bool IsGraphTransformMode = false; | |
| /// The index of the first gathered load entry in the VectorizeTree. | |
| std::optional<unsigned> GatheredLoadsEntriesFirst; | |
| /// Root entries of the subtrees built for the splat gather nodes' unique | |
| /// scalars. They have no users in the tree and must be emitted explicitly | |
| /// before the root node. | |
| SmallVector<TreeEntry *> SplatGatheredScalarsRoots; | |
| /// Checks if any of \p Entries is an internal gather of a splat gather | |
| /// subtree. | |
| bool hasSplatSubtreeGather( | |
| ArrayRef<SmallVector<const TreeEntry *>> Entries) const { | |
| if (SplatGatheredScalarsRoots.empty()) | |
| return false; | |
| return any_of(Entries, [&](ArrayRef<const TreeEntry *> TEs) { | |
| return any_of(TEs, [&](const TreeEntry *TE) { | |
| if (!TE->isGather()) | |
| return false; | |
| while (TE->UserTreeIndex) | |
| TE = TE->UserTreeIndex.UserTE; | |
| return is_contained(SplatGatheredScalarsRoots, TE); | |
| }); | |
| }); | |
| } | |
| /// Number of tree entries added while building the splat gather subtrees. | |
| /// The subtrees are auxiliary and must not inflate the tree size recorded | |
| /// for failed store chain attempts. | |
| unsigned NumCanonicalSplatSubtreeEntries = 0; | |
| /// Maps compress entries to their mask data for the final codegen. | |
| SmallDenseMap<const TreeEntry *, | |
| std::tuple<SmallVector<int>, VectorType *, unsigned, bool>> | |
| CompressEntryToData; | |
| /// The loop nest, used to check if only a single loop nest is vectorized, not | |
| /// multiple, to avoid side-effects from the loop-aware cost model. | |
| SmallVector<const Loop *> CurrentLoopNest; | |
| /// Per-depth SCEVs trip counts at every loop level where the tree builder has | |
| /// joined diverging sibling loops. | |
| SmallVector<const SCEV *> MergedLoopBTCs; | |
| /// Maps the loops to their loop nests. | |
| SmallDenseMap<const Loop *, SmallVector<const Loop *>> LoopToLoopNest; | |
| /// Per-loop cache of nest scale factors: the product of trip counts of the | |
| /// loop and all of its ancestors. Shared by getLoopNestScale() and (via it) | |
| /// by getScaleToLoopIterations() and getGatherNodeEffectiveScale(). | |
| SmallDenseMap<const Loop *, uint64_t> LoopNestScaleCache; | |
| /// This POD struct describes one external user in the vectorized tree. | |
| struct ExternalUser { | |
| ExternalUser(Value *S, llvm::User *U, const TreeEntry &E, unsigned L) | |
| : Scalar(S), User(U), E(E), Lane(L) {} | |
| /// Which scalar in our function. | |
| Value *Scalar = nullptr; | |
| /// Which user that uses the scalar. | |
| llvm::User *User = nullptr; | |
| /// Vector node, the value is part of. | |
| const TreeEntry &E; | |
| /// Which lane does the scalar belong to. | |
| unsigned Lane; | |
| }; | |
| using UserList = SmallVector<ExternalUser, 16>; | |
| /// Checks if two instructions may access the same memory. | |
| /// | |
| /// \p Loc1 is the location of \p Inst1. It is passed explicitly because it | |
| /// is invariant in the calling loop. | |
| bool isAliased(const MemoryLocation &Loc1, Instruction *Inst1, | |
| Instruction *Inst2) { | |
| assert(Loc1.Ptr && isSimple(Inst1) && "Expected simple first instruction."); | |
| // First check if the result is already in the cache. | |
| AliasCacheKey Key = std::make_pair(Inst1, Inst2); | |
| auto Res = AliasCache.try_emplace(Key); | |
| if (!Res.second) | |
| return Res.first->second; | |
| bool Aliased = isModOrRefSet(BatchAA->getModRefInfo(Inst2, Loc1)); | |
| // Store the result in the cache. | |
| Res.first->getSecond() = Aliased; | |
| return Aliased; | |
| } | |
| /// Returns true if the may-alias dependency between simple load/store | |
| /// instructions \p Inst1 and \p Inst2 could be disambiguated by a runtime | |
| /// alias check. | |
| bool isRuntimeCheckableAliasPair(Instruction *Inst1, Instruction *Inst2); | |
| /// Records the (distinct base object) pair behind the may-alias dependency | |
| /// of \p Inst1 and \p Inst2 as a runtime alias check guarding the region in | |
| /// block \p BB. Returns true if the pair was recorded. | |
| bool recordRuntimeAliasCheck(BasicBlock *BB, Instruction *Inst1, | |
| Instruction *Inst2); | |
| /// Emits the collected runtime alias checks and versions the affected block, | |
| /// duplicating its body into a scalar fallback guarded by the checks. | |
| void versionBlocksForRuntimeChecks(); | |
| /// Builds the i1 value that is true when any pair of checked base objects | |
| /// overlaps at runtime. The base address bounds are materialized from their | |
| /// SCEVs with \p Exp. | |
| Value *emitRuntimeAliasCheck(IRBuilderBase &Builder, SCEVExpander &Exp); | |
| /// Data to model and emit the runtime alias checks. | |
| struct RuntimeAliasCheckInfo { | |
| /// The block whose body is guarded by the checks. Exactly one block is | |
| /// supported per attempt. | |
| BasicBlock *BB = nullptr; | |
| /// Pairs of base objects that must be proven disjoint. | |
| SmallSetVector<std::pair<const Value *, const Value *>, 4> BasePairs; | |
| /// Accessed address range [Low, High) for each involved base object. | |
| SmallMapVector<const Value *, std::pair<const SCEV *, const SCEV *>, 4> | |
| Bounds; | |
| void clear() { | |
| BB = nullptr; | |
| BasePairs.clear(); | |
| Bounds.clear(); | |
| } | |
| }; | |
| /// When true, scheduling drops may-alias memory dependencies between | |
| /// distinct, range-checkable base objects and records them as runtime alias | |
| /// checks instead. | |
| bool TryRuntimeAliasChecks = false; | |
| /// When true, the stored values of the root store node are split into | |
| /// halves, which are vectorized as separate subtrees. | |
| bool SplitStoredValues = false; | |
| /// Runtime alias checks collected during the last optimistic buildTree(). | |
| RuntimeAliasCheckInfo RTChecks; | |
| /// Base-object pairs already proven disjoint by the block's runtime alias | |
| /// check. | |
| SmallDenseMap<BasicBlock *, | |
| SmallDenseSet<std::pair<const Value *, const Value *>, 4>, 2> | |
| VersionedBlockCheckedPairs; | |
| /// Scalar fallback blocks. | |
| SmallPtrSet<BasicBlock *, 4> ScalarFallbackBlocks; | |
| /// Blocks for which a runtime-checks versioning attempt was made | |
| /// and did not produce a profitable versioning. | |
| SmallPtrSet<BasicBlock *, 8> FailedRuntimeChecksBlocks; | |
| /// Returns true if a may-alias dependency between the simple load/store | |
| /// instructions \p Inst1 and \p Inst2 in block \p BB is already covered by a | |
| /// runtime alias check emitted for \p BB by a previous versioning. | |
| bool isCoveredByExistingVersionCheck(BasicBlock *BB, Instruction *Inst1, | |
| Instruction *Inst2) const; | |
| /// True, if a may-alias dependency between distinct, range-checkable base | |
| /// objects is observed (whether or not it was dropped). | |
| bool HasRuntimeCheckableBlockers = false; | |
| /// True, if a kept may-alias dependency is not runtime-checkable (call or a | |
| /// non-simple memaccess). | |
| bool HasNonCheckableMemBlocker = false; | |
| /// Runtime checks are validated and bounded the collected checks. | |
| bool RTChecksFinalized = false; | |
| /// Set when a block was versioned with runtime alias checks, which changes | |
| /// the CFG. Used to drop CFG-analysis preservation for the run. | |
| bool CFGChanged = false; | |
| /// Guarded block body (non-PHI, non-alloca, non-terminator) in original | |
| /// source order. | |
| SmallVector<Instruction *> RTOrigBodyOrder; | |
| using AliasCacheKey = std::pair<Instruction *, Instruction *>; | |
| /// Cache for alias results. | |
| /// TODO: consider moving this to the AliasAnalysis itself. | |
| SmallDenseMap<AliasCacheKey, bool> AliasCache; | |
| AAResults *AA; | |
| // Cache for pointerMayBeCaptured calls inside AA. Vectorizing a tree can | |
| // create new captures (e.g. a pointer inserted into a vector of pointers | |
| // feeding a masked gather), so this is reset after each vectorized tree. | |
| std::optional<BatchAAResults> BatchAA; | |
| /// Temporary store for deleted instructions. Instructions will be deleted | |
| /// eventually when the BoUpSLP is destructed. The deferral is required to | |
| /// ensure that there are no incorrect collisions in the AliasCache, which | |
| /// can happen if a new instruction is allocated at the same address as a | |
| /// previously deleted instruction. | |
| DenseSet<Instruction *> DeletedInstructions; | |
| /// The cached analyses results, cleared after each successful | |
| /// vectorizeTree(). | |
| mutable SLPAnalysisCache Cache; | |
| /// Set of the instruction, being analyzed already for reductions. | |
| SmallPtrSet<Instruction *, 16> AnalyzedReductionsRoots; | |
| /// Set of hashes for the list of reduction values already being analyzed. | |
| DenseSet<size_t> AnalyzedReductionVals; | |
| /// Set of hashes for the bundles, rejected as non-vectorizable. | |
| SmallDenseSet<size_t, 8> AnalyzedBundles; | |
| /// Set of the values, which were a part of the analyzed vector nodes. | |
| SmallPtrSet<const Value *, 32> AnalyzedScalars; | |
| /// Cache of the number of parts for the types and the parts limit. | |
| mutable SmallDenseMap<std::tuple<Type *, Type *, unsigned>, unsigned> | |
| NumberOfPartsCache; | |
| /// Cache of the number of regs for the types and the parts limit. | |
| mutable SmallDenseMap<std::tuple<Type *, Type *, unsigned>, unsigned> | |
| NumberOfRegsCache; | |
| /// Values already analyzed for minimal bitwidth and found to be | |
| /// non-profitable, mapped to the size of the tree used for the analysis. | |
| SmallDenseMap<Value *, unsigned> AnalyzedMinBWVals; | |
| /// A list of values that need to extracted out of the tree. | |
| /// This list holds pairs of (Internal Scalar : External User). External User | |
| /// can be nullptr, it means that this Internal Scalar will be used later, | |
| /// after vectorization. | |
| UserList ExternalUses; | |
| /// A list of GEPs which can be reaplced by scalar GEPs instead of | |
| /// extractelement instructions. | |
| SmallPtrSet<Value *, 4> ExternalUsesAsOriginalScalar; | |
| /// A list of scalar to be extracted without specific user necause of too many | |
| /// uses. | |
| SmallPtrSet<Value *, 4> ExternalUsesWithNonUsers; | |
| /// Externally used values and replacements emitted for the external uses | |
| /// without users, consumed after the tree vectorization; must not be | |
| /// collected as dead operands of the erased scalars. | |
| SmallPtrSet<Value *, 4> ExternalUseReplacements; | |
| /// Values used only by @llvm.assume calls. | |
| SmallPtrSet<const Value *, 32> EphValues; | |
| /// Holds all of the instructions that we gathered, shuffle instructions and | |
| /// extractelements. | |
| SetVector<Instruction *> GatherShuffleExtractSeq; | |
| /// A list of blocks that we are going to CSE. | |
| DenseSet<BasicBlock *> CSEBlocks; | |
| /// List of hashes of vector of loads, which are known to be non vectorizable. | |
| DenseSet<size_t> ListOfKnonwnNonVectorizableLoads; | |
| /// Represents a scheduling entity, either ScheduleData, ScheduleCopyableData | |
| /// or ScheduleBundle. ScheduleData used to gather dependecies for a single | |
| /// instructions, while ScheduleBundle represents a batch of instructions, | |
| /// going to be groupped together. ScheduleCopyableData models extra user for | |
| /// "copyable" instructions. | |
| class ScheduleEntity { | |
| friend class ScheduleBundle; | |
| friend class ScheduleData; | |
| friend class ScheduleCopyableData; | |
| protected: | |
| enum class Kind { ScheduleData, ScheduleBundle, ScheduleCopyableData }; | |
| Kind getKind() const { return K; } | |
| ScheduleEntity(Kind K) : K(K) {} | |
| private: | |
| /// Used for getting a "good" final ordering of instructions. | |
| int SchedulingPriority = 0; | |
| /// True if this instruction (or bundle) is scheduled (or considered as | |
| /// scheduled in the dry-run). | |
| bool IsScheduled = false; | |
| /// The kind of the ScheduleEntity. | |
| const Kind K = Kind::ScheduleData; | |
| public: | |
| ScheduleEntity() = delete; | |
| /// Gets/sets the scheduling priority. | |
| void setSchedulingPriority(int Priority) { SchedulingPriority = Priority; } | |
| int getSchedulingPriority() const { return SchedulingPriority; } | |
| bool isReady() const { | |
| if (const auto *SD = dyn_cast<ScheduleData>(this)) | |
| return SD->isReady(); | |
| if (const auto *CD = dyn_cast<ScheduleCopyableData>(this)) | |
| return CD->isReady(); | |
| return cast<ScheduleBundle>(this)->isReady(); | |
| } | |
| /// Returns true if the dependency information has been calculated. | |
| /// Note that depenendency validity can vary between instructions within | |
| /// a single bundle. | |
| bool hasValidDependencies() const { | |
| if (const auto *SD = dyn_cast<ScheduleData>(this)) | |
| return SD->hasValidDependencies(); | |
| if (const auto *CD = dyn_cast<ScheduleCopyableData>(this)) | |
| return CD->hasValidDependencies(); | |
| return cast<ScheduleBundle>(this)->hasValidDependencies(); | |
| } | |
| /// Gets the number of unscheduled dependencies. | |
| int getUnscheduledDeps() const { | |
| if (const auto *SD = dyn_cast<ScheduleData>(this)) | |
| return SD->getUnscheduledDeps(); | |
| if (const auto *CD = dyn_cast<ScheduleCopyableData>(this)) | |
| return CD->getUnscheduledDeps(); | |
| return cast<ScheduleBundle>(this)->unscheduledDepsInBundle(); | |
| } | |
| /// Increments the number of unscheduled dependencies. | |
| int incrementUnscheduledDeps(int Incr) { | |
| if (auto *SD = dyn_cast<ScheduleData>(this)) | |
| return SD->incrementUnscheduledDeps(Incr); | |
| return cast<ScheduleCopyableData>(this)->incrementUnscheduledDeps(Incr); | |
| } | |
| /// Gets the number of dependencies. | |
| int getDependencies() const { | |
| if (const auto *SD = dyn_cast<ScheduleData>(this)) | |
| return SD->getDependencies(); | |
| return cast<ScheduleCopyableData>(this)->getDependencies(); | |
| } | |
| /// Gets the instruction. | |
| Instruction *getInst() const { | |
| if (const auto *SD = dyn_cast<ScheduleData>(this)) | |
| return SD->getInst(); | |
| return cast<ScheduleCopyableData>(this)->getInst(); | |
| } | |
| /// Gets/sets if the bundle is scheduled. | |
| bool isScheduled() const { return IsScheduled; } | |
| void setScheduled(bool Scheduled) { IsScheduled = Scheduled; } | |
| static bool classof(const ScheduleEntity *) { return true; } | |
| #if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP) | |
| void dump(raw_ostream &OS) const { | |
| if (const auto *SD = dyn_cast<ScheduleData>(this)) | |
| return SD->dump(OS); | |
| if (const auto *CD = dyn_cast<ScheduleCopyableData>(this)) | |
| return CD->dump(OS); | |
| return cast<ScheduleBundle>(this)->dump(OS); | |
| } | |
| LLVM_DUMP_METHOD void dump() const { | |
| dump(dbgs()); | |
| dbgs() << '\n'; | |
| } | |
| #endif // if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP) | |
| }; | |
| #if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP) | |
| friend inline raw_ostream &operator<<(raw_ostream &OS, | |
| const BoUpSLP::ScheduleEntity &SE) { | |
| SE.dump(OS); | |
| return OS; | |
| } | |
| #endif | |
| /// Contains all scheduling relevant data for an instruction. | |
| /// A ScheduleData either represents a single instruction or a member of an | |
| /// instruction bundle (= a group of instructions which is combined into a | |
| /// vector instruction). | |
| class ScheduleData final : public ScheduleEntity { | |
| public: | |
| // The initial value for the dependency counters. It means that the | |
| // dependencies are not calculated yet. | |
| enum { InvalidDeps = -1 }; | |
| ScheduleData() : ScheduleEntity(Kind::ScheduleData) {} | |
| static bool classof(const ScheduleEntity *Entity) { | |
| return Entity->getKind() == Kind::ScheduleData; | |
| } | |
| void init(int BlockSchedulingRegionID, Instruction *I) { | |
| NextLoadStore = nullptr; | |
| IsScheduled = false; | |
| SchedulingRegionID = BlockSchedulingRegionID; | |
| clearDependencies(); | |
| Inst = I; | |
| } | |
| /// Verify basic self consistency properties | |
| void verify() { | |
| if (hasValidDependencies()) { | |
| assert(UnscheduledDeps <= Dependencies && "invariant"); | |
| } else { | |
| assert(UnscheduledDeps == Dependencies && "invariant"); | |
| } | |
| if (IsScheduled) { | |
| assert(hasValidDependencies() && UnscheduledDeps == 0 && | |
| "unexpected scheduled state"); | |
| } | |
| } | |
| /// Returns true if the dependency information has been calculated. | |
| /// Note that depenendency validity can vary between instructions within | |
| /// a single bundle. | |
| bool hasValidDependencies() const { return Dependencies != InvalidDeps; } | |
| /// Returns true if it is ready for scheduling, i.e. it has no more | |
| /// unscheduled depending instructions/bundles. | |
| bool isReady() const { return UnscheduledDeps == 0 && !IsScheduled; } | |
| /// Modifies the number of unscheduled dependencies for this instruction, | |
| /// and returns the number of remaining dependencies for the containing | |
| /// bundle. | |
| int incrementUnscheduledDeps(int Incr) { | |
| assert(hasValidDependencies() && | |
| "increment of unscheduled deps would be meaningless"); | |
| UnscheduledDeps += Incr; | |
| assert(UnscheduledDeps >= 0 && | |
| "Expected valid number of unscheduled deps"); | |
| return UnscheduledDeps; | |
| } | |
| /// Sets the number of unscheduled dependencies to the number of | |
| /// dependencies. | |
| void resetUnscheduledDeps() { UnscheduledDeps = Dependencies; } | |
| /// Clears all dependency information. | |
| void clearDependencies() { | |
| clearDirectDependencies(); | |
| MemoryDependencies.clear(); | |
| ControlDependencies.clear(); | |
| } | |
| /// Clears all direct dependencies only, except for control and memory | |
| /// dependencies. | |
| /// Required for copyable elements to correctly handle control/memory deps | |
| /// and avoid extra reclaculation of such deps. | |
| void clearDirectDependencies() { | |
| Dependencies = InvalidDeps; | |
| resetUnscheduledDeps(); | |
| IsScheduled = false; | |
| } | |
| /// Gets the number of unscheduled dependencies. | |
| int getUnscheduledDeps() const { return UnscheduledDeps; } | |
| /// Gets the number of dependencies. | |
| int getDependencies() const { return Dependencies; } | |
| /// Initializes the number of dependencies. | |
| void initDependencies() { Dependencies = 0; } | |
| /// Increments the number of dependencies. | |
| void incDependencies() { Dependencies++; } | |
| /// Gets scheduling region ID. | |
| int getSchedulingRegionID() const { return SchedulingRegionID; } | |
| /// Gets the instruction. | |
| Instruction *getInst() const { return Inst; } | |
| /// Gets the list of memory dependencies. | |
| ArrayRef<ScheduleData *> getMemoryDependencies() const { | |
| return MemoryDependencies; | |
| } | |
| /// Adds a memory dependency. | |
| void addMemoryDependency(ScheduleData *Dep) { | |
| MemoryDependencies.push_back(Dep); | |
| } | |
| /// Gets the list of control dependencies. | |
| ArrayRef<ScheduleData *> getControlDependencies() const { | |
| return ControlDependencies; | |
| } | |
| /// Adds a control dependency. | |
| void addControlDependency(ScheduleData *Dep) { | |
| ControlDependencies.push_back(Dep); | |
| } | |
| /// Gets/sets the next load/store instruction in the block. | |
| ScheduleData *getNextLoadStore() const { return NextLoadStore; } | |
| void setNextLoadStore(ScheduleData *Next) { NextLoadStore = Next; } | |
| void dump(raw_ostream &OS) const { OS << *Inst; } | |
| LLVM_DUMP_METHOD void dump() const { | |
| dump(dbgs()); | |
| dbgs() << '\n'; | |
| } | |
| private: | |
| Instruction *Inst = nullptr; | |
| /// Single linked list of all memory instructions (e.g. load, store, call) | |
| /// in the block - until the end of the scheduling region. | |
| ScheduleData *NextLoadStore = nullptr; | |
| /// The dependent memory instructions. | |
| /// This list is derived on demand in calculateDependencies(). | |
| SmallVector<ScheduleData *> MemoryDependencies; | |
| /// List of instructions which this instruction could be control dependent | |
| /// on. Allowing such nodes to be scheduled below this one could introduce | |
| /// a runtime fault which didn't exist in the original program. | |
| /// ex: this is a load or udiv following a readonly call which inf loops | |
| SmallVector<ScheduleData *> ControlDependencies; | |
| /// This ScheduleData is in the current scheduling region if this matches | |
| /// the current SchedulingRegionID of BlockScheduling. | |
| int SchedulingRegionID = 0; | |
| /// The number of dependencies. Constitutes of the number of users of the | |
| /// instruction plus the number of dependent memory instructions (if any). | |
| /// This value is calculated on demand. | |
| /// If InvalidDeps, the number of dependencies is not calculated yet. | |
| int Dependencies = InvalidDeps; | |
| /// The number of dependencies minus the number of dependencies of scheduled | |
| /// instructions. As soon as this is zero, the instruction/bundle gets ready | |
| /// for scheduling. | |
| /// Note that this is negative as long as Dependencies is not calculated. | |
| int UnscheduledDeps = InvalidDeps; | |
| }; | |
| #ifndef NDEBUG | |
| friend inline raw_ostream &operator<<(raw_ostream &OS, | |
| const BoUpSLP::ScheduleData &SD) { | |
| SD.dump(OS); | |
| return OS; | |
| } | |
| #endif | |
| class ScheduleBundle final : public ScheduleEntity { | |
| /// The schedule data for the instructions in the bundle. | |
| SmallVector<ScheduleEntity *> Bundle; | |
| /// True if this bundle is valid. | |
| bool IsValid = true; | |
| /// The TreeEntry that this instruction corresponds to. | |
| TreeEntry *TE = nullptr; | |
| ScheduleBundle(bool IsValid) | |
| : ScheduleEntity(Kind::ScheduleBundle), IsValid(IsValid) {} | |
| public: | |
| ScheduleBundle() : ScheduleEntity(Kind::ScheduleBundle) {} | |
| static bool classof(const ScheduleEntity *Entity) { | |
| return Entity->getKind() == Kind::ScheduleBundle; | |
| } | |
| /// Verify basic self consistency properties | |
| void verify() const { | |
| for (const ScheduleEntity *SD : Bundle) { | |
| if (SD->hasValidDependencies()) { | |
| assert(SD->getUnscheduledDeps() <= SD->getDependencies() && | |
| "invariant"); | |
| } else { | |
| assert(SD->getUnscheduledDeps() == SD->getDependencies() && | |
| "invariant"); | |
| } | |
| if (isScheduled()) { | |
| assert(SD->hasValidDependencies() && SD->getUnscheduledDeps() == 0 && | |
| "unexpected scheduled state"); | |
| } | |
| } | |
| } | |
| /// Returns the number of unscheduled dependencies in the bundle. | |
| int unscheduledDepsInBundle() const { | |
| assert(*this && "bundle must not be empty"); | |
| int Sum = 0; | |
| for (const ScheduleEntity *BundleMember : Bundle) { | |
| if (BundleMember->getUnscheduledDeps() == ScheduleData::InvalidDeps) | |
| return ScheduleData::InvalidDeps; | |
| Sum += BundleMember->getUnscheduledDeps(); | |
| } | |
| return Sum; | |
| } | |
| /// Returns true if the dependency information has been calculated. | |
| /// Note that depenendency validity can vary between instructions within | |
| /// a single bundle. | |
| bool hasValidDependencies() const { | |
| return all_of(Bundle, [](const ScheduleEntity *SD) { | |
| return SD->hasValidDependencies(); | |
| }); | |
| } | |
| /// Returns true if it is ready for scheduling, i.e. it has no more | |
| /// unscheduled depending instructions/bundles. | |
| bool isReady() const { | |
| assert(*this && "bundle must not be empty"); | |
| return unscheduledDepsInBundle() == 0 && !isScheduled(); | |
| } | |
| /// Returns the bundle of scheduling data, associated with the current | |
| /// instruction. | |
| ArrayRef<ScheduleEntity *> getBundle() { return Bundle; } | |
| ArrayRef<const ScheduleEntity *> getBundle() const { return Bundle; } | |
| /// Adds an instruction to the bundle. | |
| void add(ScheduleEntity *SD) { Bundle.push_back(SD); } | |
| /// Gets/sets the associated tree entry. | |
| void setTreeEntry(TreeEntry *TE) { this->TE = TE; } | |
| TreeEntry *getTreeEntry() const { return TE; } | |
| static ScheduleBundle invalid() { return {false}; } | |
| operator bool() const { return IsValid; } | |
| #ifndef NDEBUG | |
| void dump(raw_ostream &OS) const { | |
| if (!*this) { | |
| OS << "[]"; | |
| return; | |
| } | |
| OS << '['; | |
| interleaveComma(Bundle, OS, [&](const ScheduleEntity *SD) { | |
| if (isa<ScheduleCopyableData>(SD)) | |
| OS << "<Copyable>"; | |
| OS << *SD->getInst(); | |
| }); | |
| OS << ']'; | |
| } | |
| LLVM_DUMP_METHOD void dump() const { | |
| dump(dbgs()); | |
| dbgs() << '\n'; | |
| } | |
| #endif // NDEBUG | |
| }; | |
| #ifndef NDEBUG | |
| friend inline raw_ostream &operator<<(raw_ostream &OS, | |
| const BoUpSLP::ScheduleBundle &Bundle) { | |
| Bundle.dump(OS); | |
| return OS; | |
| } | |
| #endif | |
| /// Contains all scheduling relevant data for the copyable instruction. | |
| /// It models the virtual instructions, supposed to replace the original | |
| /// instructions. E.g., if instruction %0 = load is a part of the bundle [%0, | |
| /// %1], where %1 = add, then the ScheduleCopyableData models virtual | |
| /// instruction %virt = add %0, 0. | |
| class ScheduleCopyableData final : public ScheduleEntity { | |
| /// The source schedule data for the instruction. | |
| Instruction *Inst = nullptr; | |
| /// The edge information for the instruction. | |
| const EdgeInfo EI; | |
| /// This ScheduleData is in the current scheduling region if this matches | |
| /// the current SchedulingRegionID of BlockScheduling. | |
| int SchedulingRegionID = 0; | |
| /// Bundle, this data is part of. | |
| ScheduleBundle &Bundle; | |
| public: | |
| ScheduleCopyableData(int BlockSchedulingRegionID, Instruction *I, | |
| const EdgeInfo &EI, ScheduleBundle &Bundle) | |
| : ScheduleEntity(Kind::ScheduleCopyableData), Inst(I), EI(EI), | |
| SchedulingRegionID(BlockSchedulingRegionID), Bundle(Bundle) {} | |
| static bool classof(const ScheduleEntity *Entity) { | |
| return Entity->getKind() == Kind::ScheduleCopyableData; | |
| } | |
| /// Returns true if the dependency information has been calculated. | |
| /// Note that depenendency validity can vary between instructions within | |
| /// a single bundle. | |
| bool hasValidDependencies() const { | |
| return Dependencies != ScheduleData::InvalidDeps; | |
| } | |
| /// Returns true if it is ready for scheduling, i.e. it has no more | |
| /// unscheduled depending instructions/bundles. | |
| bool isReady() const { return UnscheduledDeps == 0 && !IsScheduled; } | |
| /// Modifies the number of unscheduled dependencies for this instruction, | |
| /// and returns the number of remaining dependencies for the containing | |
| /// bundle. | |
| int incrementUnscheduledDeps(int Incr) { | |
| assert(hasValidDependencies() && | |
| "increment of unscheduled deps would be meaningless"); | |
| UnscheduledDeps += Incr; | |
| assert(UnscheduledDeps >= 0 && "invariant"); | |
| return UnscheduledDeps; | |
| } | |
| /// Sets the number of unscheduled dependencies to the number of | |
| /// dependencies. | |
| void resetUnscheduledDeps() { UnscheduledDeps = Dependencies; } | |
| /// Gets the number of unscheduled dependencies. | |
| int getUnscheduledDeps() const { return UnscheduledDeps; } | |
| /// Gets the number of dependencies. | |
| int getDependencies() const { return Dependencies; } | |
| /// Initializes the number of dependencies. | |
| void initDependencies() { Dependencies = 0; } | |
| /// Increments the number of dependencies. | |
| void incDependencies() { Dependencies++; } | |
| /// Gets scheduling region ID. | |
| int getSchedulingRegionID() const { return SchedulingRegionID; } | |
| /// Gets the instruction. | |
| Instruction *getInst() const { return Inst; } | |
| /// Clears all dependency information. | |
| void clearDependencies() { | |
| Dependencies = ScheduleData::InvalidDeps; | |
| UnscheduledDeps = ScheduleData::InvalidDeps; | |
| IsScheduled = false; | |
| } | |
| /// Gets the edge information. | |
| const EdgeInfo &getEdgeInfo() const { return EI; } | |
| /// Gets the bundle. | |
| ScheduleBundle &getBundle() { return Bundle; } | |
| const ScheduleBundle &getBundle() const { return Bundle; } | |
| #if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP) | |
| void dump(raw_ostream &OS) const { OS << "[Copyable]" << *getInst(); } | |
| LLVM_DUMP_METHOD void dump() const { | |
| dump(dbgs()); | |
| dbgs() << '\n'; | |
| } | |
| #endif // !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP) | |
| private: | |
| /// true, if it has valid dependency information. These nodes always have | |
| /// only single dependency. | |
| int Dependencies = ScheduleData::InvalidDeps; | |
| /// The number of dependencies minus the number of dependencies of scheduled | |
| /// instructions. As soon as this is zero, the instruction/bundle gets ready | |
| /// for scheduling. | |
| /// Note that this is negative as long as Dependencies is not calculated. | |
| int UnscheduledDeps = ScheduleData::InvalidDeps; | |
| }; | |
| #ifndef NDEBUG | |
| friend inline raw_ostream & | |
| operator<<(raw_ostream &OS, const BoUpSLP::ScheduleCopyableData &SD) { | |
| SD.dump(OS); | |
| return OS; | |
| } | |
| #endif | |
| friend struct GraphTraits<BoUpSLP *>; | |
| friend struct DOTGraphTraits<BoUpSLP *>; | |
| friend class ::HorizontalReduction; | |
| /// Contains all scheduling data for a basic block. | |
| /// It does not schedules instructions, which are not memory read/write | |
| /// instructions and their operands are either constants, or arguments, or | |
| /// phis, or instructions from others blocks, or their users are phis or from | |
| /// the other blocks. The resulting vector instructions can be placed at the | |
| /// beginning of the basic block without scheduling (if operands does not need | |
| /// to be scheduled) or at the end of the block (if users are outside of the | |
| /// block). It allows to save some compile time and memory used by the | |
| /// compiler. | |
| /// ScheduleData is assigned for each instruction in between the boundaries of | |
| /// the tree entry, even for those, which are not part of the graph. It is | |
| /// required to correctly follow the dependencies between the instructions and | |
| /// their correct scheduling. The ScheduleData is not allocated for the | |
| /// instructions, which do not require scheduling, like phis, nodes with | |
| /// extractelements/insertelements only or nodes with instructions, with | |
| /// uses/operands outside of the block. | |
| struct BlockScheduling { | |
| BlockScheduling(BasicBlock *BB, int ScheduleRegionSizeLimit) | |
| : BB(BB), ChunkSize(BB->size()), ChunkPos(ChunkSize), | |
| ScheduleRegionSizeLimit(ScheduleRegionSizeLimit) {} | |
| void clear() { | |
| ScheduledBundles.clear(); | |
| ScheduledBundlesList.clear(); | |
| ScheduleCopyableDataMap.clear(); | |
| ScheduleCopyableDataMapByInst.clear(); | |
| ScheduleCopyableDataMapByInstUser.clear(); | |
| ScheduleCopyableDataMapByUsers.clear(); | |
| ReadyInsts.clear(); | |
| RecalcCopyableOperandDeps.clear(); | |
| IgnoredMemDeps.clear(); | |
| ScheduleStart = nullptr; | |
| ScheduleEnd = nullptr; | |
| FirstLoadStoreInRegion = nullptr; | |
| LastLoadStoreInRegion = nullptr; | |
| RegionHasStackSave = false; | |
| RegionHasMayThrow = false; | |
| // Reduce the maximum schedule region size by the size of the | |
| // previous scheduling run. | |
| ScheduleRegionSizeLimit -= ScheduleRegionSize; | |
| if (ScheduleRegionSizeLimit < MinScheduleRegionSize) | |
| ScheduleRegionSizeLimit = MinScheduleRegionSize; | |
| ScheduleRegionSize = 0; | |
| // Make a new scheduling region, i.e. all existing ScheduleData is not | |
| // in the new region yet. | |
| ++SchedulingRegionID; | |
| } | |
| ScheduleData *getScheduleData(Instruction *I) { | |
| if (!I) | |
| return nullptr; | |
| if (BB != I->getParent()) | |
| // Avoid lookup if can't possibly be in map. | |
| return nullptr; | |
| ScheduleData *SD = ScheduleDataMap.lookup(I); | |
| if (SD && isInSchedulingRegion(*SD)) | |
| return SD; | |
| return nullptr; | |
| } | |
| ScheduleData *getScheduleData(Value *V) { | |
| return getScheduleData(dyn_cast<Instruction>(V)); | |
| } | |
| /// Returns the ScheduleCopyableData for the given edge (user tree entry and | |
| /// operand number) and value. | |
| ScheduleCopyableData *getScheduleCopyableData(const EdgeInfo &EI, | |
| const Value *V) const { | |
| if (ScheduleCopyableDataMap.empty()) | |
| return nullptr; | |
| auto It = ScheduleCopyableDataMap.find(std::make_pair(EI, V)); | |
| if (It == ScheduleCopyableDataMap.end()) | |
| return nullptr; | |
| ScheduleCopyableData *SD = It->getSecond().get(); | |
| if (!isInSchedulingRegion(*SD)) | |
| return nullptr; | |
| return SD; | |
| } | |
| /// Returns the ScheduleCopyableData for the given user \p User, operand | |
| /// number and operand \p V. | |
| SmallVector<ScheduleCopyableData *> | |
| getScheduleCopyableData(const Value *User, unsigned OperandIdx, | |
| const Value *V) { | |
| if (ScheduleCopyableDataMapByInstUser.empty()) | |
| return {}; | |
| const auto It = ScheduleCopyableDataMapByInstUser.find( | |
| std::make_pair(std::make_pair(User, OperandIdx), V)); | |
| if (It == ScheduleCopyableDataMapByInstUser.end()) | |
| return {}; | |
| SmallVector<ScheduleCopyableData *> Res; | |
| for (ScheduleCopyableData *SD : It->getSecond()) { | |
| if (isInSchedulingRegion(*SD)) | |
| Res.push_back(SD); | |
| } | |
| return Res; | |
| } | |
| /// Returns true if all operands of the given instruction \p User are | |
| /// replaced by copyable data. | |
| /// \param User The user instruction. | |
| /// \param Op The operand, which might be replaced by the copyable data. | |
| /// \param SLP The SLP tree. | |
| /// \param NumOps The number of operands used. If the instruction uses the | |
| /// same operand several times, check for the first use, then the second, | |
| /// etc. | |
| bool areAllOperandsReplacedByCopyableData(Instruction *User, | |
| Instruction *Op, BoUpSLP &SLP, | |
| unsigned NumOps) const { | |
| assert(NumOps > 0 && "No operands"); | |
| if (ScheduleCopyableDataMap.empty()) | |
| return false; | |
| SmallDenseMap<TreeEntry *, unsigned> PotentiallyReorderedEntriesCount; | |
| ArrayRef<TreeEntry *> Entries = SLP.getTreeEntries(User); | |
| if (Entries.empty()) | |
| return false; | |
| unsigned CurNumOps = 0; | |
| for (const Use &U : make_filter_range( | |
| User->operands(), [&](const Use &U) { return U.get() == Op; })) { | |
| ++CurNumOps; | |
| // Check all tree entries, if they have operands replaced by copyable | |
| // data. | |
| for (TreeEntry *TE : Entries) { | |
| unsigned Inc = 0; | |
| bool IsNonSchedulableWithParentPhiNode = | |
| TE->doesNotNeedToSchedule() && TE->UserTreeIndex && | |
| TE->UserTreeIndex.UserTE->hasState() && | |
| TE->UserTreeIndex.UserTE->State != TreeEntry::SplitVectorize && | |
| TE->UserTreeIndex.UserTE->getOpcode() == Instruction::PHI; | |
| // Count the number of unique phi nodes, which are the parent for | |
| // parent entry, and exit, if all the unique phis are processed. | |
| if (IsNonSchedulableWithParentPhiNode) { | |
| SmallPtrSet<Value *, 4> ParentsUniqueUsers; | |
| const TreeEntry *ParentTE = TE->UserTreeIndex.UserTE; | |
| for (PHINode *PHI : make_isa_range<PHINode>(ParentTE->Scalars)) | |
| if (ParentsUniqueUsers.insert(PHI).second && | |
| is_contained(PHI->incoming_values(), User)) | |
| ++Inc; | |
| } else { | |
| Inc = count(TE->Scalars, User); | |
| } | |
| // Check if the user is commutative. | |
| // The commutatives are handled later, as their operands can be | |
| // reordered. | |
| // Same applies even for non-commutative cmps, because we can invert | |
| // their predicate potentially and, thus, reorder the operands. | |
| bool IsCommutativeUser = | |
| isCommutative(User) && | |
| isCommutableOperand(User, User, U.getOperandNo()); | |
| if (!IsCommutativeUser) { | |
| Instruction *MainOp = TE->getMatchingMainOpOrAltOp(User); | |
| IsCommutativeUser = | |
| isCommutative(MainOp, User) && | |
| isCommutableOperand(MainOp, User, U.getOperandNo()); | |
| } | |
| // The commutative user with the same operands can be safely | |
| // considered as non-commutative, operands reordering does not change | |
| // the semantics. Same for cmps with the same operands: inverting | |
| // the predicate does not change the operand columns in this case. | |
| assert( | |
| (!IsCommutativeUser || | |
| (((isCommutative(User) && isCommutableOperand(User, User, 0) && | |
| isCommutableOperand(User, User, 1)) || | |
| (isCommutative(TE->getMatchingMainOpOrAltOp(User), User) && | |
| isCommutableOperand(TE->getMatchingMainOpOrAltOp(User), User, | |
| 0) && | |
| isCommutableOperand(TE->getMatchingMainOpOrAltOp(User), User, | |
| 1))))) && | |
| "Expected commutative user with 2 first commutable operands"); | |
| bool IsCommutativeWithSameOps = | |
| IsCommutativeUser && User->getOperand(0) == User->getOperand(1); | |
| if ((!IsCommutativeUser || IsCommutativeWithSameOps) && | |
| (!isa<CmpInst>(User) || | |
| User->getOperand(0) == User->getOperand(1))) { | |
| if (CurNumOps != NumOps) | |
| continue; | |
| // A reassociated node flattens the operand chain, so the operand | |
| // may be placed in any operand column rather than at the | |
| // instruction's operand number. | |
| if (TE->hasReassocScalars()) { | |
| bool ReplacedByCopyable = false; | |
| for (auto It = find(TE->Scalars, User); It != TE->Scalars.end(); | |
| It = find(make_range(std::next(It), TE->Scalars.end()), | |
| User)) { | |
| int Lane = std::distance(TE->Scalars.begin(), It); | |
| for (unsigned OpIdx : seq<unsigned>(TE->getNumOperands())) | |
| ReplacedByCopyable |= | |
| TE->getOperand(OpIdx)[Lane] == Op && | |
| getScheduleCopyableData(EdgeInfo(TE, OpIdx), Op); | |
| } | |
| if (ReplacedByCopyable) | |
| continue; | |
| return false; | |
| } | |
| EdgeInfo EI(TE, U.getOperandNo()); | |
| if (getScheduleCopyableData(EI, Op)) | |
| continue; | |
| return false; | |
| } | |
| // Only count the occurrence matching this call's NumOps. | |
| if (CurNumOps != NumOps) | |
| continue; | |
| PotentiallyReorderedEntriesCount.try_emplace(TE, 0) | |
| .first->getSecond() += Inc; | |
| } | |
| } | |
| if (PotentiallyReorderedEntriesCount.empty()) | |
| return true; | |
| // Check the commutative/cmp entries. | |
| for (auto &P : PotentiallyReorderedEntriesCount) { | |
| SmallPtrSet<Value *, 4> ParentsUniqueUsers; | |
| bool IsNonSchedulableWithParentPhiNode = | |
| P.first->doesNotNeedToSchedule() && P.first->UserTreeIndex && | |
| P.first->UserTreeIndex.UserTE->hasState() && | |
| P.first->UserTreeIndex.UserTE->State != TreeEntry::SplitVectorize && | |
| P.first->UserTreeIndex.UserTE->getOpcode() == Instruction::PHI; | |
| auto *It = find(P.first->Scalars, User); | |
| do { | |
| assert(It != P.first->Scalars.end() && | |
| "User is not in the tree entry"); | |
| int Lane = std::distance(P.first->Scalars.begin(), It); | |
| assert(Lane >= 0 && "Lane is not found"); | |
| if (isa<StoreInst, InsertValueInst>(User) && | |
| !P.first->ReorderIndices.empty()) | |
| Lane = P.first->ReorderIndices[Lane]; | |
| assert(Lane < static_cast<int>(P.first->Scalars.size()) && | |
| "Couldn't find extract lane"); | |
| // Count the number of unique phi nodes, which are the parent for | |
| // parent entry, and exit, if all the unique phis are processed. | |
| if (IsNonSchedulableWithParentPhiNode) { | |
| const TreeEntry *ParentTE = P.first->UserTreeIndex.UserTE; | |
| Value *User = ParentTE->Scalars[Lane]; | |
| if (!ParentsUniqueUsers.insert(User).second) { | |
| It = | |
| find(make_range(std::next(It), P.first->Scalars.end()), User); | |
| continue; | |
| } | |
| } | |
| // Flattened nodes may place an operand in any column; scan all of | |
| // them so copyable scheduling does not double-count. | |
| for (unsigned OpIdx : | |
| seq<unsigned>(P.first->hasReassocScalars() | |
| ? P.first->getNumOperands() | |
| : getNumberOfPotentiallyCommutativeOps( | |
| P.first->getMainOp()))) { | |
| if (P.first->getOperand(OpIdx)[Lane] == Op && | |
| getScheduleCopyableData(EdgeInfo(P.first, OpIdx), Op)) | |
| --P.getSecond(); | |
| } | |
| // If parent node is schedulable, it will be handled correctly. | |
| It = find(make_range(std::next(It), P.first->Scalars.end()), User); | |
| } while (It != P.first->Scalars.end()); | |
| } | |
| return all_of(PotentiallyReorderedEntriesCount, | |
| [&](const auto &P) { return P.second == NumOps - 1; }); | |
| } | |
| SmallVector<ScheduleCopyableData *> | |
| getScheduleCopyableData(const Instruction *I) const { | |
| if (ScheduleCopyableDataMapByInst.empty()) | |
| return {}; | |
| const auto It = ScheduleCopyableDataMapByInst.find(I); | |
| if (It == ScheduleCopyableDataMapByInst.end()) | |
| return {}; | |
| SmallVector<ScheduleCopyableData *> Res; | |
| for (ScheduleCopyableData *SD : It->getSecond()) { | |
| if (isInSchedulingRegion(*SD)) | |
| Res.push_back(SD); | |
| } | |
| return Res; | |
| } | |
| SmallVector<ScheduleCopyableData *> | |
| getScheduleCopyableDataUsers(const Instruction *User) const { | |
| if (ScheduleCopyableDataMapByUsers.empty()) | |
| return {}; | |
| const auto It = ScheduleCopyableDataMapByUsers.find(User); | |
| if (It == ScheduleCopyableDataMapByUsers.end()) | |
| return {}; | |
| SmallVector<ScheduleCopyableData *> Res; | |
| for (ScheduleCopyableData *SD : It->getSecond()) { | |
| if (isInSchedulingRegion(*SD)) | |
| Res.push_back(SD); | |
| } | |
| return Res; | |
| } | |
| /// Reordering \p TE permutes its operand columns and may move an operand | |
| /// between the edges covered and not covered by copyable scheduling | |
| /// data, making the computed dependency counts stale. Mark the schedule | |
| /// data of \p TE's copyable-modeled operands for recalculation at the | |
| /// next bundle scheduling. | |
| void markCopyableDepsForRecalc(const TreeEntry &TE) { | |
| for (unsigned OpIdx : seq<unsigned>(TE.getNumOperands())) | |
| for (Instruction *I : make_isa_range<Instruction>(TE.getOperand(OpIdx))) | |
| if (ScheduleData *SD = getScheduleData(I); | |
| SD && !getScheduleCopyableData(I).empty()) | |
| RecalcCopyableOperandDeps.insert(SD); | |
| } | |
| ScheduleCopyableData &addScheduleCopyableData(const EdgeInfo &EI, | |
| Instruction *I, | |
| int SchedulingRegionID, | |
| ScheduleBundle &Bundle) { | |
| assert(!getScheduleCopyableData(EI, I) && "already in the map"); | |
| ScheduleCopyableData *CD = | |
| ScheduleCopyableDataMap | |
| .try_emplace(std::make_pair(EI, I), | |
| std::make_unique<ScheduleCopyableData>( | |
| SchedulingRegionID, I, EI, Bundle)) | |
| .first->getSecond() | |
| .get(); | |
| ScheduleCopyableDataMapByInst[I].push_back(CD); | |
| if (EI.UserTE) { | |
| ArrayRef<Value *> Op = EI.UserTE->getOperand(EI.EdgeIdx); | |
| const auto *It = find(Op, I); | |
| assert(It != Op.end() && "Lane not set"); | |
| SmallPtrSet<Instruction *, 4> Visited; | |
| do { | |
| int Lane = std::distance(Op.begin(), It); | |
| assert(Lane >= 0 && "Lane not set"); | |
| if (isa<StoreInst, InsertValueInst>(EI.UserTE->Scalars[Lane]) && | |
| !EI.UserTE->ReorderIndices.empty()) | |
| Lane = EI.UserTE->ReorderIndices[Lane]; | |
| assert(Lane < static_cast<int>(EI.UserTE->Scalars.size()) && | |
| "Couldn't find extract lane"); | |
| auto *In = cast<Instruction>(EI.UserTE->Scalars[Lane]); | |
| if (!Visited.insert(In).second) { | |
| It = find(make_range(std::next(It), Op.end()), I); | |
| continue; | |
| } | |
| ScheduleCopyableDataMapByInstUser | |
| .try_emplace(std::make_pair(std::make_pair(In, EI.EdgeIdx), I)) | |
| .first->getSecond() | |
| .push_back(CD); | |
| ScheduleCopyableDataMapByUsers.try_emplace(I) | |
| .first->getSecond() | |
| .insert(CD); | |
| // Remove extra deps for users, becoming non-immediate users of the | |
| // instruction. It may happen, if the chain of same copyable elements | |
| // appears in the tree. With the self-operand modeling (op(V, V)) the | |
| // user node has the value on more than one operand column, so its | |
| // copyable data releases the instruction's schedule data directly | |
| // and must stay a counted user. | |
| if (In == I) { | |
| unsigned NumCols = 0; | |
| for (unsigned OpIdx : seq<unsigned>(EI.UserTE->getNumOperands())) | |
| NumCols += EI.UserTE->getOperand(OpIdx)[Lane] == I; | |
| if (NumCols <= 1) { | |
| EdgeInfo UserEI = EI.UserTE->UserTreeIndex; | |
| if (ScheduleCopyableData *UserCD = | |
| getScheduleCopyableData(UserEI, In)) | |
| ScheduleCopyableDataMapByUsers[I].remove(UserCD); | |
| } | |
| } | |
| It = find(make_range(std::next(It), Op.end()), I); | |
| } while (It != Op.end()); | |
| } else { | |
| ScheduleCopyableDataMapByUsers.try_emplace(I).first->getSecond().insert( | |
| CD); | |
| } | |
| return *CD; | |
| } | |
| /// Unregisters \p Bundle from its instructions and removes its copyable | |
| /// data. The caller must destroy the bundle and recalculate the | |
| /// dependencies. | |
| void cancelScheduling(ScheduleBundle &Bundle) { | |
| for (ScheduleEntity *SE : Bundle.getBundle()) { | |
| Instruction *I = SE->getInst(); | |
| auto *CD = dyn_cast<ScheduleCopyableData>(SE); | |
| if (!CD) { | |
| llvm::erase(ScheduledBundles.find(I)->getSecond(), &Bundle); | |
| continue; | |
| } | |
| const EdgeInfo EI = CD->getEdgeInfo(); | |
| llvm::erase(ScheduleCopyableDataMapByInst.find(I)->getSecond(), CD); | |
| ScheduleCopyableDataMapByUsers[I].remove(CD); | |
| if (EI.UserTE) { | |
| ArrayRef<Value *> Op = EI.UserTE->getOperand(EI.EdgeIdx); | |
| const auto *It = find(Op, I); | |
| assert(It != Op.end() && "Lane not set"); | |
| SmallPtrSet<Instruction *, 4> Visited; | |
| bool HadSelfUse = false; | |
| do { | |
| int Lane = std::distance(Op.begin(), It); | |
| assert(Lane >= 0 && "Lane not set"); | |
| if (isa<StoreInst, InsertValueInst>(EI.UserTE->Scalars[Lane]) && | |
| !EI.UserTE->ReorderIndices.empty()) | |
| Lane = EI.UserTE->ReorderIndices[Lane]; | |
| assert(Lane < static_cast<int>(EI.UserTE->Scalars.size()) && | |
| "Couldn't find extract lane"); | |
| auto *In = cast<Instruction>(EI.UserTE->Scalars[Lane]); | |
| if (!Visited.insert(In).second) { | |
| It = find(make_range(std::next(It), Op.end()), I); | |
| continue; | |
| } | |
| HadSelfUse |= In == I; | |
| auto ByUserIt = ScheduleCopyableDataMapByInstUser.find( | |
| std::make_pair(std::make_pair(In, EI.EdgeIdx), I)); | |
| assert(ByUserIt != ScheduleCopyableDataMapByInstUser.end() && | |
| is_contained(ByUserIt->getSecond(), CD) && | |
| "copyable data is not registered for the user"); | |
| llvm::erase(ByUserIt->getSecond(), CD); | |
| It = find(make_range(std::next(It), Op.end()), I); | |
| } while (It != Op.end()); | |
| // Restore the parent-edge copyable data as a user of I only if the | |
| // cancelled data displaced it at creation (chained copyable | |
| // self-use); otherwise the extra user dependency is never released. | |
| if (HadSelfUse) { | |
| EdgeInfo UserEI = EI.UserTE->UserTreeIndex; | |
| if (ScheduleCopyableData *UserCD = | |
| getScheduleCopyableData(UserEI, I)) | |
| ScheduleCopyableDataMapByUsers[I].insert(UserCD); | |
| } | |
| } | |
| if (ScheduleCopyableDataMapByUsers[I].empty()) | |
| ScheduleCopyableDataMapByUsers.erase(I); | |
| ScheduleCopyableDataMap.erase(std::make_pair(EI, I)); | |
| } | |
| } | |
| /// Clears the dependencies of all schedule data in the scheduling region. | |
| void clearDependencies() { | |
| for_each(ScheduleDataMap, [&](auto &P) { | |
| if (BB != P.first->getParent()) | |
| return; | |
| ScheduleData *SD = P.second; | |
| if (isInSchedulingRegion(*SD)) | |
| SD->clearDependencies(); | |
| }); | |
| for_each(ScheduleCopyableDataMapByInst, [&](auto &P) { | |
| for_each(P.second, [&](ScheduleCopyableData *SD) { | |
| if (isInSchedulingRegion(*SD)) | |
| SD->clearDependencies(); | |
| }); | |
| }); | |
| } | |
| ArrayRef<ScheduleBundle *> getScheduleBundles(Value *V) const { | |
| auto *I = dyn_cast<Instruction>(V); | |
| if (!I) | |
| return {}; | |
| auto It = ScheduledBundles.find(I); | |
| if (It == ScheduledBundles.end()) | |
| return {}; | |
| return It->getSecond(); | |
| } | |
| /// Returns true if the entity is in the scheduling region. | |
| bool isInSchedulingRegion(const ScheduleEntity &SD) const { | |
| if (const auto *Data = dyn_cast<ScheduleData>(&SD)) | |
| return Data->getSchedulingRegionID() == SchedulingRegionID; | |
| if (const auto *CD = dyn_cast<ScheduleCopyableData>(&SD)) | |
| return CD->getSchedulingRegionID() == SchedulingRegionID; | |
| return all_of(cast<ScheduleBundle>(SD).getBundle(), | |
| [&](const ScheduleEntity *BundleMember) { | |
| return isInSchedulingRegion(*BundleMember); | |
| }); | |
| } | |
| /// Marks an instruction as scheduled and puts all dependent ready | |
| /// instructions into the ready-list. | |
| template <typename ReadyListType> | |
| void schedule(const BoUpSLP &R, const InstructionsState &S, | |
| const EdgeInfo &EI, ScheduleEntity *Data, | |
| ReadyListType &ReadyList) { | |
| auto ProcessBundleMember = [&](ScheduleEntity *BundleMember, | |
| ArrayRef<ScheduleBundle *> Bundles) { | |
| // Handle the def-use chain dependencies. | |
| // Decrement the unscheduled counter and insert to ready list if ready. | |
| auto DecrUnsched = [&](auto *Data, bool IsControl = false) { | |
| if ((IsControl || Data->hasValidDependencies()) && | |
| Data->incrementUnscheduledDeps(-1) == 0) { | |
| // There are no more unscheduled dependencies after | |
| // decrementing, so we can put the dependent instruction | |
| // into the ready list. | |
| SmallVector<ScheduleBundle *, 1> CopyableBundle; | |
| ArrayRef<ScheduleBundle *> Bundles; | |
| if (auto *CD = dyn_cast<ScheduleCopyableData>(Data)) { | |
| CopyableBundle.push_back(&CD->getBundle()); | |
| Bundles = CopyableBundle; | |
| } else { | |
| Bundles = getScheduleBundles(Data->getInst()); | |
| } | |
| if (!Bundles.empty()) { | |
| for (ScheduleBundle *Bundle : Bundles) { | |
| if (Bundle->unscheduledDepsInBundle() == 0) { | |
| assert(!Bundle->isScheduled() && | |
| "already scheduled bundle gets ready"); | |
| ReadyList.insert(Bundle); | |
| LLVM_DEBUG(dbgs() | |
| << "SLP: gets ready: " << *Bundle << "\n"); | |
| } | |
| } | |
| return; | |
| } | |
| assert(!Data->isScheduled() && | |
| "already scheduled bundle gets ready"); | |
| assert(!isa<ScheduleCopyableData>(Data) && | |
| "Expected non-copyable data"); | |
| ReadyList.insert(Data); | |
| LLVM_DEBUG(dbgs() << "SLP: gets ready: " << *Data << "\n"); | |
| } | |
| }; | |
| auto DecrUnschedForInst = [&](Instruction *User, unsigned OpIdx, | |
| Instruction *I) { | |
| if (!ScheduleCopyableDataMap.empty()) { | |
| SmallVector<ScheduleCopyableData *> CopyableData = | |
| getScheduleCopyableData(User, OpIdx, I); | |
| bool ReleasedAsCopyable = false; | |
| // Copyable elements modeled on a copyable user lane depend on | |
| // the user's copyable scheduling data, not on the user itself, | |
| // and are released when that copyable data is scheduled. The | |
| // user's own schedule data still carries the def-use dependency | |
| // in this case, so it must be released below. | |
| auto IsNotCopyable = [&](ScheduleCopyableData *CD) { | |
| return !CD->getEdgeInfo().UserTE->isCopyableElement(User); | |
| }; | |
| for (ScheduleCopyableData *CD : | |
| make_filter_range(CopyableData, IsNotCopyable)) { | |
| DecrUnsched(CD, /*IsControl=*/false); | |
| ReleasedAsCopyable = true; | |
| } | |
| if (ReleasedAsCopyable) | |
| return; | |
| } | |
| if (ScheduleData *OpSD = getScheduleData(I)) | |
| DecrUnsched(OpSD, /*IsControl=*/false); | |
| }; | |
| // If BundleMember is a vector bundle, its operands may have been | |
| // reordered during buildTree(). We therefore need to get its operands | |
| // through the TreeEntry. | |
| if (!Bundles.empty()) { | |
| auto *In = BundleMember->getInst(); | |
| // Count uses of each instruction operand. | |
| SmallDenseMap<const Instruction *, unsigned> OperandsUses; | |
| unsigned TotalOpCount = 0; | |
| if (isa<ScheduleCopyableData>(BundleMember)) { | |
| // Copyable data is used only once (uses itself). | |
| TotalOpCount = OperandsUses[In] = 1; | |
| } else { | |
| for (const Use &U : In->operands()) { | |
| if (auto *I = dyn_cast<Instruction>(U.get())) { | |
| auto Res = OperandsUses.try_emplace(I, 0); | |
| unsigned ExtraDeps = 1; | |
| // Count all expanded operands in the binops. | |
| for (ScheduleBundle *Bundle : Bundles) { | |
| if (const TreeEntry *TE = Bundle->getTreeEntry()) { | |
| if (TE->isExpandedBinOp(In)) | |
| ++ExtraDeps; | |
| } else if (S.isExpandedBinOp(In)) { | |
| ++ExtraDeps; | |
| } | |
| } | |
| Res.first->getSecond() += ExtraDeps; | |
| TotalOpCount += ExtraDeps; | |
| } | |
| } | |
| } | |
| // Tracks whether the bundle member instruction itself shows up in | |
| // some operand column of its node (only copyable elements modeled | |
| // through their own operands, like absorbed fmuls, do not). | |
| bool FoundInOpColumns = false; | |
| // Decrement the unscheduled counter and insert to ready list if | |
| // ready. | |
| auto DecrUnschedForInst = | |
| [&](Instruction *I, TreeEntry *UserTE, unsigned OpIdx, | |
| SmallDenseSet<std::pair<const ScheduleEntity *, unsigned>> | |
| &Checked, | |
| bool IsExpandedOperand = false, | |
| bool CopyableDepsOnly = false) { | |
| if (!ScheduleCopyableDataMap.empty()) { | |
| const EdgeInfo EI = {UserTE, OpIdx}; | |
| if (ScheduleCopyableData *CD = | |
| getScheduleCopyableData(EI, I)) { | |
| if (!Checked.insert(std::make_pair(CD, OpIdx)).second) | |
| return; | |
| DecrUnsched(CD, /*IsControl=*/false); | |
| return; | |
| } | |
| } | |
| if (CopyableDepsOnly) | |
| return; | |
| auto It = OperandsUses.find(I); | |
| if (It == OperandsUses.end()) { | |
| // Column value may be a peeled intermediate, not a direct | |
| // operand of In; its deps are released when it is scheduled. | |
| LLVM_DEBUG(dbgs() << "SLP: operand " << *I | |
| << " not modeled as a direct operand of " | |
| << *In << ", skipping.\n"); | |
| return; | |
| } | |
| if (It->second > 0) { | |
| if (ScheduleData *OpSD = getScheduleData(I)) { | |
| if (!IsExpandedOperand && | |
| !Checked.insert(std::make_pair(OpSD, OpIdx)).second) | |
| return; | |
| --It->getSecond(); | |
| assert(TotalOpCount > 0 && "No more operands to decrement"); | |
| --TotalOpCount; | |
| DecrUnsched(OpSD, /*IsControl=*/false); | |
| } else { | |
| --It->getSecond(); | |
| assert(TotalOpCount > 0 && "No more operands to decrement"); | |
| --TotalOpCount; | |
| } | |
| } | |
| }; | |
| SmallDenseSet<std::pair<const ScheduleEntity *, unsigned>> Checked; | |
| for (ScheduleBundle *Bundle : Bundles) { | |
| if (ScheduleCopyableDataMap.empty() && TotalOpCount == 0) | |
| break; | |
| SmallPtrSet<Value *, 4> ParentsUniqueUsers; | |
| // Need to search for the lane since the tree entry can be | |
| // reordered. | |
| auto *It = find(Bundle->getTreeEntry()->Scalars, In); | |
| bool IsNonSchedulableWithParentPhiNode = | |
| Bundle->getTreeEntry()->doesNotNeedToSchedule() && | |
| Bundle->getTreeEntry()->UserTreeIndex && | |
| Bundle->getTreeEntry()->UserTreeIndex.UserTE->hasState() && | |
| Bundle->getTreeEntry()->UserTreeIndex.UserTE->State != | |
| TreeEntry::SplitVectorize && | |
| Bundle->getTreeEntry()->UserTreeIndex.UserTE->getOpcode() == | |
| Instruction::PHI; | |
| do { | |
| int Lane = | |
| std::distance(Bundle->getTreeEntry()->Scalars.begin(), It); | |
| assert(Lane >= 0 && "Lane not set"); | |
| if (isa<StoreInst, InsertValueInst>(In) && | |
| !Bundle->getTreeEntry()->ReorderIndices.empty()) | |
| Lane = Bundle->getTreeEntry()->ReorderIndices[Lane]; | |
| assert(Lane < static_cast<int>( | |
| Bundle->getTreeEntry()->Scalars.size()) && | |
| "Couldn't find extract lane"); | |
| // Since vectorization tree is being built recursively this | |
| // assertion ensures that the tree entry has all operands set | |
| // before reaching this code. Couple of exceptions known at the | |
| // moment are extracts where their second (immediate) operand is | |
| // not added. Since immediates do not affect scheduler behavior | |
| // this is considered okay. | |
| assert( | |
| In && | |
| (isa<ExtractValueInst, ExtractElementInst, CallBase>(In) || | |
| In->getNumOperands() == | |
| Bundle->getTreeEntry()->getNumOperands() || | |
| (isa<ZExtInst>(In) && Bundle->getTreeEntry()->getOpcode() == | |
| Instruction::Select) || | |
| Bundle->getTreeEntry()->isCopyableElement(In) || | |
| Bundle->getTreeEntry()->hasReassocScalars()) && | |
| "Missed TreeEntry operands?"); | |
| // Count the number of unique phi nodes, which are the parent | |
| // entry, and handle the non-copyable deps only on the first lane | |
| // for each such phi. Copyable deps are counted per operand column | |
| // lane and are released on every lane. | |
| bool CopyableDepsOnly = | |
| IsNonSchedulableWithParentPhiNode && | |
| !ParentsUniqueUsers | |
| .insert(Bundle->getTreeEntry() | |
| ->UserTreeIndex.UserTE->Scalars[Lane]) | |
| .second; | |
| // A blended-load operand node is the synthetic blend mask, not an | |
| // IR operand of the load. Use the real pointer operand for | |
| // scheduling so the def-use counters stay balanced; the mask is | |
| // available earlier through the pointer's select. | |
| bool IsBlended = Bundle->getTreeEntry()->State == | |
| TreeEntry::BlendedLoadVectorize; | |
| for (unsigned OpIdx : | |
| seq<unsigned>(Bundle->getTreeEntry()->getNumOperands())) | |
| if (auto *I = dyn_cast<Instruction>( | |
| IsBlended ? In->getOperand(OpIdx) | |
| : Bundle->getTreeEntry()->getOperand( | |
| OpIdx)[Lane])) { | |
| FoundInOpColumns |= (I == In) && !CopyableDepsOnly; | |
| LLVM_DEBUG(dbgs() << "SLP: check for readiness (def): " | |
| << *I << "\n"); | |
| DecrUnschedForInst( | |
| I, Bundle->getTreeEntry(), OpIdx, Checked, | |
| Bundle->getTreeEntry()->isExpandedOperand(In, OpIdx), | |
| /*CopyableDepsOnly=*/CopyableDepsOnly); | |
| } | |
| // If parent node is schedulable, it will be handled correctly. | |
| if (Bundle->getTreeEntry()->isCopyableElement(In)) | |
| break; | |
| It = std::find(std::next(It), | |
| Bundle->getTreeEntry()->Scalars.end(), In); | |
| } while (It != Bundle->getTreeEntry()->Scalars.end()); | |
| } | |
| // A copyable element absorbed into its user modeling (e.g. a | |
| // copyable fmul turned into fmuladd(a, b, -0.0)) does not appear in | |
| // the operand columns of its own node, so the scan above never | |
| // releases the schedule data of the copyable instruction itself. | |
| // Release it here to keep the unscheduled-deps counters balanced, | |
| // consuming its self-use count so the reassociated-operand release | |
| // below cannot release the same schedule data twice. | |
| if (isa<ScheduleCopyableData>(BundleMember) && !FoundInOpColumns) { | |
| auto UseIt = OperandsUses.find(In); | |
| if (UseIt != OperandsUses.end() && UseIt->second > 0) { | |
| --UseIt->getSecond(); | |
| --TotalOpCount; | |
| } | |
| if (ScheduleData *OpSD = getScheduleData(In)) | |
| DecrUnsched(OpSD, /*IsControl=*/false); | |
| } | |
| // Vector intrinsics may keep some arguments scalar (e.g. the | |
| // exponent of llvm.powi). Such scalar arguments are not modeled as | |
| // tree-entry operands, so the per-lane loop above never releases the | |
| // dependency that calculateDependencies() registered for the | |
| // definition feeding such an argument. Release it here to keep the | |
| // unscheduled-deps counters balanced; otherwise the operand's bundle | |
| // may never become ready and scheduling would assert. | |
| if (TotalOpCount > 0) { | |
| if (auto *CI = dyn_cast<CallInst>(In)) { | |
| Intrinsic::ID ID = getVectorIntrinsicIDForCall(CI, R.TLI); | |
| for (unsigned ArgIdx : seq<unsigned>(CI->arg_size())) { | |
| if (!isVectorIntrinsicWithScalarOpAtArg(ID, ArgIdx, R.TTI)) | |
| continue; | |
| auto *OpI = dyn_cast<Instruction>(CI->getArgOperand(ArgIdx)); | |
| if (!OpI) | |
| continue; | |
| auto UseIt = OperandsUses.find(OpI); | |
| if (UseIt == OperandsUses.end() || UseIt->second == 0) | |
| continue; | |
| --UseIt->getSecond(); | |
| --TotalOpCount; | |
| if (ScheduleData *OpSD = getScheduleData(OpI)) { | |
| LLVM_DEBUG(dbgs() | |
| << "SLP: check for readiness (scalar arg): " | |
| << *OpI << "\n"); | |
| DecrUnsched(OpSD, /*IsControl=*/false); | |
| } | |
| } | |
| } | |
| // Peeled intermediates stay as direct operands but drop out of | |
| // operand columns; release their scheduling deps here. | |
| for (const ScheduleBundle *Bundle : Bundles) { | |
| if (TotalOpCount == 0) | |
| break; | |
| TreeEntry *TE = Bundle->getTreeEntry(); | |
| if (!TE->hasReassocScalars()) | |
| continue; | |
| for (Instruction *OpI : | |
| make_isa_range<Instruction>(TE->getReassocScalars())) { | |
| auto UseIt = OperandsUses.find(OpI); | |
| if (UseIt == OperandsUses.end() || UseIt->second == 0) | |
| continue; | |
| LLVM_DEBUG(dbgs() << "SLP: check for readiness " | |
| "(reassociated operand): " | |
| << *OpI << "\n"); | |
| // Copyable deps may live on per-edge ScheduleCopyableData. | |
| bool ReleasedAsCopyable = false; | |
| if (!ScheduleCopyableDataMap.empty()) { | |
| auto MatchesOp = [&](const Use &U) { return U.get() == OpI; }; | |
| for (const Use &U : | |
| make_filter_range(In->operands(), MatchesOp)) { | |
| for (ScheduleCopyableData *CD : | |
| getScheduleCopyableData(In, U.getOperandNo(), OpI)) { | |
| // Deps of reassoc scalars modeled as copyable tree | |
| // operands are released by the operand scan above; | |
| // release each remaining dep only once. | |
| if (Checked.insert(std::make_pair(CD, U.getOperandNo())) | |
| .second) | |
| DecrUnsched(CD, /*IsControl=*/false); | |
| } | |
| } | |
| // The dep is released through copyable data only if this | |
| // very entry models the scalar as a copyable operand on one | |
| // of its edges, mirroring the dependency calculation; | |
| // copyable data on some other entry's edge does not cover | |
| // the dep registered for this entry. | |
| for (auto It = find(TE->Scalars, In); | |
| It != TE->Scalars.end() && !ReleasedAsCopyable; | |
| It = find(make_range(std::next(It), TE->Scalars.end()), | |
| In)) { | |
| int Lane = std::distance(TE->Scalars.begin(), It); | |
| for (unsigned OpIdx : seq<unsigned>(TE->getNumOperands())) | |
| ReleasedAsCopyable |= | |
| TE->getOperand(OpIdx)[Lane] == OpI && | |
| getScheduleCopyableData(EdgeInfo(TE, OpIdx), OpI); | |
| } | |
| } | |
| if (!ReleasedAsCopyable) { | |
| if (ScheduleData *OpSD = getScheduleData(OpI)) | |
| for (unsigned I = 0, E = UseIt->second; I != E; ++I) | |
| DecrUnsched(OpSD, /*IsControl=*/false); | |
| } | |
| TotalOpCount -= UseIt->second; | |
| UseIt->second = 0; | |
| } | |
| } | |
| } | |
| } else { | |
| // If BundleMember is a stand-alone instruction, no operand reordering | |
| // has taken place, so we directly access its operands. | |
| for (Use &U : BundleMember->getInst()->operands()) { | |
| if (auto *I = dyn_cast<Instruction>(U.get())) { | |
| LLVM_DEBUG(dbgs() | |
| << "SLP: check for readiness (def): " << *I << "\n"); | |
| DecrUnschedForInst(BundleMember->getInst(), U.getOperandNo(), I); | |
| } | |
| } | |
| } | |
| // Handle the memory dependencies. | |
| auto *SD = dyn_cast<ScheduleData>(BundleMember); | |
| if (!SD) | |
| return; | |
| SmallPtrSet<const ScheduleData *, 4> VisitedMemory; | |
| for (ScheduleData *MemoryDep : SD->getMemoryDependencies()) { | |
| if (!VisitedMemory.insert(MemoryDep).second) | |
| continue; | |
| // There are no more unscheduled dependencies after decrementing, | |
| // so we can put the dependent instruction into the ready list. | |
| LLVM_DEBUG(dbgs() << "SLP: check for readiness (mem): " | |
| << *MemoryDep << "\n"); | |
| DecrUnsched(MemoryDep); | |
| } | |
| // Handle the control dependencies. | |
| SmallPtrSet<const ScheduleData *, 4> VisitedControl; | |
| for (ScheduleData *Dep : SD->getControlDependencies()) { | |
| if (!VisitedControl.insert(Dep).second) | |
| continue; | |
| // There are no more unscheduled dependencies after decrementing, | |
| // so we can put the dependent instruction into the ready list. | |
| LLVM_DEBUG(dbgs() | |
| << "SLP: check for readiness (ctrl): " << *Dep << "\n"); | |
| DecrUnsched(Dep, /*IsControl=*/true); | |
| } | |
| }; | |
| if (auto *SD = dyn_cast<ScheduleData>(Data)) { | |
| SD->setScheduled(/*Scheduled=*/true); | |
| LLVM_DEBUG(dbgs() << "SLP: schedule " << *SD << "\n"); | |
| SmallVector<std::unique_ptr<ScheduleBundle>> PseudoBundles; | |
| SmallVector<ScheduleBundle *> Bundles; | |
| Instruction *In = SD->getInst(); | |
| ArrayRef<TreeEntry *> Entries = R.getTreeEntries(In); | |
| if (!Entries.empty()) { | |
| for (TreeEntry *TE : make_filter_range(Entries, [&](TreeEntry *TE) { | |
| return isa<ExtractValueInst, ExtractElementInst, CallBase>( | |
| In) || | |
| In->getNumOperands() == TE->getNumOperands() || | |
| TE->hasReassocScalars(); | |
| })) { | |
| auto &BundlePtr = | |
| PseudoBundles.emplace_back(std::make_unique<ScheduleBundle>()); | |
| BundlePtr->setTreeEntry(TE); | |
| BundlePtr->add(SD); | |
| Bundles.push_back(BundlePtr.get()); | |
| } | |
| } | |
| ProcessBundleMember(SD, Bundles); | |
| } else { | |
| ScheduleBundle &Bundle = *cast<ScheduleBundle>(Data); | |
| Bundle.setScheduled(/*Scheduled=*/true); | |
| LLVM_DEBUG(dbgs() << "SLP: schedule " << Bundle << "\n"); | |
| auto AreAllBundlesScheduled = | |
| [&](const ScheduleEntity *SD, | |
| ArrayRef<ScheduleBundle *> SDBundles) { | |
| if (isa<ScheduleCopyableData>(SD)) | |
| return true; | |
| return !SDBundles.empty() && | |
| all_of(SDBundles, [&](const ScheduleBundle *SDBundle) { | |
| return SDBundle->isScheduled(); | |
| }); | |
| }; | |
| for (ScheduleEntity *SD : Bundle.getBundle()) { | |
| ArrayRef<ScheduleBundle *> SDBundles; | |
| if (!isa<ScheduleCopyableData>(SD)) | |
| SDBundles = getScheduleBundles(SD->getInst()); | |
| if (!AreAllBundlesScheduled(SD, SDBundles)) | |
| continue; | |
| SD->setScheduled(/*Scheduled=*/true); | |
| Instruction *In = SD->getInst(); | |
| // The instruction may also belong to tree entries that do not need | |
| // scheduling (e.g. all their values are used outside the block), so | |
| // no schedule bundle is registered for them. Such an entry can still | |
| // model one of this instruction's operands as a copyable element, or | |
| // model the instruction itself as an expanded binop, registered on | |
| // that non-scheduled parent edge. That dependency would never be | |
| // decremented when the instruction is scheduled through a different | |
| // bundle, leaving the operand's bundle permanently unscheduled and | |
| // tripping the unscheduled-deps assertion. Add pseudo-bundles for | |
| // these missing tree entries, so their operand dependencies are | |
| // decremented here as well. Real operand dependencies are protected | |
| // against double counting by the per-operand use counter. | |
| if (isa<ScheduleCopyableData>(SD) || | |
| (ScheduleCopyableDataMap.empty() && | |
| none_of(R.getTreeEntries(In), [&](const TreeEntry *TE) { | |
| return TE->isExpandedBinOp(In); | |
| }))) { | |
| ProcessBundleMember(SD, isa<ScheduleCopyableData>(SD) ? &Bundle | |
| : SDBundles); | |
| continue; | |
| } | |
| SmallVector<std::unique_ptr<ScheduleBundle>> PseudoBundles; | |
| SmallVector<ScheduleBundle *> AllBundles(SDBundles.begin(), | |
| SDBundles.end()); | |
| for (TreeEntry *TE : | |
| make_filter_range(R.getTreeEntries(In), [&](TreeEntry *TE) { | |
| return !TE->isCopyableElement(In); | |
| })) { | |
| if (!isa<ExtractValueInst, ExtractElementInst, CallBase>(In) && | |
| In->getNumOperands() != TE->getNumOperands() && | |
| !TE->hasReassocScalars()) | |
| continue; | |
| if (any_of(SDBundles, [&](const ScheduleBundle *SDBundle) { | |
| return SDBundle->getTreeEntry() == TE; | |
| })) | |
| continue; | |
| ScheduleBundle &PseudoBundle = | |
| *PseudoBundles.emplace_back(std::make_unique<ScheduleBundle>()); | |
| PseudoBundle.setTreeEntry(TE); | |
| PseudoBundle.add(SD); | |
| AllBundles.push_back(&PseudoBundle); | |
| } | |
| ProcessBundleMember(SD, AllBundles); | |
| } | |
| } | |
| } | |
| /// Verify basic self consistency properties of the data structure. | |
| void verify() { | |
| if (!ScheduleStart) | |
| return; | |
| assert(ScheduleStart->getParent() == ScheduleEnd->getParent() && | |
| ScheduleStart->comesBefore(ScheduleEnd) && | |
| "Not a valid scheduling region?"); | |
| for (auto *I = ScheduleStart; I != ScheduleEnd; I = I->getNextNode()) { | |
| ArrayRef<ScheduleBundle *> Bundles = getScheduleBundles(I); | |
| if (!Bundles.empty()) { | |
| for (ScheduleBundle *Bundle : Bundles) { | |
| assert(isInSchedulingRegion(*Bundle) && | |
| "primary schedule data not in window?"); | |
| Bundle->verify(); | |
| } | |
| continue; | |
| } | |
| auto *SD = getScheduleData(I); | |
| if (!SD) | |
| continue; | |
| assert(isInSchedulingRegion(*SD) && | |
| "primary schedule data not in window?"); | |
| SD->verify(); | |
| } | |
| assert(all_of(ReadyInsts, | |
| [](const ScheduleEntity *Bundle) { | |
| return Bundle->isReady(); | |
| }) && | |
| "item in ready list not ready?"); | |
| } | |
| /// Put all instructions into the ReadyList which are ready for scheduling. | |
| template <typename ReadyListType> | |
| void initialFillReadyList(ReadyListType &ReadyList) { | |
| SmallPtrSet<ScheduleBundle *, 16> Visited; | |
| for (auto *I = ScheduleStart; I != ScheduleEnd; I = I->getNextNode()) { | |
| ScheduleData *SD = getScheduleData(I); | |
| if (SD && SD->hasValidDependencies() && SD->isReady()) { | |
| if (ArrayRef<ScheduleBundle *> Bundles = getScheduleBundles(I); | |
| !Bundles.empty()) { | |
| for (ScheduleBundle *Bundle : | |
| make_filter_range(Bundles, [&](ScheduleBundle *Bundle) { | |
| return Visited.insert(Bundle).second; | |
| })) { | |
| if (Bundle->hasValidDependencies() && Bundle->isReady()) { | |
| ReadyList.insert(Bundle); | |
| LLVM_DEBUG(dbgs() << "SLP: initially in ready list: " | |
| << *Bundle << "\n"); | |
| } | |
| } | |
| continue; | |
| } | |
| ReadyList.insert(SD); | |
| LLVM_DEBUG(dbgs() | |
| << "SLP: initially in ready list: " << *SD << "\n"); | |
| } | |
| } | |
| } | |
| /// Build a bundle from the ScheduleData nodes corresponding to the | |
| /// scalar instruction for each lane. | |
| /// \param VL The list of scalar instructions. | |
| /// \param S The state of the instructions. | |
| /// \param EI The edge in the SLP graph or the user node/operand number. | |
| ScheduleBundle &buildBundle(ArrayRef<Value *> VL, | |
| const InstructionsState &S, const EdgeInfo &EI); | |
| /// Checks if a bundle of instructions can be scheduled, i.e. has no | |
| /// cyclic dependencies. This is only a dry-run, no instructions are | |
| /// actually moved at this stage. | |
| /// \returns the scheduling bundle. The returned Optional value is not | |
| /// std::nullopt if \p VL is allowed to be scheduled. | |
| std::optional<ScheduleBundle *> | |
| tryScheduleBundle(ArrayRef<Value *> VL, BoUpSLP *SLP, | |
| const InstructionsState &S, const EdgeInfo &EI); | |
| /// Allocates schedule data chunk. | |
| ScheduleData *allocateScheduleDataChunks(); | |
| /// Extends the scheduling region so that V is inside the region. | |
| /// \returns true if the region size is within the limit. | |
| bool extendSchedulingRegion(Value *V, const InstructionsState &S); | |
| /// Initialize the ScheduleData structures for new instructions in the | |
| /// scheduling region. | |
| void initScheduleData(Instruction *FromI, Instruction *ToI, | |
| ScheduleData *PrevLoadStore, | |
| ScheduleData *NextLoadStore); | |
| /// Updates the dependency information of a bundle and of all instructions/ | |
| /// bundles which depend on the original bundle. | |
| void calculateDependencies(ScheduleBundle &Bundle, bool InsertInReadyList, | |
| BoUpSLP *SLP, | |
| const SmallPtrSetImpl<Value *> &ExpandedOps, | |
| ArrayRef<ScheduleData *> ControlDeps = {}); | |
| /// Sets all instruction in the scheduling region to un-scheduled. | |
| void resetSchedule(); | |
| BasicBlock *BB; | |
| /// Simple memory allocation for ScheduleData. | |
| SmallVector<std::unique_ptr<ScheduleData[]>> ScheduleDataChunks; | |
| /// The size of a ScheduleData array in ScheduleDataChunks. | |
| int ChunkSize; | |
| /// The allocator position in the current chunk, which is the last entry | |
| /// of ScheduleDataChunks. | |
| int ChunkPos; | |
| /// Attaches ScheduleData to Instruction. | |
| /// Note that the mapping survives during all vectorization iterations, i.e. | |
| /// ScheduleData structures are recycled. | |
| SmallDenseMap<Instruction *, ScheduleData *> ScheduleDataMap; | |
| /// Attaches ScheduleCopyableData to EdgeInfo (UserTreeEntry + operand | |
| /// number) and the operand instruction, represented as copyable element. | |
| SmallDenseMap<std::pair<EdgeInfo, const Value *>, | |
| std::unique_ptr<ScheduleCopyableData>> | |
| ScheduleCopyableDataMap; | |
| /// Represents mapping between instruction and all related | |
| /// ScheduleCopyableData (for all uses in the tree, represenedt as copyable | |
| /// element). The SLP tree may contain several representations of the same | |
| /// instruction. | |
| SmallDenseMap<const Instruction *, SmallVector<ScheduleCopyableData *>> | |
| ScheduleCopyableDataMapByInst; | |
| /// Represents mapping between user value and operand number, the operand | |
| /// value and all related ScheduleCopyableData. The relation is 1:n, because | |
| /// the same user may refernce the same operand in different tree entries | |
| /// and the operand may be modelled by the different copyable data element. | |
| SmallDenseMap<std::pair<std::pair<const Value *, unsigned>, const Value *>, | |
| SmallVector<ScheduleCopyableData *>> | |
| ScheduleCopyableDataMapByInstUser; | |
| /// Represents mapping between instruction and all related | |
| /// ScheduleCopyableData. It represents the mapping between the actual | |
| /// instruction and the last copyable data element in the chain. E.g., if | |
| /// the graph models the following instructions: | |
| /// %0 = non-add instruction ... | |
| /// ... | |
| /// %4 = add %3, 1 | |
| /// %5 = add %4, 1 | |
| /// %6 = insertelement poison, %0, 0 | |
| /// %7 = insertelement %6, %5, 1 | |
| /// And the graph is modeled as: | |
| /// [%5, %0] -> [%4, copyable %0 <0> ] -> [%3, copyable %0 <1> ] | |
| /// -> [1, 0] -> [%1, 0] | |
| /// | |
| /// this map will map %0 only to the copyable element <1>, which is the last | |
| /// user (direct user of the actual instruction). <0> uses <1>, so <1> will | |
| /// keep the map to <0>, not the %0. | |
| SmallDenseMap<const Instruction *, | |
| SmallSetVector<ScheduleCopyableData *, 4>> | |
| ScheduleCopyableDataMapByUsers; | |
| /// Attaches ScheduleBundle to Instruction. | |
| SmallDenseMap<Instruction *, SmallVector<ScheduleBundle *>> | |
| ScheduledBundles; | |
| /// The list of ScheduleBundles. | |
| SmallVector<std::unique_ptr<ScheduleBundle>> ScheduledBundlesList; | |
| /// The ready-list for scheduling (only used for the dry-run). | |
| SetVector<ScheduleEntity *> ReadyInsts; | |
| /// The first instruction of the scheduling region. | |
| Instruction *ScheduleStart = nullptr; | |
| /// The first instruction _after_ the scheduling region. | |
| Instruction *ScheduleEnd = nullptr; | |
| /// The first memory accessing instruction in the scheduling region | |
| /// (can be null). | |
| ScheduleData *FirstLoadStoreInRegion = nullptr; | |
| /// The last memory accessing instruction in the scheduling region | |
| /// (can be null). | |
| ScheduleData *LastLoadStoreInRegion = nullptr; | |
| /// Is there an llvm.stacksave or llvm.stackrestore in the scheduling | |
| /// region? Used to optimize the dependence calculation for the | |
| /// common case where there isn't. | |
| bool RegionHasStackSave = false; | |
| /// Used to skip the dependencies calculation for writes that are visible | |
| /// after unwinding. | |
| bool RegionHasMayThrow = false; | |
| /// Returns true if \p I writes memory that the caller can still see | |
| /// after an unwind. | |
| bool isWriteVisibleOnUnwind(Instruction *I) const { | |
| if (!RegionHasMayThrow || !I->mayWriteToMemory()) | |
| return false; | |
| MemoryLocation Loc = getLocation(I); | |
| if (!Loc.Ptr) | |
| return true; | |
| bool RequiresNoCaptureBeforeUnwind = false; | |
| return !isNotVisibleOnUnwind(getUnderlyingObject(Loc.Ptr), | |
| RequiresNoCaptureBeforeUnwind) || | |
| RequiresNoCaptureBeforeUnwind; | |
| } | |
| /// The current size of the scheduling region. | |
| int ScheduleRegionSize = 0; | |
| /// The maximum size allowed for the scheduling region. | |
| int ScheduleRegionSizeLimit; | |
| /// Operands that are modeled as copyable elements in a previously built | |
| /// vectorized node and that are used directly by another, | |
| /// not-yet-registered node sharing a schedulable instruction with it. Their | |
| /// direct dependencies must be recomputed at the next bundle scheduling, | |
| /// when the new node is already registered in the tree, so that the direct | |
| /// use is accounted for. If the new node is the last scheduled bundle and | |
| /// no further scheduling consumes this list, the leftover entries are | |
| /// dropped on the next region reset and the dependencies are recomputed | |
| /// against the full tree in scheduleBlock instead. A set is used to avoid | |
| /// recomputing the same operand more than once. | |
| SmallSetVector<ScheduleData *, 8> RecalcCopyableOperandDeps; | |
| /// Ordered pairs (Src, Dst) of memory instructions whose may-alias | |
| /// dependency has been dropped in favor of a runtime alias check. | |
| SmallDenseSet<std::pair<Instruction *, Instruction *>, 8> IgnoredMemDeps; | |
| /// The ID of the scheduling region. For a new vectorization iteration this | |
| /// is incremented which "removes" all ScheduleData from the region. | |
| /// Make sure that the initial SchedulingRegionID is greater than the | |
| /// initial SchedulingRegionID in ScheduleData (which is 0). | |
| int SchedulingRegionID = 1; | |
| }; | |
| /// Attaches the BlockScheduling structures to basic blocks. | |
| MapVector<BasicBlock *, std::unique_ptr<BlockScheduling>> BlocksSchedules; | |
| /// Performs the "real" scheduling. Done before vectorization is actually | |
| /// performed in a basic block. | |
| void scheduleBlock(const BoUpSLP &R, BlockScheduling *BS); | |
| /// List of users to ignore during scheduling and that don't need extracting. | |
| const SmallDenseSet<Value *> *UserIgnoreList = nullptr; | |
| /// Narrowed reduction chain instructions, dropped together with the | |
| /// reduction. Subset of UserIgnoreList. | |
| SmallPtrSet<Value *, 4> NarrowedChainInsts; | |
| /// A DenseMapInfo implementation for holding DenseMaps and DenseSets of | |
| /// sorted SmallVectors of unsigned. | |
| struct OrdersTypeDenseMapInfo { | |
| static unsigned getHashValue(const OrdersType &V) { | |
| return static_cast<unsigned>(hash_combine_range(V)); | |
| } | |
| static bool isEqual(const OrdersType &LHS, const OrdersType &RHS) { | |
| return LHS == RHS; | |
| } | |
| }; | |
| // Analysis and block reference. | |
| Function *F; | |
| ScalarEvolution *SE; | |
| TargetTransformInfo *TTI; | |
| TargetLibraryInfo *TLI; | |
| LoopInfo *LI; | |
| DominatorTree *DT; | |
| AssumptionCache *AC; | |
| DemandedBits *DB; | |
| const DataLayout *DL; | |
| OptimizationRemarkEmitter *ORE; | |
| /// Cached cost-model mode for this function. | |
| /// If -Os/-Oz, use CodeSize. Otherwise use RecipThroughput. | |
| const TargetTransformInfo::TargetCostKind CostKind; | |
| unsigned MaxVecRegSize; // This is set by TTI or overridden by cl::opt. | |
| unsigned MinVecRegSize; // Set by cl::opt (default: 128). | |
| /// Instruction builder to construct the vectorized tree. | |
| IRBuilder<TargetFolder> Builder; | |
| /// A map of scalar integer values to the smallest bit width with which they | |
| /// can legally be represented. The values map to (width, signed) pairs, | |
| /// where "width" indicates the minimum bit width and "signed" is True if the | |
| /// value must be signed-extended, rather than zero-extended, back to its | |
| /// original width. | |
| DenseMap<const TreeEntry *, std::pair<uint64_t, bool>> MinBWs; | |
| /// A booleanized node is an i1 node registered in MinBWs to be emitted in | |
| /// the wide leaf type. Genuine demoted nodes never have i1 scalars (i1 | |
| /// cannot be demoted), so an i1 node in MinBWs is always a booleanized one. | |
| bool isBooleanizedNode(const TreeEntry *E) const { | |
| auto It = MinBWs.find(E); | |
| return It != MinBWs.end() && E->Scalars.front()->getType()->isIntegerTy(1); | |
| } | |
| /// Final size of the reduced vector, if the current graph represents the | |
| /// input for the reduction and it was possible to narrow the size of the | |
| /// reduction. | |
| unsigned ReductionBitWidth = 0; | |
| /// Canonical graph size before the transformations. | |
| unsigned BaseGraphSize = 1; | |
| /// If the tree contains any zext/sext/trunc nodes, contains max-min pair of | |
| /// type sizes, used in the tree. | |
| std::optional<std::pair<unsigned, unsigned>> CastMaxMinBWSizes; | |
| /// Indices of the vectorized nodes, which supposed to be the roots of the new | |
| /// bitwidth analysis attempt, like trunc, IToFP or ICmp. | |
| DenseSet<unsigned> ExtraBitWidthNodes; | |
| }; | |
| template <> struct llvm::DenseMapInfo<BoUpSLP::EdgeInfo> { | |
| using FirstInfo = DenseMapInfo<BoUpSLP::TreeEntry *>; | |
| using SecondInfo = DenseMapInfo<unsigned>; | |
| static unsigned getHashValue(const BoUpSLP::EdgeInfo &Val) { | |
| return detail::combineHashValue(FirstInfo::getHashValue(Val.UserTE), | |
| SecondInfo::getHashValue(Val.EdgeIdx)); | |
| } | |
| static bool isEqual(const BoUpSLP::EdgeInfo &LHS, | |
| const BoUpSLP::EdgeInfo &RHS) { | |
| return LHS == RHS; | |
| } | |
| }; | |
| template <> struct llvm::GraphTraits<BoUpSLP *> { | |
| using TreeEntry = BoUpSLP::TreeEntry; | |
| /// NodeRef has to be a pointer per the GraphWriter. | |
| using NodeRef = TreeEntry *; | |
| using ContainerTy = BoUpSLP::TreeEntry::VecTreeTy; | |
| /// Add the VectorizableTree to the index iterator to be able to return | |
| /// TreeEntry pointers. | |
| struct ChildIteratorType | |
| : public iterator_adaptor_base< | |
| ChildIteratorType, SmallVector<BoUpSLP::EdgeInfo, 1>::iterator> { | |
| ContainerTy &VectorizableTree; | |
| ChildIteratorType(SmallVector<BoUpSLP::EdgeInfo, 1>::iterator W, | |
| ContainerTy &VT) | |
| : ChildIteratorType::iterator_adaptor_base(W), VectorizableTree(VT) {} | |
| NodeRef operator*() { return I->UserTE; } | |
| }; | |
| static NodeRef getEntryNode(BoUpSLP &R) { return &R.getRootNode(); } | |
| static ChildIteratorType child_begin(NodeRef N) { | |
| return {&N->UserTreeIndex, N->Container}; | |
| } | |
| static ChildIteratorType child_end(NodeRef N) { | |
| return {&N->UserTreeIndex + 1, N->Container}; | |
| } | |
| /// For the node iterator we just need to turn the TreeEntry iterator into a | |
| /// TreeEntry* iterator so that it dereferences to NodeRef. | |
| class nodes_iterator { | |
| using ItTy = ContainerTy::iterator; | |
| ItTy It; | |
| public: | |
| nodes_iterator(const ItTy &It2) : It(It2) {} | |
| NodeRef operator*() { return It->get(); } | |
| nodes_iterator operator++() { | |
| ++It; | |
| return *this; | |
| } | |
| bool operator!=(const nodes_iterator &N2) const { return N2.It != It; } | |
| }; | |
| static nodes_iterator nodes_begin(BoUpSLP *R) { | |
| return nodes_iterator(R->VectorizableTree.begin()); | |
| } | |
| static nodes_iterator nodes_end(BoUpSLP *R) { | |
| return nodes_iterator(R->VectorizableTree.end()); | |
| } | |
| static unsigned size(BoUpSLP *R) { return R->VectorizableTree.size(); } | |
| }; | |
| template <> | |
| struct llvm::DOTGraphTraits<BoUpSLP *> : public DefaultDOTGraphTraits { | |
| using TreeEntry = BoUpSLP::TreeEntry; | |
| DOTGraphTraits(bool IsSimple = false) : DefaultDOTGraphTraits(IsSimple) {} | |
| std::string getNodeLabel(const TreeEntry *Entry, const BoUpSLP *R) { | |
| std::string Str; | |
| raw_string_ostream OS(Str); | |
| OS << Entry->Idx << ".\n"; | |
| if (isSplat(Entry->Scalars)) | |
| OS << "<splat> "; | |
| for (auto *V : Entry->Scalars) { | |
| OS << *V; | |
| if (llvm::any_of(R->ExternalUses, [&](const BoUpSLP::ExternalUser &EU) { | |
| return EU.Scalar == V; | |
| })) | |
| OS << " <extract>"; | |
| OS << "\n"; | |
| } | |
| return Str; | |
| } | |
| static std::string getNodeAttributes(const TreeEntry *Entry, | |
| const BoUpSLP *) { | |
| if (Entry->isGather()) | |
| return "color=red"; | |
| if (Entry->State == TreeEntry::ScatterVectorize || | |
| Entry->State == TreeEntry::StridedVectorize || | |
| Entry->State == TreeEntry::ExpandVectorize || | |
| Entry->State == TreeEntry::CompressVectorize || | |
| Entry->State == TreeEntry::BlendedLoadVectorize) | |
| return "color=blue"; | |
| return ""; | |
| } | |
| }; | |
| BoUpSLP::~BoUpSLP() { | |
| SmallVector<WeakTrackingVH> DeadInsts; | |
| for (auto *I : DeletedInstructions) { | |
| if (!I->getParent()) { | |
| // Temporarily insert instruction back to erase them from parent and | |
| // memory later. | |
| if (isa<PHINode>(I)) | |
| // Phi nodes must be the very first instructions in the block. | |
| I->insertBefore(F->getEntryBlock(), | |
| F->getEntryBlock().getFirstNonPHIIt()); | |
| else | |
| I->insertBefore(F->getEntryBlock().getTerminator()->getIterator()); | |
| continue; | |
| } | |
| for (Instruction *Op : make_isa_range<Instruction>(I->operands())) | |
| if (!DeletedInstructions.count(Op) && Op->hasOneUser() && | |
| wouldInstructionBeTriviallyDead(Op, TLI)) | |
| DeadInsts.emplace_back(Op); | |
| I->dropAllReferences(); | |
| } | |
| for (auto *I : DeletedInstructions) { | |
| assert(I->use_empty() && | |
| "trying to erase instruction with users."); | |
| I->eraseFromParent(); | |
| } | |
| // Cleanup any dead scalar code feeding the vectorized instructions | |
| RecursivelyDeleteTriviallyDeadInstructions(DeadInsts, TLI); | |
| #ifdef EXPENSIVE_CHECKS | |
| // If we could guarantee that this call is not extremely slow, we could | |
| // remove the ifdef limitation (see PR47712). | |
| assert(!verifyFunction(*F, &dbgs())); | |
| #endif | |
| } | |
| std::optional<BoUpSLP::OrdersType> | |
| BoUpSLP::findReusedOrderedScalars(const BoUpSLP::TreeEntry &TE, | |
| bool TopToBottom, bool IgnoreReorder) { | |
| assert(TE.isGather() && "Expected gather node only."); | |
| // Try to find subvector extract/insert patterns and reorder only such | |
| // patterns. | |
| SmallVector<Value *> GatheredScalars(TE.Scalars.begin(), TE.Scalars.end()); | |
| Type *ScalarTy = GatheredScalars.front()->getType(); | |
| size_t NumScalars = GatheredScalars.size(); | |
| if (!isValidElementType(ScalarTy, Opts.slp_revec)) | |
| return std::nullopt; | |
| auto *VecTy = getWidenedType(ScalarTy, NumScalars); | |
| unsigned NumParts = getNumberOfParts(VecTy, ScalarTy, NumScalars); | |
| SmallVector<int> ExtractMask; | |
| SmallVector<int> Mask; | |
| SmallVector<SmallVector<const TreeEntry *>> Entries; | |
| SmallVector<std::optional<TargetTransformInfo::ShuffleKind>> ExtractShuffles = | |
| tryToGatherExtractElements(GatheredScalars, ExtractMask, NumParts); | |
| SmallVector<std::optional<TargetTransformInfo::ShuffleKind>> GatherShuffles = | |
| isGatherShuffledEntry(&TE, GatheredScalars, Mask, Entries, NumParts, | |
| ScalarTy, /*ForOrder=*/true); | |
| // The internal gathers of the splat gather subtrees are not used as shuffle | |
| // sources, do not reorder the tree to match them. | |
| if (hasSplatSubtreeGather(Entries)) { | |
| GatherShuffles.clear(); | |
| Entries.clear(); | |
| } | |
| // No shuffled operands - ignore. | |
| if (GatherShuffles.empty() && ExtractShuffles.empty()) | |
| return std::nullopt; | |
| OrdersType CurrentOrder(NumScalars, NumScalars); | |
| if (GatherShuffles.size() == 1 && | |
| *GatherShuffles.front() == TTI::SK_PermuteSingleSrc && | |
| Entries.front().front()->isSame(TE.Scalars)) { | |
| // If the full matched node in whole tree rotation - no need to consider the | |
| // matching order, rotating the whole tree. | |
| if (TopToBottom) | |
| return std::nullopt; | |
| // No need to keep the order for the same user node. | |
| if (Entries.front().front()->UserTreeIndex.UserTE == | |
| TE.UserTreeIndex.UserTE) | |
| return std::nullopt; | |
| // No need to keep the order for the matched root node, if it can be freely | |
| // reordered. | |
| if (!IgnoreReorder && Entries.front().front()->Idx == 0) | |
| return std::nullopt; | |
| // If shuffling 2 elements only and the matching node has reverse reuses - | |
| // no need to count order, both work fine. | |
| if (!Entries.front().front()->ReuseShuffleIndices.empty() && | |
| TE.getVectorFactor() == 2 && Mask.size() == 2 && | |
| any_of(enumerate(Entries.front().front()->ReuseShuffleIndices), | |
| [](const auto &P) { | |
| return P.value() % 2 != static_cast<int>(P.index()) % 2; | |
| })) | |
| return std::nullopt; | |
| // Perfect match in the graph, will reuse the previously vectorized | |
| // node. Cost is 0. | |
| std::iota(CurrentOrder.begin(), CurrentOrder.end(), 0); | |
| return CurrentOrder; | |
| } | |
| auto IsSplatMask = [](ArrayRef<int> Mask) { | |
| int SingleElt = PoisonMaskElem; | |
| return all_of(Mask, [&](int I) { | |
| if (SingleElt == PoisonMaskElem && I != PoisonMaskElem) | |
| SingleElt = I; | |
| return I == PoisonMaskElem || I == SingleElt; | |
| }); | |
| }; | |
| // Exclusive broadcast mask - ignore. | |
| if ((ExtractShuffles.empty() && IsSplatMask(Mask) && | |
| (Entries.size() != 1 || | |
| Entries.front().front()->ReorderIndices.empty())) || | |
| (GatherShuffles.empty() && IsSplatMask(ExtractMask))) | |
| return std::nullopt; | |
| SmallBitVector ShuffledSubMasks(NumParts); | |
| auto TransformMaskToOrder = [&](MutableArrayRef<unsigned> CurrentOrder, | |
| ArrayRef<int> Mask, int PartSz, int NumParts, | |
| function_ref<unsigned(unsigned)> GetVF) { | |
| for (int I : seq<int>(NumParts)) { | |
| if (ShuffledSubMasks.test(I)) | |
| continue; | |
| const int VF = GetVF(I); | |
| if (VF == 0) | |
| continue; | |
| unsigned Limit = getNumElems(CurrentOrder.size(), PartSz, I); | |
| MutableArrayRef<unsigned> Slice = CurrentOrder.slice(I * PartSz, Limit); | |
| // Shuffle of at least 2 vectors - ignore. | |
| if (any_of(Slice, not_equal_to(NumScalars))) { | |
| llvm::fill(Slice, NumScalars); | |
| ShuffledSubMasks.set(I); | |
| continue; | |
| } | |
| // Try to include as much elements from the mask as possible. | |
| int FirstMin = INT_MAX; | |
| int SecondVecFound = false; | |
| for (int K : seq<int>(Limit)) { | |
| int Idx = Mask[I * PartSz + K]; | |
| if (Idx == PoisonMaskElem) { | |
| Value *V = GatheredScalars[I * PartSz + K]; | |
| if (isConstant(V) && !isa<PoisonValue>(V)) { | |
| SecondVecFound = true; | |
| break; | |
| } | |
| continue; | |
| } | |
| if (Idx < VF) { | |
| if (FirstMin > Idx) | |
| FirstMin = Idx; | |
| } else { | |
| SecondVecFound = true; | |
| break; | |
| } | |
| } | |
| FirstMin = (FirstMin / PartSz) * PartSz; | |
| // Shuffle of at least 2 vectors - ignore. | |
| if (SecondVecFound) { | |
| llvm::fill(Slice, NumScalars); | |
| ShuffledSubMasks.set(I); | |
| continue; | |
| } | |
| for (int K : seq<int>(Limit)) { | |
| int Idx = Mask[I * PartSz + K]; | |
| if (Idx == PoisonMaskElem) | |
| continue; | |
| Idx -= FirstMin; | |
| if (Idx >= PartSz) { | |
| // Cross-part / second-vector reference: this slice cannot be | |
| // ordered as a single first-vector permutation, give up. | |
| SecondVecFound = true; | |
| break; | |
| } | |
| // For the last partial slice, Limit < PartSz and Idx in [Limit, | |
| // PartSz) addresses the unused padded tail (no scalar at that | |
| // position). Skip the write but keep ordering the remaining K's. | |
| if (static_cast<unsigned>(I * PartSz + Idx) >= CurrentOrder.size()) | |
| continue; | |
| if (CurrentOrder[I * PartSz + Idx] > | |
| static_cast<unsigned>(I * PartSz + K) && | |
| CurrentOrder[I * PartSz + Idx] != | |
| static_cast<unsigned>(I * PartSz + Idx)) | |
| CurrentOrder[I * PartSz + Idx] = I * PartSz + K; | |
| } | |
| // Shuffle of at least 2 vectors - ignore. | |
| if (SecondVecFound) { | |
| llvm::fill(Slice, NumScalars); | |
| ShuffledSubMasks.set(I); | |
| continue; | |
| } | |
| } | |
| }; | |
| int PartSz = getPartNumElems(NumScalars, NumParts); | |
| if (!ExtractShuffles.empty()) | |
| TransformMaskToOrder( | |
| CurrentOrder, ExtractMask, PartSz, NumParts, [&](unsigned I) { | |
| if (I >= ExtractShuffles.size() || !ExtractShuffles[I]) | |
| return 0U; | |
| unsigned VF = 0; | |
| unsigned Sz = getNumElems(TE.getVectorFactor(), PartSz, I); | |
| for (unsigned Idx : seq<unsigned>(Sz)) { | |
| int K = I * PartSz + Idx; | |
| if (static_cast<unsigned>(K) >= ExtractMask.size()) | |
| break; | |
| if (ExtractMask[K] == PoisonMaskElem) | |
| continue; | |
| if (!TE.ReuseShuffleIndices.empty()) | |
| K = TE.ReuseShuffleIndices[K]; | |
| if (K == PoisonMaskElem) | |
| continue; | |
| if (!TE.ReorderIndices.empty()) | |
| K = std::distance(TE.ReorderIndices.begin(), | |
| find(TE.ReorderIndices, K)); | |
| auto *EI = dyn_cast<ExtractElementInst>(TE.Scalars[K]); | |
| if (!EI) | |
| continue; | |
| VF = std::max(VF, EI->getVectorOperandType() | |
| ->getElementCount() | |
| .getKnownMinValue()); | |
| } | |
| return VF; | |
| }); | |
| // Check special corner case - single shuffle of the same entry. | |
| if (GatherShuffles.size() == 1 && NumParts != 1) { | |
| if (ShuffledSubMasks.any()) | |
| return std::nullopt; | |
| PartSz = NumScalars; | |
| NumParts = 1; | |
| } | |
| if (!Entries.empty()) | |
| TransformMaskToOrder(CurrentOrder, Mask, PartSz, NumParts, [&](unsigned I) { | |
| if (I >= GatherShuffles.size() || !GatherShuffles[I]) | |
| return 0U; | |
| return std::max(Entries[I].front()->getVectorFactor(), | |
| Entries[I].back()->getVectorFactor()); | |
| }); | |
| unsigned NumUndefs = count(CurrentOrder, NumScalars); | |
| if (ShuffledSubMasks.all() || (NumScalars > 2 && NumUndefs >= NumScalars / 2)) | |
| return std::nullopt; | |
| return std::move(CurrentOrder); | |
| } | |
| /// Checks if strided loads can be generated out of \p VL loads with pointers \p | |
| /// PointerOps: | |
| /// 1. Target with strided load support is detected. | |
| /// 2. The number of loads is greater than MinProfitableStridedLoads, or the | |
| /// potential stride <= MaxProfitableStride and the potential stride is | |
| /// power-of-2 (to avoid perf regressions for the very small number of loads) | |
| /// and max distance > number of loads, or potential stride is -1. | |
| /// 3. The loads are ordered, or number of unordered loads <= | |
| /// MaxProfitableUnorderedLoads, or loads are in reversed order. (this check is | |
| /// to avoid extra costs for very expensive shuffles). | |
| /// 4. Any pointer operand is an instruction with the users outside of the | |
| /// current graph (for masked gathers extra extractelement instructions | |
| /// might be required). | |
| bool BoUpSLP::isStridedLoad(ArrayRef<Value *> PointerOps, Type *ScalarTy, | |
| Align Alignment, const int64_t Diff, | |
| const size_t Sz) const { | |
| if (Diff % (Sz - 1) != 0) | |
| return false; | |
| // Try to generate strided load node. | |
| auto IsAnyPointerUsedOutGraph = | |
| any_of(make_isa_range<Instruction>(PointerOps), [&](Instruction *I) { | |
| return any_of(I->users(), [&](User *U) { | |
| return !isVectorized(U) && !MustGather.contains(U); | |
| }); | |
| }); | |
| const uint64_t AbsoluteDiff = std::abs(Diff); | |
| auto *VecTy = getWidenedType(ScalarTy, Sz); | |
| if (IsAnyPointerUsedOutGraph || | |
| (AbsoluteDiff > Sz && | |
| (Sz > Opts.slp_min_strided_loads || | |
| (AbsoluteDiff <= Opts.slp_max_stride * Sz && AbsoluteDiff % Sz == 0 && | |
| has_single_bit(AbsoluteDiff / Sz)))) || | |
| Diff == -(static_cast<int64_t>(Sz) - 1)) { | |
| int64_t Stride = Diff / static_cast<int64_t>(Sz - 1); | |
| if (Diff != Stride * static_cast<int64_t>(Sz - 1)) | |
| return false; | |
| if (!TTI->isLegalStridedLoadStore(VecTy, Alignment)) | |
| return false; | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool BoUpSLP::isWidenedStridedLoad(const TreeEntry *E) const { | |
| if (E->State != TreeEntry::StridedVectorize || | |
| E->getOpcode() != Instruction::Load) | |
| return false; | |
| const StridedPtrInfo &SPtrInfo = TreeEntryToStridedPtrInfoMap.at(E); | |
| return SPtrInfo.Ty->getNumElements() != E->Scalars.size(); | |
| } | |
| bool BoUpSLP::analyzeConstantStrideCandidate( | |
| const ArrayRef<Value *> PointerOps, Type *ScalarTy, Align Alignment, | |
| const SmallVectorImpl<unsigned> &SortedIndices, const int64_t Diff, | |
| Value *Ptr0, StridedPtrInfo &SPtrInfo) const { | |
| const size_t Sz = PointerOps.size(); | |
| SmallVector<int64_t> SortedOffsetsFromBase(Sz); | |
| // Go through `PointerOps` in sorted order and record offsets from | |
| // PointerOps[0]. We use PointerOps[0] rather than Ptr0 because | |
| // sortPtrAccesses only validates getPointersDiff for pairs relative to | |
| // PointerOps[0]. This is safe since only offset differences are used below. | |
| for (unsigned I : seq<unsigned>(Sz)) { | |
| Value *Ptr = | |
| SortedIndices.empty() ? PointerOps[I] : PointerOps[SortedIndices[I]]; | |
| std::optional<int64_t> Offset = | |
| getPointersDiff(ScalarTy, PointerOps[0], ScalarTy, Ptr, *DL, *SE); | |
| assert(Offset && "sortPtrAccesses should have validated this pointer"); | |
| SortedOffsetsFromBase[I] = *Offset; | |
| } | |
| // The code below checks that `SortedOffsetsFromBase` looks as follows: | |
| // ``` | |
| // [ | |
| // (e_{0, 0}, e_{0, 1}, ..., e_{0, GroupSize - 1}), // first group | |
| // (e_{1, 0}, e_{1, 1}, ..., e_{1, GroupSize - 1}), // secon group | |
| // ... | |
| // (e_{NumGroups - 1, 0}, e_{NumGroups - 1, 1}, ..., e_{NumGroups - 1, | |
| // GroupSize - 1}), // last group | |
| // ] | |
| // ``` | |
| // The distance between consecutive elements within each group should all be | |
| // the same `StrideWithinGroup`. The distance between the first elements of | |
| // consecutive groups should all be the same `StrideBetweenGroups`. | |
| int64_t StrideWithinGroup = | |
| SortedOffsetsFromBase[1] - SortedOffsetsFromBase[0]; | |
| // Determine size of the first group. Later we will check that all other | |
| // groups have the same size. | |
| auto IsEndOfGroupIndex = [=, &SortedOffsetsFromBase](unsigned Idx) { | |
| return SortedOffsetsFromBase[Idx] - SortedOffsetsFromBase[Idx - 1] != | |
| StrideWithinGroup; | |
| }; | |
| auto Indices = seq<unsigned>(1, Sz); | |
| auto FoundIt = llvm::find_if(Indices, IsEndOfGroupIndex); | |
| unsigned GroupSize = FoundIt != Indices.end() ? *FoundIt : Sz; | |
| unsigned VecSz = Sz; | |
| Type *NewScalarTy = ScalarTy; | |
| // Quick detour: at this point we can say what the type of strided load would | |
| // be if all the checks pass. Check if this type is legal for the target. | |
| bool NeedsWidening = Sz != GroupSize; | |
| const uint64_t UnitBitWidth = DL->getTypeSizeInBits(ScalarTy).getFixedValue(); | |
| if (NeedsWidening) { | |
| if (Sz % GroupSize != 0) | |
| return false; | |
| if (StrideWithinGroup != 1) | |
| return false; | |
| VecSz = Sz / GroupSize; | |
| NewScalarTy = Type::getIntNTy(SE->getContext(), UnitBitWidth * GroupSize); | |
| } else if (ScalarTy->isVectorTy()) { | |
| NewScalarTy = Type::getIntNTy(SE->getContext(), UnitBitWidth); | |
| } | |
| if (!isStridedLoad(PointerOps, NewScalarTy, Alignment, Diff, VecSz)) | |
| return false; | |
| int64_t StrideIntVal = StrideWithinGroup; | |
| if (NeedsWidening) { | |
| // Continue with checking the "shape" of `SortedOffsetsFromBase`. | |
| // Check that the strides between groups are all the same. | |
| unsigned CurrentGroupStartIdx = GroupSize; | |
| int64_t StrideBetweenGroups = | |
| SortedOffsetsFromBase[GroupSize] - SortedOffsetsFromBase[0]; | |
| StrideIntVal = StrideBetweenGroups; | |
| for (; CurrentGroupStartIdx < Sz; CurrentGroupStartIdx += GroupSize) { | |
| if (SortedOffsetsFromBase[CurrentGroupStartIdx] - | |
| SortedOffsetsFromBase[CurrentGroupStartIdx - GroupSize] != | |
| StrideBetweenGroups) | |
| return false; | |
| } | |
| auto CheckGroup = [=](const unsigned StartIdx) -> bool { | |
| auto Indices = seq<unsigned>(StartIdx + 1, Sz); | |
| auto FoundIt = llvm::find_if(Indices, IsEndOfGroupIndex); | |
| unsigned GroupEndIdx = FoundIt != Indices.end() ? *FoundIt : Sz; | |
| return GroupEndIdx - StartIdx == GroupSize; | |
| }; | |
| for (unsigned I = 0; I < Sz; I += GroupSize) { | |
| if (!CheckGroup(I)) | |
| return false; | |
| } | |
| } | |
| Type *StrideTy = DL->getIndexType(Ptr0->getType()); | |
| SPtrInfo.StrideVal = ConstantInt::getSigned(StrideTy, StrideIntVal); | |
| SPtrInfo.Ty = cast<FixedVectorType>(getWidenedType(NewScalarTy, VecSz)); | |
| return true; | |
| } | |
| bool BoUpSLP::analyzeRtStrideCandidate(ArrayRef<Value *> PointerOps, | |
| Type *BaseTy, Align CommonAlignment, | |
| SmallVectorImpl<unsigned> &SortedIndices, | |
| StridedPtrInfo &SPtrInfo, | |
| bool IsLoad) const { | |
| const unsigned Sz = PointerOps.size(); | |
| const unsigned MinProfitableStridedOps = | |
| IsLoad ? Opts.slp_min_strided_loads : Opts.slp_min_strided_stores; | |
| if (Sz * getNumElements(BaseTy) < MinProfitableStridedOps) | |
| return false; | |
| // The element type of the strided access is fixed by the number of distinct | |
| // constant offsets the pointers fall into, which has to divide their number. | |
| auto GetStridedTy = [&](unsigned NumOffsets) -> FixedVectorType * { | |
| Type *NewScalarTy = BaseTy; | |
| if (NumOffsets > 1 || BaseTy->isVectorTy()) | |
| NewScalarTy = Type::getIntNTy( | |
| SE->getContext(), | |
| DL->getTypeSizeInBits(BaseTy).getFixedValue() * NumOffsets); | |
| return dyn_cast<FixedVectorType>( | |
| getWidenedType(NewScalarTy, Sz / NumOffsets)); | |
| }; | |
| auto IsLegalStridedTy = [&](FixedVectorType *StridedTy) { | |
| return StridedTy && TTI->isTypeLegal(StridedTy) && | |
| TTI->isLegalStridedLoadStore(StridedTy, CommonAlignment); | |
| }; | |
| // Determining the offsets below builds SCEVs for every pointer, which is | |
| // expensive. Bail out if the target does not support any strided accesses at | |
| // all. | |
| if (none_of(seq<unsigned>(1, Sz), [&](unsigned NumOffsets) { | |
| return Sz % NumOffsets == 0 && | |
| IsLegalStridedTy(GetStridedTy(NumOffsets)); | |
| })) | |
| return false; | |
| // If each value in `PointerOps` is of the form `%x + Offset` where `Offset` | |
| // is constant, we partition `PointerOps` sequence into subsequences of | |
| // pointers with the same offset. For each offset we record values from | |
| // `PointerOps` and their indicies in `PointerOps`. | |
| SmallDenseMap<int64_t, std::pair<SmallVector<Value *>, SmallVector<unsigned>>> | |
| OffsetToPointerOpIdxMap; | |
| // Track to make sure that only VecSz different stride multiples are consumed | |
| // Prevents cases such as: | |
| // 1, x + 0, x + 1, 2x + 0 from being recognized as legal RT strided as there | |
| // are 2 "0" and 2 "1" offsets and a stride of "x" between both offsets | |
| SmallDenseSet<const SCEV *> StrideMultiples; | |
| for (auto [Idx, Ptr] : enumerate(PointerOps)) { | |
| const SCEV *PtrSCEV = SE->getSCEV(Ptr); | |
| if (!PtrSCEV) | |
| return false; | |
| const auto *Add = dyn_cast<SCEVAddExpr>(PtrSCEV); | |
| int64_t Offset = 0; | |
| const SCEV *StrideMultiple = PtrSCEV; | |
| if (Add) { | |
| // `Offset` is non-zero. | |
| for (int I : seq<int>(Add->getNumOperands())) { | |
| const auto *SC = dyn_cast<SCEVConstant>(Add->getOperand(I)); | |
| if (!SC) | |
| continue; | |
| Offset = SC->getAPInt().getSExtValue(); | |
| if (Offset >= std::numeric_limits<int64_t>::max() - 1) { | |
| Offset = 0; | |
| continue; | |
| } | |
| StrideMultiple = SE->getMinusSCEV(StrideMultiple, SC); | |
| break; | |
| } | |
| } | |
| OffsetToPointerOpIdxMap[Offset].first.push_back(Ptr); | |
| OffsetToPointerOpIdxMap[Offset].second.push_back(Idx); | |
| StrideMultiples.insert(StrideMultiple); | |
| } | |
| unsigned NumOffsets = OffsetToPointerOpIdxMap.size(); | |
| // Quick detour: at this point we can say what the type of strided load would | |
| // be if all the checks pass. Check if this type is legal for the target. | |
| unsigned VecSz = Sz; | |
| if (NumOffsets > 1) { | |
| if (Sz % NumOffsets != 0) | |
| return false; | |
| VecSz = Sz / NumOffsets; | |
| } | |
| if (StrideMultiples.size() != VecSz) | |
| return false; | |
| auto *StridedLoadTy = GetStridedTy(NumOffsets); | |
| if (!IsLegalStridedTy(StridedLoadTy)) | |
| return false; | |
| // Check if the offsets are contiguous and that each group has the required | |
| // size. | |
| SmallVector<int64_t> SortedOffsetsV(NumOffsets); | |
| for (auto [Idx, MapPair] : enumerate(OffsetToPointerOpIdxMap)) { | |
| if (MapPair.second.first.size() != VecSz) | |
| return false; | |
| SortedOffsetsV[Idx] = MapPair.first; | |
| } | |
| sort(SortedOffsetsV); | |
| if (NumOffsets > 1) { | |
| int64_t BaseBytes = DL->getTypeStoreSize(BaseTy); | |
| for (int I : seq<int>(1, SortedOffsetsV.size())) { | |
| if (SortedOffsetsV[I] - SortedOffsetsV[I - 1] != BaseBytes) | |
| return false; | |
| } | |
| } | |
| // Introduce some notation for the explanations below. Let `PointerOps_j` | |
| // denote the subsequence of `PointerOps` with offsets equal to | |
| // `SortedOffsetsV[j]`. Let `SortedIndices_j` be a such that the sequence | |
| // ``` | |
| // PointerOps_j[SortedIndices_j[0]], | |
| // PointerOps_j[SortedIndices_j[1]], | |
| // PointerOps_j[SortedIndices_j[2]], | |
| // ... | |
| // ``` | |
| // is sorted. Also, let `IndicesInAllPointerOps_j` be the vector | |
| // of indices of the subsequence `PointerOps_j` in all of `PointerOps`, | |
| // i.e `PointerOps_j[i] = PointerOps[IndicesInAllPointerOps_j[i]]`. | |
| // The entire sorted `PointerOps` looks like this: | |
| // ``` | |
| // PointerOps_0[SortedIndices_0[0]] = PointerOps[IndicesInAllPointerOps_0[0]], | |
| // PointerOps_1[SortedIndices_1[0]] = PointerOps[IndicesInAllPointerOps_1[0]], | |
| // PointerOps_2[SortedIndices_2[0]] = PointerOps[IndicesInAllPointerOps_2[0]], | |
| // ... | |
| // PointerOps_(NumOffsets - 1)[SortedIndices_(NumOffsets - 1)[0]] = | |
| // PointerOps[IndicesInAllPointerOps_(NumOffsets - 1)[0]], | |
| // | |
| // PointerOps_0[SortedIndices_0[1]] = PointerOps[IndicesInAllPointerOps_0[1]], | |
| // PointerOps_1[SortedIndices_1[1]] = PointerOps[IndicesInAllPointerOps_1[1]], | |
| // PointerOps_2[SortedIndices_2[1]] = PointerOps[IndicesInAllPointerOps_2[1]], | |
| // ... | |
| // PointerOps_(NumOffsets - 1)[SortedIndices_(NumOffsets - 1)[1]] = | |
| // PointerOps[IndicesInAllPointerOps_(NumOffsets - 1)[1]], | |
| // | |
| // PointerOps_0[SortedIndices_0[2]] = PointerOps[IndicesInAllPointerOps_0[2]], | |
| // PointerOps_1[SortedIndices_1[2]] = PointerOps[IndicesInAllPointerOps_1[2]], | |
| // PointerOps_2[SortedIndices_2[2]] = PointerOps[IndicesInAllPointerOps_2[2]], | |
| // ... | |
| // PointerOps_(NumOffsets - 1)[SortedIndices_(NumOffsets - 1)[2]] = | |
| // PointerOps[IndicesInAllPointerOps_(NumOffsets - 1)[2]], | |
| // ... | |
| // ... | |
| // ... | |
| // PointerOps_0[SortedIndices_0[VecSz - 1]] = | |
| // PointerOps[IndicesInAllPointerOps_0[VecSz - 1]], | |
| // PointerOps_1[SortedIndices_1[VecSz - 1]] = | |
| // PointerOps[IndicesInAllPointerOps_1[VecSz - 1]], | |
| // PointerOps_2[SortedIndices_2[VecSz - 1]] = | |
| // PointerOps[IndicesInAllPointerOps_2[VecSz - 1]], | |
| // ... | |
| // PointerOps_(NumOffsets - 1)[SortedIndices_(NumOffsets - 1)[VecSz - 1]] = | |
| // PointerOps[IndicesInAllPointerOps_(NumOffsets - 1)[VecSz - 1]], | |
| // ``` | |
| // In order to be able to generate a strided load, for each `PointerOps_j` | |
| // check that the distance between adjacent pointers are all equal to the same | |
| // value (stride). | |
| // | |
| // As we do that, also calculate SortedIndices. Since we should not modify | |
| // `SortedIndices` unless we know that all the checks succeed, record the | |
| // indicies into `SortedIndicesDraft`. | |
| SmallVector<unsigned> SortedIndicesDraft(Sz); | |
| // Given sorted indices for a particular offset (as calculated by | |
| // calculateRtStride), update the `SortedIndicesDraft` for all of PointerOps. | |
| // Let `Offset` be `SortedOffsetsV[OffsetNum]`. | |
| // \param `OffsetNum` the index of `Offset` in `SortedOffsetsV`. | |
| // \param `IndicesInAllPointerOps` vector of indices of the | |
| // subsequence `PointerOps_OffsetNum` in `PointerOps`, i.e. using the above | |
| // notation `IndicesInAllPointerOps = IndicesInAllPointerOps_OffsetNum`. | |
| // \param `SortedIndicesForOffset = SortedIndices_OffsetNum` | |
| auto UpdateSortedIndices = | |
| [&](SmallVectorImpl<unsigned> &SortedIndicesForOffset, | |
| ArrayRef<unsigned> IndicesInAllPointerOps, const int64_t OffsetNum) { | |
| if (SortedIndicesForOffset.empty()) { | |
| SortedIndicesForOffset.resize(IndicesInAllPointerOps.size()); | |
| std::iota(SortedIndicesForOffset.begin(), | |
| SortedIndicesForOffset.end(), 0); | |
| } | |
| for (const auto [Num, Idx] : enumerate(SortedIndicesForOffset)) { | |
| SortedIndicesDraft[Num * NumOffsets + OffsetNum] = | |
| IndicesInAllPointerOps[Idx]; | |
| } | |
| }; | |
| int64_t LowestOffset = SortedOffsetsV[0]; | |
| ArrayRef<Value *> PointerOps0 = OffsetToPointerOpIdxMap[LowestOffset].first; | |
| SmallVector<unsigned> SortedIndicesForOffset0; | |
| const SCEV *Stride0 = | |
| calculateRtStride(PointerOps0, BaseTy, *DL, *SE, SortedIndicesForOffset0); | |
| if (!Stride0) | |
| return false; | |
| ArrayRef<unsigned> IndicesInAllPointerOps0 = | |
| OffsetToPointerOpIdxMap[LowestOffset].second; | |
| UpdateSortedIndices(SortedIndicesForOffset0, IndicesInAllPointerOps0, 0); | |
| // Now that we know what the common stride and coefficients has to be check | |
| // the remaining `PointerOps_j`. | |
| SmallVector<unsigned> SortedIndicesForOffset; | |
| for (int J : seq<int>(1, NumOffsets)) { | |
| SortedIndicesForOffset.clear(); | |
| int64_t Offset = SortedOffsetsV[J]; | |
| ArrayRef<Value *> PointerOpsForOffset = | |
| OffsetToPointerOpIdxMap[Offset].first; | |
| ArrayRef<unsigned> IndicesInAllPointerOps = | |
| OffsetToPointerOpIdxMap[Offset].second; | |
| const SCEV *StrideWithinGroup = calculateRtStride( | |
| PointerOpsForOffset, BaseTy, *DL, *SE, SortedIndicesForOffset); | |
| if (!StrideWithinGroup || StrideWithinGroup != Stride0) | |
| return false; | |
| UpdateSortedIndices(SortedIndicesForOffset, IndicesInAllPointerOps, J); | |
| } | |
| SortedIndices.clear(); | |
| SortedIndices = std::move(SortedIndicesDraft); | |
| SPtrInfo.StrideSCEV = Stride0; | |
| SPtrInfo.Ty = StridedLoadTy; | |
| return true; | |
| } | |
| BoUpSLP::LoadsState BoUpSLP::canVectorizeLoads( | |
| ArrayRef<Value *> VL, const Value *VL0, SmallVectorImpl<unsigned> &Order, | |
| SmallVectorImpl<Value *> &PointerOps, StridedPtrInfo &SPtrInfo, | |
| unsigned *BestVF, bool TryRecursiveCheck) const { | |
| // Check that a vectorized load would load the same memory as a scalar | |
| // load. For example, we don't want to vectorize loads that are smaller | |
| // than 8-bit. Even though we have a packed struct {<i2, i2, i2, i2>} LLVM | |
| // treats loading/storing it as an i8 struct. If we vectorize loads/stores | |
| // from such a struct, we read/write packed bits disagreeing with the | |
| // unvectorized version. | |
| if (BestVF) | |
| *BestVF = 0; | |
| if (areKnownNonVectorizableLoads(VL)) | |
| return LoadsState::Gather; | |
| Type *ScalarTy = VL0->getType(); | |
| if (DL->getTypeSizeInBits(ScalarTy) != DL->getTypeAllocSizeInBits(ScalarTy)) | |
| return LoadsState::Gather; | |
| // The undef and poison lanes are covered by a single wide load of the | |
| // consecutive loads. | |
| if (any_of(VL, IsaPred<UndefValue>)) | |
| return canWidenLoadsOverUndefLanes(VL, *DL, *SE, *AC, *DT, *TLI) | |
| ? LoadsState::Vectorize | |
| : LoadsState::Gather; | |
| // Make sure all loads in the bundle are simple - we can't vectorize | |
| // atomic or volatile loads. | |
| PointerOps.clear(); | |
| const size_t Sz = VL.size(); | |
| PointerOps.resize(Sz); | |
| auto *POIter = PointerOps.begin(); | |
| for (Value *V : VL) { | |
| auto *L = dyn_cast<LoadInst>(V); | |
| if (!L || !L->isSimple()) | |
| return LoadsState::Gather; | |
| *POIter = L->getPointerOperand(); | |
| ++POIter; | |
| } | |
| Order.clear(); | |
| // Check the order of pointer operands or that all pointers are the same. | |
| bool IsSorted = sortPtrAccesses(PointerOps, ScalarTy, *DL, *SE, Order); | |
| auto *VecTy = dyn_cast<VectorType>(getWidenedType(ScalarTy, Sz)); | |
| if (!VecTy) | |
| return LoadsState::Gather; | |
| Align CommonAlignment = computeCommonAlignment<LoadInst>(VL); | |
| // Cache masked gather legality - both the !IsSorted path below and the | |
| // post-branch check use the same VecTy/CommonAlignment, and the underlying | |
| // TTI calls are virtual. | |
| std::optional<bool> MaskedGatherLegal; | |
| auto IsMaskedGatherLegal = [&, TTI = TTI] { | |
| if (!MaskedGatherLegal) | |
| MaskedGatherLegal = | |
| TTI->isLegalMaskedGather(VecTy, CommonAlignment) && | |
| !TTI->forceScalarizeMaskedGather(VecTy, CommonAlignment); | |
| return *MaskedGatherLegal; | |
| }; | |
| if (!IsSorted) { | |
| // Check for a group of loads, each selecting its address (directly, or | |
| // via a constant-offset GEP) between the same two candidate base | |
| // pointers - the shape if-converted, fully-unrolled loop bodies of the | |
| // form `x = cond ? A[i] : B[i]` take. If found, model it as two masked | |
| // loads (one per candidate) blended by the (vectorized) condition, | |
| // rather than falling back to a gather of the individual scalar loads. | |
| Value *TrueBase = nullptr; | |
| Value *FalseBase = nullptr; | |
| SmallVector<Value *> Conditions; | |
| if (isSelectedBaseLoad(ScalarTy, PointerOps, *DL, TrueBase, FalseBase, | |
| Conditions) && | |
| TTI->isLegalMaskedLoad(VecTy, computeBlendedLoadBaseAlignment(VL, *DL), | |
| cast<LoadInst>(VL0)->getPointerAddressSpace())) | |
| return LoadsState::BlendedLoadVectorize; | |
| if (analyzeRtStrideCandidate(PointerOps, ScalarTy, CommonAlignment, Order, | |
| SPtrInfo, /*isLoad=*/true)) | |
| return LoadsState::StridedVectorize; | |
| if (!IsMaskedGatherLegal()) | |
| return LoadsState::Gather; | |
| // The gather address vector requires pointers of the same type. | |
| Value *FrontPtr = PointerOps.front(); | |
| Type *PtrTy = FrontPtr->getType(); | |
| if (!all_of(PointerOps, | |
| [&](Value *P) { | |
| return P->getType() == PtrTy && | |
| arePointersCompatible(P, FrontPtr, *TLI, | |
| Opts.slp_recursion_max_depth); | |
| }) && | |
| !isCopyableGEPAddressVector(PointerOps)) | |
| return LoadsState::Gather; | |
| } else { | |
| Value *Ptr0; | |
| Value *PtrN; | |
| if (Order.empty()) { | |
| Ptr0 = PointerOps.front(); | |
| PtrN = PointerOps.back(); | |
| } else { | |
| Ptr0 = PointerOps[Order.front()]; | |
| PtrN = PointerOps[Order.back()]; | |
| } | |
| // sortPtrAccesses validates getPointersDiff for all pointers relative to | |
| // PointerOps[0], so compute the span using PointerOps[0] as intermediate: | |
| // Diff = offset(PtrN) - offset(Ptr0) relative to PointerOps[0] | |
| std::optional<int64_t> Diff0 = | |
| getPointersDiff(ScalarTy, PointerOps[0], ScalarTy, Ptr0, *DL, *SE); | |
| std::optional<int64_t> DiffN = | |
| getPointersDiff(ScalarTy, PointerOps[0], ScalarTy, PtrN, *DL, *SE); | |
| assert(Diff0 && DiffN && | |
| "sortPtrAccesses should have validated these pointers"); | |
| int64_t Diff = *DiffN - *Diff0; | |
| // Check that the sorted loads are consecutive. | |
| if (static_cast<uint64_t>(Diff) == Sz - 1) | |
| return LoadsState::Vectorize; | |
| if (isMaskedLoadCompress( | |
| VL, PointerOps, Order, *TTI, *DL, *SE, *AC, *DT, *TLI, CostKind, | |
| [&](Value *V) { | |
| return areAllUsersVectorized(cast<Instruction>(V), | |
| UserIgnoreList); | |
| }, | |
| Opts.slp_revec, Cache)) | |
| return LoadsState::CompressVectorize; | |
| // Widened strided loads must be legal for every group, not just the first | |
| // pointer, which may have a stronger alignment than the remaining loads. | |
| if (analyzeConstantStrideCandidate(PointerOps, ScalarTy, CommonAlignment, | |
| Order, Diff, Ptr0, SPtrInfo)) | |
| return LoadsState::StridedVectorize; | |
| } | |
| if (!IsMaskedGatherLegal()) | |
| return LoadsState::Gather; | |
| // Correctly identify compare the cost of loads + shuffles rather than | |
| // strided/masked gather loads. Returns true if vectorized + shuffles | |
| // representation is better than just gather. | |
| auto CheckForShuffledLoads = [&, &TTI = *TTI](Align CommonAlignment, | |
| unsigned *BestVF, | |
| bool ProfitableGatherPointers) { | |
| if (BestVF) | |
| *BestVF = 0; | |
| // Compare masked gather cost and loads + insert subvector costs. | |
| auto [ScalarGEPCost, VectorGEPCost] = | |
| getGEPCosts(TTI, PointerOps, PointerOps.front(), Instruction::Load, | |
| CostKind, ScalarTy, VecTy); | |
| // Estimate the cost of masked gather GEP. If not a splat, roughly | |
| // estimate as a buildvector, otherwise estimate as splat. | |
| APInt DemandedElts = APInt::getAllOnes(Sz); | |
| Type *PtrScalarTy = PointerOps.front()->getType()->getScalarType(); | |
| auto *PtrVecTy = cast<VectorType>(getWidenedType(PtrScalarTy, Sz)); | |
| // Cache the underlying object of PointerOps.front() - it is invariant | |
| // across the per-V comparisons below and getUnderlyingObject walks | |
| // GEP/cast chains. | |
| const Value *FrontUO = getUnderlyingObject(PointerOps.front()); | |
| if (static_cast<unsigned>(count_if( | |
| PointerOps, IsaPred<GetElementPtrInst>)) < PointerOps.size() - 1 || | |
| any_of(PointerOps, | |
| [&](Value *V) { return getUnderlyingObject(V) != FrontUO; })) | |
| VectorGEPCost += | |
| getScalarizationOverhead(TTI, Opts.slp_revec, PtrScalarTy, PtrVecTy, | |
| DemandedElts, /*Insert=*/true, | |
| /*Extract=*/false, CostKind); | |
| else | |
| VectorGEPCost += | |
| getScalarizationOverhead(TTI, Opts.slp_revec, PtrScalarTy, PtrVecTy, | |
| APInt::getOneBitSet(Sz, 0), | |
| /*Insert=*/true, /*Extract=*/false, | |
| CostKind) + | |
| getShuffleCost(TTI, TTI::SK_Broadcast, PtrVecTy, CostKind); | |
| // The cost of scalar loads. | |
| InstructionCost ScalarLoadsCost = | |
| accumulate(VL, InstructionCost(), | |
| [&](InstructionCost C, Value *V) { | |
| return C + TTI.getInstructionCost(cast<Instruction>(V), | |
| CostKind); | |
| }) + | |
| ScalarGEPCost; | |
| // The cost of masked gather. | |
| InstructionCost MaskedGatherCost = | |
| TTI.getMemIntrinsicInstrCost( | |
| MemIntrinsicCostAttributes(Intrinsic::masked_gather, VecTy, | |
| cast<LoadInst>(VL0)->getPointerOperand(), | |
| /*VariableMask=*/false, CommonAlignment), | |
| CostKind) + | |
| (ProfitableGatherPointers ? 0 : VectorGEPCost); | |
| InstructionCost GatherCost = | |
| getScalarizationOverhead(TTI, Opts.slp_revec, ScalarTy, VecTy, | |
| DemandedElts, | |
| /*Insert=*/true, | |
| /*Extract=*/false, CostKind) + | |
| ScalarLoadsCost; | |
| // The list of loads is small or perform partial check already - directly | |
| // compare masked gather cost and gather cost. | |
| constexpr unsigned ListLimit = 4; | |
| if (!TryRecursiveCheck || VL.size() < ListLimit) | |
| return MaskedGatherCost - GatherCost >= -getCostThreshold(); | |
| unsigned Sz = DL->getTypeSizeInBits(ScalarTy); | |
| unsigned MinVF = getMinVF(2 * Sz); | |
| DemandedElts.clearAllBits(); | |
| // Iterate through possible vectorization factors and check if vectorized + | |
| // shuffles is better than just gather. | |
| for (unsigned VF = getFloorFullVectorNumberOfElements( | |
| TTI, ScalarTy, VL.size() - 1, Opts.slp_revec); | |
| VF >= MinVF; VF = getFloorFullVectorNumberOfElements( | |
| TTI, ScalarTy, VF - 1, Opts.slp_revec)) { | |
| SmallVector<std::pair<unsigned, LoadsState>> States; | |
| for (unsigned Cnt = 0, End = VL.size(); Cnt < End; Cnt += VF) { | |
| const unsigned SliceVF = std::min(VF, End - Cnt); | |
| ArrayRef<Value *> Slice = VL.slice(Cnt, SliceVF); | |
| SmallVector<unsigned> Order; | |
| SmallVector<Value *> PointerOps; | |
| LoadsState LS = canVectorizeLoads(Slice, Slice.front(), Order, | |
| PointerOps, SPtrInfo, BestVF, | |
| /*TryRecursiveCheck=*/false); | |
| // Check that the sorted loads are consecutive. | |
| if (LS == LoadsState::Gather) { | |
| if (BestVF) { | |
| DemandedElts.setAllBits(); | |
| break; | |
| } | |
| DemandedElts.setBits(Cnt, Cnt + SliceVF); | |
| continue; | |
| } | |
| // If need the reorder - consider as high-cost masked gather for now. | |
| if ((LS == LoadsState::Vectorize || | |
| LS == LoadsState::StridedVectorize || | |
| LS == LoadsState::CompressVectorize) && | |
| !Order.empty() && !isReverseOrder(Order)) | |
| LS = LoadsState::ScatterVectorize; | |
| States.emplace_back(Cnt, LS); | |
| } | |
| if (DemandedElts.isAllOnes()) | |
| // All loads gathered - try smaller VF. | |
| continue; | |
| // Can be vectorized later as a serie of loads/insertelements. | |
| InstructionCost VecLdCost = 0; | |
| if (!DemandedElts.isZero()) { | |
| VecLdCost = getScalarizationOverhead(TTI, Opts.slp_revec, ScalarTy, | |
| VecTy, DemandedElts, | |
| /*Insert=*/true, | |
| /*Extract=*/false, CostKind) + | |
| ScalarGEPCost; | |
| for (unsigned Idx : seq<unsigned>(VL.size())) | |
| if (DemandedElts[Idx]) | |
| VecLdCost += | |
| TTI.getInstructionCost(cast<Instruction>(VL[Idx]), CostKind); | |
| } | |
| for (const auto &[SliceStart, LS] : States) { | |
| const unsigned SliceVF = std::min<unsigned>(VF, VL.size() - SliceStart); | |
| auto *SubVecTy = cast<VectorType>(getWidenedType(ScalarTy, SliceVF)); | |
| auto *LI0 = cast<LoadInst>(VL[SliceStart]); | |
| InstructionCost VectorGEPCost = | |
| (LS == LoadsState::ScatterVectorize && ProfitableGatherPointers) | |
| ? 0 | |
| : getGEPCosts(TTI, | |
| ArrayRef(PointerOps).slice(SliceStart, SliceVF), | |
| LI0->getPointerOperand(), Instruction::Load, | |
| CostKind, ScalarTy, SubVecTy) | |
| .second; | |
| if (LS == LoadsState::ScatterVectorize) { | |
| if (static_cast<unsigned>( | |
| count_if(PointerOps, IsaPred<GetElementPtrInst>)) < | |
| PointerOps.size() - 1 || | |
| any_of(PointerOps, [&](Value *V) { | |
| return getUnderlyingObject(V) != FrontUO; | |
| })) | |
| VectorGEPCost += getScalarizationOverhead( | |
| TTI, Opts.slp_revec, ScalarTy, SubVecTy, | |
| APInt::getAllOnes(SliceVF), | |
| /*Insert=*/true, /*Extract=*/false, CostKind); | |
| else | |
| VectorGEPCost += | |
| getScalarizationOverhead( | |
| TTI, Opts.slp_revec, ScalarTy, SubVecTy, | |
| APInt::getOneBitSet(SliceVF, 0), | |
| /*Insert=*/true, /*Extract=*/false, CostKind) + | |
| getShuffleCost(TTI, TTI::SK_Broadcast, SubVecTy, CostKind); | |
| } | |
| switch (LS) { | |
| case LoadsState::Vectorize: | |
| VecLdCost += TTI.getMemoryOpCost( | |
| Instruction::Load, SubVecTy, LI0->getAlign(), | |
| LI0->getPointerAddressSpace(), CostKind, | |
| TTI::getOperandInfo(LI0->getPointerOperand())) + | |
| VectorGEPCost; | |
| break; | |
| case LoadsState::StridedVectorize: | |
| VecLdCost += TTI.getMemIntrinsicInstrCost( | |
| MemIntrinsicCostAttributes( | |
| Intrinsic::experimental_vp_strided_load, | |
| SubVecTy, LI0->getPointerOperand(), | |
| /*VariableMask=*/false, CommonAlignment), | |
| CostKind) + | |
| VectorGEPCost; | |
| break; | |
| case LoadsState::CompressVectorize: | |
| VecLdCost += | |
| TTI.getMemIntrinsicInstrCost( | |
| MemIntrinsicCostAttributes(Intrinsic::masked_load, SubVecTy, | |
| CommonAlignment, | |
| LI0->getPointerAddressSpace()), | |
| CostKind) + | |
| getShuffleCost(TTI, TTI::SK_PermuteSingleSrc, SubVecTy, CostKind); | |
| break; | |
| case LoadsState::ScatterVectorize: | |
| VecLdCost += TTI.getMemIntrinsicInstrCost( | |
| MemIntrinsicCostAttributes( | |
| Intrinsic::masked_gather, SubVecTy, | |
| LI0->getPointerOperand(), | |
| /*VariableMask=*/false, CommonAlignment), | |
| CostKind) + | |
| VectorGEPCost; | |
| break; | |
| case LoadsState::BlendedLoadVectorize: | |
| // Two masked loads (one per candidate base) plus a select; no address | |
| // vector is materialized, so VectorGEPCost is skipped. | |
| VecLdCost += | |
| getBlendedLoadCost(TTI, SubVecTy, | |
| computeBlendedLoadBaseAlignment( | |
| VL.slice(SliceStart, SliceVF), *DL), | |
| LI0->getPointerAddressSpace(), CostKind); | |
| break; | |
| case LoadsState::Gather: | |
| llvm_unreachable("Gathers are not added to States"); | |
| } | |
| SmallVector<int> ShuffleMask(VL.size()); | |
| const unsigned SliceIdx = SliceStart / VF; | |
| for (int Idx : seq<int>(VL.size())) | |
| ShuffleMask[Idx] = Idx / VF == SliceIdx ? VL.size() + Idx % VF : Idx; | |
| if (SliceStart > 0) | |
| VecLdCost += | |
| getShuffleCost(TTI, TTI::SK_InsertSubvector, VecTy, CostKind, | |
| ShuffleMask, SliceStart, SubVecTy); | |
| } | |
| // If masked gather cost is higher - better to vectorize, so | |
| // consider it as a gather node. It will be better estimated | |
| // later. | |
| if (MaskedGatherCost >= VecLdCost && | |
| VecLdCost - GatherCost < -getCostThreshold()) { | |
| if (BestVF) | |
| *BestVF = VF; | |
| return true; | |
| } | |
| } | |
| return MaskedGatherCost - GatherCost >= -getCostThreshold(); | |
| }; | |
| // TODO: need to improve analysis of the pointers, if not all of them are | |
| // GEPs or have > 2 operands, we end up with a gather node, which just | |
| // increases the cost. | |
| Loop *L = LI->getLoopFor(cast<LoadInst>(VL0)->getParent()); | |
| bool ProfitableGatherPointers = | |
| L && Sz > 2 && static_cast<unsigned>(count_if(PointerOps, [L](Value *V) { | |
| return L->isLoopInvariant(V); | |
| })) <= Sz / 2; | |
| if (ProfitableGatherPointers || all_of(PointerOps, [](Value *P) { | |
| auto *GEP = dyn_cast<GetElementPtrInst>(P); | |
| return (!GEP && doesNotNeedToBeScheduled(P)) || | |
| (GEP && GEP->getNumOperands() == 2 && | |
| isa<Constant, Instruction>(GEP->getOperand(1))); | |
| })) { | |
| // Check if potential masked gather can be represented as series | |
| // of loads + insertsubvectors. | |
| // If masked gather cost is higher - better to vectorize, so | |
| // consider it as a gather node. It will be better estimated | |
| // later. | |
| if (!TryRecursiveCheck || !CheckForShuffledLoads(CommonAlignment, BestVF, | |
| ProfitableGatherPointers)) | |
| return LoadsState::ScatterVectorize; | |
| } | |
| return LoadsState::Gather; | |
| } | |
| std::optional<BoUpSLP::OrdersType> | |
| BoUpSLP::findPartiallyOrderedLoads(const BoUpSLP::TreeEntry &TE) { | |
| assert(TE.isGather() && "Expected gather node only."); | |
| Type *ScalarTy = TE.Scalars[0]->getType(); | |
| SmallVector<Value *> Ptrs; | |
| Ptrs.reserve(TE.Scalars.size()); | |
| SmallVector<BasicBlock *> BBs; | |
| BBs.reserve(TE.Scalars.size()); | |
| for (Value *V : TE.Scalars) { | |
| auto *L = dyn_cast<LoadInst>(V); | |
| if (!L || !L->isSimple()) | |
| return std::nullopt; | |
| Ptrs.push_back(L->getPointerOperand()); | |
| BBs.push_back(L->getParent()); | |
| } | |
| BoUpSLP::OrdersType Order; | |
| if (!LoadEntriesToVectorize.contains(TE.Idx) && | |
| clusterSortPtrAccesses(Ptrs, BBs, ScalarTy, *DL, *SE, | |
| Opts.slp_recursion_max_depth, Order, Cache)) | |
| return std::move(Order); | |
| return std::nullopt; | |
| } | |
| /// Check if two insertelement instructions are from the same buildvector. | |
| static bool areTwoInsertFromSameBuildVector( | |
| InsertElementInst *VU, InsertElementInst *V, | |
| function_ref<Value *(InsertElementInst *)> GetBaseOperand) { | |
| // Instructions must be from the same basic blocks. | |
| if (VU->getParent() != V->getParent()) | |
| return false; | |
| // Checks if 2 insertelements are from the same buildvector. | |
| if (VU->getType() != V->getType()) | |
| return false; | |
| // Multiple used inserts are separate nodes. | |
| if (!VU->hasOneUse() && !V->hasOneUse()) | |
| return false; | |
| auto *IE1 = VU; | |
| auto *IE2 = V; | |
| std::optional<unsigned> Idx1 = getElementIndex(IE1); | |
| std::optional<unsigned> Idx2 = getElementIndex(IE2); | |
| if (Idx1 == std::nullopt || Idx2 == std::nullopt) | |
| return false; | |
| // Go through the vector operand of insertelement instructions trying to find | |
| // either VU as the original vector for IE2 or V as the original vector for | |
| // IE1. | |
| SmallBitVector ReusedIdx(VU->getType()->getElementCount().getKnownMinValue()); | |
| bool IsReusedIdx = false; | |
| do { | |
| if (IE2 == VU && !IE1) | |
| return VU->hasOneUse(); | |
| if (IE1 == V && !IE2) | |
| return V->hasOneUse(); | |
| if (IE1 && IE1 != V) { | |
| unsigned Idx1 = getElementIndex(IE1).value_or(*Idx2); | |
| IsReusedIdx |= ReusedIdx.test(Idx1); | |
| ReusedIdx.set(Idx1); | |
| if ((IE1 != VU && !IE1->hasOneUse()) || IsReusedIdx) | |
| IE1 = nullptr; | |
| else | |
| IE1 = dyn_cast_if_present<InsertElementInst>(GetBaseOperand(IE1)); | |
| } | |
| if (IE2 && IE2 != VU) { | |
| unsigned Idx2 = getElementIndex(IE2).value_or(*Idx1); | |
| IsReusedIdx |= ReusedIdx.test(Idx2); | |
| ReusedIdx.set(Idx2); | |
| if ((IE2 != V && !IE2->hasOneUse()) || IsReusedIdx) | |
| IE2 = nullptr; | |
| else | |
| IE2 = dyn_cast_if_present<InsertElementInst>(GetBaseOperand(IE2)); | |
| } | |
| } while (!IsReusedIdx && (IE1 || IE2)); | |
| return false; | |
| } | |
| std::optional<BoUpSLP::OrdersType> | |
| BoUpSLP::getReorderingData(const TreeEntry &TE, bool TopToBottom, | |
| bool IgnoreReorder) { | |
| // No need to reorder if need to shuffle reuses, still need to shuffle the | |
| // node. | |
| if (!TE.ReuseShuffleIndices.empty()) { | |
| if (isSplat(TE.Scalars)) | |
| return std::nullopt; | |
| // Check if reuse shuffle indices can be improved by reordering. | |
| // For this, check that reuse mask is "clustered", i.e. each scalar values | |
| // is used once in each submask of size <number_of_scalars>. | |
| // Example: 4 scalar values. | |
| // ReuseShuffleIndices mask: 0, 1, 2, 3, 3, 2, 0, 1 - clustered. | |
| // 0, 1, 2, 3, 3, 3, 1, 0 - not clustered, because | |
| // element 3 is used twice in the second submask. | |
| unsigned Sz = TE.Scalars.size(); | |
| if (TE.isGather()) { | |
| if (std::optional<OrdersType> CurrentOrder = | |
| findReusedOrderedScalars(TE, TopToBottom, IgnoreReorder)) { | |
| SmallVector<int> Mask; | |
| fixupOrderingIndices(*CurrentOrder); | |
| inversePermutation(*CurrentOrder, Mask); | |
| addMask(Mask, TE.ReuseShuffleIndices); | |
| OrdersType Res(TE.getVectorFactor(), TE.getVectorFactor()); | |
| unsigned Sz = TE.Scalars.size(); | |
| for (int K = 0, E = TE.getVectorFactor() / Sz; K < E; ++K) { | |
| for (auto [I, Idx] : enumerate(ArrayRef(Mask).slice(K * Sz, Sz))) | |
| if (Idx != PoisonMaskElem) | |
| Res[Idx + K * Sz] = I + K * Sz; | |
| } | |
| return std::move(Res); | |
| } | |
| } | |
| if (Sz == 2 && TE.getVectorFactor() == 4 && | |
| getNumberOfParts( | |
| getWidenedType(getValueType(TE.Scalars.front(), Opts.slp_revec), | |
| 2 * TE.getVectorFactor()), | |
| getValueType(TE.Scalars.front(), Opts.slp_revec)) == 1) | |
| return std::nullopt; | |
| if (TE.ReuseShuffleIndices.size() % Sz != 0) | |
| return std::nullopt; | |
| if (!ShuffleVectorInst::isOneUseSingleSourceMask(TE.ReuseShuffleIndices, | |
| Sz)) { | |
| SmallVector<int> ReorderMask(Sz, PoisonMaskElem); | |
| if (TE.ReorderIndices.empty()) | |
| std::iota(ReorderMask.begin(), ReorderMask.end(), 0); | |
| else | |
| inversePermutation(TE.ReorderIndices, ReorderMask); | |
| addMask(ReorderMask, TE.ReuseShuffleIndices); | |
| unsigned VF = ReorderMask.size(); | |
| OrdersType ResOrder(VF, VF); | |
| unsigned NumParts = divideCeil(VF, Sz); | |
| SmallBitVector UsedVals(NumParts); | |
| for (unsigned I = 0; I < VF; I += Sz) { | |
| int Val = PoisonMaskElem; | |
| unsigned UndefCnt = 0; | |
| unsigned Limit = std::min(Sz, VF - I); | |
| if (any_of(ArrayRef(ReorderMask).slice(I, Limit), | |
| [&](int Idx) { | |
| if (Val == PoisonMaskElem && Idx != PoisonMaskElem) | |
| Val = Idx; | |
| if (Idx == PoisonMaskElem) | |
| ++UndefCnt; | |
| return Idx != PoisonMaskElem && Idx != Val; | |
| }) || | |
| Val >= static_cast<int>(NumParts) || Val == PoisonMaskElem || | |
| UsedVals.test(Val) || UndefCnt > Sz / 2) | |
| return std::nullopt; | |
| UsedVals.set(Val); | |
| for (unsigned K = 0; K < NumParts; ++K) { | |
| unsigned Idx = Val + Sz * K; | |
| if (Idx < VF && I + K < VF) | |
| ResOrder[Idx] = I + K; | |
| } | |
| } | |
| return std::move(ResOrder); | |
| } | |
| unsigned VF = TE.getVectorFactor(); | |
| // Try build correct order for extractelement instructions. | |
| SmallVector<int> ReusedMask(TE.ReuseShuffleIndices.begin(), | |
| TE.ReuseShuffleIndices.end()); | |
| if (TE.hasState() && TE.getOpcode() == Instruction::ExtractElement && | |
| !TE.hasCopyableElements() && | |
| all_of(make_isa_range<Instruction>(TE.Scalars), [Sz](Instruction *I) { | |
| std::optional<unsigned> Idx = getExtractIndex(I); | |
| return Idx && *Idx < Sz; | |
| })) { | |
| assert(!TE.isAltShuffle() && "Alternate instructions are only supported " | |
| "by BinaryOperator and CastInst."); | |
| SmallVector<int> ReorderMask(Sz, PoisonMaskElem); | |
| if (TE.ReorderIndices.empty()) | |
| std::iota(ReorderMask.begin(), ReorderMask.end(), 0); | |
| else | |
| inversePermutation(TE.ReorderIndices, ReorderMask); | |
| for (unsigned I = 0; I < VF; ++I) { | |
| int &Idx = ReusedMask[I]; | |
| if (Idx == PoisonMaskElem) | |
| continue; | |
| Value *V = TE.Scalars[ReorderMask[Idx]]; | |
| std::optional<unsigned> EI = getExtractIndex(cast<Instruction>(V)); | |
| Idx = std::distance(ReorderMask.begin(), find(ReorderMask, *EI)); | |
| } | |
| } | |
| // Build the order of the VF size, need to reorder reuses shuffles, they are | |
| // always of VF size. | |
| OrdersType ResOrder(VF); | |
| std::iota(ResOrder.begin(), ResOrder.end(), 0); | |
| auto *It = ResOrder.begin(); | |
| for (unsigned K = 0; K < VF; K += Sz) { | |
| OrdersType CurrentOrder(TE.ReorderIndices); | |
| SmallVector<int> SubMask{ArrayRef(ReusedMask).slice(K, Sz)}; | |
| if (SubMask.front() == PoisonMaskElem) | |
| std::iota(SubMask.begin(), SubMask.end(), 0); | |
| reorderOrder(CurrentOrder, SubMask); | |
| transform(CurrentOrder, It, [K](unsigned Pos) { return Pos + K; }); | |
| std::advance(It, Sz); | |
| } | |
| if (TE.isGather() && all_of(enumerate(ResOrder), [](const auto &Data) { | |
| return Data.index() == Data.value(); | |
| })) | |
| return std::nullopt; // No need to reorder. | |
| return std::move(ResOrder); | |
| } | |
| if (TE.State == TreeEntry::StridedVectorize && !TopToBottom && | |
| (!TE.UserTreeIndex || !TE.UserTreeIndex.UserTE->hasState() || | |
| !Instruction::isBinaryOp(TE.UserTreeIndex.UserTE->getOpcode())) && | |
| (TE.ReorderIndices.empty() || TE.isReverse())) | |
| return std::nullopt; | |
| if (TE.State == TreeEntry::SplitVectorize || | |
| ((TE.State == TreeEntry::Vectorize || | |
| TE.State == TreeEntry::StridedVectorize || | |
| TE.State == TreeEntry::ExpandVectorize || | |
| TE.State == TreeEntry::CompressVectorize || | |
| TE.State == TreeEntry::BlendedLoadVectorize) && | |
| (isa<LoadInst, ExtractElementInst, ExtractValueInst>(TE.getMainOp()) || | |
| (TopToBottom && isa<StoreInst, InsertElementInst, InsertValueInst>( | |
| TE.getMainOp()))))) { | |
| assert((TE.State == TreeEntry::SplitVectorize || !TE.isAltShuffle()) && | |
| "Alternate instructions are only supported by " | |
| "BinaryOperator and CastInst."); | |
| return TE.ReorderIndices; | |
| } | |
| if (!TopToBottom && IgnoreReorder && TE.State == TreeEntry::Vectorize && | |
| TE.isAltShuffle()) { | |
| assert(TE.ReuseShuffleIndices.empty() && | |
| "ReuseShuffleIndices should be " | |
| "empty for alternate instructions."); | |
| SmallVector<int> Mask; | |
| TE.buildAltOpShuffleMask( | |
| [&](Instruction *I) { | |
| assert(TE.getMatchingMainOpOrAltOp(I) && | |
| "Unexpected main/alternate opcode"); | |
| return isAlternateInstruction(I, TE.getMainOp(), TE.getAltOp(), *TLI); | |
| }, | |
| Mask); | |
| const int VF = TE.getVectorFactor(); | |
| OrdersType ResOrder(VF, VF); | |
| for (unsigned I : seq<unsigned>(VF)) { | |
| if (Mask[I] == PoisonMaskElem) | |
| continue; | |
| ResOrder[Mask[I] % VF] = I; | |
| } | |
| return std::move(ResOrder); | |
| } | |
| if (!TE.ReorderIndices.empty()) | |
| return TE.ReorderIndices; | |
| if (TE.State == TreeEntry::Vectorize && TE.getOpcode() == Instruction::PHI) { | |
| if (!TE.ReorderIndices.empty()) | |
| return TE.ReorderIndices; | |
| SmallVector<Instruction *> UserBVHead(TE.Scalars.size()); | |
| for (auto [I, V] : | |
| make_filter_range(zip(UserBVHead, TE.Scalars), [](const auto &P) { | |
| Value *V = std::get<1>(P); | |
| return !isa<Constant>(V) && V->hasNUsesOrMore(1); | |
| })) { | |
| auto *II = dyn_cast<InsertElementInst>(*V->user_begin()); | |
| if (!II) | |
| continue; | |
| Instruction *BVHead = nullptr; | |
| BasicBlock *BB = II->getParent(); | |
| while (II && II->hasOneUse() && II->getParent() == BB) { | |
| BVHead = II; | |
| II = dyn_cast<InsertElementInst>(II->getOperand(0)); | |
| } | |
| I = BVHead; | |
| } | |
| auto CompareByBasicBlocks = [&](BasicBlock *BB1, BasicBlock *BB2) { | |
| assert(BB1 != BB2 && "Expected different basic blocks."); | |
| if (!DT->isReachableFromEntry(BB1)) | |
| return false; | |
| if (!DT->isReachableFromEntry(BB2)) | |
| return true; | |
| auto *NodeA = DT->getNode(BB1); | |
| auto *NodeB = DT->getNode(BB2); | |
| assert(NodeA && "Should only process reachable instructions"); | |
| assert(NodeB && "Should only process reachable instructions"); | |
| assert((NodeA == NodeB) == | |
| (NodeA->getDFSNumIn() == NodeB->getDFSNumIn()) && | |
| "Different nodes should have different DFS numbers"); | |
| return NodeA->getDFSNumIn() < NodeB->getDFSNumIn(); | |
| }; | |
| auto PHICompare = [&](unsigned I1, unsigned I2) { | |
| Value *V1 = TE.Scalars[I1]; | |
| Value *V2 = TE.Scalars[I2]; | |
| if (V1 == V2 || (V1->use_empty() && V2->use_empty())) | |
| return false; | |
| if (isa<PoisonValue>(V1)) | |
| return true; | |
| if (isa<PoisonValue>(V2)) | |
| return false; | |
| if (V1->getNumUses() < V2->getNumUses()) | |
| return true; | |
| if (V1->getNumUses() > V2->getNumUses()) | |
| return false; | |
| auto *FirstUserOfPhi1 = cast<Instruction>(*V1->user_begin()); | |
| auto *FirstUserOfPhi2 = cast<Instruction>(*V2->user_begin()); | |
| if (FirstUserOfPhi1->getParent() != FirstUserOfPhi2->getParent()) | |
| return CompareByBasicBlocks(FirstUserOfPhi1->getParent(), | |
| FirstUserOfPhi2->getParent()); | |
| auto *IE1 = dyn_cast<InsertElementInst>(FirstUserOfPhi1); | |
| auto *IE2 = dyn_cast<InsertElementInst>(FirstUserOfPhi2); | |
| auto *EE1 = dyn_cast<ExtractElementInst>(FirstUserOfPhi1); | |
| auto *EE2 = dyn_cast<ExtractElementInst>(FirstUserOfPhi2); | |
| if (IE1 && !IE2) | |
| return true; | |
| if (!IE1 && IE2) | |
| return false; | |
| if (IE1 && IE2) { | |
| if (UserBVHead[I1] && !UserBVHead[I2]) | |
| return true; | |
| if (!UserBVHead[I1]) | |
| return false; | |
| if (UserBVHead[I1] == UserBVHead[I2]) | |
| return getElementIndex(IE1) < getElementIndex(IE2); | |
| if (UserBVHead[I1]->getParent() != UserBVHead[I2]->getParent()) | |
| return CompareByBasicBlocks(UserBVHead[I1]->getParent(), | |
| UserBVHead[I2]->getParent()); | |
| return UserBVHead[I1]->comesBefore(UserBVHead[I2]); | |
| } | |
| if (EE1 && !EE2) | |
| return true; | |
| if (!EE1 && EE2) | |
| return false; | |
| if (EE1 && EE2) { | |
| auto *Inst1 = dyn_cast<Instruction>(EE1->getOperand(0)); | |
| auto *Inst2 = dyn_cast<Instruction>(EE2->getOperand(0)); | |
| auto *P1 = dyn_cast<Argument>(EE1->getOperand(0)); | |
| auto *P2 = dyn_cast<Argument>(EE2->getOperand(0)); | |
| if (!Inst2 && !P2) | |
| return Inst1 || P1; | |
| if (EE1->getOperand(0) == EE2->getOperand(0)) | |
| return getElementIndex(EE1) < getElementIndex(EE2); | |
| if (!Inst1 && Inst2) | |
| return false; | |
| if (Inst1 && Inst2) { | |
| if (Inst1->getParent() != Inst2->getParent()) | |
| return CompareByBasicBlocks(Inst1->getParent(), Inst2->getParent()); | |
| return Inst1->comesBefore(Inst2); | |
| } | |
| if (!P1 && P2) | |
| return false; | |
| assert(P1 && P2 && | |
| "Expected either instructions or arguments vector operands."); | |
| return P1->getArgNo() < P2->getArgNo(); | |
| } | |
| return false; | |
| }; | |
| OrdersType Phis(TE.Scalars.size()); | |
| std::iota(Phis.begin(), Phis.end(), 0); | |
| stable_sort(Phis, PHICompare); | |
| if (isIdentityOrder(Phis)) | |
| return std::nullopt; // No need to reorder. | |
| return std::move(Phis); | |
| } | |
| if (TE.isGather() && | |
| (!TE.hasState() || !TE.isAltShuffle() || | |
| ScalarsInSplitNodes.contains(TE.getMainOp())) && | |
| allSameType(TE.Scalars)) { | |
| // TODO: add analysis of other gather nodes with extractelement | |
| // instructions and other values/instructions, not only undefs. | |
| // Nodes with copyable lanes may mix in non-extract lanes, for which the | |
| // extract-index order is not applicable. | |
| if (((TE.hasState() && TE.getOpcode() == Instruction::ExtractElement && | |
| !TE.hasCopyableElements()) || | |
| (all_of(TE.Scalars, IsaPred<UndefValue, ExtractElementInst>) && | |
| any_of(TE.Scalars, IsaPred<ExtractElementInst>))) && | |
| all_of(make_isa_range<ExtractElementInst>(TE.Scalars), | |
| [](ExtractElementInst *EE) { | |
| return isa<FixedVectorType>(EE->getVectorOperandType()); | |
| })) { | |
| // Check that gather of extractelements can be represented as | |
| // just a shuffle of a single vector. | |
| OrdersType CurrentOrder; | |
| bool Reuse = | |
| canReuseExtract(TE.Scalars, CurrentOrder, /*ResizeAllowed=*/true); | |
| if (Reuse || !CurrentOrder.empty()) | |
| return std::move(CurrentOrder); | |
| } | |
| // If the gather node is <undef, v, .., poison> and | |
| // insertelement poison, v, 0 [+ permute] | |
| // is cheaper than | |
| // insertelement poison, v, n - try to reorder. | |
| // If rotating the whole graph, exclude the permute cost, the whole graph | |
| // might be transformed. | |
| int Sz = TE.Scalars.size(); | |
| if (isSplat(TE.Scalars) && !allConstant(TE.Scalars) && | |
| count_if(TE.Scalars, IsaPred<UndefValue>) == Sz - 1) { | |
| const auto *It = find_if_not(TE.Scalars, isConstant); | |
| if (It == TE.Scalars.begin()) | |
| return OrdersType(); | |
| auto *Ty = | |
| cast<VectorType>(getWidenedType(TE.Scalars.front()->getType(), Sz)); | |
| if (It != TE.Scalars.end()) { | |
| OrdersType Order(Sz, Sz); | |
| unsigned Idx = std::distance(TE.Scalars.begin(), It); | |
| Order[Idx] = 0; | |
| fixupOrderingIndices(Order); | |
| SmallVector<int> Mask; | |
| inversePermutation(Order, Mask); | |
| InstructionCost PermuteCost = | |
| TopToBottom ? 0 | |
| : getShuffleCost(*TTI, TTI::SK_PermuteSingleSrc, Ty, | |
| CostKind, Mask); | |
| InstructionCost InsertFirstCost = | |
| TTI->getVectorInstrCost(Instruction::InsertElement, Ty, CostKind, 0, | |
| PoisonValue::get(Ty), *It); | |
| InstructionCost InsertIdxCost = | |
| TTI->getVectorInstrCost(Instruction::InsertElement, Ty, CostKind, | |
| Idx, PoisonValue::get(Ty), *It); | |
| if (InsertFirstCost + PermuteCost < InsertIdxCost) { | |
| OrdersType Order(Sz, Sz); | |
| Order[Idx] = 0; | |
| return std::move(Order); | |
| } | |
| } | |
| } | |
| if (isSplat(TE.Scalars)) | |
| return std::nullopt; | |
| if (TE.Scalars.size() >= 3) | |
| if (std::optional<OrdersType> Order = findPartiallyOrderedLoads(TE)) | |
| return Order; | |
| // Check if can include the order of vectorized loads. For masked gathers do | |
| // extra analysis later, so include such nodes into a special list. | |
| if (TE.hasState() && TE.getOpcode() == Instruction::Load) { | |
| SmallVector<Value *> PointerOps; | |
| StridedPtrInfo SPtrInfo; | |
| OrdersType CurrentOrder; | |
| LoadsState Res = canVectorizeLoads(TE.Scalars, TE.Scalars.front(), | |
| CurrentOrder, PointerOps, SPtrInfo); | |
| if (Res == LoadsState::Vectorize || Res == LoadsState::StridedVectorize || | |
| Res == LoadsState::CompressVectorize || | |
| Res == LoadsState::BlendedLoadVectorize) | |
| return std::move(CurrentOrder); | |
| } | |
| if (std::optional<OrdersType> CurrentOrder = | |
| findReusedOrderedScalars(TE, TopToBottom, IgnoreReorder)) | |
| return CurrentOrder; | |
| } | |
| return std::nullopt; | |
| } | |
| void BoUpSLP::reorderNodeWithReuses(TreeEntry &TE, ArrayRef<int> Mask) const { | |
| // Reorder reuses mask. | |
| reorderReuses(TE.ReuseShuffleIndices, Mask); | |
| const unsigned Sz = TE.Scalars.size(); | |
| // For vectorized and non-clustered reused no need to do anything else. | |
| if (!TE.isGather() || | |
| !ShuffleVectorInst::isOneUseSingleSourceMask(TE.ReuseShuffleIndices, | |
| Sz) || | |
| !isRepeatedNonIdentityClusteredMask(TE.ReuseShuffleIndices, Sz)) | |
| return; | |
| SmallVector<int> NewMask; | |
| inversePermutation(TE.ReorderIndices, NewMask); | |
| addMask(NewMask, TE.ReuseShuffleIndices); | |
| // Clear reorder since it is going to be applied to the new mask. | |
| TE.ReorderIndices.clear(); | |
| // Try to improve gathered nodes with clustered reuses, if possible. | |
| ArrayRef<int> Slice = ArrayRef(NewMask).slice(0, Sz); | |
| SmallVector<unsigned> NewOrder(Slice); | |
| inversePermutation(NewOrder, NewMask); | |
| reorderScalars(TE.Scalars, NewMask); | |
| // Fill the reuses mask with the identity submasks. | |
| for (auto *It = TE.ReuseShuffleIndices.begin(), | |
| *End = TE.ReuseShuffleIndices.end(); | |
| It != End; std::advance(It, Sz)) | |
| std::iota(It, std::next(It, Sz), 0); | |
| } | |
| bool BoUpSLP::isProfitableToReorder() const { | |
| if (Opts.slp_disable_tree_reorder) | |
| return false; | |
| constexpr unsigned TinyVF = 2; | |
| constexpr unsigned TinyTree = 10; | |
| constexpr unsigned PhiOpsLimit = 12; | |
| constexpr unsigned GatherLoadsLimit = 2; | |
| if (VectorizableTree.size() <= TinyTree) | |
| return true; | |
| if (getRootNode().hasState() && !getRootNode().isGather() && | |
| (getRootNode().getOpcode() == Instruction::Store || | |
| getRootNode().getOpcode() == Instruction::PHI || | |
| (getRootNode().getVectorFactor() <= TinyVF && | |
| (getRootNode().getOpcode() == Instruction::PtrToInt || | |
| getRootNode().getOpcode() == Instruction::PtrToAddr || | |
| getRootNode().getOpcode() == Instruction::ICmp))) && | |
| getRootNode().ReorderIndices.empty()) { | |
| // Check if the tree has only single store and single (unordered) load node, | |
| // other nodes are phis or geps/binops, combined with phis, and/or single | |
| // gather load node | |
| if (getRootNode().hasState() && | |
| getRootNode().getOpcode() == Instruction::PHI && | |
| getRootNodeScalars().size() == TinyVF && | |
| getRootNode().getNumOperands() > PhiOpsLimit) | |
| return false; | |
| // Single node, which require reorder - skip. | |
| if (getRootNode().hasState() && | |
| getRootNode().getOpcode() == Instruction::Store && | |
| getRootNode().ReorderIndices.empty()) { | |
| const unsigned ReorderedSplitsCnt = | |
| count_if(VectorizableTree, [&](const std::unique_ptr<TreeEntry> &TE) { | |
| return TE->State == TreeEntry::SplitVectorize && | |
| !TE->ReorderIndices.empty() && TE->UserTreeIndex.UserTE && | |
| TE->UserTreeIndex.UserTE->State == TreeEntry::Vectorize && | |
| isCommutative(TE->UserTreeIndex.UserTE->getMainOp()); | |
| }); | |
| if (ReorderedSplitsCnt <= 1 && | |
| static_cast<unsigned>(count_if( | |
| VectorizableTree, [&](const std::unique_ptr<TreeEntry> &TE) { | |
| return ((!TE->isGather() && | |
| (TE->ReorderIndices.empty() || | |
| (TE->UserTreeIndex.UserTE && | |
| TE->UserTreeIndex.UserTE->State == | |
| TreeEntry::Vectorize && | |
| !TE->UserTreeIndex.UserTE->ReuseShuffleIndices | |
| .empty()))) || | |
| (TE->isGather() && TE->ReorderIndices.empty() && | |
| (!TE->hasState() || TE->isAltShuffle() || | |
| TE->getOpcode() == Instruction::Load || | |
| TE->getOpcode() == Instruction::ZExt || | |
| TE->getOpcode() == Instruction::SExt))) && | |
| (getRootNode().getVectorFactor() > TinyVF || | |
| !TE->isGather() || none_of(TE->Scalars, [&](Value *V) { | |
| return !isConstant(V) && isVectorized(V); | |
| })); | |
| })) >= VectorizableTree.size() - ReorderedSplitsCnt) | |
| return false; | |
| } | |
| bool HasPhis = false; | |
| bool HasLoad = true; | |
| unsigned GatherLoads = 0; | |
| for (const std::unique_ptr<TreeEntry> &TE : | |
| ArrayRef(VectorizableTree).drop_front()) { | |
| if (TE->State == TreeEntry::SplitVectorize) | |
| continue; | |
| if (!TE->hasState()) { | |
| if (all_of(TE->Scalars, IsaPred<Constant, PHINode>) || | |
| all_of(TE->Scalars, IsaPred<BinaryOperator, PHINode>)) | |
| continue; | |
| if (getRootNodeScalars().size() == TinyVF && | |
| any_of(TE->Scalars, IsaPred<PHINode, GEPOperator>)) | |
| continue; | |
| return true; | |
| } | |
| if (TE->getOpcode() == Instruction::Load && TE->ReorderIndices.empty()) { | |
| if (!TE->isGather()) { | |
| HasLoad = false; | |
| continue; | |
| } | |
| if (HasLoad) | |
| return true; | |
| ++GatherLoads; | |
| if (GatherLoads >= GatherLoadsLimit) | |
| return true; | |
| } | |
| if (TE->getOpcode() == Instruction::GetElementPtr || | |
| Instruction::isBinaryOp(TE->getOpcode())) | |
| continue; | |
| if (TE->getOpcode() != Instruction::PHI && | |
| (!TE->hasCopyableElements() || | |
| static_cast<unsigned>(count_if(TE->Scalars, IsaPred<PHINode>)) < | |
| TE->Scalars.size() / 2)) | |
| return true; | |
| if (getRootNodeScalars().size() == TinyVF && | |
| TE->getNumOperands() > PhiOpsLimit) | |
| return false; | |
| HasPhis = true; | |
| } | |
| return !HasPhis; | |
| } | |
| return true; | |
| } | |
| void BoUpSLP::TreeEntry::reorderSplitNode(unsigned Idx, ArrayRef<int> Mask, | |
| ArrayRef<int> MaskOrder) { | |
| assert(State == TreeEntry::SplitVectorize && "Expected split user node."); | |
| SmallVector<int> NewMask(getVectorFactor()); | |
| SmallVector<int> NewMaskOrder(getVectorFactor()); | |
| std::iota(NewMask.begin(), NewMask.end(), 0); | |
| std::iota(NewMaskOrder.begin(), NewMaskOrder.end(), 0); | |
| if (Idx == 0) { | |
| copy(Mask, NewMask.begin()); | |
| copy(MaskOrder, NewMaskOrder.begin()); | |
| } else { | |
| assert(Idx == 1 && "Expected either 0 or 1 index."); | |
| unsigned Offset = CombinedEntriesWithIndices.back().second; | |
| for (unsigned I : seq<unsigned>(Mask.size())) { | |
| NewMask[I + Offset] = Mask[I] + Offset; | |
| NewMaskOrder[I + Offset] = MaskOrder[I] + Offset; | |
| } | |
| } | |
| reorderScalars(Scalars, NewMask); | |
| reorderOrder(ReorderIndices, NewMaskOrder, /*BottomOrder=*/true); | |
| if (!ReorderIndices.empty() && BoUpSLP::isIdentityOrder(ReorderIndices)) | |
| ReorderIndices.clear(); | |
| } | |
| void BoUpSLP::markCopyableDepsForRecalc(TreeEntry &TE) { | |
| if (!TE.hasState()) | |
| return; | |
| if (auto It = BlocksSchedules.find(TE.getMainOp()->getParent()); | |
| It != BlocksSchedules.end()) | |
| It->second->markCopyableDepsForRecalc(TE); | |
| } | |
| void BoUpSLP::reorderTopToBottom() { | |
| // Maps VF to the graph nodes. | |
| DenseMap<unsigned, SetVector<TreeEntry *>> VFToOrderedEntries; | |
| // ExtractElement gather nodes which can be vectorized and need to handle | |
| // their ordering. | |
| DenseMap<const TreeEntry *, OrdersType> GathersToOrders; | |
| // Phi nodes can have preferred ordering based on their result users | |
| DenseMap<const TreeEntry *, OrdersType> PhisToOrders; | |
| // AltShuffles can also have a preferred ordering that leads to fewer | |
| // instructions, e.g., the addsub instruction in x86. | |
| DenseMap<const TreeEntry *, OrdersType> AltShufflesToOrders; | |
| // Maps a TreeEntry to the reorder indices of external users. | |
| DenseMap<const TreeEntry *, SmallVector<OrdersType, 1>> | |
| ExternalUserReorderMap; | |
| // TODO: Reordering of struct types is not supported. | |
| if (any_of(VectorizableTree, [&](const std::unique_ptr<TreeEntry> &TE) { | |
| return TE->State == TreeEntry::Vectorize && | |
| isa<StructType>( | |
| getValueType(TE->Scalars.front(), Opts.slp_revec)); | |
| })) | |
| return; | |
| // Compute IgnoreReorder once - it depends only on UserIgnoreList and | |
| // getRootNode(), which do not change during this loop. | |
| const bool IgnoreReorder = | |
| !UserIgnoreList && getRootNode().hasState() && | |
| (getRootNode().getOpcode() == Instruction::InsertElement || | |
| getRootNode().getOpcode() == Instruction::InsertValue || | |
| getRootNode().getOpcode() == Instruction::Store); | |
| // Find all reorderable nodes with the given VF. | |
| // Currently the are vectorized stores,loads,extracts + some gathering of | |
| // extracts. | |
| for_each(VectorizableTree, [&, &TTIRef = *TTI]( | |
| const std::unique_ptr<TreeEntry> &TE) { | |
| // Look for external users that will probably be vectorized. | |
| SmallVector<OrdersType, 1> ExternalUserReorderIndices = | |
| findExternalStoreUsersReorderIndices(TE.get()); | |
| if (!ExternalUserReorderIndices.empty()) { | |
| VFToOrderedEntries[TE->getVectorFactor()].insert(TE.get()); | |
| ExternalUserReorderMap.try_emplace(TE.get(), | |
| std::move(ExternalUserReorderIndices)); | |
| } | |
| // Patterns like [fadd,fsub] can be combined into a single instruction in | |
| // x86. Reordering them into [fsub,fadd] blocks this pattern. So we need | |
| // to take into account their order when looking for the most used order. | |
| if (TE->hasState() && TE->isAltShuffle() && | |
| TE->State != TreeEntry::SplitVectorize) { | |
| Type *ScalarTy = TE->Scalars[0]->getType(); | |
| auto *VecTy = | |
| cast<VectorType>(getWidenedType(ScalarTy, TE->Scalars.size())); | |
| unsigned Opcode0 = TE->getOpcode(); | |
| unsigned Opcode1 = TE->getAltOpcode(); | |
| SmallBitVector OpcodeMask( | |
| getAltInstrMask(TE->Scalars, ScalarTy, Opcode0, Opcode1)); | |
| // If this pattern is supported by the target then we consider the order. | |
| if (TTIRef.isLegalAltInstr(VecTy, Opcode0, Opcode1, OpcodeMask)) { | |
| VFToOrderedEntries[TE->getVectorFactor()].insert(TE.get()); | |
| AltShufflesToOrders.try_emplace(TE.get(), OrdersType()); | |
| } | |
| // TODO: Check the reverse order too. | |
| } | |
| if (std::optional<OrdersType> CurrentOrder = | |
| getReorderingData(*TE, /*TopToBottom=*/true, IgnoreReorder)) { | |
| // Do not include ordering for nodes used in the alt opcode vectorization, | |
| // better to reorder them during bottom-to-top stage. If follow the order | |
| // here, it causes reordering of the whole graph though actually it is | |
| // profitable just to reorder the subgraph that starts from the alternate | |
| // opcode vectorization node. Such nodes already end-up with the shuffle | |
| // instruction and it is just enough to change this shuffle rather than | |
| // rotate the scalars for the whole graph. | |
| unsigned Cnt = 0; | |
| const TreeEntry *UserTE = TE.get(); | |
| while (UserTE && Cnt < Opts.slp_recursion_max_depth) { | |
| if (!UserTE->UserTreeIndex) | |
| break; | |
| if (UserTE->UserTreeIndex.UserTE->State == TreeEntry::Vectorize && | |
| UserTE->UserTreeIndex.UserTE->isAltShuffle() && | |
| UserTE->UserTreeIndex.UserTE->Idx != 0) | |
| return; | |
| UserTE = UserTE->UserTreeIndex.UserTE; | |
| ++Cnt; | |
| } | |
| VFToOrderedEntries[TE->getVectorFactor()].insert(TE.get()); | |
| if (!(TE->State == TreeEntry::Vectorize || | |
| TE->State == TreeEntry::StridedVectorize || | |
| TE->State == TreeEntry::ExpandVectorize || | |
| TE->State == TreeEntry::SplitVectorize || | |
| TE->State == TreeEntry::CompressVectorize || | |
| TE->State == TreeEntry::BlendedLoadVectorize) || | |
| !TE->ReuseShuffleIndices.empty()) | |
| GathersToOrders.try_emplace(TE.get(), *CurrentOrder); | |
| if (TE->State == TreeEntry::Vectorize && | |
| TE->getOpcode() == Instruction::PHI) | |
| PhisToOrders.try_emplace(TE.get(), *CurrentOrder); | |
| } | |
| }); | |
| // Reorder the graph nodes according to their vectorization factor. | |
| for (unsigned VF = getRootNode().getVectorFactor(); | |
| !VFToOrderedEntries.empty() && VF > 1; --VF) { | |
| auto It = VFToOrderedEntries.find(VF); | |
| if (It == VFToOrderedEntries.end()) | |
| continue; | |
| // Try to find the most profitable order. We just are looking for the most | |
| // used order and reorder scalar elements in the nodes according to this | |
| // mostly used order. | |
| ArrayRef<TreeEntry *> OrderedEntries = It->second.getArrayRef(); | |
| // Delete VF entry upon exit. | |
| llvm::scope_exit Cleanup([&]() { VFToOrderedEntries.erase(It); }); | |
| // All operands are reordered and used only in this node - propagate the | |
| // most used order to the user node. | |
| MapVector<OrdersType, unsigned, | |
| DenseMap<OrdersType, unsigned, OrdersTypeDenseMapInfo>> | |
| OrdersUses; | |
| // No need to reorder this nodes, still need to extend and to use shuffle, | |
| // just need to merge reordering shuffle and the reuse shuffle. | |
| for (const TreeEntry *OpTE : | |
| make_filter_range(OrderedEntries, [&](const TreeEntry *OpTE) { | |
| return OpTE->ReuseShuffleIndices.empty() || | |
| GathersToOrders.count(OpTE) || | |
| OpTE->State == TreeEntry::SplitVectorize; | |
| })) { | |
| // Count number of orders uses. | |
| const auto &Order = [OpTE, &GathersToOrders, &AltShufflesToOrders, | |
| &PhisToOrders]() -> const OrdersType & { | |
| if (OpTE->isGather() || !OpTE->ReuseShuffleIndices.empty()) { | |
| auto It = GathersToOrders.find(OpTE); | |
| if (It != GathersToOrders.end()) | |
| return It->second; | |
| } | |
| if (OpTE->hasState() && OpTE->isAltShuffle()) { | |
| auto It = AltShufflesToOrders.find(OpTE); | |
| if (It != AltShufflesToOrders.end()) | |
| return It->second; | |
| } | |
| if (OpTE->State == TreeEntry::Vectorize && | |
| OpTE->getOpcode() == Instruction::PHI) { | |
| auto It = PhisToOrders.find(OpTE); | |
| if (It != PhisToOrders.end()) | |
| return It->second; | |
| } | |
| return OpTE->ReorderIndices; | |
| }(); | |
| // First consider the order of the external scalar users. | |
| auto It = ExternalUserReorderMap.find(OpTE); | |
| if (It != ExternalUserReorderMap.end()) { | |
| const auto &ExternalUserReorderIndices = It->second; | |
| // If the OpTE vector factor != number of scalars - use natural order, | |
| // it is an attempt to reorder node with reused scalars but with | |
| // external uses. | |
| if (OpTE->getVectorFactor() != OpTE->Scalars.size()) { | |
| OrdersUses.try_emplace(OrdersType(), 0).first->second += | |
| ExternalUserReorderIndices.size(); | |
| } else { | |
| for (const OrdersType &ExtOrder : ExternalUserReorderIndices) | |
| ++OrdersUses.try_emplace(ExtOrder, 0).first->second; | |
| } | |
| // No other useful reorder data in this entry. | |
| if (Order.empty()) | |
| continue; | |
| } | |
| // Stores actually store the mask, not the order, need to invert. | |
| if (OpTE->State == TreeEntry::Vectorize && | |
| OpTE->getOpcode() == Instruction::Store && !Order.empty()) { | |
| assert(!OpTE->isAltShuffle() && | |
| "Alternate instructions are only supported by BinaryOperator " | |
| "and CastInst."); | |
| SmallVector<int> Mask; | |
| inversePermutation(Order, Mask); | |
| unsigned E = Order.size(); | |
| OrdersType CurrentOrder(E, E); | |
| transform(Mask, CurrentOrder.begin(), [E](int Idx) { | |
| return Idx == PoisonMaskElem ? E : static_cast<unsigned>(Idx); | |
| }); | |
| fixupOrderingIndices(CurrentOrder); | |
| ++OrdersUses.try_emplace(CurrentOrder, 0).first->second; | |
| } else { | |
| ++OrdersUses.try_emplace(Order, 0).first->second; | |
| } | |
| } | |
| if (OrdersUses.empty()) | |
| continue; | |
| // Choose the most used order. | |
| unsigned IdentityCnt = 0; | |
| unsigned FilledIdentityCnt = 0; | |
| OrdersType IdentityOrder(VF, VF); | |
| for (auto &Pair : OrdersUses) { | |
| if (Pair.first.empty() || isIdentityOrder(Pair.first)) { | |
| if (!Pair.first.empty()) | |
| FilledIdentityCnt += Pair.second; | |
| IdentityCnt += Pair.second; | |
| combineOrders(IdentityOrder, Pair.first); | |
| } | |
| } | |
| MutableArrayRef<unsigned> BestOrder = IdentityOrder; | |
| unsigned Cnt = IdentityCnt; | |
| for (auto &Pair : OrdersUses) { | |
| // Prefer identity order. But, if filled identity found (non-empty order) | |
| // with same number of uses, as the new candidate order, we can choose | |
| // this candidate order. | |
| if (Cnt < Pair.second || | |
| (Cnt == IdentityCnt && IdentityCnt == FilledIdentityCnt && | |
| Cnt == Pair.second && !BestOrder.empty() && | |
| isIdentityOrder(BestOrder))) { | |
| combineOrders(Pair.first, BestOrder); | |
| BestOrder = Pair.first; | |
| Cnt = Pair.second; | |
| } else { | |
| combineOrders(BestOrder, Pair.first); | |
| } | |
| } | |
| // Set order of the user node. | |
| if (isIdentityOrder(BestOrder)) | |
| continue; | |
| fixupOrderingIndices(BestOrder); | |
| SmallVector<int> Mask; | |
| inversePermutation(BestOrder, Mask); | |
| SmallVector<int> MaskOrder(BestOrder.size(), PoisonMaskElem); | |
| unsigned E = BestOrder.size(); | |
| transform(BestOrder, MaskOrder.begin(), [E](unsigned I) { | |
| return I < E ? static_cast<int>(I) : PoisonMaskElem; | |
| }); | |
| // Do an actual reordering, if profitable. | |
| // The splat-gather subtree load roots stay in memory order: the reusing | |
| // gathers fetch lanes by value. | |
| for (std::unique_ptr<TreeEntry> &TE : make_filter_range( | |
| VectorizableTree, [&](std::unique_ptr<TreeEntry> &TE) { | |
| return TE->State != TreeEntry::Vectorize || | |
| TE->getOpcode() != Instruction::Load || | |
| !is_contained(SplatGatheredScalarsRoots, TE.get()); | |
| })) { | |
| // Just do the reordering for the nodes with the given VF. | |
| if (TE->Scalars.size() != VF) { | |
| if (TE->ReuseShuffleIndices.size() == VF && | |
| TE->State != TreeEntry::ExpandVectorize) { | |
| assert(TE->State != TreeEntry::SplitVectorize && | |
| "Split vectorized not expected."); | |
| // Need to reorder the reuses masks of the operands with smaller VF to | |
| // be able to find the match between the graph nodes and scalar | |
| // operands of the given node during vectorization/cost estimation. | |
| assert( | |
| (!TE->UserTreeIndex || | |
| TE->UserTreeIndex.UserTE->Scalars.size() == VF || | |
| TE->UserTreeIndex.UserTE->Scalars.size() == TE->Scalars.size() || | |
| TE->UserTreeIndex.UserTE->State == TreeEntry::SplitVectorize) && | |
| "All users must be of VF size."); | |
| if (Opts.slp_revec) { | |
| assert(Opts.slp_revec && "Only supported by REVEC."); | |
| // ShuffleVectorInst does not do reorderOperands (and it should not | |
| // because ShuffleVectorInst supports only a limited set of | |
| // patterns). Only do reorderNodeWithReuses if the user is not | |
| // ShuffleVectorInst. | |
| if (TE->UserTreeIndex && TE->UserTreeIndex.UserTE->hasState() && | |
| isa<ShuffleVectorInst>(TE->UserTreeIndex.UserTE->getMainOp())) | |
| continue; | |
| } | |
| // Update ordering of the operands with the smaller VF than the given | |
| // one. | |
| reorderNodeWithReuses(*TE, Mask); | |
| // Update orders in user split vectorize nodes. | |
| if (TE->UserTreeIndex && | |
| TE->UserTreeIndex.UserTE->State == TreeEntry::SplitVectorize) | |
| TE->UserTreeIndex.UserTE->reorderSplitNode( | |
| TE->UserTreeIndex.EdgeIdx, Mask, MaskOrder); | |
| } | |
| continue; | |
| } | |
| if ((TE->State == TreeEntry::SplitVectorize && | |
| TE->ReuseShuffleIndices.empty()) || | |
| ((TE->State == TreeEntry::Vectorize || | |
| TE->State == TreeEntry::StridedVectorize || | |
| TE->State == TreeEntry::ExpandVectorize || | |
| TE->State == TreeEntry::CompressVectorize || | |
| TE->State == TreeEntry::BlendedLoadVectorize) && | |
| (isa<ExtractElementInst, ExtractValueInst, LoadInst, StoreInst, | |
| InsertElementInst, InsertValueInst>(TE->getMainOp()) || | |
| (Opts.slp_revec && isa<ShuffleVectorInst>(TE->getMainOp()))))) { | |
| assert( | |
| (!TE->isAltShuffle() || (TE->State == TreeEntry::SplitVectorize && | |
| TE->ReuseShuffleIndices.empty())) && | |
| "Alternate instructions are only supported by BinaryOperator " | |
| "and CastInst."); | |
| // Build correct orders for extract{element,value}, loads, | |
| // stores and alternate (split) nodes. | |
| reorderOrder(TE->ReorderIndices, Mask); | |
| if (isa<InsertElementInst, InsertValueInst, StoreInst>( | |
| TE->getMainOp())) { | |
| TE->reorderOperands(Mask); | |
| markCopyableDepsForRecalc(*TE); | |
| } | |
| } else { | |
| // Reorder the node and its operands. | |
| TE->reorderOperands(Mask); | |
| markCopyableDepsForRecalc(*TE); | |
| assert(TE->ReorderIndices.empty() && | |
| "Expected empty reorder sequence."); | |
| reorderScalars(TE->Scalars, Mask); | |
| } | |
| if (!TE->ReuseShuffleIndices.empty() && | |
| TE->State != TreeEntry::ExpandVectorize) { | |
| // Apply reversed order to keep the original ordering of the reused | |
| // elements to avoid extra reorder indices shuffling. An ExpandVectorize | |
| // store keeps its expand mask fixed and carries the reorder in | |
| // ReorderIndices, so it is excluded here. | |
| OrdersType CurrentOrder; | |
| reorderOrder(CurrentOrder, MaskOrder); | |
| SmallVector<int> NewReuses; | |
| inversePermutation(CurrentOrder, NewReuses); | |
| addMask(NewReuses, TE->ReuseShuffleIndices); | |
| TE->ReuseShuffleIndices.swap(NewReuses); | |
| } else if (TE->UserTreeIndex && | |
| TE->UserTreeIndex.UserTE->State == TreeEntry::SplitVectorize) | |
| // Update orders in user split vectorize nodes. | |
| TE->UserTreeIndex.UserTE->reorderSplitNode(TE->UserTreeIndex.EdgeIdx, | |
| Mask, MaskOrder); | |
| } | |
| } | |
| } | |
| void BoUpSLP::buildReorderableOperands( | |
| TreeEntry *UserTE, SmallVectorImpl<std::pair<unsigned, TreeEntry *>> &Edges, | |
| const SmallPtrSetImpl<const TreeEntry *> &ReorderableGathers, | |
| SmallVectorImpl<TreeEntry *> &GatherOps) { | |
| for (unsigned I : seq<unsigned>(UserTE->getNumOperands())) { | |
| if (any_of(Edges, [I](const std::pair<unsigned, TreeEntry *> &OpData) { | |
| return OpData.first == I && | |
| (OpData.second->State == TreeEntry::Vectorize || | |
| OpData.second->State == TreeEntry::StridedVectorize || | |
| OpData.second->State == TreeEntry::ExpandVectorize || | |
| OpData.second->State == TreeEntry::CompressVectorize || | |
| OpData.second->State == TreeEntry::BlendedLoadVectorize || | |
| OpData.second->State == TreeEntry::SplitVectorize); | |
| })) | |
| continue; | |
| // Do not request operands, if they do not exist. | |
| if (UserTE->hasState()) { | |
| if (UserTE->getOpcode() == Instruction::ExtractElement || | |
| UserTE->getOpcode() == Instruction::ExtractValue) | |
| continue; | |
| if ((UserTE->getOpcode() == Instruction::InsertElement || | |
| UserTE->getOpcode() == Instruction::InsertValue) && | |
| I == 0) | |
| continue; | |
| if (UserTE->getOpcode() == Instruction::Store && I == 1 && | |
| (UserTE->State == TreeEntry::Vectorize || | |
| UserTE->State == TreeEntry::StridedVectorize || | |
| UserTE->State == TreeEntry::ExpandVectorize)) | |
| continue; | |
| if (UserTE->getOpcode() == Instruction::Load && | |
| (UserTE->State == TreeEntry::Vectorize || | |
| UserTE->State == TreeEntry::StridedVectorize || | |
| UserTE->State == TreeEntry::CompressVectorize || | |
| UserTE->State == TreeEntry::BlendedLoadVectorize)) | |
| continue; | |
| } | |
| TreeEntry *TE = getOperandEntry(UserTE, I); | |
| assert(TE && "Expected operand entry."); | |
| if (!TE->isGather()) { | |
| // Add the node to the list of the ordered nodes with the identity | |
| // order. | |
| Edges.emplace_back(I, TE); | |
| // Add ScatterVectorize nodes to the list of operands, where just | |
| // reordering of the scalars is required. Similar to the gathers, so | |
| // simply add to the list of gathered ops. | |
| // If there are reused scalars, process this node as a regular vectorize | |
| // node, just reorder reuses mask. | |
| if (TE->State == TreeEntry::ScatterVectorize && | |
| TE->ReuseShuffleIndices.empty() && TE->ReorderIndices.empty()) | |
| GatherOps.push_back(TE); | |
| continue; | |
| } | |
| if (ReorderableGathers.contains(TE)) | |
| GatherOps.push_back(TE); | |
| } | |
| } | |
| void BoUpSLP::reorderBottomToTop(bool IgnoreReorder) { | |
| struct TreeEntryCompare { | |
| bool operator()(const TreeEntry *LHS, const TreeEntry *RHS) const { | |
| if (LHS->UserTreeIndex && RHS->UserTreeIndex) | |
| return LHS->UserTreeIndex.UserTE->Idx < RHS->UserTreeIndex.UserTE->Idx; | |
| return LHS->Idx < RHS->Idx; | |
| } | |
| }; | |
| PriorityQueue<TreeEntry *, SmallVector<TreeEntry *>, TreeEntryCompare> Queue; | |
| DenseSet<const TreeEntry *> GathersToOrders; | |
| // Find all reorderable leaf nodes with the given VF. | |
| // Currently the are vectorized loads,extracts without alternate operands + | |
| // some gathering of extracts. | |
| SmallPtrSet<const TreeEntry *, 4> NonVectorized; | |
| for (const std::unique_ptr<TreeEntry> &TE : VectorizableTree) { | |
| if (TE->State != TreeEntry::Vectorize && | |
| TE->State != TreeEntry::StridedVectorize && | |
| TE->State != TreeEntry::ExpandVectorize && | |
| TE->State != TreeEntry::CompressVectorize && | |
| TE->State != TreeEntry::BlendedLoadVectorize && | |
| TE->State != TreeEntry::SplitVectorize) | |
| NonVectorized.insert(TE.get()); | |
| if (std::optional<OrdersType> CurrentOrder = | |
| getReorderingData(*TE, /*TopToBottom=*/false, IgnoreReorder)) { | |
| Queue.push(TE.get()); | |
| if (!(TE->State == TreeEntry::Vectorize || | |
| TE->State == TreeEntry::StridedVectorize || | |
| TE->State == TreeEntry::ExpandVectorize || | |
| TE->State == TreeEntry::CompressVectorize || | |
| TE->State == TreeEntry::BlendedLoadVectorize || | |
| TE->State == TreeEntry::SplitVectorize) || | |
| !TE->ReuseShuffleIndices.empty()) | |
| GathersToOrders.insert(TE.get()); | |
| } | |
| } | |
| // 1. Propagate order to the graph nodes, which use only reordered nodes. | |
| // I.e., if the node has operands, that are reordered, try to make at least | |
| // one operand order in the natural order and reorder others + reorder the | |
| // user node itself. | |
| SmallPtrSet<const TreeEntry *, 4> Visited, RevisitedOps; | |
| while (!Queue.empty()) { | |
| // 1. Filter out only reordered nodes. | |
| std::pair<TreeEntry *, SmallVector<std::pair<unsigned, TreeEntry *>>> Users; | |
| TreeEntry *TE = Queue.top(); | |
| const TreeEntry *UserTE = TE->UserTreeIndex.UserTE; | |
| Queue.pop(); | |
| SmallVector<TreeEntry *> OrderedOps(1, TE); | |
| while (!Queue.empty()) { | |
| TE = Queue.top(); | |
| if (!UserTE || UserTE != TE->UserTreeIndex.UserTE) | |
| break; | |
| Queue.pop(); | |
| OrderedOps.push_back(TE); | |
| } | |
| for (TreeEntry *TE : make_filter_range(OrderedOps, [&](TreeEntry *TE) { | |
| return (TE->State == TreeEntry::Vectorize || | |
| TE->State == TreeEntry::StridedVectorize || | |
| TE->State == TreeEntry::ExpandVectorize || | |
| TE->State == TreeEntry::CompressVectorize || | |
| TE->State == TreeEntry::BlendedLoadVectorize || | |
| TE->State == TreeEntry::SplitVectorize || | |
| (TE->isGather() && GathersToOrders.contains(TE))) && | |
| TE->UserTreeIndex && | |
| TE->UserTreeIndex.UserTE->State != | |
| TreeEntry::BlendedLoadVectorize && | |
| TE->ReuseShuffleIndices.empty() && Visited.insert(TE).second; | |
| })) { | |
| // Build a map between user nodes and their operands order to speedup | |
| // search. The graph currently does not provide this dependency directly. | |
| Users.first = TE->UserTreeIndex.UserTE; | |
| Users.second.emplace_back(TE->UserTreeIndex.EdgeIdx, TE); | |
| } | |
| if (Users.first) { | |
| auto &Data = Users; | |
| // TODO: Reordering of struct types is not supported. | |
| if (Data.first->State == TreeEntry::Vectorize && | |
| isa<StructType>( | |
| getValueType(Data.first->Scalars.front(), Opts.slp_revec))) | |
| continue; | |
| if (Data.first->State == TreeEntry::SplitVectorize) { | |
| assert( | |
| Data.second.size() <= 2 && | |
| "Expected not greater than 2 operands for split vectorize node."); | |
| if (any_of(Data.second, | |
| [](const auto &Op) { return !Op.second->UserTreeIndex; })) | |
| continue; | |
| // Update orders in user split vectorize nodes. | |
| assert(Data.first->CombinedEntriesWithIndices.size() == 2 && | |
| "Expected exactly 2 entries."); | |
| for (const auto &P : Data.first->CombinedEntriesWithIndices) { | |
| TreeEntry &OpTE = *VectorizableTree[P.first]; | |
| // The order of an operand that has both reordered and reused scalars | |
| // cannot be absorbed into the split node cleanly: clearing the | |
| // reorder indices while keeping the reuse mask (or vice versa) | |
| // desyncs the split node scalars from the operand effective order. | |
| // Skip reordering for such operands. | |
| if (OpTE.State != TreeEntry::SplitVectorize && | |
| !OpTE.ReorderIndices.empty() && !OpTE.ReuseShuffleIndices.empty()) | |
| continue; | |
| OrdersType Order = OpTE.ReorderIndices; | |
| if (Order.empty() || !OpTE.ReuseShuffleIndices.empty()) { | |
| if (!OpTE.isGather() && OpTE.ReuseShuffleIndices.empty()) | |
| continue; | |
| const auto BestOrder = | |
| getReorderingData(OpTE, /*TopToBottom=*/false, IgnoreReorder); | |
| if (!BestOrder || BestOrder->empty() || isIdentityOrder(*BestOrder)) | |
| continue; | |
| Order = *BestOrder; | |
| } | |
| fixupOrderingIndices(Order); | |
| SmallVector<int> Mask; | |
| inversePermutation(Order, Mask); | |
| const unsigned E = Order.size(); | |
| SmallVector<int> MaskOrder(E, PoisonMaskElem); | |
| transform(Order, MaskOrder.begin(), [E](unsigned I) { | |
| return I < E ? static_cast<int>(I) : PoisonMaskElem; | |
| }); | |
| Data.first->reorderSplitNode(P.second ? 1 : 0, Mask, MaskOrder); | |
| // Clear ordering of the operand. | |
| if (!OpTE.ReorderIndices.empty()) { | |
| OpTE.ReorderIndices.clear(); | |
| } else if (!OpTE.ReuseShuffleIndices.empty()) { | |
| reorderReuses(OpTE.ReuseShuffleIndices, Mask); | |
| } else { | |
| assert(OpTE.isGather() && "Expected only gather/buildvector node."); | |
| reorderScalars(OpTE.Scalars, Mask); | |
| } | |
| } | |
| if (Data.first->ReuseShuffleIndices.empty() && | |
| !Data.first->ReorderIndices.empty()) { | |
| // Insert user node to the list to try to sink reordering deeper in | |
| // the graph. | |
| Queue.push(Data.first); | |
| } | |
| continue; | |
| } | |
| // Do not move the operand order to the root PHI node when the root | |
| // order must be preserved: the root has no user to take over the order | |
| // and it cannot be dropped at the end of the reordering. | |
| if (!IgnoreReorder && Data.first == &getRootNode() && | |
| !Data.first->UserTreeIndex && | |
| Data.first->State == TreeEntry::Vectorize && | |
| Data.first->getOpcode() == Instruction::PHI && | |
| Data.first->ReuseShuffleIndices.empty()) | |
| continue; | |
| // Check that operands are used only in the User node. | |
| SmallVector<TreeEntry *> GatherOps; | |
| buildReorderableOperands(Data.first, Data.second, NonVectorized, | |
| GatherOps); | |
| // All operands are reordered and used only in this node - propagate the | |
| // most used order to the user node. | |
| MapVector<OrdersType, unsigned, | |
| DenseMap<OrdersType, unsigned, OrdersTypeDenseMapInfo>> | |
| OrdersUses; | |
| // Do the analysis for each tree entry only once, otherwise the order of | |
| // the same node my be considered several times, though might be not | |
| // profitable. | |
| SmallPtrSet<const TreeEntry *, 4> VisitedOps; | |
| SmallPtrSet<const TreeEntry *, 4> VisitedUsers; | |
| for (const auto &Op : Data.second) { | |
| TreeEntry *OpTE = Op.second; | |
| if (!VisitedOps.insert(OpTE).second) | |
| continue; | |
| if (!OpTE->ReuseShuffleIndices.empty() && !GathersToOrders.count(OpTE)) | |
| continue; | |
| const auto Order = [&]() -> const OrdersType { | |
| if (OpTE->isGather() || !OpTE->ReuseShuffleIndices.empty()) | |
| return getReorderingData(*OpTE, /*TopToBottom=*/false, | |
| IgnoreReorder) | |
| .value_or(OrdersType(1)); | |
| return OpTE->ReorderIndices; | |
| }(); | |
| // The order is partially ordered, skip it in favor of fully non-ordered | |
| // orders. | |
| if (Order.size() == 1) | |
| continue; | |
| // Check that the reordering does not increase number of shuffles, i.e. | |
| // same-values-nodes has same parents or their parents has same parents. | |
| if (!Order.empty() && !isIdentityOrder(Order)) { | |
| Value *Root = OpTE->hasState() | |
| ? OpTE->getMainOp() | |
| : *find_if_not(OpTE->Scalars, isConstant); | |
| auto GetSameNodesUsers = [&](Value *Root) { | |
| SmallSetVector<TreeEntry *, 4> Res; | |
| for (const TreeEntry *TE : ValueToGatherNodes.lookup(Root)) { | |
| if (TE != OpTE && TE->UserTreeIndex && | |
| TE->getVectorFactor() == OpTE->getVectorFactor() && | |
| TE->Scalars.size() == OpTE->Scalars.size() && | |
| ((TE->ReorderIndices.empty() && OpTE->isSame(TE->Scalars)) || | |
| (OpTE->ReorderIndices.empty() && TE->isSame(OpTE->Scalars)))) | |
| Res.insert(TE->UserTreeIndex.UserTE); | |
| } | |
| for (const TreeEntry *TE : getTreeEntries(Root)) { | |
| if (TE != OpTE && TE->UserTreeIndex && | |
| TE->getVectorFactor() == OpTE->getVectorFactor() && | |
| TE->Scalars.size() == OpTE->Scalars.size() && | |
| ((TE->ReorderIndices.empty() && OpTE->isSame(TE->Scalars)) || | |
| (OpTE->ReorderIndices.empty() && TE->isSame(OpTE->Scalars)))) | |
| Res.insert(TE->UserTreeIndex.UserTE); | |
| } | |
| return Res.takeVector(); | |
| }; | |
| auto GetNumOperands = [](const TreeEntry *TE) { | |
| if (TE->State == TreeEntry::SplitVectorize) | |
| return TE->getNumOperands(); | |
| if (auto *CI = dyn_cast<CallInst>(TE->getMainOp()); CI) | |
| return CI->arg_size(); | |
| return TE->getNumOperands(); | |
| }; | |
| auto NodeShouldBeReorderedWithOperands = [&, TTI = TTI]( | |
| const TreeEntry *TE) { | |
| Intrinsic::ID ID = Intrinsic::not_intrinsic; | |
| if (auto *CI = dyn_cast<CallInst>(TE->getMainOp()); CI) | |
| ID = getVectorIntrinsicIDForCall(CI, TLI); | |
| for (unsigned Idx : seq<unsigned>(GetNumOperands(TE))) { | |
| if (ID != Intrinsic::not_intrinsic && | |
| isVectorIntrinsicWithScalarOpAtArg(ID, Idx, TTI)) | |
| continue; | |
| const TreeEntry *Op = getOperandEntry(TE, Idx); | |
| if (Op->isGather() && Op->hasState()) { | |
| const TreeEntry *VecOp = | |
| getSameValuesTreeEntry(Op->getMainOp(), Op->Scalars); | |
| if (VecOp) | |
| Op = VecOp; | |
| } | |
| if (Op->ReorderIndices.empty() && Op->ReuseShuffleIndices.empty()) | |
| return false; | |
| } | |
| return true; | |
| }; | |
| SmallVector<TreeEntry *> Users = GetSameNodesUsers(Root); | |
| if (!Users.empty() && !all_of(Users, [&](TreeEntry *UTE) { | |
| if (!RevisitedOps.insert(UTE).second) | |
| return false; | |
| return UTE == Data.first || !UTE->ReorderIndices.empty() || | |
| !UTE->ReuseShuffleIndices.empty() || | |
| (UTE->UserTreeIndex && | |
| UTE->UserTreeIndex.UserTE == Data.first) || | |
| (Data.first->UserTreeIndex && | |
| Data.first->UserTreeIndex.UserTE == UTE) || | |
| (IgnoreReorder && UTE->UserTreeIndex && | |
| UTE->UserTreeIndex.UserTE->Idx == 0) || | |
| NodeShouldBeReorderedWithOperands(UTE); | |
| })) | |
| continue; | |
| for (TreeEntry *UTE : Users) { | |
| Intrinsic::ID ID = Intrinsic::not_intrinsic; | |
| if (auto *CI = dyn_cast<CallInst>(UTE->getMainOp()); CI) | |
| ID = getVectorIntrinsicIDForCall(CI, TLI); | |
| for (unsigned Idx : seq<unsigned>(GetNumOperands(UTE))) { | |
| if (ID != Intrinsic::not_intrinsic && | |
| isVectorIntrinsicWithScalarOpAtArg(ID, Idx, TTI)) | |
| continue; | |
| const TreeEntry *Op = getOperandEntry(UTE, Idx); | |
| Visited.erase(Op); | |
| Queue.push(const_cast<TreeEntry *>(Op)); | |
| } | |
| } | |
| } | |
| unsigned NumOps = count_if( | |
| Data.second, [OpTE](const std::pair<unsigned, TreeEntry *> &P) { | |
| return P.second == OpTE; | |
| }); | |
| // Stores actually store the mask, not the order, need to invert. | |
| if (OpTE->State == TreeEntry::Vectorize && | |
| OpTE->getOpcode() == Instruction::Store && !Order.empty()) { | |
| assert(!OpTE->isAltShuffle() && | |
| "Alternate instructions are only supported by BinaryOperator " | |
| "and CastInst."); | |
| SmallVector<int> Mask; | |
| inversePermutation(Order, Mask); | |
| unsigned E = Order.size(); | |
| OrdersType CurrentOrder(E, E); | |
| transform(Mask, CurrentOrder.begin(), [E](int Idx) { | |
| return Idx == PoisonMaskElem ? E : static_cast<unsigned>(Idx); | |
| }); | |
| fixupOrderingIndices(CurrentOrder); | |
| OrdersUses.try_emplace(CurrentOrder, 0).first->second += NumOps; | |
| } else { | |
| OrdersUses.try_emplace(Order, 0).first->second += NumOps; | |
| } | |
| auto Res = OrdersUses.try_emplace(OrdersType(), 0); | |
| const auto AllowsReordering = [&](const TreeEntry *TE) { | |
| if (!TE->ReorderIndices.empty() || !TE->ReuseShuffleIndices.empty() || | |
| (TE->State == TreeEntry::Vectorize && TE->isAltShuffle()) || | |
| (IgnoreReorder && TE->Idx == 0)) | |
| return true; | |
| if (TE->isGather()) { | |
| if (GathersToOrders.contains(TE)) | |
| return !getReorderingData(*TE, /*TopToBottom=*/false, | |
| IgnoreReorder) | |
| .value_or(OrdersType(1)) | |
| .empty(); | |
| return true; | |
| } | |
| return false; | |
| }; | |
| if (OpTE->UserTreeIndex) { | |
| TreeEntry *UserTE = OpTE->UserTreeIndex.UserTE; | |
| if (!VisitedUsers.insert(UserTE).second) | |
| continue; | |
| // May reorder user node if it requires reordering, has reused | |
| // scalars, is an alternate op vectorize node or its op nodes require | |
| // reordering. | |
| if (AllowsReordering(UserTE)) | |
| continue; | |
| // Check if users allow reordering. | |
| // Currently look up just 1 level of operands to avoid increase of | |
| // the compile time. | |
| // Profitable to reorder if definitely more operands allow | |
| // reordering rather than those with natural order. | |
| ArrayRef<std::pair<unsigned, TreeEntry *>> Ops = Users.second; | |
| if (static_cast<unsigned>(count_if( | |
| Ops, [UserTE, &AllowsReordering]( | |
| const std::pair<unsigned, TreeEntry *> &Op) { | |
| return AllowsReordering(Op.second) && | |
| Op.second->UserTreeIndex.UserTE == UserTE; | |
| })) <= Ops.size() / 2) | |
| ++Res.first->second; | |
| } | |
| } | |
| if (OrdersUses.empty()) { | |
| Visited.insert_range(llvm::make_second_range(Data.second)); | |
| continue; | |
| } | |
| // Choose the most used order. | |
| unsigned IdentityCnt = 0; | |
| unsigned VF = Data.second.front().second->getVectorFactor(); | |
| OrdersType IdentityOrder(VF, VF); | |
| for (auto &Pair : OrdersUses) { | |
| if (Pair.first.empty() || isIdentityOrder(Pair.first)) { | |
| IdentityCnt += Pair.second; | |
| combineOrders(IdentityOrder, Pair.first); | |
| } | |
| } | |
| MutableArrayRef<unsigned> BestOrder = IdentityOrder; | |
| unsigned Cnt = IdentityCnt; | |
| for (auto &Pair : OrdersUses) { | |
| // Prefer identity order. But, if filled identity found (non-empty | |
| // order) with same number of uses, as the new candidate order, we can | |
| // choose this candidate order. | |
| if (Cnt < Pair.second) { | |
| combineOrders(Pair.first, BestOrder); | |
| BestOrder = Pair.first; | |
| Cnt = Pair.second; | |
| } else { | |
| combineOrders(BestOrder, Pair.first); | |
| } | |
| } | |
| // Set order of the user node. | |
| if (isIdentityOrder(BestOrder)) { | |
| Visited.insert_range(llvm::make_second_range(Data.second)); | |
| continue; | |
| } | |
| fixupOrderingIndices(BestOrder); | |
| // Erase operands from OrderedEntries list and adjust their orders. | |
| VisitedOps.clear(); | |
| SmallVector<int> Mask; | |
| inversePermutation(BestOrder, Mask); | |
| SmallVector<int> MaskOrder(BestOrder.size(), PoisonMaskElem); | |
| unsigned E = BestOrder.size(); | |
| transform(BestOrder, MaskOrder.begin(), [E](unsigned I) { | |
| return I < E ? static_cast<int>(I) : PoisonMaskElem; | |
| }); | |
| for (const std::pair<unsigned, TreeEntry *> &Op : Data.second) { | |
| TreeEntry *TE = Op.second; | |
| if (!VisitedOps.insert(TE).second) | |
| continue; | |
| // TODO: Reordering of struct types is not supported. | |
| if (TE->State == TreeEntry::Vectorize && | |
| isa<StructType>(getValueType(TE->Scalars.front(), Opts.slp_revec))) | |
| continue; | |
| if (TE->ReuseShuffleIndices.size() == BestOrder.size()) { | |
| reorderNodeWithReuses(*TE, Mask); | |
| continue; | |
| } | |
| // Gathers are processed separately. | |
| if (TE->State != TreeEntry::Vectorize && | |
| TE->State != TreeEntry::StridedVectorize && | |
| TE->State != TreeEntry::ExpandVectorize && | |
| TE->State != TreeEntry::CompressVectorize && | |
| TE->State != TreeEntry::BlendedLoadVectorize && | |
| TE->State != TreeEntry::SplitVectorize && | |
| (TE->State != TreeEntry::ScatterVectorize || | |
| TE->ReorderIndices.empty())) | |
| continue; | |
| assert((BestOrder.size() == TE->ReorderIndices.size() || | |
| TE->ReorderIndices.empty()) && | |
| "Non-matching sizes of user/operand entries."); | |
| reorderOrder(TE->ReorderIndices, Mask); | |
| if (IgnoreReorder && TE == &getRootNode()) | |
| IgnoreReorder = false; | |
| } | |
| // For gathers just need to reorder its scalars. | |
| for (TreeEntry *Gather : GatherOps) { | |
| assert(Gather->ReorderIndices.empty() && | |
| "Unexpected reordering of gathers."); | |
| if (!Gather->ReuseShuffleIndices.empty()) { | |
| // Just reorder reuses indices. | |
| reorderReuses(Gather->ReuseShuffleIndices, Mask); | |
| continue; | |
| } | |
| // A ScatterVectorize (masked gather) node is scheduled, and the | |
| // scheduler reads its operand list at the same lane where the scalar | |
| // load sits, so Scalars and the operand list must stay aligned. | |
| // Record the reorder in ReorderIndices (applied by the final shuffle) | |
| // instead of physically permuting the scalars, matching how a scatter | |
| // node with a non-empty order is reordered above. | |
| if (Gather->State == TreeEntry::ScatterVectorize) { | |
| reorderOrder(Gather->ReorderIndices, Mask); | |
| Visited.insert(Gather); | |
| continue; | |
| } | |
| reorderScalars(Gather->Scalars, Mask); | |
| Visited.insert(Gather); | |
| } | |
| // Reorder operands of the user node and set the ordering for the user | |
| // node itself. | |
| auto IsNotProfitableAltCodeNode = [](const TreeEntry &TE) { | |
| return TE.isAltShuffle() && | |
| (!TE.ReuseShuffleIndices.empty() || TE.getVectorFactor() == 2 || | |
| TE.ReorderIndices.empty()); | |
| }; | |
| if (Data.first->State != TreeEntry::Vectorize || | |
| !isa<ExtractElementInst, ExtractValueInst, LoadInst>( | |
| Data.first->getMainOp()) || | |
| IsNotProfitableAltCodeNode(*Data.first)) { | |
| Data.first->reorderOperands(Mask); | |
| markCopyableDepsForRecalc(*Data.first); | |
| } | |
| if (!isa<InsertElementInst, InsertValueInst, StoreInst>( | |
| Data.first->getMainOp()) || | |
| IsNotProfitableAltCodeNode(*Data.first) || | |
| Data.first->State == TreeEntry::CompressVectorize) { | |
| reorderScalars(Data.first->Scalars, Mask); | |
| reorderOrder(Data.first->ReorderIndices, MaskOrder, | |
| /*BottomOrder=*/true); | |
| if (Data.first->ReuseShuffleIndices.empty() && | |
| !Data.first->ReorderIndices.empty() && | |
| !IsNotProfitableAltCodeNode(*Data.first)) { | |
| // Insert user node to the list to try to sink reordering deeper in | |
| // the graph. | |
| Queue.push(Data.first); | |
| } | |
| } else { | |
| reorderOrder(Data.first->ReorderIndices, Mask); | |
| } | |
| } | |
| } | |
| // If the reordering is unnecessary, just remove the reorder. | |
| if (IgnoreReorder && !getRootNode().ReorderIndices.empty() && | |
| getRootNode().ReuseShuffleIndices.empty()) | |
| getRootNode().ReorderIndices.clear(); | |
| } | |
| Instruction *BoUpSLP::getRootEntryInstruction(const TreeEntry &Entry) const { | |
| if (Entry.hasState() && | |
| (Entry.getOpcode() == Instruction::Store || | |
| Entry.getOpcode() == Instruction::Load) && | |
| Entry.State == TreeEntry::StridedVectorize && Entry.isReverse()) | |
| return dyn_cast<Instruction>(Entry.Scalars[Entry.ReorderIndices.front()]); | |
| return dyn_cast<Instruction>(Entry.Scalars.front()); | |
| } | |
| void BoUpSLP::buildExternalUses( | |
| const ExtraValueToDebugLocsMap &ExternallyUsedValues) { | |
| const size_t NumVectScalars = ScalarToTreeEntries.size() + 1; | |
| DenseMap<Value *, unsigned> ScalarToExtUses; | |
| // Peeled scalars still claimed by the tree (gathered, listed in some | |
| // entry's scalars, or modeled as a copyable element, which is emitted as | |
| // a scalar) survive as plain code, along with the peeled scalars | |
| // in their operand chains, which no vector node can rematerialize. | |
| KeptReassocScalars.clear(); | |
| SmallVector<const Value *, 8> KeptWorklist; | |
| for (const auto &[V, Owners] : ReassocScalarToTreeEntries) | |
| if ((isGathered(V) || !getTreeEntries(V).empty() || | |
| any_of(Owners, | |
| [V = V](const TreeEntry *TE) { | |
| return TE->isCopyableElement(const_cast<Value *>(V)); | |
| })) && | |
| KeptReassocScalars.insert(V).second) | |
| KeptWorklist.push_back(V); | |
| while (!KeptWorklist.empty()) { | |
| const Value *V = KeptWorklist.pop_back_val(); | |
| for (const Value *Op : cast<Instruction>(V)->operand_values()) | |
| if (ReassocScalarToTreeEntries.contains(Op) && | |
| KeptReassocScalars.insert(Op).second) | |
| KeptWorklist.push_back(Op); | |
| } | |
| // Collect the values that we need to extract from the tree. | |
| for (auto &TEPtr : VectorizableTree) { | |
| TreeEntry *Entry = TEPtr.get(); | |
| // No need to handle users of gathered values. | |
| if (Entry->isGather() || Entry->State == TreeEntry::SplitVectorize || | |
| DeletedNodes.contains(Entry) || | |
| TransformedToGatherNodes.contains(Entry)) | |
| continue; | |
| // For each lane: | |
| for (int Lane = 0, LE = Entry->Scalars.size(); Lane != LE; ++Lane) { | |
| Value *Scalar = Entry->Scalars[Lane]; | |
| if (!isa<Instruction>(Scalar) || Entry->isCopyableElement(Scalar)) | |
| continue; | |
| bool IsStructScalar = isa<StructType>(Scalar->getType()); | |
| // All uses must be replaced already? No need to do it again. | |
| auto It = ScalarToExtUses.find(Scalar); | |
| if (It != ScalarToExtUses.end() && !ExternalUses[It->second].User) | |
| continue; | |
| if (!IsStructScalar && Scalar->hasNUsesOrMore(NumVectScalars)) { | |
| unsigned FoundLane = Entry->findLaneForValue(Scalar); | |
| LLVM_DEBUG(dbgs() << "SLP: Need to extract from lane " << FoundLane | |
| << " from " << *Scalar << "for many users.\n"); | |
| It = ScalarToExtUses.try_emplace(Scalar, ExternalUses.size()).first; | |
| ExternalUses.emplace_back(Scalar, nullptr, *Entry, FoundLane); | |
| ExternalUsesWithNonUsers.insert(Scalar); | |
| continue; | |
| } | |
| // Check if the scalar is externally used as an extra arg. | |
| const auto ExtI = ExternallyUsedValues.find(Scalar); | |
| if (ExtI != ExternallyUsedValues.end()) { | |
| unsigned FoundLane = Entry->findLaneForValue(Scalar); | |
| LLVM_DEBUG(dbgs() << "SLP: Need to extract: Extra arg from lane " | |
| << FoundLane << " from " << *Scalar << ".\n"); | |
| ScalarToExtUses.try_emplace(Scalar, ExternalUses.size()); | |
| ExternalUses.emplace_back(Scalar, nullptr, *Entry, FoundLane); | |
| continue; | |
| } | |
| for (User *U : Scalar->users()) { | |
| LLVM_DEBUG(dbgs() << "SLP: Checking user:" << *U << ".\n"); | |
| Instruction *UserInst = dyn_cast<Instruction>(U); | |
| if (!UserInst || isDeleted(UserInst)) | |
| continue; | |
| // Ignore users in the user ignore list. | |
| if (UserIgnoreList && UserIgnoreList->contains(UserInst)) | |
| continue; | |
| // Peeled reassociated scalars are subsumed by the flattened node and | |
| // erased during vectorization, not external users. Kept ones survive | |
| // and their uses of erased scalars become extracts like any other. | |
| if (isReassocScalarVectorized(UserInst) && | |
| !KeptReassocScalars.contains(UserInst)) { | |
| LLVM_DEBUG(dbgs() << "SLP: \tInternal (reassociated) user will be " | |
| "removed:" | |
| << *U << ".\n"); | |
| continue; | |
| } | |
| // Skip in-tree scalars that become vectors | |
| if (ArrayRef<TreeEntry *> UseEntries = getTreeEntries(U); | |
| any_of(UseEntries, [this](const TreeEntry *UseEntry) { | |
| return !DeletedNodes.contains(UseEntry) && | |
| !TransformedToGatherNodes.contains(UseEntry); | |
| })) { | |
| // Some in-tree scalars will remain as scalar in vectorized | |
| // instructions. If that is the case, the one in FoundLane will | |
| // be used. | |
| if (!((Scalar->getType()->getScalarType()->isPointerTy() && | |
| isa<LoadInst, StoreInst>(UserInst)) || | |
| isa<CallInst>(UserInst)) || | |
| all_of(UseEntries, [&](TreeEntry *UseEntry) { | |
| if (DeletedNodes.contains(UseEntry) || | |
| TransformedToGatherNodes.contains(UseEntry)) | |
| return true; | |
| return UseEntry->State == TreeEntry::ScatterVectorize || | |
| !doesInTreeUserNeedToExtract( | |
| Scalar, getRootEntryInstruction(*UseEntry), TLI, | |
| TTI); | |
| })) { | |
| LLVM_DEBUG(dbgs() << "SLP: \tInternal user will be removed:" << *U | |
| << ".\n"); | |
| assert(none_of(UseEntries, | |
| [](TreeEntry *UseEntry) { | |
| return UseEntry->isGather(); | |
| }) && | |
| "Bad state"); | |
| continue; | |
| } | |
| if (!IsStructScalar) { | |
| U = nullptr; | |
| if (It != ScalarToExtUses.end()) { | |
| ExternalUses[It->second].User = nullptr; | |
| break; | |
| } | |
| } | |
| } | |
| if (U && !IsStructScalar && Scalar->hasNUsesOrMore(UsesLimit)) | |
| U = nullptr; | |
| unsigned FoundLane = Entry->findLaneForValue(Scalar); | |
| LLVM_DEBUG(dbgs() << "SLP: Need to extract:" << *UserInst | |
| << " from lane " << FoundLane << " from " << *Scalar | |
| << ".\n"); | |
| It = ScalarToExtUses.try_emplace(Scalar, ExternalUses.size()).first; | |
| ExternalUses.emplace_back(Scalar, U, *Entry, FoundLane); | |
| ExternalUsesWithNonUsers.insert(Scalar); | |
| if (!U) | |
| break; | |
| } | |
| } | |
| } | |
| // The expansion of the runtime stride may reuse an in-tree instruction with | |
| // the matching SCEV, which gets erased upon vectorization. Register an | |
| // external use for it to replace the stride operand with the extract. | |
| SmallVector<std::pair<const SCEV *, BasicBlock *>> Strides; | |
| for (const auto &[StridedTE, SPtrInfo] : TreeEntryToStridedPtrInfoMap) | |
| if (SPtrInfo.StrideSCEV && !SPtrInfo.StrideVal && | |
| !DeletedNodes.contains(StridedTE) && | |
| !TransformedToGatherNodes.contains(StridedTE)) | |
| Strides.emplace_back(SPtrInfo.StrideSCEV, | |
| StridedTE->getMainOp()->getParent()); | |
| if (Strides.empty()) | |
| return; | |
| for (const std::unique_ptr<TreeEntry> &TEPtr : VectorizableTree) { | |
| TreeEntry *Entry = TEPtr.get(); | |
| if (Entry->isGather() || Entry->State == TreeEntry::SplitVectorize || | |
| DeletedNodes.contains(Entry) || | |
| TransformedToGatherNodes.contains(Entry)) | |
| continue; | |
| for (Instruction *Scalar : make_isa_range<Instruction>(Entry->Scalars)) { | |
| if (Entry->isCopyableElement(Scalar) || | |
| !SE->isSCEVable(Scalar->getType())) | |
| continue; | |
| const SCEV *ScalarSCEV = SE->getSCEV(Scalar); | |
| if (isa<SCEVConstant>(ScalarSCEV) || | |
| none_of(Strides, [&](const auto &Stride) { | |
| return DT->dominates(Scalar, Stride.second->getTerminator()) && | |
| SCEVExprContains(Stride.first, [ScalarSCEV](const SCEV *S) { | |
| return S == ScalarSCEV; | |
| }); | |
| })) | |
| continue; | |
| auto It = ScalarToExtUses.find(Scalar); | |
| if (It != ScalarToExtUses.end()) { | |
| // Replace all uses: the stride operand is emitted later, during the | |
| // codegen. | |
| ExternalUses[It->second].User = nullptr; | |
| continue; | |
| } | |
| unsigned FoundLane = Entry->findLaneForValue(Scalar); | |
| LLVM_DEBUG(dbgs() << "SLP: Need to extract: strided load stride from " | |
| "lane " | |
| << FoundLane << " from " << *Scalar << ".\n"); | |
| ScalarToExtUses.try_emplace(Scalar, ExternalUses.size()); | |
| ExternalUses.emplace_back(Scalar, nullptr, *Entry, FoundLane); | |
| ExternalUsesWithNonUsers.insert(Scalar); | |
| } | |
| } | |
| } | |
| SmallVector<SmallVector<StoreInst *>> | |
| BoUpSLP::collectUserStores(const BoUpSLP::TreeEntry *TE) const { | |
| SmallDenseMap<std::tuple<BasicBlock *, Type *, Value *>, | |
| SmallVector<StoreInst *>, 8> | |
| PtrToStoresMap; | |
| for (unsigned Lane : seq<unsigned>(0, TE->Scalars.size())) { | |
| Value *V = TE->Scalars[Lane]; | |
| // Don't iterate over the users of constant data. | |
| if (!isa<Instruction>(V)) | |
| continue; | |
| // To save compilation time we don't visit if we have too many users. | |
| if (V->hasNUsesOrMore(UsesLimit)) | |
| break; | |
| // Collect stores per pointer object. | |
| for (User *U : V->users()) { | |
| auto *SI = dyn_cast<StoreInst>(U); | |
| // Test whether we can handle the store. V might be a global, which could | |
| // be used in a different function. | |
| if (SI == nullptr || !SI->isSimple() || SI->getFunction() != F || | |
| !isValidElementType(SI->getValueOperand()->getType(), Opts.slp_revec)) | |
| continue; | |
| // Skip entry if already | |
| if (isVectorized(U)) | |
| continue; | |
| Value *Ptr = getUnderlyingObject(SI->getPointerOperand(), | |
| Opts.slp_recursion_max_depth); | |
| auto &StoresVec = PtrToStoresMap[{SI->getParent(), | |
| SI->getValueOperand()->getType(), Ptr}]; | |
| // For now just keep one store per pointer object per lane. | |
| // TODO: Extend this to support multiple stores per pointer per lane | |
| if (StoresVec.size() > Lane) | |
| continue; | |
| if (!StoresVec.empty()) { | |
| std::optional<int64_t> Diff = getPointersDiff( | |
| SI->getValueOperand()->getType(), SI->getPointerOperand(), | |
| SI->getValueOperand()->getType(), | |
| StoresVec.front()->getPointerOperand(), *DL, *SE, | |
| /*StrictCheck=*/true); | |
| // We failed to compare the pointers so just abandon this store. | |
| if (!Diff) | |
| continue; | |
| } | |
| StoresVec.push_back(SI); | |
| } | |
| } | |
| SmallVector<SmallVector<StoreInst *>> Res(PtrToStoresMap.size()); | |
| unsigned I = 0; | |
| for (auto &P : PtrToStoresMap) { | |
| Res[I].swap(P.second); | |
| ++I; | |
| } | |
| return Res; | |
| } | |
| bool BoUpSLP::canFormVector(ArrayRef<StoreInst *> StoresVec, | |
| OrdersType &ReorderIndices) const { | |
| // We check whether the stores in StoreVec can form a vector by sorting them | |
| // and checking whether they are consecutive. | |
| // To avoid calling getPointersDiff() while sorting we create a vector of | |
| // pairs {store, offset from first} and sort this instead. | |
| SmallVector<std::pair<int64_t, unsigned>> StoreOffsetVec; | |
| StoreInst *S0 = StoresVec[0]; | |
| StoreOffsetVec.emplace_back(0, 0); | |
| Type *S0Ty = S0->getValueOperand()->getType(); | |
| Value *S0Ptr = S0->getPointerOperand(); | |
| for (unsigned Idx : seq<unsigned>(1, StoresVec.size())) { | |
| StoreInst *SI = StoresVec[Idx]; | |
| std::optional<int64_t> Diff = | |
| getPointersDiff(S0Ty, S0Ptr, SI->getValueOperand()->getType(), | |
| SI->getPointerOperand(), *DL, *SE, | |
| /*StrictCheck=*/true); | |
| StoreOffsetVec.emplace_back(*Diff, Idx); | |
| } | |
| // Check if the stores are consecutive by checking if their difference is 1. | |
| if (StoreOffsetVec.size() != StoresVec.size()) | |
| return false; | |
| sort(StoreOffsetVec, llvm::less_first()); | |
| unsigned Idx = 0; | |
| int64_t PrevDist = 0; | |
| for (const auto &P : StoreOffsetVec) { | |
| if (Idx > 0 && P.first != PrevDist + 1) | |
| return false; | |
| PrevDist = P.first; | |
| ++Idx; | |
| } | |
| // Calculate the shuffle indices according to their offset against the sorted | |
| // StoreOffsetVec. | |
| ReorderIndices.assign(StoresVec.size(), 0); | |
| bool IsIdentity = true; | |
| for (auto [I, P] : enumerate(StoreOffsetVec)) { | |
| ReorderIndices[P.second] = I; | |
| IsIdentity &= P.second == I; | |
| } | |
| // Identity order (e.g., {0,1,2,3}) is modeled as an empty OrdersType in | |
| // reorderTopToBottom() and reorderBottomToTop(), so we are following the | |
| // same convention here. | |
| if (IsIdentity) | |
| ReorderIndices.clear(); | |
| return true; | |
| } | |
| #ifndef NDEBUG | |
| LLVM_DUMP_METHOD static void dumpOrder(const BoUpSLP::OrdersType &Order) { | |
| for (unsigned Idx : Order) | |
| dbgs() << Idx << ", "; | |
| dbgs() << "\n"; | |
| } | |
| #endif | |
| SmallVector<BoUpSLP::OrdersType, 1> | |
| BoUpSLP::findExternalStoreUsersReorderIndices(TreeEntry *TE) const { | |
| unsigned NumLanes = TE->Scalars.size(); | |
| SmallVector<SmallVector<StoreInst *>> Stores = collectUserStores(TE); | |
| // Holds the reorder indices for each candidate store vector that is a user of | |
| // the current TreeEntry. | |
| SmallVector<OrdersType, 1> ExternalReorderIndices; | |
| // Now inspect the stores collected per pointer and look for vectorization | |
| // candidates. For each candidate calculate the reorder index vector and push | |
| // it into `ExternalReorderIndices` | |
| // If we have fewer than NumLanes stores, then we can't form a vector. | |
| for (ArrayRef<StoreInst *> StoresVec : | |
| make_filter_range(Stores, [&](ArrayRef<StoreInst *> StoresVec) { | |
| return StoresVec.size() == NumLanes; | |
| })) { | |
| // If the stores are not consecutive then abandon this StoresVec. | |
| OrdersType ReorderIndices; | |
| if (!canFormVector(StoresVec, ReorderIndices)) | |
| continue; | |
| // We now know that the scalars in StoresVec can form a vector instruction, | |
| // so set the reorder indices. | |
| ExternalReorderIndices.push_back(ReorderIndices); | |
| } | |
| return ExternalReorderIndices; | |
| } | |
| void BoUpSLP::buildTree(ArrayRef<Value *> Roots, | |
| const SmallDenseSet<Value *> &UserIgnoreLst) { | |
| deleteTree(); | |
| assert(TreeEntryToStridedPtrInfoMap.empty() && | |
| "TreeEntryToStridedPtrInfoMap is not cleared"); | |
| UserIgnoreList = &UserIgnoreLst; | |
| if (!allSameType(Roots)) | |
| return; | |
| buildTreeRec(Roots, 0, EdgeInfo()); | |
| // Build splat-gather subtrees here so the reordering passes cover them too. | |
| tryToVectorizeSplatGatheredScalars(); | |
| } | |
| void BoUpSLP::buildTree(ArrayRef<Value *> Roots) { | |
| deleteTree(); | |
| assert(TreeEntryToStridedPtrInfoMap.empty() && | |
| "TreeEntryToStridedPtrInfoMap is not cleared"); | |
| if (!allSameType(Roots)) | |
| return; | |
| buildTreeRec(Roots, 0, EdgeInfo()); | |
| // Build splat-gather subtrees here so the reordering passes cover them too. | |
| tryToVectorizeSplatGatheredScalars(); | |
| } | |
| /// Tries to find subvector of loads and builds new vector of only loads if can | |
| /// be profitable. | |
| static void gatherPossiblyVectorizableLoads( | |
| const BoUpSLP &R, ArrayRef<Value *> VL, const DataLayout &DL, | |
| ScalarEvolution &SE, const TargetTransformInfo &TTI, | |
| SmallVectorImpl<SmallVector<std::pair<LoadInst *, int64_t>>> &GatheredLoads, | |
| bool AddNew = true) { | |
| if (VL.empty()) | |
| return; | |
| Type *ScalarTy = getValueType(VL.front(), R.Opts.slp_revec); | |
| if (!isValidElementType(ScalarTy, R.Opts.slp_revec)) | |
| return; | |
| SmallVector<SmallVector<std::pair<LoadInst *, int64_t>>> ClusteredLoads; | |
| SmallVector<DenseMap<int64_t, LoadInst *>> ClusteredDistToLoad; | |
| for (LoadInst *LI : make_isa_range<LoadInst>(VL)) { | |
| if (R.isDeleted(LI) || R.isVectorized(LI) || !LI->isSimple()) | |
| continue; | |
| bool IsFound = false; | |
| for (auto [Map, Data] : zip(ClusteredDistToLoad, ClusteredLoads)) { | |
| assert(LI->getParent() == Data.front().first->getParent() && | |
| LI->getType() == Data.front().first->getType() && | |
| getUnderlyingObject(LI->getPointerOperand(), | |
| R.Opts.slp_recursion_max_depth) == | |
| getUnderlyingObject(Data.front().first->getPointerOperand(), | |
| R.Opts.slp_recursion_max_depth) && | |
| "Expected loads with the same type, same parent and same " | |
| "underlying pointer."); | |
| std::optional<int64_t> Dist = R.getPointersDiff( | |
| LI->getType(), LI->getPointerOperand(), Data.front().first->getType(), | |
| Data.front().first->getPointerOperand(), DL, SE, | |
| /*StrictCheck=*/true); | |
| if (!Dist) | |
| continue; | |
| auto It = Map.find(*Dist); | |
| if (It != Map.end() && It->second != LI) | |
| continue; | |
| if (It == Map.end()) { | |
| Data.emplace_back(LI, *Dist); | |
| Map.try_emplace(*Dist, LI); | |
| } | |
| IsFound = true; | |
| break; | |
| } | |
| if (!IsFound) { | |
| ClusteredLoads.emplace_back().emplace_back(LI, 0); | |
| ClusteredDistToLoad.emplace_back().try_emplace(0, LI); | |
| } | |
| } | |
| auto FindMatchingLoads = | |
| [&](ArrayRef<std::pair<LoadInst *, int64_t>> Loads, | |
| SmallVectorImpl<SmallVector<std::pair<LoadInst *, int64_t>>> | |
| &GatheredLoads, | |
| SetVector<unsigned> &ToAdd, SetVector<unsigned> &Repeated, | |
| int64_t &Offset, unsigned &Start) { | |
| if (Loads.empty()) | |
| return GatheredLoads.end(); | |
| LoadInst *LI = Loads.front().first; | |
| auto IsFromStart = [&](const auto &P) { return P.index() >= Start; }; | |
| for (auto [Idx, Data] : | |
| make_filter_range(enumerate(GatheredLoads), IsFromStart)) { | |
| ToAdd.clear(); | |
| if (LI->getParent() != Data.front().first->getParent() || | |
| LI->getType() != Data.front().first->getType()) | |
| continue; | |
| std::optional<int64_t> Dist = | |
| R.getPointersDiff(LI->getType(), LI->getPointerOperand(), | |
| Data.front().first->getType(), | |
| Data.front().first->getPointerOperand(), DL, SE, | |
| /*StrictCheck=*/true); | |
| if (!Dist) | |
| continue; | |
| SmallSet<int64_t, 4> DataDists; | |
| SmallPtrSet<LoadInst *, 4> DataLoads; | |
| for (std::pair<LoadInst *, int64_t> P : Data) { | |
| DataDists.insert(P.second); | |
| DataLoads.insert(P.first); | |
| } | |
| // Found matching gathered loads - check if all loads are unique or | |
| // can be effectively vectorized. | |
| unsigned NumUniques = 0; | |
| for (auto [Cnt, Pair] : enumerate(Loads)) { | |
| bool Used = DataLoads.contains(Pair.first); | |
| if (!Used && !DataDists.contains(*Dist + Pair.second)) { | |
| ++NumUniques; | |
| ToAdd.insert(Cnt); | |
| } else if (Used) { | |
| Repeated.insert(Cnt); | |
| } | |
| } | |
| if (NumUniques > 0 && | |
| (Loads.size() == NumUniques || | |
| (Loads.size() - NumUniques >= 2 && | |
| Loads.size() - NumUniques >= Loads.size() / 2 && | |
| (has_single_bit(Data.size() + NumUniques) || | |
| bit_ceil(Data.size()) < | |
| bit_ceil(Data.size() + NumUniques))))) { | |
| Offset = *Dist; | |
| Start = Idx + 1; | |
| return std::next(GatheredLoads.begin(), Idx); | |
| } | |
| } | |
| ToAdd.clear(); | |
| return GatheredLoads.end(); | |
| }; | |
| for (ArrayRef<std::pair<LoadInst *, int64_t>> Data : ClusteredLoads) { | |
| unsigned Start = 0; | |
| SetVector<unsigned> ToAdd, LocalToAdd, Repeated; | |
| int64_t Offset = 0; | |
| auto *It = FindMatchingLoads(Data, GatheredLoads, LocalToAdd, Repeated, | |
| Offset, Start); | |
| while (It != GatheredLoads.end()) { | |
| assert(!LocalToAdd.empty() && "Expected some elements to add."); | |
| for (unsigned Idx : LocalToAdd) | |
| It->emplace_back(Data[Idx].first, Data[Idx].second + Offset); | |
| ToAdd.insert_range(LocalToAdd); | |
| It = FindMatchingLoads(Data, GatheredLoads, LocalToAdd, Repeated, Offset, | |
| Start); | |
| } | |
| if (any_of(seq<unsigned>(Data.size()), [&](unsigned Idx) { | |
| return !ToAdd.contains(Idx) && !Repeated.contains(Idx); | |
| })) { | |
| auto AddNewLoads = | |
| [&](SmallVectorImpl<std::pair<LoadInst *, int64_t>> &Loads) { | |
| for (unsigned Idx : seq<unsigned>(Data.size())) { | |
| if (ToAdd.contains(Idx) || Repeated.contains(Idx)) | |
| continue; | |
| Loads.push_back(Data[Idx]); | |
| } | |
| }; | |
| if (!AddNew) { | |
| LoadInst *LI = Data.front().first; | |
| It = find_if( | |
| GatheredLoads, [&](ArrayRef<std::pair<LoadInst *, int64_t>> PD) { | |
| return PD.front().first->getParent() == LI->getParent() && | |
| PD.front().first->getType() == LI->getType(); | |
| }); | |
| while (It != GatheredLoads.end()) { | |
| AddNewLoads(*It); | |
| It = std::find_if( | |
| std::next(It), GatheredLoads.end(), | |
| [&](ArrayRef<std::pair<LoadInst *, int64_t>> PD) { | |
| return PD.front().first->getParent() == LI->getParent() && | |
| PD.front().first->getType() == LI->getType(); | |
| }); | |
| } | |
| } | |
| GatheredLoads.emplace_back().append(Data.begin(), Data.end()); | |
| AddNewLoads(GatheredLoads.emplace_back()); | |
| } | |
| } | |
| } | |
| void BoUpSLP::tryToVectorizeGatheredLoads( | |
| const SmallMapVector< | |
| std::tuple<BasicBlock *, Value *, Type *>, | |
| SmallVector<SmallVector<std::pair<LoadInst *, int64_t>>>, 8> | |
| &GatheredLoads) { | |
| GatheredLoadsEntriesFirst = VectorizableTree.size(); | |
| SmallVector<SmallPtrSet<const Value *, 4>> LoadSetsToVectorize( | |
| LoadEntriesToVectorize.size()); | |
| for (auto [Idx, Set] : zip(LoadEntriesToVectorize, LoadSetsToVectorize)) | |
| Set.insert_range(VectorizableTree[Idx]->Scalars); | |
| // Sort loads by distance. | |
| auto LoadSorter = [](const std::pair<LoadInst *, int64_t> &L1, | |
| const std::pair<LoadInst *, int64_t> &L2) { | |
| return L1.second > L2.second; | |
| }; | |
| auto IsMaskedGatherSupported = [&, TTI = TTI](ArrayRef<LoadInst *> Loads) { | |
| ArrayRef<Value *> Values(reinterpret_cast<Value *const *>(Loads.begin()), | |
| Loads.size()); | |
| Align Alignment = computeCommonAlignment<LoadInst>(Values); | |
| auto *Ty = cast<VectorType>( | |
| getWidenedType(Loads.front()->getType(), Loads.size())); | |
| return TTI->isLegalMaskedGather(Ty, Alignment) && | |
| !TTI->forceScalarizeMaskedGather(Ty, Alignment); | |
| }; | |
| auto GetVectorizedRanges = [this](ArrayRef<LoadInst *> Loads, | |
| BoUpSLP::ValueSet &VectorizedLoads, | |
| SmallVectorImpl<LoadInst *> &NonVectorized, | |
| bool Final, unsigned MaxVF) { | |
| SmallVector<std::pair<ArrayRef<Value *>, LoadsState>> Results; | |
| unsigned StartIdx = 0; | |
| SmallVector<int> CandidateVFs; | |
| if (isAllowedNonPowerOf2VF(MaxVF, Opts.slp_vectorize_non_power_of_2)) | |
| CandidateVFs.push_back(MaxVF); | |
| for (int NumElts = getFloorFullVectorNumberOfElements( | |
| *TTI, Loads.front()->getType(), MaxVF, Opts.slp_revec); | |
| NumElts > 1; | |
| NumElts = getFloorFullVectorNumberOfElements( | |
| *TTI, Loads.front()->getType(), NumElts - 1, Opts.slp_revec)) { | |
| CandidateVFs.push_back(NumElts); | |
| if (Opts.slp_vectorize_non_power_of_2 && NumElts > 2) | |
| CandidateVFs.push_back(NumElts - 1); | |
| } | |
| if (Final && CandidateVFs.empty()) | |
| return Results; | |
| unsigned BestVF = Final ? CandidateVFs.back() : 0; | |
| auto IsValidVF = [&](unsigned VF) { return !Final || VF <= BestVF; }; | |
| for (unsigned NumElts : make_filter_range(CandidateVFs, IsValidVF)) { | |
| SmallVector<unsigned> MaskedGatherVectorized; | |
| for (unsigned Cnt = StartIdx, E = Loads.size(); Cnt < E; | |
| ++Cnt) { | |
| ArrayRef<LoadInst *> Slice = | |
| ArrayRef(Loads).slice(Cnt, std::min(NumElts, E - Cnt)); | |
| if (VectorizedLoads.count(Slice.front()) || | |
| VectorizedLoads.count(Slice.back()) || | |
| areKnownNonVectorizableLoads(Slice)) | |
| continue; | |
| // Check if it is profitable to try vectorizing gathered loads. It is | |
| // profitable if we have more than 3 consecutive loads or if we have | |
| // less but all users are vectorized or deleted. | |
| bool AllowToVectorize = false; | |
| // Check if it is profitable to vectorize 2-elements loads. | |
| if (NumElts == 2) { | |
| bool IsLegalBroadcastLoad = TTI->isLegalBroadcastLoad( | |
| Slice.front()->getType(), ElementCount::getFixed(NumElts)); | |
| auto CheckIfAllowed = [=](ArrayRef<LoadInst *> Slice) { | |
| // If single use/user - allow to vectorize. | |
| auto IsNotSingleUse = [](LoadInst *LI) { return !LI->hasOneUse(); }; | |
| for (LoadInst *LI : make_filter_range(Slice, IsNotSingleUse)) { | |
| // 1. Check if number of uses equals number of users. | |
| // 2. All users are deleted. | |
| // 3. The load broadcasts are not allowed or the load is not | |
| // broadcasted. | |
| if (static_cast<unsigned int>(std::distance( | |
| LI->user_begin(), LI->user_end())) != LI->getNumUses()) | |
| return false; | |
| if (!IsLegalBroadcastLoad) | |
| continue; | |
| if (LI->hasNUsesOrMore(UsesLimit)) | |
| return false; | |
| for (User *U : LI->users()) { | |
| if (auto *UI = dyn_cast<Instruction>(U); UI && isDeleted(UI)) | |
| continue; | |
| for (const TreeEntry *UTE : getTreeEntries(U)) { | |
| for (int I : seq<int>(UTE->getNumOperands())) { | |
| if (all_of(UTE->getOperand(I), [LI](Value *V) { | |
| return V == LI || isa<PoisonValue>(V); | |
| })) | |
| // Found legal broadcast - do not vectorize. | |
| return false; | |
| } | |
| } | |
| } | |
| } | |
| return true; | |
| }; | |
| AllowToVectorize = CheckIfAllowed(Slice); | |
| } else { | |
| AllowToVectorize = | |
| NumElts >= 3 || | |
| any_of(ValueToGatherNodes.at(Slice.front()), | |
| [=](const TreeEntry *TE) { | |
| return TE->Scalars.size() == 2 && | |
| ((TE->Scalars.front() == Slice.front() && | |
| TE->Scalars.back() == Slice.back()) || | |
| (TE->Scalars.front() == Slice.back() && | |
| TE->Scalars.back() == Slice.front())); | |
| }); | |
| } | |
| if (AllowToVectorize) { | |
| SmallVector<Value *> PointerOps; | |
| OrdersType CurrentOrder; | |
| // Try to build vector load. | |
| ArrayRef<Value *> Values( | |
| reinterpret_cast<Value *const *>(Slice.begin()), Slice.size()); | |
| StridedPtrInfo SPtrInfo; | |
| LoadsState LS = canVectorizeLoads(Values, Slice.front(), CurrentOrder, | |
| PointerOps, SPtrInfo, &BestVF); | |
| if (LS != LoadsState::Gather || | |
| (BestVF > 1 && static_cast<unsigned>(NumElts) == 2 * BestVF)) { | |
| if (LS == LoadsState::ScatterVectorize) { | |
| if (MaskedGatherVectorized.empty() || | |
| Cnt >= MaskedGatherVectorized.back() + NumElts) | |
| MaskedGatherVectorized.push_back(Cnt); | |
| continue; | |
| } | |
| if (LS != LoadsState::Gather) { | |
| Results.emplace_back(Values, LS); | |
| VectorizedLoads.insert_range(Slice); | |
| // If we vectorized initial block, no need to try to vectorize it | |
| // again. | |
| if (Cnt == StartIdx) | |
| StartIdx += NumElts; | |
| } | |
| // Check if the whole array was vectorized already - exit. | |
| if (StartIdx >= Loads.size()) | |
| break; | |
| // Erase last masked gather candidate, if another candidate within | |
| // the range is found to be better. | |
| if (!MaskedGatherVectorized.empty() && | |
| Cnt < MaskedGatherVectorized.back() + NumElts) | |
| MaskedGatherVectorized.pop_back(); | |
| Cnt += NumElts - 1; | |
| continue; | |
| } | |
| } | |
| if (!AllowToVectorize || BestVF == 0) | |
| registerNonVectorizableLoads(Slice); | |
| } | |
| // Mark masked gathers candidates as vectorized, if any. | |
| for (unsigned Cnt : MaskedGatherVectorized) { | |
| ArrayRef<LoadInst *> Slice = ArrayRef(Loads).slice( | |
| Cnt, std::min<unsigned>(NumElts, Loads.size() - Cnt)); | |
| ArrayRef<Value *> Values( | |
| reinterpret_cast<Value *const *>(Slice.begin()), Slice.size()); | |
| Results.emplace_back(Values, LoadsState::ScatterVectorize); | |
| VectorizedLoads.insert_range(Slice); | |
| // If we vectorized initial block, no need to try to vectorize it again. | |
| if (Cnt == StartIdx) | |
| StartIdx += NumElts; | |
| } | |
| } | |
| for (LoadInst *LI : Loads) { | |
| if (!VectorizedLoads.contains(LI)) | |
| NonVectorized.push_back(LI); | |
| } | |
| return Results; | |
| }; | |
| auto ProcessGatheredLoads = | |
| [&, &TTI = *TTI]( | |
| ArrayRef<SmallVector<std::pair<LoadInst *, int64_t>>> GatheredLoads, | |
| bool Final = false) { | |
| SmallVector<LoadInst *> NonVectorized; | |
| for (ArrayRef<std::pair<LoadInst *, int64_t>> LoadsDists : | |
| GatheredLoads) { | |
| if (LoadsDists.size() <= 1) { | |
| NonVectorized.push_back(LoadsDists.back().first); | |
| continue; | |
| } | |
| SmallVector<std::pair<LoadInst *, int64_t>> LocalLoadsDists( | |
| LoadsDists); | |
| SmallVector<LoadInst *> OriginalLoads(make_first_range(LoadsDists)); | |
| stable_sort(LocalLoadsDists, LoadSorter); | |
| SmallVector<LoadInst *> Loads; | |
| unsigned MaxConsecutiveDistance = 0; | |
| unsigned CurrentConsecutiveDist = 1; | |
| int64_t LastDist = LocalLoadsDists.front().second; | |
| bool AllowMaskedGather = IsMaskedGatherSupported(OriginalLoads); | |
| auto IsNotVectorized = [&](const std::pair<LoadInst *, int64_t> &L) { | |
| return !isVectorized(L.first); | |
| }; | |
| for (const std::pair<LoadInst *, int64_t> &L : | |
| make_filter_range(LocalLoadsDists, IsNotVectorized)) { | |
| assert(LastDist >= L.second && | |
| "Expected first distance always not less than second"); | |
| if (static_cast<uint64_t>(LastDist - L.second) == | |
| CurrentConsecutiveDist) { | |
| ++CurrentConsecutiveDist; | |
| MaxConsecutiveDistance = | |
| std::max(MaxConsecutiveDistance, CurrentConsecutiveDist); | |
| Loads.push_back(L.first); | |
| continue; | |
| } | |
| if (!AllowMaskedGather && CurrentConsecutiveDist == 1 && | |
| !Loads.empty()) | |
| Loads.pop_back(); | |
| CurrentConsecutiveDist = 1; | |
| LastDist = L.second; | |
| Loads.push_back(L.first); | |
| } | |
| if (Loads.size() <= 1) | |
| continue; | |
| if (AllowMaskedGather) | |
| MaxConsecutiveDistance = Loads.size(); | |
| else if (MaxConsecutiveDistance < 2) | |
| continue; | |
| BoUpSLP::ValueSet VectorizedLoads; | |
| SmallVector<LoadInst *> SortedNonVectorized; | |
| SmallVector<std::pair<ArrayRef<Value *>, LoadsState>> Results = | |
| GetVectorizedRanges(Loads, VectorizedLoads, SortedNonVectorized, | |
| Final, MaxConsecutiveDistance); | |
| if (!Results.empty() && !SortedNonVectorized.empty() && | |
| OriginalLoads.size() == Loads.size() && | |
| MaxConsecutiveDistance == Loads.size() && | |
| all_of(Results, | |
| [](const std::pair<ArrayRef<Value *>, LoadsState> &P) { | |
| return P.second == LoadsState::ScatterVectorize; | |
| })) { | |
| VectorizedLoads.clear(); | |
| SmallVector<LoadInst *> UnsortedNonVectorized; | |
| SmallVector<std::pair<ArrayRef<Value *>, LoadsState>> | |
| UnsortedResults = | |
| GetVectorizedRanges(OriginalLoads, VectorizedLoads, | |
| UnsortedNonVectorized, Final, | |
| OriginalLoads.size()); | |
| if (SortedNonVectorized.size() >= UnsortedNonVectorized.size()) { | |
| SortedNonVectorized.swap(UnsortedNonVectorized); | |
| Results.swap(UnsortedResults); | |
| } | |
| } | |
| for (ArrayRef<Value *> Slice : make_first_range(Results)) { | |
| LLVM_DEBUG(dbgs() << "SLP: Trying to vectorize gathered loads (" | |
| << Slice.size() << ")\n"); | |
| if (any_of(Slice, [&](Value *V) { return isVectorized(V); })) { | |
| for (Value *L : Slice) | |
| if (!isVectorized(L)) | |
| SortedNonVectorized.push_back(cast<LoadInst>(L)); | |
| continue; | |
| } | |
| // Select maximum VF as a maximum of user gathered nodes and | |
| // distance between scalar loads in these nodes. | |
| unsigned MaxVF = Slice.size(); | |
| unsigned UserMaxVF = 0; | |
| unsigned InterleaveFactor = 0; | |
| if (MaxVF == 2) { | |
| UserMaxVF = MaxVF; | |
| } else { | |
| // Found distance between segments of the interleaved loads. | |
| std::optional<unsigned> InterleavedLoadsDistance = 0; | |
| unsigned Order = 0; | |
| std::optional<unsigned> CommonVF = 0; | |
| DenseMap<const TreeEntry *, unsigned> EntryToPosition; | |
| SmallPtrSet<const TreeEntry *, 8> DeinterleavedNodes; | |
| for (auto [Idx, V] : enumerate(Slice)) { | |
| for (const TreeEntry *E : ValueToGatherNodes.at(V)) { | |
| UserMaxVF = std::max<unsigned>(UserMaxVF, E->Scalars.size()); | |
| unsigned Pos = | |
| EntryToPosition.try_emplace(E, Idx).first->second; | |
| UserMaxVF = std::max<unsigned>(UserMaxVF, Idx - Pos + 1); | |
| if (CommonVF) { | |
| if (*CommonVF == 0) { | |
| CommonVF = E->Scalars.size(); | |
| continue; | |
| } | |
| if (*CommonVF != E->Scalars.size()) | |
| CommonVF.reset(); | |
| } | |
| // Check if the load is the part of the interleaved load. | |
| if (Pos != Idx && InterleavedLoadsDistance) { | |
| if (!DeinterleavedNodes.contains(E) && | |
| any_of(E->Scalars, [&, Slice = Slice](Value *V) { | |
| if (isa<Constant>(V)) | |
| return false; | |
| if (isVectorized(V)) | |
| return true; | |
| const auto &Nodes = ValueToGatherNodes.at(V); | |
| return (Nodes.size() != 1 || !Nodes.contains(E)) && | |
| !is_contained(Slice, V); | |
| })) { | |
| InterleavedLoadsDistance.reset(); | |
| continue; | |
| } | |
| DeinterleavedNodes.insert(E); | |
| if (*InterleavedLoadsDistance == 0) { | |
| InterleavedLoadsDistance = Idx - Pos; | |
| continue; | |
| } | |
| if ((Idx - Pos) % *InterleavedLoadsDistance != 0 || | |
| (Idx - Pos) / *InterleavedLoadsDistance < Order) | |
| InterleavedLoadsDistance.reset(); | |
| Order = (Idx - Pos) / InterleavedLoadsDistance.value_or(1); | |
| } | |
| } | |
| } | |
| DeinterleavedNodes.clear(); | |
| // Check if the large load represents interleaved load operation. | |
| if (InterleavedLoadsDistance.value_or(0) > 1 && | |
| CommonVF.value_or(0) != 0) { | |
| InterleaveFactor = bit_ceil(*InterleavedLoadsDistance); | |
| unsigned VF = *CommonVF; | |
| OrdersType Order; | |
| SmallVector<Value *> PointerOps; | |
| StridedPtrInfo SPtrInfo; | |
| // Segmented load detected - vectorize at maximum vector factor. | |
| if (InterleaveFactor <= Slice.size() && | |
| TTI.isLegalInterleavedAccessType( | |
| cast<VectorType>( | |
| getWidenedType(Slice.front()->getType(), VF)), | |
| InterleaveFactor, | |
| cast<LoadInst>(Slice.front())->getAlign(), | |
| cast<LoadInst>(Slice.front()) | |
| ->getPointerAddressSpace()) && | |
| canVectorizeLoads(Slice, Slice.front(), Order, PointerOps, | |
| SPtrInfo) == LoadsState::Vectorize) { | |
| UserMaxVF = InterleaveFactor * VF; | |
| } else { | |
| InterleaveFactor = 0; | |
| } | |
| } | |
| // Cannot represent the loads as consecutive vectorizable nodes - | |
| // just exit. | |
| unsigned ConsecutiveNodesSize = 0; | |
| if (!LoadEntriesToVectorize.empty() && InterleaveFactor == 0 && | |
| any_of(zip(LoadEntriesToVectorize, LoadSetsToVectorize), | |
| [&, Slice = Slice](const auto &P) { | |
| const auto *It = find_if(Slice, [&](Value *V) { | |
| return std::get<1>(P).contains(V); | |
| }); | |
| if (It == Slice.end()) | |
| return false; | |
| const TreeEntry &TE = | |
| *VectorizableTree[std::get<0>(P)]; | |
| ArrayRef<Value *> VL = TE.Scalars; | |
| OrdersType Order; | |
| SmallVector<Value *> PointerOps; | |
| StridedPtrInfo SPtrInfo; | |
| LoadsState State = canVectorizeLoads( | |
| VL, VL.front(), Order, PointerOps, SPtrInfo); | |
| if (State == LoadsState::ScatterVectorize || | |
| State == LoadsState::CompressVectorize || | |
| State == LoadsState::BlendedLoadVectorize) | |
| return false; | |
| ConsecutiveNodesSize += VL.size(); | |
| size_t Start = std::distance(Slice.begin(), It); | |
| size_t Sz = Slice.size() - Start; | |
| return Sz < VL.size() || | |
| Slice.slice(Start, VL.size()) != VL; | |
| })) | |
| continue; | |
| // Try to build long masked gather loads. | |
| UserMaxVF = bit_ceil(UserMaxVF); | |
| if (InterleaveFactor == 0 && | |
| any_of(seq<unsigned>(Slice.size() / UserMaxVF), | |
| [&, Slice = Slice](unsigned Idx) { | |
| OrdersType Order; | |
| SmallVector<Value *> PointerOps; | |
| StridedPtrInfo SPtrInfo; | |
| return canVectorizeLoads( | |
| Slice.slice(Idx * UserMaxVF, UserMaxVF), | |
| Slice[Idx * UserMaxVF], Order, PointerOps, | |
| SPtrInfo) == LoadsState::ScatterVectorize; | |
| })) | |
| UserMaxVF = MaxVF; | |
| if (Slice.size() != ConsecutiveNodesSize) | |
| MaxVF = std::min<unsigned>(MaxVF, UserMaxVF); | |
| } | |
| for (unsigned VF = MaxVF; VF >= 2; VF /= 2) { | |
| bool IsVectorized = true; | |
| for (unsigned I = 0, E = Slice.size(); I < E; I += VF) { | |
| ArrayRef<Value *> SubSlice = | |
| Slice.slice(I, std::min(VF, E - I)); | |
| if (isVectorized(SubSlice.front())) | |
| continue; | |
| // Check if the subslice is to be-vectorized entry, which is not | |
| // equal to entry. | |
| if (any_of(zip(LoadEntriesToVectorize, LoadSetsToVectorize), | |
| [&](const auto &P) { | |
| return !SubSlice.equals( | |
| VectorizableTree[std::get<0>(P)] | |
| ->Scalars) && | |
| set_is_subset(SubSlice, std::get<1>(P)); | |
| })) | |
| continue; | |
| unsigned Sz = VectorizableTree.size(); | |
| // A chunk smaller than InterleaveFactor cannot form an | |
| // interleave group; keep it non-interleaved instead. | |
| buildTreeRec( | |
| SubSlice, 0, EdgeInfo(), | |
| SubSlice.size() >= InterleaveFactor ? InterleaveFactor : 0); | |
| if (Sz == VectorizableTree.size()) { | |
| IsVectorized = false; | |
| // Try non-interleaved vectorization with smaller vector | |
| // factor. | |
| if (InterleaveFactor > 0) { | |
| VF = 2 * (MaxVF / InterleaveFactor); | |
| InterleaveFactor = 0; | |
| } | |
| continue; | |
| } | |
| } | |
| if (IsVectorized) | |
| break; | |
| } | |
| } | |
| NonVectorized.append(SortedNonVectorized); | |
| } | |
| return NonVectorized; | |
| }; | |
| for (const auto &GLs : GatheredLoads) { | |
| const auto &Ref = GLs.second; | |
| SmallVector<LoadInst *> NonVectorized = ProcessGatheredLoads(Ref); | |
| if (!Ref.empty() && !NonVectorized.empty() && | |
| accumulate( | |
| Ref, 0u, | |
| [](unsigned S, ArrayRef<std::pair<LoadInst *, int64_t>> LoadsDists) | |
| -> unsigned { return S + LoadsDists.size(); }) != | |
| NonVectorized.size() && | |
| IsMaskedGatherSupported(NonVectorized)) { | |
| SmallVector<SmallVector<std::pair<LoadInst *, int64_t>>> | |
| FinalGatheredLoads; | |
| for (LoadInst *LI : NonVectorized) { | |
| // Reinsert non-vectorized loads to other list of loads with the same | |
| // base pointers. | |
| gatherPossiblyVectorizableLoads(*this, LI, *DL, *SE, *TTI, | |
| FinalGatheredLoads, | |
| /*AddNew=*/false); | |
| } | |
| // Final attempt to vectorize non-vectorized loads. | |
| (void)ProcessGatheredLoads(FinalGatheredLoads, /*Final=*/true); | |
| } | |
| } | |
| // Try to vectorize postponed load entries, previously marked as gathered. | |
| for (unsigned Idx : LoadEntriesToVectorize) { | |
| const TreeEntry &E = *VectorizableTree[Idx]; | |
| SmallVector<Value *> GatheredScalars(E.Scalars.begin(), E.Scalars.end()); | |
| // Avoid reordering, if possible. | |
| if (!E.ReorderIndices.empty()) { | |
| // Build a mask out of the reorder indices and reorder scalars per this | |
| // mask. | |
| SmallVector<int> ReorderMask; | |
| inversePermutation(E.ReorderIndices, ReorderMask); | |
| reorderScalars(GatheredScalars, ReorderMask); | |
| } | |
| buildTreeRec(GatheredScalars, 0, EdgeInfo()); | |
| } | |
| // If no new entries created, consider it as no gathered loads entries must be | |
| // handled. | |
| if (static_cast<unsigned>(*GatheredLoadsEntriesFirst) == | |
| VectorizableTree.size()) | |
| GatheredLoadsEntriesFirst.reset(); | |
| } | |
| /// Generates key/subkey pair for the given value to provide effective sorting | |
| /// of the values and better detection of the vectorizable values sequences. The | |
| /// keys/subkeys can be used for better sorting of the values themselves (keys) | |
| /// and in values subgroups (subkeys). | |
| static std::pair<size_t, size_t> generateKeySubkey( | |
| Value *V, const TargetLibraryInfo *TLI, | |
| function_ref<hash_code(size_t, LoadInst *)> LoadsSubkeyGenerator, | |
| bool AllowAlternate) { | |
| hash_code Key = hash_value(V->getValueID() + 2); | |
| hash_code SubKey = hash_value(0); | |
| // Sort the loads by the distance between the pointers. | |
| if (auto *LI = dyn_cast<LoadInst>(V)) { | |
| Key = hash_combine(LI->getType(), hash_value(Instruction::Load), Key); | |
| if (LI->isSimple()) | |
| SubKey = hash_value(LoadsSubkeyGenerator(Key, LI)); | |
| else | |
| Key = SubKey = hash_value(LI); | |
| } else if (isVectorLikeInstWithConstOps(V)) { | |
| // Sort extracts by the vector operands. | |
| if (isa<ExtractElementInst, UndefValue>(V)) | |
| Key = hash_value(Value::UndefValueVal + 1); | |
| if (auto *EI = dyn_cast<ExtractElementInst>(V)) { | |
| if (!isUndefVector(EI->getVectorOperand()).all() && | |
| !isa<UndefValue>(EI->getIndexOperand())) | |
| SubKey = hash_value(EI->getVectorOperand()); | |
| } | |
| } else if (auto *I = dyn_cast<Instruction>(V)) { | |
| // Sort other instructions just by the opcodes except for CMPInst. | |
| // For CMP also sort by the predicate kind. | |
| if ((isa<BinaryOperator, CastInst>(I)) && | |
| isValidForAlternation(I->getOpcode())) { | |
| if (AllowAlternate) | |
| Key = hash_value(isa<BinaryOperator>(I) ? 1 : 0); | |
| else | |
| Key = hash_combine(hash_value(I->getOpcode()), Key); | |
| SubKey = hash_combine( | |
| hash_value(I->getOpcode()), hash_value(I->getType()), | |
| hash_value(isa<BinaryOperator>(I) | |
| ? I->getType() | |
| : cast<CastInst>(I)->getOperand(0)->getType())); | |
| // For casts, look through the only operand to improve compile time. | |
| if (isa<CastInst>(I)) { | |
| std::pair<size_t, size_t> OpVals = | |
| generateKeySubkey(I->getOperand(0), TLI, LoadsSubkeyGenerator, | |
| /*AllowAlternate=*/true); | |
| Key = hash_combine(OpVals.first, Key); | |
| SubKey = hash_combine(OpVals.first, SubKey); | |
| } | |
| } else if (auto *CI = dyn_cast<CmpInst>(I)) { | |
| CmpInst::Predicate Pred = CI->getPredicate(); | |
| if (CI->isCommutative()) | |
| Pred = std::min(Pred, CmpInst::getInversePredicate(Pred)); | |
| CmpInst::Predicate SwapPred = CmpInst::getSwappedPredicate(Pred); | |
| SubKey = hash_combine(hash_value(I->getOpcode()), hash_value(Pred), | |
| hash_value(SwapPred), | |
| hash_value(CI->getOperand(0)->getType())); | |
| } else if (auto *Call = dyn_cast<CallInst>(I)) { | |
| Intrinsic::ID ID = getVectorIntrinsicIDForCall(Call, TLI); | |
| if (isTriviallyVectorizable(ID)) { | |
| if (ID == Intrinsic::fmuladd) | |
| ID = Intrinsic::fma; | |
| SubKey = hash_combine(hash_value(I->getOpcode()), hash_value(ID), | |
| hash_value(I->getType())); | |
| } else if (!VFDatabase(*Call).getMappings(*Call).empty()) { | |
| SubKey = hash_combine(hash_value(I->getOpcode()), | |
| hash_value(Call->getCalledFunction()), | |
| hash_value(I->getType())); | |
| } else { | |
| Key = hash_combine(hash_value(Call), Key); | |
| SubKey = hash_combine(hash_value(I->getOpcode()), hash_value(Call)); | |
| } | |
| for (const CallBase::BundleOpInfo &Op : Call->bundle_op_infos()) | |
| SubKey = hash_combine(hash_value(Op.Begin), hash_value(Op.End), | |
| hash_value(Op.Tag), SubKey); | |
| } else if (auto *Gep = dyn_cast<GetElementPtrInst>(I)) { | |
| if (Gep->getNumOperands() == 2 && isa<ConstantInt>(Gep->getOperand(1))) | |
| SubKey = hash_value(Gep->getPointerOperand()); | |
| else | |
| SubKey = hash_value(Gep); | |
| } else if (BinaryOperator::isIntDivRem(I->getOpcode()) && | |
| !isa<ConstantInt>(I->getOperand(1))) { | |
| // Do not try to vectorize instructions with potentially high cost. | |
| SubKey = hash_value(I); | |
| } else { | |
| SubKey = | |
| hash_combine(hash_value(I->getOpcode()), hash_value(I->getType())); | |
| } | |
| Key = hash_combine(hash_value(I->getParent()->getNumber()), Key); | |
| } | |
| return std::make_pair(Key, SubKey); | |
| } | |
| /// Checks if the specified instruction \p I is an main operation for the given | |
| /// \p MainOp and \p AltOp instructions. | |
| static bool isMainInstruction(Instruction *I, Instruction *MainOp, | |
| Instruction *AltOp, const TargetLibraryInfo &TLI); | |
| /// Builds the arguments types vector for the given call instruction with the | |
| /// given \p ID for the specified vector factor. | |
| static SmallVector<Type *> | |
| buildIntrinsicArgTypes(const CallInst *CI, const Intrinsic::ID ID, | |
| const unsigned VF, unsigned MinBW, | |
| const TargetTransformInfo *TTI) { | |
| SmallVector<Type *> ArgTys; | |
| for (auto [Idx, Arg] : enumerate(CI->args())) { | |
| if (ID != Intrinsic::not_intrinsic) { | |
| if (isVectorIntrinsicWithScalarOpAtArg(ID, Idx, TTI)) { | |
| ArgTys.push_back(Arg->getType()); | |
| continue; | |
| } | |
| if (MinBW > 0) { | |
| ArgTys.push_back( | |
| getWidenedType(IntegerType::get(CI->getContext(), MinBW), VF)); | |
| continue; | |
| } | |
| } | |
| ArgTys.push_back(getWidenedType(Arg->getType(), VF)); | |
| } | |
| return ArgTys; | |
| } | |
| /// Calculates the costs of vectorized intrinsic (if possible) and vectorized | |
| /// function (if possible) calls. Returns invalid cost for the corresponding | |
| /// calls, if they cannot be vectorized/will be scalarized. | |
| static std::pair<InstructionCost, InstructionCost> | |
| getVectorCallCosts(CallInst *CI, Type *VecTy, const TargetTransformInfo *TTI, | |
| const TargetLibraryInfo *TLI, ArrayRef<Type *> ArgTys, | |
| const TTI::TargetCostKind CostKind) { | |
| auto Shape = VFShape::get(CI->getFunctionType(), | |
| ElementCount::getFixed(getNumElements(VecTy)), | |
| false /*HasGlobalPred*/); | |
| Function *VecFunc = VFDatabase(*CI).getVectorizedFunction(Shape); | |
| auto LibCost = InstructionCost::getInvalid(); | |
| if (!CI->isNoBuiltin() && VecFunc) { | |
| // Calculate the cost of the vector library call. | |
| // If the corresponding vector call is cheaper, return its cost. | |
| LibCost = TTI->getCallInstrCost(nullptr, VecTy, ArgTys, CostKind); | |
| } | |
| Intrinsic::ID ID = getVectorIntrinsicIDForCall(CI, TLI); | |
| // Calculate the cost of the vector intrinsic call. | |
| FastMathFlags FMF; | |
| if (auto *FPCI = dyn_cast<FPMathOperator>(CI)) | |
| FMF = FPCI->getFastMathFlags(); | |
| const InstructionCost ScalarLimit = 10000; | |
| IntrinsicCostAttributes CostAttrs(ID, VecTy, ArgTys, FMF, | |
| dyn_cast<IntrinsicInst>(CI), | |
| LibCost.isValid() ? LibCost : ScalarLimit); | |
| auto IntrinsicCost = TTI->getIntrinsicInstrCost(CostAttrs, CostKind); | |
| if (LibCost.isValid()) { | |
| if (IntrinsicCost > LibCost) | |
| IntrinsicCost = InstructionCost::getInvalid(); | |
| } else if (IntrinsicCost > ScalarLimit) { | |
| // A type-based query always scalarizes struct-returning intrinsics (e.g. | |
| // llvm.sincos), which do not have a VFDatabase name mapping. Retry with an | |
| // argument-aware query (as the loop vectorizer does) so such lowerings are | |
| // taken into account. | |
| SmallVector<const Value *> Args(CI->args()); | |
| IntrinsicCostAttributes ArgAwareAttrs( | |
| ID, VecTy, Args, ArgTys, FMF, dyn_cast<IntrinsicInst>(CI), ScalarLimit); | |
| IntrinsicCost = TTI->getIntrinsicInstrCost(ArgAwareAttrs, CostKind); | |
| if (IntrinsicCost > ScalarLimit) | |
| IntrinsicCost = InstructionCost::getInvalid(); | |
| } | |
| return {IntrinsicCost, LibCost}; | |
| } | |
| /// \returns the reciprocal-throughput cost of \p I widened to \p VF lanes (an | |
| /// arithmetic op or a vectorizable call). | |
| static InstructionCost getVectorOpCost(Instruction *I, unsigned VF, | |
| const TargetTransformInfo &TTI, | |
| const TargetLibraryInfo &TLI, | |
| const TTI::TargetCostKind CostKind) { | |
| assert((isa<BinaryOperator, CallInst>(I)) && | |
| "getVectorOpCost expects an arithmetic op or a vectorizable call."); | |
| Type *VecTy = getWidenedType(I->getType(), VF); | |
| if (auto *CI = dyn_cast<CallInst>(I)) { | |
| Intrinsic::ID ID = getVectorIntrinsicIDForCall(CI, &TLI); | |
| SmallVector<Type *> ArgTys = buildIntrinsicArgTypes(CI, ID, VF, 0, &TTI); | |
| auto [IntrCost, LibCost] = | |
| getVectorCallCosts(CI, VecTy, &TTI, &TLI, ArgTys, CostKind); | |
| return std::min(IntrCost, LibCost); | |
| } | |
| SmallVector<const Value *, 2> Args{I->getOperand(0), I->getOperand(1)}; | |
| return TTI.getArithmeticInstrCost( | |
| I->getOpcode(), VecTy, CostKind, TTI::getOperandInfo(I->getOperand(0)), | |
| TTI::getOperandInfo(I->getOperand(1)), Args, I); | |
| } | |
| /// Packs a type's kind and scalar width into one key, so an opcode/intrinsic | |
| /// id reused across scalar widths (e.g. fdiv float vs. fdiv double) is cached | |
| /// per width instead of sharing one verdict. | |
| static unsigned getPoorThroughputTypeKey(Type *Ty) { | |
| return (static_cast<unsigned>(Ty->getTypeID()) << 16) | | |
| Ty->getScalarSizeInBits(); | |
| } | |
| namespace { | |
| /// Memoizes opcode / intrinsic / callee kinds already ruled out as seeds. | |
| struct PoorThroughputOpCache { | |
| SmallDenseSet<std::pair<unsigned, unsigned>> CheapOpcodes; | |
| SmallDenseSet<std::pair<Intrinsic::ID, unsigned>> CheapIntrinsics; | |
| // Keyed on the callee alone: a Function's signature is fixed, so its type | |
| // never varies between calls. | |
| SmallDenseSet<const Function *> CheapCallees; | |
| }; | |
| /// Groups poor-throughput seeds for tryToVectorizeSequence. | |
| struct SeedGroupKey { | |
| unsigned Opcode = 0; | |
| Intrinsic::ID IntrID = Intrinsic::not_intrinsic; | |
| StringRef CalleeName; | |
| bool less(const SeedGroupKey &O) const { | |
| if (Opcode != O.Opcode) | |
| return Opcode < O.Opcode; | |
| if (IntrID != O.IntrID) | |
| return IntrID < O.IntrID; | |
| return CalleeName < O.CalleeName; | |
| } | |
| }; | |
| static SeedGroupKey getSeedGroupKey(const Instruction *I, | |
| const TargetLibraryInfo &TLI) { | |
| SeedGroupKey K; | |
| K.Opcode = I->getOpcode(); | |
| if (auto *CI = dyn_cast<CallInst>(I)) { | |
| K.IntrID = getVectorIntrinsicIDForCall(CI, &TLI); | |
| if (const Function *F = CI->getCalledFunction()) | |
| K.CalleeName = F->getName(); | |
| } | |
| return K; | |
| } | |
| } // namespace | |
| /// Returns true if \p I is an expensive scalar op whose vector form is cheaper | |
| /// per lane (e.g. fdiv, frem, fsqrt). If \p MaxVecRegSize is non-zero, the | |
| /// widest vector fitting into it is checked too. | |
| static bool isPoorThroughputOp(const VectorizeOptions &Opts, Instruction *I, | |
| const TargetTransformInfo &TTI, | |
| const TargetLibraryInfo &TLI, | |
| PoorThroughputOpCache &Cache, | |
| const TTI::TargetCostKind CostKind, | |
| unsigned MaxVecRegSize = 0) { | |
| if (!isa<BinaryOperator, CallInst>(I)) | |
| return false; | |
| Type *Ty = I->getType(); | |
| if ((Ty->isVectorTy() && !Opts.slp_revec) || Ty->isAggregateType() || | |
| !isValidElementType(Ty, Opts.slp_revec)) | |
| return false; | |
| auto Analyze = [&]() { | |
| InstructionCost ScalarCost = TTI.getInstructionCost(I, CostKind); | |
| if (ScalarCost < TTI::TCC_Expensive) | |
| return false; | |
| constexpr unsigned MinVF = 2; | |
| if (getVectorOpCost(I, MinVF, TTI, TLI, CostKind) < ScalarCost * MinVF) | |
| return true; | |
| // Some ops (e.g. div by constant) scalarize at VF=2 but have a cheap | |
| // vector form at the full register width. | |
| unsigned TyBits = Ty->getPrimitiveSizeInBits().getFixedValue(); | |
| unsigned MaxVF = TyBits ? llvm::bit_floor(MaxVecRegSize / TyBits) : 0; | |
| return MaxVF > MinVF && | |
| getVectorOpCost(I, MaxVF, TTI, TLI, CostKind) < ScalarCost * MaxVF; | |
| }; | |
| auto CheckCached = [&](bool IsCheap, llvm::function_ref<void()> MarkCheap) { | |
| if (IsCheap) | |
| return false; | |
| if (Analyze()) | |
| return true; | |
| MarkCheap(); | |
| return false; | |
| }; | |
| if (auto *CI = dyn_cast<CallInst>(I)) { | |
| if (any_of(CI->args(), [&](const Value *Arg) { | |
| return !isValidElementType(Arg->getType(), Opts.slp_revec); | |
| })) | |
| return false; | |
| // A call, used only as a select operand, may get the values rejected by the | |
| // select condition, which the vector library call handles on its slow path. | |
| if (!CI->use_empty() && !CI->hasNUsesOrMore(UsesLimit) && | |
| all_of(CI->users(), [&](User *U) { | |
| return match( | |
| U, m_Select(m_Unless(m_Specific(CI)), m_Value(), m_Value())); | |
| })) | |
| return false; | |
| if (Intrinsic::ID ID = getVectorIntrinsicIDForCall(CI, &TLI)) { | |
| auto Key = std::make_pair(ID, getPoorThroughputTypeKey(Ty)); | |
| return CheckCached(Cache.CheapIntrinsics.contains(Key), | |
| [&] { Cache.CheapIntrinsics.insert(Key); }); | |
| } | |
| // A call without the vector variants is never vectorized. | |
| if (VFDatabase::getMappings(*CI).empty()) | |
| return false; | |
| const Function *Callee = CI->getCalledFunction(); | |
| if (!Callee) | |
| return Analyze(); | |
| return CheckCached(Cache.CheapCallees.contains(Callee), | |
| [&] { Cache.CheapCallees.insert(Callee); }); | |
| } | |
| auto Key = std::make_pair(I->getOpcode(), getPoorThroughputTypeKey(Ty)); | |
| return CheckCached(Cache.CheapOpcodes.contains(Key), | |
| [&] { Cache.CheapOpcodes.insert(Key); }); | |
| } | |
| /// Get the loop nest for the given loop. | |
| ArrayRef<const Loop *> BoUpSLP::getLoopNest(const Loop *L) { | |
| assert(L && "Expected valid loop"); | |
| if (Opts.slp_cost_loop_trip_count == 0) | |
| return {}; | |
| SmallVector<const Loop *> &Res = | |
| LoopToLoopNest.try_emplace(L).first->getSecond(); | |
| if (!Res.empty()) | |
| return Res; | |
| SmallVector<const Loop *> LoopNest; | |
| while (L) { | |
| LoopNest.push_back(L); | |
| L = L->getParentLoop(); | |
| } | |
| Res.assign(LoopNest.rbegin(), LoopNest.rend()); | |
| return Res; | |
| } | |
| /// Detects an extractvalue bundle that can be widened by vectorizing the | |
| /// underlying struct-returning calls. | |
| /// | |
| /// \p VL is a bundle whose state \p S is Instruction::ExtractValue. The | |
| /// bundle is acceptable for widening into one struct-of-vectors call only | |
| /// when: | |
| /// - every element of \p VL is an ExtractValueInst, | |
| /// - every ExtractValueInst extracts the same struct field (its | |
| /// getIndices() matches the main op's indices), | |
| /// - the aggregate operands form a uniform set of CallInsts (per | |
| /// getSameOpcode) that is not an alt-shuffle and whose return type is | |
| /// a literal struct, and | |
| /// - every user of every such call is itself an ExtractValueInst, so the | |
| /// external-use extraction code can rebuild scalars via extractvalue + | |
| /// extractelement without needing an insertvalue chain. | |
| /// | |
| /// On success returns true and fills \p Indices with the common field | |
| /// index path and \p Calls with the per-lane aggregate calls (in VL order), | |
| /// for the caller to feed as the operand of the new tree entry. Otherwise | |
| /// returns false and leaves the output parameters untouched. | |
| static bool checkEVsForVecCalls(ArrayRef<Value *> VL, | |
| const InstructionsState &S, | |
| const TargetLibraryInfo &TLI, | |
| SmallVectorImpl<unsigned> &Indices, | |
| SmallVectorImpl<Value *> &Calls) { | |
| assert(S && S.getOpcode() == Instruction::ExtractValue && | |
| "Expected extractvalue instruction state."); | |
| if (!all_of(VL, IsaPred<ExtractValueInst>)) | |
| return false; | |
| auto *VL0 = cast<ExtractValueInst>(S.getMainOp()); | |
| ArrayRef<unsigned> VL0Indices = VL0->getIndices(); | |
| SmallVector<Value *> Aggregates; | |
| for (Value *V : VL) { | |
| if (V == VL0) { | |
| Aggregates.push_back(VL0->getAggregateOperand()); | |
| continue; | |
| } | |
| auto *IV = cast<ExtractValueInst>(V); | |
| if (IV->getIndices() != VL0Indices || | |
| isa<ScalableVectorType>(IV->getType())) | |
| return false; | |
| Value *Agg = IV->getAggregateOperand(); | |
| Aggregates.push_back(Agg); | |
| } | |
| const InstructionsState AggState = getSameOpcode(Aggregates, TLI); | |
| if (AggState && AggState.getOpcode() == Instruction::Call && | |
| !AggState.isAltShuffle() && | |
| isa<StructType>(AggState.getMainOp()->getType()) && | |
| allSameBlock(Aggregates)) { | |
| // The struct-returning call may have non-bundle users too. The external | |
| // extraction code rebuilds scalars by extractvalue + extractelement, | |
| // which only works when every user of the call is an ExtractValueInst. | |
| // Bail out if any aggregate has a different kind of user. | |
| for (Value *Agg : Aggregates) { | |
| if (!all_of(Agg->users(), IsaPred<ExtractValueInst>)) | |
| return false; | |
| } | |
| Indices.assign(VL0Indices.begin(), VL0Indices.end()); | |
| Calls.swap(Aggregates); | |
| return true; | |
| } | |
| return false; | |
| } | |
| static bool allStructUsersAreExtractValueInsts(ArrayRef<Value *> VL) { | |
| return all_of(VL, [](Value *V) { | |
| return !isa<StructType>(V->getType()) || | |
| all_of(V->users(), IsaPred<ExtractValueInst>); | |
| }); | |
| } | |
| BoUpSLP::TreeEntry::EntryState BoUpSLP::getScalarsVectorizationState( | |
| const InstructionsState &S, ArrayRef<Value *> VL, | |
| bool IsScatterVectorizeUserTE, OrdersType &CurrentOrder, | |
| SmallVectorImpl<Value *> &PointerOps, StridedPtrInfo &SPtrInfo, | |
| SmallVectorImpl<int> &ReuseShuffleIndices) { | |
| assert(S.getMainOp() && | |
| "Expected instructions with same/alternate opcodes only."); | |
| unsigned ShuffleOrOp = | |
| S.isAltShuffle() ? (unsigned)Instruction::ShuffleVector : S.getOpcode(); | |
| Instruction *VL0 = S.getMainOp(); | |
| switch (ShuffleOrOp) { | |
| case Instruction::PHI: { | |
| // Too many operands - gather, most probably won't be vectorized. | |
| if (VL0->getNumOperands() > MaxPHINumOperands) | |
| return TreeEntry::NeedToGather; | |
| // Check for terminator values (e.g. invoke). | |
| for (PHINode *PHI : make_isa_range<PHINode>(VL)) { | |
| for (Instruction *Term : | |
| make_isa_range<Instruction>(PHI->incoming_values())) { | |
| if (Term->isTerminator()) { | |
| LLVM_DEBUG(dbgs() | |
| << "SLP: Need to swizzle PHINodes (terminator use).\n"); | |
| return TreeEntry::NeedToGather; | |
| } | |
| } | |
| } | |
| return TreeEntry::Vectorize; | |
| } | |
| case Instruction::ExtractElement: | |
| if (any_of(VL, [&](Value *V) { | |
| if (S.isCopyableElement(V) || isa<PoisonValue>(V)) | |
| return false; | |
| auto *EI = dyn_cast<ExtractElementInst>(V); | |
| if (!EI) | |
| return true; | |
| Value *Op = EI->getOperand(0); | |
| if (isVectorized(Op)) | |
| return true; | |
| auto *OpI = dyn_cast<Instruction>(Op); | |
| return OpI && OpI->isTerminator(); | |
| })) | |
| return TreeEntry::NeedToGather; | |
| [[fallthrough]]; | |
| case Instruction::ExtractValue: { | |
| bool Reuse = canReuseExtract(VL, CurrentOrder); | |
| // Copyable lanes are inserted into the reused source vector at their own | |
| // index, which is correct only for the identity extract order (empty | |
| // CurrentOrder) and without a reuse shuffle; other cases fall back to | |
| // gather. | |
| if (S.areInstructionsWithCopyableElements() && | |
| (!Reuse || !ReuseShuffleIndices.empty())) | |
| return TreeEntry::NeedToGather; | |
| if (Reuse || !CurrentOrder.empty()) | |
| return TreeEntry::Vectorize; | |
| SmallVector<unsigned> Indices; | |
| SmallVector<Value *> Calls; | |
| if (ShuffleOrOp == Instruction::ExtractValue && | |
| checkEVsForVecCalls(VL, S, *TLI, Indices, Calls)) | |
| return TreeEntry::Vectorize; | |
| LLVM_DEBUG(dbgs() << "SLP: Gather extract sequence.\n"); | |
| return TreeEntry::NeedToGather; | |
| } | |
| case Instruction::InsertValue: | |
| // Handle only simple insertvalue building a homogeneous aggregate from | |
| // scalars: exactly one index and an inserted value whose type is a valid | |
| // vector element type. Vectors and aggregates (structs/arrays) are | |
| // rejected, since the inserted operand type is later widened into the | |
| // result FixedVectorType. | |
| // TODO: Support more complex insertvalues. | |
| if (any_of(make_isa_range<InsertValueInst>(VL), | |
| [&](InsertValueInst *IV) { | |
| return IV->getNumIndices() != 1 || | |
| !::isValidElementType(IV->getOperand(1)->getType(), | |
| Opts.slp_revec) || | |
| IV->getOperand(1)->getType()->isVectorTy(); | |
| }) || | |
| none_of(make_isa_range<InsertValueInst>(VL), [](InsertValueInst *IV) { | |
| return isa<UndefValue>(IV->getAggregateOperand()); | |
| })) | |
| return TreeEntry::NeedToGather; | |
| [[fallthrough]]; | |
| case Instruction::InsertElement: { | |
| // Check that we have a buildvector and not a shuffle of 2 or more | |
| // different vectors. | |
| ValueSet SourceVectors; | |
| for (Value *V : VL) { | |
| if (isa<PoisonValue>(V)) { | |
| LLVM_DEBUG( | |
| dbgs() | |
| << "SLP: Gather of insertelement/insertvalue/poison vector.\n"); | |
| return TreeEntry::NeedToGather; | |
| } | |
| SourceVectors.insert(cast<Instruction>(V)->getOperand(0)); | |
| assert(getElementIndex(V) != std::nullopt && | |
| "Non-constant or undef index?"); | |
| } | |
| if (count_if(VL, [&SourceVectors](Value *V) { | |
| return !SourceVectors.contains(V); | |
| }) >= 2) { | |
| // Found 2nd source vector - cancel. | |
| LLVM_DEBUG( | |
| dbgs() << "SLP: Gather of insertelement/insertvalue vectors with " | |
| "different source vectors.\n"); | |
| return TreeEntry::NeedToGather; | |
| } | |
| if (any_of(VL, [&SourceVectors](Value *V) { | |
| // The last InsertElement/InsertValue can have multiple uses. | |
| return SourceVectors.contains(V) && !V->hasOneUse(); | |
| })) { | |
| assert((Opts.slp_revec || ShuffleOrOp == Instruction::InsertValue) && | |
| "Only supported by REVEC or InsertValue."); | |
| LLVM_DEBUG( | |
| dbgs() << "SLP: Gather of insertelement/insertvalue vectors with " | |
| "multiple uses.\n"); | |
| return TreeEntry::NeedToGather; | |
| } | |
| return TreeEntry::Vectorize; | |
| } | |
| case Instruction::Load: { | |
| // Check that a vectorized load would load the same memory as a scalar | |
| // load. For example, we don't want to vectorize loads that are smaller | |
| // than 8-bit. Even though we have a packed struct {<i2, i2, i2, i2>} LLVM | |
| // treats loading/storing it as an i8 struct. If we vectorize loads/stores | |
| // from such a struct, we read/write packed bits disagreeing with the | |
| // unvectorized version. | |
| auto IsGatheredNode = [&]() { | |
| if (!GatheredLoadsEntriesFirst) | |
| return false; | |
| return all_of(VL, [&](Value *V) { | |
| if (isa<PoisonValue>(V)) | |
| return true; | |
| return any_of(getTreeEntries(V), [&](const TreeEntry *TE) { | |
| return TE->Idx >= *GatheredLoadsEntriesFirst; | |
| }); | |
| }); | |
| }; | |
| switch (canVectorizeLoads(VL, VL0, CurrentOrder, PointerOps, SPtrInfo)) { | |
| case LoadsState::Vectorize: | |
| return TreeEntry::Vectorize; | |
| case LoadsState::CompressVectorize: | |
| if (!IsGraphTransformMode && VectorizableTree.size() > 1) { | |
| // Delay slow vectorized nodes for better vectorization attempts. | |
| LoadEntriesToVectorize.insert(VectorizableTree.size()); | |
| return TreeEntry::NeedToGather; | |
| } | |
| return IsGatheredNode() ? TreeEntry::NeedToGather | |
| : TreeEntry::CompressVectorize; | |
| case LoadsState::ScatterVectorize: | |
| if (!IsGraphTransformMode && VectorizableTree.size() > 1) { | |
| // Delay slow vectorized nodes for better vectorization attempts. | |
| LoadEntriesToVectorize.insert(VectorizableTree.size()); | |
| return TreeEntry::NeedToGather; | |
| } | |
| return IsGatheredNode() ? TreeEntry::NeedToGather | |
| : TreeEntry::ScatterVectorize; | |
| case LoadsState::StridedVectorize: | |
| if (!IsGraphTransformMode && VectorizableTree.size() > 1) { | |
| // Delay slow vectorized nodes for better vectorization attempts. | |
| LoadEntriesToVectorize.insert(VectorizableTree.size()); | |
| return TreeEntry::NeedToGather; | |
| } | |
| return IsGatheredNode() ? TreeEntry::NeedToGather | |
| : TreeEntry::StridedVectorize; | |
| case LoadsState::BlendedLoadVectorize: | |
| if (!IsGraphTransformMode && VectorizableTree.size() > 1) { | |
| // Delay slow vectorized nodes for better vectorization attempts. | |
| LoadEntriesToVectorize.insert(VectorizableTree.size()); | |
| return TreeEntry::NeedToGather; | |
| } | |
| return IsGatheredNode() ? TreeEntry::NeedToGather | |
| : TreeEntry::BlendedLoadVectorize; | |
| case LoadsState::Gather: | |
| #ifndef NDEBUG | |
| Type *ScalarTy = VL0->getType(); | |
| if (DL->getTypeSizeInBits(ScalarTy) != | |
| DL->getTypeAllocSizeInBits(ScalarTy)) | |
| LLVM_DEBUG(dbgs() << "SLP: Gathering loads of non-packed type.\n"); | |
| else if (any_of(VL, [](Value *V) { | |
| auto *LI = dyn_cast<LoadInst>(V); | |
| return !LI || !LI->isSimple(); | |
| })) | |
| LLVM_DEBUG(dbgs() << "SLP: Gathering non-simple loads.\n"); | |
| else | |
| LLVM_DEBUG(dbgs() << "SLP: Gathering non-consecutive loads.\n"); | |
| #endif // NDEBUG | |
| registerNonVectorizableLoads(VL); | |
| return TreeEntry::NeedToGather; | |
| } | |
| llvm_unreachable("Unexpected state of loads"); | |
| } | |
| case Instruction::ZExt: | |
| case Instruction::SExt: | |
| case Instruction::FPToUI: | |
| case Instruction::FPToSI: | |
| case Instruction::FPExt: | |
| case Instruction::PtrToInt: | |
| case Instruction::PtrToAddr: | |
| case Instruction::IntToPtr: | |
| case Instruction::SIToFP: | |
| case Instruction::UIToFP: | |
| case Instruction::Trunc: | |
| case Instruction::FPTrunc: | |
| case Instruction::BitCast: { | |
| Type *SrcTy = VL0->getOperand(0)->getType(); | |
| for (Instruction *I : make_isa_range<Instruction>(VL)) { | |
| Type *Ty = I->getOperand(0)->getType(); | |
| if (Ty != SrcTy || !isValidElementType(Ty, Opts.slp_revec)) { | |
| LLVM_DEBUG( | |
| dbgs() << "SLP: Gathering casts with different src types.\n"); | |
| return TreeEntry::NeedToGather; | |
| } | |
| } | |
| return TreeEntry::Vectorize; | |
| } | |
| case Instruction::ICmp: | |
| case Instruction::FCmp: { | |
| // Check that all of the compares have the same predicate. | |
| CmpInst::Predicate P0 = cast<CmpInst>(VL0)->getPredicate(); | |
| CmpInst::Predicate SwapP0 = CmpInst::getSwappedPredicate(P0); | |
| Type *ComparedTy = VL0->getOperand(0)->getType(); | |
| for (CmpInst *Cmp : make_isa_range<CmpInst>(VL)) { | |
| if ((Cmp->getPredicate() != P0 && Cmp->getPredicate() != SwapP0 && | |
| !CmpSamePredicateHelper::canConvertTo(Cmp, P0)) || | |
| Cmp->getOperand(0)->getType() != ComparedTy) { | |
| LLVM_DEBUG(dbgs() << "SLP: Gathering cmp with different predicate.\n"); | |
| return TreeEntry::NeedToGather; | |
| } | |
| } | |
| return TreeEntry::Vectorize; | |
| } | |
| case Instruction::Select: | |
| if (Opts.slp_revec) { | |
| SmallPtrSet<Type *, 4> CondTypes; | |
| for (Value *V : VL) { | |
| Value *Cond; | |
| if (!match(V, m_Select(m_Value(Cond), m_Value(), m_Value())) && | |
| !match(V, m_ZExt(m_Value(Cond)))) | |
| continue; | |
| CondTypes.insert(Cond->getType()); | |
| } | |
| if (CondTypes.size() > 1) { | |
| LLVM_DEBUG( | |
| dbgs() | |
| << "SLP: Gathering select with different condition types.\n"); | |
| return TreeEntry::NeedToGather; | |
| } | |
| } | |
| [[fallthrough]]; | |
| case Instruction::FNeg: | |
| case Instruction::Add: | |
| case Instruction::FAdd: | |
| case Instruction::Sub: | |
| case Instruction::FSub: | |
| case Instruction::Mul: | |
| case Instruction::FMul: | |
| case Instruction::UDiv: | |
| case Instruction::SDiv: | |
| case Instruction::FDiv: | |
| case Instruction::URem: | |
| case Instruction::SRem: | |
| case Instruction::FRem: | |
| case Instruction::Shl: | |
| case Instruction::LShr: | |
| case Instruction::AShr: | |
| case Instruction::And: | |
| case Instruction::Or: | |
| case Instruction::Xor: | |
| case Instruction::Freeze: | |
| if (S.getMainOp()->getType()->isFloatingPointTy() && | |
| TTI->isFPVectorizationPotentiallyUnsafe() && | |
| any_of(make_isa_range<BinaryOperator>(VL), | |
| [](BinaryOperator *BO) { return !BO->isFast(); })) | |
| return TreeEntry::NeedToGather; | |
| return TreeEntry::Vectorize; | |
| case Instruction::GetElementPtr: { | |
| auto IsMatchingLane = [&S](Value *V) { return !S.isCopyableElement(V); }; | |
| // We don't combine GEPs with complicated (nested) indexing. | |
| for (GetElementPtrInst *GEP : make_filter_range( | |
| make_isa_range<GetElementPtrInst>(VL), IsMatchingLane)) { | |
| if (GEP->getNumOperands() != 2) { | |
| LLVM_DEBUG(dbgs() << "SLP: not-vectorizable GEP (nested indexes).\n"); | |
| return TreeEntry::NeedToGather; | |
| } | |
| } | |
| // We can't combine several GEPs into one vector if they operate on | |
| // different types. | |
| Type *Ty0 = cast<GEPOperator>(VL0)->getSourceElementType(); | |
| for (GEPOperator *GEP : | |
| make_filter_range(make_isa_range<GEPOperator>(VL), IsMatchingLane)) { | |
| Type *CurTy = GEP->getSourceElementType(); | |
| if (Ty0 != CurTy) { | |
| LLVM_DEBUG(dbgs() << "SLP: not-vectorizable GEP (different types).\n"); | |
| return TreeEntry::NeedToGather; | |
| } | |
| } | |
| // We don't combine GEPs with non-constant indexes. | |
| Type *Ty1 = VL0->getOperand(1)->getType(); | |
| for (GetElementPtrInst *GEP : make_filter_range( | |
| make_isa_range<GetElementPtrInst>(VL), IsMatchingLane)) { | |
| auto *Op = GEP->getOperand(1); | |
| if ((!IsScatterVectorizeUserTE && !isa<ConstantInt>(Op)) || | |
| (Op->getType() != Ty1 && | |
| Op->getType()->getScalarSizeInBits() > | |
| DL->getIndexSizeInBits( | |
| GEP->getType()->getPointerAddressSpace()))) { | |
| LLVM_DEBUG( | |
| dbgs() << "SLP: not-vectorizable GEP (non-constant indexes).\n"); | |
| return TreeEntry::NeedToGather; | |
| } | |
| } | |
| // The indices must be representable in a single type, the operands | |
| // builder casts the constants to it. | |
| if (!getCommonGEPIndexType( | |
| VL, VL0, | |
| [&S](Value *V) { | |
| return isa<GetElementPtrInst>(V) && !S.isCopyableElement(V); | |
| }, | |
| *DL)) { | |
| LLVM_DEBUG(dbgs() << "SLP: not-vectorizable GEP (mixed index types).\n"); | |
| return TreeEntry::NeedToGather; | |
| } | |
| return TreeEntry::Vectorize; | |
| } | |
| case Instruction::Store: { | |
| // Check if the stores are consecutive or if we need to swizzle them. | |
| llvm::Type *ScalarTy = cast<StoreInst>(VL0)->getValueOperand()->getType(); | |
| // Avoid types that are padded when being allocated as scalars, while | |
| // being packed together in a vector (such as i1). | |
| if (DL->getTypeSizeInBits(ScalarTy) != | |
| DL->getTypeAllocSizeInBits(ScalarTy)) { | |
| LLVM_DEBUG(dbgs() << "SLP: Gathering stores of non-packed type.\n"); | |
| return TreeEntry::NeedToGather; | |
| } | |
| // Make sure all stores in the bundle are simple - we can't vectorize | |
| // atomic or volatile stores. | |
| for (Value *V : VL) { | |
| auto *SI = cast<StoreInst>(V); | |
| if (!SI->isSimple()) { | |
| LLVM_DEBUG(dbgs() << "SLP: Gathering non-simple stores.\n"); | |
| return TreeEntry::NeedToGather; | |
| } | |
| PointerOps.push_back(SI->getPointerOperand()); | |
| } | |
| // Check the order of pointer operands. | |
| if (llvm::sortPtrAccesses(PointerOps, ScalarTy, *DL, *SE, CurrentOrder)) { | |
| Value *Ptr0; | |
| Value *PtrN; | |
| if (CurrentOrder.empty()) { | |
| Ptr0 = PointerOps.front(); | |
| PtrN = PointerOps.back(); | |
| } else { | |
| Ptr0 = PointerOps[CurrentOrder.front()]; | |
| PtrN = PointerOps[CurrentOrder.back()]; | |
| } | |
| Align CommonAlignment = computeCommonAlignment<StoreInst>(VL); | |
| std::optional<int64_t> Dist = | |
| getPointersDiff(ScalarTy, Ptr0, ScalarTy, PtrN, *DL, *SE); | |
| // Check that the sorted pointer operands are consecutive. | |
| if (static_cast<uint64_t>(*Dist) == VL.size() - 1) | |
| return TreeEntry::Vectorize; | |
| if (Opts.slp_enable_strided_stores && | |
| analyzeConstantStrideCandidate(PointerOps, ScalarTy, CommonAlignment, | |
| CurrentOrder, *Dist, Ptr0, SPtrInfo)) | |
| return TreeEntry::StridedVectorize; | |
| // If the stores are not consecutive but the target supports masked stores | |
| // for the widened type, lower them as a masked store. | |
| FixedVectorType *StoreVecTy = nullptr; | |
| if (Opts.slp_enable_masked_stores && | |
| isMaskedStoreCompress(VL, PointerOps, CurrentOrder, *TTI, *DL, *SE, | |
| CommonAlignment, ReuseShuffleIndices, | |
| StoreVecTy, Cache)) { | |
| SPtrInfo.Ty = StoreVecTy; | |
| return TreeEntry::ExpandVectorize; | |
| } | |
| } | |
| LLVM_DEBUG(dbgs() << "SLP: Non-consecutive store.\n"); | |
| return TreeEntry::NeedToGather; | |
| } | |
| case Instruction::Call: { | |
| if (S.getMainOp()->getType()->isFloatingPointTy() && | |
| TTI->isFPVectorizationPotentiallyUnsafe() && | |
| any_of(make_isa_range<FPMathOperator>(VL), | |
| [](FPMathOperator *FPOp) { return !FPOp->isFast(); })) | |
| return TreeEntry::NeedToGather; | |
| // Check if the calls are all to the same vectorizable intrinsic or | |
| // library function. | |
| CallInst *CI = cast<CallInst>(VL0); | |
| Intrinsic::ID ID = getVectorIntrinsicIDForCall(CI, TLI); | |
| VFShape Shape = VFShape::get( | |
| CI->getFunctionType(), | |
| ElementCount::getFixed(static_cast<unsigned int>(VL.size())), | |
| false /*HasGlobalPred*/); | |
| Function *VecFunc = VFDatabase(*CI).getVectorizedFunction(Shape); | |
| if (!VecFunc && !isTriviallyVectorizable(ID)) { | |
| LLVM_DEBUG(dbgs() << "SLP: Non-vectorizable call.\n"); | |
| return TreeEntry::NeedToGather; | |
| } | |
| if (isa<StructType>(CI->getType()) && | |
| !allStructUsersAreExtractValueInsts(VL)) { | |
| LLVM_DEBUG(dbgs() << "SLP: Struct-returning calls have non-extractvalue " | |
| "users.\n"); | |
| return TreeEntry::NeedToGather; | |
| } | |
| Function *F = CI->getCalledFunction(); | |
| unsigned NumArgs = CI->arg_size(); | |
| SmallVector<Value *, 4> ScalarArgs(NumArgs, nullptr); | |
| for (unsigned J = 0; J != NumArgs; ++J) | |
| if (isVectorIntrinsicWithScalarOpAtArg(ID, J, TTI)) | |
| ScalarArgs[J] = CI->getArgOperand(J); | |
| // Skip copyables (idempotent stand-ins) and poisons. | |
| for (Value *V : make_filter_range(VL, [&](Value *V) { | |
| return !isa<PoisonValue>(V) && !S.isCopyableElement(V); | |
| })) { | |
| CallInst *CI2 = dyn_cast<CallInst>(V); | |
| Intrinsic::ID ID2 = CI2 ? getVectorIntrinsicIDForCall(CI2, TLI) | |
| : Intrinsic::not_intrinsic; | |
| Intrinsic::ID Equivalent = isEquivalentIntrinsicID(ID, ID2); | |
| if (!CI2 || | |
| (CI2->getCalledFunction() != F && | |
| isEquivalentIntrinsicID(Equivalent, Intrinsic::fmuladd) == | |
| Intrinsic::not_intrinsic) || | |
| (ID != ID2 && Equivalent == Intrinsic::not_intrinsic) || | |
| (VecFunc && | |
| VecFunc != VFDatabase(*CI2).getVectorizedFunction(Shape)) || | |
| !CI->hasIdenticalOperandBundleSchema(*CI2)) { | |
| LLVM_DEBUG(dbgs() << "SLP: mismatched calls:" << *CI << "!=" << *V | |
| << "\n"); | |
| return TreeEntry::NeedToGather; | |
| } | |
| // Some intrinsics have scalar arguments and should be same in order for | |
| // them to be vectorized. | |
| for (unsigned J = 0; J != NumArgs; ++J) { | |
| if (isVectorIntrinsicWithScalarOpAtArg(ID, J, TTI)) { | |
| Value *A1J = CI2->getArgOperand(J); | |
| if (ScalarArgs[J] != A1J) { | |
| LLVM_DEBUG(dbgs() | |
| << "SLP: mismatched arguments in call:" << *CI | |
| << " argument " << ScalarArgs[J] << "!=" << A1J << "\n"); | |
| return TreeEntry::NeedToGather; | |
| } | |
| } | |
| } | |
| // Verify that the bundle operands are identical between the two calls. | |
| if (CI->hasOperandBundles() && | |
| !std::equal(CI->op_begin() + CI->getBundleOperandsStartIndex(), | |
| CI->op_begin() + CI->getBundleOperandsEndIndex(), | |
| CI2->op_begin() + CI2->getBundleOperandsStartIndex())) { | |
| LLVM_DEBUG(dbgs() << "SLP: mismatched bundle operands in calls:" << *CI | |
| << "!=" << *V << '\n'); | |
| return TreeEntry::NeedToGather; | |
| } | |
| } | |
| SmallVector<Type *> ArgTys = | |
| buildIntrinsicArgTypes(CI, ID, VL.size(), 0, TTI); | |
| auto *VecTy = getWidenedType(S.getMainOp()->getType(), VL.size()); | |
| auto VecCallCosts = | |
| getVectorCallCosts(CI, VecTy, TTI, TLI, ArgTys, CostKind); | |
| if (!VecCallCosts.first.isValid() && !VecCallCosts.second.isValid()) | |
| return TreeEntry::NeedToGather; | |
| return TreeEntry::Vectorize; | |
| } | |
| case Instruction::ShuffleVector: { | |
| if (!S.isAltShuffle()) { | |
| // REVEC can support non alternate shuffle. | |
| if (Opts.slp_revec && getShufflevectorNumGroups(VL)) | |
| return TreeEntry::Vectorize; | |
| // If this is not an alternate sequence of opcode like add-sub | |
| // then do not vectorize this instruction. | |
| LLVM_DEBUG(dbgs() << "SLP: ShuffleVector are not vectorized.\n"); | |
| return TreeEntry::NeedToGather; | |
| } | |
| return TreeEntry::Vectorize; | |
| } | |
| default: | |
| LLVM_DEBUG(dbgs() << "SLP: Gathering unknown instruction.\n"); | |
| return TreeEntry::NeedToGather; | |
| } | |
| } | |
| namespace { | |
| /// Allows to correctly handle operands of the phi nodes based on the \p Main | |
| /// PHINode order of incoming basic blocks/values. | |
| class PHIHandler { | |
| DominatorTree &DT; | |
| PHINode *Main = nullptr; | |
| SmallVector<Value *> Phis; | |
| SmallVector<SmallVector<Value *>> Operands; | |
| public: | |
| PHIHandler() = delete; | |
| PHIHandler(DominatorTree &DT, PHINode *Main, ArrayRef<Value *> Phis) | |
| : DT(DT), Main(Main), Phis(Phis), | |
| Operands(Main->getNumIncomingValues(), | |
| SmallVector<Value *>(Phis.size(), nullptr)) {} | |
| void buildOperands() { | |
| constexpr unsigned FastLimit = 4; | |
| if (Main->getNumIncomingValues() <= FastLimit) { | |
| for (unsigned I : seq<unsigned>(0, Main->getNumIncomingValues())) { | |
| BasicBlock *InBB = Main->getIncomingBlock(I); | |
| if (!DT.isReachableFromEntry(InBB)) { | |
| Operands[I].assign(Phis.size(), PoisonValue::get(Main->getType())); | |
| continue; | |
| } | |
| // Prepare the operand vector. | |
| for (auto [Idx, V] : enumerate(Phis)) { | |
| auto *P = dyn_cast<PHINode>(V); | |
| if (!P) { | |
| assert(isa<PoisonValue>(V) && | |
| "Expected isa instruction or poison value."); | |
| Operands[I][Idx] = V; | |
| continue; | |
| } | |
| if (P->getIncomingBlock(I) == InBB) | |
| Operands[I][Idx] = P->getIncomingValue(I); | |
| else | |
| Operands[I][Idx] = P->getIncomingValueForBlock(InBB); | |
| } | |
| } | |
| return; | |
| } | |
| SmallMapVector<BasicBlock *, SmallVector<unsigned>, 4> | |
| Blocks; | |
| for (unsigned I : seq<unsigned>(Main->getNumIncomingValues())) { | |
| BasicBlock *InBB = Main->getIncomingBlock(I); | |
| if (!DT.isReachableFromEntry(InBB)) { | |
| Operands[I].assign(Phis.size(), PoisonValue::get(Main->getType())); | |
| continue; | |
| } | |
| Blocks.try_emplace(InBB).first->second.push_back(I); | |
| } | |
| for (auto [Idx, V] : enumerate(Phis)) { | |
| if (isa<PoisonValue>(V)) { | |
| for (unsigned I : seq<unsigned>(Main->getNumIncomingValues())) | |
| Operands[I][Idx] = V; | |
| continue; | |
| } | |
| auto *P = cast<PHINode>(V); | |
| for (unsigned I : seq<unsigned>(P->getNumIncomingValues())) { | |
| BasicBlock *InBB = P->getIncomingBlock(I); | |
| if (InBB == Main->getIncomingBlock(I)) { | |
| if (isa_and_nonnull<PoisonValue>(Operands[I][Idx])) | |
| continue; | |
| Operands[I][Idx] = P->getIncomingValue(I); | |
| continue; | |
| } | |
| auto *It = Blocks.find(InBB); | |
| if (It == Blocks.end()) | |
| continue; | |
| Operands[It->second.front()][Idx] = P->getIncomingValue(I); | |
| } | |
| } | |
| for (const auto &P : Blocks) { | |
| ArrayRef<unsigned> IncomingValues = P.second; | |
| if (IncomingValues.size() <= 1) | |
| continue; | |
| unsigned BasicI = IncomingValues.consume_front(); | |
| for (unsigned I : IncomingValues) { | |
| assert(all_of(enumerate(Operands[I]), | |
| [&](const auto &Data) { | |
| return !Data.value() || | |
| Data.value() == Operands[BasicI][Data.index()]; | |
| }) && | |
| "Expected empty operands list."); | |
| Operands[I] = Operands[BasicI]; | |
| } | |
| } | |
| } | |
| ArrayRef<Value *> getOperands(unsigned I) const { return Operands[I]; } | |
| }; | |
| } // namespace | |
| /// Returns main/alternate instructions for the given \p VL. Unlike | |
| /// getSameOpcode supports non-compatible instructions for better SplitVectorize | |
| /// node support. | |
| /// \returns first main/alt instructions, if only poisons and instruction with | |
| /// only 2 opcodes exists. Returns pair of nullptr otherwise. | |
| static std::pair<Instruction *, Instruction *> | |
| getMainAltOpsNoStateVL(ArrayRef<Value *> VL) { | |
| Instruction *MainOp = nullptr; | |
| Instruction *AltOp = nullptr; | |
| for (Value *V : | |
| make_filter_range(VL, [](Value *V) { return !isa<PoisonValue>(V); })) { | |
| auto *I = dyn_cast<Instruction>(V); | |
| if (!I) | |
| return {}; | |
| if (!MainOp) { | |
| MainOp = I; | |
| continue; | |
| } | |
| if (MainOp->getOpcode() == I->getOpcode()) { | |
| if (I->getParent() != MainOp->getParent()) | |
| return {}; | |
| continue; | |
| } | |
| if (!AltOp) { | |
| AltOp = I; | |
| continue; | |
| } | |
| if (AltOp->getOpcode() == I->getOpcode()) { | |
| if (I->getParent() != AltOp->getParent()) | |
| return {}; | |
| continue; | |
| } | |
| return {}; | |
| } | |
| if (!AltOp) | |
| return {}; | |
| assert(MainOp && AltOp && MainOp->getOpcode() != AltOp->getOpcode() && | |
| "Expected different main and alt instructions."); | |
| return std::make_pair(MainOp, AltOp); | |
| } | |
| /// Checks that every instruction appears once in the list and if not, packs | |
| /// them, building \p ReuseShuffleIndices mask and mutating \p VL. The list of | |
| /// unique scalars is extended by poison values to the whole register size. | |
| /// | |
| /// \returns false if \p VL could not be uniquified, in which case \p VL is | |
| /// unchanged and \p ReuseShuffleIndices is empty. | |
| static bool tryToFindDuplicates(const VectorizeOptions &Opts, | |
| SmallVectorImpl<Value *> &VL, | |
| SmallVectorImpl<int> &ReuseShuffleIndices, | |
| const TargetTransformInfo &TTI, | |
| const TargetLibraryInfo &TLI, | |
| const InstructionsState &S, | |
| const BoUpSLP::EdgeInfo &UserTreeIdx, | |
| const BoUpSLP &R, bool BuildGatherOnly = true) { | |
| // TODO: Reordering of struct types is not supported. | |
| if (isa<StructType>(getValueType(VL.front(), Opts.slp_revec))) { | |
| LLVM_DEBUG(dbgs() << "SLP: struct type in bundle.\n"); | |
| ReuseShuffleIndices.clear(); | |
| return true; | |
| } | |
| // Check that every instruction appears once in this bundle. | |
| SmallVector<Value *> UniqueValues; | |
| SmallDenseMap<Value *, unsigned, 16> UniquePositions(VL.size()); | |
| for (Value *V : VL) { | |
| if (isConstant(V)) { | |
| // Constants are always considered distinct, even if the same constant | |
| // appears multiple times in VL. | |
| ReuseShuffleIndices.emplace_back( | |
| isa<PoisonValue>(V) ? PoisonMaskElem : UniqueValues.size()); | |
| UniqueValues.emplace_back(V); | |
| continue; | |
| } | |
| auto Res = UniquePositions.try_emplace(V, UniqueValues.size()); | |
| ReuseShuffleIndices.emplace_back(Res.first->second); | |
| if (Res.second) | |
| UniqueValues.emplace_back(V); | |
| } | |
| // Check if we need to schedule the scalars. If no, can keep original scalars | |
| // and avoid extra shuffles. | |
| bool RequireScheduling = S && S.getOpcode() != Instruction::PHI && | |
| !isVectorLikeInstWithConstOps(S.getMainOp()) && | |
| (S.areInstructionsWithCopyableElements() || | |
| !doesNotNeedToSchedule(UniqueValues)); | |
| // Compute this flag BEFORE the tail-poison erase below - it must reflect | |
| // the state of the original VL (for the InsertsCost call), not the | |
| // potentially-shrunk UniqueValues. | |
| bool AreAllValuesNonConst = UniquePositions.size() == UniqueValues.size(); | |
| // Drop tail poisons, if the values can be vectorized. | |
| if (RequireScheduling) { | |
| const auto EndIt = | |
| find_if_not(make_range(UniqueValues.rbegin(), UniqueValues.rend()), | |
| IsaPred<PoisonValue>); | |
| assert(EndIt != UniqueValues.rend() && "Expected at least one non-poison."); | |
| UniqueValues.erase(EndIt.base(), UniqueValues.end()); | |
| } | |
| unsigned NumUniqueScalarValues = UniqueValues.size(); | |
| if (NumUniqueScalarValues == VL.size()) { | |
| ReuseShuffleIndices.clear(); | |
| return true; | |
| } | |
| // For VL=4 with 3 unique values: keep originals. A <3 x T> vector is | |
| // always widened to <4 x T> on hardware, so the packing just adds an | |
| // extra expand shuffle. Does not apply to loads (a <3 x T> load is a | |
| // single memory access) or PHIs (benefit from compact packing in loops). | |
| constexpr unsigned SmallVecWidth = 4; | |
| constexpr unsigned SmallVecUniqueThreshold = 3; | |
| if (VL.size() == SmallVecWidth && | |
| NumUniqueScalarValues == SmallVecUniqueThreshold && !BuildGatherOnly && | |
| !(S && (S.getOpcode() == Instruction::Load || | |
| S.getOpcode() == Instruction::PHI))) { | |
| // Keep originals with identity reuse - no packing, no extra shuffle. | |
| ReuseShuffleIndices.clear(); | |
| return true; | |
| } | |
| // Checks if unique inserts + shuffle is more profitable than just inserts or | |
| // vectorized values. | |
| auto EstimatePackPlusShuffleVsInserts = [&]() { | |
| // Single instruction/argument insert - no shuffle. | |
| if (UniquePositions.size() == 1 && | |
| (NumUniqueScalarValues == 1 || | |
| all_of(UniqueValues, IsaPred<UndefValue, Instruction, Argument>))) | |
| return std::make_pair(false, false); | |
| // For large gathers with power-of-2 VL where packing would produce | |
| // non-power-of-2, reject if most scalars are constants - the packing | |
| // overhead (non-power-of-2 split + shuffles) outweighs the benefit. | |
| constexpr unsigned MinVLForConstGatherCheck = 4; | |
| if (BuildGatherOnly && VL.size() > MinVLForConstGatherCheck && | |
| has_single_bit(static_cast<unsigned>(VL.size())) && | |
| !has_single_bit(NumUniqueScalarValues) && | |
| UniquePositions.size() * 2 < NumUniqueScalarValues) | |
| return std::make_pair(false, false); | |
| auto GetLoadsState = [&](ArrayRef<Value *> Loads) { | |
| assert(S && S.getOpcode() == Instruction::Load && "Expected load."); | |
| BoUpSLP::OrdersType Order; | |
| SmallVector<Value *> PointerOps; | |
| BoUpSLP::StridedPtrInfo SPtrInfo; | |
| return R.canVectorizeLoads(Loads, S.getMainOp(), Order, PointerOps, | |
| SPtrInfo); | |
| }; | |
| auto CheckLoads = [&](ArrayRef<Value *> Loads, bool IncludeGather) { | |
| BoUpSLP::LoadsState Res = GetLoadsState(Loads); | |
| return (IncludeGather && Res == BoUpSLP::LoadsState::Gather) || | |
| Res == BoUpSLP::LoadsState::ScatterVectorize || | |
| Res == BoUpSLP::LoadsState::StridedVectorize || | |
| Res == BoUpSLP::LoadsState::CompressVectorize || | |
| Res == BoUpSLP::LoadsState::BlendedLoadVectorize; | |
| }; | |
| // Operand of the root tree entry on the vectorize path: always pack the | |
| // scalars (PackProfitable=true). Choose between keeping the original VL | |
| // and packing the unique values: | |
| // - For loads, prefer the originals only when both the deduplicated and | |
| // the full sequence can be vectorized non-Gather, or when the reuse | |
| // mask is the identity (the shuffle is free). | |
| // - For everything else (including !S, where RequireScheduling is forced | |
| // to false above), keep originals iff no scheduling is required. | |
| bool IsRootOperand = | |
| UserTreeIdx.UserTE && UserTreeIdx.UserTE->Idx == 0 && !BuildGatherOnly; | |
| if (IsRootOperand) { | |
| if (S && S.getOpcode() == Instruction::Load) { | |
| bool UseOrig = (CheckLoads(UniqueValues, /*IncludeGather=*/true) && | |
| CheckLoads(VL, /*IncludeGather=*/false)) || | |
| ShuffleVectorInst::isIdentityMask( | |
| ReuseShuffleIndices, ReuseShuffleIndices.size()); | |
| return std::make_pair(true, UseOrig); | |
| } | |
| return std::make_pair(true, !RequireScheduling); | |
| } | |
| APInt DemandedElts = APInt::getZero(VL.size()); | |
| for (auto [Idx, Val] : enumerate(ReuseShuffleIndices)) | |
| if (Val != PoisonMaskElem && UniquePositions.contains(UniqueValues[Val])) | |
| DemandedElts.setBit(Idx); | |
| Type *ScalarTy = ::getValueType(UniqueValues.front(), Opts.slp_revec); | |
| auto *VecTy = cast<VectorType>(getWidenedType(ScalarTy, VL.size())); | |
| auto *UniquesVecTy = | |
| cast<VectorType>(getWidenedType(ScalarTy, NumUniqueScalarValues)); | |
| const unsigned NumParts = R.getNumberOfParts(VecTy, ScalarTy); | |
| const unsigned UniquesNumParts = R.getNumberOfParts(UniquesVecTy, ScalarTy); | |
| // No need to schedule scalars and only single register used? Use original | |
| // scalars, do not pack. | |
| if (!RequireScheduling) { | |
| if (VL.size() / NumUniqueScalarValues == 1 && | |
| (NumParts <= 1 || UniquesNumParts >= NumParts)) | |
| return std::make_pair(true, true); | |
| // For PHI operands, prefer packing with reuse shuffle - the PHI | |
| // carries the vector through the loop cheaply. | |
| if (S && S.getOpcode() == Instruction::PHI && NumUniqueScalarValues > 1 && | |
| UniquesNumParts <= NumParts) | |
| return std::make_pair(true, false); | |
| } | |
| const TTI::TargetCostKind CostKind = R.getCostKind(); | |
| InstructionCost ReusesCost = getShuffleCost( | |
| TTI, TTI::SK_PermuteSingleSrc, VecTy, CostKind, | |
| NumUniqueScalarValues > VL.size() / 2 ? ArrayRef<int>() | |
| : ArrayRef(ReuseShuffleIndices), | |
| /*Index=*/0, UniquesVecTy); | |
| // For vectorizable (non-gather) nodes with low duplication, prefer keeping | |
| // the original values over packing uniques + reshuffling: | |
| // - A single duplicate (non-load) adds negligible overhead. | |
| // - When most values are already unique (>50%), or exactly half are unique | |
| // for some ops (GEPs, non-alt-shuffle casts), the reshuffle cost may | |
| // exceed the savings from a smaller packed vector - check against a | |
| // per-register-part threshold (stricter for wider vectors). | |
| if (S && !BuildGatherOnly) { | |
| bool HasOneDup = S.getOpcode() != Instruction::Load && | |
| NumUniqueScalarValues + 1 == VL.size(); | |
| bool MostlyUnique = NumUniqueScalarValues * 2 > VL.size(); | |
| bool IsHalfUniqueValues = | |
| NumUniqueScalarValues * 2 == VL.size() && | |
| (S.getOpcode() == Instruction::GetElementPtr || | |
| (isa<CastInst>(S.getMainOp()) && !S.isAltShuffle())); | |
| InstructionCost CostThreshold = | |
| NumParts * (VL.size() > SmallVecWidth ? 1 : 2); | |
| if (HasOneDup || | |
| ((MostlyUnique || IsHalfUniqueValues) && ReusesCost > CostThreshold)) | |
| return std::make_pair(true, true); | |
| } | |
| // For loads, check if either the deduplicated or the full (with | |
| // duplicates) set can be scatter/compress-vectorized. Prefer the unique | |
| // loads (pack + reshuffle) when possible, otherwise use the originals. | |
| if (S && S.getOpcode() == Instruction::Load) { | |
| // The poison lanes are covered by a single wide load, nothing to pack. | |
| if (any_of(VL, IsaPred<PoisonValue>) && | |
| GetLoadsState(VL) == BoUpSLP::LoadsState::Vectorize) | |
| return std::make_pair(true, true); | |
| bool UniquesVectorized = | |
| CheckLoads(UniqueValues, /*IncludeGather=*/false); | |
| if (UniquesVectorized || CheckLoads(VL, /*IncludeGather=*/false)) { | |
| bool UseOriginal = !UniquesVectorized; | |
| // The unique loads are consecutive: pack them unless the masked | |
| // gather of the originals is cheaper than the wide load + reshuffle. | |
| if (UseOriginal && | |
| GetLoadsState(UniqueValues) == BoUpSLP::LoadsState::Vectorize) { | |
| auto *LI0 = cast<LoadInst>(S.getMainOp()); | |
| InstructionCost PackCost = | |
| ReusesCost + TTI.getMemoryOpCost( | |
| Instruction::Load, UniquesVecTy, LI0->getAlign(), | |
| LI0->getPointerAddressSpace(), CostKind, | |
| TTI::getOperandInfo(LI0->getPointerOperand())); | |
| InstructionCost GatherCost = TTI.getMemIntrinsicInstrCost( | |
| MemIntrinsicCostAttributes( | |
| Intrinsic::masked_gather, VecTy, LI0->getPointerOperand(), | |
| /*VariableMask=*/false, computeCommonAlignment<LoadInst>(VL)), | |
| CostKind); | |
| UseOriginal = GatherCost < PackCost; | |
| } | |
| return std::make_pair(true, UseOriginal); | |
| } | |
| } | |
| bool CanSkipBVCost = | |
| (!BuildGatherOnly && !RequireScheduling) || R.hasSameNode(S, VL); | |
| InstructionCost InsertsCost = | |
| CanSkipBVCost | |
| ? InstructionCost(TTI::TCC_Free) | |
| : getScalarizationOverhead(TTI, Opts.slp_revec, ScalarTy, VecTy, | |
| DemandedElts, | |
| /*Insert=*/true, /*Extract=*/false, | |
| CostKind, AreAllValuesNonConst, VL); | |
| APInt UniquesDemandedElts = APInt::getAllOnes(NumUniqueScalarValues); | |
| for (const auto [Idx, V] : enumerate(UniqueValues)) | |
| if (isConstant(V)) | |
| UniquesDemandedElts.clearBit(Idx); | |
| InstructionCost UniquesCost = | |
| CanSkipBVCost | |
| ? InstructionCost(TTI::TCC_Free) | |
| : getScalarizationOverhead(TTI, Opts.slp_revec, ScalarTy, | |
| UniquesVecTy, UniquesDemandedElts, | |
| /*Insert=*/true, | |
| /*Extract=*/false, CostKind, | |
| AreAllValuesNonConst, UniqueValues); | |
| UniquesCost += ReusesCost; | |
| if (UniquesCost <= InsertsCost) { | |
| // Packing to a vector that occupies the same register width as the | |
| // original only adds the reshuffle cost; keep the originals. Loads may | |
| // instead benefit from a wider contiguous load. | |
| if (S && S.getOpcode() != Instruction::Load) { | |
| unsigned EltBits = | |
| S.getMainOp()->getDataLayout().getTypeSizeInBits(ScalarTy); | |
| unsigned MinVF = R.getMinVF(EltBits); | |
| auto RegWidth = [&](unsigned N) { | |
| return std::max( | |
| getFullVectorNumberOfElements(TTI, ScalarTy, N, Opts.slp_revec), | |
| MinVF); | |
| }; | |
| // Keeping the originals just moves the reshuffle to the operand | |
| // columns with duplicates; keep them only if at most one column has | |
| // duplicates, so the total number of reshuffles does not grow. | |
| auto HasExtraReshuffle = [&]() { | |
| if (BuildGatherOnly) | |
| return false; | |
| unsigned NumDupColumns = 0; | |
| for (unsigned OpIdx : | |
| seq<unsigned>(S.getMainOp()->getNumOperands())) { | |
| SmallPtrSet<const Value *, 16> UniqueOps; | |
| auto IsNotPoison = [](Value *V) { return !isa<PoisonValue>(V); }; | |
| for (Value *V : make_filter_range(VL, IsNotPoison)) { | |
| Value *Op; | |
| if (S.isCopyableElement(V)) { | |
| if (OpIdx != 0) | |
| continue; | |
| Op = V; | |
| } else { | |
| auto *I = dyn_cast<Instruction>(V); | |
| if (!I || OpIdx >= I->getNumOperands()) | |
| continue; | |
| Op = I->getOperand(OpIdx); | |
| } | |
| if (!isConstant(Op) && !UniqueOps.insert(Op).second) { | |
| ++NumDupColumns; | |
| break; | |
| } | |
| } | |
| if (NumDupColumns > 1) | |
| return true; | |
| } | |
| return false; | |
| }; | |
| return std::make_pair(true, RegWidth(NumUniqueScalarValues) >= | |
| RegWidth(VL.size()) && | |
| !HasExtraReshuffle()); | |
| } | |
| return std::make_pair(true, false); | |
| } | |
| InstructionCost CostDiff = UniquesCost - InsertsCost; | |
| if (CostDiff < TTI::TCC_Expensive || | |
| (R.getTreeSize() == 0 && R.isReductionTree() && | |
| CostDiff == TTI::TCC_Expensive)) | |
| return std::make_pair(S && (!S.isAltShuffle() || !BuildGatherOnly), | |
| false); | |
| // Otherwise, use original values, if values do not require scheduling and | |
| // pass still try to vectorize them. | |
| bool KeepOriginal = !BuildGatherOnly && !RequireScheduling; | |
| return std::make_pair(KeepOriginal, KeepOriginal); | |
| }; | |
| const auto [PackProfitable, UseOriginal] = EstimatePackPlusShuffleVsInserts(); | |
| if (PackProfitable) { | |
| if (UseOriginal) { | |
| // Prefer original scalars - avoid shuffling. | |
| ReuseShuffleIndices.clear(); | |
| } else { | |
| // Better to use uniques + reshuffle. | |
| LLVM_DEBUG(dbgs() << "SLP: Shuffle for reused scalars.\n"); | |
| VL = std::move(UniqueValues); | |
| } | |
| return true; | |
| } | |
| // Buildvector/gather of the original scalars. | |
| LLVM_DEBUG(dbgs() << "SLP: Scalar used twice in bundle.\n"); | |
| ReuseShuffleIndices.clear(); | |
| return false; | |
| } | |
| /// The nodes with no more than this number of elements are not split. | |
| static constexpr unsigned SmallNodeSize = 4; | |
| bool BoUpSLP::canBuildSplitNode(ArrayRef<Value *> VL, | |
| const InstructionsState &LocalState, | |
| SmallVectorImpl<Value *> &Op1, | |
| SmallVectorImpl<Value *> &Op2, | |
| OrdersType &ReorderIndices) const { | |
| if (VL.size() <= SmallNodeSize || TTI->preferAlternateOpcodeVectorization() || | |
| !Opts.slp_split_alternate_instructions) | |
| return false; | |
| // Split vectorization of struct types is not supported. | |
| if (isa<StructType>(getValueType(VL.front(), Opts.slp_revec))) | |
| return false; | |
| // Check if this is a duplicate of another split entry. | |
| LLVM_DEBUG(dbgs() << "SLP: \tChecking bundle: " << *LocalState.getMainOp() | |
| << ".\n"); | |
| for (TreeEntry *E : getSplitTreeEntries(LocalState.getMainOp())) { | |
| if (E->isSame(VL)) { | |
| LLVM_DEBUG(dbgs() << "SLP: Perfect diamond merge at " | |
| << *LocalState.getMainOp() << ".\n"); | |
| return false; | |
| } | |
| SmallPtrSet<Value *, 8> Values(llvm::from_range, E->Scalars); | |
| if (all_of(VL, [&](Value *V) { | |
| return isa<PoisonValue>(V) || Values.contains(V); | |
| })) { | |
| LLVM_DEBUG(dbgs() << "SLP: Gathering due to full overlap.\n"); | |
| return false; | |
| } | |
| } | |
| ReorderIndices.assign(VL.size(), VL.size()); | |
| SmallBitVector Op1Indices(VL.size()); | |
| for (auto [Idx, V] : enumerate(VL)) { | |
| auto *I = dyn_cast<Instruction>(V); | |
| if (!I) { | |
| Op1.push_back(V); | |
| Op1Indices.set(Idx); | |
| continue; | |
| } | |
| if ((LocalState.getAltOpcode() != LocalState.getOpcode() && | |
| isMainInstruction(I, LocalState.getMainOp(), LocalState.getAltOp(), | |
| *TLI)) || | |
| (LocalState.getAltOpcode() == LocalState.getOpcode() && | |
| !isAlternateInstruction(I, LocalState.getMainOp(), | |
| LocalState.getAltOp(), *TLI))) { | |
| Op1.push_back(V); | |
| Op1Indices.set(Idx); | |
| continue; | |
| } | |
| Op2.push_back(V); | |
| } | |
| Type *ScalarTy = getValueType(VL.front(), Opts.slp_revec); | |
| auto *VecTy = cast<VectorType>(getWidenedType(ScalarTy, VL.size())); | |
| unsigned Opcode0 = LocalState.getOpcode(); | |
| unsigned Opcode1 = LocalState.getAltOpcode(); | |
| SmallBitVector OpcodeMask(getAltInstrMask(VL, ScalarTy, Opcode0, Opcode1)); | |
| // Enable split node, only if all nodes do not form legal alternate | |
| // instruction (like X86 addsub). | |
| SmallPtrSet<Value *, 4> UOp1(llvm::from_range, Op1); | |
| SmallPtrSet<Value *, 4> UOp2(llvm::from_range, Op2); | |
| if (UOp1.size() <= 1 || UOp2.size() <= 1 || | |
| TTI->isLegalAltInstr(VecTy, Opcode0, Opcode1, OpcodeMask)) | |
| return false; | |
| unsigned Op1Cnt = 0, Op2Cnt = Op1.size(); | |
| for (unsigned Idx : seq<unsigned>(VL.size())) { | |
| if (Op1Indices.test(Idx)) { | |
| ReorderIndices[Op1Cnt] = Idx; | |
| ++Op1Cnt; | |
| } else { | |
| ReorderIndices[Op2Cnt] = Idx; | |
| ++Op2Cnt; | |
| } | |
| } | |
| if (isIdentityOrder(ReorderIndices)) | |
| ReorderIndices.clear(); | |
| // When VL fills a power-of-2 register but the split halves do not, the | |
| // reorder shuffle makes the split unprofitable - reject. | |
| else if (hasFullVectorsOrPowerOf2(*TTI, Op1.front()->getType(), VL.size(), | |
| Opts.slp_revec) && | |
| (!hasFullVectorsOrPowerOf2(*TTI, Op1.front()->getType(), Op1.size(), | |
| Opts.slp_revec) || | |
| !hasFullVectorsOrPowerOf2(*TTI, Op2.front()->getType(), Op2.size(), | |
| Opts.slp_revec))) | |
| return false; | |
| SmallVector<int> Mask; | |
| if (!ReorderIndices.empty()) | |
| inversePermutation(ReorderIndices, Mask); | |
| unsigned NumParts = TTI->getNumberOfParts(VecTy); | |
| auto *Op1VecTy = cast<VectorType>(getWidenedType(ScalarTy, Op1.size())); | |
| auto *Op2VecTy = cast<VectorType>(getWidenedType(ScalarTy, Op2.size())); | |
| // Check non-profitable single register ops, which better to be represented | |
| // as alternate ops. | |
| if (NumParts >= VL.size()) | |
| return false; | |
| InstructionCost InsertCost = getShuffleCost( | |
| *TTI, TTI::SK_InsertSubvector, VecTy, CostKind, {}, Op1.size(), Op2VecTy); | |
| auto *SubVecTy = cast<VectorType>( | |
| getWidenedType(ScalarTy, std::max(Op1.size(), Op2.size()))); | |
| InstructionCost NewShuffleCost = | |
| getShuffleCost(*TTI, TTI::SK_PermuteTwoSrc, SubVecTy, CostKind, Mask); | |
| if (!LocalState.isCmpOp() && NumParts <= 1 && | |
| (Mask.empty() || InsertCost >= NewShuffleCost)) | |
| return false; | |
| if ((LocalState.getMainOp()->isBinaryOp() && | |
| LocalState.getAltOp()->isBinaryOp() && | |
| (LocalState.isShiftOp() || LocalState.isBitwiseLogicOp() || | |
| LocalState.isAddSubLikeOp() || LocalState.isMulDivLikeOp())) || | |
| (LocalState.getMainOp()->isCast() && LocalState.getAltOp()->isCast()) || | |
| (LocalState.getMainOp()->isUnaryOp() && | |
| LocalState.getAltOp()->isUnaryOp())) { | |
| SmallVector<Value *> Ops0, Ops1; | |
| for (Instruction *I : make_isa_range<Instruction>(VL)) { | |
| Ops0.push_back(I->getOperand(0)); | |
| if (I->isBinaryOp()) | |
| Ops1.push_back(I->getOperand(1)); | |
| } | |
| TTI::OperandValueInfo Op0Info = TTI::getOperandInfo(Ops0); | |
| TTI::OperandValueInfo Op1Info = | |
| Ops1.empty() ? TTI::OperandValueInfo() : TTI::getOperandInfo(Ops1); | |
| InstructionCost OriginalVecOpsCost = | |
| TTI->getArithmeticInstrCost(Opcode0, VecTy, CostKind, Op0Info, Op1Info, | |
| {}, LocalState.getMainOp()) + | |
| TTI->getArithmeticInstrCost(Opcode1, VecTy, CostKind, Op0Info, Op1Info, | |
| {}, LocalState.getAltOp()); | |
| SmallVector<int> OriginalMask(VL.size(), PoisonMaskElem); | |
| for (unsigned Idx : seq<unsigned>(VL.size())) { | |
| if (isa<PoisonValue>(VL[Idx])) | |
| continue; | |
| OriginalMask[Idx] = Idx + (Op1Indices.test(Idx) ? 0 : VL.size()); | |
| } | |
| InstructionCost OriginalCost = | |
| OriginalVecOpsCost + getShuffleCost(*TTI, TTI::SK_PermuteTwoSrc, VecTy, | |
| CostKind, OriginalMask); | |
| InstructionCost NewVecOpsCost = | |
| TTI->getArithmeticInstrCost(Opcode0, Op1VecTy, CostKind, Op0Info, | |
| Op1Info, {}, LocalState.getMainOp()) + | |
| TTI->getArithmeticInstrCost(Opcode1, Op2VecTy, CostKind, Op0Info, | |
| Op1Info, {}, LocalState.getAltOp()); | |
| InstructionCost NewCost = | |
| NewVecOpsCost + InsertCost + | |
| (!VectorizableTree.empty() && getRootNode().hasState() && | |
| getRootNode().getOpcode() == Instruction::Store | |
| ? NewShuffleCost | |
| : 0); | |
| // If not profitable to split - exit. | |
| if (NewCost >= OriginalCost) | |
| return false; | |
| } | |
| return true; | |
| } | |
| namespace { | |
| /// Class accepts incoming list of values, checks if it is able to model | |
| /// "copyable" values as compatible operations, and generates the list of values | |
| /// for scheduling and list of operands doe the new nodes. | |
| class InstructionsCompatibilityAnalysis { | |
| DominatorTree &DT; | |
| const DataLayout &DL; | |
| const TargetTransformInfo &TTI; | |
| const TargetLibraryInfo &TLI; | |
| unsigned MainOpcode = 0; | |
| Instruction *MainOp = nullptr; | |
| /// Whether every copyable in the current value list is an absorbable | |
| /// single-use fmul/fadd. Computed once per buildInstructionsState call. | |
| bool AbsorbCopyableFMulOrFAdds = false; | |
| /// Checks if the opcode is supported as the main opcode for copyable | |
| /// elements. | |
| static bool isSupportedOpcode(const unsigned Opcode) { | |
| return Opcode == Instruction::Add || Opcode == Instruction::Sub || | |
| Opcode == Instruction::Mul || Opcode == Instruction::AShr || | |
| Opcode == Instruction::LShr || Opcode == Instruction::Shl || | |
| Opcode == Instruction::SDiv || Opcode == Instruction::UDiv || | |
| Opcode == Instruction::And || Opcode == Instruction::Or || | |
| Opcode == Instruction::Xor || Opcode == Instruction::FAdd || | |
| Opcode == Instruction::FSub || Opcode == Instruction::FMul || | |
| Opcode == Instruction::FDiv; | |
| } | |
| /// Checks if \p I can be the main op for copyable analysis: a supported | |
| /// binary operator, fmuladd, or an integer min/max intrinsic, the only | |
| /// call with a well-defined idempotent value (FP min/max lacks one because of | |
| /// NaNs). An extractelement with constant index from a fixed vector is also | |
| /// supported: the matching lanes reuse the source vector and the copyable | |
| /// lanes are inserted into it. A single-index scalar GEP is supported: the | |
| /// copyable lanes are modeled as zero-index GEPs of the lane values | |
| /// themselves. A scalar zext is supported: the copyable lanes are constants | |
| /// modeled as zexts of truncated constants. | |
| static bool isSupportedMainOp(Instruction *I) { | |
| return isSupportedOpcode(I->getOpcode()) || isa<MinMaxIntrinsic>(I) || | |
| RecurrenceDescriptor::isFMulAddIntrinsic(I) || | |
| (isa<ExtractElementInst>(I) && isVectorLikeInstWithConstOps(I) && | |
| isa<FixedVectorType>(I->getOperand(0)->getType())) || | |
| (isa<GetElementPtrInst>(I) && I->getNumOperands() == 2 && | |
| !I->getType()->isVectorTy()) || | |
| (isa<ZExtInst>(I) && !I->getType()->isVectorTy() && | |
| !isa<Constant>(I->getOperand(0))); | |
| } | |
| /// Identifies the best candidate value, which represents main opcode | |
| /// operation. | |
| /// Currently the best candidate is the Add instruction with the parent | |
| /// block with the highest DFS incoming number (block, that dominates other). | |
| void findAndSetMainInstruction(ArrayRef<Value *> VL, const BoUpSLP &R) { | |
| BasicBlock *Parent = nullptr; | |
| // Checks if the instruction has supported opcode. | |
| auto IsSupportedInstruction = [&](Instruction *I, bool AnyUndef) { | |
| if (AnyUndef && (I->isIntDivRem() || I->isFPDivRem() || isa<CallInst>(I))) | |
| return false; | |
| return I && isSupportedMainOp(I) && | |
| (!doesNotNeedToBeScheduled(I) || !R.isVectorized(I)); | |
| }; | |
| // Exclude operands instructions immediately to improve compile time, it | |
| // will be unable to schedule anyway. | |
| SmallDenseSet<Value *, 8> Operands; | |
| SmallMapVector<unsigned, SmallVector<Instruction *>, 4> Candidates; | |
| bool AnyUndef = false; | |
| for (Value *V : VL) { | |
| auto *I = dyn_cast<Instruction>(V); | |
| if (!I) { | |
| AnyUndef |= isa<UndefValue>(V); | |
| continue; | |
| } | |
| if (!DT.isReachableFromEntry(I->getParent())) | |
| continue; | |
| if (Candidates.empty()) { | |
| Candidates.try_emplace(I->getOpcode()).first->second.push_back(I); | |
| Parent = I->getParent(); | |
| Operands.insert(I->op_begin(), I->op_end()); | |
| continue; | |
| } | |
| if (Parent == I->getParent()) { | |
| Candidates.try_emplace(I->getOpcode()).first->second.push_back(I); | |
| Operands.insert(I->op_begin(), I->op_end()); | |
| continue; | |
| } | |
| auto *NodeA = DT.getNode(Parent); | |
| auto *NodeB = DT.getNode(I->getParent()); | |
| assert(NodeA && "Should only process reachable instructions"); | |
| assert(NodeB && "Should only process reachable instructions"); | |
| assert((NodeA == NodeB) == | |
| (NodeA->getDFSNumIn() == NodeB->getDFSNumIn()) && | |
| "Different nodes should have different DFS numbers"); | |
| if (NodeA->getDFSNumIn() < NodeB->getDFSNumIn()) { | |
| Candidates.clear(); | |
| Candidates.try_emplace(I->getOpcode()).first->second.push_back(I); | |
| Parent = I->getParent(); | |
| Operands.clear(); | |
| Operands.insert(I->op_begin(), I->op_end()); | |
| } | |
| } | |
| unsigned BestOpcodeNum = 0; | |
| MainOp = nullptr; | |
| bool UsedOutside = false; | |
| for (const auto &P : Candidates) { | |
| bool PUsedOutside = all_of(P.second, isUsedOutsideBlock); | |
| if (UsedOutside && !PUsedOutside) | |
| continue; | |
| if (!UsedOutside && PUsedOutside) | |
| BestOpcodeNum = 0; | |
| if (P.second.size() < BestOpcodeNum) | |
| continue; | |
| // If have inner dependencies - skip. | |
| if (!PUsedOutside && any_of(P.second, [&](Instruction *I) { | |
| return Operands.contains(I); | |
| })) | |
| continue; | |
| // On a tie, keep the outer binary op as MainOp rather than replacing it | |
| // with an inner op that appears as its direct operand. For example, in | |
| // (2.0f * A) + B the fadd and fmul each appear once in VL; without this | |
| // check the fmul could win and prevent vectorization of the fadd pair. | |
| if (P.second.size() == BestOpcodeNum) { | |
| auto *I = P.second.front(); | |
| if (auto *MainBO = dyn_cast<BinaryOperator>(MainOp)) { | |
| auto *MainBOOp0 = dyn_cast<Instruction>(MainBO->getOperand(0)); | |
| auto *MainBOOp1 = dyn_cast<Instruction>(MainBO->getOperand(1)); | |
| if (MainBOOp0 && MainBOOp0->getOpcode() == I->getOpcode() && | |
| MainBOOp0->getParent() == I->getParent()) | |
| continue; | |
| if (MainBOOp1 && MainBOOp1->getOpcode() == I->getOpcode() && | |
| MainBOOp1->getParent() == I->getParent()) | |
| continue; | |
| } | |
| // Keep fmuladd over fmul/fadd on a tie only when every copyable is | |
| // an absorbed fmul/fadd. | |
| if (RecurrenceDescriptor::isFMulAddIntrinsic(MainOp) && | |
| (I->getOpcode() == Instruction::FMul || | |
| I->getOpcode() == Instruction::FAdd) && | |
| AbsorbCopyableFMulOrFAdds) | |
| continue; | |
| // Same check when fmuladd replaces fmul/fadd on a tie. | |
| if ((MainOp->getOpcode() == Instruction::FMul || | |
| MainOp->getOpcode() == Instruction::FAdd) && | |
| RecurrenceDescriptor::isFMulAddIntrinsic(I) && | |
| !AbsorbCopyableFMulOrFAdds) | |
| continue; | |
| } | |
| UsedOutside = PUsedOutside; | |
| if (P.first == Instruction::GetElementPtr) { | |
| // Only the GEPs of the main op shape (source element type and index | |
| // type) are matching lanes, the others are copyable. Select the most | |
| // frequent shape to minimize the number of copyable lanes. | |
| SmallDenseMap<std::pair<Type *, Type *>, unsigned, 4> ShapeCounts; | |
| auto GetShape = [](Instruction *I) { | |
| return std::make_pair(cast<GEPOperator>(I)->getSourceElementType(), | |
| I->getOperand(1)->getType()); | |
| }; | |
| for (Instruction *I : P.second) | |
| if (IsSupportedInstruction(I, AnyUndef)) | |
| ++ShapeCounts[GetShape(I)]; | |
| unsigned BestShapeNum = 0; | |
| for (Instruction *I : P.second) { | |
| if (!IsSupportedInstruction(I, AnyUndef)) | |
| continue; | |
| unsigned ShapeNum = ShapeCounts.lookup(GetShape(I)); | |
| if (ShapeNum > BestShapeNum) { | |
| MainOp = I; | |
| BestOpcodeNum = P.second.size(); | |
| BestShapeNum = ShapeNum; | |
| } | |
| } | |
| continue; | |
| } | |
| for (Instruction *I : P.second) { | |
| if (IsSupportedInstruction(I, AnyUndef)) { | |
| MainOp = I; | |
| BestOpcodeNum = P.second.size(); | |
| break; | |
| } | |
| } | |
| } | |
| if (MainOp) { | |
| // Do not match, if any copyable is a terminator from the same block as | |
| // the main operation. | |
| if (any_of(make_isa_range<Instruction>(VL), [&](Instruction *I) { | |
| return I->getParent() == MainOp->getParent() && I->isTerminator(); | |
| })) { | |
| MainOp = nullptr; | |
| return; | |
| } | |
| MainOpcode = MainOp->getOpcode(); | |
| } | |
| } | |
| /// Returns the idempotent value for MainOp. For Add, returns 0. For Or, it | |
| /// should choose between false and the operand itself, since V or V == V. | |
| /// For the min/max intrinsics, returns the corresponding limit value (e.g. 0 | |
| /// for umax, since umax(V, 0) == V). For smin and smax the type limit is | |
| /// kept when it stays same after narrowing, otherwise the limit is deduced | |
| /// from the signed width of V. | |
| Value *selectBestIdempotentValue(Value *V) const { | |
| assert(isSupportedMainOp(MainOp) && "Unsupported opcode"); | |
| if (auto *MinMax = dyn_cast<MinMaxIntrinsic>(MainOp); | |
| MinMax && MinMax->isSigned() && | |
| !isTypeLimitNeutral(V, MinMax->getIntrinsicID() == Intrinsic::smax, | |
| DL)) { | |
| // TODO: add AC to both width queries once the analysis has one. | |
| unsigned Width = ComputeMaxSignificantBits(V, DL, nullptr, nullptr, &DT); | |
| APInt Limit = MinMax->getIntrinsicID() == Intrinsic::smax | |
| ? APInt::getSignedMinValue(Width) | |
| : APInt::getSignedMaxValue(Width); | |
| return Constant::getIntegerValue( | |
| MainOp->getType(), | |
| Limit.sext(MainOp->getType()->getScalarSizeInBits())); | |
| } | |
| return ConstantExpr::getIdentity(MainOp, MainOp->getType(), | |
| !MainOp->isCommutative()); | |
| } | |
| /// Returns the value and operands for the \p V, considering if it is original | |
| /// instruction and its actual operands should be returned, or it is a | |
| /// copyable element and its should be represented as idempotent instruction. | |
| /// \p SelfOp selects the op(V, V) modeling for a copyable lane instead of | |
| /// op(V, identity). \p PoisonPlaceholder selects op(V, poison) for the | |
| /// absorbing constant V. | |
| SmallVector<Value *> getOperands(const InstructionsState &S, Value *V, | |
| bool SelfOp, | |
| bool PoisonPlaceholder = false) const { | |
| if (SelfOp) | |
| return {V, V}; | |
| if (isa<PoisonValue>(V)) { | |
| // Poison operands of the main op operand types: a GEP index or a cast | |
| // source has a type, different from the lane type. Excludes the | |
| // trailing callee operand of a call. | |
| auto *CI = dyn_cast<CallInst>(MainOp); | |
| return map_to_vector( | |
| seq<unsigned>(CI ? CI->arg_size() : MainOp->getNumOperands()), | |
| [&](unsigned Idx) -> Value * { | |
| return PoisonValue::get(MainOp->getOperand(Idx)->getType()); | |
| }); | |
| } | |
| if (!S.isCopyableElement(V)) | |
| return convertTo(cast<Instruction>(V), S).second; | |
| if (RecurrenceDescriptor::isFMulAddIntrinsic(MainOp)) { | |
| Type *Ty = MainOp->getType(); | |
| if (S.hasAbsorbedCopyableFMulOrFAdd() && | |
| isAbsorbableCopyableFMulOrFAdd(S, V)) { | |
| auto *I = cast<Instruction>(V); | |
| // fmuladd(a, b, -0.0) == fmul a, b. | |
| if (I->getOpcode() == Instruction::FMul) | |
| return {I->getOperand(0), I->getOperand(1), | |
| ConstantFP::getNegativeZero(Ty)}; | |
| // fmuladd(1.0, a, b) == fadd a, b. | |
| return {ConstantFP::get(Ty, 1.0), I->getOperand(0), I->getOperand(1)}; | |
| } | |
| // fmuladd(V, 1.0, -0.0) == V. | |
| if (S.getCopyableOpIdx() == 0) | |
| return {V, ConstantFP::get(Ty, 1.0), ConstantFP::getNegativeZero(Ty)}; | |
| // fmuladd(0.0, -0.0, V) == V. | |
| return {ConstantFP::getZero(Ty), ConstantFP::getNegativeZero(Ty), V}; | |
| } | |
| // gep V, 0 == V. | |
| if (MainOpcode == Instruction::GetElementPtr) | |
| return {V, ConstantInt::get(MainOp->getOperand(1)->getType(), 0)}; | |
| // zext(trunc(V)) == V for the round-trippable constant lanes. | |
| if (MainOpcode == Instruction::ZExt) | |
| return {ConstantExpr::getTrunc(cast<Constant>(V), | |
| cast<CastInst>(MainOp)->getSrcTy())}; | |
| assert(isSupportedMainOp(MainOp) && "Unsupported opcode"); | |
| Type *Ty = V->getType(); | |
| if (PoisonPlaceholder && | |
| V == ConstantExpr::getBinOpAbsorber(MainOpcode, Ty)) | |
| return {V, PoisonValue::get(Ty)}; | |
| if (isa<UndefValue>(V) && isUndefTolerantBinOp(MainOp)) | |
| return {V, V}; | |
| return {V, selectBestIdempotentValue(V)}; | |
| } | |
| /// Checks if the lanes with the absorbing constant may use poison as the | |
| /// other operand: every other non-poison lane must be a binary operator with | |
| /// one constant operand and an undef-tolerant binary operator of a distinct | |
| /// load as the other operand. | |
| bool canUsePoisonPlaceholders(const InstructionsState &S, | |
| ArrayRef<Value *> VL) const { | |
| Constant *Absorber = | |
| ConstantExpr::getBinOpAbsorber(MainOpcode, VL.front()->getType()); | |
| if (!Absorber || !is_contained(VL, Absorber)) | |
| return false; | |
| SmallPtrSet<const LoadInst *, 8> Loads; | |
| return all_of(VL, [&](Value *V) { | |
| if (isa<PoisonValue>(V) || S.isCopyableElement(V)) | |
| return true; | |
| SmallVector<Value *> Ops = convertTo(cast<Instruction>(V), S).second; | |
| return count_if(Ops, IsaPred<Constant>) == 1 && | |
| any_of(Ops, [&](Value *Op) { | |
| const LoadInst *LI = getLoadOfUndefTolerantBinOp(Op); | |
| return LI && Loads.insert(LI).second; | |
| }); | |
| }); | |
| } | |
| /// Returns the copyable lanes of an idempotent binop to be modeled as | |
| /// op(V, V) == V instead of op(V, identity): when the operand column the | |
| /// lane joins also holds a constant lane, the reorder would align that | |
| /// column as constants and move the lane out, breaking the opcode-peer | |
| /// node, while the symmetric form is reorder-proof. The other column must | |
| /// be an instruction node so the lane is absorbed there as a copyable | |
| /// rather than breaking a constant column. A lane is modeled this way only | |
| /// when it has a strong opcode-peer pair of its own. | |
| SmallPtrSet<const Value *, 4> findSelfOpLanes(const InstructionsState &S, | |
| ArrayRef<Value *> VL, | |
| const BoUpSLP &R) const { | |
| SmallPtrSet<const Value *, 4> SelfOpLanes; | |
| if (!Instruction::isIdempotent(MainOpcode) || | |
| !S.areInstructionsWithCopyableElements() || none_of(VL, [&](Value *V) { | |
| if (isa<PoisonValue>(V)) | |
| return false; | |
| if (S.isCopyableElement(V)) | |
| return isa<Constant>(V); | |
| return isa<Constant>(cast<Instruction>(V)->getOperand(0)); | |
| })) | |
| return SelfOpLanes; | |
| // Index the instruction operands of the non-copyable lanes by opcode | |
| // and block, so the copyable lanes find their opcode peers without a | |
| // quadratic scan. | |
| SmallDenseMap<std::pair<unsigned, BasicBlock *>, SmallVector<Value *>> | |
| PeerOps; | |
| for (Instruction *RI : make_isa_range<Instruction>(VL)) { | |
| if (S.isCopyableElement(RI)) | |
| continue; | |
| assert(RI->getNumOperands() == 2 && "Expected binary operation."); | |
| for (unsigned OpIdx : seq<unsigned>(RI->getNumOperands())) { | |
| auto *OpI = dyn_cast<Instruction>(RI->getOperand(OpIdx)); | |
| if (OpI && | |
| isa<Instruction>(RI->getOperand(RI->getNumOperands() - 1 - OpIdx))) | |
| PeerOps[{OpI->getOpcode(), OpI->getParent()}].push_back(OpI); | |
| } | |
| } | |
| for (Instruction *I : make_isa_range<Instruction>(VL)) { | |
| if (!S.isCopyableElement(I)) | |
| continue; | |
| auto It = PeerOps.find({I->getOpcode(), I->getParent()}); | |
| if (It == PeerOps.end()) | |
| continue; | |
| SmallVector<std::pair<Value *, Value *>> Candidates; | |
| for (Value *OpI : It->second) | |
| Candidates.emplace_back(I, OpI); | |
| if (R.findBestRootPair(Candidates).first) | |
| SelfOpLanes.insert(I); | |
| } | |
| return SelfOpLanes; | |
| } | |
| /// Builds the index operands of a GEP node with the main op \p VL0. The | |
| /// indices of the matching GEP lanes (for which \p IsGEPLane returns true) | |
| /// are cast to the common index type, all other lanes (non-GEP pointers, | |
| /// copyable and poison lanes, modeled as gep V, 0) get a zero index of that | |
| /// type. Required to be able to find correct matches between different | |
| /// gather nodes and reuse the vectorized values rather than trying to | |
| /// gather them again. The vectorized nodes always have the common index | |
| /// type (a legality invariant); the operands built for the profitability | |
| /// analysis before that check may have none, the constants are cast to the | |
| /// pointer index type then. | |
| void buildGEPIndexOperands(ArrayRef<Value *> VL, Instruction *VL0, | |
| function_ref<bool(Value *)> IsGEPLane, | |
| BoUpSLP::ValueList &Indices) const { | |
| constexpr unsigned IndexIdx = 1; | |
| Type *Ty = getCommonGEPIndexType(VL, VL0, IsGEPLane, DL); | |
| if (!Ty) | |
| Ty = DL.getIndexType(VL0->getOperand(0)->getType()->getScalarType()); | |
| for (auto [Idx, V] : enumerate(VL)) { | |
| if (!IsGEPLane(V)) { | |
| Indices[Idx] = ConstantInt::getNullValue(Ty); | |
| continue; | |
| } | |
| auto *Op = cast<GetElementPtrInst>(V)->getOperand(IndexIdx); | |
| auto *CI = dyn_cast<ConstantInt>(Op); | |
| Indices[Idx] = CI ? ConstantFoldIntegerCast( | |
| CI, Ty, CI->getValue().isSignBitSet(), DL) | |
| : Op; | |
| } | |
| } | |
| /// Builds operands for the original instructions. | |
| void | |
| buildOriginalOperands(const InstructionsState &S, ArrayRef<Value *> VL, | |
| SmallVectorImpl<BoUpSLP::ValueList> &Operands) const { | |
| unsigned ShuffleOrOp = | |
| S.isAltShuffle() ? (unsigned)Instruction::ShuffleVector : S.getOpcode(); | |
| Instruction *VL0 = S.getMainOp(); | |
| switch (ShuffleOrOp) { | |
| case Instruction::PHI: { | |
| auto *PH = cast<PHINode>(VL0); | |
| // Keeps the reordered operands to avoid code duplication. | |
| PHIHandler Handler(DT, PH, VL); | |
| Handler.buildOperands(); | |
| Operands.assign(PH->getNumOperands(), {}); | |
| for (unsigned I : seq<unsigned>(PH->getNumOperands())) | |
| Operands[I].assign(Handler.getOperands(I).begin(), | |
| Handler.getOperands(I).end()); | |
| return; | |
| } | |
| case Instruction::ExtractValue: { | |
| SmallVector<unsigned> Indices; | |
| SmallVector<Value *> Calls; | |
| if (checkEVsForVecCalls(VL, S, TLI, Indices, Calls)) { | |
| Operands.assign(1, {}); | |
| Operands[0].swap(Calls); | |
| return; | |
| } | |
| [[fallthrough]]; | |
| } | |
| case Instruction::ExtractElement: | |
| // This is a special case, as it does not gather, but at the same time | |
| // we are not extending buildTree_rec() towards the operands. | |
| Operands.assign(1, {VL.size(), VL0->getOperand(0)}); | |
| return; | |
| case Instruction::InsertValue: | |
| case Instruction::InsertElement: | |
| Operands.assign(2, {VL.size(), nullptr}); | |
| for (auto [Idx, V] : enumerate(VL)) { | |
| auto *IE = cast<Instruction>(V); | |
| for (auto [OpIdx, Ops] : enumerate(Operands)) | |
| Ops[Idx] = IE->getOperand(OpIdx); | |
| } | |
| return; | |
| case Instruction::Load: | |
| Operands.assign( | |
| 1, {VL.size(), | |
| PoisonValue::get(cast<LoadInst>(VL0)->getPointerOperandType())}); | |
| for (auto [V, Op] : zip(VL, Operands.back())) { | |
| auto *LI = dyn_cast<LoadInst>(V); | |
| if (!LI) | |
| continue; | |
| Op = LI->getPointerOperand(); | |
| } | |
| return; | |
| case Instruction::ICmp: | |
| // With trunc X to i1 lanes the node is the bit tests of and A, B: the | |
| // compares give the operands of their ands, trunc X gives X and 1. | |
| if (any_of(VL, IsaPred<TruncInst>)) { | |
| Type *Ty = VL0->getOperand(0)->getType(); | |
| Operands.assign(2, {VL.size(), PoisonValue::get(Ty)}); | |
| for (auto [Idx, V] : enumerate(VL)) { | |
| if (auto *TI = dyn_cast<TruncInst>(V)) { | |
| Operands[0][Idx] = TI->getOperand(0); | |
| Operands[1][Idx] = ConstantInt::get(Ty, 1); | |
| } else if (auto *CI = dyn_cast<ICmpInst>(V)) { | |
| auto *And = cast<BinaryOperator>(CI->getOperand(0)); | |
| Operands[0][Idx] = And->getOperand(0); | |
| Operands[1][Idx] = And->getOperand(1); | |
| } | |
| } | |
| return; | |
| } | |
| [[fallthrough]]; | |
| case Instruction::ZExt: | |
| case Instruction::SExt: | |
| case Instruction::FPToUI: | |
| case Instruction::FPToSI: | |
| case Instruction::FPExt: | |
| case Instruction::PtrToInt: | |
| case Instruction::PtrToAddr: | |
| case Instruction::IntToPtr: | |
| case Instruction::SIToFP: | |
| case Instruction::UIToFP: | |
| case Instruction::Trunc: | |
| case Instruction::FPTrunc: | |
| case Instruction::BitCast: | |
| case Instruction::FCmp: | |
| case Instruction::FNeg: | |
| case Instruction::Add: | |
| case Instruction::FAdd: | |
| case Instruction::Sub: | |
| case Instruction::FSub: | |
| case Instruction::Mul: | |
| case Instruction::FMul: | |
| case Instruction::UDiv: | |
| case Instruction::SDiv: | |
| case Instruction::FDiv: | |
| case Instruction::URem: | |
| case Instruction::SRem: | |
| case Instruction::FRem: | |
| case Instruction::Shl: | |
| case Instruction::LShr: | |
| case Instruction::AShr: | |
| case Instruction::And: | |
| case Instruction::Or: | |
| case Instruction::Xor: | |
| case Instruction::Freeze: | |
| case Instruction::Store: | |
| case Instruction::ShuffleVector: | |
| Operands.assign(VL0->getNumOperands(), {VL.size(), nullptr}); | |
| for (auto [Idx, V] : enumerate(VL)) { | |
| auto *I = dyn_cast<Instruction>(V); | |
| if (!I) { | |
| for (auto [OpIdx, Ops] : enumerate(Operands)) | |
| Ops[Idx] = PoisonValue::get(VL0->getOperand(OpIdx)->getType()); | |
| continue; | |
| } | |
| SmallVector<Value *> ConvertedOps = convertTo(I, S).second; | |
| for (auto [OpIdx, Ops] : enumerate(Operands)) | |
| Ops[Idx] = ConvertedOps[OpIdx]; | |
| } | |
| return; | |
| case Instruction::Select: | |
| Operands.assign(VL0->getNumOperands(), {VL.size(), nullptr}); | |
| for (auto [Idx, V] : enumerate(VL)) { | |
| auto *I = dyn_cast<Instruction>(V); | |
| if (!I) { | |
| for (auto [OpIdx, Ops] : enumerate(Operands)) | |
| Ops[Idx] = PoisonValue::get(VL0->getOperand(OpIdx)->getType()); | |
| continue; | |
| } | |
| if (isa<ZExtInst>(I)) { | |
| // Special case for select + zext i1 to avoid explosion of different | |
| // types. We want to keep the condition as i1 to be able to match | |
| // different selects together and reuse the vectorized condition | |
| // rather than trying to gather it. | |
| Operands[0][Idx] = I->getOperand(0); | |
| Operands[1][Idx] = ConstantInt::get(I->getType(), 1); | |
| Operands[2][Idx] = ConstantInt::getNullValue(I->getType()); | |
| continue; | |
| } | |
| SmallVector<Value *> ConvertedOps = convertTo(I, S).second; | |
| for (auto [OpIdx, Ops] : enumerate(Operands)) | |
| Ops[Idx] = ConvertedOps[OpIdx]; | |
| } | |
| return; | |
| case Instruction::GetElementPtr: { | |
| Operands.assign(2, {VL.size(), nullptr}); | |
| for (auto [Idx, V] : enumerate(VL)) { | |
| auto *GEP = dyn_cast<GetElementPtrInst>(V); | |
| Operands[0][Idx] = GEP ? GEP->getPointerOperand() : V; | |
| } | |
| buildGEPIndexOperands(VL, VL0, IsaPred<GetElementPtrInst>, Operands[1]); | |
| return; | |
| } | |
| case Instruction::Call: { | |
| auto *CI = cast<CallInst>(VL0); | |
| Intrinsic::ID ID = getVectorIntrinsicIDForCall(CI, &TLI); | |
| for (unsigned Idx : seq<unsigned>(CI->arg_size())) { | |
| if (isVectorIntrinsicWithScalarOpAtArg(ID, Idx, &TTI)) | |
| continue; | |
| auto &Ops = Operands.emplace_back(); | |
| for (Value *V : VL) { | |
| auto *I = dyn_cast<Instruction>(V); | |
| Ops.push_back(I ? I->getOperand(Idx) | |
| : PoisonValue::get(VL0->getOperand(Idx)->getType())); | |
| } | |
| } | |
| return; | |
| } | |
| default: | |
| break; | |
| } | |
| llvm_unreachable("Unexpected vectorization of the instructions."); | |
| } | |
| /// Check if the specified \p VL list of values is better to represent as | |
| /// uniform with copyables, as modeled via \p CopyableS, or as alternate (or | |
| /// uniform with compatible ops), modeled via \p S. | |
| /// Performs the analysis of the operands, choosing the preferred main | |
| /// instruction and checking the matching of the operands for the main | |
| /// instruction and copyable elements. | |
| bool isCopyablePreferable(ArrayRef<Value *> VL, const BoUpSLP &R, | |
| const InstructionsState &S, | |
| const InstructionsState &CopyableS) { | |
| // If all elements are vectorized already - keep as is. | |
| if (all_of(VL, [&](Value *V) { | |
| return isa<PoisonValue>(V) || R.isVectorized(V); | |
| })) | |
| return false; | |
| Instruction *SMain = S.getMainOp(); | |
| Instruction *SAlt = S.isAltShuffle() ? S.getAltOp() : nullptr; | |
| const bool IsCommutative = isCommutative(SMain); | |
| const bool IsAltCommutative = | |
| S.isAltShuffle() ? isCommutative(SAlt) : false; | |
| const bool IsMainCommutative = isCommutative(MainOp); | |
| SmallVector<BoUpSLP::ValueList> Ops; | |
| buildOriginalOperands(S, SMain, Ops); | |
| // Support only binary operations for now. | |
| if (Ops.size() != 2) | |
| return false; | |
| // Try to find better candidate for S main instruction, which operands have | |
| // better matching. | |
| auto CheckOperands = [](Value *Op, Value *SMainOp) { | |
| auto *OpI = dyn_cast<BinaryOperator>(Op); | |
| if (!OpI) | |
| return false; | |
| auto *SMainOpI = dyn_cast<BinaryOperator>(SMainOp); | |
| if (!SMainOpI) | |
| return true; | |
| return any_of(make_isa_range<Instruction>(OpI->operands()), | |
| [&](Instruction *I) { | |
| return I->getOpcode() == SMainOpI->getOpcode(); | |
| }); | |
| }; | |
| SmallPtrSet<Value *, 8> Operands; | |
| for (Instruction *I : make_isa_range<Instruction>(VL)) { | |
| if (I == SMain) | |
| continue; | |
| Instruction *MatchingOp = S.getMatchingMainOpOrAltOp(I); | |
| if (MatchingOp != SMain) | |
| continue; | |
| SmallVector<BoUpSLP::ValueList> VOps; | |
| buildOriginalOperands(S, I, VOps); | |
| Operands.insert(I->op_begin(), I->op_end()); | |
| assert(VOps.size() == 2 && Ops.size() == 2 && | |
| "Expected binary operations only."); | |
| if (CheckOperands(VOps[0][0], Ops[0][0]) || | |
| CheckOperands(VOps[1][0], Ops[1][0]) || | |
| (IsCommutative && (CheckOperands(VOps[0][0], Ops[1][0]) || | |
| CheckOperands(VOps[1][0], Ops[0][0])))) { | |
| SMain = I; | |
| Ops.swap(VOps); | |
| break; | |
| } | |
| } | |
| SmallVector<BoUpSLP::ValueList> MainOps; | |
| buildOriginalOperands(S, MainOp, MainOps); | |
| auto BuildFirstOperandCandidates = | |
| [&](SmallVectorImpl<std::pair<Value *, Value *>> &Candidates, | |
| ArrayRef<BoUpSLP::ValueList> Ops, Value *Op0, Value *Op1, | |
| bool IsCommutative) { | |
| Candidates.emplace_back(Ops[0][0], Op0); | |
| if (IsCommutative) | |
| Candidates.emplace_back(Ops[0][0], Op1); | |
| }; | |
| auto BuildSecondOperandCandidates = | |
| [&](SmallVectorImpl<std::pair<Value *, Value *>> &Candidates, | |
| ArrayRef<BoUpSLP::ValueList> Ops, int PrevBestIdx, Value *Op0, | |
| Value *Op1, bool IsCommutative) { | |
| if (PrevBestIdx != 1) | |
| Candidates.emplace_back(Ops[1][0], Op1); | |
| if (PrevBestIdx != 0 && IsCommutative) | |
| Candidates.emplace_back(Ops[1][0], Op0); | |
| }; | |
| auto FindBestCandidate = | |
| [&](ArrayRef<std::pair<Value *, Value *>> Candidates, bool &IsConst, | |
| int &Score) { | |
| auto Res = R.findBestRootPair(Candidates); | |
| Score = Res.second; | |
| IsConst = | |
| Res.second == BoUpSLP::LookAheadHeuristics::ScoreConstants && | |
| isConstant(Candidates[Res.first.value_or(0)].first) && | |
| isConstant(Candidates[Res.first.value_or(0)].second); | |
| if (IsConst) { | |
| // Check if there are splat candidates and consider them better | |
| // option. | |
| for (const auto [Idx, P] : enumerate(Candidates)) { | |
| if (!isConstant(P.first) && !isConstant(P.second) && | |
| P.second == P.first) { | |
| Res.first = Idx; | |
| IsConst = false; | |
| Score = isa<LoadInst>(Candidates[Res.first.value_or(0)].first) | |
| ? BoUpSLP::LookAheadHeuristics::ScoreSplatLoads | |
| : BoUpSLP::LookAheadHeuristics::ScoreSplat; | |
| break; | |
| } | |
| } | |
| } | |
| return Res.first; | |
| }; | |
| for (Instruction *I : make_isa_range<Instruction>(VL)) { | |
| if (I == SMain && (I == MainOp || !S.isAltShuffle())) | |
| continue; | |
| SmallVector<BoUpSLP::ValueList> VOps; | |
| buildOriginalOperands(S, I == SMain ? MainOp : I, VOps); | |
| SmallVector<Value *> CopyableOps = | |
| getOperands(CopyableS, I == MainOp ? SMain : I, /*SelfOp=*/false); | |
| if (CopyableOps.size() == VOps.size() && | |
| all_of(zip(CopyableOps, VOps), [&](const auto &P) { | |
| return std::get<0>(P) == std::get<1>(P)[0]; | |
| })) | |
| continue; | |
| SmallVector<std::pair<Value *, Value *>> Candidates; | |
| BuildFirstOperandCandidates(Candidates, MainOps, CopyableOps[0], | |
| CopyableOps[1], IsMainCommutative); | |
| const unsigned OpSize = Candidates.size(); | |
| Instruction *MatchingOp = | |
| S.getMatchingMainOpOrAltOp(I) == S.getMainOp() ? SMain : SAlt; | |
| const bool IsCommutativeInst = | |
| (MatchingOp == SMain ? IsCommutative : IsAltCommutative) || | |
| isCommutative(I, MatchingOp); | |
| if (S.isAltShuffle() && MatchingOp == SAlt && | |
| any_of(VOps, [&](const BoUpSLP::ValueList &Ops) { | |
| auto *I = dyn_cast<BinaryOperator>(Ops[0]); | |
| return I && Operands.contains(I); | |
| })) | |
| return false; | |
| if (S.isAltShuffle() && MatchingOp == SMain) | |
| Operands.insert(I->op_begin(), I->op_end()); | |
| BuildFirstOperandCandidates(Candidates, Ops, VOps[0][0], VOps[1][0], | |
| IsCommutativeInst); | |
| bool IsBestConst; | |
| int Score; | |
| std::optional<int> BestOp = | |
| FindBestCandidate(Candidates, IsBestConst, Score); | |
| const bool IsOriginalBetter = | |
| static_cast<unsigned>(BestOp.value_or(OpSize)) >= OpSize; | |
| Candidates.clear(); | |
| BuildSecondOperandCandidates( | |
| Candidates, MainOps, IsOriginalBetter ? -1 : *BestOp, CopyableOps[0], | |
| CopyableOps[1], IsMainCommutative); | |
| const unsigned SecondOpSize = Candidates.size(); | |
| BuildSecondOperandCandidates( | |
| Candidates, Ops, | |
| IsOriginalBetter ? BestOp.value_or(OpSize - 1) - OpSize : -1, | |
| VOps[0][0], VOps[1][0], IsCommutativeInst); | |
| bool IsSecondBestConst; | |
| int SecondScore; | |
| std::optional<int> SecondBestOp = | |
| FindBestCandidate(Candidates, IsSecondBestConst, SecondScore); | |
| // No best candidates. | |
| if (!BestOp && !SecondBestOp) | |
| return false; | |
| // Original better in both ops combinations. | |
| const bool IsSecondOriginalBetter = | |
| static_cast<unsigned>(SecondBestOp.value_or(SecondOpSize)) >= | |
| SecondOpSize; | |
| if (IsOriginalBetter && IsSecondOriginalBetter) | |
| return false; | |
| // Original is better in second combination, but in the first combination | |
| // no best candidates. | |
| if (!BestOp && IsSecondOriginalBetter) | |
| return false; | |
| // Original is better in first combination, but in the second combination | |
| // no best candidates. | |
| if (!SecondBestOp && IsOriginalBetter) | |
| return false; | |
| // Copyable is best in the first combination, but it is constant, but | |
| // original is better in second non-constant combination. | |
| if (!IsOriginalBetter && IsBestConst && IsSecondOriginalBetter && | |
| !IsSecondBestConst) | |
| return false; | |
| // Copyable is best in the second combination, but it is constant, but | |
| // original is better in the first non-constant combination. | |
| if (BestOp && IsOriginalBetter && !IsBestConst && | |
| !IsSecondOriginalBetter && IsSecondBestConst) | |
| return false; | |
| // Original combination score is better. | |
| if (((Score > SecondScore || | |
| (Score <= BoUpSLP::LookAheadHeuristics::ScoreAltOpcodes && | |
| Score == SecondScore)) && | |
| IsOriginalBetter) || | |
| (IsSecondOriginalBetter && | |
| (SecondScore > Score || | |
| (Score <= BoUpSLP::LookAheadHeuristics::ScoreAltOpcodes && | |
| Score == SecondScore)))) | |
| return false; | |
| } | |
| return true; | |
| } | |
| public: | |
| InstructionsCompatibilityAnalysis(DominatorTree &DT, const DataLayout &DL, | |
| const TargetTransformInfo &TTI, | |
| const TargetLibraryInfo &TLI) | |
| : DT(DT), DL(DL), TTI(TTI), TLI(TLI) {} | |
| InstructionsState buildInstructionsState(ArrayRef<Value *> VL, | |
| const BoUpSLP &R, | |
| bool WithProfitabilityCheck = false, | |
| bool SkipSameCodeCheck = false) { | |
| InstructionsState S = (SkipSameCodeCheck || !allSameBlock(VL)) | |
| ? InstructionsState::invalid() | |
| : getSameOpcode(VL, TLI); | |
| // Check if series of selects + zext i1 %x to in can be combined into | |
| // selects + select %x, i32 1, i32 0. | |
| Instruction *SelectOp = nullptr; | |
| if (!S && allSameBlock(VL) && all_of(VL, [&](Value *V) { | |
| if (match(V, m_Select(m_Value(), m_Value(), m_Value()))) { | |
| if (!SelectOp) | |
| SelectOp = cast<Instruction>(V); | |
| return true; | |
| } | |
| auto *ZExt = dyn_cast<ZExtInst>(V); | |
| return (ZExt && ZExt->getSrcTy()->isIntegerTy(1)) || | |
| isa<PoisonValue>(V); | |
| })) { | |
| if (SelectOp) | |
| return InstructionsState(SelectOp, SelectOp); | |
| } | |
| // The undef lanes among the loads are the copyable elements, covered by a | |
| // single wide load. | |
| if (!S && R.Opts.slp_copyable_elements && | |
| all_of(VL, IsaPred<LoadInst, UndefValue>) && any_of(VL, [](Value *V) { | |
| return isa<UndefValue>(V) && !isa<PoisonValue>(V); | |
| })) { | |
| SmallVector<Value *> Loads(make_isa_range<LoadInst>(VL)); | |
| if (Loads.size() > 1 && allSameBlock(Loads)) { | |
| auto *LI = cast<LoadInst>(Loads.front()); | |
| return InstructionsState(LI, LI, /*HasCopyables=*/true); | |
| } | |
| } | |
| if (S && S.isAltShuffle()) { | |
| Type *ScalarTy = S.getMainOp()->getType(); | |
| auto *VecTy = cast<VectorType>(getWidenedType(ScalarTy, VL.size())); | |
| unsigned Opcode0 = S.getOpcode(); | |
| unsigned Opcode1 = S.getAltOpcode(); | |
| SmallBitVector OpcodeMask( | |
| getAltInstrMask(VL, ScalarTy, Opcode0, Opcode1)); | |
| // If this pattern is supported by the target then we consider the order. | |
| if (TTI.isLegalAltInstr(VecTy, Opcode0, Opcode1, OpcodeMask)) | |
| return S; | |
| } else if (S && | |
| (!R.Opts.slp_copyable_elements || | |
| !isSupportedMainOp(S.getMainOp()) || | |
| all_of(make_isa_range<Instruction>(VL), [&](Instruction *I) { | |
| return I->getOpcode() == S.getOpcode() || | |
| (S.getOpcode() == Instruction::Add && | |
| I->getOpcode() == Instruction::Shl); | |
| }))) { | |
| return S; | |
| } | |
| if (!R.Opts.slp_copyable_elements) | |
| return S; | |
| AbsorbCopyableFMulOrFAdds = hasOnlyAbsorbableCopyableFMulOrFAdds(VL); | |
| findAndSetMainInstruction(VL, R); | |
| if (!MainOp) | |
| return S; | |
| InstructionsState OrigS = S; | |
| S = InstructionsState(MainOp, MainOp, /*HasCopyables=*/true); | |
| // Cast nodes support only matching lanes from the main op block and | |
| // copyable (round-trippable constant) lanes. | |
| if (isa<CastInst>(MainOp) && any_of(VL, [&](Value *V) { | |
| auto *I = dyn_cast<Instruction>(V); | |
| return !isa<PoisonValue>(V) && | |
| !(I && I->getOpcode() == MainOpcode && | |
| I->getParent() == MainOp->getParent()) && | |
| !S.isCopyableElement(V); | |
| })) | |
| return OrigS; | |
| if (OrigS && !isCopyablePreferable(VL, R, OrigS, S)) | |
| return OrigS; | |
| if (!WithProfitabilityCheck) | |
| return S; | |
| // ExtractElement copyable nodes reuse the source vector and insert the | |
| // copyable lanes; the binary-operator operand heuristics below do not | |
| // apply, so defer profitability to the full tree cost. | |
| if (isa<ExtractElementInst>(MainOp)) { | |
| // A load as a copyable lane would be pulled out of the consecutive-load | |
| // vectorization; keep the original state so the node can be split. | |
| if (any_of(VL, [&](Value *V) { | |
| return S.isCopyableElement(V) && isa<LoadInst>(V); | |
| })) | |
| return OrigS; | |
| return S; | |
| } | |
| // Check if it is profitable to vectorize the instruction. | |
| unsigned CopyableNum = | |
| count_if(VL, [&](Value *V) { return S.isCopyableElement(V); }); | |
| // A cast copyable node just extends the sources of the matching lanes: | |
| // the constant lanes are free in a gather of the original values, so it | |
| // cannot be better unless the matching lanes are the majority and are not | |
| // vectorized already (the gather reuses the vector otherwise). | |
| if (isa<CastInst>(MainOp) && | |
| (CopyableNum * 2 >= VL.size() || none_of(VL, [&](Value *V) { | |
| return !S.isCopyableElement(V) && !isa<PoisonValue>(V) && | |
| !R.isVectorized(V); | |
| }))) | |
| return OrigS; | |
| // Absorb copyable single-use fmuls/fadds as fmuladd(a, b, -0.0) or | |
| // fmuladd(1.0, a, b) when every copyable is such a binop: the binops die | |
| // instead of being computed and gathered. | |
| if (RecurrenceDescriptor::isFMulAddIntrinsic(MainOp) && | |
| AbsorbCopyableFMulOrFAdds) | |
| S.setAbsorbCopyableFMulOrFAdd(true); | |
| SmallVector<BoUpSLP::ValueList> Operands = buildOperands(S, VL, R); | |
| auto CheckOperand = [&](ArrayRef<Value *> Ops) { | |
| if (allConstant(Ops) || isSplat(Ops)) | |
| return true; | |
| // Non-instruction operands of a call (args, constants) are always a | |
| // trivial gather, same as the constant/splat cases above. | |
| if (MainOpcode == Instruction::Call && none_of(Ops, IsaPred<Instruction>)) | |
| return true; | |
| // Check if it is "almost" splat, i.e. has >= 4 elements and only single | |
| // one is different. | |
| constexpr unsigned Limit = 4; | |
| if (Operands.front().size() >= Limit) { | |
| SmallDenseMap<const Value *, unsigned> Counters; | |
| auto IsNotUndef = [](Value *V) { return !isa<UndefValue>(V); }; | |
| for (Value *V : make_filter_range(Ops, IsNotUndef)) | |
| ++Counters[V]; | |
| if (Counters.size() == 2 && | |
| any_of(Counters, [&](const auto &C) { return C.second == 1; })) | |
| return true; | |
| } | |
| // First operand not a constant or splat? Last attempt - check for | |
| // potential vectorization. | |
| InstructionsCompatibilityAnalysis Analysis(DT, DL, TTI, TLI); | |
| InstructionsState OpS = Analysis.buildInstructionsState(Ops, R); | |
| if (!OpS || (OpS.getOpcode() == Instruction::PHI && !allSameBlock(Ops))) | |
| return false; | |
| unsigned CopyableNum = | |
| count_if(Ops, [&](Value *V) { return OpS.isCopyableElement(V); }); | |
| return CopyableNum <= VL.size() / 2; | |
| }; | |
| // A copyable GEP node is a vector GEP over the base pointers of the | |
| // matching lanes and the values of the copyable lanes. It is profitable | |
| // only if that base vector is cheap (a splat, constants or vectorizable), | |
| // otherwise it is a gather not cheaper than the gather of the pointers. | |
| if (MainOpcode == Instruction::GetElementPtr && | |
| !CheckOperand(Operands.front())) | |
| return OrigS; | |
| auto BuildCandidates = | |
| [](SmallVectorImpl<std::pair<Value *, Value *>> &Candidates, Value *V1, | |
| Value *V2) { | |
| if (V1 != V2 && isa<PHINode>(V1)) | |
| return; | |
| auto *I1 = dyn_cast<Instruction>(V1); | |
| auto *I2 = dyn_cast<Instruction>(V2); | |
| if (I1 && I2 && I1->getOpcode() == I2->getOpcode() && | |
| I1->getParent() != I2->getParent()) | |
| return; | |
| Candidates.emplace_back(V1, (I1 || I2) ? V2 : V1); | |
| }; | |
| if (VL.size() == 2) { | |
| // The operand-pairing heuristic below does not apply to calls, GEPs | |
| // and casts; defer to the full tree cost computation instead of | |
| // pre-rejecting here. | |
| if (MainOpcode == Instruction::Call || | |
| MainOpcode == Instruction::GetElementPtr || isa<CastInst>(MainOp)) | |
| return S; | |
| // Check if the operands allow better vectorization. | |
| SmallVector<std::pair<Value *, Value *>, 4> Candidates1, Candidates2; | |
| BuildCandidates(Candidates1, Operands[0][0], Operands[0][1]); | |
| BuildCandidates(Candidates2, Operands[1][0], Operands[1][1]); | |
| bool Res = !Candidates1.empty() && !Candidates2.empty() && | |
| R.findBestRootPair(Candidates1).first && | |
| R.findBestRootPair(Candidates2).first; | |
| if (!Res && isCommutative(MainOp)) { | |
| Candidates1.clear(); | |
| Candidates2.clear(); | |
| BuildCandidates(Candidates1, Operands[0][0], Operands[1][1]); | |
| BuildCandidates(Candidates2, Operands[1][0], Operands[0][1]); | |
| Res = !Candidates1.empty() && !Candidates2.empty() && | |
| R.findBestRootPair(Candidates1).first && | |
| R.findBestRootPair(Candidates2).first; | |
| } | |
| if (!Res) | |
| return OrigS; | |
| const TTI::TargetCostKind Kind = R.getCostKind(); | |
| InstructionCost ScalarCost = TTI.getInstructionCost(S.getMainOp(), Kind); | |
| InstructionCost VectorCost; | |
| auto *VecTy = getWidenedType(S.getMainOp()->getType(), VL.size()); | |
| switch (MainOpcode) { | |
| case Instruction::Add: | |
| case Instruction::Sub: | |
| case Instruction::Mul: | |
| case Instruction::AShr: | |
| case Instruction::LShr: | |
| case Instruction::Shl: | |
| case Instruction::SDiv: | |
| case Instruction::UDiv: | |
| case Instruction::And: | |
| case Instruction::Or: | |
| case Instruction::Xor: | |
| case Instruction::FAdd: | |
| case Instruction::FMul: | |
| case Instruction::FSub: | |
| case Instruction::FDiv: | |
| VectorCost = TTI.getArithmeticInstrCost( | |
| MainOpcode, VecTy, Kind, | |
| TTI::commonOperandInfo(Operands[0][0], Operands[0][1]), | |
| TTI::commonOperandInfo(Operands[1][0], Operands[1][1]), {}, | |
| S.getMainOp(), &TLI); | |
| break; | |
| default: | |
| // Calls (min/max, fmuladd) return above before reaching this switch. | |
| llvm_unreachable("Unexpected instruction."); | |
| } | |
| if (VectorCost > ScalarCost) | |
| return OrigS; | |
| return S; | |
| } | |
| // Casts are the only 1-operand and fmuladd is the only 3-operand | |
| // copyable. | |
| assert((Operands.size() == 2 || | |
| (Operands.size() == 1 && isa<CastInst>(MainOp)) || | |
| (Operands.size() == 3 && | |
| RecurrenceDescriptor::isFMulAddIntrinsic(MainOp))) && | |
| "Unexpected number of operands!"); | |
| if (CopyableNum < VL.size() / 2) | |
| return S; | |
| // Too many phi copyables - exit. | |
| const unsigned Limit = VL.size() / 24; | |
| if ((CopyableNum >= VL.size() - Limit || | |
| (CopyableNum >= VL.size() - 1 && VL.size() > 4) || | |
| CopyableNum >= MaxPHINumOperands) && | |
| all_of(VL, [&](Value *V) { | |
| return isa<PHINode>(V) || !S.isCopyableElement(V); | |
| })) | |
| return OrigS; | |
| // Check profitability if number of copyables > VL.size() / 2. | |
| // 1. Reorder operands for better matching. | |
| if (Operands.size() == 2 && isCommutative(MainOp)) { | |
| Value *BestFrontOp = nullptr; | |
| for (auto [OpL, OpR] : zip(Operands.front(), Operands.back())) { | |
| // Make instructions the first operands. | |
| if (!isa<Instruction>(OpL) && isa<Instruction>(OpR)) { | |
| BestFrontOp = OpR; | |
| std::swap(OpL, OpR); | |
| continue; | |
| } | |
| // Make constants the second operands. | |
| if ((isa<Constant>(OpL) && !match(OpR, m_Zero())) || | |
| match(OpL, m_Zero())) { | |
| if (isa<Instruction>(OpR)) | |
| BestFrontOp = OpR; | |
| std::swap(OpL, OpR); | |
| continue; | |
| } | |
| if (isa<Instruction>(OpL)) | |
| BestFrontOp = OpL; | |
| } | |
| // If some of the RHS operands better match most of LHS - swap such | |
| // operands to increase matching rate. | |
| if (auto *BestLHS = dyn_cast_if_present<Instruction>(BestFrontOp)) { | |
| const unsigned BestOpcode = BestLHS->getOpcode(); | |
| for (auto [OpL, OpR] : zip(Operands.front(), Operands.back())) { | |
| auto *OpRI = dyn_cast<Instruction>(OpR); | |
| if (!OpRI) | |
| continue; | |
| if (OpRI->getOpcode() == BestOpcode) | |
| std::swap(OpL, OpR); | |
| } | |
| } | |
| } | |
| // 2. Check, if operands can be vectorized. Skip for fmuladd; the | |
| // copyable operand is checked below and may legitimately hold many | |
| // instructions. | |
| if (Operands.size() == 2 && | |
| count_if(Operands.back(), IsaPred<Instruction>) > 1) | |
| return OrigS; | |
| // Check the operand holding the copyable values. | |
| if (!CheckOperand(Operands[S.getCopyableOpIdx()])) { | |
| if (Operands.size() == 2) | |
| return OrigS; | |
| // Retry with the copyable modeled as the first multiplicand. | |
| S.setCopyableOpIdx(0); | |
| Operands = buildOperands(S, VL, R); | |
| if (!CheckOperand(Operands[S.getCopyableOpIdx()])) | |
| return OrigS; | |
| } | |
| return S; | |
| } | |
| SmallVector<BoUpSLP::ValueList> | |
| buildOperands(const InstructionsState &S, ArrayRef<Value *> VL, | |
| const BoUpSLP &R, bool AllowPoisonPlaceholders = true) { | |
| assert(S && "Invalid state!"); | |
| SmallVector<BoUpSLP::ValueList> Operands; | |
| if (S.areInstructionsWithCopyableElements()) { | |
| MainOp = S.getMainOp(); | |
| MainOpcode = S.getOpcode(); | |
| // ExtractElement copyable nodes carry a single operand (the shared | |
| // source vector); copyable lanes are inserted during codegen and do | |
| // not contribute an operand column. | |
| if (MainOpcode == Instruction::ExtractElement) { | |
| Operands.assign(1, | |
| BoUpSLP::ValueList(VL.size(), MainOp->getOperand(0))); | |
| return Operands; | |
| } | |
| // The undef lanes of the loads have no pointer operands. | |
| if (MainOpcode == Instruction::Load) { | |
| buildOriginalOperands(S, VL, Operands); | |
| return Operands; | |
| } | |
| // Excludes the trailing callee operand (2 for min/max, 3 for fmuladd). | |
| // getNumberOfPotentiallyCommutativeOps collapses fmuladd to 2 and must | |
| // not be used here. Only the 2-operand case is commutative-normalized. | |
| auto *CI = dyn_cast<CallInst>(MainOp); | |
| const unsigned NumMainOpOperands = | |
| CI ? CI->arg_size() : MainOp->getNumOperands(); | |
| const bool IsCommutative = | |
| isCommutative(MainOp) && NumMainOpOperands == 2; | |
| Operands.assign(NumMainOpOperands, | |
| BoUpSLP::ValueList(VL.size(), nullptr)); | |
| SmallPtrSet<const Value *, 4> SelfOpLanes = findSelfOpLanes(S, VL, R); | |
| const bool PoisonPlaceholders = | |
| AllowPoisonPlaceholders && canUsePoisonPlaceholders(S, VL); | |
| // Populate operands for every lane. | |
| for (auto [Idx, V] : enumerate(VL)) { | |
| SmallVector<Value *> OperandsForValue = | |
| getOperands(S, V, SelfOpLanes.contains(V), PoisonPlaceholders); | |
| for (auto [OperandIdx, Operand] : enumerate(OperandsForValue)) | |
| Operands[OperandIdx][Idx] = Operand; | |
| } | |
| // The copyable lanes of a GEP node are gep V, 0; cast the indices of | |
| // the matching lanes and the zeroes to the common index type. | |
| if (MainOpcode == Instruction::GetElementPtr) | |
| buildGEPIndexOperands( | |
| VL, MainOp, | |
| [&](Value *V) { | |
| return isa<GetElementPtrInst>(V) && !S.isCopyableElement(V); | |
| }, | |
| Operands[1]); | |
| // Operand-order normalization below swaps OpIdx 0 and OpIdx 1 | |
| // of non-copyable lanes. That is only safe when the main op is | |
| // commutative (e.g. 0 - X is not X - 0, so `sub` must be | |
| // excluded). With absorbed fmul/fadd copyables the fmuladd | |
| // multiplicands are commutative per lane and get normalized too; | |
| // the 0/1 swaps never touch the addend column. | |
| if (IsCommutative || S.hasAbsorbedCopyableFMulOrFAdd()) { | |
| // IsCommutative can hold for MainOp (e.g. a Sub/FSub feeding only | |
| // fabs/icmp-eq-0) without every lane sharing that property, so | |
| // re-check the specific lane before swapping it. Absorbed fmul/fadd | |
| // lanes are always commutative. | |
| auto CanSwap = [&](Value *V) { | |
| if (S.hasAbsorbedCopyableFMulOrFAdd() && | |
| isAbsorbableCopyableFMulOrFAdd(S, V)) | |
| return true; | |
| return isCommutative(S.getMatchingMainOpOrAltOp(cast<Instruction>(V)), | |
| V); | |
| }; | |
| // Absorbed fmul/fadd copyables do not vote for the majority operand | |
| // pattern (their multiplicand order is arbitrary) but take part | |
| // in the swaps. | |
| auto SwappableLane = [&](Value *V) { | |
| return !isa<PoisonValue>(V) && | |
| (!S.isCopyableElement(V) || | |
| (S.hasAbsorbedCopyableFMulOrFAdd() && | |
| isAbsorbableCopyableFMulOrFAdd(S, V))); | |
| }; | |
| // Count (ID0, ID1) pair frequencies for operand normalization. | |
| // Pairs and their inverses are tracked under a canonical key | |
| // so that (Load, Add) and (Add, Load) contribute to the same | |
| // bucket. | |
| struct PairInfo { | |
| unsigned FwdCount = 0; | |
| unsigned RevCount = 0; | |
| }; | |
| SmallMapVector<std::pair<unsigned, unsigned>, PairInfo, 8> PairCounts; | |
| SmallMapVector<unsigned, unsigned, 4> AddendIDCounts; | |
| unsigned MajID0 = 0, MajID1 = 0; | |
| for (const auto &P : | |
| make_filter_range(enumerate(VL), [&](const auto &P) { | |
| return !S.isCopyableElement(P.value()) && | |
| !isa<PoisonValue>(P.value()); | |
| })) { | |
| size_t Idx = P.index(); | |
| unsigned ID0 = Operands[0][Idx]->getValueID(); | |
| unsigned ID1 = Operands[1][Idx]->getValueID(); | |
| if (S.hasAbsorbedCopyableFMulOrFAdd()) | |
| ++AddendIDCounts[Operands[2][Idx]->getValueID()]; | |
| if (ID0 == ID1) | |
| continue; | |
| unsigned MinID = std::min(ID0, ID1); | |
| unsigned MaxID = std::max(ID0, ID1); | |
| auto *It = PairCounts.try_emplace(std::make_pair(MinID, MaxID)).first; | |
| PairInfo &Info = It->second; | |
| if (ID0 < ID1) | |
| ++Info.FwdCount; | |
| else | |
| ++Info.RevCount; | |
| } | |
| // Find the most frequent (ID0, ID1) pair across non-copyable | |
| // lanes. Select the orientation (original or inverse) that | |
| // has more votes as the majority pattern. | |
| unsigned BestCount = 0; | |
| for (const auto &P : PairCounts) { | |
| const PairInfo &Info = P.second; | |
| unsigned Total = Info.FwdCount + Info.RevCount; | |
| if (Total > BestCount) { | |
| BestCount = Total; | |
| if (Info.FwdCount >= Info.RevCount) { | |
| MajID0 = P.first.first; | |
| MajID1 = P.first.second; | |
| } else { | |
| MajID0 = P.first.second; | |
| MajID1 = P.first.first; | |
| } | |
| } | |
| } | |
| // Absorbed fadd copyables are fmuladd(1.0, a, b): pick the | |
| // addend/multiplicand assignment matching the majority operand | |
| // kinds of the non-copyable lanes in the multiplicand (1) and | |
| // addend (2) columns. | |
| if (S.hasAbsorbedCopyableFMulOrFAdd()) { | |
| unsigned MajID2 = 0, Best2 = 0; | |
| for (const auto &P : AddendIDCounts) { | |
| if (P.second > Best2) { | |
| Best2 = P.second; | |
| MajID2 = P.first; | |
| } | |
| } | |
| for (auto [Idx, V] : enumerate(VL)) { | |
| auto *I = dyn_cast<Instruction>(V); | |
| if (!I || I->getOpcode() != Instruction::FAdd || | |
| !isAbsorbableCopyableFMulOrFAdd(S, I)) | |
| continue; | |
| unsigned ID1 = Operands[1][Idx]->getValueID(); | |
| unsigned ID2 = Operands[2][Idx]->getValueID(); | |
| unsigned Cur = (ID1 == MajID1) + (ID2 == MajID2); | |
| unsigned Swapped = (ID2 == MajID1) + (ID1 == MajID2); | |
| if (Swapped > Cur) | |
| std::swap(Operands[1][Idx], Operands[2][Idx]); | |
| } | |
| } | |
| // Normalize swappable lanes in two steps: | |
| // 1) Swap lanes whose operand types are the exact inverse of | |
| // the majority pattern, making the non-copyable lanes | |
| // consistent. | |
| // 2) Independently, if a strict majority of swappable lanes | |
| // have loads at OpIdx 1, swap those lanes to put loads at | |
| // OpIdx 0 for better downstream vectorization. | |
| unsigned LAt0 = 0, LAt1 = 0, TotalNC = 0; | |
| for (auto [Idx, V] : | |
| make_filter_range(enumerate(VL), [&](const auto &P) { | |
| return SwappableLane(P.value()); | |
| })) { | |
| // Step 1: swap exact-inverse lanes. | |
| if (BestCount > 0) { | |
| unsigned ID0 = Operands[0][Idx]->getValueID(); | |
| unsigned ID1 = Operands[1][Idx]->getValueID(); | |
| if (ID0 == MajID1 && ID1 == MajID0 && CanSwap(V)) | |
| std::swap(Operands[0][Idx], Operands[1][Idx]); | |
| } | |
| ++TotalNC; | |
| LAt0 += isa<LoadInst>(Operands[0][Idx]); | |
| LAt1 += isa<LoadInst>(Operands[1][Idx]); | |
| } | |
| // Step 2: if most non-copyable lanes have loads at OpIdx 1, | |
| // swap those lanes to put loads at OpIdx 0. | |
| if (TotalNC > 1 && LAt1 > LAt0 && LAt1 * 2 > TotalNC) { | |
| for (auto [Idx, V] : | |
| make_filter_range(enumerate(VL), [&](const auto &P) { | |
| return SwappableLane(P.value()); | |
| })) { | |
| if (!isa<LoadInst>(Operands[0][Idx]) && | |
| isa<LoadInst>(Operands[1][Idx]) && CanSwap(V)) | |
| std::swap(Operands[0][Idx], Operands[1][Idx]); | |
| } | |
| } | |
| } | |
| } else { | |
| buildOriginalOperands(S, VL, Operands); | |
| } | |
| return Operands; | |
| } | |
| }; | |
| } // namespace | |
| void BoUpSLP::tryToVectorizeSplatGatheredScalars() { | |
| unsigned PrevTreeSize = VectorizableTree.size(); | |
| auto LoadsSubkey = [](size_t /*Key*/, LoadInst *LI) { | |
| return hash_value(getUnderlyingObject(LI->getPointerOperand())); | |
| }; | |
| // The key includes the value type: the opcode-based key does not always | |
| // distinguish types (e.g. all extractvalue instructions share one key), | |
| // while the tree requires same-typed scalars. | |
| SmallMapVector<std::tuple<size_t, size_t, Type *>, SmallSetVector<Value *, 4>, | |
| 4> | |
| Groups; | |
| // Only gathers with vectorized (non-gather) users can reuse the broadcast. | |
| for (const std::unique_ptr<TreeEntry> &TE : make_filter_range( | |
| VectorizableTree, [&](const std::unique_ptr<TreeEntry> &TE) { | |
| return TE->isGather() && TE->UserTreeIndex && | |
| !TE->UserTreeIndex.UserTE->isGather() && | |
| isSplat(TE->Scalars); | |
| })) { | |
| auto *I = dyn_cast<Instruction>(TE->Scalars.front()); | |
| // Skip shuffle-like instructions: their splat gathers are already emitted | |
| // as cheap shuffles of the source vector. | |
| if (!I || | |
| isa<ExtractElementInst, InsertElementInst, ShuffleVectorInst>(I) || | |
| I->getType()->isVoidTy() || isVectorized(I) || isDeleted(I) || | |
| (UserIgnoreList && UserIgnoreList->contains(I))) | |
| continue; | |
| // Scheduling new memory bundles in a to-be-versioned tree records extra | |
| // alias-check pairs and can push the region over the versioning limits. | |
| if (isTryingRuntimeAliasChecks() && I->mayReadOrWriteMemory()) | |
| continue; | |
| auto [Key, SubKey] = | |
| generateKeySubkey(I, TLI, LoadsSubkey, /*AllowAlternate=*/true); | |
| Groups[std::make_tuple(Key, SubKey, I->getType())].insert(I); | |
| } | |
| // Values left in singleton groups cannot form a bundle on their own; | |
| // regroup them by the opcode-insensitive key so alternate/copyable | |
| // bundles still form. | |
| SmallMapVector<std::pair<size_t, Type *>, SmallSetVector<Value *, 4>, 4> | |
| FallbackGroups; | |
| for (auto &[Key, Group] : make_filter_range( | |
| Groups, [](const auto &P) { return P.second.size() < 2; })) { | |
| FallbackGroups[std::make_pair(std::get<0>(Key), Group.front()->getType())] | |
| .insert(Group.front()); | |
| } | |
| InstructionsCompatibilityAnalysis Analysis(*DT, *DL, *TTI, *TLI); | |
| auto BuildSubtree = [&](const auto &GroupMap) { | |
| auto IsMultiElement = [](const auto &P) { return P.second.size() >= 2; }; | |
| for (const auto &Entry : make_filter_range(GroupMap, IsMultiElement)) { | |
| const auto &Group = Entry.second; | |
| // Copyable-aware check so bundles with copyable lanes are not skipped. | |
| if (!Analysis.buildInstructionsState(Group.getArrayRef(), *this)) | |
| continue; | |
| unsigned PrevSize = VectorizableTree.size(); | |
| buildTreeRec(Group.getArrayRef(), 0, EdgeInfo()); | |
| if (PrevSize == VectorizableTree.size()) | |
| continue; | |
| TreeEntry *NewRoot = VectorizableTree[PrevSize].get(); | |
| if (NewRoot->isGather()) { | |
| // Failed to vectorize the bundle: drop the added gather entry, it has | |
| // no users and only adds cost. | |
| for (Value *V : NewRoot->Scalars) { | |
| auto It = ValueToGatherNodes.find(V); | |
| if (It != ValueToGatherNodes.end()) | |
| It->second.remove(NewRoot); | |
| } | |
| LoadEntriesToVectorize.remove(PrevSize); | |
| VectorizableTree.pop_back(); | |
| continue; | |
| } | |
| // The root has no users, the gathers reuse its lanes by value: keep the | |
| // loads in memory order instead of permuting them into the group order. | |
| if (NewRoot->State == TreeEntry::Vectorize && | |
| NewRoot->getOpcode() == Instruction::Load) | |
| NewRoot->ReorderIndices.clear(); | |
| SplatGatheredScalarsRoots.push_back(NewRoot); | |
| } | |
| }; | |
| BuildSubtree(Groups); | |
| BuildSubtree(FallbackGroups); | |
| NumCanonicalSplatSubtreeEntries = VectorizableTree.size() - PrevTreeSize; | |
| } | |
| BoUpSLP::ScalarsVectorizationLegality | |
| BoUpSLP::getScalarsVectorizationLegality(ArrayRef<Value *> VL, unsigned Depth, | |
| const EdgeInfo &UserTreeIdx) const { | |
| assert((allConstant(VL) || allSameType(VL)) && "Invalid types!"); | |
| InstructionsCompatibilityAnalysis Analysis(*DT, *DL, *TTI, *TLI); | |
| InstructionsState S = Analysis.buildInstructionsState( | |
| VL, *this, /*WithProfitabilityCheck=*/true); | |
| bool AreScatterAllGEPSameBlock = false; | |
| if (!S) { | |
| SmallVector<unsigned> SortedIndices; | |
| BasicBlock *BB = nullptr; | |
| bool IsScatterVectorizeUserTE = | |
| UserTreeIdx.UserTE && | |
| UserTreeIdx.UserTE->State == TreeEntry::ScatterVectorize; | |
| AreScatterAllGEPSameBlock = | |
| (IsScatterVectorizeUserTE && VL.front()->getType()->isPointerTy() && | |
| VL.size() > 2 && | |
| all_of(VL, | |
| [&BB](Value *V) { | |
| auto *I = dyn_cast<GetElementPtrInst>(V); | |
| if (!I) | |
| return doesNotNeedToBeScheduled(V); | |
| if (!BB) | |
| BB = I->getParent(); | |
| return BB == I->getParent() && I->getNumOperands() == 2; | |
| }) && | |
| BB && | |
| sortPtrAccesses(VL, UserTreeIdx.UserTE->getMainOp()->getType(), *DL, | |
| *SE, SortedIndices)); | |
| if (!AreScatterAllGEPSameBlock) { | |
| LLVM_DEBUG(dbgs() << "SLP: Try split and if failed, gathering due to " | |
| "C,S,B,O, small shuffle. \n"; | |
| dbgs() << "["; | |
| interleaveComma(VL, dbgs(), [&](Value *V) { dbgs() << *V; }); | |
| dbgs() << "]\n"); | |
| return ScalarsVectorizationLegality(S, /*IsLegal=*/false, | |
| /*TryToFindDuplicates=*/true, | |
| /*TrySplitVectorize=*/true); | |
| } | |
| // Reset S to make it GetElementPtr kind of node. | |
| const auto *It = find_if(VL, IsaPred<GetElementPtrInst>); | |
| assert(It != VL.end() && "Expected at least one GEP."); | |
| S = getSameOpcode(*It, *TLI); | |
| } | |
| assert(S && "Must be valid."); | |
| // Gather very wide PHI bundles. Wide PHIs (e.g. produced by | |
| // jump threading) are not profitable to vectorize and make this analysis | |
| // explode, so gather them to keep the compile time bounded. | |
| if (S.getOpcode() == Instruction::PHI) { | |
| unsigned NumIncomingValues = | |
| cast<PHINode>(S.getMainOp())->getNumIncomingValues(); | |
| if (static_cast<uint64_t>(VL.size()) * NumIncomingValues > | |
| Opts.slp_phi_vectorization_budget) { | |
| LLVM_DEBUG(dbgs() << "SLP: Gathering due to wide PHI operand fan-out (" | |
| << VL.size() << " lanes x " << NumIncomingValues | |
| << " incoming values).\n"); | |
| return ScalarsVectorizationLegality(S, /*IsLegal=*/false); | |
| } | |
| } | |
| // Don't handle vectors. | |
| if (!Opts.slp_revec && | |
| getValueType(VL.front(), Opts.slp_revec)->isVectorTy()) { | |
| LLVM_DEBUG(dbgs() << "SLP: Gathering due to vector type.\n"); | |
| // Do not try to pack to avoid extra instructions here. | |
| return ScalarsVectorizationLegality(S, /*IsLegal=*/false, | |
| /*TryToFindDuplicates=*/false); | |
| } | |
| // Check that all of the users of the scalars that we want to vectorize are | |
| // schedulable. | |
| BasicBlock *BB = S.getMainOp()->getParent(); | |
| if (BB->isEHPad() || isa<UnreachableInst>(BB->getTerminator()) || | |
| !DT->isReachableFromEntry(BB)) { | |
| // Don't go into unreachable blocks. They may contain instructions with | |
| // dependency cycles which confuse the final scheduling. | |
| // Do not vectorize EH and non-returning blocks, not profitable in most | |
| // cases. | |
| LLVM_DEBUG(dbgs() << "SLP: bundle in unreachable block.\n"); | |
| return ScalarsVectorizationLegality(S, /*IsLegal=*/false); | |
| } | |
| // Don't go into catchswitch blocks, which can happen with PHIs. | |
| // Such blocks can only have PHIs and the catchswitch. There is no | |
| // place to insert a shuffle if we need to, so just avoid that issue. | |
| if (isa<CatchSwitchInst>(BB->getTerminator())) { | |
| LLVM_DEBUG(dbgs() << "SLP: bundle in catchswitch block.\n"); | |
| // Do not try to pack to avoid extra instructions here. | |
| return ScalarsVectorizationLegality(S, /*IsLegal=*/false, | |
| /*TryToFindDuplicates=*/false); | |
| } | |
| // Don't handle scalable vectors | |
| if (S.getOpcode() == Instruction::ExtractElement && | |
| isa<ScalableVectorType>( | |
| cast<ExtractElementInst>(S.getMainOp())->getVectorOperandType())) { | |
| LLVM_DEBUG(dbgs() << "SLP: Gathering due to scalable vector type.\n"); | |
| return ScalarsVectorizationLegality(S, /*IsLegal=*/false); | |
| } | |
| // Gather if we hit the RecursionMaxDepth, unless this is a load (or z/sext of | |
| // a load), in which case peek through to include it in the tree, without | |
| // ballooning over-budget. | |
| if (Depth >= Opts.slp_recursion_max_depth && | |
| (S.isAltShuffle() || VL.size() < 4 || | |
| !(match(S.getMainOp(), m_Load(m_Value())) || | |
| all_of(VL, [&S](const Value *I) { | |
| return match(I, | |
| m_OneUse(m_ZExtOrSExt(m_OneUse(m_Load(m_Value()))))) && | |
| cast<Instruction>(I)->getOpcode() == S.getOpcode(); | |
| })))) { | |
| LLVM_DEBUG(dbgs() << "SLP: Gathering due to max recursion depth.\n"); | |
| return ScalarsVectorizationLegality(S, /*IsLegal=*/false); | |
| } | |
| // Check if this is a duplicate of another entry. | |
| LLVM_DEBUG(dbgs() << "SLP: \tChecking bundle: " << *S.getMainOp() << ".\n"); | |
| // Cache invariants to avoid recomputing for every V in VL (and every E). | |
| const bool IsPHIWithLoop = | |
| S.getOpcode() == Instruction::PHI && | |
| LI->getLoopFor(S.getMainOp()->getParent()) != nullptr; | |
| for (TreeEntry *E : getTreeEntries(S.getMainOp())) { | |
| if (E->isSame(VL)) { | |
| LLVM_DEBUG(dbgs() << "SLP: Perfect diamond merge at " << *S.getMainOp() | |
| << ".\n"); | |
| return ScalarsVectorizationLegality(S, /*IsLegal=*/false); | |
| } | |
| SmallPtrSet<Value *, 8> Values(llvm::from_range, E->Scalars); | |
| if (all_of(VL, [&](Value *V) { | |
| return isa<PoisonValue>(V) || Values.contains(V) || | |
| (IsPHIWithLoop && isa<PHINode>(V) && isVectorized(V)); | |
| })) { | |
| LLVM_DEBUG(dbgs() << "SLP: Gathering due to full overlap.\n"); | |
| return ScalarsVectorizationLegality(S, /*IsLegal=*/false); | |
| } | |
| } | |
| bool AreAllSameBlock = !AreScatterAllGEPSameBlock; | |
| bool AreAllSameInsts = AreAllSameBlock || AreScatterAllGEPSameBlock; | |
| if (!AreAllSameInsts || isSplat(VL) || | |
| (isa<InsertElementInst, ExtractValueInst, ExtractElementInst>( | |
| S.getMainOp()) && | |
| !all_of(VL, isVectorLikeInstWithConstOps))) { | |
| LLVM_DEBUG(dbgs() << "SLP: Gathering due to C,S,B,O conditions. \n"; | |
| dbgs() << "["; | |
| interleaveComma(VL, dbgs(), [&](Value *V) { dbgs() << *V; }); | |
| dbgs() << "]\n"); | |
| return ScalarsVectorizationLegality(S, /*IsLegal=*/false); | |
| } | |
| // Don't vectorize ephemeral values. | |
| if (!EphValues.empty()) { | |
| for (Value *V : VL) { | |
| if (EphValues.count(V)) { | |
| LLVM_DEBUG(dbgs() << "SLP: The instruction (" << *V | |
| << ") is ephemeral.\n"); | |
| // Do not try to pack to avoid extra instructions here. | |
| return ScalarsVectorizationLegality(S, /*IsLegal=*/false, | |
| /*TryToFindDuplicates=*/false); | |
| } | |
| } | |
| } | |
| // We now know that this is a vector of instructions of the same type from | |
| // the same block. | |
| // Check that none of the instructions in the bundle are already in the tree | |
| // and the node may be not profitable for the vectorization as the small | |
| // alternate node. | |
| if (S.isAltShuffle()) { | |
| auto GetNumVectorizedExtracted = [&]() { | |
| APInt Extracted = APInt::getZero(VL.size()); | |
| APInt Vectorized = APInt::getAllOnes(VL.size()); | |
| for (auto [Idx, V] : enumerate(VL)) { | |
| auto *I = dyn_cast<Instruction>(V); | |
| if (!I || doesNotNeedToBeScheduled(I) || | |
| all_of(I->operands(), [&](const Use &U) { | |
| return isa<ExtractElementInst>(U.get()); | |
| })) | |
| continue; | |
| if (isVectorized(I)) | |
| Vectorized.clearBit(Idx); | |
| else if (!I->hasOneUser() && !areAllUsersVectorized(I, UserIgnoreList)) | |
| Extracted.setBit(Idx); | |
| } | |
| return std::make_pair(Vectorized, Extracted); | |
| }; | |
| auto [Vectorized, Extracted] = GetNumVectorizedExtracted(); | |
| bool PreferScalarize = !Vectorized.isAllOnes() && VL.size() == 2; | |
| if (!Vectorized.isAllOnes() && !PreferScalarize) { | |
| // Rough cost estimation, if the vector code (+ potential extracts) is | |
| // more profitable than the scalar + buildvector. | |
| Type *ScalarTy = VL.front()->getType(); | |
| auto *VecTy = cast<VectorType>(getWidenedType(ScalarTy, VL.size())); | |
| InstructionCost VectorizeCostEstimate = | |
| getShuffleCost(*TTI, TTI::SK_PermuteTwoSrc, VecTy, CostKind) + | |
| getScalarizationOverhead( | |
| *TTI, Opts.slp_revec, ScalarTy, VecTy, Extracted, | |
| /*Insert=*/false, /*Extract=*/true, CostKind); | |
| InstructionCost ScalarizeCostEstimate = getScalarizationOverhead( | |
| *TTI, Opts.slp_revec, ScalarTy, VecTy, Vectorized, | |
| /*Insert=*/true, /*Extract=*/false, CostKind, /*ForPoisonSrc=*/false); | |
| PreferScalarize = VectorizeCostEstimate > ScalarizeCostEstimate; | |
| } | |
| if (PreferScalarize) { | |
| LLVM_DEBUG(dbgs() << "SLP: The instructions are in tree and alternate " | |
| "node is not profitable.\n"); | |
| return ScalarsVectorizationLegality(S, /*IsLegal=*/false); | |
| } | |
| } | |
| // The reduction nodes (stored in UserIgnoreList) also should stay scalar. | |
| if (UserIgnoreList && !UserIgnoreList->empty()) { | |
| for (Value *V : VL) { | |
| if (UserIgnoreList->contains(V)) { | |
| LLVM_DEBUG(dbgs() << "SLP: Gathering due to gathered scalar.\n"); | |
| return ScalarsVectorizationLegality(S, /*IsLegal=*/false); | |
| } | |
| } | |
| } | |
| return ScalarsVectorizationLegality(S, /*IsLegal=*/true); | |
| } | |
| /// Peel associative single-use binary chains into operand columns. Peeled | |
| /// instructions go to \p ReassocScalars; sub-operands are inserted after the | |
| /// peeled column so real pairs stay adjacent for flag reuse. | |
| /// Poison lanes are allowed through; a lane that is not itself a matching | |
| /// real instruction can still be peeled as a copyable identity leaf, as | |
| /// long as some other lane anchors the opcode. | |
| /// Add/sub (and fadd/fsub) chains peel together: a peeled subtract keeps | |
| /// the column sign for its first operand and flips it for the second one, | |
| /// recorded per column in \p NegatedColumns, so subtracted leaves stay out | |
| /// of the positive total. | |
| static void scanAssociativeOperands( | |
| const InstructionsState &S, DominatorTree &DT, const DataLayout &DL, | |
| const TargetTransformInfo &TTI, const TargetLibraryInfo &TLI, | |
| const BoUpSLP &R, SmallVectorImpl<BoUpSLP::ValueList> &Operands, | |
| SmallBitVector &NegatedColumns, SmallVectorImpl<Value *> &ReassocScalars) { | |
| assert(Operands.size() == 2 && NegatedColumns.empty() && | |
| "Expected the initial 2 operand columns."); | |
| // The subtract family flips the sign of its second operand column. | |
| auto IsSub = [](unsigned Opcode) { | |
| return Opcode == Instruction::Sub || Opcode == Instruction::FSub; | |
| }; | |
| // Signs ride with the columns so the parallel lists cannot drift apart. | |
| struct SignedColumn { | |
| BoUpSLP::ValueList Col; | |
| bool Negated; | |
| }; | |
| SmallVector<SignedColumn, 4> Columns = { | |
| {std::move(Operands[0]), false}, | |
| {std::move(Operands[1]), IsSub(S.getOpcode())}}; | |
| InstructionsCompatibilityAnalysis Analysis(DT, DL, TTI, TLI); | |
| // The opcode family a column may peel into: subtracts flatten as adds of | |
| // a negated leaf, other opcodes peel only into their own kind. | |
| const unsigned RootFamily = getReassocCombineOpcode(S.getOpcode()); | |
| // A lane whose value is itself a single-use chain link; block placement | |
| // does not matter here (buildTreeRec re-checks that later for whatever | |
| // columns end up as real leaves). | |
| auto IsRealLane = [&](Value *V) { | |
| auto *I = dyn_cast<Instruction>(V); | |
| return I && I->hasOneUse() && | |
| getReassocCombineOpcode(I->getOpcode()) == RootFamily && | |
| isReassocChainLink(I); | |
| }; | |
| // Builds on InstructionsCompatibilityAnalysis's own opcode matching | |
| // (rather than the more permissive getSameOpcode()) so a column that | |
| // falls back to a copyable identity leaf only ever anchors on a MainOp | |
| // that InstructionsCompatibilityAnalysis::isSupportedOpcode() accepts. | |
| auto CanPeel = [&](ArrayRef<Value *> Column) { | |
| InstructionsState ColS = Analysis.buildInstructionsState(Column, R); | |
| if (!ColS || getReassocCombineOpcode(ColS.getOpcode()) != RootFamily || | |
| ColS.isAltShuffle()) | |
| return InstructionsState::invalid(); | |
| // Every lane must be poison, a genuine matching real lane, or (only for | |
| // a copyable ColS) stand in as a copyable identity leaf | |
| // (Opcode(V, identity) == V). | |
| if (!all_of(Column, [&](Value *V) { | |
| return isa<PoisonValue>(V) || IsRealLane(V) || | |
| (ColS.areInstructionsWithCopyableElements() && | |
| ColS.isCopyableElement(V)); | |
| })) | |
| return InstructionsState::invalid(); | |
| return ColS; | |
| }; | |
| for (unsigned Idx = 0; Idx != Columns.size();) { | |
| InstructionsState ColS = CanPeel(Columns[Idx].Col); | |
| if (!ColS) { | |
| ++Idx; | |
| continue; | |
| } | |
| BoUpSLP::ValueList Column = std::move(Columns[Idx].Col); | |
| // The absorbing constants of the peeled lanes keep the identity as the | |
| // other operand: the poison operand is not frozen in the flattened node. | |
| SmallVector<BoUpSLP::ValueList> SubOperands = Analysis.buildOperands( | |
| ColS, Column, R, /*AllowPoisonPlaceholders=*/false); | |
| assert(SubOperands.size() == 2 && "Expected 2 operand columns."); | |
| // Poison and copyable lanes have no real instruction left to erase | |
| // later: a copyable V is used as-is, not subsumed by the flattened | |
| // combine. | |
| for (Value *V : Column) | |
| if (!isa<PoisonValue>(V) && !ColS.isCopyableElement(V)) | |
| ReassocScalars.push_back(V); | |
| // A peeled subtract negates its second operand column. | |
| const bool Negated = Columns[Idx].Negated; | |
| Columns[Idx].Col = std::move(SubOperands.front()); | |
| Columns.insert( | |
| std::next(Columns.begin(), Idx + 1), | |
| {std::move(SubOperands.back()), IsSub(ColS.getOpcode()) != Negated}); | |
| // Do not advance Idx: re-examine the column that was just placed here. | |
| } | |
| Operands.clear(); | |
| for (auto &[Col, Negated] : Columns) { | |
| Operands.push_back(std::move(Col)); | |
| NegatedColumns.push_back(Negated); | |
| } | |
| } | |
| /// Realign flattened operand columns so matching value families share a | |
| /// column across lanes. Lane 0 is canonical; other lanes match by key equality | |
| /// only (never compare hash values). Columns sharing a key are paired by the | |
| /// family of their first operand where available, so e.g. shifts fed by the | |
| /// same load family land in one column instead of pairing by encounter order. | |
| /// Values move between columns only within the same sign: a subtracted leaf | |
| /// never lands in an added column. The sign is queried per lane and column | |
| /// with \p IsNegated: alternate add/sub nodes negate only the non-leading | |
| /// columns of their subtract lanes. | |
| static SmallVector<BoUpSLP::ValueList> alignReassociatedOperandsByKey( | |
| ArrayRef<BoUpSLP::ValueList> Operands, | |
| function_ref<bool(unsigned Lane, unsigned Col)> IsNegated, | |
| const TargetLibraryInfo &TLI) { | |
| const unsigned NumCols = Operands.size(); | |
| const unsigned NumLanes = Operands.front().size(); | |
| auto LoadsSubkey = [](size_t /*Key*/, LoadInst *LI) { | |
| return hash_value(getUnderlyingObject(LI->getPointerOperand())); | |
| }; | |
| auto GetKey = [&](Value *V) { | |
| return generateKeySubkey(V, &TLI, LoadsSubkey, /*AllowAlternate=*/false); | |
| }; | |
| const std::pair<size_t, size_t> NoSecondaryKey{0, 0}; | |
| // Family of the value's first operand; loads already carry their family in | |
| // the primary key. | |
| auto GetSecondaryKey = [&](Value *V) -> std::pair<size_t, size_t> { | |
| auto *I = dyn_cast<Instruction>(V); | |
| if (!I || isa<LoadInst>(I) || I->getNumOperands() == 0) | |
| return NoSecondaryKey; | |
| return GetKey(I->getOperand(0)); | |
| }; | |
| SmallVector<std::pair<size_t, size_t>> Lane0Keys = map_to_vector( | |
| Operands, [&](const BoUpSLP::ValueList &Col) { return GetKey(Col[0]); }); | |
| SmallVector<std::pair<size_t, size_t>> Lane0SecondaryKeys = | |
| map_to_vector(Operands, [&](const BoUpSLP::ValueList &Col) { | |
| return GetSecondaryKey(Col[0]); | |
| }); | |
| SmallVector<BoUpSLP::ValueList> Aligned(NumCols, | |
| BoUpSLP::ValueList(NumLanes)); | |
| for (unsigned Col : seq<unsigned>(NumCols)) | |
| Aligned[Col][0] = Operands[Col][0]; | |
| for (unsigned Lane : seq<unsigned>(1, NumLanes)) { | |
| // Buckets are keyed by the value key and the column sign. | |
| using Key = std::pair<std::pair<size_t, size_t>, unsigned>; | |
| auto Sign = [&](unsigned Col) { return IsNegated(Lane, Col) ? 1U : 0U; }; | |
| SmallDenseMap<Key, SmallVector<unsigned, 2>, 8> Buckets; | |
| for (unsigned Col : seq<unsigned>(NumCols)) | |
| Buckets[{GetKey(Operands[Col][Lane]), Sign(Col)}].push_back(Col); | |
| SmallDenseMap<Key, unsigned, 8> NextInBucket; | |
| SmallVector<unsigned> SlotSrcCol(NumCols, NumCols); | |
| SmallBitVector ColClaimed(NumCols, false); | |
| for (unsigned Slot : seq<unsigned>(NumCols)) { | |
| const auto SlotKey = std::make_pair(Lane0Keys[Slot], Sign(Slot)); | |
| auto BucketIt = Buckets.find(SlotKey); | |
| if (BucketIt == Buckets.end()) | |
| continue; | |
| unsigned &Cursor = NextInBucket[SlotKey]; | |
| if (Cursor >= BucketIt->second.size()) | |
| continue; | |
| // Among the remaining same-key columns prefer the one whose operand | |
| // family matches lane 0's; no match keeps the bucket order. | |
| unsigned PickPos = Cursor; | |
| if (Lane0SecondaryKeys[Slot] != NoSecondaryKey) | |
| for (unsigned P = Cursor, E = BucketIt->second.size(); P != E; ++P) | |
| if (GetSecondaryKey(Operands[BucketIt->second[P]][Lane]) == | |
| Lane0SecondaryKeys[Slot]) { | |
| PickPos = P; | |
| break; | |
| } | |
| std::swap(BucketIt->second[Cursor], BucketIt->second[PickPos]); | |
| unsigned SrcCol = BucketIt->second[Cursor++]; | |
| SlotSrcCol[Slot] = SrcCol; | |
| ColClaimed[SrcCol] = true; | |
| } | |
| // Leftover slots take leftover columns of the same sign; per-lane sign | |
| // counts match, so every slot finds one. | |
| for (unsigned Slot : seq<unsigned>(NumCols)) { | |
| if (SlotSrcCol[Slot] != NumCols) | |
| continue; | |
| for (unsigned Col : seq<unsigned>(NumCols)) { | |
| if (!ColClaimed[Col] && IsNegated(Lane, Col) == IsNegated(Lane, Slot)) { | |
| SlotSrcCol[Slot] = Col; | |
| ColClaimed[Col] = true; | |
| break; | |
| } | |
| } | |
| } | |
| for (unsigned Slot : seq<unsigned>(NumCols)) | |
| Aligned[Slot][Lane] = Operands[SlotSrcCol[Slot]][Lane]; | |
| } | |
| return Aligned; | |
| } | |
| /// Rank reassociated operand layouts by vectorizable load columns, other load | |
| /// patterns, broadcast/constant columns, then fewer unique values per column. | |
| /// Identity columns are scored by the opcode of the emitted combines. | |
| static std::tuple<unsigned, unsigned, unsigned, int> | |
| getReassocColumnsQuality(ArrayRef<BoUpSLP::ValueList> Columns, const BoUpSLP &R, | |
| unsigned Opcode) { | |
| unsigned NumConsecutiveLoadCols = 0; | |
| unsigned NumOtherVecLoadCols = 0; | |
| unsigned NumBroadcastOrConstCols = 0; | |
| int NumUniqueValues = 0; | |
| const unsigned CombineOpcode = getReassocCombineOpcode(Opcode); | |
| for (ArrayRef<Value *> Col : Columns) { | |
| if (all_of(Col, IsaPred<Constant>)) { | |
| ++NumBroadcastOrConstCols; | |
| continue; | |
| } | |
| // Identity constants are free beyond the base they are inserted into. | |
| SmallPtrSet<Value *, 8> UniqueValues; | |
| for (Value *V : Col) | |
| if (!isBinOpIdentityConstant(V, CombineOpcode)) | |
| UniqueValues.insert(V); | |
| NumUniqueValues += UniqueValues.size(); | |
| if (UniqueValues.size() <= 1) { | |
| ++NumBroadcastOrConstCols; | |
| continue; | |
| } | |
| if (all_of(Col, IsaPred<LoadInst>)) { | |
| BoUpSLP::OrdersType Order; | |
| SmallVector<Value *> PointerOps; | |
| BoUpSLP::StridedPtrInfo SPtrInfo; | |
| BoUpSLP::LoadsState LS = | |
| R.canVectorizeLoads(Col, Col.front(), Order, PointerOps, SPtrInfo); | |
| if (LS == BoUpSLP::LoadsState::Vectorize) | |
| ++NumConsecutiveLoadCols; | |
| else if (LS != BoUpSLP::LoadsState::Gather) | |
| ++NumOtherVecLoadCols; | |
| } | |
| } | |
| // Greater is better; negate unique count as a tie-breaker. | |
| return std::make_tuple(NumConsecutiveLoadCols, NumOtherVecLoadCols, | |
| NumBroadcastOrConstCols, -NumUniqueValues); | |
| } | |
| void BoUpSLP::buildTreeRec(ArrayRef<Value *> VLRef, unsigned Depth, | |
| const EdgeInfo &UserTreeIdx, | |
| unsigned InterleaveFactor) { | |
| assert((allConstant(VLRef) || allSameType(VLRef)) && "Invalid types!"); | |
| SmallVector<int> ReuseShuffleIndices; | |
| SmallVector<Value *> VL(VLRef); | |
| // Tries to build split node. | |
| auto TrySplitNode = [&](const InstructionsState &LocalState, | |
| bool SplitInHalves = false) { | |
| SmallVector<Value *> Op1, Op2; | |
| OrdersType ReorderIndices; | |
| if (SplitInHalves) { | |
| auto *Mid = std::next(VL.begin(), VL.size() / 2); | |
| Op1.assign(VL.begin(), Mid); | |
| Op2.assign(Mid, VL.end()); | |
| } else if (!canBuildSplitNode(VL, LocalState, Op1, Op2, ReorderIndices)) { | |
| return false; | |
| } | |
| auto Invalid = ScheduleBundle::invalid(); | |
| auto *TE = newTreeEntry(VL, TreeEntry::SplitVectorize, Invalid, LocalState, | |
| UserTreeIdx, {}, ReorderIndices); | |
| LLVM_DEBUG(dbgs() << "SLP: split alternate node.\n"; TE->dump()); | |
| auto AddNode = [&](ArrayRef<Value *> Op, unsigned Idx) { | |
| InstructionsState S = getSameOpcode(Op, *TLI); | |
| if (S && (isa<LoadInst>(S.getMainOp()) || | |
| getSameValuesTreeEntry(S.getMainOp(), Op, /*SameVF=*/true))) { | |
| // Build gather node for loads, they will be gathered later. | |
| TE->CombinedEntriesWithIndices.emplace_back(VectorizableTree.size(), | |
| Idx == 0 ? 0 : Op1.size()); | |
| (void)newTreeEntry(Op, TreeEntry::NeedToGather, Invalid, S, {TE, Idx}); | |
| } else { | |
| TE->CombinedEntriesWithIndices.emplace_back(VectorizableTree.size(), | |
| Idx == 0 ? 0 : Op1.size()); | |
| buildTreeRec(Op, Depth, {TE, Idx}); | |
| } | |
| }; | |
| AddNode(Op1, 0); | |
| AddNode(Op2, 1); | |
| return true; | |
| }; | |
| auto AreOnlyConstsWithPHIs = [](ArrayRef<Value *> VL) { | |
| bool AreConsts = false; | |
| auto IsNotPoison = [](Value *V) { return !isa<PoisonValue>(V); }; | |
| for (Value *V : make_filter_range(VL, IsNotPoison)) { | |
| if (isa<Constant>(V)) { | |
| AreConsts = true; | |
| continue; | |
| } | |
| if (!isa<PHINode>(V)) | |
| return false; | |
| } | |
| return AreConsts; | |
| }; | |
| if (AreOnlyConstsWithPHIs(VL)) { | |
| LLVM_DEBUG(dbgs() << "SLP: Gathering due to all constants and PHIs.\n"); | |
| newGatherTreeEntry(VL, InstructionsState::invalid(), UserTreeIdx); | |
| return; | |
| } | |
| ScalarsVectorizationLegality Legality = | |
| getScalarsVectorizationLegality(VL, Depth, UserTreeIdx); | |
| InstructionsState S = Legality.getInstructionsState(); | |
| if (!Legality.isLegal()) { | |
| if (Legality.trySplitVectorize()) { | |
| auto [MainOp, AltOp] = getMainAltOpsNoStateVL(VL); | |
| // Last chance to try to vectorize alternate node. | |
| if (MainOp && AltOp && TrySplitNode(InstructionsState(MainOp, AltOp))) | |
| return; | |
| } | |
| if (Legality.tryToFindDuplicates()) | |
| (void)tryToFindDuplicates(Opts, VL, ReuseShuffleIndices, *TTI, *TLI, S, | |
| UserTreeIdx, *this); | |
| newGatherTreeEntry(VL, S, UserTreeIdx, ReuseShuffleIndices); | |
| return; | |
| } | |
| if (SplitStoredValues && S && UserTreeIdx.UserTE && | |
| UserTreeIdx.UserTE->Idx == 0 && TrySplitNode(S, /*SplitInHalves=*/true)) | |
| return; | |
| // FIXME: investigate if there are profitable cases for VL.size() <= 4. | |
| if (S.isAltShuffle() && TrySplitNode(S)) | |
| return; | |
| // Cmp nodes with interchangeable lanes (e.g. x == 0 mixed with x <u C) | |
| // try the split by the original predicates, like the alternate nodes. The | |
| // trunc X to i1 lanes have no original predicate. | |
| if (S.getOpcode() == Instruction::ICmp && !S.isAltShuffle() && | |
| none_of(VL, IsaPred<TruncInst>)) { | |
| auto SameOrSwapped = [](const ICmpInst *CI, CmpInst::Predicate P) { | |
| return CI->getPredicate() == P || | |
| CI->getPredicate() == CmpInst::getSwappedPredicate(P); | |
| }; | |
| ICmpInst *MainI = cast<ICmpInst>(*find_if(VL, IsaPred<ICmpInst>)); | |
| auto *AltIt = find_if(VL, [&](Value *V) { | |
| auto *CI = dyn_cast<ICmpInst>(V); | |
| return CI && !SameOrSwapped(CI, MainI->getPredicate()); | |
| }); | |
| ICmpInst *AltI = AltIt == VL.end() ? nullptr : cast<ICmpInst>(*AltIt); | |
| if (AltI && | |
| all_of(make_isa_range<ICmpInst>(VL), | |
| [&](ICmpInst *CI) { | |
| return SameOrSwapped(CI, MainI->getPredicate()) || | |
| SameOrSwapped(CI, AltI->getPredicate()); | |
| }) && | |
| TrySplitNode(InstructionsState(MainI, AltI))) | |
| return; | |
| } | |
| // Check that every instruction appears once in this bundle. | |
| if (!tryToFindDuplicates(Opts, VL, ReuseShuffleIndices, *TTI, *TLI, S, | |
| UserTreeIdx, *this, /*BuildGatherOnly=*/false)) { | |
| newGatherTreeEntry(VL, S, UserTreeIdx, ReuseShuffleIndices); | |
| return; | |
| } | |
| // Perform specific checks for each particular instruction kind. | |
| bool IsScatterVectorizeUserTE = | |
| UserTreeIdx.UserTE && | |
| UserTreeIdx.UserTE->State == TreeEntry::ScatterVectorize; | |
| OrdersType CurrentOrder; | |
| SmallVector<Value *> PointerOps; | |
| StridedPtrInfo SPtrInfo; | |
| SmallVector<int> ExpandShuffleMask; | |
| TreeEntry::EntryState State = getScalarsVectorizationState( | |
| S, VL, IsScatterVectorizeUserTE, CurrentOrder, PointerOps, SPtrInfo, | |
| ExpandShuffleMask); | |
| // The zero-extended sub-fields of the same wider integer scalar are | |
| // gathered and emitted as a bitcast to the field vector plus a permutation. | |
| // For 2 lanes the emission is not cheaper than the insertelement chain. | |
| if (State == TreeEntry::NeedToGather || | |
| (VL.size() > 2 && ReuseShuffleIndices.empty() && | |
| matchGatheredExtractedFields(VL, *DL))) { | |
| newGatherTreeEntry(VL, S, UserTreeIdx, ReuseShuffleIndices); | |
| return; | |
| } | |
| // Check the loop nest. We need to be sure we handle a single loop nest at a | |
| // time to avoid incorrect cost estimation because of the loop aware cost | |
| // model. | |
| if (VectorizableTree.empty()) { | |
| assert(CurrentLoopNest.empty() && "Expected empty loop nest"); | |
| // Process the first node? Initial fill of the loop nest. | |
| BasicBlock *Parent = S.getMainOp()->getParent(); | |
| if (const Loop *L = LI->getLoopFor(Parent)) { | |
| L = findInnermostNonInvariantLoop(L, VL); | |
| if (L) | |
| CurrentLoopNest.assign(getLoopNest(L)); | |
| } | |
| } else if (!UserTreeIdx || | |
| UserTreeIdx.UserTE->State == TreeEntry::SplitVectorize || | |
| UserTreeIdx.UserTE->isGather() || | |
| UserTreeIdx.UserTE->getMainOp()->getParent() != | |
| S.getMainOp()->getParent()) { | |
| BasicBlock *Parent = S.getMainOp()->getParent(); | |
| if (const Loop *L = LI->getLoopFor(Parent)) { | |
| // Check that the new loop nest shares the same outer structure as the | |
| // tree's current loop nest. Completely disjoint nests (different | |
| // outermost loops) are forced to gather because their scales cannot be | |
| // meaningfully combined. Sibling inner loops (inside a common outer | |
| // loop or outside any loops at all) are allowed: the cost model scales | |
| // each entry by its own loop via getScaleToLoopIterations(), so a tree | |
| // that spans sibling inner loops (e.g. a PHI at their merge block) can | |
| // still be costed correctly. Contract CurrentLoopNest to the longest | |
| // common prefix with the new entry's nest so subsequent entries in yet | |
| // another sibling can also be admitted. | |
| L = findInnermostNonInvariantLoop(L, VL); | |
| if (L) { | |
| SmallVector<const Loop *> NewLoopNest(getLoopNest(L)); | |
| unsigned CommonLen = 0; | |
| for (const auto [L1, L2] : zip(CurrentLoopNest, NewLoopNest)) { | |
| if (L1 != L2) | |
| break; | |
| ++CommonLen; | |
| } | |
| auto ValidateMergedBTCs = [&](unsigned StartDepth) -> bool { | |
| unsigned EndDepth = | |
| std::min<unsigned>(NewLoopNest.size(), MergedLoopBTCs.size()); | |
| for (unsigned D = StartDepth; D < EndDepth; ++D) { | |
| const SCEV *Constraint = MergedLoopBTCs[D]; | |
| if (!Constraint) | |
| continue; | |
| const SCEV *NewBTC = SE->getBackedgeTakenCount(NewLoopNest[D]); | |
| if (isa<SCEVCouldNotCompute>(NewBTC) || NewBTC != Constraint) | |
| return false; | |
| } | |
| return true; | |
| }; | |
| auto BailOutToGather = [&]() { | |
| LLVM_DEBUG(dbgs() | |
| << "SLP: Sibling loops have different trip counts.\n"); | |
| newGatherTreeEntry(VL, S, UserTreeIdx, ReuseShuffleIndices); | |
| }; | |
| if (CurrentLoopNest.empty()) { | |
| if (!ValidateMergedBTCs(0)) { | |
| BailOutToGather(); | |
| return; | |
| } | |
| CurrentLoopNest.assign(NewLoopNest); | |
| } else if (CommonLen < CurrentLoopNest.size() && | |
| CommonLen < NewLoopNest.size()) { | |
| // Divergence below the common prefix: the tree now spans sibling | |
| // loops at depth CommonLen. Admitting them into one tree makes | |
| // the profitability decision JOINT across both siblings, so a | |
| // very hot sibling could otherwise let an unprofitable cold | |
| // sibling ride along "for free" (per-entry scaling of the cold | |
| // sibling's entries would be dwarfed by the hot one). Require | |
| // SCEV-proven equal backedge-taken counts for the diverging | |
| // siblings before joining; otherwise force gather. | |
| const Loop *SibA = CurrentLoopNest[CommonLen]; | |
| const Loop *SibB = NewLoopNest[CommonLen]; | |
| const SCEV *BecA = SE->getBackedgeTakenCount(SibA); | |
| const SCEV *BecB = SE->getBackedgeTakenCount(SibB); | |
| if (isa<SCEVCouldNotCompute>(BecA) || BecA != BecB) { | |
| BailOutToGather(); | |
| return; | |
| } | |
| if (!ValidateMergedBTCs(CommonLen + 1)) { | |
| BailOutToGather(); | |
| return; | |
| } | |
| if (MergedLoopBTCs.size() <= CommonLen) | |
| MergedLoopBTCs.resize(CommonLen + 1, nullptr); | |
| MergedLoopBTCs[CommonLen] = BecA; | |
| CurrentLoopNest.truncate(CommonLen); | |
| } else if (NewLoopNest.size() > CurrentLoopNest.size()) { | |
| if (!ValidateMergedBTCs(CurrentLoopNest.size())) { | |
| BailOutToGather(); | |
| return; | |
| } | |
| CurrentLoopNest.append( | |
| std::next(NewLoopNest.begin(), CurrentLoopNest.size()), | |
| NewLoopNest.end()); | |
| } | |
| // Otherwise NewLoopNest is a prefix of CurrentLoopNest: keep as-is. | |
| } | |
| } | |
| } | |
| Instruction *VL0 = S.getMainOp(); | |
| BasicBlock *BB = VL0->getParent(); | |
| auto &BSRef = BlocksSchedules[BB]; | |
| if (!BSRef) | |
| BSRef = std::make_unique<BlockScheduling>(BB, Opts.slp_schedule_budget); | |
| BlockScheduling &BS = *BSRef; | |
| SetVector<Value *> UniqueValues(llvm::from_range, VL); | |
| std::optional<ScheduleBundle *> BundlePtr = | |
| BS.tryScheduleBundle(UniqueValues.getArrayRef(), this, S, UserTreeIdx); | |
| #ifdef EXPENSIVE_CHECKS | |
| // Make sure we didn't break any internal invariants | |
| BS.verify(); | |
| #endif | |
| if (!BundlePtr || (*BundlePtr && !*BundlePtr.value())) { | |
| LLVM_DEBUG(dbgs() << "SLP: We are not able to schedule this bundle!\n"); | |
| // Last chance to try to vectorize alternate node. | |
| if (S.isAltShuffle() && ReuseShuffleIndices.empty() && TrySplitNode(S)) | |
| return; | |
| // Last chance to try to vectorize copyable node. | |
| if (S.areInstructionsWithCopyableElements() && | |
| ReuseShuffleIndices.empty()) { | |
| InstructionsState AltS = getSameOpcode(VL, *TLI); | |
| if (AltS && AltS.isAltShuffle() && TrySplitNode(AltS)) | |
| return; | |
| } | |
| newGatherTreeEntry(VL, S, UserTreeIdx, ReuseShuffleIndices); | |
| NonScheduledFirst.insert(VL.front()); | |
| if (S.getOpcode() == Instruction::Load && | |
| BS.ScheduleRegionSize < BS.ScheduleRegionSizeLimit) | |
| registerNonVectorizableLoads(ArrayRef(VL)); | |
| return; | |
| } | |
| InstructionsCompatibilityAnalysis Analysis(*DT, *DL, *TTI, *TLI); | |
| SmallVector<ValueList> Operands = Analysis.buildOperands(S, VL, *this); | |
| // Flatten associative binary chains into operand columns. Only the peeled | |
| // chain links are required to be single-use (they are erased); the root | |
| // being flattened may have other uses. Skip lanes that are neither chain | |
| // links nor copyable identity leaves. Alternate nodes flatten too, but | |
| // each lane keeps its own opcode on every combine level. Restricted to | |
| // BinaryOperator: isAssociative() is also true for associative intrinsics | |
| // (e.g. smax/smin/umax/umin), which are CallInst, not BinaryOperator, and | |
| // are not supported by the copyable-identity machinery used below | |
| // (ConstantExpr::getBinOpIdentity, isSupportedOpcode, | |
| // isBinOpIdentityConstant). | |
| SmallVector<Value *> ReassocScalars; | |
| // Sign of each flattened operand column (a subtracted leaf is negated). | |
| SmallBitVector NegatedColumns; | |
| // Per-lane subtract markers for flattened alternate nodes. | |
| SmallBitVector SubLanes; | |
| // Cached below (when the peel is kept) so the reorder step further down | |
| // does not need to redo the aligning/scoring work. | |
| SmallVector<ValueList> ReassocAlignedOperands; | |
| // Snapshot of the pre-flatten operand columns, used by both revert points. | |
| SmallVector<ValueList> NaturalTwoColumns; | |
| std::tuple<unsigned, unsigned, unsigned, int> ReassocPeeledQuality; | |
| if (Opts.slp_reassociate_ops && Operands.size() == 2 && | |
| all_of(VL, [&](Value *V) { | |
| if (!S.isAltShuffle() && S.isCopyableElement(V)) | |
| return true; | |
| auto *I = dyn_cast<BinaryOperator>(V); | |
| return I && isReassocChainLink(I); | |
| })) { | |
| NaturalTwoColumns = Operands; | |
| if (S.isAltShuffle()) { | |
| if (SmallVector<SmallVector<Value *>> Flattened = | |
| scanAltAssociativeOperands(S, *TLI, VL, Operands[0], Operands[1], | |
| ReassocScalars, SubLanes); | |
| !Flattened.empty()) { | |
| Operands.clear(); | |
| for (auto &Col : Flattened) | |
| Operands.emplace_back(std::move(Col)); | |
| } | |
| } else { | |
| scanAssociativeOperands(S, *DT, *DL, *TTI, *TLI, *this, Operands, | |
| NegatedColumns, ReassocScalars); | |
| } | |
| // Drop flattening unless realigning improves load or broadcast column | |
| // structure; an unimproved peel ties and reverts to natural columns. | |
| if (!ReassocScalars.empty()) { | |
| auto IsNegated = [&](unsigned Lane, unsigned Col) { | |
| return S.isAltShuffle() ? Col > 0 && SubLanes.test(Lane) | |
| : NegatedColumns.test(Col); | |
| }; | |
| ReassocAlignedOperands = | |
| alignReassociatedOperandsByKey(Operands, IsNegated, *TLI); | |
| // Alternate nodes compare against the natural two-column form rather | |
| // than the raw peel: the natural form pays one lane-select shuffle per | |
| // level, so the flatten is worth keeping whenever the realigned | |
| // columns improve on the natural ones. | |
| ReassocPeeledQuality = getReassocColumnsQuality( | |
| S.isAltShuffle() ? NaturalTwoColumns : Operands, *this, | |
| S.getOpcode()); | |
| // The unique-value count (4th field) is only a tie-break for the | |
| // later reorder-or-not decision, not for this one. | |
| auto DropUniqueCount = [](const auto &Quality) { | |
| return std::make_tuple(std::get<0>(Quality), std::get<1>(Quality), | |
| std::get<2>(Quality)); | |
| }; | |
| if (DropUniqueCount(getReassocColumnsQuality(ReassocAlignedOperands, | |
| *this, S.getOpcode())) <= | |
| DropUniqueCount(ReassocPeeledQuality)) { | |
| Operands = std::move(NaturalTwoColumns); | |
| ReassocScalars.clear(); | |
| } | |
| } | |
| } | |
| // Registers peeled chain links on the node so they are erased with it and | |
| // their scheduling deps are released as reassociated operands. | |
| auto RegisterReassocScalars = [&](TreeEntry *TE) { | |
| for (Value *V : ReassocScalars) { | |
| TE->addReassocScalar(V); | |
| SmallVectorImpl<const TreeEntry *> &Owners = | |
| ReassocScalarToTreeEntries.try_emplace(V).first->second; | |
| if (!is_contained(Owners, TE)) | |
| Owners.push_back(TE); | |
| } | |
| }; | |
| // A value routed into several columns is consumed by several combines; | |
| // that distorts both the cost and the vector structure (duplicated loads, | |
| // partial masked columns), so prefer the natural two-column shape. | |
| auto HasDupColumnValues = [&]() { | |
| SmallPtrSet<const Value *, 16> ColumnValues; | |
| return any_of(Operands, [&](const ValueList &Col) { | |
| return any_of(Col, [&](const Value *V) { | |
| return !isa<Constant>(V) && !ColumnValues.insert(V).second; | |
| }); | |
| }); | |
| }; | |
| ScheduleBundle Empty; | |
| ScheduleBundle &Bundle = BundlePtr.value() ? *BundlePtr.value() : Empty; | |
| LLVM_DEBUG(dbgs() << "SLP: We are able to schedule this bundle.\n"); | |
| unsigned ShuffleOrOp = | |
| S.isAltShuffle() ? (unsigned)Instruction::ShuffleVector : S.getOpcode(); | |
| auto CreateOperandNodes = [&](TreeEntry *TE, const auto &Operands) { | |
| // Postpone PHI nodes creation | |
| SmallVector<unsigned> PHIOps; | |
| for (unsigned I : seq<unsigned>(Operands.size())) { | |
| ArrayRef<Value *> Op = Operands[I]; | |
| if (Op.empty()) | |
| continue; | |
| InstructionsState S = getSameOpcode(Op, *TLI); | |
| if ((!S || S.getOpcode() != Instruction::PHI) || S.isAltShuffle()) | |
| buildTreeRec(Op, Depth + 1, {TE, I}); | |
| else | |
| PHIOps.push_back(I); | |
| } | |
| for (unsigned I : PHIOps) | |
| buildTreeRec(Operands[I], Depth + 1, {TE, I}); | |
| }; | |
| switch (ShuffleOrOp) { | |
| case Instruction::PHI: { | |
| TreeEntry *TE = | |
| newTreeEntry(VL, Bundle, S, UserTreeIdx, ReuseShuffleIndices); | |
| LLVM_DEBUG(dbgs() << "SLP: added a new TreeEntry (PHINode).\n"; | |
| TE->dump()); | |
| TE->setOperands(Operands); | |
| CreateOperandNodes(TE, Operands); | |
| return; | |
| } | |
| case Instruction::ExtractValue: | |
| case Instruction::ExtractElement: { | |
| if (CurrentOrder.empty()) { | |
| LLVM_DEBUG(dbgs() << "SLP: Reusing or shuffling extract sequence.\n"); | |
| } else { | |
| LLVM_DEBUG({ | |
| dbgs() << "SLP: Reusing or shuffling of reordered extract sequence " | |
| "with order"; | |
| for (unsigned Idx : CurrentOrder) | |
| dbgs() << " " << Idx; | |
| dbgs() << "\n"; | |
| }); | |
| fixupOrderingIndices(CurrentOrder); | |
| } | |
| // Insert new order with initial value 0, if it does not exist, | |
| // otherwise return the iterator to the existing one. | |
| TreeEntry *TE = newTreeEntry(VL, Bundle /*vectorized*/, S, UserTreeIdx, | |
| ReuseShuffleIndices, CurrentOrder); | |
| LLVM_DEBUG(dbgs() << "SLP: added a new TreeEntry " | |
| "(ExtractValueInst/ExtractElementInst).\n"; | |
| TE->dump()); | |
| // This is a special case, as it does not gather, but at the same time | |
| // we are not extending buildTreeRec() towards the operands. | |
| TE->setOperands(Operands); | |
| if (ShuffleOrOp == Instruction::ExtractValue) { | |
| SmallVector<unsigned> Indices; | |
| SmallVector<Value *> Calls; | |
| if (checkEVsForVecCalls(VL, S, *TLI, Indices, Calls)) { | |
| TE->StructEVIndices = std::move(Indices); | |
| buildTreeRec(Operands.front(), Depth + 1, {TE, 0}); | |
| } | |
| } | |
| return; | |
| } | |
| case Instruction::InsertValue: | |
| case Instruction::InsertElement: { | |
| assert(ReuseShuffleIndices.empty() && "All inserts should be unique"); | |
| auto OrdCompare = [](const std::pair<int, int> &P1, | |
| const std::pair<int, int> &P2) { | |
| return P1.first > P2.first; | |
| }; | |
| PriorityQueue<std::pair<int, int>, SmallVector<std::pair<int, int>>, | |
| decltype(OrdCompare)> | |
| Indices(OrdCompare); | |
| for (int I = 0, E = VL.size(); I < E; ++I) { | |
| unsigned Idx = *getElementIndex(VL[I]); | |
| Indices.emplace(Idx, I); | |
| } | |
| OrdersType CurrentOrder(VL.size(), VL.size()); | |
| bool IsIdentity = true; | |
| for (int I = 0, E = VL.size(); I < E; ++I) { | |
| CurrentOrder[Indices.top().second] = I; | |
| IsIdentity &= Indices.top().second == I; | |
| Indices.pop(); | |
| } | |
| if (IsIdentity) | |
| CurrentOrder.clear(); | |
| TreeEntry *TE = newTreeEntry(VL, Bundle /*vectorized*/, S, UserTreeIdx, | |
| {}, CurrentOrder); | |
| LLVM_DEBUG(dbgs() << "SLP: added a new TreeEntry (InsertElementInst).\n"; | |
| TE->dump()); | |
| TE->setOperands(Operands); | |
| buildTreeRec(TE->getOperand(1), Depth + 1, {TE, 1}); | |
| return; | |
| } | |
| case Instruction::Load: { | |
| // Check that a vectorized load would load the same memory as a scalar | |
| // load. For example, we don't want to vectorize loads that are smaller | |
| // than 8-bit. Even though we have a packed struct {<i2, i2, i2, i2>} LLVM | |
| // treats loading/storing it as an i8 struct. If we vectorize loads/stores | |
| // from such a struct, we read/write packed bits disagreeing with the | |
| // unvectorized version. | |
| TreeEntry *TE = nullptr; | |
| fixupOrderingIndices(CurrentOrder); | |
| switch (State) { | |
| case TreeEntry::Vectorize: | |
| TE = newTreeEntry(VL, Bundle /*vectorized*/, S, UserTreeIdx, | |
| ReuseShuffleIndices, CurrentOrder, InterleaveFactor); | |
| if (CurrentOrder.empty()) | |
| LLVM_DEBUG(dbgs() << "SLP: added a new TreeEntry (LoadInst).\n"; | |
| TE->dump()); | |
| else | |
| LLVM_DEBUG(dbgs() | |
| << "SLP: added a new TreeEntry (jumbled LoadInst).\n"; | |
| TE->dump()); | |
| break; | |
| case TreeEntry::CompressVectorize: | |
| // Vectorizing non-consecutive loads with (masked)load + compress. | |
| TE = newTreeEntry(VL, TreeEntry::CompressVectorize, Bundle, S, | |
| UserTreeIdx, ReuseShuffleIndices, CurrentOrder); | |
| LLVM_DEBUG( | |
| dbgs() | |
| << "SLP: added a new TreeEntry (masked LoadInst + compress).\n"; | |
| TE->dump()); | |
| break; | |
| case TreeEntry::StridedVectorize: | |
| // Vectorizing non-consecutive loads with `llvm.masked.gather`. | |
| TE = newTreeEntry(VL, TreeEntry::StridedVectorize, Bundle, S, | |
| UserTreeIdx, ReuseShuffleIndices, CurrentOrder); | |
| TreeEntryToStridedPtrInfoMap[TE] = SPtrInfo; | |
| LLVM_DEBUG(dbgs() << "SLP: added a new TreeEntry (strided LoadInst).\n"; | |
| TE->dump()); | |
| break; | |
| case TreeEntry::ScatterVectorize: | |
| // Vectorizing non-consecutive loads with `llvm.masked.gather`. | |
| TE = newTreeEntry(VL, TreeEntry::ScatterVectorize, Bundle, S, | |
| UserTreeIdx, ReuseShuffleIndices); | |
| LLVM_DEBUG( | |
| dbgs() | |
| << "SLP: added a new TreeEntry (non-consecutive LoadInst).\n"; | |
| TE->dump()); | |
| break; | |
| case TreeEntry::BlendedLoadVectorize: { | |
| // Two masked loads from the candidate bases, blended by the per-lane | |
| // select conditions. The conditions are added as the operand node, so | |
| // the blend mask is vectorized through the tree. | |
| TE = newTreeEntry(VL, TreeEntry::BlendedLoadVectorize, Bundle, S, | |
| UserTreeIdx, ReuseShuffleIndices); | |
| LLVM_DEBUG(dbgs() << "SLP: added a new TreeEntry (blended LoadInst).\n"; | |
| TE->dump()); | |
| Value *TrueBase = nullptr; | |
| Value *FalseBase = nullptr; | |
| SmallVector<Value *> Conditions; | |
| [[maybe_unused]] bool Found = isSelectedBaseLoad( | |
| VL0->getType(), PointerOps, *DL, TrueBase, FalseBase, Conditions); | |
| assert(Found && "Expected a valid blended-load pattern."); | |
| Operands.assign(1, ValueList(Conditions.begin(), Conditions.end())); | |
| TE->setOperands(Operands); | |
| buildTreeRec(TE->getOperand(0), Depth + 1, {TE, 0}); | |
| return; | |
| } | |
| case TreeEntry::ExpandVectorize: | |
| case TreeEntry::CombinedVectorize: | |
| case TreeEntry::SplitVectorize: | |
| case TreeEntry::NeedToGather: | |
| llvm_unreachable("Unexpected loads state."); | |
| } | |
| if (!CurrentOrder.empty() && State != TreeEntry::ScatterVectorize) { | |
| assert(Operands.size() == 1 && "Expected a single operand only"); | |
| SmallVector<int> Mask; | |
| inversePermutation(CurrentOrder, Mask); | |
| reorderScalars(Operands.front(), Mask); | |
| } | |
| TE->setOperands(Operands); | |
| if (State == TreeEntry::ScatterVectorize) | |
| buildTreeRec(PointerOps, Depth + 1, {TE, 0}); | |
| return; | |
| } | |
| case Instruction::ZExt: | |
| case Instruction::SExt: | |
| case Instruction::FPToUI: | |
| case Instruction::FPToSI: | |
| case Instruction::FPExt: | |
| case Instruction::PtrToInt: | |
| case Instruction::PtrToAddr: | |
| case Instruction::IntToPtr: | |
| case Instruction::SIToFP: | |
| case Instruction::UIToFP: | |
| case Instruction::Trunc: | |
| case Instruction::FPTrunc: | |
| case Instruction::BitCast: { | |
| auto [PrevMaxBW, PrevMinBW] = CastMaxMinBWSizes.value_or( | |
| std::make_pair(std::numeric_limits<unsigned>::min(), | |
| std::numeric_limits<unsigned>::max())); | |
| if (ShuffleOrOp == Instruction::ZExt || | |
| ShuffleOrOp == Instruction::SExt) { | |
| CastMaxMinBWSizes = std::make_pair( | |
| std::max<unsigned>(DL->getTypeSizeInBits(VL0->getType()), | |
| PrevMaxBW), | |
| std::min<unsigned>( | |
| DL->getTypeSizeInBits(VL0->getOperand(0)->getType()), | |
| PrevMinBW)); | |
| } else if (ShuffleOrOp == Instruction::Trunc) { | |
| CastMaxMinBWSizes = std::make_pair( | |
| std::max<unsigned>( | |
| DL->getTypeSizeInBits(VL0->getOperand(0)->getType()), | |
| PrevMaxBW), | |
| std::min<unsigned>(DL->getTypeSizeInBits(VL0->getType()), | |
| PrevMinBW)); | |
| } | |
| TreeEntry *TE = newTreeEntry(VL, Bundle /*vectorized*/, S, UserTreeIdx, | |
| ReuseShuffleIndices); | |
| LLVM_DEBUG(dbgs() << "SLP: added a new TreeEntry (CastInst).\n"; | |
| TE->dump()); | |
| TE->setOperands(Operands); | |
| for (unsigned I : seq<unsigned>(VL0->getNumOperands())) | |
| buildTreeRec(TE->getOperand(I), Depth, {TE, I}); | |
| if (ShuffleOrOp == Instruction::Trunc) { | |
| ExtraBitWidthNodes.insert(getOperandEntry(TE, 0)->Idx); | |
| } else if (ShuffleOrOp == Instruction::SIToFP || | |
| ShuffleOrOp == Instruction::UIToFP) { | |
| unsigned NumSignBits = | |
| ComputeNumSignBits(VL0->getOperand(0), *DL, AC, nullptr, DT); | |
| if (auto *OpI = dyn_cast<Instruction>(VL0->getOperand(0))) { | |
| APInt Mask = DB->getDemandedBits(OpI); | |
| NumSignBits = std::max(NumSignBits, Mask.countl_zero()); | |
| } | |
| if (NumSignBits * 2 >= | |
| DL->getTypeSizeInBits(VL0->getOperand(0)->getType())) | |
| ExtraBitWidthNodes.insert(getOperandEntry(TE, 0)->Idx); | |
| } | |
| return; | |
| } | |
| case Instruction::ICmp: | |
| case Instruction::FCmp: { | |
| // Check that all of the compares have the same predicate. | |
| CmpInst::Predicate P0 = cast<CmpInst>(VL0)->getPredicate(); | |
| TreeEntry *TE = newTreeEntry(VL, Bundle /*vectorized*/, S, UserTreeIdx, | |
| ReuseShuffleIndices); | |
| LLVM_DEBUG(dbgs() << "SLP: added a new TreeEntry (CmpInst).\n"; | |
| TE->dump()); | |
| // The ands of the bit tests are peeled into the operands of the node. | |
| if (any_of(VL, IsaPred<TruncInst>)) { | |
| for (ICmpInst *CI : make_isa_range<ICmpInst>(VL)) | |
| ReassocScalars.push_back(CI->getOperand(0)); | |
| RegisterReassocScalars(TE); | |
| } | |
| VLOperands Ops(VL, Operands, S, *this); | |
| if (cast<CmpInst>(VL0)->isCommutative()) { | |
| // Commutative predicate - collect + sort operands of the instructions | |
| // so that each side is more likely to have the same opcode. | |
| assert(P0 == CmpInst::getSwappedPredicate(P0) && | |
| "Commutative Predicate mismatch"); | |
| Ops.reorder(); | |
| Operands.front() = Ops.getVL(0); | |
| Operands.back() = Ops.getVL(1); | |
| } else { | |
| // Collect operands - commute if it uses the swapped predicate. | |
| // Lanes interchangeable with P0 (e.g. x == 0 in an x <u C bundle) | |
| // already have their operands adjusted, no need to commute them. | |
| for (auto [Idx, V] : | |
| make_filter_range(enumerate(VL), [](const auto &P) { | |
| return isa<CmpInst>(P.value()); | |
| })) { | |
| auto *Cmp = cast<CmpInst>(V); | |
| if (Cmp->getPredicate() != P0 && | |
| !CmpSamePredicateHelper::canConvertTo(Cmp, P0)) | |
| std::swap(Operands.front()[Idx], Operands.back()[Idx]); | |
| } | |
| } | |
| TE->setOperands(Operands); | |
| buildTreeRec(Operands.front(), Depth, {TE, 0}); | |
| buildTreeRec(Operands.back(), Depth, {TE, 1}); | |
| if (ShuffleOrOp == Instruction::ICmp) { | |
| unsigned NumSignBits0 = | |
| ComputeNumSignBits(VL0->getOperand(0), *DL, AC, nullptr, DT); | |
| if (NumSignBits0 * 2 >= | |
| DL->getTypeSizeInBits(VL0->getOperand(0)->getType())) | |
| ExtraBitWidthNodes.insert(getOperandEntry(TE, 0)->Idx); | |
| unsigned NumSignBits1 = | |
| ComputeNumSignBits(VL0->getOperand(1), *DL, AC, nullptr, DT); | |
| if (NumSignBits1 * 2 >= | |
| DL->getTypeSizeInBits(VL0->getOperand(1)->getType())) | |
| ExtraBitWidthNodes.insert(getOperandEntry(TE, 1)->Idx); | |
| } | |
| return; | |
| } | |
| case Instruction::Select: | |
| case Instruction::FNeg: | |
| case Instruction::Add: | |
| case Instruction::FAdd: | |
| case Instruction::Sub: | |
| case Instruction::FSub: | |
| case Instruction::Mul: | |
| case Instruction::FMul: | |
| case Instruction::UDiv: | |
| case Instruction::SDiv: | |
| case Instruction::FDiv: | |
| case Instruction::URem: | |
| case Instruction::SRem: | |
| case Instruction::FRem: | |
| case Instruction::Shl: | |
| case Instruction::LShr: | |
| case Instruction::AShr: | |
| case Instruction::And: | |
| case Instruction::Or: | |
| case Instruction::Xor: | |
| case Instruction::Freeze: { | |
| TreeEntry *TE = newTreeEntry(VL, Bundle /*vectorized*/, S, UserTreeIdx, | |
| ReuseShuffleIndices); | |
| LLVM_DEBUG( | |
| dbgs() << "SLP: added a new TreeEntry " | |
| "(SelectInst/UnaryOperator/BinaryOperator/FreezeInst).\n"; | |
| TE->dump()); | |
| if (!ReassocScalars.empty()) { | |
| // Realign columns by value family (cached above as | |
| // ReassocAlignedOperands), then let VLOperands polish the order, | |
| // keeping the polish only if the column-quality score does not regress | |
| // vs the peeled order. That polish is quadratic in the column count, so | |
| // past ReassocReorderColumnLimit keep the realigned seed, which already | |
| // groups the vectorizable columns. The polish swaps values between | |
| // columns per lane, which must not mix added and subtracted leaves in | |
| // one column, so signed nodes polish each sign group separately. | |
| if (Operands.size() <= Opts.slp_reassociate_reorder_limit) { | |
| SmallVector<ValueList> Reordered = ReassocAlignedOperands; | |
| SmallVector<unsigned> GroupIds[2]; | |
| for (unsigned I : seq<unsigned>(NegatedColumns.size())) | |
| GroupIds[NegatedColumns[I]].push_back(I); | |
| for (ArrayRef<unsigned> Group : | |
| make_filter_range(GroupIds, [](ArrayRef<unsigned> Group) { | |
| return Group.size() > 1; | |
| })) { | |
| SmallVector<ValueList> GroupCols = map_to_vector( | |
| Group, [&](unsigned I) { return ReassocAlignedOperands[I]; }); | |
| VLOperands Ops(GroupCols, VL0->getParent(), *this); | |
| Ops.reorder(); | |
| for (unsigned Pos : seq<unsigned>(Group.size())) | |
| Reordered[Group[Pos]] = Ops.getVL(Pos); | |
| } | |
| if (getReassocColumnsQuality(Reordered, *this, S.getOpcode()) >= | |
| ReassocPeeledQuality) | |
| Operands = std::move(Reordered); | |
| else | |
| LLVM_DEBUG(dbgs() << "SLP: reassociation reorder loses column " | |
| "structure, keeping the natural operand " | |
| "order.\n"); | |
| } else { | |
| Operands = std::move(ReassocAlignedOperands); | |
| } | |
| if (HasDupColumnValues()) { | |
| Operands = std::move(NaturalTwoColumns); | |
| ReassocScalars.clear(); | |
| } | |
| } | |
| if (!ReassocScalars.empty()) | |
| RegisterReassocScalars(TE); | |
| else if (isa<BinaryOperator>(VL0) && isCommutative(VL0)) { | |
| VLOperands Ops(VL, Operands, S, *this); | |
| Ops.reorder(); | |
| Operands[0] = Ops.getVL(0); | |
| Operands[1] = Ops.getVL(1); | |
| } | |
| TE->setOperands(Operands); | |
| if (!ReassocScalars.empty() && NegatedColumns.any()) | |
| TE->setReassocNegatedOps(NegatedColumns); | |
| for (unsigned I : seq<unsigned>(TE->getNumOperands())) | |
| buildTreeRec(TE->getOperand(I), Depth + 1, {TE, I}); | |
| return; | |
| } | |
| case Instruction::GetElementPtr: { | |
| TreeEntry *TE = newTreeEntry(VL, Bundle /*vectorized*/, S, UserTreeIdx, | |
| ReuseShuffleIndices); | |
| LLVM_DEBUG(dbgs() << "SLP: added a new TreeEntry (GetElementPtrInst).\n"; | |
| TE->dump()); | |
| TE->setOperands(Operands); | |
| for (unsigned I = 0, Ops = Operands.size(); I < Ops; ++I) | |
| buildTreeRec(Operands[I], Depth + 1, {TE, I}); | |
| return; | |
| } | |
| case Instruction::Store: { | |
| assert(CurrentOrder.empty() && | |
| "Expected ordered store during tree building"); | |
| if (State == TreeEntry::StridedVectorize) { | |
| TreeEntry *TE = | |
| newTreeEntry(VL, TreeEntry::StridedVectorize, Bundle, S, | |
| UserTreeIdx, ReuseShuffleIndices, CurrentOrder); | |
| TreeEntryToStridedPtrInfoMap[TE] = SPtrInfo; | |
| LLVM_DEBUG( | |
| dbgs() << "SLP: added a new TreeEntry (strided StoreInst).\n"; | |
| TE->dump()); | |
| TE->setOperands(Operands); | |
| buildTreeRec(TE->getOperand(0), Depth + 1, {TE, 0}); | |
| return; | |
| } | |
| if (State == TreeEntry::ExpandVectorize) { | |
| assert(ReuseShuffleIndices.empty() && | |
| "Expected no reuse shuffle for an expanded masked store."); | |
| TreeEntry *TE = | |
| newTreeEntry(VL, TreeEntry::ExpandVectorize, Bundle, S, UserTreeIdx, | |
| ExpandShuffleMask, CurrentOrder); | |
| TreeEntryToStridedPtrInfoMap[TE] = SPtrInfo; | |
| LLVM_DEBUG( | |
| dbgs() | |
| << "SLP: added a new TreeEntry (expanded masked StoreInst).\n"; | |
| TE->dump()); | |
| TE->setOperands(Operands); | |
| buildTreeRec(TE->getOperand(0), Depth + 1, {TE, 0}); | |
| return; | |
| } | |
| TreeEntry *TE = newTreeEntry(VL, Bundle /*vectorized*/, S, UserTreeIdx, | |
| ReuseShuffleIndices, CurrentOrder); | |
| LLVM_DEBUG(dbgs() << "SLP: added a new TreeEntry (StoreInst).\n"; | |
| TE->dump()); | |
| TE->setOperands(Operands); | |
| buildTreeRec(TE->getOperand(0), Depth + 1, {TE, 0}); | |
| return; | |
| } | |
| case Instruction::Call: { | |
| // Check if the calls are all to the same vectorizable intrinsic or | |
| // library function. | |
| CallInst *CI = cast<CallInst>(VL0); | |
| Intrinsic::ID ID = getVectorIntrinsicIDForCall(CI, TLI); | |
| TreeEntry *TE = newTreeEntry(VL, Bundle /*vectorized*/, S, UserTreeIdx, | |
| ReuseShuffleIndices); | |
| LLVM_DEBUG(dbgs() << "SLP: added a new TreeEntry (CallInst).\n"; | |
| TE->dump()); | |
| if (isCommutative(VL0)) { | |
| VLOperands Ops(VL, Operands, S, *this); | |
| Ops.reorder(); | |
| Operands[0] = Ops.getVL(0); | |
| Operands[1] = Ops.getVL(1); | |
| } | |
| TE->setOperands(Operands); | |
| for (unsigned I : seq<unsigned>(CI->arg_size())) { | |
| // For scalar operands no need to create an entry since no need to | |
| // vectorize it. | |
| if (isVectorIntrinsicWithScalarOpAtArg(ID, I, TTI)) | |
| continue; | |
| buildTreeRec(TE->getOperand(I), Depth + 1, {TE, I}); | |
| } | |
| return; | |
| } | |
| case Instruction::ShuffleVector: { | |
| TreeEntry *TE = newTreeEntry(VL, Bundle /*vectorized*/, S, UserTreeIdx, | |
| ReuseShuffleIndices); | |
| if (S.isAltShuffle()) { | |
| LLVM_DEBUG(dbgs() << "SLP: added a new TreeEntry (isAltShuffle).\n"; | |
| TE->dump()); | |
| } else { | |
| assert(Opts.slp_revec && "Only supported by REVEC."); | |
| LLVM_DEBUG( | |
| dbgs() << "SLP: added a new TreeEntry (ShuffleVectorInst).\n"; | |
| TE->dump()); | |
| } | |
| // Reorder operands if reordering would enable vectorization. | |
| auto *CI = dyn_cast<CmpInst>(VL0); | |
| if (CI && any_of(make_isa_range<CmpInst>(VL), | |
| [](CmpInst *Cmp) { return !Cmp->isCommutative(); })) { | |
| auto *MainCI = cast<CmpInst>(S.getMainOp()); | |
| auto *AltCI = cast<CmpInst>(S.getAltOp()); | |
| CmpInst::Predicate MainP = MainCI->getPredicate(); | |
| CmpInst::Predicate AltP = AltCI->getPredicate(); | |
| assert(MainP != AltP && | |
| "Expected different main/alternate predicates."); | |
| // Collect operands - commute if it uses the swapped predicate or | |
| // alternate operation. | |
| for (auto [Idx, V] : | |
| make_filter_range(enumerate(VL), [](const auto &P) { | |
| return !isa<PoisonValue>(P.value()); | |
| })) { | |
| auto *Cmp = cast<CmpInst>(V); | |
| if (isAlternateInstruction(Cmp, MainCI, AltCI, *TLI)) { | |
| if (AltP == CmpInst::getSwappedPredicate(Cmp->getPredicate())) | |
| std::swap(Operands.front()[Idx], Operands.back()[Idx]); | |
| } else { | |
| if (MainP == CmpInst::getSwappedPredicate(Cmp->getPredicate())) | |
| std::swap(Operands.front()[Idx], Operands.back()[Idx]); | |
| } | |
| } | |
| TE->setOperands(Operands); | |
| buildTreeRec(Operands.front(), Depth + 1, {TE, 0}); | |
| buildTreeRec(Operands.back(), Depth + 1, {TE, 1}); | |
| return; | |
| } | |
| if (!ReassocScalars.empty()) { | |
| // Peeled alternate chains take the realigned columns; the operand | |
| // reorder below does not preserve the per-lane sign constraints. | |
| Operands = std::move(ReassocAlignedOperands); | |
| if (HasDupColumnValues()) { | |
| Operands = std::move(NaturalTwoColumns); | |
| ReassocScalars.clear(); | |
| } | |
| } | |
| if (ReassocScalars.empty() && (isa<BinaryOperator>(VL0) || CI)) { | |
| VLOperands Ops(VL, Operands, S, *this); | |
| Ops.reorder(); | |
| Operands[0] = Ops.getVL(0); | |
| Operands[1] = Ops.getVL(1); | |
| } | |
| if (!ReassocScalars.empty()) | |
| RegisterReassocScalars(TE); | |
| TE->setOperands(Operands); | |
| for (unsigned I : seq<unsigned>(TE->getNumOperands())) | |
| buildTreeRec(TE->getOperand(I), Depth + 1, {TE, I}); | |
| return; | |
| } | |
| default: | |
| break; | |
| } | |
| llvm_unreachable("Unexpected vectorization of the instructions."); | |
| } | |
| unsigned BoUpSLP::canMapToVector(Type *T) const { | |
| unsigned N = 1; | |
| Type *EltTy = T; | |
| while (isa<StructType, ArrayType, FixedVectorType>(EltTy)) { | |
| if (EltTy->isEmptyTy()) | |
| return 0; | |
| if (auto *ST = dyn_cast<StructType>(EltTy)) { | |
| // Check that struct is homogeneous. | |
| for (const auto *Ty : ST->elements()) | |
| if (Ty != *ST->element_begin()) | |
| return 0; | |
| N *= ST->getNumElements(); | |
| EltTy = *ST->element_begin(); | |
| } else if (auto *AT = dyn_cast<ArrayType>(EltTy)) { | |
| N *= AT->getNumElements(); | |
| EltTy = AT->getElementType(); | |
| } else { | |
| auto *VT = cast<FixedVectorType>(EltTy); | |
| N *= VT->getNumElements(); | |
| EltTy = VT->getElementType(); | |
| } | |
| } | |
| if (!isValidElementType(EltTy, Opts.slp_revec)) | |
| return 0; | |
| size_t VTSize = DL->getTypeStoreSizeInBits(getWidenedType(EltTy, N)); | |
| if (VTSize < MinVecRegSize || VTSize > MaxVecRegSize || | |
| VTSize != DL->getTypeStoreSizeInBits(T)) | |
| return 0; | |
| return N; | |
| } | |
| bool BoUpSLP::canVectorStoreInsertValue(const TreeEntry *E) const { | |
| if (E->getOpcode() != Instruction::InsertValue || | |
| E->State != TreeEntry::Vectorize) | |
| return false; | |
| bool HasStore = false; | |
| for (Value *V : E->Scalars) { | |
| auto *IV = dyn_cast<InsertValueInst>(V); | |
| if (!IV || isDeleted(IV)) | |
| return false; | |
| bool HasExternalUser = false; | |
| for (User *U : IV->users()) { | |
| if (auto *UI = dyn_cast<Instruction>(U); UI && isDeleted(UI)) | |
| continue; | |
| if (is_contained(E->Scalars, U)) | |
| continue; | |
| HasExternalUser = true; | |
| // Only a plain (non-volatile, non-atomic) store may consume the vector | |
| // directly. Volatile/atomic stores must keep their original aggregate | |
| // type and access semantics, so fall back to the store + load roundtrip. | |
| auto *SI = dyn_cast<StoreInst>(U); | |
| if (!SI || !SI->isSimple()) | |
| return false; | |
| HasStore = true; | |
| } | |
| if (HasExternalUser && !IV->hasOneUse()) | |
| return false; | |
| } | |
| return HasStore; | |
| } | |
| FixedVectorType *BoUpSLP::getInsertBuildVectorSrcTy(const TreeEntry *E) const { | |
| Value *VL0 = E->getMainOp(); | |
| if (E->getOpcode() == Instruction::InsertElement) | |
| return cast<FixedVectorType>(VL0->getType()); | |
| assert(E->getOpcode() == Instruction::InsertValue && | |
| "Expected InsertElement or InsertValue node."); | |
| unsigned MaxIdx = E->Scalars.size() - 1; | |
| for (InsertValueInst *I : make_isa_range<InsertValueInst>(E->Scalars)) | |
| MaxIdx = std::max(MaxIdx, I->getIndices().front()); | |
| return cast<FixedVectorType>( | |
| getWidenedType(getValueType(VL0, Opts.slp_revec), MaxIdx + 1)); | |
| } | |
| bool BoUpSLP::canReuseExtract(ArrayRef<Value *> VL, | |
| SmallVectorImpl<unsigned> &CurrentOrder, | |
| bool ResizeAllowed) const { | |
| const auto *It = find_if(VL, IsaPred<ExtractElementInst, ExtractValueInst>); | |
| assert(It != VL.end() && "Expected at least one extract instruction."); | |
| auto *E0 = cast<Instruction>(*It); | |
| // Check if all of the extracts come from the same vector and from the | |
| // correct offset. | |
| Value *Vec = E0->getOperand(0); | |
| CurrentOrder.clear(); | |
| // We have to extract from a vector/aggregate with the same number of elements. | |
| unsigned NElts; | |
| if (E0->getOpcode() == Instruction::ExtractValue) { | |
| NElts = canMapToVector(Vec->getType()); | |
| if (!NElts) | |
| return false; | |
| // Check if load can be rewritten as load of vector. | |
| LoadInst *LI = dyn_cast<LoadInst>(Vec); | |
| if (!LI || !LI->isSimple() || !LI->hasNUses(VL.size())) | |
| return false; | |
| } else { | |
| NElts = cast<FixedVectorType>(Vec->getType())->getNumElements(); | |
| } | |
| unsigned E = VL.size(); | |
| if (!ResizeAllowed && NElts != E) | |
| return false; | |
| SmallVector<int> Indices(E, PoisonMaskElem); | |
| unsigned MinIdx = NElts, MaxIdx = 0; | |
| // Non-extract lanes (copyable elements modeled on an extract main op, or | |
| // undefs) are treated as holes. | |
| for (auto [I, V] : make_filter_range(enumerate(VL), [](const auto &P) { | |
| return isa<ExtractElementInst, ExtractValueInst>(P.value()); | |
| })) { | |
| auto *Inst = cast<Instruction>(V); | |
| if (Inst->getOperand(0) != Vec) | |
| return false; | |
| if (auto *EE = dyn_cast<ExtractElementInst>(Inst)) | |
| if (isa<UndefValue>(EE->getIndexOperand())) | |
| continue; | |
| std::optional<unsigned> Idx = getExtractIndex(Inst); | |
| if (!Idx) | |
| return false; | |
| const unsigned ExtIdx = *Idx; | |
| if (ExtIdx >= NElts) | |
| continue; | |
| Indices[I] = ExtIdx; | |
| if (MinIdx > ExtIdx) | |
| MinIdx = ExtIdx; | |
| if (MaxIdx < ExtIdx) | |
| MaxIdx = ExtIdx; | |
| } | |
| if (MaxIdx - MinIdx + 1 > E) | |
| return false; | |
| if (MaxIdx + 1 <= E) | |
| MinIdx = 0; | |
| // Check that all of the indices extract from the correct offset. | |
| bool ShouldKeepOrder = true; | |
| // Assign to all items the initial value E + 1 so we can check if the extract | |
| // instruction index was used already. | |
| // Also, later we can check that all the indices are used and we have a | |
| // consecutive access in the extract instructions, by checking that no | |
| // element of CurrentOrder still has value E + 1. | |
| CurrentOrder.assign(E, E); | |
| for (unsigned I = 0; I < E; ++I) { | |
| if (Indices[I] == PoisonMaskElem) | |
| continue; | |
| const unsigned ExtIdx = Indices[I] - MinIdx; | |
| if (CurrentOrder[ExtIdx] != E) { | |
| CurrentOrder.clear(); | |
| return false; | |
| } | |
| ShouldKeepOrder &= ExtIdx == I; | |
| CurrentOrder[ExtIdx] = I; | |
| } | |
| if (ShouldKeepOrder) | |
| CurrentOrder.clear(); | |
| return ShouldKeepOrder; | |
| } | |
| bool BoUpSLP::areAllUsersVectorized( | |
| Instruction *I, const SmallDenseSet<Value *> *VectorizedVals) const { | |
| return (I->hasOneUse() && (!VectorizedVals || VectorizedVals->contains(I))) || | |
| all_of(I->users(), [this](User *U) { | |
| return isVectorized(U) || isVectorLikeInstWithConstOps(U) || | |
| (isa<ExtractElementInst>(U) && MustGather.contains(U)); | |
| }); | |
| } | |
| static InstructionCost canConvertToFMA(ArrayRef<Value *> VL, | |
| const InstructionsState &S, | |
| DominatorTree &DT, const DataLayout &DL, | |
| TargetTransformInfo &TTI, | |
| const TargetLibraryInfo &TLI, | |
| const BoUpSLP &R); | |
| /// \returns true if \p Op is a contractable single-use fmul, which the backend | |
| /// fuses with its fadd/fsub user \p I: \p Op is in the block of \p I or the | |
| /// target sinks it there. | |
| static bool isFusableFMulOperand(Value *Op, Instruction *I, | |
| const TargetTransformInfo &TTI) { | |
| Instruction *FMul; | |
| if (!match(Op, m_Instruction(FMul, m_OneUse(m_AllowContract( | |
| m_FMul(m_Value(), m_Value())))))) | |
| return false; | |
| if (FMul->getParent() == I->getParent()) | |
| return true; | |
| SmallVector<Use *> OpsToSink; | |
| return TTI.isProfitableToSinkOperands(I, OpsToSink) && | |
| any_of(OpsToSink, [&](const Use *U) { return U->get() == FMul; }); | |
| } | |
| /// \returns the fmul operand of the fadd/fsub \p I that the backend fuses with | |
| /// \p I into an fmuladd: the first fusable one from either operand, c - a*b | |
| /// included. \p I must allow contraction. | |
| static Instruction *getFusableFMulOperand(Instruction *I, | |
| const TargetTransformInfo &TTI) { | |
| if (!match(I, m_AllowContract(m_CombineOr(m_FAdd(m_Value(), m_Value()), | |
| m_FSub(m_Value(), m_Value()))))) | |
| return nullptr; | |
| for (Value *Op : I->operands()) | |
| if (isFusableFMulOperand(Op, I, TTI)) | |
| return cast<Instruction>(Op); | |
| return nullptr; | |
| } | |
| /// \returns the cost of the binary operator \p I, priced as not fused. No | |
| /// context instruction is passed: targets that model the fmuladd fusion would | |
| /// discount the unfused side of the comparison as well. | |
| static InstructionCost getUnfusedBinOpCost(Instruction *I, | |
| TargetTransformInfo &TTI, | |
| const TargetLibraryInfo &TLI, | |
| TTI::TargetCostKind CostKind) { | |
| assert(match(I, m_BinOp()) && "Expected a binary operator."); | |
| TTI::OperandValueInfo Op1Info = TTI::getOperandInfo(I->getOperand(0)); | |
| TTI::OperandValueInfo Op2Info = TTI::getOperandInfo(I->getOperand(1)); | |
| return TTI.getArithmeticInstrCost( | |
| I->getOpcode(), I->getType(), CostKind, Op1Info, Op2Info, | |
| {I->getOperand(0), I->getOperand(1)}, /*CxtI=*/nullptr, &TLI); | |
| } | |
| std::pair<Instruction *, InstructionCost> | |
| BoUpSLP::getFMulFusingUser(Instruction *I, bool InTree) const { | |
| if (!match(I, m_OneUse(m_FMul(m_Value(), m_Value())))) | |
| return {nullptr, InstructionCost::getInvalid()}; | |
| auto *U = cast<Instruction>(I->user_back()); | |
| // The reduction cost accounts for the fusion of the reduced values into the | |
| // reduction operations itself. The lanes of the combined fmuladd node are | |
| // fused with its own fmul operand node, not with the other operand. | |
| if (getFusableFMulOperand(U, *TTI) != I || | |
| (InTree && ((UserIgnoreList && UserIgnoreList->contains(U)) || | |
| any_of(getTreeEntries(U), [](const TreeEntry *TE) { | |
| return TE->CombinedOp == TreeEntry::FMulAdd; | |
| })))) | |
| return {nullptr, InstructionCost::getInvalid()}; | |
| // The target must actually prefer the fused form. | |
| InstructionCost FMACost = | |
| canConvertToFMA(U, InstructionsState(U, U), *DT, *DL, *TTI, *TLI, *this); | |
| return {FMACost.isValid() ? U : nullptr, FMACost}; | |
| } | |
| bool BoUpSLP::isFusedVectorizedFAddSub(Instruction *U) const { | |
| return any_of(getTreeEntries(U), [&](const TreeEntry *TE) { | |
| return !DeletedNodes.contains(TE) && !TransformedToGatherNodes.contains(TE); | |
| }); | |
| } | |
| InstructionCost BoUpSLP::getUnfusedFMulsPenalty(const TreeEntry &TE) const { | |
| if (!TE.hasState() || TE.isGather() || | |
| (TE.getOpcode() != Instruction::FMul && | |
| TE.getAltOpcode() != Instruction::FMul)) | |
| return TTI::TCC_Free; | |
| InstructionCost Penalty = TTI::TCC_Free; | |
| for (Value *V : TE.Scalars) { | |
| Instruction *I; | |
| if (!match(V, m_Instruction(I, m_FMul(m_Value(), m_Value()))) || | |
| (TE.hasCopyableElements() && TE.isCopyableElement(I))) | |
| continue; | |
| if (Instruction *U = getFMulFusingUser(I).first; | |
| U && isFusedVectorizedFAddSub(U)) | |
| Penalty += getUnfusedBinOpCost(I, *TTI, *TLI, CostKind); | |
| } | |
| return Penalty; | |
| } | |
| // A poor-throughput entry's real vector-vs-scalar savings (fdiv/frem/fsqrt) | |
| // are already folded into TreeCost like any other entry, including all | |
| // shuffle/insert/extract overhead elsewhere in the tree. So bypassing the | |
| // instruction-count veto just has to trust that already-complete TreeCost | |
| // instead of guessing at extra savings: heavy gather/shuffle overhead raises | |
| // TreeCost too, and will still block the bypass. | |
| bool BoUpSLP::bypassesInstCountCheck(InstructionCost TreeCost) const { | |
| if (!Opts.slp_vectorize_poor_throughput || TreeCost >= -getCostThreshold()) | |
| return false; | |
| PoorThroughputOpCache Cache; | |
| return any_of(VectorizableTree, [&](const std::unique_ptr<TreeEntry> &Ptr) { | |
| const TreeEntry &TE = *Ptr; | |
| return TE.hasState() && !DeletedNodes.contains(&TE) && !TE.isGather() && | |
| !TransformedToGatherNodes.contains(&TE) && | |
| TE.State != TreeEntry::CombinedVectorize && | |
| isPoorThroughputOp(Opts, TE.getMainOp(), *TTI, *TLI, Cache, | |
| CostKind); | |
| }); | |
| } | |
| bool BoUpSLP::collectGatherUserOps( | |
| const TreeEntry &E, SmallVectorImpl<TTI::BuildVectorUseOp> &UserOps) const { | |
| UserOps.clear(); | |
| for (const auto &TE : | |
| make_filter_range(VectorizableTree, [&](const auto &TE) { | |
| return !DeletedNodes.contains(TE.get()); | |
| })) { | |
| if (!(TE->isGather() || TransformedToGatherNodes.contains(TE.get())) || | |
| !E.isSame(TE->Scalars)) | |
| continue; | |
| const TreeEntry *UserTE = TE->UserTreeIndex.UserTE; | |
| if (!UserTE || !UserTE->hasState() || UserTE->isAltShuffle() || | |
| TransformedToGatherNodes.contains(UserTE)) | |
| return false; | |
| Intrinsic::ID IntrinsicID = Intrinsic::not_intrinsic; | |
| if (auto *II = dyn_cast<IntrinsicInst>(UserTE->getMainOp())) | |
| IntrinsicID = II->getIntrinsicID(); | |
| UserOps.emplace_back(UserTE->getOpcode(), IntrinsicID, | |
| TE->UserTreeIndex.EdgeIdx); | |
| } | |
| return !UserOps.empty(); | |
| } | |
| uint64_t BoUpSLP::getNumScalarInsts(bool HasTreeLoop) { | |
| uint64_t Total = 0; | |
| for (const std::unique_ptr<TreeEntry> &Ptr : VectorizableTree) { | |
| const TreeEntry &TE = *Ptr; | |
| if (DeletedNodes.contains(&TE)) | |
| continue; | |
| // CombinedVectorize entries (e.g. the fmul child of an FMulAdd, or the | |
| // cmp child of a MinMax select) are absorbed into the parent on both | |
| // scalar and vector sides. The backend fuses fadd+fmul -> fma and | |
| // select+cmp -> smin/smax even for scalar code, so skip to avoid | |
| // double-counting. Checked before the scale lookup below: some of | |
| // these entries started out as constant-only gather nodes and have no | |
| // valid instruction state to key the loop-nest scale off of. | |
| if (TE.State == TreeEntry::CombinedVectorize) | |
| continue; | |
| uint64_t Scale = getEntryEffectiveScale(TE); | |
| if (HasTreeLoop && Scale <= 1) | |
| continue; | |
| unsigned Count = 0; | |
| if (TE.isGather() || TransformedToGatherNodes.contains(&TE)) { | |
| // Count extractelement scalars in gathers - they exist in the scalar | |
| // code regardless of vectorization. ExtractElement instructions | |
| // become free when the vector input is used directly. | |
| for (Value *V : TE.Scalars) | |
| if (isa<ExtractElementInst>(V)) | |
| ++Count; | |
| Total = SaturatingMultiplyAdd<uint64_t>(Count, Scale, Total); | |
| continue; | |
| } | |
| // Each vectorize entry represents a bundle of scalar instructions. | |
| // Count per-entry without cross-entry deduplication, since shared | |
| // scalars across entries still represent separate work in scalar code. | |
| for (Instruction *I : make_isa_range<Instruction>(TE.Scalars)) { | |
| if (TE.hasCopyableElements() && TE.isCopyableElement(I)) | |
| continue; | |
| ++Count; | |
| // Calculate calls/divs/rems twice, they may cost higher, so better to | |
| // include their count twice to mimic slightly real cost here. | |
| if (I->isIntDivRem() || I->isFPDivRem()) | |
| ++Count; | |
| if (auto *CI = dyn_cast<CallInst>(I)) { | |
| Intrinsic::ID BaseID = getVectorIntrinsicIDForCall(CI, TLI); | |
| if (!isTriviallyVectorizable(BaseID)) | |
| ++Count; | |
| } | |
| } | |
| // Count peeled intermediates in the scalar baseline (not in Scalars). | |
| Count += TE.getReassocScalars().size(); | |
| // Even when the whole node is not combined, individual scalar | |
| // instructions may be fused by the backend. Each fused pair (e.g. | |
| // fadd+fmul -> fma, select+cmp -> smin/smax) becomes a single scalar | |
| // instruction, absorbing the operand instruction. Subtract 1 for each | |
| // such match to avoid over-counting the scalar side. | |
| if (TE.CombinedOp == TreeEntry::NotCombinedOp && TE.hasState()) { | |
| unsigned Opcode = TE.getOpcode(); | |
| if (Opcode == Instruction::Select) { | |
| for (Value *V : make_filter_range(TE.Scalars, [&](Value *V) { | |
| return !TE.hasCopyableElements() || !TE.isCopyableElement(V); | |
| })) { | |
| auto *SI = dyn_cast<SelectInst>(V); | |
| if (!SI) | |
| continue; | |
| auto [ID, _] = canConvertToMinOrMaxIntrinsic({V}); | |
| if (ID != Intrinsic::not_intrinsic) { | |
| assert(Count > 0 && "Underflow in scalar inst count (minmax)"); | |
| --Count; | |
| } | |
| } | |
| } else { | |
| for (Instruction *I : make_filter_range( | |
| make_isa_range<Instruction>(TE.Scalars), [&](Instruction *V) { | |
| return !TE.hasCopyableElements() || !TE.isCopyableElement(V); | |
| })) { | |
| if (match(I, m_CombineOr(m_FAdd(m_Value(), m_Value()), | |
| m_FSub(m_Value(), m_Value())))) { | |
| if (canConvertToFMA(I, InstructionsState(I, I), *DT, *DL, *TTI, | |
| *TLI, *this) | |
| .isValid()) { | |
| assert(Count > 0 && "Underflow in scalar inst count (fma)"); | |
| --Count; | |
| } | |
| } else if (match(I, m_FMul(m_Value(), m_Value()))) { | |
| // The fmul, fused into a scalar user, disappears into the user's | |
| // fmuladd. A vectorized fadd/fsub user lane already accounts for | |
| // the fusion. | |
| Instruction *U = getFMulFusingUser(I).first; | |
| if (U && !isFusedVectorizedFAddSub(U)) { | |
| assert(Count > 0 && "Underflow in scalar inst count (fma)"); | |
| --Count; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| Total = SaturatingMultiplyAdd<uint64_t>(Count, Scale, Total); | |
| } | |
| return Total; | |
| } | |
| uint64_t BoUpSLP::getNumVectorInsts(bool HasTreeLoop, bool CountExtracts) { | |
| uint64_t Total = 0; | |
| // The alternate op, fused with the multiplication by the target, is counted | |
| // with the multiplication node, the same way as the scalar fmas. | |
| auto IsFusedAlt = [&](const TreeEntry &TE) { | |
| if (!TE.hasState() || TE.State != TreeEntry::Vectorize || | |
| DeletedNodes.contains(&TE) || TransformedToGatherNodes.contains(&TE) || | |
| !TE.isAltShuffle() || TE.hasReassocScalars() || | |
| !TE.getMainOp()->getType()->isFloatingPointTy() || | |
| !TE.getOperations().isAddSubLikeOp()) | |
| return false; | |
| Type *ScalarTy = TE.getMainOp()->getType(); | |
| auto *VecTy = cast<VectorType>(getWidenedType(ScalarTy, TE.Scalars.size())); | |
| SmallBitVector OpcodeMask(getAltInstrMask( | |
| TE.Scalars, ScalarTy, TE.getOpcode(), TE.getAltOpcode())); | |
| return TTI->isLegalAltInstr(VecTy, TE.getOpcode(), TE.getAltOpcode(), | |
| OpcodeMask, TE.Scalars) && | |
| canConvertToFMA(TE.Scalars, TE.getOperations(), *DT, *DL, *TTI, *TLI, | |
| *this) | |
| .isValid(); | |
| }; | |
| // The scalar count already credits the fused scalars as fmas, so only a tree | |
| // with the fused alternate op is counted at the level of the machine | |
| // instructions. Doing the same for the other trees changes their VF=2 | |
| // decisions. | |
| const bool HasFusedAlt = | |
| any_of(VectorizableTree, [&](const std::unique_ptr<TreeEntry> &Ptr) { | |
| return IsFusedAlt(*Ptr); | |
| }); | |
| // Source vector -> max scale among the gather entries sharing it, so the | |
| // combined shufflevector is still weighted like an in-loop entry below. | |
| SmallDenseMap<Value *, uint64_t, 4> GatherExtractSourceVecs; | |
| for (const std::unique_ptr<TreeEntry> &Ptr : VectorizableTree) { | |
| const TreeEntry &TE = *Ptr; | |
| if (DeletedNodes.contains(&TE)) | |
| continue; | |
| if (TE.State == TreeEntry::CombinedVectorize) | |
| continue; | |
| uint64_t Scale = getEntryEffectiveScale(TE); | |
| if (HasTreeLoop && Scale <= 1) | |
| continue; | |
| unsigned Count = 0; | |
| if (TE.isGather() || TransformedToGatherNodes.contains(&TE)) { | |
| if (TE.hasState()) { | |
| if (const TreeEntry *E = | |
| getSameValuesTreeEntry(TE.getMainOp(), TE.Scalars); | |
| E && E != &TE && E->getVectorFactor() == TE.getVectorFactor()) | |
| continue; | |
| SmallVector<Value *> RevScalars(TE.Scalars.rbegin(), TE.Scalars.rend()); | |
| if (const TreeEntry *E = | |
| getSameValuesTreeEntry(TE.getMainOp(), RevScalars); | |
| E && E->getVectorFactor() == TE.getVectorFactor()) { | |
| Total = SaturatingAdd(Total, Scale); | |
| continue; | |
| } | |
| } | |
| // ExtractElement gathers from the same source vector become a single | |
| // shufflevector. Collect source vectors globally across all gather | |
| // entries and count once at the end. | |
| if (all_of(TE.Scalars, | |
| IsaPred<ExtractElementInst, UndefValue, Constant>)) { | |
| for (ExtractElementInst *EE : | |
| make_isa_range<ExtractElementInst>(TE.Scalars)) { | |
| uint64_t &VecScale = | |
| GatherExtractSourceVecs.try_emplace(EE->getVectorOperand(), 0) | |
| .first->second; | |
| VecScale = std::max(VecScale, Scale); | |
| } | |
| } else { | |
| // A splat is a single broadcast. In loops, its per-lane count, scaled | |
| // by the trip count, rejects the profitable trees. | |
| if ((HasFusedAlt || HasTreeLoop) && isSplat(TE.Scalars)) | |
| Count = !isConstant(TE.Scalars.front()); | |
| else | |
| Count = TE.Scalars.size() - count_if(TE.Scalars, isConstant); | |
| } | |
| Total = SaturatingMultiplyAdd<uint64_t>(Count, Scale, Total); | |
| continue; | |
| } | |
| // InsertElement/ExtractElement vectorize entries don't produce real | |
| // vector instructions - InsertElement at root IS the result, and | |
| // ExtractElement entries reference the input vector directly. | |
| if (TE.getOpcode() == Instruction::InsertElement || | |
| TE.getOpcode() == Instruction::ExtractElement) | |
| continue; | |
| if (TE.getOpcode() == Instruction::InsertValue) { | |
| // InsertValue materializes via store + load unless it is stored directly | |
| // as a vector. | |
| if (!canVectorStoreInsertValue(&TE)) | |
| Count += 2; | |
| if (!TE.ReorderIndices.empty() || !TE.ReuseShuffleIndices.empty()) | |
| ++Count; | |
| Total = SaturatingMultiplyAdd<uint64_t>(Count, Scale, Total); | |
| continue; | |
| } | |
| if (IsFusedAlt(TE)) | |
| continue; | |
| if (TE.State == TreeEntry::SplitVectorize) | |
| Count += 2; | |
| else if (TE.hasReassocScalars()) | |
| // N operand columns need N-1 binary combines. | |
| Count += TE.getNumOperands() - 1; | |
| else | |
| ++Count; | |
| if (!TE.ReorderIndices.empty() || !TE.ReuseShuffleIndices.empty()) | |
| ++Count; | |
| Total = SaturatingMultiplyAdd<uint64_t>(Count, Scale, Total); | |
| } | |
| for (const auto &VecAndScale : GatherExtractSourceVecs) | |
| Total = SaturatingAdd(Total, VecAndScale.second); | |
| if (!CountExtracts) | |
| return Total; | |
| // Count extract instructions from ExternalUses, skipping insertelements | |
| // (those get folded into shuffles, not real extracts). | |
| SmallPtrSet<Value *, 8> CountedExtracts; | |
| for (const ExternalUser &EU : | |
| make_filter_range(ExternalUses, [](const ExternalUser &EU) { | |
| return !isa_and_nonnull<InsertElementInst>(EU.User); | |
| })) { | |
| if (EU.User && EphValues.count(EU.User)) | |
| continue; | |
| // Vector-typed scalars are consumed as whole vectors, no extract | |
| // instruction is emitted for them. | |
| if (isVectorizedTy(EU.Scalar->getType()) && | |
| (!Opts.slp_revec || | |
| (EU.E.hasState() && EU.E.getOpcode() == Instruction::InsertElement))) | |
| continue; | |
| if (ExternalUsesAsOriginalScalar.contains(EU.Scalar)) | |
| continue; | |
| if (!CountedExtracts.insert(EU.Scalar).second) | |
| continue; | |
| // The extract of the lane, which is free in the target, is not emitted. | |
| Type *ScalarTy = EU.Scalar->getType(); | |
| if (HasFusedAlt && !MinBWs.contains(&EU.E) && | |
| VectorType::isValidElementType(ScalarTy) && | |
| TTI->getVectorInstrCost( | |
| Instruction::ExtractElement, | |
| getWidenedType(ScalarTy, EU.E.getVectorFactor()), CostKind, | |
| EU.Lane) == TTI::TCC_Free) | |
| continue; | |
| ++Total; | |
| } | |
| return Total; | |
| } | |
| void BoUpSLP::TreeEntry::buildAltOpShuffleMask( | |
| const function_ref<bool(Instruction *)> IsAltOp, SmallVectorImpl<int> &Mask, | |
| SmallVectorImpl<Value *> *OpScalars, | |
| SmallVectorImpl<Value *> *AltScalars) const { | |
| unsigned Sz = Scalars.size(); | |
| Mask.assign(Sz, PoisonMaskElem); | |
| SmallVector<int> OrderMask; | |
| if (!ReorderIndices.empty()) | |
| inversePermutation(ReorderIndices, OrderMask); | |
| for (unsigned I = 0; I < Sz; ++I) { | |
| unsigned Idx = I; | |
| if (!ReorderIndices.empty()) | |
| Idx = OrderMask[I]; | |
| if (isa<PoisonValue>(Scalars[Idx])) | |
| continue; | |
| auto *OpInst = cast<Instruction>(Scalars[Idx]); | |
| if (IsAltOp(OpInst)) { | |
| Mask[I] = Sz + Idx; | |
| if (AltScalars) | |
| AltScalars->push_back(OpInst); | |
| } else { | |
| Mask[I] = Idx; | |
| if (OpScalars) | |
| OpScalars->push_back(OpInst); | |
| } | |
| } | |
| if (!ReuseShuffleIndices.empty()) { | |
| SmallVector<int> NewMask(ReuseShuffleIndices.size(), PoisonMaskElem); | |
| transform(ReuseShuffleIndices, NewMask.begin(), [&Mask](int Idx) { | |
| return Idx != PoisonMaskElem ? Mask[Idx] : PoisonMaskElem; | |
| }); | |
| Mask.swap(NewMask); | |
| } | |
| } | |
| static bool isMainInstruction(Instruction *I, Instruction *MainOp, | |
| Instruction *AltOp, | |
| const TargetLibraryInfo &TLI) { | |
| return InstructionsState(MainOp, AltOp).getMatchingMainOpOrAltOp(I) == MainOp; | |
| } | |
| void BoUpSLP::reorderGatherNode(TreeEntry &TE) { | |
| assert(TE.isGather() && TE.ReorderIndices.empty() && | |
| "Expected gather node without reordering."); | |
| DenseMap<std::pair<size_t, Value *>, SmallVector<LoadInst *>> LoadsMap; | |
| SmallSet<size_t, 2> LoadKeyUsed; | |
| // Do not reorder nodes if it small (just 2 elements), all-constant or all | |
| // instructions have same opcode already. | |
| if (TE.Scalars.size() == 2 || (TE.hasState() && !TE.isAltShuffle()) || | |
| all_of(TE.Scalars, isConstant)) | |
| return; | |
| if (any_of(seq<unsigned>(TE.Idx), [&](unsigned Idx) { | |
| return VectorizableTree[Idx]->isSame(TE.Scalars); | |
| })) | |
| return; | |
| auto GenerateLoadsSubkey = [&](size_t Key, LoadInst *LI) { | |
| Key = hash_combine(hash_value(LI->getParent()->getNumber()), Key); | |
| Value *Ptr = getUnderlyingObject(LI->getPointerOperand(), | |
| Opts.slp_recursion_max_depth); | |
| if (LoadKeyUsed.contains(Key)) { | |
| auto LIt = LoadsMap.find(std::make_pair(Key, Ptr)); | |
| if (LIt != LoadsMap.end()) { | |
| for (LoadInst *RLI : LIt->second) { | |
| if (getPointersDiff(RLI->getType(), RLI->getPointerOperand(), | |
| LI->getType(), LI->getPointerOperand(), *DL, *SE, | |
| /*StrictCheck=*/true)) | |
| return hash_value(RLI->getPointerOperand()); | |
| } | |
| for (LoadInst *RLI : LIt->second) { | |
| if (arePointersCompatible(RLI->getPointerOperand(), | |
| LI->getPointerOperand(), *TLI, | |
| Opts.slp_recursion_max_depth)) { | |
| hash_code SubKey = hash_value(RLI->getPointerOperand()); | |
| return SubKey; | |
| } | |
| } | |
| if (LIt->second.size() > 2) { | |
| hash_code SubKey = | |
| hash_value(LIt->second.back()->getPointerOperand()); | |
| return SubKey; | |
| } | |
| } | |
| } | |
| LoadKeyUsed.insert(Key); | |
| LoadsMap.try_emplace(std::make_pair(Key, Ptr)).first->second.push_back(LI); | |
| return hash_value(LI->getPointerOperand()); | |
| }; | |
| MapVector<size_t, MapVector<size_t, SmallVector<Value *>>> SortedValues; | |
| SmallDenseMap<Value *, SmallVector<unsigned>, 8> KeyToIndex; | |
| bool IsOrdered = true; | |
| unsigned NumInstructions = 0; | |
| // Try to "cluster" scalar instructions, to be able to build extra vectorized | |
| // nodes. | |
| for (auto [I, V] : enumerate(TE.Scalars)) { | |
| size_t Key = 1, Idx = 1; | |
| if (auto *Inst = dyn_cast<Instruction>(V); | |
| Inst && !isa<ExtractElementInst, LoadInst, CastInst>(V) && | |
| !isDeleted(Inst) && !isVectorized(V)) { | |
| std::tie(Key, Idx) = generateKeySubkey(V, TLI, GenerateLoadsSubkey, | |
| /*AllowAlternate=*/false); | |
| ++NumInstructions; | |
| } | |
| auto &Container = SortedValues[Key]; | |
| if (IsOrdered && !KeyToIndex.contains(V) && | |
| !(isa<Constant, ExtractElementInst>(V) || | |
| isVectorLikeInstWithConstOps(V)) && | |
| ((Container.contains(Idx) && | |
| KeyToIndex.at(Container[Idx].back()).back() != I - 1) || | |
| (!Container.empty() && !Container.contains(Idx) && | |
| KeyToIndex.at(Container.back().second.back()).back() != I - 1))) | |
| IsOrdered = false; | |
| auto &KTI = KeyToIndex[V]; | |
| if (KTI.empty()) | |
| Container[Idx].push_back(V); | |
| KTI.push_back(I); | |
| } | |
| SmallVector<std::pair<unsigned, unsigned>> SubVectors; | |
| APInt DemandedElts = APInt::getAllOnes(TE.Scalars.size()); | |
| if (!IsOrdered && NumInstructions > 1) { | |
| unsigned Cnt = 0; | |
| TE.ReorderIndices.resize(TE.Scalars.size(), TE.Scalars.size()); | |
| for (const auto &D : SortedValues) { | |
| for (const auto &P : D.second) { | |
| unsigned Sz = 0; | |
| for (Value *V : P.second) { | |
| ArrayRef<unsigned> Indices = KeyToIndex.at(V); | |
| for (auto [K, Idx] : enumerate(Indices)) { | |
| TE.ReorderIndices[Cnt + K] = Idx; | |
| TE.Scalars[Cnt + K] = V; | |
| } | |
| Sz += Indices.size(); | |
| Cnt += Indices.size(); | |
| } | |
| if (Sz > 1 && isa<Instruction>(P.second.front())) { | |
| const unsigned SubVF = getFloorFullVectorNumberOfElements( | |
| *TTI, TE.Scalars.front()->getType(), Sz, Opts.slp_revec); | |
| SubVectors.emplace_back(Cnt - Sz, SubVF); | |
| for (unsigned I : seq<unsigned>(Cnt - Sz, Cnt - Sz + SubVF)) | |
| DemandedElts.clearBit(I); | |
| } else if (!P.second.empty() && isConstant(P.second.front())) { | |
| for (unsigned I : seq<unsigned>(Cnt - Sz, Cnt)) | |
| DemandedElts.clearBit(I); | |
| } | |
| } | |
| } | |
| } | |
| // Reuses always require shuffles, so consider it as profitable. | |
| if (!TE.ReuseShuffleIndices.empty() || TE.ReorderIndices.empty()) | |
| return; | |
| // Do simple cost estimation. | |
| InstructionCost Cost = 0; | |
| auto *ScalarTy = TE.Scalars.front()->getType(); | |
| auto *VecTy = cast<VectorType>(getWidenedType(ScalarTy, TE.Scalars.size())); | |
| for (auto [Idx, Sz] : SubVectors) { | |
| Cost += getShuffleCost(*TTI, TTI::SK_InsertSubvector, VecTy, CostKind, {}, | |
| Idx, cast<VectorType>(getWidenedType(ScalarTy, Sz))); | |
| } | |
| Cost += getScalarizationOverhead(*TTI, Opts.slp_revec, ScalarTy, VecTy, | |
| DemandedElts, | |
| /*Insert=*/true, | |
| /*Extract=*/false, CostKind); | |
| int Sz = TE.Scalars.size(); | |
| SmallVector<int> ReorderMask(TE.ReorderIndices.begin(), | |
| TE.ReorderIndices.end()); | |
| for (unsigned I : seq<unsigned>(Sz)) { | |
| Value *V = TE.getOrdered(I); | |
| if (isa<PoisonValue>(V)) { | |
| ReorderMask[I] = PoisonMaskElem; | |
| } else if (isConstant(V) || DemandedElts[I]) { | |
| ReorderMask[I] = I + TE.ReorderIndices.size(); | |
| } | |
| } | |
| Cost += getShuffleCost(*TTI, | |
| any_of(ReorderMask, [&](int I) { return I >= Sz; }) | |
| ? TTI::SK_PermuteTwoSrc | |
| : TTI::SK_PermuteSingleSrc, | |
| VecTy, CostKind, ReorderMask); | |
| DemandedElts = APInt::getAllOnes(TE.Scalars.size()); | |
| ReorderMask.assign(Sz, PoisonMaskElem); | |
| for (unsigned I : seq<unsigned>(Sz)) { | |
| Value *V = TE.getOrdered(I); | |
| if (isConstant(V)) { | |
| DemandedElts.clearBit(I); | |
| if (!isa<PoisonValue>(V)) | |
| ReorderMask[I] = I; | |
| } else { | |
| ReorderMask[I] = I + Sz; | |
| } | |
| } | |
| InstructionCost BVCost = getScalarizationOverhead( | |
| *TTI, Opts.slp_revec, ScalarTy, VecTy, DemandedElts, | |
| /*Insert=*/true, /*Extract=*/false, CostKind); | |
| if (!DemandedElts.isAllOnes()) | |
| BVCost += getShuffleCost(*TTI, TTI::SK_PermuteTwoSrc, VecTy, CostKind, | |
| ReorderMask); | |
| if (Cost >= BVCost) { | |
| SmallVector<int> Mask(TE.ReorderIndices.begin(), TE.ReorderIndices.end()); | |
| reorderScalars(TE.Scalars, Mask); | |
| TE.ReorderIndices.clear(); | |
| } | |
| } | |
| /// Check if we can convert fadd/fsub sequence to FMAD. | |
| /// \returns Cost of the FMAD, if conversion is possible, invalid cost | |
| /// otherwise. | |
| static InstructionCost canConvertToFMA(ArrayRef<Value *> VL, | |
| const InstructionsState &S, | |
| DominatorTree &DT, const DataLayout &DL, | |
| TargetTransformInfo &TTI, | |
| const TargetLibraryInfo &TLI, | |
| const BoUpSLP &R) { | |
| assert(all_of(VL, | |
| [](Value *V) { | |
| return V->getType()->getScalarType()->isFloatingPointTy(); | |
| }) && | |
| "Can only convert to FMA for floating point types"); | |
| assert(S.isAddSubOrFNegLikeOp() && | |
| "Can only convert to FMA for add/sub or fneg chain links"); | |
| const TTI::TargetCostKind CostKind = R.getCostKind(); | |
| auto CheckForContractable = [](ArrayRef<Value *> VL, | |
| const InstructionsState &S) { | |
| FastMathFlags FMF; | |
| FMF.set(); | |
| for (Instruction *I : make_isa_range<Instruction>(VL)) { | |
| if (S.isCopyableElement(I)) | |
| continue; | |
| Instruction *MatchingI = S.getMatchingMainOpOrAltOp(I); | |
| if (S.getMainOp() != MatchingI && S.getAltOp() != MatchingI) | |
| continue; | |
| if (auto *FPCI = dyn_cast<FPMathOperator>(I)) | |
| FMF &= FPCI->getFastMathFlags(); | |
| } | |
| return FMF.allowContract(); | |
| }; | |
| if (!CheckForContractable(VL, S)) | |
| return InstructionCost::getInvalid(); | |
| // fmul also should be contractable | |
| InstructionsCompatibilityAnalysis Analysis(DT, DL, TTI, TLI); | |
| SmallVector<BoUpSLP::ValueList> Operands = Analysis.buildOperands(S, VL, R); | |
| // The operands follow the majority pattern of the lanes, while the backend | |
| // fuses the fmul from either operand (c - a*b included); move it to the | |
| // first column. | |
| if (Operands.size() == 2) { | |
| for (auto [Idx, V] : enumerate(VL)) { | |
| auto *I = dyn_cast<Instruction>(V); | |
| if (!I || S.isCopyableElement(I)) | |
| continue; | |
| if (Instruction *FMul = getFusableFMulOperand(I, TTI); | |
| FMul && Operands[1][Idx] == FMul) | |
| std::swap(Operands[0][Idx], Operands[1][Idx]); | |
| } | |
| } | |
| // The lanes without the fmul, e.g. the constant lanes of the copyable nodes, | |
| // are the copyable lanes of the fmul column. | |
| InstructionsState OpS = Analysis.buildInstructionsState(Operands.front(), R); | |
| if (!OpS.valid()) | |
| return InstructionCost::getInvalid(); | |
| if (OpS.isAltShuffle() || OpS.getOpcode() != Instruction::FMul) | |
| return InstructionCost::getInvalid(); | |
| if (!CheckForContractable(Operands.front(), OpS)) | |
| return InstructionCost::getInvalid(); | |
| // Compare the costs. | |
| InstructionCost FMulPlusFAddCost = 0; | |
| InstructionCost FMACost = 0; | |
| FastMathFlags FMF; | |
| FMF.set(); | |
| const bool IsArithmeticState = S.isAddSubLikeOp() || S.isMulDivLikeOp() || | |
| S.isShiftOp() || S.isBitwiseLogicOp(); | |
| // A dropped fneg link stands in for the sign-flipped combine its operand | |
| // joins and is costed as an fadd plus the fneg itself (both are removed). | |
| auto GetLinkCost = [&](Instruction *I, bool IsCopyable = false) { | |
| if (S.getOpcode() != Instruction::FNeg) { | |
| if (IsCopyable || !IsArithmeticState || | |
| (I->getOpcode() != S.getOpcode() && | |
| I->getOpcode() != S.getAltOpcode())) | |
| return TTI.getInstructionCost(I, CostKind); | |
| return getUnfusedBinOpCost(I, TTI, TLI, CostKind); | |
| } | |
| return TTI.getArithmeticInstrCost(Instruction::FAdd, I->getType(), CostKind, | |
| {}, | |
| TTI::getOperandInfo(I->getOperand(0))) + | |
| TTI.getArithmeticInstrCost(Instruction::FNeg, I->getType(), CostKind, | |
| TTI::getOperandInfo(I->getOperand(0))); | |
| }; | |
| for (Instruction *I : make_isa_range<Instruction>(VL)) { | |
| const bool IsCopyable = S.isCopyableElement(I); | |
| if (!IsCopyable) | |
| if (auto *FPCI = dyn_cast<FPMathOperator>(I)) | |
| FMF &= FPCI->getFastMathFlags(); | |
| FMulPlusFAddCost += GetLinkCost(I, IsCopyable); | |
| } | |
| unsigned NumOps = 0; | |
| for (auto [V, Op] : | |
| make_filter_range(zip(VL, Operands.front()), [&](const auto &P) { | |
| return !S.isCopyableElement(std::get<0>(P)); | |
| })) { | |
| auto *I = dyn_cast<Instruction>(Op); | |
| if (!I || OpS.isCopyableElement(I) || | |
| !isFusableFMulOperand(I, cast<Instruction>(V), TTI)) { | |
| if (auto *OpI = dyn_cast<Instruction>(V)) | |
| FMACost += GetLinkCost(OpI); | |
| if (I) | |
| FMACost += TTI.getInstructionCost(I, CostKind); | |
| continue; | |
| } | |
| ++NumOps; | |
| if (auto *FPCI = dyn_cast<FPMathOperator>(I)) | |
| FMF &= FPCI->getFastMathFlags(); | |
| FMulPlusFAddCost += getUnfusedBinOpCost(I, TTI, TLI, CostKind); | |
| } | |
| Type *Ty = VL.front()->getType(); | |
| IntrinsicCostAttributes ICA(Intrinsic::fmuladd, Ty, {Ty, Ty, Ty}, FMF); | |
| FMACost += NumOps * TTI.getIntrinsicInstrCost(ICA, CostKind); | |
| return FMACost < FMulPlusFAddCost ? FMACost : InstructionCost::getInvalid(); | |
| } | |
| bool BoUpSLP::matchesShlZExt(const TreeEntry &TE, OrdersType &Order, | |
| bool &IsBSwap, bool &ForLoads) const { | |
| assert(TE.hasState() && TE.getOpcode() == Instruction::Shl && | |
| "Expected Shl node."); | |
| IsBSwap = false; | |
| ForLoads = false; | |
| if (TE.State != TreeEntry::Vectorize || TE.isAltShuffle() || | |
| !TE.ReorderIndices.empty() || !TE.ReuseShuffleIndices.empty() || | |
| MinBWs.contains(&TE) || | |
| any_of(TE.Scalars, [](Value *V) { return !V->hasOneUse(); })) | |
| return false; | |
| Type *ScalarTy = TE.getMainOp()->getType(); | |
| // TODO: Check if same can be done for the vector types. | |
| if (!ScalarTy->isIntegerTy()) | |
| return false; | |
| if (ScalarTy->isVectorTy()) | |
| return false; | |
| const unsigned Sz = DL->getTypeSizeInBits(ScalarTy); | |
| const TreeEntry *LhsTE = getOperandEntry(&TE, /*Idx=*/0); | |
| const TreeEntry *RhsTE = getOperandEntry(&TE, /*Idx=*/1); | |
| // Lhs should be zext i<stride> to I<sz>. | |
| if (!(LhsTE->State == TreeEntry::Vectorize && | |
| LhsTE->getOpcode() == Instruction::ZExt && !LhsTE->isAltShuffle() && | |
| LhsTE->ReorderIndices.empty() && LhsTE->ReuseShuffleIndices.empty() && | |
| !MinBWs.contains(LhsTE) && | |
| all_of(LhsTE->Scalars, [](Value *V) { return V->hasOneUse(); }))) | |
| return false; | |
| Type *SrcScalarTy = cast<ZExtInst>(LhsTE->getMainOp())->getSrcTy(); | |
| unsigned Stride = DL->getTypeSizeInBits(SrcScalarTy); | |
| if (!isPowerOf2_64(Stride) || Stride >= Sz || Sz % Stride != 0 || | |
| !isPowerOf2_64(LhsTE->getVectorFactor())) | |
| return false; | |
| if (!(RhsTE->isGather() && RhsTE->ReorderIndices.empty() && | |
| RhsTE->ReuseShuffleIndices.empty() && !MinBWs.contains(RhsTE))) | |
| return false; | |
| Order.clear(); | |
| unsigned CurrentValue = 0; | |
| // Rhs should be (0, Stride, 2 * Stride, ..., N-Stride), where N <= Sz. | |
| if (all_of(RhsTE->Scalars, | |
| [&](Value *V) { | |
| CurrentValue += Stride; | |
| if (isa<UndefValue>(V)) | |
| return true; | |
| auto *C = dyn_cast<Constant>(V); | |
| if (!C) | |
| return false; | |
| return C->getUniqueInteger() == CurrentValue - Stride; | |
| }) && | |
| CurrentValue <= Sz) { | |
| Order.clear(); | |
| } else { | |
| const unsigned VF = RhsTE->getVectorFactor(); | |
| Order.assign(VF, VF); | |
| // Track which logical positions we've seen; reject duplicate shift amounts. | |
| SmallBitVector SeenPositions(VF); | |
| // Check if need to reorder Rhs to make it in form (0, Stride, 2 * Stride, | |
| // ..., N-Stride), where N <= Sz. | |
| if (VF * Stride > Sz) | |
| return false; | |
| for (const auto [Idx, V] : | |
| make_filter_range(enumerate(RhsTE->Scalars), [](const auto &P) { | |
| return !isa<UndefValue>(P.value()); | |
| })) { | |
| auto *C = dyn_cast<Constant>(V); | |
| if (!C) | |
| return false; | |
| const APInt &Val = C->getUniqueInteger(); | |
| if (Val.isNegative() || Val.uge(Sz) || Val.getZExtValue() % Stride != 0) | |
| return false; | |
| unsigned Pos = Val.getZExtValue() / Stride; | |
| // TODO: Support Pos >= VF, in this case need to shift the final value. | |
| if (Order[Idx] != VF || Pos >= VF) | |
| return false; | |
| if (SeenPositions.test(Pos)) | |
| return false; | |
| SeenPositions.set(Pos); | |
| Order[Idx] = Pos; | |
| } | |
| // One of the indices not set - exit. | |
| if (is_contained(Order, VF)) | |
| return false; | |
| } | |
| auto *SrcType = IntegerType::getIntNTy(ScalarTy->getContext(), | |
| Stride * LhsTE->getVectorFactor()); | |
| FastMathFlags FMF; | |
| SmallPtrSet<Value *, 4> CheckedExtracts; | |
| auto *VecTy = | |
| cast<VectorType>(getWidenedType(ScalarTy, TE.getVectorFactor())); | |
| auto *SrcVecTy = | |
| cast<VectorType>(getWidenedType(SrcScalarTy, LhsTE->getVectorFactor())); | |
| TTI::CastContextHint CastCtx = | |
| getCastContextHint(*getOperandEntry(LhsTE, /*Idx=*/0)); | |
| InstructionCost VecCost = | |
| TTI->getArithmeticReductionCost(Instruction::Or, VecTy, FMF, CostKind) + | |
| TTI->getArithmeticInstrCost(Instruction::Shl, VecTy, CostKind, | |
| TTI::getOperandInfo(LhsTE->Scalars), | |
| TTI::getOperandInfo(RhsTE->Scalars), {}, | |
| TE.getMainOp()) + | |
| TTI->getCastInstrCost( | |
| Instruction::ZExt, VecTy, | |
| getWidenedType(SrcScalarTy, LhsTE->getVectorFactor()), CastCtx, | |
| CostKind, LhsTE->getMainOp()); | |
| InstructionCost BitcastCost = TTI->getCastInstrCost( | |
| Instruction::BitCast, SrcType, SrcVecTy, CastCtx, CostKind); | |
| if (!Order.empty()) { | |
| fixupOrderingIndices(Order); | |
| SmallVector<int> Mask; | |
| inversePermutation(Order, Mask); | |
| BitcastCost += getShuffleCost(*TTI, TTI::SK_PermuteSingleSrc, SrcVecTy, | |
| CostKind, Mask); | |
| } | |
| // Check if the combination can be modeled as a bitcast+byteswap operation. | |
| constexpr unsigned ByteSize = 8; | |
| if (!Order.empty() && isReverseOrder(Order) && | |
| DL->getTypeSizeInBits(SrcScalarTy) == ByteSize) { | |
| IntrinsicCostAttributes CostAttrs(Intrinsic::bswap, SrcType, {SrcType}); | |
| InstructionCost BSwapCost = | |
| TTI->getCastInstrCost(Instruction::BitCast, SrcType, SrcVecTy, CastCtx, | |
| CostKind) + | |
| TTI->getIntrinsicInstrCost(CostAttrs, CostKind); | |
| if (BSwapCost <= BitcastCost) { | |
| BitcastCost = BSwapCost; | |
| IsBSwap = true; | |
| Order.clear(); | |
| // Check for loads in the ZExt node. | |
| const TreeEntry *SrcTE = getOperandEntry(LhsTE, /*Idx=*/0); | |
| if (SrcTE->State == TreeEntry::Vectorize && | |
| SrcTE->ReorderIndices.empty() && SrcTE->ReuseShuffleIndices.empty() && | |
| SrcTE->getOpcode() == Instruction::Load && !SrcTE->isAltShuffle() && | |
| all_of(SrcTE->Scalars, [](Value *V) { return V->hasOneUse(); })) { | |
| auto *LI = cast<LoadInst>(SrcTE->getMainOp()); | |
| IntrinsicCostAttributes CostAttrs(Intrinsic::bswap, SrcType, {SrcType}); | |
| InstructionCost BSwapCost = | |
| TTI->getMemoryOpCost(Instruction::Load, SrcType, LI->getAlign(), | |
| LI->getPointerAddressSpace(), CostKind, | |
| TTI::getOperandInfo(LI->getPointerOperand())) + | |
| TTI->getIntrinsicInstrCost(CostAttrs, CostKind); | |
| if (BSwapCost <= BitcastCost) { | |
| VecCost += TTI->getMemoryOpCost( | |
| Instruction::Load, SrcVecTy, LI->getAlign(), | |
| LI->getPointerAddressSpace(), CostKind, | |
| TTI::getOperandInfo(LI->getPointerOperand())); | |
| BitcastCost = BSwapCost; | |
| ForLoads = true; | |
| } | |
| } | |
| } | |
| } else if (Order.empty() && DL->getTypeSizeInBits(SrcScalarTy) == ByteSize) { | |
| // Check for loads in the ZExt node. | |
| const TreeEntry *SrcTE = getOperandEntry(LhsTE, /*Idx=*/0); | |
| if (SrcTE->State == TreeEntry::Vectorize && SrcTE->ReorderIndices.empty() && | |
| SrcTE->ReuseShuffleIndices.empty() && | |
| SrcTE->getOpcode() == Instruction::Load && !SrcTE->isAltShuffle() && | |
| all_of(SrcTE->Scalars, [](Value *V) { return V->hasOneUse(); })) { | |
| auto *LI = cast<LoadInst>(SrcTE->getMainOp()); | |
| BitcastCost = | |
| TTI->getMemoryOpCost(Instruction::Load, SrcType, LI->getAlign(), | |
| LI->getPointerAddressSpace(), CostKind, | |
| TTI::getOperandInfo(LI->getPointerOperand())); | |
| VecCost += | |
| TTI->getMemoryOpCost(Instruction::Load, SrcVecTy, LI->getAlign(), | |
| LI->getPointerAddressSpace(), CostKind, | |
| TTI::getOperandInfo(LI->getPointerOperand())); | |
| ForLoads = true; | |
| } | |
| } | |
| if (SrcType != ScalarTy) { | |
| BitcastCost += TTI->getCastInstrCost(Instruction::ZExt, ScalarTy, SrcType, | |
| CastCtx, CostKind); | |
| } | |
| return BitcastCost < VecCost; | |
| } | |
| std::optional<BitPackInfo> | |
| BoUpSLP::computeBitPackLayout(const TreeEntry &TE) const { | |
| assert(TE.hasState() && | |
| (TE.getOpcode() == Instruction::And || | |
| TE.getOpcode() == Instruction::Shl) && | |
| "Expected And or Shl node."); | |
| const unsigned BitWidth = DL->getTypeSizeInBits(TE.getMainOp()->getType()); | |
| const TreeEntry *ShlTE = &TE; | |
| const TreeEntry *MaskTE = nullptr; | |
| if (TE.getOpcode() == Instruction::And) { | |
| MaskTE = getOperandEntry(&TE, 1); | |
| ShlTE = getOperandEntry(&TE, 0); | |
| } | |
| const TreeEntry *ShiftTE = getOperandEntry(ShlTE, 1); | |
| const TreeEntry *LhsTE = getOperandEntry(ShlTE, 0); | |
| const unsigned VF = TE.getVectorFactor(); | |
| SmallVector<APInt> PossibleBits(VF), Masks(VF); | |
| SmallVector<uint64_t> ShlAmts(VF); | |
| auto IsCopyable = [](const TreeEntry *TE, unsigned Idx) { | |
| return TE->hasCopyableElements() && TE->isCopyableElement(TE->Scalars[Idx]); | |
| }; | |
| for (unsigned Idx : seq(VF)) { | |
| uint64_t ShlAmt = 0; | |
| const APInt *C; | |
| if (!IsCopyable(ShlTE, Idx)) { | |
| if (!match(ShiftTE->Scalars[Idx], m_APInt(C)) || C->uge(BitWidth)) | |
| return std::nullopt; | |
| ShlAmt = C->getZExtValue(); | |
| } | |
| APInt Mask = APInt::getAllOnes(BitWidth); | |
| if (MaskTE && !IsCopyable(&TE, Idx)) { | |
| if (!match(MaskTE->Scalars[Idx], m_APInt(C))) | |
| return std::nullopt; | |
| Mask = *C; | |
| } | |
| Value *X = LhsTE->Scalars[Idx]; | |
| if (isa<PoisonValue>(X)) { | |
| PossibleBits[Idx] = APInt(BitWidth, 0); | |
| } else { | |
| PossibleBits[Idx] = | |
| APInt::getLowBitsSet(BitWidth, getScalarMaxValue(X).getActiveBits()) & | |
| ~computeKnownBits(X, SimplifyQuery(*DL)).Zero; | |
| } | |
| ShlAmts[Idx] = ShlAmt; | |
| Masks[Idx] = Mask; | |
| } | |
| return computeBitPackInfo(BitWidth, PossibleBits, ShlAmts, Masks); | |
| } | |
| const BoUpSLP::TreeEntry *BoUpSLP::getBitPackSource(const TreeEntry &LhsTE, | |
| BitPackInfo &Info) const { | |
| // The chain nodes are checked both before and after being combined into the | |
| // root. | |
| auto IsChainNode = [](const TreeEntry &TE) { | |
| return (TE.State == TreeEntry::Vectorize || | |
| TE.CombinedOp == TreeEntry::ReducedBitPack) && | |
| TE.ReorderIndices.empty() && TE.ReuseShuffleIndices.empty() && | |
| !TE.hasCopyableElements() && | |
| all_of(TE.Scalars, [](Value *V) { return V->hasOneUse(); }); | |
| }; | |
| const TreeEntry *AndTE = &LhsTE; | |
| if (AndTE->hasState() && AndTE->getOpcode() == Instruction::ZExt && | |
| IsChainNode(*AndTE)) | |
| AndTE = getOperandEntry(AndTE, /*Idx=*/0); | |
| if (!AndTE->hasState() || AndTE->getOpcode() != Instruction::And || | |
| !IsChainNode(*AndTE)) | |
| return &LhsTE; | |
| const TreeEntry *ShiftTE = getOperandEntry(AndTE, /*Idx=*/0); | |
| if (!ShiftTE->hasState() || | |
| (ShiftTE->getOpcode() != Instruction::LShr && | |
| ShiftTE->getOpcode() != Instruction::Shl) || | |
| !IsChainNode(*ShiftTE)) | |
| return &LhsTE; | |
| const TreeEntry *SrcTE = getOperandEntry(ShiftTE, /*Idx=*/0); | |
| if (SrcTE->State != TreeEntry::Vectorize || !SrcTE->ReorderIndices.empty() || | |
| !SrcTE->ReuseShuffleIndices.empty()) | |
| return &LhsTE; | |
| const unsigned SrcBW = SrcTE->getMainOp()->getType()->getScalarSizeInBits(); | |
| const unsigned FieldWidth = Info.FieldWidth; | |
| std::optional<uint64_t> UniformOff; | |
| for (auto [Idx, FieldOff] : enumerate(Info.LShrAmts)) { | |
| // The and-mask keeps the field bits, which are shifted from the same bits | |
| // of every source lane. | |
| Value *Sh; | |
| const APInt *Mask, *Amt; | |
| if (FieldOff + FieldWidth > SrcBW || | |
| !match(AndTE->Scalars[Idx], m_c_And(m_Value(Sh), m_APInt(Mask))) || | |
| !Mask->extractBits(FieldWidth, FieldOff).isAllOnes()) | |
| return &LhsTE; | |
| uint64_t Off = FieldOff; | |
| if (match(Sh, m_LShr(m_Specific(SrcTE->Scalars[Idx]), m_APInt(Amt)))) | |
| Off += Amt->getZExtValue(); | |
| else if (match(Sh, m_Shl(m_Specific(SrcTE->Scalars[Idx]), m_APInt(Amt))) && | |
| Amt->ule(FieldOff)) | |
| Off -= Amt->getZExtValue(); | |
| else | |
| return &LhsTE; | |
| if (Off + FieldWidth > SrcBW || (UniformOff && *UniformOff != Off)) | |
| return &LhsTE; | |
| UniformOff = Off; | |
| } | |
| fill(Info.LShrAmts, *UniformOff); | |
| return SrcTE; | |
| } | |
| unsigned BoUpSLP::getZExtSrcWidth(const TreeEntry &TE) const { | |
| if (!TE.hasState() || TE.hasCopyableElements() || | |
| TE.getOpcode() != Instruction::ZExt) | |
| return 0; | |
| return cast<ZExtInst>(TE.getMainOp())->getSrcTy()->getScalarSizeInBits(); | |
| } | |
| bool BoUpSLP::matchesBitPack(const TreeEntry &TE) const { | |
| assert(TE.hasState() && | |
| (TE.getOpcode() == Instruction::And || | |
| TE.getOpcode() == Instruction::Shl) && | |
| "Expected And or Shl node."); | |
| auto IsVectorizedOneUseNode = [&](const TreeEntry *N) { | |
| return N->State == TreeEntry::Vectorize && !N->isAltShuffle() && | |
| N->ReorderIndices.empty() && N->ReuseShuffleIndices.empty() && | |
| !MinBWs.contains(N) && all_of(N->Scalars, [&](Value *V) { | |
| return (N->hasCopyableElements() && N->isCopyableElement(V)) || | |
| V->hasOneUse(); | |
| }); | |
| }; | |
| if (!IsVectorizedOneUseNode(&TE)) | |
| return false; | |
| Type *ScalarTy = TE.getMainOp()->getType(); | |
| if (!ScalarTy->isIntegerTy()) | |
| return false; | |
| const unsigned BitWidth = DL->getTypeSizeInBits(ScalarTy); | |
| if (!isPowerOf2_64(BitWidth)) | |
| return false; | |
| const TreeEntry *ShlTE = &TE; | |
| const TreeEntry *MaskTE = nullptr; | |
| if (TE.getOpcode() == Instruction::And) { | |
| MaskTE = getOperandEntry(&TE, 1); | |
| ShlTE = getOperandEntry(&TE, 0); | |
| if (!ShlTE->hasState() || ShlTE->getOpcode() != Instruction::Shl) | |
| return false; | |
| } | |
| if (!IsVectorizedOneUseNode(ShlTE)) | |
| return false; | |
| const TreeEntry *ShiftTE = getOperandEntry(ShlTE, 1); | |
| const TreeEntry *LhsTE = getOperandEntry(ShlTE, 0); | |
| if (!ShiftTE->isGather() || (MaskTE && !MaskTE->isGather())) | |
| return false; | |
| // The operand must be emitted as a plain vector in lane order: a vectorized | |
| // node or a simple gather. | |
| if ((!LhsTE->isGather() && LhsTE->State != TreeEntry::Vectorize) || | |
| (LhsTE->hasState() && LhsTE->isAltShuffle()) || | |
| !LhsTE->ReorderIndices.empty() || !LhsTE->ReuseShuffleIndices.empty() || | |
| MinBWs.contains(LhsTE)) | |
| return false; | |
| std::optional<BitPackInfo> Info = computeBitPackLayout(TE); | |
| if (!Info) | |
| return false; | |
| auto *VecTy = | |
| cast<FixedVectorType>(getWidenedType(ScalarTy, TE.getVectorFactor())); | |
| FastMathFlags FMF; | |
| InstructionCost VecCost = | |
| TTI->getArithmeticReductionCost(Instruction::Or, VecTy, FMF, CostKind) + | |
| TTI->getArithmeticInstrCost(Instruction::Shl, VecTy, CostKind, | |
| TTI::getOperandInfo(LhsTE->Scalars), | |
| TTI::getOperandInfo(ShiftTE->Scalars), | |
| /*Args=*/{}, ShlTE->getMainOp(), TLI); | |
| if (TE.getOpcode() == Instruction::And) | |
| VecCost += TTI->getArithmeticInstrCost( | |
| Instruction::And, VecTy, CostKind, TTI::getOperandInfo(ShlTE->Scalars), | |
| TTI::getOperandInfo(MaskTE->Scalars), /*Args=*/{}, TE.getMainOp(), TLI); | |
| const TreeEntry *SrcTE = getBitPackSource(*LhsTE, *Info); | |
| auto *SrcTy = cast<FixedVectorType>( | |
| getWidenedType(SrcTE->Scalars.front()->getType(), TE.getVectorFactor())); | |
| unsigned ShiftWidth; | |
| InstructionCost PackCost = getBitPackCost( | |
| *TTI, SrcTy, ScalarTy, *Info, getZExtSrcWidth(*SrcTE), | |
| getCastContextHint(*SrcTE), CostKind, TLI, TE.getMainOp(), ShiftWidth); | |
| return PackCost.isValid() && PackCost <= VecCost; | |
| } | |
| bool BoUpSLP::matchesInversedZExtSelect( | |
| const TreeEntry &SelectTE, | |
| SmallVectorImpl<unsigned> &InversedCmpsIndices) const { | |
| assert(SelectTE.hasState() && SelectTE.getOpcode() == Instruction::Select && | |
| "Expected select node."); | |
| SmallVector<std::pair<Instruction *, unsigned>> ZExts; | |
| for (auto [Idx, V] : enumerate(SelectTE.Scalars)) { | |
| auto *Inst = dyn_cast<Instruction>(V); | |
| if (!Inst || Inst->getOpcode() != Instruction::ZExt) | |
| continue; | |
| ZExts.emplace_back(Inst, Idx); | |
| } | |
| if (ZExts.empty()) | |
| return false; | |
| const auto *CmpTE = getOperandEntry(&SelectTE, 0); | |
| const auto *Op1TE = getOperandEntry(&SelectTE, 1); | |
| const auto *Op2TE = getOperandEntry(&SelectTE, 2); | |
| // Compares must be alternate vectorized, and other operands must be gathers | |
| // or copyables. | |
| // TODO: investigate opportunity for reordered/reused nodes. | |
| if (CmpTE->State != TreeEntry::Vectorize || !CmpTE->isAltShuffle() || | |
| (CmpTE->getOpcode() != Instruction::ICmp && | |
| CmpTE->getOpcode() != Instruction::FCmp) || | |
| !CmpTE->ReorderIndices.empty() || !CmpTE->ReuseShuffleIndices.empty() || | |
| !Op1TE->ReorderIndices.empty() || !Op1TE->ReuseShuffleIndices.empty() || | |
| !Op2TE->ReorderIndices.empty() || !Op2TE->ReuseShuffleIndices.empty()) | |
| return false; | |
| // The operands must be buildvectors/copyables. | |
| if (!Op1TE->isGather() || !Op2TE->isGather()) | |
| return false; | |
| // TODO: investigate opportunity for the vector nodes with copyables. | |
| auto *Cmp = CmpTE->getMainOp(); | |
| CmpPredicate Pred; | |
| auto MatchCmp = m_Cmp(Pred, m_Value(), m_Value()); | |
| if (!match(Cmp, MatchCmp)) | |
| return false; | |
| CmpPredicate MainPred = Pred; | |
| CmpPredicate InversedPred(CmpInst::getInversePredicate(Pred), | |
| Pred.hasSameSign()); | |
| for (const auto [Idx, V] : | |
| make_filter_range(enumerate(CmpTE->Scalars), [&](const auto &P) { | |
| return match(P.value(), MatchCmp); | |
| })) { | |
| if (CmpPredicate::getMatching(MainPred, Pred)) | |
| continue; | |
| if (!CmpPredicate::getMatching(InversedPred, Pred)) | |
| return false; | |
| if (!V->hasOneUse()) | |
| return false; | |
| InversedCmpsIndices.push_back(Idx); | |
| } | |
| if (InversedCmpsIndices.empty()) | |
| return false; | |
| Type *VecTy = | |
| getWidenedType(Cmp->getOperand(0)->getType(), CmpTE->getVectorFactor()); | |
| Type *CmpTy = CmpInst::makeCmpResultType(VecTy); | |
| InstructionCost VecCost = TTI->getCmpSelInstrCost( | |
| CmpTE->getOpcode(), VecTy, CmpTy, MainPred, CostKind, | |
| TTI::getOperandInfo(CmpTE->getOperand(0)), | |
| TTI::getOperandInfo(CmpTE->getOperand(1)), CmpTE->getMainOp()); | |
| InstructionCost BVCost = getScalarizationOverhead( | |
| *TTI, Opts.slp_revec, Cmp->getType(), cast<VectorType>(CmpTy), | |
| APInt::getAllOnes(CmpTE->getVectorFactor()), | |
| /*Insert=*/true, /*Extract=*/false, CostKind); | |
| for (Instruction *I : make_isa_range<Instruction>(CmpTE->Scalars)) | |
| BVCost += TTI->getInstructionCost(I, CostKind); | |
| return VecCost < BVCost; | |
| } | |
| bool BoUpSLP::matchesSelectOfBits(const TreeEntry &SelectTE) const { | |
| assert(SelectTE.hasState() && SelectTE.getOpcode() == Instruction::Select && | |
| "Expected select node."); | |
| if (DL->isBigEndian()) | |
| return false; | |
| if (!SelectTE.ReorderIndices.empty() || !SelectTE.ReuseShuffleIndices.empty()) | |
| return false; | |
| if (!UserIgnoreList || SelectTE.Idx != 0) | |
| return false; | |
| if (any_of(SelectTE.Scalars, [](Value *V) { return !V->hasOneUse(); })) | |
| return false; | |
| // Check that all reduction operands are or instructions. | |
| if (any_of(*UserIgnoreList, | |
| [](Value *V) { return !match(V, m_Or(m_Value(), m_Value())); })) | |
| return false; | |
| const TreeEntry *Op1TE = getOperandEntry(&SelectTE, 1); | |
| const TreeEntry *Op2TE = getOperandEntry(&SelectTE, 2); | |
| if (!Op1TE->isGather() || !Op2TE->isGather()) | |
| return false; | |
| // No need to check for zeroes reordering. | |
| if (!Op1TE->ReorderIndices.empty() || !Op1TE->ReuseShuffleIndices.empty() || | |
| !Op2TE->ReuseShuffleIndices.empty()) | |
| return false; | |
| Type *ScalarTy = Op1TE->Scalars.front()->getType(); | |
| if (!ScalarTy->isIntegerTy()) | |
| return false; | |
| // Check that second operand is all zeroes. | |
| if (any_of(Op2TE->Scalars, [](Value *V) { return !match(V, m_ZeroInt()); })) | |
| return false; | |
| // Check that first operand is 1,2,4,... | |
| if (any_of(enumerate(Op1TE->Scalars), [](const auto &P) { | |
| uint64_t V; | |
| return !(match(P.value(), m_ConstantInt(V)) && isPowerOf2_64(V) && | |
| Log2_64(V) == P.index()); | |
| })) | |
| return false; | |
| // Check if bitcast is cheaper than select. | |
| auto *DstTy = IntegerType::getIntNTy(ScalarTy->getContext(), | |
| SelectTE.getVectorFactor()); | |
| Type *OpTy = getWidenedType(DstTy, SelectTE.getVectorFactor()); | |
| Type *CmpTy = CmpInst::makeCmpResultType(OpTy); | |
| auto *VecTy = | |
| cast<VectorType>(getWidenedType(ScalarTy, SelectTE.getVectorFactor())); | |
| auto It = MinBWs.find(&SelectTE); | |
| if (It != MinBWs.end()) { | |
| auto *EffectiveScalarTy = | |
| IntegerType::get(F->getContext(), It->second.first); | |
| VecTy = cast<VectorType>( | |
| getWidenedType(EffectiveScalarTy, SelectTE.getVectorFactor())); | |
| } | |
| InstructionCost BitcastCost = TTI->getCastInstrCost( | |
| Instruction::BitCast, DstTy, CmpTy, TTI::CastContextHint::None, CostKind); | |
| if (DstTy != ScalarTy) { | |
| BitcastCost += TTI->getCastInstrCost(Instruction::ZExt, ScalarTy, DstTy, | |
| TTI::CastContextHint::None, CostKind); | |
| } | |
| // Use the condition predicate shared by all lanes, if there is one. | |
| CmpPredicate SelPred = CmpInst::BAD_ICMP_PREDICATE; | |
| CmpPredicate P; | |
| if (match(SelectTE.getMainOp(), | |
| m_Select(m_ICmp(P, m_Value(), m_Value()), m_Value(), m_Value())) && | |
| all_of(SelectTE.Scalars, [&](Value *V) { | |
| CmpPredicate LaneP; | |
| return match(V, m_Select(m_ICmp(LaneP, m_Value(), m_Value()), m_Value(), | |
| m_Value())) && | |
| LaneP == P.dropSameSign(); | |
| })) | |
| SelPred = P; | |
| FastMathFlags FMF; | |
| InstructionCost SelectCost = | |
| TTI->getCmpSelInstrCost(Instruction::Select, VecTy, CmpTy, SelPred, | |
| CostKind, TTI::getOperandInfo(Op1TE->Scalars), | |
| TTI::getOperandInfo(Op2TE->Scalars), | |
| SelectTE.getMainOp()) + | |
| TTI->getArithmeticReductionCost(Instruction::Or, VecTy, FMF, CostKind); | |
| return BitcastCost <= SelectCost; | |
| } | |
| void BoUpSLP::transformNodes() { | |
| BaseGraphSize = VectorizableTree.size(); | |
| // Turn graph transforming mode on and off, when done. | |
| class GraphTransformModeRAAI { | |
| bool &SavedIsGraphTransformMode; | |
| public: | |
| GraphTransformModeRAAI(bool &IsGraphTransformMode) | |
| : SavedIsGraphTransformMode(IsGraphTransformMode) { | |
| IsGraphTransformMode = true; | |
| } | |
| ~GraphTransformModeRAAI() { SavedIsGraphTransformMode = false; } | |
| } TransformContext(IsGraphTransformMode); | |
| // Operands are profitable if they are: | |
| // 1. At least one constant | |
| // or | |
| // 2. Splats | |
| // or | |
| // 3. Results in good vectorization opportunity, i.e. may generate vector | |
| // nodes and reduce cost of the graph. | |
| auto CheckOperandsProfitability = [this](Instruction *I1, Instruction *I2, | |
| const InstructionsState &S) { | |
| SmallVector<SmallVector<std::pair<Value *, Value *>>> Candidates; | |
| for (unsigned Op : seq<unsigned>(S.getMainOp()->getNumOperands())) | |
| Candidates.emplace_back().emplace_back(I1->getOperand(Op), | |
| I2->getOperand(Op)); | |
| return all_of(Candidates, [this]( | |
| ArrayRef<std::pair<Value *, Value *>> Cand) { | |
| return all_of(Cand, | |
| [](const std::pair<Value *, Value *> &P) { | |
| return isa<Constant>(P.first) || | |
| isa<Constant>(P.second) || P.first == P.second; | |
| }) || | |
| findBestRootPair(Cand, LookAheadHeuristics::ScoreSplatLoads).first; | |
| }); | |
| }; | |
| // The reduction root gather feeds only the commutative reduction, which | |
| // maps the reduced values to the lanes by the root scalars. Put the distinct | |
| // sub-fields of the same wider scalar in the natural order to emit them | |
| // without a permutation. | |
| if (UserIgnoreList && getRootNode().isGather() && | |
| getRootNode().ReuseShuffleIndices.empty()) | |
| if (std::optional<std::tuple<Value *, unsigned, SmallVector<int>>> Fields = | |
| matchGatheredExtractedFields(getRootNode().Scalars, *DL)) { | |
| auto &[Src, FieldWidth, Mask] = *Fields; | |
| SmallVector<int> SortedMask(Mask); | |
| sort(SortedMask); | |
| if (equal(SortedMask, | |
| seq<int>(Src->getType()->getIntegerBitWidth() / FieldWidth))) | |
| reorderScalars(getRootNode().Scalars, Mask); | |
| } | |
| // Try to reorder gather nodes for better vectorization opportunities. | |
| for (unsigned Idx : seq<unsigned>(BaseGraphSize)) { | |
| TreeEntry &E = *VectorizableTree[Idx]; | |
| if (E.isGather()) | |
| reorderGatherNode(E); | |
| } | |
| // Better to use full gathered loads analysis, if there are only 2 loads | |
| // gathered nodes each having less than 16 elements. | |
| constexpr unsigned VFLimit = 16; | |
| bool ForceLoadGather = | |
| count_if(VectorizableTree, [&](const std::unique_ptr<TreeEntry> &TE) { | |
| return TE->isGather() && TE->hasState() && | |
| TE->getOpcode() == Instruction::Load && | |
| TE->getVectorFactor() < VFLimit; | |
| }) == 2; | |
| // Checks if the scalars are used in other node. | |
| auto AreReusedScalars = [&](const TreeEntry *TE, ArrayRef<Value *> VL, | |
| function_ref<bool(Value *)> CheckContainer) { | |
| return TE->isSame(VL) || all_of(VL, [&](Value *V) { | |
| if (isa<PoisonValue>(V)) | |
| return true; | |
| auto *I = dyn_cast<Instruction>(V); | |
| if (!I) | |
| return false; | |
| return is_contained(TE->Scalars, I) || CheckContainer(I); | |
| }); | |
| }; | |
| auto CheckForSameVectorNodes = [&](const TreeEntry &E) { | |
| if (E.hasState()) { | |
| if (ArrayRef<TreeEntry *> TEs = getTreeEntries(E.getMainOp()); | |
| !TEs.empty() && any_of(TEs, [&](const TreeEntry *TE) { | |
| return AreReusedScalars(TE, E.Scalars, [&](Value *V) { | |
| ArrayRef<TreeEntry *> VTEs = getTreeEntries(V); | |
| return !VTEs.empty() && any_of(VTEs, [&](const TreeEntry *TE) { | |
| return is_contained(TEs, TE); | |
| }); | |
| }); | |
| })) | |
| return true; | |
| ; | |
| if (ArrayRef<TreeEntry *> TEs = getSplitTreeEntries(E.getMainOp()); | |
| !TEs.empty() && any_of(TEs, [&](const TreeEntry *TE) { | |
| return AreReusedScalars(TE, E.Scalars, [&](Value *V) { | |
| ArrayRef<TreeEntry *> VTEs = getSplitTreeEntries(V); | |
| return !VTEs.empty() && any_of(VTEs, [&](const TreeEntry *TE) { | |
| return is_contained(TEs, TE); | |
| }); | |
| }); | |
| })) | |
| return true; | |
| } else { | |
| // Check if the gather node full copy of split node. | |
| auto *It = find_if(E.Scalars, IsaPred<Instruction>); | |
| if (It != E.Scalars.end()) { | |
| if (ArrayRef<TreeEntry *> TEs = getSplitTreeEntries(*It); | |
| !TEs.empty() && any_of(TEs, [&](const TreeEntry *TE) { | |
| return AreReusedScalars(TE, E.Scalars, [&](Value *V) { | |
| ArrayRef<TreeEntry *> VTEs = getSplitTreeEntries(V); | |
| return !VTEs.empty() && any_of(VTEs, [&](const TreeEntry *TE) { | |
| return is_contained(TEs, TE); | |
| }); | |
| }); | |
| })) | |
| return true; | |
| } | |
| } | |
| return false; | |
| }; | |
| // Marks the fadd/fsub node \p E as fmuladd, if its fmul operand is fused | |
| // into it. | |
| auto CombineFMulAdd = [&](TreeEntry &E) { | |
| // Check if possible to convert (a*b)+c to fma; fma takes exactly two | |
| // operands, so bail out on reassociated (>2-operand) nodes. | |
| if (E.State != TreeEntry::Vectorize || | |
| !E.getOperations().isAddSubLikeOp() || | |
| E.getOperations().isAltShuffle() || E.getNumOperands() != 2) | |
| return; | |
| const TreeEntry *LHS = getOperandEntry(&E, 0); | |
| const TreeEntry *RHS = getOperandEntry(&E, 1); | |
| auto IsOneUseVectorFMulOperand = [](const TreeEntry *TE) { | |
| return TE->State == TreeEntry::Vectorize && TE->ReorderIndices.empty() && | |
| TE->ReuseShuffleIndices.empty() && | |
| TE->getOpcode() == Instruction::FMul && !TE->isAltShuffle() && | |
| all_of(TE->Scalars, [&](Value *V) { | |
| return (TE->hasCopyableElements() && TE->isCopyableElement(V)) || | |
| V->hasOneUse(); | |
| }); | |
| }; | |
| // Both a*b - c and c - a*b are fused. | |
| const bool IsLHSFMul = IsOneUseVectorFMulOperand(LHS); | |
| if (!IsLHSFMul && !IsOneUseVectorFMulOperand(RHS)) | |
| return; | |
| if (!canConvertToFMA(E.Scalars, E.getOperations(), *DT, *DL, *TTI, *TLI, | |
| *this) | |
| .isValid()) | |
| return; | |
| // This node is a fmuladd node. | |
| E.CombinedOp = TreeEntry::FMulAdd; | |
| TreeEntry *FMulEntry = getOperandEntry(&E, IsLHSFMul ? 0 : 1); | |
| if (FMulEntry->UserTreeIndex && FMulEntry->State == TreeEntry::Vectorize) { | |
| // The FMul node is part of the combined fmuladd node. | |
| FMulEntry->State = TreeEntry::CombinedVectorize; | |
| } | |
| }; | |
| // The tree may grow here, so iterate over nodes, built before. | |
| for (unsigned Idx : seq<unsigned>(BaseGraphSize)) { | |
| TreeEntry &E = *VectorizableTree[Idx]; | |
| if (E.isGather()) { | |
| ArrayRef<Value *> VL = E.Scalars; | |
| const unsigned Sz = getVectorElementSize(VL.front()); | |
| unsigned MinVF = getMinVF(2 * Sz); | |
| // Do not try partial vectorization for small nodes (<= 2), nodes with the | |
| // same opcode and same parent block or all constants. | |
| if (VL.size() <= 2 || LoadEntriesToVectorize.contains(Idx) || | |
| !(!E.hasState() || E.getOpcode() == Instruction::Load || | |
| // We use allSameOpcode instead of isAltShuffle because we don't | |
| // want to use interchangeable instruction here. | |
| !allSameOpcode(VL) || !allSameBlock(VL)) || | |
| allConstant(VL) || isSplat(VL) || | |
| (E.ReuseShuffleIndices.empty() && | |
| matchGatheredExtractedFields(VL, *DL))) | |
| continue; | |
| if (ForceLoadGather && E.hasState() && E.getOpcode() == Instruction::Load) | |
| continue; | |
| // Check if the node is a copy of other vector nodes. | |
| if (CheckForSameVectorNodes(E)) | |
| continue; | |
| // Try to find vectorizable sequences and transform them into a series of | |
| // insertvector instructions. | |
| unsigned StartIdx = 0; | |
| unsigned End = VL.size(); | |
| SmallBitVector Processed(End); | |
| for (unsigned VF = getFloorFullVectorNumberOfElements( | |
| *TTI, VL.front()->getType(), VL.size() - 1, Opts.slp_revec); | |
| VF >= MinVF; | |
| VF = getFloorFullVectorNumberOfElements(*TTI, VL.front()->getType(), | |
| VF - 1, Opts.slp_revec)) { | |
| if (StartIdx + VF > End) | |
| continue; | |
| SmallVector<std::pair<unsigned, unsigned>> Slices; | |
| bool AllStrided = true; | |
| // Walk the range in steps of VF, but allow the trailing slice to be | |
| // shorter (SliceVF < VF) so non-power-of-2 tails can be vectorized. | |
| // Processed only records slice starts (Cnt), and downstream consumers | |
| // (test(Cnt), AddCombinedNode's range set/StartIdx update) operate on | |
| // start positions, so partial coverage is consistent. | |
| for (unsigned Cnt = StartIdx; Cnt < End; Cnt += VF) { | |
| const unsigned SliceVF = std::min(VF, End - Cnt); | |
| if (SliceVF <= 1) | |
| continue; | |
| ArrayRef<Value *> Slice = VL.slice(Cnt, SliceVF); | |
| // If any instruction is vectorized already - do not try again. | |
| // Reuse the existing node, if it fully matches the slice. | |
| if ((Processed.test(Cnt) || isVectorized(Slice.front())) && | |
| !getSameValuesTreeEntry(Slice.front(), Slice, /*SameVF=*/true)) | |
| continue; | |
| // Constant already handled effectively - skip. | |
| if (allConstant(Slice)) | |
| continue; | |
| // Do not try to vectorize small splats (less than vector register and | |
| // only with the single non-undef element). | |
| bool IsSplat = isSplat(Slice); | |
| bool IsTwoRegisterSplat = true; | |
| if (IsSplat && VF == 2) { | |
| unsigned NumRegs2VF = getNumberOfParts( | |
| getWidenedType(getValueType(Slice.front(), Opts.slp_revec), | |
| 2 * VF), | |
| getValueType(Slice.front(), Opts.slp_revec)); | |
| IsTwoRegisterSplat = NumRegs2VF == 2; | |
| } | |
| if (Slices.empty() || !IsSplat || !IsTwoRegisterSplat || | |
| count(Slice, Slice.front()) == | |
| static_cast<long>(isa<UndefValue>(Slice.front()) ? SliceVF - 1 | |
| : 1)) { | |
| if (IsSplat) | |
| continue; | |
| InstructionsState S = getSameOpcode(Slice, *TLI); | |
| if (!S || !allSameOpcode(Slice) || !allSameBlock(Slice) || | |
| (S.getOpcode() == Instruction::Load && | |
| areKnownNonVectorizableLoads(Slice))) | |
| continue; | |
| if (VF == 2) { | |
| // Cache the cost check lazily - both branches below may need it. | |
| std::optional<bool> MainOpIsCheap; | |
| auto IsMainOpCheap = [&, TTI = TTI] { | |
| if (!MainOpIsCheap) | |
| MainOpIsCheap = | |
| TTI->getInstructionCost(S.getMainOp(), CostKind) < | |
| TTI::TCC_Expensive; | |
| return *MainOpIsCheap; | |
| }; | |
| // Try to vectorize reduced values or if all users are vectorized. | |
| // For expensive instructions extra extracts might be profitable. | |
| if ((!UserIgnoreList || E.Idx != 0) && IsMainOpCheap() && | |
| !all_of(make_isa_range<Instruction>(Slice), | |
| [&](Instruction *I) { | |
| return areAllUsersVectorized(I, UserIgnoreList); | |
| })) | |
| continue; | |
| if (S.getOpcode() == Instruction::Load) { | |
| OrdersType Order; | |
| SmallVector<Value *> PointerOps; | |
| StridedPtrInfo SPtrInfo; | |
| LoadsState Res = canVectorizeLoads(Slice, Slice.front(), Order, | |
| PointerOps, SPtrInfo); | |
| AllStrided &= Res == LoadsState::StridedVectorize || | |
| Res == LoadsState::ScatterVectorize || | |
| Res == LoadsState::Gather; | |
| // Do not vectorize gathers. | |
| if (Res == LoadsState::ScatterVectorize || | |
| Res == LoadsState::Gather) { | |
| if (Res == LoadsState::Gather) { | |
| registerNonVectorizableLoads(Slice); | |
| // If reductions and the scalars from the root node are | |
| // analyzed - mark as non-vectorizable reduction. | |
| if (UserIgnoreList && E.Idx == 0) | |
| analyzedReductionVals(Slice); | |
| } | |
| continue; | |
| } | |
| } else if (S.getOpcode() == Instruction::ExtractElement || | |
| (IsMainOpCheap() && | |
| !CheckOperandsProfitability( | |
| S.getMainOp(), | |
| cast<Instruction>(*find_if(reverse(Slice), | |
| IsaPred<Instruction>)), | |
| S))) { | |
| // Do not vectorize extractelements (handled effectively | |
| // alread). Do not vectorize non-profitable instructions (with | |
| // low cost and non-vectorizable operands.) | |
| continue; | |
| } | |
| } | |
| } | |
| Slices.emplace_back(Cnt, Slice.size()); | |
| } | |
| // Do not try to vectorize if all slides are strided or gathered with | |
| // vector factor 2 and there are more than 2 slices. Better to handle | |
| // them in gathered loads analysis, may result in better vectorization. | |
| if (VF == 2 && AllStrided && Slices.size() > 2) | |
| continue; | |
| auto AddCombinedNode = [&](unsigned Idx, unsigned Cnt, unsigned Sz) { | |
| E.CombinedEntriesWithIndices.emplace_back(Idx, Cnt); | |
| Processed.set(Cnt, Cnt + Sz); | |
| if (StartIdx == Cnt) | |
| StartIdx = Cnt + Sz; | |
| if (End == Cnt + Sz) | |
| End = Cnt; | |
| }; | |
| for (auto [Cnt, Sz] : Slices) { | |
| ArrayRef<Value *> Slice = VL.slice(Cnt, Sz); | |
| const TreeEntry *SameTE = nullptr; | |
| if (const auto *It = find_if(Slice, IsaPred<Instruction>); | |
| It != Slice.end()) { | |
| // If any instruction is vectorized already - do not try again. | |
| SameTE = getSameValuesTreeEntry(*It, Slice); | |
| } | |
| unsigned PrevSize = VectorizableTree.size(); | |
| [[maybe_unused]] unsigned PrevEntriesSize = | |
| LoadEntriesToVectorize.size(); | |
| buildTreeRec(Slice, 0, EdgeInfo(&E, UINT_MAX)); | |
| if (PrevSize + 1 == VectorizableTree.size() && !SameTE && | |
| VectorizableTree[PrevSize]->isGather() && | |
| VectorizableTree[PrevSize]->hasState() && | |
| VectorizableTree[PrevSize]->getOpcode() != | |
| Instruction::ExtractElement && | |
| !isSplat(Slice)) { | |
| if (UserIgnoreList && E.Idx == 0 && VF == 2) | |
| analyzedReductionVals(Slice); | |
| VectorizableTree.pop_back(); | |
| assert(PrevEntriesSize == LoadEntriesToVectorize.size() && | |
| "LoadEntriesToVectorize expected to remain the same"); | |
| continue; | |
| } | |
| AddCombinedNode(PrevSize, Cnt, Sz); | |
| } | |
| } | |
| // Restore ordering, if no extra vectorization happened. | |
| if (E.CombinedEntriesWithIndices.empty() && !E.ReorderIndices.empty()) { | |
| SmallVector<int> Mask(E.ReorderIndices.begin(), E.ReorderIndices.end()); | |
| reorderScalars(E.Scalars, Mask); | |
| E.ReorderIndices.clear(); | |
| } | |
| } | |
| if (!E.hasState()) | |
| continue; | |
| switch (E.getOpcode()) { | |
| case Instruction::Load: { | |
| // No need to reorder masked gather loads, just reorder the scalar | |
| // operands. | |
| // Lanes without a load would break the common alignment below. | |
| if (E.State != TreeEntry::Vectorize || | |
| any_of(E.Scalars, IsaPred<UndefValue>)) | |
| break; | |
| Type *ScalarTy = E.getMainOp()->getType(); | |
| auto *VecTy = | |
| cast<FixedVectorType>(getWidenedType(ScalarTy, E.Scalars.size())); | |
| Align CommonAlignment = computeCommonAlignment<LoadInst>(E.Scalars); | |
| // Check if profitable to represent consecutive load + reverse as strided | |
| // load with stride -1. | |
| if (E.isReverse() && | |
| TTI->isLegalStridedLoadStore(VecTy, CommonAlignment)) { | |
| SmallVector<int> Mask; | |
| inversePermutation(E.ReorderIndices, Mask); | |
| auto *BaseLI = cast<LoadInst>(E.Scalars.back()); | |
| InstructionCost OriginalVecCost = | |
| TTI->getMemoryOpCost( | |
| Instruction::Load, VecTy, BaseLI->getAlign(), | |
| BaseLI->getPointerAddressSpace(), CostKind, | |
| TTI::getOperandInfo(BaseLI->getPointerOperand())) + | |
| getShuffleCost(*TTI, TTI::SK_Reverse, VecTy, CostKind, Mask); | |
| Type *StrideTy = DL->getIndexType(BaseLI->getPointerOperandType()); | |
| InstructionCost StridedCost = TTI->getMemIntrinsicInstrCost( | |
| MemIntrinsicCostAttributes( | |
| Intrinsic::experimental_vp_strided_load, VecTy, | |
| BaseLI->getPointerOperand(), | |
| /*VariableMask=*/false, CommonAlignment, BaseLI, | |
| ConstantInt::getSigned( | |
| StrideTy, | |
| -static_cast<int64_t>(DL->getTypeAllocSize(ScalarTy)))), | |
| CostKind); | |
| if (StridedCost < OriginalVecCost || Opts.slp_force_strided_loads) { | |
| // Strided load is more profitable than consecutive load + reverse - | |
| // transform the node to strided load. | |
| StridedPtrInfo SPtrInfo; | |
| SPtrInfo.StrideVal = ConstantInt::get(StrideTy, 1); | |
| SPtrInfo.Ty = VecTy; | |
| TreeEntryToStridedPtrInfoMap[&E] = SPtrInfo; | |
| E.State = TreeEntry::StridedVectorize; | |
| } | |
| } | |
| break; | |
| } | |
| case Instruction::Store: { | |
| if (E.State == TreeEntry::ExpandVectorize) | |
| break; | |
| Type *ScalarTy = | |
| cast<StoreInst>(E.getMainOp())->getValueOperand()->getType(); | |
| auto *VecTy = | |
| cast<FixedVectorType>(getWidenedType(ScalarTy, E.Scalars.size())); | |
| Align CommonAlignment = computeCommonAlignment<StoreInst>(E.Scalars); | |
| // Check if profitable to represent consecutive load + reverse as strided | |
| // load with stride -1. | |
| if (E.isReverse() && | |
| TTI->isLegalStridedLoadStore(VecTy, CommonAlignment)) { | |
| SmallVector<int> Mask; | |
| inversePermutation(E.ReorderIndices, Mask); | |
| auto *BaseSI = cast<StoreInst>(E.Scalars.back()); | |
| InstructionCost OriginalVecCost = | |
| TTI->getMemoryOpCost(Instruction::Store, VecTy, BaseSI->getAlign(), | |
| BaseSI->getPointerAddressSpace(), CostKind, | |
| TTI::getOperandInfo(E.getOperand(0))) + | |
| getShuffleCost(*TTI, TTI::SK_Reverse, VecTy, CostKind, Mask); | |
| Type *StrideTy = DL->getIndexType(BaseSI->getPointerOperandType()); | |
| InstructionCost StridedCost = TTI->getMemIntrinsicInstrCost( | |
| MemIntrinsicCostAttributes( | |
| Intrinsic::experimental_vp_strided_store, VecTy, | |
| BaseSI->getPointerOperand(), | |
| /*VariableMask=*/false, CommonAlignment, BaseSI, | |
| ConstantInt::getSigned( | |
| StrideTy, | |
| -static_cast<int64_t>(DL->getTypeAllocSize(ScalarTy)))), | |
| CostKind); | |
| if (StridedCost < OriginalVecCost) { | |
| // Strided store is more profitable than reverse + consecutive store - | |
| // transform the node to strided store. | |
| E.State = TreeEntry::StridedVectorize; | |
| StridedPtrInfo SPtrInfo; | |
| SPtrInfo.StrideVal = ConstantInt::getSigned(StrideTy, -1); | |
| SPtrInfo.Ty = VecTy; | |
| TreeEntryToStridedPtrInfoMap[&E] = SPtrInfo; | |
| } | |
| } else if (!E.ReorderIndices.empty()) { | |
| // Check for interleaved stores. | |
| auto IsInterleaveMask = [&, &TTI = *TTI](ArrayRef<int> Mask) { | |
| auto *BaseSI = cast<StoreInst>(E.Scalars.front()); | |
| assert(Mask.size() > 1 && "Expected mask greater than 1 element."); | |
| if (Mask.size() < 4) | |
| return 0u; | |
| for (unsigned Factor : seq<unsigned>(2, Mask.size() / 2 + 1)) { | |
| if (ShuffleVectorInst::isInterleaveMask( | |
| Mask, Factor, VecTy->getElementCount().getFixedValue()) && | |
| TTI.isLegalInterleavedAccessType( | |
| VecTy, Factor, BaseSI->getAlign(), | |
| BaseSI->getPointerAddressSpace())) | |
| return Factor; | |
| } | |
| return 0u; | |
| }; | |
| SmallVector<int> Mask(E.ReorderIndices.begin(), E.ReorderIndices.end()); | |
| unsigned InterleaveFactor = IsInterleaveMask(Mask); | |
| if (InterleaveFactor != 0) | |
| E.setInterleave(InterleaveFactor); | |
| } | |
| break; | |
| } | |
| case Instruction::Select: { | |
| if (E.State != TreeEntry::Vectorize) | |
| break; | |
| auto [MinMaxID, SelectOnly] = canConvertToMinOrMaxIntrinsic(E.Scalars); | |
| if (MinMaxID != Intrinsic::not_intrinsic) { | |
| // This node is a minmax node. | |
| E.CombinedOp = TreeEntry::MinMax; | |
| TreeEntry *CondEntry = getOperandEntry(&E, 0); | |
| if (SelectOnly && CondEntry->UserTreeIndex && | |
| CondEntry->State == TreeEntry::Vectorize) { | |
| // The condition node is part of the combined minmax node. | |
| CondEntry->State = TreeEntry::CombinedVectorize; | |
| } | |
| break; | |
| } | |
| // Check for zext + selects, which can be reordered. | |
| SmallVector<unsigned> InversedCmpsIndices; | |
| if (matchesInversedZExtSelect(E, InversedCmpsIndices)) { | |
| auto *CmpTE = getOperandEntry(&E, 0); | |
| auto *Op1TE = getOperandEntry(&E, 1); | |
| auto *Op2TE = getOperandEntry(&E, 2); | |
| // State now is uniform, not alternate opcode. | |
| CmpTE->setOperations( | |
| InstructionsState(CmpTE->getMainOp(), CmpTE->getMainOp())); | |
| // Update mapping between the swapped values and their internal matching | |
| // nodes. | |
| auto UpdateGatherEntry = [&](TreeEntry *OldTE, TreeEntry *NewTE, | |
| Value *V) { | |
| if (isConstant(V)) | |
| return; | |
| auto It = ValueToGatherNodes.find(V); | |
| assert(It != ValueToGatherNodes.end() && | |
| "Expected to find the value in the map."); | |
| auto &C = It->getSecond(); | |
| if (!is_contained(OldTE->Scalars, V)) | |
| C.remove(OldTE); | |
| C.insert(NewTE); | |
| }; | |
| ValueList &Op1 = E.getOperand(1); | |
| ValueList &Op2 = E.getOperand(2); | |
| for (const unsigned Idx : InversedCmpsIndices) { | |
| Value *V1 = Op1TE->Scalars[Idx]; | |
| Value *V2 = Op2TE->Scalars[Idx]; | |
| std::swap(Op1TE->Scalars[Idx], Op2TE->Scalars[Idx]); | |
| std::swap(Op1[Idx], Op2[Idx]); | |
| UpdateGatherEntry(Op1TE, Op2TE, V1); | |
| UpdateGatherEntry(Op2TE, Op1TE, V2); | |
| } | |
| OperandsToTreeEntry.emplace_or_assign(std::make_pair(&E, 1), Op1TE); | |
| OperandsToTreeEntry.emplace_or_assign(std::make_pair(&E, 2), Op2TE); | |
| // NB: Fallback to check if select can be converted to cmp bitcast. | |
| } | |
| if (matchesSelectOfBits(E)) { | |
| // This node is a (reduced or) cmp bitcast node. | |
| const TreeEntry::CombinedOpcode Code = TreeEntry::ReducedCmpBitcast; | |
| E.CombinedOp = Code; | |
| auto *Op1TE = getOperandEntry(&E, 1); | |
| auto *Op2TE = getOperandEntry(&E, 2); | |
| Op1TE->State = TreeEntry::CombinedVectorize; | |
| Op1TE->CombinedOp = Code; | |
| Op2TE->State = TreeEntry::CombinedVectorize; | |
| Op2TE->CombinedOp = Code; | |
| break; | |
| } | |
| break; | |
| } | |
| case Instruction::FSub: | |
| case Instruction::FAdd: | |
| CombineFMulAdd(E); | |
| break; | |
| case Instruction::Shl: | |
| case Instruction::And: { | |
| // Shl/And are not reassociated; guard since this case indexes operands | |
| // 0/1. | |
| if (E.getNumOperands() != 2) | |
| break; | |
| if (E.Idx != 0 || DL->isBigEndian()) | |
| break; | |
| if (!UserIgnoreList) | |
| break; | |
| // Check that all reduction operands are disjoint or instructions. | |
| if (E.getOpcode() == Instruction::Shl && | |
| all_of(*UserIgnoreList, [](Value *V) { | |
| return match(V, m_DisjointOr(m_Value(), m_Value())); | |
| })) { | |
| OrdersType Order; | |
| bool IsBSwap; | |
| bool ForLoads; | |
| if (matchesShlZExt(E, Order, IsBSwap, ForLoads)) { | |
| // This node is a (reduced disjoint or) bitcast node. | |
| TreeEntry::CombinedOpcode Code = | |
| IsBSwap ? (ForLoads ? TreeEntry::ReducedBitcastBSwapLoads | |
| : TreeEntry::ReducedBitcastBSwap) | |
| : (ForLoads ? TreeEntry::ReducedBitcastLoads | |
| : TreeEntry::ReducedBitcast); | |
| E.CombinedOp = Code; | |
| E.ReorderIndices = std::move(Order); | |
| TreeEntry *ZExtEntry = getOperandEntry(&E, 0); | |
| assert(ZExtEntry->UserTreeIndex && | |
| ZExtEntry->State == TreeEntry::Vectorize && | |
| ZExtEntry->getOpcode() == Instruction::ZExt && | |
| "Expected ZExt node."); | |
| // The ZExt node is part of the combined node. | |
| ZExtEntry->State = TreeEntry::CombinedVectorize; | |
| ZExtEntry->CombinedOp = Code; | |
| if (ForLoads) { | |
| TreeEntry *LoadsEntry = getOperandEntry(ZExtEntry, 0); | |
| assert(LoadsEntry->UserTreeIndex && | |
| LoadsEntry->State == TreeEntry::Vectorize && | |
| LoadsEntry->getOpcode() == Instruction::Load && | |
| "Expected Load node."); | |
| // The Load node is part of the combined node. | |
| LoadsEntry->State = TreeEntry::CombinedVectorize; | |
| LoadsEntry->CombinedOp = Code; | |
| } | |
| TreeEntry *ConstEntry = getOperandEntry(&E, 1); | |
| assert(ConstEntry->UserTreeIndex && ConstEntry->isGather() && | |
| "Expected constants only."); | |
| // The ConstNode node is part of the combined node. | |
| ConstEntry->State = TreeEntry::CombinedVectorize; | |
| ConstEntry->CombinedOp = Code; | |
| break; | |
| } | |
| } | |
| // Check that all reduction operands are or instructions. | |
| if (any_of(*UserIgnoreList, [](Value *V) { | |
| return !match(V, m_Or(m_Value(), m_Value())); | |
| })) | |
| break; | |
| if (!matchesBitPack(E)) | |
| break; | |
| // This node is a (reduced or) bit pack node. | |
| E.CombinedOp = TreeEntry::ReducedBitPack; | |
| TreeEntry *ShlEntry = &E; | |
| if (E.getOpcode() == Instruction::And) { | |
| ShlEntry = getOperandEntry(&E, 0); | |
| assert(ShlEntry->UserTreeIndex && | |
| ShlEntry->State == TreeEntry::Vectorize && | |
| ShlEntry->getOpcode() == Instruction::Shl && | |
| "Expected Shl node."); | |
| // The Shl node is part of the combined node. | |
| ShlEntry->State = TreeEntry::CombinedVectorize; | |
| ShlEntry->CombinedOp = TreeEntry::ReducedBitPack; | |
| TreeEntry *MaskEntry = getOperandEntry(&E, 1); | |
| assert(MaskEntry->UserTreeIndex && MaskEntry->isGather() && | |
| "Expected constants only."); | |
| MaskEntry->State = TreeEntry::CombinedVectorize; | |
| MaskEntry->CombinedOp = TreeEntry::ReducedBitPack; | |
| } | |
| TreeEntry *ShiftEntry = getOperandEntry(ShlEntry, 1); | |
| assert(ShiftEntry->UserTreeIndex && ShiftEntry->isGather() && | |
| "Expected constants only."); | |
| ShiftEntry->State = TreeEntry::CombinedVectorize; | |
| ShiftEntry->CombinedOp = TreeEntry::ReducedBitPack; | |
| // The chain of a uniform field extract and its constant operands are | |
| // part of the combined node. | |
| std::optional<BitPackInfo> Info = computeBitPackLayout(E); | |
| TreeEntry *LhsTE = getOperandEntry(ShlEntry, /*Idx=*/0); | |
| const TreeEntry *SrcTE = getBitPackSource(*LhsTE, *Info); | |
| for (TreeEntry *ChainTE = LhsTE; ChainTE != SrcTE; | |
| ChainTE = getOperandEntry(ChainTE, /*Idx=*/0)) { | |
| ChainTE->State = TreeEntry::CombinedVectorize; | |
| ChainTE->CombinedOp = TreeEntry::ReducedBitPack; | |
| for (unsigned Idx : seq<unsigned>(1, ChainTE->getNumOperands())) { | |
| TreeEntry *ConstTE = getOperandEntry(ChainTE, Idx); | |
| ConstTE->State = TreeEntry::CombinedVectorize; | |
| ConstTE->CombinedOp = TreeEntry::ReducedBitPack; | |
| } | |
| } | |
| break; | |
| } | |
| default: | |
| break; | |
| } | |
| } | |
| // The nodes, built for the slices of the gathered nodes, are fused with their | |
| // fmul operands in the vector code too. | |
| for (std::unique_ptr<TreeEntry> &TE : | |
| drop_begin(VectorizableTree, BaseGraphSize)) | |
| if (TE->hasState() && | |
| is_contained({Instruction::FAdd, Instruction::FSub}, TE->getOpcode())) | |
| CombineFMulAdd(*TE); | |
| if (LoadEntriesToVectorize.empty()) { | |
| // Single load node - exit. | |
| if (VectorizableTree.size() <= 1 && getRootNode().hasState() && | |
| getRootNode().getOpcode() == Instruction::Load) | |
| return; | |
| // Small graph with small VF - exit. | |
| constexpr unsigned SmallTree = 3; | |
| constexpr unsigned SmallVF = 2; | |
| if ((VectorizableTree.size() <= SmallTree && | |
| getRootNodeScalars().size() == SmallVF) || | |
| (VectorizableTree.size() <= 2 && UserIgnoreList)) | |
| return; | |
| if (getRootNode().isNonPowOf2Vec() && | |
| getCanonicalGraphSize() != getTreeSize() && UserIgnoreList && | |
| getCanonicalGraphSize() <= SmallTree && | |
| count_if(ArrayRef(VectorizableTree).drop_front(getCanonicalGraphSize()), | |
| [](const std::unique_ptr<TreeEntry> &TE) { | |
| return TE->isGather() && TE->hasState() && | |
| TE->getOpcode() == Instruction::Load && | |
| !allSameBlock(TE->Scalars); | |
| }) == 1) | |
| return; | |
| } | |
| // A list of loads to be gathered during the vectorization process. We can | |
| // try to vectorize them at the end, if profitable. | |
| SmallMapVector<std::tuple<BasicBlock *, Value *, Type *>, | |
| SmallVector<SmallVector<std::pair<LoadInst *, int64_t>>>, 8> | |
| GatheredLoads; | |
| for (std::unique_ptr<TreeEntry> &TE : VectorizableTree) { | |
| TreeEntry &E = *TE; | |
| if (E.isGather() && | |
| ((E.hasState() && E.getOpcode() == Instruction::Load) || | |
| (!E.hasState() && any_of(make_isa_range<LoadInst>(E.Scalars), | |
| [&](LoadInst *LI) { | |
| return !isVectorized(LI) && !isDeleted(LI); | |
| }))) && | |
| !isSplat(E.Scalars)) { | |
| for (Value *V : E.Scalars) { | |
| auto *LI = dyn_cast<LoadInst>(V); | |
| if (!LI) | |
| continue; | |
| if (isDeleted(LI) || isVectorized(LI) || !LI->isSimple()) | |
| continue; | |
| gatherPossiblyVectorizableLoads( | |
| *this, V, *DL, *SE, *TTI, | |
| GatheredLoads[std::make_tuple( | |
| LI->getParent(), | |
| getUnderlyingObject(LI->getPointerOperand(), | |
| Opts.slp_recursion_max_depth), | |
| LI->getType())]); | |
| } | |
| } | |
| } | |
| // Try to vectorize gathered loads if this is not just a gather of loads. | |
| if (!GatheredLoads.empty()) | |
| tryToVectorizeGatheredLoads(GatheredLoads); | |
| } | |
| /// Merges shuffle masks and emits final shuffle instruction, if required. It | |
| /// supports shuffling of 2 input vectors. It implements lazy shuffles emission, | |
| /// when the actual shuffle instruction is generated only if this is actually | |
| /// required. Otherwise, the shuffle instruction emission is delayed till the | |
| /// end of the process, to reduce the number of emitted instructions and further | |
| /// analysis/transformations. | |
| class BoUpSLP::ShuffleCostEstimator : public BaseShuffleAnalysis { | |
| bool IsFinalized = false; | |
| SmallVector<int> CommonMask; | |
| // Each input vector can be tagged with extra context information | |
| struct InVector { | |
| PointerUnion<Value *, const TreeEntry *> Vec = nullptr; | |
| TargetTransformInfo::VectorInstrContext Ctx = | |
| TargetTransformInfo::VectorInstrContext::None; | |
| InVector(Value *Vec, TargetTransformInfo::VectorInstrContext Ctx = | |
| TargetTransformInfo::VectorInstrContext::None) | |
| : Vec(Vec), Ctx(Ctx) {} | |
| InVector(const TreeEntry *Vec, | |
| TargetTransformInfo::VectorInstrContext Ctx = | |
| TargetTransformInfo::VectorInstrContext::None) | |
| : Vec(Vec), Ctx(Ctx) {} | |
| }; | |
| SmallVector<InVector, 2> InVectors; | |
| /// Captures the original scalar VL of a single, "clean" gather() call so | |
| /// the values can be forwarded as the Args operand to getShuffleCost() for | |
| /// the final permutation in finalize(). This lets the target cost model | |
| /// recognize patterns such as broadcast-of-load (e.g. on X86, | |
| /// vbroadcast{ss,sd} folds the broadcast and the load into one instruction | |
| /// under AVX/AVX2 and is reported as TCC_Free by getShuffleCost). The | |
| /// state machine is: | |
| /// * engaged + empty: tracking active, no qualifying gather seen yet. | |
| /// * engaged + non-empty: exactly one qualifying gather observed and its | |
| /// VL still corresponds to InVectors.front(). | |
| /// * disengaged: the cached VL is no longer trustworthy (multiple | |
| /// gather() calls, or a state-mutating add() happened). | |
| std::optional<SmallVector<Value *>> BVValues = SmallVector<Value *>(); | |
| const TargetTransformInfo &TTI; | |
| InstructionCost Cost = 0; | |
| SmallDenseSet<Value *> VectorizedVals; | |
| BoUpSLP &R; | |
| SmallPtrSetImpl<Value *> &CheckedExtracts; | |
| const TTI::TargetCostKind CostKind; | |
| /// While set, still trying to estimate the cost for the same nodes and we | |
| /// can delay actual cost estimation (virtual shuffle instruction emission). | |
| /// May help better estimate the cost if same nodes must be permuted + allows | |
| /// to move most of the long shuffles cost estimation to TTI. | |
| bool SameNodesEstimated = true; | |
| static Constant *getAllOnesValue(const DataLayout &DL, Type *Ty) { | |
| if (Ty->getScalarType()->isPointerTy()) { | |
| Constant *Res = ConstantExpr::getIntToPtr( | |
| ConstantInt::getAllOnesValue( | |
| IntegerType::get(Ty->getContext(), | |
| DL.getTypeStoreSizeInBits(Ty->getScalarType()))), | |
| Ty->getScalarType()); | |
| if (auto *VTy = dyn_cast<VectorType>(Ty)) | |
| Res = ConstantVector::getSplat(VTy->getElementCount(), Res); | |
| return Res; | |
| } | |
| return Constant::getAllOnesValue(Ty); | |
| } | |
| InstructionCost getBuildVectorCost(ArrayRef<Value *> VL, Value *Root, | |
| TTI::VectorInstrContext ContextHint) { | |
| if ((!Root && allConstant(VL)) || all_of(VL, IsaPred<UndefValue>)) | |
| return TTI::TCC_Free; | |
| auto *VecTy = cast<VectorType>(getWidenedType(ScalarTy, VL.size())); | |
| InstructionCost GatherCost = 0; | |
| if (!Root && isSplat(VL)) { | |
| // Found the broadcasting of the single scalar, calculate the cost as | |
| // the broadcast. | |
| const auto *It = find_if_not(VL, IsaPred<UndefValue>); | |
| assert(It != VL.end() && "Expected at least one non-undef value."); | |
| // Add broadcast for non-identity shuffle only. | |
| bool NeedShuffle = | |
| count(VL, *It) > 1 && | |
| (VL.front() != *It || !all_of(VL.drop_front(), IsaPred<UndefValue>)); | |
| if (!NeedShuffle) { | |
| if (isa<FixedVectorType>(ScalarTy)) { | |
| assert(R.Opts.slp_revec && "FixedVectorType is not expected."); | |
| return TTI.getShuffleCost( | |
| TTI::SK_InsertSubvector, VecTy, VecTy, CostKind, {}, | |
| std::distance(VL.begin(), It) * getNumElements(ScalarTy), | |
| cast<FixedVectorType>(ScalarTy)); | |
| } | |
| return TTI.getVectorInstrCost(Instruction::InsertElement, VecTy, | |
| CostKind, std::distance(VL.begin(), It), | |
| PoisonValue::get(VecTy), *It, | |
| ContextHint); | |
| } | |
| SmallVector<int> ShuffleMask(VL.size(), PoisonMaskElem); | |
| transform(VL, ShuffleMask.begin(), [](Value *V) { | |
| return isa<PoisonValue>(V) ? PoisonMaskElem : 0; | |
| }); | |
| InstructionCost InsertCost = | |
| TTI.getVectorInstrCost(Instruction::InsertElement, VecTy, CostKind, 0, | |
| PoisonValue::get(VecTy), *It, ContextHint); | |
| return InsertCost + getShuffleCost(TTI, TargetTransformInfo::SK_Broadcast, | |
| VecTy, CostKind, ShuffleMask, | |
| /*Index=*/0, /*SubTp=*/nullptr, | |
| /*Args=*/*It, ContextHint); | |
| } | |
| assert(ContextHint != TTI::VectorInstrContext::SplatOpFolded && | |
| "Expected SplatOpFolded to be a splat"); | |
| return GatherCost + (all_of(VL, IsaPred<UndefValue>) | |
| ? TTI::TCC_Free | |
| : R.getGatherCost(VL, !Root, ScalarTy)); | |
| }; | |
| /// Compute the cost of creating a vector containing the extracted values from | |
| /// \p VL. | |
| InstructionCost | |
| computeExtractCost(ArrayRef<Value *> VL, ArrayRef<int> Mask, | |
| ArrayRef<std::optional<TTI::ShuffleKind>> ShuffleKinds, | |
| unsigned NumParts) { | |
| assert(VL.size() > NumParts && "Unexpected scalarized shuffle."); | |
| unsigned NumElts = accumulate(VL, 0, [](unsigned Sz, Value *V) { | |
| auto *EE = dyn_cast<ExtractElementInst>(V); | |
| if (!EE) | |
| return Sz; | |
| auto *VecTy = dyn_cast<FixedVectorType>(EE->getVectorOperandType()); | |
| if (!VecTy) | |
| return Sz; | |
| return std::max(Sz, VecTy->getNumElements()); | |
| }); | |
| // FIXME: this must be moved to TTI for better estimation. | |
| unsigned EltsPerVector = getPartNumElems(VL.size(), NumParts); | |
| auto CheckPerRegistersShuffle = [&](MutableArrayRef<int> Mask, | |
| SmallVectorImpl<unsigned> &Indices, | |
| SmallVectorImpl<unsigned> &SubVecSizes) | |
| -> std::optional<TTI::ShuffleKind> { | |
| if (NumElts <= EltsPerVector) | |
| return std::nullopt; | |
| int OffsetReg0 = alignDown(accumulate(Mask, INT_MAX, | |
| [](int S, int I) { | |
| if (I == PoisonMaskElem) | |
| return S; | |
| return std::min(S, I); | |
| }), | |
| EltsPerVector); | |
| int OffsetReg1 = OffsetReg0; | |
| DenseSet<int> RegIndices; | |
| // Check that if trying to permute same single/2 input vectors. | |
| TTI::ShuffleKind ShuffleKind = TTI::SK_PermuteSingleSrc; | |
| int FirstRegId = -1; | |
| Indices.assign(1, OffsetReg0); | |
| auto HasLane = [](const auto &P) { return P.value() != PoisonMaskElem; }; | |
| for (auto [Pos, I] : make_filter_range(enumerate(Mask), HasLane)) { | |
| int Idx = I - OffsetReg0; | |
| int RegId = | |
| (Idx / NumElts) * NumParts + (Idx % NumElts) / EltsPerVector; | |
| if (FirstRegId < 0) | |
| FirstRegId = RegId; | |
| RegIndices.insert(RegId); | |
| if (RegIndices.size() > 2) | |
| return std::nullopt; | |
| if (RegIndices.size() == 2) { | |
| ShuffleKind = TTI::SK_PermuteTwoSrc; | |
| if (Indices.size() == 1) { | |
| OffsetReg1 = alignDown( | |
| std::accumulate( | |
| std::next(Mask.begin(), Pos), Mask.end(), INT_MAX, | |
| [&](int S, int I) { | |
| if (I == PoisonMaskElem) | |
| return S; | |
| int RegId = ((I - OffsetReg0) / NumElts) * NumParts + | |
| ((I - OffsetReg0) % NumElts) / EltsPerVector; | |
| if (RegId == FirstRegId) | |
| return S; | |
| return std::min(S, I); | |
| }), | |
| EltsPerVector); | |
| unsigned Index = OffsetReg1 % NumElts; | |
| Indices.push_back(Index); | |
| SubVecSizes.push_back(std::min(NumElts - Index, EltsPerVector)); | |
| } | |
| Idx = I - OffsetReg1; | |
| } | |
| I = (Idx % NumElts) % EltsPerVector + | |
| (RegId == FirstRegId ? 0 : EltsPerVector); | |
| } | |
| return ShuffleKind; | |
| }; | |
| InstructionCost Cost = 0; | |
| // Process extracts in blocks of EltsPerVector to check if the source vector | |
| // operand can be re-used directly. If not, add the cost of creating a | |
| // shuffle to extract the values into a vector register. | |
| for (unsigned Part : seq<unsigned>(NumParts)) { | |
| if (!ShuffleKinds[Part]) | |
| continue; | |
| ArrayRef<int> MaskSlice = Mask.slice( | |
| Part * EltsPerVector, getNumElems(Mask.size(), EltsPerVector, Part)); | |
| SmallVector<int> SubMask(EltsPerVector, PoisonMaskElem); | |
| copy(MaskSlice, SubMask.begin()); | |
| SmallVector<unsigned, 2> Indices; | |
| SmallVector<unsigned, 2> SubVecSizes; | |
| std::optional<TTI::ShuffleKind> RegShuffleKind = | |
| CheckPerRegistersShuffle(SubMask, Indices, SubVecSizes); | |
| if (!RegShuffleKind) { | |
| if (*ShuffleKinds[Part] != TTI::SK_PermuteSingleSrc || | |
| !ShuffleVectorInst::isIdentityMask( | |
| MaskSlice, std::max<unsigned>(NumElts, MaskSlice.size()))) | |
| Cost += getShuffleCost( | |
| TTI, *ShuffleKinds[Part], | |
| cast<VectorType>(getWidenedType(ScalarTy, NumElts)), CostKind, | |
| MaskSlice); | |
| continue; | |
| } | |
| if (*RegShuffleKind != TTI::SK_PermuteSingleSrc || | |
| !ShuffleVectorInst::isIdentityMask(SubMask, EltsPerVector)) { | |
| Cost += getShuffleCost( | |
| TTI, *RegShuffleKind, | |
| cast<VectorType>(getWidenedType(ScalarTy, EltsPerVector)), CostKind, | |
| SubMask); | |
| } | |
| const unsigned BaseVF = getFullVectorNumberOfElements( | |
| *R.TTI, VL.front()->getType(), alignTo(NumElts, EltsPerVector), | |
| R.Opts.slp_revec); | |
| for (const auto [Idx, SubVecSize] : zip(Indices, SubVecSizes)) { | |
| assert((Idx + SubVecSize) <= BaseVF && | |
| "SK_ExtractSubvector index out of range"); | |
| Cost += getShuffleCost( | |
| TTI, TTI::SK_ExtractSubvector, | |
| cast<VectorType>(getWidenedType(ScalarTy, BaseVF)), CostKind, {}, | |
| Idx, cast<VectorType>(getWidenedType(ScalarTy, SubVecSize))); | |
| } | |
| // Second attempt to check, if just a permute is better estimated than | |
| // subvector extract. | |
| SubMask.assign(NumElts, PoisonMaskElem); | |
| copy(MaskSlice, SubMask.begin()); | |
| InstructionCost OriginalCost = | |
| getShuffleCost(TTI, *ShuffleKinds[Part], | |
| cast<VectorType>(getWidenedType(ScalarTy, NumElts)), | |
| CostKind, SubMask); | |
| if (OriginalCost < Cost) | |
| Cost = OriginalCost; | |
| } | |
| return Cost; | |
| } | |
| /// Adds the cost of reshuffling \p E1 and \p E2 (if present), using given | |
| /// mask \p Mask, register number \p Part, that includes \p SliceSize | |
| /// elements. | |
| void estimateNodesPermuteCost(const TreeEntry &E1, const TreeEntry *E2, | |
| ArrayRef<int> Mask, unsigned Part, | |
| unsigned SliceSize) { | |
| if (SameNodesEstimated) { | |
| // Delay the cost estimation if the same nodes are reshuffling. | |
| // If we already requested the cost of reshuffling of E1 and E2 before, no | |
| // need to estimate another cost with the sub-Mask, instead include this | |
| // sub-Mask into the CommonMask to estimate it later and avoid double cost | |
| // estimation. | |
| if ((InVectors.size() == 2 && | |
| cast<const TreeEntry *>(InVectors.front().Vec) == &E1 && | |
| cast<const TreeEntry *>(InVectors.back().Vec) == E2) || | |
| (!E2 && cast<const TreeEntry *>(InVectors.front().Vec) == &E1)) { | |
| unsigned Limit = getNumElems(Mask.size(), SliceSize, Part); | |
| assert(all_of(ArrayRef(CommonMask).slice(Part * SliceSize, Limit), | |
| [](int Idx) { return Idx == PoisonMaskElem; }) && | |
| "Expected all poisoned elements."); | |
| ArrayRef<int> SubMask = ArrayRef(Mask).slice(Part * SliceSize, Limit); | |
| copy(SubMask, std::next(CommonMask.begin(), SliceSize * Part)); | |
| return; | |
| } | |
| // Found non-matching nodes - need to estimate the cost for the matched | |
| // and transform mask. | |
| if (InVectors.size() == 1) | |
| Cost += createShuffle(InVectors.front().Vec, nullptr, CommonMask, {}, | |
| InVectors.front().Ctx); | |
| else | |
| Cost += createShuffle(InVectors.front().Vec, InVectors.back().Vec, | |
| CommonMask, {}, InVectors.front().Ctx, | |
| InVectors.back().Ctx); | |
| transformMaskAfterShuffle(CommonMask, CommonMask); | |
| } else if (InVectors.size() == 2) { | |
| Cost += | |
| createShuffle(InVectors.front().Vec, InVectors.back().Vec, CommonMask, | |
| {}, InVectors.front().Ctx, InVectors.back().Ctx); | |
| transformMaskAfterShuffle(CommonMask, CommonMask); | |
| } | |
| SameNodesEstimated = false; | |
| if (!E2 && InVectors.size() == 1) { | |
| unsigned VF = E1.getVectorFactor(); | |
| if (Value *V1 = dyn_cast<Value *>(InVectors.front().Vec)) { | |
| VF = std::max(VF, getVF(V1)); | |
| } else { | |
| const auto *E = cast<const TreeEntry *>(InVectors.front().Vec); | |
| VF = std::max(VF, E->getVectorFactor()); | |
| } | |
| for (unsigned Idx = 0, Sz = CommonMask.size(); Idx < Sz; ++Idx) | |
| if (Mask[Idx] != PoisonMaskElem && CommonMask[Idx] == PoisonMaskElem) | |
| CommonMask[Idx] = Mask[Idx] + VF; | |
| Cost += createShuffle(InVectors.front().Vec, &E1, CommonMask, {}, | |
| InVectors.front().Ctx); | |
| transformMaskAfterShuffle(CommonMask, CommonMask); | |
| } else { | |
| auto P = InVectors.front(); | |
| Cost += createShuffle(&E1, E2, Mask, {}, TTI::VectorInstrContext::None); | |
| unsigned VF = Mask.size(); | |
| if (Value *V1 = dyn_cast<Value *>(P.Vec)) { | |
| VF = std::max(VF, | |
| getNumElements(V1->getType())); | |
| } else { | |
| const auto *E = cast<const TreeEntry *>(P.Vec); | |
| VF = std::max(VF, E->getVectorFactor()); | |
| } | |
| for (unsigned Idx = 0, Sz = CommonMask.size(); Idx < Sz; ++Idx) | |
| if (Mask[Idx] != PoisonMaskElem) | |
| CommonMask[Idx] = Idx + (InVectors.empty() ? 0 : VF); | |
| Cost += createShuffle(P.Vec, InVectors.front().Vec, CommonMask, {}, P.Ctx, | |
| InVectors.front().Ctx); | |
| transformMaskAfterShuffle(CommonMask, CommonMask); | |
| } | |
| } | |
| class ShuffleCostBuilder { | |
| const TargetTransformInfo &TTI; | |
| const TTI::TargetCostKind CostKind; | |
| static bool isEmptyOrIdentity(ArrayRef<int> Mask, unsigned VF) { | |
| int Index = -1; | |
| return Mask.empty() || | |
| (VF == Mask.size() && | |
| ShuffleVectorInst::isIdentityMask(Mask, VF)) || | |
| (ShuffleVectorInst::isExtractSubvectorMask(Mask, VF, Index) && | |
| Index == 0); | |
| } | |
| public: | |
| ShuffleCostBuilder(const TargetTransformInfo &TTI, | |
| const TTI::TargetCostKind CostKind) | |
| : TTI(TTI), CostKind(CostKind) {} | |
| ~ShuffleCostBuilder() = default; | |
| InstructionCost createShuffleVector(Value *V1, Value *, | |
| ArrayRef<int> Mask) const { | |
| // Empty mask or identity mask are free. | |
| unsigned VF = | |
| cast<VectorType>(V1->getType())->getElementCount().getKnownMinValue(); | |
| if (isEmptyOrIdentity(Mask, VF)) | |
| return TTI::TCC_Free; | |
| return getShuffleCost(TTI, TTI::SK_PermuteTwoSrc, | |
| cast<VectorType>(V1->getType()), CostKind, Mask); | |
| } | |
| InstructionCost | |
| createShuffleVector(Value *V1, ArrayRef<int> Mask, ArrayRef<Value *> VL, | |
| TTI::VectorInstrContext ContextHint = | |
| TTI::VectorInstrContext::None) const { | |
| // Empty mask or identity mask are free. | |
| unsigned VF = | |
| cast<VectorType>(V1->getType())->getElementCount().getKnownMinValue(); | |
| if (isEmptyOrIdentity(Mask, VF)) | |
| return TTI::TCC_Free; | |
| return getShuffleCost(TTI, TTI::SK_PermuteSingleSrc, | |
| cast<VectorType>(V1->getType()), CostKind, Mask, | |
| /*Index=*/0, /*SubTp=*/nullptr, VL, ContextHint); | |
| } | |
| InstructionCost createIdentity(Value *) const { return TTI::TCC_Free; } | |
| InstructionCost createPoison(Type *Ty, unsigned VF) const { | |
| return TTI::TCC_Free; | |
| } | |
| void resizeToMatch(Value *&, Value *&) const {} | |
| }; | |
| /// Smart shuffle instruction emission, walks through shuffles trees and | |
| /// tries to find the best matching vector for the actual shuffle | |
| /// instruction. | |
| InstructionCost | |
| createShuffle(const PointerUnion<Value *, const TreeEntry *> &P1, | |
| const PointerUnion<Value *, const TreeEntry *> &P2, | |
| ArrayRef<int> Mask, ArrayRef<Value *> VL = {}, | |
| TTI::VectorInstrContext Ctx1 = TTI::VectorInstrContext::None, | |
| TTI::VectorInstrContext Ctx2 = TTI::VectorInstrContext::None) { | |
| ShuffleCostBuilder Builder(TTI, CostKind); | |
| SmallVector<int> CommonMask(Mask); | |
| Value *V1 = dyn_cast<Value *>(P1), *V2 = dyn_cast_if_present<Value *>(P2); | |
| unsigned CommonVF = Mask.size(); | |
| InstructionCost ExtraCost = 0; | |
| auto GetNodeMinBWAffectedCost = [&](const TreeEntry &E, | |
| unsigned VF) -> InstructionCost { | |
| if (E.isGather() && allConstant(E.Scalars)) | |
| return TTI::TCC_Free; | |
| Type *EScalarTy = E.Scalars.front()->getType(); | |
| bool IsSigned = true; | |
| if (auto It = R.MinBWs.find(&E); It != R.MinBWs.end()) { | |
| EScalarTy = IntegerType::get(EScalarTy->getContext(), It->second.first); | |
| IsSigned = It->second.second; | |
| } | |
| if (EScalarTy != ScalarTy) { | |
| unsigned CastOpcode = Instruction::Trunc; | |
| unsigned DstSz = R.DL->getTypeSizeInBits(ScalarTy); | |
| unsigned SrcSz = R.DL->getTypeSizeInBits(EScalarTy); | |
| if (DstSz > SrcSz) | |
| CastOpcode = IsSigned ? Instruction::SExt : Instruction::ZExt; | |
| return TTI.getCastInstrCost(CastOpcode, getWidenedType(ScalarTy, VF), | |
| getWidenedType(EScalarTy, VF), | |
| R.getCastContextHint(E), CostKind); | |
| } | |
| return TTI::TCC_Free; | |
| }; | |
| auto GetValueMinBWAffectedCost = [&](const Value *V) -> InstructionCost { | |
| if (isa<Constant>(V)) | |
| return TTI::TCC_Free; | |
| auto *VecTy = cast<VectorType>(V->getType()); | |
| Type *EScalarTy = VecTy->getElementType(); | |
| if (EScalarTy != ScalarTy) { | |
| bool IsSigned = !isKnownNonNegative(V, SimplifyQuery(*R.DL)); | |
| unsigned CastOpcode = Instruction::Trunc; | |
| unsigned DstSz = R.DL->getTypeSizeInBits(ScalarTy); | |
| unsigned SrcSz = R.DL->getTypeSizeInBits(EScalarTy); | |
| if (DstSz > SrcSz) | |
| CastOpcode = IsSigned ? Instruction::SExt : Instruction::ZExt; | |
| TTI::CastContextHint CCH = TTI::CastContextHint::None; | |
| if (ArrayRef<TreeEntry *> TEs = R.getTreeEntries(V); TEs.size() == 1) | |
| CCH = R.getCastContextHint(*TEs.front()); | |
| return TTI.getCastInstrCost( | |
| CastOpcode, VectorType::get(ScalarTy, VecTy->getElementCount()), | |
| VecTy, CCH, CostKind); | |
| } | |
| return TTI::TCC_Free; | |
| }; | |
| if (!V1 && !V2 && !P2.isNull()) { | |
| // Shuffle 2 entry nodes. | |
| const TreeEntry *E = cast<const TreeEntry *>(P1); | |
| unsigned VF = E->getVectorFactor(); | |
| const TreeEntry *E2 = cast<const TreeEntry *>(P2); | |
| CommonVF = std::max(VF, E2->getVectorFactor()); | |
| assert(all_of(Mask, | |
| [=](int Idx) { | |
| return Idx < 2 * static_cast<int>(CommonVF); | |
| }) && | |
| "All elements in mask must be less than 2 * CommonVF."); | |
| if (E->Scalars.size() == E2->Scalars.size()) { | |
| SmallVector<int> EMask = E->getCommonMask(); | |
| SmallVector<int> E2Mask = E2->getCommonMask(); | |
| if (!EMask.empty() || !E2Mask.empty()) { | |
| for (int &Idx : make_filter_range( | |
| CommonMask, [](int Idx) { return Idx != PoisonMaskElem; })) { | |
| if (Idx < static_cast<int>(CommonVF) && !EMask.empty()) | |
| Idx = EMask[Idx]; | |
| else if (Idx >= static_cast<int>(CommonVF)) | |
| Idx = (E2Mask.empty() ? Idx - CommonVF : E2Mask[Idx - CommonVF]) + | |
| E->Scalars.size(); | |
| } | |
| } | |
| CommonVF = E->Scalars.size(); | |
| ExtraCost += GetNodeMinBWAffectedCost(*E, CommonVF) + | |
| GetNodeMinBWAffectedCost(*E2, CommonVF); | |
| } else { | |
| ExtraCost += GetNodeMinBWAffectedCost(*E, E->getVectorFactor()) + | |
| GetNodeMinBWAffectedCost(*E2, E2->getVectorFactor()); | |
| } | |
| V1 = Constant::getNullValue(getWidenedType(ScalarTy, CommonVF)); | |
| V2 = getAllOnesValue(*R.DL, getWidenedType(ScalarTy, CommonVF)); | |
| } else if (!V1 && P2.isNull()) { | |
| // Shuffle single entry node. | |
| const TreeEntry *E = cast<const TreeEntry *>(P1); | |
| unsigned VF = E->getVectorFactor(); | |
| CommonVF = VF; | |
| assert( | |
| all_of(Mask, | |
| [=](int Idx) { return Idx < static_cast<int>(CommonVF); }) && | |
| "All elements in mask must be less than CommonVF."); | |
| if (E->Scalars.size() == Mask.size() && VF != Mask.size()) { | |
| SmallVector<int> EMask = E->getCommonMask(); | |
| assert(!EMask.empty() && "Expected non-empty common mask."); | |
| for (int &Idx : CommonMask) { | |
| if (Idx != PoisonMaskElem) | |
| Idx = EMask[Idx]; | |
| } | |
| CommonVF = E->Scalars.size(); | |
| } else if (unsigned Factor = E->getInterleaveFactor(); | |
| Factor > 0 && E->Scalars.size() != Mask.size() && | |
| ShuffleVectorInst::isDeInterleaveMaskOfFactor(CommonMask, | |
| Factor)) { | |
| // Deinterleaved nodes are free. | |
| std::iota(CommonMask.begin(), CommonMask.end(), 0); | |
| } | |
| ExtraCost += GetNodeMinBWAffectedCost(*E, CommonVF); | |
| V1 = Constant::getNullValue(getWidenedType(ScalarTy, CommonVF)); | |
| // Not identity/broadcast? Try to see if the original vector is better. | |
| if (!E->ReorderIndices.empty() && CommonVF == E->ReorderIndices.size() && | |
| CommonVF == CommonMask.size() && | |
| any_of(enumerate(CommonMask), | |
| [](const auto &&P) { | |
| return P.value() != PoisonMaskElem && | |
| static_cast<unsigned>(P.value()) != P.index(); | |
| }) && | |
| any_of(CommonMask, | |
| [](int Idx) { return Idx != PoisonMaskElem && Idx != 0; })) { | |
| SmallVector<int> ReorderMask; | |
| inversePermutation(E->ReorderIndices, ReorderMask); | |
| addMask(CommonMask, ReorderMask); | |
| } | |
| } else if (V1 && P2.isNull()) { | |
| // Shuffle single vector. | |
| ExtraCost += GetValueMinBWAffectedCost(V1); | |
| CommonVF = getVF(V1); | |
| assert( | |
| all_of(Mask, | |
| [=](int Idx) { return Idx < static_cast<int>(CommonVF); }) && | |
| "All elements in mask must be less than CommonVF."); | |
| } else if (V1 && !V2) { | |
| // Shuffle vector and tree node. | |
| unsigned VF = getVF(V1); | |
| const TreeEntry *E2 = cast<const TreeEntry *>(P2); | |
| CommonVF = std::max(VF, E2->getVectorFactor()); | |
| assert(all_of(Mask, | |
| [=](int Idx) { | |
| return Idx < 2 * static_cast<int>(CommonVF); | |
| }) && | |
| "All elements in mask must be less than 2 * CommonVF."); | |
| if (E2->Scalars.size() == VF && VF != CommonVF) { | |
| SmallVector<int> E2Mask = E2->getCommonMask(); | |
| assert(!E2Mask.empty() && "Expected non-empty common mask."); | |
| for (int &Idx : make_filter_range( | |
| CommonMask, [](int Idx) { return Idx != PoisonMaskElem; })) { | |
| if (Idx >= static_cast<int>(CommonVF)) | |
| Idx = E2Mask[Idx - CommonVF] + VF; | |
| } | |
| CommonVF = VF; | |
| } | |
| ExtraCost += GetValueMinBWAffectedCost(V1); | |
| V1 = Constant::getNullValue(getWidenedType(ScalarTy, CommonVF)); | |
| ExtraCost += GetNodeMinBWAffectedCost( | |
| *E2, std::min(CommonVF, E2->getVectorFactor())); | |
| V2 = getAllOnesValue(*R.DL, getWidenedType(ScalarTy, CommonVF)); | |
| } else if (!V1 && V2) { | |
| // Shuffle vector and tree node. | |
| unsigned VF = getVF(V2); | |
| const TreeEntry *E1 = cast<const TreeEntry *>(P1); | |
| CommonVF = std::max(VF, E1->getVectorFactor()); | |
| assert(all_of(Mask, | |
| [=](int Idx) { | |
| return Idx < 2 * static_cast<int>(CommonVF); | |
| }) && | |
| "All elements in mask must be less than 2 * CommonVF."); | |
| if (E1->Scalars.size() == VF && VF != CommonVF) { | |
| SmallVector<int> E1Mask = E1->getCommonMask(); | |
| assert(!E1Mask.empty() && "Expected non-empty common mask."); | |
| for (int &Idx : make_filter_range( | |
| CommonMask, [](int Idx) { return Idx != PoisonMaskElem; })) { | |
| if (Idx >= static_cast<int>(CommonVF)) | |
| Idx = E1Mask[Idx - CommonVF] + VF; | |
| else | |
| Idx = E1Mask[Idx]; | |
| } | |
| CommonVF = VF; | |
| } | |
| ExtraCost += GetNodeMinBWAffectedCost( | |
| *E1, std::min(CommonVF, E1->getVectorFactor())); | |
| V1 = Constant::getNullValue(getWidenedType(ScalarTy, CommonVF)); | |
| ExtraCost += GetValueMinBWAffectedCost(V2); | |
| V2 = getAllOnesValue(*R.DL, getWidenedType(ScalarTy, CommonVF)); | |
| } else { | |
| assert(V1 && V2 && "Expected both vectors."); | |
| unsigned VF = getVF(V1); | |
| CommonVF = std::max(VF, getVF(V2)); | |
| assert(all_of(Mask, | |
| [=](int Idx) { | |
| return Idx < 2 * static_cast<int>(CommonVF); | |
| }) && | |
| "All elements in mask must be less than 2 * CommonVF."); | |
| ExtraCost += | |
| GetValueMinBWAffectedCost(V1) + GetValueMinBWAffectedCost(V2); | |
| if (V1->getType() != V2->getType()) { | |
| V1 = Constant::getNullValue(getWidenedType(ScalarTy, CommonVF)); | |
| V2 = getAllOnesValue(*R.DL, getWidenedType(ScalarTy, CommonVF)); | |
| } else { | |
| if (cast<VectorType>(V1->getType())->getElementType() != ScalarTy) | |
| V1 = Constant::getNullValue(getWidenedType(ScalarTy, CommonVF)); | |
| if (cast<VectorType>(V2->getType())->getElementType() != ScalarTy) | |
| V2 = getAllOnesValue(*R.DL, getWidenedType(ScalarTy, CommonVF)); | |
| } | |
| } | |
| InVectors.front() = | |
| Constant::getNullValue(getWidenedType(ScalarTy, CommonMask.size())); | |
| if (InVectors.size() == 2) | |
| InVectors.pop_back(); | |
| TTI::VectorInstrContext Ctx = | |
| P2.isNull() ? Ctx1 : TTI::combineVectorInstrContexts(Ctx1, Ctx2); | |
| return ExtraCost + BaseShuffleAnalysis::createShuffle<InstructionCost>( | |
| V1, V2, CommonMask, Builder, ScalarTy, | |
| R.Opts.slp_revec, VL, Ctx); | |
| } | |
| public: | |
| ShuffleCostEstimator(Type *ScalarTy, TargetTransformInfo &TTI, | |
| ArrayRef<Value *> VectorizedVals, BoUpSLP &R, | |
| SmallPtrSetImpl<Value *> &CheckedExtracts) | |
| : BaseShuffleAnalysis(ScalarTy), TTI(TTI), | |
| VectorizedVals(VectorizedVals.begin(), VectorizedVals.end()), R(R), | |
| CheckedExtracts(CheckedExtracts), CostKind(R.getCostKind()) {} | |
| Value *adjustExtracts(const TreeEntry *E, MutableArrayRef<int> Mask, | |
| ArrayRef<std::optional<TTI::ShuffleKind>> ShuffleKinds, | |
| unsigned NumParts, bool &UseVecBaseAsInput) { | |
| UseVecBaseAsInput = false; | |
| if (Mask.empty()) | |
| return nullptr; | |
| Value *VecBase = nullptr; | |
| SmallVector<Value *> VL(E->Scalars.begin(), E->Scalars.end()); | |
| if (!E->ReorderIndices.empty()) { | |
| SmallVector<int> ReorderMask(E->ReorderIndices.begin(), | |
| E->ReorderIndices.end()); | |
| reorderScalars(VL, ReorderMask); | |
| } | |
| // Check if it can be considered reused if same extractelements were | |
| // vectorized already. | |
| bool PrevNodeFound = any_of( | |
| ArrayRef(R.VectorizableTree).take_front(E->Idx), | |
| [&](const std::unique_ptr<TreeEntry> &TE) { | |
| return ((TE->hasState() && !TE->isAltShuffle() && | |
| TE->getOpcode() == Instruction::ExtractElement) || | |
| TE->isGather()) && | |
| all_of(enumerate(TE->Scalars), [&](auto &&Data) { | |
| return VL.size() > Data.index() && | |
| (Mask[Data.index()] == PoisonMaskElem || | |
| isa<UndefValue>(VL[Data.index()]) || | |
| Data.value() == VL[Data.index()]); | |
| }); | |
| }); | |
| SmallPtrSet<Value *, 4> UniqueBases; | |
| unsigned SliceSize = getPartNumElems(VL.size(), NumParts); | |
| SmallDenseMap<Value *, APInt, 4> VectorOpsToExtracts; | |
| for (unsigned Part : seq<unsigned>(NumParts)) { | |
| unsigned Limit = getNumElems(VL.size(), SliceSize, Part); | |
| ArrayRef<int> SubMask = Mask.slice(Part * SliceSize, Limit); | |
| for (auto [I, V] : | |
| enumerate(ArrayRef(VL).slice(Part * SliceSize, Limit))) { | |
| // Ignore non-extractelement scalars. | |
| if (isa<UndefValue>(V) || | |
| (!SubMask.empty() && SubMask[I] == PoisonMaskElem)) | |
| continue; | |
| // If all users of instruction are going to be vectorized and this | |
| // instruction itself is not going to be vectorized, consider this | |
| // instruction as dead and remove its cost from the final cost of the | |
| // vectorized tree. | |
| // Also, avoid adjusting the cost for extractelements with multiple uses | |
| // in different graph entries. | |
| auto *EE = cast<ExtractElementInst>(V); | |
| VecBase = EE->getVectorOperand(); | |
| UniqueBases.insert(VecBase); | |
| ArrayRef<TreeEntry *> VEs = R.getTreeEntries(V); | |
| if (!CheckedExtracts.insert(V).second || | |
| !R.areAllUsersVectorized(cast<Instruction>(V), &VectorizedVals) || | |
| any_of(VEs, | |
| [&](const TreeEntry *TE) { | |
| return R.DeletedNodes.contains(TE) || | |
| R.TransformedToGatherNodes.contains(TE); | |
| }) || | |
| (E->UserTreeIndex && E->UserTreeIndex.EdgeIdx == UINT_MAX && | |
| !R.isVectorized(EE) && | |
| count_if(E->Scalars, [&](Value *V) { return V == EE; }) != | |
| count_if(E->UserTreeIndex.UserTE->Scalars, | |
| [&](Value *V) { return V == EE; })) || | |
| any_of(EE->users(), | |
| [&](User *U) { | |
| return isa<GetElementPtrInst>(U) && | |
| !R.areAllUsersVectorized(cast<Instruction>(U), | |
| &VectorizedVals); | |
| }) || | |
| (!VEs.empty() && !is_contained(VEs, E))) | |
| continue; | |
| std::optional<unsigned> EEIdx = getExtractIndex(EE); | |
| if (!EEIdx) | |
| continue; | |
| unsigned Idx = *EEIdx; | |
| // Take credit for instruction that will become dead. | |
| if (EE->hasOneUse() || !PrevNodeFound) { | |
| Instruction *Ext = EE->user_back(); | |
| if (isa<SExtInst, ZExtInst>(Ext) && | |
| all_of(Ext->users(), IsaPred<GetElementPtrInst>)) { | |
| // Use getExtractWithExtendCost() to calculate the cost of | |
| // extractelement/ext pair. | |
| Cost -= TTI.getExtractWithExtendCost( | |
| Ext->getOpcode(), Ext->getType(), EE->getVectorOperandType(), | |
| Idx, CostKind); | |
| // Add back the cost of s|zext which is subtracted separately. | |
| Cost += TTI.getCastInstrCost( | |
| Ext->getOpcode(), Ext->getType(), EE->getType(), | |
| TTI::getCastContextHint(Ext), CostKind, Ext); | |
| continue; | |
| } | |
| } | |
| APInt &DemandedElts = | |
| VectorOpsToExtracts | |
| .try_emplace(VecBase, | |
| APInt::getZero(getNumElements(VecBase->getType()))) | |
| .first->getSecond(); | |
| DemandedElts.setBit(Idx); | |
| } | |
| } | |
| for (const auto &[Vec, DemandedElts] : VectorOpsToExtracts) | |
| Cost -= TTI.getScalarizationOverhead(cast<VectorType>(Vec->getType()), | |
| DemandedElts, /*Insert=*/false, | |
| /*Extract=*/true, CostKind); | |
| // Check that gather of extractelements can be represented as just a | |
| // shuffle of a single/two vectors the scalars are extracted from. | |
| // Found the bunch of extractelement instructions that must be gathered | |
| // into a vector and can be represented as a permutation elements in a | |
| // single input vector or of 2 input vectors. | |
| // Done for reused if same extractelements were vectorized already. | |
| if (!PrevNodeFound) | |
| Cost += computeExtractCost(VL, Mask, ShuffleKinds, NumParts); | |
| InVectors.assign(1, E); | |
| CommonMask.assign(Mask.begin(), Mask.end()); | |
| transformMaskAfterShuffle(CommonMask, CommonMask); | |
| SameNodesEstimated = false; | |
| if (NumParts != 1 && UniqueBases.size() != 1) { | |
| UseVecBaseAsInput = true; | |
| VecBase = | |
| Constant::getNullValue(getWidenedType(ScalarTy, CommonMask.size())); | |
| } | |
| return VecBase; | |
| } | |
| /// Checks if the specified entry \p E needs to be delayed because of its | |
| /// dependency nodes. | |
| std::optional<InstructionCost> | |
| needToDelay(const TreeEntry *, | |
| ArrayRef<SmallVector<const TreeEntry *>>) const { | |
| // No need to delay the cost estimation during analysis. | |
| return std::nullopt; | |
| } | |
| /// Reset the builder to handle perfect diamond match. | |
| void resetForSameNode() { | |
| IsFinalized = false; | |
| CommonMask.clear(); | |
| InVectors.clear(); | |
| Cost = 0; | |
| VectorizedVals.clear(); | |
| SameNodesEstimated = true; | |
| } | |
| void add(const TreeEntry &E1, const TreeEntry &E2, ArrayRef<int> Mask) { | |
| BVValues.reset(); | |
| if (&E1 == &E2) { | |
| assert(all_of(Mask, | |
| [&](int Idx) { | |
| return Idx < static_cast<int>(E1.getVectorFactor()); | |
| }) && | |
| "Expected single vector shuffle mask."); | |
| add(E1, Mask); | |
| return; | |
| } | |
| if (InVectors.empty()) { | |
| CommonMask.assign(Mask.begin(), Mask.end()); | |
| InVectors.assign({&E1, &E2}); | |
| return; | |
| } | |
| assert(!CommonMask.empty() && "Expected non-empty common mask."); | |
| auto *MaskVecTy = getWidenedType(ScalarTy, Mask.size()); | |
| unsigned NumParts = R.getNumberOfParts(MaskVecTy, ScalarTy, Mask.size()); | |
| unsigned SliceSize = getPartNumElems(Mask.size(), NumParts); | |
| const auto *It = find_if(Mask, not_equal_to(PoisonMaskElem)); | |
| unsigned Part = std::distance(Mask.begin(), It) / SliceSize; | |
| estimateNodesPermuteCost(E1, &E2, Mask, Part, SliceSize); | |
| } | |
| void add(const TreeEntry &E1, ArrayRef<int> Mask) { | |
| BVValues.reset(); | |
| if (InVectors.empty()) { | |
| CommonMask.assign(Mask.begin(), Mask.end()); | |
| InVectors.assign(1, &E1); | |
| return; | |
| } | |
| assert(!CommonMask.empty() && "Expected non-empty common mask."); | |
| auto *MaskVecTy = getWidenedType(ScalarTy, Mask.size()); | |
| unsigned NumParts = R.getNumberOfParts(MaskVecTy, ScalarTy, Mask.size()); | |
| unsigned SliceSize = getPartNumElems(Mask.size(), NumParts); | |
| const auto *It = find_if(Mask, not_equal_to(PoisonMaskElem)); | |
| unsigned Part = std::distance(Mask.begin(), It) / SliceSize; | |
| estimateNodesPermuteCost(E1, nullptr, Mask, Part, SliceSize); | |
| if (!SameNodesEstimated && InVectors.size() == 1) | |
| InVectors.emplace_back(&E1); | |
| } | |
| /// Adds 2 input vectors and the mask for their shuffling. | |
| void add(Value *V1, Value *V2, ArrayRef<int> Mask) { | |
| // May come only for shuffling of 2 vectors with extractelements, already | |
| // handled in adjustExtracts. | |
| assert(InVectors.size() == 1 && | |
| all_of(enumerate(CommonMask), | |
| [&](auto P) { | |
| if (P.value() == PoisonMaskElem) | |
| return Mask[P.index()] == PoisonMaskElem; | |
| auto *EI = cast<ExtractElementInst>( | |
| cast<const TreeEntry *>(InVectors.front().Vec) | |
| ->getOrdered(P.index())); | |
| return EI->getVectorOperand() == V1 || | |
| EI->getVectorOperand() == V2; | |
| }) && | |
| "Expected extractelement vectors."); | |
| } | |
| /// Adds another one input vector and the mask for the shuffling. | |
| void | |
| add(Value *V1, ArrayRef<int> Mask, bool ForExtracts = false, | |
| TTI::VectorInstrContext ContextHint = TTI::VectorInstrContext::None) { | |
| if (BVValues && !isa<Constant>(V1)) | |
| BVValues.reset(); | |
| if (InVectors.empty()) { | |
| assert(CommonMask.empty() && !ForExtracts && | |
| "Expected empty input mask/vectors."); | |
| CommonMask.assign(Mask.begin(), Mask.end()); | |
| InVectors.assign(1, InVector(V1, ContextHint)); | |
| return; | |
| } | |
| if (ForExtracts) { | |
| // No need to add vectors here, already handled them in adjustExtracts. | |
| assert(InVectors.size() == 1 && | |
| isa<const TreeEntry *>(InVectors[0].Vec) && !CommonMask.empty() && | |
| all_of(enumerate(CommonMask), | |
| [&](auto P) { | |
| Value *Scalar = cast<const TreeEntry *>(InVectors[0].Vec) | |
| ->getOrdered(P.index()); | |
| if (P.value() == PoisonMaskElem) | |
| return P.value() == Mask[P.index()] || | |
| isa<UndefValue>(Scalar); | |
| if (isa<Constant>(V1)) | |
| return true; | |
| auto *EI = cast<ExtractElementInst>(Scalar); | |
| return EI->getVectorOperand() == V1; | |
| }) && | |
| "Expected only tree entry for extractelement vectors."); | |
| return; | |
| } | |
| assert(!InVectors.empty() && !CommonMask.empty() && | |
| "Expected only tree entries from extracts/reused buildvectors."); | |
| unsigned VF = getVF(V1); | |
| if (InVectors.size() == 2) { | |
| Cost += | |
| createShuffle(InVectors.front().Vec, InVectors.back().Vec, CommonMask, | |
| {}, InVectors.front().Ctx, InVectors.back().Ctx); | |
| transformMaskAfterShuffle(CommonMask, CommonMask); | |
| VF = std::max<unsigned>(VF, CommonMask.size()); | |
| } else if (const auto *InTE = | |
| dyn_cast<const TreeEntry *>(InVectors.front().Vec)) { | |
| VF = std::max(VF, InTE->getVectorFactor()); | |
| } else { | |
| VF = std::max(VF, cast<FixedVectorType>( | |
| cast<Value *>(InVectors.front().Vec)->getType()) | |
| ->getNumElements()); | |
| } | |
| InVectors.emplace_back(V1, ContextHint); | |
| for (unsigned Idx = 0, Sz = CommonMask.size(); Idx < Sz; ++Idx) | |
| if (Mask[Idx] != PoisonMaskElem && CommonMask[Idx] == PoisonMaskElem) | |
| CommonMask[Idx] = Mask[Idx] + VF; | |
| } | |
| Value * | |
| gather(ArrayRef<Value *> VL, unsigned MaskVF = 0, Value *Root = nullptr, | |
| TTI::VectorInstrContext ContextHint = TTI::VectorInstrContext::None) { | |
| Cost += getBuildVectorCost(VL, Root, ContextHint); | |
| if (BVValues) { | |
| if (BVValues->empty() && InVectors.empty()) | |
| BVValues->assign(VL.begin(), VL.end()); | |
| else | |
| BVValues.reset(); | |
| } | |
| if (!Root) { | |
| // FIXME: Need to find a way to avoid use of getNullValue here. | |
| SmallVector<Constant *> Vals; | |
| unsigned VF = VL.size(); | |
| if (MaskVF != 0) | |
| VF = std::min(VF, MaskVF); | |
| Type *VLScalarTy = VL.front()->getType(); | |
| for (Value *V : VL.take_front(VF)) { | |
| Type *ScalarTy = VLScalarTy->getScalarType(); | |
| if (isa<PoisonValue>(V)) { | |
| Vals.push_back(PoisonValue::get(ScalarTy)); | |
| continue; | |
| } | |
| if (isa<UndefValue>(V)) { | |
| Vals.push_back(UndefValue::get(ScalarTy)); | |
| continue; | |
| } | |
| Vals.push_back(Constant::getNullValue(ScalarTy)); | |
| } | |
| if (auto *VecTy = dyn_cast<FixedVectorType>(VLScalarTy)) { | |
| assert(R.Opts.slp_revec && "FixedVectorType is not expected."); | |
| // When REVEC is enabled, we need to expand vector types into scalar | |
| // types. | |
| Vals = replicateMask(Vals, VecTy->getNumElements()); | |
| } | |
| return ConstantVector::get(Vals); | |
| } | |
| return ConstantVector::getSplat( | |
| ElementCount::getFixed( | |
| cast<FixedVectorType>(Root->getType())->getNumElements()), | |
| getAllOnesValue(*R.DL, ScalarTy->getScalarType())); | |
| } | |
| /// Estimates the cost of the gather of zero-extended sub-fields of width | |
| /// \p FieldWidth of the same wider integer scalar \p Src, permuted by | |
| /// \p Mask, as a bitcast to the field vector plus a permutation and an | |
| /// extension to the lane type. | |
| InstructionCost createExtractedFieldsVector(Value *Src, unsigned FieldWidth, | |
| ArrayRef<int> Mask, | |
| const TreeEntry &E) { | |
| auto *FieldTy = IntegerType::get(Src->getContext(), FieldWidth); | |
| unsigned SrcWidth = Src->getType()->getIntegerBitWidth(); | |
| assert(SrcWidth % FieldWidth == 0 && | |
| "Expected the field width to divide the source width."); | |
| unsigned NumFields = SrcWidth / FieldWidth; | |
| auto *FieldVecTy = cast<VectorType>(getWidenedType(FieldTy, NumFields)); | |
| TTI::CastContextHint CCH = R.getCastContextHint(E); | |
| InstructionCost Cost = TTI.getCastInstrCost( | |
| Instruction::BitCast, FieldVecTy, Src->getType(), CCH, CostKind); | |
| // The permutation is elided only for the full-length identity mask. | |
| if (Mask.size() != NumFields || | |
| !ShuffleVectorInst::isIdentityMask(Mask, NumFields)) | |
| Cost += getShuffleCost(TTI, TTI::SK_PermuteSingleSrc, FieldVecTy, | |
| CostKind, Mask); | |
| if (!ScalarTy->isIntegerTy(FieldWidth)) | |
| Cost += TTI.getCastInstrCost( | |
| Instruction::ZExt, getWidenedType(ScalarTy, Mask.size()), | |
| getWidenedType(FieldTy, Mask.size()), CCH, CostKind); | |
| // The extraction instructions die once the fields are emitted from the | |
| // source scalar; their scalar cost is credited back for the gathers | |
| // directly feeding the reduction. Instructions vectorized elsewhere are | |
| // skipped: their scalar cost is already a part of the scalar baseline. | |
| if (R.UserIgnoreList && | |
| (!E.UserTreeIndex || E.UserTreeIndex.UserTE->Idx == 0)) | |
| for (Instruction *I : make_isa_range<Instruction>(E.Scalars)) | |
| if (CheckedExtracts.insert(I).second && !R.isVectorized(I) && | |
| R.areAllUsersVectorized(I, &VectorizedVals)) | |
| Cost -= isa<CastInst>(I) | |
| ? TTI.getCastInstrCost(I->getOpcode(), I->getType(), | |
| I->getOperand(0)->getType(), | |
| TTI::getCastContextHint(I), | |
| CostKind, I) | |
| : TTI.getArithmeticInstrCost( | |
| I->getOpcode(), I->getType(), CostKind, | |
| TTI::getOperandInfo(I->getOperand(0)), | |
| TTI::getOperandInfo(I->getOperand(1)), | |
| {I->getOperand(0), I->getOperand(1)}, I); | |
| return Cost; | |
| } | |
| InstructionCost createFreeze(InstructionCost Cost) { return Cost; } | |
| /// Finalize emission of the shuffles. | |
| InstructionCost finalize( | |
| ArrayRef<int> ExtMask, | |
| ArrayRef<std::pair<const TreeEntry *, unsigned>> SubVectors, | |
| ArrayRef<int> SubVectorsMask, unsigned VF = 0, | |
| function_ref<void(Value *&, SmallVectorImpl<int> &, | |
| function_ref<Value *(Value *, Value *, ArrayRef<int>)>)> | |
| Action = {}) { | |
| IsFinalized = true; | |
| if (Action) { | |
| const PointerUnion<Value *, const TreeEntry *> &Vec = | |
| InVectors.front().Vec; | |
| if (InVectors.size() == 2) | |
| Cost += createShuffle(Vec, InVectors.back().Vec, CommonMask, {}, | |
| InVectors.front().Ctx, InVectors.back().Ctx); | |
| else | |
| Cost += | |
| createShuffle(Vec, nullptr, CommonMask, {}, InVectors.front().Ctx); | |
| transformMaskAfterShuffle(CommonMask, CommonMask); | |
| assert(VF > 0 && | |
| "Expected vector length for the final value before action."); | |
| Value *V = cast<Value *>(Vec); | |
| Action(V, CommonMask, [this](Value *V1, Value *V2, ArrayRef<int> Mask) { | |
| Cost += createShuffle(V1, V2, Mask); | |
| return V1; | |
| }); | |
| InVectors.front() = V; | |
| } | |
| if (!SubVectors.empty()) { | |
| const PointerUnion<Value *, const TreeEntry *> &Vec = | |
| InVectors.front().Vec; | |
| if (InVectors.size() == 2) | |
| Cost += createShuffle(Vec, InVectors.back().Vec, CommonMask, {}, | |
| InVectors.front().Ctx, InVectors.back().Ctx); | |
| else | |
| Cost += | |
| createShuffle(Vec, nullptr, CommonMask, {}, InVectors.front().Ctx); | |
| transformMaskAfterShuffle(CommonMask, CommonMask); | |
| // Add subvectors permutation cost. | |
| if (!SubVectorsMask.empty()) { | |
| assert(SubVectorsMask.size() <= CommonMask.size() && | |
| "Expected same size of masks for subvectors and common mask."); | |
| SmallVector<int> SVMask(CommonMask.size(), PoisonMaskElem); | |
| copy(SubVectorsMask, SVMask.begin()); | |
| for (auto [I1, I2] : zip(SVMask, CommonMask)) { | |
| if (I2 != PoisonMaskElem) { | |
| assert(I1 == PoisonMaskElem && "Expected unused subvectors mask"); | |
| I1 = I2 + CommonMask.size(); | |
| } | |
| } | |
| Cost += getShuffleCost( | |
| TTI, TTI::SK_PermuteTwoSrc, | |
| cast<VectorType>(getWidenedType(ScalarTy, CommonMask.size())), | |
| CostKind, SVMask); | |
| } | |
| for (auto [E, Idx] : SubVectors) { | |
| Type *EScalarTy = E->Scalars.front()->getType(); | |
| bool IsSigned = true; | |
| if (auto It = R.MinBWs.find(E); It != R.MinBWs.end()) { | |
| EScalarTy = | |
| IntegerType::get(EScalarTy->getContext(), It->second.first); | |
| IsSigned = It->second.second; | |
| } | |
| if (ScalarTy != EScalarTy) { | |
| unsigned CastOpcode = Instruction::Trunc; | |
| unsigned DstSz = R.DL->getTypeSizeInBits(ScalarTy); | |
| unsigned SrcSz = R.DL->getTypeSizeInBits(EScalarTy); | |
| if (DstSz > SrcSz) | |
| CastOpcode = IsSigned ? Instruction::SExt : Instruction::ZExt; | |
| Cost += TTI.getCastInstrCost( | |
| CastOpcode, getWidenedType(ScalarTy, E->getVectorFactor()), | |
| getWidenedType(EScalarTy, E->getVectorFactor()), | |
| TTI::CastContextHint::Normal, CostKind); | |
| } | |
| Cost += getShuffleCost( | |
| TTI, TTI::SK_InsertSubvector, | |
| cast<VectorType>(getWidenedType(ScalarTy, CommonMask.size())), | |
| CostKind, {}, Idx, | |
| cast<VectorType>(getWidenedType(ScalarTy, E->getVectorFactor()))); | |
| if (!CommonMask.empty()) { | |
| std::iota(std::next(CommonMask.begin(), Idx), | |
| std::next(CommonMask.begin(), Idx + E->getVectorFactor()), | |
| Idx); | |
| } | |
| } | |
| } | |
| if (!ExtMask.empty()) { | |
| if (CommonMask.empty()) { | |
| CommonMask.assign(ExtMask.begin(), ExtMask.end()); | |
| } else { | |
| SmallVector<int> NewMask(ExtMask.size(), PoisonMaskElem); | |
| for (int I = 0, Sz = ExtMask.size(); I < Sz; ++I) { | |
| if (ExtMask[I] == PoisonMaskElem) | |
| continue; | |
| NewMask[I] = CommonMask[ExtMask[I]]; | |
| } | |
| CommonMask.swap(NewMask); | |
| } | |
| } | |
| if (CommonMask.empty()) { | |
| assert(InVectors.size() == 1 && "Expected only one vector with no mask"); | |
| return Cost; | |
| } | |
| ArrayRef<Value *> VL; | |
| if (BVValues) | |
| VL = *BVValues; | |
| if (InVectors.size() == 2) | |
| return Cost + createShuffle(InVectors.front().Vec, InVectors.back().Vec, | |
| CommonMask, VL, InVectors.front().Ctx, | |
| InVectors.back().Ctx); | |
| return Cost + createShuffle(InVectors.front().Vec, nullptr, CommonMask, VL, | |
| InVectors.front().Ctx); | |
| } | |
| ~ShuffleCostEstimator() { | |
| assert((IsFinalized || CommonMask.empty()) && | |
| "Shuffle construction must be finalized."); | |
| } | |
| }; | |
| const BoUpSLP::TreeEntry *BoUpSLP::getOperandEntry(const TreeEntry *E, | |
| unsigned Idx) const { | |
| TreeEntry *Op = OperandsToTreeEntry.at({E, Idx}); | |
| assert(Op->isSame(E->getOperand(Idx)) && "Operands mismatch!"); | |
| return Op; | |
| } | |
| TTI::CastContextHint BoUpSLP::getCastContextHint(const TreeEntry &TE) const { | |
| if (TE.State == TreeEntry::ScatterVectorize || | |
| TE.State == TreeEntry::StridedVectorize) | |
| return TTI::CastContextHint::GatherScatter; | |
| if (TE.State == TreeEntry::CompressVectorize || | |
| TE.State == TreeEntry::BlendedLoadVectorize) | |
| return TTI::CastContextHint::Masked; | |
| if (TE.State == TreeEntry::Vectorize && TE.getOpcode() == Instruction::Load && | |
| !TE.isAltShuffle()) { | |
| if (TE.ReorderIndices.empty()) | |
| return TTI::CastContextHint::Normal; | |
| SmallVector<int> Mask; | |
| inversePermutation(TE.ReorderIndices, Mask); | |
| if (ShuffleVectorInst::isReverseMask(Mask, Mask.size())) | |
| return TTI::CastContextHint::Reversed; | |
| } | |
| // A gather of extracted sub-fields inherits the context of the entry | |
| // vectorizing the common source scalar, or of the source scalar load. The | |
| // scalars erased by the vectorization have no operands to match. | |
| if (TE.isGather() && none_of(make_isa_range<Instruction>(TE.Scalars), | |
| [&](Instruction *I) { return isDeleted(I); })) | |
| if (std::optional<std::tuple<Value *, unsigned, SmallVector<int>>> Fields = | |
| matchGatheredExtractedFields(TE.Scalars, *DL)) { | |
| Value *Src = std::get<0>(*Fields); | |
| if (ArrayRef<TreeEntry *> TEs = getTreeEntries(Src); !TEs.empty()) | |
| return getCastContextHint(*TEs.front()); | |
| if (isa<LoadInst>(Src)) | |
| return TTI::CastContextHint::Normal; | |
| } | |
| return TTI::CastContextHint::None; | |
| } | |
| uint64_t BoUpSLP::getScaleToLoopIterations(const TreeEntry &TE, Value *Scalar, | |
| Instruction *U) { | |
| BasicBlock *Parent = nullptr; | |
| if (U) { | |
| // The extractelement for a PHI-node user is created in the incoming | |
| // block that feeds the matching operand, not in the PHI block itself | |
| // When the PHI is inside a loop that incoming block can belong to a deeper | |
| // loop than the PHI block. Scaling by the PHI block would use | |
| // the outer trip count instead of inner*outer, and because | |
| // ExtractCostCalculated deduplicates by scalar (only the first external | |
| // user fixes the scale) it would also make the cost depend on external-user | |
| // ordering. A PHI outside all loops is a plain loop-exit phi: its live-out | |
| // lanes are normally rebuilt as a vector LCSSA phi in the exit block, which | |
| // hoists the extract out of the loop, so scale = 1 (via U->getParent() | |
| // below) is kept and the adjustment is restricted to in-loop PHIs. | |
| if (auto *PHI = dyn_cast<PHINode>(U); PHI && Scalar) { | |
| if (LI->getLoopFor(PHI->getParent())) { | |
| // Use the deepest incoming block among all slots where Scalar | |
| // appears, to be conservative when the same value appears in | |
| // multiple predecessors. | |
| for (unsigned I : seq<unsigned>(PHI->getNumIncomingValues())) { | |
| if (PHI->getIncomingValue(I) != Scalar) | |
| continue; | |
| BasicBlock *InBB = PHI->getIncomingBlock(I); | |
| if (!Parent || LI->getLoopDepth(InBB) > LI->getLoopDepth(Parent)) | |
| Parent = InBB; | |
| } | |
| } | |
| } | |
| if (!Parent) | |
| Parent = U->getParent(); | |
| } else if (TE.isGather() || TE.State == TreeEntry::SplitVectorize) { | |
| EdgeInfo EI = TE.UserTreeIndex; | |
| while (EI.UserTE) { | |
| if (EI.UserTE->isGather() || | |
| EI.UserTE->State == TreeEntry::SplitVectorize) { | |
| EI = EI.UserTE->UserTreeIndex; | |
| continue; | |
| } | |
| if (EI.UserTE->State == TreeEntry::Vectorize && | |
| EI.UserTE->getOpcode() == Instruction::PHI) { | |
| auto *PH = cast<PHINode>(EI.UserTE->getMainOp()); | |
| Parent = PH->getIncomingBlock(EI.EdgeIdx); | |
| const Loop *PhiL = LI->getLoopFor(PH->getParent()); | |
| const Loop *InL = LI->getLoopFor(Parent); | |
| if (PhiL && InL && !PhiL->contains(InL) && !InL->contains(PhiL)) { | |
| const SCEV *PhiBTC = SE->getBackedgeTakenCount(PhiL); | |
| const SCEV *InBTC = SE->getBackedgeTakenCount(InL); | |
| if (isa<SCEVCouldNotCompute>(PhiBTC) || PhiBTC != InBTC) { | |
| // If all gathered elements are invariant in the phi's nest below | |
| // the common ancestor of the two nests, the gather is a pure | |
| // nest-crossing transfer, paid once per crossing: scale it by | |
| // the common parent nest. | |
| const Loop *FirstVariantL = | |
| findInnermostNonInvariantLoop(PhiL, TE.Scalars); | |
| if (FirstVariantL && FirstVariantL->contains(InL)) | |
| Parent = FirstVariantL->getHeader(); | |
| } | |
| } | |
| } else { | |
| Parent = EI.UserTE->getMainOp()->getParent(); | |
| } | |
| break; | |
| } | |
| if (!Parent) | |
| return 1; | |
| } else { | |
| Parent = TE.getMainOp()->getParent(); | |
| } | |
| const Loop *L = LI->getLoopFor(Parent); | |
| if (!L) | |
| return 1; | |
| // The entry's cost is paid once per execution of the innermost loop in | |
| // which some of its operands are variant. Operands that are invariant in | |
| // all enclosing loops are executed once (LICM will hoist them out). | |
| return getLoopNestScale(findInnermostNonInvariantLoop( | |
| L, Scalar ? ArrayRef(Scalar) : ArrayRef(TE.Scalars))); | |
| } | |
| uint64_t BoUpSLP::getLoopNestScale(const Loop *L) { | |
| if (!L || Opts.slp_cost_loop_trip_count == 0) | |
| return 1; | |
| if (auto It = LoopNestScaleCache.find(L); It != LoopNestScaleCache.end()) | |
| return It->second; | |
| // Collect loops from L outward up to (but not including) the first cached | |
| // ancestor or the function top, then walk back inward multiplying trip | |
| // counts. Use uint64_t to avoid silent overflow on deep/large nests. | |
| SmallVector<const Loop *> Chain; | |
| for (const Loop *Cur = L; Cur; Cur = Cur->getParentLoop()) { | |
| if (LoopNestScaleCache.contains(Cur)) | |
| break; | |
| Chain.push_back(Cur); | |
| } | |
| assert(!Chain.empty() && "Early-return above should have handled cache hit."); | |
| uint64_t Scale = 1; | |
| if (const Loop *Parent = Chain.back()->getParentLoop()) | |
| Scale = LoopNestScaleCache.lookup(Parent); | |
| // Walk from the outermost uncached loop inward, accumulating trip counts. | |
| // Use SaturatingMultiply to clamp at uint64_t max on deep/large nests | |
| // rather than wrapping around. | |
| for (const Loop *Cur : reverse(Chain)) { | |
| uint64_t TC = std::max<uint64_t>( | |
| 1, getLoopTripCount(Cur, *SE, Opts.slp_cost_loop_trip_count)); | |
| Scale = SaturatingMultiply(Scale, TC); | |
| LoopNestScaleCache.try_emplace(Cur, std::max<uint64_t>(1, Scale)); | |
| } | |
| return std::max<uint64_t>(1, Scale); | |
| } | |
| uint64_t BoUpSLP::getGatherNodeEffectiveScale(const TreeEntry &TE, | |
| Instruction *U) { | |
| // Only meaningful for gather/buildvector-like entries; the per-lane | |
| // insertelements that make up such an entry are LICM-hoistable by | |
| // optimizeGatherSequence() when their operand is loop-invariant. | |
| assert((TE.isGather() || TE.State == TreeEntry::SplitVectorize) && | |
| "Expected gather/split tree entry."); | |
| uint64_t BaseScale = getScaleToLoopIterations(TE, nullptr, U); | |
| if (!Opts.slp_per_lane_gather_scale || Opts.slp_cost_loop_trip_count == 0 || | |
| BaseScale <= 1) | |
| return BaseScale; | |
| // Average the per-lane execution scales: for each lane, reuse the same | |
| // scale helper the rest of the cost model uses, but ask it about that | |
| // one lane's value. Lanes that are loop-invariant in the current nest | |
| // collapse to their outer-loop scale (or 1 for fully invariant/constant | |
| // lanes), which matches the LICM hoisting performed by | |
| // optimizeGatherSequence(). Cap per-lane contributions by BaseScale so a | |
| // refinement can never raise the cost above the whole-entry scale. | |
| // Each lane contributes at most BaseScale, so Sum is bounded above by | |
| // N * BaseScale. If BaseScale is near uint64_t max (saturated by | |
| // getLoopNestScale on a deep nest) Sum can still overflow uint64_t, | |
| // which would silently wrap and produce a wrong average. Use | |
| // SaturatingAdd and bail out to BaseScale on overflow: the true average | |
| // is bounded above by BaseScale anyway, so this preserves the | |
| // refinement's invariant that it can never raise cost. | |
| uint64_t Sum = 0; | |
| unsigned N = 0; | |
| bool Overflow = false; | |
| for (Value *V : | |
| make_filter_range(TE.Scalars, [](Value *V) { return !isConstant(V); })) { | |
| ++N; | |
| uint64_t LaneScale = | |
| std::min(getScaleToLoopIterations(TE, V, U), BaseScale); | |
| Sum = SaturatingAdd(Sum, LaneScale, &Overflow); | |
| if (Overflow) | |
| return BaseScale; | |
| } | |
| if (N == 0) | |
| return BaseScale; | |
| // Ceil-divide so we never round the effective scale down below 1. | |
| uint64_t Numerator = SaturatingAdd(Sum, uint64_t(N - 1), &Overflow); | |
| if (Overflow) | |
| return BaseScale; | |
| uint64_t Avg = Numerator / N; | |
| return std::clamp<uint64_t>(Avg, 1, BaseScale); | |
| } | |
| uint64_t BoUpSLP::getEntryEffectiveScale(const TreeEntry &TE, Instruction *U) { | |
| if (TE.isGather() || TE.State == TreeEntry::SplitVectorize) | |
| return getGatherNodeEffectiveScale(TE, U); | |
| return getScaleToLoopIterations(TE); | |
| } | |
| InstructionCost | |
| BoUpSLP::getVectorSpillReloadCost(const TreeEntry *E, Type *ScalarTy, | |
| Type *VecTy, Type *FinalVecTy, | |
| const TTI::TargetCostKind CostKind) const { | |
| InstructionCost SpillsReloads = 0; | |
| // Estimate vector register pressure per target register class: operand | |
| // vectors plus the result. The same vector operand is counted once via | |
| // CountedOpEntries deduplication. PHIs take the max operand pressure across | |
| // incoming slots (only one predecessor is live at a time) plus the result. | |
| // All-constant operand bundles are skipped. | |
| if (!E->hasState() || E->getOpcode() == Instruction::Store || | |
| E->getOpcode() == Instruction::ExtractElement || | |
| E->getOpcode() == Instruction::ExtractValue || | |
| E->getOpcode() == Instruction::Freeze || | |
| (E->getOpcode() == Instruction::Load && | |
| E->State != TreeEntry::ScatterVectorize)) | |
| return SpillsReloads; | |
| const bool IsPHI = | |
| E->State == TreeEntry::Vectorize && E->getOpcode() == Instruction::PHI; | |
| SmallPtrSet<const TreeEntry *, 8> CountedOpEntries; | |
| SmallDenseMap<unsigned, unsigned> PressureByClass; | |
| auto AddPartsToClass = [&](unsigned RegClass, unsigned Parts) { | |
| assert(Parts != 0 && "Expected non-zero number of parts (registers)."); | |
| PressureByClass[RegClass] += Parts; | |
| }; | |
| auto GetEntryVecTy = [&](const TreeEntry *TE) -> std::pair<Type *, Type *> { | |
| Type *ScalarTy = getValueType(TE->Scalars.front(), Opts.slp_revec); | |
| auto BWIt = MinBWs.find(TE); | |
| if (BWIt != MinBWs.end()) { | |
| auto *VTy = dyn_cast<FixedVectorType>(ScalarTy); | |
| ScalarTy = IntegerType::get(F->getContext(), BWIt->second.first); | |
| if (VTy) | |
| ScalarTy = getWidenedType(ScalarTy, VTy->getNumElements()); | |
| } | |
| return std::make_pair(ScalarTy, | |
| getWidenedType(ScalarTy, TE->getVectorFactor())); | |
| }; | |
| if (E->State == TreeEntry::SplitVectorize) { | |
| for (const auto &[Idx, _] : E->CombinedEntriesWithIndices) { | |
| const TreeEntry *OpTE = VectorizableTree[Idx].get(); | |
| if (!CountedOpEntries.insert(OpTE).second) | |
| continue; | |
| auto [ScalarTy, OpVecTy] = GetEntryVecTy(OpTE); | |
| const unsigned Parts = getNumberOfParts(OpVecTy, ScalarTy); | |
| if (Parts == 0) | |
| continue; | |
| const unsigned RC = | |
| TTI->getRegisterClassForType(/*Vector=*/true, OpVecTy); | |
| AddPartsToClass(RC, Parts); | |
| } | |
| } else if (IsPHI) { | |
| // Only one predecessor is live at a time - take the max operand pressure | |
| // across incoming slots. | |
| SmallDenseMap<unsigned, unsigned> MaxOpPressureByClass; | |
| for (unsigned Idx : seq<unsigned>(E->getNumOperands())) { | |
| const TreeEntry *OpTE = getOperandEntry(E, Idx); | |
| auto [ScalarTy, OpVecTy] = GetEntryVecTy(OpTE); | |
| const unsigned Parts = getNumberOfParts(OpVecTy, ScalarTy); | |
| if (Parts == 0) | |
| continue; | |
| const unsigned RC = | |
| TTI->getRegisterClassForType(/*Vector=*/true, OpVecTy); | |
| MaxOpPressureByClass[RC] = std::max(MaxOpPressureByClass[RC], Parts); | |
| } | |
| for (auto [RC, Parts] : MaxOpPressureByClass) | |
| AddPartsToClass(RC, Parts); | |
| } else { | |
| for (unsigned Idx : seq<unsigned>(E->getNumOperands())) { | |
| // InsertElement operand 0 is the vector being inserted into, which is | |
| // built incrementally and does not occupy an extra register. | |
| if ((E->getOpcode() == Instruction::InsertElement || | |
| E->getOpcode() == Instruction::InsertValue) && | |
| Idx == 0) | |
| continue; | |
| ArrayRef<Value *> Ops = E->getOperand(Idx); | |
| if (Ops.empty() || allConstant(Ops) || isSplat(Ops)) | |
| continue; | |
| Value *Op = Ops.front(); | |
| if (!Op) | |
| continue; | |
| const TreeEntry *OpTE = getOperandEntry(E, Idx); | |
| if (!CountedOpEntries.insert(OpTE).second) | |
| continue; | |
| auto *OpVecTy = getWidenedType(Op->getType(), Ops.size()); | |
| const unsigned Parts = getNumberOfParts(OpVecTy, Op->getType()); | |
| if (Parts == 0) | |
| continue; | |
| const unsigned RC = | |
| TTI->getRegisterClassForType(/*Vector=*/true, OpVecTy); | |
| AddPartsToClass(RC, Parts); | |
| } | |
| } | |
| if (E->getOpcode() != Instruction::Load) { | |
| const unsigned ResParts = getNumberOfParts(VecTy, ScalarTy); | |
| if (ResParts != 0) { | |
| const unsigned RC = TTI->getRegisterClassForType(/*Vector=*/true, VecTy); | |
| AddPartsToClass(RC, ResParts); | |
| } | |
| if (VecTy != FinalVecTy) { | |
| const unsigned FinalResParts = getNumberOfParts(FinalVecTy, ScalarTy); | |
| if (FinalResParts != 0) { | |
| const unsigned RC = | |
| TTI->getRegisterClassForType(/*Vector=*/true, FinalVecTy); | |
| AddPartsToClass(RC, FinalResParts); | |
| } | |
| } | |
| } | |
| for (auto [RegClass, UsedRegs] : PressureByClass) { | |
| const unsigned NumAvailRegs = TTI->getNumberOfRegisters(RegClass); | |
| if (NumAvailRegs == 0 || UsedRegs <= NumAvailRegs) | |
| continue; | |
| const unsigned SpillCount = UsedRegs - NumAvailRegs; | |
| InstructionCost SingleRegSpillReload = | |
| TTI->getRegisterClassReloadCost(RegClass, CostKind); | |
| // No need to spill cost only for the root entry (Idx == 0), for reduction | |
| // and non-returning instructions, like void calls. | |
| if (E->Idx > 0 || !UserIgnoreList || !E->Scalars[0]->getType()->isVoidTy()) | |
| SingleRegSpillReload += | |
| TTI->getRegisterClassSpillCost(RegClass, CostKind); | |
| SpillsReloads += SingleRegSpillReload * SpillCount; | |
| } | |
| return SpillsReloads; | |
| } | |
| /// Calculates a VectorInstrContext from the values in \p VL at indices in | |
| /// \p DemandedElts. | |
| static TTI::VectorInstrContext | |
| getVectorInstrContextHint(ArrayRef<Value *> VL, const APInt &DemandedElts) { | |
| TTI::VectorInstrContext VIC = TTI::VectorInstrContext::None; | |
| for (unsigned I : seq(VL.size())) { | |
| if (!DemandedElts[I]) | |
| continue; | |
| Value *V = VL[I]; | |
| if (isa<UndefValue>(V)) | |
| continue; | |
| auto *Inst = dyn_cast<Instruction>(V); | |
| if (!Inst) | |
| return TTI::VectorInstrContext::None; | |
| TTI::VectorInstrContext IVIC = TTI::getVectorInstrContextHint(Inst); | |
| if (VIC == TTI::VectorInstrContext::None) { | |
| VIC = IVIC; | |
| continue; | |
| } | |
| if (VIC != IVIC) | |
| return TTI::VectorInstrContext::None; | |
| } | |
| return VIC; | |
| } | |
| InstructionCost | |
| BoUpSLP::getEntryCost(const TreeEntry *E, ArrayRef<Value *> VectorizedVals, | |
| SmallPtrSetImpl<Value *> &CheckedExtracts) { | |
| ArrayRef<Value *> VL = E->Scalars; | |
| Type *ScalarTy = getValueType(VL[0], Opts.slp_revec); | |
| if (Opts.slp_revec && E->State == TreeEntry::Vectorize && | |
| E->getOpcode() == Instruction::InsertElement && | |
| !E->getOperand(1).back()->getType()->isVectorTy()) | |
| ScalarTy = ScalarTy->getScalarType(); | |
| if (!isValidElementType(ScalarTy, Opts.slp_revec)) | |
| return InstructionCost::getInvalid(); | |
| // If we have computed a smaller type for the expression, update VecTy so | |
| // that the costs will be accurate. | |
| auto It = MinBWs.find(E); | |
| Type *OrigScalarTy = ScalarTy; | |
| if (It != MinBWs.end()) { | |
| auto *VecTy = dyn_cast<FixedVectorType>(ScalarTy); | |
| ScalarTy = IntegerType::get(F->getContext(), It->second.first); | |
| if (VecTy) | |
| ScalarTy = getWidenedType(ScalarTy, VecTy->getNumElements()); | |
| } else if (E->Idx == 0 && isReducedBitcastRoot() && | |
| getRootNode().CombinedOp != TreeEntry::ReducedBitPack) { | |
| const TreeEntry *ZExt = getOperandEntry(E, /*Idx=*/0); | |
| ScalarTy = cast<CastInst>(ZExt->getMainOp())->getSrcTy(); | |
| } | |
| auto *VecTy = getWidenedType(ScalarTy, VL.size()); | |
| unsigned EntryVF = E->getVectorFactor(); | |
| auto *FinalVecTy = getWidenedType(ScalarTy, EntryVF); | |
| const InstructionCost SpillsReloads = | |
| getVectorSpillReloadCost(E, ScalarTy, VecTy, FinalVecTy, CostKind); | |
| if (E->isGather() || TransformedToGatherNodes.contains(E)) { | |
| if (allConstant(VL)) | |
| return 0; | |
| if (isa<InsertElementInst, InsertValueInst>(VL[0])) | |
| return InstructionCost::getInvalid(); | |
| // The fmuls of the node turned into a gather stay scalar and lose the | |
| // fusion with their vectorized fadd/fsub users. | |
| return SpillsReloads + | |
| processBuildVector<ShuffleCostEstimator, InstructionCost>( | |
| E, ScalarTy, *TTI, VectorizedVals, *this, CheckedExtracts) + | |
| getUnfusedFMulsPenalty(*E); | |
| } | |
| if (E->State == TreeEntry::SplitVectorize) { | |
| assert(E->CombinedEntriesWithIndices.size() == 2 && | |
| "Expected exactly 2 combined entries."); | |
| assert(E->ReuseShuffleIndices.empty() && "Expected empty reuses mask."); | |
| InstructionCost VectorCost = 0; | |
| if (E->ReorderIndices.empty()) { | |
| VectorCost = getShuffleCost( | |
| *TTI, TTI::SK_InsertSubvector, cast<VectorType>(FinalVecTy), CostKind, | |
| {}, E->CombinedEntriesWithIndices.back().second, | |
| cast<VectorType>(getWidenedType( | |
| ScalarTy, | |
| VectorizableTree[E->CombinedEntriesWithIndices.back().first] | |
| ->getVectorFactor()))); | |
| } else { | |
| unsigned CommonVF = | |
| std::max(VectorizableTree[E->CombinedEntriesWithIndices.front().first] | |
| ->getVectorFactor(), | |
| VectorizableTree[E->CombinedEntriesWithIndices.back().first] | |
| ->getVectorFactor()); | |
| VectorCost = | |
| getShuffleCost(*TTI, TTI::SK_PermuteTwoSrc, | |
| cast<VectorType>(getWidenedType(ScalarTy, CommonVF)), | |
| CostKind, E->getSplitMask()); | |
| } | |
| VectorCost += SpillsReloads; | |
| LLVM_DEBUG(dumpTreeCosts(E, 0, VectorCost, 0, "Calculated costs for Tree")); | |
| return VectorCost; | |
| } | |
| InstructionCost CommonCost = 0; | |
| SmallVector<int> Mask; | |
| if (!E->ReorderIndices.empty() && E->State != TreeEntry::CompressVectorize && | |
| (E->State != TreeEntry::StridedVectorize || !E->isReverse() || | |
| isWidenedStridedLoad(E))) { | |
| SmallVector<int> NewMask; | |
| if (E->getOpcode() == Instruction::Store) { | |
| // For stores the order is actually a mask. | |
| NewMask.resize(E->ReorderIndices.size()); | |
| copy(E->ReorderIndices, NewMask.begin()); | |
| } else { | |
| inversePermutation(E->ReorderIndices, NewMask); | |
| } | |
| addMask(Mask, NewMask); | |
| } | |
| if (!E->ReuseShuffleIndices.empty()) | |
| addMask(Mask, E->ReuseShuffleIndices); | |
| if (!Mask.empty() && !ShuffleVectorInst::isIdentityMask(Mask, Mask.size())) { | |
| assert(!isa<StructType>(FinalVecTy) && | |
| "Expected non-struct vector type for shuffle cost calculation."); | |
| CommonCost = getShuffleCost(*TTI, TTI::SK_PermuteSingleSrc, | |
| cast<VectorType>(FinalVecTy), CostKind, Mask, | |
| /*Index=*/0, cast<VectorType>(VecTy)); | |
| } | |
| assert((E->State == TreeEntry::Vectorize || | |
| E->State == TreeEntry::ScatterVectorize || | |
| E->State == TreeEntry::StridedVectorize || | |
| E->State == TreeEntry::ExpandVectorize || | |
| E->State == TreeEntry::CompressVectorize || | |
| E->State == TreeEntry::BlendedLoadVectorize) && | |
| "Unhandled state"); | |
| assert(E->getOpcode() && | |
| ((allSameType(VL) && allSameBlock(VL)) || | |
| (E->getOpcode() == Instruction::GetElementPtr && | |
| E->getMainOp()->getType()->isPointerTy()) || | |
| E->hasCopyableElements()) && | |
| "Invalid VL"); | |
| Instruction *VL0 = E->getMainOp(); | |
| unsigned ShuffleOrOp = | |
| E->isAltShuffle() ? (unsigned)Instruction::ShuffleVector : E->getOpcode(); | |
| if (E->CombinedOp != TreeEntry::NotCombinedOp) | |
| ShuffleOrOp = E->CombinedOp; | |
| SmallSetVector<Value *, 16> UniqueValues; | |
| SmallVector<unsigned, 16> UniqueIndexes; | |
| for (auto [Idx, V] : enumerate(VL)) | |
| if (UniqueValues.insert(V)) | |
| UniqueIndexes.push_back(Idx); | |
| const unsigned Sz = UniqueValues.size(); | |
| SmallBitVector UsedScalars(Sz, false); | |
| for (unsigned I = 0; I < Sz; ++I) { | |
| if (isa<Instruction>(UniqueValues[I]) && | |
| !E->isCopyableElement(UniqueValues[I]) && | |
| getTreeEntries(UniqueValues[I]).front() == E) | |
| continue; | |
| UsedScalars.set(I); | |
| } | |
| auto GetCastContextHint = [&](Value *V) { | |
| if (ArrayRef<TreeEntry *> OpTEs = getTreeEntries(V); OpTEs.size() == 1) | |
| return getCastContextHint(*OpTEs.front()); | |
| InstructionsState SrcState = getSameOpcode(E->getOperand(0), *TLI); | |
| if (SrcState && SrcState.getOpcode() == Instruction::Load && | |
| !SrcState.isAltShuffle()) | |
| return TTI::CastContextHint::GatherScatter; | |
| return TTI::CastContextHint::None; | |
| }; | |
| auto GetCostDiff = | |
| [=](function_ref<InstructionCost(unsigned)> ScalarEltCost, | |
| function_ref<InstructionCost(InstructionCost)> VectorCost) { | |
| // Calculate the cost of this instruction. | |
| InstructionCost ScalarCost = 0; | |
| if (isa<CastInst, CallInst>(VL0)) { | |
| // For some of the instructions no need to calculate cost for each | |
| // particular instruction, we can use the cost of the single | |
| // instruction x total number of scalar instructions. | |
| ScalarCost = (Sz - UsedScalars.count()) * ScalarEltCost(0); | |
| } else { | |
| for (unsigned I = 0; I < Sz; ++I) { | |
| if (UsedScalars.test(I)) | |
| continue; | |
| ScalarCost += ScalarEltCost(I); | |
| } | |
| } | |
| InstructionCost VecCost = VectorCost(CommonCost); | |
| // Check if the current node must be resized, if the parent node is not | |
| // resized. Nodes without a parent (roots of the splat gather | |
| // subtrees) are resized back to their original type when the reusing | |
| // gather nodes are emitted. | |
| if (It != MinBWs.end() && !UnaryInstruction::isCast(E->getOpcode()) && | |
| E->Idx != 0 && | |
| (E->getOpcode() != Instruction::Load || E->UserTreeIndex)) { | |
| const EdgeInfo &EI = E->UserTreeIndex; | |
| if (!EI.UserTE || !EI.UserTE->hasState() || | |
| EI.UserTE->getOpcode() != Instruction::Select || | |
| EI.EdgeIdx != 0) { | |
| auto UserBWIt = MinBWs.find(EI.UserTE); | |
| Type *UserScalarTy = | |
| !EI.UserTE ? OrigScalarTy | |
| : (EI.UserTE->isGather() || | |
| EI.UserTE->State == TreeEntry::SplitVectorize) | |
| ? EI.UserTE->Scalars.front()->getType() | |
| : EI.UserTE->getOperand(EI.EdgeIdx).front()->getType(); | |
| if (UserBWIt != MinBWs.end()) | |
| UserScalarTy = IntegerType::get(ScalarTy->getContext(), | |
| UserBWIt->second.first); | |
| if (ScalarTy != UserScalarTy) { | |
| unsigned BWSz = DL->getTypeSizeInBits(ScalarTy); | |
| unsigned SrcBWSz = DL->getTypeSizeInBits(UserScalarTy); | |
| unsigned VecOpcode; | |
| auto *UserVecTy = getWidenedType(UserScalarTy, E->Scalars.size()); | |
| if (BWSz > SrcBWSz) | |
| VecOpcode = Instruction::Trunc; | |
| else | |
| VecOpcode = | |
| It->second.second ? Instruction::SExt : Instruction::ZExt; | |
| TTI::CastContextHint CCH = GetCastContextHint(VL0); | |
| VecCost += TTI->getCastInstrCost(VecOpcode, UserVecTy, VecTy, CCH, | |
| CostKind); | |
| } | |
| } | |
| } | |
| VecCost += SpillsReloads; | |
| LLVM_DEBUG(dumpTreeCosts(E, CommonCost, VecCost - CommonCost, | |
| ScalarCost, "Calculated costs for Tree")); | |
| return VecCost - ScalarCost; | |
| }; | |
| // Price peeled intermediate instructions on the scalar side: they are | |
| // erased when the node vectorizes. The peeled cost is folded into the | |
| // first scalar-cost query so the cost dump reports the full scalar cost. | |
| // Peeled chain links are always 2-operand associative binops, priced per | |
| // instruction so the operand properties (constants, uniformity) apply. | |
| auto GetCostDiffWithPeeled = | |
| [&](function_ref<InstructionCost(unsigned)> ScalarEltCost, | |
| function_ref<InstructionCost(InstructionCost)> VectorCost) { | |
| InstructionCost PeeledScalarCost = 0; | |
| for (Value *V : E->getReassocScalars()) { | |
| auto *I = cast<Instruction>(V); | |
| TTI::OperandValueInfo Op1Info = TTI::getOperandInfo(I->getOperand(0)); | |
| TTI::OperandValueInfo Op2Info = TTI::getOperandInfo(I->getOperand(1)); | |
| PeeledScalarCost += TTI->getArithmeticInstrCost( | |
| I->getOpcode(), OrigScalarTy, CostKind, Op1Info, Op2Info, {}, I); | |
| } | |
| bool PeeledCostAdded = false; | |
| InstructionCost CostDiff = GetCostDiff( | |
| [&](unsigned Idx) { | |
| InstructionCost Cost = ScalarEltCost(Idx); | |
| if (!PeeledCostAdded) { | |
| PeeledCostAdded = true; | |
| Cost += PeeledScalarCost; | |
| } | |
| return Cost; | |
| }, | |
| VectorCost); | |
| // Every scalar may be marked as used elsewhere, leaving the | |
| // scalar-cost query uncalled and the peeled cost unapplied. | |
| if (!PeeledCostAdded) | |
| CostDiff -= PeeledScalarCost; | |
| return CostDiff; | |
| }; | |
| // Calculate cost difference from vectorizing set of GEPs. | |
| // Negative value means vectorizing is profitable. | |
| auto GetGEPCostDiff = [=](ArrayRef<Value *> Ptrs, Value *BasePtr) { | |
| assert((E->State == TreeEntry::Vectorize || | |
| E->State == TreeEntry::StridedVectorize || | |
| E->State == TreeEntry::ExpandVectorize || | |
| E->State == TreeEntry::CompressVectorize) && | |
| "Entry state expected to be Vectorize, StridedVectorize, " | |
| "ExpandVectorize or CompressVectorize here."); | |
| InstructionCost ScalarCost = 0; | |
| InstructionCost VecCost = 0; | |
| std::tie(ScalarCost, VecCost) = | |
| getGEPCosts(*TTI, Ptrs, BasePtr, E->getOpcode(), CostKind, OrigScalarTy, | |
| cast<VectorType>(VecTy)); | |
| LLVM_DEBUG(dumpTreeCosts(E, 0, VecCost, ScalarCost, | |
| "Calculated GEPs cost for Tree")); | |
| return VecCost - ScalarCost + SpillsReloads; | |
| }; | |
| auto GetMinMaxCost = [&](Type *Ty, Instruction *VI = nullptr) { | |
| auto [MinMaxID, SelectOnly] = canConvertToMinOrMaxIntrinsic(VI ? VI : VL); | |
| if (MinMaxID == Intrinsic::not_intrinsic) | |
| return InstructionCost::getInvalid(); | |
| Type *CanonicalType = Ty; | |
| if (CanonicalType->isPtrOrPtrVectorTy()) | |
| CanonicalType = CanonicalType->getWithNewType(IntegerType::get( | |
| CanonicalType->getContext(), | |
| DL->getTypeSizeInBits(CanonicalType->getScalarType()))); | |
| IntrinsicCostAttributes CostAttrs(MinMaxID, CanonicalType, | |
| {CanonicalType, CanonicalType}); | |
| InstructionCost IntrinsicCost = | |
| TTI->getIntrinsicInstrCost(CostAttrs, CostKind); | |
| // If the selects are the only uses of the compares, they will be | |
| // dead and we can adjust the cost by removing their cost. | |
| if (VI && SelectOnly) { | |
| assert((!Ty->isVectorTy() || Opts.slp_revec) && | |
| "Expected only for scalar type."); | |
| auto *CI = cast<CmpInst>(VI->getOperand(0)); | |
| IntrinsicCost -= TTI->getCmpSelInstrCost( | |
| CI->getOpcode(), Ty, Builder.getInt1Ty(), CI->getPredicate(), | |
| CostKind, TTI::getOperandInfo(CI->getOperand(0)), | |
| TTI::getOperandInfo(CI->getOperand(1)), CI); | |
| } | |
| return IntrinsicCost; | |
| }; | |
| // The fadd/fsub, fused with its fmul operand into an fmuladd in the scalar | |
| // code. If the fmul is not vectorized, the vector code still emits it, so | |
| // only the fusion delta is attributed to the fadd/fsub. | |
| auto GetFMulAddCost = [&, &TTI = *TTI](const InstructionsState &S, | |
| Instruction *VI) { | |
| InstructionCost Cost = canConvertToFMA(VI, S, *DT, *DL, TTI, *TLI, *this); | |
| Instruction *FMul = getFusableFMulOperand(VI, TTI); | |
| if (!Cost.isValid() || !FMul || isVectorized(FMul)) | |
| return Cost; | |
| InstructionCost FMulCost = getUnfusedBinOpCost(FMul, TTI, *TLI, CostKind); | |
| return Cost > FMulCost ? Cost - FMulCost : InstructionCost(TTI::TCC_Free); | |
| }; | |
| // The fmul, fused into its fadd/fsub user, is free if the user lane is | |
| // priced as fmuladd, otherwise the user stays scalar and only the fusion | |
| // delta is attributed to the fmul. | |
| auto GetFusedFMulCost = [&, &TTI = *TTI](Instruction *VI) { | |
| auto [U, FMACost] = getFMulFusingUser(VI); | |
| if (!U) | |
| return InstructionCost::getInvalid(); | |
| if (isFusedVectorizedFAddSub(U)) | |
| return InstructionCost(TTI::TCC_Free); | |
| InstructionCost FAddCost = getUnfusedBinOpCost(U, TTI, *TLI, CostKind); | |
| return FMACost > FAddCost ? FMACost - FAddCost | |
| : InstructionCost(TTI::TCC_Free); | |
| }; | |
| switch (ShuffleOrOp) { | |
| case Instruction::PHI: { | |
| // Count reused scalars. | |
| InstructionCost ScalarCost = 0; | |
| SmallPtrSet<const TreeEntry *, 4> CountedOps; | |
| for (PHINode *PHI : make_isa_range<PHINode>(UniqueValues)) { | |
| ValueList Operands(PHI->getNumIncomingValues(), nullptr); | |
| for (unsigned I = 0, N = PHI->getNumIncomingValues(); I < N; ++I) { | |
| Value *Op = PHI->getIncomingValue(I); | |
| Operands[I] = Op; | |
| } | |
| if (const TreeEntry *OpTE = | |
| getSameValuesTreeEntry(Operands.front(), Operands)) | |
| if (CountedOps.insert(OpTE).second && | |
| !OpTE->ReuseShuffleIndices.empty()) | |
| ScalarCost += TTI::TCC_Basic * (OpTE->ReuseShuffleIndices.size() - | |
| OpTE->Scalars.size()); | |
| } | |
| return CommonCost - ScalarCost + SpillsReloads; | |
| } | |
| case Instruction::ExtractValue: | |
| case Instruction::ExtractElement: { | |
| // For ExtractValue entries vectorized via the struct-call path the scalar | |
| // extractvalue instructions are free (they become extractvalue from a | |
| // struct-of-vectors, which is also free). The cost is fully captured by | |
| // the underlying Call entry and the external-use extraction costs. | |
| if (ShuffleOrOp == Instruction::ExtractValue && !E->StructEVIndices.empty()) | |
| return CommonCost; | |
| APInt DemandedElts; | |
| APInt CopyableInsertElts; | |
| VectorType *SrcVecTy = nullptr; | |
| auto GetScalarCost = [&](unsigned Idx) { | |
| if (isa<PoisonValue>(UniqueValues[Idx])) | |
| return InstructionCost(TTI::TCC_Free); | |
| // Copyable lanes are not extracts; they are inserted into the reused | |
| // source vector, so charge the insert to the vector side only. | |
| if (E->isCopyableElement(UniqueValues[Idx])) { | |
| if (CopyableInsertElts.isZero()) | |
| CopyableInsertElts = APInt::getZero(E->getVectorFactor()); | |
| CopyableInsertElts.setBit(Idx); | |
| return InstructionCost(TTI::TCC_Free); | |
| } | |
| auto *I = cast<Instruction>(UniqueValues[Idx]); | |
| // Undef/poison extract indices form holes in the reused source vector, | |
| // such lanes are free. | |
| std::optional<unsigned> ExtIdx = getExtractIndex(I); | |
| if (!ExtIdx) | |
| return InstructionCost(TTI::TCC_Free); | |
| if (!SrcVecTy) { | |
| if (ShuffleOrOp == Instruction::ExtractElement) { | |
| auto *EE = cast<ExtractElementInst>(I); | |
| SrcVecTy = EE->getVectorOperandType(); | |
| } else { | |
| auto *EV = cast<ExtractValueInst>(I); | |
| Type *AggregateTy = EV->getAggregateOperand()->getType(); | |
| unsigned NumElts; | |
| if (auto *ATy = dyn_cast<ArrayType>(AggregateTy)) | |
| NumElts = ATy->getNumElements(); | |
| else | |
| NumElts = AggregateTy->getStructNumElements(); | |
| SrcVecTy = cast<VectorType>(getWidenedType(OrigScalarTy, NumElts)); | |
| } | |
| } | |
| if (I->hasOneUse()) { | |
| Instruction *Ext = I->user_back(); | |
| if ((isa<SExtInst>(Ext) || isa<ZExtInst>(Ext)) && | |
| all_of(Ext->users(), IsaPred<GetElementPtrInst>)) { | |
| // Use getExtractWithExtendCost() to calculate the cost of | |
| // extractelement/ext pair. | |
| InstructionCost Cost = TTI->getExtractWithExtendCost( | |
| Ext->getOpcode(), Ext->getType(), SrcVecTy, *ExtIdx, CostKind); | |
| // Subtract the cost of s|zext which is subtracted separately. | |
| Cost -= TTI->getCastInstrCost( | |
| Ext->getOpcode(), Ext->getType(), I->getType(), | |
| TTI::getCastContextHint(Ext), CostKind, Ext); | |
| return Cost; | |
| } | |
| } | |
| // The extract, which the target folds into its users (e.g. the lane | |
| // operand of a by-element fmul on AArch64), costs nothing in the scalar | |
| // code; its removal saves nothing. | |
| if (ShuffleOrOp == Instruction::ExtractElement && | |
| TTI->getVectorInstrCost(*I, SrcVecTy, CostKind, *ExtIdx, | |
| TTI::getVectorInstrContextHint(I)) == 0) | |
| return InstructionCost(TTI::TCC_Free); | |
| if (DemandedElts.isZero()) | |
| DemandedElts = APInt::getZero(getNumElements(SrcVecTy)); | |
| DemandedElts.setBit(*ExtIdx); | |
| return InstructionCost(TTI::TCC_Free); | |
| }; | |
| auto GetVectorCost = [&, &TTI = *TTI](InstructionCost CommonCost) { | |
| return CommonCost + | |
| (CopyableInsertElts.isZero() | |
| ? TTI::TCC_Free | |
| : TTI.getScalarizationOverhead( | |
| cast<VectorType>( | |
| getWidenedType(OrigScalarTy, E->getVectorFactor())), | |
| CopyableInsertElts, /*Insert=*/true, | |
| /*Extract=*/false, CostKind)) - | |
| (DemandedElts.isZero() | |
| ? TTI::TCC_Free | |
| : TTI.getScalarizationOverhead(SrcVecTy, DemandedElts, | |
| /*Insert=*/false, | |
| /*Extract=*/true, CostKind)); | |
| }; | |
| return GetCostDiff(GetScalarCost, GetVectorCost); | |
| } | |
| case Instruction::InsertValue: | |
| case Instruction::InsertElement: { | |
| assert(E->ReuseShuffleIndices.empty() && | |
| "Unique insertelements only are expected."); | |
| FixedVectorType *SrcVecTy = getInsertBuildVectorSrcTy(E); | |
| unsigned const NumElts = getNumElements(SrcVecTy); | |
| unsigned const NumScalars = VL.size(); | |
| unsigned NumOfParts = | |
| getNumberOfParts(SrcVecTy, VL0->getOperand(1)->getType()); | |
| SmallVector<int> InsertMask(NumElts, PoisonMaskElem); | |
| unsigned OffsetBeg = *getElementIndex(VL.front()); | |
| unsigned OffsetEnd = OffsetBeg; | |
| InsertMask[OffsetBeg] = 0; | |
| for (auto [I, V] : enumerate(VL.drop_front())) { | |
| unsigned Idx = *getElementIndex(V); | |
| if (OffsetBeg > Idx) | |
| OffsetBeg = Idx; | |
| else if (OffsetEnd < Idx) | |
| OffsetEnd = Idx; | |
| InsertMask[Idx] = I + 1; | |
| } | |
| unsigned VecScalarsSz = PowerOf2Ceil(NumElts); | |
| if (NumOfParts > 0 && NumOfParts < NumElts) | |
| VecScalarsSz = PowerOf2Ceil((NumElts + NumOfParts - 1) / NumOfParts); | |
| unsigned VecSz = (1 + OffsetEnd / VecScalarsSz - OffsetBeg / VecScalarsSz) * | |
| VecScalarsSz; | |
| unsigned Offset = VecScalarsSz * (OffsetBeg / VecScalarsSz); | |
| unsigned InsertVecSz = std::min<unsigned>( | |
| PowerOf2Ceil(OffsetEnd - OffsetBeg + 1), | |
| ((OffsetEnd - OffsetBeg + VecScalarsSz) / VecScalarsSz) * VecScalarsSz); | |
| bool IsWholeSubvector = | |
| OffsetBeg == Offset && ((OffsetEnd + 1) % VecScalarsSz == 0); | |
| // Check if we can safely insert a subvector. If it is not possible, just | |
| // generate a whole-sized vector and shuffle the source vector and the new | |
| // subvector. | |
| if (OffsetBeg + InsertVecSz > VecSz) { | |
| // Align OffsetBeg to generate correct mask. | |
| OffsetBeg = alignDown(OffsetBeg, VecSz, Offset); | |
| InsertVecSz = VecSz; | |
| } | |
| APInt DemandedElts = APInt::getZero(NumElts); | |
| // TODO: Add support for Instruction::InsertValue. | |
| SmallVector<int> Mask; | |
| if (!E->ReorderIndices.empty()) { | |
| inversePermutation(E->ReorderIndices, Mask); | |
| Mask.append(InsertVecSz - Mask.size(), PoisonMaskElem); | |
| } else { | |
| Mask.assign(VecSz, PoisonMaskElem); | |
| std::iota(Mask.begin(), std::next(Mask.begin(), InsertVecSz), 0); | |
| } | |
| bool IsIdentity = true; | |
| SmallVector<int> PrevMask(InsertVecSz, PoisonMaskElem); | |
| SmallVector<Value *> AdjustedVL(NumElts, PoisonValue::get(ScalarTy)); | |
| Mask.swap(PrevMask); | |
| for (unsigned I = 0; I < NumScalars; ++I) { | |
| unsigned InsertIdx = *getElementIndex(VL[PrevMask[I]]); | |
| DemandedElts.setBit(InsertIdx); | |
| AdjustedVL[InsertIdx] = VL[PrevMask[I]]; | |
| IsIdentity &= InsertIdx - OffsetBeg == I; | |
| Mask[InsertIdx - OffsetBeg] = I; | |
| } | |
| assert(Offset < NumElts && "Failed to find vector index offset"); | |
| InstructionCost Cost = 0; | |
| // First cost - resize to actual vector size if not identity shuffle or | |
| // need to shift the vector. | |
| // Do not calculate the cost if the actual size is the register size and | |
| // we can merge this shuffle with the following SK_Select. | |
| auto *InsertVecTy = cast<VectorType>(getWidenedType(ScalarTy, InsertVecSz)); | |
| if (!IsIdentity) | |
| Cost += getShuffleCost(*TTI, TargetTransformInfo::SK_PermuteSingleSrc, | |
| InsertVecTy, CostKind, Mask); | |
| auto *FirstInsert = cast<Instruction>(*find_if(E->Scalars, [E](Value *V) { | |
| return !is_contained(E->Scalars, cast<Instruction>(V)->getOperand(0)); | |
| })); | |
| // Second cost - permutation with subvector, if some elements are from the | |
| // initial vector or inserting a subvector. | |
| // TODO: Implement the analysis of the FirstInsert->getOperand(0) | |
| // subvector of ActualVecTy. | |
| SmallBitVector InMask = | |
| isUndefVector(FirstInsert->getOperand(0), | |
| buildUseMask(NumElts, InsertMask, UseMask::UndefsAsMask)); | |
| Cost -= getScalarizationOverhead( | |
| *TTI, Opts.slp_revec, ScalarTy, SrcVecTy, DemandedElts, | |
| /*Insert=*/true, /*Extract=*/false, CostKind, InMask.all(), AdjustedVL, | |
| getVectorInstrContextHint(AdjustedVL, DemandedElts)); | |
| if (!InMask.all() && NumScalars != NumElts && !IsWholeSubvector) { | |
| if (InsertVecSz != VecSz) { | |
| auto *ActualVecTy = cast<VectorType>(getWidenedType(ScalarTy, VecSz)); | |
| Cost += getShuffleCost(*TTI, TTI::SK_InsertSubvector, ActualVecTy, | |
| CostKind, {}, OffsetBeg - Offset, InsertVecTy); | |
| } else { | |
| for (unsigned I = 0, End = OffsetBeg - Offset; I < End; ++I) | |
| Mask[I] = InMask.test(I) ? PoisonMaskElem : I; | |
| for (unsigned I = OffsetBeg - Offset, End = OffsetEnd - Offset; | |
| I <= End; ++I) | |
| if (Mask[I] != PoisonMaskElem) | |
| Mask[I] = I + VecSz; | |
| for (unsigned I = OffsetEnd + 1 - Offset; I < VecSz; ++I) | |
| Mask[I] = | |
| ((I >= InMask.size()) || InMask.test(I)) ? PoisonMaskElem : I; | |
| Cost += getShuffleCost(*TTI, TTI::SK_PermuteTwoSrc, InsertVecTy, | |
| CostKind, Mask); | |
| } | |
| } | |
| if (ShuffleOrOp == Instruction::InsertValue && | |
| !canVectorStoreInsertValue(E)) { | |
| // Match the stack slot alignment used during codegen (see the store + | |
| // load roundtrip in vectorizeTree()): the slot holds both the source | |
| // vector and the original aggregate. | |
| Align VecAlign = std::max(DL->getPrefTypeAlign(SrcVecTy), | |
| DL->getPrefTypeAlign(VL0->getType())); | |
| // The stack slot roundtrip is synthesized by codegen; there is no IR | |
| // value or pointer to take operand info from. | |
| Cost += TTI->getMemoryOpCost(Instruction::Store, SrcVecTy, VecAlign, | |
| /*AddressSpace=*/0, CostKind) + | |
| TTI->getMemoryOpCost(Instruction::Load, VL0->getType(), VecAlign, | |
| /*AddressSpace=*/0, CostKind); | |
| } | |
| return Cost + SpillsReloads; | |
| } | |
| case Instruction::ZExt: | |
| case Instruction::SExt: | |
| case Instruction::FPToUI: | |
| case Instruction::FPToSI: | |
| case Instruction::FPExt: | |
| case Instruction::PtrToInt: | |
| case Instruction::PtrToAddr: | |
| case Instruction::IntToPtr: | |
| case Instruction::SIToFP: | |
| case Instruction::UIToFP: | |
| case Instruction::Trunc: | |
| case Instruction::FPTrunc: | |
| case Instruction::BitCast: { | |
| auto SrcIt = MinBWs.find(getOperandEntry(E, 0)); | |
| Type *SrcScalarTy = VL0->getOperand(0)->getType(); | |
| auto *SrcVecTy = getWidenedType(SrcScalarTy, VL.size()); | |
| unsigned Opcode = ShuffleOrOp; | |
| unsigned VecOpcode = Opcode; | |
| if (!ScalarTy->isFPOrFPVectorTy() && !SrcScalarTy->isFPOrFPVectorTy() && | |
| (SrcIt != MinBWs.end() || It != MinBWs.end())) { | |
| // Check if the values are candidates to demote. | |
| unsigned SrcBWSz = DL->getTypeSizeInBits(SrcScalarTy->getScalarType()); | |
| if (SrcIt != MinBWs.end()) { | |
| SrcBWSz = SrcIt->second.first; | |
| unsigned SrcScalarTyNumElements = getNumElements(SrcScalarTy); | |
| SrcScalarTy = IntegerType::get(F->getContext(), SrcBWSz); | |
| SrcVecTy = | |
| getWidenedType(SrcScalarTy, VL.size() * SrcScalarTyNumElements); | |
| } | |
| unsigned BWSz = DL->getTypeSizeInBits(ScalarTy->getScalarType()); | |
| if (BWSz == SrcBWSz) { | |
| VecOpcode = Instruction::BitCast; | |
| } else if (BWSz < SrcBWSz) { | |
| VecOpcode = Instruction::Trunc; | |
| } else if (It != MinBWs.end()) { | |
| assert(BWSz > SrcBWSz && "Invalid cast!"); | |
| VecOpcode = It->second.second ? Instruction::SExt : Instruction::ZExt; | |
| } else if (SrcIt != MinBWs.end()) { | |
| assert(BWSz > SrcBWSz && "Invalid cast!"); | |
| VecOpcode = | |
| SrcIt->second.second ? Instruction::SExt : Instruction::ZExt; | |
| } | |
| } else if (VecOpcode == Instruction::SIToFP && SrcIt != MinBWs.end() && | |
| !SrcIt->second.second) { | |
| VecOpcode = Instruction::UIToFP; | |
| } | |
| const bool IsSExtBack = VecOpcode == Instruction::UIToFP && | |
| SrcIt != MinBWs.end() && SrcIt->second.second; | |
| auto GetScalarCost = [&](unsigned Idx) -> InstructionCost { | |
| assert(Idx == 0 && "Expected 0 index only"); | |
| return TTI->getCastInstrCost(Opcode, VL0->getType(), | |
| VL0->getOperand(0)->getType(), | |
| TTI::getCastContextHint(VL0), CostKind, VL0); | |
| }; | |
| auto GetVectorCost = [=](InstructionCost CommonCost) { | |
| // Do not count cost here if minimum bitwidth is in effect and it is just | |
| // a bitcast (here it is just a noop). | |
| if (VecOpcode != Opcode && VecOpcode == Instruction::BitCast) | |
| return CommonCost; | |
| auto *VI = VL0->getOpcode() == Opcode ? VL0 : nullptr; | |
| TTI::CastContextHint CCH = GetCastContextHint(VL0->getOperand(0)); | |
| bool IsArithmeticExtendedReduction = | |
| E->Idx == 0 && UserIgnoreList && | |
| all_of(*UserIgnoreList, [](Value *V) { | |
| auto *I = cast<Instruction>(V); | |
| return is_contained({Instruction::Add, Instruction::FAdd, | |
| Instruction::Mul, Instruction::FMul, | |
| Instruction::And, Instruction::Or, | |
| Instruction::Xor}, | |
| I->getOpcode()); | |
| }); | |
| if (IsArithmeticExtendedReduction && | |
| (VecOpcode == Instruction::ZExt || VecOpcode == Instruction::SExt)) | |
| return CommonCost; | |
| if (IsSExtBack) { | |
| auto *DemotedVecTy = getWidenedType( | |
| IntegerType::get(F->getContext(), SrcIt->second.first), VL.size()); | |
| CommonCost += TTI->getCastInstrCost(Instruction::SExt, SrcVecTy, | |
| DemotedVecTy, CCH, CostKind); | |
| } | |
| return CommonCost + | |
| TTI->getCastInstrCost(VecOpcode, VecTy, SrcVecTy, CCH, CostKind, | |
| VecOpcode == Opcode ? VI : nullptr); | |
| }; | |
| return GetCostDiff(GetScalarCost, GetVectorCost); | |
| } | |
| case Instruction::FCmp: | |
| case Instruction::ICmp: | |
| // Override ScalarTy/VecTy with the compared operand type (not i1). The | |
| // cost of a compare instruction is determined by the operand width, and | |
| // getCmpSelInstrCost expects the compared type as its first type arg. | |
| OrigScalarTy = ScalarTy = | |
| getValueType(VL0, Opts.slp_revec, /*LookThroughCmp=*/true); | |
| VecTy = getWidenedType(ScalarTy, VL.size()); | |
| [[fallthrough]]; | |
| case Instruction::Select: { | |
| CmpPredicate VecPred, SwappedVecPred; | |
| auto MatchCmp = m_Cmp(VecPred, m_Value(), m_Value()); | |
| if (match(VL0, m_Select(MatchCmp, m_Value(), m_Value())) || | |
| match(VL0, MatchCmp)) | |
| SwappedVecPred = CmpInst::getSwappedPredicate(VecPred); | |
| else | |
| SwappedVecPred = VecPred = ScalarTy->isFloatingPointTy() | |
| ? CmpInst::BAD_FCMP_PREDICATE | |
| : CmpInst::BAD_ICMP_PREDICATE; | |
| // The bit tests of trunc X to i1 lanes compare and A, B with a constant. | |
| const bool IsBitTest = any_of(E->Scalars, IsaPred<TruncInst>); | |
| auto GetScalarCost = [&](unsigned Idx) { | |
| if (isa<PoisonValue>(UniqueValues[Idx])) | |
| return InstructionCost(TTI::TCC_Free); | |
| if (!isa<SelectInst>(UniqueValues[Idx])) { | |
| auto *I = cast<Instruction>(UniqueValues[Idx]); | |
| InstructionCost Cost = TTI->getInstructionCost(I, CostKind); | |
| // The and of the bit test is erased with the compare. | |
| if (IsBitTest && isa<ICmpInst>(I)) | |
| Cost += TTI->getInstructionCost(cast<Instruction>(I->getOperand(0)), | |
| CostKind); | |
| return Cost; | |
| } | |
| auto *VI = cast<Instruction>(UniqueValues[Idx]); | |
| CmpPredicate CurrentPred = ScalarTy->isFloatingPointTy() | |
| ? CmpInst::BAD_FCMP_PREDICATE | |
| : CmpInst::BAD_ICMP_PREDICATE; | |
| Value *LHS = nullptr, *RHS = nullptr; | |
| auto MatchCmp = m_Cmp(CurrentPred, m_Value(), m_Value()); | |
| bool IsSelect = | |
| ShuffleOrOp == Instruction::Select && | |
| (match(VI, m_Select(MatchCmp, m_Value(LHS), m_Value(RHS))) || | |
| match(VI, m_Select(m_Value(), m_Value(LHS), m_Value(RHS)))); | |
| if ((!IsSelect && !match(VI, MatchCmp)) || | |
| (CurrentPred != static_cast<CmpInst::Predicate>(VecPred) && | |
| CurrentPred != static_cast<CmpInst::Predicate>(SwappedVecPred))) | |
| VecPred = SwappedVecPred = ScalarTy->isFloatingPointTy() | |
| ? CmpInst::BAD_FCMP_PREDICATE | |
| : CmpInst::BAD_ICMP_PREDICATE; | |
| // Check if operands are of i1 types, like a condition expression. | |
| // TODO: consider implementing this in TTI. | |
| InstructionCost ScalarCost = InstructionCost::getInvalid(); | |
| if (IsSelect && LHS->getType() == VI->getOperand(0)->getType()) { | |
| assert(LHS->getType() == RHS->getType() && | |
| "Expected same type for LHS/RHS"); | |
| // select i1 v, i1 true, i1 b -> or i1 v, i1 b | |
| if (match(LHS, m_AllOnes())) { | |
| ScalarCost = TTI->getArithmeticInstrCost( | |
| Instruction::Or, LHS->getType(), CostKind, | |
| TTI::getOperandInfo(VI->getOperand(0)), TTI::getOperandInfo(RHS)); | |
| } else if (match(RHS, m_Zero())) { | |
| // select i1 v, i1 b, i1 false -> and i1 v, i1 b | |
| ScalarCost = TTI->getArithmeticInstrCost( | |
| Instruction::And, LHS->getType(), CostKind, | |
| TTI::getOperandInfo(VI->getOperand(0)), TTI::getOperandInfo(LHS)); | |
| } | |
| } | |
| if (!ScalarCost.isValid()) { | |
| // For selects, the "condition type" arg is the condition operand's | |
| // type; for standalone compares, it is the result type (i1). | |
| ScalarCost = TTI->getCmpSelInstrCost( | |
| E->getOpcode(), OrigScalarTy, | |
| ShuffleOrOp == Instruction::Select ? VL0->getOperand(0)->getType() | |
| : VL0->getType(), | |
| CurrentPred, CostKind, | |
| TTI::getOperandInfo( | |
| VI->getOperand(ShuffleOrOp == Instruction::Select ? 1 : 0)), | |
| TTI::getOperandInfo( | |
| VI->getOperand(ShuffleOrOp == Instruction::Select ? 2 : 1)), | |
| VI); | |
| } | |
| InstructionCost IntrinsicCost = GetMinMaxCost(OrigScalarTy, VI); | |
| if (IntrinsicCost.isValid()) | |
| ScalarCost = IntrinsicCost; | |
| return ScalarCost; | |
| }; | |
| auto GetVectorCost = [&](InstructionCost CommonCost) { | |
| // An absorbed boolean zero-test leaf emits no conversion; only the | |
| // reorder shuffle (in CommonCost) may remain. | |
| if (isBooleanizedNode(E)) | |
| return CommonCost; | |
| // For selects, the condition type may differ from the result type | |
| // (e.g. condition is <N x i1> while result is <N x i32>). For | |
| // compares, the result type IS the mask (i1/vNi1). Construct the | |
| // right type so getCmpSelInstrCost sees the actual mask/result width. | |
| auto *MaskTy = cast<VectorType>(getWidenedType( | |
| ShuffleOrOp == Instruction::Select ? VL0->getOperand(0)->getType() | |
| : VL0->getType(), | |
| VL.size())); | |
| InstructionCost VecCost = InstructionCost::getInvalid(); | |
| if (ShuffleOrOp == Instruction::Select) { | |
| ArrayRef<Value *> Cond = E->getOperand(0); | |
| ArrayRef<Value *> LHS = E->getOperand(1); | |
| ArrayRef<Value *> RHS = E->getOperand(2); | |
| // select <VF x i1>, <VF x i1>, <VF x i1>? | |
| // TODO: consider implementing this in TTI. | |
| if (Cond.front()->getType() == LHS.front()->getType()) { | |
| // select <VF x i1> v, <VF x i1> true, <VF x i1> b -> or <VF x i1> v, | |
| // <VF x i1> b | |
| if (all_of(LHS, [&](Value *V) { return match(V, m_AllOnes()); })) { | |
| VecCost = TTI->getArithmeticInstrCost( | |
| Instruction::Or, VecTy, CostKind, TTI::getOperandInfo(Cond), | |
| TTI::getOperandInfo(RHS)); | |
| } else if (all_of(RHS, | |
| [&](Value *V) { return match(V, m_Zero()); })) { | |
| // select <VF x i1> v, <VF x i1> b, <VF x i1> false -> and <VF x i1> | |
| // v, <VF x i1> b | |
| VecCost = TTI->getArithmeticInstrCost( | |
| Instruction::And, VecTy, CostKind, TTI::getOperandInfo(Cond), | |
| TTI::getOperandInfo(LHS)); | |
| } | |
| } | |
| } | |
| if (!VecCost.isValid()) { | |
| VecCost = TTI->getCmpSelInstrCost( | |
| E->getOpcode(), VecTy, MaskTy, VecPred, CostKind, | |
| TTI::getOperandInfo( | |
| E->getOperand(ShuffleOrOp == Instruction::Select ? 1 : 0)), | |
| TTI::getOperandInfo( | |
| E->getOperand(ShuffleOrOp == Instruction::Select ? 2 : 1)), | |
| VL0); | |
| if (IsBitTest) | |
| VecCost += | |
| TTI->getArithmeticInstrCost(Instruction::And, VecTy, CostKind); | |
| if (isa<SelectInst>(VL0)) { | |
| unsigned CondNumElements = getNumElements(MaskTy); | |
| unsigned VecTyNumElements = getNumElements(VecTy); | |
| assert(VecTyNumElements >= CondNumElements && | |
| VecTyNumElements % CondNumElements == 0 && | |
| "Cannot vectorize Instruction::Select"); | |
| if (CondNumElements != VecTyNumElements) { | |
| // When the return type is i1 but the source is fixed vector type, | |
| // we need to duplicate the condition value. | |
| VecCost += getShuffleCost( | |
| *TTI, TTI::SK_PermuteSingleSrc, MaskTy, CostKind, | |
| createReplicatedMask(VecTyNumElements / CondNumElements, | |
| CondNumElements)); | |
| } | |
| } | |
| } | |
| return VecCost + CommonCost; | |
| }; | |
| return GetCostDiff(GetScalarCost, GetVectorCost); | |
| } | |
| case TreeEntry::MinMax: { | |
| auto GetScalarCost = [&](unsigned Idx) { | |
| return GetMinMaxCost(OrigScalarTy); | |
| }; | |
| auto GetVectorCost = [&](InstructionCost CommonCost) { | |
| InstructionCost VecCost = GetMinMaxCost(VecTy); | |
| return VecCost + CommonCost; | |
| }; | |
| return GetCostDiff(GetScalarCost, GetVectorCost); | |
| } | |
| case TreeEntry::FMulAdd: { | |
| auto GetScalarCost = [&](unsigned Idx) { | |
| if (isa<PoisonValue>(UniqueValues[Idx])) | |
| return InstructionCost(TTI::TCC_Free); | |
| auto *I = cast<Instruction>(UniqueValues[Idx]); | |
| InstructionCost Cost = GetFMulAddCost(E->getOperations(), I); | |
| if (Cost.isValid()) | |
| return Cost; | |
| // The lanes without the fmul, e.g. the constant lanes of the fmul node, | |
| // stay plain fadd/fsub. | |
| unsigned Lane = UniqueIndexes[Idx]; | |
| Value *Op1 = E->getOperand(0)[Lane]; | |
| Value *Op2 = E->getOperand(1)[Lane]; | |
| return TTI->getArithmeticInstrCost( | |
| E->getOpcode(), OrigScalarTy, CostKind, TTI::getOperandInfo(Op1), | |
| TTI::getOperandInfo(Op2), {Op1, Op2}, I, TLI); | |
| }; | |
| auto GetVectorCost = [&, &TTI = *TTI](InstructionCost CommonCost) { | |
| FastMathFlags FMF; | |
| FMF.set(); | |
| for (FPMathOperator *FPCI : make_isa_range<FPMathOperator>(E->Scalars)) { | |
| FMF &= FPCI->getFastMathFlags(); | |
| // The fused fmul may sit in either operand (c - a*b included). | |
| if (Instruction *FMul = | |
| getFusableFMulOperand(cast<Instruction>(FPCI), TTI)) | |
| FMF &= FMul->getFastMathFlags(); | |
| } | |
| IntrinsicCostAttributes ICA(Intrinsic::fmuladd, VecTy, | |
| {VecTy, VecTy, VecTy}, FMF); | |
| InstructionCost VecCost = TTI.getIntrinsicInstrCost(ICA, CostKind); | |
| return VecCost + CommonCost; | |
| }; | |
| return GetCostDiff(GetScalarCost, GetVectorCost); | |
| } | |
| case TreeEntry::ReducedBitcast: | |
| case TreeEntry::ReducedBitcastBSwap: { | |
| auto GetScalarCost = [&, &TTI = *TTI](unsigned Idx) { | |
| if (isa<PoisonValue>(UniqueValues[Idx])) | |
| return InstructionCost(TTI::TCC_Free); | |
| auto *Shl = dyn_cast<Instruction>(UniqueValues[Idx]); | |
| if (!Shl) | |
| return InstructionCost(TTI::TCC_Free); | |
| InstructionCost ScalarCost = TTI.getInstructionCost(Shl, CostKind); | |
| auto *ZExt = dyn_cast<Instruction>(Shl->getOperand(0)); | |
| if (!ZExt) | |
| return ScalarCost; | |
| ScalarCost += TTI.getInstructionCost(ZExt, CostKind); | |
| return ScalarCost; | |
| }; | |
| auto GetVectorCost = [&, &TTI = *TTI](InstructionCost CommonCost) { | |
| const TreeEntry *LhsTE = getOperandEntry(E, /*Idx=*/0); | |
| TTI::CastContextHint CastCtx = | |
| getCastContextHint(*getOperandEntry(LhsTE, /*Idx=*/0)); | |
| Type *SrcScalarTy = cast<ZExtInst>(LhsTE->getMainOp())->getSrcTy(); | |
| auto *SrcVecTy = getWidenedType(SrcScalarTy, LhsTE->getVectorFactor()); | |
| InstructionCost BitcastCost = TTI.getCastInstrCost( | |
| Instruction::BitCast, ScalarTy, SrcVecTy, CastCtx, CostKind); | |
| if (ShuffleOrOp == TreeEntry::ReducedBitcastBSwap) { | |
| auto *SrcType = IntegerType::getIntNTy( | |
| ScalarTy->getContext(), | |
| DL->getTypeSizeInBits(SrcScalarTy) * EntryVF); | |
| IntrinsicCostAttributes CostAttrs(Intrinsic::bswap, SrcType, {SrcType}); | |
| InstructionCost IntrinsicCost = | |
| TTI.getIntrinsicInstrCost(CostAttrs, CostKind); | |
| BitcastCost += IntrinsicCost; | |
| if (SrcType != ScalarTy) { | |
| BitcastCost += TTI.getCastInstrCost(Instruction::ZExt, ScalarTy, | |
| SrcType, CastCtx, CostKind); | |
| } | |
| } | |
| return BitcastCost + CommonCost; | |
| }; | |
| return GetCostDiff(GetScalarCost, GetVectorCost); | |
| } | |
| case TreeEntry::ReducedBitcastLoads: | |
| case TreeEntry::ReducedBitcastBSwapLoads: { | |
| auto GetScalarCost = [&, &TTI = *TTI](unsigned Idx) { | |
| if (isa<PoisonValue>(UniqueValues[Idx])) | |
| return InstructionCost(TTI::TCC_Free); | |
| auto *Shl = dyn_cast<Instruction>(UniqueValues[Idx]); | |
| if (!Shl) | |
| return InstructionCost(TTI::TCC_Free); | |
| InstructionCost ScalarCost = TTI.getInstructionCost(Shl, CostKind); | |
| auto *ZExt = dyn_cast<Instruction>(Shl->getOperand(0)); | |
| if (!ZExt) | |
| return ScalarCost; | |
| ScalarCost += TTI.getInstructionCost(ZExt, CostKind); | |
| auto *Load = dyn_cast<Instruction>(ZExt->getOperand(0)); | |
| if (!Load) | |
| return ScalarCost; | |
| ScalarCost += TTI.getInstructionCost(Load, CostKind); | |
| return ScalarCost; | |
| }; | |
| auto GetVectorCost = [&, &TTI = *TTI](InstructionCost CommonCost) { | |
| const TreeEntry *LhsTE = getOperandEntry(E, /*Idx=*/0); | |
| const TreeEntry *LoadTE = getOperandEntry(LhsTE, /*Idx=*/0); | |
| auto *LI0 = cast<LoadInst>(LoadTE->getMainOp()); | |
| auto *SrcType = IntegerType::getIntNTy( | |
| ScalarTy->getContext(), | |
| DL->getTypeSizeInBits(LI0->getType()) * EntryVF); | |
| InstructionCost LoadCost = | |
| TTI.getMemoryOpCost(Instruction::Load, SrcType, LI0->getAlign(), | |
| LI0->getPointerAddressSpace(), CostKind); | |
| if (ShuffleOrOp == TreeEntry::ReducedBitcastBSwapLoads) { | |
| IntrinsicCostAttributes CostAttrs(Intrinsic::bswap, SrcType, {SrcType}); | |
| InstructionCost IntrinsicCost = | |
| TTI.getIntrinsicInstrCost(CostAttrs, CostKind); | |
| LoadCost += IntrinsicCost; | |
| if (SrcType != ScalarTy) { | |
| LoadCost += | |
| TTI.getCastInstrCost(Instruction::ZExt, ScalarTy, SrcType, | |
| getCastContextHint(*LoadTE), CostKind); | |
| } | |
| } | |
| return LoadCost + CommonCost; | |
| }; | |
| return GetCostDiff(GetScalarCost, GetVectorCost); | |
| } | |
| case TreeEntry::ReducedBitPack: { | |
| const TreeEntry *ShlTE = | |
| E->getOpcode() == Instruction::And ? getOperandEntry(E, /*Idx=*/0) : E; | |
| const TreeEntry *LhsTE = getOperandEntry(ShlTE, /*Idx=*/0); | |
| std::optional<BitPackInfo> Info = computeBitPackLayout(*E); | |
| const TreeEntry *SrcTE = Info ? getBitPackSource(*LhsTE, *Info) : LhsTE; | |
| // The scalars of the root, the shl and the combined chain are removed. | |
| SmallVector<const TreeEntry *> CombinedTEs = {E}; | |
| if (ShlTE != E) | |
| CombinedTEs.push_back(ShlTE); | |
| for (const TreeEntry *TE = LhsTE; TE != SrcTE; | |
| TE = getOperandEntry(TE, /*Idx=*/0)) | |
| CombinedTEs.push_back(TE); | |
| auto GetLaneCost = [&, &TTI = *TTI](unsigned Lane) { | |
| InstructionCost ScalarCost = 0; | |
| for (const TreeEntry *TE : CombinedTEs) { | |
| auto *I = dyn_cast<Instruction>(TE->Scalars[Lane]); | |
| // A copyable lane is the operand of the node, costed with its node. | |
| if (!I || TE->isCopyableElement(I)) | |
| continue; | |
| if (I->isCast()) { | |
| ScalarCost += TTI.getCastInstrCost( | |
| I->getOpcode(), I->getType(), I->getOperand(0)->getType(), | |
| TTI::getCastContextHint(I), CostKind, I); | |
| continue; | |
| } | |
| assert(is_contained( | |
| {Instruction::And, Instruction::LShr, Instruction::Shl}, | |
| I->getOpcode()) && | |
| "Expected and/shift in the bit pack."); | |
| ScalarCost += TTI.getArithmeticInstrCost( | |
| I->getOpcode(), I->getType(), CostKind, | |
| TTI::getOperandInfo(I->getOperand(0)), | |
| TTI::getOperandInfo(I->getOperand(1)), | |
| {I->getOperand(0), I->getOperand(1)}, I, TLI); | |
| } | |
| return ScalarCost; | |
| }; | |
| auto GetScalarCost = [&](unsigned Idx) { | |
| return GetLaneCost(UniqueIndexes[Idx]); | |
| }; | |
| auto GetVectorCost = [&, &TTI = *TTI](InstructionCost CommonCost) { | |
| if (!Info) | |
| return InstructionCost::getInvalid(); | |
| unsigned ShiftWidth; | |
| // The pack works on the vectorized operand in its own (possibly | |
| // demoted) type. | |
| Type *SrcScalarTy = SrcTE->Scalars.front()->getType(); | |
| if (auto It = MinBWs.find(SrcTE); It != MinBWs.end()) | |
| SrcScalarTy = IntegerType::get(F->getContext(), It->second.first); | |
| // The source lanes are packed into the node type. | |
| Type *DstScalarTy = SrcTE == LhsTE ? SrcScalarTy : ScalarTy; | |
| InstructionCost PackCost = | |
| getBitPackCost(TTI, | |
| cast<FixedVectorType>(getWidenedType( | |
| SrcScalarTy, SrcTE->getVectorFactor())), | |
| DstScalarTy, *Info, getZExtSrcWidth(*SrcTE), | |
| getCastContextHint(*SrcTE), CostKind, TLI, | |
| E->getMainOp(), ShiftWidth); | |
| // A demoted result is re-extended to the reduction type. | |
| if (DstScalarTy != OrigScalarTy) { | |
| auto It = MinBWs.find(E); | |
| PackCost += TTI.getCastInstrCost( | |
| It != MinBWs.end() && It->second.second ? Instruction::SExt | |
| : Instruction::ZExt, | |
| OrigScalarTy, DstScalarTy, getCastContextHint(*LhsTE), CostKind); | |
| } | |
| // The copyable lanes are not the scalars of the node, but their scalars | |
| // in the shl and the combined chain are removed too. | |
| for (auto [Lane, V] : enumerate(E->Scalars)) | |
| if (E->isCopyableElement(V)) | |
| PackCost -= GetLaneCost(Lane); | |
| return PackCost + CommonCost; | |
| }; | |
| return GetCostDiff(GetScalarCost, GetVectorCost); | |
| } | |
| case TreeEntry::ReducedCmpBitcast: { | |
| auto GetScalarCost = [&, &TTI = *TTI](unsigned Idx) { | |
| if (isa<PoisonValue>(UniqueValues[Idx])) | |
| return InstructionCost(TTI::TCC_Free); | |
| auto *Sel = dyn_cast<Instruction>(UniqueValues[Idx]); | |
| if (!Sel) | |
| return InstructionCost(TTI::TCC_Free); | |
| InstructionCost ScalarCost = TTI.getInstructionCost(Sel, CostKind); | |
| return ScalarCost; | |
| }; | |
| auto GetVectorCost = [&, &TTI = *TTI](InstructionCost CommonCost) { | |
| Type *CmpTy = CmpInst::makeCmpResultType(VecTy); | |
| auto *DstTy = | |
| IntegerType::getIntNTy(ScalarTy->getContext(), E->getVectorFactor()); | |
| InstructionCost BitcastCost = | |
| TTI.getCastInstrCost(Instruction::BitCast, DstTy, CmpTy, | |
| TTI::CastContextHint::None, CostKind); | |
| if (DstTy != ScalarTy) { | |
| BitcastCost += | |
| TTI.getCastInstrCost(Instruction::ZExt, ScalarTy, DstTy, | |
| TTI::CastContextHint::None, CostKind); | |
| } | |
| return BitcastCost + CommonCost; | |
| }; | |
| return GetCostDiff(GetScalarCost, GetVectorCost); | |
| } | |
| case Instruction::FNeg: | |
| case Instruction::Add: | |
| case Instruction::FAdd: | |
| case Instruction::Sub: | |
| case Instruction::FSub: | |
| case Instruction::Mul: | |
| case Instruction::FMul: | |
| case Instruction::UDiv: | |
| case Instruction::SDiv: | |
| case Instruction::FDiv: | |
| case Instruction::URem: | |
| case Instruction::SRem: | |
| case Instruction::FRem: | |
| case Instruction::Shl: | |
| case Instruction::LShr: | |
| case Instruction::AShr: | |
| case Instruction::And: | |
| case Instruction::Or: | |
| case Instruction::Xor: { | |
| auto GetScalarCost = [&](unsigned Idx) { | |
| if (isa<PoisonValue>(UniqueValues[Idx])) | |
| return InstructionCost(TTI::TCC_Free); | |
| // We cannot retrieve the operand from UniqueValues[Idx] because an | |
| // interchangeable instruction may be used. The order and the actual | |
| // operand might differ from what is retrieved from UniqueValues[Idx]. | |
| unsigned Lane = UniqueIndexes[Idx]; | |
| Value *Op1 = E->getOperand(0)[Lane]; | |
| Value *Op2; | |
| SmallVector<const Value *, 2> Operands(1, Op1); | |
| if (isa<UnaryOperator>(UniqueValues[Idx])) { | |
| Op2 = Op1; | |
| } else { | |
| Op2 = E->getOperand(1)[Lane]; | |
| Operands.push_back(Op2); | |
| } | |
| TTI::OperandValueInfo Op1Info = TTI::getOperandInfo(Op1); | |
| TTI::OperandValueInfo Op2Info = TTI::getOperandInfo(Op2); | |
| auto *I = dyn_cast<Instruction>(UniqueValues[Idx]); | |
| InstructionCost ScalarCost = TTI->getArithmeticInstrCost( | |
| ShuffleOrOp, OrigScalarTy, CostKind, Op1Info, Op2Info, Operands, | |
| I && I->getOpcode() == ShuffleOrOp ? I : nullptr); | |
| if (I && (ShuffleOrOp == Instruction::FAdd || | |
| ShuffleOrOp == Instruction::FSub)) { | |
| InstructionCost IntrinsicCost = GetFMulAddCost(E->getOperations(), I); | |
| if (IntrinsicCost.isValid()) | |
| ScalarCost = IntrinsicCost; | |
| } else if (I && ShuffleOrOp == Instruction::FMul) { | |
| InstructionCost FusedCost = GetFusedFMulCost(I); | |
| if (FusedCost.isValid()) | |
| ScalarCost = FusedCost; | |
| } | |
| return ScalarCost; | |
| }; | |
| auto GetVectorCost = [=](InstructionCost CommonCost) { | |
| // And peephole only applies to plain 2-operand nodes. | |
| if (ShuffleOrOp == Instruction::And && It != MinBWs.end() && | |
| !E->hasReassocScalars()) { | |
| for (unsigned I : seq<unsigned>(0, E->getNumOperands())) { | |
| ArrayRef<Value *> Ops = E->getOperand(I); | |
| if (all_of(Ops, [&](Value *Op) { | |
| auto *CI = dyn_cast<ConstantInt>(Op); | |
| return CI && CI->getValue().countr_one() >= It->second.first; | |
| })) | |
| return CommonCost; | |
| } | |
| } | |
| // Masked path ignores Op1Info/Op2Info like its codegen does; keep it | |
| // out of the operand-aware cost comparison below. | |
| if (InstructionCost MaskedCost = getMaskedDivRemCost( | |
| *TTI, Opts.slp_revec, ShuffleOrOp, ScalarTy, VL.size(), CostKind); | |
| MaskedCost.isValid()) | |
| return MaskedCost + CommonCost; | |
| unsigned OpIdx = isa<UnaryOperator>(VL0) ? 0 : 1; | |
| TTI::OperandValueInfo Op1Info = TTI::getOperandInfo(E->getOperand(0)); | |
| TTI::OperandValueInfo Op2Info = TTI::getOperandInfo(E->getOperand(OpIdx)); | |
| InstructionCost Cost = TTI->getArithmeticInstrCost( | |
| ShuffleOrOp, VecTy, CostKind, Op1Info, Op2Info, {}, | |
| VL0->getOpcode() == ShuffleOrOp ? VL0 : nullptr, TLI); | |
| // N columns need N-1 vector combines; price extra columns | |
| // conservatively, skipping identity-only columns (not combined by | |
| // codegen). | |
| if (E->hasReassocScalars()) { | |
| const unsigned CombineOpcode = getReassocCombineOpcode(E->getOpcode()); | |
| for (unsigned Idx : seq<unsigned>(2, E->getNumOperands())) { | |
| if (all_of(E->getOperand(Idx), [&](Value *V) { | |
| return isBinOpIdentityConstant(V, CombineOpcode); | |
| })) | |
| continue; | |
| // The first operand is the running fold, a computed value with no | |
| // static properties to model. | |
| Cost += TTI->getArithmeticInstrCost( | |
| ShuffleOrOp, VecTy, CostKind, {}, | |
| TTI::getOperandInfo(E->getOperand(Idx)), {}, | |
| VL0->getOpcode() == ShuffleOrOp ? VL0 : nullptr, TLI); | |
| } | |
| } | |
| return Cost + CommonCost; | |
| }; | |
| return GetCostDiffWithPeeled(GetScalarCost, GetVectorCost); | |
| } | |
| case Instruction::GetElementPtr: { | |
| return CommonCost + GetGEPCostDiff(VL, VL0); | |
| } | |
| case Instruction::Load: { | |
| auto GetScalarCost = [&](unsigned Idx) { | |
| auto *VI = cast<LoadInst>(UniqueValues[Idx]); | |
| return TTI->getMemoryOpCost( | |
| Instruction::Load, OrigScalarTy, VI->getAlign(), | |
| VI->getPointerAddressSpace(), CostKind, | |
| TTI::getOperandInfo(VI->getPointerOperand()), VI); | |
| }; | |
| auto *LI0 = cast<LoadInst>(VL0); | |
| auto GetVectorCost = [&](InstructionCost CommonCost) { | |
| InstructionCost VecLdCost; | |
| switch (E->State) { | |
| case TreeEntry::Vectorize: | |
| if (unsigned Factor = E->getInterleaveFactor()) { | |
| VecLdCost = TTI->getInterleavedMemoryOpCost( | |
| Instruction::Load, VecTy, Factor, {}, LI0->getAlign(), | |
| LI0->getPointerAddressSpace(), CostKind); | |
| } else { | |
| VecLdCost = TTI->getMemoryOpCost( | |
| Instruction::Load, VecTy, | |
| commonAlignment(LI0->getAlign(), getVectorLoadShift(E->Scalars)), | |
| LI0->getPointerAddressSpace(), CostKind, | |
| TTI::getOperandInfo(LI0->getPointerOperand())); | |
| } | |
| break; | |
| case TreeEntry::StridedVectorize: { | |
| const StridedPtrInfo &SPtrInfo = TreeEntryToStridedPtrInfoMap.at(E); | |
| FixedVectorType *StridedLoadTy = SPtrInfo.Ty; | |
| assert(StridedLoadTy && "Missing StridedPointerInfo for tree entry."); | |
| Align CommonAlignment = | |
| computeCommonAlignment<LoadInst>(UniqueValues.getArrayRef()); | |
| bool IsReverse = E->isReverse() && !isWidenedStridedLoad(E); | |
| Value *Stride = getStrideBytesIfConstant(SPtrInfo.StrideVal, ScalarTy, | |
| *DL, IsReverse); | |
| VecLdCost = TTI->getMemIntrinsicInstrCost( | |
| MemIntrinsicCostAttributes(Intrinsic::experimental_vp_strided_load, | |
| StridedLoadTy, LI0->getPointerOperand(), | |
| /*VariableMask=*/false, CommonAlignment, | |
| /*I=*/nullptr, Stride), | |
| CostKind); | |
| if (StridedLoadTy != VecTy) | |
| VecLdCost += | |
| getWidenedStridedCastCost(*TTI, StridedLoadTy, VecTy, *DL, | |
| getCastContextHint(*E), CostKind); | |
| break; | |
| } | |
| case TreeEntry::CompressVectorize: { | |
| bool IsMasked; | |
| unsigned InterleaveFactor; | |
| SmallVector<int> CompressMask; | |
| VectorType *LoadVecTy; | |
| SmallVector<Value *> Scalars(VL); | |
| if (!E->ReorderIndices.empty()) { | |
| SmallVector<int> Mask(E->ReorderIndices.begin(), | |
| E->ReorderIndices.end()); | |
| reorderScalars(Scalars, Mask); | |
| } | |
| SmallVector<Value *> PointerOps(Scalars.size()); | |
| for (auto [I, V] : enumerate(Scalars)) | |
| PointerOps[I] = cast<LoadInst>(V)->getPointerOperand(); | |
| [[maybe_unused]] bool IsVectorized = isMaskedLoadCompress( | |
| Scalars, PointerOps, E->ReorderIndices, *TTI, *DL, *SE, *AC, *DT, | |
| *TLI, CostKind, [](Value *) { return true; }, Opts.slp_revec, | |
| IsMasked, InterleaveFactor, CompressMask, LoadVecTy, Cache); | |
| CompressEntryToData.try_emplace(E, CompressMask, LoadVecTy, | |
| InterleaveFactor, IsMasked); | |
| Align CommonAlignment = LI0->getAlign(); | |
| // For REVEC the emitted shuffle uses the compress mask expanded to the | |
| // individual elements of the vector scalars. | |
| SmallVector<int> CompressCostMask(CompressMask); | |
| transformScalarShuffleIndiciesToVector(getNumElements(LI0->getType()), | |
| CompressCostMask); | |
| if (InterleaveFactor) { | |
| VecLdCost = TTI->getInterleavedMemoryOpCost( | |
| Instruction::Load, LoadVecTy, InterleaveFactor, {}, | |
| CommonAlignment, LI0->getPointerAddressSpace(), CostKind); | |
| } else if (IsMasked) { | |
| VecLdCost = TTI->getMemIntrinsicInstrCost( | |
| MemIntrinsicCostAttributes(Intrinsic::masked_load, LoadVecTy, | |
| CommonAlignment, | |
| LI0->getPointerAddressSpace()), | |
| CostKind); | |
| // TODO: include this cost into CommonCost. | |
| VecLdCost += getShuffleCost(*TTI, TTI::SK_PermuteSingleSrc, LoadVecTy, | |
| CostKind, CompressCostMask); | |
| } else { | |
| VecLdCost = TTI->getMemoryOpCost( | |
| Instruction::Load, LoadVecTy, CommonAlignment, | |
| LI0->getPointerAddressSpace(), CostKind, | |
| TTI::getOperandInfo(LI0->getPointerOperand())); | |
| // TODO: include this cost into CommonCost. | |
| VecLdCost += getShuffleCost(*TTI, TTI::SK_PermuteSingleSrc, LoadVecTy, | |
| CostKind, CompressCostMask); | |
| } | |
| break; | |
| } | |
| case TreeEntry::ScatterVectorize: { | |
| Align CommonAlignment = | |
| computeCommonAlignment<LoadInst>(UniqueValues.getArrayRef()); | |
| VecLdCost = TTI->getMemIntrinsicInstrCost( | |
| MemIntrinsicCostAttributes(Intrinsic::masked_gather, VecTy, | |
| LI0->getPointerOperand(), | |
| /*VariableMask=*/false, CommonAlignment), | |
| CostKind); | |
| break; | |
| } | |
| case TreeEntry::BlendedLoadVectorize: { | |
| // Two masked loads (one per candidate base) blended by a select. | |
| Align CommonAlignment = | |
| computeBlendedLoadBaseAlignment(UniqueValues.getArrayRef(), *DL); | |
| VecLdCost = getBlendedLoadCost(*TTI, VecTy, CommonAlignment, | |
| LI0->getPointerAddressSpace(), CostKind); | |
| break; | |
| } | |
| case TreeEntry::ExpandVectorize: | |
| case TreeEntry::CombinedVectorize: | |
| case TreeEntry::SplitVectorize: | |
| case TreeEntry::NeedToGather: | |
| llvm_unreachable("Unexpected vectorization state."); | |
| } | |
| return VecLdCost + CommonCost; | |
| }; | |
| InstructionCost Cost = GetCostDiff(GetScalarCost, GetVectorCost); | |
| // Masked gather and blended loads are not terminal nodes: their address | |
| // cost is estimated separately (blended loads have no per-lane address). | |
| if (E->State == TreeEntry::ScatterVectorize || | |
| E->State == TreeEntry::BlendedLoadVectorize) | |
| return Cost; | |
| // Estimate cost of GEPs since this tree node is a terminator. | |
| SmallVector<Value *> PointerOps; | |
| for (LoadInst *LI : make_isa_range<LoadInst>(VL)) | |
| PointerOps.push_back(LI->getPointerOperand()); | |
| return Cost + GetGEPCostDiff(PointerOps, LI0->getPointerOperand()); | |
| } | |
| case Instruction::Store: { | |
| bool IsReorder = !E->ReorderIndices.empty(); | |
| auto GetScalarCost = [=](unsigned Idx) { | |
| auto *VI = cast<StoreInst>(VL[Idx]); | |
| TTI::OperandValueInfo OpInfo = TTI::getOperandInfo(VI->getValueOperand()); | |
| return TTI->getMemoryOpCost(Instruction::Store, OrigScalarTy, | |
| VI->getAlign(), VI->getPointerAddressSpace(), | |
| CostKind, OpInfo, VI); | |
| }; | |
| auto *BaseSI = | |
| cast<StoreInst>(IsReorder ? VL[E->ReorderIndices.front()] : VL0); | |
| auto GetVectorCost = [=](InstructionCost CommonCost) { | |
| // We know that we can merge the stores. Calculate the cost. | |
| InstructionCost VecStCost; | |
| if (E->State == TreeEntry::StridedVectorize) { | |
| const StridedPtrInfo &SPtrInfo = TreeEntryToStridedPtrInfoMap.at(E); | |
| FixedVectorType *StridedStoreTy = SPtrInfo.Ty; | |
| assert(StridedStoreTy && "Missing StridedPointerInfo for tree entry."); | |
| Align CommonAlignment = | |
| computeCommonAlignment<StoreInst>(UniqueValues.getArrayRef()); | |
| Value *Stride = | |
| getStrideBytesIfConstant(SPtrInfo.StrideVal, ScalarTy, *DL); | |
| VecStCost = TTI->getMemIntrinsicInstrCost( | |
| MemIntrinsicCostAttributes(Intrinsic::experimental_vp_strided_store, | |
| StridedStoreTy, | |
| BaseSI->getPointerOperand(), | |
| /*VariableMask=*/false, CommonAlignment, | |
| /*I=*/nullptr, Stride), | |
| CostKind); | |
| if (StridedStoreTy != VecTy) | |
| VecStCost += | |
| getWidenedStridedCastCost(*TTI, VecTy, StridedStoreTy, *DL, | |
| getCastContextHint(*E), CostKind); | |
| } else if (E->State == TreeEntry::ExpandVectorize) { | |
| const StridedPtrInfo &SPtrInfo = TreeEntryToStridedPtrInfoMap.at(E); | |
| FixedVectorType *MaskedStoreTy = SPtrInfo.Ty; | |
| assert(MaskedStoreTy && "Missing StridedPointerInfo for tree entry."); | |
| Align CommonAlignment = | |
| computeCommonAlignment<StoreInst>(UniqueValues.getArrayRef()); | |
| // Masked store: the values are expanded into the widened vector and | |
| // stored with a constant mask. | |
| VecStCost = TTI->getMemIntrinsicInstrCost( | |
| MemIntrinsicCostAttributes(Intrinsic::masked_store, MaskedStoreTy, | |
| CommonAlignment, | |
| BaseSI->getPointerAddressSpace()), | |
| CostKind); | |
| } else { | |
| assert(E->State == TreeEntry::Vectorize && | |
| "Expected either strided, consecutive, or expanded stores."); | |
| if (unsigned Factor = E->getInterleaveFactor()) { | |
| assert(E->ReuseShuffleIndices.empty() && !E->ReorderIndices.empty() && | |
| "No reused shuffles expected"); | |
| CommonCost = 0; | |
| VecStCost = TTI->getInterleavedMemoryOpCost( | |
| Instruction::Store, VecTy, Factor, {}, BaseSI->getAlign(), | |
| BaseSI->getPointerAddressSpace(), CostKind); | |
| } else { | |
| TTI::OperandValueInfo OpInfo = TTI::getOperandInfo(E->getOperand(0)); | |
| VecStCost = TTI->getMemoryOpCost( | |
| Instruction::Store, VecTy, BaseSI->getAlign(), | |
| BaseSI->getPointerAddressSpace(), CostKind, OpInfo); | |
| } | |
| } | |
| return VecStCost + CommonCost; | |
| }; | |
| SmallVector<Value *> PointerOps(VL.size()); | |
| for (auto [I, V] : enumerate(VL)) { | |
| unsigned Idx = IsReorder ? E->ReorderIndices[I] : I; | |
| PointerOps[Idx] = cast<StoreInst>(V)->getPointerOperand(); | |
| } | |
| return GetCostDiff(GetScalarCost, GetVectorCost) + | |
| GetGEPCostDiff(PointerOps, BaseSI->getPointerOperand()); | |
| } | |
| case Instruction::Call: { | |
| auto GetScalarCost = [&](unsigned Idx) { | |
| // A copyable lane has no scalar call of its own; its real cost is | |
| // accounted for wherever it is otherwise computed. | |
| if (isa<PoisonValue>(UniqueValues[Idx]) || | |
| E->isCopyableElement(UniqueValues[Idx])) | |
| return InstructionCost(TTI::TCC_Free); | |
| auto *CI = cast<CallInst>(UniqueValues[Idx]); | |
| Intrinsic::ID ID = getVectorIntrinsicIDForCall(CI, TLI); | |
| if (ID != Intrinsic::not_intrinsic) { | |
| IntrinsicCostAttributes CostAttrs(ID, *CI, 1); | |
| return TTI->getIntrinsicInstrCost(CostAttrs, CostKind); | |
| } | |
| return TTI->getCallInstrCost(CI->getCalledFunction(), | |
| CI->getFunctionType()->getReturnType(), | |
| CI->getFunctionType()->params(), CostKind); | |
| }; | |
| auto GetVectorCost = [=](InstructionCost CommonCost) { | |
| auto *CI = cast<CallInst>(VL0); | |
| Intrinsic::ID ID = getVectorIntrinsicIDForCall(CI, TLI); | |
| SmallVector<Type *> ArgTys = buildIntrinsicArgTypes( | |
| CI, ID, getNumElements(VecTy), | |
| It != MinBWs.end() ? It->second.first : 0, TTI); | |
| auto VecCallCosts = | |
| getVectorCallCosts(CI, VecTy, TTI, TLI, ArgTys, CostKind); | |
| return std::min(VecCallCosts.first, VecCallCosts.second) + CommonCost; | |
| }; | |
| return GetCostDiff(GetScalarCost, GetVectorCost); | |
| } | |
| case Instruction::ShuffleVector: { | |
| if (!Opts.slp_revec || E->isAltShuffle()) | |
| assert(E->isAltShuffle() && | |
| ((Instruction::isBinaryOp(E->getOpcode()) && | |
| Instruction::isBinaryOp(E->getAltOpcode())) || | |
| (Instruction::isCast(E->getOpcode()) && | |
| Instruction::isCast(E->getAltOpcode())) || | |
| (isa<CmpInst>(VL0) && isa<CmpInst>(E->getAltOp()))) && | |
| "Invalid Shuffle Vector Operand"); | |
| // Try to find the previous shuffle node with the same operands and same | |
| // main/alternate ops. | |
| auto TryFindNodeWithEqualOperands = [=]() { | |
| for (const std::unique_ptr<TreeEntry> &TE : VectorizableTree) { | |
| if (TE.get() == E) | |
| break; | |
| if (TE->hasState() && TE->isAltShuffle() && | |
| ((TE->getOpcode() == E->getOpcode() && | |
| TE->getAltOpcode() == E->getAltOpcode()) || | |
| (TE->getOpcode() == E->getAltOpcode() && | |
| TE->getAltOpcode() == E->getOpcode())) && | |
| TE->hasEqualOperands(*E)) | |
| return true; | |
| } | |
| return false; | |
| }; | |
| auto GetScalarCost = [&](unsigned Idx) { | |
| if (isa<PoisonValue>(UniqueValues[Idx])) | |
| return InstructionCost(TTI::TCC_Free); | |
| auto *VI = cast<Instruction>(UniqueValues[Idx]); | |
| assert(E->getMatchingMainOpOrAltOp(VI) && | |
| "Unexpected main/alternate opcode"); | |
| (void)E; | |
| if (auto *CI = dyn_cast<CmpInst>(VI)) | |
| return TTI->getCmpSelInstrCost( | |
| CI->getOpcode(), CI->getOperand(0)->getType(), CI->getType(), | |
| CI->getPredicate(), CostKind, | |
| TTI::getOperandInfo(CI->getOperand(0)), | |
| TTI::getOperandInfo(CI->getOperand(1)), CI); | |
| if (auto *CI = dyn_cast<CastInst>(VI)) | |
| return TTI->getCastInstrCost(CI->getOpcode(), CI->getDestTy(), | |
| CI->getSrcTy(), | |
| TTI::getCastContextHint(CI), CostKind, CI); | |
| if (auto *SV = dyn_cast<ShuffleVectorInst>(VI)) { | |
| int Index; | |
| [[maybe_unused]] bool IsExtractSubvectorMask = | |
| SV->isExtractSubvectorMask(Index); | |
| assert(IsExtractSubvectorMask && "Not supported shufflevector usage."); | |
| auto *SubTy = cast<VectorType>(SV->getType()); | |
| return TTI->getShuffleCost( | |
| TTI::SK_ExtractSubvector, SubTy, | |
| cast<VectorType>(SV->getOperand(0)->getType()), CostKind, | |
| SV->getShuffleMask(), Index, SubTy, | |
| {SV->getOperand(0), SV->getOperand(1)}, SV, | |
| TTI::getVectorInstrContextHint(SV)); | |
| } | |
| // The backend fuses fmul+fadd/fsub pairs in the scalar code. | |
| InstructionCost FusedCost = InstructionCost::getInvalid(); | |
| if (match(VI, m_CombineOr(m_FAdd(m_Value(), m_Value()), | |
| m_FSub(m_Value(), m_Value())))) | |
| FusedCost = GetFMulAddCost(InstructionsState(VI, VI), VI); | |
| else if (match(VI, m_FMul(m_Value(), m_Value()))) | |
| FusedCost = GetFusedFMulCost(VI); | |
| if (FusedCost.isValid()) | |
| return FusedCost; | |
| return TTI->getArithmeticInstrCost( | |
| VI->getOpcode(), VI->getType(), CostKind, | |
| TTI::getOperandInfo(VI->getOperand(0)), | |
| TTI::getOperandInfo(VI->getOperand(1)), | |
| {VI->getOperand(0), VI->getOperand(1)}, VI, TLI); | |
| }; | |
| // Need to clear CommonCost since the final shuffle cost is included into | |
| // vector cost. | |
| auto GetVectorCost = [&, &TTIRef = *TTI](InstructionCost) { | |
| // VecCost is equal to sum of the cost of creating 2 vectors | |
| // and the cost of creating shuffle. | |
| InstructionCost VecCost = 0; | |
| if (TryFindNodeWithEqualOperands()) { | |
| LLVM_DEBUG({ | |
| dbgs() << "SLP: diamond match for alternate node found.\n"; | |
| E->dump(); | |
| }); | |
| // No need to add new vector costs here since we're going to reuse | |
| // same main/alternate vector ops, just do different shuffling. | |
| } else if (Instruction::isBinaryOp(E->getOpcode())) { | |
| // Peeled alternate chains fold the operand columns into one pure | |
| // main-opcode chain and one pure alt-opcode chain, followed by a | |
| // single lane-select shuffle; a plain alternate node is a single | |
| // combine. Each combine is priced with the properties of the column | |
| // it folds in; the other operand is the running fold, which stays | |
| // constant while every folded column is constant (such combines | |
| // constant-fold away in codegen) and stays uniform while every | |
| // folded column is uniform. | |
| auto ChainCost = [&](unsigned Opcode) { | |
| InstructionCost Cost = 0; | |
| TTI::OperandValueInfo RunningInfo = | |
| TTI::getOperandInfo(E->getOperand(0)); | |
| for (unsigned Idx : seq<unsigned>(1, E->getNumOperands())) { | |
| TTI::OperandValueInfo ColInfo = | |
| TTI::getOperandInfo(E->getOperand(Idx)); | |
| if (!RunningInfo.isConstant() || !ColInfo.isConstant()) | |
| Cost += TTIRef.getArithmeticInstrCost( | |
| Opcode, VecTy, CostKind, RunningInfo, ColInfo, {}, | |
| Opcode == E->getOpcode() ? E->getMainOp() : E->getAltOp(), | |
| TLI); | |
| TTI::OperandValueKind Kind = TTI::OK_AnyValue; | |
| if (RunningInfo.isConstant() && ColInfo.isConstant()) | |
| Kind = RunningInfo.Kind == TTI::OK_UniformConstantValue && | |
| ColInfo.Kind == TTI::OK_UniformConstantValue | |
| ? TTI::OK_UniformConstantValue | |
| : TTI::OK_NonUniformConstantValue; | |
| else if (RunningInfo.isUniform() && ColInfo.isUniform()) | |
| Kind = TTI::OK_UniformValue; | |
| RunningInfo = {Kind, TTI::OP_None}; | |
| } | |
| return Cost; | |
| }; | |
| VecCost = ChainCost(E->getOpcode()) + ChainCost(E->getAltOpcode()); | |
| } else if (auto *CI0 = dyn_cast<CmpInst>(VL0)) { | |
| auto *MaskTy = getWidenedType(Builder.getInt1Ty(), VL.size()); | |
| TTI::OperandValueInfo Op0Info = TTI::getOperandInfo(E->getOperand(0)); | |
| TTI::OperandValueInfo Op1Info = TTI::getOperandInfo(E->getOperand(1)); | |
| VecCost = TTIRef.getCmpSelInstrCost(E->getOpcode(), VecTy, MaskTy, | |
| CI0->getPredicate(), CostKind, | |
| Op0Info, Op1Info, VL0); | |
| VecCost += TTIRef.getCmpSelInstrCost( | |
| E->getOpcode(), VecTy, MaskTy, | |
| cast<CmpInst>(E->getAltOp())->getPredicate(), CostKind, Op0Info, | |
| Op1Info, E->getAltOp()); | |
| } else { | |
| Type *SrcSclTy = E->getMainOp()->getOperand(0)->getType(); | |
| auto *SrcTy = getWidenedType(SrcSclTy, VL.size()); | |
| if (SrcSclTy->isIntegerTy() && ScalarTy->isIntegerTy()) { | |
| auto SrcIt = MinBWs.find(getOperandEntry(E, 0)); | |
| unsigned BWSz = DL->getTypeSizeInBits(ScalarTy); | |
| unsigned SrcBWSz = | |
| DL->getTypeSizeInBits(E->getMainOp()->getOperand(0)->getType()); | |
| if (SrcIt != MinBWs.end()) { | |
| SrcBWSz = SrcIt->second.first; | |
| SrcSclTy = IntegerType::get(SrcSclTy->getContext(), SrcBWSz); | |
| SrcTy = getWidenedType(SrcSclTy, VL.size()); | |
| } | |
| if (BWSz <= SrcBWSz) { | |
| if (BWSz < SrcBWSz) | |
| VecCost = TTIRef.getCastInstrCost( | |
| Instruction::Trunc, VecTy, SrcTy, | |
| GetCastContextHint(E->getMainOp()->getOperand(0)), CostKind); | |
| LLVM_DEBUG({ | |
| dbgs() | |
| << "SLP: alternate extension, which should be truncated.\n"; | |
| E->dump(); | |
| }); | |
| return VecCost; | |
| } | |
| } | |
| VecCost = TTIRef.getCastInstrCost( | |
| E->getOpcode(), VecTy, SrcTy, | |
| GetCastContextHint(E->getMainOp()->getOperand(0)), CostKind, | |
| E->getMainOp()); | |
| VecCost += TTIRef.getCastInstrCost( | |
| E->getAltOpcode(), VecTy, SrcTy, | |
| GetCastContextHint(E->getMainOp()->getOperand(0)), CostKind, | |
| E->getAltOp()); | |
| } | |
| SmallVector<int> Mask; | |
| E->buildAltOpShuffleMask( | |
| [&](Instruction *I) { | |
| assert(E->getMatchingMainOpOrAltOp(I) && | |
| "Unexpected main/alternate opcode"); | |
| return isAlternateInstruction(I, E->getMainOp(), E->getAltOp(), | |
| *TLI); | |
| }, | |
| Mask); | |
| VecCost += getShuffleCost(TTIRef, TargetTransformInfo::SK_PermuteTwoSrc, | |
| cast<VectorType>(FinalVecTy), CostKind, Mask); | |
| // Patterns like [fadd,fsub] can be combined into a single instruction | |
| // in x86. Reordering them into [fsub,fadd] blocks this pattern. So we | |
| // need to take into account their order when looking for the most used | |
| // order. Linearized chains emit no alternate-ops pattern. | |
| if (E->hasReassocScalars()) | |
| return VecCost; | |
| unsigned Opcode0 = E->getOpcode(); | |
| unsigned Opcode1 = E->getAltOpcode(); | |
| SmallBitVector OpcodeMask( | |
| getAltInstrMask(E->Scalars, ScalarTy, Opcode0, Opcode1)); | |
| // If this pattern is supported by the target then we consider the | |
| // order. | |
| if (TTIRef.isLegalAltInstr(cast<VectorType>(VecTy), Opcode0, Opcode1, | |
| OpcodeMask, E->Scalars)) { | |
| InstructionCost AltVecCost = | |
| TTIRef.getAltInstrCost(cast<VectorType>(VecTy), Opcode0, Opcode1, | |
| OpcodeMask, CostKind, E->Scalars); | |
| return AltVecCost < VecCost ? AltVecCost : VecCost; | |
| } | |
| // TODO: Check the reverse order too. | |
| return VecCost; | |
| }; | |
| if (Opts.slp_revec && !E->isAltShuffle()) | |
| return GetCostDiff( | |
| GetScalarCost, [&](InstructionCost) -> InstructionCost { | |
| // If a group uses mask in order, the shufflevector can be | |
| // eliminated by instcombine. Then the cost is 0. | |
| assert(isa<ShuffleVectorInst>(VL.front()) && | |
| "Not supported shufflevector usage."); | |
| auto *SV = cast<ShuffleVectorInst>(VL.front()); | |
| unsigned SVNumElements = | |
| cast<FixedVectorType>(SV->getOperand(0)->getType()) | |
| ->getNumElements(); | |
| unsigned GroupSize = SVNumElements / SV->getShuffleMask().size(); | |
| for (size_t I = 0, End = VL.size(); I != End; I += GroupSize) { | |
| ArrayRef<Value *> Group = VL.slice(I, GroupSize); | |
| int NextIndex = 0; | |
| if (!all_of(Group, [&](Value *V) { | |
| assert(isa<ShuffleVectorInst>(V) && | |
| "Not supported shufflevector usage."); | |
| auto *SV = cast<ShuffleVectorInst>(V); | |
| int Index; | |
| [[maybe_unused]] bool IsExtractSubvectorMask = | |
| SV->isExtractSubvectorMask(Index); | |
| assert(IsExtractSubvectorMask && | |
| "Not supported shufflevector usage."); | |
| if (NextIndex != Index) | |
| return false; | |
| NextIndex += SV->getShuffleMask().size(); | |
| return true; | |
| })) | |
| return getShuffleCost(*TTI, | |
| TargetTransformInfo::SK_PermuteSingleSrc, | |
| cast<VectorType>(VecTy), CostKind, | |
| calculateShufflevectorMask(E->Scalars)); | |
| } | |
| return TTI::TCC_Free; | |
| }); | |
| return GetCostDiffWithPeeled(GetScalarCost, GetVectorCost); | |
| } | |
| case Instruction::Freeze: | |
| return CommonCost; | |
| default: | |
| llvm_unreachable("Unknown instruction"); | |
| } | |
| } | |
| bool BoUpSLP::isFullyVectorizableTinyTree(bool ForReduction) const { | |
| LLVM_DEBUG(dbgs() << "SLP: Check whether the tree with height " | |
| << VectorizableTree.size() << " is fully vectorizable .\n"); | |
| auto &&AreVectorizableGathers = [this](const TreeEntry *TE, unsigned Limit) { | |
| SmallVector<int> Mask; | |
| return TE->isGather() && | |
| !any_of(TE->Scalars, | |
| [this](Value *V) { return EphValues.contains(V); }) && | |
| (allConstant(TE->Scalars) || isSplat(TE->Scalars) || | |
| TE->Scalars.size() < Limit || | |
| matchGatheredExtractedFields(TE->Scalars, *DL) || | |
| // Nodes with copyable lanes may mix in non-extract lanes, which | |
| // are not representable as a shuffle of the source vector. | |
| (((TE->hasState() && | |
| TE->getOpcode() == Instruction::ExtractElement && | |
| !TE->hasCopyableElements()) || | |
| all_of(TE->Scalars, IsaPred<ExtractElementInst, UndefValue>)) && | |
| isFixedVectorShuffle(TE->Scalars, Mask, AC, Cache)) || | |
| (TE->hasState() && TE->getOpcode() == Instruction::Load && | |
| !TE->isAltShuffle()) || | |
| any_of(TE->Scalars, IsaPred<LoadInst>)); | |
| }; | |
| // We only handle trees of heights 1 and 2. | |
| if (VectorizableTree.size() == 1 && | |
| (VectorizableTree[0]->State == TreeEntry::Vectorize || | |
| VectorizableTree[0]->State == TreeEntry::StridedVectorize || | |
| VectorizableTree[0]->State == TreeEntry::ExpandVectorize || | |
| VectorizableTree[0]->State == TreeEntry::CompressVectorize || | |
| VectorizableTree[0]->State == TreeEntry::BlendedLoadVectorize || | |
| (ForReduction && | |
| AreVectorizableGathers(VectorizableTree[0].get(), | |
| VectorizableTree[0]->Scalars.size()) && | |
| VectorizableTree[0]->getVectorFactor() > 2))) | |
| return true; | |
| if (VectorizableTree.size() != 2) | |
| return false; | |
| // Handle splat and all-constants stores. Also try to vectorize tiny trees | |
| // with the second gather nodes if they have less scalar operands rather than | |
| // the initial tree element (may be profitable to shuffle the second gather) | |
| // or they are extractelements, which form shuffle. | |
| if (VectorizableTree[0]->State == TreeEntry::Vectorize && | |
| AreVectorizableGathers(VectorizableTree[1].get(), | |
| VectorizableTree[0]->Scalars.size())) | |
| return true; | |
| // Gathering cost would be too much for tiny trees. | |
| if (VectorizableTree[0]->isGather() || | |
| (VectorizableTree[1]->isGather() && | |
| VectorizableTree[0]->State != TreeEntry::ScatterVectorize && | |
| VectorizableTree[0]->State != TreeEntry::StridedVectorize && | |
| VectorizableTree[0]->State != TreeEntry::ExpandVectorize && | |
| VectorizableTree[0]->State != TreeEntry::CompressVectorize && | |
| VectorizableTree[0]->State != TreeEntry::BlendedLoadVectorize)) | |
| return false; | |
| return true; | |
| } | |
| bool BoUpSLP::isTreeTinyAndNotFullyVectorizable(bool ForReduction) const { | |
| if (!DebugCounter::shouldExecute(VectorizedGraphs)) | |
| return true; | |
| // If we are revectorizing reduction, it may result in same reduction pattern | |
| // with shuffles as leaves of the reduction. Prevent SLP from revectorizing | |
| // that shuffle pattern. | |
| if (Opts.slp_revec && ForReduction && VectorizableTree.size() == 3 && | |
| VectorizableTree[0]->State == TreeEntry::Vectorize && | |
| VectorizableTree[0]->getOpcode() == Instruction::ShuffleVector && | |
| VectorizableTree[1]->isGather() && | |
| isSplat(VectorizableTree[1]->Scalars) && | |
| VectorizableTree[2]->isGather() && | |
| allConstant(VectorizableTree[2]->Scalars)) { | |
| LLVM_DEBUG(dbgs() << "SLP: Rejecting reduction tree with 3 nodes(shuffle " | |
| "as root and remaining are gather nodes).\n"); | |
| return true; | |
| } | |
| // Graph is empty - do nothing. | |
| if (VectorizableTree.empty()) { | |
| assert(ExternalUses.empty() && "We shouldn't have any external users"); | |
| return true; | |
| } | |
| // FIXME: support buildvector of the gather nodes with struct types. | |
| if (any_of(VectorizableTree, [&](const std::unique_ptr<TreeEntry> &TE) { | |
| return TE->isGather() && | |
| isa<StructType>(TE->Scalars.front()->getType()); | |
| })) { | |
| LLVM_DEBUG( | |
| dbgs() << "SLP: rejecting tree with buildvector struct values of size " | |
| << VectorizableTree.size() << ".\n"); | |
| return true; | |
| } | |
| // Cache values from the root node and the cost-threshold options to avoid | |
| // re-querying them inside hot predicates below. | |
| const unsigned TreeSize = VectorizableTree.size(); | |
| const TreeEntry &Front = getRootNode(); | |
| const bool FrontIsGather = Front.isGather(); | |
| const bool FrontHasState = Front.hasState(); | |
| const unsigned FrontOpcode = FrontHasState ? Front.getOpcode() : 0u; | |
| const bool ThresholdSet = Opts.slp_threshold.has_value(); | |
| const bool ThresholdNonNegative = getCostThreshold() >= 0; | |
| constexpr unsigned Limit = 4; | |
| constexpr unsigned LargeTree = 20; | |
| constexpr unsigned LimitTreeSize = 36; | |
| // The remaining size-1/size-<=MinTreeSize early bail-outs only apply to | |
| // non-reduction trees; group them under a single guard to avoid 3 separate | |
| // !ForReduction short-circuits when reducing. | |
| if (!ForReduction) { | |
| // Single gather node: bail out for ExtractElement or any node containing a | |
| // real Instruction scalar. | |
| if (TreeSize == 1 && FrontIsGather) { | |
| if (FrontHasState && FrontOpcode == Instruction::ExtractElement) | |
| return true; | |
| if (any_of(Front.Scalars, IsaPred<Instruction>)) | |
| return true; | |
| } | |
| if (TreeSize <= Opts.slp_min_tree_size && | |
| all_of(VectorizableTree, [](const std::unique_ptr<TreeEntry> &TE) { | |
| return TE->isGather() || TE->State == TreeEntry::SplitVectorize; | |
| })) | |
| return true; | |
| if (TreeSize == 1 && getCostThreshold() < 0 && FrontHasState && | |
| FrontOpcode == Instruction::ExtractElement && | |
| (Front.getVectorFactor() == 2 || | |
| all_of(make_isa_range<Instruction>(Front.Scalars), | |
| [&](Instruction *I) { | |
| return !areAllUsersVectorized(I, UserIgnoreList); | |
| }))) | |
| return true; | |
| } | |
| // No need to vectorize inserts of gathered values. | |
| if (TreeSize == 2 && isa<InsertElementInst>(Front.Scalars[0]) && | |
| VectorizableTree[1]->isGather() && | |
| (VectorizableTree[1]->getVectorFactor() <= 2 || | |
| !(isSplat(VectorizableTree[1]->Scalars) || | |
| allConstant(VectorizableTree[1]->Scalars)))) | |
| return true; | |
| // The tree with only 3 nodes, where 2 last are gathers/buildvectors, not | |
| // profitable for vectorization. | |
| if (TreeSize == 3 && getCostThreshold() == 0 && | |
| (!ForReduction || Front.getVectorFactor() <= 2) && | |
| all_of(ArrayRef(VectorizableTree).drop_front(), | |
| [&](const std::unique_ptr<TreeEntry> &TE) { | |
| return TE->isGather() && TE->getVectorFactor() <= Limit && | |
| !all_of( | |
| TE->Scalars, | |
| IsaPred<ExtractElementInst, UndefValue, Constant>); | |
| })) | |
| return true; | |
| // All remaining bail-out heuristics require !ForReduction. Group them under | |
| // a single guard so reduction trees skip them with one branch instead of one | |
| // per check. | |
| if (!ForReduction) { | |
| // The single vectorized node, fed by the buildvectors, only repacks the | |
| // scalars, the gathers and the extracts are paid for the one vector | |
| // instruction. The vector call replaces the several expensive scalar ones, | |
| // while the store merges the lanes instead of extracting them back. | |
| if (TreeSize > 1 && ThresholdNonNegative && | |
| (!FrontHasState || (FrontOpcode != Instruction::Call && | |
| FrontOpcode != Instruction::Store)) && | |
| count_if(VectorizableTree, | |
| [](const std::unique_ptr<TreeEntry> &TE) { | |
| return TE->State == TreeEntry::Vectorize; | |
| }) == 1 && | |
| all_of(VectorizableTree, [](const std::unique_ptr<TreeEntry> &TE) { | |
| return TE->State == TreeEntry::Vectorize || | |
| (TE->isGather() && !isSplat(TE->Scalars) && | |
| !all_of(TE->Scalars, | |
| IsaPred<ExtractElementInst, UndefValue, Constant>)); | |
| })) | |
| return true; | |
| // If the graph includes only PHI nodes and gathers, it is defnitely not | |
| // profitable for the vectorization, we can skip it, if the cost threshold | |
| // is default. The cost of vectorized PHI nodes is almost always 0 + the | |
| // cost of gathers/buildvectors. | |
| if (!ThresholdSet && | |
| all_of(VectorizableTree, [&](const std::unique_ptr<TreeEntry> &TE) { | |
| const bool IsGather = TE->isGather(); | |
| const bool HasState = TE->hasState(); | |
| const unsigned Op = HasState ? TE->getOpcode() : 0u; | |
| if (IsGather && (!HasState || Op != Instruction::ExtractElement) && | |
| static_cast<unsigned>( | |
| count_if(TE->Scalars, IsaPred<ExtractElementInst>)) <= Limit) | |
| return true; | |
| return HasState && Op == Instruction::PHI; | |
| })) | |
| return true; | |
| // Do not vectorize small tree of phis only, if all vector phis are also | |
| // gathered. | |
| if (ThresholdSet && TreeSize <= Limit) { | |
| bool HasVectorPhi = false; | |
| auto Compatible = [&](const std::unique_ptr<TreeEntry> &TE) { | |
| const bool IsGather = TE->isGather(); | |
| const bool HasState = TE->hasState(); | |
| const unsigned Op = HasState ? TE->getOpcode() : 0u; | |
| if (IsGather && (!HasState || Op != Instruction::ExtractElement) && | |
| static_cast<unsigned>( | |
| count_if(TE->Scalars, IsaPred<ExtractElementInst>)) <= Limit) | |
| return true; | |
| if (!HasState) | |
| return false; | |
| if (Op == Instruction::InsertElement) | |
| return true; | |
| if (Op != Instruction::PHI) | |
| return false; | |
| if (TE->State == TreeEntry::Vectorize) | |
| HasVectorPhi = true; | |
| return all_of(TE->Scalars, [&](Value *V) { | |
| return isa<PoisonValue>(V) || MustGather.contains(V); | |
| }); | |
| }; | |
| if (all_of(VectorizableTree, Compatible) && HasVectorPhi) | |
| return true; | |
| } | |
| // PHI nodes only and gathers cannot be vectorized, skip. | |
| if (ThresholdNonNegative) { | |
| const bool IsLargeTree = TreeSize >= LargeTree; | |
| bool HasSingleLoad = false; | |
| if (all_of(VectorizableTree, [&](const std::unique_ptr<TreeEntry> &TE) { | |
| const bool IsGather = TE->isGather(); | |
| const bool HasState = TE->hasState(); | |
| const unsigned Op = HasState ? TE->getOpcode() : 0u; | |
| // HasSingleLoad/PrevLoad are only consulted in the | |
| // IsLargeTree branch; skip the bookkeeping otherwise. | |
| if (IsLargeTree) { | |
| const bool PrevLoad = HasSingleLoad; | |
| HasSingleLoad |= | |
| HasState && !IsGather && | |
| (Op == Instruction::Load || TE->hasCopyableElements()) && | |
| (TE->getVectorFactor() > 2 || TE->ReorderIndices.empty()); | |
| if (HasState) { | |
| if (Op == Instruction::PHI) | |
| return true; | |
| if (TE->getVectorFactor() <= Limit && | |
| (Op == Instruction::Store || | |
| (Op == Instruction::Load && !PrevLoad))) | |
| return true; | |
| } | |
| } else if (HasState && Op == Instruction::PHI) { | |
| return true; | |
| } | |
| return IsGather && (!HasState || Op != Instruction::ExtractElement); | |
| })) | |
| return true; | |
| // Single non-phi vector node - skip the tree. | |
| if (TreeSize >= 5 && Front.getVectorFactor() <= 2 && | |
| Front.Scalars.front()->getType()->isIntegerTy()) { | |
| bool VectorNodeFound = false; | |
| bool AnyNonConst = false; | |
| if (all_of(VectorizableTree, | |
| [&](const std::unique_ptr<TreeEntry> &TE) { | |
| if (TE->State == TreeEntry::Vectorize && TE->hasState()) { | |
| const unsigned Op = TE->getOpcode(); | |
| if (Op == Instruction::PHI || | |
| !TE->ReorderIndices.empty()) | |
| return true; | |
| if (VectorNodeFound) | |
| return false; | |
| VectorNodeFound = true; | |
| return true; | |
| } | |
| // Once AnyNonConst is true, skip the O(n) allConstant | |
| // walk for subsequent entries. | |
| if (!AnyNonConst) | |
| AnyNonConst = !allConstant(TE->Scalars); | |
| return TE->isGather() || | |
| TE->State == TreeEntry::SplitVectorize; | |
| }) && | |
| AnyNonConst) | |
| return true; | |
| } | |
| } | |
| // Common predicate for "phis, buildvectors, split nodes and small nodes | |
| // with reuses" used by the two checks below. Cheap checks are evaluated | |
| // before expensive Scalars walks. | |
| auto IsBenignNode = [&](const TreeEntry &TE) { | |
| if (TE.State == TreeEntry::SplitVectorize) | |
| return true; | |
| const bool IsGather = TE.isGather(); | |
| const bool HasState = TE.hasState(); | |
| if (HasState) { | |
| const unsigned Op = TE.getOpcode(); | |
| if (Op == Instruction::PHI) | |
| return true; | |
| const unsigned ScalarsSize = TE.Scalars.size(); | |
| if (TE.Idx == 0 && ScalarsSize == 2 && Op == Instruction::ICmp && | |
| TreeSize > LimitTreeSize) | |
| return true; | |
| if (ScalarsSize == 2 && | |
| (!TE.ReuseShuffleIndices.empty() || !TE.ReorderIndices.empty() || | |
| TE.isAltShuffle())) | |
| return true; | |
| if (TE.hasCopyableElements() && | |
| static_cast<unsigned>(count_if( | |
| TE.Scalars, IsaPred<PHINode, Constant>)) >= ScalarsSize / 2) | |
| return true; | |
| } | |
| return IsGather && none_of(TE.Scalars, IsaPred<ExtractElementInst>); | |
| }; | |
| // If the tree contains only phis, buildvectors, split nodes and | |
| // small nodes with reuses, we can skip it. | |
| if (!ThresholdSet) { | |
| SmallVector<const TreeEntry *> StoreLoadNodes; | |
| unsigned NumGathers = 0; | |
| if (all_of(VectorizableTree, | |
| [&](const std::unique_ptr<TreeEntry> &TE) { | |
| const bool IsGather = TE->isGather(); | |
| if (!IsGather && TE->hasState()) { | |
| const unsigned Op = TE->getOpcode(); | |
| if (Op == Instruction::Load || Op == Instruction::Store) { | |
| StoreLoadNodes.push_back(TE.get()); | |
| return true; | |
| } | |
| } | |
| if (IsGather) | |
| ++NumGathers; | |
| return IsBenignNode(*TE); | |
| }) && | |
| (StoreLoadNodes.empty() || | |
| (TreeSize > LimitTreeSize * StoreLoadNodes.size() && | |
| (NumGathers > 0 || | |
| none_of(StoreLoadNodes, [&](const TreeEntry *TE) { | |
| return TE->getOpcode() == Instruction::Store || | |
| all_of(make_isa_range<LoadInst>(TE->Scalars), | |
| [&](LoadInst *LI) { | |
| return areAllUsersVectorized(LI); | |
| }); | |
| }))))) | |
| return true; | |
| } | |
| // If the tree contains only phis, buildvectors, split nodes and | |
| // small nodes with reuses, we can skip it. | |
| if (ThresholdNonNegative && TreeSize > LimitTreeSize) { | |
| const TreeEntry *VectorNode = nullptr; | |
| if (all_of(VectorizableTree, [&](const std::unique_ptr<TreeEntry> &TE) { | |
| if (!TE->isGather() && TE->hasState() && | |
| TE->State != TreeEntry::SplitVectorize && | |
| TE->getOpcode() != Instruction::PHI) { | |
| if (VectorNode) | |
| return false; | |
| VectorNode = TE.get(); | |
| return true; | |
| } | |
| return IsBenignNode(*TE); | |
| })) | |
| return true; | |
| } | |
| // If the tree contains only buildvector, 2 non-buildvectors (with root | |
| // user tree node) and other buildvectors, we can skip it. | |
| if (ThresholdSet && TreeSize >= Limit && | |
| Front.State == TreeEntry::SplitVectorize && | |
| count_if(ArrayRef(VectorizableTree).drop_front(), | |
| [](const std::unique_ptr<TreeEntry> &TE) { | |
| return !TE->isGather() && TE->UserTreeIndex.UserTE && | |
| TE->UserTreeIndex.UserTE->Idx == 0; | |
| }) == 2) | |
| return true; | |
| // If the tree contains only vectorization of the phi node from the | |
| // buildvector - skip it. | |
| if (ThresholdSet && TreeSize > 2 && Front.State == TreeEntry::Vectorize && | |
| FrontOpcode == Instruction::InsertElement && | |
| VectorizableTree[1]->State == TreeEntry::Vectorize && | |
| VectorizableTree[1]->getOpcode() == Instruction::PHI && | |
| all_of(ArrayRef(VectorizableTree).drop_front(2), | |
| [](const std::unique_ptr<TreeEntry> &TE) { | |
| return TE->isGather(); | |
| })) | |
| return true; | |
| } | |
| // We can vectorize the tree if its size is greater than or equal to the | |
| // minimum size specified by the MinTreeSize command line option. | |
| if (TreeSize >= Opts.slp_min_tree_size) | |
| return false; | |
| // If we have a tiny tree (a tree whose size is less than MinTreeSize), we | |
| // can vectorize it if we can prove it fully vectorizable. | |
| if (isFullyVectorizableTinyTree(ForReduction)) | |
| return false; | |
| // Check if any of the gather node forms an insertelement buildvector | |
| // somewhere. TreeSize >= 1 is guaranteed, so the multi-node case reduces to | |
| // a simple TreeSize > 1 short-circuit. | |
| // A gather with copyable lanes is not a real instruction node; do not let | |
| // its state qualify it as a buildvector-forming node. | |
| const bool IsAllowedSingleBVNode = | |
| TreeSize > 1 || | |
| (FrontHasState && !Front.isAltShuffle() && !Front.hasCopyableElements() && | |
| FrontOpcode != Instruction::PHI && | |
| FrontOpcode != Instruction::GetElementPtr && | |
| allSameBlock(Front.Scalars)); | |
| if (any_of(VectorizableTree, [&](const std::unique_ptr<TreeEntry> &TE) { | |
| return TE->isGather() && all_of(TE->Scalars, [&](Value *V) { | |
| return isa<ExtractElementInst, Constant>(V) || | |
| (IsAllowedSingleBVNode && | |
| !V->hasNUsesOrMore(UsesLimit) && | |
| any_of(V->users(), IsaPred<InsertElementInst>)); | |
| }); | |
| })) | |
| return false; | |
| const TreeEntry &Back = *VectorizableTree.back(); | |
| if (Back.isGather() && Back.hasState() && Back.isAltShuffle()) { | |
| const unsigned BackVF = Back.getVectorFactor(); | |
| if (BackVF > 2 && allSameBlock(Back.Scalars) && | |
| !Back.Scalars.front()->getType()->isVectorTy() && | |
| TTI->getScalarizationOverhead( | |
| cast<VectorType>( | |
| getWidenedType(Back.Scalars.front()->getType(), BackVF)), | |
| APInt::getAllOnes(BackVF), | |
| /*Insert=*/true, /*Extract=*/false, CostKind) > -getCostThreshold()) | |
| return false; | |
| } | |
| // Otherwise, we can't vectorize the tree. It is both tiny and not fully | |
| // vectorizable. | |
| return true; | |
| } | |
| bool BoUpSLP::isTreeNotExtendable() const { | |
| if (getCanonicalGraphSize() != getTreeSize()) { | |
| constexpr unsigned SmallTree = 3; | |
| if (getRootNode().isNonPowOf2Vec() && | |
| getCanonicalGraphSize() <= SmallTree && | |
| count_if(ArrayRef(VectorizableTree).drop_front(getCanonicalGraphSize()), | |
| [](const std::unique_ptr<TreeEntry> &TE) { | |
| return TE->isGather() && TE->hasState() && | |
| TE->getOpcode() == Instruction::Load && | |
| !allSameBlock(TE->Scalars); | |
| }) == 1) | |
| return true; | |
| return false; | |
| } | |
| bool Res = false; | |
| for (unsigned Idx : seq<unsigned>(getTreeSize())) { | |
| TreeEntry &E = *VectorizableTree[Idx]; | |
| if (E.State == TreeEntry::SplitVectorize) | |
| return false; | |
| if (!E.isGather()) | |
| continue; | |
| if ((E.hasState() && E.getOpcode() != Instruction::Load) || | |
| (!E.hasState() && | |
| all_of(E.Scalars, IsaPred<ExtractElementInst, LoadInst>)) || | |
| (isa<ExtractElementInst>(E.Scalars.front()) && | |
| getSameOpcode(ArrayRef(E.Scalars).drop_front(), *TLI).valid())) | |
| return false; | |
| if (isSplat(E.Scalars) || allConstant(E.Scalars)) | |
| continue; | |
| Res = true; | |
| } | |
| return Res; | |
| } | |
| InstructionCost BoUpSLP::getSpillCost() { | |
| // Walk the vectorizable tree from the root towards its leaves, tracking | |
| // which vectorized operand values would be live across each tree edge | |
| // (i.e. between the last instruction of an operand entry and the last | |
| // instruction of its user entry). When the live range crosses a call | |
| // instruction that is not part of the vectorized tree, query TTI for the | |
| // cost of keeping the value live across it (for example, if spills and | |
| // fills are required). | |
| const TreeEntry *Root = &getRootNode(); | |
| if (Root->isGather()) | |
| return 0; | |
| InstructionCost Cost = 0; | |
| SmallDenseMap<const TreeEntry *, SmallVector<const TreeEntry *>> | |
| EntriesToOperands; | |
| SmallDenseMap<const TreeEntry *, Instruction *> EntriesToLastInstruction; | |
| SmallPtrSet<const Instruction *, 8> LastInstructions; | |
| SmallPtrSet<const TreeEntry *, 8> ScalarOrPseudoEntries; | |
| for (const auto &TEPtr : VectorizableTree) { | |
| if (TEPtr->CombinedOp == TreeEntry::ReducedBitcast || | |
| TEPtr->CombinedOp == TreeEntry::ReducedBitcastBSwap || | |
| TEPtr->CombinedOp == TreeEntry::ReducedBitcastLoads || | |
| TEPtr->CombinedOp == TreeEntry::ReducedBitcastBSwapLoads || | |
| TEPtr->CombinedOp == TreeEntry::ReducedBitPack || | |
| TEPtr->CombinedOp == TreeEntry::ReducedCmpBitcast) { | |
| ScalarOrPseudoEntries.insert(TEPtr.get()); | |
| continue; | |
| } | |
| if (!TEPtr->isGather()) { | |
| Instruction *LastInst = &getLastInstructionInBundle(TEPtr.get()); | |
| EntriesToLastInstruction.try_emplace(TEPtr.get(), LastInst); | |
| LastInstructions.insert(LastInst); | |
| } | |
| if (TEPtr->UserTreeIndex) | |
| EntriesToOperands[TEPtr->UserTreeIndex.UserTE].push_back(TEPtr.get()); | |
| } | |
| // Cache NoCallIntrinsicOrDoesNotReturn results - the same intrinsic call may | |
| // be queried many times during the spill cost scan, and each computation | |
| // involves two potentially expensive TTI virtual calls. | |
| SmallDenseMap<const IntrinsicInst *, bool> NoCallIntrinsicCache; | |
| auto NoCallIntrinsicOrDoesNotReturn = | |
| [this, &NoCallIntrinsicCache](const Instruction *I) { | |
| const auto *CB = dyn_cast<CallBase>(I); | |
| if (!CB) | |
| return false; | |
| if (CB->doesNotReturn()) | |
| return true; | |
| const auto *II = dyn_cast<IntrinsicInst>(CB); | |
| if (!II) | |
| return false; | |
| if (II->isAssumeLikeIntrinsic()) | |
| return true; | |
| auto [It, Inserted] = NoCallIntrinsicCache.try_emplace(II); | |
| if (!Inserted) | |
| return It->second; | |
| IntrinsicCostAttributes ICA(II->getIntrinsicID(), *II); | |
| InstructionCost IntrCost = TTI->getIntrinsicInstrCost(ICA, CostKind); | |
| InstructionCost CallCost = TTI->getCallInstrCost( | |
| nullptr, II->getType(), ICA.getArgTypes(), CostKind); | |
| bool Res = IntrCost < CallCost; | |
| It->second = Res; | |
| return Res; | |
| }; | |
| // Maps last instruction in the entry to the last instruction for the one of | |
| // operand entries and the flag. If the flag is true, there are no calls in | |
| // between these instructions. | |
| SmallDenseMap<const Instruction *, PointerIntPair<const Instruction *, 1>> | |
| CheckedInstructions; | |
| unsigned Budget = 0; | |
| const unsigned BudgetLimit = | |
| Opts.slp_schedule_budget / VectorizableTree.size(); | |
| auto CheckForNonVecCallsInSameBlock = [&](Instruction *First, | |
| const Instruction *Last) { | |
| assert(First->getParent() == Last->getParent() && | |
| "Expected instructions in same block."); | |
| if (auto It = CheckedInstructions.find(Last); | |
| It != CheckedInstructions.end()) { | |
| const Instruction *Checked = It->second.getPointer(); | |
| const bool NoCallsInCachedRange = It->second.getInt() != 0; | |
| if (Checked == First) | |
| return NoCallsInCachedRange; | |
| if (Checked->comesBefore(First)) | |
| // In every cached state (full clean scan, call-found, or | |
| // budget-exhausted) the region strictly above `Checked` up to `Last` | |
| // was inspected and proved call-free. Since `First` is above | |
| // `Checked`, the queried range [First, Last] is contained in that | |
| // call-free region, regardless of whether bit is 0 or 1. | |
| return true; | |
| Last = Checked; | |
| } else if (Last == First || Last->comesBefore(First)) { | |
| // Empty range. | |
| return true; | |
| } | |
| BasicBlock::const_reverse_iterator InstIt = | |
| ++First->getIterator().getReverse(), | |
| PrevInstIt = | |
| Last->getIterator().getReverse(); | |
| SmallVector<const Instruction *> LastInstsInRange; | |
| while (InstIt != PrevInstIt && Budget <= BudgetLimit) { | |
| // Debug information does not impact spill cost. | |
| // Vectorized calls, represented as vector intrinsics, do not impact spill | |
| // cost. | |
| if (const auto *CB = dyn_cast<CallBase>(&*PrevInstIt); | |
| CB && !NoCallIntrinsicOrDoesNotReturn(CB) && !isVectorized(CB)) { | |
| for (const Instruction *LastInst : LastInstsInRange) | |
| CheckedInstructions.try_emplace(LastInst, &*PrevInstIt, 0); | |
| return false; | |
| } | |
| if (LastInstructions.contains(&*PrevInstIt)) | |
| LastInstsInRange.push_back(&*PrevInstIt); | |
| ++PrevInstIt; | |
| ++Budget; | |
| } | |
| // If we reached the scan's lower bound (`PrevInstIt == InstIt`) then the | |
| // whole [First, Last] range was inspected and found call-free, even if | |
| // Budget just overflowed at the very last step; do not mislabel such a | |
| // completed scan as "has call". | |
| const bool Completed = PrevInstIt == InstIt; | |
| const bool NoCallsInRange = Completed || Budget <= BudgetLimit; | |
| for (const Instruction *LastInst : LastInstsInRange) | |
| CheckedInstructions.try_emplace( | |
| LastInst, Completed ? First : &*PrevInstIt, NoCallsInRange ? 1 : 0); | |
| return NoCallsInRange; | |
| }; | |
| // The spill/reload is executed once per execution of the call, so the | |
| // cost is scaled by the trip count of the loop containing the call, even | |
| // for hoisted loop-invariant values defined outside of it. | |
| auto GetSpillScale = [&](const BasicBlock *BB) { | |
| return getLoopNestScale(LI->getLoopFor(BB)); | |
| }; | |
| auto AddCosts = [&](const TreeEntry *Op, uint64_t Scale) { | |
| if (ScalarOrPseudoEntries.contains(Op)) | |
| return; | |
| Type *ScalarTy = Op->Scalars.front()->getType(); | |
| auto It = MinBWs.find(Op); | |
| if (It != MinBWs.end()) | |
| ScalarTy = IntegerType::get(ScalarTy->getContext(), It->second.first); | |
| auto *VecTy = getWidenedType(ScalarTy, Op->getVectorFactor()); | |
| InstructionCost KeepLiveCost = TTI->getCostOfKeepingLiveOverCall(VecTy); | |
| KeepLiveCost *= Scale; | |
| Cost += KeepLiveCost; | |
| if (ScalarTy->isVectorTy()) { | |
| // Handle revec dead vector instructions. | |
| Cost -= Op->Scalars.size() * TTI->getCostOfKeepingLiveOverCall(ScalarTy) * | |
| Scale; | |
| } | |
| }; | |
| // Memoize the relationship between blocks, i.e. the spill scale if every | |
| // path between the blocks crosses a non-vectorized call, 0 if there is (at | |
| // least one) call-free path. This allows to skip the analysis of the same | |
| // block paths multiple times. | |
| SmallDenseMap<std::pair<const BasicBlock *, const BasicBlock *>, uint64_t> | |
| ParentOpParentToPreds; | |
| // Memoize whether a basic block contains a non-terminator no-return call. | |
| // Such blocks are dead-end paths in normal control flow (execution does not | |
| // exit them past the no-return call), so the block is excluded from the | |
| // spill cost analysis. Terminator no-return calls (invoke/callbr) are not | |
| // block-killing because they still have live CFG successors (e.g. the | |
| // unwind destination of an invoke). | |
| SmallDenseMap<const BasicBlock *, bool> BlockHasNoReturnCallCache; | |
| auto BlockHasNoReturnCall = [&](const BasicBlock *BB) { | |
| auto [It, Inserted] = BlockHasNoReturnCallCache.try_emplace(BB, false); | |
| if (!Inserted) | |
| return It->second; | |
| for (const CallBase &CB : make_isa_range<CallBase>(*BB)) { | |
| if (CB.doesNotReturn() && !CB.isTerminator()) { | |
| It->second = true; | |
| return true; | |
| } | |
| } | |
| return false; | |
| }; | |
| // Memoize whether a loop's body (all blocks of the loop, including | |
| // sub-loops) contains any non-vec call. | |
| SmallDenseMap<const Loop *, bool> LoopBodyHasNonVecCall; | |
| auto LoopBodyHasCall = [&](const Loop *L) { | |
| if (auto It = LoopBodyHasNonVecCall.find(L); | |
| It != LoopBodyHasNonVecCall.end()) | |
| return It->second; | |
| auto IsNotCatchSwitch = [](BasicBlock *BB) { | |
| return !isa<CatchSwitchInst>(BB->getTerminator()); | |
| }; | |
| for (BasicBlock *BB : make_filter_range(L->blocks(), IsNotCatchSwitch)) { | |
| // Blocks containing a no-return call are dead-end paths and never | |
| // actually flow back through the loop's back-edge, so their calls do | |
| // not keep loop-invariant vector values live across calls. | |
| if (BlockHasNoReturnCall(BB)) | |
| continue; | |
| for (const Instruction &I : *BB) { | |
| const auto *CB = dyn_cast<CallBase>(&I); | |
| if (!CB || NoCallIntrinsicOrDoesNotReturn(CB) || isVectorized(CB)) | |
| continue; | |
| LoopBodyHasNonVecCall.try_emplace(L, true); | |
| return true; | |
| } | |
| } | |
| LoopBodyHasNonVecCall.try_emplace(L, false); | |
| return false; | |
| }; | |
| auto GetLoopInvariantSpillRegion = [&](BasicBlock *UseBB, | |
| BasicBlock *DefBB) -> const Loop * { | |
| // If DefBB is outside a loop containing UseBB, the value is live across | |
| // iterations of that loop. A non-vectorized call in the loop may therefore | |
| // require a spill even when the entry path from DefBB to UseBB is | |
| // call-free. Return the outermost such loop containing a relevant call so | |
| // its execution scale can be used to cost the spill. | |
| const Loop *L = LI->getLoopFor(UseBB); | |
| const Loop *Outermost = nullptr; | |
| while (L && !L->contains(DefBB)) { | |
| Outermost = L; | |
| L = L->getParentLoop(); | |
| } | |
| return (Outermost && LoopBodyHasCall(Outermost)) ? Outermost : nullptr; | |
| }; | |
| auto CheckPredecessors = [&](BasicBlock *Root, BasicBlock *Pred, | |
| BasicBlock *OpParent) { | |
| auto Key = std::make_pair(Root, OpParent); | |
| if (auto It = ParentOpParentToPreds.find(Key); | |
| It != ParentOpParentToPreds.end()) | |
| return It->second; | |
| uint64_t Res = 0; | |
| scope_exit Cleanup([&]() { ParentOpParentToPreds.try_emplace(Key, Res); }); | |
| if (const auto *L = GetLoopInvariantSpillRegion(Root, OpParent)) { | |
| Res = getLoopNestScale(L); | |
| return Res; | |
| } | |
| SmallVector<BasicBlock *> Worklist; | |
| if (Pred) | |
| Worklist.push_back(Pred); | |
| else | |
| Worklist.append(pred_begin(Root), pred_end(Root)); | |
| SmallPtrSet<const BasicBlock *, 16> Visited; | |
| // With "at least one call-free path" semantics we can only reliably | |
| // memoize the exact (Root, OpParent) query. Pairs for intermediate | |
| // blocks that were visited during the BFS are not necessarily | |
| // call-free-reachable to OpParent themselves - we may have reached | |
| // OpParent through a *sibling* path that bypassed them. | |
| // We return 0 (no spill cost) if at least one backward path from | |
| // some predecessor of Root back to OpParent is call-free. Only when | |
| // *every* such path goes through a non-vec call do we charge the spill | |
| // cost: only then is it actually necessary to keep the vectorized value | |
| // live across a call and therefore spill/reload it. | |
| // | |
| // A BB is only explored further (its predecessors added to the worklist) | |
| // when it is itself call-free and not strictly dominated by Root (blocks | |
| // dominated by Root are only reachable via loop back-edges - they sit | |
| // *after* Root in forward execution and must not be counted). | |
| // | |
| // If we ever pop OpParent from the worklist, we have reached it through | |
| // a chain of call-free, non-dominated blocks: a call-free path exists | |
| // and we return 0. If the worklist is exhausted or the scan budget | |
| // overflows without reaching OpParent, no call-free path was found and | |
| // we return the scale of the loop containing Root, so the caller charges | |
| // the spill cost. | |
| while (!Worklist.empty()) { | |
| BasicBlock *BB = Worklist.pop_back_val(); | |
| if (BB == OpParent) | |
| return Res; | |
| if (!Visited.insert(BB).second) | |
| continue; | |
| // Blocks strictly dominated by Root are reached only *after* Root in | |
| // forward execution (via loop back-edges); skip them and their | |
| // dominated predecessors. | |
| if (DT->properlyDominates(Root, BB)) | |
| continue; | |
| // A block containing a no-return call cannot reach Root via the | |
| // forward edge being analyzed: execution does not continue past the | |
| // no-return call, so the BB -> ... -> Root path is dead. Drop the | |
| // block from the analysis without following its predecessors. | |
| if (BlockHasNoReturnCall(BB)) | |
| continue; | |
| auto Pair = std::make_pair(BB, OpParent); | |
| if (auto It = ParentOpParentToPreds.find(Pair); | |
| It != ParentOpParentToPreds.end()) { | |
| // BB is known to reach OpParent via a call-free path. | |
| if (It->second == 0) | |
| return Res; | |
| // BB is known to be blocked from OpParent by calls; keep checking | |
| // other paths. | |
| continue; | |
| } | |
| unsigned BlockSize = BB->size(); | |
| if (BlockSize > static_cast<unsigned>(Opts.slp_schedule_budget)) | |
| continue; | |
| Budget += BlockSize; | |
| if (Budget > BudgetLimit) | |
| break; | |
| if (!isa<CatchSwitchInst>(BB->getTerminator()) && | |
| !CheckForNonVecCallsInSameBlock(&*BB->getFirstNonPHIOrAlloca(), | |
| BB->getTerminator())) | |
| continue; | |
| Worklist.append(pred_begin(BB), pred_end(BB)); | |
| } | |
| Res = GetSpillScale(Root); | |
| return Res; | |
| }; | |
| SmallVector<const TreeEntry *> LiveEntries(1, Root); | |
| auto FindNonScalarParentEntry = [&](const TreeEntry *E) -> const TreeEntry * { | |
| assert(ScalarOrPseudoEntries.contains(E) && | |
| "Expected scalar or pseudo entry."); | |
| const TreeEntry *Entry = E; | |
| while (Entry->UserTreeIndex) { | |
| Entry = Entry->UserTreeIndex.UserTE; | |
| if (!ScalarOrPseudoEntries.contains(Entry)) | |
| return Entry; | |
| } | |
| return nullptr; | |
| }; | |
| auto IsCoveredByMatchingVectorEntry = [&](const TreeEntry *Gather, | |
| const Loop *SpillLoop) -> bool { | |
| assert(Gather->isGather() && "Expected a gather/buildvector entry."); | |
| Value *LookupValue = nullptr; | |
| if (Gather->hasState()) { | |
| LookupValue = Gather->getMainOp(); | |
| } else { | |
| auto *It = find_if_not(Gather->Scalars, IsaPred<UndefValue>); | |
| if (It == Gather->Scalars.end()) | |
| return false; | |
| LookupValue = *It; | |
| } | |
| // Find the real vector entry reused by this perfect-diamond gather. | |
| const TreeEntry *SameTE = | |
| getSameValuesTreeEntry(LookupValue, Gather->Scalars, /*SameVF=*/true); | |
| if (!SameTE || SameTE == Gather || SameTE->State != TreeEntry::Vectorize || | |
| ScalarOrPseudoEntries.contains(SameTE) || !SameTE->UserTreeIndex) | |
| return false; | |
| // A reordered or reuse-shuffled match materializes a separate shuffled | |
| // vector that is itself live across the call; only an exact match reuses | |
| // the same vector value. | |
| if (!SameTE->ReorderIndices.empty() || !SameTE->ReuseShuffleIndices.empty()) | |
| return false; | |
| // Only permit an ordinary vectorized user. | |
| const TreeEntry *UserTE = SameTE->UserTreeIndex.UserTE; | |
| assert(UserTE && "Expected a user tree entry."); | |
| if (UserTE->State != TreeEntry::Vectorize || | |
| ScalarOrPseudoEntries.contains(UserTE) || | |
| UserTE->getOpcode() == Instruction::PHI) | |
| return false; | |
| // Different demoted bitwidth would make the two edge costs unequal. | |
| if (MinBWs.lookup(SameTE).first != MinBWs.lookup(Gather).first) | |
| return false; | |
| // The spill walk does not descend through gather entries; if any ancestor | |
| // of the matching entry's user is a gather or a combined scalar/pseudo | |
| // entry, the matching edge is never charged and de-duplicating would lose | |
| // the spill cost entirely. | |
| for (const TreeEntry *E = UserTE; E != Root;) { | |
| if (!E->UserTreeIndex) | |
| return false; | |
| E = E->UserTreeIndex.UserTE; | |
| if (E->isGather() || ScalarOrPseudoEntries.contains(E)) | |
| return false; | |
| } | |
| Instruction *Def = EntriesToLastInstruction.at(SameTE); | |
| Instruction *Use = EntriesToLastInstruction.at(UserTE); | |
| // Require the matching entry to be defined outside the loop and its use to | |
| // execute directly in the same loop as the gather user. | |
| if (SpillLoop->contains(Def->getParent()) || | |
| LI->getLoopFor(Use->getParent()) != SpillLoop) | |
| return false; | |
| // Ensure that the matching entry is also loop invariant. | |
| if (!all_of(make_isa_range<Instruction>(SameTE->Scalars), | |
| [&](Instruction *I) { return SpillLoop->isLoopInvariant(I); })) | |
| return false; | |
| // A non-vec call between Def and the end of its block preempts the | |
| // loop-invariant charge for the matching entry's edge with a smaller | |
| // scale, so de-duplicating would drop the in-loop spill cost. | |
| if (!CheckForNonVecCallsInSameBlock(Def, Def->getParent()->getTerminator())) | |
| return false; | |
| return GetLoopInvariantSpillRegion(Use->getParent(), Def->getParent()) == | |
| SpillLoop; | |
| }; | |
| while (!LiveEntries.empty()) { | |
| const TreeEntry *Entry = LiveEntries.pop_back_val(); | |
| const auto OpIt = EntriesToOperands.find(Entry); | |
| if (OpIt == EntriesToOperands.end()) | |
| continue; | |
| ArrayRef<const TreeEntry *> Operands = OpIt->second; | |
| if (Operands.empty()) | |
| continue; | |
| if (ScalarOrPseudoEntries.contains(Entry)) { | |
| Entry = FindNonScalarParentEntry(Entry); | |
| if (!Entry) { | |
| for (const TreeEntry *Op : Operands) { | |
| if (!Op->isGather()) | |
| LiveEntries.push_back(Op); | |
| } | |
| continue; | |
| } | |
| } | |
| Instruction *LastInst = EntriesToLastInstruction.at(Entry); | |
| BasicBlock *Parent = LastInst->getParent(); | |
| for (const TreeEntry *Op : Operands) { | |
| if (!Op->isGather()) | |
| LiveEntries.push_back(Op); | |
| if (ScalarOrPseudoEntries.contains(Op)) | |
| continue; | |
| if (Entry->State == TreeEntry::SplitVectorize || | |
| (Op->isGather() && allConstant(Op->Scalars))) | |
| continue; | |
| // Reset the scan budget for analysis of each edge. | |
| Budget = 0; | |
| // A gather with all loop-invariant lanes is hoisted to the loop | |
| // preheader by optimizeGatherSequence, so its vector value becomes live | |
| // across any non-vectorized call in the loop body. Charge it like any | |
| // other vectorized value live over a call instead of skipping it. | |
| if (Entry->hasState() && Entry->getOpcode() != Instruction::PHI && | |
| Op->isGather()) { | |
| if (const Loop *L = LI->getLoopFor(Parent); | |
| L && L->getLoopPreheader() && LoopBodyHasCall(L) && | |
| all_of(make_isa_range<Instruction>(Op->Scalars), | |
| [&](Instruction *I) { return L->isLoopInvariant(I); })) | |
| if (!IsCoveredByMatchingVectorEntry(Op, L)) | |
| AddCosts(Op, GetSpillScale(Parent)); | |
| continue; | |
| } | |
| BasicBlock *Pred = nullptr; | |
| if (auto *Phi = dyn_cast<PHINode>(Entry->getMainOp())) | |
| Pred = Phi->getIncomingBlock(Op->UserTreeIndex.EdgeIdx); | |
| BasicBlock *OpParent; | |
| Instruction *OpLastInst; | |
| if (Op->isGather()) { | |
| assert(Entry->getOpcode() == Instruction::PHI && | |
| "Expected phi node only."); | |
| OpParent = cast<PHINode>(Entry->getMainOp()) | |
| ->getIncomingBlock(Op->UserTreeIndex.EdgeIdx); | |
| OpLastInst = OpParent->getTerminator(); | |
| for (Instruction *Inst : make_isa_range<Instruction>(Op->Scalars)) { | |
| if (isVectorized(Inst)) { | |
| OpParent = Inst->getParent(); | |
| OpLastInst = Inst; | |
| break; | |
| } | |
| } | |
| } else { | |
| OpLastInst = EntriesToLastInstruction.at(Op); | |
| OpParent = OpLastInst->getParent(); | |
| } | |
| // Check the call instructions within the same basic blocks. | |
| if (OpParent == Parent) { | |
| if (Entry->getOpcode() == Instruction::PHI) { | |
| if (!CheckForNonVecCallsInSameBlock(LastInst, OpLastInst)) | |
| AddCosts(Op, GetSpillScale(Parent)); | |
| continue; | |
| } | |
| if (!CheckForNonVecCallsInSameBlock(OpLastInst, LastInst)) | |
| AddCosts(Op, GetSpillScale(Parent)); | |
| continue; | |
| } | |
| // Check for call instruction in between blocks. | |
| // 1. Check entry's block to the head. | |
| if (Entry->getOpcode() != Instruction::PHI && | |
| !CheckForNonVecCallsInSameBlock(&*Parent->getFirstNonPHIOrAlloca(), | |
| LastInst)) { | |
| AddCosts(Op, GetSpillScale(Parent)); | |
| continue; | |
| } | |
| // 2. Check op's block from the end. | |
| if (!CheckForNonVecCallsInSameBlock(OpLastInst, | |
| OpParent->getTerminator())) { | |
| AddCosts(Op, GetSpillScale(OpParent)); | |
| continue; | |
| } | |
| // 3. Check the predecessors of entry's block till op's block. | |
| if (uint64_t Scale = CheckPredecessors(Parent, Pred, OpParent)) | |
| AddCosts(Op, Scale); | |
| } | |
| } | |
| return Cost; | |
| } | |
| namespace { | |
| /// Returns incoming Value *, if the requested type is Value * too, or a default | |
| /// value, otherwise. | |
| struct ValueSelect { | |
| template <typename U> | |
| static std::enable_if_t<std::is_same_v<Value *, U>, Value *> get(Value *V) { | |
| return V; | |
| } | |
| template <typename U> | |
| static std::enable_if_t<!std::is_same_v<Value *, U>, U> get(Value *) { | |
| return U(); | |
| } | |
| }; | |
| } // namespace | |
| /// Does the analysis of the provided shuffle masks and performs the requested | |
| /// actions on the vectors with the given shuffle masks. It tries to do it in | |
| /// several steps. | |
| /// 1. If the Base vector is not undef vector, resizing the very first mask to | |
| /// have common VF and perform action for 2 input vectors (including non-undef | |
| /// Base). Other shuffle masks are combined with the resulting after the 1 stage | |
| /// and processed as a shuffle of 2 elements. | |
| /// 2. If the Base is undef vector and have only 1 shuffle mask, perform the | |
| /// action only for 1 vector with the given mask, if it is not the identity | |
| /// mask. | |
| /// 3. If > 2 masks are used, perform the remaining shuffle actions for 2 | |
| /// vectors, combing the masks properly between the steps. | |
| template <typename T> | |
| static T *performExtractsShuffleAction( | |
| MutableArrayRef<std::pair<T *, SmallVector<int>>> ShuffleMask, Value *Base, | |
| function_ref<unsigned(T *)> GetVF, | |
| function_ref<std::pair<T *, bool>(T *, ArrayRef<int>, bool)> ResizeAction, | |
| function_ref<T *(ArrayRef<int>, ArrayRef<T *>)> Action) { | |
| assert(!ShuffleMask.empty() && "Empty list of shuffles for inserts."); | |
| SmallVector<int> Mask(ShuffleMask.begin()->second); | |
| auto VMIt = std::next(ShuffleMask.begin()); | |
| T *Prev = nullptr; | |
| SmallBitVector UseMask = | |
| buildUseMask(Mask.size(), Mask, UseMask::UndefsAsMask); | |
| SmallBitVector IsBaseUndef = isUndefVector(Base, UseMask); | |
| if (!IsBaseUndef.all()) { | |
| // Base is not undef, need to combine it with the next subvectors. | |
| std::pair<T *, bool> Res = | |
| ResizeAction(ShuffleMask.begin()->first, Mask, /*ForSingleMask=*/false); | |
| SmallBitVector IsBasePoison = isUndefVector<true>(Base, UseMask); | |
| for (unsigned Idx = 0, VF = Mask.size(); Idx < VF; ++Idx) { | |
| if (Mask[Idx] == PoisonMaskElem) | |
| Mask[Idx] = IsBasePoison.test(Idx) ? PoisonMaskElem : Idx; | |
| else | |
| Mask[Idx] = (Res.second ? Idx : Mask[Idx]) + VF; | |
| } | |
| [[maybe_unused]] auto *V = ValueSelect::get<T *>(Base); | |
| assert((!V || GetVF(V) == Mask.size()) && | |
| "Expected base vector of VF number of elements."); | |
| Prev = Action(Mask, {nullptr, Res.first}); | |
| } else if (ShuffleMask.size() == 1) { | |
| // Base is undef and only 1 vector is shuffled - perform the action only for | |
| // single vector, if the mask is not the identity mask. | |
| std::pair<T *, bool> Res = ResizeAction(ShuffleMask.begin()->first, Mask, | |
| /*ForSingleMask=*/true); | |
| if (Res.second) | |
| // Identity mask is found. | |
| Prev = Res.first; | |
| else | |
| Prev = Action(Mask, {ShuffleMask.begin()->first}); | |
| } else { | |
| // Base is undef and at least 2 input vectors shuffled - perform 2 vectors | |
| // shuffles step by step, combining shuffle between the steps. | |
| unsigned Vec1VF = GetVF(ShuffleMask.begin()->first); | |
| unsigned Vec2VF = GetVF(VMIt->first); | |
| if (Vec1VF == Vec2VF) { | |
| // No need to resize the input vectors since they are of the same size, we | |
| // can shuffle them directly. | |
| ArrayRef<int> SecMask = VMIt->second; | |
| for (unsigned I = 0, VF = Mask.size(); I < VF; ++I) { | |
| if (SecMask[I] != PoisonMaskElem) { | |
| assert(Mask[I] == PoisonMaskElem && "Multiple uses of scalars."); | |
| Mask[I] = SecMask[I] + Vec1VF; | |
| } | |
| } | |
| Prev = Action(Mask, {ShuffleMask.begin()->first, VMIt->first}); | |
| } else { | |
| // Vectors of different sizes - resize and reshuffle. | |
| std::pair<T *, bool> Res1 = ResizeAction(ShuffleMask.begin()->first, Mask, | |
| /*ForSingleMask=*/false); | |
| std::pair<T *, bool> Res2 = | |
| ResizeAction(VMIt->first, VMIt->second, /*ForSingleMask=*/false); | |
| ArrayRef<int> SecMask = VMIt->second; | |
| for (unsigned I = 0, VF = Mask.size(); I < VF; ++I) { | |
| if (Mask[I] != PoisonMaskElem) { | |
| assert(SecMask[I] == PoisonMaskElem && "Multiple uses of scalars."); | |
| if (Res1.second) | |
| Mask[I] = I; | |
| } else if (SecMask[I] != PoisonMaskElem) { | |
| assert(Mask[I] == PoisonMaskElem && "Multiple uses of scalars."); | |
| Mask[I] = (Res2.second ? I : SecMask[I]) + VF; | |
| } | |
| } | |
| Prev = Action(Mask, {Res1.first, Res2.first}); | |
| } | |
| VMIt = std::next(VMIt); | |
| } | |
| [[maybe_unused]] bool IsBaseNotUndef = !IsBaseUndef.all(); | |
| // Perform requested actions for the remaining masks/vectors. | |
| for (auto E = ShuffleMask.end(); VMIt != E; ++VMIt) { | |
| // Shuffle other input vectors, if any. | |
| std::pair<T *, bool> Res = | |
| ResizeAction(VMIt->first, VMIt->second, /*ForSingleMask=*/false); | |
| ArrayRef<int> SecMask = VMIt->second; | |
| for (unsigned I = 0, VF = Mask.size(); I < VF; ++I) { | |
| if (SecMask[I] != PoisonMaskElem) { | |
| assert((Mask[I] == PoisonMaskElem || IsBaseNotUndef) && | |
| "Multiple uses of scalars."); | |
| Mask[I] = (Res.second ? I : SecMask[I]) + VF; | |
| } else if (Mask[I] != PoisonMaskElem) { | |
| Mask[I] = I; | |
| } | |
| } | |
| Prev = Action(Mask, {Prev, Res.first}); | |
| } | |
| return Prev; | |
| } | |
| void BoUpSLP::detectBooleanizedNodes() { | |
| // An absorbable boolean leaf is a one-use trunc of a wide value or a | |
| // one-use zero-test of a value from [0, 1]; bit 0 of the wide value is the | |
| // i1 lane value in both cases. | |
| auto GetBoolLeafWideTy = [&](const TreeEntry *OpE) -> Type * { | |
| if (OpE->State != TreeEntry::Vectorize || OpE->isAltShuffle() || | |
| OpE->hasCopyableElements() || !OpE->ReuseShuffleIndices.empty() || | |
| OpE->CombinedOp != TreeEntry::NotCombinedOp) | |
| return nullptr; | |
| unsigned WideOpIdx; | |
| bool IsZeroTest = OpE->getOpcode() == Instruction::ICmp; | |
| if (OpE->getOpcode() == Instruction::Trunc) { | |
| if (!all_of(OpE->Scalars, [](Value *V) { | |
| return match(V, m_OneUse(m_Trunc(m_Value()))); | |
| })) | |
| return nullptr; | |
| WideOpIdx = 0; | |
| } else if (IsZeroTest) { | |
| if (!all_of(OpE->Scalars, [](Value *V) { | |
| auto *CI = dyn_cast<ICmpInst>(V); | |
| return CI && CI->hasOneUse() && | |
| CI->getPredicate() == ICmpInst::ICMP_NE; | |
| })) | |
| return nullptr; | |
| if (all_of(OpE->getOperand(0), | |
| [](Value *V) { return match(V, m_Zero()); })) | |
| WideOpIdx = 1; | |
| else if (all_of(OpE->getOperand(1), | |
| [](Value *V) { return match(V, m_Zero()); })) | |
| WideOpIdx = 0; | |
| else | |
| return nullptr; | |
| } else { | |
| return nullptr; | |
| } | |
| Type *WideTy = OpE->getOperand(WideOpIdx).front()->getType(); | |
| if (!WideTy->isIntegerTy()) | |
| return nullptr; | |
| for (Value *W : OpE->getOperand(WideOpIdx)) { | |
| if (W->getType() != WideTy || !isGuaranteedNotToBePoison(W) || | |
| (IsZeroTest && computeKnownBits(W, *DL).getMaxValue() != 1)) | |
| return nullptr; | |
| } | |
| return WideTy; | |
| }; | |
| // The wide value of a booleanized node is valid only in bit 0, so every | |
| // tree user must consume it as an i1 vector: either a booleanized and/or | |
| // node (which uses the wide value directly) or a node whose emission | |
| // truncates the operand back to the i1 vector type. | |
| auto IsSafeBoolUser = [&](const TreeEntry *UserTE, unsigned EdgeIdx) { | |
| if (!UserTE->hasState() || UserTE->isAltShuffle()) | |
| return false; | |
| if (Instruction::isBinaryOp(UserTE->getOpcode())) | |
| return true; | |
| switch (UserTE->getOpcode()) { | |
| case Instruction::Store: | |
| return EdgeIdx == 0; | |
| case Instruction::InsertElement: | |
| return EdgeIdx == 1; | |
| case Instruction::Select: | |
| return EdgeIdx != 0; | |
| case Instruction::Freeze: | |
| case Instruction::PHI: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| }; | |
| // Visit operand nodes before their users so that booleanized and/or nodes | |
| // chain: an and/or node whose operand is itself a booleanized and/or node | |
| // consumes the wide value directly. | |
| for (const std::unique_ptr<TreeEntry> &TE : reverse(VectorizableTree)) { | |
| // Logical and/or on i1 with booleanized wide leaves is emitted in the | |
| // wide type: bit 0 of a bitwise and/or is the and/or of the operands' | |
| // bit 0. The leaf conversions are absorbed and a single trunc of the | |
| // wide vector result restores the i1 lanes. Wide leaves must be | |
| // non-poison: and/or may mask poison in the i1 type, which the wide ops | |
| // would propagate instead. | |
| if (TE->State != TreeEntry::Vectorize || TE->isAltShuffle() || | |
| (TE->getOpcode() != Instruction::And && | |
| TE->getOpcode() != Instruction::Or) || | |
| !TE->getMainOp()->getType()->isIntegerTy(1) || | |
| !TE->ReuseShuffleIndices.empty() || TE->hasCopyableElements() || | |
| TE->CombinedOp != TreeEntry::NotCombinedOp || | |
| // The i1 flags (e.g. disjoint) do not apply to the wide op. | |
| any_of(TE->Scalars, | |
| [](Value *V) { | |
| auto *I = dyn_cast<Instruction>(V); | |
| return !I || I->hasPoisonGeneratingFlags(); | |
| }) || | |
| any_of(TE->getReassocScalars(), | |
| [](Value *V) { | |
| return cast<Instruction>(V)->hasPoisonGeneratingFlags(); | |
| }) || | |
| // Reduction trees are handled by the reduction's own booleanization. | |
| UserIgnoreList || | |
| (TE->UserTreeIndex.UserTE && | |
| !IsSafeBoolUser(TE->UserTreeIndex.UserTE, | |
| TE->UserTreeIndex.EdgeIdx)) || | |
| !all_of(TE->Scalars, | |
| [&](Value *V) { return getTreeEntries(V).size() == 1; })) | |
| continue; | |
| Type *WideTy = nullptr; | |
| bool Bail = false; | |
| for (unsigned I : seq<unsigned>(TE->getNumOperands())) { | |
| const TreeEntry *OpE = getOperandEntry(TE.get(), I); | |
| Type *ColTy = GetBoolLeafWideTy(OpE); | |
| if (!ColTy && isBooleanizedNode(OpE) && | |
| (OpE->getOpcode() == Instruction::And || | |
| OpE->getOpcode() == Instruction::Or)) | |
| ColTy = IntegerType::get(F->getContext(), MinBWs.lookup(OpE).first); | |
| if (!ColTy || (WideTy && ColTy != WideTy)) { | |
| Bail = true; | |
| break; | |
| } | |
| WideTy = ColTy; | |
| } | |
| if (Bail || !WideTy) | |
| continue; | |
| unsigned WideBW = WideTy->getIntegerBitWidth(); | |
| MinBWs.try_emplace(TE.get(), WideBW, /*IsSigned=*/false); | |
| for (unsigned I : seq<unsigned>(TE->getNumOperands())) | |
| MinBWs.try_emplace(getOperandEntry(TE.get(), I), WideBW, | |
| /*IsSigned=*/false); | |
| } | |
| } | |
| InstructionCost | |
| BoUpSLP::calculateTreeCostAndTrimNonProfitable(ArrayRef<Value *> VectorizedVals, | |
| Instruction *RdxRoot) { | |
| // FIXME: support buildvector of the gather nodes with struct types. | |
| if (any_of(VectorizableTree, [&](const std::unique_ptr<TreeEntry> &TE) { | |
| return TE->isGather() && | |
| isa<StructType>(TE->Scalars.front()->getType()); | |
| })) { | |
| LLVM_DEBUG( | |
| dbgs() << "SLP: rejecting tree with buildvector struct values of size " | |
| << VectorizableTree.size() << ".\n"); | |
| return InstructionCost::getInvalid(); | |
| } | |
| // The tree is priced, so its minimal vector nodes are analyzed, including the | |
| // ones, trimmed below as non-profitable. The verdict of the tree itself does | |
| // not matter: the retry with the single-user seeds rebuilds the same narrow | |
| // nodes, and their cost without the tree is not better. The wider nodes are | |
| // not registered: their rejection does not prevent the members from forming | |
| // the profitable narrower node. Same for the trees, rejected before the cost | |
| // estimation, and for the buildvector trees, rejected by the insert/extract | |
| // overhead rather than by the members themselves. | |
| const TreeEntry &Root = getRootNode(); | |
| if (!Root.hasState() || | |
| !isa<InsertElementInst, InsertValueInst>(Root.getMainOp())) { | |
| for (const std::unique_ptr<TreeEntry> &TE : make_filter_range( | |
| VectorizableTree, [](const std::unique_ptr<TreeEntry> &TE) { | |
| return !TE->isGather() && TE->Scalars.size() <= 2; | |
| })) { | |
| for (Value *V : TE->Scalars) | |
| if (isa_and_present<Instruction>(V) && !TE->isCopyableElement(V)) | |
| AnalyzedScalars.insert(V); | |
| } | |
| } | |
| SmallDenseMap<const TreeEntry *, InstructionCost> NodesCosts; | |
| SmallPtrSet<Value *, 4> CheckedExtracts; | |
| SmallSetVector<TreeEntry *, 4> GatheredLoadsNodes; | |
| SmallDenseMap<const TreeEntry *, InstructionCost> ExtractCosts; | |
| LLVM_DEBUG(dbgs() << "SLP: Calculating cost for tree of size " | |
| << VectorizableTree.size() << ".\n"); | |
| // The V-only-dependent part of the predicate. Same V is commonly seen in | |
| // multiple TEs (shared scalars), so cache the result across calls. | |
| // DeletedNodes is read-only during this cost loop, so caching is safe. | |
| const size_t NumVectScalars = ScalarToTreeEntries.size() + 1; | |
| SmallDenseMap<Value *, bool> ExternalUseVCache; | |
| auto IsExternallyUsedV = [&](Value *V) { | |
| auto [It, Inserted] = ExternalUseVCache.try_emplace(V); | |
| if (!Inserted) | |
| return It->second; | |
| bool Res = false; | |
| if (V->hasOneUse() || V->getType()->isVoidTy()) { | |
| // Res stays false. | |
| } else if (V->hasNUsesOrMore(NumVectScalars)) { | |
| Res = true; | |
| } else if (auto *I = dyn_cast<Instruction>(V)) { | |
| Res = any_of(I->users(), [&](const User *U) { | |
| // store/insertelt v, [cast]U will likely be vectorized. | |
| if (match(U, | |
| m_InsertElt(m_Value(), m_OneUse(m_CastOrSelf(m_Specific(I))), | |
| m_ConstantInt()))) | |
| return false; | |
| if (match(U, m_InsertElt(m_Value(), m_Specific(I), m_ConstantInt()))) | |
| return false; | |
| if (match(U, m_Store(m_OneUse(m_CastOrSelf(m_Specific(I))), m_Value()))) | |
| return false; | |
| if (match(U, m_Store(m_Specific(I), m_Value()))) | |
| return false; | |
| ArrayRef<TreeEntry *> Entries = getTreeEntries(U); | |
| if (Entries.empty() && !MustGather.contains(U)) | |
| return true; | |
| if (any_of(Entries, | |
| [&](TreeEntry *TE) { return DeletedNodes.contains(TE); })) | |
| return true; | |
| return any_of(ValueToGatherNodes.lookup(U), [&](const TreeEntry *TE) { | |
| return DeletedNodes.contains(TE); | |
| }); | |
| }); | |
| } | |
| It->second = Res; | |
| return Res; | |
| }; | |
| auto IsExternallyUsed = [&](const TreeEntry &TE, Value *V) { | |
| assert(TE.hasState() && !TE.isGather() && | |
| TE.State != TreeEntry::SplitVectorize && "Expected vector node."); | |
| if (TE.hasCopyableElements() && TE.isCopyableElement(V)) | |
| return false; | |
| return IsExternallyUsedV(V); | |
| }; | |
| InstructionCost Cost = 0; | |
| SmallDenseMap<const TreeEntry *, uint64_t> EntryToScale; | |
| uint64_t PrevScale = 0; | |
| BasicBlock *PrevVecParent = nullptr; | |
| for (const std::unique_ptr<TreeEntry> &Ptr : VectorizableTree) { | |
| TreeEntry &TE = *Ptr; | |
| // No need to count the cost for combined entries, they are combined and | |
| // just skip their cost. | |
| if (TE.State == TreeEntry::CombinedVectorize) { | |
| LLVM_DEBUG( | |
| dbgs() << "SLP: Skipping cost for combined node that starts with " | |
| << *TE.Scalars[0] << ".\n"; | |
| TE.dump(); dbgs() << "SLP: Current total cost = " << Cost << "\n"); | |
| NodesCosts.try_emplace(&TE); | |
| continue; | |
| } | |
| if (TE.hasState() && | |
| (TE.isGather() || TE.State == TreeEntry::SplitVectorize)) { | |
| if (const TreeEntry *E = | |
| getSameValuesTreeEntry(TE.getMainOp(), TE.Scalars); | |
| E && E->getVectorFactor() == TE.getVectorFactor()) { | |
| // Some gather nodes might be absolutely the same as some vectorizable | |
| // nodes after reordering, need to handle it. | |
| LLVM_DEBUG(dbgs() << "SLP: Adding cost 0 for bundle " | |
| << shortBundleName(TE.Scalars, TE.Idx) << ".\n" | |
| << "SLP: Current total cost = " << Cost << "\n"); | |
| NodesCosts.try_emplace(&TE); | |
| continue; | |
| } | |
| } | |
| // Exclude cost of gather loads nodes which are not used. These nodes were | |
| // built as part of the final attempt to vectorize gathered loads. | |
| assert((!TE.isGather() || TE.Idx == 0 || TE.UserTreeIndex) && | |
| "Expected gather nodes with users only."); | |
| InstructionCost C = getEntryCost(&TE, VectorizedVals, CheckedExtracts); | |
| uint64_t Scale = 0; | |
| bool CostIsFree = C == 0; | |
| // For gather/buildvector (and split-vectorize) entries, prefer the | |
| // per-lane refined scale that accounts for LICM-hoistable insertelements | |
| // when an operand is invariant in the current loop nest but defined in | |
| // an outer loop. This prevents over-costing cross-loop-nest buildvectors. | |
| if (!CostIsFree && !TE.isGather() && TE.hasState()) { | |
| if (PrevVecParent == TE.getMainOp()->getParent()) { | |
| Scale = PrevScale; | |
| C *= Scale; | |
| EntryToScale.try_emplace(&TE, Scale); | |
| } | |
| } | |
| if (!CostIsFree && !Scale) { | |
| Scale = getEntryEffectiveScale(TE, TE.Idx == 0 ? RdxRoot : nullptr); | |
| C *= Scale; | |
| EntryToScale.try_emplace(&TE, Scale); | |
| if (!TE.isGather() && TE.hasState()) { | |
| PrevVecParent = TE.getMainOp()->getParent(); | |
| PrevScale = Scale; | |
| } | |
| } | |
| Cost += C; | |
| NodesCosts.try_emplace(&TE, C); | |
| LLVM_DEBUG(dbgs() << "SLP: Adding cost " << C << " for bundle " | |
| << shortBundleName(TE.Scalars, TE.Idx) << ".\n" | |
| << "SLP: Current total cost = " << Cost << "\n"); | |
| // Add gathered loads nodes to the set for later processing. | |
| if (TE.Idx > 0 && !TE.UserTreeIndex && TE.hasState() && | |
| TE.getOpcode() == Instruction::Load) | |
| GatheredLoadsNodes.insert(&TE); | |
| if (!TE.isGather() && TE.State != TreeEntry::SplitVectorize && | |
| !(TE.Idx == 0 && (TE.getOpcode() == Instruction::InsertElement || | |
| TE.getOpcode() == Instruction::InsertValue || | |
| TE.getOpcode() == Instruction::Store)) && | |
| !isa<StructType>(getValueType(TE.Scalars.front(), Opts.slp_revec))) { | |
| // Calculate costs of external uses. | |
| APInt DemandedElts = APInt::getZero(TE.getVectorFactor()); | |
| for (Value *V : TE.Scalars) { | |
| if (IsExternallyUsed(TE, V)) | |
| DemandedElts.setBit(TE.findLaneForValue(V)); | |
| } | |
| if (!DemandedElts.isZero()) { | |
| Type *ScalarTy = TE.Scalars.front()->getType(); | |
| auto It = MinBWs.find(&TE); | |
| if (It != MinBWs.end()) | |
| ScalarTy = IntegerType::get(ScalarTy->getContext(), It->second.first); | |
| auto *VecTy = getWidenedType(ScalarTy, TE.getVectorFactor()); | |
| InstructionCost ExtCost = | |
| getScalarizationOverhead(*TTI, Opts.slp_revec, ScalarTy, | |
| cast<VectorType>(VecTy), DemandedElts, | |
| /*Insert=*/false, | |
| /*Extract=*/true, CostKind); | |
| if (ExtCost.isValid() && ExtCost != 0) { | |
| if (!Scale) | |
| Scale = getScaleToLoopIterations(TE); | |
| ExtCost *= Scale; | |
| EntryToScale.try_emplace(&TE, Scale); | |
| } | |
| ExtractCosts.try_emplace(&TE, ExtCost); | |
| } | |
| } | |
| } | |
| // Bail out if the cost threshold is negative and cost already below it. | |
| // The splat subtrees may still force extracts of their scalars on top of | |
| // the node cost and have to be trimmed, so do not bail out if there are | |
| // any. | |
| if (getCostThreshold() < 0 && Cost < -getCostThreshold() && | |
| SplatGatheredScalarsRoots.empty()) | |
| return Cost; | |
| // The narrow non-profitable tree in loop? Skip, may cause regressions. | |
| constexpr unsigned PartLimit = 2; | |
| // Measure the narrowness by the width the tree is vectorized at. | |
| const TreeEntry *RootTE = &getRootNode(); | |
| unsigned Sz; | |
| if (auto It = MinBWs.find(RootTE); It != MinBWs.end()) { | |
| Sz = It->second.first; | |
| } else { | |
| Sz = std::max<unsigned>( | |
| getVectorElementSize(RootTE->Scalars.front()), | |
| DL->getTypeSizeInBits( | |
| getValueType(RootTE->Scalars.front(), Opts.slp_revec) | |
| ->getScalarType())); | |
| } | |
| const unsigned MinVF = getMinVF(Sz); | |
| if (Cost >= -getCostThreshold() && | |
| getRootNodeScalars().size() * PartLimit <= MinVF && | |
| (!getRootNode().hasState() || | |
| (getRootNode().getOpcode() != Instruction::Store && | |
| LI->getLoopFor(getRootNode().getMainOp()->getParent())))) | |
| return Cost; | |
| // Store the cost + external uses estimation as the first element of the | |
| // tuple, just the cost as the second element of the tuple. Required to return | |
| // correct cost estimation for the tree, extracts are calculated separately. | |
| // Extracts, calculated here, are just quick estimations. | |
| SmallVector< | |
| std::tuple<InstructionCost, InstructionCost, SmallVector<unsigned>>> | |
| SubtreeCosts(VectorizableTree.size()); | |
| auto IsNotDeleted = [&](const TreeEntry *TE) { | |
| return !DeletedNodes.contains(TE); | |
| }; | |
| // (Re)computes the subtree costs for the nodes that are not deleted. | |
| auto ComputeSubtreeCosts = [&]() { | |
| SubtreeCosts.assign( | |
| VectorizableTree.size(), | |
| std::tuple<InstructionCost, InstructionCost, SmallVector<unsigned>>()); | |
| auto UpdateParentNodes = | |
| [&](const TreeEntry *UserTE, const TreeEntry *TE, | |
| InstructionCost TotalCost, InstructionCost Cost, | |
| SmallDenseSet<std::pair<const TreeEntry *, const TreeEntry *>, 4> | |
| &VisitedUser, | |
| bool AddToList = true) { | |
| while (UserTE && | |
| VisitedUser.insert(std::make_pair(TE, UserTE)).second) { | |
| std::get<0>(SubtreeCosts[UserTE->Idx]) += TotalCost; | |
| std::get<1>(SubtreeCosts[UserTE->Idx]) += Cost; | |
| if (AddToList) | |
| std::get<2>(SubtreeCosts[UserTE->Idx]).push_back(TE->Idx); | |
| UserTE = UserTE->UserTreeIndex.UserTE; | |
| } | |
| }; | |
| for (const std::unique_ptr<TreeEntry> &Ptr : VectorizableTree) { | |
| TreeEntry &TE = *Ptr; | |
| if (DeletedNodes.contains(&TE)) | |
| continue; | |
| // Combined subnodes are not costed on their own (their cost is 0), but | |
| // must be included into the ancestors' subtree node lists, so that they | |
| // get deleted together with the trimmed combined root. | |
| InstructionCost C = NodesCosts.at(&TE); | |
| InstructionCost ExtractCost = ExtractCosts.lookup(&TE); | |
| std::get<0>(SubtreeCosts[TE.Idx]) += C + ExtractCost; | |
| std::get<1>(SubtreeCosts[TE.Idx]) += C; | |
| if (const TreeEntry *UserTE = TE.UserTreeIndex.UserTE) { | |
| SmallDenseSet<std::pair<const TreeEntry *, const TreeEntry *>, 4> | |
| VisitedUser; | |
| UpdateParentNodes(UserTE, &TE, C + ExtractCost, C, VisitedUser); | |
| } | |
| } | |
| SmallDenseSet<std::pair<const TreeEntry *, const TreeEntry *>, 4> Visited; | |
| for (TreeEntry *TE : make_filter_range(GatheredLoadsNodes, IsNotDeleted)) { | |
| InstructionCost TotalCost = std::get<0>(SubtreeCosts[TE->Idx]); | |
| InstructionCost Cost = std::get<1>(SubtreeCosts[TE->Idx]); | |
| for (Value *V : TE->Scalars) { | |
| const auto Nodes = ValueToGatherNodes.lookup(V); | |
| for (const TreeEntry *BVTE : make_filter_range(Nodes, IsNotDeleted)) | |
| UpdateParentNodes(BVTE, TE, TotalCost, Cost, Visited, | |
| /*AddToList=*/false); | |
| } | |
| } | |
| }; | |
| ComputeSubtreeCosts(); | |
| using ValuesToInsertTy = | |
| SmallDenseMap<const TreeEntry *, SmallVector<Value *>>; | |
| auto GetScalarTy = [&](const TreeEntry *TE) { | |
| Type *ScalarTy = TE->Scalars.front()->getType(); | |
| auto It = MinBWs.find(TE); | |
| if (It != MinBWs.end()) | |
| ScalarTy = IntegerType::get(ScalarTy->getContext(), It->second.first); | |
| return ScalarTy; | |
| }; | |
| // Lanes of the subtree scalars used by the surviving gather nodes, and the | |
| // values to materialize in those gathers if the subtree is deleted. | |
| auto FindDemandedElts = [&](TreeEntry *TE, ValuesToInsertTy &ValuesToInsert) { | |
| APInt DemandedElts = APInt::getZero(TE->getVectorFactor()); | |
| for (Value *V : TE->Scalars) { | |
| unsigned Pos = TE->findLaneForValue(V); | |
| const auto Nodes = ValueToGatherNodes.lookup(V); | |
| for (const TreeEntry *BVE : make_filter_range(Nodes, IsNotDeleted)) { | |
| DemandedElts.setBit(Pos); | |
| ValuesToInsert.try_emplace(BVE).first->second.push_back(V); | |
| } | |
| } | |
| return DemandedElts; | |
| }; | |
| // Cost of materializing the values directly in the surviving gather nodes | |
| // that use them. | |
| auto GetGatherInsertCost = [&](Type *ScalarTy, | |
| const ValuesToInsertTy &ValuesToInsert) { | |
| InstructionCost BVCost = 0; | |
| for (const auto &[BVE, Values] : ValuesToInsert) { | |
| APInt BVDemandedElts = APInt::getZero(BVE->getVectorFactor()); | |
| SmallVector<Value *> BVValues(BVE->getVectorFactor(), | |
| PoisonValue::get(ScalarTy)); | |
| for (Value *V : Values) { | |
| unsigned Pos = BVE->findLaneForValue(V); | |
| BVValues[Pos] = V; | |
| BVDemandedElts.setBit(Pos); | |
| } | |
| BVCost += getScalarizationOverhead( | |
| *TTI, Opts.slp_revec, ScalarTy, | |
| cast<VectorType>(getWidenedType(ScalarTy, BVE->getVectorFactor())), | |
| BVDemandedElts, /*Insert=*/true, /*Extract=*/false, CostKind, | |
| BVDemandedElts.isAllOnes(), BVValues); | |
| } | |
| return BVCost; | |
| }; | |
| auto RecostEntry = [&](const TreeEntry *TE) { | |
| InstructionCost C = getEntryCost(TE, VectorizedVals, CheckedExtracts); | |
| if (!C.isValid() || C == 0) | |
| return C; | |
| uint64_t Scale = EntryToScale.lookup(TE); | |
| if (!Scale) | |
| Scale = getEntryEffectiveScale(*TE, TE->Idx == 0 ? RdxRoot : nullptr); | |
| return C * Scale; | |
| }; | |
| // A splat subtree pays off only if some surviving gather node reuses it and | |
| // its price plus the extracts of its scalars used by the remaining scalar | |
| // code plus the current cost of the gather nodes that reuse it is not worse | |
| // than re-emitting those gathers without the subtree. | |
| auto IsSplatSubtreeProfitable = [&](TreeEntry *TE, | |
| ValuesToInsertTy &ValuesToInsert, | |
| InstructionCost &CurrentGathersCost, | |
| InstructionCost &DroppedGathersCost) { | |
| if (FindDemandedElts(TE, ValuesToInsert).isZero()) | |
| return false; | |
| APInt ExtractElts = APInt::getZero(TE->getVectorFactor()); | |
| for (Instruction *I : make_isa_range<Instruction>(TE->Scalars)) { | |
| if (TE->isCopyableElement(I)) | |
| continue; | |
| // Too many users - the scalar is extracted anyway. | |
| if (I->hasNUsesOrMore(UsesLimit) || any_of(I->users(), [&](User *U) { | |
| return none_of(getTreeEntries(U), [&](const TreeEntry *UseTE) { | |
| return !DeletedNodes.contains(UseTE) && | |
| !TransformedToGatherNodes.contains(UseTE); | |
| }); | |
| })) | |
| ExtractElts.setBit(TE->findLaneForValue(I)); | |
| } | |
| Type *ScalarTy = GetScalarTy(TE); | |
| InstructionCost KeepCost = getScalarizationOverhead( | |
| *TTI, Opts.slp_revec, ScalarTy, | |
| cast<VectorType>(getWidenedType(ScalarTy, TE->getVectorFactor())), | |
| ExtractElts, /*Insert=*/false, /*Extract=*/true, CostKind); | |
| // Scale the extract cost to the subtree's execution frequency: the | |
| // subtree and gather costs it is compared against are already loop-scaled. | |
| if (KeepCost.isValid() && KeepCost != 0) | |
| KeepCost *= getEntryEffectiveScale(*TE); | |
| // Add the cost of the subtree itself, computed before any trimming: | |
| // trimming of the subtree's own nodes would otherwise make it look | |
| // artificially cheap. The inner nodes of the subtree keep their external | |
| // scalar users too, so their extracts are part of the price as well. | |
| KeepCost += std::get<1>(SubtreeCosts[TE->Idx]); | |
| for (unsigned Idx : std::get<2>(SubtreeCosts[TE->Idx])) | |
| KeepCost += ExtractCosts.lookup(VectorizableTree[Idx].get()); | |
| // The reusing gather nodes currently pay the broadcast cost; without | |
| // the subtree they fall back to plain insertion sequences. Gather | |
| // nodes already erased from NodesCosts are being deleted and do not | |
| // count on either side. | |
| CurrentGathersCost = 0; | |
| for (const auto &[BVE, _] : ValuesToInsert) | |
| CurrentGathersCost += NodesCosts.lookup(BVE); | |
| KeepCost += CurrentGathersCost; | |
| // Re-cost the gather nodes with the subtree tentatively deleted. | |
| DeletedNodes.insert(TE); | |
| SmallVector<TreeEntry *> TempDeleted; | |
| for (unsigned Idx : std::get<2>(SubtreeCosts[TE->Idx])) { | |
| TreeEntry *Child = VectorizableTree[Idx].get(); | |
| if (DeletedNodes.insert(Child).second) | |
| TempDeleted.push_back(Child); | |
| } | |
| DroppedGathersCost = 0; | |
| auto HasCost = [&](const auto &P) { return NodesCosts.contains(P.first); }; | |
| for (const auto &[BVE, _] : make_filter_range(ValuesToInsert, HasCost)) | |
| DroppedGathersCost += RecostEntry(BVE); | |
| DeletedNodes.erase(TE); | |
| for (TreeEntry *Child : TempDeleted) | |
| DeletedNodes.erase(Child); | |
| // On a cost tie prefer dropping the subtree: the reusing gathers | |
| // materialize the scalars at the same price with fewer instructions. | |
| return KeepCost < DroppedGathersCost; | |
| }; | |
| // Re-price the gathers that reused a dropped splat subtree, so the | |
| // remaining subtrees are checked against the costs with it deleted. | |
| auto SyncGatherCosts = [&](const ValuesToInsertTy &ValuesToInsert) { | |
| for (const auto &[BVE, _] : ValuesToInsert) | |
| if (!DeletedNodes.contains(BVE)) | |
| NodesCosts[BVE] = RecostEntry(BVE); | |
| }; | |
| // Deletes the subtree. The per-node costs are erased eagerly only where the | |
| // total is recomputed from them right after the deletion. | |
| auto DeleteSubtree = [&](TreeEntry *TE, bool EraseCosts) { | |
| DeletedNodes.insert(TE); | |
| if (EraseCosts) | |
| NodesCosts.erase(TE); | |
| for (unsigned Idx : std::get<2>(SubtreeCosts[TE->Idx])) { | |
| TreeEntry *Child = VectorizableTree[Idx].get(); | |
| DeletedNodes.insert(Child); | |
| if (EraseCosts) | |
| NodesCosts.erase(Child); | |
| } | |
| }; | |
| // Gathered loads subtrees left without surviving gather users are dead. | |
| auto DropDeadGatheredLoads = [&](bool EraseCosts) { | |
| for (TreeEntry *TE : make_filter_range(GatheredLoadsNodes, IsNotDeleted)) { | |
| ValuesToInsertTy ValuesToInsert; | |
| if (!FindDemandedElts(TE, ValuesToInsert).isZero()) | |
| continue; | |
| DeleteSubtree(TE, EraseCosts); | |
| } | |
| }; | |
| // Drops the splat subtrees that are unused by the surviving gathers or do | |
| // not pay off; returns true if anything was dropped. | |
| auto DropUnprofitableSplatSubtrees = [&](bool EraseCosts) { | |
| bool Dropped = false; | |
| for (TreeEntry *TE : | |
| make_filter_range(SplatGatheredScalarsRoots, IsNotDeleted)) { | |
| ValuesToInsertTy ValuesToInsert; | |
| InstructionCost CurrentGathersCost = 0, DroppedGathersCost = 0; | |
| if (IsSplatSubtreeProfitable(TE, ValuesToInsert, CurrentGathersCost, | |
| DroppedGathersCost)) | |
| continue; | |
| DeleteSubtree(TE, EraseCosts); | |
| SyncGatherCosts(ValuesToInsert); | |
| Dropped = true; | |
| } | |
| return Dropped; | |
| }; | |
| auto SumNodesCosts = [&]() { | |
| InstructionCost C = 0; | |
| for (const auto &P : NodesCosts) | |
| C += P.second; | |
| return C; | |
| }; | |
| // The splat subtrees are speculative. The ones that do not pay off even in | |
| // the full tree are dropped before the trimming: the reusing gathers are | |
| // priced for the broadcast from the subtree vector while the subtree is | |
| // around, which distorts the trimming decisions of the main tree, and fewer | |
| // surviving gathers after the trimming can only make keeping a subtree less | |
| // profitable. | |
| if (DropUnprofitableSplatSubtrees(/*EraseCosts=*/true)) { | |
| DropDeadGatheredLoads(/*EraseCosts=*/true); | |
| ComputeSubtreeCosts(); | |
| Cost = SumNodesCosts(); | |
| } | |
| using CostIndicesTy = | |
| std::pair<TreeEntry *, std::tuple<InstructionCost, InstructionCost, | |
| SmallVector<unsigned>>>; | |
| struct FirstGreater { | |
| bool operator()(const CostIndicesTy &LHS, const CostIndicesTy &RHS) const { | |
| return std::get<0>(LHS.second) < std::get<0>(RHS.second) || | |
| (std::get<0>(LHS.second) == std::get<0>(RHS.second) && | |
| LHS.first->Idx < RHS.first->Idx); | |
| } | |
| }; | |
| PriorityQueue<CostIndicesTy, SmallVector<CostIndicesTy>, FirstGreater> | |
| Worklist; | |
| // Combined subnodes are not trimmed on their own, only as a whole combined | |
| // node, so only the root nodes are checked and included into the worklist. | |
| for (const auto [Idx, P] : | |
| make_filter_range(enumerate(SubtreeCosts), [&](const auto &Elem) { | |
| return VectorizableTree[Elem.index()]->State != | |
| TreeEntry::CombinedVectorize; | |
| })) | |
| Worklist.emplace(VectorizableTree[Idx].get(), P); | |
| // Narrow store trees with non-profitable immediate values - exit. | |
| if (!UserIgnoreList && getRootNode().getVectorFactor() < MinVF && | |
| getRootNode().hasState() && | |
| getRootNode().getOpcode() == Instruction::Store && | |
| (Worklist.top().first->Idx == 0 || Worklist.top().first->Idx == 1)) | |
| return Cost; | |
| // Node costs and deleted nodes before any trimming; the restored tree is | |
| // priced from them if the trimming is reverted. | |
| const SmallDenseMap<const TreeEntry *, InstructionCost> OrigNodesCosts = | |
| NodesCosts; | |
| const SmallPtrSet<const TreeEntry *, 8> OrigDeletedNodes = DeletedNodes; | |
| bool Changed = false; | |
| bool PreferTrimmedTree = false; | |
| while (!Worklist.empty() && std::get<0>(Worklist.top().second) > 0) { | |
| TreeEntry *TE = Worklist.top().first; | |
| if (TE->isGather() || TE->Idx == 0 || DeletedNodes.contains(TE) || | |
| isa<StructType>(getValueType(TE->Scalars.front(), Opts.slp_revec)) || | |
| // Splat subtree roots are decided by the keep/drop check below: their | |
| // scalars are materialized in the reusing splat gathers, not in a | |
| // gather of the root's own scalars. | |
| is_contained(SplatGatheredScalarsRoots, TE) || | |
| // Exit early if the parent node is split node and any of scalars is | |
| // used in other split nodes. | |
| (TE->UserTreeIndex && | |
| TE->UserTreeIndex.UserTE->State == TreeEntry::SplitVectorize && | |
| any_of(TE->Scalars, [&](Value *V) { | |
| ArrayRef<TreeEntry *> Entries = getSplitTreeEntries(V); | |
| return Entries.size() > 1; | |
| }))) { | |
| Worklist.pop(); | |
| continue; | |
| } | |
| // Skip inversed compare nodes, they cannot be transformed to buildvectors. | |
| if (TE->State == TreeEntry::Vectorize && !TE->isAltShuffle() && | |
| (TE->getOpcode() == Instruction::ICmp || | |
| TE->getOpcode() == Instruction::FCmp) && | |
| any_of(make_isa_range<CmpInst>(TE->Scalars), [&](CmpInst *I) { | |
| return I->getPredicate() != | |
| cast<CmpInst>(TE->getMainOp())->getPredicate(); | |
| })) { | |
| Worklist.pop(); | |
| continue; | |
| } | |
| // A zext feeding a bit pack is priced as part of the pack: its vectorized | |
| // form makes the compaction back to the source width free, so keep it. | |
| if (TE->UserTreeIndex && TE->UserTreeIndex.EdgeIdx == 0 && | |
| TE->UserTreeIndex.UserTE->CombinedOp == TreeEntry::ReducedBitPack && | |
| getZExtSrcWidth(*TE) != 0) { | |
| Worklist.pop(); | |
| continue; | |
| } | |
| // Calculate the gather cost of the root node. | |
| InstructionCost TotalSubtreeCost = std::get<0>(Worklist.top().second); | |
| InstructionCost SubtreeCost = std::get<1>(Worklist.top().second); | |
| if (TotalSubtreeCost < TE->Scalars.size()) { | |
| Worklist.pop(); | |
| continue; | |
| } | |
| if (!TransformedToGatherNodes.empty()) { | |
| for (unsigned Idx : std::get<2>(Worklist.top().second)) { | |
| auto It = TransformedToGatherNodes.find(VectorizableTree[Idx].get()); | |
| if (It != TransformedToGatherNodes.end()) { | |
| TotalSubtreeCost -= std::get<0>(SubtreeCosts[Idx]); | |
| SubtreeCost -= std::get<1>(SubtreeCosts[Idx]); | |
| TotalSubtreeCost += It->second; | |
| SubtreeCost += It->second; | |
| } | |
| } | |
| } | |
| if (TotalSubtreeCost < 0 || TotalSubtreeCost < TE->Scalars.size()) { | |
| Worklist.pop(); | |
| continue; | |
| } | |
| const unsigned EntryVF = TE->getVectorFactor(); | |
| APInt DemandedElts = APInt::getZero(EntryVF); | |
| for (auto [Idx, V] : enumerate(TE->Scalars)) { | |
| if (!isConstant(V)) | |
| DemandedElts.setBit(Idx); | |
| } | |
| Type *ScalarTy = getValueType(TE->Scalars.front(), Opts.slp_revec); | |
| auto It = MinBWs.find(TE); | |
| if (It != MinBWs.end()) | |
| ScalarTy = IntegerType::get(ScalarTy->getContext(), It->second.first); | |
| auto *VecTy = getWidenedType(ScalarTy, EntryVF); | |
| InstructionCost GatherCost = getScalarizationOverhead( | |
| *TTI, Opts.slp_revec, ScalarTy, cast<VectorType>(VecTy), DemandedElts, | |
| /*Insert=*/true, /*Extract=*/false, CostKind); | |
| SmallVector<int> Mask; | |
| if (!TE->ReorderIndices.empty() && | |
| TE->State != TreeEntry::CompressVectorize && | |
| (TE->State != TreeEntry::StridedVectorize || !TE->isReverse() || | |
| isWidenedStridedLoad(TE))) { | |
| SmallVector<int> NewMask; | |
| if (TE->getOpcode() == Instruction::Store) { | |
| // For stores the order is actually a mask. | |
| NewMask.resize(TE->ReorderIndices.size()); | |
| copy(TE->ReorderIndices, NewMask.begin()); | |
| } else { | |
| inversePermutation(TE->ReorderIndices, NewMask); | |
| } | |
| addMask(Mask, NewMask); | |
| } | |
| if (!TE->ReuseShuffleIndices.empty()) | |
| addMask(Mask, TE->ReuseShuffleIndices); | |
| if (!Mask.empty() && !ShuffleVectorInst::isIdentityMask(Mask, EntryVF)) | |
| GatherCost += getShuffleCost(*TTI, TTI::SK_PermuteSingleSrc, | |
| cast<VectorType>(VecTy), CostKind, Mask); | |
| // If all scalars are reused in gather node(s) or other vector nodes, there | |
| // might be extra cost for inserting them. | |
| if ((!TE->hasState() || !TE->isAltShuffle()) && | |
| all_of(TE->Scalars, [&](Value *V) { | |
| return (TE->hasCopyableElements() && TE->isCopyableElement(V)) || | |
| isConstant(V) || isGathered(V) || getTreeEntries(V).size() > 1; | |
| })) | |
| GatherCost *= 2; | |
| // The fmuls, priced as fused into their vectorized fadd/fsub users, stay | |
| // scalar and unfused in the gathered form. | |
| GatherCost += getUnfusedFMulsPenalty(*TE); | |
| // Erase subtree if it is non-profitable. | |
| ArrayRef<unsigned> Nodes = std::get<2>(Worklist.top().second); | |
| // Prefer trimming equal-cost alternate-shuffle subtrees rooted at binary | |
| // ops: alt-shuffles introduce runtime shuffle overhead that the cost model | |
| // may underestimate. Skip if the subtree contains ExtractElement nodes, | |
| // since those operate on already-materialized vectors where the cost model | |
| // is more accurate. | |
| auto IsEqualCostAltShuffleToTrim = [&]() { | |
| return TotalSubtreeCost == GatherCost && TE->hasState() && | |
| TE->isAltShuffle() && Instruction::isBinaryOp(TE->getOpcode()) && | |
| none_of(Nodes, [&](unsigned Idx) { | |
| return VectorizableTree[Idx]->hasState() && | |
| VectorizableTree[Idx]->getOpcode() == | |
| Instruction::ExtractElement; | |
| }); | |
| }; | |
| // Non-power-of-2 entries may have inflated costs - add a margin of 1 | |
| // before trimming to avoid over-pruning. | |
| bool HasNonPowerOf2 = any_of(Nodes, [&](unsigned Idx) { | |
| return !has_single_bit(VectorizableTree[Idx]->Scalars.size()); | |
| }); | |
| InstructionCost TrimMargin = HasNonPowerOf2 ? 1 : 0; | |
| if (TotalSubtreeCost > GatherCost + TrimMargin || | |
| IsEqualCostAltShuffleToTrim()) { | |
| PreferTrimmedTree |= TotalSubtreeCost == GatherCost; | |
| // If the remaining tree is just a buildvector - exit, it will cause | |
| // endless attempts to vectorize. When the tree is already profitable, | |
| // skip trimming this node and let the post-loop logic (including | |
| // gathered loads processing) decide. | |
| if (getRootNode().hasState() && | |
| getRootNode().getOpcode() == Instruction::InsertElement && | |
| TE->Idx == 1) { | |
| if (Cost < -getCostThreshold()) { | |
| LLVM_DEBUG(dbgs() << "SLP: Skipping trim of node " << TE->Idx | |
| << " - tree already profitable with cost " << Cost | |
| << ".\n"); | |
| Worklist.pop(); | |
| continue; | |
| } | |
| return InstructionCost::getInvalid(); | |
| } | |
| LLVM_DEBUG(dbgs() << "SLP: Trimming unprofitable subtree at node " | |
| << TE->Idx << " with cost " | |
| << std::get<0>(Worklist.top().second) | |
| << " and gather cost " << GatherCost << ".\n"); | |
| if (TE->UserTreeIndex) { | |
| TransformedToGatherNodes.try_emplace(TE, GatherCost); | |
| NodesCosts.erase(TE); | |
| } else { | |
| DeletedNodes.insert(TE); | |
| TransformedToGatherNodes.erase(TE); | |
| NodesCosts.erase(TE); | |
| } | |
| for (unsigned Idx : Nodes) { | |
| TreeEntry &ChildTE = *VectorizableTree[Idx]; | |
| DeletedNodes.insert(&ChildTE); | |
| TransformedToGatherNodes.erase(&ChildTE); | |
| NodesCosts.erase(&ChildTE); | |
| } | |
| Changed = true; | |
| } | |
| Worklist.pop(); | |
| } | |
| // The VF=2 instruction-count veto rejects the whole tree when the vector code | |
| // has more instructions than the scalar code. The auxiliary subtrees - splat | |
| // gather roots and gathered loads - are optional: the surviving gathers can | |
| // re-materialize their scalars. Drop the reused ones while the tree exceeds | |
| // the budget so a kept subtree cannot reject the main tree. The gate mirrors | |
| // the veto conditions. | |
| const bool ApplyInstCountVeto = | |
| (!SplatGatheredScalarsRoots.empty() || !GatheredLoadsNodes.empty()) && | |
| CostKind != TTI::TCK_CodeSize && Opts.slp_inst_count_check && | |
| TTI->preferSLPInstCountCheck() && getRootNode().getVectorFactor() == 2 && | |
| getCostThreshold() == 0 && | |
| (!Opts.slp_revec || | |
| !isa<VectorType>(getRootNodeScalars().front()->getType())); | |
| const Loop *InstCountLoop = nullptr; | |
| if (ApplyInstCountVeto && Opts.slp_cost_loop_trip_count != 0 && | |
| getRootNode().hasState()) | |
| InstCountLoop = LI->getLoopFor(getRootNode().getMainOp()->getParent()); | |
| auto TrimAuxSubtreesForInstCount = [&](InstructionCost &Cost, | |
| bool UseCurrentNodeCosts) { | |
| if (!ApplyInstCountVeto || bypassesInstCountCheck(Cost)) | |
| return; | |
| // Current cost of the subtree rooted at TE from the per-node costs. | |
| auto GetCurrentSubtreeCost = [&](const TreeEntry *TE) { | |
| InstructionCost C = NodesCosts.lookup(TE); | |
| for (unsigned Idx : std::get<2>(SubtreeCosts[TE->Idx])) | |
| C += NodesCosts.lookup(VectorizableTree[Idx].get()); | |
| return C; | |
| }; | |
| auto DropReusedSubtree = [&](TreeEntry *TE, bool DeadOnly) { | |
| if (DeletedNodes.contains(TE) || TransformedToGatherNodes.contains(TE)) | |
| return false; | |
| ValuesToInsertTy ValuesToInsert; | |
| const bool IsDead = FindDemandedElts(TE, ValuesToInsert).isZero(); | |
| if (!IsDead && DeadOnly) | |
| return false; | |
| InstructionCost CurrentGathersCost = 0; | |
| for (const auto &[BVE, _] : ValuesToInsert) | |
| CurrentGathersCost += NodesCosts.lookup(BVE); | |
| DeleteSubtree(TE, /*EraseCosts=*/false); | |
| // All reusers were dropped: the subtree is dead. Without current node | |
| // costs the total conservatively keeps the subtree cost. | |
| if (IsDead) { | |
| if (UseCurrentNodeCosts) | |
| Cost -= GetCurrentSubtreeCost(TE); | |
| return true; | |
| } | |
| InstructionCost DroppedGathersCost = 0; | |
| for (const auto &[BVE, _] : ValuesToInsert) | |
| if (NodesCosts.contains(BVE)) | |
| DroppedGathersCost += RecostEntry(BVE); | |
| // The pre-trimming subtree cost matches the total only while the tree | |
| // is intact or restored; an accepted trimming leaves the total holding | |
| // just the current costs of the surviving subtree nodes. | |
| InstructionCost SubtreeCost = UseCurrentNodeCosts | |
| ? GetCurrentSubtreeCost(TE) | |
| : std::get<1>(SubtreeCosts[TE->Idx]); | |
| Cost += DroppedGathersCost - CurrentGathersCost - SubtreeCost; | |
| SyncGatherCosts(ValuesToInsert); | |
| return true; | |
| }; | |
| // Recompute the counts only after an actual drop. Dropping a root can | |
| // kill the reusers of a root from the other list, so repeat until no more | |
| // drops; dead subtrees are removed even under the budget. | |
| uint64_t NumScalar = getNumScalarInsts(InstCountLoop); | |
| uint64_t NumVector = getNumVectorInsts(InstCountLoop); | |
| bool Retry; | |
| do { | |
| Retry = false; | |
| for (ArrayRef<TreeEntry *> Roots : | |
| {ArrayRef<TreeEntry *>(SplatGatheredScalarsRoots), | |
| GatheredLoadsNodes.getArrayRef()}) | |
| for (TreeEntry *TE : Roots) | |
| if (DropReusedSubtree(TE, /*DeadOnly=*/NumVector <= NumScalar)) { | |
| NumScalar = getNumScalarInsts(InstCountLoop); | |
| NumVector = getNumVectorInsts(InstCountLoop); | |
| Retry = true; | |
| } | |
| } while (Retry); | |
| }; | |
| if (!Changed) { | |
| // The splat subtrees are not linked to the tree root, so their cost is | |
| // not included in the root's subtree cost; add it explicitly. Drop the | |
| // unprofitable ones instead of letting them reject the whole tree. | |
| InstructionCost TotalCost = std::get<1>(SubtreeCosts.front()); | |
| for (TreeEntry *TE : | |
| make_filter_range(SplatGatheredScalarsRoots, [&](TreeEntry *TE) { | |
| return !DeletedNodes.contains(TE); | |
| })) { | |
| ValuesToInsertTy ValuesToInsert; | |
| InstructionCost CurrentGathersCost = 0, DroppedGathersCost = 0; | |
| if (IsSplatSubtreeProfitable(TE, ValuesToInsert, CurrentGathersCost, | |
| DroppedGathersCost)) { | |
| TotalCost += std::get<1>(SubtreeCosts[TE->Idx]); | |
| continue; | |
| } | |
| DeleteSubtree(TE, /*EraseCosts=*/false); | |
| // The gather nodes that reused the subtree are re-emitted without it. | |
| TotalCost += DroppedGathersCost - CurrentGathersCost; | |
| SyncGatherCosts(ValuesToInsert); | |
| } | |
| DropDeadGatheredLoads(/*EraseCosts=*/false); | |
| TrimAuxSubtreesForInstCount(TotalCost, /*UseCurrentNodeCosts=*/false); | |
| return TotalCost; | |
| } | |
| SmallPtrSet<TreeEntry *, 4> SubtreesToDelete; | |
| InstructionCost LoadsExtractsCost = 0; | |
| // Check if all gather nodes that reuse the splat subtrees are marked for | |
| // deletion. In this case the whole splat subtree must be deleted. If only | |
| // some of the gathers are trimmed, keeping the subtree still costs its full | |
| // price plus the extracts of the scalars used by the remaining scalar code, | |
| // while the surviving gathers can materialize the splatted scalars | |
| // directly. Drop the subtree if it does not pay off. | |
| for (TreeEntry *TE : | |
| make_filter_range(SplatGatheredScalarsRoots, [&](TreeEntry *TE) { | |
| return !DeletedNodes.contains(TE); | |
| })) { | |
| ValuesToInsertTy ValuesToInsert; | |
| InstructionCost CurrentGathersCost, DroppedGathersCost; | |
| if (IsSplatSubtreeProfitable(TE, ValuesToInsert, CurrentGathersCost, | |
| DroppedGathersCost)) | |
| continue; | |
| SubtreesToDelete.insert(TE); | |
| NodesCosts.erase(TE); | |
| } | |
| // Check if all loads of gathered loads nodes are marked for deletion. In this | |
| // case the whole gathered loads subtree must be deleted. | |
| // Also, try to account for extracts, which might be required, if only part of | |
| // gathered load must be vectorized. Keep partially vectorized nodes, if | |
| // extracts are cheaper than gathers. | |
| for (TreeEntry *TE : | |
| make_filter_range(GatheredLoadsNodes, [&](TreeEntry *TE) { | |
| return !DeletedNodes.contains(TE) && | |
| !TransformedToGatherNodes.contains(TE); | |
| })) { | |
| SubtreesToDelete.insert(TE); | |
| // All loads are removed from gathered? Need to delete the subtree. | |
| ValuesToInsertTy ValuesToInsert; | |
| APInt DemandedElts = FindDemandedElts(TE, ValuesToInsert); | |
| if (!DemandedElts.isZero()) { | |
| Type *ScalarTy = GetScalarTy(TE); | |
| auto *VecTy = getWidenedType(ScalarTy, TE->getVectorFactor()); | |
| InstructionCost ExtractsCost = getScalarizationOverhead( | |
| *TTI, Opts.slp_revec, ScalarTy, cast<VectorType>(VecTy), DemandedElts, | |
| /*Insert=*/false, /*Extract=*/true, CostKind); | |
| InstructionCost BVCost = GetGatherInsertCost(ScalarTy, ValuesToInsert); | |
| if (ExtractsCost < BVCost) { | |
| LoadsExtractsCost += ExtractsCost; | |
| SubtreesToDelete.erase(TE); | |
| continue; | |
| } | |
| LoadsExtractsCost += BVCost; | |
| } | |
| NodesCosts.erase(TE); | |
| } | |
| // Deleted all subtrees rooted at gathered loads nodes or splat subtrees. | |
| for (std::unique_ptr<TreeEntry> &TE : VectorizableTree) { | |
| if (TE->UserTreeIndex && | |
| SubtreesToDelete.contains(TE->UserTreeIndex.UserTE)) { | |
| DeletedNodes.insert(TE.get()); | |
| NodesCosts.erase(TE.get()); | |
| SubtreesToDelete.insert(TE.get()); | |
| } | |
| if (SubtreesToDelete.contains(TE.get())) | |
| DeletedNodes.insert(TE.get()); | |
| } | |
| for (std::unique_ptr<TreeEntry> &TE : VectorizableTree) { | |
| if (!TE->UserTreeIndex && TransformedToGatherNodes.contains(TE.get())) { | |
| assert(TE->getOpcode() == Instruction::Load && "Expected load only."); | |
| continue; | |
| } | |
| if (DeletedNodes.contains(TE.get())) | |
| continue; | |
| // Gather costs depend on the set of vectorized nodes available for | |
| // reuse, which changes during trimming, so recalculate them for all | |
| // gather nodes, not just for the transformed ones. | |
| if (TE->isGather() || !NodesCosts.contains(TE.get())) | |
| NodesCosts[TE.get()] = RecostEntry(TE.get()); | |
| } | |
| LLVM_DEBUG(dbgs() << "SLP: Recalculate costs after tree trimming.\n"); | |
| InstructionCost NewCost = 0; | |
| for (const auto &P : NodesCosts) { | |
| NewCost += P.second; | |
| LLVM_DEBUG(dbgs() << "SLP: Adding cost " << P.second << " for bundle " | |
| << shortBundleName(P.first->Scalars, P.first->Idx) | |
| << ".\n" | |
| << "SLP: Current total cost = " << NewCost << "\n"); | |
| } | |
| // The trimming is reverted if the restored tree is not more expensive. The | |
| // restored tree is priced the same way as the trimmed one, from the per-node | |
| // costs: adjusting the pre-trimming total incrementally is not exact with | |
| // the auxiliary subtrees around, since the subtree costs recorded before the | |
| // trimming fold in the gathered loads they reuse, and the gathers inside a | |
| // subtree change their price once their reuse sources are dropped. | |
| auto ComputeRestoredCost = [&]() { | |
| DeletedNodes = OrigDeletedNodes; | |
| TransformedToGatherNodes.clear(); | |
| NodesCosts = OrigNodesCosts; | |
| if (!SplatGatheredScalarsRoots.empty()) { | |
| for (const std::unique_ptr<TreeEntry> &TE : VectorizableTree) | |
| if (TE->isGather() && !DeletedNodes.contains(TE.get())) | |
| NodesCosts[TE.get()] = RecostEntry(TE.get()); | |
| DropUnprofitableSplatSubtrees(/*EraseCosts=*/false); | |
| // Dropping the splat subtrees may leave the gathered loads subtrees | |
| // built for them without surviving gather users. | |
| DropDeadGatheredLoads(/*EraseCosts=*/false); | |
| for (const TreeEntry *TE : DeletedNodes) | |
| NodesCosts.erase(TE); | |
| } | |
| return SumNodesCosts(); | |
| }; | |
| const InstructionCost TrimmedCost = NewCost + LoadsExtractsCost; | |
| auto TrimmedDeletedNodes = DeletedNodes; | |
| auto TrimmedTransformedNodes = TransformedToGatherNodes; | |
| auto TrimmedNodesCosts = NodesCosts; | |
| const InstructionCost RestoredCost = ComputeRestoredCost(); | |
| LLVM_DEBUG(dbgs() << "SLP: Trimmed tree cost = " << TrimmedCost | |
| << ", restored tree cost = " << RestoredCost << ".\n"); | |
| const bool Reverted = TrimmedCost > RestoredCost || | |
| (!PreferTrimmedTree && TrimmedCost == RestoredCost); | |
| if (Reverted) { | |
| NewCost = RestoredCost; | |
| } else { | |
| DeletedNodes = std::move(TrimmedDeletedNodes); | |
| TransformedToGatherNodes = std::move(TrimmedTransformedNodes); | |
| NodesCosts = std::move(TrimmedNodesCosts); | |
| // If the remaining tree is just a buildvector - exit, it will cause | |
| // endless attempts to vectorize. | |
| if (VectorizableTree.size() >= 2 && getRootNode().hasState() && | |
| getRootNode().getOpcode() == Instruction::InsertElement && | |
| TransformedToGatherNodes.contains(VectorizableTree[1].get())) | |
| return InstructionCost::getInvalid(); | |
| if (VectorizableTree.size() >= 3 && getRootNode().hasState() && | |
| getRootNode().getOpcode() == Instruction::InsertElement && | |
| VectorizableTree[1]->hasState() && | |
| VectorizableTree[1]->State == TreeEntry::Vectorize && | |
| (VectorizableTree[1]->getOpcode() == Instruction::ZExt || | |
| VectorizableTree[1]->getOpcode() == Instruction::SExt || | |
| VectorizableTree[1]->getOpcode() == Instruction::Trunc) && | |
| TransformedToGatherNodes.contains(VectorizableTree[2].get())) | |
| return InstructionCost::getInvalid(); | |
| } | |
| TrimAuxSubtreesForInstCount(NewCost, /*UseCurrentNodeCosts=*/true); | |
| return NewCost; | |
| } | |
| namespace { | |
| /// Data type for handling buildvector sequences with the reused scalars from | |
| /// other tree entries. | |
| template <typename T> struct ShuffledInsertData { | |
| /// List of insertelements to be replaced by shuffles. | |
| SmallVector<InsertElementInst *> InsertElements; | |
| /// The parent vectors and shuffle mask for the given list of inserts. | |
| MapVector<T, SmallVector<int>> ValueMasks; | |
| }; | |
| } // namespace | |
| /// \returns true if more values with the scalar type \p Ty than registers are | |
| /// live at some point of the block \p BB. The blocks larger than the scheduling | |
| /// budget are skipped to save compile time. | |
| static bool hasHighRegisterPressure(const VectorizeOptions &Opts, | |
| const BasicBlock &BB, Type *Ty, | |
| const TargetTransformInfo &TTI) { | |
| if (hasNItemsOrMore(BB, Opts.slp_schedule_budget)) | |
| return false; | |
| const unsigned NumRegs = TTI.getNumberOfRegisters( | |
| TTI.getRegisterClassForType(/*Vector=*/false, Ty)); | |
| auto IsLiveCandidate = [&](const Value *V) { | |
| return isa<Instruction, Argument>(V) && V->getType()->getScalarType() == Ty; | |
| }; | |
| SmallPtrSet<const Value *, 32> Live; | |
| for (const Instruction &I : reverse(BB)) { | |
| // The operands of the PHIs are live in the predecessors. | |
| if (isa<PHINode>(I)) | |
| break; | |
| Live.erase(&I); | |
| Live.insert_range(make_filter_range(I.operand_values(), IsLiveCandidate)); | |
| if (Live.size() > NumRegs) | |
| return true; | |
| } | |
| return false; | |
| } | |
| InstructionCost BoUpSLP::getTreeCost(InstructionCost TreeCost, | |
| ArrayRef<Value *> VectorizedVals, | |
| InstructionCost ReductionCost, | |
| Instruction *RdxRoot) { | |
| // Reject vectorization if the vector code would produce more instructions | |
| // than the scalar code. The cost model may underestimate overhead from | |
| // shuffles, inserts, and extracts. | |
| // FIXME: remove this as soon as correct fractional model is landed for all | |
| // targets. | |
| if (CostKind != TTI::TCK_CodeSize && Opts.slp_inst_count_check && | |
| TTI->preferSLPInstCountCheck() && getRootNode().getVectorFactor() == 2 && | |
| getCostThreshold() == 0 && | |
| (!Opts.slp_revec || | |
| !isa<VectorType>(getRootNodeScalars().front()->getType()))) { | |
| // Loop containing the tree root; null for flat code or disabled | |
| // loop-aware modeling. Shared by both calls below. | |
| const Loop *TreeLoop = nullptr; | |
| if (Opts.slp_cost_loop_trip_count != 0 && getRootNode().hasState()) | |
| TreeLoop = LI->getLoopFor(getRootNode().getMainOp()->getParent()); | |
| uint64_t NumScalar = getNumScalarInsts(TreeLoop); | |
| uint64_t NumVector = getNumVectorInsts(TreeLoop); | |
| // In loops, the extracts are not scaled by the trip count and just break | |
| // the ties of the scaled counts. Ignore them under the high register | |
| // pressure: the vector code holds several lanes per register. | |
| if (NumVector > NumScalar && TreeLoop && !ExternalUses.empty()) { | |
| const uint64_t NumNoExtracts = | |
| getNumVectorInsts(TreeLoop, /*CountExtracts=*/false); | |
| Instruction *Root = getRootNode().getMainOp(); | |
| if (NumNoExtracts <= NumScalar && | |
| hasHighRegisterPressure(Opts, *Root->getParent(), | |
| getValueType(Root, Opts.slp_revec), *TTI)) | |
| NumVector = NumNoExtracts; | |
| } | |
| LLVM_DEBUG(dbgs() << "SLP: Inst count check: vector=" << NumVector | |
| << " scalar=" << NumScalar << "\n"); | |
| if (NumVector > NumScalar && !bypassesInstCountCheck(TreeCost)) { | |
| LLVM_DEBUG(dbgs() << "SLP: Rejecting tree: vector inst count " | |
| << NumVector << " > scalar inst count " << NumScalar | |
| << ".\n"); | |
| return InstructionCost::getInvalid(); | |
| } | |
| } | |
| InstructionCost Cost = TreeCost; | |
| SmallDenseMap<std::tuple<const TreeEntry *, Value *, Instruction *>, uint64_t> | |
| EntryToScale; | |
| auto ScaleCost = [&](InstructionCost C, const TreeEntry &TE, | |
| Value *Scalar = nullptr, Instruction *U = nullptr) { | |
| if (!C.isValid() || C == 0) | |
| return C; | |
| uint64_t &Scale = | |
| EntryToScale.try_emplace(std::make_tuple(&TE, Scalar, U), 0) | |
| .first->getSecond(); | |
| if (!Scale) | |
| Scale = getScaleToLoopIterations(TE, Scalar, U); | |
| LLVM_DEBUG(dbgs() << "Scale " << Scale << " For entry " << TE.Idx << "\n"); | |
| return C * Scale; | |
| }; | |
| Instruction *ReductionRoot = RdxRoot; | |
| if (UserIgnoreList) { | |
| // Scale reduction cost to the factor of the loop nest trip count. | |
| ReductionCost = ScaleCost(ReductionCost, getRootNode(), /*Scalar=*/nullptr, | |
| ReductionRoot); | |
| } | |
| // Add the cost for reduction. | |
| Cost += ReductionCost; | |
| if (any_of(ExternalUses, [](const ExternalUser &EU) { | |
| return isa<StructType>(EU.Scalar->getType()) && | |
| (EU.E.Idx != 0 || EU.E.State != TreeEntry::Vectorize || | |
| EU.E.getOpcode() != Instruction::InsertValue) && | |
| !isa_and_nonnull<ExtractValueInst>(EU.User); | |
| })) | |
| return InstructionCost::getInvalid(); | |
| // Skip trees, which are non-profitable even if there are insertelements with | |
| // external uses. | |
| constexpr unsigned CostLimit = 100; | |
| if (Cost >= -getCostThreshold() + CostLimit && | |
| (VectorizableTree.size() - DeletedNodes.size()) * | |
| getRootNode().getVectorFactor() < | |
| CostLimit) | |
| return Cost; | |
| if (Cost >= -getCostThreshold() && | |
| none_of(ExternalUses, [](const ExternalUser &EU) { | |
| return isa_and_nonnull<InsertElementInst>(EU.User); | |
| })) | |
| return Cost; | |
| SmallDenseSet<std::pair<Value *, SmallVector<unsigned, 1>>, 16> | |
| ExtractCostCalculated; | |
| InstructionCost ExtractCost = 0; | |
| SmallVector<ShuffledInsertData<const TreeEntry *>> ShuffledInserts; | |
| SmallVector<APInt> DemandedElts; | |
| SmallDenseSet<Value *, 4> UsedInserts; | |
| DenseSet<std::pair<const TreeEntry *, Type *>> VectorCasts; | |
| std::optional<DenseMap<Value *, unsigned>> ValueToExtUses; | |
| DenseMap<const TreeEntry *, DenseSet<Value *>> ExtractsCount; | |
| SmallPtrSet<Value *, 4> ScalarOpsFromCasts; | |
| // Keep track {Scalar, Index, User} tuple. | |
| // On AArch64, this helps in fusing a mov instruction, associated with | |
| // extractelement, with fmul in the backend so that extractelement is free. | |
| SmallVector<std::tuple<Value *, User *, int>, 4> ScalarUserAndIdx; | |
| // Record every external use: a missing entry is indistinguishable from | |
| // lane 0 and is priced as a free extract by the extract-fusion cost model. | |
| for (ExternalUser &EU : ExternalUses) | |
| ScalarUserAndIdx.emplace_back(EU.Scalar, EU.User, EU.Lane); | |
| // Detect external uses that drive address computations: the scalar (through | |
| // an optional single-use index-promotion cast) is used as a GEP index. | |
| bool AllUsersGEPSWithStoresLoads = true; | |
| SmallVector<const Value *> Pointers; | |
| Type *UserScalarTy = nullptr; | |
| for (ExternalUser &EU : ExternalUses) { | |
| Value *Usr = EU.User; | |
| if (Usr && match(Usr, m_OneUse(m_ZExtOrSExt(m_Value())))) | |
| Usr = cast<Instruction>(Usr)->user_back(); | |
| auto *User = dyn_cast_if_present<GetElementPtrInst>(Usr); | |
| // Only a GEP that feeds a single load/store of a fixed access type drives | |
| // a real memory address computation. | |
| Type *AccessTy = nullptr; | |
| if (User && User->hasOneUse() && | |
| isa<LoadInst, StoreInst>(User->user_back())) | |
| AccessTy = getValueType(User->user_back(), Opts.slp_revec); | |
| if (AccessTy && !isa<ScalableVectorType>(AccessTy) && | |
| (!UserScalarTy || UserScalarTy == AccessTy)) { | |
| UserScalarTy = AccessTy; | |
| Pointers.push_back(User); | |
| } else { | |
| AllUsersGEPSWithStoresLoads = false; | |
| break; | |
| } | |
| } | |
| // Pre-pass: for each externally-used scalar, find the basic block at which | |
| // the extractelement will be placed by codegen. This mirrors what | |
| // vectorizeTree does: the extract is placed at the nearest common dominator | |
| // of all effective use sites. For a non-PHI user the effective site is the | |
| // user's own block; for a PHI user it is the incoming block for the scalar | |
| // operand (the predecessor of the PHI on the edge that carries the scalar). | |
| // Using the NCD of all effective sites rather than the first-encountered | |
| // user's block makes the extract-cost scale order-independent and correct | |
| // even when users live in different loop nests. | |
| SmallDenseMap<Value *, BasicBlock *> ScalarToExtractBlock; | |
| for (const ExternalUser &EU : | |
| make_filter_range(ExternalUses, [](const ExternalUser &EU) { | |
| return EU.User && !isa<InsertElementInst>(EU.User); | |
| })) { | |
| if (EphValues.count(EU.User)) | |
| continue; | |
| BasicBlock *UserParent = cast<Instruction>(EU.User)->getParent(); | |
| if (!DT->isReachableFromEntry(UserParent) || UserParent->isEHPad() || | |
| isa<UnreachableInst>(UserParent->getTerminator())) | |
| continue; | |
| BasicBlock *UseBlock = nullptr; | |
| if (auto *PHI = dyn_cast<PHINode>(EU.User)) { | |
| // When the PHI itself is inside a loop, the extractelement is placed | |
| // in the incoming block for the scalar operand (the predecessor edge), | |
| // not in the PHI's own block. This applies to LCSSA phis at an inner- | |
| // loop exit that are still inside an outer loop: the incoming block is | |
| // in the inner loop while the PHI block is in the outer loop. | |
| // When the PHI is outside all loops (a true loop-exit phi), codegen | |
| // uses a vector phi at the exit block and the extract stays there | |
| // (scale = 1), so we keep the PHI's own block as the effective site. | |
| if (LI->getLoopFor(PHI->getParent())) { | |
| for (unsigned Idx : seq<unsigned>(PHI->getNumIncomingValues())) { | |
| if (PHI->getIncomingValue(Idx) != EU.Scalar) | |
| continue; | |
| BasicBlock *InBB = PHI->getIncomingBlock(Idx); | |
| UseBlock = | |
| UseBlock ? DT->findNearestCommonDominator(UseBlock, InBB) : InBB; | |
| } | |
| } | |
| if (!UseBlock) | |
| UseBlock = cast<Instruction>(EU.User)->getParent(); | |
| } else { | |
| UseBlock = cast<Instruction>(EU.User)->getParent(); | |
| } | |
| auto [It, Inserted] = ScalarToExtractBlock.try_emplace(EU.Scalar, UseBlock); | |
| if (!Inserted && It->second && UseBlock) | |
| It->second = DT->findNearestCommonDominator(It->second, UseBlock); | |
| } | |
| SmallDenseSet<std::pair<Value *, Value *>, 8> CheckedScalarUser; | |
| for (ExternalUser &EU : ExternalUses) { | |
| LLVM_DEBUG(dbgs() << "SLP: Computing cost for external use of TreeEntry " | |
| << EU.E.Idx << " in lane " << EU.Lane << "\n"); | |
| LLVM_DEBUG(if (EU.User) dbgs() << " User:" << *EU.User << "\n"; | |
| else dbgs() << " User: nullptr\n"); | |
| LLVM_DEBUG(dbgs() << " Use: " << EU.Scalar->getNameOrAsOperand() << "\n"); | |
| // Uses by ephemeral values are free (because the ephemeral value will be | |
| // removed prior to code generation, and so the extraction will be | |
| // removed as well). | |
| if (EphValues.count(EU.User)) | |
| continue; | |
| // Check if the scalar for the given user or all users is accounted already. | |
| if (!CheckedScalarUser.insert(std::make_pair(EU.Scalar, EU.User)).second || | |
| (EU.User && | |
| CheckedScalarUser.contains(std::make_pair(EU.Scalar, nullptr)))) | |
| continue; | |
| // Used in unreachable blocks or in EH pads (rarely executed) or is | |
| // terminated with unreachable instruction. | |
| if (BasicBlock *UserParent = | |
| EU.User ? cast<Instruction>(EU.User)->getParent() : nullptr; | |
| UserParent && | |
| (!DT->isReachableFromEntry(UserParent) || UserParent->isEHPad() || | |
| isa<UnreachableInst>(UserParent->getTerminator()))) | |
| continue; | |
| // No extract cost for vector "scalar" if REVEC is disabled. | |
| if (isVectorizedTy(EU.Scalar->getType()) && | |
| (!Opts.slp_revec || | |
| (EU.E.hasState() && EU.E.getOpcode() == Instruction::InsertElement))) | |
| continue; | |
| if (isa<InsertValueInst>(EU.Scalar)) | |
| continue; | |
| // If found user is an insertelement, do not calculate extract cost but try | |
| // to detect it as a final shuffled/identity match. | |
| // TODO: what if a user is insertvalue when REVEC is enabled? | |
| if (auto *VU = dyn_cast_if_present<InsertElementInst>(EU.User); | |
| VU && VU->getOperand(1) == EU.Scalar) { | |
| if (auto *FTy = dyn_cast<FixedVectorType>(VU->getType())) { | |
| if (!UsedInserts.insert(VU).second) | |
| continue; | |
| std::optional<unsigned> InsertIdx = getElementIndex(VU); | |
| if (InsertIdx) { | |
| const TreeEntry *ScalarTE = &EU.E; | |
| auto *It = find_if( | |
| ShuffledInserts, | |
| [this, VU](const ShuffledInsertData<const TreeEntry *> &Data) { | |
| // Checks if 2 insertelements are from the same buildvector. | |
| InsertElementInst *VecInsert = Data.InsertElements.front(); | |
| return areTwoInsertFromSameBuildVector( | |
| VU, VecInsert, [this](InsertElementInst *II) -> Value * { | |
| Value *Op0 = II->getOperand(0); | |
| if (isVectorized(II) && !isVectorized(Op0)) | |
| return nullptr; | |
| return Op0; | |
| }); | |
| }); | |
| int VecId = -1; | |
| if (It == ShuffledInserts.end()) { | |
| auto &Data = ShuffledInserts.emplace_back(); | |
| Data.InsertElements.emplace_back(VU); | |
| DemandedElts.push_back(APInt::getZero(FTy->getNumElements())); | |
| VecId = ShuffledInserts.size() - 1; | |
| auto It = MinBWs.find(ScalarTE); | |
| if (It != MinBWs.end() && | |
| VectorCasts | |
| .insert(std::make_pair(ScalarTE, FTy->getElementType())) | |
| .second) { | |
| unsigned BWSz = It->second.first; | |
| unsigned DstBWSz = DL->getTypeSizeInBits(FTy->getElementType()); | |
| unsigned VecOpcode; | |
| if (DstBWSz < BWSz) | |
| VecOpcode = Instruction::Trunc; | |
| else | |
| VecOpcode = | |
| It->second.second ? Instruction::SExt : Instruction::ZExt; | |
| InstructionCost C = TTI->getCastInstrCost( | |
| VecOpcode, FTy, | |
| getWidenedType(IntegerType::get(FTy->getContext(), BWSz), | |
| FTy->getNumElements()), | |
| getCastContextHint(*ScalarTE), CostKind); | |
| LLVM_DEBUG(dbgs() << "SLP: Adding cost " << C | |
| << " for extending externally used vector with " | |
| "non-equal minimum bitwidth.\n"); | |
| Cost += C; | |
| } | |
| } else { | |
| if (isFirstInsertElement(VU, It->InsertElements.front())) | |
| It->InsertElements.front() = VU; | |
| VecId = std::distance(ShuffledInserts.begin(), It); | |
| } | |
| int InIdx = *InsertIdx; | |
| SmallVectorImpl<int> &Mask = | |
| ShuffledInserts[VecId].ValueMasks[ScalarTE]; | |
| if (Mask.empty()) | |
| Mask.assign(FTy->getNumElements(), PoisonMaskElem); | |
| Mask[InIdx] = EU.Lane; | |
| DemandedElts[VecId].setBit(InIdx); | |
| continue; | |
| } | |
| } | |
| } | |
| // If we plan to rewrite the tree in a smaller type, we will need to sign | |
| // extend the extracted value back to the original type. Here, we account | |
| // for the extract and the added cost of the sign extend if needed. | |
| InstructionCost ExtraCost = TTI::TCC_Free; | |
| auto *ScalarTy = EU.Scalar->getType(); | |
| const unsigned BundleWidth = EU.E.getVectorFactor(); | |
| assert(EU.Lane < BundleWidth && "Extracted lane out of bounds."); | |
| auto *VecTy = getWidenedType(ScalarTy, BundleWidth); | |
| const TreeEntry *Entry = &EU.E; | |
| auto It = MinBWs.find(Entry); | |
| SmallVector<unsigned, 1> Indices; | |
| if (isa<StructType>(VecTy)) { | |
| assert(EU.User && "Expected user for struct extract"); | |
| const auto *EV = cast<ExtractValueInst>(EU.User); | |
| Indices.assign(EV->getIndices()); | |
| } | |
| // We only add extract cost once for the same scalar and struct field. | |
| auto ExtractKey = std::make_pair(EU.Scalar, Indices); | |
| if (!isa_and_nonnull<InsertElementInst>(EU.User) && | |
| !ExtractCostCalculated.insert(ExtractKey).second) | |
| continue; | |
| if (It != MinBWs.end()) { | |
| Type *MinTy = IntegerType::get(F->getContext(), It->second.first); | |
| if (auto *VecTy = dyn_cast<FixedVectorType>(ScalarTy)) | |
| MinTy = getWidenedType(MinTy, VecTy->getNumElements()); | |
| unsigned Extend = isKnownNonNegative(EU.Scalar, SimplifyQuery(*DL)) | |
| ? Instruction::ZExt | |
| : Instruction::SExt; | |
| VecTy = getWidenedType(MinTy, BundleWidth); | |
| ExtraCost = | |
| getExtractWithExtendCost(*TTI, Opts.slp_revec, Extend, ScalarTy, | |
| cast<VectorType>(VecTy), EU.Lane, CostKind); | |
| LLVM_DEBUG(dbgs() << " ExtractExtend or ExtractSubvec cost: " | |
| << ExtraCost << "\n"); | |
| } else { | |
| Type *ExtractTy = VecTy; | |
| if (auto *ST = dyn_cast<StructType>(VecTy)) { | |
| ExtractTy = ExtractValueInst::getIndexedType(ST, Indices); | |
| } | |
| ExtraCost = getVectorInstrCost( | |
| *TTI, Opts.slp_revec, ScalarTy, Instruction::ExtractElement, | |
| ExtractTy, CostKind, EU.Lane, EU.Scalar, ScalarUserAndIdx); | |
| LLVM_DEBUG(dbgs() << " ExtractElement cost for " << *ScalarTy << " from " | |
| << *VecTy << ": " << ExtraCost << "\n"); | |
| } | |
| // Leave the scalar instructions as is if they are cheaper than extracts. | |
| if (Entry->Idx != 0 || Entry->getOpcode() == Instruction::GetElementPtr || | |
| Entry->getOpcode() == Instruction::Load) { | |
| // Checks if the user of the external scalar is phi in loop body. | |
| auto IsPhiInLoop = [&](const ExternalUser &U) { | |
| if (auto *Phi = dyn_cast_if_present<PHINode>(U.User)) { | |
| auto *I = cast<Instruction>(U.Scalar); | |
| const Loop *L = LI->getLoopFor(Phi->getParent()); | |
| return L && (Phi->getParent() == I->getParent() || | |
| L == LI->getLoopFor(I->getParent())); | |
| } | |
| return false; | |
| }; | |
| if (!ValueToExtUses) { | |
| ValueToExtUses.emplace(); | |
| // Ignore phis in loops. | |
| for (const auto &P : | |
| make_filter_range(enumerate(ExternalUses), [&](const auto &P) { | |
| return !IsPhiInLoop(P.value()); | |
| })) | |
| ValueToExtUses->try_emplace(P.value().Scalar, P.index()); | |
| } | |
| // Can use original instruction, if no operands vectorized or they are | |
| // marked as externally used already. | |
| auto *Inst = cast<Instruction>(EU.Scalar); | |
| InstructionCost ScalarCost = TTI->getInstructionCost(Inst, CostKind); | |
| auto OperandIsScalar = [&](Value *V) { | |
| if (!isVectorized(V)) { | |
| // Some extractelements might be not vectorized, but | |
| // transformed into shuffle and removed from the function, | |
| // consider it here. | |
| if (auto *EE = dyn_cast<ExtractElementInst>(V)) | |
| return !EE->hasOneUse() || !MustGather.contains(EE); | |
| return true; | |
| } | |
| if (isa<StructType>(V->getType())) | |
| return false; | |
| return ValueToExtUses->contains(V); | |
| }; | |
| bool CanBeUsedAsScalar = all_of(Inst->operands(), OperandIsScalar); | |
| bool CanBeUsedAsScalarCast = false; | |
| if (auto *CI = dyn_cast<CastInst>(Inst); CI && !CanBeUsedAsScalar) { | |
| if (auto *Op = dyn_cast<Instruction>(CI->getOperand(0)); | |
| Op && all_of(Op->operands(), OperandIsScalar)) { | |
| InstructionCost OpCost = | |
| (isVectorized(Op) && !ValueToExtUses->contains(Op)) | |
| ? TTI->getInstructionCost(Op, CostKind) | |
| : 0; | |
| if (ScalarCost + OpCost <= ExtraCost) { | |
| CanBeUsedAsScalar = CanBeUsedAsScalarCast = true; | |
| ScalarCost += OpCost; | |
| } | |
| } | |
| } | |
| if (CanBeUsedAsScalar) { | |
| bool KeepScalar = ScalarCost <= ExtraCost; | |
| // Try to keep original scalar if the user is the phi node from the same | |
| // block as the root phis, currently vectorized. It allows to keep | |
| // better ordering info of PHIs, being vectorized currently. | |
| bool IsProfitablePHIUser = | |
| (KeepScalar || (ScalarCost - ExtraCost <= TTI::TCC_Basic && | |
| getRootNodeScalars().size() > 2)) && | |
| getRootNode().hasState() && | |
| getRootNode().getOpcode() == Instruction::PHI && | |
| !Inst->hasNUsesOrMore(UsesLimit) && | |
| none_of(Inst->users(), | |
| [&](User *U) { | |
| auto *PHIUser = dyn_cast<PHINode>(U); | |
| return (!PHIUser || | |
| PHIUser->getParent() != | |
| getRootNode().getMainOp()->getParent()) && | |
| !isVectorized(U); | |
| }) && | |
| count_if(Entry->Scalars, [&](Value *V) { | |
| return ValueToExtUses->contains(V); | |
| }) <= 2; | |
| if (IsProfitablePHIUser) { | |
| KeepScalar = true; | |
| } else if (KeepScalar && ScalarCost != TTI::TCC_Free && | |
| ExtraCost - ScalarCost <= TTI::TCC_Basic && | |
| (!GatheredLoadsEntriesFirst.has_value() || | |
| Entry->Idx < *GatheredLoadsEntriesFirst)) { | |
| unsigned ScalarUsesCount = count_if(Entry->Scalars, [&](Value *V) { | |
| return ValueToExtUses->contains(V); | |
| }); | |
| auto It = ExtractsCount.find(Entry); | |
| if (It != ExtractsCount.end()) { | |
| assert(ScalarUsesCount >= It->getSecond().size() && | |
| "Expected total number of external uses not less than " | |
| "number of scalar uses."); | |
| ScalarUsesCount -= It->getSecond().size(); | |
| } | |
| // Keep original scalar if number of externally used instructions in | |
| // the same entry is not power of 2. It may help to do some extra | |
| // vectorization for now. | |
| KeepScalar = ScalarUsesCount <= 1 || !has_single_bit(ScalarUsesCount); | |
| } | |
| if (KeepScalar) { | |
| ExternalUsesAsOriginalScalar.insert(EU.Scalar); | |
| // Struct operands cannot be rebuilt by the !User extraction | |
| // path (it has no insertvalue chain), so leave their existing | |
| // ExtractValueInst user in place. | |
| for (Value *V : make_filter_range(Inst->operands(), [](Value *V) { | |
| return !isa<StructType>(V->getType()); | |
| })) { | |
| auto It = ValueToExtUses->find(V); | |
| if (It != ValueToExtUses->end()) { | |
| // Replace all uses to avoid compiler crash. | |
| ExternalUses[It->second].User = nullptr; | |
| } | |
| } | |
| ExtraCost = ScalarCost; | |
| if (!IsPhiInLoop(EU)) | |
| ExtractsCount[Entry].insert(Inst); | |
| if (CanBeUsedAsScalarCast) { | |
| ScalarOpsFromCasts.insert(Inst->getOperand(0)); | |
| // Update the users of the operands of the cast operand to avoid | |
| // compiler crash. | |
| if (auto *IOp = dyn_cast<Instruction>(Inst->getOperand(0))) { | |
| for (Value *V : make_filter_range(IOp->operands(), [](Value *V) { | |
| return !isa<StructType>(V->getType()); | |
| })) { | |
| auto It = ValueToExtUses->find(V); | |
| if (It != ValueToExtUses->end()) { | |
| // Replace all uses to avoid compiler crash. | |
| ExternalUses[It->second].User = nullptr; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // Scale the extract cost by the execution frequency of the block where | |
| // codegen will place the extractelement. That block is the nearest common | |
| // dominator of all effective use sites (precomputed in ScalarToExtractBlock | |
| // above), which is order-independent. For scalars kept as originals the | |
| // existing ScaleCost path (user-block based) remains correct, since the | |
| // scalar instruction executes at its definition site's frequency. | |
| if (!ExternalUsesAsOriginalScalar.contains(EU.Scalar)) { | |
| if (ExtraCost.isValid() && ExtraCost != 0) { | |
| if (!EU.User) { | |
| // No external user instruction is recorded (User == nullptr): the | |
| // scalar stays live in vectorized instructions or is used as an | |
| // extra arg, and is not present in ScalarToExtractBlock (the | |
| // pre-pass only records sites of real users). vectorizeTree() then | |
| // places the extractelement right after the vectorized instruction | |
| // (in the entry's block) and replaces the scalar uses with it, so | |
| // scale by the entry block's execution frequency to match that | |
| // placement. | |
| ExtraCost = ScaleCost(ExtraCost, *Entry, EU.Scalar, /*U=*/nullptr); | |
| } else { | |
| BasicBlock *ExtractBB = ScalarToExtractBlock.lookup(EU.Scalar); | |
| if (const Loop *L = ExtractBB ? LI->getLoopFor(ExtractBB) : nullptr) { | |
| uint64_t Scale = getLoopNestScale( | |
| findInnermostNonInvariantLoop(L, ArrayRef<Value *>(EU.Scalar))); | |
| LLVM_DEBUG(dbgs() | |
| << "SLP: Extract scale " << Scale << " (NCD block) for " | |
| << EU.Scalar->getNameOrAsOperand() << "\n"); | |
| ExtraCost *= Scale; | |
| } | |
| } | |
| } | |
| } else { | |
| ExtraCost = ScaleCost(ExtraCost, *Entry, EU.Scalar, | |
| cast_if_present<Instruction>(EU.User)); | |
| } | |
| ExtractCost += ExtraCost; | |
| } | |
| // Charge the pointer-chain cost difference once when every escaped scalar | |
| // is used only to drive an address computation (see the detection loop | |
| // above). Vectorizing the tree in this pattern forces lane extracts (or a | |
| // vector GEP with unknown stride) to drive the address computation, which is | |
| // typically more expensive than keeping the indices scalar in a unit-stride | |
| // address chain. Add the delta once rather than per external use. | |
| if (AllUsersGEPSWithStoresLoads && !Pointers.empty()) { | |
| const TreeEntry &RootEntry = getRootNode(); | |
| const Value *CommonBase = getUnderlyingObject(Pointers.front()); | |
| if (all_of(Pointers, [CommonBase](const Value *P) { | |
| return getUnderlyingObject(P) == CommonBase; | |
| })) { | |
| auto *VecTy = getWidenedType(UserScalarTy, RootEntry.Scalars.size()); | |
| InstructionCost ScalarGEPCost = TTI->getPointersChainCost( | |
| Pointers, CommonBase, TTI::PointersChainInfo::getUnitStride(), | |
| UserScalarTy, CostKind); | |
| InstructionCost VectorGEPCost = TTI->getPointersChainCost( | |
| Pointers, CommonBase, TTI::PointersChainInfo::getUnknownStride(), | |
| VecTy, CostKind); | |
| ExtractCost += ScaleCost(VectorGEPCost - ScalarGEPCost, RootEntry); | |
| } | |
| } | |
| // Insert externals for extract of operands of casts to be emitted as scalars | |
| // instead of extractelement. | |
| for (Value *V : ScalarOpsFromCasts) { | |
| ExternalUsesAsOriginalScalar.insert(V); | |
| if (ArrayRef<TreeEntry *> TEs = getTreeEntries(V); !TEs.empty()) { | |
| const auto *It = find_if_not(TEs, [&](TreeEntry *TE) { | |
| return TransformedToGatherNodes.contains(TE) || | |
| DeletedNodes.contains(TE); | |
| }); | |
| if (It != TEs.end()) { | |
| const TreeEntry *UserTE = *It; | |
| ExternalUses.emplace_back(V, nullptr, *UserTE, | |
| UserTE->findLaneForValue(V)); | |
| } | |
| } | |
| } | |
| // Add reduced value cost, if resized. | |
| if (!VectorizedVals.empty()) { | |
| const TreeEntry &Root = getRootNode(); | |
| auto BWIt = MinBWs.find(&Root); | |
| if (BWIt != MinBWs.end()) { | |
| Type *DstTy = Root.Scalars.front()->getType(); | |
| unsigned OriginalSz = DL->getTypeSizeInBits(DstTy->getScalarType()); | |
| unsigned SrcSz = | |
| ReductionBitWidth == 0 ? BWIt->second.first : ReductionBitWidth; | |
| if (OriginalSz != SrcSz) { | |
| unsigned Opcode = Instruction::Trunc; | |
| if (OriginalSz > SrcSz) | |
| Opcode = BWIt->second.second ? Instruction::SExt : Instruction::ZExt; | |
| Type *SrcTy = IntegerType::get(DstTy->getContext(), SrcSz); | |
| if (auto *VecTy = dyn_cast<FixedVectorType>(DstTy)) { | |
| assert(Opts.slp_revec && "Only supported by REVEC."); | |
| SrcTy = getWidenedType(SrcTy, VecTy->getNumElements()); | |
| } | |
| InstructionCost CastCost = TTI->getCastInstrCost( | |
| Opcode, DstTy, SrcTy, TTI::CastContextHint::None, CostKind); | |
| CastCost = ScaleCost(CastCost, Root, /*Scalar=*/nullptr, ReductionRoot); | |
| Cost += CastCost; | |
| } | |
| } | |
| } | |
| // Buildvector with externally used scalars, which should remain as scalars, | |
| // should not be vectorized, the compiler may hang. | |
| if (getCostThreshold() < 0 && VectorizableTree.size() > 1 && | |
| isa<InsertElementInst>(VectorizableTree[0]->Scalars[0]) && | |
| VectorizableTree[1]->hasState() && | |
| VectorizableTree[1]->State == TreeEntry::Vectorize && | |
| all_of(VectorizableTree[1]->Scalars, [&](Value *V) { | |
| return ExternalUsesAsOriginalScalar.contains(V); | |
| })) | |
| return InstructionCost::getInvalid(); | |
| Cost += ExtractCost; | |
| auto &&ResizeToVF = [this, &Cost](const TreeEntry *TE, ArrayRef<int> Mask, | |
| bool ForSingleMask) { | |
| InstructionCost C = 0; | |
| unsigned VF = Mask.size(); | |
| unsigned VecVF = TE->getVectorFactor(); | |
| bool HasLargeIndex = | |
| any_of(Mask, [VF](int Idx) { return Idx >= static_cast<int>(VF); }); | |
| if ((VF != VecVF && HasLargeIndex) || | |
| !ShuffleVectorInst::isIdentityMask(Mask, VF)) { | |
| if (HasLargeIndex) { | |
| SmallVector<int> OrigMask(VecVF, PoisonMaskElem); | |
| std::copy(Mask.begin(), std::next(Mask.begin(), std::min(VF, VecVF)), | |
| OrigMask.begin()); | |
| C = getShuffleCost( | |
| *TTI, TTI::SK_PermuteSingleSrc, | |
| cast<VectorType>(getWidenedType(TE->getMainOp()->getType(), VecVF)), | |
| CostKind, OrigMask); | |
| LLVM_DEBUG( | |
| dbgs() << "SLP: Adding cost " << C | |
| << " for final shuffle of insertelement external users.\n"; | |
| TE->dump(); dbgs() << "SLP: Current total cost = " << Cost << "\n"); | |
| Cost += C; | |
| return std::make_pair(TE, true); | |
| } | |
| if (!ForSingleMask) { | |
| SmallVector<int> ResizeMask(VF, PoisonMaskElem); | |
| for (unsigned I = 0; I < VF; ++I) { | |
| if (Mask[I] != PoisonMaskElem) | |
| ResizeMask[Mask[I]] = Mask[I]; | |
| } | |
| if (!ShuffleVectorInst::isIdentityMask(ResizeMask, VF)) | |
| C = getShuffleCost(*TTI, TTI::SK_PermuteSingleSrc, | |
| cast<VectorType>(getWidenedType( | |
| TE->getMainOp()->getType(), VecVF)), | |
| CostKind, ResizeMask); | |
| LLVM_DEBUG( | |
| dbgs() << "SLP: Adding cost " << C | |
| << " for final shuffle of insertelement external users.\n"; | |
| TE->dump(); dbgs() << "SLP: Current total cost = " << Cost << "\n"); | |
| Cost += C; | |
| } | |
| } | |
| return std::make_pair(TE, false); | |
| }; | |
| // Calculate the cost of the reshuffled vectors, if any. | |
| for (int I = 0, E = ShuffledInserts.size(); I < E; ++I) { | |
| Value *Base = ShuffledInserts[I].InsertElements.front()->getOperand(0); | |
| auto Vector = ShuffledInserts[I].ValueMasks.takeVector(); | |
| unsigned VF = 0; | |
| auto EstimateShufflesCost = [&](ArrayRef<int> Mask, | |
| ArrayRef<const TreeEntry *> TEs) { | |
| assert((TEs.size() == 1 || TEs.size() == 2) && | |
| "Expected exactly 1 or 2 tree entries."); | |
| if (TEs.size() == 1) { | |
| if (VF == 0) | |
| VF = TEs.front()->getVectorFactor(); | |
| auto *FTy = getWidenedType(TEs.back()->Scalars.front()->getType(), VF); | |
| if (!ShuffleVectorInst::isIdentityMask(Mask, VF) && | |
| !all_of(enumerate(Mask), [=](const auto &Data) { | |
| return Data.value() == PoisonMaskElem || | |
| (Data.index() < VF && | |
| static_cast<int>(Data.index()) == Data.value()); | |
| })) { | |
| InstructionCost C = | |
| getShuffleCost(*TTI, TTI::SK_PermuteSingleSrc, | |
| cast<VectorType>(FTy), CostKind, Mask); | |
| C = ScaleCost(C, *TEs.front()); | |
| LLVM_DEBUG(dbgs() << "SLP: Adding cost " << C | |
| << " for final shuffle of insertelement " | |
| "external users.\n"; | |
| TEs.front()->dump(); | |
| dbgs() << "SLP: Current total cost = " << Cost << "\n"); | |
| Cost += C; | |
| } | |
| } else { | |
| if (VF == 0) { | |
| if (TEs.front() && | |
| TEs.front()->getVectorFactor() == TEs.back()->getVectorFactor()) | |
| VF = TEs.front()->getVectorFactor(); | |
| else | |
| VF = Mask.size(); | |
| } | |
| auto *FTy = getWidenedType(TEs.back()->Scalars.front()->getType(), VF); | |
| InstructionCost C = getShuffleCost( | |
| *TTI, TTI::SK_PermuteTwoSrc, cast<VectorType>(FTy), CostKind, Mask); | |
| C = ScaleCost(C, *TEs.back()); | |
| LLVM_DEBUG(dbgs() << "SLP: Adding cost " << C | |
| << " for final shuffle of vector node and external " | |
| "insertelement users.\n"; | |
| if (TEs.front()) { TEs.front()->dump(); } TEs.back()->dump(); | |
| dbgs() << "SLP: Current total cost = " << Cost << "\n"); | |
| Cost += C; | |
| } | |
| VF = Mask.size(); | |
| return TEs.back(); | |
| }; | |
| (void)performExtractsShuffleAction<const TreeEntry>( | |
| MutableArrayRef(Vector.data(), Vector.size()), Base, | |
| [](const TreeEntry *E) { return E->getVectorFactor(); }, ResizeToVF, | |
| EstimateShufflesCost); | |
| InstructionCost InsertCost = TTI->getScalarizationOverhead( | |
| cast<FixedVectorType>( | |
| ShuffledInserts[I].InsertElements.front()->getType()), | |
| DemandedElts[I], | |
| /*Insert*/ true, /*Extract*/ false, CostKind); | |
| Cost -= InsertCost; | |
| } | |
| // Add the cost for reduced value resize (if required). | |
| if (ReductionBitWidth != 0) { | |
| assert(UserIgnoreList && "Expected reduction tree."); | |
| const TreeEntry &E = getRootNode(); | |
| auto It = MinBWs.find(&E); | |
| if (It != MinBWs.end() && It->second.first != ReductionBitWidth) { | |
| unsigned SrcSize = It->second.first; | |
| unsigned DstSize = ReductionBitWidth; | |
| unsigned Opcode = Instruction::Trunc; | |
| if (SrcSize < DstSize) { | |
| bool IsArithmeticExtendedReduction = | |
| all_of(*UserIgnoreList, [](Value *V) { | |
| auto *I = cast<Instruction>(V); | |
| return is_contained({Instruction::Add, Instruction::FAdd, | |
| Instruction::Mul, Instruction::FMul, | |
| Instruction::And, Instruction::Or, | |
| Instruction::Xor}, | |
| I->getOpcode()); | |
| }); | |
| if (IsArithmeticExtendedReduction) | |
| Opcode = | |
| Instruction::BitCast; // Handle it by getExtendedReductionCost | |
| else | |
| Opcode = It->second.second ? Instruction::SExt : Instruction::ZExt; | |
| } | |
| if (Opcode != Instruction::BitCast) { | |
| auto *SrcVecTy = | |
| getWidenedType(Builder.getIntNTy(SrcSize), E.getVectorFactor()); | |
| auto *DstVecTy = | |
| getWidenedType(Builder.getIntNTy(DstSize), E.getVectorFactor()); | |
| TTI::CastContextHint CCH = getCastContextHint(E); | |
| switch (E.getOpcode()) { | |
| case Instruction::SExt: | |
| case Instruction::ZExt: | |
| case Instruction::Trunc: { | |
| // Split and gather roots have no operand entry to take the context | |
| // hint from. | |
| if (E.isGather() || E.State == TreeEntry::SplitVectorize) | |
| break; | |
| const TreeEntry *OpTE = getOperandEntry(&E, 0); | |
| CCH = getCastContextHint(*OpTE); | |
| break; | |
| } | |
| default: | |
| break; | |
| } | |
| InstructionCost CastCost = | |
| TTI->getCastInstrCost(Opcode, DstVecTy, SrcVecTy, CCH, CostKind); | |
| CastCost = ScaleCost(CastCost, getRootNode(), /*Scalar=*/nullptr, | |
| ReductionRoot); | |
| Cost += CastCost; | |
| LLVM_DEBUG(dbgs() << "SLP: Adding cost " << CastCost | |
| << " for final resize for reduction from " << SrcVecTy | |
| << " to " << DstVecTy << "\n"; | |
| dbgs() << "SLP: Current total cost = " << Cost << "\n"); | |
| } | |
| } | |
| } | |
| std::optional<InstructionCost> SpillCost; | |
| if (Cost < -getCostThreshold()) { | |
| SpillCost = getSpillCost(); | |
| Cost += *SpillCost; | |
| } | |
| #ifndef NDEBUG | |
| SmallString<256> Str; | |
| { | |
| raw_svector_ostream OS(Str); | |
| OS << "SLP: Spill Cost = "; | |
| if (SpillCost) | |
| OS << *SpillCost; | |
| else | |
| OS << "<skipped>"; | |
| OS << ".\nSLP: Extract Cost = " << ExtractCost << ".\n"; | |
| if (ReductionRoot) | |
| OS << "SLP: Reduction Cost = " << ReductionCost << ".\n"; | |
| OS << "SLP: Total Cost = " << Cost << ".\n"; | |
| } | |
| LLVM_DEBUG(dbgs() << Str); | |
| if (Opts.view_slp_tree) | |
| ViewGraph(this, "SLP" + F->getName(), false, Str); | |
| #endif | |
| return Cost; | |
| } | |
| /// Tries to find extractelement instructions with constant indices from fixed | |
| /// vector type and gather such instructions into a bunch, which highly likely | |
| /// might be detected as a shuffle of 1 or 2 input vectors. If this attempt was | |
| /// successful, the matched scalars are replaced by poison values in \p VL for | |
| /// future analysis. | |
| std::optional<TTI::ShuffleKind> | |
| BoUpSLP::tryToGatherSingleRegisterExtractElements( | |
| MutableArrayRef<Value *> VL, SmallVectorImpl<int> &Mask) const { | |
| // Scan list of gathered scalars for extractelements that can be represented | |
| // as shuffles. | |
| MapVector<Value *, SmallVector<int>> VectorOpToIdx; | |
| SmallVector<int> UndefVectorExtracts; | |
| for (int I = 0, E = VL.size(); I < E; ++I) { | |
| auto *EI = dyn_cast<ExtractElementInst>(VL[I]); | |
| if (!EI) { | |
| if (isa<UndefValue>(VL[I])) | |
| UndefVectorExtracts.push_back(I); | |
| continue; | |
| } | |
| auto *VecTy = dyn_cast<FixedVectorType>(EI->getVectorOperandType()); | |
| if (!VecTy || !isa<ConstantInt, UndefValue>(EI->getIndexOperand())) | |
| continue; | |
| std::optional<unsigned> Idx = getExtractIndex(EI); | |
| // Undefined index. | |
| if (!Idx) { | |
| UndefVectorExtracts.push_back(I); | |
| continue; | |
| } | |
| if (Idx >= VecTy->getNumElements()) { | |
| UndefVectorExtracts.push_back(I); | |
| continue; | |
| } | |
| SmallBitVector ExtractMask(VecTy->getNumElements(), true); | |
| ExtractMask.reset(*Idx); | |
| if (isUndefVector</*IsPoisonOnly=*/true>(EI->getVectorOperand(), | |
| ExtractMask) | |
| .all()) { | |
| UndefVectorExtracts.push_back(I); | |
| continue; | |
| } | |
| VectorOpToIdx[EI->getVectorOperand()].push_back(I); | |
| } | |
| // Sort the vector operands by the maximum number of uses in extractelements. | |
| SmallVector<std::pair<Value *, SmallVector<int>>> Vectors = | |
| VectorOpToIdx.takeVector(); | |
| stable_sort(Vectors, [](const auto &P1, const auto &P2) { | |
| return P1.second.size() > P2.second.size(); | |
| }); | |
| // Find the best pair of the vectors or a single vector. | |
| const int UndefSz = UndefVectorExtracts.size(); | |
| unsigned SingleMax = 0; | |
| unsigned PairMax = 0; | |
| if (!Vectors.empty()) { | |
| SingleMax = Vectors.front().second.size() + UndefSz; | |
| if (Vectors.size() > 1) { | |
| auto *ItNext = std::next(Vectors.begin()); | |
| PairMax = SingleMax + ItNext->second.size(); | |
| } | |
| } | |
| if (SingleMax == 0 && PairMax == 0 && UndefSz == 0) | |
| return std::nullopt; | |
| // Check if better to perform a shuffle of 2 vectors or just of a single | |
| // vector. | |
| SmallVector<Value *> SavedVL(VL.begin(), VL.end()); | |
| SmallVector<Value *> GatheredExtracts( | |
| VL.size(), PoisonValue::get(VL.front()->getType())); | |
| if (SingleMax >= PairMax && SingleMax) { | |
| for (int Idx : Vectors.front().second) | |
| std::swap(GatheredExtracts[Idx], VL[Idx]); | |
| } else if (!Vectors.empty()) { | |
| for (unsigned Idx : {0, 1}) | |
| for (int Idx : Vectors[Idx].second) | |
| std::swap(GatheredExtracts[Idx], VL[Idx]); | |
| } | |
| // Add extracts from undefs too. | |
| for (int Idx : UndefVectorExtracts) | |
| std::swap(GatheredExtracts[Idx], VL[Idx]); | |
| // Check that gather of extractelements can be represented as just a | |
| // shuffle of a single/two vectors the scalars are extracted from. | |
| std::optional<TTI::ShuffleKind> Res = | |
| isFixedVectorShuffle(GatheredExtracts, Mask, AC, Cache); | |
| if (!Res || all_of(Mask, equal_to(PoisonMaskElem))) { | |
| // TODO: try to check other subsets if possible. | |
| // Restore the original VL and mask if attempt was not successful. | |
| copy(SavedVL, VL.begin()); | |
| Mask.assign(VL.size(), PoisonMaskElem); | |
| return std::nullopt; | |
| } | |
| // Restore unused scalars from mask, if some of the extractelements were not | |
| // selected for shuffle. | |
| for (int I = 0, E = GatheredExtracts.size(); I < E; ++I) { | |
| if (Mask[I] == PoisonMaskElem && !isa<PoisonValue>(GatheredExtracts[I]) && | |
| isa<UndefValue>(GatheredExtracts[I])) { | |
| std::swap(VL[I], GatheredExtracts[I]); | |
| continue; | |
| } | |
| auto *EI = dyn_cast<ExtractElementInst>(VL[I]); | |
| if (!EI || !isa<FixedVectorType>(EI->getVectorOperandType()) || | |
| !isa<ConstantInt, UndefValue>(EI->getIndexOperand()) || | |
| is_contained(UndefVectorExtracts, I)) | |
| continue; | |
| } | |
| return Res; | |
| } | |
| /// Tries to find extractelement instructions with constant indices from fixed | |
| /// vector type and gather such instructions into a bunch, which highly likely | |
| /// might be detected as a shuffle of 1 or 2 input vectors. If this attempt was | |
| /// successful, the matched scalars are replaced by poison values in \p VL for | |
| /// future analysis. | |
| SmallVector<std::optional<TTI::ShuffleKind>> | |
| BoUpSLP::tryToGatherExtractElements(SmallVectorImpl<Value *> &VL, | |
| SmallVectorImpl<int> &Mask, | |
| unsigned NumParts) const { | |
| assert(NumParts > 0 && "NumParts expected be greater than or equal to 1."); | |
| SmallVector<std::optional<TTI::ShuffleKind>> ShufflesRes(NumParts); | |
| Mask.assign(VL.size(), PoisonMaskElem); | |
| unsigned SliceSize = getPartNumElems(VL.size(), NumParts); | |
| for (unsigned Part : seq<unsigned>(NumParts)) { | |
| // Scan list of gathered scalars for extractelements that can be represented | |
| // as shuffles. | |
| const unsigned PartOffset = Part * SliceSize; | |
| const unsigned PartSize = getNumElems(VL.size(), SliceSize, Part); | |
| // It may happen in case of revec, need to check no access out of bounds. | |
| if (PartOffset + PartSize > VL.size()) | |
| break; | |
| MutableArrayRef<Value *> SubVL = | |
| MutableArrayRef(VL).slice(PartOffset, PartSize); | |
| SmallVector<int> SubMask; | |
| std::optional<TTI::ShuffleKind> Res = | |
| tryToGatherSingleRegisterExtractElements(SubVL, SubMask); | |
| ShufflesRes[Part] = Res; | |
| copy(SubMask, std::next(Mask.begin(), Part * SliceSize)); | |
| if (SubVL.size() != SliceSize) | |
| break; | |
| } | |
| if (none_of(ShufflesRes, [](const std::optional<TTI::ShuffleKind> &Res) { | |
| return Res.has_value(); | |
| })) | |
| ShufflesRes.clear(); | |
| return ShufflesRes; | |
| } | |
| std::optional<TargetTransformInfo::ShuffleKind> | |
| BoUpSLP::isGatherShuffledSingleRegisterEntry( | |
| const TreeEntry *TE, ArrayRef<Value *> VL, MutableArrayRef<int> Mask, | |
| SmallVectorImpl<const TreeEntry *> &Entries, unsigned Part, bool ForOrder, | |
| unsigned SliceSize) { | |
| Entries.clear(); | |
| if (TE->Idx == 0) | |
| return std::nullopt; | |
| const unsigned MaskBase = Part * SliceSize; | |
| // TODO: currently checking only for Scalars in the tree entry, need to count | |
| // reused elements too for better cost estimation. | |
| auto GetUserEntry = [&](const TreeEntry *TE) { | |
| while (TE->UserTreeIndex && TE->UserTreeIndex.EdgeIdx == UINT_MAX) | |
| TE = TE->UserTreeIndex.UserTE; | |
| if (TE == &getRootNode()) | |
| return EdgeInfo(const_cast<TreeEntry *>(TE), 0); | |
| return TE->UserTreeIndex; | |
| }; | |
| auto HasGatherUser = [&](const TreeEntry *TE) { | |
| while (TE->Idx != 0 && TE->UserTreeIndex) { | |
| if (TE->UserTreeIndex.EdgeIdx == UINT_MAX) | |
| return true; | |
| TE = TE->UserTreeIndex.UserTE; | |
| } | |
| return false; | |
| }; | |
| const EdgeInfo TEUseEI = GetUserEntry(TE); | |
| if (!TEUseEI || (TEUseEI.UserTE->Idx == 0 && TEUseEI.UserTE->isGather() && | |
| !TEUseEI.UserTE->hasState())) | |
| return std::nullopt; | |
| const Instruction *TEInsertPt = &getLastInstructionInBundle(TEUseEI.UserTE); | |
| const BasicBlock *TEInsertBlock = nullptr; | |
| // Main node of PHI entries keeps the correct order of operands/incoming | |
| // blocks. | |
| if (auto *PHI = TEUseEI.UserTE->hasState() | |
| ? dyn_cast<PHINode>(TEUseEI.UserTE->getMainOp()) | |
| : nullptr; | |
| PHI && TEUseEI.UserTE->State != TreeEntry::SplitVectorize) { | |
| TEInsertBlock = PHI->getIncomingBlock(TEUseEI.EdgeIdx); | |
| TEInsertPt = TEInsertBlock->getTerminator(); | |
| } else { | |
| TEInsertBlock = TEInsertPt->getParent(); | |
| } | |
| if (!DT->isReachableFromEntry(TEInsertBlock)) | |
| return std::nullopt; | |
| auto *NodeUI = DT->getNode(TEInsertBlock); | |
| assert(NodeUI && "Should only process reachable instructions"); | |
| SmallPtrSet<Value *, 4> GatheredScalars(llvm::from_range, VL); | |
| auto CheckOrdering = [&](const Instruction *InsertPt) { | |
| // Argument InsertPt is an instruction where vector code for some other | |
| // tree entry (one that shares one or more scalars with TE) is going to be | |
| // generated. This lambda returns true if insertion point of vector code | |
| // for the TE dominates that point (otherwise dependency is the other way | |
| // around). The other node is not limited to be of a gather kind. Gather | |
| // nodes are not scheduled and their vector code is inserted before their | |
| // first user. If user is PHI, that is supposed to be at the end of a | |
| // predecessor block. Otherwise it is the last instruction among scalars of | |
| // the user node. So, instead of checking dependency between instructions | |
| // themselves, we check dependency between their insertion points for vector | |
| // code (since each scalar instruction ends up as a lane of a vector | |
| // instruction). | |
| const BasicBlock *InsertBlock = InsertPt->getParent(); | |
| auto *NodeEUI = DT->getNode(InsertBlock); | |
| if (!NodeEUI) | |
| return false; | |
| assert((NodeUI == NodeEUI) == | |
| (NodeUI->getDFSNumIn() == NodeEUI->getDFSNumIn()) && | |
| "Different nodes should have different DFS numbers"); | |
| // Check the order of the gather nodes users. | |
| if (TEInsertPt->getParent() != InsertBlock && | |
| (DT->dominates(NodeUI, NodeEUI) || !DT->dominates(NodeEUI, NodeUI))) | |
| return false; | |
| if (TEInsertPt->getParent() == InsertBlock && | |
| TEInsertPt->comesBefore(InsertPt)) | |
| return false; | |
| return true; | |
| }; | |
| // Find all tree entries used by the gathered values. If no common entries | |
| // found - not a shuffle. | |
| // Here we build a set of tree nodes for each gathered value and trying to | |
| // find the intersection between these sets. If we have at least one common | |
| // tree node for each gathered value - we have just a permutation of the | |
| // single vector. If we have 2 different sets, we're in situation where we | |
| // have a permutation of 2 input vectors. | |
| SmallVector<SmallPtrSet<const TreeEntry *, 4>> UsedTEs; | |
| SmallDenseMap<Value *, int> UsedValuesEntry; | |
| SmallPtrSet<const Value *, 16> VisitedValue; | |
| bool IsReusedNodeFound = false; | |
| auto CheckAndUseSameNode = [&](const TreeEntry *TEPtr) { | |
| // The node is reused - exit. | |
| if (IsReusedNodeFound) | |
| return false; | |
| if ((TEPtr->getVectorFactor() != VL.size() && | |
| TEPtr->Scalars.size() != VL.size()) || | |
| (!TEPtr->isSame(VL) && !TEPtr->isSame(TE->Scalars))) | |
| return false; | |
| IsReusedNodeFound = | |
| equal(TE->Scalars, TEPtr->Scalars) && | |
| equal(TE->ReorderIndices, TEPtr->ReorderIndices) && | |
| equal(TE->ReuseShuffleIndices, TEPtr->ReuseShuffleIndices); | |
| UsedTEs.clear(); | |
| UsedTEs.emplace_back().insert(TEPtr); | |
| auto IsNotConstant = [](Value *V) { return !isConstant(V); }; | |
| for (Value *V : make_filter_range(VL, IsNotConstant)) | |
| UsedValuesEntry.try_emplace(V, 0); | |
| return true; | |
| }; | |
| auto CheckParentNodes = [&](const TreeEntry *User1, const TreeEntry *User2, | |
| unsigned EdgeIdx) { | |
| const TreeEntry *Ptr1 = User1; | |
| const TreeEntry *Ptr2 = User2; | |
| SmallDenseMap<const TreeEntry *, unsigned> PtrToIdx; | |
| while (Ptr2) { | |
| PtrToIdx.try_emplace(Ptr2, EdgeIdx); | |
| EdgeIdx = Ptr2->UserTreeIndex.EdgeIdx; | |
| Ptr2 = Ptr2->UserTreeIndex.UserTE; | |
| } | |
| while (Ptr1) { | |
| unsigned Idx = Ptr1->UserTreeIndex.EdgeIdx; | |
| Ptr1 = Ptr1->UserTreeIndex.UserTE; | |
| if (auto It = PtrToIdx.find(Ptr1); It != PtrToIdx.end()) | |
| return Idx < It->second; | |
| } | |
| return false; | |
| }; | |
| // Cache `isUsedOutsideBlock(TEInsertPt)` - TEInsertPt is loop-invariant and | |
| // the function walks the instruction's user list. | |
| std::optional<bool> TEInsertPtUsedOutsideBlock; | |
| auto IsTEInsertPtUsedOutsideBlock = [&] { | |
| if (!TEInsertPtUsedOutsideBlock) | |
| TEInsertPtUsedOutsideBlock = | |
| isUsedOutsideBlock(const_cast<Instruction *>(TEInsertPt)); | |
| return *TEInsertPtUsedOutsideBlock; | |
| }; | |
| // Cache the TEUseEI/TEInsertPt-only prefix of the per-call lambda predicate | |
| // below - all of these depend only on outer-scope state, not the lambda's | |
| // arguments. | |
| const bool TEUseEIInsertPtUsedOutside = | |
| TEUseEI && TEUseEI.UserTE && TEUseEI.UserTE->hasCopyableElements() && | |
| !TEUseEI.UserTE->isCopyableElement( | |
| const_cast<Instruction *>(TEInsertPt)) && | |
| IsTEInsertPtUsedOutsideBlock(); | |
| auto CheckNonSchedulableOrdering = [&](const TreeEntry *E, | |
| Instruction *InsertPt) { | |
| return TEUseEIInsertPtUsedOutside && | |
| InsertPt->getNextNode() == TEInsertPt && | |
| (!E->hasCopyableElements() || !E->isCopyableElement(InsertPt) || | |
| !isUsedOutsideBlock(InsertPt)); | |
| }; | |
| // Cache the TEUseEI.UserTE-dependent predicate - it is invariant across the | |
| // double loop below. all_of with isUsedOutsideBlock walks each scalar's | |
| // users and is the expensive component. | |
| const bool TEUserNeedsEmitFirst = | |
| TEUseEI.UserTE->State == TreeEntry::Vectorize && | |
| TEUseEI.UserTE->hasState() && | |
| (TEUseEI.UserTE->getOpcode() != Instruction::PHI || | |
| TEUseEI.UserTE->isAltShuffle()) && | |
| all_of(TEUseEI.UserTE->Scalars, isUsedOutsideBlock); | |
| // Cache `all_of(UserTE->Scalars, isUsedOutsideBlock)` per UserTE - the | |
| // same UserTE may be encountered for many TEPtr values inside the loop. | |
| SmallDenseMap<const TreeEntry *, bool> ScalarsUsedOutsideBlockCache; | |
| auto AllScalarsUsedOutsideBlock = [&](const TreeEntry *UserTE) { | |
| auto [It, Inserted] = ScalarsUsedOutsideBlockCache.try_emplace(UserTE); | |
| if (!Inserted) | |
| return It->second; | |
| bool Res = all_of(UserTE->Scalars, isUsedOutsideBlock); | |
| It->second = Res; | |
| return Res; | |
| }; | |
| for (Value *V : VL) { | |
| if (isConstant(V) || !VisitedValue.insert(V).second) | |
| continue; | |
| // Build a list of tree entries where V is used. | |
| SmallPtrSet<const TreeEntry *, 4> VToTEs; | |
| SmallVector<const TreeEntry *> GatherNodes( | |
| ValueToGatherNodes.lookup(V).takeVector()); | |
| if (TransformedToGatherNodes.contains(TE)) { | |
| auto IsLiveUserGather = [&](TreeEntry *E) { | |
| return TE != E && TransformedToGatherNodes.contains(E) && | |
| E->UserTreeIndex && !E->UserTreeIndex.UserTE->isGather(); | |
| }; | |
| for (TreeEntry *E : | |
| make_filter_range(getSplitTreeEntries(V), IsLiveUserGather)) | |
| GatherNodes.push_back(E); | |
| for (TreeEntry *E : | |
| make_filter_range(getTreeEntries(V), IsLiveUserGather)) | |
| GatherNodes.push_back(E); | |
| } else if (const TreeEntry *E = getSameValuesTreeEntry(V, TE->Scalars); | |
| E && !TEUserNeedsEmitFirst && | |
| TransformedToGatherNodes.contains(E) && E->UserTreeIndex && | |
| E->UserTreeIndex.UserTE == TE->UserTreeIndex.UserTE && | |
| !E->UserTreeIndex.UserTE->isGather()) { | |
| // Regular gathers reuse only perfectly matched transformed nodes of the | |
| // same user. Gathers, emitted before their user, cannot reuse transformed | |
| // nodes, which are emitted with the user. | |
| GatherNodes.push_back(E); | |
| } | |
| for (const TreeEntry *TEPtr : | |
| make_filter_range(GatherNodes, [&](const TreeEntry *TEPtr) { | |
| return TEPtr != TE && TEPtr->Idx != 0 && | |
| !DeletedNodes.contains(TEPtr); | |
| })) { | |
| assert(any_of(TEPtr->Scalars, | |
| [&](Value *V) { return GatheredScalars.contains(V); }) && | |
| "Must contain at least single gathered value."); | |
| assert(TEPtr->UserTreeIndex && | |
| "Expected only single user of a gather node."); | |
| if (any_of(TEPtr->CombinedEntriesWithIndices, | |
| [&](const auto &P) { return P.first == TE->Idx; })) | |
| continue; | |
| const EdgeInfo &UseEI = TEPtr->UserTreeIndex; | |
| PHINode *UserPHI = (UseEI.UserTE->State != TreeEntry::SplitVectorize && | |
| UseEI.UserTE->hasState()) | |
| ? dyn_cast<PHINode>(UseEI.UserTE->getMainOp()) | |
| : nullptr; | |
| Instruction *InsertPt = | |
| UserPHI ? UserPHI->getIncomingBlock(UseEI.EdgeIdx)->getTerminator() | |
| : &getLastInstructionInBundle(UseEI.UserTE); | |
| if (TEInsertPt == InsertPt) { | |
| // Check nodes, which might be emitted first. | |
| if (TEUserNeedsEmitFirst) { | |
| if (UseEI.UserTE->State != TreeEntry::Vectorize || | |
| (UseEI.UserTE->hasState() && | |
| UseEI.UserTE->getOpcode() == Instruction::PHI && | |
| !UseEI.UserTE->isAltShuffle()) || | |
| !AllScalarsUsedOutsideBlock(UseEI.UserTE)) | |
| continue; | |
| } | |
| // If the schedulable insertion point is used in multiple entries - just | |
| // exit, no known ordering at this point, available only after real | |
| // scheduling. | |
| if (!doesNotNeedToBeScheduled(InsertPt) && | |
| (TEUseEI.UserTE != UseEI.UserTE || TEUseEI.EdgeIdx < UseEI.EdgeIdx)) | |
| continue; | |
| // If the users are the PHI nodes with the same incoming blocks - skip. | |
| if (TEUseEI.UserTE->State == TreeEntry::Vectorize && | |
| TEUseEI.UserTE->getOpcode() == Instruction::PHI && | |
| UseEI.UserTE->State == TreeEntry::Vectorize && | |
| UseEI.UserTE->getOpcode() == Instruction::PHI && | |
| TEUseEI.UserTE != UseEI.UserTE) | |
| continue; | |
| // If 2 gathers are operands of the same entry (regardless of whether | |
| // user is PHI or else), compare operands indices, use the earlier one | |
| // as the base. | |
| if (TEUseEI.UserTE == UseEI.UserTE && TEUseEI.EdgeIdx < UseEI.EdgeIdx) | |
| continue; | |
| // If the user instruction is used for some reason in different | |
| // vectorized nodes - make it depend on index. | |
| if (TEUseEI.UserTE != UseEI.UserTE && | |
| (TEUseEI.UserTE->Idx < UseEI.UserTE->Idx || | |
| HasGatherUser(TEUseEI.UserTE))) | |
| continue; | |
| // If the user node is the operand of the other user node - skip. | |
| if (CheckParentNodes(TEUseEI.UserTE, UseEI.UserTE, UseEI.EdgeIdx)) | |
| continue; | |
| } | |
| if (!TEUseEI.UserTE->isGather() && !UserPHI && | |
| TEUseEI.UserTE->doesNotNeedToSchedule() != | |
| UseEI.UserTE->doesNotNeedToSchedule() && | |
| is_contained(UseEI.UserTE->Scalars, TEInsertPt)) | |
| continue; | |
| // Check if the user node of the TE comes after user node of TEPtr, | |
| // otherwise TEPtr depends on TE. | |
| if ((TEInsertBlock != InsertPt->getParent() || | |
| TEUseEI.EdgeIdx < UseEI.EdgeIdx || TEUseEI.UserTE != UseEI.UserTE) && | |
| (!CheckOrdering(InsertPt) || | |
| (UseEI.UserTE->hasCopyableElements() && | |
| IsTEInsertPtUsedOutsideBlock() && | |
| is_contained(UseEI.UserTE->Scalars, TEInsertPt)))) | |
| continue; | |
| // The node is reused - exit. | |
| if (CheckAndUseSameNode(TEPtr)) | |
| break; | |
| // The parent node is copyable with last inst used outside? And the last | |
| // inst is the next inst for the lastinst of TEPtr? Exit, if yes, to | |
| // preserve def-use chain. | |
| if (CheckNonSchedulableOrdering(UseEI.UserTE, InsertPt)) | |
| continue; | |
| VToTEs.insert(TEPtr); | |
| } | |
| if (ArrayRef<TreeEntry *> VTEs = getSplitTreeEntries(V); !VTEs.empty()) { | |
| const auto *It = find_if(VTEs, [&](const TreeEntry *MTE) { | |
| return MTE != TE && MTE != TEUseEI.UserTE && | |
| !DeletedNodes.contains(MTE) && | |
| !TransformedToGatherNodes.contains(MTE); | |
| }); | |
| if (It != VTEs.end()) { | |
| const TreeEntry *VTE = *It; | |
| if (none_of(TE->CombinedEntriesWithIndices, | |
| [&](const auto &P) { return P.first == VTE->Idx; })) { | |
| Instruction &LastBundleInst = getLastInstructionInBundle(VTE); | |
| if (&LastBundleInst == TEInsertPt || !CheckOrdering(&LastBundleInst)) | |
| continue; | |
| } | |
| // The node is reused - exit. | |
| if (CheckAndUseSameNode(VTE)) | |
| break; | |
| VToTEs.insert(VTE); | |
| } | |
| } | |
| if (ArrayRef<TreeEntry *> VTEs = getTreeEntries(V); !VTEs.empty()) { | |
| const auto *It = find_if(VTEs, [&, MainTE = TE](const TreeEntry *TE) { | |
| return TE != MainTE && !DeletedNodes.contains(TE) && | |
| !TransformedToGatherNodes.contains(TE); | |
| }); | |
| if (It != VTEs.end()) { | |
| const TreeEntry *VTE = *It; | |
| if (ForOrder && VTE->Idx < GatheredLoadsEntriesFirst.value_or(0) && | |
| VTEs.size() > 1 && VTE->State != TreeEntry::Vectorize) { | |
| VTEs = VTEs.drop_front(); | |
| // Iterate through all vectorized nodes. | |
| const auto *MIt = find_if(VTEs, [](const TreeEntry *MTE) { | |
| return MTE->State == TreeEntry::Vectorize; | |
| }); | |
| if (MIt == VTEs.end()) | |
| continue; | |
| VTE = *MIt; | |
| } | |
| if (none_of(TE->CombinedEntriesWithIndices, | |
| [&](const auto &P) { return P.first == VTE->Idx; })) { | |
| Instruction &LastBundleInst = getLastInstructionInBundle(VTE); | |
| if (&LastBundleInst == TEInsertPt || | |
| !CheckOrdering(&LastBundleInst) || | |
| CheckNonSchedulableOrdering(VTE, &LastBundleInst)) | |
| continue; | |
| } | |
| // The node is reused - exit. | |
| if (CheckAndUseSameNode(VTE)) | |
| break; | |
| VToTEs.insert(VTE); | |
| } | |
| } | |
| if (IsReusedNodeFound) | |
| break; | |
| if (VToTEs.empty()) | |
| continue; | |
| if (UsedTEs.empty()) { | |
| // The first iteration, just insert the list of nodes to vector. | |
| UsedTEs.push_back(VToTEs); | |
| UsedValuesEntry.try_emplace(V, 0); | |
| } else { | |
| // Need to check if there are any previously used tree nodes which use V. | |
| // If there are no such nodes, consider that we have another one input | |
| // vector. | |
| SmallPtrSet<const TreeEntry *, 4> SavedVToTEs(VToTEs); | |
| unsigned Idx = 0; | |
| for (SmallPtrSet<const TreeEntry *, 4> &Set : UsedTEs) { | |
| // Do we have a non-empty intersection of previously listed tree entries | |
| // and tree entries using current V? | |
| set_intersect(VToTEs, Set); | |
| if (!VToTEs.empty()) { | |
| // Yes, write the new subset and continue analysis for the next | |
| // scalar. | |
| Set.swap(VToTEs); | |
| break; | |
| } | |
| VToTEs = SavedVToTEs; | |
| ++Idx; | |
| } | |
| // No non-empty intersection found - need to add a second set of possible | |
| // source vectors. | |
| if (Idx == UsedTEs.size()) { | |
| // If the number of input vectors is greater than 2 - not a permutation, | |
| // fallback to the regular gather. | |
| // TODO: support multiple reshuffled nodes. | |
| if (UsedTEs.size() == 2) | |
| continue; | |
| UsedTEs.push_back(SavedVToTEs); | |
| Idx = UsedTEs.size() - 1; | |
| } | |
| UsedValuesEntry.try_emplace(V, Idx); | |
| } | |
| } | |
| if (UsedTEs.empty()) { | |
| Entries.clear(); | |
| return std::nullopt; | |
| } | |
| unsigned VF = 0; | |
| if (UsedTEs.size() == 1) { | |
| // Keep the order to avoid non-determinism. | |
| SmallVector<const TreeEntry *> FirstEntries(UsedTEs.front().begin(), | |
| UsedTEs.front().end()); | |
| sort(FirstEntries, [](const TreeEntry *TE1, const TreeEntry *TE2) { | |
| return TE1->Idx < TE2->Idx; | |
| }); | |
| // Try to find the perfect match in another gather node at first. | |
| auto *It = find_if(FirstEntries, [=](const TreeEntry *EntryPtr) { | |
| return (EntryPtr->getVectorFactor() == TE->Scalars.size() && | |
| EntryPtr->isSame(TE->Scalars)) || | |
| EntryPtr->isSame(VL); | |
| }); | |
| if (It != FirstEntries.end() && | |
| (IsReusedNodeFound || (*It)->getVectorFactor() == VL.size() || | |
| ((*It)->getVectorFactor() == TE->Scalars.size() && | |
| TE->ReuseShuffleIndices.size() == VL.size() && | |
| (*It)->isSame(TE->Scalars)))) { | |
| Entries.push_back(*It); | |
| if (IsReusedNodeFound || (*It)->getVectorFactor() == VL.size()) { | |
| std::iota(std::next(Mask.begin(), MaskBase), | |
| std::next(Mask.begin(), MaskBase + VL.size()), 0); | |
| } else { | |
| SmallVector<int> CommonMask = TE->getCommonMask(); | |
| copy(CommonMask, Mask.begin()); | |
| } | |
| // Clear undef scalars. | |
| for (unsigned I : seq<unsigned>(VL.size())) | |
| if (isa<PoisonValue>(VL[I])) | |
| Mask[MaskBase + I] = PoisonMaskElem; | |
| return TargetTransformInfo::SK_PermuteSingleSrc; | |
| } | |
| // No perfect match, just shuffle, so choose the first tree node from the | |
| // tree. | |
| Entries.push_back(FirstEntries.front()); | |
| // Update mapping between values and corresponding tree entries. | |
| for (auto &P : UsedValuesEntry) | |
| P.second = 0; | |
| VF = FirstEntries.front()->getVectorFactor(); | |
| } else { | |
| // Try to find nodes with the same vector factor. | |
| assert(UsedTEs.size() == 2 && "Expected at max 2 permuted entries."); | |
| // Keep the order of tree nodes to avoid non-determinism. | |
| DenseMap<int, const TreeEntry *> VFToTE; | |
| for (const TreeEntry *TE : UsedTEs.front()) { | |
| unsigned VF = TE->getVectorFactor(); | |
| auto It = VFToTE.find(VF); | |
| if (It != VFToTE.end()) { | |
| if (It->second->Idx > TE->Idx) | |
| It->getSecond() = TE; | |
| continue; | |
| } | |
| VFToTE.try_emplace(VF, TE); | |
| } | |
| // Same, keep the order to avoid non-determinism. | |
| SmallVector<const TreeEntry *> SecondEntries(UsedTEs.back().begin(), | |
| UsedTEs.back().end()); | |
| sort(SecondEntries, [](const TreeEntry *TE1, const TreeEntry *TE2) { | |
| return TE1->Idx < TE2->Idx; | |
| }); | |
| for (const TreeEntry *TE : SecondEntries) { | |
| auto It = VFToTE.find(TE->getVectorFactor()); | |
| if (It != VFToTE.end()) { | |
| VF = It->first; | |
| Entries.push_back(It->second); | |
| Entries.push_back(TE); | |
| break; | |
| } | |
| } | |
| // No 2 source vectors with the same vector factor - just choose 2 with max | |
| // index. | |
| if (Entries.empty()) { | |
| Entries.push_back(*llvm::max_element( | |
| UsedTEs.front(), [](const TreeEntry *TE1, const TreeEntry *TE2) { | |
| return TE1->Idx < TE2->Idx; | |
| })); | |
| Entries.push_back(SecondEntries.front()); | |
| VF = std::max(Entries.front()->getVectorFactor(), | |
| Entries.back()->getVectorFactor()); | |
| } else { | |
| VF = Entries.front()->getVectorFactor(); | |
| } | |
| SmallVector<SmallPtrSet<Value *, 8>> ValuesToEntries; | |
| for (const TreeEntry *E : Entries) | |
| ValuesToEntries.emplace_back().insert(E->Scalars.begin(), | |
| E->Scalars.end()); | |
| // Update mapping between values and corresponding tree entries. | |
| for (auto &P : UsedValuesEntry) { | |
| for (unsigned Idx : seq<unsigned>(ValuesToEntries.size())) | |
| if (ValuesToEntries[Idx].contains(P.first)) { | |
| P.second = Idx; | |
| break; | |
| } | |
| } | |
| } | |
| bool IsSplatOrUndefs = isSplat(VL) || all_of(VL, IsaPred<UndefValue>); | |
| // Checks if the 2 PHIs are compatible in terms of high possibility to be | |
| // vectorized. | |
| auto AreCompatiblePHIs = [&](Value *V, Value *V1) { | |
| auto *PHI = cast<PHINode>(V); | |
| auto *PHI1 = cast<PHINode>(V1); | |
| // Check that all incoming values are compatible/from same parent (if they | |
| // are instructions). | |
| // The incoming values are compatible if they all are constants, or | |
| // instruction with the same/alternate opcodes from the same basic block. | |
| for (int I = 0, E = PHI->getNumIncomingValues(); I < E; ++I) { | |
| Value *In = PHI->getIncomingValue(I); | |
| Value *In1 = PHI1->getIncomingValue(I); | |
| if (isConstant(In) && isConstant(In1)) | |
| continue; | |
| if (!getSameOpcode({In, In1}, *TLI)) | |
| return false; | |
| if (cast<Instruction>(In)->getParent() != | |
| cast<Instruction>(In1)->getParent()) | |
| return false; | |
| } | |
| return true; | |
| }; | |
| // Check if the value can be ignored during analysis for shuffled gathers. | |
| // We suppose it is better to ignore instruction, which do not form splats, | |
| // are not vectorized/not extractelements (these instructions will be handled | |
| // by extractelements processing) or may form vector node in future. | |
| // Cache results - each V in VL is queried up to 3 times (direct + | |
| // NeighborMightBeIgnored from both neighbors), and areAllUsersVectorized | |
| // walks each instruction's user list. | |
| SmallDenseMap<Value *, bool> MightBeIgnoredCache; | |
| auto MightBeIgnored = [=, &MightBeIgnoredCache](Value *V) { | |
| auto [It, Inserted] = MightBeIgnoredCache.try_emplace(V); | |
| if (!Inserted) | |
| return It->second; | |
| auto *I = dyn_cast<Instruction>(V); | |
| bool Res = I && !IsSplatOrUndefs && !isVectorized(I) && | |
| !isVectorLikeInstWithConstOps(I) && | |
| !areAllUsersVectorized(I, UserIgnoreList) && isSimple(I); | |
| It->second = Res; | |
| return Res; | |
| }; | |
| // Check that the neighbor instruction may form a full vector node with the | |
| // current instruction V. It is possible, if they have same/alternate opcode | |
| // and same parent basic block. | |
| auto NeighborMightBeIgnored = [&](Value *V, int Idx) { | |
| Value *V1 = VL[Idx]; | |
| bool UsedInSameVTE = false; | |
| auto It = UsedValuesEntry.find(V1); | |
| if (It != UsedValuesEntry.end()) | |
| UsedInSameVTE = It->second == UsedValuesEntry.find(V)->second; | |
| return V != V1 && MightBeIgnored(V1) && !UsedInSameVTE && | |
| getSameOpcode({V, V1}, *TLI) && | |
| cast<Instruction>(V)->getParent() == | |
| cast<Instruction>(V1)->getParent() && | |
| (!isa<PHINode>(V1) || AreCompatiblePHIs(V, V1)); | |
| }; | |
| // Build a shuffle mask for better cost estimation and vector emission. | |
| SmallBitVector UsedIdxs(Entries.size()); | |
| SmallVector<std::pair<unsigned, int>> EntryLanes; | |
| for (int I = 0, E = VL.size(); I < E; ++I) { | |
| Value *V = VL[I]; | |
| auto It = UsedValuesEntry.find(V); | |
| if (It == UsedValuesEntry.end()) | |
| continue; | |
| // Do not try to shuffle scalars, if they are constants, or instructions | |
| // that can be vectorized as a result of the following vector build | |
| // vectorization. | |
| if (isConstant(V) || (MightBeIgnored(V) && | |
| ((I > 0 && NeighborMightBeIgnored(V, I - 1)) || | |
| (I != E - 1 && NeighborMightBeIgnored(V, I + 1))))) | |
| continue; | |
| unsigned Idx = It->second; | |
| EntryLanes.emplace_back(Idx, I); | |
| UsedIdxs.set(Idx); | |
| } | |
| // Iterate through all shuffled scalars and select entries, which can be used | |
| // for final shuffle. | |
| SmallVector<const TreeEntry *> TempEntries; | |
| for (unsigned I = 0, Sz = Entries.size(); I < Sz; ++I) { | |
| if (!UsedIdxs.test(I)) | |
| continue; | |
| // Fix the entry number for the given scalar. If it is the first entry, set | |
| // Pair.first to 0, otherwise to 1 (currently select at max 2 nodes). | |
| // These indices are used when calculating final shuffle mask as the vector | |
| // offset. | |
| for (std::pair<unsigned, int> &Pair : EntryLanes) | |
| if (Pair.first == I) | |
| Pair.first = TempEntries.size(); | |
| TempEntries.push_back(Entries[I]); | |
| } | |
| Entries.swap(TempEntries); | |
| if (EntryLanes.size() == Entries.size() && | |
| !VL.equals(ArrayRef(TE->Scalars) | |
| .slice(MaskBase, getNumElems(TE->Scalars.size(), SliceSize, | |
| Part)))) { | |
| // We may have here 1 or 2 entries only. If the number of scalars is equal | |
| // to the number of entries, no need to do the analysis, it is not very | |
| // profitable. Since VL is not the same as TE->Scalars, it means we already | |
| // have some shuffles before. Cut off not profitable case. | |
| Entries.clear(); | |
| return std::nullopt; | |
| } | |
| // Build the final mask, check for the identity shuffle, if possible. | |
| bool IsIdentity = Entries.size() == 1; | |
| // Pair.first is the offset to the vector, while Pair.second is the index of | |
| // scalar in the list. | |
| for (const std::pair<unsigned, int> &Pair : EntryLanes) { | |
| unsigned Idx = MaskBase + Pair.second; | |
| Mask[Idx] = | |
| Pair.first * VF + | |
| (ForOrder ? std::distance( | |
| Entries[Pair.first]->Scalars.begin(), | |
| find(Entries[Pair.first]->Scalars, VL[Pair.second])) | |
| : Entries[Pair.first]->findLaneForValue(VL[Pair.second])); | |
| IsIdentity &= Mask[Idx] == Pair.second; | |
| } | |
| if (ForOrder || IsIdentity || Entries.empty()) { | |
| switch (Entries.size()) { | |
| case 1: | |
| if (IsIdentity || EntryLanes.size() > 1 || VL.size() <= 2) | |
| return TargetTransformInfo::SK_PermuteSingleSrc; | |
| break; | |
| case 2: | |
| if (EntryLanes.size() > 2 || VL.size() <= 2) | |
| return TargetTransformInfo::SK_PermuteTwoSrc; | |
| break; | |
| default: | |
| break; | |
| } | |
| } else if (!isa<VectorType>(VL.front()->getType()) && | |
| (EntryLanes.size() > Entries.size() || VL.size() <= 2)) { | |
| // Do the cost estimation if shuffle beneficial than buildvector. | |
| SmallVector<int> SubMask(std::next(Mask.begin(), MaskBase), | |
| std::next(Mask.begin(), MaskBase + VL.size())); | |
| int MinElement = SubMask.front(), MaxElement = SubMask.front(); | |
| for (int Idx : make_filter_range( | |
| SubMask, [](int Idx) { return Idx != PoisonMaskElem; })) { | |
| if (MinElement == PoisonMaskElem || MinElement % VF > Idx % VF) | |
| MinElement = Idx; | |
| if (MaxElement == PoisonMaskElem || MaxElement % VF < Idx % VF) | |
| MaxElement = Idx; | |
| } | |
| assert(MaxElement >= 0 && MinElement >= 0 && | |
| MaxElement % VF >= MinElement % VF && | |
| "Expected at least single element."); | |
| // If the leading [0, MinIdx) range sits in its own register part(s), | |
| // skip those whole parts when sizing the destination - everything below | |
| // the register-aligned floor is unused and never indexed. | |
| unsigned Offset = 0; | |
| unsigned MinIdx = MinElement % VF; | |
| if (MinIdx > 1) { | |
| unsigned RegFloor = getFloorFullVectorNumberOfElements( | |
| *TTI, VL.front()->getType(), MinIdx, Opts.slp_revec); | |
| auto *RegFloorTy = getWidenedType(VL.front()->getType(), RegFloor); | |
| unsigned RegFloorParts = | |
| getNumberOfParts(RegFloorTy, VL.front()->getType(), RegFloor); | |
| if (RegFloorParts > 1) | |
| Offset = RegFloor; | |
| } | |
| unsigned NewVF = | |
| std::max<unsigned>(VL.size(), (MaxElement % VF) - Offset + 1); | |
| if (NewVF < VF) { | |
| for (int &Idx : make_filter_range( | |
| SubMask, [](int Idx) { return Idx != PoisonMaskElem; })) | |
| Idx = (Idx % VF) - Offset + (Idx >= static_cast<int>(VF) ? NewVF : 0); | |
| } else { | |
| NewVF = VF; | |
| } | |
| auto *VecTy = | |
| cast<VectorType>(getWidenedType(VL.front()->getType(), NewVF)); | |
| auto *MaskVecTy = | |
| cast<VectorType>(getWidenedType(VL.front()->getType(), SubMask.size())); | |
| auto GetShuffleCost = [&, | |
| &TTI = *TTI](ArrayRef<int> Mask, | |
| ArrayRef<const TreeEntry *> Entries, | |
| VectorType *VecTy) -> InstructionCost { | |
| if (Entries.size() == 1 && Entries.front()->getInterleaveFactor() > 0 && | |
| ShuffleVectorInst::isDeInterleaveMaskOfFactor( | |
| Mask, Entries.front()->getInterleaveFactor())) | |
| return TTI::TCC_Free; | |
| return getShuffleCost(TTI, | |
| Entries.size() > 1 ? TTI::SK_PermuteTwoSrc | |
| : TTI::SK_PermuteSingleSrc, | |
| VecTy, CostKind, Mask); | |
| }; | |
| InstructionCost ShuffleCost = GetShuffleCost(SubMask, Entries, VecTy); | |
| InstructionCost FirstShuffleCost = 0; | |
| SmallVector<int> FirstMask(SubMask.begin(), SubMask.end()); | |
| if (Entries.size() == 1 || !Entries[0]->isGather()) { | |
| FirstShuffleCost = ShuffleCost; | |
| } else { | |
| // Transform mask to include only first entry. | |
| APInt DemandedElts = APInt::getAllOnes(SubMask.size()); | |
| bool IsIdentity = true; | |
| for (auto [I, Idx] : enumerate(FirstMask)) { | |
| if (Idx >= static_cast<int>(NewVF)) { | |
| Idx = PoisonMaskElem; | |
| } else { | |
| DemandedElts.clearBit(I); | |
| if (Idx != PoisonMaskElem) | |
| IsIdentity &= static_cast<int>(I) == Idx; | |
| } | |
| } | |
| if (!IsIdentity) | |
| FirstShuffleCost = GetShuffleCost(FirstMask, Entries.front(), VecTy); | |
| FirstShuffleCost += | |
| getScalarizationOverhead(*TTI, Opts.slp_revec, VL.front()->getType(), | |
| MaskVecTy, DemandedElts, /*Insert=*/true, | |
| /*Extract=*/false, CostKind); | |
| } | |
| InstructionCost SecondShuffleCost = 0; | |
| SmallVector<int> SecondMask(SubMask.begin(), SubMask.end()); | |
| if (Entries.size() == 1 || !Entries[1]->isGather()) { | |
| SecondShuffleCost = ShuffleCost; | |
| } else { | |
| // Transform mask to include only first entry. | |
| APInt DemandedElts = APInt::getAllOnes(SubMask.size()); | |
| bool IsIdentity = true; | |
| for (auto [I, Idx] : enumerate(SecondMask)) { | |
| if (Idx < static_cast<int>(NewVF) && Idx >= 0) { | |
| Idx = PoisonMaskElem; | |
| } else { | |
| DemandedElts.clearBit(I); | |
| if (Idx != PoisonMaskElem) { | |
| Idx -= NewVF; | |
| IsIdentity &= static_cast<int>(I) == Idx; | |
| } | |
| } | |
| } | |
| if (!IsIdentity) | |
| SecondShuffleCost = GetShuffleCost(SecondMask, Entries[1], VecTy); | |
| SecondShuffleCost += | |
| getScalarizationOverhead(*TTI, Opts.slp_revec, VL.front()->getType(), | |
| MaskVecTy, DemandedElts, /*Insert=*/true, | |
| /*Extract=*/false, CostKind); | |
| } | |
| APInt DemandedElts = APInt::getAllOnes(SubMask.size()); | |
| for (auto [I, Idx] : enumerate(SubMask)) | |
| if (Idx == PoisonMaskElem) | |
| DemandedElts.clearBit(I); | |
| InstructionCost BuildVectorCost = | |
| getScalarizationOverhead(*TTI, Opts.slp_revec, VL.front()->getType(), | |
| MaskVecTy, DemandedElts, /*Insert=*/true, | |
| /*Extract=*/false, CostKind); | |
| const TreeEntry *BestEntry = nullptr; | |
| auto MaskSlice = MutableArrayRef(Mask).slice(MaskBase, VL.size()); | |
| if (FirstShuffleCost < ShuffleCost) { | |
| for (int &Idx : MaskSlice) | |
| if (Idx >= static_cast<int>(VF)) | |
| Idx = PoisonMaskElem; | |
| BestEntry = Entries.front(); | |
| ShuffleCost = FirstShuffleCost; | |
| } | |
| if (SecondShuffleCost < ShuffleCost) { | |
| for (int &Idx : MaskSlice) { | |
| if (Idx < static_cast<int>(VF)) | |
| Idx = PoisonMaskElem; | |
| else | |
| Idx -= VF; | |
| } | |
| BestEntry = Entries[1]; | |
| ShuffleCost = SecondShuffleCost; | |
| } | |
| if (BuildVectorCost >= ShuffleCost) { | |
| if (BestEntry) { | |
| Entries.clear(); | |
| Entries.push_back(BestEntry); | |
| } | |
| return Entries.size() > 1 ? TargetTransformInfo::SK_PermuteTwoSrc | |
| : TargetTransformInfo::SK_PermuteSingleSrc; | |
| } | |
| } | |
| Entries.clear(); | |
| // Clear the corresponding mask elements. | |
| std::fill(std::next(Mask.begin(), MaskBase), | |
| std::next(Mask.begin(), MaskBase + VL.size()), PoisonMaskElem); | |
| return std::nullopt; | |
| } | |
| SmallVector<std::optional<TargetTransformInfo::ShuffleKind>> | |
| BoUpSLP::isGatherShuffledEntry( | |
| const TreeEntry *TE, ArrayRef<Value *> VL, SmallVectorImpl<int> &Mask, | |
| SmallVectorImpl<SmallVector<const TreeEntry *>> &Entries, unsigned NumParts, | |
| Type *ScalarTy, bool ForOrder) { | |
| assert(NumParts > 0 && NumParts < VL.size() && | |
| "Expected positive number of registers."); | |
| Entries.clear(); | |
| // No need to check for the topmost gather node. | |
| if (TE == &getRootNode() && | |
| (!GatheredLoadsEntriesFirst.has_value() || | |
| none_of(ArrayRef(VectorizableTree).drop_front(), | |
| [](const std::unique_ptr<TreeEntry> &TE) { | |
| return !TE->isGather(); | |
| }))) | |
| return {}; | |
| Mask.assign(VL.size(), PoisonMaskElem); | |
| assert((TE->UserTreeIndex || TE == &getRootNode()) && | |
| "Expected only single user of the gather node."); | |
| unsigned PWSz = | |
| getFullVectorNumberOfElements(*TTI, ScalarTy, VL.size(), Opts.slp_revec); | |
| if (TE->UserTreeIndex && TE->UserTreeIndex.UserTE->isGather() && | |
| TE->UserTreeIndex.EdgeIdx == UINT_MAX && | |
| (TE->Idx == 0 || | |
| (TE->hasState() && TE->getOpcode() == Instruction::ExtractElement) || | |
| isSplat(TE->Scalars) || | |
| (TE->hasState() && | |
| getSameValuesTreeEntry(TE->getMainOp(), TE->Scalars)))) | |
| return {}; | |
| unsigned SliceSize = getPartNumElems(PWSz, NumParts); | |
| SmallVector<std::optional<TTI::ShuffleKind>> Res; | |
| for (unsigned Part : seq<unsigned>(NumParts)) { | |
| if (Part * SliceSize >= VL.size()) | |
| break; | |
| ArrayRef<Value *> SubVL = | |
| VL.slice(Part * SliceSize, getNumElems(VL.size(), SliceSize, Part)); | |
| SmallVectorImpl<const TreeEntry *> &SubEntries = Entries.emplace_back(); | |
| std::optional<TTI::ShuffleKind> SubRes = | |
| isGatherShuffledSingleRegisterEntry(TE, SubVL, Mask, SubEntries, Part, | |
| ForOrder, SliceSize); | |
| if (!SubRes) | |
| SubEntries.clear(); | |
| Res.push_back(SubRes); | |
| if (SubEntries.size() == 1 && *SubRes == TTI::SK_PermuteSingleSrc && | |
| SubEntries.front()->getVectorFactor() == VL.size() && | |
| (SubEntries.front()->isSame(TE->Scalars) || | |
| SubEntries.front()->isSame(VL))) { | |
| SmallVector<const TreeEntry *> LocalSubEntries; | |
| LocalSubEntries.swap(SubEntries); | |
| Entries.clear(); | |
| Res.clear(); | |
| std::iota(Mask.begin(), Mask.end(), 0); | |
| // Clear undef scalars. | |
| for (int I = 0, Sz = VL.size(); I < Sz; ++I) | |
| if (isa<PoisonValue>(VL[I])) | |
| Mask[I] = PoisonMaskElem; | |
| Entries.emplace_back(1, LocalSubEntries.front()); | |
| Res.push_back(TargetTransformInfo::SK_PermuteSingleSrc); | |
| return Res; | |
| } | |
| } | |
| if (all_of(Res, | |
| [](const std::optional<TTI::ShuffleKind> &SK) { return !SK; })) { | |
| Entries.clear(); | |
| return {}; | |
| } | |
| return Res; | |
| } | |
| InstructionCost BoUpSLP::getGatherCost(ArrayRef<Value *> VL, bool ForPoisonSrc, | |
| Type *ScalarTy) const { | |
| const unsigned VF = VL.size(); | |
| auto *VecTy = getWidenedType(ScalarTy, VF); | |
| // Find the cost of inserting/extracting values from the vector. | |
| // Check if the same elements are inserted several times and count them as | |
| // shuffle candidates. | |
| APInt DemandedElements = APInt::getZero(VF); | |
| InstructionCost Cost; | |
| auto EstimateInsertCost = [&](unsigned I, Value *V) { | |
| DemandedElements.setBit(I); | |
| if (V->getType() != ScalarTy) | |
| Cost += TTI->getCastInstrCost(Instruction::Trunc, ScalarTy, V->getType(), | |
| TTI::CastContextHint::None, CostKind); | |
| }; | |
| SmallVector<int> ConstantShuffleMask(VF, PoisonMaskElem); | |
| std::iota(ConstantShuffleMask.begin(), ConstantShuffleMask.end(), 0); | |
| // No need to shuffle duplicates for constants. | |
| for (auto [I, V] : make_filter_range(enumerate(VL), [&](const auto &P) { | |
| return !(ForPoisonSrc && isConstant(P.value())) && | |
| !isa<UndefValue>(P.value()); | |
| })) { | |
| if (isConstant(V)) { | |
| ConstantShuffleMask[I] = I + VF; | |
| continue; | |
| } | |
| EstimateInsertCost(I, V); | |
| } | |
| // FIXME: add a cost for constant vector materialization. | |
| bool IsAnyNonUndefConst = | |
| any_of(VL, [](Value *V) { return !isa<UndefValue>(V) && isConstant(V); }); | |
| // 1. Shuffle input source vector and constant vector. | |
| if (!ForPoisonSrc && IsAnyNonUndefConst) { | |
| Cost += | |
| getShuffleCost(*TTI, TargetTransformInfo::SK_PermuteTwoSrc, | |
| cast<VectorType>(VecTy), CostKind, ConstantShuffleMask); | |
| } | |
| // 2. Insert unique non-constants. | |
| if (!DemandedElements.isZero()) | |
| Cost += getScalarizationOverhead(*TTI, Opts.slp_revec, ScalarTy, | |
| cast<VectorType>(VecTy), DemandedElements, | |
| /*Insert=*/true, | |
| /*Extract=*/false, CostKind, | |
| ForPoisonSrc && !IsAnyNonUndefConst, VL); | |
| return Cost; | |
| } | |
| Instruction &BoUpSLP::getLastInstructionInBundle(const TreeEntry *E) { | |
| auto It = EntryToLastInstruction.find(E); | |
| if (It != EntryToLastInstruction.end()) | |
| return *cast<Instruction>(It->second); | |
| Instruction *Res = nullptr; | |
| // Get the basic block this bundle is in. All instructions in the bundle | |
| // should be in this block (except for extractelement-like instructions with | |
| // constant indices or gathered loads or copyables). | |
| Instruction *Front; | |
| unsigned Opcode; | |
| if (E->hasState()) { | |
| Front = E->getMainOp(); | |
| Opcode = E->getOpcode(); | |
| } else { | |
| Front = cast<Instruction>(*find_if(E->Scalars, IsaPred<Instruction>)); | |
| Opcode = Front->getOpcode(); | |
| } | |
| auto *BB = Front->getParent(); | |
| assert( | |
| ((GatheredLoadsEntriesFirst.has_value() && Opcode == Instruction::Load && | |
| E->isGather() && E->Idx < *GatheredLoadsEntriesFirst) || | |
| E->State == TreeEntry::SplitVectorize || E->hasCopyableElements() || | |
| all_of(E->Scalars, | |
| [=](Value *V) -> bool { | |
| if (Opcode == Instruction::GetElementPtr && | |
| !isa<GetElementPtrInst>(V)) | |
| return true; | |
| auto *I = dyn_cast<Instruction>(V); | |
| return !I || !E->getMatchingMainOpOrAltOp(I) || | |
| I->getParent() == BB || isVectorLikeInstWithConstOps(I); | |
| })) && | |
| "Expected gathered loads or GEPs or instructions from same basic " | |
| "block."); | |
| auto FindLastInst = [&]() { | |
| Instruction *LastInst = Front; | |
| for (Instruction *I : make_isa_range<Instruction>(E->Scalars)) { | |
| if (E->isCopyableElement(I)) | |
| continue; | |
| if (LastInst->getParent() == I->getParent()) { | |
| if (LastInst->comesBefore(I)) | |
| LastInst = I; | |
| continue; | |
| } | |
| assert(((Opcode == Instruction::GetElementPtr && | |
| !isa<GetElementPtrInst>(I)) || | |
| E->State == TreeEntry::SplitVectorize || | |
| (isVectorLikeInstWithConstOps(LastInst) && | |
| isVectorLikeInstWithConstOps(I)) || | |
| (GatheredLoadsEntriesFirst.has_value() && | |
| Opcode == Instruction::Load && E->isGather() && | |
| E->Idx < *GatheredLoadsEntriesFirst)) && | |
| "Expected vector-like or non-GEP in GEP node insts only."); | |
| if (!DT->isReachableFromEntry(LastInst->getParent())) { | |
| LastInst = I; | |
| continue; | |
| } | |
| if (!DT->isReachableFromEntry(I->getParent())) | |
| continue; | |
| auto *NodeA = DT->getNode(LastInst->getParent()); | |
| auto *NodeB = DT->getNode(I->getParent()); | |
| assert(NodeA && "Should only process reachable instructions"); | |
| assert(NodeB && "Should only process reachable instructions"); | |
| assert((NodeA == NodeB) == | |
| (NodeA->getDFSNumIn() == NodeB->getDFSNumIn()) && | |
| "Different nodes should have different DFS numbers"); | |
| if (NodeA->getDFSNumIn() < NodeB->getDFSNumIn()) | |
| LastInst = I; | |
| } | |
| BB = LastInst->getParent(); | |
| return LastInst; | |
| }; | |
| auto FindFirstInst = [&]() { | |
| Instruction *FirstInst = Front; | |
| for (Instruction *I : make_isa_range<Instruction>(E->Scalars)) { | |
| if (E->isCopyableElement(I)) | |
| continue; | |
| if (FirstInst->getParent() == I->getParent()) { | |
| if (I->comesBefore(FirstInst)) | |
| FirstInst = I; | |
| continue; | |
| } | |
| assert(((Opcode == Instruction::GetElementPtr && | |
| !isa<GetElementPtrInst>(I)) || | |
| (isVectorLikeInstWithConstOps(FirstInst) && | |
| isVectorLikeInstWithConstOps(I))) && | |
| "Expected vector-like or non-GEP in GEP node insts only."); | |
| if (!DT->isReachableFromEntry(FirstInst->getParent())) { | |
| FirstInst = I; | |
| continue; | |
| } | |
| if (!DT->isReachableFromEntry(I->getParent())) | |
| continue; | |
| auto *NodeA = DT->getNode(FirstInst->getParent()); | |
| auto *NodeB = DT->getNode(I->getParent()); | |
| assert(NodeA && "Should only process reachable instructions"); | |
| assert(NodeB && "Should only process reachable instructions"); | |
| assert((NodeA == NodeB) == | |
| (NodeA->getDFSNumIn() == NodeB->getDFSNumIn()) && | |
| "Different nodes should have different DFS numbers"); | |
| if (NodeA->getDFSNumIn() > NodeB->getDFSNumIn()) | |
| FirstInst = I; | |
| } | |
| return FirstInst; | |
| }; | |
| if (E->State == TreeEntry::SplitVectorize) { | |
| Res = FindLastInst(); | |
| if (ArrayRef<TreeEntry *> Entries = getTreeEntries(Res); !Entries.empty()) { | |
| for (auto *E : Entries) { | |
| auto *I = dyn_cast_if_present<Instruction>(E->VectorizedValue); | |
| if (!I) | |
| I = &getLastInstructionInBundle(E); | |
| if (Res->getParent() == I->getParent() && Res->comesBefore(I)) | |
| Res = I; | |
| } | |
| } | |
| EntryToLastInstruction.try_emplace(E, Res); | |
| return *Res; | |
| } | |
| // Set insertpoint for gathered loads to the very first load. | |
| if (GatheredLoadsEntriesFirst.has_value() && | |
| E->Idx >= *GatheredLoadsEntriesFirst && !E->isGather() && | |
| Opcode == Instruction::Load) { | |
| Res = FindFirstInst(); | |
| EntryToLastInstruction.try_emplace(E, Res); | |
| return *Res; | |
| } | |
| // Set the insert point to the beginning of the basic block if the entry | |
| // should not be scheduled. | |
| auto FindScheduleBundle = [&](const TreeEntry *E) -> const ScheduleBundle * { | |
| if (E->isGather()) | |
| return nullptr; | |
| // Found previously that the instruction do not need to be scheduled. | |
| const auto *It = BlocksSchedules.find(BB); | |
| if (It == BlocksSchedules.end()) | |
| return nullptr; | |
| for (Instruction *I : make_isa_range<Instruction>(E->Scalars)) { | |
| if (isa<PHINode>(I) || | |
| (!E->isCopyableElement(I) && doesNotNeedToBeScheduled(I))) | |
| continue; | |
| ArrayRef<ScheduleBundle *> Bundles = It->second->getScheduleBundles(I); | |
| if (Bundles.empty()) | |
| continue; | |
| const auto *It = find_if( | |
| Bundles, [&](ScheduleBundle *B) { return B->getTreeEntry() == E; }); | |
| if (It != Bundles.end()) | |
| return *It; | |
| } | |
| return nullptr; | |
| }; | |
| const ScheduleBundle *Bundle = FindScheduleBundle(E); | |
| if (!E->isGather() && !Bundle) { | |
| if ((Opcode == Instruction::GetElementPtr && | |
| any_of(E->Scalars, | |
| [](Value *V) { | |
| return !isa<GetElementPtrInst>(V) && isa<Instruction>(V); | |
| })) || | |
| (all_of(E->Scalars, | |
| [&](Value *V) { | |
| return isa<PoisonValue>(V) || | |
| (E->Idx == 0 && | |
| isa<InsertElementInst, InsertValueInst>(V)) || | |
| E->isCopyableElement(V) || | |
| (!isVectorLikeInstWithConstOps(V) && | |
| isUsedOutsideBlock(V)); | |
| }) && | |
| (!E->doesNotNeedToSchedule() || | |
| any_of(make_isa_range<Instruction>(E->Scalars), | |
| [&](Instruction *I) { | |
| if (E->hasCopyableElements() && E->isCopyableElement(I)) | |
| return false; | |
| return !areAllOperandsNonInsts(I); | |
| }) || | |
| none_of(make_isa_range<Instruction>(E->Scalars), [&](Instruction *I) { | |
| if (E->hasCopyableElements() && E->isCopyableElement(I)) | |
| return false; | |
| return MustGather.contains(I); | |
| })))) | |
| Res = FindLastInst(); | |
| else | |
| Res = FindFirstInst(); | |
| EntryToLastInstruction.try_emplace(E, Res); | |
| return *Res; | |
| } | |
| // Find the last instruction. The common case should be that BB has been | |
| // scheduled, and the last instruction is VL.back(). So we start with | |
| // VL.back() and iterate over schedule data until we reach the end of the | |
| // bundle. The end of the bundle is marked by null ScheduleData. | |
| if (Bundle) { | |
| assert(!E->isGather() && "Gathered instructions should not be scheduled"); | |
| Res = Bundle->getBundle().back()->getInst(); | |
| EntryToLastInstruction.try_emplace(E, Res); | |
| return *Res; | |
| } | |
| // LastInst can still be null at this point if there's either not an entry | |
| // for BB in BlocksSchedules or there's no ScheduleData available for | |
| // VL.back(). This can be the case if buildTreeRec aborts for various | |
| // reasons (e.g., the maximum recursion depth is reached, the maximum region | |
| // size is reached, etc.). ScheduleData is initialized in the scheduling | |
| // "dry-run". | |
| // | |
| // If this happens, we can still find the last instruction by brute force. We | |
| // iterate forwards from Front (inclusive) until we either see all | |
| // instructions in the bundle or reach the end of the block. If Front is the | |
| // last instruction in program order, LastInst will be set to Front, and we | |
| // will visit all the remaining instructions in the block. | |
| // | |
| // One of the reasons we exit early from buildTreeRec is to place an upper | |
| // bound on compile-time. Thus, taking an additional compile-time hit here is | |
| // not ideal. However, this should be exceedingly rare since it requires that | |
| // we both exit early from buildTreeRec and that the bundle be out-of-order | |
| // (causing us to iterate all the way to the end of the block). | |
| if (!Res) | |
| Res = FindLastInst(); | |
| assert(Res && "Failed to find last instruction in bundle"); | |
| EntryToLastInstruction.try_emplace(E, Res); | |
| return *Res; | |
| } | |
| void BoUpSLP::setInsertPointAfterBundle(const TreeEntry *E) { | |
| auto *Front = E->getMainOp(); | |
| Instruction *LastInst = &getLastInstructionInBundle(E); | |
| // Struct-call extractvalue entries may have scalars in different basic | |
| // blocks. The vector extractvalue depends only on the vectorized call, so | |
| // use the call bundle to set the insert point and dominate all uses. | |
| const bool UseCallBundleInsertPoint = | |
| E->getOpcode() == Instruction::ExtractValue && | |
| !E->StructEVIndices.empty() && | |
| any_of(E->Scalars, [BB = Front->getParent()](Value *V) { | |
| return cast<Instruction>(V)->getParent() != BB; | |
| }); | |
| if (UseCallBundleInsertPoint) | |
| LastInst = &getLastInstructionInBundle(getOperandEntry(E, 0)); | |
| assert(LastInst && "Failed to find last instruction in bundle"); | |
| BasicBlock::iterator LastInstIt = LastInst->getIterator(); | |
| // If the instruction is PHI, set the insert point after all the PHIs. | |
| bool IsPHI = isa<PHINode>(LastInst); | |
| if (IsPHI) { | |
| LastInstIt = LastInst->getParent()->getFirstNonPHIIt(); | |
| if (LastInstIt != LastInst->getParent()->end() && | |
| LastInstIt->getParent()->isLandingPad()) | |
| LastInstIt = std::next(LastInstIt); | |
| } | |
| if (IsPHI || | |
| (!E->isGather() && E->State != TreeEntry::SplitVectorize && | |
| !UseCallBundleInsertPoint && | |
| (E->doesNotNeedToSchedule() || | |
| (E->hasCopyableElements() && !E->isCopyableElement(LastInst) && | |
| isUsedOutsideBlock(LastInst)))) || | |
| (GatheredLoadsEntriesFirst.has_value() && | |
| E->Idx >= *GatheredLoadsEntriesFirst && !E->isGather() && | |
| E->getOpcode() == Instruction::Load)) { | |
| Builder.SetInsertPoint(LastInstIt); | |
| } else { | |
| // Set the insertion point after the last instruction in the bundle. Set the | |
| // debug location to Front. | |
| Builder.SetInsertPoint(LastInst->getNextNode()->getIterator()); | |
| if (Instruction *Res = LastInstructionToPos.lookup(LastInst)) { | |
| Builder.SetInsertPoint(Res->getIterator()); | |
| } else { | |
| Res = Builder.CreateAlignedLoad(Builder.getPtrTy(), | |
| PoisonValue::get(Builder.getPtrTy()), | |
| MaybeAlign()); | |
| Builder.SetInsertPoint(Res->getIterator()); | |
| eraseInstruction(Res); | |
| if (E->State != TreeEntry::SplitVectorize) | |
| LastInstructionToPos.try_emplace(LastInst, Res); | |
| } | |
| } | |
| Builder.SetCurrentDebugLocation(Front->getDebugLoc()); | |
| } | |
| Value *BoUpSLP::gather( | |
| ArrayRef<Value *> VL, Value *Root, Type *ScalarTy, | |
| function_ref<Value *(Value *, Value *, ArrayRef<int>)> CreateShuffle) { | |
| // List of instructions/lanes from current block and/or the blocks which are | |
| // part of the current loop. These instructions will be inserted at the end to | |
| // make it possible to optimize loops and hoist invariant instructions out of | |
| // the loops body with better chances for success. | |
| SmallVector<std::pair<Value *, unsigned>, 4> PostponedInsts; | |
| SmallSet<int, 4> PostponedIndices; | |
| Loop *L = LI->getLoopFor(Builder.GetInsertBlock()); | |
| auto &&CheckPredecessor = [](BasicBlock *InstBB, BasicBlock *InsertBB) { | |
| SmallPtrSet<BasicBlock *, 4> Visited; | |
| while (InsertBB && InsertBB != InstBB && Visited.insert(InsertBB).second) | |
| InsertBB = InsertBB->getSinglePredecessor(); | |
| return InsertBB && InsertBB == InstBB; | |
| }; | |
| for (int I = 0, E = VL.size(); I < E; ++I) { | |
| if (auto *Inst = dyn_cast<Instruction>(VL[I])) | |
| if ((CheckPredecessor(Inst->getParent(), Builder.GetInsertBlock()) || | |
| isVectorized(Inst) || | |
| (L && (!Root || L->isLoopInvariant(Root)) && L->contains(Inst))) && | |
| PostponedIndices.insert(I).second) | |
| PostponedInsts.emplace_back(Inst, I); | |
| } | |
| auto &&CreateInsertElement = [this](Value *Vec, Value *V, unsigned Pos, | |
| Type *Ty) { | |
| Value *Scalar = V; | |
| // Drop NUW from trunc to avoid incorrect codegen. | |
| Value *Trunced; | |
| if (match(Scalar, m_NUWTrunc(m_Value(Trunced)))) | |
| cast<TruncInst>(Scalar)->setHasNoUnsignedWrap(/*B=*/false); | |
| if (Scalar->getType() != Ty) { | |
| assert(Scalar->getType()->isIntOrIntVectorTy() && | |
| Ty->isIntOrIntVectorTy() && "Expected integer types only."); | |
| Value *V = Scalar; | |
| if (auto *CI = dyn_cast<CastInst>(Scalar); | |
| isa_and_nonnull<SExtInst, ZExtInst>(CI)) { | |
| Value *Op = CI->getOperand(0); | |
| if (auto *IOp = dyn_cast<Instruction>(Op); | |
| !IOp || !(isDeleted(IOp) || isVectorized(IOp))) | |
| V = Op; | |
| } | |
| Scalar = Builder.CreateIntCast( | |
| V, Ty, !isKnownNonNegative(Scalar, SimplifyQuery(*DL))); | |
| } | |
| Instruction *InsElt; | |
| if (auto *VecTy = dyn_cast<FixedVectorType>(Scalar->getType())) { | |
| assert(Opts.slp_revec && "FixedVectorType is not expected."); | |
| Vec = | |
| createInsertVector(Builder, Vec, Scalar, Pos * getNumElements(VecTy)); | |
| auto *II = dyn_cast<Instruction>(Vec); | |
| if (!II) | |
| return Vec; | |
| InsElt = II; | |
| } else { | |
| Vec = Builder.CreateInsertElement(Vec, Scalar, Pos); | |
| InsElt = dyn_cast<InsertElementInst>(Vec); | |
| if (!InsElt) | |
| return Vec; | |
| } | |
| GatherShuffleExtractSeq.insert(InsElt); | |
| CSEBlocks.insert(InsElt->getParent()); | |
| // Add to our 'need-to-extract' list. | |
| if (isa<Instruction>(V)) { | |
| ArrayRef<TreeEntry *> Entries = getTreeEntries(V); | |
| const auto *It = find_if(Entries, [&](const TreeEntry *E) { | |
| return !TransformedToGatherNodes.contains(E) && | |
| !DeletedNodes.contains(E); | |
| }); | |
| if (It != Entries.end()) { | |
| // Find which lane we need to extract. | |
| User *UserOp = nullptr; | |
| if (Scalar != V) { | |
| if (auto *SI = dyn_cast<Instruction>(Scalar)) | |
| UserOp = SI; | |
| } else { | |
| if (V->getType()->isVectorTy()) { | |
| if (auto *SV = dyn_cast<ShuffleVectorInst>(InsElt); | |
| SV && SV->getOperand(0) != V && SV->getOperand(1) != V) { | |
| // Find shufflevector, caused by resize. | |
| auto FindOperand = [](Value *Vec, Value *V) -> Instruction * { | |
| if (auto *SV = dyn_cast<ShuffleVectorInst>(Vec)) { | |
| if (SV->getOperand(0) == V) | |
| return SV; | |
| if (SV->getOperand(1) == V) | |
| return SV; | |
| } | |
| return nullptr; | |
| }; | |
| InsElt = nullptr; | |
| if (Instruction *User = FindOperand(SV->getOperand(0), V)) | |
| InsElt = User; | |
| else if (Instruction *User = FindOperand(SV->getOperand(1), V)) | |
| InsElt = User; | |
| assert(InsElt && | |
| "Failed to find shufflevector, caused by resize."); | |
| } else if (Opts.slp_revec && isa<ShuffleVectorInst>(InsElt)) { | |
| // ReVec gather used V directly as a shufflevector operand. | |
| // Register a nullptr-User external use so all remaining | |
| // in-IR uses of V get rewritten via replaceAllUsesWith, | |
| // and track V in ExternalUsesWithNonUsers to match the | |
| // bookkeeping done by buildExternalUses. | |
| unsigned FoundLane = (*It)->findLaneForValue(V); | |
| ExternalUses.emplace_back(V, nullptr, **It, FoundLane); | |
| ExternalUsesWithNonUsers.insert(V); | |
| } | |
| } | |
| UserOp = InsElt; | |
| } | |
| if (UserOp) { | |
| unsigned FoundLane = (*It)->findLaneForValue(V); | |
| ExternalUses.emplace_back(V, UserOp, **It, FoundLane); | |
| } | |
| } | |
| } | |
| return Vec; | |
| }; | |
| auto *VecTy = getWidenedType(ScalarTy, VL.size()); | |
| Value *Vec = PoisonValue::get(VecTy); | |
| SmallVector<int> NonConsts; | |
| SmallVector<int> Mask(VL.size()); | |
| std::iota(Mask.begin(), Mask.end(), 0); | |
| Value *OriginalRoot = Root; | |
| if (auto *SV = dyn_cast_if_present<ShuffleVectorInst>(Root); | |
| SV && isa<PoisonValue>(SV->getOperand(1)) && | |
| SV->getOperand(0)->getType() == VecTy) { | |
| Root = SV->getOperand(0); | |
| Mask.assign(SV->getShuffleMask().begin(), SV->getShuffleMask().end()); | |
| } | |
| // Insert constant values at first. | |
| for (int I = 0, E = VL.size(); I < E; ++I) { | |
| if (PostponedIndices.contains(I)) | |
| continue; | |
| if (!isConstant(VL[I])) { | |
| NonConsts.push_back(I); | |
| continue; | |
| } | |
| if (isa<PoisonValue>(VL[I])) | |
| continue; | |
| Vec = CreateInsertElement(Vec, VL[I], I, ScalarTy); | |
| Mask[I] = I + E; | |
| } | |
| if (Root) { | |
| if (isa<PoisonValue>(Vec)) { | |
| Vec = OriginalRoot; | |
| } else { | |
| Vec = CreateShuffle(Root, Vec, Mask); | |
| if (auto *OI = dyn_cast<Instruction>(OriginalRoot); | |
| OI && OI->use_empty() && | |
| none_of(VectorizableTree, [&](const std::unique_ptr<TreeEntry> &TE) { | |
| return TE->VectorizedValue == OI; | |
| })) | |
| eraseInstruction(OI); | |
| } | |
| } | |
| // Insert non-constant values. | |
| for (int I : NonConsts) | |
| Vec = CreateInsertElement(Vec, VL[I], I, ScalarTy); | |
| // Append instructions, which are/may be part of the loop, in the end to make | |
| // it possible to hoist non-loop-based instructions. | |
| for (const std::pair<Value *, unsigned> &Pair : PostponedInsts) | |
| Vec = CreateInsertElement(Vec, Pair.first, Pair.second, ScalarTy); | |
| return Vec; | |
| } | |
| /// Merges shuffle masks and emits final shuffle instruction, if required. It | |
| /// supports shuffling of 2 input vectors. It implements lazy shuffles emission, | |
| /// when the actual shuffle instruction is generated only if this is actually | |
| /// required. Otherwise, the shuffle instruction emission is delayed till the | |
| /// end of the process, to reduce the number of emitted instructions and further | |
| /// analysis/transformations. | |
| /// The class also will look through the previously emitted shuffle instructions | |
| /// and properly mark indices in mask as undef. | |
| /// For example, given the code | |
| /// \code | |
| /// %s1 = shufflevector <2 x ty> %0, poison, <1, 0> | |
| /// %s2 = shufflevector <2 x ty> %1, poison, <1, 0> | |
| /// \endcode | |
| /// and if need to emit shuffle of %s1 and %s2 with mask <1, 0, 3, 2>, it will | |
| /// look through %s1 and %s2 and emit | |
| /// \code | |
| /// %res = shufflevector <2 x ty> %0, %1, <0, 1, 2, 3> | |
| /// \endcode | |
| /// instead. | |
| /// If 2 operands are of different size, the smallest one will be resized and | |
| /// the mask recalculated properly. | |
| /// For example, given the code | |
| /// \code | |
| /// %s1 = shufflevector <2 x ty> %0, poison, <1, 0, 1, 0> | |
| /// %s2 = shufflevector <2 x ty> %1, poison, <1, 0, 1, 0> | |
| /// \endcode | |
| /// and if need to emit shuffle of %s1 and %s2 with mask <1, 0, 5, 4>, it will | |
| /// look through %s1 and %s2 and emit | |
| /// \code | |
| /// %res = shufflevector <2 x ty> %0, %1, <0, 1, 2, 3> | |
| /// \endcode | |
| /// instead. | |
| class BoUpSLP::ShuffleInstructionBuilder final : public BaseShuffleAnalysis { | |
| bool IsFinalized = false; | |
| /// Combined mask for all applied operands and masks. It is built during | |
| /// analysis and actual emission of shuffle vector instructions. | |
| SmallVector<int> CommonMask; | |
| /// List of operands for the shuffle vector instruction. It hold at max 2 | |
| /// operands, if the 3rd is going to be added, the first 2 are combined into | |
| /// shuffle with \p CommonMask mask, the first operand sets to be the | |
| /// resulting shuffle and the second operand sets to be the newly added | |
| /// operand. The \p CommonMask is transformed in the proper way after that. | |
| SmallVector<Value *, 2> InVectors; | |
| IRBuilderBase &Builder; | |
| BoUpSLP &R; | |
| class ShuffleIRBuilder { | |
| IRBuilderBase &Builder; | |
| /// Holds all of the instructions that we gathered. | |
| SetVector<Instruction *> &GatherShuffleExtractSeq; | |
| /// A list of blocks that we are going to CSE. | |
| DenseSet<BasicBlock *> &CSEBlocks; | |
| /// Data layout. | |
| const DataLayout &DL; | |
| public: | |
| ShuffleIRBuilder(IRBuilderBase &Builder, | |
| SetVector<Instruction *> &GatherShuffleExtractSeq, | |
| DenseSet<BasicBlock *> &CSEBlocks, const DataLayout &DL) | |
| : Builder(Builder), GatherShuffleExtractSeq(GatherShuffleExtractSeq), | |
| CSEBlocks(CSEBlocks), DL(DL) {} | |
| ~ShuffleIRBuilder() = default; | |
| /// Creates shufflevector for the 2 operands with the given mask. | |
| Value *createShuffleVector(Value *V1, Value *V2, ArrayRef<int> Mask) { | |
| if (V1->getType() != V2->getType()) { | |
| assert(V1->getType()->isIntOrIntVectorTy() && | |
| V1->getType()->isIntOrIntVectorTy() && | |
| "Expected integer vector types only."); | |
| if (V1->getType() != V2->getType()) { | |
| if (cast<VectorType>(V2->getType()) | |
| ->getElementType() | |
| ->getIntegerBitWidth() < cast<VectorType>(V1->getType()) | |
| ->getElementType() | |
| ->getIntegerBitWidth()) | |
| V2 = Builder.CreateIntCast( | |
| V2, V1->getType(), !isKnownNonNegative(V2, SimplifyQuery(DL))); | |
| else | |
| V1 = Builder.CreateIntCast( | |
| V1, V2->getType(), !isKnownNonNegative(V1, SimplifyQuery(DL))); | |
| } | |
| } | |
| Value *Vec = Builder.CreateShuffleVector(V1, V2, Mask); | |
| if (auto *I = dyn_cast<Instruction>(Vec)) { | |
| GatherShuffleExtractSeq.insert(I); | |
| CSEBlocks.insert(I->getParent()); | |
| } | |
| return Vec; | |
| } | |
| /// Creates permutation of the single vector operand with the given mask, if | |
| /// it is not identity mask. | |
| Value *createShuffleVector(Value *V1, ArrayRef<int> Mask) { | |
| if (Mask.empty()) | |
| return V1; | |
| unsigned VF = Mask.size(); | |
| unsigned LocalVF = cast<FixedVectorType>(V1->getType())->getNumElements(); | |
| if (VF == LocalVF && ShuffleVectorInst::isIdentityMask(Mask, VF)) | |
| return V1; | |
| Value *Vec = Builder.CreateShuffleVector(V1, Mask); | |
| if (auto *I = dyn_cast<Instruction>(Vec)) { | |
| GatherShuffleExtractSeq.insert(I); | |
| CSEBlocks.insert(I->getParent()); | |
| } | |
| return Vec; | |
| } | |
| Value *createIdentity(Value *V) { return V; } | |
| Value *createPoison(Type *Ty, unsigned VF) { | |
| return PoisonValue::get(getWidenedType(Ty, VF)); | |
| } | |
| /// Resizes 2 input vector to match the sizes, if the they are not equal | |
| /// yet. The smallest vector is resized to the size of the larger vector. | |
| void resizeToMatch(Value *&V1, Value *&V2) { | |
| if (V1->getType() == V2->getType()) | |
| return; | |
| int V1VF = cast<FixedVectorType>(V1->getType())->getNumElements(); | |
| int V2VF = cast<FixedVectorType>(V2->getType())->getNumElements(); | |
| int VF = std::max(V1VF, V2VF); | |
| int MinVF = std::min(V1VF, V2VF); | |
| SmallVector<int> IdentityMask(VF, PoisonMaskElem); | |
| std::iota(IdentityMask.begin(), std::next(IdentityMask.begin(), MinVF), | |
| 0); | |
| Value *&Op = MinVF == V1VF ? V1 : V2; | |
| Op = Builder.CreateShuffleVector(Op, IdentityMask); | |
| if (auto *I = dyn_cast<Instruction>(Op)) { | |
| GatherShuffleExtractSeq.insert(I); | |
| CSEBlocks.insert(I->getParent()); | |
| } | |
| if (MinVF == V1VF) | |
| V1 = Op; | |
| else | |
| V2 = Op; | |
| } | |
| }; | |
| /// Smart shuffle instruction emission, walks through shuffles trees and | |
| /// tries to find the best matching vector for the actual shuffle | |
| /// instruction. | |
| Value *createShuffle(Value *V1, Value *V2, ArrayRef<int> Mask) { | |
| assert(V1 && "Expected at least one vector value."); | |
| ShuffleIRBuilder ShuffleBuilder(Builder, R.GatherShuffleExtractSeq, | |
| R.CSEBlocks, *R.DL); | |
| return BaseShuffleAnalysis::createShuffle<Value *>( | |
| V1, V2, Mask, ShuffleBuilder, ScalarTy, R.Opts.slp_revec); | |
| } | |
| /// Cast value \p V to the vector type with the same number of elements, but | |
| /// the base type \p ScalarTy. | |
| Value *castToScalarTyElem(Value *V, | |
| std::optional<bool> IsSigned = std::nullopt) { | |
| auto *VecTy = cast<VectorType>(V->getType()); | |
| assert(getNumElements(VecTy) % getNumElements(ScalarTy) == 0); | |
| if (VecTy->getElementType() == ScalarTy->getScalarType()) | |
| return V; | |
| return Builder.CreateIntCast( | |
| V, VectorType::get(ScalarTy->getScalarType(), VecTy->getElementCount()), | |
| IsSigned.value_or(!isKnownNonNegative(V, SimplifyQuery(*R.DL)))); | |
| } | |
| Value *getVectorizedValue(const TreeEntry &E) { | |
| Value *Vec = E.VectorizedValue; | |
| if (!Vec->getType()->isIntOrIntVectorTy()) | |
| return Vec; | |
| return castToScalarTyElem(Vec, any_of(E.Scalars, [&](Value *V) { | |
| return !isa<PoisonValue>(V) && | |
| !isKnownNonNegative( | |
| V, SimplifyQuery(*R.DL)); | |
| })); | |
| } | |
| public: | |
| ShuffleInstructionBuilder(Type *ScalarTy, IRBuilderBase &Builder, BoUpSLP &R) | |
| : BaseShuffleAnalysis(ScalarTy), Builder(Builder), R(R) {} | |
| /// Adjusts extractelements after reusing them. | |
| Value *adjustExtracts(const TreeEntry *E, MutableArrayRef<int> Mask, | |
| ArrayRef<std::optional<TTI::ShuffleKind>> ShuffleKinds, | |
| unsigned NumParts, bool &UseVecBaseAsInput) { | |
| UseVecBaseAsInput = false; | |
| SmallPtrSet<Value *, 4> UniqueBases; | |
| Value *VecBase = nullptr; | |
| SmallVector<Value *> VL(E->Scalars.begin(), E->Scalars.end()); | |
| if (!E->ReorderIndices.empty()) { | |
| SmallVector<int> ReorderMask(E->ReorderIndices.begin(), | |
| E->ReorderIndices.end()); | |
| reorderScalars(VL, ReorderMask); | |
| } | |
| for (int I = 0, Sz = Mask.size(); I < Sz; ++I) { | |
| int Idx = Mask[I]; | |
| if (Idx == PoisonMaskElem) | |
| continue; | |
| auto *EI = cast<ExtractElementInst>(VL[I]); | |
| VecBase = EI->getVectorOperand(); | |
| if (ArrayRef<TreeEntry *> TEs = R.getTreeEntries(VecBase); !TEs.empty()) | |
| VecBase = TEs.front()->VectorizedValue; | |
| assert(VecBase && "Expected vectorized value."); | |
| UniqueBases.insert(VecBase); | |
| // If the only one use is vectorized - can delete the extractelement | |
| // itself. | |
| if (!EI->hasOneUse() || R.ExternalUsesAsOriginalScalar.contains(EI) || | |
| (E->UserTreeIndex && E->UserTreeIndex.EdgeIdx == UINT_MAX && | |
| !R.isVectorized(EI) && | |
| count_if(E->Scalars, [&](Value *V) { return V == EI; }) != | |
| count_if(E->UserTreeIndex.UserTE->Scalars, | |
| [&](Value *V) { return V == EI; })) || | |
| (NumParts != 1 && count(VL, EI) > 1) || | |
| any_of(EI->users(), [&](User *U) { | |
| ArrayRef<TreeEntry *> UTEs = R.getTreeEntries(U); | |
| return UTEs.empty() || UTEs.size() > 1 || | |
| any_of(UTEs, | |
| [&](const TreeEntry *TE) { | |
| return R.DeletedNodes.contains(TE) || | |
| R.TransformedToGatherNodes.contains(TE); | |
| }) || | |
| (isa<GetElementPtrInst>(U) && | |
| !R.areAllUsersVectorized(cast<Instruction>(U))) || | |
| (!UTEs.empty() && | |
| count_if(R.VectorizableTree, | |
| [&](const std::unique_ptr<TreeEntry> &TE) { | |
| return TE->UserTreeIndex.UserTE == | |
| UTEs.front() && | |
| is_contained(VL, EI); | |
| }) != 1); | |
| })) | |
| continue; | |
| R.eraseInstruction(EI); | |
| } | |
| if (NumParts == 1 || UniqueBases.size() == 1) { | |
| assert(VecBase && "Expected vectorized value."); | |
| return castToScalarTyElem(VecBase); | |
| } | |
| UseVecBaseAsInput = true; | |
| auto TransformToIdentity = [](MutableArrayRef<int> Mask) { | |
| for (auto [I, Idx] : enumerate(Mask)) | |
| if (Idx != PoisonMaskElem) | |
| Idx = I; | |
| }; | |
| // Perform multi-register vector shuffle, joining them into a single virtual | |
| // long vector. | |
| // Need to shuffle each part independently and then insert all this parts | |
| // into a long virtual vector register, forming the original vector. | |
| Value *Vec = nullptr; | |
| SmallVector<int> VecMask(Mask.size(), PoisonMaskElem); | |
| unsigned SliceSize = getPartNumElems(VL.size(), NumParts); | |
| for (unsigned Part : seq<unsigned>(NumParts)) { | |
| unsigned Limit = getNumElems(VL.size(), SliceSize, Part); | |
| ArrayRef<Value *> SubVL = ArrayRef(VL).slice(Part * SliceSize, Limit); | |
| MutableArrayRef<int> SubMask = Mask.slice(Part * SliceSize, Limit); | |
| constexpr int MaxBases = 2; | |
| SmallVector<Value *, MaxBases> Bases(MaxBases); | |
| auto VLMask = zip(SubVL, SubMask); | |
| const unsigned VF = | |
| accumulate(VLMask, 0U, [&](unsigned S, const auto &D) { | |
| if (std::get<1>(D) == PoisonMaskElem) | |
| return S; | |
| Value *VecOp = | |
| cast<ExtractElementInst>(std::get<0>(D))->getVectorOperand(); | |
| if (ArrayRef<TreeEntry *> TEs = R.getTreeEntries(VecOp); | |
| !TEs.empty()) | |
| VecOp = TEs.front()->VectorizedValue; | |
| assert(VecOp && "Expected vectorized value."); | |
| const unsigned Size = | |
| cast<FixedVectorType>(VecOp->getType())->getNumElements(); | |
| return std::max(S, Size); | |
| }); | |
| for (const auto [V, I] : make_filter_range(VLMask, [](const auto &P) { | |
| return std::get<1>(P) != PoisonMaskElem; | |
| })) { | |
| Value *VecOp = cast<ExtractElementInst>(V)->getVectorOperand(); | |
| if (ArrayRef<TreeEntry *> TEs = R.getTreeEntries(VecOp); !TEs.empty()) | |
| VecOp = TEs.front()->VectorizedValue; | |
| assert(VecOp && "Expected vectorized value."); | |
| VecOp = castToScalarTyElem(VecOp); | |
| Bases[I / VF] = VecOp; | |
| } | |
| if (!Bases.front()) | |
| continue; | |
| Value *SubVec; | |
| if (Bases.back()) { | |
| SubVec = createShuffle(Bases.front(), Bases.back(), SubMask); | |
| TransformToIdentity(SubMask); | |
| } else { | |
| SubVec = Bases.front(); | |
| } | |
| if (!Vec) { | |
| Vec = SubVec; | |
| assert((Part == 0 || all_of(seq<unsigned>(0, Part), | |
| [&](unsigned P) { | |
| ArrayRef<int> SubMask = | |
| Mask.slice(P * SliceSize, | |
| getNumElems(Mask.size(), | |
| SliceSize, P)); | |
| return all_of(SubMask, [](int Idx) { | |
| return Idx == PoisonMaskElem; | |
| }); | |
| })) && | |
| "Expected first part or all previous parts masked."); | |
| copy(SubMask, std::next(VecMask.begin(), Part * SliceSize)); | |
| } else { | |
| unsigned NewVF = | |
| cast<FixedVectorType>(Vec->getType())->getNumElements(); | |
| if (Vec->getType() != SubVec->getType()) { | |
| unsigned SubVecVF = | |
| cast<FixedVectorType>(SubVec->getType())->getNumElements(); | |
| NewVF = std::max(NewVF, SubVecVF); | |
| } | |
| // Adjust SubMask. | |
| for (int &Idx : SubMask) | |
| if (Idx != PoisonMaskElem) | |
| Idx += NewVF; | |
| copy(SubMask, std::next(VecMask.begin(), Part * SliceSize)); | |
| Vec = createShuffle(Vec, SubVec, VecMask); | |
| TransformToIdentity(VecMask); | |
| } | |
| } | |
| copy(VecMask, Mask.begin()); | |
| return Vec; | |
| } | |
| /// Checks if the specified entry \p E needs to be delayed because of its | |
| /// dependency nodes. | |
| std::optional<Value *> | |
| needToDelay(const TreeEntry *E, | |
| ArrayRef<SmallVector<const TreeEntry *>> Deps) const { | |
| // No need to delay emission if all deps are ready. | |
| if (all_of(Deps, [](ArrayRef<const TreeEntry *> TEs) { | |
| return all_of( | |
| TEs, [](const TreeEntry *TE) { return TE->VectorizedValue; }); | |
| })) | |
| return std::nullopt; | |
| // Postpone gather emission, will be emitted after the end of the | |
| // process to keep correct order. | |
| auto *ResVecTy = getWidenedType(ScalarTy, E->getVectorFactor()); | |
| return Builder.CreateAlignedLoad( | |
| ResVecTy, | |
| PoisonValue::get(PointerType::getUnqual(ScalarTy->getContext())), | |
| MaybeAlign()); | |
| } | |
| /// Reset the builder to handle perfect diamond match. | |
| void resetForSameNode() { | |
| IsFinalized = false; | |
| CommonMask.clear(); | |
| InVectors.clear(); | |
| } | |
| /// Adds 2 input vectors (in form of tree entries) and the mask for their | |
| /// shuffling. | |
| void add(const TreeEntry &E1, const TreeEntry &E2, ArrayRef<int> Mask) { | |
| Value *V1 = getVectorizedValue(E1); | |
| Value *V2 = getVectorizedValue(E2); | |
| add(V1, V2, Mask); | |
| } | |
| /// Adds single input vector (in form of tree entry) and the mask for its | |
| /// shuffling. | |
| void add(const TreeEntry &E1, ArrayRef<int> Mask) { | |
| Value *V1 = getVectorizedValue(E1); | |
| add(V1, Mask); | |
| } | |
| /// Adds 2 input vectors and the mask for their shuffling. | |
| void add(Value *V1, Value *V2, ArrayRef<int> Mask) { | |
| assert(V1 && V2 && !Mask.empty() && "Expected non-empty input vectors."); | |
| assert(isa<FixedVectorType>(V1->getType()) && | |
| isa<FixedVectorType>(V2->getType()) && | |
| "castToScalarTyElem expects V1 and V2 to be FixedVectorType"); | |
| V1 = castToScalarTyElem(V1); | |
| V2 = castToScalarTyElem(V2); | |
| if (InVectors.empty()) { | |
| InVectors.push_back(V1); | |
| InVectors.push_back(V2); | |
| CommonMask.assign(Mask.begin(), Mask.end()); | |
| return; | |
| } | |
| Value *Vec = InVectors.front(); | |
| if (InVectors.size() == 2) { | |
| Vec = createShuffle(Vec, InVectors.back(), CommonMask); | |
| transformMaskAfterShuffle(CommonMask, CommonMask); | |
| } else if (cast<FixedVectorType>(Vec->getType())->getNumElements() != | |
| Mask.size()) { | |
| Vec = createShuffle(Vec, nullptr, CommonMask); | |
| transformMaskAfterShuffle(CommonMask, CommonMask); | |
| } | |
| V1 = createShuffle(V1, V2, Mask); | |
| unsigned VF = std::max(getVF(V1), getVF(Vec)); | |
| for (unsigned Idx = 0, Sz = CommonMask.size(); Idx < Sz; ++Idx) | |
| if (Mask[Idx] != PoisonMaskElem) | |
| CommonMask[Idx] = Idx + VF; | |
| InVectors.front() = Vec; | |
| if (InVectors.size() == 2) | |
| InVectors.back() = V1; | |
| else | |
| InVectors.push_back(V1); | |
| } | |
| /// Adds another one input vector and the mask for the shuffling. | |
| void add(Value *V1, ArrayRef<int> Mask, bool = false, | |
| TTI::VectorInstrContext = TTI::VectorInstrContext::None) { | |
| assert(isa<FixedVectorType>(V1->getType()) && | |
| "castToScalarTyElem expects V1 to be FixedVectorType"); | |
| V1 = castToScalarTyElem(V1); | |
| if (InVectors.empty()) { | |
| InVectors.push_back(V1); | |
| CommonMask.assign(Mask.begin(), Mask.end()); | |
| return; | |
| } | |
| const auto *It = find(InVectors, V1); | |
| if (It == InVectors.end()) { | |
| if (InVectors.size() == 2 || | |
| InVectors.front()->getType() != V1->getType()) { | |
| Value *V = InVectors.front(); | |
| if (InVectors.size() == 2) { | |
| V = createShuffle(InVectors.front(), InVectors.back(), CommonMask); | |
| transformMaskAfterShuffle(CommonMask, CommonMask); | |
| } else if (cast<FixedVectorType>(V->getType())->getNumElements() != | |
| CommonMask.size()) { | |
| V = createShuffle(InVectors.front(), nullptr, CommonMask); | |
| transformMaskAfterShuffle(CommonMask, CommonMask); | |
| } | |
| unsigned VF = std::max(CommonMask.size(), Mask.size()); | |
| for (unsigned Idx = 0, Sz = CommonMask.size(); Idx < Sz; ++Idx) | |
| if (CommonMask[Idx] == PoisonMaskElem && Mask[Idx] != PoisonMaskElem) | |
| CommonMask[Idx] = V->getType() != V1->getType() | |
| ? Idx + VF | |
| : Mask[Idx] + getVF(V1); | |
| if (V->getType() != V1->getType()) | |
| V1 = createShuffle(V1, nullptr, Mask); | |
| InVectors.front() = V; | |
| if (InVectors.size() == 2) | |
| InVectors.back() = V1; | |
| else | |
| InVectors.push_back(V1); | |
| return; | |
| } | |
| // Check if second vector is required if the used elements are already | |
| // used from the first one. | |
| for (unsigned Idx = 0, Sz = CommonMask.size(); Idx < Sz; ++Idx) | |
| if (Mask[Idx] != PoisonMaskElem && CommonMask[Idx] == PoisonMaskElem) { | |
| InVectors.push_back(V1); | |
| break; | |
| } | |
| } | |
| unsigned VF = 0; | |
| for (Value *V : InVectors) | |
| VF = std::max(VF, getVF(V)); | |
| for (unsigned Idx = 0, Sz = CommonMask.size(); Idx < Sz; ++Idx) | |
| if (Mask[Idx] != PoisonMaskElem && CommonMask[Idx] == PoisonMaskElem) | |
| CommonMask[Idx] = Mask[Idx] + (It == InVectors.begin() ? 0 : VF); | |
| } | |
| /// Adds another one input vector and the mask for the shuffling. | |
| void addOrdered(Value *V1, ArrayRef<unsigned> Order) { | |
| SmallVector<int> NewMask; | |
| inversePermutation(Order, NewMask); | |
| add(V1, NewMask); | |
| } | |
| Value *gather(ArrayRef<Value *> VL, unsigned MaskVF = 0, | |
| Value *Root = nullptr, | |
| TTI::VectorInstrContext = TTI::VectorInstrContext::None) { | |
| return R.gather(VL, Root, ScalarTy, | |
| [&](Value *V1, Value *V2, ArrayRef<int> Mask) { | |
| return createShuffle(V1, V2, Mask); | |
| }); | |
| } | |
| /// Emits the gather of zero-extended sub-fields of width \p FieldWidth of | |
| /// the same wider integer scalar \p Src, permuted by \p Mask, as a bitcast | |
| /// to the field vector plus a permutation and an extension to the lane type. | |
| Value *createExtractedFieldsVector(Value *Src, unsigned FieldWidth, | |
| ArrayRef<int> Mask, const TreeEntry &) { | |
| auto *FieldTy = IntegerType::get(Src->getContext(), FieldWidth); | |
| unsigned SrcWidth = Src->getType()->getIntegerBitWidth(); | |
| assert(SrcWidth % FieldWidth == 0 && | |
| "Expected the field width to divide the source width."); | |
| Value *Vec = Builder.CreateBitCast( | |
| Src, getWidenedType(FieldTy, SrcWidth / FieldWidth)); | |
| if (auto *I = dyn_cast<Instruction>(Vec)) { | |
| R.GatherShuffleExtractSeq.insert(I); | |
| R.CSEBlocks.insert(I->getParent()); | |
| // A vectorized source scalar is erased; extract it for the bitcast. | |
| ArrayRef<TreeEntry *> TEs = R.getTreeEntries(Src); | |
| const auto *It = find_if_not(TEs, [&](const TreeEntry *TE) { | |
| return R.TransformedToGatherNodes.contains(TE) || | |
| R.DeletedNodes.contains(TE); | |
| }); | |
| if (It != TEs.end()) | |
| R.ExternalUses.emplace_back(Src, I, **It, (*It)->findLaneForValue(Src)); | |
| } | |
| Vec = createShuffle(Vec, /*V2=*/nullptr, Mask); | |
| // The extracted fields are unsigned. | |
| Vec = castToScalarTyElem(Vec, /*IsSigned=*/false); | |
| return Vec; | |
| } | |
| Value *createFreeze(Value *V) { return Builder.CreateFreeze(V); } | |
| /// Finalize emission of the shuffles. | |
| /// \param Action the action (if any) to be performed before final applying of | |
| /// the \p ExtMask mask. | |
| Value *finalize( | |
| ArrayRef<int> ExtMask, | |
| ArrayRef<std::pair<const TreeEntry *, unsigned>> SubVectors, | |
| ArrayRef<int> SubVectorsMask, unsigned VF = 0, | |
| function_ref<void(Value *&, SmallVectorImpl<int> &, | |
| function_ref<Value *(Value *, Value *, ArrayRef<int>)>)> | |
| Action = {}) { | |
| IsFinalized = true; | |
| if (Action) { | |
| Value *Vec = InVectors.front(); | |
| if (InVectors.size() == 2) { | |
| Vec = createShuffle(Vec, InVectors.back(), CommonMask); | |
| InVectors.pop_back(); | |
| } else { | |
| Vec = createShuffle(Vec, nullptr, CommonMask); | |
| } | |
| transformMaskAfterShuffle(CommonMask, CommonMask); | |
| assert(VF > 0 && | |
| "Expected vector length for the final value before action."); | |
| unsigned VecVF = cast<FixedVectorType>(Vec->getType())->getNumElements(); | |
| if (VecVF < VF) { | |
| SmallVector<int> ResizeMask(VF, PoisonMaskElem); | |
| std::iota(ResizeMask.begin(), std::next(ResizeMask.begin(), VecVF), 0); | |
| Vec = createShuffle(Vec, nullptr, ResizeMask); | |
| } | |
| Action(Vec, CommonMask, [this](Value *V1, Value *V2, ArrayRef<int> Mask) { | |
| return createShuffle(V1, V2, Mask); | |
| }); | |
| InVectors.front() = Vec; | |
| } | |
| if (!SubVectors.empty()) { | |
| Value *Vec = InVectors.front(); | |
| if (InVectors.size() == 2) { | |
| Vec = createShuffle(Vec, InVectors.back(), CommonMask); | |
| InVectors.pop_back(); | |
| } else { | |
| Vec = createShuffle(Vec, nullptr, CommonMask); | |
| } | |
| transformMaskAfterShuffle(CommonMask, CommonMask); | |
| auto CreateSubVectors = [&](Value *Vec, | |
| SmallVectorImpl<int> &CommonMask) { | |
| for (auto [E, Idx] : SubVectors) { | |
| Value *V = getVectorizedValue(*E); | |
| unsigned InsertionIndex = Idx * getNumElements(ScalarTy); | |
| // Use scalar version of the SCalarType to correctly handle shuffles | |
| // for revectorization. The revectorization mode operates by the | |
| // vectors, but here we need to operate on the scalars, because the | |
| // masks were already transformed for the vector elements and we don't | |
| // need doing this transformation again. | |
| Type *OrigScalarTy = ScalarTy; | |
| ScalarTy = ScalarTy->getScalarType(); | |
| Vec = createInsertVector( | |
| Builder, Vec, V, InsertionIndex, | |
| std::bind(&ShuffleInstructionBuilder::createShuffle, this, _1, _2, | |
| _3)); | |
| ScalarTy = OrigScalarTy; | |
| if (!CommonMask.empty()) { | |
| std::iota(std::next(CommonMask.begin(), Idx), | |
| std::next(CommonMask.begin(), Idx + E->getVectorFactor()), | |
| Idx); | |
| } | |
| } | |
| return Vec; | |
| }; | |
| if (SubVectorsMask.empty()) { | |
| Vec = CreateSubVectors(Vec, CommonMask); | |
| } else { | |
| SmallVector<int> SVMask(CommonMask.size(), PoisonMaskElem); | |
| copy(SubVectorsMask, SVMask.begin()); | |
| for (auto [I1, I2] : zip(SVMask, CommonMask)) { | |
| if (I2 != PoisonMaskElem) { | |
| assert(I1 == PoisonMaskElem && "Expected unused subvectors mask"); | |
| I1 = I2 + CommonMask.size(); | |
| } | |
| } | |
| Value *InsertVec = | |
| CreateSubVectors(PoisonValue::get(Vec->getType()), CommonMask); | |
| Vec = createShuffle(InsertVec, Vec, SVMask); | |
| transformMaskAfterShuffle(CommonMask, SVMask); | |
| } | |
| InVectors.front() = Vec; | |
| } | |
| if (!ExtMask.empty()) { | |
| if (CommonMask.empty()) { | |
| CommonMask.assign(ExtMask.begin(), ExtMask.end()); | |
| } else { | |
| SmallVector<int> NewMask(ExtMask.size(), PoisonMaskElem); | |
| for (int I = 0, Sz = ExtMask.size(); I < Sz; ++I) { | |
| if (ExtMask[I] == PoisonMaskElem) | |
| continue; | |
| NewMask[I] = CommonMask[ExtMask[I]]; | |
| } | |
| CommonMask.swap(NewMask); | |
| } | |
| } | |
| if (CommonMask.empty()) { | |
| assert(InVectors.size() == 1 && "Expected only one vector with no mask"); | |
| return InVectors.front(); | |
| } | |
| if (InVectors.size() == 2) | |
| return createShuffle(InVectors.front(), InVectors.back(), CommonMask); | |
| return createShuffle(InVectors.front(), nullptr, CommonMask); | |
| } | |
| ~ShuffleInstructionBuilder() { | |
| assert((IsFinalized || CommonMask.empty()) && | |
| "Shuffle construction must be finalized."); | |
| } | |
| }; | |
| Value *BoUpSLP::vectorizeOperand(TreeEntry *E, unsigned NodeIdx) { | |
| return vectorizeTree(getOperandEntry(E, NodeIdx)); | |
| } | |
| template <typename ResTy, typename BVTy> | |
| std::optional<ResTy> BoUpSLP::processExtractedFieldsGather( | |
| BVTy &ShuffleBuilder, const TreeEntry *E, Type *ScalarTy, | |
| ArrayRef<Value *> VL, | |
| ArrayRef<std::pair<const TreeEntry *, unsigned>> SubVectors) { | |
| if (!SubVectors.empty() || !E->ReuseShuffleIndices.empty() || | |
| !ScalarTy->isIntegerTy()) | |
| return std::nullopt; | |
| std::optional<std::tuple<Value *, unsigned, SmallVector<int>>> | |
| ExtractedFields = matchGatheredExtractedFields(VL, *DL); | |
| if (!ExtractedFields || | |
| ScalarTy->getIntegerBitWidth() < std::get<1>(*ExtractedFields)) | |
| return std::nullopt; | |
| Value *Src = std::get<0>(*ExtractedFields); | |
| unsigned FieldWidth = std::get<1>(*ExtractedFields); | |
| SmallVector<int> &Mask = std::get<2>(*ExtractedFields); | |
| return ShuffleBuilder.createExtractedFieldsVector(Src, FieldWidth, Mask, *E); | |
| } | |
| template <typename BVTy, typename ResTy, typename... Args> | |
| ResTy BoUpSLP::processBuildVector(const TreeEntry *E, Type *ScalarTy, | |
| Args &...Params) { | |
| assert((E->isGather() || TransformedToGatherNodes.contains(E)) && | |
| "Expected gather node."); | |
| unsigned VF = E->getVectorFactor(); | |
| bool NeedFreeze = false; | |
| SmallVector<Value *> GatheredScalars(E->Scalars.begin(), E->Scalars.end()); | |
| // Do not process split vectorize node, marked to be gathers/buildvectors. | |
| SmallVector<std::pair<const TreeEntry *, unsigned>> SubVectors( | |
| E->CombinedEntriesWithIndices.size()); | |
| if (E->State == TreeEntry::SplitVectorize && | |
| TransformedToGatherNodes.contains(E)) { | |
| SubVectors.clear(); | |
| } else { | |
| // Clear values, to be replaced by insertvector instructions. | |
| for (auto [EIdx, Idx] : E->CombinedEntriesWithIndices) | |
| for_each(MutableArrayRef(GatheredScalars) | |
| .slice(Idx, VectorizableTree[EIdx]->getVectorFactor()), | |
| [&](Value *&V) { V = PoisonValue::get(V->getType()); }); | |
| transform( | |
| E->CombinedEntriesWithIndices, SubVectors.begin(), [&](const auto &P) { | |
| return std::make_pair(VectorizableTree[P.first].get(), P.second); | |
| }); | |
| } | |
| // Build a mask out of the reorder indices and reorder scalars per this | |
| // mask. | |
| SmallVector<int> ReorderMask(E->ReorderIndices.begin(), | |
| E->ReorderIndices.end()); | |
| if (!ReorderMask.empty()) | |
| reorderScalars(GatheredScalars, ReorderMask); | |
| SmallVector<int> SubVectorsMask; | |
| inversePermutation(E->ReorderIndices, SubVectorsMask); | |
| // Transform non-clustered elements in the mask to poison (-1). | |
| // "Clustered" operations will be reordered using this mask later. | |
| if (!SubVectors.empty() && !SubVectorsMask.empty()) { | |
| for (unsigned I : seq<unsigned>(GatheredScalars.size())) | |
| if (E->Scalars[I] == GatheredScalars[ReorderMask[I]]) | |
| SubVectorsMask[ReorderMask[I]] = PoisonMaskElem; | |
| } else { | |
| SubVectorsMask.clear(); | |
| } | |
| SmallVector<Value *> StoredGS(GatheredScalars); | |
| auto FindReusedSplat = [&](MutableArrayRef<int> Mask, unsigned InputVF, | |
| unsigned I, unsigned SliceSize, | |
| bool IsNotPoisonous) { | |
| if (!isSplat(E->Scalars) || none_of(E->Scalars, [](Value *V) { | |
| return isa<UndefValue>(V) && !isa<PoisonValue>(V); | |
| })) | |
| return false; | |
| TreeEntry *UserTE = E->UserTreeIndex.UserTE; | |
| unsigned EdgeIdx = E->UserTreeIndex.EdgeIdx; | |
| if (UserTE->getNumOperands() != 2) | |
| return false; | |
| if (!IsNotPoisonous) { | |
| auto *It = find_if(ArrayRef(VectorizableTree).drop_front(UserTE->Idx + 1), | |
| [=](const std::unique_ptr<TreeEntry> &TE) { | |
| return TE->UserTreeIndex.UserTE == UserTE && | |
| TE->UserTreeIndex.EdgeIdx != EdgeIdx; | |
| }); | |
| if (It == VectorizableTree.end()) | |
| return false; | |
| SmallVector<Value *> GS((*It)->Scalars.begin(), (*It)->Scalars.end()); | |
| if (!(*It)->ReorderIndices.empty()) { | |
| inversePermutation((*It)->ReorderIndices, ReorderMask); | |
| reorderScalars(GS, ReorderMask); | |
| } | |
| if (!all_of(zip(GatheredScalars, GS), [&](const auto &P) { | |
| Value *V0 = std::get<0>(P); | |
| Value *V1 = std::get<1>(P); | |
| return !isa<UndefValue>(V0) || isa<PoisonValue>(V0) || | |
| (isa<UndefValue>(V0) && !isa<PoisonValue>(V0) && | |
| is_contained(E->Scalars, V1)); | |
| })) | |
| return false; | |
| } | |
| int Idx; | |
| if ((Mask.size() < InputVF && | |
| ShuffleVectorInst::isExtractSubvectorMask(Mask, InputVF, Idx) && | |
| Idx == 0) || | |
| (Mask.size() == InputVF && | |
| ShuffleVectorInst::isIdentityMask(Mask, Mask.size()))) { | |
| std::iota( | |
| std::next(Mask.begin(), I * SliceSize), | |
| std::next(Mask.begin(), | |
| I * SliceSize + getNumElems(Mask.size(), SliceSize, I)), | |
| 0); | |
| } else { | |
| unsigned IVal = | |
| *find_if_not(Mask, [](int Idx) { return Idx == PoisonMaskElem; }); | |
| std::fill( | |
| std::next(Mask.begin(), I * SliceSize), | |
| std::next(Mask.begin(), | |
| I * SliceSize + getNumElems(Mask.size(), SliceSize, I)), | |
| IVal); | |
| } | |
| return true; | |
| }; | |
| BVTy ShuffleBuilder(ScalarTy, Params...); | |
| ResTy Res = ResTy(); | |
| SmallVector<int> Mask; | |
| SmallVector<int> ExtractMask(GatheredScalars.size(), PoisonMaskElem); | |
| SmallVector<std::optional<TTI::ShuffleKind>> ExtractShuffles; | |
| Value *ExtractVecBase = nullptr; | |
| bool UseVecBaseAsInput = false; | |
| SmallVector<std::optional<TargetTransformInfo::ShuffleKind>> GatherShuffles; | |
| SmallVector<SmallVector<const TreeEntry *>> Entries; | |
| Type *OrigScalarTy = GatheredScalars.front()->getType(); | |
| auto *VecTy = getWidenedType(ScalarTy, GatheredScalars.size()); | |
| unsigned NumParts = getNumberOfParts(VecTy, ScalarTy, GatheredScalars.size()); | |
| if (std::optional<ResTy> Res = processExtractedFieldsGather<ResTy>( | |
| ShuffleBuilder, E, ScalarTy, GatheredScalars, SubVectors)) | |
| return *Res; | |
| if (!all_of(GatheredScalars, IsaPred<UndefValue>)) { | |
| // Check for gathered extracts. | |
| bool Resized = false; | |
| ExtractShuffles = | |
| tryToGatherExtractElements(GatheredScalars, ExtractMask, NumParts); | |
| if (!ExtractShuffles.empty()) { | |
| SmallVector<const TreeEntry *> ExtractEntries; | |
| for (const auto &P : | |
| make_filter_range(enumerate(ExtractMask), [](const auto &P) { | |
| return P.value() != PoisonMaskElem; | |
| })) { | |
| if (ArrayRef<TreeEntry *> TEs = | |
| getTreeEntries(cast<ExtractElementInst>(StoredGS[P.index()]) | |
| ->getVectorOperand()); | |
| !TEs.empty()) | |
| ExtractEntries.append(TEs.begin(), TEs.end()); | |
| } | |
| if (std::optional<ResTy> Delayed = | |
| ShuffleBuilder.needToDelay(E, ExtractEntries)) { | |
| // Delay emission of gathers which are not ready yet. | |
| PostponedGathers.insert(E); | |
| // Postpone gather emission, will be emitted after the end of the | |
| // process to keep correct order. | |
| return *Delayed; | |
| } | |
| if (Value *VecBase = ShuffleBuilder.adjustExtracts( | |
| E, ExtractMask, ExtractShuffles, NumParts, UseVecBaseAsInput)) { | |
| ExtractVecBase = VecBase; | |
| if (auto *VecBaseTy = dyn_cast<FixedVectorType>(VecBase->getType())) | |
| if (VF == VecBaseTy->getNumElements() && | |
| GatheredScalars.size() != VF) { | |
| Resized = true; | |
| GatheredScalars.append(VF - GatheredScalars.size(), | |
| PoisonValue::get(OrigScalarTy)); | |
| NumParts = getNumberOfParts(getWidenedType(OrigScalarTy, VF), | |
| OrigScalarTy, VF); | |
| } | |
| } | |
| } | |
| // Gather extracts after we check for full matched gathers only. | |
| if (!ExtractShuffles.empty() || !E->hasState() || | |
| E->getOpcode() != Instruction::Load || | |
| (((E->hasState() && E->getOpcode() == Instruction::Load) || | |
| any_of(E->Scalars, IsaPred<LoadInst>)) && | |
| any_of(make_isa_range<LoadInst>(E->Scalars), | |
| [this](LoadInst *LI) { return isVectorized(LI); })) || | |
| (E->hasState() && E->isAltShuffle()) || | |
| all_of(E->Scalars, [this](Value *V) { return isVectorized(V); }) || | |
| isSplat(E->Scalars) || | |
| (E->Scalars != GatheredScalars && GatheredScalars.size() <= 2)) { | |
| GatherShuffles = isGatherShuffledEntry(E, GatheredScalars, Mask, Entries, | |
| NumParts, ScalarTy); | |
| } | |
| // The splat gather subtrees are speculative: the keep/drop check may | |
| // delete them and does not track reuse of their internal gathers by other | |
| // gathers. | |
| if (hasSplatSubtreeGather(Entries)) { | |
| GatherShuffles.clear(); | |
| Entries.clear(); | |
| std::fill(Mask.begin(), Mask.end(), PoisonMaskElem); | |
| } | |
| if (!GatherShuffles.empty()) { | |
| if (std::optional<ResTy> Delayed = | |
| ShuffleBuilder.needToDelay(E, Entries)) { | |
| // Delay emission of gathers which are not ready yet. | |
| PostponedGathers.insert(E); | |
| // Postpone gather emission, will be emitted after the end of the | |
| // process to keep correct order. | |
| return *Delayed; | |
| } | |
| // Match against the expanded vector of the gather node, so that poison | |
| // lanes of the matched entry cannot wildcard-match its real scalars. | |
| auto IsFullVectorMatch = [&](const TreeEntry *FrontTE) { | |
| SmallVector<int> CommonMask = E->getCommonMask(); | |
| SmallVector<Value *> Expanded(E->getVectorFactor()); | |
| for (unsigned I : seq<unsigned>(E->getVectorFactor())) | |
| Expanded[I] = CommonMask.empty() ? E->Scalars[I] | |
| : CommonMask[I] == PoisonMaskElem | |
| ? PoisonValue::get(OrigScalarTy) | |
| : E->Scalars[CommonMask[I]]; | |
| return FrontTE->isSame(Expanded); | |
| }; | |
| if (GatherShuffles.size() == 1 && | |
| *GatherShuffles.front() == TTI::SK_PermuteSingleSrc && | |
| (Entries.front().front()->isSame(E->Scalars) || | |
| IsFullVectorMatch(Entries.front().front()))) { | |
| // Perfect match in the graph, will reuse the previously vectorized | |
| // node. Cost is 0. | |
| LLVM_DEBUG(dbgs() << "SLP: perfect diamond match for gather bundle " | |
| << shortBundleName(E->Scalars, E->Idx) << ".\n"); | |
| // Restore the mask for previous partially matched values. | |
| Mask.resize(E->Scalars.size()); | |
| const TreeEntry *FrontTE = Entries.front().front(); | |
| if (FrontTE->ReorderIndices.empty() && E->ReorderIndices.empty() && | |
| ((FrontTE->ReuseShuffleIndices.empty() && | |
| E->Scalars.size() == FrontTE->Scalars.size()) || | |
| (E->Scalars.size() == FrontTE->ReuseShuffleIndices.size()))) { | |
| std::iota(Mask.begin(), Mask.end(), 0); | |
| } else { | |
| for (auto [I, V] : enumerate(E->Scalars)) { | |
| if (isa<PoisonValue>(V)) { | |
| Mask[I] = PoisonMaskElem; | |
| continue; | |
| } | |
| Mask[I] = FrontTE->findLaneForValue(V); | |
| } | |
| } | |
| // Reset the builder(s) to correctly handle perfect diamond matched | |
| // nodes. | |
| ShuffleBuilder.resetForSameNode(); | |
| // Full matched entry found, no need to insert subvectors. | |
| if ((IsFullVectorMatch(FrontTE) && | |
| FrontTE->ReuseShuffleIndices.empty() && | |
| FrontTE->ReorderIndices.empty() && | |
| E->getVectorFactor() == FrontTE->getVectorFactor()) || | |
| (equal(E->Scalars, FrontTE->Scalars) && | |
| equal(E->ReorderIndices, FrontTE->ReorderIndices) && | |
| equal(E->ReuseShuffleIndices, FrontTE->ReuseShuffleIndices))) { | |
| Mask.resize(FrontTE->getVectorFactor()); | |
| std::iota(Mask.begin(), Mask.end(), 0); | |
| ShuffleBuilder.add(*FrontTE, Mask); | |
| Res = ShuffleBuilder.finalize({}, {}, {}); | |
| } else { | |
| ShuffleBuilder.add(*FrontTE, Mask); | |
| Res = ShuffleBuilder.finalize(E->getCommonMask(), {}, {}); | |
| } | |
| return Res; | |
| } | |
| if (!Resized) { | |
| if (GatheredScalars.size() != VF && | |
| any_of(Entries, [&](ArrayRef<const TreeEntry *> TEs) { | |
| return any_of(TEs, [&](const TreeEntry *TE) { | |
| return TE->getVectorFactor() == VF; | |
| }); | |
| })) | |
| GatheredScalars.append(VF - GatheredScalars.size(), | |
| PoisonValue::get(OrigScalarTy)); | |
| } | |
| // Remove shuffled elements from list of gathers. | |
| for (int I = 0, Sz = Mask.size(); I < Sz; ++I) { | |
| if (Mask[I] != PoisonMaskElem) | |
| GatheredScalars[I] = PoisonValue::get(OrigScalarTy); | |
| } | |
| } | |
| } | |
| auto TryPackScalars = [&](SmallVectorImpl<Value *> &Scalars, | |
| SmallVectorImpl<int> &ReuseMask, | |
| bool IsRootPoison) { | |
| // For splats with can emit broadcasts instead of gathers, so try to find | |
| // such sequences. | |
| bool IsSplat = IsRootPoison && isSplat(Scalars) && | |
| (Scalars.size() > 2 || Scalars.front() == Scalars.back()); | |
| Scalars.append(VF - Scalars.size(), PoisonValue::get(OrigScalarTy)); | |
| SmallVector<int> UndefPos; | |
| DenseMap<Value *, unsigned> UniquePositions; | |
| // Gather unique non-const values and all constant values. | |
| // For repeated values, just shuffle them. | |
| int NumNonConsts = 0; | |
| int SinglePos = 0; | |
| for (auto [I, V] : enumerate(Scalars)) { | |
| if (isa<UndefValue>(V)) { | |
| if (!isa<PoisonValue>(V)) { | |
| ReuseMask[I] = I; | |
| UndefPos.push_back(I); | |
| } | |
| continue; | |
| } | |
| if (isConstant(V)) { | |
| ReuseMask[I] = I; | |
| continue; | |
| } | |
| ++NumNonConsts; | |
| SinglePos = I; | |
| Value *OrigV = V; | |
| Scalars[I] = PoisonValue::get(OrigScalarTy); | |
| if (IsSplat) { | |
| Scalars.front() = OrigV; | |
| ReuseMask[I] = 0; | |
| } else { | |
| const auto Res = UniquePositions.try_emplace(OrigV, I); | |
| Scalars[Res.first->second] = OrigV; | |
| ReuseMask[I] = Res.first->second; | |
| } | |
| } | |
| if (NumNonConsts == 1) { | |
| // Restore single insert element. | |
| if (IsSplat) { | |
| ReuseMask.assign(VF, PoisonMaskElem); | |
| std::swap(Scalars.front(), Scalars[SinglePos]); | |
| if (!UndefPos.empty() && UndefPos.front() == 0) | |
| Scalars.front() = UndefValue::get(OrigScalarTy); | |
| } | |
| ReuseMask[SinglePos] = SinglePos; | |
| } else if (!UndefPos.empty() && IsSplat) { | |
| // For undef values, try to replace them with the simple broadcast. | |
| // We can do it if the broadcasted value is guaranteed to be | |
| // non-poisonous, or by freezing the incoming scalar value first. | |
| auto *It = find_if(Scalars, [this, E](Value *V) { | |
| return !isa<UndefValue>(V) && | |
| (isVectorized(V) || isGuaranteedNotToBePoison(V, AC) || | |
| (E->UserTreeIndex && any_of(V->uses(), [E](const Use &U) { | |
| // Check if the value already used in the same operation in | |
| // one of the nodes already. | |
| return E->UserTreeIndex.EdgeIdx != U.getOperandNo() && | |
| is_contained(E->UserTreeIndex.UserTE->Scalars, | |
| U.getUser()); | |
| }))); | |
| }); | |
| if (It != Scalars.end()) { | |
| // Replace undefs by the non-poisoned scalars and emit broadcast. | |
| int Pos = std::distance(Scalars.begin(), It); | |
| for (int I : UndefPos) { | |
| // Set the undef position to the non-poisoned scalar. | |
| ReuseMask[I] = Pos; | |
| // Replace the undef by the poison, in the mask it is replaced by | |
| // non-poisoned scalar already. | |
| if (I != Pos) | |
| Scalars[I] = PoisonValue::get(OrigScalarTy); | |
| } | |
| } else { | |
| // Replace undefs by the poisons, emit broadcast and then emit | |
| // freeze. | |
| for (int I : UndefPos) { | |
| ReuseMask[I] = PoisonMaskElem; | |
| if (isa<UndefValue>(Scalars[I])) | |
| Scalars[I] = PoisonValue::get(OrigScalarTy); | |
| } | |
| NeedFreeze = true; | |
| } | |
| } | |
| }; | |
| if (!ExtractShuffles.empty() || !GatherShuffles.empty()) { | |
| bool IsNonPoisoned = true; | |
| bool IsUsedInExpr = true; | |
| Value *Vec1 = nullptr; | |
| if (!ExtractShuffles.empty()) { | |
| // Gather of extractelements can be represented as just a shuffle of | |
| // a single/two vectors the scalars are extracted from. | |
| // Find input vectors. | |
| Value *Vec2 = nullptr; | |
| for (unsigned I = 0, Sz = ExtractMask.size(); I < Sz; ++I) { | |
| if (!Mask.empty() && Mask[I] != PoisonMaskElem) | |
| ExtractMask[I] = PoisonMaskElem; | |
| } | |
| if (UseVecBaseAsInput) { | |
| Vec1 = ExtractVecBase; | |
| } else { | |
| for (unsigned I = 0, Sz = ExtractMask.size(); I < Sz; ++I) { | |
| if (ExtractMask[I] == PoisonMaskElem) | |
| continue; | |
| if (isa<UndefValue>(StoredGS[I])) | |
| continue; | |
| auto *EI = cast<ExtractElementInst>(StoredGS[I]); | |
| Value *VecOp = EI->getVectorOperand(); | |
| if (ArrayRef<TreeEntry *> TEs = getTreeEntries(VecOp); | |
| !TEs.empty() && TEs.front()->VectorizedValue) | |
| VecOp = TEs.front()->VectorizedValue; | |
| if (!Vec1) { | |
| Vec1 = VecOp; | |
| } else if (Vec1 != VecOp) { | |
| assert((!Vec2 || Vec2 == VecOp) && | |
| "Expected only 1 or 2 vectors shuffle."); | |
| Vec2 = VecOp; | |
| } | |
| } | |
| } | |
| if (Vec2) { | |
| IsUsedInExpr = false; | |
| IsNonPoisoned &= isGuaranteedNotToBePoison(Vec1, AC) && | |
| isGuaranteedNotToBePoison(Vec2, AC); | |
| ShuffleBuilder.add(Vec1, Vec2, ExtractMask); | |
| } else if (Vec1) { | |
| bool IsNotPoisonedVec = isGuaranteedNotToBePoison(Vec1, AC); | |
| IsUsedInExpr &= FindReusedSplat( | |
| ExtractMask, | |
| cast<FixedVectorType>(Vec1->getType())->getNumElements(), 0, | |
| ExtractMask.size(), IsNotPoisonedVec); | |
| ShuffleBuilder.add(Vec1, ExtractMask, /*ForExtracts=*/true); | |
| IsNonPoisoned &= IsNotPoisonedVec; | |
| } else { | |
| IsUsedInExpr = false; | |
| ShuffleBuilder.add(PoisonValue::get(VecTy), ExtractMask, | |
| /*ForExtracts=*/true); | |
| } | |
| } | |
| if (!GatherShuffles.empty()) { | |
| unsigned SliceSize = getPartNumElems(VF, NumParts); | |
| if (Mask.size() == E->Scalars.size()) | |
| SliceSize = getPartNumElems( | |
| E->Scalars.size(), | |
| getNumberOfParts(VecTy, ScalarTy, E->Scalars.size())); | |
| SmallVector<int> VecMask(Mask.size(), PoisonMaskElem); | |
| for (const auto [I, TEs] : enumerate(Entries)) { | |
| if (TEs.empty()) { | |
| assert(!GatherShuffles[I] && | |
| "No shuffles with empty entries list expected."); | |
| continue; | |
| } | |
| assert((TEs.size() == 1 || TEs.size() == 2) && | |
| "Expected shuffle of 1 or 2 entries."); | |
| unsigned Limit = getNumElems(Mask.size(), SliceSize, I); | |
| auto SubMask = ArrayRef(Mask).slice(I * SliceSize, Limit); | |
| VecMask.assign(VecMask.size(), PoisonMaskElem); | |
| copy(SubMask, std::next(VecMask.begin(), I * SliceSize)); | |
| if (TEs.size() == 1) { | |
| bool IsNotPoisonedVec = | |
| TEs.front()->VectorizedValue | |
| ? isGuaranteedNotToBePoison(TEs.front()->VectorizedValue, AC) | |
| : true; | |
| IsUsedInExpr &= | |
| FindReusedSplat(VecMask, TEs.front()->getVectorFactor(), I, | |
| SliceSize, IsNotPoisonedVec); | |
| ShuffleBuilder.add(*TEs.front(), VecMask); | |
| IsNonPoisoned &= IsNotPoisonedVec; | |
| } else { | |
| IsUsedInExpr = false; | |
| ShuffleBuilder.add(*TEs.front(), *TEs.back(), VecMask); | |
| if (TEs.front()->VectorizedValue && TEs.back()->VectorizedValue) | |
| IsNonPoisoned &= | |
| isGuaranteedNotToBePoison(TEs.front()->VectorizedValue, AC) && | |
| isGuaranteedNotToBePoison(TEs.back()->VectorizedValue, AC); | |
| } | |
| } | |
| } | |
| // Try to figure out best way to combine values: build a shuffle and insert | |
| // elements or just build several shuffles. | |
| // Insert non-constant scalars. | |
| SmallVector<Value *> NonConstants(GatheredScalars); | |
| int EMSz = ExtractMask.size(); | |
| int MSz = Mask.size(); | |
| // Try to build constant vector and shuffle with it only if currently we | |
| // have a single permutation and more than 1 scalar constants. | |
| bool IsSingleShuffle = ExtractShuffles.empty() || GatherShuffles.empty(); | |
| bool IsIdentityShuffle = | |
| ((UseVecBaseAsInput || | |
| all_of(ExtractShuffles, | |
| [](const std::optional<TTI::ShuffleKind> &SK) { | |
| return SK.value_or(TTI::SK_PermuteTwoSrc) == | |
| TTI::SK_PermuteSingleSrc; | |
| })) && | |
| none_of(ExtractMask, [&](int I) { return I >= EMSz; }) && | |
| ShuffleVectorInst::isIdentityMask(ExtractMask, EMSz)) || | |
| (!GatherShuffles.empty() && | |
| all_of(GatherShuffles, | |
| [](const std::optional<TTI::ShuffleKind> &SK) { | |
| return SK.value_or(TTI::SK_PermuteTwoSrc) == | |
| TTI::SK_PermuteSingleSrc; | |
| }) && | |
| none_of(Mask, [&](int I) { return I >= MSz; }) && | |
| ShuffleVectorInst::isIdentityMask(Mask, MSz)); | |
| bool EnoughConstsForShuffle = | |
| IsSingleShuffle && | |
| (none_of(GatheredScalars, | |
| [](Value *V) { | |
| return isa<UndefValue>(V) && !isa<PoisonValue>(V); | |
| }) || | |
| any_of(GatheredScalars, | |
| [](Value *V) { | |
| return isa<Constant>(V) && !isa<UndefValue>(V); | |
| })) && | |
| (!IsIdentityShuffle || | |
| (GatheredScalars.size() == 2 && | |
| any_of(GatheredScalars, | |
| [](Value *V) { return !isa<UndefValue>(V); })) || | |
| count_if(GatheredScalars, [](Value *V) { | |
| return isa<Constant>(V) && !isa<PoisonValue>(V); | |
| }) > 1); | |
| // NonConstants array contains just non-constant values, GatheredScalars | |
| // contains only constant to build final vector and then shuffle. | |
| for (int I = 0, Sz = GatheredScalars.size(); I < Sz; ++I) { | |
| if (EnoughConstsForShuffle && isa<Constant>(GatheredScalars[I])) | |
| NonConstants[I] = PoisonValue::get(OrigScalarTy); | |
| else | |
| GatheredScalars[I] = PoisonValue::get(OrigScalarTy); | |
| } | |
| // Generate constants for final shuffle and build a mask for them. | |
| if (!all_of(GatheredScalars, IsaPred<PoisonValue>)) { | |
| SmallVector<int> BVMask(GatheredScalars.size(), PoisonMaskElem); | |
| TryPackScalars(GatheredScalars, BVMask, /*IsRootPoison=*/true); | |
| Value *BV = ShuffleBuilder.gather(GatheredScalars, BVMask.size()); | |
| ShuffleBuilder.add(BV, BVMask); | |
| } | |
| if (all_of(NonConstants, [=](Value *V) { | |
| return isa<PoisonValue>(V) || | |
| (IsSingleShuffle && ((IsIdentityShuffle && | |
| IsNonPoisoned) || IsUsedInExpr) && isa<UndefValue>(V)); | |
| })) | |
| Res = ShuffleBuilder.finalize(E->ReuseShuffleIndices, SubVectors, | |
| SubVectorsMask); | |
| else | |
| Res = ShuffleBuilder.finalize( | |
| E->ReuseShuffleIndices, SubVectors, SubVectorsMask, E->Scalars.size(), | |
| [&](Value *&Vec, SmallVectorImpl<int> &Mask, auto CreateShuffle) { | |
| bool IsSplat = isSplat(NonConstants); | |
| SmallVector<int> BVMask(Mask.size(), PoisonMaskElem); | |
| TryPackScalars(NonConstants, BVMask, /*IsRootPoison=*/false); | |
| auto CheckIfSplatIsProfitable = [&]() { | |
| // Estimate the cost of splatting + shuffle and compare with | |
| // insert + shuffle. | |
| Value *V = *find_if_not(NonConstants, IsaPred<UndefValue>); | |
| if (isa<ExtractElementInst>(V) || isVectorized(V)) | |
| return false; | |
| InstructionCost SplatCost = TTI->getVectorInstrCost( | |
| Instruction::InsertElement, VecTy, CostKind, /*Index=*/0, | |
| PoisonValue::get(VecTy), V); | |
| SmallVector<int> NewMask(Mask.begin(), Mask.end()); | |
| for (auto [Idx, I] : enumerate(BVMask)) | |
| if (I != PoisonMaskElem) | |
| NewMask[Idx] = Mask.size(); | |
| SplatCost += | |
| getShuffleCost(*TTI, TTI::SK_PermuteTwoSrc, | |
| cast<VectorType>(VecTy), CostKind, NewMask); | |
| InstructionCost BVCost = TTI->getVectorInstrCost( | |
| Instruction::InsertElement, VecTy, CostKind, | |
| *find_if(Mask, not_equal_to(PoisonMaskElem)), Vec, V); | |
| // Shuffle required? | |
| if (count(BVMask, PoisonMaskElem) < | |
| static_cast<int>(BVMask.size() - 1)) { | |
| SmallVector<int> NewMask(Mask.begin(), Mask.end()); | |
| for (auto [Idx, I] : enumerate(BVMask)) | |
| if (I != PoisonMaskElem) | |
| NewMask[Idx] = I; | |
| BVCost += | |
| getShuffleCost(*TTI, TTI::SK_PermuteSingleSrc, | |
| cast<VectorType>(VecTy), CostKind, NewMask); | |
| } | |
| return SplatCost <= BVCost; | |
| }; | |
| if (!IsSplat || Mask.size() <= 2 || !CheckIfSplatIsProfitable()) { | |
| for (auto [Idx, I] : enumerate(BVMask)) | |
| if (I != PoisonMaskElem) | |
| Mask[Idx] = I; | |
| Vec = ShuffleBuilder.gather(NonConstants, Mask.size(), Vec); | |
| } else { | |
| Value *V = *find_if_not(NonConstants, IsaPred<UndefValue>); | |
| SmallVector<Value *> Values(NonConstants.size(), | |
| PoisonValue::get(ScalarTy)); | |
| Values[0] = V; | |
| Value *BV = ShuffleBuilder.gather(Values, BVMask.size()); | |
| SmallVector<int> SplatMask(BVMask.size(), PoisonMaskElem); | |
| transform(BVMask, SplatMask.begin(), [](int I) { | |
| return I == PoisonMaskElem ? PoisonMaskElem : 0; | |
| }); | |
| if (!ShuffleVectorInst::isIdentityMask(SplatMask, VF)) | |
| BV = CreateShuffle(BV, nullptr, SplatMask); | |
| for (auto [Idx, I] : enumerate(BVMask)) | |
| if (I != PoisonMaskElem) | |
| Mask[Idx] = BVMask.size() + Idx; | |
| Vec = CreateShuffle(Vec, BV, Mask); | |
| for (auto [Idx, I] : enumerate(Mask)) | |
| if (I != PoisonMaskElem) | |
| Mask[Idx] = Idx; | |
| } | |
| }); | |
| } else if (!allConstant(GatheredScalars)) { | |
| // Gather unique scalars and all constants. | |
| SmallVector<int> ReuseMask(GatheredScalars.size(), PoisonMaskElem); | |
| TryPackScalars(GatheredScalars, ReuseMask, /*IsRootPoison=*/true); | |
| TTI::VectorInstrContext ContextHint = TTI::VectorInstrContext::None; | |
| if constexpr (std::is_same_v<ResTy, InstructionCost>) { | |
| auto GatherUserOps = | |
| [&](SmallVectorImpl<TTI::BuildVectorUseOp> &UserOps) { | |
| UserOps.clear(); | |
| return !NeedFreeze && collectGatherUserOps(*E, UserOps); | |
| }; | |
| ContextHint = | |
| TTI->getBuildVectorContextHint(ReuseMask, E->Scalars, GatherUserOps); | |
| } | |
| Value *BV = ShuffleBuilder.gather(GatheredScalars, ReuseMask.size(), | |
| /*Root*/ nullptr, ContextHint); | |
| ShuffleBuilder.add(BV, ReuseMask, /*ForExtract*/ false, ContextHint); | |
| Res = ShuffleBuilder.finalize(E->ReuseShuffleIndices, SubVectors, | |
| SubVectorsMask, /*VF*/ 0, /*Action*/ {}); | |
| } else { | |
| // Gather all constants. | |
| SmallVector<int> Mask(GatheredScalars.size(), PoisonMaskElem); | |
| for (auto [I, V] : enumerate(GatheredScalars)) { | |
| if (!isa<PoisonValue>(V)) | |
| Mask[I] = I; | |
| } | |
| Value *BV = ShuffleBuilder.gather(GatheredScalars); | |
| ShuffleBuilder.add(BV, Mask); | |
| Res = ShuffleBuilder.finalize(E->ReuseShuffleIndices, SubVectors, | |
| SubVectorsMask); | |
| } | |
| if (NeedFreeze) | |
| Res = ShuffleBuilder.createFreeze(Res); | |
| return Res; | |
| } | |
| Value *BoUpSLP::createBuildVector(const TreeEntry *E, Type *ScalarTy) { | |
| // Do not do this for split vectorize node, marked to be gathers/buildvectors. | |
| if (E->State != TreeEntry::SplitVectorize || | |
| !TransformedToGatherNodes.contains(E)) { | |
| for (auto [EIdx, _] : E->CombinedEntriesWithIndices) | |
| (void)vectorizeTree(VectorizableTree[EIdx].get()); | |
| } | |
| return processBuildVector<ShuffleInstructionBuilder, Value *>(E, ScalarTy, | |
| Builder, *this); | |
| } | |
| /// \returns \p I after propagating metadata from \p VL only for instructions in | |
| /// \p VL. | |
| static Instruction *propagateMetadata(Instruction *Inst, ArrayRef<Value *> VL) { | |
| SmallVector<Value *> Insts(make_isa_range<Instruction>(VL)); | |
| return llvm::propagateMetadata(Inst, Insts); | |
| } | |
| Value *BoUpSLP::vectorizeTree(TreeEntry *E) { | |
| IRBuilderBase::InsertPointGuard Guard(Builder); | |
| Value *V = E->Scalars.front(); | |
| Type *ScalarTy = getValueType(V, Opts.slp_revec); | |
| auto It = MinBWs.find(E); | |
| if (It != MinBWs.end()) { | |
| auto *VecTy = dyn_cast<FixedVectorType>(ScalarTy); | |
| ScalarTy = IntegerType::get(F->getContext(), It->second.first); | |
| if (VecTy) | |
| ScalarTy = getWidenedType(ScalarTy, VecTy->getNumElements()); | |
| } | |
| if (E->VectorizedValue) | |
| return E->VectorizedValue; | |
| auto *VecTy = getWidenedType(ScalarTy, E->Scalars.size()); | |
| if (E->isGather() || TransformedToGatherNodes.contains(E)) { | |
| // Set insert point for non-reduction initial nodes. | |
| if (E->hasState() && E->Idx == 0 && !UserIgnoreList) | |
| setInsertPointAfterBundle(E); | |
| Value *Vec = createBuildVector(E, ScalarTy); | |
| E->VectorizedValue = Vec; | |
| return Vec; | |
| } | |
| if (E->State == TreeEntry::SplitVectorize) { | |
| assert(E->CombinedEntriesWithIndices.size() == 2 && | |
| "Expected exactly 2 combined entries."); | |
| setInsertPointAfterBundle(E); | |
| TreeEntry &OpTE1 = | |
| *VectorizableTree[E->CombinedEntriesWithIndices.front().first]; | |
| assert(OpTE1.isSame( | |
| ArrayRef(E->Scalars).take_front(OpTE1.getVectorFactor())) && | |
| "Expected same first part of scalars."); | |
| Value *Op1 = vectorizeTree(&OpTE1); | |
| TreeEntry &OpTE2 = | |
| *VectorizableTree[E->CombinedEntriesWithIndices.back().first]; | |
| assert( | |
| OpTE2.isSame(ArrayRef(E->Scalars).take_back(OpTE2.getVectorFactor())) && | |
| "Expected same second part of scalars."); | |
| Value *Op2 = vectorizeTree(&OpTE2); | |
| auto GetOperandSignedness = [&](const TreeEntry *OpE) { | |
| bool IsSigned = false; | |
| auto It = MinBWs.find(OpE); | |
| if (It != MinBWs.end()) | |
| IsSigned = It->second.second; | |
| else | |
| IsSigned = any_of(OpE->Scalars, [&](Value *R) { | |
| if (isa<PoisonValue>(V)) | |
| return false; | |
| return !isKnownNonNegative(R, SimplifyQuery(*DL)); | |
| }); | |
| return IsSigned; | |
| }; | |
| if (cast<VectorType>(Op1->getType())->getElementType() != | |
| ScalarTy->getScalarType()) { | |
| assert(ScalarTy->isIntegerTy() && "Expected item in MinBWs."); | |
| Op1 = Builder.CreateIntCast( | |
| Op1, | |
| getWidenedType( | |
| ScalarTy, | |
| cast<FixedVectorType>(Op1->getType())->getNumElements()), | |
| GetOperandSignedness(&OpTE1)); | |
| } | |
| if (cast<VectorType>(Op2->getType())->getElementType() != | |
| ScalarTy->getScalarType()) { | |
| assert(ScalarTy->isIntegerTy() && "Expected item in MinBWs."); | |
| Op2 = Builder.CreateIntCast( | |
| Op2, | |
| getWidenedType( | |
| ScalarTy, | |
| cast<FixedVectorType>(Op2->getType())->getNumElements()), | |
| GetOperandSignedness(&OpTE2)); | |
| } | |
| if (E->ReorderIndices.empty()) { | |
| SmallVector<int> Mask(E->getVectorFactor(), PoisonMaskElem); | |
| std::iota( | |
| Mask.begin(), | |
| std::next(Mask.begin(), E->CombinedEntriesWithIndices.back().second), | |
| 0); | |
| unsigned ScalarTyNumElements = getNumElements(ScalarTy); | |
| if (ScalarTyNumElements != 1) { | |
| assert(Opts.slp_revec && "Only supported by REVEC."); | |
| transformScalarShuffleIndiciesToVector(ScalarTyNumElements, Mask); | |
| } | |
| Value *Vec = Builder.CreateShuffleVector(Op1, Mask); | |
| Vec = createInsertVector(Builder, Vec, Op2, | |
| E->CombinedEntriesWithIndices.back().second * | |
| ScalarTyNumElements); | |
| E->VectorizedValue = Vec; | |
| return Vec; | |
| } | |
| unsigned CommonVF = | |
| std::max(OpTE1.getVectorFactor(), OpTE2.getVectorFactor()); | |
| const unsigned Scale = getNumElements(ScalarTy); | |
| CommonVF *= Scale; | |
| if (getNumElements(Op1->getType()) != CommonVF) { | |
| SmallVector<int> Mask(CommonVF, PoisonMaskElem); | |
| copy(createReplicatedMask(Scale, OpTE1.getVectorFactor() * Scale), | |
| Mask.begin()); | |
| Op1 = Builder.CreateShuffleVector(Op1, Mask); | |
| } | |
| if (getNumElements(Op2->getType()) != CommonVF) { | |
| SmallVector<int> Mask(CommonVF, PoisonMaskElem); | |
| copy(createReplicatedMask(Scale, OpTE2.getVectorFactor() * Scale), | |
| Mask.begin()); | |
| Op2 = Builder.CreateShuffleVector(Op2, Mask); | |
| } | |
| Value *Vec = Builder.CreateShuffleVector(Op1, Op2, E->getSplitMask()); | |
| E->VectorizedValue = Vec; | |
| return Vec; | |
| } | |
| bool IsReverseOrder = E->isReverse() && !isWidenedStridedLoad(E); | |
| auto FinalShuffle = [&](Value *V, const TreeEntry *E) { | |
| if (isa<StructType>(ScalarTy)) { | |
| // TODO: Reordering of struct types is not supported. | |
| assert(E->ReorderIndices.empty() && | |
| "Expected no reordering for struct types."); | |
| assert(E->ReuseShuffleIndices.empty() && | |
| "Expected no reuse shuffle indices for struct types."); | |
| return V; | |
| } | |
| ShuffleInstructionBuilder ShuffleBuilder(ScalarTy, Builder, *this); | |
| if ((E->State == TreeEntry::StridedVectorize && IsReverseOrder) || | |
| E->State == TreeEntry::CompressVectorize) { | |
| ShuffleBuilder.addOrdered(V, {}); | |
| } else if (E->getOpcode() == Instruction::Store && | |
| (E->State == TreeEntry::Vectorize || | |
| E->State == TreeEntry::StridedVectorize || | |
| E->State == TreeEntry::ExpandVectorize)) { | |
| ArrayRef<int> Mask = | |
| ArrayRef(reinterpret_cast<const int *>(E->ReorderIndices.begin()), | |
| E->ReorderIndices.size()); | |
| ShuffleBuilder.add(V, Mask); | |
| } else { | |
| ShuffleBuilder.addOrdered(V, E->ReorderIndices); | |
| } | |
| SmallVector<std::pair<const TreeEntry *, unsigned>> SubVectors( | |
| E->CombinedEntriesWithIndices.size()); | |
| transform( | |
| E->CombinedEntriesWithIndices, SubVectors.begin(), [&](const auto &P) { | |
| return std::make_pair(VectorizableTree[P.first].get(), P.second); | |
| }); | |
| assert( | |
| (E->CombinedEntriesWithIndices.empty() || E->ReorderIndices.empty()) && | |
| "Expected either combined subnodes or reordering"); | |
| return ShuffleBuilder.finalize(E->ReuseShuffleIndices, SubVectors, {}); | |
| }; | |
| assert(!E->isGather() && "Unhandled state"); | |
| unsigned ShuffleOrOp = | |
| E->isAltShuffle() ? (unsigned)Instruction::ShuffleVector : E->getOpcode(); | |
| if (!E->isAltShuffle()) { | |
| switch (E->CombinedOp) { | |
| case TreeEntry::ReducedBitcast: | |
| case TreeEntry::ReducedBitcastBSwap: | |
| case TreeEntry::ReducedBitcastLoads: | |
| case TreeEntry::ReducedBitcastBSwapLoads: | |
| case TreeEntry::ReducedBitPack: | |
| case TreeEntry::ReducedCmpBitcast: | |
| ShuffleOrOp = E->CombinedOp; | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| Instruction *VL0 = E->getMainOp(); | |
| auto GetOperandSignedness = [&](unsigned Idx) { | |
| const TreeEntry *OpE = getOperandEntry(E, Idx); | |
| bool IsSigned = false; | |
| auto It = MinBWs.find(OpE); | |
| if (It != MinBWs.end()) | |
| IsSigned = It->second.second; | |
| else | |
| IsSigned = any_of(OpE->Scalars, [&](Value *R) { | |
| if (isa<PoisonValue>(V)) | |
| return false; | |
| return !isKnownNonNegative(R, SimplifyQuery(*DL)); | |
| }); | |
| return IsSigned; | |
| }; | |
| auto PropagateIRFlags = [&](Value *V, unsigned Opcode = 0, | |
| ArrayRef<Value *> VL = {}) -> Value * { | |
| ArrayRef<Value *> Scalars = VL.empty() ? E->Scalars : VL; | |
| SmallSetVector<Value *, 4> UniqueInsts; | |
| for (Instruction *I : make_isa_range<Instruction>(Scalars)) { | |
| if (E->hasCopyableElements() && E->isCopyableElement(I)) | |
| continue; | |
| UniqueInsts.insert(I); | |
| } | |
| if (!Opcode) | |
| Opcode = E->getOpcode(); | |
| propagateIRFlags(V, UniqueInsts.getArrayRef(), nullptr, It == MinBWs.end()); | |
| auto *I = dyn_cast<Instruction>(V); | |
| if (!I) | |
| return V; | |
| // Drop nneg propagated from the original cast. | |
| Value *Op; | |
| if (auto *CI = dyn_cast<CastInst>(VL0); | |
| CI && match(I, m_NNegZExt(m_Value(Op))) && | |
| Op->getType()->getScalarType() != CI->getSrcTy()) | |
| cast<ZExtInst>(I)->setNonNeg(false); | |
| // A lane may be emitted in negated form (sub C, x as add x, -C, or an | |
| // add/sub lane with swapped operands) when all its uses are | |
| // sign-insensitive (icmp eq/ne 0 or abs). The value stays correct for | |
| // such uses, but nsw/nuw of the original instruction do not cover the | |
| // negated overflow conditions. | |
| if (E->State == TreeEntry::Vectorize && E->getNumOperands() == 2 && | |
| I->getType()->isIntOrIntVectorTy() && | |
| (Opcode == Instruction::Add || Opcode == Instruction::Sub)) { | |
| auto IsNegatedLane = [&](Instruction *LaneI, unsigned Lane) { | |
| unsigned LaneOpcode = LaneI->getOpcode(); | |
| if (LaneOpcode != Instruction::Add && LaneOpcode != Instruction::Sub) | |
| return false; | |
| Value *L = E->getOperand(0)[Lane]; | |
| Value *R = E->getOperand(1)[Lane]; | |
| if (LaneOpcode == Opcode) | |
| // Only a sub lane emitted with swapped operands is negated. | |
| return Opcode == Instruction::Sub && L == LaneI->getOperand(1) && | |
| R == LaneI->getOperand(0) && L != R; | |
| if (Opcode == Instruction::Add) | |
| // sub C, x emitted as x + -C. | |
| return isa<ConstantInt>(LaneI->getOperand(0)) && | |
| !isa<ConstantInt>(LaneI->getOperand(1)); | |
| // add x, C emitted as -C - x. | |
| return isa<ConstantInt>(L) && !isa<ConstantInt>(R); | |
| }; | |
| if (any_of(enumerate(E->Scalars), [&](const auto &P) { | |
| auto *LaneI = dyn_cast<Instruction>(P.value()); | |
| return LaneI && | |
| (!E->hasCopyableElements() || | |
| !E->isCopyableElement(LaneI)) && | |
| (VL.empty() || is_contained(VL, LaneI)) && | |
| IsNegatedLane(LaneI, P.index()); | |
| })) | |
| I->dropPoisonGeneratingFlags(); | |
| } | |
| I = ::propagateMetadata(I, UniqueInsts.getArrayRef()); | |
| // A copyable lane is emitted as a synthesized operation - an absorbed | |
| // fmul becomes fmuladd(a, b, -0.0), other scalars are combined with a | |
| // binop identity value - so every fast-math flag must be justified by | |
| // each copyable scalar: an FP operator justifies only its own flags, a | |
| // constant FP the flags its value cannot violate, and any other value | |
| // only the transform permissions, which cannot change an identity | |
| // lane. An unjustified flag poisons NaN/Inf inputs, flushes a -0.0 | |
| // result, or changes the rounding of an absorbed lane. | |
| if (E->hasCopyableElements() && isa<FPMathOperator>(I)) { | |
| FastMathFlags CopyableFMF; | |
| CopyableFMF.set(); | |
| auto IsCopyable = [&](Value *V) { return E->isCopyableElement(V); }; | |
| for (Value *Scalar : make_filter_range(Scalars, IsCopyable)) { | |
| if (auto *FPMO = dyn_cast<FPMathOperator>(Scalar)) { | |
| CopyableFMF &= FPMO->getFastMathFlags(); | |
| continue; | |
| } | |
| auto *CFP = dyn_cast<ConstantFP>(Scalar); | |
| if (!CFP || CFP->isNaN()) | |
| CopyableFMF.setNoNaNs(false); | |
| if (!CFP || CFP->isInfinity()) | |
| CopyableFMF.setNoInfs(false); | |
| if (!CFP || CFP->isNegZero()) | |
| CopyableFMF.setNoSignedZeros(false); | |
| } | |
| I->copyFastMathFlags(I->getFastMathFlags() & CopyableFMF); | |
| } | |
| // Copyable lanes modeled as op(V, V) or with an undef or poison operand do | |
| // not preserve the disjoint flag: or disjoint(V, V) is poison unless V is | |
| // zero, and such an operand may be any value. Matched by value, so operand | |
| // reordering does not affect the detection. | |
| auto *PDI = dyn_cast<PossiblyDisjointInst>(I); | |
| if (E->hasCopyableElements() && PDI && | |
| any_of(enumerate(E->Scalars), [&](const auto &P) { | |
| Value *Scalar = P.value(); | |
| auto IsScalarOrUndef = [Scalar](Value *Op) { | |
| return Op == Scalar || isa<UndefValue>(Op); | |
| }; | |
| return E->isCopyableElement(Scalar) && !match(Scalar, m_Zero()) && | |
| IsScalarOrUndef(E->getOperand(0)[P.index()]) && | |
| IsScalarOrUndef(E->getOperand(1)[P.index()]); | |
| })) | |
| PDI->setIsDisjoint(false); | |
| // The undef copyable lanes are modeled as op(undef, undef), which must not | |
| // overflow into poison. | |
| if (E->hasCopyableElements() && isUndefTolerantBinOp(I) && | |
| any_of(E->Scalars, [&](Value *Scalar) { | |
| return isa<UndefValue>(Scalar) && !isa<PoisonValue>(Scalar) && | |
| E->isCopyableElement(Scalar); | |
| })) | |
| I->dropPoisonGeneratingFlags(); | |
| // Drop nuw flags for abs(sub(commutative), true). | |
| if (!MinBWs.contains(E) && Opcode == Instruction::Sub && | |
| (E->hasCopyableElements() || any_of(Scalars, [](Value *Scalar) { | |
| auto *SI = dyn_cast<Instruction>(Scalar); | |
| return !SI || isCommutative(SI); | |
| }))) | |
| I->setHasNoUnsignedWrap(/*b=*/false); | |
| // A sub feeding icmp eq/ne 0 may have its operands swapped; nsw does not | |
| // survive a - b -> b - a (a - b can be INT_MIN while b - a overflows). | |
| if (!MinBWs.contains(E) && Opcode == Instruction::Sub && | |
| any_of(make_isa_range<Instruction>(Scalars), [](Instruction *SI) { | |
| if (SI->getOpcode() != Instruction::Sub || !isCommutative(SI)) | |
| return false; | |
| return any_of(SI->uses(), [](const Use &U) { | |
| CmpPredicate Pred; | |
| return match(U.getUser(), | |
| m_ICmp(Pred, m_Specific(U.get()), m_Zero())) && | |
| ICmpInst::isEquality(Pred); | |
| }); | |
| })) | |
| I->setHasNoSignedWrap(/*b=*/false); | |
| // Interchanging add/sub negates the constant: nsw only survives if the | |
| // constant isn't INT_MIN (negating it would overflow); nuw never | |
| // survives a nonzero constant, since that flips the valid range from | |
| // X >= C to X < C. | |
| if (!MinBWs.contains(E) && | |
| (Opcode == Instruction::Add || Opcode == Instruction::Sub)) { | |
| for (Value *V : UniqueInsts) { | |
| auto *SI = cast<Instruction>(V); | |
| if (SI->getOpcode() == Opcode || | |
| !is_contained({Instruction::Add, Instruction::Sub}, | |
| SI->getOpcode())) | |
| continue; | |
| for (const ConstantInt *CI : | |
| make_isa_range<ConstantInt>(SI->operands())) { | |
| if (CI->isZero()) | |
| continue; | |
| I->setHasNoUnsignedWrap(/*b=*/false); | |
| if (CI->getValue().isMinSignedValue()) | |
| I->setHasNoSignedWrap(/*b=*/false); | |
| } | |
| } | |
| } | |
| // mul nsw X, INT_MIN is not equivalent to shl nsw X, BW-1, because shl nsw | |
| // poisons when the sign bit is shifted out of a positive value. When a mul | |
| // lane is converted to shl with shift amount BW-1, nsw must be dropped. | |
| if (!MinBWs.contains(E) && Opcode == Instruction::Shl && | |
| any_of(UniqueInsts, [&](Value *V) { | |
| auto *SI = cast<Instruction>(V); | |
| if (SI->getOpcode() != Instruction::Mul) | |
| return false; | |
| return any_of(make_isa_range<ConstantInt>(SI->operands()), | |
| [](const ConstantInt *CI) { | |
| return CI->getValue().isMinSignedValue(); | |
| }); | |
| })) | |
| I->setHasNoSignedWrap(/*b=*/false); | |
| // shl nsw X, BW-1 is not equivalent to mul nsw X, INT_MIN: shl nsw never | |
| // poisons at X = -1 (shifted-out bits match the sign bit), but mul nsw | |
| // does. A shl lane converted to mul this way must drop nsw. | |
| if (!MinBWs.contains(E) && Opcode == Instruction::Mul && | |
| any_of(UniqueInsts, [&](Value *V) { | |
| auto *SI = cast<Instruction>(V); | |
| if (SI->getOpcode() != Instruction::Shl) | |
| return false; | |
| const auto *CI = dyn_cast<ConstantInt>(SI->getOperand(1)); | |
| return CI && CI->getValue() == CI->getBitWidth() - 1; | |
| })) | |
| I->setHasNoSignedWrap(/*b=*/false); | |
| // Keep the intersected samesign unless narrowing changed the sign of some | |
| // compared operand (it neither sign-extends from the narrow type nor has | |
| // a known-zero narrow sign bit), or a converted lane's adjusted constant | |
| // flips the sign. | |
| if (auto *ICmp = dyn_cast<ICmpInst>(I); ICmp && It == MinBWs.end()) { | |
| bool SignChange = | |
| ICmp->getOperand(0)->getType()->getScalarType() != | |
| VL0->getOperand(0)->getType()->getScalarType() && | |
| ((!GetOperandSignedness(0) && | |
| !isKnownNonNegative(ICmp->getOperand(0), SimplifyQuery(*DL))) || | |
| (!GetOperandSignedness(1) && | |
| !isKnownNonNegative(ICmp->getOperand(1), SimplifyQuery(*DL)))); | |
| CmpInst::Predicate P0 = cast<CmpInst>(E->getMainOp())->getPredicate(); | |
| bool SignFlip = | |
| !SignChange && | |
| any_of(make_isa_range<ICmpInst>(E->Scalars), [&](ICmpInst *LaneCI) { | |
| const APInt *OrigC, *AdjC; | |
| Constant *Adj = | |
| CmpSamePredicateHelper::getAdjustedConstant(LaneCI, P0); | |
| return Adj && match(LaneCI->getOperand(1), m_APInt(OrigC)) && | |
| match(Adj, m_APInt(AdjC)) && | |
| AdjC->isNegative() != OrigC->isNegative(); | |
| }); | |
| if (SignChange || SignFlip) | |
| ICmp->setSameSign(/*B=*/false); | |
| } | |
| return I; | |
| }; | |
| switch (ShuffleOrOp) { | |
| case Instruction::PHI: { | |
| assert((E->ReorderIndices.empty() || !E->ReuseShuffleIndices.empty() || | |
| E != &getRootNode() || E->UserTreeIndex) && | |
| "PHI reordering is free."); | |
| auto *PH = cast<PHINode>(VL0); | |
| Builder.SetInsertPoint(PH->getParent()->getFirstNonPHIIt()); | |
| Builder.SetCurrentDebugLocation(getDebugLocFromPHI(*PH)); | |
| PHINode *NewPhi = Builder.CreatePHI(VecTy, PH->getNumIncomingValues()); | |
| Value *V = NewPhi; | |
| // Adjust insertion point once all PHI's have been generated. | |
| Builder.SetInsertPoint(PH->getParent()->getFirstInsertionPt()); | |
| Builder.SetCurrentDebugLocation(getDebugLocFromPHI(*PH)); | |
| V = FinalShuffle(V, E); | |
| E->VectorizedValue = V; | |
| // If phi node is fully emitted - exit. | |
| if (NewPhi->getNumIncomingValues() != 0) | |
| return NewPhi; | |
| // PHINodes may have multiple entries from the same block. We want to | |
| // visit every block once. | |
| SmallDenseMap<BasicBlock *, unsigned, 4> VisitedBBs; | |
| for (unsigned I : seq<unsigned>(PH->getNumIncomingValues())) { | |
| BasicBlock *IBB = PH->getIncomingBlock(I); | |
| // Stop emission if all incoming values are generated. | |
| if (NewPhi->getNumIncomingValues() == PH->getNumIncomingValues()) { | |
| LLVM_DEBUG(dbgs() << "SLP: Diamond merged for " << *VL0 << ".\n"); | |
| return NewPhi; | |
| } | |
| Builder.SetInsertPoint(IBB->getTerminator()); | |
| Builder.SetCurrentDebugLocation(getDebugLocFromPHI(*PH)); | |
| auto Res = VisitedBBs.try_emplace(IBB, I); | |
| if (!Res.second) { | |
| TreeEntry *OpTE = getOperandEntry(E, I); | |
| if (OpTE->isGather() || DeletedNodes.contains(OpTE) || | |
| TransformedToGatherNodes.contains(OpTE)) { | |
| Value *VecOp = NewPhi->getIncomingValue(Res.first->getSecond()); | |
| NewPhi->addIncoming(VecOp, IBB); | |
| assert(!OpTE->VectorizedValue && "Expected no vectorized value."); | |
| OpTE->VectorizedValue = VecOp; | |
| // The combined nodes of the reused gather node still must be | |
| // emitted. | |
| if (OpTE->isGather() && !DeletedNodes.contains(OpTE)) | |
| for (auto [EIdx, _] : OpTE->CombinedEntriesWithIndices) | |
| (void)vectorizeTree(VectorizableTree[EIdx].get()); | |
| continue; | |
| } | |
| } | |
| Value *Vec = vectorizeOperand(E, I); | |
| if (VecTy != Vec->getType()) { | |
| assert((It != MinBWs.end() || getOperandEntry(E, I)->isGather() || | |
| MinBWs.contains(getOperandEntry(E, I))) && | |
| "Expected item in MinBWs."); | |
| Vec = Builder.CreateIntCast(Vec, VecTy, GetOperandSignedness(I)); | |
| } | |
| NewPhi->addIncoming(Vec, IBB); | |
| } | |
| assert(NewPhi->getNumIncomingValues() == PH->getNumIncomingValues() && | |
| "Invalid number of incoming values"); | |
| assert(E->VectorizedValue && "Expected vectorized value."); | |
| return E->VectorizedValue; | |
| } | |
| case Instruction::ExtractElement: { | |
| Value *V = E->getSingleOperand(0); | |
| setInsertPointAfterBundle(E); | |
| V = FinalShuffle(V, E); | |
| // Insert the copyable lanes (non-extract scalars modeled on the extract | |
| // main op) into the reused source vector. The identity extract order and | |
| // absence of a reuse shuffle are guaranteed when the node is created, so | |
| // a lane maps to its own index. | |
| if (E->hasCopyableElements()) { | |
| assert(E->ReorderIndices.empty() && E->ReuseShuffleIndices.empty() && | |
| "Copyable extract lanes require identity order and no reuse."); | |
| for (auto [Idx, Scalar] : enumerate(E->Scalars)) | |
| if (E->isCopyableElement(Scalar)) | |
| V = Builder.CreateInsertElement(V, Scalar, Builder.getInt32(Idx)); | |
| } | |
| E->VectorizedValue = V; | |
| return V; | |
| } | |
| case Instruction::ExtractValue: { | |
| if (!E->StructEVIndices.empty()) { | |
| setInsertPointAfterBundle(E); | |
| Value *V = vectorizeOperand(E, 0); | |
| V = Builder.CreateExtractValue(V, E->StructEVIndices); | |
| if (auto *I = dyn_cast<Instruction>(V)) | |
| V = ::propagateMetadata(I, E->Scalars); | |
| V = FinalShuffle(V, E); | |
| E->VectorizedValue = V; | |
| return V; | |
| } | |
| auto *LI = cast<LoadInst>(E->getSingleOperand(0)); | |
| Builder.SetInsertPoint(LI); | |
| Value *Ptr = LI->getPointerOperand(); | |
| LoadInst *V = Builder.CreateAlignedLoad(VecTy, Ptr, LI->getAlign()); | |
| Value *NewV = PropagateIRFlags(V); | |
| NewV = FinalShuffle(NewV, E); | |
| E->VectorizedValue = NewV; | |
| return NewV; | |
| } | |
| case Instruction::InsertValue: | |
| case Instruction::InsertElement: { | |
| assert(E->ReuseShuffleIndices.empty() && "All inserts should be unique"); | |
| if (const TreeEntry *OpE = getOperandEntry(E, 1); | |
| OpE && !OpE->isGather() && OpE->hasState() && | |
| !OpE->hasCopyableElements()) | |
| Builder.SetInsertPoint(cast<Instruction>(E->Scalars.back())); | |
| else | |
| setInsertPointAfterBundle(E); | |
| Value *V = vectorizeOperand(E, 1); | |
| ArrayRef<Value *> Op = E->getOperand(1); | |
| Type *ScalarTy = Op.front()->getType(); | |
| if (cast<VectorType>(V->getType())->getElementType() != ScalarTy) { | |
| assert(ScalarTy->isIntegerTy() && "Expected item in MinBWs."); | |
| std::pair<unsigned, bool> Res = MinBWs.lookup(getOperandEntry(E, 1)); | |
| assert(Res.first > 0 && "Expected item in MinBWs."); | |
| V = Builder.CreateIntCast( | |
| V, | |
| getWidenedType( | |
| ScalarTy, | |
| cast<FixedVectorType>(V->getType())->getNumElements()), | |
| Res.second); | |
| } | |
| // Create InsertVector shuffle if necessary | |
| auto *FirstInsert = cast<Instruction>(*find_if(E->Scalars, [E](Value *V) { | |
| return !is_contained(E->Scalars, cast<Instruction>(V)->getOperand(0)); | |
| })); | |
| const unsigned NumScalars = E->Scalars.size(); | |
| FixedVectorType *SrcVecTy = getInsertBuildVectorSrcTy(E); | |
| const unsigned NumElts = getNumElements(SrcVecTy); | |
| unsigned Offset = *getElementIndex(VL0); | |
| assert(Offset < NumElts && "Failed to find vector index offset"); | |
| // Create shuffle to resize vector | |
| SmallVector<int> Mask; | |
| if (!E->ReorderIndices.empty()) { | |
| inversePermutation(E->ReorderIndices, Mask); | |
| Mask.append(NumElts - NumScalars, PoisonMaskElem); | |
| } else { | |
| Mask.assign(NumElts, PoisonMaskElem); | |
| std::iota(Mask.begin(), std::next(Mask.begin(), NumScalars), 0); | |
| } | |
| // Create InsertVector shuffle if necessary | |
| bool IsIdentity = true; | |
| SmallVector<int> PrevMask(NumElts, PoisonMaskElem); | |
| Mask.swap(PrevMask); | |
| for (unsigned I = 0; I < NumScalars; ++I) { | |
| Value *Scalar = E->Scalars[PrevMask[I]]; | |
| unsigned InsertIdx = *getElementIndex(Scalar); | |
| IsIdentity &= InsertIdx - Offset == I; | |
| Mask[InsertIdx - Offset] = I; | |
| } | |
| if (!IsIdentity || NumElts != NumScalars) { | |
| Value *V2 = nullptr; | |
| bool IsVNonPoisonous = | |
| !isConstant(V) && isGuaranteedNotToBePoison(V, AC); | |
| SmallVector<int> InsertMask(Mask); | |
| if (NumElts != NumScalars && Offset == 0) { | |
| // Follow all insert element instructions from the current buildvector | |
| // sequence. | |
| Instruction *Ins = VL0; | |
| Instruction *Op; | |
| do { | |
| std::optional<unsigned> InsertIdx = getElementIndex(Ins); | |
| if (!InsertIdx) | |
| break; | |
| if (InsertMask[*InsertIdx] == PoisonMaskElem) | |
| InsertMask[*InsertIdx] = *InsertIdx; | |
| if (!Ins->hasOneUse()) | |
| break; | |
| Op = Ins; | |
| Ins = dyn_cast_if_present<Instruction>( | |
| Ins->getUniqueUndroppableUser()); | |
| } while (Ins && Ins->getOperand(0) == Op); | |
| SmallBitVector UseMask = | |
| buildUseMask(NumElts, InsertMask, UseMask::UndefsAsMask); | |
| SmallBitVector IsFirstPoison = | |
| isUndefVector<true>(FirstInsert->getOperand(0), UseMask); | |
| SmallBitVector IsFirstUndef = | |
| isUndefVector(FirstInsert->getOperand(0), UseMask); | |
| if (!IsFirstPoison.all()) { | |
| unsigned Idx = 0; | |
| for (unsigned I = 0; I < NumElts; I++) { | |
| if (InsertMask[I] == PoisonMaskElem && !IsFirstPoison.test(I) && | |
| IsFirstUndef.test(I)) { | |
| if (IsVNonPoisonous) { | |
| InsertMask[I] = I < NumScalars ? I : 0; | |
| continue; | |
| } | |
| if (!V2) | |
| V2 = UndefValue::get(V->getType()); | |
| if (Idx >= NumScalars) | |
| Idx = NumScalars - 1; | |
| InsertMask[I] = NumScalars + Idx; | |
| ++Idx; | |
| } else if (InsertMask[I] != PoisonMaskElem && | |
| Mask[I] == PoisonMaskElem) { | |
| InsertMask[I] = PoisonMaskElem; | |
| } | |
| } | |
| } else { | |
| InsertMask = Mask; | |
| } | |
| } | |
| if (!V2) | |
| V2 = PoisonValue::get(V->getType()); | |
| V = Builder.CreateShuffleVector(V, V2, InsertMask); | |
| if (auto *I = dyn_cast<Instruction>(V)) { | |
| GatherShuffleExtractSeq.insert(I); | |
| CSEBlocks.insert(I->getParent()); | |
| } | |
| } | |
| SmallVector<int> InsertMask(NumElts, PoisonMaskElem); | |
| for (unsigned I = 0; I < NumElts; I++) { | |
| if (Mask[I] != PoisonMaskElem) | |
| InsertMask[Offset + I] = I; | |
| } | |
| SmallBitVector UseMask = | |
| buildUseMask(NumElts, InsertMask, UseMask::UndefsAsMask); | |
| SmallBitVector IsFirstUndef = | |
| isUndefVector(FirstInsert->getOperand(0), UseMask); | |
| if ((!IsIdentity || Offset != 0 || !IsFirstUndef.all()) && | |
| NumElts != NumScalars) { | |
| if (IsFirstUndef.all()) { | |
| if (!ShuffleVectorInst::isIdentityMask(InsertMask, NumElts)) { | |
| SmallBitVector IsFirstPoison = | |
| isUndefVector<true>(FirstInsert->getOperand(0), UseMask); | |
| if (!IsFirstPoison.all()) { | |
| for (unsigned I = 0; I < NumElts; I++) { | |
| if (InsertMask[I] == PoisonMaskElem && !IsFirstPoison.test(I)) | |
| InsertMask[I] = I + NumElts; | |
| } | |
| } | |
| V = Builder.CreateShuffleVector( | |
| V, | |
| IsFirstPoison.all() ? PoisonValue::get(V->getType()) | |
| : FirstInsert->getOperand(0), | |
| InsertMask, cast<Instruction>(E->Scalars.back())->getName()); | |
| if (auto *I = dyn_cast<Instruction>(V)) { | |
| GatherShuffleExtractSeq.insert(I); | |
| CSEBlocks.insert(I->getParent()); | |
| } | |
| } | |
| } else { | |
| SmallBitVector IsFirstPoison = | |
| isUndefVector<true>(FirstInsert->getOperand(0), UseMask); | |
| for (unsigned I = 0; I < NumElts; I++) { | |
| if (InsertMask[I] == PoisonMaskElem) | |
| InsertMask[I] = IsFirstPoison.test(I) ? PoisonMaskElem : I; | |
| else | |
| InsertMask[I] += NumElts; | |
| } | |
| V = Builder.CreateShuffleVector( | |
| FirstInsert->getOperand(0), V, InsertMask, | |
| cast<Instruction>(E->Scalars.back())->getName()); | |
| if (auto *I = dyn_cast<Instruction>(V)) { | |
| GatherShuffleExtractSeq.insert(I); | |
| CSEBlocks.insert(I->getParent()); | |
| } | |
| } | |
| } | |
| ++NumVectorInstructions; | |
| if (ShuffleOrOp == Instruction::InsertValue && | |
| !canVectorStoreInsertValue(E)) { | |
| Type *AggTy = E->getMainOp()->getType(); | |
| Align SlotAlign = std::max(DL->getPrefTypeAlign(V->getType()), | |
| DL->getPrefTypeAlign(AggTy)); | |
| AllocaInst *Slot; | |
| { | |
| // Keep the alloca in the function entry block so it is not executed | |
| // (and does not grow the stack) on every iteration of a loop. | |
| IRBuilderBase::InsertPointGuard Guard(Builder); | |
| Builder.SetInsertPointPastAllocas(F); | |
| Slot = Builder.CreateAlloca(AggTy, /*ArraySize=*/nullptr, | |
| "vec2struct.slot"); | |
| Slot->setAlignment(SlotAlign); | |
| } | |
| (void)Builder.CreateAlignedStore(V, Slot, SlotAlign); | |
| V = Builder.CreateAlignedLoad(AggTy, Slot, SlotAlign, "vec2struct"); | |
| NumVectorInstructions += 2; | |
| } | |
| E->VectorizedValue = V; | |
| return V; | |
| } | |
| case Instruction::ZExt: | |
| case Instruction::SExt: | |
| case Instruction::FPToUI: | |
| case Instruction::FPToSI: | |
| case Instruction::FPExt: | |
| case Instruction::PtrToInt: | |
| case Instruction::PtrToAddr: | |
| case Instruction::IntToPtr: | |
| case Instruction::SIToFP: | |
| case Instruction::UIToFP: | |
| case Instruction::Trunc: | |
| case Instruction::FPTrunc: | |
| case Instruction::BitCast: { | |
| setInsertPointAfterBundle(E); | |
| Value *InVec = vectorizeOperand(E, 0); | |
| auto *CI = cast<CastInst>(VL0); | |
| Instruction::CastOps VecOpcode = CI->getOpcode(); | |
| Type *SrcScalarTy = cast<VectorType>(InVec->getType())->getElementType(); | |
| auto SrcIt = MinBWs.find(getOperandEntry(E, 0)); | |
| if (!ScalarTy->isFPOrFPVectorTy() && !SrcScalarTy->isFPOrFPVectorTy() && | |
| (SrcIt != MinBWs.end() || It != MinBWs.end() || | |
| SrcScalarTy != CI->getOperand(0)->getType()->getScalarType())) { | |
| // Check if the values are candidates to demote. | |
| unsigned SrcBWSz = DL->getTypeSizeInBits(SrcScalarTy); | |
| if (SrcIt != MinBWs.end()) | |
| SrcBWSz = SrcIt->second.first; | |
| unsigned BWSz = DL->getTypeSizeInBits(ScalarTy->getScalarType()); | |
| if (BWSz == SrcBWSz) { | |
| VecOpcode = Instruction::BitCast; | |
| } else if (BWSz < SrcBWSz) { | |
| VecOpcode = Instruction::Trunc; | |
| } else if (It != MinBWs.end()) { | |
| assert(BWSz > SrcBWSz && "Invalid cast!"); | |
| VecOpcode = It->second.second ? Instruction::SExt : Instruction::ZExt; | |
| } else if (SrcIt != MinBWs.end()) { | |
| assert(BWSz > SrcBWSz && "Invalid cast!"); | |
| VecOpcode = | |
| SrcIt->second.second ? Instruction::SExt : Instruction::ZExt; | |
| } | |
| } else if (VecOpcode == Instruction::SIToFP && SrcIt != MinBWs.end() && | |
| !SrcIt->second.second) { | |
| VecOpcode = Instruction::UIToFP; | |
| } else if (VecOpcode == Instruction::BitCast && SrcIt != MinBWs.end() && | |
| ScalarTy->isFPOrFPVectorTy()) { | |
| Type *OrigSrcScalarTy = CI->getSrcTy(); | |
| auto *OrigSrcVectorTy = | |
| getWidenedType(OrigSrcScalarTy, E->Scalars.size()); | |
| InVec = | |
| Builder.CreateIntCast(InVec, OrigSrcVectorTy, SrcIt->second.second); | |
| } else if (VecOpcode == Instruction::UIToFP && SrcIt != MinBWs.end() && | |
| SrcIt->second.second) { | |
| auto *OrigSrcVectorTy = | |
| getWidenedType(CI->getSrcTy(), E->Scalars.size()); | |
| InVec = Builder.CreateSExt(InVec, OrigSrcVectorTy); | |
| } | |
| Value *V = (VecOpcode != ShuffleOrOp && VecOpcode == Instruction::BitCast) | |
| ? InVec | |
| : Builder.CreateCast(VecOpcode, InVec, VecTy); | |
| V = FinalShuffle(V, E); | |
| E->VectorizedValue = V; | |
| ++NumVectorInstructions; | |
| return V; | |
| } | |
| case Instruction::FCmp: | |
| case Instruction::ICmp: { | |
| setInsertPointAfterBundle(E); | |
| Value *L = vectorizeOperand(E, 0); | |
| Value *R = vectorizeOperand(E, 1); | |
| if (L->getType() != R->getType()) { | |
| assert((getOperandEntry(E, 0)->isGather() || | |
| getOperandEntry(E, 1)->isGather() || | |
| MinBWs.contains(getOperandEntry(E, 0)) || | |
| MinBWs.contains(getOperandEntry(E, 1))) && | |
| "Expected item in MinBWs."); | |
| const unsigned LBW = cast<VectorType>(L->getType()) | |
| ->getElementType() | |
| ->getIntegerBitWidth(); | |
| const unsigned RBW = cast<VectorType>(R->getType()) | |
| ->getElementType() | |
| ->getIntegerBitWidth(); | |
| // A signed comparison needs the sign bit getActiveBits() ignores. | |
| const bool Signed = cast<CmpInst>(VL0)->isSigned(); | |
| auto GetRequiredBits = [Signed](const APInt &V) { | |
| return Signed ? V.getSignificantBits() : V.getActiveBits(); | |
| }; | |
| if ((LBW < RBW && (!allConstant(E->getOperand(1)) || | |
| any_of( | |
| E->getOperand(1), | |
| [&](Value *V) { | |
| auto *CI = dyn_cast<ConstantInt>(V); | |
| return !CI || | |
| GetRequiredBits(CI->getValue()) > LBW; | |
| }))) || | |
| (LBW > RBW && allConstant(E->getOperand(0)) && | |
| all_of(E->getOperand(1), [&](Value *V) { | |
| auto *CI = dyn_cast<ConstantInt>(V); | |
| return CI && GetRequiredBits(CI->getValue()) <= RBW; | |
| }))) { | |
| Type *CastTy = R->getType(); | |
| L = Builder.CreateIntCast(L, CastTy, GetOperandSignedness(0)); | |
| } else { | |
| Type *CastTy = L->getType(); | |
| R = Builder.CreateIntCast(R, CastTy, GetOperandSignedness(1)); | |
| } | |
| } | |
| CmpInst::Predicate P0 = cast<CmpInst>(VL0)->getPredicate(); | |
| // The bit tests compare and L, R with 0, the eq bit tests compare the | |
| // trunc X to i1 lanes with 1, exactly in the narrowed type. | |
| const bool IsBitTest = any_of(E->Scalars, IsaPred<TruncInst>); | |
| if (IsBitTest) { | |
| L = Builder.CreateAnd(L, R); | |
| const bool IsEq = | |
| CmpSamePredicateHelper::canConvertTo(VL0, CmpInst::ICMP_EQ); | |
| P0 = IsEq ? CmpInst::ICMP_EQ : CmpInst::ICMP_NE; | |
| Type *EltTy = L->getType()->getScalarType(); | |
| SmallVector<Constant *> Consts; | |
| for (Value *V : E->Scalars) | |
| Consts.append(getNumElements(VL0->getType()), | |
| ConstantInt::get(EltTy, IsEq && isa<TruncInst>(V))); | |
| R = ConstantVector::get(Consts); | |
| } | |
| // A compare with both operands narrowed to the same type preserves the | |
| // result only if the predicate matches the operand signedness: signed | |
| // predicates need both operands sign-extended from the narrow type, | |
| // unsigned predicates need both zero-extended, equality needs the same | |
| // extension on both sides; operands with a known-zero narrow sign bit | |
| // match any predicate. Otherwise, extend the operands back to the | |
| // original compared type. | |
| auto *LTy = cast<FixedVectorType>(L->getType()); | |
| auto *OrigOpTy = | |
| dyn_cast<IntegerType>(VL0->getOperand(0)->getType()->getScalarType()); | |
| if (!IsBitTest && OrigOpTy && L->getType() == R->getType() && | |
| LTy->getElementType()->getIntegerBitWidth() < | |
| OrigOpTy->getBitWidth()) { | |
| bool IsSigned0 = GetOperandSignedness(0); | |
| bool IsSigned1 = GetOperandSignedness(1); | |
| bool NonNeg0 = isKnownNonNegative(L, SimplifyQuery(*DL)); | |
| bool NonNeg1 = isKnownNonNegative(R, SimplifyQuery(*DL)); | |
| bool KeepNarrowTy; | |
| if (ICmpInst::isSigned(P0)) | |
| KeepNarrowTy = (IsSigned0 || NonNeg0) && (IsSigned1 || NonNeg1); | |
| else if (ICmpInst::isUnsigned(P0)) | |
| KeepNarrowTy = (!IsSigned0 || NonNeg0) && (!IsSigned1 || NonNeg1); | |
| else | |
| KeepNarrowTy = IsSigned0 == IsSigned1 || NonNeg0 || NonNeg1; | |
| if (!KeepNarrowTy) { | |
| Type *CastTy = getWidenedType(OrigOpTy, LTy->getNumElements()); | |
| L = Builder.CreateIntCast(L, CastTy, IsSigned0); | |
| R = Builder.CreateIntCast(R, CastTy, IsSigned1); | |
| } | |
| } | |
| // A booleanized zero-test leaf emits no comparison: its value is the | |
| // wide operand vector, and the i1 lanes are restored by the trunc at | |
| // the booleanized user node. (Trunc leaves are no-ops after the generic | |
| // MinBWs cast processing.) | |
| bool IsBooleanized = isBooleanizedNode(E); | |
| Value *V = IsBooleanized | |
| ? (all_of(E->getOperand(0), | |
| [](Value *V) { return match(V, m_Zero()); }) | |
| ? R | |
| : L) | |
| : Builder.CreateCmp(P0, L, R); | |
| if (!IsBooleanized) | |
| V = PropagateIRFlags(V); | |
| // Do not cast for cmps. | |
| VecTy = cast<FixedVectorType>(V->getType()); | |
| V = FinalShuffle(V, E); | |
| E->VectorizedValue = V; | |
| ++NumVectorInstructions; | |
| return V; | |
| } | |
| case Instruction::Select: { | |
| setInsertPointAfterBundle(E); | |
| Value *Cond = vectorizeOperand(E, 0); | |
| Value *True = vectorizeOperand(E, 1); | |
| Value *False = vectorizeOperand(E, 2); | |
| if (True->getType() != VecTy || False->getType() != VecTy) { | |
| assert((It != MinBWs.end() || getOperandEntry(E, 1)->isGather() || | |
| getOperandEntry(E, 2)->isGather() || | |
| MinBWs.contains(getOperandEntry(E, 1)) || | |
| MinBWs.contains(getOperandEntry(E, 2))) && | |
| "Expected item in MinBWs."); | |
| if (True->getType() != VecTy) | |
| True = Builder.CreateIntCast(True, VecTy, GetOperandSignedness(1)); | |
| if (False->getType() != VecTy) | |
| False = Builder.CreateIntCast(False, VecTy, GetOperandSignedness(2)); | |
| } | |
| unsigned CondNumElements = getNumElements(Cond->getType()); | |
| unsigned TrueNumElements = getNumElements(True->getType()); | |
| assert(TrueNumElements >= CondNumElements && | |
| TrueNumElements % CondNumElements == 0 && | |
| "Cannot vectorize Instruction::Select"); | |
| assert(TrueNumElements == getNumElements(False->getType()) && | |
| "Cannot vectorize Instruction::Select"); | |
| if (CondNumElements != TrueNumElements) { | |
| // When the return type is i1 but the source is fixed vector type, we | |
| // need to duplicate the condition value. | |
| Cond = Builder.CreateShuffleVector( | |
| Cond, createReplicatedMask(TrueNumElements / CondNumElements, | |
| CondNumElements)); | |
| } | |
| assert(getNumElements(Cond->getType()) == TrueNumElements && | |
| "Cannot vectorize Instruction::Select"); | |
| Value *V = | |
| Builder.CreateSelectWithUnknownProfile(Cond, True, False, DEBUG_TYPE); | |
| V = FinalShuffle(V, E); | |
| E->VectorizedValue = V; | |
| ++NumVectorInstructions; | |
| return V; | |
| } | |
| case Instruction::FNeg: { | |
| setInsertPointAfterBundle(E); | |
| Value *Op = vectorizeOperand(E, 0); | |
| Value *V = Builder.CreateUnOp( | |
| static_cast<Instruction::UnaryOps>(E->getOpcode()), Op); | |
| V = PropagateIRFlags(V); | |
| V = FinalShuffle(V, E); | |
| E->VectorizedValue = V; | |
| ++NumVectorInstructions; | |
| return V; | |
| } | |
| case Instruction::Freeze: { | |
| setInsertPointAfterBundle(E); | |
| Value *Op = vectorizeOperand(E, 0); | |
| if (Op->getType() != VecTy) { | |
| assert((It != MinBWs.end() || getOperandEntry(E, 0)->isGather() || | |
| MinBWs.contains(getOperandEntry(E, 0))) && | |
| "Expected item in MinBWs."); | |
| Op = Builder.CreateIntCast(Op, VecTy, GetOperandSignedness(0)); | |
| } | |
| Value *V = Builder.CreateFreeze(Op); | |
| V = FinalShuffle(V, E); | |
| E->VectorizedValue = V; | |
| ++NumVectorInstructions; | |
| return V; | |
| } | |
| case Instruction::Add: | |
| case Instruction::FAdd: | |
| case Instruction::Sub: | |
| case Instruction::FSub: | |
| case Instruction::Mul: | |
| case Instruction::FMul: | |
| case Instruction::UDiv: | |
| case Instruction::SDiv: | |
| case Instruction::FDiv: | |
| case Instruction::URem: | |
| case Instruction::SRem: | |
| case Instruction::FRem: | |
| case Instruction::Shl: | |
| case Instruction::LShr: | |
| case Instruction::AShr: | |
| case Instruction::And: | |
| case Instruction::Or: | |
| case Instruction::Xor: { | |
| setInsertPointAfterBundle(E); | |
| // The lanes with the absorbing constant use poison as the other operand. | |
| // Freeze the operand, if it may be poison, to keep these lanes equal to | |
| // the constant. | |
| auto FreezePlaceholders = [&](Value *Op, unsigned OpIdx) { | |
| if (!E->hasCopyableElements() || | |
| none_of(zip(E->Scalars, E->getOperand(OpIdx)), | |
| [&](const auto &P) { | |
| auto [Scalar, OpScalar] = P; | |
| return E->isCopyableElement(Scalar) && | |
| !isa<UndefValue>(Scalar) && | |
| isa<UndefValue>(OpScalar); | |
| }) || | |
| isGuaranteedNotToBePoison(Op, AC)) | |
| return Op; | |
| return Builder.CreateFreeze(Op); | |
| }; | |
| if (E->hasReassocScalars()) { | |
| // Vectorize operand columns, then combine pairwise in a balanced | |
| // tree; negated columns (flattened subtracts) are subtracted from | |
| // the positive total at the end. | |
| SmallVector<Value *> CombinedScalars = to_vector(E->Scalars); | |
| append_range(CombinedScalars, E->getReassocScalars()); | |
| // Reuse flags when a combine exactly matches a subsumed scalar pair. | |
| // Commutative scalars are keyed operand-order independently, | |
| // subtracts keep their operand order. | |
| auto MakeOperandKey = [](Value *A, Value *B, unsigned Opcode) { | |
| return Instruction::isCommutative(Opcode) && B < A | |
| ? std::make_pair(B, A) | |
| : std::make_pair(A, B); | |
| }; | |
| SmallDenseMap<std::pair<std::pair<Value *, Value *>, unsigned>, | |
| SmallVector<Instruction *, 1>> | |
| ByOperands; | |
| // Copyable leaves are used as-is, they are never combine targets. | |
| for (Value *V : make_filter_range(CombinedScalars, [&](Value *V) { | |
| return !E->isCopyableElement(V); | |
| })) { | |
| auto *I = cast<Instruction>(V); | |
| const unsigned Opcode = I->getOpcode(); | |
| ByOperands[{MakeOperandKey(I->getOperand(0), I->getOperand(1), | |
| Opcode), | |
| Opcode}] | |
| .push_back(I); | |
| } | |
| const unsigned CombineOpcode = getReassocCombineOpcode(E->getOpcode()); | |
| // Cast operand columns to VecTy when bit-width demotion changed types. | |
| auto GetCastOperand = [&](unsigned Idx, Value *Op) { | |
| if (Op->getType() == VecTy) | |
| return Op; | |
| return Builder.CreateIntCast(Op, VecTy, GetOperandSignedness(Idx)); | |
| }; | |
| SmallVector<Value *> Ops; | |
| SmallVector<Value *> NegOps; | |
| // Track which scalar each partial result still represents for flag | |
| // reuse. | |
| SmallVector<SmallVector<Value *>> ScalarOps; | |
| SmallVector<SmallVector<Value *>> NegScalarOps; | |
| for (unsigned Idx : seq<unsigned>(E->getNumOperands())) { | |
| // Identity-only columns combine to a no-op; skip them rather than | |
| // emitting a real binop against an identity vector. | |
| if (all_of(E->getOperand(Idx), [&](Value *V) { | |
| return isBinOpIdentityConstant(V, CombineOpcode); | |
| })) | |
| continue; | |
| const bool Negated = E->isReassocNegatedOp(Idx); | |
| (Negated ? NegOps : Ops) | |
| .push_back(GetCastOperand( | |
| Idx, FreezePlaceholders(vectorizeOperand(E, Idx), Idx))); | |
| (Negated ? NegScalarOps : ScalarOps) | |
| .emplace_back(E->getOperand(Idx).begin(), | |
| E->getOperand(Idx).end()); | |
| } | |
| const bool HasNegatedOps = !NegOps.empty(); | |
| // Check whether combining the partial results of \p LHSScalars and | |
| // \p RHSScalars reproduces a subsumed scalar pair lane by lane, so | |
| // the matched scalars' flags can be reused. Fills per-lane | |
| // representatives for the next combine round on an exact match. | |
| auto TryExactCombine = | |
| [&](Value *Combined, unsigned Opcode, ArrayRef<Value *> LHSScalars, | |
| ArrayRef<Value *> RHSScalars, | |
| SmallVectorImpl<Value *> &Representatives) -> Value * { | |
| // Either side may be empty after a non-exact combine. | |
| if (LHSScalars.empty() || LHSScalars.size() != RHSScalars.size()) | |
| return nullptr; | |
| SmallVector<Value *> ExactMatches; | |
| Representatives.resize(LHSScalars.size()); | |
| bool IsExact = | |
| all_of(seq<unsigned>(LHSScalars.size()), [&](unsigned Lane) { | |
| if (!LHSScalars[Lane] || !RHSScalars[Lane]) | |
| return false; | |
| auto It = ByOperands.find( | |
| {MakeOperandKey(LHSScalars[Lane], RHSScalars[Lane], Opcode), | |
| Opcode}); | |
| if (It == ByOperands.end()) | |
| return false; | |
| append_range(ExactMatches, It->second); | |
| Representatives[Lane] = It->second.front(); | |
| return true; | |
| }); | |
| if (!IsExact) | |
| return nullptr; | |
| return PropagateIRFlags(Combined, Opcode, ExactMatches); | |
| }; | |
| auto CombineFallback = [&](Value *Combined, unsigned Opcode) { | |
| Value *V = PropagateIRFlags(Combined, Opcode, CombinedScalars); | |
| // Drop overflow/fast-math flags not proven exact; regrouping can | |
| // invalidate nsw/nuw and nnan/ninf even when each step was safe. | |
| // nuw is kept for pure add trees only: a negated leaf can make a | |
| // re-paired partial sum exceed the original running total. | |
| auto *CombinedI = dyn_cast<Instruction>(V); | |
| if (!CombinedI) | |
| return V; | |
| if (isa<FPBinaryOperator>(CombinedI)) { | |
| CombinedI->setHasNoNaNs(false); | |
| CombinedI->setHasNoInfs(false); | |
| } else if (isa<OverflowingBinaryOperator>(CombinedI)) { | |
| CombinedI->setHasNoSignedWrap(false); | |
| if (Opcode != Instruction::Add || HasNegatedOps) | |
| CombinedI->setHasNoUnsignedWrap(false); | |
| } | |
| return V; | |
| }; | |
| // Combine the group columns pairwise in a balanced tree and return | |
| // the total with the per-lane scalars it still represents. | |
| auto CombineColumns = | |
| [&](SmallVector<Value *> GroupOps, | |
| SmallVector<SmallVector<Value *>> GroupScalarOps, | |
| unsigned Opcode) -> std::pair<Value *, SmallVector<Value *>> { | |
| while (GroupOps.size() > 1) { | |
| SmallVector<Value *> NextOps((GroupOps.size() + 1) / 2); | |
| SmallVector<SmallVector<Value *>> NextScalarOps(NextOps.size()); | |
| for (unsigned Idx : seq<unsigned>(NextOps.size())) { | |
| if (2 * Idx + 1 == GroupOps.size()) { | |
| // Odd one out this round: carry it over unpaired. | |
| NextOps[Idx] = GroupOps[2 * Idx]; | |
| NextScalarOps[Idx] = std::move(GroupScalarOps[2 * Idx]); | |
| continue; | |
| } | |
| Value *Combined = Builder.CreateBinOp( | |
| static_cast<Instruction::BinaryOps>(Opcode), | |
| GroupOps[2 * Idx], GroupOps[2 * Idx + 1]); | |
| // Check whether this combine exactly matches a subsumed | |
| // scalar. | |
| SmallVector<Value *> Representatives; | |
| if (Value *Exact = TryExactCombine( | |
| Combined, Opcode, GroupScalarOps[2 * Idx], | |
| GroupScalarOps[2 * Idx + 1], Representatives)) { | |
| NextOps[Idx] = Exact; | |
| NextScalarOps[Idx] = std::move(Representatives); | |
| continue; | |
| } | |
| NextOps[Idx] = CombineFallback(Combined, Opcode); | |
| // No scalar match: deeper combines from this result cannot be | |
| // exact. | |
| } | |
| GroupOps = std::move(NextOps); | |
| GroupScalarOps = std::move(NextScalarOps); | |
| } | |
| return {GroupOps.front(), GroupScalarOps.front()}; | |
| }; | |
| // -(a+b) differs from -a + -b in the sign of a zero result, so the | |
| // negated columns are summed only for integers or with nsz on every | |
| // link; without it each is subtracted in turn. | |
| const bool CanSumNegated = | |
| CombineOpcode != Instruction::FAdd || | |
| all_of(make_isa_range<Instruction>(CombinedScalars), | |
| [&](Instruction *I) { | |
| return E->isCopyableElement(I) || I->hasNoSignedZeros(); | |
| }); | |
| const unsigned SubOpcode = CombineOpcode == Instruction::FAdd | |
| ? Instruction::FSub | |
| : Instruction::Sub; | |
| Value *V; | |
| SmallVector<Value *> PosReps; | |
| if (Ops.empty()) { | |
| // Every positive column was an identity: the total is the identity. | |
| V = ConstantExpr::getBinOpIdentity(CombineOpcode, VecTy); | |
| } else { | |
| std::tie(V, PosReps) = CombineColumns( | |
| std::move(Ops), std::move(ScalarOps), CombineOpcode); | |
| } | |
| if (CanSumNegated && !NegOps.empty()) { | |
| auto [NegTotal, NegReps] = CombineColumns( | |
| std::move(NegOps), std::move(NegScalarOps), CombineOpcode); | |
| NegOps = {NegTotal}; | |
| NegScalarOps.clear(); | |
| NegScalarOps.push_back(std::move(NegReps)); | |
| } | |
| for (auto [NegOp, NegScalars] : zip(NegOps, NegScalarOps)) { | |
| Value *Combined = Builder.CreateBinOp( | |
| static_cast<Instruction::BinaryOps>(SubOpcode), V, NegOp); | |
| SmallVector<Value *> NextReps; | |
| if (Value *Exact = TryExactCombine(Combined, SubOpcode, PosReps, | |
| NegScalars, NextReps)) { | |
| V = Exact; | |
| PosReps = std::move(NextReps); | |
| continue; | |
| } | |
| V = CombineFallback(Combined, SubOpcode); | |
| PosReps.clear(); | |
| } | |
| V = FinalShuffle(V, E); | |
| E->VectorizedValue = V; | |
| ++NumVectorInstructions; | |
| return V; | |
| } | |
| Value *LHS = vectorizeOperand(E, 0); | |
| Value *RHS = vectorizeOperand(E, 1); | |
| if (ShuffleOrOp == Instruction::And && It != MinBWs.end()) { | |
| assert(E->getNumOperands() == 2 && "Expected exactly 2 operands."); | |
| for (unsigned I : seq<unsigned>(0, E->getNumOperands())) { | |
| ArrayRef<Value *> Ops = E->getOperand(I); | |
| if (all_of(Ops, [&](Value *Op) { | |
| auto *CI = dyn_cast<ConstantInt>(Op); | |
| return CI && CI->getValue().countr_one() >= It->second.first; | |
| })) { | |
| V = FinalShuffle(I == 0 ? RHS : LHS, E); | |
| E->VectorizedValue = V; | |
| ++NumVectorInstructions; | |
| return V; | |
| } | |
| } | |
| } | |
| LHS = FreezePlaceholders(LHS, 0); | |
| RHS = FreezePlaceholders(RHS, 1); | |
| if (LHS->getType() != VecTy || RHS->getType() != VecTy) { | |
| assert((It != MinBWs.end() || getOperandEntry(E, 0)->isGather() || | |
| getOperandEntry(E, 1)->isGather() || | |
| MinBWs.contains(getOperandEntry(E, 0)) || | |
| MinBWs.contains(getOperandEntry(E, 1))) && | |
| "Expected item in MinBWs."); | |
| if (LHS->getType() != VecTy) | |
| LHS = Builder.CreateIntCast(LHS, VecTy, GetOperandSignedness(0)); | |
| if (RHS->getType() != VecTy) | |
| RHS = Builder.CreateIntCast(RHS, VecTy, GetOperandSignedness(1)); | |
| } | |
| // Structural padding does not imply profitability; re-check the cost | |
| // here so codegen matches the earlier cost-based decision. | |
| Value *V = nullptr; | |
| unsigned NumElts = E->Scalars.size(); | |
| FixedVectorType *PaddedVecTy = nullptr; | |
| if (getMaskedDivRemCost(*TTI, Opts.slp_revec, ShuffleOrOp, ScalarTy, | |
| NumElts, CostKind, &PaddedVecTy) | |
| .isValid()) { | |
| assert(PaddedVecTy && "Expected padded type for masked div/rem."); | |
| // Scale the lane count up to elements for REVEC, where each lane | |
| // is itself a vector. | |
| unsigned NumActiveElts = NumElts * getNumElements(ScalarTy); | |
| Value *WidenedLHS = | |
| createInsertVector(Builder, PoisonValue::get(PaddedVecTy), LHS, 0); | |
| Value *WidenedRHS = | |
| createInsertVector(Builder, PoisonValue::get(PaddedVecTy), RHS, 0); | |
| SmallVector<Constant *> MaskValues( | |
| PaddedVecTy->getNumElements(), | |
| ConstantInt::getFalse(Builder.getContext())); | |
| std::fill_n(MaskValues.begin(), NumActiveElts, | |
| ConstantInt::getTrue(Builder.getContext())); | |
| Value *DivRemMask = ConstantVector::get(MaskValues); | |
| Value *MaskedV = Builder.CreateIntrinsic( | |
| getMaskedDivRemIntrinsic(ShuffleOrOp), {PaddedVecTy}, | |
| {WidenedLHS, WidenedRHS, DivRemMask}); | |
| V = createExtractVector(Builder, MaskedV, NumActiveElts, 0); | |
| } | |
| if (!V) | |
| V = Builder.CreateBinOp( | |
| static_cast<Instruction::BinaryOps>(E->getOpcode()), LHS, RHS); | |
| V = PropagateIRFlags(V); | |
| V = FinalShuffle(V, E); | |
| E->VectorizedValue = V; | |
| ++NumVectorInstructions; | |
| return V; | |
| } | |
| case Instruction::Load: { | |
| // Loads are inserted at the head of the tree because we don't want to | |
| // sink them all the way down past store instructions. | |
| setInsertPointAfterBundle(E); | |
| LoadInst *LI = cast<LoadInst>(VL0); | |
| Instruction *NewLI; | |
| FixedVectorType *StridedLoadTy = nullptr; | |
| Value *PO = LI->getPointerOperand(); | |
| if (E->State == TreeEntry::Vectorize) { | |
| const uint64_t Shift = getVectorLoadShift(E->Scalars); | |
| if (Shift) | |
| PO = Builder.CreatePtrAdd( | |
| PO, ConstantInt::getSigned(DL->getIndexType(PO->getType()), | |
| -static_cast<int64_t>(Shift))); | |
| NewLI = Builder.CreateAlignedLoad( | |
| VecTy, PO, commonAlignment(LI->getAlign(), Shift)); | |
| } else if (E->State == TreeEntry::CompressVectorize) { | |
| auto [CompressMask, LoadVecTy, InterleaveFactor, IsMasked] = | |
| CompressEntryToData.at(E); | |
| Align CommonAlignment = LI->getAlign(); | |
| if (IsMasked) { | |
| unsigned VF = getNumElements(LoadVecTy); | |
| SmallVector<Constant *> MaskValues( | |
| VF / getNumElements(LI->getType()), | |
| ConstantInt::getFalse(VecTy->getContext())); | |
| for (int I : CompressMask) | |
| MaskValues[I] = ConstantInt::getTrue(VecTy->getContext()); | |
| if (auto *VecTy = dyn_cast<FixedVectorType>(LI->getType())) { | |
| assert(Opts.slp_revec && "Only supported by REVEC."); | |
| MaskValues = replicateMask(MaskValues, VecTy->getNumElements()); | |
| } | |
| Constant *MaskValue = ConstantVector::get(MaskValues); | |
| NewLI = Builder.CreateMaskedLoad(LoadVecTy, PO, CommonAlignment, | |
| MaskValue); | |
| } else { | |
| NewLI = Builder.CreateAlignedLoad(LoadVecTy, PO, CommonAlignment); | |
| } | |
| NewLI = cast<Instruction>(PropagateIRFlags(NewLI)); | |
| // TODO: include this cost into CommonCost. | |
| if (auto *VecTy = dyn_cast<FixedVectorType>(LI->getType())) { | |
| assert(Opts.slp_revec && "FixedVectorType is not expected."); | |
| transformScalarShuffleIndiciesToVector(VecTy->getNumElements(), | |
| CompressMask); | |
| } | |
| NewLI = | |
| cast<Instruction>(Builder.CreateShuffleVector(NewLI, CompressMask)); | |
| } else if (E->State == TreeEntry::StridedVectorize) { | |
| Value *Ptr0 = cast<LoadInst>(E->Scalars.front())->getPointerOperand(); | |
| Value *PtrN = cast<LoadInst>(E->Scalars.back())->getPointerOperand(); | |
| PO = IsReverseOrder ? PtrN : Ptr0; | |
| Type *StrideTy = DL->getIndexType(PO->getType()); | |
| Value *StrideVal; | |
| const StridedPtrInfo &SPtrInfo = TreeEntryToStridedPtrInfoMap.at(E); | |
| StridedLoadTy = SPtrInfo.Ty; | |
| assert(StridedLoadTy && "Missing StridedPointerInfo for tree entry."); | |
| unsigned StridedLoadEC = | |
| StridedLoadTy->getElementCount().getKnownMinValue(); | |
| Value *Stride = SPtrInfo.StrideVal; | |
| if (!Stride) { | |
| const SCEV *StrideSCEV = SPtrInfo.StrideSCEV; | |
| assert(StrideSCEV && "Neither StrideVal nor StrideSCEV were set."); | |
| SCEVExpander Expander(*SE, "strided-load-vec"); | |
| Stride = Expander.expandCodeFor(StrideSCEV, StrideSCEV->getType(), | |
| &*Builder.GetInsertPoint()); | |
| } | |
| Value *NewStride = | |
| Builder.CreateIntCast(Stride, StrideTy, /*isSigned=*/true); | |
| StrideVal = Builder.CreateMul( | |
| NewStride, ConstantInt::getSigned( | |
| StrideTy, (IsReverseOrder ? -1 : 1) * | |
| static_cast<int>( | |
| DL->getTypeAllocSize(ScalarTy)))); | |
| Align CommonAlignment = computeCommonAlignment<LoadInst>(E->Scalars); | |
| auto *Inst = Builder.CreateIntrinsicWithoutFolding( | |
| Intrinsic::experimental_vp_strided_load, | |
| {StridedLoadTy, PO->getType(), StrideTy}, | |
| {PO, StrideVal, | |
| Builder.getAllOnesMask(ElementCount::getFixed(StridedLoadEC)), | |
| Builder.getInt32(StridedLoadEC)}); | |
| Inst->addParamAttr( | |
| /*ArgNo=*/0, | |
| Attribute::getWithAlignment(Inst->getContext(), CommonAlignment)); | |
| NewLI = Inst; | |
| } else if (E->State == TreeEntry::BlendedLoadVectorize) { | |
| // Two masked loads from the candidate bases, blended by the vectorized | |
| // per-lane condition operand. E->Scalars stays in natural lane order, | |
| // so the contiguous loads line up with the mask; ReorderIndices, if | |
| // any, is applied below by the shared FinalShuffle. | |
| Value *P0 = cast<LoadInst>(E->Scalars.front())->getPointerOperand(); | |
| if (auto *GEP = dyn_cast<GetElementPtrInst>(P0)) | |
| P0 = GEP->getPointerOperand(); | |
| auto *Sel = cast<SelectInst>(P0); | |
| Align CommonAlignment = | |
| computeBlendedLoadBaseAlignment(E->Scalars, *DL); | |
| Value *BlendMask = vectorizeOperand(E, 0); | |
| // Place the loads at the bundle head, not at the mask, so they are not | |
| // hoisted above an aliasing store. | |
| setInsertPointAfterBundle(E); | |
| Value *NotMask = Builder.CreateNot(BlendMask); | |
| Value *VecPoison = PoisonValue::get(VecTy); | |
| Value *TrueVal = Builder.CreateMaskedLoad( | |
| VecTy, Sel->getTrueValue(), CommonAlignment, BlendMask, VecPoison); | |
| Value *FalseVal = Builder.CreateMaskedLoad( | |
| VecTy, Sel->getFalseValue(), CommonAlignment, NotMask, VecPoison); | |
| NewLI = cast<Instruction>( | |
| Builder.CreateSelect(BlendMask, TrueVal, FalseVal)); | |
| } else { | |
| assert(E->State == TreeEntry::ScatterVectorize && "Unhandled state"); | |
| Value *VecPtr = vectorizeOperand(E, 0); | |
| if (isa<FixedVectorType>(ScalarTy)) { | |
| assert(Opts.slp_revec && "FixedVectorType is not expected."); | |
| // CreateMaskedGather expects VecTy and VecPtr have same size. We need | |
| // to expand VecPtr if ScalarTy is a vector type. | |
| unsigned ScalarTyNumElements = getNumElements(ScalarTy); | |
| unsigned VecTyNumElements = getNumElements(VecTy); | |
| assert(VecTyNumElements % ScalarTyNumElements == 0 && | |
| "Cannot expand getelementptr."); | |
| unsigned VF = VecTyNumElements / ScalarTyNumElements; | |
| SmallVector<Constant *> Indices(VecTyNumElements); | |
| transform(seq(VecTyNumElements), Indices.begin(), [=](unsigned I) { | |
| return Builder.getInt64(I % ScalarTyNumElements); | |
| }); | |
| VecPtr = Builder.CreateGEP( | |
| toScalarizedTy(VecTy), | |
| Builder.CreateShuffleVector( | |
| VecPtr, createReplicatedMask(ScalarTyNumElements, VF)), | |
| ConstantVector::get(Indices)); | |
| } | |
| // Use the minimum alignment of the gathered loads. | |
| Align CommonAlignment = computeCommonAlignment<LoadInst>(E->Scalars); | |
| NewLI = Builder.CreateMaskedGather(VecTy, VecPtr, CommonAlignment); | |
| } | |
| // The wide load also reads the memory of the undef and poison lanes, so | |
| // the metadata of the scalar loads does not apply. | |
| const bool HasUndefLanes = any_of(E->Scalars, IsaPred<UndefValue>); | |
| Value *V = (E->State == TreeEntry::CompressVectorize || | |
| E->State == TreeEntry::BlendedLoadVectorize || HasUndefLanes) | |
| ? NewLI | |
| : PropagateIRFlags(NewLI); | |
| if (StridedLoadTy != VecTy) | |
| V = createWidenedStridedCast(Builder, V, VecTy, *DL); | |
| // The memory of the undef and poison lanes may hold poison, but these | |
| // lanes may be used, so they must not be poison. | |
| if (HasUndefLanes) | |
| V = Builder.CreateFreeze(V); | |
| V = FinalShuffle(V, E); | |
| E->VectorizedValue = V; | |
| ++NumVectorInstructions; | |
| return V; | |
| } | |
| case Instruction::Store: { | |
| auto *SI = cast<StoreInst>(VL0); | |
| setInsertPointAfterBundle(E); | |
| Value *VecValue = vectorizeOperand(E, 0); | |
| if (VecValue->getType() != VecTy) | |
| VecValue = | |
| Builder.CreateIntCast(VecValue, VecTy, GetOperandSignedness(0)); | |
| VecValue = FinalShuffle(VecValue, E); | |
| Value *Ptr = SI->getPointerOperand(); | |
| Instruction *ST; | |
| if (E->State == TreeEntry::Vectorize) { | |
| ST = Builder.CreateAlignedStore(VecValue, Ptr, SI->getAlign()); | |
| } else if (E->State == TreeEntry::ExpandVectorize) { | |
| const StridedPtrInfo &SPtrInfo = TreeEntryToStridedPtrInfoMap.at(E); | |
| FixedVectorType *StridedStoreTy = SPtrInfo.Ty; | |
| assert(StridedStoreTy && "Missing StridedPointerInfo for tree entry."); | |
| Align CommonAlignment = computeCommonAlignment<StoreInst>(E->Scalars); | |
| ArrayRef<int> ExpandMask = E->ReuseShuffleIndices; | |
| unsigned VecNumElts = getNumElements(StridedStoreTy); | |
| assert(VecValue->getType() == StridedStoreTy && | |
| "FinalShuffle must expand the value to the masked store type."); | |
| assert(ExpandMask.size() == VecNumElts && | |
| "Reuse mask must match the masked store type."); | |
| Value *BasePtr = | |
| cast<StoreInst>(E->Scalars.front())->getPointerOperand(); | |
| SmallVector<Constant *> MaskValues( | |
| VecNumElts, ConstantInt::getFalse(VecTy->getContext())); | |
| for (unsigned I : seq<unsigned>(VecNumElts)) | |
| if (ExpandMask[I] != PoisonMaskElem) | |
| MaskValues[I] = ConstantInt::getTrue(VecTy->getContext()); | |
| ST = Builder.CreateMaskedStore(VecValue, BasePtr, CommonAlignment, | |
| ConstantVector::get(MaskValues)); | |
| } else { | |
| assert(E->State == TreeEntry::StridedVectorize && | |
| "Expected either strided, masked or consecutive stores."); | |
| bool IsReverseOrder = E->isReverse(); | |
| if (IsReverseOrder) { | |
| SI = cast<StoreInst>(E->Scalars[E->ReorderIndices.front()]); | |
| Ptr = SI->getPointerOperand(); | |
| } | |
| Align CommonAlignment = computeCommonAlignment<StoreInst>(E->Scalars); | |
| Type *StrideTy = DL->getIndexType(SI->getPointerOperandType()); | |
| const StridedPtrInfo &SPtrInfo = TreeEntryToStridedPtrInfoMap.at(E); | |
| FixedVectorType *StridedStoreTy = SPtrInfo.Ty; | |
| assert(StridedStoreTy && "Missing StridedPointerInfo for tree entry."); | |
| unsigned StridedStoreEC = getNumElements(StridedStoreTy); | |
| Value *Stride = SPtrInfo.StrideVal; | |
| assert(Stride && "Missing StridedPointerInfo for tree entry."); | |
| Value *StrideVal = | |
| Builder.CreateIntCast(Stride, StrideTy, /*isSigned=*/true); | |
| // vp_strided_store::stride is defined in bytes | |
| StrideVal = Builder.CreateMul( | |
| StrideVal, | |
| ConstantInt::getSigned( | |
| StrideTy, static_cast<int>(DL->getTypeAllocSize(ScalarTy)))); | |
| if (StridedStoreTy != VecTy) | |
| VecValue = | |
| createWidenedStridedCast(Builder, VecValue, StridedStoreTy, *DL); | |
| auto *Inst = Builder.CreateIntrinsicWithoutFolding( | |
| Intrinsic::experimental_vp_strided_store, | |
| {StridedStoreTy, Ptr->getType(), StrideTy}, | |
| {VecValue, Ptr, StrideVal, | |
| Builder.getAllOnesMask(ElementCount::getFixed(StridedStoreEC)), | |
| Builder.getInt32(StridedStoreEC)}); | |
| Inst->addParamAttr( | |
| /*ArgNo=*/1, | |
| Attribute::getWithAlignment(Inst->getContext(), CommonAlignment)); | |
| ST = Inst; | |
| } | |
| Value *V = PropagateIRFlags(ST); | |
| E->VectorizedValue = V; | |
| ++NumVectorInstructions; | |
| return V; | |
| } | |
| case Instruction::GetElementPtr: { | |
| auto *GEP0 = cast<GetElementPtrInst>(VL0); | |
| setInsertPointAfterBundle(E); | |
| Value *Op0 = vectorizeOperand(E, 0); | |
| SmallVector<Value *> OpVecs; | |
| for (int J = 1, N = GEP0->getNumOperands(); J < N; ++J) { | |
| Value *OpVec = vectorizeOperand(E, J); | |
| OpVecs.push_back(OpVec); | |
| } | |
| Value *V = Builder.CreateGEP(GEP0->getSourceElementType(), Op0, OpVecs); | |
| if (Instruction *I = dyn_cast<GetElementPtrInst>(V)) { | |
| SmallVector<Value *> GEPs; | |
| for (Value *V : E->Scalars) { | |
| if (isa<GetElementPtrInst>(V)) | |
| GEPs.push_back(V); | |
| } | |
| V = PropagateIRFlags(I); | |
| } | |
| V = FinalShuffle(V, E); | |
| E->VectorizedValue = V; | |
| ++NumVectorInstructions; | |
| return V; | |
| } | |
| case Instruction::Call: { | |
| CallInst *CI = cast<CallInst>(VL0); | |
| setInsertPointAfterBundle(E); | |
| Intrinsic::ID ID = getVectorIntrinsicIDForCall(CI, TLI); | |
| SmallVector<Type *> ArgTys = buildIntrinsicArgTypes( | |
| CI, ID, getNumElements(VecTy), | |
| It != MinBWs.end() ? It->second.first : 0, TTI); | |
| auto VecCallCosts = | |
| getVectorCallCosts(CI, VecTy, TTI, TLI, ArgTys, CostKind); | |
| bool UseIntrinsic = ID != Intrinsic::not_intrinsic && | |
| VecCallCosts.first <= VecCallCosts.second; | |
| Value *ScalarArg = nullptr; | |
| SmallVector<Value *> OpVecs; | |
| SmallVector<Type *, 2> TysForDecl; | |
| // Add return type if intrinsic is overloaded on it. | |
| if (UseIntrinsic && isVectorIntrinsicWithOverloadTypeAtArg(ID, -1, TTI)) { | |
| ArrayRef<Type *> ContainedTys = getContainedTypes(VecTy); | |
| for (auto [Idx, Ty] : enumerate(ContainedTys)) { | |
| if (isVectorIntrinsicWithStructReturnOverloadAtField(ID, Idx, TTI)) | |
| TysForDecl.push_back(Ty); | |
| } | |
| } | |
| auto *CEI = cast<CallInst>(VL0); | |
| for (unsigned I : seq<unsigned>(0, CI->arg_size())) { | |
| // Some intrinsics have scalar arguments. This argument should not be | |
| // vectorized. | |
| if (UseIntrinsic && isVectorIntrinsicWithScalarOpAtArg(ID, I, TTI)) { | |
| ScalarArg = CEI->getArgOperand(I); | |
| // if decided to reduce bitwidth of abs intrinsic, it second argument | |
| // must be set false (do not return poison, if value issigned min). | |
| if (ID == Intrinsic::abs && It != MinBWs.end() && | |
| It->second.first < DL->getTypeSizeInBits(CEI->getType())) | |
| ScalarArg = Builder.getFalse(); | |
| OpVecs.push_back(ScalarArg); | |
| if (isVectorIntrinsicWithOverloadTypeAtArg(ID, I, TTI)) | |
| TysForDecl.push_back(ScalarArg->getType()); | |
| continue; | |
| } | |
| Value *OpVec = vectorizeOperand(E, I); | |
| ScalarArg = CEI->getArgOperand(I); | |
| if (cast<VectorType>(OpVec->getType())->getElementType() != | |
| ScalarArg->getType()->getScalarType() && | |
| It == MinBWs.end()) { | |
| auto *CastTy = | |
| getWidenedType(ScalarArg->getType(), getNumElements(VecTy)); | |
| OpVec = Builder.CreateIntCast(OpVec, CastTy, GetOperandSignedness(I)); | |
| } else if (It != MinBWs.end()) { | |
| OpVec = Builder.CreateIntCast(OpVec, VecTy, GetOperandSignedness(I)); | |
| } | |
| LLVM_DEBUG(dbgs() << "SLP: OpVec[" << I << "]: " << *OpVec << "\n"); | |
| OpVecs.push_back(OpVec); | |
| if (UseIntrinsic && isVectorIntrinsicWithOverloadTypeAtArg(ID, I, TTI)) | |
| TysForDecl.push_back(OpVec->getType()); | |
| } | |
| Function *CF; | |
| if (!UseIntrinsic) { | |
| VFShape Shape = | |
| VFShape::get(CI->getFunctionType(), | |
| ElementCount::getFixed(getNumElements(VecTy)), | |
| false /*HasGlobalPred*/); | |
| CF = VFDatabase(*CI).getVectorizedFunction(Shape); | |
| } else { | |
| CF = Intrinsic::getOrInsertDeclaration(F->getParent(), ID, TysForDecl); | |
| } | |
| SmallVector<OperandBundleDef, 1> OpBundles; | |
| CI->getOperandBundlesAsDefs(OpBundles); | |
| Value *V = Builder.CreateCall(CF, OpVecs, OpBundles); | |
| V = PropagateIRFlags(V); | |
| cast<CallInst>(V)->setCallingConv(CF->getCallingConv()); | |
| V = FinalShuffle(V, E); | |
| E->VectorizedValue = V; | |
| ++NumVectorInstructions; | |
| return V; | |
| } | |
| case Instruction::ShuffleVector: { | |
| Value *V; | |
| if (Opts.slp_revec && !E->isAltShuffle()) { | |
| setInsertPointAfterBundle(E); | |
| Value *Src = vectorizeOperand(E, 0); | |
| SmallVector<int> ThisMask(calculateShufflevectorMask(E->Scalars)); | |
| if (auto *SVSrc = dyn_cast<ShuffleVectorInst>(Src)) { | |
| SmallVector<int> NewMask(ThisMask.size()); | |
| transform(ThisMask, NewMask.begin(), [&SVSrc](int Mask) { | |
| return SVSrc->getShuffleMask()[Mask]; | |
| }); | |
| V = Builder.CreateShuffleVector(SVSrc->getOperand(0), | |
| SVSrc->getOperand(1), NewMask); | |
| } else { | |
| V = Builder.CreateShuffleVector(Src, ThisMask); | |
| } | |
| V = PropagateIRFlags(V); | |
| V = FinalShuffle(V, E); | |
| } else { | |
| assert(E->isAltShuffle() && | |
| ((Instruction::isBinaryOp(E->getOpcode()) && | |
| Instruction::isBinaryOp(E->getAltOpcode())) || | |
| (Instruction::isCast(E->getOpcode()) && | |
| Instruction::isCast(E->getAltOpcode())) || | |
| (isa<CmpInst>(VL0) && isa<CmpInst>(E->getAltOp()))) && | |
| "Invalid Shuffle Vector Operand"); | |
| // Gather up main and alt scalar ops to propagate IR flags to each | |
| // vector operation and build the lane-select mask. | |
| ValueList OpScalars, AltScalars; | |
| SmallVector<int> Mask; | |
| E->buildAltOpShuffleMask( | |
| [E, this](Instruction *I) { | |
| assert(E->getMatchingMainOpOrAltOp(I) && | |
| "Unexpected main/alternate opcode"); | |
| return isAlternateInstruction(I, E->getMainOp(), E->getAltOp(), | |
| *TLI); | |
| }, | |
| Mask, &OpScalars, &AltScalars); | |
| if (auto *VecTy = dyn_cast<FixedVectorType>(ScalarTy)) { | |
| assert(Opts.slp_revec && "FixedVectorType is not expected."); | |
| transformScalarShuffleIndiciesToVector(VecTy->getNumElements(), Mask); | |
| } | |
| if (E->hasReassocScalars() && Instruction::isBinaryOp(E->getOpcode())) { | |
| // Peeled alternate chains are linearized completely: the operand | |
| // columns fold into one pure main-opcode chain and one pure | |
| // alt-opcode chain, followed by a single lane-select shuffle. Every | |
| // lane keeps one opcode on all levels, so its value is exact in one | |
| // of the chains. | |
| setInsertPointAfterBundle(E); | |
| for (Value *V : E->getReassocScalars()) { | |
| auto *I = cast<Instruction>(V); | |
| (isAlternateInstruction(I, E->getMainOp(), E->getAltOp(), *TLI) | |
| ? AltScalars | |
| : OpScalars) | |
| .push_back(I); | |
| } | |
| auto FoldColumns = [&](unsigned Opcode, | |
| const ValueList &FlagScalars) { | |
| Value *R = nullptr; | |
| for (unsigned Idx : seq<unsigned>(E->getNumOperands())) { | |
| Value *Column = vectorizeOperand(E, Idx); | |
| if (Column->getType() != VecTy) | |
| Column = Builder.CreateIntCast(Column, VecTy, | |
| GetOperandSignedness(Idx)); | |
| if (!R) { | |
| R = Column; | |
| continue; | |
| } | |
| R = Builder.CreateBinOp( | |
| static_cast<Instruction::BinaryOps>(Opcode), R, Column); | |
| PropagateIRFlags(R, Opcode, FlagScalars); | |
| auto *I = dyn_cast<Instruction>(R); | |
| if (!I) | |
| continue; | |
| // Regrouping can invalidate flags even when each original | |
| // step was safe. | |
| I->dropPoisonGeneratingFlags(); | |
| GatherShuffleExtractSeq.insert(I); | |
| CSEBlocks.insert(I->getParent()); | |
| } | |
| return R; | |
| }; | |
| Value *V0 = FoldColumns(E->getOpcode(), OpScalars); | |
| Value *V1 = FoldColumns(E->getAltOpcode(), AltScalars); | |
| V = Builder.CreateShuffleVector(V0, V1, Mask); | |
| if (auto *I = dyn_cast<Instruction>(V)) { | |
| GatherShuffleExtractSeq.insert(I); | |
| CSEBlocks.insert(I->getParent()); | |
| } | |
| E->VectorizedValue = V; | |
| ++NumVectorInstructions; | |
| return V; | |
| } | |
| Value *LHS = nullptr, *RHS = nullptr; | |
| if (Instruction::isBinaryOp(E->getOpcode()) || isa<CmpInst>(VL0)) { | |
| setInsertPointAfterBundle(E); | |
| LHS = vectorizeOperand(E, 0); | |
| RHS = vectorizeOperand(E, 1); | |
| } else { | |
| setInsertPointAfterBundle(E); | |
| LHS = vectorizeOperand(E, 0); | |
| } | |
| if (LHS && RHS && | |
| ((Instruction::isBinaryOp(E->getOpcode()) && | |
| (LHS->getType() != VecTy || RHS->getType() != VecTy)) || | |
| (isa<CmpInst>(VL0) && LHS->getType() != RHS->getType()))) { | |
| assert((It != MinBWs.end() || | |
| getOperandEntry(E, 0)->State == TreeEntry::NeedToGather || | |
| getOperandEntry(E, 1)->State == TreeEntry::NeedToGather || | |
| MinBWs.contains(getOperandEntry(E, 0)) || | |
| MinBWs.contains(getOperandEntry(E, 1))) && | |
| "Expected item in MinBWs."); | |
| Type *CastTy = VecTy; | |
| if (isa<CmpInst>(VL0) && LHS->getType() != RHS->getType()) { | |
| if (cast<VectorType>(LHS->getType()) | |
| ->getElementType() | |
| ->getIntegerBitWidth() < cast<VectorType>(RHS->getType()) | |
| ->getElementType() | |
| ->getIntegerBitWidth()) | |
| CastTy = RHS->getType(); | |
| else | |
| CastTy = LHS->getType(); | |
| } | |
| if (LHS->getType() != CastTy) | |
| LHS = Builder.CreateIntCast(LHS, CastTy, GetOperandSignedness(0)); | |
| if (RHS->getType() != CastTy) | |
| RHS = Builder.CreateIntCast(RHS, CastTy, GetOperandSignedness(1)); | |
| } | |
| Value *V0, *V1; | |
| if (Instruction::isBinaryOp(E->getOpcode())) { | |
| V0 = Builder.CreateBinOp( | |
| static_cast<Instruction::BinaryOps>(E->getOpcode()), LHS, RHS); | |
| V1 = Builder.CreateBinOp( | |
| static_cast<Instruction::BinaryOps>(E->getAltOpcode()), LHS, RHS); | |
| } else if (auto *CI0 = dyn_cast<CmpInst>(VL0)) { | |
| V0 = Builder.CreateCmp(CI0->getPredicate(), LHS, RHS); | |
| auto *AltCI = cast<CmpInst>(E->getAltOp()); | |
| CmpInst::Predicate AltPred = AltCI->getPredicate(); | |
| V1 = Builder.CreateCmp(AltPred, LHS, RHS); | |
| } else { | |
| if (LHS->getType()->isIntOrIntVectorTy() && ScalarTy->isIntegerTy()) { | |
| unsigned SrcBWSz = DL->getTypeSizeInBits( | |
| cast<VectorType>(LHS->getType())->getElementType()); | |
| unsigned BWSz = DL->getTypeSizeInBits(ScalarTy); | |
| if (BWSz <= SrcBWSz) { | |
| if (BWSz < SrcBWSz) | |
| LHS = Builder.CreateIntCast(LHS, VecTy, It->second.first); | |
| assert(LHS->getType() == VecTy && | |
| "Expected same type as operand."); | |
| LHS = PropagateIRFlags(LHS); | |
| LHS = FinalShuffle(LHS, E); | |
| E->VectorizedValue = LHS; | |
| ++NumVectorInstructions; | |
| return LHS; | |
| } | |
| } | |
| V0 = Builder.CreateCast( | |
| static_cast<Instruction::CastOps>(E->getOpcode()), LHS, VecTy); | |
| V1 = Builder.CreateCast( | |
| static_cast<Instruction::CastOps>(E->getAltOpcode()), LHS, VecTy); | |
| } | |
| // Add V0 and V1 to later analysis to try to find and remove matching | |
| // instruction, if any. | |
| for (Value *V : {V0, V1}) { | |
| if (auto *I = dyn_cast<Instruction>(V)) { | |
| GatherShuffleExtractSeq.insert(I); | |
| CSEBlocks.insert(I->getParent()); | |
| } | |
| } | |
| PropagateIRFlags(V0, E->getOpcode(), OpScalars); | |
| PropagateIRFlags(V1, E->getAltOpcode(), AltScalars); | |
| // Create shuffle to take alternate operations from the vector. | |
| V = Builder.CreateShuffleVector(V0, V1, Mask); | |
| if (auto *I = dyn_cast<Instruction>(V)) { | |
| GatherShuffleExtractSeq.insert(I); | |
| CSEBlocks.insert(I->getParent()); | |
| } | |
| } | |
| E->VectorizedValue = V; | |
| ++NumVectorInstructions; | |
| return V; | |
| } | |
| case TreeEntry::ReducedBitcast: | |
| case TreeEntry::ReducedBitcastBSwap: { | |
| assert(UserIgnoreList && "Expected reduction operations only."); | |
| setInsertPointAfterBundle(E); | |
| TreeEntry *ZExt = getOperandEntry(E, /*Idx=*/0); | |
| ZExt->VectorizedValue = PoisonValue::get(getWidenedType( | |
| ZExt->getMainOp()->getType(), ZExt->getVectorFactor())); | |
| TreeEntry *Const = getOperandEntry(E, /*Idx=*/1); | |
| Const->VectorizedValue = PoisonValue::get(getWidenedType( | |
| Const->Scalars.front()->getType(), Const->getVectorFactor())); | |
| Value *Op = vectorizeOperand(ZExt, 0); | |
| auto *SrcType = IntegerType::get( | |
| Op->getContext(), | |
| DL->getTypeSizeInBits(cast<CastInst>(ZExt->getMainOp())->getSrcTy()) * | |
| E->getVectorFactor()); | |
| auto *OrigScalarTy = ScalarTy; | |
| // Set the scalar type properly to avoid casting to the extending type. | |
| ScalarTy = cast<CastInst>(ZExt->getMainOp())->getSrcTy(); | |
| Op = FinalShuffle(Op, E); | |
| auto *V = Builder.CreateBitCast(Op, SrcType); | |
| ++NumVectorInstructions; | |
| if (ShuffleOrOp == TreeEntry::ReducedBitcastBSwap) { | |
| V = Builder.CreateUnaryIntrinsic(Intrinsic::bswap, V); | |
| ++NumVectorInstructions; | |
| } | |
| if (SrcType != OrigScalarTy) { | |
| V = Builder.CreateIntCast(V, OrigScalarTy, /*isSigned=*/false); | |
| ++NumVectorInstructions; | |
| } | |
| E->VectorizedValue = V; | |
| return V; | |
| } | |
| case TreeEntry::ReducedBitcastLoads: | |
| case TreeEntry::ReducedBitcastBSwapLoads: { | |
| assert(UserIgnoreList && "Expected reduction operations only."); | |
| TreeEntry *ZExt = getOperandEntry(E, /*Idx=*/0); | |
| TreeEntry *Load = getOperandEntry(ZExt, /*Idx=*/0); | |
| setInsertPointAfterBundle(Load); | |
| ZExt->VectorizedValue = PoisonValue::get(getWidenedType( | |
| ZExt->getMainOp()->getType(), ZExt->getVectorFactor())); | |
| TreeEntry *Const = getOperandEntry(E, /*Idx=*/1); | |
| Const->VectorizedValue = PoisonValue::get(getWidenedType( | |
| Const->Scalars.front()->getType(), Const->getVectorFactor())); | |
| Load->VectorizedValue = PoisonValue::get(getWidenedType( | |
| Load->getMainOp()->getType(), Load->getVectorFactor())); | |
| LoadInst *LI = cast<LoadInst>(Load->getMainOp()); | |
| Value *PO = LI->getPointerOperand(); | |
| auto *SrcTy = IntegerType::get( | |
| ScalarTy->getContext(), | |
| DL->getTypeSizeInBits(cast<CastInst>(ZExt->getMainOp())->getSrcTy()) * | |
| E->getVectorFactor()); | |
| auto *OrigScalarTy = ScalarTy; | |
| ScalarTy = ZExt->getMainOp()->getType(); | |
| Value *V = Builder.CreateAlignedLoad(SrcTy, PO, LI->getAlign()); | |
| ++NumVectorInstructions; | |
| if (ShuffleOrOp == TreeEntry::ReducedBitcastBSwapLoads) { | |
| V = Builder.CreateUnaryIntrinsic(Intrinsic::bswap, V); | |
| ++NumVectorInstructions; | |
| } | |
| if (SrcTy != OrigScalarTy) { | |
| V = Builder.CreateIntCast(V, OrigScalarTy, /*isSigned=*/false); | |
| ++NumVectorInstructions; | |
| } | |
| E->VectorizedValue = V; | |
| return V; | |
| } | |
| case TreeEntry::ReducedBitPack: { | |
| assert(UserIgnoreList && "Expected reduction operations only."); | |
| setInsertPointAfterBundle(E); | |
| TreeEntry *ShlTE = E->getOpcode() == Instruction::And | |
| ? getOperandEntry(E, /*Idx=*/0) | |
| : E; | |
| std::optional<BitPackInfo> Info = computeBitPackLayout(*E); | |
| assert(Info && "Expected bit pack info."); | |
| TreeEntry *LhsTE = getOperandEntry(ShlTE, /*Idx=*/0); | |
| const TreeEntry *SrcTE = getBitPackSource(*LhsTE, *Info); | |
| SmallVector<TreeEntry *> CombinedTEs; | |
| if (ShlTE != E) | |
| CombinedTEs.append({ShlTE, getOperandEntry(E, /*Idx=*/1)}); | |
| CombinedTEs.push_back(getOperandEntry(ShlTE, /*Idx=*/1)); | |
| TreeEntry *OpTE = LhsTE; | |
| for (; OpTE != SrcTE; OpTE = getOperandEntry(OpTE, /*Idx=*/0)) { | |
| CombinedTEs.push_back(OpTE); | |
| for (unsigned Idx : seq<unsigned>(1, OpTE->getNumOperands())) | |
| CombinedTEs.push_back(getOperandEntry(OpTE, Idx)); | |
| } | |
| for (TreeEntry *TE : CombinedTEs) | |
| TE->VectorizedValue = PoisonValue::get(getWidenedType( | |
| TE->Scalars.front()->getType(), TE->getVectorFactor())); | |
| Value *X = vectorizeTree(OpTE); | |
| unsigned ShiftWidth; | |
| Type *DstScalarTy = | |
| SrcTE == LhsTE ? X->getType()->getScalarType() : ScalarTy; | |
| [[maybe_unused]] InstructionCost PackCost = getBitPackCost( | |
| *TTI, cast<FixedVectorType>(X->getType()), DstScalarTy, *Info, | |
| getZExtSrcWidth(*SrcTE), getCastContextHint(*SrcTE), CostKind, TLI, | |
| E->getMainOp(), ShiftWidth); | |
| assert(PackCost.isValid() && "Expected valid cost."); | |
| unsigned NumInsts; | |
| Value *V = | |
| buildBitPack(Builder, X, *Info, DstScalarTy, ShiftWidth, NumInsts); | |
| NumVectorInstructions += NumInsts; | |
| E->VectorizedValue = V; | |
| return V; | |
| } | |
| case TreeEntry::ReducedCmpBitcast: { | |
| assert(UserIgnoreList && "Expected reduction operations only."); | |
| setInsertPointAfterBundle(E); | |
| TreeEntry *Op1TE = getOperandEntry(E, /*Idx=*/1); | |
| TreeEntry *Op2TE = getOperandEntry(E, /*Idx=*/2); | |
| Op1TE->VectorizedValue = | |
| PoisonValue::get(getWidenedType(ScalarTy, Op1TE->getVectorFactor())); | |
| Op2TE->VectorizedValue = | |
| PoisonValue::get(getWidenedType(ScalarTy, Op2TE->getVectorFactor())); | |
| Value *Cmp = vectorizeOperand(E, /*NodeIdx=*/0); | |
| // Set the scalar type properly to avoid casting to the extending type. | |
| auto *DstTy = | |
| IntegerType::getIntNTy(ScalarTy->getContext(), E->getVectorFactor()); | |
| auto *V = Builder.CreateBitCast(Cmp, DstTy); | |
| ++NumVectorInstructions; | |
| if (DstTy != ScalarTy) { | |
| V = Builder.CreateIntCast(V, ScalarTy, /*isSigned=*/false); | |
| ++NumVectorInstructions; | |
| } | |
| E->VectorizedValue = V; | |
| return V; | |
| } | |
| default: | |
| llvm_unreachable("unknown inst"); | |
| } | |
| return nullptr; | |
| } | |
| bool BoUpSLP::isRuntimeCheckableAliasPair(Instruction *Inst1, | |
| Instruction *Inst2) { | |
| // Only simple (non-volatile, non-atomic) accesses may be reordered once | |
| // aliasing is ruled out at runtime; volatile/atomic ordering must be kept. | |
| if (!isSimple(Inst1) || !isSimple(Inst2)) | |
| return false; | |
| Value *Ptr1 = getLoadStorePointerOperand(Inst1); | |
| Value *Ptr2 = getLoadStorePointerOperand(Inst2); | |
| // Only simple load/store accesses have a single pointer operand whose | |
| // accessed range can be bounded and compared at runtime. | |
| if (!Ptr1 || !Ptr2) | |
| return false; | |
| if (Ptr1->getType()->getPointerAddressSpace() != | |
| Ptr2->getType()->getPointerAddressSpace()) | |
| return false; | |
| const Value *Base1 = getUnderlyingObject(Ptr1); | |
| const Value *Base2 = getUnderlyingObject(Ptr2); | |
| // A runtime range check is only meaningful between two distinct, identifiable | |
| // objects: two accesses to the same base differ by a compile-time offset, so | |
| // they are resolved statically and a base-range check would fold to a | |
| // constant predicate rather than a useful runtime guard. | |
| if (Base1 == Base2 || isa<UndefValue>(Base1) || isa<UndefValue>(Base2)) | |
| return false; | |
| return true; | |
| } | |
| bool BoUpSLP::isCoveredByExistingVersionCheck(BasicBlock *BB, | |
| Instruction *Inst1, | |
| Instruction *Inst2) const { | |
| const auto It = VersionedBlockCheckedPairs.find(BB); | |
| if (It == VersionedBlockCheckedPairs.end()) | |
| return false; | |
| Value *Ptr1 = getLoadStorePointerOperand(Inst1); | |
| Value *Ptr2 = getLoadStorePointerOperand(Inst2); | |
| if (!Ptr1 || !Ptr2) | |
| return false; | |
| const Value *Base1 = getUnderlyingObject(Ptr1); | |
| const Value *Base2 = getUnderlyingObject(Ptr2); | |
| if (Base1 == Base2) | |
| return false; | |
| // The recorded pairs keep their discovery order (see | |
| // recordRuntimeAliasCheck), so accept either orientation. | |
| return It->second.contains({Base1, Base2}) || | |
| It->second.contains({Base2, Base1}); | |
| } | |
| /// Returns true if \p BB's body already contains vector instructions, e.g. | |
| /// from an earlier SLP vectorization in the same pass. | |
| static bool blockBodyHasVectorInstructions(const VectorizeOptions &Opts, | |
| BasicBlock *BB) { | |
| // A vector-producing instruction (vector load, binop, shuffle, etc.) has a | |
| // vector result type. | |
| return any_of(*BB, [&](Instruction &I) { | |
| return !isa<PHINode>(&I) && !I.isTerminator() && | |
| getValueType(&I, Opts.slp_revec)->isVectorTy(); | |
| }); | |
| } | |
| void BoUpSLP::captureRuntimeCheckBodySnapshot() { | |
| RTOrigBodyOrder.clear(); | |
| if (!TryRuntimeAliasChecks || !RTChecks.BB) | |
| return; | |
| BasicBlock *BB = RTChecks.BB; | |
| // Allocas are not duplicated, keep them in the header block. | |
| for (Instruction &I : *BB) | |
| if (!isa<PHINode, AllocaInst>(&I) && !I.isTerminator()) | |
| RTOrigBodyOrder.push_back(&I); | |
| } | |
| bool BoUpSLP::recordRuntimeAliasCheck(BasicBlock *BB, Instruction *Inst1, | |
| Instruction *Inst2) { | |
| Value *Ptr1 = getLoadStorePointerOperand(Inst1); | |
| Value *Ptr2 = getLoadStorePointerOperand(Inst2); | |
| if (!Ptr1 || !Ptr2) | |
| return false; | |
| const Value *Base1 = getUnderlyingObject(Ptr1); | |
| const Value *Base2 = getUnderlyingObject(Ptr2); | |
| if (Base1 == Base2) | |
| return false; | |
| // Only a single block can be versioned per attempt. | |
| if (RTChecks.BB && RTChecks.BB != BB) | |
| return false; | |
| // A base-object pair is unordered: (Base1, Base2) and (Base2, Base1) are the | |
| // same check. Keep it in discovery (program) order and drop the reverse as a | |
| // duplicate, rather than ordering by pointer value, which would make the | |
| // emitted checks depend on allocation addresses. | |
| auto Pair = std::make_pair(Base1, Base2); | |
| bool Present = RTChecks.BasePairs.contains(Pair) || | |
| RTChecks.BasePairs.contains({Base2, Base1}); | |
| // After the checks have been validated and bounded, do not introduce new | |
| // pairs. | |
| if (RTChecksFinalized) | |
| return Present; | |
| RTChecks.BB = BB; | |
| if (!Present) | |
| RTChecks.BasePairs.insert(Pair); | |
| return true; | |
| } | |
| bool BoUpSLP::canVersionBlockForRuntimeChecks(BasicBlock *BB) const { | |
| assert(BB && "Expected a block to version for runtime checks."); | |
| // Versioning duplicates the block body, increasing code size on the guarded | |
| // path; do not version when the function is optimized for size (-Os/-Oz). | |
| if (F->hasOptSize()) | |
| return false; | |
| if (!DT || !LI) | |
| return false; | |
| if (!DT->isReachableFromEntry(BB)) | |
| return false; | |
| // Never version a scalar fallback block: it is the safe, original-order copy | |
| // taken when aliasing is detected and must stay scalar. | |
| if (ScalarFallbackBlocks.contains(BB)) | |
| return false; | |
| // The scalar fallback must be a faithful copy of the original scalar body. | |
| // Reject blocks that were already partially vectorized earlier in this pass. | |
| if (blockBodyHasVectorInstructions(Opts, BB)) | |
| return false; | |
| // Versioning duplicates the original block body into a scalar fallback. | |
| // Calls that cannot be duplicated or whose semantics depend on the call being | |
| // immediately followed by a return cannot be cloned into the diamond. | |
| if (BB->getTerminatingMustTailCall()) | |
| return false; | |
| if (any_of(*BB, [](Instruction &I) { | |
| auto *CB = dyn_cast<CallBase>(&I); | |
| return CB && (CB->cannotDuplicate() || CB->isConvergent()); | |
| })) | |
| return false; | |
| // Only static allocas at the start of the entry block can stay in the header | |
| // block, any other alloca would have to be duplicated with the body. | |
| if (any_of(make_range(BB->getFirstNonPHIOrAlloca(), BB->end()), | |
| IsaPred<AllocaInst>)) | |
| return false; | |
| return true; | |
| } | |
| bool BoUpSLP::canVersionForRuntimeChecks() { | |
| if (RTChecks.BasePairs.empty() || !RTChecks.BB) | |
| return false; | |
| if (RTChecks.BasePairs.size() > Opts.slp_max_runtime_alias_checks) | |
| return false; | |
| BasicBlock *BB = RTChecks.BB; | |
| // Block-level preconditions (loop/optsize/duplicability/...) are the same | |
| // ones used to gate the optimistic retry, so reuse them here. | |
| if (!canVersionBlockForRuntimeChecks(BB)) | |
| return false; | |
| // The scalar fallback is a clone of the body. Operands defined inside the | |
| // body are remapped to their clones, but operands defined outside the body | |
| // (header PHIs, dominating definitions) are reused as-is by the clone. If | |
| // such an outside operand is itself vectorized by this tree, vectorizeTree() | |
| // will delete its scalar, leaving the clone with a dangling, out-of-tree use. | |
| // Versioning cannot model that, so bail out. | |
| for (Instruction &I : make_filter_range(*BB, [](Instruction &I) { | |
| return !isa<PHINode>(&I) && !I.isTerminator(); | |
| })) { | |
| for (Instruction *OpI : make_isa_range<Instruction>(I.operands())) { | |
| bool DefinedInBody = | |
| OpI->getParent() == BB && !isa<PHINode>(OpI) && !OpI->isTerminator(); | |
| if (!DefinedInBody && isVectorized(OpI)) | |
| return false; | |
| } | |
| } | |
| SmallPtrSet<const Value *, 8> Bases; | |
| for (const auto &P : RTChecks.BasePairs) { | |
| Bases.insert(P.first); | |
| Bases.insert(P.second); | |
| } | |
| // Every base object must be available in the (PHI-only) header where the | |
| // guard branch is emitted, i.e. be one of its PHIs or dominate it. | |
| if (any_of(make_isa_range<Instruction>(Bases), [&](const Instruction *I) { | |
| return I->getParent() == BB ? !isa<PHINode>(I) : !DT->dominates(I, BB); | |
| })) | |
| return false; | |
| // Compute the accessed address range [Low, High) for every involved base. | |
| RTChecks.Bounds.clear(); | |
| SmallMapVector<Value *, std::pair<const SCEV *, const SCEV *>, 4> OffBounds; | |
| unsigned NumMemInsts = 0; | |
| for (Instruction &I : make_filter_range( | |
| *BB, [](Instruction &I) { return I.mayReadOrWriteMemory(); })) { | |
| // The dependency scan truncates beyond MaxMemDepDistance. Only version | |
| // blocks small enough that the scan is never truncated, so every | |
| // conflicting base pair is guaranteed to be recorded. | |
| if (++NumMemInsts > MaxMemDepDistance) | |
| return false; | |
| Value *Ptr = getLoadStorePointerOperand(&I); | |
| // A non-simple memory access (e.g. a call) keeps its dependencies, so its | |
| // relative order with kept-dependency partners is preserved inside the | |
| // duplicated body and it does not need a bound. | |
| if (!Ptr) | |
| continue; | |
| Value *Base = const_cast<Value *>(getUnderlyingObject(Ptr)); | |
| if (!Bases.contains(Base)) | |
| continue; | |
| TypeSize TS = DL->getTypeStoreSize(getLoadStoreType(&I)); | |
| if (TS.isScalable()) | |
| return false; | |
| Type *IntTy = DL->getAddressType(Base->getType()); | |
| // All bounds are computed and compared in a single address type, so an | |
| // access that reaches its base object through an addrspacecast between | |
| // differently-sized address spaces cannot be bounded and the block | |
| // cannot be versioned. | |
| if (DL->getAddressType(Ptr->getType()) != IntTy) | |
| return false; | |
| const SCEV *PtrSC = SE->getPtrToAddrExpr(SE->getSCEV(Ptr)); | |
| const SCEV *BaseSC = SE->getPtrToAddrExpr(SE->getSCEV(Base)); | |
| if (isa<SCEVCouldNotCompute>(PtrSC) || isa<SCEVCouldNotCompute>(BaseSC)) | |
| return false; | |
| // Byte offset of this access from its base. Require a constant so the bound | |
| // stays base-plus-constant, and non-negative so the unsigned [Low, High) | |
| // range below actually covers the access. | |
| // TODO: Support variable offsets (e.g. base + %idx) which would need | |
| // runtime umin/umax bounds and a proof that the offset range cannot wrap, | |
| // plus a richer cost model. | |
| const auto *Off = dyn_cast<SCEVConstant>(SE->getMinusSCEV(PtrSC, BaseSC)); | |
| if (!Off || Off->getAPInt().isNegative()) | |
| return false; | |
| const SCEV *EndOff = | |
| SE->getAddExpr(Off, SE->getConstant(IntTy, TS.getFixedValue())); | |
| auto [It, Inserted] = OffBounds.try_emplace(Base, Off, EndOff); | |
| if (!Inserted) { | |
| // Constant operands, so these fold to a constant min/max offset rather | |
| // than emitting a runtime umin/umax. | |
| It->second.first = SE->getUMinExpr(It->second.first, Off); | |
| It->second.second = SE->getUMaxExpr(It->second.second, EndOff); | |
| } | |
| } | |
| // Every involved base must contribute at least one bounded access. | |
| if (OffBounds.size() != Bases.size()) | |
| return false; | |
| // Materialize the absolute [Low, High) = base + [minOff, maxEndOff). The | |
| // offsets are constants, so each bound is a base-plus-constant expression | |
| // that expands to a single add off the base address (no runtime umin/umax). | |
| // The base is used as an opaque value, so the bounds only reference the base | |
| // itself, available at the guard, and a loop-variant base (e.g. a header PHI) | |
| // is reused rather than recomputed from its recurrence. | |
| for (const auto &[Base, Off] : OffBounds) { | |
| const SCEV *BaseSC = SE->getPtrToAddrExpr(SE->getUnknown(Base)); | |
| RTChecks.Bounds.try_emplace(Base, SE->getAddExpr(BaseSC, Off.first), | |
| SE->getAddExpr(BaseSC, Off.second)); | |
| } | |
| // SSA values defined in the body and used outside of it are merged from both | |
| // paths in the continuation block, so they must stay scalar. Uses by the | |
| // terminator (moved into the continuation block) and by the block's PHIs | |
| // (via the loop latch) count as outside. | |
| for (Instruction &I : make_filter_range(*BB, [&](Instruction &I) { | |
| return !isa<PHINode>(&I) && !I.isTerminator() && isVectorized(&I); | |
| })) { | |
| if (any_of(I.users(), [&](User *U) { | |
| auto *UI = dyn_cast<Instruction>(U); | |
| return !UI || UI->getParent() != BB || isa<PHINode>(UI) || | |
| UI->isTerminator(); | |
| })) | |
| return false; | |
| } | |
| // The runtime checks and the guard branch run on both the vector and the | |
| // scalar fallback path. When aliasing is detected at runtime the scalar code | |
| // executes unchanged plus the check overhead, so bound the check cost | |
| // relative to the guarded scalar region to avoid pessimizing that path too | |
| // much. The scalar region cost is the cost of the (current, still scalar) | |
| // block body; no IR is emitted here. | |
| InstructionCost ScalarCost = 0; | |
| for (Instruction &I : make_filter_range(*BB, [](Instruction &I) { | |
| return !isa<PHINode>(&I) && !I.isTerminator(); | |
| })) { | |
| // The backend fuses the fmul into its fadd/fsub user, so the pair costs as | |
| // the fmuladd. | |
| auto [U, FMACost] = getFMulFusingUser(&I, /*InTree=*/false); | |
| ScalarCost += U && U->getParent() == BB | |
| ? FMACost - TTI->getInstructionCost(U, CostKind) | |
| : TTI->getInstructionCost(&I, CostKind); | |
| } | |
| InstructionCost CheckCost = getRuntimeChecksCost(); | |
| if (!ScalarCost.isValid() || !CheckCost.isValid()) | |
| return false; | |
| const Loop *L = LI->getLoopFor(BB); | |
| unsigned MaxPercent = | |
| L && getBooleanLoopAttribute(L, "llvm.loop.isvectorized") | |
| ? Opts.slp_vec_loop_rt_checks_cost_percent | |
| : Opts.slp_runtime_alias_checks_max_scalar_cost_percent; | |
| if (CheckCost * 100 > ScalarCost * MaxPercent) | |
| return false; | |
| // Freeze the check set so the final scheduleBlock() keeps the same dropped | |
| // dependencies the bounds were just computed for. | |
| RTChecksFinalized = true; | |
| return true; | |
| } | |
| InstructionCost BoUpSLP::getRuntimeChecksCost() const { | |
| if (RTChecks.BasePairs.empty()) | |
| return 0; | |
| LLVMContext &Ctx = F->getContext(); | |
| Type *IntTy = DL->getIntPtrType(Ctx); | |
| Type *I1Ty = Type::getInt1Ty(Ctx); | |
| unsigned NumBases = RTChecks.Bounds.size(); | |
| unsigned NumPairs = RTChecks.BasePairs.size(); | |
| InstructionCost Cost = 0; | |
| // Per base: a [Low, High) address pair. canVersionForRuntimeChecks() folds | |
| // each bound to a base-plus-constant offset, so it expands to two integer | |
| // adds (one per bound) with no runtime umin/umax reduction. | |
| Cost += TTI->getArithmeticInstrCost(Instruction::Add, IntTy, CostKind) * | |
| (2 * NumBases); | |
| // Two integer compares and one logical and per checked pair. | |
| InstructionCost CmpCost = TTI->getCmpSelInstrCost( | |
| Instruction::ICmp, IntTy, I1Ty, CmpInst::ICMP_ULT, CostKind); | |
| InstructionCost AndCost = | |
| TTI->getArithmeticInstrCost(Instruction::And, I1Ty, CostKind); | |
| Cost += (CmpCost * 2 + AndCost) * NumPairs; | |
| // Or-reduction of the per-pair conflicts. | |
| if (NumPairs > 1) | |
| Cost += TTI->getArithmeticInstrCost(Instruction::Or, I1Ty, CostKind) * | |
| (NumPairs - 1); | |
| // The guard branch. | |
| Cost += TTI->getCFInstrCost(Instruction::CondBr, CostKind); | |
| return Cost; | |
| } | |
| Value *BoUpSLP::emitRuntimeAliasCheck(IRBuilderBase &Builder, | |
| SCEVExpander &Exp) { | |
| // Materialize the [Low, High) address bounds for every involved base from | |
| // their SCEVs at the builder's insertion point. | |
| SmallDenseMap<const Value *, std::pair<Value *, Value *>, 4> Range; | |
| for (const auto &[Base, Bound] : RTChecks.Bounds) { | |
| Type *IntTy = DL->getAddressType(Base->getType()); | |
| Value *Low = | |
| Exp.expandCodeFor(Bound.first, IntTy, Builder.GetInsertPoint()); | |
| Value *High = | |
| Exp.expandCodeFor(Bound.second, IntTy, Builder.GetInsertPoint()); | |
| Range[Base] = {Low, High}; | |
| } | |
| Value *Cond = nullptr; | |
| for (const auto &P : RTChecks.BasePairs) { | |
| std::pair<Value *, Value *> A = Range.lookup(P.first); | |
| std::pair<Value *, Value *> B = Range.lookup(P.second); | |
| assert(A.first && A.second && B.first && B.second && | |
| "Missing bounds for a checked base object"); | |
| // The objects overlap iff (A.Low < B.High) && (B.Low < A.High). | |
| Value *C0 = Builder.CreateICmpULT(A.first, B.second, "rt.bound0"); | |
| Value *C1 = Builder.CreateICmpULT(B.first, A.second, "rt.bound1"); | |
| Value *Conflict = Builder.CreateAnd(C0, C1, "rt.conflict"); | |
| Cond = | |
| Cond ? Builder.CreateOr(Cond, Conflict, "rt.conflict.all") : Conflict; | |
| } | |
| return Builder.CreateFreeze(Cond, "rt.guard"); | |
| } | |
| void BoUpSLP::versionBlocksForRuntimeChecks() { | |
| // Only version when this is the optimistic attempt that collected the checks. | |
| if (!TryRuntimeAliasChecks || RTChecks.BasePairs.empty() || !RTChecks.BB || | |
| RTOrigBodyOrder.empty()) | |
| return; | |
| BasicBlock *BB = RTChecks.BB; | |
| Function *Fn = BB->getParent(); | |
| LLVMContext &Ctx = BB->getContext(); | |
| Instruction *Term = BB->getTerminator(); | |
| // Build the diamond: | |
| // BB (header: PHIs + guard) -> {VecBB, ScalarBB} -> Tail (terminator). | |
| BasicBlock *VecBB = BasicBlock::Create(Ctx, BB->getName() + ".rtvec", Fn); | |
| BasicBlock *ScalarBB = | |
| BasicBlock::Create(Ctx, BB->getName() + ".rtscalar", Fn); | |
| BasicBlock *Tail = BasicBlock::Create(Ctx, BB->getName() + ".rtcont", Fn); | |
| // Emit the runtime alias check while the block is still fully connected: | |
| // LCSSA-preserving SCEV expansion rewrites out-of-loop uses of | |
| // loop-defined bases to poison in predecessor-less blocks. Expand at the | |
| // first insertion point, where no body instruction dominates, so the check | |
| // cannot reuse scalars that are moved into the vector block and deleted. | |
| IRBuilder<> ChkBuilder(BB->getFirstInsertionPt()); | |
| ChkBuilder.SetCurrentDebugLocation(Term->getDebugLoc()); | |
| SCEVExpander Exp(*SE, "slp.rtcheck"); | |
| Value *Cond = emitRuntimeAliasCheck(ChkBuilder, Exp); | |
| // Clone the body into the scalar fallback block, in the original program | |
| // order (RTOrigBodyOrder). | |
| SmallDenseMap<Value *, Value *, 16> VMap; | |
| for (Instruction *I : RTOrigBodyOrder) { | |
| assert(I->getParent() == BB && | |
| "RTOrigBodyOrder entry no longer in the versioned block"); | |
| Instruction *C = I->clone(); | |
| if (I->hasName()) | |
| C->setName(I->getName() + ".scalar"); | |
| C->insertInto(ScalarBB, ScalarBB->end()); | |
| VMap[I] = C; | |
| } | |
| // Remap the clones to use the cloned definitions; references to values | |
| // defined outside the body (header or function arguments) are unchanged. | |
| for (Instruction &I : *ScalarBB) | |
| for (Use &U : I.operands()) | |
| if (Value *V = VMap.lookup(U.get())) | |
| U.set(V); | |
| // Move the original body into the vector block in the current (scheduled) | |
| // order, keeping the emitted check instructions in the header block. | |
| SmallPtrSet<Instruction *, 16> BodyInsts(llvm::from_range, RTOrigBodyOrder); | |
| for (Instruction &I : make_early_inc_range(*BB)) | |
| if (BodyInsts.contains(&I)) | |
| I.moveBefore(*VecBB, VecBB->end()); | |
| // Move the original terminator into the continuation block and replace it | |
| // with the guard branch. | |
| Term->removeFromParent(); | |
| Term->insertInto(Tail, Tail->end()); | |
| UncondBrInst::Create(Tail, VecBB); | |
| UncondBrInst::Create(Tail, ScalarBB); | |
| ChkBuilder.SetInsertPoint(BB); | |
| CondBrInst *Guard = ChkBuilder.CreateCondBr(Cond, ScalarBB, VecBB); | |
| // The scalar fallback is taken only when the runtime alias check detects a | |
| // conflict, which is expected to be rare, so bias the guard towards the | |
| // vector path. | |
| if (!ProfcheckDisableMetadataFixes) | |
| setBranchWeights( | |
| *Guard, | |
| {MDBuilder::kUnlikelyBranchWeight, MDBuilder::kLikelyBranchWeight}, | |
| /*IsExpected=*/false); | |
| // The continuation block is the new predecessor of the original successors. | |
| for (BasicBlock *Succ : successors(Term)) | |
| Succ->replacePhiUsesWith(BB, Tail); | |
| // Merge the body values used outside of the versioned region (by the | |
| // terminator, across the loop latch or in other blocks) from both paths. The | |
| // SCEVs cached for the outside users refer to the body values, which no | |
| // longer dominate them. | |
| for (Instruction *I : RTOrigBodyOrder) { | |
| if (!I->isUsedOutsideOfBlock(VecBB)) | |
| continue; | |
| SE->forgetValue(I); | |
| PHINode *Merge = PHINode::Create(I->getType(), 2, I->getName() + ".rtmerge", | |
| Tail->getFirstNonPHIIt()); | |
| I->replaceUsesOutsideBlock(Merge, VecBB); | |
| Merge->addIncoming(I, VecBB); | |
| Merge->addIncoming(VMap.lookup(I), ScalarBB); | |
| } | |
| // Keep the dominator tree valid for the remainder of the run. | |
| if (DT) { | |
| DomTreeUpdater DTU(*DT, DomTreeUpdater::UpdateStrategy::Eager); | |
| SmallVector<DominatorTree::UpdateType> Updates = { | |
| {DominatorTree::Insert, BB, VecBB}, | |
| {DominatorTree::Insert, BB, ScalarBB}, | |
| {DominatorTree::Insert, VecBB, Tail}, | |
| {DominatorTree::Insert, ScalarBB, Tail}}; | |
| // Term (e.g. a switch) may have multiple edges to the same successor; | |
| // dedupe so each distinct successor gets exactly one Insert/Delete pair. | |
| SmallPtrSet<BasicBlock *, 4> UniqueSuccs; | |
| for (BasicBlock *Succ : successors(Term)) { | |
| if (!UniqueSuccs.insert(Succ).second) | |
| continue; | |
| Updates.push_back({DominatorTree::Insert, Tail, Succ}); | |
| Updates.push_back({DominatorTree::Delete, BB, Succ}); | |
| } | |
| DTU.applyUpdates(Updates); | |
| // Refresh DFS numbers: getLastInstructionInBundle() may use them to order | |
| // instructions across blocks for nodes emitted after versioning. | |
| DT->updateDFSNumbers(); | |
| } | |
| // Keep the loop info valid for the remainder of the run: the new blocks | |
| // belong to the loop of the versioned block. | |
| if (Loop *L = LI->getLoopFor(BB)) | |
| for (BasicBlock *NewBB : {VecBB, ScalarBB, Tail}) | |
| L->addBasicBlockToLoop(NewBB, *LI); | |
| // Record the checked base pairs for the fast-path block so that subsequent | |
| // vectorization there can reuse this guard instead of versioning again. | |
| SmallDenseSet<std::pair<const Value *, const Value *>, 4> &Covered = | |
| VersionedBlockCheckedPairs[VecBB]; | |
| Covered.insert_range(RTChecks.BasePairs); | |
| // The scalar fallback must never be vectorized or versioned again. | |
| // TODO: check that scalar fallback does not miss vectorization opportunity | |
| // without runtime checks. | |
| ScalarFallbackBlocks.insert(ScalarBB); | |
| // The checks have been emitted; clear so any later vectorizeTree() in the | |
| // same attempt does not version again. | |
| RTChecks.clear(); | |
| RTChecksFinalized = false; | |
| RTOrigBodyOrder.clear(); | |
| // The CFG was modified; CFG analyses can no longer be preserved. | |
| CFGChanged = true; | |
| } | |
| Value *BoUpSLP::vectorizeTree() { | |
| ExtraValueToDebugLocsMap ExternallyUsedValues; | |
| return vectorizeTree(ExternallyUsedValues); | |
| } | |
| Value * | |
| BoUpSLP::vectorizeTree(const ExtraValueToDebugLocsMap &ExternallyUsedValues, | |
| Instruction *ReductionRoot, | |
| ArrayRef<ReductionVectorPart> VectorValuesAndScales) { | |
| // Clean Entry-to-LastInstruction table. It can be affected after scheduling, | |
| // need to rebuild it. | |
| EntryToLastInstruction.clear(); | |
| // All blocks must be scheduled before any instructions are inserted. | |
| for (auto &BSIter : BlocksSchedules) | |
| scheduleBlock(*this, BSIter.second.get()); | |
| // Cache last instructions for the nodes to avoid side effects, which may | |
| // appear during vectorization, like extra uses, etc. | |
| for (const std::unique_ptr<TreeEntry> &TE : VectorizableTree) { | |
| // Need to generate insertion point for loads nodes of the bitcast/bswap | |
| // ops. | |
| if (TE->isGather() || DeletedNodes.contains(TE.get()) || | |
| TE->State == TreeEntry::SplitVectorize || | |
| (TE->State == TreeEntry::CombinedVectorize && | |
| (TE->CombinedOp == TreeEntry::ReducedBitcast || | |
| TE->CombinedOp == TreeEntry::ReducedBitcastBSwap || | |
| TE->CombinedOp == TreeEntry::ReducedBitPack || | |
| ((TE->CombinedOp == TreeEntry::ReducedBitcastLoads || | |
| TE->CombinedOp == TreeEntry::ReducedBitcastBSwapLoads || | |
| TE->CombinedOp == TreeEntry::ReducedCmpBitcast) && | |
| (!TE->hasState() || TE->getOpcode() != Instruction::Load))))) | |
| continue; | |
| (void)getLastInstructionInBundle(TE.get()); | |
| } | |
| // If this tree was scheduled by dropping may-alias memory dependencies in | |
| // favor of runtime alias checks, materialize the checks and version the | |
| // affected block before emitting the vector code. Scheduling has already | |
| // run, so moving the body into the fast-path block is safe, and the | |
| // dominator tree is updated for the rest of the run. | |
| versionBlocksForRuntimeChecks(); | |
| if (ReductionRoot) | |
| Builder.SetInsertPoint(ReductionRoot->getIterator()); | |
| else | |
| Builder.SetInsertPoint(F->getEntryBlock().begin()); | |
| // Vectorize gather operands of the nodes with the external uses only. | |
| SmallVector<std::pair<TreeEntry *, Instruction *>> GatherEntries; | |
| // Multiple gather TEs may share the same UserTE - cache the per-UserTE | |
| // all_of-isUsedOutsideBlock result to avoid re-walking each scalar's | |
| // user list. | |
| SmallDenseMap<const TreeEntry *, bool> UserTEScalarsUsedOutsideBlockCache; | |
| for (const std::unique_ptr<TreeEntry> &TE : make_filter_range( | |
| VectorizableTree, [&](const std::unique_ptr<TreeEntry> &TE) { | |
| return !DeletedNodes.contains(TE.get()); | |
| })) { | |
| if (TE->isGather() && !TE->VectorizedValue && TE->UserTreeIndex.UserTE && | |
| TE->UserTreeIndex.UserTE->hasState() && | |
| TE->UserTreeIndex.UserTE->State == TreeEntry::Vectorize && | |
| (TE->UserTreeIndex.UserTE->getOpcode() != Instruction::PHI || | |
| TE->UserTreeIndex.UserTE->isAltShuffle()) && | |
| !TE->UserTreeIndex.UserTE->hasCopyableElements()) { | |
| const TreeEntry *UserTE = TE->UserTreeIndex.UserTE; | |
| auto [It, Inserted] = | |
| UserTEScalarsUsedOutsideBlockCache.try_emplace(UserTE); | |
| if (Inserted) | |
| It->second = all_of(UserTE->Scalars, | |
| [](Value *V) { return isUsedOutsideBlock(V); }); | |
| if (!It->second) | |
| continue; | |
| Instruction &LastInst = getLastInstructionInBundle(UserTE); | |
| GatherEntries.emplace_back(TE.get(), &LastInst); | |
| } | |
| } | |
| for (auto &Entry : GatherEntries) { | |
| IRBuilderBase::InsertPointGuard Guard(Builder); | |
| Builder.SetInsertPoint(Entry.second); | |
| Builder.SetCurrentDebugLocation(Entry.second->getDebugLoc()); | |
| (void)vectorizeTree(Entry.first); | |
| } | |
| // Emit the subtrees built for the splat gather nodes' unique scalars, so | |
| // the splat gathers can be emitted as their broadcasts. They go before the | |
| // gathered loads, which skip entries that already have a vector value. | |
| for (TreeEntry *TE : | |
| make_filter_range(SplatGatheredScalarsRoots, [&](TreeEntry *TE) { | |
| return !DeletedNodes.contains(TE) && !TE->VectorizedValue; | |
| })) | |
| (void)vectorizeTree(TE); | |
| // Emit gathered loads first to emit better code for the users of those | |
| // gathered loads. | |
| for (const std::unique_ptr<TreeEntry> &TE : make_filter_range( | |
| VectorizableTree, [&](const std::unique_ptr<TreeEntry> &TE) { | |
| return !DeletedNodes.contains(TE.get()); | |
| })) { | |
| if (GatheredLoadsEntriesFirst.has_value() && | |
| TE->Idx >= *GatheredLoadsEntriesFirst && !TE->VectorizedValue && | |
| (!TE->isGather() || TE->UserTreeIndex)) { | |
| assert((TE->UserTreeIndex || | |
| (TE->getOpcode() == Instruction::Load && !TE->isGather())) && | |
| "Expected gathered load node."); | |
| (void)vectorizeTree(TE.get()); | |
| } | |
| } | |
| (void)vectorizeTree(&getRootNode()); | |
| // Run through the list of postponed gathers and emit them, replacing the temp | |
| // emitted allocas with actual vector instructions. | |
| ArrayRef<const TreeEntry *> PostponedNodes = PostponedGathers.getArrayRef(); | |
| DenseMap<Value *, SmallVector<TreeEntry *>> PostponedValues; | |
| for (const TreeEntry *E : PostponedNodes) { | |
| auto *TE = const_cast<TreeEntry *>(E); | |
| auto *PrevVec = cast<Instruction>(TE->VectorizedValue); | |
| TE->VectorizedValue = nullptr; | |
| auto *UserI = cast<Instruction>(TE->UserTreeIndex.UserTE->VectorizedValue); | |
| // If user is a PHI node, its vector code have to be inserted right before | |
| // block terminator. Since the node was delayed, there were some unresolved | |
| // dependencies at the moment when stab instruction was emitted. In a case | |
| // when any of these dependencies turn out an operand of another PHI, coming | |
| // from this same block, position of a stab instruction will become invalid. | |
| // The is because source vector that supposed to feed this gather node was | |
| // inserted at the end of the block [after stab instruction]. So we need | |
| // to adjust insertion point again to the end of block. | |
| if (isa<PHINode>(UserI) || | |
| (TE->UserTreeIndex.UserTE->hasState() && | |
| TE->UserTreeIndex.UserTE->State != TreeEntry::SplitVectorize && | |
| TE->UserTreeIndex.UserTE->getOpcode() == Instruction::PHI)) { | |
| // Insert before all users. | |
| Instruction *InsertPt = PrevVec->getParent()->getTerminator(); | |
| for (User *U : PrevVec->users()) { | |
| if (U == UserI) | |
| continue; | |
| auto *UI = dyn_cast<Instruction>(U); | |
| if (!UI || isa<PHINode>(UI) || UI->getParent() != InsertPt->getParent()) | |
| continue; | |
| if (UI->comesBefore(InsertPt)) | |
| InsertPt = UI; | |
| } | |
| Builder.SetInsertPoint(InsertPt); | |
| } else { | |
| Builder.SetInsertPoint(PrevVec); | |
| } | |
| Builder.SetCurrentDebugLocation(UserI->getDebugLoc()); | |
| Value *Vec = vectorizeTree(TE); | |
| if (auto *VecI = dyn_cast<Instruction>(Vec); | |
| VecI && VecI->getParent() == Builder.GetInsertBlock() && | |
| Builder.GetInsertPoint()->comesBefore(VecI)) | |
| VecI->moveBeforePreserving(*Builder.GetInsertBlock(), | |
| Builder.GetInsertPoint()); | |
| if (Vec->getType() != PrevVec->getType()) { | |
| assert(Vec->getType()->isIntOrIntVectorTy() && | |
| PrevVec->getType()->isIntOrIntVectorTy() && | |
| "Expected integer vector types only."); | |
| std::optional<bool> IsSigned; | |
| for (Value *V : TE->Scalars) { | |
| if (isVectorized(V)) { | |
| for (const TreeEntry *MNTE : getTreeEntries(V)) { | |
| auto It = MinBWs.find(MNTE); | |
| if (It != MinBWs.end()) { | |
| IsSigned = IsSigned.value_or(false) || It->second.second; | |
| if (*IsSigned) | |
| break; | |
| } | |
| } | |
| if (IsSigned.value_or(false)) | |
| break; | |
| // Scan through gather nodes. | |
| for (const TreeEntry *BVE : ValueToGatherNodes.lookup(V)) { | |
| auto It = MinBWs.find(BVE); | |
| if (It != MinBWs.end()) { | |
| IsSigned = IsSigned.value_or(false) || It->second.second; | |
| if (*IsSigned) | |
| break; | |
| } | |
| } | |
| if (IsSigned.value_or(false)) | |
| break; | |
| if (auto *EE = dyn_cast<ExtractElementInst>(V)) { | |
| IsSigned = | |
| IsSigned.value_or(false) || | |
| !isKnownNonNegative(EE->getVectorOperand(), SimplifyQuery(*DL)); | |
| continue; | |
| } | |
| if (IsSigned.value_or(false)) | |
| break; | |
| } | |
| } | |
| if (IsSigned.value_or(false)) { | |
| // Final attempt - check user node. | |
| auto It = MinBWs.find(TE->UserTreeIndex.UserTE); | |
| if (It != MinBWs.end()) | |
| IsSigned = It->second.second; | |
| } | |
| assert(IsSigned && | |
| "Expected user node or perfect diamond match in MinBWs."); | |
| Vec = Builder.CreateIntCast(Vec, PrevVec->getType(), *IsSigned); | |
| } | |
| PrevVec->replaceAllUsesWith(Vec); | |
| PostponedValues.try_emplace(Vec).first->second.push_back(TE); | |
| // Replace the stub vector node, if it was used before for one of the | |
| // buildvector nodes already. | |
| auto It = PostponedValues.find(PrevVec); | |
| if (It != PostponedValues.end()) { | |
| for (TreeEntry *VTE : It->getSecond()) | |
| VTE->VectorizedValue = Vec; | |
| } | |
| eraseInstruction(PrevVec); | |
| } | |
| LLVM_DEBUG(dbgs() << "SLP: Extracting " << ExternalUses.size() | |
| << " values .\n"); | |
| SmallVector<ShuffledInsertData<Value *>> ShuffledInserts; | |
| // Maps vector instruction to original insertelement instruction | |
| DenseMap<Value *, InsertElementInst *> VectorToInsertElement; | |
| // Maps extract Scalar (plus the struct-field index path, when extracting | |
| // from a struct of vectors) to the corresponding extractelement instruction | |
| // in the basic block. Only one extractelement per block should be emitted. | |
| // The index path is stored in an owning SmallVector so the key remains | |
| // valid after the per-lane ExtractValueInst (whose Indices buffer it was | |
| // copied from) is erased later in this loop. | |
| SmallDenseMap<std::pair<Value *, SmallVector<unsigned, 1>>, | |
| DenseMap<BasicBlock *, std::pair<Value *, Value *>>> | |
| ScalarToEEs; | |
| // Maps (struct-of-vectors Vec, field-index path) to the corresponding | |
| // per-block extractvalue, so different external lanes that need the same | |
| // struct field of the same vectorized call share a single extractvalue. | |
| SmallDenseMap<std::pair<Value *, SmallVector<unsigned, 1>>, | |
| DenseMap<BasicBlock *, Value *>> | |
| StructFieldExtracts; | |
| SmallDenseSet<Value *, 4> UsedInserts; | |
| DenseMap<std::pair<Value *, Type *>, Value *> VectorCasts; | |
| SmallDenseSet<Value *, 4> ScalarsWithNullptrUser; | |
| SmallDenseSet<ExtractElementInst *, 4> IgnoredExtracts; | |
| // Extract all of the elements with the external uses. | |
| for (const auto &ExternalUse : ExternalUses) { | |
| Value *Scalar = ExternalUse.Scalar; | |
| llvm::User *User = ExternalUse.User; | |
| // Skip users that we already RAUW. This happens when one instruction | |
| // has multiple uses of the same value. | |
| if (User && !is_contained(Scalar->users(), User)) | |
| continue; | |
| const TreeEntry *E = &ExternalUse.E; | |
| assert(E && "Invalid scalar"); | |
| assert(!E->isGather() && "Extracting from a gather list"); | |
| // Non-instruction pointers are not deleted, just skip them. | |
| if (E->getOpcode() == Instruction::GetElementPtr && | |
| !isa<GetElementPtrInst>(Scalar)) | |
| continue; | |
| Value *Vec = E->VectorizedValue; | |
| assert(Vec && "Can't find vectorizable value"); | |
| auto ExtractAndExtendIfNeeded = [&](Value *Vec) { | |
| if (isa<InsertValueInst>(Scalar)) | |
| return Vec; | |
| if (Scalar->getType() != Vec->getType()) { | |
| Value *Ex = nullptr; | |
| Value *ExV = nullptr; | |
| auto *Inst = dyn_cast<Instruction>(Scalar); | |
| bool ReplaceInst = Inst && ExternalUsesAsOriginalScalar.contains(Inst); | |
| // For struct-typed scalars, the User must be an ExtractValueInst that | |
| // describes which struct field is being extracted. Copy its indices | |
| // into an owning SmallVector so the cache key survives erasure of the | |
| // ExtractValueInst. | |
| SmallVector<unsigned, 1> Indices; | |
| if (isa<StructType>(Scalar->getType())) { | |
| assert(User && "User expected for StructType extract."); | |
| const auto *EV = cast<ExtractValueInst>(User); | |
| Indices.assign(EV->getIndices()); | |
| } | |
| auto Key = std::make_pair(Scalar, Indices); | |
| auto It = ScalarToEEs.find(Key); | |
| if (It != ScalarToEEs.end()) { | |
| // No need to emit many extracts, just move the only one in the | |
| // current block. | |
| auto EEIt = It->second.find(ReplaceInst ? Inst->getParent() | |
| : Builder.GetInsertBlock()); | |
| if (EEIt != It->second.end()) { | |
| Value *PrevV = EEIt->second.first; | |
| if (auto *I = dyn_cast<Instruction>(PrevV); | |
| I && !ReplaceInst && | |
| Builder.GetInsertPoint() != Builder.GetInsertBlock()->end() && | |
| Builder.GetInsertPoint()->comesBefore(I)) { | |
| // For struct extracts the cached extractelement depends on a | |
| // cached extractvalue (FieldVec). Move FieldVec up first so it | |
| // still dominates the relocated extractelement after the move. | |
| if (isa<StructType>(Scalar->getType())) { | |
| auto FieldKey = std::make_pair(Vec, Indices); | |
| auto FieldIt = StructFieldExtracts.find(FieldKey); | |
| if (FieldIt != StructFieldExtracts.end()) { | |
| auto BBIt = FieldIt->second.find(Builder.GetInsertBlock()); | |
| if (BBIt != FieldIt->second.end()) { | |
| if (auto *FieldI = dyn_cast<Instruction>(BBIt->second); | |
| FieldI && | |
| FieldI->getParent() == Builder.GetInsertBlock() && | |
| Builder.GetInsertPoint()->comesBefore(FieldI)) | |
| FieldI->moveBefore(*Builder.GetInsertBlock(), | |
| Builder.GetInsertPoint()); | |
| } | |
| } | |
| } | |
| I->moveBefore(*Builder.GetInsertPoint()->getParent(), | |
| Builder.GetInsertPoint()); | |
| if (auto *CI = dyn_cast<Instruction>(EEIt->second.second)) | |
| CI->moveAfter(I); | |
| } | |
| Ex = PrevV; | |
| ExV = EEIt->second.second ? EEIt->second.second : Ex; | |
| } | |
| } | |
| if (!Ex) { | |
| // "Reuse" the existing extract to improve final codegen. | |
| if (ReplaceInst) { | |
| // Leave the instruction as is, if it cheaper extracts and all | |
| // operands are scalar. | |
| if (auto *EE = dyn_cast<ExtractElementInst>(Inst)) { | |
| IgnoredExtracts.insert(EE); | |
| Ex = EE; | |
| } else { | |
| auto *CloneInst = Inst->clone(); | |
| CloneInst->insertBefore(Inst->getIterator()); | |
| if (Inst->hasName()) | |
| CloneInst->takeName(Inst); | |
| Ex = CloneInst; | |
| } | |
| } else if (auto *ES = dyn_cast<ExtractElementInst>(Scalar); | |
| ES && isa<Instruction>(Vec)) { | |
| Value *V = ES->getVectorOperand(); | |
| auto *IVec = cast<Instruction>(Vec); | |
| if (ArrayRef<TreeEntry *> ETEs = getTreeEntries(V); !ETEs.empty()) | |
| V = ETEs.front()->VectorizedValue; | |
| if (auto *IV = dyn_cast<Instruction>(V); | |
| !IV || IV == Vec || IV->getParent() != IVec->getParent() || | |
| IV->comesBefore(IVec)) | |
| Ex = Builder.CreateExtractElement(V, ES->getIndexOperand()); | |
| else | |
| Ex = Builder.CreateExtractElement(Vec, ExternalUse.Lane); | |
| } else if (auto *VecTy = | |
| dyn_cast<FixedVectorType>(Scalar->getType())) { | |
| assert(Opts.slp_revec && "FixedVectorType is not expected."); | |
| unsigned VecTyNumElements = VecTy->getNumElements(); | |
| // When REVEC is enabled, we need to extract a vector. | |
| // Note: The element size of Scalar may be different from the | |
| // element size of Vec. | |
| Ex = createExtractVector(Builder, Vec, VecTyNumElements, | |
| ExternalUse.Lane * VecTyNumElements); | |
| } else { | |
| if (isa<StructType>(Vec->getType())) { | |
| assert(isa<StructType>(Scalar->getType()) && | |
| "Vec is struct of vectors only when Scalar is struct."); | |
| auto FieldKey = std::make_pair(Vec, Indices); | |
| BasicBlock *EVBB = Builder.GetInsertBlock(); | |
| Value *FieldVec = nullptr; | |
| auto FieldIt = StructFieldExtracts.find(FieldKey); | |
| if (FieldIt != StructFieldExtracts.end()) { | |
| auto BBIt = FieldIt->second.find(EVBB); | |
| if (BBIt != FieldIt->second.end()) | |
| FieldVec = BBIt->second; | |
| } | |
| if (!FieldVec) { | |
| FieldVec = Builder.CreateExtractValue(Vec, Indices); | |
| StructFieldExtracts[FieldKey][EVBB] = FieldVec; | |
| } else if (auto *FieldI = dyn_cast<Instruction>(FieldVec); | |
| FieldI && Builder.GetInsertPoint() != EVBB->end() && | |
| Builder.GetInsertPoint()->comesBefore(FieldI)) { | |
| // Cached extractvalue is below the current insertion point; | |
| // move it up so the extractelement we are about to emit can | |
| // use it. | |
| FieldI->moveBefore(*EVBB, Builder.GetInsertPoint()); | |
| } | |
| Vec = FieldVec; | |
| } | |
| if (Opts.slp_revec && isVectorizedTy(Scalar->getType())) { | |
| unsigned VecTyNumElements = getNumElements(Scalar->getType()); | |
| // When REVEC is enabled, we need to extract a vector. | |
| // Note: The element size of Scalar may be different from the | |
| // element size of Vec. | |
| Ex = createExtractVector(Builder, Vec, VecTyNumElements, | |
| ExternalUse.Lane * VecTyNumElements); | |
| } else { | |
| Ex = Builder.CreateExtractElement(Vec, ExternalUse.Lane); | |
| } | |
| } | |
| // If necessary, sign-extend or zero-extend ScalarRoot | |
| // to the larger type. | |
| ExV = Ex; | |
| if (!isa<StructType>(Scalar->getType()) && | |
| Scalar->getType() != Ex->getType()) | |
| ExV = Builder.CreateIntCast( | |
| Ex, Scalar->getType(), | |
| !isKnownNonNegative(Scalar, SimplifyQuery(*DL))); | |
| auto *I = dyn_cast<Instruction>(Ex); | |
| ScalarToEEs[Key].try_emplace(I ? I->getParent() : &F->getEntryBlock(), | |
| std::make_pair(Ex, ExV)); | |
| if (Inst && ExV != Inst && ExV->getType() == Inst->getType()) | |
| redirectDbgValues(*Inst, *ExV); | |
| } | |
| // The then branch of the previous if may produce constants, since 0 | |
| // operand might be a constant. | |
| if (auto *ExI = dyn_cast<Instruction>(Ex); | |
| ExI && !isa<PHINode>(ExI) && !mayHaveNonDefUseDependency(*ExI)) { | |
| GatherShuffleExtractSeq.insert(ExI); | |
| CSEBlocks.insert(ExI->getParent()); | |
| } | |
| return ExV; | |
| } | |
| assert(isa<FixedVectorType>(Scalar->getType()) && | |
| isa<InsertElementInst>(Scalar) && | |
| "In-tree scalar of vector type is not insertelement?"); | |
| auto *IE = cast<InsertElementInst>(Scalar); | |
| VectorToInsertElement.try_emplace(Vec, IE); | |
| return Vec; | |
| }; | |
| // If User == nullptr, the Scalar remains as scalar in vectorized | |
| // instructions or is used as extra arg. Generate ExtractElement instruction | |
| // and update the record for this scalar in ExternallyUsedValues. | |
| if (!User) { | |
| if (!ScalarsWithNullptrUser.insert(Scalar).second) | |
| continue; | |
| assert( | |
| (ExternallyUsedValues.count(Scalar) || | |
| ExternalUsesWithNonUsers.count(Scalar) || | |
| ExternalUsesAsOriginalScalar.contains(Scalar) || | |
| any_of( | |
| Scalar->users(), | |
| [&, TTI = TTI](llvm::User *U) { | |
| if (ExternalUsesAsOriginalScalar.contains(U)) | |
| return true; | |
| ArrayRef<TreeEntry *> UseEntries = getTreeEntries(U); | |
| return !UseEntries.empty() && | |
| (E->State == TreeEntry::Vectorize || | |
| E->State == TreeEntry::StridedVectorize || | |
| E->State == TreeEntry::ExpandVectorize || | |
| E->State == TreeEntry::CompressVectorize || | |
| E->State == TreeEntry::BlendedLoadVectorize) && | |
| any_of(UseEntries, [&, TTI = TTI](TreeEntry *UseEntry) { | |
| return (UseEntry->State == TreeEntry::Vectorize || | |
| UseEntry->State == | |
| TreeEntry::StridedVectorize || | |
| UseEntry->State == | |
| TreeEntry::ExpandVectorize || | |
| UseEntry->State == | |
| TreeEntry::CompressVectorize || | |
| UseEntry->State == | |
| TreeEntry::BlendedLoadVectorize) && | |
| doesInTreeUserNeedToExtract( | |
| Scalar, getRootEntryInstruction(*UseEntry), | |
| TLI, TTI); | |
| }); | |
| })) && | |
| "Scalar with nullptr User must be registered in " | |
| "ExternallyUsedValues map or remain as scalar in vectorized " | |
| "instructions"); | |
| if (auto *VecI = dyn_cast<Instruction>(Vec)) { | |
| if (auto *PHI = dyn_cast<PHINode>(VecI)) { | |
| if (PHI->getParent()->isLandingPad()) | |
| Builder.SetInsertPoint(std::next( | |
| PHI->getParent()->getLandingPadInst()->getIterator())); | |
| else | |
| Builder.SetInsertPoint(PHI->getParent()->getFirstNonPHIIt()); | |
| } else { | |
| Builder.SetInsertPoint(std::next(VecI->getIterator())); | |
| } | |
| } else { | |
| Builder.SetInsertPoint(F->getEntryBlock().begin()); | |
| } | |
| Value *NewInst = ExtractAndExtendIfNeeded(Vec); | |
| // Required to update internally referenced instructions. | |
| if (Scalar != NewInst) { | |
| assert((!isa<ExtractElementInst>(Scalar) || | |
| !IgnoredExtracts.contains(cast<ExtractElementInst>(Scalar))) && | |
| "Extractelements should not be replaced."); | |
| // Reduced values that are not operands of the reduction ops (e.g. | |
| // narrowed reduction leaves) lose all users once the tree scalars are | |
| // erased; keep the replacement alive for the reduction epilogue. | |
| bool NeedsProtection = | |
| UserIgnoreList && none_of(Scalar->users(), [&](llvm::User *U) { | |
| return UserIgnoreList->contains(U); | |
| }); | |
| Scalar->replaceAllUsesWith(NewInst); | |
| if (NeedsProtection) | |
| if (auto *I = dyn_cast<Instruction>(NewInst)) | |
| ExternalUseReplacements.insert(I); | |
| } | |
| continue; | |
| } | |
| if (auto *VU = dyn_cast<InsertElementInst>(User); | |
| VU && VU->getOperand(1) == Scalar) { | |
| // Skip if the scalar is another vector op or Vec is not an instruction. | |
| if (!Scalar->getType()->isVectorTy() && isa<Instruction>(Vec)) { | |
| if (auto *FTy = dyn_cast<FixedVectorType>(User->getType())) { | |
| if (!UsedInserts.insert(VU).second) | |
| continue; | |
| // Need to use original vector, if the root is truncated. | |
| auto BWIt = MinBWs.find(E); | |
| if (BWIt != MinBWs.end() && Vec->getType() != VU->getType()) { | |
| auto *ScalarTy = FTy->getElementType(); | |
| auto Key = std::make_pair(Vec, ScalarTy); | |
| auto VecIt = VectorCasts.find(Key); | |
| if (VecIt == VectorCasts.end()) { | |
| IRBuilderBase::InsertPointGuard Guard(Builder); | |
| if (auto *IVec = dyn_cast<PHINode>(Vec)) { | |
| if (IVec->getParent()->isLandingPad()) | |
| Builder.SetInsertPoint(std::next( | |
| IVec->getParent()->getLandingPadInst()->getIterator())); | |
| else | |
| Builder.SetInsertPoint( | |
| IVec->getParent()->getFirstNonPHIOrLifetime()); | |
| } else if (auto *IVec = dyn_cast<Instruction>(Vec)) { | |
| Builder.SetInsertPoint(IVec->getNextNode()); | |
| } | |
| Vec = Builder.CreateIntCast( | |
| Vec, | |
| getWidenedType( | |
| ScalarTy, | |
| cast<FixedVectorType>(Vec->getType())->getNumElements()), | |
| BWIt->second.second); | |
| VectorCasts.try_emplace(Key, Vec); | |
| } else { | |
| Vec = VecIt->second; | |
| } | |
| } | |
| std::optional<unsigned> InsertIdx = getElementIndex(VU); | |
| if (InsertIdx) { | |
| auto *It = find_if( | |
| ShuffledInserts, [VU](const ShuffledInsertData<Value *> &Data) { | |
| // Checks if 2 insertelements are from the same buildvector. | |
| InsertElementInst *VecInsert = Data.InsertElements.front(); | |
| return areTwoInsertFromSameBuildVector( | |
| VU, VecInsert, | |
| [](InsertElementInst *II) { return II->getOperand(0); }); | |
| }); | |
| unsigned Idx = *InsertIdx; | |
| if (It == ShuffledInserts.end()) { | |
| (void)ShuffledInserts.emplace_back(); | |
| It = std::next(ShuffledInserts.begin(), | |
| ShuffledInserts.size() - 1); | |
| } | |
| SmallVectorImpl<int> &Mask = It->ValueMasks[Vec]; | |
| if (Mask.empty()) | |
| Mask.assign(FTy->getNumElements(), PoisonMaskElem); | |
| Mask[Idx] = ExternalUse.Lane; | |
| It->InsertElements.push_back(cast<InsertElementInst>(User)); | |
| continue; | |
| } | |
| } | |
| } | |
| } | |
| // Generate extracts for out-of-tree users. | |
| // Find the insertion point for the extractelement lane. | |
| if (auto *VecI = dyn_cast<Instruction>(Vec)) { | |
| if (PHINode *PH = dyn_cast<PHINode>(User)) { | |
| for (unsigned I : seq<unsigned>(0, PH->getNumIncomingValues())) { | |
| if (PH->getIncomingValue(I) == Scalar) { | |
| Instruction *IncomingTerminator = | |
| PH->getIncomingBlock(I)->getTerminator(); | |
| if (isa<CatchSwitchInst>(IncomingTerminator)) { | |
| Builder.SetInsertPoint(std::next(VecI->getIterator())); | |
| } else { | |
| Builder.SetInsertPoint(PH->getIncomingBlock(I)->getTerminator()); | |
| } | |
| Value *NewInst = ExtractAndExtendIfNeeded(Vec); | |
| PH->setOperand(I, NewInst); | |
| } | |
| } | |
| } else { | |
| Builder.SetInsertPoint(cast<Instruction>(User)); | |
| Value *NewInst = ExtractAndExtendIfNeeded(Vec); | |
| if (isa<StructType>(Scalar->getType()) && | |
| isa_and_nonnull<ExtractValueInst>(User) && | |
| !isa<StructType>(NewInst->getType())) { | |
| User->replaceAllUsesWith(NewInst); | |
| eraseInstruction(cast<Instruction>(User)); | |
| } else { | |
| User->replaceUsesOfWith(Scalar, NewInst); | |
| } | |
| } | |
| } else { | |
| Builder.SetInsertPoint(F->getEntryBlock().begin()); | |
| Value *NewInst = ExtractAndExtendIfNeeded(Vec); | |
| User->replaceUsesOfWith(Scalar, NewInst); | |
| } | |
| LLVM_DEBUG(dbgs() << "SLP: Replaced:" << *User << ".\n"); | |
| } | |
| auto CreateShuffle = [&](Value *V1, Value *V2, ArrayRef<int> Mask) { | |
| SmallVector<int> CombinedMask1(Mask.size(), PoisonMaskElem); | |
| SmallVector<int> CombinedMask2(Mask.size(), PoisonMaskElem); | |
| int VF = cast<FixedVectorType>(V1->getType())->getNumElements(); | |
| for (int I = 0, E = Mask.size(); I < E; ++I) { | |
| if (Mask[I] < VF) | |
| CombinedMask1[I] = Mask[I]; | |
| else | |
| CombinedMask2[I] = Mask[I] - VF; | |
| } | |
| ShuffleInstructionBuilder ShuffleBuilder( | |
| cast<VectorType>(V1->getType())->getElementType(), Builder, *this); | |
| ShuffleBuilder.add(V1, CombinedMask1); | |
| if (V2) | |
| ShuffleBuilder.add(V2, CombinedMask2); | |
| return ShuffleBuilder.finalize({}, {}, {}); | |
| }; | |
| auto &&ResizeToVF = [&CreateShuffle](Value *Vec, ArrayRef<int> Mask, | |
| bool ForSingleMask) { | |
| unsigned VF = Mask.size(); | |
| unsigned VecVF = cast<FixedVectorType>(Vec->getType())->getNumElements(); | |
| if (VF != VecVF) { | |
| if (any_of(Mask, [VF](int Idx) { return Idx >= static_cast<int>(VF); })) { | |
| Vec = CreateShuffle(Vec, nullptr, Mask); | |
| return std::make_pair(Vec, true); | |
| } | |
| if (!ForSingleMask) { | |
| SmallVector<int> ResizeMask(VF, PoisonMaskElem); | |
| for (unsigned I = 0; I < VF; ++I) { | |
| if (Mask[I] != PoisonMaskElem) | |
| ResizeMask[Mask[I]] = Mask[I]; | |
| } | |
| Vec = CreateShuffle(Vec, nullptr, ResizeMask); | |
| } | |
| } | |
| return std::make_pair(Vec, false); | |
| }; | |
| // Perform shuffling of the vectorize tree entries for better handling of | |
| // external extracts. | |
| for (int I = 0, E = ShuffledInserts.size(); I < E; ++I) { | |
| // Find the first and the last instruction in the list of insertelements. | |
| sort(ShuffledInserts[I].InsertElements, isFirstInsertElement); | |
| InsertElementInst *FirstInsert = ShuffledInserts[I].InsertElements.front(); | |
| InsertElementInst *LastInsert = ShuffledInserts[I].InsertElements.back(); | |
| Builder.SetInsertPoint(LastInsert); | |
| auto Vector = ShuffledInserts[I].ValueMasks.takeVector(); | |
| Value *NewInst = performExtractsShuffleAction<Value>( | |
| MutableArrayRef(Vector.data(), Vector.size()), | |
| FirstInsert->getOperand(0), | |
| [](Value *Vec) { | |
| return cast<VectorType>(Vec->getType()) | |
| ->getElementCount() | |
| .getKnownMinValue(); | |
| }, | |
| ResizeToVF, | |
| [FirstInsert, &CreateShuffle](ArrayRef<int> Mask, | |
| ArrayRef<Value *> Vals) { | |
| assert((Vals.size() == 1 || Vals.size() == 2) && | |
| "Expected exactly 1 or 2 input values."); | |
| if (Vals.size() == 1) { | |
| // Do not create shuffle if the mask is a simple identity | |
| // non-resizing mask. | |
| if (Mask.size() != cast<FixedVectorType>(Vals.front()->getType()) | |
| ->getNumElements() || | |
| !ShuffleVectorInst::isIdentityMask(Mask, Mask.size())) | |
| return CreateShuffle(Vals.front(), nullptr, Mask); | |
| return Vals.front(); | |
| } | |
| return CreateShuffle(Vals.front() ? Vals.front() | |
| : FirstInsert->getOperand(0), | |
| Vals.back(), Mask); | |
| }); | |
| auto It = ShuffledInserts[I].InsertElements.rbegin(); | |
| // Rebuild buildvector chain. | |
| InsertElementInst *II = nullptr; | |
| if (It != ShuffledInserts[I].InsertElements.rend()) | |
| II = *It; | |
| SmallVector<Instruction *> Inserts; | |
| while (It != ShuffledInserts[I].InsertElements.rend()) { | |
| assert(II && "Must be an insertelement instruction."); | |
| if (*It == II) | |
| ++It; | |
| else | |
| Inserts.push_back(II); | |
| II = dyn_cast<InsertElementInst>(II->getOperand(0)); | |
| } | |
| for (Instruction *II : reverse(Inserts)) { | |
| II->replaceUsesOfWith(II->getOperand(0), NewInst); | |
| if (auto *NewI = dyn_cast<Instruction>(NewInst)) | |
| if (II->getParent() == NewI->getParent() && II->comesBefore(NewI)) | |
| II->moveAfter(NewI); | |
| NewInst = II; | |
| } | |
| LastInsert->replaceAllUsesWith(NewInst); | |
| for (InsertElementInst *IE : reverse(ShuffledInserts[I].InsertElements)) { | |
| IE->replaceUsesOfWith(IE->getOperand(0), | |
| PoisonValue::get(IE->getOperand(0)->getType())); | |
| IE->replaceUsesOfWith(IE->getOperand(1), | |
| PoisonValue::get(IE->getOperand(1)->getType())); | |
| eraseInstruction(IE); | |
| } | |
| CSEBlocks.insert(LastInsert->getParent()); | |
| } | |
| SmallVector<Instruction *> RemovedInsts; | |
| // For each vectorized value: | |
| for (auto &TEPtr : VectorizableTree) { | |
| TreeEntry *Entry = TEPtr.get(); | |
| // No need to handle users of gathered values. | |
| if (Entry->isGather() || Entry->State == TreeEntry::SplitVectorize || | |
| DeletedNodes.contains(Entry) || | |
| TransformedToGatherNodes.contains(Entry)) | |
| continue; | |
| if (Entry->CombinedOp == TreeEntry::ReducedBitcast || | |
| Entry->CombinedOp == TreeEntry::ReducedBitcastBSwap || | |
| Entry->CombinedOp == TreeEntry::ReducedBitcastLoads || | |
| Entry->CombinedOp == TreeEntry::ReducedBitcastBSwapLoads || | |
| Entry->CombinedOp == TreeEntry::ReducedBitPack || | |
| Entry->CombinedOp == TreeEntry::ReducedCmpBitcast) { | |
| // Skip constant node | |
| if (!Entry->hasState()) { | |
| assert(allConstant(Entry->Scalars) && "Expected constants only."); | |
| continue; | |
| } | |
| for (Instruction *I : make_isa_range<Instruction>(Entry->Scalars)) { | |
| if (Entry->isCopyableElement(I)) | |
| continue; | |
| LLVM_DEBUG(dbgs() << "SLP: \tErasing scalar:" << *I << ".\n"); | |
| RemovedInsts.push_back(I); | |
| } | |
| continue; | |
| } | |
| assert(Entry->VectorizedValue && "Can't find vectorizable value"); | |
| // For each lane: | |
| for (int Lane = 0, LE = Entry->Scalars.size(); Lane != LE; ++Lane) { | |
| Value *Scalar = Entry->Scalars[Lane]; | |
| if (Entry->getOpcode() == Instruction::GetElementPtr && | |
| !isa<GetElementPtrInst>(Scalar)) | |
| continue; | |
| if (auto *EE = dyn_cast<ExtractElementInst>(Scalar); | |
| EE && IgnoredExtracts.contains(EE)) | |
| continue; | |
| if (!isa<Instruction>(Scalar) || Entry->isCopyableElement(Scalar)) | |
| continue; | |
| #ifndef NDEBUG | |
| Type *Ty = Scalar->getType(); | |
| if (!Ty->isVoidTy()) { | |
| for (User *U : Scalar->users()) { | |
| LLVM_DEBUG(dbgs() << "SLP: \tvalidating user:" << *U << ".\n"); | |
| // It is legal to delete users in the ignorelist. | |
| assert((isVectorized(U) || | |
| (UserIgnoreList && UserIgnoreList->contains(U)) || | |
| (isa_and_nonnull<Instruction>(U) && | |
| isDeleted(cast<Instruction>(U)))) && | |
| "Deleting out-of-tree value"); | |
| } | |
| } | |
| #endif | |
| LLVM_DEBUG(dbgs() << "SLP: \tErasing scalar:" << *Scalar << ".\n"); | |
| auto *I = cast<Instruction>(Scalar); | |
| RemovedInsts.push_back(I); | |
| } | |
| // Erase peeled intermediates (not listed in Scalars), except the ones | |
| // kept because the tree still claims them. | |
| for (Value *V : | |
| make_filter_range(Entry->getReassocScalars(), [&](Value *V) { | |
| return !KeptReassocScalars.contains(V); | |
| })) { | |
| #ifndef NDEBUG | |
| for (User *U : V->users()) { | |
| LLVM_DEBUG(dbgs() << "SLP: \tvalidating user:" << *U << ".\n"); | |
| // Single-use peeled scalars are consumed by this entry or a sibling | |
| // in the same chain. | |
| assert((isVectorized(U) || | |
| (UserIgnoreList && UserIgnoreList->contains(U)) || | |
| (isa_and_nonnull<Instruction>(U) && | |
| isDeleted(cast<Instruction>(U)))) && | |
| "Deleting out-of-tree value"); | |
| } | |
| #endif | |
| LLVM_DEBUG(dbgs() << "SLP: \tErasing scalar:" << *V << ".\n"); | |
| RemovedInsts.push_back(cast<Instruction>(V)); | |
| } | |
| } | |
| // Collect tree-entry vector instructions that ended up without any non-dead | |
| // uses (can happen with the non-power-of-2 paths, where intermediate vectors | |
| // get replaced but the original node still holds a reference). | |
| // FIXME: track and clear those references at replacement time instead of | |
| // sweeping after the fact. Done BEFORE mergeDIAssignID so any DIAssignID | |
| // from intermediate vectors is folded into the root vector instead of being | |
| // dropped on erase. | |
| { | |
| Value *RootVec = getRootNode().VectorizedValue; | |
| // Seed with the scalars already queued for removal - a vector whose only | |
| // user is a scalar-to-be-removed is also dead. | |
| SmallPtrSet<Value *, 16> DeadSet(RemovedInsts.begin(), RemovedInsts.end()); | |
| auto AllUsesAreDead = [&](const Value *V) { | |
| return all_of(V->users(), | |
| [&](const User *U) { return DeadSet.contains(U); }); | |
| }; | |
| SmallPtrSet<const Value *, 16> Candidates; | |
| SmallVector<Instruction *, 16> Worklist; | |
| for (const auto &TEPtr : VectorizableTree) { | |
| Value *VV = TEPtr->VectorizedValue; | |
| if (!VV || VV == RootVec || VV->getType()->isVoidTy() || | |
| DeadSet.contains(VV)) | |
| continue; | |
| auto *I = dyn_cast<Instruction>(VV); | |
| if (!I) | |
| continue; | |
| if (Candidates.insert(I).second) | |
| Worklist.push_back(I); | |
| } | |
| // Worklist sweep: when a vector becomes dead, its operand candidates | |
| // may have lost their last live user, so re-enqueue them. Cascaded dead | |
| // chains converge in O(N + total uses) instead of O(depth * N * uses). | |
| while (!Worklist.empty()) { | |
| Instruction *I = Worklist.pop_back_val(); | |
| if (DeadSet.contains(I)) | |
| continue; | |
| if (!I->use_empty() && !AllUsesAreDead(I)) | |
| continue; | |
| DeadSet.insert(I); | |
| RemovedInsts.push_back(I); | |
| for (Instruction *OI : make_isa_range<Instruction>(I->operand_values())) | |
| if (Candidates.contains(OI) && !DeadSet.contains(OI)) | |
| Worklist.push_back(OI); | |
| } | |
| } | |
| // Merge the DIAssignIDs from the about-to-be-deleted instructions into the | |
| // new vector instruction. | |
| if (auto *V = dyn_cast<Instruction>(getRootNode().VectorizedValue)) | |
| V->mergeDIAssignID(RemovedInsts); | |
| // Clear up reduction references, if any. | |
| if (UserIgnoreList) { | |
| for (Instruction *I : RemovedInsts) { | |
| ArrayRef<TreeEntry *> Entries = getTreeEntries(I); | |
| // Dead intermediate vector instructions collected above have no tree | |
| // entry - nothing to unlink here. | |
| if (Entries.empty()) | |
| continue; | |
| const TreeEntry *IE = Entries.front(); | |
| if (ArrayRef<TreeEntry *> SplitEntries = getSplitTreeEntries(I); | |
| !SplitEntries.empty() && SplitEntries.front()->Idx < IE->Idx) | |
| IE = SplitEntries.front(); | |
| if (IE->Idx != 0 && | |
| !(getRootNode().isGather() && IE->UserTreeIndex && | |
| (ValueToGatherNodes.lookup(I).contains(&getRootNode()) || | |
| (IE->UserTreeIndex.UserTE == &getRootNode() && | |
| IE->UserTreeIndex.EdgeIdx == UINT_MAX))) && | |
| !(getRootNode().State == TreeEntry::SplitVectorize && | |
| IE->UserTreeIndex && is_contained(getRootNodeScalars(), I)) && | |
| !(GatheredLoadsEntriesFirst.has_value() && | |
| IE->Idx >= *GatheredLoadsEntriesFirst && getRootNode().isGather() && | |
| is_contained(getRootNodeScalars(), I)) && | |
| !(!getRootNode().isGather() && getRootNode().isCopyableElement(I)) && | |
| // Dropped narrowed reduction chain instructions may still use | |
| // non-root scalars; such uses must be cleared as well. | |
| none_of(I->users(), | |
| [&](User *U) { return NarrowedChainInsts.contains(U); })) | |
| continue; | |
| SmallVector<SelectInst *> LogicalOpSelects; | |
| I->replaceUsesWithIf(PoisonValue::get(I->getType()), [&](Use &U) { | |
| // Do not replace condition of the logical op in form select <cond>. | |
| bool IsPoisoningLogicalOp = isa<SelectInst>(U.getUser()) && | |
| (match(U.getUser(), m_LogicalAnd()) || | |
| match(U.getUser(), m_LogicalOr())) && | |
| U.getOperandNo() == 0; | |
| if (IsPoisoningLogicalOp) { | |
| LogicalOpSelects.push_back(cast<SelectInst>(U.getUser())); | |
| return false; | |
| } | |
| return UserIgnoreList->contains(U.getUser()); | |
| }); | |
| // Replace conditions of the poisoning logical ops with the non-poison | |
| // constant value. | |
| for (SelectInst *SI : LogicalOpSelects) | |
| SI->setCondition(Constant::getNullValue(SI->getCondition()->getType())); | |
| } | |
| } | |
| // Externally used values, which are not operands of the reduction ops (e.g. | |
| // gathered narrowed reduction leaves), may lose all users once the tree | |
| // scalars are erased; keep them alive for the reduction epilogue. Too many | |
| // users - keep conservatively. | |
| if (UserIgnoreList) | |
| for (Instruction *I : make_isa_range<Instruction>(ExternallyUsedValues)) | |
| if (I->hasNUsesOrMore(UsesLimit) || none_of(I->users(), [&](User *U) { | |
| return UserIgnoreList->contains(U); | |
| })) | |
| ExternalUseReplacements.insert(I); | |
| // Retain to-be-deleted instructions for some debug-info bookkeeping and alias | |
| // cache correctness. | |
| // NOTE: removeInstructionAndOperands only marks the instruction for deletion | |
| // - instructions are not deleted until later. | |
| removeInstructionsAndOperands(ArrayRef(RemovedInsts), VectorValuesAndScales); | |
| // The emitted vectorized code and the erased scalars may invalidate the | |
| // cached analyses results. | |
| Cache.clear(); | |
| // The new vector code may capture pointers that were previously not captured | |
| // (e.g. the pointer operands of a masked gather), so the capture results | |
| // cached in BatchAA are stale. Alias queries against the new instructions | |
| // would otherwise treat such pointers as inaccessible to them. | |
| BatchAA.emplace(*AA); | |
| Builder.ClearInsertionPoint(); | |
| InstrElementSize.clear(); | |
| const TreeEntry &RootTE = getRootNode(); | |
| Value *Vec = RootTE.VectorizedValue; | |
| if (auto It = MinBWs.find(&RootTE); ReductionBitWidth != 0 && | |
| It != MinBWs.end() && | |
| ReductionBitWidth != It->second.first) { | |
| IRBuilder<>::InsertPointGuard Guard(Builder); | |
| Builder.SetInsertPoint(ReductionRoot->getIterator()); | |
| if (isReducedBitcastRoot() || isReducedCmpBitcastRoot()) { | |
| Vec = Builder.CreateIntCast(Vec, Builder.getIntNTy(ReductionBitWidth), | |
| It->second.second); | |
| } else { | |
| Vec = Builder.CreateIntCast( | |
| Vec, | |
| VectorType::get(Builder.getIntNTy(ReductionBitWidth), | |
| cast<VectorType>(Vec->getType())->getElementCount()), | |
| It->second.second); | |
| } | |
| } | |
| return Vec; | |
| } | |
| void BoUpSLP::optimizeGatherSequence() { | |
| LLVM_DEBUG(dbgs() << "SLP: Optimizing " << GatherShuffleExtractSeq.size() | |
| << " gather sequences instructions.\n"); | |
| // LICM InsertElementInst sequences. | |
| for (Instruction *I : | |
| make_filter_range(GatherShuffleExtractSeq, | |
| [&](Instruction *I) { return !isDeleted(I); })) { | |
| // Check if this block is inside a loop. | |
| Loop *L = LI->getLoopFor(I->getParent()); | |
| if (!L) | |
| continue; | |
| // Check if it has a preheader. | |
| BasicBlock *PreHeader = L->getLoopPreheader(); | |
| if (!PreHeader) | |
| continue; | |
| // If the vector or the element that we insert into it are | |
| // instructions that are defined in this basic block then we can't | |
| // hoist this instruction. | |
| if (any_of(make_isa_range<Instruction>(I->operands()), | |
| [L](Instruction *OpI) { return L->contains(OpI); })) | |
| continue; | |
| // We can hoist this instruction. Move it to the pre-header. | |
| I->moveBefore(PreHeader->getTerminator()->getIterator()); | |
| CSEBlocks.insert(PreHeader); | |
| } | |
| // Make a list of all reachable blocks in our CSE queue. | |
| SmallVector<const DomTreeNode *, 8> CSEWorkList; | |
| CSEWorkList.reserve(CSEBlocks.size()); | |
| for (BasicBlock *BB : CSEBlocks) | |
| if (DomTreeNode *N = DT->getNode(BB)) | |
| CSEWorkList.push_back(N); | |
| // Sort blocks by domination. This ensures we visit a block after all blocks | |
| // dominating it are visited. | |
| llvm::sort(CSEWorkList, [](const DomTreeNode *A, const DomTreeNode *B) { | |
| assert((A == B) == (A->getDFSNumIn() == B->getDFSNumIn()) && | |
| "Different nodes should have different DFS numbers"); | |
| return A->getDFSNumIn() < B->getDFSNumIn(); | |
| }); | |
| // Less defined shuffles can be replaced by the more defined copies. | |
| // Between two shuffles one is less defined if it has the same vector operands | |
| // and its mask indeces are the same as in the first one or undefs. E.g. | |
| // shuffle %0, poison, <0, 0, 0, undef> is less defined than shuffle %0, | |
| // poison, <0, 0, 0, 0>. | |
| auto &&IsIdenticalOrLessDefined = [this](Instruction *I1, Instruction *I2, | |
| SmallVectorImpl<int> &NewMask) { | |
| if (I1->getType() != I2->getType()) | |
| return false; | |
| auto *SI1 = dyn_cast<ShuffleVectorInst>(I1); | |
| auto *SI2 = dyn_cast<ShuffleVectorInst>(I2); | |
| if (!SI1 || !SI2) | |
| return I1->isIdenticalTo(I2); | |
| if (SI1->isIdenticalTo(SI2)) | |
| return true; | |
| for (int I = 0, E = SI1->getNumOperands(); I < E; ++I) | |
| if (SI1->getOperand(I) != SI2->getOperand(I)) | |
| return false; | |
| // Check if the second instruction is more defined than the first one. | |
| NewMask.assign(SI2->getShuffleMask().begin(), SI2->getShuffleMask().end()); | |
| ArrayRef<int> SM1 = SI1->getShuffleMask(); | |
| // Count trailing undefs in the mask to check the final number of used | |
| // registers. | |
| unsigned LastUndefsCnt = 0; | |
| for (int I = 0, E = NewMask.size(); I < E; ++I) { | |
| if (SM1[I] == PoisonMaskElem) | |
| ++LastUndefsCnt; | |
| else | |
| LastUndefsCnt = 0; | |
| if (NewMask[I] != PoisonMaskElem && SM1[I] != PoisonMaskElem && | |
| NewMask[I] != SM1[I]) | |
| return false; | |
| if (NewMask[I] == PoisonMaskElem) | |
| NewMask[I] = SM1[I]; | |
| } | |
| // Check if the last undefs actually change the final number of used vector | |
| // registers. | |
| return SM1.size() - LastUndefsCnt > 1 && | |
| getNumberOfParts(SI1->getType(), SI1->getType()->getElementType()) == | |
| getNumberOfParts(getWidenedType(SI1->getType()->getElementType(), | |
| SM1.size() - LastUndefsCnt), | |
| SI1->getType()->getElementType()); | |
| }; | |
| // Perform O(N^2) search over the gather/shuffle sequences and merge identical | |
| // instructions. TODO: We can further optimize this scan if we split the | |
| // instructions into different buckets based on the insert lane. | |
| SmallVector<Instruction *, 16> Visited; | |
| for (auto I = CSEWorkList.begin(), E = CSEWorkList.end(); I != E; ++I) { | |
| assert(*I && | |
| (I == CSEWorkList.begin() || !DT->dominates(*I, *std::prev(I))) && | |
| "Worklist not sorted properly!"); | |
| BasicBlock *BB = (*I)->getBlock(); | |
| // For all instructions in blocks containing gather sequences: | |
| for (Instruction &In : llvm::make_early_inc_range(*BB)) { | |
| if (isDeleted(&In)) | |
| continue; | |
| if (!isa<InsertElementInst, ExtractElementInst, ShuffleVectorInst>(&In) && | |
| !GatherShuffleExtractSeq.contains(&In)) | |
| continue; | |
| // Check if we can replace this instruction with any of the | |
| // visited instructions. | |
| bool Replaced = false; | |
| for (Instruction *&V : Visited) { | |
| SmallVector<int> NewMask; | |
| if (IsIdenticalOrLessDefined(&In, V, NewMask) && | |
| DT->dominates(V->getParent(), In.getParent())) { | |
| In.replaceAllUsesWith(V); | |
| eraseInstruction(&In); | |
| if (auto *SI = dyn_cast<ShuffleVectorInst>(V)) | |
| if (!NewMask.empty()) | |
| SI->setShuffleMask(NewMask); | |
| Replaced = true; | |
| break; | |
| } | |
| if (isa<ShuffleVectorInst>(In) && isa<ShuffleVectorInst>(V) && | |
| GatherShuffleExtractSeq.contains(V) && | |
| IsIdenticalOrLessDefined(V, &In, NewMask) && | |
| DT->dominates(In.getParent(), V->getParent())) { | |
| In.moveAfter(V); | |
| V->replaceAllUsesWith(&In); | |
| eraseInstruction(V); | |
| if (auto *SI = dyn_cast<ShuffleVectorInst>(&In)) | |
| if (!NewMask.empty()) | |
| SI->setShuffleMask(NewMask); | |
| V = &In; | |
| Replaced = true; | |
| break; | |
| } | |
| } | |
| if (!Replaced) { | |
| assert(!is_contained(Visited, &In)); | |
| Visited.push_back(&In); | |
| } | |
| } | |
| } | |
| CSEBlocks.clear(); | |
| GatherShuffleExtractSeq.clear(); | |
| } | |
| BoUpSLP::ScheduleBundle &BoUpSLP::BlockScheduling::buildBundle( | |
| ArrayRef<Value *> VL, const InstructionsState &S, const EdgeInfo &EI) { | |
| auto &BundlePtr = | |
| ScheduledBundlesList.emplace_back(std::make_unique<ScheduleBundle>()); | |
| for (Value *V : make_filter_range( | |
| VL, [&](Value *V) { return !S.isNonSchedulable(V); })) { | |
| auto *I = cast<Instruction>(V); | |
| if (S.isCopyableElement(V)) { | |
| // Add a copyable element model. | |
| ScheduleCopyableData &SD = | |
| addScheduleCopyableData(EI, I, SchedulingRegionID, *BundlePtr); | |
| // Group the instructions to a bundle. | |
| BundlePtr->add(&SD); | |
| continue; | |
| } | |
| ScheduleData *BundleMember = getScheduleData(V); | |
| assert(BundleMember && "no ScheduleData for bundle member " | |
| "(maybe not in same basic block)"); | |
| // Group the instructions to a bundle. | |
| BundlePtr->add(BundleMember); | |
| ScheduledBundles.try_emplace(I).first->getSecond().push_back( | |
| BundlePtr.get()); | |
| } | |
| assert(BundlePtr && *BundlePtr && "Failed to find schedule bundle"); | |
| return *BundlePtr; | |
| } | |
| // Groups the instructions to a bundle (which is then a single scheduling entity) | |
| // and schedules instructions until the bundle gets ready. | |
| std::optional<BoUpSLP::ScheduleBundle *> | |
| BoUpSLP::BlockScheduling::tryScheduleBundle(ArrayRef<Value *> VL, BoUpSLP *SLP, | |
| const InstructionsState &S, | |
| const EdgeInfo &EI) { | |
| // No need to schedule PHIs, insertelement, extractelement and extractvalue | |
| // instructions. | |
| if (isa<PHINode>(S.getMainOp()) || | |
| isVectorLikeInstWithConstOps(S.getMainOp())) | |
| return nullptr; | |
| bool HasCopyables = S.areInstructionsWithCopyableElements(); | |
| bool DoesNotRequireScheduling = | |
| (!HasCopyables && doesNotNeedToSchedule(VL)) || | |
| all_of(VL, [&](Value *V) { return S.isNonSchedulable(V); }); | |
| if (S.areInstructionsWithCopyableElements() && EI && EI.UserTE->hasState() && | |
| EI.UserTE->hasCopyableElements() && | |
| EI.UserTE->getMainOp()->getParent() == S.getMainOp()->getParent() && | |
| all_of(VL, [&](Value *V) { | |
| if (S.isCopyableElement(V)) | |
| return true; | |
| return isUsedOutsideBlock(V); | |
| })) | |
| return std::nullopt; | |
| // Drop a copyable scalar if it has a same-block, non-PHI, non-schedulable | |
| // user that is reused outside this block via a non-PHI use. Dependency | |
| // tracking across multiple nodes may then be incorrect. | |
| if (S.areInstructionsWithCopyableElements() && EI && EI.UserTE->hasState() && | |
| any_of(make_isa_range<Instruction>(VL), [&](Instruction *I) { | |
| if (!S.isCopyableElement(I)) | |
| return false; | |
| return any_of(I->users(), [&](User *U) { | |
| auto *IU = dyn_cast<Instruction>(U); | |
| if (!IU || IU->getParent() != I->getParent()) | |
| return false; | |
| return !isa<PHINode>(IU) && !IU->hasOneUse() && | |
| isUsedOutsideBlock(IU) && | |
| !all_of(IU->users(), IsaPred<PHINode>); | |
| }); | |
| })) | |
| return std::nullopt; | |
| // If any instruction is used outside block only and its operand is placed | |
| // immediately before it, do not schedule, it may cause wrong def-use chain. | |
| if (S.areInstructionsWithCopyableElements() && any_of(VL, [&](Value *V) { | |
| if (isa<PoisonValue>(V) || S.isCopyableElement(V)) | |
| return false; | |
| if (isUsedOutsideBlock(V)) { | |
| for (Instruction *I : | |
| make_isa_range<Instruction>(cast<Instruction>(V)->operands())) | |
| return SLP->isVectorized(I) && I->getNextNode() == V; | |
| } | |
| return false; | |
| })) | |
| return std::nullopt; | |
| if (S.areInstructionsWithCopyableElements() && EI) { | |
| bool IsNonSchedulableWithParentPhiNode = | |
| EI.UserTE->doesNotNeedToSchedule() && EI.UserTE->UserTreeIndex && | |
| EI.UserTE->UserTreeIndex.UserTE->hasState() && | |
| EI.UserTE->UserTreeIndex.UserTE->State != TreeEntry::SplitVectorize && | |
| EI.UserTE->UserTreeIndex.UserTE->getOpcode() == Instruction::PHI; | |
| if (IsNonSchedulableWithParentPhiNode) { | |
| ArrayRef<Value *> PhiScalars = EI.UserTE->UserTreeIndex.UserTE->Scalars; | |
| for (const auto [Idx, V] : enumerate(PhiScalars)) { | |
| Value *Op = EI.UserTE->UserTreeIndex.UserTE->getOperand( | |
| EI.UserTE->UserTreeIndex.EdgeIdx)[Idx]; | |
| auto *I = dyn_cast<Instruction>(Op); | |
| if (!I || !isCommutative(I)) | |
| continue; | |
| // Bail out only when I has a user that is an instruction outside the | |
| // grandparent PHI's Scalars. Multiple uses that all land in the same | |
| // vectorized PHI are tracked by the existing dependency machinery; | |
| // uses outside it (e.g. a scalar PHI in a different block) break the | |
| // scheduler's dep accounting for non-schedulable copyable bundles. | |
| if (I->hasOneUse()) | |
| continue; | |
| if (any_of(I->users(), [&](User *U) { | |
| auto *UI = dyn_cast<Instruction>(U); | |
| return UI && !is_contained(PhiScalars, UI); | |
| })) | |
| return std::nullopt; | |
| } | |
| } else { | |
| // If any of the parent requires scheduling - exit, complex dep between | |
| // schedulable/non-schedulable parents. | |
| if (any_of(EI.UserTE->Scalars, [&](Value *V) { | |
| if (EI.UserTE->hasCopyableElements() && | |
| EI.UserTE->isCopyableElement(V)) | |
| return false; | |
| ArrayRef<TreeEntry *> Entries = SLP->getTreeEntries(V); | |
| return any_of(Entries, [](const TreeEntry *TE) { | |
| return TE->doesNotNeedToSchedule() && TE->UserTreeIndex && | |
| TE->UserTreeIndex.UserTE->hasState() && | |
| TE->UserTreeIndex.UserTE->State != | |
| TreeEntry::SplitVectorize && | |
| TE->UserTreeIndex.UserTE->getOpcode() == Instruction::PHI; | |
| }); | |
| })) | |
| return std::nullopt; | |
| } | |
| } | |
| // A scalar that is a non-copyable (i.e. really vectorized) element in this | |
| // PHI-operand node, but a copyable element in another PHI-operand node, is | |
| // vectorized here and only gathered/reused there. Both nodes feed different | |
| // incoming values of the same vectorized PHI and may be emitted in different | |
| // predecessor blocks, so the gathered reuse can end up not dominated by the | |
| // vectorized value, producing broken IR. Bail out of scheduling to avoid it. | |
| if (EI && EI.UserTE->State == TreeEntry::Vectorize && | |
| EI.UserTE->getOpcode() == Instruction::PHI && | |
| any_of(make_isa_range<Instruction>(VL), [&](Instruction *I) { | |
| if (HasCopyables && S.isCopyableElement(I)) | |
| return false; | |
| return any_of( | |
| SLP->VectorizableTree, [&](const std::unique_ptr<TreeEntry> &TE) { | |
| return TE->UserTreeIndex && | |
| TE->UserTreeIndex.UserTE->State == TreeEntry::Vectorize && | |
| TE->UserTreeIndex.UserTE->getOpcode() == | |
| Instruction::PHI && | |
| TE->hasCopyableElements() && TE->isCopyableElement(I); | |
| }); | |
| })) | |
| return std::nullopt; | |
| // A scalar that is a non-schedulable copyable element of another node in the | |
| // same block (not the user of this node) is reused at the insertion point of | |
| // that node, which does not depend on this node in the schedule. The | |
| // vectorized value of this node may be emitted after the reuse, producing | |
| // broken IR. Bail out of scheduling to avoid it. | |
| if (any_of(make_isa_range<Instruction>(VL), [&](Instruction *I) { | |
| if ((HasCopyables && S.isCopyableElement(I)) || | |
| !doesNotNeedToBeScheduled(I)) | |
| return false; | |
| return any_of( | |
| SLP->VectorizableTree, [&](const std::unique_ptr<TreeEntry> &TE) { | |
| return TE.get() != EI.UserTE && !TE->isGather() && | |
| TE->hasCopyableElements() && TE->isCopyableElement(I) && | |
| TE->getMainOp()->getParent() == I->getParent() && | |
| TE->getOperations().isNonSchedulable(I); | |
| }); | |
| })) | |
| return std::nullopt; | |
| // The same conflict when the node with the real element is built first: a | |
| // non-schedulable copyable element of this node whose scalar is already | |
| // vectorized in another node of the block, or belongs to a split node, would | |
| // be reused from that node's vector, which may be emitted after this node. | |
| if (HasCopyables && | |
| any_of(make_isa_range<Instruction>(VL), [&](Instruction *I) { | |
| return I->getParent() == BB && !isa<PHINode>(I) && | |
| S.isCopyableElement(I) && S.isNonSchedulable(I) && | |
| (SLP->isVectorized(I) || !SLP->getSplitTreeEntries(I).empty()); | |
| })) | |
| return std::nullopt; | |
| if (DoesNotRequireScheduling) { | |
| // If all operands were replaced by copyables, the operands of this node | |
| // might be not, so need to recalculate dependencies for schedule data, | |
| // replaced by copyable schedule data. | |
| for (Instruction *I : make_isa_range<Instruction>(VL)) { | |
| if (HasCopyables && S.isCopyableElement(I)) | |
| continue; | |
| // Expanded binops (e.g. shl X, 1 modeled as add X, X) double the | |
| // dependency count of the duplicated operand. This non-schedulable | |
| // path does not clear operand ScheduleData dependencies the way the | |
| // regular scheduling path does (see CheckIfNeedToClearDeps below), so | |
| // when that operand has more uses than this bundle member, or its | |
| // ScheduleData already has computed dependencies that did not see | |
| // the expanded duplicate use, the schedule's decrement count exceeds | |
| // calculateDependencies' increment count and UnscheduledDeps goes | |
| // negative. Bail out instead of producing an inconsistent schedule. | |
| if (S.isExpandedBinOp(I) && | |
| any_of(enumerate(I->operands()), [&](const auto &P) { | |
| if (S.isExpandedOperand(I, P.index())) | |
| return false; | |
| auto *OpI = dyn_cast<Instruction>(P.value()); | |
| if (!OpI) | |
| return false; | |
| if (!OpI->hasOneUse()) | |
| return true; | |
| ScheduleData *SD = getScheduleData(OpI); | |
| return SD && SD->hasValidDependencies(); | |
| })) | |
| return std::nullopt; | |
| SmallDenseMap<std::pair<Instruction *, Value *>, unsigned> UserOpToNumOps; | |
| for (const Use &U : I->operands()) { | |
| unsigned &NumOps = | |
| UserOpToNumOps.try_emplace(std::make_pair(I, U.get()), 0) | |
| .first->getSecond(); | |
| ++NumOps; | |
| if (auto *Op = dyn_cast<Instruction>(U.get()); | |
| Op && areAllOperandsReplacedByCopyableData(I, Op, *SLP, NumOps)) { | |
| if (ScheduleData *OpSD = getScheduleData(Op); | |
| OpSD && OpSD->hasValidDependencies()) | |
| // TODO: investigate how to improve it instead of early exiting. | |
| return std::nullopt; | |
| } | |
| } | |
| } | |
| return nullptr; | |
| } | |
| // Any schedulable copyable with split vectorize parent - skip, not supported | |
| // currently. | |
| // TODO: investigate fix for this early exit. | |
| if (S.areInstructionsWithCopyableElements() && EI.UserTE && | |
| EI.UserTE->State == TreeEntry::SplitVectorize && | |
| any_of(VL, [&](Value *V) { | |
| return !S.isNonSchedulable(V) && S.isCopyableElement(V); | |
| })) | |
| return std::nullopt; | |
| // Reject modeling the duplicated source operand of an expanded binop as a | |
| // copyable element. shl X, 1 is represented as add X, X, so the parent uses X | |
| // on a second operand edge that stays a plain (gathered) operand. At schedule | |
| // time that duplicated use is decremented against X's own ScheduleData, which | |
| // calculateDependencies attributed to the copyable instead, so X's | |
| // unscheduled-deps counter goes negative and trips the assertion. Only the | |
| // values this bundle models as copyable that are the expanded binop source | |
| // operand are checked, matched by value so the detection is unaffected by the | |
| // parent node's operand reordering or reuse shuffle mask. | |
| // TODO: investigate modeling such operands correctly. | |
| if (S.areInstructionsWithCopyableElements() && EI.UserTE && | |
| EI.UserTE->hasState()) { | |
| for (Value *V : make_filter_range( | |
| VL, [&](Value *V) { return S.isCopyableElement(V); })) { | |
| if (any_of(make_isa_range<Instruction>(EI.UserTE->Scalars), | |
| [&](Instruction *I) { | |
| return EI.UserTE->isExpandedBinOp(I) && | |
| I->getOperand(0) == V; | |
| })) | |
| return std::nullopt; | |
| } | |
| } | |
| // The lanes cannot depend on each other, so the bundle with the operand of | |
| // another member is never scheduled. | |
| if (!HasCopyables && | |
| any_of(make_isa_range<Instruction>(VL), [&](Instruction *I) { | |
| return any_of(I->operand_values(), | |
| [&](const Value *Op) { return is_contained(VL, Op); }); | |
| })) | |
| return std::nullopt; | |
| // Initialize the instruction bundle. | |
| Instruction *OldScheduleEnd = ScheduleEnd; | |
| LLVM_DEBUG(dbgs() << "SLP: bundle: " << *S.getMainOp() << "\n"); | |
| auto TryScheduleBundleImpl = [=](bool ReSchedule, ScheduleBundle &Bundle) { | |
| // Clear deps or recalculate the region, if the memory instruction is a | |
| // copyable. It may have memory deps, which must be recalculated. | |
| SmallVector<ScheduleData *> ControlDependentMembers; | |
| // Recompute the direct dependencies of copyable operands deferred by a | |
| // previously scheduled bundle. The node that introduced the extra direct | |
| // use is now part of the tree, so clearing and recalculating the | |
| // dependencies here accounts for that use. | |
| // If the schedule is not reset in this round, skip already scheduled | |
| // operands: clearing their dependencies also resets the scheduled flag, | |
| // so they would enter the ready list and be scheduled twice, decrementing | |
| // the dependency counts of their operands once too many. | |
| const bool WillReSchedule = | |
| ReSchedule || (OldScheduleEnd && ScheduleEnd != OldScheduleEnd); | |
| auto NeedsDependencyUpdate = [&](ScheduleData *SD) { | |
| return isInSchedulingRegion(*SD) && | |
| (WillReSchedule || !SD->isScheduled()); | |
| }; | |
| for (ScheduleData *SD : | |
| make_filter_range(RecalcCopyableOperandDeps, NeedsDependencyUpdate)) { | |
| if (SD->hasValidDependencies()) | |
| SD->clearDirectDependencies(); | |
| ControlDependentMembers.push_back(SD); | |
| } | |
| RecalcCopyableOperandDeps.clear(); | |
| auto CheckIfNeedToClearDeps = [&](ScheduleBundle &Bundle) { | |
| SmallDenseMap<std::pair<Instruction *, Value *>, unsigned> UserOpToNumOps; | |
| for (ScheduleEntity *SE : Bundle.getBundle()) { | |
| if (ScheduleCopyableData *SD = dyn_cast<ScheduleCopyableData>(SE)) { | |
| if (ScheduleData *BundleMember = getScheduleData(SD->getInst()); | |
| BundleMember && BundleMember->hasValidDependencies()) { | |
| BundleMember->clearDirectDependencies(); | |
| if (RegionHasStackSave || | |
| !isGuaranteedToTransferExecutionToSuccessor( | |
| BundleMember->getInst()) || | |
| isWriteVisibleOnUnwind(BundleMember->getInst())) | |
| ControlDependentMembers.push_back(BundleMember); | |
| } | |
| continue; | |
| } | |
| auto *SD = cast<ScheduleData>(SE); | |
| if (SD->hasValidDependencies() && | |
| (!S.areInstructionsWithCopyableElements() || | |
| !S.isCopyableElement(SD->getInst())) && | |
| !getScheduleCopyableData(SD->getInst()).empty() && EI.UserTE && | |
| EI.UserTE->hasState() && | |
| (!EI.UserTE->hasCopyableElements() || | |
| !EI.UserTE->isCopyableElement(SD->getInst()))) | |
| SD->clearDirectDependencies(); | |
| const bool IsExpandedBinOp = S.isExpandedBinOp(SD->getInst()); | |
| for (const Use &U : SD->getInst()->operands()) { | |
| unsigned &NumOps = | |
| UserOpToNumOps | |
| .try_emplace(std::make_pair(SD->getInst(), U.get()), 0) | |
| .first->getSecond(); | |
| ++NumOps; | |
| auto *Op = dyn_cast<Instruction>(U.get()); | |
| if (Op && areAllOperandsReplacedByCopyableData(SD->getInst(), Op, | |
| *SLP, NumOps)) { | |
| if (ScheduleData *OpSD = getScheduleData(Op); | |
| OpSD && OpSD->hasValidDependencies()) { | |
| OpSD->clearDirectDependencies(); | |
| if (RegionHasStackSave || | |
| !isGuaranteedToTransferExecutionToSuccessor( | |
| OpSD->getInst()) || | |
| isWriteVisibleOnUnwind(OpSD->getInst())) | |
| ControlDependentMembers.push_back(OpSD); | |
| // areAllOperandsReplacedByCopyableData() returned true, so every | |
| // tree entry that currently contains this instruction models Op | |
| // as a copyable element and Op's recomputed dependencies do not | |
| // count this def-use edge. The bundle being scheduled is a new | |
| // node for the same instruction that is not registered in the | |
| // tree yet and that uses Op directly (Op is a real operand here). | |
| // Once that node is added to the tree the direct use must be | |
| // counted, otherwise the scheduler decrements Op more times than | |
| // its dependency count and trips the unscheduled-deps assertion. | |
| // Defer the operand's dependency recomputation to the next bundle | |
| // scheduling, when the new node is part of the tree and the | |
| // direct use is accounted for. | |
| RecalcCopyableOperandDeps.insert(OpSD); | |
| continue; | |
| } | |
| } | |
| if (IsExpandedBinOp) { | |
| if (ScheduleData *OpSD = getScheduleData(U.get()); | |
| OpSD && OpSD->hasValidDependencies()) { | |
| OpSD->clearDirectDependencies(); | |
| ControlDependentMembers.push_back(OpSD); | |
| continue; | |
| } | |
| } | |
| // areAllOperandsReplacedByCopyableData() can be false while Op is | |
| // still modeled as a copyable element in one or more already built | |
| // nodes (e.g. when another node that shares an instruction with this | |
| // one uses Op directly). This newly built node, not registered in the | |
| // tree yet, also uses Op directly, so Op's real ScheduleData | |
| // dependency count - which mixes direct def-use edges with copyable | |
| // uses - does not yet account for this direct use. Defer its | |
| // recomputation to the next bundle scheduling, when this node is part | |
| // of the tree, so the direct use is counted; otherwise the scheduler | |
| // decrements Op more times than its dependency count and trips the | |
| // unscheduled-deps assertion. Op's current dependencies are kept (not | |
| // cleared) so it stays schedulable for this bundle's dependency | |
| // calculation. | |
| if (Op) { | |
| if (ScheduleData *OpSD = getScheduleData(Op); | |
| OpSD && !getScheduleCopyableData(Op).empty()) | |
| RecalcCopyableOperandDeps.insert(OpSD); | |
| } | |
| } | |
| } | |
| }; | |
| // The scheduling region got new instructions at the lower end (or it is a | |
| // new region for the first bundle). This makes it necessary to | |
| // recalculate all dependencies. | |
| // It is seldom that this needs to be done a second time after adding the | |
| // initial bundle to the region. | |
| if (OldScheduleEnd && ScheduleEnd != OldScheduleEnd) { | |
| clearDependencies(); | |
| ReSchedule = true; | |
| } | |
| SmallPtrSet<Value *, 4> ExpandedOps; | |
| for (Value *V : VL) { | |
| if (S.isExpandedBinOp(V)) | |
| ExpandedOps.insert(V); | |
| } | |
| // Check if the bundle data has deps for copyable elements already. In | |
| // this case need to reset deps and recalculate it. | |
| if (Bundle && !Bundle.getBundle().empty()) { | |
| if (!ScheduleCopyableDataMap.empty() || !ExpandedOps.empty()) | |
| CheckIfNeedToClearDeps(Bundle); | |
| LLVM_DEBUG(dbgs() << "SLP: try schedule bundle " << Bundle << " in block " | |
| << BB->getName() << "\n"); | |
| calculateDependencies(Bundle, /*InsertInReadyList=*/!ReSchedule, SLP, | |
| ExpandedOps, ControlDependentMembers); | |
| } else if (!ControlDependentMembers.empty()) { | |
| ScheduleBundle Invalid = ScheduleBundle::invalid(); | |
| calculateDependencies(Invalid, /*InsertInReadyList=*/!ReSchedule, SLP, | |
| ExpandedOps, ControlDependentMembers); | |
| } | |
| if (ReSchedule) { | |
| resetSchedule(); | |
| initialFillReadyList(ReadyInsts); | |
| } | |
| // Now try to schedule the new bundle or (if no bundle) just calculate | |
| // dependencies. As soon as the bundle is "ready" it means that there are no | |
| // cyclic dependencies and we can schedule it. Note that's important that we | |
| // don't "schedule" the bundle yet. | |
| while (((!Bundle && ReSchedule) || (Bundle && !Bundle.isReady())) && | |
| !ReadyInsts.empty()) { | |
| ScheduleEntity *Picked = ReadyInsts.pop_back_val(); | |
| assert(Picked->isReady() && "must be ready to schedule"); | |
| schedule(*SLP, S, EI, Picked, ReadyInsts); | |
| if (Picked == &Bundle) | |
| break; | |
| } | |
| }; | |
| // Make sure that the scheduling region contains all | |
| // instructions of the bundle. | |
| for (Value *V : make_filter_range( | |
| VL, [&](Value *V) { return !S.isNonSchedulable(V); })) { | |
| if (!extendSchedulingRegion(V, S)) { | |
| // If the scheduling region got new instructions at the lower end (or it | |
| // is a new region for the first bundle). This makes it necessary to | |
| // recalculate all dependencies. | |
| // Otherwise the compiler may crash trying to incorrectly calculate | |
| // dependencies and emit instruction in the wrong order at the actual | |
| // scheduling. | |
| ScheduleBundle Invalid = ScheduleBundle::invalid(); | |
| TryScheduleBundleImpl(/*ReSchedule=*/false, Invalid); | |
| return std::nullopt; | |
| } | |
| } | |
| bool ReSchedule = false; | |
| for (Value *V : make_filter_range( | |
| VL, [&](Value *V) { return !S.isNonSchedulable(V); })) { | |
| SmallVector<ScheduleCopyableData *> CopyableData = | |
| getScheduleCopyableData(cast<Instruction>(V)); | |
| if (!CopyableData.empty()) { | |
| for (ScheduleCopyableData *SD : CopyableData) | |
| ReadyInsts.remove(SD); | |
| } | |
| ScheduleData *BundleMember = getScheduleData(V); | |
| assert((BundleMember || S.isCopyableElement(V)) && | |
| "no ScheduleData for bundle member (maybe not in same basic block)"); | |
| if (!BundleMember) | |
| continue; | |
| // Make sure we don't leave the pieces of the bundle in the ready list when | |
| // whole bundle might not be ready. | |
| ReadyInsts.remove(BundleMember); | |
| if (ArrayRef<ScheduleBundle *> Bundles = getScheduleBundles(V); | |
| !Bundles.empty()) { | |
| for (ScheduleBundle *B : Bundles) | |
| ReadyInsts.remove(B); | |
| } | |
| if (!S.isCopyableElement(V) && !S.isExpandedBinOp(V) && | |
| !BundleMember->isScheduled()) | |
| continue; | |
| // A bundle member was scheduled as single instruction before and now | |
| // needs to be scheduled as part of the bundle. We just get rid of the | |
| // existing schedule. | |
| // A bundle member has deps calculated before it was copyable element - need | |
| // to reschedule. | |
| LLVM_DEBUG(dbgs() << "SLP: reset schedule because " << *BundleMember | |
| << " was already scheduled\n"); | |
| ReSchedule = true; | |
| } | |
| ScheduleBundle &Bundle = buildBundle(VL, S, EI); | |
| TryScheduleBundleImpl(ReSchedule, Bundle); | |
| if (!Bundle.isReady()) { | |
| // Copyable data scheduling is just removed. | |
| for (ScheduleEntity *BD : | |
| make_filter_range(Bundle.getBundle(), [](ScheduleEntity *BD) { | |
| return !isa<ScheduleCopyableData>(BD); | |
| })) { | |
| if (BD->isReady()) { | |
| ArrayRef<ScheduleBundle *> Bundles = getScheduleBundles(BD->getInst()); | |
| if (Bundles.empty()) { | |
| ReadyInsts.insert(BD); | |
| continue; | |
| } | |
| for (ScheduleBundle *B : Bundles) | |
| if (B->isReady()) | |
| ReadyInsts.insert(B); | |
| } | |
| } | |
| cancelScheduling(Bundle); | |
| ScheduledBundlesList.pop_back(); | |
| SmallVector<ScheduleData *> ControlDependentMembers; | |
| for (Value *V : make_filter_range( | |
| VL, [&](Value *V) { return !S.isNonSchedulable(V); })) { | |
| auto *I = cast<Instruction>(V); | |
| if (S.isExpandedBinOp(I)) { | |
| for (Value *Op : I->operands()) { | |
| if (ScheduleData *OpSD = getScheduleData(Op); | |
| OpSD && OpSD->hasValidDependencies()) { | |
| OpSD->clearDirectDependencies(); | |
| ControlDependentMembers.push_back(OpSD); | |
| } | |
| } | |
| } | |
| if (S.isCopyableElement(I)) { | |
| // Need to recalculate dependencies for the actual schedule data, even | |
| // if they were already cleared by the failed bundle scheduling attempt. | |
| if (ScheduleData *OpSD = getScheduleData(I)) { | |
| OpSD->clearDirectDependencies(); | |
| ControlDependentMembers.push_back(OpSD); | |
| if (any_of(VL, [&](Value *V) { return S.isExpandedBinOp(V); })) { | |
| // Clear scheduling data for all operands, if this node is operand | |
| // of the expanded instruction. | |
| for (Value *Op : I->operands()) { | |
| if (ScheduleData *OpSD = getScheduleData(Op); | |
| OpSD && OpSD->hasValidDependencies()) { | |
| OpSD->clearDirectDependencies(); | |
| ControlDependentMembers.push_back(OpSD); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| if (!ControlDependentMembers.empty()) { | |
| ScheduleBundle Invalid = ScheduleBundle::invalid(); | |
| SmallPtrSet<Value *, 4> ExpandedOps; | |
| calculateDependencies(Invalid, /*InsertInReadyList=*/false, SLP, | |
| ExpandedOps, ControlDependentMembers); | |
| } | |
| return std::nullopt; | |
| } | |
| return &Bundle; | |
| } | |
| BoUpSLP::ScheduleData *BoUpSLP::BlockScheduling::allocateScheduleDataChunks() { | |
| // Allocate a new ScheduleData for the instruction. | |
| if (ChunkPos >= ChunkSize) { | |
| ScheduleDataChunks.push_back(std::make_unique<ScheduleData[]>(ChunkSize)); | |
| ChunkPos = 0; | |
| } | |
| return &(ScheduleDataChunks.back()[ChunkPos++]); | |
| } | |
| bool BoUpSLP::BlockScheduling::extendSchedulingRegion( | |
| Value *V, const InstructionsState &S) { | |
| Instruction *I = dyn_cast<Instruction>(V); | |
| assert(I && "bundle member must be an instruction"); | |
| if (getScheduleData(I)) | |
| return true; | |
| if (!ScheduleStart) { | |
| // It's the first instruction in the new region. | |
| initScheduleData(I, I->getNextNode(), nullptr, nullptr); | |
| ScheduleStart = I; | |
| ScheduleEnd = I->getNextNode(); | |
| assert(ScheduleEnd && "tried to vectorize a terminator?"); | |
| LLVM_DEBUG(dbgs() << "SLP: initialize schedule region to " << *I << "\n"); | |
| return true; | |
| } | |
| // Search up and down at the same time, because we don't know if the new | |
| // instruction is above or below the existing scheduling region. | |
| // Ignore debug info (and other "AssumeLike" intrinsics) so that's not counted | |
| // against the budget. Otherwise debug info could affect codegen. | |
| BasicBlock::reverse_iterator UpIter = | |
| ++ScheduleStart->getIterator().getReverse(); | |
| BasicBlock::reverse_iterator UpperEnd = BB->rend(); | |
| BasicBlock::iterator DownIter = ScheduleEnd->getIterator(); | |
| BasicBlock::iterator LowerEnd = BB->end(); | |
| // Never skip the looked-for instruction itself: a bundle member may be an | |
| // assume-like intrinsic (e.g. a copyable lane), and skipping it makes the | |
| // scan miss it and extend the region in the wrong direction. | |
| auto IsAssumeLikeIntr = [I](const Instruction &Cur) { | |
| auto *II = dyn_cast<IntrinsicInst>(&Cur); | |
| return &Cur != I && II && II->isAssumeLikeIntrinsic(); | |
| }; | |
| UpIter = std::find_if_not(UpIter, UpperEnd, IsAssumeLikeIntr); | |
| DownIter = std::find_if_not(DownIter, LowerEnd, IsAssumeLikeIntr); | |
| while (UpIter != UpperEnd && DownIter != LowerEnd && &*UpIter != I && | |
| &*DownIter != I) { | |
| if (++ScheduleRegionSize > ScheduleRegionSizeLimit) { | |
| LLVM_DEBUG(dbgs() << "SLP: exceeded schedule region size limit\n"); | |
| return false; | |
| } | |
| ++UpIter; | |
| ++DownIter; | |
| UpIter = std::find_if_not(UpIter, UpperEnd, IsAssumeLikeIntr); | |
| DownIter = std::find_if_not(DownIter, LowerEnd, IsAssumeLikeIntr); | |
| } | |
| if (DownIter == LowerEnd || (UpIter != UpperEnd && &*UpIter == I)) { | |
| assert(I->getParent() == ScheduleStart->getParent() && | |
| "Instruction is in wrong basic block."); | |
| initScheduleData(I, ScheduleStart, nullptr, FirstLoadStoreInRegion); | |
| ScheduleStart = I; | |
| LLVM_DEBUG(dbgs() << "SLP: extend schedule region start to " << *I | |
| << "\n"); | |
| return true; | |
| } | |
| assert((UpIter == UpperEnd || (DownIter != LowerEnd && &*DownIter == I)) && | |
| "Expected to reach top of the basic block or instruction down the " | |
| "lower end."); | |
| assert(I->getParent() == ScheduleEnd->getParent() && | |
| "Instruction is in wrong basic block."); | |
| initScheduleData(ScheduleEnd, I->getNextNode(), LastLoadStoreInRegion, | |
| nullptr); | |
| ScheduleEnd = I->getNextNode(); | |
| assert(ScheduleEnd && "tried to vectorize a terminator?"); | |
| LLVM_DEBUG(dbgs() << "SLP: extend schedule region end to " << *I << "\n"); | |
| return true; | |
| } | |
| void BoUpSLP::BlockScheduling::initScheduleData(Instruction *FromI, | |
| Instruction *ToI, | |
| ScheduleData *PrevLoadStore, | |
| ScheduleData *NextLoadStore) { | |
| ScheduleData *CurrentLoadStore = PrevLoadStore; | |
| for (Instruction *I = FromI; I != ToI; I = I->getNextNode()) { | |
| // No need to allocate data for non-schedulable instructions. | |
| if (isa<PHINode>(I)) | |
| continue; | |
| ScheduleData *SD = ScheduleDataMap.lookup(I); | |
| if (!SD) { | |
| SD = allocateScheduleDataChunks(); | |
| ScheduleDataMap[I] = SD; | |
| } | |
| assert(!isInSchedulingRegion(*SD) && | |
| "new ScheduleData already in scheduling region"); | |
| SD->init(SchedulingRegionID, I); | |
| auto CanIgnoreLoad = [](const Instruction *I) { | |
| const auto *LI = dyn_cast<LoadInst>(I); | |
| // If there is a simple load marked as invariant, we can ignore it. | |
| // But, in the (unlikely) case of non-simple invariant load, | |
| // we should not ignore it. | |
| return LI && LI->isSimple() && | |
| LI->getMetadata(LLVMContext::MD_invariant_load); | |
| }; | |
| if (I->mayReadOrWriteMemory() && | |
| // Simple InvariantLoad does not depend on other memory accesses. | |
| !CanIgnoreLoad(I) && | |
| (!isa<IntrinsicInst>(I) || | |
| (cast<IntrinsicInst>(I)->getIntrinsicID() != Intrinsic::sideeffect && | |
| cast<IntrinsicInst>(I)->getIntrinsicID() != | |
| Intrinsic::pseudoprobe))) { | |
| // Update the linked list of memory accessing instructions. | |
| if (CurrentLoadStore) { | |
| CurrentLoadStore->setNextLoadStore(SD); | |
| } else { | |
| FirstLoadStoreInRegion = SD; | |
| } | |
| CurrentLoadStore = SD; | |
| } | |
| if (match(I, m_Intrinsic<Intrinsic::stacksave>()) || | |
| match(I, m_Intrinsic<Intrinsic::stackrestore>())) | |
| RegionHasStackSave = true; | |
| if (I->mayThrow()) | |
| RegionHasMayThrow = true; | |
| } | |
| if (NextLoadStore) { | |
| if (CurrentLoadStore) | |
| CurrentLoadStore->setNextLoadStore(NextLoadStore); | |
| } else { | |
| LastLoadStoreInRegion = CurrentLoadStore; | |
| } | |
| } | |
| void BoUpSLP::BlockScheduling::calculateDependencies( | |
| ScheduleBundle &Bundle, bool InsertInReadyList, BoUpSLP *SLP, | |
| const SmallPtrSetImpl<Value *> &ExpandedOps, | |
| ArrayRef<ScheduleData *> ControlDeps) { | |
| SmallVector<ScheduleEntity *> WorkList; | |
| auto ProcessNode = [&](ScheduleEntity *SE) { | |
| if (auto *CD = dyn_cast<ScheduleCopyableData>(SE)) { | |
| if (CD->hasValidDependencies()) | |
| return; | |
| LLVM_DEBUG(dbgs() << "SLP: update deps of " << *CD << "\n"); | |
| CD->initDependencies(); | |
| CD->resetUnscheduledDeps(); | |
| const EdgeInfo &EI = CD->getEdgeInfo(); | |
| if (EI.UserTE) { | |
| ArrayRef<Value *> Op = EI.UserTE->getOperand(EI.EdgeIdx); | |
| const auto *It = find(Op, CD->getInst()); | |
| assert(It != Op.end() && "Lane not set"); | |
| SmallPtrSet<Instruction *, 4> Visited; | |
| do { | |
| int Lane = std::distance(Op.begin(), It); | |
| assert(Lane >= 0 && "Lane not set"); | |
| if (isa<StoreInst, InsertValueInst>(EI.UserTE->Scalars[Lane]) && | |
| !EI.UserTE->ReorderIndices.empty()) | |
| Lane = EI.UserTE->ReorderIndices[Lane]; | |
| assert(Lane < static_cast<int>(EI.UserTE->Scalars.size()) && | |
| "Couldn't find extract lane"); | |
| auto *In = cast<Instruction>(EI.UserTE->Scalars[Lane]); | |
| if (EI.UserTE->isCopyableElement(In)) { | |
| // We may have not have related copyable scheduling data, if the | |
| // instruction is non-schedulable. | |
| if (ScheduleCopyableData *UseSD = | |
| getScheduleCopyableData(EI.UserTE->UserTreeIndex, In)) { | |
| CD->incDependencies(); | |
| if (!UseSD->isScheduled()) | |
| CD->incrementUnscheduledDeps(1); | |
| if (!UseSD->hasValidDependencies() || | |
| (InsertInReadyList && UseSD->isReady())) | |
| WorkList.push_back(UseSD); | |
| } | |
| } else if (Visited.insert(In).second) { | |
| if (ScheduleData *UseSD = getScheduleData(In)) { | |
| unsigned ExtraDeps = 1; | |
| // Increment twice, since the operand was expanded in binop. | |
| if (EI.UserTE->isExpandedBinOp(In)) | |
| ExtraDeps = 2; | |
| for_each(seq(ExtraDeps), | |
| [&](unsigned) { CD->incDependencies(); }); | |
| if (!UseSD->isScheduled()) | |
| CD->incrementUnscheduledDeps(ExtraDeps); | |
| if (!UseSD->hasValidDependencies() || | |
| (InsertInReadyList && UseSD->isReady())) | |
| WorkList.push_back(UseSD); | |
| } | |
| } | |
| It = find(make_range(std::next(It), Op.end()), CD->getInst()); | |
| } while (It != Op.end()); | |
| if (CD->isReady() && CD->getDependencies() == 0 && | |
| (EI.UserTE->hasState() && | |
| (EI.UserTE->getMainOp()->getParent() != | |
| CD->getInst()->getParent() || | |
| (isa<PHINode>(EI.UserTE->getMainOp()) && | |
| (EI.UserTE->getMainOp()->hasNUsesOrMore(UsesLimit) || | |
| any_of(EI.UserTE->getMainOp()->users(), [&](User *U) { | |
| auto *IU = dyn_cast<Instruction>(U); | |
| if (!IU) | |
| return true; | |
| return IU->getParent() == EI.UserTE->getMainOp()->getParent(); | |
| })))))) { | |
| // If no uses in the block - mark as having pseudo-use, which cannot | |
| // be scheduled. | |
| // Prevents incorrect def-use tracking between external user and | |
| // actual instruction. | |
| CD->incDependencies(); | |
| CD->incrementUnscheduledDeps(1); | |
| } | |
| } | |
| return; | |
| } | |
| auto *BundleMember = cast<ScheduleData>(SE); | |
| if (BundleMember->hasValidDependencies()) | |
| return; | |
| LLVM_DEBUG(dbgs() << "SLP: update deps of " << *BundleMember << "\n"); | |
| BundleMember->initDependencies(); | |
| BundleMember->resetUnscheduledDeps(); | |
| // Handle def-use chain dependencies. | |
| SmallDenseMap<Value *, unsigned> UserToNumOps; | |
| for (User *U : BundleMember->getInst()->users()) { | |
| if (isa<PHINode>(U)) | |
| continue; | |
| if (ScheduleData *UseSD = getScheduleData(U)) { | |
| // The operand is a copyable element - skip. | |
| unsigned &NumOps = UserToNumOps.try_emplace(U, 0).first->getSecond(); | |
| ++NumOps; | |
| if (areAllOperandsReplacedByCopyableData( | |
| cast<Instruction>(U), BundleMember->getInst(), *SLP, NumOps)) | |
| continue; | |
| unsigned ExtraDeps = 1; | |
| // Increment twice, since the operand was expanded in binop. | |
| for (const TreeEntry *UserTE : SLP->getTreeEntries(U)) { | |
| if (UserTE->isExpandedBinOp(U)) | |
| ++ExtraDeps; | |
| } | |
| if (ExpandedOps.contains(U)) | |
| ++ExtraDeps; | |
| for_each(seq(ExtraDeps), | |
| [&](unsigned) { BundleMember->incDependencies(); }); | |
| if (!UseSD->isScheduled()) | |
| BundleMember->incrementUnscheduledDeps(ExtraDeps); | |
| if (!UseSD->hasValidDependencies() || | |
| (InsertInReadyList && UseSD->isReady())) | |
| WorkList.push_back(UseSD); | |
| } | |
| } | |
| for (ScheduleCopyableData *UseSD : | |
| getScheduleCopyableDataUsers(BundleMember->getInst())) { | |
| BundleMember->incDependencies(); | |
| if (!UseSD->isScheduled()) | |
| BundleMember->incrementUnscheduledDeps(1); | |
| if (!UseSD->hasValidDependencies() || | |
| (InsertInReadyList && UseSD->isReady())) | |
| WorkList.push_back(UseSD); | |
| } | |
| SmallPtrSet<const Instruction *, 4> Visited; | |
| auto MakeControlDependent = [&](Instruction *I) { | |
| // Do not mark control dependent twice. | |
| if (!Visited.insert(I).second) | |
| return; | |
| auto *DepDest = getScheduleData(I); | |
| assert(DepDest && "must be in schedule window"); | |
| DepDest->addControlDependency(BundleMember); | |
| BundleMember->incDependencies(); | |
| if (!DepDest->isScheduled()) | |
| BundleMember->incrementUnscheduledDeps(1); | |
| if (!DepDest->hasValidDependencies() || | |
| (InsertInReadyList && DepDest->isReady())) | |
| WorkList.push_back(DepDest); | |
| }; | |
| // Any instruction which isn't safe to speculate at the beginning of the | |
| // block is control depend on any early exit or non-willreturn call | |
| // which proceeds it. | |
| if (!isGuaranteedToTransferExecutionToSuccessor(BundleMember->getInst())) { | |
| for (Instruction *I = BundleMember->getInst()->getNextNode(); | |
| I != ScheduleEnd; I = I->getNextNode()) { | |
| if (isSafeToSpeculativelyExecute(I, &*BB->begin(), SLP->AC)) | |
| continue; | |
| // Add the dependency | |
| MakeControlDependent(I); | |
| if (!isGuaranteedToTransferExecutionToSuccessor(I)) | |
| // Everything past here must be control dependent on I. | |
| break; | |
| } | |
| } | |
| // A write visible after unwinding must precede the next instruction that | |
| // may throw. | |
| if (isWriteVisibleOnUnwind(BundleMember->getInst())) { | |
| for (Instruction *I = BundleMember->getInst()->getNextNode(); | |
| I != ScheduleEnd; I = I->getNextNode()) { | |
| if (!I->mayThrow()) | |
| continue; | |
| // Add the dependency | |
| MakeControlDependent(I); | |
| break; | |
| } | |
| } | |
| if (RegionHasStackSave) { | |
| // If we have an inalloc alloca instruction, it needs to be scheduled | |
| // after any preceeding stacksave. We also need to prevent any alloca | |
| // from reordering above a preceeding stackrestore. | |
| if (match(BundleMember->getInst(), m_Intrinsic<Intrinsic::stacksave>()) || | |
| match(BundleMember->getInst(), | |
| m_Intrinsic<Intrinsic::stackrestore>())) { | |
| for (Instruction *I = BundleMember->getInst()->getNextNode(); | |
| I != ScheduleEnd; I = I->getNextNode()) { | |
| if (match(I, m_Intrinsic<Intrinsic::stacksave>()) || | |
| match(I, m_Intrinsic<Intrinsic::stackrestore>())) | |
| // Any allocas past here must be control dependent on I, and I | |
| // must be memory dependend on BundleMember->Inst. | |
| break; | |
| if (!isa<AllocaInst>(I)) | |
| continue; | |
| // Add the dependency | |
| MakeControlDependent(I); | |
| } | |
| } | |
| // In addition to the cases handle just above, we need to prevent | |
| // allocas and loads/stores from moving below a stacksave or a | |
| // stackrestore. Avoiding moving allocas below stackrestore is currently | |
| // thought to be conservatism. Moving loads/stores below a stackrestore | |
| // can lead to incorrect code. | |
| if (isa<AllocaInst>(BundleMember->getInst()) || | |
| BundleMember->getInst()->mayReadOrWriteMemory()) { | |
| for (Instruction *I = BundleMember->getInst()->getNextNode(); | |
| I != ScheduleEnd; I = I->getNextNode()) { | |
| if (!match(I, m_Intrinsic<Intrinsic::stacksave>()) && | |
| !match(I, m_Intrinsic<Intrinsic::stackrestore>())) | |
| continue; | |
| // Add the dependency | |
| MakeControlDependent(I); | |
| break; | |
| } | |
| } | |
| } | |
| // Handle the memory dependencies (if any). | |
| ScheduleData *NextLoadStore = BundleMember->getNextLoadStore(); | |
| if (!NextLoadStore) | |
| return; | |
| Instruction *SrcInst = BundleMember->getInst(); | |
| assert(SrcInst->mayReadOrWriteMemory() && | |
| "NextLoadStore list for non memory effecting bundle?"); | |
| MemoryLocation SrcLoc = getLocation(SrcInst); | |
| bool SrcMayWrite = SrcInst->mayWriteToMemory(); | |
| unsigned NumAliased = 0; | |
| unsigned DistToSrc = 1; | |
| bool IsNonSimpleSrc = !SrcLoc.Ptr || !isSimple(SrcInst); | |
| for (ScheduleData *DepDest = NextLoadStore; DepDest; | |
| DepDest = DepDest->getNextLoadStore()) { | |
| assert(isInSchedulingRegion(*DepDest) && "Expected to be in region"); | |
| // We have two limits to reduce the complexity: | |
| // 1) AliasedCheckLimit: It's a small limit to reduce calls to | |
| // SLP->isAliased (which is the expensive part in this loop). | |
| // 2) MaxMemDepDistance: It's for very large blocks and it aborts | |
| // the whole loop (even if the loop is fast, it's quadratic). | |
| // It's important for the loop break condition (see below) to | |
| // check this limit even between two read-only instructions. | |
| Instruction *DepInst = DepDest->getInst(); | |
| bool MayConflict = SrcMayWrite || DepInst->mayWriteToMemory(); | |
| if (DistToSrc >= MaxMemDepDistance || | |
| (MayConflict && (IsNonSimpleSrc || NumAliased >= AliasedCheckLimit || | |
| SLP->isAliased(SrcLoc, SrcInst, DepInst)))) { | |
| // Try to turn a may-alias dependency between distinct, range-checkable | |
| // base objects into a runtime alias check. This covers both concrete | |
| // aliases and the conservative dependencies added once the | |
| // per-instruction alias-check limit is reached, so that a high alias | |
| // count cannot block a region that is otherwise versionable. The | |
| // dropped dependency is recorded so the final scheduleBlock() | |
| // recomputation keeps it dropped consistently. | |
| bool Dropped = false; | |
| if (MayConflict && SLP->Opts.slp_vectorize_with_runtime_alias_checks) { | |
| auto Key = std::make_pair(SrcInst, DepInst); | |
| // A dependency dropped during buildTree() must stay dropped on every | |
| // later recomputation (e.g. the final scheduleBlock()), independent | |
| // of the current mode, so the schedule remains consistent. | |
| if (IgnoredMemDeps.contains(Key)) { | |
| Dropped = true; | |
| } else if (SLP->isCoveredByExistingVersionCheck(BB, SrcInst, | |
| DepInst)) { | |
| // This block is the fast path of an earlier versioning whose | |
| // runtime check already proved these bases disjoint, so the | |
| // dependency can be dropped without emitting a new check. | |
| Dropped = true; | |
| } else if (SLP->isTryingRuntimeAliasChecks()) { | |
| if (SLP->isRuntimeCheckableAliasPair(SrcInst, DepInst) && | |
| SLP->recordRuntimeAliasCheck(BB, SrcInst, DepInst)) { | |
| IgnoredMemDeps.insert(Key); | |
| Dropped = true; | |
| } | |
| } else if (!SLP->hasNonCheckableMemBlocker()) { | |
| // A dependency on a non-simple access (call, a volatile/atomic, or | |
| // anything without a single load/store pointer) cannot be | |
| // range-checked, so it is a hard blocker a retry cannot drop. A | |
| // simple runtime-checkable may-alias pair could instead be dropped | |
| // by a runtime check. Other simple pairs (e.g. same base) are | |
| // resolved statically and are not blockers. | |
| if (IsNonSimpleSrc || !getLoadStorePointerOperand(DepInst) || | |
| !isSimple(DepInst)) | |
| SLP->setHasNonCheckableMemBlocker(true); | |
| else if (!SLP->hasRuntimeCheckableBlockers() && | |
| SLP->isRuntimeCheckableAliasPair(SrcInst, DepInst)) | |
| SLP->setHasRuntimeCheckableBlockers(true); | |
| } | |
| } | |
| if (!Dropped) { | |
| // Count only kept dependencies toward the alias-check limit. Counting | |
| // dropped ones could prematurely trigger the conservative | |
| // (limit-based) dependencies above and add spurious dependencies | |
| // (e.g. between non-overlapping stores to the same base) that would | |
| // block scheduling of an otherwise versionable region. | |
| NumAliased++; | |
| DepDest->addMemoryDependency(BundleMember); | |
| BundleMember->incDependencies(); | |
| if (!DepDest->isScheduled()) | |
| BundleMember->incrementUnscheduledDeps(1); | |
| if (!DepDest->hasValidDependencies() || | |
| (InsertInReadyList && DepDest->isReady())) | |
| WorkList.push_back(DepDest); | |
| } | |
| } | |
| // Example, explaining the loop break condition: Let's assume our | |
| // starting instruction is i0 and MaxMemDepDistance = 3. | |
| // | |
| // +--------v--v--v | |
| // i0,i1,i2,i3,i4,i5,i6,i7,i8 | |
| // +--------^--^--^ | |
| // | |
| // MaxMemDepDistance let us stop alias-checking at i3 and we add | |
| // dependencies from i0 to i3,i4,.. (even if they are not aliased). | |
| // Previously we already added dependencies from i3 to i6,i7,i8 | |
| // (because of MaxMemDepDistance). As we added a dependency from | |
| // i0 to i3, we have transitive dependencies from i0 to i6,i7,i8 | |
| // and we can abort this loop at i6. | |
| if (DistToSrc >= 2 * MaxMemDepDistance) | |
| break; | |
| DistToSrc++; | |
| } | |
| }; | |
| assert((Bundle || !ControlDeps.empty()) && | |
| "expected at least one instruction to schedule"); | |
| if (Bundle) | |
| WorkList.push_back(Bundle.getBundle().front()); | |
| WorkList.append(ControlDeps.begin(), ControlDeps.end()); | |
| SmallPtrSet<ScheduleBundle *, 16> Visited; | |
| while (!WorkList.empty()) { | |
| ScheduleEntity *SD = WorkList.pop_back_val(); | |
| SmallVector<ScheduleBundle *, 1> CopyableBundle; | |
| ArrayRef<ScheduleBundle *> Bundles; | |
| if (auto *CD = dyn_cast<ScheduleCopyableData>(SD)) { | |
| CopyableBundle.push_back(&CD->getBundle()); | |
| Bundles = CopyableBundle; | |
| } else { | |
| Bundles = getScheduleBundles(SD->getInst()); | |
| } | |
| if (Bundles.empty()) { | |
| if (!SD->hasValidDependencies()) | |
| ProcessNode(SD); | |
| if (InsertInReadyList && SD->isReady()) { | |
| ReadyInsts.insert(SD); | |
| LLVM_DEBUG(dbgs() << "SLP: gets ready on update: " << *SD << "\n"); | |
| } | |
| continue; | |
| } | |
| for (ScheduleBundle *Bundle : | |
| make_filter_range(Bundles, [&](ScheduleBundle *Bundle) { | |
| return !Bundle->hasValidDependencies() && | |
| Visited.insert(Bundle).second; | |
| })) { | |
| assert(isInSchedulingRegion(*Bundle) && | |
| "ScheduleData not in scheduling region"); | |
| for_each(Bundle->getBundle(), ProcessNode); | |
| } | |
| if (InsertInReadyList && SD->isReady()) { | |
| for (ScheduleBundle *Bundle : Bundles) { | |
| assert(isInSchedulingRegion(*Bundle) && | |
| "ScheduleData not in scheduling region"); | |
| if (!Bundle->isReady()) | |
| continue; | |
| ReadyInsts.insert(Bundle); | |
| LLVM_DEBUG(dbgs() << "SLP: gets ready on update: " << *Bundle | |
| << "\n"); | |
| } | |
| } | |
| } | |
| } | |
| void BoUpSLP::BlockScheduling::resetSchedule() { | |
| assert(ScheduleStart && | |
| "tried to reset schedule on block which has not been scheduled"); | |
| for_each(ScheduleDataMap, [&](auto &P) { | |
| if (BB != P.first->getParent()) | |
| return; | |
| ScheduleData *SD = P.second; | |
| if (isInSchedulingRegion(*SD)) { | |
| SD->setScheduled(/*Scheduled=*/false); | |
| SD->resetUnscheduledDeps(); | |
| } | |
| }); | |
| for_each(ScheduleCopyableDataMapByInst, [&](auto &P) { | |
| for_each(P.second, [&](ScheduleCopyableData *SD) { | |
| if (isInSchedulingRegion(*SD)) { | |
| SD->setScheduled(/*Scheduled=*/false); | |
| SD->resetUnscheduledDeps(); | |
| } | |
| }); | |
| }); | |
| for_each(ScheduledBundles, [&](auto &P) { | |
| for_each(P.second, [&](ScheduleBundle *Bundle) { | |
| if (isInSchedulingRegion(*Bundle)) | |
| Bundle->setScheduled(/*Scheduled=*/false); | |
| }); | |
| }); | |
| // Reset schedule data for copyable elements. | |
| for (auto &P : ScheduleCopyableDataMap) { | |
| if (isInSchedulingRegion(*P.second)) { | |
| P.second->setScheduled(/*Scheduled=*/false); | |
| P.second->resetUnscheduledDeps(); | |
| } | |
| } | |
| ReadyInsts.clear(); | |
| } | |
| void BoUpSLP::scheduleBlock(const BoUpSLP &R, BlockScheduling *BS) { | |
| if (!BS->ScheduleStart) | |
| return; | |
| LLVM_DEBUG(dbgs() << "SLP: schedule block " << BS->BB->getName() << "\n"); | |
| // The scalars of the trimmed nodes are not vectorized and must not be | |
| // grouped as bundles. Cancel their bundles, recalculate all the dependencies | |
| // and schedule the scalars as separate instructions: they keep their | |
| // original order and stay close to their users. | |
| auto IsTrimmed = [&](const TreeEntry *TE) { | |
| return DeletedNodes.contains(TE) || TransformedToGatherNodes.contains(TE); | |
| }; | |
| const size_t NumBundles = BS->ScheduledBundlesList.size(); | |
| SmallPtrSet<const ScheduleData *, 16> ClearedSDs; | |
| erase_if(BS->ScheduledBundlesList, | |
| [&](const std::unique_ptr<ScheduleBundle> &Bundle) { | |
| const TreeEntry *TE = Bundle->getTreeEntry(); | |
| if (!IsTrimmed(TE)) | |
| return false; | |
| LLVM_DEBUG(dbgs() << "SLP: cancel scheduling of the trimmed node " | |
| << TE->Idx << " bundle " << *Bundle << "\n"); | |
| for (ScheduleEntity *SE : Bundle->getBundle()) | |
| if (ScheduleData *SD = BS->getScheduleData(SE->getInst())) | |
| ClearedSDs.insert(SD); | |
| BS->cancelScheduling(*Bundle); | |
| return true; | |
| }); | |
| if (BS->ScheduledBundlesList.size() != NumBundles) { | |
| // Need to schedule all the instructions with the calculated dependencies, | |
| // not only the users of the remaining bundles, otherwise they are moved | |
| // above all the scheduled instructions. | |
| for (Instruction &I : make_range(BS->ScheduleStart->getIterator(), | |
| BS->ScheduleEnd->getIterator())) | |
| if (ScheduleData *SD = BS->getScheduleData(&I); | |
| SD && SD->hasValidDependencies()) | |
| ClearedSDs.insert(SD); | |
| BS->clearDependencies(); | |
| } | |
| // A key point - if we got here, pre-scheduling was able to find a valid | |
| // scheduling of the sub-graph of the scheduling window which consists | |
| // of all vector bundles and their transitive users. As such, we do not | |
| // need to reschedule anything *outside of* that subgraph. | |
| BS->resetSchedule(); | |
| // For the real scheduling we use a more sophisticated ready-list: it is | |
| // sorted by the original instruction location. This lets the final schedule | |
| // be as close as possible to the original instruction order. | |
| // WARNING: If changing this order causes a correctness issue, that means | |
| // there is some missing dependence edge in the schedule data graph. | |
| struct ScheduleDataCompare { | |
| bool operator()(const ScheduleEntity *SD1, | |
| const ScheduleEntity *SD2) const { | |
| return SD2->getSchedulingPriority() < SD1->getSchedulingPriority(); | |
| } | |
| }; | |
| std::set<ScheduleEntity *, ScheduleDataCompare> ReadyInsts; | |
| // Ensure that all dependency data is updated (for nodes in the sub-graph) | |
| // and fill the ready-list with initial instructions. | |
| int Idx = 0; | |
| for (auto *I = BS->ScheduleStart; I != BS->ScheduleEnd; | |
| I = I->getNextNode()) { | |
| ArrayRef<ScheduleBundle *> Bundles = BS->getScheduleBundles(I); | |
| if (!Bundles.empty()) { | |
| // An instruction that is modeled as a copyable element in some node may | |
| // also be used directly by another node that was registered only after | |
| // this instruction's dependencies were last computed. The deferred | |
| // copyable-operand recomputation (RecalcCopyableOperandDeps) is consumed | |
| // at the next bundle scheduling, which clears the operand's dependencies | |
| // before that node joins the tree, so the recomputation misses the | |
| // direct def-use edge and the count stays too low. The scheduler would | |
| // then decrement the instruction more times than its dependency count, | |
| // tripping the unscheduled-deps assertion. The whole tree is registered | |
| // now, so clear and recompute such dependencies against the full tree. | |
| if (ScheduleData *SD = BS->getScheduleData(I); | |
| SD && SD->hasValidDependencies() && | |
| !BS->getScheduleCopyableData(I).empty()) | |
| SD->clearDirectDependencies(); | |
| for (ScheduleBundle *Bundle : Bundles) { | |
| Bundle->setSchedulingPriority(Idx++); | |
| if (const TreeEntry *TE = Bundle->getTreeEntry(); | |
| TE && Bundle->hasValidDependencies() && TE->UserTreeIndex && | |
| ((TE->UserTreeIndex.UserTE->State == TreeEntry::Vectorize && | |
| TE->UserTreeIndex.UserTE->doesNotNeedToSchedule() && | |
| any_of(TE->UserTreeIndex.UserTE->Scalars, | |
| [&](Value *V) { | |
| return TE->UserTreeIndex.UserTE->isExpandedBinOp(V); | |
| })) || | |
| (TE->UserTreeIndex.EdgeIdx == UINT_MAX && | |
| TE->UserTreeIndex.UserTE->isGather()))) { | |
| for (ScheduleData *SD : | |
| make_isa_range<ScheduleData>(Bundle->getBundle())) | |
| SD->clearDirectDependencies(); | |
| } | |
| if (!Bundle->hasValidDependencies()) { | |
| SmallPtrSet<Value *, 4> ExpandedOps; | |
| BS->calculateDependencies(*Bundle, /*InsertInReadyList=*/false, this, | |
| ExpandedOps); | |
| } | |
| } | |
| SmallVector<ScheduleCopyableData *> SDs = BS->getScheduleCopyableData(I); | |
| for (ScheduleCopyableData *SD : reverse(SDs)) { | |
| ScheduleBundle &Bundle = SD->getBundle(); | |
| Bundle.setSchedulingPriority(Idx++); | |
| if (!Bundle.hasValidDependencies()) { | |
| SmallPtrSet<Value *, 4> ExpandedOps; | |
| BS->calculateDependencies(Bundle, /*InsertInReadyList=*/false, this, | |
| ExpandedOps); | |
| } | |
| } | |
| continue; | |
| } | |
| SmallVector<ScheduleCopyableData *> CopyableData = | |
| BS->getScheduleCopyableDataUsers(I); | |
| if (ScheduleData *SD = BS->getScheduleData(I)) { | |
| // The bundles of the trimmed nodes are cancelled, check the first | |
| // remaining node only. | |
| [[maybe_unused]] ArrayRef<TreeEntry *> SDTEs = getTreeEntries(I); | |
| [[maybe_unused]] const auto *SDTEIt = find_if_not(SDTEs, IsTrimmed); | |
| assert((isVectorLikeInstWithConstOps(SD->getInst()) || | |
| SDTEIt == SDTEs.end() || (*SDTEIt)->doesNotNeedToSchedule() || | |
| doesNotNeedToBeScheduled(I)) && | |
| "scheduler and vectorizer bundle mismatch"); | |
| SD->setSchedulingPriority(Idx++); | |
| if ((!SD->hasValidDependencies() && ClearedSDs.contains(SD)) || | |
| !CopyableData.empty() || | |
| any_of(R.ValueToGatherNodes.lookup(I), [&](const TreeEntry *TE) { | |
| assert(TE->isGather() && "expected gather node"); | |
| return TE->hasState() && TE->hasCopyableElements() && | |
| TE->isCopyableElement(I); | |
| })) { | |
| SD->clearDirectDependencies(); | |
| // Need to calculate deps for these nodes to correctly handle copyable | |
| // dependencies, even if they were cancelled. | |
| // If copyables bundle was cancelled, the deps are cleared and need to | |
| // recalculate them. | |
| ScheduleBundle Bundle; | |
| Bundle.add(SD); | |
| SmallPtrSet<Value *, 4> ExpandedOps; | |
| BS->calculateDependencies(Bundle, /*InsertInReadyList=*/false, this, | |
| ExpandedOps); | |
| } | |
| } | |
| for (ScheduleCopyableData *SD : reverse(CopyableData)) { | |
| ScheduleBundle &Bundle = SD->getBundle(); | |
| Bundle.setSchedulingPriority(Idx++); | |
| if (!Bundle.hasValidDependencies()) { | |
| SmallPtrSet<Value *, 4> ExpandedOps; | |
| BS->calculateDependencies(Bundle, /*InsertInReadyList=*/false, this, | |
| ExpandedOps); | |
| } | |
| } | |
| } | |
| BS->initialFillReadyList(ReadyInsts); | |
| Instruction *LastScheduledInst = BS->ScheduleEnd; | |
| // Do the "real" scheduling. | |
| SmallPtrSet<Instruction *, 16> Scheduled; | |
| while (!ReadyInsts.empty()) { | |
| auto *Picked = *ReadyInsts.begin(); | |
| ReadyInsts.erase(ReadyInsts.begin()); | |
| // Move the scheduled instruction(s) to their dedicated places, if not | |
| // there yet. | |
| if (auto *Bundle = dyn_cast<ScheduleBundle>(Picked)) { | |
| for (const ScheduleEntity *BundleMember : Bundle->getBundle()) { | |
| Instruction *PickedInst = BundleMember->getInst(); | |
| // If copyable must be schedule as part of something else, skip it. | |
| bool IsCopyable = Bundle->getTreeEntry()->isCopyableElement(PickedInst); | |
| if ((IsCopyable && BS->getScheduleData(PickedInst)) || | |
| (!IsCopyable && !Scheduled.insert(PickedInst).second)) | |
| continue; | |
| if (PickedInst->getNextNode() != LastScheduledInst) | |
| PickedInst->moveAfter(LastScheduledInst->getPrevNode()); | |
| LastScheduledInst = PickedInst; | |
| } | |
| if (Bundle->getTreeEntry()->hasCopyableElements()) { | |
| Instruction *MainOp = Bundle->getTreeEntry()->getMainOp(); | |
| for (Instruction *I : | |
| make_isa_range<Instruction>(Bundle->getTreeEntry()->Scalars)) | |
| if (!I->hasOneUse() && Bundle->getTreeEntry()->isCopyableElement(I) && | |
| I->getParent() == MainOp->getParent() && | |
| doesNotNeedToBeScheduled(I) && | |
| !Bundle->getTreeEntry()->getOperations().isNonSchedulable(I)) | |
| I->moveBeforePreserving(LastScheduledInst->getIterator()); | |
| } | |
| EntryToLastInstruction.try_emplace(Bundle->getTreeEntry(), | |
| LastScheduledInst); | |
| } else { | |
| auto *SD = cast<ScheduleData>(Picked); | |
| Instruction *PickedInst = SD->getInst(); | |
| if (PickedInst->getNextNode() != LastScheduledInst) | |
| PickedInst->moveAfter(LastScheduledInst->getPrevNode()); | |
| LastScheduledInst = PickedInst; | |
| } | |
| auto Invalid = InstructionsState::invalid(); | |
| BS->schedule(R, Invalid, EdgeInfo(), Picked, ReadyInsts); | |
| } | |
| // Check that we didn't break any of our invariants. | |
| #ifdef EXPENSIVE_CHECKS | |
| BS->verify(); | |
| #endif | |
| #if !defined(NDEBUG) || defined(EXPENSIVE_CHECKS) | |
| // Check that all schedulable entities got scheduled | |
| for (auto *I = BS->ScheduleStart; I != BS->ScheduleEnd; | |
| I = I->getNextNode()) { | |
| ArrayRef<ScheduleBundle *> Bundles = BS->getScheduleBundles(I); | |
| assert(all_of(Bundles, | |
| [](const ScheduleBundle *Bundle) { | |
| return Bundle->isScheduled(); | |
| }) && | |
| "must be scheduled at this point"); | |
| } | |
| #endif | |
| // Avoid duplicate scheduling of the block. | |
| BS->ScheduleStart = nullptr; | |
| } | |
| unsigned BoUpSLP::getVectorElementSize(Value *V) { | |
| // If V is a store, just return the width of the stored value (or value | |
| // truncated just before storing) without traversing the expression tree. | |
| // This is the common case. | |
| if (auto *Store = dyn_cast<StoreInst>(V)) | |
| return DL->getTypeSizeInBits(Store->getValueOperand()->getType()); | |
| if (auto *IEI = dyn_cast<InsertElementInst>(V)) | |
| return getVectorElementSize(IEI->getOperand(1)); | |
| if (auto *IVI = dyn_cast<InsertValueInst>(V)) | |
| return getVectorElementSize(IVI->getOperand(1)); | |
| auto E = InstrElementSize.find(V); | |
| if (E != InstrElementSize.end()) | |
| return E->second; | |
| // If V is not a store, we can traverse the expression tree to find loads | |
| // that feed it. The type of the loaded value may indicate a more suitable | |
| // width than V's type. We want to base the vector element size on the width | |
| // of memory operations where possible. | |
| SmallVector<std::tuple<Instruction *, BasicBlock *, unsigned>> Worklist; | |
| SmallPtrSet<Instruction *, 16> Visited; | |
| if (auto *I = dyn_cast<Instruction>(V)) { | |
| Worklist.emplace_back(I, I->getParent(), 0); | |
| Visited.insert(I); | |
| } | |
| // Traverse the expression tree in bottom-up order looking for loads. If we | |
| // encounter an instruction we don't yet handle, we give up. | |
| auto Width = 0u; | |
| Value *FirstNonBool = nullptr; | |
| while (!Worklist.empty()) { | |
| auto [I, Parent, Level] = Worklist.pop_back_val(); | |
| // We should only be looking at scalar instructions here. If the current | |
| // instruction has a vector type, skip. | |
| auto *Ty = I->getType(); | |
| if (isa<VectorType>(Ty)) | |
| continue; | |
| if (Ty != Builder.getInt1Ty() && !FirstNonBool) | |
| FirstNonBool = I; | |
| if (Level > Opts.slp_recursion_max_depth) | |
| continue; | |
| // Can we propagate widths through a call instruction | |
| // We can for simple intrinsics | |
| auto IsCompatibleIntrinsic = [](const Instruction *I) -> bool { | |
| const auto *CI = dyn_cast<IntrinsicInst>(I); | |
| if (!CI) | |
| return false; | |
| switch (CI->getIntrinsicID()) { | |
| case Intrinsic::abs: | |
| case Intrinsic::fabs: | |
| case Intrinsic::fma: | |
| case Intrinsic::fmuladd: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| }; | |
| // If the current instruction is a load, update MaxWidth to reflect the | |
| // width of the loaded value. | |
| if (isa<LoadInst, ExtractElementInst, ExtractValueInst>(I)) | |
| Width = std::max<unsigned>(Width, DL->getTypeSizeInBits(Ty)); | |
| // Otherwise, we need to visit the operands of the instruction. We only | |
| // handle the interesting cases from buildTree here. If an operand is an | |
| // instruction we haven't yet visited and from the same basic block as the | |
| // user or the use is a PHI node, we add it to the worklist. | |
| else if (isa<PHINode, CastInst, GetElementPtrInst, CmpInst, SelectInst, | |
| BinaryOperator, UnaryOperator>(I) || | |
| IsCompatibleIntrinsic(I)) { | |
| for (Use &U : I->operands()) { | |
| if (auto *J = dyn_cast<Instruction>(U.get())) | |
| if (Visited.insert(J).second && | |
| (isa<PHINode>(I) || J->getParent() == Parent)) { | |
| Worklist.emplace_back(J, J->getParent(), Level + 1); | |
| continue; | |
| } | |
| if (!FirstNonBool && U.get()->getType() != Builder.getInt1Ty()) | |
| FirstNonBool = U.get(); | |
| } | |
| } else { | |
| break; | |
| } | |
| } | |
| // If we didn't encounter a memory access in the expression tree, or if we | |
| // gave up for some reason, just return the width of V. Otherwise, return the | |
| // maximum width we found. | |
| if (!Width) { | |
| if (V->getType() == Builder.getInt1Ty() && FirstNonBool) | |
| V = FirstNonBool; | |
| Width = DL->getTypeSizeInBits(V->getType()); | |
| } | |
| for (Instruction *I : Visited) | |
| InstrElementSize[I] = Width; | |
| return Width; | |
| } | |
| bool BoUpSLP::collectValuesToDemote( | |
| const TreeEntry &E, bool IsProfitableToDemoteRoot, unsigned &BitWidth, | |
| SmallVectorImpl<unsigned> &ToDemote, DenseSet<const TreeEntry *> &Visited, | |
| const SmallDenseSet<unsigned, 8> &NodesToKeepBWs, unsigned &MaxDepthLevel, | |
| bool &IsProfitableToDemote, bool IsTruncRoot) const { | |
| // We can always demote constants. | |
| if (all_of(E.Scalars, IsaPred<Constant>)) | |
| return true; | |
| unsigned OrigBitWidth = | |
| DL->getTypeSizeInBits(E.Scalars.front()->getType()->getScalarType()); | |
| if (OrigBitWidth == BitWidth) { | |
| MaxDepthLevel = 1; | |
| return true; | |
| } | |
| // Check if the node was analyzed already and must keep its original bitwidth. | |
| if (NodesToKeepBWs.contains(E.Idx)) | |
| return false; | |
| // If the value is not a vectorized instruction in the expression and not used | |
| // by the insertelement instruction and not used in multiple vector nodes, it | |
| // cannot be demoted. | |
| bool IsSignedNode = any_of(E.Scalars, [&](Value *R) { | |
| if (isa<PoisonValue>(R)) | |
| return false; | |
| return !isKnownNonNegative(R, SimplifyQuery(*DL)); | |
| }); | |
| auto IsPotentiallyTruncated = [&](Value *V, unsigned &BitWidth) -> bool { | |
| if (isa<PoisonValue>(V)) | |
| return true; | |
| if (getTreeEntries(V).size() > 1) | |
| return false; | |
| // For lat shuffle of sext/zext with many uses need to check the extra bit | |
| // for unsigned values, otherwise may have incorrect casting for reused | |
| // scalars. | |
| bool IsSignedVal = !isKnownNonNegative(V, SimplifyQuery(*DL)); | |
| if ((!IsSignedNode || IsSignedVal) && OrigBitWidth > BitWidth) { | |
| APInt Mask = APInt::getBitsSetFrom(OrigBitWidth, BitWidth); | |
| if (MaskedValueIsZero(V, Mask, SimplifyQuery(*DL))) | |
| return true; | |
| } | |
| unsigned NumSignBits = ComputeNumSignBits(V, *DL, AC, nullptr, DT); | |
| unsigned BitWidth1 = OrigBitWidth - NumSignBits; | |
| if (IsSignedNode) | |
| ++BitWidth1; | |
| if (auto *I = dyn_cast<Instruction>(V)) { | |
| APInt Mask = DB->getDemandedBits(I); | |
| unsigned BitWidth2 = | |
| std::max<unsigned>(1, Mask.getBitWidth() - Mask.countl_zero()); | |
| while (!IsSignedNode && BitWidth2 < OrigBitWidth) { | |
| APInt Mask = APInt::getBitsSetFrom(OrigBitWidth, BitWidth2 - 1); | |
| if (MaskedValueIsZero(V, Mask, SimplifyQuery(*DL))) | |
| break; | |
| BitWidth2 *= 2; | |
| } | |
| BitWidth1 = std::min(BitWidth1, BitWidth2); | |
| } | |
| BitWidth = std::max(BitWidth, BitWidth1); | |
| return BitWidth > 0 && OrigBitWidth >= (BitWidth * 2); | |
| }; | |
| auto FinalAnalysis = [&]() { | |
| if (!IsProfitableToDemote) | |
| return false; | |
| bool Res = all_of( | |
| E.Scalars, std::bind(IsPotentiallyTruncated, _1, std::ref(BitWidth))); | |
| // Demote gathers. | |
| if (Res && E.isGather()) { | |
| if (E.hasState()) { | |
| if (const TreeEntry *SameTE = | |
| getSameValuesTreeEntry(E.getMainOp(), E.Scalars)) | |
| if (collectValuesToDemote(*SameTE, IsProfitableToDemoteRoot, BitWidth, | |
| ToDemote, Visited, NodesToKeepBWs, | |
| MaxDepthLevel, IsProfitableToDemote, | |
| IsTruncRoot)) { | |
| ToDemote.push_back(E.Idx); | |
| return true; | |
| } | |
| } | |
| // Check possible extractelement instructions bases and final vector | |
| // length. | |
| SmallPtrSet<Value *, 4> UniqueBases; | |
| for (ExtractElementInst *EE : | |
| make_isa_range<ExtractElementInst>(E.Scalars)) | |
| UniqueBases.insert(EE->getVectorOperand()); | |
| const unsigned VF = E.Scalars.size(); | |
| Type *OrigScalarTy = E.Scalars.front()->getType(); | |
| if (UniqueBases.size() <= 2 || | |
| getNumberOfParts(getWidenedType(OrigScalarTy, VF), OrigScalarTy) >= | |
| getNumberOfParts( | |
| getWidenedType( | |
| IntegerType::get(OrigScalarTy->getContext(), BitWidth), | |
| VF), | |
| IntegerType::get(OrigScalarTy->getContext(), BitWidth))) { | |
| ToDemote.push_back(E.Idx); | |
| return true; | |
| } | |
| } | |
| return Res; | |
| }; | |
| if (E.isGather() || !Visited.insert(&E).second || | |
| any_of(E.Scalars, [&](Value *V) { | |
| return !isa<Constant>(V) && all_of(V->users(), [&](User *U) { | |
| return isa<InsertElementInst>(U) && !isVectorized(U); | |
| }); | |
| })) | |
| return FinalAnalysis(); | |
| if (any_of(E.Scalars, [&](Value *V) { | |
| return !isa<Constant>(V) && !all_of(V->users(), [=](User *U) { | |
| return isVectorized(U) || | |
| (E.Idx == 0 && UserIgnoreList && | |
| UserIgnoreList->contains(U)) || | |
| (!isa<CmpInst>(U) && U->getType()->isSized() && | |
| !U->getType()->isScalableTy() && | |
| DL->getTypeSizeInBits(U->getType()) <= BitWidth); | |
| }) && !IsPotentiallyTruncated(V, BitWidth); | |
| })) | |
| return false; | |
| auto ProcessOperands = [&](ArrayRef<const TreeEntry *> Operands, | |
| bool &NeedToExit) { | |
| NeedToExit = false; | |
| unsigned InitLevel = MaxDepthLevel; | |
| for (const TreeEntry *Op : Operands) { | |
| unsigned Level = InitLevel; | |
| if (!collectValuesToDemote(*Op, IsProfitableToDemoteRoot, BitWidth, | |
| ToDemote, Visited, NodesToKeepBWs, Level, | |
| IsProfitableToDemote, IsTruncRoot)) { | |
| if (!IsProfitableToDemote) | |
| return false; | |
| NeedToExit = true; | |
| if (!FinalAnalysis()) | |
| return false; | |
| continue; | |
| } | |
| MaxDepthLevel = std::max(MaxDepthLevel, Level); | |
| } | |
| return true; | |
| }; | |
| auto AttemptCheckBitwidth = | |
| [&](function_ref<bool(unsigned, unsigned)> Checker, bool &NeedToExit) { | |
| // Try all bitwidth < OrigBitWidth. | |
| NeedToExit = false; | |
| unsigned BestFailBitwidth = 0; | |
| for (; BitWidth < OrigBitWidth; BitWidth *= 2) { | |
| if (Checker(BitWidth, OrigBitWidth)) | |
| return true; | |
| if (BestFailBitwidth == 0 && FinalAnalysis()) | |
| BestFailBitwidth = BitWidth; | |
| } | |
| if (BitWidth >= OrigBitWidth) { | |
| if (BestFailBitwidth == 0) { | |
| BitWidth = OrigBitWidth; | |
| return false; | |
| } | |
| MaxDepthLevel = 1; | |
| BitWidth = BestFailBitwidth; | |
| NeedToExit = true; | |
| return true; | |
| } | |
| return false; | |
| }; | |
| auto TryProcessInstruction = | |
| [&](unsigned &BitWidth, ArrayRef<const TreeEntry *> Operands = {}, | |
| function_ref<bool(unsigned, unsigned)> Checker = {}) { | |
| if (Operands.empty()) { | |
| if (!IsTruncRoot) | |
| MaxDepthLevel = 1; | |
| for (Value *V : E.Scalars) | |
| (void)IsPotentiallyTruncated(V, BitWidth); | |
| } else { | |
| // Several vectorized uses? Check if we can truncate it, otherwise - | |
| // exit. | |
| if (any_of(E.Scalars, [&](Value *V) { | |
| return !V->hasOneUse() && !IsPotentiallyTruncated(V, BitWidth); | |
| })) | |
| return false; | |
| bool NeedToExit = false; | |
| if (Checker && !AttemptCheckBitwidth(Checker, NeedToExit)) | |
| return false; | |
| if (NeedToExit) | |
| return true; | |
| if (!ProcessOperands(Operands, NeedToExit)) | |
| return false; | |
| if (NeedToExit) | |
| return true; | |
| } | |
| ++MaxDepthLevel; | |
| // Record the entry that we can demote. | |
| ToDemote.push_back(E.Idx); | |
| return IsProfitableToDemote; | |
| }; | |
| if (E.State == TreeEntry::SplitVectorize) | |
| return TryProcessInstruction( | |
| BitWidth, | |
| {VectorizableTree[E.CombinedEntriesWithIndices.front().first].get(), | |
| VectorizableTree[E.CombinedEntriesWithIndices.back().first].get()}); | |
| if (E.isAltShuffle()) { | |
| // Combining these opcodes may lead to incorrect analysis, skip for now. | |
| auto IsDangerousOpcode = [](unsigned Opcode) { | |
| switch (Opcode) { | |
| case Instruction::Shl: | |
| case Instruction::AShr: | |
| case Instruction::LShr: | |
| case Instruction::UDiv: | |
| case Instruction::SDiv: | |
| case Instruction::URem: | |
| case Instruction::SRem: | |
| return true; | |
| default: | |
| break; | |
| } | |
| return false; | |
| }; | |
| if (IsDangerousOpcode(E.getAltOpcode())) | |
| return FinalAnalysis(); | |
| } | |
| switch (E.getOpcode()) { | |
| // We can always demote truncations and extensions. Since truncations can | |
| // seed additional demotion, we save the truncated value. | |
| case Instruction::Trunc: | |
| if (IsProfitableToDemoteRoot) | |
| IsProfitableToDemote = true; | |
| return TryProcessInstruction(BitWidth); | |
| case Instruction::ZExt: | |
| case Instruction::SExt: | |
| if (E.UserTreeIndex.UserTE && E.UserTreeIndex.UserTE->hasState() && | |
| E.UserTreeIndex.UserTE->getOpcode() == Instruction::BitCast && | |
| E.UserTreeIndex.UserTE->getMainOp()->getType()->isFPOrFPVectorTy()) | |
| return false; | |
| IsProfitableToDemote = true; | |
| return TryProcessInstruction(BitWidth); | |
| // Demote binary ops when all operands can be demoted (may be >2 operands). | |
| case Instruction::Add: | |
| case Instruction::Sub: | |
| case Instruction::Mul: | |
| case Instruction::And: | |
| case Instruction::Or: | |
| case Instruction::Xor: { | |
| return TryProcessInstruction( | |
| BitWidth, map_to_vector(seq<unsigned>(E.getNumOperands()), | |
| [&](unsigned Idx) -> const TreeEntry * { | |
| return getOperandEntry(&E, Idx); | |
| })); | |
| } | |
| case Instruction::Freeze: | |
| return TryProcessInstruction(BitWidth, getOperandEntry(&E, 0)); | |
| case Instruction::Shl: { | |
| // If we are truncating the result of this SHL, and if it's a shift of an | |
| // inrange amount, we can always perform a SHL in a smaller type. | |
| auto ShlChecker = [&](unsigned BitWidth, unsigned) { | |
| // Check the node operands rather than the scalar instructions: lanes | |
| // converted from another opcode (e.g. mul by a power of 2) are emitted | |
| // with the converted shift amounts. | |
| return all_of(E.getOperand(1), [&](Value *V) { | |
| if (isa<PoisonValue>(V)) | |
| return true; | |
| KnownBits AmtKnownBits = computeKnownBits(V, *DL); | |
| return AmtKnownBits.getMaxValue().ult(BitWidth); | |
| }); | |
| }; | |
| return TryProcessInstruction( | |
| BitWidth, {getOperandEntry(&E, 0), getOperandEntry(&E, 1)}, ShlChecker); | |
| } | |
| case Instruction::LShr: { | |
| // If this is a truncate of a logical shr, we can truncate it to a smaller | |
| // lshr iff we know that the bits we would otherwise be shifting in are | |
| // already zeros. | |
| auto LShrChecker = [&](unsigned BitWidth, unsigned OrigBitWidth) { | |
| return all_of(E.Scalars, [&](Value *V) { | |
| if (isa<PoisonValue>(V)) | |
| return true; | |
| APInt ShiftedBits = APInt::getBitsSetFrom(OrigBitWidth, BitWidth); | |
| if (E.isCopyableElement(V)) | |
| return MaskedValueIsZero(V, ShiftedBits, SimplifyQuery(*DL)); | |
| auto *I = cast<Instruction>(V); | |
| KnownBits AmtKnownBits = computeKnownBits(I->getOperand(1), *DL); | |
| return AmtKnownBits.getMaxValue().ult(BitWidth) && | |
| MaskedValueIsZero(I->getOperand(0), ShiftedBits, | |
| SimplifyQuery(*DL)); | |
| }); | |
| }; | |
| return TryProcessInstruction( | |
| BitWidth, {getOperandEntry(&E, 0), getOperandEntry(&E, 1)}, | |
| LShrChecker); | |
| } | |
| case Instruction::AShr: { | |
| // If this is a truncate of an arithmetic shr, we can truncate it to a | |
| // smaller ashr iff we know that all the bits from the sign bit of the | |
| // original type and the sign bit of the truncate type are similar. | |
| auto AShrChecker = [&](unsigned BitWidth, unsigned OrigBitWidth) { | |
| return all_of(E.Scalars, [&](Value *V) { | |
| if (isa<PoisonValue>(V)) | |
| return true; | |
| unsigned ShiftedBits = OrigBitWidth - BitWidth; | |
| if (E.isCopyableElement(V)) | |
| return ShiftedBits < ComputeNumSignBits(V, *DL, AC, nullptr, DT); | |
| auto *I = cast<Instruction>(V); | |
| KnownBits AmtKnownBits = computeKnownBits(I->getOperand(1), *DL); | |
| return AmtKnownBits.getMaxValue().ult(BitWidth) && | |
| ShiftedBits < | |
| ComputeNumSignBits(I->getOperand(0), *DL, AC, nullptr, DT); | |
| }); | |
| }; | |
| return TryProcessInstruction( | |
| BitWidth, {getOperandEntry(&E, 0), getOperandEntry(&E, 1)}, | |
| AShrChecker); | |
| } | |
| case Instruction::UDiv: | |
| case Instruction::URem: { | |
| // UDiv and URem can be truncated if all the truncated bits are zero. | |
| auto Checker = [&](unsigned BitWidth, unsigned OrigBitWidth) { | |
| assert(BitWidth <= OrigBitWidth && "Unexpected bitwidths!"); | |
| return all_of(E.Scalars, [&](Value *V) { | |
| APInt Mask = APInt::getBitsSetFrom(OrigBitWidth, BitWidth); | |
| if (E.hasCopyableElements() && E.isCopyableElement(V)) | |
| return MaskedValueIsZero(V, Mask, SimplifyQuery(*DL)); | |
| auto *I = cast<Instruction>(V); | |
| return MaskedValueIsZero(I->getOperand(0), Mask, SimplifyQuery(*DL)) && | |
| MaskedValueIsZero(I->getOperand(1), Mask, SimplifyQuery(*DL)); | |
| }); | |
| }; | |
| return TryProcessInstruction( | |
| BitWidth, {getOperandEntry(&E, 0), getOperandEntry(&E, 1)}, Checker); | |
| } | |
| // We can demote selects if we can demote their true and false values. | |
| case Instruction::Select: { | |
| return TryProcessInstruction( | |
| BitWidth, {getOperandEntry(&E, 1), getOperandEntry(&E, 2)}); | |
| } | |
| // We can demote phis if we can demote all their incoming operands. | |
| case Instruction::PHI: { | |
| const unsigned NumOps = E.getNumOperands(); | |
| SmallVector<const TreeEntry *> Ops(NumOps); | |
| transform(seq<unsigned>(0, NumOps), Ops.begin(), | |
| [&](unsigned Idx) { return getOperandEntry(&E, Idx); }); | |
| return TryProcessInstruction(BitWidth, Ops); | |
| } | |
| case Instruction::Call: { | |
| auto *IC = dyn_cast<IntrinsicInst>(E.getMainOp()); | |
| if (!IC) | |
| break; | |
| Intrinsic::ID ID = getVectorIntrinsicIDForCall(IC, TLI); | |
| if (ID != Intrinsic::abs && ID != Intrinsic::smin && | |
| ID != Intrinsic::smax && ID != Intrinsic::umin && ID != Intrinsic::umax) | |
| break; | |
| SmallVector<const TreeEntry *, 2> Operands(1, getOperandEntry(&E, 0)); | |
| function_ref<bool(unsigned, unsigned)> CallChecker; | |
| auto CompChecker = [&](unsigned BitWidth, unsigned OrigBitWidth) { | |
| assert(BitWidth <= OrigBitWidth && "Unexpected bitwidths!"); | |
| return all_of(E.Scalars, [&](Value *V) { | |
| if (ID == Intrinsic::umin || ID == Intrinsic::umax) { | |
| APInt Mask = APInt::getBitsSetFrom(OrigBitWidth, BitWidth); | |
| // A copyable V stands for umax/umin(V, identity), which equals V. | |
| if (E.isCopyableElement(V)) | |
| return MaskedValueIsZero(V, Mask, SimplifyQuery(*DL)); | |
| auto *I = cast<Instruction>(V); | |
| return MaskedValueIsZero(I->getOperand(0), Mask, | |
| SimplifyQuery(*DL)) && | |
| MaskedValueIsZero(I->getOperand(1), Mask, SimplifyQuery(*DL)); | |
| } | |
| assert((ID == Intrinsic::smin || ID == Intrinsic::smax) && | |
| "Expected min/max intrinsics only."); | |
| unsigned SignBits = OrigBitWidth - BitWidth; | |
| APInt Mask = APInt::getBitsSetFrom(OrigBitWidth, BitWidth - 1); | |
| auto CheckOp = [&](Value *Op) { | |
| unsigned OpSignBits = ComputeNumSignBits(Op, *DL, AC, nullptr, DT); | |
| return SignBits <= OpSignBits && | |
| ((SignBits != OpSignBits && | |
| !isKnownNonNegative(Op, SimplifyQuery(*DL))) || | |
| MaskedValueIsZero(Op, Mask, SimplifyQuery(*DL))); | |
| }; | |
| // A copyable V stands for smax/smin(V, identity), which equals V. | |
| if (E.isCopyableElement(V)) | |
| return CheckOp(V) && | |
| (isTypeLimitNeutral(V, ID == Intrinsic::smax, *DL) || | |
| ComputeMaxSignificantBits(V, *DL, nullptr, nullptr, DT) <= | |
| BitWidth); | |
| auto *I = cast<Instruction>(V); | |
| return CheckOp(I->getOperand(0)) && CheckOp(I->getOperand(1)); | |
| }); | |
| }; | |
| auto AbsChecker = [&](unsigned BitWidth, unsigned OrigBitWidth) { | |
| assert(BitWidth <= OrigBitWidth && "Unexpected bitwidths!"); | |
| return all_of(E.Scalars, [&](Value *V) { | |
| auto *I = cast<Instruction>(V); | |
| unsigned SignBits = OrigBitWidth - BitWidth; | |
| APInt Mask = APInt::getBitsSetFrom(OrigBitWidth, BitWidth - 1); | |
| unsigned Op0SignBits = | |
| ComputeNumSignBits(I->getOperand(0), *DL, AC, nullptr, DT); | |
| return SignBits <= Op0SignBits && | |
| ((SignBits != Op0SignBits && | |
| !isKnownNonNegative(I->getOperand(0), SimplifyQuery(*DL))) || | |
| MaskedValueIsZero(I->getOperand(0), Mask, SimplifyQuery(*DL))); | |
| }); | |
| }; | |
| if (ID != Intrinsic::abs) { | |
| Operands.push_back(getOperandEntry(&E, 1)); | |
| CallChecker = CompChecker; | |
| } else { | |
| CallChecker = AbsChecker; | |
| } | |
| InstructionCost BestCost = | |
| std::numeric_limits<InstructionCost::CostType>::max(); | |
| unsigned BestBitWidth = BitWidth; | |
| unsigned VF = E.Scalars.size(); | |
| // Choose the best bitwidth based on cost estimations. | |
| auto Checker = [&](unsigned BitWidth, unsigned) { | |
| unsigned MinBW = PowerOf2Ceil(BitWidth); | |
| SmallVector<Type *> ArgTys = | |
| buildIntrinsicArgTypes(IC, ID, VF, MinBW, TTI); | |
| auto VecCallCosts = getVectorCallCosts( | |
| IC, getWidenedType(IntegerType::get(IC->getContext(), MinBW), VF), | |
| TTI, TLI, ArgTys, CostKind); | |
| InstructionCost Cost = std::min(VecCallCosts.first, VecCallCosts.second); | |
| if (Cost < BestCost) { | |
| BestCost = Cost; | |
| BestBitWidth = BitWidth; | |
| } | |
| return false; | |
| }; | |
| [[maybe_unused]] bool NeedToExit; | |
| (void)AttemptCheckBitwidth(Checker, NeedToExit); | |
| BitWidth = BestBitWidth; | |
| return TryProcessInstruction(BitWidth, Operands, CallChecker); | |
| } | |
| // Otherwise, conservatively give up. | |
| default: | |
| break; | |
| } | |
| MaxDepthLevel = 1; | |
| return FinalAnalysis(); | |
| } | |
| static RecurKind getRdxKind(Value *V); | |
| void BoUpSLP::computeMinimumValueSizes() { | |
| // Detect booleanized i1 and/or nodes first: they are registered in MinBWs | |
| // like the demoted nodes and processed the same way. | |
| detectBooleanizedNodes(); | |
| // We only attempt to truncate integer expressions. | |
| bool IsStoreOrInsertElt = | |
| getRootNode().hasState() && | |
| (getRootNode().getOpcode() == Instruction::Store || | |
| getRootNode().getOpcode() == Instruction::InsertElement || | |
| getRootNode().getOpcode() == Instruction::InsertValue); | |
| if ((IsStoreOrInsertElt || UserIgnoreList) && | |
| ExtraBitWidthNodes.size() <= 1 && | |
| (!CastMaxMinBWSizes || CastMaxMinBWSizes->second == 0 || | |
| CastMaxMinBWSizes->first / CastMaxMinBWSizes->second <= 2)) | |
| return; | |
| unsigned NodeIdx = 0; | |
| if (IsStoreOrInsertElt && !getRootNode().isGather()) | |
| NodeIdx = 1; | |
| // Ensure the roots of the vectorizable tree don't form a cycle. | |
| assert((VectorizableTree[NodeIdx]->isGather() || NodeIdx != 0 || | |
| !VectorizableTree[NodeIdx]->UserTreeIndex) && | |
| "Unexpected tree is graph."); | |
| // The first value node for store/insertelement is sext/zext/trunc? Skip it, | |
| // resize to the final type. | |
| bool IsTruncRoot = false; | |
| bool IsProfitableToDemoteRoot = !IsStoreOrInsertElt; | |
| SmallVector<unsigned> RootDemotes; | |
| SmallDenseSet<unsigned, 8> NodesToKeepBWs; | |
| if (NodeIdx != 0 && | |
| VectorizableTree[NodeIdx]->State == TreeEntry::Vectorize && | |
| VectorizableTree[NodeIdx]->getOpcode() == Instruction::Trunc) { | |
| assert(IsStoreOrInsertElt && "Expected store/insertelement seeded graph."); | |
| IsTruncRoot = true; | |
| RootDemotes.push_back(NodeIdx); | |
| IsProfitableToDemoteRoot = true; | |
| ++NodeIdx; | |
| } | |
| auto IsAnalyzedMinBWVal = [&](Value *V, unsigned SizeExGathers) { | |
| auto It = AnalyzedMinBWVals.find(V); | |
| return It != AnalyzedMinBWVals.end() && It->second >= SizeExGathers; | |
| }; | |
| // Analyzed the reduction already and not profitable - exit. | |
| if (IsAnalyzedMinBWVal(VectorizableTree[NodeIdx]->Scalars.front(), | |
| getTreeSizeExcludingGathers())) | |
| return; | |
| SmallVector<unsigned> ToDemote; | |
| auto ComputeMaxBitWidth = | |
| [&](const TreeEntry &E, bool IsTopRoot, bool IsProfitableToDemoteRoot, | |
| unsigned Limit, bool IsTruncRoot, bool IsSignedCmp) -> unsigned { | |
| ToDemote.clear(); | |
| // Check if the root is trunc and the next node is gather/buildvector, then | |
| // keep trunc in scalars, which is free in most cases. | |
| if (E.isGather() && IsTruncRoot && E.UserTreeIndex && | |
| !NodesToKeepBWs.contains(E.Idx) && | |
| E.Idx > (IsStoreOrInsertElt ? 2u : 1u) && | |
| all_of(E.Scalars, [&](Value *V) { | |
| return V->hasOneUse() || isa<Constant>(V) || | |
| (!V->hasNUsesOrMore(UsesLimit) && | |
| none_of(V->users(), [&](User *U) { | |
| ArrayRef<TreeEntry *> TEs = getTreeEntries(U); | |
| const TreeEntry *UserTE = E.UserTreeIndex.UserTE; | |
| if (TEs.empty() || is_contained(TEs, UserTE)) | |
| return false; | |
| if (!isa<CastInst, BinaryOperator, FreezeInst, PHINode, | |
| SelectInst>(U) || | |
| isa<SIToFPInst, UIToFPInst>(U) || | |
| (UserTE->hasState() && | |
| (!isa<CastInst, BinaryOperator, FreezeInst, PHINode, | |
| SelectInst>(UserTE->getMainOp()) || | |
| isa<SIToFPInst, UIToFPInst>(UserTE->getMainOp())))) | |
| return true; | |
| unsigned UserTESz = DL->getTypeSizeInBits( | |
| UserTE->Scalars.front()->getType()); | |
| if (all_of(TEs, [&](const TreeEntry *TE) { | |
| auto It = MinBWs.find(TE); | |
| return It != MinBWs.end() && | |
| It->second.first > UserTESz; | |
| })) | |
| return true; | |
| return DL->getTypeSizeInBits(U->getType()) > UserTESz; | |
| })); | |
| })) { | |
| ToDemote.push_back(E.Idx); | |
| const TreeEntry *UserTE = E.UserTreeIndex.UserTE; | |
| auto It = MinBWs.find(UserTE); | |
| if (It != MinBWs.end()) | |
| return It->second.first; | |
| unsigned MaxBitWidth = | |
| DL->getTypeSizeInBits(UserTE->Scalars.front()->getType()); | |
| MaxBitWidth = bit_ceil(MaxBitWidth); | |
| if (MaxBitWidth < 8 && MaxBitWidth > 1) | |
| MaxBitWidth = 8; | |
| return MaxBitWidth; | |
| } | |
| if (!E.hasState()) | |
| return 0u; | |
| unsigned VF = E.getVectorFactor(); | |
| Type *ScalarTy = E.Scalars.front()->getType(); | |
| auto *TreeRootIT = dyn_cast<IntegerType>(ScalarTy->getScalarType()); | |
| if (!TreeRootIT) | |
| return 0u; | |
| unsigned SizeExGathers = getTreeSizeExcludingGathers(); | |
| if (any_of(E.Scalars, | |
| [&](Value *V) { return IsAnalyzedMinBWVal(V, SizeExGathers); })) | |
| return 0u; | |
| unsigned NumParts = | |
| getNumberOfParts(getWidenedType(ScalarTy, VF), ScalarTy); | |
| // The maximum bit width required to represent all the values that can be | |
| // demoted without loss of precision. It would be safe to truncate the roots | |
| // of the expression to this width. | |
| unsigned MaxBitWidth = 1u; | |
| // True if the roots can be zero-extended back to their original type, | |
| // rather than sign-extended. We know that if the leading bits are not | |
| // demanded, we can safely zero-extend. So we initialize IsKnownPositive to | |
| // True. | |
| // Determine if the sign bit of all the roots is known to be zero. If not, | |
| // IsKnownPositive is set to False. | |
| bool IsKnownPositive = !IsSignedCmp && all_of(E.Scalars, [&](Value *R) { | |
| if (isa<PoisonValue>(R)) | |
| return true; | |
| KnownBits Known = computeKnownBits(R, *DL); | |
| return Known.isNonNegative(); | |
| }); | |
| if (!IsKnownPositive && !IsTopRoot && E.UserTreeIndex && | |
| E.UserTreeIndex.UserTE->hasState() && | |
| E.UserTreeIndex.UserTE->getOpcode() == Instruction::UIToFP) | |
| MaxBitWidth = | |
| std::min(DL->getTypeSizeInBits( | |
| E.UserTreeIndex.UserTE->Scalars.front()->getType()), | |
| DL->getTypeSizeInBits(ScalarTy)); | |
| // We first check if all the bits of the roots are demanded. If they're not, | |
| // we can truncate the roots to this narrower type. | |
| auto IsNotPoison = [](Value *Root) { return !isa<PoisonValue>(Root); }; | |
| for (Value *Root : make_filter_range(E.Scalars, IsNotPoison)) { | |
| unsigned NumSignBits = ComputeNumSignBits(Root, *DL, AC, nullptr, DT); | |
| TypeSize NumTypeBits = | |
| DL->getTypeSizeInBits(Root->getType()->getScalarType()); | |
| unsigned BitWidth1 = NumTypeBits - NumSignBits; | |
| // If we can't prove that the sign bit is zero, we must add one to the | |
| // maximum bit width to account for the unknown sign bit. This preserves | |
| // the existing sign bit so we can safely sign-extend the root back to the | |
| // original type. Otherwise, if we know the sign bit is zero, we will | |
| // zero-extend the root instead. | |
| // | |
| // FIXME: This is somewhat suboptimal, as there will be cases where adding | |
| // one to the maximum bit width will yield a larger-than-necessary | |
| // type. In general, we need to add an extra bit only if we can't | |
| // prove that the upper bit of the original type is equal to the | |
| // upper bit of the proposed smaller type. If these two bits are | |
| // the same (either zero or one) we know that sign-extending from | |
| // the smaller type will result in the same value. Here, since we | |
| // can't yet prove this, we are just making the proposed smaller | |
| // type larger to ensure correctness. | |
| if (!IsKnownPositive) | |
| ++BitWidth1; | |
| auto *I = dyn_cast<Instruction>(Root); | |
| if (!I) { | |
| MaxBitWidth = std::max(BitWidth1, MaxBitWidth); | |
| continue; | |
| } | |
| APInt Mask = DB->getDemandedBits(I); | |
| unsigned BitWidth2 = Mask.getBitWidth() - Mask.countl_zero(); | |
| MaxBitWidth = | |
| std::max<unsigned>(std::min(BitWidth1, BitWidth2), MaxBitWidth); | |
| } | |
| if (MaxBitWidth < 8 && MaxBitWidth > 1) | |
| MaxBitWidth = 8; | |
| // If the original type is large, but reduced type does not improve the reg | |
| // use - ignore it. | |
| if (NumParts > 1 && | |
| NumParts == | |
| getNumberOfParts( | |
| getWidenedType( | |
| IntegerType::get(F->getContext(), bit_ceil(MaxBitWidth)), | |
| VF), | |
| IntegerType::get(F->getContext(), bit_ceil(MaxBitWidth)))) | |
| return 0u; | |
| unsigned Opcode = E.getOpcode(); | |
| bool IsProfitableToDemote = Opcode == Instruction::Trunc || | |
| Opcode == Instruction::SExt || | |
| Opcode == Instruction::ZExt || NumParts > 1; | |
| // Conservatively determine if we can actually truncate the roots of the | |
| // expression. Collect the values that can be demoted in ToDemote and | |
| // additional roots that require investigating in Roots. | |
| DenseSet<const TreeEntry *> Visited; | |
| unsigned MaxDepthLevel = IsTruncRoot ? Limit : 1; | |
| bool NeedToDemote = IsProfitableToDemote; | |
| if (!collectValuesToDemote(E, IsProfitableToDemoteRoot, MaxBitWidth, | |
| ToDemote, Visited, NodesToKeepBWs, MaxDepthLevel, | |
| NeedToDemote, IsTruncRoot) || | |
| (MaxDepthLevel <= Limit && | |
| !(((Opcode == Instruction::SExt || Opcode == Instruction::ZExt) && | |
| (!IsTopRoot || !(IsStoreOrInsertElt || UserIgnoreList) || | |
| DL->getTypeSizeInBits(TreeRootIT) / | |
| DL->getTypeSizeInBits( | |
| E.getMainOp()->getOperand(0)->getType()) > | |
| 2))))) | |
| return 0u; | |
| // Round MaxBitWidth up to the next power-of-two. | |
| MaxBitWidth = bit_ceil(MaxBitWidth); | |
| return MaxBitWidth; | |
| }; | |
| // If we can truncate the root, we must collect additional values that might | |
| // be demoted as a result. That is, those seeded by truncations we will | |
| // modify. | |
| // Add reduction ops sizes, if any. | |
| if (UserIgnoreList && | |
| isa<IntegerType>(getRootNodeScalars().front()->getType())) { | |
| // Convert vector_reduce_add(ZExt(<n x i1>)) to ZExtOrTrunc(ctpop(bitcast <n | |
| // x i1> to in)). | |
| if (all_of(make_isa_range<Instruction>(*UserIgnoreList), | |
| [](Instruction *I) { | |
| return I->getOpcode() == Instruction::Add; | |
| }) && | |
| getRootNode().State == TreeEntry::Vectorize && | |
| getRootNode().getOpcode() == Instruction::ZExt && | |
| cast<CastInst>(getRootNode().getMainOp())->getSrcTy() == | |
| Builder.getInt1Ty()) { | |
| ReductionBitWidth = 1; | |
| } else { | |
| for (Value *V : make_filter_range(*UserIgnoreList, [](Value *V) { | |
| return !isa<PoisonValue>(V); | |
| })) { | |
| unsigned NumSignBits = ComputeNumSignBits(V, *DL, AC, nullptr, DT); | |
| TypeSize NumTypeBits = DL->getTypeSizeInBits(V->getType()); | |
| unsigned BitWidth1 = NumTypeBits - NumSignBits; | |
| if (!isKnownNonNegative(V, SimplifyQuery(*DL))) | |
| ++BitWidth1; | |
| unsigned BitWidth2 = BitWidth1; | |
| if (!RecurrenceDescriptor::isIntMinMaxRecurrenceKind(::getRdxKind(V))) { | |
| APInt Mask = DB->getDemandedBits(cast<Instruction>(V)); | |
| BitWidth2 = Mask.getBitWidth() - Mask.countl_zero(); | |
| } | |
| ReductionBitWidth = | |
| std::max(std::min(BitWidth1, BitWidth2), ReductionBitWidth); | |
| } | |
| if (ReductionBitWidth < 8 && ReductionBitWidth > 1) | |
| ReductionBitWidth = 8; | |
| ReductionBitWidth = bit_ceil(ReductionBitWidth); | |
| } | |
| } | |
| bool IsTopRoot = NodeIdx == 0; | |
| while (NodeIdx < VectorizableTree.size() && | |
| VectorizableTree[NodeIdx]->State == TreeEntry::Vectorize && | |
| VectorizableTree[NodeIdx]->getOpcode() == Instruction::Trunc) { | |
| RootDemotes.push_back(NodeIdx); | |
| ++NodeIdx; | |
| IsTruncRoot = true; | |
| } | |
| bool IsSignedCmp = false; | |
| if (UserIgnoreList && | |
| all_of(*UserIgnoreList, | |
| match_fn(m_CombineOr(m_SMin(m_Value(), m_Value()), | |
| m_SMax(m_Value(), m_Value()))))) | |
| IsSignedCmp = true; | |
| while (NodeIdx < VectorizableTree.size()) { | |
| ArrayRef<Value *> TreeRoot = VectorizableTree[NodeIdx]->Scalars; | |
| unsigned Limit = 2; | |
| if (IsTopRoot && | |
| ReductionBitWidth == | |
| DL->getTypeSizeInBits(getRootNodeScalars().front()->getType())) | |
| Limit = 3; | |
| unsigned MaxBitWidth = ComputeMaxBitWidth( | |
| *VectorizableTree[NodeIdx], IsTopRoot, IsProfitableToDemoteRoot, Limit, | |
| IsTruncRoot, IsSignedCmp); | |
| // The extension of a demoted scalar, which does not fit into the demoted | |
| // type, restores only its demanded bits. Keep the original bitwidth if the | |
| // scalar users may depend on the other bits via poison-generating | |
| // annotations or there are too many users to check. | |
| auto HasUnsafeScalarUsers = [&](Value *V) { | |
| auto *I = dyn_cast<Instruction>(V); | |
| if (!I) | |
| return false; | |
| unsigned NumBits = | |
| isKnownNonNegative(I, SimplifyQuery(*DL)) | |
| ? computeKnownBits(I, *DL, AC, nullptr, DT).countMaxActiveBits() | |
| : ComputeMaxSignificantBits(I, *DL, AC, nullptr, DT); | |
| if (NumBits <= MaxBitWidth) | |
| return false; | |
| SmallVector<Instruction *> Worklist(1, I); | |
| SmallPtrSet<Instruction *, 8> Visited; | |
| int NumUsers = 0; | |
| while (!Worklist.empty()) { | |
| for (User *U : Worklist.pop_back_val()->users()) { | |
| if (++NumUsers > UsesLimit) | |
| return true; | |
| auto *UI = cast<Instruction>(U); | |
| if (isVectorized(UI) || | |
| (UserIgnoreList && UserIgnoreList->contains(UI)) || | |
| !UI->getType()->isIntOrIntVectorTy() || | |
| !Visited.insert(UI).second) | |
| continue; | |
| if (UI->hasPoisonGeneratingAnnotations()) | |
| return true; | |
| if (!DB->getDemandedBits(UI).isAllOnes()) | |
| Worklist.push_back(UI); | |
| } | |
| } | |
| return false; | |
| }; | |
| if (MaxBitWidth != 0 && any_of(ToDemote, [&](unsigned Idx) { | |
| const TreeEntry &TE = *VectorizableTree[Idx]; | |
| return !TE.isGather() && !MinBWs.contains(&TE) && | |
| any_of(TE.Scalars, HasUnsafeScalarUsers); | |
| })) | |
| MaxBitWidth = 0; | |
| if (ReductionBitWidth != 0 && (IsTopRoot || !RootDemotes.empty())) { | |
| if (MaxBitWidth != 0 && ReductionBitWidth < MaxBitWidth) | |
| ReductionBitWidth = bit_ceil(MaxBitWidth); | |
| else if (MaxBitWidth == 0) | |
| ReductionBitWidth = 0; | |
| } | |
| for (unsigned Idx : RootDemotes) { | |
| if (all_of(VectorizableTree[Idx]->Scalars, [&](Value *V) { | |
| uint32_t OrigBitWidth = | |
| DL->getTypeSizeInBits(V->getType()->getScalarType()); | |
| if (OrigBitWidth > MaxBitWidth) { | |
| APInt Mask = APInt::getBitsSetFrom(OrigBitWidth, MaxBitWidth); | |
| return MaskedValueIsZero(V, Mask, SimplifyQuery(*DL)); | |
| } | |
| return false; | |
| })) | |
| ToDemote.push_back(Idx); | |
| } | |
| RootDemotes.clear(); | |
| IsTopRoot = false; | |
| IsProfitableToDemoteRoot = true; | |
| if (ExtraBitWidthNodes.empty()) { | |
| NodeIdx = VectorizableTree.size(); | |
| } else { | |
| unsigned NewIdx = 0; | |
| do { | |
| NewIdx = *ExtraBitWidthNodes.begin(); | |
| ExtraBitWidthNodes.erase(ExtraBitWidthNodes.begin()); | |
| } while (NewIdx <= NodeIdx && !ExtraBitWidthNodes.empty()); | |
| NodeIdx = NewIdx; | |
| IsTruncRoot = | |
| NodeIdx < VectorizableTree.size() && | |
| VectorizableTree[NodeIdx]->UserTreeIndex && | |
| VectorizableTree[NodeIdx]->UserTreeIndex.EdgeIdx == 0 && | |
| VectorizableTree[NodeIdx]->UserTreeIndex.UserTE->hasState() && | |
| VectorizableTree[NodeIdx]->UserTreeIndex.UserTE->getOpcode() == | |
| Instruction::Trunc && | |
| !VectorizableTree[NodeIdx]->UserTreeIndex.UserTE->isAltShuffle(); | |
| IsSignedCmp = | |
| NodeIdx < VectorizableTree.size() && | |
| VectorizableTree[NodeIdx]->UserTreeIndex && | |
| VectorizableTree[NodeIdx]->UserTreeIndex.UserTE->hasState() && | |
| VectorizableTree[NodeIdx]->UserTreeIndex.UserTE->getOpcode() == | |
| Instruction::ICmp && | |
| any_of( | |
| make_isa_range<ICmpInst>( | |
| VectorizableTree[NodeIdx]->UserTreeIndex.UserTE->Scalars), | |
| [&](ICmpInst *IC) { | |
| return IC->isSigned() || | |
| !isKnownNonNegative(IC->getOperand(0), | |
| SimplifyQuery(*DL)) || | |
| !isKnownNonNegative(IC->getOperand(1), | |
| SimplifyQuery(*DL)); | |
| }); | |
| } | |
| // If the maximum bit width we compute is less than the width of the roots' | |
| // type, we can proceed with the narrowing. Otherwise, do nothing. | |
| if (MaxBitWidth == 0 || | |
| MaxBitWidth >= | |
| cast<IntegerType>(TreeRoot.front()->getType()->getScalarType()) | |
| ->getBitWidth()) { | |
| if (UserIgnoreList) { | |
| unsigned Sz = getTreeSizeExcludingGathers(); | |
| for (Value *V : TreeRoot) | |
| AnalyzedMinBWVals.insert_or_assign(V, Sz); | |
| } | |
| NodesToKeepBWs.insert_range(ToDemote); | |
| continue; | |
| } | |
| // Finally, map the values we can demote to the maximum bit with we | |
| // computed. | |
| for (unsigned Idx : ToDemote) { | |
| TreeEntry *TE = VectorizableTree[Idx].get(); | |
| if (MinBWs.contains(TE)) | |
| continue; | |
| bool IsSigned = any_of(TE->Scalars, [&](Value *R) { | |
| if (isa<PoisonValue>(R)) | |
| return false; | |
| return !isKnownNonNegative(R, SimplifyQuery(*DL)); | |
| }); | |
| MinBWs.try_emplace(TE, MaxBitWidth, IsSigned); | |
| } | |
| } | |
| } | |
| PreservedAnalyses SLPVectorizerPass::run(Function &F, FunctionAnalysisManager &AM) { | |
| auto *SE = &AM.getResult<ScalarEvolutionAnalysis>(F); | |
| auto *TTI = &AM.getResult<TargetIRAnalysis>(F); | |
| auto *TLI = AM.getCachedResult<TargetLibraryAnalysis>(F); | |
| auto *AA = &AM.getResult<AAManager>(F); | |
| auto *LI = &AM.getResult<LoopAnalysis>(F); | |
| auto *DT = &AM.getResult<DominatorTreeAnalysis>(F); | |
| auto *AC = &AM.getResult<AssumptionAnalysis>(F); | |
| auto *DB = &AM.getResult<DemandedBitsAnalysis>(F); | |
| auto *ORE = &AM.getResult<OptimizationRemarkEmitterAnalysis>(F); | |
| bool Changed = runImpl(F, SE, TTI, TLI, AA, LI, DT, AC, DB, ORE); | |
| if (!Changed) | |
| return PreservedAnalyses::all(); | |
| PreservedAnalyses PA; | |
| // Runtime alias check versioning changes the CFG. In that case only the | |
| // dominator tree is kept up to date (via DomTreeUpdater); other CFG analyses | |
| // must be recomputed. Otherwise SLP leaves the CFG intact. | |
| if (CFGChanged) | |
| PA.preserve<DominatorTreeAnalysis>(); | |
| else | |
| PA.preserveSet<CFGAnalyses>(); | |
| return PA; | |
| } | |
| bool SLPVectorizerPass::runImpl(Function &F, ScalarEvolution *SE_, | |
| TargetTransformInfo *TTI_, | |
| TargetLibraryInfo *TLI_, AAResults *AA_, | |
| LoopInfo *LI_, DominatorTree *DT_, | |
| AssumptionCache *AC_, DemandedBits *DB_, | |
| OptimizationRemarkEmitter *ORE_) { | |
| Opts = &F.getContext().getLibraryOptions().get<VectorizeOptions>(); | |
| if (!Opts->vectorize_slp) | |
| return false; | |
| SE = SE_; | |
| TTI = TTI_; | |
| TLI = TLI_; | |
| AA = AA_; | |
| LI = LI_; | |
| DT = DT_; | |
| AC = AC_; | |
| DB = DB_; | |
| DL = &F.getDataLayout(); | |
| Stores.clear(); | |
| GEPs.clear(); | |
| bool Changed = false; | |
| // If the target claims to have no vector registers don't attempt | |
| // vectorization. | |
| if (!TTI->getNumberOfRegisters(TTI->getRegisterClassForType(true))) { | |
| LLVM_DEBUG( | |
| dbgs() << "SLP: Didn't find any vector registers for target, abort.\n"); | |
| return false; | |
| } | |
| // Don't vectorize when the attribute NoImplicitFloat is used. | |
| if (F.hasFnAttribute(Attribute::NoImplicitFloat)) | |
| return false; | |
| LLVM_DEBUG(dbgs() << "SLP: Analyzing blocks in " << F.getName() << ".\n"); | |
| // Use the bottom up slp vectorizer to construct chains that start with | |
| // store instructions. | |
| BoUpSLP R(*Opts, &F, SE, TTI, TLI, AA, LI, DT, AC, DB, DL, ORE_); | |
| // A general note: the vectorizer must use BoUpSLP::eraseInstruction() to | |
| // delete instructions. | |
| // Update DFS numbers now so that we can use them for ordering. | |
| DT->updateDFSNumbers(); | |
| SmallSetVector<Instruction *, 8> FMACandidates; | |
| // Scan the blocks in the function in post order. | |
| auto POT = post_order(&F.getEntryBlock()); | |
| for (auto *BB : make_filter_range(POT, [](auto *BB) { | |
| return !BB->isEHPad() && !isa<UnreachableInst>(BB->getTerminator()); | |
| })) { | |
| // Skip scalar fallback blocks created by runtime alias check versioning: | |
| // they duplicate the original scalar code and must not be vectorized again. | |
| if (R.isScalarFallbackBlock(BB)) | |
| continue; | |
| // Start new block - clear the list of reduction roots. | |
| R.clearReductionData(); | |
| collectSeedInstructions(BB); | |
| Changed |= vectorizeSplitStorePoorThroughputSeeds(BB, R); | |
| // Vectorize trees that end at stores. | |
| if (!Stores.empty()) { | |
| LLVM_DEBUG(dbgs() << "SLP: Found stores for " << Stores.size() | |
| << " underlying objects.\n"); | |
| Changed |= vectorizeStoreChains(R); | |
| } | |
| // Vectorize trees that end at reductions. | |
| Changed |= vectorizeChainsInBlock(BB, R, FMACandidates); | |
| // Vectorize the index computations of getelementptr instructions. This | |
| // is primarily intended to catch gather-like idioms ending at | |
| // non-consecutive loads. | |
| if (!GEPs.empty()) { | |
| LLVM_DEBUG(dbgs() << "SLP: Found GEPs for " << GEPs.size() | |
| << " underlying objects.\n"); | |
| Changed |= vectorizeGEPIndices(BB, R); | |
| } | |
| } | |
| // Vectorized on their own, the FMA candidates lose the fusion with their fmul | |
| // operands. They are retried after all the blocks, not to preempt the trees | |
| // of the blocks processed later, e.g. the store chains of a loop body, | |
| // processed after the latch PHIs reaching the same instructions. | |
| R.clearReductionData(); | |
| SmallSetVector<Instruction *, 8> Empty; | |
| for (Instruction *I : make_filter_range( | |
| FMACandidates, [&](Instruction *I) { return !R.isDeleted(I); })) | |
| Changed |= tryToVectorize(I, R, Empty, /*AllowFMACandidates=*/true); | |
| assert(Empty.empty() && | |
| "No new FMA candidates expected during AllowFMACandidates retry."); | |
| // Instructions with the single user require just one extract per lane, so | |
| // they are used as the seeds for the very last attempt, after all the other | |
| // roots in the function are exhausted. | |
| if (Opts->slp_vectorize_once_used) { | |
| auto OnceUsedPOT = post_order(&F.getEntryBlock()); | |
| for (auto *BB : make_filter_range(OnceUsedPOT, [&](auto *BB) { | |
| return !BB->isEHPad() && | |
| !isa<UnreachableInst>(BB->getTerminator()) && | |
| !R.isScalarFallbackBlock(BB); | |
| })) { | |
| R.clearReductionData(); | |
| Changed |= vectorizeOnceUsedSeeds(BB, R); | |
| } | |
| } | |
| if (Changed) { | |
| R.optimizeGatherSequence(); | |
| LLVM_DEBUG(dbgs() << "SLP: vectorized \"" << F.getName() << "\"\n"); | |
| } | |
| // Propagate whether runtime alias check versioning changed the CFG, so the | |
| // caller can drop CFG-analysis preservation only when necessary. | |
| CFGChanged = R.isCFGChanged(); | |
| return Changed; | |
| } | |
| std::optional<bool> | |
| SLPVectorizerPass::vectorizeStoreChain(ArrayRef<Value *> Chain, BoUpSLP &R, | |
| unsigned Idx, unsigned MinVF, | |
| unsigned &Size) { | |
| std::optional<bool> Res = vectorizeStoreChainImpl(Chain, R, Idx, MinVF, Size); | |
| // Retry the non-profitable tree, wider than the vector register, with the | |
| // alternate stored values (looking through the cast) split into halves. The | |
| // halves share the common operands instead of extracting them for the scalar | |
| // stores of the other half. | |
| if (Res && !*Res && Chain.size() > SmallNodeSize && Chain.size() % 2 == 0 && | |
| Chain.size() * R.getVectorElementSize(Chain.front()) > | |
| R.getMaxVecRegSize() && | |
| R.canSplitStoredValues(Chain)) { | |
| SaveAndRestore<bool> SplitGuard = R.splitStoredValues(); | |
| unsigned SplitSize; | |
| std::optional<bool> SplitRes = | |
| vectorizeStoreChainImpl(Chain, R, Idx, MinVF, SplitSize); | |
| if (SplitRes.value_or(false)) { | |
| Size = SplitSize; | |
| return SplitRes; | |
| } | |
| } | |
| assert(!R.isTryingRuntimeAliasChecks() && | |
| "Unexpected nested runtime alias check attempt"); | |
| // Retry once with runtime alias checks, but only when the normal attempt | |
| // could not even schedule the bundle, i.e. it was blocked by | |
| // a memory dependency, and that dependency is runtime-checkable. If the | |
| // region also kept a non-checkable blocker, dropping the checkable deps | |
| // cannot unblock it. | |
| if (Res.has_value() || !Opts->slp_vectorize_with_runtime_alias_checks || | |
| !R.hasRuntimeCheckableBlockers() || R.hasNonCheckableMemBlocker()) | |
| return Res; | |
| // getTreeCost() unconditionally rejects a VF=2 tree whose vector | |
| // instruction count exceeds its scalar instruction count (the | |
| // SLPInstCountCheck heuristic), because the cost model is known to | |
| // underestimate shuffle/insert/extract overhead at that width. That check | |
| // returns an invalid cost that no threshold, including the runtime-checks | |
| // one, can outweigh. Retrying such a chain would pay the full tree rebuild | |
| // cost for the one width the cost model is least trusted for, on top of | |
| // the extra guard overhead, so skip it instead of wasting the attempt. | |
| if (Chain.size() == 2) | |
| return Res; | |
| // Only retry when scheduling blocked the chain (a store or its value bundle | |
| // could not be scheduled), which dropping may-alias deps can fix. A chain | |
| // gathered for structural reasons (e.g. non-consecutive addresses) would | |
| // gather again, so the second buildTree() would be pure overhead with no | |
| // vectorization benefit. | |
| Value *FirstStore = Chain.front(); | |
| if (!R.isNotScheduled(FirstStore) && | |
| !R.isNotScheduled(cast<StoreInst>(FirstStore)->getValueOperand())) | |
| return Res; | |
| BasicBlock *BB = cast<Instruction>(FirstStore)->getParent(); | |
| // If an earlier optimistic attempt already failed for this block, do not | |
| // retry for other chains or smaller VFs in the same block. | |
| if (R.runtimeChecksFailedForBlock(BB)) | |
| return Res; | |
| // The retry rebuilds the whole tree; also skip it when the chain's block can | |
| // never be versioned (e.g. the function is optimized for size). | |
| if (!R.canVersionBlockForRuntimeChecks(BB)) { | |
| R.markRuntimeChecksFailedForBlock(BB); | |
| return Res; | |
| } | |
| R.setTryRuntimeAliasChecks(true); | |
| unsigned RTSize = Size; | |
| std::optional<bool> RTRes = | |
| vectorizeStoreChainImpl(Chain, R, Idx, MinVF, RTSize); | |
| R.setTryRuntimeAliasChecks(false); | |
| if (RTRes && *RTRes) { | |
| Size = RTSize; | |
| return RTRes; | |
| } | |
| // The versioning attempt failed for this block, skip the (expensive) retry. | |
| R.markRuntimeChecksFailedForBlock(BB); | |
| return Res; | |
| } | |
| std::optional<bool> | |
| SLPVectorizerPass::vectorizeStoreChainImpl(ArrayRef<Value *> Chain, BoUpSLP &R, | |
| unsigned Idx, unsigned MinVF, | |
| unsigned &Size) { | |
| Size = 0; | |
| R.resetRuntimeAliasCheckState(); | |
| LLVM_DEBUG(dbgs() << "SLP: Analyzing a store chain of length " << Chain.size() | |
| << "\n"); | |
| const unsigned Sz = R.getVectorElementSize(Chain[0]); | |
| unsigned VF = Chain.size(); | |
| if (!has_single_bit(Sz) || | |
| !hasFullVectorsOrPowerOf2( | |
| *TTI, cast<StoreInst>(Chain.front())->getValueOperand()->getType(), | |
| VF, Opts->slp_revec) || | |
| VF < 2 || VF < MinVF) { | |
| // Check if vectorizing with a non-power-of-2 VF should be considered; see | |
| // isAllowedNonPowerOf2VF for supported widths. | |
| if (!Opts->slp_vectorize_non_power_of_2 || (VF < MinVF && VF + 1 != MinVF)) | |
| return false; | |
| } | |
| LLVM_DEBUG(dbgs() << "SLP: Analyzing " << VF << " stores at offset " << Idx | |
| << "\n"); | |
| SetVector<Value *> ValOps; | |
| for (Value *V : Chain) | |
| ValOps.insert(cast<StoreInst>(V)->getValueOperand()); | |
| // Operands are not same/alt opcodes or non-power-of-2 uniques - exit. | |
| InstructionsCompatibilityAnalysis Analysis(*DT, *DL, *TTI, *TLI); | |
| InstructionsState S = | |
| Analysis.buildInstructionsState(ValOps.getArrayRef(), R); | |
| if (all_of(ValOps, IsaPred<Instruction>) && ValOps.size() > 1) { | |
| DenseSet<Value *> Stores(Chain.begin(), Chain.end()); | |
| bool IsAllowedSize = | |
| hasFullVectorsOrPowerOf2(*TTI, ValOps.front()->getType(), ValOps.size(), | |
| Opts->slp_revec) || | |
| isAllowedNonPowerOf2VF(ValOps.size(), | |
| Opts->slp_vectorize_non_power_of_2); | |
| if ((!IsAllowedSize && S && S.getOpcode() != Instruction::Load && | |
| (!S.getMainOp()->isSafeToRemove() || | |
| any_of(ValOps.getArrayRef(), | |
| [&](Value *V) { | |
| return !isa<ExtractElementInst>(V) && | |
| (V->getNumUses() > Chain.size() || | |
| any_of(V->users(), [&](User *U) { | |
| return !Stores.contains(U); | |
| })); | |
| }))) || | |
| (ValOps.size() > Chain.size() / 2 && !S)) { | |
| Size = (!IsAllowedSize && S) ? 1 : 2; | |
| return false; | |
| } | |
| // Every value is reused several times outside of the chain: such a chain | |
| // only merges the stores, while the scalars remain live for the other users | |
| // and all the lanes are gathered back. A single outside use may still be a | |
| // part of the larger vectorizable graph, same for the values, fed by the | |
| // loads, where the vector loads may pay off the gathering. Two outside | |
| // uses may still be part of the same profitable tree when they look like | |
| // carry deps (cmp, select, non-div/rem binop, or a two-incoming phi); | |
| // reject 3+ outside users, or exactly 2 when at least one is not benign. | |
| auto IsBenignOutsideUser = [](Instruction *UI) { | |
| assert(UI && "Expected instruction."); | |
| if (auto *PN = dyn_cast<PHINode>(UI)) | |
| return PN->getNumIncomingValues() == 2; | |
| if (auto *BO = dyn_cast<BinaryOperator>(UI)) | |
| return !BO->isIntDivRem() && !BO->isFPDivRem(); | |
| return isa<CmpInst, SelectInst>(UI); | |
| }; | |
| auto HasTooManyOutsideUsers = [&](Instruction *I) { | |
| // To save compilation time, bail out if the use list is huge. | |
| if (I->hasNUsesOrMore(UsesLimit)) | |
| return true; | |
| unsigned Outside = 0; | |
| bool HasNonBenign = false; | |
| for (User *U : I->users()) { | |
| if (Stores.contains(U)) | |
| continue; | |
| ++Outside; | |
| if (Outside > 2) | |
| return true; | |
| if (!IsBenignOutsideUser(cast<Instruction>(U))) | |
| HasNonBenign = true; | |
| if (Outside == 2 && HasNonBenign) | |
| return true; | |
| } | |
| return false; | |
| }; | |
| if (S && S.getOpcode() != Instruction::Load && | |
| all_of(ValOps.getArrayRef(), [&](Value *V) { | |
| auto *I = cast<Instruction>(V); | |
| return none_of(I->operand_values(), IsaPred<LoadInst>) && | |
| HasTooManyOutsideUsers(I); | |
| })) { | |
| Size = 1; | |
| return false; | |
| } | |
| } | |
| R.buildTree(Chain); | |
| // Check if tree tiny and store itself or its value is not vectorized. | |
| if (R.isTreeTinyAndNotFullyVectorizable()) { | |
| if (R.isGathered(Chain.front()) || | |
| R.isNotScheduled(cast<StoreInst>(Chain.front())->getValueOperand())) | |
| return std::nullopt; | |
| Size = R.getCanonicalGraphSize(); | |
| return false; | |
| } | |
| if (R.isProfitableToReorder()) { | |
| R.reorderTopToBottom(); | |
| R.reorderBottomToTop(); | |
| } | |
| if (R.isTryingRuntimeAliasChecks()) | |
| R.captureRuntimeCheckBodySnapshot(); | |
| R.transformNodes(); | |
| R.computeMinimumValueSizes(); | |
| InstructionCost TreeCost = R.calculateTreeCostAndTrimNonProfitable(); | |
| R.buildExternalUses(); | |
| Size = R.getCanonicalGraphSize() - R.getNumSplatSubtreeEntries(); | |
| if (S && S.getOpcode() == Instruction::Load) | |
| Size = 2; // cut off masked gather small trees | |
| InstructionCost Cost = R.getTreeCost(TreeCost); | |
| // When the tree was scheduled by dropping may-alias dependencies, account | |
| // for the runtime alias checks in the cost and make sure the region can | |
| // actually be versioned. If it cannot, abandon this attempt: the dropped | |
| // dependencies are unused because the tree is not vectorized. | |
| // The full check cost is charged against the vector-path benefit here; the | |
| // scalar-path overhead is bounded separately in canVersionForRuntimeChecks() | |
| // (a fraction of the scalar body cost). | |
| if (R.isTryingRuntimeAliasChecks() && R.hasRuntimeAliasChecks()) { | |
| // getRuntimeChecksCost() only ever sums non-negative per-instruction | |
| // throughput costs (address adds, compares, and/or, the guard branch), so | |
| // adding it can only push Cost up, never down, and an already-invalid | |
| // Cost (e.g. rejected by one of getTreeCost()'s own heuristics) stays | |
| // invalid either way. So if Cost already fails the threshold below, no | |
| // checks cost can rescue it: skip the (expensive) SCEV/dominance/escape | |
| // analysis in canVersionForRuntimeChecks() instead of running it on a | |
| // tree that is already rejected. | |
| if (Cost >= -R.getCostThreshold()) | |
| return false; | |
| if (!R.canVersionForRuntimeChecks()) | |
| return false; | |
| // TODO: Add probability-weighted model (benefit scaled by the expected | |
| // non-alias odds) would be more precise but needs | |
| // branch-probability/profile data. | |
| Cost += R.getRuntimeChecksCost(); | |
| } | |
| LLVM_DEBUG(dbgs() << "SLP: Found cost = " << Cost << " for VF=" << VF << "\n"); | |
| if (Cost < -R.getCostThreshold()) { | |
| LLVM_DEBUG(dbgs() << "SLP: Decided to vectorize cost = " << Cost << "\n"); | |
| using namespace ore; | |
| R.getORE()->emit(OptimizationRemark(SV_NAME, "StoresVectorized", | |
| cast<StoreInst>(Chain[0])) | |
| << "Stores SLP vectorized with cost " << NV("Cost", Cost) | |
| << " and with tree size " | |
| << NV("TreeSize", R.getTreeSize())); | |
| R.vectorizeTree(); | |
| return true; | |
| } | |
| return false; | |
| } | |
| namespace { | |
| /// A group of related stores which we are in the process of vectorizing, | |
| /// a subset of which may already be vectorized. Stores context information | |
| /// about the group as a whole as well as information about what VFs need | |
| /// to be attempted still. | |
| class StoreChainContext { | |
| public: | |
| using SizePair = std::pair<unsigned, unsigned>; | |
| explicit StoreChainContext(const VectorizeOptions &Opts, | |
| ArrayRef<Value *> Ops, | |
| ArrayRef<SizePair> RangeSizes, | |
| SmallVector<unsigned> &RangeSizesByIdx, | |
| unsigned Stride) | |
| : Opts(Opts), Operands(Ops), RangeSizesStorage(RangeSizes), | |
| RangeSizesByIdx(RangeSizesByIdx), Stride(Stride) {} | |
| /// Set up initial values using the already set Operands | |
| bool initializeContext( | |
| BoUpSLP &R, const DataLayout &DL, const TargetTransformInfo &TTI, | |
| DenseSet<std::tuple<Value *, Value *, Value *, Value *, unsigned>> | |
| &Visited); | |
| /// Get the current VF | |
| std::optional<unsigned> getCurrentVF() const; | |
| /// Return the maximum VF for the context | |
| unsigned getMaxVF() const { return MaxVF; } | |
| /// Return the stride of the context | |
| unsigned getStride() const { return Stride; } | |
| /// Attempt to vectorize Operands for the given VF | |
| /// Returns false if no more attempts should be made for the context | |
| bool vectorizeOneVF(const TargetTransformInfo &TTI, unsigned VF, | |
| BoUpSLP::ValueSet &VectorizedStores, bool &Changed, | |
| llvm::function_ref<std::optional<bool>( | |
| ArrayRef<Value *>, unsigned, unsigned, unsigned &)> | |
| VectorizeStoreChain); | |
| /// Add an additional store to the chain | |
| /// \p Store store too append to Operands | |
| /// \p Idx position within VectorizeStores::Stores | |
| void addOperand(Value *Store, unsigned Idx); | |
| private: | |
| bool isNotVectorized(const SizePair &P) const { | |
| return P.first != LocallyUnvectorizable && RangeSizesByIdx[P.first] > 0; | |
| } | |
| bool isVectorized(const SizePair &P) const { | |
| return P.first == LocallyUnvectorizable || RangeSizesByIdx[P.first] == 0; | |
| } | |
| bool isVFProfitable(unsigned Size, const SizePair &P) const { | |
| assert(P.first != LocallyUnvectorizable && RangeSizesByIdx[P.first] && | |
| "Cannot check profitability of vectorized element"); | |
| return Size >= RangeSizesByIdx[P.first]; | |
| } | |
| bool firstSizeSame(unsigned Size, const SizePair &P) const { | |
| assert(P.first != LocallyUnvectorizable && RangeSizesByIdx[P.first] && | |
| "Cannot check profitability of vectorized element"); | |
| return Size == RangeSizesByIdx[P.first]; | |
| } | |
| /// Return the index of the first unvectorized store after \p StartIdx | |
| unsigned getFirstUnvecStore(unsigned StartIdx = 0) const; | |
| /// Return the index of the first vectorized store after \p StartIdx | |
| unsigned getFirstVecStoreAfter(unsigned StartIdx) const; | |
| /// Return true if all stores have been vectorized | |
| bool allVectorized() const; | |
| /// Return true if all elements in the given range match \p TreeSize | |
| bool isFirstSizeSameRange(unsigned StartIdx, unsigned Length, | |
| unsigned TreeSize) const; | |
| /// Return true if the \p TreeSize is profitable for all elements in the range | |
| bool allOfRangeProfitable(unsigned StartIdx, unsigned Length, | |
| unsigned TreeSize) const; | |
| /// Update the live (first) range sizes from the cached values (second) | |
| void updateRangeSizesFromCache(); | |
| /// Update the cached (second) range sizes with the given \p TreeSize | |
| void updateCachedRangeSizes(unsigned StartIdx, unsigned Length, | |
| unsigned TreeSize); | |
| /// Update CandidateVFs for secondary iterations | |
| bool updateCandidateVFs(const TargetTransformInfo &TTI); | |
| /// Remove the current VF from the queue | |
| void incrementVF() { | |
| if (!CandidateVFs.empty()) | |
| CandidateVFs.pop(); | |
| } | |
| /// Record vectorization of the provided range | |
| void markRangeVectorized(unsigned StartIdx, unsigned Length, | |
| unsigned &FirstUnvecStore, unsigned &MaxSliceEnd); | |
| /// Checks if the quadratic mean deviation is less than 90% of the mean size. | |
| bool checkTreeSizes(const unsigned SliceStartIdx, const unsigned VF) const; | |
| /// In RangeSizes, element has not been vectorized, but due to the elements | |
| /// around it being vectorized, it does not have enough neighboring elements | |
| /// to make a chain longer than MinVF as part of the current Context | |
| static constexpr unsigned LocallyUnvectorizable = | |
| std::numeric_limits<unsigned>::max(); | |
| /// Maximum number of iterations through CandidateVFs | |
| static constexpr unsigned MaxAttempts = 4; | |
| const VectorizeOptions &Opts; | |
| /// For the StoreTy/Stride in the given group, what is the smallest VF | |
| /// that can be used | |
| unsigned MinVF = 0; | |
| /// Maximum number of instructions that can be vectorized, either | |
| /// constrained by register width or operands size. | |
| unsigned MaxVF = 0; | |
| /// MaxRegVF represents the number of instructions (scalar, or vector in | |
| /// case of revec) that can be vectorized to naturally fit in a vector | |
| /// register. | |
| unsigned MaxRegVF = 0; | |
| /// The largest VF checked in the current Repeat | |
| unsigned ProbeVF = 0; | |
| /// Type of the Stores in `Operands` | |
| Type *StoreTy = nullptr; | |
| /// Which VFs do we want to attempt for this chain | |
| std::queue<unsigned> CandidateVFs; | |
| /// Stores that compose this chain | |
| BoUpSLP::ValueList Operands; | |
| /// Track the TreeSizes of prior vectorization attempts using each element, | |
| /// to help us find early exit cases | |
| /// - first: contains pointer into RangeSizesByIdx to help us track | |
| /// vectorization of elements that belong to multiple chains | |
| /// - second: contains cached TreeSize value for that element | |
| SmallVector<SizePair> RangeSizesStorage; | |
| MutableArrayRef<SizePair> RangeSizes; | |
| /// RangeSize information for all elements in any chain | |
| /// Needed since may be overlap between chains | |
| SmallVector<unsigned> &RangeSizesByIdx; | |
| /// What element index is the end of the to be vectorized Operands | |
| /// i.e. Operands.size() == 16, and 12-15 were vectorized, then End == 12 | |
| unsigned End = 0; | |
| /// How many times has CandidateVFs been refilled, prevents excessive | |
| /// attempts at vectorizing large VFs | |
| unsigned Repeat = 1; | |
| /// Did any vectorization occur for the current iteration over CandidateVFs | |
| bool RepeatChanged = false; | |
| /// For constant strided stores, what is the stride amount | |
| const unsigned Stride = 0; | |
| /// Store information about failed vectorization attempts due to scheduling | |
| SmallDenseMap<Value *, SizePair> NonSchedulable; | |
| }; | |
| void StoreChainContext::addOperand(Value *Store, unsigned Idx) { | |
| Operands.push_back(Store); | |
| RangeSizesStorage.push_back({Idx, 1}); | |
| } | |
| void StoreChainContext::markRangeVectorized(unsigned StartIdx, unsigned Length, | |
| unsigned &FirstUnvecStore, | |
| unsigned &MaxSliceEnd) { | |
| for (SizePair &P : RangeSizes.slice(StartIdx, Length)) | |
| RangeSizesByIdx[P.first] = P.second = 0; | |
| if (StartIdx < FirstUnvecStore + MinVF) { | |
| for (SizePair &P : | |
| RangeSizes.slice(FirstUnvecStore, StartIdx - FirstUnvecStore)) { | |
| P.first = LocallyUnvectorizable; | |
| P.second = 0; | |
| } | |
| FirstUnvecStore = StartIdx + Length; | |
| } | |
| if (StartIdx + Length > MaxSliceEnd - MinVF) { | |
| for (SizePair &P : RangeSizes.slice(StartIdx + Length, | |
| MaxSliceEnd - (StartIdx + Length))) { | |
| P.first = LocallyUnvectorizable; | |
| P.second = 0; | |
| } | |
| if (MaxSliceEnd == End) | |
| End = StartIdx; | |
| MaxSliceEnd = StartIdx; | |
| } | |
| } | |
| bool StoreChainContext::initializeContext( | |
| BoUpSLP &R, const DataLayout &DL, const TargetTransformInfo &TTI, | |
| DenseSet<std::tuple<Value *, Value *, Value *, Value *, unsigned>> | |
| &Visited) { | |
| if (!Visited | |
| .insert({Operands.front(), | |
| cast<StoreInst>(Operands.front())->getValueOperand(), | |
| Operands.back(), | |
| cast<StoreInst>(Operands.back())->getValueOperand(), | |
| Operands.size()}) | |
| .second) | |
| return false; | |
| // Initialize range tracking in context. | |
| RangeSizes = MutableArrayRef(RangeSizesStorage); | |
| unsigned MaxVecRegSize = R.getMaxVecRegSize(); | |
| unsigned EltSize = R.getVectorElementSize(Operands[0]); | |
| unsigned MaxElts = llvm::bit_floor(MaxVecRegSize / EltSize); | |
| MaxVF = std::min(R.getMaximumVF(EltSize, Instruction::Store), MaxElts); | |
| auto *Store = cast<StoreInst>(Operands[0]); | |
| StoreTy = Store->getValueOperand()->getType(); | |
| Type *ValueTy = StoreTy; | |
| if (auto *Trunc = dyn_cast<TruncInst>(Store->getValueOperand())) | |
| ValueTy = Trunc->getSrcTy(); | |
| // When REVEC is enabled, StoreTy and ValueTy may be FixedVectorType. But | |
| // getStoreMinimumVF only support scalar type as arguments. As a result, | |
| // we need to use the element type of StoreTy and ValueTy to retrieve the | |
| // VF and then transform it back. | |
| // Remember: VF is defined as the number we want to vectorize, not the | |
| // number of elements in the final vector. | |
| Type *StoreScalarTy = StoreTy->getScalarType(); | |
| MinVF = PowerOf2Ceil(TTI.getStoreMinimumVF( | |
| R.getMinVF(DL.getTypeStoreSizeInBits(StoreScalarTy)), StoreScalarTy, | |
| ValueTy->getScalarType(), Store->getAlign(), | |
| Store->getPointerAddressSpace())); | |
| MinVF /= getNumElements(StoreTy); | |
| MinVF = std::max<unsigned>(2, MinVF); | |
| if (Stride > 1) | |
| MinVF = std::max(MinVF, Opts.slp_min_strided_stores); | |
| if (MaxVF < MinVF) { | |
| LLVM_DEBUG(dbgs() << "SLP: Vectorization infeasible as MaxVF (" << MaxVF | |
| << ") < " | |
| << "MinVF (" << MinVF << ")\n"); | |
| return false; | |
| } | |
| // First try a supported non-power-of-2 VF (see isAllowedNonPowerOf2VF). | |
| unsigned NonPowerOf2VF = 0; | |
| unsigned CandVF = std::clamp<unsigned>(Operands.size(), MinVF, MaxVF); | |
| if (isAllowedNonPowerOf2VF(CandVF, Opts.slp_vectorize_non_power_of_2)) { | |
| NonPowerOf2VF = CandVF; | |
| assert(NonPowerOf2VF != MaxVF && | |
| "Non-power-of-2 VF should not be equal to MaxVF"); | |
| } | |
| MaxRegVF = MaxVF; | |
| MaxVF = std::min<unsigned>(MaxVF, bit_floor(Operands.size())); | |
| if (MaxVF < MinVF) { | |
| LLVM_DEBUG(dbgs() << "SLP: Vectorization infeasible as MaxVF (" << MaxVF | |
| << ") < " | |
| << "MinVF (" << MinVF << ")\n"); | |
| return false; | |
| } | |
| for (unsigned VF = std::max(MaxVF, NonPowerOf2VF); VF >= MinVF; | |
| VF = divideCeil(VF, 2)) | |
| CandidateVFs.push(VF); | |
| End = Operands.size(); | |
| ProbeVF = MaxVF; | |
| return true; | |
| } | |
| // Return the index of the first unvectorized store after \p StartIdx | |
| unsigned StoreChainContext::getFirstUnvecStore(unsigned StartIdx) const { | |
| return std::distance( | |
| RangeSizes.begin(), | |
| find_if(RangeSizes.drop_front(StartIdx), | |
| [this](const SizePair &P) { return this->isNotVectorized(P); })); | |
| } | |
| // Return the index of the first vectorized store after \p StartIdx | |
| unsigned StoreChainContext::getFirstVecStoreAfter(unsigned StartIdx) const { | |
| return std::distance( | |
| RangeSizes.begin(), | |
| find_if(RangeSizes.drop_front(StartIdx), | |
| [this](const SizePair &P) { return this->isVectorized(P); })); | |
| } | |
| // Return true if all stores have been vectorized | |
| bool StoreChainContext::allVectorized() const { | |
| return all_of(RangeSizes, | |
| [this](const SizePair &P) { return this->isVectorized(P); }); | |
| } | |
| // Return true if all elements in the given range match \p TreeSize | |
| bool StoreChainContext::isFirstSizeSameRange(unsigned StartIdx, unsigned Length, | |
| unsigned TreeSize) const { | |
| return all_of(RangeSizes.slice(StartIdx, Length), | |
| [TreeSize, this](const SizePair &P) { | |
| return firstSizeSame(TreeSize, P); | |
| }); | |
| } | |
| // Return true if the \p TreeSize is profitable for all elements in the range | |
| bool StoreChainContext::allOfRangeProfitable(unsigned StartIdx, unsigned Length, | |
| unsigned TreeSize) const { | |
| return all_of(RangeSizes.slice(StartIdx, Length), | |
| [TreeSize, this](const SizePair &P) { | |
| return isVFProfitable(TreeSize, P); | |
| }); | |
| } | |
| // Update the live (first) range sizes from the cached values (second) | |
| void StoreChainContext::updateRangeSizesFromCache() { | |
| for (SizePair &P : RangeSizes) { | |
| if (P.first != LocallyUnvectorizable && RangeSizesByIdx[P.first] != 0) | |
| RangeSizesByIdx[P.first] = std::max(P.second, RangeSizesByIdx[P.first]); | |
| } | |
| } | |
| // Update the cached (second) range sizes with the given \p TreeSize | |
| void StoreChainContext::updateCachedRangeSizes(unsigned StartIdx, | |
| unsigned Length, | |
| unsigned TreeSize) { | |
| for (SizePair &P : RangeSizes.slice(StartIdx, Length)) | |
| P.second = std::max(P.second, TreeSize); | |
| } | |
| bool StoreChainContext::updateCandidateVFs(const TargetTransformInfo &TTI) { | |
| assert(CandidateVFs.empty() && "Did not use all VFs before refilling"); | |
| constexpr unsigned StoresLimit = 64; | |
| const unsigned MaxTotalNum = std::min<unsigned>( | |
| Operands.size(), static_cast<unsigned>(End - getFirstUnvecStore())); | |
| unsigned VF = bit_ceil(ProbeVF) * 2; | |
| if (VF > MaxTotalNum || VF >= StoresLimit) | |
| return false; | |
| // Attempt again to vectorize even larger chains if all previous | |
| // attempts were unsuccessful because of the cost issues. | |
| unsigned Limit = getFloorFullVectorNumberOfElements(TTI, StoreTy, MaxTotalNum, | |
| Opts.slp_revec); | |
| if (bit_floor(Limit) == VF && Limit != VF) | |
| CandidateVFs.push(Limit); | |
| CandidateVFs.push(VF); | |
| ProbeVF = CandidateVFs.front(); | |
| ++Repeat; | |
| RepeatChanged = false; | |
| return true; | |
| } | |
| // Get the current VF | |
| std::optional<unsigned> StoreChainContext::getCurrentVF() const { | |
| if (CandidateVFs.empty()) | |
| return std::nullopt; | |
| return CandidateVFs.front(); | |
| } | |
| bool StoreChainContext::checkTreeSizes(const unsigned SliceStartIdx, | |
| const unsigned VF) const { | |
| auto Sizes = RangeSizes.slice(SliceStartIdx, VF); | |
| unsigned Num = 0; | |
| uint64_t Sum = std::accumulate( | |
| Sizes.begin(), Sizes.end(), static_cast<uint64_t>(0), | |
| [&](uint64_t V, const std::pair<unsigned, unsigned> &Val) { | |
| unsigned Size = Val.first == StoreChainContext::LocallyUnvectorizable | |
| ? 0 | |
| : RangeSizesByIdx[Val.first]; | |
| if (Size == 1) | |
| return V; | |
| ++Num; | |
| return V + Size; | |
| }); | |
| if (Num == 0) | |
| return true; | |
| uint64_t Mean = Sum / Num; | |
| if (Mean == 0) | |
| return true; | |
| uint64_t Dev = | |
| accumulate(Sizes, static_cast<uint64_t>(0), | |
| [&](uint64_t V, const std::pair<unsigned, unsigned> &Val) { | |
| unsigned P = | |
| Val.first == StoreChainContext::LocallyUnvectorizable | |
| ? 0 | |
| : RangeSizesByIdx[Val.first]; | |
| if (P == 1) | |
| return V; | |
| return V + (P - Mean) * (P - Mean); | |
| }) / | |
| Num; | |
| return Dev * 96 / (Mean * Mean) == 0; | |
| } | |
| bool StoreChainContext::vectorizeOneVF( | |
| const TargetTransformInfo &TTI, unsigned VF, | |
| BoUpSLP::ValueSet &VectorizedStores, bool &Changed, | |
| llvm::function_ref<std::optional<bool>(ArrayRef<Value *>, unsigned, | |
| unsigned, unsigned &)> | |
| VectorizeStoreChain) { | |
| bool AnyProfitableGraph = false; | |
| bool PreferWideSlices = false; | |
| unsigned FirstUnvecStore = getFirstUnvecStore(); | |
| // Form slices of size VF starting from FirstUnvecStore and try to | |
| // vectorize them. | |
| while (FirstUnvecStore < End) { | |
| unsigned FirstVecStore = getFirstVecStoreAfter(FirstUnvecStore); | |
| unsigned MaxSliceEnd = FirstVecStore >= End ? End : FirstVecStore; | |
| for (unsigned SliceStartIdx = FirstUnvecStore; | |
| SliceStartIdx + VF <= MaxSliceEnd;) { | |
| if (!checkTreeSizes(SliceStartIdx, VF)) { | |
| ++SliceStartIdx; | |
| continue; | |
| } | |
| ArrayRef<Value *> Slice = ArrayRef(Operands).slice(SliceStartIdx, VF); | |
| assert(all_of(Slice, | |
| [&](Value *V) { | |
| return cast<StoreInst>(V)->getValueOperand()->getType() == | |
| cast<StoreInst>(Slice.front()) | |
| ->getValueOperand() | |
| ->getType(); | |
| }) && | |
| "Expected all operands of same type."); | |
| if (!NonSchedulable.empty()) { | |
| auto [NonSchedSizeMax, NonSchedSizeMin] = | |
| NonSchedulable.lookup(Slice.front()); | |
| if (NonSchedSizeMax > 0 && NonSchedSizeMin <= VF) { | |
| // VF is too ambitious. Try to vectorize another slice before | |
| // trying a smaller VF. | |
| SliceStartIdx += NonSchedSizeMax; | |
| continue; | |
| } | |
| } | |
| auto MarkVectorized = [&](unsigned Len) { | |
| // Mark the vectorized stores so that we don't vectorize them | |
| // again. | |
| VectorizedStores.insert_range( | |
| ArrayRef(Operands).slice(SliceStartIdx, Len)); | |
| AnyProfitableGraph = RepeatChanged = Changed = true; | |
| // If we vectorized initial block, no need to try to vectorize | |
| // it again. | |
| markRangeVectorized(SliceStartIdx, Len, FirstUnvecStore, MaxSliceEnd); | |
| SliceStartIdx += Len; | |
| ++NumStoreChains; | |
| if (Stride > 1) | |
| ++NumStridedStoreChains; | |
| NumVectorizedStores += Len; | |
| }; | |
| // With the register VF 2 the instruction count check may reject the | |
| // leading slice, shifting it strands its stores and the wider VFs are | |
| // tried only if nothing is vectorized: try the wider slice at the same | |
| // position. Once the chain took the wider slices, try them first. | |
| auto TryWideSlice = [&]() { | |
| if (MaxRegVF != 2 || VF != MaxRegVF || | |
| SliceStartIdx != FirstUnvecStore || | |
| SliceStartIdx + 2 * VF > MaxSliceEnd) | |
| return false; | |
| unsigned WideTreeSize; | |
| if (!VectorizeStoreChain( | |
| ArrayRef(Operands).slice(SliceStartIdx, 2 * VF), SliceStartIdx, | |
| MinVF, WideTreeSize) | |
| .value_or(false)) | |
| return false; | |
| MarkVectorized(2 * VF); | |
| return true; | |
| }; | |
| const bool WideFirst = PreferWideSlices; | |
| if (WideFirst && TryWideSlice()) | |
| continue; | |
| PreferWideSlices = false; | |
| unsigned TreeSize; | |
| std::optional<bool> Res = | |
| VectorizeStoreChain(Slice, SliceStartIdx, MinVF, TreeSize); | |
| if (!Res) { | |
| // Update the range of non schedulable VFs for slices starting | |
| // at SliceStartIdx. | |
| NonSchedulable.try_emplace(Slice.front(), std::make_pair(VF, VF)) | |
| .first->getSecond() | |
| .second = VF; | |
| } else if (*Res) { | |
| MarkVectorized(VF); | |
| continue; | |
| } | |
| if (Res && !WideFirst && TryWideSlice()) { | |
| PreferWideSlices = true; | |
| continue; | |
| } | |
| if (VF > 2 && Res && !allOfRangeProfitable(SliceStartIdx, VF, TreeSize)) { | |
| SliceStartIdx += VF; | |
| continue; | |
| } | |
| // Check for the very big VFs that we're not rebuilding same | |
| // trees, just with larger number of elements. | |
| if (VF > MaxRegVF && TreeSize > 1 && | |
| isFirstSizeSameRange(SliceStartIdx, VF, TreeSize)) { | |
| SliceStartIdx += VF; | |
| while (SliceStartIdx != MaxSliceEnd && | |
| isFirstSizeSameRange(SliceStartIdx, 1, TreeSize)) | |
| ++SliceStartIdx; | |
| continue; | |
| } | |
| if (TreeSize > 1) | |
| updateCachedRangeSizes(SliceStartIdx, VF, TreeSize); | |
| ++SliceStartIdx; | |
| AnyProfitableGraph = true; | |
| } | |
| if (FirstUnvecStore >= End) | |
| break; | |
| if (MaxSliceEnd - FirstUnvecStore < VF && | |
| MaxSliceEnd - FirstUnvecStore >= MinVF) | |
| AnyProfitableGraph = true; | |
| FirstUnvecStore = getFirstUnvecStore(MaxSliceEnd); | |
| } | |
| if (!AnyProfitableGraph && VF >= MaxRegVF && has_single_bit(VF)) | |
| while (!CandidateVFs.empty()) | |
| CandidateVFs.pop(); | |
| // For the MaxRegVF case, save RangeSizes to limit compile time | |
| if (VF == MaxRegVF) | |
| updateRangeSizesFromCache(); | |
| incrementVF(); | |
| if (!getCurrentVF()) { | |
| // All values vectorized - exit. | |
| if (allVectorized()) | |
| return false; | |
| // Check if tried all attempts or no need for the last attempts at | |
| // all. | |
| if (Repeat >= MaxAttempts || | |
| (Repeat > 1 && (RepeatChanged || !AnyProfitableGraph))) | |
| return false; | |
| if (!updateCandidateVFs(TTI)) | |
| return false; | |
| // Avoid double update of cache sizes | |
| if (VF != MaxRegVF) | |
| updateRangeSizesFromCache(); | |
| } | |
| return true; | |
| } | |
| /// A group of stores that we'll try to bundle together using vector ops. | |
| /// They are ordered using the signed distance of their address operand to the | |
| /// address of this group's BaseInstr. | |
| class RelatedStoreInsts { | |
| public: | |
| RelatedStoreInsts(unsigned BaseInstrIdx, ArrayRef<StoreInst *> AllStores) | |
| : AllStores(AllStores) { | |
| reset(BaseInstrIdx); | |
| } | |
| void reset(unsigned NewBaseInstr) { | |
| assert(NewBaseInstr < AllStores.size() && | |
| "Instruction index out of bounds"); | |
| BaseInstrIdx = NewBaseInstr; | |
| Instrs.clear(); | |
| insertOrLookup(NewBaseInstr, 0); | |
| } | |
| /// Tries to insert \p InstrIdx as the store with a pointer distance of | |
| /// \p PtrDist. | |
| /// Does nothing if there is already a store with that \p PtrDist. | |
| /// \returns The previously associated Instruction index, or std::nullopt | |
| std::optional<unsigned> insertOrLookup(unsigned InstrIdx, int64_t PtrDist) { | |
| auto [It, Inserted] = Instrs.emplace(PtrDist, InstrIdx); | |
| return Inserted ? std::nullopt : std::make_optional(It->second); | |
| } | |
| using DistToInstMap = std::map<int64_t, unsigned>; | |
| const DistToInstMap &getStores() const { return Instrs; } | |
| /// If \p SI is related to this group of stores, return the distance of its | |
| /// pointer operand to the one the group's BaseInstr. | |
| std::optional<int64_t> getPointerDiff(StoreInst &SI, const DataLayout &DL, | |
| ScalarEvolution &SE) const { | |
| StoreInst &BaseStore = *AllStores[BaseInstrIdx]; | |
| return getPointersDiff( | |
| BaseStore.getValueOperand()->getType(), BaseStore.getPointerOperand(), | |
| SI.getValueOperand()->getType(), SI.getPointerOperand(), DL, SE, | |
| /*StrictCheck=*/true); | |
| } | |
| /// Recompute the pointer distances to be based on \p NewBaseInstIdx. | |
| /// Stores whose index is less than \p MinSafeIdx will be dropped. | |
| void rebase(unsigned MinSafeIdx, unsigned NewBaseInstIdx, | |
| int64_t DistFromCurBase) { | |
| DistToInstMap PrevSet = std::move(Instrs); | |
| reset(NewBaseInstIdx); | |
| // Re-insert stores that come after MinSafeIdx to try and vectorize them | |
| // again. Their distance will be "rebased" to use NewBaseInstIdx as | |
| // reference. | |
| for (auto [Dist, InstIdx] : PrevSet) { | |
| if (InstIdx >= MinSafeIdx) | |
| insertOrLookup(InstIdx, Dist - DistFromCurBase); | |
| } | |
| } | |
| private: | |
| /// The index of the Base instruction, i.e. the one with a 0 pointer distance. | |
| unsigned BaseInstrIdx; | |
| /// Maps a pointer distance from \p BaseInstrIdx to an instruction index. | |
| DistToInstMap Instrs; | |
| /// Reference to all the stores in the BB being analyzed. | |
| ArrayRef<StoreInst *> AllStores; | |
| }; | |
| } // end anonymous namespace | |
| bool SLPVectorizerPass::vectorizeStores( | |
| ArrayRef<StoreInst *> Stores, BoUpSLP &R, | |
| DenseSet<std::tuple<Value *, Value *, Value *, Value *, unsigned>> &Visited, | |
| bool AllowMaskedStores) { | |
| // We may run into multiple chains that merge into a single chain. We mark the | |
| // stores that we vectorized so that we don't visit the same store twice. | |
| BoUpSLP::ValueSet VectorizedStores; | |
| bool Changed = false; | |
| SmallVector<std::unique_ptr<StoreChainContext>> AllContexts; | |
| SmallVector<unsigned> RangeSizesByIdx(Stores.size(), 1); | |
| auto ExtendContexts = [&](const RelatedStoreInsts::DistToInstMap &StoreSeq) { | |
| BoUpSLP::ValueList Operands; | |
| const unsigned MaxStride = | |
| Opts->slp_enable_strided_stores ? Opts->slp_max_stride : 1; | |
| // All chains that we're still building | |
| struct PartialChainStatus { | |
| // Has been added to AllContexts | |
| // Wait to add to AllContexts until at least two stores are found | |
| bool AddedToAllContexts; | |
| union { | |
| // If added, index into AllContexts | |
| unsigned AllContextsIdx; | |
| // Index into Stores if not added to AllContexts yet | |
| unsigned StoresIdx; | |
| }; | |
| // If not added to AllContexts, what is the single store in the chain | |
| Value *FirstStore; | |
| // What is the Stride of this chain | |
| unsigned Stride; | |
| }; | |
| SmallVector<SmallVector<PartialChainStatus, 1>> Chains(MaxStride + 1); | |
| auto GetChainsKey = [&](int64_t Query) -> unsigned { | |
| // Just modulo function, get the index into Chains for a given query | |
| int64_t Rem = (Query % (int64_t)Chains.size()); | |
| if (Rem < 0) | |
| Rem += (int64_t)Chains.size(); | |
| return (unsigned)Rem; | |
| }; | |
| int64_t LastDist; | |
| for (auto [Idx, Data] : enumerate(StoreSeq)) { | |
| auto &[Dist, InstIdx] = Data; | |
| // Clean up chains that can't be continued | |
| if (Idx > 0) | |
| for (int64_t D = LastDist; | |
| D < std::min(Dist, LastDist + (int64_t)Chains.size()); ++D) | |
| Chains[GetChainsKey(D)].clear(); | |
| LastDist = Dist; | |
| // Track which stride lengths we found existing chains for | |
| // Don't have to add new entries for these | |
| SmallVector<bool> FoundStrides(Opts->slp_max_stride + 1, false); | |
| for (auto &Status : Chains[GetChainsKey(Dist)]) { | |
| if (Status.AddedToAllContexts) { | |
| // Chain already in AllContexts() | |
| AllContexts[Status.AllContextsIdx]->addOperand(Stores[InstIdx], | |
| InstIdx); | |
| } else { | |
| // Chain just a single element, not yet in AllContexts() | |
| SmallVector<StoreChainContext::SizePair> RS = {{Status.StoresIdx, 1}, | |
| {InstIdx, 1}}; | |
| BoUpSLP::ValueList Ops = {Status.FirstStore, Stores[InstIdx]}; | |
| AllContexts.emplace_back(std::make_unique<StoreChainContext>( | |
| *Opts, Ops, RS, RangeSizesByIdx, Status.Stride)); | |
| Status.AllContextsIdx = AllContexts.size() - 1; | |
| } | |
| unsigned Key = GetChainsKey(Status.Stride + Dist); | |
| Chains[Key].push_back({/*AddedToAllContexts=*/true, | |
| {Status.AllContextsIdx}, | |
| /*FirstStore=*/nullptr, | |
| Status.Stride}); | |
| FoundStrides[Status.Stride] = true; | |
| } | |
| Chains[GetChainsKey(Dist)].clear(); | |
| // For any stride lengths that we didn't append to a chain for, | |
| // instead start a new chain | |
| for (auto Stride : seq<unsigned>(1, MaxStride + 1)) { | |
| if (FoundStrides[Stride]) | |
| continue; | |
| unsigned Key = GetChainsKey(Dist + Stride); | |
| Chains[Key].push_back({/*AddedToAllContexts=*/false, | |
| {/*StoresIdx=*/(unsigned)InstIdx}, | |
| Stores[InstIdx], | |
| Stride}); | |
| } | |
| } | |
| }; | |
| auto ActuallyVectorizeContexts = [&]() { | |
| unsigned GlobalMaxVF = 0; | |
| for (auto &CtxPtr : AllContexts) | |
| if (CtxPtr->initializeContext(R, *DL, *TTI, Visited)) | |
| GlobalMaxVF = std::max(GlobalMaxVF, CtxPtr->getMaxVF()); | |
| else | |
| CtxPtr.reset(); | |
| // Prioritize non-strided chains (Stride = 1) | |
| llvm::stable_sort(AllContexts, | |
| [](const std::unique_ptr<StoreChainContext> &A, | |
| const std::unique_ptr<StoreChainContext> &B) { | |
| return A && (!B || A->getStride() < B->getStride()); | |
| }); | |
| for (unsigned LimitVF = GlobalMaxVF; LimitVF > 0; | |
| LimitVF = bit_ceil(LimitVF) / 2) { | |
| for (auto &CtxPtr : AllContexts) { | |
| if (!CtxPtr) | |
| break; | |
| StoreChainContext &Context = *CtxPtr; | |
| for (std::optional<unsigned> VFUnval = Context.getCurrentVF(); | |
| VFUnval && *VFUnval >= LimitVF; VFUnval = Context.getCurrentVF()) { | |
| unsigned VF = *VFUnval; | |
| if (!Context.vectorizeOneVF( | |
| *TTI, VF, VectorizedStores, Changed, | |
| [this, &R](ArrayRef<Value *> Chain, unsigned Idx, | |
| unsigned MinVF, unsigned &Size) { | |
| return vectorizeStoreChain(Chain, R, Idx, MinVF, Size); | |
| })) { | |
| CtxPtr.reset(); | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| }; | |
| /// Groups of stores to vectorize | |
| SmallVector<RelatedStoreInsts> SortedStores; | |
| // Inserts the specified store SI with the given index Idx to the set of the | |
| // stores. If the store with the same distance is found already - stop | |
| // insertion, try to vectorize already found stores. If some stores from this | |
| // sequence were not vectorized - try to vectorize them with the new store | |
| // later. But this logic is applied only to the stores, that come before the | |
| // previous store with the same distance. | |
| // Example: | |
| // 1. store x, %p | |
| // 2. store y, %p+1 | |
| // 3. store z, %p+2 | |
| // 4. store a, %p | |
| // 5. store b, %p+3 | |
| // - Scan this from the last to first store. The very first bunch of stores is | |
| // {5, {{4, -3}, {2, -2}, {3, -1}, {5, 0}}} (the element in SortedStores | |
| // vector). | |
| // - The next store in the list - #1 - has the same distance from store #5 as | |
| // the store #4. | |
| // - Try to vectorize sequence of stores 4,2,3,5. | |
| // - If all these stores are vectorized - just drop them. | |
| // - If some of them are not vectorized (say, #3 and #5), do extra analysis. | |
| // - Start new stores sequence. | |
| // The new bunch of stores is {1, {1, 0}}. | |
| // - Add the stores from previous sequence, that were not vectorized. | |
| // Here we consider the stores in the reversed order, rather they are used in | |
| // the IR (Stores are reversed already, see vectorizeStoreChains() function). | |
| // Store #3 can be added -> comes after store #4 with the same distance as | |
| // store #1. | |
| // Store #5 cannot be added - comes before store #4. | |
| // This logic allows to improve the compile time, we assume that the stores | |
| // after previous store with the same distance most likely have memory | |
| // dependencies and no need to waste compile time to try to vectorize them. | |
| // - Try to vectorize the sequence {1, {1, 0}, {3, 2}}. | |
| auto FillStoresSet = [&](unsigned Idx, StoreInst *SI) { | |
| std::optional<int64_t> PtrDist; | |
| auto *RelatedStores = find_if( | |
| SortedStores, [&PtrDist, SI, this](const RelatedStoreInsts &StoreSeq) { | |
| PtrDist = StoreSeq.getPointerDiff(*SI, *DL, *SE); | |
| return PtrDist.has_value(); | |
| }); | |
| // We did not find a comparable store, start a new group. | |
| if (RelatedStores == SortedStores.end()) { | |
| SortedStores.emplace_back(Idx, Stores); | |
| return; | |
| } | |
| // If there is already a store in the group with the same PtrDiff, try to | |
| // vectorize the existing instructions before adding the current store. | |
| // Otherwise, insert this store and keep collecting. | |
| if (std::optional<unsigned> PrevInst = | |
| RelatedStores->insertOrLookup(Idx, *PtrDist)) { | |
| ExtendContexts(RelatedStores->getStores()); | |
| RelatedStores->rebase(/*MinSafeIdx=*/*PrevInst + 1, | |
| /*NewBaseInstIdx=*/Idx, | |
| /*DistFromCurBase=*/*PtrDist); | |
| } | |
| }; | |
| Type *PrevValTy = nullptr; | |
| for (auto [I, SI] : make_filter_range(enumerate(Stores), [&](const auto &P) { | |
| return !R.isDeleted(P.value()); | |
| })) { | |
| if (!PrevValTy) | |
| PrevValTy = SI->getValueOperand()->getType(); | |
| // Check that we do not try to vectorize stores of different types. | |
| if (PrevValTy != SI->getValueOperand()->getType()) { | |
| for (RelatedStoreInsts &StoreSeq : SortedStores) | |
| ExtendContexts(StoreSeq.getStores()); | |
| SortedStores.clear(); | |
| PrevValTy = SI->getValueOperand()->getType(); | |
| } | |
| FillStoresSet(I, SI); | |
| } | |
| // Final vectorization attempt. | |
| for (RelatedStoreInsts &StoreSeq : SortedStores) | |
| ExtendContexts(StoreSeq.getStores()); | |
| ActuallyVectorizeContexts(); | |
| // When some stores are still not vectorized, try to lower them together as a | |
| // single masked store (expand + masked.store). | |
| if (!AllowMaskedStores) | |
| return Changed; | |
| SmallVector<Value *> Remaining; | |
| for (StoreInst *SI : Stores) | |
| if (!R.isDeleted(SI) && !VectorizedStores.contains(SI)) | |
| Remaining.push_back(SI); | |
| if (Remaining.size() < 2) | |
| return Changed; | |
| Type *ValTy = getValueType(Remaining.front(), Opts->slp_revec); | |
| if (any_of(Remaining, [&](Value *V) { | |
| return cast<StoreInst>(V)->getValueOperand()->getType() != ValTy; | |
| })) | |
| return Changed; | |
| SmallVector<Value *> PointerOps; | |
| PointerOps.reserve(Remaining.size()); | |
| for (Value *V : Remaining) | |
| PointerOps.push_back(cast<StoreInst>(V)->getPointerOperand()); | |
| SmallVector<unsigned> Order; | |
| if (!sortPtrAccesses(PointerOps, ValTy, *DL, *SE, Order)) | |
| return Changed; | |
| SmallVector<Value *> Sorted(Remaining.size()); | |
| for (unsigned I : seq<unsigned>(Remaining.size())) | |
| Sorted[I] = Remaining[Order.empty() ? I : Order[I]]; | |
| // No need to re-check for consecutive (stride-1) pairs here: the caller | |
| // already disables masked stores for the whole base-object group when any | |
| // consecutive pair exists, so this subset cannot contain one. | |
| if (!Visited | |
| .insert({Sorted.front(), | |
| cast<StoreInst>(Sorted.front())->getValueOperand(), | |
| Sorted.back(), | |
| cast<StoreInst>(Sorted.back())->getValueOperand(), | |
| Sorted.size()}) | |
| .second) | |
| return Changed; | |
| unsigned Size = 0; | |
| if (vectorizeStoreChain(Sorted, R, /*Idx=*/0, /*MinVF=*/2, Size) | |
| .value_or(false)) { | |
| VectorizedStores.insert_range(Sorted); | |
| Changed = true; | |
| } | |
| return Changed; | |
| } | |
| void SLPVectorizerPass::collectSeedInstructions(BasicBlock *BB) { | |
| // Initialize the collections. We will make a single pass over the block. | |
| Stores.clear(); | |
| GEPs.clear(); | |
| // Visit the store and getelementptr instructions in BB and organize them in | |
| // Stores and GEPs according to the underlying objects of their pointer | |
| // operands. | |
| for (Instruction &I : *BB) { | |
| // Ignore store instructions that are volatile or have a pointer operand | |
| // that doesn't point to a scalar type. | |
| if (auto *SI = dyn_cast<StoreInst>(&I)) { | |
| if (!SI->isSimple()) | |
| continue; | |
| if (!isValidElementType(SI->getValueOperand()->getType(), | |
| Opts->slp_revec)) | |
| continue; | |
| Stores[getUnderlyingObject(SI->getPointerOperand())].push_back(SI); | |
| } | |
| // Ignore getelementptr instructions that have more than one index, a | |
| // constant index, or a pointer operand that doesn't point to a scalar | |
| // type. | |
| else if (auto *GEP = dyn_cast<GetElementPtrInst>(&I)) { | |
| if (GEP->getNumIndices() != 1) | |
| continue; | |
| Value *Idx = GEP->idx_begin()->get(); | |
| if (isa<Constant>(Idx)) | |
| continue; | |
| if (!isValidElementType(Idx->getType(), Opts->slp_revec)) | |
| continue; | |
| if (GEP->getType()->isVectorTy()) | |
| continue; | |
| GEPs[GEP->getPointerOperand()].push_back(GEP); | |
| } | |
| } | |
| } | |
| bool SLPVectorizerPass::tryToVectorizeList(ArrayRef<Value *> VL, BoUpSLP &R, | |
| bool MaxVFOnly, | |
| bool StandaloneSeeds) { | |
| if (VL.size() < 2) | |
| return false; | |
| LLVM_DEBUG(dbgs() << "SLP: Trying to vectorize a list of length = " | |
| << VL.size() << ".\n"); | |
| // Check that all of the parts are instructions of the same type, | |
| // we permit an alternate opcode via InstructionsState. | |
| InstructionsState S = getSameOpcode(VL, *TLI); | |
| if (!S) | |
| return false; | |
| Instruction *I0 = S.getMainOp(); | |
| // Make sure invalid types (including vector type) are rejected before | |
| // determining vectorization factor for scalar instructions. | |
| for (Value *V : VL) { | |
| Type *Ty = V->getType(); | |
| if (!isa<InsertElementInst, InsertValueInst>(V) && | |
| !isValidElementType(Ty, Opts->slp_revec)) { | |
| // NOTE: the following will give user internal llvm type name, which may | |
| // not be useful. | |
| R.getORE()->emit([&]() { | |
| std::string TypeStr; | |
| llvm::raw_string_ostream OS(TypeStr); | |
| Ty->print(OS); | |
| return OptimizationRemarkMissed(SV_NAME, "UnsupportedType", I0) | |
| << "Cannot SLP vectorize list: type " | |
| << TypeStr + " is unsupported by vectorizer"; | |
| }); | |
| return false; | |
| } | |
| } | |
| Type *ScalarTy = | |
| getValueType(VL[0], Opts->slp_revec, /*LookThroughCmp=*/true); | |
| unsigned Sz = R.getVectorElementSize(I0); | |
| unsigned MinVF = R.getMinVF(Sz); | |
| // The buildvector, not filling the whole registers, is vectorized as a whole, | |
| // not as the power-of-2 part plus the scalar remainder. | |
| auto *IE = dyn_cast<InsertElementInst>(I0); | |
| const bool AllowNonPowerOf2 = | |
| Opts->slp_vectorize_non_power_of_2 || | |
| (IE && getNumElements(IE->getType()) < | |
| getFullVectorNumberOfElements( | |
| *TTI, IE->getType()->getScalarType(), | |
| getNumElements(IE->getType()), Opts->slp_revec)); | |
| unsigned MaxVF = | |
| std::max<unsigned>(isAllowedNonPowerOf2VF(VL.size(), AllowNonPowerOf2) | |
| ? VL.size() | |
| : getFloorFullVectorNumberOfElements( | |
| *TTI, ScalarTy, VL.size(), Opts->slp_revec), | |
| MinVF); | |
| MaxVF = std::min(R.getMaximumVF(Sz, S.getOpcode()), MaxVF); | |
| // Standalone seeds only need one register worth of lanes. | |
| if (StandaloneSeeds && Sz != 0) | |
| MaxVF = std::min(MaxVF, std::max(MinVF, R.getMaxVecRegSize() / Sz)); | |
| if (MaxVF < 2) { | |
| R.getORE()->emit([&]() { | |
| return OptimizationRemarkMissed(SV_NAME, "SmallVF", I0) | |
| << "Cannot SLP vectorize list: vectorization factor " | |
| << "less than 2 is not supported"; | |
| }); | |
| return false; | |
| } | |
| bool Changed = false; | |
| bool CandidateFound = false; | |
| InstructionCost MinCost = R.getCostThreshold(); | |
| // The trees of the PHI nodes may walk the loop-carried chains, so the | |
| // overlapping windows of a long list rebuild almost the same large trees. | |
| // Limit total size to avoid compile time blow up. | |
| const unsigned RejectedTreesBudget = | |
| isa<PHINode>(I0) ? Opts->slp_phi_list_budget | |
| : std::numeric_limits<unsigned>::max(); | |
| unsigned RejectedTreesSize = 0; | |
| unsigned NextInst = 0, MaxInst = VL.size(); | |
| for (unsigned VF = MaxVF; NextInst + 1 < MaxInst && VF >= MinVF; | |
| VF = getFloorFullVectorNumberOfElements(*TTI, I0->getType(), VF - 1, | |
| Opts->slp_revec)) { | |
| // No actual vectorization should happen, if number of parts is the same as | |
| // provided vectorization factor (i.e. the scalar type is used for vector | |
| // code during codegen). | |
| auto *VecTy = getWidenedType(ScalarTy, VF); | |
| if (R.getNumberOfParts(VecTy, ScalarTy) == VF) | |
| continue; | |
| // The window revisiting the dependent seeds skipped by the last vectorized | |
| // window, 0 if there is none. | |
| unsigned RevisitI = 0; | |
| for (unsigned I = NextInst; | |
| I < MaxInst && RejectedTreesSize <= RejectedTreesBudget; ++I) { | |
| // The skipped seeds are revisited, continue as if they were not skipped. | |
| if (RevisitI && I > RevisitI) { | |
| I = NextInst - 1; | |
| RevisitI = 0; | |
| continue; | |
| } | |
| unsigned ActualVF = std::min(MaxInst - I, VF); | |
| if (!hasFullVectorsOrPowerOf2(*TTI, ScalarTy, ActualVF, | |
| Opts->slp_revec) && | |
| (ActualVF != VL.size() || | |
| !isAllowedNonPowerOf2VF(ActualVF, AllowNonPowerOf2))) | |
| continue; | |
| if (MaxVFOnly && ActualVF < MaxVF) | |
| break; | |
| if ((VF > MinVF && ActualVF < VF) || (VF == MinVF && ActualVF < 2)) | |
| break; | |
| SmallVector<Value *> Ops(ActualVF, nullptr); | |
| unsigned Idx = 0; | |
| SmallPtrSet<const Value *, 8> Taken; | |
| std::optional<unsigned> FirstSkipped; | |
| for (auto [Off, V] : enumerate(VL.drop_front(I))) { | |
| // Check that a previous iteration of this loop did not delete the | |
| // Value. | |
| auto *Inst = dyn_cast<Instruction>(V); | |
| if (Inst && R.isDeleted(Inst)) | |
| continue; | |
| // The lanes cannot depend on each other, so the seed, fed by the | |
| // already taken one, is left for the next window. | |
| if (StandaloneSeeds && Inst && | |
| any_of(Inst->operand_values(), | |
| [&](const Value *Op) { return Taken.contains(Op); })) { | |
| if (!FirstSkipped) | |
| FirstSkipped = I + Off; | |
| continue; | |
| } | |
| Ops[Idx] = V; | |
| if (StandaloneSeeds) | |
| Taken.insert(V); | |
| ++Idx; | |
| if (Idx == ActualVF) | |
| break; | |
| } | |
| // Not enough vectorizable instructions - exit. A skipped dependent seed | |
| // may start the next window, so the scan must not stop because of it. | |
| if (Idx != ActualVF) { | |
| if (!FirstSkipped) | |
| break; | |
| continue; | |
| } | |
| // The identical lanes compute the very same value, so the vector node | |
| // only duplicates it, and the window, rejected by another attempt in the | |
| // block, is not analyzed again. | |
| if (R.isAnalyzedBundle(Ops) || | |
| (StandaloneSeeds && all_of(drop_begin(Ops), [&](Value *V) { | |
| return cast<Instruction>(Ops.front()) | |
| ->isIdenticalTo(cast<Instruction>(V)); | |
| }))) { | |
| if (StandaloneSeeds) | |
| I += ActualVF - 1; | |
| continue; | |
| } | |
| LLVM_DEBUG(dbgs() << "SLP: Analyzing " << ActualVF << " operations " | |
| << "\n"); | |
| R.buildTree(Ops); | |
| if (R.isTreeTinyAndNotFullyVectorizable()) { | |
| RejectedTreesSize += R.getTreeSize(); | |
| R.analyzedBundle(Ops); | |
| if (StandaloneSeeds) | |
| I += ActualVF - 1; | |
| continue; | |
| } | |
| if (R.isProfitableToReorder()) { | |
| R.reorderTopToBottom(); | |
| R.reorderBottomToTop( | |
| !isa<InsertElementInst, InsertValueInst>(Ops.front())); | |
| } | |
| R.transformNodes(); | |
| R.computeMinimumValueSizes(); | |
| InstructionCost TreeCost = R.calculateTreeCostAndTrimNonProfitable(); | |
| R.buildExternalUses(); | |
| InstructionCost Cost = R.getTreeCost(TreeCost); | |
| CandidateFound = true; | |
| MinCost = std::min(MinCost, Cost); | |
| LLVM_DEBUG(dbgs() << "SLP: Found cost = " << Cost | |
| << " for VF=" << ActualVF << "\n"); | |
| if (Cost < -R.getCostThreshold()) { | |
| LLVM_DEBUG(dbgs() << "SLP: Vectorizing list at cost:" << Cost << ".\n"); | |
| R.getORE()->emit(OptimizationRemark(SV_NAME, "VectorizedList", | |
| cast<Instruction>(Ops[0])) | |
| << "SLP vectorized with cost " << ore::NV("Cost", Cost) | |
| << " and with tree size " | |
| << ore::NV("TreeSize", R.getTreeSize())); | |
| R.vectorizeTree(); | |
| // Move to the next bundle. The skipped dependent seeds are fed by the | |
| // vectorized lanes now, revisit them first. | |
| NextInst = I + VF; | |
| RevisitI = FirstSkipped.value_or(0); | |
| I = FirstSkipped.value_or(NextInst) - 1; | |
| Changed = true; | |
| } else { | |
| RejectedTreesSize += R.getTreeSize(); | |
| R.analyzedBundle(Ops); | |
| if (StandaloneSeeds) | |
| I += ActualVF - 1; | |
| } | |
| } | |
| } | |
| if (!Changed && CandidateFound) { | |
| R.getORE()->emit([&]() { | |
| return OptimizationRemarkMissed(SV_NAME, "NotBeneficial", I0) | |
| << "List vectorization was possible but not beneficial with cost " | |
| << ore::NV("Cost", MinCost) | |
| << " >= " << ore::NV("Treshold", -R.getCostThreshold()); | |
| }); | |
| } else if (!Changed) { | |
| R.getORE()->emit([&]() { | |
| return OptimizationRemarkMissed(SV_NAME, "NotPossible", I0) | |
| << "Cannot SLP vectorize list: vectorization was impossible" | |
| << " with available vectorization factors"; | |
| }); | |
| } | |
| return Changed; | |
| } | |
| namespace { | |
| /// Model horizontal reductions. | |
| /// | |
| /// A horizontal reduction is a tree of reduction instructions that has values | |
| /// that can be put into a vector as its leaves. For example: | |
| /// | |
| /// mul mul mul mul | |
| /// \ / \ / | |
| /// + + | |
| /// \ / | |
| /// + | |
| /// This tree has "mul" as its leaf values and "+" as its reduction | |
| /// instructions. A reduction can feed into a store or a binary operation | |
| /// feeding a phi. | |
| /// ... | |
| /// \ / | |
| /// + | |
| /// | | |
| /// phi += | |
| /// | |
| /// Or: | |
| /// ... | |
| /// \ / | |
| /// + | |
| /// | | |
| /// *p = | |
| /// | |
| class HorizontalReduction { | |
| using ReductionOpsType = SmallVector<Value *, 16>; | |
| using ReductionOpsListType = SmallVector<ReductionOpsType, 2>; | |
| const VectorizeOptions &Opts; | |
| ReductionOpsListType ReductionOps; | |
| /// List of possibly reduced values. | |
| SmallVector<SmallVector<Value *>> ReducedVals; | |
| /// Reduced values that enter the reduction with a flipped sign (from an | |
| /// fsub/fneg chain link), subtracted in the final combine. | |
| SmallPtrSet<Value *, 8> NegatedReducedVals; | |
| /// Maps reduced value to the corresponding reduction operation. | |
| SmallDenseMap<Value *, SmallVector<Instruction *>, 16> ReducedValsToOps; | |
| WeakTrackingVH ReductionRoot; | |
| /// The narrowing cast of the cast round-trip following the reduction | |
| /// operation that consumes the reduced value at the same index (in | |
| /// collection order, before the reversal to accumulation order), or null. | |
| /// Ordered reductions with cast-interleaved links only. | |
| SmallVector<Instruction *> RoundedLinks; | |
| /// The type of reduction operation. | |
| RecurKind RdxKind; | |
| /// Checks if the optimization of original scalar identity operations on | |
| /// matched horizontal reductions is enabled and allowed. | |
| bool IsSupportedHorRdxIdentityOp = false; | |
| /// The minimum number of the reduced values. | |
| const unsigned ReductionLimit = Opts.slp_vectorize_non_power_of_2 ? 3 : 4; | |
| /// Contains vector values for reduction including their scale factor, | |
| /// signedness and sign of their contribution to the reduction result. | |
| SmallVector<ReductionVectorPart> VectorValuesAndScales; | |
| /// Narrow reduced values mapped to the shift and the mask applied after | |
| /// widening. | |
| SmallDenseMap<Value *, NarrowedLeafInfo> NarrowedLeafShifts; | |
| /// zext/shl/and/packed-binop chain dropped with the reduction. | |
| SmallVector<Instruction *> NarrowedChainInsts; | |
| static bool isCmpSelMinMax(Instruction *I) { | |
| return match(I, m_Select(m_Cmp(), m_Value(), m_Value())) && | |
| RecurrenceDescriptor::isMinMaxRecurrenceKind(getRdxKind(I)); | |
| } | |
| // And/or are potentially poison-safe logical patterns like: | |
| // select x, y, false | |
| // select x, true, y | |
| static bool isBoolLogicOp(Instruction *I) { | |
| return isa<SelectInst>(I) && | |
| (match(I, m_LogicalAnd()) || match(I, m_LogicalOr())); | |
| } | |
| /// Checks if instruction is associative and can be vectorized. | |
| enum class ReductionOrdering { Unordered, Ordered, None }; | |
| ReductionOrdering RK = ReductionOrdering::None; | |
| static ReductionOrdering isVectorizable(RecurKind Kind, Instruction *I, | |
| bool TwoElementReduction = false) { | |
| if (Kind == RecurKind::None) | |
| return ReductionOrdering::None; | |
| // Integer ops that map to select instructions or intrinsics are fine. | |
| if (RecurrenceDescriptor::isIntMinMaxRecurrenceKind(Kind) || | |
| isBoolLogicOp(I)) | |
| return ReductionOrdering::Unordered; | |
| // No need to check for associativity, if 2 reduced values. | |
| if (TwoElementReduction) | |
| return ReductionOrdering::Unordered; | |
| if (Kind == RecurKind::FMax || Kind == RecurKind::FMin) { | |
| // FP min/max are associative except for NaN and -0.0. We do not | |
| // have to rule out -0.0 here because the intrinsic semantics do not | |
| // specify a fixed result for it. | |
| return I->getFastMathFlags().noNaNs() ? ReductionOrdering::Unordered | |
| : ReductionOrdering::Ordered; | |
| } | |
| if (Kind == RecurKind::FMaximum || Kind == RecurKind::FMinimum) | |
| return ReductionOrdering::Unordered; | |
| // Reassociation alone cannot change the sign of a zero result, so nsz | |
| // is not required for fadd reductions. | |
| if (I->isAssociative() || (Kind == RecurKind::FAdd && I->hasAllowReassoc())) | |
| return ReductionOrdering::Unordered; | |
| return isCommutative(I) ? ReductionOrdering::Ordered | |
| : ReductionOrdering::None; | |
| } | |
| static Value *getRdxOperand(Instruction *I, unsigned Index) { | |
| // Poison-safe 'or' takes the form: select X, true, Y | |
| // To make that work with the normal operand processing, we skip the | |
| // true value operand. | |
| // TODO: Change the code and data structures to handle this without a hack. | |
| if (getRdxKind(I) == RecurKind::Or && isa<SelectInst>(I) && Index == 1) | |
| return I->getOperand(2); | |
| return I->getOperand(Index); | |
| } | |
| /// Creates reduction operation with the current opcode. | |
| static Value *createOp(IRBuilderBase &Builder, RecurKind Kind, Value *LHS, | |
| Value *RHS, const Twine &Name, bool UseSelect) { | |
| Type *OpTy = LHS->getType(); | |
| assert(OpTy == RHS->getType() && "Expected LHS and RHS of same type"); | |
| switch (Kind) { | |
| case RecurKind::Or: { | |
| if (UseSelect && OpTy == CmpInst::makeCmpResultType(OpTy)) | |
| return Builder.CreateSelectWithUnknownProfile( | |
| LHS, ConstantInt::getAllOnesValue(CmpInst::makeCmpResultType(OpTy)), | |
| RHS, DEBUG_TYPE, Name); | |
| unsigned RdxOpcode = RecurrenceDescriptor::getOpcode(Kind); | |
| return Builder.CreateBinOp((Instruction::BinaryOps)RdxOpcode, LHS, RHS, | |
| Name); | |
| } | |
| case RecurKind::And: { | |
| if (UseSelect && OpTy == CmpInst::makeCmpResultType(OpTy)) | |
| return Builder.CreateSelectWithUnknownProfile( | |
| LHS, RHS, | |
| ConstantInt::getNullValue(CmpInst::makeCmpResultType(OpTy)), | |
| DEBUG_TYPE, Name); | |
| unsigned RdxOpcode = RecurrenceDescriptor::getOpcode(Kind); | |
| return Builder.CreateBinOp((Instruction::BinaryOps)RdxOpcode, LHS, RHS, | |
| Name); | |
| } | |
| case RecurKind::Add: | |
| case RecurKind::Mul: | |
| case RecurKind::Xor: | |
| case RecurKind::FAdd: | |
| case RecurKind::FSub: | |
| case RecurKind::FMul: { | |
| unsigned RdxOpcode = RecurrenceDescriptor::getOpcode(Kind); | |
| return Builder.CreateBinOp((Instruction::BinaryOps)RdxOpcode, LHS, RHS, | |
| Name); | |
| } | |
| case RecurKind::SMax: | |
| case RecurKind::SMin: | |
| case RecurKind::UMax: | |
| case RecurKind::UMin: | |
| if (UseSelect) { | |
| CmpInst::Predicate Pred = llvm::getMinMaxReductionPredicate(Kind); | |
| Value *Cmp = Builder.CreateICmp(Pred, LHS, RHS, Name); | |
| return Builder.CreateSelectWithUnknownProfile(Cmp, LHS, RHS, DEBUG_TYPE, | |
| Name); | |
| } | |
| [[fallthrough]]; | |
| case RecurKind::FMax: | |
| case RecurKind::FMin: | |
| case RecurKind::FMaximum: | |
| case RecurKind::FMinimum: | |
| case RecurKind::FMaximumNum: | |
| case RecurKind::FMinimumNum: { | |
| Intrinsic::ID Id = llvm::getMinMaxReductionIntrinsicOp(Kind); | |
| return Builder.CreateBinaryIntrinsic(Id, LHS, RHS); | |
| } | |
| default: | |
| llvm_unreachable("Unknown reduction operation."); | |
| } | |
| } | |
| /// \returns true if the disjoint flag of the reduction ops is kept for the | |
| /// emitted ops. | |
| bool keepDisjoint() const { | |
| if (getBoolReduxWideTy(RdxKind, ReductionRoot->getType(), | |
| ReducedVals.front().front()->getType())) | |
| return false; | |
| return all_of(make_second_range(NarrowedLeafShifts), | |
| [](const NarrowedLeafInfo &L) { return L.Disjoint; }); | |
| } | |
| /// Creates reduction operation with the current opcode with the IR flags | |
| /// from \p ReductionOps, dropping nuw/nsw flags, and disjoint flag if \p | |
| /// KeepDisjoint is false. | |
| static Value *createOpWithFlags(IRBuilderBase &Builder, RecurKind RdxKind, | |
| Value *LHS, Value *RHS, const Twine &Name, | |
| const ReductionOpsListType &ReductionOps, | |
| bool KeepDisjoint) { | |
| bool UseSelect = ReductionOps.size() == 2 || | |
| // Logical or/and. | |
| (ReductionOps.size() == 1 && | |
| any_of(ReductionOps.front(), IsaPred<SelectInst>)); | |
| assert((!UseSelect || ReductionOps.size() != 2 || | |
| isa<SelectInst>(ReductionOps[1][0])) && | |
| "Expected cmp + select pairs for reduction"); | |
| Value *Op = createOp(Builder, RdxKind, LHS, RHS, Name, UseSelect); | |
| if (RecurrenceDescriptor::isIntMinMaxRecurrenceKind(RdxKind)) { | |
| if (auto *Sel = dyn_cast<SelectInst>(Op)) { | |
| propagateIRFlags(Sel->getCondition(), ReductionOps[0], nullptr, | |
| /*IncludeWrapFlags=*/false); | |
| propagateIRFlags(Op, ReductionOps[1], nullptr, | |
| /*IncludeWrapFlags=*/false); | |
| return Op; | |
| } | |
| } | |
| propagateIRFlags(Op, ReductionOps[0], nullptr, /*IncludeWrapFlags=*/false); | |
| if (!KeepDisjoint && match(Op, m_DisjointOr(m_Value(), m_Value()))) | |
| cast<PossiblyDisjointInst>(Op)->setIsDisjoint(false); | |
| return Op; | |
| } | |
| /// Creates reduction operation with the current opcode with the IR flags | |
| /// from \p ReductionOps, dropping nuw/nsw flags. | |
| Value *createOp(IRBuilderBase &Builder, RecurKind Kind, Value *LHS, | |
| Value *RHS, const Twine &Name, | |
| const ReductionOpsListType &ReductionOps) const { | |
| return createOpWithFlags(Builder, Kind, LHS, RHS, Name, ReductionOps, | |
| keepDisjoint()); | |
| } | |
| public: | |
| /// \returns true if \p V has an opcode that could form an ordered reduction. | |
| static bool isSupportedOrderedReductionOp(Instruction *I) { | |
| return I->getOpcode() == Instruction::FAdd; | |
| } | |
| static RecurKind getRdxKind(Value *V) { | |
| auto *I = dyn_cast<Instruction>(V); | |
| if (!I) | |
| return RecurKind::None; | |
| if (match(I, m_Add(m_Value(), m_Value()))) | |
| return RecurKind::Add; | |
| if (match(I, m_Mul(m_Value(), m_Value()))) | |
| return RecurKind::Mul; | |
| if (match(I, m_And(m_Value(), m_Value())) || | |
| match(I, m_LogicalAnd(m_Value(), m_Value()))) | |
| return RecurKind::And; | |
| if (match(I, m_Or(m_Value(), m_Value())) || | |
| match(I, m_LogicalOr(m_Value(), m_Value()))) | |
| return RecurKind::Or; | |
| if (match(I, m_Xor(m_Value(), m_Value()))) | |
| return RecurKind::Xor; | |
| if (match(I, m_FAdd(m_Value(), m_Value()))) | |
| return RecurKind::FAdd; | |
| if (match(I, m_FMul(m_Value(), m_Value()))) | |
| return RecurKind::FMul; | |
| if (match(I, m_Intrinsic<Intrinsic::maxnum>(m_Value(), m_Value()))) | |
| return RecurKind::FMax; | |
| if (match(I, m_Intrinsic<Intrinsic::minnum>(m_Value(), m_Value()))) | |
| return RecurKind::FMin; | |
| if (match(I, m_FMaximum(m_Value(), m_Value()))) | |
| return RecurKind::FMaximum; | |
| if (match(I, m_FMinimum(m_Value(), m_Value()))) | |
| return RecurKind::FMinimum; | |
| // TODO: remove several special-case paths that deal with selects. | |
| if (match(I, m_SMax(m_Value(), m_Value()))) | |
| return RecurKind::SMax; | |
| if (match(I, m_SMin(m_Value(), m_Value()))) | |
| return RecurKind::SMin; | |
| if (match(I, m_UMax(m_Value(), m_Value()))) | |
| return RecurKind::UMax; | |
| if (match(I, m_UMin(m_Value(), m_Value()))) | |
| return RecurKind::UMin; | |
| if (auto *Select = dyn_cast<SelectInst>(I)) { | |
| // Try harder: look for min/max pattern based on instructions producing | |
| // same values such as: select ((cmp Inst1, Inst2), Inst1, Inst2). | |
| // During the intermediate stages of SLP, it's very common to have | |
| // pattern like this (since optimizeGatherSequence is run only once | |
| // at the end): | |
| // %1 = extractelement <2 x i32> %a, i32 0 | |
| // %2 = extractelement <2 x i32> %a, i32 1 | |
| // %cond = icmp sgt i32 %1, %2 | |
| // %3 = extractelement <2 x i32> %a, i32 0 | |
| // %4 = extractelement <2 x i32> %a, i32 1 | |
| // %select = select i1 %cond, i32 %3, i32 %4 | |
| CmpPredicate Pred; | |
| Instruction *L1; | |
| Instruction *L2; | |
| Value *LHS = Select->getTrueValue(); | |
| Value *RHS = Select->getFalseValue(); | |
| Value *Cond = Select->getCondition(); | |
| // TODO: Support inverse predicates. | |
| if (match(Cond, m_Cmp(Pred, m_Specific(LHS), m_Instruction(L2)))) { | |
| if (!isa<ExtractElementInst>(RHS) || | |
| !L2->isIdenticalTo(cast<Instruction>(RHS))) | |
| return RecurKind::None; | |
| } else if (match(Cond, m_Cmp(Pred, m_Instruction(L1), m_Specific(RHS)))) { | |
| if (!isa<ExtractElementInst>(LHS) || | |
| !L1->isIdenticalTo(cast<Instruction>(LHS))) | |
| return RecurKind::None; | |
| } else { | |
| if (!isa<ExtractElementInst>(LHS) || !isa<ExtractElementInst>(RHS)) | |
| return RecurKind::None; | |
| if (!match(Cond, m_Cmp(Pred, m_Instruction(L1), m_Instruction(L2))) || | |
| !L1->isIdenticalTo(cast<Instruction>(LHS)) || | |
| !L2->isIdenticalTo(cast<Instruction>(RHS))) | |
| return RecurKind::None; | |
| } | |
| switch (Pred) { | |
| default: | |
| return RecurKind::None; | |
| case CmpInst::ICMP_SGT: | |
| case CmpInst::ICMP_SGE: | |
| return RecurKind::SMax; | |
| case CmpInst::ICMP_SLT: | |
| case CmpInst::ICMP_SLE: | |
| return RecurKind::SMin; | |
| case CmpInst::ICMP_UGT: | |
| case CmpInst::ICMP_UGE: | |
| return RecurKind::UMax; | |
| case CmpInst::ICMP_ULT: | |
| case CmpInst::ICMP_ULE: | |
| return RecurKind::UMin; | |
| } | |
| } | |
| return RecurKind::None; | |
| } | |
| /// Get the index of the first operand. | |
| static unsigned getFirstOperandIndex(Instruction *I) { | |
| return isCmpSelMinMax(I) ? 1 : 0; | |
| } | |
| private: | |
| /// Total number of operands in the reduction operation. | |
| static unsigned getNumberOfOperands(Instruction *I) { | |
| return isCmpSelMinMax(I) ? 3 : (I->isUnaryOp() ? 1 : 2); | |
| } | |
| /// Expected number of uses for reduction operations/reduced values. | |
| static bool hasRequiredNumberOfUses(bool IsCmpSelMinMax, Instruction *I) { | |
| if (IsCmpSelMinMax) { | |
| // SelectInst must be used twice while the condition op must have single | |
| // use only. | |
| if (auto *Sel = dyn_cast<SelectInst>(I)) | |
| return Sel->hasNUses(2) && Sel->getCondition()->hasOneUse(); | |
| return I->hasNUses(2); | |
| } | |
| // Arithmetic reduction operation must be used once only. | |
| return I->hasOneUse(); | |
| } | |
| /// Initializes the list of reduction operations. | |
| void initReductionOps(Instruction *I) { | |
| if (isCmpSelMinMax(I)) | |
| ReductionOps.assign(2, ReductionOpsType()); | |
| else | |
| ReductionOps.assign(1, ReductionOpsType()); | |
| } | |
| /// Add all reduction operations for the reduction instruction \p I. | |
| void addReductionOps(Instruction *I) { | |
| if (isCmpSelMinMax(I)) { | |
| ReductionOps[0].emplace_back(cast<SelectInst>(I)->getCondition()); | |
| ReductionOps[1].emplace_back(I); | |
| } else { | |
| ReductionOps[0].emplace_back(I); | |
| } | |
| } | |
| static bool isGoodForReduction(ArrayRef<Value *> Data) { | |
| int Sz = Data.size(); | |
| auto *I = dyn_cast<Instruction>(Data.front()); | |
| return Sz > 1 || isConstant(Data.front()) || | |
| (I && !isa<LoadInst>(I) && isValidForAlternation(I->getOpcode())); | |
| } | |
| /// Optimizes original placement of the reduced values for the reduction tree. | |
| /// For example, if there is a zext i1 + selects, we can merge select | |
| /// into zext and improve emission of the reductions. | |
| void optimizeReducedVals(BoUpSLP &R, DominatorTree &DT, const DataLayout &DL, | |
| const TargetTransformInfo &TTI, | |
| const TargetLibraryInfo &TLI) { | |
| SmallDenseMap<unsigned, unsigned> UsedReductionOpIds; | |
| for (const auto [Idx, Vals] : enumerate(ReducedVals)) { | |
| if (auto *I = dyn_cast<Instruction>(Vals.front())) | |
| UsedReductionOpIds.try_emplace(I->getOpcode(), Idx); | |
| } | |
| // Check if zext i1 can be merged with select. | |
| auto ZExtIt = UsedReductionOpIds.find(Instruction::ZExt); | |
| auto SelectIt = UsedReductionOpIds.find(Instruction::Select); | |
| if (ZExtIt != UsedReductionOpIds.end() && | |
| SelectIt != UsedReductionOpIds.end()) { | |
| unsigned ZExtIdx = ZExtIt->second; | |
| unsigned SelectIdx = SelectIt->second; | |
| auto *ZExt = cast<ZExtInst>(ReducedVals[ZExtIdx].front()); | |
| // ZExt is compatible with Select? Merge select to zext, if so. | |
| if (ZExt->getSrcTy()->isIntegerTy(1) && | |
| ZExt->getType() == ReducedVals[SelectIdx].front()->getType()) { | |
| ReducedVals[ZExtIdx].append(ReducedVals[SelectIdx]); | |
| ReducedVals.erase(std::next(ReducedVals.begin(), SelectIdx)); | |
| } | |
| } | |
| // Merge 1 element reduced value groups into larger group of shl, if only 2 | |
| // groups available. May trigger extra vectorization with the copyables. | |
| if (ReducedVals.size() == 2 && | |
| (ReducedVals.front().size() == 1 || ReducedVals.back().size() == 1)) { | |
| SmallVector<Value *> Ops(ReducedVals.front().size() + | |
| ReducedVals.back().size()); | |
| copy(ReducedVals.front(), Ops.begin()); | |
| copy(ReducedVals.back(), | |
| std::next(Ops.begin(), ReducedVals.front().size())); | |
| InstructionsCompatibilityAnalysis Analysis(DT, DL, TTI, TLI); | |
| InstructionsState OpS = Analysis.buildInstructionsState( | |
| Ops, R, /*TryCopyableElementsVectorization=*/true, | |
| /*WithProfitabilityCheck=*/true); | |
| if (OpS && OpS.areInstructionsWithCopyableElements() && | |
| OpS.getOpcode() == Instruction::Shl) { | |
| // The smallest reduced values group should be the first. | |
| if (ReducedVals.back().size() == 1 && ReducedVals.front().size() != 1) | |
| std::swap(ReducedVals.front(), ReducedVals.back()); | |
| // Check if the largest reduced values group are shl and sort them by | |
| // the constant shift amount to improve chances of vectorization with | |
| // the copyables. | |
| auto Comparator = [](Value *V1, Value *V2) { | |
| ConstantInt *C1, *C2; | |
| if (!match(V1, m_Shl(m_Value(), m_ConstantInt(C1)))) | |
| return false; | |
| if (!match(V2, m_Shl(m_Value(), m_ConstantInt(C2)))) | |
| return true; | |
| return C1->getZExtValue() < C2->getZExtValue(); | |
| }; | |
| stable_sort(ReducedVals.back(), Comparator); | |
| ReducedVals.front().append(ReducedVals.back()); | |
| ReducedVals.pop_back(); | |
| } | |
| } | |
| } | |
| public: | |
| explicit HorizontalReduction(const VectorizeOptions &Opts) : Opts(Opts) {} | |
| HorizontalReduction(const VectorizeOptions &Opts, Instruction *I, | |
| ArrayRef<Value *> Ops) | |
| : Opts(Opts), ReductionRoot(I), ReductionLimit(2) { | |
| RdxKind = HorizontalReduction::getRdxKind(I); | |
| ReductionOps.emplace_back().push_back(I); | |
| ReducedVals.emplace_back().assign(Ops.begin(), Ops.end()); | |
| for (Value *V : Ops) | |
| ReducedValsToOps[V].push_back(I); | |
| } | |
| /// Returns true if the matched reduction chain has flattened fsub/fneg | |
| /// links (the leaves behind them may all be positive, e.g. with double | |
| /// negations). | |
| bool hasFlattenedNegations() const { | |
| return any_of(ReductionOps, [](ArrayRef<Value *> Ops) { | |
| return any_of(Ops, [](Value *Op) { | |
| return Op && isFlattenableNegation(cast<Instruction>(Op)); | |
| }); | |
| }); | |
| } | |
| bool matchReductionForOperands() { | |
| // Analyze "regular" integer/FP types for reductions - no target-specific | |
| // types or pointers. | |
| assert(ReductionRoot && "Reduction root is not set!"); | |
| RK = isVectorizable(RdxKind, cast<Instruction>(ReductionRoot), | |
| all_of(ReducedVals, [](ArrayRef<Value *> Ops) { | |
| return Ops.size() == 2; | |
| })); | |
| return RK != ReductionOrdering::None; | |
| } | |
| /// Analyze whether \p Root forms a linearized ordered reduction chain. | |
| /// If \p MatchLHS is true, analyzes LHS-associated (left-linearized) chains | |
| /// where the chain recurses on the LHS and the RHS at each level is a leaf: | |
| /// ((((v0 op v1) op v2) op v3) op v4) | |
| /// If \p MatchLHS is false, analyzes RHS-associated (right-linearized) chains | |
| /// where the chain recurses on the RHS and the LHS at each level is a leaf: | |
| /// (v0 op (v1 op (v2 op (v3 op v4)))) | |
| /// Leaf values are stored in ReducedVals.back() in accumulation order | |
| /// (innermost pair first, outermost last), e.g. [v0,v1,v2,v3,v4] for | |
| /// LHS-associated and [v4,v3,v2,v1,v0] for RHS-associated chains. | |
| /// \returns true if the chain is a valid ordered reduction. | |
| bool matchOrderedReduction(BoUpSLP &R, Instruction *Root, bool MatchLHS) { | |
| ReducedVals.clear(); | |
| ReducedValsToOps.clear(); | |
| ReductionOps.clear(); | |
| RoundedLinks.clear(); | |
| NarrowedLeafShifts.clear(); | |
| NarrowedChainInsts.clear(); | |
| NegatedReducedVals.clear(); | |
| RdxKind = getRdxKind(Root); | |
| // Currently, only ordered fadd reductions are supported. | |
| if (RdxKind != RecurKind::FAdd) | |
| return false; | |
| // Reassociable chains with non-elidable cast round-trip links are | |
| // handled here as well: the emission keeps the original order. | |
| if (isVectorizable(RdxKind, Root) == ReductionOrdering::None) | |
| return false; | |
| // Ordered reductions only support simple binary ops, not min/max | |
| // select(cmp) patterns or poison-safe bool logic ops. | |
| if (isCmpSelMinMax(Root) || isBoolLogicOp(Root)) | |
| return false; | |
| Type *Ty = Root->getType(); | |
| if (!isValidElementType(Ty, Opts.slp_revec) || Ty->isPointerTy()) | |
| return false; | |
| // This is an ordered (linearized) reduction regardless of whether the | |
| // individual operations are associative. | |
| RK = ReductionOrdering::Ordered; | |
| ReductionRoot = Root; | |
| BasicBlock *BB = Root->getParent(); | |
| unsigned FirstOpIdx = getFirstOperandIndex(Root); | |
| unsigned ChainOpIdx = MatchLHS ? FirstOpIdx : FirstOpIdx + 1; | |
| unsigned LeafOpIdx = MatchLHS ? FirstOpIdx + 1 : FirstOpIdx; | |
| initReductionOps(Root); | |
| ReducedVals.emplace_back(); | |
| SmallPtrSet<Instruction *, 16> Visited; | |
| Instruction *TreeN = Root; | |
| unsigned Depth = 0; | |
| bool ChainComplete = false; | |
| constexpr unsigned MaxReducedVals = 1024; | |
| Instruction *PendingCast = nullptr; | |
| while (TreeN) { | |
| if (Depth++ > Opts.slp_recursion_max_depth) | |
| break; | |
| if (ReducedVals.back().size() >= MaxReducedVals) | |
| break; | |
| if (!Visited.insert(TreeN).second) | |
| break; | |
| if (getRdxKind(TreeN) != RdxKind) | |
| break; | |
| if (TreeN->getParent() != BB) | |
| break; | |
| addReductionOps(TreeN); | |
| Value *LeafVal = getRdxOperand(TreeN, LeafOpIdx); | |
| Value *ChainVal = getRdxOperand(TreeN, ChainOpIdx); | |
| ReducedValsToOps[LeafVal].push_back(TreeN); | |
| ReducedValsToOps[ChainVal].push_back(TreeN); | |
| ReducedVals.back().push_back(LeafVal); | |
| RoundedLinks.push_back(PendingCast); | |
| PendingCast = nullptr; | |
| auto *ChainInst = dyn_cast<Instruction>(ChainVal); | |
| // The link may round the accumulator to a narrower type and extend it | |
| // back; look through the cast pair, the rounding is re-emitted later. | |
| if (ChainInst && getRdxKind(ChainInst) != RdxKind) { | |
| if (Instruction *NarrowCast = | |
| lookThroughCastRoundTrip(ChainVal, /*MustBeElidable=*/false)) { | |
| auto *I = cast<Instruction>(NarrowCast->getOperand(0)); | |
| if (getRdxKind(I) == RdxKind && | |
| hasRequiredNumberOfUses(/*IsCmpSelMinMax=*/false, I)) { | |
| ReductionOps[0].push_back(ChainVal); | |
| ReductionOps[0].push_back(NarrowCast); | |
| PendingCast = NarrowCast; | |
| ChainInst = I; | |
| } | |
| } | |
| } | |
| if (!ChainInst || getRdxKind(ChainInst) != RdxKind || | |
| !hasRequiredNumberOfUses(/*IsCmpSelMinMax=*/false, ChainInst)) { | |
| ReducedVals.back().push_back(ChainVal); | |
| RoundedLinks.push_back(nullptr); | |
| ChainComplete = true; | |
| break; | |
| } | |
| TreeN = ChainInst; | |
| } | |
| // Leaves are collected outer-to-inner (top-down). Reverse to get the | |
| // accumulation order (innermost first) needed by ordered reduction | |
| // intrinsics. This is correct for both LHS- and RHS-associated chains | |
| // because fadd is commutative — each step only relies on a+b == b+a, | |
| // never on associativity. | |
| if (!ChainComplete || ReducedVals.back().size() < ReductionLimit) { | |
| for (ReductionOpsType &RdxOps : ReductionOps) | |
| for (Value *RdxOp : RdxOps) | |
| R.analyzedReductionRoot(cast<Instruction>(RdxOp)); | |
| ReducedVals.pop_back(); | |
| ReducedValsToOps.clear(); | |
| ReductionOps.clear(); | |
| RoundedLinks.clear(); | |
| return false; | |
| } | |
| std::reverse(ReducedVals.back().begin(), ReducedVals.back().end()); | |
| return true; | |
| } | |
| /// Checks if \p I is an fsub/fneg, which can be flattened into an fadd | |
| /// reduction chain with a flipped sign of its subtracted operand. nsz is | |
| /// required: subtracted leaves are regrouped and negated as a whole, and | |
| /// -a + -b == -(a + b) may flip the sign of a zero result. | |
| static bool isFlattenableNegation(Instruction *I) { | |
| return (I->getOpcode() == Instruction::FSub || | |
| I->getOpcode() == Instruction::FNeg) && | |
| I->hasAllowReassoc() && I->hasNoSignedZeros(); | |
| } | |
| /// A group of loads or non-instructions rarely amortizes on its own the | |
| /// cost of the reduction operation, charged to the first vectorized group. | |
| static bool isCheapReductionGroup(ArrayRef<Value *> P) { | |
| return !isa<Instruction>(P.front()) || isa<LoadInst>(P.front()); | |
| } | |
| /// Try to find a reduction tree. If \p FlattenNegations is false, the | |
| /// reassociable fsub/fneg links of an fadd chain are not flattened (they are | |
| /// leaves of the reduction). | |
| bool matchAssociativeReduction(BoUpSLP &R, Instruction *Root, | |
| ScalarEvolution &SE, DominatorTree &DT, | |
| const DataLayout &DL, | |
| const TargetTransformInfo &TTI, | |
| const TargetLibraryInfo &TLI, | |
| bool FlattenNegations = true) { | |
| RdxKind = HorizontalReduction::getRdxKind(Root); | |
| // A reassociable fsub root is a flattened link of an fadd reduction: its | |
| // subtracted operand enters with a flipped sign. Without the flattening | |
| // there is no reduction to match. (An fneg is not a reduction candidate.) | |
| const bool IsNegatedRoot = RdxKind == RecurKind::None && | |
| Root->getOpcode() == Instruction::FSub && | |
| isFlattenableNegation(Root); | |
| if (IsNegatedRoot) | |
| RdxKind = RecurKind::FAdd; | |
| RK = isVectorizable(RdxKind, Root); | |
| if (RK == ReductionOrdering::None) | |
| return false; | |
| // Analyze "regular" integer/FP types for reductions - no target-specific | |
| // types or pointers. | |
| Type *Ty = Root->getType(); | |
| if (!isValidElementType(Ty, Opts.slp_revec) || Ty->isPointerTy()) | |
| return false; | |
| // Though the ultimate reduction may have multiple uses, its condition must | |
| // have only single use. | |
| if (auto *Sel = dyn_cast<SelectInst>(Root)) | |
| if (!Sel->getCondition()->hasOneUse()) | |
| RK = ReductionOrdering::Ordered; | |
| ReductionRoot = Root; | |
| // Iterate through all the operands of the possible reduction tree and | |
| // gather all the reduced values, sorting them by their value id. | |
| BasicBlock *BB = Root->getParent(); | |
| bool IsCmpSelMinMax = isCmpSelMinMax(Root); | |
| const ReductionOrdering InitialRK = RK; | |
| // For unordered fadd reductions, reassociable fsub/fneg chain links are | |
| // flattened with a flipped sign on the subtracted operand: the leaves, | |
| // reduced through such links, are subtracted in the final combine. | |
| // Ordered reductions keep their accumulation order and are excluded. | |
| bool TrackSign = FlattenNegations && RdxKind == RecurKind::FAdd && | |
| RK == ReductionOrdering::Unordered; | |
| // The leaf signs are modeled per value. If they cannot be modeled this way | |
| // (the same value occurs both added and subtracted, or the reduction | |
| // switches to ordered after some fsub/fneg links have been flattened | |
| // already), the analysis restarts without the fsub/fneg flattening. | |
| bool Restart = false; | |
| do { | |
| if (Restart) { | |
| assert(TrackSign && "Expected a sign-tracking analysis to restart"); | |
| TrackSign = false; | |
| } | |
| // The root itself is a flattened link - there is no reduction without | |
| // the flattening. | |
| if (IsNegatedRoot && !TrackSign) | |
| return false; | |
| Restart = false; | |
| RK = InitialRK; | |
| ReducedVals.clear(); | |
| ReducedValsToOps.clear(); | |
| NegatedReducedVals.clear(); | |
| bool FlattenedNegations = false; | |
| initReductionOps(Root); | |
| DenseMap<std::pair<size_t, Value *>, SmallVector<LoadInst *>> LoadsMap; | |
| SmallSet<size_t, 2> LoadKeyUsed; | |
| auto GenerateLoadsSubkey = [&](size_t Key, LoadInst *LI) { | |
| Key = hash_combine(hash_value(LI->getParent()->getNumber()), Key); | |
| Value *Ptr = getUnderlyingObject(LI->getPointerOperand(), | |
| Opts.slp_recursion_max_depth); | |
| if (!LoadKeyUsed.insert(Key).second) { | |
| auto LIt = LoadsMap.find(std::make_pair(Key, Ptr)); | |
| if (LIt != LoadsMap.end()) { | |
| for (LoadInst *RLI : LIt->second) { | |
| if (getPointersDiff(RLI->getType(), RLI->getPointerOperand(), | |
| LI->getType(), LI->getPointerOperand(), DL, | |
| SE, /*StrictCheck=*/true)) | |
| return hash_value(RLI->getPointerOperand()); | |
| } | |
| for (LoadInst *RLI : LIt->second) { | |
| if (arePointersCompatible(RLI->getPointerOperand(), | |
| LI->getPointerOperand(), TLI, | |
| Opts.slp_recursion_max_depth)) { | |
| hash_code SubKey = hash_value(RLI->getPointerOperand()); | |
| return SubKey; | |
| } | |
| } | |
| if (LIt->second.size() > 2) { | |
| hash_code SubKey = | |
| hash_value(LIt->second.back()->getPointerOperand()); | |
| return SubKey; | |
| } | |
| } | |
| } | |
| LoadsMap.try_emplace(std::make_pair(Key, Ptr)) | |
| .first->second.push_back(LI); | |
| return hash_value(LI->getPointerOperand()); | |
| }; | |
| SmallVector<Value *> ReducedValsCandidates; | |
| bool AdjustedToOrdered = false; | |
| // True if the chain has an opaque fsub/fneg leaf (not reassociable, extra | |
| // uses, too deep). | |
| bool HasOpaqueNegationLeaf = false; | |
| SmallVector<std::tuple<Instruction *, unsigned, bool>> Worklist( | |
| 1, std::make_tuple(Root, 0, /*Negated=*/false)); | |
| SmallPtrSet<Value *, 8> Operands; | |
| SmallVector<std::pair<Instruction *, unsigned>> | |
| PossibleOrderedReductionOps; | |
| // Leaves that (also) occur with a non-flipped sign. | |
| SmallPtrSet<Value *, 8> PositiveReducedVals; | |
| // Checks if the instruction continues the reduction chain. An fsub/fneg | |
| // chain link in an fadd reduction is recursed into like an fadd, but its | |
| // subtracted operand enters with a flipped sign. Only reassociable links | |
| // qualify. | |
| auto IsChainLink = [&](Instruction *I) { | |
| if (getRdxKind(I) == RdxKind) | |
| return true; | |
| return TrackSign && isFlattenableNegation(I); | |
| }; | |
| // Checks if the operands of the \p TreeN instruction are also reduction | |
| // operations or should be treated as reduced values or an extra | |
| // argument, which is not part of the reduction. | |
| auto CheckOperands = | |
| [&](Instruction *TreeN, SmallVectorImpl<Value *> &PossibleReducedVals, | |
| SmallVectorImpl<std::pair<Instruction *, bool>> &ReductionOps, | |
| ReductionOpsType &AllReductionOps, unsigned Level, bool Negated) { | |
| for (int I : reverse(seq<int>(getFirstOperandIndex(TreeN), | |
| getNumberOfOperands(TreeN)))) { | |
| Value *EdgeVal = getRdxOperand(TreeN, I); | |
| auto *EdgeInst = dyn_cast<Instruction>(EdgeVal); | |
| // The link may be an elidable cast round-trip; look through it to | |
| // continue the chain. It is dropped when the reduction is folded. | |
| if (EdgeInst && getRdxKind(EdgeInst) != RdxKind) { | |
| if (Instruction *NarrowCast = lookThroughCastRoundTrip( | |
| EdgeVal, /*MustBeElidable=*/true)) { | |
| auto *SrcI = cast<Instruction>(NarrowCast->getOperand(0)); | |
| if (getRdxKind(SrcI) == RdxKind && | |
| hasRequiredNumberOfUses(IsCmpSelMinMax, SrcI)) { | |
| AllReductionOps.push_back(EdgeVal); | |
| AllReductionOps.push_back(NarrowCast); | |
| EdgeVal = SrcI; | |
| EdgeInst = SrcI; | |
| } | |
| } | |
| } | |
| ReducedValsToOps[EdgeVal].push_back(TreeN); | |
| // fsub flips its second operand's sign, fneg its only | |
| // operand's. | |
| bool EdgeNegated = | |
| Negated != | |
| (TreeN->getOpcode() == Instruction::FNeg || | |
| (TreeN->getOpcode() == Instruction::FSub && I == 1)); | |
| // If the edge is not an instruction, or it is different from | |
| // the main reduction opcode or has too many uses - possible | |
| // reduced value. Also, do not try to reduce const values, if | |
| // the operation is not foldable. | |
| bool IsReducedVal = !EdgeInst || | |
| Level > Opts.slp_recursion_max_depth || | |
| !IsChainLink(EdgeInst) || | |
| IsCmpSelMinMax != isCmpSelMinMax(EdgeInst); | |
| ReductionOrdering CurrentRK = | |
| IsReducedVal ? ReductionOrdering::None | |
| : isVectorizable(RdxKind, EdgeInst); | |
| if (!IsReducedVal && CurrentRK == ReductionOrdering::Unordered && | |
| RK == ReductionOrdering::Unordered && | |
| !hasRequiredNumberOfUses(IsCmpSelMinMax, EdgeInst)) { | |
| IsReducedVal = true; | |
| CurrentRK = ReductionOrdering::None; | |
| if (PossibleReducedVals.size() < ReductionLimit && | |
| !Operands.contains(EdgeInst)) | |
| PossibleOrderedReductionOps.emplace_back(EdgeInst, Level); | |
| } | |
| if (CurrentRK == ReductionOrdering::None || | |
| Operands.contains(EdgeInst) || | |
| (R.isAnalyzedReductionRoot(EdgeInst) && | |
| all_of(EdgeInst->operands(), IsaPred<Constant>))) { | |
| // Keep the sign of the leaf. A leaf, occurring with both | |
| // signs, cannot be modeled with a per-value sign - restart | |
| // without sign tracking. | |
| if (TrackSign) { | |
| auto &Same = | |
| EdgeNegated ? NegatedReducedVals : PositiveReducedVals; | |
| auto &Other = | |
| EdgeNegated ? PositiveReducedVals : NegatedReducedVals; | |
| Same.insert(EdgeVal); | |
| Restart |= Other.contains(EdgeVal); | |
| HasOpaqueNegationLeaf |= | |
| EdgeInst && (EdgeInst->getOpcode() == Instruction::FSub || | |
| EdgeInst->getOpcode() == Instruction::FNeg); | |
| } | |
| PossibleReducedVals.push_back(EdgeVal); | |
| if (EdgeInst && !isCmpSelMinMax(EdgeInst)) | |
| Operands.insert_range(EdgeInst->operands()); | |
| continue; | |
| } | |
| if (CurrentRK == ReductionOrdering::Ordered) | |
| RK = ReductionOrdering::Ordered; | |
| ReductionOps.emplace_back(EdgeInst, EdgeNegated); | |
| } | |
| }; | |
| SmallPtrSet<Instruction *, 16> Visited; | |
| while (!Worklist.empty()) { | |
| auto [TreeN, Level, Negated] = Worklist.pop_back_val(); | |
| if (!Visited.insert(TreeN).second) | |
| continue; | |
| // A chain link other than the reduction operation is a flattened | |
| // fsub/fneg. | |
| if (getRdxKind(TreeN) != RdxKind) | |
| FlattenedNegations = true; | |
| SmallVector<Value *> PossibleRedVals; | |
| SmallVector<std::pair<Instruction *, bool>> PossibleReductionOps; | |
| CheckOperands(TreeN, PossibleRedVals, PossibleReductionOps, | |
| ReductionOps[0], Level, Negated); | |
| // A leaf occurred with both signs - stop, the analysis restarts | |
| // without the fsub/fneg flattening. | |
| if (Restart) | |
| break; | |
| addReductionOps(TreeN); | |
| ReducedValsCandidates.append(PossibleRedVals.begin(), | |
| PossibleRedVals.end()); | |
| for (auto [I, OpNegated] : reverse(PossibleReductionOps)) | |
| Worklist.emplace_back(I, I->getParent() == BB ? 0 : Level + 1, | |
| OpNegated); | |
| // If not enough elements for unordered vectorization, check if there | |
| // are potential candidates for the ordered vectorization and try to | |
| // add them to the worklist. | |
| if (Worklist.empty() && ReducedValsCandidates.size() < ReductionLimit && | |
| !PossibleOrderedReductionOps.empty() && | |
| RK == ReductionOrdering::Unordered) { | |
| RK = ReductionOrdering::Ordered; | |
| AdjustedToOrdered = true; | |
| SmallPtrSet<const Instruction *, 4> Ops; | |
| // The ordered switch discards the tracked signs (the analysis | |
| // restarts without the fsub/fneg flattening, if any occurred), so | |
| // the candidates re-enter the walk with the non-flipped sign. | |
| for (const auto &[I, Level] : PossibleOrderedReductionOps) { | |
| Ops.insert(I); | |
| Worklist.emplace_back(I, Level, /*Negated=*/false); | |
| } | |
| erase_if(ReducedValsCandidates, [&](Value *V) { | |
| auto *I = dyn_cast<Instruction>(V); | |
| return I && Ops.contains(I); | |
| }); | |
| PossibleOrderedReductionOps.clear(); | |
| } | |
| } | |
| // The flattening reassociates the chain, which the ordered reduction | |
| // must not do, and negating the regrouped subtracted leaves as a whole | |
| // is not sign-of-zero-safe unless the whole flattened chain is nsz (this | |
| // conservatively includes the elidable cast round-trips, collected into | |
| // the reduction operations) - restart without the fsub/fneg flattening, | |
| // if any occurred. Also, an opaque fsub/fneg leaf forms a homogeneous | |
| // group with the flattened links when not flattened, which the | |
| // sign-uniform regrouping of their operands may only fragment - keep the | |
| // unflattened form. | |
| if (FlattenedNegations && | |
| (RK == ReductionOrdering::Ordered || HasOpaqueNegationLeaf || | |
| (!NegatedReducedVals.empty() && | |
| any_of(ReductionOps.front(), [](Value *Op) { | |
| return !cast<Instruction>(Op)->hasNoSignedZeros(); | |
| })))) | |
| Restart = true; | |
| if (Restart) | |
| continue; | |
| // InstCombine may pack same-kind binops under a zext, hiding the | |
| // leaves. Analyze them in the narrow type; the vectorized result is | |
| // widened back, shifted and masked per lane before the reduction. | |
| if (Instruction::isBitwiseLogicOp( | |
| RecurrenceDescriptor::getOpcode(RdxKind)) && | |
| RK == ReductionOrdering::Unordered && !IsCmpSelMinMax && | |
| Ty->isIntegerTy() && !Ty->isIntegerTy(1) && | |
| any_of(ReducedValsCandidates, [](Value *V) { | |
| return isa<ZExtInst>(V) || | |
| match(V, m_Shl(m_ZExt(m_Value()), m_ConstantInt())); | |
| })) { | |
| unsigned WideBW = Ty->getIntegerBitWidth(); | |
| SmallVector<SmallVector<NarrowedLeafInfo>> AllLeaves; | |
| SmallVector<Instruction *> ChainInsts; | |
| Type *NarrowTy = nullptr; | |
| bool Bail = false; | |
| unsigned NumLeaves = 0; | |
| SmallPtrSet<Value *, 8> UniqueLeaves; | |
| for (Value *Cand : ReducedValsCandidates) { | |
| SmallVector<NarrowedLeafInfo> &Leaves = AllLeaves.emplace_back(); | |
| collectNarrowedLeaves(Cand, RecurrenceDescriptor::getOpcode(RdxKind), | |
| WideBW, Opts.slp_recursion_max_depth, Leaves, | |
| ChainInsts); | |
| for (const NarrowedLeafInfo &L : Leaves) { | |
| if (isa<Constant>(L.V) || !L.V->getType()->isIntegerTy() || | |
| (NarrowTy && L.V->getType() != NarrowTy) || | |
| !UniqueLeaves.insert(L.V).second) { | |
| Bail = true; | |
| break; | |
| } | |
| if (!NarrowTy) | |
| NarrowTy = L.V->getType(); | |
| } | |
| if (Bail) | |
| break; | |
| NumLeaves += Leaves.size(); | |
| } | |
| if (!Bail && NumLeaves > ReducedValsCandidates.size() && | |
| NarrowTy != Ty) { | |
| // Collected user-first; dies with the reduction iff all users die. | |
| SmallPtrSet<Value *, 8> Ignorable; | |
| for (ArrayRef<Value *> RdxOps : ReductionOps) | |
| Ignorable.insert_range(RdxOps); | |
| for (Instruction *I : ChainInsts) | |
| if (all_of(I->users(), | |
| [&](User *U) { return Ignorable.contains(U); })) { | |
| Ignorable.insert(I); | |
| NarrowedChainInsts.push_back(I); | |
| } | |
| const bool AllDisjoint = | |
| all_of(make_isa_range<PossiblyDisjointInst>(ChainInsts), | |
| [](PossiblyDisjointInst *I) { return I->isDisjoint(); }); | |
| SmallVector<Value *> NewCandidates; | |
| for (const auto &[Cand, Leaves] : | |
| zip(ReducedValsCandidates, AllLeaves)) { | |
| SmallVector<Instruction *> RdxOps = ReducedValsToOps.lookup(Cand); | |
| for (NarrowedLeafInfo &L : Leaves) { | |
| L.Disjoint = AllDisjoint; | |
| NarrowedLeafShifts.try_emplace(L.V, L); | |
| NewCandidates.push_back(L.V); | |
| ReducedValsToOps[L.V].append(RdxOps); | |
| } | |
| } | |
| ReducedValsCandidates = std::move(NewCandidates); | |
| } | |
| } | |
| // Cap on the number of reduced values, including booleanized leaves. | |
| constexpr unsigned ReducedValsLimit = 1024; | |
| // Logical and/or reductions on i1 may have booleanized wide leaves: | |
| // truncs of same-op chains or zero-tests of values from [0, 1]. | |
| // Reducing in the wide type and taking bit 0 of the result is | |
| // equivalent, since trunc of a bitwise and/or is the bitwise and/or of | |
| // the truncs, and it exposes the underlying (usually consecutive) wide | |
| // values to the tree. All final leaves must be non-poison: the logical | |
| // select/and/or ops may mask poison, which the wide reduction would | |
| // propagate instead. | |
| if ((RdxKind == RecurKind::And || RdxKind == RecurKind::Or) && | |
| RK == ReductionOrdering::Unordered && !IsCmpSelMinMax && | |
| Ty->isIntegerTy(1) && any_of(ReducedValsCandidates, [](Value *V) { | |
| return isa<TruncInst, ICmpInst>(V); | |
| })) { | |
| unsigned RdxOpcode = RecurrenceDescriptor::getOpcode(RdxKind); | |
| auto BooleanizeLeaves = [&]() { | |
| Type *WideTy = nullptr; | |
| SmallVector<Value *> NewCandidates; | |
| SmallVector<Instruction *> ChainInsts; | |
| for (Value *Cand : ReducedValsCandidates) { | |
| SmallVector<Instruction *> RdxOps = ReducedValsToOps.lookup(Cand); | |
| SmallVector<Value *> Worklist(1, Cand); | |
| while (!Worklist.empty()) { | |
| Value *V = Worklist.pop_back_val(); | |
| if (V->getType()->isIntegerTy(1)) { | |
| Value *X; | |
| // trunc iN to i1: bit 0 of the wide value. | |
| if (match(V, m_OneUse(m_Trunc(m_Value(X))))) { | |
| ChainInsts.push_back(cast<Instruction>(V)); | |
| Worklist.push_back(X); | |
| continue; | |
| } | |
| // Zero-test of a value from [0, 1]: same as bit 0 of the | |
| // value. | |
| CmpPredicate Pred; | |
| if (match(V, m_OneUse(m_c_ICmp(Pred, m_Value(X), m_Zero()))) && | |
| Pred == ICmpInst::ICMP_NE && X->getType()->isIntegerTy() && | |
| computeKnownBits(X, DL).getMaxValue() == 1) { | |
| ChainInsts.push_back(cast<Instruction>(V)); | |
| Worklist.push_back(X); | |
| continue; | |
| } | |
| return; | |
| } | |
| if (!V->getType()->isIntegerTy()) | |
| return; | |
| // Same-op chain node: bit 0 of a bitwise and/or is the and/or | |
| // of the operands' bit 0. | |
| if (auto *BO = dyn_cast<BinaryOperator>(V); | |
| BO && BO->getOpcode() == RdxOpcode && BO->hasOneUse() && | |
| !BO->hasPoisonGeneratingFlags()) { | |
| ChainInsts.push_back(BO); | |
| Worklist.push_back(BO->getOperand(1)); | |
| Worklist.push_back(BO->getOperand(0)); | |
| continue; | |
| } | |
| if (!isGuaranteedNotToBePoison(V) || | |
| (WideTy && V->getType() != WideTy)) | |
| return; | |
| if (!WideTy) | |
| WideTy = V->getType(); | |
| NewCandidates.push_back(V); | |
| ReducedValsToOps[V].append(RdxOps); | |
| if (NewCandidates.size() > ReducedValsLimit) | |
| return; | |
| } | |
| } | |
| if (WideTy) { | |
| NarrowedChainInsts.append(ChainInsts); | |
| ReducedValsCandidates = std::move(NewCandidates); | |
| } | |
| }; | |
| BooleanizeLeaves(); | |
| } | |
| // Too many integer reduced values candidates for the ordered | |
| // reductions after adjustements - try to switch to unordered | |
| // reductions instead. | |
| if (ReducedValsCandidates.size() > ReducedValsLimit && | |
| AdjustedToOrdered && | |
| ReducedValsCandidates.front()->getType()->isIntOrIntVectorTy()) | |
| return false; | |
| // Try to regroup reduced values so that it gets more profitable to try | |
| // to reduce them. Values are grouped by their value ids, instructions - | |
| // by instruction op id and/or alternate op id, plus do extra analysis | |
| // for loads (grouping them by the distance between pointers) and cmp | |
| // instructions (grouping them by the predicate). | |
| SmallMapVector< | |
| size_t, | |
| SmallMapVector<size_t, SmallMapVector<Value *, unsigned, 2>, 2>, 8> | |
| PossibleReducedVals; | |
| // Add reduction values. The values are sorted for better vectorization | |
| // results. | |
| for (Value *V : make_filter_range(ReducedValsCandidates, [&](Value *V) { | |
| return RK != ReductionOrdering::Ordered || isa<Instruction>(V); | |
| })) { | |
| size_t Key, Idx; | |
| std::tie(Key, Idx) = generateKeySubkey(V, &TLI, GenerateLoadsSubkey, | |
| /*AllowAlternate=*/false); | |
| // The sign of the contribution is a part of the grouping key: a | |
| // mixed-sign group cannot be negated as a whole. | |
| Key = hash_combine(Key, NegatedReducedVals.contains(V)); | |
| ++PossibleReducedVals[Key][Idx].try_emplace(V, 0).first->second; | |
| } | |
| auto PossibleReducedValsVect = PossibleReducedVals.takeVector(); | |
| // Sort values by the total number of values kinds to start the | |
| // reduction from the longest possible reduced values sequences. | |
| for (auto &PossibleReducedVals : PossibleReducedValsVect) { | |
| auto PossibleRedVals = PossibleReducedVals.second.takeVector(); | |
| SmallVector<SmallVector<Value *>> PossibleRedValsVect; | |
| for (auto &Slice : PossibleRedVals) { | |
| PossibleRedValsVect.emplace_back(); | |
| auto RedValsVect = Slice.second.takeVector(); | |
| stable_sort(RedValsVect, llvm::less_second()); | |
| for (const std::pair<Value *, unsigned> &Data : RedValsVect) | |
| PossibleRedValsVect.back().append(Data.second, Data.first); | |
| } | |
| stable_sort(PossibleRedValsVect, [](const auto &P1, const auto &P2) { | |
| return P1.size() > P2.size(); | |
| }); | |
| bool First = true; | |
| for (ArrayRef<Value *> Data : PossibleRedValsVect) { | |
| if (First) { | |
| First = false; | |
| ReducedVals.emplace_back(); | |
| } else if (!isGoodForReduction(Data)) { | |
| auto *LI = dyn_cast<LoadInst>(Data.front()); | |
| auto *LastLI = dyn_cast<LoadInst>(ReducedVals.back().front()); | |
| if (!LI || !LastLI || | |
| getUnderlyingObject(LI->getPointerOperand()) != | |
| getUnderlyingObject(LastLI->getPointerOperand())) | |
| ReducedVals.emplace_back(); | |
| } | |
| ReducedVals.back().append(Data.rbegin(), Data.rend()); | |
| } | |
| } | |
| // Post optimize reduced values to get better reduction sequences and | |
| // sort them by size. | |
| optimizeReducedVals(R, DT, DL, TTI, TLI); | |
| // Sort the reduced values by number of same/alternate opcode and/or | |
| // pointer operand. For the sign-aware reductions, same-size groups are | |
| // ordered by their expected profitability: the first vectorized group | |
| // is charged the cost of the reduction operation itself, which a group | |
| // of loads or non-instructions rarely amortizes on its own, while it is | |
| // a cheap addition (a single vector operation) to an already | |
| // vectorized group - such groups go last. Among the rest, non-negated | |
| // groups go first. | |
| stable_sort(ReducedVals, [&](ArrayRef<Value *> P1, ArrayRef<Value *> P2) { | |
| if (P1.size() != P2.size()) | |
| return P1.size() > P2.size(); | |
| if (NegatedReducedVals.empty()) | |
| return false; | |
| bool Cheap1 = isCheapReductionGroup(P1); | |
| bool Cheap2 = isCheapReductionGroup(P2); | |
| if (Cheap1 != Cheap2) | |
| return Cheap2; | |
| return NegatedReducedVals.contains(P1.front()) < | |
| NegatedReducedVals.contains(P2.front()); | |
| }); | |
| // A group of subtracted loads, which are not consecutive (a gather): | |
| // the flattening splits the fsub leaves over them into separate load | |
| // groups of their operands, which the unflattened form vectorizes as a | |
| // whole (a vector fsub with shuffled operands). | |
| auto IsGatheredNegatedLoads = [&](ArrayRef<Value *> Vals) { | |
| auto *LI0 = dyn_cast<LoadInst>(Vals.front()); | |
| if (!LI0 || Vals.size() < 2 || !NegatedReducedVals.contains(LI0)) | |
| return false; | |
| SmallVector<int64_t> Diffs; | |
| for (Value *V : Vals) { | |
| auto *LI = dyn_cast<LoadInst>(V); | |
| if (!LI) | |
| return false; | |
| std::optional<int64_t> Diff = getPointersDiff( | |
| LI0->getType(), LI0->getPointerOperand(), LI->getType(), | |
| LI->getPointerOperand(), DL, SE, /*StrictCheck=*/true); | |
| if (!Diff) | |
| return true; | |
| Diffs.push_back(*Diff); | |
| } | |
| sort(Diffs); | |
| Diffs.erase(llvm::unique(Diffs), Diffs.end()); | |
| return Diffs.back() - Diffs.front() + 1 != | |
| static_cast<int64_t>(Diffs.size()); | |
| }; | |
| // The flattening must not make the reduction unviable: the leaves, | |
| // hidden behind the flattened fsub/fneg operations, may split into | |
| // too small sign-uniform groups, while the unflattened form may | |
| // still be vectorizable (e.g. with alternate fadd/fsub opcodes). | |
| if (FlattenedNegations && | |
| (accumulate(ReducedVals, 0u, | |
| [](unsigned Num, ArrayRef<Value *> Vals) -> unsigned { | |
| if (!isGoodForReduction(Vals)) | |
| return Num; | |
| return Num + Vals.size(); | |
| }) < ReductionLimit || | |
| any_of(ReducedVals, IsGatheredNegatedLoads))) | |
| Restart = true; | |
| } while (Restart); | |
| return true; | |
| } | |
| /// Loop accumulator emission mode of the reduction: a reduction that | |
| /// accumulates a loop phi (the phi is one of the reduced values and the | |
| /// root is its backedge value) can be emitted as a vector accumulator in | |
| /// the loop plus a single reduction after the loop instead of a horizontal | |
| /// reduction on every iteration. | |
| class LoopAccumulator { | |
| /// The reduction root and the reduction operation kind and ordering. | |
| Instruction *Root = nullptr; | |
| RecurKind RdxKind = RecurKind::None; | |
| ReductionOrdering RK = ReductionOrdering::None; | |
| /// The vectorizer state and the analyses the accumulator cost and | |
| /// emission are computed with. | |
| BoUpSLP &R; | |
| const TargetTransformInfo &TTI; | |
| LoopInfo &LI; | |
| /// The accumulated phi and the identity constant that replaced it in the | |
| /// reduced values. The identity is never folded as a leftover: it does | |
| /// not change the result. | |
| PHINode *Phi = nullptr; | |
| Constant *Identity = nullptr; | |
| /// The number of calls in the loop, across which the vector accumulator | |
| /// is kept live. | |
| unsigned NumCalls = 0; | |
| /// Exit phis consuming the reduction result outside the loop. | |
| SmallSetVector<PHINode *, 2> ExitPhis; | |
| /// \returns the identity constant of the reduction operation for \p Ty | |
| /// under the fast-math flags \p FMF. | |
| Constant *getIdentity(Type *Ty, FastMathFlags FMF) const { | |
| Intrinsic::ID Id; | |
| switch (RdxKind) { | |
| case RecurKind::FMax: | |
| case RecurKind::FMaxNum: | |
| Id = Intrinsic::vector_reduce_fmax; | |
| break; | |
| case RecurKind::FMin: | |
| case RecurKind::FMinNum: | |
| Id = Intrinsic::vector_reduce_fmin; | |
| break; | |
| case RecurKind::FMaximum: | |
| Id = Intrinsic::vector_reduce_fmaximum; | |
| break; | |
| case RecurKind::FMinimum: | |
| Id = Intrinsic::vector_reduce_fminimum; | |
| break; | |
| default: | |
| Id = RecurrenceDescriptor::isIntMinMaxRecurrenceKind(RdxKind) | |
| ? getMinMaxReductionIntrinsicID( | |
| getMinMaxReductionIntrinsicOp(RdxKind)) | |
| : getReductionForBinop(static_cast<Instruction::BinaryOps>( | |
| RecurrenceDescriptor::getOpcode(RdxKind))); | |
| break; | |
| } | |
| return cast<Constant>(getReductionIdentity(Id, Ty, FMF)); | |
| } | |
| /// \returns the extra cost of accumulating the slice lane-wise into the | |
| /// vector accumulator instead of reducing it on every iteration: the | |
| /// lane-wise operation, the spill and reload on every iteration of the | |
| /// values live beyond the register file and keeping the vector | |
| /// accumulator live across the calls of the loop. | |
| InstructionCost getAccumulationCost(FastMathFlags FMF, VectorType *VectorTy, | |
| Type *ScalarTy) const { | |
| const TTI::TargetCostKind CostKind = R.getCostKind(); | |
| auto GetOpCost = [&](Type *OpTy) { | |
| if (RecurrenceDescriptor::isMinMaxRecurrenceKind(RdxKind)) { | |
| IntrinsicCostAttributes ICA(getMinMaxReductionIntrinsicOp(RdxKind), | |
| OpTy, {OpTy, OpTy}, FMF); | |
| return TTI.getIntrinsicInstrCost(ICA, CostKind); | |
| } | |
| return TTI.getArithmeticInstrCost( | |
| RecurrenceDescriptor::getOpcode(RdxKind), OpTy, CostKind); | |
| }; | |
| // The lane-wise operation; also replaces the scalar operation folding | |
| // the accumulator phi on top of the horizontal reduction (the scalar | |
| // cost of the slice credits it neither, the slice lacks the phi). | |
| InstructionCost Cost = GetOpCost(VectorTy) - GetOpCost(ScalarTy); | |
| // The vector accumulator and the reduced vector are live at the same | |
| // time; the registers they need beyond the register file are spilled | |
| // and reloaded on every iteration. | |
| constexpr unsigned NumLiveVectors = 2; | |
| unsigned Regs = TTI.getRegUsageForType(VectorTy); | |
| unsigned RC = TTI.getRegisterClassForType(/*Vector=*/true, VectorTy); | |
| unsigned NumRegs = TTI.getNumberOfRegisters(RC); | |
| InstructionCost SpillReload = | |
| TTI.getRegisterClassSpillCost(RC, CostKind) + | |
| TTI.getRegisterClassReloadCost(RC, CostKind); | |
| InstructionCost Spilled = 0; | |
| if (NumRegs != 0 && NumLiveVectors * Regs > NumRegs) | |
| Spilled = SpillReload * (NumLiveVectors * Regs - NumRegs); | |
| Cost += Spilled; | |
| // The vector accumulator replaces the scalar one across the calls of | |
| // the loop: without callee-saved registers of the class it is spilled | |
| // and reloaded around every call, like the scalar one. The accumulator | |
| // registers already spilled on every iteration stay in memory across | |
| // the calls and are not spilled again. | |
| if (NumCalls != 0) { | |
| InstructionCost VecLive = | |
| std::max(TTI.getCostOfKeepingLiveOverCall(VectorTy), | |
| SpillReload * Regs) - | |
| std::min(Spilled, SpillReload * Regs); | |
| unsigned SRC = TTI.getRegisterClassForType(/*Vector=*/false, ScalarTy); | |
| InstructionCost ScalarLive = | |
| std::max(TTI.getCostOfKeepingLiveOverCall(ScalarTy), | |
| TTI.getRegisterClassSpillCost(SRC, CostKind) + | |
| TTI.getRegisterClassReloadCost(SRC, CostKind)); | |
| Cost += (VecLive - ScalarLive) * NumCalls; | |
| } | |
| return Cost; | |
| } | |
| public: | |
| LoopAccumulator(Instruction *Root, RecurKind RdxKind, ReductionOrdering RK, | |
| BoUpSLP &R, const TargetTransformInfo &TTI, LoopInfo &LI) | |
| : Root(Root), RdxKind(RdxKind), RK(RK), R(R), TTI(TTI), LI(LI) {} | |
| /// If the reduction accumulates a loop phi (the phi is one of the reduced | |
| /// values and the root is its backedge value), replaces the phi by the | |
| /// reduction identity in the reduced values, so that the emitted tree | |
| /// does not reference the phi. The identity does not change the result, | |
| /// so if the accumulator emission does not apply, the phi is just folded | |
| /// back on top of the emitted reduction. All unordered reduction kinds | |
| /// have an identity constant. | |
| void prepare(FastMathFlags RdxFMF, | |
| SmallVectorImpl<SmallVector<Value *>> &ReducedVals, | |
| SmallDenseMap<Value *, SmallVector<Instruction *>, 16> | |
| &ReducedValsToOps, | |
| const SmallPtrSetImpl<Value *> &NegatedReducedVals, | |
| bool HasNarrowedLeafShifts) { | |
| // A two-value reduction is unordered by commutativity alone; the | |
| // accumulator reassociates the reduction across the loop iterations, | |
| // which requires a genuinely associative operation. | |
| if (!R.Opts.slp_vectorize_loop_acc_rdx || | |
| RK != ReductionOrdering::Unordered || | |
| isVectorizable(RdxKind, Root) != ReductionOrdering::Unordered || | |
| Root->getType()->isIntegerTy(1) || Root->getType()->isVectorTy() || | |
| HasNarrowedLeafShifts || isCmpSelMinMax(Root) || | |
| Root->hasNUsesOrMore(UsesLimit)) | |
| return; | |
| Loop *L = LI.getLoopFor(Root->getParent()); | |
| if (!L) | |
| return; | |
| BasicBlock *Latch = L->getLoopLatch(); | |
| if (!Latch) | |
| return; | |
| // Only a single accumulated phi, used by the reduction operations | |
| // only, is supported. | |
| auto FindAccPhi = [&]() -> PHINode * { | |
| PHINode *AccPhi = nullptr; | |
| for (ArrayRef<Value *> Candidates : ReducedVals) | |
| for (PHINode *P : make_isa_range<PHINode>(Candidates)) { | |
| if (P->getParent() != L->getHeader() || | |
| P->getNumIncomingValues() > MaxPHINumOperands || | |
| P->getIncomingValueForBlock(Latch) != Root) | |
| continue; | |
| if (AccPhi || P->hasNUsesOrMore(UsesLimit) || | |
| P->getNumUses() != ReducedValsToOps.at(P).size()) | |
| return nullptr; | |
| AccPhi = P; | |
| } | |
| return AccPhi; | |
| }; | |
| PHINode *AccPhi = FindAccPhi(); | |
| // A negated phi is subtracted from the reduction result, not | |
| // accumulated. | |
| if (!AccPhi || NegatedReducedVals.contains(AccPhi)) | |
| return; | |
| Constant *IdC = getIdentity(AccPhi->getType(), RdxFMF); | |
| if (ReducedValsToOps.contains(IdC)) | |
| return; | |
| // The root may be used only by the accumulator phi and by phis outside | |
| // the loop (the exit phis). The initial values are inserted into the | |
| // identity vector at the end of their incoming blocks, so they must not | |
| // be defined by the terminators; the final reduction is emitted at the | |
| // beginning of the exit blocks, which must not be EH pads. Neither may | |
| // land in a loop not containing the reduction loop, which would execute | |
| // them repeatedly. | |
| auto IsExecutedOncePerLoop = [&](BasicBlock *BB) { | |
| Loop *BBL = LI.getLoopFor(BB); | |
| return !BBL || BBL->contains(L); | |
| }; | |
| if (any_of(zip(AccPhi->incoming_values(), AccPhi->blocks()), | |
| [&](const auto &P) { | |
| auto [IncV, B] = P; | |
| return IncV != Root && (IncV == B->getTerminator() || | |
| !IsExecutedOncePerLoop(B)); | |
| })) | |
| return; | |
| auto IsValidExitPhi = [&](User *U) { | |
| auto *ExitPhi = dyn_cast<PHINode>(U); | |
| return ExitPhi && | |
| ExitPhi->getNumIncomingValues() <= MaxPHINumOperands && | |
| !ExitPhi->getParent()->isEHPad() && | |
| !L->contains(ExitPhi->getParent()) && | |
| IsExecutedOncePerLoop(ExitPhi->getParent()); | |
| }; | |
| auto CollectExitPhis = [&] { | |
| for (User *U : Root->users()) { | |
| if (U == AccPhi) | |
| continue; | |
| if (!IsValidExitPhi(U)) | |
| return false; | |
| ExitPhis.insert(cast<PHINode>(U)); | |
| } | |
| return true; | |
| }; | |
| if (!CollectExitPhis()) | |
| return; | |
| // The final reduction and the insert of the initial value would be on | |
| // the loop-carried chain of an enclosing loop if the reduction result | |
| // feeds the initial value of the accumulator through it: keep the | |
| // horizontal reduction, whose chain is the scalar accumulator only. | |
| auto IsFedByExitPhi = [&](Value *InitV) { | |
| auto *InitPhi = dyn_cast<PHINode>(InitV); | |
| if (InitV == Root || !InitPhi) | |
| return false; | |
| // Too many incoming values to scan: keep the horizontal reduction. | |
| if (InitPhi->getNumIncomingValues() > MaxPHINumOperands) | |
| return true; | |
| return ExitPhis.contains(InitPhi) || | |
| any_of(make_isa_range<PHINode>(InitPhi->incoming_values()), | |
| [&](PHINode *P) { return ExitPhis.contains(P); }); | |
| }; | |
| if (any_of(AccPhi->incoming_values(), IsFedByExitPhi)) | |
| return; | |
| // Calls clobber the vector registers: the vector accumulator has to be | |
| // kept live across them. Intrinsics cheaper than a call are not calls. | |
| for (BasicBlock *BB : L->blocks()) | |
| NumCalls += count_if(*BB, [&](const Instruction &I) { | |
| auto *CB = dyn_cast<CallBase>(&I); | |
| if (!CB || CB->doesNotReturn()) | |
| return false; | |
| auto *II = dyn_cast<IntrinsicInst>(CB); | |
| if (!II) | |
| return true; | |
| if (II->isAssumeLikeIntrinsic()) | |
| return false; | |
| IntrinsicCostAttributes ICA(II->getIntrinsicID(), *II); | |
| return TTI.getIntrinsicInstrCost(ICA, R.getCostKind()) >= | |
| TTI.getCallInstrCost(nullptr, II->getType(), ICA.getArgTypes(), | |
| R.getCostKind()); | |
| }); | |
| for (SmallVector<Value *> &Candidates : ReducedVals) | |
| for (Value *&RdxVal : Candidates) | |
| if (RdxVal == AccPhi) | |
| RdxVal = IdC; | |
| ReducedValsToOps.try_emplace(IdC, ReducedValsToOps.lookup(AccPhi)); | |
| ReducedValsToOps.erase(AccPhi); | |
| Phi = AccPhi; | |
| Identity = IdC; | |
| } | |
| /// Collects the reduced values that were not vectorized; they are folded | |
| /// into the reduction on top of the vectorized part. The accumulator | |
| /// identity constant is never folded. The operation list of a value has | |
| /// one entry per occurrence of the value in the reduction, all reducing | |
| /// the same value, so the vectorized occurrences are accounted for by | |
| /// count only. | |
| void collectLeftovers( | |
| const DenseMap<Value *, unsigned> &VectorizedVals, | |
| ArrayRef<SmallVector<Value *>> ReducedVals, | |
| const SmallDenseMap<Value *, SmallVector<Instruction *>, 16> | |
| &ReducedValsToOps, | |
| SmallVectorImpl<std::pair<Instruction *, Value *>> &LeftoverReductions) | |
| const { | |
| SmallPtrSet<Value *, 8> Visited; | |
| for (ArrayRef<Value *> Candidates : ReducedVals) | |
| for (Value *RdxVal : make_filter_range(Candidates, [&](Value *RdxVal) { | |
| return RdxVal != Identity && Visited.insert(RdxVal).second; | |
| })) { | |
| unsigned NumOps = VectorizedVals.lookup(RdxVal); | |
| for (Instruction *RedOp : | |
| ArrayRef(ReducedValsToOps.at(RdxVal)).drop_back(NumOps)) | |
| LeftoverReductions.emplace_back(RedOp, RdxVal); | |
| } | |
| } | |
| /// \returns the cost of the code the vector accumulator emits outside the | |
| /// loop: the final reductions in the exit blocks and the insert of the | |
| /// initial value, scaled by the enclosing loop nest. | |
| InstructionCost getExitCost(FastMathFlags FMF, const Loop *L) const { | |
| assert( | |
| (RecurrenceDescriptor::isMinMaxRecurrenceKind(RdxKind) || | |
| Instruction::isBinaryOp(RecurrenceDescriptor::getOpcode(RdxKind))) && | |
| "Expected arithmetic or min/max reduction operation"); | |
| FixedVectorType *VecTy = R.getReductionType(); | |
| TTI::TargetCostKind CostKind = R.getCostKind(); | |
| InstructionCost RdxCost = | |
| RecurrenceDescriptor::isMinMaxRecurrenceKind(RdxKind) | |
| ? TTI.getMinMaxReductionCost( | |
| getMinMaxReductionIntrinsicOp(RdxKind), VecTy, FMF, | |
| CostKind) | |
| : TTI.getArithmeticReductionCost( | |
| RecurrenceDescriptor::getOpcode(RdxKind), VecTy, FMF, | |
| CostKind); | |
| // One exit block is executed per loop execution: charge the final | |
| // reductions of the largest one. Code size counts all of them. | |
| unsigned MaxExitRdx = 0; | |
| SmallDenseMap<BasicBlock *, unsigned> ExitBlockPhis; | |
| for (PHINode *ExitPhi : ExitPhis) | |
| MaxExitRdx = | |
| std::max(MaxExitRdx, ++ExitBlockPhis[ExitPhi->getParent()]); | |
| InstructionCost Cost = | |
| RdxCost * | |
| (CostKind == TTI::TCK_CodeSize ? ExitPhis.size() : MaxExitRdx) + | |
| TTI.getVectorInstrCost(Instruction::InsertElement, VecTy, CostKind, | |
| /*Index=*/0); | |
| return Cost * R.getLoopNestScale(L->getParentLoop()); | |
| } | |
| /// Checks that the slice can be emitted as the vector accumulator: it is | |
| /// the only one (all other reduced values are the identity), not scaled, | |
| /// not reduced in-tree, of the root type and fully vectorized. An integer | |
| /// accumulator stays in a callee-saved register across the calls of the | |
| /// loop, the vector one would be spilled around every call on the | |
| /// loop-carried chain. | |
| bool isCandidate(const Value *VectorizedTree, bool HasVectorizedSlices, | |
| bool IsSupportedHorRdxIdentityOp, | |
| ArrayRef<SmallVector<Value *>> ReducedVals, | |
| ArrayRef<Value *> Candidates, unsigned CandidatesIdx, | |
| unsigned Pos, unsigned ReduxWidth, bool OptReusedScalars, | |
| bool SameScaleFactor) const { | |
| if (!Phi || VectorizedTree || HasVectorizedSlices || Pos != 0 || | |
| (OptReusedScalars && SameScaleFactor) || R.isReducedBitcastRoot() || | |
| R.isReducedCmpBitcastRoot() || | |
| (NumCalls != 0 && !Root->getType()->isFloatingPointTy())) | |
| return false; | |
| if (!all_of(ArrayRef(Candidates).drop_front(ReduxWidth), | |
| [&](Value *RdxVal) { return RdxVal == Identity; })) | |
| return false; | |
| if (!all_of(enumerate(ReducedVals), [&](const auto &P) { | |
| auto [Idx, RV] = P; | |
| return Idx == CandidatesIdx || | |
| (RV.size() == 1 && RV.front() == Identity); | |
| })) | |
| return false; | |
| if (R.getReductionType()->getElementType() != Root->getType()) | |
| return false; | |
| return IsSupportedHorRdxIdentityOp || | |
| all_of(Candidates.slice(Pos, ReduxWidth), | |
| [&](Value *RdxVal) { return R.isVectorized(RdxVal); }); | |
| } | |
| /// \returns the cost delta of the vector accumulator form vs the | |
| /// horizontal reduction on every iteration. Both remove the same scalar | |
| /// operations, so only the vector code is compared: the lane-wise | |
| /// operation is executed on every iteration of the loop, the horizontal | |
| /// reduction as often as the tree cost model assumes (once, if the | |
| /// reduced values are loop-invariant), the final reduction and the | |
| /// initial insert once, outside the loop. | |
| InstructionCost getCostDelta(InstructionCost HorVecCost, | |
| InstructionCost RdxOpCost, FastMathFlags FMF, | |
| VectorType *VectorTy, Type *ScalarTy) const { | |
| // The lane-wise accumulation replaces the reduction operation in the | |
| // vector code. | |
| InstructionCost AccVecCost = | |
| HorVecCost - RdxOpCost + getAccumulationCost(FMF, VectorTy, ScalarTy); | |
| Loop *L = LI.getLoopFor(Phi->getParent()); | |
| InstructionCost Delta = AccVecCost * R.getLoopNestScale(L) - | |
| HorVecCost * R.getScaleToLoopIterations( | |
| R.getRootNode(), nullptr, Root) + | |
| getExitCost(FMF, L); | |
| LLVM_DEBUG(dbgs() << "SLP: Loop accumulator cost delta " << Delta | |
| << "\n"); | |
| return Delta; | |
| } | |
| /// Emits the reduction as a vector accumulator if the accumulator form | |
| /// applies; otherwise folds the accumulator phi back on top of the | |
| /// reduction like a leftover and returns null. | |
| Value *tryEmit( | |
| IRBuilderBase &Builder, FastMathFlags RdxFMF, bool Vectorized, | |
| const Value *VectorizedTree, | |
| SmallVectorImpl<std::pair<Instruction *, Value *>> &LeftoverReductions, | |
| ArrayRef<ReductionVectorPart> VectorValuesAndScales, | |
| const SmallPtrSetImpl<Value *> &RequiredExtract, | |
| const SmallDenseMap<Value *, SmallVector<Instruction *>, 16> | |
| &ReducedValsToOps, | |
| const ReductionOpsListType &ReductionOps, | |
| function_ref<Value *(Value *, IRBuilderBase &, Type *)> EmitReduction) { | |
| if (!shouldEmit(Vectorized, VectorizedTree, LeftoverReductions, | |
| VectorValuesAndScales, RequiredExtract)) { | |
| // The accumulator phi is folded back on top of the reduction like a | |
| // leftover: the identity replaced it in the reduced values and does | |
| // not change the result. | |
| if (Phi) | |
| LeftoverReductions.emplace_back(ReducedValsToOps.at(Identity).front(), | |
| Phi); | |
| return nullptr; | |
| } | |
| return emit(Builder, RdxFMF, VectorValuesAndScales.front().Vec, | |
| ReductionOps, EmitReduction); | |
| } | |
| private: | |
| /// Checks that the accumulator form applies: the slice was vectorized as | |
| /// the accumulator and no other vectorized value or leftover reduced | |
| /// values remain to fold; the slice is of the root type, not scaled, not | |
| /// reduced in-tree and without extracts. | |
| bool | |
| shouldEmit(bool Vectorized, const Value *VectorizedTree, | |
| ArrayRef<std::pair<Instruction *, Value *>> LeftoverReductions, | |
| ArrayRef<ReductionVectorPart> VectorValuesAndScales, | |
| const SmallPtrSetImpl<Value *> &RequiredExtract) const { | |
| if (!Vectorized || VectorizedTree || !LeftoverReductions.empty() || | |
| VectorValuesAndScales.size() != 1 || !RequiredExtract.empty()) | |
| return false; | |
| const ReductionVectorPart &P = VectorValuesAndScales.front(); | |
| return P.Scale == 1 && !P.ReducedInTree && | |
| cast<VectorType>(P.Vec->getType())->getElementType() == | |
| Root->getType(); | |
| } | |
| /// Emits the reduction as a vector accumulator: the vectorized reduced | |
| /// values are accumulated lane-wise into a vector phi in the loop and | |
| /// reduced once in the exit blocks. | |
| Value *emit( | |
| IRBuilderBase &Builder, FastMathFlags RdxFMF, Value *Vec, | |
| const ReductionOpsListType &ReductionOps, | |
| function_ref<Value *(Value *, IRBuilderBase &, Type *)> EmitReduction) { | |
| auto *VecTy = cast<FixedVectorType>(Vec->getType()); | |
| // The initial values are placed in lane 0 of the identity splat at the | |
| // end of their incoming blocks; multiple edges from a block carry the | |
| // same value and share the insert. | |
| Constant *IdVec = | |
| ConstantVector::getSplat(VecTy->getElementCount(), Identity); | |
| auto *VAcc = PHINode::Create(VecTy, Phi->getNumIncomingValues(), | |
| "slprdx.acc", Phi->getIterator()); | |
| SmallDenseMap<BasicBlock *, Value *> InitVecs; | |
| for (auto [IncV, B] : make_filter_range( | |
| zip(Phi->incoming_values(), Phi->blocks()), | |
| [&](const auto &P) { return std::get<0>(P) != Root; })) { | |
| Value *&InitVec = InitVecs[B]; | |
| if (!InitVec) { | |
| IRBuilder<> EB(B->getTerminator()); | |
| InitVec = EB.CreateInsertElement(IdVec, IncV, EB.getInt32(0), | |
| "slprdx.init"); | |
| } | |
| VAcc->addIncoming(InitVec, B); | |
| } | |
| Builder.SetInsertPoint(Root); | |
| Value *VAdd = createOpWithFlags(Builder, RdxKind, VAcc, Vec, "slprdx.acc", | |
| ReductionOps, /*KeepDisjoint=*/true); | |
| // The accumulation is on the loop-carried chain: do not let it be | |
| // contracted into an FMA with the reduced multiplication, which would | |
| // lengthen the chain. | |
| if (auto *FPOp = dyn_cast<FPMathOperator>(VAdd)) { | |
| FastMathFlags FMF = FPOp->getFastMathFlags(); | |
| FMF.setAllowContract(false); | |
| cast<Instruction>(VAdd)->copyFastMathFlags(FMF); | |
| } | |
| // The accumulator is the backedge value. | |
| for (auto [IncV, B] : zip(Phi->incoming_values(), Phi->blocks())) | |
| if (IncV == Root) | |
| VAcc->addIncoming(VAdd, B); | |
| LLVM_DEBUG(dbgs() << "SLP: Emitting loop accumulator reduction for " | |
| "reduction with root " | |
| << *Root << "\n"); | |
| // The root is tracked by a weak handle: break its uses on the phi side. | |
| Value *Poison = PoisonValue::get(Root->getType()); | |
| for (PHINode *ExitPhi : ExitPhis) { | |
| // The accumulator replaces the root on the edges carrying it and is | |
| // reduced once in the exit block. The values bypassing the loop never | |
| // went through the reduction operations and must stay exact: the | |
| // scalar phi keeps them and they are selected past the reduction | |
| // without its fast-math flags. | |
| unsigned NumIncoming = ExitPhi->getNumIncomingValues(); | |
| auto *VExit = PHINode::Create(VecTy, NumIncoming, "slprdx.exit", | |
| ExitPhi->getIterator()); | |
| PHINode *FromLoop = nullptr; | |
| if (any_of(ExitPhi->incoming_values(), | |
| [this](Value *V) { return V != Root; })) | |
| FromLoop = PHINode::Create(Builder.getInt1Ty(), NumIncoming, | |
| "slprdx.fromloop", ExitPhi->getIterator()); | |
| Value *VecPoison = PoisonValue::get(VecTy); | |
| for (auto [V, B] : zip(ExitPhi->incoming_values(), ExitPhi->blocks())) { | |
| bool IsRoot = V == Root; | |
| VExit->addIncoming(IsRoot ? VAdd : VecPoison, B); | |
| if (FromLoop) | |
| FromLoop->addIncoming(Builder.getInt1(IsRoot), B); | |
| } | |
| ExitPhi->replaceUsesOfWith(Root, Poison); | |
| IRBuilder<> XB(&*ExitPhi->getParent()->getFirstInsertionPt()); | |
| XB.SetCurrentDebugLocation(ExitPhi->getDebugLoc()); | |
| XB.setFastMathFlags(RdxFMF); | |
| Value *Res = EmitReduction(VExit, XB, ExitPhi->getType()); | |
| if (!FromLoop) { | |
| ExitPhi->replaceAllUsesWith(Res); | |
| // The erasure is deferred: the block iteration is still in progress. | |
| R.eraseInstruction(ExitPhi); | |
| continue; | |
| } | |
| Value *Sel = XB.CreateSelectFMFWithUnknownProfile( | |
| FromLoop, Res, ExitPhi, FastMathFlags(), DEBUG_TYPE, "slprdx.sel"); | |
| ExitPhi->replaceUsesWithIf( | |
| Sel, [Sel](Use &U) { return U.getUser() != Sel; }); | |
| } | |
| Phi->replaceUsesOfWith(Root, Poison); | |
| R.eraseInstruction(Phi); | |
| // Do not let the caller re-analyze the emitted vector operation as a new | |
| // reduction seed. | |
| R.analyzedReductionRoot(cast<Instruction>(VAdd)); | |
| return VAdd; | |
| } | |
| }; | |
| /// Attempt to vectorize the tree found by matchAssociativeReduction. | |
| /// \p IsSeedRoot allows vectorizing the groups at the minimum vector | |
| /// factor. Set for single-use seed-level roots only. | |
| Value *tryToReduce(BoUpSLP &V, const DataLayout &DL, TargetTransformInfo *TTI, | |
| const TargetLibraryInfo &TLI, AssumptionCache *AC, | |
| DominatorTree &DT, LoopInfo &LI, bool IsSeedRoot = false) { | |
| constexpr unsigned RegMaxNumber = 4; | |
| const unsigned RedValsMaxNumber = | |
| (RK == ReductionOrdering::Ordered && | |
| ReductionRoot->getType()->isIntOrIntVectorTy()) | |
| ? 48 | |
| : 128; | |
| // If there are a sufficient number of reduction values, reduce | |
| // to a nearby power-of-2. We can safely generate oversized | |
| // vectors and rely on the backend to split them to legal sizes. | |
| if (unsigned NumReducedVals = | |
| accumulate(ReducedVals, 0, | |
| [](unsigned Num, ArrayRef<Value *> Vals) -> unsigned { | |
| if (!isGoodForReduction(Vals)) | |
| return Num; | |
| return Num + Vals.size(); | |
| }); | |
| NumReducedVals < ReductionLimit && | |
| all_of( | |
| ReducedVals, | |
| [](ArrayRef<Value *> RedV) { | |
| return RedV.size() < 2 || !allConstant(RedV) || !isSplat(RedV); | |
| })) { | |
| // For ordered reductions the leaves are pulled in via the fallback in | |
| // matchAssociativeReduction and may still be valid reduction roots on | |
| // their own; only the root is a dead end. For unordered reductions keep | |
| // marking every reduction op as analyzed. | |
| if (RK == ReductionOrdering::Ordered) { | |
| V.analyzedReductionRoot(cast<Instruction>(ReductionRoot)); | |
| } else { | |
| for (ReductionOpsType &RdxOps : ReductionOps) | |
| for (Value *RdxOp : RdxOps) | |
| V.analyzedReductionRoot(cast<Instruction>(RdxOp)); | |
| } | |
| return nullptr; | |
| } | |
| IRBuilder<TargetFolder> Builder( | |
| cast<Instruction>(ReductionRoot)->getIterator(), TargetFolder(DL)); | |
| // The scalar parts (scaled reused values, their combines) are emitted right | |
| // before the root; the instruction preceding them is remembered to drop | |
| // them, if the sign-aware reduction is abandoned. | |
| Instruction *InstBeforeRoot = | |
| cast<Instruction>(ReductionRoot)->getPrevNode(); | |
| // Track the reduced values in case if they are replaced by extractelement | |
| // because of the vectorization. | |
| DenseMap<Value *, WeakTrackingVH> TrackedVals(ReducedVals.size() * | |
| ReducedVals.front().size()); | |
| // The compare instruction of a min/max is the insertion point for new | |
| // instructions and may be replaced with a new compare instruction. | |
| auto &&GetCmpForMinMaxReduction = [](Instruction *RdxRootInst) { | |
| assert(isa<SelectInst>(RdxRootInst) && | |
| "Expected min/max reduction to have select root instruction"); | |
| Value *ScalarCond = cast<SelectInst>(RdxRootInst)->getCondition(); | |
| assert(isa<Instruction>(ScalarCond) && | |
| "Expected min/max reduction to have compare condition"); | |
| return cast<Instruction>(ScalarCond); | |
| }; | |
| bool AnyBoolLogicOp = any_of(ReductionOps.back(), [](Value *V) { | |
| return isBoolLogicOp(cast<Instruction>(V)); | |
| }); | |
| // Return new VectorizedTree, based on previous value. | |
| auto GetNewVectorizedTree = [&](Value *VectorizedTree, Value *Res) { | |
| if (VectorizedTree) { | |
| // Update the final value in the reduction. | |
| Builder.SetCurrentDebugLocation( | |
| cast<Instruction>(ReductionOps.front().front())->getDebugLoc()); | |
| if (AnyBoolLogicOp) { | |
| auto It = ReducedValsToOps.find(VectorizedTree); | |
| auto It1 = ReducedValsToOps.find(Res); | |
| if ((It == ReducedValsToOps.end() && It1 == ReducedValsToOps.end()) || | |
| isGuaranteedNotToBePoison(VectorizedTree, AC) || | |
| (It != ReducedValsToOps.end() && | |
| any_of(It->getSecond(), [&](Instruction *I) { | |
| return isBoolLogicOp(I) && | |
| getRdxOperand(I, 0) == VectorizedTree; | |
| }))) { | |
| ; | |
| } else if (isGuaranteedNotToBePoison(Res, AC) || | |
| (It1 != ReducedValsToOps.end() && | |
| any_of(It1->getSecond(), [&](Instruction *I) { | |
| return isBoolLogicOp(I) && getRdxOperand(I, 0) == Res; | |
| }))) { | |
| std::swap(VectorizedTree, Res); | |
| } else { | |
| VectorizedTree = Builder.CreateFreeze(VectorizedTree); | |
| } | |
| } | |
| return createOp(Builder, RdxKind, VectorizedTree, Res, "op.rdx", | |
| ReductionOps); | |
| } | |
| // Initialize the final value in the reduction. | |
| return Res; | |
| }; | |
| SmallDenseSet<Value *> IgnoreList(ReductionOps.size() * | |
| ReductionOps.front().size()); | |
| for (ReductionOpsType &RdxOps : ReductionOps) | |
| for (Value *RdxOp : | |
| make_filter_range(RdxOps, [](Value *RdxOp) { return RdxOp; })) | |
| IgnoreList.insert(RdxOp); | |
| // Dropped with the reduction; do not treat as external uses. | |
| IgnoreList.insert_range(NarrowedChainInsts); | |
| // Intersect the fast-math-flags from all reduction operations. | |
| FastMathFlags RdxFMF; | |
| RdxFMF.set(); | |
| for (FPMathOperator *FPMO : make_isa_range<FPMathOperator>(IgnoreList)) | |
| RdxFMF &= FPMO->getFastMathFlags(); | |
| // Returns true if the original reduced value \p V is subtracted from the | |
| // reduction result. | |
| auto IsNegated = [&](Value *V) { return NegatedReducedVals.contains(V); }; | |
| // Scalar values subtracted from the reduction result: combined pairwise | |
| // like the positive ones and subtracted in the final combine, so no | |
| // extra negations are emitted. | |
| SmallVector<std::pair<Instruction *, Value *>> NegExtraReductions; | |
| // For ordered reductions here we need to generate extractelement | |
| // instructions, so clear IgnoreList. | |
| if (RK == ReductionOrdering::Ordered) | |
| IgnoreList.clear(); | |
| bool IsCmpSelMinMax = isCmpSelMinMax(cast<Instruction>(ReductionRoot)); | |
| // A reduction that accumulates a loop phi can be emitted as a vector | |
| // accumulator in the loop plus a single reduction after it. | |
| LoopAccumulator LoopAcc(cast<Instruction>(ReductionRoot), RdxKind, RK, V, | |
| *TTI, LI); | |
| LoopAcc.prepare(RdxFMF, ReducedVals, ReducedValsToOps, NegatedReducedVals, | |
| !NarrowedLeafShifts.empty()); | |
| // Set when the slice costed as the vector accumulator was vectorized. | |
| bool LoopAccVectorized = false; | |
| // Need to track reduced vals, they may be changed during vectorization of | |
| // subvectors. | |
| for (ArrayRef<Value *> Candidates : ReducedVals) | |
| for (Value *V : Candidates) | |
| TrackedVals.try_emplace(V, V); | |
| auto At = [](SmallMapVector<Value *, unsigned, 16> &MV, | |
| Value *V) -> unsigned & { | |
| auto *It = MV.find(V); | |
| assert(It != MV.end() && "Unable to find given key."); | |
| return It->second; | |
| }; | |
| DenseMap<Value *, unsigned> VectorizedVals(ReducedVals.size()); | |
| // List of the values that were reduced in other trees as part of gather | |
| // nodes and thus requiring extract if fully vectorized in other trees. | |
| SmallPtrSet<Value *, 4> RequiredExtract; | |
| WeakTrackingVH VectorizedTree = nullptr; | |
| // The flattened reduction may be abandoned and retried without the | |
| // fsub/fneg flattening, where the same lists of reduced values may be | |
| // profitable - they are not marked as analyzed. | |
| auto MarkAnalyzedVals = [&](ArrayRef<Value *> VL) { | |
| if (!hasFlattenedNegations()) | |
| V.analyzedReductionVals(VL); | |
| }; | |
| // Routes the emitted value by the sign of its group: negated parts are | |
| // combined separately and subtracted in the final combine. \p OrigV is | |
| // the original reduced value (null for the combined vector reduction | |
| // result); its reduction operation provides the debug location. | |
| auto AddReducedPart = [&](Value *OrigV, Value *Res, bool Negated) { | |
| if (Negated) { | |
| NegExtraReductions.emplace_back(OrigV | |
| ? ReducedValsToOps.at(OrigV).front() | |
| : cast<Instruction>(ReductionRoot), | |
| Res); | |
| return; | |
| } | |
| VectorizedTree = GetNewVectorizedTree(VectorizedTree, Res); | |
| }; | |
| bool CheckForReusedReductionOps = false; | |
| // Try to vectorize elements based on their type. | |
| SmallVector<InstructionsState> States; | |
| SmallVector<SmallVector<Value *>> LocalReducedVals; | |
| // Try merge consecutive reduced values into a single vectorizable group and | |
| // check, if they can be vectorized as copyables. | |
| const bool TwoGroupsOnly = ReducedVals.size() == 2; | |
| const bool TwoGroupsOfSameSmallSize = | |
| TwoGroupsOnly && | |
| ReducedVals.front().size() == ReducedVals.back().size() && | |
| ReducedVals.front().size() < ReductionLimit; | |
| for (ArrayRef<Value *> RV : ReducedVals) { | |
| // Groups with different signs of their contribution cannot be merged: | |
| // the mixed group cannot be added/subtracted as a whole. The merge | |
| // candidate is the last group of the same sign. | |
| int MergeIdx = LocalReducedVals.size() - 1; | |
| while (MergeIdx >= 0 && IsNegated(LocalReducedVals[MergeIdx].front()) != | |
| IsNegated(RV.front())) | |
| --MergeIdx; | |
| const bool SameSign = LocalReducedVals.empty() || MergeIdx >= 0; | |
| // Loads are not very compatible with undefs. | |
| if (isa<UndefValue>(RV.front()) && | |
| (States.empty() || !States.back() || | |
| States.back().getOpcode() == Instruction::Load)) { | |
| LocalReducedVals.emplace_back().append(RV.begin(), RV.end()); | |
| States.push_back(InstructionsState::invalid()); | |
| continue; | |
| } | |
| if (!LocalReducedVals.empty() && | |
| isa<UndefValue>(LocalReducedVals.back().front()) && | |
| isa<LoadInst>(RV.front())) { | |
| LocalReducedVals.emplace_back().append(RV.begin(), RV.end()); | |
| States.push_back(getSameOpcode(RV, TLI)); | |
| continue; | |
| } | |
| // Do some copyables analysis only if more than 2 groups exist or they | |
| // are large enough. | |
| if (SameSign && !TwoGroupsOfSameSmallSize) { | |
| SmallVector<Value *> Ops; | |
| if (!LocalReducedVals.empty()) | |
| Ops = LocalReducedVals[MergeIdx]; | |
| Ops.append(RV.begin(), RV.end()); | |
| InstructionsCompatibilityAnalysis Analysis(DT, DL, *TTI, TLI); | |
| InstructionsState OpS = Analysis.buildInstructionsState( | |
| Ops, V, /*WithProfitabilityCheck=*/true); | |
| if (OpS) { | |
| if (LocalReducedVals.empty()) { | |
| LocalReducedVals.push_back(Ops); | |
| States.push_back(OpS); | |
| continue; | |
| } | |
| LocalReducedVals[MergeIdx].swap(Ops); | |
| States[MergeIdx] = OpS; | |
| continue; | |
| } | |
| // For safety, allow split vectorization only if 2 groups are available | |
| // overall. | |
| if (TwoGroupsOnly) { | |
| auto [MainOp, AltOp] = getMainAltOpsNoStateVL(Ops); | |
| OpS = InstructionsState(MainOp, AltOp); | |
| // Last chance to try to vectorize alternate node. | |
| SmallVector<Value *> Op1, Op2; | |
| BoUpSLP::OrdersType ReorderIndices; | |
| // canBuildSplitNode() relies on VectorizableTree(), make sure to | |
| // clear here since it may contain leftover state from prior attempts. | |
| V.deleteTree(); | |
| if (MainOp && AltOp && | |
| V.canBuildSplitNode(Ops, OpS, Op1, Op2, ReorderIndices)) { | |
| if (LocalReducedVals.empty()) { | |
| LocalReducedVals.push_back(Ops); | |
| States.push_back(OpS); | |
| continue; | |
| } | |
| LocalReducedVals[MergeIdx].swap(Ops); | |
| States[MergeIdx] = OpS; | |
| continue; | |
| } | |
| } | |
| } | |
| LocalReducedVals.emplace_back().append(RV.begin(), RV.end()); | |
| States.push_back(getSameOpcode(RV, TLI)); | |
| } | |
| ReducedVals.swap(LocalReducedVals); | |
| FastMathFlags GroupRdxFMF = RdxFMF; | |
| // The minimum vector factor pays off only when it covers the whole | |
| // reduction: every group must be either a pair of distinct values or | |
| // large enough for the regular vector factor. | |
| const bool NoScalarLeftovers = | |
| all_of(ReducedVals, [this](ArrayRef<Value *> Vals) { | |
| return Vals.size() >= ReductionLimit || | |
| (Vals.size() == 2 && Vals.front() != Vals.back()); | |
| }); | |
| // Small sign-aware groups pay off only when paired via the vector fsub: | |
| // each sign must have a group of at least 2 values. | |
| auto HasGroupOfSign = [&](bool Negated) { | |
| return any_of(ReducedVals, [&](ArrayRef<Value *> Vals) { | |
| return Vals.size() >= 2 && IsNegated(Vals.front()) == Negated; | |
| }); | |
| }; | |
| const bool CanPairSigns = | |
| HasGroupOfSign(/*Negated=*/false) && HasGroupOfSign(/*Negated=*/true); | |
| // Same-size groups of the seed-level reduction get the profitability | |
| // ordering (cheap groups go last) when the minimum vector factor covers | |
| // the whole reduction. | |
| if (IsSeedRoot && NegatedReducedVals.empty() && NoScalarLeftovers && | |
| any_of(ReducedVals, | |
| [](ArrayRef<Value *> Vals) { return Vals.size() == 2; })) { | |
| SmallVector<unsigned> GroupOrder(ReducedVals.size()); | |
| std::iota(GroupOrder.begin(), GroupOrder.end(), 0); | |
| stable_sort(GroupOrder, [&](unsigned L, unsigned R) { | |
| ArrayRef<Value *> P1 = ReducedVals[L], P2 = ReducedVals[R]; | |
| if (P1.size() != P2.size()) | |
| return P1.size() > P2.size(); | |
| return !isCheapReductionGroup(P1) && isCheapReductionGroup(P2); | |
| }); | |
| SmallVector<SmallVector<Value *>> SortedVals; | |
| SmallVector<InstructionsState> SortedStates; | |
| SortedVals.reserve(ReducedVals.size()); | |
| SortedStates.reserve(States.size()); | |
| for (unsigned Idx : GroupOrder) { | |
| SortedVals.emplace_back(std::move(ReducedVals[Idx])); | |
| SortedStates.push_back(States[Idx]); | |
| } | |
| ReducedVals = std::move(SortedVals); | |
| States = std::move(SortedStates); | |
| } | |
| for (unsigned I = 0, E = ReducedVals.size(); I < E; ++I) { | |
| ArrayRef<Value *> OrigReducedVals = ReducedVals[I]; | |
| InstructionsState S = States[I]; | |
| // The main operation of a state with copyable elements may be deleted | |
| // during previous vectorization attempts, the state is not usable then. | |
| if (S && S.areInstructionsWithCopyableElements() && | |
| V.isDeleted(S.getMainOp())) | |
| S = InstructionsState::invalid(); | |
| SmallVector<Value *> Candidates; | |
| Candidates.reserve(2 * OrigReducedVals.size()); | |
| SmallVector<Value *> TrackedToOrig; | |
| // Reduced values that were replaced by an extractelement (because they | |
| // were vectorized in another reduction subset or operand cone) and are | |
| // not compatible with this group. They are not vectorized again here, but | |
| // they are still live and consumed by the final reduction, so they must | |
| // be kept externally used to avoid erasing them while vectorizing this | |
| // group, which may use them as operands. | |
| SmallVector<Value *> ExcludedVectorizedReducedVals; | |
| for (Value *ReducedVal : OrigReducedVals) { | |
| Value *RdxVal = TrackedVals.at(ReducedVal); | |
| // Check if the reduction value was not overriden by the extractelement | |
| // instruction because of the vectorization and exclude it, if it is not | |
| // compatible with other values. | |
| // Also check if the instruction was folded to constant/other value. | |
| auto *Inst = dyn_cast<Instruction>(RdxVal); | |
| if (Inst && V.isDeleted(Inst)) | |
| continue; | |
| if (Inst && isVectorLikeInstWithConstOps(Inst) && | |
| (!S || (!S.getMatchingMainOpOrAltOp(Inst) && | |
| !S.isCopyableElement(Inst)))) { | |
| ExcludedVectorizedReducedVals.push_back(RdxVal); | |
| continue; | |
| } | |
| if (S && !Inst && !isa<UndefValue>(RdxVal) && | |
| !S.isCopyableElement(RdxVal)) | |
| continue; | |
| Candidates.push_back(RdxVal); | |
| TrackedToOrig.push_back(ReducedVal); | |
| } | |
| // The groups are sign-uniform by matching, so the first original | |
| // value's sign applies to all of them. | |
| const bool GroupNegated = | |
| !TrackedToOrig.empty() && IsNegated(TrackedToOrig.front()); | |
| bool ShuffledExtracts = false; | |
| // Try to handle shuffled extractelements. Only pure extractelement | |
| // groups can be merged: merged groups are skipped for external uses, | |
| // and other values would be erased while still used by reduction ops. | |
| if (S && S.getOpcode() == Instruction::ExtractElement && | |
| !S.isAltShuffle() && I + 1 < E && | |
| all_of(ReducedVals[I + 1], | |
| [&](Value *RV) { | |
| return isa<ExtractElementInst>(TrackedVals.at(RV)); | |
| }) && | |
| GroupNegated == IsNegated(ReducedVals[I + 1].front())) { | |
| SmallVector<Value *> CommonCandidates(Candidates); | |
| for (Value *RV : ReducedVals[I + 1]) { | |
| Value *RdxVal = TrackedVals.at(RV); | |
| // Check if the reduction value was not overriden by the | |
| // extractelement instruction because of the vectorization and | |
| // exclude it, if it is not compatible with other values. | |
| auto *Inst = dyn_cast<ExtractElementInst>(RdxVal); | |
| if (!Inst) | |
| continue; | |
| CommonCandidates.push_back(RdxVal); | |
| TrackedToOrig.push_back(RV); | |
| } | |
| SmallVector<int> Mask; | |
| if (isFixedVectorShuffle(CommonCandidates, Mask, AC, | |
| V.getAnalysisCache())) { | |
| ++I; | |
| Candidates.swap(CommonCandidates); | |
| ShuffledExtracts = true; | |
| } | |
| } | |
| assert(all_of(TrackedToOrig, | |
| [&](Value *V) { return IsNegated(V) == GroupNegated; }) && | |
| "Expected sign-uniform group of reduced values"); | |
| // Emit code for constant values. | |
| if (Candidates.size() > 1 && allConstant(Candidates)) { | |
| if (RK == ReductionOrdering::Ordered) | |
| continue; | |
| Value *Res = Candidates.front(); | |
| Value *OrigV = TrackedToOrig.front(); | |
| ++VectorizedVals.try_emplace(OrigV).first->getSecond(); | |
| for (const auto [Idx, VC] : | |
| enumerate(ArrayRef(Candidates).drop_front())) { | |
| Res = createOp(Builder, RdxKind, Res, VC, "const.rdx", ReductionOps); | |
| Value *OrigV = TrackedToOrig[Idx + 1]; | |
| ++VectorizedVals.try_emplace(OrigV).first->getSecond(); | |
| if (auto *ResI = dyn_cast<Instruction>(Res)) | |
| V.analyzedReductionRoot(ResI); | |
| } | |
| AddReducedPart(TrackedToOrig.front(), Res, GroupNegated); | |
| continue; | |
| } | |
| unsigned NumReducedVals = Candidates.size(); | |
| auto UsedByReductionOnly = [&](Value *V) { | |
| if (!V->hasUseList()) | |
| return true; | |
| // Bail out if we have too many uses to save compilation time. | |
| if (V->hasNUsesOrMore(UsesLimit)) | |
| return false; | |
| return all_of(V->users(), | |
| [&](User *U) { return IgnoreList.contains(U); }); | |
| }; | |
| // Sign-aware reductions pair small positive/negative groups: allow | |
| // non-splat groups down to 2 elements. Seed-level reductions get the | |
| // same for groups whose values are used by the reduction operations | |
| // only, if the minimum vector factor covers the whole reduction. | |
| const bool MinVFAllowed = IsSeedRoot && NoScalarLeftovers && | |
| all_of(Candidates, UsedByReductionOnly); | |
| if (NumReducedVals < ReductionLimit && | |
| (NumReducedVals < 2 || | |
| (!isSplat(Candidates) && !CanPairSigns && !MinVFAllowed))) | |
| continue; | |
| // Check if we support repeated scalar values processing (optimization of | |
| // original scalar identity operations on matched horizontal reductions). | |
| IsSupportedHorRdxIdentityOp = | |
| RK == ReductionOrdering::Unordered && RdxKind != RecurKind::Mul && | |
| RdxKind != RecurKind::FMul && RdxKind != RecurKind::FMulAdd && | |
| (!Opts.slp_revec || !Candidates.front()->getType()->isVectorTy()); | |
| // Gather same values. | |
| SmallMapVector<Value *, unsigned, 16> SameValuesCounter; | |
| if (IsSupportedHorRdxIdentityOp) | |
| for (const auto [Idx, V] : enumerate(Candidates)) { | |
| Value *OrigV = TrackedToOrig[Idx]; | |
| ++SameValuesCounter.try_emplace(OrigV).first->second; | |
| } | |
| // Used to check if the reduced values used same number of times. In this | |
| // case the compiler may produce better code. E.g. if reduced values are | |
| // aabbccdd (8 x values), then the first node of the tree will have a node | |
| // for 4 x abcd + shuffle <4 x abcd>, <0, 0, 1, 1, 2, 2, 3, 3>. | |
| // Plus, the final reduction will be performed on <8 x aabbccdd>. | |
| // Instead compiler may build <4 x abcd> tree immediately, + reduction (4 | |
| // x abcd) * 2. | |
| // Currently it only handles add/fadd/xor. and/or/min/max do not require | |
| // this analysis, other operations may require an extra estimation of | |
| // the profitability. | |
| bool SameScaleFactor = false; | |
| bool OptReusedScalars = IsSupportedHorRdxIdentityOp && | |
| SameValuesCounter.size() != Candidates.size(); | |
| BoUpSLP::ExtraValueToDebugLocsMap ExternallyUsedValues; | |
| for (Value *RdxVal : ExcludedVectorizedReducedVals) | |
| ExternallyUsedValues.insert(RdxVal); | |
| if (OptReusedScalars) { | |
| SameScaleFactor = | |
| (RdxKind == RecurKind::Add || RdxKind == RecurKind::FAdd || | |
| RdxKind == RecurKind::Xor) && | |
| all_of(drop_begin(SameValuesCounter), | |
| [&SameValuesCounter](const std::pair<Value *, unsigned> &P) { | |
| return P.second == SameValuesCounter.front().second; | |
| }); | |
| Candidates.resize(SameValuesCounter.size()); | |
| TrackedToOrig.resize(SameValuesCounter.size()); | |
| transform(SameValuesCounter, Candidates.begin(), | |
| [&](const auto &P) { return TrackedVals.at(P.first); }); | |
| transform(SameValuesCounter, TrackedToOrig.begin(), | |
| [](const auto &P) { return P.first; }); | |
| NumReducedVals = Candidates.size(); | |
| // Have a reduction of the same element. | |
| if (NumReducedVals == 1) { | |
| Value *OrigV = TrackedToOrig.front(); | |
| unsigned Cnt = At(SameValuesCounter, OrigV); | |
| // A negated repeated value joins an already vectorized part of the | |
| // same width as a splat operand of the vector fsub (a per-lane fnma | |
| // with a multiply part) instead of the scalar scale and subtract. | |
| if (GroupNegated && | |
| any_of(VectorValuesAndScales, [&](const ReductionVectorPart &P) { | |
| return P.Vec && !P.ReducedInTree && | |
| getNumElements(P.Vec->getType()) == Cnt; | |
| })) { | |
| Value *Splat = Builder.CreateVectorSplat(Cnt, Candidates.front()); | |
| VectorValuesAndScales.push_back({Splat, /*Scale=*/1, | |
| /*IsSigned=*/true, | |
| /*ReducedInTree=*/false, | |
| GroupNegated}); | |
| } else { | |
| Value *RedVal = | |
| emitScaleForReusedOps(Candidates.front(), Builder, Cnt); | |
| AddReducedPart(OrigV, RedVal, GroupNegated); | |
| } | |
| VectorizedVals.try_emplace(OrigV, Cnt); | |
| ExternallyUsedValues.insert(OrigV); | |
| continue; | |
| } | |
| } | |
| GroupRdxFMF = RdxFMF; | |
| // No need to check for associativity, if 2 reduced values. | |
| if (NumReducedVals == 2 && | |
| (RdxKind == RecurKind::FAdd || RdxKind == RecurKind::FMul)) | |
| GroupRdxFMF.setAllowReassoc(true); | |
| unsigned MaxVecRegSize = V.getMaxVecRegSize(); | |
| unsigned EltSize = V.getVectorElementSize(Candidates[0]); | |
| const unsigned MaxElts = std::clamp<unsigned>( | |
| llvm::bit_floor(MaxVecRegSize / EltSize), RedValsMaxNumber, | |
| RegMaxNumber * RedValsMaxNumber); | |
| unsigned ReduxWidth = NumReducedVals; | |
| auto GetVectorFactor = [&, &TTI = *TTI](unsigned ReduxWidth) { | |
| unsigned NumRegs, NumAvailRegs; | |
| Type *ScalarTy = Candidates.front()->getType(); | |
| ReduxWidth = getFloorFullVectorNumberOfElements( | |
| TTI, ScalarTy, ReduxWidth, Opts.slp_revec); | |
| VectorType *Tp = cast<VectorType>(getWidenedType(ScalarTy, ReduxWidth)); | |
| NumRegs = V.getRegUsageForType(Tp, ScalarTy); | |
| NumAvailRegs = | |
| TTI.getNumberOfRegisters(TTI.getRegisterClassForType(true, Tp)); | |
| while (NumRegs > NumAvailRegs) { | |
| assert(ReduxWidth > 0 && "ReduxWidth is unexpectedly 0."); | |
| ReduxWidth = bit_floor(ReduxWidth - 1); | |
| VectorType *Tp = | |
| cast<VectorType>(getWidenedType(ScalarTy, ReduxWidth)); | |
| NumRegs = V.getRegUsageForType(Tp, ScalarTy); | |
| NumAvailRegs = | |
| TTI.getNumberOfRegisters(TTI.getRegisterClassForType(true, Tp)); | |
| } | |
| if (NumRegs > NumAvailRegs / 2) | |
| ReduxWidth = bit_floor(ReduxWidth); | |
| return ReduxWidth; | |
| }; | |
| if (!isAllowedNonPowerOf2VF(ReduxWidth, | |
| Opts.slp_vectorize_non_power_of_2)) | |
| ReduxWidth = GetVectorFactor(ReduxWidth); | |
| ReduxWidth = std::min(ReduxWidth, MaxElts); | |
| unsigned Start = 0; | |
| unsigned Pos = Start; | |
| // Restarts vectorization attempt with lower vector factor. | |
| unsigned PrevReduxWidth = ReduxWidth; | |
| bool CheckForReusedReductionOpsLocal = false; | |
| auto AdjustReducedVals = [&](bool IgnoreVL = false) { | |
| bool IsAnyRedOpGathered = | |
| !IgnoreVL && | |
| (RK == ReductionOrdering::Ordered || V.isAnyGathered(IgnoreList)); | |
| if (!CheckForReusedReductionOpsLocal && PrevReduxWidth == ReduxWidth) { | |
| // Check if any of the reduction ops are gathered. If so, worth | |
| // trying again with less number of reduction ops. | |
| CheckForReusedReductionOpsLocal |= IsAnyRedOpGathered; | |
| } | |
| ++Pos; | |
| if (Pos < NumReducedVals - ReduxWidth + 1) | |
| return IsAnyRedOpGathered; | |
| Pos = Start; | |
| --ReduxWidth; | |
| if (ReduxWidth > 1) | |
| ReduxWidth = GetVectorFactor(ReduxWidth); | |
| return IsAnyRedOpGathered; | |
| }; | |
| bool AnyVectorized = false; | |
| SmallDenseSet<std::pair<unsigned, unsigned>, 8> IgnoredCandidates; | |
| // The overlapping windows rebuild almost the same trees. Like the | |
| // scheduling region, the size of the next tree, estimated by the last | |
| // rejected one, is limited by the budget, reduced by the sizes of the | |
| // rejected trees, but not below the minimal size. The non-power-of-2 | |
| // widths get only the minimal size for the rejected trees. The budget | |
| // limits only the series of the rejected trees, the vectorized tree | |
| // starts a new series. | |
| unsigned ReductionBudget = Opts.slp_reduction_budget; | |
| unsigned LastRejectedTreeSize = 1; | |
| unsigned NonPowerOf2RejectedSize = 0; | |
| auto RejectTree = [&]() { | |
| LastRejectedTreeSize = V.getTreeSize(); | |
| const unsigned Size = ReduxWidth * LastRejectedTreeSize; | |
| ReductionBudget -= std::min(ReductionBudget, Size); | |
| ReductionBudget = std::max(ReductionBudget, MinReductionTreeSize); | |
| if (!has_single_bit(ReduxWidth)) | |
| NonPowerOf2RejectedSize += Size; | |
| }; | |
| // Same small-group allowance for the vector width, if it covers the | |
| // whole group. | |
| const unsigned MinReduxWidth = | |
| CanPairSigns || (MinVFAllowed && NumReducedVals == 2) | |
| ? 2 | |
| : ReductionLimit; | |
| while (Pos < NumReducedVals - ReduxWidth + 1 && | |
| ReduxWidth >= MinReduxWidth) { | |
| // Dependency in tree of the reduction ops - drop this attempt, try | |
| // later. | |
| if (CheckForReusedReductionOpsLocal && PrevReduxWidth != ReduxWidth && | |
| Start == 0) { | |
| CheckForReusedReductionOps = true; | |
| break; | |
| } | |
| PrevReduxWidth = ReduxWidth; | |
| ArrayRef<Value *> VL(std::next(Candidates.begin(), Pos), ReduxWidth); | |
| // Been analyzed already or out of budget - skip. | |
| if (ReduxWidth * LastRejectedTreeSize > ReductionBudget || | |
| IgnoredCandidates.contains(std::make_pair(Pos, ReduxWidth)) || | |
| (!has_single_bit(ReduxWidth) && | |
| (NonPowerOf2RejectedSize > MinReductionTreeSize || | |
| IgnoredCandidates.contains( | |
| std::make_pair(Pos, bit_floor(ReduxWidth))) || | |
| IgnoredCandidates.contains( | |
| std::make_pair(Pos + (ReduxWidth - bit_floor(ReduxWidth)), | |
| bit_floor(ReduxWidth))))) || | |
| V.areAnalyzedReductionVals(VL)) { | |
| (void)AdjustReducedVals(/*IgnoreVL=*/true); | |
| continue; | |
| } | |
| // Early exit if any of the reduction values were deleted during | |
| // previous vectorization attempts. | |
| if (any_of(make_isa_range<Instruction>(VL), | |
| [&V](Instruction *RedValI) { return V.isDeleted(RedValI); })) | |
| break; | |
| if (RK == ReductionOrdering::Ordered) { | |
| V.buildTree(VL); | |
| } else { | |
| V.buildTree(VL, IgnoreList); | |
| V.setNarrowedChainInsts(NarrowedChainInsts); | |
| } | |
| if (V.isTreeTinyAndNotFullyVectorizable(RK == | |
| ReductionOrdering::Unordered)) { | |
| RejectTree(); | |
| constexpr unsigned CandidatesLimit = 64; | |
| if (!AdjustReducedVals(RK == ReductionOrdering::Ordered && | |
| Candidates.size() >= CandidatesLimit)) | |
| MarkAnalyzedVals(VL); | |
| continue; | |
| } | |
| V.reorderTopToBottom(); | |
| // No need to reorder the root node at all for reassociative reduction. | |
| V.reorderBottomToTop(/*IgnoreReorder=*/GroupRdxFMF.allowReassoc() || | |
| VL.front()->getType()->isIntOrIntVectorTy() || | |
| ReductionLimit > 2 || | |
| RK == ReductionOrdering::Ordered); | |
| // Keep extracted other reduction values, if they are used in the | |
| // vectorization trees. | |
| BoUpSLP::ExtraValueToDebugLocsMap LocalExternallyUsedValues( | |
| ExternallyUsedValues); | |
| // The reduction root is used as the insertion point for new | |
| // instructions, so set it as externally used to prevent it from being | |
| // deleted. | |
| LocalExternallyUsedValues.insert(ReductionRoot); | |
| for (unsigned Cnt = 0, Sz = ReducedVals.size(); Cnt < Sz; ++Cnt) { | |
| if (Cnt == I || (ShuffledExtracts && Cnt == I - 1)) | |
| continue; | |
| for (Instruction *Inst : | |
| make_isa_range<Instruction>(ReducedVals[Cnt])) | |
| LocalExternallyUsedValues.insert(TrackedVals[Inst]); | |
| } | |
| if (!IsSupportedHorRdxIdentityOp) { | |
| // Number of uses of the candidates in the vector of values. | |
| assert(SameValuesCounter.empty() && | |
| "Reused values counter map is not empty"); | |
| for (unsigned Cnt = 0; Cnt < NumReducedVals; ++Cnt) { | |
| if (Cnt >= Pos && Cnt < Pos + ReduxWidth) | |
| continue; | |
| Value *OrigV = TrackedToOrig[Cnt]; | |
| ++SameValuesCounter.try_emplace(OrigV).first->second; | |
| } | |
| } | |
| V.transformNodes(); | |
| V.computeMinimumValueSizes(); | |
| InstructionCost TreeCost = V.calculateTreeCostAndTrimNonProfitable( | |
| VL, cast<Instruction>(ReductionRoot)); | |
| // A negated slice is subtracted in the final combine, it cannot be | |
| // accumulated lane-wise. | |
| const bool LoopAccCandidate = | |
| !GroupNegated && | |
| LoopAcc.isCandidate(VectorizedTree, !VectorValuesAndScales.empty(), | |
| IsSupportedHorRdxIdentityOp, ReducedVals, | |
| Candidates, I, Pos, ReduxWidth, | |
| OptReusedScalars, SameScaleFactor); | |
| SmallPtrSet<Value *, 4> VLScalars(llvm::from_range, VL); | |
| // Gather externally used values. | |
| SmallPtrSet<Value *, 4> Visited; | |
| for (unsigned Cnt = 0; Cnt < NumReducedVals; ++Cnt) { | |
| if (Cnt >= Pos && Cnt < Pos + ReduxWidth) | |
| continue; | |
| // The candidate may be replaced by the previous vectorization | |
| // attempts, use the currently tracked value. | |
| Value *RdxVal = TrackedVals.at(TrackedToOrig[Cnt]); | |
| if (!Visited.insert(RdxVal).second) | |
| continue; | |
| // Scalars not reduced by the top node may be used by the reduction | |
| // epilogue, even if not vectorized as part of the tree. | |
| if (!VLScalars.contains(RdxVal)) { | |
| LocalExternallyUsedValues.insert(RdxVal); | |
| continue; | |
| } | |
| Value *OrigV = TrackedToOrig[Cnt]; | |
| unsigned NumOps = | |
| VectorizedVals.lookup(OrigV) + At(SameValuesCounter, OrigV); | |
| if (NumOps != ReducedValsToOps.at(OrigV).size()) | |
| LocalExternallyUsedValues.insert(RdxVal); | |
| } | |
| // Do not need the list of reused scalars in regular mode anymore. | |
| if (!IsSupportedHorRdxIdentityOp) | |
| SameValuesCounter.clear(); | |
| for (Value *RdxVal : VL) | |
| if (RequiredExtract.contains(RdxVal)) | |
| LocalExternallyUsedValues.insert(RdxVal); | |
| V.buildExternalUses(LocalExternallyUsedValues); | |
| // Estimate cost. | |
| InstructionCost ReductionCost, HorVecCost = 0, RdxOpCost = 0; | |
| if (RK == ReductionOrdering::Ordered || V.isReducedBitcastRoot() || | |
| V.isReducedCmpBitcastRoot()) | |
| ReductionCost = 0; | |
| else | |
| ReductionCost = getReductionCost( | |
| TTI, VL, TrackedToOrig, Pos, SameValuesCounter, IsCmpSelMinMax, | |
| GroupRdxFMF, V, DT, DL, TLI, HorVecCost, RdxOpCost); | |
| // The vector accumulator form is used if it is not more expensive than | |
| // the horizontal reduction on every iteration. | |
| InstructionCost LoopAccDelta = 0; | |
| if (LoopAccCandidate && ReductionCost.isValid()) | |
| LoopAccDelta = | |
| LoopAcc.getCostDelta(HorVecCost, RdxOpCost, RdxFMF, | |
| V.getReductionType(), VL.front()->getType()); | |
| // On a tie the accumulator wins: it also replaces the loop-carried | |
| // scalar dependency through the horizontal reduction by a lane-wise | |
| // one. An invalid reduction cost rejects the tree below anyway. | |
| const bool UseLoopAccForm = | |
| LoopAccCandidate && ReductionCost.isValid() && LoopAccDelta <= 0; | |
| // If the root is a select (min/max idiom), the insert point is the | |
| // compare condition of that select. | |
| Instruction *RdxRootInst = cast<Instruction>(ReductionRoot); | |
| Instruction *InsertPt = RdxRootInst; | |
| if (IsCmpSelMinMax) | |
| InsertPt = GetCmpForMinMaxReduction(RdxRootInst); | |
| InstructionCost Cost = | |
| V.getTreeCost(TreeCost, VL, ReductionCost, InsertPt); | |
| if (UseLoopAccForm) | |
| Cost += LoopAccDelta; | |
| LLVM_DEBUG(dbgs() << "SLP: Found cost = " << Cost | |
| << " for reduction\n"); | |
| if (!Cost.isValid()) | |
| break; | |
| if (Cost >= -V.getCostThreshold()) { | |
| V.getORE()->emit([&]() { | |
| return OptimizationRemarkMissed(SV_NAME, "HorSLPNotBeneficial", | |
| ReducedValsToOps.at(VL[0]).front()) | |
| << "Vectorizing horizontal reduction is possible " | |
| << "but not beneficial with cost " << ore::NV("Cost", Cost) | |
| << " and threshold " | |
| << ore::NV("Threshold", -V.getCostThreshold()); | |
| }); | |
| RejectTree(); | |
| if (!AdjustReducedVals()) { | |
| MarkAnalyzedVals(VL); | |
| unsigned Offset = Pos == Start ? Pos : Pos - 1; | |
| if (ReduxWidth > ReductionLimit && V.isTreeNotExtendable()) { | |
| // Add subvectors of VL to the list of the analyzed values. | |
| for (unsigned VF = getFloorFullVectorNumberOfElements( | |
| *TTI, VL.front()->getType(), ReduxWidth - 1, | |
| Opts.slp_revec); | |
| VF >= ReductionLimit; | |
| VF = getFloorFullVectorNumberOfElements( | |
| *TTI, VL.front()->getType(), VF - 1, Opts.slp_revec)) { | |
| if (has_single_bit(VF) && | |
| V.getCanonicalGraphSize() != V.getTreeSize()) | |
| continue; | |
| for (unsigned Idx : seq<unsigned>(ReduxWidth - VF)) | |
| IgnoredCandidates.insert(std::make_pair(Offset + Idx, VF)); | |
| } | |
| } | |
| } | |
| continue; | |
| } | |
| LLVM_DEBUG(dbgs() << "SLP: Vectorizing horizontal reduction at cost:" | |
| << Cost << ". (HorRdx)\n"); | |
| ReductionBudget = Opts.slp_reduction_budget; | |
| LastRejectedTreeSize = 1; | |
| NonPowerOf2RejectedSize = 0; | |
| V.getORE()->emit([&]() { | |
| return OptimizationRemark(SV_NAME, "VectorizedHorizontalReduction", | |
| ReducedValsToOps.at(VL[0]).front()) | |
| << "Vectorized horizontal reduction with cost " | |
| << ore::NV("Cost", Cost) << " and with tree size " | |
| << ore::NV("TreeSize", V.getTreeSize()); | |
| }); | |
| Builder.setFastMathFlags(RdxFMF); | |
| // Match the bitmask form of the first vector part before the | |
| // vectorization, which drops the operands of the vectorized leaves. | |
| BoolBitmask BitmaskMatch = | |
| VectorValuesAndScales.empty() | |
| ? isBoolBitmaskRdx(RdxKind, NarrowedLeafShifts, DL) | |
| : BoolBitmask::None; | |
| // Vectorize a tree. | |
| Value *VectorizedRoot = V.vectorizeTree( | |
| LocalExternallyUsedValues, InsertPt, VectorValuesAndScales); | |
| if (RK == ReductionOrdering::Ordered) { | |
| // No need to generate reduction here, emit extractelements instead in | |
| // the tree vectorizer. | |
| assert(VectorizedRoot && "Expected vectorized tree"); | |
| // Count vectorized reduced values to exclude them from final | |
| // reduction. | |
| for (Value *RdxVal : VL) | |
| ++VectorizedVals.try_emplace(RdxVal).first->getSecond(); | |
| Pos += ReduxWidth; | |
| Start = Pos; | |
| ReduxWidth = NumReducedVals - Pos; | |
| if (ReduxWidth > 1) | |
| ReduxWidth = GetVectorFactor(NumReducedVals - Pos); | |
| AnyVectorized = true; | |
| VectorizedTree = ReductionRoot; | |
| continue; | |
| } | |
| Builder.SetInsertPoint(InsertPt); | |
| // To prevent poison from leaking across what used to be sequential, | |
| // safe, scalar boolean logic operations, the reduction operand must be | |
| // frozen. | |
| if (AnyBoolLogicOp && !isGuaranteedNotToBePoison(VectorizedRoot, AC)) | |
| VectorizedRoot = Builder.CreateFreeze(VectorizedRoot); | |
| // Emit code to correctly handle reused reduced values, if required. | |
| if (OptReusedScalars && !SameScaleFactor) { | |
| // The reuse counters are stored in the deduplicated candidates | |
| // order, but the emitted reduction vector lane order is defined by | |
| // the root node, which may be reordered, split or have copyable | |
| // elements. Place each counter at the lane the matching candidate is | |
| // vectorized to, so the counter is applied to the correct lane. | |
| ArrayRef<Value *> RootVL = V.getRootNodeScalars(); | |
| ArrayRef<Value *> CandSlice(Candidates.begin() + Pos, ReduxWidth); | |
| SmallVector<Value *> RootTrackedToOrig(RootVL.size()); | |
| for (auto [Idx, Val] : enumerate(CandSlice)) | |
| RootTrackedToOrig[V.findRootLaneForValue(Val)] = | |
| TrackedToOrig[Pos + Idx]; | |
| VectorizedRoot = emitReusedOps(VectorizedRoot, Builder, V, | |
| SameValuesCounter, RootTrackedToOrig); | |
| } | |
| // Mask in the narrow type, then widen and shift per lane. Map back | |
| // from extractelements to the original reduced values for the shift | |
| // amounts. The absorbed narrow and-masks and the bits truncated by | |
| // the absorbed narrow shls are masked off before the widening. | |
| if (!NarrowedLeafShifts.empty()) { | |
| Type *WideTy = ReductionRoot->getType(); | |
| Type *NarrowTy = VL.front()->getType(); | |
| unsigned VF = getNumElements(VectorizedRoot->getType()); | |
| // The root may be demoted and resized to the reduction bitwidth, | |
| // which is computed for the wide reduction operations. | |
| if (VectorizedRoot->getType()->getScalarType() != NarrowTy) { | |
| VectorizedRoot = Builder.CreateIntCast( | |
| VectorizedRoot, getWidenedType(NarrowTy, VF), | |
| V.isSignedMinBitwidthRootNode()); | |
| ++NumVectorInstructions; | |
| } | |
| SmallVector<Constant *> ShiftConsts(VF, ConstantInt::get(WideTy, 0)); | |
| SmallVector<Constant *> MaskConsts( | |
| VF, Constant::getAllOnesValue(NarrowTy)); | |
| bool AnyShift = false; | |
| bool AnyMask = false; | |
| for (auto [Idx, Val] : enumerate(VL)) { | |
| const NarrowedLeafInfo &L = | |
| NarrowedLeafShifts.at(TrackedToOrig[Pos + Idx]); | |
| unsigned Lane = V.findRootLaneForValue(Val); | |
| ShiftConsts[Lane] = ConstantInt::get(WideTy, L.Shift); | |
| AnyShift |= L.Shift != 0; | |
| if (!L.Mask.isAllOnes()) { | |
| MaskConsts[Lane] = ConstantInt::get(NarrowTy, L.Mask); | |
| AnyMask = true; | |
| } | |
| } | |
| if (Value *Bitmask = tryEmitBoolBitmaskRdx( | |
| Builder, V, *TTI, BitmaskMatch, VectorizedRoot, VL, | |
| TrackedToOrig, Pos, MaskConsts, GroupRdxFMF)) { | |
| VectorizedRoot = Bitmask; | |
| } else { | |
| if (AnyMask) { | |
| VectorizedRoot = Builder.CreateAnd( | |
| VectorizedRoot, ConstantVector::get(MaskConsts)); | |
| ++NumVectorInstructions; | |
| } | |
| VectorizedRoot = | |
| Builder.CreateZExt(VectorizedRoot, getWidenedType(WideTy, VF)); | |
| ++NumVectorInstructions; | |
| if (AnyShift) { | |
| VectorizedRoot = Builder.CreateShl( | |
| VectorizedRoot, ConstantVector::get(ShiftConsts)); | |
| ++NumVectorInstructions; | |
| } | |
| } | |
| } | |
| Type *ScalarTy = VL.front()->getType(); | |
| Type *VecTy = VectorizedRoot->getType(); | |
| Type *RedScalarTy = VecTy->getScalarType(); | |
| // A group of negated reduced values (from an fsub/fneg in the chain) | |
| // is subtracted in the final combine. | |
| VectorValuesAndScales.push_back( | |
| {VectorizedRoot, | |
| OptReusedScalars && SameScaleFactor | |
| ? SameValuesCounter.front().second | |
| : 1, | |
| RedScalarTy != ScalarTy->getScalarType() | |
| ? NarrowedLeafShifts.empty() && V.isSignedMinBitwidthRootNode() | |
| : true, | |
| V.isReducedBitcastRoot() || V.isReducedCmpBitcastRoot() || | |
| !VectorizedRoot->getType()->isVectorTy(), | |
| GroupNegated}); | |
| LoopAccVectorized = UseLoopAccForm; | |
| // Count vectorized reduced values to exclude them from final reduction. | |
| for (const auto [Idx, RdxVal] : enumerate(VL)) { | |
| Value *OrigV = TrackedToOrig[Pos + Idx]; | |
| if (IsSupportedHorRdxIdentityOp) { | |
| VectorizedVals.try_emplace(OrigV, At(SameValuesCounter, OrigV)); | |
| continue; | |
| } | |
| ++VectorizedVals.try_emplace(OrigV).first->getSecond(); | |
| if (!V.isVectorized(RdxVal)) | |
| RequiredExtract.insert(RdxVal); | |
| } | |
| Pos += ReduxWidth; | |
| Start = Pos; | |
| ReduxWidth = NumReducedVals - Pos; | |
| if (ReduxWidth > 1) | |
| ReduxWidth = GetVectorFactor(NumReducedVals - Pos); | |
| AnyVectorized = true; | |
| // All remaining reduced values are the identity: nothing left to | |
| // vectorize for the accumulator form. | |
| if (UseLoopAccForm) | |
| break; | |
| } | |
| if (OptReusedScalars && !AnyVectorized) { | |
| for (const std::pair<Value *, unsigned> &P : SameValuesCounter) { | |
| Value *RdxVal = TrackedVals.at(P.first); | |
| Value *RedVal = emitScaleForReusedOps(RdxVal, Builder, P.second); | |
| AddReducedPart(P.first, RedVal, GroupNegated); | |
| VectorizedVals.try_emplace(P.first, P.second); | |
| } | |
| continue; | |
| } | |
| } | |
| // Early exit for the ordered reductions. | |
| // No need to do anything else here, so we can just exit. | |
| if (RK == ReductionOrdering::Ordered) { | |
| assert(NegatedReducedVals.empty() && | |
| "Unexpected negated reduced values in the ordered reduction"); | |
| // Only the root is a dead end, the leaves may still be the roots of the | |
| // other reductions. | |
| if (!VectorizedTree) | |
| V.analyzedReductionRoot(cast<Instruction>(ReductionRoot)); | |
| return VectorizedTree; | |
| } | |
| // A booleanized logical and/or reduction has an i1 root and wide integer | |
| // reduced values; it is performed in the wide type and bit 0 of the | |
| // result is the final value. | |
| Type *BoolReduxWideTy = | |
| getBoolReduxWideTy(RdxKind, ReductionRoot->getType(), | |
| ReducedVals.front().front()->getType()); | |
| SmallVector<std::pair<Instruction *, Value *>> LeftoverReductions; | |
| LoopAcc.collectLeftovers(VectorizedVals, ReducedVals, ReducedValsToOps, | |
| LeftoverReductions); | |
| // The accumulator form requires a single vectorized slice and no | |
| // leftover reduced values; the latter would have to be folded into the | |
| // scalar accumulator on every iteration. Otherwise the accumulator phi is | |
| // folded back on top of the reduction like a leftover. | |
| Value *AccV = LoopAcc.tryEmit( | |
| Builder, RdxFMF, LoopAccVectorized, VectorizedTree, LeftoverReductions, | |
| VectorValuesAndScales, RequiredExtract, ReducedValsToOps, ReductionOps, | |
| [this, TTI = TTI, &V](Value *Vec, IRBuilderBase &B, Type *Ty) { | |
| return emitReduction(Vec, B, TTI, | |
| V.getCastContextHint(V.getRootNode()), Ty); | |
| }); | |
| if (AccV) | |
| VectorizedTree = AccV; | |
| else if (!VectorValuesAndScales.empty()) { | |
| Builder.setFastMathFlags(GroupRdxFMF); | |
| Value *Res = nullptr; | |
| bool ResNegated = false; | |
| const ReductionVectorPart &P = VectorValuesAndScales.front(); | |
| // A single fully vectorized booleanized reduction without scalar | |
| // remainders can be emitted as trunc+bitcast+cmp, if cheaper. | |
| if (BoolReduxWideTy && !VectorizedTree && | |
| VectorValuesAndScales.size() == 1 && !P.ReducedInTree && !P.Negated && | |
| isa<FixedVectorType>(P.Vec->getType()) && | |
| none_of(ReducedVals, [&](ArrayRef<Value *> Candidates) { | |
| return any_of(Candidates, [&](Value *RdxVal) { | |
| return VectorizedVals.lookup(RdxVal) < | |
| ReducedValsToOps.at(RdxVal).size(); | |
| }); | |
| })) | |
| Res = tryEmitBoolReduxBitcastCmp(Builder, *TTI, RdxKind, P.Vec, | |
| ReductionRoot, GroupRdxFMF, | |
| V.getCostKind()); | |
| if (!Res) | |
| std::tie(Res, ResNegated) = emitReduction( | |
| Builder, *TTI, V.getCastContextHint(V.getRootNode()), | |
| BoolReduxWideTy ? BoolReduxWideTy : ReductionRoot->getType()); | |
| Builder.setFastMathFlags(RdxFMF); | |
| // The reduction result of the all-negated parts is subtracted in the | |
| // final combine. | |
| AddReducedPart(/*OrigV=*/nullptr, Res, ResNegated); | |
| } else if (hasFlattenedNegations()) { | |
| // The flattened reduction pays off only with a vector part. Without one | |
| // the scalar chain is merely reshuffled (which may also break the scalar | |
| // fma formation), while the original code may still be vectorizable as | |
| // an operand tree - drop the scalar parts emitted so far and give up. | |
| auto *Root = cast<Instruction>(ReductionRoot); | |
| for (Instruction *I = Root->getPrevNode(); I && I != InstBeforeRoot;) { | |
| Instruction *Prev = I->getPrevNode(); | |
| if (I->use_empty() && !V.isAnalyzedReductionRoot(I)) | |
| I->eraseFromParent(); | |
| I = Prev; | |
| } | |
| // The fsub/fneg leaves themselves may still form vectorizable groups | |
| // and the chains behind them are matched as separate reductions - leave | |
| // the reduction operations unmarked for the retry. | |
| return nullptr; | |
| } | |
| if (!VectorizedTree && NegExtraReductions.empty()) { | |
| if (!CheckForReusedReductionOps) { | |
| for (ReductionOpsType &RdxOps : ReductionOps) | |
| for (Value *RdxOp : RdxOps) | |
| V.analyzedReductionRoot(cast<Instruction>(RdxOp)); | |
| } | |
| return nullptr; | |
| } | |
| // Reorder operands of bool logical op in the natural order to avoid | |
| // possible problem with poison propagation. If not possible to reorder | |
| // (both operands are originally RHS), emit an extra freeze instruction | |
| // for the LHS operand. | |
| // I.e., if we have original code like this: | |
| // RedOp1 = select i1 ?, i1 LHS, i1 false | |
| // RedOp2 = select i1 RHS, i1 ?, i1 false | |
| // Then, we swap LHS/RHS to create a new op that matches the poison | |
| // semantics of the original code. | |
| // If we have original code like this and both values could be poison: | |
| // RedOp1 = select i1 ?, i1 LHS, i1 false | |
| // RedOp2 = select i1 ?, i1 RHS, i1 false | |
| // Then, we must freeze LHS in the new op. | |
| auto FixBoolLogicalOps = | |
| [&, VectorizedTree](Value *&LHS, Value *&RHS, Instruction *RedOp1, | |
| Instruction *RedOp2, bool InitStep) { | |
| if (!AnyBoolLogicOp) | |
| return; | |
| if (isBoolLogicOp(RedOp1) && ((!InitStep && LHS == VectorizedTree) || | |
| getRdxOperand(RedOp1, 0) == LHS || | |
| isGuaranteedNotToBePoison(LHS, AC))) | |
| return; | |
| bool NeedFreeze = LHS != VectorizedTree; | |
| if (isBoolLogicOp(RedOp2) && ((!InitStep && RHS == VectorizedTree) || | |
| getRdxOperand(RedOp2, 0) == RHS || | |
| isGuaranteedNotToBePoison(RHS, AC))) { | |
| // If RedOp2 was used as a second operand - do not swap. | |
| if ((InitStep || RHS != VectorizedTree) && | |
| getRdxOperand(RedOp2, 0) == RHS && | |
| ((isBoolLogicOp(RedOp1) && | |
| getRdxOperand(RedOp1, 1) == RedOp2) || | |
| any_of(ReductionOps, [&](ArrayRef<Value *> Ops) { | |
| return any_of(make_isa_range<Instruction>(Ops), | |
| [&](Instruction *OpI) { | |
| return isBoolLogicOp(OpI) && | |
| getRdxOperand(OpI, 1) == RedOp2; | |
| }); | |
| }))) { | |
| NeedFreeze = false; | |
| } else { | |
| std::swap(LHS, RHS); | |
| return; | |
| } | |
| } | |
| if (NeedFreeze) | |
| LHS = Builder.CreateFreeze(LHS); | |
| }; | |
| // Leftover narrow values need the same mask/zext/shift as the vectorized | |
| // ones. | |
| auto WidenNarrowedVal = [&](Value *OrigV, Value *&V) { | |
| auto It = NarrowedLeafShifts.find(OrigV); | |
| if (It == NarrowedLeafShifts.end()) | |
| return; | |
| if (!It->second.Mask.isAllOnes()) | |
| V = Builder.CreateAnd(V, It->second.Mask); | |
| V = Builder.CreateZExt(V, ReductionRoot->getType()); | |
| if (It->second.Shift != 0) | |
| V = Builder.CreateShl(V, It->second.Shift); | |
| }; | |
| // Finish the reduction. | |
| // Need to add extra arguments and not vectorized possible reduction values. | |
| // Try to avoid dependencies between the scalar remainders after reductions. | |
| auto FinalGen = [&](ArrayRef<std::pair<Instruction *, Value *>> InstVals, | |
| bool InitStep) { | |
| unsigned Sz = InstVals.size(); | |
| SmallVector<std::pair<Instruction *, Value *>> ExtraReds(Sz / 2 + Sz % 2); | |
| for (unsigned I = 0, E = (Sz / 2) * 2; I < E; I += 2) { | |
| Instruction *RedOp = InstVals[I + 1].first; | |
| Builder.SetCurrentDebugLocation(RedOp->getDebugLoc()); | |
| Value *RdxVal1 = InstVals[I].second; | |
| Value *StableRdxVal1 = RdxVal1; | |
| auto It1 = TrackedVals.find(RdxVal1); | |
| if (It1 != TrackedVals.end()) | |
| StableRdxVal1 = It1->second; | |
| Value *RdxVal2 = InstVals[I + 1].second; | |
| Value *StableRdxVal2 = RdxVal2; | |
| auto It2 = TrackedVals.find(RdxVal2); | |
| if (It2 != TrackedVals.end()) | |
| StableRdxVal2 = It2->second; | |
| WidenNarrowedVal(RdxVal1, StableRdxVal1); | |
| WidenNarrowedVal(RdxVal2, StableRdxVal2); | |
| // To prevent poison from leaking across what used to be sequential, | |
| // safe, scalar boolean logic operations, the reduction operand must be | |
| // frozen. | |
| FixBoolLogicalOps(StableRdxVal1, StableRdxVal2, InstVals[I].first, | |
| RedOp, InitStep); | |
| Value *ExtraRed = createOp(Builder, RdxKind, StableRdxVal1, | |
| StableRdxVal2, "op.rdx", ReductionOps); | |
| ExtraReds[I / 2] = std::make_pair(InstVals[I].first, ExtraRed); | |
| } | |
| if (Sz % 2 == 1) | |
| ExtraReds[Sz / 2] = InstVals.back(); | |
| return ExtraReds; | |
| }; | |
| SmallVector<std::pair<Instruction *, Value *>> ExtraReductions; | |
| if (VectorizedTree) | |
| ExtraReductions.emplace_back(cast<Instruction>(ReductionRoot), | |
| VectorizedTree); | |
| for (const auto &[RedOp, RdxVal] : LeftoverReductions) | |
| (IsNegated(RdxVal) ? NegExtraReductions : ExtraReductions) | |
| .emplace_back(RedOp, RdxVal); | |
| // Iterate through all not-vectorized reduction values/extra arguments, | |
| // combining them pairwise. | |
| auto CombineReds = | |
| [&](SmallVectorImpl<std::pair<Instruction *, Value *>> &Reds) { | |
| bool InitStep = true; | |
| while (Reds.size() > 1) { | |
| SmallVector<std::pair<Instruction *, Value *>> NewReds = | |
| FinalGen(Reds, InitStep); | |
| Reds.swap(NewReds); | |
| InitStep = false; | |
| } | |
| Value *RdxVal = Reds.front().second; | |
| auto It = TrackedVals.find(RdxVal); | |
| if (It != TrackedVals.end()) | |
| RdxVal = It->second; | |
| return RdxVal; | |
| }; | |
| VectorizedTree = nullptr; | |
| if (!ExtraReductions.empty()) | |
| VectorizedTree = CombineReds(ExtraReductions); | |
| // Combine the subtracted values pairwise as well and subtract the combined | |
| // value from the positive part of the reduction (or just negate it, if | |
| // there is no positive part). | |
| if (!NegExtraReductions.empty()) { | |
| Value *NegTree = CombineReds(NegExtraReductions); | |
| Builder.SetCurrentDebugLocation( | |
| cast<Instruction>(ReductionOps.front().front())->getDebugLoc()); | |
| Value *Op = VectorizedTree | |
| ? Builder.CreateFSub(VectorizedTree, NegTree, "op.rdx") | |
| : Builder.CreateFNeg(NegTree, "op.rdx"); | |
| propagateIRFlags(Op, ReductionOps.front(), nullptr, | |
| /*IncludeWrapFlags=*/false); | |
| VectorizedTree = Op; | |
| } | |
| if (BoolReduxWideTy && | |
| VectorizedTree->getType() != ReductionRoot->getType()) | |
| VectorizedTree = | |
| Builder.CreateTrunc(VectorizedTree, ReductionRoot->getType()); | |
| // The accumulator emission already replaced all uses of the root. | |
| if (!AccV) | |
| ReductionRoot->replaceAllUsesWith(VectorizedTree); | |
| // The original scalar reduction is expected to have no remaining | |
| // uses outside the reduction tree itself. Assert that we got this | |
| // correct, replace internal uses with undef, and mark for eventual | |
| // deletion. | |
| #ifndef NDEBUG | |
| SmallPtrSet<Value *, 4> IgnoreSet; | |
| for (ArrayRef<Value *> RdxOps : ReductionOps) | |
| IgnoreSet.insert_range(RdxOps); | |
| #endif | |
| for (ArrayRef<Value *> RdxOps : ReductionOps) { | |
| for (Value *Ignore : | |
| make_filter_range(RdxOps, [](Value *Ignore) { return Ignore; })) { | |
| #ifndef NDEBUG | |
| for (auto *U : Ignore->users()) { | |
| assert(IgnoreSet.count(U) && | |
| "All users must be either in the reduction ops list."); | |
| } | |
| #endif | |
| if (!Ignore->use_empty()) { | |
| Value *P = PoisonValue::get(Ignore->getType()); | |
| Ignore->replaceAllUsesWith(P); | |
| } | |
| } | |
| V.removeInstructionsAndOperands(RdxOps, VectorValuesAndScales); | |
| } | |
| return VectorizedTree; | |
| } | |
| /// Attempt to vectorize an ordered (linearized) reduction chain. | |
| /// Reduced values from matchOrderedReduction() are in accumulation order. | |
| /// Vectorized subsets are immediately reduced via ordered reduction | |
| /// intrinsics; non-vectorized values are folded linearly. | |
| Value *tryToReduceOrdered(BoUpSLP &V, const DataLayout &DL, | |
| TargetTransformInfo *TTI, | |
| const TargetLibraryInfo &TLI, AssumptionCache *AC, | |
| DominatorTree &DT) { | |
| constexpr unsigned RegMaxNumber = 4; | |
| constexpr unsigned RedValsMaxNumber = 128; | |
| assert(RK == ReductionOrdering::Ordered && "Expected ordered reduction"); | |
| assert(ReducedVals.size() == 1 && | |
| "Expected single group from matchOrderedReduction"); | |
| assert(NegatedReducedVals.empty() && | |
| "Unexpected negated reduced values in the ordered reduction"); | |
| Instruction *RdxRootInst = cast<Instruction>(ReductionRoot); | |
| IRBuilder<TargetFolder> Builder(RdxRootInst->getIterator(), | |
| TargetFolder(DL)); | |
| // Track the reduced values in case if they are replaced by extractelement | |
| // because of the vectorization. | |
| SmallDenseMap<Value *, WeakTrackingVH> TrackedVals( | |
| ReducedVals.size() * ReducedVals.front().size()); | |
| // Need to track reduced vals, they may be changed during vectorization of | |
| // subvectors. | |
| for (ArrayRef<Value *> Candidates : ReducedVals) | |
| for (Value *V : Candidates) | |
| TrackedVals.try_emplace(V, V); | |
| SmallVector<Value *> Candidates(ReducedVals.back()); | |
| // Intersect the fast-math-flags from all reduction operations. | |
| FastMathFlags RdxFMF; | |
| RdxFMF.set(); | |
| for (Value *RdxVal : Candidates) | |
| for (FPMathOperator *FPMO : | |
| make_isa_range<FPMathOperator>(ReducedValsToOps.at(RdxVal))) | |
| RdxFMF &= FPMO->getFastMathFlags(); | |
| // This is an ordered reduction, selected because the reduction operations | |
| // are not associative (e.g. an fadd without nsz). The accumulation order | |
| // must be preserved, so the generated llvm.vector.reduce.fadd has to stay | |
| // ordered. Drop 'reassoc' from the flags used to emit and cost the | |
| // reduction. | |
| RdxFMF.setAllowReassoc(/*B=*/false); | |
| unsigned MaxVecRegSize = V.getMaxVecRegSize(); | |
| unsigned EltSize = V.getVectorElementSize(Candidates[0]); | |
| const unsigned MaxElts = | |
| std::clamp<unsigned>(llvm::bit_floor(MaxVecRegSize / EltSize), | |
| RedValsMaxNumber, RegMaxNumber * RedValsMaxNumber); | |
| unsigned ReduxWidth = 0; | |
| auto GetVectorFactor = [&, &TTI = *TTI](unsigned ReduxWidth) { | |
| Type *ScalarTy = Candidates.front()->getType(); | |
| ReduxWidth = getFloorFullVectorNumberOfElements(TTI, ScalarTy, ReduxWidth, | |
| Opts.slp_revec); | |
| Type *Tp = getWidenedType(ScalarTy, ReduxWidth); | |
| unsigned NumParts = V.getNumberOfParts(Tp, ScalarTy); | |
| unsigned NumRegs = | |
| TTI.getNumberOfRegisters(TTI.getRegisterClassForType(true, Tp)); | |
| while (NumParts > NumRegs) { | |
| assert(ReduxWidth > 0 && "ReduxWidth is unexpectedly 0."); | |
| ReduxWidth = bit_floor(ReduxWidth - 1); | |
| Type *Tp = getWidenedType(ScalarTy, ReduxWidth); | |
| NumParts = V.getNumberOfParts(Tp, ScalarTy); | |
| NumRegs = | |
| TTI.getNumberOfRegisters(TTI.getRegisterClassForType(true, Tp)); | |
| } | |
| if (NumParts > NumRegs / 2) | |
| ReduxWidth = bit_floor(ReduxWidth); | |
| return ReduxWidth; | |
| }; | |
| auto ShrinkReduxWidth = [&]() { | |
| --ReduxWidth; | |
| if (ReduxWidth > 1) | |
| ReduxWidth = GetVectorFactor(ReduxWidth); | |
| }; | |
| // Try to build, cost-check, and vectorize a window [Start, Start+Width). | |
| unsigned SuccessStart = 0, SuccessWidth = 0; | |
| Value *SuccessRoot = nullptr; | |
| SmallMapVector<Value *, unsigned, 16> EmptySameValuesCounter; | |
| auto TryWindow = [&](unsigned Start, unsigned Width) -> bool { | |
| ArrayRef<Value *> VL = ArrayRef(Candidates).slice(Start, Width); | |
| if (V.areAnalyzedReductionVals(VL)) | |
| return false; | |
| if (any_of(make_isa_range<Instruction>(VL), | |
| [&V](Instruction *RedValI) { return V.isDeleted(RedValI); })) | |
| return false; | |
| // Ignore the whole reduction operation chain, not only the ops tied to | |
| // the current window. | |
| SmallDenseSet<Value *> IgnoreList; | |
| for (ReductionOpsType &RdxOps : ReductionOps) | |
| for (Value *RdxOp : RdxOps) | |
| if (RdxOp) | |
| IgnoreList.insert(RdxOp); | |
| V.buildTree(VL, IgnoreList); | |
| if (V.isTreeTinyAndNotFullyVectorizable(/*ForReduction=*/true)) { | |
| V.analyzedReductionVals(VL); | |
| return false; | |
| } | |
| // Do not reorder whole tree to preserve the original order of the ordered | |
| // reduction. | |
| V.reorderBottomToTop(); | |
| BoUpSLP::ExtraValueToDebugLocsMap LocalExternallyUsedValues; | |
| LocalExternallyUsedValues.insert(ReductionRoot); | |
| for (unsigned Cnt : seq<unsigned>(Candidates.size())) { | |
| if (Cnt >= Start && Cnt < Start + Width) | |
| continue; | |
| if (isa<Instruction>(Candidates[Cnt])) | |
| LocalExternallyUsedValues.insert(Candidates[Cnt]); | |
| } | |
| V.transformNodes(); | |
| V.computeMinimumValueSizes(); | |
| InstructionCost TreeCost = | |
| V.calculateTreeCostAndTrimNonProfitable(VL, RdxRootInst); | |
| V.buildExternalUses(LocalExternallyUsedValues); | |
| InstructionCost VectorCost, RdxOpCost; | |
| InstructionCost ReductionCost = getReductionCost( | |
| TTI, VL, /*TrackedToOrig=*/{}, /*Pos=*/0, EmptySameValuesCounter, | |
| /*IsCmpSelMinMax=*/false, RdxFMF, V, DT, DL, TLI, VectorCost, | |
| RdxOpCost); | |
| InstructionCost Cost = | |
| V.getTreeCost(TreeCost, VL, ReductionCost, RdxRootInst); | |
| LLVM_DEBUG(dbgs() << "SLP: Found cost = " << Cost | |
| << " for ordered reduction\n"); | |
| if (Cost > -V.getCostThreshold() || | |
| (Cost == -V.getCostThreshold() && V.getTreeSize() > 1)) { | |
| if (Cost.isValid()) | |
| V.getORE()->emit([&]() { | |
| return OptimizationRemarkMissed(SV_NAME, "HorSLPNotBeneficial", | |
| ReducedValsToOps.at(VL[0]).front()) | |
| << "Vectorizing ordered reduction is possible " | |
| << "but not beneficial with cost " << ore::NV("Cost", Cost) | |
| << " and threshold " | |
| << ore::NV("Threshold", -V.getCostThreshold()); | |
| }); | |
| V.analyzedReductionVals(VL); | |
| return false; | |
| } | |
| LLVM_DEBUG(dbgs() << "SLP: Vectorizing ordered reduction at cost:" << Cost | |
| << ". (HorRdx)\n"); | |
| V.getORE()->emit([&]() { | |
| return OptimizationRemark(SV_NAME, "VectorizedHorizontalReduction", | |
| ReducedValsToOps.at(VL[0]).front()) | |
| << "Vectorized ordered reduction with cost " | |
| << ore::NV("Cost", Cost) << " and with tree size " | |
| << ore::NV("TreeSize", V.getTreeSize()); | |
| }); | |
| Builder.setFastMathFlags(RdxFMF); | |
| SuccessRoot = V.vectorizeTree(LocalExternallyUsedValues, RdxRootInst); | |
| assert(SuccessRoot && "Expected vectorized tree"); | |
| SuccessStart = Start; | |
| SuccessWidth = Width; | |
| return true; | |
| }; | |
| // Interleaved window search - for each width W (shrinking), | |
| // try front-anchored [0, W) then back-anchored [N-W, N). | |
| unsigned N = Candidates.size(); | |
| ReduxWidth = N; | |
| if (!Opts.slp_vectorize_non_power_of_2 || !has_single_bit(ReduxWidth + 1)) | |
| ReduxWidth = GetVectorFactor(ReduxWidth); | |
| ReduxWidth = std::min(ReduxWidth, MaxElts); | |
| while (ReduxWidth >= ReductionLimit) { | |
| if (TryWindow(0, ReduxWidth)) | |
| break; | |
| unsigned BackStart = N - ReduxWidth; | |
| if (BackStart != 0 && TryWindow(BackStart, ReduxWidth)) | |
| break; | |
| ShrinkReduxWidth(); | |
| } | |
| if (!SuccessRoot) { | |
| for (ReductionOpsType &RdxOps : ReductionOps) | |
| for (Value *RdxOp : RdxOps) | |
| V.analyzedReductionRoot(cast<Instruction>(RdxOp)); | |
| return nullptr; | |
| } | |
| // Fold leading scalars [0, SuccessStart) into an accumulator. | |
| Type *DestTy = ReductionRoot->getType(); | |
| WeakTrackingVH VectorizedTree = nullptr; | |
| if (!all_of(RoundedLinks, equal_to(nullptr))) { | |
| // The round-trip links cannot be expressed by the reduction intrinsic: | |
| // fold the values into a scalar chain in the original order, extracting | |
| // the vectorized ones, and re-emit the round-trips between the folded | |
| // operations. The emitted chain matches the same pattern again, so its | |
| // operations are marked as analyzed to prevent repeated vectorization. | |
| for (unsigned Idx : seq<unsigned>(N)) { | |
| Value *RdxVal = Candidates[Idx]; | |
| Builder.SetCurrentDebugLocation( | |
| ReducedValsToOps.at(RdxVal).front()->getDebugLoc()); | |
| Value *NextVal = TrackedVals.at(RdxVal); | |
| if (Idx >= SuccessStart && Idx < SuccessStart + SuccessWidth) | |
| NextVal = | |
| Builder.CreateExtractElement(SuccessRoot, Idx - SuccessStart); | |
| if (!VectorizedTree) { | |
| VectorizedTree = NextVal; | |
| continue; | |
| } | |
| auto *Op = cast<Instruction>(createOp(Builder, RdxKind, VectorizedTree, | |
| NextVal, "op.rdx", ReductionOps)); | |
| V.analyzedReductionRoot(Op); | |
| VectorizedTree = Op; | |
| Instruction *Narrow = RoundedLinks[N - 1 - Idx]; | |
| if (!Narrow) | |
| continue; | |
| Instruction *NewNarrow = Narrow->clone(); | |
| NewNarrow->setOperand(0, VectorizedTree); | |
| Builder.Insert(NewNarrow); | |
| Instruction *NewWide = | |
| cast<Instruction>(*Narrow->user_begin())->clone(); | |
| NewWide->setOperand(0, NewNarrow); | |
| VectorizedTree = Builder.Insert(NewWide); | |
| } | |
| } else { | |
| for (Value *RdxVal : ArrayRef(Candidates).take_front(SuccessStart)) { | |
| Builder.SetCurrentDebugLocation( | |
| ReducedValsToOps.at(RdxVal).front()->getDebugLoc()); | |
| if (!VectorizedTree) | |
| VectorizedTree = TrackedVals.at(RdxVal); | |
| else | |
| VectorizedTree = | |
| createOp(Builder, RdxKind, VectorizedTree, TrackedVals.at(RdxVal), | |
| "op.rdx", ReductionOps); | |
| } | |
| // Emit ordered reduction for the vectorized window. The reduction only | |
| // applies to floating point types. | |
| assert(DestTy->isFPOrFPVectorTy() && | |
| SuccessRoot->getType()->isFPOrFPVectorTy() && | |
| "Expected floating point types for ordered reduction"); | |
| Builder.SetCurrentDebugLocation( | |
| cast<Instruction>(ReductionRoot)->getDebugLoc()); | |
| VectorizedTree = | |
| createSingleOp(Builder, *TTI, V.getCastContextHint(V.getRootNode()), | |
| SuccessRoot, /*Scale=*/1, | |
| /*IsSigned=*/false, DestTy, | |
| /*ReducedInTree=*/false, VectorizedTree); | |
| // Fold trailing scalars [SuccessStart+SuccessWidth, N). | |
| for (Value *RdxVal : | |
| ArrayRef(Candidates).drop_front(SuccessStart + SuccessWidth)) { | |
| Builder.SetCurrentDebugLocation( | |
| ReducedValsToOps.at(RdxVal).front()->getDebugLoc()); | |
| VectorizedTree = | |
| createOp(Builder, RdxKind, VectorizedTree, TrackedVals.at(RdxVal), | |
| "op.rdx", ReductionOps); | |
| } | |
| } | |
| ReductionRoot->replaceAllUsesWith(VectorizedTree); | |
| #ifndef NDEBUG | |
| SmallPtrSet<Value *, 4> IgnoreSet; | |
| for (ArrayRef<Value *> RdxOps : ReductionOps) | |
| IgnoreSet.insert_range(RdxOps); | |
| #endif | |
| for (ArrayRef<Value *> RdxOps : ReductionOps) { | |
| for (Value *Ignore : | |
| make_filter_range(RdxOps, [](Value *Ignore) { return Ignore; })) { | |
| #ifndef NDEBUG | |
| for (auto *U : Ignore->users()) { | |
| assert(IgnoreSet.count(U) && | |
| "All users must be either in the reduction ops list."); | |
| } | |
| #endif | |
| if (!Ignore->use_empty()) { | |
| Value *P = PoisonValue::get(Ignore->getType()); | |
| Ignore->replaceAllUsesWith(P); | |
| } | |
| } | |
| V.removeInstructionsAndOperands(RdxOps, {}); | |
| } | |
| return VectorizedTree; | |
| } | |
| private: | |
| /// Creates the reduction from the given \p Vec vector value with the given | |
| /// scale \p Scale and signedness \p IsSigned. | |
| Value *createSingleOp(IRBuilderBase &Builder, const TargetTransformInfo &TTI, | |
| TTI::CastContextHint Ctx, Value *Vec, unsigned Scale, | |
| bool IsSigned, Type *DestTy, bool ReducedInTree, | |
| Value *Start = nullptr) { | |
| Value *Rdx; | |
| if (ReducedInTree) { | |
| Rdx = Vec; | |
| } else if (auto *VecTy = dyn_cast<FixedVectorType>(DestTy); | |
| VecTy && Opts.slp_revec) { | |
| unsigned DestTyNumElements = getNumElements(VecTy); | |
| unsigned VF = getNumElements(Vec->getType()) / DestTyNumElements; | |
| assert(getNumElements(Vec->getType()) % DestTyNumElements == 0 && | |
| "Vec element count must be a multiple of DestTy element count"); | |
| // e.g. Consider vector reduce add. | |
| // Initial reduction is | |
| // clang-format off | |
| // %add0 = add <4 x i32> zeroinitializer, %A | |
| // %add1 = add <4 x i32> %add0, %B | |
| // | |
| // where, | |
| // %A = <a, b, c, d> | |
| // %B = <e, f, g, h> | |
| // %add1 = A + B = <a + e, b + f, c + g, d + h> | |
| // | |
| // After revectorization with VF=2 and Vec = <a, b, c, d, e, f, g, h>, | |
| // the reduction can be expressed as: | |
| // %A = shufflevector <8 x i32> %Vec, <8 x i32> poison, <4 x i32> <i32 0, i32 1, i32 2, i32 3> | |
| // %B = shufflevector <8 x i32> %Vec, <8 x i32> poison, <4 x i32> <i32 4, i32 5, i32 6, i32 7> | |
| // %add0 = add <4 x i32> zeroinitializer, %A | |
| // %add1 = add <4 x i32> %add0, %B | |
| // clang-format on | |
| Rdx = Start; | |
| for (auto I : seq<unsigned>(VF)) { | |
| auto Position = I * DestTyNumElements; | |
| Value *SubVec = | |
| createExtractVector(Builder, Vec, DestTyNumElements, Position); | |
| if (!Rdx) | |
| Rdx = SubVec; | |
| else | |
| Rdx = createOp(Builder, RdxKind, Rdx, SubVec, "rdx.op", ReductionOps); | |
| } | |
| } else { | |
| Rdx = emitReduction(Vec, Builder, &TTI, Ctx, DestTy, Start); | |
| } | |
| if (Rdx->getType() != DestTy) | |
| Rdx = Builder.CreateIntCast(Rdx, DestTy, IsSigned); | |
| // Improved analysis for add/fadd/xor reductions with same scale | |
| // factor for all operands of reductions. We can emit scalar ops for | |
| // them instead. | |
| if (Scale > 1) | |
| Rdx = emitScaleForReusedOps(Rdx, Builder, Scale); | |
| return Rdx; | |
| } | |
| /// The lane order of the zero tests by their bit position, shared between | |
| /// the cost evaluation and the emission of the boolean bitmask reduction. | |
| SmallVector<int> getBoolBitmaskPermMask(ArrayRef<Value *> VL, | |
| ArrayRef<Value *> TrackedToOrig, | |
| unsigned Pos, | |
| const BoUpSLP &R) const { | |
| // Narrowed leaves exist for unordered reductions only, where the tracked | |
| // original values are always available. | |
| assert(TrackedToOrig.size() >= Pos + VL.size() && | |
| "tracked original values must be available for all reduced values"); | |
| SmallVector<int> PermMask(VL.size(), PoisonMaskElem); | |
| for (auto [Idx, Val] : enumerate(VL)) | |
| PermMask[NarrowedLeafShifts.at(TrackedToOrig[Pos + Idx]).Shift] = | |
| R.findRootLaneForValue(Val); | |
| return PermMask; | |
| } | |
| /// Emits the boolean bitmask form of the narrowed-leaf or-reduction (a | |
| /// bitcast of the per-lane zero tests to an integer) if it covers the whole | |
| /// reduction in a single vector part and is cheaper than the shift and | |
| /// reduction sequence. Returns nullptr otherwise. | |
| Value *tryEmitBoolBitmaskRdx(IRBuilderBase &Builder, const BoUpSLP &R, | |
| const TargetTransformInfo &TTI, | |
| BoolBitmask Match, Value *VectorizedRoot, | |
| ArrayRef<Value *> VL, | |
| ArrayRef<Value *> TrackedToOrig, unsigned Pos, | |
| ArrayRef<Constant *> MaskConsts, | |
| FastMathFlags FMF) const { | |
| Type *WideTy = ReductionRoot->getType(); | |
| auto *NarrowVecTy = dyn_cast<VectorType>(VectorizedRoot->getType()); | |
| if (!NarrowVecTy) | |
| return nullptr; | |
| Type *NarrowTy = NarrowVecTy->getScalarType(); | |
| unsigned VF = getNumElements(NarrowVecTy); | |
| // Applies only when the whole reduction is a single vector part. | |
| if (NarrowedLeafShifts.size() != VL.size() || VF != VL.size() || | |
| !VectorValuesAndScales.empty() || | |
| !R.getRootNode().ReuseShuffleIndices.empty()) | |
| return nullptr; | |
| if (Match == BoolBitmask::None) | |
| return nullptr; | |
| SmallVector<int> PermMask = | |
| getBoolBitmaskPermMask(VL, TrackedToOrig, Pos, R); | |
| // Emit the bitmask form only if it is cheaper than the shift and | |
| // reduction sequence. | |
| const TTI::TargetCostKind CostKind = R.getCostKind(); | |
| auto *WideVecTy = cast<VectorType>(getWidenedType(WideTy, VF)); | |
| InstructionCost GenericCost = | |
| TTI.getExtendedReductionCost(Instruction::Or, /*IsUnsigned=*/true, | |
| WideTy, NarrowVecTy, FMF, CostKind) + | |
| getNarrowedLeafOpsCost(TTI, NarrowedLeafShifts, NarrowVecTy, WideVecTy, | |
| cast<Instruction>(ReductionRoot), CostKind); | |
| if (getBoolBitmaskCost(TTI, Match == BoolBitmask::NeedMask, NarrowTy, | |
| WideTy, VF, PermMask, ReductionRoot, | |
| CostKind) >= GenericCost) | |
| return nullptr; | |
| Value *Vec = VectorizedRoot; | |
| if (Match == BoolBitmask::NeedMask) { | |
| Vec = Builder.CreateAnd(Vec, ConstantVector::get(MaskConsts)); | |
| ++NumVectorInstructions; | |
| } | |
| // Reorder the lanes by their bit position and pack the zero tests into an | |
| // integer. | |
| if (!ShuffleVectorInst::isIdentityMask(PermMask, VF)) { | |
| Vec = Builder.CreateShuffleVector(Vec, PermMask); | |
| ++NumVectorInstructions; | |
| } | |
| if (!NarrowTy->isIntegerTy(1)) { | |
| Vec = Builder.CreateICmpNE(Vec, Constant::getNullValue(Vec->getType())); | |
| ++NumVectorInstructions; | |
| } | |
| Vec = Builder.CreateBitCast(Vec, Builder.getIntNTy(VF)); | |
| ++NumVectorInstructions; | |
| if (Vec->getType() != WideTy) { | |
| Vec = Builder.CreateIntCast(Vec, WideTy, /*isSigned=*/false); | |
| ++NumVectorInstructions; | |
| } | |
| return Vec; | |
| } | |
| /// Calculate the cost of a reduction. | |
| /// \p VectorCost receives the cost of the emitted vector code alone and | |
| /// \p RdxOpCost the cost of the reduction operation in it. | |
| InstructionCost getReductionCost( | |
| TargetTransformInfo *TTI, ArrayRef<Value *> ReducedVals, | |
| ArrayRef<Value *> TrackedToOrig, unsigned Pos, | |
| const SmallMapVector<Value *, unsigned, 16> SameValuesCounter, | |
| bool IsCmpSelMinMax, FastMathFlags FMF, const BoUpSLP &R, | |
| DominatorTree &DT, const DataLayout &DL, const TargetLibraryInfo &TLI, | |
| InstructionCost &VectorCost, InstructionCost &RdxOpCost) { | |
| const TTI::TargetCostKind CostKind = R.getCostKind(); | |
| Type *ScalarTy = ReducedVals.front()->getType(); | |
| unsigned ReduxWidth = ReducedVals.size(); | |
| FixedVectorType *VectorTy = R.getReductionType(); | |
| InstructionCost ScalarCost; | |
| VectorCost = 0; | |
| RdxOpCost = 0; | |
| // If all of the reduced values are constant, the vector cost is 0, since | |
| // the reduction value can be calculated at the compile time. | |
| bool AllConsts = allConstant(ReducedVals); | |
| // A reduced value may have been replaced by an extractelement while | |
| // vectorizing a previous subvector of the reduction. Such tracked values | |
| // are not present in ReducedValsToOps, so look through them to the | |
| // reduction operation that uses the replaced value. | |
| auto GetReductionOp = [&](Value *RdxVal) -> Instruction * { | |
| auto It = ReducedValsToOps.find(RdxVal); | |
| if (It != ReducedValsToOps.end()) | |
| return It->second.front(); | |
| for (User *U : RdxVal->users()) | |
| // For flattened fadd reductions the reduced value may be consumed by | |
| // an fsub/fneg chain link instead of an fadd. No reassoc/nsz gating | |
| // is needed: the result is only a cost context. | |
| if (getRdxKind(U) == RdxKind || | |
| (RdxKind == RecurKind::FAdd && | |
| (match(U, m_FSub(m_Value(), m_Value())) || | |
| match(U, m_FNeg(m_Value()))))) | |
| return cast<Instruction>(U); | |
| return cast<Instruction>(RdxVal); | |
| }; | |
| // The root is accumulated through the phi of its loop. | |
| const bool IsLoopCarriedRoot = | |
| RdxKind == RecurKind::FAdd && | |
| any_of(ReductionRoot->users(), [&](User *U) { | |
| auto *Phi = dyn_cast<PHINode>(U); | |
| return Phi && | |
| DT.dominates(Phi->getParent(), | |
| cast<Instruction>(ReductionRoot)->getParent()); | |
| }); | |
| auto EvaluateScalarCost = | |
| [&](function_ref<InstructionCost(Instruction *)> GenCostFn) { | |
| InstructionCost Cost = 0; | |
| // Scalar cost is repeated for N-1 elements. | |
| int Cnt = ReducedVals.size(); | |
| for (Instruction *RdxVal : make_isa_range<Instruction>(ReducedVals)) { | |
| Instruction *RdxOp = GetReductionOp(RdxVal); | |
| if (Cnt == 1) { | |
| unsigned SameValueCount = SameValuesCounter.lookup(RdxVal); | |
| // A loop-carried chain removes one operation per reduced value; | |
| // its last operation, the reduction root, folds the accumulator | |
| // phi back and is credited by the accumulation cost instead. | |
| unsigned NumRemoved = SameValueCount ? SameValueCount - 1 : 0; | |
| if (RdxKind == RecurKind::FAdd && IsLoopCarriedRoot && | |
| RdxOp != ReductionRoot) | |
| NumRemoved = SameValueCount ? SameValueCount : 1; | |
| Cost += NumRemoved * GenCostFn(RdxOp); | |
| break; | |
| } | |
| --Cnt; | |
| if (RdxVal->hasNUsesOrMore(IsCmpSelMinMax ? 3 : 2)) { | |
| unsigned SameValueCount = SameValuesCounter.lookup(RdxVal); | |
| Cost += (SameValueCount ? SameValueCount : 1) * GenCostFn(RdxOp); | |
| continue; | |
| } | |
| InstructionCost ScalarCost = 0; | |
| for (User *U : RdxVal->users()) { | |
| auto *RdxOp = cast<Instruction>(U); | |
| // The loop-carried root is not removed: the horizontal form | |
| // folds the accumulator phi back through it, the accumulator | |
| // form replaces it with the lane-wise operation, costed by | |
| // the accumulation cost. | |
| if (RdxKind == RecurKind::FAdd && RdxOp == ReductionRoot && | |
| IsLoopCarriedRoot) | |
| continue; | |
| // The reduction root is used outside of the reduction any | |
| // number of times, its fmul operand is still fused into it. | |
| if ((RdxKind == RecurKind::FAdd && RdxOp == ReductionRoot) || | |
| hasRequiredNumberOfUses(IsCmpSelMinMax, RdxOp)) { | |
| InstructionsState RdxOpS = RdxKind == RecurKind::FAdd | |
| ? getSameOpcode(RdxOp, TLI) | |
| : InstructionsState::invalid(); | |
| // The reduced value itself must be a fusable fmul, whose cost | |
| // is excluded. | |
| if (RdxOpS && RdxOpS.isAddSubOrFNegLikeOp() && | |
| isFusableFMulOperand(RdxVal, RdxOp, *TTI)) { | |
| InstructionCost FMACost = | |
| canConvertToFMA(RdxOp, RdxOpS, DT, DL, *TTI, TLI, R); | |
| if (FMACost.isValid()) { | |
| LLVM_DEBUG(dbgs() << "FMA cost: " << FMACost << "\n"); | |
| // Also, exclude scalar fmul cost. | |
| InstructionCost FMulCost = | |
| TTI->getInstructionCost(RdxVal, CostKind); | |
| LLVM_DEBUG(dbgs() | |
| << "Minus FMul cost: " << FMulCost << "\n"); | |
| FMACost -= FMulCost; | |
| ScalarCost += FMACost; | |
| continue; | |
| } | |
| } | |
| ScalarCost += TTI->getInstructionCost(RdxOp, CostKind); | |
| continue; | |
| } | |
| ScalarCost = InstructionCost::getInvalid(); | |
| break; | |
| } | |
| if (ScalarCost.isValid()) | |
| Cost += ScalarCost; | |
| else | |
| Cost += GenCostFn(RdxOp); | |
| } | |
| return Cost; | |
| }; | |
| // Require reduction cost if: | |
| // 1. This type is not a full register type and no other vectors with the | |
| // same type in the storage (first vector with small type). | |
| // 2. The storage does not have any vector with full vector use (first | |
| // vector with full register use). | |
| bool DoesRequireReductionOp = !AllConsts && VectorValuesAndScales.empty(); | |
| switch (RdxKind) { | |
| case RecurKind::Add: | |
| case RecurKind::Mul: | |
| case RecurKind::Or: | |
| case RecurKind::And: | |
| case RecurKind::Xor: | |
| case RecurKind::FAdd: | |
| case RecurKind::FMul: { | |
| unsigned RdxOpcode = RecurrenceDescriptor::getOpcode(RdxKind); | |
| Type *BoolReduxWideTy = | |
| getBoolReduxWideTy(RdxKind, ReductionRoot->getType(), ScalarTy); | |
| if (!AllConsts) { | |
| if (!NarrowedLeafShifts.empty()) { | |
| Type *WideTy = ReductionRoot->getType(); | |
| auto *NarrowVecTy = | |
| cast<VectorType>(getWidenedType(ScalarTy, ReduxWidth)); | |
| auto *WideVecTy = | |
| cast<VectorType>(getWidenedType(WideTy, ReduxWidth)); | |
| if (DoesRequireReductionOp) { | |
| VectorCost = TTI->getExtendedReductionCost( | |
| RdxOpcode, /*IsUnsigned=*/true, WideTy, NarrowVecTy, FMF, | |
| CostKind); | |
| } else { | |
| VectorCost = | |
| TTI->getCastInstrCost(Instruction::ZExt, WideVecTy, NarrowVecTy, | |
| TTI::CastContextHint::None, CostKind) + | |
| TTI->getArithmeticInstrCost(RdxOpcode, WideVecTy, CostKind); | |
| } | |
| for (Instruction *I : NarrowedChainInsts) | |
| VectorCost -= TTI->getInstructionCost(I, CostKind); | |
| VectorCost += getNarrowedLeafOpsCost( | |
| *TTI, NarrowedLeafShifts, NarrowVecTy, WideVecTy, | |
| cast<Instruction>(ReductionRoot), CostKind); | |
| // The boolean bitmask emission may be cheaper. Restricted to the | |
| // first vector part, same as in the emission. | |
| BoolBitmask Match = | |
| VectorValuesAndScales.empty() && | |
| NarrowedLeafShifts.size() == ReduxWidth && | |
| R.getRootNode().getVectorFactor() == ReduxWidth && | |
| R.getRootNode().ReuseShuffleIndices.empty() | |
| ? isBoolBitmaskRdx(RdxKind, NarrowedLeafShifts, DL) | |
| : BoolBitmask::None; | |
| if (Match != BoolBitmask::None) { | |
| SmallVector<int> PermMask = | |
| getBoolBitmaskPermMask(ReducedVals, TrackedToOrig, Pos, R); | |
| InstructionCost BitmaskCost = getBoolBitmaskCost( | |
| *TTI, Match == BoolBitmask::NeedMask, ScalarTy, WideTy, | |
| ReduxWidth, PermMask, ReductionRoot, CostKind); | |
| for (Instruction *I : NarrowedChainInsts) | |
| BitmaskCost -= TTI->getInstructionCost(I, CostKind); | |
| VectorCost = std::min(VectorCost, BitmaskCost); | |
| } | |
| } else if (DoesRequireReductionOp) { | |
| if (auto *VecTy = dyn_cast<FixedVectorType>(ScalarTy)) { | |
| assert(Opts.slp_revec && "FixedVectorType is not expected."); | |
| unsigned ScalarTyNumElements = VecTy->getNumElements(); | |
| for (unsigned I : seq<unsigned>(ReducedVals.size())) { | |
| VectorCost += | |
| getShuffleCost(*TTI, TTI::SK_ExtractSubvector, VectorTy, | |
| CostKind, {}, I * ScalarTyNumElements, VecTy); | |
| } | |
| // We get one less arithmetic instruction compared to number of | |
| // reduced values. We are also passing CtxI so that the backend can | |
| // synthesize FMF from the respective reduction instruction. | |
| for (unsigned I : seq<unsigned>(ReducedVals.size() - 1)) { | |
| VectorCost += TTI->getArithmeticInstrCost( | |
| RdxOpcode, VecTy, CostKind, | |
| TTI::getOperandInfo(ReducedVals[I]), | |
| TTI::getOperandInfo(ReducedVals[I + 1]), {}, | |
| GetReductionOp(ReducedVals[I])); | |
| } | |
| } else { | |
| Type *RedTy = VectorTy->getElementType(); | |
| auto [RType, IsSigned] = R.getRootNodeTypeWithNoCast().value_or( | |
| std::make_pair(RedTy, true)); | |
| if (BoolReduxWideTy) { | |
| auto *WideVecTy = cast<FixedVectorType>( | |
| getWidenedType(BoolReduxWideTy, ReduxWidth)); | |
| VectorCost = getBoolReduxWideRdxCost( | |
| *TTI, RdxKind, WideVecTy, ReductionRoot, FMF, CostKind); | |
| // The trunc+bitcast+cmp form applies only when the whole | |
| // reduction is a single vector part, without scalar remainders. | |
| if (this->ReducedVals.size() == 1 && | |
| ReducedVals.size() == this->ReducedVals.front().size()) | |
| VectorCost = std::min( | |
| VectorCost, getBoolReduxBitcastCmpCost( | |
| *TTI, RdxKind, WideVecTy, ReductionRoot, | |
| NarrowedChainInsts, CostKind)); | |
| } else if (RType == RedTy) { | |
| if (VectorTy->getElementType()->isIntegerTy(1) && | |
| ((ScalarTy->isIntegerTy(1) && | |
| (RdxKind == RecurKind::And || RdxKind == RecurKind::Or)) || | |
| (RdxKind == RecurKind::Add && !ScalarTy->isIntegerTy(1)))) { | |
| VectorCost = | |
| getI1ReductionCost(RdxKind, *TTI, VectorTy, ScalarTy, | |
| R.getCastContextHint(R.getRootNode()), | |
| CostKind) | |
| .first; | |
| } else { | |
| VectorCost = TTI->getArithmeticReductionCost( | |
| RdxOpcode, VectorTy, FMF, CostKind); | |
| } | |
| } else { | |
| VectorCost = TTI->getExtendedReductionCost( | |
| RdxOpcode, !IsSigned, RedTy, | |
| cast<VectorType>(getWidenedType(RType, ReduxWidth)), FMF, | |
| CostKind); | |
| } | |
| RdxOpCost = VectorCost; | |
| } | |
| } else { | |
| Type *RedTy = VectorTy->getElementType(); | |
| auto [RType, IsSigned] = R.getRootNodeTypeWithNoCast().value_or( | |
| std::make_pair(RedTy, true)); | |
| VectorType *RVecTy = | |
| cast<VectorType>(getWidenedType(RType, ReduxWidth)); | |
| InstructionCost FMACost = InstructionCost::getInvalid(); | |
| if (RdxKind == RecurKind::FAdd) { | |
| // Check if the reduction operands can be converted to FMA. | |
| SmallVector<Value *> Ops; | |
| FastMathFlags FMF; | |
| FMF.set(); | |
| for (Value *RdxVal : ReducedVals) { | |
| // The vector fmul fuses only if all the reduced values are | |
| // fusable fmuls. | |
| if (!RdxVal->hasOneUse() || | |
| !isFusableFMulOperand( | |
| RdxVal, cast<Instruction>(RdxVal->user_back()), *TTI)) { | |
| Ops.clear(); | |
| break; | |
| } | |
| if (auto *FPCI = dyn_cast<FPMathOperator>(RdxVal)) | |
| FMF &= FPCI->getFastMathFlags(); | |
| Ops.push_back(RdxVal->user_back()); | |
| } | |
| if (!Ops.empty()) { | |
| InstructionsState S = getSameOpcode(Ops, TLI); | |
| if (S && S.isAddSubOrFNegLikeOp()) | |
| FMACost = canConvertToFMA(Ops, S, DT, DL, *TTI, TLI, R); | |
| if (FMACost.isValid()) { | |
| // Calculate actual FMAD cost. | |
| IntrinsicCostAttributes ICA(Intrinsic::fmuladd, RVecTy, | |
| {RVecTy, RVecTy, RVecTy}, FMF); | |
| FMACost = TTI->getIntrinsicInstrCost(ICA, CostKind); | |
| LLVM_DEBUG(dbgs() << "Vector FMA cost: " << FMACost << "\n"); | |
| // Also, exclude vector fmul cost. | |
| InstructionCost FMulCost = TTI->getArithmeticInstrCost( | |
| Instruction::FMul, RVecTy, CostKind); | |
| LLVM_DEBUG(dbgs() | |
| << "Minus vector FMul cost: " << FMulCost << "\n"); | |
| FMACost -= FMulCost; | |
| } | |
| } | |
| } | |
| if (FMACost.isValid()) | |
| VectorCost += FMACost; | |
| else | |
| VectorCost += | |
| TTI->getArithmeticInstrCost(RdxOpcode, RVecTy, CostKind); | |
| if (RType != RedTy) { | |
| unsigned Opcode = Instruction::Trunc; | |
| if (RedTy->getScalarSizeInBits() > RType->getScalarSizeInBits()) | |
| Opcode = IsSigned ? Instruction::SExt : Instruction::ZExt; | |
| VectorCost += TTI->getCastInstrCost( | |
| Opcode, VectorTy, RVecTy, TTI::CastContextHint::None, CostKind); | |
| } | |
| } | |
| } | |
| Type *ScalarCostTy = | |
| !NarrowedLeafShifts.empty() ? ReductionRoot->getType() : ScalarTy; | |
| ScalarCost = EvaluateScalarCost([&](Instruction *RdxOp) { | |
| // A flattened fneg chain link is removed together with the fadd/fsub | |
| // consuming it - cost both. Also, RdxOp may be the reduced value | |
| // itself, if the actual reduction operation was not found - guard the | |
| // second operand access. | |
| if (RdxOp->isUnaryOp()) | |
| return TTI->getArithmeticInstrCost( | |
| RdxOp->getOpcode(), ScalarCostTy, CostKind, | |
| TTI::getOperandInfo(RdxOp->getOperand(0)), {}, {}, RdxOp) + | |
| TTI->getArithmeticInstrCost(RdxOpcode, ScalarCostTy, CostKind); | |
| TTI::OperandValueInfo Op2Info; | |
| if (RdxOp->getNumOperands() > 1) | |
| Op2Info = TTI::getOperandInfo(RdxOp->getOperand(1)); | |
| return TTI->getArithmeticInstrCost( | |
| RdxOpcode, ScalarCostTy, CostKind, | |
| TTI::getOperandInfo(RdxOp->getOperand(0)), Op2Info, {}, RdxOp); | |
| }); | |
| // Sign-flipped combines are emitted as fsubs but costed as fadds: | |
| // same op count as the unflattened chain, same cost. | |
| break; | |
| } | |
| case RecurKind::FMax: | |
| case RecurKind::FMin: | |
| case RecurKind::FMaximum: | |
| case RecurKind::FMinimum: | |
| case RecurKind::SMax: | |
| case RecurKind::SMin: | |
| case RecurKind::UMax: | |
| case RecurKind::UMin: { | |
| Intrinsic::ID Id = getMinMaxReductionIntrinsicOp(RdxKind); | |
| if (!AllConsts) { | |
| if (DoesRequireReductionOp) { | |
| VectorCost = TTI->getMinMaxReductionCost(Id, VectorTy, FMF, CostKind); | |
| RdxOpCost = VectorCost; | |
| } else { | |
| // Check if the previous reduction already exists and account it as | |
| // series of operations + single reduction. | |
| Type *RedTy = VectorTy->getElementType(); | |
| auto [RType, IsSigned] = R.getRootNodeTypeWithNoCast().value_or( | |
| std::make_pair(RedTy, true)); | |
| VectorType *RVecTy = | |
| cast<VectorType>(getWidenedType(RType, ReduxWidth)); | |
| IntrinsicCostAttributes ICA(Id, RVecTy, {RVecTy, RVecTy}, FMF); | |
| VectorCost += TTI->getIntrinsicInstrCost(ICA, CostKind); | |
| if (RType != RedTy) { | |
| unsigned Opcode = Instruction::Trunc; | |
| if (RedTy->getScalarSizeInBits() > RType->getScalarSizeInBits()) | |
| Opcode = IsSigned ? Instruction::SExt : Instruction::ZExt; | |
| VectorCost += TTI->getCastInstrCost( | |
| Opcode, VectorTy, RVecTy, TTI::CastContextHint::None, CostKind); | |
| } | |
| RdxOpCost = VectorCost; | |
| } | |
| } | |
| ScalarCost = EvaluateScalarCost([&](Instruction *RdxOp) { | |
| IntrinsicCostAttributes ICA(Id, ScalarTy, {ScalarTy, ScalarTy}, FMF); | |
| return TTI->getIntrinsicInstrCost(ICA, CostKind); | |
| }); | |
| break; | |
| } | |
| default: | |
| llvm_unreachable("Expected arithmetic or min/max reduction operation"); | |
| } | |
| LLVM_DEBUG(dbgs() << "SLP: Adding cost " << VectorCost - ScalarCost | |
| << " for reduction of " << shortBundleName(ReducedVals) | |
| << " (It is a splitting reduction)\n"); | |
| return VectorCost - ScalarCost; | |
| } | |
| /// Splits the values, stored in VectorValuesAndScales, into registers/free | |
| /// sub-registers, combines them with the given reduction operation as a | |
| /// vector operation and then performs single (small enough) reduction. | |
| /// The parts, marked as negated, are combined with the vector fsub operation | |
| /// (which may form per-lane fma with the multiplications in the reduced | |
| /// values). \returns the combined value and a flag set if it represents a | |
| /// negated result (all parts negated) and must be subtracted in the final | |
| /// combine. | |
| std::pair<Value *, bool> emitReduction(IRBuilderBase &Builder, | |
| const TargetTransformInfo &TTI, | |
| TTI::CastContextHint Ctx, | |
| Type *DestTy) { | |
| Value *ReducedSubTree = nullptr; | |
| bool ResNegated = false; | |
| // Combines the accumulated value \p Acc (with the sign \p AccNegated) | |
| // with \p V (with the sign \p VNegated). Values with different signs are | |
| // combined via fsub, subtracting the negated one from the positive one, so | |
| // the result is positive. If \p KeepAccSign is set (the result is inserted | |
| // back into the wider \p Acc), the result keeps the sign of \p Acc | |
| // instead: with the negated Acc it is Acc - V == -(V - Acc). | |
| // \returns the combined value and its sign. | |
| auto CombineOps = [&](Value *Acc, bool AccNegated, Value *V, bool VNegated, | |
| const Twine &Name, | |
| bool KeepAccSign) -> std::pair<Value *, bool> { | |
| if (AccNegated == VNegated) | |
| return {createOp(Builder, RdxKind, Acc, V, Name, ReductionOps), | |
| AccNegated}; | |
| assert(RdxKind == RecurKind::FAdd && | |
| "Negated parts expected in the fadd reductions only"); | |
| if (AccNegated && !KeepAccSign) | |
| std::swap(Acc, V); | |
| return {createOp(Builder, RecurKind::FSub, Acc, V, Name, ReductionOps), | |
| AccNegated && KeepAccSign}; | |
| }; | |
| // Creates reduction and combines with the previous reduction, respecting | |
| // the signs of the operands. | |
| auto CreateSingleOp = [&](Value *Vec, unsigned Scale, bool IsSigned, | |
| bool ReducedInTree, bool Negated) { | |
| Value *Rdx = createSingleOp(Builder, TTI, Ctx, Vec, Scale, IsSigned, | |
| DestTy, ReducedInTree); | |
| if (!ReducedSubTree) { | |
| ReducedSubTree = Rdx; | |
| ResNegated = Negated; | |
| return; | |
| } | |
| std::tie(ReducedSubTree, ResNegated) = | |
| CombineOps(ReducedSubTree, ResNegated, Rdx, Negated, "op.rdx", | |
| /*KeepAccSign=*/false); | |
| }; | |
| if (VectorValuesAndScales.size() == 1) { | |
| const ReductionVectorPart &P = VectorValuesAndScales.front(); | |
| CreateSingleOp(P.Vec, P.Scale, P.IsSigned, P.ReducedInTree, P.Negated); | |
| return {ReducedSubTree, ResNegated}; | |
| } | |
| // Scales Vec using given Cnt scale factor and then performs vector combine | |
| // with previous value of VecOp. | |
| Value *VecRes = nullptr; | |
| bool VecResSignedness = false; | |
| bool VecResNegated = false; | |
| auto CreateVecOp = [&](Value *Vec, unsigned Cnt, bool IsSigned, | |
| bool ReducedInTree, bool Negated) { | |
| if (ReducedInTree) { | |
| CreateSingleOp(Vec, Cnt, IsSigned, ReducedInTree, Negated); | |
| return; | |
| } | |
| Type *ScalarTy = Vec->getType()->getScalarType(); | |
| // Scale Vec using given Cnt scale factor. | |
| if (Cnt > 1) { | |
| ElementCount EC = cast<VectorType>(Vec->getType())->getElementCount(); | |
| switch (RdxKind) { | |
| case RecurKind::Add: { | |
| if (ScalarTy == Builder.getInt1Ty() && ScalarTy != DestTy) { | |
| unsigned VF = getNumElements(Vec->getType()); | |
| LLVM_DEBUG(dbgs() << "SLP: ctpop " << Cnt << "of " << Vec | |
| << ". (HorRdx)\n"); | |
| SmallVector<int> Mask(Cnt * VF, PoisonMaskElem); | |
| for (unsigned I : seq<unsigned>(Cnt)) | |
| std::iota(std::next(Mask.begin(), VF * I), | |
| std::next(Mask.begin(), VF * (I + 1)), 0); | |
| ++NumVectorInstructions; | |
| Vec = Builder.CreateShuffleVector(Vec, Mask); | |
| break; | |
| } | |
| // res = mul vv, n | |
| if (ScalarTy != DestTy->getScalarType()) | |
| Vec = Builder.CreateIntCast( | |
| Vec, getWidenedType(DestTy, getNumElements(Vec->getType())), | |
| IsSigned); | |
| Value *Scale = ConstantVector::getSplat( | |
| EC, ConstantInt::get(DestTy->getScalarType(), Cnt)); | |
| LLVM_DEBUG(dbgs() << "SLP: Add (to-mul) " << Cnt << "of " << Vec | |
| << ". (HorRdx)\n"); | |
| ++NumVectorInstructions; | |
| Vec = Builder.CreateMul(Vec, Scale); | |
| break; | |
| } | |
| case RecurKind::Xor: { | |
| // res = n % 2 ? 0 : vv | |
| LLVM_DEBUG(dbgs() | |
| << "SLP: Xor " << Cnt << "of " << Vec << ". (HorRdx)\n"); | |
| if (Cnt % 2 == 0) | |
| Vec = Constant::getNullValue(Vec->getType()); | |
| break; | |
| } | |
| case RecurKind::FAdd: { | |
| // res = fmul v, n | |
| Value *Scale = | |
| ConstantVector::getSplat(EC, ConstantFP::get(ScalarTy, Cnt)); | |
| LLVM_DEBUG(dbgs() << "SLP: FAdd (to-fmul) " << Cnt << "of " << Vec | |
| << ". (HorRdx)\n"); | |
| ++NumVectorInstructions; | |
| Vec = Builder.CreateFMul(Vec, Scale); | |
| break; | |
| } | |
| case RecurKind::And: | |
| case RecurKind::Or: | |
| case RecurKind::SMax: | |
| case RecurKind::SMin: | |
| case RecurKind::UMax: | |
| case RecurKind::UMin: | |
| case RecurKind::FMax: | |
| case RecurKind::FMin: | |
| case RecurKind::FMaximum: | |
| case RecurKind::FMinimum: | |
| // res = vv | |
| break; | |
| case RecurKind::Sub: | |
| case RecurKind::FSub: | |
| case RecurKind::FAddChainWithSubs: | |
| case RecurKind::AddChainWithSubs: | |
| case RecurKind::Mul: | |
| case RecurKind::FMul: | |
| case RecurKind::FMulAdd: | |
| case RecurKind::AnyOf: | |
| case RecurKind::FindIV: | |
| case RecurKind::FindLast: | |
| case RecurKind::FMaxNum: | |
| case RecurKind::FMinNum: | |
| case RecurKind::FMaximumNum: | |
| case RecurKind::FMinimumNum: | |
| case RecurKind::None: | |
| llvm_unreachable("Unexpected reduction kind for repeated scalar."); | |
| } | |
| } | |
| // Combine Vec with the previous VecOp. | |
| if (!VecRes) { | |
| VecRes = Vec; | |
| VecResSignedness = IsSigned; | |
| VecResNegated = Negated; | |
| } else { | |
| ++NumVectorInstructions; | |
| if (ScalarTy == Builder.getInt1Ty() && ScalarTy != DestTy && | |
| VecRes->getType()->getScalarType() == Builder.getInt1Ty()) { | |
| // Handle ctpop. | |
| unsigned VecResVF = getNumElements(VecRes->getType()); | |
| unsigned VecVF = getNumElements(Vec->getType()); | |
| SmallVector<int> Mask(VecResVF + VecVF, PoisonMaskElem); | |
| std::iota(Mask.begin(), Mask.end(), 0); | |
| // Ensure that VecRes is always larger than Vec | |
| if (VecResVF < VecVF) { | |
| std::swap(VecRes, Vec); | |
| std::swap(VecResVF, VecVF); | |
| } | |
| if (VecResVF != VecVF) { | |
| SmallVector<int> ResizeMask(VecResVF, PoisonMaskElem); | |
| std::iota(ResizeMask.begin(), std::next(ResizeMask.begin(), VecVF), 0); | |
| Vec = Builder.CreateShuffleVector(Vec, ResizeMask); | |
| } | |
| VecRes = Builder.CreateShuffleVector(VecRes, Vec, Mask, "rdx.op"); | |
| return; | |
| } | |
| if (VecRes->getType()->getScalarType() != DestTy->getScalarType()) { | |
| assert(getNumElements(VecRes->getType()) % getNumElements(DestTy) == | |
| 0 && | |
| "Expected the number of elements in VecRes to be a multiple " | |
| "of the number of elements in DestTy"); | |
| VecRes = Builder.CreateIntCast( | |
| VecRes, | |
| getWidenedType(DestTy->getScalarType(), | |
| getNumElements(VecRes->getType())), | |
| VecResSignedness); | |
| } | |
| if (ScalarTy != DestTy->getScalarType()) | |
| Vec = Builder.CreateIntCast( | |
| Vec, | |
| getWidenedType(DestTy->getScalarType(), | |
| getNumElements(Vec->getType())), | |
| IsSigned); | |
| unsigned VecResVF = getNumElements(VecRes->getType()); | |
| unsigned VecVF = getNumElements(Vec->getType()); | |
| // Ensure that VecRes is always larger than Vec | |
| if (VecResVF < VecVF) { | |
| std::swap(VecRes, Vec); | |
| std::swap(VecResVF, VecVF); | |
| std::swap(VecResNegated, Negated); | |
| } | |
| // extract + op + insert | |
| Value *Op = VecRes; | |
| if (VecResVF != VecVF) | |
| Op = createExtractVector(Builder, VecRes, VecVF, /*Index=*/0); | |
| std::tie(Op, VecResNegated) = | |
| CombineOps(Op, VecResNegated, Vec, Negated, "rdx.op", | |
| /*KeepAccSign=*/VecResVF != VecVF); | |
| if (VecResVF != VecVF) | |
| Op = createInsertVector(Builder, VecRes, Op, /*Index=*/0); | |
| VecRes = Op; | |
| } | |
| }; | |
| // Emit the positive parts first, so the combined vector value stays | |
| // positive when there is at least one positive part. | |
| for (bool Negated : {false, true}) | |
| for (const ReductionVectorPart &P : VectorValuesAndScales) | |
| if (P.Negated == Negated) | |
| CreateVecOp(P.Vec, P.Scale, P.IsSigned, P.ReducedInTree, P.Negated); | |
| // All parts may be reduced in the tree already, leaving no vector value | |
| // to reduce. | |
| if (VecRes) | |
| CreateSingleOp(VecRes, /*Scale=*/1, /*IsSigned=*/false, | |
| /*ReducedInTree=*/false, VecResNegated); | |
| return {ReducedSubTree, ResNegated}; | |
| } | |
| /// Emits an i1 reduction in the cheaper of the plain and the bitcast-based | |
| /// form. Returns nullptr if not an i1 reduction handled here. | |
| Value *emitI1Reduction(Value *VectorizedValue, IRBuilderBase &Builder, | |
| const TargetTransformInfo &TTI, | |
| TTI::CastContextHint Ctx, Type *DestTy, Value *Start) { | |
| auto *FTy = cast<FixedVectorType>(VectorizedValue->getType()); | |
| bool IsAdd = RdxKind == RecurKind::Add && | |
| DestTy->getScalarType() != FTy->getScalarType(); | |
| bool IsBoolLogic = | |
| (RdxKind == RecurKind::And || RdxKind == RecurKind::Or) && | |
| DestTy->getScalarType() == FTy->getScalarType(); | |
| if (Start || !FTy->getScalarType()->isIntegerTy(1) || | |
| (!IsAdd && !IsBoolLogic)) | |
| return nullptr; | |
| if (getI1ReductionCost( | |
| RdxKind, TTI, FTy, DestTy->getScalarType(), Ctx, | |
| getSLPCostKind(Builder.GetInsertBlock()->getParent())) | |
| .second) { | |
| // Convert vector_reduce_and/or(<n x i1>) to icmp eq/ne(bitcast <n x i1> | |
| // to in) and vector_reduce_add(ZExt(<n x i1>)) to | |
| // ZExtOrTrunc(ctpop(bitcast <n x i1> to in)). | |
| Value *V = Builder.CreateBitCast( | |
| VectorizedValue, Builder.getIntNTy(FTy->getNumElements())); | |
| ++NumVectorInstructions; | |
| switch (RdxKind) { | |
| case RecurKind::And: | |
| return Builder.CreateICmpEQ(V, | |
| ConstantInt::getAllOnesValue(V->getType())); | |
| case RecurKind::Or: | |
| return Builder.CreateIsNotNull(V); | |
| case RecurKind::Add: | |
| return Builder.CreateUnaryIntrinsic(Intrinsic::ctpop, V); | |
| default: | |
| llvm_unreachable("Unexpected reduction kind for the i1 bitcast form"); | |
| } | |
| } | |
| if (IsAdd) { | |
| // Keep the extended vector_reduce_add form. | |
| VectorizedValue = Builder.CreateZExt( | |
| VectorizedValue, | |
| getWidenedType(DestTy->getScalarType(), FTy->getNumElements())); | |
| ++NumVectorInstructions; | |
| } | |
| ++NumVectorInstructions; | |
| return createSimpleReduction(Builder, VectorizedValue, RdxKind); | |
| } | |
| /// Emit a horizontal reduction of the vectorized value. | |
| /// If \p Start is non-null, emit an ordered reduction intrinsic that | |
| /// sequentially accumulates into \p Start (only valid for FAdd/FMulAdd). | |
| Value *emitReduction(Value *VectorizedValue, IRBuilderBase &Builder, | |
| const TargetTransformInfo *TTI, TTI::CastContextHint Ctx, | |
| Type *DestTy, Value *Start = nullptr) { | |
| assert(VectorizedValue && "Need to have a vectorized tree node"); | |
| assert(RdxKind != RecurKind::FMulAdd && | |
| "A call to the llvm.fmuladd intrinsic is not handled yet"); | |
| if (Value *Rdx = | |
| emitI1Reduction(VectorizedValue, Builder, *TTI, Ctx, DestTy, Start)) | |
| return Rdx; | |
| ++NumVectorInstructions; | |
| if (Start) | |
| return createOrderedReduction(Builder, RdxKind, VectorizedValue, Start); | |
| return createSimpleReduction(Builder, VectorizedValue, RdxKind); | |
| } | |
| /// Emits optimized code for unique scalar value reused \p Cnt times. | |
| Value *emitScaleForReusedOps(Value *VectorizedValue, IRBuilderBase &Builder, | |
| unsigned Cnt) { | |
| assert(IsSupportedHorRdxIdentityOp && | |
| "The optimization of matched scalar identity horizontal reductions " | |
| "must be supported."); | |
| if (Cnt == 1) | |
| return VectorizedValue; | |
| switch (RdxKind) { | |
| case RecurKind::Add: { | |
| // res = mul vv, n | |
| Value *Scale = | |
| ConstantInt::get(VectorizedValue->getType(), Cnt, | |
| /*IsSigned=*/false, /*ImplicitTrunc=*/true); | |
| LLVM_DEBUG(dbgs() << "SLP: Add (to-mul) " << Cnt << "of " | |
| << VectorizedValue << ". (HorRdx)\n"); | |
| return Builder.CreateMul(VectorizedValue, Scale); | |
| } | |
| case RecurKind::Xor: { | |
| // res = n % 2 ? 0 : vv | |
| LLVM_DEBUG(dbgs() << "SLP: Xor " << Cnt << "of " << VectorizedValue | |
| << ". (HorRdx)\n"); | |
| if (Cnt % 2 == 0) | |
| return Constant::getNullValue(VectorizedValue->getType()); | |
| return VectorizedValue; | |
| } | |
| case RecurKind::FAdd: { | |
| // res = fmul v, n | |
| Value *Scale = ConstantFP::get(VectorizedValue->getType(), Cnt); | |
| LLVM_DEBUG(dbgs() << "SLP: FAdd (to-fmul) " << Cnt << "of " | |
| << VectorizedValue << ". (HorRdx)\n"); | |
| // The mul stands in for a run of the chain operations and joins the | |
| // same combines, so it carries the flags of the whole chain, except | |
| // contract: an fma candidate would only block the later vectorization | |
| // of the scaled value's operands (fma candidates are deferred). | |
| Value *Mul = createOp(Builder, RecurKind::FMul, VectorizedValue, Scale, | |
| "", ReductionOps); | |
| if (auto *MulI = dyn_cast<Instruction>(Mul)) | |
| MulI->setHasAllowContract(false); | |
| return Mul; | |
| } | |
| case RecurKind::And: | |
| case RecurKind::Or: | |
| case RecurKind::SMax: | |
| case RecurKind::SMin: | |
| case RecurKind::UMax: | |
| case RecurKind::UMin: | |
| case RecurKind::FMax: | |
| case RecurKind::FMin: | |
| case RecurKind::FMaximum: | |
| case RecurKind::FMinimum: | |
| // res = vv | |
| return VectorizedValue; | |
| case RecurKind::Sub: | |
| case RecurKind::FSub: | |
| case RecurKind::FAddChainWithSubs: | |
| case RecurKind::AddChainWithSubs: | |
| case RecurKind::Mul: | |
| case RecurKind::FMul: | |
| case RecurKind::FMulAdd: | |
| case RecurKind::AnyOf: | |
| case RecurKind::FindIV: | |
| case RecurKind::FindLast: | |
| case RecurKind::FMaxNum: | |
| case RecurKind::FMinNum: | |
| case RecurKind::FMaximumNum: | |
| case RecurKind::FMinimumNum: | |
| case RecurKind::None: | |
| llvm_unreachable("Unexpected reduction kind for repeated scalar."); | |
| } | |
| return nullptr; | |
| } | |
| /// Emits actual operation for the scalar identity values, found during | |
| /// horizontal reduction analysis. | |
| Value * | |
| emitReusedOps(Value *VectorizedValue, IRBuilderBase &Builder, BoUpSLP &R, | |
| const SmallMapVector<Value *, unsigned, 16> &SameValuesCounter, | |
| ArrayRef<Value *> TrackedToOrig) { | |
| assert(IsSupportedHorRdxIdentityOp && | |
| "The optimization of matched scalar identity horizontal reductions " | |
| "must be supported."); | |
| ArrayRef<Value *> VL = R.getRootNodeScalars(); | |
| auto *VTy = cast<FixedVectorType>(VectorizedValue->getType()); | |
| if (VTy->getElementType() != VL.front()->getType()) { | |
| VectorizedValue = Builder.CreateIntCast( | |
| VectorizedValue, | |
| getWidenedType(VL.front()->getType(), VTy->getNumElements()), | |
| R.isSignedMinBitwidthRootNode()); | |
| } | |
| switch (RdxKind) { | |
| case RecurKind::Add: { | |
| // root = mul prev_root, <1, 1, n, 1> | |
| SmallVector<Constant *> Vals; | |
| for (const auto [Idx, V] : enumerate(VL)) { | |
| unsigned Cnt = SameValuesCounter.lookup(TrackedToOrig[Idx]); | |
| // The repeat count may not fit the element type (e.g. i1); the | |
| // modular reduction arithmetic keeps the truncated count equivalent. | |
| Vals.push_back(ConstantInt::get( | |
| V->getType(), APInt(V->getType()->getIntegerBitWidth(), Cnt, | |
| /*isSigned=*/false, /*implicitTrunc=*/true))); | |
| } | |
| auto *Scale = ConstantVector::get(Vals); | |
| LLVM_DEBUG(dbgs() << "SLP: Add (to-mul) " << Scale << "of " | |
| << VectorizedValue << ". (HorRdx)\n"); | |
| return Builder.CreateMul(VectorizedValue, Scale); | |
| } | |
| case RecurKind::And: | |
| case RecurKind::Or: | |
| // No need for multiple or/and(s). | |
| LLVM_DEBUG(dbgs() << "SLP: And/or of same " << VectorizedValue | |
| << ". (HorRdx)\n"); | |
| return VectorizedValue; | |
| case RecurKind::SMax: | |
| case RecurKind::SMin: | |
| case RecurKind::UMax: | |
| case RecurKind::UMin: | |
| case RecurKind::FMax: | |
| case RecurKind::FMin: | |
| case RecurKind::FMaximum: | |
| case RecurKind::FMinimum: | |
| // No need for multiple min/max(s) of the same value. | |
| LLVM_DEBUG(dbgs() << "SLP: Max/min of same " << VectorizedValue | |
| << ". (HorRdx)\n"); | |
| return VectorizedValue; | |
| case RecurKind::Xor: { | |
| // Replace values with even number of repeats with 0, since | |
| // x xor x = 0. | |
| // root = shuffle prev_root, zeroinitalizer, <0, 1, 2, vf, 4, vf, 5, 6, | |
| // 7>, if elements 4th and 6th elements have even number of repeats. | |
| SmallVector<int> Mask( | |
| cast<FixedVectorType>(VectorizedValue->getType())->getNumElements(), | |
| PoisonMaskElem); | |
| std::iota(Mask.begin(), Mask.end(), 0); | |
| bool NeedShuffle = false; | |
| for (const auto [Idx, V] : enumerate(VL)) { | |
| unsigned Cnt = SameValuesCounter.lookup(TrackedToOrig[Idx]); | |
| if (Cnt % 2 == 0) { | |
| Mask[Idx] = VL.size(); | |
| NeedShuffle = true; | |
| } | |
| } | |
| LLVM_DEBUG(dbgs() << "SLP: Xor <"; for (int I | |
| : Mask) dbgs() | |
| << I << " "; | |
| dbgs() << "> of " << VectorizedValue << ". (HorRdx)\n"); | |
| if (NeedShuffle) | |
| VectorizedValue = Builder.CreateShuffleVector( | |
| VectorizedValue, | |
| ConstantVector::getNullValue(VectorizedValue->getType()), Mask); | |
| return VectorizedValue; | |
| } | |
| case RecurKind::FAdd: { | |
| // root = fmul prev_root, <1.0, 1.0, n.0, 1.0> | |
| SmallVector<Constant *> Vals; | |
| for (const auto [Idx, V] : enumerate(VL)) { | |
| unsigned Cnt = SameValuesCounter.lookup(TrackedToOrig[Idx]); | |
| Vals.push_back(ConstantFP::get(V->getType(), Cnt)); | |
| } | |
| auto *Scale = ConstantVector::get(Vals); | |
| return Builder.CreateFMul(VectorizedValue, Scale); | |
| } | |
| case RecurKind::Sub: | |
| case RecurKind::FSub: | |
| case RecurKind::FAddChainWithSubs: | |
| case RecurKind::AddChainWithSubs: | |
| case RecurKind::Mul: | |
| case RecurKind::FMul: | |
| case RecurKind::FMulAdd: | |
| case RecurKind::AnyOf: | |
| case RecurKind::FindIV: | |
| case RecurKind::FindLast: | |
| case RecurKind::FMaxNum: | |
| case RecurKind::FMinNum: | |
| case RecurKind::FMaximumNum: | |
| case RecurKind::FMinimumNum: | |
| case RecurKind::None: | |
| llvm_unreachable("Unexpected reduction kind for reused scalars."); | |
| } | |
| return nullptr; | |
| } | |
| }; | |
| } // end anonymous namespace | |
| /// Gets recurrence kind from the specified value. | |
| static RecurKind getRdxKind(Value *V) { | |
| return HorizontalReduction::getRdxKind(V); | |
| } | |
| static void findBuildAggregateRec(Instruction *LastInsertInst, | |
| TargetTransformInfo *TTI, | |
| SmallVectorImpl<Value *> &BuildVectorOpds, | |
| SmallVectorImpl<Value *> &InsertElts, | |
| unsigned OperandOffset, const BoUpSLP &R) { | |
| do { | |
| Value *InsertedOperand = LastInsertInst->getOperand(1); | |
| std::optional<unsigned> OperandIndex = | |
| getElementIndex(LastInsertInst, OperandOffset); | |
| if (!OperandIndex || R.isDeleted(LastInsertInst)) | |
| return; | |
| if (isa<InsertElementInst, InsertValueInst>(InsertedOperand)) { | |
| findBuildAggregateRec(cast<Instruction>(InsertedOperand), TTI, | |
| BuildVectorOpds, InsertElts, *OperandIndex, R); | |
| } else { | |
| BuildVectorOpds[*OperandIndex] = InsertedOperand; | |
| InsertElts[*OperandIndex] = LastInsertInst; | |
| } | |
| LastInsertInst = dyn_cast<Instruction>(LastInsertInst->getOperand(0)); | |
| } while (LastInsertInst != nullptr && | |
| isa<InsertValueInst, InsertElementInst>(LastInsertInst) && | |
| LastInsertInst->hasOneUse()); | |
| } | |
| /// Recognize construction of vectors like | |
| /// %ra = insertelement <4 x float> poison, float %s0, i32 0 | |
| /// %rb = insertelement <4 x float> %ra, float %s1, i32 1 | |
| /// %rc = insertelement <4 x float> %rb, float %s2, i32 2 | |
| /// %rd = insertelement <4 x float> %rc, float %s3, i32 3 | |
| /// starting from the last insertelement or insertvalue instruction. | |
| /// | |
| /// Also recognize homogeneous aggregates like {<2 x float>, <2 x float>}, | |
| /// {{float, float}, {float, float}}, [2 x {float, float}] and so on. | |
| /// See llvm/test/Transforms/SLPVectorizer/X86/pr42022.ll for examples. | |
| /// | |
| /// Assume LastInsertInst is of InsertElementInst or InsertValueInst type. | |
| /// | |
| /// \return true if it matches. | |
| static bool findBuildAggregate(Instruction *LastInsertInst, | |
| TargetTransformInfo *TTI, | |
| SmallVectorImpl<Value *> &BuildVectorOpds, | |
| SmallVectorImpl<Value *> &InsertElts, | |
| const BoUpSLP &R) { | |
| assert((isa<InsertElementInst>(LastInsertInst) || | |
| isa<InsertValueInst>(LastInsertInst)) && | |
| "Expected insertelement or insertvalue instruction!"); | |
| assert((BuildVectorOpds.empty() && InsertElts.empty()) && | |
| "Expected empty result vectors!"); | |
| std::optional<unsigned> AggregateSize = getAggregateSize(LastInsertInst); | |
| if (!AggregateSize) | |
| return false; | |
| BuildVectorOpds.resize(*AggregateSize); | |
| InsertElts.resize(*AggregateSize); | |
| findBuildAggregateRec(LastInsertInst, TTI, BuildVectorOpds, InsertElts, 0, R); | |
| llvm::erase(BuildVectorOpds, nullptr); | |
| llvm::erase(InsertElts, nullptr); | |
| if (BuildVectorOpds.size() >= 2) | |
| return true; | |
| return false; | |
| } | |
| /// We could have an initial reduction that is not an add. | |
| /// r *= v1 + v2 + v3 + v4 | |
| /// In such a case start looking for a tree rooted in the first '+'. | |
| /// \Returns the new root if found, which may be nullptr if not an instruction. | |
| static Instruction *tryGetSecondaryReductionRoot(PHINode *Phi, | |
| Instruction *Root) { | |
| assert((isa<BinaryOperator>(Root) || isa<SelectInst>(Root) || | |
| isa<IntrinsicInst>(Root)) && | |
| "Expected binop, select, or intrinsic for reduction matching"); | |
| Value *LHS = | |
| Root->getOperand(HorizontalReduction::getFirstOperandIndex(Root)); | |
| Value *RHS = | |
| Root->getOperand(HorizontalReduction::getFirstOperandIndex(Root) + 1); | |
| if (LHS == Phi) | |
| return dyn_cast<Instruction>(RHS); | |
| if (RHS == Phi) | |
| return dyn_cast<Instruction>(LHS); | |
| return nullptr; | |
| } | |
| bool SLPVectorizerPass::vectorizeHorReduction( | |
| PHINode *P, Instruction *Root, BasicBlock *BB, BoUpSLP &R, | |
| SmallVectorImpl<WeakTrackingVH> &PostponedInsts) { | |
| if (!Opts->slp_vectorize_hor) | |
| return false; | |
| bool TryOperandsAsNewSeeds = P && isa<BinaryOperator>(Root); | |
| if (Root->getParent() != BB || isa<PHINode>(Root)) | |
| return false; | |
| // If we can find a secondary reduction root, use that instead. | |
| auto SelectRoot = [&]() { | |
| if (TryOperandsAsNewSeeds && isReductionCandidate(Root) && | |
| HorizontalReduction::getRdxKind(Root) != RecurKind::None) | |
| if (Instruction *NewRoot = tryGetSecondaryReductionRoot(P, Root)) | |
| return NewRoot; | |
| return Root; | |
| }; | |
| // Start analysis starting from Root instruction. If horizontal reduction is | |
| // found, try to vectorize it. If it is not a horizontal reduction or | |
| // vectorization is not possible or not effective, and currently analyzed | |
| // instruction is a binary operation, try to vectorize the operands, using | |
| // pre-order DFS traversal order. If the operands were not vectorized, repeat | |
| // the same procedure considering each operand as a possible root of the | |
| // horizontal reduction. | |
| // Interrupt the process if the Root instruction itself was vectorized or all | |
| // sub-trees not higher that RecursionMaxDepth were analyzed/vectorized. | |
| // If a horizintal reduction was not matched or vectorized we collect | |
| // instructions for possible later attempts for vectorization. | |
| std::queue<std::pair<Instruction *, unsigned>> Stack; | |
| Stack.emplace(SelectRoot(), 0); | |
| SmallPtrSet<Value *, 8> VisitedInstrs; | |
| bool Res = false; | |
| auto TryToReduce = [this, &R, TTI = TTI](Instruction *Inst, | |
| bool IsSeedRoot) -> Value * { | |
| if (R.isAnalyzedReductionRoot(Inst)) | |
| return nullptr; | |
| if (!isReductionCandidate(Inst)) | |
| return nullptr; | |
| HorizontalReduction HorRdx(*Opts); | |
| Value *Res = nullptr; | |
| if (HorRdx.matchAssociativeReduction(R, Inst, *SE, *DT, *DL, *TTI, *TLI)) { | |
| Value *Red = | |
| HorRdx.tryToReduce(R, *DL, TTI, *TLI, AC, *DT, *LI, IsSeedRoot); | |
| // The chain with the flattened fsub/fneg links produced no vector part: | |
| // retry with the fsub/fneg links as leaves, they may be vectorizable on | |
| // their own. | |
| if (!Red && HorRdx.hasFlattenedNegations()) { | |
| HorizontalReduction UnflattenedHorRdx(*Opts); | |
| if (UnflattenedHorRdx.matchAssociativeReduction( | |
| R, Inst, *SE, *DT, *DL, *TTI, *TLI, /*FlattenNegations=*/false)) | |
| Red = UnflattenedHorRdx.tryToReduce(R, *DL, TTI, *TLI, AC, *DT, *LI, | |
| IsSeedRoot); | |
| } | |
| if (Red) { | |
| if (Red != Inst) | |
| return Red; | |
| Res = Red; | |
| } | |
| } | |
| if (HorizontalReduction::isSupportedOrderedReductionOp(Inst)) { | |
| if (HorRdx.matchOrderedReduction(R, Inst, /*MatchLHS=*/true)) | |
| if (Value *Red = | |
| HorRdx.tryToReduceOrdered(R, *DL, TTI, *TLI, AC, *DT)) { | |
| if (Red != Inst) | |
| return Red; | |
| Res = Red; | |
| } | |
| if (HorRdx.matchOrderedReduction(R, Inst, /*MatchLHS=*/false)) | |
| if (Value *Red = HorRdx.tryToReduceOrdered(R, *DL, TTI, *TLI, AC, *DT)) | |
| return Red; | |
| } | |
| return Res; | |
| }; | |
| auto TryAppendToPostponedInsts = [&](Instruction *FutureSeed) { | |
| if (TryOperandsAsNewSeeds && FutureSeed == Root) { | |
| FutureSeed = getNonPhiOperand(Root, P); | |
| if (!FutureSeed) | |
| return false; | |
| } | |
| // Do not collect CmpInst or InsertElementInst/InsertValueInst as their | |
| // analysis is done separately. | |
| if (!isa<CmpInst, InsertElementInst, InsertValueInst>(FutureSeed)) | |
| PostponedInsts.push_back(FutureSeed); | |
| return true; | |
| }; | |
| while (!Stack.empty()) { | |
| Instruction *Inst; | |
| unsigned Level; | |
| std::tie(Inst, Level) = Stack.front(); | |
| Stack.pop(); | |
| // Do not try to analyze instruction that has already been vectorized. | |
| // This may happen when we vectorize instruction operands on a previous | |
| // iteration while stack was populated before that happened. | |
| if (R.isDeleted(Inst)) | |
| continue; | |
| // The minimum-vector-factor allowance applies to single-use seed-level | |
| // roots only. | |
| if (Value *VectorizedV = | |
| TryToReduce(Inst, /*IsSeedRoot=*/Level == 0 && Inst->hasOneUse())) { | |
| Res = true; | |
| if (auto *I = dyn_cast<Instruction>(VectorizedV); I && I != Inst) { | |
| // Try to find another reduction. | |
| Stack.emplace(I, Level); | |
| continue; | |
| } | |
| if (R.isDeleted(Inst)) | |
| continue; | |
| } else { | |
| // We could not vectorize `Inst` so try to use it as a future seed. | |
| if (!TryAppendToPostponedInsts(Inst)) { | |
| assert(Stack.empty() && "Expected empty stack"); | |
| break; | |
| } | |
| } | |
| // Try to vectorize operands. | |
| // Continue analysis for the instruction from the same basic block only to | |
| // save compile time. | |
| if (++Level < Opts->slp_recursion_max_depth) | |
| for (auto *Op : Inst->operand_values()) | |
| if (VisitedInstrs.insert(Op).second) | |
| if (auto *I = dyn_cast<Instruction>(Op)) | |
| // Do not try to vectorize CmpInst operands, this is done | |
| // separately. | |
| if (!isa<PHINode, CmpInst, InsertElementInst, InsertValueInst>(I) && | |
| !R.isDeleted(I) && I->getParent() == BB) | |
| Stack.emplace(I, Level); | |
| } | |
| return Res; | |
| } | |
| bool SLPVectorizerPass::tryToVectorize( | |
| Instruction *I, BoUpSLP &R, SmallSetVector<Instruction *, 8> &FMACandidates, | |
| bool AllowFMACandidates) { | |
| if (!I) | |
| return false; | |
| if (!isa<BinaryOperator, CmpInst>(I) || isa<VectorType>(I->getType())) | |
| return false; | |
| // Skip potential FMA candidates and collect them for a retry after all | |
| // blocks of the function have been processed. | |
| if (!AllowFMACandidates && | |
| (I->getOpcode() == Instruction::FAdd || | |
| I->getOpcode() == Instruction::FSub) && | |
| canConvertToFMA(I, getSameOpcode(I, *TLI), *DT, *DL, *TTI, *TLI, R) | |
| .isValid()) { | |
| FMACandidates.insert(I); | |
| return false; | |
| } | |
| Value *P = I->getParent(); | |
| // Vectorize in current basic block only. | |
| auto *Op0 = dyn_cast<Instruction>(I->getOperand(0)); | |
| auto *Op1 = dyn_cast<Instruction>(I->getOperand(1)); | |
| if (!Op0 || !Op1 || Op0->getParent() != P || Op1->getParent() != P || | |
| R.isDeleted(Op0) || R.isDeleted(Op1)) | |
| return false; | |
| // First collect all possible candidates | |
| SmallVector<std::pair<Value *, Value *>, 4> Candidates; | |
| Candidates.emplace_back(Op0, Op1); | |
| auto *A = dyn_cast<BinaryOperator>(Op0); | |
| auto *B = dyn_cast<BinaryOperator>(Op1); | |
| // Try to skip B. | |
| if (A && B && B->hasOneUse()) { | |
| auto *B0 = dyn_cast<BinaryOperator>(B->getOperand(0)); | |
| auto *B1 = dyn_cast<BinaryOperator>(B->getOperand(1)); | |
| if (B0 && B0->getParent() == P && !R.isDeleted(B0)) | |
| Candidates.emplace_back(A, B0); | |
| if (B1 && B1->getParent() == P && !R.isDeleted(B1)) | |
| Candidates.emplace_back(A, B1); | |
| } | |
| // Try to skip A. | |
| if (B && A && A->hasOneUse()) { | |
| auto *A0 = dyn_cast<BinaryOperator>(A->getOperand(0)); | |
| auto *A1 = dyn_cast<BinaryOperator>(A->getOperand(1)); | |
| if (A0 && A0->getParent() == P && !R.isDeleted(A0)) | |
| Candidates.emplace_back(A0, B); | |
| if (A1 && A1->getParent() == P && !R.isDeleted(A1)) | |
| Candidates.emplace_back(A1, B); | |
| } | |
| auto TryToReduce = [this, &R, &TTI = *TTI](Instruction *Inst, | |
| ArrayRef<Value *> Ops) { | |
| if (!isReductionCandidate(Inst)) | |
| return false; | |
| // A 2-element reduction that is just a link in a larger reduction chain | |
| // or feeds a buildvector is better handled by the whole-chain reduction | |
| // or buildvector analysis. | |
| RecurKind Kind = getRdxKind(Inst); | |
| auto *FPMO = dyn_cast<FPMathOperator>(Inst); | |
| if (FPMO && !FPMO->hasAllowReassoc() && | |
| (any_of(Inst->users(), | |
| [Kind](User *U) { | |
| return getRdxKind(U) == Kind || isa<InsertElementInst>(U); | |
| }) || | |
| any_of(Inst->operands(), | |
| [Kind](Value *Op) { return getRdxKind(Op) == Kind; }))) | |
| return false; | |
| Type *Ty = Inst->getType(); | |
| if (!isValidElementType(Ty, Opts->slp_revec) || Ty->isPointerTy()) | |
| return false; | |
| HorizontalReduction HorRdx(*Opts, Inst, Ops); | |
| if (!HorRdx.matchReductionForOperands()) | |
| return false; | |
| // Check the cost of operations. | |
| auto *VecTy = cast<VectorType>(getWidenedType(Ty, Ops.size())); | |
| const TTI::TargetCostKind CostKind = R.getCostKind(); | |
| InstructionCost ScalarCost = | |
| TTI.getScalarizationOverhead( | |
| VecTy, APInt::getAllOnes(getNumElements(VecTy)), /*Insert=*/false, | |
| /*Extract=*/true, CostKind) + | |
| TTI.getInstructionCost(Inst, CostKind); | |
| // The reduction-only cost cannot price the compared-values tree of i1 | |
| // and/or reductions, so ties are left to the full analysis. | |
| if (Ty->isIntegerTy(1) && | |
| (Kind == RecurKind::And || Kind == RecurKind::Or)) { | |
| TTI::CastContextHint Ctx = | |
| all_of(Ops, [](Value *Op) { return isa<LoadInst>(Op); }) | |
| ? TTI::CastContextHint::Normal | |
| : TTI::CastContextHint::None; | |
| if (getI1ReductionCost(Kind, TTI, cast<FixedVectorType>(VecTy), Ty, Ctx, | |
| CostKind) | |
| .first > ScalarCost) | |
| return false; | |
| return HorRdx.tryToReduce(R, *DL, &TTI, *TLI, AC, *DT, *LI) != nullptr; | |
| } | |
| InstructionCost RedCost; | |
| switch (::getRdxKind(Inst)) { | |
| case RecurKind::Add: | |
| case RecurKind::Mul: | |
| case RecurKind::Or: | |
| case RecurKind::And: | |
| case RecurKind::Xor: | |
| case RecurKind::FAdd: | |
| case RecurKind::FMul: { | |
| FastMathFlags FMF; | |
| if (auto *FPCI = dyn_cast<FPMathOperator>(Inst)) { | |
| FMF = FPCI->getFastMathFlags(); | |
| // No need to check for associativity, if 2 reduced values. | |
| if (Ops.size() == 2) | |
| FMF.setAllowReassoc(true); | |
| } | |
| RedCost = TTI.getArithmeticReductionCost(Inst->getOpcode(), VecTy, FMF, | |
| CostKind); | |
| break; | |
| } | |
| default: | |
| return false; | |
| } | |
| if (RedCost >= ScalarCost) | |
| return false; | |
| return HorRdx.tryToReduce(R, *DL, &TTI, *TLI, AC, *DT, *LI) != nullptr; | |
| }; | |
| if (Candidates.size() == 1) | |
| return TryToReduce(I, {Op0, Op1}) || | |
| (!AllowFMACandidates && tryToVectorizeList({Op0, Op1}, R)); | |
| // We have multiple options. Try to pick the single best. | |
| std::optional<int> BestCandidate = R.findBestRootPair(Candidates).first; | |
| if (!BestCandidate) | |
| return false; | |
| return (*BestCandidate == 0 && | |
| TryToReduce(I, {Candidates[*BestCandidate].first, | |
| Candidates[*BestCandidate].second})) || | |
| (!AllowFMACandidates && | |
| tryToVectorizeList({Candidates[*BestCandidate].first, | |
| Candidates[*BestCandidate].second}, | |
| R)); | |
| } | |
| bool SLPVectorizerPass::vectorizeRootInstruction( | |
| PHINode *P, Instruction *Root, BasicBlock *BB, BoUpSLP &R, | |
| SmallSetVector<Instruction *, 8> &FMACandidates) { | |
| SmallVector<WeakTrackingVH> PostponedInsts; | |
| bool Res = vectorizeHorReduction(P, Root, BB, R, PostponedInsts); | |
| Res |= tryToVectorize(PostponedInsts, R, FMACandidates); | |
| return Res; | |
| } | |
| bool SLPVectorizerPass::tryToVectorize( | |
| ArrayRef<WeakTrackingVH> Insts, BoUpSLP &R, | |
| SmallSetVector<Instruction *, 8> &FMACandidates) { | |
| bool Res = false; | |
| for (Value *V : Insts) | |
| if (auto *Inst = dyn_cast<Instruction>(V); Inst && !R.isDeleted(Inst)) | |
| Res |= tryToVectorize(Inst, R, FMACandidates); | |
| return Res; | |
| } | |
| /// Vectorizes the aggregate \p IVI, built of the packs of the same layout, as | |
| /// the single vector of all the fields, bitcasted to the vector of the packs. | |
| /// The packs vectorized alone would leave the vectors narrower than possible. | |
| static bool vectorizePackedAggregate(InsertValueInst *IVI, BoUpSLP &R, | |
| TargetTransformInfo &TTI, | |
| const DataLayout &DL) { | |
| if (!R.Opts.slp_vectorize_hor || R.Opts.slp_revec || DL.isBigEndian() || | |
| !R.canMapToVector(IVI->getType())) | |
| return false; | |
| SmallVector<Value *, 16> Packs; | |
| SmallVector<Value *, 16> Inserts; | |
| if (!findBuildAggregate(IVI, &TTI, Packs, Inserts, R)) | |
| return false; | |
| // The inserts must form the plain chain, which is moved after the vector. | |
| BasicBlock *BB = IVI->getParent(); | |
| if (!all_of(Inserts, [&](Value *V) { | |
| auto *Insert = dyn_cast<InsertValueInst>(V); | |
| if (!Insert || Insert->getParent() != BB || | |
| (Insert != IVI && !(Insert->hasOneUse() && | |
| is_contained(Inserts, Insert->user_back())))) | |
| return false; | |
| Value *Agg = Insert->getAggregateOperand(); | |
| return !isa<InsertValueInst>(Agg) || is_contained(Inserts, Agg); | |
| })) | |
| return false; | |
| SmallVector<Value *> Fields; | |
| SmallVector<Instruction *> Chain; | |
| for (Value *Pack : Packs) { | |
| auto *Root = dyn_cast<Instruction>(Pack); | |
| if (!Root || Root->getParent() != BB || | |
| !matchPackedFields(Root, R.Opts.slp_recursion_max_depth, Fields, Chain)) | |
| return false; | |
| } | |
| // The pack instructions are dropped, so they must be used by the packs only. | |
| SmallDenseSet<Value *> IgnoreList; | |
| IgnoreList.insert_range(Chain); | |
| if (!all_of(Chain, [&](Instruction *I) { | |
| return I->getParent() == BB && all_of(I->users(), [&](User *U) { | |
| return IgnoreList.contains(U) || is_contained(Inserts, U); | |
| }); | |
| })) | |
| return false; | |
| // Merging the loads into the wider load is better done for each pack. | |
| if (all_of(Fields, IsaPred<LoadInst>)) | |
| return false; | |
| // The fields of different types are not vectorized, the tree is empty. | |
| R.buildTree(Fields, IgnoreList); | |
| if (R.isTreeTinyAndNotFullyVectorizable(/*ForReduction=*/true)) | |
| return false; | |
| if (R.isProfitableToReorder()) { | |
| R.reorderTopToBottom(); | |
| // The root is shuffled back to the order of the fields, its order can be | |
| // ignored. | |
| R.reorderBottomToTop(/*IgnoreReorder=*/true); | |
| } | |
| R.transformNodes(); | |
| // The fields are not demoted, the bitcast depends on their width. | |
| InstructionCost TreeCost = R.calculateTreeCostAndTrimNonProfitable(Fields); | |
| R.buildExternalUses(); | |
| // The gathered operands of the fields are unpacked better for each pack. | |
| if (R.hasNonConstantGathers()) | |
| return false; | |
| // The packs take the lanes in the order of the fields. | |
| SmallVector<int> OrderMask(Fields.size()); | |
| SmallBitVector Seen(Fields.size()); | |
| for (auto [Idx, F] : enumerate(Fields)) { | |
| unsigned Lane = R.findRootLaneForValue(F); | |
| if (Lane >= Fields.size() || Seen.test(Lane)) | |
| return false; | |
| Seen.set(Lane); | |
| OrderMask[Idx] = Lane; | |
| } | |
| const bool IsIdentity = | |
| ShuffleVectorInst::isIdentityMask(OrderMask, OrderMask.size()); | |
| const TTI::TargetCostKind CostKind = R.getCostKind(); | |
| const unsigned NumPacks = Packs.size(); | |
| Type *FieldsTy = getWidenedType(Fields.front()->getType(), Fields.size()); | |
| Type *PacksTy = getWidenedType(Packs.front()->getType(), NumPacks); | |
| InstructionCost PackCost = | |
| TTI.getCastInstrCost(Instruction::BitCast, PacksTy, FieldsTy, | |
| TTI::CastContextHint::None, CostKind); | |
| if (!IsIdentity) | |
| PackCost += getShuffleCost(TTI, TTI::SK_PermuteSingleSrc, | |
| cast<VectorType>(FieldsTy), CostKind, OrderMask); | |
| PackCost += TTI.getScalarizationOverhead( | |
| cast<VectorType>(PacksTy), APInt::getAllOnes(NumPacks), | |
| /*Insert=*/false, /*Extract=*/true, CostKind); | |
| for (Instruction *I : Chain) | |
| PackCost -= TTI.getInstructionCost(I, CostKind); | |
| InstructionCost Cost = R.getTreeCost(TreeCost, Fields, PackCost, IVI); | |
| LLVM_DEBUG(dbgs() << "SLP: Found cost = " << Cost | |
| << " for packed aggregate\n"); | |
| if (!Cost.isValid() || Cost >= -R.getCostThreshold()) | |
| return false; | |
| Value *Vec = R.vectorizeTree({}, IVI); | |
| assert(Vec->getType() == FieldsTy && "Expected the vector of the fields."); | |
| IRBuilder<TargetFolder> Builder(IVI->getIterator(), TargetFolder(DL)); | |
| if (!IsIdentity) | |
| Vec = Builder.CreateShuffleVector(Vec, OrderMask); | |
| Value *Packed = Builder.CreateBitCast(Vec, PacksTy); | |
| for (auto [Idx, Insert] : enumerate(Inserts)) | |
| cast<InsertValueInst>(Insert)->setOperand( | |
| 1, Builder.CreateExtractElement(Packed, Idx)); | |
| // The inserts may precede the vector, move them after the extracts. | |
| InsertValueInst *Next = IVI; | |
| while (auto *Prev = dyn_cast<InsertValueInst>(Next->getAggregateOperand())) { | |
| if (!is_contained(Inserts, Prev)) | |
| break; | |
| Prev->moveBefore(Next->getIterator()); | |
| Next = Prev; | |
| } | |
| R.removeInstructionsAndOperands(ArrayRef(Chain), {}); | |
| return true; | |
| } | |
| bool SLPVectorizerPass::vectorizeInsertValueInst(InsertValueInst *IVI, | |
| BasicBlock *BB, BoUpSLP &R, | |
| bool MaxVFOnly) { | |
| if (!R.canMapToVector(IVI->getType())) | |
| return false; | |
| SmallVector<Value *, 16> BuildVectorOpds; | |
| SmallVector<Value *, 16> BuildVectorInsts; | |
| if (!findBuildAggregate(IVI, TTI, BuildVectorOpds, BuildVectorInsts, R)) | |
| return false; | |
| if (MaxVFOnly && BuildVectorOpds.size() == 2) { | |
| R.getORE()->emit([&]() { | |
| return OptimizationRemarkMissed(SV_NAME, "NotPossible", IVI) | |
| << "Cannot SLP vectorize list: only 2 elements of buildvalue, " | |
| "trying reduction first."; | |
| }); | |
| return false; | |
| } | |
| LLVM_DEBUG(dbgs() << "SLP: array mappable to vector: " << *IVI << "\n"); | |
| // Aggregate value is unlikely to be processed in vector register. | |
| // Try to vectorize the insertvalue chain itself (rebuilding the aggregate | |
| // from a vector) only for struct aggregates: getContainedTypes() returns the | |
| // per-field types for a struct, but returns the aggregate type itself as a | |
| // single element for arrays and other aggregates, so the size check below | |
| // fails for them and they fall back to vectorizing the scalar operands. | |
| // TODO: Extend the vectorized-store path to array aggregates. | |
| Type *BVType = BuildVectorInsts.front()->getType(); | |
| ArrayRef<Type *> ContainedTypes = getContainedTypes(BVType); | |
| if (ContainedTypes.size() == BuildVectorInsts.size() && | |
| isValidElementType(ContainedTypes.front(), Opts->slp_revec) && | |
| all_of(ContainedTypes, | |
| [&](Type *Ty) { return Ty == ContainedTypes.front(); })) | |
| if (tryToVectorizeList(BuildVectorInsts, R, MaxVFOnly)) | |
| return true; | |
| return tryToVectorizeList(BuildVectorOpds, R, MaxVFOnly); | |
| } | |
| bool SLPVectorizerPass::vectorizeInsertElementInst(InsertElementInst *IEI, | |
| BasicBlock *BB, BoUpSLP &R, | |
| bool MaxVFOnly) { | |
| SmallVector<Value *, 16> BuildVectorInsts; | |
| SmallVector<Value *, 16> BuildVectorOpds; | |
| SmallVector<int> Mask; | |
| if (!findBuildAggregate(IEI, TTI, BuildVectorOpds, BuildVectorInsts, R) || | |
| (all_of(BuildVectorOpds, IsaPred<ExtractElementInst, UndefValue>) && | |
| isFixedVectorShuffle(BuildVectorOpds, Mask, AC, R.getAnalysisCache()))) | |
| return false; | |
| if (MaxVFOnly && BuildVectorInsts.size() == 2) { | |
| R.getORE()->emit([&]() { | |
| return OptimizationRemarkMissed(SV_NAME, "NotPossible", IEI) | |
| << "Cannot SLP vectorize list: only 2 elements of buildvector, " | |
| "trying reduction first."; | |
| }); | |
| return false; | |
| } | |
| LLVM_DEBUG(dbgs() << "SLP: array mappable to vector: " << *IEI << "\n"); | |
| return tryToVectorizeList(BuildVectorInsts, R, MaxVFOnly); | |
| } | |
| template <typename T> | |
| static bool tryToVectorizeSequence( | |
| const VectorizeOptions &Opts, SmallVectorImpl<T *> &Incoming, | |
| function_ref<bool(T *, T *)> Comparator, | |
| function_ref<bool(ArrayRef<T *>, T *)> AreCompatible, | |
| function_ref<bool(ArrayRef<T *>, bool)> TryToVectorizeHelper, | |
| bool MaxVFOnly, BoUpSLP &R, | |
| function_ref<bool(ArrayRef<T *>)> Filter = nullptr) { | |
| bool Changed = false; | |
| // Sort by type, parent, operands. | |
| stable_sort(Incoming, Comparator); | |
| // Try to vectorize elements base on their type. | |
| SmallVector<T *> Candidates; | |
| SmallVector<T *> VL; | |
| for (auto *IncIt = Incoming.begin(), *E = Incoming.end(); IncIt != E; | |
| VL.clear()) { | |
| // Look for the next elements with the same type, parent and operand | |
| // kinds. | |
| auto *I = dyn_cast<Instruction>(*IncIt); | |
| if (!I || R.isDeleted(I) || | |
| !isValidElementType(getValueType(I, Opts.slp_revec), Opts.slp_revec)) { | |
| ++IncIt; | |
| continue; | |
| } | |
| auto *SameTypeIt = IncIt; | |
| while (SameTypeIt != E && (!isa<Instruction>(*SameTypeIt) || | |
| R.isDeleted(cast<Instruction>(*SameTypeIt)) || | |
| AreCompatible(VL, *SameTypeIt))) { | |
| auto *I = dyn_cast<Instruction>(*SameTypeIt); | |
| ++SameTypeIt; | |
| if (I && !R.isDeleted(I)) | |
| VL.push_back(cast<T>(I)); | |
| } | |
| // Rejected groups are dropped, not left among the candidates. | |
| if (Filter && !Filter(ArrayRef(VL))) | |
| VL.clear(); | |
| // Try to vectorize them. | |
| unsigned NumElts = VL.size(); | |
| LLVM_DEBUG(dbgs() << "SLP: Trying to vectorize starting at nodes (" | |
| << NumElts << ")\n"); | |
| // The vectorization is a 3-state attempt: | |
| // 1. Try to vectorize instructions with the same/alternate opcodes with the | |
| // size of maximal register at first. | |
| // 2. Try to vectorize remaining instructions with the same type, if | |
| // possible. This may result in the better vectorization results rather than | |
| // if we try just to vectorize instructions with the same/alternate opcodes. | |
| // 3. Final attempt to try to vectorize all instructions with the | |
| // same/alternate ops only, this may result in some extra final | |
| // vectorization. | |
| if (NumElts > 1 && TryToVectorizeHelper(ArrayRef(VL), MaxVFOnly)) { | |
| // Success start over because instructions might have been changed. | |
| Changed = true; | |
| VL.swap(Candidates); | |
| Candidates.clear(); | |
| for (T *V : VL) { | |
| if (auto *I = dyn_cast<Instruction>(V); I && !R.isDeleted(I)) | |
| Candidates.push_back(V); | |
| } | |
| } else { | |
| /// \Returns the minimum number of elements that we will attempt to | |
| /// vectorize. | |
| auto GetMinNumElements = [&R](Value *V) { | |
| unsigned EltSize = R.getVectorElementSize(V); | |
| return std::max(2U, R.getMaxVecRegSize() / EltSize); | |
| }; | |
| if (NumElts < GetMinNumElements(*IncIt) && | |
| (Candidates.empty() || | |
| Candidates.front()->getType() == (*IncIt)->getType())) { | |
| for (T *V : VL) { | |
| if (auto *I = dyn_cast<Instruction>(V); I && !R.isDeleted(I)) | |
| Candidates.push_back(V); | |
| } | |
| } | |
| } | |
| // Final attempt to vectorize instructions with the same types. | |
| if (Candidates.size() > 1 && | |
| (SameTypeIt == E || (*SameTypeIt)->getType() != (*IncIt)->getType())) { | |
| if (TryToVectorizeHelper(Candidates, /*MaxVFOnly=*/false)) { | |
| // Success start over because instructions might have been changed. | |
| Changed = true; | |
| } else if (MaxVFOnly) { | |
| // Try to vectorize using small vectors. | |
| SmallVector<T *> VL; | |
| for (auto *It = Candidates.begin(), *End = Candidates.end(); It != End; | |
| VL.clear()) { | |
| auto *I = dyn_cast<Instruction>(*It); | |
| if (!I || R.isDeleted(I)) { | |
| ++It; | |
| continue; | |
| } | |
| auto *SameTypeIt = It; | |
| while (SameTypeIt != End && | |
| (!isa<Instruction>(*SameTypeIt) || | |
| R.isDeleted(cast<Instruction>(*SameTypeIt)) || | |
| AreCompatible(*SameTypeIt, *It))) { | |
| auto *I = dyn_cast<Instruction>(*SameTypeIt); | |
| ++SameTypeIt; | |
| if (I && !R.isDeleted(I)) | |
| VL.push_back(cast<T>(I)); | |
| } | |
| unsigned NumElts = VL.size(); | |
| if (NumElts > 1 && TryToVectorizeHelper(ArrayRef(VL), | |
| /*MaxVFOnly=*/false)) | |
| Changed = true; | |
| It = SameTypeIt; | |
| } | |
| } | |
| Candidates.clear(); | |
| } | |
| // Start over at the next instruction of a different type (or the end). | |
| IncIt = SameTypeIt; | |
| } | |
| return Changed; | |
| } | |
| /// Compare two cmp instructions. If IsCompatibility is true, function returns | |
| /// true if 2 cmps have same/swapped predicates and mos compatible corresponding | |
| /// operands. If IsCompatibility is false, function implements strict weak | |
| /// ordering relation between two cmp instructions, returning true if the first | |
| /// instruction is "less" than the second, i.e. its predicate is less than the | |
| /// predicate of the second or the operands IDs are less than the operands IDs | |
| /// of the second cmp instruction. | |
| template <bool IsCompatibility> | |
| static bool compareCmp(const VectorizeOptions &Opts, Value *V, Value *V2, | |
| TargetLibraryInfo &TLI, const DominatorTree &DT) { | |
| assert(isValidElementType(V->getType(), Opts.slp_revec) && | |
| isValidElementType(V2->getType(), Opts.slp_revec) && | |
| "Expected valid element types only."); | |
| if (V == V2) | |
| return IsCompatibility; | |
| auto *CI1 = cast<CmpInst>(V); | |
| auto *CI2 = cast<CmpInst>(V2); | |
| if (CI1->getOperand(0)->getType()->getTypeID() < | |
| CI2->getOperand(0)->getType()->getTypeID()) | |
| return !IsCompatibility; | |
| if (CI1->getOperand(0)->getType()->getTypeID() > | |
| CI2->getOperand(0)->getType()->getTypeID()) | |
| return false; | |
| if (CI1->getOperand(0)->getType()->getScalarSizeInBits() < | |
| CI2->getOperand(0)->getType()->getScalarSizeInBits()) | |
| return !IsCompatibility; | |
| if (CI1->getOperand(0)->getType()->getScalarSizeInBits() > | |
| CI2->getOperand(0)->getType()->getScalarSizeInBits()) | |
| return false; | |
| CmpInst::Predicate Pred1 = CI1->getPredicate(); | |
| CmpInst::Predicate Pred2 = CI2->getPredicate(); | |
| CmpInst::Predicate SwapPred1 = CmpInst::getSwappedPredicate(Pred1); | |
| CmpInst::Predicate SwapPred2 = CmpInst::getSwappedPredicate(Pred2); | |
| CmpInst::Predicate BasePred1 = std::min(Pred1, SwapPred1); | |
| CmpInst::Predicate BasePred2 = std::min(Pred2, SwapPred2); | |
| if (BasePred1 < BasePred2) | |
| return !IsCompatibility; | |
| if (BasePred1 > BasePred2) | |
| return false; | |
| // Compare operands. | |
| bool CI1Preds = Pred1 == BasePred1; | |
| bool CI2Preds = Pred2 == BasePred1; | |
| for (int I = 0, E = CI1->getNumOperands(); I < E; ++I) { | |
| auto *Op1 = CI1->getOperand(CI1Preds ? I : E - I - 1); | |
| auto *Op2 = CI2->getOperand(CI2Preds ? I : E - I - 1); | |
| if (Op1 == Op2) | |
| continue; | |
| if (Op1->getValueID() < Op2->getValueID()) | |
| return !IsCompatibility; | |
| if (Op1->getValueID() > Op2->getValueID()) | |
| return false; | |
| if (auto *I1 = dyn_cast<Instruction>(Op1)) | |
| if (auto *I2 = dyn_cast<Instruction>(Op2)) { | |
| if (IsCompatibility) { | |
| if (I1->getParent() != I2->getParent()) | |
| return false; | |
| } else { | |
| // Try to compare nodes with same parent. | |
| DomTreeNodeBase<BasicBlock> *NodeI1 = DT.getNode(I1->getParent()); | |
| DomTreeNodeBase<BasicBlock> *NodeI2 = DT.getNode(I2->getParent()); | |
| if (!NodeI1) | |
| return NodeI2 != nullptr; | |
| if (!NodeI2) | |
| return false; | |
| assert((NodeI1 == NodeI2) == | |
| (NodeI1->getDFSNumIn() == NodeI2->getDFSNumIn()) && | |
| "Different nodes should have different DFS numbers"); | |
| if (NodeI1 != NodeI2) | |
| return NodeI1->getDFSNumIn() < NodeI2->getDFSNumIn(); | |
| } | |
| InstructionsState S = getSameOpcode({I1, I2}, TLI); | |
| if (S && (IsCompatibility || !S.isAltShuffle())) | |
| continue; | |
| if (IsCompatibility) | |
| return false; | |
| if (I1->getOpcode() != I2->getOpcode()) | |
| return I1->getOpcode() < I2->getOpcode(); | |
| } | |
| } | |
| return IsCompatibility; | |
| } | |
| template <typename ItT> | |
| bool SLPVectorizerPass::vectorizeCmpInsts( | |
| iterator_range<ItT> CmpInsts, BasicBlock *BB, BoUpSLP &R, | |
| SmallSetVector<Instruction *, 8> &FMACandidates) { | |
| bool Changed = false; | |
| // Try to find reductions first. | |
| for (CmpInst *I : make_filter_range( | |
| CmpInsts, [&](CmpInst *I) { return !R.isDeleted(I); })) { | |
| for (Instruction *RootOp : make_isa_range<Instruction>(I->operands())) { | |
| Changed |= | |
| vectorizeRootInstruction(nullptr, RootOp, BB, R, FMACandidates); | |
| if (R.isDeleted(I)) | |
| break; | |
| } | |
| } | |
| // Try to vectorize operands as vector bundles. | |
| for (CmpInst *I : | |
| make_filter_range(CmpInsts, [&](CmpInst *I) { return !R.isDeleted(I); })) | |
| Changed |= tryToVectorize(I, R, FMACandidates); | |
| // Try to vectorize list of compares. | |
| // Sort by type, compare predicate, etc. | |
| auto CompareSorter = [&](Value *V, Value *V2) { | |
| if (V == V2) | |
| return false; | |
| return compareCmp<false>(*Opts, V, V2, *TLI, *DT); | |
| }; | |
| auto AreCompatibleCompares = [&](ArrayRef<Value *> VL, Value *V1) { | |
| if (VL.empty() || VL.back() == V1) | |
| return true; | |
| return compareCmp<true>(*Opts, V1, VL.back(), *TLI, *DT); | |
| }; | |
| SmallVector<Value *> Vals; | |
| for (Instruction *V : CmpInsts) | |
| if (!R.isDeleted(V) && | |
| isValidElementType( | |
| getValueType(V, Opts->slp_revec, /*LookThroughCmp=*/true), | |
| Opts->slp_revec)) | |
| Vals.push_back(V); | |
| if (Vals.size() <= 1) | |
| return Changed; | |
| Changed |= tryToVectorizeSequence<Value>( | |
| *Opts, Vals, CompareSorter, AreCompatibleCompares, | |
| [this, &R](ArrayRef<Value *> Candidates, bool MaxVFOnly) { | |
| // Exclude possible reductions from other blocks. | |
| bool ArePossiblyReducedInOtherBlock = any_of(Candidates, [](Value *V) { | |
| return any_of(V->users(), [V](User *U) { | |
| auto *Select = dyn_cast<SelectInst>(U); | |
| return Select && | |
| Select->getParent() != cast<Instruction>(V)->getParent(); | |
| }); | |
| }); | |
| if (ArePossiblyReducedInOtherBlock) | |
| return false; | |
| return tryToVectorizeList(Candidates, R, MaxVFOnly); | |
| }, | |
| /*MaxVFOnly=*/true, R); | |
| return Changed; | |
| } | |
| /// Returns true if \p I is an instruction whose result the SLP vectorizer | |
| /// cannot turn into a vector instruction directly, but whose operand chains | |
| /// may still be worth vectorizing as bundle seeds. | |
| static bool isNonVectorizableInst(const VectorizeOptions &Opts, | |
| const Instruction *I, | |
| const TargetLibraryInfo *TLI) { | |
| if (const auto *CB = dyn_cast<CallBase>(I)) { | |
| if (CB->isInlineAsm()) | |
| return false; | |
| if (const auto *II = dyn_cast<IntrinsicInst>(CB)) { | |
| if (II->isAssumeLikeIntrinsic()) | |
| return false; | |
| if (isa<AnyMemIntrinsic>(II)) | |
| return false; | |
| } | |
| if (const auto *CI = dyn_cast<CallInst>(CB)) { | |
| Intrinsic::ID ID = getVectorIntrinsicIDForCall(CI, TLI); | |
| if (isTriviallyVectorizable(ID)) | |
| return false; | |
| if (!VFDatabase::getMappings(*CI).empty()) | |
| return false; | |
| if (all_of(make_isa_range<Instruction>(CI->args()), | |
| [](const Instruction *Arg) { | |
| return Arg->getType()->isPointerTy(); | |
| })) | |
| return false; | |
| if (any_of(CI->args(), [](const Value *Arg) { | |
| return Arg->getType()->isPointerTy(); | |
| })) | |
| return false; | |
| } | |
| // Skip vector-returning calls in non-revec mode - we cannot turn their | |
| // results into wider vectors here. | |
| return Opts.slp_revec || !CB->getType()->isVectorTy(); | |
| } | |
| if (isa<AtomicRMWInst, AtomicCmpXchgInst>(I)) | |
| return true; | |
| if (const auto *EV = dyn_cast<ExtractValueInst>(I)) { | |
| const auto *Arg = EV->getAggregateOperand(); | |
| if (const auto *CI = dyn_cast<CallInst>(Arg)) { | |
| Intrinsic::ID ID = getVectorIntrinsicIDForCall(CI, TLI); | |
| if (isTriviallyVectorizable(ID)) | |
| return true; | |
| if (!VFDatabase::getMappings(*CI).empty()) | |
| return true; | |
| return false; | |
| } | |
| } | |
| if (const auto *RI = dyn_cast<ReturnInst>(I)) | |
| return RI->getNumOperands() > 0 && | |
| (Opts.slp_revec || !I->getOperand(0)->getType()->isVectorTy()) && | |
| isa<Instruction>(I->getOperand(0)); | |
| return false; | |
| } | |
| /// Visits the value operands of \p I that are candidates for operand-chain | |
| /// vectorization. | |
| template <typename Func> | |
| static void forEachOperandChainCandidate(const VectorizeOptions &Opts, | |
| Instruction *I, Func F, | |
| bool ForReduction) { | |
| if (auto *CB = dyn_cast<CallBase>(I)) { | |
| if (ForReduction && !Opts.slp_non_vectorizables_as_reductions && | |
| CB->arg_size() > 1) | |
| return; | |
| for (auto [Idx, U] : enumerate(CB->args())) | |
| F(U.get(), Idx); | |
| return; | |
| } | |
| if (auto *AI = dyn_cast<AtomicRMWInst>(I)) { | |
| F(AI->getValOperand(), 0); | |
| return; | |
| } | |
| if (auto *AI = dyn_cast<AtomicCmpXchgInst>(I)) { | |
| F(AI->getCompareOperand(), 0); | |
| F(AI->getNewValOperand(), 1); | |
| return; | |
| } | |
| if (auto *EV = dyn_cast<ExtractValueInst>(I)) { | |
| F(EV->getAggregateOperand(), 0); | |
| return; | |
| } | |
| if (ForReduction && !Opts.slp_non_vectorizables_as_reductions) | |
| return; | |
| if (auto *SI = dyn_cast<StoreInst>(I)) { | |
| F(SI->getValueOperand(), 0); | |
| return; | |
| } | |
| if (auto *RI = dyn_cast<ReturnInst>(I)) { | |
| if (RI->getNumOperands() > 0) | |
| F(RI->getReturnValue(), 0); | |
| return; | |
| } | |
| llvm_unreachable("Unexpected instruction kind for operand-chain seeding"); | |
| } | |
| template <typename ItT> | |
| bool SLPVectorizerPass::vectorizeNonVectorizableInsts( | |
| iterator_range<ItT> InstRange, BasicBlock *BB, BoUpSLP &R, | |
| SmallSetVector<Instruction *, 8> &FMACandidates) { | |
| SmallVector<Instruction *> Insts(InstRange); | |
| if (Insts.empty()) | |
| return false; | |
| stable_sort(Insts, [](const Instruction *A, const Instruction *B) { | |
| return A->getOpcode() < B->getOpcode(); | |
| }); | |
| bool Changed = false; | |
| // Pass 1 - try to find horizontal reductions feeding the root operands. | |
| SmallPtrSet<Value *, 8> RootSeen; | |
| for (Instruction *I : make_filter_range( | |
| Insts, [&](Instruction *I) { return !R.isDeleted(I); })) { | |
| bool RootDeleted = false; | |
| forEachOperandChainCandidate( | |
| *Opts, I, | |
| [&](Value *Op, unsigned /*Position*/) { | |
| if (RootDeleted) | |
| return; | |
| auto *RootOp = dyn_cast<Instruction>(Op); | |
| if (!RootOp || RootOp->getParent() != BB || R.isDeleted(RootOp) || | |
| isa<ShuffleVectorInst>(RootOp) || | |
| !isValidElementType(RootOp->getType(), Opts->slp_revec)) | |
| return; | |
| if (!RootSeen.insert(RootOp).second) | |
| return; | |
| Changed |= | |
| vectorizeRootInstruction(nullptr, RootOp, BB, R, FMACandidates); | |
| if (R.isDeleted(I)) | |
| RootDeleted = true; | |
| }, | |
| /*ForReduction=*/true); | |
| } | |
| // Pass 2 - collect the operand instructions across all roots and try to | |
| // vectorize them as bundles. | |
| if (Insts.size() < 2) | |
| return Changed; | |
| struct OperandGroupKey { | |
| enum class Kind : unsigned { | |
| NonCall = 0, | |
| Intrinsic, | |
| NamedFunction, | |
| IndirectCall, | |
| }; | |
| Kind RootKind; | |
| unsigned KindID; // Intrinsic ID for Intrinsic, opcode for NonCall, | |
| // callee value-kind for IndirectCall, 0 for | |
| // NamedFunction. | |
| unsigned SubOp; // AtomicRMW operation; 0 otherwise. | |
| StringRef FuncName; // Non-empty only for NamedFunction. | |
| unsigned Position; // Operand slot within the root. | |
| bool operator==(const OperandGroupKey &O) const { | |
| return RootKind == O.RootKind && KindID == O.KindID && SubOp == O.SubOp && | |
| FuncName == O.FuncName && Position == O.Position; | |
| } | |
| bool operator!=(const OperandGroupKey &O) const { return !(*this == O); } | |
| bool less(const OperandGroupKey &O) const { | |
| if (RootKind != O.RootKind) | |
| return static_cast<unsigned>(RootKind) < | |
| static_cast<unsigned>(O.RootKind); | |
| if (KindID != O.KindID) | |
| return KindID < O.KindID; | |
| if (SubOp != O.SubOp) | |
| return SubOp < O.SubOp; | |
| if (int C = FuncName.compare(O.FuncName)) | |
| return C < 0; | |
| return Position < O.Position; | |
| } | |
| }; | |
| SmallDenseMap<Value *, OperandGroupKey> OpKeys; | |
| auto BuildKey = [](Instruction *I, unsigned Position) -> OperandGroupKey { | |
| if (auto *CB = dyn_cast<CallBase>(I)) { | |
| if (auto *II = dyn_cast<IntrinsicInst>(CB)) | |
| return {OperandGroupKey::Kind::Intrinsic, | |
| II->getIntrinsicID(), | |
| 0, | |
| {}, | |
| Position}; | |
| if (Function *F = CB->getCalledFunction()) | |
| return {OperandGroupKey::Kind::NamedFunction, 0, 0, F->getName(), | |
| Position}; | |
| return {OperandGroupKey::Kind::IndirectCall, | |
| CB->getCalledOperand()->getValueID(), | |
| 0, | |
| {}, | |
| Position}; | |
| } | |
| unsigned SubOp = 0; | |
| if (auto *AI = dyn_cast<AtomicRMWInst>(I)) | |
| SubOp = static_cast<unsigned>(AI->getOperation()); | |
| return { | |
| OperandGroupKey::Kind::NonCall, I->getOpcode(), SubOp, {}, Position}; | |
| }; | |
| auto OperandSorter = [&OpKeys](Value *V1, Value *V2) -> bool { | |
| if (V1 == V2) | |
| return false; | |
| const OperandGroupKey &K1 = OpKeys.at(V1); | |
| const OperandGroupKey &K2 = OpKeys.at(V2); | |
| if (K1 != K2) | |
| return K1.less(K2); | |
| auto *I1 = cast<Instruction>(V1); | |
| auto *I2 = cast<Instruction>(V2); | |
| if (I1->getType()->getTypeID() != I2->getType()->getTypeID()) | |
| return I1->getType()->getTypeID() < I2->getType()->getTypeID(); | |
| if (I1->getType()->getScalarSizeInBits() != | |
| I2->getType()->getScalarSizeInBits()) | |
| return I1->getType()->getScalarSizeInBits() < | |
| I2->getType()->getScalarSizeInBits(); | |
| if (I1->getOpcode() != I2->getOpcode()) | |
| return I1->getOpcode() < I2->getOpcode(); | |
| return I1->comesBefore(I2); | |
| }; | |
| auto AreCompatibleOperands = [&OpKeys](ArrayRef<Value *> VL, | |
| Value *V) -> bool { | |
| if (VL.empty() || VL.back() == V) | |
| return true; | |
| const OperandGroupKey &KBack = OpKeys.at(VL.back()); | |
| const OperandGroupKey &K = OpKeys.at(V); | |
| if (KBack != K) | |
| return false; | |
| auto *I1 = cast<Instruction>(VL.back()); | |
| auto *I2 = cast<Instruction>(V); | |
| return I1->getType() == I2->getType() && I1->getOpcode() == I2->getOpcode(); | |
| }; | |
| SmallVector<Value *> Operands; | |
| SmallPtrSet<Value *, 8> Seen; | |
| for (Instruction *I : make_filter_range( | |
| Insts, [&](Instruction *I) { return !R.isDeleted(I); })) { | |
| forEachOperandChainCandidate( | |
| *Opts, I, | |
| [&](Value *Op, unsigned Position) { | |
| auto *OpI = dyn_cast<Instruction>(Op); | |
| if (!OpI || OpI->getParent() != BB || R.isDeleted(OpI) || | |
| isa<ShuffleVectorInst>(OpI) || | |
| (!isValidElementType(OpI->getType(), Opts->slp_revec) && | |
| !isa<IntrinsicInst>(OpI))) | |
| return; | |
| if (!Seen.insert(OpI).second) | |
| return; | |
| OpKeys.try_emplace(OpI, BuildKey(I, Position)); | |
| Operands.push_back(OpI); | |
| }, | |
| /*ForReduction=*/false); | |
| } | |
| if (Operands.size() <= 1) | |
| return Changed; | |
| Changed |= tryToVectorizeSequence<Value>( | |
| *Opts, Operands, OperandSorter, AreCompatibleOperands, | |
| [this, &R](ArrayRef<Value *> Candidates, bool MaxVFOnly) { | |
| constexpr unsigned Limit = 32; | |
| // Limit to StandaloneSeeds if !MaxVFOnly to avoid quadratic scan for | |
| // large set of candidates. | |
| return tryToVectorizeList(Candidates, R, MaxVFOnly, | |
| /*StandaloneSeeds=*/!MaxVFOnly && | |
| Candidates.size() >= Limit); | |
| }, | |
| /*MaxVFOnly=*/true, R); | |
| return Changed; | |
| } | |
| bool SLPVectorizerPass::vectorizeInserts( | |
| InstSetVector &Instructions, BasicBlock *BB, BoUpSLP &R, | |
| SmallSetVector<Instruction *, 8> &FMACandidates) { | |
| assert(all_of(Instructions, IsaPred<InsertElementInst, InsertValueInst>) && | |
| "This function only accepts Insert instructions"); | |
| bool OpsChanged = false; | |
| SmallVector<WeakTrackingVH> PostponedInsts; | |
| // pass1 - try to match and vectorize a buildvector sequence for MaxVF only. | |
| for (auto *I : make_filter_range(reverse(Instructions), [&](auto *I) { | |
| return !R.isDeleted(I) && !isa<CmpInst>(I); | |
| })) { | |
| // The packs of the aggregate are vectorized together, not each alone. | |
| if (auto *IVI = dyn_cast<InsertValueInst>(I); | |
| IVI && vectorizePackedAggregate(IVI, R, *TTI, *DL)) { | |
| OpsChanged = true; | |
| continue; | |
| } | |
| if (auto *LastInsertValue = dyn_cast<InsertValueInst>(I)) { | |
| OpsChanged |= | |
| vectorizeInsertValueInst(LastInsertValue, BB, R, /*MaxVFOnly=*/true); | |
| } else if (auto *LastInsertElem = dyn_cast<InsertElementInst>(I)) { | |
| OpsChanged |= | |
| vectorizeInsertElementInst(LastInsertElem, BB, R, /*MaxVFOnly=*/true); | |
| } | |
| // pass2 - try to vectorize reductions only | |
| if (R.isDeleted(I)) | |
| continue; | |
| OpsChanged |= vectorizeHorReduction(nullptr, I, BB, R, PostponedInsts); | |
| if (R.isDeleted(I) || isa<CmpInst>(I)) | |
| continue; | |
| // pass3 - try to match and vectorize a buildvector sequence. | |
| if (auto *LastInsertValue = dyn_cast<InsertValueInst>(I)) { | |
| OpsChanged |= | |
| vectorizeInsertValueInst(LastInsertValue, BB, R, /*MaxVFOnly=*/false); | |
| } else if (auto *LastInsertElem = dyn_cast<InsertElementInst>(I)) { | |
| OpsChanged |= vectorizeInsertElementInst(LastInsertElem, BB, R, | |
| /*MaxVFOnly=*/false); | |
| } | |
| } | |
| // Now try to vectorize postponed instructions. | |
| OpsChanged |= tryToVectorize(PostponedInsts, R, FMACandidates); | |
| Instructions.clear(); | |
| return OpsChanged; | |
| } | |
| bool SLPVectorizerPass::vectorizeChainsInBlock( | |
| BasicBlock *BB, BoUpSLP &R, | |
| SmallSetVector<Instruction *, 8> &FMACandidates) { | |
| bool Changed = false; | |
| SmallVector<Value *, 4> Incoming; | |
| SmallPtrSet<Value *, 16> VisitedInstrs; | |
| // Only the original compares are the seeds. With revec the vectorized | |
| // alternate compares are the pairs of compares of the same operands, which | |
| // otherwise are vectorized again and again. | |
| SmallPtrSet<const CmpInst *, 16> OrigCmps; | |
| for (const CmpInst &CI : make_isa_range<CmpInst>(*BB)) | |
| OrigCmps.insert(&CI); | |
| // Maps phi nodes to the non-phi nodes found in the use tree for each phi | |
| // node. Allows better to identify the chains that can be vectorized in the | |
| // better way. | |
| DenseMap<Value *, SmallVector<Value *, 4>> PHIToOpcodes; | |
| auto PHICompare = [this, &PHIToOpcodes](Value *V1, Value *V2) { | |
| assert(isValidElementType(V1->getType(), Opts->slp_revec) && | |
| isValidElementType(V2->getType(), Opts->slp_revec) && | |
| "Expected vectorizable types only."); | |
| if (V1 == V2) | |
| return false; | |
| // It is fine to compare type IDs here, since we expect only vectorizable | |
| // types, like ints, floats and pointers, we don't care about other type. | |
| if (V1->getType()->getTypeID() < V2->getType()->getTypeID()) | |
| return true; | |
| if (V1->getType()->getTypeID() > V2->getType()->getTypeID()) | |
| return false; | |
| if (V1->getType()->getScalarSizeInBits() < | |
| V2->getType()->getScalarSizeInBits()) | |
| return true; | |
| if (V1->getType()->getScalarSizeInBits() > | |
| V2->getType()->getScalarSizeInBits()) | |
| return false; | |
| ArrayRef<Value *> Opcodes1 = PHIToOpcodes[V1]; | |
| ArrayRef<Value *> Opcodes2 = PHIToOpcodes[V2]; | |
| if (Opcodes1.size() < Opcodes2.size()) | |
| return true; | |
| if (Opcodes1.size() > Opcodes2.size()) | |
| return false; | |
| for (int I = 0, E = Opcodes1.size(); I < E; ++I) { | |
| { | |
| // Instructions come first. | |
| auto *I1 = dyn_cast<Instruction>(Opcodes1[I]); | |
| auto *I2 = dyn_cast<Instruction>(Opcodes2[I]); | |
| if (I1 && I2) { | |
| DomTreeNodeBase<BasicBlock> *NodeI1 = DT->getNode(I1->getParent()); | |
| DomTreeNodeBase<BasicBlock> *NodeI2 = DT->getNode(I2->getParent()); | |
| if (!NodeI1) | |
| return NodeI2 != nullptr; | |
| if (!NodeI2) | |
| return false; | |
| assert((NodeI1 == NodeI2) == | |
| (NodeI1->getDFSNumIn() == NodeI2->getDFSNumIn()) && | |
| "Different nodes should have different DFS numbers"); | |
| if (NodeI1 != NodeI2) | |
| return NodeI1->getDFSNumIn() < NodeI2->getDFSNumIn(); | |
| InstructionsState S = getSameOpcode({I1, I2}, *TLI); | |
| if (S && !S.isAltShuffle() && I1->getOpcode() == I2->getOpcode()) { | |
| const auto *E1 = dyn_cast<ExtractElementInst>(I1); | |
| const auto *E2 = dyn_cast<ExtractElementInst>(I2); | |
| if (!E1 || !E2) | |
| continue; | |
| // Sort on ExtractElementInsts primarily by vector operands. Prefer | |
| // program order of the vector operands. | |
| const auto *V1 = dyn_cast<Instruction>(E1->getVectorOperand()); | |
| const auto *V2 = dyn_cast<Instruction>(E2->getVectorOperand()); | |
| if (V1 != V2) { | |
| if (V1 && !V2) | |
| return true; | |
| if (!V1 && V2) | |
| return false; | |
| DomTreeNodeBase<BasicBlock> *NodeI1 = | |
| DT->getNode(V1->getParent()); | |
| DomTreeNodeBase<BasicBlock> *NodeI2 = | |
| DT->getNode(V2->getParent()); | |
| if (!NodeI1) | |
| return NodeI2 != nullptr; | |
| if (!NodeI2) | |
| return false; | |
| assert((NodeI1 == NodeI2) == | |
| (NodeI1->getDFSNumIn() == NodeI2->getDFSNumIn()) && | |
| "Different nodes should have different DFS numbers"); | |
| if (NodeI1 != NodeI2) | |
| return NodeI1->getDFSNumIn() < NodeI2->getDFSNumIn(); | |
| return V1->comesBefore(V2); | |
| } | |
| // If we have the same vector operand, try to sort by constant | |
| // index. | |
| std::optional<unsigned> Id1 = getExtractIndex(E1); | |
| std::optional<unsigned> Id2 = getExtractIndex(E2); | |
| // Bring constants to the top | |
| if (Id1 && !Id2) | |
| return true; | |
| if (!Id1 && Id2) | |
| return false; | |
| // First elements come first. | |
| if (Id1 && Id2) | |
| return *Id1 < *Id2; | |
| continue; | |
| } | |
| if (I1->getOpcode() == I2->getOpcode()) | |
| continue; | |
| return I1->getOpcode() < I2->getOpcode(); | |
| } | |
| if (I1) | |
| return true; | |
| if (I2) | |
| return false; | |
| } | |
| { | |
| // Non-undef constants come next. | |
| bool C1 = isa<Constant>(Opcodes1[I]) && !isa<UndefValue>(Opcodes1[I]); | |
| bool C2 = isa<Constant>(Opcodes2[I]) && !isa<UndefValue>(Opcodes2[I]); | |
| if (C1 && C2) | |
| continue; | |
| if (C1) | |
| return true; | |
| if (C2) | |
| return false; | |
| } | |
| bool U1 = isa<UndefValue>(Opcodes1[I]); | |
| bool U2 = isa<UndefValue>(Opcodes2[I]); | |
| { | |
| // Non-constant non-instructions come next. | |
| if (!U1 && !U2) { | |
| auto ValID1 = Opcodes1[I]->getValueID(); | |
| auto ValID2 = Opcodes2[I]->getValueID(); | |
| if (ValID1 == ValID2) | |
| continue; | |
| if (ValID1 < ValID2) | |
| return true; | |
| if (ValID1 > ValID2) | |
| return false; | |
| } | |
| if (!U1) | |
| return true; | |
| if (!U2) | |
| return false; | |
| } | |
| // Undefs come last. | |
| assert(U1 && U2 && "The only thing left should be undef & undef."); | |
| } | |
| return false; | |
| }; | |
| auto AreCompatiblePHIs = [&PHIToOpcodes, this, &R](ArrayRef<Value *> VL, | |
| Value *V1) { | |
| if (VL.empty() || V1 == VL.back()) | |
| return true; | |
| Value *V2 = VL.back(); | |
| if (V1->getType() != V2->getType()) | |
| return false; | |
| ArrayRef<Value *> Opcodes1 = PHIToOpcodes[V1]; | |
| ArrayRef<Value *> Opcodes2 = PHIToOpcodes[V2]; | |
| if (Opcodes1.size() != Opcodes2.size()) | |
| return false; | |
| for (int I = 0, E = Opcodes1.size(); I < E; ++I) { | |
| // Undefs are compatible with any other value. | |
| if (isa<UndefValue>(Opcodes1[I]) || isa<UndefValue>(Opcodes2[I])) | |
| continue; | |
| if (auto *I1 = dyn_cast<Instruction>(Opcodes1[I])) | |
| if (auto *I2 = dyn_cast<Instruction>(Opcodes2[I])) { | |
| if (R.isDeleted(I1) || R.isDeleted(I2)) | |
| return false; | |
| if (I1->getParent() != I2->getParent()) | |
| return false; | |
| if (getSameOpcode({I1, I2}, *TLI)) | |
| continue; | |
| return false; | |
| } | |
| if (isa<Constant>(Opcodes1[I]) && isa<Constant>(Opcodes2[I])) | |
| continue; | |
| if (Opcodes1[I]->getValueID() != Opcodes2[I]->getValueID()) | |
| return false; | |
| } | |
| return true; | |
| }; | |
| bool HaveVectorizedPhiNodes = false; | |
| do { | |
| // Collect the incoming values from the PHIs. | |
| Incoming.clear(); | |
| for (Instruction &I : *BB) { | |
| auto *P = dyn_cast<PHINode>(&I); | |
| if (!P || P->getNumIncomingValues() > MaxPHINumOperands) | |
| break; | |
| // No need to analyze deleted, vectorized and non-vectorizable | |
| // instructions. | |
| if (!VisitedInstrs.count(P) && !R.isDeleted(P) && | |
| isValidElementType(P->getType(), Opts->slp_revec)) | |
| Incoming.push_back(P); | |
| } | |
| if (Incoming.size() <= 1) | |
| break; | |
| // Find the corresponding non-phi nodes for better matching when trying to | |
| // build the tree. | |
| for (Value *V : Incoming) { | |
| SmallVectorImpl<Value *> &Opcodes = | |
| PHIToOpcodes.try_emplace(V).first->getSecond(); | |
| if (!Opcodes.empty()) | |
| continue; | |
| SmallVector<Value *, 4> Nodes(1, V); | |
| SmallPtrSet<Value *, 4> Visited; | |
| while (!Nodes.empty()) { | |
| auto *PHI = cast<PHINode>(Nodes.pop_back_val()); | |
| if (!Visited.insert(PHI).second) | |
| continue; | |
| for (Value *V : PHI->incoming_values()) { | |
| if (auto *PHI1 = dyn_cast<PHINode>((V))) { | |
| Nodes.push_back(PHI1); | |
| continue; | |
| } | |
| Opcodes.emplace_back(V); | |
| } | |
| } | |
| } | |
| HaveVectorizedPhiNodes = tryToVectorizeSequence<Value>( | |
| *Opts, Incoming, PHICompare, AreCompatiblePHIs, | |
| [this, &R](ArrayRef<Value *> Candidates, bool MaxVFOnly) { | |
| return tryToVectorizeList(Candidates, R, MaxVFOnly); | |
| }, | |
| /*MaxVFOnly=*/true, R); | |
| Changed |= HaveVectorizedPhiNodes; | |
| if (HaveVectorizedPhiNodes && any_of(PHIToOpcodes, [&](const auto &P) { | |
| auto *PHI = dyn_cast<PHINode>(P.first); | |
| return !PHI || R.isDeleted(PHI); | |
| })) | |
| PHIToOpcodes.clear(); | |
| VisitedInstrs.insert_range(Incoming); | |
| } while (HaveVectorizedPhiNodes); | |
| VisitedInstrs.clear(); | |
| InstSetVector PostProcessInserts; | |
| SmallSetVector<CmpInst *, 8> PostProcessCmps; | |
| // Non-vectorizable root instructions other than stores: calls | |
| // (regular and intrinsic), invokes, callbrs, atomic RMW/cmpxchg, and | |
| // returns. | |
| SmallSetVector<Instruction *, 8> PostProcessInsts; | |
| // Stores are processed after all other instructions/roots. | |
| SmallSetVector<StoreInst *, 8> PostProcessStores; | |
| SmallSetVector<Instruction *, 8> PoorThroughputSeeds; | |
| PoorThroughputOpCache PoorThroughputCache; | |
| auto VectorizeInsertsAndCmps = [&](bool AtTerminator) { | |
| bool Changed = vectorizeInserts(PostProcessInserts, BB, R, FMACandidates); | |
| if (AtTerminator) { | |
| Changed |= | |
| vectorizeCmpInsts(reverse(PostProcessCmps), BB, R, FMACandidates); | |
| PostProcessCmps.clear(); | |
| if (!PostProcessInsts.empty()) | |
| Changed |= vectorizeNonVectorizableInsts(reverse(PostProcessInsts), BB, | |
| R, FMACandidates); | |
| PostProcessInsts.clear(); | |
| } | |
| PostProcessInserts.clear(); | |
| return Changed; | |
| }; | |
| // Returns true if `I` is in any of the post-process sets. | |
| auto IsInPostProcessInstrs = [&](Instruction *I) { | |
| if (auto *Cmp = dyn_cast<CmpInst>(I)) | |
| return PostProcessCmps.contains(Cmp); | |
| if (PostProcessInsts.contains(I)) | |
| return true; | |
| if (auto *SI = dyn_cast<StoreInst>(I)) | |
| return PostProcessStores.contains(SI); | |
| return isa<InsertElementInst, InsertValueInst>(I) && | |
| PostProcessInserts.contains(I); | |
| }; | |
| // Returns true if `I` is an instruction without users, like terminator, or | |
| // function call with ignored return value, store. Ignore unused instructions | |
| // (basing on instruction type, except for CallInst and InvokeInst). | |
| auto HasNoUsers = [](Instruction *I) { | |
| return I->use_empty() && | |
| (I->getType()->isVoidTy() || isa<CallInst, InvokeInst>(I)); | |
| }; | |
| // The number of the stores to each underlying object, which may start the | |
| // reduction. Vectorized stores and stores of loads or non-instruction values | |
| // never start it. | |
| SmallDenseMap<Value *, unsigned> NumRdxRootStores; | |
| for (const auto &[Obj, SIs] : Stores) | |
| NumRdxRootStores[Obj] = count_if(SIs, [&](StoreInst *SI) { | |
| return !R.isDeleted(SI) && isa<Instruction>(SI->getValueOperand()) && | |
| !isa<LoadInst>(SI->getValueOperand()); | |
| }); | |
| for (BasicBlock::iterator It = BB->begin(), E = BB->end(); It != E; ++It) { | |
| // Skip instructions with scalable type. The num of elements is unknown at | |
| // compile-time for scalable type. | |
| if (isa<ScalableVectorType>(It->getType())) | |
| continue; | |
| // Skip instructions marked for the deletion. | |
| if (R.isDeleted(&*It)) | |
| continue; | |
| // We may go through BB multiple times so skip the one we have checked. | |
| if (!VisitedInstrs.insert(&*It).second) { | |
| if (HasNoUsers(&*It) && | |
| VectorizeInsertsAndCmps(/*AtTerminator=*/It->isTerminator())) { | |
| // We would like to start over since some instructions are deleted | |
| // and the iterator may become invalid value. | |
| Changed = true; | |
| It = BB->begin(); | |
| E = BB->end(); | |
| } | |
| continue; | |
| } | |
| // Try to vectorize reductions that use PHINodes. | |
| if (PHINode *P = dyn_cast<PHINode>(It)) { | |
| // Check that the PHI is a reduction PHI. | |
| if (P->getNumIncomingValues() == 2) { | |
| // Try to match and vectorize a horizontal reduction. | |
| Instruction *Root = getReductionInstr(DT, P, BB, LI); | |
| if (Root && vectorizeRootInstruction(P, Root, BB, R, FMACandidates)) { | |
| Changed = true; | |
| It = BB->begin(); | |
| E = BB->end(); | |
| continue; | |
| } | |
| } | |
| // Try to vectorize the incoming values of the PHI, to catch reductions | |
| // that feed into PHIs. | |
| for (unsigned I : seq<unsigned>(P->getNumIncomingValues())) { | |
| // Skip if the incoming block is the current BB for now. Also, bypass | |
| // unreachable IR for efficiency and to avoid crashing. | |
| // TODO: Collect the skipped incoming values and try to vectorize them | |
| // after processing BB. | |
| if (BB == P->getIncomingBlock(I) || | |
| !DT->isReachableFromEntry(P->getIncomingBlock(I))) | |
| continue; | |
| // Postponed instructions should not be vectorized here, delay their | |
| // vectorization. | |
| if (auto *PI = dyn_cast<Instruction>(P->getIncomingValue(I)); | |
| PI && !IsInPostProcessInstrs(PI)) { | |
| bool Res = vectorizeRootInstruction( | |
| nullptr, PI, P->getIncomingBlock(I), R, FMACandidates); | |
| Changed |= Res; | |
| if (Res && R.isDeleted(P)) { | |
| It = BB->begin(); | |
| E = BB->end(); | |
| break; | |
| } | |
| } | |
| } | |
| continue; | |
| } | |
| if (HasNoUsers(&*It)) { | |
| bool OpsChanged = false; | |
| auto *SI = dyn_cast<StoreInst>(It); | |
| bool TryToVectorizeRoot = Opts->slp_vectorize_hor_store || !SI; | |
| if (SI) { | |
| // Try to vectorize chain in store, if at most one store to the address | |
| // in the block may start the reduction. | |
| // TODO: This is just a temporarily solution to save compile time. Need | |
| // to investigate if we can safely turn on slp-vectorize-hor-store | |
| // instead to allow lookup for reduction chains in all non-vectorized | |
| // stores (need to check side effects and compile time). | |
| TryToVectorizeRoot |= NumRdxRootStores.lookup(getUnderlyingObject( | |
| SI->getPointerOperand())) <= 1 && | |
| SI->getValueOperand()->hasOneUse(); | |
| } | |
| if (TryToVectorizeRoot) { | |
| for (auto *V : It->operand_values()) { | |
| // Postponed instructions should not be vectorized here, delay their | |
| // vectorization. | |
| if (auto *VI = dyn_cast<Instruction>(V); | |
| VI && !IsInPostProcessInstrs(VI)) | |
| // Try to match and vectorize a horizontal reduction. | |
| OpsChanged |= | |
| vectorizeRootInstruction(nullptr, VI, BB, R, FMACandidates); | |
| } | |
| } | |
| // Start vectorization of post-process list of instructions from the | |
| // top-tree instructions to try to vectorize as many instructions as | |
| // possible. | |
| OpsChanged |= | |
| VectorizeInsertsAndCmps(/*AtTerminator=*/It->isTerminator()); | |
| if (OpsChanged) { | |
| // We would like to start over since some instructions are deleted | |
| // and the iterator may become invalid value. | |
| Changed = true; | |
| It = BB->begin(); | |
| E = BB->end(); | |
| continue; | |
| } | |
| } | |
| // The values, feeding only the assumes, and the instructions, producing no | |
| // code, are dropped before the codegen, the tree is rejected for them | |
| // anyway. | |
| if (isAssumeLikeIntrinsic(&*It) || isa<PseudoProbeInst>(It) || | |
| R.isEphemeralValue(&*It)) | |
| continue; | |
| if (isa<InsertElementInst, InsertValueInst>(It)) | |
| PostProcessInserts.insert(&*It); | |
| else if (auto *CI = dyn_cast<CmpInst>(It)) { | |
| if (OrigCmps.contains(CI)) | |
| PostProcessCmps.insert(CI); | |
| } else if (auto *SI = dyn_cast<StoreInst>(It); | |
| SI && | |
| (Opts->slp_revec || | |
| !SI->getValueOperand()->getType()->isVectorTy()) && | |
| isa<Instruction>(SI->getValueOperand())) | |
| PostProcessStores.insert(SI); | |
| else if (isNonVectorizableInst(*Opts, &*It, TLI)) | |
| PostProcessInsts.insert(&*It); | |
| else if (Opts->slp_vectorize_poor_throughput && | |
| isPoorThroughputOp(*Opts, &*It, *TTI, *TLI, PoorThroughputCache, | |
| R.getCostKind(), R.getMaxVecRegSize())) | |
| PoorThroughputSeeds.insert(&*It); | |
| } | |
| // Late post-process: run operand-chain vectorization for stores. | |
| if (!PostProcessStores.empty() && | |
| (Opts->slp_non_vectorizables_as_reductions || | |
| PostProcessStores.size() >= 2)) { | |
| bool TryVectorize = true; | |
| if (!Opts->slp_postprocess_stores_operands && R.getCostThreshold() >= 0) { | |
| // Use pessimistic cost estimation to avoid long compile time when there | |
| // are many stores in the list. | |
| Type *ScalarTy = getValueType(PostProcessStores.front(), Opts->slp_revec); | |
| if (!::isValidElementType(ScalarTy, Opts->slp_revec)) { | |
| TryVectorize = false; | |
| } else { | |
| auto *IF = | |
| dyn_cast<Instruction>(PostProcessStores.front()->getValueOperand()); | |
| auto *IB = | |
| dyn_cast<Instruction>(PostProcessStores.back()->getValueOperand()); | |
| if (!Opts->slp_non_vectorizables_as_reductions && | |
| PostProcessStores.size() == 2 && | |
| (!IF || !IB || IF->getOpcode() != IB->getOpcode())) { | |
| TryVectorize = false; | |
| } else { | |
| ScalarTy = IntegerType::get( | |
| ScalarTy->getContext(), | |
| DL->getTypeSizeInBits(ScalarTy->getScalarType())); | |
| if (auto *ValTy = dyn_cast<VectorType>( | |
| PostProcessStores.front()->getValueOperand()->getType())) | |
| ScalarTy = ::getWidenedType(ScalarTy, getNumElements(ValTy)); | |
| auto *VecTy = cast<VectorType>( | |
| ::getWidenedType(ScalarTy, PostProcessStores.size())); | |
| InstructionCost ExtractsCost = getScalarizationOverhead( | |
| *TTI, Opts->slp_revec, ScalarTy, VecTy, | |
| APInt::getAllOnes(PostProcessStores.size()), | |
| /*Insert=*/false, /*Extract=*/true, R.getCostKind(), | |
| /*ForPoisonSrc=*/true, {}, TTI::VectorInstrContext::Store); | |
| TryVectorize = ExtractsCost <= PostProcessStores.size() + 1; | |
| } | |
| } | |
| } | |
| if (TryVectorize) { | |
| Changed |= vectorizeNonVectorizableInsts(reverse(PostProcessStores), BB, | |
| R, FMACandidates); | |
| } | |
| } | |
| Changed |= vectorizePoorThroughputSeeds(PoorThroughputSeeds.getArrayRef(), R); | |
| return Changed; | |
| } | |
| bool SLPVectorizerPass::vectorizePoorThroughputSeeds( | |
| ArrayRef<Instruction *> Candidates, BoUpSLP &R, | |
| function_ref<bool(ArrayRef<Value *>)> Filter) { | |
| if (Candidates.size() < 2) | |
| return false; | |
| SmallVector<Value *> Seeds; | |
| SmallDenseMap<Value *, SeedGroupKey> SeedKeys; | |
| for (Instruction *I : make_filter_range(Candidates, [&](Instruction *I) { | |
| return !R.isDeleted(I) && | |
| isValidElementType(getValueType(I, Opts->slp_revec), | |
| Opts->slp_revec) && | |
| !R.hasResolvedUser(I); | |
| })) { | |
| SeedKeys.try_emplace(I, getSeedGroupKey(I, *TLI)); | |
| Seeds.push_back(I); | |
| } | |
| return vectorizeSeeds( | |
| Seeds, | |
| [&](Value *V1, Value *V2) { | |
| return SeedKeys.at(V1).less(SeedKeys.at(V2)); | |
| }, | |
| R, Filter); | |
| } | |
| // Seed the groups with scalars not stored locally (e.g. the store is sunk to | |
| // the join block) before the store chains split them into a remainder too | |
| // small to vectorize. | |
| bool SLPVectorizerPass::vectorizeSplitStorePoorThroughputSeeds(BasicBlock *BB, | |
| BoUpSLP &R) { | |
| if (!Opts->slp_vectorize_poor_throughput || Stores.empty()) | |
| return false; | |
| auto HasLocalStoreUser = [&](Value *V) { | |
| return any_of(V->users(), [&](User *U) { | |
| auto *SI = dyn_cast<StoreInst>(U); | |
| return SI && SI->isSimple() && SI->getParent() == BB && | |
| SI->getValueOperand() == V; | |
| }); | |
| }; | |
| auto IsCandidate = [&](Instruction &I, PoorThroughputOpCache &Cache) { | |
| return !R.isDeleted(&I) && !isAssumeLikeIntrinsic(&I) && | |
| !isa<PseudoProbeInst>(I) && !R.isEphemeralValue(&I) && | |
| !isNonVectorizableInst(*Opts, &I, TLI) && | |
| isPoorThroughputOp(*Opts, &I, *TTI, *TLI, Cache, R.getCostKind(), | |
| R.getMaxVecRegSize()); | |
| }; | |
| // The groups without the stored scalars are rejected, so the block is | |
| // skipped if no stored scalar is a candidate. The stored scalars are checked | |
| // in the block order, a scalar rejected here is rejected by the full scan. | |
| PoorThroughputOpCache StoredCache; | |
| if (none_of(*BB, [&](Instruction &I) { | |
| return HasLocalStoreUser(&I) && IsCandidate(I, StoredCache); | |
| })) | |
| return false; | |
| PoorThroughputOpCache PoorThroughputCache; | |
| SmallVector<Instruction *> Candidates; | |
| for (Instruction &I : *BB) | |
| if (IsCandidate(I, PoorThroughputCache)) | |
| Candidates.push_back(&I); | |
| return vectorizePoorThroughputSeeds(Candidates, R, [&](ArrayRef<Value *> VL) { | |
| // The scalars not stored locally must widen the stored scalars, left by | |
| // the store chains after the full vectors. | |
| unsigned MaxVF = bit_floor(R.getMaxVecRegSize() / | |
| DL->getTypeSizeInBits(VL.front()->getType())); | |
| unsigned NumStored = count_if(VL, HasLocalStoreUser); | |
| unsigned Rem = MaxVF ? NumStored % MaxVF : 0; | |
| return Rem != 0 && bit_floor(Rem) < bit_floor(VL.size() - NumStored + Rem); | |
| }); | |
| } | |
| bool SLPVectorizerPass::vectorizeSeeds( | |
| SmallVectorImpl<Value *> &Seeds, | |
| function_ref<bool(Value *, Value *)> IsLessGroup, BoUpSLP &R, | |
| function_ref<bool(ArrayRef<Value *>)> Filter) { | |
| if (Seeds.size() < 2) | |
| return false; | |
| auto SeedSorter = [&](Value *V1, Value *V2) { | |
| if (V1 == V2) | |
| return false; | |
| auto *I1 = cast<Instruction>(V1); | |
| auto *I2 = cast<Instruction>(V2); | |
| Type *T1 = I1->getType(); | |
| Type *T2 = I2->getType(); | |
| if (T1->getTypeID() != T2->getTypeID()) | |
| return T1->getTypeID() < T2->getTypeID(); | |
| if (T1->getScalarSizeInBits() != T2->getScalarSizeInBits()) | |
| return T1->getScalarSizeInBits() < T2->getScalarSizeInBits(); | |
| if (IsLessGroup(V1, V2)) | |
| return true; | |
| if (IsLessGroup(V2, V1)) | |
| return false; | |
| return I1->comesBefore(I2); | |
| }; | |
| auto AreCompatibleSeeds = [&](ArrayRef<Value *> VL, Value *V) { | |
| if (VL.empty() || VL.back() == V) | |
| return true; | |
| return cast<Instruction>(VL.back())->getType() == | |
| cast<Instruction>(V)->getType() && | |
| !IsLessGroup(VL.back(), V) && !IsLessGroup(V, VL.back()); | |
| }; | |
| return tryToVectorizeSequence<Value>( | |
| *Opts, Seeds, SeedSorter, AreCompatibleSeeds, | |
| [this, &R](ArrayRef<Value *> Candidates, bool MaxVFOnly) { | |
| // The same list may be retried for the different groups of the same | |
| // type, the earlier rejection is still valid for the block. | |
| if (R.isAnalyzedBundle(Candidates)) | |
| return false; | |
| if (tryToVectorizeList(Candidates, R, MaxVFOnly, | |
| /*StandaloneSeeds=*/true)) | |
| return true; | |
| R.analyzedBundle(Candidates); | |
| return false; | |
| }, | |
| /*MaxVFOnly=*/false, R, Filter); | |
| } | |
| bool SLPVectorizerPass::vectorizeOnceUsedSeeds(BasicBlock *BB, BoUpSLP &R) { | |
| // Loads are not seeds, their subkeys do not affect the grouping. | |
| auto GenerateLoadsSubkey = [](size_t, LoadInst *LI) { | |
| return hash_value(LI->getPointerOperand()); | |
| }; | |
| SmallVector<Value *> Seeds; | |
| SmallDenseMap<std::pair<size_t, size_t>, unsigned> KeyToGroup; | |
| SmallDenseMap<Value *, unsigned> SeedGroups; | |
| PoorThroughputOpCache PoorThroughputCache; | |
| for (Instruction &I : make_filter_range(*BB, [&](Instruction &I) { | |
| return !R.isDeleted(&I) && I.hasOneUse() && !R.isEphemeralValue(&I) && | |
| !R.isVectorized(&I) && !R.isAnalyzedScalar(&I) && | |
| isValidElementType(getValueType(&I, Opts->slp_revec), | |
| Opts->slp_revec) && | |
| isOnceUsedSeed(&I) && !isNonVectorizableInst(*Opts, &I, TLI) && | |
| !R.hasResolvedUser(&I); | |
| })) { | |
| // The poor-throughput ops are seeded on their own, with the different | |
| // grouping. | |
| if (Opts->slp_vectorize_poor_throughput && | |
| isPoorThroughputOp(*Opts, &I, *TTI, *TLI, PoorThroughputCache, | |
| R.getCostKind(), R.getMaxVecRegSize())) | |
| continue; | |
| // The multiplication is contracted into the scalar FMA with its user, the | |
| // vector node breaks the contraction. | |
| if (I.getOpcode() == Instruction::FMul) { | |
| auto *U = cast<Instruction>(I.user_back()); | |
| if (InstructionsState S = getSameOpcode(U, *TLI); | |
| S && S.isAddSubLikeOp() && | |
| canConvertToFMA(U, S, *DT, *DL, *TTI, *TLI, R).isValid()) | |
| continue; | |
| } | |
| // The keys are hashes, so the groups are numbered by the first seed to | |
| // keep the order deterministic. | |
| std::pair<size_t, size_t> Key = generateKeySubkey( | |
| &I, TLI, GenerateLoadsSubkey, /*AllowAlternate=*/false); | |
| SeedGroups.try_emplace( | |
| &I, KeyToGroup.try_emplace(Key, KeyToGroup.size()).first->second); | |
| Seeds.push_back(&I); | |
| } | |
| return vectorizeSeeds( | |
| Seeds, | |
| [&](Value *V1, Value *V2) { | |
| return SeedGroups.at(V1) < SeedGroups.at(V2); | |
| }, | |
| R); | |
| } | |
| bool SLPVectorizerPass::vectorizeGEPIndices(BasicBlock *BB, BoUpSLP &R) { | |
| auto Changed = false; | |
| const Loop *L = LI->getLoopFor(BB); | |
| auto IsAffineInLoop = [L](const SCEV *S) { | |
| const auto *AddRec = dyn_cast<SCEVAddRecExpr>(S); | |
| return AddRec && AddRec->getLoop() == L && AddRec->isAffine(); | |
| }; | |
| // If the getelementptr list has fewer than two elements, there's nothing | |
| // to do. | |
| for (auto &Entry : make_filter_range( | |
| GEPs, [](auto &Entry) { return Entry.second.size() >= 2; })) { | |
| LLVM_DEBUG(dbgs() << "SLP: Analyzing a getelementptr list of length " | |
| << Entry.second.size() << ".\n"); | |
| // Process the GEP list in chunks suitable for the target's supported | |
| // vector size. If a vector register can't hold 1 element, we are done. We | |
| // are trying to vectorize the index computations, so the maximum number of | |
| // elements is based on the size of the index expression, rather than the | |
| // size of the GEP itself (the target's pointer size). | |
| auto *It = find_if(Entry.second, [&](GetElementPtrInst *GEP) { | |
| return !R.isDeleted(GEP); | |
| }); | |
| if (It == Entry.second.end()) | |
| continue; | |
| unsigned MaxVecRegSize = R.getMaxVecRegSize(); | |
| unsigned EltSize = R.getVectorElementSize(*(*It)->idx_begin()); | |
| if (MaxVecRegSize < EltSize) | |
| continue; | |
| unsigned MaxElts = MaxVecRegSize / EltSize; | |
| for (unsigned BI = 0, BE = Entry.second.size(); BI < BE; BI += MaxElts) { | |
| auto Len = std::min<unsigned>(BE - BI, MaxElts); | |
| ArrayRef<GetElementPtrInst *> GEPList(&Entry.second[BI], Len); | |
| // Initialize a set a candidate getelementptrs. Note that we use a | |
| // SetVector here to preserve program order. If the index computations | |
| // are vectorizable and begin with loads, we want to minimize the chance | |
| // of having to reorder them later. | |
| SetVector<Value *> Candidates(llvm::from_range, GEPList); | |
| // Some of the candidates may have already been vectorized after we | |
| // initially collected them or their index is optimized to constant value. | |
| // If so, they are marked as deleted, so remove them from the set of | |
| // candidates. | |
| Candidates.remove_if([&R](Value *I) { | |
| return R.isDeleted(cast<Instruction>(I)) || | |
| isa<Constant>(cast<GetElementPtrInst>(I)->idx_begin()->get()); | |
| }); | |
| // Remove from the set of candidates all pairs of getelementptrs with | |
| // constant differences. Such getelementptrs are likely not good | |
| // candidates for vectorization in a bottom-up phase since one can be | |
| // computed from the other. The same holds for affine recurrences of the | |
| // enclosing loop that differ by a loop-invariant amount: loop strength | |
| // reduction turns them into pointer increments. We also ensure all | |
| // candidate getelementptr indices are unique. | |
| for (int I = 0, E = GEPList.size(); I < E && Candidates.size() > 1; ++I) { | |
| auto *GEPI = GEPList[I]; | |
| if (!Candidates.count(GEPI)) | |
| continue; | |
| const SCEV *SCEVI = SE->getSCEV(GEPList[I]); | |
| bool IsAffineI = IsAffineInLoop(SCEVI); | |
| for (int J = I + 1; J < E && Candidates.size() > 1; ++J) { | |
| auto *GEPJ = GEPList[J]; | |
| if (!Candidates.count(GEPJ)) | |
| continue; | |
| const SCEV *SCEVJ = SE->getSCEV(GEPList[J]); | |
| const SCEV *Diff = SE->getMinusSCEV(SCEVI, SCEVJ); | |
| if (isa<SCEVConstant>(Diff) || (IsAffineI && IsAffineInLoop(SCEVJ) && | |
| SE->isLoopInvariant(Diff, L))) { | |
| Candidates.remove(GEPI); | |
| Candidates.remove(GEPJ); | |
| } else if (GEPI->idx_begin()->get() == GEPJ->idx_begin()->get()) { | |
| Candidates.remove(GEPJ); | |
| } | |
| } | |
| } | |
| // We break out of the above computation as soon as we know there are | |
| // fewer than two candidates remaining. | |
| if (Candidates.size() < 2) | |
| continue; | |
| // Add the single, non-constant index of each candidate to the bundle. We | |
| // ensured the indices met these constraints when we originally collected | |
| // the getelementptrs. | |
| SmallVector<Value *, 16> Bundle(Candidates.size()); | |
| auto BundleIndex = 0u; | |
| for (auto *V : Candidates) { | |
| auto *GEP = cast<GetElementPtrInst>(V); | |
| auto *GEPIdx = GEP->idx_begin()->get(); | |
| assert(GEP->getNumIndices() == 1 && !isa<Constant>(GEPIdx)); | |
| Bundle[BundleIndex++] = GEPIdx; | |
| } | |
| // Try and vectorize the indices. We are currently only interested in | |
| // gather-like cases of the form: | |
| // | |
| // ... = g[a[0] - b[0]] + g[a[1] - b[1]] + ... | |
| // | |
| // where the loads of "a", the loads of "b", and the subtractions can be | |
| // performed in parallel. It's likely that detecting this pattern in a | |
| // bottom-up phase will be simpler and less costly than building a | |
| // full-blown top-down phase beginning at the consecutive loads. | |
| Changed |= tryToVectorizeList(Bundle, R); | |
| } | |
| } | |
| return Changed; | |
| } | |
| bool SLPVectorizerPass::vectorizeStoreChains(BoUpSLP &R) { | |
| bool Changed = false; | |
| // Sort by type, base pointers and values operand. Value operands must be | |
| // compatible (have the same opcode, same parent), otherwise it is | |
| // definitely not profitable to try to vectorize them. | |
| auto &&StoreSorter = [this](StoreInst *V, StoreInst *V2) { | |
| if (V->getValueOperand()->getType()->getTypeID() < | |
| V2->getValueOperand()->getType()->getTypeID()) | |
| return true; | |
| if (V->getValueOperand()->getType()->getTypeID() > | |
| V2->getValueOperand()->getType()->getTypeID()) | |
| return false; | |
| if (V->getPointerOperandType()->getTypeID() < | |
| V2->getPointerOperandType()->getTypeID()) | |
| return true; | |
| if (V->getPointerOperandType()->getTypeID() > | |
| V2->getPointerOperandType()->getTypeID()) | |
| return false; | |
| if (V->getValueOperand()->getType()->getScalarSizeInBits() < | |
| V2->getValueOperand()->getType()->getScalarSizeInBits()) | |
| return true; | |
| if (V->getValueOperand()->getType()->getScalarSizeInBits() > | |
| V2->getValueOperand()->getType()->getScalarSizeInBits()) | |
| return false; | |
| // UndefValues are compatible with all other values. | |
| auto *I1 = dyn_cast<Instruction>(V->getValueOperand()); | |
| auto *I2 = dyn_cast<Instruction>(V2->getValueOperand()); | |
| if (I1 && I2) { | |
| DomTreeNodeBase<llvm::BasicBlock> *NodeI1 = DT->getNode(I1->getParent()); | |
| DomTreeNodeBase<llvm::BasicBlock> *NodeI2 = DT->getNode(I2->getParent()); | |
| assert(NodeI1 && "Should only process reachable instructions"); | |
| assert(NodeI2 && "Should only process reachable instructions"); | |
| assert((NodeI1 == NodeI2) == | |
| (NodeI1->getDFSNumIn() == NodeI2->getDFSNumIn()) && | |
| "Different nodes should have different DFS numbers"); | |
| if (NodeI1 != NodeI2) | |
| return NodeI1->getDFSNumIn() < NodeI2->getDFSNumIn(); | |
| return I1->getOpcode() < I2->getOpcode(); | |
| } | |
| if (I1 && !I2) | |
| return true; | |
| if (!I1 && I2) | |
| return false; | |
| return V->getValueOperand()->getValueID() < | |
| V2->getValueOperand()->getValueID(); | |
| }; | |
| bool SameParent = true; | |
| auto AreCompatibleStores = [&](ArrayRef<StoreInst *> VL, StoreInst *V1) { | |
| if (VL.empty()) { | |
| SameParent = true; | |
| return true; | |
| } | |
| StoreInst *V2 = VL.back(); | |
| if (V1 == V2) | |
| return true; | |
| if (V1->getValueOperand()->getType() != V2->getValueOperand()->getType()) | |
| return false; | |
| if (V1->getPointerOperandType() != V2->getPointerOperandType()) | |
| return false; | |
| // Undefs are compatible with any other value. | |
| if (isa<UndefValue>(V1->getValueOperand()) || | |
| isa<UndefValue>(V2->getValueOperand())) | |
| return true; | |
| if (isa<Constant>(V1->getValueOperand()) && | |
| isa<Constant>(V2->getValueOperand())) | |
| return true; | |
| // Check if the operands of the stores can be vectorized. They can be | |
| // vectorized, if they have compatible operands or have operands, which can | |
| // be vectorized as copyables. | |
| auto *I1 = dyn_cast<Instruction>(V1->getValueOperand()); | |
| auto *I2 = dyn_cast<Instruction>(V2->getValueOperand()); | |
| if (I1 || I2) { | |
| // Accept only tail-following non-compatible values for now. | |
| // TODO: investigate if it is possible to vectorize incompatible values, | |
| // if the copyables are first in the list. | |
| if (I1 && !I2) | |
| return false; | |
| SameParent &= I1 && I2 && I1->getParent() == I2->getParent(); | |
| SmallVector<Value *> NewVL(VL.size() + 1); | |
| for (auto [SI, V] : zip(VL, NewVL)) | |
| V = SI->getValueOperand(); | |
| NewVL.back() = V1->getValueOperand(); | |
| InstructionsCompatibilityAnalysis Analysis(*DT, *DL, *TTI, *TLI); | |
| InstructionsState S = Analysis.buildInstructionsState( | |
| NewVL, R, /*WithProfitabilityCheck=*/true, | |
| /*SkipSameCodeCheck=*/!SameParent); | |
| // A copyable GEP node with non-constant indices is vectorizable only as | |
| // the address vector of a masked gather, not as a stored value. | |
| if (S && S.getOpcode() == Instruction::GetElementPtr && | |
| S.areInstructionsWithCopyableElements() && | |
| any_of(NewVL, [&](Value *V) { | |
| auto *GEP = dyn_cast<GetElementPtrInst>(V); | |
| return GEP && !S.isCopyableElement(V) && | |
| !isa<ConstantInt>(GEP->getOperand(1)); | |
| })) | |
| return false; | |
| if (S) | |
| return true; | |
| if (!SameParent) | |
| return false; | |
| } | |
| return V1->getValueOperand()->getValueID() == | |
| V2->getValueOperand()->getValueID(); | |
| }; | |
| // Returns true if any two stores in the group are at consecutive (stride-1) | |
| // addresses. Masked stores are disabled for the entire base-object group when | |
| // this is true - consecutive vectorization is always preferred. | |
| auto HasConsecutiveStoresOrSameAddress = [&](ArrayRef<StoreInst *> SIs) { | |
| if (SIs.size() < 2) | |
| return false; | |
| Type *Ty = SIs.front()->getValueOperand()->getType(); | |
| Value *Base = SIs.front()->getPointerOperand(); | |
| SmallVector<int64_t> Offsets; | |
| Offsets.reserve(SIs.size()); | |
| for (StoreInst *SI : SIs) { | |
| if (std::optional<int64_t> Off = | |
| getPointersDiff(Ty, Base, Ty, SI->getPointerOperand(), *DL, *SE)) | |
| Offsets.push_back(*Off); | |
| } | |
| sort(Offsets); | |
| return any_of(seq<size_t>(1, Offsets.size()), [&](size_t I) { | |
| return Offsets[I] - Offsets[I - 1] == 1 || Offsets[I] == Offsets[I - 1]; | |
| }); | |
| }; | |
| // Attempt to sort and vectorize each of the store-groups. | |
| DenseSet<std::tuple<Value *, Value *, Value *, Value *, unsigned>> Attempted; | |
| // The longer groups go first: the shorter ones sharing the scalars with them | |
| // then reuse their vectors instead of splitting the longer chains. | |
| SmallVector<StoreListMap::value_type *> Groups(make_pointer_range(Stores)); | |
| stable_sort(Groups, [](const auto *A, const auto *B) { | |
| return A->second.size() > B->second.size(); | |
| }); | |
| auto TryToVectorizeGroup = [&](StoreListMap::value_type &Pair) { | |
| LLVM_DEBUG(dbgs() << "SLP: Analyzing a store chain of length " | |
| << Pair.second.size() << ".\n"); | |
| if (!isValidElementType(Pair.second.front()->getValueOperand()->getType(), | |
| Opts->slp_revec)) | |
| return false; | |
| // Masked stores are only attempted when no consecutive pair exists in the | |
| // full base-object group. | |
| const bool AllowMaskedStores = | |
| Opts->slp_enable_masked_stores && | |
| !HasConsecutiveStoresOrSameAddress(Pair.second); | |
| // Reverse stores to do bottom-to-top analysis. This is important if the | |
| // values are stores to the same addresses several times, in this case need | |
| // to follow the stores order (reversed to meet the memory dependecies). | |
| SmallVector<StoreInst *> ReversedStores(Pair.second.rbegin(), | |
| Pair.second.rend()); | |
| return tryToVectorizeSequence<StoreInst>( | |
| *Opts, ReversedStores, StoreSorter, AreCompatibleStores, | |
| [&](ArrayRef<StoreInst *> Candidates, bool) { | |
| return vectorizeStores(Candidates, R, Attempted, AllowMaskedStores); | |
| }, | |
| /*MaxVFOnly=*/false, R); | |
| }; | |
| // The groups, rejected before the vectorized one, were analyzed for the | |
| // original scalars and may become profitable now, so they are retried once. | |
| SmallVector<StoreListMap::value_type *> Rejected; | |
| unsigned NumToRetry = 0; | |
| for (auto &Pair : | |
| make_filter_range(make_pointee_range(Groups), | |
| [](auto &Pair) { return Pair.second.size() >= 2; })) { | |
| if (TryToVectorizeGroup(Pair)) { | |
| Changed = true; | |
| NumToRetry = Rejected.size(); | |
| } else { | |
| Rejected.push_back(&Pair); | |
| } | |
| } | |
| Attempted.clear(); | |
| for (auto *Pair : ArrayRef(Rejected).take_front(NumToRetry)) | |
| Changed |= TryToVectorizeGroup(*Pair); | |
| return Changed; | |
| } |