MLIR 24.0.0git
AffineAnalysis.cpp
Go to the documentation of this file.
1//===- AffineAnalysis.cpp - Affine structures analysis routines -----------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file implements miscellaneous analysis routines for affine structures
10// (expressions, maps, sets), and other utilities relying on such analysis.
11//
12//===----------------------------------------------------------------------===//
13
25#include "llvm/ADT/TypeSwitch.h"
26#include "llvm/Support/Debug.h"
27#include "llvm/Support/raw_ostream.h"
28#include <optional>
29
30#define DEBUG_TYPE "affine-analysis"
31
32using namespace mlir;
33using namespace affine;
34using namespace presburger;
35
36/// Get the value that is being reduced by `pos`-th reduction in the loop if
37/// such a reduction can be performed by affine parallel loops. This assumes
38/// floating-point operations are commutative. On success, `kind` will be the
39/// reduction kind suitable for use in affine parallel loop builder. If the
40/// reduction is not supported, returns null.
41static Value getSupportedReduction(AffineForOp forOp, unsigned pos,
42 arith::AtomicRMWKind &kind) {
43 SmallVector<Operation *> combinerOps;
44 Value reducedVal =
45 matchReduction(forOp.getRegionIterArgs(), pos, combinerOps);
46 if (!reducedVal)
47 return nullptr;
48
49 // Expected only one combiner operation.
50 if (combinerOps.size() > 1)
51 return nullptr;
52
53 Operation *combinerOp = combinerOps.back();
54 std::optional<arith::AtomicRMWKind> maybeKind =
56 .Case([](arith::AddFOp) { return arith::AtomicRMWKind::addf; })
57 .Case([](arith::MulFOp) { return arith::AtomicRMWKind::mulf; })
58 .Case([](arith::AddIOp) { return arith::AtomicRMWKind::addi; })
59 .Case([](arith::AndIOp) { return arith::AtomicRMWKind::andi; })
60 .Case([](arith::OrIOp) { return arith::AtomicRMWKind::ori; })
61 .Case([](arith::MulIOp) { return arith::AtomicRMWKind::muli; })
62 .Case(
63 [](arith::MinimumFOp) { return arith::AtomicRMWKind::minimumf; })
64 .Case(
65 [](arith::MaximumFOp) { return arith::AtomicRMWKind::maximumf; })
66 .Case([](arith::MinSIOp) { return arith::AtomicRMWKind::mins; })
67 .Case([](arith::MaxSIOp) { return arith::AtomicRMWKind::maxs; })
68 .Case([](arith::MinUIOp) { return arith::AtomicRMWKind::minu; })
69 .Case([](arith::MaxUIOp) { return arith::AtomicRMWKind::maxu; })
70 .Case([](arith::XOrIOp) { return arith::AtomicRMWKind::xori; })
71 .Case([](arith::MaxNumFOp) { return arith::AtomicRMWKind::maxnumf; })
72 .Case([](arith::MinNumFOp) { return arith::AtomicRMWKind::minnumf; })
73 .Default([](Operation *) -> std::optional<arith::AtomicRMWKind> {
74 return std::nullopt;
75 });
76 if (!maybeKind)
77 return nullptr;
78
79 kind = *maybeKind;
80 return reducedVal;
81}
82
83/// Populate `supportedReductions` with descriptors of the supported reductions.
85 AffineForOp forOp, SmallVectorImpl<LoopReduction> &supportedReductions) {
86 unsigned numIterArgs = forOp.getNumIterOperands();
87 if (numIterArgs == 0)
88 return;
89 supportedReductions.reserve(numIterArgs);
90 for (unsigned i = 0; i < numIterArgs; ++i) {
91 arith::AtomicRMWKind kind;
92 if (Value value = getSupportedReduction(forOp, i, kind))
93 supportedReductions.emplace_back(LoopReduction{kind, i, value});
94 }
95}
96
97/// Returns true if `forOp' is a parallel loop. If `parallelReductions` is
98/// provided, populates it with descriptors of the parallelizable reductions and
99/// treats them as not preventing parallelization.
100bool mlir::affine::isLoopParallel(
101 AffineForOp forOp, SmallVectorImpl<LoopReduction> *parallelReductions) {
102 unsigned numIterArgs = forOp.getNumIterOperands();
103
104 // Loop is not parallel if it has SSA loop-carried dependences and reduction
105 // detection is not requested.
106 if (numIterArgs > 0 && !parallelReductions)
107 return false;
108
109 // Find supported reductions of requested.
110 if (parallelReductions) {
111 getSupportedReductions(forOp, *parallelReductions);
112 // Return later to allow for identifying all parallel reductions even if the
113 // loop is not parallel.
114 if (parallelReductions->size() != numIterArgs)
115 return false;
116 }
117
118 // Check memory dependences.
119 return isLoopMemoryParallel(forOp);
120}
121
122/// Returns true if `v` is allocated locally to `enclosingOp` -- i.e., it is
123/// allocated by an operation nested within `enclosingOp`.
124static bool isLocallyDefined(Value v, Operation *enclosingOp) {
125 Operation *defOp = v.getDefiningOp();
126 if (!defOp)
127 return false;
128
130 enclosingOp->isProperAncestor(defOp))
131 return true;
132
133 // Aliasing ops.
134 auto viewOp = dyn_cast<ViewLikeOpInterface>(defOp);
135 return viewOp && isLocallyDefined(viewOp.getViewSource(), enclosingOp);
136}
137
138bool mlir::affine::isLoopMemoryParallel(AffineForOp forOp) {
139 // Any memref-typed iteration arguments are treated as serializing.
140 if (llvm::any_of(forOp.getResultTypes(), llvm::IsaPred<BaseMemRefType>))
141 return false;
142
143 // Collect all load and store ops in loop nest rooted at 'forOp'.
144 SmallVector<Operation *, 8> loadAndStoreOps;
145 auto walkResult = forOp.walk([&](Operation *op) -> WalkResult {
146 if (auto readOp = dyn_cast<AffineReadOpInterface>(op)) {
147 // Memrefs that are allocated inside `forOp` need not be considered.
148 if (!isLocallyDefined(readOp.getMemRef(), forOp))
149 loadAndStoreOps.push_back(op);
150 } else if (auto writeOp = dyn_cast<AffineWriteOpInterface>(op)) {
151 // Filter out stores the same way as above.
152 if (!isLocallyDefined(writeOp.getMemRef(), forOp))
153 loadAndStoreOps.push_back(op);
154 } else if (!isa<AffineForOp, AffineYieldOp, AffineIfOp>(op) &&
156 !isMemoryEffectFree(op)) {
157 // Alloc-like ops inside `forOp` are fine (they don't impact parallelism)
158 // as long as they don't escape the loop (which has been checked above).
159 return WalkResult::interrupt();
160 }
161
162 return WalkResult::advance();
163 });
164
165 // Stop early if the loop has unknown ops with side effects.
166 if (walkResult.wasInterrupted())
167 return false;
168
169 // Dep check depth would be number of enclosing loops + 1.
170 unsigned depth = getNestingDepth(forOp) + 1;
171
172 // Check dependences between all pairs of ops in 'loadAndStoreOps'.
173 for (auto *srcOp : loadAndStoreOps) {
174 MemRefAccess srcAccess(srcOp);
175 for (auto *dstOp : loadAndStoreOps) {
176 MemRefAccess dstAccess(dstOp);
178 checkMemrefAccessDependence(srcAccess, dstAccess, depth);
180 return false;
181 }
182 }
183 return true;
184}
185
186/// Returns the sequence of AffineApplyOp Operations operation in
187/// 'affineApplyOps', which are reachable via a search starting from 'operands',
188/// and ending at operands which are not defined by AffineApplyOps.
189// TODO: Add a method to AffineApplyOp which forward substitutes the
190// AffineApplyOp into any user AffineApplyOps.
192 ArrayRef<Value> operands, SmallVectorImpl<Operation *> &affineApplyOps) {
193 struct State {
194 // The ssa value for this node in the DFS traversal.
195 Value value;
196 // The operand index of 'value' to explore next during DFS traversal.
197 unsigned operandIndex;
198 };
199 SmallVector<State, 4> worklist;
200 for (auto operand : operands) {
201 worklist.push_back({operand, 0});
202 }
203
204 while (!worklist.empty()) {
205 State &state = worklist.back();
206 auto *opInst = state.value.getDefiningOp();
207 // Note: getDefiningOp will return nullptr if the operand is not an
208 // Operation (i.e. block argument), which is a terminator for the search.
209 if (!isa_and_nonnull<AffineApplyOp>(opInst)) {
210 worklist.pop_back();
211 continue;
212 }
213
214 if (state.operandIndex == 0) {
215 // Pre-Visit: Add 'opInst' to reachable sequence.
216 affineApplyOps.push_back(opInst);
217 }
218 if (state.operandIndex < opInst->getNumOperands()) {
219 // Visit: Add next 'affineApplyOp' operand to worklist.
220 // Get next operand to visit at 'operandIndex'.
221 auto nextOperand = opInst->getOperand(state.operandIndex);
222 // Increment 'operandIndex' in 'state'.
223 ++state.operandIndex;
224 // Add 'nextOperand' to worklist.
225 worklist.push_back({nextOperand, 0});
226 } else {
227 // Post-visit: done visiting operands AffineApplyOp, pop off stack.
228 worklist.pop_back();
229 }
230 }
231}
232
233// Builds a system of constraints with dimensional variables corresponding to
234// the loop IVs of the forOps appearing in that order. Any symbols founds in
235// the bound operands are added as symbols in the system. Returns failure for
236// the yet unimplemented cases.
237// TODO: Handle non-unit steps through local variables or stride information in
238// FlatAffineValueConstraints. (For eg., by using iv - lb % step = 0 and/or by
239// introducing a method in FlatAffineValueConstraints
240// setExprStride(ArrayRef<int64_t> expr, int64_t stride)
245 size_t numDims = 0;
246 for (Operation *op : ops) {
247 if (!isa<AffineForOp, AffineIfOp, AffineParallelOp>(op)) {
248 LLVM_DEBUG(llvm::dbgs() << "getIndexSet only handles affine.for/if/"
249 "parallel ops");
250 return failure();
251 }
252 if (AffineForOp forOp = dyn_cast<AffineForOp>(op)) {
253 loopOps.push_back(forOp);
254 // An AffineForOp retains only 1 induction variable.
255 numDims += 1;
256 } else if (AffineParallelOp parallelOp = dyn_cast<AffineParallelOp>(op)) {
257 loopOps.push_back(parallelOp);
258 numDims += parallelOp.getNumDims();
259 }
260 }
262 // Reset while associating Values in 'indices' to the domain.
263 *domain = FlatAffineValueConstraints(numDims, /*numSymbols=*/0,
264 /*numLocals=*/0, indices);
265 for (Operation *op : ops) {
266 // Add constraints from forOp's bounds.
267 if (AffineForOp forOp = dyn_cast<AffineForOp>(op)) {
268 if (failed(domain->addAffineForOpDomain(forOp)))
269 return failure();
270 } else if (auto ifOp = dyn_cast<AffineIfOp>(op)) {
271 domain->addAffineIfOpDomain(ifOp);
272 } else if (auto parallelOp = dyn_cast<AffineParallelOp>(op))
273 if (failed(domain->addAffineParallelOpDomain(parallelOp)))
274 return failure();
275 }
276 return success();
277}
278
279/// Computes the iteration domain for 'op' and populates 'indexSet', which
280/// encapsulates the constraints involving loops surrounding 'op' and
281/// potentially involving any Function symbols. The dimensional variables in
282/// 'indexSet' correspond to the loops surrounding 'op' from outermost to
283/// innermost.
284static LogicalResult getOpIndexSet(Operation *op,
285 FlatAffineValueConstraints *indexSet) {
287 getEnclosingAffineOps(*op, &ops);
288 return getIndexSet(ops, indexSet);
289}
290
291// Returns the number of outer loop common to 'src/dstDomain'.
292// Loops common to 'src/dst' domains are added to 'commonLoops' if non-null.
293static unsigned
295 const FlatAffineValueConstraints &dstDomain,
296 SmallVectorImpl<AffineForOp> *commonLoops = nullptr) {
297 // Find the number of common loops shared by src and dst accesses.
298 unsigned minNumLoops =
299 std::min(srcDomain.getNumDimVars(), dstDomain.getNumDimVars());
300 unsigned numCommonLoops = 0;
301 for (unsigned i = 0; i < minNumLoops; ++i) {
302 if ((!isAffineForInductionVar(srcDomain.getValue(i)) &&
303 !isAffineParallelInductionVar(srcDomain.getValue(i))) ||
304 (!isAffineForInductionVar(dstDomain.getValue(i)) &&
305 !isAffineParallelInductionVar(dstDomain.getValue(i))) ||
306 srcDomain.getValue(i) != dstDomain.getValue(i))
307 break;
308 if (commonLoops != nullptr)
309 commonLoops->push_back(getForInductionVarOwner(srcDomain.getValue(i)));
310 ++numCommonLoops;
311 }
312 if (commonLoops != nullptr)
313 assert(commonLoops->size() == numCommonLoops);
314 return numCommonLoops;
315}
316
317/// Returns the closest surrounding block common to `opA` and `opB`. `opA` and
318/// `opB` should be in the same affine scope. Returns nullptr if such a block
319/// does not exist (when the two ops are in different blocks of an op starting
320/// an `AffineScope`).
322 // Get the chain of ancestor blocks for the given `MemRefAccess` instance. The
323 // chain extends up to and includnig an op that starts an affine scope.
324 auto getChainOfAncestorBlocks =
325 [&](Operation *op, SmallVectorImpl<Block *> &ancestorBlocks) {
326 Block *currBlock = op->getBlock();
327 // Loop terminates when the currBlock is nullptr or its parent operation
328 // holds an affine scope.
329 while (currBlock &&
330 !currBlock->getParentOp()->hasTrait<OpTrait::AffineScope>()) {
331 ancestorBlocks.push_back(currBlock);
332 currBlock = currBlock->getParentOp()->getBlock();
333 }
334 assert(currBlock &&
335 "parent op starting an affine scope is always expected");
336 ancestorBlocks.push_back(currBlock);
337 };
338
339 // Find the closest common block.
340 SmallVector<Block *, 4> srcAncestorBlocks, dstAncestorBlocks;
341 getChainOfAncestorBlocks(opA, srcAncestorBlocks);
342 getChainOfAncestorBlocks(opB, dstAncestorBlocks);
343
344 Block *commonBlock = nullptr;
345 for (int i = srcAncestorBlocks.size() - 1, j = dstAncestorBlocks.size() - 1;
346 i >= 0 && j >= 0 && srcAncestorBlocks[i] == dstAncestorBlocks[j];
347 i--, j--)
348 commonBlock = srcAncestorBlocks[i];
349
350 return commonBlock;
351}
352
353/// Returns true if the ancestor operation of 'srcAccess' appears before the
354/// ancestor operation of 'dstAccess' in their common ancestral block. The
355/// operations for `srcAccess` and `dstAccess` are expected to be in the same
356/// affine scope and have a common surrounding block within it.
358 const MemRefAccess &dstAccess) {
359 // Get Block common to 'srcAccess.opInst' and 'dstAccess.opInst'.
360 Block *commonBlock =
361 getCommonBlockInAffineScope(srcAccess.opInst, dstAccess.opInst);
362 assert(commonBlock &&
363 "ops expected to have a common surrounding block in affine scope");
364
365 // Check the dominance relationship between the respective ancestors of the
366 // src and dst in the Block of the innermost among the common loops.
367 Operation *srcOp = commonBlock->findAncestorOpInBlock(*srcAccess.opInst);
368 assert(srcOp && "src access op must lie in common block");
369 Operation *dstOp = commonBlock->findAncestorOpInBlock(*dstAccess.opInst);
370 assert(dstOp && "dest access op must lie in common block");
371
372 // Determine whether dstOp comes after srcOp.
373 return srcOp->isBeforeInBlock(dstOp);
374}
375
376// Adds ordering constraints to 'dependenceDomain' based on number of loops
377// common to 'src/dstDomain' and requested 'loopDepth'.
378// Note that 'loopDepth' cannot exceed the number of common loops plus one.
379// EX: Given a loop nest of depth 2 with IVs 'i' and 'j':
380// *) If 'loopDepth == 1' then one constraint is added: i' >= i + 1
381// *) If 'loopDepth == 2' then two constraints are added: i == i' and j' > j + 1
382// *) If 'loopDepth == 3' then two constraints are added: i == i' and j == j'
384 const FlatAffineValueConstraints &dstDomain,
385 unsigned loopDepth,
386 IntegerRelation *dependenceDomain) {
387 unsigned numCols = dependenceDomain->getNumCols();
388 SmallVector<int64_t, 4> eq(numCols);
389 unsigned numSrcDims = srcDomain.getNumDimVars();
390 unsigned numCommonLoops = getNumCommonLoops(srcDomain, dstDomain);
391 unsigned numCommonLoopConstraints = std::min(numCommonLoops, loopDepth);
392 for (unsigned i = 0; i < numCommonLoopConstraints; ++i) {
393 llvm::fill(eq, 0);
394 eq[i] = -1;
395 eq[i + numSrcDims] = 1;
396 if (i == loopDepth - 1) {
397 eq[numCols - 1] = -1;
398 dependenceDomain->addInequality(eq);
399 } else {
400 dependenceDomain->addEquality(eq);
401 }
402 }
403}
404
405// Computes distance and direction vectors in 'dependences', by adding
406// variables to 'dependenceDomain' which represent the difference of the IVs,
407// eliminating all other variables, and reading off distance vectors from
408// equality constraints (if possible), and direction vectors from inequalities.
410 const FlatAffineValueConstraints &srcDomain,
411 const FlatAffineValueConstraints &dstDomain, unsigned loopDepth,
412 IntegerPolyhedron *dependenceDomain,
413 SmallVector<DependenceComponent, 2> *dependenceComponents) {
414 // Find the number of common loops shared by src and dst accesses.
415 SmallVector<AffineForOp, 4> commonLoops;
416 unsigned numCommonLoops =
417 getNumCommonLoops(srcDomain, dstDomain, &commonLoops);
418 if (numCommonLoops == 0)
419 return;
420 // Compute direction vectors for requested loop depth.
421 unsigned numIdsToEliminate = dependenceDomain->getNumVars();
422 // Add new variables to 'dependenceDomain' to represent the direction
423 // constraints for each shared loop.
424 dependenceDomain->insertVar(VarKind::SetDim, /*pos=*/0,
425 /*num=*/numCommonLoops);
426
427 // Add equality constraints for each common loop, setting newly introduced
428 // variable at column 'j' to the 'dst' IV minus the 'src IV.
430 eq.resize(dependenceDomain->getNumCols());
431 unsigned numSrcDims = srcDomain.getNumDimVars();
432 // Constraint variables format:
433 // [num-common-loops][num-src-dim-ids][num-dst-dim-ids][num-symbols][constant]
434 for (unsigned j = 0; j < numCommonLoops; ++j) {
435 llvm::fill(eq, 0);
436 eq[j] = 1;
437 eq[j + numCommonLoops] = 1;
438 eq[j + numCommonLoops + numSrcDims] = -1;
439 dependenceDomain->addEquality(eq);
440 }
441
442 // Eliminate all variables other than the direction variables just added.
443 dependenceDomain->projectOut(numCommonLoops, numIdsToEliminate);
444
445 // Scan each common loop variable column and set direction vectors based
446 // on eliminated constraint system.
447 dependenceComponents->resize(numCommonLoops);
448 for (unsigned j = 0; j < numCommonLoops; ++j) {
449 (*dependenceComponents)[j].op = commonLoops[j].getOperation();
450 auto lbConst = dependenceDomain->getConstantBound64(BoundType::LB, j);
451 (*dependenceComponents)[j].lb =
452 lbConst.value_or(std::numeric_limits<int64_t>::min());
453 auto ubConst = dependenceDomain->getConstantBound64(BoundType::UB, j);
454 (*dependenceComponents)[j].ub =
455 ubConst.value_or(std::numeric_limits<int64_t>::max());
456 }
457}
458
459LogicalResult
461 const FlatAffineValueConstraints &domain,
462 IntegerRelation &rel) {
463 // Get access relation from access map.
464 if (failed(getRelationFromMap(accessValueMap, rel)))
465 return failure();
466
467 // Merge and align domain ids of `rel` with ids of `domain`. Since the domain
468 // of the access map is a subset of the domain of access, the domain ids of
469 // `rel` are guranteed to be a subset of ids of `domain`.
470 unsigned inserts = 0;
471 for (unsigned i = 0, e = domain.getNumDimVars(); i < e; ++i) {
472 const Identifier domainIdi = Identifier(domain.getValue(i));
473 const Identifier *findBegin = rel.getIds(VarKind::SetDim).begin() + i;
474 const Identifier *findEnd = rel.getIds(VarKind::SetDim).end();
475 const Identifier *itr = std::find(findBegin, findEnd, domainIdi);
476 if (itr != findEnd) {
477 rel.swapVar(i, i + std::distance(findBegin, itr));
478 } else {
479 ++inserts;
480 rel.insertVar(VarKind::SetDim, i);
481 rel.setId(VarKind::SetDim, i, domainIdi);
482 }
483 }
484
485 // Append domain constraints to `rel`.
486 IntegerRelation domainRel = domain;
487 // For 0-d spaces, there will be no IDs. Enable if that's the case.
488 if (!domainRel.getSpace().isUsingIds())
489 domainRel.resetIds();
490 if (!rel.getSpace().isUsingIds())
491 rel.resetIds();
492 domainRel.appendVar(VarKind::Range, accessValueMap.getNumResults());
493 domainRel.mergeAndAlignSymbols(rel);
494 domainRel.mergeLocalVars(rel);
495 rel.append(domainRel);
496
497 rel.convertVarKind(VarKind::SetDim, 0, accessValueMap.getNumDims() + inserts,
498 VarKind::Domain);
499
500 return success();
501}
502
504 // Create set corresponding to domain of access.
506 if (failed(getOpIndexSet(opInst, &domain)))
507 return failure();
508
509 AffineValueMap accessValueMap;
510 getAccessMap(&accessValueMap);
511 return mlir::affine::getAccessRelation(accessValueMap, domain, rel);
512}
513
514// Populates 'accessMap' with composition of AffineApplyOps reachable from
515// indices of MemRefAccess.
517 // Get affine map from AffineLoad/Store.
518 AffineMap map;
519 if (auto loadOp = dyn_cast<AffineReadOpInterface>(opInst))
520 map = loadOp.getAffineMap();
521 else
522 map = cast<AffineWriteOpInterface>(opInst).getAffineMap();
523
524 SmallVector<Value, 8> operands(indices.begin(), indices.end());
525 fullyComposeAffineMapAndOperands(&map, &operands);
526 map = simplifyAffineMap(map);
527 canonicalizeMapAndOperands(&map, &operands);
528 accessMap->reset(map, operands);
529}
530
531// Builds a flat affine constraint system to check if there exists a dependence
532// between memref accesses 'srcAccess' and 'dstAccess'.
533// Returns 'NoDependence' if the accesses can be definitively shown not to
534// access the same element.
535// Returns 'HasDependence' if the accesses do access the same element.
536// Returns 'Failure' if an error or unsupported case was encountered.
537// If a dependence exists, returns in 'dependenceComponents' a direction
538// vector for the dependence, with a component for each loop IV in loops
539// common to both accesses (see Dependence in AffineAnalysis.h for details).
540//
541// The memref access dependence check is comprised of the following steps:
542// *) Build access relation for each access. An access relation maps elements
543// of an iteration domain to the element(s) of an array domain accessed by
544// that iteration of the associated statement through some array reference.
545// *) Compute the dependence relation by composing access relation of
546// `srcAccess` with the inverse of access relation of `dstAccess`.
547// Doing this builds a relation between iteration domain of `srcAccess`
548// to the iteration domain of `dstAccess` which access the same memory
549// location.
550// *) Add ordering constraints for `srcAccess` to be accessed before
551// `dstAccess`.
552//
553// This method builds a constraint system with the following column format:
554//
555// [src-dim-variables, dst-dim-variables, symbols, constant]
556//
557// For example, given the following MLIR code with "source" and "destination"
558// accesses to the same memref label, and symbols %M, %N, %K:
559//
560// affine.for %i0 = 0 to 100 {
561// affine.for %i1 = 0 to 50 {
562// %a0 = affine.apply
563// (d0, d1) -> (d0 * 2 - d1 * 4 + s1, d1 * 3 - s0) (%i0, %i1)[%M, %N]
564// // Source memref access.
565// store %v0, %m[%a0#0, %a0#1] : memref<4x4xf32>
566// }
567// }
568//
569// affine.for %i2 = 0 to 100 {
570// affine.for %i3 = 0 to 50 {
571// %a1 = affine.apply
572// (d0, d1) -> (d0 * 7 + d1 * 9 - s1, d1 * 11 + s0) (%i2, %i3)[%K, %M]
573// // Destination memref access.
574// %v1 = load %m[%a1#0, %a1#1] : memref<4x4xf32>
575// }
576// }
577//
578// The access relation for `srcAccess` would be the following:
579//
580// [src_dim0, src_dim1, mem_dim0, mem_dim1, %N, %M, const]
581// 2 -4 -1 0 1 0 0 = 0
582// 0 3 0 -1 0 -1 0 = 0
583// 1 0 0 0 0 0 0 >= 0
584// -1 0 0 0 0 0 100 >= 0
585// 0 1 0 0 0 0 0 >= 0
586// 0 -1 0 0 0 0 50 >= 0
587//
588// The access relation for `dstAccess` would be the following:
589//
590// [dst_dim0, dst_dim1, mem_dim0, mem_dim1, %M, %K, const]
591// 7 9 -1 0 -1 0 0 = 0
592// 0 11 0 -1 0 -1 0 = 0
593// 1 0 0 0 0 0 0 >= 0
594// -1 0 0 0 0 0 100 >= 0
595// 0 1 0 0 0 0 0 >= 0
596// 0 -1 0 0 0 0 50 >= 0
597//
598// The equalities in the above relations correspond to the access maps while
599// the inequalities corresspond to the iteration domain constraints.
600//
601// The dependence relation formed:
602//
603// [src_dim0, src_dim1, dst_dim0, dst_dim1, %M, %N, %K, const]
604// 2 -4 -7 -9 1 1 0 0 = 0
605// 0 3 0 -11 -1 0 1 0 = 0
606// 1 0 0 0 0 0 0 0 >= 0
607// -1 0 0 0 0 0 0 100 >= 0
608// 0 1 0 0 0 0 0 0 >= 0
609// 0 -1 0 0 0 0 0 50 >= 0
610// 0 0 1 0 0 0 0 0 >= 0
611// 0 0 -1 0 0 0 0 100 >= 0
612// 0 0 0 1 0 0 0 0 >= 0
613// 0 0 0 -1 0 0 0 50 >= 0
614//
615//
616// TODO: Support AffineExprs mod/floordiv/ceildiv.
618 const MemRefAccess &srcAccess, const MemRefAccess &dstAccess,
619 unsigned loopDepth, FlatAffineValueConstraints *dependenceConstraints,
620 SmallVector<DependenceComponent, 2> *dependenceComponents, bool allowRAR) {
621 LLVM_DEBUG(llvm::dbgs() << "Checking for dependence at depth: "
622 << Twine(loopDepth) << " between:\n";);
623 LLVM_DEBUG(srcAccess.opInst->dump());
624 LLVM_DEBUG(dstAccess.opInst->dump());
625
626 // Return 'NoDependence' if these accesses do not access the same memref.
627 if (srcAccess.memref != dstAccess.memref)
629
630 // Return 'NoDependence' if one of these accesses is not an
631 // AffineWriteOpInterface.
632 if (!allowRAR && !isa<AffineWriteOpInterface>(srcAccess.opInst) &&
633 !isa<AffineWriteOpInterface>(dstAccess.opInst))
635
636 // We can't analyze further if the ops lie in different affine scopes or have
637 // no common block in an affine scope.
638 if (getAffineAnalysisScope(srcAccess.opInst) !=
641 if (!getCommonBlockInAffineScope(srcAccess.opInst, dstAccess.opInst))
643
644 // Create access relation from each MemRefAccess.
646 IntegerRelation srcRel(space), dstRel(space);
647 if (failed(srcAccess.getAccessRelation(srcRel)))
649 if (failed(dstAccess.getAccessRelation(dstRel)))
651
652 FlatAffineValueConstraints srcDomain(srcRel.getDomainSet());
653 FlatAffineValueConstraints dstDomain(dstRel.getDomainSet());
654
655 // Return 'NoDependence' if loopDepth > numCommonLoops and if the ancestor
656 // operation of 'srcAccess' does not properly dominate the ancestor
657 // operation of 'dstAccess' in the same common operation block.
658 // Note: this check is skipped if 'allowRAR' is true, because RAR deps
659 // can exist irrespective of lexicographic ordering b/w src and dst.
660 unsigned numCommonLoops = getNumCommonLoops(srcDomain, dstDomain);
661 assert(loopDepth <= numCommonLoops + 1 && "Invalid depth");
662 if (!allowRAR && loopDepth > numCommonLoops &&
663 !srcAppearsBeforeDstInAncestralBlock(srcAccess, dstAccess)) {
665 }
666
667 return checkAccessDependence(srcRel, dstRel, loopDepth, dependenceConstraints,
668 dependenceComponents);
669}
670
672 IntegerRelation srcRel, IntegerRelation dstRel, unsigned loopDepth,
673 FlatAffineValueConstraints *dependenceConstraints,
674 SmallVector<DependenceComponent, 2> *dependenceComponents) {
675 FlatAffineValueConstraints srcDomain(srcRel.getDomainSet());
676 FlatAffineValueConstraints dstDomain(dstRel.getDomainSet());
677 assert(loopDepth <= getNumCommonLoops(srcDomain, dstDomain) + 1 &&
678 "Invalid depth");
679
680 // Compute the dependence relation by composing `srcRel` with the inverse of
681 // `dstRel`. Doing this builds a relation between the iteration domain of the
682 // source access to the iteration domain of the destination access which
683 // access the same memory locations.
684 dstRel.inverse();
685 // For 0-d spaces, there will be no IDs. Enable if that's the case.
686 if (!dstRel.getSpace().isUsingIds())
687 dstRel.resetIds();
688 if (!srcRel.getSpace().isUsingIds())
689 srcRel.resetIds();
690 dstRel.mergeAndCompose(srcRel);
691 dstRel.convertVarKind(VarKind::Domain, 0, dstRel.getNumDomainVars(),
692 VarKind::Range, 0);
693 IntegerPolyhedron dependenceDomain(dstRel);
694
695 // Add 'src' happens before 'dst' ordering constraints.
696 addOrderingConstraints(srcDomain, dstDomain, loopDepth, &dependenceDomain);
697
698 // Return 'NoDependence' if the solution space is empty: no dependence.
699 if (dependenceDomain.isEmpty())
701
702 // Compute dependence direction vector and return true.
703 if (dependenceComponents != nullptr)
704 computeDirectionVector(srcDomain, dstDomain, loopDepth, &dependenceDomain,
705 dependenceComponents);
706
707 LLVM_DEBUG(llvm::dbgs() << "Dependence polyhedron:\n");
708 LLVM_DEBUG(dependenceDomain.dump());
709
710 FlatAffineValueConstraints result(dependenceDomain);
711 if (dependenceConstraints)
712 *dependenceConstraints = result;
714}
715
716/// Gathers dependence components for dependences between all ops in loop nest
717/// rooted at 'forOp' at loop depths in range [1, maxLoopDepth].
719 AffineForOp forOp, unsigned maxLoopDepth,
720 std::vector<SmallVector<DependenceComponent, 2>> *depCompsVec) {
721 // Collect all load and store ops in loop nest rooted at 'forOp'.
722 SmallVector<Operation *, 8> loadAndStoreOps;
723 forOp->walk([&](Operation *op) {
724 if (isa<AffineReadOpInterface, AffineWriteOpInterface>(op))
725 loadAndStoreOps.push_back(op);
726 });
727
728 unsigned numOps = loadAndStoreOps.size();
729 for (unsigned d = 1; d <= maxLoopDepth; ++d) {
730 for (unsigned i = 0; i < numOps; ++i) {
731 auto *srcOp = loadAndStoreOps[i];
732 MemRefAccess srcAccess(srcOp);
733 for (unsigned j = 0; j < numOps; ++j) {
734 auto *dstOp = loadAndStoreOps[j];
735 MemRefAccess dstAccess(dstOp);
736
738 // TODO: Explore whether it would be profitable to pre-compute and store
739 // deps instead of repeatedly checking.
741 srcAccess, dstAccess, d, /*dependenceConstraints=*/nullptr,
742 &depComps);
744 depCompsVec->push_back(depComps);
745 }
746 }
747 }
748}
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...
return success()
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.
Definition AffineMap.h:46
Block represents an ordered list of Operations.
Definition Block.h:34
Operation * findAncestorOpInBlock(Operation &op)
Returns 'op' if 'op' lies in this block, or otherwise finds the ancestor operation of 'op' that lies ...
Definition Block.cpp:74
Operation * getParentOp()
Returns the closest surrounding operation that contains this block.
Definition Block.cpp:31
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.
Definition Operation.h:87
bool hasTrait()
Returns true if the operation was registered with a particular trait, e.g.
Definition Operation.h:801
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.
Definition Operation.h:230
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...
Definition Value.h:96
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition Value.cpp:18
A utility result that is used to signal how to proceed with an ongoing walk:
Definition WalkResult.h:29
static WalkResult advance()
Definition WalkResult.h:47
static WalkResult interrupt()
Definition WalkResult.h:46
An AffineValueMap is an affine map plus its ML value operands and results for analysis purposes.
void reset(AffineMap map, ValueRange operands, ValueRange results={})
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.
const PresburgerSpace & getSpace() const
Returns a reference to the underlying space.
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.
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)
LogicalResult getAccessRelation(const AffineValueMap &accessValueMap, const FlatAffineValueConstraints &domain, presburger::IntegerRelation &rel)
Builds in rel the access relation of an access that reads or writes accessValueMap over the iteration...
void getEnclosingAffineOps(Operation &op, SmallVectorImpl< Operation * > *ops)
Populates 'ops' with affine operations enclosing op ordered from outermost to innermost while stoppin...
Definition Utils.cpp:876
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.
Definition Utils.cpp:2087
DependenceResult checkAccessDependence(presburger::IntegerRelation srcRel, presburger::IntegerRelation dstRel, unsigned loopDepth, FlatAffineValueConstraints *dependenceConstraints=nullptr, SmallVector< DependenceComponent, 2 > *dependenceComponents=nullptr)
Checks whether the accesses two relations describe touch the same element, i.e.
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
Definition LLVM.h:139
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.