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