[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