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 14 #include "mlir/Dialect/Affine/Analysis/AffineAnalysis.h" 15 #include "mlir/Analysis/SliceAnalysis.h" 16 #include "mlir/Dialect/Affine/Analysis/LoopAnalysis.h" 17 #include "mlir/Dialect/Affine/Analysis/Utils.h" 18 #include "mlir/Dialect/Affine/IR/AffineOps.h" 19 #include "mlir/Dialect/Affine/IR/AffineValueMap.h" 20 #include "mlir/Dialect/Func/IR/FuncOps.h" 21 #include "mlir/IR/AffineExprVisitor.h" 22 #include "mlir/IR/BuiltinOps.h" 23 #include "mlir/IR/IntegerSet.h" 24 #include "mlir/Interfaces/SideEffectInterfaces.h" 25 #include "mlir/Interfaces/ViewLikeInterface.h" 26 #include "llvm/ADT/TypeSwitch.h" 27 #include "llvm/Support/Debug.h" 28 #include "llvm/Support/raw_ostream.h" 29 30 #define DEBUG_TYPE "affine-analysis" 31 32 using namespace mlir; 33 using 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. 40 static 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 Optional<arith::AtomicRMWKind> maybeKind = 54 TypeSwitch<Operation *, Optional<arith::AtomicRMWKind>>(combinerOp) 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([](arith::MinFOp) { return arith::AtomicRMWKind::minf; }) 62 .Case([](arith::MaxFOp) { return arith::AtomicRMWKind::maxf; }) 63 .Case([](arith::MinSIOp) { return arith::AtomicRMWKind::mins; }) 64 .Case([](arith::MaxSIOp) { return arith::AtomicRMWKind::maxs; }) 65 .Case([](arith::MinUIOp) { return arith::AtomicRMWKind::minu; }) 66 .Case([](arith::MaxUIOp) { return arith::AtomicRMWKind::maxu; }) 67 .Default([](Operation *) -> Optional<arith::AtomicRMWKind> { 68 // TODO: AtomicRMW supports other kinds of reductions this is 69 // currently not detecting, add those when the need arises. 70 return llvm::None; 71 }); 72 if (!maybeKind) 73 return nullptr; 74 75 kind = *maybeKind; 76 return reducedVal; 77 } 78 79 /// Populate `supportedReductions` with descriptors of the supported reductions. 80 void mlir::getSupportedReductions( 81 AffineForOp forOp, SmallVectorImpl<LoopReduction> &supportedReductions) { 82 unsigned numIterArgs = forOp.getNumIterOperands(); 83 if (numIterArgs == 0) 84 return; 85 supportedReductions.reserve(numIterArgs); 86 for (unsigned i = 0; i < numIterArgs; ++i) { 87 arith::AtomicRMWKind kind; 88 if (Value value = getSupportedReduction(forOp, i, kind)) 89 supportedReductions.emplace_back(LoopReduction{kind, i, value}); 90 } 91 } 92 93 /// Returns true if `forOp' is a parallel loop. If `parallelReductions` is 94 /// provided, populates it with descriptors of the parallelizable reductions and 95 /// treats them as not preventing parallelization. 96 bool mlir::isLoopParallel(AffineForOp forOp, 97 SmallVectorImpl<LoopReduction> *parallelReductions) { 98 unsigned numIterArgs = forOp.getNumIterOperands(); 99 100 // Loop is not parallel if it has SSA loop-carried dependences and reduction 101 // detection is not requested. 102 if (numIterArgs > 0 && !parallelReductions) 103 return false; 104 105 // Find supported reductions of requested. 106 if (parallelReductions) { 107 getSupportedReductions(forOp, *parallelReductions); 108 // Return later to allow for identifying all parallel reductions even if the 109 // loop is not parallel. 110 if (parallelReductions->size() != numIterArgs) 111 return false; 112 } 113 114 // Check memory dependences. 115 return isLoopMemoryParallel(forOp); 116 } 117 118 /// Returns true if `v` is allocated locally to `enclosingOp` -- i.e., it is 119 /// allocated by an operation nested within `enclosingOp`. 120 static bool isLocallyDefined(Value v, Operation *enclosingOp) { 121 Operation *defOp = v.getDefiningOp(); 122 if (!defOp) 123 return false; 124 125 if (hasSingleEffect<MemoryEffects::Allocate>(defOp, v) && 126 enclosingOp->isProperAncestor(defOp)) 127 return true; 128 129 // Aliasing ops. 130 auto viewOp = dyn_cast<ViewLikeOpInterface>(defOp); 131 return viewOp && isLocallyDefined(viewOp.getViewSource(), enclosingOp); 132 } 133 134 bool mlir::isLoopMemoryParallel(AffineForOp forOp) { 135 // Any memref-typed iteration arguments are treated as serializing. 136 if (llvm::any_of(forOp.getResultTypes(), 137 [](Type type) { return type.isa<BaseMemRefType>(); })) 138 return false; 139 140 // Collect all load and store ops in loop nest rooted at 'forOp'. 141 SmallVector<Operation *, 8> loadAndStoreOps; 142 auto walkResult = forOp.walk([&](Operation *op) -> WalkResult { 143 if (auto readOp = dyn_cast<AffineReadOpInterface>(op)) { 144 // Memrefs that are allocated inside `forOp` need not be considered. 145 if (!isLocallyDefined(readOp.getMemRef(), forOp)) 146 loadAndStoreOps.push_back(op); 147 } else if (auto writeOp = dyn_cast<AffineWriteOpInterface>(op)) { 148 // Filter out stores the same way as above. 149 if (!isLocallyDefined(writeOp.getMemRef(), forOp)) 150 loadAndStoreOps.push_back(op); 151 } else if (!isa<AffineForOp, AffineYieldOp, AffineIfOp>(op) && 152 !hasSingleEffect<MemoryEffects::Allocate>(op) && 153 !MemoryEffectOpInterface::hasNoEffect(op)) { 154 // Alloc-like ops inside `forOp` are fine (they don't impact parallelism) 155 // as long as they don't escape the loop (which has been checked above). 156 return WalkResult::interrupt(); 157 } 158 159 return WalkResult::advance(); 160 }); 161 162 // Stop early if the loop has unknown ops with side effects. 163 if (walkResult.wasInterrupted()) 164 return false; 165 166 // Dep check depth would be number of enclosing loops + 1. 167 unsigned depth = getNestingDepth(forOp) + 1; 168 169 // Check dependences between all pairs of ops in 'loadAndStoreOps'. 170 for (auto *srcOp : loadAndStoreOps) { 171 MemRefAccess srcAccess(srcOp); 172 for (auto *dstOp : loadAndStoreOps) { 173 MemRefAccess dstAccess(dstOp); 174 FlatAffineValueConstraints dependenceConstraints; 175 DependenceResult result = checkMemrefAccessDependence( 176 srcAccess, dstAccess, depth, &dependenceConstraints, 177 /*dependenceComponents=*/nullptr); 178 if (result.value != DependenceResult::NoDependence) 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. 190 void mlir::getReachableAffineApplyOps( 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) 240 LogicalResult mlir::getIndexSet(MutableArrayRef<Operation *> ops, 241 FlatAffineValueConstraints *domain) { 242 SmallVector<Value, 4> indices; 243 SmallVector<AffineForOp, 8> forOps; 244 245 for (Operation *op : ops) { 246 assert((isa<AffineForOp, AffineIfOp>(op)) && 247 "ops should have either AffineForOp or AffineIfOp"); 248 if (AffineForOp forOp = dyn_cast<AffineForOp>(op)) 249 forOps.push_back(forOp); 250 } 251 extractForInductionVars(forOps, &indices); 252 // Reset while associated Values in 'indices' to the domain. 253 domain->reset(forOps.size(), /*numSymbols=*/0, /*numLocals=*/0, indices); 254 for (Operation *op : ops) { 255 // Add constraints from forOp's bounds. 256 if (AffineForOp forOp = dyn_cast<AffineForOp>(op)) { 257 if (failed(domain->addAffineForOpDomain(forOp))) 258 return failure(); 259 } else if (AffineIfOp ifOp = dyn_cast<AffineIfOp>(op)) { 260 domain->addAffineIfOpDomain(ifOp); 261 } 262 } 263 return success(); 264 } 265 266 /// Computes the iteration domain for 'op' and populates 'indexSet', which 267 /// encapsulates the constraints involving loops surrounding 'op' and 268 /// potentially involving any Function symbols. The dimensional variables in 269 /// 'indexSet' correspond to the loops surrounding 'op' from outermost to 270 /// innermost. 271 static LogicalResult getOpIndexSet(Operation *op, 272 FlatAffineValueConstraints *indexSet) { 273 SmallVector<Operation *, 4> ops; 274 getEnclosingAffineForAndIfOps(*op, &ops); 275 return getIndexSet(ops, indexSet); 276 } 277 278 // Returns the number of outer loop common to 'src/dstDomain'. 279 // Loops common to 'src/dst' domains are added to 'commonLoops' if non-null. 280 static unsigned 281 getNumCommonLoops(const FlatAffineValueConstraints &srcDomain, 282 const FlatAffineValueConstraints &dstDomain, 283 SmallVectorImpl<AffineForOp> *commonLoops = nullptr) { 284 // Find the number of common loops shared by src and dst accesses. 285 unsigned minNumLoops = 286 std::min(srcDomain.getNumDimVars(), dstDomain.getNumDimVars()); 287 unsigned numCommonLoops = 0; 288 for (unsigned i = 0; i < minNumLoops; ++i) { 289 if (!isForInductionVar(srcDomain.getValue(i)) || 290 !isForInductionVar(dstDomain.getValue(i)) || 291 srcDomain.getValue(i) != dstDomain.getValue(i)) 292 break; 293 if (commonLoops != nullptr) 294 commonLoops->push_back(getForInductionVarOwner(srcDomain.getValue(i))); 295 ++numCommonLoops; 296 } 297 if (commonLoops != nullptr) 298 assert(commonLoops->size() == numCommonLoops); 299 return numCommonLoops; 300 } 301 302 /// Returns Block common to 'srcAccess.opInst' and 'dstAccess.opInst'. 303 static Block *getCommonBlock(const MemRefAccess &srcAccess, 304 const MemRefAccess &dstAccess, 305 const FlatAffineValueConstraints &srcDomain, 306 unsigned numCommonLoops) { 307 // Get the chain of ancestor blocks to the given `MemRefAccess` instance. The 308 // search terminates when either an op with the `AffineScope` trait or 309 // `endBlock` is reached. 310 auto getChainOfAncestorBlocks = [&](const MemRefAccess &access, 311 SmallVector<Block *, 4> &ancestorBlocks, 312 Block *endBlock = nullptr) { 313 Block *currBlock = access.opInst->getBlock(); 314 // Loop terminates when the currBlock is nullptr or equals to the endBlock, 315 // or its parent operation holds an affine scope. 316 while (currBlock && currBlock != endBlock && 317 !currBlock->getParentOp()->hasTrait<OpTrait::AffineScope>()) { 318 ancestorBlocks.push_back(currBlock); 319 currBlock = currBlock->getParentOp()->getBlock(); 320 } 321 }; 322 323 if (numCommonLoops == 0) { 324 Block *block = srcAccess.opInst->getBlock(); 325 while (!llvm::isa<func::FuncOp>(block->getParentOp())) { 326 block = block->getParentOp()->getBlock(); 327 } 328 return block; 329 } 330 Value commonForIV = srcDomain.getValue(numCommonLoops - 1); 331 AffineForOp forOp = getForInductionVarOwner(commonForIV); 332 assert(forOp && "commonForValue was not an induction variable"); 333 334 // Find the closest common block including those in AffineIf. 335 SmallVector<Block *, 4> srcAncestorBlocks, dstAncestorBlocks; 336 getChainOfAncestorBlocks(srcAccess, srcAncestorBlocks, forOp.getBody()); 337 getChainOfAncestorBlocks(dstAccess, dstAncestorBlocks, forOp.getBody()); 338 339 Block *commonBlock = forOp.getBody(); 340 for (int i = srcAncestorBlocks.size() - 1, j = dstAncestorBlocks.size() - 1; 341 i >= 0 && j >= 0 && srcAncestorBlocks[i] == dstAncestorBlocks[j]; 342 i--, j--) 343 commonBlock = srcAncestorBlocks[i]; 344 345 return commonBlock; 346 } 347 348 // Returns true if the ancestor operation of 'srcAccess' appears before the 349 // ancestor operation of 'dstAccess' in the common ancestral block. Returns 350 // false otherwise. 351 // Note that because 'srcAccess' or 'dstAccess' may be nested in conditionals, 352 // the function is named 'srcAppearsBeforeDstInCommonBlock'. Note that 353 // 'numCommonLoops' is the number of contiguous surrounding outer loops. 354 static bool srcAppearsBeforeDstInAncestralBlock( 355 const MemRefAccess &srcAccess, const MemRefAccess &dstAccess, 356 const FlatAffineValueConstraints &srcDomain, unsigned numCommonLoops) { 357 // Get Block common to 'srcAccess.opInst' and 'dstAccess.opInst'. 358 auto *commonBlock = 359 getCommonBlock(srcAccess, dstAccess, srcDomain, numCommonLoops); 360 // Check the dominance relationship between the respective ancestors of the 361 // src and dst in the Block of the innermost among the common loops. 362 auto *srcInst = commonBlock->findAncestorOpInBlock(*srcAccess.opInst); 363 assert(srcInst != nullptr); 364 auto *dstInst = commonBlock->findAncestorOpInBlock(*dstAccess.opInst); 365 assert(dstInst != nullptr); 366 367 // Determine whether dstInst comes after srcInst. 368 return srcInst->isBeforeInBlock(dstInst); 369 } 370 371 // Adds ordering constraints to 'dependenceDomain' based on number of loops 372 // common to 'src/dstDomain' and requested 'loopDepth'. 373 // Note that 'loopDepth' cannot exceed the number of common loops plus one. 374 // EX: Given a loop nest of depth 2 with IVs 'i' and 'j': 375 // *) If 'loopDepth == 1' then one constraint is added: i' >= i + 1 376 // *) If 'loopDepth == 2' then two constraints are added: i == i' and j' > j + 1 377 // *) If 'loopDepth == 3' then two constraints are added: i == i' and j == j' 378 static void 379 addOrderingConstraints(const FlatAffineValueConstraints &srcDomain, 380 const FlatAffineValueConstraints &dstDomain, 381 unsigned loopDepth, 382 FlatAffineValueConstraints *dependenceDomain) { 383 unsigned numCols = dependenceDomain->getNumCols(); 384 SmallVector<int64_t, 4> eq(numCols); 385 unsigned numSrcDims = srcDomain.getNumDimVars(); 386 unsigned numCommonLoops = getNumCommonLoops(srcDomain, dstDomain); 387 unsigned numCommonLoopConstraints = std::min(numCommonLoops, loopDepth); 388 for (unsigned i = 0; i < numCommonLoopConstraints; ++i) { 389 std::fill(eq.begin(), eq.end(), 0); 390 eq[i] = -1; 391 eq[i + numSrcDims] = 1; 392 if (i == loopDepth - 1) { 393 eq[numCols - 1] = -1; 394 dependenceDomain->addInequality(eq); 395 } else { 396 dependenceDomain->addEquality(eq); 397 } 398 } 399 } 400 401 // Computes distance and direction vectors in 'dependences', by adding 402 // variables to 'dependenceDomain' which represent the difference of the IVs, 403 // eliminating all other variables, and reading off distance vectors from 404 // equality constraints (if possible), and direction vectors from inequalities. 405 static void computeDirectionVector( 406 const FlatAffineValueConstraints &srcDomain, 407 const FlatAffineValueConstraints &dstDomain, unsigned loopDepth, 408 FlatAffineValueConstraints *dependenceDomain, 409 SmallVector<DependenceComponent, 2> *dependenceComponents) { 410 // Find the number of common loops shared by src and dst accesses. 411 SmallVector<AffineForOp, 4> commonLoops; 412 unsigned numCommonLoops = 413 getNumCommonLoops(srcDomain, dstDomain, &commonLoops); 414 if (numCommonLoops == 0) 415 return; 416 // Compute direction vectors for requested loop depth. 417 unsigned numIdsToEliminate = dependenceDomain->getNumVars(); 418 // Add new variables to 'dependenceDomain' to represent the direction 419 // constraints for each shared loop. 420 dependenceDomain->insertDimVar(/*pos=*/0, /*num=*/numCommonLoops); 421 422 // Add equality constraints for each common loop, setting newly introduced 423 // variable at column 'j' to the 'dst' IV minus the 'src IV. 424 SmallVector<int64_t, 4> eq; 425 eq.resize(dependenceDomain->getNumCols()); 426 unsigned numSrcDims = srcDomain.getNumDimVars(); 427 // Constraint variables format: 428 // [num-common-loops][num-src-dim-ids][num-dst-dim-ids][num-symbols][constant] 429 for (unsigned j = 0; j < numCommonLoops; ++j) { 430 std::fill(eq.begin(), eq.end(), 0); 431 eq[j] = 1; 432 eq[j + numCommonLoops] = 1; 433 eq[j + numCommonLoops + numSrcDims] = -1; 434 dependenceDomain->addEquality(eq); 435 } 436 437 // Eliminate all variables other than the direction variables just added. 438 dependenceDomain->projectOut(numCommonLoops, numIdsToEliminate); 439 440 // Scan each common loop variable column and set direction vectors based 441 // on eliminated constraint system. 442 dependenceComponents->resize(numCommonLoops); 443 for (unsigned j = 0; j < numCommonLoops; ++j) { 444 (*dependenceComponents)[j].op = commonLoops[j].getOperation(); 445 auto lbConst = dependenceDomain->getConstantBound(IntegerPolyhedron::LB, j); 446 (*dependenceComponents)[j].lb = 447 lbConst.value_or(std::numeric_limits<int64_t>::min()); 448 auto ubConst = dependenceDomain->getConstantBound(IntegerPolyhedron::UB, j); 449 (*dependenceComponents)[j].ub = 450 ubConst.value_or(std::numeric_limits<int64_t>::max()); 451 } 452 } 453 454 LogicalResult MemRefAccess::getAccessRelation(FlatAffineRelation &rel) const { 455 // Create set corresponding to domain of access. 456 FlatAffineValueConstraints domain; 457 if (failed(getOpIndexSet(opInst, &domain))) 458 return failure(); 459 460 // Get access relation from access map. 461 AffineValueMap accessValueMap; 462 getAccessMap(&accessValueMap); 463 if (failed(getRelationFromMap(accessValueMap, rel))) 464 return failure(); 465 466 FlatAffineRelation domainRel(rel.getNumDomainDims(), /*numRangeDims=*/0, 467 domain); 468 469 // Merge and align domain ids of `ret` and ids of `domain`. Since the domain 470 // of the access map is a subset of the domain of access, the domain ids of 471 // `ret` are guranteed to be a subset of ids of `domain`. 472 for (unsigned i = 0, e = domain.getNumDimVars(); i < e; ++i) { 473 unsigned loc; 474 if (rel.findVar(domain.getValue(i), &loc)) { 475 rel.swapVar(i, loc); 476 } else { 477 rel.insertDomainVar(i); 478 rel.setValue(i, domain.getValue(i)); 479 } 480 } 481 482 // Append domain constraints to `rel`. 483 domainRel.appendRangeVar(rel.getNumRangeDims()); 484 domainRel.mergeSymbolVars(rel); 485 domainRel.mergeLocalVars(rel); 486 rel.append(domainRel); 487 488 return success(); 489 } 490 491 // Populates 'accessMap' with composition of AffineApplyOps reachable from 492 // indices of MemRefAccess. 493 void MemRefAccess::getAccessMap(AffineValueMap *accessMap) const { 494 // Get affine map from AffineLoad/Store. 495 AffineMap map; 496 if (auto loadOp = dyn_cast<AffineReadOpInterface>(opInst)) 497 map = loadOp.getAffineMap(); 498 else 499 map = cast<AffineWriteOpInterface>(opInst).getAffineMap(); 500 501 SmallVector<Value, 8> operands(indices.begin(), indices.end()); 502 fullyComposeAffineMapAndOperands(&map, &operands); 503 map = simplifyAffineMap(map); 504 canonicalizeMapAndOperands(&map, &operands); 505 accessMap->reset(map, operands); 506 } 507 508 // Builds a flat affine constraint system to check if there exists a dependence 509 // between memref accesses 'srcAccess' and 'dstAccess'. 510 // Returns 'NoDependence' if the accesses can be definitively shown not to 511 // access the same element. 512 // Returns 'HasDependence' if the accesses do access the same element. 513 // Returns 'Failure' if an error or unsupported case was encountered. 514 // If a dependence exists, returns in 'dependenceComponents' a direction 515 // vector for the dependence, with a component for each loop IV in loops 516 // common to both accesses (see Dependence in AffineAnalysis.h for details). 517 // 518 // The memref access dependence check is comprised of the following steps: 519 // *) Build access relation for each access. An access relation maps elements 520 // of an iteration domain to the element(s) of an array domain accessed by 521 // that iteration of the associated statement through some array reference. 522 // *) Compute the dependence relation by composing access relation of 523 // `srcAccess` with the inverse of access relation of `dstAccess`. 524 // Doing this builds a relation between iteration domain of `srcAccess` 525 // to the iteration domain of `dstAccess` which access the same memory 526 // location. 527 // *) Add ordering constraints for `srcAccess` to be accessed before 528 // `dstAccess`. 529 // 530 // This method builds a constraint system with the following column format: 531 // 532 // [src-dim-variables, dst-dim-variables, symbols, constant] 533 // 534 // For example, given the following MLIR code with "source" and "destination" 535 // accesses to the same memref label, and symbols %M, %N, %K: 536 // 537 // affine.for %i0 = 0 to 100 { 538 // affine.for %i1 = 0 to 50 { 539 // %a0 = affine.apply 540 // (d0, d1) -> (d0 * 2 - d1 * 4 + s1, d1 * 3 - s0) (%i0, %i1)[%M, %N] 541 // // Source memref access. 542 // store %v0, %m[%a0#0, %a0#1] : memref<4x4xf32> 543 // } 544 // } 545 // 546 // affine.for %i2 = 0 to 100 { 547 // affine.for %i3 = 0 to 50 { 548 // %a1 = affine.apply 549 // (d0, d1) -> (d0 * 7 + d1 * 9 - s1, d1 * 11 + s0) (%i2, %i3)[%K, %M] 550 // // Destination memref access. 551 // %v1 = load %m[%a1#0, %a1#1] : memref<4x4xf32> 552 // } 553 // } 554 // 555 // The access relation for `srcAccess` would be the following: 556 // 557 // [src_dim0, src_dim1, mem_dim0, mem_dim1, %N, %M, const] 558 // 2 -4 -1 0 1 0 0 = 0 559 // 0 3 0 -1 0 -1 0 = 0 560 // 1 0 0 0 0 0 0 >= 0 561 // -1 0 0 0 0 0 100 >= 0 562 // 0 1 0 0 0 0 0 >= 0 563 // 0 -1 0 0 0 0 50 >= 0 564 // 565 // The access relation for `dstAccess` would be the following: 566 // 567 // [dst_dim0, dst_dim1, mem_dim0, mem_dim1, %M, %K, const] 568 // 7 9 -1 0 -1 0 0 = 0 569 // 0 11 0 -1 0 -1 0 = 0 570 // 1 0 0 0 0 0 0 >= 0 571 // -1 0 0 0 0 0 100 >= 0 572 // 0 1 0 0 0 0 0 >= 0 573 // 0 -1 0 0 0 0 50 >= 0 574 // 575 // The equalities in the above relations correspond to the access maps while 576 // the inequalities corresspond to the iteration domain constraints. 577 // 578 // The dependence relation formed: 579 // 580 // [src_dim0, src_dim1, dst_dim0, dst_dim1, %M, %N, %K, const] 581 // 2 -4 -7 -9 1 1 0 0 = 0 582 // 0 3 0 -11 -1 0 1 0 = 0 583 // 1 0 0 0 0 0 0 0 >= 0 584 // -1 0 0 0 0 0 0 100 >= 0 585 // 0 1 0 0 0 0 0 0 >= 0 586 // 0 -1 0 0 0 0 0 50 >= 0 587 // 0 0 1 0 0 0 0 0 >= 0 588 // 0 0 -1 0 0 0 0 100 >= 0 589 // 0 0 0 1 0 0 0 0 >= 0 590 // 0 0 0 -1 0 0 0 50 >= 0 591 // 592 // 593 // TODO: Support AffineExprs mod/floordiv/ceildiv. 594 DependenceResult mlir::checkMemrefAccessDependence( 595 const MemRefAccess &srcAccess, const MemRefAccess &dstAccess, 596 unsigned loopDepth, FlatAffineValueConstraints *dependenceConstraints, 597 SmallVector<DependenceComponent, 2> *dependenceComponents, bool allowRAR) { 598 LLVM_DEBUG(llvm::dbgs() << "Checking for dependence at depth: " 599 << Twine(loopDepth) << " between:\n";); 600 LLVM_DEBUG(srcAccess.opInst->dump();); 601 LLVM_DEBUG(dstAccess.opInst->dump();); 602 603 // Return 'NoDependence' if these accesses do not access the same memref. 604 if (srcAccess.memref != dstAccess.memref) 605 return DependenceResult::NoDependence; 606 607 // Return 'NoDependence' if one of these accesses is not an 608 // AffineWriteOpInterface. 609 if (!allowRAR && !isa<AffineWriteOpInterface>(srcAccess.opInst) && 610 !isa<AffineWriteOpInterface>(dstAccess.opInst)) 611 return DependenceResult::NoDependence; 612 613 // Create access relation from each MemRefAccess. 614 FlatAffineRelation srcRel, dstRel; 615 if (failed(srcAccess.getAccessRelation(srcRel))) 616 return DependenceResult::Failure; 617 if (failed(dstAccess.getAccessRelation(dstRel))) 618 return DependenceResult::Failure; 619 620 FlatAffineValueConstraints srcDomain = srcRel.getDomainSet(); 621 FlatAffineValueConstraints dstDomain = dstRel.getDomainSet(); 622 623 // Return 'NoDependence' if loopDepth > numCommonLoops and if the ancestor 624 // operation of 'srcAccess' does not properly dominate the ancestor 625 // operation of 'dstAccess' in the same common operation block. 626 // Note: this check is skipped if 'allowRAR' is true, because because RAR 627 // deps can exist irrespective of lexicographic ordering b/w src and dst. 628 unsigned numCommonLoops = getNumCommonLoops(srcDomain, dstDomain); 629 assert(loopDepth <= numCommonLoops + 1); 630 if (!allowRAR && loopDepth > numCommonLoops && 631 !srcAppearsBeforeDstInAncestralBlock(srcAccess, dstAccess, srcDomain, 632 numCommonLoops)) { 633 return DependenceResult::NoDependence; 634 } 635 636 // Compute the dependence relation by composing `srcRel` with the inverse of 637 // `dstRel`. Doing this builds a relation between iteration domain of 638 // `srcAccess` to the iteration domain of `dstAccess` which access the same 639 // memory locations. 640 dstRel.inverse(); 641 dstRel.compose(srcRel); 642 *dependenceConstraints = dstRel; 643 644 // Add 'src' happens before 'dst' ordering constraints. 645 addOrderingConstraints(srcDomain, dstDomain, loopDepth, 646 dependenceConstraints); 647 648 // Return 'NoDependence' if the solution space is empty: no dependence. 649 if (dependenceConstraints->isEmpty()) 650 return DependenceResult::NoDependence; 651 652 // Compute dependence direction vector and return true. 653 if (dependenceComponents != nullptr) 654 computeDirectionVector(srcDomain, dstDomain, loopDepth, 655 dependenceConstraints, dependenceComponents); 656 657 LLVM_DEBUG(llvm::dbgs() << "Dependence polyhedron:\n"); 658 LLVM_DEBUG(dependenceConstraints->dump()); 659 return DependenceResult::HasDependence; 660 } 661 662 /// Gathers dependence components for dependences between all ops in loop nest 663 /// rooted at 'forOp' at loop depths in range [1, maxLoopDepth]. 664 void mlir::getDependenceComponents( 665 AffineForOp forOp, unsigned maxLoopDepth, 666 std::vector<SmallVector<DependenceComponent, 2>> *depCompsVec) { 667 // Collect all load and store ops in loop nest rooted at 'forOp'. 668 SmallVector<Operation *, 8> loadAndStoreOps; 669 forOp->walk([&](Operation *op) { 670 if (isa<AffineReadOpInterface, AffineWriteOpInterface>(op)) 671 loadAndStoreOps.push_back(op); 672 }); 673 674 unsigned numOps = loadAndStoreOps.size(); 675 for (unsigned d = 1; d <= maxLoopDepth; ++d) { 676 for (unsigned i = 0; i < numOps; ++i) { 677 auto *srcOp = loadAndStoreOps[i]; 678 MemRefAccess srcAccess(srcOp); 679 for (unsigned j = 0; j < numOps; ++j) { 680 auto *dstOp = loadAndStoreOps[j]; 681 MemRefAccess dstAccess(dstOp); 682 683 FlatAffineValueConstraints dependenceConstraints; 684 SmallVector<DependenceComponent, 2> depComps; 685 // TODO: Explore whether it would be profitable to pre-compute and store 686 // deps instead of repeatedly checking. 687 DependenceResult result = checkMemrefAccessDependence( 688 srcAccess, dstAccess, d, &dependenceConstraints, &depComps); 689 if (hasDependence(result)) 690 depCompsVec->push_back(depComps); 691 } 692 } 693 } 694 } 695