[llvm-branch-commits] [llvm] Implement support for NSDI DebugFunctionDefinition. (PR #211853)

Manuel Carrasco via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Tue Jul 28 09:46:11 PDT 2026


https://github.com/mgcarrasco updated https://github.com/llvm/llvm-project/pull/211853

>From aba011f5243bb8507b80a68cce796b874af727ab Mon Sep 17 00:00:00 2001
From: Manuel Carrasco <Manuel.Carrasco at amd.com>
Date: Fri, 24 Jul 2026 04:42:51 -0500
Subject: [PATCH 1/9] Implement support for NSDI DebugFunction opcode.

---
 llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
index 953a66162ba26..f7d90a53e6df1 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
@@ -847,6 +847,12 @@ void SPIRVNonSemanticDebugHandler::emitNonSemanticDebugStrings(
     ScopeToPathOpStringReg[SP] = emitOpStringIfNew(getDebugFullPath(SP), MAI);
   }
 
+  for (const DISubprogram *SP : SubprogramDefinitions) {
+    emitOpStringIfNew(SP->getName(), MAI);
+    emitOpStringIfNew(SP->getLinkageName(), MAI);
+    ScopeToPathOpStringReg[SP] = emitOpStringIfNew(getDebugFullPath(SP), MAI);
+  }
+
   for (const auto &[GV, _] : GlobalVariableDebugInfoMap) {
     emitOpStringIfNew(GV->getName(), MAI);
     emitOpStringIfNew(GV->getLinkageName(), MAI);

>From c820e9b529b40f3b03d856162ec77b1b401fd2a3 Mon Sep 17 00:00:00 2001
From: Manuel Carrasco <Manuel.Carrasco at amd.com>
Date: Fri, 24 Jul 2026 11:45:11 -0500
Subject: [PATCH 2/9] Implement support for NSDI DebugFunctionDefinition.

---
 llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp     |  17 +-
 .../SPIRV/SPIRVNonSemanticDebugHandler.cpp    | 166 +++++++++++++++++-
 .../SPIRV/SPIRVNonSemanticDebugHandler.h      |  71 +++++---
 ...ug-function-definition-after-opvariable.ll |  36 ++++
 .../debug-info/debug-function-definition.ll   |  40 +++++
 5 files changed, 300 insertions(+), 30 deletions(-)
 create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition-after-opvariable.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition.ll

diff --git a/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp b/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp
index 31953bf811982..4c9c0255397cc 100644
--- a/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp
@@ -322,16 +322,27 @@ void SPIRVAsmPrinter::emitInstruction(const MachineInstr *MI) {
   SPIRV_MC::verifyInstructionPredicates(MI->getOpcode(),
                                         getSubtargetInfo().getFeatureBits());
 
-  if (!MAI->getSkipEmission(MI))
+  bool InstructionEmitted = !MAI->getSkipEmission(MI);
+  if (InstructionEmitted) {
     outputInstruction(MI);
+    if (NSDebugHandler && !isHidden() && MAI)
+      NSDebugHandler->notifyMachineInstructionEmitted(MI, *MF, *MAI);
+  }
 
   // Output OpLabel after OpFunction and OpFunctionParameter in the first MBB.
   const MachineInstr *NextMI = MI->getNextNode();
-  if (!LabeledMBB.contains(MI->getParent()) && isFuncOrHeaderInstr(MI, TII) &&
-      (!NextMI || !isFuncOrHeaderInstr(NextMI, TII))) {
+  bool BlockHasLabel = LabeledMBB.contains(MI->getParent());
+  bool IsFunctionPreambleInstruction = isFuncOrHeaderInstr(MI, TII);
+  bool IsNextInstructionFunctionPreamble =
+      NextMI && isFuncOrHeaderInstr(NextMI, TII);
+  bool ShouldEmitEntryLabel = !BlockHasLabel && IsFunctionPreambleInstruction &&
+                              !IsNextInstructionFunctionPreamble;
+  if (ShouldEmitEntryLabel) {
     assert(MI->getParent()->getNumber() == MF->front().getNumber() &&
            "OpFunction is not in the front MBB of MF");
     emitOpLabel(*MI->getParent());
+    if (NSDebugHandler && !isHidden() && MAI)
+      NSDebugHandler->notifyEntryLabelEmitted(*MF, *MAI);
   }
 }
 
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
index f7d90a53e6df1..157385b6428df 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
@@ -14,6 +14,8 @@
 #include "llvm/ADT/SmallVectorExtras.h"
 #include "llvm/BinaryFormat/Dwarf.h"
 #include "llvm/CodeGen/AsmPrinter.h"
+#include "llvm/CodeGen/MachineFunction.h"
+#include "llvm/CodeGen/MachineInstr.h"
 #include "llvm/IR/DebugInfo.h"
 #include "llvm/IR/DebugInfoMetadata.h"
 #include "llvm/IR/GlobalVariable.h"
@@ -168,6 +170,35 @@ static uint32_t transDebugFlags(const DINode *DN) {
   return Flags;
 }
 
+static const MachineInstr *
+findLastEmittedFunctionOpVariable(const MachineFunction &MF,
+                                  SPIRV::ModuleAnalysisInfo &MAI) {
+
+  // We iterate over the instructions to find the last OpVariable instruction if
+  // any. The following SPIRV rule is used to terminate the traversal earlier:
+  // SPIR-V 2.16.1, Function Structure: "All OpVariable instructions in a
+  // function must be in the first block in the function. These instructions,
+  // together with any intermixed OpLine and OpNoLine instructions, must be the
+  // first instructions in that block."
+  const MachineInstr *LastOpVariable = nullptr;
+  bool SeenOpVariable = false;
+  for (const MachineInstr &MI : MF.front()) {
+    if (MI.getOpcode() == SPIRV::OpVariable) {
+      SeenOpVariable = true;
+      if (!MAI.getSkipEmission(&MI))
+        LastOpVariable = &MI;
+      continue;
+    }
+
+    bool CanInterleaveWithOpVariable =
+        MI.getOpcode() == SPIRV::OpLine || MI.getOpcode() == SPIRV::OpNoLine;
+    if (SeenOpVariable && !CanInterleaveWithOpVariable &&
+        !MAI.getSkipEmission(&MI))
+      break;
+  }
+  return LastOpVariable;
+}
+
 } // namespace
 
 SPIRVNonSemanticDebugHandler::SPIRVNonSemanticDebugHandler(AsmPrinter &AP)
@@ -215,6 +246,7 @@ void SPIRVNonSemanticDebugHandler::beginModule(Module *M) {
   SubprogramDefinitions.clear();
   GlobalVariableDebugInfoMap.clear();
   DebugFunctionDeclarationRegs.clear();
+  DebugFunctionRegs.clear();
   ScopeToPathOpStringReg.clear();
   CUToCompilationUnitDbgReg.clear();
   DebugSourceRegByFileStr.clear();
@@ -223,6 +255,9 @@ void SPIRVNonSemanticDebugHandler::beginModule(Module *M) {
   I32ConstantCache.clear();
   DebugTypeFunctionCache.clear();
   GlobalDIEmitted = false;
+  GlobalNSDIEnabled = false;
+  CurrentMAI = nullptr;
+  CachedExtInstSetReg = MCRegister();
 #ifndef NDEBUG
   NonSemanticOpStringsSectionEmitted = false;
 #endif
@@ -869,18 +904,128 @@ void SPIRVNonSemanticDebugHandler::emitNonSemanticDebugStrings(
 #endif
 }
 
-void SPIRVNonSemanticDebugHandler::emitNonSemanticGlobalDebugInfo(
+void SPIRVNonSemanticDebugHandler::emitDebugFunctionDefinition(
+    MCRegister DebugFunctionReg, MCRegister OpFunctionReg,
     SPIRV::ModuleAnalysisInfo &MAI) {
-  if (GlobalDIEmitted || CompileUnits.empty())
+  assert(DebugFunctionReg.isValid() && OpFunctionReg.isValid() &&
+         "DebugFunctionDefinition operands must be valid");
+  MCRegister VoidTypeReg = getOrEmitOpTypeVoidReg(MAI);
+  emitExtInst(SPIRV::NonSemanticExtInst::DebugFunctionDefinition, VoidTypeReg,
+              CachedExtInstSetReg, {DebugFunctionReg, OpFunctionReg}, MAI);
+}
+
+void SPIRVNonSemanticDebugHandler::resetPerFunctionDebugState() {
+  CurrentMF = nullptr;
+  LastFunctionOpVariable = nullptr;
+  DebugFunctionDefinitionEmitted = false;
+}
+
+void SPIRVNonSemanticDebugHandler::preparePerFunctionDebug(
+    const MachineFunction *MF) {
+  resetPerFunctionDebugState();
+  if (!GlobalNSDIEnabled || !CurrentMAI)
     return;
+
+  CurrentMF = MF;
+
+  if (MF->getFunction()
+          .getFnAttribute(SPIRV_BACKEND_SERVICE_FUN_NAME)
+          .isValid())
+    return;
+
+  const DISubprogram *SP = MF->getFunction().getSubprogram();
+  if (!SP || !SP->isDefinition())
+    return;
+
+  // DebugFunctionDefinition is emitted after the last function-level
+  // OpVariable. If there are none, it is emitted after the entry OpLabel.
+  LastFunctionOpVariable = findLastEmittedFunctionOpVariable(*MF, *CurrentMAI);
+}
+
+void SPIRVNonSemanticDebugHandler::tryEmitDebugFunctionDefinition(
+    SPIRV::ModuleAnalysisInfo &MAI) {
+  if (DebugFunctionDefinitionEmitted || !GlobalNSDIEnabled)
+    return;
+
+  assert(CurrentMF && "no current MachineFunction");
+  const Function &F = CurrentMF->getFunction();
+  const DISubprogram *SP = F.getSubprogram();
+  if (!SP || !SP->isDefinition())
+    return;
+
+  auto DFIt = DebugFunctionRegs.find(SP);
+  if (DFIt == DebugFunctionRegs.end())
+    return;
+
+  MCRegister OpFunctionReg = MAI.getGlobalObjReg(&F);
+  if (!OpFunctionReg.isValid())
+    return;
+
+  emitDebugFunctionDefinition(DFIt->second, OpFunctionReg, MAI);
+  DebugFunctionDefinitionEmitted = true;
+}
+
+void SPIRVNonSemanticDebugHandler::beginFunctionImpl(
+    const MachineFunction *MF) {
+  preparePerFunctionDebug(MF);
+}
+
+void SPIRVNonSemanticDebugHandler::endFunctionImpl(const MachineFunction *MF) {
+  (void)MF;
+  resetPerFunctionDebugState();
+}
+
+void SPIRVNonSemanticDebugHandler::notifyMachineInstructionEmitted(
+    const MachineInstr *MI, const MachineFunction &MF,
+    SPIRV::ModuleAnalysisInfo &MAI) {
+  if (!GlobalNSDIEnabled || DebugFunctionDefinitionEmitted)
+    return;
+  assert(CurrentMF == &MF &&
+         "notification does not match the current MachineFunction");
+  if (MI->getParent() != &MF.front())
+    return;
+
+  // If this is the last function-level OpVariable, emit the
+  // DebugFunctionDefinition. Otherwise, we had already done it before right
+  // after the OpLabel.
+  if (MI == LastFunctionOpVariable)
+    tryEmitDebugFunctionDefinition(MAI);
+}
+
+void SPIRVNonSemanticDebugHandler::notifyEntryLabelEmitted(
+    const MachineFunction &MF, SPIRV::ModuleAnalysisInfo &MAI) {
+  if (!GlobalNSDIEnabled || DebugFunctionDefinitionEmitted)
+    return;
+  assert(CurrentMF == &MF &&
+         "notification does not match the current MachineFunction");
+
+  // If there are no function-level OpVariables, emit the
+  // DebugFunctionDefinition. Otherwise, DebugFunctionDefinition is emitted
+  // after the last OpVariable.
+  if (!LastFunctionOpVariable)
+    tryEmitDebugFunctionDefinition(MAI);
+}
+
+bool SPIRVNonSemanticDebugHandler::emitNonSemanticGlobalDebugInfo(
+    SPIRV::ModuleAnalysisInfo &MAI) {
+  if (GlobalDIEmitted)
+    return GlobalNSDIEnabled;
+
   GlobalDIEmitted = true;
 
+  if (CompileUnits.empty()) {
+    GlobalNSDIEnabled = false;
+    return false;
+  }
+
   // 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.
+  if (!ExtInstSetReg.isValid()) {
+    GlobalNSDIEnabled = false;
+    return false;
+  }
 
 #ifndef NDEBUG
   assert(NonSemanticOpStringsSectionEmitted &&
@@ -888,6 +1033,9 @@ void SPIRVNonSemanticDebugHandler::emitNonSemanticGlobalDebugInfo(
          "emitNonSemanticGlobalDebugInfo()");
 #endif
 
+  CurrentMAI = &MAI;
+  CachedExtInstSetReg = ExtInstSetReg;
+
   MCRegister VoidTypeReg = getOrEmitOpTypeVoidReg(MAI);
   MCRegister I32TypeReg = getOrEmitOpTypeInt32Reg(MAI);
 
@@ -1011,13 +1159,19 @@ void SPIRVNonSemanticDebugHandler::emitNonSemanticGlobalDebugInfo(
   }
 
   // Emit DebugFunction for DISubprogram definitions.
-  for (const DISubprogram *SP : SubprogramDefinitions)
-    emitDebugFunction(SP, VoidTypeReg, I32TypeReg, ExtInstSetReg, MAI);
+  for (const DISubprogram *SP : SubprogramDefinitions) {
+    if (auto FnReg =
+            emitDebugFunction(SP, VoidTypeReg, I32TypeReg, ExtInstSetReg, MAI))
+      DebugFunctionRegs[SP] = *FnReg;
+  }
 
   // Emit DebugGlobalVariable for each collected DIGlobalVariable.
   for (const auto &[GV, Info] : GlobalVariableDebugInfoMap)
     emitDebugGlobalVariable(GV, Info, VoidTypeReg, I32TypeReg, ExtInstSetReg,
                             MAI);
+
+  GlobalNSDIEnabled = true;
+  return true;
 }
 
 SmallString<128>
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
index c9e1947536443..54e14565837fb 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
@@ -40,16 +40,15 @@ class SPIRVSubtarget;
 /// the module contains debug info (llvm.dbg.cu).
 ///
 /// Call sequence:
-///   beginModule()                    -- collect compile-unit metadata.
-///   prepareModuleOutput()            -- add extension + ext inst set to MAI.
-///   emitNonSemanticDebugStrings()    -- OpString for NSDI strings (sec. 7).
-///   emitNonSemanticGlobalDebugInfo() -- emit DebugSource,
-///                                       DebugCompilationUnit, DebugTypeBasic,
-///                                       DebugTypePointer, DebugTypeFunction,
-///                                       DebugFunctionDeclaration,
-///                                       DebugFunction.
-///   beginFunctionImpl()              -- no-op (no per-function DI yet).
-///   endFunctionImpl()                -- no-op.
+/// - beginModule() collects compile-unit metadata.
+/// - prepareModuleOutput() adds the extension and ext-inst set to MAI.
+/// - emitNonSemanticDebugStrings() emits NSDI OpStrings in section 7.
+/// - emitNonSemanticGlobalDebugInfo() emits module-scope NSDI and sets
+///   GlobalNSDIEnabled.
+/// - beginFunctionImpl() prepares per-function DebugFunctionDefinition state.
+/// - SPIRVAsmPrinter notifies the handler when it emits MachineInstrs and the
+///   synthesized entry OpLabel.
+/// - endFunctionImpl() resets per-function state.
 class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
   struct CompileUnitInfo {
     const DICompileUnit *TheCU = nullptr;
@@ -95,6 +94,10 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
   // (only entries where emission succeeded).
   DenseMap<const DISubprogram *, MCRegister> DebugFunctionDeclarationRegs;
 
+  // DebugFunction result id per emitted definition DISubprogram (only entries
+  // where emission succeeded).
+  DenseMap<const DISubprogram *, MCRegister> DebugFunctionRegs;
+
   // Path \c OpString result id per \c DIScope (CU, \c DIFile, declaration
   // \c DISubprogram, …). Filled during \c emitNonSemanticDebugStrings() using
   // \c getDebugFullPath + \c emitOpStringIfNew; section 10 uses it for
@@ -142,6 +145,20 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
   // change.
   bool GlobalDIEmitted = false;
 
+  // True when emitNonSemanticGlobalDebugInfo() completed module-scope NSDI
+  // emission for this module.
+  bool GlobalNSDIEnabled = false;
+
+  SPIRV::ModuleAnalysisInfo *CurrentMAI = nullptr;
+
+  MCRegister CachedExtInstSetReg;
+
+  const MachineFunction *CurrentMF = nullptr;
+
+  const MachineInstr *LastFunctionOpVariable = nullptr;
+
+  bool DebugFunctionDefinitionEmitted = false;
+
 public:
   explicit SPIRVNonSemanticDebugHandler(AsmPrinter &AP);
 
@@ -171,7 +188,17 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
   /// SPIRVAsmPrinter::outputModuleSections() at section 10 in place of
   /// outputModuleSection(MB_NonSemanticGlobalDI). Requires
   /// emitNonSemanticDebugStrings() to have run first when NSDI strings apply.
-  void emitNonSemanticGlobalDebugInfo(SPIRV::ModuleAnalysisInfo &MAI);
+  /// \returns true when module-scope NSDI emission ran; false when skipped.
+  bool emitNonSemanticGlobalDebugInfo(SPIRV::ModuleAnalysisInfo &MAI);
+
+  /// Called after an MI has been emitted.
+  void notifyMachineInstructionEmitted(const MachineInstr *MI,
+                                       const MachineFunction &MF,
+                                       SPIRV::ModuleAnalysisInfo &MAI);
+
+  /// Called after the synthesized entry \c OpLabel has been emitted.
+  void notifyEntryLabelEmitted(const MachineFunction &MF,
+                               SPIRV::ModuleAnalysisInfo &MAI);
 
 protected:
   // All module-level output is driven by emitNonSemanticGlobalDebugInfo(),
@@ -185,21 +212,23 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
   // 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().
+  // Future local NSDI that needs MCContext must use
+  // Asm->OutStreamer->getContext() rather than MMI->getContext().
   void beginInstruction(const MachineInstr *MI) override {}
   void endInstruction() override {}
 
-  // TODO: Emit 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 DebugFunctionDefinition emission is in
-  // place.
-  void endFunctionImpl(const MachineFunction *MF) override {}
+  void beginFunctionImpl(const MachineFunction *MF) override;
+  void endFunctionImpl(const MachineFunction *MF) override;
 
 private:
+  void emitDebugFunctionDefinition(MCRegister DebugFunctionReg,
+                                   MCRegister OpFunctionReg,
+                                   SPIRV::ModuleAnalysisInfo &MAI);
+
+  void resetPerFunctionDebugState();
+  void preparePerFunctionDebug(const MachineFunction *MF);
+  void tryEmitDebugFunctionDefinition(SPIRV::ModuleAnalysisInfo &MAI);
+
   void emitMCInst(MCInst &Inst);
   MCRegister emitOpString(StringRef S, SPIRV::ModuleAnalysisInfo &MAI);
 
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition-after-opvariable.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition-after-opvariable.ll
new file mode 100644
index 0000000000000..ec3c93c5115b6
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition-after-opvariable.ll
@@ -0,0 +1,36 @@
+; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_non_semantic_info %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 %}
+
+; DebugFunctionDefinition must follow function-local OpVariable instructions.
+
+; CHECK-DAG: [[EXT:%[0-9]+]] = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+; CHECK-DAG: [[VOID:%[0-9]+]] = OpTypeVoid
+; CHECK-DAG: [[DF:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugFunction {{.*}}
+; CHECK: [[FOO:%[0-9]+]] = OpFunction
+; CHECK: OpVariable {{.*}} Function
+; CHECK-NEXT: OpVariable {{.*}} Function
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF]] [[FOO]]
+
+target triple = "spirv64-unknown-unknown"
+
+define spir_func void @foo() !dbg !4 {
+entry:
+  %x = alloca i32, align 4
+  %y = alloca i32, align 4
+  store i32 0, ptr %x
+  store i32 1, ptr %y
+  ret void, !dbg !7
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "debug-function-definition-after-opvariable.c", directory: "/src")
+!2 = !{i32 7, !"Dwarf Version", i32 5}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+
+!4 = distinct !DISubprogram(name: "foo", scope: !1, file: !1, line: 1, type: !5, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!5 = !DISubroutineType(types: !6)
+!6 = !{null}
+!7 = !DILocation(line: 2, column: 1, scope: !4)
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition.ll
new file mode 100644
index 0000000000000..5cacfd5c14281
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition.ll
@@ -0,0 +1,40 @@
+; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_non_semantic_info %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 %}
+
+; Exercise NonSemantic DebugFunctionDefinition for a defined function.
+
+; CHECK-DAG: [[EXT:%[0-9]+]] = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+; CHECK-DAG: [[VOID:%[0-9]+]] = OpTypeVoid
+; CHECK-DAG: [[I32:%[0-9]+]] = OpTypeInt 32 0
+; CHECK-DAG: [[PATH:%[0-9]+]] = OpString "/src/debug-function-definition.c"
+; CHECK-DAG: [[NAME:%[0-9]+]] = OpString "add_one"
+; CHECK-DAG: [[DS:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugSource [[PATH]]
+; CHECK-DAG: [[CU:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugCompilationUnit {{.*}}
+; CHECK-DAG: [[TF:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugTypeFunction {{.*}}
+; CHECK-DAG: [[DF:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugFunction [[NAME]] [[TF]] [[DS]] {{.*}}
+; CHECK: [[ADD_ONE:%[0-9]+]] = OpFunction
+; CHECK: OpLabel
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF]] [[ADD_ONE]]
+
+target triple = "spirv64-unknown-unknown"
+
+define spir_func i32 @add_one(i32 %value) !dbg !5 {
+entry:
+  %result = add i32 %value, 1
+  ret i32 %result, !dbg !8
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "debug-function-definition.c", directory: "/src")
+!2 = !{i32 7, !"Dwarf Version", i32 5}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+
+!4 = !DISubroutineType(types: !6)
+!6 = !{!7, !7}
+!7 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+
+!5 = distinct !DISubprogram(name: "add_one", linkageName: "add_one", scope: !1, file: !1, line: 1, type: !4, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!8 = !DILocation(line: 3, column: 3, scope: !5)

>From ecc33b0c224f9817181d2f8b570549a8900d58fe Mon Sep 17 00:00:00 2001
From: Manuel Carrasco <Manuel.Carrasco at amd.com>
Date: Fri, 24 Jul 2026 11:51:06 -0500
Subject: [PATCH 3/9] Keep original comment.

---
 llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
index 54e14565837fb..9f64b2e7ef61f 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
@@ -217,6 +217,9 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
   void beginInstruction(const MachineInstr *MI) override {}
   void endInstruction() override {}
 
+  // Override beginFunctionImpl(), not beginFunction():
+  // DebugHandlerBase::beginFunction() populates LScopes and DbgValues needed
+  // for future DebugLine emission.
   void beginFunctionImpl(const MachineFunction *MF) override;
   void endFunctionImpl(const MachineFunction *MF) override;
 

>From 65f73ad51cfd53ab5066a5863f356a30be481bd6 Mon Sep 17 00:00:00 2001
From: Manuel Carrasco <Manuel.Carrasco at amd.com>
Date: Tue, 28 Jul 2026 10:33:43 -0500
Subject: [PATCH 4/9] [SPIRV] Drive DebugFunctionDefinition placement via
 begin/endInstruction

Replace the SPIRV-specific notifyMachineInstructionEmitted() callback with
DebugHandlerBase::beginInstruction()/endInstruction().
Keep notifyEntryLabelEmitted() for the synthesized entry OpLabel, which
has no MachineInstr.
---
 llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp     |  7 ++--
 .../SPIRV/SPIRVNonSemanticDebugHandler.cpp    | 36 +++++++++++--------
 .../SPIRV/SPIRVNonSemanticDebugHandler.h      | 20 +++++------
 3 files changed, 32 insertions(+), 31 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp b/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp
index 4c9c0255397cc..7376e97b80901 100644
--- a/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp
@@ -323,11 +323,8 @@ void SPIRVAsmPrinter::emitInstruction(const MachineInstr *MI) {
                                         getSubtargetInfo().getFeatureBits());
 
   bool InstructionEmitted = !MAI->getSkipEmission(MI);
-  if (InstructionEmitted) {
+  if (InstructionEmitted)
     outputInstruction(MI);
-    if (NSDebugHandler && !isHidden() && MAI)
-      NSDebugHandler->notifyMachineInstructionEmitted(MI, *MF, *MAI);
-  }
 
   // Output OpLabel after OpFunction and OpFunctionParameter in the first MBB.
   const MachineInstr *NextMI = MI->getNextNode();
@@ -341,7 +338,7 @@ void SPIRVAsmPrinter::emitInstruction(const MachineInstr *MI) {
     assert(MI->getParent()->getNumber() == MF->front().getNumber() &&
            "OpFunction is not in the front MBB of MF");
     emitOpLabel(*MI->getParent());
-    if (NSDebugHandler && !isHidden() && MAI)
+    if (NSDebugHandler && !isHidden())
       NSDebugHandler->notifyEntryLabelEmitted(*MF, *MAI);
   }
 }
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
index 157385b6428df..1553495b00b69 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
@@ -975,35 +975,43 @@ void SPIRVNonSemanticDebugHandler::endFunctionImpl(const MachineFunction *MF) {
   resetPerFunctionDebugState();
 }
 
-void SPIRVNonSemanticDebugHandler::notifyMachineInstructionEmitted(
-    const MachineInstr *MI, const MachineFunction &MF,
-    SPIRV::ModuleAnalysisInfo &MAI) {
-  if (!GlobalNSDIEnabled || DebugFunctionDefinitionEmitted)
+void SPIRVNonSemanticDebugHandler::beginInstruction(const MachineInstr *MI) {
+  assert(CurMI == nullptr && "CurMI must be null");
+  CurMI = MI;
+}
+
+void SPIRVNonSemanticDebugHandler::endInstruction() {
+  const MachineInstr *MI = CurMI;
+  CurMI = nullptr;
+
+  if (!MI || !GlobalNSDIEnabled || DebugFunctionDefinitionEmitted || !CurrentMF)
     return;
-  assert(CurrentMF == &MF &&
-         "notification does not match the current MachineFunction");
-  if (MI->getParent() != &MF.front())
+
+  if (MI != LastFunctionOpVariable)
     return;
 
   // If this is the last function-level OpVariable, emit the
   // DebugFunctionDefinition. Otherwise, we had already done it before right
-  // after the OpLabel.
-  if (MI == LastFunctionOpVariable)
-    tryEmitDebugFunctionDefinition(MAI);
+  // after the OpLabel (see notifyEntryLabelEmitted).
+  assert(CurrentMAI && "CurrentMAI must be set");
+  tryEmitDebugFunctionDefinition(*CurrentMAI);
 }
 
 void SPIRVNonSemanticDebugHandler::notifyEntryLabelEmitted(
     const MachineFunction &MF, SPIRV::ModuleAnalysisInfo &MAI) {
-  if (!GlobalNSDIEnabled || DebugFunctionDefinitionEmitted)
+  if (!GlobalNSDIEnabled || DebugFunctionDefinitionEmitted || !CurrentMF)
     return;
+
   assert(CurrentMF == &MF &&
          "notification does not match the current MachineFunction");
 
+  if (LastFunctionOpVariable)
+    return;
+
   // If there are no function-level OpVariables, emit the
   // DebugFunctionDefinition. Otherwise, DebugFunctionDefinition is emitted
-  // after the last OpVariable.
-  if (!LastFunctionOpVariable)
-    tryEmitDebugFunctionDefinition(MAI);
+  // after the last OpVariable (see endInstruction).
+  tryEmitDebugFunctionDefinition(*CurrentMAI);
 }
 
 bool SPIRVNonSemanticDebugHandler::emitNonSemanticGlobalDebugInfo(
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
index 9f64b2e7ef61f..01e49d066e4b9 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
@@ -46,8 +46,9 @@ class SPIRVSubtarget;
 /// - emitNonSemanticGlobalDebugInfo() emits module-scope NSDI and sets
 ///   GlobalNSDIEnabled.
 /// - beginFunctionImpl() prepares per-function DebugFunctionDefinition state.
-/// - SPIRVAsmPrinter notifies the handler when it emits MachineInstrs and the
-///   synthesized entry OpLabel.
+/// - endInstruction() emits DebugFunctionDefinition after the last function-
+///   level OpVariable; SPIRVAsmPrinter calls notifyEntryLabelEmitted() after
+///   the synthesized entry OpLabel when there are no OpVariables.
 /// - endFunctionImpl() resets per-function state.
 class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
   struct CompileUnitInfo {
@@ -191,11 +192,6 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
   /// \returns true when module-scope NSDI emission ran; false when skipped.
   bool emitNonSemanticGlobalDebugInfo(SPIRV::ModuleAnalysisInfo &MAI);
 
-  /// Called after an MI has been emitted.
-  void notifyMachineInstructionEmitted(const MachineInstr *MI,
-                                       const MachineFunction &MF,
-                                       SPIRV::ModuleAnalysisInfo &MAI);
-
   /// Called after the synthesized entry \c OpLabel has been emitted.
   void notifyEntryLabelEmitted(const MachineFunction &MF,
                                SPIRV::ModuleAnalysisInfo &MAI);
@@ -209,13 +205,13 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
   // 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.
+  // point and MMI remains null for this handler's entire lifetime. Do not call
+  // the base-class beginInstruction/endInstruction — they dereference MMI to
+  // create temp symbols for label tracking and would crash.
   // Future local NSDI that needs MCContext must use
   // Asm->OutStreamer->getContext() rather than MMI->getContext().
-  void beginInstruction(const MachineInstr *MI) override {}
-  void endInstruction() override {}
+  void beginInstruction(const MachineInstr *MI) override;
+  void endInstruction() override;
 
   // Override beginFunctionImpl(), not beginFunction():
   // DebugHandlerBase::beginFunction() populates LScopes and DbgValues needed

>From 6e71e2168858ed1274432db3fda163ea310cc3e9 Mon Sep 17 00:00:00 2001
From: Manuel Carrasco <Manuel.Carrasco at amd.com>
Date: Tue, 28 Jul 2026 10:53:42 -0500
Subject: [PATCH 5/9] Fix path check.

---
 llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition.ll | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition.ll
index 5cacfd5c14281..96007db44ea3c 100644
--- a/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition.ll
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition.ll
@@ -6,7 +6,7 @@
 ; CHECK-DAG: [[EXT:%[0-9]+]] = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
 ; CHECK-DAG: [[VOID:%[0-9]+]] = OpTypeVoid
 ; CHECK-DAG: [[I32:%[0-9]+]] = OpTypeInt 32 0
-; CHECK-DAG: [[PATH:%[0-9]+]] = OpString "/src/debug-function-definition.c"
+; CHECK-DAG: [[PATH:%[0-9]+]] = OpString "{{[/\\]}}src{{[/\\]}}debug-function-definition.c"
 ; CHECK-DAG: [[NAME:%[0-9]+]] = OpString "add_one"
 ; CHECK-DAG: [[DS:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugSource [[PATH]]
 ; CHECK-DAG: [[CU:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugCompilationUnit {{.*}}

>From 240f2dde55c1aadab854fb70a53bdcb2dab3d6aa Mon Sep 17 00:00:00 2001
From: Manuel Carrasco <Manuel.Carrasco at amd.com>
Date: Tue, 28 Jul 2026 10:59:54 -0500
Subject: [PATCH 6/9] Remove CachedExtInstSetReg.

---
 .../lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp | 11 ++---------
 llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h  |  5 +++--
 2 files changed, 5 insertions(+), 11 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
index 1553495b00b69..34f960a04837a 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
@@ -257,7 +257,6 @@ void SPIRVNonSemanticDebugHandler::beginModule(Module *M) {
   GlobalDIEmitted = false;
   GlobalNSDIEnabled = false;
   CurrentMAI = nullptr;
-  CachedExtInstSetReg = MCRegister();
 #ifndef NDEBUG
   NonSemanticOpStringsSectionEmitted = false;
 #endif
@@ -344,8 +343,6 @@ void SPIRVNonSemanticDebugHandler::prepareModuleOutput(
   // 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();
 }
@@ -858,8 +855,6 @@ void SPIRVNonSemanticDebugHandler::emitNonSemanticDebugStrings(
   // 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;
 
@@ -910,8 +905,9 @@ void SPIRVNonSemanticDebugHandler::emitDebugFunctionDefinition(
   assert(DebugFunctionReg.isValid() && OpFunctionReg.isValid() &&
          "DebugFunctionDefinition operands must be valid");
   MCRegister VoidTypeReg = getOrEmitOpTypeVoidReg(MAI);
+  MCRegister ExtInstSetReg = MAI.getExtInstSetReg(NSSet);
   emitExtInst(SPIRV::NonSemanticExtInst::DebugFunctionDefinition, VoidTypeReg,
-              CachedExtInstSetReg, {DebugFunctionReg, OpFunctionReg}, MAI);
+              ExtInstSetReg, {DebugFunctionReg, OpFunctionReg}, MAI);
 }
 
 void SPIRVNonSemanticDebugHandler::resetPerFunctionDebugState() {
@@ -1027,8 +1023,6 @@ bool SPIRVNonSemanticDebugHandler::emitNonSemanticGlobalDebugInfo(
   }
 
   // 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()) {
     GlobalNSDIEnabled = false;
@@ -1042,7 +1036,6 @@ bool SPIRVNonSemanticDebugHandler::emitNonSemanticGlobalDebugInfo(
 #endif
 
   CurrentMAI = &MAI;
-  CachedExtInstSetReg = ExtInstSetReg;
 
   MCRegister VoidTypeReg = getOrEmitOpTypeVoidReg(MAI);
   MCRegister I32TypeReg = getOrEmitOpTypeInt32Reg(MAI);
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
index 01e49d066e4b9..2dbbc40cc1995 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
@@ -51,6 +51,9 @@ class SPIRVSubtarget;
 ///   the synthesized entry OpLabel when there are no OpVariables.
 /// - endFunctionImpl() resets per-function state.
 class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
+  static constexpr unsigned NSSet = static_cast<unsigned>(
+      SPIRV::InstructionSet::NonSemantic_Shader_DebugInfo_100);
+
   struct CompileUnitInfo {
     const DICompileUnit *TheCU = nullptr;
     SmallString<128> FilePath;
@@ -152,8 +155,6 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
 
   SPIRV::ModuleAnalysisInfo *CurrentMAI = nullptr;
 
-  MCRegister CachedExtInstSetReg;
-
   const MachineFunction *CurrentMF = nullptr;
 
   const MachineInstr *LastFunctionOpVariable = nullptr;

>From 85a2f1c459eb08451c29c0d3bdbd4887ab224958 Mon Sep 17 00:00:00 2001
From: Manuel Carrasco <Manuel.Carrasco at amd.com>
Date: Tue, 28 Jul 2026 11:05:46 -0500
Subject: [PATCH 7/9] Change signature.

---
 llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp | 9 ++++-----
 llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h   | 4 ++--
 2 files changed, 6 insertions(+), 7 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
index 34f960a04837a..579af66d369d6 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
@@ -1010,23 +1010,23 @@ void SPIRVNonSemanticDebugHandler::notifyEntryLabelEmitted(
   tryEmitDebugFunctionDefinition(*CurrentMAI);
 }
 
-bool SPIRVNonSemanticDebugHandler::emitNonSemanticGlobalDebugInfo(
+void SPIRVNonSemanticDebugHandler::emitNonSemanticGlobalDebugInfo(
     SPIRV::ModuleAnalysisInfo &MAI) {
   if (GlobalDIEmitted)
-    return GlobalNSDIEnabled;
+    return;
 
   GlobalDIEmitted = true;
 
   if (CompileUnits.empty()) {
     GlobalNSDIEnabled = false;
-    return false;
+    return;
   }
 
   // Retrieve the ext inst set register allocated by prepareModuleOutput().
   MCRegister ExtInstSetReg = MAI.getExtInstSetReg(NSSet);
   if (!ExtInstSetReg.isValid()) {
     GlobalNSDIEnabled = false;
-    return false;
+    return;
   }
 
 #ifndef NDEBUG
@@ -1172,7 +1172,6 @@ bool SPIRVNonSemanticDebugHandler::emitNonSemanticGlobalDebugInfo(
                             MAI);
 
   GlobalNSDIEnabled = true;
-  return true;
 }
 
 SmallString<128>
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
index 2dbbc40cc1995..5e6c6548a0cfa 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
@@ -190,8 +190,8 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
   /// SPIRVAsmPrinter::outputModuleSections() at section 10 in place of
   /// outputModuleSection(MB_NonSemanticGlobalDI). Requires
   /// emitNonSemanticDebugStrings() to have run first when NSDI strings apply.
-  /// \returns true when module-scope NSDI emission ran; false when skipped.
-  bool emitNonSemanticGlobalDebugInfo(SPIRV::ModuleAnalysisInfo &MAI);
+  /// Sets \c GlobalNSDIEnabled when module-scope NSDI emission completes.
+  void emitNonSemanticGlobalDebugInfo(SPIRV::ModuleAnalysisInfo &MAI);
 
   /// Called after the synthesized entry \c OpLabel has been emitted.
   void notifyEntryLabelEmitted(const MachineFunction &MF,

>From 583b986a765fd1e58f69a0ccba02c4b5b36e5200 Mon Sep 17 00:00:00 2001
From: Manuel Carrasco <Manuel.Carrasco at amd.com>
Date: Tue, 28 Jul 2026 11:17:02 -0500
Subject: [PATCH 8/9] Improve test.

---
 .../debug-info/debug-function-definition.ll   | 23 +++++++++++++++----
 1 file changed, 19 insertions(+), 4 deletions(-)

diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition.ll
index 96007db44ea3c..57c246edff8f3 100644
--- a/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition.ll
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition.ll
@@ -1,20 +1,26 @@
 ; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_non_semantic_info %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 %}
 
-; Exercise NonSemantic DebugFunctionDefinition for a defined function.
+; Exercise NonSemantic DebugFunctionDefinition for defined functions, including
+; per-function emitter state across multiple definitions in one module.
 
 ; CHECK-DAG: [[EXT:%[0-9]+]] = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
 ; CHECK-DAG: [[VOID:%[0-9]+]] = OpTypeVoid
 ; CHECK-DAG: [[I32:%[0-9]+]] = OpTypeInt 32 0
 ; CHECK-DAG: [[PATH:%[0-9]+]] = OpString "{{[/\\]}}src{{[/\\]}}debug-function-definition.c"
-; CHECK-DAG: [[NAME:%[0-9]+]] = OpString "add_one"
+; CHECK-DAG: [[NAME1:%[0-9]+]] = OpString "add_one"
+; CHECK-DAG: [[NAME2:%[0-9]+]] = OpString "add_two"
 ; CHECK-DAG: [[DS:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugSource [[PATH]]
 ; CHECK-DAG: [[CU:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugCompilationUnit {{.*}}
 ; CHECK-DAG: [[TF:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugTypeFunction {{.*}}
-; CHECK-DAG: [[DF:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugFunction [[NAME]] [[TF]] [[DS]] {{.*}}
+; CHECK-DAG: [[DF1:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugFunction [[NAME1]] [[TF]] [[DS]] {{.*}}
+; CHECK-DAG: [[DF2:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugFunction [[NAME2]] [[TF]] [[DS]] {{.*}}
 ; CHECK: [[ADD_ONE:%[0-9]+]] = OpFunction
 ; CHECK: OpLabel
-; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF]] [[ADD_ONE]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF1]] [[ADD_ONE]]
+; CHECK: [[ADD_TWO:%[0-9]+]] = OpFunction
+; CHECK: OpLabel
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF2]] [[ADD_TWO]]
 
 target triple = "spirv64-unknown-unknown"
 
@@ -24,6 +30,12 @@ entry:
   ret i32 %result, !dbg !8
 }
 
+define spir_func i32 @add_two(i32 %value) !dbg !9 {
+entry:
+  %result = add i32 %value, 2
+  ret i32 %result, !dbg !10
+}
+
 !llvm.dbg.cu = !{!0}
 !llvm.module.flags = !{!2, !3}
 
@@ -38,3 +50,6 @@ entry:
 
 !5 = distinct !DISubprogram(name: "add_one", linkageName: "add_one", scope: !1, file: !1, line: 1, type: !4, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
 !8 = !DILocation(line: 3, column: 3, scope: !5)
+
+!9 = distinct !DISubprogram(name: "add_two", linkageName: "add_two", scope: !1, file: !1, line: 5, type: !4, scopeLine: 5, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!10 = !DILocation(line: 7, column: 3, scope: !9)

>From ab77d2b3ef0cfc1a1b1d55c736435c9bb70156ab Mon Sep 17 00:00:00 2001
From: Manuel Carrasco <Manuel.Carrasco at amd.com>
Date: Tue, 28 Jul 2026 11:45:49 -0500
Subject: [PATCH 9/9] Fix rebase.

---
 llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp | 6 ------
 1 file changed, 6 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
index 579af66d369d6..01fa481435470 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
@@ -877,12 +877,6 @@ void SPIRVNonSemanticDebugHandler::emitNonSemanticDebugStrings(
     ScopeToPathOpStringReg[SP] = emitOpStringIfNew(getDebugFullPath(SP), MAI);
   }
 
-  for (const DISubprogram *SP : SubprogramDefinitions) {
-    emitOpStringIfNew(SP->getName(), MAI);
-    emitOpStringIfNew(SP->getLinkageName(), MAI);
-    ScopeToPathOpStringReg[SP] = emitOpStringIfNew(getDebugFullPath(SP), MAI);
-  }
-
   for (const auto &[GV, _] : GlobalVariableDebugInfoMap) {
     emitOpStringIfNew(GV->getName(), MAI);
     emitOpStringIfNew(GV->getLinkageName(), MAI);



More information about the llvm-branch-commits mailing list