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