//===- Utils.cpp - Utilities to support the Tensor dialect ----------------===// // // 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 // //===----------------------------------------------------------------------===// // // This file implements utilities for the Tensor dialect. // //===----------------------------------------------------------------------===// #include "mlir/Dialect/Tensor/Utils/Utils.h" #include "mlir/Dialect/Affine/IR/AffineOps.h" #include "mlir/Dialect/Arith/Utils/Utils.h" #include "mlir/Dialect/Utils/IndexingUtils.h" #include "mlir/Interfaces/ValueBoundsOpInterface.h" using namespace mlir; using namespace mlir::tensor; PadOp mlir::tensor::createPadHighOp(RankedTensorType resType, Value source, Value pad, bool nofold, Location loc, OpBuilder &b, ValueRange dynOutDims) { // This assumption simplifies the following logic without limiting what's // required _today_. If needed, we can relax it in the future. assert(((resType.getNumDynamicDims() == dynOutDims.size()) || dynOutDims.empty()) && "Either none or all output dynamic dims must be specified!"); // Init "low" and "high" padding values ("low" is kept as is, "high" is // computed below). SmallVector low(resType.getRank(), b.getIndexAttr(0)); SmallVector high(resType.getRank(), b.getIndexAttr(0)); size_t outDimIdx = 0; for (const auto [idx, val] : enumerate(resType.getShape())) { bool isDimDynamic = ShapedType::isDynamic(val); bool updatePadHigh = !isDimDynamic || !dynOutDims.empty(); // Keep the default padding width (i.e. "0") when the output dim is dynamic // and no actual output sizes have been provided. if (!updatePadHigh) continue; // Compute the padding width: resDim - sourceDim. AffineExpr d0, d1; bindDims(b.getContext(), d0, d1); OpFoldResult sourceDim = tensor::getMixedSize(b, loc, source, idx); OpFoldResult outDim = isDimDynamic ? OpFoldResult(dynOutDims[outDimIdx++]) : OpFoldResult(b.getIndexAttr(val)); high[idx] = affine::makeComposedFoldedAffineApply(b, loc, d0 - d1, {outDim, sourceDim}); } return PadOp::create(b, loc, resType, source, low, high, pad, nofold); } SmallVector mlir::tensor::createDynamicDimValues(OpBuilder &b, Location loc, Value rankedTensor) { auto tensorTy = cast(rankedTensor.getType()); SmallVector dynamicDims; for (const auto &en : llvm::enumerate(tensorTy.getShape())) { if (en.value() == ShapedType::kDynamic) dynamicDims.push_back( tensor::DimOp::create(b, loc, rankedTensor, en.index())); } return dynamicDims; } FailureOr mlir::tensor::computeTransposedType(RankedTensorType rankedTensorType, ArrayRef transposeVector) { if (transposeVector.empty()) return rankedTensorType; if (!isPermutationVector(transposeVector) || transposeVector.size() != static_cast(rankedTensorType.getRank())) return failure(); SmallVector transposedShape(rankedTensorType.getShape()); applyPermutationToVector(transposedShape, transposeVector); using RTTBuilder = RankedTensorType::Builder; RankedTensorType transposedTensorType = RTTBuilder(rankedTensorType).setShape(transposedShape); return transposedTensorType; } CollapseShapeOp mlir::tensor::dropGivenUnitDims(OpBuilder &b, Location loc, Value src, const llvm::SmallBitVector &dropDims) { auto srcType = cast(src.getType()); int64_t rank = srcType.getRank(); assert(rank == static_cast(dropDims.size()) && "dropDims dimension does not match src tensor rank"); assert(llvm::all_of( dropDims.set_bits(), [&](unsigned dim) { return srcType.getShape()[dim] == 1; }) && "Dropping non unit dimension"); // Computed reassociation map for the corresponding tensor.collapse_shape. SmallVector reassocMaps; // Current reassociation group to add dropped dimension to. int64_t nextDimToGroup = 0; llvm::SmallBitVector keptDims(dropDims); keptDims.flip(); int64_t lastSetBit = keptDims.find_last(); for (int64_t setBit : keptDims.set_bits()) { // Group consecutive dropped dimension with the next non-dropped dimension. // If this is the last set dimension, also group all subsequent dropped // dimension, if any. int64_t upTo = setBit == lastSetBit ? rank - 1 : setBit; auto seq = llvm::seq_inclusive(nextDimToGroup, upTo); reassocMaps.emplace_back(llvm::make_range(seq.begin(), seq.end())); nextDimToGroup = setBit + 1; } return tensor::CollapseShapeOp::create(b, loc, src, reassocMaps); } bool mlir::tensor::isCastLikeInsertSliceOp(InsertSliceOp op) { llvm::SmallBitVector droppedDims = op.getDroppedDims(); int64_t srcDim = 0; RankedTensorType resultType = op.getDestType(); // Source dims and destination dims (apart from dropped dims) must have the // same size. for (int64_t resultDim = 0; resultDim < resultType.getRank(); ++resultDim) { if (droppedDims.test(resultDim)) { // InsertSlice may expand unit dimensions that result from inserting a // size-1 slice into a non-size-1 result dimension. if (resultType.getDimSize(resultDim) != 1) return false; continue; } FailureOr equalDimSize = ValueBoundsConstraintSet::areEqual( {op.getSource(), srcDim}, {op.getResult(), resultDim}); if (failed(equalDimSize) || !*equalDimSize) return false; ++srcDim; } return true; } bool mlir::tensor::isCastLikeExtractSliceOp(ExtractSliceOp op) { llvm::SmallBitVector droppedDims = op.getDroppedDims(); int64_t resultDim = 0; // Source dims and result dims (apart from dropped dims) must have the same // size. RankedTensorType sourceType = op.getSourceType(); for (int64_t dim = 0, e = sourceType.getRank(); dim < e; ++dim) { if (droppedDims.test(dim)) { // ExtractSlice may drop unit dimensions that result from taking a size-1 // slice from a non-size-1 source dimension. if (sourceType.getDimSize(dim) != 1) return false; continue; } FailureOr equalDimSize = ValueBoundsConstraintSet::areEqual( {op.getSource(), dim}, {op.getResult(), resultDim}); if (failed(equalDimSize) || !*equalDimSize) return false; ++resultDim; } return true; }