//===-- 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( cast(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(Ty)) { BasicTypes.insert(BT); } else if (const auto *DT = dyn_cast(Ty)) { if (DT->getTag() == dwarf::DW_TAG_pointer_type) { PointerTypes.insert(DT); if (const auto *BT = dyn_cast_or_null(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( 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(Value))); emitMCInst(Inst); return Reg; } MCRegister SPIRVNonSemanticDebugHandler::emitExtInst( SPIRV::NonSemanticExtInst::NonSemanticExtInst Opcode, MCRegister VoidTypeReg, MCRegister ExtInstSetReg, ArrayRef 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(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 &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(Asm->getSubtargetInfo()); MCRegister StorageClassReg = emitOpConstantI32( addressSpaceToStorageClass(PT->getDWARFAddressSpace().value(), ST), I32TypeReg, MAI); if (const auto *BaseType = dyn_cast_or_null(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( 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( 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(DwarfVersion), I32TypeReg, MAI); // Pre-emit source language constants for all compile units before entering // the DebugSource loop. SmallVector 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 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(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); }