MLIR 22.0.0git
Serializer.cpp
Go to the documentation of this file.
1//===- Serializer.cpp - MLIR SPIR-V Serializer ----------------------------===//
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 defines the MLIR SPIR-V module to SPIR-V binary serializer.
10//
11//===----------------------------------------------------------------------===//
12
13#include "Serializer.h"
14
21#include "llvm/ADT/STLExtras.h"
22#include "llvm/ADT/Sequence.h"
23#include "llvm/ADT/StringExtras.h"
24#include "llvm/ADT/TypeSwitch.h"
25#include "llvm/ADT/bit.h"
26#include "llvm/Support/Debug.h"
27#include <cstdint>
28#include <optional>
29
30#define DEBUG_TYPE "spirv-serialization"
31
32using namespace mlir;
33
34/// Returns the merge block if the given `op` is a structured control flow op.
35/// Otherwise returns nullptr.
37 if (auto selectionOp = dyn_cast<spirv::SelectionOp>(op))
38 return selectionOp.getMergeBlock();
39 if (auto loopOp = dyn_cast<spirv::LoopOp>(op))
40 return loopOp.getMergeBlock();
41 return nullptr;
42}
43
44/// Given a predecessor `block` for a block with arguments, returns the block
45/// that should be used as the parent block for SPIR-V OpPhi instructions
46/// corresponding to the block arguments.
48 // If the predecessor block in question is the entry block for a
49 // spirv.mlir.loop, we jump to this spirv.mlir.loop from its enclosing block.
50 if (block->isEntryBlock()) {
51 if (auto loopOp = dyn_cast<spirv::LoopOp>(block->getParentOp())) {
52 // Then the incoming parent block for OpPhi should be the merge block of
53 // the structured control flow op before this loop.
54 Operation *op = loopOp.getOperation();
55 while ((op = op->getPrevNode()) != nullptr)
56 if (Block *incomingBlock = getStructuredControlFlowOpMergeBlock(op))
57 return incomingBlock;
58 // Or the enclosing block itself if no structured control flow ops
59 // exists before this loop.
60 return loopOp->getBlock();
61 }
62 }
63
64 // Otherwise, we jump from the given predecessor block. Try to see if there is
65 // a structured control flow op inside it.
66 for (Operation &op : llvm::reverse(block->getOperations())) {
67 if (Block *incomingBlock = getStructuredControlFlowOpMergeBlock(&op))
68 return incomingBlock;
69 }
70 return block;
71}
72
73static bool isZeroValue(Attribute attr) {
74 if (auto floatAttr = dyn_cast<FloatAttr>(attr)) {
75 return floatAttr.getValue().isZero();
76 }
77 if (auto boolAttr = dyn_cast<BoolAttr>(attr)) {
78 return !boolAttr.getValue();
79 }
80 if (auto intAttr = dyn_cast<IntegerAttr>(attr)) {
81 return intAttr.getValue().isZero();
82 }
83 if (auto splatElemAttr = dyn_cast<SplatElementsAttr>(attr)) {
84 return isZeroValue(splatElemAttr.getSplatValue<Attribute>());
85 }
86 if (auto denseElemAttr = dyn_cast<DenseElementsAttr>(attr)) {
87 return all_of(denseElemAttr.getValues<Attribute>(), isZeroValue);
88 }
89 return false;
90}
91
92/// Move all functions declaration before functions definitions. In SPIR-V
93/// "declarations" are functions without a body and "definitions" functions
94/// with a body. This is stronger than necessary. It should be sufficient to
95/// ensure any declarations precede their uses and not all definitions, however
96/// this allows to avoid analysing every function in the module this way.
97static void moveFuncDeclarationsToTop(spirv::ModuleOp moduleOp) {
98 Block::OpListType &ops = moduleOp.getBody()->getOperations();
99 if (ops.empty())
100 return;
101 Operation &firstOp = ops.front();
102 for (Operation &op : llvm::drop_begin(ops))
103 if (auto funcOp = dyn_cast<spirv::FuncOp>(op))
104 if (funcOp.getBody().empty())
105 funcOp->moveBefore(&firstOp);
106}
107
108namespace mlir {
109namespace spirv {
110
111/// Encodes an SPIR-V instruction with the given `opcode` and `operands` into
112/// the given `binary` vector.
114 ArrayRef<uint32_t> operands) {
115 uint32_t wordCount = 1 + operands.size();
116 binary.push_back(spirv::getPrefixedOpcode(wordCount, op));
117 binary.append(operands.begin(), operands.end());
118}
119
120Serializer::Serializer(spirv::ModuleOp module,
121 const SerializationOptions &options)
122 : module(module), mlirBuilder(module.getContext()), options(options) {}
123
124LogicalResult Serializer::serialize() {
125 LLVM_DEBUG(llvm::dbgs() << "+++ starting serialization +++\n");
126
127 if (failed(module.verifyInvariants()))
128 return failure();
129
130 // TODO: handle the other sections
131 processCapability();
132 if (failed(processExtension())) {
133 return failure();
134 }
135 processMemoryModel();
136 processDebugInfo();
137
139
140 // Iterate over the module body to serialize it. Assumptions are that there is
141 // only one basic block in the moduleOp
142 for (auto &op : *module.getBody()) {
143 if (failed(processOperation(&op))) {
144 return failure();
145 }
146 }
147
148 LLVM_DEBUG(llvm::dbgs() << "+++ completed serialization +++\n");
149 return success();
150}
151
153 auto moduleSize = spirv::kHeaderWordCount + capabilities.size() +
154 extensions.size() + extendedSets.size() +
155 memoryModel.size() + entryPoints.size() +
156 executionModes.size() + decorations.size() +
157 typesGlobalValues.size() + functions.size() + graphs.size();
158
159 binary.clear();
160 binary.reserve(moduleSize);
161
162 spirv::appendModuleHeader(binary, module.getVceTriple()->getVersion(),
163 nextID);
164 binary.append(capabilities.begin(), capabilities.end());
165 binary.append(extensions.begin(), extensions.end());
166 binary.append(extendedSets.begin(), extendedSets.end());
167 binary.append(memoryModel.begin(), memoryModel.end());
168 binary.append(entryPoints.begin(), entryPoints.end());
169 binary.append(executionModes.begin(), executionModes.end());
170 binary.append(debug.begin(), debug.end());
171 binary.append(names.begin(), names.end());
172 binary.append(decorations.begin(), decorations.end());
173 binary.append(typesGlobalValues.begin(), typesGlobalValues.end());
174 binary.append(functions.begin(), functions.end());
175 binary.append(graphs.begin(), graphs.end());
176}
177
178#ifndef NDEBUG
180 os << "\n= Value <id> Map =\n\n";
181 for (auto valueIDPair : valueIDMap) {
182 Value val = valueIDPair.first;
183 os << " " << val << " "
184 << "id = " << valueIDPair.second << ' ';
185 if (auto *op = val.getDefiningOp()) {
186 os << "from op '" << op->getName() << "'";
187 } else if (auto arg = dyn_cast<BlockArgument>(val)) {
188 Block *block = arg.getOwner();
189 os << "from argument of block " << block << ' ';
190 os << " in op '" << block->getParentOp()->getName() << "'";
191 }
192 os << '\n';
193 }
194}
195#endif
196
197//===----------------------------------------------------------------------===//
198// Module structure
199//===----------------------------------------------------------------------===//
200
201uint32_t Serializer::getOrCreateFunctionID(StringRef fnName) {
202 auto funcID = funcIDMap.lookup(fnName);
203 if (!funcID) {
204 funcID = getNextID();
205 funcIDMap[fnName] = funcID;
206 }
207 return funcID;
208}
209
210void Serializer::processCapability() {
211 for (auto cap : module.getVceTriple()->getCapabilities())
212 encodeInstructionInto(capabilities, spirv::Opcode::OpCapability,
213 {static_cast<uint32_t>(cap)});
214}
215
216void Serializer::processDebugInfo() {
217 if (!options.emitDebugInfo)
218 return;
219 auto fileLoc = dyn_cast<FileLineColLoc>(module.getLoc());
220 auto fileName = fileLoc ? fileLoc.getFilename().strref() : "<unknown>";
221 fileID = getNextID();
222 SmallVector<uint32_t, 16> operands;
223 operands.push_back(fileID);
224 spirv::encodeStringLiteralInto(operands, fileName);
225 encodeInstructionInto(debug, spirv::Opcode::OpString, operands);
226 // TODO: Encode more debug instructions.
227}
228
229LogicalResult Serializer::processExtension() {
230 llvm::SmallVector<uint32_t, 16> extName;
231 llvm::SmallSet<Extension, 4> deducedExts(
232 llvm::from_range, module.getVceTriple()->getExtensions());
233 auto nonSemanticInfoExt = spirv::Extension::SPV_KHR_non_semantic_info;
234 if (options.emitDebugInfo && !deducedExts.contains(nonSemanticInfoExt)) {
235 TargetEnvAttr targetEnvAttr = lookupTargetEnvOrDefault(module);
236 if (!is_contained(targetEnvAttr.getExtensions(), nonSemanticInfoExt))
237 return module.emitError(
238 "SPV_KHR_non_semantic_info extension not available");
239 deducedExts.insert(nonSemanticInfoExt);
240 }
241 for (spirv::Extension ext : deducedExts) {
242 extName.clear();
243 spirv::encodeStringLiteralInto(extName, spirv::stringifyExtension(ext));
244 encodeInstructionInto(extensions, spirv::Opcode::OpExtension, extName);
245 }
246 return success();
247}
248
249void Serializer::processMemoryModel() {
250 StringAttr memoryModelName = module.getMemoryModelAttrName();
251 auto mm = static_cast<uint32_t>(
252 module->getAttrOfType<spirv::MemoryModelAttr>(memoryModelName)
253 .getValue());
254
255 StringAttr addressingModelName = module.getAddressingModelAttrName();
256 auto am = static_cast<uint32_t>(
257 module->getAttrOfType<spirv::AddressingModelAttr>(addressingModelName)
258 .getValue());
259
260 encodeInstructionInto(memoryModel, spirv::Opcode::OpMemoryModel, {am, mm});
261}
262
263static std::string getDecorationName(StringRef attrName) {
264 // convertToCamelFromSnakeCase will convert this to FpFastMathMode instead of
265 // expected FPFastMathMode.
266 if (attrName == "fp_fast_math_mode")
267 return "FPFastMathMode";
268 // similar here
269 if (attrName == "fp_rounding_mode")
270 return "FPRoundingMode";
271 // convertToCamelFromSnakeCase will not capitalize "INTEL".
272 if (attrName == "cache_control_load_intel")
273 return "CacheControlLoadINTEL";
274 if (attrName == "cache_control_store_intel")
275 return "CacheControlStoreINTEL";
276
277 return llvm::convertToCamelFromSnakeCase(attrName, /*capitalizeFirst=*/true);
278}
279
280template <typename AttrTy, typename EmitF>
281static LogicalResult processDecorationList(Location loc, Decoration decoration,
282 Attribute attrList,
283 StringRef attrName, EmitF emitter) {
284 auto arrayAttr = dyn_cast<ArrayAttr>(attrList);
285 if (!arrayAttr) {
286 return emitError(loc, "expecting array attribute of ")
287 << attrName << " for " << stringifyDecoration(decoration);
288 }
289 if (arrayAttr.empty()) {
290 return emitError(loc, "expecting non-empty array attribute of ")
291 << attrName << " for " << stringifyDecoration(decoration);
292 }
293 for (Attribute attr : arrayAttr.getValue()) {
294 auto cacheControlAttr = dyn_cast<AttrTy>(attr);
295 if (!cacheControlAttr) {
296 return emitError(loc, "expecting array attribute of ")
297 << attrName << " for " << stringifyDecoration(decoration);
298 }
299 // This named attribute encodes several decorations. Emit one per
300 // element in the array.
301 if (failed(emitter(cacheControlAttr)))
302 return failure();
303 }
304 return success();
305}
306
307LogicalResult Serializer::processDecorationAttr(Location loc, uint32_t resultID,
308 Decoration decoration,
309 Attribute attr) {
311 switch (decoration) {
312 case spirv::Decoration::LinkageAttributes: {
313 // Get the value of the Linkage Attributes
314 // e.g., LinkageAttributes=["linkageName", linkageType].
315 auto linkageAttr = dyn_cast<spirv::LinkageAttributesAttr>(attr);
316 auto linkageName = linkageAttr.getLinkageName();
317 auto linkageType = linkageAttr.getLinkageType().getValue();
318 // Encode the Linkage Name (string literal to uint32_t).
319 spirv::encodeStringLiteralInto(args, linkageName);
320 // Encode LinkageType & Add the Linkagetype to the args.
321 args.push_back(static_cast<uint32_t>(linkageType));
322 break;
323 }
324 case spirv::Decoration::FPFastMathMode:
325 if (auto intAttr = dyn_cast<FPFastMathModeAttr>(attr)) {
326 args.push_back(static_cast<uint32_t>(intAttr.getValue()));
327 break;
328 }
329 return emitError(loc, "expected FPFastMathModeAttr attribute for ")
330 << stringifyDecoration(decoration);
331 case spirv::Decoration::FPRoundingMode:
332 if (auto intAttr = dyn_cast<FPRoundingModeAttr>(attr)) {
333 args.push_back(static_cast<uint32_t>(intAttr.getValue()));
334 break;
335 }
336 return emitError(loc, "expected FPRoundingModeAttr attribute for ")
337 << stringifyDecoration(decoration);
338 case spirv::Decoration::Binding:
339 case spirv::Decoration::DescriptorSet:
340 case spirv::Decoration::Location:
341 case spirv::Decoration::Index:
342 if (auto intAttr = dyn_cast<IntegerAttr>(attr)) {
343 args.push_back(intAttr.getValue().getZExtValue());
344 break;
345 }
346 return emitError(loc, "expected integer attribute for ")
347 << stringifyDecoration(decoration);
348 case spirv::Decoration::BuiltIn:
349 if (auto strAttr = dyn_cast<StringAttr>(attr)) {
350 auto enumVal = spirv::symbolizeBuiltIn(strAttr.getValue());
351 if (enumVal) {
352 args.push_back(static_cast<uint32_t>(*enumVal));
353 break;
354 }
355 return emitError(loc, "invalid ")
356 << stringifyDecoration(decoration) << " decoration attribute "
357 << strAttr.getValue();
358 }
359 return emitError(loc, "expected string attribute for ")
360 << stringifyDecoration(decoration);
361 case spirv::Decoration::Aliased:
362 case spirv::Decoration::AliasedPointer:
363 case spirv::Decoration::Flat:
364 case spirv::Decoration::NonReadable:
365 case spirv::Decoration::NonWritable:
366 case spirv::Decoration::NoPerspective:
367 case spirv::Decoration::NoSignedWrap:
368 case spirv::Decoration::NoUnsignedWrap:
369 case spirv::Decoration::RelaxedPrecision:
370 case spirv::Decoration::Restrict:
371 case spirv::Decoration::RestrictPointer:
372 case spirv::Decoration::NoContraction:
373 case spirv::Decoration::Constant:
374 case spirv::Decoration::Block:
375 case spirv::Decoration::Invariant:
376 case spirv::Decoration::Patch:
377 case spirv::Decoration::Coherent:
378 // For unit attributes and decoration attributes, the args list
379 // has no values so we do nothing.
380 if (isa<UnitAttr, DecorationAttr>(attr))
381 break;
382 return emitError(loc,
383 "expected unit attribute or decoration attribute for ")
384 << stringifyDecoration(decoration);
385 case spirv::Decoration::CacheControlLoadINTEL:
387 loc, decoration, attr, "CacheControlLoadINTEL",
388 [&](CacheControlLoadINTELAttr attr) {
389 unsigned cacheLevel = attr.getCacheLevel();
390 LoadCacheControl loadCacheControl = attr.getLoadCacheControl();
391 return emitDecoration(
392 resultID, decoration,
393 {cacheLevel, static_cast<uint32_t>(loadCacheControl)});
394 });
395 case spirv::Decoration::CacheControlStoreINTEL:
397 loc, decoration, attr, "CacheControlStoreINTEL",
398 [&](CacheControlStoreINTELAttr attr) {
399 unsigned cacheLevel = attr.getCacheLevel();
400 StoreCacheControl storeCacheControl = attr.getStoreCacheControl();
401 return emitDecoration(
402 resultID, decoration,
403 {cacheLevel, static_cast<uint32_t>(storeCacheControl)});
404 });
405 default:
406 return emitError(loc, "unhandled decoration ")
407 << stringifyDecoration(decoration);
408 }
409 return emitDecoration(resultID, decoration, args);
410}
411
412LogicalResult Serializer::processDecoration(Location loc, uint32_t resultID,
413 NamedAttribute attr) {
414 StringRef attrName = attr.getName().strref();
415 std::string decorationName = getDecorationName(attrName);
416 std::optional<Decoration> decoration =
417 spirv::symbolizeDecoration(decorationName);
418 if (!decoration) {
419 return emitError(
420 loc, "non-argument attributes expected to have snake-case-ified "
421 "decoration name, unhandled attribute with name : ")
422 << attrName;
423 }
424 return processDecorationAttr(loc, resultID, *decoration, attr.getValue());
425}
426
427LogicalResult Serializer::processName(uint32_t resultID, StringRef name) {
428 assert(!name.empty() && "unexpected empty string for OpName");
429 if (!options.emitSymbolName)
430 return success();
431
432 SmallVector<uint32_t, 4> nameOperands;
433 nameOperands.push_back(resultID);
434 spirv::encodeStringLiteralInto(nameOperands, name);
435 encodeInstructionInto(names, spirv::Opcode::OpName, nameOperands);
436 return success();
437}
438
439template <>
440LogicalResult Serializer::processTypeDecoration<spirv::ArrayType>(
441 Location loc, spirv::ArrayType type, uint32_t resultID) {
442 if (unsigned stride = type.getArrayStride()) {
443 // OpDecorate %arrayTypeSSA ArrayStride strideLiteral
444 return emitDecoration(resultID, spirv::Decoration::ArrayStride, {stride});
445 }
446 return success();
447}
448
449template <>
450LogicalResult Serializer::processTypeDecoration<spirv::RuntimeArrayType>(
451 Location loc, spirv::RuntimeArrayType type, uint32_t resultID) {
452 if (unsigned stride = type.getArrayStride()) {
453 // OpDecorate %arrayTypeSSA ArrayStride strideLiteral
454 return emitDecoration(resultID, spirv::Decoration::ArrayStride, {stride});
455 }
456 return success();
457}
458
459LogicalResult Serializer::processMemberDecoration(
460 uint32_t structID,
461 const spirv::StructType::MemberDecorationInfo &memberDecoration) {
463 {structID, memberDecoration.memberIndex,
464 static_cast<uint32_t>(memberDecoration.decoration)});
465 if (memberDecoration.hasValue()) {
466 args.push_back(
467 cast<IntegerAttr>(memberDecoration.decorationValue).getInt());
468 }
469 encodeInstructionInto(decorations, spirv::Opcode::OpMemberDecorate, args);
470 return success();
471}
472
473//===----------------------------------------------------------------------===//
474// Type
475//===----------------------------------------------------------------------===//
476
477// According to the SPIR-V spec "Validation Rules for Shader Capabilities":
478// "Composite objects in the StorageBuffer, PhysicalStorageBuffer, Uniform, and
479// PushConstant Storage Classes must be explicitly laid out."
480bool Serializer::isInterfaceStructPtrType(Type type) const {
481 if (auto ptrType = dyn_cast<spirv::PointerType>(type)) {
482 switch (ptrType.getStorageClass()) {
483 case spirv::StorageClass::PhysicalStorageBuffer:
484 case spirv::StorageClass::PushConstant:
485 case spirv::StorageClass::StorageBuffer:
486 case spirv::StorageClass::Uniform:
487 return isa<spirv::StructType>(ptrType.getPointeeType());
488 default:
489 break;
490 }
491 }
492 return false;
493}
494
495LogicalResult Serializer::processType(Location loc, Type type,
496 uint32_t &typeID) {
497 // Maintains a set of names for nested identified struct types. This is used
498 // to properly serialize recursive references.
499 SetVector<StringRef> serializationCtx;
500 return processTypeImpl(loc, type, typeID, serializationCtx);
501}
502
503LogicalResult
504Serializer::processTypeImpl(Location loc, Type type, uint32_t &typeID,
505 SetVector<StringRef> &serializationCtx) {
506
507 // Map unsigned integer types to singless integer types.
508 // This is needed otherwise the generated spirv assembly will contain
509 // twice a type declaration (like OpTypeInt 32 0) which is no permitted and
510 // such module fails validation. Indeed at MLIR level the two types are
511 // different and lookup in the cache below misses.
512 // Note: This conversion needs to happen here before the type is looked up in
513 // the cache.
514 if (type.isUnsignedInteger()) {
515 type = IntegerType::get(loc->getContext(), type.getIntOrFloatBitWidth(),
516 IntegerType::SignednessSemantics::Signless);
517 }
518
519 typeID = getTypeID(type);
520 if (typeID)
521 return success();
522
523 typeID = getNextID();
524 SmallVector<uint32_t, 4> operands;
525
526 operands.push_back(typeID);
527 auto typeEnum = spirv::Opcode::OpTypeVoid;
528 bool deferSerialization = false;
529
530 if ((isa<FunctionType>(type) &&
531 succeeded(prepareFunctionType(loc, cast<FunctionType>(type), typeEnum,
532 operands))) ||
533 (isa<GraphType>(type) &&
534 succeeded(
535 prepareGraphType(loc, cast<GraphType>(type), typeEnum, operands))) ||
536 succeeded(prepareBasicType(loc, type, typeID, typeEnum, operands,
537 deferSerialization, serializationCtx))) {
538 if (deferSerialization)
539 return success();
540
541 typeIDMap[type] = typeID;
542
543 encodeInstructionInto(typesGlobalValues, typeEnum, operands);
544
545 if (recursiveStructInfos.count(type) != 0) {
546 // This recursive struct type is emitted already, now the OpTypePointer
547 // instructions referring to recursive references are emitted as well.
548 for (auto &ptrInfo : recursiveStructInfos[type]) {
549 // TODO: This might not work if more than 1 recursive reference is
550 // present in the struct.
551 SmallVector<uint32_t, 4> ptrOperands;
552 ptrOperands.push_back(ptrInfo.pointerTypeID);
553 ptrOperands.push_back(static_cast<uint32_t>(ptrInfo.storageClass));
554 ptrOperands.push_back(typeIDMap[type]);
555
556 encodeInstructionInto(typesGlobalValues, spirv::Opcode::OpTypePointer,
557 ptrOperands);
558 }
559
560 recursiveStructInfos[type].clear();
561 }
562
563 return success();
564 }
565
566 return emitError(loc, "failed to process type: ") << type;
567}
568
569LogicalResult Serializer::prepareBasicType(
570 Location loc, Type type, uint32_t resultID, spirv::Opcode &typeEnum,
571 SmallVectorImpl<uint32_t> &operands, bool &deferSerialization,
572 SetVector<StringRef> &serializationCtx) {
573 deferSerialization = false;
574
575 if (isVoidType(type)) {
576 typeEnum = spirv::Opcode::OpTypeVoid;
577 return success();
578 }
579
580 if (auto intType = dyn_cast<IntegerType>(type)) {
581 if (intType.getWidth() == 1) {
582 typeEnum = spirv::Opcode::OpTypeBool;
583 return success();
584 }
585
586 typeEnum = spirv::Opcode::OpTypeInt;
587 operands.push_back(intType.getWidth());
588 // SPIR-V OpTypeInt "Signedness specifies whether there are signed semantics
589 // to preserve or validate.
590 // 0 indicates unsigned, or no signedness semantics
591 // 1 indicates signed semantics."
592 operands.push_back(intType.isSigned() ? 1 : 0);
593 return success();
594 }
595
596 if (auto floatType = dyn_cast<FloatType>(type)) {
597 typeEnum = spirv::Opcode::OpTypeFloat;
598 operands.push_back(floatType.getWidth());
599 if (floatType.isBF16()) {
600 operands.push_back(static_cast<uint32_t>(spirv::FPEncoding::BFloat16KHR));
601 }
602 return success();
603 }
604
605 if (auto vectorType = dyn_cast<VectorType>(type)) {
606 uint32_t elementTypeID = 0;
607 if (failed(processTypeImpl(loc, vectorType.getElementType(), elementTypeID,
608 serializationCtx))) {
609 return failure();
610 }
611 typeEnum = spirv::Opcode::OpTypeVector;
612 operands.push_back(elementTypeID);
613 operands.push_back(vectorType.getNumElements());
614 return success();
615 }
616
617 if (auto imageType = dyn_cast<spirv::ImageType>(type)) {
618 typeEnum = spirv::Opcode::OpTypeImage;
619 uint32_t sampledTypeID = 0;
620 if (failed(processType(loc, imageType.getElementType(), sampledTypeID)))
621 return failure();
622
623 llvm::append_values(operands, sampledTypeID,
624 static_cast<uint32_t>(imageType.getDim()),
625 static_cast<uint32_t>(imageType.getDepthInfo()),
626 static_cast<uint32_t>(imageType.getArrayedInfo()),
627 static_cast<uint32_t>(imageType.getSamplingInfo()),
628 static_cast<uint32_t>(imageType.getSamplerUseInfo()),
629 static_cast<uint32_t>(imageType.getImageFormat()));
630 return success();
631 }
632
633 if (auto arrayType = dyn_cast<spirv::ArrayType>(type)) {
634 typeEnum = spirv::Opcode::OpTypeArray;
635 uint32_t elementTypeID = 0;
636 if (failed(processTypeImpl(loc, arrayType.getElementType(), elementTypeID,
637 serializationCtx))) {
638 return failure();
639 }
640 operands.push_back(elementTypeID);
641 if (auto elementCountID = prepareConstantInt(
642 loc, mlirBuilder.getI32IntegerAttr(arrayType.getNumElements()))) {
643 operands.push_back(elementCountID);
644 }
645 return processTypeDecoration(loc, arrayType, resultID);
646 }
647
648 if (auto ptrType = dyn_cast<spirv::PointerType>(type)) {
649 uint32_t pointeeTypeID = 0;
650 spirv::StructType pointeeStruct =
651 dyn_cast<spirv::StructType>(ptrType.getPointeeType());
652
653 if (pointeeStruct && pointeeStruct.isIdentified() &&
654 serializationCtx.count(pointeeStruct.getIdentifier()) != 0) {
655 // A recursive reference to an enclosing struct is found.
656 //
657 // 1. Prepare an OpTypeForwardPointer with resultID and the ptr storage
658 // class as operands.
659 SmallVector<uint32_t, 2> forwardPtrOperands;
660 forwardPtrOperands.push_back(resultID);
661 forwardPtrOperands.push_back(
662 static_cast<uint32_t>(ptrType.getStorageClass()));
663
664 encodeInstructionInto(typesGlobalValues,
665 spirv::Opcode::OpTypeForwardPointer,
666 forwardPtrOperands);
667
668 // 2. Find the pointee (enclosing) struct.
669 auto structType = spirv::StructType::getIdentified(
670 module.getContext(), pointeeStruct.getIdentifier());
671
672 if (!structType)
673 return failure();
674
675 // 3. Mark the OpTypePointer that is supposed to be emitted by this call
676 // as deferred.
677 deferSerialization = true;
678
679 // 4. Record the info needed to emit the deferred OpTypePointer
680 // instruction when the enclosing struct is completely serialized.
681 recursiveStructInfos[structType].push_back(
682 {resultID, ptrType.getStorageClass()});
683 } else {
684 if (failed(processTypeImpl(loc, ptrType.getPointeeType(), pointeeTypeID,
685 serializationCtx)))
686 return failure();
687 }
688
689 typeEnum = spirv::Opcode::OpTypePointer;
690 operands.push_back(static_cast<uint32_t>(ptrType.getStorageClass()));
691 operands.push_back(pointeeTypeID);
692
693 // TODO: Now struct decorations are supported this code may not be
694 // necessary. However, it is left to support backwards compatibility.
695 // Ideally, Block decorations should be inserted when converting to SPIR-V.
696 if (isInterfaceStructPtrType(ptrType)) {
697 auto structType = cast<spirv::StructType>(ptrType.getPointeeType());
698 if (!structType.hasDecoration(spirv::Decoration::Block))
699 if (failed(emitDecoration(getTypeID(pointeeStruct),
700 spirv::Decoration::Block)))
701 return emitError(loc, "cannot decorate ")
702 << pointeeStruct << " with Block decoration";
703 }
704
705 return success();
706 }
707
708 if (auto runtimeArrayType = dyn_cast<spirv::RuntimeArrayType>(type)) {
709 uint32_t elementTypeID = 0;
710 if (failed(processTypeImpl(loc, runtimeArrayType.getElementType(),
711 elementTypeID, serializationCtx))) {
712 return failure();
713 }
714 typeEnum = spirv::Opcode::OpTypeRuntimeArray;
715 operands.push_back(elementTypeID);
716 return processTypeDecoration(loc, runtimeArrayType, resultID);
717 }
718
719 if (auto sampledImageType = dyn_cast<spirv::SampledImageType>(type)) {
720 typeEnum = spirv::Opcode::OpTypeSampledImage;
721 uint32_t imageTypeID = 0;
722 if (failed(
723 processType(loc, sampledImageType.getImageType(), imageTypeID))) {
724 return failure();
725 }
726 operands.push_back(imageTypeID);
727 return success();
728 }
729
730 if (auto structType = dyn_cast<spirv::StructType>(type)) {
731 if (structType.isIdentified()) {
732 if (failed(processName(resultID, structType.getIdentifier())))
733 return failure();
734 serializationCtx.insert(structType.getIdentifier());
735 }
736
737 bool hasOffset = structType.hasOffset();
738 for (auto elementIndex :
739 llvm::seq<uint32_t>(0, structType.getNumElements())) {
740 uint32_t elementTypeID = 0;
741 if (failed(processTypeImpl(loc, structType.getElementType(elementIndex),
742 elementTypeID, serializationCtx))) {
743 return failure();
744 }
745 operands.push_back(elementTypeID);
746 if (hasOffset) {
747 auto intType = IntegerType::get(structType.getContext(), 32);
748 // Decorate each struct member with an offset
749 spirv::StructType::MemberDecorationInfo offsetDecoration{
750 elementIndex, spirv::Decoration::Offset,
751 IntegerAttr::get(intType,
752 structType.getMemberOffset(elementIndex))};
753 if (failed(processMemberDecoration(resultID, offsetDecoration))) {
754 return emitError(loc, "cannot decorate ")
755 << elementIndex << "-th member of " << structType
756 << " with its offset";
757 }
758 }
759 }
760 SmallVector<spirv::StructType::MemberDecorationInfo, 4> memberDecorations;
761 structType.getMemberDecorations(memberDecorations);
762
763 for (auto &memberDecoration : memberDecorations) {
764 if (failed(processMemberDecoration(resultID, memberDecoration))) {
765 return emitError(loc, "cannot decorate ")
766 << static_cast<uint32_t>(memberDecoration.memberIndex)
767 << "-th member of " << structType << " with "
768 << stringifyDecoration(memberDecoration.decoration);
769 }
770 }
771
772 SmallVector<spirv::StructType::StructDecorationInfo, 1> structDecorations;
773 structType.getStructDecorations(structDecorations);
774
775 for (spirv::StructType::StructDecorationInfo &structDecoration :
776 structDecorations) {
777 if (failed(processDecorationAttr(loc, resultID,
778 structDecoration.decoration,
779 structDecoration.decorationValue))) {
780 return emitError(loc, "cannot decorate struct ")
781 << structType << " with "
782 << stringifyDecoration(structDecoration.decoration);
783 }
784 }
785
786 typeEnum = spirv::Opcode::OpTypeStruct;
787
788 if (structType.isIdentified())
789 serializationCtx.remove(structType.getIdentifier());
790
791 return success();
792 }
793
794 if (auto cooperativeMatrixType =
795 dyn_cast<spirv::CooperativeMatrixType>(type)) {
796 uint32_t elementTypeID = 0;
797 if (failed(processTypeImpl(loc, cooperativeMatrixType.getElementType(),
798 elementTypeID, serializationCtx))) {
799 return failure();
800 }
801 typeEnum = spirv::Opcode::OpTypeCooperativeMatrixKHR;
802 auto getConstantOp = [&](uint32_t id) {
803 auto attr = IntegerAttr::get(IntegerType::get(type.getContext(), 32), id);
804 return prepareConstantInt(loc, attr);
805 };
806 llvm::append_values(
807 operands, elementTypeID,
808 getConstantOp(static_cast<uint32_t>(cooperativeMatrixType.getScope())),
809 getConstantOp(cooperativeMatrixType.getRows()),
810 getConstantOp(cooperativeMatrixType.getColumns()),
811 getConstantOp(static_cast<uint32_t>(cooperativeMatrixType.getUse())));
812 return success();
813 }
814
815 if (auto matrixType = dyn_cast<spirv::MatrixType>(type)) {
816 uint32_t elementTypeID = 0;
817 if (failed(processTypeImpl(loc, matrixType.getColumnType(), elementTypeID,
818 serializationCtx))) {
819 return failure();
820 }
821 typeEnum = spirv::Opcode::OpTypeMatrix;
822 llvm::append_values(operands, elementTypeID, matrixType.getNumColumns());
823 return success();
824 }
825
826 if (auto tensorArmType = dyn_cast<TensorArmType>(type)) {
827 uint32_t elementTypeID = 0;
828 uint32_t rank = 0;
829 uint32_t shapeID = 0;
830 uint32_t rankID = 0;
831 if (failed(processTypeImpl(loc, tensorArmType.getElementType(),
832 elementTypeID, serializationCtx))) {
833 return failure();
834 }
835 if (tensorArmType.hasRank()) {
836 ArrayRef<int64_t> dims = tensorArmType.getShape();
837 rank = dims.size();
838 rankID = prepareConstantInt(loc, mlirBuilder.getI32IntegerAttr(rank));
839 if (rankID == 0) {
840 return failure();
841 }
842
843 bool shaped = llvm::all_of(dims, [](const auto &dim) { return dim > 0; });
844 if (rank > 0 && shaped) {
845 auto I32Type = IntegerType::get(type.getContext(), 32);
846 auto shapeType = ArrayType::get(I32Type, rank);
847 if (rank == 1) {
848 SmallVector<uint64_t, 1> index(rank);
849 shapeID = prepareDenseElementsConstant(
850 loc, shapeType,
851 mlirBuilder.getI32TensorAttr(SmallVector<int32_t>(dims)), 0,
852 index);
853 } else {
854 shapeID = prepareArrayConstant(
855 loc, shapeType,
856 mlirBuilder.getI32ArrayAttr(SmallVector<int32_t>(dims)));
857 }
858 if (shapeID == 0) {
859 return failure();
860 }
861 }
862 }
863 typeEnum = spirv::Opcode::OpTypeTensorARM;
864 operands.push_back(elementTypeID);
865 if (rankID == 0)
866 return success();
867 operands.push_back(rankID);
868 if (shapeID == 0)
869 return success();
870 operands.push_back(shapeID);
871 return success();
872 }
873
874 // TODO: Handle other types.
875 return emitError(loc, "unhandled type in serialization: ") << type;
876}
877
878LogicalResult
879Serializer::prepareFunctionType(Location loc, FunctionType type,
880 spirv::Opcode &typeEnum,
881 SmallVectorImpl<uint32_t> &operands) {
882 typeEnum = spirv::Opcode::OpTypeFunction;
883 assert(type.getNumResults() <= 1 &&
884 "serialization supports only a single return value");
885 uint32_t resultID = 0;
886 if (failed(processType(
887 loc, type.getNumResults() == 1 ? type.getResult(0) : getVoidType(),
888 resultID))) {
889 return failure();
890 }
891 operands.push_back(resultID);
892 for (auto &res : type.getInputs()) {
893 uint32_t argTypeID = 0;
894 if (failed(processType(loc, res, argTypeID))) {
895 return failure();
896 }
897 operands.push_back(argTypeID);
898 }
899 return success();
900}
901
902LogicalResult
903Serializer::prepareGraphType(Location loc, GraphType type,
904 spirv::Opcode &typeEnum,
905 SmallVectorImpl<uint32_t> &operands) {
906 typeEnum = spirv::Opcode::OpTypeGraphARM;
907 assert(type.getNumResults() >= 1 &&
908 "serialization requires at least a return value");
909
910 operands.push_back(type.getNumInputs());
911
912 for (Type argType : type.getInputs()) {
913 uint32_t argTypeID = 0;
914 if (failed(processType(loc, argType, argTypeID)))
915 return failure();
916 operands.push_back(argTypeID);
917 }
918
919 for (Type resType : type.getResults()) {
920 uint32_t resTypeID = 0;
921 if (failed(processType(loc, resType, resTypeID)))
922 return failure();
923 operands.push_back(resTypeID);
924 }
925
926 return success();
927}
928
929//===----------------------------------------------------------------------===//
930// Constant
931//===----------------------------------------------------------------------===//
932
933uint32_t Serializer::prepareConstant(Location loc, Type constType,
934 Attribute valueAttr) {
935 if (auto id = prepareConstantScalar(loc, valueAttr)) {
936 return id;
937 }
938
939 // This is a composite literal. We need to handle each component separately
940 // and then emit an OpConstantComposite for the whole.
941
942 if (auto id = getConstantID(valueAttr)) {
943 return id;
944 }
945
946 uint32_t typeID = 0;
947 if (failed(processType(loc, constType, typeID))) {
948 return 0;
949 }
950
951 uint32_t resultID = 0;
952 if (auto attr = dyn_cast<DenseElementsAttr>(valueAttr)) {
953 int rank = dyn_cast<ShapedType>(attr.getType()).getRank();
954 SmallVector<uint64_t, 4> index(rank);
955 resultID = prepareDenseElementsConstant(loc, constType, attr,
956 /*dim=*/0, index);
957 } else if (auto arrayAttr = dyn_cast<ArrayAttr>(valueAttr)) {
958 resultID = prepareArrayConstant(loc, constType, arrayAttr);
959 }
960
961 if (resultID == 0) {
962 emitError(loc, "cannot serialize attribute: ") << valueAttr;
963 return 0;
964 }
965
966 constIDMap[valueAttr] = resultID;
967 return resultID;
968}
969
970uint32_t Serializer::prepareArrayConstant(Location loc, Type constType,
971 ArrayAttr attr) {
972 uint32_t typeID = 0;
973 if (failed(processType(loc, constType, typeID))) {
974 return 0;
975 }
976
977 uint32_t resultID = getNextID();
978 SmallVector<uint32_t, 4> operands = {typeID, resultID};
979 operands.reserve(attr.size() + 2);
980 auto elementType = cast<spirv::ArrayType>(constType).getElementType();
981 for (Attribute elementAttr : attr) {
982 if (auto elementID = prepareConstant(loc, elementType, elementAttr)) {
983 operands.push_back(elementID);
984 } else {
985 return 0;
986 }
987 }
988 spirv::Opcode opcode = spirv::Opcode::OpConstantComposite;
989 encodeInstructionInto(typesGlobalValues, opcode, operands);
990
991 return resultID;
992}
993
994// TODO: Turn the below function into iterative function, instead of
995// recursive function.
996uint32_t
997Serializer::prepareDenseElementsConstant(Location loc, Type constType,
998 DenseElementsAttr valueAttr, int dim,
999 MutableArrayRef<uint64_t> index) {
1000 auto shapedType = dyn_cast<ShapedType>(valueAttr.getType());
1001 assert(dim <= shapedType.getRank());
1002 if (shapedType.getRank() == dim) {
1003 if (auto attr = dyn_cast<DenseIntElementsAttr>(valueAttr)) {
1004 return attr.getType().getElementType().isInteger(1)
1005 ? prepareConstantBool(loc, attr.getValues<BoolAttr>()[index])
1006 : prepareConstantInt(loc,
1007 attr.getValues<IntegerAttr>()[index]);
1008 }
1009 if (auto attr = dyn_cast<DenseFPElementsAttr>(valueAttr)) {
1010 return prepareConstantFp(loc, attr.getValues<FloatAttr>()[index]);
1011 }
1012 return 0;
1013 }
1014
1015 uint32_t typeID = 0;
1016 if (failed(processType(loc, constType, typeID))) {
1017 return 0;
1018 }
1019
1020 int64_t numberOfConstituents = shapedType.getDimSize(dim);
1021 uint32_t resultID = getNextID();
1022 SmallVector<uint32_t, 4> operands = {typeID, resultID};
1023 auto elementType = cast<spirv::CompositeType>(constType).getElementType(0);
1024 if (auto tensorArmType = dyn_cast<spirv::TensorArmType>(constType)) {
1025 ArrayRef<int64_t> innerShape = tensorArmType.getShape().drop_front();
1026 if (!innerShape.empty())
1027 elementType = spirv::TensorArmType::get(innerShape, elementType);
1028 }
1029
1030 // "If the Result Type is a cooperative matrix type, then there must be only
1031 // one Constituent, with scalar type matching the cooperative matrix Component
1032 // Type, and all components of the matrix are initialized to that value."
1033 // (https://github.khronos.org/SPIRV-Registry/extensions/KHR/SPV_KHR_cooperative_matrix.html)
1034 if (isa<spirv::CooperativeMatrixType>(constType)) {
1035 if (!valueAttr.isSplat()) {
1036 emitError(
1037 loc,
1038 "cannot serialize a non-splat value for a cooperative matrix type");
1039 return 0;
1040 }
1041 // numberOfConstituents is 1, so we only need one more elements in the
1042 // SmallVector, so the total is 3 (1 + 2).
1043 operands.reserve(3);
1044 // We set dim directly to `shapedType.getRank()` so the recursive call
1045 // directly returns the scalar type.
1046 if (auto elementID = prepareDenseElementsConstant(
1047 loc, elementType, valueAttr, /*dim=*/shapedType.getRank(), index)) {
1048 operands.push_back(elementID);
1049 } else {
1050 return 0;
1051 }
1052 } else if (isa<spirv::TensorArmType>(constType) && isZeroValue(valueAttr)) {
1053 encodeInstructionInto(typesGlobalValues, spirv::Opcode::OpConstantNull,
1054 {typeID, resultID});
1055 return resultID;
1056 } else {
1057 operands.reserve(numberOfConstituents + 2);
1058 for (int i = 0; i < numberOfConstituents; ++i) {
1059 index[dim] = i;
1060 if (auto elementID = prepareDenseElementsConstant(
1061 loc, elementType, valueAttr, dim + 1, index)) {
1062 operands.push_back(elementID);
1063 } else {
1064 return 0;
1065 }
1066 }
1067 }
1068 spirv::Opcode opcode = spirv::Opcode::OpConstantComposite;
1069 encodeInstructionInto(typesGlobalValues, opcode, operands);
1070
1071 return resultID;
1072}
1073
1074uint32_t Serializer::prepareConstantScalar(Location loc, Attribute valueAttr,
1075 bool isSpec) {
1076 if (auto floatAttr = dyn_cast<FloatAttr>(valueAttr)) {
1077 return prepareConstantFp(loc, floatAttr, isSpec);
1078 }
1079 if (auto boolAttr = dyn_cast<BoolAttr>(valueAttr)) {
1080 return prepareConstantBool(loc, boolAttr, isSpec);
1081 }
1082 if (auto intAttr = dyn_cast<IntegerAttr>(valueAttr)) {
1083 return prepareConstantInt(loc, intAttr, isSpec);
1084 }
1085
1086 return 0;
1087}
1088
1089uint32_t Serializer::prepareConstantBool(Location loc, BoolAttr boolAttr,
1090 bool isSpec) {
1091 if (!isSpec) {
1092 // We can de-duplicate normal constants, but not specialization constants.
1093 if (auto id = getConstantID(boolAttr)) {
1094 return id;
1095 }
1096 }
1097
1098 // Process the type for this bool literal
1099 uint32_t typeID = 0;
1100 if (failed(processType(loc, cast<IntegerAttr>(boolAttr).getType(), typeID))) {
1101 return 0;
1102 }
1103
1104 auto resultID = getNextID();
1105 auto opcode = boolAttr.getValue()
1106 ? (isSpec ? spirv::Opcode::OpSpecConstantTrue
1107 : spirv::Opcode::OpConstantTrue)
1108 : (isSpec ? spirv::Opcode::OpSpecConstantFalse
1109 : spirv::Opcode::OpConstantFalse);
1110 encodeInstructionInto(typesGlobalValues, opcode, {typeID, resultID});
1111
1112 if (!isSpec) {
1113 constIDMap[boolAttr] = resultID;
1114 }
1115 return resultID;
1116}
1117
1118uint32_t Serializer::prepareConstantInt(Location loc, IntegerAttr intAttr,
1119 bool isSpec) {
1120 if (!isSpec) {
1121 // We can de-duplicate normal constants, but not specialization constants.
1122 if (auto id = getConstantID(intAttr)) {
1123 return id;
1124 }
1125 }
1126
1127 // Process the type for this integer literal
1128 uint32_t typeID = 0;
1129 if (failed(processType(loc, intAttr.getType(), typeID))) {
1130 return 0;
1131 }
1132
1133 auto resultID = getNextID();
1134 APInt value = intAttr.getValue();
1135 unsigned bitwidth = value.getBitWidth();
1136 bool isSigned = intAttr.getType().isSignedInteger();
1137 auto opcode =
1138 isSpec ? spirv::Opcode::OpSpecConstant : spirv::Opcode::OpConstant;
1139
1140 switch (bitwidth) {
1141 // According to SPIR-V spec, "When the type's bit width is less than
1142 // 32-bits, the literal's value appears in the low-order bits of the word,
1143 // and the high-order bits must be 0 for a floating-point type, or 0 for an
1144 // integer type with Signedness of 0, or sign extended when Signedness
1145 // is 1."
1146 case 32:
1147 case 16:
1148 case 8: {
1149 uint32_t word = 0;
1150 if (isSigned) {
1151 word = static_cast<int32_t>(value.getSExtValue());
1152 } else {
1153 word = static_cast<uint32_t>(value.getZExtValue());
1154 }
1155 encodeInstructionInto(typesGlobalValues, opcode, {typeID, resultID, word});
1156 } break;
1157 // According to SPIR-V spec: "When the type's bit width is larger than one
1158 // word, the literal’s low-order words appear first."
1159 case 64: {
1160 struct DoubleWord {
1161 uint32_t word1;
1162 uint32_t word2;
1163 } words;
1164 if (isSigned) {
1165 words = llvm::bit_cast<DoubleWord>(value.getSExtValue());
1166 } else {
1167 words = llvm::bit_cast<DoubleWord>(value.getZExtValue());
1168 }
1169 encodeInstructionInto(typesGlobalValues, opcode,
1170 {typeID, resultID, words.word1, words.word2});
1171 } break;
1172 default: {
1173 std::string valueStr;
1174 llvm::raw_string_ostream rss(valueStr);
1175 value.print(rss, /*isSigned=*/false);
1176
1177 emitError(loc, "cannot serialize ")
1178 << bitwidth << "-bit integer literal: " << valueStr;
1179 return 0;
1180 }
1181 }
1182
1183 if (!isSpec) {
1184 constIDMap[intAttr] = resultID;
1185 }
1186 return resultID;
1187}
1188
1189uint32_t Serializer::prepareGraphConstantId(Location loc, Type graphConstType,
1190 IntegerAttr intAttr) {
1191 // De-duplicate graph constants.
1192 if (uint32_t id = getGraphConstantARMId(intAttr)) {
1193 return id;
1194 }
1195
1196 // Process the type for this graph constant.
1197 uint32_t typeID = 0;
1198 if (failed(processType(loc, graphConstType, typeID))) {
1199 return 0;
1200 }
1201
1202 uint32_t resultID = getNextID();
1203 APInt value = intAttr.getValue();
1204 unsigned bitwidth = value.getBitWidth();
1205 if (bitwidth > 32) {
1206 emitError(loc, "Too wide attribute for OpGraphConstantARM: ")
1207 << bitwidth << " bits";
1208 return 0;
1209 }
1210 bool isSigned = value.isSignedIntN(bitwidth);
1211
1212 uint32_t word = 0;
1213 if (isSigned) {
1214 word = static_cast<int32_t>(value.getSExtValue());
1215 } else {
1216 word = static_cast<uint32_t>(value.getZExtValue());
1217 }
1218 encodeInstructionInto(typesGlobalValues, spirv::Opcode::OpGraphConstantARM,
1219 {typeID, resultID, word});
1220 graphConstIDMap[intAttr] = resultID;
1221 return resultID;
1222}
1223
1224uint32_t Serializer::prepareConstantFp(Location loc, FloatAttr floatAttr,
1225 bool isSpec) {
1226 if (!isSpec) {
1227 // We can de-duplicate normal constants, but not specialization constants.
1228 if (auto id = getConstantID(floatAttr)) {
1229 return id;
1230 }
1231 }
1232
1233 // Process the type for this float literal
1234 uint32_t typeID = 0;
1235 if (failed(processType(loc, floatAttr.getType(), typeID))) {
1236 return 0;
1237 }
1238
1239 auto resultID = getNextID();
1240 APFloat value = floatAttr.getValue();
1241 const llvm::fltSemantics *semantics = &value.getSemantics();
1242
1243 auto opcode =
1244 isSpec ? spirv::Opcode::OpSpecConstant : spirv::Opcode::OpConstant;
1245
1246 if (semantics == &APFloat::IEEEsingle()) {
1247 uint32_t word = llvm::bit_cast<uint32_t>(value.convertToFloat());
1248 encodeInstructionInto(typesGlobalValues, opcode, {typeID, resultID, word});
1249 } else if (semantics == &APFloat::IEEEdouble()) {
1250 struct DoubleWord {
1251 uint32_t word1;
1252 uint32_t word2;
1253 } words = llvm::bit_cast<DoubleWord>(value.convertToDouble());
1254 encodeInstructionInto(typesGlobalValues, opcode,
1255 {typeID, resultID, words.word1, words.word2});
1256 } else if (semantics == &APFloat::IEEEhalf() ||
1257 semantics == &APFloat::BFloat()) {
1258 uint32_t word =
1259 static_cast<uint32_t>(value.bitcastToAPInt().getZExtValue());
1260 encodeInstructionInto(typesGlobalValues, opcode, {typeID, resultID, word});
1261 } else {
1262 std::string valueStr;
1263 llvm::raw_string_ostream rss(valueStr);
1264 value.print(rss);
1265
1266 emitError(loc, "cannot serialize ")
1267 << floatAttr.getType() << "-typed float literal: " << valueStr;
1268 return 0;
1269 }
1270
1271 if (!isSpec) {
1272 constIDMap[floatAttr] = resultID;
1273 }
1274 return resultID;
1275}
1276
1277// Returns type of attribute. In case of a TypedAttr this will simply return
1278// the type. But for an ArrayAttr which is untyped and can be multidimensional
1279// it creates the ArrayType recursively.
1281 if (auto typedAttr = dyn_cast<TypedAttr>(attr)) {
1282 return typedAttr.getType();
1283 }
1284
1285 if (auto arrayAttr = dyn_cast<ArrayAttr>(attr)) {
1286 return spirv::ArrayType::get(getValueType(arrayAttr[0]), arrayAttr.size());
1287 }
1288
1289 return nullptr;
1290}
1291
1292uint32_t Serializer::prepareConstantCompositeReplicate(Location loc,
1293 Type resultType,
1294 Attribute valueAttr) {
1295 std::pair<Attribute, Type> valueTypePair{valueAttr, resultType};
1296 if (uint32_t id = getConstantCompositeReplicateID(valueTypePair)) {
1297 return id;
1298 }
1299
1300 uint32_t typeID = 0;
1301 if (failed(processType(loc, resultType, typeID))) {
1302 return 0;
1303 }
1304
1305 Type valueType = getValueType(valueAttr);
1306 if (!valueAttr)
1307 return 0;
1308
1309 auto compositeType = dyn_cast<CompositeType>(resultType);
1310 if (!compositeType)
1311 return 0;
1312 Type elementType = compositeType.getElementType(0);
1313
1314 uint32_t constandID;
1315 if (elementType == valueType) {
1316 constandID = prepareConstant(loc, elementType, valueAttr);
1317 } else {
1318 constandID = prepareConstantCompositeReplicate(loc, elementType, valueAttr);
1319 }
1320
1321 uint32_t resultID = getNextID();
1322 if (dyn_cast<spirv::TensorArmType>(resultType) && isZeroValue(valueAttr)) {
1323 encodeInstructionInto(typesGlobalValues, spirv::Opcode::OpConstantNull,
1324 {typeID, resultID});
1325 } else {
1326 encodeInstructionInto(typesGlobalValues,
1327 spirv::Opcode::OpConstantCompositeReplicateEXT,
1328 {typeID, resultID, constandID});
1329 }
1330
1331 constCompositeReplicateIDMap[valueTypePair] = resultID;
1332 return resultID;
1333}
1334
1335//===----------------------------------------------------------------------===//
1336// Control flow
1337//===----------------------------------------------------------------------===//
1338
1339uint32_t Serializer::getOrCreateBlockID(Block *block) {
1340 if (uint32_t id = getBlockID(block))
1341 return id;
1342 return blockIDMap[block] = getNextID();
1343}
1344
1345#ifndef NDEBUG
1346void Serializer::printBlock(Block *block, raw_ostream &os) {
1347 os << "block " << block << " (id = ";
1348 if (uint32_t id = getBlockID(block))
1349 os << id;
1350 else
1351 os << "unknown";
1352 os << ")\n";
1353}
1354#endif
1355
1356LogicalResult
1357Serializer::processBlock(Block *block, bool omitLabel,
1358 function_ref<LogicalResult()> emitMerge) {
1359 LLVM_DEBUG(llvm::dbgs() << "processing block " << block << ":\n");
1360 LLVM_DEBUG(block->print(llvm::dbgs()));
1361 LLVM_DEBUG(llvm::dbgs() << '\n');
1362 if (!omitLabel) {
1363 uint32_t blockID = getOrCreateBlockID(block);
1364 LLVM_DEBUG(printBlock(block, llvm::dbgs()));
1365
1366 // Emit OpLabel for this block.
1367 encodeInstructionInto(functionBody, spirv::Opcode::OpLabel, {blockID});
1368 }
1369
1370 // Emit OpPhi instructions for block arguments, if any.
1371 if (failed(emitPhiForBlockArguments(block)))
1372 return failure();
1373
1374 // If we need to emit merge instructions, it must happen in this block. Check
1375 // whether we have other structured control flow ops, which will be expanded
1376 // into multiple basic blocks. If that's the case, we need to emit the merge
1377 // right now and then create new blocks for further serialization of the ops
1378 // in this block.
1379 if (emitMerge &&
1380 llvm::any_of(block->getOperations(),
1381 llvm::IsaPred<spirv::LoopOp, spirv::SelectionOp>)) {
1382 if (failed(emitMerge()))
1383 return failure();
1384 emitMerge = nullptr;
1385
1386 // Start a new block for further serialization.
1387 uint32_t blockID = getNextID();
1388 encodeInstructionInto(functionBody, spirv::Opcode::OpBranch, {blockID});
1389 encodeInstructionInto(functionBody, spirv::Opcode::OpLabel, {blockID});
1390 }
1391
1392 // Process each op in this block except the terminator.
1393 for (Operation &op : llvm::drop_end(*block)) {
1394 if (failed(processOperation(&op)))
1395 return failure();
1396 }
1397
1398 // Process the terminator.
1399 if (emitMerge)
1400 if (failed(emitMerge()))
1401 return failure();
1402 if (failed(processOperation(&block->back())))
1403 return failure();
1404
1405 return success();
1406}
1407
1408LogicalResult Serializer::emitPhiForBlockArguments(Block *block) {
1409 // Nothing to do if this block has no arguments or it's the entry block, which
1410 // always has the same arguments as the function signature.
1411 if (block->args_empty() || block->isEntryBlock())
1412 return success();
1413
1414 LLVM_DEBUG(llvm::dbgs() << "emitting phi instructions..\n");
1415
1416 // If the block has arguments, we need to create SPIR-V OpPhi instructions.
1417 // A SPIR-V OpPhi instruction is of the syntax:
1418 // OpPhi | result type | result <id> | (value <id>, parent block <id>) pair
1419 // So we need to collect all predecessor blocks and the arguments they send
1420 // to this block.
1421 SmallVector<std::pair<Block *, OperandRange>, 4> predecessors;
1422 for (Block *mlirPredecessor : block->getPredecessors()) {
1423 auto *terminator = mlirPredecessor->getTerminator();
1424 LLVM_DEBUG(llvm::dbgs() << " mlir predecessor ");
1425 LLVM_DEBUG(printBlock(mlirPredecessor, llvm::dbgs()));
1426 LLVM_DEBUG(llvm::dbgs() << " terminator: " << *terminator << "\n");
1427 // The predecessor here is the immediate one according to MLIR's IR
1428 // structure. It does not directly map to the incoming parent block for the
1429 // OpPhi instructions at SPIR-V binary level. This is because structured
1430 // control flow ops are serialized to multiple SPIR-V blocks. If there is a
1431 // spirv.mlir.selection/spirv.mlir.loop op in the MLIR predecessor block,
1432 // the branch op jumping to the OpPhi's block then resides in the previous
1433 // structured control flow op's merge block.
1434 Block *spirvPredecessor = getPhiIncomingBlock(mlirPredecessor);
1435 LLVM_DEBUG(llvm::dbgs() << " spirv predecessor ");
1436 LLVM_DEBUG(printBlock(spirvPredecessor, llvm::dbgs()));
1437 if (auto branchOp = dyn_cast<spirv::BranchOp>(terminator)) {
1438 predecessors.emplace_back(spirvPredecessor, branchOp.getOperands());
1439 } else if (auto branchCondOp =
1440 dyn_cast<spirv::BranchConditionalOp>(terminator)) {
1441 std::optional<OperandRange> blockOperands;
1442 if (branchCondOp.getTrueTarget() == block) {
1443 blockOperands = branchCondOp.getTrueTargetOperands();
1444 } else {
1445 assert(branchCondOp.getFalseTarget() == block);
1446 blockOperands = branchCondOp.getFalseTargetOperands();
1447 }
1448 assert(!blockOperands->empty() &&
1449 "expected non-empty block operand range");
1450 predecessors.emplace_back(spirvPredecessor, *blockOperands);
1451 } else if (auto switchOp = dyn_cast<spirv::SwitchOp>(terminator)) {
1452 std::optional<OperandRange> blockOperands;
1453 if (block == switchOp.getDefaultTarget()) {
1454 blockOperands = switchOp.getDefaultOperands();
1455 } else {
1456 SuccessorRange targets = switchOp.getTargets();
1457 auto it = llvm::find(targets, block);
1458 assert(it != targets.end());
1459 size_t index = std::distance(targets.begin(), it);
1460 blockOperands = switchOp.getTargetOperands(index);
1461 }
1462 assert(!blockOperands->empty() &&
1463 "expected non-empty block operand range");
1464 predecessors.emplace_back(spirvPredecessor, *blockOperands);
1465 } else {
1466 return terminator->emitError("unimplemented terminator for Phi creation");
1467 }
1468 LLVM_DEBUG({
1469 llvm::dbgs() << " block arguments:\n";
1470 for (Value v : predecessors.back().second)
1471 llvm::dbgs() << " " << v << "\n";
1472 });
1473 }
1474
1475 // Then create OpPhi instruction for each of the block argument.
1476 for (auto argIndex : llvm::seq<unsigned>(0, block->getNumArguments())) {
1477 BlockArgument arg = block->getArgument(argIndex);
1478
1479 // Get the type <id> and result <id> for this OpPhi instruction.
1480 uint32_t phiTypeID = 0;
1481 if (failed(processType(arg.getLoc(), arg.getType(), phiTypeID)))
1482 return failure();
1483 uint32_t phiID = getNextID();
1484
1485 LLVM_DEBUG(llvm::dbgs() << "[phi] for block argument #" << argIndex << ' '
1486 << arg << " (id = " << phiID << ")\n");
1487
1488 // Prepare the (value <id>, parent block <id>) pairs.
1489 SmallVector<uint32_t, 8> phiArgs;
1490 phiArgs.push_back(phiTypeID);
1491 phiArgs.push_back(phiID);
1492
1493 for (auto predIndex : llvm::seq<unsigned>(0, predecessors.size())) {
1494 Value value = predecessors[predIndex].second[argIndex];
1495 uint32_t predBlockId = getOrCreateBlockID(predecessors[predIndex].first);
1496 LLVM_DEBUG(llvm::dbgs() << "[phi] use predecessor (id = " << predBlockId
1497 << ") value " << value << ' ');
1498 // Each pair is a value <id> ...
1499 uint32_t valueId = getValueID(value);
1500 if (valueId == 0) {
1501 // The op generating this value hasn't been visited yet so we don't have
1502 // an <id> assigned yet. Record this to fix up later.
1503 LLVM_DEBUG(llvm::dbgs() << "(need to fix)\n");
1504 deferredPhiValues[value].push_back(functionBody.size() + 1 +
1505 phiArgs.size());
1506 } else {
1507 LLVM_DEBUG(llvm::dbgs() << "(id = " << valueId << ")\n");
1508 }
1509 phiArgs.push_back(valueId);
1510 // ... and a parent block <id>.
1511 phiArgs.push_back(predBlockId);
1512 }
1513
1514 encodeInstructionInto(functionBody, spirv::Opcode::OpPhi, phiArgs);
1515 valueIDMap[arg] = phiID;
1516 }
1517
1518 return success();
1519}
1520
1521//===----------------------------------------------------------------------===//
1522// Operation
1523//===----------------------------------------------------------------------===//
1524
1525LogicalResult Serializer::encodeExtensionInstruction(
1526 Operation *op, StringRef extensionSetName, uint32_t extensionOpcode,
1527 ArrayRef<uint32_t> operands) {
1528 // Check if the extension has been imported.
1529 auto &setID = extendedInstSetIDMap[extensionSetName];
1530 if (!setID) {
1531 setID = getNextID();
1532 SmallVector<uint32_t, 16> importOperands;
1533 importOperands.push_back(setID);
1534 spirv::encodeStringLiteralInto(importOperands, extensionSetName);
1535 encodeInstructionInto(extendedSets, spirv::Opcode::OpExtInstImport,
1536 importOperands);
1537 }
1538
1539 // The first two operands are the result type <id> and result <id>. The set
1540 // <id> and the opcode need to be insert after this.
1541 if (operands.size() < 2) {
1542 return op->emitError("extended instructions must have a result encoding");
1543 }
1544 SmallVector<uint32_t, 8> extInstOperands;
1545 extInstOperands.reserve(operands.size() + 2);
1546 extInstOperands.append(operands.begin(), std::next(operands.begin(), 2));
1547 extInstOperands.push_back(setID);
1548 extInstOperands.push_back(extensionOpcode);
1549 extInstOperands.append(std::next(operands.begin(), 2), operands.end());
1550 encodeInstructionInto(functionBody, spirv::Opcode::OpExtInst,
1551 extInstOperands);
1552 return success();
1553}
1554
1555LogicalResult Serializer::processOperation(Operation *opInst) {
1556 LLVM_DEBUG(llvm::dbgs() << "[op] '" << opInst->getName() << "'\n");
1557
1558 // First dispatch the ops that do not directly mirror an instruction from
1559 // the SPIR-V spec.
1561 .Case([&](spirv::AddressOfOp op) { return processAddressOfOp(op); })
1562 .Case([&](spirv::BranchOp op) { return processBranchOp(op); })
1563 .Case([&](spirv::BranchConditionalOp op) {
1564 return processBranchConditionalOp(op);
1565 })
1566 .Case([&](spirv::ConstantOp op) { return processConstantOp(op); })
1567 .Case([&](spirv::EXTConstantCompositeReplicateOp op) {
1568 return processConstantCompositeReplicateOp(op);
1569 })
1570 .Case([&](spirv::FuncOp op) { return processFuncOp(op); })
1571 .Case([&](spirv::GraphARMOp op) { return processGraphARMOp(op); })
1572 .Case([&](spirv::GraphEntryPointARMOp op) {
1573 return processGraphEntryPointARMOp(op);
1574 })
1575 .Case([&](spirv::GraphOutputsARMOp op) {
1576 return processGraphOutputsARMOp(op);
1577 })
1578 .Case([&](spirv::GlobalVariableOp op) {
1579 return processGlobalVariableOp(op);
1580 })
1581 .Case([&](spirv::GraphConstantARMOp op) {
1582 return processGraphConstantARMOp(op);
1583 })
1584 .Case([&](spirv::LoopOp op) { return processLoopOp(op); })
1585 .Case([&](spirv::ReferenceOfOp op) { return processReferenceOfOp(op); })
1586 .Case([&](spirv::SelectionOp op) { return processSelectionOp(op); })
1587 .Case([&](spirv::SpecConstantOp op) { return processSpecConstantOp(op); })
1588 .Case([&](spirv::SpecConstantCompositeOp op) {
1589 return processSpecConstantCompositeOp(op);
1590 })
1591 .Case([&](spirv::EXTSpecConstantCompositeReplicateOp op) {
1592 return processSpecConstantCompositeReplicateOp(op);
1593 })
1594 .Case([&](spirv::SpecConstantOperationOp op) {
1595 return processSpecConstantOperationOp(op);
1596 })
1597 .Case([&](spirv::SwitchOp op) { return processSwitchOp(op); })
1598 .Case([&](spirv::UndefOp op) { return processUndefOp(op); })
1599 .Case([&](spirv::VariableOp op) { return processVariableOp(op); })
1600
1601 // Then handle all the ops that directly mirror SPIR-V instructions with
1602 // auto-generated methods.
1603 .Default(
1604 [&](Operation *op) { return dispatchToAutogenSerialization(op); });
1605}
1606
1607LogicalResult Serializer::processOpWithoutGrammarAttr(Operation *op,
1608 StringRef extInstSet,
1609 uint32_t opcode) {
1610 SmallVector<uint32_t, 4> operands;
1611 Location loc = op->getLoc();
1612
1613 uint32_t resultID = 0;
1614 if (op->getNumResults() != 0) {
1615 uint32_t resultTypeID = 0;
1616 if (failed(processType(loc, op->getResult(0).getType(), resultTypeID)))
1617 return failure();
1618 operands.push_back(resultTypeID);
1619
1620 resultID = getNextID();
1621 operands.push_back(resultID);
1622 valueIDMap[op->getResult(0)] = resultID;
1623 };
1624
1625 for (Value operand : op->getOperands())
1626 operands.push_back(getValueID(operand));
1627
1628 if (failed(emitDebugLine(functionBody, loc)))
1629 return failure();
1630
1631 if (extInstSet.empty()) {
1632 encodeInstructionInto(functionBody, static_cast<spirv::Opcode>(opcode),
1633 operands);
1634 } else {
1635 if (failed(encodeExtensionInstruction(op, extInstSet, opcode, operands)))
1636 return failure();
1637 }
1638
1639 if (op->getNumResults() != 0) {
1640 for (auto attr : op->getAttrs()) {
1641 if (failed(processDecoration(loc, resultID, attr)))
1642 return failure();
1643 }
1644 }
1645
1646 return success();
1647}
1648
1649LogicalResult Serializer::emitDecoration(uint32_t target,
1650 spirv::Decoration decoration,
1651 ArrayRef<uint32_t> params) {
1652 uint32_t wordCount = 3 + params.size();
1653 llvm::append_values(
1654 decorations,
1655 spirv::getPrefixedOpcode(wordCount, spirv::Opcode::OpDecorate), target,
1656 static_cast<uint32_t>(decoration));
1657 llvm::append_range(decorations, params);
1658 return success();
1659}
1660
1661LogicalResult Serializer::emitDebugLine(SmallVectorImpl<uint32_t> &binary,
1662 Location loc) {
1663 if (!options.emitDebugInfo)
1664 return success();
1665
1666 if (lastProcessedWasMergeInst) {
1667 lastProcessedWasMergeInst = false;
1668 return success();
1669 }
1670
1671 auto fileLoc = dyn_cast<FileLineColLoc>(loc);
1672 if (fileLoc)
1673 encodeInstructionInto(binary, spirv::Opcode::OpLine,
1674 {fileID, fileLoc.getLine(), fileLoc.getColumn()});
1675 return success();
1676}
1677} // namespace spirv
1678} // namespace mlir
return success()
ArrayAttr()
b getContext())
static Block * getStructuredControlFlowOpMergeBlock(Operation *op)
Returns the merge block if the given op is a structured control flow op.
static Block * getPhiIncomingBlock(Block *block)
Given a predecessor block for a block with arguments, returns the block that should be used as the pa...
static bool isZeroValue(Attribute attr)
static void moveFuncDeclarationsToTop(spirv::ModuleOp moduleOp)
Move all functions declaration before functions definitions.
Attributes are known-constant values of operations.
Definition Attributes.h:25
MLIRContext * getContext() const
Return the context this attribute belongs to.
Location getLoc() const
Return the location for this argument.
Definition Value.h:324
Block represents an ordered list of Operations.
Definition Block.h:33
BlockArgument getArgument(unsigned i)
Definition Block.h:139
unsigned getNumArguments()
Definition Block.h:138
iterator_range< pred_iterator > getPredecessors()
Definition Block.h:250
OpListType & getOperations()
Definition Block.h:147
Operation & back()
Definition Block.h:162
void print(raw_ostream &os)
bool args_empty()
Definition Block.h:109
bool isEntryBlock()
Return if this block is the entry block in the parent region.
Definition Block.cpp:36
Operation * getParentOp()
Returns the closest surrounding operation that contains this block.
Definition Block.cpp:31
llvm::iplist< Operation > OpListType
This is the list of operations in the block.
Definition Block.h:146
bool getValue() const
Return the boolean value of this attribute.
bool isSplat() const
Returns true if this attribute corresponds to a splat, i.e.
ShapedType getType() const
Return the type of this ElementsAttr, guaranteed to be a vector or tensor with static shape.
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
StringAttr getName() const
Return the name of the attribute.
Attribute getValue() const
Return the value of the attribute.
Definition Attributes.h:179
Operation is the basic unit of execution within MLIR.
Definition Operation.h:88
ArrayRef< NamedAttribute > getAttrs()
Return all of the attributes on this operation.
Definition Operation.h:512
Block * getBlock()
Returns the operation block that contains this operation.
Definition Operation.h:213
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Definition Operation.h:407
Location getLoc()
The source location the operation was defined or derived from.
Definition Operation.h:223
InFlightDiagnostic emitError(const Twine &message={})
Emit an error about fatal conditions with this operation, reporting up to any diagnostic handlers tha...
OperationName getName()
The name of an operation is the key identifier for it.
Definition Operation.h:119
operand_range getOperands()
Returns an iterator on the underlying Value's.
Definition Operation.h:378
void moveBefore(Operation *existingOp)
Unlink this operation from its current block and insert it right before existingOp which may be in th...
unsigned getNumResults()
Return the number of results held by this operation.
Definition Operation.h:404
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 isUnsignedInteger() const
Return true if this is an unsigned integer type (with the specified width).
Definition Types.cpp:88
unsigned getIntOrFloatBitWidth() const
Return the bit width of an integer or a float type, assert failure on other types.
Definition Types.cpp:122
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
Type getType() const
Return the type of this value.
Definition Value.h:105
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition Value.cpp:18
unsigned getArrayStride() const
Returns the array stride in bytes.
static ArrayType get(Type elementType, unsigned elementCount)
unsigned getArrayStride() const
Returns the array stride in bytes.
void printValueIDMap(raw_ostream &os)
(For debugging) prints each value and its corresponding result <id>.
Serializer(spirv::ModuleOp module, const SerializationOptions &options)
Creates a serializer for the given SPIR-V module.
LogicalResult serialize()
Serializes the remembered SPIR-V module.
void collect(SmallVectorImpl< uint32_t > &binary)
Collects the final SPIR-V binary.
static StructType getIdentified(MLIRContext *context, StringRef identifier)
Construct an identified StructType.
bool isIdentified() const
Returns true if the StructType is identified.
StringRef getIdentifier() const
For literal structs, return an empty string.
static TensorArmType get(ArrayRef< int64_t > shape, Type elementType)
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:573
static Type getValueType(Attribute attr)
void encodeStringLiteralInto(SmallVectorImpl< uint32_t > &binary, StringRef literal)
Encodes an SPIR-V literal string into the given binary vector.
TargetEnvAttr lookupTargetEnvOrDefault(Operation *op)
Queries the target environment recursively from enclosing symbol table ops containing the given op or...
uint32_t getPrefixedOpcode(uint32_t wordCount, spirv::Opcode opcode)
Returns the word-count-prefixed opcode for an SPIR-V instruction.
void encodeInstructionInto(SmallVectorImpl< uint32_t > &binary, spirv::Opcode op, ArrayRef< uint32_t > operands)
Encodes an SPIR-V instruction with the given opcode and operands into the given binary vector.
void appendModuleHeader(SmallVectorImpl< uint32_t > &header, spirv::Version version, uint32_t idBound)
Appends a SPRI-V module header to header with the given version and idBound.
constexpr unsigned kHeaderWordCount
SPIR-V binary header word count.
static LogicalResult processDecorationList(Location loc, Decoration decoration, Attribute attrList, StringRef attrName, EmitF emitter)
static std::string getDecorationName(StringRef attrName)
Include the generated interface declarations.
Type getType(OpFoldResult ofr)
Returns the int type of the integer in ofr.
Definition Utils.cpp:304
InFlightDiagnostic emitError(Location loc)
Utility method to emit an error message using this location.
llvm::SetVector< T, Vector, Set, N > SetVector
Definition LLVM.h:131
llvm::TypeSwitch< T, ResultT > TypeSwitch
Definition LLVM.h:144
llvm::function_ref< Fn > function_ref
Definition LLVM.h:152