MLIR 24.0.0git
MemorySlotInterfacesImpl.cpp
Go to the documentation of this file.
1//===- MemorySlotInterfacesImpl.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// Implementation of Memory Slot Interfaces for MemRef Ops.
10//
11// This file also implements DestructurableTypeInterface for MemRefType that is
12// required for some of the interfaces implemented here. This particular
13// interface is added as an external model - otherwise we would need to add it
14// to the MemRef builtin type and make the builtin dialect depend on
15// `DestructurableTypeInterface`.
16//
17//===----------------------------------------------------------------------===//
18
26#include "mlir/IR/Matchers.h"
27#include "mlir/IR/Value.h"
29#include "llvm/ADT/ArrayRef.h"
30#include "llvm/ADT/TypeSwitch.h"
31#include "llvm/Support/ErrorHandling.h"
32
33using namespace mlir;
34
35//===----------------------------------------------------------------------===//
36// Utilities
37//===----------------------------------------------------------------------===//
38
39/// Walks over the indices of the elements of a tensor of a given `shape` by
40/// updating `index` in place to the next index. This returns failure if the
41/// provided index was the last index.
42static LogicalResult nextIndex(ArrayRef<int64_t> shape,
44 for (size_t i = 0; i < shape.size(); ++i) {
45 index[i]++;
46 if (index[i] < shape[i])
47 return success();
48 index[i] = 0;
49 }
50 return failure();
51}
52
53/// Calls `walker` for each index within a tensor of a given `shape`, providing
54/// the index as an array attribute of the coordinates.
55template <typename CallableT>
57 CallableT &&walker) {
58 Type indexType = IndexType::get(ctx);
59 SmallVector<int64_t> shapeIter(shape.size(), 0);
60 do {
61 SmallVector<Attribute> indexAsAttr;
62 for (int64_t dim : shapeIter)
63 indexAsAttr.push_back(IntegerAttr::get(indexType, dim));
64 walker(ArrayAttr::get(ctx, indexAsAttr));
65 } while (succeeded(nextIndex(shape, shapeIter)));
66}
67
68/// Returns the index of a memref in attribute form, given its indices. Returns
69/// a null pointer if whether the indices form a valid index for the provided
70/// MemRefType cannot be computed. The indices must come from a valid memref
71/// StoreOp or LoadOp.
74 MemRefType memrefType) {
76 for (auto [coord, dimSize] : llvm::zip(indices, memrefType.getShape())) {
77 IntegerAttr coordAttr;
78 if (!matchPattern(coord, m_Constant<IntegerAttr>(&coordAttr)))
79 return {};
80 // MemRefType shape dimensions are always positive (checked by verifier).
81 std::optional<uint64_t> coordInt = coordAttr.getValue().tryZExtValue();
82 if (!coordInt || coordInt.value() >= static_cast<uint64_t>(dimSize))
83 return {};
84 index.push_back(coordAttr);
85 }
86 return ArrayAttr::get(ctx, index);
87}
88
89//===----------------------------------------------------------------------===//
90// AllocaOp: PromotableAllocationOpInterface
91//===----------------------------------------------------------------------===//
92/// Returns the scalable vector width a `vscale`-sized memref maps to: the
93/// factor C when `size` is a known multiple of `vscale`.
94static std::optional<int64_t> matchVScaleMultiple(Value size) {
95 Operation *defOp = size.getDefiningOp();
96 if (!defOp)
97 return std::nullopt;
98
99 auto isVScale = [](Value v) {
100 Operation *op = v.getDefiningOp();
101 // Matched by name to avoid a MemRef -> Vector circular dependency, as in
102 // arith::MulIOp::getAsmResultNames.
103 return op && op->getName().getStringRef() == "vector.vscale";
104 };
105
106 // Bare `vector.vscale` == vscale * 1.
107 if (isVScale(size))
108 return 1;
109
110 // `vscale * C` or `C * vscale` (multiplication is commutative).
111 if (auto mul = dyn_cast<arith::MulIOp>(defOp)) {
112 if (isVScale(mul.getLhs()))
113 return getConstantIntValue(mul.getRhs());
114 if (isVScale(mul.getRhs()))
115 return getConstantIntValue(mul.getLhs());
116 }
117 return std::nullopt;
118}
119
120SmallVector<MemorySlot> memref::AllocaOp::getPromotableSlots() {
121 MemRefType type = getType();
122
123 // A single-element memref is promoted to a scalar SSA value.
124 if (type.hasStaticShape()) {
125 std::optional<int64_t> numElements =
126 ShapedType::tryGetNumElements(type.getShape());
127 // Element count overflow: not promotable.
128 if (!numElements)
129 return {};
130 if (*numElements == 1)
131 return {MemorySlot{getResult(), type.getElementType()}};
132 }
133
134 // A multi-element memref can be promoted to a single vector SSA value when it
135 // is only ever accessed as a whole buffer (e.g. through whole-buffer
136 // `vector.transfer_read`/`vector.transfer_write`).
137 if (VectorType::isValidElementType(type.getElementType())) {
138 // Vector types require strictly positive extents, so a memref with a zero
139 // extent has nothing to promote.
140 if (llvm::is_contained(type.getShape(), 0))
141 return {};
142
143 // Static shape: a fixed-size vector of the same extents.
144 if (type.hasStaticShape())
145 return {MemorySlot{getResult(), VectorType::get(type.getShape(),
146 type.getElementType())}};
147
148 // A 1-D memref whose single dynamic extent is `vector.vscale * N` maps to a
149 // scalable `vector<[N]x...>` slot, for a strictly positive multiple `N`.
150 if (type.getRank() == 1 && type.isDynamicDim(0)) {
151 if (std::optional<int64_t> multiple =
153 multiple && *multiple > 0)
154 return {MemorySlot{getResult(),
155 VectorType::get({*multiple}, type.getElementType(),
156 /*scalableDims=*/{true})}};
157 }
158 }
159
160 return {};
161}
162
163Value memref::AllocaOp::getDefaultValue(const MemorySlot &slot,
164 OpBuilder &builder) {
165 return ub::PoisonOp::create(builder, getLoc(), slot.valueType);
166}
167
168std::optional<PromotableAllocationOpInterface>
169memref::AllocaOp::handlePromotionComplete(const MemorySlot &slot,
170 Value defaultValue,
171 OpBuilder &builder) {
172 if (defaultValue && defaultValue.use_empty())
173 defaultValue.getDefiningOp()->erase();
174 this->erase();
175 return std::nullopt;
176}
177
178//===----------------------------------------------------------------------===//
179// AllocaOp: DestructurableAllocationOpInterface
180//===----------------------------------------------------------------------===//
181void memref::AllocaOp::handleBlockArgument(const MemorySlot &slot,
182 BlockArgument argument,
183 OpBuilder &builder) {}
184
186memref::AllocaOp::getDestructurableSlots() {
187 MemRefType memrefType = getType();
188 auto destructurable = llvm::dyn_cast<DestructurableTypeInterface>(memrefType);
189 if (!destructurable)
190 return {};
191
192 std::optional<DenseMap<Attribute, Type>> destructuredType =
193 destructurable.getSubelementIndexMap();
194 if (!destructuredType)
195 return {};
196
197 return {
198 DestructurableMemorySlot{{getMemref(), memrefType}, *destructuredType}};
199}
200
201DenseMap<Attribute, MemorySlot> memref::AllocaOp::destructure(
202 const DestructurableMemorySlot &slot,
203 const SmallPtrSetImpl<Attribute> &usedIndices, OpBuilder &builder,
205 builder.setInsertionPointAfter(*this);
206
208
209 auto memrefType = llvm::cast<DestructurableTypeInterface>(getType());
210 for (Attribute usedIndex : usedIndices) {
211 Type elemType = memrefType.getTypeAtIndex(usedIndex);
212 MemRefType elemPtr = MemRefType::get({}, elemType);
213 auto subAlloca = memref::AllocaOp::create(builder, getLoc(), elemPtr);
214 newAllocators.push_back(subAlloca);
215 slotMap.try_emplace<MemorySlot>(usedIndex,
216 {subAlloca.getResult(), elemType});
217 }
218
219 return slotMap;
220}
221
222std::optional<DestructurableAllocationOpInterface>
223memref::AllocaOp::handleDestructuringComplete(
224 const DestructurableMemorySlot &slot, OpBuilder &builder) {
225 assert(slot.ptr == getResult());
226 this->erase();
227 return std::nullopt;
228}
229
230//===----------------------------------------------------------------------===//
231// LoadOp: PromotableMemOpInterface
232//===----------------------------------------------------------------------===//
233bool memref::LoadOp::loadsFrom(const MemorySlot &slot) {
234 return getMemRef() == slot.ptr;
235}
236
237bool memref::LoadOp::storesTo(const MemorySlot &slot) { return false; }
238
239Value memref::LoadOp::getStored(const MemorySlot &slot, OpBuilder &builder,
240 Value reachingDef,
241 const DataLayout &dataLayout) {
242 llvm_unreachable("getStored should not be called on LoadOp");
243}
244
245bool memref::LoadOp::canUsesBeRemoved(
246 const MemorySlot &slot, const SmallPtrSetImpl<OpOperand *> &blockingUses,
247 SmallVectorImpl<OpOperand *> &newBlockingUses,
248 const DataLayout &dataLayout) {
249 if (blockingUses.size() != 1)
250 return false;
251 Value blockingUse = (*blockingUses.begin())->get();
252 return blockingUse == slot.ptr && getMemRef() == slot.ptr &&
253 getResult().getType() == slot.valueType;
254}
255
256DeletionKind memref::LoadOp::removeBlockingUses(
257 const MemorySlot &slot, const SmallPtrSetImpl<OpOperand *> &blockingUses,
258 OpBuilder &builder, Value reachingDefinition,
259 const DataLayout &dataLayout) {
260 // `canUsesBeRemoved` checked this blocking use must be the loaded slot
261 // pointer.
262 getResult().replaceAllUsesWith(reachingDefinition);
264}
265
266//===----------------------------------------------------------------------===//
267// LoadOp: DestructurableAccessorOpInterface
268//===----------------------------------------------------------------------===//
269bool memref::LoadOp::canRewire(const DestructurableMemorySlot &slot,
270 SmallPtrSetImpl<Attribute> &usedIndices,
271 SmallVectorImpl<MemorySlot> &mustBeSafelyUsed,
272 const DataLayout &dataLayout) {
273 if (slot.ptr != getMemRef())
274 return false;
277 if (!index)
278 return false;
279 usedIndices.insert(index);
280 return true;
281}
282
283DeletionKind memref::LoadOp::rewire(const DestructurableMemorySlot &slot,
285 OpBuilder &builder,
286 const DataLayout &dataLayout) {
289 const MemorySlot &memorySlot = subslots.at(index);
290 setMemRef(memorySlot.ptr);
291 getIndicesMutable().clear();
292 return DeletionKind::Keep;
293}
294
295bool memref::StoreOp::loadsFrom(const MemorySlot &slot) { return false; }
296
297bool memref::StoreOp::storesTo(const MemorySlot &slot) {
298 return getMemRef() == slot.ptr;
299}
300
301Value memref::StoreOp::getStored(const MemorySlot &slot, OpBuilder &builder,
302 Value reachingDef,
303 const DataLayout &dataLayout) {
304 return getValue();
305}
306
307//===----------------------------------------------------------------------===//
308// StoreOp: PromotableMemOpInterface
309//===----------------------------------------------------------------------===//
310bool memref::StoreOp::canUsesBeRemoved(
311 const MemorySlot &slot, const SmallPtrSetImpl<OpOperand *> &blockingUses,
312 SmallVectorImpl<OpOperand *> &newBlockingUses,
313 const DataLayout &dataLayout) {
314 if (blockingUses.size() != 1)
315 return false;
316 Value blockingUse = (*blockingUses.begin())->get();
317 return blockingUse == slot.ptr && getMemRef() == slot.ptr &&
318 getValue() != slot.ptr && getValue().getType() == slot.valueType;
319}
320
321DeletionKind memref::StoreOp::removeBlockingUses(
322 const MemorySlot &slot, const SmallPtrSetImpl<OpOperand *> &blockingUses,
323 OpBuilder &builder, Value reachingDefinition,
324 const DataLayout &dataLayout) {
326}
327
328//===----------------------------------------------------------------------===//
329// StoreOp: DestructurableAccessorOpInterface
330//===----------------------------------------------------------------------===//
331bool memref::StoreOp::canRewire(const DestructurableMemorySlot &slot,
332 SmallPtrSetImpl<Attribute> &usedIndices,
333 SmallVectorImpl<MemorySlot> &mustBeSafelyUsed,
334 const DataLayout &dataLayout) {
335 if (slot.ptr != getMemRef() || getValue() == slot.ptr)
336 return false;
339 if (!index || !slot.subelementTypes.contains(index))
340 return false;
341 usedIndices.insert(index);
342 return true;
343}
344
345DeletionKind memref::StoreOp::rewire(const DestructurableMemorySlot &slot,
347 OpBuilder &builder,
348 const DataLayout &dataLayout) {
351 const MemorySlot &memorySlot = subslots.at(index);
352 setMemRef(memorySlot.ptr);
353 getIndicesMutable().clear();
354 return DeletionKind::Keep;
355}
356
357//===----------------------------------------------------------------------===//
358// MemRefType: DestructurableTypeInterface
359//===----------------------------------------------------------------------===//
360
361namespace {
362struct MemRefDestructurableTypeExternalModel
363 : public DestructurableTypeInterface::ExternalModel<
364 MemRefDestructurableTypeExternalModel, MemRefType> {
365 std::optional<DenseMap<Attribute, Type>>
366 getSubelementIndexMap(Type type) const {
367 auto memrefType = llvm::cast<MemRefType>(type);
368 constexpr int64_t maxMemrefSizeForDestructuring = 16;
369 if (!memrefType.hasStaticShape())
370 return {};
371 std::optional<int64_t> numElements =
372 ShapedType::tryGetNumElements(memrefType.getShape());
373 if (!numElements || *numElements > maxMemrefSizeForDestructuring ||
374 *numElements == 1)
375 return {};
376
377 DenseMap<Attribute, Type> destructured;
379 memrefType.getContext(), memrefType.getShape(), [&](Attribute index) {
380 destructured.insert({index, memrefType.getElementType()});
381 });
382
383 return destructured;
384 }
385
386 Type getTypeAtIndex(Type type, Attribute index) const {
387 auto memrefType = llvm::cast<MemRefType>(type);
388 auto coordArrAttr = llvm::dyn_cast<ArrayAttr>(index);
389 if (!coordArrAttr || coordArrAttr.size() != memrefType.getShape().size())
390 return {};
391
392 Type indexType = IndexType::get(memrefType.getContext());
393 for (const auto &[coordAttr, dimSize] :
394 llvm::zip(coordArrAttr, memrefType.getShape())) {
395 auto coord = llvm::dyn_cast<IntegerAttr>(coordAttr);
396 if (!coord || coord.getType() != indexType || coord.getInt() < 0 ||
397 coord.getInt() >= dimSize)
398 return {};
399 }
400
401 return memrefType.getElementType();
402 }
403};
404} // namespace
405
406//===----------------------------------------------------------------------===//
407// Register MemRefDestructurableTypeExternalModel
408//===----------------------------------------------------------------------===//
410 DialectRegistry &registry) {
411 registry.addExtension(+[](MLIRContext *ctx, BuiltinDialect *dialect) {
412 MemRefType::attachInterface<MemRefDestructurableTypeExternalModel>(*ctx);
413 });
414}
return success()
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:64
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 registerDestructurableTypeExternalModels(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:311
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:123
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.
Type valueType
Type of the value contained in the slot.
Value ptr
Pointer to the memory slot, used by operations to refer to it.