xref: /llvm-project/mlir/include/mlir/Dialect/Affine/Analysis/Utils.h (revision b4785cebfb0a756e19ff4ae3e41d2563f10cd292)
1 //===- Utils.h - General analysis utilities ---------------------*- C++ -*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 // This header file defines prototypes for various transformation utilities for
10 // memref's and non-loop IR structures. These are not passes by themselves but
11 // are used either by passes, optimization sequences, or in turn by other
12 // transformation utilities.
13 //
14 //===----------------------------------------------------------------------===//
15 
16 #ifndef MLIR_DIALECT_AFFINE_ANALYSIS_UTILS_H
17 #define MLIR_DIALECT_AFFINE_ANALYSIS_UTILS_H
18 
19 #include "mlir/Dialect/Affine/Analysis/AffineStructures.h"
20 #include "mlir/Dialect/Affine/IR/AffineOps.h"
21 #include <memory>
22 #include <optional>
23 
24 namespace mlir {
25 class Block;
26 class Location;
27 class Operation;
28 class Value;
29 
30 namespace affine {
31 class AffineForOp;
32 class AffineValueMap;
33 struct MemRefAccess;
34 
35 // LoopNestStateCollector walks loop nests and collects load and store
36 // operations, and whether or not a region holding op other than ForOp and IfOp
37 // was encountered in the loop nest.
38 struct LoopNestStateCollector {
39   SmallVector<AffineForOp, 4> forOps;
40   SmallVector<Operation *, 4> loadOpInsts;
41   SmallVector<Operation *, 4> storeOpInsts;
42   bool hasNonAffineRegionOp = false;
43 
44   // Collects load and store operations, and whether or not a region holding op
45   // other than ForOp and IfOp was encountered in the loop nest.
46   void collect(Operation *opToWalk);
47 };
48 
49 // MemRefDependenceGraph is a graph data structure where graph nodes are
50 // top-level operations in a `Block` which contain load/store ops, and edges
51 // are memref dependences between the nodes.
52 // TODO: Add a more flexible dependence graph representation.
53 struct MemRefDependenceGraph {
54 public:
55   // Node represents a node in the graph. A Node is either an entire loop nest
56   // rooted at the top level which contains loads/stores, or a top level
57   // load/store.
58   struct Node {
59     // The unique identifier of this node in the graph.
60     unsigned id;
61     // The top-level statement which is (or contains) a load/store.
62     Operation *op;
63     // List of load operations.
64     SmallVector<Operation *, 4> loads;
65     // List of store op insts.
66     SmallVector<Operation *, 4> stores;
67 
NodeMemRefDependenceGraph::Node68     Node(unsigned id, Operation *op) : id(id), op(op) {}
69 
70     // Returns the load op count for 'memref'.
71     unsigned getLoadOpCount(Value memref) const;
72 
73     // Returns the store op count for 'memref'.
74     unsigned getStoreOpCount(Value memref) const;
75 
76     // Returns all store ops in 'storeOps' which access 'memref'.
77     void getStoreOpsForMemref(Value memref,
78                               SmallVectorImpl<Operation *> *storeOps) const;
79 
80     // Returns all load ops in 'loadOps' which access 'memref'.
81     void getLoadOpsForMemref(Value memref,
82                              SmallVectorImpl<Operation *> *loadOps) const;
83 
84     // Returns all memrefs in 'loadAndStoreMemrefSet' for which this node
85     // has at least one load and store operation.
86     void getLoadAndStoreMemrefSet(DenseSet<Value> *loadAndStoreMemrefSet) const;
87   };
88 
89   // Edge represents a data dependence between nodes in the graph.
90   struct Edge {
91     // The id of the node at the other end of the edge.
92     // If this edge is stored in Edge = Node.inEdges[i], then
93     // 'Node.inEdges[i].id' is the identifier of the source node of the edge.
94     // If this edge is stored in Edge = Node.outEdges[i], then
95     // 'Node.outEdges[i].id' is the identifier of the dest node of the edge.
96     unsigned id;
97     // The SSA value on which this edge represents a dependence.
98     // If the value is a memref, then the dependence is between graph nodes
99     // which contain accesses to the same memref 'value'. If the value is a
100     // non-memref value, then the dependence is between a graph node which
101     // defines an SSA value and another graph node which uses the SSA value
102     // (e.g. a constant or load operation defining a value which is used inside
103     // a loop nest).
104     Value value;
105   };
106 
107   // Map from node id to Node.
108   DenseMap<unsigned, Node> nodes;
109   // Map from node id to list of input edges.
110   DenseMap<unsigned, SmallVector<Edge, 2>> inEdges;
111   // Map from node id to list of output edges.
112   DenseMap<unsigned, SmallVector<Edge, 2>> outEdges;
113   // Map from memref to a count on the dependence edges associated with that
114   // memref.
115   DenseMap<Value, unsigned> memrefEdgeCount;
116   // The next unique identifier to use for newly created graph nodes.
117   unsigned nextNodeId = 0;
118 
MemRefDependenceGraphMemRefDependenceGraph119   MemRefDependenceGraph(Block &block) : block(block) {}
120 
121   // Initializes the dependence graph based on operations in `block'.
122   // Returns true on success, false otherwise.
123   bool init();
124 
125   // Returns the graph node for 'id'.
126   Node *getNode(unsigned id);
127 
128   // Returns the graph node for 'forOp'.
129   Node *getForOpNode(AffineForOp forOp);
130 
131   // Adds a node with 'op' to the graph and returns its unique identifier.
132   unsigned addNode(Operation *op);
133 
134   // Remove node 'id' (and its associated edges) from graph.
135   void removeNode(unsigned id);
136 
137   // Returns true if node 'id' writes to any memref which escapes (or is an
138   // argument to) the block. Returns false otherwise.
139   bool writesToLiveInOrEscapingMemrefs(unsigned id);
140 
141   // Returns true iff there is an edge from node 'srcId' to node 'dstId' which
142   // is for 'value' if non-null, or for any value otherwise. Returns false
143   // otherwise.
144   bool hasEdge(unsigned srcId, unsigned dstId, Value value = nullptr);
145 
146   // Adds an edge from node 'srcId' to node 'dstId' for 'value'.
147   void addEdge(unsigned srcId, unsigned dstId, Value value);
148 
149   // Removes an edge from node 'srcId' to node 'dstId' for 'value'.
150   void removeEdge(unsigned srcId, unsigned dstId, Value value);
151 
152   // Returns true if there is a path in the dependence graph from node 'srcId'
153   // to node 'dstId'. Returns false otherwise. `srcId`, `dstId`, and the
154   // operations that the edges connected are expected to be from the same block.
155   bool hasDependencePath(unsigned srcId, unsigned dstId);
156 
157   // Returns the input edge count for node 'id' and 'memref' from src nodes
158   // which access 'memref' with a store operation.
159   unsigned getIncomingMemRefAccesses(unsigned id, Value memref);
160 
161   // Returns the output edge count for node 'id' and 'memref' (if non-null),
162   // otherwise returns the total output edge count from node 'id'.
163   unsigned getOutEdgeCount(unsigned id, Value memref = nullptr);
164 
165   /// Return all nodes which define SSA values used in node 'id'.
166   void gatherDefiningNodes(unsigned id, DenseSet<unsigned> &definingNodes);
167 
168   // Computes and returns an insertion point operation, before which the
169   // the fused <srcId, dstId> loop nest can be inserted while preserving
170   // dependences. Returns nullptr if no such insertion point is found.
171   Operation *getFusedLoopNestInsertionPoint(unsigned srcId, unsigned dstId);
172 
173   // Updates edge mappings from node 'srcId' to node 'dstId' after fusing them,
174   // taking into account that:
175   //   *) if 'removeSrcId' is true, 'srcId' will be removed after fusion,
176   //   *) memrefs in 'privateMemRefs' has been replaced in node at 'dstId' by a
177   //      private memref.
178   void updateEdges(unsigned srcId, unsigned dstId,
179                    const DenseSet<Value> &privateMemRefs, bool removeSrcId);
180 
181   // Update edge mappings for nodes 'sibId' and 'dstId' to reflect fusion
182   // of sibling node 'sibId' into node 'dstId'.
183   void updateEdges(unsigned sibId, unsigned dstId);
184 
185   // Adds ops in 'loads' and 'stores' to node at 'id'.
186   void addToNode(unsigned id, const SmallVectorImpl<Operation *> &loads,
187                  const SmallVectorImpl<Operation *> &stores);
188 
189   void clearNodeLoadAndStores(unsigned id);
190 
191   // Calls 'callback' for each input edge incident to node 'id' which carries a
192   // memref dependence.
193   void forEachMemRefInputEdge(unsigned id,
194                               const std::function<void(Edge)> &callback);
195 
196   // Calls 'callback' for each output edge from node 'id' which carries a
197   // memref dependence.
198   void forEachMemRefOutputEdge(unsigned id,
199                                const std::function<void(Edge)> &callback);
200 
201   // Calls 'callback' for each edge in 'edges' which carries a memref
202   // dependence.
203   void forEachMemRefEdge(ArrayRef<Edge> edges,
204                          const std::function<void(Edge)> &callback);
205 
206   void print(raw_ostream &os) const;
207 
dumpMemRefDependenceGraph208   void dump() const { print(llvm::errs()); }
209 
210   /// The block for which this graph is created to perform fusion.
211   Block &block;
212 };
213 
214 /// Populates 'loops' with IVs of the affine.for ops surrounding 'op' ordered
215 /// from the outermost 'affine.for' operation to the innermost one while not
216 /// traversing outside of the surrounding affine scope.
217 void getAffineForIVs(Operation &op, SmallVectorImpl<AffineForOp> *loops);
218 
219 /// Populates 'ivs' with IVs of the surrounding affine.for and affine.parallel
220 /// ops ordered from the outermost one to the innermost while not traversing
221 /// outside of the surrounding affine scope.
222 void getAffineIVs(Operation &op, SmallVectorImpl<Value> &ivs);
223 
224 /// Populates 'ops' with affine operations enclosing `op` ordered from outermost
225 /// to innermost while stopping at the boundary of the affine scope. affine.for,
226 /// affine.if, or affine.parallel ops comprise such surrounding affine ops.
227 /// `ops` is guaranteed by design to have a successive chain of affine parent
228 /// ops.
229 void getEnclosingAffineOps(Operation &op, SmallVectorImpl<Operation *> *ops);
230 
231 /// Returns the nesting depth of this operation, i.e., the number of loops
232 /// surrounding this operation.
233 unsigned getNestingDepth(Operation *op);
234 
235 /// Returns whether a loop is a parallel loop and contains a reduction loop.
236 bool isLoopParallelAndContainsReduction(AffineForOp forOp);
237 
238 /// Returns in 'sequentialLoops' all sequential loops in loop nest rooted
239 /// at 'forOp'.
240 void getSequentialLoops(AffineForOp forOp,
241                         llvm::SmallDenseSet<Value, 8> *sequentialLoops);
242 
243 /// Enumerates different result statuses of slice computation by
244 /// `computeSliceUnion`
245 // TODO: Identify and add different kinds of failures during slice computation.
246 struct SliceComputationResult {
247   enum ResultEnum {
248     Success,
249     IncorrectSliceFailure, // Slice is computed, but it is incorrect.
250     GenericFailure,        // Unable to compute src loop computation slice.
251   } value;
SliceComputationResultSliceComputationResult252   SliceComputationResult(ResultEnum v) : value(v) {}
253 };
254 
255 /// ComputationSliceState aggregates loop IVs, loop bound AffineMaps and their
256 /// associated operands for a set of loops within a loop nest (typically the
257 /// set of loops surrounding a store operation). Loop bound AffineMaps which
258 /// are non-null represent slices of that loop's iteration space.
259 struct ComputationSliceState {
260   // List of sliced loop IVs (ordered from outermost to innermost).
261   // EX: 'ivs[i]' has lower bound 'lbs[i]' and upper bound 'ubs[i]'.
262   SmallVector<Value, 4> ivs;
263   // List of lower bound AffineMaps.
264   SmallVector<AffineMap, 4> lbs;
265   // List of upper bound AffineMaps.
266   SmallVector<AffineMap, 4> ubs;
267   // List of lower bound operands (lbOperands[i] are used by 'lbs[i]').
268   std::vector<SmallVector<Value, 4>> lbOperands;
269   // List of upper bound operands (ubOperands[i] are used by 'ubs[i]').
270   std::vector<SmallVector<Value, 4>> ubOperands;
271   // Slice loop nest insertion point in target loop nest.
272   Block::iterator insertPoint;
273   // Adds to 'cst' with constraints which represent the slice bounds on 'ivs'
274   // in 'this'. Specifically, the values in 'ivs' are added to 'cst' as dim
275   // variables and the values in 'lb/ubOperands' are added as symbols.
276   // Constraints are added for all loop IV bounds (dim or symbol), and
277   // constraints are added for slice bounds in 'lbs'/'ubs'.
278   // Returns failure if we cannot add loop bounds because of unsupported cases.
279   LogicalResult getAsConstraints(FlatAffineValueConstraints *cst) const;
280 
281   /// Adds to 'cst' constraints which represent the original loop bounds on
282   /// 'ivs' in 'this'. This corresponds to the original domain of the loop nest
283   /// from which the slice is being computed. Returns failure if we cannot add
284   /// loop bounds because of unsupported cases.
285   LogicalResult getSourceAsConstraints(FlatAffineValueConstraints &cst) const;
286 
287   // Clears all bounds and operands in slice state.
288   void clearBounds();
289 
290   /// Returns true if the computation slice is empty.
isEmptyComputationSliceState291   bool isEmpty() const { return ivs.empty(); }
292 
293   /// Returns true if the computation slice encloses all the iterations of the
294   /// sliced loop nest. Returns false if it does not. Returns std::nullopt if it
295   /// cannot determine if the slice is maximal or not.
296   // TODO: Cache 'isMaximal' so that we don't recompute it when the slice
297   // information hasn't changed.
298   std::optional<bool> isMaximal() const;
299 
300   /// Checks the validity of the slice computed. This is done using the
301   /// following steps:
302   /// 1. Get the new domain of the slice that would be created if fusion
303   /// succeeds. This domain gets constructed with source loop IVS and
304   /// destination loop IVS as dimensions.
305   /// 2. Project out the dimensions of the destination loop from the domain
306   /// above calculated in step(1) to express it purely in terms of the source
307   /// loop IVs.
308   /// 3. Calculate a set difference between the iterations of the new domain and
309   /// the original domain of the source loop.
310   /// If this difference is empty, the slice is declared to be valid. Otherwise,
311   /// return false as it implies that the effective fusion results in at least
312   /// one iteration of the slice that was not originally in the source's domain.
313   /// If the validity cannot be determined, returns std::nullopt.
314   std::optional<bool> isSliceValid() const;
315 
316   void dump() const;
317 
318 private:
319   /// Fast check to determine if the computation slice is maximal. Returns true
320   /// if each slice dimension maps to an existing dst dimension and both the src
321   /// and the dst loops for those dimensions have the same bounds. Returns false
322   /// if both the src and the dst loops don't have the same bounds. Returns
323   /// std::nullopt if none of the above can be proven.
324   std::optional<bool> isSliceMaximalFastCheck() const;
325 };
326 
327 /// Computes the computation slice loop bounds for one loop nest as affine maps
328 /// of the other loop nest's IVs and symbols, using 'dependenceConstraints'
329 /// computed between 'depSourceAccess' and 'depSinkAccess'.
330 /// If 'isBackwardSlice' is true, a backwards slice is computed in which the
331 /// slice bounds of loop nest surrounding 'depSourceAccess' are computed in
332 /// terms of loop IVs and symbols of the loop nest surrounding 'depSinkAccess'
333 /// at 'loopDepth'.
334 /// If 'isBackwardSlice' is false, a forward slice is computed in which the
335 /// slice bounds of loop nest surrounding 'depSinkAccess' are computed in terms
336 /// of loop IVs and symbols of the loop nest surrounding 'depSourceAccess' at
337 /// 'loopDepth'.
338 /// The slice loop bounds and associated operands are returned in 'sliceState'.
339 //
340 //  Backward slice example:
341 //
342 //    affine.for %i0 = 0 to 10 {
343 //      affine.store %cst, %0[%i0] : memref<100xf32>  // 'depSourceAccess'
344 //    }
345 //    affine.for %i1 = 0 to 10 {
346 //      %v = affine.load %0[%i1] : memref<100xf32>    // 'depSinkAccess'
347 //    }
348 //
349 //    // Backward computation slice of loop nest '%i0'.
350 //    affine.for %i0 = (d0) -> (d0)(%i1) to (d0) -> (d0 + 1)(%i1) {
351 //      affine.store %cst, %0[%i0] : memref<100xf32>  // 'depSourceAccess'
352 //    }
353 //
354 //  Forward slice example:
355 //
356 //    affine.for %i0 = 0 to 10 {
357 //      affine.store %cst, %0[%i0] : memref<100xf32>  // 'depSourceAccess'
358 //    }
359 //    affine.for %i1 = 0 to 10 {
360 //      %v = affine.load %0[%i1] : memref<100xf32>    // 'depSinkAccess'
361 //    }
362 //
363 //    // Forward computation slice of loop nest '%i1'.
364 //    affine.for %i1 = (d0) -> (d0)(%i0) to (d0) -> (d0 + 1)(%i0) {
365 //      %v = affine.load %0[%i1] : memref<100xf32>    // 'depSinkAccess'
366 //    }
367 //
368 void getComputationSliceState(Operation *depSourceOp, Operation *depSinkOp,
369                               FlatAffineValueConstraints *dependenceConstraints,
370                               unsigned loopDepth, bool isBackwardSlice,
371                               ComputationSliceState *sliceState);
372 
373 /// Return the number of iterations for the `slicetripCountMap` provided.
374 uint64_t getSliceIterationCount(
375     const llvm::SmallDenseMap<Operation *, uint64_t, 8> &sliceTripCountMap);
376 
377 /// Builds a map 'tripCountMap' from AffineForOp to constant trip count for
378 /// loop nest surrounding represented by slice loop bounds in 'slice'. Returns
379 /// true on success, false otherwise (if a non-constant trip count was
380 /// encountered).
381 bool buildSliceTripCountMap(
382     const ComputationSliceState &slice,
383     llvm::SmallDenseMap<Operation *, uint64_t, 8> *tripCountMap);
384 
385 /// Computes in 'sliceUnion' the union of all slice bounds computed at
386 /// 'loopDepth' between all dependent pairs of ops in 'opsA' and 'opsB', and
387 /// then verifies if it is valid. The parameter 'numCommonLoops' is the number
388 /// of loops common to the operations in 'opsA' and 'opsB'. If 'isBackwardSlice'
389 /// is true, computes slice bounds for loop nest surrounding ops in 'opsA', as a
390 /// function of IVs and symbols of loop nest surrounding ops in 'opsB' at
391 /// 'loopDepth'. If 'isBackwardSlice' is false, computes slice bounds for loop
392 /// nest surrounding ops in 'opsB', as a function of IVs and symbols of loop
393 /// nest surrounding ops in 'opsA' at 'loopDepth'. Returns
394 /// 'SliceComputationResult::Success' if union was computed correctly, an
395 /// appropriate 'failure' otherwise.
396 SliceComputationResult
397 computeSliceUnion(ArrayRef<Operation *> opsA, ArrayRef<Operation *> opsB,
398                   unsigned loopDepth, unsigned numCommonLoops,
399                   bool isBackwardSlice, ComputationSliceState *sliceUnion);
400 
401 /// Creates a clone of the computation contained in the loop nest surrounding
402 /// 'srcOpInst', slices the iteration space of src loop based on slice bounds
403 /// in 'sliceState', and inserts the computation slice at the beginning of the
404 /// operation block of the loop at 'dstLoopDepth' in the loop nest surrounding
405 /// 'dstOpInst'. Returns the top-level loop of the computation slice on
406 /// success, returns nullptr otherwise.
407 // Loop depth is a crucial optimization choice that determines where to
408 // materialize the results of the backward slice - presenting a trade-off b/w
409 // storage and redundant computation in several cases.
410 // TODO: Support computation slices with common surrounding loops.
411 AffineForOp insertBackwardComputationSlice(Operation *srcOpInst,
412                                            Operation *dstOpInst,
413                                            unsigned dstLoopDepth,
414                                            ComputationSliceState *sliceState);
415 
416 /// A region of a memref's data space; this is typically constructed by
417 /// analyzing load/store op's on this memref and the index space of loops
418 /// surrounding such op's.
419 // For example, the memref region for a load operation at loop depth = 1:
420 //
421 //    affine.for %i = 0 to 32 {
422 //      affine.for %ii = %i to (d0) -> (d0 + 8) (%i) {
423 //        affine.load %A[%ii]
424 //      }
425 //    }
426 //
427 // Region:  {memref = %A, write = false, {%i <= m0 <= %i + 7} }
428 // The last field is a 2-d FlatAffineValueConstraints symbolic in %i.
429 //
430 struct MemRefRegion {
MemRefRegionMemRefRegion431   explicit MemRefRegion(Location loc) : loc(loc) {}
432 
433   /// Computes the memory region accessed by this memref with the region
434   /// represented as constraints symbolic/parametric in 'loopDepth' loops
435   /// surrounding opInst. The computed region's 'cst' field has exactly as many
436   /// dimensional variables as the rank of the memref, and *potentially*
437   /// additional symbolic variables which could include any of the loop IVs
438   /// surrounding opInst up until 'loopDepth' and another additional Function
439   /// symbols involved with the access (for eg., those appear in affine.apply's,
440   /// loop bounds, etc.). If 'sliceState' is non-null, operands from
441   /// 'sliceState' are added as symbols, and the following constraints are added
442   /// to the system:
443   /// *) Inequality constraints which represent loop bounds for 'sliceState'
444   ///    operands which are loop IVS (these represent the destination loop IVs
445   ///    of the slice, and are added as symbols to MemRefRegion's constraint
446   ///    system).
447   /// *) Inequality constraints for the slice bounds in 'sliceState', which
448   ///    represent the bounds on the loop IVs in this constraint system w.r.t
449   ///    to slice operands (which correspond to symbols).
450   /// If 'addMemRefDimBounds' is true, constant upper/lower bounds
451   /// [0, memref.getDimSize(i)) are added for each MemRef dimension 'i'.
452   ///
453   ///  For example, the memref region for this operation at loopDepth = 1 will
454   ///  be:
455   ///
456   ///    affine.for %i = 0 to 32 {
457   ///      affine.for %ii = %i to (d0) -> (d0 + 8) (%i) {
458   ///        load %A[%ii]
459   ///      }
460   ///    }
461   ///
462   ///   {memref = %A, write = false, {%i <= m0 <= %i + 7} }
463   /// The last field is a 2-d FlatAffineValueConstraints symbolic in %i.
464   ///
465   LogicalResult compute(Operation *op, unsigned loopDepth,
466                         const ComputationSliceState *sliceState = nullptr,
467                         bool addMemRefDimBounds = true);
468 
getConstraintsMemRefRegion469   FlatAffineValueConstraints *getConstraints() { return &cst; }
getConstraintsMemRefRegion470   const FlatAffineValueConstraints *getConstraints() const { return &cst; }
isWriteMemRefRegion471   bool isWrite() const { return write; }
setWriteMemRefRegion472   void setWrite(bool flag) { write = flag; }
473 
474   /// Returns a constant upper bound on the number of elements in this region if
475   /// bounded by a known constant (always possible for static shapes),
476   /// std::nullopt otherwise. Note that the symbols of the region are treated
477   /// specially, i.e., the returned bounding constant holds for *any given*
478   /// value of the symbol variables. The 'shape' vector is set to the
479   /// corresponding dimension-wise bounds major to minor. The number of elements
480   /// and all the dimension-wise bounds are guaranteed to be non-negative. We
481   /// use int64_t instead of uint64_t since index types can be at most
482   /// int64_t. `lbs` are set to the lower bounds for each of the rank
483   /// dimensions, and lbDivisors contains the corresponding denominators for
484   /// floorDivs.
485   std::optional<int64_t> getConstantBoundingSizeAndShape(
486       SmallVectorImpl<int64_t> *shape = nullptr,
487       std::vector<SmallVector<int64_t, 4>> *lbs = nullptr,
488       SmallVectorImpl<int64_t> *lbDivisors = nullptr) const;
489 
490   /// Gets the lower and upper bound map for the dimensional variable at
491   /// `pos`.
492   void getLowerAndUpperBound(unsigned pos, AffineMap &lbMap,
493                              AffineMap &ubMap) const;
494 
495   /// A wrapper around FlatAffineValueConstraints::getConstantBoundOnDimSize().
496   /// 'pos' corresponds to the position of the memref shape's dimension (major
497   /// to minor) which matches 1:1 with the dimensional variable positions in
498   /// 'cst'.
499   std::optional<int64_t>
500   getConstantBoundOnDimSize(unsigned pos,
501                             SmallVectorImpl<int64_t> *lb = nullptr,
502                             int64_t *lbFloorDivisor = nullptr) const {
503     assert(pos < getRank() && "invalid position");
504     return cst.getConstantBoundOnDimSize64(pos, lb);
505   }
506 
507   /// Returns the size of this MemRefRegion in bytes.
508   std::optional<int64_t> getRegionSize();
509 
510   // Wrapper around FlatAffineValueConstraints::unionBoundingBox.
511   LogicalResult unionBoundingBox(const MemRefRegion &other);
512 
513   /// Returns the rank of the memref that this region corresponds to.
514   unsigned getRank() const;
515 
516   /// Memref that this region corresponds to.
517   Value memref;
518 
519   /// Read or write.
520   bool write = false;
521 
522   /// If there is more than one load/store op associated with the region, the
523   /// location information would correspond to one of those op's.
524   Location loc;
525 
526   /// Region (data space) of the memref accessed. This set will thus have at
527   /// least as many dimensional variables as the shape dimensionality of the
528   /// memref, and these are the leading dimensions of the set appearing in that
529   /// order (major to minor / outermost to innermost). There may be additional
530   /// variables since getMemRefRegion() is called with a specific loop depth,
531   /// and thus the region is symbolic in the outer surrounding loops at that
532   /// depth.
533   FlatAffineValueConstraints cst;
534 };
535 
536 /// Returns the size of a memref with element type int or float in bytes if it's
537 /// statically shaped, std::nullopt otherwise.
538 std::optional<uint64_t> getIntOrFloatMemRefSizeInBytes(MemRefType memRefType);
539 
540 /// Checks a load or store op for an out of bound access; returns failure if the
541 /// access is out of bounds along any of the dimensions, success otherwise.
542 /// Emits a diagnostic error (with location information) if emitError is true.
543 template <typename LoadOrStoreOpPointer>
544 LogicalResult boundCheckLoadOrStoreOp(LoadOrStoreOpPointer loadOrStoreOp,
545                                       bool emitError = true);
546 
547 /// Returns the number of surrounding loops common to both A and B.
548 unsigned getNumCommonSurroundingLoops(Operation &a, Operation &b);
549 
550 /// Gets the memory footprint of all data touched in the specified memory space
551 /// in bytes; if the memory space is unspecified, considers all memory spaces.
552 std::optional<int64_t> getMemoryFootprintBytes(AffineForOp forOp,
553                                                int memorySpace = -1);
554 
555 /// Returns the memref's element type's size in bytes where the elemental type
556 /// is an int or float or a vector of such types.
557 std::optional<int64_t> getMemRefIntOrFloatEltSizeInBytes(MemRefType memRefType);
558 
559 /// Simplify the integer set by simplifying the underlying affine expressions by
560 /// flattening and some simple inference. Also, drop any duplicate constraints.
561 /// Returns the simplified integer set. This method runs in time linear in the
562 /// number of constraints.
563 IntegerSet simplifyIntegerSet(IntegerSet set);
564 
565 /// Returns the innermost common loop depth for the set of operations in 'ops'.
566 unsigned getInnermostCommonLoopDepth(
567     ArrayRef<Operation *> ops,
568     SmallVectorImpl<AffineForOp> *surroundingLoops = nullptr);
569 
570 /// Try to simplify the given affine.min or affine.max op to an affine map with
571 /// a single result and operands, taking into account the specified constraint
572 /// set. Return failure if no simplified version could be found.
573 FailureOr<AffineValueMap>
574 simplifyConstrainedMinMaxOp(Operation *op,
575                             FlatAffineValueConstraints constraints);
576 
577 } // namespace affine
578 } // namespace mlir
579 
580 #endif // MLIR_DIALECT_AFFINE_ANALYSIS_UTILS_H
581