| //===- IndexedAccessOpInterfaceImpl.cpp -----------------------------------===// | |
| // | |
| // 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 | |
| // | |
| //===----------------------------------------------------------------------===// | |
| // Implement IndexedAccessOpInterface on vector dialect operations with | |
| // %memref[%i, %j, ...] operands so generic memref-dialect passes can rewrite | |
| // their base/index pairs. Redundant leading unit vector dimensions are omitted | |
| // from the accessed shape and restored with vector.shape_casts when an alias | |
| // rewrite drops those dimensions. Transfer ops keep their | |
| // VectorTransferOpInterface patterns; gather/scatter have tensor-or-memref | |
| // bases and index-vector operands that do not fit IndexedAccessOpInterface's | |
| // rank-matched index contract. | |
| //===----------------------------------------------------------------------===// | |
| #include "mlir/Dialect/Vector/Transforms/IndexedAccessOpInterfaceImpl.h" | |
| #include "mlir/Dialect/MemRef/IR/MemoryAccessOpInterfaces.h" | |
| #include "mlir/Dialect/Vector/IR/VectorOps.h" | |
| #include "mlir/IR/Dialect.h" | |
| #include "mlir/IR/IRMapping.h" | |
| #include "mlir/IR/Operation.h" | |
| #include "mlir/IR/PatternMatch.h" | |
| #include <type_traits> | |
| using namespace mlir; | |
| using namespace mlir::memref; | |
| /// Return true if this op has the memref semantics expected by this model. | |
| template <typename LoadStoreOp> | |
| static bool hasMemrefSemantics(Operation *op) { | |
| return llvm::isa<MemRefType>(cast<LoadStoreOp>(op).getBase().getType()); | |
| } | |
| /// Return true if this op supports rank-0 vector operands/results. | |
| template <typename LoadStoreOp> | |
| static constexpr bool supportsRankZeroVectorAccess() { | |
| return std::is_same_v<LoadStoreOp, vector::LoadOp> || | |
| std::is_same_v<LoadStoreOp, vector::StoreOp>; | |
| } | |
| /// Return the number of leading static unit dimensions in `vecTy`. | |
| static unsigned getNumLeadingUnitDims(VectorType vecTy) { | |
| unsigned numLeadingUnitDims = 0; | |
| for (auto [size, scalable] : | |
| llvm::zip_equal(vecTy.getShape(), vecTy.getScalableDims())) { | |
| if (size != 1 || scalable) | |
| break; | |
| ++numLeadingUnitDims; | |
| } | |
| return numLeadingUnitDims; | |
| } | |
| /// Return the vector shape whose access strides must be preserved, omitting | |
| /// redundant leading static unit dimensions and marking scalable dimensions as | |
| /// dynamic. If the op cannot access rank-0 vectors, preserve one trailing unit | |
| /// dimension instead of returning an empty shape. | |
| static SmallVector<int64_t> getAccessedVectorShape(VectorType vecTy, | |
| bool supportsRankZero) { | |
| unsigned numLeadingUnitDims = getNumLeadingUnitDims(vecTy); | |
| unsigned rank = static_cast<unsigned>(vecTy.getRank()); | |
| if (!supportsRankZero && numLeadingUnitDims == rank) | |
| --numLeadingUnitDims; | |
| return llvm::map_to_vector( | |
| llvm::zip_equal(vecTy.getShape().drop_front(numLeadingUnitDims), | |
| vecTy.getScalableDims().drop_front(numLeadingUnitDims)), | |
| [](auto dim) { | |
| auto [size, scalable] = dim; | |
| return scalable ? ShapedType::kDynamic : size; | |
| }); | |
| } | |
| /// Return `vecTy` with `numLeadingDims` dimensions dropped from the front. | |
| static VectorType dropLeadingDims(VectorType vecTy, unsigned numLeadingDims) { | |
| return VectorType::get(vecTy.getShape().drop_front(numLeadingDims), | |
| vecTy.getElementType(), | |
| vecTy.getScalableDims().drop_front(numLeadingDims)); | |
| } | |
| /// Return the shape-cast type for vector operands that match `vecTy`. | |
| static std::optional<VectorType> | |
| getShapeCastTypeForOperand(Value operand, VectorType vecTy, | |
| unsigned numLeadingDims) { | |
| auto operandTy = dyn_cast<VectorType>(operand.getType()); | |
| if (!operandTy || operandTy.getShape() != vecTy.getShape() || | |
| operandTy.getScalableDims() != vecTy.getScalableDims()) | |
| return std::nullopt; | |
| return dropLeadingDims(operandTy, numLeadingDims); | |
| } | |
| namespace { | |
| template <typename LoadStoreOp> | |
| struct VectorLoadStoreLikeOpImpl final | |
| : IndexedAccessOpInterface::ExternalModel< | |
| VectorLoadStoreLikeOpImpl<LoadStoreOp>, LoadStoreOp> { | |
| TypedValue<MemRefType> getAccessedMemref(Operation *op) const { | |
| return cast<LoadStoreOp>(op).getBase(); | |
| } | |
| Operation::operand_range getIndices(Operation *op) const { | |
| return cast<LoadStoreOp>(op).getIndices(); | |
| } | |
| SmallVector<int64_t> getAccessedShape(Operation *op) const { | |
| assert(hasMemrefSemantics<LoadStoreOp>(op) && | |
| "expected vector op with memref semantics"); | |
| return getAccessedVectorShape(cast<LoadStoreOp>(op).getVectorType(), | |
| supportsRankZeroVectorAccess<LoadStoreOp>()); | |
| } | |
| std::optional<SmallVector<Value>> | |
| updateMemrefAndIndices(Operation *op, RewriterBase &rewriter, Value newMemref, | |
| ValueRange newIndices) const { | |
| assert(hasMemrefSemantics<LoadStoreOp>(op) && | |
| "expected vector op with memref semantics"); | |
| assert(llvm::isa<MemRefType>(newMemref.getType()) && | |
| "expected replacement memref"); | |
| VectorType vecTy = cast<LoadStoreOp>(op).getVectorType(); | |
| if (static_cast<int64_t>(newIndices.size()) >= vecTy.getRank()) { | |
| rewriter.modifyOpInPlace(op, [&]() { | |
| auto concreteOp = cast<LoadStoreOp>(op); | |
| concreteOp.getBaseMutable().assign(newMemref); | |
| concreteOp.getIndicesMutable().assign(newIndices); | |
| }); | |
| return std::nullopt; | |
| } | |
| unsigned numLeadingDimsToDrop = static_cast<unsigned>( | |
| vecTy.getRank() - static_cast<int64_t>(newIndices.size())); | |
| assert(numLeadingDimsToDrop <= getNumLeadingUnitDims(vecTy) && | |
| "expected only redundant leading unit dimensions to be dropped"); | |
| IRMapping dropDimsMap; | |
| for (Value operand : op->getOperands()) { | |
| std::optional<VectorType> castTy = | |
| getShapeCastTypeForOperand(operand, vecTy, numLeadingDimsToDrop); | |
| if (!castTy || dropDimsMap.lookupOrNull(operand)) | |
| continue; | |
| Value castedOperand = vector::ShapeCastOp::create( | |
| rewriter, operand.getLoc(), *castTy, operand); | |
| dropDimsMap.map(operand, castedOperand); | |
| } | |
| if (op->getNumResults() == 1) { | |
| // Result types cannot be changed in place on the original op because the | |
| // caller replaces it using the returned value. Clone at the lower rank, | |
| // then cast the result back to the original vector type. | |
| VectorType droppedDimsTy = dropLeadingDims(vecTy, numLeadingDimsToDrop); | |
| Operation *newOp = rewriter.clone(*op, dropDimsMap); | |
| rewriter.modifyOpInPlace(newOp, [&]() { | |
| auto concreteOp = cast<LoadStoreOp>(newOp); | |
| concreteOp.getBaseMutable().assign(newMemref); | |
| concreteOp.getIndicesMutable().assign(newIndices); | |
| newOp->getResult(0).setType(droppedDimsTy); | |
| }); | |
| Value castBack = vector::ShapeCastOp::create(rewriter, newOp->getLoc(), | |
| vecTy, newOp->getResult(0)); | |
| return {{castBack}}; | |
| } | |
| // Store-like ops have no results to replace, so update their vector | |
| // operands and base/index pair in place. | |
| rewriter.modifyOpInPlace(op, [&]() { | |
| auto concreteOp = cast<LoadStoreOp>(op); | |
| concreteOp.getBaseMutable().assign(newMemref); | |
| concreteOp.getIndicesMutable().assign(newIndices); | |
| for (OpOperand &operand : op->getOpOperands()) { | |
| if (Value replacement = dropDimsMap.lookupOrNull(operand.get())) | |
| operand.set(replacement); | |
| } | |
| }); | |
| return std::nullopt; | |
| } | |
| // TODO: The various load and store operations, at the very least vector.load | |
| // and vector.store, should be taught a starts-in-bounds attribute that would | |
| // let us optimize index generation. | |
| bool hasInboundsIndices(Operation *op) const { | |
| assert(hasMemrefSemantics<LoadStoreOp>(op) && | |
| "expected vector op with memref semantics"); | |
| return false; | |
| } | |
| }; | |
| template <typename... Ops> | |
| static void attachLoadStoreLike(MLIRContext *ctx) { | |
| (Ops::template attachInterface<VectorLoadStoreLikeOpImpl<Ops>>(*ctx), ...); | |
| } | |
| } // namespace | |
| void mlir::vector::registerIndexedAccessOpInterfaceExternalModels( | |
| DialectRegistry ®istry) { | |
| registry.addExtension(+[](MLIRContext *ctx, vector::VectorDialect *dialect) { | |
| attachLoadStoreLike<vector::LoadOp, vector::StoreOp, vector::MaskedLoadOp, | |
| vector::MaskedStoreOp, vector::ExpandLoadOp, | |
| vector::CompressStoreOp>(ctx); | |
| }); | |
| } |