MLIR 24.0.0git
PtrDialect.cpp
Go to the documentation of this file.
1//===- PtrDialect.cpp - Pointer dialect ---------------------*- C++ -*-===//
2//
3// This file is licensed 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 the Pointer dialect.
10//
11//===----------------------------------------------------------------------===//
12
14#include "mlir/IR/Matchers.h"
17
18using namespace mlir;
19using namespace mlir::ptr;
20
21//===----------------------------------------------------------------------===//
22// Pointer dialect
23//===----------------------------------------------------------------------===//
24
25void PtrDialect::initialize() {
26 addOperations<
27#define GET_OP_LIST
28#include "mlir/Dialect/Ptr/IR/PtrOps.cpp.inc"
29 >();
30 addAttributes<
31#define GET_ATTRDEF_LIST
32#include "mlir/Dialect/Ptr/IR/PtrOpsAttrs.cpp.inc"
33 >();
34 addTypes<
35#define GET_TYPEDEF_LIST
36#include "mlir/Dialect/Ptr/IR/PtrOpsTypes.cpp.inc"
37 >();
38}
39
40//===----------------------------------------------------------------------===//
41// Common helper functions.
42//===----------------------------------------------------------------------===//
43
44/// Verifies that the alignment attribute is a power of 2 if present.
45static LogicalResult
46verifyAlignment(std::optional<int64_t> alignment,
48 if (!alignment)
49 return success();
50 if (alignment.value() <= 0)
51 return emitError() << "alignment must be positive";
52 if (!llvm::isPowerOf2_64(alignment.value()))
53 return emitError() << "alignment must be a power of 2";
54 return success();
55}
56
57//===----------------------------------------------------------------------===//
58// ConstantOp
59//===----------------------------------------------------------------------===//
60
61OpFoldResult ConstantOp::fold(FoldAdaptor adaptor) { return getValue(); }
62
63//===----------------------------------------------------------------------===//
64// FromPtrOp
65//===----------------------------------------------------------------------===//
66
67OpFoldResult FromPtrOp::fold(FoldAdaptor adaptor) {
68 // Fold the pattern:
69 // %ptr = ptr.to_ptr %v : type -> ptr
70 // (%mda = ptr.get_metadata %v : type)?
71 // %val = ptr.from_ptr %ptr (metadata %mda)? : ptr -> type
72 // To:
73 // %val -> %v
74 Value ptrLike;
75 FromPtrOp fromPtr = *this;
76 while (fromPtr != nullptr) {
77 auto toPtr = fromPtr.getPtr().getDefiningOp<ToPtrOp>();
78 // Cannot fold if it's not a `to_ptr` op or the initial and final types are
79 // different.
80 if (!toPtr || toPtr.getPtr().getType() != fromPtr.getType())
81 return ptrLike;
82 Value md = fromPtr.getMetadata();
83 // If the type has trivial metadata fold.
84 if (!fromPtr.getType().hasPtrMetadata()) {
85 ptrLike = toPtr.getPtr();
86 } else if (md) {
87 // Fold if the metadata can be verified to be equal.
88 if (auto mdOp = md.getDefiningOp<GetMetadataOp>();
89 mdOp && mdOp.getPtr() == toPtr.getPtr())
90 ptrLike = toPtr.getPtr();
91 }
92 // Check for a sequence of casts.
93 fromPtr = ptrLike ? ptrLike.getDefiningOp<FromPtrOp>() : nullptr;
94 }
95 return ptrLike;
96}
97
98LogicalResult FromPtrOp::verify() {
99 if (isa<PtrType>(getType()))
100 return emitError() << "the result type cannot be `!ptr.ptr`";
101 if (getType().getMemorySpace() != getPtr().getType().getMemorySpace()) {
102 return emitError()
103 << "expected the input and output to have the same memory space";
104 }
105 return success();
106}
107
108//===----------------------------------------------------------------------===//
109// GatherOp
110//===----------------------------------------------------------------------===//
111
112void GatherOp::getEffects(
114 &effects) {
115 // Gather performs reads from multiple memory locations specified by ptrs
116 effects.emplace_back(MemoryEffects::Read::get(), &getPtrsMutable());
117}
118
119LogicalResult GatherOp::verify() {
120 auto emitDiag = [&]() -> InFlightDiagnostic { return emitError(); };
121
122 // Verify that the pointer type's memory space allows loads.
123 MemorySpaceAttrInterface ms =
124 cast<PtrType>(getPtrs().getType().getElementType()).getMemorySpace();
125 DataLayout dataLayout = DataLayout::closest(*this);
126 if (!ms.isValidLoad(getResult().getType(), AtomicOrdering::not_atomic,
127 getAlignment(), &dataLayout, emitDiag))
128 return failure();
129
130 // Verify the alignment.
131 return verifyAlignment(getAlignment(), emitDiag);
132}
133
134void GatherOp::build(OpBuilder &builder, OperationState &state, Type resultType,
135 Value ptrs, Value mask, Value passthrough,
136 unsigned alignment) {
137 build(builder, state, resultType, ptrs, mask, passthrough,
138 alignment ? std::optional<int64_t>(alignment) : std::nullopt);
139}
140
141//===----------------------------------------------------------------------===//
142// LoadOp
143//===----------------------------------------------------------------------===//
144
145/// Verifies the attributes and the type of atomic memory access operations.
146template <typename OpTy>
147static LogicalResult
148verifyAtomicMemOp(OpTy memOp, ArrayRef<AtomicOrdering> unsupportedOrderings) {
149 if (memOp.getOrdering() != AtomicOrdering::not_atomic) {
150 if (llvm::is_contained(unsupportedOrderings, memOp.getOrdering()))
151 return memOp.emitOpError("unsupported ordering '")
152 << stringifyAtomicOrdering(memOp.getOrdering()) << "'";
153 if (!memOp.getAlignment())
154 return memOp.emitOpError("expected alignment for atomic access");
155 return success();
156 }
157 if (memOp.getSyncscope()) {
158 return memOp.emitOpError(
159 "expected syncscope to be null for non-atomic access");
160 }
161 return success();
162}
163
164void LoadOp::getEffects(
166 &effects) {
167 effects.emplace_back(MemoryEffects::Read::get(), &getPtrMutable());
168 // Volatile operations can have target-specific read-write effects on
169 // memory besides the one referred to by the pointer operand.
170 // Similarly, atomic operations that are monotonic or stricter cause
171 // synchronization that from a language point-of-view, are arbitrary
172 // read-writes into memory.
173 if (getVolatile_() || (getOrdering() != AtomicOrdering::not_atomic &&
174 getOrdering() != AtomicOrdering::unordered)) {
175 effects.emplace_back(MemoryEffects::Write::get());
176 effects.emplace_back(MemoryEffects::Read::get());
177 }
178}
179
180LogicalResult LoadOp::verify() {
181 auto emitDiag = [&]() -> InFlightDiagnostic { return emitError(); };
182 MemorySpaceAttrInterface ms = getPtr().getType().getMemorySpace();
183 DataLayout dataLayout = DataLayout::closest(*this);
184 if (!ms.isValidLoad(getResult().getType(), getOrdering(), getAlignment(),
185 &dataLayout, emitDiag))
186 return failure();
187 if (failed(verifyAlignment(getAlignment(), emitDiag)))
188 return failure();
189 return verifyAtomicMemOp(*this,
190 {AtomicOrdering::release, AtomicOrdering::acq_rel});
191}
192
193void LoadOp::build(OpBuilder &builder, OperationState &state, Type type,
194 Value addr, unsigned alignment, bool isVolatile,
195 bool isNonTemporal, bool isInvariant, bool isInvariantGroup,
196 AtomicOrdering ordering, StringRef syncscope) {
197 build(builder, state, type, addr,
198 alignment ? std::optional<int64_t>(alignment) : std::nullopt,
199 isVolatile, isNonTemporal, isInvariant, isInvariantGroup, ordering,
200 syncscope.empty() ? nullptr : builder.getStringAttr(syncscope));
201}
202//===----------------------------------------------------------------------===//
203// MaskedLoadOp
204//===----------------------------------------------------------------------===//
205
206void MaskedLoadOp::getEffects(
208 &effects) {
209 // MaskedLoad performs reads from the memory location specified by ptr.
210 effects.emplace_back(MemoryEffects::Read::get(), &getPtrMutable());
211}
212
213LogicalResult MaskedLoadOp::verify() {
214 auto emitDiag = [&]() -> InFlightDiagnostic { return emitError(); };
215 // Verify that the pointer type's memory space allows loads.
216 MemorySpaceAttrInterface ms = getPtr().getType().getMemorySpace();
217 DataLayout dataLayout = DataLayout::closest(*this);
218 if (!ms.isValidLoad(getResult().getType(), AtomicOrdering::not_atomic,
219 getAlignment(), &dataLayout, emitDiag))
220 return failure();
221
222 // Verify the alignment.
223 return verifyAlignment(getAlignment(), emitDiag);
224}
225
226void MaskedLoadOp::build(OpBuilder &builder, OperationState &state,
227 Type resultType, Value ptr, Value mask,
228 Value passthrough, unsigned alignment) {
229 build(builder, state, resultType, ptr, mask, passthrough,
230 alignment ? std::optional<int64_t>(alignment) : std::nullopt);
231}
232
233//===----------------------------------------------------------------------===//
234// MaskedStoreOp
235//===----------------------------------------------------------------------===//
236
237void MaskedStoreOp::getEffects(
239 &effects) {
240 // MaskedStore performs writes to the memory location specified by ptr
241 effects.emplace_back(MemoryEffects::Write::get(), &getPtrMutable());
242}
243
244LogicalResult MaskedStoreOp::verify() {
245 auto emitDiag = [&]() -> InFlightDiagnostic { return emitError(); };
246 // Verify that the pointer type's memory space allows stores.
247 MemorySpaceAttrInterface ms = getPtr().getType().getMemorySpace();
248 DataLayout dataLayout = DataLayout::closest(*this);
249 if (!ms.isValidStore(getValue().getType(), AtomicOrdering::not_atomic,
250 getAlignment(), &dataLayout, emitDiag))
251 return failure();
252
253 // Verify the alignment.
254 return verifyAlignment(getAlignment(), emitDiag);
255}
256
257void MaskedStoreOp::build(OpBuilder &builder, OperationState &state,
258 Value value, Value ptr, Value mask,
259 unsigned alignment) {
260 build(builder, state, value, ptr, mask,
261 alignment ? std::optional<int64_t>(alignment) : std::nullopt);
262}
263
264//===----------------------------------------------------------------------===//
265// ScatterOp
266//===----------------------------------------------------------------------===//
267
268void ScatterOp::getEffects(
270 &effects) {
271 // Scatter performs writes to multiple memory locations specified by ptrs
272 effects.emplace_back(MemoryEffects::Write::get(), &getPtrsMutable());
273}
274
275LogicalResult ScatterOp::verify() {
276 auto emitDiag = [&]() -> InFlightDiagnostic { return emitError(); };
277
278 // Verify that the pointer type's memory space allows stores.
279 MemorySpaceAttrInterface ms =
280 cast<PtrType>(getPtrs().getType().getElementType()).getMemorySpace();
281 DataLayout dataLayout = DataLayout::closest(*this);
282 if (!ms.isValidStore(getValue().getType(), AtomicOrdering::not_atomic,
283 getAlignment(), &dataLayout, emitDiag))
284 return failure();
285
286 // Verify the alignment.
287 return verifyAlignment(getAlignment(), emitDiag);
288}
289
290void ScatterOp::build(OpBuilder &builder, OperationState &state, Value value,
291 Value ptrs, Value mask, unsigned alignment) {
292 build(builder, state, value, ptrs, mask,
293 alignment ? std::optional<int64_t>(alignment) : std::nullopt);
294}
295
296//===----------------------------------------------------------------------===//
297// StoreOp
298//===----------------------------------------------------------------------===//
299
300void StoreOp::getEffects(
302 &effects) {
303 effects.emplace_back(MemoryEffects::Write::get(), &getPtrMutable());
304 // Volatile operations can have target-specific read-write effects on
305 // memory besides the one referred to by the pointer operand.
306 // Similarly, atomic operations that are monotonic or stricter cause
307 // synchronization that from a language point-of-view, are arbitrary
308 // read-writes into memory.
309 if (getVolatile_() || (getOrdering() != AtomicOrdering::not_atomic &&
310 getOrdering() != AtomicOrdering::unordered)) {
311 effects.emplace_back(MemoryEffects::Write::get());
312 effects.emplace_back(MemoryEffects::Read::get());
313 }
314}
315
316LogicalResult StoreOp::verify() {
317 auto emitDiag = [&]() -> InFlightDiagnostic { return emitError(); };
318 MemorySpaceAttrInterface ms = getPtr().getType().getMemorySpace();
319 DataLayout dataLayout = DataLayout::closest(*this);
320 if (!ms.isValidStore(getValue().getType(), getOrdering(), getAlignment(),
321 &dataLayout, emitDiag))
322 return failure();
323 if (failed(verifyAlignment(getAlignment(), emitDiag)))
324 return failure();
325 return verifyAtomicMemOp(*this,
326 {AtomicOrdering::acquire, AtomicOrdering::acq_rel});
327}
328
329void StoreOp::build(OpBuilder &builder, OperationState &state, Value value,
330 Value addr, unsigned alignment, bool isVolatile,
331 bool isNonTemporal, bool isInvariantGroup,
332 AtomicOrdering ordering, StringRef syncscope) {
333 build(builder, state, value, addr,
334 alignment ? std::optional<int64_t>(alignment) : std::nullopt,
335 isVolatile, isNonTemporal, isInvariantGroup, ordering,
336 syncscope.empty() ? nullptr : builder.getStringAttr(syncscope));
337}
338
339//===----------------------------------------------------------------------===//
340// PtrAddOp
341//===----------------------------------------------------------------------===//
342
343/// Fold: ptradd ptr + 0 -> ptr
344OpFoldResult PtrAddOp::fold(FoldAdaptor adaptor) {
345 Attribute attr = adaptor.getOffset();
346 // A shaped offset can broadcast a scalar base to a shaped result.
347 if (!attr || getBase().getType() != getType())
348 return nullptr;
349 if (llvm::APInt value; m_ConstantInt(&value).match(attr) && value.isZero())
350 return getBase();
351 return nullptr;
352}
353
354LogicalResult PtrAddOp::inferReturnTypes(
355 MLIRContext *context, std::optional<Location> location, ValueRange operands,
356 DictionaryAttr attributes, PropertyRef properties, RegionRange regions,
357 SmallVectorImpl<Type> &inferredReturnTypes) {
358 // Get the base pointer and offset types.
359 Type baseType = operands[0].getType();
360 Type offsetType = operands[1].getType();
361
362 auto offTy = dyn_cast<ShapedType>(offsetType);
363 if (!offTy) {
364 // If the offset isn't shaped, the result is always the base type.
365 inferredReturnTypes.push_back(baseType);
366 return success();
367 }
368 auto baseTy = dyn_cast<ShapedType>(baseType);
369 if (!baseTy) {
370 // Base isn't shaped, but offset is, use the ShapedType from offset with the
371 // base pointer as element type.
372 inferredReturnTypes.push_back(offTy.clone(baseType));
373 return success();
374 }
375
376 // Both are shaped, their shape must match.
377 if (offTy.getShape() != baseTy.getShape()) {
378 if (location)
379 mlir::emitError(*location) << "shapes of base and offset must match";
380 return failure();
381 }
382
383 // Make sure they are the same kind of shaped type.
384 if (baseType.getTypeID() != offsetType.getTypeID()) {
385 if (location)
386 mlir::emitError(*location) << "the shaped containers type must match";
387 return failure();
388 }
389 inferredReturnTypes.push_back(baseType);
390 return success();
391}
392
393//===----------------------------------------------------------------------===//
394// PtrDiffOp
395//===----------------------------------------------------------------------===//
396
397LogicalResult PtrDiffOp::verify() {
398 // If the operands are not shaped early exit.
399 if (!isa<ShapedType>(getLhs().getType()))
400 return success();
401
402 // Just check the container type matches, `SameOperandsAndResultShape` handles
403 // the actual shape.
404 if (getResult().getType().getTypeID() != getLhs().getType().getTypeID()) {
405 return emitError() << "expected the result to have the same container "
406 "type as the operands when operands are shaped";
407 }
408
409 return success();
410}
411
412ptr::PtrType PtrDiffOp::getPtrType() {
413 Type lhsType = getLhs().getType();
414 if (auto shapedType = dyn_cast<ShapedType>(lhsType))
415 return cast<ptr::PtrType>(shapedType.getElementType());
416 return cast<ptr::PtrType>(lhsType);
417}
418
419Type PtrDiffOp::getIntType() {
420 Type resultType = getResult().getType();
421 if (auto shapedType = dyn_cast<ShapedType>(resultType))
422 return shapedType.getElementType();
423 return resultType;
424}
425
426//===----------------------------------------------------------------------===//
427// ToPtrOp
428//===----------------------------------------------------------------------===//
429
430OpFoldResult ToPtrOp::fold(FoldAdaptor adaptor) {
431 // Fold the pattern:
432 // %val = ptr.from_ptr %p (metadata ...)? : ptr -> type
433 // %ptr = ptr.to_ptr %val : type -> ptr
434 // To:
435 // %ptr -> %p
436 Value ptr;
437 ToPtrOp toPtr = *this;
438 while (toPtr != nullptr) {
439 auto fromPtr = toPtr.getPtr().getDefiningOp<FromPtrOp>();
440 // Cannot fold if it's not a `from_ptr` op.
441 if (!fromPtr)
442 return ptr;
443 ptr = fromPtr.getPtr();
444 // Check for chains of casts.
445 toPtr = ptr.getDefiningOp<ToPtrOp>();
446 }
447 return ptr;
448}
449
450LogicalResult ToPtrOp::verify() {
451 if (isa<PtrType>(getPtr().getType()))
452 return emitError() << "the input value cannot be of type `!ptr.ptr`";
453 if (getType().getMemorySpace() != getPtr().getType().getMemorySpace()) {
454 return emitError()
455 << "expected the input and output to have the same memory space";
456 }
457 return success();
458}
459
460//===----------------------------------------------------------------------===//
461// TypeOffsetOp
462//===----------------------------------------------------------------------===//
463
464llvm::TypeSize TypeOffsetOp::getTypeSize(std::optional<DataLayout> layout) {
465 if (!layout)
466 layout.emplace(DataLayout::closest(*this));
467 return layout->getTypeAllocSize(getElementType());
468}
469
470//===----------------------------------------------------------------------===//
471// Pointer API.
472//===----------------------------------------------------------------------===//
473
474#include "mlir/Dialect/Ptr/IR/PtrOpsDialect.cpp.inc"
475
476#define GET_ATTRDEF_CLASSES
477#include "mlir/Dialect/Ptr/IR/PtrOpsAttrs.cpp.inc"
478
479#include "mlir/Dialect/Ptr/IR/PtrOpsEnums.cpp.inc"
480
481#define GET_TYPEDEF_CLASSES
482#include "mlir/Dialect/Ptr/IR/PtrOpsTypes.cpp.inc"
483
484#define GET_OP_CLASSES
485#include "mlir/Dialect/Ptr/IR/PtrOps.cpp.inc"
return success()
static Value getBase(Value v)
Looks through known "view-like" ops to find the base memref.
static InFlightDiagnostic emitDiag(Location location, DiagnosticSeverity severity, const Twine &message)
Helper function used to emit a diagnostic with an optionally empty twine message.
static LogicalResult verifyAtomicMemOp(OpTy memOp, ArrayRef< AtomicOrdering > unsupportedOrderings)
Verifies the attributes and the type of atomic memory access operations.
static LogicalResult verifyAlignment(std::optional< int64_t > alignment, function_ref< InFlightDiagnostic()> emitError)
Verifies that the alignment attribute is a power of 2 if present.
static Type getElementType(Type type, ArrayRef< int32_t > indices, function_ref< InFlightDiagnostic(StringRef)> emitErrorFn)
Walks the given type hierarchy with the given indices, potentially down to component granularity,...
Definition SPIRVOps.cpp:229
Attributes are known-constant values of operations.
Definition Attributes.h:25
StringAttr getStringAttr(const Twine &bytes)
Definition Builders.cpp:271
The main mechanism for performing data layout queries.
static DataLayout closest(Operation *op)
Returns the layout of the closest parent operation carrying layout info.
This class represents a diagnostic that is inflight and set to be reported.
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:71
This class helps build Operations.
Definition Builders.h:210
This class represents a single result from folding an operation.
Type-safe wrapper around a void* for passing properties, including the properties structs of operatio...
This class provides an abstraction over the different types of ranges over Regions.
Definition Region.h:363
This class represents a specific instance of an effect.
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
TypeID getTypeID()
Return a unique identifier for the concrete type.
Definition Types.h:101
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:392
type_range getType() const
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition Value.cpp:18
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:853
Include the generated interface declarations.
detail::constant_int_value_binder m_ConstantInt(IntegerAttr::ValueType *bind_value)
Matches a constant holding a scalar/vector/tensor integer (splat) and writes the integer value to bin...
Definition Matchers.h:527
Type getType(OpFoldResult ofr)
Returns the int type of the integer in ofr.
Definition Utils.cpp:311
InFlightDiagnostic emitError(Location loc)
Utility method to emit an error message using this location.
llvm::function_ref< Fn > function_ref
Definition LLVM.h:155
This represents an operation in an abstracted form, suitable for use with the builder APIs.