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