MLIR 23.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/STLExtras.h"
32#include "llvm/ADT/SmallVector.h"
33#include "llvm/ADT/StringExtras.h"
34#include "llvm/Support/Debug.h"
35#include "llvm/Support/MathExtras.h"
36
37#include <optional>
38
39#define DEBUG_TYPE "mlir-spirv-conversion"
40
41using namespace mlir;
42
43namespace {
44
45//===----------------------------------------------------------------------===//
46// Utility functions
47//===----------------------------------------------------------------------===//
48
49static std::optional<SmallVector<int64_t>> getTargetShape(VectorType vecType) {
50 LLVM_DEBUG(llvm::dbgs() << "Get target shape\n");
51 if (vecType.isScalable()) {
52 LLVM_DEBUG(llvm::dbgs()
53 << "--scalable vectors are not supported -> BAIL\n");
54 return std::nullopt;
55 }
56 if (vecType.getRank() == 0) {
57 LLVM_DEBUG(llvm::dbgs() << "--0-D vectors are not supported -> BAIL\n");
58 return std::nullopt;
59 }
60 SmallVector<int64_t> unrollShape = llvm::to_vector<4>(vecType.getShape());
61 std::optional<SmallVector<int64_t>> targetShape = SmallVector<int64_t>(
62 1, mlir::spirv::getComputeVectorSize(vecType.getShape().back()));
63 if (!targetShape) {
64 LLVM_DEBUG(llvm::dbgs() << "--no unrolling target shape defined\n");
65 return std::nullopt;
66 }
67 auto maybeShapeRatio = computeShapeRatio(unrollShape, *targetShape);
68 if (!maybeShapeRatio) {
69 LLVM_DEBUG(llvm::dbgs()
70 << "--could not compute integral shape ratio -> BAIL\n");
71 return std::nullopt;
72 }
73 if (llvm::all_of(*maybeShapeRatio, [](int64_t v) { return v == 1; })) {
74 LLVM_DEBUG(llvm::dbgs() << "--no unrolling needed -> SKIP\n");
75 return std::nullopt;
76 }
77 LLVM_DEBUG(llvm::dbgs()
78 << "--found an integral shape ratio to unroll to -> SUCCESS\n");
79 return targetShape;
80}
81
82/// Checks that `candidates` extension requirements are possible to be satisfied
83/// with the given `targetEnv`.
84///
85/// `candidates` is a vector of vector for extension requirements following
86/// ((Extension::A OR Extension::B) AND (Extension::C OR Extension::D))
87/// convention.
88template <typename LabelT>
89static LogicalResult checkExtensionRequirements(
90 LabelT label, const spirv::TargetEnv &targetEnv,
92 for (const auto &ors : candidates) {
93 if (targetEnv.allows(ors))
94 continue;
95
96 LLVM_DEBUG({
97 SmallVector<StringRef> extStrings;
98 for (spirv::Extension ext : ors)
99 extStrings.push_back(spirv::stringifyExtension(ext));
100
101 llvm::dbgs() << label << " illegal: requires at least one extension in ["
102 << llvm::join(extStrings, ", ")
103 << "] but none allowed in target environment\n";
104 });
105 return failure();
106 }
107 return success();
108}
109
110/// Checks that `candidates`capability requirements are possible to be satisfied
111/// with the given `isAllowedFn`.
112///
113/// `candidates` is a vector of vector for capability requirements following
114/// ((Capability::A OR Capability::B) AND (Capability::C OR Capability::D))
115/// convention.
116template <typename LabelT>
117static LogicalResult checkCapabilityRequirements(
118 LabelT label, const spirv::TargetEnv &targetEnv,
120 for (const auto &ors : candidates) {
121 if (targetEnv.allows(ors))
122 continue;
123
124 LLVM_DEBUG({
125 SmallVector<StringRef> capStrings;
126 for (spirv::Capability cap : ors)
127 capStrings.push_back(spirv::stringifyCapability(cap));
128
129 llvm::dbgs() << label << " illegal: requires at least one capability in ["
130 << llvm::join(capStrings, ", ")
131 << "] but none allowed in target environment\n";
132 });
133 return failure();
134 }
135 return success();
136}
137
138/// Returns true if the given `storageClass` needs explicit layout when used in
139/// Shader environments.
140static bool needsExplicitLayout(spirv::StorageClass storageClass) {
141 switch (storageClass) {
142 case spirv::StorageClass::PhysicalStorageBuffer:
143 case spirv::StorageClass::PushConstant:
144 case spirv::StorageClass::StorageBuffer:
145 case spirv::StorageClass::Uniform:
146 return true;
147 default:
148 return false;
149 }
150}
151
152/// Wraps the given `elementType` in a struct and gets the pointer to the
153/// struct. This is used to satisfy Vulkan interface requirements.
155wrapInStructAndGetPointer(Type elementType, spirv::StorageClass storageClass) {
156 auto structType = needsExplicitLayout(storageClass)
157 ? spirv::StructType::get(elementType, /*offsetInfo=*/0)
158 : spirv::StructType::get(elementType);
159 return spirv::PointerType::get(structType, storageClass);
160}
161
162//===----------------------------------------------------------------------===//
163// Type Conversion
164//===----------------------------------------------------------------------===//
165
166static spirv::ScalarType getIndexType(MLIRContext *ctx,
168 return cast<spirv::ScalarType>(
169 IntegerType::get(ctx, options.use64bitIndex ? 64 : 32));
170}
171
172// TODO: This is a utility function that should probably be exposed by the
173// SPIR-V dialect. Keeping it local till the use case arises.
174static std::optional<int64_t>
175getTypeNumBytes(const SPIRVConversionOptions &options, Type type) {
176 if (isa<spirv::ScalarType>(type)) {
177 auto bitWidth = type.getIntOrFloatBitWidth();
178 // According to the SPIR-V spec:
179 // "There is no physical size or bit pattern defined for values with boolean
180 // type. If they are stored (in conjunction with OpVariable), they can only
181 // be used with logical addressing operations, not physical, and only with
182 // non-externally visible shader Storage Classes: Workgroup, CrossWorkgroup,
183 // Private, Function, Input, and Output."
184 if (bitWidth == 1)
185 return std::nullopt;
186 return bitWidth / 8;
187 }
188
189 // Handle 8-bit floats.
190 if (options.emulateUnsupportedFloatTypes && isa<FloatType>(type)) {
191 auto bitWidth = type.getIntOrFloatBitWidth();
192 if (bitWidth == 8)
193 return bitWidth / 8;
194 return std::nullopt;
195 }
196
197 if (auto complexType = dyn_cast<ComplexType>(type)) {
198 auto elementSize = getTypeNumBytes(options, complexType.getElementType());
199 if (!elementSize)
200 return std::nullopt;
201 return 2 * *elementSize;
202 }
203
204 if (auto vecType = dyn_cast<VectorType>(type)) {
205 auto elementSize = getTypeNumBytes(options, vecType.getElementType());
206 if (!elementSize)
207 return std::nullopt;
208 return vecType.getNumElements() * *elementSize;
209 }
210
211 if (auto memRefType = dyn_cast<MemRefType>(type)) {
212 // TODO: Layout should also be controlled by the ABI attributes. For now
213 // using the layout from MemRef.
214 int64_t offset;
216 if (!memRefType.hasStaticShape() ||
217 failed(memRefType.getStridesAndOffset(strides, offset)))
218 return std::nullopt;
219
220 // To get the size of the memref object in memory, the total size is the
221 // max(stride * dimension-size) computed for all dimensions times the size
222 // of the element.
223 auto elementSize = getTypeNumBytes(options, memRefType.getElementType());
224 if (!elementSize)
225 return std::nullopt;
226
227 if (memRefType.getRank() == 0)
228 return elementSize;
229
230 auto dims = memRefType.getShape();
231 if (llvm::is_contained(dims, ShapedType::kDynamic) ||
232 ShapedType::isDynamic(offset) ||
233 llvm::is_contained(strides, ShapedType::kDynamic))
234 return std::nullopt;
235
236 int64_t memrefSize = -1;
237 for (const auto &shape : enumerate(dims))
238 memrefSize = std::max(memrefSize, shape.value() * strides[shape.index()]);
239
240 return (offset + memrefSize) * *elementSize;
241 }
242
243 if (auto tensorType = dyn_cast<TensorType>(type)) {
244 if (!tensorType.hasStaticShape())
245 return std::nullopt;
246
247 auto elementSize = getTypeNumBytes(options, tensorType.getElementType());
248 if (!elementSize)
249 return std::nullopt;
250
251 int64_t size = *elementSize;
252 for (auto shape : tensorType.getShape())
253 size *= shape;
254
255 return size;
256 }
257
258 // TODO: Add size computation for other types.
259 return std::nullopt;
260}
261
262/// Converts a scalar `type` to a suitable type under the given `targetEnv`.
263static Type
264convertScalarType(const spirv::TargetEnv &targetEnv,
266 std::optional<spirv::StorageClass> storageClass = {}) {
267 // Get extension and capability requirements for the given type.
270 type.getExtensions(extensions, storageClass);
271 type.getCapabilities(capabilities, storageClass);
272
273 // If all requirements are met, then we can accept this type as-is.
274 if (succeeded(checkCapabilityRequirements(type, targetEnv, capabilities)) &&
275 succeeded(checkExtensionRequirements(type, targetEnv, extensions)))
276 return type;
277
278 // Otherwise we need to adjust the type, which really means adjusting the
279 // bitwidth given this is a scalar type.
280 if (!options.emulateLT32BitScalarTypes)
281 return nullptr;
282
283 // We only emulate narrower scalar types here and do not truncate results.
284 if (type.getIntOrFloatBitWidth() > 32) {
285 LLVM_DEBUG(llvm::dbgs()
286 << type
287 << " not converted to 32-bit for SPIR-V to avoid truncation\n");
288 return nullptr;
289 }
290
291 if (auto floatType = dyn_cast<FloatType>(type)) {
292 LLVM_DEBUG(llvm::dbgs() << type << " converted to 32-bit for SPIR-V\n");
293 return Builder(targetEnv.getContext()).getF32Type();
294 }
295
296 auto intType = cast<IntegerType>(type);
297 LLVM_DEBUG(llvm::dbgs() << type << " converted to 32-bit for SPIR-V\n");
298 return IntegerType::get(targetEnv.getContext(), /*width=*/32,
299 intType.getSignedness());
300}
301
302/// Converts a sub-byte integer `type` to i32 regardless of target environment.
303/// Returns a nullptr for unsupported integer types, including non sub-byte
304/// types.
305///
306/// Note that we don't recognize sub-byte types in `spirv::ScalarType` and use
307/// the above given that these sub-byte types are not supported at all in
308/// SPIR-V; there are no compute/storage capability for them like other
309/// supported integer types.
310static Type convertSubByteIntegerType(const SPIRVConversionOptions &options,
311 IntegerType type) {
312 if (type.getWidth() > 8) {
313 LLVM_DEBUG(llvm::dbgs() << "not a subbyte type\n");
314 return nullptr;
315 }
316 if (options.subByteTypeStorage != SPIRVSubByteTypeStorage::Packed) {
317 LLVM_DEBUG(llvm::dbgs() << "unsupported sub-byte storage kind\n");
318 return nullptr;
319 }
320
321 if (!llvm::isPowerOf2_32(type.getWidth())) {
322 LLVM_DEBUG(llvm::dbgs()
323 << "unsupported non-power-of-two bitwidth in sub-byte" << type
324 << "\n");
325 return nullptr;
326 }
327
328 LLVM_DEBUG(llvm::dbgs() << type << " converted to 32-bit for SPIR-V\n");
329 return IntegerType::get(type.getContext(), /*width=*/32,
330 type.getSignedness());
331}
332
333/// Converts 8-bit float types to integer types with the same bit width.
334/// Returns a nullptr for unsupported 8-bit float types.
335static Type convert8BitFloatType(const SPIRVConversionOptions &options,
336 FloatType type) {
337 if (!options.emulateUnsupportedFloatTypes)
338 return nullptr;
339 // F8 types are converted to integer types with the same bit width.
340 if (isa<Float8E5M2Type, Float8E4M3Type, Float8E4M3FNType, Float8E5M2FNUZType,
341 Float8E4M3FNUZType, Float8E4M3B11FNUZType, Float8E3M4Type,
342 Float8E8M0FNUType>(type))
343 return IntegerType::get(type.getContext(), type.getWidth());
344 LLVM_DEBUG(llvm::dbgs() << "unsupported 8-bit float type: " << type << "\n");
345 return nullptr;
346}
347
348/// Returns a type with the same shape but with any 8-bit float element type
349/// converted to the same bit width integer type. This is a noop when the
350/// element type is not the 8-bit float type or emulation flag is set to false.
351static ShapedType
352convertShaped8BitFloatType(ShapedType type,
354 if (!options.emulateUnsupportedFloatTypes)
355 return type;
356 Type srcElementType = type.getElementType();
357 Type convertedElementType = nullptr;
358 // F8 types are converted to integer types with the same bit width.
359 if (isa<Float8E5M2Type, Float8E4M3Type, Float8E4M3FNType, Float8E5M2FNUZType,
360 Float8E4M3FNUZType, Float8E4M3B11FNUZType, Float8E3M4Type,
361 Float8E8M0FNUType>(srcElementType))
362 convertedElementType = IntegerType::get(
363 type.getContext(), srcElementType.getIntOrFloatBitWidth());
364
365 if (!convertedElementType)
366 return type;
367
368 return type.clone(convertedElementType);
369}
370
371/// Returns a type with the same shape but with any index element type converted
372/// to the matching integer type. This is a noop when the element type is not
373/// the index type.
374static ShapedType
375convertIndexElementType(ShapedType type,
377 Type indexType = dyn_cast<IndexType>(type.getElementType());
378 if (!indexType)
379 return type;
380
381 return type.clone(getIndexType(type.getContext(), options));
382}
383
384/// Converts a vector `type` to a suitable type under the given `targetEnv`.
385static Type
386convertVectorType(const spirv::TargetEnv &targetEnv,
387 const SPIRVConversionOptions &options, VectorType type,
388 std::optional<spirv::StorageClass> storageClass = {}) {
389 type = cast<VectorType>(convertIndexElementType(type, options));
390 type = cast<VectorType>(convertShaped8BitFloatType(type, options));
391 auto scalarType = dyn_cast_or_null<spirv::ScalarType>(type.getElementType());
392 if (!scalarType) {
393 // If this is not a spec allowed scalar type, try to handle sub-byte integer
394 // types.
395 auto intType = dyn_cast<IntegerType>(type.getElementType());
396 if (!intType) {
397 LLVM_DEBUG(llvm::dbgs()
398 << type
399 << " illegal: cannot convert non-scalar element type\n");
400 return nullptr;
401 }
402
403 Type elementType = convertSubByteIntegerType(options, intType);
404 if (!elementType)
405 return nullptr;
406
407 if (type.getRank() <= 1 && type.getNumElements() == 1)
408 return elementType;
409
410 if (type.getNumElements() > 4) {
411 LLVM_DEBUG(llvm::dbgs()
412 << type << " illegal: > 4-element unimplemented\n");
413 return nullptr;
414 }
415
416 return VectorType::get(type.getShape(), elementType);
417 }
418
419 if (type.getRank() <= 1 && type.getNumElements() == 1)
420 return convertScalarType(targetEnv, options, scalarType, storageClass);
421
423 LLVM_DEBUG(llvm::dbgs()
424 << type << " illegal: not a valid composite type\n");
425 return nullptr;
426 }
427
428 // Get extension and capability requirements for the given type.
431 cast<spirv::CompositeType>(type).getExtensions(extensions, storageClass);
432 cast<spirv::CompositeType>(type).getCapabilities(capabilities, storageClass);
433
434 // If all requirements are met, then we can accept this type as-is.
435 if (succeeded(checkCapabilityRequirements(type, targetEnv, capabilities)) &&
436 succeeded(checkExtensionRequirements(type, targetEnv, extensions)))
437 return type;
438
439 auto elementType =
440 convertScalarType(targetEnv, options, scalarType, storageClass);
441 if (elementType)
442 return VectorType::get(type.getShape(), elementType);
443 return nullptr;
444}
445
446static Type
447convertComplexType(const spirv::TargetEnv &targetEnv,
448 const SPIRVConversionOptions &options, ComplexType type,
449 std::optional<spirv::StorageClass> storageClass = {}) {
450 auto scalarType = dyn_cast_or_null<spirv::ScalarType>(type.getElementType());
451 if (!scalarType) {
452 LLVM_DEBUG(llvm::dbgs()
453 << type << " illegal: cannot convert non-scalar element type\n");
454 return nullptr;
455 }
456
457 auto elementType =
458 convertScalarType(targetEnv, options, scalarType, storageClass);
459 if (!elementType)
460 return nullptr;
461 if (elementType != type.getElementType()) {
462 LLVM_DEBUG(llvm::dbgs()
463 << type << " illegal: complex type emulation unsupported\n");
464 return nullptr;
465 }
466
467 return VectorType::get(2, elementType);
468}
469
470/// Converts a tensor `type` to a suitable type under the given `targetEnv`.
471///
472/// Note that this is mainly for lowering constant tensors. In SPIR-V one can
473/// create composite constants with OpConstantComposite to embed relative large
474/// constant values and use OpCompositeExtract and OpCompositeInsert to
475/// manipulate, like what we do for vectors.
476static Type convertTensorType(const spirv::TargetEnv &targetEnv,
478 TensorType type) {
479 // TODO: Handle dynamic shapes.
480 if (!type.hasStaticShape()) {
481 LLVM_DEBUG(llvm::dbgs()
482 << type << " illegal: dynamic shape unimplemented\n");
483 return nullptr;
484 }
485
486 type = cast<TensorType>(convertIndexElementType(type, options));
487 type = cast<TensorType>(convertShaped8BitFloatType(type, options));
488 auto scalarType = dyn_cast_or_null<spirv::ScalarType>(type.getElementType());
489 if (!scalarType) {
490 LLVM_DEBUG(llvm::dbgs()
491 << type << " illegal: cannot convert non-scalar element type\n");
492 return nullptr;
493 }
494
495 std::optional<int64_t> scalarSize = getTypeNumBytes(options, scalarType);
496 std::optional<int64_t> tensorSize = getTypeNumBytes(options, type);
497 if (!scalarSize || !tensorSize) {
498 LLVM_DEBUG(llvm::dbgs()
499 << type << " illegal: cannot deduce element count\n");
500 return nullptr;
501 }
502
503 int64_t arrayElemCount = *tensorSize / *scalarSize;
504 if (arrayElemCount == 0) {
505 LLVM_DEBUG(llvm::dbgs()
506 << type << " illegal: cannot handle zero-element tensors\n");
507 return nullptr;
508 }
509 if (arrayElemCount > std::numeric_limits<unsigned>::max()) {
510 LLVM_DEBUG(llvm::dbgs()
511 << type << " illegal: cannot fit tensor into target type\n");
512 return nullptr;
513 }
514
515 Type arrayElemType = convertScalarType(targetEnv, options, scalarType);
516 if (!arrayElemType)
517 return nullptr;
518 std::optional<int64_t> arrayElemSize =
519 getTypeNumBytes(options, arrayElemType);
520 if (!arrayElemSize) {
521 LLVM_DEBUG(llvm::dbgs()
522 << type << " illegal: cannot deduce converted element size\n");
523 return nullptr;
524 }
525
526 return spirv::ArrayType::get(arrayElemType, arrayElemCount);
527}
528
529static Type convertBoolMemrefType(const spirv::TargetEnv &targetEnv,
531 MemRefType type,
532 spirv::StorageClass storageClass) {
533 unsigned numBoolBits = options.boolNumBits;
534 if (numBoolBits != 8) {
535 LLVM_DEBUG(llvm::dbgs()
536 << "using non-8-bit storage for bool types unimplemented");
537 return nullptr;
538 }
539 auto elementType = dyn_cast<spirv::ScalarType>(
540 IntegerType::get(type.getContext(), numBoolBits));
541 if (!elementType)
542 return nullptr;
543 Type arrayElemType =
544 convertScalarType(targetEnv, options, elementType, storageClass);
545 if (!arrayElemType)
546 return nullptr;
547 std::optional<int64_t> arrayElemSize =
548 getTypeNumBytes(options, arrayElemType);
549 if (!arrayElemSize) {
550 LLVM_DEBUG(llvm::dbgs()
551 << type << " illegal: cannot deduce converted element size\n");
552 return nullptr;
553 }
554
555 if (!type.hasStaticShape()) {
556 // For OpenCL Kernel, dynamic shaped memrefs convert into a pointer pointing
557 // to the element.
558 if (targetEnv.allows(spirv::Capability::Kernel))
559 return spirv::PointerType::get(arrayElemType, storageClass);
560 int64_t stride = needsExplicitLayout(storageClass) ? *arrayElemSize : 0;
561 auto arrayType = spirv::RuntimeArrayType::get(arrayElemType, stride);
562 // For Vulkan we need extra wrapping struct and array to satisfy interface
563 // needs.
564 return wrapInStructAndGetPointer(arrayType, storageClass);
565 }
566
567 if (type.getNumElements() == 0) {
568 LLVM_DEBUG(llvm::dbgs()
569 << type << " illegal: zero-element memrefs are not supported\n");
570 return nullptr;
571 }
572
573 int64_t memrefSize = llvm::divideCeil(type.getNumElements() * numBoolBits, 8);
574 int64_t arrayElemCount = llvm::divideCeil(memrefSize, *arrayElemSize);
575 int64_t stride = needsExplicitLayout(storageClass) ? *arrayElemSize : 0;
576 auto arrayType = spirv::ArrayType::get(arrayElemType, arrayElemCount, stride);
577 if (targetEnv.allows(spirv::Capability::Kernel))
578 return spirv::PointerType::get(arrayType, storageClass);
579 return wrapInStructAndGetPointer(arrayType, storageClass);
580}
581
582static Type convertSubByteMemrefType(const spirv::TargetEnv &targetEnv,
584 MemRefType type,
585 spirv::StorageClass storageClass) {
586 IntegerType elementType = cast<IntegerType>(type.getElementType());
587 Type arrayElemType = convertSubByteIntegerType(options, elementType);
588 if (!arrayElemType)
589 return nullptr;
590 int64_t arrayElemSize = *getTypeNumBytes(options, arrayElemType);
591
592 if (!type.hasStaticShape()) {
593 // For OpenCL Kernel, dynamic shaped memrefs convert into a pointer pointing
594 // to the element.
595 if (targetEnv.allows(spirv::Capability::Kernel))
596 return spirv::PointerType::get(arrayElemType, storageClass);
597 int64_t stride = needsExplicitLayout(storageClass) ? arrayElemSize : 0;
598 auto arrayType = spirv::RuntimeArrayType::get(arrayElemType, stride);
599 // For Vulkan we need extra wrapping struct and array to satisfy interface
600 // needs.
601 return wrapInStructAndGetPointer(arrayType, storageClass);
602 }
603
604 if (type.getNumElements() == 0) {
605 LLVM_DEBUG(llvm::dbgs()
606 << type << " illegal: zero-element memrefs are not supported\n");
607 return nullptr;
608 }
609
610 int64_t memrefSize =
611 llvm::divideCeil(type.getNumElements() * elementType.getWidth(), 8);
612 int64_t arrayElemCount = llvm::divideCeil(memrefSize, arrayElemSize);
613 int64_t stride = needsExplicitLayout(storageClass) ? arrayElemSize : 0;
614 auto arrayType = spirv::ArrayType::get(arrayElemType, arrayElemCount, stride);
615 if (targetEnv.allows(spirv::Capability::Kernel))
616 return spirv::PointerType::get(arrayType, storageClass);
617 return wrapInStructAndGetPointer(arrayType, storageClass);
618}
619
620static spirv::Dim convertRank(int64_t rank) {
621 switch (rank) {
622 case 1:
623 return spirv::Dim::Dim1D;
624 case 2:
625 return spirv::Dim::Dim2D;
626 case 3:
627 return spirv::Dim::Dim3D;
628 default:
629 llvm_unreachable("Invalid memref rank!");
630 }
631}
632
633static spirv::ImageFormat getImageFormat(Type elementType) {
634 return TypeSwitch<Type, spirv::ImageFormat>(elementType)
635 .Case([](Float16Type) { return spirv::ImageFormat::R16f; })
636 .Case([](Float32Type) { return spirv::ImageFormat::R32f; })
637 .Case([](IntegerType intType) {
638 auto const isSigned = intType.isSigned() || intType.isSignless();
639#define BIT_WIDTH_CASE(BIT_WIDTH) \
640 case BIT_WIDTH: \
641 return isSigned ? spirv::ImageFormat::R##BIT_WIDTH##i \
642 : spirv::ImageFormat::R##BIT_WIDTH##ui
643
644 switch (intType.getWidth()) {
645 BIT_WIDTH_CASE(16);
646 BIT_WIDTH_CASE(32);
647 default:
648 llvm_unreachable("Unhandled integer type!");
649 }
650 })
651 .DefaultUnreachable("Unhandled element type!");
652#undef BIT_WIDTH_CASE
653}
654
655static Type convertMemrefType(const spirv::TargetEnv &targetEnv,
657 MemRefType type) {
658 auto attr = dyn_cast_or_null<spirv::StorageClassAttr>(type.getMemorySpace());
659 if (!attr) {
660 LLVM_DEBUG(
661 llvm::dbgs()
662 << type
663 << " illegal: expected memory space to be a SPIR-V storage class "
664 "attribute; please use MemorySpaceToStorageClassConverter to map "
665 "numeric memory spaces beforehand\n");
666 return nullptr;
667 }
668 spirv::StorageClass storageClass = attr.getValue();
669
670 // Images are a special case since they are an opaque type from which elements
671 // may be accessed via image specific ops or directly through a texture
672 // pointer.
673 if (storageClass == spirv::StorageClass::Image) {
674 const int64_t rank = type.getRank();
675 if (rank < 1 || rank > 3) {
676 LLVM_DEBUG(llvm::dbgs()
677 << type << " illegal: cannot lower memref of rank " << rank
678 << " to a SPIR-V Image\n");
679 return nullptr;
680 }
681
682 // Note that we currently only support lowering to single element texels
683 // e.g. R32f.
684 auto elementType = type.getElementType();
685 if (!isa<spirv::ScalarType>(elementType)) {
686 LLVM_DEBUG(llvm::dbgs() << type << " illegal: cannot lower memref of "
687 << elementType << " to a SPIR-V Image\n");
688 return nullptr;
689 }
690
691 // Currently every memref in the image storage class is converted to a
692 // sampled image so we can hardcode the NeedSampler field. Future work
693 // will generalize this to support regular non-sampled images.
694 auto spvImageType = spirv::ImageType::get(
695 elementType, convertRank(rank), spirv::ImageDepthInfo::DepthUnknown,
696 spirv::ImageArrayedInfo::NonArrayed,
697 spirv::ImageSamplingInfo::SingleSampled,
698 spirv::ImageSamplerUseInfo::NeedSampler, getImageFormat(elementType));
699 auto spvSampledImageType = spirv::SampledImageType::get(spvImageType);
700 auto imagePtrType = spirv::PointerType::get(
701 spvSampledImageType, spirv::StorageClass::UniformConstant);
702 return imagePtrType;
703 }
704
705 if (isa<IntegerType>(type.getElementType())) {
706 if (type.getElementTypeBitWidth() == 1)
707 return convertBoolMemrefType(targetEnv, options, type, storageClass);
708 if (type.getElementTypeBitWidth() < 8)
709 return convertSubByteMemrefType(targetEnv, options, type, storageClass);
710 }
711
712 Type arrayElemType;
713 Type elementType = type.getElementType();
714 if (auto vecType = dyn_cast<VectorType>(elementType)) {
715 arrayElemType =
716 convertVectorType(targetEnv, options, vecType, storageClass);
717 } else if (auto complexType = dyn_cast<ComplexType>(elementType)) {
718 arrayElemType =
719 convertComplexType(targetEnv, options, complexType, storageClass);
720 } else if (auto scalarType = dyn_cast<spirv::ScalarType>(elementType)) {
721 arrayElemType =
722 convertScalarType(targetEnv, options, scalarType, storageClass);
723 } else if (auto indexType = dyn_cast<IndexType>(elementType)) {
724 type = cast<MemRefType>(convertIndexElementType(type, options));
725 arrayElemType = type.getElementType();
726 } else if (auto floatType = dyn_cast<FloatType>(elementType)) {
727 // Hnadle 8 bit float types.
728 type = cast<MemRefType>(convertShaped8BitFloatType(type, options));
729 arrayElemType = type.getElementType();
730 } else {
731 LLVM_DEBUG(
732 llvm::dbgs()
733 << type
734 << " unhandled: can only convert scalar or vector element type\n");
735 return nullptr;
736 }
737 if (!arrayElemType)
738 return nullptr;
739
740 std::optional<int64_t> arrayElemSize =
741 getTypeNumBytes(options, arrayElemType);
742 if (!arrayElemSize) {
743 LLVM_DEBUG(llvm::dbgs()
744 << type << " illegal: cannot deduce converted element size\n");
745 return nullptr;
746 }
747
748 if (!type.hasStaticShape()) {
749 // For OpenCL Kernel, dynamic shaped memrefs convert into a pointer pointing
750 // to the element.
751 if (targetEnv.allows(spirv::Capability::Kernel))
752 return spirv::PointerType::get(arrayElemType, storageClass);
753 int64_t stride = needsExplicitLayout(storageClass) ? *arrayElemSize : 0;
754 auto arrayType = spirv::RuntimeArrayType::get(arrayElemType, stride);
755 // For Vulkan we need extra wrapping struct and array to satisfy interface
756 // needs.
757 return wrapInStructAndGetPointer(arrayType, storageClass);
758 }
759
760 std::optional<int64_t> memrefSize = getTypeNumBytes(options, type);
761 if (!memrefSize) {
762 LLVM_DEBUG(llvm::dbgs()
763 << type << " illegal: cannot deduce element count\n");
764 return nullptr;
765 }
766
767 if (*memrefSize == 0) {
768 LLVM_DEBUG(llvm::dbgs()
769 << type << " illegal: zero-element memrefs are not supported\n");
770 return nullptr;
771 }
772
773 int64_t arrayElemCount = llvm::divideCeil(*memrefSize, *arrayElemSize);
774 int64_t stride = needsExplicitLayout(storageClass) ? *arrayElemSize : 0;
775 auto arrayType = spirv::ArrayType::get(arrayElemType, arrayElemCount, stride);
776 if (targetEnv.allows(spirv::Capability::Kernel))
777 return spirv::PointerType::get(arrayType, storageClass);
778 return wrapInStructAndGetPointer(arrayType, storageClass);
779}
780
781//===----------------------------------------------------------------------===//
782// Type casting materialization
783//===----------------------------------------------------------------------===//
784
785/// Converts the given `inputs` to the original source `type` considering the
786/// `targetEnv`'s capabilities.
787///
788/// This function is meant to be used for source materialization in type
789/// converters. When the type converter needs to materialize a cast op back
790/// to some original source type, we need to check whether the original source
791/// type is supported in the target environment. If so, we can insert legal
792/// SPIR-V cast ops accordingly.
793///
794/// Note that in SPIR-V the capabilities for storage and compute are separate.
795/// This function is meant to handle the **compute** side; so it does not
796/// involve storage classes in its logic. The storage side is expected to be
797/// handled by MemRef conversion logic.
798static Value castToSourceType(const spirv::TargetEnv &targetEnv,
799 OpBuilder &builder, Type type, ValueRange inputs,
800 Location loc) {
801 // We can only cast one value in SPIR-V.
802 if (inputs.size() != 1) {
803 auto castOp =
804 UnrealizedConversionCastOp::create(builder, loc, type, inputs);
805 return castOp.getResult(0);
806 }
807 Value input = inputs.front();
808
809 // Only support integer types for now. Floating point types to be implemented.
810 if (!isa<IntegerType>(type)) {
811 auto castOp =
812 UnrealizedConversionCastOp::create(builder, loc, type, inputs);
813 return castOp.getResult(0);
814 }
815 auto inputType = cast<IntegerType>(input.getType());
816
817 auto scalarType = dyn_cast<spirv::ScalarType>(type);
818 if (!scalarType) {
819 auto castOp =
820 UnrealizedConversionCastOp::create(builder, loc, type, inputs);
821 return castOp.getResult(0);
822 }
823
824 // Only support source type with a smaller bitwidth. This would mean we are
825 // truncating to go back so we don't need to worry about the signedness.
826 // For extension, we cannot have enough signal here to decide which op to use.
827 if (inputType.getIntOrFloatBitWidth() < scalarType.getIntOrFloatBitWidth()) {
828 auto castOp =
829 UnrealizedConversionCastOp::create(builder, loc, type, inputs);
830 return castOp.getResult(0);
831 }
832
833 // Boolean values would need to use different ops than normal integer values.
834 if (type.isInteger(1)) {
835 Value one = spirv::ConstantOp::getOne(inputType, loc, builder);
836 return spirv::IEqualOp::create(builder, loc, input, one);
837 }
838
839 // Check that the source integer type is supported by the environment.
842 scalarType.getExtensions(exts);
843 scalarType.getCapabilities(caps);
844 if (failed(checkCapabilityRequirements(type, targetEnv, caps)) ||
845 failed(checkExtensionRequirements(type, targetEnv, exts))) {
846 auto castOp =
847 UnrealizedConversionCastOp::create(builder, loc, type, inputs);
848 return castOp.getResult(0);
849 }
850
851 // We've already made sure this is truncating previously, so we don't need to
852 // care about signedness here. Still try to use a corresponding op for better
853 // consistency though.
854 if (type.isSignedInteger()) {
855 return spirv::SConvertOp::create(builder, loc, type, input);
856 }
857 return spirv::UConvertOp::create(builder, loc, type, input);
858}
859
860//===----------------------------------------------------------------------===//
861// Builtin Variables
862//===----------------------------------------------------------------------===//
863
864static spirv::GlobalVariableOp getBuiltinVariable(Block &body,
865 spirv::BuiltIn builtin) {
866 // Look through all global variables in the given `body` block and check if
867 // there is a spirv.GlobalVariable that has the same `builtin` attribute.
868 for (auto varOp : body.getOps<spirv::GlobalVariableOp>()) {
869 if (auto builtinAttr = varOp->getAttrOfType<StringAttr>(
870 spirv::SPIRVDialect::getAttributeName(
871 spirv::Decoration::BuiltIn))) {
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 // Copy over all attributes other than the function name and type.
1025 for (NamedAttribute namedAttr : funcOp->getAttrs()) {
1026 if (namedAttr.getName() != funcOp.getFunctionTypeAttrName() &&
1027 namedAttr.getName() != SymbolTable::getSymbolAttrName())
1028 newFuncOp->setAttr(namedAttr.getName(), namedAttr.getValue());
1029 }
1030
1031 rewriter.inlineRegionBefore(funcOp.getBody(), newFuncOp.getBody(),
1032 newFuncOp.end());
1033 if (failed(rewriter.convertRegionTypes(
1034 &newFuncOp.getBody(), *getTypeConverter(), &signatureConverter)))
1035 return failure();
1036 rewriter.eraseOp(funcOp);
1037 return success();
1038 }
1039};
1040
1041/// A pattern for rewriting function signature to convert vector arguments of
1042/// functions to be of valid types
1043struct FuncOpVectorUnroll final : OpRewritePattern<func::FuncOp> {
1044 using Base::Base;
1045
1046 LogicalResult matchAndRewrite(func::FuncOp funcOp,
1047 PatternRewriter &rewriter) const override {
1048 FunctionType fnType = funcOp.getFunctionType();
1049
1050 // TODO: Handle declarations.
1051 if (funcOp.isDeclaration()) {
1052 LLVM_DEBUG(llvm::dbgs()
1053 << fnType << " illegal: declarations are unsupported\n");
1054 return failure();
1055 }
1056
1057 // Bail out early for dynamically-shaped argument types: getZeroAttr
1058 // requires a statically-shaped type. VectorType is always statically
1059 // shaped, so this correctly skips it without a special-case guard.
1060 if (llvm::any_of(fnType.getInputs(), [](Type argType) {
1061 auto shapedType = dyn_cast<ShapedType>(argType);
1062 return shapedType && !shapedType.hasStaticShape();
1063 }))
1064 return failure();
1065
1066 // Create a new func op with the original type and copy the function body.
1067 auto newFuncOp = func::FuncOp::create(rewriter, funcOp.getLoc(),
1068 funcOp.getName(), fnType);
1069 rewriter.inlineRegionBefore(funcOp.getBody(), newFuncOp.getBody(),
1070 newFuncOp.end());
1071
1072 Location loc = newFuncOp.getBody().getLoc();
1073
1074 Block &entryBlock = newFuncOp.getBlocks().front();
1075 OpBuilder::InsertionGuard guard(rewriter);
1076 rewriter.setInsertionPointToStart(&entryBlock);
1077
1078 TypeConverter::SignatureConversion oneToNTypeMapping(
1079 fnType.getInputs().size());
1080
1081 // For arguments that are of illegal types and require unrolling.
1082 // `unrolledInputNums` stores the indices of arguments that result from
1083 // unrolling in the new function signature. `newInputNo` is a counter.
1084 SmallVector<size_t> unrolledInputNums;
1085 size_t newInputNo = 0;
1086
1087 // For arguments that are of legal types and do not require unrolling.
1088 // `tmpOps` stores a mapping from temporary operations that serve as
1089 // placeholders for new arguments that will be added later. These operations
1090 // will be erased once the entry block's argument list is updated.
1091 llvm::SmallDenseMap<Operation *, size_t> tmpOps;
1092
1093 // This counts the number of new operations created.
1094 size_t newOpCount = 0;
1095
1096 // Enumerate through the arguments.
1097 for (auto [origInputNo, origType] : enumerate(fnType.getInputs())) {
1098 // Check whether the argument is of vector type.
1099 auto origVecType = dyn_cast<VectorType>(origType);
1100 if (!origVecType) {
1101 // We need a placeholder for the old argument that will be erased later.
1102 Value result = arith::ConstantOp::create(
1103 rewriter, loc, origType, rewriter.getZeroAttr(origType));
1104 rewriter.replaceAllUsesWith(newFuncOp.getArgument(origInputNo), result);
1105 tmpOps.insert({result.getDefiningOp(), newInputNo});
1106 oneToNTypeMapping.addInputs(origInputNo, origType);
1107 ++newInputNo;
1108 ++newOpCount;
1109 continue;
1110 }
1111 // Check whether the vector needs unrolling.
1112 auto targetShape = getTargetShape(origVecType);
1113 if (!targetShape) {
1114 // We need a placeholder for the old argument that will be erased later.
1115 Value result = arith::ConstantOp::create(
1116 rewriter, loc, origType, rewriter.getZeroAttr(origType));
1117 rewriter.replaceAllUsesWith(newFuncOp.getArgument(origInputNo), result);
1118 tmpOps.insert({result.getDefiningOp(), newInputNo});
1119 oneToNTypeMapping.addInputs(origInputNo, origType);
1120 ++newInputNo;
1121 ++newOpCount;
1122 continue;
1123 }
1124 VectorType unrolledType =
1125 VectorType::get(*targetShape, origVecType.getElementType());
1126 auto originalShape =
1127 llvm::to_vector_of<int64_t, 4>(origVecType.getShape());
1128
1129 // Prepare the result vector.
1130 Value result = arith::ConstantOp::create(
1131 rewriter, loc, origVecType, rewriter.getZeroAttr(origVecType));
1132 ++newOpCount;
1133 // Prepare the placeholder for the new arguments that will be added later.
1134 Value dummy = arith::ConstantOp::create(
1135 rewriter, loc, unrolledType, rewriter.getZeroAttr(unrolledType));
1136 ++newOpCount;
1137
1138 // Create the `vector.insert_strided_slice` ops.
1139 SmallVector<int64_t> strides(targetShape->size(), 1);
1140 SmallVector<Type> newTypes;
1141 for (SmallVector<int64_t> offsets :
1142 StaticTileOffsetRange(originalShape, *targetShape)) {
1143 result = vector::InsertStridedSliceOp::create(rewriter, loc, dummy,
1144 result, offsets, strides);
1145 newTypes.push_back(unrolledType);
1146 unrolledInputNums.push_back(newInputNo);
1147 ++newInputNo;
1148 ++newOpCount;
1149 }
1150 rewriter.replaceAllUsesWith(newFuncOp.getArgument(origInputNo), result);
1151 oneToNTypeMapping.addInputs(origInputNo, newTypes);
1152 }
1153
1154 // Change the function signature.
1155 auto convertedTypes = oneToNTypeMapping.getConvertedTypes();
1156 auto newFnType = fnType.clone(convertedTypes, fnType.getResults());
1157 rewriter.modifyOpInPlace(newFuncOp,
1158 [&] { newFuncOp.setFunctionType(newFnType); });
1159
1160 // Update the arguments in the entry block.
1161 entryBlock.eraseArguments(0, fnType.getNumInputs());
1162 SmallVector<Location> locs(convertedTypes.size(), newFuncOp.getLoc());
1163 entryBlock.addArguments(convertedTypes, locs);
1164
1165 // Replace all uses of placeholders for initially legal arguments with their
1166 // original function arguments (that were added to `newFuncOp`).
1167 for (auto &[placeholderOp, argIdx] : tmpOps) {
1168 if (!placeholderOp)
1169 continue;
1170 Value replacement = newFuncOp.getArgument(argIdx);
1171 rewriter.replaceAllUsesWith(placeholderOp->getResult(0), replacement);
1172 }
1173
1174 // Replace dummy operands of new `vector.insert_strided_slice` ops with
1175 // their corresponding new function arguments. The new
1176 // `vector.insert_strided_slice` ops are inserted only into the entry block,
1177 // so iterating over that block is sufficient.
1178 size_t unrolledInputIdx = 0;
1179 for (auto [count, op] : enumerate(entryBlock.getOperations())) {
1180 Operation &curOp = op;
1181 // Since all newly created operations are in the beginning, reaching the
1182 // end of them means that any later `vector.insert_strided_slice` should
1183 // not be touched.
1184 if (count >= newOpCount)
1185 continue;
1186 if (auto vecOp = dyn_cast<vector::InsertStridedSliceOp>(op)) {
1187 size_t unrolledInputNo = unrolledInputNums[unrolledInputIdx];
1188 rewriter.modifyOpInPlace(&curOp, [&] {
1189 curOp.setOperand(0, newFuncOp.getArgument(unrolledInputNo));
1190 });
1191 ++unrolledInputIdx;
1192 }
1193 }
1194
1195 // Erase the original funcOp. The `tmpOps` do not need to be erased since
1196 // they have no uses and will be handled by dead-code elimination.
1197 rewriter.eraseOp(funcOp);
1198 return success();
1199 }
1200};
1201
1202//===----------------------------------------------------------------------===//
1203// func::ReturnOp Conversion Patterns
1204//===----------------------------------------------------------------------===//
1205
1206/// A pattern for rewriting function signature and the return op to convert
1207/// vectors to be of valid types.
1208struct ReturnOpVectorUnroll final : OpRewritePattern<func::ReturnOp> {
1209 using Base::Base;
1210
1211 LogicalResult matchAndRewrite(func::ReturnOp returnOp,
1212 PatternRewriter &rewriter) const override {
1213 // Check whether the parent funcOp is valid.
1214 auto funcOp = dyn_cast<func::FuncOp>(returnOp->getParentOp());
1215 if (!funcOp)
1216 return failure();
1217
1218 FunctionType fnType = funcOp.getFunctionType();
1219 TypeConverter::SignatureConversion oneToNTypeMapping(
1220 fnType.getResults().size());
1221 Location loc = returnOp.getLoc();
1222
1223 // For the new return op.
1224 SmallVector<Value> newOperands;
1225
1226 // Enumerate through the results.
1227 for (auto [origResultNo, origType] : enumerate(fnType.getResults())) {
1228 // Check whether the argument is of vector type.
1229 auto origVecType = dyn_cast<VectorType>(origType);
1230 if (!origVecType) {
1231 oneToNTypeMapping.addInputs(origResultNo, origType);
1232 newOperands.push_back(returnOp.getOperand(origResultNo));
1233 continue;
1234 }
1235 // Check whether the vector needs unrolling.
1236 auto targetShape = getTargetShape(origVecType);
1237 if (!targetShape) {
1238 // The original argument can be used.
1239 oneToNTypeMapping.addInputs(origResultNo, origType);
1240 newOperands.push_back(returnOp.getOperand(origResultNo));
1241 continue;
1242 }
1243 VectorType unrolledType =
1244 VectorType::get(*targetShape, origVecType.getElementType());
1245
1246 // Create `vector.extract_strided_slice` ops to form legal vectors from
1247 // the original operand of illegal type.
1248 auto originalShape =
1249 llvm::to_vector_of<int64_t, 4>(origVecType.getShape());
1250 SmallVector<int64_t> strides(originalShape.size(), 1);
1251 SmallVector<int64_t> extractShape(originalShape.size(), 1);
1252 extractShape.back() = targetShape->back();
1253 SmallVector<Type> newTypes;
1254 Value returnValue = returnOp.getOperand(origResultNo);
1255 for (SmallVector<int64_t> offsets :
1256 StaticTileOffsetRange(originalShape, *targetShape)) {
1257 Value result = vector::ExtractStridedSliceOp::create(
1258 rewriter, loc, returnValue, offsets, extractShape, strides);
1259 if (originalShape.size() > 1) {
1260 SmallVector<int64_t> extractIndices(originalShape.size() - 1, 0);
1261 result =
1262 vector::ExtractOp::create(rewriter, loc, result, extractIndices);
1263 }
1264 newOperands.push_back(result);
1265 newTypes.push_back(unrolledType);
1266 }
1267 oneToNTypeMapping.addInputs(origResultNo, newTypes);
1268 }
1269
1270 // Change the function signature.
1271 auto newFnType =
1272 FunctionType::get(rewriter.getContext(), TypeRange(fnType.getInputs()),
1273 TypeRange(oneToNTypeMapping.getConvertedTypes()));
1274 rewriter.modifyOpInPlace(funcOp,
1275 [&] { funcOp.setFunctionType(newFnType); });
1276
1277 // Replace the return op using the new operands. This will automatically
1278 // update the entry block as well.
1279 rewriter.replaceOp(returnOp,
1280 func::ReturnOp::create(rewriter, loc, newOperands));
1281
1282 return success();
1283 }
1284};
1285
1286} // namespace
1287
1288//===----------------------------------------------------------------------===//
1289// Public function for builtin variables
1290//===----------------------------------------------------------------------===//
1291
1293 spirv::BuiltIn builtin,
1294 Type integerType, OpBuilder &builder,
1295 StringRef prefix, StringRef suffix) {
1297 if (!parent) {
1298 op->emitError("expected operation to be within a module-like op");
1299 return nullptr;
1300 }
1301
1302 spirv::GlobalVariableOp varOp =
1303 getOrInsertBuiltinVariable(*parent->getRegion(0).begin(), op->getLoc(),
1304 builtin, integerType, builder, prefix, suffix);
1305 Value ptr = spirv::AddressOfOp::create(builder, op->getLoc(), varOp);
1306 return spirv::LoadOp::create(builder, op->getLoc(), ptr);
1307}
1308
1309//===----------------------------------------------------------------------===//
1310// Public function for pushing constant storage
1311//===----------------------------------------------------------------------===//
1312
1314 unsigned offset, Type integerType,
1315 OpBuilder &builder) {
1316 Location loc = op->getLoc();
1318 if (!parent) {
1319 op->emitError("expected operation to be within a module-like op");
1320 return nullptr;
1321 }
1322
1323 spirv::GlobalVariableOp varOp = getOrInsertPushConstantVariable(
1324 loc, parent->getRegion(0).front(), elementCount, builder, integerType);
1325
1326 Value zeroOp = spirv::ConstantOp::getZero(integerType, loc, builder);
1327 Value offsetOp = spirv::ConstantOp::create(builder, loc, integerType,
1328 builder.getI32IntegerAttr(offset));
1329 auto addrOp = spirv::AddressOfOp::create(builder, loc, varOp);
1330 auto acOp = spirv::AccessChainOp::create(builder, loc, addrOp,
1331 llvm::ArrayRef({zeroOp, offsetOp}));
1332 return spirv::LoadOp::create(builder, loc, acOp);
1333}
1334
1335//===----------------------------------------------------------------------===//
1336// Public functions for index calculation
1337//===----------------------------------------------------------------------===//
1338
1340 int64_t offset, Type integerType,
1341 Location loc, OpBuilder &builder) {
1342 assert(indices.size() == strides.size() &&
1343 "must provide indices for all dimensions");
1344
1345 // TODO: Consider moving to use affine.apply and patterns converting
1346 // affine.apply to standard ops. This needs converting to SPIR-V passes to be
1347 // broken down into progressive small steps so we can have intermediate steps
1348 // using other dialects. At the moment SPIR-V is the final sink.
1349
1350 Value linearizedIndex = builder.createOrFold<spirv::ConstantOp>(
1351 loc, integerType, IntegerAttr::get(integerType, offset));
1352 for (const auto &index : llvm::enumerate(indices)) {
1353 Value strideVal = builder.createOrFold<spirv::ConstantOp>(
1354 loc, integerType,
1355 IntegerAttr::get(integerType, strides[index.index()]));
1356 Value update =
1357 builder.createOrFold<spirv::IMulOp>(loc, index.value(), strideVal);
1358 linearizedIndex =
1359 builder.createOrFold<spirv::IAddOp>(loc, update, linearizedIndex);
1360 }
1361 return linearizedIndex;
1362}
1363
1365 MemRefType baseType, Value basePtr,
1367 OpBuilder &builder) {
1368 // Get base and offset of the MemRefType and verify they are static.
1369
1370 int64_t offset;
1372 if (failed(baseType.getStridesAndOffset(strides, offset)) ||
1373 llvm::is_contained(strides, ShapedType::kDynamic) ||
1374 ShapedType::isDynamic(offset)) {
1375 return nullptr;
1376 }
1377
1378 auto indexType = typeConverter.getIndexType();
1379
1380 SmallVector<Value, 2> linearizedIndices;
1381 auto zero = spirv::ConstantOp::getZero(indexType, loc, builder);
1382
1383 // Add a '0' at the start to index into the struct.
1384 linearizedIndices.push_back(zero);
1385
1386 if (baseType.getRank() == 0) {
1387 linearizedIndices.push_back(zero);
1388 } else {
1389 linearizedIndices.push_back(
1390 linearizeIndex(indices, strides, offset, indexType, loc, builder));
1391 }
1392 return spirv::AccessChainOp::create(builder, loc, basePtr, linearizedIndices);
1393}
1394
1396 MemRefType baseType, Value basePtr,
1398 OpBuilder &builder) {
1399 // Get base and offset of the MemRefType and verify they are static.
1400
1401 int64_t offset;
1403 if (failed(baseType.getStridesAndOffset(strides, offset)) ||
1404 llvm::is_contained(strides, ShapedType::kDynamic) ||
1405 ShapedType::isDynamic(offset)) {
1406 return nullptr;
1407 }
1408
1409 auto indexType = typeConverter.getIndexType();
1410
1411 SmallVector<Value, 2> linearizedIndices;
1412 Value linearIndex;
1413 if (baseType.getRank() == 0) {
1414 linearIndex = spirv::ConstantOp::getZero(indexType, loc, builder);
1415 } else {
1416 linearIndex =
1417 linearizeIndex(indices, strides, offset, indexType, loc, builder);
1418 }
1419 Type pointeeType =
1420 cast<spirv::PointerType>(basePtr.getType()).getPointeeType();
1421 if (isa<spirv::ArrayType>(pointeeType)) {
1422 linearizedIndices.push_back(linearIndex);
1423 return spirv::AccessChainOp::create(builder, loc, basePtr,
1424 linearizedIndices);
1425 }
1426 return spirv::PtrAccessChainOp::create(builder, loc, basePtr, linearIndex,
1427 linearizedIndices);
1428}
1429
1431 MemRefType baseType, Value basePtr,
1433 OpBuilder &builder) {
1434
1435 if (typeConverter.allows(spirv::Capability::Kernel)) {
1436 return getOpenCLElementPtr(typeConverter, baseType, basePtr, indices, loc,
1437 builder);
1438 }
1439
1440 return getVulkanElementPtr(typeConverter, baseType, basePtr, indices, loc,
1441 builder);
1442}
1443
1444//===----------------------------------------------------------------------===//
1445// Public functions for vector unrolling
1446//===----------------------------------------------------------------------===//
1447
1449 for (int i : {4, 3, 2}) {
1450 if (size % i == 0)
1451 return i;
1452 }
1453 return 1;
1454}
1455
1458 VectorType srcVectorType = op.getSourceVectorType();
1459 assert(srcVectorType.getRank() == 1); // Guaranteed by semantics
1460 int64_t vectorSize =
1461 mlir::spirv::getComputeVectorSize(srcVectorType.getDimSize(0));
1462 return {vectorSize};
1463}
1464
1467 VectorType vectorType = op.getResultVectorType();
1468 SmallVector<int64_t> nativeSize(vectorType.getRank(), 1);
1469 nativeSize.back() =
1470 mlir::spirv::getComputeVectorSize(vectorType.getShape().back());
1471 return nativeSize;
1472}
1473
1474std::optional<SmallVector<int64_t>>
1477 if (auto vecType = dyn_cast<VectorType>(op->getResultTypes()[0])) {
1478 if (vecType.getRank() == 0)
1479 return std::nullopt;
1480 SmallVector<int64_t> nativeSize(vecType.getRank(), 1);
1481 nativeSize.back() =
1482 mlir::spirv::getComputeVectorSize(vecType.getShape().back());
1483 return nativeSize;
1484 }
1485 }
1486
1488 .Case<vector::ReductionOp, vector::TransposeOp>(
1489 [](auto typedOp) { return getNativeVectorShapeImpl(typedOp); })
1490 .Default(std::nullopt);
1491}
1492
1494 MLIRContext *context = op->getContext();
1495 RewritePatternSet patterns(context);
1498 // We only want to apply signature conversion once to the existing func ops.
1499 // Without specifying strictMode, the greedy pattern rewriter will keep
1500 // looking for newly created func ops.
1501 return applyPatternsGreedily(op, std::move(patterns),
1502 GreedyRewriteConfig().setStrictness(
1504}
1505
1507 MLIRContext *context = op->getContext();
1508
1509 // Unroll vectors in function bodies to native vector size.
1510 {
1511 RewritePatternSet patterns(context);
1513 [](auto op) { return mlir::spirv::getNativeVectorShape(op); });
1514 populateVectorUnrollPatterns(patterns, options);
1515 if (failed(applyPatternsGreedily(op, std::move(patterns))))
1516 return failure();
1517 }
1518
1519 // Convert transpose ops into extract and insert pairs, in preparation of
1520 // further transformations to canonicalize/cancel.
1521 {
1522 RewritePatternSet patterns(context);
1524 patterns, vector::VectorTransposeLowering::EltWise);
1526 if (failed(applyPatternsGreedily(op, std::move(patterns))))
1527 return failure();
1528 }
1529
1530 // Run canonicalization to cast away leading size-1 dimensions.
1531 {
1532 RewritePatternSet patterns(context);
1533
1534 // We need to pull in casting way leading one dims.
1535 vector::populateCastAwayVectorLeadingOneDimPatterns(patterns);
1536 vector::ReductionOp::getCanonicalizationPatterns(patterns, context);
1537 vector::TransposeOp::getCanonicalizationPatterns(patterns, context);
1538
1539 // Decompose different rank insert_strided_slice and n-D
1540 // extract_slided_slice.
1541 vector::populateVectorInsertExtractStridedSliceDecompositionPatterns(
1542 patterns);
1543 vector::InsertOp::getCanonicalizationPatterns(patterns, context);
1544 vector::ExtractOp::getCanonicalizationPatterns(patterns, context);
1545
1546 // Trimming leading unit dims may generate broadcast/shape_cast ops. Clean
1547 // them up.
1548 vector::BroadcastOp::getCanonicalizationPatterns(patterns, context);
1549 vector::ShapeCastOp::getCanonicalizationPatterns(patterns, context);
1550
1551 if (failed(applyPatternsGreedily(op, std::move(patterns))))
1552 return failure();
1553 }
1554 return success();
1555}
1556
1557//===----------------------------------------------------------------------===//
1558// SPIR-V TypeConverter
1559//===----------------------------------------------------------------------===//
1560
1562 const SPIRVConversionOptions &options)
1563 : targetEnv(targetAttr), options(options) {
1564 // Add conversions. The order matters here: later ones will be tried earlier.
1565
1566 // Allow all SPIR-V dialect specific types. This assumes all builtin types
1567 // adopted in the SPIR-V dialect (i.e., IntegerType, FloatType, VectorType)
1568 // were tried before.
1569 //
1570 // TODO: This assumes that the SPIR-V types are valid to use in the given
1571 // target environment, which should be the case if the whole pipeline is
1572 // driven by the same target environment. Still, we probably still want to
1573 // validate and convert to be safe.
1574 addConversion([](spirv::SPIRVType type) { return type; });
1575
1576 addConversion([this](IndexType /*indexType*/) { return getIndexType(); });
1577
1578 addConversion([this](IntegerType intType) -> std::optional<Type> {
1579 if (auto scalarType = dyn_cast<spirv::ScalarType>(intType))
1580 return convertScalarType(this->targetEnv, this->options, scalarType);
1581 if (intType.getWidth() < 8)
1582 return convertSubByteIntegerType(this->options, intType);
1583 return Type();
1584 });
1585
1586 addConversion([this](FloatType floatType) -> std::optional<Type> {
1587 if (auto scalarType = dyn_cast<spirv::ScalarType>(floatType))
1588 return convertScalarType(this->targetEnv, this->options, scalarType);
1589 if (floatType.getWidth() == 8)
1590 return convert8BitFloatType(this->options, floatType);
1591 return Type();
1592 });
1593
1594 addConversion([this](ComplexType complexType) {
1595 return convertComplexType(this->targetEnv, this->options, complexType);
1596 });
1597
1598 addConversion([this](VectorType vectorType) {
1599 return convertVectorType(this->targetEnv, this->options, vectorType);
1600 });
1601
1602 addConversion([this](TensorType tensorType) {
1603 return convertTensorType(this->targetEnv, this->options, tensorType);
1604 });
1605
1606 addConversion([this](MemRefType memRefType) {
1607 return convertMemrefType(this->targetEnv, this->options, memRefType);
1608 });
1609
1610 // Register some last line of defense casting logic.
1611 addSourceMaterialization(
1612 [this](OpBuilder &builder, Type type, ValueRange inputs, Location loc) {
1613 return castToSourceType(this->targetEnv, builder, type, inputs, loc);
1614 });
1615 addTargetMaterialization([](OpBuilder &builder, Type type, ValueRange inputs,
1616 Location loc) {
1617 auto cast = UnrealizedConversionCastOp::create(builder, loc, type, inputs);
1618 return cast.getResult(0);
1619 });
1620}
1621
1623 return ::getIndexType(getContext(), options);
1624}
1625
1626MLIRContext *SPIRVTypeConverter::getContext() const {
1627 return targetEnv.getAttr().getContext();
1628}
1629
1630bool SPIRVTypeConverter::allows(spirv::Capability capability) const {
1631 return targetEnv.allows(capability);
1632}
1633
1634//===----------------------------------------------------------------------===//
1635// SPIR-V ConversionTarget
1636//===----------------------------------------------------------------------===//
1637
1638std::unique_ptr<SPIRVConversionTarget>
1640 std::unique_ptr<SPIRVConversionTarget> target(
1641 // std::make_unique does not work here because the constructor is private.
1642 new SPIRVConversionTarget(targetAttr));
1643 SPIRVConversionTarget *targetPtr = target.get();
1644 target->addDynamicallyLegalDialect<spirv::SPIRVDialect>(
1645 // We need to capture the raw pointer here because it is stable:
1646 // target will be destroyed once this function is returned.
1647 [targetPtr](Operation *op) { return targetPtr->isLegalOp(op); });
1648 return target;
1649}
1650
1651SPIRVConversionTarget::SPIRVConversionTarget(spirv::TargetEnvAttr targetAttr)
1652 : ConversionTarget(*targetAttr.getContext()), targetEnv(targetAttr) {}
1653
1654bool SPIRVConversionTarget::isLegalOp(Operation *op) {
1655 // Make sure this op is available at the given version. Ops not implementing
1656 // QueryMinVersionInterface/QueryMaxVersionInterface are available to all
1657 // SPIR-V versions.
1658 if (auto minVersionIfx = dyn_cast<spirv::QueryMinVersionInterface>(op)) {
1659 std::optional<spirv::Version> minVersion = minVersionIfx.getMinVersion();
1660 if (minVersion && *minVersion > this->targetEnv.getVersion()) {
1661 LLVM_DEBUG(llvm::dbgs()
1662 << op->getName() << " illegal: requiring min version "
1663 << spirv::stringifyVersion(*minVersion) << "\n");
1664 return false;
1665 }
1666 }
1667 if (auto maxVersionIfx = dyn_cast<spirv::QueryMaxVersionInterface>(op)) {
1668 std::optional<spirv::Version> maxVersion = maxVersionIfx.getMaxVersion();
1669 if (maxVersion && *maxVersion < this->targetEnv.getVersion()) {
1670 LLVM_DEBUG(llvm::dbgs()
1671 << op->getName() << " illegal: requiring max version "
1672 << spirv::stringifyVersion(*maxVersion) << "\n");
1673 return false;
1674 }
1675 }
1676
1677 // Make sure this op's required extensions are allowed to use. Ops not
1678 // implementing QueryExtensionInterface do not require extensions to be
1679 // available.
1680 if (auto extensions = dyn_cast<spirv::QueryExtensionInterface>(op))
1681 if (failed(checkExtensionRequirements(op->getName(), this->targetEnv,
1682 extensions.getExtensions())))
1683 return false;
1684
1685 // Make sure this op's required extensions are allowed to use. Ops not
1686 // implementing QueryCapabilityInterface do not require capabilities to be
1687 // available.
1688 if (auto capabilities = dyn_cast<spirv::QueryCapabilityInterface>(op))
1689 if (failed(checkCapabilityRequirements(op->getName(), this->targetEnv,
1690 capabilities.getCapabilities())))
1691 return false;
1692
1693 SmallVector<Type, 4> valueTypes;
1694 valueTypes.append(op->operand_type_begin(), op->operand_type_end());
1695 valueTypes.append(op->result_type_begin(), op->result_type_end());
1696
1697 // Ensure that all types have been converted to SPIRV types.
1698 if (llvm::any_of(valueTypes,
1699 [](Type t) { return !isa<spirv::SPIRVType>(t); }))
1700 return false;
1701
1702 // Special treatment for global variables, whose type requirements are
1703 // conveyed by type attributes.
1704 if (auto globalVar = dyn_cast<spirv::GlobalVariableOp>(op))
1705 valueTypes.push_back(globalVar.getType());
1706
1707 // Make sure the op's operands/results use types that are allowed by the
1708 // target environment.
1709 SmallVector<ArrayRef<spirv::Extension>, 4> typeExtensions;
1710 SmallVector<ArrayRef<spirv::Capability>, 8> typeCapabilities;
1711 for (Type valueType : valueTypes) {
1712 typeExtensions.clear();
1713 cast<spirv::SPIRVType>(valueType).getExtensions(typeExtensions);
1714 if (failed(checkExtensionRequirements(op->getName(), this->targetEnv,
1715 typeExtensions)))
1716 return false;
1717
1718 typeCapabilities.clear();
1719 cast<spirv::SPIRVType>(valueType).getCapabilities(typeCapabilities);
1720 if (failed(checkCapabilityRequirements(op->getName(), this->targetEnv,
1721 typeCapabilities)))
1722 return false;
1723 }
1724
1725 return true;
1726}
1727
1728//===----------------------------------------------------------------------===//
1729// Public functions for populating patterns
1730//===----------------------------------------------------------------------===//
1731
1733 const SPIRVTypeConverter &typeConverter, RewritePatternSet &patterns) {
1734 patterns.add<FuncOpConversion>(typeConverter, patterns.getContext());
1735}
1736
1738 patterns.add<FuncOpVectorUnroll>(patterns.getContext());
1739}
1740
1742 patterns.add<ReturnOpVectorUnroll>(patterns.getContext());
1743}
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.
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:203
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:147
Operation & front()
Definition Block.h:163
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
IntegerAttr getI32IntegerAttr(int32_t value)
Definition Builders.cpp:204
FloatType getF32Type()
Definition Builders.cpp:47
TypedAttr getZeroAttr(Type type)
Definition Builders.cpp:329
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
RAII guard to reset the insertion point of the builder when destroyed.
Definition Builders.h:350
This class helps build Operations.
Definition Builders.h:209
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:242
void setInsertionPointToStart(Block *block)
Sets the insertion point to the start of the specified block.
Definition Builders.h:433
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:711
void setOperand(unsigned idx, Value value)
Definition Operation.h:376
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.
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
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)
Value linearizeIndex(ValueRange indices, ArrayRef< int64_t > strides, int64_t offset, Type integerType, Location loc, OpBuilder &builder)
Generates IR to perform index linearization with the given indices and their corresponding strides,...
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)
int getComputeVectorSize(int64_t size)
LogicalResult unrollVectorsInSignatures(Operation *op)
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...
Options that control the vector unrolling.
UnrollVectorOptions & setNativeShapeFn(NativeShapeFnType fn)