MLIR 24.0.0git
MemorySlotOpInterfaceImpl.cpp
Go to the documentation of this file.
1//===- MemorySlotOpInterfaceImpl.cpp - Mem2Reg for vector ops -------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file implements Mem2Reg-related interfaces that let a statically-shaped
10// memref be promoted into a single vector SSA value, provided every access to
11// the buffer is a whole-buffer read or write (or a whole-sub-region access of
12// such a buffer via a subview). With these models, Mem2Reg replaces the memory
13// slot with a vector value, threading it as the reaching definition:
14//
15// * `vector.transfer_read` of the whole buffer becomes a use of the current
16// vector value; `vector.transfer_write` of the whole buffer becomes a new
17// definition of it (see the `PromotableMemOpInterface` models below).
18//
19// * a static, same-rank `memref.subview` is exposed as a promotable sub-slice
20// alias of the buffer's slot (via `PromotableAliaserInterface`): a read of
21// the subview projects out of the vector value with
22// `vector.extract_strided_slice`, and a write into it composes back into
23// the value with `vector.insert_strided_slice`. This lets a buffer that is
24// only ever accessed through static subviews promote as well, with partial
25// and overlapping sub-writes composing in program order.
26//
27// Accesses that are not whole-(sub-)buffer -- dynamic offsets, rank-reducing or
28// non-unit-stride subviews, masked or partial transfers, non-zero transfer
29// indices -- are left untouched, so the buffer is not promoted.
30//
31//===----------------------------------------------------------------------===//
32
34
40
41using namespace mlir;
42using namespace mlir::vector;
43
44//===----------------------------------------------------------------------===//
45// Utilities
46//===----------------------------------------------------------------------===//
47
48/// Returns whether `xferOp` accesses exactly the whole contents of `slot`, so
49/// it can act as a plain whole-buffer load/store during Mem2Reg.
50static bool
51isWholeBufferTransfer(VectorTransferOpInterface xferOp, const MemorySlot &slot,
52 const SmallPtrSetImpl<OpOperand *> &blockingUses) {
53 // The sole blocking use must be the slot pointer as the transfer's base.
54 if (blockingUses.size() != 1)
55 return false;
56 Value blockingUse = (*blockingUses.begin())->get();
57 if (blockingUse != slot.ptr || xferOp.getBase() != slot.ptr)
58 return false;
59
60 // Reject the tensor form (already implied, since slot pointers are memrefs).
61 if (!isa<MemRefType>(xferOp.getBase().getType()))
62 return false;
63
64 // Exact type match pins rank/extents/element type/scalable dims.
65 if (xferOp.getVectorType() != slot.elemType)
66 return false;
67
68 // Access must start at the buffer origin in every dimension.
69 for (Value index : xferOp.getIndices()) {
70 std::optional<int64_t> constIndex = getConstantIntValue(index);
71 if (!constIndex || *constIndex != 0)
72 return false;
73 }
74
75 // Identity map: no broadcast or transpose.
76 if (!xferOp.getPermutationMap().isIdentity())
77 return false;
78
79 // All dimensions must be in bounds. An out-of-bounds dimension means the
80 // transfer reaches past the buffer, so a read would materialize padding
81 // rather than buffer contents and a write would only cover part of the
82 // buffer: in neither case does the transfer stand in for the whole slot.
83 if (xferOp.hasOutOfBoundsDim())
84 return false;
85
86 // A mask could make the access partial.
87 if (xferOp.getMask())
88 return false;
89
90 return true;
91}
92
93//===----------------------------------------------------------------------===//
94// Interface models
95//===----------------------------------------------------------------------===//
96
97namespace {
98
99struct TransferReadOpMemOpModel
100 : public PromotableMemOpInterface::ExternalModel<TransferReadOpMemOpModel,
101 vector::TransferReadOp> {
102 bool loadsFrom(Operation *op, const MemorySlot &slot) const {
103 return cast<vector::TransferReadOp>(op).getBase() == slot.ptr;
104 }
105
106 bool storesTo(Operation *op, const MemorySlot &slot) const { return false; }
107
108 Value getStored(Operation *op, const MemorySlot &slot, OpBuilder &builder,
109 Value reachingDef, const DataLayout &dataLayout) const {
110 llvm_unreachable("getStored should not be called on TransferReadOp");
111 }
112
113 bool canUsesBeRemoved(Operation *op, const MemorySlot &slot,
114 const SmallPtrSetImpl<OpOperand *> &blockingUses,
115 SmallVectorImpl<OpOperand *> &newBlockingUses,
116 const DataLayout &dataLayout) const {
117 return isWholeBufferTransfer(cast<VectorTransferOpInterface>(op), slot,
118 blockingUses);
119 }
120
122 removeBlockingUses(Operation *op, const MemorySlot &slot,
123 const SmallPtrSetImpl<OpOperand *> &blockingUses,
124 OpBuilder &builder, Value reachingDefinition,
125 const DataLayout &dataLayout) const {
126 // Whole-buffer read: replace the loaded vector with the reaching
127 // definition.
128 cast<vector::TransferReadOp>(op).getVector().replaceAllUsesWith(
129 reachingDefinition);
130 return DeletionKind::Delete;
131 }
132};
133
134struct TransferWriteOpMemOpModel
135 : public PromotableMemOpInterface::ExternalModel<TransferWriteOpMemOpModel,
136 vector::TransferWriteOp> {
137 bool loadsFrom(Operation *op, const MemorySlot &slot) const { return false; }
138
139 bool storesTo(Operation *op, const MemorySlot &slot) const {
140 return cast<vector::TransferWriteOp>(op).getBase() == slot.ptr;
141 }
142
143 Value getStored(Operation *op, const MemorySlot &slot, OpBuilder &builder,
144 Value reachingDef, const DataLayout &dataLayout) const {
145 return cast<vector::TransferWriteOp>(op).getValueToStore();
146 }
147
148 bool canUsesBeRemoved(Operation *op, const MemorySlot &slot,
149 const SmallPtrSetImpl<OpOperand *> &blockingUses,
150 SmallVectorImpl<OpOperand *> &newBlockingUses,
151 const DataLayout &dataLayout) const {
152 // No self-store guard needed: a vector value can never equal a memref slot.
153 return isWholeBufferTransfer(cast<VectorTransferOpInterface>(op), slot,
154 blockingUses);
155 }
156
158 removeBlockingUses(Operation *op, const MemorySlot &slot,
159 const SmallPtrSetImpl<OpOperand *> &blockingUses,
160 OpBuilder &builder, Value reachingDefinition,
161 const DataLayout &dataLayout) const {
162 return DeletionKind::Delete;
163 }
164};
165
166} // namespace
167
168//===----------------------------------------------------------------------===//
169// memref.subview aliaser
170//===----------------------------------------------------------------------===//
171
172/// Returns the offsets of `subView` as a static, contiguous, same-rank slice of
173/// its source, or nullopt if the subview is not promotable as a whole-buffer
174/// sub-slice. Promotion projects the parent buffer's vector value through
175/// `vector.extract_strided_slice` / `insert_strided_slice`, which require:
176/// * fully static offsets and sizes,
177/// * unit strides,
178/// * no rank reduction (result rank == source rank),
179/// so a dropped or dynamic dimension disqualifies the subview.
180static std::optional<SmallVector<int64_t>>
181getPromotableSubViewOffsets(memref::SubViewOp subView) {
182 auto srcType = dyn_cast<MemRefType>(subView.getSource().getType());
183 auto resType = dyn_cast<MemRefType>(subView.getResult().getType());
184 if (!srcType || !resType || !srcType.hasStaticShape() ||
185 !resType.hasStaticShape())
186 return std::nullopt;
187
188 // No rank reduction: extract/insert_strided_slice operate at a single rank.
189 if (srcType.getRank() != resType.getRank())
190 return std::nullopt;
191
192 // Unit strides only.
193 for (OpFoldResult stride : subView.getMixedStrides()) {
194 std::optional<int64_t> s = getConstantIntValue(stride);
195 if (!s || *s != 1)
196 return std::nullopt;
197 }
198
199 // Static offsets.
200 SmallVector<int64_t> offsets;
201 for (OpFoldResult offset : subView.getMixedOffsets()) {
202 std::optional<int64_t> o = getConstantIntValue(offset);
203 if (!o)
204 return std::nullopt;
205 offsets.push_back(*o);
206 }
207
208 // Static sizes (already implied by the result's static shape, but the sizes
209 // must match the result shape so the slice covers exactly the subview).
210 for (auto [size, dim] :
211 llvm::zip_equal(subView.getMixedSizes(), resType.getShape())) {
212 std::optional<int64_t> s = getConstantIntValue(size);
213 if (!s || *s != dim)
214 return std::nullopt;
215 }
216 return offsets;
217}
218
219namespace {
220
221/// Exposes a static, same-rank `memref.subview` as a sub-slice alias of a
222/// whole-buffer vector slot. Reads of the subview become
223/// `vector.extract_strided_slice` of the parent value; writes become
224/// `vector.insert_strided_slice` into the current reaching definition.
225struct SubViewOpAliasModel
226 : public PromotableAliaserInterface::ExternalModel<SubViewOpAliasModel,
227 memref::SubViewOp> {
228 void getPromotableSlotAliases(Operation *op,
229 OpOperand &aliasedSlotPointerOperand,
230 const MemorySlot &parentSlot,
231 SmallVectorImpl<MemorySlot> &newSlots) const {
232 auto subView = cast<memref::SubViewOp>(op);
233 if (aliasedSlotPointerOperand.get() != subView.getSource())
234 return;
235
236 // The parent slot must promote to a vector (whole-buffer promotion). A
237 // scalar (single-element) parent slot cannot be sliced.
238 auto parentVecType = dyn_cast<VectorType>(parentSlot.elemType);
239 if (!parentVecType)
240 return;
241
242 if (!getPromotableSubViewOffsets(subView))
243 return;
244
245 // The alias's value type is the sub-vector matching the subview's shape.
246 auto resType = cast<MemRefType>(subView.getResult().getType());
247 if (!VectorType::isValidElementType(resType.getElementType()))
248 return;
249 VectorType aliasVecType =
250 VectorType::get(resType.getShape(), resType.getElementType());
251 newSlots.push_back(MemorySlot{subView.getResult(), aliasVecType});
252 }
253
254 Value projectSlotValueToAliasValue(Operation *op,
255 OpOperand & /*aliasedSlotPointerOperand*/,
256 const MemorySlot & /*parentSlot*/,
257 const MemorySlot &aliasSlot,
258 Value slotValue,
259 OpBuilder &builder) const {
260 auto subView = cast<memref::SubViewOp>(op);
261 SmallVector<int64_t> offsets = *getPromotableSubViewOffsets(subView);
262 auto aliasVecType = cast<VectorType>(aliasSlot.elemType);
263 SmallVector<int64_t> strides(offsets.size(), 1);
264 return vector::ExtractStridedSliceOp::create(
265 builder, op->getLoc(), slotValue, offsets,
266 aliasVecType.getShape(), strides)
267 .getResult();
268 }
269
270 Value projectAliasValueToSlotValue(Operation *op,
271 OpOperand & /*aliasedSlotPointerOperand*/,
272 const MemorySlot & /*parentSlot*/,
273 const MemorySlot & /*aliasSlot*/,
274 Value aliasValue, Value reachingDef,
275 OpBuilder &builder) const {
276 auto subView = cast<memref::SubViewOp>(op);
277 SmallVector<int64_t> offsets = *getPromotableSubViewOffsets(subView);
278 SmallVector<int64_t> strides(offsets.size(), 1);
279 return vector::InsertStridedSliceOp::create(
280 builder, op->getLoc(), aliasValue, reachingDef, offsets, strides)
281 .getResult();
282 }
283};
284
285/// Companion `PromotableOpInterface` model: once the slot is promoted, the
286/// subview has no remaining memory uses and is erased.
287struct SubViewOpPromotableModel
288 : public PromotableOpInterface::ExternalModel<SubViewOpPromotableModel,
289 memref::SubViewOp> {
290 bool canUsesBeRemoved(Operation *op,
291 const SmallPtrSetImpl<OpOperand *> &blockingUses,
292 SmallVectorImpl<OpOperand *> &newBlockingUses,
293 const DataLayout &dataLayout) const {
294 // The subview result is itself a blocking use of the parent slot; its own
295 // users (the transfers) are resolved through the alias projections.
296 for (OpOperand &use : op->getResult(0).getUses())
297 newBlockingUses.push_back(&use);
298 return true;
299 }
300
302 removeBlockingUses(Operation *op,
303 const SmallPtrSetImpl<OpOperand *> &blockingUses,
304 OpBuilder &builder) const {
305 return DeletionKind::Delete;
306 }
307};
308
309} // namespace
310
311//===----------------------------------------------------------------------===//
312// Register external models
313//===----------------------------------------------------------------------===//
314
316 DialectRegistry &registry) {
317 registry.addExtension(+[](MLIRContext *ctx, vector::VectorDialect *dialect) {
318 TransferReadOp::attachInterface<TransferReadOpMemOpModel>(*ctx);
319 TransferWriteOp::attachInterface<TransferWriteOpMemOpModel>(*ctx);
320 });
321 // The subview aliaser attaches to a MemRef op but lives here because the
322 // projections build Vector ops; Vector already depends on MemRef.
323 registry.addExtension(+[](MLIRContext *ctx, memref::MemRefDialect *dialect) {
324 memref::SubViewOp::attachInterface<SubViewOpAliasModel>(*ctx);
325 memref::SubViewOp::attachInterface<SubViewOpPromotableModel>(*ctx);
326 });
327}
static std::optional< SmallVector< int64_t > > getPromotableSubViewOffsets(memref::SubViewOp subView)
Returns the offsets of subView as a static, contiguous, same-rank slice of its source,...
static bool isWholeBufferTransfer(VectorTransferOpInterface xferOp, const MemorySlot &slot, const SmallPtrSetImpl< OpOperand * > &blockingUses)
Returns whether xferOp accesses exactly the whole contents of slot, so it can act as a plain whole-bu...
The DialectRegistry maps a dialect namespace to a constructor for the matching dialect.
bool addExtension(TypeID extensionID, std::unique_ptr< DialectExtensionBase > extension)
Add the given extension to the registry.
IRValueT get() const
Return the current value being used by this operand.
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
This class represents a single result from folding an operation.
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Definition Operation.h:432
Location getLoc()
The source location the operation was defined or derived from.
Definition Operation.h:240
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
use_range getUses() const
Returns a range of all uses, which is useful for iterating over all uses.
Definition Value.h:188
void registerMemorySlotOpInterfaceExternalModels(DialectRegistry &registry)
Include the generated interface declarations.
std::optional< int64_t > getConstantIntValue(OpFoldResult ofr)
If ofr is a constant integer or an IntegerAttr, return the integer.
DeletionKind
Returned by operation promotion logic requesting the deletion of an operation.
Represents a slot in memory.
Value ptr
Pointer to the memory slot, used by operations to refer to it.
Type elemType
Type of the value contained in the slot.