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