MLIR 24.0.0git
MemorySlotInterfacesImpl.cpp
Go to the documentation of this file.
1//===- MemorySlotInterfacesImpl.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 memory slot interface models for
10// `vector.transfer_read`, `vector.transfer_write`, and `vector.mask`. They
11// implement `PromotableMemOpInterface` to replace vector transfers with uses
12// and definitions of a vector SSA value. The models for `memref.subview`
13// live in the MemRef dialect.
14//
15// A read uses the slot's current value; a write defines its next value.
16// Static subviews use a vector matching the subview's shape: reads extract that
17// sub-vector from the parent, and writes insert it back into the parent.
18// Supported transfers through dynamic subviews use a vector type matching the
19// whole static parent, and the subview's slot holds that whole vector. Reads
20// pad lanes outside the runtime extent, and writes preserve the previous value
21// of those lanes. `arith.select` implements this behavior and also handles
22// lanes disabled by a transfer's mask operand.
23//
24// `vector.mask` owns its wrapped transfer's blocking use and is promoted as a
25// whole. Its region cannot accept extra operations, so `arith.select` is built
26// outside it.
27//
28//===----------------------------------------------------------------------===//
29
31
39
40using namespace mlir;
41using namespace mlir::vector;
42
43//===----------------------------------------------------------------------===//
44// Utilities
45//===----------------------------------------------------------------------===//
46
47/// Returns whether `xferOp` can be promoted to use or define `slot`'s vector
48/// value. The transfer's memref base must be the slot pointer, its vector type
49/// must equal `slot.valueType`, its indices must be zero, and its permutation
50/// map must be the identity. Out-of-bounds dimensions are allowed only for a
51/// supported dynamic subview.
52///
53/// More general transfers may be supportable, but their value depends on
54/// motivating use cases, implementation complexity, and generated code quality.
55static bool
56isPromotableTransfer(VectorTransferOpInterface xferOp, const MemorySlot &slot,
57 const SmallPtrSetImpl<OpOperand *> &blockingUses) {
58 // The transfer's memref base must be the slot pointer.
59 if (blockingUses.size() != 1)
60 return false;
61 Value blockingUse = (*blockingUses.begin())->get();
62 if (blockingUse != slot.ptr || xferOp.getBase() != slot.ptr)
63 return false;
64
65 // Reject the tensor form (already implied, since slot pointers are memrefs).
66 if (!isa<MemRefType>(xferOp.getBase().getType()))
67 return false;
68
69 // Exact type match pins rank/extents/element type/scalable dims.
70 if (xferOp.getVectorType() != slot.valueType)
71 return false;
72
73 // Access must start at the buffer origin in every dimension.
74 for (Value index : xferOp.getIndices()) {
75 std::optional<int64_t> constIndex = getConstantIntValue(index);
76 if (!constIndex || *constIndex != 0)
77 return false;
78 }
79
80 // Identity map: no broadcast or transpose.
81 if (!xferOp.getPermutationMap().isIdentity())
82 return false;
83
84 // Out-of-bounds is allowed only for a dynamic view.
85 if (xferOp.hasOutOfBoundsDim() && !memref::isDynamicSubViewSlot(slot.ptr))
86 return false;
87
88 return true;
89}
90
91//===----------------------------------------------------------------------===//
92// PromotableMemOpInterface models
93//===----------------------------------------------------------------------===//
94
95namespace {
96/// `PromotableMemOpInterface` model for `vector.transfer_read`.
97struct TransferReadOpMemOpModel
98 : public PromotableMemOpInterface::ExternalModel<TransferReadOpMemOpModel,
99 vector::TransferReadOp> {
100 bool loadsFrom(Operation *op, const MemorySlot &slot) const {
101 return cast<vector::TransferReadOp>(op).getBase() == slot.ptr;
102 }
103
104 bool storesTo(Operation *op, const MemorySlot &slot) const { return false; }
105
106 Value getStored(Operation *op, const MemorySlot &slot, OpBuilder &builder,
107 Value reachingDef, const DataLayout &dataLayout) const {
108 llvm_unreachable("getStored should not be called on TransferReadOp");
109 }
110
111 bool canUsesBeRemoved(Operation *op, const MemorySlot &slot,
112 const SmallPtrSetImpl<OpOperand *> &blockingUses,
113 SmallVectorImpl<OpOperand *> &newBlockingUses,
114 const DataLayout &dataLayout) const {
115 return isPromotableTransfer(cast<VectorTransferOpInterface>(op), slot,
116 blockingUses);
117 }
118
119 // Replace the read with the reaching definition, selecting padding for lanes
120 // outside the subview's extent or disabled by the transfer's mask.
121 // PromotableAliaserInterface::projectSlotValueToAliasValue supplies the whole
122 // parent vector for a dynamic subview. Apply the extent mask here because
123 // this hook has access to the transfer's padding operand. If the transfer
124 // also has a mask operand, combine the two masks with arith.andi.
126 removeBlockingUses(Operation *op, const MemorySlot &slot,
127 const SmallPtrSetImpl<OpOperand *> &blockingUses,
128 OpBuilder &builder, Value reachingDefinition,
129 const DataLayout &dataLayout) const {
130 auto readOp = cast<vector::TransferReadOp>(op);
131 Location loc = op->getLoc();
132 Value mask = memref::buildDynamicSubViewMask(builder, loc, slot.ptr);
133 if (Value opMask = readOp.getMask())
134 mask = mask
135 ? arith::AndIOp::create(builder, loc, mask, opMask).getResult()
136 : opMask;
137
138 Value result = reachingDefinition;
139 if (mask) {
140 Value padSplat = vector::BroadcastOp::create(
141 builder, loc, readOp.getVectorType(), readOp.getPadding());
142 result = arith::SelectOp::create(builder, loc, mask, reachingDefinition,
143 padSplat);
144 }
145 readOp.getResult().replaceAllUsesWith(result);
146 return DeletionKind::Delete;
147 }
148};
149
150/// `PromotableMemOpInterface` model for `vector.transfer_write`.
151struct TransferWriteOpMemOpModel
152 : public PromotableMemOpInterface::ExternalModel<TransferWriteOpMemOpModel,
153 vector::TransferWriteOp> {
154 bool loadsFrom(Operation *op, const MemorySlot &slot) const { return false; }
155
156 bool storesTo(Operation *op, const MemorySlot &slot) const {
157 return cast<vector::TransferWriteOp>(op).getBase() == slot.ptr;
158 }
159
160 // The stored value is the transfer's vector, with inactive lanes preserved
161 // from the reaching definition when the transfer has a mask.
162 Value getStored(Operation *op, const MemorySlot &slot, OpBuilder &builder,
163 Value reachingDef, const DataLayout &dataLayout) const {
164 auto writeOp = cast<vector::TransferWriteOp>(op);
165 Value stored = writeOp.getValueToStore();
166 // PromotableAliaserInterface::projectAliasValueToSlotValue separately
167 // preserves parent lanes outside a dynamic subview's extent.
168 if (Value mask = writeOp.getMask())
169 stored = arith::SelectOp::create(builder, op->getLoc(), mask, stored,
170 reachingDef);
171 return stored;
172 }
173
174 bool canUsesBeRemoved(Operation *op, const MemorySlot &slot,
175 const SmallPtrSetImpl<OpOperand *> &blockingUses,
176 SmallVectorImpl<OpOperand *> &newBlockingUses,
177 const DataLayout &dataLayout) const {
178 return isPromotableTransfer(cast<VectorTransferOpInterface>(op), slot,
179 blockingUses);
180 }
181
182 // `getStored` already provided the value for later uses, so the write can
183 // simply be erased.
185 removeBlockingUses(Operation *op, const MemorySlot &slot,
186 const SmallPtrSetImpl<OpOperand *> &blockingUses,
187 OpBuilder &builder, Value reachingDefinition,
188 const DataLayout &dataLayout) const {
189 return DeletionKind::Delete;
190 }
191};
192
193/// Returns the operation wrapped by the `vector.mask` `op`.
194PromotableMemOpInterface getWrappedMemOp(Operation *op) {
195 Operation *maskableOp = cast<vector::MaskOp>(op).getMaskableOp();
196 if (!maskableOp)
197 return {};
198 if (!isa<vector::TransferReadOp, vector::TransferWriteOp>(maskableOp))
199 return {};
200 return cast<PromotableMemOpInterface>(maskableOp);
201}
202
203/// `PromotableMemOpInterface` model for `vector.mask` wrapping a promotable
204/// memory operation. `vector.mask` owns the wrapped operation's slot use,
205/// delegates promotion to it, and applies its mask to the result.
206struct MaskOpMemOpModel
207 : public PromotableMemOpInterface::ExternalModel<MaskOpMemOpModel,
208 vector::MaskOp> {
209 bool ownsWrappedBlockingUses(Operation *op) const {
210 return static_cast<bool>(getWrappedMemOp(op));
211 }
212
213 bool loadsFrom(Operation *op, const MemorySlot &slot) const {
214 PromotableMemOpInterface memOp = getWrappedMemOp(op);
215 return memOp && memOp.loadsFrom(slot);
216 }
217
218 bool storesTo(Operation *op, const MemorySlot &slot) const {
219 PromotableMemOpInterface memOp = getWrappedMemOp(op);
220 return memOp && memOp.storesTo(slot);
221 }
222
223 Value getStored(Operation *op, const MemorySlot &slot, OpBuilder &builder,
224 Value reachingDef, const DataLayout &dataLayout) const {
225 Value stored =
226 getWrappedMemOp(op).getStored(slot, builder, reachingDef, dataLayout);
227 return arith::SelectOp::create(builder, op->getLoc(),
228 cast<vector::MaskOp>(op).getMask(), stored,
229 reachingDef);
230 }
231
232 bool canUsesBeRemoved(Operation *op, const MemorySlot &slot,
233 const SmallPtrSetImpl<OpOperand *> &blockingUses,
234 SmallVectorImpl<OpOperand *> &newBlockingUses,
235 const DataLayout &dataLayout) const {
236 PromotableMemOpInterface memOp = getWrappedMemOp(op);
237 // Transfers with their own mask operand are unsupported.
238 if (!memOp || cast<VectorTransferOpInterface>(*memOp).getMask())
239 return false;
240 return memOp.canUsesBeRemoved(slot, blockingUses, newBlockingUses,
241 dataLayout);
242 }
243
244 // Let the wrapped memory op promote itself, then apply the wrapper's mask to
245 // the value it now yields.
247 removeBlockingUses(Operation *op, const MemorySlot &slot,
248 const SmallPtrSetImpl<OpOperand *> &blockingUses,
249 OpBuilder &builder, Value reachingDefinition,
250 const DataLayout &dataLayout) const {
251 auto maskOp = cast<vector::MaskOp>(op);
252 PromotableMemOpInterface memOp = getWrappedMemOp(op);
253 memOp.removeBlockingUses(slot, blockingUses, builder, reachingDefinition,
254 dataLayout);
255 auto readOp = dyn_cast<vector::TransferReadOp>(*memOp);
256 if (!readOp)
257 return DeletionKind::Delete;
258
259 Location loc = op->getLoc();
260 Value enabled = maskOp.getMaskBlock()->getTerminator()->getOperand(0);
261 Value disabled =
262 maskOp.hasPassthru()
263 ? maskOp.getPassthru()
264 : vector::BroadcastOp::create(builder, loc, readOp.getVectorType(),
265 readOp.getPadding());
266 Value result = arith::SelectOp::create(builder, loc, maskOp.getMask(),
267 enabled, disabled);
268 maskOp->getResult(0).replaceAllUsesWith(result);
269 return DeletionKind::Delete;
270 }
271};
272} // namespace
273
274//===----------------------------------------------------------------------===//
275// Register external models
276//===----------------------------------------------------------------------===//
277
279 DialectRegistry &registry) {
280 registry.addExtension(+[](MLIRContext *ctx, vector::VectorDialect *dialect) {
281 TransferReadOp::attachInterface<TransferReadOpMemOpModel>(*ctx);
282 TransferWriteOp::attachInterface<TransferWriteOpMemOpModel>(*ctx);
283 MaskOp::attachInterface<MaskOpMemOpModel>(*ctx);
284 });
285}
static bool isPromotableTransfer(VectorTransferOpInterface xferOp, const MemorySlot &slot, const SmallPtrSetImpl< OpOperand * > &blockingUses)
Returns whether xferOp can be promoted to use or define slot's vector value.
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.
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:71
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
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
void replaceAllUsesWith(Value newValue)
Replace all uses of 'this' value with the new value, updating anything in the IR that uses 'this' to ...
Definition Value.h:149
Value buildDynamicSubViewMask(OpBuilder &builder, Location loc, Value slotPtr)
Builds a mask for the subview's valid region in the parent vector, or returns null if isDynamicSubVie...
bool isDynamicSubViewSlot(Value slotPtr)
Returns whether slotPtr is a dynamic memref.subview that can be promoted as an alias of the whole par...
void registerMemorySlotInterfacesExternalModels(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.
Type valueType
Type of the value contained in the slot.
Value ptr
Pointer to the memory slot, used by operations to refer to it.