llvm-project/mlir/lib/Dialect/Linalg/Transforms/LinalgTransforms.cpp
Nicolas Vasilache 508d4e672e Continue refactoring StructuredOps utilities
This CL adds more common information to StructuredOpsUtils.h
The n_view attribute is retired in favor of args_in + args_out but the CL is otherwise NFC.

PiperOrigin-RevId: 285000621
2019-12-11 09:27:34 -08:00

233 lines
9.5 KiB
C++

//===- LinalgTransforms.cpp - Linalg transformations as patterns ----------===//
//
// Copyright 2019 The MLIR Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// =============================================================================
//
// This file implements logic for transforming Linalg operations.
//
//===----------------------------------------------------------------------===//
#include "mlir/Dialect/Linalg/Transforms/LinalgTransforms.h"
#include "mlir/Dialect/Linalg/Analysis/DependenceAnalysis.h"
#include "mlir/Dialect/Linalg/IR/LinalgOps.h"
#include "mlir/Dialect/Linalg/Utils/Intrinsics.h"
#include "mlir/Dialect/Linalg/Utils/Utils.h"
#include "mlir/Dialect/VectorOps/VectorOps.h"
#include "mlir/EDSC/Helpers.h"
#include "mlir/IR/AffineExpr.h"
#include "mlir/IR/Matchers.h"
#include "mlir/IR/PatternMatch.h"
#include "mlir/Pass/Pass.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/raw_ostream.h"
#include <type_traits>
#define DEBUG_TYPE "linalg-transforms"
using namespace mlir;
using namespace mlir::edsc;
using namespace mlir::edsc::intrinsics;
using namespace mlir::linalg;
using namespace mlir::linalg::intrinsics;
using llvm::dbgs;
// Marker used as attribute name in generated Linalg rewriting transformations.
const StringLiteral mlir::linalg::LinalgTransforms::kLinalgTransformMarker =
"__internal_linalg_transform__";
LogicalResult mlir::linalg::tileLinalgOpAndSetMarker(
PatternRewriter &rewriter, Operation *op, ArrayRef<int64_t> sizes,
StringRef linalgMarker, ArrayRef<unsigned> permutation) {
assert(permutation.empty() || permutation.size() == sizes.size());
auto tileRes = tileLinalgOperation(rewriter, op, sizes, permutation);
if (!tileRes)
return failure();
tileRes->op.setAttr(LinalgTransforms::kLinalgTransformMarker,
rewriter.getStringAttr(linalgMarker));
return success();
}
LogicalResult mlir::linalg::tileAndFuseLinalgOpAndSetMarker(
PatternRewriter &rewriter, Operation *op, ArrayRef<int64_t> sizes,
ArrayRef<int64_t> operandIndicesToFuse, StringRef linalgMarker) {
auto tileRes = tileLinalgOperation(rewriter, op, sizes);
if (!tileRes)
return failure();
tileRes->op.setAttr(LinalgTransforms::kLinalgTransformMarker,
rewriter.getStringAttr(linalgMarker));
Aliases aliases;
auto G = LinalgDependenceGraph::buildDependenceGraph(
aliases, op->getParentOfType<FuncOp>());
SmallVector<Operation *, 4> originalProducers;
for (auto operandIdx : operandIndicesToFuse) {
auto fusionRes = fuseProducerOf(rewriter, tileRes->op, operandIdx, G);
if (!fusionRes) {
// Linalg fusion requires tiled loops to even determine whether it is
// possible to fuse. As a consequence, the pattern may fail even though a
// tiled version of op has already been introduced.
// So we need to remove the tiled version ourselves in case of failure.
// Another possibility is to ensure the constraints on the pattern
// guarantee that fusion will occur and just assert here. As we develop
// more complex patterns we can choose what is best.
rewriter.eraseOp(tileRes->loops[0]);
return failure();
}
fusionRes->fusedProducer.setAttr(LinalgTransforms::kLinalgTransformMarker,
rewriter.getStringAttr(linalgMarker));
originalProducers.push_back(fusionRes->originalProducer);
}
// The originalProducers can now be safely erased. This is similar to
// SSA-value use-def but in the world of buffer + structured ops.
for (auto *originalProducer : originalProducers)
rewriter.eraseOp(originalProducer);
return success();
}
bool mlir::linalg::detail::isProducedByOpOfTypeImpl(
Operation *consumerOp, Value *consumedView,
llvm::function_ref<bool(Operation *)> isaOpType) {
LinalgOp consumer = dyn_cast<LinalgOp>(consumerOp);
if (!consumer)
return false;
auto maybeConsumerIndex = consumer.getIndexOfInput(consumedView);
if (!maybeConsumerIndex)
return false;
Aliases aliases;
auto G = LinalgDependenceGraph::buildDependenceGraph(
aliases, consumer.getParentOfType<FuncOp>());
for (auto dependence : G.getDependencesInto(
consumer, LinalgDependenceGraph::DependenceType::RAW)) {
auto producer = cast<LinalgOp>(dependence.dependentOpView.op);
if (!isProducerLastWriteOfView(G, consumer, consumedView, producer))
continue;
if (isaOpType(dependence.dependentOpView.op))
return true;
}
return false;
}
static bool hasMultiplyAddBody(linalg::GenericOp op) {
auto &r = op.region();
if (r.empty())
return false;
if (r.getBlocks().size() != 1)
return false;
auto &ops = r.front().getOperations();
if (ops.size() != 3)
return false;
using mlir::matchers::m_Val;
auto a = m_Val(r.front().getArgument(0));
auto b = m_Val(r.front().getArgument(1));
auto c = m_Val(r.front().getArgument(2));
// TODO(ntv) Update this detection once we have matcher support for
// specifying that any permutation of operands matches.
auto pattern1 = m_Op<YieldOp>(m_Op<AddFOp>(m_Op<MulFOp>(a, b), c));
auto pattern2 = m_Op<YieldOp>(m_Op<AddFOp>(c, m_Op<MulFOp>(a, b)));
auto pattern3 = m_Op<YieldOp>(m_Op<AddFOp>(m_Op<MulFOp>(b, a), c));
auto pattern4 = m_Op<YieldOp>(m_Op<AddFOp>(c, m_Op<MulFOp>(b, a)));
return pattern1.match(&ops.back()) || pattern2.match(&ops.back()) ||
pattern3.match(&ops.back()) || pattern4.match(&ops.back());
}
// TODO(ntv) should be Tablegen'd from a single source that generates the op
// itself.
static bool isMatmul(linalg::GenericOp genericOp) {
auto *ctx = genericOp.getContext();
auto m = getAffineDimExpr(0, ctx);
auto n = getAffineDimExpr(1, ctx);
auto k = getAffineDimExpr(2, ctx);
auto mapA = AffineMapAttr::get(AffineMap::get(3, 0, {m, k}));
auto mapB = AffineMapAttr::get(AffineMap::get(3, 0, {k, n}));
auto mapC = AffineMapAttr::get(AffineMap::get(3, 0, {m, n}));
auto maps = ArrayAttr::get({mapA, mapB, mapC}, ctx);
return genericOp.getNumInputs() == 2 && genericOp.getNumOutputs() == 1 &&
genericOp.indexing_maps() == maps && hasMultiplyAddBody(genericOp);
}
LogicalResult mlir::linalg::vectorizeGenericOp(PatternRewriter &rewriter,
Operation *op) {
LLVM_DEBUG(dbgs() << "\n[" DEBUG_TYPE
"]: Rewrite linalg op as vector.contract: "
<< *op << ":\n");
// TODO(ntv): This is in fact much more general than just vectorization for
// matmul ops.
auto genericOp = dyn_cast<linalg::GenericOp>(op);
if (!genericOp || !isMatmul(genericOp))
return failure();
// TODO(ntv): non-identity layout.
auto isStaticMemRefWithIdentityLayout = [](Value *v) {
auto m = v->getType().dyn_cast<MemRefType>();
if (!m || !m.hasStaticShape() || !m.getAffineMaps().empty())
return false;
return true;
};
if (!llvm::all_of(genericOp.getInputsAndOutputs(),
isStaticMemRefWithIdentityLayout))
return failure();
edsc::ScopedContext scope(rewriter, op->getLoc());
using edsc::intrinsics::std_load;
using edsc::intrinsics::std_store;
using vector_contract = edsc::intrinsics::ValueBuilder<vector::ContractionOp>;
using vector_type_cast = edsc::intrinsics::ValueBuilder<vector::TypeCastOp>;
auto vA = std_load(vector_type_cast(genericOp.getInput(0)));
auto vB = std_load(vector_type_cast(genericOp.getInput(1)));
auto vectorMemRefC = vector_type_cast(genericOp.getOutput(0));
auto vC = std_load(vectorMemRefC);
auto vRes = vector_contract(vA, vB, vC, genericOp.indexing_maps(),
genericOp.iterator_types());
std_store(vRes, vectorMemRefC);
return success();
}
LogicalResult
mlir::linalg::permuteGenericLinalgOp(PatternRewriter &rewriter, Operation *op,
ArrayRef<unsigned> permutation,
StringRef linalgMarker) {
// If permutation is empty, there is nothing to be done.
if (permutation.empty())
return failure();
auto linOp = cast<LinalgOp>(op);
auto permutationMap = inversePermutation(
AffineMap::getPermutationMap(permutation, rewriter.getContext()));
SmallVector<AffineMap, 4> newIndexingMap;
auto indexingMaps = linOp.indexing_maps().getValue();
for (unsigned i = 0, e = linOp.getNumInputsAndOutputs(); i != e; ++i) {
AffineMap m = indexingMaps[i].cast<AffineMapAttr>().getValue().compose(
permutationMap);
newIndexingMap.push_back(m);
}
auto itTypes = linOp.iterator_types().getValue();
SmallVector<Attribute, 4> itTypesVector;
for (unsigned i = 0, e = itTypes.size(); i != e; ++i)
itTypesVector.push_back(itTypes[i]);
applyPermutationToVector(itTypesVector, permutation);
op->setAttr(getIndexingMapsAttrName(),
rewriter.getAffineMapArrayAttr(newIndexingMap));
op->setAttr(getIteratorTypesAttrName(), rewriter.getArrayAttr(itTypesVector));
op->setAttr(LinalgTransforms::kLinalgTransformMarker,
rewriter.getStringAttr(linalgMarker));
linOp.clone(rewriter, linOp.getLoc(), op->getOperands());
return success();
}