llvm-project/mlir/lib/IR/AffineMap.cpp
Nicolas Vasilache 93a873dfc9 [mlir][Affine] Revisit and simplify composeAffineMapAndOperands.
In prehistorical times, AffineApplyOp was allowed to produce multiple values.
This allowed the creation of intricate SSA use-def chains.
AffineApplyNormalizer was originally introduced as a means of reusing the AffineMap::compose method to write SSA use-def chains.
Unfortunately, symbols that were produced by an AffineApplyOp needed to be promoted to dims and reordered for the mathematical composition to be valid.

Since then, single result AffineApplyOp became the law of the land but the original assumptions were not revisited.

This revision revisits these assumptions and retires AffineApplyNormalizer.

Differential Revision: https://reviews.llvm.org/D94920
2021-01-19 13:52:07 +00:00

541 lines
20 KiB
C++

//===- AffineMap.cpp - MLIR Affine Map Classes ----------------------------===//
//
// Part of the LLVM 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
//
//===----------------------------------------------------------------------===//
#include "mlir/IR/AffineMap.h"
#include "AffineMapDetail.h"
#include "mlir/IR/BuiltinAttributes.h"
#include "mlir/IR/BuiltinTypes.h"
#include "mlir/Support/LogicalResult.h"
#include "mlir/Support/MathExtras.h"
#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/raw_ostream.h"
using namespace mlir;
namespace {
// AffineExprConstantFolder evaluates an affine expression using constant
// operands passed in 'operandConsts'. Returns an IntegerAttr attribute
// representing the constant value of the affine expression evaluated on
// constant 'operandConsts', or nullptr if it can't be folded.
class AffineExprConstantFolder {
public:
AffineExprConstantFolder(unsigned numDims, ArrayRef<Attribute> operandConsts)
: numDims(numDims), operandConsts(operandConsts) {}
/// Attempt to constant fold the specified affine expr, or return null on
/// failure.
IntegerAttr constantFold(AffineExpr expr) {
if (auto result = constantFoldImpl(expr))
return IntegerAttr::get(IndexType::get(expr.getContext()), *result);
return nullptr;
}
private:
Optional<int64_t> constantFoldImpl(AffineExpr expr) {
switch (expr.getKind()) {
case AffineExprKind::Add:
return constantFoldBinExpr(
expr, [](int64_t lhs, int64_t rhs) { return lhs + rhs; });
case AffineExprKind::Mul:
return constantFoldBinExpr(
expr, [](int64_t lhs, int64_t rhs) { return lhs * rhs; });
case AffineExprKind::Mod:
return constantFoldBinExpr(
expr, [](int64_t lhs, int64_t rhs) { return mod(lhs, rhs); });
case AffineExprKind::FloorDiv:
return constantFoldBinExpr(
expr, [](int64_t lhs, int64_t rhs) { return floorDiv(lhs, rhs); });
case AffineExprKind::CeilDiv:
return constantFoldBinExpr(
expr, [](int64_t lhs, int64_t rhs) { return ceilDiv(lhs, rhs); });
case AffineExprKind::Constant:
return expr.cast<AffineConstantExpr>().getValue();
case AffineExprKind::DimId:
if (auto attr = operandConsts[expr.cast<AffineDimExpr>().getPosition()]
.dyn_cast_or_null<IntegerAttr>())
return attr.getInt();
return llvm::None;
case AffineExprKind::SymbolId:
if (auto attr = operandConsts[numDims +
expr.cast<AffineSymbolExpr>().getPosition()]
.dyn_cast_or_null<IntegerAttr>())
return attr.getInt();
return llvm::None;
}
llvm_unreachable("Unknown AffineExpr");
}
// TODO: Change these to operate on APInts too.
Optional<int64_t> constantFoldBinExpr(AffineExpr expr,
int64_t (*op)(int64_t, int64_t)) {
auto binOpExpr = expr.cast<AffineBinaryOpExpr>();
if (auto lhs = constantFoldImpl(binOpExpr.getLHS()))
if (auto rhs = constantFoldImpl(binOpExpr.getRHS()))
return op(*lhs, *rhs);
return llvm::None;
}
// The number of dimension operands in AffineMap containing this expression.
unsigned numDims;
// The constant valued operands used to evaluate this AffineExpr.
ArrayRef<Attribute> operandConsts;
};
} // end anonymous namespace
/// Returns a single constant result affine map.
AffineMap AffineMap::getConstantMap(int64_t val, MLIRContext *context) {
return get(/*dimCount=*/0, /*symbolCount=*/0,
{getAffineConstantExpr(val, context)});
}
/// Returns an identity affine map (d0, ..., dn) -> (dp, ..., dn) on the most
/// minor dimensions.
AffineMap AffineMap::getMinorIdentityMap(unsigned dims, unsigned results,
MLIRContext *context) {
assert(dims >= results && "Dimension mismatch");
auto id = AffineMap::getMultiDimIdentityMap(dims, context);
return AffineMap::get(dims, 0, id.getResults().take_back(results), context);
}
bool AffineMap::isMinorIdentity() const {
return *this ==
getMinorIdentityMap(getNumDims(), getNumResults(), getContext());
}
/// Returns an AffineMap representing a permutation.
AffineMap AffineMap::getPermutationMap(ArrayRef<unsigned> permutation,
MLIRContext *context) {
assert(!permutation.empty() &&
"Cannot create permutation map from empty permutation vector");
SmallVector<AffineExpr, 4> affExprs;
for (auto index : permutation)
affExprs.push_back(getAffineDimExpr(index, context));
auto m = std::max_element(permutation.begin(), permutation.end());
auto permutationMap = AffineMap::get(*m + 1, 0, affExprs, context);
assert(permutationMap.isPermutation() && "Invalid permutation vector");
return permutationMap;
}
template <typename AffineExprContainer>
static void getMaxDimAndSymbol(ArrayRef<AffineExprContainer> exprsList,
int64_t &maxDim, int64_t &maxSym) {
for (const auto &exprs : exprsList) {
for (auto expr : exprs) {
expr.walk([&maxDim, &maxSym](AffineExpr e) {
if (auto d = e.dyn_cast<AffineDimExpr>())
maxDim = std::max(maxDim, static_cast<int64_t>(d.getPosition()));
if (auto s = e.dyn_cast<AffineSymbolExpr>())
maxSym = std::max(maxSym, static_cast<int64_t>(s.getPosition()));
});
}
}
}
template <typename AffineExprContainer>
static SmallVector<AffineMap, 4>
inferFromExprList(ArrayRef<AffineExprContainer> exprsList) {
assert(!exprsList.empty());
assert(!exprsList[0].empty());
auto context = exprsList[0][0].getContext();
int64_t maxDim = -1, maxSym = -1;
getMaxDimAndSymbol(exprsList, maxDim, maxSym);
SmallVector<AffineMap, 4> maps;
maps.reserve(exprsList.size());
for (const auto &exprs : exprsList)
maps.push_back(AffineMap::get(/*dimCount=*/maxDim + 1,
/*symbolCount=*/maxSym + 1, exprs, context));
return maps;
}
SmallVector<AffineMap, 4>
AffineMap::inferFromExprList(ArrayRef<ArrayRef<AffineExpr>> exprsList) {
return ::inferFromExprList(exprsList);
}
SmallVector<AffineMap, 4>
AffineMap::inferFromExprList(ArrayRef<SmallVector<AffineExpr, 4>> exprsList) {
return ::inferFromExprList(exprsList);
}
AffineMap AffineMap::getMultiDimIdentityMap(unsigned numDims,
MLIRContext *context) {
SmallVector<AffineExpr, 4> dimExprs;
dimExprs.reserve(numDims);
for (unsigned i = 0; i < numDims; ++i)
dimExprs.push_back(mlir::getAffineDimExpr(i, context));
return get(/*dimCount=*/numDims, /*symbolCount=*/0, dimExprs, context);
}
MLIRContext *AffineMap::getContext() const { return map->context; }
bool AffineMap::isIdentity() const {
if (getNumDims() != getNumResults())
return false;
ArrayRef<AffineExpr> results = getResults();
for (unsigned i = 0, numDims = getNumDims(); i < numDims; ++i) {
auto expr = results[i].dyn_cast<AffineDimExpr>();
if (!expr || expr.getPosition() != i)
return false;
}
return true;
}
bool AffineMap::isEmpty() const {
return getNumDims() == 0 && getNumSymbols() == 0 && getNumResults() == 0;
}
bool AffineMap::isSingleConstant() const {
return getNumResults() == 1 && getResult(0).isa<AffineConstantExpr>();
}
int64_t AffineMap::getSingleConstantResult() const {
assert(isSingleConstant() && "map must have a single constant result");
return getResult(0).cast<AffineConstantExpr>().getValue();
}
unsigned AffineMap::getNumDims() const {
assert(map && "uninitialized map storage");
return map->numDims;
}
unsigned AffineMap::getNumSymbols() const {
assert(map && "uninitialized map storage");
return map->numSymbols;
}
unsigned AffineMap::getNumResults() const {
assert(map && "uninitialized map storage");
return map->results.size();
}
unsigned AffineMap::getNumInputs() const {
assert(map && "uninitialized map storage");
return map->numDims + map->numSymbols;
}
ArrayRef<AffineExpr> AffineMap::getResults() const {
assert(map && "uninitialized map storage");
return map->results;
}
AffineExpr AffineMap::getResult(unsigned idx) const {
assert(map && "uninitialized map storage");
return map->results[idx];
}
unsigned AffineMap::getDimPosition(unsigned idx) const {
return getResult(idx).cast<AffineDimExpr>().getPosition();
}
/// Folds the results of the application of an affine map on the provided
/// operands to a constant if possible. Returns false if the folding happens,
/// true otherwise.
LogicalResult
AffineMap::constantFold(ArrayRef<Attribute> operandConstants,
SmallVectorImpl<Attribute> &results) const {
// Attempt partial folding.
SmallVector<int64_t, 2> integers;
partialConstantFold(operandConstants, &integers);
// If all expressions folded to a constant, populate results with attributes
// containing those constants.
if (integers.empty())
return failure();
auto range = llvm::map_range(integers, [this](int64_t i) {
return IntegerAttr::get(IndexType::get(getContext()), i);
});
results.append(range.begin(), range.end());
return success();
}
AffineMap
AffineMap::partialConstantFold(ArrayRef<Attribute> operandConstants,
SmallVectorImpl<int64_t> *results) const {
assert(getNumInputs() == operandConstants.size());
// Fold each of the result expressions.
AffineExprConstantFolder exprFolder(getNumDims(), operandConstants);
SmallVector<AffineExpr, 4> exprs;
exprs.reserve(getNumResults());
for (auto expr : getResults()) {
auto folded = exprFolder.constantFold(expr);
// If did not fold to a constant, keep the original expression, and clear
// the integer results vector.
if (folded) {
exprs.push_back(
getAffineConstantExpr(folded.getInt(), folded.getContext()));
if (results)
results->push_back(folded.getInt());
} else {
exprs.push_back(expr);
if (results) {
results->clear();
results = nullptr;
}
}
}
return get(getNumDims(), getNumSymbols(), exprs, getContext());
}
/// Walk all of the AffineExpr's in this mapping. Each node in an expression
/// tree is visited in postorder.
void AffineMap::walkExprs(std::function<void(AffineExpr)> callback) const {
for (auto expr : getResults())
expr.walk(callback);
}
/// This method substitutes any uses of dimensions and symbols (e.g.
/// dim#0 with dimReplacements[0]) in subexpressions and returns the modified
/// expression mapping. Because this can be used to eliminate dims and
/// symbols, the client needs to specify the number of dims and symbols in
/// the result. The returned map always has the same number of results.
AffineMap AffineMap::replaceDimsAndSymbols(ArrayRef<AffineExpr> dimReplacements,
ArrayRef<AffineExpr> symReplacements,
unsigned numResultDims,
unsigned numResultSyms) const {
SmallVector<AffineExpr, 8> results;
results.reserve(getNumResults());
for (auto expr : getResults())
results.push_back(
expr.replaceDimsAndSymbols(dimReplacements, symReplacements));
return get(numResultDims, numResultSyms, results, getContext());
}
/// Sparse replace method. Apply AffineExpr::replace(`expr`, `replacement`) to
/// each of the results and return a new AffineMap with the new results and
/// with the specified number of dims and symbols.
AffineMap AffineMap::replace(AffineExpr expr, AffineExpr replacement,
unsigned numResultDims,
unsigned numResultSyms) const {
SmallVector<AffineExpr, 4> newResults;
newResults.reserve(getNumResults());
for (AffineExpr e : getResults())
newResults.push_back(e.replace(expr, replacement));
return AffineMap::get(numResultDims, numResultSyms, newResults, getContext());
}
/// Sparse replace method. Apply AffineExpr::replace(`map`) to each of the
/// results and return a new AffineMap with the new results and with the
/// specified number of dims and symbols.
AffineMap AffineMap::replace(const DenseMap<AffineExpr, AffineExpr> &map,
unsigned numResultDims,
unsigned numResultSyms) const {
SmallVector<AffineExpr, 4> newResults;
newResults.reserve(getNumResults());
for (AffineExpr e : getResults())
newResults.push_back(e.replace(map));
return AffineMap::get(numResultDims, numResultSyms, newResults, getContext());
}
AffineMap AffineMap::compose(AffineMap map) {
assert(getNumDims() == map.getNumResults() && "Number of results mismatch");
// Prepare `map` by concatenating the symbols and rewriting its exprs.
unsigned numDims = map.getNumDims();
unsigned numSymbolsThisMap = getNumSymbols();
unsigned numSymbols = numSymbolsThisMap + map.getNumSymbols();
SmallVector<AffineExpr, 8> newDims(numDims);
for (unsigned idx = 0; idx < numDims; ++idx) {
newDims[idx] = getAffineDimExpr(idx, getContext());
}
SmallVector<AffineExpr, 8> newSymbols(numSymbols - numSymbolsThisMap);
for (unsigned idx = numSymbolsThisMap; idx < numSymbols; ++idx) {
newSymbols[idx - numSymbolsThisMap] =
getAffineSymbolExpr(idx, getContext());
}
auto newMap =
map.replaceDimsAndSymbols(newDims, newSymbols, numDims, numSymbols);
SmallVector<AffineExpr, 8> exprs;
exprs.reserve(getResults().size());
for (auto expr : getResults())
exprs.push_back(expr.compose(newMap));
return AffineMap::get(numDims, numSymbols, exprs, map.getContext());
}
SmallVector<int64_t, 4> AffineMap::compose(ArrayRef<int64_t> values) {
assert(getNumSymbols() == 0 && "Expected symbol-less map");
SmallVector<AffineExpr, 4> exprs;
exprs.reserve(values.size());
MLIRContext *ctx = getContext();
for (auto v : values)
exprs.push_back(getAffineConstantExpr(v, ctx));
auto resMap = compose(AffineMap::get(0, 0, exprs, ctx));
SmallVector<int64_t, 4> res;
res.reserve(resMap.getNumResults());
for (auto e : resMap.getResults())
res.push_back(e.cast<AffineConstantExpr>().getValue());
return res;
}
bool AffineMap::isProjectedPermutation() {
if (getNumSymbols() > 0)
return false;
SmallVector<bool, 8> seen(getNumInputs(), false);
for (auto expr : getResults()) {
if (auto dim = expr.dyn_cast<AffineDimExpr>()) {
if (seen[dim.getPosition()])
return false;
seen[dim.getPosition()] = true;
continue;
}
return false;
}
return true;
}
bool AffineMap::isPermutation() {
if (getNumDims() != getNumResults())
return false;
return isProjectedPermutation();
}
AffineMap AffineMap::getSubMap(ArrayRef<unsigned> resultPos) {
SmallVector<AffineExpr, 4> exprs;
exprs.reserve(resultPos.size());
for (auto idx : resultPos)
exprs.push_back(getResult(idx));
return AffineMap::get(getNumDims(), getNumSymbols(), exprs, getContext());
}
AffineMap AffineMap::getMajorSubMap(unsigned numResults) {
if (numResults == 0)
return AffineMap();
if (numResults > getNumResults())
return *this;
return getSubMap(llvm::to_vector<4>(llvm::seq<unsigned>(0, numResults)));
}
AffineMap AffineMap::getMinorSubMap(unsigned numResults) {
if (numResults == 0)
return AffineMap();
if (numResults > getNumResults())
return *this;
return getSubMap(llvm::to_vector<4>(
llvm::seq<unsigned>(getNumResults() - numResults, getNumResults())));
}
AffineMap mlir::simplifyAffineMap(AffineMap map) {
SmallVector<AffineExpr, 8> exprs;
for (auto e : map.getResults()) {
exprs.push_back(
simplifyAffineExpr(e, map.getNumDims(), map.getNumSymbols()));
}
return AffineMap::get(map.getNumDims(), map.getNumSymbols(), exprs,
map.getContext());
}
AffineMap mlir::removeDuplicateExprs(AffineMap map) {
auto results = map.getResults();
SmallVector<AffineExpr, 4> uniqueExprs(results.begin(), results.end());
uniqueExprs.erase(std::unique(uniqueExprs.begin(), uniqueExprs.end()),
uniqueExprs.end());
return AffineMap::get(map.getNumDims(), map.getNumSymbols(), uniqueExprs,
map.getContext());
}
AffineMap mlir::inversePermutation(AffineMap map) {
if (map.isEmpty())
return map;
assert(map.getNumSymbols() == 0 && "expected map without symbols");
SmallVector<AffineExpr, 4> exprs(map.getNumDims());
for (auto en : llvm::enumerate(map.getResults())) {
auto expr = en.value();
// Skip non-permutations.
if (auto d = expr.dyn_cast<AffineDimExpr>()) {
if (exprs[d.getPosition()])
continue;
exprs[d.getPosition()] = getAffineDimExpr(en.index(), d.getContext());
}
}
SmallVector<AffineExpr, 4> seenExprs;
seenExprs.reserve(map.getNumDims());
for (auto expr : exprs)
if (expr)
seenExprs.push_back(expr);
if (seenExprs.size() != map.getNumInputs())
return AffineMap();
return AffineMap::get(map.getNumResults(), 0, seenExprs, map.getContext());
}
AffineMap mlir::concatAffineMaps(ArrayRef<AffineMap> maps) {
unsigned numResults = 0, numDims = 0, numSymbols = 0;
for (auto m : maps)
numResults += m.getNumResults();
SmallVector<AffineExpr, 8> results;
results.reserve(numResults);
for (auto m : maps) {
for (auto res : m.getResults())
results.push_back(res.shiftSymbols(m.getNumSymbols(), numSymbols));
numSymbols += m.getNumSymbols();
numDims = std::max(m.getNumDims(), numDims);
}
return AffineMap::get(numDims, numSymbols, results,
maps.front().getContext());
}
AffineMap mlir::getProjectedMap(AffineMap map,
ArrayRef<unsigned> projectedDimensions) {
DenseSet<unsigned> projectedDims(projectedDimensions.begin(),
projectedDimensions.end());
MLIRContext *context = map.getContext();
SmallVector<AffineExpr, 4> resultExprs;
for (auto dim : enumerate(llvm::seq<unsigned>(0, map.getNumDims()))) {
if (!projectedDims.count(dim.value()))
resultExprs.push_back(getAffineDimExpr(dim.index(), context));
else
resultExprs.push_back(getAffineConstantExpr(0, context));
}
return map.compose(AffineMap::get(
map.getNumDims() - projectedDimensions.size(), 0, resultExprs, context));
}
//===----------------------------------------------------------------------===//
// MutableAffineMap.
//===----------------------------------------------------------------------===//
MutableAffineMap::MutableAffineMap(AffineMap map)
: numDims(map.getNumDims()), numSymbols(map.getNumSymbols()),
context(map.getContext()) {
for (auto result : map.getResults())
results.push_back(result);
}
void MutableAffineMap::reset(AffineMap map) {
results.clear();
numDims = map.getNumDims();
numSymbols = map.getNumSymbols();
context = map.getContext();
for (auto result : map.getResults())
results.push_back(result);
}
bool MutableAffineMap::isMultipleOf(unsigned idx, int64_t factor) const {
if (results[idx].isMultipleOf(factor))
return true;
// TODO: use simplifyAffineExpr and FlatAffineConstraints to
// complete this (for a more powerful analysis).
return false;
}
// Simplifies the result affine expressions of this map. The expressions have to
// be pure for the simplification implemented.
void MutableAffineMap::simplify() {
// Simplify each of the results if possible.
// TODO: functional-style map
for (unsigned i = 0, e = getNumResults(); i < e; i++) {
results[i] = simplifyAffineExpr(getResult(i), numDims, numSymbols);
}
}
AffineMap MutableAffineMap::getAffineMap() const {
return AffineMap::get(numDims, numSymbols, results, context);
}