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