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