| //===- MemorySlotOpInterfaceImpl.cpp - Mem2Reg for MemRef 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 used by Mem2Reg to promote |
| // a memref to a single vector SSA value. `PromotableAliaserInterface` exposes |
| // `memref.subview` aliases, and `PromotableOpInterface` removes the subviews |
| // after promotion. Vector transfer ops are handled by the models in the |
| // Vector dialect. |
| // |
| // A memory slot pairs a memref with the vector type used to represent its |
| // contents. Promotion replaces accesses to the slot with uses and definitions |
| // of that vector value. All uses, including those through subviews, must be |
| // supported by the models. |
| // |
| // The accesses are rewritten as follows: |
| // |
| // * A same-rank, unit-stride `memref.subview` gets an alias slot that is |
| // promoted together with the parent: |
| // |
| // - A static subview holds the corresponding sub-vector. Reads extract it |
| // with `vector.extract_strided_slice`; writes update the parent with |
| // `vector.insert_strided_slice`. |
| // |
| // - A dynamic subview holds the whole parent vector because vector shapes |
| // must be static. `vector.create_mask` and `arith.select` restrict |
| // accesses to the subview's extent. The subview must start at the |
| // parent's origin. |
| // |
| // Subviews require a statically shaped parent buffer. A dynamically |
| // shaped buffer can instead promote to a 1-D scalable vector through |
| // whole-buffer transfers, but subviews are not supported. |
| // |
| // Unsupported accesses, such as dynamic subview offsets, rank reduction, |
| // non-unit subview strides, or non-zero transfer indices, prevent promotion. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "mlir/Dialect/MemRef/Transforms/MemorySlotOpInterfaceImpl.h" |
| |
| #include "mlir/Dialect/Arith/Utils/Utils.h" |
| #include "mlir/Dialect/MemRef/IR/MemRef.h" |
| #include "mlir/Dialect/MemRef/IR/MemRefDialect.h" |
| #include "mlir/Dialect/Vector/IR/VectorOps.h" |
| #include "mlir/Interfaces/MemorySlotInterfaces.h" |
| |
| using namespace mlir; |
| using namespace mlir::memref; |
| using namespace mlir::vector; |
| |
| //===----------------------------------------------------------------------===// |
| // Utilities |
| //===----------------------------------------------------------------------===// |
| |
| /// Checks the requirements shared by static and dynamic subview aliases: a |
| /// parent with a static shape and a valid vector element type, no rank |
| /// reduction, and unit slice strides. |
| static bool isUnitStrideSliceOfStaticParent(memref::SubViewOp subView) { |
| MemRefType srcType = subView.getSourceType(); |
| // vector requires a static parent shape. |
| if (!srcType.hasStaticShape()) |
| return false; |
| |
| // implementation limitation: vector.extract_strided_slice must have the same |
| // rank (static). Alternative vector.extract could be used but only drop |
| // leading dimensions. |
| if (srcType.getRank() != subView.getType().getRank()) |
| return false; |
| |
| // No straightforward mapping to vector code for non-unit stride slices. |
| if (!subView.hasUnitStride()) |
| return false; |
| |
| return VectorType::isValidElementType(srcType.getElementType()); |
| } |
| |
| /// Returns whether static `subView` can alias a slice of the parent vector |
| /// using `vector.extract_strided_slice` and `vector.insert_strided_slice`. |
| /// Requires static source and result shapes, constant offsets, unit strides, |
| /// and no rank reduction. |
| static bool isAliasableStaticShapeSubView(memref::SubViewOp subView) { |
| if (!subView.getType().hasStaticShape()) |
| return false; |
| |
| if (!isUnitStrideSliceOfStaticParent(subView)) |
| return false; |
| |
| // extract/insert_strided_slice requires all offsets to be constant. |
| return llvm::all_of(subView.getMixedOffsets(), [](OpFoldResult offset) { |
| return getConstantIntValue(offset).has_value(); |
| }); |
| } |
| |
| /// Returns whether dynamic `subView` can alias the whole parent vector. The |
| /// alias uses the parent vector's shape because vector dimensions cannot be |
| /// dynamic. Requires a static source shape, a dynamic result shape, zero |
| /// offsets, unit strides, and no rank reduction. |
| static bool isAliasableDynamicShapeSubView(memref::SubViewOp subView) { |
| if (subView.getType().hasStaticShape()) |
| return false; |
| |
| if (!isUnitStrideSliceOfStaticParent(subView)) |
| return false; |
| |
| /// TODO: Allow non-zero offsets by improving buildSubViewMask(). |
| return subView.hasZeroOffset(); |
| } |
| |
| /// Returns the static offsets of `subView` within the parent vector for |
| /// `vector.extract_strided_slice` and `vector.insert_strided_slice`. |
| static SmallVector<int64_t> getStaticSubViewOffsets(memref::SubViewOp subView) { |
| SmallVector<int64_t> offsets; |
| for (OpFoldResult offset : subView.getMixedOffsets()) { |
| std::optional<int64_t> value = getConstantIntValue(offset); |
| assert(value && "expected a static offset"); |
| offsets.push_back(*value); |
| } |
| return offsets; |
| } |
| |
| /// The vector type covering `subView`'s whole parent buffer, which is the type |
| /// the alias slot of a dynamically-shaped subview takes. |
| static VectorType getWholeParentVectorType(memref::SubViewOp subView) { |
| auto parentType = cast<MemRefType>(subView.getSource().getType()); |
| assert(parentType.hasStaticShape() && "expected a statically-shaped parent"); |
| return VectorType::get(parentType.getShape(), parentType.getElementType()); |
| } |
| |
| /// If `slotPtr` is a dynamic subview, returns it. |
| static memref::SubViewOp getDynamicSubView(Value slotPtr) { |
| auto subView = slotPtr.getDefiningOp<memref::SubViewOp>(); |
| if (subView && isAliasableDynamicShapeSubView(subView)) |
| return subView; |
| return {}; |
| } |
| |
| /// Builds the mask of `subView`'s valid region: a `vector.create_mask` of the |
| /// subview's sizes, in the shape of the whole parent that its alias holds. |
| static Value buildSubViewMask(OpBuilder &builder, Location loc, |
| memref::SubViewOp subView) { |
| VectorType parentVecType = getWholeParentVectorType(subView); |
| SmallVector<Value> bounds = |
| getValueOrCreateConstantIndexOp(builder, loc, subView.getMixedSizes()); |
| return vector::CreateMaskOp::create( |
| builder, loc, |
| VectorType::get(parentVecType.getShape(), builder.getI1Type()), bounds); |
| } |
| |
| bool mlir::memref::isDynamicSubViewSlot(Value slotPtr) { |
| return static_cast<bool>(getDynamicSubView(slotPtr)); |
| } |
| |
| Value mlir::memref::buildDynamicSubViewMask(OpBuilder &builder, Location loc, |
| Value slotPtr) { |
| memref::SubViewOp subView = getDynamicSubView(slotPtr); |
| if (!subView) |
| return {}; |
| return buildSubViewMask(builder, loc, subView); |
| } |
| |
| namespace { |
| //===----------------------------------------------------------------------===// |
| // PromotableOpInterface model for memref.subview |
| //===----------------------------------------------------------------------===// |
| |
| /// `PromotableOpInterface` model for `memref.subview`. Works with the |
| /// `PromotableAliaserInterface` model below to remove the subview once its |
| /// result's uses have been promoted. |
| struct SubViewOpPromotableModel |
| : public PromotableOpInterface::ExternalModel<SubViewOpPromotableModel, |
| memref::SubViewOp> { |
| bool canUsesBeRemoved(Operation *op, |
| const SmallPtrSetImpl<OpOperand *> &blockingUses, |
| SmallVectorImpl<OpOperand *> &newBlockingUses, |
| const DataLayout &dataLayout) const { |
| // Removing the subview requires removing all uses of its memref result. |
| for (OpOperand &use : op->getResult(0).getUses()) |
| newBlockingUses.push_back(&use); |
| return true; |
| } |
| |
| DeletionKind |
| removeBlockingUses(Operation *op, |
| const SmallPtrSetImpl<OpOperand *> &blockingUses, |
| OpBuilder &builder) const { |
| return DeletionKind::Delete; |
| } |
| }; |
| |
| //===----------------------------------------------------------------------===// |
| // PromotableAliaserInterface model for memref.subview |
| //===----------------------------------------------------------------------===// |
| |
| /// `PromotableAliaserInterface` model exposing a same-rank `memref.subview` as |
| /// an alias of a vector slot. |
| /// |
| /// `projectSlotValueToAliasValue` provides the alias value for loads and for a |
| /// store's `getStored` hook. `projectAliasValueToSlotValue` merges the stored |
| /// alias value back into the parent. |
| /// |
| /// Static subviews use `vector.extract_strided_slice` and |
| /// `vector.insert_strided_slice`. Dynamic subviews keep the whole parent |
| /// vector: loads use it directly, and stores use `arith.select` with a mask of |
| /// the subview's sizes to preserve the parent value outside the subview. |
| struct SubViewOpAliasModel |
| : public PromotableAliaserInterface::ExternalModel<SubViewOpAliasModel, |
| memref::SubViewOp> { |
| void getPromotableSlotAliases(Operation *op, |
| OpOperand &aliasedSlotPointerOperand, |
| const MemorySlot &parentSlot, |
| SmallVectorImpl<MemorySlot> &newSlots) const { |
| auto subView = cast<memref::SubViewOp>(op); |
| // Called once per operand holding the slot pointer; only the viewed source |
| // exposes an alias. |
| if (aliasedSlotPointerOperand.get() != subView.getSource()) |
| return; |
| |
| // The parent slot must promote to a vector. A scalar (single-element) |
| // parent slot cannot be sliced. |
| auto parentVecType = dyn_cast<VectorType>(parentSlot.valueType); |
| if (!parentVecType) |
| return; |
| |
| // A static slice aliases the sub-vector matching the subview's shape. |
| if (isAliasableStaticShapeSubView(subView)) { |
| auto resType = cast<MemRefType>(subView.getResult().getType()); |
| VectorType aliasVecType = |
| VectorType::get(resType.getShape(), resType.getElementType()); |
| newSlots.push_back(MemorySlot{subView.getResult(), aliasVecType}); |
| } |
| |
| // A dynamic slice aliases the whole parent value. |
| if (isAliasableDynamicShapeSubView(subView)) { |
| newSlots.push_back(MemorySlot{subView.getResult(), parentVecType}); |
| } |
| } |
| |
| Value projectSlotValueToAliasValue(Operation *op, |
| OpOperand & /*aliasedSlotPointerOperand*/, |
| const MemorySlot & /*parentSlot*/, |
| const MemorySlot &aliasSlot, |
| Value slotValue, |
| OpBuilder &builder) const { |
| auto subView = cast<memref::SubViewOp>(op); |
| // Identity: the alias holds the whole parent value. Masking is left to |
| // `TransferReadOpMemOpModel::removeBlockingUses`, which unlike this hook |
| // can see the consuming read's padding value. |
| if (isAliasableDynamicShapeSubView(subView)) |
| return slotValue; |
| |
| SmallVector<int64_t> offsets = getStaticSubViewOffsets(subView); |
| auto aliasVecType = cast<VectorType>(aliasSlot.valueType); |
| SmallVector<int64_t> strides(offsets.size(), 1); |
| return vector::ExtractStridedSliceOp::create( |
| builder, op->getLoc(), slotValue, offsets, |
| aliasVecType.getShape(), strides) |
| .getResult(); |
| } |
| |
| Value projectAliasValueToSlotValue(Operation *op, |
| OpOperand & /*aliasedSlotPointerOperand*/, |
| const MemorySlot & /*parentSlot*/, |
| const MemorySlot & /*aliasSlot*/, |
| Value aliasValue, Value reachingDef, |
| OpBuilder &builder) const { |
| auto subView = cast<memref::SubViewOp>(op); |
| // Compose the stored value over the subview's extent only, leaving the rest |
| // of the parent value untouched. |
| if (isAliasableDynamicShapeSubView(subView)) { |
| Location loc = op->getLoc(); |
| Value mask = buildSubViewMask(builder, loc, subView); |
| return arith::SelectOp::create(builder, loc, mask, aliasValue, |
| reachingDef); |
| } |
| |
| SmallVector<int64_t> offsets = getStaticSubViewOffsets(subView); |
| SmallVector<int64_t> strides(offsets.size(), 1); |
| return vector::InsertStridedSliceOp::create( |
| builder, op->getLoc(), aliasValue, reachingDef, offsets, strides) |
| .getResult(); |
| } |
| }; |
| } // namespace |
| |
| //===----------------------------------------------------------------------===// |
| // Register external models |
| //===----------------------------------------------------------------------===// |
| |
| void mlir::memref::registerMemorySlotOpInterfaceExternalModels( |
| DialectRegistry ®istry) { |
| registry.addExtension(+[](MLIRContext *ctx, memref::MemRefDialect *dialect) { |
| memref::SubViewOp::attachInterface<SubViewOpAliasModel>(*ctx); |
| memref::SubViewOp::attachInterface<SubViewOpPromotableModel>(*ctx); |
| }); |
| } |