[llvm] Implement support for NSDI DebugFunction opcode. (PR #211760)
via llvm-commits
llvm-commits at lists.llvm.org
Fri Jul 24 06:29:57 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-backend-spir-v
Author: Manuel Carrasco (mgcarrasco)
<details>
<summary>Changes</summary>
Add support for [DebugFunction](https://github.khronos.org/SPIRV-Registry/nonsemantic/NonSemantic.Shader.DebugInfo.html#DebugFunction).
The next PR will bring support for DebugFunctionDefinition.
---
Full diff: https://github.com/llvm/llvm-project/pull/211760.diff
5 Files Affected:
- (modified) llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp (+57-1)
- (modified) llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h (+21-7)
- (added) llvm/test/CodeGen/SPIRV/debug-info/debug-function-namespace-scope.ll (+41)
- (added) llvm/test/CodeGen/SPIRV/debug-info/debug-function-with-declaration.ll (+37)
- (added) llvm/test/CodeGen/SPIRV/debug-info/debug-function.ll (+43)
``````````diff
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
index 5c03e39e0ce21..cc916eded5010 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
@@ -195,6 +195,7 @@ void SPIRVNonSemanticDebugHandler::beginModule(Module *M) {
SubroutineTypes.clear();
VectorTypes.clear();
SubprogramDeclarations.clear();
+ SubprogramDefinitions.clear();
GlobalVariableDebugInfoMap.clear();
DebugFunctionDeclarationRegs.clear();
ScopeToPathOpStringReg.clear();
@@ -251,7 +252,9 @@ void SPIRVNonSemanticDebugHandler::beginModule(Module *M) {
});
for (const DISubprogram *SP : Finder.subprograms()) {
- if (!SP->isDefinition())
+ if (SP->isDefinition())
+ SubprogramDefinitions.push_back(SP);
+ else
SubprogramDeclarations.push_back(SP);
}
@@ -582,6 +585,49 @@ SPIRVNonSemanticDebugHandler::emitDebugFunctionDeclaration(
MAI);
}
+std::optional<MCRegister> SPIRVNonSemanticDebugHandler::emitDebugFunction(
+ const DISubprogram *SP, MCRegister VoidTypeReg, MCRegister I32TypeReg,
+ MCRegister ExtInstSetReg, SPIRV::ModuleAnalysisInfo &MAI) {
+ assert(SP && "SP must not be null in emitDebugFunction");
+ assert(SP->isDefinition() && "SP must be a definition in emitDebugFunction");
+
+ const DISubroutineType *ST = SP->getType();
+ auto FnTyRegOpt = lookupOptReg(DebugTypeRegs, ST);
+ if (!FnTyRegOpt)
+ return std::nullopt;
+
+ auto ParentRegOpt = resolveDebugFunctionDeclarationParent(SP);
+ if (!ParentRegOpt)
+ return std::nullopt;
+
+ MCRegister NameReg = getCachedOpStringReg(SP->getName());
+ MCRegister LinkageReg = getCachedOpStringReg(SP->getLinkageName());
+ MCRegister FileStrReg = getCachedScopePathOpStringReg(SP);
+ MCRegister SrcReg = getOrEmitDebugSourceForFileStrReg(FileStrReg, VoidTypeReg,
+ ExtInstSetReg, MAI);
+
+ MCRegister LineReg =
+ emitOpConstantI32(static_cast<uint32_t>(SP->getLine()), I32TypeReg, MAI);
+ // LLVM's DISubprogram has no column field but SPIR-V expects one in
+ // DebugFunction.
+ MCRegister ColReg = emitOpConstantI32(0, I32TypeReg, MAI);
+ MCRegister FlagsReg = emitOpConstantI32(transDebugFlags(SP), I32TypeReg, MAI);
+ MCRegister ScopeLineReg = emitOpConstantI32(
+ static_cast<uint32_t>(SP->getScopeLine()), I32TypeReg, MAI);
+
+ SmallVector<MCRegister, 10> Ops = {NameReg, *FnTyRegOpt, SrcReg,
+ LineReg, ColReg, *ParentRegOpt,
+ LinkageReg, FlagsReg, ScopeLineReg};
+
+ if (const DISubprogram *Decl = SP->getDeclaration()) {
+ if (auto DeclRegOpt = lookupOptReg(DebugFunctionDeclarationRegs, Decl))
+ Ops.push_back(*DeclRegOpt);
+ }
+
+ return emitExtInst(SPIRV::NonSemanticExtInst::DebugFunction, VoidTypeReg,
+ ExtInstSetReg, Ops, MAI);
+}
+
std::optional<MCRegister> SPIRVNonSemanticDebugHandler::mapDISignatureTypeToReg(
const DIType *Ty, MCRegister VoidTypeReg, bool ReturnType) {
if (!Ty) {
@@ -742,6 +788,12 @@ void SPIRVNonSemanticDebugHandler::emitNonSemanticDebugStrings(
ScopeToPathOpStringReg[SP] = emitOpStringIfNew(getDebugFullPath(SP), MAI);
}
+ for (const DISubprogram *SP : SubprogramDefinitions) {
+ emitOpStringIfNew(SP->getName(), MAI);
+ emitOpStringIfNew(SP->getLinkageName(), MAI);
+ ScopeToPathOpStringReg[SP] = emitOpStringIfNew(getDebugFullPath(SP), MAI);
+ }
+
for (const auto &[GV, _] : GlobalVariableDebugInfoMap) {
emitOpStringIfNew(GV->getName(), MAI);
emitOpStringIfNew(GV->getLinkageName(), MAI);
@@ -891,6 +943,10 @@ void SPIRVNonSemanticDebugHandler::emitNonSemanticGlobalDebugInfo(
DebugFunctionDeclarationRegs[SP] = *DeclReg;
}
+ // Emit DebugFunction for DISubprogram definitions.
+ for (const DISubprogram *SP : SubprogramDefinitions)
+ emitDebugFunction(SP, VoidTypeReg, I32TypeReg, ExtInstSetReg, MAI);
+
// 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 1bbe19c1eaa34..35e3a79499c14 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
@@ -46,7 +46,8 @@ class SPIRVSubtarget;
/// emitNonSemanticGlobalDebugInfo() -- emit DebugSource,
/// DebugCompilationUnit, DebugTypeBasic,
/// DebugTypePointer, DebugTypeFunction,
-/// DebugFunctionDeclaration.
+/// DebugFunctionDeclaration,
+/// DebugFunction.
/// beginFunctionImpl() -- no-op (no per-function DI yet).
/// endFunctionImpl() -- no-op.
class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
@@ -76,6 +77,10 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
// in beginModule() for DebugFunctionDeclaration emission.
SmallVector<const DISubprogram *> SubprogramDeclarations;
+ // DISubprogram nodes that are definitions, collected in beginModule() for
+ // DebugFunction emission.
+ SmallVector<const DISubprogram *> SubprogramDefinitions;
+
struct GlobalVariableDebugInfo {
const DIExpression *Expr = nullptr;
const GlobalVariable *LLVMGV = nullptr;
@@ -159,7 +164,7 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
/// Emit module-scope NSDI instructions (DebugSource, DebugCompilationUnit,
/// DebugTypeBasic, DebugTypePointer, DebugTypeFunction,
- /// DebugFunctionDeclaration). Called by
+ /// DebugFunctionDeclaration, DebugFunction). Called by
/// SPIRVAsmPrinter::outputModuleSections() at section 10 in place of
/// outputModuleSection(MB_NonSemanticGlobalDI). Requires
/// emitNonSemanticDebugStrings() to have run first when NSDI strings apply.
@@ -182,12 +187,13 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
void beginInstruction(const MachineInstr *MI) override {}
void endInstruction() override {}
- // TODO: Emit DebugFunction and DebugFunctionDefinition here once per-function
- // NSDI emission is implemented. DebugHandlerBase::beginFunction() populates
- // LScopes and DbgValues, which are needed for DebugLine emission. Do not
- // override beginFunction() until that work is in place.
+ // TODO: Emit DebugFunctionDefinition here once per-function NSDI emission is
+ // implemented. DebugHandlerBase::beginFunction() populates LScopes and
+ // DbgValues, which are needed for DebugLine emission. Do not override
+ // beginFunction() until that work is in place.
void beginFunctionImpl(const MachineFunction *MF) override {}
- // TODO: Add per-function cleanup when DebugFunction emission is in place.
+ // TODO: Add per-function cleanup when DebugFunctionDefinition emission is in
+ // place.
void endFunctionImpl(const MachineFunction *MF) override {}
private:
@@ -274,6 +280,14 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
MCRegister I32TypeReg, MCRegister ExtInstSetReg,
SPIRV::ModuleAnalysisInfo &MAI);
+ /// Emit \c DebugFunction for a defining \c DISubprogram (\p SP must satisfy
+ /// \c isDefinition()).
+ std::optional<MCRegister> emitDebugFunction(const DISubprogram *SP,
+ MCRegister VoidTypeReg,
+ MCRegister I32TypeReg,
+ MCRegister ExtInstSetReg,
+ SPIRV::ModuleAnalysisInfo &MAI);
+
/// Emit \c DebugGlobalVariable for the source global variable \p GV.
///
/// (\c SPIRVDebug::Operand::GlobalVariable): Name, Type, Source, Line,
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
new file mode 100644
index 0000000000000..a9e9160435b37
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-function-namespace-scope.ll
@@ -0,0 +1,41 @@
+; 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 definition scoped in a DINamespace. Namespace scopes are not yet
+; supported as DebugFunction Parent, so no DebugFunction is emitted.
+
+; 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: [[PATH:%[0-9]+]] = OpString "{{[/\\]}}tmp{{[/\\]}}namespace-scope-fn.c"
+; 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: OpExtInst [[VOID]] [[EXT]] DebugTypeFunction [[C0]] [[VOID]]
+; CHECK-NOT: DebugFunction
+
+target triple = "spirv64-unknown-unknown"
+
+define spir_func void @ns_fn() !dbg !9 {
+entry:
+ ret void
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3, !4, !5}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "namespace-scope-fn.c", directory: "/tmp", checksumkind: CSK_MD5, checksum: "00000000000000000000000000000000")
+!2 = !{i32 7, !"Dwarf Version", i32 5}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = !{i32 1, !"wchar_size", i32 4}
+!5 = !{i32 7, !"frame-pointer", i32 2}
+
+!6 = !DISubroutineType(cc: DW_CC_LLVM_SpirFunction, types: !7)
+!7 = !{}
+!8 = !DINamespace(name: "ns", scope: !1)
+
+!9 = distinct !DISubprogram(name: "ns_fn", linkageName: "ns_fn", scope: !8, file: !1, line: 2, type: !6, scopeLine: 2, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-function-with-declaration.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-function-with-declaration.ll
new file mode 100644
index 0000000000000..7a9523010d6f9
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-function-with-declaration.ll
@@ -0,0 +1,37 @@
+; 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 %}
+
+; DebugFunction should reference the emitted DebugFunctionDeclaration when the
+; defining DISubprogram carries a declaration operand.
+
+; CHECK-DAG: [[EXT:%[0-9]+]] = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+; CHECK-DAG: [[VOID:%[0-9]+]] = OpTypeVoid
+; CHECK-DAG: [[NAME:%[0-9]+]] = OpString "add_one"
+; CHECK-DAG: [[DECL:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugFunctionDeclaration [[NAME]] {{.*}} [[NAME]] {{.*}}
+; CHECK-DAG: [[DF:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugFunction [[NAME]] {{.*}} [[DECL]]
+
+target triple = "spirv64-unknown-unknown"
+
+define spir_func i32 @add_one(i32 %value) !dbg !5 {
+entry:
+ %result = add i32 %value, 1
+ ret i32 %result, !dbg !8
+}
+
+!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, retainedTypes: !9)
+!1 = !DIFile(filename: "debug-function-with-declaration.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)
+
+!9 = !{!10}
+!10 = !DISubprogram(name: "add_one", linkageName: "add_one", scope: !1, file: !1, line: 1, type: !4, scopeLine: 1, flags: DIFlagPrototyped, spFlags: 0)
+
+!5 = distinct !DISubprogram(name: "add_one", linkageName: "add_one", scope: !1, file: !1, line: 1, type: !4, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, declaration: !10)
+!8 = !DILocation(line: 3, column: 3, scope: !5)
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-function.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-function.ll
new file mode 100644
index 0000000000000..e71dc38829e94
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-function.ll
@@ -0,0 +1,43 @@
+; 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 DebugFunction for a defined function (module scope).
+
+; 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-function.c"
+; CHECK-DAG: [[NAME:%[0-9]+]] = OpString "add_one"
+; 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: [[C136:%[0-9]+]] = OpConstant [[I32]] 136
+; CHECK-DAG: [[DS:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugSource [[PATH]]
+; CHECK-DAG: [[CU:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugCompilationUnit [[C100]] [[C5]] [[DS]] [[C0]]
+; CHECK-DAG: [[INTNAME:%[0-9]+]] = OpString "int"
+; CHECK-DAG: [[INT:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugTypeBasic [[INTNAME]] {{.*}} [[C0]]
+; CHECK-DAG: [[TF:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugTypeFunction [[C0]] [[INT]] [[INT]]
+; CHECK-DAG: [[DF:%[0-9]+]] = OpExtInst [[VOID]] [[EXT]] DebugFunction [[NAME]] [[TF]] [[DS]] {{.*}} [[C0]] [[CU]] [[NAME]] [[C136]] {{.*}}
+
+target triple = "spirv64-unknown-unknown"
+
+define spir_func i32 @add_one(i32 %value) !dbg !5 {
+entry:
+ %result = add i32 %value, 1
+ ret i32 %result, !dbg !8
+}
+
+!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-function.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: "add_one", linkageName: "add_one", scope: !1, file: !1, line: 1, type: !4, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!8 = !DILocation(line: 3, column: 3, scope: !5)
``````````
</details>
https://github.com/llvm/llvm-project/pull/211760
More information about the llvm-commits
mailing list