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