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