[llvm] Add support for NSDI DebugLine and DebugNoLine. (PR #215851)

Manuel Carrasco via llvm-commits llvm-commits at lists.llvm.org
Fri Aug 14 07:40:23 PDT 2026


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

>From ab49292f900471825fa2b3e415a0541ca193c8bf Mon Sep 17 00:00:00 2001
From: Manuel Carrasco <Manuel.Carrasco at amd.com>
Date: Wed, 12 Aug 2026 11:05:04 -0500
Subject: [PATCH 1/7] Add support for NSDI DebugLine and DebugNoLine.

---
 .../SPIRV/SPIRVNonSemanticDebugHandler.cpp    | 138 ++++++++++++++++++
 .../SPIRV/SPIRVNonSemanticDebugHandler.h      |  18 +++
 ...-function-definition-call-before-alloca.ll |   2 +
 .../debug-function-definition-calls.ll        |  16 +-
 ...debug-function-definition-external-call.ll |   1 +
 .../SPIRV/debug-info/debug-line-block.ll      |  62 ++++++++
 .../SPIRV/debug-info/debug-line-calls.ll      |  83 +++++++++++
 .../SPIRV/debug-info/debug-line-shared.ll     |  55 +++++++
 .../CodeGen/SPIRV/debug-info/debug-line.ll    |  68 +++++++++
 .../CodeGen/SPIRV/debug-info/debug-no-line.ll |  59 ++++++++
 10 files changed, 501 insertions(+), 1 deletion(-)
 create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-line-block.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-line-calls.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-line-shared.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-line.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-no-line.ll

diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
index f921df3912d38..b158cea940815 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/SmallPtrSet.h"
 #include "llvm/ADT/SmallVectorExtras.h"
 #include "llvm/ADT/Twine.h"
 #include "llvm/BinaryFormat/Dwarf.h"
@@ -19,7 +20,10 @@
 #include "llvm/CodeGen/MachineInstr.h"
 #include "llvm/IR/DebugInfo.h"
 #include "llvm/IR/DebugInfoMetadata.h"
+#include "llvm/IR/DebugProgramInstruction.h"
 #include "llvm/IR/GlobalVariable.h"
+#include "llvm/IR/InstIterator.h"
+#include "llvm/IR/Instructions.h"
 #include "llvm/IR/Module.h"
 #include "llvm/MC/MCInst.h"
 #include "llvm/MC/MCStreamer.h"
@@ -253,6 +257,25 @@ unsigned SPIRVNonSemanticDebugHandler::toNSDISrcLang(unsigned DwarfSrcLang) {
   }
 }
 
