| //===-- SPIRVNonSemanticDebugHandler.cpp - NSDI AsmPrinter handler -*- C++ |
| //-*-===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "SPIRVNonSemanticDebugHandler.h" |
| #include "MCTargetDesc/SPIRVMCTargetDesc.h" |
| #include "SPIRVSubtarget.h" |
| #include "SPIRVUtils.h" |
| #include "llvm/ADT/SmallVectorExtras.h" |
| #include "llvm/ADT/Twine.h" |
| #include "llvm/BinaryFormat/Dwarf.h" |
| #include "llvm/CodeGen/AsmPrinter.h" |
| #include "llvm/IR/DebugInfo.h" |
| #include "llvm/IR/DebugInfoMetadata.h" |
| #include "llvm/IR/GlobalVariable.h" |
| #include "llvm/IR/Module.h" |
| #include "llvm/MC/MCInst.h" |
| #include "llvm/MC/MCStreamer.h" |
| #include "llvm/Support/ErrorHandling.h" |
| #include "llvm/Support/Path.h" |
| #include <cassert> |
| |
| using namespace llvm; |
| |
| namespace { |
| |
| /// Look up \p Key in a register map and return its value, or std::nullopt when |
| /// the key is absent. |
| template <typename MapT> |
| static std::optional<MCRegister> lookupOptReg(const MapT &Map, |
| typename MapT::key_type Key) { |
| auto It = Map.find(Key); |
| if (It == Map.end()) |
| return std::nullopt; |
| assert(It->second.isValid() && "invalid register stored in map"); |
| return It->second; |
| } |
| |
| /// Partition \p Ty into \p BasicTypes, \p PointerTypes, \p SubroutineTypes, |
| /// \p VectorTypes, \p ArrayTypes, and \p CompositeTypes for NSDI emission. Used |
| /// when iterating DebugInfoFinder.types(); each DI node is seen once, so no |
| /// recursion into pointer bases. Non-pointer derived kinds are ignored because |
| /// they are not yet supported. Only types that are supported (later used) are |
| /// partitioned. |
| static void |
| partitionTypes(const DIType *Ty, SmallVector<const DIBasicType *> &BasicTypes, |
| SmallVector<const DIDerivedType *> &PointerTypes, |
| SmallVector<const DISubroutineType *> &SubroutineTypes, |
| SmallVector<const DICompositeType *> &VectorTypes, |
| SmallVector<const DICompositeType *> &ArrayTypes, |
| SmallVector<const DICompositeType *> &CompositeTypes) { |
| if (const auto *BT = dyn_cast<DIBasicType>(Ty)) { |
| BasicTypes.push_back(BT); |
| return; |
| } |
| if (const auto *ST = dyn_cast<DISubroutineType>(Ty)) { |
| SubroutineTypes.push_back(ST); |
| return; |
| } |
| if (const auto *CT = dyn_cast<DICompositeType>(Ty)) { |
| if (CT->getTag() == dwarf::DW_TAG_array_type) { |
| // A vector is an array with DINode::FlagVector. A plain array is the |
| // same tag without it. A matrix is also lowered to a DW_TAG_array_type |
| // (two subranges), so it is indistinguishable from a 2D array here and |
| // is emitted as a DebugTypeArray. |
| // |
| // FIXME: Emitting a matrix as a DebugTypeArray is valid but loses the |
| // matrix shape. DWARF has no matrix tag, so distinguishing a matrix needs |
| // a new DINode flag analogous to FlagVector, set on the array, plus a way |
| // to carry column-major vs row-major traits. Array-of-vectors alone would |
| // not disambiguate a matrix from a genuine array of vectors. Once the |
| // frontend marks matrices, route them to a DebugTypeMatrix path here. |
| if (CT->isVector()) |
| VectorTypes.push_back(CT); |
| else |
| ArrayTypes.push_back(CT); |
| } else if (CT->getTag() == dwarf::DW_TAG_structure_type || |
| CT->getTag() == dwarf::DW_TAG_class_type || |
| CT->getTag() == dwarf::DW_TAG_union_type) { |
| CompositeTypes.push_back(CT); |
| } |
| return; |
| } |
| const auto *DT = dyn_cast<DIDerivedType>(Ty); |
| if (DT && DT->getTag() == dwarf::DW_TAG_pointer_type) |
| PointerTypes.push_back(DT); |
| } |
| |
| enum : uint32_t { |
| NSDIFlagIsProtected = 1u << 0, |
| NSDIFlagIsPrivate = 1u << 1, |
| NSDIFlagIsPublic = NSDIFlagIsPrivate | NSDIFlagIsProtected, |
| NSDIFlagIsLocal = 1u << 2, |
| NSDIFlagIsDefinition = 1u << 3, |
| NSDIFlagFwdDecl = 1u << 4, |
| NSDIFlagArtificial = 1u << 5, |
| NSDIFlagExplicit = 1u << 6, |
| NSDIFlagPrototyped = 1u << 7, |
| NSDIFlagObjectPointer = 1u << 8, |
| NSDIFlagStaticMember = 1u << 9, |
| NSDIFlagIndirectVariable = 1u << 10, |
| NSDIFlagLValueReference = 1u << 11, |
| NSDIFlagRValueReference = 1u << 12, |
| NSDIFlagIsOptimized = 1u << 13, |
| NSDIFlagIsEnumClass = 1u << 14, |
| NSDIFlagTypePassByValue = 1u << 15, |
| NSDIFlagTypePassByReference = 1u << 16, |
| NSDIFlagUnknownPhysicalLayout = 1u << 17, |
| }; |
| |
| static uint32_t mapDIFlagsToNonSemantic(DINode::DIFlags DFlags) { |
| uint32_t Flags = 0; |
| if ((DFlags & DINode::FlagAccessibility) == DINode::FlagPublic) |
| Flags |= NSDIFlagIsPublic; |
| if ((DFlags & DINode::FlagAccessibility) == DINode::FlagProtected) |
| Flags |= NSDIFlagIsProtected; |
| if ((DFlags & DINode::FlagAccessibility) == DINode::FlagPrivate) |
| Flags |= NSDIFlagIsPrivate; |
| if (DFlags & DINode::FlagFwdDecl) |
| Flags |= NSDIFlagFwdDecl; |
| if (DFlags & DINode::FlagArtificial) |
| Flags |= NSDIFlagArtificial; |
| if (DFlags & DINode::FlagExplicit) |
| Flags |= NSDIFlagExplicit; |
| if (DFlags & DINode::FlagPrototyped) |
| Flags |= NSDIFlagPrototyped; |
| if (DFlags & DINode::FlagObjectPointer) |
| Flags |= NSDIFlagObjectPointer; |
| if (DFlags & DINode::FlagStaticMember) |
| Flags |= NSDIFlagStaticMember; |
| if (DFlags & DINode::FlagLValueReference) |
| Flags |= NSDIFlagLValueReference; |
| if (DFlags & DINode::FlagRValueReference) |
| Flags |= NSDIFlagRValueReference; |
| if (DFlags & DINode::FlagTypePassByValue) |
| Flags |= NSDIFlagTypePassByValue; |
| if (DFlags & DINode::FlagTypePassByReference) |
| Flags |= NSDIFlagTypePassByReference; |
| if (DFlags & DINode::FlagEnumClass) |
| Flags |= NSDIFlagIsEnumClass; |
| return Flags; |
| } |
| |
| static uint32_t transDebugFlags(const DINode *DN) { |
| uint32_t Flags = 0; |
| if (const auto *GV = dyn_cast<DIGlobalVariable>(DN)) { |
| if (GV->isLocalToUnit()) |
| Flags |= NSDIFlagIsLocal; |
| if (GV->isDefinition()) |
| Flags |= NSDIFlagIsDefinition; |
| } |
| if (const auto *SP = dyn_cast<DISubprogram>(DN)) { |
| if (SP->isLocalToUnit()) |
| Flags |= NSDIFlagIsLocal; |
| if (SP->isOptimized()) |
| Flags |= NSDIFlagIsOptimized; |
| if (SP->isDefinition()) |
| Flags |= NSDIFlagIsDefinition; |
| Flags |= mapDIFlagsToNonSemantic(SP->getFlags()); |
| } |
| if (DN->getTag() == dwarf::DW_TAG_reference_type) |
| Flags |= NSDIFlagLValueReference; |
| if (DN->getTag() == dwarf::DW_TAG_rvalue_reference_type) |
| Flags |= NSDIFlagRValueReference; |
| if (const auto *Ty = dyn_cast<DIType>(DN)) |
| Flags |= mapDIFlagsToNonSemantic(Ty->getFlags()); |
| if (const auto *LV = dyn_cast<DILocalVariable>(DN)) |
| Flags |= mapDIFlagsToNonSemantic(LV->getFlags()); |
| return Flags; |
| } |
| |
| // Map a DWARF composite tag to a NonSemantic.Shader.DebugInfo Composite Type |
| // value: Class 0, Structure 1, Union 2. |
| static uint32_t mapCompositeTypeTag(unsigned Tag) { |
| switch (Tag) { |
| case dwarf::DW_TAG_class_type: |
| return 0; |
| case dwarf::DW_TAG_structure_type: |
| return 1; |
| case dwarf::DW_TAG_union_type: |
| return 2; |
| default: |
| reportFatalInternalError("unexpected DWARF composite tag " + Twine(Tag) + |
| ". Expecting 0, 1 or 2"); |
| } |
| } |
| |
| } // namespace |
| |
| SPIRVNonSemanticDebugHandler::SPIRVNonSemanticDebugHandler(AsmPrinter &AP) |
| : DebugHandlerBase(&AP) {} |
| |
| // Map DWARF source language codes to NonSemantic.Shader.DebugInfo.100 source |
| // language codes. Values are from the SourceLanguage enum in the |
| // NonSemantic.Shader.DebugInfo.100 specification, section 4.3. |
| unsigned SPIRVNonSemanticDebugHandler::toNSDISrcLang(unsigned DwarfSrcLang) { |
| switch (DwarfSrcLang) { |
| case dwarf::DW_LANG_OpenCL: |
| return 3; // OpenCL_C |
| case dwarf::DW_LANG_OpenCL_CPP: |
| return 4; // OpenCL_CPP |
| case dwarf::DW_LANG_CPP_for_OpenCL: |
| return 6; // CPP_for_OpenCL |
| case dwarf::DW_LANG_GLSL: |
| return 2; // GLSL |
| case dwarf::DW_LANG_HLSL: |
| return 5; // HLSL |
| case dwarf::DW_LANG_SYCL: |
| return 7; // SYCL |
| case dwarf::DW_LANG_Zig: |
| return 12; // Zig |
| default: |
| return 0; // Unknown |
| } |
| } |
| |
| void SPIRVNonSemanticDebugHandler::beginModule(Module *M) { |
| // The base class sets Asm = nullptr when the module has no compile units, |
| // and initializes lexical scope tracking otherwise. |
| DebugHandlerBase::beginModule(M); |
| |
| if (!Asm) |
| return; |
| |
| CompileUnits.clear(); |
| BasicTypes.clear(); |
| PointerTypes.clear(); |
| SubroutineTypes.clear(); |
| VectorTypes.clear(); |
| ArrayTypes.clear(); |
| CompositeTypes.clear(); |
| SubprogramDeclarations.clear(); |
| GlobalVariableDebugInfoMap.clear(); |
| DebugFunctionDeclarationRegs.clear(); |
| ScopeToPathOpStringReg.clear(); |
| CUToCompilationUnitDbgReg.clear(); |
| DebugSourceRegByFileStr.clear(); |
| DebugTypeRegs.clear(); |
| OpStringContentCache.clear(); |
| I32ConstantCache.clear(); |
| DebugTypeFunctionCache.clear(); |
| GlobalDIEmitted = false; |
| #ifndef NDEBUG |
| NonSemanticOpStringsSectionEmitted = false; |
| #endif |
| CachedDebugInfoNoneReg = MCRegister(); |
| CachedEmptyStringReg = MCRegister(); |
| CachedOpTypeVoidReg = MCRegister(); |
| CachedOpTypeInt32Reg = MCRegister(); |
| |
| // Collect compile-unit info: file paths and source languages. |
| for (const DICompileUnit *CU : M->debug_compile_units()) { |
| const DIFile *File = CU->getFile(); |
| CompileUnitInfo Info; |
| Info.TheCU = CU; |
| if (sys::path::is_absolute(File->getFilename())) |
| Info.FilePath = File->getFilename(); |
| else |
| sys::path::append(Info.FilePath, File->getDirectory(), |
| File->getFilename()); |
| // getName() returns the language code regardless of whether the name is |
| // versioned. getUnversionedName() would assert on versioned names. |
| Info.SpirvSourceLanguage = toNSDISrcLang(CU->getSourceLanguage().getName()); |
| CompileUnits.push_back(std::move(Info)); |
| } |
| |
| // Collect DWARF version from module flags. For CodeView modules there is no |
| // "Dwarf Version" flag; DwarfVersion remains 0, which is the correct value |
| // for the DebugCompilationUnit DWARF Version operand in that case. |
| if (const NamedMDNode *Flags = M->getNamedMetadata("llvm.module.flags")) { |
| for (const auto *Op : Flags->operands()) { |
| const MDOperand &NameOp = Op->getOperand(1); |
| if (NameOp.equalsStr("Dwarf Version")) |
| DwarfVersion = |
| cast<ConstantInt>( |
| cast<ConstantAsMetadata>(Op->getOperand(2))->getValue()) |
| ->getSExtValue(); |
| } |
| } |
| |
| // Find all debug info types that may be referenced by NSDI instructions. |
| DebugInfoFinder Finder; |
| Finder.processModule(*M); |
| llvm::for_each(Finder.types(), [&](DIType *Ty) { |
| partitionTypes(Ty, BasicTypes, PointerTypes, SubroutineTypes, VectorTypes, |
| ArrayTypes, CompositeTypes); |
| }); |
| |
| for (const DISubprogram *SP : Finder.subprograms()) { |
| if (!SP->isDefinition()) |
| SubprogramDeclarations.push_back(SP); |
| } |
| |
| // Walk LLVM globals to map each DIGlobalVariable to its llvm::GlobalVariable. |
| DenseMap<const DIGlobalVariable *, const GlobalVariable *> DIGVToLLVMGV; |
| for (const GlobalVariable &G : M->globals()) { |
| SmallVector<DIGlobalVariableExpression *> GVEs; |
| G.getDebugInfo(GVEs); |
| for (DIGlobalVariableExpression *GVE : GVEs) { |
| if (const DIGlobalVariable *GV = GVE->getVariable()) { |
| DIGVToLLVMGV.try_emplace(GV, &G); |
| } |
| } |
| } |
| |
| for (const DIGlobalVariableExpression *GVE : Finder.global_variables()) { |
| const DIGlobalVariable *GV = GVE->getVariable(); |
| const DIExpression *Expr = GVE->getExpression(); |
| GlobalVariableDebugInfoMap.try_emplace( |
| GV, GlobalVariableDebugInfo{Expr, DIGVToLLVMGV.lookup(GV)}); |
| } |
| } |
| |
| void SPIRVNonSemanticDebugHandler::prepareModuleOutput( |
| const SPIRVSubtarget &ST, SPIRV::ModuleAnalysisInfo &MAI) { |
| if (CompileUnits.empty()) |
| return; |
| if (!ST.canUseExtension(SPIRV::Extension::SPV_KHR_non_semantic_info)) |
| return; |
| |
| // Add the extension to requirements so OpExtension is output. |
| MAI.Reqs.addExtension(SPIRV::Extension::SPV_KHR_non_semantic_info); |
| |
| // Add the NonSemantic.Shader.DebugInfo.100 entry to ExtInstSetMap so that |
| // outputOpExtInstImports() emits the OpExtInstImport instruction. Allocate a |
| // fresh result ID for it now; the same ID is used in emitExtInst() operands. |
| constexpr unsigned NSSet = static_cast<unsigned>( |
| SPIRV::InstructionSet::NonSemantic_Shader_DebugInfo_100); |
| if (!MAI.ExtInstSetMap.count(NSSet)) |
| MAI.ExtInstSetMap[NSSet] = MAI.getNextIDRegister(); |
| } |
| |
| void SPIRVNonSemanticDebugHandler::emitMCInst(MCInst &Inst) { |
| Asm->OutStreamer->emitInstruction(Inst, Asm->getSubtargetInfo()); |
| } |
| |
| MCRegister |
| SPIRVNonSemanticDebugHandler::emitOpString(StringRef S, |
| SPIRV::ModuleAnalysisInfo &MAI) { |
| MCRegister Reg = MAI.getNextIDRegister(); |
| MCInst Inst; |
| Inst.setOpcode(SPIRV::OpString); |
| Inst.addOperand(MCOperand::createReg(Reg)); |
| addStringImm(S, Inst); |
| emitMCInst(Inst); |
| return Reg; |
| } |
| |
| MCRegister SPIRVNonSemanticDebugHandler::emitOpStringIfNew( |
| StringRef S, SPIRV::ModuleAnalysisInfo &MAI) { |
| #ifndef NDEBUG |
| assert(!NonSemanticOpStringsSectionEmitted && |
| "emitOpStringIfNew is only valid while emitting SPIR-V section 7"); |
| #endif |
| auto [It, Inserted] = OpStringContentCache.try_emplace(S, MCRegister()); |
| if (Inserted) |
| It->second = emitOpString(S, MAI); |
| |
| return It->second; |
| } |
| |
| MCRegister SPIRVNonSemanticDebugHandler::getCachedOpStringReg(StringRef S) { |
| #ifndef NDEBUG |
| assert(NonSemanticOpStringsSectionEmitted && |
| "getCachedOpStringReg requires emitNonSemanticDebugStrings() first"); |
| #endif |
| auto It = OpStringContentCache.find(S); |
| assert(It != OpStringContentCache.end() && |
| "NSDI OpString missing from cache; emitNonSemanticDebugStrings must " |
| "cache every string used in section 10"); |
| return It->second; |
| } |
| |
| MCRegister SPIRVNonSemanticDebugHandler::emitAndCacheScopePathOpStringReg( |
| const DIScope *Scope, SPIRV::ModuleAnalysisInfo &MAI) { |
| auto [It, Inserted] = ScopeToPathOpStringReg.try_emplace(Scope, MCRegister()); |
| if (Inserted) |
| It->second = emitOpStringIfNew(getDebugFullPath(Scope), MAI); |
| return It->second; |
| } |
| |
| MCRegister SPIRVNonSemanticDebugHandler::getCachedScopePathOpStringReg( |
| const DIScope *Scope, bool UseEmptyPathIfNullScope) { |
| if (!Scope) { |
| assert(UseEmptyPathIfNullScope && |
| "null scope path lookup requires UseEmptyPathIfNullScope"); |
| assert(CachedEmptyStringReg.isValid() && |
| "empty path OpString must be cached in emitNonSemanticDebugStrings"); |
| return CachedEmptyStringReg; |
| } |
| auto It = ScopeToPathOpStringReg.find(Scope); |
| assert(It != ScopeToPathOpStringReg.end() && |
| "path OpString must be cached in emitNonSemanticDebugStrings"); |
| MCRegister FileStrReg = It->second; |
| assert(FileStrReg.isValid() && "path OpString id must be valid once cached"); |
| return FileStrReg; |
| } |
| |
| MCRegister SPIRVNonSemanticDebugHandler::emitOpConstantI32( |
| uint32_t Value, MCRegister I32TypeReg, SPIRV::ModuleAnalysisInfo &MAI) { |
| auto [It, Inserted] = I32ConstantCache.try_emplace(Value); |
| if (!Inserted) |
| return It->second; |
| |
| MCRegister Reg = MAI.getNextIDRegister(); |
| It->second = Reg; |
| MCInst Inst; |
| Inst.setOpcode(SPIRV::OpConstantI); |
| Inst.addOperand(MCOperand::createReg(Reg)); |
| Inst.addOperand(MCOperand::createReg(I32TypeReg)); |
| Inst.addOperand(MCOperand::createImm(static_cast<int64_t>(Value))); |
| emitMCInst(Inst); |
| return Reg; |
| } |
| |
| MCRegister SPIRVNonSemanticDebugHandler::emitExtInst( |
| SPIRV::NonSemanticExtInst::NonSemanticExtInst Opcode, |
| MCRegister VoidTypeReg, MCRegister ExtInstSetReg, |
| ArrayRef<MCRegister> Operands, SPIRV::ModuleAnalysisInfo &MAI) { |
| MCRegister Reg = MAI.getNextIDRegister(); |
| MCInst Inst; |
| Inst.setOpcode(SPIRV::OpExtInst); |
| Inst.addOperand(MCOperand::createReg(Reg)); |
| Inst.addOperand(MCOperand::createReg(VoidTypeReg)); |
| Inst.addOperand(MCOperand::createReg(ExtInstSetReg)); |
| Inst.addOperand(MCOperand::createImm(static_cast<int64_t>(Opcode))); |
| for (MCRegister R : Operands) |
| Inst.addOperand(MCOperand::createReg(R)); |
| emitMCInst(Inst); |
| return Reg; |
| } |
| |
| MCRegister SPIRVNonSemanticDebugHandler::getOrEmitDebugTypeFunction( |
| ArrayRef<MCRegister> Ops, MCRegister VoidTypeReg, MCRegister ExtInstSetReg, |
| SPIRV::ModuleAnalysisInfo &MAI) { |
| auto [It, Inserted] = |
| DebugTypeFunctionCache.try_emplace(SmallVector<MCRegister, 8>(Ops)); |
| if (!Inserted) |
| return It->second; |
| |
| MCRegister Reg = emitExtInst(SPIRV::NonSemanticExtInst::DebugTypeFunction, |
| VoidTypeReg, ExtInstSetReg, Ops, MAI); |
| It->second = Reg; |
| return Reg; |
| } |
| |
| MCRegister SPIRVNonSemanticDebugHandler::getOrEmitOpTypeVoidReg( |
| SPIRV::ModuleAnalysisInfo &MAI) { |
| if (!CachedOpTypeVoidReg.isValid()) |
| CachedOpTypeVoidReg = findOrEmitOpTypeVoid(MAI); |
| return CachedOpTypeVoidReg; |
| } |
| |
| MCRegister SPIRVNonSemanticDebugHandler::getOrEmitOpTypeInt32Reg( |
| SPIRV::ModuleAnalysisInfo &MAI) { |
| if (!CachedOpTypeInt32Reg.isValid()) |
| CachedOpTypeInt32Reg = findOrEmitOpTypeInt32(MAI); |
| return CachedOpTypeInt32Reg; |
| } |
| |
| MCRegister SPIRVNonSemanticDebugHandler::findOrEmitOpTypeVoid( |
| SPIRV::ModuleAnalysisInfo &MAI) { |
| for (const MachineInstr *MI : MAI.getMSInstrs(SPIRV::MB_TypeConstVars)) { |
| if (MI->getOpcode() == SPIRV::OpTypeVoid) |
| return MAI.getRegisterAlias(MI->getMF(), MI->getOperand(0).getReg()); |
| } |
| MCRegister Reg = MAI.getNextIDRegister(); |
| MCInst Inst; |
| Inst.setOpcode(SPIRV::OpTypeVoid); |
| Inst.addOperand(MCOperand::createReg(Reg)); |
| emitMCInst(Inst); |
| return Reg; |
| } |
| |
| MCRegister SPIRVNonSemanticDebugHandler::findOrEmitOpTypeInt32( |
| SPIRV::ModuleAnalysisInfo &MAI) { |
| for (const MachineInstr *MI : MAI.getMSInstrs(SPIRV::MB_TypeConstVars)) { |
| if (MI->getOpcode() == SPIRV::OpTypeInt && |
| MI->getOperand(1).getImm() == 32 && MI->getOperand(2).getImm() == 0) |
| return MAI.getRegisterAlias(MI->getMF(), MI->getOperand(0).getReg()); |
| } |
| MCRegister Reg = MAI.getNextIDRegister(); |
| MCInst Inst; |
| Inst.setOpcode(SPIRV::OpTypeInt); |
| Inst.addOperand(MCOperand::createReg(Reg)); |
| Inst.addOperand(MCOperand::createImm(32)); // width |
| Inst.addOperand(MCOperand::createImm(0)); // signedness (unsigned) |
| emitMCInst(Inst); |
| return Reg; |
| } |
| |
| std::optional<MCRegister> SPIRVNonSemanticDebugHandler::emitDebugTypePointer( |
| const DIDerivedType *PT, MCRegister ExtInstSetReg, |
| SPIRV::ModuleAnalysisInfo &MAI) { |
| // A DWARF address space is required to determine the SPIR-V storage class. |
| // Skip pointer types that do not carry one. |
| if (!PT->getDWARFAddressSpace().has_value()) |
| return std::nullopt; |
| |
| MCRegister VoidTypeReg = getOrEmitOpTypeVoidReg(MAI); |
| MCRegister I32TypeReg = getOrEmitOpTypeInt32Reg(MAI); |
| MCRegister DebugTypePointerFlagsReg = |
| emitOpConstantI32(transDebugFlags(PT), I32TypeReg, MAI); |
| |
| // For SPIR-V targets, Clang sets DwarfAddressSpace to the LLVM IR address |
| // space, which addressSpaceToStorageClass expects. |
| const auto &ST = static_cast<const SPIRVSubtarget &>(Asm->getSubtargetInfo()); |
| MCRegister StorageClassReg = emitOpConstantI32( |
| addressSpaceToStorageClass(PT->getDWARFAddressSpace().value(), ST), |
| I32TypeReg, MAI); |
| |
| if (const DIType *BaseTy = PT->getBaseType()) { |
| auto BaseIt = DebugTypeRegs.find(BaseTy); |
| if (BaseIt != DebugTypeRegs.end()) |
| return emitExtInst( |
| SPIRV::NonSemanticExtInst::DebugTypePointer, VoidTypeReg, |
| ExtInstSetReg, |
| {BaseIt->second, StorageClassReg, DebugTypePointerFlagsReg}, MAI); |
| // Unsupported type, no DebugType* id available. |
| return std::nullopt; |
| } |
| // No getBaseType() (typical for void*): use DebugInfoNone as Base Type, |
| // same as SPIRV-LLVM-Translator (see issue #109287 and the DISABLED |
| // spirv-val run in debug-type-pointer.ll). spirv-val may still reject this |
| // encoding; see https://github.com/KhronosGroup/SPIRV-Registry/pull/287. |
| return emitExtInst( |
| SPIRV::NonSemanticExtInst::DebugTypePointer, VoidTypeReg, ExtInstSetReg, |
| {CachedDebugInfoNoneReg, StorageClassReg, DebugTypePointerFlagsReg}, MAI); |
| } |
| |
| std::optional<MCRegister> |
| SPIRVNonSemanticDebugHandler::emitDebugTypeFunctionForSubroutineType( |
| const DISubroutineType *ST, MCRegister ExtInstSetReg, |
| SPIRV::ModuleAnalysisInfo &MAI) { |
| MCRegister VoidTypeReg = getOrEmitOpTypeVoidReg(MAI); |
| MCRegister I32TypeReg = getOrEmitOpTypeInt32Reg(MAI); |
| MCRegister DebugTypeFunctionFlagsReg = |
| emitOpConstantI32(transDebugFlags(ST), I32TypeReg, MAI); |
| DITypeArray TA = ST->getTypeArray(); |
| SmallVector<MCRegister, 8> Ops; |
| Ops.push_back(DebugTypeFunctionFlagsReg); |
| // Empty DI type tuple: no explicit return or parameter slots (hand-written IR |
| // may use !{}). Emit void-only prototype. Same as SPIRV-LLVM-Translator when |
| // DISubroutineType::getTypeArray() has zero elements. |
| if (TA.empty()) { |
| Ops.push_back(VoidTypeReg); |
| } else { |
| for (unsigned I = 0, E = TA.size(); I != E; ++I) { |
| bool IsReturnType = (I == 0); |
| auto OptReg = mapDISignatureTypeToReg(TA[I], VoidTypeReg, IsReturnType); |
| // No emitted DebugType* id for this slot (e.g., pointer that |
| // was skipped due missing address space, etc.). |
| if (!OptReg) |
| return std::nullopt; |
| Ops.push_back(*OptReg); |
| } |
| } |
| return getOrEmitDebugTypeFunction(Ops, VoidTypeReg, ExtInstSetReg, MAI); |
| } |
| |
| // Match SPIRV-LLVM-Translator's selection logic for the Parent operand. |
| std::optional<MCRegister> |
| SPIRVNonSemanticDebugHandler::resolveDebugFunctionDeclarationParent( |
| const DISubprogram *SP) const { |
| const DIScope *Scope = SP->getScope(); |
| if (Scope && !isa<DIFile>(Scope)) { |
| // TODO: Complete with other lookups once other scopes are supported |
| // (subclasses of DIScope). |
| const DIType *Ty = dyn_cast<DIType>(Scope); |
| if (!Ty) |
| return std::nullopt; |
| return lookupOptReg(DebugTypeRegs, Ty); |
| } |
| |
| const DICompileUnit *ParentCU = SP->getUnit(); |
| if (!ParentCU && !CompileUnits.empty()) |
| ParentCU = CompileUnits[0].TheCU; |
| if (!ParentCU) |
| return std::nullopt; |
| return lookupOptReg(CUToCompilationUnitDbgReg, ParentCU); |
| } |
| |
| std::optional<MCRegister> SPIRVNonSemanticDebugHandler::resolveTypeScopeParent( |
| const DIScope *Scope) const { |
| // When the scope is itself a type (e.g. a struct nested in another struct), |
| // the parent is that enclosing type's debug id. |
| if (const auto *Ty = dyn_cast_or_null<DIType>(Scope)) |
| return lookupOptReg(DebugTypeRegs, Ty); |
| |
| // For a file, compile-unit, namespace, or absent scope, the parent is the |
| // first module DebugCompilationUnit. |
| if (CompileUnits.empty()) |
| return std::nullopt; |
| |
| return lookupOptReg(CUToCompilationUnitDbgReg, CompileUnits[0].TheCU); |
| } |
| |
| std::optional<MCRegister> |
| SPIRVNonSemanticDebugHandler::emitDebugFunctionDeclaration( |
| const DISubprogram *SP, MCRegister VoidTypeReg, MCRegister I32TypeReg, |
| MCRegister ExtInstSetReg, SPIRV::ModuleAnalysisInfo &MAI) { |
| assert(SP && "SP must not be null in emitDebugFunctionDeclaration"); |
| assert(!SP->isDefinition() && |
| "SP must not be a definition in emitDebugFunctionDeclaration"); |
| |
| // The IR verifier already enforces that this cannot be null. |
| const DISubroutineType *ST = SP->getType(); |
| |
| auto FnTyRegOpt = lookupOptReg(DebugTypeRegs, ST); |
| if (!FnTyRegOpt) |
| return std::nullopt; |
| MCRegister FnTyReg = *FnTyRegOpt; |
| |
| auto ParentRegOpt = resolveDebugFunctionDeclarationParent(SP); |
| if (!ParentRegOpt) |
| return std::nullopt; |
| |
| MCRegister ParentReg = *ParentRegOpt; |
| |
| MCRegister FileStrReg = getCachedScopePathOpStringReg(SP); |
| |
| MCRegister NameReg = getCachedOpStringReg(SP->getName()); |
| MCRegister LinkageReg = getCachedOpStringReg(SP->getLinkageName()); |
| MCRegister SrcReg = getOrEmitDebugSourceForFileStrReg(FileStrReg, VoidTypeReg, |
| ExtInstSetReg, MAI); |
| |
| MCRegister LineReg = |
| emitOpConstantI32(static_cast<uint32_t>(SP->getLine()), I32TypeReg, MAI); |
| MCRegister ColReg = emitOpConstantI32(0, I32TypeReg, MAI); |
| |
| uint32_t FlagsVal = transDebugFlags(SP); |
| // TODO: When composite scopes are DebugFunctionDeclaration parents (available |
| // in DebugTypeRegs), sync declaration Flags with SPIRV-LLVM-Translator. |
| FlagsVal &= ~NSDIFlagIsDefinition; |
| MCRegister FlagsReg = emitOpConstantI32(FlagsVal, I32TypeReg, MAI); |
| |
| return emitExtInst(SPIRV::NonSemanticExtInst::DebugFunctionDeclaration, |
| VoidTypeReg, ExtInstSetReg, |
| {NameReg, FnTyReg, SrcReg, LineReg, ColReg, ParentReg, |
| LinkageReg, FlagsReg}, |
| MAI); |
| } |
| |
| std::optional<MCRegister> SPIRVNonSemanticDebugHandler::mapDISignatureTypeToReg( |
| const DIType *Ty, MCRegister VoidTypeReg, bool ReturnType) { |
| if (!Ty) { |
| if (ReturnType) |
| return VoidTypeReg; |
| assert(CachedDebugInfoNoneReg.isValid() && |
| "DebugInfoNone must be emitted before DISubroutineType operands"); |
| return CachedDebugInfoNoneReg; |
| } |
| return lookupOptReg(DebugTypeRegs, Ty); |
| } |
| |
| MCRegister SPIRVNonSemanticDebugHandler::resolveGlobalVariableParent( |
| const DIGlobalVariable *) const { |
| // TODO: When this backend emits debug instructions for namespace, subprogram, |
| // compilation units, and module scopes return GV->getScope()'s debug id. |
| |
| // !CompileUnits.empty() was already checked before staring the emission of |
| // NSDI instructions. |
| assert(!CompileUnits.empty() && |
| "resolveGlobalVariableParent requires non-empty CompileUnits"); |
| std::optional<MCRegister> ParentRegOpt = |
| lookupOptReg(CUToCompilationUnitDbgReg, CompileUnits[0].TheCU); |
| assert(ParentRegOpt && "DebugCompilationUnit must be emitted before " |
| "resolveGlobalVariableParent"); |
| // Fallback: first module compile unit (SPIRV-LLVM-Translator default). |
| return *ParentRegOpt; |
| } |
| |
| // Unimplemented no-op; see emitDebugExpression declaration. |
| std::optional<MCRegister> SPIRVNonSemanticDebugHandler::emitDebugExpression( |
| const DIExpression *, MCRegister, MCRegister, SPIRV::ModuleAnalysisInfo &) { |
| return std::nullopt; |
| } |
| |
| std::optional<MCRegister> SPIRVNonSemanticDebugHandler::emitDebugGlobalVariable( |
| const DIGlobalVariable *GV, const GlobalVariableDebugInfo &Info, |
| MCRegister VoidTypeReg, MCRegister I32TypeReg, MCRegister ExtInstSetReg, |
| SPIRV::ModuleAnalysisInfo &MAI) { |
| assert(GV && "GV must not be null in emitDebugGlobalVariable"); |
| |
| MCRegister ParentReg = resolveGlobalVariableParent(GV); |
| |
| // TyReg: DebugInfoNone when GV has no DI type (as done in |
| // SPIRV-LLVM-Translator). Declarations (isDefinition: false) can have null |
| // getType() while definitions must have a non-null one (enforced by the IR |
| // verifier). |
| MCRegister TyReg = CachedDebugInfoNoneReg; |
| if (const DIType *Ty = GV->getType()) { |
| auto TyRegOpt = lookupOptReg(DebugTypeRegs, Ty); |
| if (!TyRegOpt) |
| return std::nullopt; |
| TyReg = *TyRegOpt; |
| } |
| |
| std::optional<MCRegister> StaticMemberRegOpt; |
| if (const DIDerivedType *SM = GV->getStaticDataMemberDeclaration()) { |
| StaticMemberRegOpt = lookupOptReg(DebugTypeRegs, SM); |
| if (!StaticMemberRegOpt) |
| return std::nullopt; |
| } |
| |
| MCRegister NameReg = getCachedOpStringReg(GV->getName()); |
| MCRegister LinkageReg = getCachedOpStringReg(GV->getLinkageName()); |
| MCRegister FileStrReg = getCachedScopePathOpStringReg( |
| GV->getFile(), /*UseEmptyPathIfNullScope=*/true); |
| MCRegister SrcReg = getOrEmitDebugSourceForFileStrReg(FileStrReg, VoidTypeReg, |
| ExtInstSetReg, MAI); |
| |
| MCRegister LineReg = |
| emitOpConstantI32(static_cast<uint32_t>(GV->getLine()), I32TypeReg, MAI); |
| // DIGlobalVariable or DIGlobalVariableExpression metadata carry no column |
| // field. Column is hardcoded to 0 (because it can't be determined), matching |
| // SPIRV-LLVM-Translator. |
| MCRegister ColReg = emitOpConstantI32(0, I32TypeReg, MAI); |
| |
| // Variable: @g OpVariable id when !dbg matches; else a DebugExpression for |
| // the GVE init value when no @g exists; else DebugInfoNone. |
| MCRegister VariableReg = CachedDebugInfoNoneReg; |
| if (const GlobalVariable *LLVMGV = Info.LLVMGV) { |
| MCRegister GVReg = MAI.getGlobalObjReg(LLVMGV); |
| if (GVReg.isValid()) |
| VariableReg = GVReg; |
| } else if (Info.Expr) { |
| if (auto ExprReg = |
| emitDebugExpression(Info.Expr, VoidTypeReg, ExtInstSetReg, MAI)) |
| VariableReg = *ExprReg; |
| } |
| |
| MCRegister FlagsReg = emitOpConstantI32(transDebugFlags(GV), I32TypeReg, MAI); |
| |
| SmallVector<MCRegister, 10> Ops = {NameReg, TyReg, SrcReg, |
| LineReg, ColReg, ParentReg, |
| LinkageReg, VariableReg, FlagsReg}; |
| |
| if (StaticMemberRegOpt) |
| Ops.push_back(*StaticMemberRegOpt); |
| |
| return emitExtInst(SPIRV::NonSemanticExtInst::DebugGlobalVariable, |
| VoidTypeReg, ExtInstSetReg, Ops, MAI); |
| } |
| |
| std::optional<MCRegister> SPIRVNonSemanticDebugHandler::emitDebugTypeVector( |
| const DICompositeType *VT, MCRegister ExtInstSetReg, |
| SPIRV::ModuleAnalysisInfo &MAI) { |
| const auto *BaseTy = dyn_cast_or_null<DIBasicType>(VT->getBaseType()); |
| if (!BaseTy) |
| return std::nullopt; |
| auto BTIt = DebugTypeRegs.find(BaseTy); |
| if (BTIt == DebugTypeRegs.end()) |
| return std::nullopt; |
| |
| // DebugTypeVector models only 1D vectors (multi-subrange types cannot be |
| // encoded). |
| DINodeArray Elements = VT->getElements(); |
| if (Elements.size() != 1) |
| return std::nullopt; |
| const auto *SR = cast<DISubrange>(Elements[0]); |
| const auto *CI = dyn_cast_if_present<ConstantInt *>(SR->getCount()); |
| if (!CI) |
| return std::nullopt; |
| |
| MCRegister VoidTypeReg = getOrEmitOpTypeVoidReg(MAI); |
| MCRegister I32TypeReg = getOrEmitOpTypeInt32Reg(MAI); |
| MCRegister CountReg = emitOpConstantI32( |
| static_cast<uint32_t>(CI->getZExtValue()), I32TypeReg, MAI); |
| return emitExtInst(SPIRV::NonSemanticExtInst::DebugTypeVector, VoidTypeReg, |
| ExtInstSetReg, {BTIt->second, CountReg}, MAI); |
| } |
| |
| std::optional<MCRegister> SPIRVNonSemanticDebugHandler::emitDebugTypeArray( |
| const DICompositeType *AT, MCRegister ExtInstSetReg, |
| SPIRV::ModuleAnalysisInfo &MAI) { |
| // The element (base) type must already be in DebugTypeRegs. Unlike |
| // DebugTypeVector, the element may be any debug type, not only a basic type. |
| auto BaseRegOpt = lookupOptReg(DebugTypeRegs, AT->getBaseType()); |
| if (!BaseRegOpt) |
| return std::nullopt; |
| |
| MCRegister VoidTypeReg = getOrEmitOpTypeVoidReg(MAI); |
| MCRegister I32TypeReg = getOrEmitOpTypeInt32Reg(MAI); |
| |
| SmallVector<MCRegister> Ops; |
| Ops.push_back(*BaseRegOpt); |
| |
| // One component count per DISubrange, in DWARF subrange order. Emit 0 for |
| // counts that are not a compile-time constant (dynamic arrays). This matches |
| // OpTypeRuntimeArray. |
| for (const DINode *Element : AT->getElements()) { |
| const auto *SR = dyn_cast<DISubrange>(Element); |
| if (!SR) |
| continue; |
| // A DIVariable count (a variable-length array) is not a ConstantInt, so it |
| // maps to 0 here. DebugTypeArray also allows a DebugLocalVariable or |
| // DebugGlobalVariable id for it, but no frontend we target emits one. A |
| // constant wider than 32 bits maps to 0 too, since the count operand is a |
| // 32-bit OpConstant and such an array cannot occur in a shader. |
| uint32_t Count = 0; |
| if (const auto *CI = dyn_cast_if_present<ConstantInt *>(SR->getCount())) { |
| const APInt &Value = CI->getValue(); |
| if (Value.getActiveBits() <= 32) |
| Count = static_cast<uint32_t>(Value.getZExtValue()); |
| } |
| Ops.push_back(emitOpConstantI32(Count, I32TypeReg, MAI)); |
| } |
| |
| return emitExtInst(SPIRV::NonSemanticExtInst::DebugTypeArray, VoidTypeReg, |
| ExtInstSetReg, Ops, MAI); |
| } |
| |
| std::optional<MCRegister> SPIRVNonSemanticDebugHandler::emitDebugTypeMember( |
| const DIDerivedType *M, MCRegister VoidTypeReg, MCRegister I32TypeReg, |
| MCRegister ExtInstSetReg, SPIRV::ModuleAnalysisInfo &MAI) { |
| // The member type must already be in DebugTypeRegs. |
| auto TyRegOpt = lookupOptReg(DebugTypeRegs, M->getBaseType()); |
| if (!TyRegOpt) |
| return std::nullopt; |
| |
| MCRegister NameReg = getCachedOpStringReg(M->getName()); |
| MCRegister FileStrReg = getCachedScopePathOpStringReg( |
| M->getFile(), /*UseEmptyPathIfNullScope=*/true); |
| MCRegister SrcReg = getOrEmitDebugSourceForFileStrReg(FileStrReg, VoidTypeReg, |
| ExtInstSetReg, MAI); |
| MCRegister LineReg = |
| emitOpConstantI32(static_cast<uint32_t>(M->getLine()), I32TypeReg, MAI); |
| |
| // DIDerivedType members carry no column, so emit 0. |
| MCRegister ColReg = emitOpConstantI32(0, I32TypeReg, MAI); |
| MCRegister OffsetReg = emitOpConstantI32( |
| static_cast<uint32_t>(M->getOffsetInBits()), I32TypeReg, MAI); |
| MCRegister SizeReg = emitOpConstantI32( |
| static_cast<uint32_t>(M->getSizeInBits()), I32TypeReg, MAI); |
| MCRegister FlagsReg = emitOpConstantI32(transDebugFlags(M), I32TypeReg, MAI); |
| |
| // In NonSemantic.Shader.DebugInfo a DebugTypeMember has no Parent operand: |
| // only the composite references its members. This is by design, it drops the |
| // Parent that OpenCL.DebugInfo.100 had, and it avoids a composite/member |
| // reference cycle. |
| // |
| // FIXME: Static members are not handled yet: their constant initializer is |
| // available but is not emitted as the optional Value operand, and under DWARF |
| // 5 a static member is tagged DW_TAG_variable, which the caller's member loop |
| // skips. |
| return emitExtInst(SPIRV::NonSemanticExtInst::DebugTypeMember, VoidTypeReg, |
| ExtInstSetReg, |
| {NameReg, *TyRegOpt, SrcReg, LineReg, ColReg, OffsetReg, |
| SizeReg, FlagsReg}, |
| MAI); |
| } |
| |
| std::optional<MCRegister> SPIRVNonSemanticDebugHandler::emitDebugTypeComposite( |
| const DICompositeType *CT, ArrayRef<MCRegister> MemberRegs, |
| MCRegister VoidTypeReg, MCRegister I32TypeReg, MCRegister ExtInstSetReg, |
| SPIRV::ModuleAnalysisInfo &MAI) { |
| auto ParentRegOpt = resolveTypeScopeParent(CT->getScope()); |
| if (!ParentRegOpt) |
| return std::nullopt; |
| |
| MCRegister NameReg = getCachedOpStringReg(CT->getName()); |
| MCRegister LinkageReg = getCachedOpStringReg(CT->getIdentifier()); |
| MCRegister FileStrReg = getCachedScopePathOpStringReg( |
| CT->getFile(), /*UseEmptyPathIfNullScope=*/true); |
| MCRegister SrcReg = getOrEmitDebugSourceForFileStrReg(FileStrReg, VoidTypeReg, |
| ExtInstSetReg, MAI); |
| |
| MCRegister TagReg = |
| emitOpConstantI32(mapCompositeTypeTag(CT->getTag()), I32TypeReg, MAI); |
| MCRegister LineReg = |
| emitOpConstantI32(static_cast<uint32_t>(CT->getLine()), I32TypeReg, MAI); |
| MCRegister ColReg = emitOpConstantI32(0, I32TypeReg, MAI); |
| |
| // A forward declaration has no known size or members: Size is DebugInfoNone. |
| MCRegister SizeReg = CachedDebugInfoNoneReg; |
| if (!CT->isForwardDecl()) |
| SizeReg = emitOpConstantI32(static_cast<uint32_t>(CT->getSizeInBits()), |
| I32TypeReg, MAI); |
| |
| MCRegister FlagsReg = emitOpConstantI32(transDebugFlags(CT), I32TypeReg, MAI); |
| |
| SmallVector<MCRegister> Ops = {NameReg, TagReg, SrcReg, |
| LineReg, ColReg, *ParentRegOpt, |
| LinkageReg, SizeReg, FlagsReg}; |
| Ops.append(MemberRegs.begin(), MemberRegs.end()); |
| return emitExtInst(SPIRV::NonSemanticExtInst::DebugTypeComposite, VoidTypeReg, |
| ExtInstSetReg, Ops, MAI); |
| } |
| |
| void SPIRVNonSemanticDebugHandler::emitNonSemanticDebugStrings( |
| SPIRV::ModuleAnalysisInfo &MAI) { |
| if (CompileUnits.empty()) |
| return; |
| // Check that prepareModuleOutput() registered the extended instruction set. |
| // If the subtarget does not support the extension, neither strings nor ext |
| // insts are emitted. |
| constexpr unsigned NSSet = static_cast<unsigned>( |
| SPIRV::InstructionSet::NonSemantic_Shader_DebugInfo_100); |
| if (!MAI.getExtInstSetReg(NSSet).isValid()) |
| return; |
| |
| for (const CompileUnitInfo &Info : CompileUnits) { |
| if (Info.TheCU) { |
| MCRegister PathReg = emitOpStringIfNew(Info.FilePath, MAI); |
| ScopeToPathOpStringReg[Info.TheCU] = PathReg; |
| if (const DIFile *F = Info.TheCU->getFile()) |
| ScopeToPathOpStringReg[F] = PathReg; |
| } |
| } |
| |
| for (const DIBasicType *BT : BasicTypes) |
| emitOpStringIfNew(BT->getName(), MAI); |
| |
| for (const DISubprogram *SP : SubprogramDeclarations) { |
| emitOpStringIfNew(SP->getName(), MAI); |
| emitOpStringIfNew(SP->getLinkageName(), MAI); |
| emitAndCacheScopePathOpStringReg(SP, MAI); |
| } |
| |
| // Cache the OpStrings each DebugTypeComposite and its DebugTypeMembers use: |
| // the composite name, identifier (linkage name), and path, plus each member |
| // name and path. |
| for (const DICompositeType *CT : CompositeTypes) { |
| emitOpStringIfNew(CT->getName(), MAI); |
| emitOpStringIfNew(CT->getIdentifier(), MAI); |
| emitAndCacheScopePathOpStringReg(CT->getFile(), MAI); |
| for (const DINode *Element : CT->getElements()) { |
| const auto *M = dyn_cast<DIDerivedType>(Element); |
| if (!M || M->getTag() != dwarf::DW_TAG_member) |
| continue; |
| emitOpStringIfNew(M->getName(), MAI); |
| emitAndCacheScopePathOpStringReg(M->getFile(), MAI); |
| } |
| } |
| |
| for (const auto &[GV, _] : GlobalVariableDebugInfoMap) { |
| emitOpStringIfNew(GV->getName(), MAI); |
| emitOpStringIfNew(GV->getLinkageName(), MAI); |
| emitAndCacheScopePathOpStringReg(GV->getFile(), MAI); |
| } |
| |
| CachedEmptyStringReg = emitOpStringIfNew("", MAI); |
| |
| #ifndef NDEBUG |
| NonSemanticOpStringsSectionEmitted = true; |
| #endif |
| } |
| |
| void SPIRVNonSemanticDebugHandler::emitNonSemanticGlobalDebugInfo( |
| SPIRV::ModuleAnalysisInfo &MAI) { |
| if (GlobalDIEmitted || CompileUnits.empty()) |
| return; |
| GlobalDIEmitted = true; |
| |
| // Retrieve the ext inst set register allocated by prepareModuleOutput(). |
| constexpr unsigned NSSet = static_cast<unsigned>( |
| SPIRV::InstructionSet::NonSemantic_Shader_DebugInfo_100); |
| MCRegister ExtInstSetReg = MAI.getExtInstSetReg(NSSet); |
| if (!ExtInstSetReg.isValid()) |
| return; // Extension not available. |
| |
| #ifndef NDEBUG |
| assert(NonSemanticOpStringsSectionEmitted && |
| "emitNonSemanticDebugStrings() must run before " |
| "emitNonSemanticGlobalDebugInfo()"); |
| #endif |
| |
| MCRegister VoidTypeReg = getOrEmitOpTypeVoidReg(MAI); |
| MCRegister I32TypeReg = getOrEmitOpTypeInt32Reg(MAI); |
| |
| CachedDebugInfoNoneReg = emitExtInst(SPIRV::NonSemanticExtInst::DebugInfoNone, |
| VoidTypeReg, ExtInstSetReg, {}, MAI); |
| |
| // Emit integer constants shared across all NSDI instructions. The constant |
| // cache ensures each value is emitted at most once even when referenced from |
| // multiple instructions. All constants are pre-emitted before any DebugSource |
| // so that the output order is: constants, then |
| // DebugSource+DebugCompilationUnit pairs. This keeps OpConstant instructions |
| // grouped before the OpExtInst instructions. |
| |
| // The Version operand of DebugCompilationUnit is the version of the |
| // NonSemantic.Shader.DebugInfo instruction set, which is 100 for |
| // "NonSemantic.Shader.DebugInfo.100" (NonSemanticShaderDebugInfo100Version). |
| MCRegister DebugInfoVersionReg = emitOpConstantI32(100, I32TypeReg, MAI); |
| MCRegister DwarfVersionReg = |
| emitOpConstantI32(static_cast<uint32_t>(DwarfVersion), I32TypeReg, MAI); |
| |
| // Pre-emit source language constants for all compile units before entering |
| // the DebugSource loop. |
| SmallVector<MCRegister> SrcLangRegs = |
| map_to_vector(CompileUnits, [&](const CompileUnitInfo &Info) { |
| return emitOpConstantI32(Info.SpirvSourceLanguage, I32TypeReg, MAI); |
| }); |
| |
| // Emit DebugSource and DebugCompilationUnit for each compile unit. |
| for (auto [Info, SrcLangReg] : llvm::zip(CompileUnits, SrcLangRegs)) { |
| MCRegister FileStrReg = ScopeToPathOpStringReg.lookup(Info.TheCU); |
| assert(FileStrReg.isValid() && |
| "CU path OpString must be emitted in emitNonSemanticDebugStrings"); |
| MCRegister DebugSourceReg = getOrEmitDebugSourceForFileStrReg( |
| FileStrReg, VoidTypeReg, ExtInstSetReg, MAI); |
| MCRegister CUDbgReg = emitExtInst( |
| SPIRV::NonSemanticExtInst::DebugCompilationUnit, VoidTypeReg, |
| ExtInstSetReg, |
| {DebugInfoVersionReg, DwarfVersionReg, DebugSourceReg, SrcLangReg}, |
| MAI); |
| if (Info.TheCU) |
| CUToCompilationUnitDbgReg[Info.TheCU] = CUDbgReg; |
| } |
| |
| // Zero constant used as the Flags operand in DebugTypeBasic and |
| // DebugTypePointer. Cached with other i32 constants. |
| MCRegister I32ZeroReg = emitOpConstantI32(0, I32TypeReg, MAI); |
| |
| DebugTypeRegs.clear(); |
| |
| for (const DIBasicType *BT : BasicTypes) { |
| MCRegister NameReg = getCachedOpStringReg(BT->getName()); |
| MCRegister SizeReg = emitOpConstantI32( |
| static_cast<uint32_t>(BT->getSizeInBits()), I32TypeReg, MAI); |
| |
| // Map DWARF base type encodings to NSDI encoding codes per |
| // NonSemantic.Shader.DebugInfo.100 specification, section 4.5. |
| unsigned Encoding = 0; // Unspecified |
| switch (BT->getEncoding()) { |
| case dwarf::DW_ATE_address: |
| Encoding = 1; |
| break; |
| case dwarf::DW_ATE_boolean: |
| Encoding = 2; |
| break; |
| case dwarf::DW_ATE_float: |
| Encoding = 3; |
| break; |
| case dwarf::DW_ATE_signed: |
| Encoding = 4; |
| break; |
| case dwarf::DW_ATE_signed_char: |
| Encoding = 5; |
| break; |
| case dwarf::DW_ATE_unsigned: |
| Encoding = 6; |
| break; |
| case dwarf::DW_ATE_unsigned_char: |
| Encoding = 7; |
| break; |
| } |
| MCRegister EncodingReg = emitOpConstantI32(Encoding, I32TypeReg, MAI); |
| |
| MCRegister BTReg = emitExtInst( |
| SPIRV::NonSemanticExtInst::DebugTypeBasic, VoidTypeReg, ExtInstSetReg, |
| {NameReg, SizeReg, EncodingReg, I32ZeroReg}, MAI); |
| DebugTypeRegs[BT] = BTReg; |
| } |
| |
| // Emit DebugTypeVector for each collected vector type. |
| for (const DICompositeType *VT : VectorTypes) { |
| if (auto VecReg = emitDebugTypeVector(VT, ExtInstSetReg, MAI)) |
| DebugTypeRegs[VT] = *VecReg; |
| } |
| |
| // Emit DebugTypePointer for each referenced pointer type. |
| for (const DIDerivedType *PT : PointerTypes) { |
| if (auto PtrReg = emitDebugTypePointer(PT, ExtInstSetReg, MAI)) |
| DebugTypeRegs[PT] = *PtrReg; |
| } |
| |
| // Emit DebugTypeArray for each collected array type. Placed after the basic, |
| // vector, and pointer types so an array over any of them can resolve its |
| // element id. An array whose element type was not emitted is skipped. |
| for (const DICompositeType *AT : ArrayTypes) { |
| if (auto ArrReg = emitDebugTypeArray(AT, ExtInstSetReg, MAI)) |
| DebugTypeRegs[AT] = *ArrReg; |
| } |
| |
| // Emit DebugTypeFunction for each distinct DISubroutineType. |
| for (const DISubroutineType *ST : SubroutineTypes) { |
| if (auto FnTyReg = |
| emitDebugTypeFunctionForSubroutineType(ST, ExtInstSetReg, MAI)) |
| DebugTypeRegs[ST] = *FnTyReg; |
| } |
| |
| // Emit DebugFunctionDeclaration for DISubprogram declarations. |
| for (const DISubprogram *SP : SubprogramDeclarations) { |
| if (auto DeclReg = emitDebugFunctionDeclaration(SP, VoidTypeReg, I32TypeReg, |
| ExtInstSetReg, MAI)) |
| DebugFunctionDeclarationRegs[SP] = *DeclReg; |
| } |
| |
| // Emit DebugTypeMember and DebugTypeComposite for each struct, class, or |
| // union. Each member is emitted before the composite that lists it, so the |
| // Members operand references already-defined ids. A member whose type is not |
| // in DebugTypeRegs is skipped. |
| for (const DICompositeType *CT : CompositeTypes) { |
| SmallVector<MCRegister> MemberRegs; |
| for (const DINode *Element : CT->getElements()) { |
| const auto *M = dyn_cast<DIDerivedType>(Element); |
| if (!M || M->getTag() != dwarf::DW_TAG_member) |
| continue; |
| if (auto MemberReg = emitDebugTypeMember(M, VoidTypeReg, I32TypeReg, |
| ExtInstSetReg, MAI)) |
| MemberRegs.push_back(*MemberReg); |
| } |
| if (auto CompReg = emitDebugTypeComposite(CT, MemberRegs, VoidTypeReg, |
| I32TypeReg, ExtInstSetReg, MAI)) |
| DebugTypeRegs[CT] = *CompReg; |
| } |
| |
| // Emit DebugGlobalVariable for each collected DIGlobalVariable. |
| for (const auto &[GV, Info] : GlobalVariableDebugInfoMap) |
| emitDebugGlobalVariable(GV, Info, VoidTypeReg, I32TypeReg, ExtInstSetReg, |
| MAI); |
| } |
| |
| SmallString<128> |
| SPIRVNonSemanticDebugHandler::getDebugFullPath(const DIScope *Scope) const { |
| SmallString<128> Out; |
| if (!Scope) |
| return Out; |
| StringRef Filename = Scope->getFilename(); |
| const auto Style = sys::path::Style::native; |
| if (sys::path::is_absolute(Filename, Style)) |
| Out.assign(Filename.begin(), Filename.end()); |
| else { |
| StringRef Dir = Scope->getDirectory(); |
| Out.assign(Dir.begin(), Dir.end()); |
| sys::path::append(Out, Style, Filename); |
| } |
| return Out; |
| } |
| |
| MCRegister SPIRVNonSemanticDebugHandler::getOrEmitDebugSourceForFileStrReg( |
| MCRegister FileStrReg, MCRegister VoidTypeReg, MCRegister ExtInstSetReg, |
| SPIRV::ModuleAnalysisInfo &MAI) { |
| const unsigned Key = FileStrReg.id(); |
| auto It = DebugSourceRegByFileStr.find(Key); |
| if (It != DebugSourceRegByFileStr.end()) |
| return It->second; |
| |
| MCRegister DS = emitExtInst(SPIRV::NonSemanticExtInst::DebugSource, |
| VoidTypeReg, ExtInstSetReg, {FileStrReg}, MAI); |
| DebugSourceRegByFileStr[Key] = DS; |
| return DS; |
| } |