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