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 (isa<spirv::SamplerType>(type)) {
732 typeEnum = spirv::Opcode::OpTypeSampler;
733 return success();
734 }
735
736 if (auto sampledImageType = dyn_cast<spirv::SampledImageType>(type)) {
737 typeEnum = spirv::Opcode::OpTypeSampledImage;
738 uint32_t imageTypeID = 0;
739 if (failed(
740 processType(loc, sampledImageType.getImageType(), imageTypeID))) {
741 return failure();
742 }
743 operands.push_back(imageTypeID);
744 return success();
745 }
746
747 if (auto structType = dyn_cast<spirv::StructType>(type)) {
748 if (structType.isIdentified()) {
749 if (failed(processName(resultID, structType.getIdentifier())))
750 return failure();
751 serializationCtx.insert(structType.getIdentifier());
752 }
753
754 bool hasOffset = structType.hasOffset();
755 for (auto elementIndex :
756 llvm::seq<uint32_t>(0, structType.getNumElements())) {
757 uint32_t elementTypeID = 0;
758 if (failed(processTypeImpl(loc, structType.getElementType(elementIndex),
759 elementTypeID, serializationCtx))) {
760 return failure();
761 }
762 operands.push_back(elementTypeID);
763 if (hasOffset) {
764 auto intType = IntegerType::get(structType.getContext(), 32);
765 // Decorate each struct member with an offset
766 spirv::StructType::MemberDecorationInfo offsetDecoration{
767 elementIndex, spirv::Decoration::Offset,
768 IntegerAttr::get(intType,
769 structType.getMemberOffset(elementIndex))};
770 if (failed(processMemberDecoration(resultID, offsetDecoration))) {
771 return emitError(loc, "cannot decorate ")
772 << elementIndex << "-th member of " << structType
773 << " with its offset";
774 }
775 }
776 }
777 SmallVector<spirv::StructType::MemberDecorationInfo, 4> memberDecorations;
778 structType.getMemberDecorations(memberDecorations);
779
780 for (auto &memberDecoration : memberDecorations) {
781 if (failed(processMemberDecoration(resultID, memberDecoration))) {
782 return emitError(loc, "cannot decorate ")
783 << static_cast<uint32_t>(memberDecoration.memberIndex)
784 << "-th member of " << structType << " with "
785 << stringifyDecoration(memberDecoration.decoration);
786 }
787 }
788
789 SmallVector<spirv::StructType::StructDecorationInfo, 1> structDecorations;
790 structType.getStructDecorations(structDecorations);
791
792 for (spirv::StructType::StructDecorationInfo &structDecoration :
793 structDecorations) {
794 if (failed(processDecorationAttr(loc, resultID,
795 structDecoration.decoration,
796 structDecoration.decorationValue))) {
797 return emitError(loc, "cannot decorate struct ")
798 << structType << " with "
799 << stringifyDecoration(structDecoration.decoration);
800 }
801 }
802
803 typeEnum = spirv::Opcode::OpTypeStruct;
804
805 if (structType.isIdentified())
806 serializationCtx.remove(structType.getIdentifier());
807
808 return success();
809 }
810
811 if (auto cooperativeMatrixType =
812 dyn_cast<spirv::CooperativeMatrixType>(type)) {
813 uint32_t elementTypeID = 0;
814 if (failed(processTypeImpl(loc, cooperativeMatrixType.getElementType(),
815 elementTypeID, serializationCtx))) {
816 return failure();
817 }
818 typeEnum = spirv::Opcode::OpTypeCooperativeMatrixKHR;
819 auto getConstantOp = [&](uint32_t id) {
820 auto attr = IntegerAttr::get(IntegerType::get(type.getContext(), 32), id);
821 return prepareConstantInt(loc, attr);
822 };
823 llvm::append_values(
824 operands, elementTypeID,
825 getConstantOp(static_cast<uint32_t>(cooperativeMatrixType.getScope())),
826 getConstantOp(cooperativeMatrixType.getRows()),
827 getConstantOp(cooperativeMatrixType.getColumns()),
828 getConstantOp(static_cast<uint32_t>(cooperativeMatrixType.getUse())));
829 return success();
830 }
831
832 if (auto matrixType = dyn_cast<spirv::MatrixType>(type)) {
833 uint32_t elementTypeID = 0;
834 if (failed(processTypeImpl(loc, matrixType.getColumnType(), elementTypeID,
835 serializationCtx))) {
836 return failure();
837 }
838 typeEnum = spirv::Opcode::OpTypeMatrix;
839 llvm::append_values(operands, elementTypeID, matrixType.getNumColumns());
840 return success();
841 }
842
843 if (auto tensorArmType = dyn_cast<TensorArmType>(type)) {
844 uint32_t elementTypeID = 0;
845 uint32_t rank = 0;
846 uint32_t shapeID = 0;
847 uint32_t rankID = 0;
848 if (failed(processTypeImpl(loc, tensorArmType.getElementType(),
849 elementTypeID, serializationCtx))) {
850 return failure();
851 }
852 if (tensorArmType.hasRank()) {
853 ArrayRef<int64_t> dims = tensorArmType.getShape();
854 rank = dims.size();
855 rankID = prepareConstantInt(loc, mlirBuilder.getI32IntegerAttr(rank));
856 if (rankID == 0) {
857 return failure();
858 }
859
860 bool shaped = llvm::all_of(dims, [](const auto &dim) { return dim > 0; });
861 if (rank > 0 && shaped) {
862 auto I32Type = IntegerType::get(type.getContext(), 32);
863 auto shapeType = ArrayType::get(I32Type, rank);
864 if (rank == 1) {
865 SmallVector<uint64_t, 1> index(rank);
866 shapeID = prepareDenseElementsConstant(
867 loc, shapeType,
868 mlirBuilder.getI32TensorAttr(SmallVector<int32_t>(dims)), 0,
869 index);
870 } else {
871 shapeID = prepareArrayConstant(
872 loc, shapeType,
873 mlirBuilder.getI32ArrayAttr(SmallVector<int32_t>(dims)));
874 }
875 if (shapeID == 0) {
876 return failure();
877 }
878 }
879 }
880 typeEnum = spirv::Opcode::OpTypeTensorARM;
881 operands.push_back(elementTypeID);
882 if (rankID == 0)
883 return success();
884 operands.push_back(rankID);
885 if (shapeID == 0)
886 return success();
887 operands.push_back(shapeID);
888 return success();
889 }
890
891 // TODO: Handle other types.
892 return emitError(loc, "unhandled type in serialization: ") << type;
893}
894
895LogicalResult
896Serializer::prepareFunctionType(Location loc, FunctionType type,
897 spirv::Opcode &typeEnum,
898 SmallVectorImpl<uint32_t> &operands) {
899 typeEnum = spirv::Opcode::OpTypeFunction;
900 assert(type.getNumResults() <= 1 &&
901 "serialization supports only a single return value");
902 uint32_t resultID = 0;
903 if (failed(processType(
904 loc, type.getNumResults() == 1 ? type.getResult(0) : getVoidType(),
905 resultID))) {
906 return failure();
907 }
908 operands.push_back(resultID);
909 for (auto &res : type.getInputs()) {
910 uint32_t argTypeID = 0;
911 if (failed(processType(loc, res, argTypeID))) {
912 return failure();
913 }
914 operands.push_back(argTypeID);
915 }
916 return success();
917}
918
919LogicalResult
920Serializer::prepareGraphType(Location loc, GraphType type,
921 spirv::Opcode &typeEnum,
922 SmallVectorImpl<uint32_t> &operands) {
923 typeEnum = spirv::Opcode::OpTypeGraphARM;
924 assert(type.getNumResults() >= 1 &&
925 "serialization requires at least a return value");
926
927 operands.push_back(type.getNumInputs());
928
929 for (Type argType : type.getInputs()) {
930 uint32_t argTypeID = 0;
931 if (failed(processType(loc, argType, argTypeID)))
932 return failure();
933 operands.push_back(argTypeID);
934 }
935
936 for (Type resType : type.getResults()) {
937 uint32_t resTypeID = 0;
938 if (failed(processType(loc, resType, resTypeID)))
939 return failure();
940 operands.push_back(resTypeID);
941 }
942
943 return success();
944}
945
946//===----------------------------------------------------------------------===//
947// Constant
948//===----------------------------------------------------------------------===//
949
950uint32_t Serializer::prepareConstant(Location loc, Type constType,
951 Attribute valueAttr) {
952 if (auto id = prepareConstantScalar(loc, valueAttr)) {
953 return id;
954 }
955
956 // This is a composite literal. We need to handle each component separately
957 // and then emit an OpConstantComposite for the whole.
958
959 if (auto id = getConstantID(valueAttr)) {
960 return id;
961 }
962
963 uint32_t typeID = 0;
964 if (failed(processType(loc, constType, typeID))) {
965 return 0;
966 }
967
968 uint32_t resultID = 0;
969 if (auto attr = dyn_cast<DenseElementsAttr>(valueAttr)) {
970 int rank = dyn_cast<ShapedType>(attr.getType()).getRank();
971 SmallVector<uint64_t, 4> index(rank);
972 resultID = prepareDenseElementsConstant(loc, constType, attr,
973 /*dim=*/0, index);
974 } else if (auto arrayAttr = dyn_cast<ArrayAttr>(valueAttr)) {
975 resultID = prepareArrayConstant(loc, constType, arrayAttr);
976 }
977
978 if (resultID == 0) {
979 emitError(loc, "cannot serialize attribute: ") << valueAttr;
980 return 0;
981 }
982
983 constIDMap[valueAttr] = resultID;
984 return resultID;
985}
986
987uint32_t Serializer::prepareArrayConstant(Location loc, Type constType,
988 ArrayAttr attr) {
989 uint32_t typeID = 0;
990 if (failed(processType(loc, constType, typeID))) {
991 return 0;
992 }
993
994 uint32_t resultID = getNextID();
995 SmallVector<uint32_t, 4> operands = {typeID, resultID};
996 operands.reserve(attr.size() + 2);
997 auto elementType = cast<spirv::ArrayType>(constType).getElementType();
998 for (Attribute elementAttr : attr) {
999 if (auto elementID = prepareConstant(loc, elementType, elementAttr)) {
1000 operands.push_back(elementID);
1001 } else {
1002 return 0;
1003 }
1004 }
1005 spirv::Opcode opcode = spirv::Opcode::OpConstantComposite;
1006 encodeInstructionInto(typesGlobalValues, opcode, operands);
1007
1008 return resultID;
1009}
1010
1011// TODO: Turn the below function into iterative function, instead of
1012// recursive function.
1013uint32_t
1014Serializer::prepareDenseElementsConstant(Location loc, Type constType,
1015 DenseElementsAttr valueAttr, int dim,
1016 MutableArrayRef<uint64_t> index) {
1017 auto shapedType = dyn_cast<ShapedType>(valueAttr.getType());
1018 assert(dim <= shapedType.getRank());
1019 if (shapedType.getRank() == dim) {
1020 if (auto attr = dyn_cast<DenseIntElementsAttr>(valueAttr)) {
1021 return attr.getType().getElementType().isInteger(1)
1022 ? prepareConstantBool(loc, attr.getValues<BoolAttr>()[index])
1023 : prepareConstantInt(loc,
1024 attr.getValues<IntegerAttr>()[index]);
1025 }
1026 if (auto attr = dyn_cast<DenseFPElementsAttr>(valueAttr)) {
1027 return prepareConstantFp(loc, attr.getValues<FloatAttr>()[index]);
1028 }
1029 return 0;
1030 }
1031
1032 uint32_t typeID = 0;
1033 if (failed(processType(loc, constType, typeID))) {
1034 return 0;
1035 }
1036
1037 int64_t numberOfConstituents = shapedType.getDimSize(dim);
1038 uint32_t resultID = getNextID();
1039 SmallVector<uint32_t, 4> operands = {typeID, resultID};
1040 auto elementType = cast<spirv::CompositeType>(constType).getElementType(0);
1041 if (auto tensorArmType = dyn_cast<spirv::TensorArmType>(constType)) {
1042 ArrayRef<int64_t> innerShape = tensorArmType.getShape().drop_front();
1043 if (!innerShape.empty())
1044 elementType = spirv::TensorArmType::get(innerShape, elementType);
1045 }
1046
1047 // "If the Result Type is a cooperative matrix type, then there must be only
1048 // one Constituent, with scalar type matching the cooperative matrix Component
1049 // Type, and all components of the matrix are initialized to that value."
1050 // (https://github.khronos.org/SPIRV-Registry/extensions/KHR/SPV_KHR_cooperative_matrix.html)
1051 if (isa<spirv::CooperativeMatrixType>(constType)) {
1052 if (!valueAttr.isSplat()) {
1053 emitError(
1054 loc,
1055 "cannot serialize a non-splat value for a cooperative matrix type");
1056 return 0;
1057 }
1058 // numberOfConstituents is 1, so we only need one more elements in the
1059 // SmallVector, so the total is 3 (1 + 2).
1060 operands.reserve(3);
1061 // We set dim directly to `shapedType.getRank()` so the recursive call
1062 // directly returns the scalar type.
1063 if (auto elementID = prepareDenseElementsConstant(
1064 loc, elementType, valueAttr, /*dim=*/shapedType.getRank(), index)) {
1065 operands.push_back(elementID);
1066 } else {
1067 return 0;
1068 }
1069 } else if (isa<spirv::TensorArmType>(constType) && isZeroValue(valueAttr)) {
1070 encodeInstructionInto(typesGlobalValues, spirv::Opcode::OpConstantNull,
1071 {typeID, resultID});
1072 return resultID;
1073 } else {
1074 operands.reserve(numberOfConstituents + 2);
1075 for (int i = 0; i < numberOfConstituents; ++i) {
1076 index[dim] = i;
1077 if (auto elementID = prepareDenseElementsConstant(
1078 loc, elementType, valueAttr, dim + 1, index)) {
1079 operands.push_back(elementID);
1080 } else {
1081 return 0;
1082 }
1083 }
1084 }
1085 spirv::Opcode opcode = spirv::Opcode::OpConstantComposite;
1086 encodeInstructionInto(typesGlobalValues, opcode, operands);
1087
1088 return resultID;
1089}
1090
1091uint32_t Serializer::prepareConstantScalar(Location loc, Attribute valueAttr,
1092 bool isSpec) {
1093 if (auto floatAttr = dyn_cast<FloatAttr>(valueAttr)) {
1094 return prepareConstantFp(loc, floatAttr, isSpec);
1095 }
1096 if (auto boolAttr = dyn_cast<BoolAttr>(valueAttr)) {
1097 return prepareConstantBool(loc, boolAttr, isSpec);
1098 }
1099 if (auto intAttr = dyn_cast<IntegerAttr>(valueAttr)) {
1100 return prepareConstantInt(loc, intAttr, isSpec);
1101 }
1102
1103 return 0;
1104}
1105
1106uint32_t Serializer::prepareConstantBool(Location loc, BoolAttr boolAttr,
1107 bool isSpec) {
1108 if (!isSpec) {
1109 // We can de-duplicate normal constants, but not specialization constants.
1110 if (auto id = getConstantID(boolAttr)) {
1111 return id;
1112 }
1113 }
1114
1115 // Process the type for this bool literal
1116 uint32_t typeID = 0;
1117 if (failed(processType(loc, cast<IntegerAttr>(boolAttr).getType(), typeID))) {
1118 return 0;
1119 }
1120
1121 auto resultID = getNextID();
1122 auto opcode = boolAttr.getValue()
1123 ? (isSpec ? spirv::Opcode::OpSpecConstantTrue
1124 : spirv::Opcode::OpConstantTrue)
1125 : (isSpec ? spirv::Opcode::OpSpecConstantFalse
1126 : spirv::Opcode::OpConstantFalse);
1127 encodeInstructionInto(typesGlobalValues, opcode, {typeID, resultID});
1128
1129 if (!isSpec) {
1130 constIDMap[boolAttr] = resultID;
1131 }
1132 return resultID;
1133}
1134
1135uint32_t Serializer::prepareConstantInt(Location loc, IntegerAttr intAttr,
1136 bool isSpec) {
1137 if (!isSpec) {
1138 // We can de-duplicate normal constants, but not specialization constants.
1139 if (auto id = getConstantID(intAttr)) {
1140 return id;
1141 }
1142 }
1143
1144 // Process the type for this integer literal
1145 uint32_t typeID = 0;
1146 if (failed(processType(loc, intAttr.getType(), typeID))) {
1147 return 0;
1148 }
1149
1150 auto resultID = getNextID();
1151 APInt value = intAttr.getValue();
1152 unsigned bitwidth = value.getBitWidth();
1153 bool isSigned = intAttr.getType().isSignedInteger();
1154 auto opcode =
1155 isSpec ? spirv::Opcode::OpSpecConstant : spirv::Opcode::OpConstant;
1156
1157 switch (bitwidth) {
1158 // According to SPIR-V spec, "When the type's bit width is less than
1159 // 32-bits, the literal's value appears in the low-order bits of the word,
1160 // and the high-order bits must be 0 for a floating-point type, or 0 for an
1161 // integer type with Signedness of 0, or sign extended when Signedness
1162 // is 1."
1163 case 32:
1164 case 16:
1165 case 8: {
1166 uint32_t word = 0;
1167 if (isSigned) {
1168 word = static_cast<int32_t>(value.getSExtValue());
1169 } else {
1170 word = static_cast<uint32_t>(value.getZExtValue());
1171 }
1172 encodeInstructionInto(typesGlobalValues, opcode, {typeID, resultID, word});
1173 } break;
1174 // According to SPIR-V spec: "When the type's bit width is larger than one
1175 // word, the literal’s low-order words appear first."
1176 case 64: {
1177 struct DoubleWord {
1178 uint32_t word1;
1179 uint32_t word2;
1180 } words;
1181 if (isSigned) {
1182 words = llvm::bit_cast<DoubleWord>(value.getSExtValue());
1183 } else {
1184 words = llvm::bit_cast<DoubleWord>(value.getZExtValue());
1185 }
1186 encodeInstructionInto(typesGlobalValues, opcode,
1187 {typeID, resultID, words.word1, words.word2});
1188 } break;
1189 default: {
1190 std::string valueStr;
1191 llvm::raw_string_ostream rss(valueStr);
1192 value.print(rss, /*isSigned=*/false);
1193
1194 emitError(loc, "cannot serialize ")
1195 << bitwidth << "-bit integer literal: " << valueStr;
1196 return 0;
1197 }
1198 }
1199
1200 if (!isSpec) {
1201 constIDMap[intAttr] = resultID;
1202 }
1203 return resultID;
1204}
1205
1206uint32_t Serializer::prepareGraphConstantId(Location loc, Type graphConstType,
1207 IntegerAttr intAttr) {
1208 // De-duplicate graph constants.
1209 if (uint32_t id = getGraphConstantARMId(intAttr)) {
1210 return id;
1211 }
1212
1213 // Process the type for this graph constant.
1214 uint32_t typeID = 0;
1215 if (failed(processType(loc, graphConstType, typeID))) {
1216 return 0;
1217 }
1218
1219 uint32_t resultID = getNextID();
1220 APInt value = intAttr.getValue();
1221 unsigned bitwidth = value.getBitWidth();
1222 if (bitwidth > 32) {
1223 emitError(loc, "Too wide attribute for OpGraphConstantARM: ")
1224 << bitwidth << " bits";
1225 return 0;
1226 }
1227 bool isSigned = value.isSignedIntN(bitwidth);
1228
1229 uint32_t word = 0;
1230 if (isSigned) {
1231 word = static_cast<int32_t>(value.getSExtValue());
1232 } else {
1233 word = static_cast<uint32_t>(value.getZExtValue());
1234 }
1235 encodeInstructionInto(typesGlobalValues, spirv::Opcode::OpGraphConstantARM,
1236 {typeID, resultID, word});
1237 graphConstIDMap[intAttr] = resultID;
1238 return resultID;
1239}
1240
1241uint32_t Serializer::prepareConstantFp(Location loc, FloatAttr floatAttr,
1242 bool isSpec) {
1243 if (!isSpec) {
1244 // We can de-duplicate normal constants, but not specialization constants.
1245 if (auto id = getConstantID(floatAttr)) {
1246 return id;
1247 }
1248 }
1249
1250 // Process the type for this float literal
1251 uint32_t typeID = 0;
1252 if (failed(processType(loc, floatAttr.getType(), typeID))) {
1253 return 0;
1254 }
1255
1256 auto resultID = getNextID();
1257 APFloat value = floatAttr.getValue();
1258 const llvm::fltSemantics *semantics = &value.getSemantics();
1259
1260 auto opcode =
1261 isSpec ? spirv::Opcode::OpSpecConstant : spirv::Opcode::OpConstant;
1262
1263 if (semantics == &APFloat::IEEEsingle()) {
1264 uint32_t word = llvm::bit_cast<uint32_t>(value.convertToFloat());
1265 encodeInstructionInto(typesGlobalValues, opcode, {typeID, resultID, word});
1266 } else if (semantics == &APFloat::IEEEdouble()) {
1267 struct DoubleWord {
1268 uint32_t word1;
1269 uint32_t word2;
1270 } words = llvm::bit_cast<DoubleWord>(value.convertToDouble());
1271 encodeInstructionInto(typesGlobalValues, opcode,
1272 {typeID, resultID, words.word1, words.word2});
1273 } else if (llvm::is_contained({&APFloat::IEEEhalf(), &APFloat::BFloat(),
1274 &APFloat::Float8E4M3FN(),
1275 &APFloat::Float8E5M2()},
1276 semantics)) {
1277 uint32_t word =
1278 static_cast<uint32_t>(value.bitcastToAPInt().getZExtValue());
1279 encodeInstructionInto(typesGlobalValues, opcode, {typeID, resultID, word});
1280 } else {
1281 std::string valueStr;
1282 llvm::raw_string_ostream rss(valueStr);
1283 value.print(rss);
1284
1285 emitError(loc, "cannot serialize ")
1286 << floatAttr.getType() << "-typed float literal: " << valueStr;
1287 return 0;
1288 }
1289
1290 if (!isSpec) {
1291 constIDMap[floatAttr] = resultID;
1292 }
1293 return resultID;
1294}
1295
1296// Returns type of attribute. In case of a TypedAttr this will simply return
1297// the type. But for an ArrayAttr which is untyped and can be multidimensional
1298// it creates the ArrayType recursively.
1300 if (auto typedAttr = dyn_cast<TypedAttr>(attr)) {
1301 return typedAttr.getType();
1302 }
1303
1304 if (auto arrayAttr = dyn_cast<ArrayAttr>(attr)) {
1305 return spirv::ArrayType::get(getValueType(arrayAttr[0]), arrayAttr.size());
1306 }
1307
1308 return nullptr;
1309}
1310
1311uint32_t Serializer::prepareConstantCompositeReplicate(Location loc,
1312 Type resultType,
1313 Attribute valueAttr) {
1314 std::pair<Attribute, Type> valueTypePair{valueAttr, resultType};
1315 if (uint32_t id = getConstantCompositeReplicateID(valueTypePair)) {
1316 return id;
1317 }
1318
1319 uint32_t typeID = 0;
1320 if (failed(processType(loc, resultType, typeID))) {
1321 return 0;
1322 }
1323
1324 Type valueType = getValueType(valueAttr);
1325 if (!valueAttr)
1326 return 0;
1327
1328 auto compositeType = dyn_cast<CompositeType>(resultType);
1329 if (!compositeType)
1330 return 0;
1331 Type elementType = compositeType.getElementType(0);
1332
1333 uint32_t constandID;
1334 if (elementType == valueType) {
1335 constandID = prepareConstant(loc, elementType, valueAttr);
1336 } else {
1337 constandID = prepareConstantCompositeReplicate(loc, elementType, valueAttr);
1338 }
1339
1340 uint32_t resultID = getNextID();
1341 if (dyn_cast<spirv::TensorArmType>(resultType) && isZeroValue(valueAttr)) {
1342 encodeInstructionInto(typesGlobalValues, spirv::Opcode::OpConstantNull,
1343 {typeID, resultID});
1344 } else {
1345 encodeInstructionInto(typesGlobalValues,
1346 spirv::Opcode::OpConstantCompositeReplicateEXT,
1347 {typeID, resultID, constandID});
1348 }
1349
1350 constCompositeReplicateIDMap[valueTypePair] = resultID;
1351 return resultID;
1352}
1353
1354//===----------------------------------------------------------------------===//
1355// Control flow
1356//===----------------------------------------------------------------------===//
1357
1358uint32_t Serializer::getOrCreateBlockID(Block *block) {
1359 if (uint32_t id = getBlockID(block))
1360 return id;
1361 return blockIDMap[block] = getNextID();
1362}
1363
1364#ifndef NDEBUG
1365void Serializer::printBlock(Block *block, raw_ostream &os) {
1366 os << "block " << block << " (id = ";
1367 if (uint32_t id = getBlockID(block))
1368 os << id;
1369 else
1370 os << "unknown";
1371 os << ")\n";
1372}
1373#endif
1374
1375LogicalResult
1376Serializer::processBlock(Block *block, bool omitLabel,
1377 function_ref<LogicalResult()> emitMerge) {
1378 LLVM_DEBUG(llvm::dbgs() << "processing block " << block << ":\n");
1379 LLVM_DEBUG(block->print(llvm::dbgs()));
1380 LLVM_DEBUG(llvm::dbgs() << '\n');
1381 if (!omitLabel) {
1382 uint32_t blockID = getOrCreateBlockID(block);
1383 LLVM_DEBUG(printBlock(block, llvm::dbgs()));
1384
1385 // Emit OpLabel for this block.
1386 encodeInstructionInto(functionBody, spirv::Opcode::OpLabel, {blockID});
1387 }
1388
1389 // Emit OpPhi instructions for block arguments, if any.
1390 if (failed(emitPhiForBlockArguments(block)))
1391 return failure();
1392
1393 // If we need to emit merge instructions, it must happen in this block. Check
1394 // whether we have other structured control flow ops, which will be expanded
1395 // into multiple basic blocks. If that's the case, we need to emit the merge
1396 // right now and then create new blocks for further serialization of the ops
1397 // in this block.
1398 if (emitMerge &&
1399 llvm::any_of(block->getOperations(),
1400 llvm::IsaPred<spirv::LoopOp, spirv::SelectionOp>)) {
1401 if (failed(emitMerge()))
1402 return failure();
1403 emitMerge = nullptr;
1404
1405 // Start a new block for further serialization.
1406 uint32_t blockID = getNextID();
1407 encodeInstructionInto(functionBody, spirv::Opcode::OpBranch, {blockID});
1408 encodeInstructionInto(functionBody, spirv::Opcode::OpLabel, {blockID});
1409 }
1410
1411 // Process each op in this block except the terminator.
1412 for (Operation &op : llvm::drop_end(*block)) {
1413 if (failed(processOperation(&op)))
1414 return failure();
1415 }
1416
1417 // Process the terminator.
1418 if (emitMerge)
1419 if (failed(emitMerge()))
1420 return failure();
1421 if (failed(processOperation(&block->back())))
1422 return failure();
1423
1424 return success();
1425}
1426
1427LogicalResult Serializer::emitPhiForBlockArguments(Block *block) {
1428 // Nothing to do if this block has no arguments or it's the entry block, which
1429 // always has the same arguments as the function signature.
1430 if (block->args_empty() || block->isEntryBlock())
1431 return success();
1432
1433 LLVM_DEBUG(llvm::dbgs() << "emitting phi instructions..\n");
1434
1435 // If the block has arguments, we need to create SPIR-V OpPhi instructions.
1436 // A SPIR-V OpPhi instruction is of the syntax:
1437 // OpPhi | result type | result <id> | (value <id>, parent block <id>) pair
1438 // So we need to collect all predecessor blocks and the arguments they send
1439 // to this block.
1440 SmallVector<std::pair<Block *, OperandRange>, 4> predecessors;
1441 for (Block *mlirPredecessor : block->getPredecessors()) {
1442 auto *terminator = mlirPredecessor->getTerminator();
1443 LLVM_DEBUG(llvm::dbgs() << " mlir predecessor ");
1444 LLVM_DEBUG(printBlock(mlirPredecessor, llvm::dbgs()));
1445 LLVM_DEBUG(llvm::dbgs() << " terminator: " << *terminator << "\n");
1446 // The predecessor here is the immediate one according to MLIR's IR
1447 // structure. It does not directly map to the incoming parent block for the
1448 // OpPhi instructions at SPIR-V binary level. This is because structured
1449 // control flow ops are serialized to multiple SPIR-V blocks. If there is a
1450 // spirv.mlir.selection/spirv.mlir.loop op in the MLIR predecessor block,
1451 // the branch op jumping to the OpPhi's block then resides in the previous
1452 // structured control flow op's merge block.
1453 Block *spirvPredecessor = getPhiIncomingBlock(mlirPredecessor);
1454 LLVM_DEBUG(llvm::dbgs() << " spirv predecessor ");
1455 LLVM_DEBUG(printBlock(spirvPredecessor, llvm::dbgs()));
1456 if (auto branchOp = dyn_cast<spirv::BranchOp>(terminator)) {
1457 predecessors.emplace_back(spirvPredecessor, branchOp.getOperands());
1458 } else if (auto branchCondOp =
1459 dyn_cast<spirv::BranchConditionalOp>(terminator)) {
1460 std::optional<OperandRange> blockOperands;
1461 if (branchCondOp.getTrueTarget() == block) {
1462 blockOperands = branchCondOp.getTrueTargetOperands();
1463 } else {
1464 assert(branchCondOp.getFalseTarget() == block);
1465 blockOperands = branchCondOp.getFalseTargetOperands();
1466 }
1467 assert(!blockOperands->empty() &&
1468 "expected non-empty block operand range");
1469 predecessors.emplace_back(spirvPredecessor, *blockOperands);
1470 } else if (auto switchOp = dyn_cast<spirv::SwitchOp>(terminator)) {
1471 std::optional<OperandRange> blockOperands;
1472 if (block == switchOp.getDefaultTarget()) {
1473 blockOperands = switchOp.getDefaultOperands();
1474 } else {
1475 SuccessorRange targets = switchOp.getTargets();
1476 auto it = llvm::find(targets, block);
1477 assert(it != targets.end());
1478 size_t index = std::distance(targets.begin(), it);
1479 blockOperands = switchOp.getTargetOperands(index);
1480 }
1481 assert(!blockOperands->empty() &&
1482 "expected non-empty block operand range");
1483 predecessors.emplace_back(spirvPredecessor, *blockOperands);
1484 } else {
1485 return terminator->emitError("unimplemented terminator for Phi creation");
1486 }
1487 LLVM_DEBUG({
1488 llvm::dbgs() << " block arguments:\n";
1489 for (Value v : predecessors.back().second)
1490 llvm::dbgs() << " " << v << "\n";
1491 });
1492 }
1493
1494 // Then create OpPhi instruction for each of the block argument.
1495 for (auto argIndex : llvm::seq<unsigned>(0, block->getNumArguments())) {
1496 BlockArgument arg = block->getArgument(argIndex);
1497
1498 // Get the type <id> and result <id> for this OpPhi instruction.
1499 uint32_t phiTypeID = 0;
1500 if (failed(processType(arg.getLoc(), arg.getType(), phiTypeID)))
1501 return failure();
1502 uint32_t phiID = getNextID();
1503
1504 LLVM_DEBUG(llvm::dbgs() << "[phi] for block argument #" << argIndex << ' '
1505 << arg << " (id = " << phiID << ")\n");
1506
1507 // Prepare the (value <id>, parent block <id>) pairs.
1508 SmallVector<uint32_t, 8> phiArgs;
1509 phiArgs.push_back(phiTypeID);
1510 phiArgs.push_back(phiID);
1511
1512 for (auto predIndex : llvm::seq<unsigned>(0, predecessors.size())) {
1513 Value value = predecessors[predIndex].second[argIndex];
1514 uint32_t predBlockId = getOrCreateBlockID(predecessors[predIndex].first);
1515 LLVM_DEBUG(llvm::dbgs() << "[phi] use predecessor (id = " << predBlockId
1516 << ") value " << value << ' ');
1517 // Each pair is a value <id> ...
1518 uint32_t valueId = getValueID(value);
1519 if (valueId == 0) {
1520 // The op generating this value hasn't been visited yet so we don't have
1521 // an <id> assigned yet. Record this to fix up later.
1522 LLVM_DEBUG(llvm::dbgs() << "(need to fix)\n");
1523 deferredPhiValues[value].push_back(functionBody.size() + 1 +
1524 phiArgs.size());
1525 } else {
1526 LLVM_DEBUG(llvm::dbgs() << "(id = " << valueId << ")\n");
1527 }
1528 phiArgs.push_back(valueId);
1529 // ... and a parent block <id>.
1530 phiArgs.push_back(predBlockId);
1531 }
1532
1533 encodeInstructionInto(functionBody, spirv::Opcode::OpPhi, phiArgs);
1534 valueIDMap[arg] = phiID;
1535 }
1536
1537 return success();
1538}
1539
1540//===----------------------------------------------------------------------===//
1541// Operation
1542//===----------------------------------------------------------------------===//
1543
1544LogicalResult Serializer::encodeExtensionInstruction(
1545 Operation *op, StringRef extensionSetName, uint32_t extensionOpcode,
1546 ArrayRef<uint32_t> operands) {
1547 // Check if the extension has been imported.
1548 auto &setID = extendedInstSetIDMap[extensionSetName];
1549 if (!setID) {
1550 setID = getNextID();
1551 SmallVector<uint32_t, 16> importOperands;
1552 importOperands.push_back(setID);
1553 spirv::encodeStringLiteralInto(importOperands, extensionSetName);
1554 encodeInstructionInto(extendedSets, spirv::Opcode::OpExtInstImport,
1555 importOperands);
1556 }
1557
1558 // The first two operands are the result type <id> and result <id>. The set
1559 // <id> and the opcode need to be insert after this.
1560 if (operands.size() < 2) {
1561 return op->emitError("extended instructions must have a result encoding");
1562 }
1563 SmallVector<uint32_t, 8> extInstOperands;
1564 extInstOperands.reserve(operands.size() + 2);
1565 extInstOperands.append(operands.begin(), std::next(operands.begin(), 2));
1566 extInstOperands.push_back(setID);
1567 extInstOperands.push_back(extensionOpcode);
1568 extInstOperands.append(std::next(operands.begin(), 2), operands.end());
1569 encodeInstructionInto(functionBody, spirv::Opcode::OpExtInst,
1570 extInstOperands);
1571 return success();
1572}
1573
1574LogicalResult Serializer::processOperation(Operation *opInst) {
1575 LLVM_DEBUG(llvm::dbgs() << "[op] '" << opInst->getName() << "'\n");
1576
1577 // First dispatch the ops that do not directly mirror an instruction from
1578 // the SPIR-V spec.
1580 .Case([&](spirv::AddressOfOp op) { return processAddressOfOp(op); })
1581 .Case([&](spirv::BranchOp op) { return processBranchOp(op); })
1582 .Case([&](spirv::BranchConditionalOp op) {
1583 return processBranchConditionalOp(op);
1584 })
1585 .Case([&](spirv::ConstantOp op) { return processConstantOp(op); })
1586 .Case([&](spirv::EXTConstantCompositeReplicateOp op) {
1587 return processConstantCompositeReplicateOp(op);
1588 })
1589 .Case([&](spirv::FuncOp op) { return processFuncOp(op); })
1590 .Case([&](spirv::GraphARMOp op) { return processGraphARMOp(op); })
1591 .Case([&](spirv::GraphEntryPointARMOp op) {
1592 return processGraphEntryPointARMOp(op);
1593 })
1594 .Case([&](spirv::GraphOutputsARMOp op) {
1595 return processGraphOutputsARMOp(op);
1596 })
1597 .Case([&](spirv::GlobalVariableOp op) {
1598 return processGlobalVariableOp(op);
1599 })
1600 .Case([&](spirv::GraphConstantARMOp op) {
1601 return processGraphConstantARMOp(op);
1602 })
1603 .Case([&](spirv::LoopOp op) { return processLoopOp(op); })
1604 .Case([&](spirv::ReferenceOfOp op) { return processReferenceOfOp(op); })
1605 .Case([&](spirv::SelectionOp op) { return processSelectionOp(op); })
1606 .Case([&](spirv::SpecConstantOp op) { return processSpecConstantOp(op); })
1607 .Case([&](spirv::SpecConstantCompositeOp op) {
1608 return processSpecConstantCompositeOp(op);
1609 })
1610 .Case([&](spirv::EXTSpecConstantCompositeReplicateOp op) {
1611 return processSpecConstantCompositeReplicateOp(op);
1612 })
1613 .Case([&](spirv::SpecConstantOperationOp op) {
1614 return processSpecConstantOperationOp(op);
1615 })
1616 .Case([&](spirv::SwitchOp op) { return processSwitchOp(op); })
1617 .Case([&](spirv::UndefOp op) { return processUndefOp(op); })
1618 .Case([&](spirv::VariableOp op) { return processVariableOp(op); })
1619
1620 // Then handle all the ops that directly mirror SPIR-V instructions with
1621 // auto-generated methods.
1622 .Default(
1623 [&](Operation *op) { return dispatchToAutogenSerialization(op); });
1624}
1625
1626LogicalResult Serializer::processOpWithoutGrammarAttr(Operation *op,
1627 StringRef extInstSet,
1628 uint32_t opcode) {
1629 SmallVector<uint32_t, 4> operands;
1630 Location loc = op->getLoc();
1631
1632 uint32_t resultID = 0;
1633 if (op->getNumResults() != 0) {
1634 uint32_t resultTypeID = 0;
1635 if (failed(processType(loc, op->getResult(0).getType(), resultTypeID)))
1636 return failure();
1637 operands.push_back(resultTypeID);
1638
1639 resultID = getNextID();
1640 operands.push_back(resultID);
1641 valueIDMap[op->getResult(0)] = resultID;
1642 };
1643
1644 for (Value operand : op->getOperands())
1645 operands.push_back(getValueID(operand));
1646
1647 if (failed(emitDebugLine(functionBody, loc)))
1648 return failure();
1649
1650 if (extInstSet.empty()) {
1651 encodeInstructionInto(functionBody, static_cast<spirv::Opcode>(opcode),
1652 operands);
1653 } else {
1654 if (failed(encodeExtensionInstruction(op, extInstSet, opcode, operands)))
1655 return failure();
1656 }
1657
1658 if (op->getNumResults() != 0) {
1659 for (auto attr : op->getAttrs()) {
1660 if (failed(processDecoration(loc, resultID, attr)))
1661 return failure();
1662 }
1663 }
1664
1665 return success();
1666}
1667
1668LogicalResult Serializer::emitDecoration(uint32_t target,
1669 spirv::Decoration decoration,
1670 ArrayRef<uint32_t> params) {
1671 uint32_t wordCount = 3 + params.size();
1672 llvm::append_values(
1673 decorations,
1674 spirv::getPrefixedOpcode(wordCount, spirv::Opcode::OpDecorate), target,
1675 static_cast<uint32_t>(decoration));
1676 llvm::append_range(decorations, params);
1677 return success();
1678}
1679
1680LogicalResult Serializer::emitDebugLine(SmallVectorImpl<uint32_t> &binary,
1681 Location loc) {
1682 if (!options.emitDebugInfo)
1683 return success();
1684
1685 if (lastProcessedWasMergeInst) {
1686 lastProcessedWasMergeInst = false;
1687 return success();
1688 }
1689
1690 auto fileLoc = dyn_cast<FileLineColLoc>(loc);
1691 if (fileLoc)
1692 encodeInstructionInto(binary, spirv::Opcode::OpLine,
1693 {fileID, fileLoc.getLine(), fileLoc.getColumn()});
1694 return success();
1695}
1696} // namespace spirv
1697} // 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:321
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:538
Block * getBlock()
Returns the operation block that contains this operation.
Definition Operation.h:231
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Definition Operation.h:433
Location getLoc()
The source location the operation was defined or derived from.
Definition Operation.h:241
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:116
operand_range getOperands()
Returns an iterator on the underlying Value's.
Definition Operation.h:404
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:430
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:307
InFlightDiagnostic emitError(Location loc)
Utility method to emit an error message using this location.
llvm::SetVector< T, Vector, Set, N > SetVector
Definition LLVM.h:125
llvm::TypeSwitch< T, ResultT > TypeSwitch
Definition LLVM.h:139
llvm::function_ref< Fn > function_ref
Definition LLVM.h:147