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