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 
24 using namespace mlir;
25 using namespace mlir::linalg;
26 
27 namespace {
28 
29 /// Implementation of fusion of generic ops and indexed_generic ops.
30 struct FuseGenericOpsOnTensors {
31   static bool isFusible(LinalgOp producer, LinalgOp consumer,
32                         unsigned consumerIdx) {
33     // Producer and consumer must have tensor semantics.
34     if (!producer.hasTensorSemantics() || !consumer.hasTensorSemantics())
35       return false;
36 
37     // Verify that
38     // - the producer has all "parallel" iterator type.
39     if (producer.getNumParallelLoops() != producer.getNumLoops())
40       return false;
41 
42     // Get the consumer index map. The number of results of the consumer index
43     // map must match the number of loops of the producer.
44     AffineMap consumerIndexMap = consumer.getIndexingMap(consumerIdx);
45     if (consumerIndexMap.getNumResults() != producer.getNumLoops())
46       return false;
47 
48     // Finally the index_map for the result must be invertible. For now just
49     // verify it is a permutation.
50     AffineMap producerResultIndexMap = producer.getOutputIndexingMap(0);
51     return producerResultIndexMap.isPermutation();
52   }
53 
54   static LinalgOp fuse(LinalgOp producer, LinalgOp consumer,
55                        unsigned consumerIdx, PatternRewriter &rewriter,
56                        OperationFolder *folder = nullptr) {
57     if (!isFusible(producer, consumer, consumerIdx))
58       return nullptr;
59 
60     unsigned numFusedOperands = producer.getOperation()->getNumOperands() +
61                                 consumer.getOperation()->getNumOperands() - 1;
62 
63     // Compute the fused operands list,
64     SmallVector<Value, 2> fusedOperands;
65     fusedOperands.reserve(numFusedOperands);
66     auto consumerOperands = consumer.getOperation()->getOperands();
67     auto producerOperands = producer.getOperation()->getOperands();
68     fusedOperands.assign(consumerOperands.begin(),
69                          std::next(consumerOperands.begin(), consumerIdx));
70     fusedOperands.append(producerOperands.begin(), producerOperands.end());
71     fusedOperands.append(std::next(consumerOperands.begin(), consumerIdx + 1),
72                          consumerOperands.end());
73 
74     // Compute indexing_maps for the fused operation. The indexing_maps for the
75     // operands of the consumers that arent fused are the same. The
76     // indexing_maps for the producers need to be computed based on the
77     // indexing_map of the operand at consumerIdx in the consumer.
78     SmallVector<Attribute, 4> fusedIndexMaps;
79     auto consumerIndexMaps = consumer.indexing_maps();
80     fusedIndexMaps.reserve(fusedOperands.size() +
81                            consumer.getOperation()->getNumResults());
82     fusedIndexMaps.assign(consumerIndexMaps.begin(),
83                           std::next(consumerIndexMaps.begin(), consumerIdx));
84     // Compute indexing maps for the producer args in the fused operation.
85     computeProducerOperandIndex(
86         producer, consumer.getInputIndexingMap(consumerIdx), fusedIndexMaps);
87 
88     // Append the indexing maps for the remaining consumer operands.
89     fusedIndexMaps.append(std::next(consumerIndexMaps.begin(), consumerIdx + 1),
90                           consumerIndexMaps.end());
91 
92     // Generate the fused op.
93     // Tensor-level fusion is only on ops without initTensors and outputBuffers.
94     LinalgOp fusedOp;
95     if (isa<GenericOp>(producer.getOperation()) &&
96         isa<GenericOp>(consumer.getOperation())) {
97       fusedOp =
98           rewriter
99               .create<GenericOp>(consumer.getLoc(),
100                                  consumer.getOperation()->getResultTypes(),
101                                  /*inputs=*/fusedOperands,
102                                  /*outputBuffers=*/ValueRange{},
103                                  /*initTensors=*/ValueRange{},
104                                  rewriter.getArrayAttr(fusedIndexMaps),
105                                  consumer.iterator_types(),
106                                  /*doc=*/nullptr,
107                                  /*library_call=*/nullptr,
108                                  /*symbol_source=*/nullptr)
109               .getOperation();
110     } else {
111       fusedOp =
112           rewriter
113               .create<IndexedGenericOp>(
114                   consumer.getLoc(), consumer.getOperation()->getResultTypes(),
115                   /*inputs=*/fusedOperands,
116                   /*outputBuffers=*/ValueRange{},
117                   /*initTensors=*/ValueRange{},
118                   rewriter.getArrayAttr(fusedIndexMaps),
119                   consumer.iterator_types(),
120                   /*doc=*/nullptr,
121                   /*library_call=*/nullptr,
122                   /*symbol_source=*/nullptr)
123               .getOperation();
124     }
125 
126     // Construct an AffineMap from consumer loops to producer loops.
127     // consumer loop -> tensor index
128     AffineMap consumerResultIndexMap =
129         consumer.getInputIndexingMap(consumerIdx);
130     // producer loop -> tensor index
131     AffineMap producerResultIndexMap = producer.getOutputIndexingMap(0);
132     // tensor index -> producer loop
133     AffineMap invProducerResultIndexMap =
134         inversePermutation(producerResultIndexMap);
135     assert(invProducerResultIndexMap &&
136            "expected producer result indexig map to be invertible");
137     // consumer loop -> producer loop
138     AffineMap consumerToProducerLoopsMap =
139         invProducerResultIndexMap.compose(consumerResultIndexMap);
140 
141     generateFusedRegion(rewriter, fusedOp, producer, consumer,
142                         consumerToProducerLoopsMap, consumerIdx,
143                         consumer.getNumLoops());
144     return fusedOp;
145   }
146 
147 private:
148   /// Append to `fusedOpIndexingMapAttrs` the indexing maps for the operands of
149   /// the `producer` to use in the fused operation given the indexing map of the
150   /// result of the producer in the consumer.
151   static void computeProducerOperandIndex(
152       LinalgOp producer, AffineMap fusedConsumerArgIndexMap,
153       SmallVectorImpl<Attribute> &fusedOpIndexingMapAttrs) {
154     // The indexing map in the consumer op (fusedConsumerArgIndexMap) is a map
155     // from consumer loop -> consumer arg tensor index/producer result tensor
156     // index. The fused loop is same as the consumer loop. For each producer arg
157     // the indexing map to be computed is a map from consumer loop -> producer
158     // arg tensor index.
159 
160     AffineMap producerResultIndexMap = producer.getOutputIndexingMap(0);
161     // producerResultIndexMap is a map from producer loop -> tensor index.
162     // Compute the inverse to get map from tensor index -> producer loop.
163     // The inverse is a map from producer result tensor index -> producer loop.
164     AffineMap invProducerResultIndexMap =
165         inversePermutation(producerResultIndexMap);
166     assert(invProducerResultIndexMap &&
167            "expected producer result indexig map to be invertible");
168     for (unsigned argNum : llvm::seq<unsigned>(0, producer.getNumInputs())) {
169       // argMap is a map from producer loop -> producer arg tensor index.
170       AffineMap argMap = producer.getInputIndexingMap(argNum);
171 
172       // Compose argMap with invProducerResultIndexMap to get a map from
173       // producer result tensor index -> producer arg tensor index.
174       AffineMap t1 = argMap.compose(invProducerResultIndexMap);
175 
176       // Compose t1 with fusedConsumerArgIndexMap gives an indexing map from
177       // consumer loop/ fused loop -> producer arg tensor index.
178       AffineMap indexingMap = t1.compose(fusedConsumerArgIndexMap);
179       fusedOpIndexingMapAttrs.push_back(AffineMapAttr::get(indexingMap));
180     }
181   }
182 
183   /// Generate the region of the fused operation. The region of the fused op
184   /// must be empty.
185   static void generateFusedRegion(PatternRewriter &rewriter, Operation *fusedOp,
186                                   LinalgOp producer, LinalgOp consumer,
187                                   AffineMap consumerToProducerLoopsMap,
188                                   unsigned consumerIdx, unsigned nloops) {
189     // Build the region of the fused op.
190     Block &producerBlock = producer.getOperation()->getRegion(0).front();
191     Block &consumerBlock = consumer.getOperation()->getRegion(0).front();
192     Block *fusedBlock = new Block();
193     fusedOp->getRegion(0).push_back(fusedBlock);
194     BlockAndValueMapping mapper;
195     OpBuilder::InsertionGuard guard(rewriter);
196     rewriter.setInsertionPointToStart(fusedBlock);
197 
198     // The block arguments are
199     // [index_0, index_1, ... ,
200     //   consumer_operand_0, ... , consumer_operand_(`consumerIdx`-1),
201     //   producer_operand_0, ... , producer_operand_(n-1)],
202     //   consumer_operand_(`consumerIdx`), .. consumer_operand_(m-1)]
203     // , where n is the number of producer's operand and m is the number
204     // consumer's operand.
205     // If both `numProducerIndices` and `numConsumerIndices` are zero, this is a
206     // generic op. In this case, there are no indices in block arguments.
207     unsigned numProducerIndices =
208         isa<IndexedGenericOp>(producer.getOperation()) ? nloops : 0;
209     unsigned numConsumerIndices =
210         isa<IndexedGenericOp>(consumer.getOperation()) ? nloops : 0;
211     // Firstly, add all the indices to the block arguments.
212     for (unsigned i = 0, e = std::max(numProducerIndices, numConsumerIndices);
213          i < e; ++i)
214       fusedBlock->addArgument(rewriter.getIndexType());
215     // Map the arguments for the unmodified args from the consumer.
216     for (auto consumerArg : llvm::enumerate(consumerBlock.getArguments())) {
217       if (consumerArg.index() == consumerIdx + numConsumerIndices) {
218         // Map the arguments for the args from the producer.
219         for (auto producerArg : llvm::enumerate(producerBlock.getArguments())) {
220           // If producer is an indexed_generic op, map the indices from consumer
221           // loop to producer loop (because the fusedOp is built based on
222           // consumer's perspective).
223           if (producerArg.index() < numProducerIndices) {
224             auto newIndex = rewriter.create<mlir::AffineApplyOp>(
225                 producer.getLoc(),
226                 consumerToProducerLoopsMap.getSubMap(producerArg.index()),
227                 fusedBlock->getArguments().take_front(nloops));
228             mapper.map(producerArg.value(), newIndex);
229           } else {
230             mapper.map(producerArg.value(),
231                        fusedBlock->addArgument(producerArg.value().getType()));
232           }
233         }
234         continue;
235       }
236 
237       // If consumer is an indexed_generic op, map the indices to the block
238       // arguments directly. Otherwise, add the same type of arugment and map to
239       // it.
240       if (consumerArg.index() < numConsumerIndices) {
241         mapper.map(consumerArg.value(),
242                    fusedBlock->getArgument(consumerArg.index()));
243       } else {
244         mapper.map(consumerArg.value(),
245                    fusedBlock->addArgument(consumerArg.value().getType()));
246       }
247     }
248 
249     // Add operations from producer (except the yield operation) to the fused
250     // op.
251     for (auto &op : producerBlock.getOperations()) {
252       if (auto yieldOp = dyn_cast<linalg::YieldOp>(op)) {
253         // Lookup the value the yield operation is mapped to.
254         Value yieldVal = yieldOp.getOperand(0);
255         if (Value clonedVal = mapper.lookupOrNull(yieldVal))
256           mapper.map(
257               consumerBlock.getArgument(consumerIdx + numConsumerIndices),
258               clonedVal);
259         continue;
260       }
261       rewriter.clone(op, mapper);
262     }
263     for (auto &op : consumerBlock.getOperations())
264       rewriter.clone(op, mapper);
265   }
266 };
267 } // namespace
268 
269 /// Linearize the expressions in `sourceMap` based on the `reassociationMaps`
270 /// provided, given the shape of the source tensor that corresponds to the
271 /// `sourceMap`. Note that this implicitly assumes that the tensors dimensions
272 /// are "row-major" ordered logically.
273 ///
274 /// For example:
275 ///
276 /// %0 = op ... : tensor<?x?x4x5xf32>
277 /// with output index_map `affine_map<(d0, d1, d2, d3) -> (d0, d1, d2, d3)>`
278 ///
279 /// and reshape:
280 /// %1 = linalg.tensor_reshape %0 [affine_map<(i, j, k, l) -> (i)>,
281 ///                                affine_map<(i, j, k, l) -> (j, k, l)>] :
282 ///        tensor<?x?x4x5xf32> into tensor<?x?xf32>
283 ///
284 /// would be rewritten into:
285 /// %0 = op ... : tensor<?x?x4x5xf32>
286 /// with output index_map
287 ///   `affine_map<(d0, d1, d2, d3) -> (d0, d1 * 20 + d2 * 5 + d3)>`
288 static AffineMap linearizeCollapsedDims(AffineMap sourceMap,
289                                         ArrayRef<int64_t> sourceShape,
290                                         ArrayRef<AffineMap> reassociationMaps) {
291   SmallVector<AffineExpr, 4> resultExprs;
292   resultExprs.reserve(reassociationMaps.size());
293   ArrayRef<AffineExpr> sourceExprs = sourceMap.getResults();
294   MLIRContext *context = sourceMap.getContext();
295 
296   // Compute the result exprs based on the reassociation maps.
297   for (AffineMap map : reassociationMaps) {
298     ArrayRef<AffineExpr> collapsedDims = map.getResults();
299     // Assume that they are in-order and contiguous (already checked in
300     // verifier).
301     assert(!collapsedDims.empty());
302     unsigned startDim =
303         collapsedDims.front().cast<AffineDimExpr>().getPosition();
304     AffineExpr linearizedExpr = makeCanonicalStridedLayoutExpr(
305         sourceShape.slice(startDim, collapsedDims.size()),
306         sourceExprs.slice(startDim, collapsedDims.size()), context);
307     resultExprs.push_back(linearizedExpr);
308   }
309   return AffineMap::get(sourceMap.getNumDims(), sourceMap.getNumSymbols(),
310                         resultExprs, context);
311 }
312 
313 /// Checks if the `reshapeOp` can be fused with it consumer (if `asProducer` is
314 /// true) or its producer (if `asProducer` is false) given the indexing map at
315 /// its use.
316 static bool isTensorReshapeOpFusible(TensorReshapeOp reshapeOp,
317                                      AffineMap useIndexMap, bool asProducer) {
318   RankedTensorType returnType = reshapeOp.getResultType();
319   RankedTensorType operandType = reshapeOp.getSrcType();
320   // Reshape is fusible with its consumer (i.e. reshape as a producer) when its
321   // operand is of lesser rank than the result. Fusing when operand has higher
322   // rank will require use of mods and divs in the indexing maps of the fused op
323   // which would make it non-invertible. Similarly reshape is fused with its
324   // producer (i.e. reshape as consumer) only if the return type has lesser
325   // rank.
326   if ((asProducer && returnType.getRank() < operandType.getRank()) ||
327       (!asProducer && operandType.getRank() < returnType.getRank()))
328     return false;
329   return useIndexMap.isIdentity();
330 }
331 
332 /// Based on the type of `op` create a linalg op of the same type, i.e. if `op`
333 /// is a linalg.generic operation, the create a `linalg.generic` operation with
334 /// the given `args`. Expects `op` to be `linalg.generic` or
335 /// `linalg.indexed_generic`.
336 template <typename... Args>
337 static LinalgOp createLinalgOpOfSameType(LinalgOp op, PatternRewriter &rewriter,
338                                          Args... args) {
339   if (isa<GenericOp>(op.getOperation()))
340     return cast<LinalgOp>(rewriter.create<GenericOp>(args...).getOperation());
341   if (isa<IndexedGenericOp>(op.getOperation()))
342     return cast<LinalgOp>(
343         rewriter.create<IndexedGenericOp>(args...).getOperation());
344   llvm_unreachable(
345       "expected only linalg.generic or linalg.indexed_generic ops");
346   return nullptr;
347 }
348 
349 namespace {
350 
351 /// Implementation of fusion on tensor ops when producer is a TensorReshapeOp.
352 struct FuseTensorReshapeOpAsProducer {
353   static bool isFusible(TensorReshapeOp producer, LinalgOp consumer,
354                         unsigned consumerIdx) {
355     return isa<GenericOp, IndexedGenericOp>(consumer.getOperation()) &&
356            consumer.hasTensorSemantics() &&
357            isTensorReshapeOpFusible(producer,
358                                     consumer.getInputIndexingMap(consumerIdx),
359                                     /*asProducer=*/true);
360   }
361 
362   static LinalgOp fuse(TensorReshapeOp producer, LinalgOp consumer,
363                        unsigned consumerIdx, PatternRewriter &rewriter,
364                        OperationFolder *folder = nullptr) {
365     if (producer.src().getDefiningOp<ConstantOp>())
366       return nullptr;
367 
368     if (!isFusible(producer, consumer, consumerIdx))
369       return nullptr;
370 
371     // Compute the fused operands list,
372     Operation *consumerOp = consumer.getOperation();
373     SmallVector<Value, 2> fusedOperands(consumerOp->getOperands());
374     fusedOperands[consumerIdx] = producer.src();
375 
376     // Compute indexing_maps for the fused operation. The indexing_maps for the
377     // operands of the consumers that arent fused are the same.
378     SmallVector<AffineMap, 4> fusedIndexMaps =
379         llvm::to_vector<4>(llvm::map_range(
380             consumer.indexing_maps(), [](Attribute attr) -> AffineMap {
381               return attr.cast<AffineMapAttr>().getValue();
382             }));
383 
384     // Compute the indexing map to use for the operand of the producer.
385     AffineMap modifiedMap = linearizeCollapsedDims(
386         fusedIndexMaps[consumerIdx], producer.getResultType().getShape(),
387         producer.getReassociationMaps());
388     for (AffineExpr expr : modifiedMap.getResults()) {
389       if (!expr.isPureAffine())
390         return nullptr;
391     }
392     fusedIndexMaps[consumerIdx] = modifiedMap;
393 
394     // Further check that the resulting index maps can be fused and
395     // inverted. Without this the resultant op is not legal.
396     if (!inversePermutation(concatAffineMaps(fusedIndexMaps)))
397       return nullptr;
398 
399     SmallVector<Attribute, 4> indexMapAttrs = llvm::to_vector<4>(
400         llvm::map_range(fusedIndexMaps, [](AffineMap map) -> Attribute {
401           return AffineMapAttr::get(map);
402         }));
403     LinalgOp fusedOp = createLinalgOpOfSameType(
404         consumer, rewriter, rewriter.getUnknownLoc(),
405         consumerOp->getResultTypes(),
406         /*inputs=*/fusedOperands,
407         /*outputBuffers=*/ValueRange{},
408         /*initTensors=*/ValueRange{}, // no init tensors for now.
409         rewriter.getArrayAttr(indexMapAttrs), consumer.iterator_types(),
410         /*doc=*/nullptr,
411         /*library_call=*/nullptr,
412         /*symbol_source=*/nullptr);
413     auto &fusedRegion = fusedOp.getOperation()->getRegion(0);
414     rewriter.cloneRegionBefore(consumerOp->getRegion(0), fusedRegion,
415                                fusedRegion.begin());
416     return fusedOp;
417   }
418 };
419 
420 /// Implementation of fusion on tensor ops when consumer is a TensorReshapeOp.
421 struct FuseTensorReshapeOpAsConsumer {
422   static bool isCollapsingAndFusible(LinalgOp producer,
423                                      TensorReshapeOp consumer,
424                                      unsigned consumerIdx) {
425     return isa<GenericOp, IndexedGenericOp>(producer.getOperation()) &&
426            producer.hasTensorSemantics() &&
427            isTensorReshapeOpFusible(consumer, producer.getOutputIndexingMap(0),
428                                     /*asProducer=*/false);
429   }
430 
431   static LinalgOp fuseCollapsingCase(LinalgOp producer,
432                                      TensorReshapeOp consumer,
433                                      unsigned consumerIdx,
434                                      PatternRewriter &rewriter) {
435     // The indexing_maps for the operands of the fused operation are same as
436     // those for the operands of the producer.
437     SmallVector<AffineMap, 4> fusedIndexMaps =
438         llvm::to_vector<4>(llvm::map_range(
439             producer.indexing_maps(), [](Attribute attr) -> AffineMap {
440               return attr.cast<AffineMapAttr>().getValue();
441             }));
442     // Compute the indexing map to use for the operand of the producer.
443     AffineMap modifiedMap = linearizeCollapsedDims(
444         producer.getOutputIndexingMap(0), consumer.getSrcType().getShape(),
445         consumer.getReassociationMaps());
446     for (AffineExpr expr : modifiedMap.getResults()) {
447       if (!expr.isPureAffine())
448         return nullptr;
449     }
450     fusedIndexMaps.back() = modifiedMap;
451 
452     // Further check that the resulting index maps can be fused and
453     // inverted. Without this the resultant op is not legal.
454     if (!inversePermutation(concatAffineMaps(fusedIndexMaps)))
455       return nullptr;
456 
457     SmallVector<Attribute, 4> indexMapAttrs = llvm::to_vector<4>(
458         llvm::map_range(fusedIndexMaps, [](AffineMap map) -> Attribute {
459           return AffineMapAttr::get(map);
460         }));
461 
462     Operation *producerOp = producer.getOperation();
463     LinalgOp fusedOp = createLinalgOpOfSameType(
464         producer, rewriter, rewriter.getUnknownLoc(), consumer.getResultType(),
465         /*inputs=*/producerOp->getOperands(),
466         /*outputBuffers=*/ValueRange{},
467         /*initTensors=*/ValueRange{}, // no init tensors for now.
468         rewriter.getArrayAttr(indexMapAttrs), producer.iterator_types(),
469         /*doc=*/nullptr,
470         /*library_call=*/nullptr,
471         /*symbol_source=*/nullptr);
472     auto &fusedRegion = fusedOp.getOperation()->getRegion(0);
473     rewriter.cloneRegionBefore(producerOp->getRegion(0), fusedRegion,
474                                fusedRegion.begin());
475     return fusedOp;
476   }
477 
478   static bool isExpandingAndFusible(LinalgOp producer, TensorReshapeOp consumer,
479                                     unsigned consumerIdx) {
480     // Is fusible only if:
481     //   1) The producer is a generic op.
482     //   2) The producer has tensor semantics.
483     //   3) The tensor reshape op is a expanding case.
484     //   4) All the shapes are the same for the generic op.
485     //   5) All the indexing maps in producer are identity.
486     //   6) All the loops in producer are parallel loops.
487     //   7) The producer has a single user.
488     auto types = producer.getInputOutputShapedTypes();
489     assert(!types.empty());
490     return isa<GenericOp>(producer.getOperation()) &&
491            producer.hasTensorSemantics() &&
492            consumer.getSrcType().getRank() <
493                consumer.getResultType().getRank() &&
494            std::equal(types.begin() + 1, types.end(), types.begin()) &&
495            llvm::all_of(producer.getIndexingMaps(),
496                         [](AffineMap map) { return map.isIdentity(); }) &&
497            llvm::all_of(producer.iterator_types(),
498                         [](Attribute attr) {
499                           return attr.cast<StringAttr>().getValue() ==
500                                  getParallelIteratorTypeName();
501                         }) &&
502            producer.getOperation()->hasOneUse();
503   }
504 
505   static LinalgOp fuseExpandingCase(LinalgOp producer, TensorReshapeOp consumer,
506                                     unsigned consumerIdx,
507                                     PatternRewriter &rewriter) {
508     Location loc = producer.getLoc();
509     auto dstShape = consumer.getResultType().cast<ShapedType>().getShape();
510     SmallVector<Value, 4> args;
511     for (auto arg : producer.getOperation()->getOperands()) {
512       auto type = RankedTensorType::get(
513           dstShape, arg.getType().cast<ShapedType>().getElementType());
514       args.push_back(rewriter.createOrFold<linalg::TensorReshapeOp>(
515           loc, type, arg, consumer.reassociation()));
516     }
517 
518     SmallVector<Type, 4> resultTypes;
519     for (auto t : producer.getOutputTensorTypes()) {
520       Type type = RankedTensorType::get(dstShape,
521                                         t.cast<ShapedType>().getElementType());
522       resultTypes.push_back(type);
523     }
524 
525     int rank = dstShape.size();
526     auto genericOp = rewriter.create<linalg::GenericOp>(
527         loc, resultTypes, /*inputs=*/args,
528         /*outputBuffers=*/ValueRange{},
529         /*initTensors=*/ValueRange{},
530         SmallVector<AffineMap, 3>(args.size() + resultTypes.size(),
531                                   rewriter.getMultiDimIdentityMap(rank)),
532         SmallVector<StringRef, 3>(rank, getParallelIteratorTypeName()));
533     Region &region = genericOp.getRegion();
534     rewriter.cloneRegionBefore(producer.getOperation()->getRegion(0), region,
535                                region.begin());
536     return cast<LinalgOp>(genericOp.getOperation());
537   }
538 
539   static LinalgOp fuse(LinalgOp producer, TensorReshapeOp consumer,
540                        unsigned consumerIdx, PatternRewriter &rewriter,
541                        OperationFolder *folder = nullptr) {
542     if (isCollapsingAndFusible(producer, consumer, consumerIdx))
543       return fuseCollapsingCase(producer, consumer, consumerIdx, rewriter);
544     if (isExpandingAndFusible(producer, consumer, consumerIdx))
545       return fuseExpandingCase(producer, consumer, consumerIdx, rewriter);
546     return nullptr;
547   }
548 };
549 
550 /// Implementation of fusion on tensor ops when producer is a splat constant.
551 struct FuseConstantOpAsProducer {
552   static bool isFusible(ConstantOp producer, LinalgOp consumer,
553                         unsigned consumerIdx) {
554     return isa<GenericOp, IndexedGenericOp>(consumer.getOperation()) &&
555            consumer.hasTensorSemantics() &&
556            producer.getResult().getType().isa<RankedTensorType>() &&
557            producer.value().cast<DenseElementsAttr>().isSplat();
558   }
559 
560   static LinalgOp fuse(ConstantOp producer, LinalgOp consumer,
561                        unsigned consumerIdx, PatternRewriter &rewriter,
562                        OperationFolder *folder = nullptr) {
563     if (!isFusible(producer, consumer, consumerIdx))
564       return nullptr;
565 
566     // The indexing_maps for the operands of the fused operation are same as
567     // those for the operands of the consumer without the indexing map at
568     // consumerIdx
569     SmallVector<AffineMap, 4> fusedIndexMaps =
570         llvm::to_vector<4>(llvm::map_range(
571             consumer.indexing_maps(), [](Attribute attr) -> AffineMap {
572               return attr.cast<AffineMapAttr>().getValue();
573             }));
574     fusedIndexMaps.erase(std::next(fusedIndexMaps.begin(), consumerIdx));
575 
576     // The operands list is same as the consumer with the argument for constant
577     // index dropped.
578     Operation *consumerOp = consumer.getOperation();
579     SmallVector<Value, 4> fusedOperands(consumerOp->getOperands());
580     fusedOperands.erase(std::next(fusedOperands.begin(), consumerIdx));
581 
582     // Create a constant scalar value from the splat constant.
583     Value scalarConstant = rewriter.create<ConstantOp>(
584         producer.getLoc(),
585         producer.value().cast<DenseElementsAttr>().getSplatValue());
586 
587     LinalgOp fusedOp = createLinalgOpOfSameType(
588         consumer, rewriter, rewriter.getUnknownLoc(),
589         consumerOp->getResultTypes(),
590         /*inputs=*/fusedOperands,
591         /*outputBuffers=*/ValueRange{},
592         /*initTensors=*/ValueRange{}, // no init tensors for now.
593         rewriter.getAffineMapArrayAttr(fusedIndexMaps),
594         consumer.iterator_types(),
595         /*doc=*/nullptr,
596         /*library_call=*/nullptr,
597         /*symbol_source=*/nullptr);
598 
599     // Map the block argument corresponding to the replaced argument with the
600     // scalar constant.
601     Region &consumerRegion = consumerOp->getRegion(0);
602     Block &entryBlock = *consumerRegion.begin();
603     unsigned argIndex = entryBlock.getNumArguments() -
604                         consumerOp->getNumOperands() + consumerIdx;
605     BlockAndValueMapping mapping;
606     mapping.map(entryBlock.getArgument(argIndex), scalarConstant);
607     Region &fusedRegion = fusedOp.getOperation()->getRegion(0);
608     rewriter.cloneRegionBefore(consumerRegion, fusedRegion, fusedRegion.begin(),
609                                mapping);
610     return fusedOp;
611   }
612 };
613 } // namespace
614 
615 Operation *mlir::linalg::fuseTensorOps(PatternRewriter &rewriter,
616                                        Operation *consumer,
617                                        unsigned consumerIdx,
618                                        OperationFolder *folder) {
619   if (consumerIdx >= consumer->getNumOperands())
620     return nullptr;
621   Operation *producer = consumer->getOperand(consumerIdx).getDefiningOp();
622   if (!producer || producer->getNumResults() != 1)
623     return nullptr;
624 
625   // Fuse when consumer is GenericOp or IndexedGenericOp.
626   if (isa<GenericOp, IndexedGenericOp>(consumer)) {
627     if (isa<GenericOp, IndexedGenericOp>(producer))
628       return FuseGenericOpsOnTensors::fuse(cast<LinalgOp>(producer),
629                                            cast<LinalgOp>(consumer),
630                                            consumerIdx, rewriter, folder);
631     if (auto reshapeOpProducer = dyn_cast<TensorReshapeOp>(producer))
632       return FuseTensorReshapeOpAsProducer::fuse(reshapeOpProducer,
633                                                  cast<LinalgOp>(consumer),
634                                                  consumerIdx, rewriter, folder);
635     if (auto constantOpProducer = dyn_cast<ConstantOp>(producer))
636       return FuseConstantOpAsProducer::fuse(constantOpProducer,
637                                             cast<LinalgOp>(consumer),
638                                             consumerIdx, rewriter, folder);
639     return nullptr;
640   }
641 
642   if (isa<GenericOp, IndexedGenericOp>(producer)) {
643     // Fuse when consumer is a TensorReshapeOp.
644     if (TensorReshapeOp reshapeOp = dyn_cast<TensorReshapeOp>(consumer)) {
645       return FuseTensorReshapeOpAsConsumer::fuse(
646           cast<LinalgOp>(producer), reshapeOp, consumerIdx, rewriter, folder);
647     }
648   }
649 
650   return nullptr;
651 }
652 
653 namespace {
654 /// Patterns to fuse a generic op, with the producer of its operands.
655 template <typename LinalgOpTy>
656 struct FuseTensorOps : public OpRewritePattern<LinalgOpTy> {
657   using OpRewritePattern<LinalgOpTy>::OpRewritePattern;
658 
659   LogicalResult matchAndRewrite(LinalgOpTy op,
660                                 PatternRewriter &rewriter) const override {
661     // Find the first operand that is defined by another generic op on tensors.
662     for (auto operandNum :
663          llvm::seq<unsigned>(0, op.getOperation()->getNumOperands())) {
664       Operation *producer =
665           op.getOperation()->getOperand(operandNum).getDefiningOp();
666       if (Operation *fusedOp = fuseTensorOps(rewriter, op, operandNum)) {
667         rewriter.replaceOp(op, fusedOp->getResults());
668         if (producer && llvm::all_of(producer->getResults(),
669                                      [](Value val) { return val.use_empty(); }))
670           rewriter.eraseOp(producer);
671         return success();
672       }
673     }
674     return failure();
675   }
676 };
677 
678 /// Pass that fuses generic ops on tensors. Used only for testing.
679 struct FusionOfTensorOpsPass
680     : public LinalgFusionOfTensorOpsBase<FusionOfTensorOpsPass> {
681   void runOnOperation() override {
682     OwningRewritePatternList patterns;
683     Operation *op = getOperation();
684     populateLinalgTensorOpsFusionPatterns(op->getContext(), patterns);
685     applyPatternsAndFoldGreedily(op->getRegions(), patterns);
686   };
687 };
688 } // namespace
689 
690 void mlir::populateLinalgTensorOpsFusionPatterns(
691     MLIRContext *context, OwningRewritePatternList &patterns) {
692   patterns.insert<FuseTensorOps<GenericOp>, FuseTensorOps<IndexedGenericOp>,
693                   FuseTensorOps<TensorReshapeOp>>(context);
694 }
695 
696 std::unique_ptr<Pass> mlir::createLinalgFusionOfTensorOpsPass() {
697   return std::make_unique<FusionOfTensorOpsPass>();
698 }
699