MLIR  22.0.0git
IterationGraphSorter.cpp
Go to the documentation of this file.
1 //===- IterationGraphSorter.cpp -------------------------------------------===//
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 #include "IterationGraphSorter.h"
10 
15 #include "mlir/IR/BuiltinTypes.h"
16 
17 using namespace mlir;
18 using namespace mlir::sparse_tensor;
19 
20 namespace {
21 
22 /// A helper class that visits an affine expression and tries to find
23 /// an AffineDimExpr to which the corresponding iterator from a GenericOp
24 /// matches the desired iterator type. If there is no matched iterator
25 /// type, the method returns the first DimExpr in the expression.
26 class AffineDimFinder : public AffineExprVisitor<AffineDimFinder> {
27 public:
28  explicit AffineDimFinder(ArrayRef<utils::IteratorType> itTypes)
29  : iterTypes(itTypes) {}
30 
31  /// Overrides the visit method from AffineExprVisitor.
32  void visitDimExpr(AffineDimExpr expr) {
33  if (pickedDim == nullptr || pickIterType == iterTypes[expr.getPosition()])
34  pickedDim = expr;
35  }
36 
37  /// Sets the desired iterator type that we want to pick.
38  void setPickedIterType(utils::IteratorType iterType) {
39  pickIterType = iterType;
40  }
41 
42  /// Gets the desired AffineDimExpr.
43  AffineDimExpr getDimExpr() const {
44  return llvm::cast<AffineDimExpr>(pickedDim);
45  }
46 
47  /// Walks the graph in post order to find dim expr.
48  void walkPostOrder(AffineExpr expr) {
49  pickedDim = nullptr;
51  }
52 
53 private:
54  /// The picked AffineDimExpr after visit.
55  AffineExpr pickedDim;
56  /// The iterator type that we want.
57  utils::IteratorType pickIterType;
58  /// The mapping between levels and iterator types.
60 };
61 
62 /// Flattens an affine expression into a list of AffineDimExprs.
63 struct AffineDimCollector : public AffineExprVisitor<AffineDimCollector> {
64  // Overrides method from AffineExprVisitor.
65  void visitDimExpr(AffineDimExpr expr) { dims.push_back(expr); }
67 };
68 
69 } // namespace
70 
71 inline static bool includesAny(SortMask mask1, SortMask mask2) {
72  return static_cast<unsigned>(mask1) & static_cast<unsigned>(mask2);
73 }
74 
75 inline static bool includesDenseInput(SortMask mask) {
77 }
78 
79 inline static bool includesDenseOutput(SortMask mask) {
81 }
82 
83 AffineMap IterationGraphSorter::topoSort() {
84  // The sorted result will put the first Reduction iterator to the
85  // latest possible position.
86  std::vector<unsigned> redIt; // reduce iterator with 0 degree
87  std::vector<unsigned> parIt; // parallel iterator with 0 degree
88  const unsigned numLoops = getNumLoops();
89  for (unsigned i = 0; i < numLoops; i++) {
90  if (inDegree[i] == 0) {
91  if (iterTypes[i] == utils::IteratorType::reduction)
92  redIt.push_back(i);
93  else
94  parIt.push_back(i);
95  }
96  }
97 
98  SmallVector<unsigned> loopOrder;
99  while (!redIt.empty() || !parIt.empty()) {
100  // We always prefer a parallel loop over a reduction loop because putting
101  // a reduction loop early might make the loop sequence inadmissible.
102  auto &it = !parIt.empty() ? parIt : redIt;
103 
104  // Select loop based on strategy.
105  unsigned src;
106  switch (strategy) {
108  src = it.back();
109  break;
110  }
111 
112  loopOrder.push_back(src);
113  it.pop_back();
114  // Update in-degree, and push 0-degree node into worklist.
115  for (unsigned dst = 0; dst < numLoops; dst++) {
116  if (itGraph[src][dst] && --inDegree[dst] == 0) {
117  if (iterTypes[dst] == utils::IteratorType::reduction)
118  redIt.push_back(dst);
119  else
120  parIt.push_back(dst);
121  }
122  }
123  }
124 
125  // Return the topological sort on success.
126  if (loopOrder.size() == numLoops)
127  return AffineMap::getPermutationMap(loopOrder, out.getContext());
128 
129  // Cycle detected.
130  return AffineMap();
131 }
132 
134  linalg::GenericOp genericOp, sparse_tensor::LoopOrderingStrategy strategy) {
135  // Must be a demapped sparse kernel.
136  assert(!hasAnyNonIdentityOperandsOrResults(genericOp) &&
137  hasAnySparseOperandOrResult(genericOp) &&
138  genericOp.getNumDpsInits() == 1);
139 
140  SmallVector<AffineMap> loopMap = genericOp.getIndexingMapsArray();
141  SmallVector<Value> ins = genericOp.getDpsInputs();
142 
143  AffineMap outMap = loopMap.back();
144  loopMap.pop_back();
145 
146  Value out = genericOp.getDpsInitOperand(0)->get();
148  genericOp.getIteratorTypesArray();
149 
150  return IterationGraphSorter(std::move(ins), std::move(loopMap), out, outMap,
151  std::move(iterTypes), strategy);
152 }
153 
154 IterationGraphSorter::IterationGraphSorter(
155  SmallVector<Value> &&insArg, SmallVector<AffineMap> &&loop2InsLvlArg,
156  Value out, AffineMap loop2OutLvl,
157  SmallVector<utils::IteratorType> &&iterTypesArg,
159  : ins(std::move(insArg)), loop2InsLvl(std::move(loop2InsLvlArg)), out(out),
160  loop2OutLvl(loop2OutLvl), iterTypes(std::move(iterTypesArg)),
161  strategy(strategy) {
162  // One map per tensor.
163  assert(loop2InsLvl.size() == ins.size());
164  // All the affine maps have the same number of dimensions (loops).
165  assert(llvm::all_equal(llvm::map_range(
166  loop2InsLvl, [](AffineMap m) { return m.getNumDims(); })));
167  // The number of results of the map should match the rank of the tensor.
168  assert(llvm::all_of(llvm::zip(loop2InsLvl, ins), [](auto mvPair) {
169  auto [m, v] = mvPair;
170 
171  // For ranked types the rank must match.
172  // Simply return true for UnrankedTensorType
173  if (auto shapedType = llvm::dyn_cast<ShapedType>(v.getType())) {
174  return !shapedType.hasRank() ||
175  (m.getNumResults() == shapedType.getRank());
176  }
177  // Non-shaped (scalar) types behave like rank-0.
178  return m.getNumResults() == 0;
179  }));
180 
181  itGraph.resize(getNumLoops(), std::vector<bool>(getNumLoops(), false));
182  inDegree.resize(getNumLoops());
183 }
184 
186  // Reset the adjacency matrix that represents the iteration graph.
187  for (auto &row : itGraph)
188  llvm::fill(row, false);
189 
190  // Reset in-degree.
191  llvm::fill(inDegree, 0);
192 
193  // Add the constraints for the loop to level map.
194  for (auto [in, map] : llvm::zip(ins, loop2InsLvl)) {
195  // Get map and encoding.
196  const auto enc = getSparseTensorEncoding(in.getType());
197  // Skip dense inputs when not requested.
198  if ((!enc && !includesDenseInput(mask)) || in == ignored)
199  continue;
200  addConstraints(in, map);
201  }
202 
203  // Add the constraints for the output map.
204  const auto enc = getSparseTensorEncoding(out.getType());
205  if ((enc || includesDenseOutput(mask)) && out != ignored)
206  addConstraints(out, loop2OutLvl);
207 
208  // Return the topological sort (empty for cyclic).
209  return topoSort();
210 }
211 
212 void IterationGraphSorter::addConstraints(Value t, AffineMap loop2LvlMap) {
213  auto addIterOrdering = [this](unsigned f, unsigned t) {
214  if (!itGraph[f][t] && f != t) {
215  itGraph[f][t] = true;
216  inDegree[t]++;
217  }
218  };
219 
220  // Set up a reduction finder.
221  AffineDimFinder finder(iterTypes);
222  finder.setPickedIterType(utils::IteratorType::reduction);
223 
224  // To compute iteration graph for tensor[d0 + d1 + d3, d4 + d5 + d6],
225  // we require there exist d_x \in {d0, d1, d3} and d_y \in {d4, d5, d6},
226  // and d_x > d_y && {d0, d1, d3} - d_x > {d4, d5, d6} - d_y
227  const Level lvlRank = loop2LvlMap.getNumResults();
228  for (Level lvl = 1; lvl < lvlRank; lvl++) {
229  const AffineExpr fa = loop2LvlMap.getResult(lvl - 1);
230  const AffineExpr ta = loop2LvlMap.getResult(lvl);
231 
232  if (llvm::isa<AffineDimExpr>(fa) || llvm::isa<AffineDimExpr>(ta)) {
233  // Special case when at least one loop2LvlExp is a simple AffineDimExpr
234  // (say, d0) and we require d0 > {d1, d2, ...} or {d1, d2, ...} > d0
235  AffineDimCollector fCollector;
236  fCollector.walkPostOrder(fa);
237  AffineDimCollector tCollector;
238  tCollector.walkPostOrder(ta);
239 
240  for (auto fd : fCollector.dims) {
241  for (auto td : tCollector.dims) {
242  const unsigned f = fd.getPosition();
243  const unsigned t = td.getPosition();
244  addIterOrdering(f, t);
245  }
246  }
247  continue;
248  }
249 
250  // When both loop2LvlExpr is compound, we pick an abitrary reduction loop
251  // from lhs and rhs and use them as d_x and d_y.
252  finder.walkPostOrder(fa);
253  const AffineDimExpr fexp = finder.getDimExpr();
254  const unsigned fldx = fexp.getPosition();
255 
256  finder.walkPostOrder(ta);
257  const AffineDimExpr texp = finder.getDimExpr();
258  const unsigned tldx = texp.getPosition();
259 
260  // d_x > d_y
261  addIterOrdering(fldx, tldx);
262 
263  AffineDimCollector fCollector;
264  fCollector.walkPostOrder(fa);
265  AffineDimCollector tCollector;
266  tCollector.walkPostOrder(ta);
267 
268  // Make sure dx and dy is the last.
269  for (auto fd : fCollector.dims) {
270  const unsigned f = fd.getPosition();
271  addIterOrdering(f, fldx);
272  }
273  for (auto td : tCollector.dims) {
274  const unsigned t = td.getPosition();
275  addIterOrdering(t, tldx);
276  }
277  // {d0, d1, d3} - d_x > {d4, d5, d6} - d_y
278  // This is to ensure that the affine expressions are reduced in sparse
279  // tensor level ordering.
280  for (auto fd : fCollector.dims) {
281  const unsigned f = fd.getPosition();
282  if (f == fldx) // skip d_x
283  continue;
284  for (auto td : tCollector.dims) {
285  const unsigned t = td.getPosition();
286  if (t == tldx) // skip d_y
287  continue;
288  addIterOrdering(f, t);
289  }
290  }
291  }
292 }
static bool includesDenseOutput(SortMask mask)
static bool includesAny(SortMask mask1, SortMask mask2)
static bool includesDenseInput(SortMask mask)
A dimensional identifier appearing in an affine expression.
Definition: AffineExpr.h:223
unsigned getPosition() const
Definition: AffineExpr.cpp:346
See documentation for AffineExprVisitorBase.
RetTy walkPostOrder(AffineExpr expr)
Base type for affine expression.
Definition: AffineExpr.h:68
A multi-dimensional affine map Affine map's are immutable like Type's, and they are uniqued.
Definition: AffineMap.h:46
unsigned getNumDims() const
Definition: AffineMap.cpp:390
unsigned getNumResults() const
Definition: AffineMap.cpp:398
AffineExpr getResult(unsigned idx) const
Definition: AffineMap.cpp:407
static AffineMap getPermutationMap(ArrayRef< unsigned > permutation, MLIRContext *context)
Returns an AffineMap representing a permutation.
Definition: AffineMap.cpp:260
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition: Value.h:96
MLIRContext * getContext() const
Utility to get the associated MLIRContext that this value is defined in.
Definition: Value.h:108
Type getType() const
Return the type of this value.
Definition: Value.h:105
static IterationGraphSorter fromGenericOp(linalg::GenericOp genericOp, sparse_tensor::LoopOrderingStrategy strategy)
Factory method that constructs an iteration graph sorter for the given linalg.generic operation with ...
unsigned getNumLoops() const
Returns the number of loops in the iteration graph.
AffineMap sort(SortMask mask, Value ignored=nullptr)
Returns a permutation that represents the scheduled loop order.
bool hasAnySparseOperandOrResult(Operation *op)
Returns true iff MLIR operand has any sparse operand or result.
Definition: SparseTensor.h:194
uint64_t Level
The type of level identifiers and level-ranks.
Definition: SparseTensor.h:42
LoopOrderingStrategy
Defines a strategy for loop ordering during sparse code generation.
Definition: Passes.h:61
@ kDefault
Default strategy (eagerly selects last loop in topological sort).
SparseTensorEncodingAttr getSparseTensorEncoding(Type type)
Convenience method to get a sparse encoding attribute from a type.
bool hasAnyNonIdentityOperandsOrResults(Operation *op)
Returns true iff MLIR operation has any sparse tensor with non-identity dim2lvl maps.
SortMask
Iteration graph sorting mask,.
Include the generated interface declarations.