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 
13 #include "mlir/Dialect/SPIRV/Transforms/SPIRVConversion.h"
14 #include "mlir/Dialect/SPIRV/IR/SPIRVDialect.h"
15 #include "mlir/Dialect/SPIRV/IR/SPIRVOps.h"
16 #include "mlir/Transforms/DialectConversion.h"
17 #include "llvm/ADT/Sequence.h"
18 #include "llvm/ADT/StringExtras.h"
19 #include "llvm/Support/Debug.h"
20 
21 #include <functional>
22 
23 #define DEBUG_TYPE "mlir-spirv-conversion"
24 
25 using namespace mlir;
26 
27 //===----------------------------------------------------------------------===//
28 // Utility functions
29 //===----------------------------------------------------------------------===//
30 
31 /// Checks that `candidates` extension requirements are possible to be satisfied
32 /// with the given `targetEnv`.
33 ///
34 ///  `candidates` is a vector of vector for extension requirements following
35 /// ((Extension::A OR Extension::B) AND (Extension::C OR Extension::D))
36 /// convention.
37 template <typename LabelT>
38 static LogicalResult checkExtensionRequirements(
39     LabelT label, const spirv::TargetEnv &targetEnv,
40     const spirv::SPIRVType::ExtensionArrayRefVector &candidates) {
41   for (const auto &ors : candidates) {
42     if (targetEnv.allows(ors))
43       continue;
44 
45     LLVM_DEBUG({
46       SmallVector<StringRef> extStrings;
47       for (spirv::Extension ext : ors)
48         extStrings.push_back(spirv::stringifyExtension(ext));
49 
50       llvm::dbgs() << label << " illegal: requires at least one extension in ["
51                    << llvm::join(extStrings, ", ")
52                    << "] but none allowed in target environment\n";
53     });
54     return failure();
55   }
56   return success();
57 }
58 
59 /// Checks that `candidates`capability requirements are possible to be satisfied
60 /// with the given `isAllowedFn`.
61 ///
62 ///  `candidates` is a vector of vector for capability requirements following
63 /// ((Capability::A OR Capability::B) AND (Capability::C OR Capability::D))
64 /// convention.
65 template <typename LabelT>
66 static LogicalResult checkCapabilityRequirements(
67     LabelT label, const spirv::TargetEnv &targetEnv,
68     const spirv::SPIRVType::CapabilityArrayRefVector &candidates) {
69   for (const auto &ors : candidates) {
70     if (targetEnv.allows(ors))
71       continue;
72 
73     LLVM_DEBUG({
74       SmallVector<StringRef> capStrings;
75       for (spirv::Capability cap : ors)
76         capStrings.push_back(spirv::stringifyCapability(cap));
77 
78       llvm::dbgs() << label << " illegal: requires at least one capability in ["
79                    << llvm::join(capStrings, ", ")
80                    << "] but none allowed in target environment\n";
81     });
82     return failure();
83   }
84   return success();
85 }
86 
87 //===----------------------------------------------------------------------===//
88 // Type Conversion
89 //===----------------------------------------------------------------------===//
90 
91 Type SPIRVTypeConverter::getIndexType(MLIRContext *context) {
92   // Convert to 32-bit integers for now. Might need a way to control this in
93   // future.
94   // TODO: It is probably better to make it 64-bit integers. To
95   // this some support is needed in SPIR-V dialect for Conversion
96   // instructions. The Vulkan spec requires the builtins like
97   // GlobalInvocationID, etc. to be 32-bit (unsigned) integers which should be
98   // SExtended to 64-bit for index computations.
99   return IntegerType::get(context, 32);
100 }
101 
102 /// Mapping between SPIR-V storage classes to memref memory spaces.
103 ///
104 /// Note: memref does not have a defined semantics for each memory space; it
105 /// depends on the context where it is used. There are no particular reasons
106 /// behind the number assignments; we try to follow NVVM conventions and largely
107 /// give common storage classes a smaller number. The hope is use symbolic
108 /// memory space representation eventually after memref supports it.
109 // TODO: swap Generic and StorageBuffer assignment to be more akin
110 // to NVVM.
111 #define STORAGE_SPACE_MAP_LIST(MAP_FN)                                         \
112   MAP_FN(spirv::StorageClass::Generic, 1)                                      \
113   MAP_FN(spirv::StorageClass::StorageBuffer, 0)                                \
114   MAP_FN(spirv::StorageClass::Workgroup, 3)                                    \
115   MAP_FN(spirv::StorageClass::Uniform, 4)                                      \
116   MAP_FN(spirv::StorageClass::Private, 5)                                      \
117   MAP_FN(spirv::StorageClass::Function, 6)                                     \
118   MAP_FN(spirv::StorageClass::PushConstant, 7)                                 \
119   MAP_FN(spirv::StorageClass::UniformConstant, 8)                              \
120   MAP_FN(spirv::StorageClass::Input, 9)                                        \
121   MAP_FN(spirv::StorageClass::Output, 10)                                      \
122   MAP_FN(spirv::StorageClass::CrossWorkgroup, 11)                              \
123   MAP_FN(spirv::StorageClass::AtomicCounter, 12)                               \
124   MAP_FN(spirv::StorageClass::Image, 13)                                       \
125   MAP_FN(spirv::StorageClass::CallableDataNV, 14)                              \
126   MAP_FN(spirv::StorageClass::IncomingCallableDataNV, 15)                      \
127   MAP_FN(spirv::StorageClass::RayPayloadNV, 16)                                \
128   MAP_FN(spirv::StorageClass::HitAttributeNV, 17)                              \
129   MAP_FN(spirv::StorageClass::IncomingRayPayloadNV, 18)                        \
130   MAP_FN(spirv::StorageClass::ShaderRecordBufferNV, 19)                        \
131   MAP_FN(spirv::StorageClass::PhysicalStorageBuffer, 20)
132 
133 unsigned
134 SPIRVTypeConverter::getMemorySpaceForStorageClass(spirv::StorageClass storage) {
135 #define STORAGE_SPACE_MAP_FN(storage, space)                                   \
136   case storage:                                                                \
137     return space;
138 
139   switch (storage) { STORAGE_SPACE_MAP_LIST(STORAGE_SPACE_MAP_FN) }
140 #undef STORAGE_SPACE_MAP_FN
141   llvm_unreachable("unhandled storage class!");
142 }
143 
144 Optional<spirv::StorageClass>
145 SPIRVTypeConverter::getStorageClassForMemorySpace(unsigned space) {
146 #define STORAGE_SPACE_MAP_FN(storage, space)                                   \
147   case space:                                                                  \
148     return storage;
149 
150   switch (space) {
151     STORAGE_SPACE_MAP_LIST(STORAGE_SPACE_MAP_FN)
152   default:
153     return llvm::None;
154   }
155 #undef STORAGE_SPACE_MAP_FN
156 }
157 
158 const SPIRVTypeConverter::Options &SPIRVTypeConverter::getOptions() const {
159   return options;
160 }
161 
162 #undef STORAGE_SPACE_MAP_LIST
163 
164 // TODO: This is a utility function that should probably be exposed by the
165 // SPIR-V dialect. Keeping it local till the use case arises.
166 static Optional<int64_t>
167 getTypeNumBytes(const SPIRVTypeConverter::Options &options, Type type) {
168   if (type.isa<spirv::ScalarType>()) {
169     auto bitWidth = type.getIntOrFloatBitWidth();
170     // According to the SPIR-V spec:
171     // "There is no physical size or bit pattern defined for values with boolean
172     // type. If they are stored (in conjunction with OpVariable), they can only
173     // be used with logical addressing operations, not physical, and only with
174     // non-externally visible shader Storage Classes: Workgroup, CrossWorkgroup,
175     // Private, Function, Input, and Output."
176     if (bitWidth == 1)
177       return llvm::None;
178     return bitWidth / 8;
179   }
180 
181   if (auto vecType = type.dyn_cast<VectorType>()) {
182     auto elementSize = getTypeNumBytes(options, vecType.getElementType());
183     if (!elementSize)
184       return llvm::None;
185     return vecType.getNumElements() * elementSize.getValue();
186   }
187 
188   if (auto memRefType = type.dyn_cast<MemRefType>()) {
189     // TODO: Layout should also be controlled by the ABI attributes. For now
190     // using the layout from MemRef.
191     int64_t offset;
192     SmallVector<int64_t, 4> strides;
193     if (!memRefType.hasStaticShape() ||
194         failed(getStridesAndOffset(memRefType, strides, offset)))
195       return llvm::None;
196 
197     // To get the size of the memref object in memory, the total size is the
198     // max(stride * dimension-size) computed for all dimensions times the size
199     // of the element.
200     auto elementSize = getTypeNumBytes(options, memRefType.getElementType());
201     if (!elementSize)
202       return llvm::None;
203 
204     if (memRefType.getRank() == 0)
205       return elementSize;
206 
207     auto dims = memRefType.getShape();
208     if (llvm::is_contained(dims, ShapedType::kDynamicSize) ||
209         offset == MemRefType::getDynamicStrideOrOffset() ||
210         llvm::is_contained(strides, MemRefType::getDynamicStrideOrOffset()))
211       return llvm::None;
212 
213     int64_t memrefSize = -1;
214     for (auto shape : enumerate(dims))
215       memrefSize = std::max(memrefSize, shape.value() * strides[shape.index()]);
216 
217     return (offset + memrefSize) * elementSize.getValue();
218   }
219 
220   if (auto tensorType = type.dyn_cast<TensorType>()) {
221     if (!tensorType.hasStaticShape())
222       return llvm::None;
223 
224     auto elementSize = getTypeNumBytes(options, tensorType.getElementType());
225     if (!elementSize)
226       return llvm::None;
227 
228     int64_t size = elementSize.getValue();
229     for (auto shape : tensorType.getShape())
230       size *= shape;
231 
232     return size;
233   }
234 
235   // TODO: Add size computation for other types.
236   return llvm::None;
237 }
238 
239 /// Converts a scalar `type` to a suitable type under the given `targetEnv`.
240 static Type convertScalarType(const spirv::TargetEnv &targetEnv,
241                               const SPIRVTypeConverter::Options &options,
242                               spirv::ScalarType type,
243                               Optional<spirv::StorageClass> storageClass = {}) {
244   // Get extension and capability requirements for the given type.
245   SmallVector<ArrayRef<spirv::Extension>, 1> extensions;
246   SmallVector<ArrayRef<spirv::Capability>, 2> capabilities;
247   type.getExtensions(extensions, storageClass);
248   type.getCapabilities(capabilities, storageClass);
249 
250   // If all requirements are met, then we can accept this type as-is.
251   if (succeeded(checkCapabilityRequirements(type, targetEnv, capabilities)) &&
252       succeeded(checkExtensionRequirements(type, targetEnv, extensions)))
253     return type;
254 
255   // Otherwise we need to adjust the type, which really means adjusting the
256   // bitwidth given this is a scalar type.
257 
258   if (!options.emulateNon32BitScalarTypes)
259     return nullptr;
260 
261   if (auto floatType = type.dyn_cast<FloatType>()) {
262     LLVM_DEBUG(llvm::dbgs() << type << " converted to 32-bit for SPIR-V\n");
263     return Builder(targetEnv.getContext()).getF32Type();
264   }
265 
266   auto intType = type.cast<IntegerType>();
267   LLVM_DEBUG(llvm::dbgs() << type << " converted to 32-bit for SPIR-V\n");
268   return IntegerType::get(targetEnv.getContext(), /*width=*/32,
269                           intType.getSignedness());
270 }
271 
272 /// Converts a vector `type` to a suitable type under the given `targetEnv`.
273 static Type convertVectorType(const spirv::TargetEnv &targetEnv,
274                               const SPIRVTypeConverter::Options &options,
275                               VectorType type,
276                               Optional<spirv::StorageClass> storageClass = {}) {
277   if (type.getRank() == 1 && type.getNumElements() == 1)
278     return type.getElementType();
279 
280   if (!spirv::CompositeType::isValid(type)) {
281     // TODO: Vector types with more than four elements can be translated into
282     // array types.
283     LLVM_DEBUG(llvm::dbgs() << type << " illegal: > 4-element unimplemented\n");
284     return nullptr;
285   }
286 
287   // Get extension and capability requirements for the given type.
288   SmallVector<ArrayRef<spirv::Extension>, 1> extensions;
289   SmallVector<ArrayRef<spirv::Capability>, 2> capabilities;
290   type.cast<spirv::CompositeType>().getExtensions(extensions, storageClass);
291   type.cast<spirv::CompositeType>().getCapabilities(capabilities, storageClass);
292 
293   // If all requirements are met, then we can accept this type as-is.
294   if (succeeded(checkCapabilityRequirements(type, targetEnv, capabilities)) &&
295       succeeded(checkExtensionRequirements(type, targetEnv, extensions)))
296     return type;
297 
298   auto elementType = convertScalarType(
299       targetEnv, options, type.getElementType().cast<spirv::ScalarType>(),
300       storageClass);
301   if (elementType)
302     return VectorType::get(type.getShape(), elementType);
303   return nullptr;
304 }
305 
306 /// Converts a tensor `type` to a suitable type under the given `targetEnv`.
307 ///
308 /// Note that this is mainly for lowering constant tensors. In SPIR-V one can
309 /// create composite constants with OpConstantComposite to embed relative large
310 /// constant values and use OpCompositeExtract and OpCompositeInsert to
311 /// manipulate, like what we do for vectors.
312 static Type convertTensorType(const spirv::TargetEnv &targetEnv,
313                               const SPIRVTypeConverter::Options &options,
314                               TensorType type) {
315   // TODO: Handle dynamic shapes.
316   if (!type.hasStaticShape()) {
317     LLVM_DEBUG(llvm::dbgs()
318                << type << " illegal: dynamic shape unimplemented\n");
319     return nullptr;
320   }
321 
322   auto scalarType = type.getElementType().dyn_cast<spirv::ScalarType>();
323   if (!scalarType) {
324     LLVM_DEBUG(llvm::dbgs()
325                << type << " illegal: cannot convert non-scalar element type\n");
326     return nullptr;
327   }
328 
329   Optional<int64_t> scalarSize = getTypeNumBytes(options, scalarType);
330   Optional<int64_t> tensorSize = getTypeNumBytes(options, type);
331   if (!scalarSize || !tensorSize) {
332     LLVM_DEBUG(llvm::dbgs()
333                << type << " illegal: cannot deduce element count\n");
334     return nullptr;
335   }
336 
337   auto arrayElemCount = *tensorSize / *scalarSize;
338   auto arrayElemType = convertScalarType(targetEnv, options, scalarType);
339   if (!arrayElemType)
340     return nullptr;
341   Optional<int64_t> arrayElemSize = getTypeNumBytes(options, arrayElemType);
342   if (!arrayElemSize) {
343     LLVM_DEBUG(llvm::dbgs()
344                << type << " illegal: cannot deduce converted element size\n");
345     return nullptr;
346   }
347 
348   return spirv::ArrayType::get(arrayElemType, arrayElemCount, *arrayElemSize);
349 }
350 
351 static Type convertBoolMemrefType(const spirv::TargetEnv &targetEnv,
352                                   const SPIRVTypeConverter::Options &options,
353                                   MemRefType type) {
354   if (!type.hasStaticShape()) {
355     LLVM_DEBUG(llvm::dbgs()
356                << type << " dynamic shape on i1 is not supported yet\n");
357     return nullptr;
358   }
359 
360   Optional<spirv::StorageClass> storageClass =
361       SPIRVTypeConverter::getStorageClassForMemorySpace(
362           type.getMemorySpaceAsInt());
363   if (!storageClass) {
364     LLVM_DEBUG(llvm::dbgs()
365                << type << " illegal: cannot convert memory space\n");
366     return nullptr;
367   }
368 
369   unsigned numBoolBits = options.boolNumBits;
370   if (numBoolBits != 8) {
371     LLVM_DEBUG(llvm::dbgs()
372                << "using non-8-bit storage for bool types unimplemented");
373     return nullptr;
374   }
375   auto elementType = IntegerType::get(type.getContext(), numBoolBits)
376                          .dyn_cast<spirv::ScalarType>();
377   if (!elementType)
378     return nullptr;
379   Type arrayElemType =
380       convertScalarType(targetEnv, options, elementType, storageClass);
381   if (!arrayElemType)
382     return nullptr;
383   Optional<int64_t> arrayElemSize = getTypeNumBytes(options, arrayElemType);
384   if (!arrayElemSize) {
385     LLVM_DEBUG(llvm::dbgs()
386                << type << " illegal: cannot deduce converted element size\n");
387     return nullptr;
388   }
389 
390   int64_t memrefSize = (type.getNumElements() * numBoolBits + 7) / 8;
391   auto arrayElemCount = (memrefSize + *arrayElemSize - 1) / *arrayElemSize;
392   auto arrayType =
393       spirv::ArrayType::get(arrayElemType, arrayElemCount, *arrayElemSize);
394 
395   // Wrap in a struct to satisfy Vulkan interface requirements. Memrefs with
396   // workgroup storage class do not need the struct to be laid out explicitly.
397   auto structType = *storageClass == spirv::StorageClass::Workgroup
398                         ? spirv::StructType::get(arrayType)
399                         : spirv::StructType::get(arrayType, 0);
400   return spirv::PointerType::get(structType, *storageClass);
401 }
402 
403 static Type convertMemrefType(const spirv::TargetEnv &targetEnv,
404                               const SPIRVTypeConverter::Options &options,
405                               MemRefType type) {
406   if (type.getElementType().isa<IntegerType>() &&
407       type.getElementTypeBitWidth() == 1) {
408     return convertBoolMemrefType(targetEnv, options, type);
409   }
410 
411   Optional<spirv::StorageClass> storageClass =
412       SPIRVTypeConverter::getStorageClassForMemorySpace(
413           type.getMemorySpaceAsInt());
414   if (!storageClass) {
415     LLVM_DEBUG(llvm::dbgs()
416                << type << " illegal: cannot convert memory space\n");
417     return nullptr;
418   }
419 
420   Type arrayElemType;
421   Type elementType = type.getElementType();
422   if (auto vecType = elementType.dyn_cast<VectorType>()) {
423     arrayElemType =
424         convertVectorType(targetEnv, options, vecType, storageClass);
425   } else if (auto scalarType = elementType.dyn_cast<spirv::ScalarType>()) {
426     arrayElemType =
427         convertScalarType(targetEnv, options, scalarType, storageClass);
428   } else {
429     LLVM_DEBUG(
430         llvm::dbgs()
431         << type
432         << " unhandled: can only convert scalar or vector element type\n");
433     return nullptr;
434   }
435   if (!arrayElemType)
436     return nullptr;
437 
438   Optional<int64_t> elementSize = getTypeNumBytes(options, elementType);
439   if (!elementSize) {
440     LLVM_DEBUG(llvm::dbgs()
441                << type << " illegal: cannot deduce element size\n");
442     return nullptr;
443   }
444 
445   Optional<int64_t> arrayElemSize = getTypeNumBytes(options, arrayElemType);
446   if (!arrayElemSize) {
447     LLVM_DEBUG(llvm::dbgs()
448                << type << " illegal: cannot deduce converted element size\n");
449     return nullptr;
450   }
451 
452   if (!type.hasStaticShape()) {
453     auto arrayType =
454         spirv::RuntimeArrayType::get(arrayElemType, *arrayElemSize);
455     // Wrap in a struct to satisfy Vulkan interface requirements.
456     auto structType = spirv::StructType::get(arrayType, 0);
457     return spirv::PointerType::get(structType, *storageClass);
458   }
459 
460   Optional<int64_t> memrefSize = getTypeNumBytes(options, type);
461   if (!memrefSize) {
462     LLVM_DEBUG(llvm::dbgs()
463                << type << " illegal: cannot deduce element count\n");
464     return nullptr;
465   }
466 
467   auto arrayElemCount = *memrefSize / *elementSize;
468 
469 
470   auto arrayType =
471       spirv::ArrayType::get(arrayElemType, arrayElemCount, *arrayElemSize);
472 
473   // Wrap in a struct to satisfy Vulkan interface requirements. Memrefs with
474   // workgroup storage class do not need the struct to be laid out explicitly.
475   auto structType = *storageClass == spirv::StorageClass::Workgroup
476                         ? spirv::StructType::get(arrayType)
477                         : spirv::StructType::get(arrayType, 0);
478   return spirv::PointerType::get(structType, *storageClass);
479 }
480 
481 SPIRVTypeConverter::SPIRVTypeConverter(spirv::TargetEnvAttr targetAttr,
482                                        Options options)
483     : targetEnv(targetAttr), options(options) {
484   // Add conversions. The order matters here: later ones will be tried earlier.
485 
486   // Allow all SPIR-V dialect specific types. This assumes all builtin types
487   // adopted in the SPIR-V dialect (i.e., IntegerType, FloatType, VectorType)
488   // were tried before.
489   //
490   // TODO: this assumes that the SPIR-V types are valid to use in
491   // the given target environment, which should be the case if the whole
492   // pipeline is driven by the same target environment. Still, we probably still
493   // want to validate and convert to be safe.
494   addConversion([](spirv::SPIRVType type) { return type; });
495 
496   addConversion([](IndexType indexType) {
497     return SPIRVTypeConverter::getIndexType(indexType.getContext());
498   });
499 
500   addConversion([this](IntegerType intType) -> Optional<Type> {
501     if (auto scalarType = intType.dyn_cast<spirv::ScalarType>())
502       return convertScalarType(this->targetEnv, this->options, scalarType);
503     return Type();
504   });
505 
506   addConversion([this](FloatType floatType) -> Optional<Type> {
507     if (auto scalarType = floatType.dyn_cast<spirv::ScalarType>())
508       return convertScalarType(this->targetEnv, this->options, scalarType);
509     return Type();
510   });
511 
512   addConversion([this](VectorType vectorType) {
513     return convertVectorType(this->targetEnv, this->options, vectorType);
514   });
515 
516   addConversion([this](TensorType tensorType) {
517     return convertTensorType(this->targetEnv, this->options, tensorType);
518   });
519 
520   addConversion([this](MemRefType memRefType) {
521     return convertMemrefType(this->targetEnv, this->options, memRefType);
522   });
523 }
524 
525 //===----------------------------------------------------------------------===//
526 // FuncOp Conversion Patterns
527 //===----------------------------------------------------------------------===//
528 
529 namespace {
530 /// A pattern for rewriting function signature to convert arguments of functions
531 /// to be of valid SPIR-V types.
532 class FuncOpConversion final : public OpConversionPattern<FuncOp> {
533 public:
534   using OpConversionPattern<FuncOp>::OpConversionPattern;
535 
536   LogicalResult
537   matchAndRewrite(FuncOp funcOp, ArrayRef<Value> operands,
538                   ConversionPatternRewriter &rewriter) const override;
539 };
540 } // namespace
541 
542 LogicalResult
543 FuncOpConversion::matchAndRewrite(FuncOp funcOp, ArrayRef<Value> operands,
544                                   ConversionPatternRewriter &rewriter) const {
545   auto fnType = funcOp.getType();
546   if (fnType.getNumResults() > 1)
547     return failure();
548 
549   TypeConverter::SignatureConversion signatureConverter(fnType.getNumInputs());
550   for (auto argType : enumerate(fnType.getInputs())) {
551     auto convertedType = getTypeConverter()->convertType(argType.value());
552     if (!convertedType)
553       return failure();
554     signatureConverter.addInputs(argType.index(), convertedType);
555   }
556 
557   Type resultType;
558   if (fnType.getNumResults() == 1) {
559     resultType = getTypeConverter()->convertType(fnType.getResult(0));
560     if (!resultType)
561       return failure();
562   }
563 
564   // Create the converted spv.func op.
565   auto newFuncOp = rewriter.create<spirv::FuncOp>(
566       funcOp.getLoc(), funcOp.getName(),
567       rewriter.getFunctionType(signatureConverter.getConvertedTypes(),
568                                resultType ? TypeRange(resultType)
569                                           : TypeRange()));
570 
571   // Copy over all attributes other than the function name and type.
572   for (const auto &namedAttr : funcOp->getAttrs()) {
573     if (namedAttr.first != impl::getTypeAttrName() &&
574         namedAttr.first != SymbolTable::getSymbolAttrName())
575       newFuncOp->setAttr(namedAttr.first, namedAttr.second);
576   }
577 
578   rewriter.inlineRegionBefore(funcOp.getBody(), newFuncOp.getBody(),
579                               newFuncOp.end());
580   if (failed(rewriter.convertRegionTypes(
581           &newFuncOp.getBody(), *getTypeConverter(), &signatureConverter)))
582     return failure();
583   rewriter.eraseOp(funcOp);
584   return success();
585 }
586 
587 void mlir::populateBuiltinFuncToSPIRVPatterns(SPIRVTypeConverter &typeConverter,
588                                               RewritePatternSet &patterns) {
589   patterns.add<FuncOpConversion>(typeConverter, patterns.getContext());
590 }
591 
592 //===----------------------------------------------------------------------===//
593 // Builtin Variables
594 //===----------------------------------------------------------------------===//
595 
596 static spirv::GlobalVariableOp getBuiltinVariable(Block &body,
597                                                   spirv::BuiltIn builtin) {
598   // Look through all global variables in the given `body` block and check if
599   // there is a spv.GlobalVariable that has the same `builtin` attribute.
600   for (auto varOp : body.getOps<spirv::GlobalVariableOp>()) {
601     if (auto builtinAttr = varOp->getAttrOfType<StringAttr>(
602             spirv::SPIRVDialect::getAttributeName(
603                 spirv::Decoration::BuiltIn))) {
604       auto varBuiltIn = spirv::symbolizeBuiltIn(builtinAttr.getValue());
605       if (varBuiltIn && varBuiltIn.getValue() == builtin) {
606         return varOp;
607       }
608     }
609   }
610   return nullptr;
611 }
612 
613 /// Gets name of global variable for a builtin.
614 static std::string getBuiltinVarName(spirv::BuiltIn builtin) {
615   return std::string("__builtin_var_") + stringifyBuiltIn(builtin).str() + "__";
616 }
617 
618 /// Gets or inserts a global variable for a builtin within `body` block.
619 static spirv::GlobalVariableOp
620 getOrInsertBuiltinVariable(Block &body, Location loc, spirv::BuiltIn builtin,
621                            OpBuilder &builder) {
622   if (auto varOp = getBuiltinVariable(body, builtin))
623     return varOp;
624 
625   OpBuilder::InsertionGuard guard(builder);
626   builder.setInsertionPointToStart(&body);
627 
628   spirv::GlobalVariableOp newVarOp;
629   switch (builtin) {
630   case spirv::BuiltIn::NumWorkgroups:
631   case spirv::BuiltIn::WorkgroupSize:
632   case spirv::BuiltIn::WorkgroupId:
633   case spirv::BuiltIn::LocalInvocationId:
634   case spirv::BuiltIn::GlobalInvocationId: {
635     auto ptrType = spirv::PointerType::get(
636         VectorType::get({3}, builder.getIntegerType(32)),
637         spirv::StorageClass::Input);
638     std::string name = getBuiltinVarName(builtin);
639     newVarOp =
640         builder.create<spirv::GlobalVariableOp>(loc, ptrType, name, builtin);
641     break;
642   }
643   case spirv::BuiltIn::SubgroupId:
644   case spirv::BuiltIn::NumSubgroups:
645   case spirv::BuiltIn::SubgroupSize: {
646     auto ptrType = spirv::PointerType::get(builder.getIntegerType(32),
647                                            spirv::StorageClass::Input);
648     std::string name = getBuiltinVarName(builtin);
649     newVarOp =
650         builder.create<spirv::GlobalVariableOp>(loc, ptrType, name, builtin);
651     break;
652   }
653   default:
654     emitError(loc, "unimplemented builtin variable generation for ")
655         << stringifyBuiltIn(builtin);
656   }
657   return newVarOp;
658 }
659 
660 Value mlir::spirv::getBuiltinVariableValue(Operation *op,
661                                            spirv::BuiltIn builtin,
662                                            OpBuilder &builder) {
663   Operation *parent = SymbolTable::getNearestSymbolTable(op->getParentOp());
664   if (!parent) {
665     op->emitError("expected operation to be within a module-like op");
666     return nullptr;
667   }
668 
669   spirv::GlobalVariableOp varOp = getOrInsertBuiltinVariable(
670       *parent->getRegion(0).begin(), op->getLoc(), builtin, builder);
671   Value ptr = builder.create<spirv::AddressOfOp>(op->getLoc(), varOp);
672   return builder.create<spirv::LoadOp>(op->getLoc(), ptr);
673 }
674 
675 //===----------------------------------------------------------------------===//
676 // Push constant storage
677 //===----------------------------------------------------------------------===//
678 
679 /// Returns the pointer type for the push constant storage containing
680 /// `elementCount` 32-bit integer values.
681 static spirv::PointerType getPushConstantStorageType(unsigned elementCount,
682                                                      Builder &builder) {
683   auto arrayType = spirv::ArrayType::get(
684       SPIRVTypeConverter::getIndexType(builder.getContext()), elementCount,
685       /*stride=*/4);
686   auto structType = spirv::StructType::get({arrayType}, /*offsetInfo=*/0);
687   return spirv::PointerType::get(structType, spirv::StorageClass::PushConstant);
688 }
689 
690 /// Returns the push constant varible containing `elementCount` 32-bit integer
691 /// values in `body`. Returns null op if such an op does not exit.
692 static spirv::GlobalVariableOp getPushConstantVariable(Block &body,
693                                                        unsigned elementCount) {
694   for (auto varOp : body.getOps<spirv::GlobalVariableOp>()) {
695     auto ptrType = varOp.type().cast<spirv::PointerType>();
696     // Note that Vulkan requires "There must be no more than one push constant
697     // block statically used per shader entry point." So we should always reuse
698     // the existing one.
699     if (ptrType.getStorageClass() == spirv::StorageClass::PushConstant) {
700       auto numElements = ptrType.getPointeeType()
701                              .cast<spirv::StructType>()
702                              .getElementType(0)
703                              .cast<spirv::ArrayType>()
704                              .getNumElements();
705       if (numElements == elementCount)
706         return varOp;
707     }
708   }
709   return nullptr;
710 }
711 
712 /// Gets or inserts a global variable for push constant storage containing
713 /// `elementCount` 32-bit integer values in `block`.
714 static spirv::GlobalVariableOp
715 getOrInsertPushConstantVariable(Location loc, Block &block,
716                                 unsigned elementCount, OpBuilder &b) {
717   if (auto varOp = getPushConstantVariable(block, elementCount))
718     return varOp;
719 
720   auto builder = OpBuilder::atBlockBegin(&block, b.getListener());
721   auto type = getPushConstantStorageType(elementCount, builder);
722   const char *name = "__push_constant_var__";
723   return builder.create<spirv::GlobalVariableOp>(loc, type, name,
724                                                  /*initializer=*/nullptr);
725 }
726 
727 Value spirv::getPushConstantValue(Operation *op, unsigned elementCount,
728                                   unsigned offset, OpBuilder &builder) {
729   Location loc = op->getLoc();
730   Operation *parent = SymbolTable::getNearestSymbolTable(op->getParentOp());
731   if (!parent) {
732     op->emitError("expected operation to be within a module-like op");
733     return nullptr;
734   }
735 
736   spirv::GlobalVariableOp varOp = getOrInsertPushConstantVariable(
737       loc, parent->getRegion(0).front(), elementCount, builder);
738 
739   auto i32Type = SPIRVTypeConverter::getIndexType(builder.getContext());
740   Value zeroOp = spirv::ConstantOp::getZero(i32Type, loc, builder);
741   Value offsetOp = builder.create<spirv::ConstantOp>(
742       loc, i32Type, builder.getI32IntegerAttr(offset));
743   auto addrOp = builder.create<spirv::AddressOfOp>(loc, varOp);
744   auto acOp = builder.create<spirv::AccessChainOp>(
745       loc, addrOp, llvm::makeArrayRef({zeroOp, offsetOp}));
746   return builder.create<spirv::LoadOp>(loc, acOp);
747 }
748 
749 //===----------------------------------------------------------------------===//
750 // Index calculation
751 //===----------------------------------------------------------------------===//
752 
753 Value mlir::spirv::linearizeIndex(ValueRange indices, ArrayRef<int64_t> strides,
754                                   int64_t offset, Location loc,
755                                   OpBuilder &builder) {
756   assert(indices.size() == strides.size() &&
757          "must provide indices for all dimensions");
758 
759   auto indexType = SPIRVTypeConverter::getIndexType(builder.getContext());
760 
761   // TODO: Consider moving to use affine.apply and patterns converting
762   // affine.apply to standard ops. This needs converting to SPIR-V passes to be
763   // broken down into progressive small steps so we can have intermediate steps
764   // using other dialects. At the moment SPIR-V is the final sink.
765 
766   Value linearizedIndex = builder.create<spirv::ConstantOp>(
767       loc, indexType, IntegerAttr::get(indexType, offset));
768   for (auto index : llvm::enumerate(indices)) {
769     Value strideVal = builder.create<spirv::ConstantOp>(
770         loc, indexType, IntegerAttr::get(indexType, strides[index.index()]));
771     Value update = builder.create<spirv::IMulOp>(loc, strideVal, index.value());
772     linearizedIndex =
773         builder.create<spirv::IAddOp>(loc, linearizedIndex, update);
774   }
775   return linearizedIndex;
776 }
777 
778 spirv::AccessChainOp mlir::spirv::getElementPtr(
779     SPIRVTypeConverter &typeConverter, MemRefType baseType, Value basePtr,
780     ValueRange indices, Location loc, OpBuilder &builder) {
781   // Get base and offset of the MemRefType and verify they are static.
782 
783   int64_t offset;
784   SmallVector<int64_t, 4> strides;
785   if (failed(getStridesAndOffset(baseType, strides, offset)) ||
786       llvm::is_contained(strides, MemRefType::getDynamicStrideOrOffset()) ||
787       offset == MemRefType::getDynamicStrideOrOffset()) {
788     return nullptr;
789   }
790 
791   auto indexType = typeConverter.getIndexType(builder.getContext());
792 
793   SmallVector<Value, 2> linearizedIndices;
794   auto zero = spirv::ConstantOp::getZero(indexType, loc, builder);
795 
796   // Add a '0' at the start to index into the struct.
797   linearizedIndices.push_back(zero);
798 
799   if (baseType.getRank() == 0) {
800     linearizedIndices.push_back(zero);
801   } else {
802     linearizedIndices.push_back(
803         linearizeIndex(indices, strides, offset, loc, builder));
804   }
805   return builder.create<spirv::AccessChainOp>(loc, basePtr, linearizedIndices);
806 }
807 
808 //===----------------------------------------------------------------------===//
809 // SPIR-V ConversionTarget
810 //===----------------------------------------------------------------------===//
811 
812 std::unique_ptr<SPIRVConversionTarget>
813 SPIRVConversionTarget::get(spirv::TargetEnvAttr targetAttr) {
814   std::unique_ptr<SPIRVConversionTarget> target(
815       // std::make_unique does not work here because the constructor is private.
816       new SPIRVConversionTarget(targetAttr));
817   SPIRVConversionTarget *targetPtr = target.get();
818   target->addDynamicallyLegalDialect<spirv::SPIRVDialect>(
819       // We need to capture the raw pointer here because it is stable:
820       // target will be destroyed once this function is returned.
821       [targetPtr](Operation *op) { return targetPtr->isLegalOp(op); });
822   return target;
823 }
824 
825 SPIRVConversionTarget::SPIRVConversionTarget(spirv::TargetEnvAttr targetAttr)
826     : ConversionTarget(*targetAttr.getContext()), targetEnv(targetAttr) {}
827 
828 bool SPIRVConversionTarget::isLegalOp(Operation *op) {
829   // Make sure this op is available at the given version. Ops not implementing
830   // QueryMinVersionInterface/QueryMaxVersionInterface are available to all
831   // SPIR-V versions.
832   if (auto minVersion = dyn_cast<spirv::QueryMinVersionInterface>(op))
833     if (minVersion.getMinVersion() > this->targetEnv.getVersion()) {
834       LLVM_DEBUG(llvm::dbgs()
835                  << op->getName() << " illegal: requiring min version "
836                  << spirv::stringifyVersion(minVersion.getMinVersion())
837                  << "\n");
838       return false;
839     }
840   if (auto maxVersion = dyn_cast<spirv::QueryMaxVersionInterface>(op))
841     if (maxVersion.getMaxVersion() < this->targetEnv.getVersion()) {
842       LLVM_DEBUG(llvm::dbgs()
843                  << op->getName() << " illegal: requiring max version "
844                  << spirv::stringifyVersion(maxVersion.getMaxVersion())
845                  << "\n");
846       return false;
847     }
848 
849   // Make sure this op's required extensions are allowed to use. Ops not
850   // implementing QueryExtensionInterface do not require extensions to be
851   // available.
852   if (auto extensions = dyn_cast<spirv::QueryExtensionInterface>(op))
853     if (failed(checkExtensionRequirements(op->getName(), this->targetEnv,
854                                           extensions.getExtensions())))
855       return false;
856 
857   // Make sure this op's required extensions are allowed to use. Ops not
858   // implementing QueryCapabilityInterface do not require capabilities to be
859   // available.
860   if (auto capabilities = dyn_cast<spirv::QueryCapabilityInterface>(op))
861     if (failed(checkCapabilityRequirements(op->getName(), this->targetEnv,
862                                            capabilities.getCapabilities())))
863       return false;
864 
865   SmallVector<Type, 4> valueTypes;
866   valueTypes.append(op->operand_type_begin(), op->operand_type_end());
867   valueTypes.append(op->result_type_begin(), op->result_type_end());
868 
869   // Special treatment for global variables, whose type requirements are
870   // conveyed by type attributes.
871   if (auto globalVar = dyn_cast<spirv::GlobalVariableOp>(op))
872     valueTypes.push_back(globalVar.type());
873 
874   // Make sure the op's operands/results use types that are allowed by the
875   // target environment.
876   SmallVector<ArrayRef<spirv::Extension>, 4> typeExtensions;
877   SmallVector<ArrayRef<spirv::Capability>, 8> typeCapabilities;
878   for (Type valueType : valueTypes) {
879     typeExtensions.clear();
880     valueType.cast<spirv::SPIRVType>().getExtensions(typeExtensions);
881     if (failed(checkExtensionRequirements(op->getName(), this->targetEnv,
882                                           typeExtensions)))
883       return false;
884 
885     typeCapabilities.clear();
886     valueType.cast<spirv::SPIRVType>().getCapabilities(typeCapabilities);
887     if (failed(checkCapabilityRequirements(op->getName(), this->targetEnv,
888                                            typeCapabilities)))
889       return false;
890   }
891 
892   return true;
893 }
894