MLIR 24.0.0git
Deserializer.cpp
Go to the documentation of this file.
1//===- Deserializer.cpp - MLIR SPIR-V Deserializer ------------------------===//
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 SPIR-V binary to MLIR SPIR-V module deserializer.
10//
11//===----------------------------------------------------------------------===//
12
13#include "Deserializer.h"
14
19#include "mlir/IR/Builders.h"
20#include "mlir/IR/IRMapping.h"
21#include "mlir/IR/Location.h"
23#include "llvm/ADT/STLExtras.h"
24#include "llvm/ADT/Sequence.h"
25#include "llvm/ADT/SmallVector.h"
26#include "llvm/ADT/StringExtras.h"
27#include "llvm/ADT/bit.h"
28#include "llvm/Support/Debug.h"
29#include "llvm/Support/SaveAndRestore.h"
30#include "llvm/Support/raw_ostream.h"
31#include <optional>
32#include <utility>
33
34using namespace mlir;
35
36#define DEBUG_TYPE "spirv-deserialization"
37
38//===----------------------------------------------------------------------===//
39// Utility Functions
40//===----------------------------------------------------------------------===//
41
42/// Returns true if the given `block` is a function entry block.
43static inline bool isFnEntryBlock(Block *block) {
44 return block->isEntryBlock() &&
45 isa_and_nonnull<spirv::FuncOp>(block->getParentOp());
46}
47
48static void setInherentOrDiscardableAttr(Operation *op, StringAttr name,
49 Attribute value) {
50 if (op->getName().getInherentAttr(op, name).has_value()) {
51 op->getName().setInherentAttr(op, name, value);
52 return;
53 }
54 op->setDiscardableAttr(name, value);
55}
56
57//===----------------------------------------------------------------------===//
58// Deserializer Method Definitions
59//===----------------------------------------------------------------------===//
60
61spirv::Deserializer::Deserializer(ArrayRef<uint32_t> binary,
62 MLIRContext *context,
64 : binary(binary), context(context), unknownLoc(UnknownLoc::get(context)),
65 module(createModuleOp()), opBuilder(module->getRegion()), options(options)
66#ifndef NDEBUG
67 ,
68 logger(llvm::dbgs())
69#endif
70{
71}
72
73LogicalResult spirv::Deserializer::deserialize() {
74 LLVM_DEBUG({
75 logger.resetIndent();
76 logger.startLine()
77 << "//+++---------- start deserialization ----------+++//\n";
78 });
79
80 if (failed(processHeader()))
81 return failure();
82
83 spirv::Opcode opcode = spirv::Opcode::OpNop;
84 ArrayRef<uint32_t> operands;
85 auto binarySize = binary.size();
86 while (curOffset < binarySize) {
87 // Slice the next instruction out and populate `opcode` and `operands`.
88 // Internally this also updates `curOffset`.
89 if (failed(sliceInstruction(opcode, operands)))
90 return failure();
91
92 if (failed(processInstruction(opcode, operands)))
93 return failure();
94 }
95
96 assert(curOffset == binarySize &&
97 "deserializer should never index beyond the binary end");
98
99 for (auto &deferred : deferredInstructions) {
100 if (failed(processInstruction(deferred.first, deferred.second, false))) {
101 return failure();
102 }
103 }
104
105 if (failed(resolveDeferredIdDecorations()))
106 return failure();
107
108 attachVCETriple();
109
110 LLVM_DEBUG(logger.startLine()
111 << "//+++-------- completed deserialization --------+++//\n");
112 return success();
113}
114
115OwningOpRef<spirv::ModuleOp> spirv::Deserializer::collect() {
116 return std::move(module);
117}
118
119//===----------------------------------------------------------------------===//
120// Module structure
121//===----------------------------------------------------------------------===//
122
123OwningOpRef<spirv::ModuleOp> spirv::Deserializer::createModuleOp() {
124 OpBuilder builder(context);
125 OperationState state(unknownLoc, spirv::ModuleOp::getOperationName());
126 spirv::ModuleOp::build(builder, state);
127 return cast<spirv::ModuleOp>(Operation::create(state));
128}
129
130LogicalResult spirv::Deserializer::processHeader() {
131 if (binary.size() < spirv::kHeaderWordCount)
132 return emitError(unknownLoc,
133 "SPIR-V binary module must have a 5-word header");
134
135 if (binary[0] != spirv::kMagicNumber)
136 return emitError(unknownLoc, "incorrect magic number");
137
138 // Version number bytes: 0 | major number | minor number | 0
139 uint32_t majorVersion = (binary[1] << 8) >> 24;
140 uint32_t minorVersion = (binary[1] << 16) >> 24;
141 if (majorVersion == 1) {
142 switch (minorVersion) {
143#define MIN_VERSION_CASE(v) \
144 case v: \
145 version = spirv::Version::V_1_##v; \
146 break
147
155#undef MIN_VERSION_CASE
156 default:
157 return emitError(unknownLoc, "unsupported SPIR-V minor version: ")
158 << minorVersion;
159 }
160 } else {
161 return emitError(unknownLoc, "unsupported SPIR-V major version: ")
162 << majorVersion;
163 }
164
165 // TODO: generator number, bound, schema
166 curOffset = spirv::kHeaderWordCount;
167 return success();
168}
169
170LogicalResult
171spirv::Deserializer::processCapability(ArrayRef<uint32_t> operands) {
172 if (operands.size() != 1)
173 return emitError(unknownLoc, "OpCapability must have one parameter");
174
175 auto cap = spirv::symbolizeCapability(operands[0]);
176 if (!cap)
177 return emitError(unknownLoc, "unknown capability: ") << operands[0];
178
179 capabilities.insert(*cap);
180 return success();
181}
182
183LogicalResult spirv::Deserializer::processExtension(ArrayRef<uint32_t> words) {
184 if (words.empty()) {
185 return emitError(
186 unknownLoc,
187 "OpExtension must have a literal string for the extension name");
188 }
189
190 unsigned wordIndex = 0;
191 StringRef extName = decodeStringLiteral(words, wordIndex);
192 if (wordIndex != words.size())
193 return emitError(unknownLoc,
194 "unexpected trailing words in OpExtension instruction");
195 auto ext = spirv::symbolizeExtension(extName);
196 if (!ext)
197 return emitError(unknownLoc, "unknown extension: ") << extName;
198
199 extensions.insert(*ext);
200 return success();
201}
202
203LogicalResult
204spirv::Deserializer::processExtInstImport(ArrayRef<uint32_t> words) {
205 if (words.size() < 2) {
206 return emitError(unknownLoc,
207 "OpExtInstImport must have a result <id> and a literal "
208 "string for the extended instruction set name");
209 }
210
211 unsigned wordIndex = 1;
212 extendedInstSets[words[0]] = decodeStringLiteral(words, wordIndex);
213 if (wordIndex != words.size()) {
214 return emitError(unknownLoc,
215 "unexpected trailing words in OpExtInstImport");
216 }
217 return success();
218}
219
220void spirv::Deserializer::attachVCETriple() {
221 module->setVceTripleAttr(spirv::VerCapExtAttr::get(
222 version, capabilities.getArrayRef(), extensions.getArrayRef(), context));
223}
224
225LogicalResult
226spirv::Deserializer::processMemoryModel(ArrayRef<uint32_t> operands) {
227 if (operands.size() != 2)
228 return emitError(unknownLoc, "OpMemoryModel must have two operands");
229
230 module->setAddressingModel(
231 static_cast<spirv::AddressingModel>(operands.front()));
232
233 module->setMemoryModel(static_cast<spirv::MemoryModel>(operands.back()));
234
235 return success();
236}
237
238template <typename AttrTy, typename EnumAttrTy, typename EnumTy>
240 Location loc, OpBuilder &opBuilder,
242 StringAttr symbol, StringRef decorationName, StringRef cacheControlKind) {
243 if (words.size() != 4) {
244 return emitError(loc, "OpDecorate with ")
245 << decorationName << " needs a cache control integer literal and a "
246 << cacheControlKind << " cache control literal";
247 }
248 unsigned cacheLevel = words[2];
249 auto cacheControlAttr = static_cast<EnumTy>(words[3]);
250 auto value = opBuilder.getAttr<AttrTy>(cacheLevel, cacheControlAttr);
252 if (auto attrList =
253 dyn_cast_or_null<ArrayAttr>(decorations[words[0]].get(symbol)))
254 llvm::append_range(attrs, attrList);
255 attrs.push_back(value);
256 decorations[words[0]].set(symbol, opBuilder.getArrayAttr(attrs));
257 return success();
258}
259
260LogicalResult spirv::Deserializer::processDecoration(ArrayRef<uint32_t> words) {
261 // TODO: This function should also be auto-generated. For now, since only a
262 // few decorations are processed/handled in a meaningful manner, going with a
263 // manual implementation.
264 if (words.size() < 2) {
265 return emitError(
266 unknownLoc, "OpDecorate must have at least result <id> and Decoration");
267 }
268 auto decorationName =
269 stringifyDecoration(static_cast<spirv::Decoration>(words[1]));
270 if (decorationName.empty()) {
271 return emitError(unknownLoc, "invalid Decoration code : ") << words[1];
272 }
273 auto symbol = getSymbolDecoration(decorationName);
274 switch (static_cast<spirv::Decoration>(words[1])) {
275 case spirv::Decoration::FPFastMathMode:
276 if (words.size() != 3) {
277 return emitError(unknownLoc, "OpDecorate with ")
278 << decorationName << " needs a single integer literal";
279 }
280 decorations[words[0]].set(
281 symbol, FPFastMathModeAttr::get(opBuilder.getContext(),
282 static_cast<FPFastMathMode>(words[2])));
283 break;
284 case spirv::Decoration::FPRoundingMode:
285 if (words.size() != 3) {
286 return emitError(unknownLoc, "OpDecorate with ")
287 << decorationName << " needs a single integer literal";
288 }
289 decorations[words[0]].set(
290 symbol, FPRoundingModeAttr::get(opBuilder.getContext(),
291 static_cast<FPRoundingMode>(words[2])));
292 break;
293 case spirv::Decoration::DescriptorSet:
294 case spirv::Decoration::Binding:
295 case spirv::Decoration::Location:
296 case spirv::Decoration::SpecId:
297 case spirv::Decoration::Index:
298 case spirv::Decoration::Offset:
299 case spirv::Decoration::XfbBuffer:
300 case spirv::Decoration::XfbStride:
301 if (words.size() != 3) {
302 return emitError(unknownLoc, "OpDecorate with ")
303 << decorationName << " needs a single integer literal";
304 }
305 decorations[words[0]].set(
306 symbol, opBuilder.getI32IntegerAttr(static_cast<int32_t>(words[2])));
307 break;
308 case spirv::Decoration::BuiltIn:
309 if (words.size() != 3) {
310 return emitError(unknownLoc, "OpDecorate with ")
311 << decorationName << " needs a single integer literal";
312 }
313 decorations[words[0]].set(
314 symbol, opBuilder.getStringAttr(
315 stringifyBuiltIn(static_cast<spirv::BuiltIn>(words[2]))));
316 break;
317 case spirv::Decoration::ArrayStride:
318 if (words.size() != 3) {
319 return emitError(unknownLoc, "OpDecorate with ")
320 << decorationName << " needs a single integer literal";
321 }
322 typeDecorations[words[0]] = words[2];
323 break;
324 case spirv::Decoration::LinkageAttributes: {
325 if (words.size() < 4) {
326 return emitError(unknownLoc, "OpDecorate with ")
327 << decorationName
328 << " needs at least 1 string and 1 integer literal";
329 }
330 // LinkageAttributes has two parameters ["linkageName", linkageType]
331 // e.g., OpDecorate %imported_func LinkageAttributes "outside.func" Import
332 // "linkageName" is a stringliteral encoded as uint32_t,
333 // hence the size of name is variable length which results in words.size()
334 // being variable length, words.size() = 3 + strlen(name)/4 + 1 or
335 // 3 + ceildiv(strlen(name), 4).
336 unsigned wordIndex = 2;
337 auto linkageName = spirv::decodeStringLiteral(words, wordIndex).str();
338 auto linkageTypeAttr = opBuilder.getAttr<::mlir::spirv::LinkageTypeAttr>(
339 static_cast<::mlir::spirv::LinkageType>(words[wordIndex++]));
340 auto linkageAttr = opBuilder.getAttr<::mlir::spirv::LinkageAttributesAttr>(
341 StringAttr::get(context, linkageName), linkageTypeAttr);
342 decorations[words[0]].set(symbol, dyn_cast<Attribute>(linkageAttr));
343 break;
344 }
345 case spirv::Decoration::Aliased:
346 case spirv::Decoration::AliasedPointer:
347 case spirv::Decoration::Block:
348 case spirv::Decoration::BufferBlock:
349 case spirv::Decoration::Flat:
350 case spirv::Decoration::NonReadable:
351 case spirv::Decoration::NonWritable:
352 case spirv::Decoration::NoPerspective:
353 case spirv::Decoration::NoSignedWrap:
354 case spirv::Decoration::NoUnsignedWrap:
355 case spirv::Decoration::RelaxedPrecision:
356 case spirv::Decoration::Restrict:
357 case spirv::Decoration::RestrictPointer:
358 case spirv::Decoration::NoContraction:
359 case spirv::Decoration::Constant:
360 case spirv::Decoration::Invariant:
361 case spirv::Decoration::Patch:
362 case spirv::Decoration::Coherent:
363 case spirv::Decoration::Volatile:
364 if (words.size() != 2) {
365 return emitError(unknownLoc, "OpDecorate with ")
366 << decorationName << " needs a single target <id>";
367 }
368 decorations[words[0]].set(symbol, opBuilder.getUnitAttr());
369 break;
370 case spirv::Decoration::CacheControlLoadINTEL: {
371 LogicalResult res = deserializeCacheControlDecoration<
372 CacheControlLoadINTELAttr, LoadCacheControlAttr, LoadCacheControl>(
373 unknownLoc, opBuilder, decorations, words, symbol, decorationName,
374 "load");
375 if (failed(res))
376 return res;
377 break;
378 }
379 case spirv::Decoration::CacheControlStoreINTEL: {
380 LogicalResult res = deserializeCacheControlDecoration<
381 CacheControlStoreINTELAttr, StoreCacheControlAttr, StoreCacheControl>(
382 unknownLoc, opBuilder, decorations, words, symbol, decorationName,
383 "store");
384 if (failed(res))
385 return res;
386 break;
387 }
388 case spirv::Decoration::AlignmentId:
389 case spirv::Decoration::MaxByteOffsetId:
390 case spirv::Decoration::CounterBuffer:
391 if (words.size() != 3) {
392 return emitError(unknownLoc, "OpDecorateId with ")
393 << decorationName << " needs a single <id> operand";
394 }
395 pendingIdDecorations.push_back({words[0],
396 static_cast<spirv::Decoration>(words[1]),
397 words[2], unknownLoc});
398 break;
399 default:
400 return emitError(unknownLoc, "unhandled Decoration : '") << decorationName;
401 }
402 return success();
403}
404
405LogicalResult spirv::Deserializer::resolveDeferredIdDecorations() {
406 for (const DeferredIdDecoration &entry : pendingIdDecorations) {
407 StringRef decorationName = stringifyDecoration(entry.decoration);
408 StringAttr symbol = getSymbolDecoration(decorationName);
409
410 // Resolve the operand <id> to a symbol name. The operand must reference a
411 // module-scope symbol op (global variable or specialization constant).
412 StringRef operandSymName;
413 if (spirv::GlobalVariableOp varOp =
414 globalVariableMap.lookup(entry.operandID))
415 operandSymName = varOp.getSymName();
416 else if (spirv::SpecConstantOp specOp =
417 specConstMap.lookup(entry.operandID))
418 operandSymName = specOp.getSymName();
419 else
420 return emitError(entry.loc, "OpDecorateId with ")
421 << decorationName << " references <id> " << entry.operandID
422 << " which is not a global variable or specialization constant";
423
424 auto symRef = FlatSymbolRefAttr::get(context, operandSymName);
425
426 // Resolve the decoration target. By the time this method runs, all
427 // instructions have been processed, so every defined <id> must appear in
428 // one of these maps; an unresolved target indicates malformed input.
429 Operation *targetOp = nullptr;
430 if (spirv::GlobalVariableOp varOp =
431 globalVariableMap.lookup(entry.targetID))
432 targetOp = varOp;
433 else if (spirv::SpecConstantOp specOp = specConstMap.lookup(entry.targetID))
434 targetOp = specOp;
435 else if (spirv::FuncOp fnOp = funcMap.lookup(entry.targetID))
436 targetOp = fnOp;
437 else if (Value v = valueMap.lookup(entry.targetID))
438 targetOp = v.getDefiningOp();
439
440 if (!targetOp)
441 return emitError(entry.loc, "OpDecorateId with ")
442 << decorationName << " references unknown target <id> "
443 << entry.targetID;
444
445 setInherentOrDiscardableAttr(targetOp, symbol, symRef);
446 }
447 return success();
448}
449
450LogicalResult
451spirv::Deserializer::processMemberDecoration(ArrayRef<uint32_t> words) {
452 // The binary layout of OpMemberDecorate is different comparing to OpDecorate
453 if (words.size() < 3) {
454 return emitError(unknownLoc,
455 "OpMemberDecorate must have at least 3 operands");
456 }
457
458 auto decoration = static_cast<spirv::Decoration>(words[2]);
459 if (decoration == spirv::Decoration::Offset && words.size() != 4) {
460 return emitError(unknownLoc,
461 " missing offset specification in OpMemberDecorate with "
462 "Offset decoration");
463 }
464 ArrayRef<uint32_t> decorationOperands;
465 if (words.size() > 3) {
466 decorationOperands = words.slice(3);
467 }
468 memberDecorationMap[words[0]][words[1]][decoration] = decorationOperands;
469 return success();
470}
471
472LogicalResult spirv::Deserializer::processMemberName(ArrayRef<uint32_t> words) {
473 if (words.size() < 3) {
474 return emitError(unknownLoc, "OpMemberName must have at least 3 operands");
475 }
476 unsigned wordIndex = 2;
477 auto name = decodeStringLiteral(words, wordIndex);
478 if (wordIndex != words.size()) {
479 return emitError(unknownLoc,
480 "unexpected trailing words in OpMemberName instruction");
481 }
482 memberNameMap[words[0]][words[1]] = name;
483 return success();
484}
485
487 uint32_t argID, SmallVectorImpl<Attribute> &argAttrs, size_t argIndex) {
488 if (!decorations.contains(argID)) {
489 argAttrs[argIndex] = DictionaryAttr::get(context, {});
490 return success();
491 }
492
493 spirv::DecorationAttr foundDecorationAttr;
494 for (NamedAttribute decAttr : decorations[argID]) {
495 for (auto decoration :
496 {spirv::Decoration::Aliased, spirv::Decoration::Restrict,
497 spirv::Decoration::AliasedPointer,
498 spirv::Decoration::RestrictPointer}) {
499
500 if (decAttr.getName() !=
501 getSymbolDecoration(stringifyDecoration(decoration)))
502 continue;
503
504 if (foundDecorationAttr)
505 return emitError(unknownLoc,
506 "more than one Aliased/Restrict decorations for "
507 "function argument with result <id> ")
508 << argID;
509
510 foundDecorationAttr = spirv::DecorationAttr::get(context, decoration);
511 break;
512 }
513
514 if (decAttr.getName() == getSymbolDecoration(stringifyDecoration(
515 spirv::Decoration::RelaxedPrecision))) {
516 // TODO: Current implementation supports only one decoration per function
517 // parameter so RelaxedPrecision cannot be applied at the same time as,
518 // for example, Aliased/Restrict/etc. This should be relaxed to allow any
519 // combination of decoration allowed by the spec to be supported.
520 if (foundDecorationAttr)
521 return emitError(unknownLoc, "already found a decoration for function "
522 "argument with result <id> ")
523 << argID;
524
525 foundDecorationAttr = spirv::DecorationAttr::get(
526 context, spirv::Decoration::RelaxedPrecision);
527 }
528 }
529
530 if (!foundDecorationAttr)
531 return emitError(unknownLoc, "unimplemented decoration support for "
532 "function argument with result <id> ")
533 << argID;
534
535 NamedAttribute attr(StringAttr::get(context, spirv::DecorationAttr::name),
536 foundDecorationAttr);
537 argAttrs[argIndex] = DictionaryAttr::get(context, attr);
538 return success();
539}
540
541LogicalResult
543 if (curFunction) {
544 return emitError(unknownLoc, "found function inside function");
545 }
546
547 // Get the result type
548 if (operands.size() != 4) {
549 return emitError(unknownLoc, "OpFunction must have 4 parameters");
550 }
551 Type resultType = getType(operands[0]);
552 if (!resultType) {
553 return emitError(unknownLoc, "undefined result type from <id> ")
554 << operands[0];
555 }
556
557 uint32_t fnID = operands[1];
558 if (funcMap.count(fnID)) {
559 return emitError(unknownLoc, "duplicate function definition/declaration");
560 }
561
562 auto fnControl = spirv::symbolizeFunctionControl(operands[2]);
563 if (!fnControl) {
564 return emitError(unknownLoc, "unknown Function Control: ") << operands[2];
565 }
566
567 Type fnType = getType(operands[3]);
568 if (!fnType || !isa<FunctionType>(fnType)) {
569 return emitError(unknownLoc, "unknown function type from <id> ")
570 << operands[3];
571 }
572 auto functionType = cast<FunctionType>(fnType);
573
574 if ((isVoidType(resultType) && functionType.getNumResults() != 0) ||
575 (functionType.getNumResults() == 1 &&
576 functionType.getResult(0) != resultType)) {
577 return emitError(unknownLoc, "mismatch in function type ")
578 << functionType << " and return type " << resultType << " specified";
579 }
580
581 std::string fnName = getFunctionSymbol(fnID);
582 auto funcOp = spirv::FuncOp::create(opBuilder, unknownLoc, fnName,
583 functionType, fnControl.value());
584 // Processing other function attributes.
585 if (decorations.count(fnID)) {
586 for (auto attr : decorations[fnID].getAttrs()) {
587 setInherentOrDiscardableAttr(funcOp, attr.getName(), attr.getValue());
588 }
589 }
590 curFunction = funcMap[fnID] = funcOp;
591 auto *entryBlock = funcOp.addEntryBlock();
592 LLVM_DEBUG({
593 logger.startLine()
594 << "//===-------------------------------------------===//\n";
595 logger.startLine() << "[fn] name: " << fnName << "\n";
596 logger.startLine() << "[fn] type: " << fnType << "\n";
597 logger.startLine() << "[fn] ID: " << fnID << "\n";
598 logger.startLine() << "[fn] entry block: " << entryBlock << "\n";
599 logger.indent();
600 });
601
602 SmallVector<Attribute> argAttrs;
603 argAttrs.resize(functionType.getNumInputs());
604
605 // Parse the op argument instructions
606 if (functionType.getNumInputs()) {
607 for (size_t i = 0, e = functionType.getNumInputs(); i != e; ++i) {
608 auto argType = functionType.getInput(i);
609 spirv::Opcode opcode = spirv::Opcode::OpNop;
610 ArrayRef<uint32_t> operands;
611 if (failed(sliceInstruction(opcode, operands,
612 spirv::Opcode::OpFunctionParameter))) {
613 return failure();
614 }
615 if (opcode != spirv::Opcode::OpFunctionParameter) {
616 return emitError(
617 unknownLoc,
618 "missing OpFunctionParameter instruction for argument ")
619 << i;
620 }
621 if (operands.size() != 2) {
622 return emitError(
623 unknownLoc,
624 "expected result type and result <id> for OpFunctionParameter");
625 }
626 auto argDefinedType = getType(operands[0]);
627 if (!argDefinedType || argDefinedType != argType) {
628 return emitError(unknownLoc,
629 "mismatch in argument type between function type "
630 "definition ")
631 << functionType << " and argument type definition "
632 << argDefinedType << " at argument " << i;
633 }
634 if (getValue(operands[1])) {
635 return emitError(unknownLoc, "duplicate definition of result <id> ")
636 << operands[1];
637 }
638 if (failed(setFunctionArgAttrs(operands[1], argAttrs, i))) {
639 return failure();
640 }
641
642 auto argValue = funcOp.getArgument(i);
643 valueMap[operands[1]] = argValue;
644 }
645 }
646
647 if (llvm::any_of(argAttrs, [](Attribute attr) {
648 auto argAttr = cast<DictionaryAttr>(attr);
649 return !argAttr.empty();
650 }))
651 funcOp.setArgAttrsAttr(ArrayAttr::get(context, argAttrs));
652
653 // entryBlock is needed to access the arguments, Once that is done, we can
654 // erase the block for functions with 'Import' LinkageAttributes, since these
655 // are essentially function declarations, so they have no body.
656 auto linkageAttr = funcOp.getLinkageAttributes();
657 auto hasImportLinkage =
658 linkageAttr && (linkageAttr.value().getLinkageType().getValue() ==
659 spirv::LinkageType::Import);
660 if (hasImportLinkage)
661 funcOp.eraseBody();
662
663 // RAII guard to reset the insertion point to the module's region after
664 // deserializing the body of this function.
665 OpBuilder::InsertionGuard moduleInsertionGuard(opBuilder);
666
667 spirv::Opcode opcode = spirv::Opcode::OpNop;
668 ArrayRef<uint32_t> instOperands;
669
670 // Special handling for the entry block. We need to make sure it starts with
671 // an OpLabel instruction. The entry block takes the same parameters as the
672 // function. All other blocks do not take any parameter. We have already
673 // created the entry block, here we need to register it to the correct label
674 // <id>.
675 if (failed(sliceInstruction(opcode, instOperands,
676 spirv::Opcode::OpFunctionEnd))) {
677 return failure();
678 }
679 if (opcode == spirv::Opcode::OpFunctionEnd) {
680 return processFunctionEnd(instOperands);
681 }
682 if (opcode != spirv::Opcode::OpLabel) {
683 return emitError(unknownLoc, "a basic block must start with OpLabel");
684 }
685 if (instOperands.size() != 1) {
686 return emitError(unknownLoc, "OpLabel should only have result <id>");
687 }
688 blockMap[instOperands[0]] = entryBlock;
689 if (failed(processLabel(instOperands))) {
690 return failure();
691 }
692
693 // Then process all the other instructions in the function until we hit
694 // OpFunctionEnd.
695 while (succeeded(sliceInstruction(opcode, instOperands,
696 spirv::Opcode::OpFunctionEnd)) &&
697 opcode != spirv::Opcode::OpFunctionEnd) {
698 if (failed(processInstruction(opcode, instOperands))) {
699 return failure();
700 }
701 }
702 if (opcode != spirv::Opcode::OpFunctionEnd) {
703 return failure();
704 }
705
706 return processFunctionEnd(instOperands);
707}
708
709LogicalResult
711 // Process OpFunctionEnd.
712 if (!operands.empty()) {
713 return emitError(unknownLoc, "unexpected operands for OpFunctionEnd");
714 }
715
716 // Wire up block arguments from OpPhi instructions.
717 // Put all structured control flow in spirv.mlir.selection/spirv.mlir.loop
718 // ops.
719 if (failed(wireUpBlockArgument()) || failed(structurizeControlFlow())) {
720 return failure();
721 }
722
723 curBlock = nullptr;
724 curFunction = std::nullopt;
725
726 LLVM_DEBUG({
727 logger.unindent();
728 logger.startLine()
729 << "//===-------------------------------------------===//\n";
730 });
731 return success();
732}
733
734LogicalResult
736 if (operands.size() < 2) {
737 return emitError(unknownLoc,
738 "missing graph defintion in OpGraphEntryPointARM");
739 }
740
741 unsigned wordIndex = 0;
742 uint32_t graphID = operands[wordIndex++];
743 if (!graphMap.contains(graphID)) {
744 return emitError(unknownLoc,
745 "missing graph definition/declaration with id ")
746 << graphID;
747 }
748
749 spirv::GraphARMOp graphARM = graphMap[graphID];
750 StringRef name = decodeStringLiteral(operands, wordIndex);
751 graphARM.setSymName(name);
752 graphARM.setEntryPoint(true);
753
755 for (int64_t size = operands.size(); wordIndex < size; ++wordIndex) {
756 if (spirv::GlobalVariableOp arg = getGlobalVariable(operands[wordIndex])) {
757 interface.push_back(SymbolRefAttr::get(arg.getOperation()));
758 } else {
759 return emitError(unknownLoc, "undefined result <id> ")
760 << operands[wordIndex] << " while decoding OpGraphEntryPoint";
761 }
762 }
763
764 // RAII guard to reset the insertion point to previous value when done.
765 OpBuilder::InsertionGuard insertionGuard(opBuilder);
766 opBuilder.setInsertionPoint(graphARM);
767 spirv::GraphEntryPointARMOp::create(
768 opBuilder, unknownLoc, SymbolRefAttr::get(opBuilder.getContext(), name),
769 opBuilder.getArrayAttr(interface));
770
771 return success();
772}
773
774LogicalResult
776 if (curGraph) {
777 return emitError(unknownLoc, "found graph inside graph");
778 }
779 // Get the result type.
780 if (operands.size() < 2) {
781 return emitError(unknownLoc, "OpGraphARM must have at least 2 parameters");
782 }
783
784 Type type = getType(operands[0]);
785 if (!type || !isa<GraphType>(type)) {
786 return emitError(unknownLoc, "unknown graph type from <id> ")
787 << operands[0];
788 }
789 auto graphType = cast<GraphType>(type);
790 if (graphType.getNumResults() <= 0) {
791 return emitError(unknownLoc, "expected at least one result");
792 }
793
794 uint32_t graphID = operands[1];
795 if (graphMap.count(graphID)) {
796 return emitError(unknownLoc, "duplicate graph definition/declaration");
797 }
798
799 std::string graphName = getGraphSymbol(graphID);
800 auto graphOp =
801 spirv::GraphARMOp::create(opBuilder, unknownLoc, graphName, graphType);
802 curGraph = graphMap[graphID] = graphOp;
803 Block *entryBlock = graphOp.addEntryBlock();
804 LLVM_DEBUG({
805 logger.startLine()
806 << "//===-------------------------------------------===//\n";
807 logger.startLine() << "[graph] name: " << graphName << "\n";
808 logger.startLine() << "[graph] type: " << graphType << "\n";
809 logger.startLine() << "[graph] ID: " << graphID << "\n";
810 logger.startLine() << "[graph] entry block: " << entryBlock << "\n";
811 logger.indent();
812 });
813
814 // Parse the op argument instructions.
815 for (auto [index, argType] : llvm::enumerate(graphType.getInputs())) {
816 spirv::Opcode opcode;
817 ArrayRef<uint32_t> operands;
818 if (failed(sliceInstruction(opcode, operands,
819 spirv::Opcode::OpGraphInputARM))) {
820 return failure();
821 }
822 if (operands.size() != 3) {
823 return emitError(unknownLoc, "expected result type, result <id> and "
824 "input index for OpGraphInputARM");
825 }
826
827 Type argDefinedType = getType(operands[0]);
828 if (!argDefinedType) {
829 return emitError(unknownLoc, "unknown operand type <id> ") << operands[0];
830 }
831
832 if (argDefinedType != argType) {
833 return emitError(unknownLoc,
834 "mismatch in argument type between graph type "
835 "definition ")
836 << graphType << " and argument type definition " << argDefinedType
837 << " at argument " << index;
838 }
839 if (getValue(operands[1])) {
840 return emitError(unknownLoc, "duplicate definition of result <id> ")
841 << operands[1];
842 }
843
844 IntegerAttr inputIndexAttr = getConstantInt(operands[2]);
845 if (!inputIndexAttr) {
846 return emitError(unknownLoc,
847 "unable to read inputIndex value from constant op ")
848 << operands[2];
849 }
850 BlockArgument argValue = graphOp.getArgument(inputIndexAttr.getInt());
851 valueMap[operands[1]] = argValue;
852 }
853
854 graphOutputs.resize(graphType.getNumResults());
855
856 // RAII guard to reset the insertion point to the module's region after
857 // deserializing the body of this function.
858 OpBuilder::InsertionGuard moduleInsertionGuard(opBuilder);
859
860 blockMap[graphID] = entryBlock;
861 if (failed(createGraphBlock(graphID))) {
862 return failure();
863 }
864
865 // Process all the instructions in the graph until and including
866 // OpGraphEndARM.
867 spirv::Opcode opcode;
868 ArrayRef<uint32_t> instOperands;
869 do {
870 if (failed(sliceInstruction(opcode, instOperands, std::nullopt))) {
871 return failure();
872 }
873
874 if (failed(processInstruction(opcode, instOperands))) {
875 return failure();
876 }
877 } while (opcode != spirv::Opcode::OpGraphEndARM);
878
879 return success();
880}
881
882LogicalResult
884 if (operands.size() != 2) {
885 return emitError(
886 unknownLoc,
887 "expected value id and output index for OpGraphSetOutputARM");
888 }
889
890 uint32_t id = operands[0];
891 Value value = getValue(id);
892 if (!value) {
893 return emitError(unknownLoc, "could not find result <id> ") << id;
894 }
895
896 IntegerAttr outputIndexAttr = getConstantInt(operands[1]);
897 if (!outputIndexAttr) {
898 return emitError(unknownLoc,
899 "unable to read outputIndex value from constant op ")
900 << operands[1];
901 }
902 graphOutputs[outputIndexAttr.getInt()] = value;
903 return success();
904}
905
906LogicalResult
908 // Create GraphOutputsARM instruction.
909 spirv::GraphOutputsARMOp::create(opBuilder, unknownLoc, graphOutputs);
910
911 // Process OpGraphEndARM.
912 if (!operands.empty()) {
913 return emitError(unknownLoc, "unexpected operands for OpGraphEndARM");
914 }
915
916 curBlock = nullptr;
917 curGraph = std::nullopt;
918 graphOutputs.clear();
919
920 LLVM_DEBUG({
921 logger.unindent();
922 logger.startLine()
923 << "//===-------------------------------------------===//\n";
924 });
925 return success();
926}
927
928std::optional<std::pair<Attribute, Type>>
930 auto constIt = constantMap.find(id);
931 if (constIt != constantMap.end())
932 return constIt->getSecond();
933
934 auto replicatedConstIt = constantCompositeReplicateMap.find(id);
935 if (replicatedConstIt == constantCompositeReplicateMap.end())
936 return std::nullopt;
937
938 auto [value, type] = replicatedConstIt->getSecond();
939 auto shapedType = dyn_cast<ShapedType>(type);
940 if (!shapedType)
941 return std::nullopt;
942 return std::make_pair(SplatElementsAttr::get(shapedType, value), type);
943}
944
945std::optional<std::pair<Attribute, Type>>
947 if (auto it = constantCompositeReplicateMap.find(id);
948 it != constantCompositeReplicateMap.end())
949 return it->second;
950 return std::nullopt;
951}
952
953std::optional<spirv::SpecConstOperationMaterializationInfo>
955 auto constIt = specConstOperationMap.find(id);
956 if (constIt == specConstOperationMap.end())
957 return std::nullopt;
958 return constIt->getSecond();
959}
960
962 auto funcName = nameMap.lookup(id).str();
963 if (funcName.empty()) {
964 funcName = "spirv_fn_" + std::to_string(id);
965 }
966 return funcName;
967}
968
969std::string spirv::Deserializer::getGraphSymbol(uint32_t id) {
970 std::string graphName = nameMap.lookup(id).str();
971 if (graphName.empty()) {
972 graphName = "spirv_graph_" + std::to_string(id);
973 }
974 return graphName;
975}
976
978 auto constName = nameMap.lookup(id).str();
979 if (constName.empty()) {
980 constName = "spirv_spec_const_" + std::to_string(id);
981 }
982 return constName;
983}
984
985spirv::SpecConstantOp
987 TypedAttr defaultValue) {
988 auto symName = opBuilder.getStringAttr(getSpecConstantSymbol(resultID));
989 auto op = spirv::SpecConstantOp::create(opBuilder, unknownLoc, symName,
990 defaultValue,
991 /*sym_visibility=*/nullptr);
992 if (decorations.count(resultID)) {
993 for (auto attr : decorations[resultID].getAttrs())
994 setInherentOrDiscardableAttr(op, attr.getName(), attr.getValue());
995 }
996 specConstMap[resultID] = op;
997 return op;
998}
999
1000std::optional<spirv::GraphConstantARMOpMaterializationInfo>
1002 auto graphConstIt = graphConstantMap.find(id);
1003 if (graphConstIt == graphConstantMap.end())
1004 return std::nullopt;
1005 return graphConstIt->getSecond();
1006}
1007
1008LogicalResult
1010 unsigned wordIndex = 0;
1011 if (operands.size() < 3) {
1012 return emitError(
1013 unknownLoc,
1014 "OpVariable needs at least 3 operands, type, <id> and storage class");
1015 }
1016
1017 // Result Type.
1018 auto type = getType(operands[wordIndex]);
1019 if (!type) {
1020 return emitError(unknownLoc, "unknown result type <id> : ")
1021 << operands[wordIndex];
1022 }
1023 auto ptrType = dyn_cast<spirv::PointerType>(type);
1024 if (!ptrType) {
1025 return emitError(unknownLoc,
1026 "expected a result type <id> to be a spirv.ptr, found : ")
1027 << type;
1028 }
1029 wordIndex++;
1030
1031 // Result <id>.
1032 auto variableID = operands[wordIndex];
1033 auto variableName = nameMap.lookup(variableID).str();
1034 if (variableName.empty()) {
1035 variableName = "spirv_var_" + std::to_string(variableID);
1036 }
1037 wordIndex++;
1038
1039 // Storage class.
1040 auto storageClass = static_cast<spirv::StorageClass>(operands[wordIndex]);
1041 if (ptrType.getStorageClass() != storageClass) {
1042 return emitError(unknownLoc, "mismatch in storage class of pointer type ")
1043 << type << " and that specified in OpVariable instruction : "
1044 << stringifyStorageClass(storageClass);
1045 }
1046 wordIndex++;
1047
1048 // Initializer.
1049 FlatSymbolRefAttr initializer = nullptr;
1050
1051 if (wordIndex < operands.size()) {
1052 Operation *op = nullptr;
1053
1054 if (auto initOp = getGlobalVariable(operands[wordIndex]))
1055 op = initOp;
1056 else if (auto initOp = getSpecConstant(operands[wordIndex]))
1057 op = initOp;
1058 else if (auto initOp = getSpecConstantComposite(operands[wordIndex]))
1059 op = initOp;
1060 else
1061 return emitError(unknownLoc, "unknown <id> ")
1062 << operands[wordIndex] << "used as initializer";
1063
1064 initializer = SymbolRefAttr::get(op);
1065 wordIndex++;
1066 }
1067 if (wordIndex != operands.size()) {
1068 return emitError(unknownLoc,
1069 "found more operands than expected when deserializing "
1070 "OpVariable instruction, only ")
1071 << wordIndex << " of " << operands.size() << " processed";
1072 }
1073 auto loc = createFileLineColLoc(opBuilder);
1074 auto varOp = spirv::GlobalVariableOp::create(
1075 opBuilder, loc, TypeAttr::get(type),
1076 opBuilder.getStringAttr(variableName), initializer);
1077
1078 // Decorations.
1079 if (decorations.count(variableID)) {
1080 for (auto attr : decorations[variableID].getAttrs())
1081 setInherentOrDiscardableAttr(varOp, attr.getName(), attr.getValue());
1082 }
1083 globalVariableMap[variableID] = varOp;
1084 return success();
1085}
1086
1087IntegerAttr spirv::Deserializer::getConstantInt(uint32_t id) {
1088 auto constInfo = getConstant(id);
1089 if (!constInfo) {
1090 return nullptr;
1091 }
1092 return dyn_cast<IntegerAttr>(constInfo->first);
1093}
1094
1095LogicalResult spirv::Deserializer::processName(ArrayRef<uint32_t> operands) {
1096 if (operands.size() < 2) {
1097 return emitError(unknownLoc, "OpName needs at least 2 operands");
1098 }
1099
1100 unsigned wordIndex = 1;
1101 StringRef name = decodeStringLiteral(operands, wordIndex);
1102 if (wordIndex != operands.size()) {
1103 return emitError(unknownLoc,
1104 "unexpected trailing words in OpName instruction");
1105 }
1106
1107 // In SPIRV it's valid for multiple OpName instructions to refer to the same
1108 // <id>. Use a "last one wins" approach to resolve such cases.
1109 nameMap.emplace_or_assign(operands[0], name);
1110
1111 return success();
1112}
1113
1114//===----------------------------------------------------------------------===//
1115// Type
1116//===----------------------------------------------------------------------===//
1117
1118LogicalResult spirv::Deserializer::processType(spirv::Opcode opcode,
1119 ArrayRef<uint32_t> operands) {
1120 if (operands.empty()) {
1121 return emitError(unknownLoc, "type instruction with opcode ")
1122 << spirv::stringifyOpcode(opcode) << " needs at least one <id>";
1123 }
1124
1125 /// TODO: Types might be forward declared in some instructions and need to be
1126 /// handled appropriately.
1127 if (typeMap.count(operands[0])) {
1128 return emitError(unknownLoc, "duplicate definition for result <id> ")
1129 << operands[0];
1130 }
1131
1132 switch (opcode) {
1133 case spirv::Opcode::OpTypeVoid:
1134 if (operands.size() != 1)
1135 return emitError(unknownLoc, "OpTypeVoid must have no parameters");
1136 typeMap[operands[0]] = opBuilder.getNoneType();
1137 break;
1138 case spirv::Opcode::OpTypeBool:
1139 if (operands.size() != 1)
1140 return emitError(unknownLoc, "OpTypeBool must have no parameters");
1141 typeMap[operands[0]] = opBuilder.getI1Type();
1142 break;
1143 case spirv::Opcode::OpTypeInt: {
1144 if (operands.size() != 3)
1145 return emitError(
1146 unknownLoc, "OpTypeInt must have bitwidth and signedness parameters");
1147
1148 // SPIR-V OpTypeInt "Signedness specifies whether there are signed semantics
1149 // to preserve or validate.
1150 // 0 indicates unsigned, or no signedness semantics
1151 // 1 indicates signed semantics."
1152 //
1153 // So we cannot differentiate signless and unsigned integers; always use
1154 // signless semantics for such cases.
1155 auto sign = operands[2] == 1 ? IntegerType::SignednessSemantics::Signed
1156 : IntegerType::SignednessSemantics::Signless;
1157 typeMap[operands[0]] = IntegerType::get(context, operands[1], sign);
1158 } break;
1159 case spirv::Opcode::OpTypeFloat: {
1160 if (operands.size() != 2 && operands.size() != 3)
1161 return emitError(unknownLoc,
1162 "OpTypeFloat expects either 2 operands (type, bitwidth) "
1163 "or 3 operands (type, bitwidth, encoding), but got ")
1164 << operands.size();
1165 uint32_t bitWidth = operands[1];
1166
1167 Type floatTy;
1168 if (operands.size() == 2) {
1169 switch (bitWidth) {
1170 case 16:
1171 floatTy = opBuilder.getF16Type();
1172 break;
1173 case 32:
1174 floatTy = opBuilder.getF32Type();
1175 break;
1176 case 64:
1177 floatTy = opBuilder.getF64Type();
1178 break;
1179 default:
1180 return emitError(unknownLoc, "unsupported OpTypeFloat bitwidth: ")
1181 << bitWidth;
1182 }
1183 }
1184
1185 if (operands.size() == 3) {
1186 if (spirv::FPEncoding(operands[2]) == spirv::FPEncoding::BFloat16KHR &&
1187 bitWidth == 16)
1188 floatTy = opBuilder.getBF16Type();
1189 else if (spirv::FPEncoding(operands[2]) ==
1190 spirv::FPEncoding::Float8E4M3EXT &&
1191 bitWidth == 8)
1192 floatTy = opBuilder.getF8E4M3FNType();
1193 else if (spirv::FPEncoding(operands[2]) ==
1194 spirv::FPEncoding::Float8E5M2EXT &&
1195 bitWidth == 8)
1196 floatTy = opBuilder.getF8E5M2Type();
1197 else
1198 return emitError(unknownLoc, "unsupported OpTypeFloat FP encoding: ")
1199 << operands[2] << " and bitWidth " << bitWidth;
1200 }
1201
1202 typeMap[operands[0]] = floatTy;
1203 } break;
1204 case spirv::Opcode::OpTypeVector: {
1205 if (operands.size() != 3) {
1206 return emitError(
1207 unknownLoc,
1208 "OpTypeVector must have element type and count parameters");
1209 }
1210 Type elementTy = getType(operands[1]);
1211 if (!elementTy) {
1212 return emitError(unknownLoc, "OpTypeVector references undefined <id> ")
1213 << operands[1];
1214 }
1215 typeMap[operands[0]] = VectorType::get({operands[2]}, elementTy);
1216 } break;
1217 case spirv::Opcode::OpTypePointer: {
1218 return processOpTypePointer(operands);
1219 } break;
1220 case spirv::Opcode::OpTypeArray:
1221 return processArrayType(operands);
1222 case spirv::Opcode::OpTypeCooperativeMatrixKHR:
1223 return processCooperativeMatrixTypeKHR(operands);
1224 case spirv::Opcode::OpTypeFunction:
1225 return processFunctionType(operands);
1226 case spirv::Opcode::OpTypeImage:
1227 return processImageType(operands);
1228 case spirv::Opcode::OpTypeSampler:
1229 return processSamplerType(operands);
1230 case spirv::Opcode::OpTypeNamedBarrier:
1231 return processNamedBarrierType(operands);
1232 case spirv::Opcode::OpTypeSampledImage:
1233 return processSampledImageType(operands);
1234 case spirv::Opcode::OpTypeRuntimeArray:
1235 return processRuntimeArrayType(operands);
1236 case spirv::Opcode::OpTypeStruct:
1237 return processStructType(operands);
1238 case spirv::Opcode::OpTypeMatrix:
1239 return processMatrixType(operands);
1240 case spirv::Opcode::OpTypeTensorARM:
1241 return processTensorARMType(operands);
1242 case spirv::Opcode::OpTypeGraphARM:
1243 return processGraphTypeARM(operands);
1244 default:
1245 return emitError(unknownLoc, "unhandled type instruction");
1246 }
1247 return success();
1248}
1249
1250LogicalResult
1252 if (operands.size() != 3)
1253 return emitError(unknownLoc, "OpTypePointer must have two parameters");
1254
1255 auto pointeeType = getType(operands[2]);
1256 if (!pointeeType)
1257 return emitError(unknownLoc, "unknown OpTypePointer pointee type <id> ")
1258 << operands[2];
1259
1260 uint32_t typePointerID = operands[0];
1261 auto storageClass = static_cast<spirv::StorageClass>(operands[1]);
1262 typeMap[typePointerID] = spirv::PointerType::get(pointeeType, storageClass);
1263
1264 for (auto *deferredStructIt = std::begin(deferredStructTypesInfos);
1265 deferredStructIt != std::end(deferredStructTypesInfos);) {
1266 for (auto *unresolvedMemberIt =
1267 std::begin(deferredStructIt->unresolvedMemberTypes);
1268 unresolvedMemberIt !=
1269 std::end(deferredStructIt->unresolvedMemberTypes);) {
1270 if (unresolvedMemberIt->first == typePointerID) {
1271 // The newly constructed pointer type can resolve one of the
1272 // deferred struct type members; update the memberTypes list and
1273 // clean the unresolvedMemberTypes list accordingly.
1274 deferredStructIt->memberTypes[unresolvedMemberIt->second] =
1275 typeMap[typePointerID];
1276 unresolvedMemberIt =
1277 deferredStructIt->unresolvedMemberTypes.erase(unresolvedMemberIt);
1278 } else {
1279 ++unresolvedMemberIt;
1280 }
1281 }
1282
1283 if (deferredStructIt->unresolvedMemberTypes.empty()) {
1284 // All deferred struct type members are now resolved, set the struct body.
1285 auto structType = deferredStructIt->deferredStructType;
1286
1287 assert(structType && "expected a spirv::StructType");
1288 assert(structType.isIdentified() && "expected an indentified struct");
1289
1290 if (failed(structType.trySetBody(
1291 deferredStructIt->memberTypes, deferredStructIt->offsetInfo,
1292 deferredStructIt->memberDecorationsInfo,
1293 deferredStructIt->structDecorationsInfo)))
1294 return failure();
1295
1296 deferredStructIt = deferredStructTypesInfos.erase(deferredStructIt);
1297 } else {
1298 ++deferredStructIt;
1299 }
1300 }
1301
1302 return success();
1303}
1304
1305LogicalResult
1307 if (operands.size() != 3) {
1308 return emitError(unknownLoc,
1309 "OpTypeArray must have element type and count parameters");
1310 }
1311
1312 Type elementTy = getType(operands[1]);
1313 if (!elementTy) {
1314 return emitError(unknownLoc, "OpTypeArray references undefined <id> ")
1315 << operands[1];
1316 }
1317
1318 unsigned count = 0;
1319 // TODO: The count can also come frome a specialization constant.
1320 auto countInfo = getConstant(operands[2]);
1321 if (!countInfo) {
1322 return emitError(unknownLoc, "OpTypeArray count <id> ")
1323 << operands[2] << "can only come from normal constant right now";
1324 }
1325
1326 if (auto intVal = dyn_cast<IntegerAttr>(countInfo->first)) {
1327 count = intVal.getValue().getZExtValue();
1328 } else {
1329 return emitError(unknownLoc, "OpTypeArray count must come from a "
1330 "scalar integer constant instruction");
1331 }
1332
1333 typeMap[operands[0]] = spirv::ArrayType::get(
1334 elementTy, count, typeDecorations.lookup(operands[0]));
1335 return success();
1336}
1337
1338LogicalResult
1340 assert(!operands.empty() && "No operands for processing function type");
1341 if (operands.size() == 1) {
1342 return emitError(unknownLoc, "missing return type for OpTypeFunction");
1343 }
1344 auto returnType = getType(operands[1]);
1345 if (!returnType) {
1346 return emitError(unknownLoc, "unknown return type in OpTypeFunction");
1347 }
1348 SmallVector<Type, 1> argTypes;
1349 for (size_t i = 2, e = operands.size(); i < e; ++i) {
1350 auto ty = getType(operands[i]);
1351 if (!ty) {
1352 return emitError(unknownLoc, "unknown argument type in OpTypeFunction");
1353 }
1354 argTypes.push_back(ty);
1355 }
1356 ArrayRef<Type> returnTypes;
1357 if (!isVoidType(returnType)) {
1358 returnTypes = llvm::ArrayRef(returnType);
1359 }
1360 typeMap[operands[0]] = FunctionType::get(context, argTypes, returnTypes);
1361 return success();
1362}
1363
1365 ArrayRef<uint32_t> operands) {
1366 if (operands.size() != 6) {
1367 return emitError(unknownLoc,
1368 "OpTypeCooperativeMatrixKHR must have element type, "
1369 "scope, row and column parameters, and use");
1370 }
1371
1372 Type elementTy = getType(operands[1]);
1373 if (!elementTy) {
1374 return emitError(unknownLoc,
1375 "OpTypeCooperativeMatrixKHR references undefined <id> ")
1376 << operands[1];
1377 }
1378
1379 std::optional<spirv::Scope> scope =
1380 spirv::symbolizeScope(getConstantInt(operands[2]).getInt());
1381 if (!scope) {
1382 return emitError(
1383 unknownLoc,
1384 "OpTypeCooperativeMatrixKHR references undefined scope <id> ")
1385 << operands[2];
1386 }
1387
1388 IntegerAttr rowsAttr = getConstantInt(operands[3]);
1389 IntegerAttr columnsAttr = getConstantInt(operands[4]);
1390 IntegerAttr useAttr = getConstantInt(operands[5]);
1391
1392 if (!rowsAttr)
1393 return emitError(unknownLoc, "OpTypeCooperativeMatrixKHR `Rows` references "
1394 "undefined constant <id> ")
1395 << operands[3];
1396
1397 if (!columnsAttr)
1398 return emitError(unknownLoc, "OpTypeCooperativeMatrixKHR `Columns` "
1399 "references undefined constant <id> ")
1400 << operands[4];
1401
1402 if (!useAttr)
1403 return emitError(unknownLoc, "OpTypeCooperativeMatrixKHR `Use` references "
1404 "undefined constant <id> ")
1405 << operands[5];
1406
1407 unsigned rows = rowsAttr.getInt();
1408 unsigned columns = columnsAttr.getInt();
1409
1410 std::optional<spirv::CooperativeMatrixUseKHR> use =
1411 spirv::symbolizeCooperativeMatrixUseKHR(useAttr.getInt());
1412 if (!use) {
1413 return emitError(
1414 unknownLoc,
1415 "OpTypeCooperativeMatrixKHR references undefined use <id> ")
1416 << operands[5];
1417 }
1418
1419 typeMap[operands[0]] =
1420 spirv::CooperativeMatrixType::get(elementTy, rows, columns, *scope, *use);
1421 return success();
1422}
1423
1424LogicalResult
1426 if (operands.size() != 2) {
1427 return emitError(unknownLoc, "OpTypeRuntimeArray must have two operands");
1428 }
1429 Type memberType = getType(operands[1]);
1430 if (!memberType) {
1431 return emitError(unknownLoc,
1432 "OpTypeRuntimeArray references undefined <id> ")
1433 << operands[1];
1434 }
1435 typeMap[operands[0]] = spirv::RuntimeArrayType::get(
1436 memberType, typeDecorations.lookup(operands[0]));
1437 return success();
1438}
1439
1440LogicalResult
1442 // TODO: Find a way to handle identified structs when debug info is stripped.
1443
1444 if (operands.empty()) {
1445 return emitError(unknownLoc, "OpTypeStruct must have at least result <id>");
1446 }
1447
1448 if (operands.size() == 1) {
1449 // Handle empty struct.
1450 typeMap[operands[0]] =
1451 spirv::StructType::getEmpty(context, nameMap.lookup(operands[0]).str());
1452 return success();
1453 }
1454
1455 // First element is operand ID, second element is member index in the struct.
1456 SmallVector<std::pair<uint32_t, unsigned>, 0> unresolvedMemberTypes;
1457 SmallVector<Type, 4> memberTypes;
1458
1459 for (auto op : llvm::drop_begin(operands, 1)) {
1460 Type memberType = getType(op);
1461 bool typeForwardPtr = (typeForwardPointerIDs.count(op) != 0);
1462
1463 if (!memberType && !typeForwardPtr)
1464 return emitError(unknownLoc, "OpTypeStruct references undefined <id> ")
1465 << op;
1466
1467 if (!memberType)
1468 unresolvedMemberTypes.emplace_back(op, memberTypes.size());
1469
1470 memberTypes.push_back(memberType);
1471 }
1472
1475 if (memberDecorationMap.count(operands[0])) {
1476 auto &allMemberDecorations = memberDecorationMap[operands[0]];
1477 for (auto memberIndex : llvm::seq<uint32_t>(0, memberTypes.size())) {
1478 if (allMemberDecorations.count(memberIndex)) {
1479 for (auto &memberDecoration : allMemberDecorations[memberIndex]) {
1480 // Check for offset.
1481 if (memberDecoration.first == spirv::Decoration::Offset) {
1482 // If offset info is empty, resize to the number of members;
1483 if (offsetInfo.empty()) {
1484 offsetInfo.resize(memberTypes.size());
1485 }
1486 offsetInfo[memberIndex] = memberDecoration.second[0];
1487 } else {
1488 auto intType = mlir::IntegerType::get(context, 32);
1489 if (!memberDecoration.second.empty()) {
1490 memberDecorationsInfo.emplace_back(
1491 memberIndex, memberDecoration.first,
1492 IntegerAttr::get(intType, memberDecoration.second[0]));
1493 } else {
1494 memberDecorationsInfo.emplace_back(
1495 memberIndex, memberDecoration.first, UnitAttr::get(context));
1496 }
1497 }
1498 }
1499 }
1500 }
1501 }
1502
1504 if (decorations.count(operands[0])) {
1505 NamedAttrList &allDecorations = decorations[operands[0]];
1506 for (NamedAttribute &decorationAttr : allDecorations) {
1507 std::optional<spirv::Decoration> decoration = spirv::symbolizeDecoration(
1508 llvm::convertToCamelFromSnakeCase(decorationAttr.getName(), true));
1509 assert(decoration.has_value());
1510 structDecorationsInfo.emplace_back(decoration.value(),
1511 decorationAttr.getValue());
1512 }
1513 }
1514
1515 uint32_t structID = operands[0];
1516 std::string structIdentifier = nameMap.lookup(structID).str();
1517
1518 if (structIdentifier.empty()) {
1519 assert(unresolvedMemberTypes.empty() &&
1520 "didn't expect unresolved member types");
1521 typeMap[structID] = spirv::StructType::get(
1522 memberTypes, offsetInfo, memberDecorationsInfo, structDecorationsInfo);
1523 } else {
1524 auto structTy = spirv::StructType::getIdentified(context, structIdentifier);
1525 typeMap[structID] = structTy;
1526
1527 if (!unresolvedMemberTypes.empty())
1528 deferredStructTypesInfos.push_back(
1529 {structTy, std::move(unresolvedMemberTypes), std::move(memberTypes),
1530 std::move(offsetInfo), std::move(memberDecorationsInfo),
1531 std::move(structDecorationsInfo)});
1532 else if (failed(structTy.trySetBody(memberTypes, offsetInfo,
1533 memberDecorationsInfo,
1534 structDecorationsInfo)))
1535 return failure();
1536 }
1537
1538 // TODO: Update StructType to have member name as attribute as
1539 // well.
1540 return success();
1541}
1542
1543LogicalResult
1545 if (operands.size() != 3) {
1546 // Three operands are needed: result_id, column_type, and column_count
1547 return emitError(unknownLoc, "OpTypeMatrix must have 3 operands"
1548 " (result_id, column_type, and column_count)");
1549 }
1550 // Matrix columns must be of vector type
1551 Type elementTy = getType(operands[1]);
1552 if (!elementTy) {
1553 return emitError(unknownLoc,
1554 "OpTypeMatrix references undefined column type.")
1555 << operands[1];
1556 }
1557
1558 uint32_t colsCount = operands[2];
1559 typeMap[operands[0]] = spirv::MatrixType::get(elementTy, colsCount);
1560 return success();
1561}
1562
1563LogicalResult
1565 unsigned size = operands.size();
1566 if (size < 2 || size > 4)
1567 return emitError(unknownLoc, "OpTypeTensorARM must have 2-4 operands "
1568 "(result_id, element_type, (rank), (shape)) ")
1569 << size;
1570
1571 Type elementTy = getType(operands[1]);
1572 if (!elementTy)
1573 return emitError(unknownLoc,
1574 "OpTypeTensorARM references undefined element type ")
1575 << operands[1];
1576
1577 if (size == 2) {
1578 typeMap[operands[0]] = TensorArmType::get({}, elementTy);
1579 return success();
1580 }
1581
1582 IntegerAttr rankAttr = getConstantInt(operands[2]);
1583 if (!rankAttr)
1584 return emitError(unknownLoc, "OpTypeTensorARM rank must come from a "
1585 "scalar integer constant instruction");
1586 unsigned rank = rankAttr.getValue().getZExtValue();
1587 if (size == 3) {
1588 SmallVector<int64_t, 4> shape(rank, ShapedType::kDynamic);
1589 typeMap[operands[0]] = TensorArmType::get(shape, elementTy);
1590 return success();
1591 }
1592
1593 std::optional<std::pair<Attribute, Type>> shapeInfo =
1594 getConstant(operands[3]);
1595 if (!shapeInfo)
1596 return emitError(unknownLoc, "OpTypeTensorARM shape must come from a "
1597 "constant instruction of type OpTypeArray");
1598
1599 ArrayAttr shapeArrayAttr = dyn_cast<ArrayAttr>(shapeInfo->first);
1601 for (auto dimAttr : shapeArrayAttr.getValue()) {
1602 auto dimIntAttr = dyn_cast<IntegerAttr>(dimAttr);
1603 if (!dimIntAttr)
1604 return emitError(unknownLoc, "OpTypeTensorARM shape has an invalid "
1605 "dimension size");
1606 shape.push_back(dimIntAttr.getValue().getSExtValue());
1607 }
1608 typeMap[operands[0]] = TensorArmType::get(shape, elementTy);
1609 return success();
1610}
1611
1612LogicalResult
1614 unsigned size = operands.size();
1615 if (size < 2) {
1616 return emitError(unknownLoc, "OpTypeGraphARM must have at least 2 operands "
1617 "(result_id, num_inputs, (inout0_type, "
1618 "inout1_type, ...))")
1619 << size;
1620 }
1621 uint32_t numInputs = operands[1];
1622 SmallVector<Type, 1> argTypes;
1623 SmallVector<Type, 1> returnTypes;
1624 for (unsigned i = 2; i < size; ++i) {
1625 Type inOutTy = getType(operands[i]);
1626 if (!inOutTy) {
1627 return emitError(unknownLoc,
1628 "OpTypeGraphARM references undefined element type.")
1629 << operands[i];
1630 }
1631 if (i - 2 >= numInputs) {
1632 returnTypes.push_back(inOutTy);
1633 } else {
1634 argTypes.push_back(inOutTy);
1635 }
1636 }
1637 typeMap[operands[0]] = GraphType::get(context, argTypes, returnTypes);
1638 return success();
1639}
1640
1641LogicalResult
1643 if (operands.size() != 2)
1644 return emitError(unknownLoc,
1645 "OpTypeForwardPointer instruction must have two operands");
1646
1647 typeForwardPointerIDs.insert(operands[0]);
1648 // TODO: Use the 2nd operand (Storage Class) to validate the OpTypePointer
1649 // instruction that defines the actual type.
1650
1651 return success();
1652}
1653
1654LogicalResult
1656 // TODO: Add support for Access Qualifier.
1657 if (operands.size() != 8)
1658 return emitError(
1659 unknownLoc,
1660 "OpTypeImage with non-eight operands are not supported yet");
1661
1662 Type elementTy = getType(operands[1]);
1663 if (!elementTy)
1664 return emitError(unknownLoc, "OpTypeImage references undefined <id>: ")
1665 << operands[1];
1666
1667 auto dim = spirv::symbolizeDim(operands[2]);
1668 if (!dim)
1669 return emitError(unknownLoc, "unknown Dim for OpTypeImage: ")
1670 << operands[2];
1671
1672 auto depthInfo = spirv::symbolizeImageDepthInfo(operands[3]);
1673 if (!depthInfo)
1674 return emitError(unknownLoc, "unknown Depth for OpTypeImage: ")
1675 << operands[3];
1676
1677 auto arrayedInfo = spirv::symbolizeImageArrayedInfo(operands[4]);
1678 if (!arrayedInfo)
1679 return emitError(unknownLoc, "unknown Arrayed for OpTypeImage: ")
1680 << operands[4];
1681
1682 auto samplingInfo = spirv::symbolizeImageSamplingInfo(operands[5]);
1683 if (!samplingInfo)
1684 return emitError(unknownLoc, "unknown MS for OpTypeImage: ") << operands[5];
1685
1686 auto samplerUseInfo = spirv::symbolizeImageSamplerUseInfo(operands[6]);
1687 if (!samplerUseInfo)
1688 return emitError(unknownLoc, "unknown Sampled for OpTypeImage: ")
1689 << operands[6];
1690
1691 auto format = spirv::symbolizeImageFormat(operands[7]);
1692 if (!format)
1693 return emitError(unknownLoc, "unknown Format for OpTypeImage: ")
1694 << operands[7];
1695
1696 typeMap[operands[0]] = spirv::ImageType::get(
1697 elementTy, dim.value(), depthInfo.value(), arrayedInfo.value(),
1698 samplingInfo.value(), samplerUseInfo.value(), format.value());
1699 return success();
1700}
1701
1702LogicalResult
1704 if (operands.size() != 2)
1705 return emitError(unknownLoc, "OpTypeSampledImage must have two operands");
1706
1707 Type elementTy = getType(operands[1]);
1708 if (!elementTy)
1709 return emitError(unknownLoc,
1710 "OpTypeSampledImage references undefined <id>: ")
1711 << operands[1];
1712
1713 typeMap[operands[0]] = spirv::SampledImageType::get(elementTy);
1714 return success();
1715}
1716
1717LogicalResult
1719 if (operands.size() != 1)
1720 return emitError(unknownLoc, "OpTypeSampler must have no parameters");
1721
1722 typeMap[operands[0]] = spirv::SamplerType::get(context);
1723 return success();
1724}
1725
1726LogicalResult
1728 if (operands.size() != 1)
1729 return emitError(unknownLoc, "OpTypeNamedBarrier must have no parameters");
1730
1731 typeMap[operands[0]] = spirv::NamedBarrierType::get(context);
1732 return success();
1733}
1734
1735//===----------------------------------------------------------------------===//
1736// Constant
1737//===----------------------------------------------------------------------===//
1738
1740 bool isSpec) {
1741 StringRef opname = isSpec ? "OpSpecConstant" : "OpConstant";
1742
1743 if (operands.size() < 2) {
1744 return emitError(unknownLoc)
1745 << opname << " must have type <id> and result <id>";
1746 }
1747 if (operands.size() < 3) {
1748 return emitError(unknownLoc)
1749 << opname << " must have at least 1 more parameter";
1750 }
1751
1752 Type resultType = getType(operands[0]);
1753 if (!resultType) {
1754 return emitError(unknownLoc, "undefined result type from <id> ")
1755 << operands[0];
1756 }
1757
1758 auto checkOperandSizeForBitwidth = [&](unsigned bitwidth) -> LogicalResult {
1759 if (bitwidth == 64) {
1760 if (operands.size() == 4) {
1761 return success();
1762 }
1763 return emitError(unknownLoc)
1764 << opname << " should have 2 parameters for 64-bit values";
1765 }
1766 if (bitwidth <= 32) {
1767 if (operands.size() == 3) {
1768 return success();
1769 }
1770
1771 return emitError(unknownLoc)
1772 << opname
1773 << " should have 1 parameter for values with no more than 32 bits";
1774 }
1775 return emitError(unknownLoc, "unsupported OpConstant bitwidth: ")
1776 << bitwidth;
1777 };
1778
1779 auto resultID = operands[1];
1780
1781 if (auto intType = dyn_cast<IntegerType>(resultType)) {
1782 auto bitwidth = intType.getWidth();
1783 if (failed(checkOperandSizeForBitwidth(bitwidth))) {
1784 return failure();
1785 }
1786
1787 APInt value;
1788 if (bitwidth == 64) {
1789 // 64-bit integers are represented with two SPIR-V words. According to
1790 // SPIR-V spec: "When the type’s bit width is larger than one word, the
1791 // literal’s low-order words appear first."
1792 struct DoubleWord {
1793 uint32_t word1;
1794 uint32_t word2;
1795 } words = {operands[2], operands[3]};
1796 value = APInt(64, llvm::bit_cast<uint64_t>(words), /*isSigned=*/true);
1797 } else if (bitwidth <= 32) {
1798 value = APInt(bitwidth, operands[2], /*isSigned=*/true,
1799 /*implicitTrunc=*/true);
1800 }
1801
1802 auto attr = opBuilder.getIntegerAttr(intType, value);
1803
1804 if (isSpec) {
1805 createSpecConstant(unknownLoc, resultID, attr);
1806 } else {
1807 // For normal constants, we just record the attribute (and its type) for
1808 // later materialization at use sites.
1809 constantMap.try_emplace(resultID, attr, intType);
1810 }
1811
1812 return success();
1813 }
1814
1815 if (auto floatType = dyn_cast<FloatType>(resultType)) {
1816 auto bitwidth = floatType.getWidth();
1817 if (failed(checkOperandSizeForBitwidth(bitwidth))) {
1818 return failure();
1819 }
1820
1821 APFloat value(0.f);
1822 if (floatType.isF64()) {
1823 // Double values are represented with two SPIR-V words. According to
1824 // SPIR-V spec: "When the type’s bit width is larger than one word, the
1825 // literal’s low-order words appear first."
1826 struct DoubleWord {
1827 uint32_t word1;
1828 uint32_t word2;
1829 } words = {operands[2], operands[3]};
1830 value = APFloat(llvm::bit_cast<double>(words));
1831 } else if (floatType.isF32()) {
1832 value = APFloat(llvm::bit_cast<float>(operands[2]));
1833 } else if (floatType.isF16()) {
1834 APInt data(16, operands[2]);
1835 value = APFloat(APFloat::IEEEhalf(), data);
1836 } else if (floatType.isBF16()) {
1837 APInt data(16, operands[2]);
1838 value = APFloat(APFloat::BFloat(), data);
1839 } else if (floatType.isF8E4M3FN()) {
1840 APInt data(8, operands[2]);
1841 value = APFloat(APFloat::Float8E4M3FN(), data);
1842 } else if (floatType.isF8E5M2()) {
1843 APInt data(8, operands[2]);
1844 value = APFloat(APFloat::Float8E5M2(), data);
1845 }
1846
1847 auto attr = opBuilder.getFloatAttr(floatType, value);
1848 if (isSpec) {
1849 createSpecConstant(unknownLoc, resultID, attr);
1850 } else {
1851 // For normal constants, we just record the attribute (and its type) for
1852 // later materialization at use sites.
1853 constantMap.try_emplace(resultID, attr, floatType);
1854 }
1855
1856 return success();
1857 }
1858
1859 return emitError(unknownLoc, "OpConstant can only generate values of "
1860 "scalar integer or floating-point type");
1861}
1862
1864 bool isTrue, ArrayRef<uint32_t> operands, bool isSpec) {
1865 if (operands.size() != 2) {
1866 return emitError(unknownLoc, "Op")
1867 << (isSpec ? "Spec" : "") << "Constant"
1868 << (isTrue ? "True" : "False")
1869 << " must have type <id> and result <id>";
1870 }
1871
1872 auto attr = opBuilder.getBoolAttr(isTrue);
1873 auto resultID = operands[1];
1874 if (isSpec) {
1875 createSpecConstant(unknownLoc, resultID, attr);
1876 } else {
1877 // For normal constants, we just record the attribute (and its type) for
1878 // later materialization at use sites.
1879 constantMap.try_emplace(resultID, attr, opBuilder.getI1Type());
1880 }
1881
1882 return success();
1883}
1884
1885LogicalResult
1887 if (operands.size() < 2) {
1888 return emitError(unknownLoc,
1889 "OpConstantComposite must have type <id> and result <id>");
1890 }
1891 if (operands.size() < 3) {
1892 return emitError(unknownLoc,
1893 "OpConstantComposite must have at least 1 parameter");
1894 }
1895
1896 Type resultType = getType(operands[0]);
1897 if (!resultType) {
1898 return emitError(unknownLoc, "undefined result type from <id> ")
1899 << operands[0];
1900 }
1901
1903 elements.reserve(operands.size() - 2);
1904 for (unsigned i = 2, e = operands.size(); i < e; ++i) {
1905 auto elementInfo = getConstant(operands[i]);
1906 if (!elementInfo) {
1907 return emitError(unknownLoc, "OpConstantComposite component <id> ")
1908 << operands[i] << " must come from a normal constant";
1909 }
1910 elements.push_back(elementInfo->first);
1911 }
1912
1913 auto resultID = operands[1];
1914 if (auto tensorType = dyn_cast<TensorArmType>(resultType)) {
1915 SmallVector<Attribute> flattenedElems;
1916 for (Attribute element : elements) {
1917 if (auto denseElemAttr = dyn_cast<DenseElementsAttr>(element)) {
1918 for (auto value : denseElemAttr.getValues<Attribute>())
1919 flattenedElems.push_back(value);
1920 } else {
1921 flattenedElems.push_back(element);
1922 }
1923 }
1924 auto attr = DenseElementsAttr::get(tensorType, flattenedElems);
1925 constantMap.try_emplace(resultID, attr, tensorType);
1926 } else if (auto shapedType = dyn_cast<ShapedType>(resultType)) {
1927 auto attr = DenseElementsAttr::get(shapedType, elements);
1928 // For normal constants, we just record the attribute (and its type) for
1929 // later materialization at use sites.
1930 constantMap.try_emplace(resultID, attr, shapedType);
1931 } else if (isa<spirv::ArrayType, spirv::StructType>(resultType)) {
1932 auto attr = opBuilder.getArrayAttr(elements);
1933 constantMap.try_emplace(resultID, attr, resultType);
1934 } else {
1935 return emitError(unknownLoc, "unsupported OpConstantComposite type: ")
1936 << resultType;
1937 }
1938
1939 return success();
1940}
1941
1943 ArrayRef<uint32_t> operands) {
1944 if (operands.size() != 3) {
1945 return emitError(
1946 unknownLoc,
1947 "OpConstantCompositeReplicateEXT expects 3 operands but found ")
1948 << operands.size();
1949 }
1950
1951 Type resultType = getType(operands[0]);
1952 if (!resultType) {
1953 return emitError(unknownLoc, "undefined result type from <id> ")
1954 << operands[0];
1955 }
1956
1957 auto compositeType = dyn_cast<CompositeType>(resultType);
1958 if (!compositeType) {
1959 return emitError(unknownLoc,
1960 "result type from <id> is not a composite type")
1961 << operands[0];
1962 }
1963
1964 uint32_t resultID = operands[1];
1965 uint32_t constantID = operands[2];
1966
1967 std::optional<std::pair<Attribute, Type>> replicatedConstantCompositeInfo =
1969 if (replicatedConstantCompositeInfo.has_value()) {
1970 constantCompositeReplicateMap.try_emplace(
1971 resultID, replicatedConstantCompositeInfo.value().first, resultType);
1972 return success();
1973 }
1974
1975 std::optional<std::pair<Attribute, Type>> constantInfo =
1976 getConstant(constantID);
1977 if (constantInfo.has_value()) {
1978 constantCompositeReplicateMap.try_emplace(
1979 resultID, constantInfo.value().first, resultType);
1980 return success();
1981 }
1982
1983 return emitError(unknownLoc, "OpConstantCompositeReplicateEXT operand <id> ")
1984 << constantID
1985 << " must come from a normal constant or a "
1986 "OpConstantCompositeReplicateEXT";
1987}
1988
1989LogicalResult
1991 if (operands.size() < 2) {
1992 return emitError(
1993 unknownLoc,
1994 "OpSpecConstantComposite must have type <id> and result <id>");
1995 }
1996 if (operands.size() < 3) {
1997 return emitError(unknownLoc,
1998 "OpSpecConstantComposite must have at least 1 parameter");
1999 }
2000
2001 Type resultType = getType(operands[0]);
2002 if (!resultType) {
2003 return emitError(unknownLoc, "undefined result type from <id> ")
2004 << operands[0];
2005 }
2006
2007 auto resultID = operands[1];
2008 auto symName = opBuilder.getStringAttr(getSpecConstantSymbol(resultID));
2009
2011 elements.reserve(operands.size() - 2);
2012 for (unsigned i = 2, e = operands.size(); i < e; ++i) {
2013 auto elementInfo = getSpecConstant(operands[i]);
2014 elements.push_back(SymbolRefAttr::get(elementInfo));
2015 }
2016
2017 auto op = spirv::SpecConstantCompositeOp::create(
2018 opBuilder, unknownLoc, TypeAttr::get(resultType), symName,
2019 opBuilder.getArrayAttr(elements), /*sym_visibility=*/nullptr);
2020 specConstCompositeMap[resultID] = op;
2021
2022 return success();
2023}
2024
2026 ArrayRef<uint32_t> operands) {
2027 if (operands.size() != 3) {
2028 return emitError(unknownLoc, "OpSpecConstantCompositeReplicateEXT expects "
2029 "3 operands but found ")
2030 << operands.size();
2031 }
2032
2033 Type resultType = getType(operands[0]);
2034 if (!resultType) {
2035 return emitError(unknownLoc, "undefined result type from <id> ")
2036 << operands[0];
2037 }
2038
2039 auto compositeType = dyn_cast<CompositeType>(resultType);
2040 if (!compositeType) {
2041 return emitError(unknownLoc,
2042 "result type from <id> is not a composite type")
2043 << operands[0];
2044 }
2045
2046 uint32_t resultID = operands[1];
2047
2048 auto symName = opBuilder.getStringAttr(getSpecConstantSymbol(resultID));
2049 spirv::SpecConstantOp constituentSpecConstantOp =
2050 getSpecConstant(operands[2]);
2051 auto op = spirv::EXTSpecConstantCompositeReplicateOp::create(
2052 opBuilder, unknownLoc, TypeAttr::get(resultType), symName,
2053 SymbolRefAttr::get(constituentSpecConstantOp),
2054 /*sym_visibility=*/nullptr);
2055
2056 specConstCompositeReplicateMap[resultID] = op;
2057
2058 return success();
2059}
2060
2061LogicalResult
2063 if (operands.size() < 3)
2064 return emitError(unknownLoc, "OpConstantOperation must have type <id>, "
2065 "result <id>, and operand opcode");
2066
2067 uint32_t resultTypeID = operands[0];
2068
2069 if (!getType(resultTypeID))
2070 return emitError(unknownLoc, "undefined result type from <id> ")
2071 << resultTypeID;
2072
2073 uint32_t resultID = operands[1];
2074 spirv::Opcode enclosedOpcode = static_cast<spirv::Opcode>(operands[2]);
2075 auto emplaceResult = specConstOperationMap.try_emplace(
2076 resultID,
2078 enclosedOpcode, resultTypeID,
2079 SmallVector<uint32_t>{operands.begin() + 3, operands.end()}});
2080
2081 if (!emplaceResult.second)
2082 return emitError(unknownLoc, "value with <id>: ")
2083 << resultID << " is probably defined before.";
2084
2085 return success();
2086}
2087
2089 uint32_t resultID, spirv::Opcode enclosedOpcode, uint32_t resultTypeID,
2090 ArrayRef<uint32_t> enclosedOpOperands) {
2091
2092 Type resultType = getType(resultTypeID);
2093
2094 // Instructions wrapped by OpSpecConstantOp need an ID for their
2095 // Deserializer::processOp<op_name>(...) to emit the corresponding SPIR-V
2096 // dialect wrapped op. For that purpose, a new value map is created and "fake"
2097 // ID in that map is assigned to the result of the enclosed instruction. Note
2098 // that there is no need to update this fake ID since we only need to
2099 // reference the created Value for the enclosed op from the spv::YieldOp
2100 // created later in this method (both of which are the only values in their
2101 // region: the SpecConstantOperation's region). If we encounter another
2102 // SpecConstantOperation in the module, we simply re-use the fake ID since the
2103 // previous Value assigned to it isn't visible in the current scope anyway.
2104 DenseMap<uint32_t, Value> newValueMap;
2105 llvm::SaveAndRestore valueMapGuard(valueMap, newValueMap);
2106 constexpr uint32_t fakeID = static_cast<uint32_t>(-3);
2107
2108 SmallVector<uint32_t, 4> enclosedOpResultTypeAndOperands;
2109 enclosedOpResultTypeAndOperands.push_back(resultTypeID);
2110 enclosedOpResultTypeAndOperands.push_back(fakeID);
2111 enclosedOpResultTypeAndOperands.append(enclosedOpOperands.begin(),
2112 enclosedOpOperands.end());
2113
2114 // Process enclosed instruction before creating the enclosing
2115 // specConstantOperation (and its region). This way, references to constants,
2116 // global variables, and spec constants will be materialized outside the new
2117 // op's region. For more info, see Deserializer::getValue's implementation.
2118 if (failed(
2119 processInstruction(enclosedOpcode, enclosedOpResultTypeAndOperands)))
2120 return Value();
2121
2122 // Since the enclosed op is emitted in the current block, split it in a
2123 // separate new block.
2124 Block *enclosedBlock = curBlock->splitBlock(&curBlock->back());
2125
2126 auto loc = createFileLineColLoc(opBuilder);
2127 auto specConstOperationOp =
2128 spirv::SpecConstantOperationOp::create(opBuilder, loc, resultType);
2129
2130 Region &body = specConstOperationOp.getBody();
2131 // Move the new block into SpecConstantOperation's body.
2132 body.getBlocks().splice(body.end(), curBlock->getParent()->getBlocks(),
2133 Region::iterator(enclosedBlock));
2134 Block &block = body.back();
2135
2136 // RAII guard to reset the insertion point to the module's region after
2137 // deserializing the body of the specConstantOperation.
2138 OpBuilder::InsertionGuard moduleInsertionGuard(opBuilder);
2139 opBuilder.setInsertionPointToEnd(&block);
2140
2141 spirv::YieldOp::create(opBuilder, loc, block.front().getResult(0));
2142 return specConstOperationOp.getResult();
2143}
2144
2145LogicalResult
2147 if (operands.size() != 2) {
2148 return emitError(unknownLoc,
2149 "OpConstantNull must only have type <id> and result <id>");
2150 }
2151
2152 Type resultType = getType(operands[0]);
2153 if (!resultType) {
2154 return emitError(unknownLoc, "undefined result type from <id> ")
2155 << operands[0];
2156 }
2157
2158 auto resultID = operands[1];
2159 Attribute attr;
2160 if (resultType.isIntOrFloat() || isa<VectorType>(resultType)) {
2161 attr = opBuilder.getZeroAttr(resultType);
2162 } else if (auto tensorType = dyn_cast<TensorArmType>(resultType)) {
2163 if (auto element = opBuilder.getZeroAttr(tensorType.getElementType()))
2164 attr = DenseElementsAttr::get(tensorType, element);
2165 }
2166
2167 if (attr) {
2168 // For normal constants, we just record the attribute (and its type) for
2169 // later materialization at use sites.
2170 constantMap.try_emplace(resultID, attr, resultType);
2171 return success();
2172 }
2173
2174 return emitError(unknownLoc, "unsupported OpConstantNull type: ")
2175 << resultType;
2176}
2177
2178LogicalResult
2180 if (operands.size() < 3) {
2181 return emitError(unknownLoc)
2182 << "OpGraphConstantARM must have at least 2 operands";
2183 }
2184
2185 Type resultType = getType(operands[0]);
2186 if (!resultType) {
2187 return emitError(unknownLoc, "undefined result type from <id> ")
2188 << operands[0];
2189 }
2190
2191 uint32_t resultID = operands[1];
2192
2193 if (!dyn_cast<spirv::TensorArmType>(resultType)) {
2194 return emitError(unknownLoc, "result must be of type OpTypeTensorARM");
2195 }
2196
2197 APInt graph_constant_id = APInt(32, operands[2], /*isSigned=*/true);
2198 Type i32Ty = opBuilder.getIntegerType(32);
2199 IntegerAttr attr = opBuilder.getIntegerAttr(i32Ty, graph_constant_id);
2200 graphConstantMap.try_emplace(
2201 resultID, GraphConstantARMOpMaterializationInfo{resultType, attr});
2202
2203 return success();
2204}
2205
2206//===----------------------------------------------------------------------===//
2207// Control flow
2208//===----------------------------------------------------------------------===//
2209
2211 if (auto *block = getBlock(id)) {
2212 LLVM_DEBUG(logger.startLine() << "[block] got exiting block for id = " << id
2213 << " @ " << block << "\n");
2214 return block;
2215 }
2216
2217 // We don't know where this block will be placed finally (in a
2218 // spirv.mlir.selection or spirv.mlir.loop or function). Create it into the
2219 // function for now and sort out the proper place later.
2220 auto *block = curFunction->addBlock();
2221 LLVM_DEBUG(logger.startLine() << "[block] created block for id = " << id
2222 << " @ " << block << "\n");
2223 return blockMap[id] = block;
2224}
2225
2227 if (!curBlock) {
2228 return emitError(unknownLoc, "OpBranch must appear inside a block");
2229 }
2230
2231 if (operands.size() != 1) {
2232 return emitError(unknownLoc, "OpBranch must take exactly one target label");
2233 }
2234
2235 auto *target = getOrCreateBlock(operands[0]);
2236 auto loc = createFileLineColLoc(opBuilder);
2237 // The preceding instruction for the OpBranch instruction could be an
2238 // OpLoopMerge or an OpSelectionMerge instruction, in this case they will have
2239 // the same OpLine information.
2240 spirv::BranchOp::create(opBuilder, loc, target);
2241
2243 return success();
2244}
2245
2246LogicalResult
2248 if (!curBlock) {
2249 return emitError(unknownLoc,
2250 "OpBranchConditional must appear inside a block");
2251 }
2252
2253 if (operands.size() != 3 && operands.size() != 5) {
2254 return emitError(unknownLoc,
2255 "OpBranchConditional must have condition, true label, "
2256 "false label, and optionally two branch weights");
2257 }
2258
2259 auto condition = getValue(operands[0]);
2260 auto *trueBlock = getOrCreateBlock(operands[1]);
2261 auto *falseBlock = getOrCreateBlock(operands[2]);
2262
2263 std::optional<std::pair<uint32_t, uint32_t>> weights;
2264 if (operands.size() == 5) {
2265 weights = std::make_pair(operands[3], operands[4]);
2266 }
2267 // The preceding instruction for the OpBranchConditional instruction could be
2268 // an OpSelectionMerge instruction, in this case they will have the same
2269 // OpLine information.
2270 auto loc = createFileLineColLoc(opBuilder);
2271 spirv::BranchConditionalOp::create(
2272 opBuilder, loc, condition, trueBlock,
2273 /*trueArguments=*/ArrayRef<Value>(), falseBlock,
2274 /*falseArguments=*/ArrayRef<Value>(), weights);
2275
2277 return success();
2278}
2279
2281 if (!curFunction) {
2282 return emitError(unknownLoc, "OpLabel must appear inside a function");
2283 }
2284
2285 if (operands.size() != 1) {
2286 return emitError(unknownLoc, "OpLabel should only have result <id>");
2287 }
2288
2289 auto labelID = operands[0];
2290 // We may have forward declared this block.
2291 auto *block = getOrCreateBlock(labelID);
2292 LLVM_DEBUG(logger.startLine()
2293 << "[block] populating block " << block << "\n");
2294 // If we have seen this block, make sure it was just a forward declaration.
2295 assert(block->empty() && "re-deserialize the same block!");
2296
2297 opBuilder.setInsertionPointToStart(block);
2298 blockMap[labelID] = curBlock = block;
2299
2300 return success();
2301}
2302
2303LogicalResult spirv::Deserializer::createGraphBlock(uint32_t graphID) {
2304 if (!curGraph) {
2305 return emitError(unknownLoc, "a graph block must appear inside a graph");
2306 }
2307
2308 // We may have forward declared this block.
2309 Block *block = getOrCreateBlock(graphID);
2310 LLVM_DEBUG(logger.startLine()
2311 << "[block] populating block " << block << "\n");
2312 // If we have seen this block, make sure it was just a forward declaration.
2313 assert(block->empty() && "re-deserialize the same block!");
2314
2315 opBuilder.setInsertionPointToStart(block);
2316 blockMap[graphID] = curBlock = block;
2317
2318 return success();
2319}
2320
2321LogicalResult
2323 if (!curBlock) {
2324 return emitError(unknownLoc, "OpSelectionMerge must appear in a block");
2325 }
2326
2327 if (operands.size() < 2) {
2328 return emitError(
2329 unknownLoc,
2330 "OpSelectionMerge must specify merge target and selection control");
2331 }
2332
2333 auto *mergeBlock = getOrCreateBlock(operands[0]);
2334 auto loc = createFileLineColLoc(opBuilder);
2335 auto selectionControl = operands[1];
2336
2337 if (!blockMergeInfo.try_emplace(curBlock, loc, selectionControl, mergeBlock)
2338 .second) {
2339 return emitError(
2340 unknownLoc,
2341 "a block cannot have more than one OpSelectionMerge instruction");
2342 }
2343
2344 return success();
2345}
2346
2347LogicalResult
2349 if (!curBlock) {
2350 return emitError(unknownLoc, "OpLoopMerge must appear in a block");
2351 }
2352
2353 if (operands.size() < 3) {
2354 return emitError(unknownLoc, "OpLoopMerge must specify merge target, "
2355 "continue target and loop control");
2356 }
2357
2358 auto *mergeBlock = getOrCreateBlock(operands[0]);
2359 auto *continueBlock = getOrCreateBlock(operands[1]);
2360 auto loc = createFileLineColLoc(opBuilder);
2361 uint32_t loopControl = operands[2];
2362
2363 if (!blockMergeInfo
2364 .try_emplace(curBlock, loc, loopControl, mergeBlock, continueBlock)
2365 .second) {
2366 return emitError(
2367 unknownLoc,
2368 "a block cannot have more than one OpLoopMerge instruction");
2369 }
2370
2371 return success();
2372}
2373
2375 if (!curBlock) {
2376 return emitError(unknownLoc, "OpPhi must appear in a block");
2377 }
2378
2379 if (operands.size() < 4) {
2380 return emitError(unknownLoc, "OpPhi must specify result type, result <id>, "
2381 "and variable-parent pairs");
2382 }
2383
2384 // Create a block argument for this OpPhi instruction.
2385 Type blockArgType = getType(operands[0]);
2386 BlockArgument blockArg = curBlock->addArgument(blockArgType, unknownLoc);
2387 valueMap[operands[1]] = blockArg;
2388 LLVM_DEBUG(logger.startLine()
2389 << "[phi] created block argument " << blockArg
2390 << " id = " << operands[1] << " of type " << blockArgType << "\n");
2391
2392 // For each (value, predecessor) pair, insert the value to the predecessor's
2393 // blockPhiInfo entry so later we can fix the block argument there.
2394 for (unsigned i = 2, e = operands.size(); i < e; i += 2) {
2395 uint32_t value = operands[i];
2396 Block *predecessor = getOrCreateBlock(operands[i + 1]);
2397 std::pair<Block *, Block *> predecessorTargetPair{predecessor, curBlock};
2398 blockPhiInfo[predecessorTargetPair].push_back(value);
2399 LLVM_DEBUG(logger.startLine() << "[phi] predecessor @ " << predecessor
2400 << " with arg id = " << value << "\n");
2401 }
2402
2403 return success();
2404}
2405
2407 if (!curBlock)
2408 return emitError(unknownLoc, "OpSwitch must appear in a block");
2409
2410 if (operands.size() < 2)
2411 return emitError(unknownLoc, "OpSwitch must at least specify selector and "
2412 "a default target");
2413
2414 if (operands.size() % 2)
2415 return emitError(unknownLoc,
2416 "OpSwitch must at have an even number of operands: "
2417 "selector, default target and any number of literal and "
2418 "label <id> pairs");
2419
2420 Value selector = getValue(operands[0]);
2421 Block *defaultBlock = getOrCreateBlock(operands[1]);
2422 Location loc = createFileLineColLoc(opBuilder);
2423
2424 SmallVector<int32_t> literals;
2425 SmallVector<Block *> blocks;
2426 for (unsigned i = 2, e = operands.size(); i < e; i += 2) {
2427 literals.push_back(operands[i]);
2428 blocks.push_back(getOrCreateBlock(operands[i + 1]));
2429 }
2430
2431 SmallVector<ValueRange> targetOperands(blocks.size(), {});
2432 spirv::SwitchOp::create(opBuilder, loc, selector, defaultBlock,
2433 ArrayRef<Value>(), literals, blocks, targetOperands);
2434
2435 return success();
2436}
2437
2438namespace {
2439/// A class for putting all blocks in a structured selection/loop in a
2440/// spirv.mlir.selection/spirv.mlir.loop op.
2441class ControlFlowStructurizer {
2442public:
2443#ifndef NDEBUG
2444 ControlFlowStructurizer(Location loc, uint32_t control,
2445 spirv::BlockMergeInfoMap &mergeInfo, Block *header,
2446 Block *merge, Block *cont,
2447 llvm::ScopedPrinter &logger)
2448 : location(loc), control(control), blockMergeInfo(mergeInfo),
2449 headerBlock(header), mergeBlock(merge), continueBlock(cont),
2450 logger(logger) {}
2451#else
2452 ControlFlowStructurizer(Location loc, uint32_t control,
2453 spirv::BlockMergeInfoMap &mergeInfo, Block *header,
2454 Block *merge, Block *cont)
2455 : location(loc), control(control), blockMergeInfo(mergeInfo),
2456 headerBlock(header), mergeBlock(merge), continueBlock(cont) {}
2457#endif
2458
2459 /// Structurizes the loop at the given `headerBlock`.
2460 ///
2461 /// This method will create an spirv.mlir.loop op in the `mergeBlock` and move
2462 /// all blocks in the structured loop into the spirv.mlir.loop's region. All
2463 /// branches to the `headerBlock` will be redirected to the `mergeBlock`. This
2464 /// method will also update `mergeInfo` by remapping all blocks inside to the
2465 /// newly cloned ones inside structured control flow op's regions.
2466 LogicalResult structurize();
2467
2468private:
2469 /// Creates a new spirv.mlir.selection op at the beginning of the
2470 /// `mergeBlock`.
2471 spirv::SelectionOp createSelectionOp(uint32_t selectionControl);
2472
2473 /// Creates a new spirv.mlir.loop op at the beginning of the `mergeBlock`.
2474 spirv::LoopOp createLoopOp(uint32_t loopControl);
2475
2476 /// Collects all blocks reachable from `headerBlock` except `mergeBlock`.
2477 void collectBlocksInConstruct();
2478
2479 Location location;
2480 uint32_t control;
2481
2482 spirv::BlockMergeInfoMap &blockMergeInfo;
2483
2484 Block *headerBlock;
2485 Block *mergeBlock;
2486 Block *continueBlock; // nullptr for spirv.mlir.selection
2487
2488 SetVector<Block *> constructBlocks;
2489
2490#ifndef NDEBUG
2491 /// A logger used to emit information during the deserialzation process.
2492 llvm::ScopedPrinter &logger;
2493#endif
2494};
2495} // namespace
2496
2497spirv::SelectionOp
2498ControlFlowStructurizer::createSelectionOp(uint32_t selectionControl) {
2499 // Create a builder and set the insertion point to the beginning of the
2500 // merge block so that the newly created SelectionOp will be inserted there.
2501 OpBuilder builder(&mergeBlock->front());
2502
2503 auto control = static_cast<spirv::SelectionControl>(selectionControl);
2504 auto selectionOp = spirv::SelectionOp::create(builder, location, control);
2505 selectionOp.addMergeBlock(builder);
2506
2507 return selectionOp;
2508}
2509
2510spirv::LoopOp ControlFlowStructurizer::createLoopOp(uint32_t loopControl) {
2511 // Create a builder and set the insertion point to the beginning of the
2512 // merge block so that the newly created LoopOp will be inserted there.
2513 OpBuilder builder(&mergeBlock->front());
2514
2515 auto control = static_cast<spirv::LoopControl>(loopControl);
2516 auto loopOp = spirv::LoopOp::create(builder, location, control);
2517 loopOp.addEntryAndMergeBlock(builder);
2518
2519 return loopOp;
2520}
2521
2522void ControlFlowStructurizer::collectBlocksInConstruct() {
2523 assert(constructBlocks.empty() && "expected empty constructBlocks");
2524
2525 // Put the header block in the work list first.
2526 constructBlocks.insert(headerBlock);
2527
2528 // For each item in the work list, add its successors excluding the merge
2529 // block.
2530 for (unsigned i = 0; i < constructBlocks.size(); ++i) {
2531 for (auto *successor : constructBlocks[i]->getSuccessors())
2532 if (successor != mergeBlock)
2533 constructBlocks.insert(successor);
2534 }
2535}
2536
2537LogicalResult ControlFlowStructurizer::structurize() {
2538 Operation *op = nullptr;
2539 bool isLoop = continueBlock != nullptr;
2540 if (isLoop) {
2541 if (auto loopOp = createLoopOp(control))
2542 op = loopOp.getOperation();
2543 } else {
2544 if (auto selectionOp = createSelectionOp(control))
2545 op = selectionOp.getOperation();
2546 }
2547 if (!op)
2548 return failure();
2549 Region &body = op->getRegion(0);
2550
2551 IRMapping mapper;
2552 // All references to the old merge block should be directed to the
2553 // selection/loop merge block in the SelectionOp/LoopOp's region.
2554 mapper.map(mergeBlock, &body.back());
2555
2556 collectBlocksInConstruct();
2557
2558 // We've identified all blocks belonging to the selection/loop's region. Now
2559 // need to "move" them into the selection/loop. Instead of really moving the
2560 // blocks, in the following we copy them and remap all values and branches.
2561 // This is because:
2562 // * Inserting a block into a region requires the block not in any region
2563 // before. But selections/loops can nest so we can create selection/loop ops
2564 // in a nested manner, which means some blocks may already be in a
2565 // selection/loop region when to be moved again.
2566 // * It's much trickier to fix up the branches into and out of the loop's
2567 // region: we need to treat not-moved blocks and moved blocks differently:
2568 // Not-moved blocks jumping to the loop header block need to jump to the
2569 // merge point containing the new loop op but not the loop continue block's
2570 // back edge. Moved blocks jumping out of the loop need to jump to the
2571 // merge block inside the loop region but not other not-moved blocks.
2572 // We cannot use replaceAllUsesWith clearly and it's harder to follow the
2573 // logic.
2574
2575 // Create a corresponding block in the SelectionOp/LoopOp's region for each
2576 // block in this loop construct.
2577 OpBuilder builder(body);
2578 for (auto *block : constructBlocks) {
2579 // Create a block and insert it before the selection/loop merge block in the
2580 // SelectionOp/LoopOp's region.
2581 auto *newBlock = builder.createBlock(&body.back());
2582 mapper.map(block, newBlock);
2583 LLVM_DEBUG(logger.startLine() << "[cf] cloned block " << newBlock
2584 << " from block " << block << "\n");
2585 if (!isFnEntryBlock(block)) {
2586 for (BlockArgument blockArg : block->getArguments()) {
2587 auto newArg =
2588 newBlock->addArgument(blockArg.getType(), blockArg.getLoc());
2589 mapper.map(blockArg, newArg);
2590 LLVM_DEBUG(logger.startLine() << "[cf] remapped block argument "
2591 << blockArg << " to " << newArg << "\n");
2592 }
2593 } else {
2594 LLVM_DEBUG(logger.startLine()
2595 << "[cf] block " << block << " is a function entry block\n");
2596 }
2597
2598 for (auto &op : *block)
2599 newBlock->push_back(op.clone(mapper));
2600 }
2601
2602 // Go through all ops and remap the operands.
2603 auto remapOperands = [&](Operation *op) {
2604 for (auto &operand : op->getOpOperands())
2605 if (Value mappedOp = mapper.lookupOrNull(operand.get()))
2606 operand.set(mappedOp);
2607 for (auto &succOp : op->getBlockOperands())
2608 if (Block *mappedOp = mapper.lookupOrNull(succOp.get()))
2609 succOp.set(mappedOp);
2610 };
2611 for (auto &block : body)
2612 block.walk(remapOperands);
2613
2614 // We have created the SelectionOp/LoopOp and "moved" all blocks belonging to
2615 // the selection/loop construct into its region. Next we need to fix the
2616 // connections between this new SelectionOp/LoopOp with existing blocks.
2617
2618 // All existing incoming branches should go to the merge block, where the
2619 // SelectionOp/LoopOp resides right now.
2620 headerBlock->replaceAllUsesWith(mergeBlock);
2621
2622 LLVM_DEBUG({
2623 logger.startLine() << "[cf] after cloning and fixing references:\n";
2624 headerBlock->getParentOp()->print(logger.getOStream());
2625 logger.startLine() << "\n";
2626 });
2627
2628 if (isLoop) {
2629 if (!mergeBlock->args_empty()) {
2630 return mergeBlock->getParentOp()->emitError(
2631 "OpPhi in loop merge block unsupported");
2632 }
2633
2634 // The loop header block may have block arguments. Since now we place the
2635 // loop op inside the old merge block, we need to make sure the old merge
2636 // block has the same block argument list.
2637 for (BlockArgument blockArg : headerBlock->getArguments())
2638 mergeBlock->addArgument(blockArg.getType(), blockArg.getLoc());
2639
2640 // If the loop header block has block arguments, make sure the spirv.Branch
2641 // op matches.
2642 SmallVector<Value, 4> blockArgs;
2643 if (!headerBlock->args_empty())
2644 blockArgs = {mergeBlock->args_begin(), mergeBlock->args_end()};
2645
2646 // The loop entry block should have a unconditional branch jumping to the
2647 // loop header block.
2648 builder.setInsertionPointToEnd(&body.front());
2649 spirv::BranchOp::create(builder, location, mapper.lookupOrNull(headerBlock),
2650 ArrayRef<Value>(blockArgs));
2651 }
2652
2653 // Values defined inside the selection region that need to be yielded outside
2654 // the region.
2655 SmallVector<Value> valuesToYield;
2656 // Outside uses of values that were sunk into the selection region. Those uses
2657 // will be replaced with values returned by the SelectionOp.
2658 SmallVector<Value> outsideUses;
2659
2660 // Move block arguments of the original block (`mergeBlock`) into the merge
2661 // block inside the selection (`body.back()`). Values produced by block
2662 // arguments will be yielded by the selection region. We do not update uses or
2663 // erase original block arguments yet. It will be done later in the code.
2664 //
2665 // Code below is not executed for loops as it would interfere with the logic
2666 // above. Currently block arguments in the merge block are not supported, but
2667 // instead, the code above copies those arguments from the header block into
2668 // the merge block. As such, running the code would yield those copied
2669 // arguments that is most likely not a desired behaviour. This may need to be
2670 // revisited in the future.
2671 if (!isLoop)
2672 for (BlockArgument blockArg : mergeBlock->getArguments()) {
2673 // Create new block arguments in the last block ("merge block") of the
2674 // selection region. We create one argument for each argument in
2675 // `mergeBlock`. This new value will need to be yielded, and the original
2676 // value replaced, so add them to appropriate vectors.
2677 body.back().addArgument(blockArg.getType(), blockArg.getLoc());
2678 valuesToYield.push_back(body.back().getArguments().back());
2679 outsideUses.push_back(blockArg);
2680 }
2681
2682 // All the blocks cloned into the SelectionOp/LoopOp's region can now be
2683 // cleaned up.
2684 LLVM_DEBUG(logger.startLine() << "[cf] cleaning up blocks after clone\n");
2685 // First we need to drop all operands' references inside all blocks. This is
2686 // needed because we can have blocks referencing SSA values from one another.
2687 for (auto *block : constructBlocks)
2688 block->dropAllReferences();
2689
2690 // All internal uses should be removed from original blocks by now, so
2691 // whatever is left is an outside use and will need to be yielded from
2692 // the newly created selection / loop region.
2693 for (Block *block : constructBlocks) {
2694 for (Operation &op : *block) {
2695 if (!op.use_empty())
2696 for (Value result : op.getResults()) {
2697 valuesToYield.push_back(mapper.lookupOrNull(result));
2698 outsideUses.push_back(result);
2699 }
2700 }
2701 for (BlockArgument &arg : block->getArguments()) {
2702 if (!arg.use_empty()) {
2703 valuesToYield.push_back(mapper.lookupOrNull(arg));
2704 outsideUses.push_back(arg);
2705 }
2706 }
2707 }
2708
2709 assert(valuesToYield.size() == outsideUses.size());
2710
2711 // If we need to yield any values from the selection / loop region we will
2712 // take care of it here.
2713 if (!valuesToYield.empty()) {
2714 LLVM_DEBUG(logger.startLine()
2715 << "[cf] yielding values from the selection / loop region\n");
2716
2717 // Update `mlir.merge` with values to be yield.
2718 auto mergeOps = body.back().getOps<spirv::MergeOp>();
2719 Operation *merge = llvm::getSingleElement(mergeOps);
2720 assert(merge);
2721 merge->setOperands(valuesToYield);
2722
2723 // MLIR does not allow changing the number of results of an operation, so
2724 // we create a new SelectionOp / LoopOp with required list of results and
2725 // move the region from the initial SelectionOp / LoopOp. The initial
2726 // operation is then removed. Since we move the region to the new op all
2727 // links between blocks and remapping we have previously done should be
2728 // preserved.
2729 builder.setInsertionPoint(&mergeBlock->front());
2730
2731 Operation *newOp = nullptr;
2732
2733 if (isLoop)
2734 newOp = spirv::LoopOp::create(builder, location,
2735 TypeRange(ValueRange(outsideUses)),
2736 static_cast<spirv::LoopControl>(control));
2737 else
2738 newOp = spirv::SelectionOp::create(
2739 builder, location, TypeRange(ValueRange(outsideUses)),
2740 static_cast<spirv::SelectionControl>(control));
2741
2742 newOp->getRegion(0).takeBody(body);
2743
2744 // Remove initial op and swap the pointer to the newly created one.
2745 op->erase();
2746 op = newOp;
2747
2748 // Update all outside uses to use results of the SelectionOp / LoopOp and
2749 // remove block arguments from the original merge block.
2750 for (unsigned i = 0, e = outsideUses.size(); i != e; ++i)
2751 outsideUses[i].replaceAllUsesWith(op->getResult(i));
2752
2753 // We do not support block arguments in loop merge block. Also running this
2754 // function with loop would break some of the loop specific code above
2755 // dealing with block arguments.
2756 if (!isLoop)
2757 mergeBlock->eraseArguments(0, mergeBlock->getNumArguments());
2758 }
2759
2760 // Check that whether some op in the to-be-erased blocks still has uses. Those
2761 // uses come from blocks that won't be sinked into the SelectionOp/LoopOp's
2762 // region. We cannot handle such cases given that once a value is sinked into
2763 // the SelectionOp/LoopOp's region, there is no escape for it.
2764 for (auto *block : constructBlocks) {
2765 if (!block->use_empty())
2766 return emitError(block->getParent()->getLoc(),
2767 "failed control flow structurization: "
2768 "block has uses outside of the "
2769 "enclosing selection/loop construct");
2770 for (Operation &op : *block)
2771 if (!op.use_empty())
2772 return op.emitOpError("failed control flow structurization: value has "
2773 "uses outside of the "
2774 "enclosing selection/loop construct");
2775 for (BlockArgument &arg : block->getArguments())
2776 if (!arg.use_empty())
2777 return emitError(arg.getLoc(), "failed control flow structurization: "
2778 "block argument has uses outside of the "
2779 "enclosing selection/loop construct");
2780 }
2781
2782 // Then erase all old blocks.
2783 for (auto *block : constructBlocks) {
2784 // We've cloned all blocks belonging to this construct into the structured
2785 // control flow op's region. Among these blocks, some may compose another
2786 // selection/loop. If so, they will be recorded within blockMergeInfo.
2787 // We need to update the pointers there to the newly remapped ones so we can
2788 // continue structurizing them later.
2789 //
2790 // We need to walk each block as constructBlocks do not include blocks
2791 // internal to ops already structured within those blocks. It is not
2792 // fully clear to me why the mergeInfo of blocks (yet to be structured)
2793 // inside already structured selections/loops get invalidated and needs
2794 // updating, however the following example code can cause a crash (depending
2795 // on the structuring order), when the most inner selection is being
2796 // structured after the outer selection and loop have been already
2797 // structured:
2798 //
2799 // spirv.mlir.for {
2800 // // ...
2801 // spirv.mlir.selection {
2802 // // ..
2803 // // A selection region that hasn't been yet structured!
2804 // // ..
2805 // }
2806 // // ...
2807 // }
2808 //
2809 // If the loop gets structured after the outer selection, but before the
2810 // inner selection. Moving the already structured selection inside the loop
2811 // will invalidate the mergeInfo of the region that is not yet structured.
2812 // Just going over constructBlocks will not check and updated header blocks
2813 // inside the already structured selection region. Walking block fixes that.
2814 //
2815 // TODO: If structuring was done in a fixed order starting with inner
2816 // most constructs this most likely not be an issue and the whole code
2817 // section could be removed. However, with the current non-deterministic
2818 // order this is not possible.
2819 //
2820 // TODO: The asserts in the following assumes input SPIR-V blob forms
2821 // correctly nested selection/loop constructs. We should relax this and
2822 // support error cases better.
2823 auto updateMergeInfo = [&](Block *block) -> WalkResult {
2824 auto it = blockMergeInfo.find(block);
2825 if (it != blockMergeInfo.end()) {
2826 // Use the original location for nested selection/loop ops.
2827 Location loc = it->second.loc;
2828
2829 Block *newHeader = mapper.lookupOrNull(block);
2830 if (!newHeader)
2831 return emitError(loc, "failed control flow structurization: nested "
2832 "loop header block should be remapped!");
2833
2834 Block *newContinue = it->second.continueBlock;
2835 if (newContinue) {
2836 newContinue = mapper.lookupOrNull(newContinue);
2837 if (!newContinue)
2838 return emitError(loc, "failed control flow structurization: nested "
2839 "loop continue block should be remapped!");
2840 }
2841
2842 Block *newMerge = it->second.mergeBlock;
2843 if (Block *mappedTo = mapper.lookupOrNull(newMerge))
2844 newMerge = mappedTo;
2845
2846 // The iterator should be erased before adding a new entry into
2847 // blockMergeInfo to avoid iterator invalidation.
2848 blockMergeInfo.erase(it);
2849 blockMergeInfo.try_emplace(newHeader, loc, it->second.control, newMerge,
2850 newContinue);
2851 }
2852
2853 return WalkResult::advance();
2854 };
2855
2856 if (block->walk(updateMergeInfo).wasInterrupted())
2857 return failure();
2858
2859 // The structured selection/loop's entry block does not have arguments.
2860 // If the function's header block is also part of the structured control
2861 // flow, we cannot just simply erase it because it may contain arguments
2862 // matching the function signature and used by the cloned blocks.
2863 if (isFnEntryBlock(block)) {
2864 LLVM_DEBUG(logger.startLine() << "[cf] changing entry block " << block
2865 << " to only contain a spirv.Branch op\n");
2866 // Still keep the function entry block for the potential block arguments,
2867 // but replace all ops inside with a branch to the merge block.
2868 block->clear();
2869 builder.setInsertionPointToEnd(block);
2870 spirv::BranchOp::create(builder, location, mergeBlock);
2871 } else {
2872 LLVM_DEBUG(logger.startLine() << "[cf] erasing block " << block << "\n");
2873 block->erase();
2874 }
2875 }
2876
2877 LLVM_DEBUG(logger.startLine()
2878 << "[cf] after structurizing construct with header block "
2879 << headerBlock << ":\n"
2880 << *op << "\n");
2881
2882 return success();
2883}
2884
2886 LLVM_DEBUG({
2887 logger.startLine()
2888 << "//----- [phi] start wiring up block arguments -----//\n";
2889 logger.indent();
2890 });
2891
2892 OpBuilder::InsertionGuard guard(opBuilder);
2893
2894 for (const auto &info : blockPhiInfo) {
2895 Block *block = info.first.first;
2896 Block *target = info.first.second;
2897 const BlockPhiInfo &phiInfo = info.second;
2898 LLVM_DEBUG({
2899 logger.startLine() << "[phi] block " << block << "\n";
2900 logger.startLine() << "[phi] before creating block argument:\n";
2901 block->getParentOp()->print(logger.getOStream());
2902 logger.startLine() << "\n";
2903 });
2904
2905 // Set insertion point to before this block's terminator early because we
2906 // may materialize ops via getValue() call.
2907 auto *op = block->getTerminator();
2908 opBuilder.setInsertionPoint(op);
2909
2910 SmallVector<Value, 4> blockArgs;
2911 blockArgs.reserve(phiInfo.size());
2912 for (uint32_t valueId : phiInfo) {
2913 if (Value value = getValue(valueId)) {
2914 blockArgs.push_back(value);
2915 LLVM_DEBUG(logger.startLine() << "[phi] block argument " << value
2916 << " id = " << valueId << "\n");
2917 } else {
2918 return emitError(unknownLoc, "OpPhi references undefined value!");
2919 }
2920 }
2921
2922 if (auto branchOp = dyn_cast<spirv::BranchOp>(op)) {
2923 // Replace the previous branch op with a new one with block arguments.
2924 spirv::BranchOp::create(opBuilder, branchOp.getLoc(),
2925 branchOp.getTarget(), blockArgs);
2926 branchOp.erase();
2927 } else if (auto branchCondOp = dyn_cast<spirv::BranchConditionalOp>(op)) {
2928 assert((branchCondOp.getTrueBlock() == target ||
2929 branchCondOp.getFalseBlock() == target) &&
2930 "expected target to be either the true or false target");
2931 if (target == branchCondOp.getTrueTarget())
2932 spirv::BranchConditionalOp::create(
2933 opBuilder, branchCondOp.getLoc(), branchCondOp.getCondition(),
2934 blockArgs, branchCondOp.getFalseBlockArguments(),
2935 branchCondOp.getBranchWeightsAttr(), branchCondOp.getTrueTarget(),
2936 branchCondOp.getFalseTarget());
2937 else
2938 spirv::BranchConditionalOp::create(
2939 opBuilder, branchCondOp.getLoc(), branchCondOp.getCondition(),
2940 branchCondOp.getTrueBlockArguments(), blockArgs,
2941 branchCondOp.getBranchWeightsAttr(), branchCondOp.getTrueBlock(),
2942 branchCondOp.getFalseBlock());
2943
2944 branchCondOp.erase();
2945 } else if (auto switchOp = dyn_cast<spirv::SwitchOp>(op)) {
2946 if (target == switchOp.getDefaultTarget()) {
2947 SmallVector<ValueRange> targetOperands(switchOp.getTargetOperands());
2948 DenseIntElementsAttr literals =
2949 switchOp.getLiterals().value_or(DenseIntElementsAttr());
2950 spirv::SwitchOp::create(
2951 opBuilder, switchOp.getLoc(), switchOp.getSelector(),
2952 switchOp.getDefaultTarget(), blockArgs, literals,
2953 switchOp.getTargets(), targetOperands);
2954 switchOp.erase();
2955 } else {
2956 SuccessorRange targets = switchOp.getTargets();
2957 auto it = llvm::find(targets, target);
2958 assert(it != targets.end());
2959 size_t index = std::distance(targets.begin(), it);
2960 switchOp.getTargetOperandsMutable(index).assign(blockArgs);
2961 }
2962 } else {
2963 return emitError(unknownLoc, "unimplemented terminator for Phi creation");
2964 }
2965
2966 LLVM_DEBUG({
2967 logger.startLine() << "[phi] after creating block argument:\n";
2968 block->getParentOp()->print(logger.getOStream());
2969 logger.startLine() << "\n";
2970 });
2971 }
2972 blockPhiInfo.clear();
2973
2974 LLVM_DEBUG({
2975 logger.unindent();
2976 logger.startLine()
2977 << "//--- [phi] completed wiring up block arguments ---//\n";
2978 });
2979 return success();
2980}
2981
2983 // Create a copy, so we can modify keys in the original.
2984 BlockMergeInfoMap blockMergeInfoCopy = blockMergeInfo;
2985 for (auto [block, mergeInfo] : blockMergeInfoCopy) {
2986 // Skip processing loop regions. For loop regions continueBlock is non-null.
2987 if (mergeInfo.continueBlock)
2988 continue;
2989
2990 if (!block->mightHaveTerminator())
2991 continue;
2992
2993 Operation *terminator = block->getTerminator();
2994 assert(terminator);
2995
2996 if (!isa<spirv::BranchConditionalOp, spirv::SwitchOp>(terminator))
2997 continue;
2998
2999 // Check if the current header block is a merge block of another construct.
3000 bool splitHeaderMergeBlock = false;
3001 for (const auto &[_, mergeInfo] : blockMergeInfo) {
3002 if (mergeInfo.mergeBlock == block)
3003 splitHeaderMergeBlock = true;
3004 }
3005
3006 // Do not split a block that only contains a conditional branch / switch,
3007 // unless it is also a merge block of another construct - in that case we
3008 // want to split the block. We do not want two constructs to share header /
3009 // merge block.
3010 if (!llvm::hasSingleElement(*block) || splitHeaderMergeBlock) {
3011 Block *newBlock = block->splitBlock(terminator);
3012 OpBuilder builder(block, block->end());
3013 spirv::BranchOp::create(builder, block->getParent()->getLoc(), newBlock);
3014
3015 // After splitting we need to update the map to use the new block as a
3016 // header.
3017 blockMergeInfo.erase(block);
3018 blockMergeInfo.try_emplace(newBlock, mergeInfo);
3019 }
3020 }
3021
3022 return success();
3023}
3024
3026 if (!options.enableControlFlowStructurization) {
3027 LLVM_DEBUG(
3028 {
3029 logger.startLine()
3030 << "//----- [cf] skip structurizing control flow -----//\n";
3031 logger.indent();
3032 });
3033 return success();
3034 }
3035
3036 LLVM_DEBUG({
3037 logger.startLine()
3038 << "//----- [cf] start structurizing control flow -----//\n";
3039 logger.indent();
3040 });
3041
3042 LLVM_DEBUG({
3043 logger.startLine() << "[cf] split conditional blocks\n";
3044 logger.startLine() << "\n";
3045 });
3046
3047 if (failed(splitSelectionHeader())) {
3048 return failure();
3049 }
3050
3051 while (!blockMergeInfo.empty()) {
3052 Block *headerBlock = blockMergeInfo.begin()->first;
3053 BlockMergeInfo mergeInfo = blockMergeInfo.begin()->second;
3054
3055 LLVM_DEBUG({
3056 logger.startLine() << "[cf] header block " << headerBlock << ":\n";
3057 headerBlock->print(logger.getOStream());
3058 logger.startLine() << "\n";
3059 });
3060
3061 auto *mergeBlock = mergeInfo.mergeBlock;
3062 assert(mergeBlock && "merge block cannot be nullptr");
3063 if (mergeInfo.continueBlock && !mergeBlock->args_empty())
3064 return emitError(unknownLoc, "OpPhi in loop merge block unimplemented");
3065 LLVM_DEBUG({
3066 logger.startLine() << "[cf] merge block " << mergeBlock << ":\n";
3067 mergeBlock->print(logger.getOStream());
3068 logger.startLine() << "\n";
3069 });
3070
3071 auto *continueBlock = mergeInfo.continueBlock;
3072 LLVM_DEBUG(if (continueBlock) {
3073 logger.startLine() << "[cf] continue block " << continueBlock << ":\n";
3074 continueBlock->print(logger.getOStream());
3075 logger.startLine() << "\n";
3076 });
3077 // Erase this case before calling into structurizer, who will update
3078 // blockMergeInfo.
3079 blockMergeInfo.erase(blockMergeInfo.begin());
3080 ControlFlowStructurizer structurizer(mergeInfo.loc, mergeInfo.control,
3081 blockMergeInfo, headerBlock,
3082 mergeBlock, continueBlock
3083#ifndef NDEBUG
3084 ,
3085 logger
3086#endif
3087 );
3088 if (failed(structurizer.structurize()))
3089 return failure();
3090 }
3091
3092 LLVM_DEBUG({
3093 logger.unindent();
3094 logger.startLine()
3095 << "//--- [cf] completed structurizing control flow ---//\n";
3096 });
3097 return success();
3098}
3099
3100//===----------------------------------------------------------------------===//
3101// Debug
3102//===----------------------------------------------------------------------===//
3103
3105 if (!debugLine)
3106 return unknownLoc;
3107
3108 auto fileName = debugInfoMap.lookup(debugLine->fileID).str();
3109 if (fileName.empty())
3110 fileName = "<unknown>";
3111 return FileLineColLoc::get(opBuilder.getStringAttr(fileName), debugLine->line,
3112 debugLine->column);
3113}
3114
3115LogicalResult
3117 // According to SPIR-V spec:
3118 // "This location information applies to the instructions physically
3119 // following this instruction, up to the first occurrence of any of the
3120 // following: the next end of block, the next OpLine instruction, or the next
3121 // OpNoLine instruction."
3122 if (operands.size() != 3)
3123 return emitError(unknownLoc, "OpLine must have 3 operands");
3124 debugLine = DebugLine{operands[0], operands[1], operands[2]};
3125 return success();
3126}
3127
3128void spirv::Deserializer::clearDebugLine() { debugLine = std::nullopt; }
3129
3130LogicalResult
3132 if (operands.size() < 2)
3133 return emitError(unknownLoc, "OpString needs at least 2 operands");
3134
3135 if (!debugInfoMap.lookup(operands[0]).empty())
3136 return emitError(unknownLoc,
3137 "duplicate debug string found for result <id> ")
3138 << operands[0];
3139
3140 unsigned wordIndex = 1;
3141 StringRef debugString = decodeStringLiteral(operands, wordIndex);
3142 if (wordIndex != operands.size())
3143 return emitError(unknownLoc,
3144 "unexpected trailing words in OpString instruction");
3145
3146 debugInfoMap[operands[0]] = debugString;
3147 return success();
3148}
return success()
static bool isLoop(Operation *op)
Returns true if the given operation represents a loop by testing whether it implements the LoopLikeOp...
static bool isFnEntryBlock(Block *block)
Returns true if the given block is a function entry block.
#define MIN_VERSION_CASE(v)
static void setInherentOrDiscardableAttr(Operation *op, StringAttr name, Attribute value)
static LogicalResult deserializeCacheControlDecoration(Location loc, OpBuilder &opBuilder, DenseMap< uint32_t, NamedAttrList > &decorations, ArrayRef< uint32_t > words, StringAttr symbol, StringRef decorationName, StringRef cacheControlKind)
static llvm::ManagedStatic< PassManagerOptions > options
Attributes are known-constant values of operations.
Definition Attributes.h:25
This class represents an argument of a Block.
Definition Value.h:306
Block represents an ordered list of Operations.
Definition Block.h:33
bool empty()
Definition Block.h:172
void erase()
Unlink this Block from its parent region and delete it.
Definition Block.cpp:66
Block * splitBlock(iterator splitBefore)
Split the block into two blocks before the specified operation or iterator.
Definition Block.cpp:323
Operation & front()
Definition Block.h:177
Operation * getTerminator()
Get the terminator operation of this block.
Definition Block.cpp:249
void print(raw_ostream &os)
bool args_empty()
Definition Block.h:123
iterator begin()
Definition Block.h:167
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
ArrayAttr getArrayAttr(ArrayRef< Attribute > value)
Definition Builders.cpp:275
Attr getAttr(Args &&...args)
Get or construct an instance of the attribute Attr with provided arguments.
Definition Builders.h:101
static DenseElementsAttr get(ShapedType type, ArrayRef< Attribute > values)
Constructs a dense elements attribute from an array of element values.
An attribute that represents a reference to a dense integer vector or tensor object.
static FileLineColLoc get(StringAttr filename, unsigned line, unsigned column)
Definition Location.cpp:157
A symbol reference with a reference path containing a single element.
static FlatSymbolRefAttr get(StringAttr value)
Construct a symbol reference for the given value name.
void map(Value from, Value to)
Inserts a new mapping for 'from' to 'to'.
Definition IRMapping.h:30
auto lookupOrNull(T from) const
Lookup a mapped value within the map.
Definition IRMapping.h:58
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
NamedAttrList is array of NamedAttributes that tracks whether it is sorted and does some basic work t...
NamedAttribute represents a combination of a name and an Attribute value.
Definition Attributes.h:164
RAII guard to reset the insertion point of the builder when destroyed.
Definition Builders.h:351
This class helps build Operations.
Definition Builders.h:210
void setInherentAttr(Operation *op, StringAttr name, Attribute value) const
std::optional< Attribute > getInherentAttr(Operation *op, StringRef name) const
Lookup an inherent attribute by name, this method isn't recommended and may be removed in the future.
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
MutableArrayRef< BlockOperand > getBlockOperands()
Definition Operation.h:747
Region & getRegion(unsigned index)
Returns the region held by this operation at position 'index'.
Definition Operation.h:738
bool use_empty()
Returns true if this operation has no uses.
Definition Operation.h:904
void setDiscardableAttr(StringAttr name, Attribute value)
Set a discardable attribute by name.
Definition Operation.h:512
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Definition Operation.h:432
MutableArrayRef< OpOperand > getOpOperands()
Definition Operation.h:408
OperationName getName()
The name of an operation is the key identifier for it.
Definition Operation.h:115
void print(raw_ostream &os, const OpPrintingFlags &flags={})
static Operation * create(Location location, OperationName name, TypeRange resultTypes, ValueRange operands, NamedAttrList &&attributes, PropertyRef properties, BlockRange successors, unsigned numRegions)
Create a new Operation with the specific fields.
Definition Operation.cpp:65
result_range getResults()
Definition Operation.h:440
Operation * clone(IRMapping &mapper, const CloneOptions &options=CloneOptions::all())
Create a deep copy of this operation, remapping any operands that use values outside of the operation...
InFlightDiagnostic emitOpError(const Twine &message={})
Emit an error with the op name prefixed, like "'dim' op " which is convenient for verifiers.
void erase()
Remove this operation from its parent block and delete it.
This class contains a list of basic blocks and a link to the parent operation it is attached to.
Definition Region.h:26
Block & back()
Definition Region.h:64
iterator end()
Definition Region.h:56
BlockListType & getBlocks()
Definition Region.h:45
BlockListType::iterator iterator
Definition Region.h:52
void takeBody(Region &other)
Takes body of another region (that region will have no body after this operation completes).
Definition Region.h:268
This class implements the successor iterators for Block.
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
bool isIntOrFloat() const
Return true if this is an integer (of any signedness) or a float type.
Definition Types.cpp:118
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
static WalkResult advance()
Definition WalkResult.h:47
static ArrayType get(Type elementType, unsigned elementCount)
static CooperativeMatrixType get(Type elementType, uint32_t rows, uint32_t columns, Scope scope, CooperativeMatrixUseKHR use)
LogicalResult wireUpBlockArgument()
Creates block arguments on predecessors previously recorded when handling OpPhi instructions.
Value materializeSpecConstantOperation(uint32_t resultID, spirv::Opcode enclosedOpcode, uint32_t resultTypeID, ArrayRef< uint32_t > enclosedOpOperands)
Materializes/emits an OpSpecConstantOp instruction.
LogicalResult processOpTypePointer(ArrayRef< uint32_t > operands)
Value getValue(uint32_t id)
Get the Value associated with a result <id>.
LogicalResult processMatrixType(ArrayRef< uint32_t > operands)
LogicalResult processGlobalVariable(ArrayRef< uint32_t > operands)
Processes the OpVariable instructions at current offset into binary.
std::optional< SpecConstOperationMaterializationInfo > getSpecConstantOperation(uint32_t id)
Gets the info needed to materialize the spec constant operation op associated with the given <id>.
LogicalResult processConstantNull(ArrayRef< uint32_t > operands)
Processes a SPIR-V OpConstantNull instruction with the given operands.
LogicalResult processSpecConstantComposite(ArrayRef< uint32_t > operands)
Processes a SPIR-V OpSpecConstantComposite instruction with the given operands.
LogicalResult processInstruction(spirv::Opcode opcode, ArrayRef< uint32_t > operands, bool deferInstructions=true)
Processes a SPIR-V instruction with the given opcode and operands.
LogicalResult processBranchConditional(ArrayRef< uint32_t > operands)
spirv::GlobalVariableOp getGlobalVariable(uint32_t id)
Gets the global variable associated with a result <id> of OpVariable.
LogicalResult createGraphBlock(uint32_t graphID)
Creates a block for graph with the given graphID.
LogicalResult processStructType(ArrayRef< uint32_t > operands)
LogicalResult processGraphARM(ArrayRef< uint32_t > operands)
LogicalResult processSamplerType(ArrayRef< uint32_t > operands)
LogicalResult setFunctionArgAttrs(uint32_t argID, SmallVectorImpl< Attribute > &argAttrs, size_t argIndex)
Sets the function argument's attributes.
LogicalResult structurizeControlFlow()
Extracts blocks belonging to a structured selection/loop into a spirv.mlir.selection/spirv....
LogicalResult processLabel(ArrayRef< uint32_t > operands)
Processes a SPIR-V OpLabel instruction with the given operands.
LogicalResult processSampledImageType(ArrayRef< uint32_t > operands)
LogicalResult processTensorARMType(ArrayRef< uint32_t > operands)
std::optional< spirv::GraphConstantARMOpMaterializationInfo > getGraphConstantARM(uint32_t id)
Gets the GraphConstantARM ID attribute and result type with the given result <id>.
std::optional< std::pair< Attribute, Type > > getConstant(uint32_t id)
Gets the constant's attribute and type associated with the given <id>.
LogicalResult processType(spirv::Opcode opcode, ArrayRef< uint32_t > operands)
Processes a SPIR-V type instruction with given opcode and operands and registers the type into module...
LogicalResult processLoopMerge(ArrayRef< uint32_t > operands)
Processes a SPIR-V OpLoopMerge instruction with the given operands.
LogicalResult processArrayType(ArrayRef< uint32_t > operands)
LogicalResult sliceInstruction(spirv::Opcode &opcode, ArrayRef< uint32_t > &operands, std::optional< spirv::Opcode > expectedOpcode=std::nullopt)
Slices the first instruction out of binary and returns its opcode and operands via opcode and operand...
spirv::SpecConstantCompositeOp getSpecConstantComposite(uint32_t id)
Gets the composite specialization constant with the given result <id>.
LogicalResult processNamedBarrierType(ArrayRef< uint32_t > operands)
SmallVector< uint32_t, 2 > BlockPhiInfo
For OpPhi instructions, we use block arguments to represent them.
LogicalResult processSpecConstantCompositeReplicateEXT(ArrayRef< uint32_t > operands)
Processes a SPIR-V OpSpecConstantCompositeReplicateEXT instruction with the given operands.
LogicalResult processCooperativeMatrixTypeKHR(ArrayRef< uint32_t > operands)
LogicalResult processGraphEntryPointARM(ArrayRef< uint32_t > operands)
LogicalResult processFunction(ArrayRef< uint32_t > operands)
Creates a deserializer for the given SPIR-V binary module.
StringAttr getSymbolDecoration(StringRef decorationName)
Gets the symbol name from the name of decoration.
Block * getOrCreateBlock(uint32_t id)
Gets or creates the block corresponding to the given label <id>.
bool isVoidType(Type type) const
Returns true if the given type is for SPIR-V void type.
std::string getSpecConstantSymbol(uint32_t id)
Returns a symbol to be used for the specialization constant with the given result <id>.
LogicalResult processDebugString(ArrayRef< uint32_t > operands)
Processes a SPIR-V OpString instruction with the given operands.
LogicalResult processPhi(ArrayRef< uint32_t > operands)
Processes a SPIR-V OpPhi instruction with the given operands.
std::string getFunctionSymbol(uint32_t id)
Returns a symbol to be used for the function name with the given result <id>.
void clearDebugLine()
Discontinues any source-level location information that might be active from a previous OpLine instru...
LogicalResult processFunctionType(ArrayRef< uint32_t > operands)
IntegerAttr getConstantInt(uint32_t id)
Gets the constant's integer attribute with the given <id>.
LogicalResult processTypeForwardPointer(ArrayRef< uint32_t > operands)
LogicalResult processSwitch(ArrayRef< uint32_t > operands)
Processes a SPIR-V OpSwitch instruction with the given operands.
LogicalResult processGraphEndARM(ArrayRef< uint32_t > operands)
LogicalResult processImageType(ArrayRef< uint32_t > operands)
LogicalResult processConstantComposite(ArrayRef< uint32_t > operands)
Processes a SPIR-V OpConstantComposite instruction with the given operands.
spirv::SpecConstantOp createSpecConstant(Location loc, uint32_t resultID, TypedAttr defaultValue)
Creates a spirv::SpecConstantOp.
Block * getBlock(uint32_t id) const
Returns the block for the given label <id>.
LogicalResult processGraphTypeARM(ArrayRef< uint32_t > operands)
LogicalResult processBranch(ArrayRef< uint32_t > operands)
std::optional< std::pair< Attribute, Type > > getConstantCompositeReplicate(uint32_t id)
Gets the replicated composite constant's attribute and type associated with the given <id>.
LogicalResult processFunctionEnd(ArrayRef< uint32_t > operands)
Processes OpFunctionEnd and finalizes function.
LogicalResult processRuntimeArrayType(ArrayRef< uint32_t > operands)
LogicalResult processSpecConstantOperation(ArrayRef< uint32_t > operands)
Processes a SPIR-V OpSpecConstantOp instruction with the given operands.
LogicalResult processConstant(ArrayRef< uint32_t > operands, bool isSpec)
Processes a SPIR-V Op{|Spec}Constant instruction with the given operands.
Location createFileLineColLoc(OpBuilder opBuilder)
Creates a FileLineColLoc with the OpLine location information.
LogicalResult processGraphConstantARM(ArrayRef< uint32_t > operands)
Processes a SPIR-V OpGraphConstantARM instruction with the given operands.
LogicalResult processConstantBool(bool isTrue, ArrayRef< uint32_t > operands, bool isSpec)
Processes a SPIR-V Op{|Spec}Constant{True|False} instruction with the given operands.
spirv::SpecConstantOp getSpecConstant(uint32_t id)
Gets the specialization constant with the given result <id>.
LogicalResult processConstantCompositeReplicateEXT(ArrayRef< uint32_t > operands)
Processes a SPIR-V OpConstantCompositeReplicateEXT instruction with the given operands.
LogicalResult processSelectionMerge(ArrayRef< uint32_t > operands)
Processes a SPIR-V OpSelectionMerge instruction with the given operands.
LogicalResult processOpGraphSetOutputARM(ArrayRef< uint32_t > operands)
LogicalResult processDebugLine(ArrayRef< uint32_t > operands)
Processes a SPIR-V OpLine instruction with the given operands.
LogicalResult splitSelectionHeader()
Move a conditional branch or a switch into a separate basic block to avoid unnecessary sinking of def...
std::string getGraphSymbol(uint32_t id)
Returns a symbol to be used for the graph name with the given result <id>.
static ImageType get(Type elementType, Dim dim, ImageDepthInfo depth=ImageDepthInfo::DepthUnknown, ImageArrayedInfo arrayed=ImageArrayedInfo::NonArrayed, ImageSamplingInfo samplingInfo=ImageSamplingInfo::SingleSampled, ImageSamplerUseInfo samplerUse=ImageSamplerUseInfo::SamplerUnknown, ImageFormat format=ImageFormat::Unknown)
Definition SPIRVTypes.h:148
static MatrixType get(Type columnType, uint32_t columnCount)
static NamedBarrierType get(MLIRContext *context)
static PointerType get(Type pointeeType, StorageClass storageClass)
static RuntimeArrayType get(Type elementType)
static SampledImageType get(Type imageType)
static SamplerType get(MLIRContext *context)
static StructType getIdentified(MLIRContext *context, StringRef identifier)
Construct an identified StructType.
static StructType getEmpty(MLIRContext *context, StringRef identifier="")
Construct a (possibly identified) StructType with no members.
static StructType get(ArrayRef< Type > memberTypes, ArrayRef< OffsetInfo > offsetInfo={}, ArrayRef< MemberDecorationInfo > memberDecorations={}, ArrayRef< StructDecorationInfo > structDecorations={})
Construct a literal StructType with at least one member.
static TensorArmType get(ArrayRef< int64_t > shape, Type elementType)
The OpAsmOpInterface, see OpAsmInterface.td for more details.
Definition CallGraph.h:227
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:732
SmallVector< Operation * > mergeOps
Computation function returning, for the op currently being tiled or fused, the per-iteration-domain-d...
constexpr uint32_t kMagicNumber
SPIR-V magic number.
llvm::MapVector< Block *, BlockMergeInfo > BlockMergeInfoMap
Map from a selection/loop's header block to its merge (and continue) target.
StringRef decodeStringLiteral(ArrayRef< uint32_t > words, unsigned &wordIndex)
Decodes a string literal in words starting at wordIndex.
constexpr unsigned kHeaderWordCount
SPIR-V binary header word count.
Include the generated interface declarations.
Type getType(OpFoldResult ofr)
Returns the int type of the integer in ofr.
Definition Utils.cpp:310
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
auto get(MLIRContext *context, Ts &&...params)
Helper method that injects context only if needed, this helps unify some of the attribute constructio...
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Definition LLVM.h:120
std::string debugString(T &&op)
A struct for containing a header block's merge and continue targets.
A struct for containing OpLine instruction information.
A struct that collects the info needed to materialize/emit a GraphConstantARMOp.
A struct that collects the info needed to materialize/emit a SpecConstantOperation op.