MLIR 24.0.0git
MemRefToSPIRV.cpp
Go to the documentation of this file.
1//===- MemRefToSPIRV.cpp - MemRef to SPIR-V Patterns ----------------------===//
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 patterns to convert MemRef dialect to SPIR-V dialect.
10//
11//===----------------------------------------------------------------------===//
12
22#include "mlir/IR/MLIRContext.h"
23#include "mlir/IR/Visitors.h"
24#include <cassert>
25#include <limits>
26#include <optional>
27
28#define DEBUG_TYPE "memref-to-spirv-pattern"
29
30using namespace mlir;
31
32//===----------------------------------------------------------------------===//
33// Utility functions
34//===----------------------------------------------------------------------===//
35
36/// Returns the offset of the value in `targetBits` representation.
37///
38/// `srcIdx` is an index into a 1-D array with each element having `sourceBits`.
39/// It's assumed to be non-negative.
40///
41/// When accessing an element in the array treating as having elements of
42/// `targetBits`, multiple values are loaded in the same time. The method
43/// returns the offset where the `srcIdx` locates in the value. For example, if
44/// `sourceBits` equals to 8 and `targetBits` equals to 32, the x-th element is
45/// located at (x % 4) * 8. Because there are four elements in one i32, and one
46/// element has 8 bits.
47static Value getOffsetForBitwidth(Location loc, Value srcIdx, int sourceBits,
48 int targetBits, OpBuilder &builder) {
49 assert(targetBits % sourceBits == 0);
50 Type type = srcIdx.getType();
51 IntegerAttr idxAttr = builder.getIntegerAttr(type, targetBits / sourceBits);
52 auto idx = builder.createOrFold<spirv::ConstantOp>(loc, type, idxAttr);
53 IntegerAttr srcBitsAttr = builder.getIntegerAttr(type, sourceBits);
54 auto srcBitsValue =
55 builder.createOrFold<spirv::ConstantOp>(loc, type, srcBitsAttr);
56 auto m = builder.createOrFold<spirv::UModOp>(loc, srcIdx, idx);
57 return builder.createOrFold<spirv::IMulOp>(loc, type, m, srcBitsValue);
58}
59
60/// Returns an adjusted spirv::AccessChainOp. Based on the
61/// extension/capabilities, certain integer bitwidths `sourceBits` might not be
62/// supported. During conversion if a memref of an unsupported type is used,
63/// load/stores to this memref need to be modified to use a supported higher
64/// bitwidth `targetBits` and extracting the required bits. For an accessing a
65/// 1D array (spirv.array or spirv.rtarray), the last index is modified to load
66/// the bits needed. The extraction of the actual bits needed are handled
67/// separately. Note that this only works for a 1-D tensor.
68static Value
70 spirv::AccessChainOp op, int sourceBits,
71 int targetBits, OpBuilder &builder) {
72 assert(targetBits % sourceBits == 0);
73 const auto loc = op.getLoc();
74 Value lastDim = op->getOperand(op.getNumOperands() - 1);
75 Type type = lastDim.getType();
76 IntegerAttr attr = builder.getIntegerAttr(type, targetBits / sourceBits);
77 auto idx = builder.createOrFold<spirv::ConstantOp>(loc, type, attr);
78 auto indices = llvm::to_vector<4>(op.getIndices());
79 // There are two elements if this is a 1-D tensor.
80 assert(indices.size() == 2);
81 indices.back() = builder.createOrFold<spirv::SDivOp>(loc, lastDim, idx);
82 Type t = typeConverter.convertType(op.getComponentPtr().getType());
83 return spirv::AccessChainOp::create(builder, loc, t, op.getBasePtr(),
84 indices);
85}
86
87/// Casts the given `srcBool` into an integer of `dstType`.
88static Value castBoolToIntN(Location loc, Value srcBool, Type dstType,
89 OpBuilder &builder) {
90 assert(srcBool.getType().isInteger(1));
91 if (dstType.isInteger(1))
92 return srcBool;
93 Value zero = spirv::ConstantOp::getZero(dstType, loc, builder);
94 Value one = spirv::ConstantOp::getOne(dstType, loc, builder);
95 return builder.createOrFold<spirv::SelectOp>(loc, dstType, srcBool, one,
96 zero);
97}
98
99/// Returns the `targetBits`-bit value shifted by the given `offset`, and cast
100/// to the type destination type, and masked.
101static Value shiftValue(Location loc, Value value, Value offset, Value mask,
102 OpBuilder &builder) {
103 IntegerType dstType = cast<IntegerType>(mask.getType());
104 int targetBits = static_cast<int>(dstType.getWidth());
105 int valueBits = value.getType().getIntOrFloatBitWidth();
106 assert(valueBits <= targetBits);
107
108 if (valueBits == 1) {
109 value = castBoolToIntN(loc, value, dstType, builder);
110 } else {
111 if (valueBits < targetBits) {
112 value = spirv::UConvertOp::create(
113 builder, loc, builder.getIntegerType(targetBits), value);
114 }
115
116 value = builder.createOrFold<spirv::BitwiseAndOp>(loc, value, mask);
117 }
118 return builder.createOrFold<spirv::ShiftLeftLogicalOp>(loc, value.getType(),
119 value, offset);
120}
121
122/// Returns true if the allocations of memref `type` generated from `allocOp`
123/// can be lowered to SPIR-V.
124static bool isAllocationSupported(Operation *allocOp, MemRefType type) {
125 if (isa<memref::AllocOp, memref::DeallocOp>(allocOp)) {
126 auto sc = dyn_cast_or_null<spirv::StorageClassAttr>(type.getMemorySpace());
127 if (!sc || sc.getValue() != spirv::StorageClass::Workgroup)
128 return false;
129 } else if (isa<memref::AllocaOp>(allocOp)) {
130 auto sc = dyn_cast_or_null<spirv::StorageClassAttr>(type.getMemorySpace());
131 if (!sc || sc.getValue() != spirv::StorageClass::Function)
132 return false;
133 } else {
134 return false;
135 }
136
137 // Currently only support static shape and int or float, complex of int or
138 // float, or vector of int or float element type.
139 if (!type.hasStaticShape())
140 return false;
141
142 Type elementType = type.getElementType();
143 if (auto vecType = dyn_cast<VectorType>(elementType))
144 elementType = vecType.getElementType();
145 if (auto compType = dyn_cast<ComplexType>(elementType))
146 elementType = compType.getElementType();
147 return elementType.isIntOrFloat();
148}
149
150/// Returns the scope to use for atomic operations use for emulating store
151/// operations of unsupported integer bitwidths, based on the memref
152/// type. Returns std::nullopt on failure.
153static std::optional<spirv::Scope> getAtomicOpScope(MemRefType type) {
154 auto sc = dyn_cast_or_null<spirv::StorageClassAttr>(type.getMemorySpace());
155 switch (sc.getValue()) {
156 case spirv::StorageClass::StorageBuffer:
157 return spirv::Scope::Device;
158 case spirv::StorageClass::Workgroup:
159 return spirv::Scope::Workgroup;
160 default:
161 break;
162 }
163 return {};
164}
165
166/// Returns the MemorySemantics storage-class bit corresponding to `sc`.
167/// Per SPIR-V spec section 3.32 (Memory Semantics) this bit must be OR'd
168/// with the ordering bits (Acquire/Release/...) on atomic operations.
169static spirv::MemorySemantics
170getMemorySemanticsForStorageClass(spirv::StorageClass sc) {
171 switch (sc) {
172 case spirv::StorageClass::StorageBuffer:
173 case spirv::StorageClass::Uniform:
174 return spirv::MemorySemantics::UniformMemory;
175 case spirv::StorageClass::Workgroup:
176 return spirv::MemorySemantics::WorkgroupMemory;
177 case spirv::StorageClass::CrossWorkgroup:
178 return spirv::MemorySemantics::CrossWorkgroupMemory;
179 case spirv::StorageClass::AtomicCounter:
180 return spirv::MemorySemantics::AtomicCounterMemory;
181 case spirv::StorageClass::Image:
182 return spirv::MemorySemantics::ImageMemory;
183 default:
184 return spirv::MemorySemantics::None;
185 }
186}
187
188/// Returns the AcquireRelease memory semantics OR'd with the storage-class
189/// bit derived from the memory space of `type`.
190static spirv::MemorySemantics getAtomicAcqRelMemorySemantics(MemRefType type) {
191 auto sc = cast<spirv::StorageClassAttr>(type.getMemorySpace()).getValue();
192 return spirv::MemorySemantics::AcquireRelease |
194}
195
196/// Extracts the element type from a SPIR-V pointer type pointing to storage.
197///
198/// For Kernel capability, the pointer points directly to the element type
199/// (possibly wrapped in an array). For Vulkan, the pointer points to a struct
200/// containing an array or runtime array, and we need to unwrap to get the
201/// element type.
202static Type
204 const SPIRVTypeConverter &typeConverter) {
205 if (typeConverter.allows(spirv::Capability::Kernel)) {
206 if (auto arrayType = dyn_cast<spirv::ArrayType>(pointeeType))
207 return arrayType.getElementType();
208 return pointeeType;
209 }
210 // For Vulkan we need to extract element from wrapping struct and array.
211 Type structElemType = cast<spirv::StructType>(pointeeType).getElementType(0);
212 if (auto arrayType = dyn_cast<spirv::ArrayType>(structElemType))
213 return arrayType.getElementType();
214 return cast<spirv::RuntimeArrayType>(structElemType).getElementType();
215}
216
217/// Casts the given `srcInt` into a boolean value.
218static Value castIntNToBool(Location loc, Value srcInt, OpBuilder &builder) {
219 if (srcInt.getType().isInteger(1))
220 return srcInt;
221
222 auto one = spirv::ConstantOp::getZero(srcInt.getType(), loc, builder);
223 return builder.createOrFold<spirv::INotEqualOp>(loc, srcInt, one);
224}
225
226//===----------------------------------------------------------------------===//
227// Operation conversion
228//===----------------------------------------------------------------------===//
229
230// Note that DRR cannot be used for the patterns in this file: we may need to
231// convert type along the way, which requires ConversionPattern. DRR generates
232// normal RewritePattern.
233
234namespace {
235
236/// Converts memref.alloca to SPIR-V Function variables.
237class AllocaOpPattern final : public OpConversionPattern<memref::AllocaOp> {
238public:
239 using Base::Base;
240
241 LogicalResult
242 matchAndRewrite(memref::AllocaOp allocaOp, OpAdaptor adaptor,
243 ConversionPatternRewriter &rewriter) const override;
244};
245
246/// Converts an allocation operation to SPIR-V. Currently only supports lowering
247/// to Workgroup memory when the size is constant. Note that this pattern needs
248/// to be applied in a pass that runs at least at spirv.module scope since it
249/// wil ladd global variables into the spirv.module.
250class AllocOpPattern final : public OpConversionPattern<memref::AllocOp> {
251public:
252 using Base::Base;
253
254 LogicalResult
255 matchAndRewrite(memref::AllocOp operation, OpAdaptor adaptor,
256 ConversionPatternRewriter &rewriter) const override;
257};
258
259/// Converts memref.automic_rmw operations to SPIR-V atomic operations.
260class AtomicRMWOpPattern final
261 : public OpConversionPattern<memref::AtomicRMWOp> {
262public:
263 using Base::Base;
264
265 LogicalResult
266 matchAndRewrite(memref::AtomicRMWOp atomicOp, OpAdaptor adaptor,
267 ConversionPatternRewriter &rewriter) const override;
268};
269
270/// Removed a deallocation if it is a supported allocation. Currently only
271/// removes deallocation if the memory space is workgroup memory.
272class DeallocOpPattern final : public OpConversionPattern<memref::DeallocOp> {
273public:
274 using Base::Base;
275
276 LogicalResult
277 matchAndRewrite(memref::DeallocOp operation, OpAdaptor adaptor,
278 ConversionPatternRewriter &rewriter) const override;
279};
280
281/// Converts memref.load to spirv.Load + spirv.AccessChain on integers.
282class IntLoadOpPattern final : public OpConversionPattern<memref::LoadOp> {
283public:
284 using Base::Base;
285
286 LogicalResult
287 matchAndRewrite(memref::LoadOp loadOp, OpAdaptor adaptor,
288 ConversionPatternRewriter &rewriter) const override;
289};
290
291/// Converts memref.load to spirv.Load + spirv.AccessChain.
292class LoadOpPattern final : public OpConversionPattern<memref::LoadOp> {
293public:
294 using Base::Base;
295
296 LogicalResult
297 matchAndRewrite(memref::LoadOp loadOp, OpAdaptor adaptor,
298 ConversionPatternRewriter &rewriter) const override;
299};
300
301/// Converts memref.load to spirv.Image + spirv.ImageFetch
302class ImageLoadOpPattern final : public OpConversionPattern<memref::LoadOp> {
303public:
304 using Base::Base;
305
306 LogicalResult
307 matchAndRewrite(memref::LoadOp loadOp, OpAdaptor adaptor,
308 ConversionPatternRewriter &rewriter) const override;
309};
310
311/// Converts memref.store to spirv.Store on integers.
312class IntStoreOpPattern final : public OpConversionPattern<memref::StoreOp> {
313public:
314 using Base::Base;
315
316 LogicalResult
317 matchAndRewrite(memref::StoreOp storeOp, OpAdaptor adaptor,
318 ConversionPatternRewriter &rewriter) const override;
319};
320
321/// Converts memref.memory_space_cast to the appropriate spirv cast operations.
322class MemorySpaceCastOpPattern final
323 : public OpConversionPattern<memref::MemorySpaceCastOp> {
324public:
325 using Base::Base;
326
327 LogicalResult
328 matchAndRewrite(memref::MemorySpaceCastOp addrCastOp, OpAdaptor adaptor,
329 ConversionPatternRewriter &rewriter) const override;
330};
331
332/// Converts memref.store to spirv.Store.
333class StoreOpPattern final : public OpConversionPattern<memref::StoreOp> {
334public:
335 using Base::Base;
336
337 LogicalResult
338 matchAndRewrite(memref::StoreOp storeOp, OpAdaptor adaptor,
339 ConversionPatternRewriter &rewriter) const override;
340};
341
342/// Converts memref.copy to spirv.CopyMemory.
343class CopyOpPattern final : public OpConversionPattern<memref::CopyOp> {
344public:
345 using Base::Base;
346
347 LogicalResult
348 matchAndRewrite(memref::CopyOp copyOp, OpAdaptor adaptor,
349 ConversionPatternRewriter &rewriter) const override;
350};
351
352class ReinterpretCastPattern final
353 : public OpConversionPattern<memref::ReinterpretCastOp> {
354public:
355 using Base::Base;
356
357 LogicalResult
358 matchAndRewrite(memref::ReinterpretCastOp op, OpAdaptor adaptor,
359 ConversionPatternRewriter &rewriter) const override;
360};
361
362class CastPattern final : public OpConversionPattern<memref::CastOp> {
363public:
364 using Base::Base;
365
366 LogicalResult
367 matchAndRewrite(memref::CastOp op, OpAdaptor adaptor,
368 ConversionPatternRewriter &rewriter) const override {
369 Value src = adaptor.getSource();
370 Type srcType = src.getType();
371
372 const TypeConverter *converter = getTypeConverter();
373 Type dstType = converter->convertType(op.getType());
374 if (srcType != dstType)
375 return rewriter.notifyMatchFailure(op, [&](Diagnostic &diag) {
376 diag << "types doesn't match: " << srcType << " and " << dstType;
377 });
378
379 rewriter.replaceOp(op, src);
380 return success();
381 }
382};
383
384/// Converts memref.extract_aligned_pointer_as_index to spirv.ConvertPtrToU.
385class ExtractAlignedPointerAsIndexOpPattern final
386 : public OpConversionPattern<memref::ExtractAlignedPointerAsIndexOp> {
387public:
388 using Base::Base;
389
390 LogicalResult
391 matchAndRewrite(memref::ExtractAlignedPointerAsIndexOp extractOp,
392 OpAdaptor adaptor,
393 ConversionPatternRewriter &rewriter) const override;
394};
395} // namespace
396
397//===----------------------------------------------------------------------===//
398// AllocaOp
399//===----------------------------------------------------------------------===//
400
401LogicalResult
402AllocaOpPattern::matchAndRewrite(memref::AllocaOp allocaOp, OpAdaptor adaptor,
403 ConversionPatternRewriter &rewriter) const {
404 MemRefType allocType = allocaOp.getType();
405 if (!isAllocationSupported(allocaOp, allocType))
406 return rewriter.notifyMatchFailure(allocaOp, "unhandled allocation type");
407
408 // Get the SPIR-V type for the allocation.
409 Type spirvType = getTypeConverter()->convertType(allocType);
410 if (!spirvType)
411 return rewriter.notifyMatchFailure(allocaOp, "type conversion failed");
412
413 rewriter.replaceOpWithNewOp<spirv::VariableOp>(allocaOp, spirvType,
414 spirv::StorageClass::Function,
415 /*initializer=*/nullptr);
416 return success();
417}
418
419//===----------------------------------------------------------------------===//
420// AllocOp
421//===----------------------------------------------------------------------===//
422
423LogicalResult
424AllocOpPattern::matchAndRewrite(memref::AllocOp operation, OpAdaptor adaptor,
425 ConversionPatternRewriter &rewriter) const {
426 MemRefType allocType = operation.getType();
427 if (!isAllocationSupported(operation, allocType))
428 return rewriter.notifyMatchFailure(operation, "unhandled allocation type");
429
430 // Get the SPIR-V type for the allocation.
431 Type spirvType = getTypeConverter()->convertType(allocType);
432 if (!spirvType)
433 return rewriter.notifyMatchFailure(operation, "type conversion failed");
434
435 // Insert spirv.GlobalVariable for this allocation.
436 Operation *parent =
437 SymbolTable::getNearestSymbolTable(operation->getParentOp());
438 if (!parent)
439 return failure();
440 Location loc = operation.getLoc();
441 spirv::GlobalVariableOp varOp;
442 {
443 OpBuilder::InsertionGuard guard(rewriter);
444 Block &entryBlock = *parent->getRegion(0).begin();
445 rewriter.setInsertionPointToStart(&entryBlock);
446 auto varOps = entryBlock.getOps<spirv::GlobalVariableOp>();
447 std::string varName =
448 std::string("__workgroup_mem__") +
449 std::to_string(std::distance(varOps.begin(), varOps.end()));
450 varOp = spirv::GlobalVariableOp::create(rewriter, loc, spirvType, varName,
451 /*initializer=*/nullptr);
452 }
453
454 // Get pointer to global variable at the current scope.
455 rewriter.replaceOpWithNewOp<spirv::AddressOfOp>(operation, varOp);
456 return success();
457}
458
459//===----------------------------------------------------------------------===//
460// AllocOp
461//===----------------------------------------------------------------------===//
462
463LogicalResult
464AtomicRMWOpPattern::matchAndRewrite(memref::AtomicRMWOp atomicOp,
465 OpAdaptor adaptor,
466 ConversionPatternRewriter &rewriter) const {
467 auto memrefType = cast<MemRefType>(atomicOp.getMemref().getType());
468 std::optional<spirv::Scope> scope = getAtomicOpScope(memrefType);
469 if (!scope)
470 return rewriter.notifyMatchFailure(atomicOp,
471 "unsupported memref memory space");
472
473 auto &typeConverter = *getTypeConverter<SPIRVTypeConverter>();
474 Type resultType = typeConverter.convertType(atomicOp.getType());
475 if (!resultType)
476 return rewriter.notifyMatchFailure(atomicOp,
477 "failed to convert result type");
478
479 auto loc = atomicOp.getLoc();
480 Value ptr =
481 spirv::getElementPtr(typeConverter, memrefType, adaptor.getMemref(),
482 adaptor.getIndices(), loc, rewriter);
483
484 if (!ptr)
485 return failure();
486
487 // Determine the source and destination bitwidths. The source is the original
488 // memref element type and the destination is the SPIR-V storage type (e.g.,
489 // i32 for Vulkan).
490 int srcBits = memrefType.getElementType().getIntOrFloatBitWidth();
491 auto pointerType = typeConverter.convertType<spirv::PointerType>(memrefType);
492 if (!pointerType)
493 return rewriter.notifyMatchFailure(atomicOp,
494 "failed to convert memref type");
495
496 Type pointeeType = pointerType.getPointeeType();
497 Type storageElemType =
498 getElementTypeForStoragePointer(pointeeType, typeConverter);
499 if (!storageElemType || !storageElemType.isIntOrFloat())
500 return rewriter.notifyMatchFailure(
501 atomicOp, "failed to determine destination element type");
502
503 int dstBits = static_cast<int>(storageElemType.getIntOrFloatBitWidth());
504 assert(dstBits % srcBits == 0);
505
506 spirv::MemorySemantics memSem = getAtomicAcqRelMemorySemantics(memrefType);
507
508 // When the source and destination bitwidths match, emit the atomic operation
509 // directly.
510 if (srcBits == dstBits) {
511#define ATOMIC_CASE(kind, spirvOp) \
512 case arith::AtomicRMWKind::kind: \
513 rewriter.replaceOpWithNewOp<spirv::spirvOp>( \
514 atomicOp, resultType, ptr, *scope, memSem, adaptor.getValue()); \
515 break
516
517 switch (atomicOp.getKind()) {
518 ATOMIC_CASE(addf, EXTAtomicFAddOp);
519 ATOMIC_CASE(addi, AtomicIAddOp);
520 ATOMIC_CASE(maxs, AtomicSMaxOp);
521 ATOMIC_CASE(maxu, AtomicUMaxOp);
522 ATOMIC_CASE(mins, AtomicSMinOp);
523 ATOMIC_CASE(minu, AtomicUMinOp);
524 ATOMIC_CASE(ori, AtomicOrOp);
525 ATOMIC_CASE(andi, AtomicAndOp);
526 default:
527 return rewriter.notifyMatchFailure(atomicOp, "unimplemented atomic kind");
528 }
529
530#undef ATOMIC_CASE
531
532 return success();
533 }
534
535 // Sub-element-width atomic: the element type (e.g., i8) is narrower than the
536 // storage type (e.g., i32). We need to adjust the index and shift/mask the
537 // value to operate on the correct bits within the wider storage element.
538 //
539 // Only ori and andi can be emulated because they operate bitwise and don't
540 // carry across byte boundaries. Other kinds (addi, max, min) would require
541 // CAS loops.
542 if (atomicOp.getKind() != arith::AtomicRMWKind::ori &&
543 atomicOp.getKind() != arith::AtomicRMWKind::andi) {
544 return rewriter.notifyMatchFailure(
545 atomicOp,
546 "atomic op on sub-element-width types is only supported for ori/andi");
547 }
548
549 // Bitcasting is currently unsupported for Kernel capability /
550 // spirv.PtrAccessChain.
551 if (typeConverter.allows(spirv::Capability::Kernel))
552 return rewriter.notifyMatchFailure(
553 atomicOp,
554 "sub-element-width atomic ops unsupported with Kernel capability");
555
556 auto dstType = cast<IntegerType>(storageElemType);
557
558 auto accessChainOp = ptr.getDefiningOp<spirv::AccessChainOp>();
559 if (!accessChainOp)
560 return failure();
561
562 // Compute the bit offset within the storage element and adjust the pointer
563 // to address the containing storage element.
564 assert(accessChainOp.getIndices().size() == 2);
565 Value lastDim = accessChainOp->getOperand(accessChainOp.getNumOperands() - 1);
566 Value offset = getOffsetForBitwidth(loc, lastDim, srcBits, dstBits, rewriter);
567 Value adjustedPtr = adjustAccessChainForBitwidth(typeConverter, accessChainOp,
568 srcBits, dstBits, rewriter);
569 Value result;
570 switch (atomicOp.getKind()) {
571 case arith::AtomicRMWKind::ori: {
572 // OR only sets bits, so shifting the value to the target position and
573 // ORing with zeros in other positions preserves the unaffected bits.
574 Value elemMask = rewriter.createOrFold<spirv::ConstantOp>(
575 loc, dstType, rewriter.getIntegerAttr(dstType, (1uLL << srcBits) - 1));
576 Value storeVal =
577 shiftValue(loc, adaptor.getValue(), offset, elemMask, rewriter);
578 result = spirv::AtomicOrOp::create(rewriter, loc, dstType, adjustedPtr,
579 *scope, memSem, storeVal);
580 break;
581 }
582 case arith::AtomicRMWKind::andi: {
583 // Build a mask that preserves all bits outside the target element
584 // and applies the operand mask to the target element.
585 // mask = (operand << offset) | ~(elemMask << offset)
586 Value elemMask = rewriter.createOrFold<spirv::ConstantOp>(
587 loc, dstType, rewriter.getIntegerAttr(dstType, (1uLL << srcBits) - 1));
588 Value storeVal =
589 shiftValue(loc, adaptor.getValue(), offset, elemMask, rewriter);
590 Value shiftedElemMask = rewriter.createOrFold<spirv::ShiftLeftLogicalOp>(
591 loc, dstType, elemMask, offset);
592 Value invertedElemMask =
593 rewriter.createOrFold<spirv::NotOp>(loc, dstType, shiftedElemMask);
594 Value mask = rewriter.createOrFold<spirv::BitwiseOrOp>(loc, storeVal,
595 invertedElemMask);
596 result = spirv::AtomicAndOp::create(rewriter, loc, dstType, adjustedPtr,
597 *scope, memSem, mask);
598 break;
599 }
600 default:
601 return rewriter.notifyMatchFailure(atomicOp, "unimplemented atomic kind");
602 }
603
604 // The atomic op returns the old value of the full storage element (e.g.,
605 // i32). Extract the original sub-element value from the correct position.
606 result = rewriter.createOrFold<spirv::ShiftRightLogicalOp>(loc, dstType,
607 result, offset);
608 Value mask = rewriter.createOrFold<spirv::ConstantOp>(
609 loc, dstType, rewriter.getIntegerAttr(dstType, (1uLL << srcBits) - 1));
610 result =
611 rewriter.createOrFold<spirv::BitwiseAndOp>(loc, dstType, result, mask);
612 rewriter.replaceOp(atomicOp, result);
613
614 return success();
615}
616
617//===----------------------------------------------------------------------===//
618// DeallocOp
619//===----------------------------------------------------------------------===//
620
621LogicalResult
622DeallocOpPattern::matchAndRewrite(memref::DeallocOp operation,
623 OpAdaptor adaptor,
624 ConversionPatternRewriter &rewriter) const {
625 MemRefType deallocType = cast<MemRefType>(operation.getMemref().getType());
626 if (!isAllocationSupported(operation, deallocType))
627 return rewriter.notifyMatchFailure(operation, "unhandled allocation type");
628 rewriter.eraseOp(operation);
629 return success();
630}
631
632//===----------------------------------------------------------------------===//
633// LoadOp
634//===----------------------------------------------------------------------===//
635
637 spirv::MemoryAccessAttr memoryAccess;
638 IntegerAttr alignment;
639};
640
641/// Given an accessed SPIR-V pointer, calculates its alignment requirements, if
642/// any.
643static FailureOr<MemoryRequirements>
644calculateMemoryRequirements(Value accessedPtr, bool isNontemporal,
645 uint64_t preferredAlignment) {
646 if (preferredAlignment >= std::numeric_limits<uint32_t>::max()) {
647 return failure();
648 }
649
650 MLIRContext *ctx = accessedPtr.getContext();
651
652 auto memoryAccess = spirv::MemoryAccess::None;
653 if (isNontemporal) {
654 memoryAccess = spirv::MemoryAccess::Nontemporal;
655 }
656
657 auto ptrType = cast<spirv::PointerType>(accessedPtr.getType());
658 bool mayOmitAlignment =
659 !preferredAlignment &&
660 ptrType.getStorageClass() != spirv::StorageClass::PhysicalStorageBuffer;
661 if (mayOmitAlignment) {
662 if (memoryAccess == spirv::MemoryAccess::None) {
663 return MemoryRequirements{spirv::MemoryAccessAttr{}, IntegerAttr{}};
664 }
665 return MemoryRequirements{spirv::MemoryAccessAttr::get(ctx, memoryAccess),
666 IntegerAttr{}};
667 }
668
669 // PhysicalStorageBuffers require the `Aligned` attribute.
670 // Other storage types may show an `Aligned` attribute.
671 std::optional<int64_t> sizeInBytes;
672 Type rawPointeeType = ptrType.getPointeeType();
673 if (auto scalarType = dyn_cast<spirv::ScalarType>(rawPointeeType)) {
674 // For scalar types, the alignment is determined by their size.
675 sizeInBytes = scalarType.getSizeInBytes();
676 } else if (auto vecType = dyn_cast<VectorType>(rawPointeeType)) {
677 // For vector element types, the alignment should equal the total size of
678 // the vector.
679 if (auto scalarElem =
680 dyn_cast<spirv::ScalarType>(vecType.getElementType())) {
681 if (auto elemSize = scalarElem.getSizeInBytes())
682 sizeInBytes = *elemSize * vecType.getNumElements();
683 }
684 }
685
686 if (!sizeInBytes.has_value())
687 return failure();
688
689 memoryAccess |= spirv::MemoryAccess::Aligned;
690 auto memAccessAttr = spirv::MemoryAccessAttr::get(ctx, memoryAccess);
691 auto alignmentValue = preferredAlignment ? preferredAlignment : *sizeInBytes;
692 auto alignment = IntegerAttr::get(IntegerType::get(ctx, 32), alignmentValue);
693 return MemoryRequirements{memAccessAttr, alignment};
694}
695
696/// Given an accessed SPIR-V pointer and the original memref load/store
697/// `memAccess` op, calculates the alignment requirements, if any. Takes into
698/// account the alignment attributes applied to the load/store op.
699template <class LoadOrStoreOp>
700static FailureOr<MemoryRequirements>
701calculateMemoryRequirements(Value accessedPtr, LoadOrStoreOp loadOrStoreOp) {
702 static_assert(
703 llvm::is_one_of<LoadOrStoreOp, memref::LoadOp, memref::StoreOp>::value,
704 "Must be called on either memref::LoadOp or memref::StoreOp");
705
706 return calculateMemoryRequirements(accessedPtr,
707 loadOrStoreOp.getNontemporal(),
708 loadOrStoreOp.getAlignment().value_or(0));
709}
710
711LogicalResult
712IntLoadOpPattern::matchAndRewrite(memref::LoadOp loadOp, OpAdaptor adaptor,
713 ConversionPatternRewriter &rewriter) const {
714 auto loc = loadOp.getLoc();
715 auto memrefType = cast<MemRefType>(loadOp.getMemref().getType());
716 if (!memrefType.getElementType().isSignlessInteger())
717 return failure();
718
719 auto memorySpaceAttr =
720 dyn_cast_if_present<spirv::StorageClassAttr>(memrefType.getMemorySpace());
721 if (!memorySpaceAttr)
722 return rewriter.notifyMatchFailure(
723 loadOp, "missing memory space SPIR-V storage class attribute");
724
725 if (memorySpaceAttr.getValue() == spirv::StorageClass::Image)
726 return rewriter.notifyMatchFailure(
727 loadOp,
728 "failed to lower memref in image storage class to storage buffer");
729
730 const auto &typeConverter = *getTypeConverter<SPIRVTypeConverter>();
731 Value accessChain =
732 spirv::getElementPtr(typeConverter, memrefType, adaptor.getMemref(),
733 adaptor.getIndices(), loc, rewriter);
734
735 if (!accessChain)
736 return failure();
737
738 int srcBits = memrefType.getElementType().getIntOrFloatBitWidth();
739 bool isBool = srcBits == 1;
740 if (isBool)
741 srcBits = typeConverter.getOptions().boolNumBits;
742
743 auto pointerType = typeConverter.convertType<spirv::PointerType>(memrefType);
744 if (!pointerType)
745 return rewriter.notifyMatchFailure(loadOp, "failed to convert memref type");
746
747 Type pointeeType = pointerType.getPointeeType();
748 Type dstType = getElementTypeForStoragePointer(pointeeType, typeConverter);
749 int dstBits = dstType.getIntOrFloatBitWidth();
750 assert(dstBits % srcBits == 0);
751
752 // If the rewritten load op has the same bit width, use the loading value
753 // directly.
754 if (srcBits == dstBits) {
755 auto memoryRequirements = calculateMemoryRequirements(accessChain, loadOp);
756 if (failed(memoryRequirements))
757 return rewriter.notifyMatchFailure(
758 loadOp, "failed to determine memory requirements");
759
760 auto [memoryAccess, alignment] = *memoryRequirements;
761 Value loadVal = spirv::LoadOp::create(rewriter, loc, accessChain,
762 memoryAccess, alignment);
763 if (isBool)
764 loadVal = castIntNToBool(loc, loadVal, rewriter);
765 rewriter.replaceOp(loadOp, loadVal);
766 return success();
767 }
768
769 // Bitcasting is currently unsupported for Kernel capability /
770 // spirv.PtrAccessChain.
771 if (typeConverter.allows(spirv::Capability::Kernel))
772 return failure();
773
774 auto accessChainOp = accessChain.getDefiningOp<spirv::AccessChainOp>();
775 if (!accessChainOp)
776 return failure();
777
778 // Assume that getElementPtr() works linearizely. If it's a scalar, the method
779 // still returns a linearized accessing. If the accessing is not linearized,
780 // there will be offset issues.
781 assert(accessChainOp.getIndices().size() == 2);
782 Value adjustedPtr = adjustAccessChainForBitwidth(typeConverter, accessChainOp,
783 srcBits, dstBits, rewriter);
784 auto memoryRequirements = calculateMemoryRequirements(adjustedPtr, loadOp);
785 if (failed(memoryRequirements))
786 return rewriter.notifyMatchFailure(
787 loadOp, "failed to determine memory requirements");
788
789 auto [memoryAccess, alignment] = *memoryRequirements;
790 Value spvLoadOp = spirv::LoadOp::create(rewriter, loc, dstType, adjustedPtr,
791 memoryAccess, alignment);
792
793 // Shift the bits to the rightmost.
794 // ____XXXX________ -> ____________XXXX
795 Value lastDim = accessChainOp->getOperand(accessChainOp.getNumOperands() - 1);
796 Value offset = getOffsetForBitwidth(loc, lastDim, srcBits, dstBits, rewriter);
797 Value result = rewriter.createOrFold<spirv::ShiftRightArithmeticOp>(
798 loc, spvLoadOp.getType(), spvLoadOp, offset);
799
800 // Apply the mask to extract corresponding bits.
801 Value mask = rewriter.createOrFold<spirv::ConstantOp>(
802 loc, dstType, rewriter.getIntegerAttr(dstType, (1 << srcBits) - 1));
803 result =
804 rewriter.createOrFold<spirv::BitwiseAndOp>(loc, dstType, result, mask);
805
806 // Apply sign extension on the loading value unconditionally. The signedness
807 // semantic is carried in the operator itself, we relies other pattern to
808 // handle the casting.
809 IntegerAttr shiftValueAttr =
810 rewriter.getIntegerAttr(dstType, dstBits - srcBits);
811 Value shiftValue =
812 rewriter.createOrFold<spirv::ConstantOp>(loc, dstType, shiftValueAttr);
813 result = rewriter.createOrFold<spirv::ShiftLeftLogicalOp>(loc, dstType,
815 result = rewriter.createOrFold<spirv::ShiftRightArithmeticOp>(
816 loc, dstType, result, shiftValue);
817
818 rewriter.replaceOp(loadOp, result);
819
820 assert(accessChainOp.use_empty());
821 rewriter.eraseOp(accessChainOp);
822
823 return success();
824}
825
826LogicalResult
827LoadOpPattern::matchAndRewrite(memref::LoadOp loadOp, OpAdaptor adaptor,
828 ConversionPatternRewriter &rewriter) const {
829 auto memrefType = cast<MemRefType>(loadOp.getMemref().getType());
830 if (memrefType.getElementType().isSignlessInteger())
831 return failure();
832
833 auto memorySpaceAttr =
834 dyn_cast_if_present<spirv::StorageClassAttr>(memrefType.getMemorySpace());
835 if (!memorySpaceAttr)
836 return rewriter.notifyMatchFailure(
837 loadOp, "missing memory space SPIR-V storage class attribute");
838
839 if (memorySpaceAttr.getValue() == spirv::StorageClass::Image)
840 return rewriter.notifyMatchFailure(
841 loadOp,
842 "failed to lower memref in image storage class to storage buffer");
843
844 Value loadPtr = spirv::getElementPtr(
845 *getTypeConverter<SPIRVTypeConverter>(), memrefType, adaptor.getMemref(),
846 adaptor.getIndices(), loadOp.getLoc(), rewriter);
847
848 if (!loadPtr)
849 return failure();
850
851 auto memoryRequirements = calculateMemoryRequirements(loadPtr, loadOp);
852 if (failed(memoryRequirements))
853 return rewriter.notifyMatchFailure(
854 loadOp, "failed to determine memory requirements");
855
856 auto [memoryAccess, alignment] = *memoryRequirements;
857 rewriter.replaceOpWithNewOp<spirv::LoadOp>(loadOp, loadPtr, memoryAccess,
858 alignment);
859 return success();
860}
861
862template <typename OpAdaptor>
863static FailureOr<SmallVector<Value>>
864extractLoadCoordsForComposite(memref::LoadOp loadOp, OpAdaptor adaptor,
865 ConversionPatternRewriter &rewriter) {
866 // At present we only support linear "tiling" as specified in Vulkan, this
867 // means that texels are assumed to be laid out in memory in a row-major
868 // order. This allows us to support any memref layout that is a permutation of
869 // the dimensions. Future work will pass an optional image layout to the
870 // rewrite pattern so that we can support optimized target specific tilings.
871 SmallVector<Value> indices = adaptor.getIndices();
872 AffineMap map = loadOp.getMemRefType().getLayout().getAffineMap();
873 if (!map.isPermutation())
874 return rewriter.notifyMatchFailure(
875 loadOp,
876 "Cannot lower memrefs with memory layout which is not a permutation");
877
878 // The memrefs layout determines the dimension ordering so we need to follow
879 // the map to get the ordering of the dimensions/indices.
880 const unsigned dimCount = map.getNumDims();
881 SmallVector<Value, 3> coords(dimCount);
882 for (unsigned dim = 0; dim < dimCount; ++dim)
883 coords[map.getDimPosition(dim)] = indices[dim];
884
885 // We need to reverse the coordinates because the memref layout is slowest to
886 // fastest moving and the vector coordinates for the image op is fastest to
887 // slowest moving.
888 return llvm::to_vector(llvm::reverse(coords));
889}
890
891LogicalResult
892ImageLoadOpPattern::matchAndRewrite(memref::LoadOp loadOp, OpAdaptor adaptor,
893 ConversionPatternRewriter &rewriter) const {
894 auto memrefType = cast<MemRefType>(loadOp.getMemref().getType());
895
896 auto memorySpaceAttr =
897 dyn_cast_if_present<spirv::StorageClassAttr>(memrefType.getMemorySpace());
898 if (!memorySpaceAttr)
899 return rewriter.notifyMatchFailure(
900 loadOp, "missing memory space SPIR-V storage class attribute");
901
902 if (memorySpaceAttr.getValue() != spirv::StorageClass::Image)
903 return rewriter.notifyMatchFailure(
904 loadOp, "failed to lower memref in non-image storage class to image");
905
906 Value loadPtr = adaptor.getMemref();
907 auto memoryRequirements = calculateMemoryRequirements(loadPtr, loadOp);
908 if (failed(memoryRequirements))
909 return rewriter.notifyMatchFailure(
910 loadOp, "failed to determine memory requirements");
911
912 const auto [memoryAccess, alignment] = *memoryRequirements;
913
914 if (!loadOp.getMemRefType().hasRank())
915 return rewriter.notifyMatchFailure(
916 loadOp, "cannot lower unranked memrefs to SPIR-V images");
917
918 // We currently only support lowering of scalar memref elements to texels in
919 // the R[16|32][f|i|ui] formats. Future work will enable lowering of vector
920 // elements to texels in richer formats.
921 if (!isa<spirv::ScalarType>(loadOp.getMemRefType().getElementType()))
922 return rewriter.notifyMatchFailure(
923 loadOp,
924 "cannot lower memrefs who's element type is not a SPIR-V scalar type"
925 "to SPIR-V images");
926
927 // We currently only support sampled images since OpImageFetch does not work
928 // for plain images and the OpImageRead instruction needs to be materialized
929 // instead or texels need to be accessed via atomics through a texel pointer.
930 // Future work will generalize support to plain images.
931 auto convertedPointeeType = cast<spirv::PointerType>(
932 getTypeConverter()->convertType(loadOp.getMemRefType()));
933 if (!isa<spirv::SampledImageType>(convertedPointeeType.getPointeeType()))
934 return rewriter.notifyMatchFailure(loadOp,
935 "cannot lower memrefs which do not "
936 "convert to SPIR-V sampled images");
937
938 // Materialize the lowering.
939 Location loc = loadOp->getLoc();
940 auto imageLoadOp =
941 spirv::LoadOp::create(rewriter, loc, loadPtr, memoryAccess, alignment);
942 // Extract the image from the sampled image.
943 auto imageOp = spirv::ImageOp::create(rewriter, loc, imageLoadOp);
944
945 // Build a vector of coordinates or just a scalar index if we have a 1D image.
946 Value coords;
947 if (memrefType.getRank() == 1) {
948 coords = adaptor.getIndices()[0];
949 } else {
950 FailureOr<SmallVector<Value>> maybeCoords =
951 extractLoadCoordsForComposite(loadOp, adaptor, rewriter);
952 if (failed(maybeCoords))
953 return failure();
954 auto coordVectorType = VectorType::get({loadOp.getMemRefType().getRank()},
955 adaptor.getIndices().getType()[0]);
956 coords = spirv::CompositeConstructOp::create(rewriter, loc, coordVectorType,
957 maybeCoords.value());
958 }
959
960 // Fetch the value out of the image.
961 auto resultVectorType = VectorType::get({4}, loadOp.getType());
962 auto fetchOp = spirv::ImageFetchOp::create(
963 rewriter, loc, resultVectorType, imageOp, coords,
964 mlir::spirv::ImageOperandsAttr{}, ValueRange{});
965
966 // Note that because OpImageFetch returns a rank 4 vector we need to extract
967 // the elements corresponding to the load which will since we only support the
968 // R[16|32][f|i|ui] formats will always be the R(red) 0th vector element.
969 auto compositeExtractOp =
970 spirv::CompositeExtractOp::create(rewriter, loc, fetchOp, 0);
971
972 rewriter.replaceOp(loadOp, compositeExtractOp);
973 return success();
974}
975
976LogicalResult
977IntStoreOpPattern::matchAndRewrite(memref::StoreOp storeOp, OpAdaptor adaptor,
978 ConversionPatternRewriter &rewriter) const {
979 auto memrefType = cast<MemRefType>(storeOp.getMemref().getType());
980 if (!memrefType.getElementType().isSignlessInteger())
981 return rewriter.notifyMatchFailure(storeOp,
982 "element type is not a signless int");
983
984 auto loc = storeOp.getLoc();
985 auto &typeConverter = *getTypeConverter<SPIRVTypeConverter>();
986 Value accessChain =
987 spirv::getElementPtr(typeConverter, memrefType, adaptor.getMemref(),
988 adaptor.getIndices(), loc, rewriter);
989
990 if (!accessChain)
991 return rewriter.notifyMatchFailure(
992 storeOp, "failed to convert element pointer type");
993
994 int srcBits = memrefType.getElementType().getIntOrFloatBitWidth();
995
996 bool isBool = srcBits == 1;
997 if (isBool)
998 srcBits = typeConverter.getOptions().boolNumBits;
999
1000 auto pointerType = typeConverter.convertType<spirv::PointerType>(memrefType);
1001 if (!pointerType)
1002 return rewriter.notifyMatchFailure(storeOp,
1003 "failed to convert memref type");
1004
1005 Type pointeeType = pointerType.getPointeeType();
1006 auto dstType = dyn_cast<IntegerType>(
1007 getElementTypeForStoragePointer(pointeeType, typeConverter));
1008 if (!dstType)
1009 return rewriter.notifyMatchFailure(
1010 storeOp, "failed to determine destination element type");
1011
1012 int dstBits = static_cast<int>(dstType.getWidth());
1013 assert(dstBits % srcBits == 0);
1014
1015 if (srcBits == dstBits) {
1016 auto memoryRequirements = calculateMemoryRequirements(accessChain, storeOp);
1017 if (failed(memoryRequirements))
1018 return rewriter.notifyMatchFailure(
1019 storeOp, "failed to determine memory requirements");
1020
1021 auto [memoryAccess, alignment] = *memoryRequirements;
1022 Value storeVal = adaptor.getValue();
1023 if (isBool)
1024 storeVal = castBoolToIntN(loc, storeVal, dstType, rewriter);
1025 rewriter.replaceOpWithNewOp<spirv::StoreOp>(storeOp, accessChain, storeVal,
1026 memoryAccess, alignment);
1027 return success();
1028 }
1029
1030 // Bitcasting is currently unsupported for Kernel capability /
1031 // spirv.PtrAccessChain.
1032 if (typeConverter.allows(spirv::Capability::Kernel))
1033 return failure();
1034
1035 auto accessChainOp = accessChain.getDefiningOp<spirv::AccessChainOp>();
1036 if (!accessChainOp)
1037 return failure();
1038
1039 // Since there are multiple threads in the processing, the emulation will be
1040 // done with atomic operations. E.g., if the stored value is i8, rewrite the
1041 // StoreOp to:
1042 // 1) load a 32-bit integer
1043 // 2) clear 8 bits in the loaded value
1044 // 3) set 8 bits in the loaded value
1045 // 4) store 32-bit value back
1046 //
1047 // Step 2 is done with AtomicAnd, and step 3 is done with AtomicOr (of the
1048 // loaded 32-bit value and the shifted 8-bit store value) as another atomic
1049 // step.
1050 assert(accessChainOp.getIndices().size() == 2);
1051 Value lastDim = accessChainOp->getOperand(accessChainOp.getNumOperands() - 1);
1052 Value offset = getOffsetForBitwidth(loc, lastDim, srcBits, dstBits, rewriter);
1053
1054 // Create a mask to clear the destination. E.g., if it is the second i8 in
1055 // i32, 0xFFFF00FF is created.
1056 Value mask = rewriter.createOrFold<spirv::ConstantOp>(
1057 loc, dstType, rewriter.getIntegerAttr(dstType, (1 << srcBits) - 1));
1058 Value clearBitsMask = rewriter.createOrFold<spirv::ShiftLeftLogicalOp>(
1059 loc, dstType, mask, offset);
1060 clearBitsMask =
1061 rewriter.createOrFold<spirv::NotOp>(loc, dstType, clearBitsMask);
1062
1063 Value storeVal = shiftValue(loc, adaptor.getValue(), offset, mask, rewriter);
1064 Value adjustedPtr = adjustAccessChainForBitwidth(typeConverter, accessChainOp,
1065 srcBits, dstBits, rewriter);
1066 std::optional<spirv::Scope> scope = getAtomicOpScope(memrefType);
1067 if (!scope)
1068 return rewriter.notifyMatchFailure(storeOp, "atomic scope not available");
1069
1070 spirv::MemorySemantics memSem = getAtomicAcqRelMemorySemantics(memrefType);
1071 Value result = spirv::AtomicAndOp::create(rewriter, loc, dstType, adjustedPtr,
1072 *scope, memSem, clearBitsMask);
1073 result = spirv::AtomicOrOp::create(rewriter, loc, dstType, adjustedPtr,
1074 *scope, memSem, storeVal);
1075
1076 // The AtomicOrOp has no side effect. Since it is already inserted, we can
1077 // just remove the original StoreOp. Note that rewriter.replaceOp()
1078 // doesn't work because it only accepts that the numbers of result are the
1079 // same.
1080 rewriter.eraseOp(storeOp);
1081
1082 assert(accessChainOp.use_empty());
1083 rewriter.eraseOp(accessChainOp);
1084
1085 return success();
1086}
1087
1088//===----------------------------------------------------------------------===//
1089// MemorySpaceCastOp
1090//===----------------------------------------------------------------------===//
1091
1092LogicalResult MemorySpaceCastOpPattern::matchAndRewrite(
1093 memref::MemorySpaceCastOp addrCastOp, OpAdaptor adaptor,
1094 ConversionPatternRewriter &rewriter) const {
1095 Location loc = addrCastOp.getLoc();
1096 auto &typeConverter = *getTypeConverter<SPIRVTypeConverter>();
1097 if (!typeConverter.allows(spirv::Capability::Kernel))
1098 return rewriter.notifyMatchFailure(
1099 loc, "address space casts require kernel capability");
1100
1101 auto sourceType = dyn_cast<MemRefType>(addrCastOp.getSource().getType());
1102 if (!sourceType)
1103 return rewriter.notifyMatchFailure(
1104 loc, "SPIR-V lowering requires ranked memref types");
1105 auto resultType = cast<MemRefType>(addrCastOp.getResult().getType());
1106
1107 auto sourceStorageClassAttr =
1108 dyn_cast_or_null<spirv::StorageClassAttr>(sourceType.getMemorySpace());
1109 if (!sourceStorageClassAttr)
1110 return rewriter.notifyMatchFailure(loc, [sourceType](Diagnostic &diag) {
1111 diag << "source address space " << sourceType.getMemorySpace()
1112 << " must be a SPIR-V storage class";
1113 });
1114 auto resultStorageClassAttr =
1115 dyn_cast_or_null<spirv::StorageClassAttr>(resultType.getMemorySpace());
1116 if (!resultStorageClassAttr)
1117 return rewriter.notifyMatchFailure(loc, [resultType](Diagnostic &diag) {
1118 diag << "result address space " << resultType.getMemorySpace()
1119 << " must be a SPIR-V storage class";
1120 });
1121
1122 spirv::StorageClass sourceSc = sourceStorageClassAttr.getValue();
1123 spirv::StorageClass resultSc = resultStorageClassAttr.getValue();
1124
1125 Value result = adaptor.getSource();
1126 Type resultPtrType = typeConverter.convertType(resultType);
1127 if (!resultPtrType)
1128 return rewriter.notifyMatchFailure(addrCastOp,
1129 "failed to convert memref type");
1130
1131 Type genericPtrType = resultPtrType;
1132 // SPIR-V doesn't have a general address space cast operation. Instead, it has
1133 // conversions to and from generic pointers. To implement the general case,
1134 // we use specific-to-generic conversions when the source class is not
1135 // generic. Then when the result storage class is not generic, we convert the
1136 // generic pointer (either the input on ar intermediate result) to that
1137 // class. This also means that we'll need the intermediate generic pointer
1138 // type if neither the source or destination have it.
1139 if (sourceSc != spirv::StorageClass::Generic &&
1140 resultSc != spirv::StorageClass::Generic) {
1141 Type intermediateType =
1142 MemRefType::get(sourceType.getShape(), sourceType.getElementType(),
1143 sourceType.getLayout(),
1144 rewriter.getAttr<spirv::StorageClassAttr>(
1145 spirv::StorageClass::Generic));
1146 genericPtrType = typeConverter.convertType(intermediateType);
1147 }
1148 if (sourceSc != spirv::StorageClass::Generic) {
1149 result = spirv::PtrCastToGenericOp::create(rewriter, loc, genericPtrType,
1150 result);
1151 }
1152 if (resultSc != spirv::StorageClass::Generic) {
1153 result =
1154 spirv::GenericCastToPtrOp::create(rewriter, loc, resultPtrType, result);
1155 }
1156 rewriter.replaceOp(addrCastOp, result);
1157 return success();
1158}
1159
1160LogicalResult
1161StoreOpPattern::matchAndRewrite(memref::StoreOp storeOp, OpAdaptor adaptor,
1162 ConversionPatternRewriter &rewriter) const {
1163 auto memrefType = cast<MemRefType>(storeOp.getMemref().getType());
1164 if (memrefType.getElementType().isSignlessInteger())
1165 return rewriter.notifyMatchFailure(storeOp, "signless int");
1166 auto storePtr = spirv::getElementPtr(
1167 *getTypeConverter<SPIRVTypeConverter>(), memrefType, adaptor.getMemref(),
1168 adaptor.getIndices(), storeOp.getLoc(), rewriter);
1169
1170 if (!storePtr)
1171 return rewriter.notifyMatchFailure(storeOp, "type conversion failed");
1172
1173 auto memoryRequirements = calculateMemoryRequirements(storePtr, storeOp);
1174 if (failed(memoryRequirements))
1175 return rewriter.notifyMatchFailure(
1176 storeOp, "failed to determine memory requirements");
1177
1178 auto [memoryAccess, alignment] = *memoryRequirements;
1179 rewriter.replaceOpWithNewOp<spirv::StoreOp>(
1180 storeOp, storePtr, adaptor.getValue(), memoryAccess, alignment);
1181 return success();
1182}
1183
1184//===----------------------------------------------------------------------===//
1185// CopyOp
1186//===----------------------------------------------------------------------===//
1187
1188LogicalResult
1189CopyOpPattern::matchAndRewrite(memref::CopyOp copyOp, OpAdaptor adaptor,
1190 ConversionPatternRewriter &rewriter) const {
1191 auto memrefType = cast<MemRefType>(copyOp.getSource().getType());
1192 if (!memrefType.hasStaticShape())
1193 return rewriter.notifyMatchFailure(copyOp, "unsupported dynamic shape");
1194
1195 for (MemRefType type :
1196 {memrefType, cast<MemRefType>(copyOp.getTarget().getType())}) {
1197 auto memorySpaceAttr =
1198 dyn_cast_if_present<spirv::StorageClassAttr>(type.getMemorySpace());
1199 if (memorySpaceAttr &&
1200 memorySpaceAttr.getValue() == spirv::StorageClass::Image)
1201 return rewriter.notifyMatchFailure(
1202 copyOp, "cannot lower memref.copy in image storage class");
1203 }
1204
1205 // The converted operands are SPIR-V pointers to the source and target
1206 // storage. spirv.CopyMemory copies the whole pointed-to object, so it only
1207 // applies when both pointers point to the same fixed-size element type.
1208 Value source = adaptor.getSource();
1209 Value target = adaptor.getTarget();
1210 auto sourcePtrType = dyn_cast<spirv::PointerType>(source.getType());
1211 auto targetPtrType = dyn_cast<spirv::PointerType>(target.getType());
1212 if (!sourcePtrType || !targetPtrType)
1213 return rewriter.notifyMatchFailure(copyOp, "failed to convert memref type");
1214
1215 if (sourcePtrType.getPointeeType() != targetPtrType.getPointeeType())
1216 return rewriter.notifyMatchFailure(
1217 copyOp, "source and target pointee types do not match");
1218
1219 rewriter.replaceOpWithNewOp<spirv::CopyMemoryOp>(
1220 copyOp, target, source, /*memory_access=*/spirv::MemoryAccessAttr{},
1221 /*alignment=*/IntegerAttr{}, /*source_memory_access=*/
1222 spirv::MemoryAccessAttr{}, /*source_alignment=*/IntegerAttr{});
1223 return success();
1224}
1225
1226LogicalResult ReinterpretCastPattern::matchAndRewrite(
1227 memref::ReinterpretCastOp op, OpAdaptor adaptor,
1228 ConversionPatternRewriter &rewriter) const {
1229 Value src = adaptor.getSource();
1230 auto srcType = dyn_cast<spirv::PointerType>(src.getType());
1231
1232 if (!srcType)
1233 return rewriter.notifyMatchFailure(op, [&](Diagnostic &diag) {
1234 diag << "invalid src type " << src.getType();
1235 });
1236
1237 const TypeConverter *converter = getTypeConverter();
1238
1239 auto dstType = converter->convertType<spirv::PointerType>(op.getType());
1240 if (dstType != srcType)
1241 return rewriter.notifyMatchFailure(op, [&](Diagnostic &diag) {
1242 diag << "invalid dst type " << op.getType();
1243 });
1244
1245 OpFoldResult offset =
1246 getMixedValues(adaptor.getStaticOffsets(), adaptor.getOffsets(), rewriter)
1247 .front();
1248 if (isZeroInteger(offset)) {
1249 rewriter.replaceOp(op, src);
1250 return success();
1251 }
1252
1253 Type intType = converter->convertType(rewriter.getIndexType());
1254 if (!intType)
1255 return rewriter.notifyMatchFailure(op, "failed to convert index type");
1256
1257 Location loc = op.getLoc();
1258 auto offsetValue = [&]() -> Value {
1259 if (auto val = dyn_cast<Value>(offset))
1260 return val;
1261
1262 int64_t attrVal = cast<IntegerAttr>(cast<Attribute>(offset)).getInt();
1263 Attribute attr = rewriter.getIntegerAttr(intType, attrVal);
1264 return rewriter.createOrFold<spirv::ConstantOp>(loc, intType, attr);
1265 }();
1266
1267 rewriter.replaceOpWithNewOp<spirv::InBoundsPtrAccessChainOp>(
1268 op, src, offsetValue, ValueRange());
1269 return success();
1270}
1271
1272//===----------------------------------------------------------------------===//
1273// ExtractAlignedPointerAsIndexOp
1274//===----------------------------------------------------------------------===//
1275
1276LogicalResult ExtractAlignedPointerAsIndexOpPattern::matchAndRewrite(
1277 memref::ExtractAlignedPointerAsIndexOp extractOp, OpAdaptor adaptor,
1278 ConversionPatternRewriter &rewriter) const {
1279 auto &typeConverter = *getTypeConverter<SPIRVTypeConverter>();
1280 Type indexType = typeConverter.getIndexType();
1281 rewriter.replaceOpWithNewOp<spirv::ConvertPtrToUOp>(extractOp, indexType,
1282 adaptor.getSource());
1283 return success();
1284}
1285
1286//===----------------------------------------------------------------------===//
1287// Pattern population
1288//===----------------------------------------------------------------------===//
1289
1290namespace mlir {
1292 RewritePatternSet &patterns) {
1293 patterns.add<AllocaOpPattern, AllocOpPattern, AtomicRMWOpPattern,
1294 CopyOpPattern, DeallocOpPattern, IntLoadOpPattern,
1295 ImageLoadOpPattern, IntStoreOpPattern, LoadOpPattern,
1296 MemorySpaceCastOpPattern, StoreOpPattern, ReinterpretCastPattern,
1297 CastPattern, ExtractAlignedPointerAsIndexOpPattern>(
1298 typeConverter, patterns.getContext());
1299}
1300} // namespace mlir
return success()
static spirv::MemorySemantics getMemorySemanticsForStorageClass(spirv::StorageClass sc)
Returns the MemorySemantics storage-class bit corresponding to sc.
static Value castIntNToBool(Location loc, Value srcInt, OpBuilder &builder)
Casts the given srcInt into a boolean value.
static Type getElementTypeForStoragePointer(Type pointeeType, const SPIRVTypeConverter &typeConverter)
Extracts the element type from a SPIR-V pointer type pointing to storage.
static std::optional< spirv::Scope > getAtomicOpScope(MemRefType type)
Returns the scope to use for atomic operations use for emulating store operations of unsupported inte...
static Value shiftValue(Location loc, Value value, Value offset, Value mask, OpBuilder &builder)
Returns the targetBits-bit value shifted by the given offset, and cast to the type destination type,...
static FailureOr< SmallVector< Value > > extractLoadCoordsForComposite(memref::LoadOp loadOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter)
static Value adjustAccessChainForBitwidth(const SPIRVTypeConverter &typeConverter, spirv::AccessChainOp op, int sourceBits, int targetBits, OpBuilder &builder)
Returns an adjusted spirv::AccessChainOp.
static bool isAllocationSupported(Operation *allocOp, MemRefType type)
Returns true if the allocations of memref type generated from allocOp can be lowered to SPIR-V.
static Value getOffsetForBitwidth(Location loc, Value srcIdx, int sourceBits, int targetBits, OpBuilder &builder)
Returns the offset of the value in targetBits representation.
static spirv::MemorySemantics getAtomicAcqRelMemorySemantics(MemRefType type)
Returns the AcquireRelease memory semantics OR'd with the storage-class bit derived from the memory s...
#define ATOMIC_CASE(kind, spirvOp)
static FailureOr< MemoryRequirements > calculateMemoryRequirements(Value accessedPtr, bool isNontemporal, uint64_t preferredAlignment)
Given an accessed SPIR-V pointer, calculates its alignment requirements, if any.
static Value castBoolToIntN(Location loc, Value srcBool, Type dstType, OpBuilder &builder)
Casts the given srcBool into an integer of dstType.
static std::string diag(const llvm::Value &value)
A multi-dimensional affine map Affine map's are immutable like Type's, and they are uniqued.
Definition AffineMap.h:46
unsigned getDimPosition(unsigned idx) const
Extracts the position of the dimensional expression at the given result, when the caller knows it is ...
unsigned getNumDims() const
bool isPermutation() const
Returns true if the AffineMap represents a symbol-less permutation map.
iterator_range< op_iterator< OpT > > getOps()
Return an iterator range over the operations within this block that are of 'OpT'.
Definition Block.h:217
IntegerAttr getIntegerAttr(Type type, int64_t value)
Definition Builders.cpp:237
IntegerType getIntegerType(unsigned width)
Definition Builders.cpp:75
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
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 createOrFold(SmallVectorImpl< Value > &results, Location location, Args &&...args)
Create an operation of specific op type at the current insertion point, and immediately try to fold i...
Definition Builders.h:529
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
Region & getRegion(unsigned index)
Returns the region held by this operation at position 'index'.
Definition Operation.h:711
iterator begin()
Definition Region.h:55
MLIRContext * getContext() const
RewritePatternSet & add(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
Type conversion from builtin types to SPIR-V types for shader interface.
bool allows(spirv::Capability capability) const
Checks if the SPIR-V capability inquired is supported.
static Operation * getNearestSymbolTable(Operation *from)
Returns the nearest symbol table from a given operation from.
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
bool isInteger() const
Return true if this is an integer type (with the specified width).
Definition Types.cpp:58
bool isIntOrFloat() const
Return true if this is an integer (of any signedness) or a float type.
Definition Types.cpp:118
unsigned getIntOrFloatBitWidth() const
Return the bit width of an integer or a float type, assert failure on other types.
Definition Types.cpp:124
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
MLIRContext * getContext() const
Utility to get the associated MLIRContext that this value is defined in.
Definition Value.h:108
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
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:717
Value getElementPtr(const SPIRVTypeConverter &typeConverter, MemRefType baseType, Value basePtr, ValueRange indices, Location loc, OpBuilder &builder)
Performs the index computation to get to the element at indices of the memory pointed to by basePtr,...
Include the generated interface declarations.
SmallVector< OpFoldResult > getMixedValues(ArrayRef< int64_t > staticValues, ValueRange dynamicValues, MLIRContext *context)
Return a vector of OpFoldResults with the same size a staticValues, but all elements for which Shaped...
Type getType(OpFoldResult ofr)
Returns the int type of the integer in ofr.
Definition Utils.cpp:307
bool isZeroInteger(OpFoldResult v)
Return "true" if v is an integer value/attribute with constant value 0.
void populateMemRefToSPIRVPatterns(const SPIRVTypeConverter &typeConverter, RewritePatternSet &patterns)
Appends to a pattern list additional patterns for translating MemRef ops to SPIR-V ops.
spirv::MemoryAccessAttr memoryAccess