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