21 #include "llvm/Support/MathExtras.h"
23 #include "llvm/ADT/DenseSet.h"
24 #include "llvm/ADT/SmallPtrSet.h"
25 #include "llvm/ADT/SmallString.h"
26 #include "llvm/Support/Debug.h"
29 #include <type_traits>
34 #define DEBUG_TYPE "affine-loop-analysis"
42 AffineForOp forOp,
AffineMap *tripCountMap,
45 int64_t step = forOp.getStepAsInt();
47 if (forOp.hasConstantBounds()) {
48 int64_t lb = forOp.getConstantLowerBound();
49 int64_t ub = forOp.getConstantUpperBound();
54 llvm::divideCeilSigned(loopSpan, step), context);
55 tripCountOperands->clear();
58 auto lbMap = forOp.getLowerBoundMap();
59 auto ubMap = forOp.getUpperBoundMap();
60 if (lbMap.getNumResults() != 1) {
72 auto lbMapSplat =
AffineMap::get(lbMap.getNumDims(), lbMap.getNumSymbols(),
73 lbSplatExpr, context);
74 AffineValueMap lbSplatValueMap(lbMapSplat, forOp.getLowerBoundOperands());
78 for (
unsigned i = 0, e = tripCountValueMap.
getNumResults(); i < e; ++i)
83 tripCountOperands->assign(tripCountValueMap.
getOperands().begin(),
100 std::optional<uint64_t> tripCount;
102 if (
auto constExpr = dyn_cast<AffineConstantExpr>(resultExpr)) {
103 if (tripCount.has_value())
105 std::min(*tripCount,
static_cast<uint64_t
>(constExpr.getValue()));
107 tripCount = constExpr.getValue();
127 assert(map.
getNumResults() >= 1 &&
"expected one or more results");
128 std::optional<uint64_t> gcd;
131 if (
auto constExpr = dyn_cast<AffineConstantExpr>(resultExpr)) {
132 uint64_t tripCount = constExpr.getValue();
141 thisGcd = resultExpr.getLargestKnownDivisor();
144 gcd = std::gcd(*gcd, thisGcd);
148 assert(gcd.has_value() &&
"value expected per above logic");
158 assert(isa<IndexType>(index.
getType()) &&
"index must be of 'index' type");
167 template <
typename LoadOrStoreOp>
171 return !llvm::is_contained(avm.
getOperands(), forOp.getInductionVar());
185 for (
Value index : indices) {
193 template <
typename LoadOrStoreOp>
196 static_assert(llvm::is_one_of<LoadOrStoreOp, AffineReadOpInterface,
197 AffineWriteOpInterface>::value,
198 "Must be called on either an affine read or write op");
199 assert(memRefDim &&
"memRefDim == nullptr");
200 auto memRefType = memoryOp.getMemRefType();
202 if (!memRefType.getLayout().isIdentity())
203 return memoryOp.emitError(
"NYI: non-trivial layout map"),
false;
205 int uniqueVaryingIndexAlongIv = -1;
206 auto accessMap = memoryOp.getAffineMap();
208 unsigned numDims = accessMap.getNumDims();
209 for (
unsigned i = 0, e = memRefType.getRank(); i < e; ++i) {
212 auto resultExpr = accessMap.getResult(i);
214 if (
auto dimExpr = dyn_cast<AffineDimExpr>(expr))
215 exprOperands.push_back(mapOperands[dimExpr.getPosition()]);
216 else if (
auto symExpr = dyn_cast<AffineSymbolExpr>(expr))
217 exprOperands.push_back(mapOperands[numDims + symExpr.getPosition()]);
220 for (
Value exprOperand : exprOperands) {
222 if (uniqueVaryingIndexAlongIv != -1) {
226 uniqueVaryingIndexAlongIv = i;
231 if (uniqueVaryingIndexAlongIv == -1)
234 *memRefDim = memRefType.getRank() - (uniqueVaryingIndexAlongIv + 1);
239 AffineReadOpInterface loadOp,
242 AffineWriteOpInterface loadOp,
245 template <
typename LoadOrStoreOp>
247 auto memRefType = memoryOp.getMemRefType();
248 return isa<VectorType>(memRefType.getElementType());
257 auto *forOp = loop.getOperation();
262 conditionals.match(forOp, &conditionalsMatched);
263 if (!conditionalsMatched.empty()) {
271 if (MemRefType t = dyn_cast<MemRefType>(type))
272 return !VectorType::isValidElementType(t.getElementType());
273 return !VectorType::isValidElementType(type);
277 return !VectorType::isValidElementType(type);
281 types.match(forOp, &opsMatched);
282 if (!opsMatched.empty()) {
288 return op.
getNumRegions() != 0 && !isa<AffineIfOp, AffineForOp>(op);
291 regions.match(forOp, ®ionsMatched);
292 if (!regionsMatched.empty()) {
297 vectorTransferMatcher.
match(forOp, &vectorTransfersMatched);
298 if (!vectorTransfersMatched.empty()) {
304 loadAndStores.match(forOp, &loadAndStoresMatched);
305 for (
auto ls : loadAndStoresMatched) {
306 auto *op = ls.getMatchedOperation();
307 auto load = dyn_cast<AffineLoadOp>(op);
308 auto store = dyn_cast<AffineStoreOp>(op);
316 if (isVectorizableOp && !isVectorizableOp(loop, *op)) {
324 AffineForOp loop,
int *memRefDim,
NestedPattern &vectorTransferMatcher) {
327 auto load = dyn_cast<AffineLoadOp>(op);
328 auto store = dyn_cast<AffineStoreOp>(op);
329 int thisOpMemRefDim = -1;
332 cast<AffineReadOpInterface>(*load),
335 cast<AffineWriteOpInterface>(*store),
337 if (thisOpMemRefDim != -1) {
340 if (*memRefDim != -1 && *memRefDim != thisOpMemRefDim)
342 *memRefDim = thisOpMemRefDim;
361 auto *forBody = forOp.getBody();
362 assert(shifts.size() == forBody->getOperations().size());
367 for (
const auto &it :
369 auto &op = it.value();
373 size_t index = shifts.size() - it.index() - 1;
376 uint64_t shift = shifts[index];
377 forBodyShift.try_emplace(&op, shift);
380 for (
unsigned i = 0, e = op.getNumResults(); i < e; ++i) {
381 Value result = op.getResult(i);
382 for (
auto *user : result.
getUsers()) {
385 if (
auto *ancOp = forBody->findAncestorOpInBlock(*user)) {
386 assert(forBodyShift.count(ancOp) > 0 &&
"ancestor expected in map");
387 if (shift != forBodyShift[ancOp])
397 assert(!loops.empty() &&
"no original loops provided");
402 if (isa<AffineReadOpInterface, AffineWriteOpInterface>(op))
403 loadAndStoreOps.push_back(op);
406 unsigned numOps = loadAndStoreOps.size();
407 unsigned numLoops = loops.size();
408 for (
unsigned d = 1; d <= numLoops + 1; ++d) {
409 for (
unsigned i = 0; i < numOps; ++i) {
412 for (
unsigned j = 0;
j < numOps; ++
j) {
418 srcAccess, dstAccess, d,
nullptr,
428 LLVM_DEBUG(llvm::dbgs() <<
"Checking whether tiling legality violated "
429 "for dependence at depth: "
430 << Twine(d) <<
" between:\n";);
434 if (depComp.lb.has_value() && depComp.ub.has_value() &&
435 *depComp.lb < *depComp.ub && *depComp.ub < 0) {
436 LLVM_DEBUG(llvm::dbgs()
437 <<
"Dependence component lb = " << Twine(*depComp.lb)
438 <<
" ub = " << Twine(*depComp.ub)
439 <<
" is negative at depth: " << Twine(d)
440 <<
" and thus violates the legality rule.\n");
static bool isVectorizableLoopBodyWithOpCond(AffineForOp loop, const VectorizableOpFun &isVectorizableOp, NestedPattern &vectorTransferMatcher)
std::function< bool(AffineForOp, Operation &)> VectorizableOpFun
static bool isAccessIndexInvariant(Value iv, Value index)
Given an affine.for iv and an access index of type index, returns true if index is independent of iv ...
static bool isVectorElement(LoadOrStoreOp memoryOp)
static Value max(ImplicitLocOpBuilder &builder, Value value, Value bound)
static Value min(ImplicitLocOpBuilder &builder, Value value, Value bound)
Base type for affine expression.
AffineExpr ceilDiv(uint64_t v) const
A multi-dimensional affine map Affine map's are immutable like Type's, and they are uniqued.
static AffineMap getMultiDimIdentityMap(unsigned numDims, MLIRContext *context)
Returns an AffineMap with 'numDims' identity result dim exprs.
static AffineMap get(MLIRContext *context)
Returns a zero result affine map with no dimensions or symbols: () -> ().
ArrayRef< AffineExpr > getResults() const
unsigned getNumResults() const
static AffineMap getConstantMap(int64_t val, MLIRContext *context)
Returns a single constant result affine map.
MLIRContext is the top-level object for a collection of MLIR operations.
Operation is the basic unit of execution within MLIR.
unsigned getNumRegions()
Returns the number of regions held by this operation.
operand_type_range getOperandTypes()
result_type_range getResultTypes()
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
MLIRContext * getContext() const
Utility to get the associated MLIRContext that this value is defined in.
Type getType() const
Return the type of this value.
user_range getUsers() const
An AffineValueMap is an affine map plus its ML value operands and results for analysis purposes.
void composeSimplifyAndCanonicalize()
Composes all incoming affine.apply ops and then simplifies and canonicalizes the map and operands.
ArrayRef< Value > getOperands() const
AffineExpr getResult(unsigned i)
AffineMap getAffineMap() const
bool isFunctionOf(unsigned idx, Value value) const
Return true if the idx^th result depends on 'value', false otherwise.
void setResult(unsigned i, AffineExpr e)
unsigned getNumResults() const
static void difference(const AffineValueMap &a, const AffineValueMap &b, AffineValueMap *res)
Return the value map that is the difference of value maps 'a' and 'b', represented as an affine map a...
void match(Operation *op, SmallVectorImpl< NestedMatch > *matches)
Returns all the top-level matches in op.
NestedPattern If(const NestedPattern &child)
bool isLoadOrStore(Operation &op)
NestedPattern Op(FilterFunctionType filter=defaultFilterFunction)
std::optional< uint64_t > getConstantTripCount(AffineForOp forOp)
Returns the trip count of the loop if it's a constant, std::nullopt otherwise.
bool isTilingValid(ArrayRef< AffineForOp > loops)
Checks whether hyper-rectangular loop tiling of the nest represented by loops is valid.
bool isVectorizableLoopBody(AffineForOp loop, NestedPattern &vectorTransferMatcher)
Checks whether the loop is structurally vectorizable; i.e.
DenseSet< Value, DenseMapInfo< Value > > getInvariantAccesses(Value iv, ArrayRef< Value > indices)
Given an induction variable iv of type AffineForOp and indices of type IndexType, returns the set of ...
void getTripCountMapAndOperands(AffineForOp forOp, AffineMap *map, SmallVectorImpl< Value > *operands)
Returns the trip count of the loop as an affine map with its corresponding operands if the latter is ...
bool isInvariantAccess(LoadOrStoreOp memOp, AffineForOp forOp)
Checks if an affine read or write operation depends on forOp's IV, i.e., if the memory access is inva...
DependenceResult checkMemrefAccessDependence(const MemRefAccess &srcAccess, const MemRefAccess &dstAccess, unsigned loopDepth, FlatAffineValueConstraints *dependenceConstraints=nullptr, SmallVector< DependenceComponent, 2 > *dependenceComponents=nullptr, bool allowRAR=false)
bool isAffineForInductionVar(Value val)
Returns true if the provided value is the induction variable of an AffineForOp.
uint64_t getLargestDivisorOfTripCount(AffineForOp forOp)
Returns the greatest known integral divisor of the trip count.
bool isContiguousAccess(Value iv, LoadOrStoreOp memoryOp, int *memRefDim)
Given:
bool hasDependence(DependenceResult result)
Utility function that returns true if the provided DependenceResult corresponds to a dependence resul...
bool isOpwiseShiftValid(AffineForOp forOp, ArrayRef< uint64_t > shifts)
Checks where SSA dominance would be violated if a for op's body operations are shifted by the specifi...
constexpr void enumerate(std::tuple< Tys... > &tuple, CallbackT &&callback)
Include the generated interface declarations.
Checks whether two accesses to the same memref access the same element.
Encapsulates a memref load or store access information.
Eliminates variable at the specified position using Fourier-Motzkin variable elimination.