21#include "llvm/Support/MathExtras.h"
23#include "llvm/Support/Debug.h"
24#include "llvm/Support/DebugLog.h"
28#define DEBUG_TYPE "affine-loop-analysis"
35class DirectedOpGraph {
39 assert(!hasNode(op) &&
"node already added");
40 nodes.emplace_back(op);
47 assert(hasNode(src) &&
"src node does not exist in graph");
48 assert(hasNode(dest) &&
"dest node does not exist in graph");
49 edges[src].push_back(getNode(dest));
53 bool hasCycle() {
return dfs(
true); }
56 for (
auto &en : edges) {
57 llvm::dbgs() << *en.first <<
" (" << en.first <<
")"
58 <<
" has " << en.second.size() <<
" edges:\n";
59 for (
auto *node : en.second) {
60 llvm::dbgs() <<
'\t' << *node->op <<
'\n';
87 llvm::find_if(nodes, [&](
const DGNode &node) {
return node.op == op; });
88 assert(value != nodes.end() &&
"node doesn't exist in graph");
94 return llvm::find_if(nodes, [&](
const DGNode &node) {
95 return node.op == key;
104 bool dfs(
bool cycleCheck =
false) {
105 for (DGNode &node : nodes) {
111 for (DGNode &node : nodes) {
113 bool ret = dfsNode(node, cycleCheck, time);
115 if (cycleCheck && ret)
117 }
else if (cycleCheck && node.fn == -1) {
128 bool dfsNode(DGNode &node,
bool cycleCheck,
unsigned &time)
const {
129 auto nodeEdges = edges.find(node.op);
130 assert(nodeEdges != edges.end() &&
"missing node in graph");
133 for (
auto &neighbour : nodeEdges->second) {
134 if (neighbour->vn == 0) {
135 bool ret = dfsNode(*neighbour, cycleCheck, time);
136 if (cycleCheck && ret)
138 }
else if (cycleCheck && neighbour->fn == -1) {
165 AffineForOp forOp,
AffineMap *tripCountMap,
168 int64_t step = forOp.getStepAsInt();
170 if (forOp.hasConstantBounds()) {
171 int64_t lb = forOp.getConstantLowerBound();
172 int64_t ub = forOp.getConstantUpperBound();
177 llvm::divideCeilSigned(loopSpan, step), context);
178 tripCountOperands->clear();
181 auto lbMap = forOp.getLowerBoundMap();
182 auto ubMap = forOp.getUpperBoundMap();
183 if (lbMap.getNumResults() != 1) {
197 for (
unsigned i = 0, e = tripCountValueMap.
getNumResults(); i < e; ++i)
202 tripCountOperands->assign(tripCountValueMap.
getOperands().begin(),
211 if (std::optional<APInt> tripCount = forOp.getStaticTripCount())
212 return tripCount->getZExtValue();
229 assert(map.
getNumResults() >= 1 &&
"expected one or more results");
230 std::optional<uint64_t> gcd;
233 if (
auto constExpr = dyn_cast<AffineConstantExpr>(resultExpr)) {
234 uint64_t tripCount = constExpr.getValue();
237 thisGcd = std::numeric_limits<uint64_t>::max();
243 thisGcd = resultExpr.getLargestKnownDivisor();
246 gcd = std::gcd(*gcd, thisGcd);
250 assert(gcd.has_value() &&
"value expected per above logic");
260 assert(isa<IndexType>(
index.getType()) &&
"index must be of 'index' type");
269template <
typename LoadOrStoreOp>
273 return !llvm::is_contained(avm.
getOperands(), forOp.getInductionVar());
295template <
typename LoadOrStoreOp>
298 static_assert(llvm::is_one_of<LoadOrStoreOp, AffineReadOpInterface,
299 AffineWriteOpInterface>::value,
300 "Must be called on either an affine read or write op");
301 assert(memRefDim &&
"memRefDim == nullptr");
302 auto memRefType = memoryOp.getMemRefType();
304 if (!memRefType.getLayout().isIdentity())
305 return memoryOp.emitError(
"NYI: non-trivial layout map"),
false;
307 int uniqueVaryingIndexAlongIv = -1;
308 auto accessMap = memoryOp.getAffineMap();
310 unsigned numDims = accessMap.getNumDims();
311 for (
unsigned i = 0, e = memRefType.getRank(); i < e; ++i) {
314 auto resultExpr = accessMap.getResult(i);
316 if (
auto dimExpr = dyn_cast<AffineDimExpr>(expr))
317 exprOperands.push_back(mapOperands[dimExpr.getPosition()]);
318 else if (
auto symExpr = dyn_cast<AffineSymbolExpr>(expr))
319 exprOperands.push_back(mapOperands[numDims + symExpr.getPosition()]);
322 for (
Value exprOperand : exprOperands) {
324 if (uniqueVaryingIndexAlongIv != -1) {
328 uniqueVaryingIndexAlongIv = i;
333 if (uniqueVaryingIndexAlongIv == -1)
336 *memRefDim = memRefType.getRank() - (uniqueVaryingIndexAlongIv + 1);
341 AffineReadOpInterface loadOp,
344 AffineWriteOpInterface loadOp,
347template <
typename LoadOrStoreOp>
349 auto memRefType = memoryOp.getMemRefType();
350 return isa<VectorType>(memRefType.getElementType());
359 auto *forOp = loop.getOperation();
364 conditionals.match(forOp, &conditionalsMatched);
365 if (!conditionalsMatched.empty()) {
373 if (MemRefType t = dyn_cast<MemRefType>(type))
374 return !VectorType::isValidElementType(t.getElementType());
375 return !VectorType::isValidElementType(type);
378 return !llvm::all_of(op.
getResultTypes(), VectorType::isValidElementType);
381 types.match(forOp, &opsMatched);
382 if (!opsMatched.empty()) {
388 return op.
getNumRegions() != 0 && !isa<AffineIfOp, AffineForOp>(op);
391 regions.match(forOp, ®ionsMatched);
392 if (!regionsMatched.empty()) {
397 vectorTransferMatcher.
match(forOp, &vectorTransfersMatched);
398 if (!vectorTransfersMatched.empty()) {
404 loadAndStores.match(forOp, &loadAndStoresMatched);
405 for (
auto ls : loadAndStoresMatched) {
406 auto *op = ls.getMatchedOperation();
407 auto load = dyn_cast<AffineLoadOp>(op);
408 auto store = dyn_cast<AffineStoreOp>(op);
416 if (isVectorizableOp && !isVectorizableOp(loop, *op)) {
424 AffineForOp loop,
int *memRefDim,
NestedPattern &vectorTransferMatcher) {
427 auto load = dyn_cast<AffineLoadOp>(op);
428 auto store = dyn_cast<AffineStoreOp>(op);
429 int thisOpMemRefDim = -1;
432 cast<AffineReadOpInterface>(*
load),
435 cast<AffineWriteOpInterface>(*store),
437 if (thisOpMemRefDim != -1) {
440 if (*memRefDim != -1 && *memRefDim != thisOpMemRefDim)
442 *memRefDim = thisOpMemRefDim;
461 auto *forBody = forOp.getBody();
462 assert(shifts.size() == forBody->getOperations().size());
467 for (
const auto &it :
468 llvm::enumerate(llvm::reverse(forBody->getOperations()))) {
469 auto &op = it.value();
473 size_t index = shifts.size() - it.index() - 1;
476 uint64_t shift = shifts[
index];
477 forBodyShift.try_emplace(&op, shift);
480 for (
unsigned i = 0, e = op.getNumResults(); i < e; ++i) {
482 for (
auto *user :
result.getUsers()) {
485 if (
auto *ancOp = forBody->findAncestorOpInBlock(*user)) {
486 assert(forBodyShift.count(ancOp) > 0 &&
"ancestor expected in map");
487 if (shift != forBodyShift[ancOp])
497 assert(!loops.empty() &&
"no original loops provided");
502 if (isa<AffineReadOpInterface, AffineWriteOpInterface>(op))
503 loadAndStoreOps.push_back(op);
506 unsigned numOps = loadAndStoreOps.size();
507 unsigned numLoops = loops.size();
508 for (
unsigned d = 1; d <= numLoops + 1; ++d) {
509 for (
unsigned i = 0; i < numOps; ++i) {
512 for (
unsigned j = 0;
j < numOps; ++
j) {
518 srcAccess, dstAccess, d,
nullptr,
528 LDBG() <<
"Checking whether tiling legality violated "
529 <<
"for dependence at depth: " << Twine(d) <<
" between:"
535 if (depComp.lb.has_value() && depComp.ub.has_value() &&
536 *depComp.lb < *depComp.ub && *depComp.ub < 0) {
537 LDBG() <<
"Dependence component lb = " << Twine(*depComp.lb)
538 <<
" ub = " << Twine(*depComp.ub)
539 <<
" is negative at depth: " << Twine(d)
540 <<
" and thus violates the legality rule.";
554 DirectedOpGraph graph;
557 if (isa<AffineReadOpInterface, AffineWriteOpInterface>(op)) {
558 accesses.emplace_back(op);
565 for (
const auto &accA : accesses) {
566 for (
const auto &accB : accesses) {
567 if (accA.memref != accB.memref)
570 unsigned numCommonLoops =
572 for (
unsigned d = rootDepth + 1; d <= numCommonLoops + 1; ++d) {
574 graph.addEdge(accA.opInst, accB.opInst);
578 return graph.hasCycle();
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)
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.
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.
Set of flags used to control the behavior of the various IR print methods (e.g.
A wrapper class that allows for printing an operation with a set of flags, useful to act as a "stream...
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.
An AffineValueMap is an affine map plus its ML value operands and results for analysis purposes.
static void cartesianProductDifference(const AffineValueMap &a, const AffineValueMap &b, AffineValueMap *res)
Subtract results of 'b' from results of 'a' over their Cartesian product.
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
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.:
unsigned getNumCommonSurroundingLoops(Operation &a, Operation &b)
Returns the number of surrounding loops common to both A and B.
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...
unsigned getNestingDepth(Operation *op)
Returns the nesting depth of this operation, i.e., the number of loops surrounding this operation.
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...
bool hasCyclicDependence(AffineForOp root)
Returns true if the affine nest rooted at root has a cyclic dependence among its affine memory access...
bool noDependence(DependenceResult result)
Returns true if the provided DependenceResult corresponds to the absence of a dependence.
Include the generated interface declarations.
llvm::DenseSet< ValueT, ValueInfoT > DenseSet
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
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.