| //===- 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/SmallBitVector.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 getNumDims() >= getNumResults() && |
| *this == |
| getMinorIdentityMap(getNumDims(), getNumResults(), getContext()); |
| } |
| |
| /// Returns true if this affine map is a minor identity up to broadcasted |
| /// dimensions which are indicated by value 0 in the result. |
| bool AffineMap::isMinorIdentityWithBroadcasting( |
| SmallVectorImpl<unsigned> *broadcastedDims) const { |
| if (broadcastedDims) |
| broadcastedDims->clear(); |
| if (getNumDims() < getNumResults()) |
| return false; |
| unsigned suffixStart = getNumDims() - getNumResults(); |
| for (auto idxAndExpr : llvm::enumerate(getResults())) { |
| unsigned resIdx = idxAndExpr.index(); |
| AffineExpr expr = idxAndExpr.value(); |
| if (auto constExpr = expr.dyn_cast<AffineConstantExpr>()) { |
| // Each result may be either a constant 0 (broadcasted dimension). |
| if (constExpr.getValue() != 0) |
| return false; |
| if (broadcastedDims) |
| broadcastedDims->push_back(resIdx); |
| } else if (auto dimExpr = expr.dyn_cast<AffineDimExpr>()) { |
| // Or it may be the input dimension corresponding to this result position. |
| if (dimExpr.getPosition() != suffixStart + resIdx) |
| return false; |
| } else { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /// Return true if this affine map can be converted to a minor identity with |
| /// broadcast by doing a permute. Return a permutation (there may be |
| /// several) to apply to get to a minor identity with broadcasts. |
| /// Ex: |
| /// * (d0, d1, d2) -> (0, d1) maps to minor identity (d1, 0 = d2) with |
| /// perm = [1, 0] and broadcast d2 |
| /// * (d0, d1, d2) -> (d0, 0) cannot be mapped to a minor identity by |
| /// permutation + broadcast |
| /// * (d0, d1, d2, d3) -> (0, d1, d3) maps to minor identity (d1, 0 = d2, d3) |
| /// with perm = [1, 0, 2] and broadcast d2 |
| /// * (d0, d1) -> (d1, 0, 0, d0) maps to minor identity (d0, d1) with extra |
| /// leading broadcat dimensions. The map returned would be (0, 0, d0, d1) with |
| /// perm = [3, 0, 1, 2] |
| bool AffineMap::isPermutationOfMinorIdentityWithBroadcasting( |
| SmallVectorImpl<unsigned> &permutedDims) const { |
| unsigned projectionStart = |
| getNumResults() < getNumInputs() ? getNumInputs() - getNumResults() : 0; |
| permutedDims.clear(); |
| SmallVector<unsigned> broadcastDims; |
| permutedDims.resize(getNumResults(), 0); |
| // If there are more results than input dimensions we want the new map to |
| // start with broadcast dimensions in order to be a minor identity with |
| // broadcasting. |
| unsigned leadingBroadcast = |
| getNumResults() > getNumInputs() ? getNumResults() - getNumInputs() : 0; |
| llvm::SmallBitVector dimFound(std::max(getNumInputs(), getNumResults()), |
| false); |
| for (auto idxAndExpr : llvm::enumerate(getResults())) { |
| unsigned resIdx = idxAndExpr.index(); |
| AffineExpr expr = idxAndExpr.value(); |
| // Each result may be either a constant 0 (broadcast dimension) or a |
| // dimension. |
| if (auto constExpr = expr.dyn_cast<AffineConstantExpr>()) { |
| if (constExpr.getValue() != 0) |
| return false; |
| broadcastDims.push_back(resIdx); |
| } else if (auto dimExpr = expr.dyn_cast<AffineDimExpr>()) { |
| if (dimExpr.getPosition() < projectionStart) |
| return false; |
| unsigned newPosition = |
| dimExpr.getPosition() - projectionStart + leadingBroadcast; |
| permutedDims[resIdx] = newPosition; |
| dimFound[newPosition] = true; |
| } else { |
| return false; |
| } |
| } |
| // Find a permuation for the broadcast dimension. Since they are broadcasted |
| // any valid permutation is acceptable. We just permute the dim into a slot |
| // without an existing dimension. |
| unsigned pos = 0; |
| for (auto dim : broadcastDims) { |
| while (pos < dimFound.size() && dimFound[pos]) { |
| pos++; |
| } |
| permutedDims[dim] = pos++; |
| } |
| return true; |
| } |
| |
| /// 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 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>(); |
| } |
| |
| bool AffineMap::isConstant() const { |
| return llvm::all_of(getResults(), [](AffineExpr expr) { |
| return expr.isa<AffineConstantExpr>(); |
| }); |
| } |
| |
| int64_t AffineMap::getSingleConstantResult() const { |
| assert(isSingleConstant() && "map must have a single constant result"); |
| return getResult(0).cast<AffineConstantExpr>().getValue(); |
| } |
| |
| SmallVector<int64_t> AffineMap::getConstantResults() const { |
| assert(isConstant() && "map must have only constant results"); |
| SmallVector<int64_t> result; |
| for (auto expr : getResults()) |
| result.emplace_back(expr.cast<AffineConstantExpr>().getValue()); |
| return result; |
| } |
| |
| 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(); |
| } |
| |
| unsigned AffineMap::getPermutedPosition(unsigned input) const { |
| assert(isPermutation() && "invalid permutation request"); |
| for (unsigned i = 0, numResults = getNumResults(); i < numResults; i++) |
| if (getDimPosition(i) == input) |
| return i; |
| llvm_unreachable("incorrect permutation request"); |
| } |
| |
| /// 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::replace(const DenseMap<AffineExpr, AffineExpr> &map) const { |
| SmallVector<AffineExpr, 4> newResults; |
| newResults.reserve(getNumResults()); |
| for (AffineExpr e : getResults()) |
| newResults.push_back(e.replace(map)); |
| return AffineMap::inferFromExprList(newResults).front(); |
| } |
| |
| AffineMap AffineMap::compose(AffineMap map) const { |
| 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) const { |
| 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(bool allowZeroInResults) const { |
| if (getNumSymbols() > 0) |
| return false; |
| |
| // Having more results than inputs means that results have duplicated dims or |
| // zeros that can't be mapped to input dims. |
| if (getNumResults() > getNumInputs()) |
| return false; |
| |
| SmallVector<bool, 8> seen(getNumInputs(), false); |
| // A projected permutation can have, at most, only one instance of each input |
| // dimension in the result expressions. Zeros are allowed as long as the |
| // number of result expressions is lower or equal than the number of input |
| // expressions. |
| for (auto expr : getResults()) { |
| if (auto dim = expr.dyn_cast<AffineDimExpr>()) { |
| if (seen[dim.getPosition()]) |
| return false; |
| seen[dim.getPosition()] = true; |
| } else { |
| auto constExpr = expr.dyn_cast<AffineConstantExpr>(); |
| if (!allowZeroInResults || !constExpr || constExpr.getValue() != 0) |
| return false; |
| } |
| } |
| |
| // Results are either dims or zeros and zeros can be mapped to input dims. |
| return true; |
| } |
| |
| bool AffineMap::isPermutation() const { |
| if (getNumDims() != getNumResults()) |
| return false; |
| return isProjectedPermutation(); |
| } |
| |
| AffineMap AffineMap::getSubMap(ArrayRef<unsigned> resultPos) const { |
| 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::getSliceMap(unsigned start, unsigned length) const { |
| return AffineMap::get(getNumDims(), getNumSymbols(), |
| getResults().slice(start, length), getContext()); |
| } |
| |
| AffineMap AffineMap::getMajorSubMap(unsigned numResults) const { |
| 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) const { |
| if (numResults == 0) |
| return AffineMap(); |
| if (numResults > getNumResults()) |
| return *this; |
| return getSubMap(llvm::to_vector<4>( |
| llvm::seq<unsigned>(getNumResults() - numResults, getNumResults()))); |
| } |
| |
| AffineMap mlir::compressDims(AffineMap map, |
| const llvm::SmallDenseSet<unsigned> &unusedDims) { |
| unsigned numDims = 0; |
| SmallVector<AffineExpr> dimReplacements; |
| dimReplacements.reserve(map.getNumDims()); |
| MLIRContext *context = map.getContext(); |
| for (unsigned dim = 0, e = map.getNumDims(); dim < e; ++dim) { |
| if (unusedDims.contains(dim)) |
| dimReplacements.push_back(getAffineConstantExpr(0, context)); |
| else |
| dimReplacements.push_back(getAffineDimExpr(numDims++, context)); |
| } |
| SmallVector<AffineExpr> resultExprs; |
| resultExprs.reserve(map.getNumResults()); |
| for (auto e : map.getResults()) |
| resultExprs.push_back(e.replaceDims(dimReplacements)); |
| return AffineMap::get(numDims, map.getNumSymbols(), resultExprs, context); |
| } |
| |
| AffineMap mlir::compressUnusedDims(AffineMap map) { |
| llvm::SmallDenseSet<unsigned> usedDims; |
| map.walkExprs([&](AffineExpr expr) { |
| if (auto dimExpr = expr.dyn_cast<AffineDimExpr>()) |
| usedDims.insert(dimExpr.getPosition()); |
| }); |
| llvm::SmallDenseSet<unsigned> unusedDims; |
| for (unsigned d = 0, e = map.getNumDims(); d != e; ++d) |
| if (!usedDims.contains(d)) |
| unusedDims.insert(d); |
| return compressDims(map, unusedDims); |
| } |
| |
| static SmallVector<AffineMap> |
| compressUnusedImpl(ArrayRef<AffineMap> maps, |
| llvm::function_ref<AffineMap(AffineMap)> compressionFun) { |
| if (maps.empty()) |
| return SmallVector<AffineMap>(); |
| SmallVector<AffineExpr> allExprs; |
| allExprs.reserve(maps.size() * maps.front().getNumResults()); |
| unsigned numDims = maps.front().getNumDims(), |
| numSymbols = maps.front().getNumSymbols(); |
| for (auto m : maps) { |
| assert(numDims == m.getNumDims() && numSymbols == m.getNumSymbols() && |
| "expected maps with same num dims and symbols"); |
| llvm::append_range(allExprs, m.getResults()); |
| } |
| AffineMap unifiedMap = compressionFun( |
| AffineMap::get(numDims, numSymbols, allExprs, maps.front().getContext())); |
| unsigned unifiedNumDims = unifiedMap.getNumDims(), |
| unifiedNumSymbols = unifiedMap.getNumSymbols(); |
| ArrayRef<AffineExpr> unifiedResults = unifiedMap.getResults(); |
| SmallVector<AffineMap> res; |
| res.reserve(maps.size()); |
| for (auto m : maps) { |
| res.push_back(AffineMap::get(unifiedNumDims, unifiedNumSymbols, |
| unifiedResults.take_front(m.getNumResults()), |
| m.getContext())); |
| unifiedResults = unifiedResults.drop_front(m.getNumResults()); |
| } |
| return res; |
| } |
| |
| SmallVector<AffineMap> mlir::compressUnusedDims(ArrayRef<AffineMap> maps) { |
| return compressUnusedImpl(maps, |
| [](AffineMap m) { return compressUnusedDims(m); }); |
| } |
| |
| AffineMap |
| mlir::compressSymbols(AffineMap map, |
| const llvm::SmallDenseSet<unsigned> &unusedSymbols) { |
| unsigned numSymbols = 0; |
| SmallVector<AffineExpr> symReplacements; |
| symReplacements.reserve(map.getNumSymbols()); |
| MLIRContext *context = map.getContext(); |
| for (unsigned sym = 0, e = map.getNumSymbols(); sym < e; ++sym) { |
| if (unusedSymbols.contains(sym)) |
| symReplacements.push_back(getAffineConstantExpr(0, context)); |
| else |
| symReplacements.push_back(getAffineSymbolExpr(numSymbols++, context)); |
| } |
| SmallVector<AffineExpr> resultExprs; |
| resultExprs.reserve(map.getNumResults()); |
| for (auto e : map.getResults()) |
| resultExprs.push_back(e.replaceSymbols(symReplacements)); |
| return AffineMap::get(map.getNumDims(), numSymbols, resultExprs, context); |
| } |
| |
| AffineMap mlir::compressUnusedSymbols(AffineMap map) { |
| llvm::SmallDenseSet<unsigned> usedSymbols; |
| map.walkExprs([&](AffineExpr expr) { |
| if (auto symExpr = expr.dyn_cast<AffineSymbolExpr>()) |
| usedSymbols.insert(symExpr.getPosition()); |
| }); |
| llvm::SmallDenseSet<unsigned> unusedSymbols; |
| for (unsigned d = 0, e = map.getNumSymbols(); d != e; ++d) |
| if (!usedSymbols.contains(d)) |
| unusedSymbols.insert(d); |
| return compressSymbols(map, unusedSymbols); |
| } |
| |
| SmallVector<AffineMap> mlir::compressUnusedSymbols(ArrayRef<AffineMap> maps) { |
| return compressUnusedImpl( |
| maps, [](AffineMap m) { return compressUnusedSymbols(m); }); |
| } |
| |
| 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::inverseAndBroadcastProjectedPermuation(AffineMap map) { |
| assert(map.isProjectedPermutation(/*allowZeroInResults=*/true)); |
| MLIRContext *context = map.getContext(); |
| AffineExpr zero = mlir::getAffineConstantExpr(0, context); |
| // Start with all the results as 0. |
| SmallVector<AffineExpr, 4> exprs(map.getNumInputs(), zero); |
| for (unsigned i : llvm::seq(unsigned(0), map.getNumResults())) { |
| // Skip zeros from input map. 'exprs' is already initialized to zero. |
| if (auto constExpr = map.getResult(i).dyn_cast<AffineConstantExpr>()) { |
| assert(constExpr.getValue() == 0 && |
| "Unexpected constant in projected permutation"); |
| (void)constExpr; |
| continue; |
| } |
| |
| // Reverse each dimension existing in the original map result. |
| exprs[map.getDimPosition(i)] = getAffineDimExpr(i, context); |
| } |
| return AffineMap::get(map.getNumResults(), /*symbolCount=*/0, exprs, context); |
| } |
| |
| 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, |
| const llvm::SmallDenseSet<unsigned> &unusedDims) { |
| return compressUnusedSymbols(compressDims(map, unusedDims)); |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // 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); |
| } |