blob: e9ea0c8d2d03dbddb11610e30cfb1c95f41e6824 [file]
//===- SLPMemoryUtils.cpp - SLP pointer/stride 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 "SLPMemoryUtils.h"
#include "SLPCompatibilityAnalysis.h"
#include "SLPCostAnalysis.h"
#include "SLPTypeUtils.h"
#include "SLPUtils.h"
#include "llvm/ADT/APInt.h"
#include "llvm/ADT/MapVector.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/Sequence.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/Analysis/Loads.h"
#include "llvm/Analysis/LoopAccessAnalysis.h"
#include "llvm/Analysis/ScalarEvolution.h"
#include "llvm/Analysis/ScalarEvolutionExpressions.h"
#include "llvm/Analysis/TargetTransformInfo.h"
#include "llvm/Analysis/ValueTracking.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/DataLayout.h"
#include "llvm/IR/DerivedTypes.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/Intrinsics.h"
#include "llvm/Support/InstructionCost.h"
#include <algorithm>
#include <limits>
#include <optional>
#include <set>
#include <tuple>
#include <utility>
using namespace llvm;
namespace llvm::slpvectorizer {
ConstantInt *getStrideBytesIfConstant(Value *Stride, Type *ScalarTy,
const DataLayout &DL, bool IsReverse) {
auto *CI = dyn_cast_or_null<ConstantInt>(Stride);
if (!CI)
return nullptr;
uint64_t ElementSize = DL.getTypeAllocSize(ScalarTy).getFixedValue();
APInt Bytes = CI->getValue() * ElementSize;
return ConstantInt::get(CI->getContext(), IsReverse ? -Bytes : Bytes);
}
bool arePointersCompatible(Value *Ptr1, Value *Ptr2,
const TargetLibraryInfo &TLI, unsigned MaxDepth,
bool CompareOpcodes) {
if (getUnderlyingObject(Ptr1, MaxDepth) !=
getUnderlyingObject(Ptr2, MaxDepth))
return false;
auto *GEP1 = dyn_cast<GetElementPtrInst>(Ptr1);
auto *GEP2 = dyn_cast<GetElementPtrInst>(Ptr2);
return (!GEP1 || GEP1->getNumOperands() == 2) &&
(!GEP2 || GEP2->getNumOperands() == 2) &&
(((!GEP1 || isConstant(GEP1->getOperand(1))) &&
(!GEP2 || isConstant(GEP2->getOperand(1)))) ||
!CompareOpcodes ||
(GEP1 && GEP2 &&
getSameOpcode({GEP1->getOperand(1), GEP2->getOperand(1)}, TLI)));
}
/// Calculates minimal alignment as a common alignment.
template <typename T> Align computeCommonAlignment(ArrayRef<Value *> VL) {
Align CommonAlignment = cast<T>(VL.consume_front())->getAlign();
for (Value *V : VL)
CommonAlignment = std::min(CommonAlignment, cast<T>(V)->getAlign());
return CommonAlignment;
}
template Align computeCommonAlignment<LoadInst>(ArrayRef<Value *>);
template Align computeCommonAlignment<StoreInst>(ArrayRef<Value *>);
const SCEV *calculateRtStride(ArrayRef<Value *> PointerOps, Type *ElemTy,
const DataLayout &DL, ScalarEvolution &SE,
SmallVectorImpl<unsigned> &SortedIndices) {
SmallVector<const SCEV *> SCEVs;
const SCEV *PtrSCEVLowest = nullptr;
const SCEV *PtrSCEVHighest = nullptr;
// Find lower/upper pointers from the PointerOps (i.e. with lowest and highest
// addresses).
for (Value *Ptr : PointerOps) {
const SCEV *PtrSCEV = SE.getSCEV(Ptr);
if (!PtrSCEV)
return nullptr;
SCEVs.push_back(PtrSCEV);
if (!PtrSCEVLowest && !PtrSCEVHighest) {
PtrSCEVLowest = PtrSCEVHighest = PtrSCEV;
continue;
}
const SCEV *Diff = SE.getMinusSCEV(PtrSCEV, PtrSCEVLowest);
if (isa<SCEVCouldNotCompute>(Diff))
return nullptr;
if (Diff->isNonConstantNegative()) {
PtrSCEVLowest = PtrSCEV;
continue;
}
const SCEV *Diff1 = SE.getMinusSCEV(PtrSCEVHighest, PtrSCEV);
if (isa<SCEVCouldNotCompute>(Diff1))
return nullptr;
if (Diff1->isNonConstantNegative()) {
PtrSCEVHighest = PtrSCEV;
continue;
}
}
// Dist = PtrSCEVHighest - PtrSCEVLowest;
const SCEV *Dist = SE.getMinusSCEV(PtrSCEVHighest, PtrSCEVLowest);
if (isa<SCEVCouldNotCompute>(Dist))
return nullptr;
int Size = DL.getTypeStoreSize(ElemTy);
auto TryGetStride = [&](const SCEV *Dist,
const SCEV *Multiplier) -> const SCEV * {
if (const auto *M = dyn_cast<SCEVMulExpr>(Dist)) {
if (M->getOperand(0) == Multiplier)
return M->getOperand(1);
if (M->getOperand(1) == Multiplier)
return M->getOperand(0);
return nullptr;
}
if (Multiplier == Dist)
return SE.getConstant(Dist->getType(), 1);
return SE.getUDivExactExpr(Dist, Multiplier);
};
// Stride_in_elements = Dist / element_size * (num_elems - 1).
const SCEV *Stride = nullptr;
if (Size != 1 || SCEVs.size() > 1) {
const SCEV *Sz = SE.getConstant(Dist->getType(), Size * (SCEVs.size() - 1));
Stride = TryGetStride(Dist, Sz);
if (!Stride)
return nullptr;
}
if (!Stride || isa<SCEVConstant>(Stride))
return nullptr;
// Iterate through all pointers and check if all distances are
// unique multiple of Stride.
using DistOrdPair = std::pair<int64_t, int>;
auto Compare = llvm::less_first();
std::set<DistOrdPair, decltype(Compare)> Offsets(Compare);
bool IsConsecutive = true;
for (const auto [Idx, PtrSCEV] : enumerate(SCEVs)) {
unsigned Dist = 0;
if (PtrSCEV != PtrSCEVLowest) {
const SCEV *Diff = SE.getMinusSCEV(PtrSCEV, PtrSCEVLowest);
const SCEV *Coeff = TryGetStride(Diff, Stride);
if (!Coeff)
return nullptr;
const auto *SC = dyn_cast<SCEVConstant>(Coeff);
if (!SC || isa<SCEVCouldNotCompute>(SC))
return nullptr;
if (!SE.getMinusSCEV(PtrSCEV, SE.getAddExpr(PtrSCEVLowest,
SE.getMulExpr(Stride, SC)))
->isZero())
return nullptr;
Dist = SC->getAPInt().getZExtValue();
}
// If the strides are not the same or repeated, we can't vectorize.
if ((Dist / Size) * Size != Dist || (Dist / Size) >= SCEVs.size())
return nullptr;
auto Res = Offsets.emplace(Dist, Idx);
if (!Res.second)
return nullptr;
// Consecutive order if the inserted element is the last one.
IsConsecutive = IsConsecutive && std::next(Res.first) == Offsets.end();
}
SortedIndices.clear();
if (!IsConsecutive) {
// Fill SortedIndices array only if it is non-consecutive.
SortedIndices.resize(PointerOps.size());
for (const auto [Idx, Pair] : enumerate(Offsets))
SortedIndices[Idx] = Pair.second;
}
return Stride;
}
/// Builds compress-like mask for shuffles for the given \p PointerOps, ordered
/// with \p Order.
/// \return true if the mask represents strided access, false - otherwise.
static bool buildCompressMask(ArrayRef<Value *> PointerOps,
ArrayRef<unsigned> Order, Type *ScalarTy,
const DataLayout &DL, ScalarEvolution &SE,
SmallVectorImpl<int> &CompressMask) {
const unsigned Sz = PointerOps.size();
CompressMask.assign(Sz, PoisonMaskElem);
// The first element always set.
CompressMask[0] = 0;
// Check if the mask represents strided access.
std::optional<unsigned> Stride = 0;
Value *Ptr0 = Order.empty() ? PointerOps.front() : PointerOps[Order.front()];
for (unsigned I : seq<unsigned>(1, Sz)) {
Value *Ptr = Order.empty() ? PointerOps[I] : PointerOps[Order[I]];
std::optional<int64_t> OptPos =
getPointersDiff(ScalarTy, Ptr0, ScalarTy, Ptr, DL, SE);
if (!OptPos || OptPos > std::numeric_limits<unsigned>::max())
return false;
unsigned Pos = static_cast<unsigned>(*OptPos);
CompressMask[I] = Pos;
if (!Stride)
continue;
if (*Stride == 0) {
*Stride = Pos;
continue;
}
if (Pos != *Stride * I)
Stride.reset();
}
return Stride.has_value();
}
/// Checks if the \p VL can be transformed to a (masked)load + compress or
/// (masked) interleaved load.
bool isMaskedLoadCompress(
ArrayRef<Value *> VL, ArrayRef<Value *> PointerOps,
ArrayRef<unsigned> Order, const TargetTransformInfo &TTI,
const DataLayout &DL, ScalarEvolution &SE, AssumptionCache &AC,
const DominatorTree &DT, const TargetLibraryInfo &TLI,
const TargetTransformInfo::TargetCostKind CostKind,
const function_ref<bool(Value *)> AreAllUsersVectorized, bool ReVec,
bool &IsMasked, unsigned &InterleaveFactor,
SmallVectorImpl<int> &CompressMask, VectorType *&LoadVecTy) {
InterleaveFactor = 0;
Type *ScalarTy = VL.front()->getType();
const size_t Sz = VL.size();
auto *VecTy = cast<VectorType>(getWidenedType(ScalarTy, Sz));
SmallVector<int> Mask;
if (!Order.empty())
inversePermutation(Order, Mask);
// Check external uses.
for (const auto [I, V] : enumerate(VL)) {
if (AreAllUsersVectorized(V))
continue;
InstructionCost ExtractCost =
TTI.getVectorInstrCost(Instruction::ExtractElement, VecTy, CostKind,
Mask.empty() ? I : Mask[I]);
InstructionCost ScalarCost =
TTI.getInstructionCost(cast<Instruction>(V), CostKind);
if (ExtractCost <= ScalarCost)
return false;
}
Value *Ptr0;
Value *PtrN;
if (Order.empty()) {
Ptr0 = PointerOps.front();
PtrN = PointerOps.back();
} else {
Ptr0 = PointerOps[Order.front()];
PtrN = PointerOps[Order.back()];
}
std::optional<int64_t> Diff =
getPointersDiff(ScalarTy, Ptr0, ScalarTy, PtrN, DL, SE);
if (!Diff)
return false;
const size_t MaxRegSize =
TTI.getRegisterBitWidth(TargetTransformInfo::RGK_FixedWidthVector)
.getFixedValue();
// Check for very large distances between elements.
if (*Diff / Sz >= MaxRegSize / 8)
return false;
LoadVecTy = cast<FixedVectorType>(getWidenedType(ScalarTy, *Diff + 1));
auto *LI = cast<LoadInst>(Order.empty() ? VL.front() : VL[Order.front()]);
Align CommonAlignment = LI->getAlign();
SimplifyQuery SQ(
DL, &TLI, &DT, &AC,
cast<LoadInst>(Order.empty() ? VL.back() : VL[Order.back()]));
IsMasked = !isSafeToLoadUnconditionally(Ptr0, LoadVecTy, CommonAlignment, SQ);
if (IsMasked && !TTI.isLegalMaskedLoad(LoadVecTy, CommonAlignment,
LI->getPointerAddressSpace()))
return false;
// TODO: perform the analysis of each scalar load for better
// safe-load-unconditionally analysis.
bool IsStrided =
buildCompressMask(PointerOps, Order, ScalarTy, DL, SE, CompressMask);
assert(CompressMask.size() >= 2 && "At least two elements are required");
SmallVector<Value *> OrderedPointerOps(PointerOps);
if (!Order.empty())
reorderScalars(OrderedPointerOps, Mask);
auto [ScalarGEPCost, VectorGEPCost] =
getGEPCosts(TTI, OrderedPointerOps, OrderedPointerOps.front(),
Instruction::Load, CostKind, ScalarTy, LoadVecTy);
// The cost of scalar loads.
InstructionCost ScalarLoadsCost =
accumulate(VL, InstructionCost(),
[&](InstructionCost C, Value *V) {
return C + TTI.getInstructionCost(cast<Instruction>(V),
CostKind);
}) +
ScalarGEPCost;
APInt DemandedElts = APInt::getAllOnes(Sz);
InstructionCost GatherCost =
getScalarizationOverhead(TTI, ReVec, ScalarTy, VecTy, DemandedElts,
/*Insert=*/true,
/*Extract=*/false, CostKind) +
ScalarLoadsCost;
InstructionCost LoadCost = 0;
if (IsMasked) {
LoadCost = TTI.getMemIntrinsicInstrCost(
MemIntrinsicCostAttributes(Intrinsic::masked_load, LoadVecTy,
CommonAlignment,
LI->getPointerAddressSpace()),
CostKind);
} else {
LoadCost =
TTI.getMemoryOpCost(Instruction::Load, LoadVecTy, CommonAlignment,
LI->getPointerAddressSpace(), CostKind,
TTI::getOperandInfo(LI->getPointerOperand()));
}
if (IsStrided && !IsMasked && Order.empty()) {
// Check for potential segmented(interleaved) loads.
VectorType *AlignedLoadVecTy = cast<VectorType>(getWidenedType(
ScalarTy,
getFullVectorNumberOfElements(TTI, ScalarTy, *Diff + 1, ReVec)));
SimplifyQuery SQ(DL, &TLI, &DT, &AC, cast<LoadInst>(VL.back()));
if (!isSafeToLoadUnconditionally(Ptr0, AlignedLoadVecTy, CommonAlignment,
SQ))
AlignedLoadVecTy = LoadVecTy;
if (TTI.isLegalInterleavedAccessType(AlignedLoadVecTy, CompressMask[1],
CommonAlignment,
LI->getPointerAddressSpace())) {
InstructionCost InterleavedCost =
VectorGEPCost + TTI.getInterleavedMemoryOpCost(
Instruction::Load, AlignedLoadVecTy,
CompressMask[1], {}, CommonAlignment,
LI->getPointerAddressSpace(), CostKind, IsMasked);
if (InterleavedCost < GatherCost) {
InterleaveFactor = CompressMask[1];
LoadVecTy = AlignedLoadVecTy;
return true;
}
}
}
// Estimating the compression shuffle cost below can be extremely expensive
// for a very wide LoadVecTy, which is split into a large number of vector
// registers (see processShuffleMasks). The shuffle cost is always
// non-negative, so if the load cost alone already reaches the gather cost the
// masked-load-compress cannot be profitable. Bail out before the costly
// shuffle cost estimation in that case.
if (VectorGEPCost + LoadCost >= GatherCost)
return false;
InstructionCost CompressCost = getShuffleCost(
TTI, TTI::SK_PermuteSingleSrc, LoadVecTy, CostKind, CompressMask);
if (!Order.empty()) {
SmallVector<int> NewMask(Sz, PoisonMaskElem);
for (unsigned I : seq<unsigned>(Sz)) {
NewMask[I] = CompressMask[Mask[I]];
}
CompressMask.swap(NewMask);
}
InstructionCost TotalVecCost = VectorGEPCost + LoadCost + CompressCost;
return TotalVecCost < GatherCost;
}
/// Checks if the \p VL can be transformed to a (masked)load + compress or
/// (masked) interleaved load.
bool isMaskedLoadCompress(
ArrayRef<Value *> VL, ArrayRef<Value *> PointerOps,
ArrayRef<unsigned> Order, const TargetTransformInfo &TTI,
const DataLayout &DL, ScalarEvolution &SE, AssumptionCache &AC,
const DominatorTree &DT, const TargetLibraryInfo &TLI,
const TargetTransformInfo::TargetCostKind CostKind,
const function_ref<bool(Value *)> AreAllUsersVectorized, bool ReVec) {
bool IsMasked;
unsigned InterleaveFactor;
SmallVector<int> CompressMask;
VectorType *LoadVecTy;
return isMaskedLoadCompress(VL, PointerOps, Order, TTI, DL, SE, AC, DT, TLI,
CostKind, AreAllUsersVectorized, ReVec, IsMasked,
InterleaveFactor, CompressMask, LoadVecTy);
}
/// Checks if the stores \p VL with pointers \p PointerOps can be lowered as a
/// single masked store. On success \p StoreVecTy is the widened store type and
/// \p ReuseShuffleIndices is the expand mask that places each stored value at
/// its element offset from the base (poison in the gaps).
bool isMaskedStoreCompress(ArrayRef<Value *> VL, ArrayRef<Value *> PointerOps,
ArrayRef<unsigned> Order,
const TargetTransformInfo &TTI, const DataLayout &DL,
ScalarEvolution &SE, Align CommonAlignment,
SmallVectorImpl<int> &ReuseShuffleIndices,
FixedVectorType *&StoreVecTy) {
Type *ScalarTy = cast<StoreInst>(VL.front())->getValueOperand()->getType();
const size_t Sz = VL.size();
// Only simple scalar element types are supported.
if (Sz < 2 || (!ScalarTy->isIntOrPtrTy() && !ScalarTy->isFloatingPointTy()))
return false;
Value *Ptr0 = Order.empty() ? PointerOps.front() : PointerOps[Order.front()];
Value *PtrN = Order.empty() ? PointerOps.back() : PointerOps[Order.back()];
std::optional<int64_t> Diff =
getPointersDiff(ScalarTy, Ptr0, ScalarTy, PtrN, DL, SE);
if (!Diff || *Diff <= 0)
return false;
// Avoid widened vectors with very large gaps between the stored elements.
const unsigned MaxRegSize =
TTI.getRegisterBitWidth(TargetTransformInfo::RGK_FixedWidthVector)
.getFixedValue();
const unsigned ScalarBits = DL.getTypeSizeInBits(ScalarTy).getFixedValue();
if (ScalarBits == 0 ||
static_cast<uint64_t>(*Diff) / Sz >= MaxRegSize / ScalarBits)
return false;
StoreVecTy = cast<FixedVectorType>(getWidenedType(ScalarTy, *Diff + 1));
unsigned AS = cast<StoreInst>(VL.front())->getPointerAddressSpace();
if (!TTI.isLegalMaskedStore(StoreVecTy, CommonAlignment, AS,
TTI::ConstantMask))
return false;
// Build the expand mask: store I (in address-sorted order) is placed at its
// element offset from the base, other widened lanes are poison.
ReuseShuffleIndices.assign(*Diff + 1, PoisonMaskElem);
int64_t Prev = -1;
for (unsigned I : seq<unsigned>(Sz)) {
Value *Ptr = Order.empty() ? PointerOps[I] : PointerOps[Order[I]];
std::optional<int64_t> Off =
getPointersDiff(ScalarTy, Ptr0, ScalarTy, Ptr, DL, SE);
if (!Off || *Off <= Prev || *Off > *Diff)
return false;
ReuseShuffleIndices[*Off] = static_cast<int>(I);
Prev = *Off;
}
return true;
}
bool clusterSortPtrAccesses(ArrayRef<Value *> VL, ArrayRef<BasicBlock *> BBs,
Type *ElemTy, const DataLayout &DL,
ScalarEvolution &SE, unsigned MaxDepth,
SmallVectorImpl<unsigned> &SortedIndices) {
assert(
all_of(VL, [](const Value *V) { return V->getType()->isPointerTy(); }) &&
"Expected list of pointer operands.");
// Map from bases to a vector of (Ptr, Offset, OrigIdx), which we insert each
// Ptr into, sort and return the sorted indices with values next to one
// another.
SmallMapVector<
std::pair<BasicBlock *, Value *>,
SmallVector<SmallVector<std::tuple<Value *, int64_t, unsigned>>>, 8>
Bases;
Bases
.try_emplace(std::make_pair(BBs.front(),
getUnderlyingObject(VL.front(), MaxDepth)))
.first->second.emplace_back()
.emplace_back(VL.front(), 0U, 0U);
SortedIndices.clear();
for (auto [Cnt, Ptr] : enumerate(VL.drop_front())) {
auto Key = std::make_pair(BBs[Cnt + 1], getUnderlyingObject(Ptr, MaxDepth));
bool Found = any_of(Bases.try_emplace(Key).first->second,
[&, &Cnt = Cnt, &Ptr = Ptr](auto &Base) {
std::optional<int64_t> Diff =
getPointersDiff(ElemTy, std::get<0>(Base.front()),
ElemTy, Ptr, DL, SE,
/*StrictCheck=*/true);
if (!Diff)
return false;
Base.emplace_back(Ptr, *Diff, Cnt + 1);
return true;
});
if (!Found) {
// If we haven't found enough to usefully cluster, return early.
if (Bases.size() > VL.size() / 2 - 1)
return false;
// Not found already - add a new Base
Bases.find(Key)->second.emplace_back().emplace_back(Ptr, 0, Cnt + 1);
}
}
if (Bases.size() == VL.size())
return false;
if (Bases.size() == 1 && (Bases.front().second.size() == 1 ||
Bases.front().second.size() == VL.size()))
return false;
// For each of the bases sort the pointers by Offset and check if any of the
// base become consecutively allocated.
auto ComparePointers = [MaxDepth](Value *Ptr1, Value *Ptr2) {
SmallPtrSet<Value *, 13> FirstPointers;
SmallPtrSet<Value *, 13> SecondPointers;
Value *P1 = Ptr1;
Value *P2 = Ptr2;
unsigned Depth = 0;
while (!FirstPointers.contains(P2) && !SecondPointers.contains(P1)) {
if (P1 == P2 || Depth > MaxDepth)
return false;
FirstPointers.insert(P1);
SecondPointers.insert(P2);
P1 = getUnderlyingObject(P1, /*MaxLookup=*/1);
P2 = getUnderlyingObject(P2, /*MaxLookup=*/1);
++Depth;
}
assert((FirstPointers.contains(P2) || SecondPointers.contains(P1)) &&
"Unable to find matching root.");
return FirstPointers.contains(P2) && !SecondPointers.contains(P1);
};
for (auto &Base : Bases) {
for (auto &Vec : Base.second) {
if (Vec.size() > 1) {
stable_sort(Vec, llvm::less_second());
int64_t InitialOffset = std::get<1>(Vec[0]);
bool AnyConsecutive =
all_of(enumerate(Vec), [InitialOffset](const auto &P) {
return std::get<1>(P.value()) ==
int64_t(P.index()) + InitialOffset;
});
// Fill SortedIndices array only if it looks worth-while to sort the
// ptrs.
if (!AnyConsecutive)
return false;
}
}
stable_sort(Base.second, [&](const auto &V1, const auto &V2) {
return ComparePointers(std::get<0>(V1.front()), std::get<0>(V2.front()));
});
}
for (auto &T : Bases)
for (const auto &Vec : T.second)
for (const auto &P : Vec)
SortedIndices.push_back(std::get<2>(P));
assert(SortedIndices.size() == VL.size() &&
"Expected SortedIndices to be the size of VL");
return true;
}
} // namespace llvm::slpvectorizer