24#include "llvm/ADT/TypeSwitch.h"
25#include "llvm/Support/Debug.h"
26#include "llvm/Support/raw_ostream.h"
29#define DEBUG_TYPE "affine-analysis"
41 arith::AtomicRMWKind &kind) {
49 if (combinerOps.size() > 1)
52 Operation *combinerOp = combinerOps.back();
53 std::optional<arith::AtomicRMWKind> maybeKind =
55 .Case([](arith::AddFOp) {
return arith::AtomicRMWKind::addf; })
56 .Case([](arith::MulFOp) {
return arith::AtomicRMWKind::mulf; })
57 .Case([](arith::AddIOp) {
return arith::AtomicRMWKind::addi; })
58 .Case([](arith::AndIOp) {
return arith::AtomicRMWKind::andi; })
59 .Case([](arith::OrIOp) {
return arith::AtomicRMWKind::ori; })
60 .Case([](arith::MulIOp) {
return arith::AtomicRMWKind::muli; })
62 [](arith::MinimumFOp) {
return arith::AtomicRMWKind::minimumf; })
64 [](arith::MaximumFOp) {
return arith::AtomicRMWKind::maximumf; })
65 .Case([](arith::MinSIOp) {
return arith::AtomicRMWKind::mins; })
66 .Case([](arith::MaxSIOp) {
return arith::AtomicRMWKind::maxs; })
67 .Case([](arith::MinUIOp) {
return arith::AtomicRMWKind::minu; })
68 .Case([](arith::MaxUIOp) {
return arith::AtomicRMWKind::maxu; })
69 .Case([](arith::XOrIOp) {
return arith::AtomicRMWKind::xori; })
70 .Case([](arith::MaxNumFOp) {
return arith::AtomicRMWKind::maxnumf; })
71 .Case([](arith::MinNumFOp) {
return arith::AtomicRMWKind::minnumf; })
72 .Default([](
Operation *) -> std::optional<arith::AtomicRMWKind> {
85 unsigned numIterArgs = forOp.getNumIterOperands();
88 supportedReductions.reserve(numIterArgs);
89 for (
unsigned i = 0; i < numIterArgs; ++i) {
90 arith::AtomicRMWKind kind;
92 supportedReductions.emplace_back(
LoopReduction{kind, i, value});
99bool mlir::affine::isLoopParallel(
101 unsigned numIterArgs = forOp.getNumIterOperands();
105 if (numIterArgs > 0 && !parallelReductions)
109 if (parallelReductions) {
113 if (parallelReductions->size() != numIterArgs)
133 auto viewOp = dyn_cast<ViewLikeOpInterface>(defOp);
134 return viewOp && isLocallyDefined(viewOp.getViewSource(), enclosingOp);
139 if (llvm::any_of(forOp.getResultTypes(), llvm::IsaPred<BaseMemRefType>))
145 if (
auto readOp = dyn_cast<AffineReadOpInterface>(op)) {
147 if (!isLocallyDefined(readOp.getMemRef(), forOp))
148 loadAndStoreOps.push_back(op);
149 }
else if (
auto writeOp = dyn_cast<AffineWriteOpInterface>(op)) {
151 if (!isLocallyDefined(writeOp.getMemRef(), forOp))
152 loadAndStoreOps.push_back(op);
153 }
else if (!isa<AffineForOp, AffineYieldOp, AffineIfOp>(op) &&
165 if (walkResult.wasInterrupted())
172 for (
auto *srcOp : loadAndStoreOps) {
174 for (
auto *dstOp : loadAndStoreOps) {
196 unsigned operandIndex;
199 for (
auto operand : operands) {
200 worklist.push_back({operand, 0});
203 while (!worklist.empty()) {
204 State &state = worklist.back();
205 auto *opInst = state.value.getDefiningOp();
208 if (!isa_and_nonnull<AffineApplyOp>(opInst)) {
213 if (state.operandIndex == 0) {
215 affineApplyOps.push_back(opInst);
217 if (state.operandIndex < opInst->getNumOperands()) {
220 auto nextOperand = opInst->getOperand(state.operandIndex);
222 ++state.operandIndex;
224 worklist.push_back({nextOperand, 0});
246 if (!isa<AffineForOp, AffineIfOp, AffineParallelOp>(op)) {
247 LLVM_DEBUG(llvm::dbgs() <<
"getIndexSet only handles affine.for/if/"
251 if (AffineForOp forOp = dyn_cast<AffineForOp>(op)) {
252 loopOps.push_back(forOp);
255 }
else if (AffineParallelOp parallelOp = dyn_cast<AffineParallelOp>(op)) {
256 loopOps.push_back(parallelOp);
257 numDims += parallelOp.getNumDims();
266 if (AffineForOp forOp = dyn_cast<AffineForOp>(op)) {
269 }
else if (
auto ifOp = dyn_cast<AffineIfOp>(op)) {
271 }
else if (
auto parallelOp = dyn_cast<AffineParallelOp>(op))
297 unsigned minNumLoops =
299 unsigned numCommonLoops = 0;
300 for (
unsigned i = 0; i < minNumLoops; ++i) {
307 if (commonLoops !=
nullptr)
311 if (commonLoops !=
nullptr)
312 assert(commonLoops->size() == numCommonLoops);
313 return numCommonLoops;
323 auto getChainOfAncestorBlocks =
330 ancestorBlocks.push_back(currBlock);
334 "parent op starting an affine scope is always expected");
335 ancestorBlocks.push_back(currBlock);
340 getChainOfAncestorBlocks(opA, srcAncestorBlocks);
341 getChainOfAncestorBlocks(opB, dstAncestorBlocks);
343 Block *commonBlock =
nullptr;
344 for (
int i = srcAncestorBlocks.size() - 1,
j = dstAncestorBlocks.size() - 1;
345 i >= 0 &&
j >= 0 && srcAncestorBlocks[i] == dstAncestorBlocks[
j];
347 commonBlock = srcAncestorBlocks[i];
361 assert(commonBlock &&
362 "ops expected to have a common surrounding block in affine scope");
367 assert(srcOp &&
"src access op must lie in common block");
369 assert(dstOp &&
"dest access op must lie in common block");
386 unsigned numCols = dependenceDomain->
getNumCols();
390 unsigned numCommonLoopConstraints = std::min(numCommonLoops, loopDepth);
391 for (
unsigned i = 0; i < numCommonLoopConstraints; ++i) {
394 eq[i + numSrcDims] = 1;
395 if (i == loopDepth - 1) {
396 eq[numCols - 1] = -1;
415 unsigned numCommonLoops =
417 if (numCommonLoops == 0)
420 unsigned numIdsToEliminate = dependenceDomain->
getNumVars();
423 dependenceDomain->
insertVar(VarKind::SetDim, 0,
433 for (
unsigned j = 0;
j < numCommonLoops; ++
j) {
436 eq[
j + numCommonLoops] = 1;
437 eq[
j + numCommonLoops + numSrcDims] = -1;
442 dependenceDomain->
projectOut(numCommonLoops, numIdsToEliminate);
446 dependenceComponents->resize(numCommonLoops);
447 for (
unsigned j = 0;
j < numCommonLoops; ++
j) {
448 (*dependenceComponents)[
j].op = commonLoops[
j].getOperation();
450 (*dependenceComponents)[
j].lb =
451 lbConst.value_or(std::numeric_limits<int64_t>::min());
453 (*dependenceComponents)[
j].ub =
454 ubConst.value_or(std::numeric_limits<int64_t>::max());
473 unsigned inserts = 0;
474 for (
unsigned i = 0, e = domain.
getNumDimVars(); i < e; ++i) {
478 const Identifier *itr = std::find(findBegin, findEnd, domainIdi);
479 if (itr != findEnd) {
480 rel.
swapVar(i, i + std::distance(findBegin, itr));
484 rel.
setId(VarKind::SetDim, i, domainIdi);
511 if (
auto loadOp = dyn_cast<AffineReadOpInterface>(
opInst))
512 map = loadOp.getAffineMap();
514 map = cast<AffineWriteOpInterface>(
opInst).getAffineMap();
520 accessMap->
reset(map, operands);
613 LLVM_DEBUG(llvm::dbgs() <<
"Checking for dependence at depth: "
614 << Twine(loopDepth) <<
" between:\n";);
624 if (!allowRAR && !isa<AffineWriteOpInterface>(srcAccess.
opInst) &&
625 !isa<AffineWriteOpInterface>(dstAccess.
opInst))
653 assert(loopDepth <= numCommonLoops + 1);
654 if (!allowRAR && loopDepth > numCommonLoops &&
667 if (!srcRel.getSpace().isUsingIds())
678 if (dependenceDomain.
isEmpty())
682 if (dependenceComponents !=
nullptr)
684 dependenceComponents);
686 LLVM_DEBUG(llvm::dbgs() <<
"Dependence polyhedron:\n");
687 LLVM_DEBUG(dependenceDomain.
dump());
690 if (dependenceConstraints)
691 *dependenceConstraints =
result;
698 AffineForOp forOp,
unsigned maxLoopDepth,
703 if (isa<AffineReadOpInterface, AffineWriteOpInterface>(op))
704 loadAndStoreOps.push_back(op);
707 unsigned numOps = loadAndStoreOps.size();
708 for (
unsigned d = 1; d <= maxLoopDepth; ++d) {
709 for (
unsigned i = 0; i < numOps; ++i) {
710 auto *srcOp = loadAndStoreOps[i];
712 for (
unsigned j = 0;
j < numOps; ++
j) {
713 auto *dstOp = loadAndStoreOps[
j];
720 srcAccess, dstAccess, d,
nullptr,
723 depCompsVec->push_back(depComps);
static LogicalResult getOpIndexSet(Operation *op, FlatAffineValueConstraints *indexSet)
Computes the iteration domain for 'op' and populates 'indexSet', which encapsulates the constraints i...
static Value getSupportedReduction(AffineForOp forOp, unsigned pos, arith::AtomicRMWKind &kind)
Get the value that is being reduced by pos-th reduction in the loop if such a reduction can be perfor...
static void computeDirectionVector(const FlatAffineValueConstraints &srcDomain, const FlatAffineValueConstraints &dstDomain, unsigned loopDepth, IntegerPolyhedron *dependenceDomain, SmallVector< DependenceComponent, 2 > *dependenceComponents)
static unsigned getNumCommonLoops(const FlatAffineValueConstraints &srcDomain, const FlatAffineValueConstraints &dstDomain, SmallVectorImpl< AffineForOp > *commonLoops=nullptr)
static bool srcAppearsBeforeDstInAncestralBlock(const MemRefAccess &srcAccess, const MemRefAccess &dstAccess)
Returns true if the ancestor operation of 'srcAccess' appears before the ancestor operation of 'dstAc...
static Block * getCommonBlockInAffineScope(Operation *opA, Operation *opB)
Returns the closest surrounding block common to opA and opB. opA and opB should be in the same affine...
static void addOrderingConstraints(const FlatAffineValueConstraints &srcDomain, const FlatAffineValueConstraints &dstDomain, unsigned loopDepth, IntegerRelation *dependenceDomain)
A multi-dimensional affine map Affine map's are immutable like Type's, and they are uniqued.
Block represents an ordered list of Operations.
Operation * findAncestorOpInBlock(Operation &op)
Returns 'op' if 'op' lies in this block, or otherwise finds the ancestor operation of 'op' that lies ...
Operation * getParentOp()
Returns the closest surrounding operation that contains this block.
Value getValue(unsigned pos) const
Returns the Value associated with the pos^th variable.
A trait of region holding operations that defines a new scope for polyhedral optimization purposes.
Operation is the basic unit of execution within MLIR.
bool hasTrait()
Returns true if the operation was registered with a particular trait, e.g.
bool isBeforeInBlock(Operation *other)
Given an operation 'other' that is within the same parent block, return whether the current operation...
Block * getBlock()
Returns the operation block that contains this operation.
bool isProperAncestor(Operation *other)
Return true if this operation is a proper ancestor of the other operation.
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
A utility result that is used to signal how to proceed with an ongoing walk:
static WalkResult advance()
static WalkResult interrupt()
An AffineValueMap is an affine map plus its ML value operands and results for analysis purposes.
unsigned getNumDims() const
void reset(AffineMap map, ValueRange operands, ValueRange results={})
unsigned getNumResults() const
FlatAffineValueConstraints is an extension of FlatLinearValueConstraints with helper functions for Af...
void addAffineIfOpDomain(AffineIfOp ifOp)
Adds constraints imposed by the affine.if operation.
LogicalResult addAffineParallelOpDomain(AffineParallelOp parallelOp)
Add constraints (lower and upper bounds) for the specified 'affine.parallel' operation's Value using ...
LogicalResult addAffineForOpDomain(AffineForOp forOp)
Adds constraints (lower and upper bounds) for the specified 'affine.for' operation's Value using IR i...
An Identifier stores a pointer to an object, such as a Value or an Operation.
An IntegerPolyhedron represents the set of points from a PresburgerSpace that satisfy a list of affin...
unsigned insertVar(VarKind kind, unsigned pos, unsigned num=1) override
Insert num variables of the specified kind at position pos.
An IntegerRelation represents the set of points from a PresburgerSpace that satisfy a list of affine ...
void setId(VarKind kind, unsigned i, Identifier id)
Set the identifier for the ith variable of the specified kind of the IntegerRelation's PresburgerSpac...
virtual void swapVar(unsigned posA, unsigned posB)
Swap the posA^th variable with the posB^th variable.
ArrayRef< Identifier > getIds(VarKind kind)
Get the identifiers for the variables of specified varKind.
void convertVarKind(VarKind srcKind, unsigned varStart, unsigned varLimit, VarKind dstKind, unsigned pos)
Converts variables of kind srcKind in the range [varStart, varLimit) to variables of kind dstKind.
unsigned appendVar(VarKind kind, unsigned num=1)
Append num variables of the specified kind after the last variable of that kind.
virtual unsigned insertVar(VarKind kind, unsigned pos, unsigned num=1)
Insert num variables of the specified kind at position pos.
unsigned getNumDomainVars() const
const PresburgerSpace & getSpace() const
Returns a reference to the underlying space.
unsigned getNumVars() const
void inverse()
Invert the relation i.e., swap its domain and range.
void append(const IntegerRelation &other)
Appends constraints from other into this.
void addEquality(ArrayRef< DynamicAPInt > eq)
Adds an equality from the coefficients specified in eq.
bool isEmpty() const
Checks for emptiness by performing variable elimination on all variables, running the GCD test on eac...
unsigned getNumCols() const
Returns the number of columns in the constraint system.
void mergeAndCompose(const IntegerRelation &other)
Given a relation other: (A -> B), this operation merges the symbol and local variables and then takes...
IntegerPolyhedron getDomainSet() const
Return a set corresponding to all points in the domain of the relation.
void projectOut(unsigned pos, unsigned num)
Projects out (aka eliminates) num variables starting at position pos.
void addInequality(ArrayRef< DynamicAPInt > inEq)
Adds an inequality (>= 0) from the coefficients specified in inEq.
void mergeAndAlignSymbols(IntegerRelation &other)
Merge and align symbol variables of this and other with respect to identifiers.
unsigned mergeLocalVars(IntegerRelation &other)
Adds additional local vars to the sets such that they both have the union of the local vars in each s...
std::optional< int64_t > getConstantBound64(BoundType type, unsigned pos) const
The same, but casts to int64_t.
unsigned getNumDimVars() const
PresburgerSpace is the space of all possible values of a tuple of integer valued variables/variables.
bool isUsingIds() const
Returns if identifiers are being used.
static PresburgerSpace getRelationSpace(unsigned numDomain=0, unsigned numRange=0, unsigned numSymbols=0, unsigned numLocals=0)
void getEnclosingAffineOps(Operation &op, SmallVectorImpl< Operation * > *ops)
Populates 'ops' with affine operations enclosing op ordered from outermost to innermost while stoppin...
LogicalResult getIndexSet(MutableArrayRef< Operation * > ops, FlatAffineValueConstraints *domain)
Builds a system of constraints with dimensional variables corresponding to the loop IVs of the forOps...
AffineForOp getForInductionVarOwner(Value val)
Returns the loop parent of an induction variable.
void getSupportedReductions(AffineForOp forOp, SmallVectorImpl< LoopReduction > &supportedReductions)
Populate supportedReductions with descriptors of the supported reductions.
DependenceResult checkMemrefAccessDependence(const MemRefAccess &srcAccess, const MemRefAccess &dstAccess, unsigned loopDepth, FlatAffineValueConstraints *dependenceConstraints=nullptr, SmallVector< DependenceComponent, 2 > *dependenceComponents=nullptr, bool allowRAR=false)
void canonicalizeMapAndOperands(AffineMap *map, SmallVectorImpl< Value > *operands)
Modifies both map and operands in-place so as to:
void getReachableAffineApplyOps(ArrayRef< Value > operands, SmallVectorImpl< Operation * > &affineApplyOps)
Returns in affineApplyOps, the sequence of those AffineApplyOp Operations that are reachable via a se...
bool isAffineForInductionVar(Value val)
Returns true if the provided value is the induction variable of an AffineForOp.
void getDependenceComponents(AffineForOp forOp, unsigned maxLoopDepth, std::vector< SmallVector< DependenceComponent, 2 > > *depCompsVec)
Returns in 'depCompsVec', dependence components for dependences between all load and store ops in loo...
bool isLoopMemoryParallel(AffineForOp forOp)
Returns true if ‘forOp’ is a parallel loop.
Region * getAffineAnalysisScope(Operation *op)
Returns the closest region enclosing op that is held by a non-affine operation; nullptr if there is n...
void fullyComposeAffineMapAndOperands(AffineMap *map, SmallVectorImpl< Value > *operands, bool composeAffineMin=false)
Given an affine map map and its input operands, this method composes into map, maps of AffineApplyOps...
LogicalResult getRelationFromMap(AffineMap &map, presburger::IntegerRelation &rel)
Builds a relation from the given AffineMap/AffineValueMap map, containing all pairs of the form opera...
void extractInductionVars(ArrayRef< Operation * > affineOps, SmallVectorImpl< Value > &ivs)
Extracts the induction variables from a list of either AffineForOp or AffineParallelOp and places the...
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 isAffineParallelInductionVar(Value val)
Returns true if val is the induction variable of an AffineParallelOp.
Include the generated interface declarations.
AffineMap simplifyAffineMap(AffineMap map)
Simplifies an affine map by simplifying its underlying AffineExpr results.
bool hasSingleEffect(Operation *op)
Returns "true" if op has only an effect of type EffectTy.
bool isMemoryEffectFree(Operation *op)
Returns true if the given operation is free of memory effects.
Value matchReduction(ArrayRef< BlockArgument > iterCarriedArgs, unsigned redPos, SmallVectorImpl< Operation * > &combinerOps)
Utility to match a generic reduction given a list of iteration-carried arguments, iterCarriedArgs and...
llvm::TypeSwitch< T, ResultT > TypeSwitch
Checks whether two accesses to the same memref access the same element.
A description of a (parallelizable) reduction in an affine loop.
Encapsulates a memref load or store access information.
SmallVector< Value, 4 > indices
LogicalResult getAccessRelation(presburger::IntegerRelation &accessRel) const
Creates an access relation for the access.
void getAccessMap(AffineValueMap *accessMap) const
Populates 'accessMap' with composition of AffineApplyOps reachable from 'indices'.
Eliminates variable at the specified position using Fourier-Motzkin variable elimination.