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