[llvm] Add support for DebugLexicalBlock. (PR #217965)
Manuel Carrasco via llvm-commits
llvm-commits at lists.llvm.org
Mon Aug 24 07:32:23 PDT 2026
https://github.com/mgcarrasco updated https://github.com/llvm/llvm-project/pull/217965
>From 399561cad7b198e1b89d0fccb19e3b3f35fd2c99 Mon Sep 17 00:00:00 2001
From: Manuel Carrasco <Manuel.Carrasco at amd.com>
Date: Fri, 21 Aug 2026 11:09:56 -0500
Subject: [PATCH] Add support for DebugLexicalBlock.
---
.../SPIRV/SPIRVNonSemanticDebugHandler.cpp | 128 +++++++++++++++++-
.../SPIRV/SPIRVNonSemanticDebugHandler.h | 31 +++++
...ug-function-declaration-namespace-scope.ll | 13 +-
.../debug-function-namespace-scope.ll | 15 +-
.../debug-lexical-block-namespace.ll | 48 +++++++
.../SPIRV/debug-info/debug-lexical-block.ll | 61 +++++++++
6 files changed, 281 insertions(+), 15 deletions(-)
create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-lexical-block-namespace.ll
create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-lexical-block.ll
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
index 74c2fbab99a09..64a239470ce8e 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
@@ -276,6 +276,20 @@ static void collectUniqueDebugLocations(const Module &M,
}
}
+// 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.
@@ -296,8 +310,10 @@ void SPIRVNonSemanticDebugHandler::beginModule(Module *M) {
SubprogramDefinitions.clear();
UniqueDebugLocations.clear();
GlobalVariableDebugInfoMap.clear();
+ LexicalBlocks.clear();
DebugFunctionDeclarationRegs.clear();
DebugFunctionRegs.clear();
+ DebugLexicalBlockRegs.clear();
ScopeToPathOpStringReg.clear();
CUToCompilationUnitDbgReg.clear();
DebugSourceRegByFileStr.clear();
@@ -381,6 +397,12 @@ void SPIRVNonSemanticDebugHandler::beginModule(Module *M) {
}
collectUniqueDebugLocations(*M, UniqueDebugLocations);
+
+ // 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);
}
void SPIRVNonSemanticDebugHandler::prepareModuleOutput(
@@ -634,6 +656,9 @@ SPIRVNonSemanticDebugHandler::resolveDebugFunctionParent(
const DISubprogram *SP) const {
const DIScope *Scope = SP->getScope();
if (Scope && !isa<DIFile>(Scope)) {
+ // Find the DINamespace that was emitted as a lexical block.
+ if (isa<DINamespace>(Scope))
+ return lookupOptReg(DebugLexicalBlockRegs, Scope);
// TODO: Complete with other lookups once other scopes are supported
// (subclasses of DIScope).
const DIType *Ty = dyn_cast<DIType>(Scope);
@@ -657,14 +682,65 @@ std::optional<MCRegister> SPIRVNonSemanticDebugHandler::resolveTypeScopeParent(
if (const auto *Ty = dyn_cast_or_null<DIType>(Scope))
return lookupOptReg(DebugTypeRegs, Ty);
- // For a file, compile-unit, namespace, or absent scope, the parent is the
- // first module DebugCompilationUnit.
+ // Find the DINamespace that was emitted as a lexical block.
+ if (isa_and_nonnull<DINamespace>(Scope))
+ return lookupOptReg(DebugLexicalBlockRegs, Scope);
+
+ // For a file, compile-unit, or absent scope, the parent is the first module
+ // DebugCompilationUnit.
if (CompileUnits.empty())
return std::nullopt;
return lookupOptReg(CUToCompilationUnitDbgReg, CompileUnits[0].TheCU);
}
+std::optional<MCRegister>
+SPIRVNonSemanticDebugHandler::resolveLexicalBlockParent(
+ const DIScope *Scope) const {
+ if (isa_and_nonnull<DILexicalBlock>(Scope) ||
+ isa_and_nonnull<DINamespace>(Scope))
+ return lookupOptReg(DebugLexicalBlockRegs, Scope);
+ if (const auto *SP = dyn_cast_or_null<DISubprogram>(Scope))
+ return lookupOptReg(DebugFunctionRegs, SP);
+ if (CompileUnits.empty())
+ return std::nullopt;
+ return lookupOptReg(CUToCompilationUnitDbgReg, CompileUnits[0].TheCU);
+}
+
+std::optional<MCRegister> SPIRVNonSemanticDebugHandler::emitDebugLexicalBlock(
+ const DIScope *S, MCRegister VoidTypeReg, MCRegister I32TypeReg,
+ MCRegister ExtInstSetReg, SPIRV::ModuleAnalysisInfo &MAI) {
+ assert((isa<DILexicalBlock>(S) || isa<DINamespace>(S)) &&
+ "S must be a DILexicalBlock or DINamespace in emitDebugLexicalBlock");
+ auto ParentRegOpt = resolveLexicalBlockParent(S->getScope());
+ if (!ParentRegOpt)
+ return std::nullopt;
+
+ MCRegister FileStrReg = getCachedScopePathOpStringReg(
+ S->getFile(), /*UseEmptyPathIfNullScope=*/true);
+ MCRegister SrcReg = getOrEmitDebugSourceForFileStrReg(FileStrReg, VoidTypeReg,
+ ExtInstSetReg, MAI);
+
+ SmallVector<MCRegister, 5> Ops;
+ if (const auto *LB = dyn_cast<DILexicalBlock>(S)) {
+ MCRegister LineReg = emitOpConstantI32(static_cast<uint32_t>(LB->getLine()),
+ I32TypeReg, MAI);
+ MCRegister ColReg = emitOpConstantI32(
+ static_cast<uint32_t>(LB->getColumn()), I32TypeReg, MAI);
+ Ops = {SrcReg, LineReg, ColReg, *ParentRegOpt};
+ } else {
+ const auto *NS = cast<DINamespace>(S);
+ // DINamespace carries no line/column info.
+ MCRegister LineReg = emitOpConstantI32(0, I32TypeReg, MAI);
+ MCRegister ColReg = emitOpConstantI32(0, I32TypeReg, MAI);
+ MCRegister NameReg = getCachedOpStringReg(NS->getName());
+ Ops = {SrcReg, LineReg, ColReg, *ParentRegOpt, NameReg};
+ }
+
+ return emitExtInst(SPIRV::NonSemanticExtInst::DebugLexicalBlock, VoidTypeReg,
+ ExtInstSetReg, Ops, MAI);
+}
+
std::optional<MCRegister>
SPIRVNonSemanticDebugHandler::emitDebugFunctionDeclaration(
const DISubprogram *SP, MCRegister VoidTypeReg, MCRegister I32TypeReg,
@@ -767,9 +843,16 @@ std::optional<MCRegister> SPIRVNonSemanticDebugHandler::mapDISignatureTypeToReg(
}
MCRegister SPIRVNonSemanticDebugHandler::resolveGlobalVariableParent(
- const DIGlobalVariable *) const {
- // TODO: When this backend emits debug instructions for namespace, subprogram,
- // compilation units, and module scopes return GV->getScope()'s debug id.
+ const DIGlobalVariable *GV) const {
+ // A namespace-scoped global variable's parent is the enclosing
+ // DebugLexicalBlock emitted for that DINamespace.
+ // TODO: When this backend emits debug instructions for subprogram,
+ // compilation units, and module scopes, also return GV->getScope()'s debug
+ // id for those cases.
+ if (isa_and_nonnull<DINamespace>(GV->getScope())) {
+ if (auto ParentRegOpt = lookupOptReg(DebugLexicalBlockRegs, GV->getScope()))
+ return *ParentRegOpt;
+ }
// !CompileUnits.empty() was already checked before staring the emission of
// NSDI instructions.
@@ -1100,6 +1183,14 @@ void SPIRVNonSemanticDebugHandler::emitNonSemanticDebugStrings(
emitAndCacheScopePathOpStringReg(GV->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))
+ emitOpStringIfNew(NS->getName(), MAI);
+ }
+
for (const DILocation *DL : UniqueDebugLocations)
emitAndCacheScopePathOpStringReg(DL->getScope(), MAI);
@@ -1484,6 +1575,21 @@ void SPIRVNonSemanticDebugHandler::emitNonSemanticGlobalDebugInfo(
DebugTypeRegs[ST] = *FnTyReg;
}
+ // Emit DebugLexicalBlock for each collected DINamespace, in parent-before-
+ // child order. Placed before any DINamespace-scoped entity (typedefs,
+ // function declarations, composite types, functions, global variables) so
+ // their Parent operand can reference an already-emitted DebugLexicalBlock.
+ // DINamespace never chains through a DISubprogram (DINamespace::getScope()
+ // returns DIScope, not DILocalScope), so this never depends on
+ // DebugFunctionRegs.
+ for (const DIScope *S : LexicalBlocks) {
+ if (!isa<DINamespace>(S))
+ continue;
+ if (auto LBReg = emitDebugLexicalBlock(S, VoidTypeReg, I32TypeReg,
+ ExtInstSetReg, MAI))
+ DebugLexicalBlockRegs[S] = *LBReg;
+ }
+
// Emit DebugTypedef for each typedef. Placed after the other type loops so a
// typedef can resolve its underlying type. A typedef whose base type is not
// emitted is skipped. A typedef whose base is another typedef emitted later
@@ -1528,6 +1634,18 @@ void SPIRVNonSemanticDebugHandler::emitNonSemanticGlobalDebugInfo(
DebugFunctionRegs[SP] = *FnReg;
}
+ // Emit DebugLexicalBlock for each collected DILexicalBlock, in parent-
+ // before-child order. Placed after DebugFunction so a block directly
+ // enclosed by a function (the common case) can resolve its Parent operand;
+ // DINamespace entries were already emitted above.
+ for (const DIScope *S : LexicalBlocks) {
+ if (!isa<DILexicalBlock>(S))
+ continue;
+ if (auto LBReg = emitDebugLexicalBlock(S, VoidTypeReg, I32TypeReg,
+ ExtInstSetReg, MAI))
+ DebugLexicalBlockRegs[S] = *LBReg;
+ }
+
// Emit DebugGlobalVariable for each collected DIGlobalVariable.
for (const auto &[GV, Info] : GlobalVariableDebugInfoMap)
emitDebugGlobalVariable(GV, Info, VoidTypeReg, I32TypeReg, ExtInstSetReg,
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
index 17ca354778588..0b3c6fa0aa081 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
@@ -105,6 +105,10 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
DenseMap<const DIGlobalVariable *, GlobalVariableDebugInfo>
GlobalVariableDebugInfoMap;
+ // Distinct DILexicalBlock and DINamespace scopes, parent-before-child
+ // order, collected in beginModule() for DebugLexicalBlock emission.
+ SetVector<const DIScope *> LexicalBlocks;
+
// DebugFunctionDeclaration result id per emitted declaration DISubprogram
// (only entries where emission succeeded).
DenseMap<const DISubprogram *, MCRegister> DebugFunctionDeclarationRegs;
@@ -113,6 +117,9 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
// where emission succeeded).
DenseMap<const DISubprogram *, MCRegister> DebugFunctionRegs;
+ // DebugLexicalBlock result id per emitted DILexicalBlock/DINamespace.
+ DenseMap<const DIScope *, MCRegister> DebugLexicalBlockRegs;
+
// Path \c OpString result id per \c DIScope (CU, \c DIFile, declaration
// \c DISubprogram, …). Filled during \c emitNonSemanticDebugStrings() using
// \c getDebugFullPath + \c emitOpStringIfNew; section 10 uses it for
@@ -490,6 +497,30 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
/// \returns \c std::nullopt when \p Scope is a \c DIType that has not been
/// emitted, or when there is no compile unit.
std::optional<MCRegister> resolveTypeScopeParent(const DIScope *Scope) const;
+
+ /// Resolve the \c Parent operand for \c DebugLexicalBlock, and for any other
+ /// instruction whose LLVM scope may be a \c DILexicalBlock or \c
+ /// DINamespace: an emitted \c DebugLexicalBlock id when \p Scope is a \c
+ /// DILexicalBlock or \c DINamespace already in \c DebugLexicalBlockRegs, an
+ /// emitted \c DebugFunction id when \p Scope is a defining \c DISubprogram,
+ /// otherwise the first module \c DebugCompilationUnit.
+ /// \returns \c std::nullopt when \p Scope requires a parent we cannot
+ /// supply, or the fallback CU has no emitted id.
+ std::optional<MCRegister>
+ resolveLexicalBlockParent(const DIScope *Scope) const;
+
+ /// Emit \c DebugLexicalBlock for \p S, which must be a \c DILexicalBlock or
+ /// a \c DINamespace. A \c DILexicalBlock supplies Line/Column
+ /// from \c getLine()/getColumn(); a \c DINamespace has neither, so both are
+ /// emitted as 0, and its Name is appended as an extra \c OpString operand.
+ ///
+ /// \returns The result id register on success. Returns \c std::nullopt and
+ /// emits nothing if \c resolveLexicalBlockParent returns no id for
+ /// \c S->getScope().
+ std::optional<MCRegister>
+ emitDebugLexicalBlock(const DIScope *S, MCRegister VoidTypeReg,
+ MCRegister I32TypeReg, MCRegister ExtInstSetReg,
+ SPIRV::ModuleAnalysisInfo &MAI);
};
} // namespace llvm
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-function-declaration-namespace-scope.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-function-declaration-namespace-scope.ll
index 8d85f70e961cc..712891e3fc5d8 100644
--- a/llvm/test/CodeGen/SPIRV/debug-info/debug-function-declaration-namespace-scope.ll
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-function-declaration-namespace-scope.ll
@@ -1,21 +1,26 @@
; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_non_semantic_info %s -o - | FileCheck %s
; RUN: %if spirv-tools %{ llc --verify-machineinstrs --spirv-ext=+SPV_KHR_non_semantic_info -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
-; DISubprogram declaration scoped in a DINamespace. Namespace scopes are not yet
-; supported, so no DebugFunctionDeclaration is emitted.
+; DISubprogram declaration scoped in a DINamespace. The namespace is emitted
+; as a DebugLexicalBlock (with a Name operand) and used as the
+; DebugFunctionDeclaration Parent.
; CHECK: [[EXT:%[0-9]+]] = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
; CHECK-DAG: [[VOID:%[0-9]+]] = OpTypeVoid
; CHECK-DAG: [[I32:%[0-9]+]] = OpTypeInt 32 0
; CHECK-DAG: OpString "ns_fn"
+; CHECK-DAG: [[NS:%[0-9]+]] = OpString "ns"
; CHECK-DAG: [[PATH:%[0-9]+]] = OpString "{{[/\\]}}tmp{{[/\\]}}namespace-scope-decl.c"
+; CHECK-DAG: [[EMPTY_PATH:%[0-9]+]] = OpString ""
; CHECK-DAG: [[C100:%[0-9]+]] = OpConstant [[I32]] 100
; CHECK-DAG: [[C5:%[0-9]+]] = OpConstant [[I32]] 5
; CHECK-DAG: [[C0:%[0-9]+]] = OpConstant [[I32]] 0
; CHECK-DAG: [[DS:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugSource [[PATH]]
-; CHECK-DAG: OpExtInst [[VOID]] [[EXT]] DebugCompilationUnit [[C100]] [[C5]] [[DS]] [[C0]]
+; CHECK-DAG: [[CU:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugCompilationUnit [[C100]] [[C5]] [[DS]] [[C0]]
; CHECK-DAG: OpExtInst [[VOID]] [[EXT]] DebugTypeFunction [[C0]] [[VOID]]
-; CHECK-NOT: DebugFunctionDeclaration
+; CHECK-DAG: [[NS_SRC:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugSource [[EMPTY_PATH]]
+; CHECK-DAG: [[LB:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugLexicalBlock [[NS_SRC]] [[C0]] [[C0]] [[CU]] [[NS]]
+; CHECK-DAG: OpExtInst [[VOID]] [[EXT]] DebugFunctionDeclaration {{%[0-9]+}} {{%[0-9]+}} [[DS]] {{%[0-9]+}} [[C0]] [[LB]]
target triple = "spirv64-unknown-unknown"
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-function-namespace-scope.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-function-namespace-scope.ll
index e97c705abeb33..09d6d593096fe 100644
--- a/llvm/test/CodeGen/SPIRV/debug-info/debug-function-namespace-scope.ll
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-function-namespace-scope.ll
@@ -1,24 +1,27 @@
; RUN: llc --verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_non_semantic_info %s -o %t.spt
; RUN: FileCheck %s --check-prefix=CHECK --input-file %t.spt
-; RUN: FileCheck %s --check-prefix=NO-DBG-FUNC --input-file %t.spt
; 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 %}
-; DISubprogram definition scoped in a DINamespace. Namespace scopes are not yet
-; supported as DebugFunction Parent, so no DebugFunction is emitted.
+; DISubprogram definition scoped in a DINamespace. The namespace is emitted as
+; a DebugLexicalBlock (with a Name operand) and used as the DebugFunction
+; Parent.
; CHECK: [[EXT:%[0-9]+]] = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
; CHECK-DAG: [[VOID:%[0-9]+]] = OpTypeVoid
; CHECK-DAG: [[I32:%[0-9]+]] = OpTypeInt 32 0
; CHECK-DAG: OpString "ns_fn"
+; CHECK-DAG: [[NS:%[0-9]+]] = OpString "ns"
; CHECK-DAG: [[PATH:%[0-9]+]] = OpString "{{[/\\]}}tmp{{[/\\]}}namespace-scope-fn.c"
+; CHECK-DAG: [[EMPTY_PATH:%[0-9]+]] = OpString ""
; CHECK-DAG: [[C100:%[0-9]+]] = OpConstant [[I32]] 100
; CHECK-DAG: [[C5:%[0-9]+]] = OpConstant [[I32]] 5
; CHECK-DAG: [[C0:%[0-9]+]] = OpConstant [[I32]] 0
; CHECK-DAG: [[DS:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugSource [[PATH]]
-; CHECK-DAG: OpExtInst [[VOID]] [[EXT]] DebugCompilationUnit [[C100]] [[C5]] [[DS]] [[C0]]
+; CHECK-DAG: [[CU:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugCompilationUnit [[C100]] [[C5]] [[DS]] [[C0]]
; CHECK-DAG: OpExtInst [[VOID]] [[EXT]] DebugTypeFunction [[C0]] [[VOID]]
-
-; NO-DBG-FUNC-NOT: DebugFunction
+; CHECK-DAG: [[NS_SRC:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugSource [[EMPTY_PATH]]
+; CHECK-DAG: [[LB:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugLexicalBlock [[NS_SRC]] [[C0]] [[C0]] [[CU]] [[NS]]
+; CHECK-DAG: OpExtInst [[VOID]] [[EXT]] DebugFunction {{%[0-9]+}} {{%[0-9]+}} [[DS]] {{%[0-9]+}} [[C0]] [[LB]]
target triple = "spirv64-unknown-unknown"
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-lexical-block-namespace.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-lexical-block-namespace.ll
new file mode 100644
index 0000000000000..8a8d65b71ad38
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-lexical-block-namespace.ll
@@ -0,0 +1,48 @@
+; 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 that a DINamespace becomes a DebugLexicalBlock when it is the Parent of
+; a DebugGlobalVariable. Two cases are covered: a named namespace nested in
+; another named one, and an anonymous namespace nested in a named one.
+; Per the NSDI spec, an anonymous namespace must get an empty OpString as its
+; Name operand. Clang emits such namespaces as `!DINamespace(scope: ...)` with
+; no `name:` field, so DINamespace::getName() returns "".
+
+; 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: [[OUTER_NAME:%[0-9]+]] = OpString "outer"
+; CHECK-DAG: [[INNER_NAME:%[0-9]+]] = OpString "inner"
+; CHECK-DAG: [[GNAME:%[0-9]+]] = OpString "g"
+; CHECK-DAG: [[HNAME:%[0-9]+]] = OpString "h"
+; CHECK-DAG: [[EMPTY:%[0-9]+]] = OpString ""
+; CHECK-DAG: [[CU:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugCompilationUnit {{.*}}
+; CHECK-DAG: [[OUTER_LB:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugLexicalBlock {{%[0-9]+}} {{%[0-9]+}} {{%[0-9]+}} [[CU]] [[OUTER_NAME]]
+; CHECK-DAG: [[INNER_LB:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugLexicalBlock {{%[0-9]+}} {{%[0-9]+}} {{%[0-9]+}} [[OUTER_LB]] [[INNER_NAME]]
+; CHECK-DAG: [[ANON_LB:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugLexicalBlock {{%[0-9]+}} {{%[0-9]+}} {{%[0-9]+}} [[OUTER_LB]] [[EMPTY]]
+; CHECK-DAG: OpExtInst [[VOID]] [[EXT]] DebugGlobalVariable [[GNAME]] {{.*}} [[INNER_LB]] {{.*}}
+; CHECK-DAG: OpExtInst [[VOID]] [[EXT]] DebugGlobalVariable [[HNAME]] {{.*}} [[ANON_LB]] {{.*}}
+
+target triple = "spirv64-unknown-unknown"
+
+ at g = addrspace(1) global i32 0, !dbg !13
+ at h = addrspace(1) global i32 0, !dbg !16
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus, file: !1, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, globals: !12, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "debug-lexical-block-namespace.cpp", directory: "/src")
+!2 = !{i32 7, !"Dwarf Version", i32 5}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+
+!7 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!8 = !DINamespace(name: "outer", scope: !1)
+!9 = !DINamespace(name: "inner", scope: !8)
+!10 = !DINamespace(scope: !8)
+
+!12 = !{!13, !16}
+!13 = !DIGlobalVariableExpression(var: !14, expr: !DIExpression())
+!14 = distinct !DIGlobalVariable(name: "g", linkageName: "g", scope: !9, file: !1, line: 4, type: !7, isLocal: false, isDefinition: true)
+!16 = !DIGlobalVariableExpression(var: !17, expr: !DIExpression())
+!17 = distinct !DIGlobalVariable(name: "h", linkageName: "h", scope: !10, file: !1, line: 5, type: !7, isLocal: true, isDefinition: true)
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-lexical-block.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-lexical-block.ll
new file mode 100644
index 0000000000000..6a4d2bf079c1b
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-lexical-block.ll
@@ -0,0 +1,61 @@
+; 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 DebugLexicalBlock for two levels of DILexicalBlock
+; nesting. Scope tree on the left, emitted NSDI instruction on the right:
+;
+; !5 DISubprogram "nested" -> DebugFunction [[DF]]
+; `- !8 DILexicalBlock 42:43 -> DebugLexicalBlock [[OUTER]]
+; `- !10 DILexicalBlock 44:45 -> DebugLexicalBlock [[INNER]]
+;
+; Each block's Parent operand is the instruction emitted for its parent scope,
+; so [[OUTER]] points at [[DF]] and [[INNER]] points at [[OUTER]].
+
+; 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-lexical-block.c"
+; CHECK-DAG: [[NAME:%[0-9]+]] = OpString "nested"
+; CHECK-DAG: [[C42:%[0-9]+]] = OpConstant [[I32]] 42
+; CHECK-DAG: [[C43:%[0-9]+]] = OpConstant [[I32]] 43
+; CHECK-DAG: [[C44:%[0-9]+]] = OpConstant [[I32]] 44
+; CHECK-DAG: [[C45:%[0-9]+]] = OpConstant [[I32]] 45
+; CHECK-DAG: [[DS:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugSource [[PATH]]
+; CHECK-DAG: [[DF:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugFunction [[NAME]] {{.*}} [[DS]] {{.*}}
+; CHECK-DAG: [[OUTER:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugLexicalBlock [[DS]] [[C42]] [[C43]] [[DF]]
+; CHECK-DAG: [[INNER:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugLexicalBlock [[DS]] [[C44]] [[C45]] [[OUTER]]
+
+target triple = "spirv64-unknown-unknown"
+
+define spir_func i32 @nested(i32 %n) !dbg !5 {
+entry:
+ %n.addr = alloca i32, align 4
+ store i32 %n, ptr %n.addr, align 4
+ br label %outer, !dbg !9
+
+outer:
+ br label %inner, !dbg !11
+
+inner:
+ %v = load i32, ptr %n.addr, align 4, !dbg !13
+ ret i32 %v, !dbg !13
+}
+
+!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-lexical-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: "nested", linkageName: "nested", scope: !1, file: !1, line: 1, type: !4, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!8 = distinct !DILexicalBlock(scope: !5, file: !1, line: 42, column: 43)
+!9 = !DILocation(line: 42, column: 43, scope: !8)
+!10 = distinct !DILexicalBlock(scope: !8, file: !1, line: 44, column: 45)
+!11 = !DILocation(line: 44, column: 45, scope: !10)
+!13 = !DILocation(line: 46, column: 47, scope: !10)
More information about the llvm-commits
mailing list