MLIR 24.0.0git
SPIRVConversion.cpp
Go to the documentation of this file.
1//===- SPIRVConversion.cpp - SPIR-V Conversion Utilities ------------------===//
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 utilities used to lower to SPIR-V dialect.
10//
11//===----------------------------------------------------------------------===//
12
26#include "mlir/IR/Operation.h"
28#include "mlir/Support/LLVM.h"
31#include "llvm/ADT/APInt.h"
32#include "llvm/ADT/STLExtras.h"
33#include "llvm/ADT/SmallVector.h"
34#include "llvm/ADT/StringExtras.h"
35#include "llvm/Support/CheckedArithmetic.h"
36#include "llvm/Support/Debug.h"
37#include "llvm/Support/MathExtras.h"
38
39#include <optional>
40
41#define DEBUG_TYPE "mlir-spirv-conversion"
42
43using namespace mlir;
44
45namespace {
46
47//===----------------------------------------------------------------------===//
48// Utility functions
49//===----------------------------------------------------------------------===//
50
51static std::optional<SmallVector<int64_t>> getTargetShape(VectorType vecType) {
52 LLVM_DEBUG(llvm::dbgs() << "Get target shape\n");
53 if (vecType.isScalable()) {
54 LLVM_DEBUG(llvm::dbgs()
55 << "--scalable vectors are not supported -> BAIL\n");
56 return std::nullopt;
57 }
58 if (vecType.getRank() == 0) {
59 LLVM_DEBUG(llvm::dbgs() << "--0-D vectors are not supported -> BAIL\n");
60 return std::nullopt;
61 }
62 SmallVector<int64_t> unrollShape = llvm::to_vector<4>(vecType.getShape());
63 std::optional<SmallVector<int64_t>> targetShape = SmallVector<int64_t>(
64 1, mlir::spirv::getComputeVectorSize(vecType.getShape().back()));
65 if (!targetShape) {
66 LLVM_DEBUG(llvm::dbgs() << "--no unrolling target shape defined\n");
67 return std::nullopt;
68 }
69 auto maybeShapeRatio = computeShapeRatio(unrollShape, *targetShape);
70 if (!maybeShapeRatio) {
71 LLVM_DEBUG(llvm::dbgs()
72 << "--could not compute integral shape ratio -> BAIL\n");
73 return std::nullopt;
74 }
75 if (llvm::all_of(*maybeShapeRatio, [](int64_t v) { return v == 1; })) {
76 LLVM_DEBUG(llvm::dbgs() << "--no unrolling needed -> SKIP\n");
77 return std::nullopt;
78 }
79 LLVM_DEBUG(llvm::dbgs()
80 << "--found an integral shape ratio to unroll to -> SUCCESS\n");
81 return targetShape;
82}
83
84/// Checks that `candidates` extension requirements are possible to be satisfied
85/// with the given `targetEnv`.
86///
87/// `candidates` is a vector of vector for extension requirements following
88/// ((Extension::A OR Extension::B) AND (Extension::C OR Extension::D))
89/// convention.
90template <typename LabelT>
91static LogicalResult checkExtensionRequirements(
92 LabelT label, const spirv::TargetEnv &targetEnv,
94 for (const auto &ors : candidates) {
95 if (targetEnv.allows(ors))
96 continue;
97
98 LLVM_DEBUG({
99 SmallVector<StringRef> extStrings;
100 for (spirv::Extension ext : ors)
101 extStrings.push_back(spirv::stringifyExtension(ext));
102
103 llvm::dbgs() << label << " illegal: requires at least one extension in ["
104 << llvm::join(extStrings, ", ")
105 << "] but none allowed in target environment\n";
106 });
107 return failure();
108 }
109 return success();
110}
111
112/// Checks that `candidates`capability requirements are possible to be satisfied
113/// with the given `isAllowedFn`.
114///
115/// `candidates` is a vector of vector for capability requirements following
116/// ((Capability::A OR Capability::B) AND (Capability::C OR Capability::D))
117/// convention.
118template <typename LabelT>
119static LogicalResult checkCapabilityRequirements(
120 LabelT label, const spirv::TargetEnv &targetEnv,
122 for (const auto &ors : candidates) {
123 if (targetEnv.allows(ors))
124 continue;
125
126 LLVM_DEBUG({
127 SmallVector<StringRef> capStrings;
128 for (spirv::Capability cap : ors)
129 capStrings.push_back(spirv::stringifyCapability(cap));
130
131 llvm::dbgs() << label << " illegal: requires at least one capability in ["
132 << llvm::join(capStrings, ", ")
133 << "] but none allowed in target environment\n";
134 });
135 return failure();
136 }
137 return success();
138}
139
140/// Returns true if the given `storageClass` needs explicit layout when used in
141/// Shader environments.
142static bool needsExplicitLayout(spirv::StorageClass storageClass) {
143 switch (storageClass) {
144 case spirv::StorageClass::PhysicalStorageBuffer:
145 case spirv::StorageClass::PushConstant:
146 case spirv::StorageClass::StorageBuffer:
147 case spirv::StorageClass::Uniform:
148 return true;
149 default:
150 return false;
151 }
152}
153
154/// Wraps the given `elementType` in a struct and gets the pointer to the
155/// struct. This is used to satisfy Vulkan interface requirements.
157wrapInStructAndGetPointer(Type elementType, spirv::StorageClass storageClass) {
158 auto structType = needsExplicitLayout(storageClass)
159 ? spirv::StructType::get(elementType, /*offsetInfo=*/0)
160 : spirv::StructType::get(elementType);
161 return spirv::PointerType::get(structType, storageClass);
162}
163
164//===----------------------------------------------------------------------===//
165// Type Conversion
166//===----------------------------------------------------------------------===//
167
168static spirv::ScalarType getIndexType(MLIRContext *ctx,
170 return cast<spirv::ScalarType>(
171 IntegerType::get(ctx, options.use64bitIndex ? 64 : 32));
172}
173
174// TODO: This is a utility function that should probably be exposed by the
175// SPIR-V dialect. Keeping it local till the use case arises.
176static std::optional<int64_t>
177getTypeNumBytes(const SPIRVConversionOptions &options, Type type) {
178 if (isa<spirv::ScalarType>(type)) {
179 auto bitWidth = type.getIntOrFloatBitWidth();
180 // According to the SPIR-V spec:
181 // "There is no physical size or bit pattern defined for values with boolean
182 // type. If they are stored (in conjunction with OpVariable), they can only
183 // be used with logical addressing operations, not physical, and only with
184 // non-externally visible shader Storage Classes: Workgroup, CrossWorkgroup,
185 // Private, Function, Input, and Output."
186 if (bitWidth == 1)
187 return std::nullopt;
188 return bitWidth / 8;
189 }
190
191 // Handle 8-bit floats.
192 if (options.emulateUnsupportedFloatTypes && isa<FloatType>(type)) {
193 auto bitWidth = type.getIntOrFloatBitWidth();
194 if (bitWidth == 8)
195 return bitWidth / 8;
196 return std::nullopt;
197 }
198
199 if (auto complexType = dyn_cast<ComplexType>(type)) {
200 auto elementSize = getTypeNumBytes(options, complexType.getElementType());
201 if (!elementSize)
202 return std::nullopt;
203 return 2 * *elementSize;
204 }
205
206 if (auto vecType = dyn_cast<VectorType>(type)) {
207 auto elementSize = getTypeNumBytes(options, vecType.getElementType());
208 if (!elementSize)
209 return std::nullopt;
210 return vecType.getNumElements() * *elementSize;
211 }
212
213 if (auto memRefType = dyn_cast<MemRefType>(type)) {
214 // TODO: Layout should also be controlled by the ABI attributes. For now
215 // using the layout from MemRef.
216 int64_t offset;
218 if (!memRefType.hasStaticShape() ||
219 failed(memRefType.getStridesAndOffset(strides, offset)))
220 return std::nullopt;
221
222 // To get the size of the memref object in memory, the total size is the
223 // max(stride * dimension-size) computed for all dimensions times the size
224 // of the element.
225 auto elementSize = getTypeNumBytes(options, memRefType.getElementType());
226 if (!elementSize)
227 return std::nullopt;
228
229 if (memRefType.getRank() == 0)
230 return elementSize;
231
232 auto dims = memRefType.getShape();
233 if (llvm::is_contained(dims, ShapedType::kDynamic) ||
234 ShapedType::isDynamic(offset) ||
235 llvm::is_contained(strides, ShapedType::kDynamic))
236 return std::nullopt;
237
238 int64_t memrefSize = -1;
239 for (const auto &shape : enumerate(dims))
240 memrefSize = std::max(memrefSize, shape.value() * strides[shape.index()]);
241
242 return (offset + memrefSize) * *elementSize;
243 }
244
245 if (auto tensorType = dyn_cast<TensorType>(type)) {
246 if (!tensorType.hasStaticShape())
247 return std::nullopt;
248
249 auto elementSize = getTypeNumBytes(options, tensorType.getElementType());
250 if (!elementSize)
251 return std::nullopt;
252
253 int64_t size = *elementSize;
254 for (auto shape : tensorType.getShape())
255 size *= shape;
256
257 return size;
258 }
259
260 // TODO: Add size computation for other types.
261 return std::nullopt;
262}
263
264/// Converts a scalar `type` to a suitable type under the given `targetEnv`.
265static Type
266convertScalarType(const spirv::TargetEnv &targetEnv,
268 std::optional<spirv::StorageClass> storageClass = {}) {
269 // Get extension and capability requirements for the given type.
272 type.getExtensions(extensions, storageClass);
273 type.getCapabilities(capabilities, storageClass);
274
275 // If all requirements are met, then we can accept this type as-is.
276 if (succeeded(checkCapabilityRequirements(type, targetEnv, capabilities)) &&
277 succeeded(checkExtensionRequirements(type, targetEnv, extensions)))
278 return type;
279
280 // Otherwise we need to adjust the type, which really means adjusting the
281 // bitwidth given this is a scalar type.
282 if (!options.emulateLT32BitScalarTypes)
283 return nullptr;
284
285 // We only emulate narrower scalar types here and do not truncate results.
286 if (type.getIntOrFloatBitWidth() > 32) {
287 LLVM_DEBUG(llvm::dbgs()
288 << type
289 << " not converted to 32-bit for SPIR-V to avoid truncation\n");
290 return nullptr;
291 }
292
293 if (auto floatType = dyn_cast<FloatType>(type)) {
294 LLVM_DEBUG(llvm::dbgs() << type << " converted to 32-bit for SPIR-V\n");
295 return Builder(targetEnv.getContext()).getF32Type();
296 }
297
298 auto intType = cast<IntegerType>(type);
299 LLVM_DEBUG(llvm::dbgs() << type << " converted to 32-bit for SPIR-V\n");
300 return IntegerType::get(targetEnv.getContext(), /*width=*/32,
301 intType.getSignedness());
302}
303
304/// Converts a sub-byte integer `type` to i32 regardless of target environment.
305/// Returns a nullptr for unsupported integer types, including non sub-byte
306/// types.
307///
308/// Note that we don't recognize sub-byte types in `spirv::ScalarType` and use
309/// the above given that these sub-byte types are not supported at all in
310/// SPIR-V; there are no compute/storage capability for them like other
311/// supported integer types.
312static Type convertSubByteIntegerType(const SPIRVConversionOptions &options,
313 IntegerType type) {
314 if (type.getWidth() > 8) {
315 LLVM_DEBUG(llvm::dbgs() << "not a subbyte type\n");
316 return nullptr;
317 }
318 if (options.subByteTypeStorage != SPIRVSubByteTypeStorage::Packed) {
319 LLVM_DEBUG(llvm::dbgs() << "unsupported sub-byte storage kind\n");
320 return nullptr;
321 }
322
323 if (!llvm::isPowerOf2_32(type.getWidth())) {
324 LLVM_DEBUG(llvm::dbgs()
325 << "unsupported non-power-of-two bitwidth in sub-byte" << type
326 << "\n");
327 return nullptr;
328 }
329
330 LLVM_DEBUG(llvm::dbgs() << type << " converted to 32-bit for SPIR-V\n");
331 return IntegerType::get(type.getContext(), /*width=*/32,
332 type.getSignedness());
333}
334
335/// Converts 8-bit float types to integer types with the same bit width.
336/// Returns a nullptr for unsupported 8-bit float types.
337static Type convert8BitFloatType(const SPIRVConversionOptions &options,
338 FloatType type) {
339 if (!options.emulateUnsupportedFloatTypes)
340 return nullptr;
341 // F8 types are converted to integer types with the same bit width.
342 if (isa<Float8E5M2Type, Float8E4M3Type, Float8E4M3FNType, Float8E5M2FNUZType,
343 Float8E4M3FNUZType, Float8E4M3B11FNUZType, Float8E3M4Type,
344 Float8E8M0FNUType>(type))
345 return IntegerType::get(type.getContext(), type.getWidth());
346 LLVM_DEBUG(llvm::dbgs() << "unsupported 8-bit float type: " << type << "\n");
347 return nullptr;
348}
349
350/// Returns a type with the same shape but with any 8-bit float element type
351/// converted to the same bit width integer type. This is a noop when the
352/// element type is not the 8-bit float type or emulation flag is set to false.
353static ShapedType
354convertShaped8BitFloatType(ShapedType type,
356 if (!options.emulateUnsupportedFloatTypes)
357 return type;
358 Type srcElementType = type.getElementType();
359 Type convertedElementType = nullptr;
360 // F8 types are converted to integer types with the same bit width.
361 if (isa<Float8E5M2Type, Float8E4M3Type, Float8E4M3FNType, Float8E5M2FNUZType,
362 Float8E4M3FNUZType, Float8E4M3B11FNUZType, Float8E3M4Type,
363 Float8E8M0FNUType>(srcElementType))
364 convertedElementType = IntegerType::get(
365 type.getContext(), srcElementType.getIntOrFloatBitWidth());
366
367 if (!convertedElementType)
368 return type;
369
370 return type.clone(convertedElementType);
371}
372
373/// Returns a type with the same shape but with any index element type converted
374/// to the matching integer type. This is a noop when the element type is not
375/// the index type.
376static ShapedType
377convertIndexElementType(ShapedType type,
379 Type indexType = dyn_cast<IndexType>(type.getElementType());
380 if (!indexType)
381 return type;
382
383 return type.clone(getIndexType(type.getContext(), options));
384}
385
386/// Converts a vector `type` to a suitable type under the given `targetEnv`.
387static Type
388convertVectorType(const spirv::TargetEnv &targetEnv,
389 const SPIRVConversionOptions &options, VectorType type,
390 std::optional<spirv::StorageClass> storageClass = {}) {
391 type = cast<VectorType>(convertIndexElementType(type, options));
392 type = cast<VectorType>(convertShaped8BitFloatType(type, options));
393 auto scalarType = dyn_cast_or_null<spirv::ScalarType>(type.getElementType());
394 if (!scalarType) {
395 // If this is not a spec allowed scalar type, try to handle sub-byte integer
396 // types.
397 auto intType = dyn_cast<IntegerType>(type.getElementType());
398 if (!intType) {
399 LLVM_DEBUG(llvm::dbgs()
400 << type
401 << " illegal: cannot convert non-scalar element type\n");
402 return nullptr;
403 }
404
405 Type elementType = convertSubByteIntegerType(options, intType);
406 if (!elementType)
407 return nullptr;
408
409 if (type.getRank() <= 1 && type.getNumElements() == 1)
410 return elementType;
411
412 if (type.getNumElements() > 4) {
413 LLVM_DEBUG(llvm::dbgs()
414 << type << " illegal: > 4-element unimplemented\n");
415 return nullptr;
416 }
417
418 return VectorType::get(type.getShape(), elementType);
419 }
420
421 if (type.getRank() <= 1 && type.getNumElements() == 1)
422 return convertScalarType(targetEnv, options, scalarType, storageClass);
423
425 LLVM_DEBUG(llvm::dbgs()
426 << type << " illegal: not a valid composite type\n");
427 return nullptr;
428 }
429
430 // Get extension and capability requirements for the given type.
433 cast<spirv::CompositeType>(type).getExtensions(extensions, storageClass);
434 cast<spirv::CompositeType>(type).getCapabilities(capabilities, storageClass);
435
436 // If all requirements are met, then we can accept this type as-is.
437 if (succeeded(checkCapabilityRequirements(type, targetEnv, capabilities)) &&
438 succeeded(checkExtensionRequirements(type, targetEnv, extensions)))
439 return type;
440
441 auto elementType =
442 convertScalarType(targetEnv, options, scalarType, storageClass);
443 if (elementType)
444 return VectorType::get(type.getShape(), elementType);
445 return nullptr;
446}
447
448static Type
449convertComplexType(const spirv::TargetEnv &targetEnv,
450 const SPIRVConversionOptions &options, ComplexType type,
451 std::optional<spirv::StorageClass> storageClass = {}) {
452 auto scalarType = dyn_cast_or_null<spirv::ScalarType>(type.getElementType());
453 if (!scalarType) {
454 LLVM_DEBUG(llvm::dbgs()
455 << type << " illegal: cannot convert non-scalar element type\n");
456 return nullptr;
457 }
458
459 auto elementType =
460 convertScalarType(targetEnv, options, scalarType, storageClass);
461 if (!elementType)
462 return nullptr;
463 if (elementType != type.getElementType()) {
464 LLVM_DEBUG(llvm::dbgs()
465 << type << " illegal: complex type emulation unsupported\n");
466 return nullptr;
467 }
468
469 return VectorType::get(2, elementType);
470}
471
472/// Converts a tensor `type` to a suitable type under the given `targetEnv`.
473///
474/// Note that this is mainly for lowering constant tensors. In SPIR-V one can
475/// create composite constants with OpConstantComposite to embed relative large
476/// constant values and use OpCompositeExtract and OpCompositeInsert to
477/// manipulate, like what we do for vectors.
478static Type convertTensorType(const spirv::TargetEnv &targetEnv,
480 TensorType type) {
481 // TODO: Handle dynamic shapes.
482 if (!type.hasStaticShape()) {
483 LLVM_DEBUG(llvm::dbgs()
484 << type << " illegal: dynamic shape unimplemented\n");
485 return nullptr;
486 }
487
488 type = cast<TensorType>(convertIndexElementType(type, options));
489 type = cast<TensorType>(convertShaped8BitFloatType(type, options));
490 auto scalarType = dyn_cast_or_null<spirv::ScalarType>(type.getElementType());
491 if (!scalarType) {
492 LLVM_DEBUG(llvm::dbgs()
493 << type << " illegal: cannot convert non-scalar element type\n");
494 return nullptr;
495 }
496
497 std::optional<int64_t> scalarSize = getTypeNumBytes(options, scalarType);
498 std::optional<int64_t> tensorSize = getTypeNumBytes(options, type);
499 if (!scalarSize || !tensorSize) {
500 LLVM_DEBUG(llvm::dbgs()
501 << type << " illegal: cannot deduce element count\n");
502 return nullptr;
503 }
504
505 int64_t arrayElemCount = *tensorSize / *scalarSize;
506 if (arrayElemCount == 0) {
507 LLVM_DEBUG(llvm::dbgs()
508 << type << " illegal: cannot handle zero-element tensors\n");
509 return nullptr;
510 }
511 if (arrayElemCount > std::numeric_limits<unsigned>::max()) {
512 LLVM_DEBUG(llvm::dbgs()
513 << type << " illegal: cannot fit tensor into target type\n");
514 return nullptr;
515 }
516
517 Type arrayElemType = convertScalarType(targetEnv, options, scalarType);
518 if (!arrayElemType)
519 return nullptr;
520 std::optional<int64_t> arrayElemSize =
521 getTypeNumBytes(options, arrayElemType);
522 if (!arrayElemSize) {
523 LLVM_DEBUG(llvm::dbgs()
524 << type << " illegal: cannot deduce converted element size\n");
525 return nullptr;
526 }
527
528 return spirv::ArrayType::get(arrayElemType, arrayElemCount);
529}
530
531static Type convertBoolMemrefType(const spirv::TargetEnv &targetEnv,
533 MemRefType type,
534 spirv::StorageClass storageClass) {
535 unsigned numBoolBits = options.boolNumBits;
536 if (numBoolBits != 8) {
537 LLVM_DEBUG(llvm::dbgs()
538 << "using non-8-bit storage for bool types unimplemented");
539 return nullptr;
540 }
541 auto elementType = dyn_cast<spirv::ScalarType>(
542 IntegerType::get(type.getContext(), numBoolBits));
543 if (!elementType)
544 return nullptr;
545 Type arrayElemType =
546 convertScalarType(targetEnv, options, elementType, storageClass);
547 if (!arrayElemType)
548 return nullptr;
549 std::optional<int64_t> arrayElemSize =
550 getTypeNumBytes(options, arrayElemType);
551 if (!arrayElemSize) {
552 LLVM_DEBUG(llvm::dbgs()
553 << type << " illegal: cannot deduce converted element size\n");
554 return nullptr;
555 }
556
557 if (!type.hasStaticShape()) {
558 // For OpenCL Kernel, dynamic shaped memrefs convert into a pointer pointing
559 // to the element.
560 if (targetEnv.allows(spirv::Capability::Kernel))
561 return spirv::PointerType::get(arrayElemType, storageClass);
562 int64_t stride = needsExplicitLayout(storageClass) ? *arrayElemSize : 0;
563 auto arrayType = spirv::RuntimeArrayType::get(arrayElemType, stride);
564 // For Vulkan we need extra wrapping struct and array to satisfy interface
565 // needs.
566 return wrapInStructAndGetPointer(arrayType, storageClass);
567 }
568
569 if (type.getNumElements() == 0) {
570 LLVM_DEBUG(llvm::dbgs()
571 << type << " illegal: zero-element memrefs are not supported\n");
572 return nullptr;
573 }
574
575 int64_t memrefSize = llvm::divideCeil(type.getNumElements() * numBoolBits, 8);
576 int64_t arrayElemCount = llvm::divideCeil(memrefSize, *arrayElemSize);
577 int64_t stride = needsExplicitLayout(storageClass) ? *arrayElemSize : 0;
578 auto arrayType = spirv::ArrayType::get(arrayElemType, arrayElemCount, stride);
579 if (targetEnv.allows(spirv::Capability::Kernel))
580 return spirv::PointerType::get(arrayType, storageClass);
581 return wrapInStructAndGetPointer(arrayType, storageClass);
582}
583
584static Type convertSubByteMemrefType(const spirv::TargetEnv &targetEnv,
586 MemRefType type,
587 spirv::StorageClass storageClass) {
588 IntegerType elementType = cast<IntegerType>(type.getElementType());
589 Type arrayElemType = convertSubByteIntegerType(options, elementType);
590 if (!arrayElemType)
591 return nullptr;
592 int64_t arrayElemSize = *getTypeNumBytes(options, arrayElemType);
593
594 if (!type.hasStaticShape()) {
595 // For OpenCL Kernel, dynamic shaped memrefs convert into a pointer pointing
596 // to the element.
597 if (targetEnv.allows(spirv::Capability::Kernel))
598 return spirv::PointerType::get(arrayElemType, storageClass);
599 int64_t stride = needsExplicitLayout(storageClass) ? arrayElemSize : 0;
600 auto arrayType = spirv::RuntimeArrayType::get(arrayElemType, stride);
601 // For Vulkan we need extra wrapping struct and array to satisfy interface
602 // needs.
603 return wrapInStructAndGetPointer(arrayType, storageClass);
604 }
605
606 if (type.getNumElements() == 0) {
607 LLVM_DEBUG(llvm::dbgs()
608 << type << " illegal: zero-element memrefs are not supported\n");
609 return nullptr;
610 }
611
612 int64_t memrefSize =
613 llvm::divideCeil(type.getNumElements() * elementType.getWidth(), 8);
614 int64_t arrayElemCount = llvm::divideCeil(memrefSize, arrayElemSize);
615 int64_t stride = needsExplicitLayout(storageClass) ? arrayElemSize : 0;
616 auto arrayType = spirv::ArrayType::get(arrayElemType, arrayElemCount, stride);
617 if (targetEnv.allows(spirv::Capability::Kernel))
618 return spirv::PointerType::get(arrayType, storageClass);
619 return wrapInStructAndGetPointer(arrayType, storageClass);
620}
621
622static spirv::Dim convertRank(int64_t rank) {
623 switch (rank) {
624 case 1:
625 return spirv::Dim::Dim1D;
626 case 2:
627 return spirv::Dim::Dim2D;
628 case 3:
629 return spirv::Dim::Dim3D;
630 default:
631 llvm_unreachable("Invalid memref rank!");
632 }
633}
634
635static spirv::ImageFormat getImageFormat(Type elementType) {
636 return TypeSwitch<Type, spirv::ImageFormat>(elementType)
637 .Case([](Float16Type) { return spirv::ImageFormat::R16f; })
638 .Case([](Float32Type) { return spirv::ImageFormat::R32f; })
639 .Case([](IntegerType intType) {
640 auto const isSigned = intType.isSigned() || intType.isSignless();
641#define BIT_WIDTH_CASE(BIT_WIDTH) \
642 case BIT_WIDTH: \
643 return isSigned ? spirv::ImageFormat::R##BIT_WIDTH##i \
644 : spirv::ImageFormat::R##BIT_WIDTH##ui
645
646 switch (intType.getWidth()) {
647 BIT_WIDTH_CASE(16);
648 BIT_WIDTH_CASE(32);
649 default:
650 llvm_unreachable("Unhandled integer type!");
651 }
652 })
653 .DefaultUnreachable("Unhandled element type!");
654#undef BIT_WIDTH_CASE
655}
656
657static Type convertMemrefType(const spirv::TargetEnv &targetEnv,
659 MemRefType type) {
660 auto attr = dyn_cast_or_null<spirv::StorageClassAttr>(type.getMemorySpace());
661 if (!attr) {
662 LLVM_DEBUG(
663 llvm::dbgs()
664 << type
665 << " illegal: expected memory space to be a SPIR-V storage class "
666 "attribute; please use MemorySpaceToStorageClassConverter to map "
667 "numeric memory spaces beforehand\n");
668 return nullptr;
669 }
670 spirv::StorageClass storageClass = attr.getValue();
671
672 // Images are a special case since they are an opaque type from which elements
673 // may be accessed via image specific ops or directly through a texture
674 // pointer.
675 if (storageClass == spirv::StorageClass::Image) {
676 const int64_t rank = type.getRank();
677 if (rank < 1 || rank > 3) {
678 LLVM_DEBUG(llvm::dbgs()
679 << type << " illegal: cannot lower memref of rank " << rank
680 << " to a SPIR-V Image\n");
681 return nullptr;
682 }
683
684 // Note that we currently only support lowering to single element texels
685 // e.g. R32f.
686 auto elementType = type.getElementType();
687 if (!isa<spirv::ScalarType>(elementType)) {
688 LLVM_DEBUG(llvm::dbgs() << type << " illegal: cannot lower memref of "
689 << elementType << " to a SPIR-V Image\n");
690 return nullptr;
691 }
692
693 // Currently every memref in the image storage class is converted to a
694 // sampled image so we can hardcode the NeedSampler field. Future work
695 // will generalize this to support regular non-sampled images.
696 auto spvImageType = spirv::ImageType::get(
697 elementType, convertRank(rank), spirv::ImageDepthInfo::DepthUnknown,
698 spirv::ImageArrayedInfo::NonArrayed,
699 spirv::ImageSamplingInfo::SingleSampled,
700 spirv::ImageSamplerUseInfo::NeedSampler, getImageFormat(elementType));
701 auto spvSampledImageType = spirv::SampledImageType::get(spvImageType);
702 auto imagePtrType = spirv::PointerType::get(
703 spvSampledImageType, spirv::StorageClass::UniformConstant);
704 return imagePtrType;
705 }
706
707 if (isa<IntegerType>(type.getElementType())) {
708 if (type.getElementTypeBitWidth() == 1)
709 return convertBoolMemrefType(targetEnv, options, type, storageClass);
710 if (type.getElementTypeBitWidth() < 8)
711 return convertSubByteMemrefType(targetEnv, options, type, storageClass);
712 }
713
714 Type arrayElemType;
715 Type elementType = type.getElementType();
716 if (auto vecType = dyn_cast<VectorType>(elementType)) {
717 arrayElemType =
718 convertVectorType(targetEnv, options, vecType, storageClass);
719 } else if (auto complexType = dyn_cast<ComplexType>(elementType)) {
720 arrayElemType =
721 convertComplexType(targetEnv, options, complexType, storageClass);
722 } else if (auto scalarType = dyn_cast<spirv::ScalarType>(elementType)) {
723 arrayElemType =
724 convertScalarType(targetEnv, options, scalarType, storageClass);
725 } else if (auto indexType = dyn_cast<IndexType>(elementType)) {
726 type = cast<MemRefType>(convertIndexElementType(type, options));
727 arrayElemType = type.getElementType();
728 } else if (auto floatType = dyn_cast<FloatType>(elementType)) {
729 // Hnadle 8 bit float types.
730 type = cast<MemRefType>(convertShaped8BitFloatType(type, options));
731 arrayElemType = type.getElementType();
732 } else {
733 LLVM_DEBUG(
734 llvm::dbgs()
735 << type
736 << " unhandled: can only convert scalar or vector element type\n");
737 return nullptr;
738 }
739 if (!arrayElemType)
740 return nullptr;
741
742 std::optional<int64_t> arrayElemSize =
743 getTypeNumBytes(options, arrayElemType);
744 if (!arrayElemSize) {
745 LLVM_DEBUG(llvm::dbgs()
746 << type << " illegal: cannot deduce converted element size\n");
747 return nullptr;
748 }
749
750 if (!type.hasStaticShape()) {
751 // For OpenCL Kernel, dynamic shaped memrefs convert into a pointer pointing
752 // to the element.
753 if (targetEnv.allows(spirv::Capability::Kernel))
754 return spirv::PointerType::get(arrayElemType, storageClass);
755 int64_t stride = needsExplicitLayout(storageClass) ? *arrayElemSize : 0;
756 auto arrayType = spirv::RuntimeArrayType::get(arrayElemType, stride);
757 // For Vulkan we need extra wrapping struct and array to satisfy interface
758 // needs.
759 return wrapInStructAndGetPointer(arrayType, storageClass);
760 }
761
762 std::optional<int64_t> memrefSize = getTypeNumBytes(options, type);
763 if (!memrefSize) {
764 LLVM_DEBUG(llvm::dbgs()
765 << type << " illegal: cannot deduce element count\n");
766 return nullptr;
767 }
768
769 if (*memrefSize == 0) {
770 LLVM_DEBUG(llvm::dbgs()
771 << type << " illegal: zero-element memrefs are not supported\n");
772 return nullptr;
773 }
774
775 int64_t arrayElemCount = llvm::divideCeil(*memrefSize, *arrayElemSize);
776 int64_t stride = needsExplicitLayout(storageClass) ? *arrayElemSize : 0;
777 auto arrayType = spirv::ArrayType::get(arrayElemType, arrayElemCount, stride);
778 if (targetEnv.allows(spirv::Capability::Kernel))
779 return spirv::PointerType::get(arrayType, storageClass);
780 return wrapInStructAndGetPointer(arrayType, storageClass);
781}
782
783//===----------------------------------------------------------------------===//
784// Type casting materialization
785//===----------------------------------------------------------------------===//
786
787/// Converts the given `inputs` to the original source `type` considering the
788/// `targetEnv`'s capabilities.
789///
790/// This function is meant to be used for source materialization in type
791/// converters. When the type converter needs to materialize a cast op back
792/// to some original source type, we need to check whether the original source
793/// type is supported in the target environment. If so, we can insert legal
794/// SPIR-V cast ops accordingly.
795///
796/// Note that in SPIR-V the capabilities for storage and compute are separate.
797/// This function is meant to handle the **compute** side; so it does not
798/// involve storage classes in its logic. The storage side is expected to be
799/// handled by MemRef conversion logic.
800static Value castToSourceType(const spirv::TargetEnv &targetEnv,
801 OpBuilder &builder, Type type, ValueRange inputs,
802 Location loc) {
803 // We can only cast one value in SPIR-V.
804 if (inputs.size() != 1) {
805 auto castOp =
806 UnrealizedConversionCastOp::create(builder, loc, type, inputs);
807 return castOp.getResult(0);
808 }
809 Value input = inputs.front();
810
811 // Only support integer types for now. Floating point types to be implemented.
812 if (!isa<IntegerType>(type)) {
813 auto castOp =
814 UnrealizedConversionCastOp::create(builder, loc, type, inputs);
815 return castOp.getResult(0);
816 }
817 auto inputType = cast<IntegerType>(input.getType());
818
819 auto scalarType = dyn_cast<spirv::ScalarType>(type);
820 if (!scalarType) {
821 auto castOp =
822 UnrealizedConversionCastOp::create(builder, loc, type, inputs);
823 return castOp.getResult(0);
824 }
825
826 // Only support source type with a smaller bitwidth. This would mean we are
827 // truncating to go back so we don't need to worry about the signedness.
828 // For extension, we cannot have enough signal here to decide which op to use.
829 if (inputType.getIntOrFloatBitWidth() < scalarType.getIntOrFloatBitWidth()) {
830 auto castOp =
831 UnrealizedConversionCastOp::create(builder, loc, type, inputs);
832 return castOp.getResult(0);
833 }
834
835 // Boolean values would need to use different ops than normal integer values.
836 if (type.isInteger(1)) {
837 Value one = spirv::ConstantOp::getOne(inputType, loc, builder);
838 return spirv::IEqualOp::create(builder, loc, input, one);
839 }
840
841 // Check that the source integer type is supported by the environment.
844 scalarType.getExtensions(exts);
845 scalarType.getCapabilities(caps);
846 if (failed(checkCapabilityRequirements(type, targetEnv, caps)) ||
847 failed(checkExtensionRequirements(type, targetEnv, exts))) {
848 auto castOp =
849 UnrealizedConversionCastOp::create(builder, loc, type, inputs);
850 return castOp.getResult(0);
851 }
852
853 // We've already made sure this is truncating previously, so we don't need to
854 // care about signedness here. Still try to use a corresponding op for better
855 // consistency though.
856 if (type.isSignedInteger()) {
857 return spirv::SConvertOp::create(builder, loc, type, input);
858 }
859 return spirv::UConvertOp::create(builder, loc, type, input);
860}
861
862//===----------------------------------------------------------------------===//
863// Builtin Variables
864//===----------------------------------------------------------------------===//
865
866static spirv::GlobalVariableOp getBuiltinVariable(Block &body,
867 spirv::BuiltIn builtin) {
868 // Look through all global variables in the given `body` block and check if
869 // there is a spirv.GlobalVariable that has the same `builtin` attribute.
870 for (auto varOp : body.getOps<spirv::GlobalVariableOp>()) {
871 if (StringAttr builtinAttr = varOp.getBuiltInAttr()) {
872 auto varBuiltIn = spirv::symbolizeBuiltIn(builtinAttr.getValue());
873 if (varBuiltIn == builtin) {
874 return varOp;
875 }
876 }
877 }
878 return nullptr;
879}
880
881/// Gets name of global variable for a builtin.
882std::string getBuiltinVarName(spirv::BuiltIn builtin, StringRef prefix,
883 StringRef suffix) {
884 return Twine(prefix).concat(stringifyBuiltIn(builtin)).concat(suffix).str();
885}
886
887/// Gets or inserts a global variable for a builtin within `body` block.
888static spirv::GlobalVariableOp
889getOrInsertBuiltinVariable(Block &body, Location loc, spirv::BuiltIn builtin,
890 Type integerType, OpBuilder &builder,
891 StringRef prefix, StringRef suffix) {
892 if (auto varOp = getBuiltinVariable(body, builtin))
893 return varOp;
894
895 OpBuilder::InsertionGuard guard(builder);
896 builder.setInsertionPointToStart(&body);
897
898 spirv::GlobalVariableOp newVarOp;
899 switch (builtin) {
900 case spirv::BuiltIn::NumWorkgroups:
901 case spirv::BuiltIn::WorkgroupSize:
902 case spirv::BuiltIn::WorkgroupId:
903 case spirv::BuiltIn::LocalInvocationId:
904 case spirv::BuiltIn::GlobalInvocationId: {
905 auto ptrType = spirv::PointerType::get(VectorType::get({3}, integerType),
906 spirv::StorageClass::Input);
907 std::string name = getBuiltinVarName(builtin, prefix, suffix);
908 newVarOp =
909 spirv::GlobalVariableOp::create(builder, loc, ptrType, name, builtin);
910 break;
911 }
912 case spirv::BuiltIn::SubgroupId:
913 case spirv::BuiltIn::NumSubgroups:
914 case spirv::BuiltIn::SubgroupSize:
915 case spirv::BuiltIn::SubgroupLocalInvocationId: {
916 auto ptrType =
917 spirv::PointerType::get(integerType, spirv::StorageClass::Input);
918 std::string name = getBuiltinVarName(builtin, prefix, suffix);
919 newVarOp =
920 spirv::GlobalVariableOp::create(builder, loc, ptrType, name, builtin);
921 break;
922 }
923 default:
924 emitError(loc, "unimplemented builtin variable generation for ")
925 << stringifyBuiltIn(builtin);
926 }
927 return newVarOp;
928}
929
930//===----------------------------------------------------------------------===//
931// Push constant storage
932//===----------------------------------------------------------------------===//
933
934/// Returns the pointer type for the push constant storage containing
935/// `elementCount` 32-bit integer values.
936static spirv::PointerType getPushConstantStorageType(unsigned elementCount,
937 Builder &builder,
938 Type indexType) {
939 auto arrayType = spirv::ArrayType::get(indexType, elementCount,
940 /*stride=*/4);
941 auto structType = spirv::StructType::get({arrayType}, /*offsetInfo=*/0);
942 return spirv::PointerType::get(structType, spirv::StorageClass::PushConstant);
943}
944
945/// Returns the push constant varible containing `elementCount` 32-bit integer
946/// values in `body`. Returns null op if such an op does not exit.
947static spirv::GlobalVariableOp getPushConstantVariable(Block &body,
948 unsigned elementCount) {
949 for (auto varOp : body.getOps<spirv::GlobalVariableOp>()) {
950 auto ptrType = dyn_cast<spirv::PointerType>(varOp.getType());
951 if (!ptrType)
952 continue;
953
954 // Note that Vulkan requires "There must be no more than one push constant
955 // block statically used per shader entry point." So we should always reuse
956 // the existing one.
957 if (ptrType.getStorageClass() == spirv::StorageClass::PushConstant) {
958 auto numElements = cast<spirv::ArrayType>(
959 cast<spirv::StructType>(ptrType.getPointeeType())
960 .getElementType(0))
961 .getNumElements();
962 if (numElements == elementCount)
963 return varOp;
964 }
965 }
966 return nullptr;
967}
968
969/// Gets or inserts a global variable for push constant storage containing
970/// `elementCount` 32-bit integer values in `block`.
971static spirv::GlobalVariableOp
972getOrInsertPushConstantVariable(Location loc, Block &block,
973 unsigned elementCount, OpBuilder &b,
974 Type indexType) {
975 if (auto varOp = getPushConstantVariable(block, elementCount))
976 return varOp;
977
978 auto builder = OpBuilder::atBlockBegin(&block, b.getListener());
979 auto type = getPushConstantStorageType(elementCount, builder, indexType);
980 const char *name = "__push_constant_var__";
981 return spirv::GlobalVariableOp::create(builder, loc, type, name,
982 /*initializer=*/nullptr);
983}
984
985//===----------------------------------------------------------------------===//
986// func::FuncOp Conversion Patterns
987//===----------------------------------------------------------------------===//
988
989/// A pattern for rewriting function signature to convert arguments of functions
990/// to be of valid SPIR-V types.
991struct FuncOpConversion final : OpConversionPattern<func::FuncOp> {
992 using Base::Base;
993
994 LogicalResult
995 matchAndRewrite(func::FuncOp funcOp, OpAdaptor adaptor,
996 ConversionPatternRewriter &rewriter) const override {
997 FunctionType fnType = funcOp.getFunctionType();
998 if (fnType.getNumResults() > 1)
999 return failure();
1000
1001 TypeConverter::SignatureConversion signatureConverter(
1002 fnType.getNumInputs());
1003 for (const auto &argType : enumerate(fnType.getInputs())) {
1004 auto convertedType = getTypeConverter()->convertType(argType.value());
1005 if (!convertedType)
1006 return failure();
1007 signatureConverter.addInputs(argType.index(), convertedType);
1008 }
1009
1010 Type resultType;
1011 if (fnType.getNumResults() == 1) {
1012 resultType = getTypeConverter()->convertType(fnType.getResult(0));
1013 if (!resultType)
1014 return failure();
1015 }
1016
1017 // Create the converted spirv.func op.
1018 auto newFuncOp = spirv::FuncOp::create(
1019 rewriter, funcOp.getLoc(), funcOp.getName(),
1020 rewriter.getFunctionType(signatureConverter.getConvertedTypes(),
1021 resultType ? TypeRange(resultType)
1022 : TypeRange()));
1023
1024 newFuncOp.setArgAttrsAttr(funcOp.getArgAttrsAttr());
1025 newFuncOp.setResAttrsAttr(funcOp.getResAttrsAttr());
1026 cast<SymbolOpInterface>(newFuncOp.getOperation())
1027 .setVisibility(
1028 cast<SymbolOpInterface>(funcOp.getOperation()).getVisibility());
1029
1030 // Copy over all attributes other than the function name and type.
1031 for (NamedAttribute namedAttr :
1032 funcOp->getDiscardableAttrDictionary().getValue()) {
1033 if (namedAttr.getName() != funcOp.getFunctionTypeAttrName() &&
1034 namedAttr.getName() != SymbolTable::getSymbolAttrName())
1035 newFuncOp->setDiscardableAttr(namedAttr.getName(),
1036 namedAttr.getValue());
1037 }
1038
1039 rewriter.inlineRegionBefore(funcOp.getBody(), newFuncOp.getBody(),
1040 newFuncOp.end());
1041 if (failed(rewriter.convertRegionTypes(
1042 &newFuncOp.getBody(), *getTypeConverter(), &signatureConverter)))
1043 return failure();
1044 rewriter.eraseOp(funcOp);
1045 return success();
1046 }
1047};
1048
1049/// A pattern for rewriting function signature to convert vector arguments of
1050/// functions to be of valid types
1051struct FuncOpVectorUnroll final : OpRewritePattern<func::FuncOp> {
1052 using Base::Base;
1053
1054 LogicalResult matchAndRewrite(func::FuncOp funcOp,
1055 PatternRewriter &rewriter) const override {
1056 FunctionType fnType = funcOp.getFunctionType();
1057
1058 // TODO: Handle declarations.
1059 if (funcOp.isDeclaration()) {
1060 LLVM_DEBUG(llvm::dbgs()
1061 << fnType << " illegal: declarations are unsupported\n");
1062 return failure();
1063 }
1064
1065 // Bail out early for dynamically-shaped argument types: getZeroAttr
1066 // requires a statically-shaped type. VectorType is always statically
1067 // shaped, so this correctly skips it without a special-case guard.
1068 if (llvm::any_of(fnType.getInputs(), [](Type argType) {
1069 auto shapedType = dyn_cast<ShapedType>(argType);
1070 return shapedType && !shapedType.hasStaticShape();
1071 }))
1072 return failure();
1073
1074 // Create a new func op with the original type and copy the function body.
1075 auto newFuncOp = func::FuncOp::create(rewriter, funcOp.getLoc(),
1076 funcOp.getName(), fnType);
1077 rewriter.inlineRegionBefore(funcOp.getBody(), newFuncOp.getBody(),
1078 newFuncOp.end());
1079
1080 Location loc = newFuncOp.getBody().getLoc();
1081
1082 Block &entryBlock = newFuncOp.getBlocks().front();
1083 OpBuilder::InsertionGuard guard(rewriter);
1084 rewriter.setInsertionPointToStart(&entryBlock);
1085
1086 TypeConverter::SignatureConversion oneToNTypeMapping(
1087 fnType.getInputs().size());
1088
1089 // For arguments that are of illegal types and require unrolling.
1090 // `unrolledInputNums` stores the indices of arguments that result from
1091 // unrolling in the new function signature. `newInputNo` is a counter.
1092 SmallVector<size_t> unrolledInputNums;
1093 size_t newInputNo = 0;
1094
1095 // For arguments that are of legal types and do not require unrolling.
1096 // `tmpOps` stores a mapping from temporary operations that serve as
1097 // placeholders for new arguments that will be added later. These operations
1098 // will be erased once the entry block's argument list is updated.
1099 llvm::SmallDenseMap<Operation *, size_t> tmpOps;
1100
1101 // This counts the number of new operations created.
1102 size_t newOpCount = 0;
1103
1104 // Enumerate through the arguments.
1105 for (auto [origInputNo, origType] : enumerate(fnType.getInputs())) {
1106 // Check whether the argument is of vector type.
1107 auto origVecType = dyn_cast<VectorType>(origType);
1108 if (!origVecType) {
1109 // We need a placeholder for the old argument that will be erased later.
1110 Value result = arith::ConstantOp::create(
1111 rewriter, loc, origType, rewriter.getZeroAttr(origType));
1112 rewriter.replaceAllUsesWith(newFuncOp.getArgument(origInputNo), result);
1113 tmpOps.insert({result.getDefiningOp(), newInputNo});
1114 oneToNTypeMapping.addInputs(origInputNo, origType);
1115 ++newInputNo;
1116 ++newOpCount;
1117 continue;
1118 }
1119 // Check whether the vector needs unrolling.
1120 auto targetShape = getTargetShape(origVecType);
1121 if (!targetShape) {
1122 // We need a placeholder for the old argument that will be erased later.
1123 Value result = arith::ConstantOp::create(
1124 rewriter, loc, origType, rewriter.getZeroAttr(origType));
1125 rewriter.replaceAllUsesWith(newFuncOp.getArgument(origInputNo), result);
1126 tmpOps.insert({result.getDefiningOp(), newInputNo});
1127 oneToNTypeMapping.addInputs(origInputNo, origType);
1128 ++newInputNo;
1129 ++newOpCount;
1130 continue;
1131 }
1132 VectorType unrolledType =
1133 VectorType::get(*targetShape, origVecType.getElementType());
1134 auto originalShape =
1135 llvm::to_vector_of<int64_t, 4>(origVecType.getShape());
1136
1137 // Prepare the result vector.
1138 Value result = arith::ConstantOp::create(
1139 rewriter, loc, origVecType, rewriter.getZeroAttr(origVecType));
1140 ++newOpCount;
1141 // Prepare the placeholder for the new arguments that will be added later.
1142 Value dummy = arith::ConstantOp::create(
1143 rewriter, loc, unrolledType, rewriter.getZeroAttr(unrolledType));
1144 ++newOpCount;
1145
1146 // Create the `vector.insert_strided_slice` ops.
1147 SmallVector<int64_t> strides(targetShape->size(), 1);
1148 SmallVector<Type> newTypes;
1149 for (SmallVector<int64_t> offsets :
1150 StaticTileOffsetRange(originalShape, *targetShape)) {
1151 result = vector::InsertStridedSliceOp::create(rewriter, loc, dummy,
1152 result, offsets, strides);
1153 newTypes.push_back(unrolledType);
1154 unrolledInputNums.push_back(newInputNo);
1155 ++newInputNo;
1156 ++newOpCount;
1157 }
1158 rewriter.replaceAllUsesWith(newFuncOp.getArgument(origInputNo), result);
1159 oneToNTypeMapping.addInputs(origInputNo, newTypes);
1160 }
1161
1162 // Change the function signature.
1163 auto convertedTypes = oneToNTypeMapping.getConvertedTypes();
1164 auto newFnType = fnType.clone(convertedTypes, fnType.getResults());
1165 rewriter.modifyOpInPlace(newFuncOp,
1166 [&] { newFuncOp.setFunctionType(newFnType); });
1167
1168 // Update the arguments in the entry block.
1169 entryBlock.eraseArguments(0, fnType.getNumInputs());
1170 SmallVector<Location> locs(convertedTypes.size(), newFuncOp.getLoc());
1171 entryBlock.addArguments(convertedTypes, locs);
1172
1173 // Replace all uses of placeholders for initially legal arguments with their
1174 // original function arguments (that were added to `newFuncOp`).
1175 for (auto &[placeholderOp, argIdx] : tmpOps) {
1176 if (!placeholderOp)
1177 continue;
1178 Value replacement = newFuncOp.getArgument(argIdx);
1179 rewriter.replaceAllUsesWith(placeholderOp->getResult(0), replacement);
1180 }
1181
1182 // Replace dummy operands of new `vector.insert_strided_slice` ops with
1183 // their corresponding new function arguments. The new
1184 // `vector.insert_strided_slice` ops are inserted only into the entry block,
1185 // so iterating over that block is sufficient.
1186 size_t unrolledInputIdx = 0;
1187 for (auto [count, op] : enumerate(entryBlock.getOperations())) {
1188 Operation &curOp = op;
1189 // Since all newly created operations are in the beginning, reaching the
1190 // end of them means that any later `vector.insert_strided_slice` should
1191 // not be touched.
1192 if (count >= newOpCount)
1193 continue;
1194 if (auto vecOp = dyn_cast<vector::InsertStridedSliceOp>(op)) {
1195 size_t unrolledInputNo = unrolledInputNums[unrolledInputIdx];
1196 rewriter.modifyOpInPlace(&curOp, [&] {
1197 curOp.setOperand(0, newFuncOp.getArgument(unrolledInputNo));
1198 });
1199 ++unrolledInputIdx;
1200 }
1201 }
1202
1203 // Erase the original funcOp. The `tmpOps` do not need to be erased since
1204 // they have no uses and will be handled by dead-code elimination.
1205 rewriter.eraseOp(funcOp);
1206 return success();
1207 }
1208};
1209
1210//===----------------------------------------------------------------------===//
1211// func::ReturnOp Conversion Patterns
1212//===----------------------------------------------------------------------===//
1213
1214/// A pattern for rewriting function signature and the return op to convert
1215/// vectors to be of valid types.
1216struct ReturnOpVectorUnroll final : OpRewritePattern<func::ReturnOp> {
1217 using Base::Base;
1218
1219 LogicalResult matchAndRewrite(func::ReturnOp returnOp,
1220 PatternRewriter &rewriter) const override {
1221 // Check whether the parent funcOp is valid.
1222 auto funcOp = dyn_cast<func::FuncOp>(returnOp->getParentOp());
1223 if (!funcOp)
1224 return failure();
1225
1226 FunctionType fnType = funcOp.getFunctionType();
1227 TypeConverter::SignatureConversion oneToNTypeMapping(
1228 fnType.getResults().size());
1229 Location loc = returnOp.getLoc();
1230
1231 // For the new return op.
1232 SmallVector<Value> newOperands;
1233
1234 // Enumerate through the results.
1235 for (auto [origResultNo, origType] : enumerate(fnType.getResults())) {
1236 // Check whether the argument is of vector type.
1237 auto origVecType = dyn_cast<VectorType>(origType);
1238 if (!origVecType) {
1239 oneToNTypeMapping.addInputs(origResultNo, origType);
1240 newOperands.push_back(returnOp.getOperand(origResultNo));
1241 continue;
1242 }
1243 // Check whether the vector needs unrolling.
1244 auto targetShape = getTargetShape(origVecType);
1245 if (!targetShape) {
1246 // The original argument can be used.
1247 oneToNTypeMapping.addInputs(origResultNo, origType);
1248 newOperands.push_back(returnOp.getOperand(origResultNo));
1249 continue;
1250 }
1251 VectorType unrolledType =
1252 VectorType::get(*targetShape, origVecType.getElementType());
1253
1254 // Create `vector.extract_strided_slice` ops to form legal vectors from
1255 // the original operand of illegal type.
1256 auto originalShape =
1257 llvm::to_vector_of<int64_t, 4>(origVecType.getShape());
1258 SmallVector<int64_t> strides(originalShape.size(), 1);
1259 SmallVector<int64_t> extractShape(originalShape.size(), 1);
1260 extractShape.back() = targetShape->back();
1261 SmallVector<Type> newTypes;
1262 Value returnValue = returnOp.getOperand(origResultNo);
1263 for (SmallVector<int64_t> offsets :
1264 StaticTileOffsetRange(originalShape, *targetShape)) {
1265 Value result = vector::ExtractStridedSliceOp::create(
1266 rewriter, loc, returnValue, offsets, extractShape, strides);
1267 if (originalShape.size() > 1) {
1268 SmallVector<int64_t> extractIndices(originalShape.size() - 1, 0);
1269 result =
1270 vector::ExtractOp::create(rewriter, loc, result, extractIndices);
1271 }
1272 newOperands.push_back(result);
1273 newTypes.push_back(unrolledType);
1274 }
1275 oneToNTypeMapping.addInputs(origResultNo, newTypes);
1276 }
1277
1278 // Change the function signature.
1279 auto newFnType =
1280 FunctionType::get(rewriter.getContext(), TypeRange(fnType.getInputs()),
1281 TypeRange(oneToNTypeMapping.getConvertedTypes()));
1282 rewriter.modifyOpInPlace(funcOp,
1283 [&] { funcOp.setFunctionType(newFnType); });
1284
1285 // Replace the return op using the new operands. This will automatically
1286 // update the entry block as well.
1287 rewriter.replaceOp(returnOp,
1288 func::ReturnOp::create(rewriter, loc, newOperands));
1289
1290 return success();
1291 }
1292};
1293
1294static void addNoWrapDecorations(Operation *op,
1296 OpBuilder &builder) {
1297 if (flags.noSignedWrap)
1299 spirv::getDecorationString(spirv::Decoration::NoSignedWrap),
1300 builder.getUnitAttr());
1301 if (flags.noUnsignedWrap)
1303 spirv::getDecorationString(spirv::Decoration::NoUnsignedWrap),
1304 builder.getUnitAttr());
1305}
1306
1307static std::optional<uint64_t> getMaxLinearizedIndex(ArrayRef<int64_t> shape,
1308 ArrayRef<int64_t> strides,
1309 int64_t offset) {
1310 if (shape.size() != strides.size() || offset < 0)
1311 return std::nullopt;
1312
1313 uint64_t maxLinearIndex = offset;
1314 for (auto [dimension, stride] : llvm::zip(shape, strides)) {
1315 if (dimension <= 0 || stride < 0)
1316 return std::nullopt;
1317 std::optional<uint64_t> nextMaxLinearIndex = llvm::checkedMulAddUnsigned(
1318 static_cast<uint64_t>(dimension - 1), static_cast<uint64_t>(stride),
1319 maxLinearIndex);
1320 if (!nextMaxLinearIndex)
1321 return std::nullopt;
1322 maxLinearIndex = *nextMaxLinearIndex;
1323 }
1324 return maxLinearIndex;
1325}
1326
1327static std::optional<uint64_t> getStorageBufferElementCount(Value basePtr) {
1328 auto pointerType = dyn_cast<spirv::PointerType>(basePtr.getType());
1329 if (!pointerType ||
1330 pointerType.getStorageClass() != spirv::StorageClass::StorageBuffer)
1331 return std::nullopt;
1332
1333 Type pointeeType = pointerType.getPointeeType();
1334 if (auto structType = dyn_cast<spirv::StructType>(pointeeType)) {
1335 if (structType.getNumElements() != 1)
1336 return std::nullopt;
1337 pointeeType = structType.getElementType(0);
1338 }
1339 auto arrayType = dyn_cast<spirv::ArrayType>(pointeeType);
1340 if (!arrayType)
1341 return std::nullopt;
1342 return arrayType.getNumElements();
1343}
1344
1345static bool shouldEmitInBoundsAccessChain(MemRefType baseType, Value basePtr,
1346 ArrayRef<int64_t> strides,
1347 int64_t offset,
1348 uint64_t accessElementCount) {
1349 // Sub-16-bit integer memrefs may be stored using a wider SPIR-V array element
1350 // than the source element. Keep a plain access chain so later bitwidth
1351 // emulation can adjust the final index in storage-element units.
1352 if (auto integerType = dyn_cast<IntegerType>(baseType.getElementType()))
1353 if (integerType.getWidth() < 16)
1354 return false;
1355
1356 std::optional<uint64_t> maxSourceElementIndex =
1357 getMaxLinearizedIndex(baseType.getShape(), strides, offset);
1358 std::optional<uint64_t> storageElementCount =
1359 getStorageBufferElementCount(basePtr);
1360 if (!maxSourceElementIndex || !storageElementCount)
1361 return false;
1362
1363 if (accessElementCount == 0 || accessElementCount > *storageElementCount)
1364 return false;
1365
1366 // `InBoundsAccessChain` requires the computed pointer to stay within the
1367 // SPIR-V base object. Dynamic index validity is assumed from the source
1368 // operation/caller contract; for vector accesses, `accessElementCount` only
1369 // rejects widths that cannot fit in the fixed StorageBuffer object at all.
1370 // The static proof here is that the memref layout's linear index space maps
1371 // into that same object.
1372 return *maxSourceElementIndex < *storageElementCount;
1373}
1374
1375} // namespace
1376
1377//===----------------------------------------------------------------------===//
1378// Public function for builtin variables
1379//===----------------------------------------------------------------------===//
1380
1382 spirv::BuiltIn builtin,
1383 Type integerType, OpBuilder &builder,
1384 StringRef prefix, StringRef suffix) {
1386 if (!parent) {
1387 op->emitError("expected operation to be within a module-like op");
1388 return nullptr;
1389 }
1390
1391 spirv::GlobalVariableOp varOp =
1392 getOrInsertBuiltinVariable(*parent->getRegion(0).begin(), op->getLoc(),
1393 builtin, integerType, builder, prefix, suffix);
1394 Value ptr = spirv::AddressOfOp::create(builder, op->getLoc(), varOp);
1395 return spirv::LoadOp::create(builder, op->getLoc(), ptr);
1396}
1397
1398//===----------------------------------------------------------------------===//
1399// Public function for pushing constant storage
1400//===----------------------------------------------------------------------===//
1401
1403 unsigned offset, Type integerType,
1404 OpBuilder &builder) {
1405 Location loc = op->getLoc();
1407 if (!parent) {
1408 op->emitError("expected operation to be within a module-like op");
1409 return nullptr;
1410 }
1411
1412 spirv::GlobalVariableOp varOp = getOrInsertPushConstantVariable(
1413 loc, parent->getRegion(0).front(), elementCount, builder, integerType);
1414
1415 Value zeroOp = spirv::ConstantOp::getZero(integerType, loc, builder);
1416 Value offsetOp = spirv::ConstantOp::create(builder, loc, integerType,
1417 builder.getI32IntegerAttr(offset));
1418 auto addrOp = spirv::AddressOfOp::create(builder, loc, varOp);
1419 auto acOp = spirv::AccessChainOp::create(builder, loc, addrOp,
1420 llvm::ArrayRef({zeroOp, offsetOp}));
1421 return spirv::LoadOp::create(builder, loc, acOp);
1422}
1423
1424//===----------------------------------------------------------------------===//
1425// Public functions for index calculation
1426//===----------------------------------------------------------------------===//
1427
1431 ArrayRef<int64_t> strides,
1432 int64_t offset, Type integerType) {
1434 if (!targetEnv.allows(Extension::SPV_KHR_no_integer_wrap_decoration))
1435 return flags;
1436
1437 auto integer = dyn_cast<IntegerType>(integerType);
1438 if (!integer)
1439 return flags;
1440
1441 std::optional<uint64_t> maxLinearIndex =
1442 getMaxLinearizedIndex(shape, strides, offset);
1443 if (!maxLinearIndex)
1444 return flags;
1445
1446 flags.noSignedWrap =
1447 *maxLinearIndex <=
1448 APInt::getSignedMaxValue(integer.getWidth()).getZExtValue();
1449 flags.noUnsignedWrap =
1450 *maxLinearIndex <= APInt::getMaxValue(integer.getWidth()).getZExtValue();
1451 return flags;
1452}
1453
1455 int64_t offset, Type integerType,
1456 Location loc, OpBuilder &builder,
1457 LinearizedIndexNoWrapFlags noWrapFlags) {
1458 assert(indices.size() == strides.size() &&
1459 "must provide indices for all dimensions");
1460
1461 // TODO: Consider moving to use affine.apply and patterns converting
1462 // affine.apply to standard ops. This needs converting to SPIR-V passes to be
1463 // broken down into progressive small steps so we can have intermediate steps
1464 // using other dialects. At the moment SPIR-V is the final sink.
1465
1466 Value linearizedIndex = builder.createOrFold<spirv::ConstantOp>(
1467 loc, integerType, IntegerAttr::get(integerType, offset));
1468 for (const auto &index : llvm::enumerate(indices)) {
1469 Value strideVal = builder.createOrFold<spirv::ConstantOp>(
1470 loc, integerType,
1471 IntegerAttr::get(integerType, strides[index.index()]));
1472 Value update =
1473 builder.createOrFold<spirv::IMulOp>(loc, index.value(), strideVal);
1474 if (noWrapFlags.noSignedWrap || noWrapFlags.noUnsignedWrap)
1475 if (auto mul = update.getDefiningOp<spirv::IMulOp>())
1476 addNoWrapDecorations(mul, noWrapFlags, builder);
1477
1478 linearizedIndex =
1479 builder.createOrFold<spirv::IAddOp>(loc, update, linearizedIndex);
1480 if (noWrapFlags.noSignedWrap || noWrapFlags.noUnsignedWrap)
1481 if (auto add = linearizedIndex.getDefiningOp<spirv::IAddOp>())
1482 addNoWrapDecorations(add, noWrapFlags, builder);
1483 }
1484 return linearizedIndex;
1485}
1486
1488 MemRefType baseType, Value basePtr,
1490 OpBuilder &builder,
1491 uint64_t accessElementCount) {
1492 // Get base and offset of the MemRefType and verify they are static.
1493
1494 int64_t offset;
1496 if (failed(baseType.getStridesAndOffset(strides, offset)) ||
1497 llvm::is_contained(strides, ShapedType::kDynamic) ||
1498 ShapedType::isDynamic(offset)) {
1499 return nullptr;
1500 }
1501
1502 auto indexType = typeConverter.getIndexType();
1504 typeConverter.getTargetEnv(), baseType.getShape(), strides, offset,
1505 indexType);
1506
1507 SmallVector<Value, 2> linearizedIndices;
1508 auto zero = spirv::ConstantOp::getZero(indexType, loc, builder);
1509
1510 if (baseType.getRank() == 0) {
1511 linearizedIndices.push_back(zero);
1512 } else {
1513 linearizedIndices.push_back(linearizeIndex(
1514 indices, strides, offset, indexType, loc, builder, noWrapFlags));
1515 }
1516
1517 const Type pointeeType =
1518 cast<spirv::PointerType>(basePtr.getType()).getPointeeType();
1519 // Interface memrefs are wrapped in a struct: index to its first elem.
1520 if (isa<spirv::StructType>(pointeeType))
1521 linearizedIndices.insert(linearizedIndices.begin(), zero);
1522 if (shouldEmitInBoundsAccessChain(baseType, basePtr, strides, offset,
1523 accessElementCount))
1524 return spirv::InBoundsAccessChainOp::create(builder, loc, basePtr,
1525 linearizedIndices);
1526 return spirv::AccessChainOp::create(builder, loc, basePtr, linearizedIndices);
1527}
1528
1530 MemRefType baseType, Value basePtr,
1532 OpBuilder &builder) {
1533 return getVulkanElementPtr(typeConverter, baseType, basePtr, indices, loc,
1534 builder, /*accessElementCount=*/1);
1535}
1536
1538 MemRefType baseType, Value basePtr,
1540 OpBuilder &builder) {
1541 // Get base and offset of the MemRefType and verify they are static.
1542
1543 int64_t offset;
1545 if (failed(baseType.getStridesAndOffset(strides, offset)) ||
1546 llvm::is_contained(strides, ShapedType::kDynamic) ||
1547 ShapedType::isDynamic(offset)) {
1548 return nullptr;
1549 }
1550
1551 auto indexType = typeConverter.getIndexType();
1553 typeConverter.getTargetEnv(), baseType.getShape(), strides, offset,
1554 indexType);
1555
1556 SmallVector<Value, 2> linearizedIndices;
1557 Value linearIndex;
1558 if (baseType.getRank() == 0) {
1559 linearIndex = spirv::ConstantOp::getZero(indexType, loc, builder);
1560 } else {
1561 linearIndex = linearizeIndex(indices, strides, offset, indexType, loc,
1562 builder, noWrapFlags);
1563 }
1564 Type pointeeType =
1565 cast<spirv::PointerType>(basePtr.getType()).getPointeeType();
1566 if (isa<spirv::ArrayType>(pointeeType)) {
1567 linearizedIndices.push_back(linearIndex);
1568 return spirv::AccessChainOp::create(builder, loc, basePtr,
1569 linearizedIndices);
1570 }
1571 return spirv::PtrAccessChainOp::create(builder, loc, basePtr, linearIndex,
1572 linearizedIndices);
1573}
1574
1576 MemRefType baseType, Value basePtr,
1578 OpBuilder &builder,
1579 uint64_t accessElementCount) {
1580
1581 if (typeConverter.allows(spirv::Capability::Kernel)) {
1582 return getOpenCLElementPtr(typeConverter, baseType, basePtr, indices, loc,
1583 builder);
1584 }
1585
1586 return getVulkanElementPtr(typeConverter, baseType, basePtr, indices, loc,
1587 builder, accessElementCount);
1588}
1589
1591 MemRefType baseType, Value basePtr,
1593 OpBuilder &builder) {
1594 return getElementPtr(typeConverter, baseType, basePtr, indices, loc, builder,
1595 /*accessElementCount=*/1);
1596}
1597
1598//===----------------------------------------------------------------------===//
1599// Public functions for vector unrolling
1600//===----------------------------------------------------------------------===//
1601
1603 for (int i : {4, 3, 2}) {
1604 if (size % i == 0)
1605 return i;
1606 }
1607 return 1;
1608}
1609
1612 VectorType srcVectorType = op.getSourceVectorType();
1613 assert(srcVectorType.getRank() == 1); // Guaranteed by semantics
1614 int64_t vectorSize =
1615 mlir::spirv::getComputeVectorSize(srcVectorType.getDimSize(0));
1616 return {vectorSize};
1617}
1618
1621 VectorType vectorType = op.getResultVectorType();
1622 SmallVector<int64_t> nativeSize(vectorType.getRank(), 1);
1623 nativeSize.back() =
1624 mlir::spirv::getComputeVectorSize(vectorType.getShape().back());
1625 return nativeSize;
1626}
1627
1628std::optional<SmallVector<int64_t>>
1631 if (auto vecType = dyn_cast<VectorType>(op->getResultTypes()[0])) {
1632 if (vecType.getRank() == 0)
1633 return std::nullopt;
1634 SmallVector<int64_t> nativeSize(vecType.getRank(), 1);
1635 nativeSize.back() =
1636 mlir::spirv::getComputeVectorSize(vecType.getShape().back());
1637 return nativeSize;
1638 }
1639 }
1640
1642 .Case<vector::ReductionOp, vector::TransposeOp>(
1643 [](auto typedOp) { return getNativeVectorShapeImpl(typedOp); })
1644 .Default(std::nullopt);
1645}
1646
1648 MLIRContext *context = op->getContext();
1649 RewritePatternSet patterns(context);
1652 // We only want to apply signature conversion once to the existing func ops.
1653 // Without specifying strictMode, the greedy pattern rewriter will keep
1654 // looking for newly created func ops.
1655 return applyPatternsGreedily(op, std::move(patterns),
1656 GreedyRewriteConfig().setStrictness(
1658}
1659
1661 MLIRContext *context = op->getContext();
1662
1663 // Unroll vectors in function bodies to native vector size.
1664 {
1665 RewritePatternSet patterns(context);
1667 [](auto op) { return mlir::spirv::getNativeVectorShape(op); });
1668 populateVectorUnrollPatterns(patterns, options);
1669 if (failed(applyPatternsGreedily(op, std::move(patterns))))
1670 return failure();
1671 }
1672
1673 // Convert transpose ops into extract and insert pairs, in preparation of
1674 // further transformations to canonicalize/cancel.
1675 {
1676 RewritePatternSet patterns(context);
1678 patterns, vector::VectorTransposeLowering::EltWise);
1680 if (failed(applyPatternsGreedily(op, std::move(patterns))))
1681 return failure();
1682 }
1683
1684 // Run canonicalization to cast away leading size-1 dimensions.
1685 {
1686 RewritePatternSet patterns(context);
1687
1688 // We need to pull in casting way leading one dims.
1689 vector::populateCastAwayVectorLeadingOneDimPatterns(patterns);
1690 vector::ReductionOp::getCanonicalizationPatterns(patterns, context);
1691 vector::TransposeOp::getCanonicalizationPatterns(patterns, context);
1692
1693 // Decompose different rank insert_strided_slice and n-D
1694 // extract_slided_slice.
1695 vector::populateVectorInsertExtractStridedSliceDecompositionPatterns(
1696 patterns);
1697 vector::InsertOp::getCanonicalizationPatterns(patterns, context);
1698 vector::ExtractOp::getCanonicalizationPatterns(patterns, context);
1699
1700 // Trimming leading unit dims may generate broadcast/shape_cast ops. Clean
1701 // them up.
1702 vector::BroadcastOp::getCanonicalizationPatterns(patterns, context);
1703 vector::ShapeCastOp::getCanonicalizationPatterns(patterns, context);
1704
1705 if (failed(applyPatternsGreedily(op, std::move(patterns))))
1706 return failure();
1707 }
1708 return success();
1709}
1710
1711//===----------------------------------------------------------------------===//
1712// SPIR-V TypeConverter
1713//===----------------------------------------------------------------------===//
1714
1716 const SPIRVConversionOptions &options)
1717 : targetEnv(targetAttr), options(options) {
1718 // Add conversions. The order matters here: later ones will be tried earlier.
1719
1720 // Allow all SPIR-V dialect specific types. This assumes all builtin types
1721 // adopted in the SPIR-V dialect (i.e., IntegerType, FloatType, VectorType)
1722 // were tried before.
1723 //
1724 // TODO: This assumes that the SPIR-V types are valid to use in the given
1725 // target environment, which should be the case if the whole pipeline is
1726 // driven by the same target environment. Still, we probably still want to
1727 // validate and convert to be safe.
1728 addConversion([](spirv::SPIRVType type) { return type; });
1729
1730 addConversion([this](IndexType /*indexType*/) { return getIndexType(); });
1731
1732 addConversion([this](IntegerType intType) -> std::optional<Type> {
1733 if (auto scalarType = dyn_cast<spirv::ScalarType>(intType))
1734 return convertScalarType(this->targetEnv, this->options, scalarType);
1735 if (intType.getWidth() < 8)
1736 return convertSubByteIntegerType(this->options, intType);
1737 return Type();
1738 });
1739
1740 addConversion([this](FloatType floatType) -> std::optional<Type> {
1741 if (auto scalarType = dyn_cast<spirv::ScalarType>(floatType))
1742 return convertScalarType(this->targetEnv, this->options, scalarType);
1743 if (floatType.getWidth() == 8)
1744 return convert8BitFloatType(this->options, floatType);
1745 return Type();
1746 });
1747
1748 addConversion([this](ComplexType complexType) {
1749 return convertComplexType(this->targetEnv, this->options, complexType);
1750 });
1751
1752 addConversion([this](VectorType vectorType) {
1753 return convertVectorType(this->targetEnv, this->options, vectorType);
1754 });
1755
1756 addConversion([this](TensorType tensorType) {
1757 return convertTensorType(this->targetEnv, this->options, tensorType);
1758 });
1759
1760 addConversion([this](MemRefType memRefType) {
1761 return convertMemrefType(this->targetEnv, this->options, memRefType);
1762 });
1763
1764 // Register some last line of defense casting logic.
1765 addSourceMaterialization(
1766 [this](OpBuilder &builder, Type type, ValueRange inputs, Location loc) {
1767 return castToSourceType(this->targetEnv, builder, type, inputs, loc);
1768 });
1769 addTargetMaterialization([](OpBuilder &builder, Type type, ValueRange inputs,
1770 Location loc) {
1771 auto cast = UnrealizedConversionCastOp::create(builder, loc, type, inputs);
1772 return cast.getResult(0);
1773 });
1774}
1775
1777 return ::getIndexType(getContext(), options);
1778}
1779
1780MLIRContext *SPIRVTypeConverter::getContext() const {
1781 return targetEnv.getAttr().getContext();
1782}
1783
1784bool SPIRVTypeConverter::allows(spirv::Capability capability) const {
1785 return targetEnv.allows(capability);
1786}
1787
1788//===----------------------------------------------------------------------===//
1789// SPIR-V ConversionTarget
1790//===----------------------------------------------------------------------===//
1791
1792std::unique_ptr<SPIRVConversionTarget>
1794 std::unique_ptr<SPIRVConversionTarget> target(
1795 // std::make_unique does not work here because the constructor is private.
1796 new SPIRVConversionTarget(targetAttr));
1797 SPIRVConversionTarget *targetPtr = target.get();
1798 target->addDynamicallyLegalDialect<spirv::SPIRVDialect>(
1799 // We need to capture the raw pointer here because it is stable:
1800 // target will be destroyed once this function is returned.
1801 [targetPtr](Operation *op) { return targetPtr->isLegalOp(op); });
1802 return target;
1803}
1804
1805SPIRVConversionTarget::SPIRVConversionTarget(spirv::TargetEnvAttr targetAttr)
1806 : ConversionTarget(*targetAttr.getContext()), targetEnv(targetAttr) {}
1807
1808bool SPIRVConversionTarget::isLegalOp(Operation *op) {
1809 // Make sure this op is available at the given version. Ops not implementing
1810 // QueryMinVersionInterface/QueryMaxVersionInterface are available to all
1811 // SPIR-V versions.
1812 if (auto minVersionIfx = dyn_cast<spirv::QueryMinVersionInterface>(op)) {
1813 std::optional<spirv::Version> minVersion = minVersionIfx.getMinVersion();
1814 if (minVersion && *minVersion > this->targetEnv.getVersion()) {
1815 LLVM_DEBUG(llvm::dbgs()
1816 << op->getName() << " illegal: requiring min version "
1817 << spirv::stringifyVersion(*minVersion) << "\n");
1818 return false;
1819 }
1820 }
1821 if (auto maxVersionIfx = dyn_cast<spirv::QueryMaxVersionInterface>(op)) {
1822 std::optional<spirv::Version> maxVersion = maxVersionIfx.getMaxVersion();
1823 if (maxVersion && *maxVersion < this->targetEnv.getVersion()) {
1824 LLVM_DEBUG(llvm::dbgs()
1825 << op->getName() << " illegal: requiring max version "
1826 << spirv::stringifyVersion(*maxVersion) << "\n");
1827 return false;
1828 }
1829 }
1830
1831 // Make sure this op's required extensions are allowed to use. Ops not
1832 // implementing QueryExtensionInterface do not require extensions to be
1833 // available.
1834 if (auto extensions = dyn_cast<spirv::QueryExtensionInterface>(op))
1835 if (failed(checkExtensionRequirements(op->getName(), this->targetEnv,
1836 extensions.getExtensions())))
1837 return false;
1838
1839 // Make sure this op's required extensions are allowed to use. Ops not
1840 // implementing QueryCapabilityInterface do not require capabilities to be
1841 // available.
1842 if (auto capabilities = dyn_cast<spirv::QueryCapabilityInterface>(op))
1843 if (failed(checkCapabilityRequirements(op->getName(), this->targetEnv,
1844 capabilities.getCapabilities())))
1845 return false;
1846
1847 SmallVector<Type, 4> valueTypes;
1848 valueTypes.append(op->operand_type_begin(), op->operand_type_end());
1849 valueTypes.append(op->result_type_begin(), op->result_type_end());
1850
1851 // Ensure that all types have been converted to SPIRV types.
1852 if (llvm::any_of(valueTypes,
1853 [](Type t) { return !isa<spirv::SPIRVType>(t); }))
1854 return false;
1855
1856 // Special treatment for global variables, whose type requirements are
1857 // conveyed by type attributes.
1858 if (auto globalVar = dyn_cast<spirv::GlobalVariableOp>(op))
1859 valueTypes.push_back(globalVar.getType());
1860
1861 // Make sure the op's operands/results use types that are allowed by the
1862 // target environment.
1863 SmallVector<ArrayRef<spirv::Extension>, 4> typeExtensions;
1864 SmallVector<ArrayRef<spirv::Capability>, 8> typeCapabilities;
1865 for (Type valueType : valueTypes) {
1866 typeExtensions.clear();
1867 cast<spirv::SPIRVType>(valueType).getExtensions(typeExtensions);
1868 if (failed(checkExtensionRequirements(op->getName(), this->targetEnv,
1869 typeExtensions)))
1870 return false;
1871
1872 typeCapabilities.clear();
1873 cast<spirv::SPIRVType>(valueType).getCapabilities(typeCapabilities);
1874 if (failed(checkCapabilityRequirements(op->getName(), this->targetEnv,
1875 typeCapabilities)))
1876 return false;
1877 }
1878
1879 return true;
1880}
1881
1882//===----------------------------------------------------------------------===//
1883// Public functions for populating patterns
1884//===----------------------------------------------------------------------===//
1885
1887 const SPIRVTypeConverter &typeConverter, RewritePatternSet &patterns) {
1888 patterns.add<FuncOpConversion>(typeConverter, patterns.getContext());
1889}
1890
1892 patterns.add<FuncOpVectorUnroll>(patterns.getContext());
1893}
1894
1896 patterns.add<ReturnOpVectorUnroll>(patterns.getContext());
1897}
return success()
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
b getContext())
*if copies could not be generated due to yet unimplemented cases *copyInPlacementStart and copyOutPlacementStart in copyPlacementBlock *specify the insertion points where the incoming copies and outgoing should be the output argument nBegin is set to its * replacement(set to `begin` if no invalidation happens). Since outgoing *copies could have been inserted at `end`
static llvm::ManagedStatic< PassManagerOptions > options
#define BIT_WIDTH_CASE(BIT_WIDTH)
static std::optional< SmallVector< int64_t > > getTargetShape(const vector::UnrollVectorOptions &options, Operation *op)
Return the target shape for unrolling for the given op.
#define mul(a, b)
#define add(a, b)
Block represents an ordered list of Operations.
Definition Block.h:33
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_range< args_iterator > addArguments(TypeRange types, ArrayRef< Location > locs)
Add one argument to the argument list for each type specified in the list.
Definition Block.cpp:165
OpListType & getOperations()
Definition Block.h:161
Operation & front()
Definition Block.h:177
void eraseArguments(unsigned start, unsigned num)
Erases 'num' arguments from the index 'start'.
Definition Block.cpp:206
This class is a general helper class for creating context-global objects like types,...
Definition Builders.h:51
UnitAttr getUnitAttr()
Definition Builders.cpp:106
IntegerAttr getI32IntegerAttr(int32_t value)
Definition Builders.cpp:208
FloatType getF32Type()
Definition Builders.cpp:51
TypedAttr getZeroAttr(Type type)
Definition Builders.cpp:333
MLIRContext * getContext() const
Definition Builders.h:56
This class allows control over how the GreedyPatternRewriteDriver works.
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
NamedAttribute represents a combination of a name and an Attribute value.
Definition Attributes.h:164
Attribute getValue() const
Return the value of the attribute.
Definition Attributes.h:179
RAII guard to reset the insertion point of the builder when destroyed.
Definition Builders.h:351
This class helps build Operations.
Definition Builders.h:210
static OpBuilder atBlockBegin(Block *block, Listener *listener=nullptr)
Create a builder and set the insertion point to before the first operation in the block but still ins...
Definition Builders.h:243
void setInsertionPointToStart(Block *block)
Sets the insertion point to the start of the specified block.
Definition Builders.h:434
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
void setOperand(unsigned idx, Value value)
Definition Operation.h:376
void setDiscardableAttr(StringAttr name, Attribute value)
Set a discardable attribute by name.
Definition Operation.h:505
operand_type_iterator operand_type_end()
Definition Operation.h:421
Location getLoc()
The source location the operation was defined or derived from.
Definition Operation.h:240
Operation * getParentOp()
Returns the closest surrounding operation that contains this operation or nullptr if this is a top-le...
Definition Operation.h:251
result_type_iterator result_type_end()
Definition Operation.h:452
InFlightDiagnostic emitError(const Twine &message={})
Emit an error about fatal conditions with this operation, reporting up to any diagnostic handlers tha...
result_type_iterator result_type_begin()
Definition Operation.h:451
OperationName getName()
The name of an operation is the key identifier for it.
Definition Operation.h:115
result_type_range getResultTypes()
Definition Operation.h:453
MLIRContext * getContext()
Return the context this operation is associated with.
Definition Operation.h:233
unsigned getNumResults()
Return the number of results held by this operation.
Definition Operation.h:429
operand_type_iterator operand_type_begin()
Definition Operation.h:420
A special type of RewriterBase that coordinates the application of a rewrite pattern on the current I...
Block & front()
Definition Region.h:65
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.
virtual void replaceOp(Operation *op, ValueRange newValues)
Replace the results of the given (original) operation with the specified list of values (replacements...
virtual void eraseOp(Operation *op)
This method erases an operation that is known to have no uses.
void modifyOpInPlace(Operation *root, CallableT &&callable)
This method is a utility wrapper around an in-place modification of an operation.
void inlineRegionBefore(Region &region, Region &parent, Region::iterator before)
Move the blocks that belong to "region" before the given position in another region "parent".
virtual void replaceAllUsesWith(Value from, Value to)
Find uses of from and replace them with to.
static std::unique_ptr< SPIRVConversionTarget > get(spirv::TargetEnvAttr targetAttr)
Creates a SPIR-V conversion target for the given target environment.
Type conversion from builtin types to SPIR-V types for shader interface.
Type getIndexType() const
Gets the SPIR-V correspondence for the standard index type.
const spirv::TargetEnv & getTargetEnv() const
SPIRVTypeConverter(spirv::TargetEnvAttr targetAttr, const SPIRVConversionOptions &options={})
bool allows(spirv::Capability capability) const
Checks if the SPIR-V capability inquired is supported.
A range-style iterator that allows for iterating over the offsets of all potential tiles of size tile...
static StringRef getSymbolAttrName()
Return the name of the attribute used for symbol names.
Definition SymbolTable.h:76
static Operation * getNearestSymbolTable(Operation *from)
Returns the nearest symbol table from a given operation from.
Tensor types represent multi-dimensional arrays, and have two variants: RankedTensorType and Unranked...
Type getElementType() const
Returns the element type of this tensor type.
This class provides an abstraction over the various different ranges of value types.
Definition TypeRange.h:40
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
MLIRContext * getContext() const
Return the MLIRContext in which this type was uniqued.
Definition Types.cpp:35
bool isSignedInteger() const
Return true if this is a signed integer type (with the specified width).
Definition Types.cpp:78
bool isInteger() const
Return true if this is an integer type (with the specified width).
Definition Types.cpp:58
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 provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:389
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
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
static ArrayType get(Type elementType, unsigned elementCount)
static bool isValid(VectorType)
Returns true if the given vector type is valid for the SPIR-V dialect.
static ImageType get(Type elementType, Dim dim, ImageDepthInfo depth=ImageDepthInfo::DepthUnknown, ImageArrayedInfo arrayed=ImageArrayedInfo::NonArrayed, ImageSamplingInfo samplingInfo=ImageSamplingInfo::SingleSampled, ImageSamplerUseInfo samplerUse=ImageSamplerUseInfo::SamplerUnknown, ImageFormat format=ImageFormat::Unknown)
Definition SPIRVTypes.h:148
static PointerType get(Type pointeeType, StorageClass storageClass)
static RuntimeArrayType get(Type elementType)
SmallVectorImpl< ArrayRef< Capability > > CapabilityArrayRefVector
The capability requirements for each type are following the ((Capability::A OR Extension::B) AND (Cap...
Definition SPIRVTypes.h:66
SmallVectorImpl< ArrayRef< Extension > > ExtensionArrayRefVector
The extension requirements for each type are following the ((Extension::A OR Extension::B) AND (Exten...
Definition SPIRVTypes.h:55
static SampledImageType get(Type imageType)
static StructType get(ArrayRef< Type > memberTypes, ArrayRef< OffsetInfo > offsetInfo={}, ArrayRef< MemberDecorationInfo > memberDecorations={}, ArrayRef< StructDecorationInfo > structDecorations={})
Construct a literal StructType with at least one member.
An attribute that specifies the target version, allowed extensions and capabilities,...
A wrapper class around a spirv::TargetEnvAttr to provide query methods for allowed version/capabiliti...
Version getVersion() const
bool allows(Capability) const
Returns true if the given capability is allowed.
TargetEnvAttr getAttr() const
MLIRContext * getContext() const
Returns the MLIRContext.
bool hasElementwiseMappableTraits(Operation *op)
Together, Elementwise, Scalarizable, Vectorizable, and Tensorizable provide an easy way for scalar op...
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:717
Value getBuiltinVariableValue(Operation *op, BuiltIn builtin, Type integerType, OpBuilder &builder, StringRef prefix="__builtin__", StringRef suffix="__")
Returns the value for the given builtin variable.
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,...
Value getOpenCLElementPtr(const SPIRVTypeConverter &typeConverter, MemRefType baseType, Value basePtr, ValueRange indices, Location loc, OpBuilder &builder)
Value getPushConstantValue(Operation *op, unsigned elementCount, unsigned offset, Type integerType, OpBuilder &builder)
Gets the value at the given offset of the push constant storage with a total of elementCount integerT...
std::optional< SmallVector< int64_t > > getNativeVectorShape(Operation *op)
LinearizedIndexNoWrapFlags getLinearizedIndexNoWrapFlags(const TargetEnv &targetEnv, ArrayRef< int64_t > shape, ArrayRef< int64_t > strides, int64_t offset, Type integerType)
Returns no-wrap guarantees for an in-bounds index into the static layout described by shape,...
LogicalResult unrollVectorsInFuncBodies(Operation *op)
Value getVulkanElementPtr(const SPIRVTypeConverter &typeConverter, MemRefType baseType, Value basePtr, ValueRange indices, Location loc, OpBuilder &builder)
SmallVector< int64_t > getNativeVectorShapeImpl(vector::ReductionOp op)
std::string getDecorationString(Decoration decoration)
Converts a SPIR-V Decoration enum value to its snake_case string representation for use in MLIR attri...
int getComputeVectorSize(int64_t size)
LogicalResult unrollVectorsInSignatures(Operation *op)
Value linearizeIndex(ValueRange indices, ArrayRef< int64_t > strides, int64_t offset, Type integerType, Location loc, OpBuilder &builder, LinearizedIndexNoWrapFlags noWrapFlags={})
Generates IR to perform index linearization with the given indices and their corresponding strides,...
void populateVectorShapeCastLoweringPatterns(RewritePatternSet &patterns, PatternBenefit benefit=1)
Populate the pattern set with the following patterns:
void populateVectorTransposeLoweringPatterns(RewritePatternSet &patterns, VectorTransposeLowering vectorTransposeLowering, PatternBenefit benefit=1)
Populate the pattern set with the following patterns:
Include the generated interface declarations.
void populateFuncOpVectorRewritePatterns(RewritePatternSet &patterns)
void populateReturnOpVectorRewritePatterns(RewritePatternSet &patterns)
@ Packed
Sub-byte values are tightly packed without any padding, e.g., 4xi2 -> i8.
LogicalResult applyPatternsGreedily(Region &region, const FrozenRewritePatternSet &patterns, GreedyRewriteConfig config=GreedyRewriteConfig(), bool *changed=nullptr)
Rewrite ops in the given region, which must be isolated from above, by repeatedly applying the highes...
InFlightDiagnostic emitError(Location loc)
Utility method to emit an error message using this location.
void populateBuiltinFuncToSPIRVPatterns(const SPIRVTypeConverter &typeConverter, RewritePatternSet &patterns)
Appends to a pattern list additional patterns for translating the builtin func op to the SPIR-V diale...
llvm::TypeSwitch< T, ResultT > TypeSwitch
Definition LLVM.h:139
@ ExistingOps
Only pre-existing ops are processed.
std::optional< SmallVector< int64_t > > computeShapeRatio(ArrayRef< int64_t > shape, ArrayRef< int64_t > subShape)
Return the multi-dimensional integral ratio of subShape to the trailing dimensions of shape.
OpRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting against an...
No-wrap guarantees proven for a linearized index calculation.
Options that control the vector unrolling.
UnrollVectorOptions & setNativeShapeFn(NativeShapeFnType fn)