1 //===- ElementwiseOpFusion.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 <utility>
13 
14 #include "PassDetail.h"
15 #include "mlir/Dialect/Affine/IR/AffineOps.h"
16 #include "mlir/Dialect/Linalg/IR/Linalg.h"
17 #include "mlir/Dialect/Linalg/Passes.h"
18 #include "mlir/Dialect/Linalg/Transforms/Transforms.h"
19 #include "mlir/Dialect/Linalg/Utils/Utils.h"
20 #include "mlir/Dialect/SparseTensor/IR/SparseTensor.h"
21 #include "mlir/IR/AffineExpr.h"
22 #include "mlir/IR/AffineMap.h"
23 #include "mlir/IR/Matchers.h"
24 #include "mlir/IR/PatternMatch.h"
25 #include "mlir/Support/LLVM.h"
26 #include "mlir/Transforms/GreedyPatternRewriteDriver.h"
27 
28 using namespace mlir;
29 using namespace mlir::linalg;
30 
31 //===---------------------------------------------------------------------===//
32 // Methods and patterns that fuse elementwise `linalg.generic` operations.
33 //===---------------------------------------------------------------------===//
34 
35 /// Append to `fusedOpIndexingMapAttrs` the indexing maps for the operands of
36 /// the `producer` to use in the fused operation given the indexing map of the
37 /// result of the producer in the consumer.
38 static AffineMap getIndexingMapOfProducerOperandsInCoordinatesOfFusedOp(
39     OpOperand *producerOpOperand, AffineMap producerResultIndexMap,
40     AffineMap fusedConsumerArgIndexMap) {
41   // The indexing map in the consumer op (fusedConsumerArgIndexMap) is a map
42   // from consumer loop -> consumer arg tensor index/producer result tensor
43   // index. The fused loop is same as the consumer loop. For each producer arg
44   // the indexing map to be computed is a map from consumer loop -> producer
45   // arg tensor index.
46   // producerResultIndexMap is a map from producer loop -> tensor index.
47   // Compute the inverse to get map from tensor index -> producer loop.
48   // The inverse is a map from producer result tensor index -> producer loop.
49   AffineMap invProducerResultIndexMap =
50       inversePermutation(producerResultIndexMap);
51   assert(invProducerResultIndexMap &&
52          "expected producer result indexing map to be invertible");
53 
54   LinalgOp producer = cast<LinalgOp>(producerOpOperand->getOwner());
55   // argMap is a map from producer loop -> producer arg tensor index.
56   AffineMap argMap = producer.getTiedIndexingMap(producerOpOperand);
57 
58   // Compose argMap with invProducerResultIndexMap to get a map from
59   // producer result tensor index -> producer arg tensor index.
60   AffineMap t1 = argMap.compose(invProducerResultIndexMap);
61 
62   // Compose t1 with fusedConsumerArgIndexMap gives an indexing map from
63   // consumer loop/ fused loop -> producer arg tensor index.
64   return t1.compose(fusedConsumerArgIndexMap);
65 }
66 
67 /// Conditions for elementwise fusion of generic operations.
68 static bool areElementwiseOpsFusable(GenericOp producer, GenericOp consumer,
69                                      OpOperand *consumerOpOperand) {
70   // Producer and consumer must have tensor semantics.
71   if (!producer.hasTensorSemantics() || !consumer.hasTensorSemantics())
72     return false;
73 
74   // Verify that
75   // - the producer has all "parallel" iterator type.
76   if (producer.getNumParallelLoops() != producer.getNumLoops())
77     return false;
78 
79   // Only allow fusing the producer of an input operand for now.
80   // TODO: allow fusing the producer of an output operand.
81   if (!consumer.isInputTensor(consumerOpOperand))
82     return false;
83 
84   // Get the consumer index map. The number of results of the consumer index
85   // map must match the number of loops of the producer.
86   AffineMap consumerIndexMap = consumer.getTiedIndexingMap(consumerOpOperand);
87   if (consumerIndexMap.getNumResults() != producer.getNumLoops())
88     return false;
89 
90   // Currently support only operations with single result.
91   if (producer.getNumOutputs() != 1)
92     return false;
93 
94   // Finally the index_map for the result must be invertible. For now just
95   // verify it is a permutation.
96   AffineMap producerResultIndexMap =
97       producer.getTiedIndexingMap(producer.getOutputOperand(0));
98   if (!producerResultIndexMap.isPermutation())
99     return false;
100 
101   // Ensure that the fusion does not remove size information required to
102   // get the loop bounds. For non-reduction generics, this is trivially the
103   // case due to the output operand. For reductions, we need to check that after
104   // the fusion, each loop dimension has at least one input that defines it.
105   if ((consumer.getNumReductionLoops())) {
106     BitVector coveredDims(consumer.getNumLoops(), false);
107 
108     auto addToCoveredDims = [&](AffineMap map) {
109       for (auto result : map.getResults())
110         if (auto dimExpr = result.dyn_cast<AffineDimExpr>())
111           coveredDims[dimExpr.getPosition()] = true;
112     };
113 
114     for (auto pair :
115          llvm::zip(consumer->getOperands(), consumer.getIndexingMaps())) {
116       Value operand = std::get<0>(pair);
117       if (operand == consumerOpOperand->get())
118         continue;
119       AffineMap operandMap = std::get<1>(pair);
120       addToCoveredDims(operandMap);
121     }
122 
123     for (OpOperand *operand : producer.getInputOperands()) {
124       AffineMap newIndexingMap =
125           getIndexingMapOfProducerOperandsInCoordinatesOfFusedOp(
126               operand, producerResultIndexMap, consumerIndexMap);
127       addToCoveredDims(newIndexingMap);
128     }
129     if (!coveredDims.all())
130       return false;
131   }
132 
133   return true;
134 }
135 
136 /// Generate the region of the fused tensor operation. The region of the fused
137 /// op must be empty.
138 static void
139 generateFusedElementwiseOpRegion(PatternRewriter &rewriter, GenericOp fusedOp,
140                                  AffineMap consumerToProducerLoopsMap,
141                                  OpOperand *consumerOpOperand,
142                                  unsigned nloops) {
143   auto producer = cast<GenericOp>(consumerOpOperand->get().getDefiningOp());
144   auto consumer = cast<GenericOp>(consumerOpOperand->getOwner());
145   // Build the region of the fused op.
146   Block &producerBlock = producer->getRegion(0).front();
147   Block &consumerBlock = consumer->getRegion(0).front();
148   Block *fusedBlock = new Block();
149   fusedOp.region().push_back(fusedBlock);
150   BlockAndValueMapping mapper;
151   OpBuilder::InsertionGuard guard(rewriter);
152   rewriter.setInsertionPointToStart(fusedBlock);
153 
154   // 2. Add an index operation for every fused loop dimension and use the
155   // `consumerToProducerLoopsMap` to map the producer indices.
156   if (producer.hasIndexSemantics()) {
157     // Add an index operation for every fused loop dimension.
158     unsigned numFusedOpLoops =
159         std::max(producer.getNumLoops(), consumer.getNumLoops());
160     SmallVector<Value> fusedIndices;
161     fusedIndices.reserve(numFusedOpLoops);
162     llvm::transform(llvm::seq<uint64_t>(0, numFusedOpLoops),
163                     std::back_inserter(fusedIndices), [&](uint64_t dim) {
164                       return rewriter.create<IndexOp>(producer.getLoc(), dim);
165                     });
166     for (IndexOp indexOp :
167          llvm::make_early_inc_range(producerBlock.getOps<IndexOp>())) {
168       Value newIndex = rewriter.create<mlir::AffineApplyOp>(
169           producer.getLoc(),
170           consumerToProducerLoopsMap.getSubMap(indexOp.dim()), fusedIndices);
171       mapper.map(indexOp.getResult(), newIndex);
172     }
173   }
174   // TODO: allow fusing the producer of an output operand.
175   assert(consumer.isInputTensor(consumerOpOperand) &&
176          "expected producer of input operand");
177   // 3. Consumer input operands up to consumerIdx (exclusive).
178   for (BlockArgument bbArg : consumerBlock.getArguments().take_front(
179            consumerOpOperand->getOperandNumber())) // input assumption.
180     mapper.map(bbArg, fusedBlock->addArgument(bbArg.getType(), bbArg.getLoc()));
181 
182   // Replacing consumerIdx requires getting the cloned, yielded, value from
183   // the (cloned) producer block. This happens in step 9.
184 
185   // 4. Splice in producer's input operands.
186   for (BlockArgument bbArg :
187        producerBlock.getArguments().take_front(producer.getNumInputs()))
188     mapper.map(bbArg, fusedBlock->addArgument(bbArg.getType(), bbArg.getLoc()));
189 
190   // 4.b. Producer output operand/map that is fused needs to be mapped to the
191   // producer bbArg if it is an "initTensor" (i.e. its value is actually read).
192   assert(producer->getNumResults() == 1 && "expected single result producer");
193   if (producer.isInitTensor(producer.getOutputOperand(0))) {
194     BlockArgument bbArg = producerBlock.getArguments()
195                               .drop_front(producer.getNumInputs())
196                               // TODO: bbArg index of
197                               .front();
198     mapper.map(bbArg, fusedBlock->addArgument(bbArg.getType(), bbArg.getLoc()));
199   }
200   // 5. Remaining consumer's input operands (drop past index `consumerIdx`).
201   for (BlockArgument bbArg :
202        consumerBlock.getArguments()
203            .take_front(consumer.getNumInputs())
204            .drop_front(consumerOpOperand->getOperandNumber() + 1))
205     mapper.map(bbArg, fusedBlock->addArgument(bbArg.getType(), bbArg.getLoc()));
206   // 6. All of consumer's output operands.
207   for (BlockArgument bbArg :
208        consumerBlock.getArguments().take_back(consumer.getNumOutputs()))
209     mapper.map(bbArg, fusedBlock->addArgument(bbArg.getType(), bbArg.getLoc()));
210   // 7. All of producer's output operands except the one fused.
211   // TODO: allow fusion of multi-result producers.
212   assert(producer->getNumResults() == 1 && "expected single result producer");
213 
214   // 8. Clone all producer operations except for the yield and index operations
215   // to the fused operation.
216   for (auto &op : producerBlock.without_terminator()) {
217     if (!isa<IndexOp>(op))
218       rewriter.clone(op, mapper);
219   }
220   // 9. Now we can map the consumerBlock's `consumerIdx` block argument. Just
221   // forward the yield operand.
222   auto yieldOp = cast<linalg::YieldOp>(producerBlock.getTerminator());
223   // TODO: allow fusion of multi-result producers.
224   assert(producer->getNumResults() == 1 && "expected single result producer");
225   unsigned producerResultNumber = 0;
226   Value replacement =
227       mapper.lookupOrDefault(yieldOp.getOperand(producerResultNumber));
228   // Sanity checks, if replacement is not already in the mapper then it must be
229   // produced outside.
230   if (replacement == yieldOp.getOperand(producerResultNumber)) {
231     if (auto bb = replacement.dyn_cast<BlockArgument>())
232       assert(bb.getOwner() != &producerBlock &&
233              "yielded block argument must have been mapped");
234     else
235       assert(!producer->isAncestor(replacement.getDefiningOp()) &&
236              "yielded value must have been mapped");
237   }
238   mapper.map(consumerBlock.getArgument(consumerOpOperand->getOperandNumber()),
239              replacement);
240   // 10. Clone operations from the consumer to the fused op.
241   for (auto &op : consumerBlock.getOperations())
242     rewriter.clone(op, mapper);
243 
244   // Sanity checks.
245   assert(fusedBlock->getNumArguments() == fusedOp.getNumOperands() &&
246          "Ill-formed GenericOp region");
247 }
248 
249 static Optional<SmallVector<Value>>
250 fuseElementwiseOpsImpl(GenericOp producer, OpOperand *consumerOpOperand,
251                        const ControlFusionFn &controlFn,
252                        PatternRewriter &rewriter) {
253   auto consumer = cast<GenericOp>(consumerOpOperand->getOwner());
254   if (!areElementwiseOpsFusable(producer, consumer, consumerOpOperand) ||
255       !controlFn(producer->getResult(0), *consumerOpOperand))
256     return llvm::None;
257 
258   // TODO: allow fusing the producer of an output operand.
259   assert(consumer.isInputTensor(consumerOpOperand) &&
260          "expected producer of input operand");
261 
262   // Compute the fused operands list and indexing maps.
263   SmallVector<Value> fusedOperands;
264   SmallVector<AffineMap> fusedIndexMaps;
265   fusedOperands.reserve(producer->getNumOperands() +
266                         consumer->getNumOperands());
267   fusedIndexMaps.reserve(producer->getNumOperands() +
268                          consumer->getNumOperands());
269   // In the following, numbering matches that of `generateFusedTensorOpRegion`.
270   // 3. Consumer input operands/maps up to consumerIdx (exclusive).
271   SmallVector<OpOperand *> consumerInputs = consumer.getInputOperands();
272   SmallVector<OpOperand *>::iterator it =
273       llvm::find(consumerInputs, consumerOpOperand);
274   assert(it != consumerInputs.end() && "expected to find the consumer operand");
275   for (OpOperand *opOperand : llvm::make_range(consumerInputs.begin(), it)) {
276     fusedOperands.push_back(opOperand->get());
277     fusedIndexMaps.push_back(consumer.getTiedIndexingMap(opOperand));
278   }
279   // 4. Splice in producer's input operands/maps.
280   assert(producer->getNumResults() == 1 && "expected single result producer");
281   AffineMap producerResultIndexMap =
282       producer.getTiedIndexingMap(producer.getOutputOperand(0));
283   for (OpOperand *opOperand : producer.getInputOperands()) {
284     fusedOperands.push_back(opOperand->get());
285     // Compute indexing maps for the producer args in the fused operation.
286     AffineMap map = getIndexingMapOfProducerOperandsInCoordinatesOfFusedOp(
287         opOperand, producerResultIndexMap,
288         consumer.getTiedIndexingMap(consumerOpOperand));
289     fusedIndexMaps.push_back(map);
290   }
291   // 4.b. Producer output operand/map that is fused needs to be passed if it is
292   // an "initTensor" (i.e. its value is actually read).
293   assert(producer->getNumResults() == 1 && "expected single result producer");
294   if (producer.isInitTensor(producer.getOutputOperand(0))) {
295     fusedOperands.push_back(producer.getOutputOperand(0)->get());
296     // Compute indexing maps for the producer args in the fused operation.
297     AffineMap map = getIndexingMapOfProducerOperandsInCoordinatesOfFusedOp(
298         producer.getOutputOperand(0), producerResultIndexMap,
299         consumer.getTiedIndexingMap(consumerOpOperand));
300     fusedIndexMaps.push_back(map);
301   }
302   // 5. Remaining consumer's input operands/maps (drop past index
303   // `consumerIdx`).
304   for (OpOperand *opOperand :
305        llvm::make_range(std::next(it), consumerInputs.end())) {
306     fusedOperands.push_back(opOperand->get());
307     fusedIndexMaps.push_back(consumer.getTiedIndexingMap(opOperand));
308   }
309   // 6. All of consumer's output operands (skip operands: added by the builder).
310   for (OpOperand *opOperand : consumer.getOutputOperands())
311     fusedIndexMaps.push_back(consumer.getTiedIndexingMap(opOperand));
312   // 7. All of producer's output operands/maps except the one fused.
313   // TODO: allow fusion of multi-result producers.
314   assert(producer->getNumResults() == 1 && "expected single result producer");
315 
316   // Generate the fused op.
317   SmallVector<Value> consumerOutputs = consumer.getOutputOperands();
318   auto fusedOp = rewriter.create<GenericOp>(
319       consumer.getLoc(), consumer->getResultTypes(),
320       /*inputs=*/fusedOperands,
321       // TODO: handle outputs.
322       consumerOutputs, rewriter.getAffineMapArrayAttr(fusedIndexMaps),
323       consumer.iterator_types(),
324       /*doc=*/nullptr,
325       /*library_call=*/nullptr);
326   if (!fusedOp.getShapesToLoopsMap()) {
327     // Fused op has invalid indexing maps. Typically this means something is off
328     // in the input, but going ahead here would result in verification errors.
329     // So cleanup and abort.
330     rewriter.eraseOp(fusedOp);
331     return llvm::None;
332   }
333 
334   // Construct an AffineMap from consumer loops to producer loops.
335   // consumer loop -> tensor index
336   AffineMap consumerResultIndexMap =
337       consumer.getTiedIndexingMap(consumerOpOperand);
338   // tensor index -> producer loop
339   AffineMap invProducerResultIndexMap =
340       inversePermutation(producerResultIndexMap);
341   assert(invProducerResultIndexMap &&
342          "expected producer result indexig map to be invertible");
343   // consumer loop -> producer loop
344   AffineMap consumerToProducerLoopsMap =
345       invProducerResultIndexMap.compose(consumerResultIndexMap);
346 
347   generateFusedElementwiseOpRegion(rewriter, fusedOp,
348                                    consumerToProducerLoopsMap,
349                                    consumerOpOperand, consumer.getNumLoops());
350   return SmallVector<Value>(fusedOp->getResults());
351 }
352 
353 static Optional<SmallVector<Value>>
354 fuseElementwiseOps(PatternRewriter &rewriter, OpOperand *consumerOpOperand,
355                    GenericOp producer, const ControlFusionFn &controlFn) {
356   if (producer->getNumResults() != 1)
357     return llvm::None;
358 
359   return fuseElementwiseOpsImpl(producer, consumerOpOperand, controlFn,
360                                 rewriter);
361 }
362 
363 namespace {
364 /// Patterns to fuse a generic op, with the producer of its operands.
365 class FuseElementwiseOps : public OpRewritePattern<GenericOp> {
366 public:
367   FuseElementwiseOps(MLIRContext *context, ControlFusionFn fun,
368                      PatternBenefit benefit = 1)
369       : OpRewritePattern<GenericOp>(context, benefit),
370         controlFn(std::move(fun)) {}
371 
372   LogicalResult matchAndRewrite(GenericOp genericOp,
373                                 PatternRewriter &rewriter) const override {
374     // Find the first operand that is defined by another generic op on tensors.
375     for (OpOperand *opOperand : genericOp.getInputAndOutputOperands()) {
376       auto producer =
377           dyn_cast_or_null<GenericOp>(opOperand->get().getDefiningOp());
378       if (!producer || !producer.hasTensorSemantics())
379         continue;
380       Optional<SmallVector<Value>> fusedOpResults =
381           fuseElementwiseOps(rewriter, opOperand, producer, controlFn);
382       if (fusedOpResults) {
383         rewriter.replaceOp(genericOp, *fusedOpResults);
384         return success();
385       }
386     }
387     return failure();
388   }
389 
390 private:
391   ControlFusionFn controlFn;
392 };
393 } // namespace
394 
395 //===---------------------------------------------------------------------===//
396 // Methods and patterns that fuse reshape ops with elementwise operations by
397 // linearization of indexing maps.
398 //===---------------------------------------------------------------------===//
399 
400 // TODO(ravishankarm): The indexing maps
401 // these produce in the general case are detrimental to transformations.
402 // These patterns are on deprecation path in favor of using fusion by
403 // collapsing, which covers the only legitimate use case of this pattern of
404 // folding unit-extent dims.
405 
406 /// Linearize the expressions in `sourceMap` based on the `reassociationMaps`
407 /// provided, given the shape of the source tensor that corresponds to the
408 /// `sourceMap`. Note that this implicitly assumes that the tensors dimensions
409 /// are "row-major" ordered logically.
410 ///
411 /// For example:
412 ///
413 /// %0 = op ... : tensor<?x?x4x5xf32>
414 /// with output index_map `affine_map<(d0, d1, d2, d3) -> (d0, d1, d2, d3)>`
415 ///
416 /// and reshape:
417 /// %1 = tensor.collapse_shape %0 [[0], [0, 1, 2]] :
418 ///        tensor<?x?x4x5xf32> into tensor<?x?xf32>
419 ///
420 /// would be rewritten into:
421 /// %0 = op ... : tensor<?x?x4x5xf32>
422 /// with output index_map
423 ///   `affine_map<(d0, d1, d2, d3) -> (d0, d1 * 20 + d2 * 5 + d3)>`
424 template <typename TensorReshapeOp>
425 static AffineMap linearizeCollapsedDims(AffineMap sourceMap,
426                                         TensorReshapeOp reshapeOp) {
427   constexpr bool isExpanding =
428       std::is_same<TensorReshapeOp, tensor::ExpandShapeOp>::value;
429   ArrayRef<int64_t> sourceShape =
430       (isExpanding ? reshapeOp.getResultType().getShape()
431                    : reshapeOp.getSrcType().getShape());
432   SmallVector<AffineExpr> resultExprs;
433   ArrayRef<AffineExpr> sourceExprs = sourceMap.getResults();
434   MLIRContext *context = sourceMap.getContext();
435 
436   // Compute the result exprs based on the reassociation maps.
437   for (auto &indices : reshapeOp.getReassociationIndices()) {
438     // Assume that they are in-order and contiguous (already checked in
439     // verifier).
440     assert(!indices.empty());
441     SmallVector<int64_t> sizes;
442     SmallVector<AffineExpr> dimExprs;
443     for (auto en : llvm::zip(sourceShape.slice(indices[0], indices.size()),
444                              sourceExprs.slice(indices[0], indices.size()))) {
445       if (std::get<0>(en) == 1)
446         continue;
447       sizes.push_back(std::get<0>(en));
448       dimExprs.push_back(std::get<1>(en));
449     }
450     AffineExpr linearizedExpr =
451         makeCanonicalStridedLayoutExpr(sizes, dimExprs, context);
452     resultExprs.push_back(linearizedExpr);
453   }
454   // The new affine map cannot drop unused dimension but some new symbols may
455   // have been added. Create a map with at least as many dimensions/symbols as
456   // the original affine map.
457   int64_t maxDim = -1;
458   int64_t maxSym = -1;
459   getMaxDimAndSymbol<SmallVector<AffineExpr>>({resultExprs}, maxDim, maxSym);
460   unsigned numDims = std::max(unsigned(maxDim + 1), sourceMap.getNumDims());
461   unsigned numSyms = std::max(unsigned(maxSym + 1), sourceMap.getNumSymbols());
462   return AffineMap::get(numDims, numSyms, resultExprs, context);
463 }
464 
465 // tensor::ExpandShapeOp is fusable with its consumer (i.e. reshape as a
466 // producer). Fusing when operand has higher rank will require use of mods and
467 // divs in the indexing maps of the fused op which would make it non-invertible.
468 static bool isTensorReshapeOpFoldableByLinearization(
469     tensor::ExpandShapeOp expandOp, AffineMap useIndexMap, bool asProducer) {
470   if (!asProducer)
471     return false;
472   return useIndexMap.isPermutation();
473 }
474 
475 // tensor::CollapseShapeOp is fusable with its producer (i.e. reshape as a
476 // consumer).
477 static bool
478 isTensorReshapeOpFoldableByLinearization(tensor::CollapseShapeOp collapseOp,
479                                          AffineMap useIndexMap,
480                                          bool asProducer) {
481   if (asProducer)
482     return false;
483   return useIndexMap.isPermutation();
484 }
485 
486 /// Check if the reshape operation is only expansion into/collapsing of
487 /// unit-dimension.
488 template <typename TensorReshapeOp>
489 static bool isUnitDimExpansionOnly(TensorReshapeOp reshapeOp) {
490   constexpr bool isExpanding =
491       std::is_same<TensorReshapeOp, tensor::ExpandShapeOp>::value;
492   ArrayRef<int64_t> expandedShape =
493       (isExpanding ? reshapeOp.getResultType().getShape()
494                    : reshapeOp.getSrcType().getShape());
495   for (auto &indices : reshapeOp.getReassociationIndices()) {
496     unsigned numUnitDims = 0;
497     for (int64_t position : indices)
498       if (expandedShape[position] == 1)
499         numUnitDims++;
500     if (numUnitDims != indices.size() - 1)
501       return false;
502   }
503   return true;
504 }
505 
506 namespace {
507 /// Pattern to fold tensor_expand_shape op with its consumer by using the source
508 /// of the reshape op as the operand in the consumer (instead of the result of
509 /// the tensor_collapse_shape). The corresponding index map in the consumer
510 /// needs to be modified to linearize the folded dimension.
511 ///
512 /// For example,
513 ///
514 /// #map0 = affine_map<(d0, d1, d2, d3) -> (d0, d1, d2, d3)>
515 /// %0 = tensor.expand_shape %arg0 [[0], [1, 2], [3]]
516 ///      tensor<?x?x?xf32> into tensor<?x?x4x?xf32>
517 /// %1 = linalg.generic { indexing_maps = [#map0, #map0, #map0], ... }
518 ///        ins(%0, %arg1 : tensor<?x?x4x?xf32>, tensor<?x?x4x?xf32>) ...
519 ///        -> tensor<?x?x4x?xf32>
520 ///
521 /// can be folded into
522 ///
523 /// #map0 = affine_map<(d0, d1, d2, d3) -> (d0, d1 * 4 + d2, d3)>
524 /// #map1 = affine_map<(d0, d1, d2, d3) -> (d0, d1, d2, d3)>
525 /// %0 = linalg.generic { indexing_maps = [#map0, #map1, #map1] ... }
526 ///        ins(%arg0, %arg1 : tensor<?x?x?xf32>, tensor<?x?x4x?xf32>) ...
527 ///        -> tensor<?x?x4x?xf32>
528 template <bool foldUnitDimReshapesOnly, typename TensorReshapeOp>
529 struct FoldProducerReshapeOpByLinearization
530     : public OpRewritePattern<GenericOp> {
531   using OpRewritePattern<GenericOp>::OpRewritePattern;
532 
533   LogicalResult matchAndRewrite(GenericOp genericOp,
534                                 PatternRewriter &rewriter) const override {
535     if (!genericOp.hasTensorSemantics())
536       return failure();
537     SmallVector<OpOperand *> inputOperands = genericOp.getInputOperands();
538     for (const auto &en : llvm::enumerate(inputOperands)) {
539       auto reshapeOp = en.value()->get().getDefiningOp<TensorReshapeOp>();
540       if (!reshapeOp)
541         continue;
542 
543       if (!isTensorReshapeOpFoldableByLinearization(
544               reshapeOp, genericOp.getTiedIndexingMap(en.value()),
545               /*asProducer =*/true) ||
546           (foldUnitDimReshapesOnly && !isUnitDimExpansionOnly(reshapeOp)))
547         continue;
548 
549       // Compute the fused operands list,
550       SmallVector<Value> fusedOperands = genericOp.getInputOperands();
551       fusedOperands[en.index()] = reshapeOp.src();
552       SmallVector<Value> outputOperands = genericOp.getOutputOperands();
553       llvm::append_range(fusedOperands, outputOperands);
554 
555       // Compute indexing_maps for the fused operation. The indexing_maps for
556       // the operands of the consumers that arent fused are the same.
557       SmallVector<AffineMap> fusedIndexMaps = genericOp.getIndexingMaps();
558 
559       // Compute the indexing map to use for the result of the producer.
560       AffineMap modifiedMap =
561           linearizeCollapsedDims(fusedIndexMaps[en.index()], reshapeOp);
562       // The modified map cannot have symbols.
563       if (modifiedMap.getNumSymbols())
564         return failure();
565       for (AffineExpr expr : modifiedMap.getResults()) {
566         if (!expr.isPureAffine())
567           return failure();
568       }
569       fusedIndexMaps[en.index()] = modifiedMap;
570 
571       // Further check that the resulting index maps can be fused and
572       // inverted. Without this the resultant op is not legal.
573       if (!inversePermutation(concatAffineMaps(fusedIndexMaps))) {
574         return rewriter.notifyMatchFailure(
575             genericOp, "fused op loop bound computation failed");
576       }
577 
578       rewriter.startRootUpdate(genericOp);
579       genericOp->setOperands(fusedOperands);
580       genericOp.indexing_mapsAttr(
581           rewriter.getAffineMapArrayAttr(fusedIndexMaps));
582       rewriter.finalizeRootUpdate(genericOp);
583       return success();
584     }
585     return failure();
586   }
587 };
588 
589 /// Pattern to fold tensor_collapse_shape or tensor_expand_shape op with its
590 /// producer. The corresponding index map in the consumer needs to be modified
591 /// to linearize the folded dimension.
592 template <bool foldUnitDimReshapesOnly, typename TensorReshapeOp>
593 struct FoldConsumerReshapeOpByLinearization
594     : public OpRewritePattern<TensorReshapeOp> {
595   using OpRewritePattern<TensorReshapeOp>::OpRewritePattern;
596 
597   LogicalResult matchAndRewrite(TensorReshapeOp reshapeOp,
598                                 PatternRewriter &rewriter) const override {
599     GenericOp producer = reshapeOp.src().template getDefiningOp<GenericOp>();
600     if (!producer || !producer.hasTensorSemantics() ||
601         producer.getNumOutputs() != 1 ||
602         !isTensorReshapeOpFoldableByLinearization(
603             reshapeOp,
604             producer.getTiedIndexingMap(producer.getOutputOperand(0)),
605             /*asProducer =*/false) ||
606         (foldUnitDimReshapesOnly && !isUnitDimExpansionOnly(reshapeOp)))
607       return failure();
608     // The indexing_maps for the operands of the fused operation are same as
609     // those for the operands of the producer.
610     SmallVector<AffineMap> fusedIndexMaps = producer.getIndexingMaps();
611 
612     // Compute the indexing map to use for the operand of the producer.
613     AffineMap modifiedMap = linearizeCollapsedDims(
614         producer.getTiedIndexingMap(producer.getOutputOperand(0)), reshapeOp);
615     for (AffineExpr expr : modifiedMap.getResults()) {
616       if (!expr.isPureAffine()) {
617         return rewriter.notifyMatchFailure(
618             producer, "fused op indexing map is not affine");
619       }
620     }
621     fusedIndexMaps.back() = modifiedMap;
622 
623     // Further check that the resulting index maps can be fused and
624     // inverted. Without this the resultant op is not legal.
625     if (!inversePermutation(concatAffineMaps(fusedIndexMaps))) {
626       return rewriter.notifyMatchFailure(
627           producer, "fused op loop bound computation failed");
628     }
629 
630     Location loc = producer.getLoc();
631     SmallVector<Value> inputOperands = producer.getInputOperands();
632     Value output = rewriter.create<TensorReshapeOp>(
633         loc, producer.getOutputOperand(0)->get(),
634         reshapeOp.getReassociationExprs());
635     auto fusedOp = rewriter.create<GenericOp>(
636         loc, reshapeOp.getResultType(),
637         /*inputs=*/inputOperands,
638         // TODO: handle outputs.
639         /*outputs=*/output, rewriter.getAffineMapArrayAttr(fusedIndexMaps),
640         producer.iterator_types(),
641         /*doc=*/nullptr,
642         /*library_call=*/nullptr);
643     auto &fusedRegion = fusedOp->getRegion(0);
644     rewriter.cloneRegionBefore(producer->getRegion(0), fusedRegion,
645                                fusedRegion.begin());
646     rewriter.replaceOp(reshapeOp, fusedOp->getResults());
647     return success();
648   }
649 };
650 } // namespace
651 
652 //===---------------------------------------------------------------------===//
653 // Methods and patterns that fuse reshape ops with elementwise operations by
654 // expanding the dimensionality of the elementwise operations.
655 //===---------------------------------------------------------------------===//
656 
657 /// Conditions for folding a generic operation with a reshape op by expanding
658 /// the iteration space dimensionality for tensor operations. These are
659 /// preconditions assumed by `foldReshapeByDimExpansion` which implements the
660 /// following fusion pattern.
661 ///
662 ///  Consider
663 ///
664 ///  %c = linalg.generic ins(%a, %b : memref<?x?x?xf32>, memref<?x?xf32>)
665 ///         indexing_maps = [affine_map<(d0, d1, d2) -> (d1, d0, d2)>,
666 ///                          affine_map<(d0, d1, d2) -> (d1, d2)>,
667 ///                          affine_map<(d0, d1, d2) -> (d0, d2, d1)>]
668 ///  %d = tensor.expand_shape %c [[0, 1], [2], [3, 4, 5]]
669 ///       : tensor<?x?x?xf32> into tensor<?x?x?x?x?x?xf32>
670 ///
671 ///  The reshape can be folded into the `genericOp` if its loop dimensionality
672 ///  is increased to match the result (operand) of the tensor_expand_shape.
673 ///  The indexing_map of the fused tensor in the `genericOp` and the
674 ///  reassociation map helps compute the indexing maps of the modified op.
675 ///  For the above example, based on the reassociation map it
676 ///  can be concluded that
677 ///
678 ///  - The loop used to access the first dimension of the fused tensor is split
679 ///    into two.
680 ///  - The loop used to access the second dimension of the fused tensor is kept
681 ///    as is.
682 ///  - The loop used to access the third dimension of the fused tensor is split
683 ///    into three.
684 ///
685 ///  i.e. (e0, e1, e2, e3, e4) is the domain of the indexing map of the modified
686 ///  op, then
687 ///
688 ///   d0 -> e0, e1
689 ///   d1 -> e2, e3, e4
690 ///   d2 -> e5
691 ///
692 ///  substituting this, the generic op can be rewritten as
693 ///
694 ///  %d = linalg.generic ins(%0, %1 : )
695 ///        indexing_maps =
696 ///         [affine_map<(e0, e1, e2, e3, e4, e5) -> (e2, e3, e4, e0, e1, e5)>,
697 ///          affine_map<(e0, e1, e2, e3, e4, e5) -> (e2, e3, e4, e5)>,
698 ///          affine_map<(e0, e1, e2, e3, e4, e5) -> (e0, e1, e5, e2, e3, e4)>]
699 ///
700 ///  Since operands to the linalg generic are now 5D, reshapes can be introduced
701 ///  to make it consistent
702 ///
703 ///  %0 = tensor.expand_shape %a [[0, 1, 2], [3, 4], [5]]
704 ///       : tensor<?x?x?xf32> into tensor<?x?x?x?x?x?xf32>
705 ///  %1 = tensor.expand_shape %b [[0, 1, 2], [3]]
706 ///       : tensor<?x?x?xf32> into tensor<?x?x?x?xf32>
707 ///
708 ///  The added reshapes are again expanding patterns, so they will get fused
709 ///  with its producers if possible.
710 static bool isFusableWithReshapeByDimExpansion(GenericOp genericOp,
711                                                OpOperand *fusableOpOperand) {
712   // Is fusable only if:
713   // - All the indexing maps for operands and results are projected
714   //   permutations.
715   // - The fused tensor is not a scalar.
716   // - All the loops are parallel loops.
717   return genericOp.hasTensorSemantics() &&
718          llvm::all_of(genericOp.indexing_maps().getValue(),
719                       [](Attribute attr) {
720                         return attr.cast<AffineMapAttr>()
721                             .getValue()
722                             .isProjectedPermutation();
723                       }) &&
724          genericOp.getTiedIndexingMap(fusableOpOperand).getNumResults() > 0 &&
725          llvm::all_of(genericOp.iterator_types(), [](Attribute attr) {
726            return attr.cast<StringAttr>().getValue() ==
727                   getParallelIteratorTypeName();
728          });
729 }
730 
731 namespace {
732 /// Information needed to expand a generic operation to fold the reshape with
733 /// it.
734 class ExpansionInfo {
735 public:
736   // Computes the mapping from original dimensions of the op to the dimensions
737   // of the expanded op given the `indexingMap` of the fused operand/result of
738   // the generic op, the `reassocationMaps` of the reshape op and the shape of
739   // the expanded op.
740   LogicalResult compute(LinalgOp linalgOp, OpOperand *fusableOpOperand,
741                         ArrayRef<AffineMap> reassociationMaps,
742                         ArrayRef<int64_t> expandedShape,
743                         ArrayRef<int64_t> collapsedShape,
744                         PatternRewriter &rewriter);
745   unsigned getOrigOpNumDims() const { return reassociation.size(); }
746   unsigned getExpandedOpNumDims() const { return expandedOpNumDims; }
747   ReassociationIndicesRef getExpandedDims(unsigned i) const {
748     return reassociation[i];
749   }
750   ArrayRef<int64_t> getExpandedShapeOfDim(unsigned i) const {
751     return expandedShapeMap[i];
752   }
753   ArrayRef<int64_t> getOriginalShape() const { return originalLoopExtent; }
754 
755 private:
756   /// Reassociation from the dimensions in the original operation to the
757   /// dimension of the expanded operation.
758   SmallVector<ReassociationIndices> reassociation;
759   /// Mapping from extent of loops in the original operation, to the extent of
760   /// loops in the expanded operation.
761   SmallVector<SmallVector<int64_t>> expandedShapeMap;
762   /// Extent of the loop in the original operation.
763   SmallVector<int64_t> originalLoopExtent;
764   unsigned expandedOpNumDims;
765 };
766 } // namespace
767 
768 LogicalResult ExpansionInfo::compute(LinalgOp linalgOp,
769                                      OpOperand *fusableOpOperand,
770                                      ArrayRef<AffineMap> reassociationMaps,
771                                      ArrayRef<int64_t> expandedShape,
772                                      ArrayRef<int64_t> collapsedShape,
773                                      PatternRewriter &rewriter) {
774   if (reassociationMaps.empty())
775     return failure();
776   AffineMap fusedIndexMap = linalgOp.getTiedIndexingMap(fusableOpOperand);
777 
778   Optional<SmallVector<int64_t, 4>> originalLoopRange =
779       linalgOp.getStaticLoopRanges();
780   if (!originalLoopRange)
781     return rewriter.notifyMatchFailure(linalgOp, "unable to find loop range");
782   originalLoopExtent.assign(originalLoopRange->begin(),
783                             originalLoopRange->end());
784 
785   reassociation.clear();
786   expandedShapeMap.clear();
787   // Compute the number of dimension in the expanded op that correspond to each
788   // dimension of the original op.
789   SmallVector<unsigned> numExpandedDims(fusedIndexMap.getNumDims(), 1);
790   expandedShapeMap.resize(fusedIndexMap.getNumDims());
791   for (const auto &resultExpr : llvm::enumerate(fusedIndexMap.getResults())) {
792     unsigned pos = resultExpr.value().cast<AffineDimExpr>().getPosition();
793     AffineMap foldedDims = reassociationMaps[resultExpr.index()];
794     numExpandedDims[pos] = foldedDims.getNumResults();
795     ArrayRef<int64_t> shape =
796         expandedShape.slice(foldedDims.getDimPosition(0), numExpandedDims[pos]);
797     expandedShapeMap[pos].assign(shape.begin(), shape.end());
798   }
799   // The remaining dimensions remain the same.
800   for (unsigned i : llvm::seq<unsigned>(0, fusedIndexMap.getNumDims()))
801     if (expandedShapeMap[i].empty())
802       expandedShapeMap[i] = {originalLoopExtent[i]};
803 
804   // Compute reassociation map from the original op to the expanded op.
805   unsigned sum = 0;
806   reassociation.reserve(fusedIndexMap.getNumDims());
807   for (const auto &numFoldedDim : llvm::enumerate(numExpandedDims)) {
808     auto seq = llvm::seq<int64_t>(sum, sum + numFoldedDim.value());
809     reassociation.emplace_back(seq.begin(), seq.end());
810     sum += numFoldedDim.value();
811   }
812   expandedOpNumDims = sum;
813   return success();
814 }
815 
816 /// Epanding the body of a linalg operation requires adaptations of the accessed
817 /// loop indices. Specifically, access of indices in the original operation need
818 /// to be replaced with linearizations of indices in the expanded op. That
819 /// requires the shape of the expanded dimensions to be static (at least all but
820 /// the most significant). For now check that these are all statically sized.
821 /// Note that this could be extended to handle dynamic case, but the
822 /// implementation below uses `affine.apply` which seems to have issues when the
823 /// shapes are not static.
824 static LogicalResult isGenericOpExpandable(GenericOp genericOp,
825                                            const ExpansionInfo &expansionInfo,
826                                            PatternRewriter &rewriter) {
827   if (!genericOp.hasIndexSemantics())
828     return success();
829   for (unsigned i : llvm::seq<unsigned>(0, expansionInfo.getOrigOpNumDims())) {
830     ArrayRef<int64_t> expandedShape = expansionInfo.getExpandedShapeOfDim(i);
831     if (expandedShape.size() == 1)
832       continue;
833     for (int64_t shape : expandedShape.drop_front()) {
834       if (ShapedType::isDynamic(shape)) {
835         return rewriter.notifyMatchFailure(
836             genericOp, "cannot expand due to index semantics and dynamic dims");
837       }
838     }
839   }
840   return success();
841 }
842 
843 /// Return the indexing map to use in the expanded op for a given the
844 /// `indexingMap` of the original operation.
845 static AffineMap
846 getIndexingMapInExpandedOp(OpBuilder &builder, AffineMap indexingMap,
847                            const ExpansionInfo &expansionInfo) {
848   SmallVector<AffineExpr> newExprs;
849   for (AffineExpr expr : indexingMap.getResults()) {
850     unsigned pos = expr.cast<AffineDimExpr>().getPosition();
851     SmallVector<AffineExpr, 4> expandedExprs = llvm::to_vector<4>(
852         llvm::map_range(expansionInfo.getExpandedDims(pos), [&](int64_t v) {
853           return builder.getAffineDimExpr(static_cast<unsigned>(v));
854         }));
855     newExprs.append(expandedExprs.begin(), expandedExprs.end());
856   }
857   return AffineMap::get(expansionInfo.getExpandedOpNumDims(),
858                         indexingMap.getNumSymbols(), newExprs,
859                         builder.getContext());
860 }
861 
862 /// Return the type of the operand/result to use in the expanded op given the
863 /// type in the original op.
864 static RankedTensorType getExpandedType(RankedTensorType originalType,
865                                         AffineMap indexingMap,
866                                         const ExpansionInfo &expansionInfo) {
867   SmallVector<int64_t> expandedShape;
868   for (AffineExpr expr : indexingMap.getResults()) {
869     unsigned dim = expr.cast<AffineDimExpr>().getPosition();
870     auto dimExpansion = expansionInfo.getExpandedShapeOfDim(dim);
871     expandedShape.append(dimExpansion.begin(), dimExpansion.end());
872   }
873   return RankedTensorType::get(expandedShape, originalType.getElementType());
874 }
875 
876 /// Returns the reassociation maps to use in the `tensor.expand_shape`
877 /// operation to convert the operands of the original operation to operands of
878 /// the expanded operation. The same method is used to compute the
879 /// `tensor.collapse_shape` used to collapse the result of the expanded
880 /// op to get the value that can replace all uses of the results of the original
881 /// op.
882 static SmallVector<ReassociationIndices>
883 getReassociationForExpansion(AffineMap indexingMap,
884                              const ExpansionInfo &expansionInfo) {
885   SmallVector<ReassociationIndices> reassociation;
886   unsigned numReshapeDims = 0;
887   for (AffineExpr expr : indexingMap.getResults()) {
888     unsigned dim = expr.cast<AffineDimExpr>().getPosition();
889     auto numExpandedDims = expansionInfo.getExpandedDims(dim).size();
890     SmallVector<int64_t, 2> indices = llvm::to_vector<2>(
891         llvm::seq<int64_t>(numReshapeDims, numReshapeDims + numExpandedDims));
892     reassociation.emplace_back(std::move(indices));
893     numReshapeDims += numExpandedDims;
894   }
895   return reassociation;
896 }
897 
898 /// Update the body of an expanded linalg operation having index semantics. The
899 /// indices of the original operation need to be recovered by linearizing the
900 /// indices of the correspoding dimensions of the expanded operation. For now it
901 /// is assumed that the shapes of the expanded operation needed for
902 /// linearization are static.
903 static void updateExpandedGenericOpRegion(PatternRewriter &rewriter,
904                                           Location loc, Region &fusedRegion,
905                                           const ExpansionInfo &expansionInfo) {
906   // Replace the original indices by the linearization of the expanded indices.
907   for (IndexOp indexOp :
908        llvm::make_early_inc_range(fusedRegion.front().getOps<IndexOp>())) {
909     ArrayRef<int64_t> expandedDims =
910         expansionInfo.getExpandedDims(indexOp.dim());
911     assert(!expandedDims.empty() && "expected valid expansion info");
912 
913     // Skip index operations that are not affected by the expansion.
914     if (expandedDims.size() == 1 &&
915         expandedDims.front() == (int64_t)indexOp.dim())
916       continue;
917 
918     // Linearize the expanded indices of the original index dimension.
919     OpBuilder::InsertionGuard guard(rewriter);
920     rewriter.setInsertionPointAfter(indexOp);
921     ArrayRef<int64_t> expandedDimsShape =
922         expansionInfo.getExpandedShapeOfDim(indexOp.dim()).drop_front();
923     SmallVector<Value> expandedIndices;
924     expandedIndices.reserve(expandedDims.size() - 1);
925     llvm::transform(
926         expandedDims.drop_front(), std::back_inserter(expandedIndices),
927         [&](int64_t dim) { return rewriter.create<IndexOp>(loc, dim); });
928     Value newIndex = rewriter.create<IndexOp>(loc, expandedDims.front());
929     for (auto it : llvm::zip(expandedDimsShape, expandedIndices)) {
930       assert(!ShapedType::isDynamic(std::get<0>(it)));
931       AffineExpr idx, acc;
932       bindDims(rewriter.getContext(), idx, acc);
933       newIndex = rewriter.create<AffineApplyOp>(
934           indexOp.getLoc(), idx + acc * std::get<0>(it),
935           ValueRange{std::get<1>(it), newIndex});
936     }
937     rewriter.replaceOp(indexOp, newIndex);
938   }
939 }
940 
941 /// Implements the fusion of a tensor_collapse_shape or a tensor_expand_shape op
942 /// and a generic op as explained in `isFusableWithReshapeByExpansion`. Assumes
943 /// that those conditions have been satisfied.
944 static Optional<SmallVector<Value>>
945 fuseWithReshapeByExpansion(GenericOp genericOp, Operation *reshapeOp,
946                            OpOperand *fusableOpOperand,
947                            PatternRewriter &rewriter) {
948   assert(isFusableWithReshapeByDimExpansion(genericOp, fusableOpOperand) &&
949          "preconditions for fuse operation failed");
950   // Check if reshape is expanding or collapsing.
951   auto expandingReshapeOp = dyn_cast<tensor::ExpandShapeOp>(*reshapeOp);
952   auto collapsingReshapeOp = dyn_cast<tensor::CollapseShapeOp>(*reshapeOp);
953   bool isExpanding = (expandingReshapeOp != nullptr);
954   RankedTensorType expandedType = isExpanding
955                                       ? expandingReshapeOp.getResultType()
956                                       : collapsingReshapeOp.getSrcType();
957   RankedTensorType collapsedType = isExpanding
958                                        ? expandingReshapeOp.getSrcType()
959                                        : collapsingReshapeOp.getResultType();
960 
961   ExpansionInfo expansionInfo;
962   if (failed(expansionInfo.compute(
963           genericOp, fusableOpOperand,
964           isExpanding ? expandingReshapeOp.getReassociationMaps()
965                       : collapsingReshapeOp.getReassociationMaps(),
966           expandedType.getShape(), collapsedType.getShape(), rewriter)))
967     return llvm::None;
968 
969   if (failed(isGenericOpExpandable(genericOp, expansionInfo, rewriter)))
970     return llvm::None;
971 
972   SmallVector<AffineMap, 4> expandedOpIndexingMaps = llvm::to_vector<4>(
973       llvm::map_range(genericOp.getIndexingMaps(), [&](AffineMap m) {
974         return getIndexingMapInExpandedOp(rewriter, m, expansionInfo);
975       }));
976 
977   SmallVector<Value> expandedOpOperands;
978   expandedOpOperands.reserve(genericOp.getNumInputs());
979   for (OpOperand *opOperand : genericOp.getInputOperands()) {
980     if (opOperand == fusableOpOperand) {
981       expandedOpOperands.push_back(isExpanding ? expandingReshapeOp.src()
982                                                : collapsingReshapeOp.src());
983       continue;
984     }
985     if (genericOp.isInputTensor(opOperand)) {
986       AffineMap indexingMap = genericOp.getTiedIndexingMap(opOperand);
987       auto opOperandType = opOperand->get().getType().cast<RankedTensorType>();
988       RankedTensorType expandedOperandType =
989           getExpandedType(opOperandType, indexingMap, expansionInfo);
990       if (expandedOperandType != opOperand->get().getType()) {
991         // Reshape the operand to get the right type.
992         SmallVector<ReassociationIndices> reassociation =
993             getReassociationForExpansion(indexingMap, expansionInfo);
994         if (failed(reshapeLikeShapesAreCompatible(
995                 [&](const Twine &msg) {
996                   return rewriter.notifyMatchFailure(genericOp, msg);
997                 },
998                 opOperandType.getShape(), expandedOperandType.getShape(),
999                 reassociation,
1000                 /*isExpandingReshape=*/true)))
1001           return llvm::None;
1002         expandedOpOperands.push_back(rewriter.create<tensor::ExpandShapeOp>(
1003             genericOp.getLoc(), expandedOperandType, opOperand->get(),
1004             reassociation));
1005         continue;
1006       }
1007     }
1008     expandedOpOperands.push_back(opOperand->get());
1009   }
1010 
1011   Location loc = genericOp.getLoc();
1012   SmallVector<Value> outputs;
1013   for (OpOperand *opOperand : genericOp.getOutputOperands()) {
1014     AffineMap indexingMap = genericOp.getTiedIndexingMap(opOperand);
1015     auto opOperandType = opOperand->get().getType().cast<RankedTensorType>();
1016     RankedTensorType expandedOutputType =
1017         getExpandedType(opOperandType, indexingMap, expansionInfo);
1018     if (expandedOutputType != opOperand->get().getType()) {
1019       SmallVector<ReassociationIndices> reassociation =
1020           getReassociationForExpansion(indexingMap, expansionInfo);
1021       if (failed(reshapeLikeShapesAreCompatible(
1022               [&](const Twine &msg) {
1023                 return rewriter.notifyMatchFailure(genericOp, msg);
1024               },
1025               opOperandType.getShape(), expandedOutputType.getShape(),
1026               reassociation,
1027               /*isExpandingReshape=*/true)))
1028         return llvm::None;
1029       outputs.push_back(rewriter.create<tensor::ExpandShapeOp>(
1030           genericOp.getLoc(), expandedOutputType, opOperand->get(),
1031           reassociation));
1032     }
1033   }
1034 
1035   // The iterator types of the expanded op are all parallel.
1036   SmallVector<StringRef> iteratorTypes(expansionInfo.getExpandedOpNumDims(),
1037                                        getParallelIteratorTypeName());
1038 
1039   TypeRange resultTypes = ValueRange(outputs).getTypes();
1040   auto fusedOp =
1041       rewriter.create<GenericOp>(genericOp.getLoc(), resultTypes,
1042                                  /*inputs=*/expandedOpOperands, outputs,
1043                                  expandedOpIndexingMaps, iteratorTypes);
1044   Region &fusedRegion = fusedOp->getRegion(0);
1045   Region &originalRegion = genericOp->getRegion(0);
1046   rewriter.cloneRegionBefore(originalRegion, fusedRegion, fusedRegion.begin());
1047 
1048   // Update the index accesses after the expansion.
1049   updateExpandedGenericOpRegion(rewriter, loc, fusedRegion, expansionInfo);
1050 
1051   // Reshape the result values to their original shape if this is a collapsing
1052   // reshape folded into its consumer.
1053   SmallVector<Value> resultVals;
1054   for (OpResult opResult : genericOp->getOpResults()) {
1055     int64_t resultNumber = opResult.getResultNumber();
1056     if (!isExpanding && resultTypes[resultNumber] != opResult.getType()) {
1057       SmallVector<ReassociationIndices> reassociation =
1058           getReassociationForExpansion(
1059               genericOp.getTiedIndexingMap(
1060                   genericOp.getOutputOperand(resultNumber)),
1061               expansionInfo);
1062       resultVals.push_back(rewriter.create<tensor::CollapseShapeOp>(
1063           genericOp.getLoc(), opResult.getType(),
1064           fusedOp->getResult(resultNumber), reassociation));
1065     } else {
1066       resultVals.push_back(fusedOp->getResult(resultNumber));
1067     }
1068   }
1069   // Assuming a single result.
1070   return resultVals;
1071 }
1072 
1073 namespace {
1074 
1075 /// Pattern to fuse a tensor_collapse_shape op with its consumer generic op,
1076 /// when the reshape op is collapsing dimensions. The dimensionality of the loop
1077 /// in the consumer is expanded.
1078 class FoldWithProducerReshapeOpByExpansion
1079     : public OpRewritePattern<GenericOp> {
1080 public:
1081   FoldWithProducerReshapeOpByExpansion(MLIRContext *context,
1082                                        ControlFusionFn foldReshapes,
1083                                        PatternBenefit benefit = 1)
1084       : OpRewritePattern<GenericOp>(context, benefit),
1085         controlFoldingReshapes(std::move(foldReshapes)) {}
1086 
1087   LogicalResult matchAndRewrite(GenericOp genericOp,
1088                                 PatternRewriter &rewriter) const override {
1089     for (OpOperand *opOperand : genericOp.getInputTensorOperands()) {
1090       tensor::CollapseShapeOp reshapeOp =
1091           opOperand->get().getDefiningOp<tensor::CollapseShapeOp>();
1092       if (!reshapeOp)
1093         continue;
1094       // Fold only if
1095       // - The tensor reshape op is folding.
1096       // - All constraints of fusing with reshape by expansion are met.
1097       if (!isFusableWithReshapeByDimExpansion(genericOp, opOperand) ||
1098           (!controlFoldingReshapes(reshapeOp->getResult(0), *opOperand)))
1099         continue;
1100 
1101       Optional<SmallVector<Value>> replacementValues =
1102           fuseWithReshapeByExpansion(genericOp, reshapeOp, opOperand, rewriter);
1103       if (!replacementValues)
1104         return failure();
1105       rewriter.replaceOp(genericOp, replacementValues.getValue());
1106       return success();
1107     }
1108     return failure();
1109   }
1110 
1111 private:
1112   ControlFusionFn controlFoldingReshapes;
1113 };
1114 
1115 /// Pattern to fold a tensor_expand_shape op with its producer generic op
1116 /// by expanding the dimensionality of the loop in the producer op.
1117 struct FoldReshapeWithGenericOpByExpansion
1118     : public OpRewritePattern<tensor::ExpandShapeOp> {
1119 
1120   FoldReshapeWithGenericOpByExpansion(MLIRContext *context,
1121                                       ControlFusionFn foldReshapes,
1122                                       PatternBenefit benefit = 1)
1123       : OpRewritePattern<tensor::ExpandShapeOp>(context, benefit),
1124         controlFoldingReshapes(std::move(foldReshapes)) {}
1125 
1126   LogicalResult matchAndRewrite(tensor::ExpandShapeOp reshapeOp,
1127                                 PatternRewriter &rewriter) const override {
1128     // Fold only if all constraints of fusing with reshape by expansion are met.
1129     GenericOp producer = reshapeOp.src().getDefiningOp<GenericOp>();
1130     if (!producer || producer.getNumOutputs() != 1 ||
1131         !isFusableWithReshapeByDimExpansion(producer,
1132                                             producer.getOutputOperand(0)) ||
1133         !controlFoldingReshapes(producer->getResult(0),
1134                                 reshapeOp->getOpOperand(0)))
1135       return failure();
1136     Optional<SmallVector<Value>> replacementValues = fuseWithReshapeByExpansion(
1137         producer, reshapeOp, producer.getOutputOperand(0), rewriter);
1138     if (!replacementValues)
1139       return failure();
1140     rewriter.replaceOp(reshapeOp, replacementValues.getValue());
1141     return success();
1142   }
1143 
1144 private:
1145   ControlFusionFn controlFoldingReshapes;
1146 };
1147 } // namespace
1148 
1149 //===---------------------------------------------------------------------===//
1150 // Methods and patterns to fuse reshape with linalg.generic operations by
1151 // contraction of dimensions.
1152 //===---------------------------------------------------------------------===//
1153 
1154 /// For a given list of indices in the range of the `indexingMap` that are
1155 /// folded, return the indices of the corresponding domain. Return `llvm::None`
1156 /// on failure. Ensures that all the elements of the returned reassociation are
1157 /// distinct.
1158 static ReassociationIndices
1159 getDomainReassociation(AffineMap indexingMap,
1160                        ReassociationIndicesRef rangeReassociation) {
1161   assert(indexingMap.isProjectedPermutation() &&
1162          "expected projected permutation");
1163 
1164   ReassociationIndices domainReassociation = llvm::to_vector<4>(
1165       llvm::map_range(rangeReassociation, [&](int64_t pos) -> int64_t {
1166         return indexingMap.getResults()[pos]
1167             .cast<AffineDimExpr>()
1168             .getPosition();
1169       }));
1170   // The projected permutation semantics ensures that there is no repetition of
1171   // the domain indices.
1172   return domainReassociation;
1173 }
1174 
1175 /// For a given `dimSequence`, check if the sequence is conserved in the
1176 /// `indexingMap`. `indexingMap` is expected to be a projected permutation.
1177 /// Non-existence of the sequence returns true as well.
1178 static bool isDimSequencePreserved(AffineMap indexingMap,
1179                                    ReassociationIndicesRef dimSequence) {
1180   assert(!dimSequence.empty() &&
1181          "expected non-empty list for dimension sequence");
1182   assert(indexingMap.isProjectedPermutation() &&
1183          "expected indexing map to be projected permutation");
1184 
1185   llvm::SmallDenseSet<unsigned, 4> sequenceElements;
1186   sequenceElements.insert(dimSequence.begin(), dimSequence.end());
1187 
1188   unsigned dimSequenceStart = dimSequence[0];
1189   for (const auto &expr : enumerate(indexingMap.getResults())) {
1190     unsigned dimInMapStart = expr.value().cast<AffineDimExpr>().getPosition();
1191     // 1.  Check if this start of the sequence.
1192     if (dimInMapStart == dimSequenceStart) {
1193       if (expr.index() + dimSequence.size() > indexingMap.getNumResults())
1194         return false;
1195       // 1a. Check if sequence is preserved.
1196       for (const auto &dimInSequence : enumerate(dimSequence)) {
1197         unsigned dimInMap =
1198             indexingMap.getResult(expr.index() + dimInSequence.index())
1199                 .cast<AffineDimExpr>()
1200                 .getPosition();
1201         if (dimInMap != dimInSequence.value())
1202           return false;
1203       }
1204       // Found the sequence. Projected permutation
1205       // enforces that all AffineDimExprs in the result are unique, so no
1206       // further checks are needed.
1207       return true;
1208     }
1209     // 2. If position in the expr (which is of type AffineDimExpr) is part
1210     // of sequence, return false here. This implies the entire sequence does not
1211     // exist in the indexing map.
1212     if (sequenceElements.count(dimInMapStart))
1213       return false;
1214   }
1215   // 3. No element of sequence found. Return true.
1216   return true;
1217 }
1218 
1219 // Return the list of dimensions of the iteration domain that can be
1220 // collapsed to allow for fusion with the a producer that is an expand_shape
1221 // operation. If all dimensions created by expansion can be collapsed in the
1222 // iteration space then the reshape is defunct.
1223 //
1224 // Example:
1225 //
1226 // ```mlir
1227 // #map = affine_map<(d0, d1) -> (d0, d1)>
1228 // %1 = tensor.expand_shape %0 [[0, 1]] : tensor<?xf32> into tensor<?x4xf32>
1229 // %2 = linalg.init_tensor [..] : tensor<?x4xf32>
1230 // %3 = linalg.generic {
1231 //     indexing_maps = [#map, #map],
1232 //     iterator_types = ["parallel" ,"parallel"]}
1233 //     ins(%1 : tensor<?x4xf32>) outs(%2 : tensor<?x4xf32>) {.. }
1234 // ```
1235 //
1236 // can be fused by collapsing the dimensions of the iteration space.
1237 //
1238 // ```mlir
1239 // #map = affine_map<(d0) -> (d0)>
1240 // %2 = linalg.init_tensor [..] : tensor<?xf32>
1241 // %3 = linalg.generic {
1242 //     indexing_maps = [#map, #map],
1243 //     iterator_types = ["parallel"]}
1244 //     ins(%1 : tensor<?xf32>) outs(%2 : tensor<?xf32>) {.. }
1245 // %4 = tensor.expand_shape %3 [[0, 1]] : tensor<?xf32> into tensor<?x4xf32>
1246 // ```
1247 //
1248 // In the following example,
1249 //
1250 // ```mlir
1251 // #map0 = affine_map<(d0, d1) -> (d0, d1)>
1252 // #map1 = affine_map<(d0, d1) -> (d1, d0)>
1253 // %1 = tensor.expand_shape %0 [[0, 1]] : tensor<?xf32> into tensor<?x4xf32>
1254 // %2 = linalg.init_tensor [..] : tensor<4x?xf32>
1255 // %2 = linalg.generic {
1256 //     indexing_maps = [#map0, #map1],
1257 //     iterator_types = ["parallel" ,"parallel"]}
1258 //     ins(%1 : tensor<?x4xf32>) outs(%2 : tensor<4x?xf32>) {.. }
1259 // ```
1260 //
1261 // the reshape cannot be fused with the generic op by collapsing the op
1262 // dimensions since the indexing maps will have to contain mods and divs
1263 // to preserve the accesses pattern. When no dimensions of the iteration
1264 // space are collapsable and empty vector is returned.
1265 static SmallVector<ReassociationIndices>
1266 getCollapsableIterationSpaceDims(GenericOp genericOp, OpOperand *fusableOperand,
1267                                  ArrayRef<ReassociationIndices> reassociation) {
1268   // Some basic checks for this fusion to be valid.
1269   if (!genericOp.hasTensorSemantics() || genericOp.getNumOutputs() != 1)
1270     return {};
1271 
1272   if (!llvm::all_of(genericOp.getIndexingMaps(), [](AffineMap map) {
1273         return map.isProjectedPermutation();
1274       })) {
1275     return {};
1276   }
1277 
1278   // Compute all the loops with the reduction iterator types.
1279   SmallVector<int64_t> reductionDims;
1280   for (const auto &iteratorType : llvm::enumerate(genericOp.iterator_types())) {
1281     if (isReductionIterator(iteratorType.value())) {
1282       reductionDims.push_back(iteratorType.index());
1283     }
1284   }
1285 
1286   llvm::SmallDenseSet<unsigned, 4> processedIterationDims;
1287   AffineMap indexingMap = genericOp.getTiedIndexingMap(fusableOperand);
1288   auto iteratorTypes = genericOp.iterator_types().getValue();
1289   SmallVector<ReassociationIndices> iterationSpaceReassociation;
1290   for (ReassociationIndicesRef foldedRangeDims : reassociation) {
1291     assert(!foldedRangeDims.empty() && "unexpected empty reassociation");
1292 
1293     // Ignore dims that are not folded.
1294     if (foldedRangeDims.size() == 1)
1295       continue;
1296 
1297     ReassociationIndices foldedIterationSpaceDims =
1298         getDomainReassociation(indexingMap, foldedRangeDims);
1299 
1300     // Check that the folded iteration dims do not contain already processed
1301     // dims.
1302     if (llvm::any_of(foldedIterationSpaceDims, [&](int64_t dim) {
1303           return processedIterationDims.count(dim);
1304         }))
1305       continue;
1306 
1307     // Check that all folded iterator types are all parallel or all reductions.
1308     Attribute startIteratorType = iteratorTypes[foldedIterationSpaceDims[0]];
1309     if (!isParallelIterator(startIteratorType) &&
1310         !isReductionIterator(startIteratorType))
1311       continue;
1312     if (llvm::any_of(foldedIterationSpaceDims, [&](int64_t dim) {
1313           return iteratorTypes[dim] != startIteratorType;
1314         }))
1315       continue;
1316 
1317     // If the folded dimensions correspond to a "reduction" iterator type,
1318     // the folded dimensions need to be "in-order". Strictly speaking this is
1319     // not necessary, for reductions that are associative and commutative,  but
1320     // using a more strict definition of reduction for now.
1321     if (isReductionIterator(startIteratorType)) {
1322       bool isContiguous = false;
1323       for (const auto &startDim : llvm::enumerate(reductionDims)) {
1324         // Move window in `reductionDims` to start of the folded iteration dims.
1325         if (startDim.value() != foldedIterationSpaceDims[0])
1326           continue;
1327         // If sizes doesnt match, trivial not contiguous. This condition should
1328         // not be hit.
1329         if (startDim.index() + foldedIterationSpaceDims.size() >
1330             reductionDims.size())
1331           break;
1332         // Check that the contiguity is maintained.
1333         isContiguous = true;
1334         for (const auto &foldedDim :
1335              llvm::enumerate(foldedIterationSpaceDims)) {
1336           if (reductionDims[foldedDim.index() + startDim.index()] !=
1337               foldedDim.value()) {
1338             isContiguous = false;
1339             break;
1340           }
1341         }
1342         break;
1343       }
1344       if (!isContiguous)
1345         continue;
1346     }
1347 
1348     // Check that the sequence is preserved in all indexing maps.
1349     if (llvm::any_of(genericOp.getIndexingMaps(), [&](AffineMap indexingMap) {
1350           return !isDimSequencePreserved(indexingMap, foldedIterationSpaceDims);
1351         }))
1352       continue;
1353 
1354     processedIterationDims.insert(foldedIterationSpaceDims.begin(),
1355                                   foldedIterationSpaceDims.end());
1356     iterationSpaceReassociation.emplace_back(
1357         std::move(foldedIterationSpaceDims));
1358   }
1359 
1360   return iterationSpaceReassociation;
1361 }
1362 
1363 /// Helper class to carry state while collapsing the `linalg.generic` op.
1364 namespace {
1365 class CollapsingInfo {
1366 public:
1367   LogicalResult initialize(unsigned origNumLoops,
1368                            ArrayRef<ReassociationIndices> foldedIterationDims) {
1369     llvm::SmallDenseSet<int64_t, 4> processedDims;
1370     // Find all the dims that are folded.
1371     for (ReassociationIndicesRef foldedIterationDim : foldedIterationDims) {
1372       if (foldedIterationDim.empty())
1373         continue;
1374       // If the folded dims contain dims already folded, that's illegal
1375       // specification. Repetition within a list is also illegal.
1376       for (auto dim : foldedIterationDim) {
1377         if (dim >= origNumLoops)
1378           return failure();
1379         if (processedDims.count(dim))
1380           return failure();
1381         processedDims.insert(dim);
1382       }
1383       collapsedOpToOrigOpIterationDim.emplace_back(foldedIterationDim.begin(),
1384                                                    foldedIterationDim.end());
1385     }
1386     if (processedDims.size() > origNumLoops)
1387       return failure();
1388 
1389     // Add all the preserved dims of the original op as single
1390     // elements to `collapsedOpToOrigOpIterationDim`.
1391     for (auto dim : llvm::seq<int64_t>(0, origNumLoops)) {
1392       if (processedDims.count(dim))
1393         continue;
1394       collapsedOpToOrigOpIterationDim.emplace_back(ReassociationIndices{dim});
1395     }
1396 
1397     llvm::sort(collapsedOpToOrigOpIterationDim,
1398                [&](ReassociationIndicesRef lhs, ReassociationIndicesRef rhs) {
1399                  return lhs[0] < rhs[0];
1400                });
1401     origOpToCollapsedOpIterationDim.resize(origNumLoops);
1402     for (const auto &foldedDims :
1403          llvm::enumerate(collapsedOpToOrigOpIterationDim)) {
1404       for (const auto &dim : enumerate(foldedDims.value()))
1405         origOpToCollapsedOpIterationDim[dim.value()] =
1406             std::make_pair<int64_t, unsigned>(foldedDims.index(), dim.index());
1407     }
1408     return success();
1409   }
1410 
1411   /// Return mapping from collapsed loop domain to original loop domain.
1412   ArrayRef<ReassociationIndices> getCollapsedOpToOrigOpMapping() const {
1413     return collapsedOpToOrigOpIterationDim;
1414   }
1415 
1416   /// Return mapping from original loop domain to collapsed loop domain. The
1417   /// mapping is a pair. First value is the dimension in the collapsed loop that
1418   /// the original loop is mapped to. Second is the relative position in folded
1419   /// list of this domain. For example if the original loop domain is 3D, and
1420   /// the collapsed loop domain is folding all of it, i.e.
1421   ///
1422   /// ```
1423   /// collapsedOpToOrigOpMapping = [[0, 1, 2] [3, 4]]`
1424   /// ```
1425   ///
1426   /// then
1427   ///
1428   /// ```
1429   ///  origOpToCollapsedOpMapping[0] = {0, 0};
1430   ///  origOpToCollapsedOpMapping[1] = {0, 1};
1431   ///  origOpToCollapsedOpMapping[2] = {0, 2};
1432   ///  origOpToCollapsedOpMapping[3] = {1, 0};
1433   ///  origOpToCollapsedOpMapping[4] = {1, 1};
1434   /// ```
1435   ///
1436   ArrayRef<std::pair<int64_t, unsigned>> getOrigOpToCollapsedOpMapping() const {
1437     return origOpToCollapsedOpIterationDim;
1438   }
1439 
1440   /// Return the collapsed op iteration domain rank.
1441   unsigned getCollapsedOpIterationRank() const {
1442     return collapsedOpToOrigOpIterationDim.size();
1443   }
1444 
1445 private:
1446   /// Map from the iteration domain index in collapsed op to the iteration
1447   /// domain indices in the original op.
1448   SmallVector<ReassociationIndices> collapsedOpToOrigOpIterationDim;
1449 
1450   /// Map from iteration domain index in the original op to the iteration domain
1451   /// index in the collapsed op.
1452   SmallVector<std::pair<int64_t, unsigned>> origOpToCollapsedOpIterationDim;
1453 };
1454 } // namespace
1455 
1456 /// Get the iterator types for the collapsed operation given the original
1457 /// iterator types and collapsed dimensions.
1458 static SmallVector<StringRef>
1459 getCollapsedOpIteratorTypes(ArrayRef<Attribute> iteratorTypes,
1460                             const CollapsingInfo &collapsingInfo) {
1461   SmallVector<StringRef> collapsedIteratorTypes;
1462   for (ReassociationIndicesRef foldedIterDims :
1463        collapsingInfo.getCollapsedOpToOrigOpMapping()) {
1464     assert(!foldedIterDims.empty() &&
1465            "reassociation indices expected to have non-empty sets");
1466     // Just pick the iterator type of the first folded dim. Pre-condition checks
1467     // expected to have checked that iterator types of all folded dimensions are
1468     // the same.
1469     collapsedIteratorTypes.push_back(
1470         iteratorTypes[foldedIterDims[0]].cast<StringAttr>().getValue());
1471   }
1472   return collapsedIteratorTypes;
1473 }
1474 
1475 /// Compute the indexing map in the collapsed op that corresponds to the given
1476 /// `indexingMap` of the original operation.
1477 static AffineMap
1478 getCollapsedOpIndexingMap(AffineMap indexingMap,
1479                           const CollapsingInfo &collapsingInfo) {
1480   MLIRContext *context = indexingMap.getContext();
1481   assert(indexingMap.isProjectedPermutation() &&
1482          "expected indexing map to be projected permutation");
1483   SmallVector<AffineExpr> resultExprs;
1484   auto origOpToCollapsedOpMapping =
1485       collapsingInfo.getOrigOpToCollapsedOpMapping();
1486   for (auto expr : indexingMap.getResults()) {
1487     unsigned dim = expr.cast<AffineDimExpr>().getPosition();
1488     // If the dim is not the first of the collapsed dim, do nothing.
1489     if (origOpToCollapsedOpMapping[dim].second != 0)
1490       continue;
1491     // The next n-dims are guaranteed to be collapsed. So just use the
1492     // iteration dimension of the collapsed op.
1493     resultExprs.push_back(
1494         getAffineDimExpr(origOpToCollapsedOpMapping[dim].first, context));
1495   }
1496   return AffineMap::get(collapsingInfo.getCollapsedOpIterationRank(), 0,
1497                         resultExprs, context);
1498 }
1499 
1500 /// Return the `reassociation` indices to use to collapse the operand when the
1501 /// iteration space of a generic op is collapsed.
1502 static SmallVector<ReassociationIndices>
1503 getOperandReassociation(AffineMap indexingMap,
1504                         const CollapsingInfo &collapsingInfo) {
1505   unsigned counter = 0;
1506   SmallVector<ReassociationIndices> operandReassociation;
1507   auto origOpToCollapsedOpMapping =
1508       collapsingInfo.getOrigOpToCollapsedOpMapping();
1509   auto collapsedOpToOrigOpMapping =
1510       collapsingInfo.getCollapsedOpToOrigOpMapping();
1511   while (counter < indexingMap.getNumResults()) {
1512     unsigned dim =
1513         indexingMap.getResult(counter).cast<AffineDimExpr>().getPosition();
1514     if (origOpToCollapsedOpMapping[dim].second == 0) {
1515       // This is the start of a collapsed dimensions of the iteration that
1516       // is gauranteed to be preserved in the indexing map. The number of folded
1517       // dims is obtained from the collapsed op to original op mapping.
1518       unsigned numFoldedDims =
1519           collapsedOpToOrigOpMapping[origOpToCollapsedOpMapping[dim].first]
1520               .size();
1521       auto range = llvm::seq<unsigned>(counter, counter + numFoldedDims);
1522       operandReassociation.emplace_back(range.begin(), range.end());
1523       counter += numFoldedDims;
1524     }
1525   }
1526   return operandReassociation;
1527 }
1528 
1529 /// Get the new value to use for a given `OpOperand` in the collapsed operation.
1530 static Value getCollapsedOpOperand(Location loc, GenericOp genericOp,
1531                                    OpOperand *opOperand,
1532                                    const CollapsingInfo &collapsingInfo,
1533                                    OpBuilder &builder) {
1534   AffineMap indexingMap = genericOp.getTiedIndexingMap(opOperand);
1535   SmallVector<ReassociationIndices> operandReassociation =
1536       getOperandReassociation(indexingMap, collapsingInfo);
1537 
1538   // If the number of entries in the reassocation for the operand is same as the
1539   // number of results of the indexing map, then nothing to do for this operand.
1540   Value operand = opOperand->get();
1541   if (operandReassociation.size() == indexingMap.getNumResults())
1542     return operand;
1543 
1544   // Insert a reshape to collapse the dimensions.
1545   auto reshapeOp = builder.create<tensor::CollapseShapeOp>(
1546       loc, operand, operandReassociation);
1547   return reshapeOp.getResult();
1548 }
1549 
1550 /// Modify the `linalg.index` operations in the original generic op, to its
1551 /// value in the collapsed operation.
1552 void generateCollapsedIndexingRegion(Location loc, Block *block,
1553                                      const CollapsingInfo &collapsingInfo,
1554                                      ValueRange loopRange,
1555                                      PatternRewriter &rewriter) {
1556   OpBuilder::InsertionGuard g(rewriter);
1557   rewriter.setInsertionPointToStart(block);
1558 
1559   // Collect all the original index ops.
1560   auto indexOps = llvm::to_vector(block->getOps<linalg::IndexOp>());
1561 
1562   // For each folded dimension list resolve the original induction variable
1563   // values in terms of the folded dimension induction variable.
1564   //   i_{folded} = (i_0 * d1 + i1) * d2 + i2.
1565   // can be inverted to
1566   //   i2 = i_{folded} % d2
1567   //   i1 = (i_{folded} / d2) % d1
1568   //   i0 = i_{folded} / (d1 * d2)
1569   llvm::DenseMap<unsigned, Value> indexReplacementVals;
1570   for (auto &foldedDims :
1571        enumerate(collapsingInfo.getCollapsedOpToOrigOpMapping())) {
1572     ReassociationIndicesRef foldedDimsRef(foldedDims.value());
1573     Value newIndexVal =
1574         rewriter.create<linalg::IndexOp>(loc, foldedDims.index());
1575     for (auto dim : llvm::reverse(foldedDimsRef.drop_front())) {
1576       indexReplacementVals[dim] =
1577           rewriter.create<arith::RemUIOp>(loc, newIndexVal, loopRange[dim]);
1578       newIndexVal =
1579           rewriter.create<arith::DivUIOp>(loc, newIndexVal, loopRange[dim]);
1580     }
1581     indexReplacementVals[foldedDims.value().front()] = newIndexVal;
1582   }
1583 
1584   for (auto indexOp : indexOps) {
1585     auto dim = indexOp.dim();
1586     rewriter.replaceOp(indexOp, indexReplacementVals[dim]);
1587   }
1588 }
1589 
1590 /// Implementation of fusion with reshape operation by collapsing dimensions.
1591 static FailureOr<SmallVector<Value>> collapseGenericOpIterationDims(
1592     GenericOp genericOp, ArrayRef<ReassociationIndices> foldedIterationDims,
1593     OpOperand *fusableOpOperand, PatternRewriter &rewriter) {
1594   // Bail on trivial no-op cases.
1595   if (genericOp.getNumLoops() <= 1 || foldedIterationDims.empty() ||
1596       llvm::all_of(foldedIterationDims, [](ReassociationIndicesRef foldedDims) {
1597         return foldedDims.size() <= 1;
1598       }))
1599     return failure();
1600 
1601   CollapsingInfo collapsingInfo;
1602   if (failed(collapsingInfo.initialize(genericOp.getNumLoops(),
1603                                        foldedIterationDims))) {
1604     return rewriter.notifyMatchFailure(
1605         genericOp, "illegal to collapse specified dimensions");
1606   }
1607 
1608   // Get the iterator types for the operand.
1609   SmallVector<StringRef> iteratorTypes = getCollapsedOpIteratorTypes(
1610       genericOp.iterator_types().getValue(), collapsingInfo);
1611 
1612   // Get the indexing maps.
1613   auto indexingMaps = llvm::to_vector(
1614       llvm::map_range(genericOp.getIndexingMaps(), [&](AffineMap map) {
1615         return getCollapsedOpIndexingMap(map, collapsingInfo);
1616       }));
1617 
1618   Location loc = genericOp->getLoc();
1619 
1620   // Get the input operands.
1621   auto inputOperands = llvm::to_vector(
1622       llvm::map_range(genericOp.getInputOperands(), [&](OpOperand *opOperand) {
1623         return getCollapsedOpOperand(loc, genericOp, opOperand, collapsingInfo,
1624                                      rewriter);
1625       }));
1626 
1627   // Get the output operands and result types.
1628   SmallVector<Type> resultTypes;
1629   SmallVector<Value> outputOperands;
1630   resultTypes.reserve(genericOp.getNumOutputs());
1631   outputOperands.reserve(genericOp.getNumOutputs());
1632   for (OpOperand *output : genericOp.getOutputOperands()) {
1633     Value newOutput =
1634         getCollapsedOpOperand(loc, genericOp, output, collapsingInfo, rewriter);
1635     outputOperands.push_back(newOutput);
1636     resultTypes.push_back(newOutput.getType());
1637   }
1638 
1639   // Create the generic op.
1640   auto collapsedGenericOp = rewriter.create<linalg::GenericOp>(
1641       loc, resultTypes, inputOperands, outputOperands, indexingMaps,
1642       iteratorTypes, [](OpBuilder &builder, Location loc, ValueRange args) {});
1643   Block *origOpBlock = &genericOp->getRegion(0).front();
1644   Block *collapsedOpBlock = &collapsedGenericOp->getRegion(0).front();
1645   rewriter.mergeBlocks(origOpBlock, collapsedOpBlock,
1646                        collapsedOpBlock->getArguments());
1647 
1648   if (collapsedGenericOp.hasIndexSemantics()) {
1649     // Collect the loop range of the generic op.
1650     OpBuilder::InsertionGuard g(rewriter);
1651     rewriter.setInsertionPoint(collapsedGenericOp);
1652     SmallVector<Range> loopRanges =
1653         cast<LinalgOp>(genericOp.getOperation())
1654             .createLoopRanges(rewriter, genericOp.getLoc());
1655     assert(llvm::all_of(loopRanges,
1656                         [](Range range) {
1657                           return matchPattern(range.offset, m_Zero()) &&
1658                                  matchPattern(range.stride, m_One());
1659                         }) &&
1660            "expected all loop ranges to have zero start and unit stride");
1661     SmallVector<Value> loopBound = llvm::to_vector(
1662         llvm::map_range(loopRanges, [](Range range) { return range.size; }));
1663     generateCollapsedIndexingRegion(loc,
1664                                     &collapsedGenericOp->getRegion(0).front(),
1665                                     collapsingInfo, loopBound, rewriter);
1666   }
1667 
1668   // Insert expanding reshape for the result to get back the original result
1669   // type.
1670   SmallVector<Value> results;
1671   for (const auto &originalResult : llvm::enumerate(genericOp->getResults())) {
1672     Value collapsedOpResult =
1673         collapsedGenericOp->getResult(originalResult.index());
1674     auto originalResultType =
1675         originalResult.value().getType().cast<ShapedType>();
1676     auto collapsedOpResultType = collapsedOpResult.getType().cast<ShapedType>();
1677     if (collapsedOpResultType.getRank() != originalResultType.getRank()) {
1678       AffineMap indexingMap =
1679           genericOp.getTiedIndexingMapForResult(originalResult.value());
1680       SmallVector<ReassociationIndices> reassociation =
1681           getOperandReassociation(indexingMap, collapsingInfo);
1682       Value result = rewriter.create<tensor::ExpandShapeOp>(
1683           loc, originalResultType, collapsedOpResult, reassociation);
1684       results.push_back(result);
1685     } else {
1686       results.push_back(collapsedOpResult);
1687     }
1688   }
1689   return results;
1690 }
1691 
1692 namespace {
1693 
1694 /// Pattern to fuse a tensor.expand_shape op with its consumer generic op by
1695 /// contracting dimensions of the loop.
1696 class FoldWithProducerReshapeOpByCollapsing
1697     : public OpRewritePattern<GenericOp> {
1698 public:
1699   FoldWithProducerReshapeOpByCollapsing(MLIRContext *context,
1700                                         ControlFusionFn foldReshapes,
1701                                         PatternBenefit benefit = 1)
1702       : OpRewritePattern<GenericOp>(context, benefit),
1703         controlFoldingReshapes(std::move(foldReshapes)) {}
1704 
1705   LogicalResult matchAndRewrite(GenericOp genericOp,
1706                                 PatternRewriter &rewriter) const override {
1707     for (OpOperand *opOperand : genericOp.getInputTensorOperands()) {
1708       tensor::ExpandShapeOp reshapeOp =
1709           opOperand->get().getDefiningOp<tensor::ExpandShapeOp>();
1710       if (!reshapeOp)
1711         continue;
1712 
1713       SmallVector<ReassociationIndices> collapsableIterationDims =
1714           getCollapsableIterationSpaceDims(genericOp, opOperand,
1715                                            reshapeOp.getReassociationIndices());
1716       if (collapsableIterationDims.empty() ||
1717           !controlFoldingReshapes(reshapeOp->getResult(0), *opOperand)) {
1718         continue;
1719       }
1720 
1721       Optional<SmallVector<Value>> replacements =
1722           collapseGenericOpIterationDims(genericOp, collapsableIterationDims,
1723                                          opOperand, rewriter);
1724       if (!replacements) {
1725         return rewriter.notifyMatchFailure(
1726             genericOp, "failed to do the fusion by collapsing transformation");
1727       }
1728 
1729       rewriter.replaceOp(genericOp, replacements.getValue());
1730       return success();
1731     }
1732     return failure();
1733   }
1734 
1735 private:
1736   ControlFusionFn controlFoldingReshapes;
1737 };
1738 } // namespace
1739 
1740 //===---------------------------------------------------------------------===//
1741 // Methods and patterns to convert tensor.expand_shape -> linalg.generic
1742 // into linalg.generic -> tensor.expand_shape, i.e. push the reshape down.
1743 //===---------------------------------------------------------------------===//
1744 
1745 // TODO(ravishankarm): This pattern is to be deprecated in favor of fusion by
1746 // collapsing that provides a more general functionality. This pattern is very
1747 // specific to a particular use case. The fusion by collapsing can provide the
1748 // same control to clients using the control function there.
1749 
1750 static SmallVector<ReassociationIndices>
1751 getReassociationIndices(ArrayRef<AffineMap> maps) {
1752   SmallVector<ReassociationIndices> reassociation;
1753   for (AffineMap map : maps) {
1754     ReassociationIndices indices;
1755     for (unsigned i = 0, e = map.getNumResults(); i < e; i++) {
1756       unsigned pos = map.getResult(i).cast<AffineDimExpr>().getPosition();
1757       indices.push_back(pos);
1758     }
1759     reassociation.push_back(indices);
1760   }
1761   return reassociation;
1762 }
1763 
1764 namespace {
1765 /// Pattern to move rank reducing reshape after an elementwise linalg generic
1766 /// op. This is useful to expose more fusion opportunities between named ops and
1767 /// generic ops. This can only be done if there is no broadcast or permuation
1768 /// within the dimensions we need to merge.
1769 ///
1770 /// For example,
1771 ///
1772 ///  %0 = tensor.expand_shape %A [[0, 1], [2]]
1773 ///      : tensor<12544x16xf32> into tensor<112x112x16xf32>
1774 ///  %2 = linalg.generic {indexing_maps = [
1775 ///    affine_map<(d0, d1, d2) -> (d0, d1, d2)>,
1776 ///    affine_map<(d0, d1, d2) -> (d2)>,
1777 ///    affine_map<(d0, d1, d2) -> (d0, d1, d2)>], iterator_types =
1778 ///    ["parallel", "parallel", "parallel"]} {
1779 ///  } -> tensor<112x112x16xf32>
1780 ///
1781 ///  into
1782 ///
1783 ///  %2 = linalg.generic {indexing_maps = [
1784 ///    affine_map<(d0, d1) -> (d0, d1)>,
1785 ///    affine_map<(d0, d1) -> (d1)>,
1786 ///    affine_map<(d0, d1) -> (d0, d1)>],
1787 ///    iterator_types = ["parallel", "parallel"]} ins(%arg0, %arg1
1788 ///    : tensor<12544x16xf32>, tensor<16xf32>) outs(%1 : tensor<12544x16xf32>) {
1789 ///  } -> tensor<12544x16xf32>
1790 ///  %3 = tensor.expand_shape %2 [[0, 1], [2]]
1791 ///    : tensor<12544x16xf32> into tensor<112x112x16xf32>
1792 struct PushExpandingReshape : public OpRewritePattern<GenericOp> {
1793   using OpRewritePattern<GenericOp>::OpRewritePattern;
1794 
1795   LogicalResult matchAndRewrite(GenericOp genericOp,
1796                                 PatternRewriter &rewriter) const override {
1797     // Only apply to elementwise linalg on tensor.
1798     if (!genericOp.hasTensorSemantics() || genericOp.hasIndexSemantics() ||
1799         genericOp.getNumParallelLoops() != genericOp.getNumLoops())
1800       return failure();
1801     // Only support identity output maps. It could be extended to permuations if
1802     // needed.
1803     if (llvm::any_of(genericOp.getOutputOperands(), [&](OpOperand *opOperand) {
1804           return !genericOp.getTiedIndexingMap(opOperand).isIdentity();
1805         }))
1806       return failure();
1807     int64_t destRank = genericOp.getNumParallelLoops();
1808     SmallVector<Value> newOperands = genericOp.getInputOperands();
1809     tensor::ExpandShapeOp reshapeFound;
1810     // 1. Look for tensor_expand_shape operands and figure out save the
1811     // dimensions merged.
1812     SmallVector<OpOperand *> inputOperands = genericOp.getInputOperands();
1813     for (const auto &en : llvm::enumerate(inputOperands)) {
1814       auto reshapeOp =
1815           en.value()->get().template getDefiningOp<tensor::ExpandShapeOp>();
1816       if (!reshapeOp)
1817         continue;
1818       // TODO: We could support non-identity map as long as the merged
1819       // dimensions are still contiguous.
1820       if (!genericOp.getTiedIndexingMap(en.value()).isIdentity())
1821         continue;
1822       if (reshapeFound) {
1823         // Only support a second reshape op if it has the same reassociate maps.
1824         if (reshapeFound.getReassociationMaps() ==
1825             reshapeOp.getReassociationMaps())
1826           newOperands[en.index()] = reshapeOp.src();
1827         continue;
1828       }
1829       reshapeFound = reshapeOp;
1830       newOperands[en.index()] = reshapeOp.src();
1831     }
1832     if (!reshapeFound)
1833       return failure();
1834 
1835     // Calculate the reassociation indices and rassociated reverse map.
1836     SmallVector<ReassociationIndices> reassociation =
1837         getReassociationIndices(reshapeFound.getReassociationMaps());
1838     SmallVector<unsigned> remap(destRank);
1839     for (auto &indices : llvm::enumerate(reassociation)) {
1840       for (int64_t index : indices.value()) {
1841         remap[index] = indices.index();
1842       }
1843     }
1844     // 2. Verify that we can merge the dimensions in the linalg and that we
1845     // don't need to create new reshapes operands. Inserting new reshape
1846     // operands would defeat the purpose of the transformation.
1847     for (const auto &en : llvm::enumerate(inputOperands)) {
1848       if (en.value()->get() == newOperands[en.index()]) {
1849         AffineMap map = genericOp.getTiedIndexingMap(en.value());
1850         for (unsigned i : llvm::seq(unsigned(0), map.getNumResults())) {
1851           if (reassociation[remap[map.getDimPosition(i)]].size() > 1)
1852             return failure();
1853         }
1854       }
1855     }
1856 
1857     // 3. Calculate the affine map remapping and the reassociation to apply to
1858     // output tensors.
1859     SmallVector<AffineMap> newMaps;
1860     unsigned newRank = reassociation.size();
1861     for (auto map : genericOp.getIndexingMaps()) {
1862       SmallVector<AffineExpr> newExprs;
1863       for (auto expr : map.getResults()) {
1864         unsigned position = expr.template cast<AffineDimExpr>().getPosition();
1865         // Skip dimension merged except for the last of the group.
1866         if (reassociation[remap[position]].back() == position) {
1867           newExprs.push_back(
1868               getAffineDimExpr(remap[position], genericOp.getContext()));
1869         }
1870       }
1871       newMaps.push_back(
1872           AffineMap::get(newRank, 0, newExprs, genericOp.getContext()));
1873     }
1874 
1875     // 4. Reshape the output tensors.
1876     SmallVector<Value> newOutputs;
1877     SmallVector<Type> newOutputTypes;
1878     for (auto output : genericOp.outputs()) {
1879       auto newOutputType = RankedTensorType::get(
1880           reshapeFound.getSrcType().getShape(),
1881           output.getType().template cast<RankedTensorType>().getElementType());
1882       Value newOutput = rewriter.create<tensor::CollapseShapeOp>(
1883           genericOp->getLoc(), newOutputType, output, reassociation);
1884       newOutputTypes.push_back(newOutputType);
1885       newOutputs.push_back(newOutput);
1886     }
1887     // 5. Create a new generic op with lowerer rank.
1888     SmallVector<StringRef> iteratorTypes(newRank,
1889                                          getParallelIteratorTypeName());
1890     auto newOp = rewriter.create<GenericOp>(genericOp->getLoc(), newOutputTypes,
1891                                             newOperands, newOutputs, newMaps,
1892                                             iteratorTypes);
1893     rewriter.inlineRegionBefore(genericOp.region(), newOp.region(),
1894                                 newOp.region().begin());
1895     // 6. Reshape the so that the type matches the uses.
1896     SmallVector<Value> newResults;
1897     for (const auto &result : llvm::enumerate(newOp->getResults())) {
1898       newResults.push_back(rewriter.create<tensor::ExpandShapeOp>(
1899           genericOp->getLoc(), genericOp.getOutputTensorTypes()[result.index()],
1900           result.value(), reassociation));
1901     }
1902     rewriter.replaceOp(genericOp, newResults);
1903     return success();
1904   }
1905 };
1906 } // namespace
1907 
1908 //===---------------------------------------------------------------------===//
1909 // Methods and patterns that fuse constants with linalg.generic operations.
1910 //===---------------------------------------------------------------------===//
1911 
1912 namespace {
1913 /// Pattern to fold a generic op with a splat constant/scalar constant. Does not
1914 /// handle cases where the constant is not single-valued.
1915 class FoldScalarOrSplatConstant : public OpRewritePattern<GenericOp> {
1916 public:
1917   FoldScalarOrSplatConstant(MLIRContext *context, PatternBenefit benefit = 1)
1918       : OpRewritePattern<GenericOp>(context, benefit) {}
1919 
1920   LogicalResult matchAndRewrite(GenericOp genericOp,
1921                                 PatternRewriter &rewriter) const override {
1922     if (!genericOp.hasTensorSemantics())
1923       return failure();
1924     for (OpOperand *opOperand : genericOp.getInputOperands()) {
1925       Operation *def = opOperand->get().getDefiningOp();
1926       Attribute constantAttr;
1927       auto isScalarOrSplatConstantOp = [&constantAttr](Operation *def) -> bool {
1928         {
1929           DenseElementsAttr splatAttr;
1930           if (matchPattern(def, m_Constant<DenseElementsAttr>(&splatAttr)) &&
1931               splatAttr.isSplat() &&
1932               splatAttr.getType().getElementType().isIntOrFloat()) {
1933             constantAttr = splatAttr.getSplatValue<Attribute>();
1934             return true;
1935           }
1936         }
1937         {
1938           IntegerAttr intAttr;
1939           if (matchPattern(def, m_Constant<IntegerAttr>(&intAttr))) {
1940             constantAttr = intAttr;
1941             return true;
1942           }
1943         }
1944         {
1945           FloatAttr floatAttr;
1946           if (matchPattern(def, m_Constant<FloatAttr>(&floatAttr))) {
1947             constantAttr = floatAttr;
1948             return true;
1949           }
1950         }
1951         return false;
1952       };
1953 
1954       auto resultValue = opOperand->get().dyn_cast<OpResult>();
1955       if (!def || !resultValue || !isScalarOrSplatConstantOp(def))
1956         continue;
1957 
1958       // The operands and the indexing_maps of the fused operation the same as
1959       // the operands and indexing_maps of the generic operations with the
1960       // values at the constant index dropped.
1961       SmallVector<AffineMap> fusedIndexMaps;
1962       SmallVector<Value> fusedOperands;
1963       SmallVector<Location> fusedLocs{genericOp.getLoc()};
1964       fusedIndexMaps.reserve(genericOp.getNumInputsAndOutputs());
1965       fusedOperands.reserve(genericOp.getNumInputs());
1966       fusedLocs.reserve(fusedLocs.size() + genericOp.getNumInputs());
1967       for (OpOperand *inputOperand : genericOp.getInputOperands()) {
1968         if (inputOperand == opOperand)
1969           continue;
1970         Value inputValue = inputOperand->get();
1971         fusedIndexMaps.push_back(genericOp.getTiedIndexingMap(inputOperand));
1972         fusedOperands.push_back(inputValue);
1973         fusedLocs.push_back(inputValue.getLoc());
1974       }
1975       for (OpOperand *outputOperand : genericOp.getOutputOperands())
1976         fusedIndexMaps.push_back(genericOp.getTiedIndexingMap(outputOperand));
1977 
1978       // Check if the operation shapes to loops map is computable.
1979       if (!inversePermutation(concatAffineMaps(fusedIndexMaps))) {
1980         return rewriter.notifyMatchFailure(
1981             genericOp, "fused op loop bound computation failed");
1982       }
1983 
1984       // Create a constant scalar value from the splat constant.
1985       Value scalarConstant = rewriter.create<arith::ConstantOp>(
1986           def->getLoc(), constantAttr, constantAttr.getType());
1987 
1988       SmallVector<Value> outputOperands = genericOp.getOutputOperands();
1989       auto fusedOp = rewriter.create<GenericOp>(
1990           rewriter.getFusedLoc(fusedLocs), genericOp->getResultTypes(),
1991           /*inputs=*/fusedOperands,
1992           /*outputs=*/outputOperands,
1993           rewriter.getAffineMapArrayAttr(fusedIndexMaps),
1994           genericOp.iterator_types(),
1995           /*doc=*/nullptr,
1996           /*library_call=*/nullptr);
1997 
1998       // Map the block argument corresponding to the replaced argument with the
1999       // scalar constant.
2000       Region &region = genericOp->getRegion(0);
2001       Block &entryBlock = *region.begin();
2002       BlockAndValueMapping mapping;
2003       mapping.map(entryBlock.getArgument(opOperand->getOperandNumber()),
2004                   scalarConstant);
2005       Region &fusedRegion = fusedOp->getRegion(0);
2006       rewriter.cloneRegionBefore(region, fusedRegion, fusedRegion.begin(),
2007                                  mapping);
2008       rewriter.replaceOp(genericOp, fusedOp->getResults());
2009       return success();
2010     }
2011     return failure();
2012   }
2013 };
2014 
2015 } // namespace
2016 
2017 //===---------------------------------------------------------------------===//
2018 // Miscellaneous patterns that help fusion.
2019 //===---------------------------------------------------------------------===//
2020 
2021 namespace {
2022 /// Forces `outs` operands of linalg operations to use `linalg.init_tensor` if
2023 /// the value of the `outs` operand is not used within the op.  This is only
2024 /// implemented for `linalg.generic` operations for now, but should hold for all
2025 /// linalg structured ops.
2026 struct RemoveOutsDependency : public OpRewritePattern<GenericOp> {
2027   using OpRewritePattern<GenericOp>::OpRewritePattern;
2028 
2029   LogicalResult matchAndRewrite(GenericOp op,
2030                                 PatternRewriter &rewriter) const override {
2031     rewriter.startRootUpdate(op);
2032     bool modifiedOutput = false;
2033     Location loc = op.getLoc();
2034     for (OpOperand *opOperand : op.getOutputOperands()) {
2035       if (!op.payloadUsesValueFromOperand(opOperand)) {
2036         Value operandVal = opOperand->get();
2037         auto operandType = operandVal.getType().dyn_cast<RankedTensorType>();
2038         if (!operandType)
2039           continue;
2040 
2041         // If outs is sparse, leave it to the sparse compiler.
2042         if (sparse_tensor::getSparseTensorEncoding(operandVal.getType()))
2043           continue;
2044 
2045         // If outs is already an `init_tensor` operation, nothing to do.
2046         auto definingOp = operandVal.getDefiningOp<InitTensorOp>();
2047         if (definingOp)
2048           continue;
2049         modifiedOutput = true;
2050         SmallVector<Value> dynamicDims;
2051         for (const auto &dim : llvm::enumerate(operandType.getShape())) {
2052           if (dim.value() != ShapedType::kDynamicSize)
2053             continue;
2054           dynamicDims.push_back(rewriter.createOrFold<tensor::DimOp>(
2055               loc, operandVal, dim.index()));
2056         }
2057         Value initTensor = rewriter.create<InitTensorOp>(
2058             loc, dynamicDims, operandType.getShape(),
2059             operandType.getElementType());
2060         op->setOperand(opOperand->getOperandNumber(), initTensor);
2061       }
2062     }
2063     if (!modifiedOutput) {
2064       rewriter.cancelRootUpdate(op);
2065       return failure();
2066     }
2067     rewriter.finalizeRootUpdate(op);
2068     return success();
2069   }
2070 };
2071 
2072 /// Fold linalg.fill into linalg.generic
2073 struct FoldFillWithGenericOp : public OpRewritePattern<GenericOp> {
2074   using OpRewritePattern<GenericOp>::OpRewritePattern;
2075 
2076   LogicalResult matchAndRewrite(GenericOp genericOp,
2077                                 PatternRewriter &rewriter) const override {
2078     if (!genericOp.hasTensorSemantics())
2079       return failure();
2080     bool fillFound = false;
2081     Block &payload = genericOp.region().front();
2082     for (OpOperand *opOperand : genericOp.getInputOperands()) {
2083       if (!genericOp.payloadUsesValueFromOperand(opOperand))
2084         continue;
2085       FillOp fillOp = opOperand->get().getDefiningOp<FillOp>();
2086       if (!fillOp)
2087         continue;
2088       fillFound = true;
2089       payload.getArgument(opOperand->getOperandNumber())
2090           .replaceAllUsesWith(fillOp.value());
2091     }
2092     return success(fillFound);
2093   }
2094 };
2095 } // namespace
2096 //===---------------------------------------------------------------------===//
2097 // Methods that add patterns described in this file to a pattern list.
2098 //===---------------------------------------------------------------------===//
2099 
2100 void mlir::linalg::populateFoldReshapeOpsByLinearizationPatterns(
2101     RewritePatternSet &patterns) {
2102   patterns.add<
2103       FoldProducerReshapeOpByLinearization<false, tensor::CollapseShapeOp>,
2104       FoldProducerReshapeOpByLinearization<false, tensor::ExpandShapeOp>,
2105       FoldConsumerReshapeOpByLinearization<false, tensor::CollapseShapeOp>>(
2106       patterns.getContext());
2107 }
2108 
2109 void mlir::linalg::populateFoldUnitDimsReshapeOpsByLinearizationPatterns(
2110     RewritePatternSet &patterns) {
2111   patterns
2112       .add<FoldProducerReshapeOpByLinearization<true, tensor::CollapseShapeOp>,
2113            FoldProducerReshapeOpByLinearization<true, tensor::ExpandShapeOp>,
2114            FoldConsumerReshapeOpByLinearization<true, tensor::CollapseShapeOp>>(
2115           patterns.getContext());
2116 }
2117 
2118 void mlir::linalg::populateFoldReshapeOpsByExpansionPatterns(
2119     RewritePatternSet &patterns,
2120     const ControlFusionFn &controlFoldingReshapes) {
2121   patterns.add<FoldReshapeWithGenericOpByExpansion>(patterns.getContext(),
2122                                                     controlFoldingReshapes);
2123   patterns.add<FoldWithProducerReshapeOpByExpansion>(patterns.getContext(),
2124                                                      controlFoldingReshapes);
2125 }
2126 
2127 void mlir::linalg::populateFoldReshapeOpsByCollapsingPatterns(
2128     RewritePatternSet &patterns,
2129     const ControlFusionFn &controlFoldingReshapes) {
2130   patterns.add<FoldWithProducerReshapeOpByCollapsing>(patterns.getContext(),
2131                                                       controlFoldingReshapes);
2132 }
2133 
2134 void mlir::linalg::populateElementwiseOpsFusionPatterns(
2135     RewritePatternSet &patterns,
2136     const ControlFusionFn &controlElementwiseOpsFusion) {
2137   auto *context = patterns.getContext();
2138   patterns.add<FuseElementwiseOps>(context, controlElementwiseOpsFusion);
2139   patterns.add<FoldFillWithGenericOp, FoldScalarOrSplatConstant,
2140                RemoveOutsDependency>(context);
2141 }
2142 
2143 void mlir::linalg::populatePushReshapeOpsPatterns(RewritePatternSet &patterns) {
2144   auto *context = patterns.getContext();
2145   patterns.add<PushExpandingReshape>(context);
2146 }
2147 
2148 //===---------------------------------------------------------------------===//
2149 // Passes
2150 //===---------------------------------------------------------------------===//
2151 
2152 bool mlir::linalg::skipUnitDimReshape(const OpResult &producer,
2153                                       OpOperand &consumer) {
2154   if (auto producerCollapseOp =
2155           dyn_cast<tensor::CollapseShapeOp>(producer.getOwner())) {
2156     return !isUnitDimExpansionOnly(producerCollapseOp);
2157   }
2158   if (auto consumerExpandOp =
2159           dyn_cast<tensor::ExpandShapeOp>(consumer.getOwner())) {
2160     return !isUnitDimExpansionOnly(consumerExpandOp);
2161   }
2162   return true;
2163 }
2164 
2165 namespace {
2166 
2167 /// Pass that fuses generic ops on tensors. Used only for testing.
2168 // TODO(ravishankarm): This pass is to be deprecated. The efficacy of the
2169 // patterns added here heavily depends on the cost function used. Having an
2170 // opinionated pass of this form is not recommended. Deprecate this pass in
2171 // favor of test passes that check the functionality of each of the patterns
2172 // added here individually.
2173 struct LinalgElementwiseOpFusionPass
2174     : public LinalgElementwiseOpFusionBase<LinalgElementwiseOpFusionPass> {
2175   void runOnOperation() override {
2176     Operation *op = getOperation();
2177     MLIRContext *context = op->getContext();
2178     RewritePatternSet patterns(context);
2179 
2180     // Add folding with reshape by expansion patterns.
2181     ControlFusionFn defaultControlFn = [](const OpResult &producer,
2182                                           const OpOperand &consumer) {
2183       return producer.hasOneUse();
2184     };
2185 
2186     // Add elementwise op fusion patterns.
2187     populateElementwiseOpsFusionPatterns(patterns, defaultControlFn);
2188 
2189     populateFoldReshapeOpsByExpansionPatterns(
2190         patterns,
2191         allowFoldingUnitDimReshapes ? defaultControlFn : skipUnitDimReshape);
2192 
2193     // Add the sparse tensor rewriting patterns.
2194     populateSparseTensorRewriting(patterns);
2195 
2196     // General canonicalization patterns.
2197     AffineApplyOp::getCanonicalizationPatterns(patterns, context);
2198     GenericOp::getCanonicalizationPatterns(patterns, context);
2199     tensor::ExpandShapeOp::getCanonicalizationPatterns(patterns, context);
2200     tensor::CollapseShapeOp::getCanonicalizationPatterns(patterns, context);
2201     context->getLoadedDialect<LinalgDialect>()->getCanonicalizationPatterns(
2202         patterns);
2203 
2204     // Add constant folding patterns.
2205     populateConstantFoldLinalgOperations(patterns, defaultControlFn);
2206 
2207     // Use TopDownTraversal for compile time reasons
2208     GreedyRewriteConfig grc;
2209     grc.useTopDownTraversal = true;
2210     (void)applyPatternsAndFoldGreedily(op->getRegions(), std::move(patterns),
2211                                        grc);
2212   }
2213 };
2214 
2215 /// Pass to test folding of reshape ops with generic ops by linearization.
2216 struct FoldReshapeOpsByLinearizationPass
2217     : public LinalgFoldReshapeOpsByLinearizationBase<
2218           FoldReshapeOpsByLinearizationPass> {
2219   void runOnOperation() override {
2220     Operation *op = getOperation();
2221     RewritePatternSet patterns(op->getContext());
2222     populateFoldReshapeOpsByLinearizationPatterns(patterns);
2223     if (allowFoldingUnitDimReshapes) {
2224       populateFoldUnitDimsReshapeOpsByLinearizationPatterns(patterns);
2225     }
2226     (void)applyPatternsAndFoldGreedily(op->getRegions(), std::move(patterns));
2227   }
2228 };
2229 
2230 } // namespace
2231 
2232 std::unique_ptr<Pass> mlir::createLinalgElementwiseOpFusionPass() {
2233   return std::make_unique<LinalgElementwiseOpFusionPass>();
2234 }
2235 
2236 std::unique_ptr<Pass> mlir::createFoldReshapeOpsByLinearizationPass() {
2237   return std::make_unique<FoldReshapeOpsByLinearizationPass>();
2238 }
2239