+static void collectUniqueDebugLocations(const Module &M,
+                                        SmallVector<const DILocation *> &Out) {
+  SmallPtrSet<const DILocation *, 16> Seen;
+  auto AddUnique = [&](const DILocation *DL) {
+    if (DL && Seen.insert(DL).second)
+      Out.push_back(DL);
+  };
+
+  for (const Function &F : M) {
+    if (!F.getSubprogram())
+      continue;
+    for (const Instruction &I : instructions(F)) {
+      AddUnique(I.getDebugLoc().get());
+      for (DbgRecord &DR : I.getDbgRecordRange())
+        AddUnique(DR.getDebugLoc().get());
+    }
+  }
+}
+
 void SPIRVNonSemanticDebugHandler::beginModule(Module *M) {
   // The base class sets Asm = nullptr when the module has no compile units,
   // and initializes lexical scope tracking otherwise.
@@ -271,6 +294,7 @@ void SPIRVNonSemanticDebugHandler::beginModule(Module *M) {
   TypedefTypes.clear();
   SubprogramDeclarations.clear();
   SubprogramDefinitions.clear();
+  UniqueDebugLocations.clear();
   GlobalVariableDebugInfoMap.clear();
   DebugFunctionDeclarationRegs.clear();
   DebugFunctionRegs.clear();
@@ -355,6 +379,8 @@ void SPIRVNonSemanticDebugHandler::beginModule(Module *M) {
     GlobalVariableDebugInfoMap.try_emplace(
         GV, GlobalVariableDebugInfo{Expr, DIGVToLLVMGV.lookup(GV)});
   }
+
+  collectUniqueDebugLocations(*M, UniqueDebugLocations);
 }
 
 void SPIRVNonSemanticDebugHandler::prepareModuleOutput(
@@ -1074,6 +1100,9 @@ void SPIRVNonSemanticDebugHandler::emitNonSemanticDebugStrings(
     emitAndCacheScopePathOpStringReg(GV->getFile(), MAI);
   }
 
+  for (const DILocation *DL : UniqueDebugLocations)
+    emitAndCacheScopePathOpStringReg(DL->getScope(), MAI);
+
   CachedEmptyStringReg = emitOpStringIfNew("", MAI);
 
 #ifndef NDEBUG
@@ -1096,8 +1125,13 @@ void SPIRVNonSemanticDebugHandler::resetPerFunctionDebugState() {
   CurrentMF = nullptr;
   LastFunctionOpVariable = nullptr;
   DebugFunctionDefinitionEmitted = false;
+  EmitDebugLineForCurrentFn = false;
+  clearCurLineState();
+  CurLineMBB = nullptr;
 }
 
+void SPIRVNonSemanticDebugHandler::clearCurLineState() { CurLineState.reset(); }
+
 void SPIRVNonSemanticDebugHandler::preparePerFunctionDebug(
     const MachineFunction *MF) {
   resetPerFunctionDebugState();
@@ -1115,6 +1149,8 @@ void SPIRVNonSemanticDebugHandler::preparePerFunctionDebug(
   if (!SP || !SP->isDefinition())
     return;
 
+  EmitDebugLineForCurrentFn = true;
+
   // DebugFunctionDefinition is emitted after the last function-level
   // OpVariable. If there are none, it is emitted after the entry OpLabel.
   LastFunctionOpVariable =
@@ -1157,6 +1193,97 @@ void SPIRVNonSemanticDebugHandler::endFunctionImpl(const MachineFunction *MF) {
 void SPIRVNonSemanticDebugHandler::beginInstruction(const MachineInstr *MI) {
   assert(CurMI == nullptr && "CurMI must be null");
   CurMI = MI;
+
+  if (!EmitDebugLineForCurrentFn || !DebugFunctionDefinitionEmitted)
+    return;
+  emitDebugLineForInstruction(MI);
+}
+
+static bool isDebugLineTarget(const MachineInstr *MI,
+                              SPIRV::ModuleAnalysisInfo &MAI) {
+  if (MAI.getSkipEmission(MI))
+    return false;
+  switch (MI->getOpcode()) {
+  case SPIRV::OpFunction:
+  case SPIRV::OpFunctionParameter:
+  case SPIRV::OpFunctionEnd:
+  case SPIRV::OpLabel:
+    return false;
+  default:
+    return true;
+  }
+}
+
+void SPIRVNonSemanticDebugHandler::emitDebugLineForInstruction(
+    const MachineInstr *MI) {
+  assert(DebugFunctionDefinitionEmitted &&
+         "DebugFunctionDefinition must be emitted");
+  assert(CurrentMAI && "CurrentMAI must be set");
+
+  SPIRV::ModuleAnalysisInfo &MAI = *CurrentMAI;
+
+  // Structural opcodes don't require a DebugLine, other opcodes might have
+  // already been emitted in the module scope.
+  if (!isDebugLineTarget(MI, MAI))
+    return;
+
+  // The range of DebugLine must be reset at each basic block boundary.
+  bool IsNewBlock = MI->getParent() != CurLineMBB;
+  if (IsNewBlock) {
+    clearCurLineState();
+    CurLineMBB = MI->getParent();
+  }
+
+  MCRegister VoidTypeReg = getOrEmitOpTypeVoidReg(MAI);
+  MCRegister ExtInstSetReg = MAI.getExtInstSetReg(NSSet);
+
+  const DILocation *DL = MI->getDebugLoc().get();
+  if (!DL) {
+    // No location for the current instruction
+    if (CurLineState) {
+      // Close the current DebugLine region.
+      emitExtInst(SPIRV::NonSemanticExtInst::DebugNoLine, VoidTypeReg,
+                  ExtInstSetReg, {}, MAI);
+      clearCurLineState();
+    }
+    // No DebugLine region to close.
+    return;
+  }
+
+  // At this point, there is a location for the current instruction.
+  // If it matches the one in the current state, no new DebugLine region is
+  // needed. Otherwise, emit a new DebugLine region and update the current
+  // state.
+
+  MCRegister FileStrReg = getCachedScopePathOpStringReg(
+      DL->getScope(), /*UseEmptyPathIfNullScope=*/true);
+  unsigned Line = DL->getLine();
+  unsigned Col = DL->getColumn();
+
+  MCRegister SrcReg = DebugSourceRegByFileStr.lookup(FileStrReg.id());
+  MCRegister LineReg = I32ConstantCache.lookup(Line);
+  MCRegister ColStartReg = I32ConstantCache.lookup(Col);
+  MCRegister ColEndReg = I32ConstantCache.lookup(Col + 1);
+
+  // Pre-emit walks IR instruction and debug-program-record locations; MIR is
+  // expected to reuse those same locations (or carry none). A miss here means
+  // codegen attached a source position that never appeared in the module IR.
+  assert(SrcReg.isValid() && LineReg.isValid() && ColStartReg.isValid() &&
+         ColEndReg.isValid() &&
+         "DebugLine operands must be pre-emitted in "
+         "emitNonSemanticGlobalDebugInfo()");
+
+  // Current location matches the one of the current state, no new DebugLine
+  // region is needed.
+  if (CurLineState && SrcReg == CurLineState->SrcReg &&
+      Line == CurLineState->Line && Col == CurLineState->Col)
+    return;
+
+  // A new DebugLine region is needed. Emit it and update the current state.
+  emitExtInst(SPIRV::NonSemanticExtInst::DebugLine, VoidTypeReg, ExtInstSetReg,
+              {SrcReg, LineReg, LineReg, ColStartReg, ColEndReg}, MAI);
+
+  CurLineState = DebugLineState{SrcReg, Line, Col};
 }
 
 void SPIRVNonSemanticDebugHandler::endInstruction() {
@@ -1384,6 +1511,17 @@ void SPIRVNonSemanticDebugHandler::emitNonSemanticGlobalDebugInfo(
     emitDebugGlobalVariable(GV, Info, VoidTypeReg, I32TypeReg, ExtInstSetReg,
                             MAI);
 
+  for (const DILocation *DL : UniqueDebugLocations) {
+    emitOpConstantI32(DL->getLine(), I32TypeReg, MAI);
+    emitOpConstantI32(DL->getColumn(), I32TypeReg, MAI);
+    emitOpConstantI32(DL->getColumn() + 1, I32TypeReg, MAI);
+    MCRegister FileStrReg =
+        getCachedScopePathOpStringReg(DL->getScope(),
+                                      /*UseEmptyPathIfNullScope=*/true);
+    getOrEmitDebugSourceForFileStrReg(FileStrReg, VoidTypeReg, ExtInstSetReg,
+                                      MAI);
+  }
+
   GlobalNSDIEnabled = true;
 }
 
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
index 4fce1b73004e1..a34c3becfb2e7 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
@@ -93,6 +93,10 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
   // DebugFunction emission.
   SmallVector<const DISubprogram *> SubprogramDefinitions;
 
+  // Distinct DILocations from instruction !dbg attachments and debug program
+  // records (#dbg_declare, #dbg_value, #dbg_assign, #dbg_label).
+  SmallVector<const DILocation *> UniqueDebugLocations;
+
   struct GlobalVariableDebugInfo {
     const DIExpression *Expr = nullptr;
     const GlobalVariable *LLVMGV = nullptr;
@@ -167,6 +171,16 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
 
   bool DebugFunctionDefinitionEmitted = false;
 
+  struct DebugLineState {
+    MCRegister SrcReg;
+    unsigned Line;
+    unsigned Col;
+  };
+  const MachineBasicBlock *CurLineMBB = nullptr;
+  std::optional<DebugLineState> CurLineState;
+
+  bool EmitDebugLineForCurrentFn = false;
+
 public:
   explicit SPIRVNonSemanticDebugHandler(AsmPrinter &AP);
 
@@ -231,6 +245,10 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
                                    SPIRV::ModuleAnalysisInfo &MAI);
 
   void resetPerFunctionDebugState();
+
+  void clearCurLineState();
+
+  void emitDebugLineForInstruction(const MachineInstr *MI);
   void preparePerFunctionDebug(const MachineFunction *MF);
   void tryEmitDebugFunctionDefinition(SPIRV::ModuleAnalysisInfo &MAI);
 
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition-call-before-alloca.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition-call-before-alloca.ll
index 19d9c1b09db2d..ed46c25d3ba20 100644
--- a/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition-call-before-alloca.ll
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition-call-before-alloca.ll
@@ -33,6 +33,7 @@
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF]] [[CALLER]]
 ; CHECK-NEXT: OpFunctionCall
 ; CHECK-NEXT: OpStore
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine
 ; CHECK-NEXT: OpReturnValue
 ; CHECK-NEXT: OpFunctionEnd
 
@@ -43,6 +44,7 @@
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF_NOARGS]] [[CALLER_NOARGS]]
 ; CHECK-NEXT: OpFunctionCall
 ; CHECK-NEXT: OpStore
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine
 ; CHECK-NEXT: OpReturn
 ; CHECK-NEXT: OpFunctionEnd
 
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition-calls.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition-calls.ll
index 19189b8d5bca7..8db6cc586a01e 100644
--- a/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition-calls.ll
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition-calls.ll
@@ -26,6 +26,7 @@
 ; CHECK-NEXT: OpFunctionParameter
 ; CHECK-NEXT: OpLabel
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF_LEAF]] [[LEAF]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine
 ; CHECK-NEXT: OpReturnValue
 ; CHECK-NEXT: OpFunctionEnd
 
@@ -36,7 +37,14 @@
 ; CHECK-NEXT: OpVariable {{.*}} Function
 ; CHECK-NEXT: OpVariable {{.*}} Function
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF_WITH]] [[WITH]]
-; CHECK: OpReturnValue
+; CHECK-NEXT: OpStore
+; CHECK-NEXT: OpStore
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine
+; CHECK-NEXT: OpFunctionCall
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugNoLine
+; CHECK-NEXT: OpIAdd
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine
+; CHECK-NEXT: OpReturnValue
 ; CHECK-NEXT: OpFunctionEnd
 
 ; caller_no_vars: calls both helpers, no locals -> after OpLabel.
