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 = llvm::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 if (auto intAttr = dyn_cast<IntegerAttr>(attr)) {
342 args.push_back(intAttr.getValue().getZExtValue());
343 break;
344 }
345 return emitError(loc, "expected integer attribute for ")
346 << stringifyDecoration(decoration);
347 case spirv::Decoration::BuiltIn:
348 if (auto strAttr = dyn_cast<StringAttr>(attr)) {
349 auto enumVal = spirv::symbolizeBuiltIn(strAttr.getValue());
350 if (enumVal) {
351 args.push_back(static_cast<uint32_t>(*enumVal));
352 break;
353 }
354 return emitError(loc, "invalid ")
355 << stringifyDecoration(decoration) << " decoration attribute "
356 << strAttr.getValue();
357 }
358 return emitError(loc, "expected string attribute for ")
359 << stringifyDecoration(decoration);
360 case spirv::Decoration::Aliased:
361 case spirv::Decoration::AliasedPointer:
362 case spirv::Decoration::Flat:
363 case spirv::Decoration::NonReadable:
364 case spirv::Decoration::NonWritable:
365 case spirv::Decoration::NoPerspective:
366 case spirv::Decoration::NoSignedWrap:
367 case spirv::Decoration::NoUnsignedWrap:
368 case spirv::Decoration::RelaxedPrecision:
369 case spirv::Decoration::Restrict:
370 case spirv::Decoration::RestrictPointer:
371 case spirv::Decoration::NoContraction:
372 case spirv::Decoration::Constant:
373 case spirv::Decoration::Block:
374 case spirv::Decoration::Invariant:
375 case spirv::Decoration::Patch:
376 case spirv::Decoration::Coherent:
377 // For unit attributes and decoration attributes, the args list
378 // has no values so we do nothing.
379 if (isa<UnitAttr, DecorationAttr>(attr))
380 break;
381 return emitError(loc,
382 "expected unit attribute or decoration attribute for ")
383 << stringifyDecoration(decoration);
384 case spirv::Decoration::CacheControlLoadINTEL:
386 loc, decoration, attr, "CacheControlLoadINTEL",
387 [&](CacheControlLoadINTELAttr attr) {
388 unsigned cacheLevel = attr.getCacheLevel();
389 LoadCacheControl loadCacheControl = attr.getLoadCacheControl();
390 return emitDecoration(
391 resultID, decoration,
392 {cacheLevel, static_cast<uint32_t>(loadCacheControl)});
393 });
394 case spirv::Decoration::CacheControlStoreINTEL:
396 loc, decoration, attr, "CacheControlStoreINTEL",
397 [&](CacheControlStoreINTELAttr attr) {
398 unsigned cacheLevel = attr.getCacheLevel();
399 StoreCacheControl storeCacheControl = attr.getStoreCacheControl();
400 return emitDecoration(
401 resultID, decoration,
402 {cacheLevel, static_cast<uint32_t>(storeCacheControl)});
403 });
404 default:
405 return emitError(loc, "unhandled decoration ")
406 << stringifyDecoration(decoration);
407 }
408 return emitDecoration(resultID, decoration, args);
409}
410
411LogicalResult Serializer::processDecoration(Location loc, uint32_t resultID,
412 NamedAttribute attr) {
413 StringRef attrName = attr.getName().strref();
414 std::string decorationName = getDecorationName(attrName);
415 std::optional<Decoration> decoration =
416 spirv::symbolizeDecoration(decorationName);
417 if (!decoration) {
418 return emitError(
419 loc, "non-argument attributes expected to have snake-case-ified "
420 "decoration name, unhandled attribute with name : ")
421 << attrName;
422 }
423 return processDecorationAttr(loc, resultID, *decoration, attr.getValue());
424}
425
426LogicalResult Serializer::processName(uint32_t resultID, StringRef name) {
427 assert(!name.empty() && "unexpected empty string for OpName");
428 if (!options.emitSymbolName)
429 return success();
430
431 SmallVector<uint32_t, 4> nameOperands;
432 nameOperands.push_back(resultID);
433 spirv::encodeStringLiteralInto(nameOperands, name);
434 encodeInstructionInto(names, spirv::Opcode::OpName, nameOperands);
435 return success();
436}
437
438template <>
439LogicalResult Serializer::processTypeDecoration<spirv::ArrayType>(
440 Location loc, spirv::ArrayType type, uint32_t resultID) {
441 if (unsigned stride = type.getArrayStride()) {
442 // OpDecorate %arrayTypeSSA ArrayStride strideLiteral
443 return emitDecoration(resultID, spirv::Decoration::ArrayStride, {stride});
444 }
445 return success();
446}
447
448template <>
449LogicalResult Serializer::processTypeDecoration<spirv::RuntimeArrayType>(
450 Location loc, spirv::RuntimeArrayType type, uint32_t resultID) {
451 if (unsigned stride = type.getArrayStride()) {
452 // OpDecorate %arrayTypeSSA ArrayStride strideLiteral
453 return emitDecoration(resultID, spirv::Decoration::ArrayStride, {stride});
454 }
455 return success();
456}
457
458LogicalResult Serializer::processMemberDecoration(
459 uint32_t structID,
460 const spirv::StructType::MemberDecorationInfo &memberDecoration) {
462 {structID, memberDecoration.memberIndex,
463 static_cast<uint32_t>(memberDecoration.decoration)});
464 if (memberDecoration.hasValue()) {
465 args.push_back(
466 cast<IntegerAttr>(memberDecoration.decorationValue).getInt());
467 }
468 encodeInstructionInto(decorations, spirv::Opcode::OpMemberDecorate, args);
469 return success();
470}
471
472//===----------------------------------------------------------------------===//
473// Type
474//===----------------------------------------------------------------------===//
475
476// According to the SPIR-V spec "Validation Rules for Shader Capabilities":
477// "Composite objects in the StorageBuffer, PhysicalStorageBuffer, Uniform, and
478// PushConstant Storage Classes must be explicitly laid out."
479bool Serializer::isInterfaceStructPtrType(Type type) const {
480 if (auto ptrType = dyn_cast<spirv::PointerType>(type)) {
481 switch (ptrType.getStorageClass()) {
482 case spirv::StorageClass::PhysicalStorageBuffer:
483 case spirv::StorageClass::PushConstant:
484 case spirv::StorageClass::StorageBuffer:
485 case spirv::StorageClass::Uniform:
486 return isa<spirv::StructType>(ptrType.getPointeeType());
487 default:
488 break;
489 }
490 }
491 return false;
492}
493
494LogicalResult Serializer::processType(Location loc, Type type,
495 uint32_t &typeID) {
496 // Maintains a set of names for nested identified struct types. This is used
497 // to properly serialize recursive references.
498 SetVector<StringRef> serializationCtx;
499 return processTypeImpl(loc, type, typeID, serializationCtx);
500}
501
502LogicalResult
503Serializer::processTypeImpl(Location loc, Type type, uint32_t &typeID,
504 SetVector<StringRef> &serializationCtx) {
505
506 // Map unsigned integer types to singless integer types.
507 // This is needed otherwise the generated spirv assembly will contain
508 // twice a type declaration (like OpTypeInt 32 0) which is no permitted and
509 // such module fails validation. Indeed at MLIR level the two types are
510 // different and lookup in the cache below misses.
511 // Note: This conversion needs to happen here before the type is looked up in
512 // the cache.
513 if (type.isUnsignedInteger()) {
514 type = IntegerType::get(loc->getContext(), type.getIntOrFloatBitWidth(),
515 IntegerType::SignednessSemantics::Signless);
516 }
517
518 typeID = getTypeID(type);
519 if (typeID)
520 return success();
521
522 typeID = getNextID();
523 SmallVector<uint32_t, 4> operands;
524
525 operands.push_back(typeID);
526 auto typeEnum = spirv::Opcode::OpTypeVoid;
527 bool deferSerialization = false;
528
529 if ((isa<FunctionType>(type) &&
530 succeeded(prepareFunctionType(loc, cast<FunctionType>(type), typeEnum,
531 operands))) ||
532 (isa<GraphType>(type) &&
533 succeeded(
534 prepareGraphType(loc, cast<GraphType>(type), typeEnum, operands))) ||
535 succeeded(prepareBasicType(loc, type, typeID, typeEnum, operands,
536 deferSerialization, serializationCtx))) {
537 if (deferSerialization)
538 return success();
539
540 typeIDMap[type] = typeID;
541
542 encodeInstructionInto(typesGlobalValues, typeEnum, operands);
543
544 if (recursiveStructInfos.count(type) != 0) {
545 // This recursive struct type is emitted already, now the OpTypePointer
546 // instructions referring to recursive references are emitted as well.
547 for (auto &ptrInfo : recursiveStructInfos[type]) {
548 // TODO: This might not work if more than 1 recursive reference is
549 // present in the struct.
550 SmallVector<uint32_t, 4> ptrOperands;
551 ptrOperands.push_back(ptrInfo.pointerTypeID);
552 ptrOperands.push_back(static_cast<uint32_t>(ptrInfo.storageClass));
553 ptrOperands.push_back(typeIDMap[type]);
554
555 encodeInstructionInto(typesGlobalValues, spirv::Opcode::OpTypePointer,
556 ptrOperands);
557 }
558
559 recursiveStructInfos[type].clear();
560 }
561
562 return success();
563 }
564
565 return emitError(loc, "failed to process type: ") << type;
566}
567
568LogicalResult Serializer::prepareBasicType(
569 Location loc, Type type, uint32_t resultID, spirv::Opcode &typeEnum,
570 SmallVectorImpl<uint32_t> &operands, bool &deferSerialization,
571 SetVector<StringRef> &serializationCtx) {
572 deferSerialization = false;
573
574 if (isVoidType(type)) {
575 typeEnum = spirv::Opcode::OpTypeVoid;
576 return success();
577 }
578
579 if (auto intType = dyn_cast<IntegerType>(type)) {
580 if (intType.getWidth() == 1) {
581 typeEnum = spirv::Opcode::OpTypeBool;
582 return success();
583 }
584
585 typeEnum = spirv::Opcode::OpTypeInt;
586 operands.push_back(intType.getWidth());
587 // SPIR-V OpTypeInt "Signedness specifies whether there are signed semantics
588 // to preserve or validate.
589 // 0 indicates unsigned, or no signedness semantics
590 // 1 indicates signed semantics."
591 operands.push_back(intType.isSigned() ? 1 : 0);
592 return success();
593 }
594
595 if (auto floatType = dyn_cast<FloatType>(type)) {
596 typeEnum = spirv::Opcode::OpTypeFloat;
597 operands.push_back(floatType.getWidth());
598 if (floatType.isBF16()) {
599 operands.push_back(static_cast<uint32_t>(spirv::FPEncoding::BFloat16KHR));
600 }
601 return success();
602 }
603
604 if (auto vectorType = dyn_cast<VectorType>(type)) {
605 uint32_t elementTypeID = 0;
606 if (failed(processTypeImpl(loc, vectorType.getElementType(), elementTypeID,
607 serializationCtx))) {
608 return failure();
609 }
610 typeEnum = spirv::Opcode::OpTypeVector;
611 operands.push_back(elementTypeID);
612 operands.push_back(vectorType.getNumElements());
613 return success();
614 }
615
616 if (auto imageType = dyn_cast<spirv::ImageType>(type)) {
617 typeEnum = spirv::Opcode::OpTypeImage;
618 uint32_t sampledTypeID = 0;
619 if (failed(processType(loc, imageType.getElementType(), sampledTypeID)))
620 return failure();
621
622 llvm::append_values(operands, sampledTypeID,
623 static_cast<uint32_t>(imageType.getDim()),
624 static_cast<uint32_t>(imageType.getDepthInfo()),
625 static_cast<uint32_t>(imageType.getArrayedInfo()),
626 static_cast<uint32_t>(imageType.getSamplingInfo()),
627 static_cast<uint32_t>(imageType.getSamplerUseInfo()),
628 static_cast<uint32_t>(imageType.getImageFormat()));
629 return success();
630 }
631
632 if (auto arrayType = dyn_cast<spirv::ArrayType>(type)) {
633 typeEnum = spirv::Opcode::OpTypeArray;
634 uint32_t elementTypeID = 0;
635 if (failed(processTypeImpl(loc, arrayType.getElementType(), elementTypeID,
636 serializationCtx))) {
637 return failure();
638 }
639 operands.push_back(elementTypeID);
640 if (auto elementCountID = prepareConstantInt(
641 loc, mlirBuilder.getI32IntegerAttr(arrayType.getNumElements()))) {
642 operands.push_back(elementCountID);
643 }
644 return processTypeDecoration(loc, arrayType, resultID);
645 }
646
647 if (auto ptrType = dyn_cast<spirv::PointerType>(type)) {
648 uint32_t pointeeTypeID = 0;
649 spirv::StructType pointeeStruct =
650 dyn_cast<spirv::StructType>(ptrType.getPointeeType());
651
652 if (pointeeStruct && pointeeStruct.isIdentified() &&
653 serializationCtx.count(pointeeStruct.getIdentifier()) != 0) {
654 // A recursive reference to an enclosing struct is found.
655 //
656 // 1. Prepare an OpTypeForwardPointer with resultID and the ptr storage
657 // class as operands.
658 SmallVector<uint32_t, 2> forwardPtrOperands;
659 forwardPtrOperands.push_back(resultID);
660 forwardPtrOperands.push_back(
661 static_cast<uint32_t>(ptrType.getStorageClass()));
662
663 encodeInstructionInto(typesGlobalValues,
664 spirv::Opcode::OpTypeForwardPointer,
665 forwardPtrOperands);
666
667 // 2. Find the pointee (enclosing) struct.
668 auto structType = spirv::StructType::getIdentified(
669 module.getContext(), pointeeStruct.getIdentifier());
670
671 if (!structType)
672 return failure();
673
674 // 3. Mark the OpTypePointer that is supposed to be emitted by this call
675 // as deferred.
676 deferSerialization = true;
677
678 // 4. Record the info needed to emit the deferred OpTypePointer
679 // instruction when the enclosing struct is completely serialized.
680 recursiveStructInfos[structType].push_back(
681 {resultID, ptrType.getStorageClass()});
682 } else {
683 if (failed(processTypeImpl(loc, ptrType.getPointeeType(), pointeeTypeID,
684 serializationCtx)))
685 return failure();
686 }
687
688 typeEnum = spirv::Opcode::OpTypePointer;
689 operands.push_back(static_cast<uint32_t>(ptrType.getStorageClass()));
690 operands.push_back(pointeeTypeID);
691
692 // TODO: Now struct decorations are supported this code may not be
693 // necessary. However, it is left to support backwards compatibility.
694 // Ideally, Block decorations should be inserted when converting to SPIR-V.
695 if (isInterfaceStructPtrType(ptrType)) {
696 auto structType = cast<spirv::StructType>(ptrType.getPointeeType());
697 if (!structType.hasDecoration(spirv::Decoration::Block))
698 if (failed(emitDecoration(getTypeID(pointeeStruct),
699 spirv::Decoration::Block)))
700 return emitError(loc, "cannot decorate ")
701 << pointeeStruct << " with Block decoration";
702 }
703
704 return success();
705 }
706
707 if (auto runtimeArrayType = dyn_cast<spirv::RuntimeArrayType>(type)) {
708 uint32_t elementTypeID = 0;
709 if (failed(processTypeImpl(loc, runtimeArrayType.getElementType(),
710 elementTypeID, serializationCtx))) {
711 return failure();
712 }
713 typeEnum = spirv::Opcode::OpTypeRuntimeArray;
714 operands.push_back(elementTypeID);
715 return processTypeDecoration(loc, runtimeArrayType, resultID);
716 }
717
718 if (auto sampledImageType = dyn_cast<spirv::SampledImageType>(type)) {
719 typeEnum = spirv::Opcode::OpTypeSampledImage;
720 uint32_t imageTypeID = 0;
721 if (failed(
722 processType(loc, sampledImageType.getImageType(), imageTypeID))) {
723 return failure();
724 }
725 operands.push_back(imageTypeID);
726 return success();
727 }
728
729 if (auto structType = dyn_cast<spirv::StructType>(type)) {
730 if (structType.isIdentified()) {
731 if (failed(processName(resultID, structType.getIdentifier())))
732 return failure();
733 serializationCtx.insert(structType.getIdentifier());
734 }
735
736 bool hasOffset = structType.hasOffset();
737 for (auto elementIndex :
738 llvm::seq<uint32_t>(0, structType.getNumElements())) {
739 uint32_t elementTypeID = 0;
740 if (failed(processTypeImpl(loc, structType.getElementType(elementIndex),
741 elementTypeID, serializationCtx))) {
742 return failure();
743 }
744 operands.push_back(elementTypeID);
745 if (hasOffset) {
746 auto intType = IntegerType::get(structType.getContext(), 32);
747 // Decorate each struct member with an offset
748 spirv::StructType::MemberDecorationInfo offsetDecoration{
749 elementIndex, spirv::Decoration::Offset,
750 IntegerAttr::get(intType,
751 structType.getMemberOffset(elementIndex))};
752 if (failed(processMemberDecoration(resultID, offsetDecoration))) {
753 return emitError(loc, "cannot decorate ")
754 << elementIndex << "-th member of " << structType
755 << " with its offset";
756 }
757 }
758 }
759 SmallVector<spirv::StructType::MemberDecorationInfo, 4> memberDecorations;
760 structType.getMemberDecorations(memberDecorations);
761
762 for (auto &memberDecoration : memberDecorations) {
763 if (failed(processMemberDecoration(resultID, memberDecoration))) {
764 return emitError(loc, "cannot decorate ")
765 << static_cast<uint32_t>(memberDecoration.memberIndex)
766 << "-th member of " << structType << " with "
767 << stringifyDecoration(memberDecoration.decoration);
768 }
769 }
770
771 SmallVector<spirv::StructType::StructDecorationInfo, 1> structDecorations;
772 structType.getStructDecorations(structDecorations);
773
774 for (spirv::StructType::StructDecorationInfo &structDecoration :
775 structDecorations) {
776 if (failed(processDecorationAttr(loc, resultID,
777 structDecoration.decoration,
778 structDecoration.decorationValue))) {
779 return emitError(loc, "cannot decorate struct ")
780 << structType << " with "
781 << stringifyDecoration(structDecoration.decoration);
782 }
783 }
784
785 typeEnum = spirv::Opcode::OpTypeStruct;
786
787 if (structType.isIdentified())
788 serializationCtx.remove(structType.getIdentifier());
789
790 return success();
791 }
792
793 if (auto cooperativeMatrixType =
794 dyn_cast<spirv::CooperativeMatrixType>(type)) {
795 uint32_t elementTypeID = 0;
796 if (failed(processTypeImpl(loc, cooperativeMatrixType.getElementType(),
797 elementTypeID, serializationCtx))) {
798 return failure();
799 }
800 typeEnum = spirv::Opcode::OpTypeCooperativeMatrixKHR;
801 auto getConstantOp = [&](uint32_t id) {
802 auto attr = IntegerAttr::get(IntegerType::get(type.getContext(), 32), id);
803 return prepareConstantInt(loc, attr);
804 };
805 llvm::append_values(
806 operands, elementTypeID,
807 getConstantOp(static_cast<uint32_t>(cooperativeMatrixType.getScope())),
808 getConstantOp(cooperativeMatrixType.getRows()),
809 getConstantOp(cooperativeMatrixType.getColumns()),
810 getConstantOp(static_cast<uint32_t>(cooperativeMatrixType.getUse())));
811 return success();
812 }
813
814 if (auto matrixType = dyn_cast<spirv::MatrixType>(type)) {
815 uint32_t elementTypeID = 0;
816 if (failed(processTypeImpl(loc, matrixType.getColumnType(), elementTypeID,
817 serializationCtx))) {
818 return failure();
819 }
820 typeEnum = spirv::Opcode::OpTypeMatrix;
821 llvm::append_values(operands, elementTypeID, matrixType.getNumColumns());
822 return success();
823 }
824
825 if (auto tensorArmType = llvm::dyn_cast<TensorArmType>(type)) {
826 uint32_t elementTypeID = 0;
827 uint32_t rank = 0;
828 uint32_t shapeID = 0;
829 uint32_t rankID = 0;
830 if (failed(processTypeImpl(loc, tensorArmType.getElementType(),
831 elementTypeID, serializationCtx))) {
832 return failure();
833 }
834 if (tensorArmType.hasRank()) {
835 ArrayRef<int64_t> dims = tensorArmType.getShape();
836 rank = dims.size();
837 rankID = prepareConstantInt(loc, mlirBuilder.getI32IntegerAttr(rank));
838 if (rankID == 0) {
839 return failure();
840 }
841
842 bool shaped = llvm::all_of(dims, [](const auto &dim) { return dim > 0; });
843 if (rank > 0 && shaped) {
844 auto I32Type = IntegerType::get(type.getContext(), 32);
845 auto shapeType = ArrayType::get(I32Type, rank);
846 if (rank == 1) {
847 SmallVector<uint64_t, 1> index(rank);
848 shapeID = prepareDenseElementsConstant(
849 loc, shapeType,
850 mlirBuilder.getI32TensorAttr(SmallVector<int32_t>(dims)), 0,
851 index);
852 } else {
853 shapeID = prepareArrayConstant(
854 loc, shapeType,
855 mlirBuilder.getI32ArrayAttr(SmallVector<int32_t>(dims)));
856 }
857 if (shapeID == 0) {
858 return failure();
859 }
860 }
861 }
862 typeEnum = spirv::Opcode::OpTypeTensorARM;
863 operands.push_back(elementTypeID);
864 if (rankID == 0)
865 return success();
866 operands.push_back(rankID);
867 if (shapeID == 0)
868 return success();
869 operands.push_back(shapeID);
870 return success();
871 }
872
873 // TODO: Handle other types.
874 return emitError(loc, "unhandled type in serialization: ") << type;
875}
876
877LogicalResult
878Serializer::prepareFunctionType(Location loc, FunctionType type,
879 spirv::Opcode &typeEnum,
880 SmallVectorImpl<uint32_t> &operands) {
881 typeEnum = spirv::Opcode::OpTypeFunction;
882 assert(type.getNumResults() <= 1 &&
883 "serialization supports only a single return value");
884 uint32_t resultID = 0;
885 if (failed(processType(
886 loc, type.getNumResults() == 1 ? type.getResult(0) : getVoidType(),
887 resultID))) {
888 return failure();
889 }
890 operands.push_back(resultID);
891 for (auto &res : type.getInputs()) {
892 uint32_t argTypeID = 0;
893 if (failed(processType(loc, res, argTypeID))) {
894 return failure();
895 }
896 operands.push_back(argTypeID);
897 }
898 return success();
899}
900
901LogicalResult
902Serializer::prepareGraphType(Location loc, GraphType type,
903 spirv::Opcode &typeEnum,
904 SmallVectorImpl<uint32_t> &operands) {
905 typeEnum = spirv::Opcode::OpTypeGraphARM;
906 assert(type.getNumResults() >= 1 &&
907 "serialization requires at least a return value");
908
909 operands.push_back(type.getNumInputs());
910
911 for (Type argType : type.getInputs()) {
912 uint32_t argTypeID = 0;
913 if (failed(processType(loc, argType, argTypeID)))
914 return failure();
915 operands.push_back(argTypeID);
916 }
917
918 for (Type resType : type.getResults()) {
919 uint32_t resTypeID = 0;
920 if (failed(processType(loc, resType, resTypeID)))
921 return failure();
922 operands.push_back(resTypeID);
923 }
924
925 return success();
926}
927
928//===----------------------------------------------------------------------===//
929// Constant
930//===----------------------------------------------------------------------===//
931
932uint32_t Serializer::prepareConstant(Location loc, Type constType,
933 Attribute valueAttr) {
934 if (auto id = prepareConstantScalar(loc, valueAttr)) {
935 return id;
936 }
937
938 // This is a composite literal. We need to handle each component separately
939 // and then emit an OpConstantComposite for the whole.
940
941 if (auto id = getConstantID(valueAttr)) {
942 return id;
943 }
944
945 uint32_t typeID = 0;
946 if (failed(processType(loc, constType, typeID))) {
947 return 0;
948 }
949
950 uint32_t resultID = 0;
951 if (auto attr = dyn_cast<DenseElementsAttr>(valueAttr)) {
952 int rank = dyn_cast<ShapedType>(attr.getType()).getRank();
953 SmallVector<uint64_t, 4> index(rank);
954 resultID = prepareDenseElementsConstant(loc, constType, attr,
955 /*dim=*/0, index);
956 } else if (auto arrayAttr = dyn_cast<ArrayAttr>(valueAttr)) {
957 resultID = prepareArrayConstant(loc, constType, arrayAttr);
958 }
959
960 if (resultID == 0) {
961 emitError(loc, "cannot serialize attribute: ") << valueAttr;
962 return 0;
963 }
964
965 constIDMap[valueAttr] = resultID;
966 return resultID;
967}
968
969uint32_t Serializer::prepareArrayConstant(Location loc, Type constType,
970 ArrayAttr attr) {
971 uint32_t typeID = 0;
972 if (failed(processType(loc, constType, typeID))) {
973 return 0;
974 }
975
976 uint32_t resultID = getNextID();
977 SmallVector<uint32_t, 4> operands = {typeID, resultID};
978 operands.reserve(attr.size() + 2);
979 auto elementType = cast<spirv::ArrayType>(constType).getElementType();
980 for (Attribute elementAttr : attr) {
981 if (auto elementID = prepareConstant(loc, elementType, elementAttr)) {
982 operands.push_back(elementID);
983 } else {
984 return 0;
985 }
986 }
987 spirv::Opcode opcode = spirv::Opcode::OpConstantComposite;
988 encodeInstructionInto(typesGlobalValues, opcode, operands);
989
990 return resultID;
991}
992
993// TODO: Turn the below function into iterative function, instead of
994// recursive function.
995uint32_t
996Serializer::prepareDenseElementsConstant(Location loc, Type constType,
997 DenseElementsAttr valueAttr, int dim,
998 MutableArrayRef<uint64_t> index) {
999 auto shapedType = dyn_cast<ShapedType>(valueAttr.getType());
1000 assert(dim <= shapedType.getRank());
1001 if (shapedType.getRank() == dim) {
1002 if (auto attr = dyn_cast<DenseIntElementsAttr>(valueAttr)) {
1003 return attr.getType().getElementType().isInteger(1)
1004 ? prepareConstantBool(loc, attr.getValues<BoolAttr>()[index])
1005 : prepareConstantInt(loc,
1006 attr.getValues<IntegerAttr>()[index]);
1007 }
1008 if (auto attr = dyn_cast<DenseFPElementsAttr>(valueAttr)) {
1009 return prepareConstantFp(loc, attr.getValues<FloatAttr>()[index]);
1010 }
1011 return 0;
1012 }
1013
1014 uint32_t typeID = 0;
1015 if (failed(processType(loc, constType, typeID))) {
1016 return 0;
1017 }
1018
1019 int64_t numberOfConstituents = shapedType.getDimSize(dim);
1020 uint32_t resultID = getNextID();
1021 SmallVector<uint32_t, 4> operands = {typeID, resultID};
1022 auto elementType = cast<spirv::CompositeType>(constType).getElementType(0);
1023 if (auto tensorArmType = dyn_cast<spirv::TensorArmType>(constType)) {
1024 ArrayRef<int64_t> innerShape = tensorArmType.getShape().drop_front();
1025 if (!innerShape.empty())
1026 elementType = spirv::TensorArmType::get(innerShape, elementType);
1027 }
1028
1029 // "If the Result Type is a cooperative matrix type, then there must be only
1030 // one Constituent, with scalar type matching the cooperative matrix Component
1031 // Type, and all components of the matrix are initialized to that value."
1032 // (https://github.khronos.org/SPIRV-Registry/extensions/KHR/SPV_KHR_cooperative_matrix.html)
1033 if (isa<spirv::CooperativeMatrixType>(constType)) {
1034 if (!valueAttr.isSplat()) {
1035 emitError(
1036 loc,
1037 "cannot serialize a non-splat value for a cooperative matrix type");
1038 return 0;
1039 }
1040 // numberOfConstituents is 1, so we only need one more elements in the
1041 // SmallVector, so the total is 3 (1 + 2).
1042 operands.reserve(3);
1043 // We set dim directly to `shapedType.getRank()` so the recursive call
1044 // directly returns the scalar type.
1045 if (auto elementID = prepareDenseElementsConstant(
1046 loc, elementType, valueAttr, /*dim=*/shapedType.getRank(), index)) {
1047 operands.push_back(elementID);
1048 } else {
1049 return 0;
1050 }
1051 } else if (isa<spirv::TensorArmType>(constType) && isZeroValue(valueAttr)) {
1052 encodeInstructionInto(typesGlobalValues, spirv::Opcode::OpConstantNull,
1053 {typeID, resultID});
1054 return resultID;
1055 } else {
1056 operands.reserve(numberOfConstituents + 2);
1057 for (int i = 0; i < numberOfConstituents; ++i) {
1058 index[dim] = i;
1059 if (auto elementID = prepareDenseElementsConstant(
1060 loc, elementType, valueAttr, dim + 1, index)) {
1061 operands.push_back(elementID);
1062 } else {
1063 return 0;
1064 }
1065 }
1066 }
1067 spirv::Opcode opcode = spirv::Opcode::OpConstantComposite;
1068 encodeInstructionInto(typesGlobalValues, opcode, operands);
1069
1070 return resultID;
1071}
1072
1073uint32_t Serializer::prepareConstantScalar(Location loc, Attribute valueAttr,
1074 bool isSpec) {
1075 if (auto floatAttr = dyn_cast<FloatAttr>(valueAttr)) {
1076 return prepareConstantFp(loc, floatAttr, isSpec);
1077 }
1078 if (auto boolAttr = dyn_cast<BoolAttr>(valueAttr)) {
1079 return prepareConstantBool(loc, boolAttr, isSpec);
1080 }
1081 if (auto intAttr = dyn_cast<IntegerAttr>(valueAttr)) {
1082 return prepareConstantInt(loc, intAttr, isSpec);
1083 }
1084
1085 return 0;
1086}
1087
1088uint32_t Serializer::prepareConstantBool(Location loc, BoolAttr boolAttr,
1089 bool isSpec) {
1090 if (!isSpec) {
1091 // We can de-duplicate normal constants, but not specialization constants.
1092 if (auto id = getConstantID(boolAttr)) {
1093 return id;
1094 }
1095 }
1096
1097 // Process the type for this bool literal
1098 uint32_t typeID = 0;
1099 if (failed(processType(loc, cast<IntegerAttr>(boolAttr).getType(), typeID))) {
1100 return 0;
1101 }
1102
1103 auto resultID = getNextID();
1104 auto opcode = boolAttr.getValue()
1105 ? (isSpec ? spirv::Opcode::OpSpecConstantTrue
1106 : spirv::Opcode::OpConstantTrue)
1107 : (isSpec ? spirv::Opcode::OpSpecConstantFalse
1108 : spirv::Opcode::OpConstantFalse);
1109 encodeInstructionInto(typesGlobalValues, opcode, {typeID, resultID});
1110
1111 if (!isSpec) {
1112 constIDMap[boolAttr] = resultID;
1113 }
1114 return resultID;
1115}
1116
1117uint32_t Serializer::prepareConstantInt(Location loc, IntegerAttr intAttr,
1118 bool isSpec) {
1119 if (!isSpec) {
1120 // We can de-duplicate normal constants, but not specialization constants.
1121 if (auto id = getConstantID(intAttr)) {
1122 return id;
1123 }
1124 }
1125
1126 // Process the type for this integer literal
1127 uint32_t typeID = 0;
1128 if (failed(processType(loc, intAttr.getType(), typeID))) {
1129 return 0;
1130 }
1131
1132 auto resultID = getNextID();
1133 APInt value = intAttr.getValue();
1134 unsigned bitwidth = value.getBitWidth();
1135 bool isSigned = intAttr.getType().isSignedInteger();
1136 auto opcode =
1137 isSpec ? spirv::Opcode::OpSpecConstant : spirv::Opcode::OpConstant;
1138
1139 switch (bitwidth) {
1140 // According to SPIR-V spec, "When the type's bit width is less than
1141 // 32-bits, the literal's value appears in the low-order bits of the word,
1142 // and the high-order bits must be 0 for a floating-point type, or 0 for an
1143 // integer type with Signedness of 0, or sign extended when Signedness
1144 // is 1."
1145 case 32:
1146 case 16:
1147 case 8: {
1148 uint32_t word = 0;
1149 if (isSigned) {
1150 word = static_cast<int32_t>(value.getSExtValue());
1151 } else {
1152 word = static_cast<uint32_t>(value.getZExtValue());
1153 }
1154 encodeInstructionInto(typesGlobalValues, opcode, {typeID, resultID, word});
1155 } break;
1156 // According to SPIR-V spec: "When the type's bit width is larger than one
1157 // word, the literal’s low-order words appear first."
1158 case 64: {
1159 struct DoubleWord {
1160 uint32_t word1;
1161 uint32_t word2;
1162 } words;
1163 if (isSigned) {
1164 words = llvm::bit_cast<DoubleWord>(value.getSExtValue());
1165 } else {
1166 words = llvm::bit_cast<DoubleWord>(value.getZExtValue());
1167 }
1168 encodeInstructionInto(typesGlobalValues, opcode,
1169 {typeID, resultID, words.word1, words.word2});
1170 } break;
1171 default: {
1172 std::string valueStr;
1173 llvm::raw_string_ostream rss(valueStr);
1174 value.print(rss, /*isSigned=*/false);
1175
1176 emitError(loc, "cannot serialize ")
1177 << bitwidth << "-bit integer literal: " << valueStr;
1178 return 0;
1179 }
1180 }
1181
1182 if (!isSpec) {
1183 constIDMap[intAttr] = resultID;
1184 }
1185 return resultID;
1186}
1187
1188uint32_t Serializer::prepareGraphConstantId(Location loc, Type graphConstType,
1189 IntegerAttr intAttr) {
1190 // De-duplicate graph constants.
1191 if (uint32_t id = getGraphConstantARMId(intAttr)) {
1192 return id;
1193 }
1194
1195 // Process the type for this graph constant.
1196 uint32_t typeID = 0;
1197 if (failed(processType(loc, graphConstType, typeID))) {
1198 return 0;
1199 }
1200
1201 uint32_t resultID = getNextID();
1202 APInt value = intAttr.getValue();
1203 unsigned bitwidth = value.getBitWidth();
1204 if (bitwidth > 32) {
1205 emitError(loc, "Too wide attribute for OpGraphConstantARM: ")
1206 << bitwidth << " bits";
1207 return 0;
1208 }
1209 bool isSigned = value.isSignedIntN(bitwidth);
1210
1211 uint32_t word = 0;
1212 if (isSigned) {
1213 word = static_cast<int32_t>(value.getSExtValue());
1214 } else {
1215 word = static_cast<uint32_t>(value.getZExtValue());
1216 }
1217 encodeInstructionInto(typesGlobalValues, spirv::Opcode::OpGraphConstantARM,
1218 {typeID, resultID, word});
1219 graphConstIDMap[intAttr] = resultID;
1220 return resultID;
1221}
1222
1223uint32_t Serializer::prepareConstantFp(Location loc, FloatAttr floatAttr,
1224 bool isSpec) {
1225 if (!isSpec) {
1226 // We can de-duplicate normal constants, but not specialization constants.
1227 if (auto id = getConstantID(floatAttr)) {
1228 return id;
1229 }
1230 }
1231
1232 // Process the type for this float literal
1233 uint32_t typeID = 0;
1234 if (failed(processType(loc, floatAttr.getType(), typeID))) {
1235 return 0;
1236 }
1237
1238 auto resultID = getNextID();
1239 APFloat value = floatAttr.getValue();
1240 const llvm::fltSemantics *semantics = &value.getSemantics();
1241
1242 auto opcode =
1243 isSpec ? spirv::Opcode::OpSpecConstant : spirv::Opcode::OpConstant;
1244
1245 if (semantics == &APFloat::IEEEsingle()) {
1246 uint32_t word = llvm::bit_cast<uint32_t>(value.convertToFloat());
1247 encodeInstructionInto(typesGlobalValues, opcode, {typeID, resultID, word});
1248 } else if (semantics == &APFloat::IEEEdouble()) {
1249 struct DoubleWord {
1250 uint32_t word1;
1251 uint32_t word2;
1252 } words = llvm::bit_cast<DoubleWord>(value.convertToDouble());
1253 encodeInstructionInto(typesGlobalValues, opcode,
1254 {typeID, resultID, words.word1, words.word2});
1255 } else if (semantics == &APFloat::IEEEhalf() ||
1256 semantics == &APFloat::BFloat()) {
1257 uint32_t word =
1258 static_cast<uint32_t>(value.bitcastToAPInt().getZExtValue());
1259 encodeInstructionInto(typesGlobalValues, opcode, {typeID, resultID, word});
1260 } else {
1261 std::string valueStr;
1262 llvm::raw_string_ostream rss(valueStr);
1263 value.print(rss);
1264
1265 emitError(loc, "cannot serialize ")
1266 << floatAttr.getType() << "-typed float literal: " << valueStr;
1267 return 0;
1268 }
1269
1270 if (!isSpec) {
1271 constIDMap[floatAttr] = resultID;
1272 }
1273 return resultID;
1274}
1275
1276// Returns type of attribute. In case of a TypedAttr this will simply return
1277// the type. But for an ArrayAttr which is untyped and can be multidimensional
1278// it creates the ArrayType recursively.
1280 if (auto typedAttr = dyn_cast<TypedAttr>(attr)) {
1281 return typedAttr.getType();
1282 }
1283
1284 if (auto arrayAttr = dyn_cast<ArrayAttr>(attr)) {
1285 return spirv::ArrayType::get(getValueType(arrayAttr[0]), arrayAttr.size());
1286 }
1287
1288 return nullptr;
1289}
1290
1291uint32_t Serializer::prepareConstantCompositeReplicate(Location loc,
1292 Type resultType,
1293 Attribute valueAttr) {
1294 std::pair<Attribute, Type> valueTypePair{valueAttr, resultType};
1295 if (uint32_t id = getConstantCompositeReplicateID(valueTypePair)) {
1296 return id;
1297 }
1298
1299 uint32_t typeID = 0;
1300 if (failed(processType(loc, resultType, typeID))) {
1301 return 0;
1302 }
1303
1304 Type valueType = getValueType(valueAttr);
1305 if (!valueAttr)
1306 return 0;
1307
1308 auto compositeType = dyn_cast<CompositeType>(resultType);
1309 if (!compositeType)
1310 return 0;
1311 Type elementType = compositeType.getElementType(0);
1312
1313 uint32_t constandID;
1314 if (elementType == valueType) {
1315 constandID = prepareConstant(loc, elementType, valueAttr);
1316 } else {
1317 constandID = prepareConstantCompositeReplicate(loc, elementType, valueAttr);
1318 }
1319
1320 uint32_t resultID = getNextID();
1321 if (dyn_cast<spirv::TensorArmType>(resultType) && isZeroValue(valueAttr)) {
1322 encodeInstructionInto(typesGlobalValues, spirv::Opcode::OpConstantNull,
1323 {typeID, resultID});
1324 } else {
1325 encodeInstructionInto(typesGlobalValues,
1326 spirv::Opcode::OpConstantCompositeReplicateEXT,
1327 {typeID, resultID, constandID});
1328 }
1329
1330 constCompositeReplicateIDMap[valueTypePair] = resultID;
1331 return resultID;
1332}
1333
1334//===----------------------------------------------------------------------===//
1335// Control flow
1336//===----------------------------------------------------------------------===//
1337
1338uint32_t Serializer::getOrCreateBlockID(Block *block) {
1339 if (uint32_t id = getBlockID(block))
1340 return id;
1341 return blockIDMap[block] = getNextID();
1342}
1343
1344#ifndef NDEBUG
1345void Serializer::printBlock(Block *block, raw_ostream &os) {
1346 os << "block " << block << " (id = ";
1347 if (uint32_t id = getBlockID(block))
1348 os << id;
1349 else
1350 os << "unknown";
1351 os << ")\n";
1352}
1353#endif
1354
1355LogicalResult
1356Serializer::processBlock(Block *block, bool omitLabel,
1357 function_ref<LogicalResult()> emitMerge) {
1358 LLVM_DEBUG(llvm::dbgs() << "processing block " << block << ":\n");
1359 LLVM_DEBUG(block->print(llvm::dbgs()));
1360 LLVM_DEBUG(llvm::dbgs() << '\n');
1361 if (!omitLabel) {
1362 uint32_t blockID = getOrCreateBlockID(block);
1363 LLVM_DEBUG(printBlock(block, llvm::dbgs()));
1364
1365 // Emit OpLabel for this block.
1366 encodeInstructionInto(functionBody, spirv::Opcode::OpLabel, {blockID});
1367 }
1368
1369 // Emit OpPhi instructions for block arguments, if any.
1370 if (failed(emitPhiForBlockArguments(block)))
1371 return failure();
1372
1373 // If we need to emit merge instructions, it must happen in this block. Check
1374 // whether we have other structured control flow ops, which will be expanded
1375 // into multiple basic blocks. If that's the case, we need to emit the merge
1376 // right now and then create new blocks for further serialization of the ops
1377 // in this block.
1378 if (emitMerge &&
1379 llvm::any_of(block->getOperations(),
1380 llvm::IsaPred<spirv::LoopOp, spirv::SelectionOp>)) {
1381 if (failed(emitMerge()))
1382 return failure();
1383 emitMerge = nullptr;
1384
1385 // Start a new block for further serialization.
1386 uint32_t blockID = getNextID();
1387 encodeInstructionInto(functionBody, spirv::Opcode::OpBranch, {blockID});
1388 encodeInstructionInto(functionBody, spirv::Opcode::OpLabel, {blockID});
1389 }
1390
1391 // Process each op in this block except the terminator.
1392 for (Operation &op : llvm::drop_end(*block)) {
1393 if (failed(processOperation(&op)))
1394 return failure();
1395 }
1396
1397 // Process the terminator.
1398 if (emitMerge)
1399 if (failed(emitMerge()))
1400 return failure();
1401 if (failed(processOperation(&block->back())))
1402 return failure();
1403
1404 return success();
1405}
1406
1407LogicalResult Serializer::emitPhiForBlockArguments(Block *block) {
1408 // Nothing to do if this block has no arguments or it's the entry block, which
1409 // always has the same arguments as the function signature.
1410 if (block->args_empty() || block->isEntryBlock())
1411 return success();
1412
1413 LLVM_DEBUG(llvm::dbgs() << "emitting phi instructions..\n");
1414
1415 // If the block has arguments, we need to create SPIR-V OpPhi instructions.
1416 // A SPIR-V OpPhi instruction is of the syntax:
1417 // OpPhi | result type | result <id> | (value <id>, parent block <id>) pair
1418 // So we need to collect all predecessor blocks and the arguments they send
1419 // to this block.
1420 SmallVector<std::pair<Block *, OperandRange>, 4> predecessors;
1421 for (Block *mlirPredecessor : block->getPredecessors()) {
1422 auto *terminator = mlirPredecessor->getTerminator();
1423 LLVM_DEBUG(llvm::dbgs() << " mlir predecessor ");
1424 LLVM_DEBUG(printBlock(mlirPredecessor, llvm::dbgs()));
1425 LLVM_DEBUG(llvm::dbgs() << " terminator: " << *terminator << "\n");
1426 // The predecessor here is the immediate one according to MLIR's IR
1427 // structure. It does not directly map to the incoming parent block for the
1428 // OpPhi instructions at SPIR-V binary level. This is because structured
1429 // control flow ops are serialized to multiple SPIR-V blocks. If there is a
1430 // spirv.mlir.selection/spirv.mlir.loop op in the MLIR predecessor block,
1431 // the branch op jumping to the OpPhi's block then resides in the previous
1432 // structured control flow op's merge block.
1433 Block *spirvPredecessor = getPhiIncomingBlock(mlirPredecessor);
1434 LLVM_DEBUG(llvm::dbgs() << " spirv predecessor ");
1435 LLVM_DEBUG(printBlock(spirvPredecessor, llvm::dbgs()));
1436 if (auto branchOp = dyn_cast<spirv::BranchOp>(terminator)) {
1437 predecessors.emplace_back(spirvPredecessor, branchOp.getOperands());
1438 } else if (auto branchCondOp =
1439 dyn_cast<spirv::BranchConditionalOp>(terminator)) {
1440 std::optional<OperandRange> blockOperands;
1441 if (branchCondOp.getTrueTarget() == block) {
1442 blockOperands = branchCondOp.getTrueTargetOperands();
1443 } else {
1444 assert(branchCondOp.getFalseTarget() == block);
1445 blockOperands = branchCondOp.getFalseTargetOperands();
1446 }
1447 assert(!blockOperands->empty() &&
1448 "expected non-empty block operand range");
1449 predecessors.emplace_back(spirvPredecessor, *blockOperands);
1450 } else if (auto switchOp = dyn_cast<spirv::SwitchOp>(terminator)) {
1451 std::optional<OperandRange> blockOperands;
1452 if (block == switchOp.getDefaultTarget()) {
1453 blockOperands = switchOp.getDefaultOperands();
1454 } else {
1455 SuccessorRange targets = switchOp.getTargets();
1456 auto it = llvm::find(targets, block);
1457 assert(it != targets.end());
1458 size_t index = std::distance(targets.begin(), it);
1459 blockOperands = switchOp.getTargetOperands(index);
1460 }
1461 assert(!blockOperands->empty() &&
1462 "expected non-empty block operand range");
1463 predecessors.emplace_back(spirvPredecessor, *blockOperands);
1464 } else {
1465 return terminator->emitError("unimplemented terminator for Phi creation");
1466 }
1467 LLVM_DEBUG({
1468 llvm::dbgs() << " block arguments:\n";
1469 for (Value v : predecessors.back().second)
1470 llvm::dbgs() << " " << v << "\n";
1471 });
1472 }
1473
1474 // Then create OpPhi instruction for each of the block argument.
1475 for (auto argIndex : llvm::seq<unsigned>(0, block->getNumArguments())) {
1476 BlockArgument arg = block->getArgument(argIndex);
1477
1478 // Get the type <id> and result <id> for this OpPhi instruction.
1479 uint32_t phiTypeID = 0;
1480 if (failed(processType(arg.getLoc(), arg.getType(), phiTypeID)))
1481 return failure();
1482 uint32_t phiID = getNextID();
1483
1484 LLVM_DEBUG(llvm::dbgs() << "[phi] for block argument #" << argIndex << ' '
1485 << arg << " (id = " << phiID << ")\n");
1486
1487 // Prepare the (value <id>, parent block <id>) pairs.
1488 SmallVector<uint32_t, 8> phiArgs;
1489 phiArgs.push_back(phiTypeID);
1490 phiArgs.push_back(phiID);
1491
1492 for (auto predIndex : llvm::seq<unsigned>(0, predecessors.size())) {
1493 Value value = predecessors[predIndex].second[argIndex];
1494 uint32_t predBlockId = getOrCreateBlockID(predecessors[predIndex].first);
1495 LLVM_DEBUG(llvm::dbgs() << "[phi] use predecessor (id = " << predBlockId
1496 << ") value " << value << ' ');
1497 // Each pair is a value <id> ...
1498 uint32_t valueId = getValueID(value);
1499 if (valueId == 0) {
1500 // The op generating this value hasn't been visited yet so we don't have
1501 // an <id> assigned yet. Record this to fix up later.
1502 LLVM_DEBUG(llvm::dbgs() << "(need to fix)\n");
1503 deferredPhiValues[value].push_back(functionBody.size() + 1 +
1504 phiArgs.size());
1505 } else {
1506 LLVM_DEBUG(llvm::dbgs() << "(id = " << valueId << ")\n");
1507 }
1508 phiArgs.push_back(valueId);
1509 // ... and a parent block <id>.
1510 phiArgs.push_back(predBlockId);
1511 }
1512
1513 encodeInstructionInto(functionBody, spirv::Opcode::OpPhi, phiArgs);
1514 valueIDMap[arg] = phiID;
1515 }
1516
1517 return success();
1518}
1519
1520//===----------------------------------------------------------------------===//
1521// Operation
1522//===----------------------------------------------------------------------===//
1523
1524LogicalResult Serializer::encodeExtensionInstruction(
1525 Operation *op, StringRef extensionSetName, uint32_t extensionOpcode,
1526 ArrayRef<uint32_t> operands) {
1527 // Check if the extension has been imported.
1528 auto &setID = extendedInstSetIDMap[extensionSetName];
1529 if (!setID) {
1530 setID = getNextID();
1531 SmallVector<uint32_t, 16> importOperands;
1532 importOperands.push_back(setID);
1533 spirv::encodeStringLiteralInto(importOperands, extensionSetName);
1534 encodeInstructionInto(extendedSets, spirv::Opcode::OpExtInstImport,
1535 importOperands);
1536 }
1537
1538 // The first two operands are the result type <id> and result <id>. The set
1539 // <id> and the opcode need to be insert after this.
1540 if (operands.size() < 2) {
1541 return op->emitError("extended instructions must have a result encoding");
1542 }
1543 SmallVector<uint32_t, 8> extInstOperands;
1544 extInstOperands.reserve(operands.size() + 2);
1545 extInstOperands.append(operands.begin(), std::next(operands.begin(), 2));
1546 extInstOperands.push_back(setID);
1547 extInstOperands.push_back(extensionOpcode);
1548 extInstOperands.append(std::next(operands.begin(), 2), operands.end());
1549 encodeInstructionInto(functionBody, spirv::Opcode::OpExtInst,
1550 extInstOperands);
1551 return success();
1552}
1553
1554LogicalResult Serializer::processOperation(Operation *opInst) {
1555 LLVM_DEBUG(llvm::dbgs() << "[op] '" << opInst->getName() << "'\n");
1556
1557 // First dispatch the ops that do not directly mirror an instruction from
1558 // the SPIR-V spec.
1560 .Case([&](spirv::AddressOfOp op) { return processAddressOfOp(op); })
1561 .Case([&](spirv::BranchOp op) { return processBranchOp(op); })
1562 .Case([&](spirv::BranchConditionalOp op) {
1563 return processBranchConditionalOp(op);
1564 })
1565 .Case([&](spirv::ConstantOp op) { return processConstantOp(op); })
1566 .Case([&](spirv::EXTConstantCompositeReplicateOp op) {
1567 return processConstantCompositeReplicateOp(op);
1568 })
1569 .Case([&](spirv::FuncOp op) { return processFuncOp(op); })
1570 .Case([&](spirv::GraphARMOp op) { return processGraphARMOp(op); })
1571 .Case([&](spirv::GraphEntryPointARMOp op) {
1572 return processGraphEntryPointARMOp(op);
1573 })
1574 .Case([&](spirv::GraphOutputsARMOp op) {
1575 return processGraphOutputsARMOp(op);
1576 })
1577 .Case([&](spirv::GlobalVariableOp op) {
1578 return processGlobalVariableOp(op);
1579 })
1580 .Case([&](spirv::GraphConstantARMOp op) {
1581 return processGraphConstantARMOp(op);
1582 })
1583 .Case([&](spirv::LoopOp op) { return processLoopOp(op); })
1584 .Case([&](spirv::ReferenceOfOp op) { return processReferenceOfOp(op); })
1585 .Case([&](spirv::SelectionOp op) { return processSelectionOp(op); })
1586 .Case([&](spirv::SpecConstantOp op) { return processSpecConstantOp(op); })
1587 .Case([&](spirv::SpecConstantCompositeOp op) {
1588 return processSpecConstantCompositeOp(op);
1589 })
1590 .Case([&](spirv::EXTSpecConstantCompositeReplicateOp op) {
1591 return processSpecConstantCompositeReplicateOp(op);
1592 })
1593 .Case([&](spirv::SpecConstantOperationOp op) {
1594 return processSpecConstantOperationOp(op);
1595 })
1596 .Case([&](spirv::SwitchOp op) { return processSwitchOp(op); })
1597 .Case([&](spirv::UndefOp op) { return processUndefOp(op); })
1598 .Case([&](spirv::VariableOp op) { return processVariableOp(op); })
1599
1600 // Then handle all the ops that directly mirror SPIR-V instructions with
1601 // auto-generated methods.
1602 .Default(
1603 [&](Operation *op) { return dispatchToAutogenSerialization(op); });
1604}
1605
1606LogicalResult Serializer::processOpWithoutGrammarAttr(Operation *op,
1607 StringRef extInstSet,
1608 uint32_t opcode) {
1609 SmallVector<uint32_t, 4> operands;
1610 Location loc = op->getLoc();
1611
1612 uint32_t resultID = 0;
1613 if (op->getNumResults() != 0) {
1614 uint32_t resultTypeID = 0;
1615 if (failed(processType(loc, op->getResult(0).getType(), resultTypeID)))
1616 return failure();
1617 operands.push_back(resultTypeID);
1618
1619 resultID = getNextID();
1620 operands.push_back(resultID);
1621 valueIDMap[op->getResult(0)] = resultID;
1622 };
1623
1624 for (Value operand : op->getOperands())
1625 operands.push_back(getValueID(operand));
1626
1627 if (failed(emitDebugLine(functionBody, loc)))
1628 return failure();
1629
1630 if (extInstSet.empty()) {
1631 encodeInstructionInto(functionBody, static_cast<spirv::Opcode>(opcode),
1632 operands);
1633 } else {
1634 if (failed(encodeExtensionInstruction(op, extInstSet, opcode, operands)))
1635 return failure();
1636 }
1637
1638 if (op->getNumResults() != 0) {
1639 for (auto attr : op->getAttrs()) {
1640 if (failed(processDecoration(loc, resultID, attr)))
1641 return failure();
1642 }
1643 }
1644
1645 return success();
1646}
1647
1648LogicalResult Serializer::emitDecoration(uint32_t target,
1649 spirv::Decoration decoration,
1650 ArrayRef<uint32_t> params) {
1651 uint32_t wordCount = 3 + params.size();
1652 llvm::append_values(
1653 decorations,
1654 spirv::getPrefixedOpcode(wordCount, spirv::Opcode::OpDecorate), target,
1655 static_cast<uint32_t>(decoration));
1656 llvm::append_range(decorations, params);
1657 return success();
1658}
1659
1660LogicalResult Serializer::emitDebugLine(SmallVectorImpl<uint32_t> &binary,
1661 Location loc) {
1662 if (!options.emitDebugInfo)
1663 return success();
1664
1665 if (lastProcessedWasMergeInst) {
1666 lastProcessedWasMergeInst = false;
1667 return success();
1668 }
1669
1670 auto fileLoc = dyn_cast<FileLineColLoc>(loc);
1671 if (fileLoc)
1672 encodeInstructionInto(binary, spirv::Opcode::OpLine,
1673 {fileID, fileLoc.getLine(), fileLoc.getColumn()});
1674 return success();
1675}
1676} // namespace spirv
1677} // 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.
static bool isZeroValue(Value val)
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:129
unsigned getNumArguments()
Definition Block.h:128
iterator_range< pred_iterator > getPredecessors()
Definition Block.h:240
OpListType & getOperations()
Definition Block.h:137
Operation & back()
Definition Block.h:152
void print(raw_ostream &os)
bool args_empty()
Definition Block.h:99
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:136
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