1 //===- AffineMap.cpp - MLIR Affine Map Classes ----------------------------===//
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 "mlir/IR/AffineMap.h"
10 #include "AffineMapDetail.h"
11 #include "mlir/IR/Attributes.h"
12 #include "mlir/IR/StandardTypes.h"
13 #include "mlir/Support/LogicalResult.h"
14 #include "mlir/Support/MathExtras.h"
15 #include "llvm/ADT/StringRef.h"
16 #include "llvm/Support/raw_ostream.h"
17 
18 using namespace mlir;
19 
20 namespace {
21 
22 // AffineExprConstantFolder evaluates an affine expression using constant
23 // operands passed in 'operandConsts'. Returns an IntegerAttr attribute
24 // representing the constant value of the affine expression evaluated on
25 // constant 'operandConsts', or nullptr if it can't be folded.
26 class AffineExprConstantFolder {
27 public:
28   AffineExprConstantFolder(unsigned numDims, ArrayRef<Attribute> operandConsts)
29       : numDims(numDims), operandConsts(operandConsts) {}
30 
31   /// Attempt to constant fold the specified affine expr, or return null on
32   /// failure.
33   IntegerAttr constantFold(AffineExpr expr) {
34     if (auto result = constantFoldImpl(expr))
35       return IntegerAttr::get(IndexType::get(expr.getContext()), *result);
36     return nullptr;
37   }
38 
39 private:
40   Optional<int64_t> constantFoldImpl(AffineExpr expr) {
41     switch (expr.getKind()) {
42     case AffineExprKind::Add:
43       return constantFoldBinExpr(
44           expr, [](int64_t lhs, int64_t rhs) { return lhs + rhs; });
45     case AffineExprKind::Mul:
46       return constantFoldBinExpr(
47           expr, [](int64_t lhs, int64_t rhs) { return lhs * rhs; });
48     case AffineExprKind::Mod:
49       return constantFoldBinExpr(
50           expr, [](int64_t lhs, int64_t rhs) { return mod(lhs, rhs); });
51     case AffineExprKind::FloorDiv:
52       return constantFoldBinExpr(
53           expr, [](int64_t lhs, int64_t rhs) { return floorDiv(lhs, rhs); });
54     case AffineExprKind::CeilDiv:
55       return constantFoldBinExpr(
56           expr, [](int64_t lhs, int64_t rhs) { return ceilDiv(lhs, rhs); });
57     case AffineExprKind::Constant:
58       return expr.cast<AffineConstantExpr>().getValue();
59     case AffineExprKind::DimId:
60       if (auto attr = operandConsts[expr.cast<AffineDimExpr>().getPosition()]
61                           .dyn_cast_or_null<IntegerAttr>())
62         return attr.getInt();
63       return llvm::None;
64     case AffineExprKind::SymbolId:
65       if (auto attr = operandConsts[numDims +
66                                     expr.cast<AffineSymbolExpr>().getPosition()]
67                           .dyn_cast_or_null<IntegerAttr>())
68         return attr.getInt();
69       return llvm::None;
70     }
71     llvm_unreachable("Unknown AffineExpr");
72   }
73 
74   // TODO: Change these to operate on APInts too.
75   Optional<int64_t> constantFoldBinExpr(AffineExpr expr,
76                                         int64_t (*op)(int64_t, int64_t)) {
77     auto binOpExpr = expr.cast<AffineBinaryOpExpr>();
78     if (auto lhs = constantFoldImpl(binOpExpr.getLHS()))
79       if (auto rhs = constantFoldImpl(binOpExpr.getRHS()))
80         return op(*lhs, *rhs);
81     return llvm::None;
82   }
83 
84   // The number of dimension operands in AffineMap containing this expression.
85   unsigned numDims;
86   // The constant valued operands used to evaluate this AffineExpr.
87   ArrayRef<Attribute> operandConsts;
88 };
89 
90 } // end anonymous namespace
91 
92 /// Returns a single constant result affine map.
93 AffineMap AffineMap::getConstantMap(int64_t val, MLIRContext *context) {
94   return get(/*dimCount=*/0, /*symbolCount=*/0,
95              {getAffineConstantExpr(val, context)});
96 }
97 
98 /// Returns an identity affine map (d0, ..., dn) -> (dp, ..., dn) on the most
99 /// minor dimensions.
100 AffineMap AffineMap::getMinorIdentityMap(unsigned dims, unsigned results,
101                                          MLIRContext *context) {
102   assert(dims >= results && "Dimension mismatch");
103   auto id = AffineMap::getMultiDimIdentityMap(dims, context);
104   return AffineMap::get(dims, 0, id.getResults().take_back(results), context);
105 }
106 
107 bool AffineMap::isMinorIdentity(AffineMap map) {
108   if (!map)
109     return false;
110   return map == getMinorIdentityMap(map.getNumDims(), map.getNumResults(),
111                                     map.getContext());
112 }
113 
114 /// Returns an AffineMap representing a permutation.
115 AffineMap AffineMap::getPermutationMap(ArrayRef<unsigned> permutation,
116                                        MLIRContext *context) {
117   assert(!permutation.empty() &&
118          "Cannot create permutation map from empty permutation vector");
119   SmallVector<AffineExpr, 4> affExprs;
120   for (auto index : permutation)
121     affExprs.push_back(getAffineDimExpr(index, context));
122   auto m = std::max_element(permutation.begin(), permutation.end());
123   auto permutationMap = AffineMap::get(*m + 1, 0, affExprs, context);
124   assert(permutationMap.isPermutation() && "Invalid permutation vector");
125   return permutationMap;
126 }
127 
128 template <typename AffineExprContainer>
129 static void getMaxDimAndSymbol(ArrayRef<AffineExprContainer> exprsList,
130                                int64_t &maxDim, int64_t &maxSym) {
131   for (const auto &exprs : exprsList) {
132     for (auto expr : exprs) {
133       expr.walk([&maxDim, &maxSym](AffineExpr e) {
134         if (auto d = e.dyn_cast<AffineDimExpr>())
135           maxDim = std::max(maxDim, static_cast<int64_t>(d.getPosition()));
136         if (auto s = e.dyn_cast<AffineSymbolExpr>())
137           maxSym = std::max(maxSym, static_cast<int64_t>(s.getPosition()));
138       });
139     }
140   }
141 }
142 
143 template <typename AffineExprContainer>
144 static SmallVector<AffineMap, 4>
145 inferFromExprList(ArrayRef<AffineExprContainer> exprsList) {
146   assert(!exprsList.empty());
147   assert(!exprsList[0].empty());
148   auto context = exprsList[0][0].getContext();
149   int64_t maxDim = -1, maxSym = -1;
150   getMaxDimAndSymbol(exprsList, maxDim, maxSym);
151   SmallVector<AffineMap, 4> maps;
152   maps.reserve(exprsList.size());
153   for (const auto &exprs : exprsList)
154     maps.push_back(AffineMap::get(/*dimCount=*/maxDim + 1,
155                                   /*symbolCount=*/maxSym + 1, exprs, context));
156   return maps;
157 }
158 
159 SmallVector<AffineMap, 4>
160 AffineMap::inferFromExprList(ArrayRef<ArrayRef<AffineExpr>> exprsList) {
161   return ::inferFromExprList(exprsList);
162 }
163 
164 SmallVector<AffineMap, 4>
165 AffineMap::inferFromExprList(ArrayRef<SmallVector<AffineExpr, 4>> exprsList) {
166   return ::inferFromExprList(exprsList);
167 }
168 
169 AffineMap AffineMap::getMultiDimIdentityMap(unsigned numDims,
170                                             MLIRContext *context) {
171   SmallVector<AffineExpr, 4> dimExprs;
172   dimExprs.reserve(numDims);
173   for (unsigned i = 0; i < numDims; ++i)
174     dimExprs.push_back(mlir::getAffineDimExpr(i, context));
175   return get(/*dimCount=*/numDims, /*symbolCount=*/0, dimExprs, context);
176 }
177 
178 MLIRContext *AffineMap::getContext() const { return map->context; }
179 
180 bool AffineMap::isIdentity() const {
181   if (getNumDims() != getNumResults())
182     return false;
183   ArrayRef<AffineExpr> results = getResults();
184   for (unsigned i = 0, numDims = getNumDims(); i < numDims; ++i) {
185     auto expr = results[i].dyn_cast<AffineDimExpr>();
186     if (!expr || expr.getPosition() != i)
187       return false;
188   }
189   return true;
190 }
191 
192 bool AffineMap::isEmpty() const {
193   return getNumDims() == 0 && getNumSymbols() == 0 && getNumResults() == 0;
194 }
195 
196 bool AffineMap::isSingleConstant() const {
197   return getNumResults() == 1 && getResult(0).isa<AffineConstantExpr>();
198 }
199 
200 int64_t AffineMap::getSingleConstantResult() const {
201   assert(isSingleConstant() && "map must have a single constant result");
202   return getResult(0).cast<AffineConstantExpr>().getValue();
203 }
204 
205 unsigned AffineMap::getNumDims() const {
206   assert(map && "uninitialized map storage");
207   return map->numDims;
208 }
209 unsigned AffineMap::getNumSymbols() const {
210   assert(map && "uninitialized map storage");
211   return map->numSymbols;
212 }
213 unsigned AffineMap::getNumResults() const {
214   assert(map && "uninitialized map storage");
215   return map->results.size();
216 }
217 unsigned AffineMap::getNumInputs() const {
218   assert(map && "uninitialized map storage");
219   return map->numDims + map->numSymbols;
220 }
221 
222 ArrayRef<AffineExpr> AffineMap::getResults() const {
223   assert(map && "uninitialized map storage");
224   return map->results;
225 }
226 AffineExpr AffineMap::getResult(unsigned idx) const {
227   assert(map && "uninitialized map storage");
228   return map->results[idx];
229 }
230 
231 /// Folds the results of the application of an affine map on the provided
232 /// operands to a constant if possible. Returns false if the folding happens,
233 /// true otherwise.
234 LogicalResult
235 AffineMap::constantFold(ArrayRef<Attribute> operandConstants,
236                         SmallVectorImpl<Attribute> &results) const {
237   // Attempt partial folding.
238   SmallVector<int64_t, 2> integers;
239   partialConstantFold(operandConstants, &integers);
240 
241   // If all expressions folded to a constant, populate results with attributes
242   // containing those constants.
243   if (integers.empty())
244     return failure();
245 
246   auto range = llvm::map_range(integers, [this](int64_t i) {
247     return IntegerAttr::get(IndexType::get(getContext()), i);
248   });
249   results.append(range.begin(), range.end());
250   return success();
251 }
252 
253 AffineMap
254 AffineMap::partialConstantFold(ArrayRef<Attribute> operandConstants,
255                                SmallVectorImpl<int64_t> *results) const {
256   assert(getNumInputs() == operandConstants.size());
257 
258   // Fold each of the result expressions.
259   AffineExprConstantFolder exprFolder(getNumDims(), operandConstants);
260   SmallVector<AffineExpr, 4> exprs;
261   exprs.reserve(getNumResults());
262 
263   for (auto expr : getResults()) {
264     auto folded = exprFolder.constantFold(expr);
265     // If did not fold to a constant, keep the original expression, and clear
266     // the integer results vector.
267     if (folded) {
268       exprs.push_back(
269           getAffineConstantExpr(folded.getInt(), folded.getContext()));
270       if (results)
271         results->push_back(folded.getInt());
272     } else {
273       exprs.push_back(expr);
274       if (results) {
275         results->clear();
276         results = nullptr;
277       }
278     }
279   }
280 
281   return get(getNumDims(), getNumSymbols(), exprs, getContext());
282 }
283 
284 /// Walk all of the AffineExpr's in this mapping. Each node in an expression
285 /// tree is visited in postorder.
286 void AffineMap::walkExprs(std::function<void(AffineExpr)> callback) const {
287   for (auto expr : getResults())
288     expr.walk(callback);
289 }
290 
291 /// This method substitutes any uses of dimensions and symbols (e.g.
292 /// dim#0 with dimReplacements[0]) in subexpressions and returns the modified
293 /// expression mapping.  Because this can be used to eliminate dims and
294 /// symbols, the client needs to specify the number of dims and symbols in
295 /// the result.  The returned map always has the same number of results.
296 AffineMap AffineMap::replaceDimsAndSymbols(ArrayRef<AffineExpr> dimReplacements,
297                                            ArrayRef<AffineExpr> symReplacements,
298                                            unsigned numResultDims,
299                                            unsigned numResultSyms) const {
300   SmallVector<AffineExpr, 8> results;
301   results.reserve(getNumResults());
302   for (auto expr : getResults())
303     results.push_back(
304         expr.replaceDimsAndSymbols(dimReplacements, symReplacements));
305 
306   return get(numResultDims, numResultSyms, results, getContext());
307 }
308 
309 AffineMap AffineMap::compose(AffineMap map) {
310   assert(getNumDims() == map.getNumResults() && "Number of results mismatch");
311   // Prepare `map` by concatenating the symbols and rewriting its exprs.
312   unsigned numDims = map.getNumDims();
313   unsigned numSymbolsThisMap = getNumSymbols();
314   unsigned numSymbols = numSymbolsThisMap + map.getNumSymbols();
315   SmallVector<AffineExpr, 8> newDims(numDims);
316   for (unsigned idx = 0; idx < numDims; ++idx) {
317     newDims[idx] = getAffineDimExpr(idx, getContext());
318   }
319   SmallVector<AffineExpr, 8> newSymbols(numSymbols);
320   for (unsigned idx = numSymbolsThisMap; idx < numSymbols; ++idx) {
321     newSymbols[idx - numSymbolsThisMap] =
322         getAffineSymbolExpr(idx, getContext());
323   }
324   auto newMap =
325       map.replaceDimsAndSymbols(newDims, newSymbols, numDims, numSymbols);
326   SmallVector<AffineExpr, 8> exprs;
327   exprs.reserve(getResults().size());
328   for (auto expr : getResults())
329     exprs.push_back(expr.compose(newMap));
330   return AffineMap::get(numDims, numSymbols, exprs, map.getContext());
331 }
332 
333 bool AffineMap::isProjectedPermutation() {
334   if (getNumSymbols() > 0)
335     return false;
336   SmallVector<bool, 8> seen(getNumInputs(), false);
337   for (auto expr : getResults()) {
338     if (auto dim = expr.dyn_cast<AffineDimExpr>()) {
339       if (seen[dim.getPosition()])
340         return false;
341       seen[dim.getPosition()] = true;
342       continue;
343     }
344     return false;
345   }
346   return true;
347 }
348 
349 bool AffineMap::isPermutation() {
350   if (getNumDims() != getNumResults())
351     return false;
352   return isProjectedPermutation();
353 }
354 
355 AffineMap AffineMap::getSubMap(ArrayRef<unsigned> resultPos) {
356   SmallVector<AffineExpr, 4> exprs;
357   exprs.reserve(resultPos.size());
358   for (auto idx : resultPos) {
359     exprs.push_back(getResult(idx));
360   }
361   return AffineMap::get(getNumDims(), getNumSymbols(), exprs, getContext());
362 }
363 
364 AffineMap mlir::simplifyAffineMap(AffineMap map) {
365   SmallVector<AffineExpr, 8> exprs;
366   for (auto e : map.getResults()) {
367     exprs.push_back(
368         simplifyAffineExpr(e, map.getNumDims(), map.getNumSymbols()));
369   }
370   return AffineMap::get(map.getNumDims(), map.getNumSymbols(), exprs,
371                         map.getContext());
372 }
373 
374 AffineMap mlir::removeDuplicateExprs(AffineMap map) {
375   auto results = map.getResults();
376   SmallVector<AffineExpr, 4> uniqueExprs(results.begin(), results.end());
377   uniqueExprs.erase(std::unique(uniqueExprs.begin(), uniqueExprs.end()),
378                     uniqueExprs.end());
379   return AffineMap::get(map.getNumDims(), map.getNumSymbols(), uniqueExprs,
380                         map.getContext());
381 }
382 
383 AffineMap mlir::inversePermutation(AffineMap map) {
384   if (map.isEmpty())
385     return map;
386   assert(map.getNumSymbols() == 0 && "expected map without symbols");
387   SmallVector<AffineExpr, 4> exprs(map.getNumDims());
388   for (auto en : llvm::enumerate(map.getResults())) {
389     auto expr = en.value();
390     // Skip non-permutations.
391     if (auto d = expr.dyn_cast<AffineDimExpr>()) {
392       if (exprs[d.getPosition()])
393         continue;
394       exprs[d.getPosition()] = getAffineDimExpr(en.index(), d.getContext());
395     }
396   }
397   SmallVector<AffineExpr, 4> seenExprs;
398   seenExprs.reserve(map.getNumDims());
399   for (auto expr : exprs)
400     if (expr)
401       seenExprs.push_back(expr);
402   if (seenExprs.size() != map.getNumInputs())
403     return AffineMap();
404   return AffineMap::get(map.getNumResults(), 0, seenExprs, map.getContext());
405 }
406 
407 AffineMap mlir::concatAffineMaps(ArrayRef<AffineMap> maps) {
408   unsigned numResults = 0;
409   for (auto m : maps)
410     numResults += m.getNumResults();
411   unsigned numDims = 0;
412   SmallVector<AffineExpr, 8> results;
413   results.reserve(numResults);
414   for (auto m : maps) {
415     assert(m.getNumSymbols() == 0 && "expected map without symbols");
416     results.append(m.getResults().begin(), m.getResults().end());
417     numDims = std::max(m.getNumDims(), numDims);
418   }
419   return AffineMap::get(numDims, /*numSymbols=*/0, results,
420                         maps.front().getContext());
421 }
422 
423 //===----------------------------------------------------------------------===//
424 // MutableAffineMap.
425 //===----------------------------------------------------------------------===//
426 
427 MutableAffineMap::MutableAffineMap(AffineMap map)
428     : numDims(map.getNumDims()), numSymbols(map.getNumSymbols()),
429       context(map.getContext()) {
430   for (auto result : map.getResults())
431     results.push_back(result);
432 }
433 
434 void MutableAffineMap::reset(AffineMap map) {
435   results.clear();
436   numDims = map.getNumDims();
437   numSymbols = map.getNumSymbols();
438   context = map.getContext();
439   for (auto result : map.getResults())
440     results.push_back(result);
441 }
442 
443 bool MutableAffineMap::isMultipleOf(unsigned idx, int64_t factor) const {
444   if (results[idx].isMultipleOf(factor))
445     return true;
446 
447   // TODO(bondhugula): use simplifyAffineExpr and FlatAffineConstraints to
448   // complete this (for a more powerful analysis).
449   return false;
450 }
451 
452 // Simplifies the result affine expressions of this map. The expressions have to
453 // be pure for the simplification implemented.
454 void MutableAffineMap::simplify() {
455   // Simplify each of the results if possible.
456   // TODO(ntv): functional-style map
457   for (unsigned i = 0, e = getNumResults(); i < e; i++) {
458     results[i] = simplifyAffineExpr(getResult(i), numDims, numSymbols);
459   }
460 }
461 
462 AffineMap MutableAffineMap::getAffineMap() const {
463   return AffineMap::get(numDims, numSymbols, results, context);
464 }
465