[llvm] [SPIRV] Emit NonSemantic DebugTypedef (PR #211883)
Diego Novillo via llvm-commits
llvm-commits at lists.llvm.org
Thu Jul 30 06:10:44 PDT 2026
https://github.com/dnovillo updated https://github.com/llvm/llvm-project/pull/211883
>From 74018c80c9bc061bb3350de7cbcc7879ac1d7014 Mon Sep 17 00:00:00 2001
From: Diego Novillo <dnovillo at nvidia.com>
Date: Fri, 24 Jul 2026 12:33:23 -0400
Subject: [PATCH] [SPIRV] Emit NonSemantic DebugTypedef
---
.../SPIRV/SPIRVNonSemanticDebugHandler.cpp | 77 +++++++++++++++++--
.../SPIRV/SPIRVNonSemanticDebugHandler.h | 18 +++++
.../debug-info/debug-typedef-nested-drop.ll | 43 +++++++++++
.../debug-typedef-skip-base-not-in-regs.ll | 30 ++++++++
.../CodeGen/SPIRV/debug-info/debug-typedef.ll | 36 +++++++++
5 files changed, 197 insertions(+), 7 deletions(-)
create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-typedef-nested-drop.ll
create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-typedef-skip-base-not-in-regs.ll
create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-typedef.ll
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
index 8125b1b7dc7f3..f22cc1a38a794 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
@@ -42,18 +42,19 @@ static std::optional<MCRegister> lookupOptReg(const MapT &Map,
}
/// Partition \p Ty into \p BasicTypes, \p PointerTypes, \p SubroutineTypes,
-/// \p VectorTypes, \p ArrayTypes, and \p CompositeTypes for NSDI emission. Used
-/// when iterating DebugInfoFinder.types(); each DI node is seen once, so no
-/// recursion into pointer bases. Non-pointer derived kinds are ignored because
-/// they are not yet supported. Only types that are supported (later used) are
-/// partitioned.
+/// \p VectorTypes, \p ArrayTypes, \p CompositeTypes, and \p TypedefTypes for
+/// NSDI emission. Used when iterating DebugInfoFinder.types(); each DI node is
+/// seen once, so no recursion into pointer bases. Other composites and the
+/// remaining derived kinds are ignored because they are not yet supported.
+/// Only types that are supported (later used) are partitioned.
static void
partitionTypes(const DIType *Ty, SmallVector<const DIBasicType *> &BasicTypes,
SmallVector<const DIDerivedType *> &PointerTypes,
SmallVector<const DISubroutineType *> &SubroutineTypes,
SmallVector<const DICompositeType *> &VectorTypes,
SmallVector<const DICompositeType *> &ArrayTypes,
- SmallVector<const DICompositeType *> &CompositeTypes) {
+ SmallVector<const DICompositeType *> &CompositeTypes,
+ SmallVector<const DIDerivedType *> &TypedefTypes) {
if (const auto *BT = dyn_cast<DIBasicType>(Ty)) {
BasicTypes.push_back(BT);
return;
@@ -89,6 +90,8 @@ partitionTypes(const DIType *Ty, SmallVector<const DIBasicType *> &BasicTypes,
const auto *DT = dyn_cast<DIDerivedType>(Ty);
if (DT && DT->getTag() == dwarf::DW_TAG_pointer_type)
PointerTypes.push_back(DT);
+ else if (DT && DT->getTag() == dwarf::DW_TAG_typedef)
+ TypedefTypes.push_back(DT);
}
enum : uint32_t {
@@ -234,6 +237,7 @@ void SPIRVNonSemanticDebugHandler::beginModule(Module *M) {
VectorTypes.clear();
ArrayTypes.clear();
CompositeTypes.clear();
+ TypedefTypes.clear();
SubprogramDeclarations.clear();
GlobalVariableDebugInfoMap.clear();
DebugFunctionDeclarationRegs.clear();
@@ -288,7 +292,7 @@ void SPIRVNonSemanticDebugHandler::beginModule(Module *M) {
Finder.processModule(*M);
llvm::for_each(Finder.types(), [&](DIType *Ty) {
partitionTypes(Ty, BasicTypes, PointerTypes, SubroutineTypes, VectorTypes,
- ArrayTypes, CompositeTypes);
+ ArrayTypes, CompositeTypes, TypedefTypes);
});
for (const DISubprogram *SP : Finder.subprograms()) {
@@ -893,6 +897,48 @@ std::optional<MCRegister> SPIRVNonSemanticDebugHandler::emitDebugTypeComposite(
ExtInstSetReg, Ops, MAI);
}
+std::optional<MCRegister> SPIRVNonSemanticDebugHandler::emitDebugTypedef(
+ const DIDerivedType *TD, MCRegister VoidTypeReg, MCRegister I32TypeReg,
+ MCRegister ExtInstSetReg, SPIRV::ModuleAnalysisInfo &MAI) {
+ // The underlying (base) type must already be in DebugTypeRegs.
+ auto BaseRegOpt = lookupOptReg(DebugTypeRegs, TD->getBaseType());
+ if (!BaseRegOpt)
+ return std::nullopt;
+
+ MCRegister NameReg = getCachedOpStringReg(TD->getName());
+ MCRegister FileStrReg = getCachedScopePathOpStringReg(
+ TD->getFile(), /*UseEmptyPathIfNullScope=*/true);
+ MCRegister SrcReg = getOrEmitDebugSourceForFileStrReg(FileStrReg, VoidTypeReg,
+ ExtInstSetReg, MAI);
+ MCRegister LineReg =
+ emitOpConstantI32(static_cast<uint32_t>(TD->getLine()), I32TypeReg, MAI);
+ // DIDerivedType typedefs carry no column, so emit 0.
+ MCRegister ColReg = emitOpConstantI32(0, I32TypeReg, MAI);
+
+ // Parent must be a lexical scope. Valid NSDI lexical scopes are
+ // DebugCompilationUnit, DebugFunction, DebugLexicalBlock, or
+ // DebugTypeComposite.
+ //
+ // FIXME: We currently only emit DebugCompilationUnit, so the compile unit is
+ // the only parent available today.
+ MCRegister ParentReg;
+ if (const auto *Ty = dyn_cast_or_null<DIType>(TD->getScope()))
+ if (auto TyRegOpt = lookupOptReg(DebugTypeRegs, Ty))
+ ParentReg = *TyRegOpt;
+ if (!ParentReg.isValid()) {
+ assert(!CompileUnits.empty() &&
+ "emitDebugTypedef requires a compile unit for the Parent operand");
+ auto CURegOpt =
+ lookupOptReg(CUToCompilationUnitDbgReg, CompileUnits[0].TheCU);
+ assert(CURegOpt && "DebugCompilationUnit must be emitted before typedefs");
+ ParentReg = *CURegOpt;
+ }
+
+ return emitExtInst(
+ SPIRV::NonSemanticExtInst::DebugTypedef, VoidTypeReg, ExtInstSetReg,
+ {NameReg, *BaseRegOpt, SrcReg, LineReg, ColReg, ParentReg}, MAI);
+}
+
void SPIRVNonSemanticDebugHandler::emitNonSemanticDebugStrings(
SPIRV::ModuleAnalysisInfo &MAI) {
if (CompileUnits.empty())
@@ -939,6 +985,12 @@ void SPIRVNonSemanticDebugHandler::emitNonSemanticDebugStrings(
}
}
+ // Cache the name and path OpStrings each DebugTypedef uses.
+ for (const DIDerivedType *TD : TypedefTypes) {
+ emitOpStringIfNew(TD->getName(), MAI);
+ emitAndCacheScopePathOpStringReg(TD->getFile(), MAI);
+ }
+
for (const auto &[GV, _] : GlobalVariableDebugInfoMap) {
emitOpStringIfNew(GV->getName(), MAI);
emitOpStringIfNew(GV->getLinkageName(), MAI);
@@ -1086,6 +1138,17 @@ void SPIRVNonSemanticDebugHandler::emitNonSemanticGlobalDebugInfo(
DebugTypeRegs[ST] = *FnTyReg;
}
+ // 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
+ // in this same pass is also skipped, the emission-order gap tracked in
+ // https://github.com/llvm/llvm-project/issues/211850.
+ for (const DIDerivedType *TD : TypedefTypes) {
+ if (auto TDReg =
+ emitDebugTypedef(TD, VoidTypeReg, I32TypeReg, ExtInstSetReg, MAI))
+ DebugTypeRegs[TD] = *TDReg;
+ }
+
// Emit DebugFunctionDeclaration for DISubprogram declarations.
for (const DISubprogram *SP : SubprogramDeclarations) {
if (auto DeclReg = emitDebugFunctionDeclaration(SP, VoidTypeReg, I32TypeReg,
diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
index 59653c19146fd..eb1469a5baf69 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.h
@@ -73,6 +73,9 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
// DICompositeType nodes with DW_TAG_structure_type, DW_TAG_class_type, or
// DW_TAG_union_type, partitioned in beginModule() for DebugTypeComposite.
SmallVector<const DICompositeType *> CompositeTypes;
+ // DIDerivedType nodes with DW_TAG_typedef, partitioned in beginModule() for
+ // DebugTypedef emission.
+ SmallVector<const DIDerivedType *> TypedefTypes;
// Filled in emitNonSemanticGlobalDebugInfo(): DI types to their result
// registers.
@@ -370,6 +373,21 @@ class SPIRVNonSemanticDebugHandler : public DebugHandlerBase {
MCRegister VoidTypeReg, MCRegister I32TypeReg, MCRegister ExtInstSetReg,
SPIRV::ModuleAnalysisInfo &MAI);
+ /// Emit \c DebugTypedef for the typedef derived type \p TD (a \c
+ /// DIDerivedType with \c DW_TAG_typedef). Operands: Name, Base Type, Source,
+ /// Line, Column, Parent. Parent is the enclosing type when \c TD->getScope()
+ /// is an emitted \c DIType, otherwise the first module \c
+ /// DebugCompilationUnit.
+ ///
+ /// \returns The result id register on success. Returns \c std::nullopt and
+ /// emits nothing if \p TD's base type has not been emitted into \c
+ /// DebugTypeRegs.
+ std::optional<MCRegister> emitDebugTypedef(const DIDerivedType *TD,
+ MCRegister VoidTypeReg,
+ MCRegister I32TypeReg,
+ MCRegister ExtInstSetReg,
+ SPIRV::ModuleAnalysisInfo &MAI);
+
/// Map a \c DISubroutineType::getTypeArray() element to an operand register
/// for
/// \c DebugTypeFunction. Non-null \p Ty resolves via \c DebugTypeRegs; if the
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-typedef-nested-drop.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-typedef-nested-drop.ll
new file mode 100644
index 0000000000000..39b9852fc0490
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-typedef-nested-drop.ll
@@ -0,0 +1,43 @@
+; RUN: llc --verify-machineinstrs --spirv-ext=+SPV_KHR_non_semantic_info -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s --check-prefix=CHECK-SPIRV --implicit-check-not=DebugTypedef
+; 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 %}
+
+; Typedefs are emitted in a single DebugInfoFinder pass. Bar is discovered
+; before Foo, so when Bar is emitted its Foo base has no id yet and Bar is
+; dropped. Foo is emitted next and survives. Only one DebugTypedef is emitted
+; here, for Foo. Emitting typedefs in dependency order, would add a second
+; DebugTypedef for Bar (see https://github.com/llvm/llvm-project/issues/211850).
+
+; CHECK-SPIRV: [[ext:%[0-9]+]] = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+; CHECK-SPIRV-DAG: [[void:%[0-9]+]] = OpTypeVoid
+; CHECK-SPIRV-DAG: [[i32:%[0-9]+]] = OpTypeInt 32 0
+; CHECK-SPIRV-DAG: [[str_int:%[0-9]+]] = OpString "int"
+; CHECK-SPIRV-DAG: [[str_foo:%[0-9]+]] = OpString "Foo"
+; CHECK-SPIRV-DAG: [[c0:%[0-9]+]] = OpConstant [[i32]] 0{{$}}
+; CHECK-SPIRV-DAG: [[c2:%[0-9]+]] = OpConstant [[i32]] 2{{$}}
+; CHECK-SPIRV-DAG: [[ds:%[0-9]+]] = OpExtInst [[void]] [[ext]] DebugSource
+; CHECK-SPIRV-DAG: [[cu:%[0-9]+]] = OpExtInst [[void]] [[ext]] DebugCompilationUnit
+; CHECK-SPIRV-DAG: [[basic_int:%[0-9]+]] = OpExtInst [[void]] [[ext]] DebugTypeBasic [[str_int]]
+; CHECK-SPIRV-DAG: OpExtInst [[void]] [[ext]] DebugTypedef [[str_foo]] [[basic_int]] [[ds]] [[c2]] [[c0]] [[cu]]{{$}}
+
+define spir_func void @test() !dbg !6 {
+entry:
+ ret void
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!1, !2}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_HLSL, file: !3, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, retainedTypes: !4)
+!1 = !{i32 7, !"Dwarf Version", i32 5}
+!2 = !{i32 2, !"Debug Info Version", i32 3}
+!3 = !DIFile(filename: "typedef.hlsl", directory: "/src")
+
+; Only Bar is retained. Foo is reached solely through Bar's base type, so the
+; finder visits Bar first and Foo second.
+!4 = !{!5}
+!5 = !DIDerivedType(tag: DW_TAG_typedef, name: "Bar", file: !3, line: 3, baseType: !7, scope: !3)
+!6 = distinct !DISubprogram(name: "test", scope: !3, file: !3, line: 1, type: !8, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!7 = !DIDerivedType(tag: DW_TAG_typedef, name: "Foo", file: !3, line: 2, baseType: !9, scope: !3)
+!8 = !DISubroutineType(types: !10)
+!9 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!10 = !{null}
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-typedef-skip-base-not-in-regs.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-typedef-skip-base-not-in-regs.ll
new file mode 100644
index 0000000000000..59aa3d3df9a08
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-typedef-skip-base-not-in-regs.ll
@@ -0,0 +1,30 @@
+; RUN: llc --verify-machineinstrs --spirv-ext=+SPV_KHR_non_semantic_info -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s --check-prefix=CHECK-SPIRV --implicit-check-not=DebugTypedef
+; 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 %}
+
+; emitDebugTypedef emits nothing when the base type is not in DebugTypeRegs.
+; The base type here is a pointer with no DWARF address space, which
+; emitDebugTypePointer skips for lack of a storage class. The base type stays
+; out of DebugTypeRegs regardless of later type support, so the typedef is
+; skipped.
+
+; CHECK-SPIRV: OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+
+define spir_func void @test() !dbg !6 {
+entry:
+ ret void
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!1, !2}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_HLSL, file: !3, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, retainedTypes: !4)
+!1 = !{i32 7, !"Dwarf Version", i32 5}
+!2 = !{i32 2, !"Debug Info Version", i32 3}
+!3 = !DIFile(filename: "typedef.hlsl", directory: "/src")
+!4 = !{!5}
+!5 = !DIDerivedType(tag: DW_TAG_typedef, name: "MyPtr", file: !3, line: 2, baseType: !7, scope: !3)
+!6 = distinct !DISubprogram(name: "test", scope: !3, file: !3, line: 1, type: !8, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!7 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !10, size: 64)
+!8 = !DISubroutineType(types: !9)
+!9 = !{null}
+!10 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-typedef.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-typedef.ll
new file mode 100644
index 0000000000000..de3df04366add
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-typedef.ll
@@ -0,0 +1,36 @@
+; RUN: llc --verify-machineinstrs --spirv-ext=+SPV_KHR_non_semantic_info -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s --check-prefix=CHECK-SPIRV
+; 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 %}
+
+; A DIDerivedType with DW_TAG_typedef lowers to DebugTypedef: Name, Base Type,
+; Source, Line, Column, Parent. A file-scope typedef parents to the compile unit.
+
+; CHECK-SPIRV: [[ext:%[0-9]+]] = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+; CHECK-SPIRV-DAG: [[void:%[0-9]+]] = OpTypeVoid
+; CHECK-SPIRV-DAG: [[i32:%[0-9]+]] = OpTypeInt 32 0
+; CHECK-SPIRV-DAG: [[str_int:%[0-9]+]] = OpString "int"
+; CHECK-SPIRV-DAG: [[str_myint:%[0-9]+]] = OpString "MyInt"
+; CHECK-SPIRV-DAG: [[c0:%[0-9]+]] = OpConstant [[i32]] 0{{$}}
+; CHECK-SPIRV-DAG: [[c2:%[0-9]+]] = OpConstant [[i32]] 2{{$}}
+; CHECK-SPIRV-DAG: [[ds:%[0-9]+]] = OpExtInst [[void]] [[ext]] DebugSource
+; CHECK-SPIRV-DAG: [[cu:%[0-9]+]] = OpExtInst [[void]] [[ext]] DebugCompilationUnit
+; CHECK-SPIRV-DAG: [[basic_int:%[0-9]+]] = OpExtInst [[void]] [[ext]] DebugTypeBasic [[str_int]]
+; CHECK-SPIRV-DAG: OpExtInst [[void]] [[ext]] DebugTypedef [[str_myint]] [[basic_int]] [[ds]] [[c2]] [[c0]] [[cu]]{{$}}
+
+define spir_func void @test() !dbg !6 {
+entry:
+ ret void
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!1, !2}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_HLSL, file: !3, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, retainedTypes: !4)
+!1 = !{i32 7, !"Dwarf Version", i32 5}
+!2 = !{i32 2, !"Debug Info Version", i32 3}
+!3 = !DIFile(filename: "typedef.hlsl", directory: "/src")
+!4 = !{!5}
+!5 = !DIDerivedType(tag: DW_TAG_typedef, name: "MyInt", file: !3, line: 2, baseType: !7, scope: !3)
+!6 = distinct !DISubprogram(name: "test", scope: !3, file: !3, line: 1, type: !8, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!7 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!8 = !DISubroutineType(types: !9)
+!9 = !{null}
More information about the llvm-commits
mailing list