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