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