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 static bool areTensorOpsFusable(LinalgOp producer, LinalgOp consumer,
30                                 unsigned consumerIdx) {
31   // Producer and consumer must have tensor semantics.
32   if (!producer.hasTensorSemantics() || !consumer.hasTensorSemantics())
33     return false;
34 
35   // Verify that
36   // - the producer has all "parallel" iterator type.
37   if (producer.getNumParallelLoops() != producer.getNumLoops())
38     return false;
39 
40   // Only allow fusing the producer of an input operand for now.
41   // TODO: allow fusing the producer of an output operand.
42   if (consumerIdx >= consumer.getNumInputs())
43     return false;
44 
45   // Get the consumer index map. The number of results of the consumer index
46   // map must match the number of loops of the producer.
47   AffineMap consumerIndexMap = consumer.getIndexingMap(consumerIdx);
48   if (consumerIndexMap.getNumResults() != producer.getNumLoops())
49     return false;
50 
51   // Finally the index_map for the result must be invertible. For now just
52   // verify it is a permutation.
53   AffineMap producerResultIndexMap = producer.getOutputIndexingMap(0);
54   return producerResultIndexMap.isPermutation();
55 }
56 
57 /// Append to `fusedOpIndexingMapAttrs` the indexing maps for the operands of
58 /// the `producer` to use in the fused operation given the indexing map of the
59 /// result of the producer in the consumer.
60 static void getIndexingMapOfProducerOperandsInFusedOp(
61     LinalgOp producer, AffineMap fusedConsumerArgIndexMap,
62     SmallVectorImpl<Attribute> &fusedOpIndexingMapAttrs) {
63   // The indexing map in the consumer op (fusedConsumerArgIndexMap) is a map
64   // from consumer loop -> consumer arg tensor index/producer result tensor
65   // index. The fused loop is same as the consumer loop. For each producer arg
66   // the indexing map to be computed is a map from consumer loop -> producer
67   // arg tensor index.
68 
69   AffineMap producerResultIndexMap = producer.getOutputIndexingMap(0);
70   // producerResultIndexMap is a map from producer loop -> tensor index.
71   // Compute the inverse to get map from tensor index -> producer loop.
72   // The inverse is a map from producer result tensor index -> producer loop.
73   AffineMap invProducerResultIndexMap =
74       inversePermutation(producerResultIndexMap);
75   assert(invProducerResultIndexMap &&
76          "expected producer result indexig map to be invertible");
77   for (unsigned argNum : llvm::seq<unsigned>(0, producer.getNumInputs())) {
78     // argMap is a map from producer loop -> producer arg tensor index.
79     AffineMap argMap = producer.getInputIndexingMap(argNum);
80 
81     // Compose argMap with invProducerResultIndexMap to get a map from
82     // producer result tensor index -> producer arg tensor index.
83     AffineMap t1 = argMap.compose(invProducerResultIndexMap);
84 
85     // Compose t1 with fusedConsumerArgIndexMap gives an indexing map from
86     // consumer loop/ fused loop -> producer arg tensor index.
87     AffineMap indexingMap = t1.compose(fusedConsumerArgIndexMap);
88     fusedOpIndexingMapAttrs.push_back(AffineMapAttr::get(indexingMap));
89   }
90 }
91 
92 /// Generate the region of the fused tensor operation. The region of the fused
93 /// op must be empty.
94 static void generateFusedTensorOpRegion(PatternRewriter &rewriter,
95                                         Operation *fusedOp, LinalgOp producer,
96                                         LinalgOp consumer,
97                                         AffineMap consumerToProducerLoopsMap,
98                                         unsigned consumerIdx, unsigned nloops) {
99   // Build the region of the fused op.
100   Block &producerBlock = producer->getRegion(0).front();
101   Block &consumerBlock = consumer->getRegion(0).front();
102   Block *fusedBlock = new Block();
103   fusedOp->getRegion(0).push_back(fusedBlock);
104   BlockAndValueMapping mapper;
105   OpBuilder::InsertionGuard guard(rewriter);
106   rewriter.setInsertionPointToStart(fusedBlock);
107 
108   // The block arguments are
109   // [index_0, index_1, ... ,
110   //   consumer_operand_0, ... , consumer_operand_(`consumerIdx`-1),
111   //   producer_operand_0, ... , producer_operand_(n-1)],
112   //   consumer_operand_(`consumerIdx`), .. consumer_operand_(m-1)]
113   // , where n is the number of producer's operand and m is the number
114   // consumer's operand.
115   // If both `numProducerIndices` and `numConsumerIndices` are zero, this is a
116   // generic op. In this case, there are no indices in block arguments.
117   unsigned numProducerIndices = isa<IndexedGenericOp>(producer.getOperation())
118                                     ? producer.getNumLoops()
119                                     : 0;
120   unsigned numConsumerIndices = isa<IndexedGenericOp>(consumer.getOperation())
121                                     ? consumer.getNumLoops()
122                                     : 0;
123   unsigned numFusedOpIndices =
124       (isa<IndexedGenericOp>(producer.getOperation()) ||
125        isa<IndexedGenericOp>(consumer.getOperation()))
126           ? std::max(producer.getNumLoops(), consumer.getNumLoops())
127           : 0;
128 
129   // 0. Firstly, add all the indices to the block arguments.
130   for (unsigned i = 0, e = numFusedOpIndices; i < e; ++i)
131     fusedBlock->addArgument(rewriter.getIndexType());
132   // 1. Map consumer indices to fusedBlock indices 1-1.
133   mapper.map(consumerBlock.getArguments().take_front(numConsumerIndices),
134              fusedBlock->getArguments().take_front(numConsumerIndices));
135   // 2. Embed producer indices into fusedBlock index space 1-1.
136   for (auto it :
137        llvm::zip(producerBlock.getArguments().take_front(numProducerIndices),
138                  fusedBlock->getArguments().take_front(numProducerIndices))) {
139     auto newIndex = rewriter.create<mlir::AffineApplyOp>(
140         producer.getLoc(),
141         consumerToProducerLoopsMap.getSubMap(std::get<0>(it).getArgNumber()),
142         fusedBlock->getArguments().take_front(numFusedOpIndices));
143     mapper.map(std::get<0>(it), newIndex);
144   }
145   // TODO: allow fusing the producer of an output operand.
146   assert(consumerIdx < consumer.getNumInputs() &&
147          "expected producer of input operand");
148   // 3. Consumer input operands up to consumerIdx (exclusive).
149   for (BlockArgument bbArg : consumerBlock.getArguments()
150                                  .drop_front(numConsumerIndices)
151                                  .take_front(consumerIdx)) // input assumption.
152     mapper.map(bbArg, fusedBlock->addArgument(bbArg.getType()));
153 
154   // Replacing consumerIdx requires getting the cloned, yielded, value from
155   // the (cloned) producer block. This happens in step 9.
156 
157   // 4. Splice in producer's input operands.
158   for (BlockArgument bbArg : producerBlock.getArguments()
159                                  .drop_front(numProducerIndices)
160                                  .take_front(producer.getNumInputs()))
161     mapper.map(bbArg, fusedBlock->addArgument(bbArg.getType()));
162   // 5. Remaining consumer's input operands (drop past index `consumerIdx`).
163   for (BlockArgument bbArg : consumerBlock.getArguments()
164                                  .drop_front(numConsumerIndices)
165                                  .take_front(consumer.getNumInputs())
166                                  .drop_front(consumerIdx + 1))
167     mapper.map(bbArg, fusedBlock->addArgument(bbArg.getType()));
168   // 6. All of consumer's output operands.
169   for (BlockArgument bbArg :
170        consumerBlock.getArguments().take_back(consumer.getNumOutputs()))
171     mapper.map(bbArg, fusedBlock->addArgument(bbArg.getType()));
172   // 7. All of producer's output operands except the one fused.
173   // TODO: allow fusion of multi-result producers.
174   assert(producer->getNumResults() == 1 && "expected single result producer");
175 
176   // 8. Clone operations from producer (except the yield operation) to the fused
177   // op.
178   for (auto &op : producerBlock.without_terminator())
179     rewriter.clone(op, mapper);
180   // 9. Now we can map the consumerBlock's `consumerIdx` block argument. Just
181   // forward the yield operand.
182   auto yieldOp = cast<linalg::YieldOp>(producerBlock.getTerminator());
183   // TODO: allow fusion of multi-result producers.
184   assert(producer->getNumResults() == 1 && "expected single result producer");
185   unsigned producerResultNumber = 0;
186   Value replacement =
187       mapper.lookupOrDefault(yieldOp.getOperand(producerResultNumber));
188   // Sanity checks, if replacement is not already in the mapper then it must be
189   // produced outside.
190   if (replacement == yieldOp.getOperand(producerResultNumber)) {
191     if (auto bb = replacement.dyn_cast<BlockArgument>())
192       assert(bb.getOwner() != &producerBlock &&
193              "yielded block argument must have been mapped");
194     else
195       assert(!producer->isAncestor(replacement.getDefiningOp()) &&
196              "yielded value must have been mapped");
197   }
198   mapper.map(consumerBlock.getArgument(consumerIdx + numConsumerIndices),
199              replacement);
200   // 10. Clone operations from the consumer to the fused op.
201   for (auto &op : consumerBlock.getOperations())
202     rewriter.clone(op, mapper);
203 
204   // Sanity checks.
205   assert(fusedBlock->getNumArguments() ==
206              fusedOp->getNumOperands() + numFusedOpIndices &&
207          "Ill-formed LinalgOp region");
208 }
209 
210 static Optional<SmallVector<Value, 1>>
211 fuseTensorOpsImpl(LinalgOp producer, OpOperand &consumerOpOperand,
212                   PatternRewriter &rewriter) {
213   LinalgOp consumer = cast<LinalgOp>(consumerOpOperand.getOwner());
214   unsigned consumerIdx = consumerOpOperand.getOperandNumber();
215   if (!areTensorOpsFusable(producer, consumer, consumerIdx))
216     return llvm::None;
217 
218   unsigned numFusedOperands =
219       producer.getNumInputs() + consumer.getNumInputs() - 1;
220 
221   // Compute the fused operands list,
222   SmallVector<Value, 2> fusedOperands;
223   fusedOperands.reserve(numFusedOperands);
224   auto consumerOperands = consumer.getInputs();
225   auto producerOperands = producer.getInputs();
226   fusedOperands.assign(consumerOperands.begin(),
227                        std::next(consumerOperands.begin(), consumerIdx));
228   fusedOperands.append(producerOperands.begin(), producerOperands.end());
229   fusedOperands.append(std::next(consumerOperands.begin(), consumerIdx + 1),
230                        consumerOperands.end());
231 
232   // Compute indexing_maps for the fused operation. The indexing_maps for the
233   // operands of the consumers that aren't fused are the same. The
234   // indexing_maps for the producers need to be computed based on the
235   // indexing_map of the operand at consumerIdx in the consumer.
236   SmallVector<Attribute, 4> fusedIndexMaps;
237   auto consumerIndexMaps = consumer.indexing_maps();
238   fusedIndexMaps.reserve(fusedOperands.size() + consumer.getNumOutputs());
239   fusedIndexMaps.assign(consumerIndexMaps.begin(),
240                         std::next(consumerIndexMaps.begin(), consumerIdx));
241   // Compute indexing maps for the producer args in the fused operation.
242   getIndexingMapOfProducerOperandsInFusedOp(
243       producer, consumer.getInputIndexingMap(consumerIdx), fusedIndexMaps);
244 
245   // Append the indexing maps for the remaining consumer operands.
246   fusedIndexMaps.append(std::next(consumerIndexMaps.begin(), consumerIdx + 1),
247                         consumerIndexMaps.end());
248 
249   // Generate the fused op.
250   LinalgOp fusedOp;
251   if (isa<GenericOp>(producer.getOperation()) &&
252       isa<GenericOp>(consumer.getOperation())) {
253     fusedOp =
254         rewriter
255             .create<GenericOp>(consumer.getLoc(), consumer->getResultTypes(),
256                                /*inputs=*/fusedOperands,
257                                // TODO: handle outputs.
258                                consumer.getOutputs(),
259                                rewriter.getArrayAttr(fusedIndexMaps),
260                                consumer.iterator_types(),
261                                /*doc=*/nullptr,
262                                /*library_call=*/nullptr,
263                                /*sparse=*/nullptr)
264             .getOperation();
265   } else {
266     fusedOp =
267         rewriter
268             .create<IndexedGenericOp>(
269                 consumer.getLoc(), consumer->getResultTypes(),
270                 /*inputs=*/fusedOperands,
271                 // TODO: handle outputs.
272                 consumer.getOutputs(), rewriter.getArrayAttr(fusedIndexMaps),
273                 consumer.iterator_types(),
274                 /*doc=*/nullptr,
275                 /*library_call=*/nullptr,
276                 /*sparse=*/nullptr)
277             .getOperation();
278   }
279 
280   // Construct an AffineMap from consumer loops to producer loops.
281   // consumer loop -> tensor index
282   AffineMap consumerResultIndexMap = consumer.getInputIndexingMap(consumerIdx);
283   // producer loop -> tensor index
284   AffineMap producerResultIndexMap = producer.getOutputIndexingMap(0);
285   // tensor index -> producer loop
286   AffineMap invProducerResultIndexMap =
287       inversePermutation(producerResultIndexMap);
288   assert(invProducerResultIndexMap &&
289          "expected producer result indexig map to be invertible");
290   // consumer loop -> producer loop
291   AffineMap consumerToProducerLoopsMap =
292       invProducerResultIndexMap.compose(consumerResultIndexMap);
293 
294   generateFusedTensorOpRegion(rewriter, fusedOp.getOperation(), producer,
295                               consumer, consumerToProducerLoopsMap, consumerIdx,
296                               consumer.getNumLoops());
297   return SmallVector<Value, 1>(fusedOp->getResults());
298 }
299 
300 /// Linearize the expressions in `sourceMap` based on the `reassociationMaps`
301 /// provided, given the shape of the source tensor that corresponds to the
302 /// `sourceMap`. Note that this implicitly assumes that the tensors dimensions
303 /// are "row-major" ordered logically.
304 ///
305 /// For example:
306 ///
307 /// %0 = op ... : tensor<?x?x4x5xf32>
308 /// with output index_map `affine_map<(d0, d1, d2, d3) -> (d0, d1, d2, d3)>`
309 ///
310 /// and reshape:
311 /// %1 = linalg.tensor_reshape %0 [affine_map<(i, j, k, l) -> (i)>,
312 ///                                affine_map<(i, j, k, l) -> (j, k, l)>] :
313 ///        tensor<?x?x4x5xf32> into tensor<?x?xf32>
314 ///
315 /// would be rewritten into:
316 /// %0 = op ... : tensor<?x?x4x5xf32>
317 /// with output index_map
318 ///   `affine_map<(d0, d1, d2, d3) -> (d0, d1 * 20 + d2 * 5 + d3)>`
319 static AffineMap linearizeCollapsedDims(AffineMap sourceMap,
320                                         ArrayRef<int64_t> sourceShape,
321                                         ArrayRef<AffineMap> reassociationMaps) {
322   SmallVector<AffineExpr, 4> resultExprs;
323   resultExprs.reserve(reassociationMaps.size());
324   ArrayRef<AffineExpr> sourceExprs = sourceMap.getResults();
325   MLIRContext *context = sourceMap.getContext();
326 
327   // Compute the result exprs based on the reassociation maps.
328   for (AffineMap map : reassociationMaps) {
329     ArrayRef<AffineExpr> collapsedDims = map.getResults();
330     // Assume that they are in-order and contiguous (already checked in
331     // verifier).
332     assert(!collapsedDims.empty());
333     unsigned startDim =
334         collapsedDims.front().cast<AffineDimExpr>().getPosition();
335     SmallVector<int64_t, 4> sizes;
336     SmallVector<AffineExpr, 4> dimExprs;
337     for (auto en :
338          llvm::zip(sourceShape.slice(startDim, collapsedDims.size()),
339                    sourceExprs.slice(startDim, collapsedDims.size()))) {
340       if (std::get<0>(en) == 1)
341         continue;
342       sizes.push_back(std::get<0>(en));
343       dimExprs.push_back(std::get<1>(en));
344     }
345     AffineExpr linearizedExpr =
346         makeCanonicalStridedLayoutExpr(sizes, dimExprs, context);
347     resultExprs.push_back(linearizedExpr);
348   }
349   return AffineMap::get(sourceMap.getNumDims(), sourceMap.getNumSymbols(),
350                         resultExprs, context);
351 }
352 
353 /// Checks if the `reshapeOp` can be fused with it consumer (if `asProducer` is
354 /// true) or its producer (if `asProducer` is false) given the indexing map at
355 /// its use.
356 static bool isTensorReshapeOpFoldableByLinearization(TensorReshapeOp reshapeOp,
357                                                      AffineMap useIndexMap,
358                                                      bool asProducer) {
359   RankedTensorType returnType = reshapeOp.getResultType();
360   RankedTensorType operandType = reshapeOp.getSrcType();
361   // Reshape is fusable with its consumer (i.e. reshape as a producer) when its
362   // operand is of lesser rank than the result. Fusing when operand has higher
363   // rank will require use of mods and divs in the indexing maps of the fused op
364   // which would make it non-invertible. Similarly reshape is fused with its
365   // producer (i.e. reshape as consumer) only if the return type has lesser
366   // rank.
367   if ((asProducer && reshapeOp.getSrcType().hasStaticShape() &&
368        returnType.getRank() < operandType.getRank()) ||
369       (!asProducer && reshapeOp.getResultType().hasStaticShape() &&
370        operandType.getRank() < returnType.getRank()))
371     return false;
372   return useIndexMap.isPermutation();
373 }
374 
375 /// Based on the type of `op` create a linalg op of the same type, i.e. if `op`
376 /// is a linalg.generic operation, the create a `linalg.generic` operation with
377 /// the given `args`. Expects `op` to be `linalg.generic` or
378 /// `linalg.indexed_generic`.
379 template <typename... Args>
380 static LinalgOp createLinalgOpOfSameType(LinalgOp op, PatternRewriter &rewriter,
381                                          Args... args) {
382   if (isa<GenericOp>(op.getOperation()))
383     return rewriter.create<GenericOp>(args...);
384   if (isa<IndexedGenericOp>(op.getOperation()))
385     return rewriter.create<IndexedGenericOp>(args...);
386   llvm_unreachable(
387       "expected only linalg.generic or linalg.indexed_generic ops");
388   return nullptr;
389 }
390 
391 /// Check if the reshape operation is only expansion into/collapsing of
392 /// unit-dimension.
393 static bool isUnitDimExpansionOnly(ArrayRef<int64_t> expandedShape,
394                                    ArrayRef<AffineMap> reassociation) {
395   for (auto &map : reassociation) {
396     unsigned numUnitDims = 0;
397     for (AffineExpr expr : map.getResults()) {
398       unsigned position = expr.cast<AffineDimExpr>().getPosition();
399       if (expandedShape[position] == 1)
400         numUnitDims++;
401     }
402     if (numUnitDims != map.getNumResults() - 1)
403       return false;
404   }
405   return true;
406 }
407 
408 /// Conditions for folding a generic/indexed-generic operation with a reshape op
409 /// by expanding the iteration space dimensionality for tensor operations. These
410 /// are preconditions assumed by `foldReshapeByDimExpansion` which implements
411 /// the following fusion pattern.
412 ///
413 ///  Consider
414 ///
415 ///  %c = linalg.generic ins(%a, %b : memref<?x?x?xf32>, memref<?x?xf32>)
416 ///         indexing_maps = [affine_map<(d0, d1, d2) -> (d1, d0, d2)>,
417 ///                          affine_map<(d0, d1, d2) -> (d1, d2)>,
418 ///                          affine_map<(d0, d1, d2) -> (d0, d2, d1)>]
419 ///  %d = linalg.tensor_reshape %c
420 ///         [affine_map<(d0, d1, d2, d3, d4, d5) -> (d0, d1)>,
421 ///          affine_map<(d0, d1, d2, d3, d4, d5) -> (d2)>,
422 ///          affine_map<(d0, d1, d2, d3, d4, d5) -> (d3, d4, d5)>]
423 ///       : tensor<?x?x?xf32> into tensor<?x?x?x?x?x?xf32>
424 ///
425 ///  The reshape can be folded into the `linalgOp` if the
426 ///  generic/indexed-generic op loop dimensionality is increased to match the
427 ///  result (operand) of the tensor_reshape when the reshape is expanding
428 ///  (folding). The indexing_map of the fused tensor in the `linalgOp` and the
429 ///  reassociation map helps compute the indexing maps of the modified op. For
430 ///  the above example, based on the reassociation map it can be concluded that
431 ///
432 ///  - The loop used to access the first dimension of the fused tensor is split
433 ///    into two.
434 ///  - The loop used to access the second dimension of the fused tensor is kept
435 ///    as is.
436 ///  - The loop used to access the third dimension of the fused tensor is split
437 ///    into three.
438 ///
439 ///  i.e. (e0, e1, e2, e3, e4) is the domain of the indexing map of the modified
440 ///  op, then
441 ///
442 ///   d0 -> e0, e1
443 ///   d1 -> e2, e3, e4
444 ///   d2 -> e5
445 ///
446 ///  substituting this, the generic op can be rewritten as
447 ///
448 ///  %d = linalg.generic ins(%0, %1 : )
449 ///        indexing_maps =
450 ///         [affine_map<(e0, e1, e2, e3, e4, e5) -> (e2, e3, e4, e0, e1, e5)>,
451 ///          affine_map<(e0, e1, e2, e3, e4, e5) -> (e2, e3, e4, e5)>,
452 ///          affine_map<(e0, e1, e2, e3, e4, e5) -> (e0, e1, e5, e2, e3, e4)>]
453 ///
454 ///  Since operands to the linalg generic are now 5D, reshapes can be introduced
455 ///  to make it consistent
456 ///
457 ///  %0 = linalg.tensor_reshape %a
458 ///         [affine_map<(e0, e1, e2, e3, e4, e5) -> (e0, e1, e2),
459 ///          affine_map<(e0, e1, e2, e3, e4, e5) -> (e3, e4),
460 ///          affine_map<(e0, e1, e2, e3, e4, e5) -> (e5)]
461 ///       : tensor<?x?x?xf32> into tensor<?x?x?x?x?x?xf32>
462 ///  %1 = linalg.tensor_reshape %b
463 ///         [affine_map<(e0, e1, e2, e3) -> (e0, e1, e2),
464 ///          affine_map<(e0, e1, e2, e3) -> (e3)]
465 ///       : tensor<?x?x?xf32> into tensor<?x?x?x?xf32>
466 ///
467 ///  The added reshapes are again expanding patterns, so they will get fused
468 ///  with its producers if possible.
469 static bool isFusableWithReshapeByDimExpansion(LinalgOp linalgOp,
470                                                unsigned fusedTensorIndex) {
471   // Is fusable only if:
472   // - The linalgOp is a generic op, or an indexed_generic.
473   // - All the indexing maps for operands and results in linalgOp are projected
474   //   permutations.
475   // - The fused tensor is not a scalar.
476   // - All the loops in linalgOp are parallel loops.
477   return isa<GenericOp, IndexedGenericOp>(linalgOp.getOperation()) &&
478          linalgOp.hasTensorSemantics() &&
479          llvm::all_of(linalgOp.indexing_maps().getValue(),
480                       [](Attribute attr) {
481                         return attr.cast<AffineMapAttr>()
482                             .getValue()
483                             .isProjectedPermutation();
484                       }) &&
485          linalgOp.getIndexingMap(fusedTensorIndex).getNumResults() > 0 &&
486          llvm::all_of(linalgOp.iterator_types(), [](Attribute attr) {
487            return attr.cast<StringAttr>().getValue() ==
488                   getParallelIteratorTypeName();
489          });
490 }
491 
492 namespace {
493 /// Information needed to expand a generic/indexed_generic operation to fold the
494 /// reshape with it.
495 class ExpansionInfo {
496 public:
497   // Computes the mapping from original dimensions of the op to the dimensions
498   // of the expanded op given the `indexingMap` of the fused operand/result of
499   // the generic/indexed_generic op, the `reassocationMaps` of the reshape op
500   // and the shape of the expanded op.
501   LogicalResult compute(LinalgOp linalgOp, unsigned fusedTensorIndex,
502                         ArrayRef<AffineMap> reassociationMaps,
503                         ArrayRef<int64_t> expandedShape);
504   unsigned getOrigOpNumDims() const { return reassociation.size(); }
505   unsigned getExpandedOpNumDims() const { return expandedOpNumDims; }
506   ReassociationIndicesRef getExpandedDims(unsigned i) const {
507     return reassociation[i];
508   }
509   ArrayRef<int64_t> getExpandedShapeOfDim(unsigned i) const {
510     return expandedShapeMap[i];
511   }
512 
513 private:
514   /// Reassociation from the dimensions in the original operation to the
515   /// dimension of the expanded operation.
516   SmallVector<ReassociationIndices, 4> reassociation;
517   /// Mapping from extent of loops in the original operation, to the extent of
518   /// loops in the expanded operation.
519   SmallVector<SmallVector<int64_t, 4>, 4> expandedShapeMap;
520   unsigned expandedOpNumDims;
521 };
522 } // namespace
523 
524 LogicalResult ExpansionInfo::compute(LinalgOp linalgOp,
525                                      unsigned fusedTensorIndex,
526                                      ArrayRef<AffineMap> reassociationMaps,
527                                      ArrayRef<int64_t> expandedShape) {
528   if (reassociationMaps.empty())
529     return failure();
530   AffineMap fusedIndexMap = linalgOp.getIndexingMap(fusedTensorIndex);
531 
532   Optional<SmallVector<int64_t, 4>> originalLoopRange =
533       linalgOp.getStaticLoopRanges();
534   if (!originalLoopRange)
535     return linalgOp.emitError("unable to find loop range for operation");
536 
537   reassociation.clear();
538   expandedShapeMap.clear();
539   // Compute the number of dimension in the expanded op that correspond to each
540   // dimension of the original op.
541   SmallVector<unsigned, 4> numExpandedDims(fusedIndexMap.getNumDims(), 1);
542   expandedShapeMap.resize(fusedIndexMap.getNumDims());
543   for (auto resultExpr : llvm::enumerate(fusedIndexMap.getResults())) {
544     unsigned pos = resultExpr.value().cast<AffineDimExpr>().getPosition();
545     AffineMap foldedDims = reassociationMaps[resultExpr.index()];
546     numExpandedDims[pos] = foldedDims.getNumResults();
547     ArrayRef<int64_t> shape =
548         expandedShape.slice(foldedDims.getDimPosition(0), numExpandedDims[pos]);
549     expandedShapeMap[pos].assign(shape.begin(), shape.end());
550   }
551   // The remaining dimensions remain the same.
552   for (unsigned i : llvm::seq<unsigned>(0, fusedIndexMap.getNumDims()))
553     if (expandedShapeMap[i].empty())
554       expandedShapeMap[i] = {(*originalLoopRange)[i]};
555 
556   // Compute reassociation map from the original op to the expanded op.
557   unsigned sum = 0;
558   reassociation.reserve(fusedIndexMap.getNumDims());
559   for (auto numFoldedDim : llvm::enumerate(numExpandedDims)) {
560     auto seq = llvm::seq<int64_t>(sum, sum + numFoldedDim.value());
561     reassociation.emplace_back(seq.begin(), seq.end());
562     sum += numFoldedDim.value();
563   }
564   expandedOpNumDims = sum;
565   return success();
566 }
567 
568 /// To expand an indexed_generic operation, the body of the indexed generic op
569 /// need to be modified appropriately. Specifically, uses of arguments for
570 /// induction variables in the original operation need to be replaced with
571 /// linearization of the corresponding arguments in the expanded op. That
572 /// requires the shape of the expanded dimensions (at least all but the most
573 /// significant. For now check that these are all statically sized. Note that
574 /// this could be extended to handle dynamic case, but the implementation below
575 /// uses `affine.apply` which seems to have issues when the shapes are not
576 /// static.
577 LogicalResult isIndexedGenericOpExpandable(LinalgOp linalgOp,
578                                            const ExpansionInfo &expansionInfo) {
579   for (unsigned i : llvm::seq<unsigned>(0, expansionInfo.getOrigOpNumDims())) {
580     ArrayRef<int64_t> expandedShape = expansionInfo.getExpandedShapeOfDim(i);
581     if (expandedShape.size() == 1)
582       continue;
583     for (int64_t shape : expandedShape.drop_front()) {
584       if (ShapedType::isDynamic(shape)) {
585         return linalgOp.emitError(
586             "unable to fuse indexed generic op where the expanded dim is "
587             "dynamic");
588       }
589     }
590   }
591   return success();
592 }
593 
594 /// Return the indexing map to use in the expanded op for a given the
595 /// `indexingMap` of the original operation.
596 static AffineMap
597 getIndexingMapInExpandedOp(OpBuilder &builder, AffineMap indexingMap,
598                            const ExpansionInfo &expansionInfo) {
599   SmallVector<AffineExpr, 4> newExprs;
600   for (AffineExpr expr : indexingMap.getResults()) {
601     unsigned pos = expr.cast<AffineDimExpr>().getPosition();
602     SmallVector<AffineExpr, 4> expandedExprs = llvm::to_vector<4>(
603         llvm::map_range(expansionInfo.getExpandedDims(pos), [&](int64_t v) {
604           return builder.getAffineDimExpr(static_cast<unsigned>(v));
605         }));
606     newExprs.append(expandedExprs.begin(), expandedExprs.end());
607   }
608   return AffineMap::get(expansionInfo.getExpandedOpNumDims(),
609                         indexingMap.getNumSymbols(), newExprs,
610                         builder.getContext());
611 }
612 
613 /// Return the type of the operand/result to use in the expanded op given the
614 /// type in the original op.
615 static RankedTensorType getExpandedType(RankedTensorType originalType,
616                                         AffineMap indexingMap,
617                                         const ExpansionInfo &expansionInfo) {
618   SmallVector<int64_t, 4> expandedShape;
619   for (AffineExpr expr : indexingMap.getResults()) {
620     unsigned dim = expr.cast<AffineDimExpr>().getPosition();
621     auto dimExpansion = expansionInfo.getExpandedShapeOfDim(dim);
622     expandedShape.append(dimExpansion.begin(), dimExpansion.end());
623   }
624   return RankedTensorType::get(expandedShape, originalType.getElementType());
625 }
626 
627 /// Returns the reassociation maps to use in the `linalg.tensor_reshape`
628 /// operation to convert the operands of the origial operation to operands of
629 /// the expanded operation. The same method is used to compute the
630 /// `linalg.tensor_reshape` used to collapse the result of the expanded op to
631 /// get the value that can replace all uses of the results of the original op.
632 static SmallVector<ReassociationIndices, 4>
633 getReassociationForExpansion(AffineMap indexingMap,
634                              const ExpansionInfo &expansionInfo) {
635   SmallVector<ReassociationIndices, 4> reassociation;
636   unsigned numReshapeDims = 0;
637   for (AffineExpr expr : indexingMap.getResults()) {
638     unsigned dim = expr.cast<AffineDimExpr>().getPosition();
639     auto numExpandedDims = expansionInfo.getExpandedDims(dim).size();
640     auto indices = llvm::to_vector<2>(
641         llvm::seq<int64_t>(numReshapeDims, numReshapeDims + numExpandedDims));
642     reassociation.emplace_back(std::move(indices));
643     numReshapeDims += numExpandedDims;
644   }
645   return reassociation;
646 }
647 
648 /// Build the body of the expanded IndexedGenericOp. The arguments for the
649 /// induction variables of the original operation need to be recovered by
650 /// linearizing the arguments of the corresponding dimensions of the expanded
651 /// op. For now it is assumed that the shapes of the expanded op needed for
652 /// linearization are static.
653 static void buildExpandedIndexedGenericOpRegion(
654     PatternRewriter &rewriter, Location loc, Region &originalOpRegion,
655     Region &fusedOpRegion, const ExpansionInfo &expansionInfo) {
656   assert(fusedOpRegion.empty() && "expected fused op to have empty region");
657   // Create an entry block in the fused region with same number of arguments
658   // as the fused op
659   Block *fusedEntryBlock = new Block;
660   fusedOpRegion.push_back(fusedEntryBlock);
661   rewriter.cloneRegionBefore(originalOpRegion, fusedOpRegion,
662                              fusedOpRegion.end());
663 
664   // Merge the entry block of the fused op with the cloned blocks. For this
665   // compute the value for arguments of the region in the original operation
666   // in terms of the arguments of the fused op. Since the original operation
667   // is expanded, the expanded dimensions need to be folded back to get the
668   // replacement value for the arguments corresponding to interation index.
669   // For now this expects that all the loop ranges are constants, which is
670   // true if the shapes are all static. This has already been checked in the
671   // precondition.
672   using namespace edsc::op;
673   using namespace edsc::intrinsics;
674   OpBuilder::InsertionGuard guard(rewriter);
675   SmallVector<Value, 4> argReplacements(originalOpRegion.getNumArguments());
676   rewriter.setInsertionPointToStart(fusedEntryBlock);
677   edsc::ScopedContext scopedContext(rewriter, loc);
678   IndexType indexType = rewriter.getIndexType();
679   for (auto i : llvm::seq<unsigned>(0, expansionInfo.getOrigOpNumDims())) {
680     Value linearizedIndex = fusedEntryBlock->addArgument(indexType);
681     ArrayRef<int64_t> expandedDimsShape =
682         expansionInfo.getExpandedShapeOfDim(i).drop_front();
683     for (unsigned shape : expandedDimsShape) {
684       assert(!ShapedType::isDynamic(shape));
685       linearizedIndex = linearizedIndex * std_constant_index(shape);
686       linearizedIndex =
687           linearizedIndex + fusedEntryBlock->addArgument(indexType);
688     }
689     argReplacements[i] = linearizedIndex;
690   }
691   for (auto i : llvm::seq<unsigned>(expansionInfo.getOrigOpNumDims(),
692                                     argReplacements.size())) {
693     argReplacements[i] =
694         fusedEntryBlock->addArgument(originalOpRegion.getArgument(i).getType());
695   }
696   rewriter.mergeBlocks(fusedEntryBlock->getNextNode(), fusedEntryBlock,
697                        argReplacements);
698 }
699 
700 /// Implements the fusion of a tensor_reshape op and a generic/indexed_generic
701 /// op as explained in `isFusableWithReshapeByExpansion`. Assumes that those
702 /// conditions have been satisfied.
703 static Optional<SmallVector<Value, 1>>
704 fuseWithReshapeByExpansion(LinalgOp linalgOp, TensorReshapeOp reshapeOp,
705                            unsigned fusedTensorIndex,
706                            PatternRewriter &rewriter) {
707   assert(isFusableWithReshapeByDimExpansion(linalgOp, fusedTensorIndex) &&
708          "preconditions for fuse operation failed");
709   // Check if reshape is expanding or collapsing.
710   bool isExpanding =
711       reshapeOp.getSrcType().getRank() < reshapeOp.getResultType().getRank();
712   RankedTensorType expandedType =
713       isExpanding ? reshapeOp.getResultType() : reshapeOp.getSrcType();
714 
715   ExpansionInfo expansionInfo;
716   if (failed(expansionInfo.compute(linalgOp, fusedTensorIndex,
717                                    reshapeOp.getReassociationMaps(),
718                                    expandedType.getShape())))
719     return llvm::None;
720 
721   if (isa<IndexedGenericOp>(linalgOp.getOperation()) &&
722       failed(isIndexedGenericOpExpandable(linalgOp, expansionInfo)))
723     return llvm::None;
724 
725   SmallVector<AffineMap, 4> expandedOpIndexingMaps = llvm::to_vector<4>(
726       llvm::map_range(linalgOp.getIndexingMaps(), [&](AffineMap m) {
727         return getIndexingMapInExpandedOp(rewriter, m, expansionInfo);
728       }));
729 
730   SmallVector<Value, 4> expandedOpOperands;
731   for (auto operand : llvm::enumerate(linalgOp.getInputs())) {
732     if (operand.index() == fusedTensorIndex) {
733       expandedOpOperands.push_back(reshapeOp.src());
734       continue;
735     }
736     AffineMap indexingMap = linalgOp.getInputIndexingMap(operand.index());
737     RankedTensorType expandedOperandType =
738         getExpandedType(operand.value().getType().cast<RankedTensorType>(),
739                         indexingMap, expansionInfo);
740     if (expandedOperandType != operand.value().getType()) {
741       // Reshape the operand to get the right type.
742       SmallVector<ReassociationIndices, 4> reassociation =
743           getReassociationForExpansion(indexingMap, expansionInfo);
744       expandedOpOperands.push_back(rewriter.create<TensorReshapeOp>(
745           linalgOp.getLoc(), expandedOperandType, operand.value(),
746           reassociation));
747       continue;
748     }
749     expandedOpOperands.push_back(operand.value());
750   }
751 
752   Location loc = linalgOp.getLoc();
753   SmallVector<Value, 1> outputs;
754   for (auto result : llvm::enumerate(linalgOp.getOutputs())) {
755     AffineMap indexingMap = linalgOp.getOutputIndexingMap(result.index());
756     RankedTensorType expandedOutputType =
757         getExpandedType(result.value().getType().cast<RankedTensorType>(),
758                         indexingMap, expansionInfo);
759     if (expandedOutputType != result.value().getType()) {
760       SmallVector<ReassociationIndices, 4> reassociation =
761           getReassociationForExpansion(indexingMap, expansionInfo);
762       outputs.push_back(rewriter.create<TensorReshapeOp>(
763           linalgOp.getLoc(), expandedOutputType, result.value(),
764           reassociation));
765     }
766   }
767 
768   // The iterator types of the expanded op are all parallel.
769   SmallVector<StringRef, 4> iteratorTypes(expansionInfo.getExpandedOpNumDims(),
770                                           getParallelIteratorTypeName());
771 
772   TypeRange resultTypes = ValueRange(outputs).getTypes();
773   LinalgOp fusedOp = createLinalgOpOfSameType(
774       linalgOp, rewriter, linalgOp.getLoc(), resultTypes,
775       /*inputs=*/expandedOpOperands, outputs, expandedOpIndexingMaps,
776       iteratorTypes);
777   Region &fusedRegion = fusedOp->getRegion(0);
778   Region &originalRegion = linalgOp->getRegion(0);
779 
780   if (isa<GenericOp>(linalgOp.getOperation())) {
781     rewriter.cloneRegionBefore(originalRegion, fusedRegion,
782                                fusedRegion.begin());
783   } else {
784     assert(isa<IndexedGenericOp>(linalgOp.getOperation()));
785     buildExpandedIndexedGenericOpRegion(rewriter, loc, originalRegion,
786                                         fusedRegion, expansionInfo);
787   }
788 
789   // Reshape the result values to their original shape if this is a collapsing
790   // reshape folded into its consumer.
791   SmallVector<Value, 1> resultVals;
792   for (auto result : llvm::enumerate(linalgOp->getResults())) {
793     if (!isExpanding &&
794         resultTypes[result.index()] != result.value().getType()) {
795       SmallVector<ReassociationIndices, 4> reassociation =
796           getReassociationForExpansion(
797               linalgOp.getOutputIndexingMap(result.index()), expansionInfo);
798       resultVals.push_back(rewriter.create<TensorReshapeOp>(
799           linalgOp.getLoc(), result.value().getType(),
800           fusedOp->getResult(result.index()), reassociation));
801     } else {
802       resultVals.push_back(fusedOp->getResult(result.index()));
803     }
804   }
805   // Assuming a single result.
806   return resultVals;
807 }
808 
809 namespace {
810 
811 /// Pattern to fold tensor_reshape op with its consumer by using the source of
812 /// the reshape op as the operand in the consumer (instead of the result of the
813 /// tensor_reshapeop) when the tensor_reshape op is collapsing. The
814 /// corresponding index map in the consumer needs to be modified to linearize
815 /// the folded dimension.
816 ///
817 /// For example,
818 ///
819 /// #map0 = affine_map<(d0, d1, d2, d3) -> (d0, d1, d2, d3)>
820 /// %0 = linalg.tensor_reshape %arg0
821 ///        [affine_map<(i, j, k, l) -> (i)>, affine_map<(i, j, k, l) -> (j, k)>,
822 ///         affine_map<(i, j, k, l) -> (l)>]
823 ///      tensor<?x?x?xf32> into tensor<?x?x4x?xf32>
824 /// %1 = linalg.generic { indexing_maps = [#map0, #map0, #map0], ... }
825 ///        ins(%0, %arg1 : tensor<?x?x4x?xf32>, tensor<?x?x4x?xf32>) ...
826 ///        -> tensor<?x?x4x?xf32>
827 ///
828 /// can be folded into
829 ///
830 /// #map0 = affine_map<(d0, d1, d2, d3) -> (d0, d1 * 4 + d2, d3)>
831 /// #map1 = affine_map<(d0, d1, d2, d3) -> (d0, d1, d2, d3)>
832 /// %0 = linalg.generic { indexing_maps = [#map0, #map1, #map1] ... }
833 ///        ins(%arg0, %arg1 : tensor<?x?x?xf32>, tensor<?x?x4x?xf32>) ...
834 ///        -> tensor<?x?x4x?xf32>
835 template <typename LinalgOpTy, bool foldUnitDimReshapesOnly>
836 struct FoldProducerReshapeOpByLinearization
837     : public OpRewritePattern<LinalgOpTy> {
838   using OpRewritePattern<LinalgOpTy>::OpRewritePattern;
839 
840   LogicalResult matchAndRewrite(LinalgOpTy op,
841                                 PatternRewriter &rewriter) const override {
842     if (!op.hasTensorSemantics())
843       return failure();
844     LinalgOp linalgOp = cast<LinalgOp>(op.getOperation());
845     for (auto operand : llvm::enumerate(linalgOp.getInputs())) {
846       TensorReshapeOp reshapeOp =
847           operand.value().getDefiningOp<TensorReshapeOp>();
848       if (!reshapeOp ||
849           !isTensorReshapeOpFoldableByLinearization(
850               reshapeOp, linalgOp.getInputIndexingMap(operand.index()),
851               /*asProducer =*/true) ||
852           (foldUnitDimReshapesOnly &&
853            !isUnitDimExpansionOnly(reshapeOp.getResultType().getShape(),
854                                    reshapeOp.getReassociationMaps())))
855         continue;
856 
857       // Compute the fused operands list,
858       SmallVector<Value, 2> fusedOperands(linalgOp.getInputs());
859       fusedOperands[operand.index()] = reshapeOp.src();
860       fusedOperands.append(linalgOp.getOutputs().begin(),
861                            linalgOp.getOutputs().end());
862 
863       // Compute indexing_maps for the fused operation. The indexing_maps for
864       // the operands of the consumers that arent fused are the same.
865       SmallVector<AffineMap, 4> fusedIndexMaps = llvm::to_vector<4>(
866           op.indexing_maps().template getAsValueRange<AffineMapAttr>());
867 
868       // Accepted consumer maps are either identity or permutation.
869       auto invMap = inversePermutation(fusedIndexMaps[operand.index()]);
870 
871       // Compute the indexing map to use for the result of the producer.
872       AffineMap modifiedMap =
873           linearizeCollapsedDims(invMap, reshapeOp.getResultType().getShape(),
874                                  reshapeOp.getReassociationMaps());
875       for (AffineExpr expr : modifiedMap.getResults()) {
876         if (!expr.isPureAffine())
877           return failure();
878       }
879       fusedIndexMaps[operand.index()] = modifiedMap;
880 
881       // Further check that the resulting index maps can be fused and
882       // inverted. Without this the resultant op is not legal.
883       if (!inversePermutation(concatAffineMaps(fusedIndexMaps)))
884         return op.emitRemark("fused op loop bound computation failed");
885 
886       rewriter.startRootUpdate(op);
887       op->setOperands(fusedOperands);
888       op.indexing_mapsAttr(rewriter.getAffineMapArrayAttr(fusedIndexMaps));
889       rewriter.finalizeRootUpdate(op);
890       return success();
891     }
892     return failure();
893   }
894 };
895 
896 /// Pattern to fuse a tensor_reshape op with its consumer
897 /// generic/indexed_generic op, when the reshape op is collapsing
898 /// dimensions. The dimensionality of the loop in the consumer is expanded.
899 template <typename GenericOpTy>
900 struct FoldWithProducerReshapeOpByExpansion
901     : public OpRewritePattern<GenericOpTy> {
902   using OpRewritePattern<GenericOpTy>::OpRewritePattern;
903 
904   LogicalResult matchAndRewrite(GenericOpTy genericOp,
905                                 PatternRewriter &rewriter) const override {
906     LinalgOp linalgOp = cast<LinalgOp>(genericOp.getOperation());
907     for (auto operand : llvm::enumerate(linalgOp.getInputs())) {
908       TensorReshapeOp reshapeOp =
909           operand.value().getDefiningOp<TensorReshapeOp>();
910       if (!reshapeOp)
911         continue;
912 
913       // Fold only if
914       // - The tensor reshape op is folding.
915       // - All constraints of fusing with reshape by expansion are met.
916       if (reshapeOp.getSrcType().getRank() <
917               reshapeOp.getResultType().getRank() ||
918           !isFusableWithReshapeByDimExpansion(linalgOp, operand.index()) ||
919           isUnitDimExpansionOnly(reshapeOp.getSrcType().getShape(),
920                                  reshapeOp.getReassociationMaps()))
921         continue;
922 
923       Optional<SmallVector<Value, 1>> replacementValues =
924           fuseWithReshapeByExpansion(linalgOp, reshapeOp, operand.index(),
925                                      rewriter);
926       if (!replacementValues)
927         return failure();
928       rewriter.replaceOp(genericOp, replacementValues.getValue());
929       return success();
930     }
931     return failure();
932   }
933 };
934 
935 /// Pattern to fold tensor_reshape op with its producer. The corresponding index
936 /// map in the consumer needs to be modified to linearize the folded dimension.
937 template <bool foldUnitDimReshapesOnly>
938 struct FoldConsumerReshapeOpByLinearization
939     : public OpRewritePattern<TensorReshapeOp> {
940   using OpRewritePattern<TensorReshapeOp>::OpRewritePattern;
941 
942   LogicalResult matchAndRewrite(TensorReshapeOp reshapeOp,
943                                 PatternRewriter &rewriter) const override {
944     LinalgOp producer = reshapeOp.src().getDefiningOp<LinalgOp>();
945     if (!producer ||
946         !isa<GenericOp, IndexedGenericOp>(producer.getOperation()) ||
947         !producer.hasTensorSemantics() || producer.getNumOutputs() != 1 ||
948         !isTensorReshapeOpFoldableByLinearization(
949             reshapeOp, producer.getOutputIndexingMap(0),
950             /*asProducer =*/false) ||
951         (foldUnitDimReshapesOnly &&
952          !isUnitDimExpansionOnly(reshapeOp.getSrcType().getShape(),
953                                  reshapeOp.getReassociationMaps())))
954       return failure();
955     // The indexing_maps for the operands of the fused operation are same as
956     // those for the operands of the producer.
957     SmallVector<AffineMap, 4> fusedIndexMaps = llvm::to_vector<4>(
958         producer.indexing_maps().getAsValueRange<AffineMapAttr>());
959 
960     auto invMap = inversePermutation(producer.getOutputIndexingMap(0));
961 
962     // Compute the indexing map to use for the operand of the producer.
963     AffineMap modifiedMap =
964         linearizeCollapsedDims(invMap, reshapeOp.getSrcType().getShape(),
965                                reshapeOp.getReassociationMaps());
966     for (AffineExpr expr : modifiedMap.getResults()) {
967       if (!expr.isPureAffine())
968         return producer.emitRemark("fused op indexing map is not affine");
969     }
970     fusedIndexMaps.back() = modifiedMap;
971 
972     // Further check that the resulting index maps can be fused and
973     // inverted. Without this the resultant op is not legal.
974     if (!inversePermutation(concatAffineMaps(fusedIndexMaps)))
975       return reshapeOp.emitRemark("fused op loop bound computation failed");
976 
977     Location loc = producer.getLoc();
978     Value output = rewriter.create<TensorReshapeOp>(
979         loc, producer.getOutputs()[0], reshapeOp.getReassociationExprs());
980     LinalgOp fusedOp = createLinalgOpOfSameType(
981         producer, rewriter, loc, reshapeOp.getResultType(),
982         /*inputs=*/producer.getInputs(),
983         // TODO: handle outputs.
984         /*outputs=*/output, rewriter.getAffineMapArrayAttr(fusedIndexMaps),
985         producer.iterator_types(),
986         /*doc=*/nullptr,
987         /*library_call=*/nullptr,
988         /*sparse=*/nullptr);
989     auto &fusedRegion = fusedOp->getRegion(0);
990     rewriter.cloneRegionBefore(producer->getRegion(0), fusedRegion,
991                                fusedRegion.begin());
992     rewriter.replaceOp(reshapeOp, fusedOp->getResults());
993     return success();
994   }
995 };
996 
997 /// Pattern to fold a tensor_reshape op with its producer generic op if the
998 /// tensor_reshape op is expanding, by expanding the dimensionality of the loop
999 /// in the producer op.
1000 struct FoldReshapeWithGenericOpByExpansion
1001     : public OpRewritePattern<TensorReshapeOp> {
1002   using OpRewritePattern<TensorReshapeOp>::OpRewritePattern;
1003   LogicalResult matchAndRewrite(TensorReshapeOp reshapeOp,
1004                                 PatternRewriter &rewriter) const override {
1005     // Fold only if
1006     // - The tensor reshape op is a expanding case.
1007     // - All constraints of fusing with reshape by expansion are met.
1008     if (reshapeOp.getSrcType().getRank() > reshapeOp.getResultType().getRank())
1009       return failure();
1010     LinalgOp producer = reshapeOp.src().getDefiningOp<LinalgOp>();
1011     if (!producer || producer.getNumOutputs() != 1 ||
1012         !isFusableWithReshapeByDimExpansion(producer,
1013                                             producer.getNumInputs()) ||
1014         isUnitDimExpansionOnly(reshapeOp.getResultType().getShape(),
1015                                reshapeOp.getReassociationMaps()))
1016       return failure();
1017     Optional<SmallVector<Value, 1>> replacementValues =
1018         fuseWithReshapeByExpansion(producer, reshapeOp, producer.getNumInputs(),
1019                                    rewriter);
1020     if (!replacementValues)
1021       return failure();
1022     rewriter.replaceOp(reshapeOp, replacementValues.getValue());
1023     return success();
1024   }
1025 };
1026 
1027 /// Pattern to fold a GenericOp/IndexedGenericOp with a splat constant.
1028 template <typename LinalgOpTy>
1029 struct FoldSplatConstants : public OpRewritePattern<LinalgOpTy> {
1030   using OpRewritePattern<LinalgOpTy>::OpRewritePattern;
1031 
1032   LogicalResult matchAndRewrite(LinalgOpTy op,
1033                                 PatternRewriter &rewriter) const override {
1034     if (!op.hasTensorSemantics())
1035       return failure();
1036     LinalgOp linalgOp = cast<LinalgOp>(op.getOperation());
1037     for (auto operand : llvm::enumerate(linalgOp.getInputs())) {
1038       ConstantOp constantOp = operand.value().getDefiningOp<ConstantOp>();
1039       if (!constantOp ||
1040           !constantOp.value().cast<DenseElementsAttr>().isSplat())
1041         continue;
1042 
1043       // The indexing_maps for the operands of the fused operation are same as
1044       // those for the operands of the linalgOp without the indexing map at
1045       // operand.index()
1046       SmallVector<AffineMap, 4> fusedIndexMaps = llvm::to_vector<4>(
1047           linalgOp.indexing_maps().getAsValueRange<AffineMapAttr>());
1048       fusedIndexMaps.erase(std::next(fusedIndexMaps.begin(), operand.index()));
1049 
1050       // The operands list is same as the linalgOp with the argument for
1051       // constant index dropped.
1052       SmallVector<Value, 4> fusedOperands(linalgOp.getInputs());
1053       fusedOperands.erase(std::next(fusedOperands.begin(), operand.index()));
1054 
1055       // Create a constant scalar value from the splat constant.
1056       Value scalarConstant = rewriter.create<ConstantOp>(
1057           constantOp.getLoc(),
1058           constantOp.value().cast<DenseElementsAttr>().getSplatValue());
1059 
1060       LinalgOp fusedOp = createLinalgOpOfSameType(
1061           linalgOp, rewriter, rewriter.getUnknownLoc(),
1062           linalgOp->getResultTypes(),
1063           /*inputs=*/fusedOperands,
1064           /*outputs=*/linalgOp.getOutputs(),
1065           rewriter.getAffineMapArrayAttr(fusedIndexMaps),
1066           linalgOp.iterator_types(),
1067           /*doc=*/nullptr,
1068           /*library_call=*/nullptr,
1069           /*sparse=*/nullptr);
1070 
1071       // Map the block argument corresponding to the replaced argument with the
1072       // scalar constant.
1073       Region &linalgOpRegion = linalgOp->getRegion(0);
1074       Block &entryBlock = *linalgOpRegion.begin();
1075       unsigned argIndex = entryBlock.getNumArguments() -
1076                           linalgOp.getNumShapedOperands() + operand.index();
1077       BlockAndValueMapping mapping;
1078       mapping.map(entryBlock.getArgument(argIndex), scalarConstant);
1079       Region &fusedRegion = fusedOp->getRegion(0);
1080       rewriter.cloneRegionBefore(linalgOpRegion, fusedRegion,
1081                                  fusedRegion.begin(), mapping);
1082       rewriter.replaceOp(linalgOp, fusedOp->getResults());
1083       return success();
1084     }
1085     return failure();
1086   }
1087 };
1088 } // namespace
1089 
1090 Optional<SmallVector<Value, 1>>
1091 mlir::linalg::fuseTensorOps(PatternRewriter &rewriter,
1092                             OpOperand &consumerOpOperand) {
1093   Operation *producer = consumerOpOperand.get().getDefiningOp();
1094   if (!producer || producer->getNumResults() != 1)
1095     return llvm::None;
1096 
1097   // Fuse when consumer is GenericOp or IndexedGenericOp.
1098   if (!isa<GenericOp, IndexedGenericOp>(consumerOpOperand.getOwner()) ||
1099       !isa<GenericOp, IndexedGenericOp>(producer))
1100     return llvm::None;
1101 
1102   return fuseTensorOpsImpl(cast<LinalgOp>(producer), consumerOpOperand,
1103                            rewriter);
1104 }
1105 
1106 namespace {
1107 /// Patterns to fuse a generic op, with the producer of its operands.
1108 template <typename LinalgOpTy>
1109 struct FuseTensorOps : public OpRewritePattern<LinalgOpTy> {
1110   using OpRewritePattern<LinalgOpTy>::OpRewritePattern;
1111 
1112   LogicalResult matchAndRewrite(LinalgOpTy op,
1113                                 PatternRewriter &rewriter) const override {
1114     // Find the first operand that is defined by another generic op on tensors.
1115     for (OpOperand &opOperand : op.getShapedOpOperands()) {
1116       LinalgOp producerOp =
1117           dyn_cast_or_null<LinalgOp>(opOperand.get().getDefiningOp());
1118       if (!producerOp || !producerOp.hasTensorSemantics())
1119         continue;
1120       Optional<SmallVector<Value, 1>> fusedOpResults =
1121           fuseTensorOps(rewriter, opOperand);
1122       if (fusedOpResults) {
1123         rewriter.replaceOp(op, *fusedOpResults);
1124         return success();
1125       }
1126     }
1127     return failure();
1128   }
1129 };
1130 
1131 /// Pass that fuses generic ops on tensors. Used only for testing.
1132 struct FusionOfTensorOpsPass
1133     : public LinalgFusionOfTensorOpsBase<FusionOfTensorOpsPass> {
1134   void runOnOperation() override {
1135     Operation *op = getOperation();
1136     OwningRewritePatternList patterns(op->getContext());
1137     populateLinalgTensorOpsFusionPatterns(patterns);
1138     (void)applyPatternsAndFoldGreedily(op->getRegions(), std::move(patterns));
1139   }
1140 };
1141 
1142 /// Pass to test folding of reshape op with generic/indexed_generic ops by
1143 /// linearization.
1144 struct FoldReshapeOpsByLinearizationPass
1145     : public LinalgFoldReshapeOpsByLinearizationBase<
1146           FoldReshapeOpsByLinearizationPass> {
1147   void runOnOperation() override {
1148     Operation *op = getOperation();
1149     OwningRewritePatternList patterns(op->getContext());
1150     populateFoldReshapeOpsByLinearizationPatterns(patterns);
1151     (void)applyPatternsAndFoldGreedily(op->getRegions(), std::move(patterns));
1152   }
1153 };
1154 
1155 } // namespace
1156 
1157 void mlir::populateFoldReshapeOpsByLinearizationPatterns(
1158     OwningRewritePatternList &patterns) {
1159   patterns.insert<FoldProducerReshapeOpByLinearization<GenericOp, false>,
1160                   FoldProducerReshapeOpByLinearization<IndexedGenericOp, false>,
1161                   FoldConsumerReshapeOpByLinearization<false>>(
1162       patterns.getContext());
1163 }
1164 
1165 void mlir::populateFoldUnitDimsReshapeOpsByLinearizationPatterns(
1166     OwningRewritePatternList &patterns) {
1167   patterns.insert<FoldProducerReshapeOpByLinearization<GenericOp, true>,
1168                   FoldProducerReshapeOpByLinearization<IndexedGenericOp, true>,
1169                   FoldConsumerReshapeOpByLinearization<true>>(
1170       patterns.getContext());
1171 }
1172 
1173 void mlir::populateFoldReshapeOpsByExpansionPatterns(
1174     OwningRewritePatternList &patterns) {
1175   patterns.insert<FoldReshapeWithGenericOpByExpansion,
1176                   FoldWithProducerReshapeOpByExpansion<GenericOp>,
1177                   FoldWithProducerReshapeOpByExpansion<IndexedGenericOp>>(
1178       patterns.getContext());
1179 }
1180 
1181 void mlir::populateLinalgTensorOpsFusionPatterns(
1182     OwningRewritePatternList &patterns) {
1183   auto *context = patterns.getContext();
1184   patterns.insert<FuseTensorOps<GenericOp>, FuseTensorOps<IndexedGenericOp>,
1185                   FoldSplatConstants<GenericOp>,
1186                   FoldSplatConstants<IndexedGenericOp>>(context);
1187   populateFoldReshapeOpsByExpansionPatterns(patterns);
1188   GenericOp::getCanonicalizationPatterns(patterns, context);
1189   IndexedGenericOp::getCanonicalizationPatterns(patterns, context);
1190   TensorReshapeOp::getCanonicalizationPatterns(patterns, context);
1191 }
1192 
1193 std::unique_ptr<Pass> mlir::createLinalgFusionOfTensorOpsPass() {
1194   return std::make_unique<FusionOfTensorOpsPass>();
1195 }
1196 
1197 std::unique_ptr<Pass> mlir::createFoldReshapeOpsByLinearizationPass() {
1198   return std::make_unique<FoldReshapeOpsByLinearizationPass>();
1199 }
1200