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