1 //===- Fusion.cpp - Implementation of linalg Fusion -----------------------===//
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 file implements the linalg dialect Fusion on tensors operations pass.
10 //
11 //===----------------------------------------------------------------------===//
12 #include "PassDetail.h"
13 #include "mlir/Dialect/Affine/IR/AffineOps.h"
14 #include "mlir/Dialect/Linalg/IR/LinalgOps.h"
15 #include "mlir/Dialect/Linalg/IR/LinalgTypes.h"
16 #include "mlir/Dialect/Linalg/Passes.h"
17 #include "mlir/Dialect/Linalg/Transforms/Transforms.h"
18 #include "mlir/Dialect/Linalg/Utils/Utils.h"
19 #include "mlir/IR/AffineExpr.h"
20 #include "mlir/IR/AffineMap.h"
21 #include "mlir/IR/PatternMatch.h"
22 #include "mlir/Support/LLVM.h"
23 #include "mlir/Transforms/GreedyPatternRewriteDriver.h"
24 
25 using namespace mlir;
26 using namespace mlir::linalg;
27 
28 /// Implementation of fusion of generic ops and indexed_generic ops.
29 // struct FuseGenericOpsOnTensors {
30 static bool areTensorOpsFusable(LinalgOp producer, LinalgOp consumer,
31                                 unsigned consumerIdx) {
32   // Producer and consumer must have tensor semantics.
33   if (!producer.hasTensorSemantics() || !consumer.hasTensorSemantics())
34     return false;
35 
36   // Verify that
37   // - the producer has all "parallel" iterator type.
38   if (producer.getNumParallelLoops() != producer.getNumLoops())
39     return false;
40 
41   // Get the consumer index map. The number of results of the consumer index
42   // map must match the number of loops of the producer.
43   AffineMap consumerIndexMap = consumer.getIndexingMap(consumerIdx);
44   if (consumerIndexMap.getNumResults() != producer.getNumLoops())
45     return false;
46 
47   // Finally the index_map for the result must be invertible. For now just
48   // verify it is a permutation.
49   AffineMap producerResultIndexMap = producer.getOutputIndexingMap(0);
50   return producerResultIndexMap.isPermutation();
51 }
52 
53 /// Append to `fusedOpIndexingMapAttrs` the indexing maps for the operands of
54 /// the `producer` to use in the fused operation given the indexing map of the
55 /// result of the producer in the consumer.
56 static void getIndexingMapOfProducerOperandsInFusedOp(
57     LinalgOp producer, AffineMap fusedConsumerArgIndexMap,
58     SmallVectorImpl<Attribute> &fusedOpIndexingMapAttrs) {
59   // The indexing map in the consumer op (fusedConsumerArgIndexMap) is a map
60   // from consumer loop -> consumer arg tensor index/producer result tensor
61   // index. The fused loop is same as the consumer loop. For each producer arg
62   // the indexing map to be computed is a map from consumer loop -> producer
63   // arg tensor index.
64 
65   AffineMap producerResultIndexMap = producer.getOutputIndexingMap(0);
66   // producerResultIndexMap is a map from producer loop -> tensor index.
67   // Compute the inverse to get map from tensor index -> producer loop.
68   // The inverse is a map from producer result tensor index -> producer loop.
69   AffineMap invProducerResultIndexMap =
70       inversePermutation(producerResultIndexMap);
71   assert(invProducerResultIndexMap &&
72          "expected producer result indexig map to be invertible");
73   for (unsigned argNum : llvm::seq<unsigned>(0, producer.getNumInputs())) {
74     // argMap is a map from producer loop -> producer arg tensor index.
75     AffineMap argMap = producer.getInputIndexingMap(argNum);
76 
77     // Compose argMap with invProducerResultIndexMap to get a map from
78     // producer result tensor index -> producer arg tensor index.
79     AffineMap t1 = argMap.compose(invProducerResultIndexMap);
80 
81     // Compose t1 with fusedConsumerArgIndexMap gives an indexing map from
82     // consumer loop/ fused loop -> producer arg tensor index.
83     AffineMap indexingMap = t1.compose(fusedConsumerArgIndexMap);
84     fusedOpIndexingMapAttrs.push_back(AffineMapAttr::get(indexingMap));
85   }
86 }
87 
88 /// Generate the region of the fused tensor operation. The region of the fused
89 /// op must be empty.
90 static void generateFusedTensorOpRegion(PatternRewriter &rewriter,
91                                         Operation *fusedOp, LinalgOp producer,
92                                         LinalgOp consumer,
93                                         AffineMap consumerToProducerLoopsMap,
94                                         unsigned consumerIdx, unsigned nloops) {
95   // Build the region of the fused op.
96   Block &producerBlock = producer.getOperation()->getRegion(0).front();
97   Block &consumerBlock = consumer.getOperation()->getRegion(0).front();
98   Block *fusedBlock = new Block();
99   fusedOp->getRegion(0).push_back(fusedBlock);
100   BlockAndValueMapping mapper;
101   OpBuilder::InsertionGuard guard(rewriter);
102   rewriter.setInsertionPointToStart(fusedBlock);
103 
104   // The block arguments are
105   // [index_0, index_1, ... ,
106   //   consumer_operand_0, ... , consumer_operand_(`consumerIdx`-1),
107   //   producer_operand_0, ... , producer_operand_(n-1)],
108   //   consumer_operand_(`consumerIdx`), .. consumer_operand_(m-1)]
109   // , where n is the number of producer's operand and m is the number
110   // consumer's operand.
111   // If both `numProducerIndices` and `numConsumerIndices` are zero, this is a
112   // generic op. In this case, there are no indices in block arguments.
113   unsigned numProducerIndices = isa<IndexedGenericOp>(producer.getOperation())
114                                     ? producer.getNumLoops()
115                                     : 0;
116   unsigned numConsumerIndices = isa<IndexedGenericOp>(consumer.getOperation())
117                                     ? consumer.getNumLoops()
118                                     : 0;
119   unsigned numFusedOpIndices =
120       (isa<IndexedGenericOp>(producer.getOperation()) ||
121        isa<IndexedGenericOp>(consumer.getOperation()))
122           ? std::max(producer.getNumLoops(), consumer.getNumLoops())
123           : 0;
124   // Firstly, add all the indices to the block arguments.
125   for (unsigned i = 0, e = numFusedOpIndices; i < e; ++i)
126     fusedBlock->addArgument(rewriter.getIndexType());
127   // Map the arguments for the unmodified args from the consumer.
128   for (auto consumerArg : llvm::enumerate(consumerBlock.getArguments())) {
129     if (consumerArg.index() == consumerIdx + numConsumerIndices) {
130       // Map the arguments for the args from the producer.
131       for (auto producerArg : llvm::enumerate(producerBlock.getArguments())) {
132         // If producer is an indexed_generic op, map the indices from consumer
133         // loop to producer loop (because the fusedOp is built based on
134         // consumer's perspective).
135         if (producerArg.index() < numProducerIndices) {
136           auto newIndex = rewriter.create<mlir::AffineApplyOp>(
137               producer.getLoc(),
138               consumerToProducerLoopsMap.getSubMap(producerArg.index()),
139               fusedBlock->getArguments().take_front(numFusedOpIndices));
140           mapper.map(producerArg.value(), newIndex);
141         } else {
142           mapper.map(producerArg.value(),
143                      fusedBlock->addArgument(producerArg.value().getType()));
144         }
145       }
146       continue;
147     }
148 
149     // If consumer is an indexed_generic op, map the indices to the block
150     // arguments directly. Otherwise, add the same type of arugment and map to
151     // it.
152     if (consumerArg.index() < numConsumerIndices) {
153       mapper.map(consumerArg.value(),
154                  fusedBlock->getArgument(consumerArg.index()));
155     } else {
156       mapper.map(consumerArg.value(),
157                  fusedBlock->addArgument(consumerArg.value().getType()));
158     }
159   }
160 
161   // Add operations from producer (except the yield operation) to the fused
162   // op.
163   for (auto &op : producerBlock.getOperations()) {
164     if (auto yieldOp = dyn_cast<linalg::YieldOp>(op)) {
165       // Lookup the value the yield operation is mapped to.
166       Value yieldVal = yieldOp.getOperand(0);
167       if (Value clonedVal = mapper.lookupOrNull(yieldVal))
168         mapper.map(consumerBlock.getArgument(consumerIdx + numConsumerIndices),
169                    clonedVal);
170       continue;
171     }
172     rewriter.clone(op, mapper);
173   }
174   for (auto &op : consumerBlock.getOperations())
175     rewriter.clone(op, mapper);
176 }
177 
178 static Optional<SmallVector<Value, 1>>
179 fuseTensorOpsImpl(LinalgOp producer, LinalgOp consumer, unsigned consumerIdx,
180                   PatternRewriter &rewriter,
181                   OperationFolder *folder = nullptr) {
182   if (!areTensorOpsFusable(producer, consumer, consumerIdx))
183     return llvm::None;
184 
185   unsigned numFusedOperands =
186       producer.getNumInputs() + consumer.getNumInputs() - 1;
187 
188   // Compute the fused operands list,
189   SmallVector<Value, 2> fusedOperands;
190   fusedOperands.reserve(numFusedOperands);
191   auto consumerOperands = consumer.getInputs();
192   auto producerOperands = producer.getInputs();
193   fusedOperands.assign(consumerOperands.begin(),
194                        std::next(consumerOperands.begin(), consumerIdx));
195   fusedOperands.append(producerOperands.begin(), producerOperands.end());
196   fusedOperands.append(std::next(consumerOperands.begin(), consumerIdx + 1),
197                        consumerOperands.end());
198 
199   // Compute indexing_maps for the fused operation. The indexing_maps for the
200   // operands of the consumers that arent fused are the same. The
201   // indexing_maps for the producers need to be computed based on the
202   // indexing_map of the operand at consumerIdx in the consumer.
203   SmallVector<Attribute, 4> fusedIndexMaps;
204   auto consumerIndexMaps = consumer.indexing_maps();
205   fusedIndexMaps.reserve(fusedOperands.size() + consumer.getNumOutputs());
206   fusedIndexMaps.assign(consumerIndexMaps.begin(),
207                         std::next(consumerIndexMaps.begin(), consumerIdx));
208   // Compute indexing maps for the producer args in the fused operation.
209   getIndexingMapOfProducerOperandsInFusedOp(
210       producer, consumer.getInputIndexingMap(consumerIdx), fusedIndexMaps);
211 
212   // Append the indexing maps for the remaining consumer operands.
213   fusedIndexMaps.append(std::next(consumerIndexMaps.begin(), consumerIdx + 1),
214                         consumerIndexMaps.end());
215 
216   // Generate the fused op.
217   // Tensor-level fusion is only on ops without initTensors and outputBuffers.
218   LinalgOp fusedOp;
219   if (isa<GenericOp>(producer.getOperation()) &&
220       isa<GenericOp>(consumer.getOperation())) {
221     fusedOp = rewriter
222                   .create<GenericOp>(consumer.getLoc(),
223                                      consumer.getOperation()->getResultTypes(),
224                                      /*inputs=*/fusedOperands,
225                                      /*outputBuffers=*/ValueRange{},
226                                      /*initTensors=*/ValueRange{},
227                                      rewriter.getArrayAttr(fusedIndexMaps),
228                                      consumer.iterator_types(),
229                                      /*doc=*/nullptr,
230                                      /*library_call=*/nullptr,
231                                      /*symbol_source=*/nullptr)
232                   .getOperation();
233   } else {
234     fusedOp =
235         rewriter
236             .create<IndexedGenericOp>(consumer.getLoc(),
237                                       consumer.getOperation()->getResultTypes(),
238                                       /*inputs=*/fusedOperands,
239                                       /*outputBuffers=*/ValueRange{},
240                                       /*initTensors=*/ValueRange{},
241                                       rewriter.getArrayAttr(fusedIndexMaps),
242                                       consumer.iterator_types(),
243                                       /*doc=*/nullptr,
244                                       /*library_call=*/nullptr,
245                                       /*symbol_source=*/nullptr)
246             .getOperation();
247   }
248 
249   // Construct an AffineMap from consumer loops to producer loops.
250   // consumer loop -> tensor index
251   AffineMap consumerResultIndexMap = consumer.getInputIndexingMap(consumerIdx);
252   // producer loop -> tensor index
253   AffineMap producerResultIndexMap = producer.getOutputIndexingMap(0);
254   // tensor index -> producer loop
255   AffineMap invProducerResultIndexMap =
256       inversePermutation(producerResultIndexMap);
257   assert(invProducerResultIndexMap &&
258          "expected producer result indexig map to be invertible");
259   // consumer loop -> producer loop
260   AffineMap consumerToProducerLoopsMap =
261       invProducerResultIndexMap.compose(consumerResultIndexMap);
262 
263   generateFusedTensorOpRegion(rewriter, fusedOp.getOperation(), producer,
264                               consumer, consumerToProducerLoopsMap, consumerIdx,
265                               consumer.getNumLoops());
266   return SmallVector<Value, 1>(fusedOp.getOperation()->getResults());
267 }
268 
269 /// Linearize the expressions in `sourceMap` based on the `reassociationMaps`
270 /// provided, given the shape of the source tensor that corresponds to the
271 /// `sourceMap`. Note that this implicitly assumes that the tensors dimensions
272 /// are "row-major" ordered logically.
273 ///
274 /// For example:
275 ///
276 /// %0 = op ... : tensor<?x?x4x5xf32>
277 /// with output index_map `affine_map<(d0, d1, d2, d3) -> (d0, d1, d2, d3)>`
278 ///
279 /// and reshape:
280 /// %1 = linalg.tensor_reshape %0 [affine_map<(i, j, k, l) -> (i)>,
281 ///                                affine_map<(i, j, k, l) -> (j, k, l)>] :
282 ///        tensor<?x?x4x5xf32> into tensor<?x?xf32>
283 ///
284 /// would be rewritten into:
285 /// %0 = op ... : tensor<?x?x4x5xf32>
286 /// with output index_map
287 ///   `affine_map<(d0, d1, d2, d3) -> (d0, d1 * 20 + d2 * 5 + d3)>`
288 static AffineMap linearizeCollapsedDims(AffineMap sourceMap,
289                                         ArrayRef<int64_t> sourceShape,
290                                         ArrayRef<AffineMap> reassociationMaps) {
291   SmallVector<AffineExpr, 4> resultExprs;
292   resultExprs.reserve(reassociationMaps.size());
293   ArrayRef<AffineExpr> sourceExprs = sourceMap.getResults();
294   MLIRContext *context = sourceMap.getContext();
295 
296   // Compute the result exprs based on the reassociation maps.
297   for (AffineMap map : reassociationMaps) {
298     ArrayRef<AffineExpr> collapsedDims = map.getResults();
299     // Assume that they are in-order and contiguous (already checked in
300     // verifier).
301     assert(!collapsedDims.empty());
302     unsigned startDim =
303         collapsedDims.front().cast<AffineDimExpr>().getPosition();
304     AffineExpr linearizedExpr = makeCanonicalStridedLayoutExpr(
305         sourceShape.slice(startDim, collapsedDims.size()),
306         sourceExprs.slice(startDim, collapsedDims.size()), context);
307     resultExprs.push_back(linearizedExpr);
308   }
309   return AffineMap::get(sourceMap.getNumDims(), sourceMap.getNumSymbols(),
310                         resultExprs, context);
311 }
312 
313 /// Checks if the `reshapeOp` can be fused with it consumer (if `asProducer` is
314 /// true) or its producer (if `asProducer` is false) given the indexing map at
315 /// its use.
316 static bool isTensorReshapeOpFoldableByLinearization(TensorReshapeOp reshapeOp,
317                                                      AffineMap useIndexMap,
318                                                      bool asProducer) {
319   RankedTensorType returnType = reshapeOp.getResultType();
320   RankedTensorType operandType = reshapeOp.getSrcType();
321   // Reshape is fusable with its consumer (i.e. reshape as a producer) when its
322   // operand is of lesser rank than the result. Fusing when operand has higher
323   // rank will require use of mods and divs in the indexing maps of the fused op
324   // which would make it non-invertible. Similarly reshape is fused with its
325   // producer (i.e. reshape as consumer) only if the return type has lesser
326   // rank.
327   if ((asProducer && reshapeOp.getSrcType().hasStaticShape() &&
328        returnType.getRank() < operandType.getRank()) ||
329       (!asProducer && reshapeOp.getResultType().hasStaticShape() &&
330        operandType.getRank() < returnType.getRank()))
331     return false;
332   return useIndexMap.isPermutation();
333 }
334 
335 /// Based on the type of `op` create a linalg op of the same type, i.e. if `op`
336 /// is a linalg.generic operation, the create a `linalg.generic` operation with
337 /// the given `args`. Expects `op` to be `linalg.generic` or
338 /// `linalg.indexed_generic`.
339 template <typename... Args>
340 static LinalgOp createLinalgOpOfSameType(LinalgOp op, PatternRewriter &rewriter,
341                                          Args... args) {
342   if (isa<GenericOp>(op.getOperation()))
343     return cast<LinalgOp>(rewriter.create<GenericOp>(args...).getOperation());
344   if (isa<IndexedGenericOp>(op.getOperation()))
345     return cast<LinalgOp>(
346         rewriter.create<IndexedGenericOp>(args...).getOperation());
347   llvm_unreachable(
348       "expected only linalg.generic or linalg.indexed_generic ops");
349   return nullptr;
350 }
351 
352 /// Conditions for folding a generic/indexed-generic operation with a reshape op
353 /// by expanding the iteration space dimensionality for tensor operations. These
354 /// are preconditions assumed by `foldReshapeByDimExpansion` which implements
355 /// the following fusion pattern.
356 ///
357 ///  Consider
358 ///
359 ///  %c = linalg.generic ins(%a, %b : memref<?x?x?xf32>, memref<?x?xf32>)
360 ///         indexing_maps = [affine_map<(d0, d1, d2) -> (d1, d0, d2)>,
361 ///                          affine_map<(d0, d1, d2) -> (d1, d2)>,
362 ///                          affine_map<(d0, d1, d2) -> (d0, d2, d1)>]
363 ///  %d = linalg.tensor_reshape %c
364 ///         [affine_map<(d0, d1, d2, d3, d4, d5) -> (d0, d1)>,
365 ///          affine_map<(d0, d1, d2, d3, d4, d5) -> (d2)>,
366 ///          affine_map<(d0, d1, d2, d3, d4, d5) -> (d3, d4, d5)>]
367 ///       : tensor<?x?x?xf32> into tensor<?x?x?x?x?x?xf32>
368 ///
369 ///  The reshape can be folded into the `linalgOp` if the
370 ///  generic/indexed-generic op loop dimensionality is increased to match the
371 ///  result (operand) of the tensor_reshape when the reshape is expanding
372 ///  (folding). The indexing_map of the fused tensor in the `linalgOp` and the
373 ///  reassociation map helps compute the indexing maps of the modified op. For
374 ///  the above example, based on the reassociation map it can be concluded that
375 ///
376 ///  - The loop used to access the first dimension of the fused tensor is split
377 ///    into two.
378 ///  - The loop used to access the second dimension of the fused tensor is kept
379 ///    as is.
380 ///  - The loop used to access the third dimension of the fused tensor is split
381 ///    into three.
382 ///
383 ///  i.e. (e0, e1, e2, e3, e4) is the domain of the indexing map of the modified
384 ///  op, then
385 ///
386 ///   d0 -> e0, e1
387 ///   d1 -> e2, e3, e4
388 ///   d2 -> e5
389 ///
390 ///  substituting this, the generic op can be rewritten as
391 ///
392 ///  %d = linalg.generic ins(%0, %1 : )
393 ///        indexing_maps =
394 ///         [affine_map<(e0, e1, e2, e3, e4, e5) -> (e2, e3, e4, e0, e1, e5)>,
395 ///          affine_map<(e0, e1, e2, e3, e4, e5) -> (e2, e3, e4, e5)>,
396 ///          affine_map<(e0, e1, e2, e3, e4, e5) -> (e0, e1, e5, e2, e3, e4)>]
397 ///
398 ///  Since operands to the linalg generic are now 5D, reshapes can be introduced
399 ///  to make it consistent
400 ///
401 ///  %0 = linalg.tensor_reshape %a
402 ///         [affine_map<(e0, e1, e2, e3, e4, e5) -> (e0, e1, e2),
403 ///          affine_map<(e0, e1, e2, e3, e4, e5) -> (e3, e4),
404 ///          affine_map<(e0, e1, e2, e3, e4, e5) -> (e5)]
405 ///       : tensor<?x?x?xf32> into tensor<?x?x?x?x?x?xf32>
406 ///  %1 = linalg.tensor_reshape %b
407 ///         [affine_map<(e0, e1, e2, e3) -> (e0, e1, e2),
408 ///          affine_map<(e0, e1, e2, e3) -> (e3)]
409 ///       : tensor<?x?x?xf32> into tensor<?x?x?x?xf32>
410 ///
411 ///  The added reshapes are again expanding patterns, so they will get fused
412 ///  with its producers if possible.
413 static bool isFusableWithReshapeByDimExpansion(LinalgOp linalgOp,
414                                                unsigned fusedTensorIndex) {
415   // Is fusable only if:
416   // - The linalgOp is a generic op.
417   // - All the indexing maps for operands in linalgOp are projected
418   //   permutations.
419   // - The indexing map at the position representing the fused tensor is a
420   //   permutation.
421   // - All the loops in linalgOp are parallel loops.
422   return isa<GenericOp>(linalgOp.getOperation()) &&
423          linalgOp.hasTensorSemantics() &&
424          llvm::all_of(linalgOp.indexing_maps().getValue().take_front(
425                           linalgOp.getNumInputs()),
426                       [](Attribute attr) {
427                         return attr.cast<AffineMapAttr>()
428                             .getValue()
429                             .isProjectedPermutation();
430                       }) &&
431          linalgOp.getIndexingMap(fusedTensorIndex).isPermutation() &&
432          llvm::all_of(linalgOp.iterator_types(), [](Attribute attr) {
433            return attr.cast<StringAttr>().getValue() ==
434                   getParallelIteratorTypeName();
435          });
436 }
437 
438 /// Implements the fusion of a tensor_reshape op and a generic/indexed_generic
439 /// op as explained in `isFusableWithReshapeByExpansion`. Assumes that those
440 /// conditions have been satisfied.
441 static Optional<SmallVector<Value, 1>>
442 fuseWithReshapeByExpansion(LinalgOp linalgOp, TensorReshapeOp reshapeOp,
443                            unsigned fusedTensorIndex, PatternRewriter &rewriter,
444                            OperationFolder *folder = nullptr) {
445   assert(isFusableWithReshapeByDimExpansion(linalgOp, fusedTensorIndex) &&
446          "preconditions for fuse operation failed");
447   // Check if reshape is expanding or collapsing.
448   bool isExpanding =
449       reshapeOp.getSrcType().getRank() < reshapeOp.getResultType().getRank();
450   RankedTensorType expandedType =
451       isExpanding ? reshapeOp.getResultType() : reshapeOp.getSrcType();
452   RankedTensorType foldedType =
453       isExpanding ? reshapeOp.getSrcType() : reshapeOp.getResultType();
454   AffineMap fusedIndexMap = linalgOp.getIndexingMap(fusedTensorIndex);
455 
456   // The reshape is folding/expanding consecutive dimensions. Given the indexing
457   // map of the fused tensor find the number of dimensions each of the loops of
458   // the original op is expanded into. Also record the shape of the expanded
459   // dimensions.
460   ArrayRef<int64_t> expandedShape = expandedType.getShape();
461   SmallVector<unsigned, 4> numFoldedDims(foldedType.getRank(), 0);
462   SmallVector<SmallVector<int64_t, 4>, 4> expandedDimsShape(
463       expandedType.getRank());
464   auto reassociationMaps = reshapeOp.getReassociationMaps();
465   for (auto resultExpr : llvm::enumerate(fusedIndexMap.getResults())) {
466     unsigned pos = resultExpr.value().cast<AffineDimExpr>().getPosition();
467     AffineMap foldedDims = reassociationMaps[resultExpr.index()];
468     numFoldedDims[pos] = foldedDims.getNumResults();
469     ArrayRef<int64_t> shape = expandedShape.slice(
470         foldedDims.getResult(0).cast<AffineDimExpr>().getPosition(),
471         numFoldedDims[pos]);
472     expandedDimsShape[pos].assign(shape.begin(), shape.end());
473   }
474 
475   // The remapping of the indices is then the prefix sum (inclusive) of the
476   // numFoldedDims.
477   SmallVector<unsigned, 4> remapping(numFoldedDims.size() + 1, 0);
478   unsigned sum = 0;
479   for (auto numFoldedDim : llvm::enumerate(numFoldedDims)) {
480     sum += numFoldedDim.value();
481     remapping[numFoldedDim.index() + 1] = sum;
482   }
483 
484   SmallVector<AffineMap, 4> expandedOpIndexingMaps;
485   // Compute the modified indexing maps by replacing every loop (AffineDimExpr)
486   // in the original indexing map with the sequence of loops that it is expanded
487   // to.
488   for (AffineMap indexingMap : linalgOp.getIndexingMaps()) {
489     SmallVector<AffineExpr, 4> newExprs;
490     for (AffineExpr expr : indexingMap.getResults()) {
491       unsigned pos = expr.cast<AffineDimExpr>().getPosition();
492       for (unsigned newPos :
493            llvm::seq<unsigned>(remapping[pos], remapping[pos + 1])) {
494         newExprs.push_back(rewriter.getAffineDimExpr(newPos));
495       }
496     }
497     expandedOpIndexingMaps.push_back(
498         AffineMap::get(remapping.back(), indexingMap.getNumSymbols(), newExprs,
499                        rewriter.getContext()));
500   }
501 
502   // The operands of the expanded op are computed by reshaping the original
503   // operands. The reshape depends on the ordering of the loop used to access
504   // the tensor in the original operation, and are expanded into as many
505   // dimensions as the loop is expanded into (as computed by `remapping`).
506   auto getReshapeInfo =
507       [&](AffineMap operandIndexingMap,
508           SmallVectorImpl<ReassociationIndices> &reassociation,
509           SmallVectorImpl<int64_t> &expandedOpOperandShape) {
510         unsigned reshapeDims = 0;
511         for (AffineExpr expr : operandIndexingMap.getResults()) {
512           unsigned origDim = expr.cast<AffineDimExpr>().getPosition();
513           auto foldedDims = llvm::seq<int64_t>(
514               reshapeDims, reshapeDims + numFoldedDims[origDim]);
515           reassociation.emplace_back(foldedDims.begin(), foldedDims.end());
516           expandedOpOperandShape.append(expandedDimsShape[origDim].begin(),
517                                         expandedDimsShape[origDim].end());
518           reshapeDims += numFoldedDims[origDim];
519         }
520       };
521   SmallVector<Value, 4> expandedOpOperands;
522   for (auto operand : llvm::enumerate(linalgOp.getInputs())) {
523     if (operand.index() == fusedTensorIndex) {
524       expandedOpOperands.push_back(reshapeOp.src());
525       continue;
526     }
527     AffineMap indexingMap = linalgOp.getIndexingMap(operand.index());
528     SmallVector<ReassociationIndices, 4> reassociation;
529     SmallVector<int64_t, 4> expandedOperandShape;
530     getReshapeInfo(indexingMap, reassociation, expandedOperandShape);
531     Type expandedOperandType = RankedTensorType::get(
532         expandedOperandShape,
533         operand.value().getType().cast<ShapedType>().getElementType());
534     if (expandedOperandType != operand.value().getType()) {
535       expandedOpOperands.push_back(rewriter.create<TensorReshapeOp>(
536           linalgOp.getLoc(), expandedOperandType, operand.value(),
537           reassociation));
538     } else {
539       expandedOpOperands.push_back(operand.value());
540     }
541   }
542   SmallVector<Type, 1> resultTypes;
543   SmallVector<SmallVector<ReassociationIndices, 4>, 1> resultReassociation;
544   for (auto result : llvm::enumerate(linalgOp.getOperation()->getResults())) {
545     AffineMap indexingMap =
546         linalgOp.getIndexingMap(linalgOp.getNumInputs() + result.index());
547     SmallVector<ReassociationIndices, 4> reassociation;
548     SmallVector<int64_t, 4> expandedResultShape;
549     getReshapeInfo(indexingMap, reassociation, expandedResultShape);
550     resultTypes.push_back(RankedTensorType::get(
551         expandedResultShape,
552         result.value().getType().cast<ShapedType>().getElementType()));
553     resultReassociation.emplace_back(std::move(reassociation));
554   }
555 
556   // The iterator types of the expanded op are all parallel.
557   SmallVector<StringRef, 4> iteratorTypes(remapping.back(),
558                                           getParallelIteratorTypeName());
559 
560   LinalgOp fusedOp = createLinalgOpOfSameType(
561       linalgOp, rewriter, linalgOp.getLoc(), resultTypes,
562       /*inputs=*/expandedOpOperands,
563       /*outputBuffers=*/ValueRange{},
564       /*initTensors=*/ValueRange{}, expandedOpIndexingMaps, iteratorTypes);
565   Region &fusedRegion = fusedOp.getOperation()->getRegion(0);
566   // TODO: Add support for indexed generic op, which would need mapping the
567   // expanded dimensions to the original dimension arguments.
568   rewriter.cloneRegionBefore(linalgOp.getOperation()->getRegion(0), fusedRegion,
569                              fusedRegion.begin());
570 
571   // Reshape the result values to their original shape if this is a collapsing
572   // reshape folded into its consumer.
573   SmallVector<Value, 1> resultVals;
574   for (auto result : llvm::enumerate(linalgOp.getOperation()->getResults())) {
575     if (!isExpanding &&
576         resultTypes[result.index()] != result.value().getType()) {
577       resultVals.push_back(rewriter.create<TensorReshapeOp>(
578           linalgOp.getLoc(), result.value().getType(),
579           fusedOp.getOperation()->getResult(result.index()),
580           resultReassociation[result.index()]));
581     } else {
582       resultVals.push_back(fusedOp.getOperation()->getResult(result.index()));
583     }
584   }
585   // Assuming a single result.
586   return resultVals;
587 }
588 
589 namespace {
590 
591 /// Pattern to fold tensor_reshape op with its consumer by using the source of
592 /// the reshape op as the operand in the consumer (instead of the result of the
593 /// tensor_reshapeop) when the tensor_reshape op is collapsing. The
594 /// corresponding index map in the consumer needs to be modified to linearize
595 /// the folded dimension.
596 ///
597 /// For example,
598 ///
599 /// #map0 = affine_map<(d0, d1, d2, d3) -> (d0, d1, d2, d3)>
600 /// %0 = linalg.tensor_reshape %arg0
601 ///        [affine_map<(i, j, k, l) -> (i)>, affine_map<(i, j, k, l) -> (j, k)>,
602 ///         affine_map<(i, j, k, l) -> (l)>]
603 ///      tensor<?x?x?xf32> into tensor<?x?x4x?xf32>
604 /// %1 = linalg.generic { indexing_maps = [#map0, #map0, #map0], ... }
605 ///        ins(%0, %arg1 : tensor<?x?x4x?xf32>, tensor<?x?x4x?xf32>) ...
606 ///        -> tensor<?x?x4x?xf32>
607 ///
608 /// can be folded into
609 ///
610 /// #map0 = affine_map<(d0, d1, d2, d3) -> (d0, d1 * 4 + d2, d3)>
611 /// #map1 = affine_map<(d0, d1, d2, d3) -> (d0, d1, d2, d3)>
612 /// %0 = linalg.generic { indexing_maps = [#map0, #map1, #map1] ... }
613 ///        ins(%arg0, %arg1 : tensor<?x?x?xf32>, tensor<?x?x4x?xf32>) ...
614 ///        -> tensor<?x?x4x?xf32>
615 template <typename LinalgOpTy>
616 struct FoldProducerReshapeOpByLinearization
617     : public OpRewritePattern<LinalgOpTy> {
618   using OpRewritePattern<LinalgOpTy>::OpRewritePattern;
619 
620   LogicalResult matchAndRewrite(LinalgOpTy op,
621                                 PatternRewriter &rewriter) const override {
622     if (!op.hasTensorSemantics())
623       return failure();
624     LinalgOp linalgOp = cast<LinalgOp>(op.getOperation());
625     for (auto operand : llvm::enumerate(linalgOp.getInputs())) {
626       TensorReshapeOp reshapeOp =
627           operand.value().getDefiningOp<TensorReshapeOp>();
628       if (!reshapeOp ||
629           !isTensorReshapeOpFoldableByLinearization(
630               reshapeOp, linalgOp.getInputIndexingMap(operand.index()),
631               /*asProducer =*/true))
632         continue;
633 
634       // Compute the fused operands list,
635       SmallVector<Value, 2> fusedOperands(linalgOp.getInputs());
636       fusedOperands[operand.index()] = reshapeOp.src();
637 
638       // Compute indexing_maps for the fused operation. The indexing_maps for
639       // the operands of the consumers that arent fused are the same.
640       SmallVector<AffineMap, 4> fusedIndexMaps = llvm::to_vector<4>(
641           op.indexing_maps().template getAsValueRange<AffineMapAttr>());
642 
643       // Accepted consumer maps are either identity or permutation.
644       auto invMap = inversePermutation(fusedIndexMaps[operand.index()]);
645 
646       // Compute the indexing map to use for the result of the producer.
647       AffineMap modifiedMap =
648           linearizeCollapsedDims(invMap, reshapeOp.getResultType().getShape(),
649                                  reshapeOp.getReassociationMaps());
650       for (AffineExpr expr : modifiedMap.getResults()) {
651         if (!expr.isPureAffine())
652           return failure();
653       }
654       fusedIndexMaps[operand.index()] = modifiedMap;
655 
656       // Further check that the resulting index maps can be fused and
657       // inverted. Without this the resultant op is not legal.
658       if (!inversePermutation(concatAffineMaps(fusedIndexMaps)))
659         return op.emitRemark("fused op loop bound computation failed");
660 
661       rewriter.startRootUpdate(op);
662       op.getOperation()->setOperands(fusedOperands);
663       op.indexing_mapsAttr(rewriter.getAffineMapArrayAttr(fusedIndexMaps));
664       rewriter.finalizeRootUpdate(op);
665       if (reshapeOp.use_empty())
666         rewriter.eraseOp(reshapeOp);
667       return success();
668     }
669     return op.emitRemark("no fusion candidates found");
670   }
671 };
672 
673 /// Pattern to fuse a tensor_reshape op with its consumer generic op, when the
674 /// reshape op is collapsing dimensions. The dimensionality of the loop in the
675 /// consumer generic op is expanded.
676 struct FoldWithProducerReshapeOpByExpansion
677     : public OpRewritePattern<GenericOp> {
678   using OpRewritePattern<GenericOp>::OpRewritePattern;
679 
680   LogicalResult matchAndRewrite(GenericOp genericOp,
681                                 PatternRewriter &rewriter) const override {
682     LinalgOp linalgOp = cast<LinalgOp>(genericOp.getOperation());
683     for (auto operand : llvm::enumerate(linalgOp.getInputs())) {
684       TensorReshapeOp reshapeOp =
685           operand.value().getDefiningOp<TensorReshapeOp>();
686       if (!reshapeOp)
687         continue;
688 
689       // Fold only if
690       // - The tensor reshape op is folding.
691       // - All constraints of fusing with reshape by expansion are met.
692       if (reshapeOp.getSrcType().getRank() <
693               reshapeOp.getResultType().getRank() ||
694           !isFusableWithReshapeByDimExpansion(linalgOp, operand.index()))
695         continue;
696 
697       Optional<SmallVector<Value, 1>> replacementValues =
698           fuseWithReshapeByExpansion(linalgOp, reshapeOp, operand.index(),
699                                      rewriter);
700       if (!replacementValues)
701         return failure();
702       rewriter.replaceOp(genericOp, replacementValues.getValue());
703       if (reshapeOp.use_empty())
704         rewriter.eraseOp(reshapeOp);
705       return success();
706     }
707     return failure();
708   }
709 };
710 
711 /// Pattern to fold tensor_reshape op with its producer. The corresponding index
712 /// map in the consumer needs to be modified to linearize the folded dimension.
713 struct FoldConsumerReshapeOpByLinearization
714     : public OpRewritePattern<TensorReshapeOp> {
715   using OpRewritePattern<TensorReshapeOp>::OpRewritePattern;
716 
717   LogicalResult matchAndRewrite(TensorReshapeOp reshapeOp,
718                                 PatternRewriter &rewriter) const override {
719     LinalgOp producer = reshapeOp.src().getDefiningOp<LinalgOp>();
720     if (!producer ||
721         !isa<GenericOp, IndexedGenericOp>(producer.getOperation()) ||
722         !producer.hasTensorSemantics() || producer.getNumOutputs() != 1 ||
723         !isTensorReshapeOpFoldableByLinearization(
724             reshapeOp, producer.getOutputIndexingMap(0), /*asProducer =*/false))
725       return failure();
726     // The indexing_maps for the operands of the fused operation are same as
727     // those for the operands of the producer.
728     SmallVector<AffineMap, 4> fusedIndexMaps = llvm::to_vector<4>(
729         producer.indexing_maps().getAsValueRange<AffineMapAttr>());
730 
731     auto invMap = inversePermutation(producer.getOutputIndexingMap(0));
732 
733     // Compute the indexing map to use for the operand of the producer.
734     AffineMap modifiedMap =
735         linearizeCollapsedDims(invMap, reshapeOp.getSrcType().getShape(),
736                                reshapeOp.getReassociationMaps());
737     for (AffineExpr expr : modifiedMap.getResults()) {
738       if (!expr.isPureAffine())
739         return reshapeOp.emitRemark("fused op indexing map is not affine");
740     }
741     fusedIndexMaps.back() = modifiedMap;
742 
743     // Further check that the resulting index maps can be fused and
744     // inverted. Without this the resultant op is not legal.
745     if (!inversePermutation(concatAffineMaps(fusedIndexMaps)))
746       return reshapeOp.emitRemark("fused op loop bound computation failed");
747 
748     LinalgOp fusedOp = createLinalgOpOfSameType(
749         producer, rewriter, rewriter.getUnknownLoc(), reshapeOp.getResultType(),
750         /*inputs=*/producer.getInputs(),
751         /*outputBuffers=*/ValueRange{},
752         /*initTensors=*/ValueRange{}, // no init tensors for now.
753         rewriter.getAffineMapArrayAttr(fusedIndexMaps),
754         producer.iterator_types(),
755         /*doc=*/nullptr,
756         /*library_call=*/nullptr,
757         /*symbol_source=*/nullptr);
758     auto &fusedRegion = fusedOp.getOperation()->getRegion(0);
759     rewriter.cloneRegionBefore(producer.getOperation()->getRegion(0),
760                                fusedRegion, fusedRegion.begin());
761     rewriter.replaceOp(reshapeOp, fusedOp.getOperation()->getResults());
762     if (producer.use_empty())
763       rewriter.eraseOp(producer);
764     return success();
765   }
766 };
767 
768 /// Pattern to fold a tensor_reshape op with its producer generic op if the
769 /// tensor_reshape op is expanding, by expanding the dimensionality of the loop
770 /// in the producer op.
771 struct FoldReshapeWithGenericOpByExpansion
772     : public OpRewritePattern<TensorReshapeOp> {
773   using OpRewritePattern<TensorReshapeOp>::OpRewritePattern;
774   LogicalResult matchAndRewrite(TensorReshapeOp reshapeOp,
775                                 PatternRewriter &rewriter) const override {
776     // Fold only if
777     // - The tensor reshape op is a expanding case.
778     // - All constraints of fusing with reshape by expansion are met.
779     if (reshapeOp.getSrcType().getRank() > reshapeOp.getResultType().getRank())
780       return failure();
781     LinalgOp producer = reshapeOp.src().getDefiningOp<LinalgOp>();
782     if (!producer || producer.getNumOutputs() != 1 ||
783         !isFusableWithReshapeByDimExpansion(producer, producer.getNumInputs()))
784       return failure();
785     Optional<SmallVector<Value, 1>> replacementValues =
786         fuseWithReshapeByExpansion(producer, reshapeOp, producer.getNumInputs(),
787                                    rewriter);
788     if (!replacementValues)
789       return failure();
790     rewriter.replaceOp(reshapeOp, replacementValues.getValue());
791     if (producer.use_empty())
792       rewriter.eraseOp(producer);
793     return success();
794   }
795 };
796 
797 /// Pattern to fold a GenericOp/IndexedGenericOp with a splat constant.
798 template <typename LinalgOpTy>
799 struct FoldSplatConstants : public OpRewritePattern<LinalgOpTy> {
800   using OpRewritePattern<LinalgOpTy>::OpRewritePattern;
801 
802   LogicalResult matchAndRewrite(LinalgOpTy op,
803                                 PatternRewriter &rewriter) const override {
804     if (!op.hasTensorSemantics())
805       return failure();
806     LinalgOp linalgOp = cast<LinalgOp>(op.getOperation());
807     for (auto operand : llvm::enumerate(linalgOp.getInputs())) {
808       ConstantOp constantOp = operand.value().getDefiningOp<ConstantOp>();
809       if (!constantOp ||
810           !constantOp.value().cast<DenseElementsAttr>().isSplat())
811         continue;
812 
813       // The indexing_maps for the operands of the fused operation are same as
814       // those for the operands of the linalgOp without the indexing map at
815       // operand.index()
816       SmallVector<AffineMap, 4> fusedIndexMaps = llvm::to_vector<4>(
817           linalgOp.indexing_maps().getAsValueRange<AffineMapAttr>());
818       fusedIndexMaps.erase(std::next(fusedIndexMaps.begin(), operand.index()));
819 
820       // The operands list is same as the linalgOp with the argument for
821       // constant index dropped.
822       SmallVector<Value, 4> fusedOperands(linalgOp.getInputs());
823       fusedOperands.erase(std::next(fusedOperands.begin(), operand.index()));
824 
825       // Create a constant scalar value from the splat constant.
826       Value scalarConstant = rewriter.create<ConstantOp>(
827           constantOp.getLoc(),
828           constantOp.value().cast<DenseElementsAttr>().getSplatValue());
829 
830       LinalgOp fusedOp = createLinalgOpOfSameType(
831           linalgOp, rewriter, rewriter.getUnknownLoc(),
832           linalgOp.getOperation()->getResultTypes(),
833           /*inputs=*/fusedOperands,
834           /*outputBuffers=*/ValueRange{},
835           /*initTensors=*/ValueRange{}, // no init tensors for now.
836           rewriter.getAffineMapArrayAttr(fusedIndexMaps),
837           linalgOp.iterator_types(),
838           /*doc=*/nullptr,
839           /*library_call=*/nullptr,
840           /*symbol_source=*/nullptr);
841 
842       // Map the block argument corresponding to the replaced argument with the
843       // scalar constant.
844       Region &linalgOpRegion = linalgOp.getOperation()->getRegion(0);
845       Block &entryBlock = *linalgOpRegion.begin();
846       unsigned argIndex = entryBlock.getNumArguments() -
847                           linalgOp.getNumInputs() + operand.index();
848       BlockAndValueMapping mapping;
849       mapping.map(entryBlock.getArgument(argIndex), scalarConstant);
850       Region &fusedRegion = fusedOp.getOperation()->getRegion(0);
851       rewriter.cloneRegionBefore(linalgOpRegion, fusedRegion,
852                                  fusedRegion.begin(), mapping);
853       rewriter.replaceOp(linalgOp, fusedOp.getOperation()->getResults());
854       if (constantOp.use_empty())
855         rewriter.eraseOp(constantOp);
856       return success();
857     }
858     return failure();
859   }
860 };
861 } // namespace
862 
863 Optional<SmallVector<Value, 1>>
864 mlir::linalg::fuseTensorOps(PatternRewriter &rewriter, Operation *consumer,
865                             unsigned consumerIdx, OperationFolder *folder) {
866   if (consumerIdx >= consumer->getNumOperands())
867     return llvm::None;
868   Operation *producer = consumer->getOperand(consumerIdx).getDefiningOp();
869   if (!producer || producer->getNumResults() != 1)
870     return llvm::None;
871 
872   // Fuse when consumer is GenericOp or IndexedGenericOp.
873   if (!isa<GenericOp, IndexedGenericOp>(consumer) ||
874       !isa<GenericOp, IndexedGenericOp>(producer))
875     return llvm::None;
876 
877   return fuseTensorOpsImpl(cast<LinalgOp>(producer), cast<LinalgOp>(consumer),
878                            consumerIdx, rewriter, folder);
879 }
880 
881 namespace {
882 /// Patterns to fuse a generic op, with the producer of its operands.
883 template <typename LinalgOpTy>
884 struct FuseTensorOps : public OpRewritePattern<LinalgOpTy> {
885   using OpRewritePattern<LinalgOpTy>::OpRewritePattern;
886 
887   LogicalResult matchAndRewrite(LinalgOpTy op,
888                                 PatternRewriter &rewriter) const override {
889     // Find the first operand that is defined by another generic op on tensors.
890     for (auto operandNum :
891          llvm::seq<unsigned>(0, op.getOperation()->getNumOperands())) {
892       Operation *producer =
893           op.getOperation()->getOperand(operandNum).getDefiningOp();
894       if (!producer)
895         continue;
896       Optional<SmallVector<Value, 1>> fusedOpResults =
897           fuseTensorOps(rewriter, op, operandNum);
898       if (fusedOpResults) {
899         rewriter.replaceOp(op, *fusedOpResults);
900         if (producer->use_empty())
901           rewriter.eraseOp(producer);
902         return success();
903       }
904     }
905     return failure();
906   }
907 };
908 
909 /// Pass that fuses generic ops on tensors. Used only for testing.
910 struct FusionOfTensorOpsPass
911     : public LinalgFusionOfTensorOpsBase<FusionOfTensorOpsPass> {
912   void runOnOperation() override {
913     OwningRewritePatternList patterns;
914     Operation *op = getOperation();
915     populateLinalgTensorOpsFusionPatterns(op->getContext(), patterns);
916     applyPatternsAndFoldGreedily(op->getRegions(), std::move(patterns));
917   }
918 };
919 
920 /// Pass to test folding of reshape op with generic/indexed_generic ops by
921 /// linearization.
922 struct FoldReshapeOpsByLinearizationPass
923     : public LinalgFoldReshapeOpsByLinearizationBase<
924           FoldReshapeOpsByLinearizationPass> {
925   void runOnOperation() override {
926     OwningRewritePatternList patterns;
927     Operation *op = getOperation();
928     populateFoldReshapeOpsByLinearizationPatterns(op->getContext(), patterns);
929     applyPatternsAndFoldGreedily(op->getRegions(), std::move(patterns));
930   }
931 };
932 
933 } // namespace
934 
935 void mlir::populateFoldReshapeOpsByLinearizationPatterns(
936     MLIRContext *context, OwningRewritePatternList &patterns) {
937   patterns.insert<FoldProducerReshapeOpByLinearization<GenericOp>,
938                   FoldProducerReshapeOpByLinearization<IndexedGenericOp>,
939                   FoldConsumerReshapeOpByLinearization>(context);
940 }
941 
942 void mlir::populateFoldReshapeOpsByExpansionPatterns(
943     MLIRContext *context, OwningRewritePatternList &patterns) {
944   patterns.insert<FoldReshapeWithGenericOpByExpansion,
945                   FoldWithProducerReshapeOpByExpansion>(context);
946 }
947 
948 void mlir::populateLinalgTensorOpsFusionPatterns(
949     MLIRContext *context, OwningRewritePatternList &patterns) {
950   patterns.insert<FuseTensorOps<GenericOp>, FuseTensorOps<IndexedGenericOp>,
951                   FoldSplatConstants<GenericOp>,
952                   FoldSplatConstants<IndexedGenericOp>>(context);
953   populateFoldReshapeOpsByExpansionPatterns(context, patterns);
954   GenericOp::getCanonicalizationPatterns(patterns, context);
955   IndexedGenericOp::getCanonicalizationPatterns(patterns, context);
956   TensorReshapeOp::getCanonicalizationPatterns(patterns, context);
957 }
958 
959 std::unique_ptr<Pass> mlir::createLinalgFusionOfTensorOpsPass() {
960   return std::make_unique<FusionOfTensorOpsPass>();
961 }
962 
963 std::unique_ptr<Pass> mlir::createFoldReshapeOpsByLinearizationPass() {
964   return std::make_unique<FoldReshapeOpsByLinearizationPass>();
965 }
966