[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:18:47 PDT 2026


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

>From efb61c49c4bfdfb2ff21f2f35b6ff1f55f30d51b Mon Sep 17 00:00:00 2001
From: Diego Novillo <dnovillo at nvidia.com>
Date: Thu, 9 Apr 2026 07:20:41 -0400
Subject: [PATCH 1/4] [SPIRV] Migrate NSDI emission from MachineFunctionPass to
 DebugHandlerBase

---
 llvm/lib/Target/SPIRV/CMakeLists.txt          |   2 +-
 llvm/lib/Target/SPIRV/SPIRV.h                 |   5 +-
 llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp     |  38 +-
 .../Target/SPIRV/SPIRVEmitNonSemanticDI.cpp   | 366 ------------------
 llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp |   6 +
 .../SPIRV/SPIRVNonSemanticDebugHandler.cpp    | 334 ++++++++++++++++
 .../SPIRV/SPIRVNonSemanticDebugHandler.h      | 135 +++++++
 llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp  |  14 +-
 .../debug-info/debug-compilation-unit.ll      |  35 +-
 .../debug-info/debug-optype-void-emission.ll  |  27 ++
 .../debug-info/debug-source-language-glsl.ll  |  26 ++
 .../debug-source-language-unknown.ll          |  28 ++
 .../SPIRV/debug-info/debug-type-basic.ll      |  68 ++--
 .../SPIRV/debug-info/debug-type-pointer.ll    |  62 +--
 llvm/test/CodeGen/SPIRV/llc-pipeline.ll       |   2 -
 15 files changed, 654 insertions(+), 494 deletions(-)
 delete mode 100644 llvm/lib/Target/SPIRV/SPIRVEmitNonSemanticDI.cpp
 create mode 100644 llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
 create mode 100644 llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
 create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-optype-void-emission.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-source-language-glsl.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-source-language-unknown.ll

diff --git a/llvm/lib/Target/SPIRV/CMakeLists.txt b/llvm/lib/Target/SPIRV/CMakeLists.txt
index 0ce96c23603ff..c0da775b318a6 100644
--- a/llvm/lib/Target/SPIRV/CMakeLists.txt
+++ b/llvm/lib/Target/SPIRV/CMakeLists.txt
@@ -52,7 +52,7 @@ add_llvm_target(SPIRVCodeGen
   SPIRVTargetTransformInfo.cpp
   SPIRVTypeInst.cpp
   SPIRVUtils.cpp
-  SPIRVEmitNonSemanticDI.cpp
+  SPIRVNonSemanticDebugHandler.cpp
   SPIRVCBufferAccess.cpp
   SPIRVPushConstantAccess.cpp
 
diff --git a/llvm/lib/Target/SPIRV/SPIRV.h b/llvm/lib/Target/SPIRV/SPIRV.h
index 448a1f0ddaf96..fa3e1c30f5735 100644
--- a/llvm/lib/Target/SPIRV/SPIRV.h
+++ b/llvm/lib/Target/SPIRV/SPIRV.h
@@ -10,7 +10,8 @@
 #define LLVM_LIB_TARGET_SPIRV_SPIRV_H
 
 #include "MCTargetDesc/SPIRVMCTargetDesc.h"
-#include "llvm/CodeGen/MachineFunctionPass.h"
+#include "llvm/Pass.h"
+#include "llvm/PassRegistry.h"
 #include "llvm/Target/TargetMachine.h"
 
 namespace llvm {
@@ -34,7 +35,6 @@ FunctionPass *createSPIRVPreLegalizerPass();
 FunctionPass *createSPIRVPostLegalizerPass();
 ModulePass *createSPIRVEmitIntrinsicsPass(const SPIRVTargetMachine &TM);
 ModulePass *createSPIRVPrepareGlobalsPass();
-MachineFunctionPass *createSPIRVEmitNonSemanticDIPass(SPIRVTargetMachine *TM);
 ModulePass *createSPIRVCtorDtorLoweringLegacyPass();
 InstructionSelector *
 createSPIRVInstructionSelector(const SPIRVTargetMachine &TM,
@@ -51,7 +51,6 @@ void initializeSPIRVStructurizerPass(PassRegistry &);
 void initializeSPIRVCBufferAccessLegacyPass(PassRegistry &);
 void initializeSPIRVPushConstantAccessLegacyPass(PassRegistry &);
 void initializeSPIRVEmitIntrinsicsPass(PassRegistry &);
-void initializeSPIRVEmitNonSemanticDIPass(PassRegistry &);
 void initializeSPIRVLegalizePointerCastPass(PassRegistry &);
 void initializeSPIRVRegularizerPass(PassRegistry &);
 void initializeSPIRVMergeRegionExitTargetsPass(PassRegistry &);
diff --git a/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp b/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp
index e1fceea086287..66e4bded32882 100644
--- a/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp
@@ -16,6 +16,7 @@
 #include "SPIRVInstrInfo.h"
 #include "SPIRVMCInstLower.h"
 #include "SPIRVModuleAnalysis.h"
+#include "SPIRVNonSemanticDebugHandler.h"
 #include "SPIRVSubtarget.h"
 #include "SPIRVTargetMachine.h"
 #include "SPIRVUtils.h"
@@ -105,6 +106,10 @@ class SPIRVAsmPrinter : public AsmPrinter {
   void getAnalysisUsage(AnalysisUsage &AU) const override;
   SPIRV::ModuleAnalysisInfo *MAI;
 
+  // Non-owning pointer to the NSDI handler registered via addAsmPrinterHandler.
+  // Set in doInitialization() when the module has debug info.
+  SPIRVNonSemanticDebugHandler *NSDebugHandler = nullptr;
+
 protected:
   void cleanUp(Module &M);
 };
@@ -118,7 +123,7 @@ void SPIRVAsmPrinter::getAnalysisUsage(AnalysisUsage &AU) const {
 
 // If the module has no functions, we need output global info anyway.
 void SPIRVAsmPrinter::emitEndOfAsmFile(Module &M) {
-  if (ModuleSectionsEmitted == false) {
+  if (!ModuleSectionsEmitted) {
     outputModuleSections();
     ModuleSectionsEmitted = true;
   }
@@ -149,7 +154,7 @@ void SPIRVAsmPrinter::cleanUp(Module &M) {
 }
 
 void SPIRVAsmPrinter::emitFunctionHeader() {
-  if (ModuleSectionsEmitted == false) {
+  if (!ModuleSectionsEmitted) {
     outputModuleSections();
     ModuleSectionsEmitted = true;
   }
@@ -166,6 +171,15 @@ void SPIRVAsmPrinter::emitFunctionHeader() {
 
   auto Section = getObjFileLowering().SectionForGlobal(&F, TM);
   MF->setSection(Section);
+
+  // SPIRVAsmPrinter::emitFunctionHeader() does not call the base class,
+  // so handlers never receive beginFunction() from the normal path. Drive the
+  // per-function lifecycle here, matching what AsmPrinter::emitFunctionHeader()
+  // does for other targets.
+  for (auto &Handler : Handlers) {
+    Handler->beginFunction(MF);
+    Handler->beginBasicBlockSection(MF->front());
+  }
 }
 
 void SPIRVAsmPrinter::outputOpFunctionEnd() {
@@ -821,6 +835,12 @@ void SPIRVAsmPrinter::outputModuleSections() {
   TII = ST->getInstrInfo();
   MAI = &getAnalysis<SPIRVModuleAnalysis>().MAI;
   assert(ST && TII && MAI && M && "Module analysis is required");
+
+  // Let the NSDI handler add its extension and ext inst import entry to MAI
+  // before the module header sections are emitted.
+  if (NSDebugHandler)
+    NSDebugHandler->prepareModuleOutput(*ST, *MAI);
+
   // Output instructions according to the Logical Layout of a Module:
   // 1,2. All OpCapability instructions, then optional OpExtension
   // instructions.
@@ -851,8 +871,11 @@ void SPIRVAsmPrinter::outputModuleSections() {
   // the first section to allow use of: OpLine and OpNoLine debug information;
   // non-semantic instructions with OpExtInst.
   outputModuleSection(SPIRV::MB_TypeConstVars);
-  // 10. All global NonSemantic.Shader.DebugInfo.100 instructions.
-  outputModuleSection(SPIRV::MB_NonSemanticGlobalDI);
+  // 10. All global NonSemantic.Shader.DebugInfo.100 instructions. The
+  // SPIRVNonSemanticDebugHandler emits these directly as MCInsts; the
+  // MB_NonSemanticGlobalDI section in MAI is intentionally left empty.
+  if (NSDebugHandler)
+    NSDebugHandler->emitNonSemanticGlobalDebugInfo(*MAI);
   // 11. All function declarations (functions without a body).
   outputExtFuncDecls();
   // 12. All function definitions (functions with a body).
@@ -861,6 +884,13 @@ void SPIRVAsmPrinter::outputModuleSections() {
 
 bool SPIRVAsmPrinter::doInitialization(Module &M) {
   ModuleSectionsEmitted = false;
+  // Register the NSDI handler before calling the base class so that
+  // AsmPrinter::doInitialization() calls Handler->beginModule(M) for it.
+  if (M.getNamedMetadata("llvm.dbg.cu")) {
+    auto Handler = std::make_unique<SPIRVNonSemanticDebugHandler>(*this);
+    NSDebugHandler = Handler.get();
+    addAsmPrinterHandler(std::move(Handler));
+  }
   // We need to call the parent's one explicitly.
   return AsmPrinter::doInitialization(M);
 }
diff --git a/llvm/lib/Target/SPIRV/SPIRVEmitNonSemanticDI.cpp b/llvm/lib/Target/SPIRV/SPIRVEmitNonSemanticDI.cpp
deleted file mode 100644
index f705a0c81db0e..0000000000000
--- a/llvm/lib/Target/SPIRV/SPIRVEmitNonSemanticDI.cpp
+++ /dev/null
@@ -1,366 +0,0 @@
-#include "MCTargetDesc/SPIRVBaseInfo.h"
-#include "MCTargetDesc/SPIRVMCTargetDesc.h"
-#include "SPIRV.h"
-#include "SPIRVGlobalRegistry.h"
-#include "SPIRVRegisterInfo.h"
-#include "SPIRVTargetMachine.h"
-#include "SPIRVUtils.h"
-#include "llvm/ADT/SetVector.h"
-#include "llvm/ADT/SmallString.h"
-#include "llvm/BinaryFormat/Dwarf.h"
-#include "llvm/CodeGen/GlobalISel/MachineIRBuilder.h"
-#include "llvm/CodeGen/MachineBasicBlock.h"
-#include "llvm/CodeGen/MachineFunction.h"
-#include "llvm/CodeGen/MachineFunctionPass.h"
-#include "llvm/CodeGen/MachineInstr.h"
-#include "llvm/CodeGen/MachineInstrBuilder.h"
-#include "llvm/CodeGen/MachineModuleInfo.h"
-#include "llvm/CodeGen/MachineOperand.h"
-#include "llvm/CodeGen/MachineRegisterInfo.h"
-#include "llvm/CodeGen/Register.h"
-#include "llvm/IR/DebugInfoMetadata.h"
-#include "llvm/IR/DebugProgramInstruction.h"
-#include "llvm/IR/Metadata.h"
-#include "llvm/Support/Casting.h"
-#include "llvm/Support/Path.h"
-
-#define DEBUG_TYPE "spirv-nonsemantic-debug-info"
-
-using namespace llvm;
-
-namespace {
-struct SPIRVEmitNonSemanticDI : public MachineFunctionPass {
-  static char ID;
-  SPIRVTargetMachine *TM;
-  SPIRVEmitNonSemanticDI(SPIRVTargetMachine *TM = nullptr)
-      : MachineFunctionPass(ID), TM(TM) {}
-
-  bool runOnMachineFunction(MachineFunction &MF) override;
-
-private:
-  bool IsGlobalDIEmitted = false;
-  bool emitGlobalDI(MachineFunction &MF);
-};
-} // anonymous namespace
-
-INITIALIZE_PASS(SPIRVEmitNonSemanticDI, DEBUG_TYPE,
-                "SPIRV NonSemantic.Shader.DebugInfo.100 emitter", false, false)
-
-char SPIRVEmitNonSemanticDI::ID = 0;
-
-MachineFunctionPass *
-llvm::createSPIRVEmitNonSemanticDIPass(SPIRVTargetMachine *TM) {
-  return new SPIRVEmitNonSemanticDI(TM);
-}
-
-enum BaseTypeAttributeEncoding {
-  Unspecified = 0,
-  Address = 1,
-  Boolean = 2,
-  Float = 3,
-  Signed = 4,
-  SignedChar = 5,
-  Unsigned = 6,
-  UnsignedChar = 7
-};
-
-enum SourceLanguage {
-  Unknown = 0,
-  ESSL = 1,
-  GLSL = 2,
-  OpenCL_C = 3,
-  OpenCL_CPP = 4,
-  HLSL = 5,
-  CPP_for_OpenCL = 6,
-  SYCL = 7,
-  HERO_C = 8,
-  NZSL = 9,
-  WGSL = 10,
-  Slang = 11,
-  Zig = 12
-};
-
-bool SPIRVEmitNonSemanticDI::emitGlobalDI(MachineFunction &MF) {
-  // If this MachineFunction doesn't have any BB repeat procedure
-  // for the next
-  if (MF.begin() == MF.end()) {
-    IsGlobalDIEmitted = false;
-    return false;
-  }
-
-  // Required variables to get from metadata search
-  LLVMContext *Context;
-  SmallVector<SmallString<128>> FilePaths;
-  SmallVector<int64_t> LLVMSourceLanguages;
-  int64_t DwarfVersion = 0;
-  int64_t DebugInfoVersion = 0;
-  SetVector<DIBasicType *> BasicTypes;
-  SetVector<DIDerivedType *> PointerDerivedTypes;
-  // Searching through the Module metadata to find nescessary
-  // information like DwarfVersion or SourceLanguage
-  {
-    const MachineModuleInfo &MMI =
-        getAnalysis<MachineModuleInfoWrapperPass>().getMMI();
-    const Module *M = MMI.getModule();
-    Context = &M->getContext();
-    const NamedMDNode *DbgCu = M->getNamedMetadata("llvm.dbg.cu");
-    if (!DbgCu)
-      return false;
-    // NonSemantic.Shader.DebugInfo.100 requires SPV_KHR_non_semantic_info.
-    // Bail out if the extension is not available for this target. Targets that
-    // do not enable this extension by default should enable it explicitly with
-    // --spirv-ext=+SPV_KHR_non_semantic_info.
-    if (!TM->getSubtargetImpl()->canUseExtension(
-            SPIRV::Extension::SPV_KHR_non_semantic_info))
-      return false;
-    for (const auto *Op : DbgCu->operands()) {
-      if (const auto *CompileUnit = dyn_cast<DICompileUnit>(Op)) {
-        DIFile *File = CompileUnit->getFile();
-        FilePaths.emplace_back();
-        sys::path::append(FilePaths.back(), File->getDirectory(),
-                          File->getFilename());
-        LLVMSourceLanguages.push_back(
-            CompileUnit->getSourceLanguage().getUnversionedName());
-      }
-    }
-    const NamedMDNode *ModuleFlags = M->getNamedMetadata("llvm.module.flags");
-    assert(ModuleFlags && "Expected llvm.module.flags metadata to be present");
-    for (const auto *Op : ModuleFlags->operands()) {
-      const MDOperand &MaybeStrOp = Op->getOperand(1);
-      if (MaybeStrOp.equalsStr("Dwarf Version"))
-        DwarfVersion =
-            cast<ConstantInt>(
-                cast<ConstantAsMetadata>(Op->getOperand(2))->getValue())
-                ->getSExtValue();
-      else if (MaybeStrOp.equalsStr("Debug Info Version"))
-        DebugInfoVersion =
-            cast<ConstantInt>(
-                cast<ConstantAsMetadata>(Op->getOperand(2))->getValue())
-                ->getSExtValue();
-    }
-
-    // This traversal is the only supported way to access
-    // instruction related DI metadata like DIBasicType
-    for (auto &F : *M) {
-      for (auto &BB : F) {
-        for (auto &I : BB) {
-          for (DbgVariableRecord &DVR : filterDbgVars(I.getDbgRecordRange())) {
-            DILocalVariable *LocalVariable = DVR.getVariable();
-            if (auto *BasicType =
-                    dyn_cast<DIBasicType>(LocalVariable->getType())) {
-              BasicTypes.insert(BasicType);
-            } else if (auto *DerivedType =
-                           dyn_cast<DIDerivedType>(LocalVariable->getType())) {
-              if (DerivedType->getTag() == dwarf::DW_TAG_pointer_type) {
-                PointerDerivedTypes.insert(DerivedType);
-                // DIBasicType can be unreachable from DbgRecord and only
-                // pointed on from other DI types
-                // DerivedType->getBaseType is null when pointer
-                // is representing a void type
-                if (auto *BT = dyn_cast_or_null<DIBasicType>(
-                        DerivedType->getBaseType()))
-                  BasicTypes.insert(BT);
-              }
-            }
-          }
-        }
-      }
-    }
-  }
-  // NonSemantic.Shader.DebugInfo.100 global DI instruction emitting
-  {
-    // Required LLVM variables for emitting logic
-    const SPIRVInstrInfo *TII = TM->getSubtargetImpl()->getInstrInfo();
-    const SPIRVRegisterInfo *TRI = TM->getSubtargetImpl()->getRegisterInfo();
-    const RegisterBankInfo *RBI = TM->getSubtargetImpl()->getRegBankInfo();
-    SPIRVGlobalRegistry *GR = TM->getSubtargetImpl()->getSPIRVGlobalRegistry();
-    MachineRegisterInfo &MRI = MF.getRegInfo();
-    MachineBasicBlock &MBB = *MF.begin();
-
-    // To correct placement of a OpLabel instruction during SPIRVAsmPrinter
-    // emission all new instructions needs to be placed after OpFunction
-    // and before first terminator
-    MachineIRBuilder MIRBuilder(MBB, MBB.getFirstTerminator());
-
-    const auto EmitOpString = [&](StringRef SR) {
-      const Register StrReg = MRI.createVirtualRegister(&SPIRV::IDRegClass);
-      MRI.setType(StrReg, LLT::scalar(32));
-      MachineInstrBuilder MIB = MIRBuilder.buildInstr(SPIRV::OpString);
-      MIB.addDef(StrReg);
-      addStringImm(SR, MIB);
-      return StrReg;
-    };
-
-    const SPIRVTypeInst VoidTy =
-        GR->getOrCreateSPIRVType(Type::getVoidTy(*Context), MIRBuilder,
-                                 SPIRV::AccessQualifier::ReadWrite, false);
-
-    const auto EmitDIInstruction =
-        [&](SPIRV::NonSemanticExtInst::NonSemanticExtInst Inst,
-            std::initializer_list<Register> Registers) {
-          const Register InstReg =
-              MRI.createVirtualRegister(&SPIRV::IDRegClass);
-          MRI.setType(InstReg, LLT::scalar(32));
-          MachineInstrBuilder MIB =
-              MIRBuilder.buildInstr(SPIRV::OpExtInst)
-                  .addDef(InstReg)
-                  .addUse(GR->getSPIRVTypeID(VoidTy))
-                  .addImm(static_cast<int64_t>(
-                      SPIRV::InstructionSet::NonSemantic_Shader_DebugInfo_100))
-                  .addImm(Inst);
-          for (auto Reg : Registers) {
-            MIB.addUse(Reg);
-          }
-          MIB.constrainAllUses(*TII, *TRI, *RBI);
-          GR->assignSPIRVTypeToVReg(VoidTy, InstReg, MF);
-          return InstReg;
-        };
-
-    const SPIRVTypeInst I32Ty =
-        GR->getOrCreateSPIRVType(Type::getInt32Ty(*Context), MIRBuilder,
-                                 SPIRV::AccessQualifier::ReadWrite, false);
-
-    const Register DwarfVersionReg =
-        GR->buildConstantInt(DwarfVersion, MIRBuilder, I32Ty, false);
-
-    const Register DebugInfoVersionReg =
-        GR->buildConstantInt(DebugInfoVersion, MIRBuilder, I32Ty, false);
-
-    for (unsigned Idx = 0; Idx < LLVMSourceLanguages.size(); ++Idx) {
-      const Register FilePathStrReg = EmitOpString(FilePaths[Idx]);
-
-      const Register DebugSourceResIdReg = EmitDIInstruction(
-          SPIRV::NonSemanticExtInst::DebugSource, {FilePathStrReg});
-
-      SourceLanguage SpirvSourceLanguage = SourceLanguage::Unknown;
-      switch (LLVMSourceLanguages[Idx]) {
-      case dwarf::DW_LANG_OpenCL:
-        SpirvSourceLanguage = SourceLanguage::OpenCL_C;
-        break;
-      case dwarf::DW_LANG_OpenCL_CPP:
-        SpirvSourceLanguage = SourceLanguage::OpenCL_CPP;
-        break;
-      case dwarf::DW_LANG_CPP_for_OpenCL:
-        SpirvSourceLanguage = SourceLanguage::CPP_for_OpenCL;
-        break;
-      case dwarf::DW_LANG_GLSL:
-        SpirvSourceLanguage = SourceLanguage::GLSL;
-        break;
-      case dwarf::DW_LANG_HLSL:
-        SpirvSourceLanguage = SourceLanguage::HLSL;
-        break;
-      case dwarf::DW_LANG_SYCL:
-        SpirvSourceLanguage = SourceLanguage::SYCL;
-        break;
-      case dwarf::DW_LANG_Zig:
-        SpirvSourceLanguage = SourceLanguage::Zig;
-      }
-
-      const Register SourceLanguageReg =
-          GR->buildConstantInt(SpirvSourceLanguage, MIRBuilder, I32Ty, false);
-
-      [[maybe_unused]]
-      const Register DebugCompUnitResIdReg =
-          EmitDIInstruction(SPIRV::NonSemanticExtInst::DebugCompilationUnit,
-                            {DebugInfoVersionReg, DwarfVersionReg,
-                             DebugSourceResIdReg, SourceLanguageReg});
-    }
-
-    // We aren't extracting any DebugInfoFlags now so we
-    // emitting zero to use as <id>Flags argument for DebugBasicType
-    const Register I32ZeroReg =
-        GR->buildConstantInt(0, MIRBuilder, I32Ty, false, false);
-
-    // We need to store pairs because further instructions reference
-    // the DIBasicTypes and size will be always small so there isn't
-    // need for any kind of map
-    SmallVector<std::pair<const DIBasicType *const, const Register>, 12>
-        BasicTypeRegPairs;
-    for (auto *BasicType : BasicTypes) {
-      const Register BasicTypeStrReg = EmitOpString(BasicType->getName());
-
-      const Register ConstIntBitwidthReg = GR->buildConstantInt(
-          BasicType->getSizeInBits(), MIRBuilder, I32Ty, false);
-
-      uint64_t AttributeEncoding = BaseTypeAttributeEncoding::Unspecified;
-      switch (BasicType->getEncoding()) {
-      case dwarf::DW_ATE_signed:
-        AttributeEncoding = BaseTypeAttributeEncoding::Signed;
-        break;
-      case dwarf::DW_ATE_unsigned:
-        AttributeEncoding = BaseTypeAttributeEncoding::Unsigned;
-        break;
-      case dwarf::DW_ATE_unsigned_char:
-        AttributeEncoding = BaseTypeAttributeEncoding::UnsignedChar;
-        break;
-      case dwarf::DW_ATE_signed_char:
-        AttributeEncoding = BaseTypeAttributeEncoding::SignedChar;
-        break;
-      case dwarf::DW_ATE_float:
-        AttributeEncoding = BaseTypeAttributeEncoding::Float;
-        break;
-      case dwarf::DW_ATE_boolean:
-        AttributeEncoding = BaseTypeAttributeEncoding::Boolean;
-        break;
-      case dwarf::DW_ATE_address:
-        AttributeEncoding = BaseTypeAttributeEncoding::Address;
-      }
-
-      const Register AttributeEncodingReg =
-          GR->buildConstantInt(AttributeEncoding, MIRBuilder, I32Ty, false);
-
-      const Register BasicTypeReg =
-          EmitDIInstruction(SPIRV::NonSemanticExtInst::DebugTypeBasic,
-                            {BasicTypeStrReg, ConstIntBitwidthReg,
-                             AttributeEncodingReg, I32ZeroReg});
-      BasicTypeRegPairs.emplace_back(BasicType, BasicTypeReg);
-    }
-
-    if (PointerDerivedTypes.size()) {
-      for (const auto *PointerDerivedType : PointerDerivedTypes) {
-
-        assert(PointerDerivedType->getDWARFAddressSpace().has_value());
-        const Register StorageClassReg = GR->buildConstantInt(
-            addressSpaceToStorageClass(
-                PointerDerivedType->getDWARFAddressSpace().value(),
-                *TM->getSubtargetImpl()),
-            MIRBuilder, I32Ty, false);
-
-        // If the Pointer is representing a void type it's getBaseType
-        // is a nullptr
-        const auto *MaybeNestedBasicType =
-            dyn_cast_or_null<DIBasicType>(PointerDerivedType->getBaseType());
-        if (MaybeNestedBasicType) {
-          for (const auto &BasicTypeRegPair : BasicTypeRegPairs) {
-            const auto &[DefinedBasicType, BasicTypeReg] = BasicTypeRegPair;
-            if (DefinedBasicType == MaybeNestedBasicType) {
-              [[maybe_unused]]
-              const Register DebugPointerTypeReg = EmitDIInstruction(
-                  SPIRV::NonSemanticExtInst::DebugTypePointer,
-                  {BasicTypeReg, StorageClassReg, I32ZeroReg});
-            }
-          }
-        } else {
-          const Register DebugInfoNoneReg =
-              EmitDIInstruction(SPIRV::NonSemanticExtInst::DebugInfoNone, {});
-          [[maybe_unused]]
-          const Register DebugPointerTypeReg = EmitDIInstruction(
-              SPIRV::NonSemanticExtInst::DebugTypePointer,
-              {DebugInfoNoneReg, StorageClassReg, I32ZeroReg});
-        }
-      }
-    }
-  }
-  return true;
-}
-
-bool SPIRVEmitNonSemanticDI::runOnMachineFunction(MachineFunction &MF) {
-  bool Res = false;
-  // emitGlobalDI needs to be executed only once to avoid
-  // emitting duplicates
-  if (!IsGlobalDIEmitted) {
-    IsGlobalDIEmitted = true;
-    Res = emitGlobalDI(MF);
-  }
-  return Res;
-}
diff --git a/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp b/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp
index 2eb84dec2cf29..652afd10f9191 100644
--- a/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp
@@ -678,6 +678,12 @@ void SPIRVModuleAnalysis::processOtherInstrs(const Module &M) {
                    MI.getOperand(2).getImm() ==
                        SPIRV::InstructionSet::
                            NonSemantic_Shader_DebugInfo_100) {
+          // TODO: This branch is dead. SPIRVNonSemanticDebugHandler emits NSDI
+          // instructions directly as MCInsts at print time; no
+          // MachineInstructions with the NonSemantic_Shader_DebugInfo_100 ext
+          // set are created anymore. Remove this block and
+          // MB_NonSemanticGlobalDI once per-function NSDI emission is confirmed
+          // not to need MIR routing.
           MachineOperand Ins = MI.getOperand(3);
           namespace NS = SPIRV::NonSemanticExtInst;
           static constexpr int64_t GlobalNonSemanticDITy[] = {
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
new file mode 100644
index 0000000000000..2d11d7fa46317
--- /dev/null
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
@@ -0,0 +1,334 @@
+//===-- 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::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.
+  MCRegister DebugInfoVersionReg = emitOpConstantI32(
+      static_cast<uint32_t>(DebugInfoVersion), I32TypeReg, MAI);
+  MCRegister DwarfVersionReg =
+      emitOpConstantI32(static_cast<uint32_t>(DwarfVersion), I32TypeReg, MAI);
+
+  // Emit DebugSource and DebugCompilationUnit for each compile unit.
+  for (const CompileUnitInfo &Info : CompileUnits) {
+    MCRegister FileStrReg = emitOpString(Info.FilePath, MAI);
+    MCRegister DebugSourceReg =
+        emitExtInst(SPIRV::NonSemanticExtInst::DebugSource, VoidTypeReg,
+                    ExtInstSetReg, {FileStrReg}, MAI);
+
+    MCRegister SrcLangReg =
+        emitOpConstantI32(Info.SpirvSourceLanguage, I32TypeReg, 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;
+
+  for (const DIBasicType *BT : BasicTypes) {
+    MCRegister NameReg = emitOpString(BT->getName(), MAI);
+    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;
+        }
+      }
+    } 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);
+    }
+  }
+}
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
new file mode 100644
index 0000000000000..ae549e8a39da8
--- /dev/null
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
@@ -0,0 +1,135 @@
+//===-- SPIRVNonSemanticDebugHandler.h - 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This file declares SPIRVNonSemanticDebugHandler, a DebugHandlerBase subclass
+// that emits NonSemantic.Shader.DebugInfo.100 instructions in the SPIR-V
+// AsmPrinter. It replaces SPIRVEmitNonSemanticDI, which was a
+// MachineFunctionPass, with a handler that controls instruction placement
+// directly instead of routing through SPIRVModuleAnalysis.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIB_TARGET_SPIRV_SPIRVNONSEMANTICDEBUGHANDLER_H
+#define LLVM_LIB_TARGET_SPIRV_SPIRVNONSEMANTICDEBUGHANDLER_H
+
+#include "MCTargetDesc/SPIRVBaseInfo.h"
+#include "SPIRVModuleAnalysis.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/SetVector.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/CodeGen/DebugHandlerBase.h"
+#include "llvm/IR/DebugInfoMetadata.h"
+#include "llvm/MC/MCInst.h"
+#include "llvm/MC/MCRegister.h"
+
+namespace llvm {
+
+class SPIRVSubtarget;
+
+/// AsmPrinter handler that emits NonSemantic.Shader.DebugInfo.100 (NSDI)
+/// instructions for the SPIR-V backend. Registered with SPIRVAsmPrinter when
+/// the module contains debug info (llvm.dbg.cu).
+///
+/// Call sequence:
+///   beginModule()                    -- collect compile-unit metadata.
+///   prepareModuleOutput()            -- add extension + ext inst set to MAI.
+///   emitNonSemanticGlobalDebugInfo() -- emit DebugSource,
+///                                       DebugCompilationUnit, DebugTypeBasic,
+///                                       DebugTypePointer.
+///   beginFunctionImpl()              -- no-op (no per-function DI yet).
+///   endFunctionImpl()                -- no-op.
+class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
+  struct CompileUnitInfo {
+    SmallString<128> FilePath;
+    unsigned SpirvSourceLanguage = 0; // NonSemantic.Shader.DebugInfo.100 source
+                                      // language code (section 4.3)
+  };
+  SmallVector<CompileUnitInfo> CompileUnits;
+  int64_t DwarfVersion = 0;
+  int64_t DebugInfoVersion = 0;
+
+  // Types referenced by debug variable records, collected in beginModule().
+  SetVector<const DIBasicType *> BasicTypes;
+  SetVector<const DIDerivedType *> PointerTypes;
+
+  // Cache of already-emitted i32 constants, keyed by value. Prevents
+  // duplicate OpConstant instructions for the same integer value.
+  DenseMap<uint32_t, MCRegister> I32ConstantCache;
+
+  // True once emitNonSemanticGlobalDebugInfo() has run. Both
+  // SPIRVAsmPrinter::emitFunctionHeader() and emitEndOfAsmFile() may call
+  // outputModuleSections(), each guarded by ModuleSectionsEmitted, so only
+  // one fires. This flag provides a secondary guard in case the call sites
+  // change.
+  bool GlobalDIEmitted = false;
+
+public:
+  explicit SPIRVNonSemanticDebugHandler(AsmPrinter &AP);
+
+  /// Collect compile-unit metadata from the module. Called by
+  /// AsmPrinter::doInitialization() via the handler list. No emission.
+  void beginModule(Module *M) override;
+
+  /// Add SPV_KHR_non_semantic_info extension and
+  /// NonSemantic.Shader.DebugInfo.100 ext inst set entry to MAI. Must be called
+  /// before outputGlobalRequirements() and outputOpExtInstImports() in
+  /// SPIRVAsmPrinter::outputModuleSections().
+  void prepareModuleOutput(const SPIRVSubtarget &ST,
+                           SPIRV::ModuleAnalysisInfo &MAI);
+
+  /// Emit module-scope NSDI instructions (DebugSource, DebugCompilationUnit,
+  /// DebugTypeBasic, DebugTypePointer). Called by SPIRVAsmPrinter::
+  /// outputModuleSections() at section 10 in place of
+  /// outputModuleSection(MB_NonSemanticGlobalDI).
+  void emitNonSemanticGlobalDebugInfo(SPIRV::ModuleAnalysisInfo &MAI);
+
+protected:
+  // All module-level output is driven by emitNonSemanticGlobalDebugInfo(),
+  // called explicitly from SPIRVAsmPrinter::outputModuleSections(). Nothing
+  // needs to happen in the AsmPrinterHandler::endModule() callback.
+  void endModule() override {}
+
+  // DebugHandlerBase stores MMI as a pointer copy from Asm->MMI at construction
+  // time (DebugHandlerBase.cpp: `MMI(Asm->MMI)`). The handler is constructed
+  // before AsmPrinter::doInitialization() runs, so Asm->MMI is null at that
+  // point and MMI remains null for this handler's entire lifetime. The
+  // base-class beginInstruction/endInstruction dereference MMI to create temp
+  // symbols for label tracking and would crash. Override them as no-ops.
+  // When per-function NSDI is implemented, use Asm->OutStreamer->getContext()
+  // for MCContext access rather than MMI->getContext().
+  void beginInstruction(const MachineInstr *MI) override {}
+  void endInstruction() override {}
+
+  // TODO: Emit DebugFunction and DebugFunctionDefinition here once per-function
+  // NSDI emission is implemented. DebugHandlerBase::beginFunction() populates
+  // LScopes and DbgValues, which are needed for DebugLine emission. Do not
+  // override beginFunction() until that work is in place.
+  void beginFunctionImpl(const MachineFunction *MF) override {}
+  // TODO: Add per-function cleanup when DebugFunction emission is in place.
+  void endFunctionImpl(const MachineFunction *MF) override {}
+
+private:
+  void emitMCInst(MCInst &Inst);
+  MCRegister emitOpString(StringRef S, SPIRV::ModuleAnalysisInfo &MAI);
+  MCRegister emitOpConstantI32(uint32_t Value, MCRegister I32TypeReg,
+                               SPIRV::ModuleAnalysisInfo &MAI);
+  MCRegister emitExtInst(SPIRV::NonSemanticExtInst::NonSemanticExtInst Opcode,
+                         MCRegister VoidTypeReg, MCRegister ExtInstSetReg,
+                         ArrayRef<MCRegister> Operands,
+                         SPIRV::ModuleAnalysisInfo &MAI);
+
+  /// Map a DWARF source language code to a NonSemantic.Shader.DebugInfo.100
+  /// source language code.
+  static unsigned toNSDISrcLang(unsigned DwarfSrcLang);
+};
+
+} // namespace llvm
+
+#endif // LLVM_LIB_TARGET_SPIRV_SPIRVNONSEMANTICDEBUGHANDLER_H
diff --git a/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp b/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp
index 6797722579137..b128d401be3fb 100644
--- a/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp
@@ -63,7 +63,6 @@ extern "C" LLVM_ABI LLVM_EXTERNAL_VISIBILITY void LLVMInitializeSPIRVTarget() {
   initializeSPIRVPostLegalizerPass(PR);
   initializeSPIRVMergeRegionExitTargetsPass(PR);
   initializeSPIRVEmitIntrinsicsPass(PR);
-  initializeSPIRVEmitNonSemanticDIPass(PR);
   initializeSPIRVPrepareFunctionsPass(PR);
   initializeSPIRVPrepareGlobalsPass(PR);
   initializeSPIRVStripConvergentIntrinsicsPass(PR);
@@ -127,7 +126,6 @@ class SPIRVPassConfig : public TargetPassConfig {
   void addOptimizedRegAlloc() override {}
 
   void addPostRegAlloc() override;
-  void addPreEmitPass() override;
 
 private:
   const SPIRVTargetMachine &TM;
@@ -263,19 +261,17 @@ bool SPIRVPassConfig::addRegBankSelect() {
   return false;
 }
 
+// Deprecated flag kept for backward compatibility. NSDI emission is now handled
+// by SPIRVNonSemanticDebugHandler, registered in SPIRVAsmPrinter::
+// doInitialization() when the module contains debug info (llvm.dbg.cu).
+// TODO: Remove this option after a deprecation period. Callers that used
+// -spv-emit-nonsemantic-debug-info should switch to -g.
 static cl::opt<bool> SPVEnableNonSemanticDI(
     "spv-emit-nonsemantic-debug-info",
     cl::desc("Deprecated. Use -g to emit SPIR-V NonSemantic.Shader.DebugInfo "
              "instructions"),
     cl::Optional, cl::init(false));
 
-void SPIRVPassConfig::addPreEmitPass() {
-  // The SPIRVEmitNonSemanticDI pass self-activates when the module contains
-  // debug info (llvm.dbg.cu). --spv-emit-nonsemantic-debug-info is a
-  // deprecated synonym for -g.
-  addPass(createSPIRVEmitNonSemanticDIPass(&getTM<SPIRVTargetMachine>()));
-}
-
 namespace {
 // A custom subclass of InstructionSelect, which is mostly the same except from
 // not requiring RegBankSelect to occur previously.
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-compilation-unit.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-compilation-unit.ll
index fd2f46590f103..ae936181fddeb 100644
--- a/llvm/test/CodeGen/SPIRV/debug-info/debug-compilation-unit.ll
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-compilation-unit.ll
@@ -1,33 +1,26 @@
-; RUN: llc --verify-machineinstrs --spv-emit-nonsemantic-debug-info --spirv-ext=+SPV_KHR_non_semantic_info --print-after=spirv-nonsemantic-debug-info -O0 -mtriple=spirv64-unknown-unknown %s -o - 2>&1 | FileCheck %s --check-prefix=CHECK-MIR
-; RUN: llc --verify-machineinstrs --spv-emit-nonsemantic-debug-info --spirv-ext=+SPV_KHR_non_semantic_info -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s --check-prefix=CHECK-SPIRV
-; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_non_semantic_info %s -o - | FileCheck %s --check-prefix=CHECK-OPTION 
-; RUN: %if spirv-tools %{ llc --verify-machineinstrs --spv-emit-nonsemantic-debug-info --spirv-ext=+SPV_KHR_non_semantic_info -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+; RUN: llc --verify-machineinstrs --spirv-ext=+SPV_KHR_non_semantic_info -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s --check-prefix=CHECK-SPIRV
+; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_non_semantic_info %s -o - | FileCheck %s --check-prefix=CHECK-OPTION
+; RUN: %if spirv-tools %{ llc --verify-machineinstrs --spirv-ext=+SPV_KHR_non_semantic_info -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
 
-; CHECK-MIR-DAG: [[type_void:%[0-9]+\:type]] = OpTypeVoid
-; CHECK-MIR-DAG: [[type_i64:%[0-9]+\:type\(s64\)]] = OpTypeInt 32, 0
-; CHECK-MIR-DAG: [[dwarf_version:%[0-9]+\:iid\(s32\)]] = OpConstantI [[type_i64]], 5
-; CHECK-MIR-DAG: [[debug_info_version:%[0-9]+\:iid\(s32\)]] = OpConstantI [[type_i64]], 3
-; CHECK-MIR-DAG: [[source_language_sycl:%[0-9]+\:iid\(s32\)]] = OpConstantI [[type_i64]], 7
-; CHECK-MIR-DAG: [[source_language_cpp:%[0-9]+\:iid\(s32\)]] = OpConstantI [[type_i64]], 4
-; CHECK-MIR-DAG: [[filename_str_sycl:%[0-9]+\:id\(s32\)]] = OpString 1094795567, 1094795585, 792805697, 1111638594, 1111638594, 1128481583, 1128481603, {{1697596227|1700545347}}, 1886216568, 1663985004, 0
-; CHECK-MIR-DAG: [[filename_str_cpp:%[0-9]+\:id\(s32\)]] = OpString 1145324591, 1145324612, 793003076, 1162167621, 1162167621, 1179010607, 1179010630, {{1697596998|1700546118}}, 1886216568, 774989164, 7368803
-; CHECK-MIR-DAG: [[debug_source_sycl:%[0-9]+\:id\(s32\)]] = OpExtInst [[type_void]], 3, 35, [[filename_str_sycl]]
-; CHECK-MIR-DAG: OpExtInst [[type_void]], 3, 1, [[debug_info_version]], [[dwarf_version]], [[debug_source_sycl]], [[source_language_sycl]]
-; CHECK-MIR-DAG: [[debug_source_cpp:%[0-9]+\:id\(s32\)]] = OpExtInst [[type_void]], 3, 35, [[filename_str_cpp]]
-; CHECK-MIR-DAG: OpExtInst [[type_void]], 3, 1, [[debug_info_version]], [[dwarf_version]], [[debug_source_cpp]], [[source_language_cpp]]
+; The old SPIRVEmitNonSemanticDI MachineFunctionPass emitted DebugSource and
+; DebugCompilationUnit instructions as MachineInstructions, which were tested via
+; --print-after=spirv-nonsemantic-debug-info (CHECK-MIR prefix).
+; SPIRVNonSemanticDebugHandler replaces that pass. It emits NSDI instructions
+; directly as MCInsts at print time, with no MIR representation. CHECK-SPIRV
+; tests the same instructions at the SPIR-V text output level.
 
 ; CHECK-SPIRV: [[ext_inst_non_semantic:%[0-9]+]] = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
-; CHECK-SPIRV: [[filename_str_sycl:%[0-9]+]] = OpString "/AAAAAAAAAA/BBBBBBBB/CCCCCCCCC{{[/\\]}}example.c"
-; CHECK-SPIRV: [[filename_str_cpp:%[0-9]+]] = OpString "/DDDDDDDDDD/EEEEEEEE/FFFFFFFFF{{[/\\]}}example1.cpp"
 ; CHECK-SPIRV-DAG: [[type_void:%[0-9]+]] = OpTypeVoid
 ; CHECK-SPIRV-DAG: [[type_i32:%[0-9]+]] = OpTypeInt 32 0
-; CHECK-SPIRV-DAG: [[dwarf_version:%[0-9]+]] = OpConstant [[type_i32]] 5
-; CHECK-SPIRV-DAG: [[source_language_sycl:%[0-9]+]] = OpConstant [[type_i32]] 7
-; CHECK-SPIRV-DAG: [[source_language_cpp:%[0-9]+]] = OpConstant [[type_i32]] 4
 ; CHECK-SPIRV-DAG: [[debug_info_version:%[0-9]+]] = OpConstant [[type_i32]] 3
+; CHECK-SPIRV-DAG: [[dwarf_version:%[0-9]+]] = OpConstant [[type_i32]] 5
+; CHECK-SPIRV: [[filename_str_sycl:%[0-9]+]] = OpString "/AAAAAAAAAA/BBBBBBBB/CCCCCCCCC{{[/\\]}}example.c"
 ; CHECK-SPIRV: [[debug_source_sycl:%[0-9]+]] = OpExtInst [[type_void]] [[ext_inst_non_semantic]] DebugSource [[filename_str_sycl]]
+; CHECK-SPIRV-DAG: [[source_language_sycl:%[0-9]+]] = OpConstant [[type_i32]] 7
 ; CHECK-SPIRV: OpExtInst [[type_void]] [[ext_inst_non_semantic]] DebugCompilationUnit [[debug_info_version]] [[dwarf_version]] [[debug_source_sycl]] [[source_language_sycl]]
+; CHECK-SPIRV: [[filename_str_cpp:%[0-9]+]] = OpString "/DDDDDDDDDD/EEEEEEEE/FFFFFFFFF{{[/\\]}}example1.cpp"
 ; CHECK-SPIRV: [[debug_source_cpp:%[0-9]+]] = OpExtInst [[type_void]] [[ext_inst_non_semantic]] DebugSource [[filename_str_cpp]]
+; CHECK-SPIRV-DAG: [[source_language_cpp:%[0-9]+]] = OpConstant [[type_i32]] 4
 ; CHECK-SPIRV: OpExtInst [[type_void]] [[ext_inst_non_semantic]] DebugCompilationUnit [[debug_info_version]] [[dwarf_version]] [[debug_source_cpp]] [[source_language_cpp]]
 
 ; CHECK-OPTION: OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-optype-void-emission.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-optype-void-emission.ll
new file mode 100644
index 0000000000000..b298297ec2c9d
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-optype-void-emission.ll
@@ -0,0 +1,27 @@
+; Verify that SPIRVNonSemanticDebugHandler emits OpTypeVoid when the module
+; has debug info but no void-returning function. Every NSDI OpExtInst uses
+; OpTypeVoid as its result-type operand; without it the output is invalid
+; SPIR-V. The spirv-val run checks for that.
+
+; RUN: llc --verify-machineinstrs --spirv-ext=+SPV_KHR_non_semantic_info -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc --verify-machineinstrs --spirv-ext=+SPV_KHR_non_semantic_info -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+
+; CHECK: [[ext_inst:%[0-9]+]] = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+; CHECK-DAG: [[type_void:%[0-9]+]] = OpTypeVoid
+; CHECK: OpExtInst [[type_void]] [[ext_inst]] DebugCompilationUnit
+
+define spir_func i32 @get_value() !dbg !5 {
+entry:
+  ret i32 42
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_HLSL, file: !1, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "shader.hlsl", directory: "/src")
+!2 = !{i32 7, !"Dwarf Version", i32 5}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = !DISubroutineType(cc: DW_CC_LLVM_SpirFunction, types: !{!9})
+!5 = distinct !DISubprogram(name: "get_value", linkageName: "get_value", scope: !1, file: !1, line: 1, type: !4, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !{})
+!9 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-source-language-glsl.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-source-language-glsl.ll
new file mode 100644
index 0000000000000..bdca7071d2975
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-source-language-glsl.ll
@@ -0,0 +1,26 @@
+; Verify that a GLSL module produces source language code 2 (GLSL) in
+; DebugCompilationUnit. The mapping is defined in the
+; NonSemantic.Shader.DebugInfo.100 specification, section 4.3.
+
+; RUN: llc --verify-machineinstrs --spirv-ext=+SPV_KHR_non_semantic_info -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc --verify-machineinstrs --spirv-ext=+SPV_KHR_non_semantic_info -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+
+; CHECK: [[ext_inst:%[0-9]+]] = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+; CHECK-DAG: [[type_void:%[0-9]+]] = OpTypeVoid
+; CHECK-DAG: [[type_i32:%[0-9]+]] = OpTypeInt 32 0
+; CHECK-DAG: [[lang_glsl:%[0-9]+]] = OpConstant [[type_i32]] 2{{$}}
+; CHECK: OpExtInst [[type_void]] [[ext_inst]] DebugCompilationUnit
+; CHECK-SAME: [[lang_glsl]]
+
+define spir_func void @main() {
+entry:
+  ret void
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_GLSL, file: !1, producer: "glslang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "shader.glsl", directory: "/src")
+!2 = !{i32 7, !"Dwarf Version", i32 5}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-source-language-unknown.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-source-language-unknown.ll
new file mode 100644
index 0000000000000..4579275de8a4a
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-source-language-unknown.ll
@@ -0,0 +1,28 @@
+; Verify that an unrecognized DWARF source language produces source language
+; code 0 (Unknown) in DebugCompilationUnit. SPIRVNonSemanticDebugHandler maps
+; any language not explicitly listed in toNSDISrcLang() to 0 rather than
+; failing. DW_LANG_C_plus_plus_14 is used here as a representative unmapped
+; language.
+
+; RUN: llc --verify-machineinstrs --spirv-ext=+SPV_KHR_non_semantic_info -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc --verify-machineinstrs --spirv-ext=+SPV_KHR_non_semantic_info -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+
+; CHECK: [[ext_inst:%[0-9]+]] = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+; CHECK-DAG: [[type_void:%[0-9]+]] = OpTypeVoid
+; CHECK-DAG: [[type_i32:%[0-9]+]] = OpTypeInt 32 0
+; CHECK-DAG: [[lang_unknown:%[0-9]+]] = OpConstant [[type_i32]] 0{{$}}
+; CHECK: OpExtInst [[type_void]] [[ext_inst]] DebugCompilationUnit
+; CHECK-SAME: [[lang_unknown]]
+
+define spir_func void @main() {
+entry:
+  ret void
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "test.cpp", directory: "/src")
+!2 = !{i32 7, !"Dwarf Version", i32 5}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-type-basic.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-type-basic.ll
index 04aa85008d47c..6cfa0ca31d813 100644
--- a/llvm/test/CodeGen/SPIRV/debug-info/debug-type-basic.ll
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-type-basic.ll
@@ -1,45 +1,26 @@
-; RUN: llc --verify-machineinstrs --spv-emit-nonsemantic-debug-info --spirv-ext=+SPV_KHR_non_semantic_info --print-after=spirv-nonsemantic-debug-info -O0 -mtriple=spirv64-unknown-unknown %s -o - 2>&1 | FileCheck %s --check-prefix=CHECK-MIR
-; RUN: llc --verify-machineinstrs --spv-emit-nonsemantic-debug-info --spirv-ext=+SPV_KHR_non_semantic_info -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s --check-prefix=CHECK-SPIRV
+; RUN: llc --verify-machineinstrs --spirv-ext=+SPV_KHR_non_semantic_info -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s --check-prefix=CHECK-SPIRV
 ; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_non_semantic_info %s -o - | FileCheck %s --check-prefix=CHECK-OPTION
-; RUN: %if spirv-tools %{ llc --verify-machineinstrs --spv-emit-nonsemantic-debug-info --spirv-ext=+SPV_KHR_non_semantic_info -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+; RUN: %if spirv-tools %{ llc --verify-machineinstrs --spirv-ext=+SPV_KHR_non_semantic_info -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
 
-; CHECK-MIR-DAG: [[type_void:%[0-9]+\:type]] = OpTypeVoid
-; CHECK-MIR-DAG: [[type_i32:%[0-9]+\:type]] = OpTypeInt 32, 0
-; CHECK-MIR-DAG: [[encoding_signedchar:%[0-9]+\:iid\(s32\)]] = OpConstantI [[type_i32]], 5
-; CHECK-MIR-DAG: [[encoding_float:%[0-9]+\:iid\(s32\)]] = OpConstantI [[type_i32]], 3
-; CHECK-MIR-DAG: [[flag_zero:%[0-9]+\:iid\(s32\)]] = OpConstantI [[type_i32]], 0
-; CHECK-MIR-DAG: [[str_bool:%[0-9]+\:id\(s32\)]] = OpString 1819242338, 0
-; CHECK-MIR-DAG: [[size_8bits:%[0-9]+\:iid\(s32\)]] = OpConstantI [[type_i32]], 8
-; CHECK-MIR-DAG: [[encoding_boolean:%[0-9]+\:iid\(s32\)]] = OpConstantI [[type_i32]], 2
-; CHECK-MIR-DAG: OpExtInst [[type_void]], 3, 2, [[str_bool]], [[size_8bits]], [[encoding_boolean]], [[flag_zero]]
-; CHECK-MIR-DAG: [[str_int:%[0-9]+\:id\(s32\)]] = OpString 7630441
-; CHECK-MIR-DAG: [[size_32bits:%[0-9]+\:iid\(s32\)]] = OpConstantI [[type_i32]], 32
-; CHECK-MIR-DAG: [[encoding_signed:%[0-9]+\:iid\(s32\)]] = OpConstantI [[type_i32]], 4
-; CHECK-MIR-DAG: OpExtInst [[type_void]], 3, 2, [[str_int]], [[size_32bits]], [[encoding_signed]], [[flag_zero]]
-; CHECK-MIR-DAG: [[str_short:%[0-9]+\:id\(s32\)]] = OpString 1919903859, 116
-; CHECK-MIR-DAG: [[size_16bits:%[0-9]+\:iid\(s32\)]] = OpConstantI [[type_i32]], 16
-; CHECK-MIR-DAG: OpExtInst [[type_void]], 3, 2, [[str_short]], [[size_16bits]], [[encoding_signed]], [[flag_zero]]
-; CHECK-MIR-DAG: [[str_char:%[0-9]+\:id\(s32\)]] = OpString 1918986339, 0
-; CHECK-MIR-DAG: OpExtInst [[type_void]], 3, 2, [[str_char]], [[size_8bits]], [[encoding_signedchar]], [[flag_zero]]
-; CHECK-MIR-DAG: [[str_long:%[0-9]+\:id\(s32\)]] = OpString 1735290732, 0
-; CHECK-MIR-DAG: [[size_64bits:%[0-9]+\:iid\(s32\)]] = OpConstantI [[type_i32]], 64
-; CHECK-MIR-DAG: OpExtInst [[type_void]], 3, 2, [[str_long]], [[size_64bits]], [[encoding_signed]], [[flag_zero]]
-; CHECK-MIR-DAG: [[str_uint:%[0-9]+\:id\(s32\)]] = OpString 1769172597, 1684368999, 1953392928, 0
-; CHECK-MIR-DAG: [[encoding_unsigned:%[0-9]+\:iid\(s32\)]] = OpConstantI [[type_i32]], 6
-; CHECK-MIR-DAG: OpExtInst [[type_void]], 3, 2, [[str_uint]], [[size_32bits]], [[encoding_unsigned]], [[flag_zero]]
-; CHECK-MIR-DAG: [[str_ushort:%[0-9]+\:id\(s32\)]] = OpString 1769172597, 1684368999, 1869116192, 29810
-; CHECK-MIR-DAG: OpExtInst [[type_void]], 3, 2, [[str_ushort]], [[size_16bits]], [[encoding_unsigned]], [[flag_zero]]
-; CHECK-MIR-DAG: [[str_uchar:%[0-9]+\:id\(s32\)]] = OpString 1769172597, 1684368999, 1634231072, 114
-; CHECK-MIR-DAG: [[encoding_unsignedchar:%[0-9]+\:iid\(s32\)]] = OpConstantI [[type_i32]], 7
-; CHECK-MIR-DAG: OpExtInst [[type_void]], 3, 2, [[str_uchar]], [[size_8bits]], [[encoding_unsignedchar]], [[flag_zero]]
-; CHECK-MIR-DAG: [[str_ulong:%[0-9]+\:id\(s32\)]] = OpString 1769172597, 1684368999, 1852795936, 103
-; CHECK-MIR-DAG: OpExtInst [[type_void]], 3, 2, [[str_ulong]], [[size_64bits]], [[encoding_unsigned]], [[flag_zero]]
-; CHECK-MIR-DAG: [[str_float:%[0-9]+\:id\(s32\)]] = OpString 1634692198, 116
-; CHECK-MIR-DAG: OpExtInst [[type_void]], 3, 2, [[str_float]], [[size_32bits]], [[encoding_float]], [[flag_zero]]
-; CHECK-MIR-DAG: [[str_double:%[0-9]+\:id\(s32\)]] = OpString 1651863396, 25964
-; CHECK-MIR-DAG: OpExtInst [[type_void]], 3, 2, [[str_double]], [[size_64bits]], [[encoding_float]], [[flag_zero]]
+; The old SPIRVEmitNonSemanticDI MachineFunctionPass emitted DebugTypeBasic
+; instructions as MachineInstructions, which were tested via
+; --print-after=spirv-nonsemantic-debug-info (CHECK-MIR prefix).
+; SPIRVNonSemanticDebugHandler replaces that pass. It emits NSDI instructions
+; directly as MCInsts at print time, with no MIR representation. CHECK-SPIRV
+; tests the same instructions at the SPIR-V text output level.
 
+; All CHECK-SPIRV-DAG directives form a single group anchored at OpExtInstImport.
+; OpTypeVoid and OpTypeInt 32 0 are emitted before the OpString/OpExtInst block,
+; so they must be in the same group as the string and DebugTypeBasic checks rather
+; than after a NOT fence (which would advance the scan position past them).
 ; CHECK-SPIRV: [[ext_inst_non_semantic:%[0-9]+]] = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+; CHECK-SPIRV-DAG: [[type_void:%[0-9]+]] = OpTypeVoid
+; CHECK-SPIRV-DAG: [[type_float64:%[0-9]+]] = OpTypeFloat 64
+; CHECK-SPIRV-DAG: [[type_float32:%[0-9]+]] = OpTypeFloat 32
+; CHECK-SPIRV-DAG: [[type_int64:%[0-9]+]] = OpTypeInt 64 0
+; CHECK-SPIRV-DAG: [[type_int8:%[0-9]+]] = OpTypeInt 8 0
+; CHECK-SPIRV-DAG: [[type_int16:%[0-9]+]] = OpTypeInt 16 0
+; CHECK-SPIRV-DAG: [[type_int32:%[0-9]+]] = OpTypeInt 32 0
 ; CHECK-SPIRV-DAG: [[str_bool:%[0-9]+]] = OpString "bool"
 ; CHECK-SPIRV-DAG: [[str_int:%[0-9]+]] = OpString "int"
 ; CHECK-SPIRV-DAG: [[str_short:%[0-9]+]] = OpString "short"
@@ -51,15 +32,10 @@
 ; CHECK-SPIRV-DAG: [[str_ulong:%[0-9]+]] = OpString "unsigned long"
 ; CHECK-SPIRV-DAG: [[str_float:%[0-9]+]] = OpString "float"
 ; CHECK-SPIRV-DAG: [[str_double:%[0-9]+]] = OpString "double"
-; CHECK-SPIRV-NOT: ----------------
-; CHECK-SPIRV-DAG: [[type_void:%[0-9]+]] = OpTypeVoid
-; CHECK-SPIRV-DAG: [[type_float64:%[0-9]+]] = OpTypeFloat 64
-; CHECK-SPIRV-DAG: [[type_float32:%[0-9]+]] = OpTypeFloat 32
-; CHECK-SPIRV-DAG: [[type_int64:%[0-9]+]] = OpTypeInt 64 0
-; CHECK-SPIRV-DAG: [[type_int8:%[0-9]+]] = OpTypeInt 8 0
-; CHECK-SPIRV-DAG: [[type_int16:%[0-9]+]] = OpTypeInt 16 0
-; CHECK-SPIRV-DAG: [[type_int32:%[0-9]+]] = OpTypeInt 32 0
 ; CHECK-SPIRV-DAG: [[encoding_signedchar:%[0-9]+]] = OpConstant [[type_int32]] 5{{$}}
+; The Flags operand of DebugTypeBasic is an integer bitmask. OpConstant with an
+; explicit value is the canonical form for integer constants in NSDI, consistent
+; with other NSDI producers such as DXC and glslang. OpConstantNull is not used.
 ; CHECK-SPIRV-DAG: [[flag_zero:%[0-9]+]] = OpConstant [[type_int32]] 0{{$}}
 ; CHECK-SPIRV-DAG: [[encoding_float:%[0-9]+]] = OpConstant [[type_int32]] 3{{$}}
 ; CHECK-SPIRV-DAG: [[size_8bit:%[0-9]+]] = OpConstant [[type_int32]] 8{{$}}
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-type-pointer.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-type-pointer.ll
index f2d7440ae7a65..030542bad16b1 100644
--- a/llvm/test/CodeGen/SPIRV/debug-info/debug-type-pointer.ll
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-type-pointer.ll
@@ -1,52 +1,30 @@
-; RUN: llc --verify-machineinstrs --spv-emit-nonsemantic-debug-info --spirv-ext=+SPV_KHR_non_semantic_info --print-after=spirv-nonsemantic-debug-info -O0 -mtriple=spirv64-unknown-unknown %s -o - 2>&1 | FileCheck %s --check-prefix=CHECK-MIR
-; RUN: llc --verify-machineinstrs --print-after=spirv-nonsemantic-debug-info -O0 -mtriple=spirv64-amd-amdhsa %s -o - 2>&1 | FileCheck %s --check-prefix=CHECK-MIR
-; RUN: llc --verify-machineinstrs --spv-emit-nonsemantic-debug-info --spirv-ext=+SPV_KHR_non_semantic_info -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s --check-prefix=CHECK-SPIRV
+; RUN: llc --verify-machineinstrs --spirv-ext=+SPV_KHR_non_semantic_info -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s --check-prefix=CHECK-SPIRV
 ; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-amd-amdhsa %s -o - | FileCheck %s --check-prefix=CHECK-SPIRV
 ; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_non_semantic_info %s -o - | FileCheck %s --check-prefix=CHECK-OPTION
 ; TODO(#109287): When type is void * the spirv-val raises an error when DebugInfoNone is set as <id> Base Type argument of DebugTypePointer.
-; DISABLED: %if spirv-tools %{ llc --verify-machineinstrs --spv-emit-nonsemantic-debug-info --spirv-ext=+SPV_KHR_non_semantic_info -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+; DISABLED: %if spirv-tools %{ llc --verify-machineinstrs --spirv-ext=+SPV_KHR_non_semantic_info -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
 
-; CHECK-MIR-DAG:   [[i32type:%[0-9]+\:type]] = OpTypeInt 32, 0
-; CHECK-MIR-DAG:   [[void_type:%[0-9]+\:type\(s64\)]] = OpTypeVoid
-; CHECK-MIR-DAG:   [[i32_8:%[0-9]+\:iid]] = OpConstantI [[i32type]], 8{{$}}
-; CHECK-MIR-DAG:   [[i32_0:%[0-9]+\:iid(\(s32\))?]] = OpConstantNull [[i32type]]
-; CHECK-MIR-DAG:   [[i32_5:%[0-9]+\:iid\(s32\)]] = OpConstantI [[i32type]], 5{{$}}
-; CHECK-MIR-DAG:   [[enc_float:%[0-9]+\:iid\(s32\)]] = OpConstantI [[i32type]], 3{{$}}
-; CHECK-MIR-DAG:   [[enc_boolean:%[0-9]+\:iid\(s32\)]] = OpConstantI [[i32type]], 2{{$}}
-; CHECK-MIR-DAG:   [[bool:%[0-9]+\:id\(s32\)]] = OpExtInst [[void_type]], 3, 2, {{%[0-9]+\:[a-z0-9\(\)]+}}, [[i32_8]], [[enc_boolean]], [[i32_0]]
-; CHECK-MIR-DAG:   [[i32_16:%[0-9]+\:iid\(s32\)]] = OpConstantI [[i32type]], 16{{$}}
-; CHECK-MIR-DAG:   [[enc_signed:%[0-9]+\:iid\(s32\)]] = OpConstantI [[i32type]], 4{{$}}
-; CHECK-MIR-DAG:   [[short:%[0-9]+\:id\(s32\)]] = OpExtInst [[void_type]], 3, 2, {{%[0-9]+\:[a-z0-9\(\)]+}}, [[i32_16]], [[enc_signed]], [[i32_0]]
-; CHECK-MIR-DAG:   [[char:%[0-9]+\:id\(s32\)]] = OpExtInst [[void_type]], 3, 2, {{%[0-9]+\:[a-z0-9\(\)]+}}, [[i32_8]], [[i32_5]], [[i32_0]]
-; CHECK-MIR-DAG:   [[i32_64:%[0-9]+\:iid\(s32\)]] = OpConstantI [[i32type]], 64{{$}}
-; CHECK-MIR-DAG:   [[long:%[0-9]+\:id\(s32\)]] = OpExtInst [[void_type]], 3, 2, {{%[0-9]+\:[a-z0-9\(\)]+}}, [[i32_64]], [[enc_signed]], [[i32_0]]
-; CHECK-MIR-DAG:   [[i32_32:%[0-9]+\:iid\(s32\)]] = OpConstantI [[i32type]], 32{{$}}
-; CHECK-MIR-DAG:   [[enc_unsigned:%[0-9]+\:iid\(s32\)]] = OpConstantI [[i32type]], 6{{$}}
-; CHECK-MIR-DAG:   [[unsigned_int:%[0-9]+\:id\(s32\)]] = OpExtInst [[void_type]], 3, 2, {{%[0-9]+\:[a-z0-9\(\)]+}}, [[i32_32]], [[enc_unsigned]], [[i32_0]]
-; CHECK-MIR-DAG:   [[unsigned_short:%[0-9]+\:id\(s32\)]] = OpExtInst [[void_type]], 3, 2, {{%[0-9]+\:[a-z0-9\(\)]+}}, [[i32_16]], [[enc_unsigned]], [[i32_0]]
-; CHECK-MIR-DAG:   [[enc_unsigned_char:%[0-9]+\:iid\(s32\)]] = OpConstantI [[i32type]], 7{{$}}
-; CHECK-MIR-DAG:   [[unsigned_char:%[0-9]+\:id\(s32\)]] = OpExtInst [[void_type]], 3, 2, {{%[0-9]+\:[a-z0-9\(\)]+}}, [[i32_8]], [[enc_unsigned_char]], [[i32_0]]
-; CHECK-MIR-DAG:   [[unsigned_long:%[0-9]+\:id\(s32\)]] = OpExtInst [[void_type]], 3, 2, {{%[0-9]+\:[a-z0-9\(\)]+}}, [[i32_64]], [[enc_unsigned]], [[i32_0]]
-; CHECK-MIR-DAG:   [[float:%[0-9]+\:id\(s32\)]] = OpExtInst [[void_type]], 3, 2, {{%[0-9]+\:[a-z0-9\(\)]+}}, [[i32_32]], [[enc_float]], [[i32_0]]
-; CHECK-MIR-DAG:   [[double:%[0-9]+\:id\(s32\)]] = OpExtInst [[void_type]], 3, 2, {{%[0-9]+\:[a-z0-9\(\)]+}}, [[i32_64]], [[enc_float]], [[i32_0]]
-; CHECK-MIR-DAG:   [[int:%[0-9]+\:id\(s32\)]] = OpExtInst [[void_type]], 3, 2, {{%[0-9]+\:[a-z0-9\(\)]+}}, [[i32_32]], [[enc_signed]], [[i32_0]]
-; CHECK-MIR:   OpExtInst [[void_type]], 3, 3, [[bool]], [[i32_8]], [[i32_0]]
-; CHECK-MIR:   OpExtInst [[void_type]], 3, 3, [[short]], [[i32_8]], [[i32_0]]
-; CHECK-MIR:   OpExtInst [[void_type]], 3, 3, [[char]], [[i32_8]], [[i32_0]]
-; CHECK-MIR:   OpExtInst [[void_type]], 3, 3, [[long]], [[i32_8]], [[i32_0]]
-; CHECK-MIR:   OpExtInst [[void_type]], 3, 3, [[unsigned_int]], [[i32_8]], [[i32_0]]
-; CHECK-MIR:   OpExtInst [[void_type]], 3, 3, [[unsigned_short]], [[i32_8]], [[i32_0]]
-; CHECK-MIR:   OpExtInst [[void_type]], 3, 3, [[unsigned_char]], [[i32_8]], [[i32_0]]
-; CHECK-MIR:   OpExtInst [[void_type]], 3, 3, [[unsigned_long]], [[i32_8]], [[i32_0]]
-; CHECK-MIR:   OpExtInst [[void_type]], 3, 3, [[float]], [[i32_8]], [[i32_0]]
-; CHECK-MIR:   OpExtInst [[void_type]], 3, 3, [[double]], [[i32_8]], [[i32_0]]
-; CHECK-MIR:   OpExtInst [[void_type]], 3, 3, [[int]], [[i32_5]], [[i32_0]]
-; CHECK-MIR:   [[debug_info_none:%[0-9]+\:id\(s32\)]] = OpExtInst [[void_type]], 3, 0
-; CHECK-MIR:   OpExtInst [[void_type]], 3, 3, [[debug_info_none]], [[i32_5]], [[i32_0]]
+; The old SPIRVEmitNonSemanticDI MachineFunctionPass emitted DebugTypeBasic and
+; DebugTypePointer instructions as MachineInstructions, which were tested via
+; --print-after=spirv-nonsemantic-debug-info (CHECK-MIR prefix).
+; SPIRVNonSemanticDebugHandler replaces that pass. It emits NSDI instructions
+; directly as MCInsts at print time, with no MIR representation. CHECK-SPIRV
+; tests the same instructions at the SPIR-V text output level.
 
+; The Flags operand of DebugTypeBasic and DebugTypePointer is an integer bitmask.
+; SPIRVNonSemanticDebugHandler emits it as OpConstant (not OpConstantNull) because
+; OpConstant with an explicit value is the canonical form for integer constants in
+; NSDI, consistent with other NSDI producers such as DXC and glslang.
+
+; Anchor on OpTypeVoid rather than OpTypeInt 32 0. The module may already contain
+; OpConstant i32 N instructions (e.g. for array dimensions) before the NSDI section,
+; which have the same pattern as the NSDI-emitted constants. Anchoring after
+; OpTypeVoid (emitted immediately before the NSDI constants) ensures the DAG group
+; binds [[i32_8]] etc. to the NSDI constants rather than earlier module constants.
 ; CHECK-SPIRV:	[[i32type:%[0-9]+]] = OpTypeInt 32 0
+; CHECK-SPIRV:	[[void_type:%[0-9]+]] = OpTypeVoid
 ; CHECK-SPIRV-DAG:	[[i32_8:%[0-9]+]] = OpConstant [[i32type]] 8{{$}}
-; CHECK-SPIRV-DAG:	[[i32_0:%[0-9]+]] = OpConstantNull [[i32type]]{{$}}
+; CHECK-SPIRV-DAG:	[[i32_0:%[0-9]+]] = OpConstant [[i32type]] 0{{$}}
 ; CHECK-SPIRV-DAG:	[[i32_5:%[0-9]+]] = OpConstant [[i32type]] 5{{$}}
 ; CHECK-SPIRV-DAG:	[[enc_float:%[0-9]+]] = OpConstant [[i32type]] 3{{$}}
 ; CHECK-SPIRV-DAG:	[[enc_boolean:%[0-9]+]] = OpConstant [[i32type]] 2{{$}}
diff --git a/llvm/test/CodeGen/SPIRV/llc-pipeline.ll b/llvm/test/CodeGen/SPIRV/llc-pipeline.ll
index 81d7c1b985eb4..6955aca246ed8 100644
--- a/llvm/test/CodeGen/SPIRV/llc-pipeline.ll
+++ b/llvm/test/CodeGen/SPIRV/llc-pipeline.ll
@@ -81,7 +81,6 @@
 ; SPIRV-O0-NEXT:      Analyze Machine Code For Garbage Collection
 ; SPIRV-O0-NEXT:      Insert fentry calls
 ; SPIRV-O0-NEXT:      Insert XRay ops
-; SPIRV-O0-NEXT:      SPIRV NonSemantic.Shader.DebugInfo.100 emitter
 ; SPIRV-O0-NEXT:      Machine Sanitizer Binary Metadata
 ; SPIRV-O0-NEXT:      Lazy Machine Block Frequency Analysis
 ; SPIRV-O0-NEXT:      Machine Optimization Remark Emitter
@@ -219,7 +218,6 @@
 ; SPIRV-Opt-NEXT:      Analyze Machine Code For Garbage Collection
 ; SPIRV-Opt-NEXT:      Insert fentry calls
 ; SPIRV-Opt-NEXT:      Insert XRay ops
-; SPIRV-Opt-NEXT:      SPIRV NonSemantic.Shader.DebugInfo.100 emitter
 ; SPIRV-Opt-NEXT:      Machine Sanitizer Binary Metadata
 ; SPIRV-Opt-NEXT:      Lazy Machine Block Frequency Analysis
 ; SPIRV-Opt-NEXT:      Machine Optimization Remark Emitter

>From 206612b8f20588134c2a7e6305bd3eafd601af17 Mon Sep 17 00:00:00 2001
From: Diego Novillo <dnovillo at nvidia.com>
Date: Thu, 9 Apr 2026 14:01:31 -0400
Subject: [PATCH 2/4] [SPIRV] Fix OpString section placement and ID bound in
 NSDI handler

OpString was emitted in the wrong SPIR-V module section (SPIR-V spec section 2.4). Split it into emitNonSemanticDebugStrings(), called from outputDebugSourceAndStrings() at section 7, caching the result registers for use in emitNonSemanticGlobalDebugInfo().

SPIRVModuleAnalysis sets the ID bound before the handler runs. Refresh GR->Bound from MAI->MaxID in emitEndOfAsmFile() after outputModuleSections() so handler-allocated IDs are counted.
---
 llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp     | 13 +++++
 .../SPIRV/SPIRVNonSemanticDebugHandler.cpp    | 50 ++++++++++++++++---
 .../SPIRV/SPIRVNonSemanticDebugHandler.h      | 16 ++++++
 .../debug-info/debug-compilation-unit.ll      | 12 +++--
 4 files changed, 80 insertions(+), 11 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp b/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp
index 66e4bded32882..8a34a8b59882c 100644
--- a/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp
@@ -129,6 +129,12 @@ void SPIRVAsmPrinter::emitEndOfAsmFile(Module &M) {
   }
 
   ST = static_cast<const SPIRVTargetMachine &>(TM).getSubtargetImpl();
+  // SPIRVModuleAnalysis sets GR->Bound = MAI->MaxID before printing. Any IDs
+  // allocated by AsmPrinter handlers (e.g. SPIRVNonSemanticDebugHandler) during
+  // outputModuleSections() are not counted. Refresh the bound here so the
+  // formula below sees the final allocation count.
+  if (MAI)
+    ST->getSPIRVGlobalRegistry()->setBound(MAI->MaxID);
   VersionTuple SPIRVVersion = ST->getSPIRVVersion();
   uint32_t Major = SPIRVVersion.getMajor();
   uint32_t Minor = SPIRVVersion.getMinor().value_or(0);
@@ -330,6 +336,13 @@ void SPIRVAsmPrinter::outputDebugSourceAndStrings(const Module &M) {
   Inst.addOperand(
       MCOperand::createImm(static_cast<unsigned>(MAI->SrcLangVersion)));
   outputMCInst(Inst);
+  // Emit OpString instructions for NSDI file paths and type names here, in
+  // section 7. OpString must precede type/constant declarations per the SPIR-V
+  // module layout (section 2.4). The OpExtInst instructions that reference
+  // these strings are emitted later at section 10 by
+  // emitNonSemanticGlobalDebugInfo().
+  if (NSDebugHandler)
+    NSDebugHandler->emitNonSemanticDebugStrings(*MAI);
 }
 
 void SPIRVAsmPrinter::outputOpExtInstImports(const Module &M) {
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
index 2d11d7fa46317..0420503993901 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
@@ -177,6 +177,25 @@ MCRegister SPIRVNonSemanticDebugHandler::emitExtInst(
   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())
@@ -229,21 +248,32 @@ void SPIRVNonSemanticDebugHandler::emitNonSemanticGlobalDebugInfo(
 
   // 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.
+  // 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.
-  for (const CompileUnitInfo &Info : CompileUnits) {
-    MCRegister FileStrReg = emitOpString(Info.FilePath, MAI);
+  // 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);
-
-    MCRegister SrcLangReg =
-        emitOpConstantI32(Info.SpirvSourceLanguage, I32TypeReg, MAI);
     emitExtInst(
         SPIRV::NonSemanticExtInst::DebugCompilationUnit, VoidTypeReg,
         ExtInstSetReg,
@@ -259,8 +289,14 @@ void SPIRVNonSemanticDebugHandler::emitNonSemanticGlobalDebugInfo(
   // 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 = emitOpString(BT->getName(), MAI);
+    MCRegister NameReg = BasicTypeNameRegs[BTIdx++];
     MCRegister SizeReg = emitOpConstantI32(
         static_cast<uint32_t>(BT->getSizeInBits()), I32TypeReg, MAI);
 
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
index ae549e8a39da8..09c6a68957d82 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
@@ -63,6 +63,14 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
   // duplicate OpConstant instructions for the same integer value.
   DenseMap<uint32_t, MCRegister> I32ConstantCache;
 
+  // OpString registers for NSDI instructions, populated by
+  // emitNonSemanticDebugStrings() (section 7) and consumed by
+  // emitNonSemanticGlobalDebugInfo() (section 10). OpString must appear in
+  // section 7 per the SPIR-V module layout; it cannot be emitted alongside the
+  // OpExtInst instructions in section 10.
+  SmallVector<MCRegister> FileStringRegs;    // one per CompileUnits entry
+  SmallVector<MCRegister> BasicTypeNameRegs; // one per BasicTypes entry
+
   // True once emitNonSemanticGlobalDebugInfo() has run. Both
   // SPIRVAsmPrinter::emitFunctionHeader() and emitEndOfAsmFile() may call
   // outputModuleSections(), each guarded by ModuleSectionsEmitted, so only
@@ -77,6 +85,14 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
   /// AsmPrinter::doInitialization() via the handler list. No emission.
   void beginModule(Module *M) override;
 
+  /// Emit OpString instructions for all NSDI file paths and basic type names
+  /// into the debug section (section 7 of the SPIR-V module layout). Must be
+  /// called from SPIRVAsmPrinter::outputDebugSourceAndStrings(), after
+  /// prepareModuleOutput() has registered the ext inst set. The resulting
+  /// registers are cached in FileStringRegs and BasicTypeNameRegs for use by
+  /// emitNonSemanticGlobalDebugInfo().
+  void emitNonSemanticDebugStrings(SPIRV::ModuleAnalysisInfo &MAI);
+
   /// Add SPV_KHR_non_semantic_info extension and
   /// NonSemantic.Shader.DebugInfo.100 ext inst set entry to MAI. Must be called
   /// before outputGlobalRequirements() and outputOpExtInstImports() in
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-compilation-unit.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-compilation-unit.ll
index ae936181fddeb..9de595b302f59 100644
--- a/llvm/test/CodeGen/SPIRV/debug-info/debug-compilation-unit.ll
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-compilation-unit.ll
@@ -10,17 +10,21 @@
 ; tests the same instructions at the SPIR-V text output level.
 
 ; CHECK-SPIRV: [[ext_inst_non_semantic:%[0-9]+]] = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+; OpString is in section 7 (before types/constants), so it appears before OpTypeVoid.
+; CHECK-SPIRV: [[filename_str_sycl:%[0-9]+]] = OpString "/AAAAAAAAAA/BBBBBBBB/CCCCCCCCC{{[/\\]}}example.c"
+; CHECK-SPIRV: [[filename_str_cpp:%[0-9]+]] = OpString "/DDDDDDDDDD/EEEEEEEE/FFFFFFFFF{{[/\\]}}example1.cpp"
+; Types and constants appear after OpString (sections 9+) but before DebugSource.
+; Source language constants are pre-emitted before the DebugSource loop, so they
+; can be matched here as a DAG group together with the types.
 ; CHECK-SPIRV-DAG: [[type_void:%[0-9]+]] = OpTypeVoid
 ; CHECK-SPIRV-DAG: [[type_i32:%[0-9]+]] = OpTypeInt 32 0
 ; CHECK-SPIRV-DAG: [[debug_info_version:%[0-9]+]] = OpConstant [[type_i32]] 3
 ; CHECK-SPIRV-DAG: [[dwarf_version:%[0-9]+]] = OpConstant [[type_i32]] 5
-; CHECK-SPIRV: [[filename_str_sycl:%[0-9]+]] = OpString "/AAAAAAAAAA/BBBBBBBB/CCCCCCCCC{{[/\\]}}example.c"
-; CHECK-SPIRV: [[debug_source_sycl:%[0-9]+]] = OpExtInst [[type_void]] [[ext_inst_non_semantic]] DebugSource [[filename_str_sycl]]
 ; CHECK-SPIRV-DAG: [[source_language_sycl:%[0-9]+]] = OpConstant [[type_i32]] 7
+; CHECK-SPIRV-DAG: [[source_language_cpp:%[0-9]+]] = OpConstant [[type_i32]] 4
+; CHECK-SPIRV: [[debug_source_sycl:%[0-9]+]] = OpExtInst [[type_void]] [[ext_inst_non_semantic]] DebugSource [[filename_str_sycl]]
 ; CHECK-SPIRV: OpExtInst [[type_void]] [[ext_inst_non_semantic]] DebugCompilationUnit [[debug_info_version]] [[dwarf_version]] [[debug_source_sycl]] [[source_language_sycl]]
-; CHECK-SPIRV: [[filename_str_cpp:%[0-9]+]] = OpString "/DDDDDDDDDD/EEEEEEEE/FFFFFFFFF{{[/\\]}}example1.cpp"
 ; CHECK-SPIRV: [[debug_source_cpp:%[0-9]+]] = OpExtInst [[type_void]] [[ext_inst_non_semantic]] DebugSource [[filename_str_cpp]]
-; CHECK-SPIRV-DAG: [[source_language_cpp:%[0-9]+]] = OpConstant [[type_i32]] 4
 ; CHECK-SPIRV: OpExtInst [[type_void]] [[ext_inst_non_semantic]] DebugCompilationUnit [[debug_info_version]] [[dwarf_version]] [[debug_source_cpp]] [[source_language_cpp]]
 
 ; CHECK-OPTION: OpExtInstImport "NonSemantic.Shader.DebugInfo.100"

>From d2cfa6e8912f5aeb7781cea26a10a19b9c49fb78 Mon Sep 17 00:00:00 2001
From: Diego Novillo <dnovillo at nvidia.com>
Date: Mon, 13 Apr 2026 03:51:03 -0400
Subject: [PATCH 3/4] Address review feedback.

---
 llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp     |   3 +-
 .../SPIRV/SPIRVNonSemanticDebugHandler.cpp    | 156 +++++++++---------
 .../SPIRV/SPIRVNonSemanticDebugHandler.h      |  19 +++
 .../SPIRV/debug-info/debug-type-pointer.ll    |  12 --
 4 files changed, 102 insertions(+), 88 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp b/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp
index 8a34a8b59882c..3f10d52a61b0e 100644
--- a/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp
@@ -107,7 +107,8 @@ class SPIRVAsmPrinter : public AsmPrinter {
   SPIRV::ModuleAnalysisInfo *MAI;
 
   // Non-owning pointer to the NSDI handler registered via addAsmPrinterHandler.
-  // Set in doInitialization() when the module has debug info.
+  // The handler's lifetime is managed by AsmPrinter (the base class of this
+  // object), so this pointer cannot dangle.
   SPIRVNonSemanticDebugHandler *NSDebugHandler = nullptr;
 
 protected:
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
index 0420503993901..1b52ac7990276 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
@@ -11,6 +11,7 @@
 #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"
@@ -177,6 +178,75 @@ MCRegister SPIRVNonSemanticDebugHandler::emitExtInst(
   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())
@@ -209,42 +279,8 @@ void SPIRVNonSemanticDebugHandler::emitNonSemanticGlobalDebugInfo(
   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);
-  }
+  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
@@ -259,11 +295,10 @@ void SPIRVNonSemanticDebugHandler::emitNonSemanticGlobalDebugInfo(
 
   // 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));
+  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.
@@ -287,7 +322,7 @@ void SPIRVNonSemanticDebugHandler::emitNonSemanticGlobalDebugInfo(
 
   // Maps each DIBasicType to its DebugTypeBasic result register for use as
   // operands in DebugTypePointer instructions.
-  SmallVector<std::pair<const DIBasicType *, MCRegister>> BasicTypeRegs;
+  DenseMap<const DIBasicType *, MCRegister> BasicTypeRegs;
 
   // BasicTypeNameRegs was populated by emitNonSemanticDebugStrings() in
   // section 7.
@@ -331,40 +366,11 @@ void SPIRVNonSemanticDebugHandler::emitNonSemanticGlobalDebugInfo(
     MCRegister BTReg = emitExtInst(
         SPIRV::NonSemanticExtInst::DebugTypeBasic, VoidTypeReg, ExtInstSetReg,
         {NameReg, SizeReg, EncodingReg, I32ZeroReg}, MAI);
-    BasicTypeRegs.emplace_back(BT, BTReg);
+    BasicTypeRegs[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;
-        }
-      }
-    } 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);
-    }
-  }
+  for (const DIDerivedType *PT : PointerTypes)
+    emitDebugTypePointer(PT, VoidTypeReg, I32TypeReg, ExtInstSetReg,
+                         I32ZeroReg, BasicTypeRegs, MAI);
 }
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
index 09c6a68957d82..300e573d3cc5a 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
@@ -141,6 +141,25 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
                          ArrayRef<MCRegister> Operands,
                          SPIRV::ModuleAnalysisInfo &MAI);
 
+  /// Find OpTypeVoid in the already-emitted TypeConstVars section, or emit one
+  /// if the module does not contain it (e.g. no void-returning functions).
+  MCRegister findOrEmitOpTypeVoid(SPIRV::ModuleAnalysisInfo &MAI);
+
+  /// Find OpTypeInt 32 0 in the already-emitted TypeConstVars section, or emit
+  /// one if the module does not contain it.
+  MCRegister findOrEmitOpTypeInt32(SPIRV::ModuleAnalysisInfo &MAI);
+
+  /// Emit a DebugTypePointer instruction for PT. Skips pointer types that do
+  /// not carry a DWARF address space. For pointers whose base type is a
+  /// DIBasicType, looks up the base type's DebugTypeBasic register in
+  /// BasicTypeRegs. All other pointers (void pointers and pointers whose base
+  /// type is not a DIBasicType) use DebugInfoNone as the base type operand.
+  void emitDebugTypePointer(
+      const DIDerivedType *PT, MCRegister VoidTypeReg, MCRegister I32TypeReg,
+      MCRegister ExtInstSetReg, MCRegister I32ZeroReg,
+      const DenseMap<const DIBasicType *, MCRegister> &BasicTypeRegs,
+      SPIRV::ModuleAnalysisInfo &MAI);
+
   /// Map a DWARF source language code to a NonSemantic.Shader.DebugInfo.100
   /// source language code.
   static unsigned toNSDISrcLang(unsigned DwarfSrcLang);
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-type-pointer.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-type-pointer.ll
index 030542bad16b1..66f3ba898483c 100644
--- a/llvm/test/CodeGen/SPIRV/debug-info/debug-type-pointer.ll
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-type-pointer.ll
@@ -4,18 +4,6 @@
 ; TODO(#109287): When type is void * the spirv-val raises an error when DebugInfoNone is set as <id> Base Type argument of DebugTypePointer.
 ; DISABLED: %if spirv-tools %{ llc --verify-machineinstrs --spirv-ext=+SPV_KHR_non_semantic_info -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
 
-; The old SPIRVEmitNonSemanticDI MachineFunctionPass emitted DebugTypeBasic and
-; DebugTypePointer instructions as MachineInstructions, which were tested via
-; --print-after=spirv-nonsemantic-debug-info (CHECK-MIR prefix).
-; SPIRVNonSemanticDebugHandler replaces that pass. It emits NSDI instructions
-; directly as MCInsts at print time, with no MIR representation. CHECK-SPIRV
-; tests the same instructions at the SPIR-V text output level.
-
-; The Flags operand of DebugTypeBasic and DebugTypePointer is an integer bitmask.
-; SPIRVNonSemanticDebugHandler emits it as OpConstant (not OpConstantNull) because
-; OpConstant with an explicit value is the canonical form for integer constants in
-; NSDI, consistent with other NSDI producers such as DXC and glslang.
-
 ; Anchor on OpTypeVoid rather than OpTypeInt 32 0. The module may already contain
 ; OpConstant i32 N instructions (e.g. for array dimensions) before the NSDI section,
 ; which have the same pattern as the NSDI-emitted constants. Anchoring after

>From 4b70b280927a2993de3606dab5ca60abb0b2743d Mon Sep 17 00:00:00 2001
From: Diego Novillo <dnovillo at nvidia.com>
Date: Mon, 13 Apr 2026 04:18:31 -0400
Subject: [PATCH 4/4] run clang-format

---
 .../SPIRV/SPIRVNonSemanticDebugHandler.cpp    | 21 ++++++++-----------
 1 file changed, 9 insertions(+), 12 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
index 1b52ac7990276..01c74e4fc785e 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
@@ -196,8 +196,7 @@ 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)
+        MI->getOperand(1).getImm() == 32 && MI->getOperand(2).getImm() == 0)
       return MAI.getRegisterAlias(MI->getMF(), MI->getOperand(0).getReg());
   }
   MCRegister Reg = MAI.getNextIDRegister();
@@ -222,19 +221,17 @@ void SPIRVNonSemanticDebugHandler::emitDebugTypePointer(
 
   // 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());
+  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())) {
+  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);
+                  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
@@ -295,8 +292,8 @@ void SPIRVNonSemanticDebugHandler::emitNonSemanticGlobalDebugInfo(
 
   // Pre-emit source language constants for all compile units before entering
   // the DebugSource loop.
-  SmallVector<MCRegister> SrcLangRegs = map_to_vector(
-      CompileUnits, [&](const CompileUnitInfo &Info) {
+  SmallVector<MCRegister> SrcLangRegs =
+      map_to_vector(CompileUnits, [&](const CompileUnitInfo &Info) {
         return emitOpConstantI32(Info.SpirvSourceLanguage, I32TypeReg, MAI);
       });
 
@@ -371,6 +368,6 @@ void SPIRVNonSemanticDebugHandler::emitNonSemanticGlobalDebugInfo(
 
   // Emit DebugTypePointer for each referenced pointer type.
   for (const DIDerivedType *PT : PointerTypes)
-    emitDebugTypePointer(PT, VoidTypeReg, I32TypeReg, ExtInstSetReg,
-                         I32ZeroReg, BasicTypeRegs, MAI);
+    emitDebugTypePointer(PT, VoidTypeReg, I32TypeReg, ExtInstSetReg, I32ZeroReg,
+                         BasicTypeRegs, MAI);
 }



More information about the llvm-commits mailing list