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