[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