MLIR 24.0.0git
MemRefMemorySlot.cpp
Go to the documentation of this file.
1//===- MemRefMemorySlot.cpp - Memory Slot Interfaces ------------*- C++ -*-===//
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 for MemRef dialect
10// operations.
11//
12//===----------------------------------------------------------------------===//
13
21#include "mlir/IR/Matchers.h"
22#include "mlir/IR/Value.h"
24#include "llvm/ADT/ArrayRef.h"
25#include "llvm/ADT/TypeSwitch.h"
26#include "llvm/Support/ErrorHandling.h"
27
28using namespace mlir;
29
30//===----------------------------------------------------------------------===//
31// Utilities
32//===----------------------------------------------------------------------===//
33
34/// Walks over the indices of the elements of a tensor of a given `shape` by
35/// updating `index` in place to the next index. This returns failure if the
36/// provided index was the last index.
37static LogicalResult nextIndex(ArrayRef<int64_t> shape,
39 for (size_t i = 0; i < shape.size(); ++i) {
40 index[i]++;
41 if (index[i] < shape[i])
42 return success();
43 index[i] = 0;
44 }
45 return failure();
46}
47
48/// Calls `walker` for each index within a tensor of a given `shape`, providing
49/// the index as an array attribute of the coordinates.
50template <typename CallableT>
52 CallableT &&walker) {
53 Type indexType = IndexType::get(ctx);
54 SmallVector<int64_t> shapeIter(shape.size(), 0);
55 do {
56 SmallVector<Attribute> indexAsAttr;
57 for (int64_t dim : shapeIter)
58 indexAsAttr.push_back(IntegerAttr::get(indexType, dim));
59 walker(ArrayAttr::get(ctx, indexAsAttr));
60 } while (succeeded(nextIndex(shape, shapeIter)));
61}
62
63//===----------------------------------------------------------------------===//
64// Interfaces for AllocaOp
65//===----------------------------------------------------------------------===//
66
67/// Returns the scalable vector width a `vscale`-sized memref maps to: the
68/// factor C when `size` is a known multiple of `vscale`.
69static std::optional<int64_t> matchVScaleMultiple(Value size) {
70 Operation *defOp = size.getDefiningOp();
71 if (!defOp)
72 return std::nullopt;
73
74 auto isVScale = [](Value v) {
75 Operation *op = v.getDefiningOp();
76 // Matched by name to avoid a MemRef -> Vector circular dependency, as in
77 // arith::MulIOp::getAsmResultNames.
78 return op && op->getName().getStringRef() == "vector.vscale";
79 };
80
81 // Bare `vector.vscale` == vscale * 1.
82 if (isVScale(size))
83 return 1;
84
85 // `vscale * C` or `C * vscale` (multiplication is commutative).
86 if (auto mul = dyn_cast<arith::MulIOp>(defOp)) {
87 if (isVScale(mul.getLhs()))
88 return getConstantIntValue(mul.getRhs());
89 if (isVScale(mul.getRhs()))
90 return getConstantIntValue(mul.getLhs());
91 }
92 return std::nullopt;
93}
94
95SmallVector<MemorySlot> memref::AllocaOp::getPromotableSlots() {
96 MemRefType type = getType();
97
98 // A single-element memref is promoted to a scalar SSA value.
99 if (type.hasStaticShape()) {
100 std::optional<int64_t> numElements =
101 ShapedType::tryGetNumElements(type.getShape());
102 // Element count overflow: not promotable.
103 if (!numElements)
104 return {};
105 if (*numElements == 1)
106 return {MemorySlot{getResult(), type.getElementType()}};
107 }
108
109 // A multi-element memref can be promoted to a single vector SSA value when it
110 // is only ever accessed as a whole buffer (e.g. through whole-buffer
111 // `vector.transfer_read`/`vector.transfer_write`).
112 if (VectorType::isValidElementType(type.getElementType())) {
113 // Vector types require strictly positive extents, so a memref with a zero
114 // extent has nothing to promote.
115 if (llvm::is_contained(type.getShape(), 0))
116 return {};
117
118 // Static shape: a fixed-size vector of the same extents.
119 if (type.hasStaticShape())
120 return {MemorySlot{getResult(), VectorType::get(type.getShape(),
121 type.getElementType())}};
122
123 // A 1-D memref whose single dynamic extent is `vector.vscale * N` maps to a
124 // scalable `vector<[N]x...>` slot, for a strictly positive multiple `N`.
125 if (type.getRank() == 1 && type.isDynamicDim(0)) {
126 if (std::optional<int64_t> multiple =
128 multiple && *multiple > 0)
129 return {MemorySlot{getResult(),
130 VectorType::get({*multiple}, type.getElementType(),
131 /*scalableDims=*/{true})}};
132 }
133 }
134
135 return {};
136}
137
138Value memref::AllocaOp::getDefaultValue(const MemorySlot &slot,
139 OpBuilder &builder) {
140 return ub::PoisonOp::create(builder, getLoc(), slot.elemType);
141}
142
143std::optional<PromotableAllocationOpInterface>
144memref::AllocaOp::handlePromotionComplete(const MemorySlot &slot,
145 Value defaultValue,
146 OpBuilder &builder) {
147 if (defaultValue && defaultValue.use_empty())
148 defaultValue.getDefiningOp()->erase();
149 this->erase();
150 return std::nullopt;
151}
152
153void memref::AllocaOp::handleBlockArgument(const MemorySlot &slot,
154 BlockArgument argument,
155 OpBuilder &builder) {}
156
158memref::AllocaOp::getDestructurableSlots() {
159 MemRefType memrefType = getType();
160 auto destructurable = llvm::dyn_cast<DestructurableTypeInterface>(memrefType);
161 if (!destructurable)
162 return {};
163
164 std::optional<DenseMap<Attribute, Type>> destructuredType =
165 destructurable.getSubelementIndexMap();
166 if (!destructuredType)
167 return {};
168
169 return {
170 DestructurableMemorySlot{{getMemref(), memrefType}, *destructuredType}};
171}
172
173DenseMap<Attribute, MemorySlot> memref::AllocaOp::destructure(
174 const DestructurableMemorySlot &slot,
175 const SmallPtrSetImpl<Attribute> &usedIndices, OpBuilder &builder,
177 builder.setInsertionPointAfter(*this);
178
180
181 auto memrefType = llvm::cast<DestructurableTypeInterface>(getType());
182 for (Attribute usedIndex : usedIndices) {
183 Type elemType = memrefType.getTypeAtIndex(usedIndex);
184 MemRefType elemPtr = MemRefType::get({}, elemType);
185 auto subAlloca = memref::AllocaOp::create(builder, getLoc(), elemPtr);
186 newAllocators.push_back(subAlloca);
187 slotMap.try_emplace<MemorySlot>(usedIndex,
188 {subAlloca.getResult(), elemType});
189 }
190
191 return slotMap;
192}
193
194std::optional<DestructurableAllocationOpInterface>
195memref::AllocaOp::handleDestructuringComplete(
196 const DestructurableMemorySlot &slot, OpBuilder &builder) {
197 assert(slot.ptr == getResult());
198 this->erase();
199 return std::nullopt;
200}
201
202//===----------------------------------------------------------------------===//
203// Interfaces for LoadOp/StoreOp
204//===----------------------------------------------------------------------===//
205
206bool memref::LoadOp::loadsFrom(const MemorySlot &slot) {
207 return getMemRef() == slot.ptr;
208}
209
210bool memref::LoadOp::storesTo(const MemorySlot &slot) { return false; }
211
212Value memref::LoadOp::getStored(const MemorySlot &slot, OpBuilder &builder,
213 Value reachingDef,
214 const DataLayout &dataLayout) {
215 llvm_unreachable("getStored should not be called on LoadOp");
216}
217
218bool memref::LoadOp::canUsesBeRemoved(
219 const MemorySlot &slot, const SmallPtrSetImpl<OpOperand *> &blockingUses,
220 SmallVectorImpl<OpOperand *> &newBlockingUses,
221 const DataLayout &dataLayout) {
222 if (blockingUses.size() != 1)
223 return false;
224 Value blockingUse = (*blockingUses.begin())->get();
225 return blockingUse == slot.ptr && getMemRef() == slot.ptr &&
226 getResult().getType() == slot.elemType;
227}
228
229DeletionKind memref::LoadOp::removeBlockingUses(
230 const MemorySlot &slot, const SmallPtrSetImpl<OpOperand *> &blockingUses,
231 OpBuilder &builder, Value reachingDefinition,
232 const DataLayout &dataLayout) {
233 // `canUsesBeRemoved` checked this blocking use must be the loaded slot
234 // pointer.
235 getResult().replaceAllUsesWith(reachingDefinition);
237}
238
239/// Returns the index of a memref in attribute form, given its indices. Returns
240/// a null pointer if whether the indices form a valid index for the provided
241/// MemRefType cannot be computed. The indices must come from a valid memref
242/// StoreOp or LoadOp.
245 MemRefType memrefType) {
247 for (auto [coord, dimSize] : llvm::zip(indices, memrefType.getShape())) {
248 IntegerAttr coordAttr;
249 if (!matchPattern(coord, m_Constant<IntegerAttr>(&coordAttr)))
250 return {};
251 // MemRefType shape dimensions are always positive (checked by verifier).
252 std::optional<uint64_t> coordInt = coordAttr.getValue().tryZExtValue();
253 if (!coordInt || coordInt.value() >= static_cast<uint64_t>(dimSize))
254 return {};
255 index.push_back(coordAttr);
256 }
257 return ArrayAttr::get(ctx, index);
258}
259
260bool memref::LoadOp::canRewire(const DestructurableMemorySlot &slot,
261 SmallPtrSetImpl<Attribute> &usedIndices,
262 SmallVectorImpl<MemorySlot> &mustBeSafelyUsed,
263 const DataLayout &dataLayout) {
264 if (slot.ptr != getMemRef())
265 return false;
268 if (!index)
269 return false;
270 usedIndices.insert(index);
271 return true;
272}
273
274DeletionKind memref::LoadOp::rewire(const DestructurableMemorySlot &slot,
276 OpBuilder &builder,
277 const DataLayout &dataLayout) {
280 const MemorySlot &memorySlot = subslots.at(index);
281 setMemRef(memorySlot.ptr);
282 getIndicesMutable().clear();
283 return DeletionKind::Keep;
284}
285
286bool memref::StoreOp::loadsFrom(const MemorySlot &slot) { return false; }
287
288bool memref::StoreOp::storesTo(const MemorySlot &slot) {
289 return getMemRef() == slot.ptr;
290}
291
292Value memref::StoreOp::getStored(const MemorySlot &slot, OpBuilder &builder,
293 Value reachingDef,
294 const DataLayout &dataLayout) {
295 return getValue();
296}
297
298bool memref::StoreOp::canUsesBeRemoved(
299 const MemorySlot &slot, const SmallPtrSetImpl<OpOperand *> &blockingUses,
300 SmallVectorImpl<OpOperand *> &newBlockingUses,
301 const DataLayout &dataLayout) {
302 if (blockingUses.size() != 1)
303 return false;
304 Value blockingUse = (*blockingUses.begin())->get();
305 return blockingUse == slot.ptr && getMemRef() == slot.ptr &&
306 getValue() != slot.ptr && getValue().getType() == slot.elemType;
307}
308
309DeletionKind memref::StoreOp::removeBlockingUses(
310 const MemorySlot &slot, const SmallPtrSetImpl<OpOperand *> &blockingUses,
311 OpBuilder &builder, Value reachingDefinition,
312 const DataLayout &dataLayout) {
314}
315
316bool memref::StoreOp::canRewire(const DestructurableMemorySlot &slot,
317 SmallPtrSetImpl<Attribute> &usedIndices,
318 SmallVectorImpl<MemorySlot> &mustBeSafelyUsed,
319 const DataLayout &dataLayout) {
320 if (slot.ptr != getMemRef() || getValue() == slot.ptr)
321 return false;
324 if (!index || !slot.subelementTypes.contains(index))
325 return false;
326 usedIndices.insert(index);
327 return true;
328}
329
330DeletionKind memref::StoreOp::rewire(const DestructurableMemorySlot &slot,
332 OpBuilder &builder,
333 const DataLayout &dataLayout) {
336 const MemorySlot &memorySlot = subslots.at(index);
337 setMemRef(memorySlot.ptr);
338 getIndicesMutable().clear();
339 return DeletionKind::Keep;
340}
341
342//===----------------------------------------------------------------------===//
343// Interfaces for destructurable types
344//===----------------------------------------------------------------------===//
345
346namespace {
347
348struct MemRefDestructurableTypeExternalModel
349 : public DestructurableTypeInterface::ExternalModel<
350 MemRefDestructurableTypeExternalModel, MemRefType> {
351 std::optional<DenseMap<Attribute, Type>>
352 getSubelementIndexMap(Type type) const {
353 auto memrefType = llvm::cast<MemRefType>(type);
354 constexpr int64_t maxMemrefSizeForDestructuring = 16;
355 if (!memrefType.hasStaticShape())
356 return {};
357 std::optional<int64_t> numElements =
358 ShapedType::tryGetNumElements(memrefType.getShape());
359 if (!numElements || *numElements > maxMemrefSizeForDestructuring ||
360 *numElements == 1)
361 return {};
362
363 DenseMap<Attribute, Type> destructured;
365 memrefType.getContext(), memrefType.getShape(), [&](Attribute index) {
366 destructured.insert({index, memrefType.getElementType()});
367 });
368
369 return destructured;
370 }
371
372 Type getTypeAtIndex(Type type, Attribute index) const {
373 auto memrefType = llvm::cast<MemRefType>(type);
374 auto coordArrAttr = llvm::dyn_cast<ArrayAttr>(index);
375 if (!coordArrAttr || coordArrAttr.size() != memrefType.getShape().size())
376 return {};
377
378 Type indexType = IndexType::get(memrefType.getContext());
379 for (const auto &[coordAttr, dimSize] :
380 llvm::zip(coordArrAttr, memrefType.getShape())) {
381 auto coord = llvm::dyn_cast<IntegerAttr>(coordAttr);
382 if (!coord || coord.getType() != indexType || coord.getInt() < 0 ||
383 coord.getInt() >= dimSize)
384 return {};
385 }
386
387 return memrefType.getElementType();
388 }
389};
390
391} // namespace
392
393//===----------------------------------------------------------------------===//
394// Register external models
395//===----------------------------------------------------------------------===//
396
398 registry.addExtension(+[](MLIRContext *ctx, BuiltinDialect *dialect) {
399 MemRefType::attachInterface<MemRefDestructurableTypeExternalModel>(*ctx);
400 });
401}
return success()
static Value getMemRef(Operation *memOp)
Returns the memref being read/written by a memref/affine load/store op.
Definition Utils.cpp:247
static Type getTypeAtIndex(const DestructurableMemorySlot &slot, Attribute index)
Returns the subslot's type at the requested index.
b getContext())
static Attribute getAttributeIndexFromIndexOperands(MLIRContext *ctx, ValueRange indices, MemRefType memrefType)
Returns the index of a memref in attribute form, given its indices.
static std::optional< int64_t > matchVScaleMultiple(Value size)
Returns the scalable vector width a vscale-sized memref maps to: the factor C when size is a known mu...
static LogicalResult nextIndex(ArrayRef< int64_t > shape, MutableArrayRef< int64_t > index)
Walks over the indices of the elements of a tensor of a given shape by updating index in place to the...
static void walkIndicesAsAttr(MLIRContext *ctx, ArrayRef< int64_t > shape, CallableT &&walker)
Calls walker for each index within a tensor of a given shape, providing the index as an array attribu...
static void getDynamicSizes(RankedTensorType tp, ValueRange sizes, SmallVectorImpl< Value > &dynSizes)
Collects the dynamic dimension sizes for tp with the assumption that sizes are the dimension sizes fo...
#define mul(a, b)
Attributes are known-constant values of operations.
Definition Attributes.h:25
This class represents an argument of a Block.
Definition Value.h:306
The main mechanism for performing data layout queries.
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:63
This class helps build Operations.
Definition Builders.h:210
void setInsertionPointAfter(Operation *op)
Sets the insertion point to the node after the specified operation, which will cause subsequent inser...
Definition Builders.h:415
StringRef getStringRef() const
Return the name of this operation. This always succeeds.
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
OperationName getName()
The name of an operation is the key identifier for it.
Definition Operation.h:115
void erase()
Remove this operation from its parent block and delete it.
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:389
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
bool use_empty() const
Returns true if this value has no uses.
Definition Value.h:208
Type getType() const
Return the type of this value.
Definition Value.h:105
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition Value.cpp:18
void registerMemorySlotExternalModels(DialectRegistry &registry)
Operation::operand_range getIndices(Operation *op)
Get the indices that the given load/store operation is operating on.
Definition Utils.cpp:18
MemRefType getMemRefType(T &&t)
Convenience method to abbreviate casting getType().
Include the generated interface declarations.
bool matchPattern(Value value, const Pattern &pattern)
Entry point for matching a pattern over a Value.
Definition Matchers.h:490
std::optional< int64_t > getConstantIntValue(OpFoldResult ofr)
If ofr is a constant integer or an IntegerAttr, return the integer.
Type getType(OpFoldResult ofr)
Returns the int type of the integer in ofr.
Definition Utils.cpp:307
DeletionKind
Returned by operation promotion logic requesting the deletion of an operation.
@ Keep
Keep the operation after promotion.
@ Delete
Delete the operation after promotion.
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Definition LLVM.h:120
detail::constant_op_matcher m_Constant()
Matches a constant foldable operation.
Definition Matchers.h:369
Memory slot attached with information about its destructuring procedure.
DenseMap< Attribute, Type > subelementTypes
Maps an index within the memory slot to the corresponding subelement type.
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.