xref: /llvm-project/mlir/lib/Dialect/Linalg/Transforms/Promotion.cpp (revision 5cf6e0ce7f03f9841675b1a9d44232540f3df5cc)
1 //===- Promotion.cpp - Implementation of linalg Promotion -----------------===//
2 //
3 // Copyright 2019 The MLIR Authors.
4 //
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
8 //
9 //   http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
16 // =============================================================================
17 //
18 // This file implements the linalg dialect Promotion pass.
19 //
20 //===----------------------------------------------------------------------===//
21 
22 #include "mlir/Dialect/Linalg/IR/LinalgOps.h"
23 #include "mlir/Dialect/Linalg/IR/LinalgTypes.h"
24 #include "mlir/Dialect/Linalg/Passes.h"
25 #include "mlir/Dialect/Linalg/Utils/Intrinsics.h"
26 #include "mlir/Dialect/Linalg/Utils/Utils.h"
27 #include "mlir/Dialect/LoopOps/LoopOps.h"
28 #include "mlir/EDSC/Helpers.h"
29 #include "mlir/IR/AffineExpr.h"
30 #include "mlir/IR/AffineExprVisitor.h"
31 #include "mlir/IR/AffineMap.h"
32 #include "mlir/IR/OpImplementation.h"
33 #include "mlir/Pass/Pass.h"
34 #include "mlir/Support/LLVM.h"
35 #include "mlir/Support/STLExtras.h"
36 #include "mlir/Transforms/FoldUtils.h"
37 
38 #include "llvm/ADT/SetVector.h"
39 #include "llvm/Support/CommandLine.h"
40 
41 using namespace mlir;
42 using namespace mlir::edsc;
43 using namespace mlir::edsc::intrinsics;
44 using namespace mlir::linalg;
45 using namespace mlir::linalg::intrinsics;
46 using namespace mlir::loop;
47 
48 using llvm::SetVector;
49 
50 #define DEBUG_TYPE "linalg-promotion"
51 
52 static llvm::cl::OptionCategory clOptionsCategory(DEBUG_TYPE " options");
53 static llvm::cl::opt<bool> clPromoteDynamic(
54     "test-linalg-promote-dynamic",
55     llvm::cl::desc("Test generation of dynamic promoted buffers"),
56     llvm::cl::cat(clOptionsCategory), llvm::cl::init(false));
57 
58 static AffineMap getAffineDifferenceMap(MLIRContext *context) {
59   AffineExpr d0(getAffineDimExpr(0, context)), d1(getAffineDimExpr(1, context));
60   return AffineMap::get(2, 0, {d0 - d1});
61 }
62 
63 static Value *allocBuffer(Type elementType, Value *size, bool dynamicBuffers) {
64   auto *ctx = size->getContext();
65   auto width = llvm::divideCeil(elementType.getIntOrFloatBitWidth(), 8);
66   if (!dynamicBuffers)
67     if (auto cst = dyn_cast_or_null<ConstantIndexOp>(size->getDefiningOp()))
68       return alloc(
69           MemRefType::get(width * cst.getValue(), IntegerType::get(8, ctx)));
70   Value *mul = muli(constant_index(width), size);
71   return alloc(MemRefType::get(-1, IntegerType::get(8, ctx)), mul);
72 }
73 
74 // Performs promotion of a `subView` into a local buffer of the size of the
75 // *ranges* of the `subView`. This produces a buffer whose size may be bigger
76 // than the actual size of the `subView` at the boundaries.
77 // This is related to the full/partial tile problem.
78 // Returns a PromotionInfo containing a `buffer`, `fullLocalView` and
79 // `partialLocalView` such that:
80 //   * `buffer` is always the size of the full tile.
81 //   * `fullLocalView` is a dense contiguous view into that buffer.
82 //   * `partialLocalView` is a dense non-contiguous slice of `fullLocalView`
83 //     that corresponds to the size of `subView` and accounting for boundary
84 //     effects.
85 // The point of the full tile buffer is that constant static tile sizes are
86 // folded and result in a buffer type with statically known size and alignment
87 // properties.
88 // To account for general boundary effects, padding must be performed on the
89 // boundary tiles. For now this is done with an unconditional `fill` op followed
90 // by a partial `copy` op.
91 static PromotionInfo promoteFullTileBuffer(OpBuilder &b, Location loc,
92                                            mlir::linalg::SubViewOp subView,
93                                            bool dynamicBuffers,
94                                            OperationFolder *folder) {
95   auto zero = constant_index(folder, 0);
96   auto one = constant_index(folder, 1);
97 
98   auto viewType = subView.getViewType();
99   auto rank = viewType.getRank();
100   Value *allocSize = one;
101   SmallVector<Value *, 8> fullRanges, partialRanges;
102   fullRanges.reserve(rank);
103   partialRanges.reserve(rank);
104   for (auto en : llvm::enumerate(subView.getRanges())) {
105     auto rank = en.index();
106     auto rangeValue = en.value();
107     Value *d =
108         isa<DimOp>(rangeValue.max->getDefiningOp())
109             ? rangeValue.max
110             : applyMapToValues(b, loc, getAffineDifferenceMap(b.getContext()),
111                                {rangeValue.max, rangeValue.min}, folder)
112                   .front();
113     allocSize = muli(folder, allocSize, d).getValue();
114     fullRanges.push_back(d);
115     partialRanges.push_back(range(folder, zero, dim(subView, rank), one));
116   }
117   SmallVector<int64_t, 4> dynSizes(fullRanges.size(), -1);
118   auto *buffer =
119       allocBuffer(viewType.getElementType(), allocSize, dynamicBuffers);
120   auto fullLocalView = view(
121       MemRefType::get(dynSizes, viewType.getElementType()), buffer, fullRanges);
122   auto partialLocalView = slice(fullLocalView, partialRanges);
123   return PromotionInfo{buffer, fullLocalView, partialLocalView};
124 }
125 
126 SmallVector<PromotionInfo, 8>
127 mlir::linalg::promoteSubViews(OpBuilder &b, Location loc,
128                               ArrayRef<Value *> subViews, bool dynamicBuffers,
129                               OperationFolder *folder) {
130   if (subViews.empty())
131     return {};
132 
133   ScopedContext scope(b, loc);
134   SmallVector<PromotionInfo, 8> res;
135   res.reserve(subViews.size());
136   DenseMap<Value *, PromotionInfo> promotionInfoMap;
137   for (auto *v : subViews) {
138     mlir::linalg::SubViewOp subView =
139         cast<mlir::linalg::SubViewOp>(v->getDefiningOp());
140     auto viewType = subView.getViewType();
141     // TODO(ntv): support more cases than just float.
142     if (!viewType.getElementType().isa<FloatType>())
143       continue;
144     auto promotionInfo =
145         promoteFullTileBuffer(b, loc, subView, dynamicBuffers, folder);
146     promotionInfoMap.insert(std::make_pair(subView.getResult(), promotionInfo));
147     res.push_back(promotionInfo);
148   }
149 
150   for (auto *v : subViews) {
151     mlir::linalg::SubViewOp subView =
152         cast<mlir::linalg::SubViewOp>(v->getDefiningOp());
153     auto info = promotionInfoMap.find(v);
154     if (info == promotionInfoMap.end())
155       continue;
156     // TODO(ntv): value to fill with should be related to the operation.
157     // For now, just use APFloat(0.0f).
158     auto t = subView.getViewType().getElementType().cast<FloatType>();
159     Value *fillVal = constant_float(folder, APFloat(0.0f), t);
160     // TODO(ntv): fill is only necessary if `promotionInfo` has a full local
161     // view that is different from the partial local view and we are on the
162     // boundary.
163     fill(info->second.fullLocalView, fillVal);
164   }
165 
166   for (auto *v : subViews) {
167     auto info = promotionInfoMap.find(v);
168     if (info == promotionInfoMap.end())
169       continue;
170     copy(cast<mlir::linalg::SubViewOp>(v->getDefiningOp()),
171          info->second.partialLocalView);
172   }
173   return res;
174 }
175 
176 static void promoteSubViewOperands(LinalgOp op, SetVector<Value *> subViews,
177                                    bool dynamicBuffers,
178                                    OperationFolder *folder) {
179   // 1. Promote the specified views and use them in the new op.
180   OpBuilder b(op);
181   ScopedContext scope(b, op.getLoc());
182   auto promotedBufferAndViews = promoteSubViews(
183       b, op.getLoc(), subViews.getArrayRef(), dynamicBuffers, folder);
184   SmallVector<Value *, 8> opViews;
185   opViews.reserve(op.getNumInputsAndOutputs());
186   SmallVector<std::pair<Value *, Value *>, 8> writebackViews;
187   writebackViews.reserve(subViews.size());
188   unsigned promotedIdx = 0;
189   for (auto *view : op.getInputsAndOutputs()) {
190     if (subViews.count(view) != 0) {
191       opViews.push_back(promotedBufferAndViews[promotedIdx].fullLocalView);
192       writebackViews.emplace_back(std::make_pair(
193           view, promotedBufferAndViews[promotedIdx].partialLocalView));
194       promotedIdx++;
195     } else {
196       opViews.push_back(view);
197     }
198   }
199 
200   // 2. Append all other operands as they appear, this enforces that such
201   // operands are not views. This is to support cases such as FillOp taking
202   // extra scalars etc.
203   auto operands = getAssumedNonViewOperands(op);
204   opViews.append(operands.begin(), operands.end());
205   op.clone(b, op.getLoc(), opViews);
206 
207   // 3. Emit write-back for the promoted output views: copy the partial view.
208   for (auto viewAndPartialLocalView : writebackViews) {
209     // Note: use the old op to determine whether the operand view is an output.
210     bool isOutput =
211         op.getIndexOfOutput(viewAndPartialLocalView.first).hasValue();
212     if (isOutput)
213       copy(viewAndPartialLocalView.second, viewAndPartialLocalView.first);
214   }
215 
216   // 4. Dealloc local buffers.
217   for (const auto &pi : promotedBufferAndViews)
218     dealloc(pi.buffer);
219 }
220 
221 static void promoteSubViews(FuncOp f, bool dynamicBuffers) {
222   SmallVector<LinalgOp, 8> toErase;
223   OperationFolder folder(f.getContext());
224   f.walk([dynamicBuffers, &folder, &toErase](LinalgOp op) {
225     // TODO(ntv) some heuristic here to decide what to promote. Atm it is all or
226     // nothing.
227     SetVector<Value *> subViews;
228     for (auto it : op.getInputsAndOutputs())
229       if (auto sv =
230               dyn_cast_or_null<mlir::linalg::SubViewOp>(it->getDefiningOp()))
231         subViews.insert(sv);
232     if (!subViews.empty()) {
233       promoteSubViewOperands(op, subViews, dynamicBuffers, &folder);
234       toErase.push_back(op);
235     }
236   });
237   for (auto op : toErase)
238     op.erase();
239 }
240 
241 namespace {
242 struct LinalgPromotionPass : public FunctionPass<LinalgPromotionPass> {
243   LinalgPromotionPass() = default;
244   LinalgPromotionPass(bool dynamicBuffers) : dynamicBuffers(dynamicBuffers) {}
245 
246   void runOnFunction() override {
247     promoteSubViews(getFunction(), dynamicBuffers);
248   }
249 
250   bool dynamicBuffers;
251 };
252 } // namespace
253 
254 std::unique_ptr<OpPassBase<FuncOp>>
255 mlir::linalg::createLinalgPromotionPass(bool dynamicBuffers) {
256   return std::make_unique<LinalgPromotionPass>(dynamicBuffers);
257 }
258 
259 static PassRegistration<LinalgPromotionPass>
260     pass("linalg-promote-subviews", "promote subview ops to local buffers", [] {
261       return std::make_unique<LinalgPromotionPass>(clPromoteDynamic);
262     });
263