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/IR/AffineExprVisitor.h" 21 #include "mlir/IR/BuiltinOps.h" 22 #include "mlir/IR/IntegerSet.h" 23 #include "mlir/Interfaces/ViewLikeInterface.h" 24 #include "llvm/ADT/TypeSwitch.h" 25 #include "llvm/Support/Debug.h" 26 #include "llvm/Support/raw_ostream.h" 27 28 #define DEBUG_TYPE "affine-analysis" 29 30 using namespace mlir; 31 32 /// Get the value that is being reduced by `pos`-th reduction in the loop if 33 /// such a reduction can be performed by affine parallel loops. This assumes 34 /// floating-point operations are commutative. On success, `kind` will be the 35 /// reduction kind suitable for use in affine parallel loop builder. If the 36 /// reduction is not supported, returns null. 37 static Value getSupportedReduction(AffineForOp forOp, unsigned pos, 38 arith::AtomicRMWKind &kind) { 39 SmallVector<Operation *> combinerOps; 40 Value reducedVal = 41 matchReduction(forOp.getRegionIterArgs(), pos, combinerOps); 42 if (!reducedVal) 43 return nullptr; 44 45 // Expected only one combiner operation. 46 if (combinerOps.size() > 1) 47 return nullptr; 48 49 Operation *combinerOp = combinerOps.back(); 50 Optional<arith::AtomicRMWKind> maybeKind = 51 TypeSwitch<Operation *, Optional<arith::AtomicRMWKind>>(combinerOp) 52 .Case([](arith::AddFOp) { return arith::AtomicRMWKind::addf; }) 53 .Case([](arith::MulFOp) { return arith::AtomicRMWKind::mulf; }) 54 .Case([](arith::AddIOp) { return arith::AtomicRMWKind::addi; }) 55 .Case([](arith::AndIOp) { return arith::AtomicRMWKind::andi; }) 56 .Case([](arith::OrIOp) { return arith::AtomicRMWKind::ori; }) 57 .Case([](arith::MulIOp) { return arith::AtomicRMWKind::muli; }) 58 .Case([](arith::MinFOp) { return arith::AtomicRMWKind::minf; }) 59 .Case([](arith::MaxFOp) { return arith::AtomicRMWKind::maxf; }) 60 .Case([](arith::MinSIOp) { return arith::AtomicRMWKind::mins; }) 61 .Case([](arith::MaxSIOp) { return arith::AtomicRMWKind::maxs; }) 62 .Case([](arith::MinUIOp) { return arith::AtomicRMWKind::minu; }) 63 .Case([](arith::MaxUIOp) { return arith::AtomicRMWKind::maxu; }) 64 .Default([](Operation *) -> Optional<arith::AtomicRMWKind> { 65 // TODO: AtomicRMW supports other kinds of reductions this is 66 // currently not detecting, add those when the need arises. 67 return llvm::None; 68 }); 69 if (!maybeKind) 70 return nullptr; 71 72 kind = *maybeKind; 73 return reducedVal; 74 } 75 76 /// Populate `supportedReductions` with descriptors of the supported reductions. 77 void mlir::getSupportedReductions( 78 AffineForOp forOp, SmallVectorImpl<LoopReduction> &supportedReductions) { 79 unsigned numIterArgs = forOp.getNumIterOperands(); 80 if (numIterArgs == 0) 81 return; 82 supportedReductions.reserve(numIterArgs); 83 for (unsigned i = 0; i < numIterArgs; ++i) { 84 arith::AtomicRMWKind kind; 85 if (Value value = getSupportedReduction(forOp, i, kind)) 86 supportedReductions.emplace_back(LoopReduction{kind, i, value}); 87 } 88 } 89 90 /// Returns true if `forOp' is a parallel loop. If `parallelReductions` is 91 /// provided, populates it with descriptors of the parallelizable reductions and 92 /// treats them as not preventing parallelization. 93 bool mlir::isLoopParallel(AffineForOp forOp, 94 SmallVectorImpl<LoopReduction> *parallelReductions) { 95 unsigned numIterArgs = forOp.getNumIterOperands(); 96 97 // Loop is not parallel if it has SSA loop-carried dependences and reduction 98 // detection is not requested. 99 if (numIterArgs > 0 && !parallelReductions) 100 return false; 101 102 // Find supported reductions of requested. 103 if (parallelReductions) { 104 getSupportedReductions(forOp, *parallelReductions); 105 // Return later to allow for identifying all parallel reductions even if the 106 // loop is not parallel. 107 if (parallelReductions->size() != numIterArgs) 108 return false; 109 } 110 111 // Check memory dependences. 112 return isLoopMemoryParallel(forOp); 113 } 114 115 /// Returns true if `op` is an alloc-like op, i.e., one allocating memrefs. 116 static bool isAllocLikeOp(Operation *op) { 117 auto memEffects = dyn_cast<MemoryEffectOpInterface>(op); 118 return memEffects && memEffects.hasEffect<MemoryEffects::Allocate>(); 119 } 120 121 /// Returns true if `v` is allocated locally to `enclosingOp` -- i.e., it is 122 /// allocated by an operation nested within `enclosingOp`. 123 static bool isLocallyDefined(Value v, Operation *enclosingOp) { 124 Operation *defOp = v.getDefiningOp(); 125 if (!defOp) 126 return false; 127 128 if (isAllocLikeOp(defOp) && enclosingOp->isProperAncestor(defOp)) 129 return true; 130 131 // Aliasing ops. 132 auto viewOp = dyn_cast<ViewLikeOpInterface>(defOp); 133 return viewOp && isLocallyDefined(viewOp.getViewSource(), enclosingOp); 134 } 135 136 bool mlir::isLoopMemoryParallel(AffineForOp forOp) { 137 // Any memref-typed iteration arguments are treated as serializing. 138 if (llvm::any_of(forOp.getResultTypes(), 139 [](Type type) { return type.isa<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) && 154 !isAllocLikeOp(op) && 155 !MemoryEffectOpInterface::hasNoEffect(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); 176 FlatAffineValueConstraints dependenceConstraints; 177 DependenceResult result = checkMemrefAccessDependence( 178 srcAccess, dstAccess, depth, &dependenceConstraints, 179 /*dependenceComponents=*/nullptr); 180 if (result.value != DependenceResult::NoDependence) 181 return false; 182 } 183 } 184 return true; 185 } 186 187 /// Returns the sequence of AffineApplyOp Operations operation in 188 /// 'affineApplyOps', which are reachable via a search starting from 'operands', 189 /// and ending at operands which are not defined by AffineApplyOps. 190 // TODO: Add a method to AffineApplyOp which forward substitutes the 191 // AffineApplyOp into any user AffineApplyOps. 192 void mlir::getReachableAffineApplyOps( 193 ArrayRef<Value> operands, SmallVectorImpl<Operation *> &affineApplyOps) { 194 struct State { 195 // The ssa value for this node in the DFS traversal. 196 Value value; 197 // The operand index of 'value' to explore next during DFS traversal. 198 unsigned operandIndex; 199 }; 200 SmallVector<State, 4> worklist; 201 for (auto operand : operands) { 202 worklist.push_back({operand, 0}); 203 } 204 205 while (!worklist.empty()) { 206 State &state = worklist.back(); 207 auto *opInst = state.value.getDefiningOp(); 208 // Note: getDefiningOp will return nullptr if the operand is not an 209 // Operation (i.e. block argument), which is a terminator for the search. 210 if (!isa_and_nonnull<AffineApplyOp>(opInst)) { 211 worklist.pop_back(); 212 continue; 213 } 214 215 if (state.operandIndex == 0) { 216 // Pre-Visit: Add 'opInst' to reachable sequence. 217 affineApplyOps.push_back(opInst); 218 } 219 if (state.operandIndex < opInst->getNumOperands()) { 220 // Visit: Add next 'affineApplyOp' operand to worklist. 221 // Get next operand to visit at 'operandIndex'. 222 auto nextOperand = opInst->getOperand(state.operandIndex); 223 // Increment 'operandIndex' in 'state'. 224 ++state.operandIndex; 225 // Add 'nextOperand' to worklist. 226 worklist.push_back({nextOperand, 0}); 227 } else { 228 // Post-visit: done visiting operands AffineApplyOp, pop off stack. 229 worklist.pop_back(); 230 } 231 } 232 } 233 234 // Builds a system of constraints with dimensional identifiers corresponding to 235 // the loop IVs of the forOps appearing in that order. Any symbols founds in 236 // the bound operands are added as symbols in the system. Returns failure for 237 // the yet unimplemented cases. 238 // TODO: Handle non-unit steps through local variables or stride information in 239 // FlatAffineValueConstraints. (For eg., by using iv - lb % step = 0 and/or by 240 // introducing a method in FlatAffineValueConstraints 241 // setExprStride(ArrayRef<int64_t> expr, int64_t stride) 242 LogicalResult mlir::getIndexSet(MutableArrayRef<Operation *> ops, 243 FlatAffineValueConstraints *domain) { 244 SmallVector<Value, 4> indices; 245 SmallVector<AffineForOp, 8> forOps; 246 247 for (Operation *op : ops) { 248 assert((isa<AffineForOp, AffineIfOp>(op)) && 249 "ops should have either AffineForOp or AffineIfOp"); 250 if (AffineForOp forOp = dyn_cast<AffineForOp>(op)) 251 forOps.push_back(forOp); 252 } 253 extractForInductionVars(forOps, &indices); 254 // Reset while associated Values in 'indices' to the domain. 255 domain->reset(forOps.size(), /*numSymbols=*/0, /*numLocals=*/0, indices); 256 for (Operation *op : ops) { 257 // Add constraints from forOp's bounds. 258 if (AffineForOp forOp = dyn_cast<AffineForOp>(op)) { 259 if (failed(domain->addAffineForOpDomain(forOp))) 260 return failure(); 261 } else if (AffineIfOp ifOp = dyn_cast<AffineIfOp>(op)) { 262 domain->addAffineIfOpDomain(ifOp); 263 } 264 } 265 return success(); 266 } 267 268 /// Computes the iteration domain for 'op' and populates 'indexSet', which 269 /// encapsulates the constraints involving loops surrounding 'op' and 270 /// potentially involving any Function symbols. The dimensional identifiers in 271 /// 'indexSet' correspond to the loops surrounding 'op' from outermost to 272 /// innermost. 273 static LogicalResult getOpIndexSet(Operation *op, 274 FlatAffineValueConstraints *indexSet) { 275 SmallVector<Operation *, 4> ops; 276 getEnclosingAffineForAndIfOps(*op, &ops); 277 return getIndexSet(ops, indexSet); 278 } 279 280 // Returns the number of outer loop common to 'src/dstDomain'. 281 // Loops common to 'src/dst' domains are added to 'commonLoops' if non-null. 282 static unsigned 283 getNumCommonLoops(const FlatAffineValueConstraints &srcDomain, 284 const FlatAffineValueConstraints &dstDomain, 285 SmallVectorImpl<AffineForOp> *commonLoops = nullptr) { 286 // Find the number of common loops shared by src and dst accesses. 287 unsigned minNumLoops = 288 std::min(srcDomain.getNumDimIds(), dstDomain.getNumDimIds()); 289 unsigned numCommonLoops = 0; 290 for (unsigned i = 0; i < minNumLoops; ++i) { 291 if (!isForInductionVar(srcDomain.getValue(i)) || 292 !isForInductionVar(dstDomain.getValue(i)) || 293 srcDomain.getValue(i) != dstDomain.getValue(i)) 294 break; 295 if (commonLoops != nullptr) 296 commonLoops->push_back(getForInductionVarOwner(srcDomain.getValue(i))); 297 ++numCommonLoops; 298 } 299 if (commonLoops != nullptr) 300 assert(commonLoops->size() == numCommonLoops); 301 return numCommonLoops; 302 } 303 304 /// Returns Block common to 'srcAccess.opInst' and 'dstAccess.opInst'. 305 static Block *getCommonBlock(const MemRefAccess &srcAccess, 306 const MemRefAccess &dstAccess, 307 const FlatAffineValueConstraints &srcDomain, 308 unsigned numCommonLoops) { 309 // Get the chain of ancestor blocks to the given `MemRefAccess` instance. The 310 // search terminates when either an op with the `AffineScope` trait or 311 // `endBlock` is reached. 312 auto getChainOfAncestorBlocks = [&](const MemRefAccess &access, 313 SmallVector<Block *, 4> &ancestorBlocks, 314 Block *endBlock = nullptr) { 315 Block *currBlock = access.opInst->getBlock(); 316 // Loop terminates when the currBlock is nullptr or equals to the endBlock, 317 // or its parent operation holds an affine scope. 318 while (currBlock && currBlock != endBlock && 319 !currBlock->getParentOp()->hasTrait<OpTrait::AffineScope>()) { 320 ancestorBlocks.push_back(currBlock); 321 currBlock = currBlock->getParentOp()->getBlock(); 322 } 323 }; 324 325 if (numCommonLoops == 0) { 326 Block *block = srcAccess.opInst->getBlock(); 327 while (!llvm::isa<FuncOp>(block->getParentOp())) { 328 block = block->getParentOp()->getBlock(); 329 } 330 return block; 331 } 332 Value commonForIV = srcDomain.getValue(numCommonLoops - 1); 333 AffineForOp forOp = getForInductionVarOwner(commonForIV); 334 assert(forOp && "commonForValue was not an induction variable"); 335 336 // Find the closest common block including those in AffineIf. 337 SmallVector<Block *, 4> srcAncestorBlocks, dstAncestorBlocks; 338 getChainOfAncestorBlocks(srcAccess, srcAncestorBlocks, forOp.getBody()); 339 getChainOfAncestorBlocks(dstAccess, dstAncestorBlocks, forOp.getBody()); 340 341 Block *commonBlock = forOp.getBody(); 342 for (int i = srcAncestorBlocks.size() - 1, j = dstAncestorBlocks.size() - 1; 343 i >= 0 && j >= 0 && srcAncestorBlocks[i] == dstAncestorBlocks[j]; 344 i--, j--) 345 commonBlock = srcAncestorBlocks[i]; 346 347 return commonBlock; 348 } 349 350 // Returns true if the ancestor operation of 'srcAccess' appears before the 351 // ancestor operation of 'dstAccess' in the common ancestral block. Returns 352 // false otherwise. 353 // Note that because 'srcAccess' or 'dstAccess' may be nested in conditionals, 354 // the function is named 'srcAppearsBeforeDstInCommonBlock'. Note that 355 // 'numCommonLoops' is the number of contiguous surrounding outer loops. 356 static bool srcAppearsBeforeDstInAncestralBlock( 357 const MemRefAccess &srcAccess, const MemRefAccess &dstAccess, 358 const FlatAffineValueConstraints &srcDomain, unsigned numCommonLoops) { 359 // Get Block common to 'srcAccess.opInst' and 'dstAccess.opInst'. 360 auto *commonBlock = 361 getCommonBlock(srcAccess, dstAccess, srcDomain, numCommonLoops); 362 // Check the dominance relationship between the respective ancestors of the 363 // src and dst in the Block of the innermost among the common loops. 364 auto *srcInst = commonBlock->findAncestorOpInBlock(*srcAccess.opInst); 365 assert(srcInst != nullptr); 366 auto *dstInst = commonBlock->findAncestorOpInBlock(*dstAccess.opInst); 367 assert(dstInst != nullptr); 368 369 // Determine whether dstInst comes after srcInst. 370 return srcInst->isBeforeInBlock(dstInst); 371 } 372 373 // Adds ordering constraints to 'dependenceDomain' based on number of loops 374 // common to 'src/dstDomain' and requested 'loopDepth'. 375 // Note that 'loopDepth' cannot exceed the number of common loops plus one. 376 // EX: Given a loop nest of depth 2 with IVs 'i' and 'j': 377 // *) If 'loopDepth == 1' then one constraint is added: i' >= i + 1 378 // *) If 'loopDepth == 2' then two constraints are added: i == i' and j' > j + 1 379 // *) If 'loopDepth == 3' then two constraints are added: i == i' and j == j' 380 static void 381 addOrderingConstraints(const FlatAffineValueConstraints &srcDomain, 382 const FlatAffineValueConstraints &dstDomain, 383 unsigned loopDepth, 384 FlatAffineValueConstraints *dependenceDomain) { 385 unsigned numCols = dependenceDomain->getNumCols(); 386 SmallVector<int64_t, 4> eq(numCols); 387 unsigned numSrcDims = srcDomain.getNumDimIds(); 388 unsigned numCommonLoops = getNumCommonLoops(srcDomain, dstDomain); 389 unsigned numCommonLoopConstraints = std::min(numCommonLoops, loopDepth); 390 for (unsigned i = 0; i < numCommonLoopConstraints; ++i) { 391 std::fill(eq.begin(), eq.end(), 0); 392 eq[i] = -1; 393 eq[i + numSrcDims] = 1; 394 if (i == loopDepth - 1) { 395 eq[numCols - 1] = -1; 396 dependenceDomain->addInequality(eq); 397 } else { 398 dependenceDomain->addEquality(eq); 399 } 400 } 401 } 402 403 // Computes distance and direction vectors in 'dependences', by adding 404 // variables to 'dependenceDomain' which represent the difference of the IVs, 405 // eliminating all other variables, and reading off distance vectors from 406 // equality constraints (if possible), and direction vectors from inequalities. 407 static void computeDirectionVector( 408 const FlatAffineValueConstraints &srcDomain, 409 const FlatAffineValueConstraints &dstDomain, unsigned loopDepth, 410 FlatAffineValueConstraints *dependenceDomain, 411 SmallVector<DependenceComponent, 2> *dependenceComponents) { 412 // Find the number of common loops shared by src and dst accesses. 413 SmallVector<AffineForOp, 4> commonLoops; 414 unsigned numCommonLoops = 415 getNumCommonLoops(srcDomain, dstDomain, &commonLoops); 416 if (numCommonLoops == 0) 417 return; 418 // Compute direction vectors for requested loop depth. 419 unsigned numIdsToEliminate = dependenceDomain->getNumIds(); 420 // Add new variables to 'dependenceDomain' to represent the direction 421 // constraints for each shared loop. 422 dependenceDomain->insertDimId(/*pos=*/0, /*num=*/numCommonLoops); 423 424 // Add equality constraints for each common loop, setting newly introduced 425 // variable at column 'j' to the 'dst' IV minus the 'src IV. 426 SmallVector<int64_t, 4> eq; 427 eq.resize(dependenceDomain->getNumCols()); 428 unsigned numSrcDims = srcDomain.getNumDimIds(); 429 // Constraint variables format: 430 // [num-common-loops][num-src-dim-ids][num-dst-dim-ids][num-symbols][constant] 431 for (unsigned j = 0; j < numCommonLoops; ++j) { 432 std::fill(eq.begin(), eq.end(), 0); 433 eq[j] = 1; 434 eq[j + numCommonLoops] = 1; 435 eq[j + numCommonLoops + numSrcDims] = -1; 436 dependenceDomain->addEquality(eq); 437 } 438 439 // Eliminate all variables other than the direction variables just added. 440 dependenceDomain->projectOut(numCommonLoops, numIdsToEliminate); 441 442 // Scan each common loop variable column and set direction vectors based 443 // on eliminated constraint system. 444 dependenceComponents->resize(numCommonLoops); 445 for (unsigned j = 0; j < numCommonLoops; ++j) { 446 (*dependenceComponents)[j].op = commonLoops[j].getOperation(); 447 auto lbConst = 448 dependenceDomain->getConstantBound(FlatAffineConstraints::LB, j); 449 (*dependenceComponents)[j].lb = 450 lbConst.getValueOr(std::numeric_limits<int64_t>::min()); 451 auto ubConst = 452 dependenceDomain->getConstantBound(FlatAffineConstraints::UB, j); 453 (*dependenceComponents)[j].ub = 454 ubConst.getValueOr(std::numeric_limits<int64_t>::max()); 455 } 456 } 457 458 LogicalResult MemRefAccess::getAccessRelation(FlatAffineRelation &rel) const { 459 // Create set corresponding to domain of access. 460 FlatAffineValueConstraints domain; 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 FlatAffineRelation domainRel(rel.getNumDomainDims(), /*numRangeDims=*/0, 471 domain); 472 473 // Merge and align domain ids of `ret` and ids of `domain`. Since the domain 474 // of the access map is a subset of the domain of access, the domain ids of 475 // `ret` are guranteed to be a subset of ids of `domain`. 476 for (unsigned i = 0, e = domain.getNumDimIds(); i < e; ++i) { 477 unsigned loc; 478 if (rel.findId(domain.getValue(i), &loc)) { 479 rel.swapId(i, loc); 480 } else { 481 rel.insertDomainId(i); 482 rel.setValue(i, domain.getValue(i)); 483 } 484 } 485 486 // Append domain constraints to `rel`. 487 domainRel.appendRangeId(rel.getNumRangeDims()); 488 domainRel.mergeSymbolIds(rel); 489 domainRel.mergeLocalIds(rel); 490 rel.append(domainRel); 491 492 return success(); 493 } 494 495 // Populates 'accessMap' with composition of AffineApplyOps reachable from 496 // indices of MemRefAccess. 497 void MemRefAccess::getAccessMap(AffineValueMap *accessMap) const { 498 // Get affine map from AffineLoad/Store. 499 AffineMap map; 500 if (auto loadOp = dyn_cast<AffineReadOpInterface>(opInst)) 501 map = loadOp.getAffineMap(); 502 else 503 map = cast<AffineWriteOpInterface>(opInst).getAffineMap(); 504 505 SmallVector<Value, 8> operands(indices.begin(), indices.end()); 506 fullyComposeAffineMapAndOperands(&map, &operands); 507 map = simplifyAffineMap(map); 508 canonicalizeMapAndOperands(&map, &operands); 509 accessMap->reset(map, operands); 510 } 511 512 // Builds a flat affine constraint system to check if there exists a dependence 513 // between memref accesses 'srcAccess' and 'dstAccess'. 514 // Returns 'NoDependence' if the accesses can be definitively shown not to 515 // access the same element. 516 // Returns 'HasDependence' if the accesses do access the same element. 517 // Returns 'Failure' if an error or unsupported case was encountered. 518 // If a dependence exists, returns in 'dependenceComponents' a direction 519 // vector for the dependence, with a component for each loop IV in loops 520 // common to both accesses (see Dependence in AffineAnalysis.h for details). 521 // 522 // The memref access dependence check is comprised of the following steps: 523 // *) Build access relation for each access. An access relation maps elements 524 // of an iteration domain to the element(s) of an array domain accessed by 525 // that iteration of the associated statement through some array reference. 526 // *) Compute the dependence relation by composing access relation of 527 // `srcAccess` with the inverse of access relation of `dstAccess`. 528 // Doing this builds a relation between iteration domain of `srcAccess` 529 // to the iteration domain of `dstAccess` which access the same memory 530 // location. 531 // *) Add ordering constraints for `srcAccess` to be accessed before 532 // `dstAccess`. 533 // 534 // This method builds a constraint system with the following column format: 535 // 536 // [src-dim-identifiers, dst-dim-identifiers, symbols, constant] 537 // 538 // For example, given the following MLIR code with "source" and "destination" 539 // accesses to the same memref label, and symbols %M, %N, %K: 540 // 541 // affine.for %i0 = 0 to 100 { 542 // affine.for %i1 = 0 to 50 { 543 // %a0 = affine.apply 544 // (d0, d1) -> (d0 * 2 - d1 * 4 + s1, d1 * 3 - s0) (%i0, %i1)[%M, %N] 545 // // Source memref access. 546 // store %v0, %m[%a0#0, %a0#1] : memref<4x4xf32> 547 // } 548 // } 549 // 550 // affine.for %i2 = 0 to 100 { 551 // affine.for %i3 = 0 to 50 { 552 // %a1 = affine.apply 553 // (d0, d1) -> (d0 * 7 + d1 * 9 - s1, d1 * 11 + s0) (%i2, %i3)[%K, %M] 554 // // Destination memref access. 555 // %v1 = load %m[%a1#0, %a1#1] : memref<4x4xf32> 556 // } 557 // } 558 // 559 // The access relation for `srcAccess` would be the following: 560 // 561 // [src_dim0, src_dim1, mem_dim0, mem_dim1, %N, %M, const] 562 // 2 -4 -1 0 1 0 0 = 0 563 // 0 3 0 -1 0 -1 0 = 0 564 // 1 0 0 0 0 0 0 >= 0 565 // -1 0 0 0 0 0 100 >= 0 566 // 0 1 0 0 0 0 0 >= 0 567 // 0 -1 0 0 0 0 50 >= 0 568 // 569 // The access relation for `dstAccess` would be the following: 570 // 571 // [dst_dim0, dst_dim1, mem_dim0, mem_dim1, %M, %K, const] 572 // 7 9 -1 0 -1 0 0 = 0 573 // 0 11 0 -1 0 -1 0 = 0 574 // 1 0 0 0 0 0 0 >= 0 575 // -1 0 0 0 0 0 100 >= 0 576 // 0 1 0 0 0 0 0 >= 0 577 // 0 -1 0 0 0 0 50 >= 0 578 // 579 // The equalities in the above relations correspond to the access maps while 580 // the inequalities corresspond to the iteration domain constraints. 581 // 582 // The dependence relation formed: 583 // 584 // [src_dim0, src_dim1, dst_dim0, dst_dim1, %M, %N, %K, const] 585 // 2 -4 -7 -9 1 1 0 0 = 0 586 // 0 3 0 -11 -1 0 1 0 = 0 587 // 1 0 0 0 0 0 0 0 >= 0 588 // -1 0 0 0 0 0 0 100 >= 0 589 // 0 1 0 0 0 0 0 0 >= 0 590 // 0 -1 0 0 0 0 0 50 >= 0 591 // 0 0 1 0 0 0 0 0 >= 0 592 // 0 0 -1 0 0 0 0 100 >= 0 593 // 0 0 0 1 0 0 0 0 >= 0 594 // 0 0 0 -1 0 0 0 50 >= 0 595 // 596 // 597 // TODO: Support AffineExprs mod/floordiv/ceildiv. 598 DependenceResult mlir::checkMemrefAccessDependence( 599 const MemRefAccess &srcAccess, const MemRefAccess &dstAccess, 600 unsigned loopDepth, FlatAffineValueConstraints *dependenceConstraints, 601 SmallVector<DependenceComponent, 2> *dependenceComponents, bool allowRAR) { 602 LLVM_DEBUG(llvm::dbgs() << "Checking for dependence at depth: " 603 << Twine(loopDepth) << " between:\n";); 604 LLVM_DEBUG(srcAccess.opInst->dump();); 605 LLVM_DEBUG(dstAccess.opInst->dump();); 606 607 // Return 'NoDependence' if these accesses do not access the same memref. 608 if (srcAccess.memref != dstAccess.memref) 609 return DependenceResult::NoDependence; 610 611 // Return 'NoDependence' if one of these accesses is not an 612 // AffineWriteOpInterface. 613 if (!allowRAR && !isa<AffineWriteOpInterface>(srcAccess.opInst) && 614 !isa<AffineWriteOpInterface>(dstAccess.opInst)) 615 return DependenceResult::NoDependence; 616 617 // Create access relation from each MemRefAccess. 618 FlatAffineRelation srcRel, dstRel; 619 if (failed(srcAccess.getAccessRelation(srcRel))) 620 return DependenceResult::Failure; 621 if (failed(dstAccess.getAccessRelation(dstRel))) 622 return DependenceResult::Failure; 623 624 FlatAffineValueConstraints srcDomain = srcRel.getDomainSet(); 625 FlatAffineValueConstraints dstDomain = dstRel.getDomainSet(); 626 627 // Return 'NoDependence' if loopDepth > numCommonLoops and if the ancestor 628 // operation of 'srcAccess' does not properly dominate the ancestor 629 // operation of 'dstAccess' in the same common operation block. 630 // Note: this check is skipped if 'allowRAR' is true, because because RAR 631 // deps can exist irrespective of lexicographic ordering b/w src and dst. 632 unsigned numCommonLoops = getNumCommonLoops(srcDomain, dstDomain); 633 assert(loopDepth <= numCommonLoops + 1); 634 if (!allowRAR && loopDepth > numCommonLoops && 635 !srcAppearsBeforeDstInAncestralBlock(srcAccess, dstAccess, srcDomain, 636 numCommonLoops)) { 637 return DependenceResult::NoDependence; 638 } 639 640 // Compute the dependence relation by composing `srcRel` with the inverse of 641 // `dstRel`. Doing this builds a relation between iteration domain of 642 // `srcAccess` to the iteration domain of `dstAccess` which access the same 643 // memory locations. 644 dstRel.inverse(); 645 dstRel.compose(srcRel); 646 *dependenceConstraints = dstRel; 647 648 // Add 'src' happens before 'dst' ordering constraints. 649 addOrderingConstraints(srcDomain, dstDomain, loopDepth, 650 dependenceConstraints); 651 652 // Return 'NoDependence' if the solution space is empty: no dependence. 653 if (dependenceConstraints->isEmpty()) 654 return DependenceResult::NoDependence; 655 656 // Compute dependence direction vector and return true. 657 if (dependenceComponents != nullptr) 658 computeDirectionVector(srcDomain, dstDomain, loopDepth, 659 dependenceConstraints, dependenceComponents); 660 661 LLVM_DEBUG(llvm::dbgs() << "Dependence polyhedron:\n"); 662 LLVM_DEBUG(dependenceConstraints->dump()); 663 return DependenceResult::HasDependence; 664 } 665 666 /// Gathers dependence components for dependences between all ops in loop nest 667 /// rooted at 'forOp' at loop depths in range [1, maxLoopDepth]. 668 void mlir::getDependenceComponents( 669 AffineForOp forOp, unsigned maxLoopDepth, 670 std::vector<SmallVector<DependenceComponent, 2>> *depCompsVec) { 671 // Collect all load and store ops in loop nest rooted at 'forOp'. 672 SmallVector<Operation *, 8> loadAndStoreOps; 673 forOp->walk([&](Operation *op) { 674 if (isa<AffineReadOpInterface, AffineWriteOpInterface>(op)) 675 loadAndStoreOps.push_back(op); 676 }); 677 678 unsigned numOps = loadAndStoreOps.size(); 679 for (unsigned d = 1; d <= maxLoopDepth; ++d) { 680 for (unsigned i = 0; i < numOps; ++i) { 681 auto *srcOp = loadAndStoreOps[i]; 682 MemRefAccess srcAccess(srcOp); 683 for (unsigned j = 0; j < numOps; ++j) { 684 auto *dstOp = loadAndStoreOps[j]; 685 MemRefAccess dstAccess(dstOp); 686 687 FlatAffineValueConstraints dependenceConstraints; 688 SmallVector<DependenceComponent, 2> depComps; 689 // TODO: Explore whether it would be profitable to pre-compute and store 690 // deps instead of repeatedly checking. 691 DependenceResult result = checkMemrefAccessDependence( 692 srcAccess, dstAccess, d, &dependenceConstraints, &depComps); 693 if (hasDependence(result)) 694 depCompsVec->push_back(depComps); 695 } 696 } 697 } 698 } 699