@@ -44,8 +52,11 @@
 ; CHECK-NEXT: OpFunctionParameter
 ; CHECK-NEXT: OpLabel
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF_CALLER]] [[CALLER]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine
 ; CHECK-NEXT: OpFunctionCall
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine
 ; CHECK-NEXT: OpFunctionCall
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine
 ; CHECK-NEXT: OpReturnValue
 ; CHECK-NEXT: OpFunctionEnd
 
@@ -53,8 +64,11 @@
 ; CHECK: [[ORCH]] = OpFunction %{{.*}} ; -- Begin function orchestrator
 ; CHECK-NEXT: OpLabel
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF_ORCH]] [[ORCH]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine
 ; CHECK-NEXT: OpFunctionCall
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine
 ; CHECK-NEXT: OpFunctionCall
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine
 ; CHECK-NEXT: OpReturn
 ; CHECK-NEXT: OpFunctionEnd
 
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition-external-call.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition-external-call.ll
index da5deeb43ac1f..bd0c6b56a7dd4 100644
--- a/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition-external-call.ll
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-function-definition-external-call.ll
@@ -25,6 +25,7 @@
 ; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF]] [[CALLER]]
 ; CHECK-NEXT: OpStore
 ; CHECK-NEXT: OpFunctionCall
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine
 ; CHECK-NEXT: OpReturnValue
 ; CHECK-NEXT: OpFunctionEnd
 
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-line-block.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-line-block.ll
new file mode 100644
index 0000000000000..48431e188b9d3
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-line-block.ll
@@ -0,0 +1,62 @@
+; 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 DebugLine re-emission at basic block boundaries.
+
+; 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-line-block.c"
+; CHECK-DAG: [[DS:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugSource [[PATH]]
+; CHECK-DAG: [[DF:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugFunction {{.*}}
+; CHECK-DAG: [[V3:%[0-9]+]] = OpConstant [[I32]] 3{{$}}
+; CHECK-DAG: [[V4:%[0-9]+]] = OpConstant [[I32]] 4{{$}}
+; CHECK-DAG: [[V5:%[0-9]+]] = OpConstant [[I32]] 5{{$}}
+; CHECK-DAG: [[V10:%[0-9]+]] = OpConstant [[I32]] 10{{$}}
+; CHECK-DAG: [[V11:%[0-9]+]] = OpConstant [[I32]] 11{{$}}
+
+; entry block: add and br share line 3.
+; CHECK:      [[FN:%[0-9]+]] = OpFunction
+; CHECK-NEXT: OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF]] [[FN]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V3]] [[V3]] [[V10]] [[V11]]
+; CHECK-NEXT: [[T0:%[0-9]+]] = OpIAdd [[I32]]
+; CHECK-NOT:  OpExtInst [[VOID]] [[EXT]] DebugLine
+; CHECK-NEXT: OpBranch
+
+; then block: mul reuses line 3; region must be reopened at the block boundary.
+; CHECK:      OpLabel
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V3]] [[V3]] [[V10]] [[V11]]
+; CHECK-NEXT: [[T1:%[0-9]+]] = OpIMul [[I32]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V5]] [[V5]] [[V3]] [[V4]]
+; CHECK-NEXT: OpReturnValue [[T1]]
+; CHECK-NEXT: OpFunctionEnd
+
+target triple = "spirv64-unknown-unknown"
+
+define spir_func i32 @block_boundary(i32 %x) !dbg !5 {
+entry:
+  %t0 = add i32 %x, 1, !dbg !8   ; line 3, col 10
+  br label %then, !dbg !8        ; line 3, col 10 (same region as add)
+
+then:
+  %t1 = mul i32 %t0, %t0, !dbg !8 ; line 3, col 10 (same loc, new block)
+  ret i32 %t1, !dbg !9             ; line 5, col 3
+}
+
+!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-line-block.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: "block_boundary", linkageName: "block_boundary", scope: !1, file: !1, line: 1, type: !4, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!8 = !DILocation(line: 3, column: 10, scope: !5)
+!9 = !DILocation(line: 5, column: 3, scope: !5)
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-line-calls.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-line-calls.ll
new file mode 100644
index 0000000000000..8928b121401bc
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-line-calls.ll
@@ -0,0 +1,83 @@
+; 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 %}
+
+; 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-line-calls.c"
+; CHECK-DAG: [[DS:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugSource [[PATH]]
+; CHECK-DAG: [[NAME_INC:%[0-9]+]] = OpString "inc"
+; CHECK-DAG: [[NAME_CALLER:%[0-9]+]] = OpString "caller"
+; CHECK-DAG: [[DF_INC:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugFunction [[NAME_INC]]
+; CHECK-DAG: [[DF_CALLER:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugFunction [[NAME_CALLER]]
+; CHECK-DAG: [[V2:%[0-9]+]] = OpConstant [[I32]] 2{{$}}
+; CHECK-DAG: [[V3:%[0-9]+]] = OpConstant [[I32]] 3{{$}}
+; CHECK-DAG: [[V4:%[0-9]+]] = OpConstant [[I32]] 4{{$}}
+; CHECK-DAG: [[V5:%[0-9]+]] = OpConstant [[I32]] 5{{$}}
+; CHECK-DAG: [[V6:%[0-9]+]] = OpConstant [[I32]] 6{{$}}
+; CHECK-DAG: [[V7:%[0-9]+]] = OpConstant [[I32]] 7{{$}}
+; CHECK-DAG: [[V8:%[0-9]+]] = OpConstant [[I32]] 8{{$}}
+; CHECK-DAG: [[V10:%[0-9]+]] = OpConstant [[I32]] 10{{$}}
+; CHECK-DAG: [[V11:%[0-9]+]] = OpConstant [[I32]] 11{{$}}
+; CHECK-DAG: [[V12:%[0-9]+]] = OpConstant [[I32]] 12{{$}}
+; CHECK-DAG: [[V13:%[0-9]+]] = OpConstant [[I32]] 13{{$}}
+
+; inc: add and return.
+; CHECK:      [[INC:%[0-9]+]] = OpFunction
+; CHECK-NEXT: OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF_INC]] [[INC]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V2]] [[V2]] [[V10]] [[V11]]
+; CHECK-NEXT: [[T0:%[0-9]+]] = OpIAdd [[I32]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V3]] [[V3]] [[V3]] [[V4]]
+; CHECK-NEXT: OpReturnValue [[T0]]
+; CHECK-NEXT: OpFunctionEnd
+
+; caller: two calls to inc, then return.
+; CHECK:      [[CALLER:%[0-9]+]] = OpFunction
+; CHECK-NEXT: OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF_CALLER]] [[CALLER]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V6]] [[V6]] [[V12]] [[V13]]
+; CHECK-NEXT: OpFunctionCall [[I32]] [[INC]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V7]] [[V7]] [[V12]] [[V13]]
+; CHECK-NEXT: OpFunctionCall [[I32]] [[INC]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V8]] [[V8]] [[V3]] [[V4]]
+; CHECK-NEXT: OpReturnValue
+; CHECK-NEXT: OpFunctionEnd
+
+target triple = "spirv64-unknown-unknown"
+
+define spir_func i32 @inc(i32 %x) !dbg !5 {
+entry:
+  %r = add i32 %x, 1, !dbg !8   ; line 2, col 10
+  ret i32 %r, !dbg !9           ; line 3, col 3
+}
+
+define spir_func i32 @caller(i32 %x) !dbg !10 {
+entry:
+  %a = call spir_func i32 @inc(i32 %x), !dbg !13   ; line 6, col 12
+  %b = call spir_func i32 @inc(i32 %a), !dbg !14   ; line 7, col 12
+  ret i32 %b, !dbg !15                              ; line 8, col 3
+}
+
+!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-line-calls.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: "inc", linkageName: "inc", scope: !1, file: !1, line: 1, type: !4, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!8 = !DILocation(line: 2, column: 10, scope: !5)
+!9 = !DILocation(line: 3, column: 3, scope: !5)
+
+!10 = distinct !DISubprogram(name: "caller", linkageName: "caller", scope: !1, file: !1, line: 5, type: !4, scopeLine: 5, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!13 = !DILocation(line: 6, column: 12, scope: !10)
+!14 = !DILocation(line: 7, column: 12, scope: !10)
+!15 = !DILocation(line: 8, column: 3, scope: !10)
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-line-shared.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-line-shared.ll
new file mode 100644
index 0000000000000..6f106d8ba4d44
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-line-shared.ll
@@ -0,0 +1,55 @@
+; 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 DebugLine deduplication within a basic block: consecutive instructions
+; with the same source location share one DebugLine region.
+
+; 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-line-shared.c"
+; CHECK-DAG: [[DS:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugSource [[PATH]]
+; CHECK-DAG: [[DF:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugFunction {{.*}}
+; CHECK-DAG: [[V3:%[0-9]+]] = OpConstant [[I32]] 3{{$}}
+; CHECK-DAG: [[V4:%[0-9]+]] = OpConstant [[I32]] 4{{$}}
+; CHECK-DAG: [[V5:%[0-9]+]] = OpConstant [[I32]] 5{{$}}
+; CHECK-DAG: [[V10:%[0-9]+]] = OpConstant [[I32]] 10{{$}}
+; CHECK-DAG: [[V11:%[0-9]+]] = OpConstant [[I32]] 11{{$}}
+
+; CHECK:      [[FN:%[0-9]+]] = OpFunction
+; CHECK-NEXT: [[A:%[0-9]+]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%[0-9]+]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF]] [[FN]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V3]] [[V3]] [[V10]] [[V11]]
+; CHECK-NEXT: [[T0:%[0-9]+]] = OpIAdd [[I32]] [[A]] [[B]]
+; CHECK-NOT:  OpExtInst [[VOID]] [[EXT]] DebugLine
+; CHECK-NEXT: [[T1:%[0-9]+]] = OpIMul [[I32]] [[T0]] [[A]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V5]] [[V5]] [[V3]] [[V4]]
+; CHECK-NEXT: OpReturnValue [[T1]]
+; CHECK-NEXT: OpFunctionEnd
+
+target triple = "spirv64-unknown-unknown"
+
+define spir_func i32 @shared_line(i32 %a, i32 %b) !dbg !5 {
+entry:
+  %t0 = add i32 %a, %b, !dbg !8   ; line 3, col 10
+  %t1 = mul i32 %t0, %a, !dbg !8  ; line 3, col 10 (same location)
+  ret i32 %t1, !dbg !9            ; line 5, col 3
+}
+
+!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-line-shared.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}
+!7 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+
+!5 = distinct !DISubprogram(name: "shared_line", linkageName: "shared_line", scope: !1, file: !1, line: 1, type: !4, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!8 = !DILocation(line: 3, column: 10, scope: !5)
+!9 = !DILocation(line: 5, column: 3, scope: !5)
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-line.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-line.ll
new file mode 100644
index 0000000000000..0178126982ca2
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-line.ll
@@ -0,0 +1,68 @@
+; 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 DebugLine emission:
+;
+;   OpExtInst %void %ext DebugLine %src %lineStart %lineEnd %colStart %colEnd
+;
+; with LineStart == LineEnd == DILocation::getLine(), ColStart == getCol(),
+; ColEnd == getCol() + 1. All four are 32-bit integer OpConstants.
+;
+; DebugLine is an *in-block* instruction: it is emitted inline, in program order,
+; immediately before the semantic instruction whose source location it describes,
+; and applies to all following instructions until the next DebugLine, the next
+; DebugNoLine, or the end of the block.
+;
+
+; 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-line.c"
+; CHECK-DAG: [[DS:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugSource [[PATH]]
+; CHECK-DAG: [[DF:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugFunction {{.*}}
+; CHECK-DAG: [[V3:%[0-9]+]] = OpConstant [[I32]] 3{{$}}
+; CHECK-DAG: [[V4:%[0-9]+]] = OpConstant [[I32]] 4{{$}}
+; CHECK-DAG: [[V5:%[0-9]+]] = OpConstant [[I32]] 5{{$}}
+; CHECK-DAG: [[V10:%[0-9]+]] = OpConstant [[I32]] 10{{$}}
+; CHECK-DAG: [[V11:%[0-9]+]] = OpConstant [[I32]] 11{{$}}
+; CHECK-DAG: [[V12:%[0-9]+]] = OpConstant [[I32]] 12{{$}}
+; CHECK-DAG: [[V13:%[0-9]+]] = OpConstant [[I32]] 13{{$}}
+
+; CHECK:      [[FN:%[0-9]+]] = OpFunction
+; CHECK-NEXT: [[A:%[0-9]+]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%[0-9]+]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF]] [[FN]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V3]] [[V3]] [[V10]] [[V11]]
+; CHECK-NEXT: [[T0:%[0-9]+]] = OpIAdd [[I32]] [[A]] [[B]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V4]] [[V4]] [[V12]] [[V13]]
+; CHECK-NEXT: [[T1:%[0-9]+]] = OpIMul [[I32]] [[T0]] [[A]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V5]] [[V5]] [[V3]] [[V4]]
+; CHECK-NEXT: OpReturnValue [[T1]]
+; CHECK-NEXT: OpFunctionEnd
+
+target triple = "spirv64-unknown-unknown"
+
+define spir_func i32 @add_line(i32 %a, i32 %b) !dbg !5 {
+entry:
+  %t0 = add i32 %a, %b, !dbg !8   ; line 3, col 10
+  %t1 = mul i32 %t0, %a, !dbg !9  ; line 4, col 12
+  ret i32 %t1, !dbg !10           ; line 5, col 3
+}
+
+!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-line.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}
+!7 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+
+!5 = distinct !DISubprogram(name: "add_line", linkageName: "add_line", scope: !1, file: !1, line: 1, type: !4, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!8 = !DILocation(line: 3, column: 10, scope: !5)
+!9 = !DILocation(line: 4, column: 12, scope: !5)
+!10 = !DILocation(line: 5, column: 3, scope: !5)
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-no-line.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-no-line.ll
new file mode 100644
index 0000000000000..ce5acc50391bb
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-no-line.ll
@@ -0,0 +1,59 @@
+; 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 DebugNoLine emission.
+;
+; DebugNoLine has no operands. Per the NonSemantic.Shader.DebugInfo
+; spec it "discontinues any source-level line and column information specified by
+; any previous DebugLine instruction" and must appear within a block.
+;
+
+; 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-no-line.c"
+; CHECK-DAG: [[DS:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugSource [[PATH]]
+; CHECK-DAG: [[DF:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugFunction {{.*}}
+; CHECK-DAG: [[V3:%[0-9]+]] = OpConstant [[I32]] 3{{$}}
+; CHECK-DAG: [[V5:%[0-9]+]] = OpConstant [[I32]] 5{{$}}
+; CHECK-DAG: [[V10:%[0-9]+]] = OpConstant [[I32]] 10{{$}}
+; CHECK-DAG: [[V11:%[0-9]+]] = OpConstant [[I32]] 11{{$}}
+; CHECK-DAG: [[V4:%[0-9]+]] = OpConstant [[I32]] 4{{$}}
+
+; CHECK:      [[FN:%[0-9]+]] = OpFunction
+; CHECK-NEXT: [[A:%[0-9]+]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%[0-9]+]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF]] [[FN]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V3]] [[V3]] [[V10]] [[V11]]
+; CHECK-NEXT: [[T0:%[0-9]+]] = OpIAdd [[I32]] [[A]] [[B]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugNoLine
+; CHECK-NEXT: [[T1:%[0-9]+]] = OpIMul [[I32]] [[T0]] [[A]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[V5]] [[V5]] [[V3]] [[V4]]
+; CHECK-NEXT: OpReturnValue [[T1]]
+; CHECK-NEXT: OpFunctionEnd
+
+target triple = "spirv64-unknown-unknown"
+
+define spir_func i32 @maybe_line(i32 %a, i32 %b) !dbg !5 {
+entry:
+  %t0 = add i32 %a, %b, !dbg !8   ; line 3, col 10
+  %t1 = mul i32 %t0, %a           ; no debug location
+  ret i32 %t1, !dbg !10           ; line 5, col 3
+}
+
+!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-no-line.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}
+!7 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+
+!5 = distinct !DISubprogram(name: "maybe_line", linkageName: "maybe_line", scope: !1, file: !1, line: 1, type: !4, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!8 = !DILocation(line: 3, column: 10, scope: !5)
+!10 = !DILocation(line: 5, column: 3, scope: !5)

>From 79a00781734ee1bf9eacf2a1402b5e1691271a3d Mon Sep 17 00:00:00 2001
From: Manuel Carrasco <Manuel.Carrasco at amd.com>
Date: Fri, 14 Aug 2026 07:55:26 -0500
Subject: [PATCH 2/7] [reviews] Use SetVector.

---
 .../SPIRV/SPIRVNonSemanticDebugHandler.cpp       | 16 ++++++----------
 .../Target/SPIRV/SPIRVNonSemanticDebugHandler.h  |  3 ++-
 2 files changed, 8 insertions(+), 11 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
index b158cea940815..949dc612788f5 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
@@ -11,7 +11,7 @@
 #include "MCTargetDesc/SPIRVMCTargetDesc.h"
 #include "SPIRVSubtarget.h"
 #include "SPIRVUtils.h"
-#include "llvm/ADT/SmallPtrSet.h"
+#include "llvm/ADT/SetVector.h"
 #include "llvm/ADT/SmallVectorExtras.h"
 #include "llvm/ADT/Twine.h"
 #include "llvm/BinaryFormat/Dwarf.h"
@@ -258,20 +258,16 @@ unsigned SPIRVNonSemanticDebugHandler::toNSDISrcLang(unsigned DwarfSrcLang) {
 }
 
 static void collectUniqueDebugLocations(const Module &M,
-                                        SmallVector<const DILocation *> &Out) {
-  SmallPtrSet<const DILocation *, 16> Seen;
-  auto AddUnique = [&](const DILocation *DL) {
-    if (DL && Seen.insert(DL).second)
-      Out.push_back(DL);
-  };
-
+                                        SetVector<const DILocation *> &Out) {
   for (const Function &F : M) {
     if (!F.getSubprogram())
       continue;
     for (const Instruction &I : instructions(F)) {
-      AddUnique(I.getDebugLoc().get());
+      if (const DILocation *DL = I.getDebugLoc().get())
+        Out.insert(DL);
       for (DbgRecord &DR : I.getDbgRecordRange())
-        AddUnique(DR.getDebugLoc().get());
+        if (const DILocation *DL = DR.getDebugLoc().get())
+          Out.insert(DL);
     }
   }
 }
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
index a34c3becfb2e7..42c3911570b0b 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
@@ -21,6 +21,7 @@
 #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/ADT/StringMap.h"
@@ -95,7 +96,7 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
 
   // Distinct DILocations from instruction !dbg attachments and debug program
   // records (#dbg_declare, #dbg_value, #dbg_assign, #dbg_label).
-  SmallVector<const DILocation *> UniqueDebugLocations;
+  SetVector<const DILocation *> UniqueDebugLocations;
 
   struct GlobalVariableDebugInfo {
     const DIExpression *Expr = nullptr;

>From 1e6b0556fd5c8e666395db153cbe14a329decbbb Mon Sep 17 00:00:00 2001
From: Manuel Carrasco <Manuel.Carrasco at amd.com>
Date: Fri, 14 Aug 2026 08:02:16 -0500
Subject: [PATCH 3/7] [reviews] Relax handling of untracked locations.

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

diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
index 949dc612788f5..0ec34d262ecfb 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
@@ -257,6 +257,10 @@ unsigned SPIRVNonSemanticDebugHandler::toNSDISrcLang(unsigned DwarfSrcLang) {
   }
 }
 
+// Collect distinct DILocations from LLVM IR. DebugLine pre-emission and MIR
+// lookups assume every machine-instruction debug location already appeared
+// here; a codegen-only location would not be collected and emission will be
+// skipped.
 static void collectUniqueDebugLocations(const Module &M,
                                         SetVector<const DILocation *> &Out) {
   for (const Function &F : M) {
@@ -1261,13 +1265,14 @@ void SPIRVNonSemanticDebugHandler::emitDebugLineForInstruction(
   MCRegister ColStartReg = I32ConstantCache.lookup(Col);
   MCRegister ColEndReg = I32ConstantCache.lookup(Col + 1);
 
-  // Pre-emit walks IR instruction and debug-program-record locations; MIR is
-  // expected to reuse those same locations (or carry none). A miss here means
-  // codegen attached a source position that never appeared in the module IR.
-  assert(SrcReg.isValid() && LineReg.isValid() && ColStartReg.isValid() &&
-         ColEndReg.isValid() &&
-         "DebugLine operands must be pre-emitted in "
-         "emitNonSemanticGlobalDebugInfo()");
+  // The elements of each collected DILocation (DebugSource, line/column
+  // constants) are pre-emitted from LLVM-IR instruction !dbg attachments and
+  // debug-program records; MIR is expected to reuse those same locations (or
+  // carry none). A lookup miss means codegen attached a source position whose
+  // elements were never pre-emitted, and debug-line emission is skipped.
+  if (!SrcReg.isValid() || !LineReg.isValid() || !ColStartReg.isValid() ||
+      !ColEndReg.isValid())
+    return;
 
   // Current location matches the one of the current state, no new DebugLine
   // region is needed.

>From 0a9d67b4df74c5ca06dd3f425f07dbffa09373cf Mon Sep 17 00:00:00 2001
From: Manuel Carrasco <Manuel.Carrasco at amd.com>
Date: Fri, 14 Aug 2026 09:06:09 -0500
Subject: [PATCH 4/7] [reviews] Remove state variable.

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

diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
index 0ec34d262ecfb..b491f7d07d98c 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
@@ -1125,7 +1125,6 @@ void SPIRVNonSemanticDebugHandler::resetPerFunctionDebugState() {
   CurrentMF = nullptr;
   LastFunctionOpVariable = nullptr;
   DebugFunctionDefinitionEmitted = false;
-  EmitDebugLineForCurrentFn = false;
   clearCurLineState();
   CurLineMBB = nullptr;
 }
@@ -1149,8 +1148,6 @@ void SPIRVNonSemanticDebugHandler::preparePerFunctionDebug(
   if (!SP || !SP->isDefinition())
     return;
 
-  EmitDebugLineForCurrentFn = true;
-
   // DebugFunctionDefinition is emitted after the last function-level
   // OpVariable. If there are none, it is emitted after the entry OpLabel.
   LastFunctionOpVariable =
@@ -1194,7 +1191,7 @@ void SPIRVNonSemanticDebugHandler::beginInstruction(const MachineInstr *MI) {
   assert(CurMI == nullptr && "CurMI must be null");
   CurMI = MI;
 
-  if (!EmitDebugLineForCurrentFn || !DebugFunctionDefinitionEmitted)
+  if (!DebugFunctionDefinitionEmitted)
     return;
   emitDebugLineForInstruction(MI);
 }
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
index 42c3911570b0b..4bab584e37230 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
@@ -180,8 +180,6 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
   const MachineBasicBlock *CurLineMBB = nullptr;
   std::optional<DebugLineState> CurLineState;
 
-  bool EmitDebugLineForCurrentFn = false;
-
 public:
   explicit SPIRVNonSemanticDebugHandler(AsmPrinter &AP);
 

>From e3b0d11079cf0e2e9394277cf869f71473c7248c Mon Sep 17 00:00:00 2001
From: Manuel Carrasco <Manuel.Carrasco at amd.com>
Date: Fri, 14 Aug 2026 09:08:48 -0500
Subject: [PATCH 5/7] [reviews] Remove helper.

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

diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
index b491f7d07d98c..1cc0c49ffae16 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
@@ -1125,12 +1125,10 @@ void SPIRVNonSemanticDebugHandler::resetPerFunctionDebugState() {
   CurrentMF = nullptr;
   LastFunctionOpVariable = nullptr;
   DebugFunctionDefinitionEmitted = false;
-  clearCurLineState();
+  CurLineState.reset();
   CurLineMBB = nullptr;
 }
 
-void SPIRVNonSemanticDebugHandler::clearCurLineState() { CurLineState.reset(); }
-
 void SPIRVNonSemanticDebugHandler::preparePerFunctionDebug(
     const MachineFunction *MF) {
   resetPerFunctionDebugState();
@@ -1227,7 +1225,7 @@ void SPIRVNonSemanticDebugHandler::emitDebugLineForInstruction(
   // The range of DebugLine must be reset at each basic block boundary.
   bool IsNewBlock = MI->getParent() != CurLineMBB;
   if (IsNewBlock) {
-    clearCurLineState();
+    CurLineState.reset();
     CurLineMBB = MI->getParent();
   }
 
@@ -1241,7 +1239,7 @@ void SPIRVNonSemanticDebugHandler::emitDebugLineForInstruction(
       // Close the current DebugLine region.
       emitExtInst(SPIRV::NonSemanticExtInst::DebugNoLine, VoidTypeReg,
                   ExtInstSetReg, {}, MAI);
-      clearCurLineState();
+      CurLineState.reset();
     }
     // No DebugLine region to close.
     return;
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
index 4bab584e37230..81c320f608c31 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
@@ -245,8 +245,6 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
 
   void resetPerFunctionDebugState();
 
-  void clearCurLineState();
-
   void emitDebugLineForInstruction(const MachineInstr *MI);
   void preparePerFunctionDebug(const MachineFunction *MF);
   void tryEmitDebugFunctionDefinition(SPIRV::ModuleAnalysisInfo &MAI);

>From 36f576746cfcdc22d9672f5ce2a191f33a7b0045 Mon Sep 17 00:00:00 2001
From: Manuel Carrasco <Manuel.Carrasco at amd.com>
Date: Fri, 14 Aug 2026 09:31:18 -0500
Subject: [PATCH 6/7] [reviews] Simplify state logic.

---
 .../SPIRV/SPIRVNonSemanticDebugHandler.cpp     | 18 ++++++++----------
 .../SPIRV/SPIRVNonSemanticDebugHandler.h       |  4 ++--
 2 files changed, 10 insertions(+), 12 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
index 1cc0c49ffae16..b48592c9d6ed1 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
@@ -1125,8 +1125,7 @@ void SPIRVNonSemanticDebugHandler::resetPerFunctionDebugState() {
   CurrentMF = nullptr;
   LastFunctionOpVariable = nullptr;
   DebugFunctionDefinitionEmitted = false;
-  CurLineState.reset();
-  CurLineMBB = nullptr;
+  LastLineState.reset();
 }
 
 void SPIRVNonSemanticDebugHandler::preparePerFunctionDebug(
@@ -1223,10 +1222,9 @@ void SPIRVNonSemanticDebugHandler::emitDebugLineForInstruction(
     return;
 
   // The range of DebugLine must be reset at each basic block boundary.
-  bool IsNewBlock = MI->getParent() != CurLineMBB;
+  bool IsNewBlock = LastLineState && MI->getParent() != LastLineState->MBB;
   if (IsNewBlock) {
-    CurLineState.reset();
-    CurLineMBB = MI->getParent();
+    LastLineState.reset();
   }
 
   MCRegister VoidTypeReg = getOrEmitOpTypeVoidReg(MAI);
@@ -1235,11 +1233,11 @@ void SPIRVNonSemanticDebugHandler::emitDebugLineForInstruction(
   const DILocation *DL = MI->getDebugLoc().get();
   if (!DL) {
     // No location for the current instruction
-    if (CurLineState) {
+    if (LastLineState) {
       // Close the current DebugLine region.
       emitExtInst(SPIRV::NonSemanticExtInst::DebugNoLine, VoidTypeReg,
                   ExtInstSetReg, {}, MAI);
-      CurLineState.reset();
+      LastLineState.reset();
     }
     // No DebugLine region to close.
     return;
@@ -1271,15 +1269,15 @@ void SPIRVNonSemanticDebugHandler::emitDebugLineForInstruction(
 
   // Current location matches the one of the current state, no new DebugLine
   // region is needed.
-  if (CurLineState && SrcReg == CurLineState->SrcReg &&
-      Line == CurLineState->Line && Col == CurLineState->Col)
+  if (LastLineState && SrcReg == LastLineState->SrcReg &&
+      Line == LastLineState->Line && Col == LastLineState->Col)
     return;
 
   // A new DebugLine region is needed. Emit it and update the current state.
   emitExtInst(SPIRV::NonSemanticExtInst::DebugLine, VoidTypeReg, ExtInstSetReg,
               {SrcReg, LineReg, LineReg, ColStartReg, ColEndReg}, MAI);
 
-  CurLineState = DebugLineState{SrcReg, Line, Col};
+  LastLineState = DebugLineState{SrcReg, Line, Col, MI->getParent()};
 }
 
 void SPIRVNonSemanticDebugHandler::endInstruction() {
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
index 81c320f608c31..678bdaff62353 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
@@ -176,9 +176,9 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
     MCRegister SrcReg;
     unsigned Line;
     unsigned Col;
+    const MachineBasicBlock *MBB;
   };
-  const MachineBasicBlock *CurLineMBB = nullptr;
-  std::optional<DebugLineState> CurLineState;
+  std::optional<DebugLineState> LastLineState;
 
 public:
   explicit SPIRVNonSemanticDebugHandler(AsmPrinter &AP);

>From cc511f8648d47bb2471d37d7b6947ae9009a4939 Mon Sep 17 00:00:00 2001
From: Manuel Carrasco <Manuel.Carrasco at amd.com>
Date: Fri, 14 Aug 2026 09:38:53 -0500
Subject: [PATCH 7/7] [reviews] Simplify logic.

---
 .../SPIRV/SPIRVNonSemanticDebugHandler.cpp    | 27 ++++++++-----------
 .../SPIRV/SPIRVNonSemanticDebugHandler.h      |  8 +-----
 2 files changed, 12 insertions(+), 23 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
index b48592c9d6ed1..81fb9292df464 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
@@ -1125,7 +1125,7 @@ void SPIRVNonSemanticDebugHandler::resetPerFunctionDebugState() {
   CurrentMF = nullptr;
   LastFunctionOpVariable = nullptr;
   DebugFunctionDefinitionEmitted = false;
-  LastLineState.reset();
+  LastLineMI = nullptr;
 }
 
 void SPIRVNonSemanticDebugHandler::preparePerFunctionDebug(
@@ -1222,10 +1222,8 @@ void SPIRVNonSemanticDebugHandler::emitDebugLineForInstruction(
     return;
 
   // The range of DebugLine must be reset at each basic block boundary.
-  bool IsNewBlock = LastLineState && MI->getParent() != LastLineState->MBB;
-  if (IsNewBlock) {
-    LastLineState.reset();
-  }
+  if (LastLineMI && MI->getParent() != LastLineMI->getParent())
+    LastLineMI = nullptr;
 
   MCRegister VoidTypeReg = getOrEmitOpTypeVoidReg(MAI);
   MCRegister ExtInstSetReg = MAI.getExtInstSetReg(NSSet);
@@ -1233,20 +1231,19 @@ void SPIRVNonSemanticDebugHandler::emitDebugLineForInstruction(
   const DILocation *DL = MI->getDebugLoc().get();
   if (!DL) {
     // No location for the current instruction
-    if (LastLineState) {
+    if (LastLineMI) {
       // Close the current DebugLine region.
       emitExtInst(SPIRV::NonSemanticExtInst::DebugNoLine, VoidTypeReg,
                   ExtInstSetReg, {}, MAI);
-      LastLineState.reset();
+      LastLineMI = nullptr;
     }
     // No DebugLine region to close.
     return;
   }
 
   // At this point, there is a location for the current instruction.
-  // If it matches the one in the current state, no new DebugLine region is
-  // needed. Otherwise, emit a new DebugLine region and update the current
-  // state.
+  // If it matches the last emitted DebugLine, no new DebugLine region is
+  // needed. Otherwise, emit a new DebugLine region and update LastLineMI.
 
   MCRegister FileStrReg = getCachedScopePathOpStringReg(
       DL->getScope(), /*UseEmptyPathIfNullScope=*/true);
@@ -1267,17 +1264,15 @@ void SPIRVNonSemanticDebugHandler::emitDebugLineForInstruction(
       !ColEndReg.isValid())
     return;
 
-  // Current location matches the one of the current state, no new DebugLine
-  // region is needed.
-  if (LastLineState && SrcReg == LastLineState->SrcReg &&
-      Line == LastLineState->Line && Col == LastLineState->Col)
+  // Current location matches the last emitted DebugLine region.
+  if (LastLineMI && MI->getDebugLoc() == LastLineMI->getDebugLoc())
     return;
 
-  // A new DebugLine region is needed. Emit it and update the current state.
+  // A new DebugLine region is needed. Emit it and update LastLineMI.
   emitExtInst(SPIRV::NonSemanticExtInst::DebugLine, VoidTypeReg, ExtInstSetReg,
               {SrcReg, LineReg, LineReg, ColStartReg, ColEndReg}, MAI);
 
-  LastLineState = DebugLineState{SrcReg, Line, Col, MI->getParent()};
+  LastLineMI = MI;
 }
 
 void SPIRVNonSemanticDebugHandler::endInstruction() {
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
index 678bdaff62353..17ca354778588 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
@@ -172,13 +172,7 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
 
   bool DebugFunctionDefinitionEmitted = false;
 
-  struct DebugLineState {
-    MCRegister SrcReg;
-    unsigned Line;
-    unsigned Col;
-    const MachineBasicBlock *MBB;
-  };
-  std::optional<DebugLineState> LastLineState;
+  const MachineInstr *LastLineMI = nullptr;
 
 public:
   explicit SPIRVNonSemanticDebugHandler(AsmPrinter &AP);



More information about the llvm-commits mailing list