[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:09:46 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) {
+      for (const auto &[BT, BTReg] : BasicTypeRegs) {
+        if (BT == BaseType) {
+          emitExtInst(SPIRV::NonSemanticExtInst::DebugTypePointer, VoidTypeReg,
+                      ExtInstSetReg, {BTReg, StorageClassReg, I32ZeroReg}, MAI);
+          break;
+        }
+      }
----------------
dnovillo wrote:

Done.

https://github.com/llvm/llvm-project/pull/191212


More information about the llvm-commits mailing list