[llvm] [SPIRV] Emit NonSemantic DebugTypedef (PR #211883)

Diego Novillo via llvm-commits llvm-commits at lists.llvm.org
Wed Jul 29 11:49:45 PDT 2026


https://github.com/dnovillo updated https://github.com/llvm/llvm-project/pull/211883

>From 68ce8d8a80d61fe193cf19c38c70de4adac8f219 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 1/4] [SPIRV] Emit NonSemantic DebugTypedef

---
 .../SPIRV/SPIRVNonSemanticDebugHandler.cpp    | 78 +++++++++++++++++--
 .../SPIRV/SPIRVNonSemanticDebugHandler.h      | 18 +++++
 .../debug-info/debug-typedef-nested-drop.ll   | 34 ++++++++
 .../debug-typedef-skip-base-not-in-regs.ll    | 30 +++++++
 .../CodeGen/SPIRV/debug-info/debug-typedef.ll | 36 +++++++++
 5 files changed, 189 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..6caf688f16354 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,47 @@ 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: the enclosing type when the scope is an emitted DIType (a typedef
+  // nested in another type), otherwise the compile unit. Fall back to the
+  // compile unit rather than dropping the typedef when the scope type is not
+  // emitted.
+  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 +984,14 @@ void SPIRVNonSemanticDebugHandler::emitNonSemanticDebugStrings(
     }
   }
 
+  // Cache the name and path OpStrings each DebugTypedef uses.
+  for (const DIDerivedType *TD : TypedefTypes) {
+    emitOpStringIfNew(TD->getName(), MAI);
+    MCRegister PathReg = emitOpStringIfNew(getDebugFullPath(TD->getFile()), MAI);
+    if (const DIFile *F = TD->getFile())
+      ScopeToPathOpStringReg[F] = PathReg;
+  }
+
   for (const auto &[GV, _] : GlobalVariableDebugInfoMap) {
     emitOpStringIfNew(GV->getName(), MAI);
     emitOpStringIfNew(GV->getLinkageName(), MAI);
@@ -1086,6 +1139,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..df4cf9973d9af
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-typedef-nested-drop.ll
@@ -0,0 +1,34 @@
+; 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: OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+; CHECK-SPIRV: DebugTypedef
+
+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}

>From 399e9410afd14da9c66fe2b034445736f31ac886 Mon Sep 17 00:00:00 2001
From: Diego Novillo <dnovillo at nvidia.com>
Date: Mon, 27 Jul 2026 11:37:29 -0400
Subject: [PATCH 2/4] fix formatting issues.

---
 llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
index 6caf688f16354..9517fdc58e464 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
@@ -932,10 +932,9 @@ std::optional<MCRegister> SPIRVNonSemanticDebugHandler::emitDebugTypedef(
     ParentReg = *CURegOpt;
   }
 
-  return emitExtInst(SPIRV::NonSemanticExtInst::DebugTypedef, VoidTypeReg,
-                     ExtInstSetReg,
-                     {NameReg, *BaseRegOpt, SrcReg, LineReg, ColReg, ParentReg},
-                     MAI);
+  return emitExtInst(
+      SPIRV::NonSemanticExtInst::DebugTypedef, VoidTypeReg, ExtInstSetReg,
+      {NameReg, *BaseRegOpt, SrcReg, LineReg, ColReg, ParentReg}, MAI);
 }
 
 void SPIRVNonSemanticDebugHandler::emitNonSemanticDebugStrings(
@@ -987,7 +986,8 @@ void SPIRVNonSemanticDebugHandler::emitNonSemanticDebugStrings(
   // Cache the name and path OpStrings each DebugTypedef uses.
   for (const DIDerivedType *TD : TypedefTypes) {
     emitOpStringIfNew(TD->getName(), MAI);
-    MCRegister PathReg = emitOpStringIfNew(getDebugFullPath(TD->getFile()), MAI);
+    MCRegister PathReg =
+        emitOpStringIfNew(getDebugFullPath(TD->getFile()), MAI);
     if (const DIFile *F = TD->getFile())
       ScopeToPathOpStringReg[F] = PathReg;
   }

>From f203219b3cfa0efc83b3469f6bd543f39848dc69 Mon Sep 17 00:00:00 2001
From: Diego Novillo <dnovillo at nvidia.com>
Date: Mon, 27 Jul 2026 15:35:30 -0400
Subject: [PATCH 3/4] Address review feedback

---
 llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
index 9517fdc58e464..3ae8f2d7d4d1f 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
@@ -915,10 +915,12 @@ std::optional<MCRegister> SPIRVNonSemanticDebugHandler::emitDebugTypedef(
   // DIDerivedType typedefs carry no column, so emit 0.
   MCRegister ColReg = emitOpConstantI32(0, I32TypeReg, MAI);
 
-  // Parent: the enclosing type when the scope is an emitted DIType (a typedef
-  // nested in another type), otherwise the compile unit. Fall back to the
-  // compile unit rather than dropping the typedef when the scope type is not
-  // emitted.
+  // 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))

>From 2ec17825caa492e2069f275c2413a9d17712f474 Mon Sep 17 00:00:00 2001
From: Diego Novillo <dnovillo at nvidia.com>
Date: Wed, 29 Jul 2026 14:47:35 -0400
Subject: [PATCH 4/4] Address review feedback.

---
 .../Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp   |  5 +----
 .../SPIRV/debug-info/debug-typedef-nested-drop.ll   | 13 +++++++++++--
 2 files changed, 12 insertions(+), 6 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
index 3ae8f2d7d4d1f..f22cc1a38a794 100644
--- a/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVNonSemanticDebugHandler.cpp
@@ -988,10 +988,7 @@ void SPIRVNonSemanticDebugHandler::emitNonSemanticDebugStrings(
   // Cache the name and path OpStrings each DebugTypedef uses.
   for (const DIDerivedType *TD : TypedefTypes) {
     emitOpStringIfNew(TD->getName(), MAI);
-    MCRegister PathReg =
-        emitOpStringIfNew(getDebugFullPath(TD->getFile()), MAI);
-    if (const DIFile *F = TD->getFile())
-      ScopeToPathOpStringReg[F] = PathReg;
+    emitAndCacheScopePathOpStringReg(TD->getFile(), MAI);
   }
 
   for (const auto &[GV, _] : GlobalVariableDebugInfoMap) {
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
index df4cf9973d9af..39b9852fc0490 100644
--- a/llvm/test/CodeGen/SPIRV/debug-info/debug-typedef-nested-drop.ll
+++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-typedef-nested-drop.ll
@@ -7,8 +7,17 @@
 ; 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: OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
-; CHECK-SPIRV: DebugTypedef
+; 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:



More information about the llvm-commits mailing list