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