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 pass. 10 // 11 //===----------------------------------------------------------------------===// 12 13 #include "mlir/Analysis/Dominance.h" 14 #include "mlir/Dialect/Linalg/Analysis/DependenceAnalysis.h" 15 #include "mlir/Dialect/Linalg/EDSC/Intrinsics.h" 16 #include "mlir/Dialect/Linalg/IR/LinalgOps.h" 17 #include "mlir/Dialect/Linalg/IR/LinalgTypes.h" 18 #include "mlir/Dialect/Linalg/Passes.h" 19 #include "mlir/Dialect/Linalg/Utils/Utils.h" 20 #include "mlir/Dialect/StandardOps/EDSC/Intrinsics.h" 21 #include "mlir/IR/AffineExpr.h" 22 #include "mlir/IR/AffineMap.h" 23 #include "mlir/IR/OpImplementation.h" 24 #include "mlir/IR/PatternMatch.h" 25 #include "mlir/Pass/Pass.h" 26 #include "mlir/Support/LLVM.h" 27 #include "mlir/Support/STLExtras.h" 28 #include "mlir/Transforms/FoldUtils.h" 29 #include "llvm/ADT/SetVector.h" 30 #include "llvm/Support/CommandLine.h" 31 #include "llvm/Support/Debug.h" 32 33 #define DEBUG_TYPE "linalg-fusion" 34 35 using namespace mlir; 36 using namespace mlir::edsc; 37 using namespace mlir::edsc::intrinsics; 38 using namespace mlir::linalg; 39 40 using folded_std_constant_index = folded::ValueBuilder<ConstantIndexOp>; 41 42 using llvm::dbgs; 43 44 /// Implements a simple high-level fusion pass of linalg library operations. 45 /// 46 /// In each block, linalg ops are processed in reverse textual order. 47 /// Given a linalg op `O`, fusion occurs by: 48 /// 1. inspecting the linalg ops that write into the views read by `O`. This 49 /// uses the SSA value of the views and a simple subview/slice analysis to 50 /// determine producer-consumer dependences; 51 /// 2. greedily fuse the linalg ops that produce subview 52 /// 3. inspect the fused ops and determine whether they have other remaining 53 /// LinalgOp uses. If not, then erase the original producing linalg op. 54 /// 55 /// More advanced use cases, analyses as well as profitability heuristics are 56 /// left for future work. 57 58 static llvm::cl::OptionCategory clOptionsCategory(DEBUG_TYPE " options"); 59 static llvm::cl::list<unsigned> clTileSizes( 60 "linalg-fusion-tile-sizes", 61 llvm::cl::desc( 62 "Tile sizes by which to tile linalg operations during linalg fusion"), 63 llvm::cl::ZeroOrMore, llvm::cl::MiscFlags::CommaSeparated, 64 llvm::cl::cat(clOptionsCategory)); 65 66 // Return a cloned version of `op` that operates on `loopRanges`, assumed to be 67 // a subset of the original loop ranges of `op`. 68 // This is achieved by applying the `loopToOperandRangesMaps` permutation maps 69 // to the `loopRanges` in order to obtain view ranges. 70 static LinalgOp cloneWithLoopRanges(OpBuilder &b, Location loc, LinalgOp op, 71 ArrayRef<SubViewOp::Range> loopRanges) { 72 assert(op.hasBufferSemantics() && "expected linalg op with buffer semantics"); 73 auto maps = loopToOperandRangesMaps(op); 74 SmallVector<Value, 8> clonedViews; 75 clonedViews.reserve(op.getNumInputsAndOutputs()); 76 // Iterate over the inputs and outputs in order. 77 // Extract the subranges from the linearized ranges. 78 SmallVector<Value, 8> ios(op.getInputsAndOutputBuffers()); 79 for (auto en : llvm::enumerate(ios)) { 80 unsigned idx = en.index(); 81 auto map = maps[idx]; 82 LLVM_DEBUG(dbgs() << "map: " << map << "\n"); 83 Value view = en.value(); 84 SmallVector<SubViewOp::Range, 4> viewRanges(map.getNumResults()); 85 for (auto en2 : llvm::enumerate(map.getResults())) { 86 unsigned d = en2.index(); 87 // loopToOperandRangesMaps are permutations-only. 88 unsigned loopPos = en2.value().cast<AffineDimExpr>().getPosition(); 89 viewRanges[d] = loopRanges[loopPos]; 90 LLVM_DEBUG(dbgs() << "\ni,j: " << en.index() << ", " << en2.index() 91 << "\t" 92 << "loopPos: " << loopPos << "\t" << viewRanges[d]); 93 } 94 // Construct a new subview for the tile. 95 unsigned rank = viewRanges.size(); 96 SmallVector<Value, 4> offsets, sizes, strides; 97 offsets.reserve(rank); 98 sizes.reserve(rank); 99 strides.reserve(rank); 100 for (auto r : viewRanges) { 101 offsets.push_back(r.offset); 102 sizes.push_back(r.size); 103 strides.push_back(r.stride); 104 } 105 clonedViews.push_back( 106 b.create<SubViewOp>(loc, view, offsets, sizes, strides)); 107 } 108 auto operands = getAssumedNonViewOperands(op); 109 clonedViews.append(operands.begin(), operands.end()); 110 return op.clone(b, loc, clonedViews); 111 } 112 113 struct ViewDimension { 114 Value view; 115 unsigned dimension; 116 }; 117 118 // Given an `op`, returns the first (`view`, `dimension`) pair that identifies 119 // the loop range at `loopDepth`. The semantics of the loopToOperandRangesMaps 120 // guarantees at least one such dimension is found. If multiple candidates exist 121 // they must agree by construction (i.e. have the same size) and we just return 122 // the first one. 123 static ViewDimension getViewDefiningLoopRange(LinalgOp op, unsigned loopDepth) { 124 assert(op.hasBufferSemantics() && "expected linalg op with buffer semantics"); 125 auto maps = loopToOperandRangesMaps(op); 126 // Iterate over the inputs and outputs in order. 127 // Extract the subranges from the linearized ranges. 128 SmallVector<Value, 8> ios(op.getInputsAndOutputBuffers()); 129 for (auto en : llvm::enumerate(ios)) { 130 unsigned idx = en.index(); 131 auto map = maps[idx]; 132 LLVM_DEBUG(dbgs() << "getViewDefiningLoopRange I/O idx: " << idx << "\n"); 133 LLVM_DEBUG(dbgs() << "getViewDefiningLoopRange map: " << map << "\n"); 134 Value view = en.value(); 135 SmallVector<Value, 8> viewRanges(map.getNumResults(), nullptr); 136 for (auto en2 : llvm::enumerate(map.getResults())) { 137 if (loopDepth == en2.value().cast<AffineDimExpr>().getPosition()) { 138 LLVM_DEBUG(dbgs() << "getViewDefiningLoopRange loopDepth: " << loopDepth 139 << "\n"); 140 LLVM_DEBUG(dbgs() << "getViewDefiningLoopRange view: " << view << "\n"); 141 return ViewDimension{view, static_cast<unsigned>(en2.index())}; 142 } 143 } 144 } 145 llvm_unreachable("Expect to be able to extract a view defining loop range"); 146 } 147 148 static LinalgOp fuse(Value producedView, LinalgOp producer, LinalgOp consumer, 149 unsigned consumerIdx, unsigned producerIdx, 150 OperationFolder *folder) { 151 assert(producer.hasBufferSemantics() && 152 "expected linalg op with buffer semantics"); 153 assert(consumer.hasBufferSemantics() && 154 "expected linalg op with buffer semantics"); 155 auto subView = dyn_cast_or_null<SubViewOp>( 156 consumer.getInput(consumerIdx).getDefiningOp()); 157 auto slice = 158 dyn_cast_or_null<SliceOp>(consumer.getInput(consumerIdx).getDefiningOp()); 159 assert(subView || slice); 160 (void)subView; 161 (void)slice; 162 163 // loopToOperandRangesMaps are permutations-only by construction: 164 // we can always identify a data dimension with a (at least one) loop 165 // dimension. 166 AffineMap producerMap = 167 loopToOperandRangesMaps(producer)[producer.getNumInputs() + producerIdx]; 168 LLVM_DEBUG(dbgs() << "Producer Idx: " << producerIdx 169 << ", producer map: " << producerMap << "\n"); 170 171 unsigned nPar = producer.getNumParallelLoops(); 172 unsigned nRed = producer.getNumReductionLoops(); 173 unsigned nWin = producer.getNumWindowLoops(); 174 SmallVector<SubViewOp::Range, 8> loopRanges(nPar + nRed + nWin); 175 176 // Iterate over dimensions identified by the producer map for `producerIdx`. 177 // This defines a subset of the loop ranges that we need to complete later. 178 for (auto en : llvm::enumerate(producerMap.getResults())) { 179 unsigned posInProducerLoop = en.value().cast<AffineDimExpr>().getPosition(); 180 loopRanges[posInProducerLoop] = subView.getRanges()[en.index()]; 181 } 182 183 OpBuilder b(consumer.getOperation()); 184 auto loc = consumer.getLoc(); 185 // Iterate over all dimensions. For the dimensions not identified by the 186 // producer map for `producerIdx`, we need to explicitly compute the view that 187 // defines the loop ranges using the `producer`. 188 for (unsigned i = 0, nLoops = loopRanges.size(); i < nLoops; ++i) { 189 if (loopRanges[i].offset) 190 LLVM_DEBUG(llvm::dbgs() 191 << "existing LoopRange: " << loopRanges[i] << "\n"); 192 else { 193 auto viewDim = getViewDefiningLoopRange(producer, i); 194 loopRanges[i] = SubViewOp::Range{ 195 folded_std_constant_index(folder, 0), 196 std_dim(viewDim.view, viewDim.dimension), 197 folded_std_constant_index(folder, 1) 198 }; 199 LLVM_DEBUG(llvm::dbgs() << "new LoopRange: " << loopRanges[i] << "\n"); 200 } 201 } 202 203 return cloneWithLoopRanges(b, loc, producer, loopRanges); 204 } 205 206 // Encode structural fusion safety preconditions. 207 // Some of these will be lifted in the future with better analysis. 208 static bool isStructurallyFusableProducer(LinalgOp producer, Value consumedView, 209 LinalgOp consumer) { 210 assert(producer.hasBufferSemantics() && 211 "expected linalg op with buffer semantics"); 212 assert(consumer.hasBufferSemantics() && 213 "expected linalg op with buffer semantics"); 214 if (producer.getNumOutputs() != 1) { 215 LLVM_DEBUG(dbgs() << "\nNot structurally fusable (multi-output)"); 216 return false; 217 } 218 // Only fuse when the producer block dominates. 219 DominanceInfo dom(producer.getOperation()); 220 if (!dom.dominates(producer.getOperation()->getBlock(), 221 consumer.getOperation()->getBlock())) { 222 LLVM_DEBUG( 223 dbgs() 224 << "\nNot structurally fusable (producer block does not dominate)"); 225 return false; 226 } 227 return true; 228 } 229 230 bool mlir::linalg::isProducerLastWriteOfView(const LinalgDependenceGraph &graph, 231 LinalgOp consumer, 232 Value consumedView, 233 LinalgOp producer) { 234 assert(producer.hasBufferSemantics() && 235 "expected linalg op with buffer semantics"); 236 assert(consumer.hasBufferSemantics() && 237 "expected linalg op with buffer semantics"); 238 // Make some simple structural checks that alleviate the need for more 239 // complex analyses. 240 if (!isStructurallyFusableProducer(producer, consumedView, consumer)) { 241 LLVM_DEBUG(dbgs() << "\n***Not static last write due to structure:\t" 242 << *producer.getOperation()); 243 return false; 244 } 245 // Check for any interleaved write to consumedView. 246 if (!graph.findCoveringWrites(producer, consumer, consumedView).empty()) { 247 LLVM_DEBUG(dbgs() << "\n***Not fusable due to interleaved write:\t" 248 << *producer.getOperation()); 249 return false; 250 } 251 return true; 252 } 253 254 bool mlir::linalg::isFusableInto(const LinalgDependenceGraph &graph, 255 LinalgOp consumer, Value consumedView, 256 LinalgOp producer) { 257 assert(producer.hasBufferSemantics() && 258 "expected linalg op with buffer semantics"); 259 assert(consumer.hasBufferSemantics() && 260 "expected linalg op with buffer semantics"); 261 if (!isProducerLastWriteOfView(graph, consumer, consumedView, producer)) 262 return false; 263 // Check for any fusion-preventing dependence to any view read/written that 264 // would violate dependences. 265 if (!graph.findCoveringDependences(producer, consumer).empty()) { 266 LLVM_DEBUG(dbgs() << "\n***Not fusable due to an interleaved dependence:\t" 267 << *producer.getOperation()); 268 return false; 269 } 270 return true; 271 } 272 273 // Only consider RAW atm. 274 Optional<FusionInfo> mlir::linalg::fuseProducerOf( 275 OpBuilder &b, LinalgOp consumer, unsigned consumerIdx, 276 const LinalgDependenceGraph &graph, OperationFolder *folder) { 277 assert(consumer.hasBufferSemantics() && 278 "expected linalg op with buffer semantics"); 279 LLVM_DEBUG(dbgs() << "\nStart examining consumer: " 280 << *consumer.getOperation()); 281 for (auto dependence : graph.getDependencesInto( 282 consumer, LinalgDependenceGraph::DependenceType::RAW)) { 283 LLVM_DEBUG(dbgs() << "\n***Consider producer:\t" 284 << *dependence.dependentOpView.op << "\n"); 285 auto producer = cast<LinalgOp>(dependence.dependentOpView.op); 286 if (isa<linalg::IndexedGenericOp>(dependence.dependentOpView.op)) { 287 LLVM_DEBUG(dbgs() << "Not fusing indexed_generic producer"); 288 continue; 289 } 290 291 // Check that the dependence is indeed on the input `consumerIdx` view. 292 auto consumedView = dependence.indexingView; 293 if (consumer.getInput(consumerIdx) != consumedView) 294 continue; 295 296 // Consumer consumes this view, `isStructurallyFusableProducer` also checks 297 // whether it is a strict subview of the producer view. 298 auto producedView = dependence.dependentOpView.view; 299 auto producerIdx = producer.getIndexOfOutputBuffer(producedView).getValue(); 300 // `consumerIdx` and `producerIdx` exist by construction. 301 LLVM_DEBUG(dbgs() << "\nRAW producer: " << *producer.getOperation() 302 << " view: " << producedView 303 << " output index: " << producerIdx); 304 305 // Must be a subview or a slice to guarantee there are loops we can fuse 306 // into. 307 auto subView = dyn_cast_or_null<SubViewOp>(consumedView.getDefiningOp()); 308 auto slice = dyn_cast_or_null<SliceOp>(consumedView.getDefiningOp()); 309 if (!subView && !slice) { 310 LLVM_DEBUG(dbgs() << "\nNot fusable (not a subview or slice)"); 311 continue; 312 } 313 314 // Simple fusability checks. 315 if (!isFusableInto(graph, consumer, consumedView, producer)) 316 continue; 317 318 // Fuse `producer` just before `consumer`. 319 OpBuilder::InsertionGuard g(b); 320 b.setInsertionPoint(consumer.getOperation()); 321 ScopedContext scope(b, consumer.getLoc()); 322 LLVM_DEBUG(dbgs() << "Fuse into consumer: " << *consumer << "\n"); 323 auto fusedProducer = fuse(producedView, producer, consumer, consumerIdx, 324 producerIdx, folder); 325 326 return FusionInfo{producer, fusedProducer}; 327 } 328 return llvm::None; 329 } 330 331 /// Checks if two Generic ops are fusible, when one is a producer and another is 332 /// a consumer (with the result of the producer being the `consumerIdx` operand 333 /// of the consumer). 334 static bool areTensorOpsFusible(LinalgOp producer, LinalgOp consumer, 335 unsigned consumerIdx) { 336 // Verify that the producer and consumer are ops on tensors. 337 if (!producer.hasTensorSemantics() || !consumer.hasTensorSemantics()) 338 return false; 339 340 auto producerOp = dyn_cast<linalg::GenericOp>(producer.getOperation()); 341 auto consumerOp = dyn_cast<linalg::GenericOp>(consumer.getOperation()); 342 // Verify that 343 // - the producer and consumers are generic ops, 344 // - only handle cases where the producer has a single return value, 345 // - the producer return value should be the same as argument at `consumerIdx` 346 // of the consumer, 347 // - the producer has all "parallel" iterator type. 348 // - only handle ops that use regions for specifying the scalar operations. 349 if (!producerOp || !consumerOp || producerOp.getNumOutputs() != 1 || 350 producerOp.getResult(0) != consumerOp.getOperand(consumerIdx) || 351 producerOp.getNumParallelLoops() != producerOp.getNumLoops() || 352 producerOp.fun() || consumerOp.fun()) 353 return false; 354 355 // Get the consumer index map. The number of results of the consumer index map 356 // must match the number of loops of the producer. 357 AffineMap consumerIndexMap = consumerOp.getIndexingMap(consumerIdx); 358 if (consumerIndexMap.getNumResults() != producerOp.getNumLoops()) 359 return false; 360 361 // Finally the index_map for the result must be invertible. For now just 362 // verify it is a permutation. 363 AffineMap producerResultIndexMap = producerOp.getOutputIndexingMap(0); 364 return producerResultIndexMap.isPermutation(); 365 } 366 367 /// Computes the indexing maps for arguments of a producer generic op when the 368 /// result of the producer is fused with the consumer. 369 /// - consumerIndexMap is the indexing_map for the argument in the consumer op 370 /// that is the result of the producer op. 371 /// - invProducerResultIndexMap is the inverse of the indexing_map for the 372 /// result in the producer op. 373 /// - producerArgIndexMap is the indexing_map of the argument of the producer 374 /// op. 375 /// The result is the indexing_map to use for the producer argument when the 376 /// producer and consumer ops are fused. 377 static AffineMap computeProducerArgMap(AffineMap consumerIndexMap, 378 AffineMap invProducerResultIndexMap, 379 AffineMap producerArgIndexMap) { 380 // t1 is map from producer result tensor index -> producer arg tensor index. 381 auto t1 = producerArgIndexMap.compose(invProducerResultIndexMap); 382 // The return is map from consumer loop -> producer arg tensor index, 383 // i.e. indexing_map for the producer argument in the fused operation. 384 return t1.compose(consumerIndexMap); 385 } 386 387 Optional<LinalgOp> mlir::linalg::fuseTensorOps(OpBuilder &b, LinalgOp producer, 388 LinalgOp consumer, 389 unsigned consumerIdx, 390 OperationFolder *folder) { 391 if (!areTensorOpsFusible(producer, consumer, consumerIdx)) 392 return {}; 393 394 MLIRContext *context = b.getContext(); 395 auto producerOp = cast<linalg::GenericOp>(producer.getOperation()); 396 auto consumerOp = cast<linalg::GenericOp>(consumer.getOperation()); 397 AffineMap consumerIndexMap = consumerOp.getIndexingMap(consumerIdx); 398 AffineMap invProducerResultIndexMap = 399 inversePermutation(producerOp.getOutputIndexingMap(0)); 400 401 // Compute the fused op operandslist by replacing the operand corresponding to 402 // the result of the producer, with the operands of the producer. 403 unsigned fusedArgsIn = 404 producerOp.getNumInputs() + consumerOp.getNumInputs() - 1; 405 auto fusedArgsOut = consumerOp.getNumOutputs(); 406 SmallVector<Value, 2> fusedOperandsList(consumerOp.getOperands()); 407 fusedOperandsList.erase(std::next(fusedOperandsList.begin(), consumerIdx)); 408 fusedOperandsList.reserve(fusedArgsIn + fusedArgsOut); 409 fusedOperandsList.insert( 410 std::next(fusedOperandsList.begin(), consumerIdx), 411 producerOp.operand_begin(), 412 std::next(producerOp.operand_begin(), producerOp.getNumInputs())); 413 414 // Compute the fused indexing_maps of the operands/results of the fused op. 415 SmallVector<Attribute, 2> fusedIndexingMapAttrs; 416 fusedIndexingMapAttrs.reserve(fusedArgsIn + fusedArgsOut); 417 fusedIndexingMapAttrs.append(consumerOp.indexing_maps().begin(), 418 consumerOp.indexing_maps().end()); 419 fusedIndexingMapAttrs.erase( 420 std::next(fusedIndexingMapAttrs.begin(), consumerIdx)); 421 auto *insertPos = std::next(fusedIndexingMapAttrs.begin(), consumerIdx); 422 for (auto producerArgIndexAttr : 423 llvm::enumerate(producerOp.indexing_maps())) { 424 if (producerArgIndexAttr.index() == producerOp.getNumInputs()) 425 break; 426 auto composedIndexMap = computeProducerArgMap( 427 consumerIndexMap, invProducerResultIndexMap, 428 producerArgIndexAttr.value().cast<AffineMapAttr>().getValue()); 429 insertPos = std::next(fusedIndexingMapAttrs.insert( 430 insertPos, AffineMapAttr::get(composedIndexMap))); 431 } 432 433 // Generate the fused op. 434 auto fusedLinalgOp = b.create<GenericOp>( 435 UnknownLoc::get(context), consumerOp.getResultTypes(), fusedOperandsList, 436 b.getI64IntegerAttr(fusedArgsIn), b.getI64IntegerAttr(fusedArgsOut), 437 b.getArrayAttr(fusedIndexingMapAttrs), consumerOp.iterator_types(), 438 /*doc=*/nullptr, 439 /*fun=*/nullptr, 440 /*library_call=*/nullptr); 441 442 // Build the region of the fused op. 443 auto &fusedOpRegion = fusedLinalgOp.region(); 444 Block &producerOpBlock = producerOp.region().front(); 445 Block &consumerOpBlock = consumerOp.region().front(); 446 Block *fusedBlock = new Block(); 447 fusedOpRegion.push_back(fusedBlock); 448 BlockAndValueMapping mapper; 449 // Map the arguments for the unmodified args from the consumer. 450 for (auto consumerOpArg : llvm::enumerate(consumerOpBlock.getArguments())) { 451 if (consumerOpArg.index() == consumerIdx) { 452 // Map the arguments for the args from the producer. 453 for (auto producerOpArg : producerOpBlock.getArguments()) 454 mapper.map(producerOpArg, 455 fusedBlock->addArgument(producerOpArg.getType())); 456 continue; 457 } 458 mapper.map(consumerOpArg.value(), 459 fusedBlock->addArgument(consumerOpArg.value().getType())); 460 } 461 462 // Add operations from producer (except the yield operation) to the fused op. 463 for (auto &op : producerOpBlock.getOperations()) { 464 if (auto yieldOp = dyn_cast<YieldOp>(op)) { 465 // Lookup the value the yield operation is mapped to. 466 Value yieldVal = yieldOp.getOperand(0); 467 auto clonedVal = mapper.lookup(yieldVal); 468 mapper.map(consumerOpBlock.getArgument(consumerIdx), clonedVal); 469 continue; 470 } 471 fusedBlock->push_back(op.clone(mapper)); 472 } 473 for (auto &op : consumerOpBlock.getOperations()) 474 fusedBlock->push_back(op.clone(mapper)); 475 476 return cast<LinalgOp>(fusedLinalgOp.getOperation()); 477 } 478 479 static void fuseLinalgOpsGreedily(FuncOp f) { 480 LLVM_DEBUG(f.print(dbgs() << "\nBefore linalg-fusion: \n")); 481 482 OpBuilder b(f); 483 OperationFolder folder(f.getContext()); 484 DenseSet<Operation *> eraseSet; 485 486 // Save original Linalg ops, we only want to make a pass over those. 487 SmallVector<Operation *, 8> linalgOps; 488 f.walk([&](LinalgOp op) { 489 if (op.hasBufferSemantics()) 490 linalgOps.push_back(op); 491 }); 492 493 // TODO(pifon, ntv): LinalgDependenceGraph should be able to update itself. 494 // The current naive and expensive reconstruction of the graph should be 495 // removed. 496 for (auto *op : llvm::reverse(linalgOps)) { 497 for (unsigned id = 0, e = LinalgOp(op).getNumInputs(); id < e; ++id) { 498 linalg::Aliases aliases; 499 linalg::LinalgDependenceGraph graph(aliases, linalgOps); 500 if (auto info = fuseProducerOf(b, op, id, graph, &folder)) { 501 auto *originalOp = info->originalProducer.getOperation(); 502 eraseSet.insert(originalOp); 503 auto *originalOpInLinalgOpsVector = 504 std::find(linalgOps.begin(), linalgOps.end(), originalOp); 505 *originalOpInLinalgOpsVector = info->fusedProducer.getOperation(); 506 } 507 } 508 } 509 // The `fuseProducerOf` function performs structural checks and in particular 510 // that no covering read or write exist between the consumer and the producer. 511 // As a consequence, the only fusions that may occur preserve subsequent 512 // dependences and are guaranteed by construction to produce the whole view. 513 // We may thus erase the producer once it is fused. 514 for (auto *e : eraseSet) 515 e->erase(); 516 LLVM_DEBUG(f.print(dbgs() << "\nAfter linalg-fusion: \n")); 517 } 518 519 namespace { 520 521 /// Patterns to fuse a generic op, with the producer of its operands. 522 struct FuseGenericTensorOps : public OpRewritePattern<GenericOp> { 523 using OpRewritePattern<GenericOp>::OpRewritePattern; 524 525 PatternMatchResult matchAndRewrite(GenericOp op, 526 PatternRewriter &rewriter) const override { 527 if (!op.hasTensorSemantics()) 528 return matchFailure(); 529 530 // Find the first operand that is defined by another generic op on tensors. 531 for (auto operand : llvm::enumerate(op.getOperation()->getOperands())) { 532 auto definingOp = 533 dyn_cast_or_null<GenericOp>(operand.value().getDefiningOp()); 534 if (!definingOp || !definingOp.hasTensorSemantics()) 535 continue; 536 auto fusedOp = 537 fuseTensorOps(rewriter, cast<LinalgOp>(definingOp.getOperation()), 538 cast<LinalgOp>(op.getOperation()), operand.index()); 539 if (!fusedOp) 540 continue; 541 rewriter.replaceOp(op, fusedOp.getValue().getOperation()->getResults()); 542 return matchSuccess(); 543 } 544 return matchFailure(); 545 } 546 }; 547 548 /// Pass that fuses generic ops on tensors. Used only for testing. 549 struct FusionOfTensorOpsPass : public OperationPass<FusionOfTensorOpsPass> { 550 void runOnOperation() override { 551 OwningRewritePatternList patterns; 552 Operation *op = getOperation(); 553 patterns.insert<FuseGenericTensorOps>(op->getContext()); 554 applyPatternsGreedily(op->getRegions(), patterns); 555 }; 556 }; 557 558 struct LinalgFusionPass : public FunctionPass<LinalgFusionPass> { 559 void runOnFunction() override { fuseLinalgOpsGreedily(getFunction()); } 560 }; 561 } // namespace 562 563 std::unique_ptr<OpPassBase<FuncOp>> mlir::createLinalgFusionPass() { 564 return std::make_unique<LinalgFusionPass>(); 565 } 566 567 static PassRegistration<LinalgFusionPass> 568 pass("linalg-fusion", "Fuse operations in the linalg dialect"); 569 570 static PassRegistration<FusionOfTensorOpsPass> 571 tensorOpsPass("linalg-fusion-for-tensor-ops", 572 "Fuse operations on RankedTensorType in linalg dialect"); 573