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