//===- AffineAnalysis.cpp - Affine structures analysis routines -----------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // This file implements miscellaneous analysis routines for affine structures // (expressions, maps, sets), and other utilities relying on such analysis. // //===----------------------------------------------------------------------===// #include "mlir/Dialect/Affine/Analysis/AffineAnalysis.h" #include "mlir/Analysis/Presburger/IntegerRelation.h" #include "mlir/Analysis/Presburger/PresburgerSpace.h" #include "mlir/Analysis/SliceAnalysis.h" #include "mlir/Dialect/Affine/Analysis/LoopAnalysis.h" #include "mlir/Dialect/Affine/Analysis/Utils.h" #include "mlir/Dialect/Affine/IR/AffineOps.h" #include "mlir/Dialect/Affine/IR/AffineValueMap.h" #include "mlir/IR/AffineExprVisitor.h" #include "mlir/IR/BuiltinOps.h" #include "mlir/IR/IntegerSet.h" #include "mlir/Interfaces/SideEffectInterfaces.h" #include "mlir/Interfaces/ViewLikeInterface.h" #include "llvm/ADT/TypeSwitch.h" #include "llvm/Support/Debug.h" #include "llvm/Support/raw_ostream.h" #include <optional> #define DEBUG_TYPE … usingnamespacemlir; usingnamespaceaffine; usingnamespacepresburger; /// Get the value that is being reduced by `pos`-th reduction in the loop if /// such a reduction can be performed by affine parallel loops. This assumes /// floating-point operations are commutative. On success, `kind` will be the /// reduction kind suitable for use in affine parallel loop builder. If the /// reduction is not supported, returns null. static Value getSupportedReduction(AffineForOp forOp, unsigned pos, arith::AtomicRMWKind &kind) { … } /// Populate `supportedReductions` with descriptors of the supported reductions. void mlir::affine::getSupportedReductions( AffineForOp forOp, SmallVectorImpl<LoopReduction> &supportedReductions) { … } /// Returns true if `forOp' is a parallel loop. If `parallelReductions` is /// provided, populates it with descriptors of the parallelizable reductions and /// treats them as not preventing parallelization. bool mlir::affine::isLoopParallel( AffineForOp forOp, SmallVectorImpl<LoopReduction> *parallelReductions) { … } /// Returns true if `v` is allocated locally to `enclosingOp` -- i.e., it is /// allocated by an operation nested within `enclosingOp`. static bool isLocallyDefined(Value v, Operation *enclosingOp) { … } bool mlir::affine::isLoopMemoryParallel(AffineForOp forOp) { … } /// Returns the sequence of AffineApplyOp Operations operation in /// 'affineApplyOps', which are reachable via a search starting from 'operands', /// and ending at operands which are not defined by AffineApplyOps. // TODO: Add a method to AffineApplyOp which forward substitutes the // AffineApplyOp into any user AffineApplyOps. void mlir::affine::getReachableAffineApplyOps( ArrayRef<Value> operands, SmallVectorImpl<Operation *> &affineApplyOps) { … } // Builds a system of constraints with dimensional variables corresponding to // the loop IVs of the forOps appearing in that order. Any symbols founds in // the bound operands are added as symbols in the system. Returns failure for // the yet unimplemented cases. // TODO: Handle non-unit steps through local variables or stride information in // FlatAffineValueConstraints. (For eg., by using iv - lb % step = 0 and/or by // introducing a method in FlatAffineValueConstraints // setExprStride(ArrayRef<int64_t> expr, int64_t stride) LogicalResult mlir::affine::getIndexSet(MutableArrayRef<Operation *> ops, FlatAffineValueConstraints *domain) { … } /// Computes the iteration domain for 'op' and populates 'indexSet', which /// encapsulates the constraints involving loops surrounding 'op' and /// potentially involving any Function symbols. The dimensional variables in /// 'indexSet' correspond to the loops surrounding 'op' from outermost to /// innermost. static LogicalResult getOpIndexSet(Operation *op, FlatAffineValueConstraints *indexSet) { … } // Returns the number of outer loop common to 'src/dstDomain'. // Loops common to 'src/dst' domains are added to 'commonLoops' if non-null. static unsigned getNumCommonLoops(const FlatAffineValueConstraints &srcDomain, const FlatAffineValueConstraints &dstDomain, SmallVectorImpl<AffineForOp> *commonLoops = nullptr) { … } /// Returns the closest surrounding block common to `opA` and `opB`. `opA` and /// `opB` should be in the same affine scope. Returns nullptr if such a block /// does not exist (when the two ops are in different blocks of an op starting /// an `AffineScope`). static Block *getCommonBlockInAffineScope(Operation *opA, Operation *opB) { … } /// Returns true if the ancestor operation of 'srcAccess' appears before the /// ancestor operation of 'dstAccess' in their common ancestral block. The /// operations for `srcAccess` and `dstAccess` are expected to be in the same /// affine scope and have a common surrounding block within it. static bool srcAppearsBeforeDstInAncestralBlock(const MemRefAccess &srcAccess, const MemRefAccess &dstAccess) { … } // Adds ordering constraints to 'dependenceDomain' based on number of loops // common to 'src/dstDomain' and requested 'loopDepth'. // Note that 'loopDepth' cannot exceed the number of common loops plus one. // EX: Given a loop nest of depth 2 with IVs 'i' and 'j': // *) If 'loopDepth == 1' then one constraint is added: i' >= i + 1 // *) If 'loopDepth == 2' then two constraints are added: i == i' and j' > j + 1 // *) If 'loopDepth == 3' then two constraints are added: i == i' and j == j' static void addOrderingConstraints(const FlatAffineValueConstraints &srcDomain, const FlatAffineValueConstraints &dstDomain, unsigned loopDepth, IntegerRelation *dependenceDomain) { … } // Computes distance and direction vectors in 'dependences', by adding // variables to 'dependenceDomain' which represent the difference of the IVs, // eliminating all other variables, and reading off distance vectors from // equality constraints (if possible), and direction vectors from inequalities. static void computeDirectionVector( const FlatAffineValueConstraints &srcDomain, const FlatAffineValueConstraints &dstDomain, unsigned loopDepth, IntegerPolyhedron *dependenceDomain, SmallVector<DependenceComponent, 2> *dependenceComponents) { … } LogicalResult MemRefAccess::getAccessRelation(IntegerRelation &rel) const { … } // Populates 'accessMap' with composition of AffineApplyOps reachable from // indices of MemRefAccess. void MemRefAccess::getAccessMap(AffineValueMap *accessMap) const { … } // Builds a flat affine constraint system to check if there exists a dependence // between memref accesses 'srcAccess' and 'dstAccess'. // Returns 'NoDependence' if the accesses can be definitively shown not to // access the same element. // Returns 'HasDependence' if the accesses do access the same element. // Returns 'Failure' if an error or unsupported case was encountered. // If a dependence exists, returns in 'dependenceComponents' a direction // vector for the dependence, with a component for each loop IV in loops // common to both accesses (see Dependence in AffineAnalysis.h for details). // // The memref access dependence check is comprised of the following steps: // *) Build access relation for each access. An access relation maps elements // of an iteration domain to the element(s) of an array domain accessed by // that iteration of the associated statement through some array reference. // *) Compute the dependence relation by composing access relation of // `srcAccess` with the inverse of access relation of `dstAccess`. // Doing this builds a relation between iteration domain of `srcAccess` // to the iteration domain of `dstAccess` which access the same memory // location. // *) Add ordering constraints for `srcAccess` to be accessed before // `dstAccess`. // // This method builds a constraint system with the following column format: // // [src-dim-variables, dst-dim-variables, symbols, constant] // // For example, given the following MLIR code with "source" and "destination" // accesses to the same memref label, and symbols %M, %N, %K: // // affine.for %i0 = 0 to 100 { // affine.for %i1 = 0 to 50 { // %a0 = affine.apply // (d0, d1) -> (d0 * 2 - d1 * 4 + s1, d1 * 3 - s0) (%i0, %i1)[%M, %N] // // Source memref access. // store %v0, %m[%a0#0, %a0#1] : memref<4x4xf32> // } // } // // affine.for %i2 = 0 to 100 { // affine.for %i3 = 0 to 50 { // %a1 = affine.apply // (d0, d1) -> (d0 * 7 + d1 * 9 - s1, d1 * 11 + s0) (%i2, %i3)[%K, %M] // // Destination memref access. // %v1 = load %m[%a1#0, %a1#1] : memref<4x4xf32> // } // } // // The access relation for `srcAccess` would be the following: // // [src_dim0, src_dim1, mem_dim0, mem_dim1, %N, %M, const] // 2 -4 -1 0 1 0 0 = 0 // 0 3 0 -1 0 -1 0 = 0 // 1 0 0 0 0 0 0 >= 0 // -1 0 0 0 0 0 100 >= 0 // 0 1 0 0 0 0 0 >= 0 // 0 -1 0 0 0 0 50 >= 0 // // The access relation for `dstAccess` would be the following: // // [dst_dim0, dst_dim1, mem_dim0, mem_dim1, %M, %K, const] // 7 9 -1 0 -1 0 0 = 0 // 0 11 0 -1 0 -1 0 = 0 // 1 0 0 0 0 0 0 >= 0 // -1 0 0 0 0 0 100 >= 0 // 0 1 0 0 0 0 0 >= 0 // 0 -1 0 0 0 0 50 >= 0 // // The equalities in the above relations correspond to the access maps while // the inequalities corresspond to the iteration domain constraints. // // The dependence relation formed: // // [src_dim0, src_dim1, dst_dim0, dst_dim1, %M, %N, %K, const] // 2 -4 -7 -9 1 1 0 0 = 0 // 0 3 0 -11 -1 0 1 0 = 0 // 1 0 0 0 0 0 0 0 >= 0 // -1 0 0 0 0 0 0 100 >= 0 // 0 1 0 0 0 0 0 0 >= 0 // 0 -1 0 0 0 0 0 50 >= 0 // 0 0 1 0 0 0 0 0 >= 0 // 0 0 -1 0 0 0 0 100 >= 0 // 0 0 0 1 0 0 0 0 >= 0 // 0 0 0 -1 0 0 0 50 >= 0 // // // TODO: Support AffineExprs mod/floordiv/ceildiv. DependenceResult mlir::affine::checkMemrefAccessDependence( const MemRefAccess &srcAccess, const MemRefAccess &dstAccess, unsigned loopDepth, FlatAffineValueConstraints *dependenceConstraints, SmallVector<DependenceComponent, 2> *dependenceComponents, bool allowRAR) { … } /// Gathers dependence components for dependences between all ops in loop nest /// rooted at 'forOp' at loop depths in range [1, maxLoopDepth]. void mlir::affine::getDependenceComponents( AffineForOp forOp, unsigned maxLoopDepth, std::vector<SmallVector<DependenceComponent, 2>> *depCompsVec) { … }