| //===- SLPUtils.cpp - SLP Vectorizer free utility helpers -----------------===// |
| // |
| // 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 |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "SLPUtils.h" |
| |
| #include "llvm/ADT/APInt.h" |
| #include "llvm/ADT/STLExtras.h" |
| #include "llvm/ADT/Sequence.h" |
| #include "llvm/ADT/SmallPtrSet.h" |
| #include "llvm/Analysis/AssumptionCache.h" |
| #include "llvm/Analysis/ValueTracking.h" |
| #include "llvm/Analysis/VectorUtils.h" |
| #include "llvm/IR/Constants.h" |
| #include "llvm/IR/DataLayout.h" |
| #include "llvm/IR/DebugInfo.h" |
| #include "llvm/IR/DerivedTypes.h" |
| #include "llvm/IR/IRBuilder.h" |
| #include "llvm/IR/Instructions.h" |
| #include "llvm/IR/IntrinsicInst.h" |
| #include "llvm/IR/PatternMatch.h" |
| #include "llvm/Support/Casting.h" |
| #include "llvm/Support/MathExtras.h" |
| #include "llvm/Support/raw_ostream.h" |
| |
| #include <algorithm> |
| #include <numeric> |
| #include <string> |
| #include <type_traits> |
| |
| using namespace llvm; |
| using namespace llvm::PatternMatch; |
| |
| namespace llvm::slpvectorizer { |
| |
| bool isConstant(Value *V) { |
| return isa<Constant>(V) && !isa<ConstantExpr, GlobalValue>(V); |
| } |
| |
| bool isBinOpIdentityConstant(const Value *V, unsigned Opcode) { |
| const auto *CI = dyn_cast<ConstantInt>(V); |
| return CI && ConstantExpr::getBinOpIdentity(Opcode, CI->getType()) == CI; |
| } |
| |
| unsigned getReassocCombineOpcode(unsigned Opcode) { |
| switch (Opcode) { |
| case Instruction::Sub: |
| return Instruction::Add; |
| case Instruction::FSub: |
| return Instruction::FAdd; |
| default: |
| return Opcode; |
| } |
| } |
| |
| bool isReassocChainLink(const Instruction *I) { |
| if (I->getOpcode() == Instruction::Sub) |
| return true; |
| if (I->getOpcode() == Instruction::FSub) |
| return I->hasAllowReassoc(); |
| return I->isAssociative(); |
| } |
| |
| bool isVectorLikeInstWithConstOps(Value *V) { |
| auto *I = dyn_cast<Instruction>(V); |
| // Non-instructions are vector-like only if they are undef. |
| if (!I) |
| return isa<UndefValue>(V); |
| switch (I->getOpcode()) { |
| case Instruction::ExtractValue: |
| case Instruction::InsertValue: |
| return true; |
| case Instruction::ExtractElement: |
| return isa<FixedVectorType>(I->getOperand(0)->getType()) && |
| isConstant(I->getOperand(1)); |
| case Instruction::InsertElement: |
| return isa<FixedVectorType>(I->getOperand(0)->getType()) && |
| isConstant(I->getOperand(2)); |
| default: |
| return false; |
| } |
| } |
| |
| unsigned getNumElements(Type *Ty) { |
| assert(!isa<ScalableVectorType>(Ty) && |
| "ScalableVectorType is not supported."); |
| if (isVectorizedTy(Ty)) |
| return getVectorizedTypeVF(Ty).getFixedValue(); |
| return 1; |
| } |
| |
| unsigned getPartNumElems(unsigned Size, unsigned NumParts) { |
| return std::min<unsigned>(Size, bit_ceil(divideCeil(Size, NumParts))); |
| } |
| |
| unsigned getNumElems(unsigned Size, unsigned PartNumElems, unsigned Part) { |
| return std::min<unsigned>(PartNumElems, Size - Part * PartNumElems); |
| } |
| |
| #if !defined(NDEBUG) |
| std::string shortBundleName(ArrayRef<Value *> VL, int Idx) { |
| std::string Result; |
| raw_string_ostream OS(Result); |
| if (Idx >= 0) |
| OS << "Idx: " << Idx << ", "; |
| OS << "n=" << VL.size() << " [" << *VL.front() << ", ..]"; |
| return Result; |
| } |
| #endif |
| |
| bool allSameBlock(ArrayRef<Value *> VL) { |
| auto *It = find_if(VL, IsaPred<Instruction>); |
| if (It == VL.end()) |
| return false; |
| Instruction *I0 = cast<Instruction>(*It); |
| if (all_of(VL, isVectorLikeInstWithConstOps)) |
| return true; |
| |
| BasicBlock *BB = I0->getParent(); |
| for (Value *V : make_filter_range(iterator_range(It, VL.end()), [](Value *V) { |
| return !isa<PoisonValue>(V); |
| })) { |
| auto *II = dyn_cast<Instruction>(V); |
| if (!II) |
| return false; |
| |
| if (BB != II->getParent()) |
| return false; |
| } |
| return true; |
| } |
| |
| bool allConstant(ArrayRef<Value *> VL) { |
| // Constant expressions and globals can't be vectorized like normal integer/FP |
| // constants. |
| return all_of(VL, isConstant); |
| } |
| |
| bool isSplat(ArrayRef<Value *> VL) { |
| Value *FirstNonUndef = nullptr; |
| for (Value *V : |
| make_filter_range(VL, [](Value *V) { return !isa<UndefValue>(V); })) { |
| if (!FirstNonUndef) { |
| FirstNonUndef = V; |
| continue; |
| } |
| if (V != FirstNonUndef) |
| return false; |
| } |
| return FirstNonUndef != nullptr; |
| } |
| |
| Intrinsic::ID isEquivalentIntrinsicID(Intrinsic::ID LHS, Intrinsic::ID RHS) { |
| if (LHS == RHS) |
| return RHS; |
| if ((LHS == Intrinsic::fma || LHS == Intrinsic::fmuladd) && |
| (RHS == Intrinsic::fma || RHS == Intrinsic::fmuladd)) |
| return Intrinsic::fma; |
| return Intrinsic::not_intrinsic; |
| } |
| |
| bool isCommutative(const Instruction *I, const Value *ValWithUses, |
| bool IsCopyable) { |
| if (auto *Cmp = dyn_cast<CmpInst>(I)) |
| return Cmp->isCommutative(); |
| if (auto *BO = dyn_cast<BinaryOperator>(I)) |
| return BO->isCommutative() || |
| (BO->getOpcode() == Instruction::Sub && ValWithUses->hasUseList() && |
| !ValWithUses->hasNUsesOrMore(UsesLimit) && |
| all_of( |
| ValWithUses->uses(), |
| [&](const Use &U) { |
| // Commutative, if icmp eq/ne sub, 0 |
| CmpPredicate Pred; |
| if (match(U.getUser(), |
| m_ICmp(Pred, m_Specific(U.get()), m_Zero())) && |
| (Pred == ICmpInst::ICMP_EQ || Pred == ICmpInst::ICMP_NE)) |
| return true; |
| // Commutative, if abs(sub nsw, true) or abs(sub, false). |
| ConstantInt *Flag; |
| auto *I = dyn_cast<BinaryOperator>(U.get()); |
| return match(U.getUser(), |
| m_Intrinsic<Intrinsic::abs>( |
| m_Specific(U.get()), m_ConstantInt(Flag))) && |
| ((!IsCopyable && I && !I->hasNoSignedWrap()) || |
| Flag->isOne()); |
| })) || |
| (BO->getOpcode() == Instruction::FSub && ValWithUses->hasUseList() && |
| !ValWithUses->hasNUsesOrMore(UsesLimit) && |
| all_of(ValWithUses->uses(), [](const Use &U) { |
| return match(U.getUser(), |
| m_Intrinsic<Intrinsic::fabs>(m_Specific(U.get()))); |
| })); |
| return I->isCommutative(); |
| } |
| |
| bool isCommutative(const Instruction *I) { return isCommutative(I, I); } |
| |
| bool isCommutableOperand(const Instruction *I, Value *ValWithUses, unsigned Op, |
| bool IsCopyable) { |
| assert(isCommutative(I, ValWithUses, IsCopyable) && |
| "The instruction is not commutative."); |
| if (isa<CmpInst>(I)) |
| return true; |
| if (auto *BO = dyn_cast<BinaryOperator>(I)) { |
| switch (BO->getOpcode()) { |
| case Instruction::Sub: |
| case Instruction::FSub: |
| return true; |
| default: |
| break; |
| } |
| } |
| return I->isCommutableOperand(Op); |
| } |
| |
| unsigned getNumberOfPotentiallyCommutativeOps(Instruction *I) { |
| if (isa<IntrinsicInst>(I) && isCommutative(I)) { |
| // IntrinsicInst::isCommutative returns true if swapping the first "two" |
| // arguments to the intrinsic produces the same result. |
| constexpr unsigned IntrinsicNumOperands = 2; |
| return IntrinsicNumOperands; |
| } |
| return I->getNumOperands(); |
| } |
| |
| std::optional<unsigned> getElementIndex(const Value *Inst, unsigned Offset) { |
| if (auto Index = getInsertExtractIndex<InsertElementInst>(Inst, Offset)) |
| return Index; |
| if (auto Index = getInsertExtractIndex<ExtractElementInst>(Inst, Offset)) |
| return Index; |
| |
| unsigned Index = Offset; |
| |
| const auto *IV = dyn_cast<InsertValueInst>(Inst); |
| if (!IV) |
| return std::nullopt; |
| |
| Type *CurrentType = IV->getType(); |
| for (unsigned I : IV->indices()) { |
| if (const auto *ST = dyn_cast<StructType>(CurrentType)) { |
| Index *= ST->getNumElements(); |
| CurrentType = ST->getElementType(I); |
| } else if (const auto *AT = dyn_cast<ArrayType>(CurrentType)) { |
| Index *= AT->getNumElements(); |
| CurrentType = AT->getElementType(); |
| } else { |
| return std::nullopt; |
| } |
| Index += I; |
| } |
| return Index; |
| } |
| |
| bool allSameOpcode(ArrayRef<Value *> VL) { |
| auto *It = find_if(VL, IsaPred<Instruction>); |
| if (It == VL.end()) |
| return true; |
| Instruction *MainOp = cast<Instruction>(*It); |
| unsigned Opcode = MainOp->getOpcode(); |
| bool IsCmpOp = isa<CmpInst>(MainOp); |
| CmpInst::Predicate BasePred = IsCmpOp ? cast<CmpInst>(MainOp)->getPredicate() |
| : CmpInst::BAD_ICMP_PREDICATE; |
| return all_of(make_range(It, VL.end()), [&](Value *V) { |
| if (auto *CI = dyn_cast<CmpInst>(V)) |
| return BasePred == CI->getPredicate(); |
| if (auto *I = dyn_cast<Instruction>(V)) |
| return I->getOpcode() == Opcode; |
| return isa<PoisonValue>(V); |
| }); |
| } |
| |
| std::optional<unsigned> getExtractIndex(const Instruction *E) { |
| unsigned Opcode = E->getOpcode(); |
| assert((Opcode == Instruction::ExtractElement || |
| Opcode == Instruction::ExtractValue) && |
| "Expected extractelement or extractvalue instruction."); |
| if (Opcode == Instruction::ExtractElement) { |
| auto *CI = dyn_cast<ConstantInt>(E->getOperand(1)); |
| if (!CI) |
| return std::nullopt; |
| // Check if the index is out of bound. We can get the source vector from |
| // operand 0. |
| unsigned Idx = CI->getZExtValue(); |
| auto *EE = cast<ExtractElementInst>(E); |
| const unsigned VF = getNumElements(EE->getVectorOperandType()); |
| if (Idx >= VF) |
| return std::nullopt; |
| return Idx; |
| } |
| auto *EI = cast<ExtractValueInst>(E); |
| if (EI->getNumIndices() != 1) |
| return std::nullopt; |
| return *EI->idx_begin(); |
| } |
| |
| void inversePermutation(ArrayRef<unsigned> Indices, |
| SmallVectorImpl<int> &Mask) { |
| Mask.clear(); |
| const unsigned E = Indices.size(); |
| Mask.resize(E, PoisonMaskElem); |
| for (unsigned I = 0; I < E; ++I) |
| Mask[Indices[I]] = I; |
| } |
| |
| void reorderScalars(SmallVectorImpl<Value *> &Scalars, ArrayRef<int> Mask) { |
| assert(!Mask.empty() && "Expected non-empty mask."); |
| SmallVector<Value *> Prev(Scalars.size(), |
| PoisonValue::get(Scalars.front()->getType())); |
| Prev.swap(Scalars); |
| for (unsigned I = 0, E = Prev.size(); I < E; ++I) |
| if (Mask[I] != PoisonMaskElem) |
| Scalars[Mask[I]] = Prev[I]; |
| } |
| |
| void reorderReuses(SmallVectorImpl<int> &Reuses, ArrayRef<int> Mask) { |
| assert(!Mask.empty() && Reuses.size() == Mask.size() && |
| "Expected non-empty mask."); |
| SmallVector<int> Prev(Reuses.begin(), Reuses.end()); |
| Prev.swap(Reuses); |
| for (unsigned I = 0, E = Prev.size(); I < E; ++I) |
| if (Mask[I] != PoisonMaskElem) |
| Reuses[Mask[I]] = Prev[I]; |
| } |
| |
| void reorderOrder(SmallVectorImpl<unsigned> &Order, ArrayRef<int> Mask, |
| bool BottomOrder) { |
| assert(!Mask.empty() && "Expected non-empty mask."); |
| unsigned Sz = Mask.size(); |
| if (BottomOrder) { |
| SmallVector<unsigned> PrevOrder; |
| if (Order.empty()) { |
| PrevOrder.resize(Sz); |
| std::iota(PrevOrder.begin(), PrevOrder.end(), 0); |
| } else { |
| PrevOrder.swap(Order); |
| } |
| Order.assign(Sz, Sz); |
| for (unsigned I = 0; I < Sz; ++I) |
| if (Mask[I] != PoisonMaskElem) |
| Order[I] = PrevOrder[Mask[I]]; |
| if (all_of(enumerate(Order), [&](const auto &Data) { |
| return Data.value() == Sz || Data.index() == Data.value(); |
| })) { |
| Order.clear(); |
| return; |
| } |
| fixupOrderingIndices(Order); |
| return; |
| } |
| SmallVector<int> MaskOrder; |
| if (Order.empty()) { |
| MaskOrder.resize(Sz); |
| std::iota(MaskOrder.begin(), MaskOrder.end(), 0); |
| } else { |
| inversePermutation(Order, MaskOrder); |
| } |
| reorderReuses(MaskOrder, Mask); |
| if (ShuffleVectorInst::isIdentityMask(MaskOrder, Sz)) { |
| Order.clear(); |
| return; |
| } |
| Order.assign(Sz, Sz); |
| for (unsigned I = 0; I < Sz; ++I) |
| if (MaskOrder[I] != PoisonMaskElem) |
| Order[MaskOrder[I]] = I; |
| fixupOrderingIndices(Order); |
| } |
| |
| bool isReverseOrder(ArrayRef<unsigned> Order) { |
| assert(!Order.empty() && |
| "Order is empty. Please check it before using isReverseOrder."); |
| unsigned Sz = Order.size(); |
| return all_of(enumerate(Order), [&](const auto &Pair) { |
| return Pair.value() == Sz || Sz - Pair.index() - 1 == Pair.value(); |
| }); |
| } |
| |
| bool isRepeatedNonIdentityClusteredMask(ArrayRef<int> Mask, unsigned Sz) { |
| ArrayRef<int> FirstCluster = Mask.slice(0, Sz); |
| if (ShuffleVectorInst::isIdentityMask(FirstCluster, Sz)) |
| return false; |
| for (unsigned I = Sz, E = Mask.size(); I < E; I += Sz) { |
| ArrayRef<int> Cluster = Mask.slice(I, Sz); |
| if (Cluster != FirstCluster) |
| return false; |
| } |
| return true; |
| } |
| |
| void combineOrders(MutableArrayRef<unsigned> Order, |
| ArrayRef<unsigned> SecondaryOrder) { |
| assert((SecondaryOrder.empty() || Order.size() == SecondaryOrder.size()) && |
| "Expected same size of orders"); |
| size_t Sz = Order.size(); |
| SmallBitVector UsedIndices(Sz); |
| for (unsigned Idx : seq<unsigned>(0, Sz)) { |
| if (Order[Idx] != Sz) |
| UsedIndices.set(Order[Idx]); |
| } |
| if (SecondaryOrder.empty()) { |
| for (unsigned Idx : seq<unsigned>(0, Sz)) |
| if (Order[Idx] == Sz && !UsedIndices.test(Idx)) |
| Order[Idx] = Idx; |
| } else { |
| for (unsigned Idx : seq<unsigned>(0, Sz)) |
| if (SecondaryOrder[Idx] != Sz && Order[Idx] == Sz && |
| !UsedIndices.test(SecondaryOrder[Idx])) |
| Order[Idx] = SecondaryOrder[Idx]; |
| } |
| } |
| |
| bool allSameType(ArrayRef<Value *> VL) { |
| assert(!VL.empty() && "Expected non-empty list of values."); |
| Type *Ty = VL.consume_front()->getType(); |
| return all_of(VL, [&](Value *V) { return V->getType() == Ty; }); |
| } |
| |
| template <typename T> |
| std::optional<unsigned> getInsertExtractIndex(const Value *Inst, |
| unsigned Offset) { |
| static_assert(std::is_same_v<T, InsertElementInst> || |
| std::is_same_v<T, ExtractElementInst>, |
| "unsupported T"); |
| const auto *IE = dyn_cast<T>(Inst); |
| if (!IE) |
| return std::nullopt; |
| // InsertElement: result is the vector, index is op 2. |
| // ExtractElement: result is scalar, vector is op 0, index is op 1. |
| constexpr bool IsInsert = std::is_same_v<T, InsertElementInst>; |
| Type *VecTy = IsInsert ? IE->getType() : IE->getOperand(0)->getType(); |
| const auto *VT = dyn_cast<FixedVectorType>(VecTy); |
| if (!VT) |
| return std::nullopt; |
| const auto *CI = dyn_cast<ConstantInt>(IE->getOperand(IsInsert ? 2 : 1)); |
| if (!CI) |
| return std::nullopt; |
| if (CI->getValue().uge(VT->getNumElements())) |
| return std::nullopt; |
| unsigned Index = Offset; |
| Index *= VT->getNumElements(); |
| Index += CI->getZExtValue(); |
| return Index; |
| } |
| |
| // Only these two specializations are used; instantiate them here so the |
| // definition can stay out of the header. |
| template std::optional<unsigned> |
| getInsertExtractIndex<InsertElementInst>(const Value *, unsigned); |
| template std::optional<unsigned> |
| getInsertExtractIndex<ExtractElementInst>(const Value *, unsigned); |
| |
| bool areAllOperandsNonInsts(Value *V) { |
| auto *I = dyn_cast<Instruction>(V); |
| if (!I) |
| return true; |
| return !mayHaveNonDefUseDependency(*I) && |
| all_of(make_isa_range<Instruction>(I->operands()), |
| [I](Instruction *IO) { |
| return isa<PHINode>(IO) || IO->getParent() != I->getParent(); |
| }); |
| } |
| |
| bool isUsedOutsideBlock(Value *V) { |
| auto *I = dyn_cast<Instruction>(V); |
| if (!I) |
| return true; |
| // Limits the number of uses to save compile time. |
| return !I->mayReadOrWriteMemory() && !I->hasNUsesOrMore(UsesLimit) && |
| all_of(I->users(), [I](User *U) { |
| auto *IU = dyn_cast<Instruction>(U); |
| if (!IU) |
| return true; |
| return IU->getParent() != I->getParent() || isa<PHINode>(IU); |
| }); |
| } |
| |
| bool doesNotNeedToBeScheduled(Value *V) { |
| return areAllOperandsNonInsts(V) && isUsedOutsideBlock(V); |
| } |
| |
| bool doesNotNeedToSchedule(ArrayRef<Value *> VL) { |
| return !VL.empty() && |
| (all_of(VL, isUsedOutsideBlock) || all_of(VL, areAllOperandsNonInsts)); |
| } |
| |
| void transformScalarShuffleIndiciesToVector(unsigned VecTyNumElements, |
| SmallVectorImpl<int> &Mask) { |
| // The ShuffleBuilder implementation use shufflevector to splat an "element". |
| // But the element have different meaning for SLP (scalar) and REVEC |
| // (vector). We need to expand Mask into masks which shufflevector can use |
| // directly. |
| SmallVector<int> NewMask(Mask.size() * VecTyNumElements); |
| for (unsigned I : seq<unsigned>(Mask.size())) |
| for (auto [J, MaskV] : enumerate(MutableArrayRef(NewMask).slice( |
| I * VecTyNumElements, VecTyNumElements))) |
| MaskV = Mask[I] == PoisonMaskElem ? PoisonMaskElem |
| : Mask[I] * VecTyNumElements + J; |
| Mask.swap(NewMask); |
| } |
| |
| unsigned getShufflevectorNumGroups(ArrayRef<Value *> VL) { |
| if (VL.empty()) |
| return 0; |
| if (!all_of(VL, IsaPred<ShuffleVectorInst>)) |
| return 0; |
| auto *SV = cast<ShuffleVectorInst>(VL.front()); |
| unsigned SVNumElements = |
| cast<FixedVectorType>(SV->getOperand(0)->getType())->getNumElements(); |
| unsigned ShuffleMaskSize = SV->getShuffleMask().size(); |
| if (SVNumElements % ShuffleMaskSize != 0) |
| return 0; |
| unsigned GroupSize = SVNumElements / ShuffleMaskSize; |
| if (GroupSize == 0 || (VL.size() % GroupSize) != 0) |
| return 0; |
| unsigned NumGroup = 0; |
| for (size_t I = 0, E = VL.size(); I != E; I += GroupSize) { |
| auto *SV = cast<ShuffleVectorInst>(VL[I]); |
| Value *Src = SV->getOperand(0); |
| ArrayRef<Value *> Group = VL.slice(I, GroupSize); |
| SmallBitVector ExpectedIndex(GroupSize); |
| if (!all_of(Group, [&](Value *V) { |
| auto *SV = cast<ShuffleVectorInst>(V); |
| // From the same source. |
| if (SV->getOperand(0) != Src) |
| return false; |
| int Index; |
| if (!SV->isExtractSubvectorMask(Index)) |
| return false; |
| ExpectedIndex.set(Index / ShuffleMaskSize); |
| return true; |
| })) |
| return 0; |
| if (!ExpectedIndex.all()) |
| return 0; |
| ++NumGroup; |
| } |
| assert(NumGroup == (VL.size() / GroupSize) && "Unexpected number of groups"); |
| return NumGroup; |
| } |
| |
| SmallVector<int> calculateShufflevectorMask(ArrayRef<Value *> VL) { |
| assert(getShufflevectorNumGroups(VL) && "Not supported shufflevector usage."); |
| auto *SV = cast<ShuffleVectorInst>(VL.front()); |
| unsigned SVNumElements = |
| cast<FixedVectorType>(SV->getOperand(0)->getType())->getNumElements(); |
| SmallVector<int> Mask; |
| unsigned AccumulateLength = 0; |
| for (Value *V : VL) { |
| auto *SV = cast<ShuffleVectorInst>(V); |
| for (int M : SV->getShuffleMask()) |
| Mask.push_back(M == PoisonMaskElem ? PoisonMaskElem |
| : AccumulateLength + M); |
| AccumulateLength += SVNumElements; |
| } |
| return Mask; |
| } |
| |
| /// Checks if the vector of instructions can be represented as a shuffle, like: |
| /// %x0 = extractelement <4 x i8> %x, i32 0 |
| /// %x3 = extractelement <4 x i8> %x, i32 3 |
| /// %y1 = extractelement <4 x i8> %y, i32 1 |
| /// %y2 = extractelement <4 x i8> %y, i32 2 |
| /// %x0x0 = mul i8 %x0, %x0 |
| /// %x3x3 = mul i8 %x3, %x3 |
| /// %y1y1 = mul i8 %y1, %y1 |
| /// %y2y2 = mul i8 %y2, %y2 |
| /// %ins1 = insertelement <4 x i8> poison, i8 %x0x0, i32 0 |
| /// %ins2 = insertelement <4 x i8> %ins1, i8 %x3x3, i32 1 |
| /// %ins3 = insertelement <4 x i8> %ins2, i8 %y1y1, i32 2 |
| /// %ins4 = insertelement <4 x i8> %ins3, i8 %y2y2, i32 3 |
| /// ret <4 x i8> %ins4 |
| /// can be transformed into: |
| /// %1 = shufflevector <4 x i8> %x, <4 x i8> %y, <4 x i32> <i32 0, i32 3, i32 5, |
| /// i32 6> |
| /// %2 = mul <4 x i8> %1, %1 |
| /// ret <4 x i8> %2 |
| /// Mask will return the Shuffle Mask equivalent to the extracted elements. |
| /// TODO: Can we split off and reuse the shuffle mask detection from |
| /// ShuffleVectorInst/getShuffleCost? |
| std::optional<TargetTransformInfo::ShuffleKind> |
| isFixedVectorShuffle(ArrayRef<Value *> VL, SmallVectorImpl<int> &Mask, |
| AssumptionCache *AC) { |
| const auto *It = find_if(VL, IsaPred<ExtractElementInst>); |
| if (It == VL.end()) |
| return std::nullopt; |
| unsigned Size = accumulate(VL, 0u, [](unsigned S, Value *V) { |
| auto *EI = dyn_cast<ExtractElementInst>(V); |
| if (!EI) |
| return S; |
| auto *VTy = dyn_cast<FixedVectorType>(EI->getVectorOperandType()); |
| if (!VTy) |
| return S; |
| return std::max(S, VTy->getNumElements()); |
| }); |
| |
| Value *Vec1 = nullptr; |
| Value *Vec2 = nullptr; |
| bool HasNonUndefVec = any_of(make_isa_range<ExtractElementInst>(VL), |
| [&](ExtractElementInst *EE) { |
| Value *Vec = EE->getVectorOperand(); |
| if (isa<UndefValue>(Vec)) |
| return false; |
| return isGuaranteedNotToBePoison(Vec, AC); |
| }); |
| enum ShuffleMode { Unknown, Select, Permute }; |
| ShuffleMode CommonShuffleMode = Unknown; |
| Mask.assign(VL.size(), PoisonMaskElem); |
| for (unsigned I = 0, E = VL.size(); I < E; ++I) { |
| // Undef, or a copyable lane modeled on an extract main op, can be |
| // represented as an undef element in a vector. |
| if (isa<UndefValue>(VL[I])) |
| continue; |
| auto *EI = dyn_cast<ExtractElementInst>(VL[I]); |
| if (!EI) |
| continue; |
| if (isa<ScalableVectorType>(EI->getVectorOperandType())) |
| return std::nullopt; |
| auto *Vec = EI->getVectorOperand(); |
| // We can extractelement from undef or poison vector. |
| if (isUndefVector</*isPoisonOnly=*/true>(Vec).all()) |
| continue; |
| // All vector operands must have the same number of vector elements. |
| if (isa<UndefValue>(Vec)) { |
| Mask[I] = I; |
| } else { |
| if (isa<UndefValue>(EI->getIndexOperand())) |
| continue; |
| auto *Idx = dyn_cast<ConstantInt>(EI->getIndexOperand()); |
| if (!Idx) |
| return std::nullopt; |
| // Undefined behavior if Idx is negative or >= Size. |
| if (Idx->getValue().uge(Size)) |
| continue; |
| unsigned IntIdx = Idx->getValue().getZExtValue(); |
| Mask[I] = IntIdx; |
| } |
| if (isUndefVector(Vec).all() && HasNonUndefVec) |
| continue; |
| // For correct shuffling we have to have at most 2 different vector operands |
| // in all extractelement instructions. |
| if (!Vec1 || Vec1 == Vec) { |
| Vec1 = Vec; |
| } else if (!Vec2 || Vec2 == Vec) { |
| Vec2 = Vec; |
| Mask[I] += Size; |
| } else { |
| return std::nullopt; |
| } |
| if (CommonShuffleMode == Permute) |
| continue; |
| // If the extract index is not the same as the operation number, it is a |
| // permutation. |
| if (Mask[I] % Size != I) { |
| CommonShuffleMode = Permute; |
| continue; |
| } |
| CommonShuffleMode = Select; |
| } |
| // If we're not crossing lanes in different vectors, consider it as blending. |
| if (CommonShuffleMode == Select && Vec2) |
| return TargetTransformInfo::SK_Select; |
| // If Vec2 was never used, we have a permutation of a single vector, otherwise |
| // we have permutation of 2 vectors. |
| return Vec2 ? TargetTransformInfo::SK_PermuteTwoSrc |
| : TargetTransformInfo::SK_PermuteSingleSrc; |
| } |
| |
| Value *createInsertVector( |
| IRBuilderBase &Builder, Value *Vec, Value *V, unsigned Index, |
| function_ref<Value *(Value *, Value *, ArrayRef<int>)> Generator) { |
| if (isa<PoisonValue>(Vec) && isa<PoisonValue>(V)) |
| return Vec; |
| const unsigned SubVecVF = getNumElements(V->getType()); |
| // Create shuffle, insertvector requires that index is multiple of |
| // the subvector length. |
| const unsigned VecVF = getNumElements(Vec->getType()); |
| SmallVector<int> Mask(VecVF, PoisonMaskElem); |
| if (isa<PoisonValue>(Vec)) { |
| auto *Begin = std::next(Mask.begin(), Index); |
| std::iota(Begin, std::next(Begin, SubVecVF), 0); |
| Vec = Builder.CreateShuffleVector(V, Mask); |
| return Vec; |
| } |
| std::iota(Mask.begin(), Mask.end(), 0); |
| std::iota(std::next(Mask.begin(), Index), |
| std::next(Mask.begin(), Index + SubVecVF), VecVF); |
| if (Generator) |
| return Generator(Vec, V, Mask); |
| // 1. Resize V to the size of Vec. |
| SmallVector<int> ResizeMask(VecVF, PoisonMaskElem); |
| std::iota(ResizeMask.begin(), std::next(ResizeMask.begin(), SubVecVF), 0); |
| V = Builder.CreateShuffleVector(V, ResizeMask); |
| // 2. Insert V into Vec. |
| return Builder.CreateShuffleVector(Vec, V, Mask); |
| } |
| |
| Value *createExtractVector(IRBuilderBase &Builder, Value *Vec, |
| unsigned SubVecVF, unsigned Index) { |
| SmallVector<int> Mask(SubVecVF, PoisonMaskElem); |
| std::iota(Mask.begin(), Mask.end(), Index); |
| return Builder.CreateShuffleVector(Vec, Mask); |
| } |
| |
| SmallBitVector buildUseMask(int VF, ArrayRef<int> Mask, UseMask MaskArg) { |
| SmallBitVector UseMask(VF, true); |
| for (auto [Idx, Value] : enumerate(Mask)) { |
| if (Value == PoisonMaskElem) { |
| if (MaskArg == UseMask::UndefsAsMask) |
| UseMask.reset(Idx); |
| continue; |
| } |
| if (MaskArg == UseMask::FirstArg && Value < VF) |
| UseMask.reset(Value); |
| else if (MaskArg == UseMask::SecondArg && Value >= VF) |
| UseMask.reset(Value - VF); |
| } |
| return UseMask; |
| } |
| |
| template <bool IsPoisonOnly> |
| SmallBitVector isUndefVector(const Value *V, const SmallBitVector &UseMask) { |
| SmallBitVector Res(UseMask.empty() ? 1 : UseMask.size(), true); |
| using T = std::conditional_t<IsPoisonOnly, PoisonValue, UndefValue>; |
| if (isa<T>(V)) |
| return Res; |
| auto *VecTy = dyn_cast<FixedVectorType>(V->getType()); |
| if (!VecTy) |
| return Res.reset(); |
| auto *C = dyn_cast<Constant>(V); |
| if (!C) { |
| if (!UseMask.empty()) { |
| const Value *Base = V; |
| while (auto *II = dyn_cast<InsertElementInst>(Base)) { |
| Base = II->getOperand(0); |
| if (isa<T>(II->getOperand(1))) |
| continue; |
| std::optional<unsigned> Idx = getElementIndex(II); |
| if (!Idx) { |
| Res.reset(); |
| return Res; |
| } |
| if (*Idx < UseMask.size() && !UseMask.test(*Idx)) |
| Res.reset(*Idx); |
| } |
| // TODO: Add analysis for shuffles here too. |
| if (V == Base) { |
| Res.reset(); |
| } else { |
| SmallBitVector SubMask(UseMask.size(), false); |
| Res &= isUndefVector<IsPoisonOnly>(Base, SubMask); |
| } |
| } else { |
| Res.reset(); |
| } |
| return Res; |
| } |
| for (unsigned I = 0, E = VecTy->getNumElements(); I != E; ++I) { |
| if (Constant *Elem = C->getAggregateElement(I)) |
| if (!isa<T>(Elem) && |
| (UseMask.empty() || (I < UseMask.size() && !UseMask.test(I)))) |
| Res.reset(I); |
| } |
| return Res; |
| } |
| |
| template SmallBitVector isUndefVector<false>(const Value *, |
| const SmallBitVector &); |
| template SmallBitVector isUndefVector<true>(const Value *, |
| const SmallBitVector &); |
| |
| bool doesInTreeUserNeedToExtract(Value *Scalar, Instruction *UserInst, |
| TargetLibraryInfo *TLI, |
| const TargetTransformInfo *TTI) { |
| if (!UserInst) |
| return false; |
| unsigned Opcode = UserInst->getOpcode(); |
| switch (Opcode) { |
| case Instruction::Load: { |
| LoadInst *LI = cast<LoadInst>(UserInst); |
| return (LI->getPointerOperand() == Scalar); |
| } |
| case Instruction::Store: { |
| StoreInst *SI = cast<StoreInst>(UserInst); |
| return (SI->getPointerOperand() == Scalar); |
| } |
| case Instruction::Call: { |
| CallInst *CI = cast<CallInst>(UserInst); |
| Intrinsic::ID ID = getVectorIntrinsicIDForCall(CI, TLI); |
| return any_of(enumerate(CI->args()), [&](auto &&Arg) { |
| return isVectorIntrinsicWithScalarOpAtArg(ID, Arg.index(), TTI) && |
| Arg.value().get() == Scalar; |
| }); |
| } |
| default: |
| return false; |
| } |
| } |
| |
| MemoryLocation getLocation(Instruction *I) { |
| if (StoreInst *SI = dyn_cast<StoreInst>(I)) |
| return MemoryLocation::get(SI); |
| if (LoadInst *LI = dyn_cast<LoadInst>(I)) |
| return MemoryLocation::get(LI); |
| return MemoryLocation(); |
| } |
| |
| bool isSimple(Instruction *I) { |
| if (LoadInst *LI = dyn_cast<LoadInst>(I)) |
| return LI->isSimple(); |
| if (StoreInst *SI = dyn_cast<StoreInst>(I)) |
| return SI->isSimple(); |
| if (MemIntrinsic *MI = dyn_cast<MemIntrinsic>(I)) |
| return !MI->isVolatile(); |
| return true; |
| } |
| |
| bool isSelectedBaseLoad(Type *ScalarTy, ArrayRef<Value *> PointerOps, |
| const DataLayout &DL, Value *&TrueBase, |
| Value *&FalseBase, |
| SmallVectorImpl<Value *> &Conditions) { |
| TrueBase = nullptr; |
| FalseBase = nullptr; |
| uint64_t ScalarSize = DL.getTypeStoreSize(ScalarTy); |
| Conditions.assign(PointerOps.size(), nullptr); |
| for (auto [Idx, P] : enumerate(PointerOps)) { |
| Value *Base = P; |
| uint64_t Offset = 0; |
| if (auto *GEP = dyn_cast<GetElementPtrInst>(P)) { |
| APInt OffsetAP(DL.getIndexTypeSizeInBits(GEP->getType()), 0); |
| if (!GEP->accumulateConstantOffset(DL, OffsetAP) || OffsetAP.isNegative()) |
| return false; |
| Offset = OffsetAP.getZExtValue(); |
| Base = GEP->getPointerOperand(); |
| } |
| auto *Sel = dyn_cast<SelectInst>(Base); |
| if (!Sel) |
| return false; |
| Value *T = Sel->getTrueValue(); |
| Value *F = Sel->getFalseValue(); |
| if (!TrueBase) { |
| if (T == F) |
| return false; |
| TrueBase = T; |
| FalseBase = F; |
| } else if (TrueBase != T || FalseBase != F) { |
| return false; |
| } |
| // Lane Idx must be at exactly Base + Idx * sizeof(ScalarTy); codegen reads |
| // contiguously from TrueBase/FalseBase starting at lane 0. |
| if (Offset != static_cast<uint64_t>(Idx) * ScalarSize) |
| return false; |
| Conditions[Idx] = Sel->getCondition(); |
| } |
| return TrueBase != nullptr; |
| } |
| |
| Type *getCommonGEPIndexType(ArrayRef<Value *> VL, Instruction *VL0, |
| function_ref<bool(Value *)> IsGEPLane, |
| const DataLayout &DL) { |
| constexpr unsigned IndexIdx = 1; |
| Type *VL0Ty = VL0->getOperand(IndexIdx)->getType(); |
| Type *PtrIdxTy = |
| DL.getIndexType(VL0->getOperand(0)->getType()->getScalarType()); |
| bool AllSameTy = true; |
| bool HasNonConstIdx = false; |
| bool ConstsFitVL0Ty = true; |
| for (Value *V : make_filter_range(VL, IsGEPLane)) { |
| Value *Op = cast<GetElementPtrInst>(V)->getOperand(IndexIdx); |
| if (Op->getType() != VL0Ty) |
| AllSameTy = false; |
| auto *CI = dyn_cast<ConstantInt>(Op); |
| if (!CI) { |
| // Non-constant indices are not cast, they must have the main op type. |
| if (Op->getType() != VL0Ty) |
| return nullptr; |
| HasNonConstIdx = true; |
| continue; |
| } |
| if (!CI->getValue().isSignedIntN(VL0Ty->getIntegerBitWidth())) |
| ConstsFitVL0Ty = false; |
| } |
| if (AllSameTy) |
| return VL0Ty; |
| if (!HasNonConstIdx || VL0Ty == PtrIdxTy) |
| return PtrIdxTy; |
| return ConstsFitVL0Ty ? VL0Ty : nullptr; |
| } |
| |
| bool isCopyableGEPAddressVector(ArrayRef<Value *> PointerOps) { |
| SmallPtrSet<Value *, 16> UniquePtrs(llvm::from_range, PointerOps); |
| if (UniquePtrs.size() != PointerOps.size()) |
| return false; |
| auto IsConstantOffsetPtr = [](Value *P) { |
| auto *GEP = dyn_cast<GetElementPtrInst>(P); |
| return !GEP || |
| (GEP->getNumOperands() == 2 && isConstant(GEP->getOperand(1))); |
| }; |
| auto *RefIt = find_if_not(PointerOps, IsConstantOffsetPtr); |
| if (RefIt == PointerOps.end()) |
| return false; |
| auto *RefGEP = dyn_cast<GetElementPtrInst>(*RefIt); |
| if (!RefGEP || RefGEP->getNumOperands() != 2) |
| return false; |
| Value *Base = RefGEP->getPointerOperand(); |
| Type *PtrTy = RefGEP->getType(); |
| Type *SrcElemTy = RefGEP->getSourceElementType(); |
| // The stride and the (optional) cast opcode of the runtime indices. |
| Value *Stride = nullptr; |
| unsigned CastOpcode = 0; |
| for (Value *P : PointerOps) { |
| if (P->getType() != PtrTy) |
| return false; |
| if (P == Base) |
| continue; |
| auto *GEP = dyn_cast<GetElementPtrInst>(P); |
| if (!GEP || GEP->getNumOperands() != 2 || |
| GEP->getPointerOperand() != Base || |
| GEP->getSourceElementType() != SrcElemTy) |
| return false; |
| Value *Idx = GEP->getOperand(1); |
| if (isConstant(Idx)) |
| continue; |
| unsigned LaneCastOpcode = 0; |
| if (auto *Cast = dyn_cast<CastInst>(Idx)) { |
| LaneCastOpcode = Cast->getOpcode(); |
| Idx = Cast->getOperand(0); |
| } |
| Value *LaneStride = Idx; |
| if (auto *BO = dyn_cast<BinaryOperator>(Idx)) { |
| if (isa<Constant>(BO->getOperand(1))) |
| LaneStride = BO->getOperand(0); |
| else if (isa<Constant>(BO->getOperand(0))) |
| LaneStride = BO->getOperand(1); |
| } |
| if (!Stride) { |
| Stride = LaneStride; |
| CastOpcode = LaneCastOpcode; |
| continue; |
| } |
| if (LaneStride != Stride || LaneCastOpcode != CastOpcode) |
| return false; |
| } |
| return Stride != nullptr; |
| } |
| |
| void addMask(SmallVectorImpl<int> &Mask, ArrayRef<int> SubMask, |
| bool ExtendingManyInputs) { |
| if (SubMask.empty()) |
| return; |
| assert( |
| (!ExtendingManyInputs || SubMask.size() > Mask.size() || |
| // Check if input scalars were extended to match the size of other node. |
| (SubMask.size() == Mask.size() && Mask.back() == PoisonMaskElem)) && |
| "SubMask with many inputs support must be larger than the mask."); |
| if (Mask.empty()) { |
| Mask.append(SubMask.begin(), SubMask.end()); |
| return; |
| } |
| SmallVector<int> NewMask(SubMask.size(), PoisonMaskElem); |
| int TermValue = std::min(Mask.size(), SubMask.size()); |
| for (int I = 0, E = SubMask.size(); I < E; ++I) { |
| if (SubMask[I] == PoisonMaskElem || |
| (!ExtendingManyInputs && |
| (SubMask[I] >= TermValue || Mask[SubMask[I]] >= TermValue))) |
| continue; |
| NewMask[I] = Mask[SubMask[I]]; |
| } |
| Mask.swap(NewMask); |
| } |
| |
| void fixupOrderingIndices(MutableArrayRef<unsigned> Order) { |
| const size_t Sz = Order.size(); |
| SmallBitVector UnusedIndices(Sz, /*t=*/true); |
| SmallBitVector MaskedIndices(Sz); |
| for (unsigned I = 0; I < Sz; ++I) { |
| if (Order[I] < Sz) |
| UnusedIndices.reset(Order[I]); |
| else |
| MaskedIndices.set(I); |
| } |
| if (MaskedIndices.none()) |
| return; |
| assert(UnusedIndices.count() == MaskedIndices.count() && |
| "Non-synced masked/available indices."); |
| int Idx = UnusedIndices.find_first(); |
| int MIdx = MaskedIndices.find_first(); |
| while (MIdx >= 0) { |
| assert(Idx >= 0 && "Indices must be synced."); |
| Order[MIdx] = Idx; |
| Idx = UnusedIndices.find_next(Idx); |
| MIdx = MaskedIndices.find_next(MIdx); |
| } |
| } |
| |
| SmallBitVector getAltInstrMask(ArrayRef<Value *> VL, Type *ScalarTy, |
| unsigned Opcode0, unsigned Opcode1) { |
| unsigned ScalarTyNumElements = getNumElements(ScalarTy); |
| SmallBitVector OpcodeMask(VL.size() * ScalarTyNumElements, false); |
| for (unsigned Lane : seq<unsigned>(VL.size())) { |
| if (isa<PoisonValue>(VL[Lane])) |
| continue; |
| if (cast<Instruction>(VL[Lane])->getOpcode() == Opcode1) |
| OpcodeMask.set(Lane * ScalarTyNumElements, |
| Lane * ScalarTyNumElements + ScalarTyNumElements); |
| } |
| return OpcodeMask; |
| } |
| |
| SmallVector<Constant *> replicateMask(ArrayRef<Constant *> Val, unsigned VF) { |
| assert(none_of(Val, [](Constant *C) { return C->getType()->isVectorTy(); }) && |
| "Expected scalar constants."); |
| SmallVector<Constant *> NewVal(Val.size() * VF); |
| for (auto [I, V] : enumerate(Val)) |
| std::fill_n(NewVal.begin() + I * VF, VF, V); |
| return NewVal; |
| } |
| |
| Intrinsic::ID getMaskedDivRemIntrinsic(unsigned Opcode) { |
| switch (Opcode) { |
| case Instruction::UDiv: |
| return Intrinsic::masked_udiv; |
| case Instruction::SDiv: |
| return Intrinsic::masked_sdiv; |
| case Instruction::URem: |
| return Intrinsic::masked_urem; |
| case Instruction::SRem: |
| return Intrinsic::masked_srem; |
| default: |
| llvm_unreachable("Unexpected opcode"); |
| } |
| } |
| |
| /// Returns true if \p I is a part of a single-use chain, computing an address, |
| /// which does not pay off the vectorization: a constant table is accessed by a |
| /// gather, while the indices, unrelated between the lanes, require a full |
| /// buildvector, unlike the ones, shifted by a constant from a common base. |
| static bool isNonProfitableIndex(const Instruction *I) { |
| constexpr unsigned MaxIndexChainLength = 3; |
| // A constant shift of a common base is a cheap buildvector, while the loads |
| // are vectorized together with the indices, computed from them. |
| auto IsProfitableOperand = [](const Value *V) { |
| if (isa<Constant>(V)) |
| return true; |
| if (const auto *Cast = dyn_cast<CastInst>(V); Cast && Cast->hasOneUse()) |
| V = Cast->getOperand(0); |
| return isa<LoadInst>(V); |
| }; |
| const User *U = I->user_back(); |
| for ([[maybe_unused]] unsigned _ : seq<unsigned>(MaxIndexChainLength)) { |
| if (const auto *GEP = dyn_cast<GetElementPtrInst>(U)) |
| return isa<Constant>(GEP->getPointerOperand()) || |
| none_of(I->operand_values(), IsProfitableOperand); |
| if (!isa<Instruction>(U) || !U->hasOneUse()) |
| return false; |
| U = U->user_back(); |
| } |
| return false; |
| } |
| |
| bool isOnceUsedSeed(const Instruction *I) { |
| if (!I->hasOneUse() || isNonProfitableIndex(I)) |
| return false; |
| // The operation with the identity or the absorbing constant is folded away |
| // before the codegen, the vector node only repacks the lanes. |
| if (const auto *BO = dyn_cast<BinaryOperator>(I)) { |
| unsigned Opcode = BO->getOpcode(); |
| Type *Ty = BO->getType(); |
| for (unsigned Idx : seq<unsigned>(2)) { |
| const auto *C = dyn_cast<Constant>(BO->getOperand(Idx)); |
| if (C && (C == ConstantExpr::getBinOpIdentity( |
| Opcode, Ty, /*AllowRHSConstant=*/Idx == 1) || |
| C == ConstantExpr::getBinOpAbsorber( |
| Opcode, Ty, /*AllowLHSConstant=*/Idx == 0))) |
| return false; |
| } |
| } |
| const User *U = I->user_back(); |
| if (isa<ExtractElementInst, ExtractValueInst>(I)) |
| return isa<InsertElementInst, InsertValueInst>(U); |
| if (isa<CastInst>(I)) |
| return !isa<FPToSIInst, FPToUIInst>(I) && |
| (!isa<CastInst>(U) || U->hasOneUse()); |
| return isa<BinaryOperator, UnaryOperator, SelectInst, FreezeInst, CallInst>( |
| I); |
| } |
| |
| Instruction *lookThroughCastRoundTrip(Value *V, bool MustBeElidable) { |
| auto *Wide = dyn_cast<FPExtInst>(V); |
| if (!Wide || !Wide->hasOneUse()) |
| return nullptr; |
| auto *Narrow = dyn_cast<FPTruncInst>(Wide->getOperand(0)); |
| if (!Narrow || !Narrow->hasOneUse()) |
| return nullptr; |
| Value *Src = Narrow->getOperand(0); |
| if (!isa<Instruction>(Src) || Src->getType() != Wide->getType()) |
| return nullptr; |
| if (MustBeElidable && !(Wide->hasAllowContract() && Wide->hasNoNaNs() && |
| Wide->hasNoInfs() && Narrow->hasAllowContract())) |
| return nullptr; |
| return Narrow; |
| } |
| |
| namespace { |
| |
| /// Shifts and the mask accumulated from the narrow ops on the current path: |
| /// the shifts above and at the narrow level, the bitwidth of the narrow ops |
| /// (0 if none) and the mask from the absorbed narrow ands. |
| struct NarrowedChainState { |
| unsigned Shift = 0; |
| unsigned NarrowShift = 0; |
| unsigned NarrowBW = 0; |
| APInt NarrowMask = APInt(1, 0); |
| |
| /// The mask for the absorbed narrow ops in the leaf type, applied before |
| /// widening and shifting; all-ones if nothing was absorbed. |
| APInt getMask(unsigned LeafBW) const { |
| if (NarrowBW == 0) |
| return APInt::getAllOnes(LeafBW); |
| return (NarrowMask & (APInt::getAllOnes(NarrowBW) << NarrowShift)) |
| .lshr(NarrowShift) |
| .trunc(LeafBW); |
| } |
| }; |
| |
| } // namespace |
| |
| static void |
| collectNarrowedLeavesImpl(Value *V, unsigned RdxOpcode, unsigned WideBW, |
| NarrowedChainState S, unsigned Depth, |
| unsigned MaxDepth, |
| SmallVectorImpl<NarrowedLeafInfo> &Leaves, |
| SmallVectorImpl<Instruction *> &ChainInsts) { |
| if (Depth < MaxDepth) { |
| if (auto *Z = dyn_cast<ZExtInst>(V); |
| Z && Z->getSrcTy()->isIntegerTy() && !Z->getSrcTy()->isIntegerTy(1)) { |
| ChainInsts.push_back(Z); |
| return collectNarrowedLeavesImpl(Z->getOperand(0), RdxOpcode, WideBW, S, |
| Depth + 1, MaxDepth, Leaves, ChainInsts); |
| } |
| if (auto *BO = dyn_cast<BinaryOperator>(V)) { |
| if (BO->getOpcode() == RdxOpcode) { |
| ChainInsts.push_back(BO); |
| collectNarrowedLeavesImpl(BO->getOperand(0), RdxOpcode, WideBW, S, |
| Depth + 1, MaxDepth, Leaves, ChainInsts); |
| collectNarrowedLeavesImpl(BO->getOperand(1), RdxOpcode, WideBW, S, |
| Depth + 1, MaxDepth, Leaves, ChainInsts); |
| return; |
| } |
| const APInt *Amt; |
| unsigned BW = V->getType()->getScalarSizeInBits(); |
| auto *Z = dyn_cast<ZExtInst>(BO->getOperand(0)); |
| if (BO->getOpcode() == Instruction::Shl && Z && S.NarrowBW == 0 && |
| match(BO->getOperand(1), m_APInt(Amt)) && Amt->ult(BW) && |
| Z->getSrcTy()->isIntegerTy() && !Z->getSrcTy()->isIntegerTy(1) && |
| (BW == WideBW || |
| Z->getSrcTy()->getIntegerBitWidth() + Amt->getZExtValue() <= BW) && |
| S.Shift + Amt->getZExtValue() < WideBW) { |
| ChainInsts.push_back(BO); |
| ChainInsts.push_back(Z); |
| S.Shift += Amt->getZExtValue(); |
| return collectNarrowedLeavesImpl(Z->getOperand(0), RdxOpcode, WideBW, S, |
| Depth + 1, MaxDepth, Leaves, |
| ChainInsts); |
| } |
| // Narrow shls fold into the shift and narrow ands into the mask; the |
| // mask clears the bits the shls shift out. Only same-width ops compose |
| // on one path, and the combined shift must stay a valid shift amount in |
| // both types. |
| if (BW < WideBW && (S.NarrowBW == 0 || BW == S.NarrowBW)) { |
| if (BO->getOpcode() == Instruction::Shl && |
| match(BO->getOperand(1), m_APInt(Amt)) && Amt->ult(BW) && |
| S.NarrowShift + Amt->getZExtValue() < BW && |
| S.Shift + S.NarrowShift + Amt->getZExtValue() < WideBW) { |
| ChainInsts.push_back(BO); |
| if (BO->hasNoUnsignedWrap() && S.NarrowBW == 0) { |
| S.Shift += Amt->getZExtValue(); |
| // Lossless shls shift out only known-zero bits; record them as |
| // the mask so matching lanes can form a splat. |
| S.NarrowBW = BW; |
| S.NarrowMask = APInt::getLowBitsSet(BW, BW - Amt->getZExtValue()); |
| } else { |
| if (S.NarrowBW == 0) { |
| S.NarrowBW = BW; |
| S.NarrowMask = APInt::getAllOnes(BW); |
| } |
| S.NarrowShift += Amt->getZExtValue(); |
| } |
| return collectNarrowedLeavesImpl(BO->getOperand(0), RdxOpcode, WideBW, |
| S, Depth + 1, MaxDepth, Leaves, |
| ChainInsts); |
| } |
| Value *X; |
| if (match(BO, m_c_And(m_Value(X), m_APInt(Amt)))) { |
| ChainInsts.push_back(BO); |
| if (S.NarrowBW == 0) { |
| S.NarrowBW = BW; |
| S.NarrowMask = APInt::getAllOnes(BW); |
| } |
| S.NarrowMask &= *Amt << S.NarrowShift; |
| return collectNarrowedLeavesImpl(X, RdxOpcode, WideBW, S, Depth + 1, |
| MaxDepth, Leaves, ChainInsts); |
| } |
| } |
| } |
| } |
| Leaves.emplace_back(V, S.Shift + S.NarrowShift, |
| S.getMask(V->getType()->getScalarSizeInBits())); |
| } |
| |
| void collectNarrowedLeaves(Value *V, unsigned RdxOpcode, unsigned WideBW, |
| unsigned MaxDepth, |
| SmallVectorImpl<NarrowedLeafInfo> &Leaves, |
| SmallVectorImpl<Instruction *> &ChainInsts) { |
| collectNarrowedLeavesImpl(V, RdxOpcode, WideBW, NarrowedChainState(), |
| /*Depth=*/0, MaxDepth, Leaves, ChainInsts); |
| } |
| |
| TargetTransformInfo::TargetCostKind getSLPCostKind(const Function *F) { |
| assert(F && "Expected function."); |
| return F->hasOptSize() ? TTI::TCK_CodeSize : TTI::TCK_RecipThroughput; |
| } |
| |
| /// Checks if \p V is a zero-extended sub-field of a wider integer scalar. |
| /// Returns the source scalar, the field width and the field offset. |
| static std::optional<std::tuple<Value *, unsigned, unsigned>> |
| matchExtractedField(Value *V) { |
| if (!V->getType()->isIntegerTy()) |
| return std::nullopt; |
| // Field offset for the field-aligned shift amount, if the shifted value of |
| // the given bit width keeps at least one full field. |
| auto GetFieldOffset = [](const APInt *Amt, unsigned BitWidth, |
| unsigned FieldWidth) -> std::optional<unsigned> { |
| uint64_t ShAmt = Amt->getLimitedValue(BitWidth); |
| if (ShAmt % FieldWidth != 0 || ShAmt + FieldWidth > BitWidth) |
| return std::nullopt; |
| return ShAmt / FieldWidth; |
| }; |
| // Checks if the low bits of Val are a sub-field of the given width of a |
| // wider integer scalar. Val is a scalar integer, since V is one, and so is |
| // the matched source. |
| auto MatchLowField = |
| [&](Value *Val, |
| unsigned FieldWidth) -> std::optional<std::pair<Value *, unsigned>> { |
| Value *Src; |
| const APInt *Amt; |
| // Only the low bits of Val are observed, so lshr and ashr are equivalent. |
| if (match(Val, m_Trunc(m_Shr(m_Value(Src), m_APInt(Amt)))) || |
| match(Val, m_Shr(m_Value(Src), m_APInt(Amt)))) { |
| if (std::optional<unsigned> Offset = GetFieldOffset( |
| Amt, Src->getType()->getIntegerBitWidth(), FieldWidth)) { |
| // The truncation of the shifted value keeps the field, look through it. |
| match(Src, m_Trunc(m_Value(Src))); |
| return std::make_pair(Src, *Offset); |
| } |
| return std::nullopt; |
| } |
| if (match(Val, m_Trunc(m_Value(Src))) && |
| Src->getType()->getIntegerBitWidth() >= FieldWidth) |
| return std::make_pair(Src, 0u); |
| // Val itself is the source of its low field. |
| if (Val->getType()->getIntegerBitWidth() > FieldWidth) |
| return std::make_pair(Val, 0u); |
| return std::nullopt; |
| }; |
| Value *Val; |
| const APInt *Mask; |
| // and Val, (1 << FieldWidth) - 1 or zext i<FieldWidth> Val - the low bits of |
| // Val. |
| unsigned FieldWidth = 0; |
| if (match(V, m_c_And(m_Value(Val), m_APInt(Mask))) && Mask->isMask()) |
| FieldWidth = Mask->popcount(); |
| else if (match(V, m_ZExt(m_Value(Val)))) |
| FieldWidth = Val->getType()->getIntegerBitWidth(); |
| if (FieldWidth != 0) { |
| if (std::optional<std::pair<Value *, unsigned>> Field = |
| MatchLowField(Val, FieldWidth)) |
| return std::make_tuple(Field->first, FieldWidth, Field->second); |
| return std::nullopt; |
| } |
| unsigned LaneWidth = V->getType()->getIntegerBitWidth(); |
| Value *Src; |
| const APInt *Amt; |
| if (match(V, m_Trunc(m_LShr(m_Value(Src), m_APInt(Amt))))) { |
| unsigned SrcWidth = Src->getType()->getIntegerBitWidth(); |
| uint64_t ShAmt = Amt->getLimitedValue(SrcWidth); |
| // The field itself, if the lane width is the field width. |
| if (std::optional<unsigned> Offset = |
| GetFieldOffset(Amt, SrcWidth, LaneWidth)) |
| return std::make_tuple(Src, LaneWidth, *Offset); |
| // The zero-extended top field of the source. |
| unsigned FieldWidth = SrcWidth - ShAmt; |
| if (FieldWidth > 0 && FieldWidth < LaneWidth && ShAmt % FieldWidth == 0) |
| return std::make_tuple(Src, FieldWidth, ShAmt / FieldWidth); |
| return std::nullopt; |
| } |
| if (match(V, m_LShr(m_Value(Src), m_APInt(Amt)))) { |
| // The zero-extended top field of the source, if the result keeps exactly |
| // one field. Look through a truncation of the shifted value. |
| unsigned ShfWidth = Src->getType()->getIntegerBitWidth(); |
| uint64_t ShAmt = Amt->getLimitedValue(ShfWidth); |
| unsigned FieldWidth = ShfWidth - ShAmt; |
| if (FieldWidth > 0 && ShAmt % FieldWidth == 0) { |
| match(Src, m_Trunc(m_Value(Src))); |
| return std::make_tuple(Src, FieldWidth, ShAmt / FieldWidth); |
| } |
| return std::nullopt; |
| } |
| if (match(V, m_Trunc(m_Value(Src)))) |
| return std::make_tuple(Src, LaneWidth, 0u); |
| return std::nullopt; |
| } |
| |
| std::optional<std::tuple<Value *, unsigned, SmallVector<int>>> |
| matchGatheredExtractedFields(ArrayRef<Value *> VL, const DataLayout &DL) { |
| // Splats are emitted as broadcasts, sub-fields of a constant are folded. |
| // The bitcast to the field vector maps lane 0 to the least significant |
| // field on little-endian targets only. |
| if (VL.size() < 2 || !VL.front()->getType()->isIntegerTy() || isSplat(VL) || |
| DL.isBigEndian()) |
| return std::nullopt; |
| Value *Src = nullptr; |
| unsigned FieldWidth = 0; |
| SmallVector<int> Mask(VL.size(), PoisonMaskElem); |
| for (auto [Idx, V] : make_filter_range(enumerate(VL), [](const auto &P) { |
| return !isa<UndefValue>(P.value()); |
| })) { |
| if (V->getType() != VL.front()->getType()) |
| return std::nullopt; |
| std::optional<std::tuple<Value *, unsigned, unsigned>> Field = |
| matchExtractedField(V); |
| if (!Field || (Src && (Src != std::get<0>(*Field) || |
| FieldWidth != std::get<1>(*Field)))) |
| return std::nullopt; |
| Src = std::get<0>(*Field); |
| FieldWidth = std::get<1>(*Field); |
| Mask[Idx] = std::get<2>(*Field); |
| } |
| // The field width is a whole number of bytes and divides the source |
| // exactly, same as for the packing layout, so the source bitcasts to the |
| // field vector. |
| if (!Src || isa<Constant>(Src) || FieldWidth % 8 != 0 || |
| Src->getType()->getIntegerBitWidth() % FieldWidth != 0) |
| return std::nullopt; |
| // The same field in every lane is a splat, emitted as a broadcast. |
| if (all_of(Mask, [First = *find_if(Mask, not_equal_to(PoisonMaskElem))]( |
| int MaskElt) { |
| return MaskElt == PoisonMaskElem || MaskElt == First; |
| })) |
| return std::nullopt; |
| return std::make_tuple(Src, FieldWidth, std::move(Mask)); |
| } |
| |
| /// Deeper than the standard analysis recursion depth to keep the numeric |
| /// bound precise through arithmetic carry chains. |
| constexpr unsigned MaxBitPackAnalysisDepth = MaxAnalysisRecursionDepth + 2; |
| |
| APInt getScalarMaxValue(const Value *V, unsigned Depth) { |
| unsigned BitWidth = V->getType()->getScalarSizeInBits(); |
| const APInt Unknown = APInt::getAllOnes(BitWidth); |
| if (Depth > MaxBitPackAnalysisDepth || !V->getType()->isIntegerTy()) |
| return Unknown; |
| const APInt *C, *Amt; |
| if (match(V, m_APInt(C))) |
| return *C; |
| Value *L, *R; |
| if (match(V, m_Add(m_Value(L), m_Value(R))) || |
| match(V, m_Or(m_Value(L), m_Value(R))) || |
| match(V, m_Xor(m_Value(L), m_Value(R)))) |
| return getScalarMaxValue(L, Depth + 1) |
| .uadd_sat(getScalarMaxValue(R, Depth + 1)); |
| if (match(V, m_NUWSub(m_Value(L), m_Value(R)))) |
| return getScalarMaxValue(L, Depth + 1); |
| if (match(V, m_Mul(m_Value(L), m_Value(R)))) |
| return getScalarMaxValue(L, Depth + 1) |
| .umul_sat(getScalarMaxValue(R, Depth + 1)); |
| if (match(V, m_And(m_Value(L), m_Value(R)))) |
| return APIntOps::umin(getScalarMaxValue(L, Depth + 1), |
| getScalarMaxValue(R, Depth + 1)); |
| if (match(V, m_LShr(m_Value(L), m_APInt(Amt))) && Amt->ult(BitWidth)) |
| return getScalarMaxValue(L, Depth + 1).lshr(*Amt); |
| if (match(V, m_Shl(m_Value(L), m_APInt(Amt))) && Amt->ult(BitWidth)) { |
| APInt LMax = getScalarMaxValue(L, Depth + 1); |
| return LMax.getActiveBits() + Amt->getZExtValue() <= BitWidth |
| ? LMax.shl(*Amt) |
| : Unknown; |
| } |
| if (match(V, m_ZExt(m_Value(L)))) |
| return getScalarMaxValue(L, Depth + 1).zext(BitWidth); |
| if (match(V, m_Trunc(m_Value(L)))) { |
| APInt Max = getScalarMaxValue(L, Depth + 1); |
| return Max.getActiveBits() <= BitWidth ? Max.trunc(BitWidth) : Unknown; |
| } |
| if (match(V, m_SExt(m_Value(L)))) { |
| APInt Max = getScalarMaxValue(L, Depth + 1); |
| return Max.isNonNegative() ? Max.zext(BitWidth) : Unknown; |
| } |
| Value *F; |
| if (match(V, m_Select(m_Value(), m_Value(L), m_Value(F)))) |
| return APIntOps::umax(getScalarMaxValue(L, Depth + 1), |
| getScalarMaxValue(F, Depth + 1)); |
| return Unknown; |
| } |
| |
| std::optional<BitPackInfo> computeBitPackInfo(unsigned BitWidth, |
| ArrayRef<APInt> PossibleBits, |
| ArrayRef<uint64_t> ShlAmts, |
| ArrayRef<APInt> Masks) { |
| unsigned NumElts = PossibleBits.size(); |
| BitPackInfo Info; |
| Info.LShrAmts.assign(NumElts, 0); |
| for (unsigned Idx : seq(NumElts)) { |
| APInt Possible = PossibleBits[Idx].shl(ShlAmts[Idx]) & Masks[Idx]; |
| if (Possible.isZero()) |
| continue; |
| unsigned Lo, W; |
| if (!Possible.isShiftedMask(Lo, W)) |
| return std::nullopt; |
| if (Info.FieldWidth == 0) { |
| if (W % 8 != 0 || BitWidth % W != 0) |
| return std::nullopt; |
| Info.FieldWidth = W; |
| Info.LaneOfField.assign(BitWidth / W, BitPackInfo::NoLane); |
| } |
| if (W != Info.FieldWidth || Lo % W != 0) |
| return std::nullopt; |
| unsigned Field = Lo / W; |
| if (Info.LaneOfField[Field] != BitPackInfo::NoLane) |
| return std::nullopt; |
| Info.LaneOfField[Field] = Idx; |
| Info.LShrAmts[Idx] = Lo - ShlAmts[Idx]; |
| } |
| if (Info.FieldWidth == 0) |
| return std::nullopt; |
| return Info; |
| } |
| |
| SmallVector<int> getBitPackMask(const BitPackInfo &Info, unsigned NumBytes, |
| unsigned NumElts, unsigned BytesPerLane) { |
| unsigned BytesPerField = Info.FieldWidth / 8; |
| SmallVector<int> Mask; |
| for (unsigned J : seq(NumBytes)) { |
| unsigned Lane = Info.LaneOfField[J / BytesPerField]; |
| Mask.push_back(Lane == BitPackInfo::NoLane |
| ? (int)(NumElts * BytesPerLane) |
| : (int)(Lane * BytesPerLane + J % BytesPerField)); |
| } |
| return Mask; |
| } |
| |
| Value *buildBitPack(IRBuilderBase &Builder, Value *X, const BitPackInfo &Info, |
| unsigned ShiftWidth, unsigned &NumInsts) { |
| NumInsts = 0; |
| auto *VecTy = cast<FixedVectorType>(X->getType()); |
| unsigned BitWidth = VecTy->getScalarSizeInBits(); |
| assert(BitWidth % 8 == 0 && |
| "The byte-multiple field width divides the result bit width."); |
| unsigned NumElts = VecTy->getNumElements(); |
| Value *Y = X; |
| if (ShiftWidth != BitWidth) { |
| // Compacting a zext back to its source is free, use it directly. |
| if (auto *Z = dyn_cast<ZExtInst>(X); |
| Z && Z->getSrcTy()->getScalarSizeInBits() == ShiftWidth) |
| Y = Z->getOperand(0); |
| else { |
| Y = Builder.CreateTrunc( |
| Y, FixedVectorType::get(IntegerType::get(X->getContext(), ShiftWidth), |
| NumElts)); |
| ++NumInsts; |
| } |
| } |
| if (Info.needsShift()) { |
| SmallVector<Constant *> Amts; |
| for (uint64_t A : Info.LShrAmts) |
| Amts.push_back( |
| ConstantInt::get(IntegerType::get(X->getContext(), ShiftWidth), A)); |
| Y = Builder.CreateLShr(Y, ConstantVector::get(Amts)); |
| ++NumInsts; |
| } |
| unsigned InBytes = NumElts * (ShiftWidth / 8); |
| auto *ByteTy = FixedVectorType::get(Builder.getInt8Ty(), InBytes); |
| SmallVector<int> Mask = |
| getBitPackMask(Info, BitWidth / 8, NumElts, ShiftWidth / 8); |
| auto *IntTy = IntegerType::get(X->getContext(), BitWidth); |
| // A plain byte reversal of the shifted lanes is a bswap. |
| if (ShuffleVectorInst::isReverseMask(Mask, InBytes)) { |
| NumInsts += 2; |
| return Builder.CreateUnaryIntrinsic(Intrinsic::bswap, |
| Builder.CreateBitCast(Y, IntTy)); |
| } |
| // An identity byte order needs no shuffle. |
| if (ShuffleVectorInst::isIdentityMask(Mask, InBytes)) { |
| ++NumInsts; |
| return Builder.CreateBitCast(Y, IntTy); |
| } |
| Value *Packed = Builder.CreateShuffleVector( |
| Builder.CreateBitCast(Y, ByteTy), |
| is_contained(Info.LaneOfField, BitPackInfo::NoLane) |
| ? Constant::getNullValue(ByteTy) |
| : PoisonValue::get(ByteTy), |
| Mask); |
| NumInsts += 3; |
| return Builder.CreateBitCast(Packed, IntTy); |
| } |
| |
| void redirectDbgValues(Instruction &From, Value &To) { |
| SmallVector<DbgVariableRecord *, 2> DVRs; |
| findDbgValues(&From, DVRs); |
| auto *ExI = dyn_cast<Instruction>(&To); |
| for (DbgVariableRecord *DVR : DVRs) { |
| if (!DVR->isDbgValue()) |
| continue; |
| Instruction *MarkedI = DVR->getInstruction(); |
| if (ExI && MarkedI->getParent() != ExI->getParent()) |
| continue; |
| if (!ExI || ExI->comesBefore(MarkedI)) { |
| DVR->replaceVariableLocationOp(&From, &To); |
| continue; |
| } |
| DebugVariableAggregate Var(DVR); |
| auto HasSameVar = [&](auto Records) { |
| return any_of(filterDbgVars(Records), |
| [&](const DbgVariableRecord &Other) { |
| return DebugVariableAggregate(&Other) == Var; |
| }); |
| }; |
| if (HasSameVar(make_range(std::next(DVR->getIterator()), |
| MarkedI->getDbgRecordRange().end())) || |
| any_of(make_range(std::next(MarkedI->getIterator()), |
| std::next(ExI->getIterator())), |
| [&](const Instruction &I) { |
| return HasSameVar(I.getDbgRecordRange()); |
| })) |
| continue; |
| DbgVariableRecord *NewDVR = DVR->clone(); |
| NewDVR->replaceVariableLocationOp(&From, &To); |
| ExI->getParent()->insertDbgRecordAfter(NewDVR, ExI); |
| } |
| } |
| |
| } // namespace llvm::slpvectorizer |