27 #include "llvm/ADT/TypeSwitch.h"
28 #include "llvm/Support/Debug.h"
29 #include "llvm/Support/raw_ostream.h"
32 #define DEBUG_TYPE "affine-analysis"
35 using namespace affine;
36 using namespace presburger;
44 arith::AtomicRMWKind &kind) {
52 if (combinerOps.size() > 1)
55 Operation *combinerOp = combinerOps.back();
56 std::optional<arith::AtomicRMWKind> maybeKind =
58 .Case([](arith::AddFOp) {
return arith::AtomicRMWKind::addf; })
59 .Case([](arith::MulFOp) {
return arith::AtomicRMWKind::mulf; })
60 .Case([](arith::AddIOp) {
return arith::AtomicRMWKind::addi; })
61 .Case([](arith::AndIOp) {
return arith::AtomicRMWKind::andi; })
62 .Case([](arith::OrIOp) {
return arith::AtomicRMWKind::ori; })
63 .Case([](arith::MulIOp) {
return arith::AtomicRMWKind::muli; })
65 [](arith::MinimumFOp) {
return arith::AtomicRMWKind::minimumf; })
67 [](arith::MaximumFOp) {
return arith::AtomicRMWKind::maximumf; })
68 .Case([](arith::MinSIOp) {
return arith::AtomicRMWKind::mins; })
69 .Case([](arith::MaxSIOp) {
return arith::AtomicRMWKind::maxs; })
70 .Case([](arith::MinUIOp) {
return arith::AtomicRMWKind::minu; })
71 .Case([](arith::MaxUIOp) {
return arith::AtomicRMWKind::maxu; })
72 .Default([](
Operation *) -> std::optional<arith::AtomicRMWKind> {
87 unsigned numIterArgs = forOp.getNumIterOperands();
90 supportedReductions.reserve(numIterArgs);
91 for (
unsigned i = 0; i < numIterArgs; ++i) {
92 arith::AtomicRMWKind kind;
94 supportedReductions.emplace_back(
LoopReduction{kind, i, value});
103 unsigned numIterArgs = forOp.getNumIterOperands();
107 if (numIterArgs > 0 && !parallelReductions)
111 if (parallelReductions) {
115 if (parallelReductions->size() != numIterArgs)
130 if (hasSingleEffect<MemoryEffects::Allocate>(defOp, v) &&
135 auto viewOp = dyn_cast<ViewLikeOpInterface>(defOp);
141 if (llvm::any_of(forOp.getResultTypes(), llvm::IsaPred<BaseMemRefType>))
147 if (
auto readOp = dyn_cast<AffineReadOpInterface>(op)) {
150 loadAndStoreOps.push_back(op);
151 }
else if (
auto writeOp = dyn_cast<AffineWriteOpInterface>(op)) {
154 loadAndStoreOps.push_back(op);
155 }
else if (!isa<AffineForOp, AffineYieldOp, AffineIfOp>(op) &&
156 !hasSingleEffect<MemoryEffects::Allocate>(op) &&
167 if (walkResult.wasInterrupted())
174 for (
auto *srcOp : loadAndStoreOps) {
176 for (
auto *dstOp : loadAndStoreOps) {
198 unsigned operandIndex;
201 for (
auto operand : operands) {
202 worklist.push_back({operand, 0});
205 while (!worklist.empty()) {
206 State &state = worklist.back();
207 auto *opInst = state.value.getDefiningOp();
210 if (!isa_and_nonnull<AffineApplyOp>(opInst)) {
215 if (state.operandIndex == 0) {
217 affineApplyOps.push_back(opInst);
219 if (state.operandIndex < opInst->getNumOperands()) {
222 auto nextOperand = opInst->getOperand(state.operandIndex);
224 ++state.operandIndex;
226 worklist.push_back({nextOperand, 0});
248 if (!isa<AffineForOp, AffineIfOp, AffineParallelOp>(op)) {
249 LLVM_DEBUG(llvm::dbgs() <<
"getIndexSet only handles affine.for/if/"
253 if (AffineForOp forOp = dyn_cast<AffineForOp>(op)) {
254 loopOps.push_back(forOp);
257 }
else if (AffineParallelOp parallelOp = dyn_cast<AffineParallelOp>(op)) {
258 loopOps.push_back(parallelOp);
259 numDims += parallelOp.getNumDims();
268 if (AffineForOp forOp = dyn_cast<AffineForOp>(op)) {
271 }
else if (
auto ifOp = dyn_cast<AffineIfOp>(op)) {
273 }
else if (
auto parallelOp = dyn_cast<AffineParallelOp>(op))
299 unsigned minNumLoops =
301 unsigned numCommonLoops = 0;
302 for (
unsigned i = 0; i < minNumLoops; ++i) {
309 if (commonLoops !=
nullptr)
313 if (commonLoops !=
nullptr)
314 assert(commonLoops->size() == numCommonLoops);
315 return numCommonLoops;
325 auto getChainOfAncestorBlocks =
336 "parent op starting an affine scope is always expected");
337 ancestorBlocks.push_back(currBlock);
342 getChainOfAncestorBlocks(opA, srcAncestorBlocks);
343 getChainOfAncestorBlocks(opB, dstAncestorBlocks);
345 Block *commonBlock =
nullptr;
346 for (
int i = srcAncestorBlocks.size() - 1,
j = dstAncestorBlocks.size() - 1;
347 i >= 0 &&
j >= 0 && srcAncestorBlocks[i] == dstAncestorBlocks[
j];
349 commonBlock = srcAncestorBlocks[i];
363 assert(commonBlock &&
364 "ops expected to have a common surrounding block in affine scope");
369 assert(srcOp &&
"src access op must lie in common block");
371 assert(dstOp &&
"dest access op must lie in common block");
388 unsigned numCols = dependenceDomain->
getNumCols();
392 unsigned numCommonLoopConstraints =
std::min(numCommonLoops, loopDepth);
393 for (
unsigned i = 0; i < numCommonLoopConstraints; ++i) {
394 std::fill(eq.begin(), eq.end(), 0);
396 eq[i + numSrcDims] = 1;
397 if (i == loopDepth - 1) {
398 eq[numCols - 1] = -1;
417 unsigned numCommonLoops =
419 if (numCommonLoops == 0)
422 unsigned numIdsToEliminate = dependenceDomain->
getNumVars();
425 dependenceDomain->
insertVar(VarKind::SetDim, 0,
435 for (
unsigned j = 0;
j < numCommonLoops; ++
j) {
436 std::fill(eq.begin(), eq.end(), 0);
438 eq[
j + numCommonLoops] = 1;
439 eq[
j + numCommonLoops + numSrcDims] = -1;
444 dependenceDomain->
projectOut(numCommonLoops, numIdsToEliminate);
448 dependenceComponents->resize(numCommonLoops);
449 for (
unsigned j = 0;
j < numCommonLoops; ++
j) {
450 (*dependenceComponents)[
j].op = commonLoops[
j].getOperation();
452 (*dependenceComponents)[
j].lb =
455 (*dependenceComponents)[
j].ub =
468 getAccessMap(&accessValueMap);
475 unsigned inserts = 0;
476 for (
unsigned i = 0, e = domain.
getNumDimVars(); i < e; ++i) {
480 const Identifier *itr = std::find(findBegin, findEnd, domainIdi);
481 if (itr != findEnd) {
482 rel.
swapVar(i, i + std::distance(findBegin, itr));
486 rel.
setId(VarKind::SetDim, i, domainIdi);
510 if (
auto loadOp = dyn_cast<AffineReadOpInterface>(opInst))
511 map = loadOp.getAffineMap();
513 map = cast<AffineWriteOpInterface>(opInst).getAffineMap();
519 accessMap->
reset(map, operands);
612 LLVM_DEBUG(llvm::dbgs() <<
"Checking for dependence at depth: "
613 << Twine(loopDepth) <<
" between:\n";);
623 if (!allowRAR && !isa<AffineWriteOpInterface>(srcAccess.
opInst) &&
624 !isa<AffineWriteOpInterface>(dstAccess.
opInst))
651 assert(loopDepth <= numCommonLoops + 1);
652 if (!allowRAR && loopDepth > numCommonLoops &&
671 if (dependenceDomain.
isEmpty())
675 if (dependenceComponents !=
nullptr)
677 dependenceComponents);
679 LLVM_DEBUG(llvm::dbgs() <<
"Dependence polyhedron:\n");
680 LLVM_DEBUG(dependenceDomain.
dump());
683 if (dependenceConstraints)
684 *dependenceConstraints = result;
691 AffineForOp forOp,
unsigned maxLoopDepth,
696 if (isa<AffineReadOpInterface, AffineWriteOpInterface>(op))
697 loadAndStoreOps.push_back(op);
700 unsigned numOps = loadAndStoreOps.size();
701 for (
unsigned d = 1; d <= maxLoopDepth; ++d) {
702 for (
unsigned i = 0; i < numOps; ++i) {
703 auto *srcOp = loadAndStoreOps[i];
705 for (
unsigned j = 0;
j < numOps; ++
j) {
706 auto *dstOp = loadAndStoreOps[
j];
713 srcAccess, dstAccess, d,
nullptr,
716 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 bool isLocallyDefined(Value v, Operation *enclosingOp)
Returns true if v is allocated locally to enclosingOp – i.e., it is allocated by an operation nested ...
static Block * getCommonBlockInAffineScope(Operation *opA, Operation *opB)
Returns the closest surrounding block common to opA and opB.
static void addOrderingConstraints(const FlatAffineValueConstraints &srcDomain, const FlatAffineValueConstraints &dstDomain, unsigned loopDepth, IntegerRelation *dependenceDomain)
static Value max(ImplicitLocOpBuilder &builder, Value value, Value bound)
static Value min(ImplicitLocOpBuilder &builder, Value value, Value bound)
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 ...
void push_back(Operation *op)
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.
std::optional< int64_t > getConstantBound64(BoundType type, unsigned pos) const
The same, but casts to int64_t.
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
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...
const PresburgerSpace & getSpace() const
Returns a reference to the underlying space.
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.
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...
void fullyComposeAffineMapAndOperands(AffineMap *map, SmallVectorImpl< Value > *operands)
Given an affine map map and its input operands, this method composes into map, maps of AffineApplyOps...
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.
bool isLoopMemoryParallel(AffineForOp forOp)
Returns true if ‘forOp’ doesn't have memory dependences preventing parallelization.
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 isLoopParallel(AffineForOp forOp, SmallVectorImpl< LoopReduction > *parallelReductions=nullptr)
Returns true if ‘forOp’ is a parallel loop.
Region * getAffineScope(Operation *op)
Returns the closest region enclosing op that is held by an operation with trait AffineScope; nullptr ...
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 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...
Checks whether two accesses to the same memref access the same element.
enum mlir::affine::DependenceResult::ResultEnum value
A description of a (parallelizable) reduction in an affine loop.
Encapsulates a memref load or store access information.
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.