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/IR/LinalgOps.h" 16 #include "mlir/Dialect/Linalg/IR/LinalgTypes.h" 17 #include "mlir/Dialect/Linalg/Passes.h" 18 #include "mlir/Dialect/Linalg/Utils/Utils.h" 19 #include "mlir/EDSC/Helpers.h" 20 #include "mlir/IR/AffineExpr.h" 21 #include "mlir/IR/AffineMap.h" 22 #include "mlir/IR/OpImplementation.h" 23 #include "mlir/Pass/Pass.h" 24 #include "mlir/Support/LLVM.h" 25 #include "mlir/Support/STLExtras.h" 26 #include "mlir/Transforms/FoldUtils.h" 27 #include "llvm/ADT/SetVector.h" 28 #include "llvm/Support/CommandLine.h" 29 #include "llvm/Support/Debug.h" 30 31 #define DEBUG_TYPE "linalg-fusion" 32 33 using namespace mlir; 34 using namespace mlir::edsc; 35 using namespace mlir::edsc::intrinsics; 36 using namespace mlir::linalg; 37 38 using llvm::dbgs; 39 40 /// Implements a simple high-level fusion pass of linalg library operations. 41 /// 42 /// In each block, linalg ops are processed in reverse textual order. 43 /// Given a linalg op `O`, fusion occurs by: 44 /// 1. inspecting the linalg ops that write into the views read by `O`. This 45 /// uses the SSA value of the views and a simple subview/slice analysis to 46 /// determine producer-consumer dependences; 47 /// 2. greedily fuse the linalg ops that produce subview 48 /// 3. inspect the fused ops and determine whether they have other remaining 49 /// LinalgOp uses. If not, then erase the original producing linalg op. 50 /// 51 /// More advanced use cases, analyses as well as profitability heuristics are 52 /// left for future work. 53 54 static llvm::cl::OptionCategory clOptionsCategory(DEBUG_TYPE " options"); 55 static llvm::cl::list<unsigned> clTileSizes( 56 "linalg-fusion-tile-sizes", 57 llvm::cl::desc( 58 "Tile sizes by which to tile linalg operations during linalg fusion"), 59 llvm::cl::ZeroOrMore, llvm::cl::MiscFlags::CommaSeparated, 60 llvm::cl::cat(clOptionsCategory)); 61 62 // Return a cloned version of `op` that operates on `loopRanges`, assumed to be 63 // a subset of the original loop ranges of `op`. 64 // This is achieved by applying the `loopToOperandRangesMaps` permutation maps 65 // to the `loopRanges` in order to obtain view ranges. 66 static LinalgOp cloneWithLoopRanges(OpBuilder &b, Location loc, LinalgOp op, 67 ArrayRef<SubViewOp::Range> loopRanges) { 68 assert(op.hasBufferSemantics() && "expected linalg op with buffer semantics"); 69 auto maps = loopToOperandRangesMaps(op); 70 SmallVector<Value, 8> clonedViews; 71 clonedViews.reserve(op.getNumInputsAndOutputs()); 72 // Iterate over the inputs and outputs in order. 73 // Extract the subranges from the linearized ranges. 74 SmallVector<Value, 8> ios(op.getInputsAndOutputBuffers()); 75 for (auto en : llvm::enumerate(ios)) { 76 unsigned idx = en.index(); 77 auto map = maps[idx]; 78 LLVM_DEBUG(dbgs() << "map: " << map << "\n"); 79 Value view = en.value(); 80 SmallVector<SubViewOp::Range, 4> viewRanges(map.getNumResults()); 81 for (auto en2 : llvm::enumerate(map.getResults())) { 82 unsigned d = en2.index(); 83 // loopToOperandRangesMaps are permutations-only. 84 unsigned loopPos = en2.value().cast<AffineDimExpr>().getPosition(); 85 viewRanges[d] = loopRanges[loopPos]; 86 LLVM_DEBUG(dbgs() << "\ni,j: " << en.index() << ", " << en2.index() 87 << "\t" 88 << "loopPos: " << loopPos << "\t" << viewRanges[d]); 89 } 90 // Construct a new subview for the tile. 91 unsigned rank = viewRanges.size(); 92 SmallVector<Value, 4> offsets, sizes, strides; 93 offsets.reserve(rank); 94 sizes.reserve(rank); 95 strides.reserve(rank); 96 for (auto r : viewRanges) { 97 offsets.push_back(r.offset); 98 sizes.push_back(r.size); 99 strides.push_back(r.stride); 100 } 101 clonedViews.push_back( 102 b.create<SubViewOp>(loc, view, offsets, sizes, strides)); 103 } 104 auto operands = getAssumedNonViewOperands(op); 105 clonedViews.append(operands.begin(), operands.end()); 106 return op.clone(b, loc, clonedViews); 107 } 108 109 struct ViewDimension { 110 Value view; 111 unsigned dimension; 112 }; 113 114 // Given an `op`, returns the first (`view`, `dimension`) pair that identifies 115 // the loop range at `loopDepth`. The semantics of the loopToOperandRangesMaps 116 // guarantees at least one such dimension is found. If multiple candidates exist 117 // they must agree by construction (i.e. have the same size) and we just return 118 // the first one. 119 static ViewDimension getViewDefiningLoopRange(LinalgOp op, unsigned loopDepth) { 120 assert(op.hasBufferSemantics() && "expected linalg op with buffer semantics"); 121 auto maps = loopToOperandRangesMaps(op); 122 // Iterate over the inputs and outputs in order. 123 // Extract the subranges from the linearized ranges. 124 SmallVector<Value, 8> ios(op.getInputsAndOutputBuffers()); 125 for (auto en : llvm::enumerate(ios)) { 126 unsigned idx = en.index(); 127 auto map = maps[idx]; 128 LLVM_DEBUG(dbgs() << "getViewDefiningLoopRange I/O idx: " << idx << "\n"); 129 LLVM_DEBUG(dbgs() << "getViewDefiningLoopRange map: " << map << "\n"); 130 Value view = en.value(); 131 SmallVector<Value, 8> viewRanges(map.getNumResults(), nullptr); 132 for (auto en2 : llvm::enumerate(map.getResults())) { 133 if (loopDepth == en2.value().cast<AffineDimExpr>().getPosition()) { 134 LLVM_DEBUG(dbgs() << "getViewDefiningLoopRange loopDepth: " << loopDepth 135 << "\n"); 136 LLVM_DEBUG(dbgs() << "getViewDefiningLoopRange view: " << view << "\n"); 137 return ViewDimension{view, static_cast<unsigned>(en2.index())}; 138 } 139 } 140 } 141 llvm_unreachable("Expect to be able to extract a view defining loop range"); 142 } 143 144 static LinalgOp fuse(Value producedView, LinalgOp producer, LinalgOp consumer, 145 unsigned consumerIdx, unsigned producerIdx, 146 OperationFolder *folder) { 147 assert(producer.hasBufferSemantics() && 148 "expected linalg op with buffer semantics"); 149 assert(consumer.hasBufferSemantics() && 150 "expected linalg op with buffer semantics"); 151 auto subView = dyn_cast_or_null<SubViewOp>( 152 consumer.getInput(consumerIdx).getDefiningOp()); 153 auto slice = 154 dyn_cast_or_null<SliceOp>(consumer.getInput(consumerIdx).getDefiningOp()); 155 assert(subView || slice); 156 (void)subView; 157 (void)slice; 158 159 // loopToOperandRangesMaps are permutations-only by construction: 160 // we can always identify a data dimension with a (at least one) loop 161 // dimension. 162 AffineMap producerMap = 163 loopToOperandRangesMaps(producer)[producer.getNumInputs() + producerIdx]; 164 LLVM_DEBUG(dbgs() << "Producer Idx: " << producerIdx 165 << ", producer map: " << producerMap << "\n"); 166 167 unsigned nPar = producer.getNumParallelLoops(); 168 unsigned nRed = producer.getNumReductionLoops(); 169 unsigned nWin = producer.getNumWindowLoops(); 170 SmallVector<SubViewOp::Range, 8> loopRanges(nPar + nRed + nWin); 171 172 // Iterate over dimensions identified by the producer map for `producerIdx`. 173 // This defines a subset of the loop ranges that we need to complete later. 174 for (auto en : llvm::enumerate(producerMap.getResults())) { 175 unsigned posInProducerLoop = en.value().cast<AffineDimExpr>().getPosition(); 176 loopRanges[posInProducerLoop] = subView.getRanges()[en.index()]; 177 } 178 179 OpBuilder b(consumer.getOperation()); 180 auto loc = consumer.getLoc(); 181 // Iterate over all dimensions. For the dimensions not identified by the 182 // producer map for `producerIdx`, we need to explicitly compute the view that 183 // defines the loop ranges using the `producer`. 184 for (unsigned i = 0, nLoops = loopRanges.size(); i < nLoops; ++i) { 185 if (loopRanges[i].offset) 186 LLVM_DEBUG(llvm::dbgs() 187 << "existing LoopRange: " << loopRanges[i] << "\n"); 188 else { 189 auto viewDim = getViewDefiningLoopRange(producer, i); 190 loopRanges[i] = SubViewOp::Range{constant_index(folder, 0), 191 dim(viewDim.view, viewDim.dimension), 192 constant_index(folder, 1)}; 193 LLVM_DEBUG(llvm::dbgs() << "new LoopRange: " << loopRanges[i] << "\n"); 194 } 195 } 196 197 return cloneWithLoopRanges(b, loc, producer, loopRanges); 198 } 199 200 // Encode structural fusion safety preconditions. 201 // Some of these will be lifted in the future with better analysis. 202 static bool isStructurallyFusableProducer(LinalgOp producer, Value consumedView, 203 LinalgOp consumer) { 204 assert(producer.hasBufferSemantics() && 205 "expected linalg op with buffer semantics"); 206 assert(consumer.hasBufferSemantics() && 207 "expected linalg op with buffer semantics"); 208 if (producer.getNumOutputs() != 1) { 209 LLVM_DEBUG(dbgs() << "\nNot structurally fusable (multi-output)"); 210 return false; 211 } 212 // Only fuse when the producer block dominates. 213 DominanceInfo dom(producer.getOperation()); 214 if (!dom.dominates(producer.getOperation()->getBlock(), 215 consumer.getOperation()->getBlock())) { 216 LLVM_DEBUG( 217 dbgs() 218 << "\nNot structurally fusable (producer block does not dominate)"); 219 return false; 220 } 221 return true; 222 } 223 224 bool mlir::linalg::isProducerLastWriteOfView(const LinalgDependenceGraph &graph, 225 LinalgOp consumer, 226 Value consumedView, 227 LinalgOp producer) { 228 assert(producer.hasBufferSemantics() && 229 "expected linalg op with buffer semantics"); 230 assert(consumer.hasBufferSemantics() && 231 "expected linalg op with buffer semantics"); 232 // Make some simple structural checks that alleviate the need for more 233 // complex analyses. 234 if (!isStructurallyFusableProducer(producer, consumedView, consumer)) { 235 LLVM_DEBUG(dbgs() << "\n***Not static last write due to structure:\t" 236 << *producer.getOperation()); 237 return false; 238 } 239 // Check for any interleaved write to consumedView. 240 if (!graph.findCoveringWrites(producer, consumer, consumedView).empty()) { 241 LLVM_DEBUG(dbgs() << "\n***Not fusable due to interleaved write:\t" 242 << *producer.getOperation()); 243 return false; 244 } 245 return true; 246 } 247 248 bool mlir::linalg::isFusableInto(const LinalgDependenceGraph &graph, 249 LinalgOp consumer, Value consumedView, 250 LinalgOp producer) { 251 assert(producer.hasBufferSemantics() && 252 "expected linalg op with buffer semantics"); 253 assert(consumer.hasBufferSemantics() && 254 "expected linalg op with buffer semantics"); 255 if (!isProducerLastWriteOfView(graph, consumer, consumedView, producer)) 256 return false; 257 // Check for any fusion-preventing dependence to any view read/written that 258 // would violate dependences. 259 if (!graph.findCoveringDependences(producer, consumer).empty()) { 260 LLVM_DEBUG(dbgs() << "\n***Not fusable due to an interleaved dependence:\t" 261 << *producer.getOperation()); 262 return false; 263 } 264 return true; 265 } 266 267 // Only consider RAW atm. 268 Optional<FusionInfo> mlir::linalg::fuseProducerOf( 269 OpBuilder &b, LinalgOp consumer, unsigned consumerIdx, 270 const LinalgDependenceGraph &graph, OperationFolder *folder) { 271 assert(consumer.hasBufferSemantics() && 272 "expected linalg op with buffer semantics"); 273 LLVM_DEBUG(dbgs() << "\nStart examining consumer: " 274 << *consumer.getOperation()); 275 for (auto dependence : graph.getDependencesInto( 276 consumer, LinalgDependenceGraph::DependenceType::RAW)) { 277 LLVM_DEBUG(dbgs() << "\n***Consider producer:\t" 278 << *dependence.dependentOpView.op << "\n"); 279 auto producer = cast<LinalgOp>(dependence.dependentOpView.op); 280 281 // Check that the dependence is indeed on the input `consumerIdx` view. 282 auto consumedView = dependence.indexingView; 283 if (consumer.getInput(consumerIdx) != consumedView) 284 continue; 285 286 // Consumer consumes this view, `isStructurallyFusableProducer` also checks 287 // whether it is a strict subview of the producer view. 288 auto producedView = dependence.dependentOpView.view; 289 auto producerIdx = producer.getIndexOfOutputBuffer(producedView).getValue(); 290 // `consumerIdx` and `producerIdx` exist by construction. 291 LLVM_DEBUG(dbgs() << "\nRAW producer: " << *producer.getOperation() 292 << " view: " << producedView 293 << " output index: " << producerIdx); 294 295 // Must be a subview or a slice to guarantee there are loops we can fuse 296 // into. 297 auto subView = dyn_cast_or_null<SubViewOp>(consumedView.getDefiningOp()); 298 auto slice = dyn_cast_or_null<SliceOp>(consumedView.getDefiningOp()); 299 if (!subView && !slice) { 300 LLVM_DEBUG(dbgs() << "\nNot fusable (not a subview or slice)"); 301 continue; 302 } 303 304 // Simple fusability checks. 305 if (!isFusableInto(graph, consumer, consumedView, producer)) 306 continue; 307 308 // Fuse `producer` just before `consumer`. 309 OpBuilder::InsertionGuard g(b); 310 b.setInsertionPoint(consumer.getOperation()); 311 ScopedContext scope(b, consumer.getLoc()); 312 LLVM_DEBUG(dbgs() << "Fuse into consumer: " << *consumer << "\n"); 313 auto fusedProducer = fuse(producedView, producer, consumer, consumerIdx, 314 producerIdx, folder); 315 316 return FusionInfo{producer, fusedProducer}; 317 } 318 return llvm::None; 319 } 320 321 static void fuseLinalgOpsGreedily(FuncOp f) { 322 LLVM_DEBUG(f.print(dbgs() << "\nBefore linalg-fusion: \n")); 323 324 OpBuilder b(f); 325 OperationFolder folder(f.getContext()); 326 DenseSet<Operation *> eraseSet; 327 328 // Save original Linalg ops, we only want to make a pass over those. 329 SmallVector<Operation *, 8> linalgOps; 330 f.walk([&](LinalgOp op) { 331 if (op.hasBufferSemantics()) 332 linalgOps.push_back(op); 333 }); 334 335 // TODO(pifon, ntv): LinalgDependenceGraph should be able to update itself. 336 // The current naive and expensive reconstruction of the graph should be 337 // removed. 338 for (auto *op : llvm::reverse(linalgOps)) { 339 for (unsigned id = 0, e = LinalgOp(op).getNumInputs(); id < e; ++id) { 340 linalg::Aliases aliases; 341 linalg::LinalgDependenceGraph graph(aliases, linalgOps); 342 if (auto info = fuseProducerOf(b, op, id, graph, &folder)) { 343 auto *originalOp = info->originalProducer.getOperation(); 344 eraseSet.insert(originalOp); 345 auto *originalOpInLinalgOpsVector = 346 std::find(linalgOps.begin(), linalgOps.end(), originalOp); 347 *originalOpInLinalgOpsVector = info->fusedProducer.getOperation(); 348 } 349 } 350 } 351 // The `fuseProducerOf` function performs structural checks and in particular 352 // that no covering read or write exist between the consumer and the producer. 353 // As a consequence, the only fusions that may occur preserve subsequent 354 // dependences and are guaranteed by construction to produce the whole view. 355 // We may thus erase the producer once it is fused. 356 for (auto *e : eraseSet) 357 e->erase(); 358 LLVM_DEBUG(f.print(dbgs() << "\nAfter linalg-fusion: \n")); 359 } 360 361 namespace { 362 struct LinalgFusionPass : public FunctionPass<LinalgFusionPass> { 363 void runOnFunction() override { fuseLinalgOpsGreedily(getFunction()); } 364 }; 365 } // namespace 366 367 std::unique_ptr<OpPassBase<FuncOp>> mlir::createLinalgFusionPass() { 368 return std::make_unique<LinalgFusionPass>(); 369 } 370 371 static PassRegistration<LinalgFusionPass> 372 pass("linalg-fusion", "Fuse operations in the linalg dialect"); 373