| //===-- 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/BinaryFormat/Dwarf.h" |
| #include "llvm/CodeGen/AsmPrinter.h" |
| #include "llvm/IR/DebugInfoMetadata.h" |
| #include "llvm/IR/DebugProgramInstruction.h" |
| #include "llvm/IR/Module.h" |
| #include "llvm/MC/MCInst.h" |
| #include "llvm/MC/MCStreamer.h" |
| #include "llvm/Support/Path.h" |
| |
| using namespace llvm; |
| |
| 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; |
| |
| // Collect compile-unit info: file paths and source languages. |
| for (const DICompileUnit *CU : M->debug_compile_units()) { |
| const DIFile *File = CU->getFile(); |
| CompileUnitInfo Info; |
| 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(); |
| } |
| } |
| |
| // Collect basic and pointer types referenced by debug variable records. |
| for (const auto &F : *M) { |
| for (const auto &BB : F) { |
| for (const auto &I : BB) { |
| for (const DbgVariableRecord &DVR : |
| filterDbgVars(I.getDbgRecordRange())) { |
| const DIType *Ty = DVR.getVariable()->getType(); |
| if (const auto *BT = dyn_cast<DIBasicType>(Ty)) { |
| BasicTypes.insert(BT); |
| } else if (const auto *DT = dyn_cast<DIDerivedType>(Ty)) { |
| if (DT->getTag() == dwarf::DW_TAG_pointer_type) { |
| PointerTypes.insert(DT); |
| if (const auto *BT = |
| dyn_cast_or_null<DIBasicType>(DT->getBaseType())) |
| BasicTypes.insert(BT); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| 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::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::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; |
| } |
| |
| void SPIRVNonSemanticDebugHandler::emitDebugTypePointer( |
| const DIDerivedType *PT, MCRegister VoidTypeReg, MCRegister I32TypeReg, |
| MCRegister ExtInstSetReg, MCRegister I32ZeroReg, |
| const DenseMap<const DIBasicType *, MCRegister> &BasicTypeRegs, |
| 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; |
| |
| // 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 auto *BaseType = dyn_cast_or_null<DIBasicType>(PT->getBaseType())) { |
| auto BTIt = BasicTypeRegs.find(BaseType); |
| if (BTIt != BasicTypeRegs.end()) |
| emitExtInst(SPIRV::NonSemanticExtInst::DebugTypePointer, VoidTypeReg, |
| ExtInstSetReg, {BTIt->second, StorageClassReg, I32ZeroReg}, |
| MAI); |
| } else { |
| // Void pointer: use DebugInfoNone for the base type. Note that |
| // spirv-val currently rejects DebugInfoNone as the base type of |
| // DebugTypePointer; see issue #109287 and the DISABLED spirv-val run |
| // in debug-type-pointer.ll. |
| MCRegister NoneReg = emitExtInst(SPIRV::NonSemanticExtInst::DebugInfoNone, |
| VoidTypeReg, ExtInstSetReg, {}, MAI); |
| emitExtInst(SPIRV::NonSemanticExtInst::DebugTypePointer, VoidTypeReg, |
| ExtInstSetReg, {NoneReg, StorageClassReg, I32ZeroReg}, 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) |
| FileStringRegs.push_back(emitOpString(Info.FilePath, MAI)); |
| |
| for (const DIBasicType *BT : BasicTypes) |
| BasicTypeNameRegs.push_back(emitOpString(BT->getName(), MAI)); |
| } |
| |
| 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. |
| |
| MCRegister VoidTypeReg = findOrEmitOpTypeVoid(MAI); |
| MCRegister I32TypeReg = findOrEmitOpTypeInt32(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. |
| // FileStringRegs was populated by emitNonSemanticDebugStrings() in section 7. |
| assert(FileStringRegs.size() == CompileUnits.size() && |
| "FileStringRegs must be populated by emitNonSemanticDebugStrings()"); |
| for (auto [Info, FileStrReg, SrcLangReg] : |
| llvm::zip(CompileUnits, FileStringRegs, SrcLangRegs)) { |
| MCRegister DebugSourceReg = |
| emitExtInst(SPIRV::NonSemanticExtInst::DebugSource, VoidTypeReg, |
| ExtInstSetReg, {FileStrReg}, MAI); |
| emitExtInst( |
| SPIRV::NonSemanticExtInst::DebugCompilationUnit, VoidTypeReg, |
| ExtInstSetReg, |
| {DebugInfoVersionReg, DwarfVersionReg, DebugSourceReg, SrcLangReg}, |
| MAI); |
| } |
| |
| // Zero constant used as the Flags operand in DebugTypeBasic and |
| // DebugTypePointer. Cached with other i32 constants. |
| MCRegister I32ZeroReg = emitOpConstantI32(0, I32TypeReg, MAI); |
| |
| // Maps each DIBasicType to its DebugTypeBasic result register for use as |
| // operands in DebugTypePointer instructions. |
| DenseMap<const DIBasicType *, MCRegister> BasicTypeRegs; |
| |
| // BasicTypeNameRegs was populated by emitNonSemanticDebugStrings() in |
| // section 7. |
| assert( |
| BasicTypeNameRegs.size() == BasicTypes.size() && |
| "BasicTypeNameRegs must be populated by emitNonSemanticDebugStrings()"); |
| unsigned BTIdx = 0; |
| for (const DIBasicType *BT : BasicTypes) { |
| MCRegister NameReg = BasicTypeNameRegs[BTIdx++]; |
| 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); |
| BasicTypeRegs[BT] = BTReg; |
| } |
| |
| // Emit DebugTypePointer for each referenced pointer type. |
| for (const DIDerivedType *PT : PointerTypes) |
| emitDebugTypePointer(PT, VoidTypeReg, I32TypeReg, ExtInstSetReg, I32ZeroReg, |
| BasicTypeRegs, MAI); |
| } |