| //===- MemorySlotOpInterfaceImpl.cpp - Mem2Reg for Vector ops -------------===// |
| // |
| // 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 memory slot interface models for |
| // `vector.transfer_read` and `vector.transfer_write`. These models implement |
| // `PromotableMemOpInterface` so Mem2Reg can replace vector transfers with uses |
| // and definitions of a vector SSA value. The models for `memref.subview` |
| // live in the MemRef dialect. |
| // |
| // A read uses the slot's current value; a write defines its next value. |
| // Static subviews use a vector matching the subview's shape: reads extract that |
| // sub-vector from the parent, and writes insert it back into the parent. |
| // Supported transfers through dynamic subviews use a vector type matching the |
| // whole static parent, and the subview's slot holds that whole vector. Reads |
| // pad lanes outside the runtime extent, and writes preserve the previous value |
| // of those lanes. `arith.select` implements this behavior and also handles |
| // lanes disabled by a transfer's mask operand. |
| // |
| // Transfers wrapped in `vector.mask` prevent promotion because that op does |
| // not implement `PromotableRegionOpInterface`, which Mem2Reg requires to |
| // promote accesses inside a region. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "mlir/Dialect/Vector/Transforms/MemorySlotOpInterfaceImpl.h" |
| |
| #include "mlir/Dialect/Arith/IR/Arith.h" |
| #include "mlir/Dialect/Arith/Utils/Utils.h" |
| #include "mlir/Dialect/MemRef/Transforms/MemorySlotOpInterfaceImpl.h" |
| #include "mlir/Dialect/Utils/StaticValueUtils.h" |
| #include "mlir/Dialect/Vector/IR/VectorOps.h" |
| #include "mlir/IR/BuiltinTypes.h" |
| #include "mlir/Interfaces/MemorySlotInterfaces.h" |
| |
| using namespace mlir; |
| using namespace mlir::vector; |
| |
| //===----------------------------------------------------------------------===// |
| // Utilities |
| //===----------------------------------------------------------------------===// |
| |
| /// Returns whether `xferOp` can be promoted to use or define `slot`'s vector |
| /// value. The transfer's memref base must be the slot pointer, its vector type |
| /// must equal `slot.valueType`, its indices must be zero, and its permutation |
| /// map must be the identity. Out-of-bounds dimensions are allowed only for a |
| /// supported dynamic subview. |
| /// |
| /// More general transfers may be supportable, but their value depends on |
| /// motivating use cases, implementation complexity, and generated code quality. |
| static bool |
| isPromotableTransfer(VectorTransferOpInterface xferOp, const MemorySlot &slot, |
| const SmallPtrSetImpl<OpOperand *> &blockingUses) { |
| // The transfer's memref base must be the slot pointer. |
| if (blockingUses.size() != 1) |
| return false; |
| Value blockingUse = (*blockingUses.begin())->get(); |
| if (blockingUse != slot.ptr || xferOp.getBase() != slot.ptr) |
| return false; |
| |
| // Reject the tensor form (already implied, since slot pointers are memrefs). |
| if (!isa<MemRefType>(xferOp.getBase().getType())) |
| return false; |
| |
| // Exact type match pins rank/extents/element type/scalable dims. |
| if (xferOp.getVectorType() != slot.valueType) |
| return false; |
| |
| // Access must start at the buffer origin in every dimension. |
| for (Value index : xferOp.getIndices()) { |
| std::optional<int64_t> constIndex = getConstantIntValue(index); |
| if (!constIndex || *constIndex != 0) |
| return false; |
| } |
| |
| // Identity map: no broadcast or transpose. |
| if (!xferOp.getPermutationMap().isIdentity()) |
| return false; |
| |
| // Out-of-bounds is allowed only for a dynamic view. |
| if (xferOp.hasOutOfBoundsDim() && !memref::isDynamicSubViewSlot(slot.ptr)) |
| return false; |
| |
| return true; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // PromotableMemOpInterface models |
| //===----------------------------------------------------------------------===// |
| |
| namespace { |
| /// `PromotableMemOpInterface` model for `vector.transfer_read`. |
| struct TransferReadOpMemOpModel |
| : public PromotableMemOpInterface::ExternalModel<TransferReadOpMemOpModel, |
| vector::TransferReadOp> { |
| bool loadsFrom(Operation *op, const MemorySlot &slot) const { |
| return cast<vector::TransferReadOp>(op).getBase() == slot.ptr; |
| } |
| |
| bool storesTo(Operation *op, const MemorySlot &slot) const { return false; } |
| |
| Value getStored(Operation *op, const MemorySlot &slot, OpBuilder &builder, |
| Value reachingDef, const DataLayout &dataLayout) const { |
| llvm_unreachable("getStored should not be called on TransferReadOp"); |
| } |
| |
| bool canUsesBeRemoved(Operation *op, const MemorySlot &slot, |
| const SmallPtrSetImpl<OpOperand *> &blockingUses, |
| SmallVectorImpl<OpOperand *> &newBlockingUses, |
| const DataLayout &dataLayout) const { |
| return isPromotableTransfer(cast<VectorTransferOpInterface>(op), slot, |
| blockingUses); |
| } |
| |
| // Replace the read with the reaching definition, selecting padding for lanes |
| // outside the subview's extent or disabled by the transfer's mask. |
| // PromotableAliaserInterface::projectSlotValueToAliasValue supplies the whole |
| // parent vector for a dynamic subview. Apply the extent mask here because |
| // this hook has access to the transfer's padding operand. If the transfer |
| // also has a mask operand, combine the two masks with arith.andi. |
| DeletionKind |
| removeBlockingUses(Operation *op, const MemorySlot &slot, |
| const SmallPtrSetImpl<OpOperand *> &blockingUses, |
| OpBuilder &builder, Value reachingDefinition, |
| const DataLayout &dataLayout) const { |
| auto readOp = cast<vector::TransferReadOp>(op); |
| Location loc = op->getLoc(); |
| Value mask = memref::buildDynamicSubViewMask(builder, loc, slot.ptr); |
| if (Value opMask = readOp.getMask()) |
| mask = mask |
| ? arith::AndIOp::create(builder, loc, mask, opMask).getResult() |
| : opMask; |
| |
| Value result = reachingDefinition; |
| if (mask) { |
| Value padSplat = vector::BroadcastOp::create( |
| builder, loc, readOp.getVectorType(), readOp.getPadding()); |
| result = arith::SelectOp::create(builder, loc, mask, reachingDefinition, |
| padSplat); |
| } |
| readOp.getVector().replaceAllUsesWith(result); |
| return DeletionKind::Delete; |
| } |
| }; |
| |
| /// `PromotableMemOpInterface` model for `vector.transfer_write`. |
| struct TransferWriteOpMemOpModel |
| : public PromotableMemOpInterface::ExternalModel<TransferWriteOpMemOpModel, |
| vector::TransferWriteOp> { |
| bool loadsFrom(Operation *op, const MemorySlot &slot) const { return false; } |
| |
| bool storesTo(Operation *op, const MemorySlot &slot) const { |
| return cast<vector::TransferWriteOp>(op).getBase() == slot.ptr; |
| } |
| |
| // The stored value is the transfer's vector, with inactive lanes preserved |
| // from the reaching definition when the transfer has a mask. |
| Value getStored(Operation *op, const MemorySlot &slot, OpBuilder &builder, |
| Value reachingDef, const DataLayout &dataLayout) const { |
| auto writeOp = cast<vector::TransferWriteOp>(op); |
| Value stored = writeOp.getValueToStore(); |
| // PromotableAliaserInterface::projectAliasValueToSlotValue separately |
| // preserves parent lanes outside a dynamic subview's extent. |
| if (Value mask = writeOp.getMask()) |
| stored = arith::SelectOp::create(builder, op->getLoc(), mask, stored, |
| reachingDef); |
| return stored; |
| } |
| |
| bool canUsesBeRemoved(Operation *op, const MemorySlot &slot, |
| const SmallPtrSetImpl<OpOperand *> &blockingUses, |
| SmallVectorImpl<OpOperand *> &newBlockingUses, |
| const DataLayout &dataLayout) const { |
| return isPromotableTransfer(cast<VectorTransferOpInterface>(op), slot, |
| blockingUses); |
| } |
| |
| // `getStored` already provided the value for later uses, so the write can |
| // simply be erased. |
| DeletionKind |
| removeBlockingUses(Operation *op, const MemorySlot &slot, |
| const SmallPtrSetImpl<OpOperand *> &blockingUses, |
| OpBuilder &builder, Value reachingDefinition, |
| const DataLayout &dataLayout) const { |
| return DeletionKind::Delete; |
| } |
| }; |
| } // namespace |
| |
| //===----------------------------------------------------------------------===// |
| // Register external models |
| //===----------------------------------------------------------------------===// |
| |
| void mlir::vector::registerMemorySlotExternalModels(DialectRegistry ®istry) { |
| registry.addExtension(+[](MLIRContext *ctx, vector::VectorDialect *dialect) { |
| TransferReadOp::attachInterface<TransferReadOpMemOpModel>(*ctx); |
| TransferWriteOp::attachInterface<TransferWriteOpMemOpModel>(*ctx); |
| }); |
| } |