blob: 93239b7cd2725c27016c6b50c7c6497409eb17dd [file]
//===- 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/STLExtras.h"
#include "llvm/ADT/Sequence.h"
#include "llvm/Analysis/ValueTracking.h"
#include "llvm/Analysis/VectorUtils.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/DerivedTypes.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 <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 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 : iterator_range(It, VL.end())) {
if (isa<PoisonValue>(V))
continue;
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 : VL) {
if (isa<UndefValue>(V))
continue;
if (!FirstNonUndef) {
FirstNonUndef = V;
continue;
}
if (V != FirstNonUndef)
return false;
}
return FirstNonUndef != nullptr;
}
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];
}
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(I->operands(), [I](Value *V) {
auto *IO = dyn_cast<Instruction>(V);
if (!IO)
return true;
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;
}
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;
}
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;
}
} // namespace llvm::slpvectorizer