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/Matchers.h"
22 #include "mlir/IR/PatternMatch.h"
23 #include "mlir/Support/LLVM.h"
24 #include "mlir/Transforms/GreedyPatternRewriteDriver.h"
25 
26 using namespace mlir;
27 using namespace mlir::linalg;
28 
29 /// Conditions for elementwise fusion of generic operations.
30 static bool areElementwiseOpsFusable(GenericOp producer, GenericOp consumer,
31                                      OpOperand *consumerOpOperand) {
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   // Only allow fusing the producer of an input operand for now.
42   // TODO: allow fusing the producer of an output operand.
43   if (!consumer.isInputTensor(consumerOpOperand))
44     return false;
45 
46   // Get the consumer index map. The number of results of the consumer index
47   // map must match the number of loops of the producer.
48   AffineMap consumerIndexMap = consumer.getTiedIndexingMap(consumerOpOperand);
49   if (consumerIndexMap.getNumResults() != producer.getNumLoops())
50     return false;
51 
52   // Currently support only operations with single result.
53   if (producer.getNumOutputs() != 1)
54     return false;
55 
56   // Finally the index_map for the result must be invertible. For now just
57   // verify it is a permutation.
58   AffineMap producerResultIndexMap =
59       producer.getTiedIndexingMap(producer.getOutputOperand(0));
60   return producerResultIndexMap.isPermutation();
61 }
62 
63 /// Append to `fusedOpIndexingMapAttrs` the indexing maps for the operands of
64 /// the `producer` to use in the fused operation given the indexing map of the
65 /// result of the producer in the consumer.
66 static AffineMap getIndexingMapOfProducerOperandsInCoordinatesOfFusedOp(
67     OpOperand *producerOpOperand, AffineMap producerResultIndexMap,
68     AffineMap fusedConsumerArgIndexMap) {
69   // The indexing map in the consumer op (fusedConsumerArgIndexMap) is a map
70   // from consumer loop -> consumer arg tensor index/producer result tensor
71   // index. The fused loop is same as the consumer loop. For each producer arg
72   // the indexing map to be computed is a map from consumer loop -> producer
73   // arg tensor index.
74   // producerResultIndexMap is a map from producer loop -> tensor index.
75   // Compute the inverse to get map from tensor index -> producer loop.
76   // The inverse is a map from producer result tensor index -> producer loop.
77   AffineMap invProducerResultIndexMap =
78       inversePermutation(producerResultIndexMap);
79   assert(invProducerResultIndexMap &&
80          "expected producer result indexig map to be invertible");
81 
82   LinalgOp producer = cast<LinalgOp>(producerOpOperand->getOwner());
83   // argMap is a map from producer loop -> producer arg tensor index.
84   AffineMap argMap = producer.getTiedIndexingMap(producerOpOperand);
85 
86   // Compose argMap with invProducerResultIndexMap to get a map from
87   // producer result tensor index -> producer arg tensor index.
88   AffineMap t1 = argMap.compose(invProducerResultIndexMap);
89 
90   // Compose t1 with fusedConsumerArgIndexMap gives an indexing map from
91   // consumer loop/ fused loop -> producer arg tensor index.
92   return t1.compose(fusedConsumerArgIndexMap);
93 }
94 
95 /// Generate the region of the fused tensor operation. The region of the fused
96 /// op must be empty.
97 static void
98 generateFusedElementwiseOpRegion(PatternRewriter &rewriter, GenericOp fusedOp,
99                                  AffineMap consumerToProducerLoopsMap,
100                                  OpOperand *consumerOpOperand,
101                                  unsigned nloops) {
102   auto producer = cast<GenericOp>(consumerOpOperand->get().getDefiningOp());
103   auto consumer = cast<GenericOp>(consumerOpOperand->getOwner());
104   // Build the region of the fused op.
105   Block &producerBlock = producer->getRegion(0).front();
106   Block &consumerBlock = consumer->getRegion(0).front();
107   Block *fusedBlock = new Block();
108   fusedOp.region().push_back(fusedBlock);
109   BlockAndValueMapping mapper;
110   OpBuilder::InsertionGuard guard(rewriter);
111   rewriter.setInsertionPointToStart(fusedBlock);
112 
113   // 2. Add an index operation for every fused loop dimension and use the
114   // `consumerToProducerLoopsMap` to map the producer indices.
115   if (producer.hasIndexSemantics()) {
116     // Add an index operation for every fused loop dimension.
117     unsigned numFusedOpLoops =
118         std::max(producer.getNumLoops(), consumer.getNumLoops());
119     SmallVector<Value> fusedIndices;
120     fusedIndices.reserve(numFusedOpLoops);
121     llvm::transform(llvm::seq<uint64_t>(0, numFusedOpLoops),
122                     std::back_inserter(fusedIndices), [&](uint64_t dim) {
123                       return rewriter.create<IndexOp>(producer.getLoc(), dim);
124                     });
125     for (IndexOp indexOp :
126          llvm::make_early_inc_range(producerBlock.getOps<IndexOp>())) {
127       Value newIndex = rewriter.create<mlir::AffineApplyOp>(
128           producer.getLoc(),
129           consumerToProducerLoopsMap.getSubMap(indexOp.dim()), fusedIndices);
130       mapper.map(indexOp.getResult(), newIndex);
131     }
132   }
133   // TODO: allow fusing the producer of an output operand.
134   assert(consumer.isInputTensor(consumerOpOperand) &&
135          "expected producer of input operand");
136   // 3. Consumer input operands up to consumerIdx (exclusive).
137   for (BlockArgument bbArg : consumerBlock.getArguments().take_front(
138            consumerOpOperand->getOperandNumber())) // input assumption.
139     mapper.map(bbArg, fusedBlock->addArgument(bbArg.getType()));
140 
141   // Replacing consumerIdx requires getting the cloned, yielded, value from
142   // the (cloned) producer block. This happens in step 9.
143 
144   // 4. Splice in producer's input operands.
145   for (BlockArgument bbArg :
146        producerBlock.getArguments().take_front(producer.getNumInputs()))
147     mapper.map(bbArg, fusedBlock->addArgument(bbArg.getType()));
148 
149   // 4.b. Producer output operand/map that is fused needs to be mapped to the
150   // producer bbArg if it is an "initTensor" (i.e. its value is actually read).
151   assert(producer->getNumResults() == 1 && "expected single result producer");
152   if (producer.isInitTensor(producer.getOutputOperand(0))) {
153     BlockArgument bbArg = producerBlock.getArguments()
154                               .drop_front(producer.getNumInputs())
155                               // TODO: bbArg index of
156                               .front();
157     mapper.map(bbArg, fusedBlock->addArgument(bbArg.getType()));
158   }
159   // 5. Remaining consumer's input operands (drop past index `consumerIdx`).
160   for (BlockArgument bbArg :
161        consumerBlock.getArguments()
162            .take_front(consumer.getNumInputs())
163            .drop_front(consumerOpOperand->getOperandNumber() + 1))
164     mapper.map(bbArg, fusedBlock->addArgument(bbArg.getType()));
165   // 6. All of consumer's output operands.
166   for (BlockArgument bbArg :
167        consumerBlock.getArguments().take_back(consumer.getNumOutputs()))
168     mapper.map(bbArg, fusedBlock->addArgument(bbArg.getType()));
169   // 7. All of producer's output operands except the one fused.
170   // TODO: allow fusion of multi-result producers.
171   assert(producer->getNumResults() == 1 && "expected single result producer");
172 
173   // 8. Clone all producer operations except for the yield and index operations
174   // to the fused operation.
175   for (auto &op : producerBlock.without_terminator()) {
176     if (!isa<IndexOp>(op))
177       rewriter.clone(op, mapper);
178   }
179   // 9. Now we can map the consumerBlock's `consumerIdx` block argument. Just
180   // forward the yield operand.
181   auto yieldOp = cast<linalg::YieldOp>(producerBlock.getTerminator());
182   // TODO: allow fusion of multi-result producers.
183   assert(producer->getNumResults() == 1 && "expected single result producer");
184   unsigned producerResultNumber = 0;
185   Value replacement =
186       mapper.lookupOrDefault(yieldOp.getOperand(producerResultNumber));
187   // Sanity checks, if replacement is not already in the mapper then it must be
188   // produced outside.
189   if (replacement == yieldOp.getOperand(producerResultNumber)) {
190     if (auto bb = replacement.dyn_cast<BlockArgument>())
191       assert(bb.getOwner() != &producerBlock &&
192              "yielded block argument must have been mapped");
193     else
194       assert(!producer->isAncestor(replacement.getDefiningOp()) &&
195              "yielded value must have been mapped");
196   }
197   mapper.map(consumerBlock.getArgument(consumerOpOperand->getOperandNumber()),
198              replacement);
199   // 10. Clone operations from the consumer to the fused op.
200   for (auto &op : consumerBlock.getOperations())
201     rewriter.clone(op, mapper);
202 
203   // Sanity checks.
204   assert(fusedBlock->getNumArguments() == fusedOp.getNumOperands() &&
205          "Ill-formed GenericOp region");
206 }
207 
208 static Optional<SmallVector<Value>>
209 fuseElementwiseOpsImpl(GenericOp producer, OpOperand *consumerOpOperand,
210                        const ControlElementwiseOpsFusionFn &controlFn,
211                        PatternRewriter &rewriter) {
212   auto consumer = cast<GenericOp>(consumerOpOperand->getOwner());
213   if (!areElementwiseOpsFusable(producer, consumer, consumerOpOperand) ||
214       !controlFn(producer->getResult(0), *consumerOpOperand))
215     return llvm::None;
216 
217   // TODO: allow fusing the producer of an output operand.
218   assert(consumer.isInputTensor(consumerOpOperand) &&
219          "expected producer of input operand");
220 
221   // Compute the fused operands list and indexing maps.
222   SmallVector<Value> fusedOperands;
223   SmallVector<AffineMap> fusedIndexMaps;
224   fusedOperands.reserve(producer->getNumOperands() +
225                         consumer->getNumOperands());
226   fusedIndexMaps.reserve(producer->getNumOperands() +
227                          consumer->getNumOperands());
228   // In the following, numbering matches that of `generateFusedTensorOpRegion`.
229   // 3. Consumer input operands/maps up to consumerIdx (exclusive).
230   SmallVector<OpOperand *> consumerInputs = consumer.getInputOperands();
231   SmallVector<OpOperand *>::iterator it =
232       llvm::find(consumerInputs, consumerOpOperand);
233   assert(it != consumerInputs.end() && "expected to find the consumer operand");
234   for (OpOperand *opOperand : llvm::make_range(consumerInputs.begin(), it)) {
235     fusedOperands.push_back(opOperand->get());
236     fusedIndexMaps.push_back(consumer.getTiedIndexingMap(opOperand));
237   }
238   // 4. Splice in producer's input operands/maps.
239   assert(producer->getNumResults() == 1 && "expected single result producer");
240   AffineMap producerResultIndexMap =
241       producer.getTiedIndexingMap(producer.getOutputOperand(0));
242   for (OpOperand *opOperand : producer.getInputOperands()) {
243     fusedOperands.push_back(opOperand->get());
244     // Compute indexing maps for the producer args in the fused operation.
245     AffineMap map = getIndexingMapOfProducerOperandsInCoordinatesOfFusedOp(
246         opOperand, producerResultIndexMap,
247         consumer.getTiedIndexingMap(consumerOpOperand));
248     fusedIndexMaps.push_back(map);
249   }
250   // 4.b. Producer output operand/map that is fused needs to be passed if it is
251   // an "initTensor" (i.e. its value is actually read).
252   assert(producer->getNumResults() == 1 && "expected single result producer");
253   if (producer.isInitTensor(producer.getOutputOperand(0))) {
254     fusedOperands.push_back(producer.getOutputOperand(0)->get());
255     // Compute indexing maps for the producer args in the fused operation.
256     AffineMap map = getIndexingMapOfProducerOperandsInCoordinatesOfFusedOp(
257         producer.getOutputOperand(0), producerResultIndexMap,
258         consumer.getTiedIndexingMap(consumerOpOperand));
259     fusedIndexMaps.push_back(map);
260   }
261   // 5. Remaining consumer's input operands/maps (drop past index
262   // `consumerIdx`).
263   for (OpOperand *opOperand :
264        llvm::make_range(std::next(it), consumerInputs.end())) {
265     fusedOperands.push_back(opOperand->get());
266     fusedIndexMaps.push_back(consumer.getTiedIndexingMap(opOperand));
267   }
268   // 6. All of consumer's output operands (skip operands: added by the builder).
269   for (OpOperand *opOperand : consumer.getOutputOperands())
270     fusedIndexMaps.push_back(consumer.getTiedIndexingMap(opOperand));
271   // 7. All of producer's output operands/maps except the one fused.
272   // TODO: allow fusion of multi-result producers.
273   assert(producer->getNumResults() == 1 && "expected single result producer");
274 
275   // Generate the fused op.
276   SmallVector<Value> consumerOutputs = consumer.getOutputOperands();
277   auto fusedOp = rewriter.create<GenericOp>(
278       consumer.getLoc(), consumer->getResultTypes(),
279       /*inputs=*/fusedOperands,
280       // TODO: handle outputs.
281       consumerOutputs, rewriter.getAffineMapArrayAttr(fusedIndexMaps),
282       consumer.iterator_types(),
283       /*doc=*/nullptr,
284       /*library_call=*/nullptr);
285 
286   // Construct an AffineMap from consumer loops to producer loops.
287   // consumer loop -> tensor index
288   AffineMap consumerResultIndexMap =
289       consumer.getTiedIndexingMap(consumerOpOperand);
290   // tensor index -> producer loop
291   AffineMap invProducerResultIndexMap =
292       inversePermutation(producerResultIndexMap);
293   assert(invProducerResultIndexMap &&
294          "expected producer result indexig map to be invertible");
295   // consumer loop -> producer loop
296   AffineMap consumerToProducerLoopsMap =
297       invProducerResultIndexMap.compose(consumerResultIndexMap);
298 
299   generateFusedElementwiseOpRegion(rewriter, fusedOp,
300                                    consumerToProducerLoopsMap,
301                                    consumerOpOperand, consumer.getNumLoops());
302   return SmallVector<Value>(fusedOp->getResults());
303 }
304 
305 /// Linearize the expressions in `sourceMap` based on the `reassociationMaps`
306 /// provided, given the shape of the source tensor that corresponds to the
307 /// `sourceMap`. Note that this implicitly assumes that the tensors dimensions
308 /// are "row-major" ordered logically.
309 ///
310 /// For example:
311 ///
312 /// %0 = op ... : tensor<?x?x4x5xf32>
313 /// with output index_map `affine_map<(d0, d1, d2, d3) -> (d0, d1, d2, d3)>`
314 ///
315 /// and reshape:
316 /// %1 = linalg.tensor_collapse_shape %0 [[0], [0, 1, 2]] :
317 ///        tensor<?x?x4x5xf32> into tensor<?x?xf32>
318 ///
319 /// would be rewritten into:
320 /// %0 = op ... : tensor<?x?x4x5xf32>
321 /// with output index_map
322 ///   `affine_map<(d0, d1, d2, d3) -> (d0, d1 * 20 + d2 * 5 + d3)>`
323 static AffineMap linearizeCollapsedDims(AffineMap sourceMap,
324                                         ArrayRef<int64_t> sourceShape,
325                                         ArrayRef<AffineMap> reassociationMaps) {
326   SmallVector<AffineExpr> resultExprs;
327   resultExprs.reserve(reassociationMaps.size());
328   ArrayRef<AffineExpr> sourceExprs = sourceMap.getResults();
329   MLIRContext *context = sourceMap.getContext();
330 
331   // Compute the result exprs based on the reassociation maps.
332   for (AffineMap map : reassociationMaps) {
333     ArrayRef<AffineExpr> collapsedDims = map.getResults();
334     // Assume that they are in-order and contiguous (already checked in
335     // verifier).
336     assert(!collapsedDims.empty());
337     unsigned startDim =
338         collapsedDims.front().cast<AffineDimExpr>().getPosition();
339     SmallVector<int64_t> sizes;
340     SmallVector<AffineExpr> dimExprs;
341     for (auto en :
342          llvm::zip(sourceShape.slice(startDim, collapsedDims.size()),
343                    sourceExprs.slice(startDim, collapsedDims.size()))) {
344       if (std::get<0>(en) == 1)
345         continue;
346       sizes.push_back(std::get<0>(en));
347       dimExprs.push_back(std::get<1>(en));
348     }
349     AffineExpr linearizedExpr =
350         makeCanonicalStridedLayoutExpr(sizes, dimExprs, context);
351     resultExprs.push_back(linearizedExpr);
352   }
353   return AffineMap::get(sourceMap.getNumDims(), sourceMap.getNumSymbols(),
354                         resultExprs, context);
355 }
356 
357 // TensorExpandShapeOp is fusable with its consumer (i.e. reshape as a
358 // producer). Fusing when operand has higher rank will require use of mods and
359 // divs in the indexing maps of the fused op which would make it non-invertible.
360 static bool isTensorReshapeOpFoldableByLinearization(
361     TensorExpandShapeOp expandOp, AffineMap useIndexMap, bool asProducer) {
362   if (!asProducer && expandOp.getResultType().hasStaticShape())
363     return false;
364   return useIndexMap.isPermutation();
365 }
366 
367 // TensorCollapseShapeOp is fusable with its producer (i.e. reshape as a
368 // consumer).
369 static bool isTensorReshapeOpFoldableByLinearization(
370     TensorCollapseShapeOp collapseOp, AffineMap useIndexMap, bool asProducer) {
371   if (asProducer && collapseOp.getSrcType().hasStaticShape())
372     return false;
373   return useIndexMap.isPermutation();
374 }
375 
376 /// Check if the reshape operation is only expansion into/collapsing of
377 /// unit-dimension.
378 static bool isUnitDimExpansionOnly(ArrayRef<int64_t> expandedShape,
379                                    ArrayRef<AffineMap> reassociation) {
380   for (auto &map : reassociation) {
381     unsigned numUnitDims = 0;
382     for (AffineExpr expr : map.getResults()) {
383       unsigned position = expr.cast<AffineDimExpr>().getPosition();
384       if (expandedShape[position] == 1)
385         numUnitDims++;
386     }
387     if (numUnitDims != map.getNumResults() - 1)
388       return false;
389   }
390   return true;
391 }
392 
393 /// Conditions for folding a generic operation with a reshape op by expanding
394 /// the iteration space dimensionality for tensor operations. These are
395 /// preconditions assumed by `foldReshapeByDimExpansion` which implements the
396 /// following fusion pattern.
397 ///
398 ///  Consider
399 ///
400 ///  %c = linalg.generic ins(%a, %b : memref<?x?x?xf32>, memref<?x?xf32>)
401 ///         indexing_maps = [affine_map<(d0, d1, d2) -> (d1, d0, d2)>,
402 ///                          affine_map<(d0, d1, d2) -> (d1, d2)>,
403 ///                          affine_map<(d0, d1, d2) -> (d0, d2, d1)>]
404 ///  %d = linalg.tensor_expand_shape %c [[0, 1], [2], [3, 4, 5]]
405 ///       : tensor<?x?x?xf32> into tensor<?x?x?x?x?x?xf32>
406 ///
407 ///  The reshape can be folded into the `genericOp` if its loop dimensionality
408 ///  is increased to match the result (operand) of the tensor_expand_shape.
409 ///  The indexing_map of the fused tensor in the `genericOp` and the
410 ///  reassociation map helps compute the indexing maps of the modified op.
411 ///  For the above example, based on the reassociation map it
412 ///  can be concluded that
413 ///
414 ///  - The loop used to access the first dimension of the fused tensor is split
415 ///    into two.
416 ///  - The loop used to access the second dimension of the fused tensor is kept
417 ///    as is.
418 ///  - The loop used to access the third dimension of the fused tensor is split
419 ///    into three.
420 ///
421 ///  i.e. (e0, e1, e2, e3, e4) is the domain of the indexing map of the modified
422 ///  op, then
423 ///
424 ///   d0 -> e0, e1
425 ///   d1 -> e2, e3, e4
426 ///   d2 -> e5
427 ///
428 ///  substituting this, the generic op can be rewritten as
429 ///
430 ///  %d = linalg.generic ins(%0, %1 : )
431 ///        indexing_maps =
432 ///         [affine_map<(e0, e1, e2, e3, e4, e5) -> (e2, e3, e4, e0, e1, e5)>,
433 ///          affine_map<(e0, e1, e2, e3, e4, e5) -> (e2, e3, e4, e5)>,
434 ///          affine_map<(e0, e1, e2, e3, e4, e5) -> (e0, e1, e5, e2, e3, e4)>]
435 ///
436 ///  Since operands to the linalg generic are now 5D, reshapes can be introduced
437 ///  to make it consistent
438 ///
439 ///  %0 = linalg.tensor_expand_shape %a [[0, 1, 2], [3, 4], [5]]
440 ///       : tensor<?x?x?xf32> into tensor<?x?x?x?x?x?xf32>
441 ///  %1 = linalg.tensor_expand_shape %b [[0, 1, 2], [3]]
442 ///       : tensor<?x?x?xf32> into tensor<?x?x?x?xf32>
443 ///
444 ///  The added reshapes are again expanding patterns, so they will get fused
445 ///  with its producers if possible.
446 static bool isFusableWithReshapeByDimExpansion(GenericOp genericOp,
447                                                OpOperand *fusableOpOperand) {
448   // Is fusable only if:
449   // - All the indexing maps for operands and results are projected
450   //   permutations.
451   // - The fused tensor is not a scalar.
452   // - All the loops are parallel loops.
453   return genericOp.hasTensorSemantics() &&
454          llvm::all_of(genericOp.indexing_maps().getValue(),
455                       [](Attribute attr) {
456                         return attr.cast<AffineMapAttr>()
457                             .getValue()
458                             .isProjectedPermutation();
459                       }) &&
460          genericOp.getTiedIndexingMap(fusableOpOperand).getNumResults() > 0 &&
461          llvm::all_of(genericOp.iterator_types(), [](Attribute attr) {
462            return attr.cast<StringAttr>().getValue() ==
463                   getParallelIteratorTypeName();
464          });
465 }
466 
467 namespace {
468 /// Information needed to expand a generic operation to fold the reshape with
469 /// it.
470 class ExpansionInfo {
471 public:
472   // Computes the mapping from original dimensions of the op to the dimensions
473   // of the expanded op given the `indexingMap` of the fused operand/result of
474   // the generic op, the `reassocationMaps` of the reshape op and the shape of
475   // the expanded op.
476   LogicalResult compute(LinalgOp linalgOp, OpOperand *fusableOpOperand,
477                         ArrayRef<AffineMap> reassociationMaps,
478                         ArrayRef<int64_t> expandedShape,
479                         PatternRewriter &rewriter);
480   unsigned getOrigOpNumDims() const { return reassociation.size(); }
481   unsigned getExpandedOpNumDims() const { return expandedOpNumDims; }
482   ReassociationIndicesRef getExpandedDims(unsigned i) const {
483     return reassociation[i];
484   }
485   ArrayRef<int64_t> getExpandedShapeOfDim(unsigned i) const {
486     return expandedShapeMap[i];
487   }
488 
489 private:
490   /// Reassociation from the dimensions in the original operation to the
491   /// dimension of the expanded operation.
492   SmallVector<ReassociationIndices> reassociation;
493   /// Mapping from extent of loops in the original operation, to the extent of
494   /// loops in the expanded operation.
495   SmallVector<SmallVector<int64_t>> expandedShapeMap;
496   unsigned expandedOpNumDims;
497 };
498 } // namespace
499 
500 LogicalResult ExpansionInfo::compute(LinalgOp linalgOp,
501                                      OpOperand *fusableOpOperand,
502                                      ArrayRef<AffineMap> reassociationMaps,
503                                      ArrayRef<int64_t> expandedShape,
504                                      PatternRewriter &rewriter) {
505   if (reassociationMaps.empty())
506     return failure();
507   AffineMap fusedIndexMap = linalgOp.getTiedIndexingMap(fusableOpOperand);
508 
509   Optional<SmallVector<int64_t, 4>> originalLoopRange =
510       linalgOp.getStaticLoopRanges();
511   if (!originalLoopRange)
512     return rewriter.notifyMatchFailure(linalgOp, "unable to find loop range");
513 
514   reassociation.clear();
515   expandedShapeMap.clear();
516   // Compute the number of dimension in the expanded op that correspond to each
517   // dimension of the original op.
518   SmallVector<unsigned> numExpandedDims(fusedIndexMap.getNumDims(), 1);
519   expandedShapeMap.resize(fusedIndexMap.getNumDims());
520   for (auto resultExpr : llvm::enumerate(fusedIndexMap.getResults())) {
521     unsigned pos = resultExpr.value().cast<AffineDimExpr>().getPosition();
522     AffineMap foldedDims = reassociationMaps[resultExpr.index()];
523     numExpandedDims[pos] = foldedDims.getNumResults();
524     ArrayRef<int64_t> shape =
525         expandedShape.slice(foldedDims.getDimPosition(0), numExpandedDims[pos]);
526     expandedShapeMap[pos].assign(shape.begin(), shape.end());
527   }
528   // The remaining dimensions remain the same.
529   for (unsigned i : llvm::seq<unsigned>(0, fusedIndexMap.getNumDims()))
530     if (expandedShapeMap[i].empty())
531       expandedShapeMap[i] = {(*originalLoopRange)[i]};
532 
533   // Compute reassociation map from the original op to the expanded op.
534   unsigned sum = 0;
535   reassociation.reserve(fusedIndexMap.getNumDims());
536   for (auto numFoldedDim : llvm::enumerate(numExpandedDims)) {
537     auto seq = llvm::seq<int64_t>(sum, sum + numFoldedDim.value());
538     reassociation.emplace_back(seq.begin(), seq.end());
539     sum += numFoldedDim.value();
540   }
541   expandedOpNumDims = sum;
542   return success();
543 }
544 
545 /// Epanding the body of a linalg operation requires adaptations of the accessed
546 /// loop indices. Specifically, access of indices in the original operation need
547 /// to be replaced with linearizations of indices in the expanded op. That
548 /// requires the shape of the expanded dimensions to be static (at least all but
549 /// the most significant). For now check that these are all statically sized.
550 /// Note that this could be extended to handle dynamic case, but the
551 /// implementation below uses `affine.apply` which seems to have issues when the
552 /// shapes are not static.
553 LogicalResult isGenericOpExpandable(GenericOp genericOp,
554                                     const ExpansionInfo &expansionInfo,
555                                     PatternRewriter &rewriter) {
556   if (!genericOp.hasIndexSemantics())
557     return success();
558   for (unsigned i : llvm::seq<unsigned>(0, expansionInfo.getOrigOpNumDims())) {
559     ArrayRef<int64_t> expandedShape = expansionInfo.getExpandedShapeOfDim(i);
560     if (expandedShape.size() == 1)
561       continue;
562     for (int64_t shape : expandedShape.drop_front()) {
563       if (ShapedType::isDynamic(shape)) {
564         return rewriter.notifyMatchFailure(
565             genericOp, "cannot expand due to index semantics and dynamic dims");
566       }
567     }
568   }
569   return success();
570 }
571 
572 /// Return the indexing map to use in the expanded op for a given the
573 /// `indexingMap` of the original operation.
574 static AffineMap
575 getIndexingMapInExpandedOp(OpBuilder &builder, AffineMap indexingMap,
576                            const ExpansionInfo &expansionInfo) {
577   SmallVector<AffineExpr> newExprs;
578   for (AffineExpr expr : indexingMap.getResults()) {
579     unsigned pos = expr.cast<AffineDimExpr>().getPosition();
580     SmallVector<AffineExpr, 4> expandedExprs = llvm::to_vector<4>(
581         llvm::map_range(expansionInfo.getExpandedDims(pos), [&](int64_t v) {
582           return builder.getAffineDimExpr(static_cast<unsigned>(v));
583         }));
584     newExprs.append(expandedExprs.begin(), expandedExprs.end());
585   }
586   return AffineMap::get(expansionInfo.getExpandedOpNumDims(),
587                         indexingMap.getNumSymbols(), newExprs,
588                         builder.getContext());
589 }
590 
591 /// Return the type of the operand/result to use in the expanded op given the
592 /// type in the original op.
593 static RankedTensorType getExpandedType(RankedTensorType originalType,
594                                         AffineMap indexingMap,
595                                         const ExpansionInfo &expansionInfo) {
596   SmallVector<int64_t> expandedShape;
597   for (AffineExpr expr : indexingMap.getResults()) {
598     unsigned dim = expr.cast<AffineDimExpr>().getPosition();
599     auto dimExpansion = expansionInfo.getExpandedShapeOfDim(dim);
600     expandedShape.append(dimExpansion.begin(), dimExpansion.end());
601   }
602   return RankedTensorType::get(expandedShape, originalType.getElementType());
603 }
604 
605 /// Returns the reassociation maps to use in the `linalg.tensor_expand_shape`
606 /// operation to convert the operands of the original operation to operands of
607 /// the expanded operation. The same method is used to compute the
608 /// `linalg.tensor_collapse_shape` used to collapse the result of the expanded
609 /// op to get the value that can replace all uses of the results of the original
610 /// op.
611 static SmallVector<ReassociationIndices>
612 getReassociationForExpansion(AffineMap indexingMap,
613                              const ExpansionInfo &expansionInfo) {
614   SmallVector<ReassociationIndices> reassociation;
615   unsigned numReshapeDims = 0;
616   for (AffineExpr expr : indexingMap.getResults()) {
617     unsigned dim = expr.cast<AffineDimExpr>().getPosition();
618     auto numExpandedDims = expansionInfo.getExpandedDims(dim).size();
619     SmallVector<int64_t, 2> indices = llvm::to_vector<2>(
620         llvm::seq<int64_t>(numReshapeDims, numReshapeDims + numExpandedDims));
621     reassociation.emplace_back(std::move(indices));
622     numReshapeDims += numExpandedDims;
623   }
624   return reassociation;
625 }
626 
627 /// Update the body of an expanded linalg operation having index semantics. The
628 /// indices of the original operation need to be recovered by linearizing the
629 /// indices of the correspoding dimensions of the expanded operation. For now it
630 /// is assumed that the shapes of the expanded operation needed for
631 /// linearization are static.
632 static void updateExpandedGenericOpRegion(PatternRewriter &rewriter,
633                                           Location loc, Region &fusedRegion,
634                                           const ExpansionInfo &expansionInfo) {
635   // Replace the original indices by the linearization of the expanded indices.
636   for (IndexOp indexOp :
637        llvm::make_early_inc_range(fusedRegion.front().getOps<IndexOp>())) {
638     ArrayRef<int64_t> expandedDims =
639         expansionInfo.getExpandedDims(indexOp.dim());
640     assert(!expandedDims.empty() && "expected valid expansion info");
641 
642     // Skip index operations that are not affected by the expansion.
643     if (expandedDims.size() == 1 &&
644         expandedDims.front() == (int64_t)indexOp.dim())
645       continue;
646 
647     // Linearize the expanded indices of the original index dimension.
648     OpBuilder::InsertionGuard guard(rewriter);
649     rewriter.setInsertionPointAfter(indexOp);
650     ArrayRef<int64_t> expandedDimsShape =
651         expansionInfo.getExpandedShapeOfDim(indexOp.dim()).drop_front();
652     SmallVector<Value> expandedIndices;
653     expandedIndices.reserve(expandedDims.size() - 1);
654     llvm::transform(
655         expandedDims.drop_front(), std::back_inserter(expandedIndices),
656         [&](int64_t dim) { return rewriter.create<IndexOp>(loc, dim); });
657     Value newIndex = rewriter.create<IndexOp>(loc, expandedDims.front());
658     for (auto it : llvm::zip(expandedDimsShape, expandedIndices)) {
659       assert(!ShapedType::isDynamic(std::get<0>(it)));
660       AffineExpr idx, acc;
661       bindDims(rewriter.getContext(), idx, acc);
662       newIndex = rewriter.create<AffineApplyOp>(
663           indexOp.getLoc(), idx + acc * std::get<0>(it),
664           ValueRange{std::get<1>(it), newIndex});
665     }
666     rewriter.replaceOp(indexOp, newIndex);
667   }
668 }
669 
670 /// Implements the fusion of a tensor_collapse_shape or a tensor_expand_shape op
671 /// and a generic op as explained in `isFusableWithReshapeByExpansion`. Assumes
672 /// that those conditions have been satisfied.
673 static Optional<SmallVector<Value>>
674 fuseWithReshapeByExpansion(GenericOp genericOp, Operation *reshapeOp,
675                            OpOperand *fusableOpOperand,
676                            PatternRewriter &rewriter) {
677   assert(isFusableWithReshapeByDimExpansion(genericOp, fusableOpOperand) &&
678          "preconditions for fuse operation failed");
679   // Check if reshape is expanding or collapsing.
680   auto expandingReshapeOp = dyn_cast<TensorExpandShapeOp>(*reshapeOp);
681   auto collapsingReshapeOp = dyn_cast<TensorCollapseShapeOp>(*reshapeOp);
682   bool isExpanding = (expandingReshapeOp != nullptr);
683   RankedTensorType expandedType = isExpanding
684                                       ? expandingReshapeOp.getResultType()
685                                       : collapsingReshapeOp.getSrcType();
686 
687   ExpansionInfo expansionInfo;
688   if (failed(expansionInfo.compute(
689           genericOp, fusableOpOperand,
690           isExpanding ? expandingReshapeOp.getReassociationMaps()
691                       : collapsingReshapeOp.getReassociationMaps(),
692           expandedType.getShape(), rewriter)))
693     return llvm::None;
694 
695   if (failed(isGenericOpExpandable(genericOp, expansionInfo, rewriter)))
696     return llvm::None;
697 
698   SmallVector<AffineMap, 4> expandedOpIndexingMaps = llvm::to_vector<4>(
699       llvm::map_range(genericOp.getIndexingMaps(), [&](AffineMap m) {
700         return getIndexingMapInExpandedOp(rewriter, m, expansionInfo);
701       }));
702 
703   SmallVector<Value> expandedOpOperands;
704   expandedOpOperands.reserve(genericOp.getNumInputs());
705   for (OpOperand *opOperand : genericOp.getInputOperands()) {
706     if (opOperand == fusableOpOperand) {
707       expandedOpOperands.push_back(isExpanding ? expandingReshapeOp.src()
708                                                : collapsingReshapeOp.src());
709       continue;
710     }
711     if (genericOp.isInputTensor(opOperand)) {
712       AffineMap indexingMap = genericOp.getTiedIndexingMap(opOperand);
713       RankedTensorType expandedOperandType =
714           getExpandedType(opOperand->get().getType().cast<RankedTensorType>(),
715                           indexingMap, expansionInfo);
716       if (expandedOperandType != opOperand->get().getType()) {
717         // Reshape the operand to get the right type.
718         SmallVector<ReassociationIndices> reassociation =
719             getReassociationForExpansion(indexingMap, expansionInfo);
720         expandedOpOperands.push_back(rewriter.create<TensorExpandShapeOp>(
721             genericOp.getLoc(), expandedOperandType, opOperand->get(),
722             reassociation));
723         continue;
724       }
725     }
726     expandedOpOperands.push_back(opOperand->get());
727   }
728 
729   Location loc = genericOp.getLoc();
730   SmallVector<Value> outputs;
731   for (OpOperand *opOperand : genericOp.getOutputOperands()) {
732     AffineMap indexingMap = genericOp.getTiedIndexingMap(opOperand);
733     RankedTensorType expandedOutputType =
734         getExpandedType(opOperand->get().getType().cast<RankedTensorType>(),
735                         indexingMap, expansionInfo);
736     if (expandedOutputType != opOperand->get().getType()) {
737       SmallVector<ReassociationIndices> reassociation =
738           getReassociationForExpansion(indexingMap, expansionInfo);
739       outputs.push_back(rewriter.create<TensorExpandShapeOp>(
740           genericOp.getLoc(), expandedOutputType, opOperand->get(),
741           reassociation));
742     }
743   }
744 
745   // The iterator types of the expanded op are all parallel.
746   SmallVector<StringRef> iteratorTypes(expansionInfo.getExpandedOpNumDims(),
747                                        getParallelIteratorTypeName());
748 
749   TypeRange resultTypes = ValueRange(outputs).getTypes();
750   auto fusedOp =
751       rewriter.create<GenericOp>(genericOp.getLoc(), resultTypes,
752                                  /*inputs=*/expandedOpOperands, outputs,
753                                  expandedOpIndexingMaps, iteratorTypes);
754   Region &fusedRegion = fusedOp->getRegion(0);
755   Region &originalRegion = genericOp->getRegion(0);
756   rewriter.cloneRegionBefore(originalRegion, fusedRegion, fusedRegion.begin());
757 
758   // Update the index accesses after the expansion.
759   updateExpandedGenericOpRegion(rewriter, loc, fusedRegion, expansionInfo);
760 
761   // Reshape the result values to their original shape if this is a collapsing
762   // reshape folded into its consumer.
763   SmallVector<Value> resultVals;
764   for (OpResult opResult : genericOp->getOpResults()) {
765     int64_t resultNumber = opResult.getResultNumber();
766     if (!isExpanding && resultTypes[resultNumber] != opResult.getType()) {
767       SmallVector<ReassociationIndices> reassociation =
768           getReassociationForExpansion(
769               genericOp.getTiedIndexingMap(
770                   genericOp.getOutputOperand(resultNumber)),
771               expansionInfo);
772       resultVals.push_back(rewriter.create<TensorCollapseShapeOp>(
773           genericOp.getLoc(), opResult.getType(),
774           fusedOp->getResult(resultNumber), reassociation));
775     } else {
776       resultVals.push_back(fusedOp->getResult(resultNumber));
777     }
778   }
779   // Assuming a single result.
780   return resultVals;
781 }
782 
783 namespace {
784 
785 /// Pattern to fold tensor_expand_shape op with its consumer by using the source
786 /// of the reshape op as the operand in the consumer (instead of the result of
787 /// the tensor_collapse_shape). The corresponding index map in the consumer
788 /// needs to be modified to linearize the folded dimension.
789 ///
790 /// For example,
791 ///
792 /// #map0 = affine_map<(d0, d1, d2, d3) -> (d0, d1, d2, d3)>
793 /// %0 = linalg.tensor_expand_shape %arg0 [[0], [1, 2], [3]]
794 ///      tensor<?x?x?xf32> into tensor<?x?x4x?xf32>
795 /// %1 = linalg.generic { indexing_maps = [#map0, #map0, #map0], ... }
796 ///        ins(%0, %arg1 : tensor<?x?x4x?xf32>, tensor<?x?x4x?xf32>) ...
797 ///        -> tensor<?x?x4x?xf32>
798 ///
799 /// can be folded into
800 ///
801 /// #map0 = affine_map<(d0, d1, d2, d3) -> (d0, d1 * 4 + d2, d3)>
802 /// #map1 = affine_map<(d0, d1, d2, d3) -> (d0, d1, d2, d3)>
803 /// %0 = linalg.generic { indexing_maps = [#map0, #map1, #map1] ... }
804 ///        ins(%arg0, %arg1 : tensor<?x?x?xf32>, tensor<?x?x4x?xf32>) ...
805 ///        -> tensor<?x?x4x?xf32>
806 template <bool foldUnitDimReshapesOnly, typename TensorReshapeOp>
807 struct FoldProducerReshapeOpByLinearization
808     : public OpRewritePattern<GenericOp> {
809   using OpRewritePattern<GenericOp>::OpRewritePattern;
810 
811   LogicalResult matchAndRewrite(GenericOp genericOp,
812                                 PatternRewriter &rewriter) const override {
813     if (!genericOp.hasTensorSemantics())
814       return failure();
815     SmallVector<OpOperand *> inputOperands = genericOp.getInputOperands();
816     for (auto en : llvm::enumerate(inputOperands)) {
817       auto reshapeOp = en.value()->get().getDefiningOp<TensorReshapeOp>();
818       if (!reshapeOp)
819         continue;
820 
821       RankedTensorType returnType = reshapeOp.getResultType();
822 
823       if (!isTensorReshapeOpFoldableByLinearization(
824               reshapeOp, genericOp.getTiedIndexingMap(en.value()),
825               /*asProducer =*/true) ||
826           (foldUnitDimReshapesOnly &&
827            !isUnitDimExpansionOnly(returnType.getShape(),
828                                    reshapeOp.getReassociationMaps())))
829         continue;
830 
831       // Compute the fused operands list,
832       SmallVector<Value> fusedOperands = genericOp.getInputOperands();
833       fusedOperands[en.index()] = reshapeOp.src();
834       SmallVector<Value> outputOperands = genericOp.getOutputOperands();
835       llvm::append_range(fusedOperands, outputOperands);
836 
837       // Compute indexing_maps for the fused operation. The indexing_maps for
838       // the operands of the consumers that arent fused are the same.
839       SmallVector<AffineMap> fusedIndexMaps = genericOp.getIndexingMaps();
840 
841       // Accepted consumer maps are either identity or permutation.
842       auto invMap = inversePermutation(fusedIndexMaps[en.index()]);
843 
844       // Compute the indexing map to use for the result of the producer.
845       AffineMap modifiedMap = linearizeCollapsedDims(
846           invMap, returnType.getShape(), reshapeOp.getReassociationMaps());
847       for (AffineExpr expr : modifiedMap.getResults()) {
848         if (!expr.isPureAffine())
849           return failure();
850       }
851       fusedIndexMaps[en.index()] = modifiedMap;
852 
853       // Further check that the resulting index maps can be fused and
854       // inverted. Without this the resultant op is not legal.
855       if (!inversePermutation(concatAffineMaps(fusedIndexMaps))) {
856         return rewriter.notifyMatchFailure(
857             genericOp, "fused op loop bound computation failed");
858       }
859 
860       rewriter.startRootUpdate(genericOp);
861       genericOp->setOperands(fusedOperands);
862       genericOp.indexing_mapsAttr(
863           rewriter.getAffineMapArrayAttr(fusedIndexMaps));
864       rewriter.finalizeRootUpdate(genericOp);
865       return success();
866     }
867     return failure();
868   }
869 };
870 
871 static SmallVector<ReassociationIndices>
872 getReassociationIndices(ArrayRef<AffineMap> maps) {
873   SmallVector<ReassociationIndices> reassociation;
874   for (AffineMap map : maps) {
875     ReassociationIndices indices;
876     for (unsigned i = 0, e = map.getNumResults(); i < e; i++) {
877       unsigned pos = map.getResult(i).cast<AffineDimExpr>().getPosition();
878       indices.push_back(pos);
879     }
880     reassociation.push_back(indices);
881   }
882   return reassociation;
883 }
884 
885 /// Pattern to move rank reducing reshape after an elementwise linalg generic
886 /// op. This is useful to expose more fusion opportunities between named ops and
887 /// generic ops. This can only be done if there is no broadcast or permuation
888 /// within the dimensions we need to merge.
889 ///
890 /// For example,
891 ///
892 ///  %0 = linalg.tensor_expand_shape %A [[0, 1], [2]]
893 ///      : tensor<12544x16xf32> into tensor<112x112x16xf32>
894 ///  %2 = linalg.generic {indexing_maps = [
895 ///    affine_map<(d0, d1, d2) -> (d0, d1, d2)>,
896 ///    affine_map<(d0, d1, d2) -> (d2)>,
897 ///    affine_map<(d0, d1, d2) -> (d0, d1, d2)>], iterator_types =
898 ///    ["parallel", "parallel", "parallel"]} {
899 ///  } -> tensor<112x112x16xf32>
900 ///
901 ///  into
902 ///
903 ///  %2 = linalg.generic {indexing_maps = [
904 ///    affine_map<(d0, d1) -> (d0, d1)>,
905 ///    affine_map<(d0, d1) -> (d1)>,
906 ///    affine_map<(d0, d1) -> (d0, d1)>],
907 ///    iterator_types = ["parallel", "parallel"]} ins(%arg0, %arg1
908 ///    : tensor<12544x16xf32>, tensor<16xf32>) outs(%1 : tensor<12544x16xf32>) {
909 ///  } -> tensor<12544x16xf32>
910 ///  %3 = linalg.tensor_expand_shape %2 [[0, 1], [2]]
911 ///    : tensor<12544x16xf32> into tensor<112x112x16xf32>
912 struct PushExpandingReshape : public OpRewritePattern<GenericOp> {
913   using OpRewritePattern<GenericOp>::OpRewritePattern;
914 
915   LogicalResult matchAndRewrite(GenericOp genericOp,
916                                 PatternRewriter &rewriter) const override {
917     // Only apply to elementwise linalg on tensor.
918     if (!genericOp.hasTensorSemantics() ||
919         genericOp.getNumParallelLoops() != genericOp.getNumLoops())
920       return failure();
921     // Only support identity output maps. It could be extended to permuations if
922     // needed.
923     if (llvm::any_of(genericOp.getOutputOperands(), [&](OpOperand *opOperand) {
924           return !genericOp.getTiedIndexingMap(opOperand).isIdentity();
925         }))
926       return failure();
927     int64_t destRank = genericOp.getNumParallelLoops();
928     SmallVector<Value> newOperands = genericOp.getInputOperands();
929     TensorExpandShapeOp reshapeFound;
930     // 1. Look for tensor_expand_shape operands and figure out save the
931     // dimensions merged.
932     SmallVector<OpOperand *> inputOperands = genericOp.getInputOperands();
933     for (auto en : llvm::enumerate(inputOperands)) {
934       auto reshapeOp =
935           en.value()->get().template getDefiningOp<TensorExpandShapeOp>();
936       if (!reshapeOp)
937         continue;
938       // TODO: We could support non-identity map as long as the merged
939       // dimensions are still contiguous.
940       if (!genericOp.getTiedIndexingMap(en.value()).isIdentity())
941         continue;
942       if (reshapeFound) {
943         // Only support a second reshape op if it has the same reassociate maps.
944         if (reshapeFound.getReassociationMaps() ==
945             reshapeOp.getReassociationMaps())
946           newOperands[en.index()] = reshapeOp.src();
947         continue;
948       }
949       reshapeFound = reshapeOp;
950       newOperands[en.index()] = reshapeOp.src();
951     }
952     if (!reshapeFound)
953       return failure();
954 
955     // Calculate the reassociation indices and rassociated reverse map.
956     SmallVector<ReassociationIndices> reassociation =
957         getReassociationIndices(reshapeFound.getReassociationMaps());
958     SmallVector<unsigned> remap(destRank);
959     for (auto &indices : llvm::enumerate(reassociation)) {
960       for (int64_t index : indices.value()) {
961         remap[index] = indices.index();
962       }
963     }
964     // 2. Verify that we can merge the dimensions in the linalg and that we
965     // don't need to create new reshapes operands. Inserting new reshape
966     // operands would defeat the purpose of the transformation.
967     for (auto en : llvm::enumerate(inputOperands)) {
968       if (en.value()->get() == newOperands[en.index()]) {
969         AffineMap map = genericOp.getTiedIndexingMap(en.value());
970         for (unsigned i : llvm::seq(unsigned(0), map.getNumResults())) {
971           if (reassociation[remap[map.getDimPosition(i)]].size() > 1)
972             return failure();
973         }
974       }
975     }
976 
977     // 3. Calculate the affine map remapping and the reassociation to apply to
978     // output tensors.
979     SmallVector<AffineMap> newMaps;
980     unsigned newRank = reassociation.size();
981     for (auto map : genericOp.getIndexingMaps()) {
982       SmallVector<AffineExpr> newExprs;
983       for (auto expr : map.getResults()) {
984         unsigned position = expr.template cast<AffineDimExpr>().getPosition();
985         // Skip dimension merged except for the last of the group.
986         if (reassociation[remap[position]].back() == position) {
987           newExprs.push_back(
988               getAffineDimExpr(remap[position], genericOp.getContext()));
989         }
990       }
991       newMaps.push_back(
992           AffineMap::get(newRank, 0, newExprs, genericOp.getContext()));
993     }
994 
995     // 4. Reshape the output tensors.
996     SmallVector<Value> newOutputs;
997     SmallVector<Type> newOutputTypes;
998     for (auto output : genericOp.outputs()) {
999       auto newOutputType = RankedTensorType::get(
1000           reshapeFound.getSrcType().getShape(),
1001           output.getType().template cast<RankedTensorType>().getElementType());
1002       Value newOutput = rewriter.create<TensorCollapseShapeOp>(
1003           genericOp->getLoc(), newOutputType, output, reassociation);
1004       newOutputTypes.push_back(newOutputType);
1005       newOutputs.push_back(newOutput);
1006     }
1007     // 5. Create a new generic op with lowerer rank.
1008     SmallVector<StringRef> iteratorTypes(newRank,
1009                                          getParallelIteratorTypeName());
1010     auto newOp = rewriter.create<GenericOp>(genericOp->getLoc(), newOutputTypes,
1011                                             newOperands, newOutputs, newMaps,
1012                                             iteratorTypes);
1013     rewriter.inlineRegionBefore(genericOp.region(), newOp.region(),
1014                                 newOp.region().begin());
1015     // 6. Reshape the so that the type matches the uses.
1016     SmallVector<Value> newResults;
1017     for (auto result : llvm::enumerate(newOp->getResults())) {
1018       newResults.push_back(rewriter.create<TensorExpandShapeOp>(
1019           genericOp->getLoc(), genericOp.getOutputTensorTypes()[result.index()],
1020           result.value(), reassociation));
1021     }
1022     rewriter.replaceOp(genericOp, newResults);
1023     return success();
1024   }
1025 };
1026 
1027 /// Pattern to fuse a tensor_collapse_shape op with its consumer generic op,
1028 /// when the reshape op is collapsing dimensions. The dimensionality of the loop
1029 /// in the consumer is expanded.
1030 class FoldWithProducerReshapeOpByExpansion
1031     : public OpRewritePattern<GenericOp> {
1032 public:
1033   FoldWithProducerReshapeOpByExpansion(
1034       MLIRContext *context, ControlElementwiseOpsFusionFn foldReshapes,
1035       PatternBenefit benefit = 1)
1036       : OpRewritePattern<GenericOp>(context, benefit),
1037         controlFoldingReshapes(foldReshapes) {}
1038 
1039   LogicalResult matchAndRewrite(GenericOp genericOp,
1040                                 PatternRewriter &rewriter) const override {
1041     for (OpOperand *opOperand : genericOp.getInputTensorOperands()) {
1042       TensorCollapseShapeOp reshapeOp =
1043           opOperand->get().getDefiningOp<TensorCollapseShapeOp>();
1044       if (!reshapeOp)
1045         continue;
1046       // Fold only if
1047       // - The tensor reshape op is folding.
1048       // - All constraints of fusing with reshape by expansion are met.
1049       if (!isFusableWithReshapeByDimExpansion(genericOp, opOperand) ||
1050           (!controlFoldingReshapes(reshapeOp->getResult(0), *opOperand)))
1051         continue;
1052 
1053       Optional<SmallVector<Value>> replacementValues =
1054           fuseWithReshapeByExpansion(genericOp, reshapeOp, opOperand, rewriter);
1055       if (!replacementValues)
1056         return failure();
1057       rewriter.replaceOp(genericOp, replacementValues.getValue());
1058       return success();
1059     }
1060     return failure();
1061   }
1062 
1063 private:
1064   ControlElementwiseOpsFusionFn controlFoldingReshapes;
1065 };
1066 
1067 /// Pattern to fold tensor_collapse_shape or tensor_expand_shape op with its
1068 /// producer. The corresponding index map in the consumer needs to be modified
1069 /// to linearize the folded dimension.
1070 template <bool foldUnitDimReshapesOnly, typename TensorReshapeOp>
1071 struct FoldConsumerReshapeOpByLinearization
1072     : public OpRewritePattern<TensorReshapeOp> {
1073   using OpRewritePattern<TensorReshapeOp>::OpRewritePattern;
1074 
1075   LogicalResult matchAndRewrite(TensorReshapeOp reshapeOp,
1076                                 PatternRewriter &rewriter) const override {
1077     GenericOp producer = reshapeOp.src().template getDefiningOp<GenericOp>();
1078     if (!producer || !producer.hasTensorSemantics() ||
1079         producer.getNumOutputs() != 1 ||
1080         !isTensorReshapeOpFoldableByLinearization(
1081             reshapeOp,
1082             producer.getTiedIndexingMap(producer.getOutputOperand(0)),
1083             /*asProducer =*/false) ||
1084         (foldUnitDimReshapesOnly &&
1085          !isUnitDimExpansionOnly(reshapeOp.getSrcType().getShape(),
1086                                  reshapeOp.getReassociationMaps())))
1087       return failure();
1088     // The indexing_maps for the operands of the fused operation are same as
1089     // those for the operands of the producer.
1090     SmallVector<AffineMap> fusedIndexMaps = producer.getIndexingMaps();
1091 
1092     auto invMap = inversePermutation(
1093         producer.getTiedIndexingMap(producer.getOutputOperand(0)));
1094 
1095     // Compute the indexing map to use for the operand of the producer.
1096     AffineMap modifiedMap =
1097         linearizeCollapsedDims(invMap, reshapeOp.getSrcType().getShape(),
1098                                reshapeOp.getReassociationMaps());
1099     for (AffineExpr expr : modifiedMap.getResults()) {
1100       if (!expr.isPureAffine()) {
1101         return rewriter.notifyMatchFailure(
1102             producer, "fused op indexing map is not affine");
1103       }
1104     }
1105     fusedIndexMaps.back() = modifiedMap;
1106 
1107     // Further check that the resulting index maps can be fused and
1108     // inverted. Without this the resultant op is not legal.
1109     if (!inversePermutation(concatAffineMaps(fusedIndexMaps))) {
1110       return rewriter.notifyMatchFailure(
1111           producer, "fused op loop bound computation failed");
1112     }
1113 
1114     Location loc = producer.getLoc();
1115     SmallVector<Value> inputOperands = producer.getInputOperands();
1116     Value output = rewriter.create<TensorReshapeOp>(
1117         loc, producer.getOutputOperand(0)->get(),
1118         reshapeOp.getReassociationExprs());
1119     auto fusedOp = rewriter.create<GenericOp>(
1120         loc, reshapeOp.getResultType(),
1121         /*inputs=*/inputOperands,
1122         // TODO: handle outputs.
1123         /*outputs=*/output, rewriter.getAffineMapArrayAttr(fusedIndexMaps),
1124         producer.iterator_types(),
1125         /*doc=*/nullptr,
1126         /*library_call=*/nullptr);
1127     auto &fusedRegion = fusedOp->getRegion(0);
1128     rewriter.cloneRegionBefore(producer->getRegion(0), fusedRegion,
1129                                fusedRegion.begin());
1130     rewriter.replaceOp(reshapeOp, fusedOp->getResults());
1131     return success();
1132   }
1133 };
1134 
1135 /// Pattern to fold a tensor_expand_shape op with its producer generic op
1136 /// by expanding the dimensionality of the loop in the producer op.
1137 struct FoldReshapeWithGenericOpByExpansion
1138     : public OpRewritePattern<TensorExpandShapeOp> {
1139   using OpRewritePattern<TensorExpandShapeOp>::OpRewritePattern;
1140   LogicalResult matchAndRewrite(TensorExpandShapeOp reshapeOp,
1141                                 PatternRewriter &rewriter) const override {
1142     // Fold only if all constraints of fusing with reshape by expansion are met.
1143     GenericOp producer = reshapeOp.src().getDefiningOp<GenericOp>();
1144     if (!producer || producer.getNumOutputs() != 1 ||
1145         !isFusableWithReshapeByDimExpansion(producer,
1146                                             producer.getOutputOperand(0)) ||
1147         isUnitDimExpansionOnly(reshapeOp.getResultType().getShape(),
1148                                reshapeOp.getReassociationMaps()))
1149       return failure();
1150     Optional<SmallVector<Value>> replacementValues = fuseWithReshapeByExpansion(
1151         producer, reshapeOp, producer.getOutputOperand(0), rewriter);
1152     if (!replacementValues)
1153       return failure();
1154     rewriter.replaceOp(reshapeOp, replacementValues.getValue());
1155     return success();
1156   }
1157 };
1158 
1159 /// Pattern to fold a generic op with a splat constant.
1160 class FoldSplatConstants : public OpRewritePattern<GenericOp> {
1161 public:
1162   FoldSplatConstants(MLIRContext *context, ControlElementwiseOpsFusionFn &fun,
1163                      PatternBenefit benefit = 1)
1164       : OpRewritePattern<GenericOp>(context, benefit), controlFn(fun) {}
1165 
1166   LogicalResult matchAndRewrite(GenericOp genericOp,
1167                                 PatternRewriter &rewriter) const override {
1168     if (!genericOp.hasTensorSemantics())
1169       return failure();
1170     for (OpOperand *opOperand : genericOp.getInputOperands()) {
1171       Operation *def = opOperand->get().getDefiningOp();
1172       DenseElementsAttr constantAttr;
1173       if (!def ||
1174           !matchPattern(def, m_Constant<DenseElementsAttr>(&constantAttr)) ||
1175           !constantAttr.isSplat() || !controlFn(def->getResult(0), *opOperand))
1176         continue;
1177 
1178       // The operands and the indexing_maps of the fused operation the same as
1179       // the operands and indexing_maps of the generic operations with the
1180       // values at the constant index dropped.
1181       SmallVector<AffineMap> fusedIndexMaps;
1182       SmallVector<Value> fusedOperands;
1183       fusedIndexMaps.reserve(genericOp.getNumInputsAndOutputs());
1184       fusedOperands.reserve(genericOp.getNumInputs());
1185       for (OpOperand *inputOperand : genericOp.getInputOperands()) {
1186         if (inputOperand == opOperand)
1187           continue;
1188         fusedIndexMaps.push_back(genericOp.getTiedIndexingMap(inputOperand));
1189         fusedOperands.push_back(inputOperand->get());
1190       }
1191       for (OpOperand *outputOperand : genericOp.getOutputOperands())
1192         fusedIndexMaps.push_back(genericOp.getTiedIndexingMap(outputOperand));
1193 
1194       // Check if the operation shapes to loops map is computable.
1195       if (!inversePermutation(concatAffineMaps(fusedIndexMaps))) {
1196         return rewriter.notifyMatchFailure(
1197             genericOp, "fused op loop bound computation failed");
1198       }
1199 
1200       // Create a constant scalar value from the splat constant.
1201       Value scalarConstant = rewriter.create<ConstantOp>(
1202           def->getLoc(), constantAttr.getSplatValue(),
1203           constantAttr.getType().getElementType());
1204 
1205       SmallVector<Value> outputOperands = genericOp.getOutputOperands();
1206       auto fusedOp = rewriter.create<GenericOp>(
1207           rewriter.getUnknownLoc(), genericOp->getResultTypes(),
1208           /*inputs=*/fusedOperands,
1209           /*outputs=*/outputOperands,
1210           rewriter.getAffineMapArrayAttr(fusedIndexMaps),
1211           genericOp.iterator_types(),
1212           /*doc=*/nullptr,
1213           /*library_call=*/nullptr);
1214 
1215       // Map the block argument corresponding to the replaced argument with the
1216       // scalar constant.
1217       Region &region = genericOp->getRegion(0);
1218       Block &entryBlock = *region.begin();
1219       BlockAndValueMapping mapping;
1220       mapping.map(entryBlock.getArgument(opOperand->getOperandNumber()),
1221                   scalarConstant);
1222       Region &fusedRegion = fusedOp->getRegion(0);
1223       rewriter.cloneRegionBefore(region, fusedRegion, fusedRegion.begin(),
1224                                  mapping);
1225       rewriter.replaceOp(genericOp, fusedOp->getResults());
1226       return success();
1227     }
1228     return failure();
1229   }
1230 
1231 private:
1232   ControlElementwiseOpsFusionFn controlFn;
1233 };
1234 } // namespace
1235 
1236 static Optional<SmallVector<Value>>
1237 fuseElementwiseOps(PatternRewriter &rewriter, OpOperand *consumerOpOperand,
1238                    GenericOp producer,
1239                    const ControlElementwiseOpsFusionFn &controlFn) {
1240   if (producer->getNumResults() != 1)
1241     return llvm::None;
1242 
1243   return fuseElementwiseOpsImpl(producer, consumerOpOperand, controlFn,
1244                                 rewriter);
1245 }
1246 
1247 bool mlir::linalg::skipUnitDimReshape(const OpResult &producer,
1248                                       const OpOperand &consumer) {
1249   auto expandShapeOp = producer.getDefiningOp<linalg::TensorExpandShapeOp>();
1250   if (expandShapeOp)
1251     return !isUnitDimExpansionOnly(expandShapeOp.getSrcType().getShape(),
1252                                    expandShapeOp.getReassociationMaps());
1253   auto collapseShapeOp =
1254       producer.getDefiningOp<linalg::TensorCollapseShapeOp>();
1255   return !isUnitDimExpansionOnly(collapseShapeOp.getSrcType().getShape(),
1256                                  collapseShapeOp.getReassociationMaps());
1257 }
1258 
1259 namespace {
1260 /// Patterns to fuse a generic op, with the producer of its operands.
1261 class FuseElementwiseOps : public OpRewritePattern<GenericOp> {
1262 public:
1263   FuseElementwiseOps(MLIRContext *context, ControlElementwiseOpsFusionFn &fun,
1264                      PatternBenefit benefit = 1)
1265       : OpRewritePattern<GenericOp>(context, benefit), controlFn(fun) {}
1266 
1267   LogicalResult matchAndRewrite(GenericOp genericOp,
1268                                 PatternRewriter &rewriter) const override {
1269     // Find the first operand that is defined by another generic op on tensors.
1270     for (OpOperand *opOperand : genericOp.getInputAndOutputOperands()) {
1271       auto producer =
1272           dyn_cast_or_null<GenericOp>(opOperand->get().getDefiningOp());
1273       if (!producer || !producer.hasTensorSemantics())
1274         continue;
1275       Optional<SmallVector<Value>> fusedOpResults =
1276           fuseElementwiseOps(rewriter, opOperand, producer, controlFn);
1277       if (fusedOpResults) {
1278         rewriter.replaceOp(genericOp, *fusedOpResults);
1279         return success();
1280       }
1281     }
1282     return failure();
1283   }
1284 
1285 private:
1286   ControlElementwiseOpsFusionFn controlFn;
1287 };
1288 
1289 /// Pass that fuses generic ops on tensors. Used only for testing.
1290 struct FusionOfTensorOpsPass
1291     : public LinalgFusionOfTensorOpsBase<FusionOfTensorOpsPass> {
1292   void runOnOperation() override {
1293     Operation *op = getOperation();
1294     RewritePatternSet patterns(op->getContext());
1295     ControlElementwiseOpsFusionFn allowFoldingFn =
1296         [](const OpResult &producer, const OpOperand &consumer) {
1297           return true;
1298         };
1299     populateElementwiseOpsFusionPatterns(
1300         patterns,
1301         LinalgElementwiseFusionOptions().setControlFoldingReshapes(
1302             allowFoldingUnitDimReshapes ? allowFoldingFn : skipUnitDimReshape));
1303     (void)applyPatternsAndFoldGreedily(op->getRegions(), std::move(patterns));
1304   }
1305 };
1306 
1307 /// Pass to test folding of reshape ops with generic ops by linearization.
1308 struct FoldReshapeOpsByLinearizationPass
1309     : public LinalgFoldReshapeOpsByLinearizationBase<
1310           FoldReshapeOpsByLinearizationPass> {
1311   void runOnOperation() override {
1312     Operation *op = getOperation();
1313     RewritePatternSet patterns(op->getContext());
1314     populateFoldReshapeOpsByLinearizationPatterns(patterns);
1315     (void)applyPatternsAndFoldGreedily(op->getRegions(), std::move(patterns));
1316   }
1317 };
1318 
1319 /// Forces `outs` operands of linalg operations to use `linalg.init_tensor` if
1320 /// the value of the `outs` operand is not used within the op.  This is only
1321 /// implemented for `linalg.generic` operations for now, but should hold for all
1322 /// linalg structured ops.
1323 struct RemoveOutsDependency : public OpRewritePattern<GenericOp> {
1324   using OpRewritePattern<GenericOp>::OpRewritePattern;
1325 
1326   LogicalResult matchAndRewrite(GenericOp op,
1327                                 PatternRewriter &rewriter) const override {
1328     rewriter.startRootUpdate(op);
1329     bool modifiedOutput = false;
1330     Location loc = op.getLoc();
1331     for (OpOperand *opOperand : op.getOutputOperands()) {
1332       if (!op.payloadUsesValueFromOperand(opOperand)) {
1333         Value operandVal = opOperand->get();
1334         auto operandType = operandVal.getType().dyn_cast<RankedTensorType>();
1335         if (!operandType)
1336           continue;
1337 
1338         // If outs is already an `init_tensor` operation, nothing to do.
1339         auto definingOp = operandVal.getDefiningOp<InitTensorOp>();
1340         if (definingOp)
1341           continue;
1342         modifiedOutput = true;
1343         SmallVector<Value> dynamicDims;
1344         for (auto dim : llvm::enumerate(operandType.getShape())) {
1345           if (dim.value() != ShapedType::kDynamicSize)
1346             continue;
1347           dynamicDims.push_back(rewriter.createOrFold<memref::DimOp>(
1348               loc, operandVal, dim.index()));
1349         }
1350         Value initTensor = rewriter.create<InitTensorOp>(
1351             loc, dynamicDims, operandType.getShape(),
1352             operandType.getElementType());
1353         op->setOperand(opOperand->getOperandNumber(), initTensor);
1354       }
1355     }
1356     if (!modifiedOutput) {
1357       rewriter.cancelRootUpdate(op);
1358       return failure();
1359     }
1360     rewriter.finalizeRootUpdate(op);
1361     return success();
1362   }
1363 };
1364 
1365 } // namespace
1366 
1367 void mlir::linalg::populateFoldReshapeOpsByLinearizationPatterns(
1368     RewritePatternSet &patterns) {
1369   patterns
1370       .add<FoldProducerReshapeOpByLinearization<false, TensorCollapseShapeOp>,
1371            FoldProducerReshapeOpByLinearization<false, TensorExpandShapeOp>,
1372            FoldConsumerReshapeOpByLinearization<false, TensorCollapseShapeOp>,
1373            FoldConsumerReshapeOpByLinearization<false, TensorExpandShapeOp>>(
1374           patterns.getContext());
1375 }
1376 
1377 void mlir::linalg::populateFoldUnitDimsReshapeOpsByLinearizationPatterns(
1378     RewritePatternSet &patterns) {
1379   patterns
1380       .add<FoldProducerReshapeOpByLinearization<true, TensorCollapseShapeOp>,
1381            FoldProducerReshapeOpByLinearization<true, TensorExpandShapeOp>,
1382            FoldConsumerReshapeOpByLinearization<true, TensorCollapseShapeOp>,
1383            FoldConsumerReshapeOpByLinearization<true, TensorExpandShapeOp>>(
1384           patterns.getContext());
1385 }
1386 
1387 void mlir::linalg::populateFoldReshapeOpsByExpansionPatterns(
1388     RewritePatternSet &patterns,
1389     ControlElementwiseOpsFusionFn controlFoldingReshapes) {
1390   patterns.add<FoldReshapeWithGenericOpByExpansion>(patterns.getContext());
1391   patterns.add<FoldWithProducerReshapeOpByExpansion>(patterns.getContext(),
1392                                                      controlFoldingReshapes);
1393 }
1394 
1395 void mlir::linalg::populateElementwiseOpsFusionPatterns(
1396     RewritePatternSet &patterns, LinalgElementwiseFusionOptions options) {
1397   auto *context = patterns.getContext();
1398   patterns.add<FuseElementwiseOps, FoldSplatConstants>(
1399       context, options.controlElementwiseOpsFusionFn);
1400   patterns.add<RemoveOutsDependency>(context);
1401   populateFoldReshapeOpsByExpansionPatterns(patterns,
1402                                             options.controlFoldingReshapesFn);
1403   AffineApplyOp::getCanonicalizationPatterns(patterns, context);
1404   GenericOp::getCanonicalizationPatterns(patterns, context);
1405   IndexedGenericOp::getCanonicalizationPatterns(patterns, context);
1406   TensorExpandShapeOp::getCanonicalizationPatterns(patterns, context);
1407   TensorCollapseShapeOp::getCanonicalizationPatterns(patterns, context);
1408 }
1409 
1410 void mlir::linalg::populatePushReshapeOpsPatterns(RewritePatternSet &patterns) {
1411   auto *context = patterns.getContext();
1412   patterns.add<PushExpandingReshape>(context);
1413 }
1414 
1415 std::unique_ptr<Pass> mlir::createLinalgFusionOfTensorOpsPass() {
1416   return std::make_unique<FusionOfTensorOpsPass>();
1417 }
1418 
1419 std::unique_ptr<Pass> mlir::createFoldReshapeOpsByLinearizationPass() {
1420   return std::make_unique<FoldReshapeOpsByLinearizationPass>();
1421 }
1422