2428 lines · cpp
1//===- Utils.cpp ---- Misc utilities for analysis -------------------------===//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-exception6//7//===----------------------------------------------------------------------===//8//9// This file implements miscellaneous analysis routines for non-loop IR10// structures.11//12//===----------------------------------------------------------------------===//13 14#include "mlir/Dialect/Affine/Analysis/Utils.h"15 16#include "mlir/Analysis/Presburger/PresburgerRelation.h"17#include "mlir/Dialect/Affine/Analysis/AffineAnalysis.h"18#include "mlir/Dialect/Affine/Analysis/LoopAnalysis.h"19#include "mlir/Dialect/Affine/IR/AffineOps.h"20#include "mlir/Dialect/Affine/IR/AffineValueMap.h"21#include "mlir/Dialect/MemRef/IR/MemRef.h"22#include "mlir/Dialect/Utils/StaticValueUtils.h"23#include "mlir/IR/IntegerSet.h"24#include "llvm/ADT/SetVector.h"25#include "llvm/Support/Debug.h"26#include "llvm/Support/DebugLog.h"27#include "llvm/Support/raw_ostream.h"28#include <optional>29 30#define DEBUG_TYPE "analysis-utils"31 32using namespace mlir;33using namespace affine;34using namespace presburger;35 36using llvm::SmallDenseMap;37 38using Node = MemRefDependenceGraph::Node;39 40// LoopNestStateCollector walks loop nests and collects load and store41// operations, and whether or not a region holding op other than ForOp and IfOp42// was encountered in the loop nest.43void LoopNestStateCollector::collect(Operation *opToWalk) {44 opToWalk->walk([&](Operation *op) {45 if (auto forOp = dyn_cast<AffineForOp>(op)) {46 forOps.push_back(forOp);47 } else if (isa<AffineReadOpInterface>(op)) {48 loadOpInsts.push_back(op);49 } else if (isa<AffineWriteOpInterface>(op)) {50 storeOpInsts.push_back(op);51 } else {52 auto memInterface = dyn_cast<MemoryEffectOpInterface>(op);53 if (!memInterface) {54 if (op->hasTrait<OpTrait::HasRecursiveMemoryEffects>())55 // This op itself is memory-effect free.56 return;57 // Check operands. Eg. ops like the `call` op are handled here.58 for (Value v : op->getOperands()) {59 if (!isa<MemRefType>(v.getType()))60 continue;61 // Conservatively, we assume the memref is read and written to.62 memrefLoads.push_back(op);63 memrefStores.push_back(op);64 }65 } else {66 // Non-affine loads and stores.67 if (hasEffect<MemoryEffects::Read>(op))68 memrefLoads.push_back(op);69 if (hasEffect<MemoryEffects::Write>(op))70 memrefStores.push_back(op);71 if (hasEffect<MemoryEffects::Free>(op))72 memrefFrees.push_back(op);73 }74 }75 });76}77 78unsigned Node::getLoadOpCount(Value memref) const {79 unsigned loadOpCount = 0;80 for (Operation *loadOp : loads) {81 // Common case: affine reads.82 if (auto affineLoad = dyn_cast<AffineReadOpInterface>(loadOp)) {83 if (memref == affineLoad.getMemRef())84 ++loadOpCount;85 } else if (hasEffect<MemoryEffects::Read>(loadOp, memref)) {86 ++loadOpCount;87 }88 }89 return loadOpCount;90}91 92// Returns the store op count for 'memref'.93unsigned Node::getStoreOpCount(Value memref) const {94 unsigned storeOpCount = 0;95 for (auto *storeOp : llvm::concat<Operation *const>(stores, memrefStores)) {96 // Common case: affine writes.97 if (auto affineStore = dyn_cast<AffineWriteOpInterface>(storeOp)) {98 if (memref == affineStore.getMemRef())99 ++storeOpCount;100 } else if (hasEffect<MemoryEffects::Write>(const_cast<Operation *>(storeOp),101 memref)) {102 ++storeOpCount;103 }104 }105 return storeOpCount;106}107 108// Returns the store op count for 'memref'.109unsigned Node::hasStore(Value memref) const {110 return llvm::any_of(111 llvm::concat<Operation *const>(stores, memrefStores),112 [&](Operation *storeOp) {113 if (auto affineStore = dyn_cast<AffineWriteOpInterface>(storeOp)) {114 if (memref == affineStore.getMemRef())115 return true;116 } else if (hasEffect<MemoryEffects::Write>(storeOp, memref)) {117 return true;118 }119 return false;120 });121}122 123unsigned Node::hasFree(Value memref) const {124 return llvm::any_of(memrefFrees, [&](Operation *freeOp) {125 return hasEffect<MemoryEffects::Free>(freeOp, memref);126 });127}128 129// Returns all store ops in 'storeOps' which access 'memref'.130void Node::getStoreOpsForMemref(Value memref,131 SmallVectorImpl<Operation *> *storeOps) const {132 for (Operation *storeOp : stores) {133 if (memref == cast<AffineWriteOpInterface>(storeOp).getMemRef())134 storeOps->push_back(storeOp);135 }136}137 138// Returns all load ops in 'loadOps' which access 'memref'.139void Node::getLoadOpsForMemref(Value memref,140 SmallVectorImpl<Operation *> *loadOps) const {141 for (Operation *loadOp : loads) {142 if (memref == cast<AffineReadOpInterface>(loadOp).getMemRef())143 loadOps->push_back(loadOp);144 }145}146 147// Returns all memrefs in 'loadAndStoreMemrefSet' for which this node148// has at least one load and store operation.149void Node::getLoadAndStoreMemrefSet(150 DenseSet<Value> *loadAndStoreMemrefSet) const {151 llvm::SmallDenseSet<Value, 2> loadMemrefs;152 for (Operation *loadOp : loads) {153 loadMemrefs.insert(cast<AffineReadOpInterface>(loadOp).getMemRef());154 }155 for (Operation *storeOp : stores) {156 auto memref = cast<AffineWriteOpInterface>(storeOp).getMemRef();157 if (loadMemrefs.count(memref) > 0)158 loadAndStoreMemrefSet->insert(memref);159 }160}161 162/// Returns the values that this op has a memref effect of type `EffectTys` on,163/// not considering recursive effects.164template <typename... EffectTys>165static void getEffectedValues(Operation *op, SmallVectorImpl<Value> &values) {166 auto memOp = dyn_cast<MemoryEffectOpInterface>(op);167 if (!memOp) {168 if (op->hasTrait<OpTrait::HasRecursiveMemoryEffects>())169 // No effects.170 return;171 // Memref operands have to be considered as being affected.172 for (Value operand : op->getOperands()) {173 if (isa<MemRefType>(operand.getType()))174 values.push_back(operand);175 }176 return;177 }178 SmallVector<SideEffects::EffectInstance<MemoryEffects::Effect>, 4> effects;179 memOp.getEffects(effects);180 for (auto &effect : effects) {181 Value effectVal = effect.getValue();182 if (isa<EffectTys...>(effect.getEffect()) && effectVal &&183 isa<MemRefType>(effectVal.getType()))184 values.push_back(effectVal);185 };186}187 188/// Add `op` to MDG creating a new node and adding its memory accesses (affine189/// or non-affine to memrefAccesses (memref -> list of nodes with accesses) map.190static Node *191addNodeToMDG(Operation *nodeOp, MemRefDependenceGraph &mdg,192 DenseMap<Value, SetVector<unsigned>> &memrefAccesses) {193 auto &nodes = mdg.nodes;194 // Create graph node 'id' to represent top-level 'forOp' and record195 // all loads and store accesses it contains.196 LoopNestStateCollector collector;197 collector.collect(nodeOp);198 unsigned newNodeId = mdg.nextNodeId++;199 Node &node = nodes.insert({newNodeId, Node(newNodeId, nodeOp)}).first->second;200 for (Operation *op : collector.loadOpInsts) {201 node.loads.push_back(op);202 auto memref = cast<AffineReadOpInterface>(op).getMemRef();203 memrefAccesses[memref].insert(node.id);204 }205 for (Operation *op : collector.storeOpInsts) {206 node.stores.push_back(op);207 auto memref = cast<AffineWriteOpInterface>(op).getMemRef();208 memrefAccesses[memref].insert(node.id);209 }210 for (Operation *op : collector.memrefLoads) {211 SmallVector<Value> effectedValues;212 getEffectedValues<MemoryEffects::Read>(op, effectedValues);213 if (llvm::any_of(((ValueRange)effectedValues).getTypes(),214 [](Type type) { return !isa<MemRefType>(type); }))215 // We do not know the interaction here.216 return nullptr;217 for (Value memref : effectedValues)218 memrefAccesses[memref].insert(node.id);219 node.memrefLoads.push_back(op);220 }221 for (Operation *op : collector.memrefStores) {222 SmallVector<Value> effectedValues;223 getEffectedValues<MemoryEffects::Write>(op, effectedValues);224 if (llvm::any_of((ValueRange(effectedValues)).getTypes(),225 [](Type type) { return !isa<MemRefType>(type); }))226 return nullptr;227 for (Value memref : effectedValues)228 memrefAccesses[memref].insert(node.id);229 node.memrefStores.push_back(op);230 }231 for (Operation *op : collector.memrefFrees) {232 SmallVector<Value> effectedValues;233 getEffectedValues<MemoryEffects::Free>(op, effectedValues);234 if (llvm::any_of((ValueRange(effectedValues)).getTypes(),235 [](Type type) { return !isa<MemRefType>(type); }))236 return nullptr;237 for (Value memref : effectedValues)238 memrefAccesses[memref].insert(node.id);239 node.memrefFrees.push_back(op);240 }241 242 return &node;243}244 245/// Returns the memref being read/written by a memref/affine load/store op.246static Value getMemRef(Operation *memOp) {247 if (auto memrefLoad = dyn_cast<memref::LoadOp>(memOp))248 return memrefLoad.getMemRef();249 if (auto affineLoad = dyn_cast<AffineReadOpInterface>(memOp))250 return affineLoad.getMemRef();251 if (auto memrefStore = dyn_cast<memref::StoreOp>(memOp))252 return memrefStore.getMemRef();253 if (auto affineStore = dyn_cast<AffineWriteOpInterface>(memOp))254 return affineStore.getMemRef();255 llvm_unreachable("unexpected op");256}257 258/// Returns true if there may be a dependence on `memref` from srcNode's259/// memory ops to dstNode's memory ops, while using the affine memory260/// dependence analysis checks. The method assumes that there is at least one261/// memory op in srcNode's loads and stores on `memref`, and similarly for262/// `dstNode`. `srcNode.op` and `destNode.op` are expected to be nested in the263/// same block and so the dependences are tested at the depth of that block.264static bool mayDependence(const Node &srcNode, const Node &dstNode,265 Value memref) {266 assert(srcNode.op->getBlock() == dstNode.op->getBlock());267 if (!isa<AffineForOp>(srcNode.op) || !isa<AffineForOp>(dstNode.op))268 return true;269 270 // Conservatively handle dependences involving non-affine load/stores. Return271 // true if there exists a conflicting read/write access involving such.272 273 // Check whether there is a dependence from a source read/write op to a274 // destination read/write one; all expected to be memref/affine load/store.275 auto hasNonAffineDep = [&](ArrayRef<Operation *> srcMemOps,276 ArrayRef<Operation *> dstMemOps) {277 return llvm::any_of(srcMemOps, [&](Operation *srcOp) {278 Value srcMemref = getMemRef(srcOp);279 if (srcMemref != memref)280 return false;281 return llvm::find_if(dstMemOps, [&](Operation *dstOp) {282 return srcMemref == getMemRef(dstOp);283 }) != dstMemOps.end();284 });285 };286 287 SmallVector<Operation *> dstOps;288 // Between non-affine src stores and dst load/store.289 llvm::append_range(dstOps, llvm::concat<Operation *const>(290 dstNode.loads, dstNode.stores,291 dstNode.memrefLoads, dstNode.memrefStores));292 if (hasNonAffineDep(srcNode.memrefStores, dstOps))293 return true;294 // Between non-affine loads and dst stores.295 dstOps.clear();296 llvm::append_range(dstOps, llvm::concat<Operation *const>(297 dstNode.stores, dstNode.memrefStores));298 if (hasNonAffineDep(srcNode.memrefLoads, dstOps))299 return true;300 // Between affine stores and memref load/stores.301 dstOps.clear();302 llvm::append_range(dstOps, llvm::concat<Operation *const>(303 dstNode.memrefLoads, dstNode.memrefStores));304 if (hasNonAffineDep(srcNode.stores, dstOps))305 return true;306 // Between affine loads and memref stores.307 dstOps.clear();308 llvm::append_range(dstOps, dstNode.memrefStores);309 if (hasNonAffineDep(srcNode.loads, dstOps))310 return true;311 312 // Affine load/store pairs. We don't need to check for locally allocated313 // memrefs since the dependence analysis here is between mem ops from314 // srcNode's for op to dstNode's for op at the depth at which those315 // `affine.for` ops are nested, i.e., dependences at depth `d + 1` where316 // `d` is the number of common surrounding loops.317 for (auto *srcMemOp :318 llvm::concat<Operation *const>(srcNode.stores, srcNode.loads)) {319 MemRefAccess srcAcc(srcMemOp);320 if (srcAcc.memref != memref)321 continue;322 for (auto *destMemOp :323 llvm::concat<Operation *const>(dstNode.stores, dstNode.loads)) {324 MemRefAccess destAcc(destMemOp);325 if (destAcc.memref != memref)326 continue;327 // Check for a top-level dependence between srcNode and destNode's ops.328 if (!noDependence(checkMemrefAccessDependence(329 srcAcc, destAcc, getNestingDepth(srcNode.op) + 1)))330 return true;331 }332 }333 return false;334}335 336bool MemRefDependenceGraph::init(bool fullAffineDependences) {337 LDBG() << "--- Initializing MDG ---";338 // Map from a memref to the set of ids of the nodes that have ops accessing339 // the memref.340 DenseMap<Value, SetVector<unsigned>> memrefAccesses;341 342 // Create graph nodes.343 DenseMap<Operation *, unsigned> forToNodeMap;344 for (Operation &op : block) {345 if (auto forOp = dyn_cast<AffineForOp>(op)) {346 Node *node = addNodeToMDG(&op, *this, memrefAccesses);347 if (!node)348 return false;349 forToNodeMap[&op] = node->id;350 } else if (isa<AffineReadOpInterface>(op)) {351 // Create graph node for top-level load op.352 Node node(nextNodeId++, &op);353 node.loads.push_back(&op);354 auto memref = cast<AffineReadOpInterface>(op).getMemRef();355 memrefAccesses[memref].insert(node.id);356 nodes.insert({node.id, node});357 } else if (isa<AffineWriteOpInterface>(op)) {358 // Create graph node for top-level store op.359 Node node(nextNodeId++, &op);360 node.stores.push_back(&op);361 auto memref = cast<AffineWriteOpInterface>(op).getMemRef();362 memrefAccesses[memref].insert(node.id);363 nodes.insert({node.id, node});364 } else if (op.getNumResults() > 0 && !op.use_empty()) {365 // Create graph node for top-level producer of SSA values, which366 // could be used by loop nest nodes.367 Node *node = addNodeToMDG(&op, *this, memrefAccesses);368 if (!node)369 return false;370 } else if (!isMemoryEffectFree(&op) &&371 (op.getNumRegions() == 0 || isa<RegionBranchOpInterface>(op))) {372 // Create graph node for top-level op unless it is known to be373 // memory-effect free. This covers all unknown/unregistered ops,374 // non-affine ops with memory effects, and region-holding ops with a375 // well-defined control flow. During the fusion validity checks, edges376 // to/from these ops get looked at.377 Node *node = addNodeToMDG(&op, *this, memrefAccesses);378 if (!node)379 return false;380 } else if (op.getNumRegions() != 0 && !isa<RegionBranchOpInterface>(op)) {381 // Return false if non-handled/unknown region-holding ops are found. We382 // won't know what such ops do or what its regions mean; for e.g., it may383 // not be an imperative op.384 LDBG() << "MDG init failed; unknown region-holding op found!";385 return false;386 }387 // We aren't creating nodes for memory-effect free ops either with no388 // regions (unless it has results being used) or those with branch op389 // interface.390 }391 392 LDBG() << "Created " << nodes.size() << " nodes";393 394 // Add dependence edges between nodes which produce SSA values and their395 // users. Load ops can be considered as the ones producing SSA values.396 for (auto &idAndNode : nodes) {397 const Node &node = idAndNode.second;398 // Stores don't define SSA values, skip them.399 if (!node.stores.empty())400 continue;401 Operation *opInst = node.op;402 for (Value value : opInst->getResults()) {403 for (Operation *user : value.getUsers()) {404 // Ignore users outside of the block.405 if (block.getParent()->findAncestorOpInRegion(*user)->getBlock() !=406 &block)407 continue;408 SmallVector<AffineForOp, 4> loops;409 getAffineForIVs(*user, &loops);410 // Find the surrounding affine.for nested immediately within the411 // block.412 auto *it = llvm::find_if(loops, [&](AffineForOp loop) {413 return loop->getBlock() == █414 });415 if (it == loops.end())416 continue;417 assert(forToNodeMap.count(*it) > 0 && "missing mapping");418 unsigned userLoopNestId = forToNodeMap[*it];419 addEdge(node.id, userLoopNestId, value);420 }421 }422 }423 424 // Walk memref access lists and add graph edges between dependent nodes.425 for (auto &memrefAndList : memrefAccesses) {426 unsigned n = memrefAndList.second.size();427 Value srcMemRef = memrefAndList.first;428 // Add edges between all dependent pairs among the node IDs on this memref.429 for (unsigned i = 0; i < n; ++i) {430 unsigned srcId = memrefAndList.second[i];431 Node *srcNode = getNode(srcId);432 bool srcHasStoreOrFree =433 srcNode->hasStore(srcMemRef) || srcNode->hasFree(srcMemRef);434 for (unsigned j = i + 1; j < n; ++j) {435 unsigned dstId = memrefAndList.second[j];436 Node *dstNode = getNode(dstId);437 bool dstHasStoreOrFree =438 dstNode->hasStore(srcMemRef) || dstNode->hasFree(srcMemRef);439 if ((srcHasStoreOrFree || dstHasStoreOrFree)) {440 // Check precise affine deps if asked for; otherwise, conservative.441 if (!fullAffineDependences ||442 mayDependence(*srcNode, *dstNode, srcMemRef))443 addEdge(srcId, dstId, srcMemRef);444 }445 }446 }447 }448 return true;449}450 451// Returns the graph node for 'id'.452const Node *MemRefDependenceGraph::getNode(unsigned id) const {453 auto it = nodes.find(id);454 assert(it != nodes.end());455 return &it->second;456}457 458// Returns the graph node for 'forOp'.459const Node *MemRefDependenceGraph::getForOpNode(AffineForOp forOp) const {460 for (auto &idAndNode : nodes)461 if (idAndNode.second.op == forOp)462 return &idAndNode.second;463 return nullptr;464}465 466// Adds a node with 'op' to the graph and returns its unique identifier.467unsigned MemRefDependenceGraph::addNode(Operation *op) {468 Node node(nextNodeId++, op);469 nodes.insert({node.id, node});470 return node.id;471}472 473// Remove node 'id' (and its associated edges) from graph.474void MemRefDependenceGraph::removeNode(unsigned id) {475 // Remove each edge in 'inEdges[id]'.476 if (inEdges.count(id) > 0) {477 SmallVector<Edge, 2> oldInEdges = inEdges[id];478 for (auto &inEdge : oldInEdges) {479 removeEdge(inEdge.id, id, inEdge.value);480 }481 }482 // Remove each edge in 'outEdges[id]'.483 if (outEdges.contains(id)) {484 SmallVector<Edge, 2> oldOutEdges = outEdges[id];485 for (auto &outEdge : oldOutEdges) {486 removeEdge(id, outEdge.id, outEdge.value);487 }488 }489 // Erase remaining node state.490 inEdges.erase(id);491 outEdges.erase(id);492 nodes.erase(id);493}494 495// Returns true if node 'id' writes to any memref which escapes (or is an496// argument to) the block. Returns false otherwise.497bool MemRefDependenceGraph::writesToLiveInOrEscapingMemrefs(unsigned id) const {498 const Node *node = getNode(id);499 for (auto *storeOpInst : node->stores) {500 auto memref = cast<AffineWriteOpInterface>(storeOpInst).getMemRef();501 auto *op = memref.getDefiningOp();502 // Return true if 'memref' is a block argument.503 if (!op)504 return true;505 // Return true if any use of 'memref' does not deference it in an affine506 // way.507 for (auto *user : memref.getUsers())508 if (!isa<AffineMapAccessInterface>(*user))509 return true;510 }511 return false;512}513 514// Returns true iff there is an edge from node 'srcId' to node 'dstId' which515// is for 'value' if non-null, or for any value otherwise. Returns false516// otherwise.517bool MemRefDependenceGraph::hasEdge(unsigned srcId, unsigned dstId,518 Value value) const {519 if (!outEdges.contains(srcId) || !inEdges.contains(dstId)) {520 return false;521 }522 bool hasOutEdge = llvm::any_of(outEdges.lookup(srcId), [=](const Edge &edge) {523 return edge.id == dstId && (!value || edge.value == value);524 });525 bool hasInEdge = llvm::any_of(inEdges.lookup(dstId), [=](const Edge &edge) {526 return edge.id == srcId && (!value || edge.value == value);527 });528 return hasOutEdge && hasInEdge;529}530 531// Adds an edge from node 'srcId' to node 'dstId' for 'value'.532void MemRefDependenceGraph::addEdge(unsigned srcId, unsigned dstId,533 Value value) {534 if (!hasEdge(srcId, dstId, value)) {535 outEdges[srcId].push_back({dstId, value});536 inEdges[dstId].push_back({srcId, value});537 if (isa<MemRefType>(value.getType()))538 memrefEdgeCount[value]++;539 }540}541 542// Removes an edge from node 'srcId' to node 'dstId' for 'value'.543void MemRefDependenceGraph::removeEdge(unsigned srcId, unsigned dstId,544 Value value) {545 assert(inEdges.count(dstId) > 0);546 assert(outEdges.count(srcId) > 0);547 if (isa<MemRefType>(value.getType())) {548 assert(memrefEdgeCount.count(value) > 0);549 memrefEdgeCount[value]--;550 }551 // Remove 'srcId' from 'inEdges[dstId]'.552 for (auto *it = inEdges[dstId].begin(); it != inEdges[dstId].end(); ++it) {553 if ((*it).id == srcId && (*it).value == value) {554 inEdges[dstId].erase(it);555 break;556 }557 }558 // Remove 'dstId' from 'outEdges[srcId]'.559 for (auto *it = outEdges[srcId].begin(); it != outEdges[srcId].end(); ++it) {560 if ((*it).id == dstId && (*it).value == value) {561 outEdges[srcId].erase(it);562 break;563 }564 }565}566 567// Returns true if there is a path in the dependence graph from node 'srcId'568// to node 'dstId'. Returns false otherwise. `srcId`, `dstId`, and the569// operations that the edges connected are expected to be from the same block.570bool MemRefDependenceGraph::hasDependencePath(unsigned srcId,571 unsigned dstId) const {572 // Worklist state is: <node-id, next-output-edge-index-to-visit>573 SmallVector<std::pair<unsigned, unsigned>, 4> worklist;574 worklist.push_back({srcId, 0});575 Operation *dstOp = getNode(dstId)->op;576 // Run DFS traversal to see if 'dstId' is reachable from 'srcId'.577 while (!worklist.empty()) {578 auto &idAndIndex = worklist.back();579 // Return true if we have reached 'dstId'.580 if (idAndIndex.first == dstId)581 return true;582 // Pop and continue if node has no out edges, or if all out edges have583 // already been visited.584 if (!outEdges.contains(idAndIndex.first) ||585 idAndIndex.second == outEdges.lookup(idAndIndex.first).size()) {586 worklist.pop_back();587 continue;588 }589 // Get graph edge to traverse.590 const Edge edge = outEdges.lookup(idAndIndex.first)[idAndIndex.second];591 // Increment next output edge index for 'idAndIndex'.592 ++idAndIndex.second;593 // Add node at 'edge.id' to the worklist. We don't need to consider594 // nodes that are "after" dstId in the containing block; one can't have a595 // path to `dstId` from any of those nodes.596 bool afterDst = dstOp->isBeforeInBlock(getNode(edge.id)->op);597 if (!afterDst && edge.id != idAndIndex.first)598 worklist.push_back({edge.id, 0});599 }600 return false;601}602 603// Returns the input edge count for node 'id' and 'memref' from src nodes604// which access 'memref' with a store operation.605unsigned MemRefDependenceGraph::getIncomingMemRefAccesses(unsigned id,606 Value memref) const {607 unsigned inEdgeCount = 0;608 for (const Edge &inEdge : inEdges.lookup(id)) {609 if (inEdge.value == memref) {610 const Node *srcNode = getNode(inEdge.id);611 // Only count in edges from 'srcNode' if 'srcNode' accesses 'memref'612 if (srcNode->getStoreOpCount(memref) > 0)613 ++inEdgeCount;614 }615 }616 return inEdgeCount;617}618 619// Returns the output edge count for node 'id' and 'memref' (if non-null),620// otherwise returns the total output edge count from node 'id'.621unsigned MemRefDependenceGraph::getOutEdgeCount(unsigned id,622 Value memref) const {623 unsigned outEdgeCount = 0;624 for (const auto &outEdge : outEdges.lookup(id))625 if (!memref || outEdge.value == memref)626 ++outEdgeCount;627 return outEdgeCount;628}629 630/// Return all nodes which define SSA values used in node 'id'.631void MemRefDependenceGraph::gatherDefiningNodes(632 unsigned id, DenseSet<unsigned> &definingNodes) const {633 for (const Edge &edge : inEdges.lookup(id))634 // By definition of edge, if the edge value is a non-memref value,635 // then the dependence is between a graph node which defines an SSA value636 // and another graph node which uses the SSA value.637 if (!isa<MemRefType>(edge.value.getType()))638 definingNodes.insert(edge.id);639}640 641// Computes and returns an insertion point operation, before which the642// the fused <srcId, dstId> loop nest can be inserted while preserving643// dependences. Returns nullptr if no such insertion point is found.644Operation *645MemRefDependenceGraph::getFusedLoopNestInsertionPoint(unsigned srcId,646 unsigned dstId) const {647 if (!outEdges.contains(srcId))648 return getNode(dstId)->op;649 650 // Skip if there is any defining node of 'dstId' that depends on 'srcId'.651 DenseSet<unsigned> definingNodes;652 gatherDefiningNodes(dstId, definingNodes);653 if (llvm::any_of(definingNodes,654 [&](unsigned id) { return hasDependencePath(srcId, id); })) {655 LDBG() << "Can't fuse: a defining op with a user in the dst "656 << "loop has dependence from the src loop";657 return nullptr;658 }659 660 // Build set of insts in range (srcId, dstId) which depend on 'srcId'.661 llvm::SmallPtrSet<Operation *, 2> srcDepInsts;662 for (auto &outEdge : outEdges.lookup(srcId))663 if (outEdge.id != dstId)664 srcDepInsts.insert(getNode(outEdge.id)->op);665 666 // Build set of insts in range (srcId, dstId) on which 'dstId' depends.667 llvm::SmallPtrSet<Operation *, 2> dstDepInsts;668 for (auto &inEdge : inEdges.lookup(dstId))669 if (inEdge.id != srcId)670 dstDepInsts.insert(getNode(inEdge.id)->op);671 672 Operation *srcNodeInst = getNode(srcId)->op;673 Operation *dstNodeInst = getNode(dstId)->op;674 675 // Computing insertion point:676 // *) Walk all operation positions in Block operation list in the677 // range (src, dst). For each operation 'op' visited in this search:678 // *) Store in 'firstSrcDepPos' the first position where 'op' has a679 // dependence edge from 'srcNode'.680 // *) Store in 'lastDstDepPost' the last position where 'op' has a681 // dependence edge to 'dstNode'.682 // *) Compare 'firstSrcDepPos' and 'lastDstDepPost' to determine the683 // operation insertion point (or return null pointer if no such684 // insertion point exists: 'firstSrcDepPos' <= 'lastDstDepPos').685 SmallVector<Operation *, 2> depInsts;686 std::optional<unsigned> firstSrcDepPos;687 std::optional<unsigned> lastDstDepPos;688 unsigned pos = 0;689 for (Block::iterator it = std::next(Block::iterator(srcNodeInst));690 it != Block::iterator(dstNodeInst); ++it) {691 Operation *op = &(*it);692 if (srcDepInsts.count(op) > 0 && firstSrcDepPos == std::nullopt)693 firstSrcDepPos = pos;694 if (dstDepInsts.count(op) > 0)695 lastDstDepPos = pos;696 depInsts.push_back(op);697 ++pos;698 }699 700 if (firstSrcDepPos.has_value()) {701 if (lastDstDepPos.has_value()) {702 if (*firstSrcDepPos <= *lastDstDepPos) {703 // No valid insertion point exists which preserves dependences.704 return nullptr;705 }706 }707 // Return the insertion point at 'firstSrcDepPos'.708 return depInsts[*firstSrcDepPos];709 }710 // No dependence targets in range (or only dst deps in range), return711 // 'dstNodInst' insertion point.712 return dstNodeInst;713}714 715// Updates edge mappings from node 'srcId' to node 'dstId' after fusing them,716// taking into account that:717// *) if 'removeSrcId' is true, 'srcId' will be removed after fusion,718// *) memrefs in 'privateMemRefs' has been replaced in node at 'dstId' by a719// private memref.720void MemRefDependenceGraph::updateEdges(unsigned srcId, unsigned dstId,721 const DenseSet<Value> &privateMemRefs,722 bool removeSrcId) {723 // For each edge in 'inEdges[srcId]': add new edge remapping to 'dstId'.724 if (inEdges.count(srcId) > 0) {725 SmallVector<Edge, 2> oldInEdges = inEdges[srcId];726 for (auto &inEdge : oldInEdges) {727 // Add edge from 'inEdge.id' to 'dstId' if it's not a private memref.728 if (!privateMemRefs.contains(inEdge.value))729 addEdge(inEdge.id, dstId, inEdge.value);730 }731 }732 // For each edge in 'outEdges[srcId]': remove edge from 'srcId' to 'dstId'.733 // If 'srcId' is going to be removed, remap all the out edges to 'dstId'.734 if (outEdges.count(srcId) > 0) {735 SmallVector<Edge, 2> oldOutEdges = outEdges[srcId];736 for (auto &outEdge : oldOutEdges) {737 // Remove any out edges from 'srcId' to 'dstId' across memrefs.738 if (outEdge.id == dstId)739 removeEdge(srcId, outEdge.id, outEdge.value);740 else if (removeSrcId) {741 addEdge(dstId, outEdge.id, outEdge.value);742 removeEdge(srcId, outEdge.id, outEdge.value);743 }744 }745 }746 // Remove any edges in 'inEdges[dstId]' on 'oldMemRef' (which is being747 // replaced by a private memref). These edges could come from nodes748 // other than 'srcId' which were removed in the previous step.749 if (inEdges.count(dstId) > 0 && !privateMemRefs.empty()) {750 SmallVector<Edge, 2> oldInEdges = inEdges[dstId];751 for (auto &inEdge : oldInEdges)752 if (privateMemRefs.count(inEdge.value) > 0)753 removeEdge(inEdge.id, dstId, inEdge.value);754 }755}756 757// Update edge mappings for nodes 'sibId' and 'dstId' to reflect fusion758// of sibling node 'sibId' into node 'dstId'.759void MemRefDependenceGraph::updateEdges(unsigned sibId, unsigned dstId) {760 // For each edge in 'inEdges[sibId]':761 // *) Add new edge from source node 'inEdge.id' to 'dstNode'.762 // *) Remove edge from source node 'inEdge.id' to 'sibNode'.763 if (inEdges.count(sibId) > 0) {764 SmallVector<Edge, 2> oldInEdges = inEdges[sibId];765 for (auto &inEdge : oldInEdges) {766 addEdge(inEdge.id, dstId, inEdge.value);767 removeEdge(inEdge.id, sibId, inEdge.value);768 }769 }770 771 // For each edge in 'outEdges[sibId]' to node 'id'772 // *) Add new edge from 'dstId' to 'outEdge.id'.773 // *) Remove edge from 'sibId' to 'outEdge.id'.774 if (outEdges.count(sibId) > 0) {775 SmallVector<Edge, 2> oldOutEdges = outEdges[sibId];776 for (auto &outEdge : oldOutEdges) {777 addEdge(dstId, outEdge.id, outEdge.value);778 removeEdge(sibId, outEdge.id, outEdge.value);779 }780 }781}782 783// Adds ops in 'loads' and 'stores' to node at 'id'.784void MemRefDependenceGraph::addToNode(unsigned id, ArrayRef<Operation *> loads,785 ArrayRef<Operation *> stores,786 ArrayRef<Operation *> memrefLoads,787 ArrayRef<Operation *> memrefStores,788 ArrayRef<Operation *> memrefFrees) {789 Node *node = getNode(id);790 llvm::append_range(node->loads, loads);791 llvm::append_range(node->stores, stores);792 llvm::append_range(node->memrefLoads, memrefLoads);793 llvm::append_range(node->memrefStores, memrefStores);794 llvm::append_range(node->memrefFrees, memrefFrees);795}796 797void MemRefDependenceGraph::clearNodeLoadAndStores(unsigned id) {798 Node *node = getNode(id);799 node->loads.clear();800 node->stores.clear();801}802 803// Calls 'callback' for each input edge incident to node 'id' which carries a804// memref dependence.805void MemRefDependenceGraph::forEachMemRefInputEdge(806 unsigned id, const std::function<void(Edge)> &callback) {807 if (inEdges.count(id) > 0)808 forEachMemRefEdge(inEdges.at(id), callback);809}810 811// Calls 'callback' for each output edge from node 'id' which carries a812// memref dependence.813void MemRefDependenceGraph::forEachMemRefOutputEdge(814 unsigned id, const std::function<void(Edge)> &callback) {815 if (outEdges.count(id) > 0)816 forEachMemRefEdge(outEdges.at(id), callback);817}818 819// Calls 'callback' for each edge in 'edges' which carries a memref820// dependence.821void MemRefDependenceGraph::forEachMemRefEdge(822 ArrayRef<Edge> edges, const std::function<void(Edge)> &callback) {823 for (const auto &edge : edges) {824 // Skip if 'edge' is not a memref dependence edge.825 if (!isa<MemRefType>(edge.value.getType()))826 continue;827 assert(nodes.count(edge.id) > 0);828 // Visit current input edge 'edge'.829 callback(edge);830 }831}832 833void MemRefDependenceGraph::print(raw_ostream &os) const {834 os << "\nMemRefDependenceGraph\n";835 os << "\nNodes:\n";836 for (const auto &idAndNode : nodes) {837 os << "Node: " << idAndNode.first << "\n";838 auto it = inEdges.find(idAndNode.first);839 if (it != inEdges.end()) {840 for (const auto &e : it->second)841 os << " InEdge: " << e.id << " " << e.value << "\n";842 }843 it = outEdges.find(idAndNode.first);844 if (it != outEdges.end()) {845 for (const auto &e : it->second)846 os << " OutEdge: " << e.id << " " << e.value << "\n";847 }848 }849}850 851void mlir::affine::getAffineForIVs(Operation &op,852 SmallVectorImpl<AffineForOp> *loops) {853 auto *currOp = op.getParentOp();854 AffineForOp currAffineForOp;855 // Traverse up the hierarchy collecting all 'affine.for' operation while856 // skipping over 'affine.if' operations.857 while (currOp && !currOp->hasTrait<OpTrait::AffineScope>()) {858 if (auto currAffineForOp = dyn_cast<AffineForOp>(currOp))859 loops->push_back(currAffineForOp);860 currOp = currOp->getParentOp();861 }862 std::reverse(loops->begin(), loops->end());863}864 865void mlir::affine::getEnclosingAffineOps(Operation &op,866 SmallVectorImpl<Operation *> *ops) {867 ops->clear();868 Operation *currOp = op.getParentOp();869 870 // Traverse up the hierarchy collecting all `affine.for`, `affine.if`, and871 // affine.parallel operations.872 while (currOp && !currOp->hasTrait<OpTrait::AffineScope>()) {873 if (isa<AffineIfOp, AffineForOp, AffineParallelOp>(currOp))874 ops->push_back(currOp);875 currOp = currOp->getParentOp();876 }877 std::reverse(ops->begin(), ops->end());878}879 880// Populates 'cst' with FlatAffineValueConstraints which represent original881// domain of the loop bounds that define 'ivs'.882LogicalResult ComputationSliceState::getSourceAsConstraints(883 FlatAffineValueConstraints &cst) const {884 assert(!ivs.empty() && "Cannot have a slice without its IVs");885 cst = FlatAffineValueConstraints(/*numDims=*/ivs.size(), /*numSymbols=*/0,886 /*numLocals=*/0, ivs);887 for (Value iv : ivs) {888 AffineForOp loop = getForInductionVarOwner(iv);889 assert(loop && "Expected affine for");890 if (failed(cst.addAffineForOpDomain(loop)))891 return failure();892 }893 return success();894}895 896// Populates 'cst' with FlatAffineValueConstraints which represent slice bounds.897LogicalResult898ComputationSliceState::getAsConstraints(FlatAffineValueConstraints *cst) const {899 assert(!lbOperands.empty());900 // Adds src 'ivs' as dimension variables in 'cst'.901 unsigned numDims = ivs.size();902 // Adds operands (dst ivs and symbols) as symbols in 'cst'.903 unsigned numSymbols = lbOperands[0].size();904 905 SmallVector<Value, 4> values(ivs);906 // Append 'ivs' then 'operands' to 'values'.907 values.append(lbOperands[0].begin(), lbOperands[0].end());908 *cst = FlatAffineValueConstraints(numDims, numSymbols, 0, values);909 910 // Add loop bound constraints for values which are loop IVs of the destination911 // of fusion and equality constraints for symbols which are constants.912 for (unsigned i = numDims, end = values.size(); i < end; ++i) {913 Value value = values[i];914 assert(cst->containsVar(value) && "value expected to be present");915 if (isValidSymbol(value)) {916 // Check if the symbol is a constant.917 if (std::optional<int64_t> cOp = getConstantIntValue(value))918 cst->addBound(BoundType::EQ, value, cOp.value());919 } else if (auto loop = getForInductionVarOwner(value)) {920 if (failed(cst->addAffineForOpDomain(loop)))921 return failure();922 }923 }924 925 // Add slices bounds on 'ivs' using maps 'lbs'/'ubs' with 'lbOperands[0]'926 LogicalResult ret = cst->addSliceBounds(ivs, lbs, ubs, lbOperands[0]);927 assert(succeeded(ret) &&928 "should not fail as we never have semi-affine slice maps");929 (void)ret;930 return success();931}932 933// Clears state bounds and operand state.934void ComputationSliceState::clearBounds() {935 lbs.clear();936 ubs.clear();937 lbOperands.clear();938 ubOperands.clear();939}940 941void ComputationSliceState::dump() const {942 llvm::errs() << "\tIVs:\n";943 for (Value iv : ivs)944 llvm::errs() << "\t\t" << iv << "\n";945 946 llvm::errs() << "\tLBs:\n";947 for (auto en : llvm::enumerate(lbs)) {948 llvm::errs() << "\t\t" << en.value() << "\n";949 llvm::errs() << "\t\tOperands:\n";950 for (Value lbOp : lbOperands[en.index()])951 llvm::errs() << "\t\t\t" << lbOp << "\n";952 }953 954 llvm::errs() << "\tUBs:\n";955 for (auto en : llvm::enumerate(ubs)) {956 llvm::errs() << "\t\t" << en.value() << "\n";957 llvm::errs() << "\t\tOperands:\n";958 for (Value ubOp : ubOperands[en.index()])959 llvm::errs() << "\t\t\t" << ubOp << "\n";960 }961}962 963/// Fast check to determine if the computation slice is maximal. Returns true if964/// each slice dimension maps to an existing dst dimension and both the src965/// and the dst loops for those dimensions have the same bounds. Returns false966/// if both the src and the dst loops don't have the same bounds. Returns967/// std::nullopt if none of the above can be proven.968std::optional<bool> ComputationSliceState::isSliceMaximalFastCheck() const {969 assert(lbs.size() == ubs.size() && !lbs.empty() && !ivs.empty() &&970 "Unexpected number of lbs, ubs and ivs in slice");971 972 for (unsigned i = 0, end = lbs.size(); i < end; ++i) {973 AffineMap lbMap = lbs[i];974 AffineMap ubMap = ubs[i];975 976 // Check if this slice is just an equality along this dimension.977 if (!lbMap || !ubMap || lbMap.getNumResults() != 1 ||978 ubMap.getNumResults() != 1 ||979 lbMap.getResult(0) + 1 != ubMap.getResult(0) ||980 // The condition above will be true for maps describing a single981 // iteration (e.g., lbMap.getResult(0) = 0, ubMap.getResult(0) = 1).982 // Make sure we skip those cases by checking that the lb result is not983 // just a constant.984 isa<AffineConstantExpr>(lbMap.getResult(0)))985 return std::nullopt;986 987 // Limited support: we expect the lb result to be just a loop dimension for988 // now.989 AffineDimExpr result = dyn_cast<AffineDimExpr>(lbMap.getResult(0));990 if (!result)991 return std::nullopt;992 993 // Retrieve dst loop bounds.994 AffineForOp dstLoop =995 getForInductionVarOwner(lbOperands[i][result.getPosition()]);996 if (!dstLoop)997 return std::nullopt;998 AffineMap dstLbMap = dstLoop.getLowerBoundMap();999 AffineMap dstUbMap = dstLoop.getUpperBoundMap();1000 1001 // Retrieve src loop bounds.1002 AffineForOp srcLoop = getForInductionVarOwner(ivs[i]);1003 assert(srcLoop && "Expected affine for");1004 AffineMap srcLbMap = srcLoop.getLowerBoundMap();1005 AffineMap srcUbMap = srcLoop.getUpperBoundMap();1006 1007 // Limited support: we expect simple src and dst loops with a single1008 // constant component per bound for now.1009 if (srcLbMap.getNumResults() != 1 || srcUbMap.getNumResults() != 1 ||1010 dstLbMap.getNumResults() != 1 || dstUbMap.getNumResults() != 1)1011 return std::nullopt;1012 1013 AffineExpr srcLbResult = srcLbMap.getResult(0);1014 AffineExpr dstLbResult = dstLbMap.getResult(0);1015 AffineExpr srcUbResult = srcUbMap.getResult(0);1016 AffineExpr dstUbResult = dstUbMap.getResult(0);1017 if (!isa<AffineConstantExpr>(srcLbResult) ||1018 !isa<AffineConstantExpr>(srcUbResult) ||1019 !isa<AffineConstantExpr>(dstLbResult) ||1020 !isa<AffineConstantExpr>(dstUbResult))1021 return std::nullopt;1022 1023 // Check if src and dst loop bounds are the same. If not, we can guarantee1024 // that the slice is not maximal.1025 if (srcLbResult != dstLbResult || srcUbResult != dstUbResult ||1026 srcLoop.getStep() != dstLoop.getStep())1027 return false;1028 }1029 1030 return true;1031}1032 1033/// Returns true if it is deterministically verified that the original iteration1034/// space of the slice is contained within the new iteration space that is1035/// created after fusing 'this' slice into its destination.1036std::optional<bool> ComputationSliceState::isSliceValid() const {1037 // Fast check to determine if the slice is valid. If the following conditions1038 // are verified to be true, slice is declared valid by the fast check:1039 // 1. Each slice loop is a single iteration loop bound in terms of a single1040 // destination loop IV.1041 // 2. Loop bounds of the destination loop IV (from above) and those of the1042 // source loop IV are exactly the same.1043 // If the fast check is inconclusive or false, we proceed with a more1044 // expensive analysis.1045 // TODO: Store the result of the fast check, as it might be used again in1046 // `canRemoveSrcNodeAfterFusion`.1047 std::optional<bool> isValidFastCheck = isSliceMaximalFastCheck();1048 if (isValidFastCheck && *isValidFastCheck)1049 return true;1050 1051 // Create constraints for the source loop nest using which slice is computed.1052 FlatAffineValueConstraints srcConstraints;1053 // TODO: Store the source's domain to avoid computation at each depth.1054 if (failed(getSourceAsConstraints(srcConstraints))) {1055 LDBG() << "Unable to compute source's domain";1056 return std::nullopt;1057 }1058 // As the set difference utility currently cannot handle symbols in its1059 // operands, validity of the slice cannot be determined.1060 if (srcConstraints.getNumSymbolVars() > 0) {1061 LDBG() << "Cannot handle symbols in source domain";1062 return std::nullopt;1063 }1064 // TODO: Handle local vars in the source domains while using the 'projectOut'1065 // utility below. Currently, aligning is not done assuming that there will be1066 // no local vars in the source domain.1067 if (srcConstraints.getNumLocalVars() != 0) {1068 LDBG() << "Cannot handle locals in source domain";1069 return std::nullopt;1070 }1071 1072 // Create constraints for the slice loop nest that would be created if the1073 // fusion succeeds.1074 FlatAffineValueConstraints sliceConstraints;1075 if (failed(getAsConstraints(&sliceConstraints))) {1076 LDBG() << "Unable to compute slice's domain";1077 return std::nullopt;1078 }1079 1080 // Projecting out every dimension other than the 'ivs' to express slice's1081 // domain completely in terms of source's IVs.1082 sliceConstraints.projectOut(ivs.size(),1083 sliceConstraints.getNumVars() - ivs.size());1084 1085 LDBG() << "Domain of the source of the slice:\n"1086 << "Source constraints:" << srcConstraints1087 << "\nDomain of the slice if this fusion succeeds "1088 << "(expressed in terms of its source's IVs):\n"1089 << "Slice constraints:" << sliceConstraints;1090 1091 // TODO: Store 'srcSet' to avoid recalculating for each depth.1092 PresburgerSet srcSet(srcConstraints);1093 PresburgerSet sliceSet(sliceConstraints);1094 PresburgerSet diffSet = sliceSet.subtract(srcSet);1095 1096 if (!diffSet.isIntegerEmpty()) {1097 LDBG() << "Incorrect slice";1098 return false;1099 }1100 return true;1101}1102 1103/// Returns true if the computation slice encloses all the iterations of the1104/// sliced loop nest. Returns false if it does not. Returns std::nullopt if it1105/// cannot determine if the slice is maximal or not.1106std::optional<bool> ComputationSliceState::isMaximal() const {1107 // Fast check to determine if the computation slice is maximal. If the result1108 // is inconclusive, we proceed with a more expensive analysis.1109 std::optional<bool> isMaximalFastCheck = isSliceMaximalFastCheck();1110 if (isMaximalFastCheck)1111 return isMaximalFastCheck;1112 1113 // Create constraints for the src loop nest being sliced.1114 FlatAffineValueConstraints srcConstraints(/*numDims=*/ivs.size(),1115 /*numSymbols=*/0,1116 /*numLocals=*/0, ivs);1117 for (Value iv : ivs) {1118 AffineForOp loop = getForInductionVarOwner(iv);1119 assert(loop && "Expected affine for");1120 if (failed(srcConstraints.addAffineForOpDomain(loop)))1121 return std::nullopt;1122 }1123 1124 // Create constraints for the slice using the dst loop nest information. We1125 // retrieve existing dst loops from the lbOperands.1126 SmallVector<Value> consumerIVs;1127 for (Value lbOp : lbOperands[0])1128 if (getForInductionVarOwner(lbOp))1129 consumerIVs.push_back(lbOp);1130 1131 // Add empty IV Values for those new loops that are not equalities and,1132 // therefore, are not yet materialized in the IR.1133 for (int i = consumerIVs.size(), end = ivs.size(); i < end; ++i)1134 consumerIVs.push_back(Value());1135 1136 FlatAffineValueConstraints sliceConstraints(/*numDims=*/consumerIVs.size(),1137 /*numSymbols=*/0,1138 /*numLocals=*/0, consumerIVs);1139 1140 if (failed(sliceConstraints.addDomainFromSliceMaps(lbs, ubs, lbOperands[0])))1141 return std::nullopt;1142 1143 if (srcConstraints.getNumDimVars() != sliceConstraints.getNumDimVars())1144 // Constraint dims are different. The integer set difference can't be1145 // computed so we don't know if the slice is maximal.1146 return std::nullopt;1147 1148 // Compute the difference between the src loop nest and the slice integer1149 // sets.1150 PresburgerSet srcSet(srcConstraints);1151 PresburgerSet sliceSet(sliceConstraints);1152 PresburgerSet diffSet = srcSet.subtract(sliceSet);1153 return diffSet.isIntegerEmpty();1154}1155 1156unsigned MemRefRegion::getRank() const {1157 return cast<MemRefType>(memref.getType()).getRank();1158}1159 1160std::optional<int64_t> MemRefRegion::getConstantBoundingSizeAndShape(1161 SmallVectorImpl<int64_t> *shape, SmallVectorImpl<AffineMap> *lbs) const {1162 auto memRefType = cast<MemRefType>(memref.getType());1163 MLIRContext *context = memref.getContext();1164 unsigned rank = memRefType.getRank();1165 if (shape)1166 shape->reserve(rank);1167 1168 assert(rank == cst.getNumDimVars() && "inconsistent memref region");1169 1170 // Use a copy of the region constraints that has upper/lower bounds for each1171 // memref dimension with static size added to guard against potential1172 // over-approximation from projection or union bounding box. We may not add1173 // this on the region itself since they might just be redundant constraints1174 // that will need non-trivials means to eliminate.1175 FlatLinearValueConstraints cstWithShapeBounds(cst);1176 for (unsigned r = 0; r < rank; r++) {1177 cstWithShapeBounds.addBound(BoundType::LB, r, 0);1178 int64_t dimSize = memRefType.getDimSize(r);1179 if (ShapedType::isDynamic(dimSize))1180 continue;1181 cstWithShapeBounds.addBound(BoundType::UB, r, dimSize - 1);1182 }1183 1184 // Find a constant upper bound on the extent of this memref region along1185 // each dimension.1186 int64_t numElements = 1;1187 int64_t diffConstant;1188 for (unsigned d = 0; d < rank; d++) {1189 AffineMap lb;1190 std::optional<int64_t> diff =1191 cstWithShapeBounds.getConstantBoundOnDimSize(context, d, &lb);1192 if (diff.has_value()) {1193 diffConstant = *diff;1194 assert(diffConstant >= 0 && "dim size bound cannot be negative");1195 } else {1196 // If no constant bound is found, then it can always be bound by the1197 // memref's dim size if the latter has a constant size along this dim.1198 auto dimSize = memRefType.getDimSize(d);1199 if (ShapedType::isDynamic(dimSize))1200 return std::nullopt;1201 diffConstant = dimSize;1202 // Lower bound becomes 0.1203 lb = AffineMap::get(/*dimCount=*/0, cstWithShapeBounds.getNumSymbolVars(),1204 /*result=*/getAffineConstantExpr(0, context));1205 }1206 numElements *= diffConstant;1207 // Populate outputs if available.1208 if (lbs)1209 lbs->push_back(lb);1210 if (shape)1211 shape->push_back(diffConstant);1212 }1213 return numElements;1214}1215 1216void MemRefRegion::getLowerAndUpperBound(unsigned pos, AffineMap &lbMap,1217 AffineMap &ubMap) const {1218 assert(pos < cst.getNumDimVars() && "invalid position");1219 auto memRefType = cast<MemRefType>(memref.getType());1220 unsigned rank = memRefType.getRank();1221 1222 assert(rank == cst.getNumDimVars() && "inconsistent memref region");1223 1224 auto boundPairs = cst.getLowerAndUpperBound(1225 pos, /*offset=*/0, /*num=*/rank, cst.getNumDimAndSymbolVars(),1226 /*localExprs=*/{}, memRefType.getContext());1227 lbMap = boundPairs.first;1228 ubMap = boundPairs.second;1229 assert(lbMap && "lower bound for a region must exist");1230 assert(ubMap && "upper bound for a region must exist");1231 assert(lbMap.getNumInputs() == cst.getNumDimAndSymbolVars() - rank);1232 assert(ubMap.getNumInputs() == cst.getNumDimAndSymbolVars() - rank);1233}1234 1235LogicalResult MemRefRegion::unionBoundingBox(const MemRefRegion &other) {1236 assert(memref == other.memref);1237 return cst.unionBoundingBox(*other.getConstraints());1238}1239 1240/// Computes the memory region accessed by this memref with the region1241/// represented as constraints symbolic/parametric in 'loopDepth' loops1242/// surrounding opInst and any additional Function symbols.1243// For example, the memref region for this load operation at loopDepth = 1 will1244// be as below:1245//1246// affine.for %i = 0 to 32 {1247// affine.for %ii = %i to (d0) -> (d0 + 8) (%i) {1248// load %A[%ii]1249// }1250// }1251//1252// region: {memref = %A, write = false, {%i <= m0 <= %i + 7} }1253// The last field is a 2-d FlatAffineValueConstraints symbolic in %i.1254//1255// TODO: extend this to any other memref dereferencing ops1256// (dma_start, dma_wait).1257LogicalResult MemRefRegion::compute(Operation *op, unsigned loopDepth,1258 const ComputationSliceState *sliceState,1259 bool addMemRefDimBounds, bool dropLocalVars,1260 bool dropOuterIvs) {1261 assert((isa<AffineReadOpInterface, AffineWriteOpInterface>(op)) &&1262 "affine read/write op expected");1263 1264 MemRefAccess access(op);1265 memref = access.memref;1266 write = access.isStore();1267 1268 unsigned rank = access.getRank();1269 1270 LDBG() << "MemRefRegion::compute: " << *op << " depth: " << loopDepth;1271 1272 // 0-d memrefs.1273 if (rank == 0) {1274 SmallVector<Value, 4> ivs;1275 getAffineIVs(*op, ivs);1276 assert(loopDepth <= ivs.size() && "invalid 'loopDepth'");1277 // The first 'loopDepth' IVs are symbols for this region.1278 ivs.resize(loopDepth);1279 // A 0-d memref has a 0-d region.1280 cst = FlatAffineValueConstraints(rank, loopDepth, /*numLocals=*/0, ivs);1281 return success();1282 }1283 1284 // Build the constraints for this region.1285 AffineValueMap accessValueMap;1286 access.getAccessMap(&accessValueMap);1287 AffineMap accessMap = accessValueMap.getAffineMap();1288 1289 unsigned numDims = accessMap.getNumDims();1290 unsigned numSymbols = accessMap.getNumSymbols();1291 unsigned numOperands = accessValueMap.getNumOperands();1292 // Merge operands with slice operands.1293 SmallVector<Value, 4> operands;1294 operands.resize(numOperands);1295 for (unsigned i = 0; i < numOperands; ++i)1296 operands[i] = accessValueMap.getOperand(i);1297 1298 if (sliceState != nullptr) {1299 operands.reserve(operands.size() + sliceState->lbOperands[0].size());1300 // Append slice operands to 'operands' as symbols.1301 for (auto extraOperand : sliceState->lbOperands[0]) {1302 if (!llvm::is_contained(operands, extraOperand)) {1303 operands.push_back(extraOperand);1304 numSymbols++;1305 }1306 }1307 }1308 // We'll first associate the dims and symbols of the access map to the dims1309 // and symbols resp. of cst. This will change below once cst is1310 // fully constructed out.1311 cst = FlatAffineValueConstraints(numDims, numSymbols, 0, operands);1312 1313 // Add equality constraints.1314 // Add inequalities for loop lower/upper bounds.1315 for (unsigned i = 0; i < numDims + numSymbols; ++i) {1316 auto operand = operands[i];1317 if (auto affineFor = getForInductionVarOwner(operand)) {1318 // Note that cst can now have more dimensions than accessMap if the1319 // bounds expressions involve outer loops or other symbols.1320 // TODO: rewrite this to use getInstIndexSet; this way1321 // conditionals will be handled when the latter supports it.1322 if (failed(cst.addAffineForOpDomain(affineFor)))1323 return failure();1324 } else if (auto parallelOp = getAffineParallelInductionVarOwner(operand)) {1325 if (failed(cst.addAffineParallelOpDomain(parallelOp)))1326 return failure();1327 } else if (isValidSymbol(operand)) {1328 // Check if the symbol is a constant.1329 Value symbol = operand;1330 if (auto constVal = getConstantIntValue(symbol))1331 cst.addBound(BoundType::EQ, symbol, constVal.value());1332 } else {1333 LDBG() << "unknown affine dimensional value";1334 return failure();1335 }1336 }1337 1338 // Add lower/upper bounds on loop IVs using bounds from 'sliceState'.1339 if (sliceState != nullptr) {1340 // Add dim and symbol slice operands.1341 for (auto operand : sliceState->lbOperands[0]) {1342 if (failed(cst.addInductionVarOrTerminalSymbol(operand)))1343 return failure();1344 }1345 // Add upper/lower bounds from 'sliceState' to 'cst'.1346 LogicalResult ret =1347 cst.addSliceBounds(sliceState->ivs, sliceState->lbs, sliceState->ubs,1348 sliceState->lbOperands[0]);1349 assert(succeeded(ret) &&1350 "should not fail as we never have semi-affine slice maps");1351 (void)ret;1352 }1353 1354 // Add access function equalities to connect loop IVs to data dimensions.1355 if (failed(cst.composeMap(&accessValueMap))) {1356 op->emitError("getMemRefRegion: compose affine map failed");1357 LDBG() << "Access map: " << accessValueMap.getAffineMap();1358 return failure();1359 }1360 1361 // Set all variables appearing after the first 'rank' variables as1362 // symbolic variables - so that the ones corresponding to the memref1363 // dimensions are the dimensional variables for the memref region.1364 cst.setDimSymbolSeparation(cst.getNumDimAndSymbolVars() - rank);1365 1366 // Eliminate any loop IVs other than the outermost 'loopDepth' IVs, on which1367 // this memref region is symbolic.1368 SmallVector<Value, 4> enclosingIVs;1369 getAffineIVs(*op, enclosingIVs);1370 assert(loopDepth <= enclosingIVs.size() && "invalid loop depth");1371 enclosingIVs.resize(loopDepth);1372 SmallVector<Value, 4> vars;1373 cst.getValues(cst.getNumDimVars(), cst.getNumDimAndSymbolVars(), &vars);1374 for (auto en : llvm::enumerate(vars)) {1375 if ((isAffineInductionVar(en.value())) &&1376 !llvm::is_contained(enclosingIVs, en.value())) {1377 if (dropOuterIvs) {1378 cst.projectOut(en.value());1379 } else {1380 unsigned varPosition;1381 cst.findVar(en.value(), &varPosition);1382 auto varKind = cst.getVarKindAt(varPosition);1383 varPosition -= cst.getNumDimVars();1384 cst.convertToLocal(varKind, varPosition, varPosition + 1);1385 }1386 }1387 }1388 1389 // Project out any local variables (these would have been added for any1390 // mod/divs) if specified.1391 if (dropLocalVars)1392 cst.projectOut(cst.getNumDimAndSymbolVars(), cst.getNumLocalVars());1393 1394 // Constant fold any symbolic variables.1395 cst.constantFoldVarRange(/*pos=*/cst.getNumDimVars(),1396 /*num=*/cst.getNumSymbolVars());1397 1398 assert(cst.getNumDimVars() == rank && "unexpected MemRefRegion format");1399 1400 // Add upper/lower bounds for each memref dimension with static size1401 // to guard against potential over-approximation from projection.1402 // TODO: Support dynamic memref dimensions.1403 if (addMemRefDimBounds) {1404 auto memRefType = cast<MemRefType>(memref.getType());1405 for (unsigned r = 0; r < rank; r++) {1406 cst.addBound(BoundType::LB, /*pos=*/r, /*value=*/0);1407 if (memRefType.isDynamicDim(r))1408 continue;1409 cst.addBound(BoundType::UB, /*pos=*/r, memRefType.getDimSize(r) - 1);1410 }1411 }1412 cst.removeTrivialRedundancy();1413 1414 LDBG() << "Memory region: " << cst;1415 return success();1416}1417 1418std::optional<int64_t>1419mlir::affine::getMemRefIntOrFloatEltSizeInBytes(MemRefType memRefType) {1420 auto elementType = memRefType.getElementType();1421 1422 unsigned sizeInBits;1423 if (elementType.isIntOrFloat()) {1424 sizeInBits = elementType.getIntOrFloatBitWidth();1425 } else if (auto vectorType = dyn_cast<VectorType>(elementType)) {1426 if (vectorType.getElementType().isIntOrFloat())1427 sizeInBits =1428 vectorType.getElementTypeBitWidth() * vectorType.getNumElements();1429 else1430 return std::nullopt;1431 } else {1432 return std::nullopt;1433 }1434 return llvm::divideCeil(sizeInBits, 8);1435}1436 1437// Returns the size of the region.1438std::optional<int64_t> MemRefRegion::getRegionSize() {1439 auto memRefType = cast<MemRefType>(memref.getType());1440 1441 if (!memRefType.getLayout().isIdentity()) {1442 LDBG() << "Non-identity layout map not yet supported";1443 return false;1444 }1445 1446 // Compute the extents of the buffer.1447 std::optional<int64_t> numElements = getConstantBoundingSizeAndShape();1448 if (!numElements) {1449 LDBG() << "Dynamic shapes not yet supported";1450 return std::nullopt;1451 }1452 auto eltSize = getMemRefIntOrFloatEltSizeInBytes(memRefType);1453 if (!eltSize)1454 return std::nullopt;1455 return *eltSize * *numElements;1456}1457 1458/// Returns the size of memref data in bytes if it's statically shaped,1459/// std::nullopt otherwise. If the element of the memref has vector type, takes1460/// into account size of the vector as well.1461// TODO: improve/complete this when we have target data.1462std::optional<uint64_t>1463mlir::affine::getIntOrFloatMemRefSizeInBytes(MemRefType memRefType) {1464 if (!memRefType.hasStaticShape())1465 return std::nullopt;1466 auto elementType = memRefType.getElementType();1467 if (!elementType.isIntOrFloat() && !isa<VectorType>(elementType))1468 return std::nullopt;1469 1470 auto sizeInBytes = getMemRefIntOrFloatEltSizeInBytes(memRefType);1471 if (!sizeInBytes)1472 return std::nullopt;1473 for (unsigned i = 0, e = memRefType.getRank(); i < e; i++) {1474 sizeInBytes = *sizeInBytes * memRefType.getDimSize(i);1475 }1476 return sizeInBytes;1477}1478 1479template <typename LoadOrStoreOp>1480LogicalResult mlir::affine::boundCheckLoadOrStoreOp(LoadOrStoreOp loadOrStoreOp,1481 bool emitError) {1482 static_assert(llvm::is_one_of<LoadOrStoreOp, AffineReadOpInterface,1483 AffineWriteOpInterface>::value,1484 "argument should be either a AffineReadOpInterface or a "1485 "AffineWriteOpInterface");1486 1487 Operation *op = loadOrStoreOp.getOperation();1488 MemRefRegion region(op->getLoc());1489 if (failed(region.compute(op, /*loopDepth=*/0, /*sliceState=*/nullptr,1490 /*addMemRefDimBounds=*/false)))1491 return success();1492 1493 LDBG() << "Memory region: " << region.getConstraints();1494 1495 bool outOfBounds = false;1496 unsigned rank = loadOrStoreOp.getMemRefType().getRank();1497 1498 // For each dimension, check for out of bounds.1499 for (unsigned r = 0; r < rank; r++) {1500 FlatAffineValueConstraints ucst(*region.getConstraints());1501 1502 // Intersect memory region with constraint capturing out of bounds (both out1503 // of upper and out of lower), and check if the constraint system is1504 // feasible. If it is, there is at least one point out of bounds.1505 SmallVector<int64_t, 4> ineq(rank + 1, 0);1506 int64_t dimSize = loadOrStoreOp.getMemRefType().getDimSize(r);1507 // TODO: handle dynamic dim sizes.1508 if (dimSize == -1)1509 continue;1510 1511 // Check for overflow: d_i >= memref dim size.1512 ucst.addBound(BoundType::LB, r, dimSize);1513 outOfBounds = !ucst.isEmpty();1514 if (outOfBounds && emitError) {1515 loadOrStoreOp.emitOpError()1516 << "memref out of upper bound access along dimension #" << (r + 1);1517 }1518 1519 // Check for a negative index.1520 FlatAffineValueConstraints lcst(*region.getConstraints());1521 llvm::fill(ineq, 0);1522 // d_i <= -1;1523 lcst.addBound(BoundType::UB, r, -1);1524 outOfBounds = !lcst.isEmpty();1525 if (outOfBounds && emitError) {1526 loadOrStoreOp.emitOpError()1527 << "memref out of lower bound access along dimension #" << (r + 1);1528 }1529 }1530 return failure(outOfBounds);1531}1532 1533// Explicitly instantiate the template so that the compiler knows we need them!1534template LogicalResult1535mlir::affine::boundCheckLoadOrStoreOp(AffineReadOpInterface loadOp,1536 bool emitError);1537template LogicalResult1538mlir::affine::boundCheckLoadOrStoreOp(AffineWriteOpInterface storeOp,1539 bool emitError);1540 1541// Returns in 'positions' the Block positions of 'op' in each ancestor1542// Block from the Block containing operation, stopping at 'limitBlock'.1543static void findInstPosition(Operation *op, Block *limitBlock,1544 SmallVectorImpl<unsigned> *positions) {1545 Block *block = op->getBlock();1546 while (block != limitBlock) {1547 // FIXME: This algorithm is unnecessarily O(n) and should be improved to not1548 // rely on linear scans.1549 int instPosInBlock = std::distance(block->begin(), op->getIterator());1550 positions->push_back(instPosInBlock);1551 op = block->getParentOp();1552 block = op->getBlock();1553 }1554 std::reverse(positions->begin(), positions->end());1555}1556 1557// Returns the Operation in a possibly nested set of Blocks, where the1558// position of the operation is represented by 'positions', which has a1559// Block position for each level of nesting.1560static Operation *getInstAtPosition(ArrayRef<unsigned> positions,1561 unsigned level, Block *block) {1562 unsigned i = 0;1563 for (auto &op : *block) {1564 if (i != positions[level]) {1565 ++i;1566 continue;1567 }1568 if (level == positions.size() - 1)1569 return &op;1570 if (auto childAffineForOp = dyn_cast<AffineForOp>(op))1571 return getInstAtPosition(positions, level + 1,1572 childAffineForOp.getBody());1573 1574 for (auto ®ion : op.getRegions()) {1575 for (auto &b : region)1576 if (auto *ret = getInstAtPosition(positions, level + 1, &b))1577 return ret;1578 }1579 return nullptr;1580 }1581 return nullptr;1582}1583 1584// Adds loop IV bounds to 'cst' for loop IVs not found in 'ivs'.1585static LogicalResult addMissingLoopIVBounds(SmallPtrSet<Value, 8> &ivs,1586 FlatAffineValueConstraints *cst) {1587 for (unsigned i = 0, e = cst->getNumDimVars(); i < e; ++i) {1588 auto value = cst->getValue(i);1589 if (ivs.count(value) == 0) {1590 assert(isAffineForInductionVar(value));1591 auto loop = getForInductionVarOwner(value);1592 if (failed(cst->addAffineForOpDomain(loop)))1593 return failure();1594 }1595 }1596 return success();1597}1598 1599/// Returns the innermost common loop depth for the set of operations in 'ops'.1600// TODO: Move this to LoopUtils.1601unsigned mlir::affine::getInnermostCommonLoopDepth(1602 ArrayRef<Operation *> ops, SmallVectorImpl<AffineForOp> *surroundingLoops) {1603 unsigned numOps = ops.size();1604 assert(numOps > 0 && "Expected at least one operation");1605 1606 std::vector<SmallVector<AffineForOp, 4>> loops(numOps);1607 unsigned loopDepthLimit = std::numeric_limits<unsigned>::max();1608 for (unsigned i = 0; i < numOps; ++i) {1609 getAffineForIVs(*ops[i], &loops[i]);1610 loopDepthLimit =1611 std::min(loopDepthLimit, static_cast<unsigned>(loops[i].size()));1612 }1613 1614 unsigned loopDepth = 0;1615 for (unsigned d = 0; d < loopDepthLimit; ++d) {1616 unsigned i;1617 for (i = 1; i < numOps; ++i) {1618 if (loops[i - 1][d] != loops[i][d])1619 return loopDepth;1620 }1621 if (surroundingLoops)1622 surroundingLoops->push_back(loops[i - 1][d]);1623 ++loopDepth;1624 }1625 return loopDepth;1626}1627 1628/// Computes in 'sliceUnion' the union of all slice bounds computed at1629/// 'loopDepth' between all dependent pairs of ops in 'opsA' and 'opsB', and1630/// then verifies if it is valid. Returns 'SliceComputationResult::Success' if1631/// union was computed correctly, an appropriate failure otherwise.1632SliceComputationResult1633mlir::affine::computeSliceUnion(ArrayRef<Operation *> opsA,1634 ArrayRef<Operation *> opsB, unsigned loopDepth,1635 unsigned numCommonLoops, bool isBackwardSlice,1636 ComputationSliceState *sliceUnion) {1637 // Compute the union of slice bounds between all pairs in 'opsA' and1638 // 'opsB' in 'sliceUnionCst'.1639 FlatAffineValueConstraints sliceUnionCst;1640 assert(sliceUnionCst.getNumDimAndSymbolVars() == 0);1641 std::vector<std::pair<Operation *, Operation *>> dependentOpPairs;1642 MemRefAccess srcAccess;1643 MemRefAccess dstAccess;1644 for (Operation *a : opsA) {1645 srcAccess = MemRefAccess(a);1646 for (Operation *b : opsB) {1647 dstAccess = MemRefAccess(b);1648 if (srcAccess.memref != dstAccess.memref)1649 continue;1650 // Check if 'loopDepth' exceeds nesting depth of src/dst ops.1651 if ((!isBackwardSlice && loopDepth > getNestingDepth(a)) ||1652 (isBackwardSlice && loopDepth > getNestingDepth(b))) {1653 LDBG() << "Invalid loop depth";1654 return SliceComputationResult::GenericFailure;1655 }1656 1657 bool readReadAccesses = isa<AffineReadOpInterface>(srcAccess.opInst) &&1658 isa<AffineReadOpInterface>(dstAccess.opInst);1659 FlatAffineValueConstraints dependenceConstraints;1660 // Check dependence between 'srcAccess' and 'dstAccess'.1661 DependenceResult result = checkMemrefAccessDependence(1662 srcAccess, dstAccess, /*loopDepth=*/numCommonLoops + 1,1663 &dependenceConstraints, /*dependenceComponents=*/nullptr,1664 /*allowRAR=*/readReadAccesses);1665 if (result.value == DependenceResult::Failure) {1666 LDBG() << "Dependence check failed";1667 return SliceComputationResult::GenericFailure;1668 }1669 if (result.value == DependenceResult::NoDependence)1670 continue;1671 dependentOpPairs.emplace_back(a, b);1672 1673 // Compute slice bounds for 'srcAccess' and 'dstAccess'.1674 ComputationSliceState tmpSliceState;1675 getComputationSliceState(a, b, dependenceConstraints, loopDepth,1676 isBackwardSlice, &tmpSliceState);1677 1678 if (sliceUnionCst.getNumDimAndSymbolVars() == 0) {1679 // Initialize 'sliceUnionCst' with the bounds computed in previous step.1680 if (failed(tmpSliceState.getAsConstraints(&sliceUnionCst))) {1681 LDBG() << "Unable to compute slice bound constraints";1682 return SliceComputationResult::GenericFailure;1683 }1684 assert(sliceUnionCst.getNumDimAndSymbolVars() > 0);1685 continue;1686 }1687 1688 // Compute constraints for 'tmpSliceState' in 'tmpSliceCst'.1689 FlatAffineValueConstraints tmpSliceCst;1690 if (failed(tmpSliceState.getAsConstraints(&tmpSliceCst))) {1691 LDBG() << "Unable to compute slice bound constraints";1692 return SliceComputationResult::GenericFailure;1693 }1694 1695 // Align coordinate spaces of 'sliceUnionCst' and 'tmpSliceCst' if needed.1696 if (!sliceUnionCst.areVarsAlignedWithOther(tmpSliceCst)) {1697 1698 // Pre-constraint var alignment: record loop IVs used in each constraint1699 // system.1700 SmallPtrSet<Value, 8> sliceUnionIVs;1701 for (unsigned k = 0, l = sliceUnionCst.getNumDimVars(); k < l; ++k)1702 sliceUnionIVs.insert(sliceUnionCst.getValue(k));1703 SmallPtrSet<Value, 8> tmpSliceIVs;1704 for (unsigned k = 0, l = tmpSliceCst.getNumDimVars(); k < l; ++k)1705 tmpSliceIVs.insert(tmpSliceCst.getValue(k));1706 1707 sliceUnionCst.mergeAndAlignVarsWithOther(/*offset=*/0, &tmpSliceCst);1708 1709 // Post-constraint var alignment: add loop IV bounds missing after1710 // var alignment to constraint systems. This can occur if one constraint1711 // system uses an loop IV that is not used by the other. The call1712 // to unionBoundingBox below expects constraints for each Loop IV, even1713 // if they are the unsliced full loop bounds added here.1714 if (failed(addMissingLoopIVBounds(sliceUnionIVs, &sliceUnionCst)))1715 return SliceComputationResult::GenericFailure;1716 if (failed(addMissingLoopIVBounds(tmpSliceIVs, &tmpSliceCst)))1717 return SliceComputationResult::GenericFailure;1718 }1719 // Compute union bounding box of 'sliceUnionCst' and 'tmpSliceCst'.1720 if (sliceUnionCst.getNumLocalVars() > 0 ||1721 tmpSliceCst.getNumLocalVars() > 0 ||1722 failed(sliceUnionCst.unionBoundingBox(tmpSliceCst))) {1723 LDBG() << "Unable to compute union bounding box of slice bounds";1724 return SliceComputationResult::GenericFailure;1725 }1726 }1727 }1728 1729 // Empty union.1730 if (sliceUnionCst.getNumDimAndSymbolVars() == 0) {1731 LDBG() << "empty slice union - unexpected";1732 return SliceComputationResult::GenericFailure;1733 }1734 1735 // Gather loops surrounding ops from loop nest where slice will be inserted.1736 SmallVector<Operation *, 4> ops;1737 for (auto &dep : dependentOpPairs) {1738 ops.push_back(isBackwardSlice ? dep.second : dep.first);1739 }1740 SmallVector<AffineForOp, 4> surroundingLoops;1741 unsigned innermostCommonLoopDepth =1742 getInnermostCommonLoopDepth(ops, &surroundingLoops);1743 if (loopDepth > innermostCommonLoopDepth) {1744 LDBG() << "Exceeds max loop depth";1745 return SliceComputationResult::GenericFailure;1746 }1747 1748 // Store 'numSliceLoopIVs' before converting dst loop IVs to dims.1749 unsigned numSliceLoopIVs = sliceUnionCst.getNumDimVars();1750 1751 // Convert any dst loop IVs which are symbol variables to dim variables.1752 sliceUnionCst.convertLoopIVSymbolsToDims();1753 sliceUnion->clearBounds();1754 sliceUnion->lbs.resize(numSliceLoopIVs, AffineMap());1755 sliceUnion->ubs.resize(numSliceLoopIVs, AffineMap());1756 1757 // Get slice bounds from slice union constraints 'sliceUnionCst'.1758 sliceUnionCst.getSliceBounds(/*offset=*/0, numSliceLoopIVs,1759 opsA[0]->getContext(), &sliceUnion->lbs,1760 &sliceUnion->ubs);1761 1762 // Add slice bound operands of union.1763 SmallVector<Value, 4> sliceBoundOperands;1764 sliceUnionCst.getValues(numSliceLoopIVs,1765 sliceUnionCst.getNumDimAndSymbolVars(),1766 &sliceBoundOperands);1767 1768 // Copy src loop IVs from 'sliceUnionCst' to 'sliceUnion'.1769 sliceUnion->ivs.clear();1770 sliceUnionCst.getValues(0, numSliceLoopIVs, &sliceUnion->ivs);1771 1772 // Set loop nest insertion point to block start at 'loopDepth' for forward1773 // slices, while at the end for backward slices.1774 sliceUnion->insertPoint =1775 isBackwardSlice1776 ? surroundingLoops[loopDepth - 1].getBody()->begin()1777 : std::prev(surroundingLoops[loopDepth - 1].getBody()->end());1778 1779 // Give each bound its own copy of 'sliceBoundOperands' for subsequent1780 // canonicalization.1781 sliceUnion->lbOperands.resize(numSliceLoopIVs, sliceBoundOperands);1782 sliceUnion->ubOperands.resize(numSliceLoopIVs, sliceBoundOperands);1783 1784 // Check if the slice computed is valid. Return success only if it is verified1785 // that the slice is valid, otherwise return appropriate failure status.1786 std::optional<bool> isSliceValid = sliceUnion->isSliceValid();1787 if (!isSliceValid) {1788 LDBG() << "Cannot determine if the slice is valid";1789 return SliceComputationResult::GenericFailure;1790 }1791 if (!*isSliceValid)1792 return SliceComputationResult::IncorrectSliceFailure;1793 1794 return SliceComputationResult::Success;1795}1796 1797// TODO: extend this to handle multiple result maps.1798static std::optional<uint64_t> getConstDifference(AffineMap lbMap,1799 AffineMap ubMap) {1800 assert(lbMap.getNumResults() == 1 && "expected single result bound map");1801 assert(ubMap.getNumResults() == 1 && "expected single result bound map");1802 assert(lbMap.getNumDims() == ubMap.getNumDims());1803 assert(lbMap.getNumSymbols() == ubMap.getNumSymbols());1804 AffineExpr lbExpr(lbMap.getResult(0));1805 AffineExpr ubExpr(ubMap.getResult(0));1806 auto loopSpanExpr = simplifyAffineExpr(ubExpr - lbExpr, lbMap.getNumDims(),1807 lbMap.getNumSymbols());1808 auto cExpr = dyn_cast<AffineConstantExpr>(loopSpanExpr);1809 if (!cExpr)1810 return std::nullopt;1811 return cExpr.getValue();1812}1813 1814// Builds a map 'tripCountMap' from AffineForOp to constant trip count for loop1815// nest surrounding represented by slice loop bounds in 'slice'. Returns true1816// on success, false otherwise (if a non-constant trip count was encountered).1817// TODO: Make this work with non-unit step loops.1818bool mlir::affine::buildSliceTripCountMap(1819 const ComputationSliceState &slice,1820 llvm::SmallDenseMap<Operation *, uint64_t, 8> *tripCountMap) {1821 unsigned numSrcLoopIVs = slice.ivs.size();1822 // Populate map from AffineForOp -> trip count1823 for (unsigned i = 0; i < numSrcLoopIVs; ++i) {1824 AffineForOp forOp = getForInductionVarOwner(slice.ivs[i]);1825 auto *op = forOp.getOperation();1826 AffineMap lbMap = slice.lbs[i];1827 AffineMap ubMap = slice.ubs[i];1828 // If lower or upper bound maps are null or provide no results, it implies1829 // that source loop was not at all sliced, and the entire loop will be a1830 // part of the slice.1831 if (!lbMap || lbMap.getNumResults() == 0 || !ubMap ||1832 ubMap.getNumResults() == 0) {1833 // The iteration of src loop IV 'i' was not sliced. Use full loop bounds.1834 if (forOp.hasConstantLowerBound() && forOp.hasConstantUpperBound()) {1835 (*tripCountMap)[op] =1836 forOp.getConstantUpperBound() - forOp.getConstantLowerBound();1837 continue;1838 }1839 std::optional<uint64_t> maybeConstTripCount = getConstantTripCount(forOp);1840 if (maybeConstTripCount.has_value()) {1841 (*tripCountMap)[op] = *maybeConstTripCount;1842 continue;1843 }1844 return false;1845 }1846 std::optional<uint64_t> tripCount = getConstDifference(lbMap, ubMap);1847 // Slice bounds are created with a constant ub - lb difference.1848 if (!tripCount.has_value())1849 return false;1850 (*tripCountMap)[op] = *tripCount;1851 }1852 return true;1853}1854 1855// Return the number of iterations in the given slice.1856uint64_t mlir::affine::getSliceIterationCount(1857 const llvm::SmallDenseMap<Operation *, uint64_t, 8> &sliceTripCountMap) {1858 uint64_t iterCount = 1;1859 for (const auto &count : sliceTripCountMap) {1860 iterCount *= count.second;1861 }1862 return iterCount;1863}1864 1865const char *const kSliceFusionBarrierAttrName = "slice_fusion_barrier";1866// Computes slice bounds by projecting out any loop IVs from1867// 'dependenceConstraints' at depth greater than 'loopDepth', and computes slice1868// bounds in 'sliceState' which represent the one loop nest's IVs in terms of1869// the other loop nest's IVs, symbols and constants (using 'isBackwardsSlice').1870void mlir::affine::getComputationSliceState(1871 Operation *depSourceOp, Operation *depSinkOp,1872 const FlatAffineValueConstraints &dependenceConstraints, unsigned loopDepth,1873 bool isBackwardSlice, ComputationSliceState *sliceState) {1874 // Get loop nest surrounding src operation.1875 SmallVector<AffineForOp, 4> srcLoopIVs;1876 getAffineForIVs(*depSourceOp, &srcLoopIVs);1877 unsigned numSrcLoopIVs = srcLoopIVs.size();1878 1879 // Get loop nest surrounding dst operation.1880 SmallVector<AffineForOp, 4> dstLoopIVs;1881 getAffineForIVs(*depSinkOp, &dstLoopIVs);1882 unsigned numDstLoopIVs = dstLoopIVs.size();1883 1884 assert((!isBackwardSlice && loopDepth <= numSrcLoopIVs) ||1885 (isBackwardSlice && loopDepth <= numDstLoopIVs));1886 1887 // Project out dimensions other than those up to 'loopDepth'.1888 unsigned pos = isBackwardSlice ? numSrcLoopIVs + loopDepth : loopDepth;1889 unsigned num =1890 isBackwardSlice ? numDstLoopIVs - loopDepth : numSrcLoopIVs - loopDepth;1891 FlatAffineValueConstraints sliceCst(dependenceConstraints);1892 sliceCst.projectOut(pos, num);1893 1894 // Add slice loop IV values to 'sliceState'.1895 unsigned offset = isBackwardSlice ? 0 : loopDepth;1896 unsigned numSliceLoopIVs = isBackwardSlice ? numSrcLoopIVs : numDstLoopIVs;1897 sliceCst.getValues(offset, offset + numSliceLoopIVs, &sliceState->ivs);1898 1899 // Set up lower/upper bound affine maps for the slice.1900 sliceState->lbs.resize(numSliceLoopIVs, AffineMap());1901 sliceState->ubs.resize(numSliceLoopIVs, AffineMap());1902 1903 // Get bounds for slice IVs in terms of other IVs, symbols, and constants.1904 sliceCst.getSliceBounds(offset, numSliceLoopIVs, depSourceOp->getContext(),1905 &sliceState->lbs, &sliceState->ubs);1906 1907 // Set up bound operands for the slice's lower and upper bounds.1908 SmallVector<Value, 4> sliceBoundOperands;1909 unsigned numDimsAndSymbols = sliceCst.getNumDimAndSymbolVars();1910 for (unsigned i = 0; i < numDimsAndSymbols; ++i) {1911 if (i < offset || i >= offset + numSliceLoopIVs)1912 sliceBoundOperands.push_back(sliceCst.getValue(i));1913 }1914 1915 // Give each bound its own copy of 'sliceBoundOperands' for subsequent1916 // canonicalization.1917 sliceState->lbOperands.resize(numSliceLoopIVs, sliceBoundOperands);1918 sliceState->ubOperands.resize(numSliceLoopIVs, sliceBoundOperands);1919 1920 // Set destination loop nest insertion point to block start at 'dstLoopDepth'.1921 sliceState->insertPoint =1922 isBackwardSlice ? dstLoopIVs[loopDepth - 1].getBody()->begin()1923 : std::prev(srcLoopIVs[loopDepth - 1].getBody()->end());1924 1925 llvm::SmallDenseSet<Value, 8> sequentialLoops;1926 if (isa<AffineReadOpInterface>(depSourceOp) &&1927 isa<AffineReadOpInterface>(depSinkOp)) {1928 // For read-read access pairs, clear any slice bounds on sequential loops.1929 // Get sequential loops in loop nest rooted at 'srcLoopIVs[0]'.1930 getSequentialLoops(isBackwardSlice ? srcLoopIVs[0] : dstLoopIVs[0],1931 &sequentialLoops);1932 }1933 auto getSliceLoop = [&](unsigned i) {1934 return isBackwardSlice ? srcLoopIVs[i] : dstLoopIVs[i];1935 };1936 auto isInnermostInsertion = [&]() {1937 return (isBackwardSlice ? loopDepth >= srcLoopIVs.size()1938 : loopDepth >= dstLoopIVs.size());1939 };1940 llvm::SmallDenseMap<Operation *, uint64_t, 8> sliceTripCountMap;1941 auto srcIsUnitSlice = [&]() {1942 return (buildSliceTripCountMap(*sliceState, &sliceTripCountMap) &&1943 (getSliceIterationCount(sliceTripCountMap) == 1));1944 };1945 // Clear all sliced loop bounds beginning at the first sequential loop, or1946 // first loop with a slice fusion barrier attribute..1947 1948 for (unsigned i = 0; i < numSliceLoopIVs; ++i) {1949 Value iv = getSliceLoop(i).getInductionVar();1950 if (sequentialLoops.count(iv) == 0 &&1951 getSliceLoop(i)->getAttr(kSliceFusionBarrierAttrName) == nullptr)1952 continue;1953 // Skip reset of bounds of reduction loop inserted in the destination loop1954 // that meets the following conditions:1955 // 1. Slice is single trip count.1956 // 2. Loop bounds of the source and destination match.1957 // 3. Is being inserted at the innermost insertion point.1958 std::optional<bool> isMaximal = sliceState->isMaximal();1959 if (isLoopParallelAndContainsReduction(getSliceLoop(i)) &&1960 isInnermostInsertion() && srcIsUnitSlice() && isMaximal && *isMaximal)1961 continue;1962 for (unsigned j = i; j < numSliceLoopIVs; ++j) {1963 sliceState->lbs[j] = AffineMap();1964 sliceState->ubs[j] = AffineMap();1965 }1966 break;1967 }1968}1969 1970/// Creates a computation slice of the loop nest surrounding 'srcOpInst',1971/// updates the slice loop bounds with any non-null bound maps specified in1972/// 'sliceState', and inserts this slice into the loop nest surrounding1973/// 'dstOpInst' at loop depth 'dstLoopDepth'.1974// TODO: extend the slicing utility to compute slices that1975// aren't necessarily a one-to-one relation b/w the source and destination. The1976// relation between the source and destination could be many-to-many in general.1977// TODO: the slice computation is incorrect in the cases1978// where the dependence from the source to the destination does not cover the1979// entire destination index set. Subtract out the dependent destination1980// iterations from destination index set and check for emptiness --- this is one1981// solution.1982AffineForOp mlir::affine::insertBackwardComputationSlice(1983 Operation *srcOpInst, Operation *dstOpInst, unsigned dstLoopDepth,1984 ComputationSliceState *sliceState) {1985 // Get loop nest surrounding src operation.1986 SmallVector<AffineForOp, 4> srcLoopIVs;1987 getAffineForIVs(*srcOpInst, &srcLoopIVs);1988 unsigned numSrcLoopIVs = srcLoopIVs.size();1989 1990 // Get loop nest surrounding dst operation.1991 SmallVector<AffineForOp, 4> dstLoopIVs;1992 getAffineForIVs(*dstOpInst, &dstLoopIVs);1993 unsigned dstLoopIVsSize = dstLoopIVs.size();1994 if (dstLoopDepth > dstLoopIVsSize) {1995 dstOpInst->emitError("invalid destination loop depth");1996 return AffineForOp();1997 }1998 1999 // Find the op block positions of 'srcOpInst' within 'srcLoopIVs'.2000 SmallVector<unsigned, 4> positions;2001 // TODO: This code is incorrect since srcLoopIVs can be 0-d.2002 findInstPosition(srcOpInst, srcLoopIVs[0]->getBlock(), &positions);2003 2004 // Clone src loop nest and insert it a the beginning of the operation block2005 // of the loop at 'dstLoopDepth' in 'dstLoopIVs'.2006 auto dstAffineForOp = dstLoopIVs[dstLoopDepth - 1];2007 OpBuilder b(dstAffineForOp.getBody(), dstAffineForOp.getBody()->begin());2008 auto sliceLoopNest =2009 cast<AffineForOp>(b.clone(*srcLoopIVs[0].getOperation()));2010 2011 Operation *sliceInst =2012 getInstAtPosition(positions, /*level=*/0, sliceLoopNest.getBody());2013 // Get loop nest surrounding 'sliceInst'.2014 SmallVector<AffineForOp, 4> sliceSurroundingLoops;2015 getAffineForIVs(*sliceInst, &sliceSurroundingLoops);2016 2017 // Sanity check.2018 unsigned sliceSurroundingLoopsSize = sliceSurroundingLoops.size();2019 (void)sliceSurroundingLoopsSize;2020 assert(dstLoopDepth + numSrcLoopIVs >= sliceSurroundingLoopsSize);2021 unsigned sliceLoopLimit = dstLoopDepth + numSrcLoopIVs;2022 (void)sliceLoopLimit;2023 assert(sliceLoopLimit >= sliceSurroundingLoopsSize);2024 2025 // Update loop bounds for loops in 'sliceLoopNest'.2026 for (unsigned i = 0; i < numSrcLoopIVs; ++i) {2027 auto forOp = sliceSurroundingLoops[dstLoopDepth + i];2028 if (AffineMap lbMap = sliceState->lbs[i])2029 forOp.setLowerBound(sliceState->lbOperands[i], lbMap);2030 if (AffineMap ubMap = sliceState->ubs[i])2031 forOp.setUpperBound(sliceState->ubOperands[i], ubMap);2032 }2033 return sliceLoopNest;2034}2035 2036// Constructs MemRefAccess populating it with the memref, its indices and2037// opinst from 'loadOrStoreOpInst'.2038MemRefAccess::MemRefAccess(Operation *memOp) {2039 if (auto loadOp = dyn_cast<AffineReadOpInterface>(memOp)) {2040 memref = loadOp.getMemRef();2041 opInst = memOp;2042 llvm::append_range(indices, loadOp.getMapOperands());2043 } else {2044 assert(isa<AffineWriteOpInterface>(memOp) &&2045 "Affine read/write op expected");2046 auto storeOp = cast<AffineWriteOpInterface>(memOp);2047 opInst = memOp;2048 memref = storeOp.getMemRef();2049 llvm::append_range(indices, storeOp.getMapOperands());2050 }2051}2052 2053unsigned MemRefAccess::getRank() const {2054 return cast<MemRefType>(memref.getType()).getRank();2055}2056 2057bool MemRefAccess::isStore() const {2058 return isa<AffineWriteOpInterface>(opInst);2059}2060 2061/// Returns the nesting depth of this statement, i.e., the number of loops2062/// surrounding this statement.2063unsigned mlir::affine::getNestingDepth(Operation *op) {2064 Operation *currOp = op;2065 unsigned depth = 0;2066 while ((currOp = currOp->getParentOp())) {2067 if (isa<AffineForOp>(currOp))2068 depth++;2069 if (auto parOp = dyn_cast<AffineParallelOp>(currOp))2070 depth += parOp.getNumDims();2071 }2072 return depth;2073}2074 2075/// Equal if both affine accesses are provably equivalent (at compile2076/// time) when considering the memref, the affine maps and their respective2077/// operands. The equality of access functions + operands is checked by2078/// subtracting fully composed value maps, and then simplifying the difference2079/// using the expression flattener.2080/// TODO: this does not account for aliasing of memrefs.2081bool MemRefAccess::operator==(const MemRefAccess &rhs) const {2082 if (memref != rhs.memref)2083 return false;2084 2085 AffineValueMap diff, thisMap, rhsMap;2086 getAccessMap(&thisMap);2087 rhs.getAccessMap(&rhsMap);2088 return thisMap == rhsMap;2089}2090 2091void mlir::affine::getAffineIVs(Operation &op, SmallVectorImpl<Value> &ivs) {2092 auto *currOp = op.getParentOp();2093 AffineForOp currAffineForOp;2094 // Traverse up the hierarchy collecting all 'affine.for' and affine.parallel2095 // operation while skipping over 'affine.if' operations.2096 while (currOp) {2097 if (AffineForOp currAffineForOp = dyn_cast<AffineForOp>(currOp))2098 ivs.push_back(currAffineForOp.getInductionVar());2099 else if (auto parOp = dyn_cast<AffineParallelOp>(currOp))2100 llvm::append_range(ivs, parOp.getIVs());2101 currOp = currOp->getParentOp();2102 }2103 std::reverse(ivs.begin(), ivs.end());2104}2105 2106/// Returns the number of surrounding loops common to 'loopsA' and 'loopsB',2107/// where each lists loops from outer-most to inner-most in loop nest.2108unsigned mlir::affine::getNumCommonSurroundingLoops(Operation &a,2109 Operation &b) {2110 SmallVector<Value, 4> loopsA, loopsB;2111 getAffineIVs(a, loopsA);2112 getAffineIVs(b, loopsB);2113 2114 unsigned minNumLoops = std::min(loopsA.size(), loopsB.size());2115 unsigned numCommonLoops = 0;2116 for (unsigned i = 0; i < minNumLoops; ++i) {2117 if (loopsA[i] != loopsB[i])2118 break;2119 ++numCommonLoops;2120 }2121 return numCommonLoops;2122}2123 2124static std::optional<int64_t> getMemoryFootprintBytes(Block &block,2125 Block::iterator start,2126 Block::iterator end,2127 int memorySpace) {2128 SmallDenseMap<Value, std::unique_ptr<MemRefRegion>, 4> regions;2129 2130 // Walk this 'affine.for' operation to gather all memory regions.2131 auto result = block.walk(start, end, [&](Operation *opInst) -> WalkResult {2132 if (!isa<AffineReadOpInterface, AffineWriteOpInterface>(opInst)) {2133 // Neither load nor a store op.2134 return WalkResult::advance();2135 }2136 2137 // Compute the memref region symbolic in any IVs enclosing this block.2138 auto region = std::make_unique<MemRefRegion>(opInst->getLoc());2139 if (failed(2140 region->compute(opInst,2141 /*loopDepth=*/getNestingDepth(&*block.begin())))) {2142 LDBG() << "Error obtaining memory region";2143 opInst->emitError("error obtaining memory region");2144 return failure();2145 }2146 2147 auto [it, inserted] = regions.try_emplace(region->memref);2148 if (inserted) {2149 it->second = std::move(region);2150 } else if (failed(it->second->unionBoundingBox(*region))) {2151 LDBG() << "getMemoryFootprintBytes: unable to perform a union on a "2152 "memory region";2153 opInst->emitWarning(2154 "getMemoryFootprintBytes: unable to perform a union on a memory "2155 "region");2156 return failure();2157 }2158 return WalkResult::advance();2159 });2160 if (result.wasInterrupted())2161 return std::nullopt;2162 2163 int64_t totalSizeInBytes = 0;2164 for (const auto ®ion : regions) {2165 std::optional<int64_t> size = region.second->getRegionSize();2166 if (!size.has_value())2167 return std::nullopt;2168 totalSizeInBytes += *size;2169 }2170 return totalSizeInBytes;2171}2172 2173std::optional<int64_t> mlir::affine::getMemoryFootprintBytes(AffineForOp forOp,2174 int memorySpace) {2175 auto *forInst = forOp.getOperation();2176 return ::getMemoryFootprintBytes(2177 *forInst->getBlock(), Block::iterator(forInst),2178 std::next(Block::iterator(forInst)), memorySpace);2179}2180 2181/// Returns whether a loop is parallel and contains a reduction loop.2182bool mlir::affine::isLoopParallelAndContainsReduction(AffineForOp forOp) {2183 SmallVector<LoopReduction> reductions;2184 if (!isLoopParallel(forOp, &reductions))2185 return false;2186 return !reductions.empty();2187}2188 2189/// Returns in 'sequentialLoops' all sequential loops in loop nest rooted2190/// at 'forOp'.2191void mlir::affine::getSequentialLoops(2192 AffineForOp forOp, llvm::SmallDenseSet<Value, 8> *sequentialLoops) {2193 forOp->walk([&](Operation *op) {2194 if (auto innerFor = dyn_cast<AffineForOp>(op))2195 if (!isLoopParallel(innerFor))2196 sequentialLoops->insert(innerFor.getInductionVar());2197 });2198}2199 2200IntegerSet mlir::affine::simplifyIntegerSet(IntegerSet set) {2201 FlatAffineValueConstraints fac(set);2202 if (fac.isEmpty())2203 return IntegerSet::getEmptySet(set.getNumDims(), set.getNumSymbols(),2204 set.getContext());2205 fac.removeTrivialRedundancy();2206 2207 auto simplifiedSet = fac.getAsIntegerSet(set.getContext());2208 assert(simplifiedSet && "guaranteed to succeed while roundtripping");2209 return simplifiedSet;2210}2211 2212static void unpackOptionalValues(ArrayRef<std::optional<Value>> source,2213 SmallVector<Value> &target) {2214 target =2215 llvm::to_vector<4>(llvm::map_range(source, [](std::optional<Value> val) {2216 return val.has_value() ? *val : Value();2217 }));2218}2219 2220/// Bound an identifier `pos` in a given FlatAffineValueConstraints with2221/// constraints drawn from an affine map. Before adding the constraint, the2222/// dimensions/symbols of the affine map are aligned with `constraints`.2223/// `operands` are the SSA Value operands used with the affine map.2224/// Note: This function adds a new symbol column to the `constraints` for each2225/// dimension/symbol that exists in the affine map but not in `constraints`.2226static LogicalResult alignAndAddBound(FlatAffineValueConstraints &constraints,2227 BoundType type, unsigned pos,2228 AffineMap map, ValueRange operands) {2229 SmallVector<Value> dims, syms, newSyms;2230 unpackOptionalValues(constraints.getMaybeValues(VarKind::SetDim), dims);2231 unpackOptionalValues(constraints.getMaybeValues(VarKind::Symbol), syms);2232 2233 AffineMap alignedMap =2234 alignAffineMapWithValues(map, operands, dims, syms, &newSyms);2235 for (unsigned i = syms.size(); i < newSyms.size(); ++i)2236 constraints.appendSymbolVar(newSyms[i]);2237 return constraints.addBound(type, pos, alignedMap);2238}2239 2240/// Add `val` to each result of `map`.2241static AffineMap addConstToResults(AffineMap map, int64_t val) {2242 SmallVector<AffineExpr> newResults;2243 for (AffineExpr r : map.getResults())2244 newResults.push_back(r + val);2245 return AffineMap::get(map.getNumDims(), map.getNumSymbols(), newResults,2246 map.getContext());2247}2248 2249// Attempt to simplify the given min/max operation by proving that its value is2250// bounded by the same lower and upper bound.2251//2252// Bounds are computed by FlatAffineValueConstraints. Invariants required for2253// finding/proving bounds should be supplied via `constraints`.2254//2255// 1. Add dimensions for `op` and `opBound` (lower or upper bound of `op`).2256// 2. Compute an upper bound of `op` (in case of `isMin`) or a lower bound (in2257// case of `!isMin`) and bind it to `opBound`. SSA values that are used in2258// `op` but are not part of `constraints`, are added as extra symbols.2259// 3. For each result of `op`: Add result as a dimension `r_i`. Prove that:2260// * If `isMin`: r_i >= opBound2261// * If `isMax`: r_i <= opBound2262// If this is the case, ub(op) == lb(op).2263// 4. Replace `op` with `opBound`.2264//2265// In summary, the following constraints are added throughout this function.2266// Note: `invar` are dimensions added by the caller to express the invariants.2267// (Showing only the case where `isMin`.)2268//2269// invar | op | opBound | r_i | extra syms... | const | eq/ineq2270// ------+-------+---------+-----+---------------+-------+-------------------2271// (various eq./ineq. constraining `invar`, added by the caller)2272// ... | 0 | 0 | 0 | 0 | ... | ...2273// ------+-------+---------+-----+---------------+-------+-------------------2274// (various ineq. constraining `op` in terms of `op` operands (`invar` and2275// extra `op` operands "extra syms" that are not in `invar`)).2276// ... | -1 | 0 | 0 | ... | ... | >= 02277// ------+-------+---------+-----+---------------+-------+-------------------2278// (set `opBound` to `op` upper bound in terms of `invar` and "extra syms")2279// ... | 0 | -1 | 0 | ... | ... | = 02280// ------+-------+---------+-----+---------------+-------+-------------------2281// (for each `op` map result r_i: set r_i to corresponding map result,2282// prove that r_i >= minOpUb via contradiction)2283// ... | 0 | 0 | -1 | ... | ... | = 02284// 0 | 0 | 1 | -1 | 0 | -1 | >= 02285//2286FailureOr<AffineValueMap> mlir::affine::simplifyConstrainedMinMaxOp(2287 Operation *op, FlatAffineValueConstraints constraints) {2288 bool isMin = isa<AffineMinOp>(op);2289 assert((isMin || isa<AffineMaxOp>(op)) && "expect AffineMin/MaxOp");2290 MLIRContext *ctx = op->getContext();2291 Builder builder(ctx);2292 AffineMap map =2293 isMin ? cast<AffineMinOp>(op).getMap() : cast<AffineMaxOp>(op).getMap();2294 ValueRange operands = op->getOperands();2295 unsigned numResults = map.getNumResults();2296 2297 // Add a few extra dimensions.2298 unsigned dimOp = constraints.appendDimVar(); // `op`2299 unsigned dimOpBound = constraints.appendDimVar(); // `op` lower/upper bound2300 unsigned resultDimStart = constraints.appendDimVar(/*num=*/numResults);2301 2302 // Add an inequality for each result expr_i of map:2303 // isMin: op <= expr_i, !isMin: op >= expr_i2304 auto boundType = isMin ? BoundType::UB : BoundType::LB;2305 // Upper bounds are exclusive, so add 1. (`affine.min` ops are inclusive.)2306 AffineMap mapLbUb = isMin ? addConstToResults(map, 1) : map;2307 if (failed(2308 alignAndAddBound(constraints, boundType, dimOp, mapLbUb, operands)))2309 return failure();2310 2311 // Try to compute a lower/upper bound for op, expressed in terms of the other2312 // `dims` and extra symbols.2313 SmallVector<AffineMap> opLb(1), opUb(1);2314 constraints.getSliceBounds(dimOp, 1, ctx, &opLb, &opUb);2315 AffineMap sliceBound = isMin ? opUb[0] : opLb[0];2316 // TODO: `getSliceBounds` may return multiple bounds at the moment. This is2317 // a TODO of `getSliceBounds` and not handled here.2318 if (!sliceBound || sliceBound.getNumResults() != 1)2319 return failure(); // No or multiple bounds found.2320 // Recover the inclusive UB in the case of an `affine.min`.2321 AffineMap boundMap = isMin ? addConstToResults(sliceBound, -1) : sliceBound;2322 2323 // Add an equality: Set dimOpBound to computed bound.2324 // Add back dimension for op. (Was removed by `getSliceBounds`.)2325 AffineMap alignedBoundMap = boundMap.shiftDims(/*shift=*/1, /*offset=*/dimOp);2326 if (failed(constraints.addBound(BoundType::EQ, dimOpBound, alignedBoundMap)))2327 return failure();2328 2329 // If the constraint system is empty, there is an inconsistency. (E.g., this2330 // can happen if loop lb > ub.)2331 if (constraints.isEmpty())2332 return failure();2333 2334 // In the case of `isMin` (`!isMin` is inversed):2335 // Prove that each result of `map` has a lower bound that is equal to (or2336 // greater than) the upper bound of `op` (`dimOpBound`). In that case, `op`2337 // can be replaced with the bound. I.e., prove that for each result2338 // expr_i (represented by dimension r_i):2339 //2340 // r_i >= opBound2341 //2342 // To prove this inequality, add its negation to the constraint set and prove2343 // that the constraint set is empty.2344 for (unsigned i = resultDimStart; i < resultDimStart + numResults; ++i) {2345 FlatAffineValueConstraints newConstr(constraints);2346 2347 // Add an equality: r_i = expr_i2348 // Note: These equalities could have been added earlier and used to express2349 // minOp <= expr_i. However, then we run the risk that `getSliceBounds`2350 // computes minOpUb in terms of r_i dims, which is not desired.2351 if (failed(alignAndAddBound(newConstr, BoundType::EQ, i,2352 map.getSubMap({i - resultDimStart}), operands)))2353 return failure();2354 2355 // If `isMin`: Add inequality: r_i < opBound2356 // equiv.: opBound - r_i - 1 >= 02357 // If `!isMin`: Add inequality: r_i > opBound2358 // equiv.: -opBound + r_i - 1 >= 02359 SmallVector<int64_t> ineq(newConstr.getNumCols(), 0);2360 ineq[dimOpBound] = isMin ? 1 : -1;2361 ineq[i] = isMin ? -1 : 1;2362 ineq[newConstr.getNumCols() - 1] = -1;2363 newConstr.addInequality(ineq);2364 if (!newConstr.isEmpty())2365 return failure();2366 }2367 2368 // Lower and upper bound of `op` are equal. Replace `minOp` with its bound.2369 AffineMap newMap = alignedBoundMap;2370 SmallVector<Value> newOperands;2371 unpackOptionalValues(constraints.getMaybeValues(), newOperands);2372 // If dims/symbols have known constant values, use those in order to simplify2373 // the affine map further.2374 for (int64_t i = 0, e = constraints.getNumDimAndSymbolVars(); i < e; ++i) {2375 // Skip unused operands and operands that are already constants.2376 if (!newOperands[i] || getConstantIntValue(newOperands[i]))2377 continue;2378 if (auto bound = constraints.getConstantBound64(BoundType::EQ, i)) {2379 AffineExpr expr =2380 i < newMap.getNumDims()2381 ? builder.getAffineDimExpr(i)2382 : builder.getAffineSymbolExpr(i - newMap.getNumDims());2383 newMap = newMap.replace(expr, builder.getAffineConstantExpr(*bound),2384 newMap.getNumDims(), newMap.getNumSymbols());2385 }2386 }2387 affine::canonicalizeMapAndOperands(&newMap, &newOperands);2388 return AffineValueMap(newMap, newOperands);2389}2390 2391Block *mlir::affine::findInnermostCommonBlockInScope(Operation *a,2392 Operation *b) {2393 Region *aScope = getAffineAnalysisScope(a);2394 Region *bScope = getAffineAnalysisScope(b);2395 if (aScope != bScope)2396 return nullptr;2397 2398 // Get the block ancestry of `op` while stopping at the affine scope `aScope`2399 // and store them in `ancestry`.2400 auto getBlockAncestry = [&](Operation *op,2401 SmallVectorImpl<Block *> &ancestry) {2402 Operation *curOp = op;2403 do {2404 ancestry.push_back(curOp->getBlock());2405 if (curOp->getParentRegion() == aScope)2406 break;2407 curOp = curOp->getParentOp();2408 } while (curOp);2409 assert(curOp && "can't reach root op without passing through affine scope");2410 std::reverse(ancestry.begin(), ancestry.end());2411 };2412 2413 SmallVector<Block *, 4> aAncestors, bAncestors;2414 getBlockAncestry(a, aAncestors);2415 getBlockAncestry(b, bAncestors);2416 assert(!aAncestors.empty() && !bAncestors.empty() &&2417 "at least one Block ancestor expected");2418 2419 Block *innermostCommonBlock = nullptr;2420 for (unsigned a = 0, b = 0, e = aAncestors.size(), f = bAncestors.size();2421 a < e && b < f; ++a, ++b) {2422 if (aAncestors[a] != bAncestors[b])2423 break;2424 innermostCommonBlock = aAncestors[a];2425 }2426 return innermostCommonBlock;2427}2428