summaryrefslogtreecommitdiffstats
path: root/mlir/lib/Dialect/Linalg/Transforms/Promotion.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'mlir/lib/Dialect/Linalg/Transforms/Promotion.cpp')
-rw-r--r--mlir/lib/Dialect/Linalg/Transforms/Promotion.cpp243
1 files changed, 243 insertions, 0 deletions
diff --git a/mlir/lib/Dialect/Linalg/Transforms/Promotion.cpp b/mlir/lib/Dialect/Linalg/Transforms/Promotion.cpp
new file mode 100644
index 00000000000..b8b27958ff5
--- /dev/null
+++ b/mlir/lib/Dialect/Linalg/Transforms/Promotion.cpp
@@ -0,0 +1,243 @@
+//===- Promotion.cpp - Implementation of linalg Promotion -----------------===//
+//
+// Part of the MLIR Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements the linalg dialect Promotion pass.
+//
+//===----------------------------------------------------------------------===//
+
+#include "mlir/Dialect/Linalg/IR/LinalgOps.h"
+#include "mlir/Dialect/Linalg/IR/LinalgTypes.h"
+#include "mlir/Dialect/Linalg/Passes.h"
+#include "mlir/Dialect/Linalg/Utils/Intrinsics.h"
+#include "mlir/Dialect/Linalg/Utils/Utils.h"
+#include "mlir/Dialect/LoopOps/LoopOps.h"
+#include "mlir/EDSC/Helpers.h"
+#include "mlir/IR/AffineExpr.h"
+#include "mlir/IR/AffineExprVisitor.h"
+#include "mlir/IR/AffineMap.h"
+#include "mlir/IR/OpImplementation.h"
+#include "mlir/Pass/Pass.h"
+#include "mlir/Support/LLVM.h"
+#include "mlir/Support/STLExtras.h"
+#include "mlir/Transforms/FoldUtils.h"
+
+#include "llvm/ADT/SetVector.h"
+#include "llvm/Support/CommandLine.h"
+
+using namespace mlir;
+using namespace mlir::edsc;
+using namespace mlir::edsc::intrinsics;
+using namespace mlir::linalg;
+using namespace mlir::linalg::intrinsics;
+using namespace mlir::loop;
+
+using llvm::SetVector;
+
+#define DEBUG_TYPE "linalg-promotion"
+
+static llvm::cl::OptionCategory clOptionsCategory(DEBUG_TYPE " options");
+static llvm::cl::opt<bool> clPromoteDynamic(
+ "test-linalg-promote-dynamic",
+ llvm::cl::desc("Test generation of dynamic promoted buffers"),
+ llvm::cl::cat(clOptionsCategory), llvm::cl::init(false));
+
+static Value allocBuffer(Type elementType, Value size, bool dynamicBuffers) {
+ auto *ctx = size->getContext();
+ auto width = llvm::divideCeil(elementType.getIntOrFloatBitWidth(), 8);
+ if (!dynamicBuffers)
+ if (auto cst = dyn_cast_or_null<ConstantIndexOp>(size->getDefiningOp()))
+ return alloc(
+ MemRefType::get(width * cst.getValue(), IntegerType::get(8, ctx)));
+ Value mul = muli(constant_index(width), size);
+ return alloc(MemRefType::get(-1, IntegerType::get(8, ctx)), mul);
+}
+
+// Performs promotion of a `subView` into a local buffer of the size of the
+// *ranges* of the `subView`. This produces a buffer whose size may be bigger
+// than the actual size of the `subView` at the boundaries.
+// This is related to the full/partial tile problem.
+// Returns a PromotionInfo containing a `buffer`, `fullLocalView` and
+// `partialLocalView` such that:
+// * `buffer` is always the size of the full tile.
+// * `fullLocalView` is a dense contiguous view into that buffer.
+// * `partialLocalView` is a dense non-contiguous slice of `fullLocalView`
+// that corresponds to the size of `subView` and accounting for boundary
+// effects.
+// The point of the full tile buffer is that constant static tile sizes are
+// folded and result in a buffer type with statically known size and alignment
+// properties.
+// To account for general boundary effects, padding must be performed on the
+// boundary tiles. For now this is done with an unconditional `fill` op followed
+// by a partial `copy` op.
+static PromotionInfo promoteFullTileBuffer(OpBuilder &b, Location loc,
+ SubViewOp subView,
+ bool dynamicBuffers,
+ OperationFolder *folder) {
+ auto zero = constant_index(folder, 0);
+ auto one = constant_index(folder, 1);
+
+ auto viewType = subView.getType();
+ auto rank = viewType.getRank();
+ Value allocSize = one;
+ SmallVector<Value, 8> fullRanges, partialRanges;
+ fullRanges.reserve(rank);
+ partialRanges.reserve(rank);
+ for (auto en : llvm::enumerate(subView.getRanges())) {
+ auto rank = en.index();
+ auto rangeValue = en.value();
+ Value d = rangeValue.size;
+ allocSize = muli(folder, allocSize, d).getValue();
+ fullRanges.push_back(d);
+ partialRanges.push_back(range(folder, zero, dim(subView, rank), one));
+ }
+ SmallVector<int64_t, 4> dynSizes(fullRanges.size(), -1);
+ auto buffer =
+ allocBuffer(viewType.getElementType(), allocSize, dynamicBuffers);
+ auto fullLocalView = view(
+ MemRefType::get(dynSizes, viewType.getElementType()), buffer, fullRanges);
+ auto partialLocalView = slice(fullLocalView, partialRanges);
+ return PromotionInfo{buffer, fullLocalView, partialLocalView};
+}
+
+SmallVector<PromotionInfo, 8>
+mlir::linalg::promoteSubViews(OpBuilder &b, Location loc,
+ ArrayRef<Value> subViews, bool dynamicBuffers,
+ OperationFolder *folder) {
+ if (subViews.empty())
+ return {};
+
+ ScopedContext scope(b, loc);
+ SmallVector<PromotionInfo, 8> res;
+ res.reserve(subViews.size());
+ DenseMap<Value, PromotionInfo> promotionInfoMap;
+ for (auto v : subViews) {
+ SubViewOp subView = cast<SubViewOp>(v->getDefiningOp());
+ auto viewType = subView.getType();
+ // TODO(ntv): support more cases than just float.
+ if (!viewType.getElementType().isa<FloatType>())
+ continue;
+ auto promotionInfo =
+ promoteFullTileBuffer(b, loc, subView, dynamicBuffers, folder);
+ promotionInfoMap.insert(std::make_pair(subView.getResult(), promotionInfo));
+ res.push_back(promotionInfo);
+ }
+
+ for (auto v : subViews) {
+ SubViewOp subView = cast<SubViewOp>(v->getDefiningOp());
+ auto info = promotionInfoMap.find(v);
+ if (info == promotionInfoMap.end())
+ continue;
+ // TODO(ntv): value to fill with should be related to the operation.
+ // For now, just use APFloat(0.0f).
+ auto t = subView.getType().getElementType().cast<FloatType>();
+ Value fillVal = constant_float(folder, APFloat(0.0f), t);
+ // TODO(ntv): fill is only necessary if `promotionInfo` has a full local
+ // view that is different from the partial local view and we are on the
+ // boundary.
+ fill(info->second.fullLocalView, fillVal);
+ }
+
+ for (auto v : subViews) {
+ auto info = promotionInfoMap.find(v);
+ if (info == promotionInfoMap.end())
+ continue;
+ copy(cast<SubViewOp>(v->getDefiningOp()), info->second.partialLocalView);
+ }
+ return res;
+}
+
+LinalgOp mlir::linalg::promoteSubViewOperands(OpBuilder &b, LinalgOp op,
+ SetVector<Value> subViews,
+ bool dynamicBuffers,
+ OperationFolder *folder) {
+ // 1. Promote the specified views and use them in the new op.
+ ScopedContext scope(b, op.getLoc());
+ auto promotedBufferAndViews = promoteSubViews(
+ b, op.getLoc(), subViews.getArrayRef(), dynamicBuffers, folder);
+ SmallVector<Value, 8> opViews;
+ opViews.reserve(op.getNumInputsAndOutputs());
+ SmallVector<std::pair<Value, Value>, 8> writebackViews;
+ writebackViews.reserve(subViews.size());
+ unsigned promotedIdx = 0;
+ for (auto view : op.getInputsAndOutputs()) {
+ if (subViews.count(view) != 0) {
+ opViews.push_back(promotedBufferAndViews[promotedIdx].fullLocalView);
+ writebackViews.emplace_back(std::make_pair(
+ view, promotedBufferAndViews[promotedIdx].partialLocalView));
+ promotedIdx++;
+ } else {
+ opViews.push_back(view);
+ }
+ }
+
+ // 2. Append all other operands as they appear, this enforces that such
+ // operands are not views. This is to support cases such as FillOp taking
+ // extra scalars etc.
+ auto operands = getAssumedNonViewOperands(op);
+ opViews.append(operands.begin(), operands.end());
+ LinalgOp res = op.clone(b, op.getLoc(), opViews);
+
+ // 3. Emit write-back for the promoted output views: copy the partial view.
+ for (auto viewAndPartialLocalView : writebackViews) {
+ // WARNING: MUST use the old op to determine whether the operand view is an
+ // output.
+ bool isOutput =
+ op.getIndexOfOutput(viewAndPartialLocalView.first).hasValue();
+ if (isOutput)
+ copy(viewAndPartialLocalView.second, viewAndPartialLocalView.first);
+ }
+
+ // 4. Dealloc local buffers.
+ for (const auto &pi : promotedBufferAndViews)
+ dealloc(pi.buffer);
+
+ return res;
+}
+
+static void promoteSubViews(FuncOp f, bool dynamicBuffers) {
+ SmallVector<LinalgOp, 8> toErase;
+ OperationFolder folder(f.getContext());
+ f.walk([dynamicBuffers, &folder, &toErase](LinalgOp op) {
+ // TODO(ntv) some heuristic here to decide what to promote. Atm it is all or
+ // nothing.
+ SetVector<Value> subViews;
+ OpBuilder b(op);
+ for (auto it : op.getInputsAndOutputs())
+ if (auto sv = dyn_cast_or_null<SubViewOp>(it->getDefiningOp()))
+ subViews.insert(sv);
+ if (!subViews.empty()) {
+ promoteSubViewOperands(b, op, subViews, dynamicBuffers, &folder);
+ toErase.push_back(op);
+ }
+ });
+ for (auto op : toErase)
+ op.erase();
+}
+
+namespace {
+struct LinalgPromotionPass : public FunctionPass<LinalgPromotionPass> {
+ LinalgPromotionPass() = default;
+ LinalgPromotionPass(bool dynamicBuffers) : dynamicBuffers(dynamicBuffers) {}
+
+ void runOnFunction() override {
+ promoteSubViews(getFunction(), dynamicBuffers);
+ }
+
+ bool dynamicBuffers;
+};
+} // namespace
+
+std::unique_ptr<OpPassBase<FuncOp>>
+mlir::linalg::createLinalgPromotionPass(bool dynamicBuffers) {
+ return std::make_unique<LinalgPromotionPass>(dynamicBuffers);
+}
+
+static PassRegistration<LinalgPromotionPass>
+ pass("linalg-promote-subviews", "promote subview ops to local buffers", [] {
+ return std::make_unique<LinalgPromotionPass>(clPromoteDynamic);
+ });
OpenPOWER on IntegriCloud