[llvm-branch-commits] [llvm] [SPIRV] Emit DebugLexicalBlockDiscriminator for a DILexicalBlockFile. (PR #232795)
Manuel Carrasco via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Mon Oct 19 05:57:38 PDT 2026
https://github.com/mgcarrasco updated https://github.com/llvm/llvm-project/pull/232795
>From 60b68aca86b0918421554520dd4119a3a6cf92db Mon Sep 17 00:00:00 2001
From: Manuel Carrasco <Manuel.Carrasco at amd.com>
Date: Fri, 16 Oct 2026 09:08:15 -0500
Subject: [PATCH] [SPIRV] Emit DebugLexicalBlockDiscriminator for a
DILexicalBlockFile.
A location scoped at a DILexicalBlockFile was emitted as a DebugScope of the compilation unit, so the discriminator did not round-trip.
Use the discriminator as the DebugScope operand, matching SPIRV-LLVM-Translator.
---
.../SPIRV/SPIRVNonSemanticDebugHandler.cpp | 75 +++++++++++--------
.../SPIRV/SPIRVNonSemanticDebugHandler.h | 23 ++++--
.../debug-lexical-block-discriminator.ll | 67 +++++++++++++++++
.../debug-scope-lexical-block-file.ll | 12 +--
4 files changed, 133 insertions(+), 44 deletions(-)
create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-lexical-block-discriminator.ll
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
index d0412e95ed213..0655252c7558b 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
@@ -46,6 +46,12 @@ static cl::opt<bool> SPIRVDebugScopeForwardRefs(
"SPV_KHR_relaxed_extended_instruction is enabled"),
cl::init(false));
+// When disabled, unwrap a DILexicalBlockFile to its enclosing scope.
+static cl::opt<bool> SPIRVDebugLexicalBlockDiscriminatorScope(
+ "spirv-debug-lexical-block-discriminator-scope",
+ cl::desc("Use a DebugLexicalBlockDiscriminator as a DebugScope"),
+ cl::init(false));
+
namespace {
/// Look up \p Key in a register map and return its value, or std::nullopt when
@@ -313,20 +319,6 @@ static void collectDebugLocationsAndLocalVariables(
}
}
-// Insert \p S and its enclosing DILexicalBlock/DINamespace chain into \p Out,
-// parent before child, so single-pass emission never needs a forward
-// reference for the Parent operand.
-static void collectLexicalBlockChain(const DIScope *S,
- SetVector<const DIScope *> &Out) {
- // Walk up child-first, then insert in reverse to get parents in first.
- SmallVector<const DIScope *, 8> Chain;
- while (S && !Out.contains(S) && isa<DILexicalBlock, DINamespace>(S)) {
- Chain.push_back(S);
- S = S->getScope();
- }
- Out.insert(Chain.rbegin(), Chain.rend());
-}
-
void SPIRVNonSemanticDebugHandler::beginModule(Module *M) {
// The base class sets Asm = nullptr when the module has no compile units,
// and initializes lexical scope tracking otherwise.
@@ -434,11 +426,9 @@ void SPIRVNonSemanticDebugHandler::beginModule(Module *M) {
collectDebugLocationsAndLocalVariables(*M, UniqueDebugLocations,
LocalVariables);
- // DILexicalBlock and DINamespace scopes are lowered to DebugLexicalBlock.
- // Collect them in parent-before-child order so they can be later emitted in a
- // single pass.
for (const DIScope *S : Finder.scopes())
- collectLexicalBlockChain(S, LexicalBlocks);
+ if (isa<DILexicalBlock, DINamespace, DILexicalBlockFile>(S))
+ LexicalBlocks.insert(S);
}
// The emitted SPIR-V id of \p MI, or an invalid register when \p MI defines
@@ -947,6 +937,9 @@ auto SPIRVNonSemanticDebugHandler::emitDebugScope(const DIScope *S)
if (isa<DILexicalBlock, DINamespace>(S))
return emitDebugLexicalBlock(S, VoidTypeReg, I32TypeReg, ExtInstSetReg,
MAI);
+ if (const auto *LBF = dyn_cast<DILexicalBlockFile>(S))
+ return emitDebugLexicalBlockDiscriminator(LBF, VoidTypeReg, I32TypeReg,
+ ExtInstSetReg, MAI);
if (const auto *SP = dyn_cast<DISubprogram>(S)) {
if (SP->isDefinition())
return emitDebugFunction(SP, VoidTypeReg, I32TypeReg, ExtInstSetReg, MAI);
@@ -1072,14 +1065,16 @@ auto SPIRVNonSemanticDebugHandler::emitDebugTypeFunctionForSubroutineType(
}
// Match SPIRV-LLVM-Translator's selection logic for the Parent operand.
-SPIRVNonSemanticDebugHandler::EmitResult<MCRegister>
-SPIRVNonSemanticDebugHandler::resolveScope(const DIScope *Scope,
- const DICompileUnit *FallbackCU) {
- // DebugLexicalBlockDiscriminator cannot be used as a scope, so unwrap it.
- while (const auto *DLBF = dyn_cast_or_null<DILexicalBlockFile>(Scope))
- Scope = DLBF->getScope();
-
- if (isa_and_nonnull<DIType, DILexicalBlock, DINamespace, DISubprogram>(Scope))
+auto SPIRVNonSemanticDebugHandler::resolveScope(const DIScope *Scope,
+ const DICompileUnit *FallbackCU)
+ -> EmitResult<MCRegister> {
+ if (!SPIRVDebugLexicalBlockDiscriminatorScope)
+ while (const auto *LBF = dyn_cast_or_null<DILexicalBlockFile>(Scope))
+ Scope = LBF->getScope();
+
+ // These scopes have their own debug instruction.
+ if (isa_and_nonnull<DIType, DILexicalBlock, DINamespace, DISubprogram,
+ DILexicalBlockFile>(Scope))
return getOrCreateDebugScope(Scope);
// For a file, compile-unit, or absent scope, fall back to a compile unit.
@@ -1131,6 +1126,26 @@ auto SPIRVNonSemanticDebugHandler::emitDebugLexicalBlock(
PreparedScope{SPIRV::NonSemanticExtInst::DebugLexicalBlock, Ops});
}
+auto SPIRVNonSemanticDebugHandler::emitDebugLexicalBlockDiscriminator(
+ const DILexicalBlockFile *LBF, MCRegister VoidTypeReg,
+ MCRegister I32TypeReg, MCRegister ExtInstSetReg,
+ SPIRV::ModuleAnalysisInfo &MAI) -> EmitResult<PreparedScope> {
+ assert(LBF && "LBF must not be null");
+ EmitResult<MCRegister> Parent = resolveScope(LBF->getScope());
+ if (!Parent)
+ return EmitResult<PreparedScope>::failure(Parent);
+
+ MCRegister FileStrReg = getCachedScopePathOpStringReg(
+ LBF->getFile(), /*UseEmptyPathIfNullScope=*/true);
+ MCRegister SrcReg = getOrEmitDebugSourceForFileStrReg(FileStrReg, VoidTypeReg,
+ ExtInstSetReg, MAI);
+ MCRegister DiscReg = emitOpConstantI32(
+ static_cast<uint32_t>(LBF->getDiscriminator()), I32TypeReg, MAI);
+ return EmitResult<PreparedScope>::emitted(
+ PreparedScope{SPIRV::NonSemanticExtInst::DebugLexicalBlockDiscriminator,
+ {SrcReg, DiscReg, *Parent.Value}});
+}
+
MCRegister SPIRVNonSemanticDebugHandler::getOrEmitDebugInlinedAt(
const DILocation *IA, MCRegister VoidTypeReg, MCRegister I32TypeReg,
MCRegister ExtInstSetReg, SPIRV::ModuleAnalysisInfo &MAI) {
@@ -1741,8 +1756,6 @@ void SPIRVNonSemanticDebugHandler::emitNonSemanticDebugStrings(
emitAndCacheScopePathOpStringReg(LV->getFile(), MAI);
}
- // Cache the path OpString each DebugLexicalBlock uses (source file), plus
- // the Name OpString for the DINamespace case.
for (const DIScope *S : LexicalBlocks) {
emitAndCacheScopePathOpStringReg(S->getFile(), MAI);
if (const auto *NS = dyn_cast<DINamespace>(S))
@@ -2386,16 +2399,12 @@ void SPIRVNonSemanticDebugHandler::emitNonSemanticGlobalDebugInfo(
DebugScopeRegs[Info.TheCU] = CUDbgReg;
}
- for (const DIScope *S :
- make_filter_range(LexicalBlocks, IsaPred<DINamespace>))
- getOrCreateDebugScope(S);
for (const DIType *Ty : DebugTypes)
getOrCreateDebugScope(Ty);
for (const DISubprogram *SP : concat<const DISubprogram *>(
SubprogramDeclarations, SubprogramDefinitions))
getOrCreateDebugScope(SP);
- for (const DIScope *S :
- make_filter_range(LexicalBlocks, IsaPred<DILexicalBlock>))
+ for (const DIScope *S : LexicalBlocks)
getOrCreateDebugScope(S);
// Emit DebugLocalVariable after DebugFunction and their lexical blocks so the
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
index e0f351238bace..b0bf540e285f6 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
@@ -184,8 +184,8 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
// so declares referencing it are skipped rather than described wrongly.
DenseMap<const DIExpression *, MCRegister> DebugExpressionRegs;
- // Distinct DILexicalBlock and DINamespace scopes, parent-before-child
- // order, collected in beginModule() for DebugLexicalBlock emission.
+ // Distinct DILexicalBlock, DINamespace, and DILexicalBlockFile scopes
+ // collected in beginModule().
SetVector<const DIScope *> LexicalBlocks;
// DebugInlinedAt result id per DILocation used as an inlined-at chain link.
@@ -728,10 +728,14 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
/// Map \p Scope to the NonSemantic debug id used as a \c Parent operand.
///
/// A \c DIType, \c DILexicalBlock, \c DINamespace, or \c DISubprogram scope
- /// is created through \c getOrCreateDebugScope. When \p Scope is null, a
- /// \c DIFile, or another scope without a dedicated debug instruction, falls
- /// back to \p FallbackCU or the first module \c DebugCompilationUnit recorded
- /// in \c DebugScopeRegs; no such compile unit is Unsupported.
+ /// is created through \c getOrCreateDebugScope. When
+ /// \c -spirv-debug-lexical-block-discriminator-scope is enabled, a
+ /// \c DILexicalBlockFile is also created this way, becoming a
+ /// \c DebugLexicalBlockDiscriminator; otherwise it is unwrapped to its
+ /// enclosing scope first. When \p Scope is null, a \c DIFile, or another
+ /// scope without a dedicated debug instruction, it falls back to
+ /// \p FallbackCU or the first module \c DebugCompilationUnit recorded in
+ /// \c DebugScopeRegs; no such compile unit is Unsupported.
EmitResult<MCRegister>
resolveScope(const DIScope *Scope, const DICompileUnit *FallbackCU = nullptr);
@@ -747,6 +751,13 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
MCRegister I32TypeReg, MCRegister ExtInstSetReg,
SPIRV::ModuleAnalysisInfo &MAI);
+ /// Build \c DebugLexicalBlockDiscriminator for \p LBF.
+ /// Operands are Source, Discriminator, and Parent (\c LBF->getScope()).
+ EmitResult<PreparedScope> emitDebugLexicalBlockDiscriminator(
+ const DILexicalBlockFile *LBF, MCRegister VoidTypeReg,
+ MCRegister I32TypeReg, MCRegister ExtInstSetReg,
+ SPIRV::ModuleAnalysisInfo &MAI);
+
/// Return a cached \c DebugInlinedAt id for \p IA, or emit one (recursing
/// into \c IA->getInlinedAt() first for the optional Inlined operand, so
/// outer frames are always emitted before the inner frame that references
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-lexical-block-discriminator.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-lexical-block-discriminator.ll
new file mode 100644
index 0000000000000..b41027bc95d79
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-lexical-block-discriminator.ll
@@ -0,0 +1,67 @@
+; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_non_semantic_info -spirv-debug-lexical-block-discriminator-scope %s -o - | FileCheck %s
+
+; A DILocation scoped to a DILexicalBlockFile. The block-file becomes a
+; DebugLexicalBlockDiscriminator whose parent is the DebugLexicalBlock, and
+; DebugScope uses that discriminator as its scope.
+;
+; !6 DISubprogram "foo" -> DebugFunction [[DF]]
+; `- !13 DILexicalBlock 4:3 -> DebugLexicalBlock [[LB]]
+; `- !12 DILexicalBlockFile d=1 -> DebugLexicalBlockDiscriminator [[DISC]]
+;
+; spirv-val rejects this module. DebugScope's Scope operand must be a lexical
+; scope, and the spec does not list DebugLexicalBlockDiscriminator as one.
+; DebugScope is the only instruction that gives instructions a scope, and
+; DebugLine has no discriminator operand, so the spec leaves no valid way to
+; attach a discriminator to code. SPIRV-LLVM-Translator uses the discriminator
+; as the DebugScope operand, as here, so the value round-trips.
+
+; 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 "{{[/\\]}}tmp{{[/\\]}}foo.c"
+; CHECK-DAG: [[NAME:%[0-9]+]] = OpString "foo"
+; CHECK-DAG: [[C1:%[0-9]+]] = OpConstant [[I32]] 1
+; CHECK-DAG: [[C3:%[0-9]+]] = OpConstant [[I32]] 3
+; CHECK-DAG: [[C4:%[0-9]+]] = OpConstant [[I32]] 4
+; CHECK-DAG: [[DS:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugSource [[PATH]]
+; CHECK-DAG: [[DF:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugFunction [[NAME]]
+; CHECK-DAG: [[LB:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugLexicalBlock [[DS]] [[C4]] [[C3]] [[DF]]
+; CHECK-DAG: [[DISC:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugLexicalBlockDiscriminator [[DS]] [[C1]] [[LB]]
+; CHECK-DAG: [[CU:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugCompilationUnit
+
+; CHECK: OpFunction
+; CHECK: OpLabel
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugFunctionDefinition [[DF]] {{%[0-9]+}}
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugScope [[DISC]]
+; CHECK-NEXT: OpExtInst [[VOID]] [[EXT]] DebugLine [[DS]] [[C4]] [[C4]] {{%[0-9]+}} {{%[0-9]+}}
+; CHECK-NEXT: {{%[0-9]+}} = OpSGreaterThan
+; CHECK-NOT: DebugScope [[CU]]
+
+target triple = "spirv64-unknown-unknown"
+
+define spir_func i32 @foo(i32 %N) !dbg !6 {
+entry:
+ %cmp = icmp sgt i32 %N, 0, !dbg !11
+ br i1 %cmp, label %t, label %f, !dbg !11
+
+t:
+ ret i32 1, !dbg !14
+
+f:
+ ret i32 0, !dbg !14
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!3, !4}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug)
+!1 = !DIFile(filename: "foo.c", directory: "/tmp")
+!2 = !{}
+!3 = !{i32 2, !"Dwarf Version", i32 4}
+!4 = !{i32 2, !"Debug Info Version", i32 3}
+!6 = distinct !DISubprogram(name: "foo", scope: !1, file: !1, line: 2, type: !7, isDefinition: true, isOptimized: true, unit: !0)
+!7 = !DISubroutineType(types: !2)
+!11 = !DILocation(line: 4, scope: !12)
+!12 = !DILexicalBlockFile(scope: !13, file: !1, discriminator: 1)
+!13 = distinct !DILexicalBlock(scope: !6, file: !1, line: 4, column: 3)
+!14 = !DILocation(line: 5, scope: !6)
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-scope-lexical-block-file.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-scope-lexical-block-file.ll
index 1c7a716568201..32aa4e66b5d08 100644
--- a/llvm/test/CodeGen/SPIRV/debug-info/debug-scope-lexical-block-file.ll
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-scope-lexical-block-file.ll
@@ -1,11 +1,13 @@
+; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_non_semantic_info -spirv-debug-lexical-block-discriminator-scope=false %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc --verify-machineinstrs --spirv-ext=+SPV_KHR_non_semantic_info -spirv-debug-lexical-block-discriminator-scope=false -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
; 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 %}
-;; Verify that a DILocation whose scope is a DILexicalBlockFile resolves to the
-;; underlying DISubprogram's DebugFunction, not to the DebugCompilationUnit.
-;; DILexicalBlockFile's SPIR-V counterpart (DebugLexicalBlockDiscriminator)
-;; cannot be used as a scope, so resolveScope() must unwrap it.
-;; Also tests the nested case (DILexicalBlockFile wrapping DILexicalBlockFile).
+;; With -spirv-debug-lexical-block-discriminator-scope=false, a DILocation
+;; whose scope is a DILexicalBlockFile resolves to the underlying
+;; DISubprogram's DebugFunction, not to the DebugCompilationUnit.
+;; Also tests the nested case (DILexicalBlockFile wrapping
+;; DILexicalBlockFile).
; CHECK-DAG: [[EXT:%[0-9]+]] = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
; CHECK-DAG: [[VOID:%[0-9]+]] = OpTypeVoid
More information about the llvm-branch-commits
mailing list