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