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