[llvm] [SPIRV] Migrate NSDI emission from a machine pass to DebugHandlerBase (PR #191212)
Diego Novillo via llvm-commits
llvm-commits at lists.llvm.org
Mon Apr 13 01:07:45 PDT 2026
================
@@ -0,0 +1,370 @@
+//===-- 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/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;
+ 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 and debug-info version numbers from module flags.
+ 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();
+ else if (NameOp.equalsStr("Debug Info Version"))
+ DebugInfoVersion =
+ 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;
+}
+
+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.
+
+ // Find OpTypeVoid and OpTypeInt 32 0 from the already-emitted TypeConstVars
+ // section. These are the result-type operand for OpExtInst and the type for
+ // OpConstant integers, respectively.
+ MCRegister VoidTypeReg, I32TypeReg;
+ for (const MachineInstr *MI : MAI.getMSInstrs(SPIRV::MB_TypeConstVars)) {
+ if (MI->getOpcode() == SPIRV::OpTypeVoid)
+ VoidTypeReg =
+ MAI.getRegisterAlias(MI->getMF(), MI->getOperand(0).getReg());
+ else if (MI->getOpcode() == SPIRV::OpTypeInt &&
+ MI->getOperand(1).getImm() == 32 &&
+ MI->getOperand(2).getImm() == 0)
+ I32TypeReg =
+ MAI.getRegisterAlias(MI->getMF(), MI->getOperand(0).getReg());
+ }
+
+ // Emit OpTypeVoid if the module did not already define one. A module with
+ // debug info but no void-returning functions would not have it in
+ // MB_TypeConstVars.
+ if (!VoidTypeReg.isValid()) {
+ VoidTypeReg = MAI.getNextIDRegister();
+ MCInst Inst;
+ Inst.setOpcode(SPIRV::OpTypeVoid);
+ Inst.addOperand(MCOperand::createReg(VoidTypeReg));
+ emitMCInst(Inst);
+ }
+
+ // Emit OpTypeInt 32 0 if the module did not already define one.
+ if (!I32TypeReg.isValid()) {
+ I32TypeReg = MAI.getNextIDRegister();
+ MCInst Inst;
+ Inst.setOpcode(SPIRV::OpTypeInt);
+ Inst.addOperand(MCOperand::createReg(I32TypeReg));
+ Inst.addOperand(MCOperand::createImm(32)); // width
+ Inst.addOperand(MCOperand::createImm(0)); // signedness (unsigned)
+ emitMCInst(Inst);
+ }
+
+ // 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.
+ MCRegister DebugInfoVersionReg = emitOpConstantI32(
+ static_cast<uint32_t>(DebugInfoVersion), 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;
+ SrcLangRegs.reserve(CompileUnits.size());
+ for (const CompileUnitInfo &Info : CompileUnits)
+ SrcLangRegs.push_back(
+ 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.
+ SmallVector<std::pair<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.emplace_back(BT, BTReg);
+ }
+
+ // Emit DebugTypePointer for each referenced pointer type.
+ for (const DIDerivedType *PT : PointerTypes) {
+ // 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())
+ continue;
+
+ const auto &ST =
+ static_cast<const SPIRVSubtarget &>(Asm->getSubtargetInfo());
+ MCRegister StorageClassReg = emitOpConstantI32(
+ addressSpaceToStorageClass(PT->getDWARFAddressSpace().value(), ST),
+ I32TypeReg, MAI);
+
+ const auto *BaseType = dyn_cast_or_null<DIBasicType>(PT->getBaseType());
+ if (BaseType) {
----------------
dnovillo wrote:
Done.
https://github.com/llvm/llvm-project/pull/191212
More information about the llvm-commits
mailing list