Skip to content

Reland "[DebugMetadata][DwarfDebug] Support function-local types in lexical block scopes (4/7)"#165032

Merged
dzhidzhoev merged 7 commits intollvm:mainfrom
dzhidzhoev:debuginfo/rfc-krisb/4/base
Feb 3, 2026
Merged

Reland "[DebugMetadata][DwarfDebug] Support function-local types in lexical block scopes (4/7)"#165032
dzhidzhoev merged 7 commits intollvm:mainfrom
dzhidzhoev:debuginfo/rfc-krisb/4/base

Conversation

@dzhidzhoev
Copy link
Copy Markdown
Member

@dzhidzhoev dzhidzhoev commented Oct 24, 2025

This is an attempt to merge https://reviews.llvm.org/D144006 with LTO fix.

The last merge attempt was #75385.
The issue with it was investigated in #75385 (comment).
The problem happens when

  1. Several modules are being linked.
  2. There are several DISubprograms that initially belong to different modules but represent the same source code function (for example, a function included from the same source code file).
  3. Some of such DISubprograms survive IR linking. It may happen if one of them is inlined somewhere or if the functions that have these DISubprograms attached have internal linkage.
  4. Each of these DISubprograms has a local type that corresponds to the same source code type. These types are initially from different modules, but have the same ODR identifier.

If the same (in the sense of ODR identifier/ODR uniquing rules) local type is present in two modules, and these modules are linked together, the type gets uniqued. A DIType, that happens to be loaded first, survives linking, and the references on other types with the same ODR identifier from the modules loaded later are replaced with the references on the DIType loaded first. Since defintion subprograms, in scope of which these types are located, are not deduplicated, the linker output may contain multiple DISubprogram's having the same (uniqued) type in their retainedNodes lists.
Further compilation of such modules causes crashes.

To tackle that,

  • previous solution to handle LTO linking with local types in retainedNodes is removed (cloneLocalTypes() function),
  • for each loaded distinct (definition) DISubprogram, its retainedNodes list is scanned after loading, and DITypes with a scope of another subprogram are removed. If something from a Function corresponding to the DISubprogram references uniqued type, we rely on cross-CU links.

With this approach, clang builds without crashes in FullLTO (which is not the case for #75385).

Additionally:

Commit #75385 and the new changes are separate commits to simplify review process.

@llvmbot
Copy link
Copy Markdown
Member

llvmbot commented Oct 24, 2025

@llvm/pr-subscribers-clang-codegen
@llvm/pr-subscribers-llvm-transforms
@llvm/pr-subscribers-clang-static-analyzer-1
@llvm/pr-subscribers-backend-x86
@llvm/pr-subscribers-debuginfo

@llvm/pr-subscribers-llvm-ir

Author: Vladislav Dzhidzhoev (dzhidzhoev)

Changes

This is an attempt to merge https://reviews.llvm.org/D144006 with LTO fix.

The last merge attempt was #75385.
The issue with it was investigated in #75385 (comment).
If the same (in the sense of ODR identifier/ODR uniquing rules) local type is present in two modules, and these modules are linked together, the type gets uniqued. A DIType, that happens to be loaded first, survives linking, and the references on other types with the same ODR identifier from the modules loaded later are replaced with the references on the DIType loaded first. Since defintion subprograms, in scope of which these types are located, are not deduplicated, the linker output may contain multiple DISubprogram's having the same (uniqued) type in their retainedNodes lists.
Further compilation of such modules causes crashes.

To tackle that,

  • previous solution to handle LTO linking with local types in retainedNodes is removed (cloneLocalTypes() function),
  • for each loaded distinct (definition) DISubprogram, its retainedNodes list is scanned after loading, and DITypes with a scope of another subprogram are removed. If something from a Function corresponding to the DISubprogram references uniqued type, we rely on cross-CU links.

With this approach, clang builds without crashes in FullLTO (which is not the case for #75385).

Additionally:

Commit #75385 and the new changes are separate commits to simplify review process.


Patch is 112.61 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/165032.diff

40 Files Affected:

  • (modified) clang/test/Analysis/analyzeOneFunction.cpp (+2-2)
  • (modified) clang/test/DebugInfo/CXX/access.cpp (+1-1)
  • (modified) clang/test/DebugInfo/CXX/anon-union-vars.cpp (+6-6)
  • (modified) clang/test/DebugInfo/CXX/codeview-unnamed.cpp (+62-48)
  • (modified) clang/test/DebugInfo/CXX/gline-tables-only-codeview.cpp (+2-2)
  • (modified) clang/test/DebugInfo/CXX/lambda-capture-packs.cpp (+7-8)
  • (modified) clang/test/DebugInfo/CXX/lambda-this.cpp (+2-2)
  • (modified) clang/test/DebugInfo/Generic/codeview-unnamed.c (+8-8)
  • (modified) clang/test/DebugInfo/Generic/unused-types.c (+9-7)
  • (modified) clang/test/DebugInfo/Generic/unused-types.cpp (+8-6)
  • (modified) llvm/include/llvm/AsmParser/LLParser.h (+4)
  • (modified) llvm/include/llvm/IR/DIBuilder.h (+3-3)
  • (modified) llvm/include/llvm/IR/DebugInfoMetadata.h (+15)
  • (modified) llvm/lib/AsmParser/LLParser.cpp (+7)
  • (modified) llvm/lib/Bitcode/Reader/MetadataLoader.cpp (+65-33)
  • (modified) llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.cpp (+42-22)
  • (modified) llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.h (+8-8)
  • (modified) llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp (+10-3)
  • (modified) llvm/lib/IR/DIBuilder.cpp (+48-18)
  • (modified) llvm/lib/IR/DebugInfo.cpp (+2)
  • (modified) llvm/lib/IR/DebugInfoMetadata.cpp (+39)
  • (modified) llvm/lib/IR/Verifier.cpp (+13-3)
  • (modified) llvm/lib/Transforms/Utils/CloneFunction.cpp (+15-2)
  • (added) llvm/test/Bitcode/local-type-scope.ll (+48)
  • (modified) llvm/test/Bitcode/upgrade-cu-locals.ll (+41-27)
  • (modified) llvm/test/Bitcode/upgrade-cu-locals.ll.bc ()
  • (modified) llvm/test/CodeGen/ARM/call-graph-section-assembly.ll (+2-2)
  • (modified) llvm/test/CodeGen/X86/call-graph-section-assembly.ll (+2-2)
  • (added) llvm/test/DebugInfo/Generic/inlined-local-type.ll (+128)
  • (added) llvm/test/DebugInfo/Generic/lexical-block-retained-types.ll (+55)
  • (added) llvm/test/DebugInfo/Generic/lexical-block-types.ll (+425)
  • (modified) llvm/test/DebugInfo/Generic/verifier-invalid-disubprogram.ll (+1-1)
  • (added) llvm/test/DebugInfo/Inputs/cleanup-retained-nodes.ll (+17)
  • (added) llvm/test/DebugInfo/X86/cleanup-retained-nodes.ll (+46)
  • (added) llvm/test/DebugInfo/X86/llparser-cleanup-retained-nodes.ll (+37)
  • (added) llvm/test/DebugInfo/X86/local-type-as-template-parameter.ll (+161)
  • (renamed) llvm/test/DebugInfo/X86/split-dwarf-local-import.ll (-1)
  • (renamed) llvm/test/DebugInfo/X86/split-dwarf-local-import2.ll (-1)
  • (renamed) llvm/test/DebugInfo/X86/split-dwarf-local-import3.ll ()
  • (modified) llvm/unittests/Transforms/Utils/CloningTest.cpp (+105)
diff --git a/clang/test/Analysis/analyzeOneFunction.cpp b/clang/test/Analysis/analyzeOneFunction.cpp
index 3a362dfd9a08c..e4c96eb35f06b 100644
--- a/clang/test/Analysis/analyzeOneFunction.cpp
+++ b/clang/test/Analysis/analyzeOneFunction.cpp
@@ -5,9 +5,9 @@
 // RUN:   -analyze-function="c:@S@Window@F@overloaded#I#"
 
 // RUN: %clang_extdef_map %s | FileCheck %s
-// CHECK:      27:c:@S@Window@F@overloaded#I#
+// CHECK:      27:c:@S@Window@F@overloaded#d#
 // CHECK-NEXT: 27:c:@S@Window@F@overloaded#C#
-// CHECK-NEXT: 27:c:@S@Window@F@overloaded#d#
+// CHECK-NEXT: 27:c:@S@Window@F@overloaded#I#
 
 void clang_analyzer_warnIfReached();
 
diff --git a/clang/test/DebugInfo/CXX/access.cpp b/clang/test/DebugInfo/CXX/access.cpp
index 9f2c044843d0f..7c0bf8ccb0384 100644
--- a/clang/test/DebugInfo/CXX/access.cpp
+++ b/clang/test/DebugInfo/CXX/access.cpp
@@ -18,9 +18,9 @@ class B : public A {
   static int public_static;
 
 protected:
+  // CHECK-DAG: !DIDerivedType(tag: DW_TAG_typedef, name: "prot_using",{{.*}} line: [[@LINE+3]],{{.*}} flags: DIFlagProtected)
   // CHECK-DAG: !DIDerivedType(tag: DW_TAG_typedef, name: "prot_typedef",{{.*}} line: [[@LINE+1]],{{.*}} flags: DIFlagProtected)
   typedef int prot_typedef;
-  // CHECK-DAG: !DIDerivedType(tag: DW_TAG_typedef, name: "prot_using",{{.*}} line: [[@LINE+1]],{{.*}} flags: DIFlagProtected)
   using prot_using = prot_typedef;
   prot_using prot_member;
 
diff --git a/clang/test/DebugInfo/CXX/anon-union-vars.cpp b/clang/test/DebugInfo/CXX/anon-union-vars.cpp
index 3aca4e199ab8d..27e6c6850e013 100644
--- a/clang/test/DebugInfo/CXX/anon-union-vars.cpp
+++ b/clang/test/DebugInfo/CXX/anon-union-vars.cpp
@@ -51,13 +51,13 @@ void instantiate(int x) {
 // CHECK: !DIGlobalVariable(name: "b",{{.*}} file: [[FILE]], line: 6,{{.*}} isLocal: true, isDefinition: true
 // CHECK: !DIGlobalVariable(name: "result", {{.*}} isLocal: false, isDefinition: true
 // CHECK: !DIGlobalVariable(name: "value", {{.*}} isLocal: false, isDefinition: true
-// CHECK: !DILocalVariable(name: "i", {{.*}}, flags: DIFlagArtificial
-// CHECK: !DILocalVariable(name: "c", {{.*}}, flags: DIFlagArtificial
-// CHECK: !DILocalVariable(
-// CHECK-NOT: name:
-// CHECK: type: ![[UNION:[0-9]+]]
-// CHECK: ![[UNION]] = distinct !DICompositeType(tag: DW_TAG_union_type,
+// CHECK: ![[UNION:[0-9]+]] = distinct !DICompositeType(tag: DW_TAG_union_type,
 // CHECK-NOT: name:
 // CHECK: elements
 // CHECK: !DIDerivedType(tag: DW_TAG_member, name: "i", scope: ![[UNION]],
 // CHECK: !DIDerivedType(tag: DW_TAG_member, name: "c", scope: ![[UNION]],
+// CHECK: !DILocalVariable(name: "i", {{.*}}, flags: DIFlagArtificial
+// CHECK: !DILocalVariable(name: "c", {{.*}}, flags: DIFlagArtificial
+// CHECK: !DILocalVariable(
+// CHECK-NOT: name:
+// CHECK: type: ![[UNION]]
diff --git a/clang/test/DebugInfo/CXX/codeview-unnamed.cpp b/clang/test/DebugInfo/CXX/codeview-unnamed.cpp
index 30815bda020ea..b625a80313c38 100644
--- a/clang/test/DebugInfo/CXX/codeview-unnamed.cpp
+++ b/clang/test/DebugInfo/CXX/codeview-unnamed.cpp
@@ -4,6 +4,60 @@
 
 int main(int argc, char* argv[], char* arge[]) {
   //
+  // LINUX:      [[TYPE_OF_ONE:![0-9]+]] = distinct !DICompositeType(
+  // LINUX-SAME:     tag: DW_TAG_structure_type
+  // LINUX-NOT:      name:
+  // LINUX-NOT:      identifier:
+  // LINUX-SAME:     )
+  //
+  // MSVC:       [[TYPE_OF_ONE:![0-9]+]] = distinct !DICompositeType
+  // MSVC-SAME:      tag: DW_TAG_structure_type
+  // MSVC-SAME:      name: "<unnamed-type-one>"
+  // MSVC-SAME:      identifier: ".?AU<unnamed-type-one>@?1??main@@9@"
+  // MSVC-SAME:      )
+
+
+  //
+  // LINUX:      [[TYPE_OF_TWO:![0-9]+]] = distinct !DICompositeType(
+  // LINUX-SAME:     tag: DW_TAG_structure_type
+  // LINUX-NOT:      name:
+  // LINUX-NOT:      identifier:
+  // LINUX-SAME:     )
+  //
+  // MSVC:       [[TYPE_OF_TWO:![0-9]+]] = distinct !DICompositeType
+  // MSVC-SAME:      tag: DW_TAG_structure_type
+  // MSVC-SAME:      name: "<unnamed-type-two>"
+  // MSVC-SAME:      identifier: ".?AU<unnamed-type-two>@?2??main@@9@"
+  // MSVC-SAME:      )
+
+
+  //
+  // LINUX:      [[TYPE_OF_THREE:![0-9]+]] = distinct !DICompositeType(
+  // LINUX-SAME:     tag: DW_TAG_structure_type
+  // LINUX-SAME:     name: "named"
+  // LINUX-NOT:      identifier:
+  // LINUX-SAME:     )
+  //
+  // MSVC:       [[TYPE_OF_THREE:![0-9]+]] = distinct !DICompositeType
+  // MSVC-SAME:      tag: DW_TAG_structure_type
+  // MSVC-SAME:      name: "named"
+  // MSVC-SAME:      identifier: ".?AUnamed@?1??main@@9@"
+  // MSVC-SAME:      )
+
+  //
+  // LINUX:      [[TYPE_OF_FOUR:![0-9]+]] = distinct !DICompositeType(
+  // LINUX-SAME:     tag: DW_TAG_class_type
+  // LINUX-NOT:      name:
+  // LINUX-NOT:      identifier:
+  // LINUX-SAME:     )
+  //
+  // MSVC:       [[TYPE_OF_FOUR:![0-9]+]] = distinct !DICompositeType
+  // MSVC-SAME:      tag: DW_TAG_class_type
+  // MSVC-SAME:      name: "<lambda_0>"
+  // MSVC-SAME:      identifier: ".?AV<lambda_0>@?0??main@@9@"
+  // MSVC-SAME:      )
+
+
   // In CodeView, the LF_MFUNCTION entry for "bar()" refers to the forward
   // reference of the unnamed struct. Visual Studio requires a unique
   // identifier to match the LF_STRUCTURE forward reference to the definition.
@@ -11,21 +65,11 @@ int main(int argc, char* argv[], char* arge[]) {
   struct { void bar() {} } one;
   //
   // LINUX:      !{{[0-9]+}} = !DILocalVariable(name: "one"
-  // LINUX-SAME:     type: [[TYPE_OF_ONE:![0-9]+]]
-  // LINUX-SAME:     )
-  // LINUX:      [[TYPE_OF_ONE]] = distinct !DICompositeType(
-  // LINUX-SAME:     tag: DW_TAG_structure_type
-  // LINUX-NOT:      name:
-  // LINUX-NOT:      identifier:
+  // LINUX-SAME:     type: [[TYPE_OF_ONE]]
   // LINUX-SAME:     )
   //
   // MSVC:       !{{[0-9]+}} = !DILocalVariable(name: "one"
-  // MSVC-SAME:      type: [[TYPE_OF_ONE:![0-9]+]]
-  // MSVC-SAME:      )
-  // MSVC:       [[TYPE_OF_ONE]] = distinct !DICompositeType
-  // MSVC-SAME:      tag: DW_TAG_structure_type
-  // MSVC-SAME:      name: "<unnamed-type-one>"
-  // MSVC-SAME:      identifier: ".?AU<unnamed-type-one>@?1??main@@9@"
+  // MSVC-SAME:      type: [[TYPE_OF_ONE]]
   // MSVC-SAME:      )
 
 
@@ -37,21 +81,11 @@ int main(int argc, char* argv[], char* arge[]) {
   int decltype(two)::*ptr2unnamed = &decltype(two)::bar;
   //
   // LINUX:      !{{[0-9]+}} = !DILocalVariable(name: "two"
-  // LINUX-SAME:     type: [[TYPE_OF_TWO:![0-9]+]]
-  // LINUX-SAME:     )
-  // LINUX:      [[TYPE_OF_TWO]] = distinct !DICompositeType(
-  // LINUX-SAME:     tag: DW_TAG_structure_type
-  // LINUX-NOT:      name:
-  // LINUX-NOT:      identifier:
+  // LINUX-SAME:     type: [[TYPE_OF_TWO]]
   // LINUX-SAME:     )
   //
   // MSVC:       !{{[0-9]+}} = !DILocalVariable(name: "two"
-  // MSVC-SAME:      type: [[TYPE_OF_TWO:![0-9]+]]
-  // MSVC-SAME:      )
-  // MSVC:       [[TYPE_OF_TWO]] = distinct !DICompositeType
-  // MSVC-SAME:      tag: DW_TAG_structure_type
-  // MSVC-SAME:      name: "<unnamed-type-two>"
-  // MSVC-SAME:      identifier: ".?AU<unnamed-type-two>@?2??main@@9@"
+  // MSVC-SAME:      type: [[TYPE_OF_TWO]]
   // MSVC-SAME:      )
 
 
@@ -62,21 +96,11 @@ int main(int argc, char* argv[], char* arge[]) {
   struct named { int bar; int named::* p2mem; } three = { 42, &named::bar };
   //
   // LINUX:      !{{[0-9]+}} = !DILocalVariable(name: "three"
-  // LINUX-SAME:     type: [[TYPE_OF_THREE:![0-9]+]]
-  // LINUX-SAME:     )
-  // LINUX:      [[TYPE_OF_THREE]] = distinct !DICompositeType(
-  // LINUX-SAME:     tag: DW_TAG_structure_type
-  // LINUX-SAME:     name: "named"
-  // LINUX-NOT:      identifier:
+  // LINUX-SAME:     type: [[TYPE_OF_THREE]]
   // LINUX-SAME:     )
   //
   // MSVC:       !{{[0-9]+}} = !DILocalVariable(name: "three"
-  // MSVC-SAME:      type: [[TYPE_OF_THREE:![0-9]+]]
-  // MSVC-SAME:      )
-  // MSVC:       [[TYPE_OF_THREE]] = distinct !DICompositeType
-  // MSVC-SAME:      tag: DW_TAG_structure_type
-  // MSVC-SAME:      name: "named"
-  // MSVC-SAME:      identifier: ".?AUnamed@?1??main@@9@"
+  // MSVC-SAME:      type: [[TYPE_OF_THREE]]
   // MSVC-SAME:      )
 
 
@@ -88,21 +112,11 @@ int main(int argc, char* argv[], char* arge[]) {
   auto four = [argc](int i) -> int { return argc == i ? 1 : 0; };
   //
   // LINUX:      !{{[0-9]+}} = !DILocalVariable(name: "four"
-  // LINUX-SAME:     type: [[TYPE_OF_FOUR:![0-9]+]]
-  // LINUX-SAME:     )
-  // LINUX:      [[TYPE_OF_FOUR]] = distinct !DICompositeType(
-  // LINUX-SAME:     tag: DW_TAG_class_type
-  // LINUX-NOT:      name:
-  // LINUX-NOT:      identifier:
+  // LINUX-SAME:     type: [[TYPE_OF_FOUR]]
   // LINUX-SAME:     )
   //
   // MSVC:       !{{[0-9]+}} = !DILocalVariable(name: "four"
-  // MSVC-SAME:      type: [[TYPE_OF_FOUR:![0-9]+]]
-  // MSVC-SAME:      )
-  // MSVC:       [[TYPE_OF_FOUR]] = distinct !DICompositeType
-  // MSVC-SAME:      tag: DW_TAG_class_type
-  // MSVC-SAME:      name: "<lambda_0>"
-  // MSVC-SAME:      identifier: ".?AV<lambda_0>@?0??main@@9@"
+  // MSVC-SAME:      type: [[TYPE_OF_FOUR]]
   // MSVC-SAME:      )
 
   return 0;
diff --git a/clang/test/DebugInfo/CXX/gline-tables-only-codeview.cpp b/clang/test/DebugInfo/CXX/gline-tables-only-codeview.cpp
index 6b9c9a243decd..122e4aa62ea7d 100644
--- a/clang/test/DebugInfo/CXX/gline-tables-only-codeview.cpp
+++ b/clang/test/DebugInfo/CXX/gline-tables-only-codeview.cpp
@@ -51,9 +51,9 @@ void test() {
   // CHECK-SAME:                              name: "<lambda_2_1>",
   c.lambda_params();
 
-  // CHECK: !DISubprogram(name: "operator()", scope: ![[LAMBDA1:[0-9]+]],
-  // CHECK: ![[LAMBDA1]] = !DICompositeType(tag: DW_TAG_class_type,
+  // CHECK: ![[LAMBDA1:[0-9]+]] = !DICompositeType(tag: DW_TAG_class_type,
   // CHECK-SAME:                            name: "<lambda_1>",
   // CHECK-SAME:                            flags: DIFlagFwdDecl
+  // CHECK: !DISubprogram(name: "operator()", scope: ![[LAMBDA1]],
   c.lambda2();
 }
diff --git a/clang/test/DebugInfo/CXX/lambda-capture-packs.cpp b/clang/test/DebugInfo/CXX/lambda-capture-packs.cpp
index 021b85d4ce3a4..609a71c6ec015 100644
--- a/clang/test/DebugInfo/CXX/lambda-capture-packs.cpp
+++ b/clang/test/DebugInfo/CXX/lambda-capture-packs.cpp
@@ -2,14 +2,6 @@
 // RUN:   -debug-info-kind=standalone -std=c++26 %s -o - | FileCheck %s
 
 
-// CHECK: ![[PACK1:[0-9]+]] = distinct !DISubprogram(name: "capture_pack<int>"
-// CHECK: ![[PACK2:[0-9]+]] = distinct !DISubprogram(name: "capture_pack<int, int>"
-// CHECK: ![[PACK3:[0-9]+]] = distinct !DISubprogram(name: "capture_pack_and_locals<int>"
-// CHECK: ![[PACK4:[0-9]+]] = distinct !DISubprogram(name: "capture_pack_and_locals<int, int>"
-// CHECK: ![[PACK5:[0-9]+]] = distinct !DISubprogram(name: "capture_pack_and_this<int>"
-// CHECK: ![[PACK6:[0-9]+]] = distinct !DISubprogram(name: "capture_pack_and_this<int, int>"
-// CHECK: ![[PACK7:[0-9]+]] = distinct !DISubprogram(name: "capture_binding_and_param_pack<int, int>"
-
 template<typename... Args>
 auto capture_pack(Args... args) {
   return [args..., ...params = args] {
@@ -17,6 +9,7 @@ auto capture_pack(Args... args) {
   }();
 }
 
+// CHECK: ![[PACK1:[0-9]+]] = distinct !DISubprogram(name: "capture_pack<int>"
 // CHECK:      distinct !DICompositeType(tag: DW_TAG_class_type, scope: ![[PACK1]]
 // CHECK-SAME:                           elements: ![[PACK1_ELEMS:[0-9]+]]
 // CHECK-DAG:  ![[PACK1_ELEMS]] = !{![[PACK1_ARGS:[0-9]+]], ![[PACK1_PARAMS:[0-9]+]]}
@@ -24,6 +17,7 @@ auto capture_pack(Args... args) {
 // CHECK-DAG:  ![[PACK1_PARAMS]] = !DIDerivedType(tag: DW_TAG_member, name: "params"
 // CHECK-NOT:  DW_TAG_member
 
+// CHECK: ![[PACK2:[0-9]+]] = distinct !DISubprogram(name: "capture_pack<int, int>"
 // CHECK:      distinct !DICompositeType(tag: DW_TAG_class_type, scope: ![[PACK2]]
 // CHECK-SAME:                           elements: ![[PACK2_ELEMS:[0-9]+]]
 // CHECK:      ![[PACK2_ELEMS]] = !{![[PACK2_ARGS:[0-9]+]]
@@ -42,6 +36,7 @@ auto capture_pack_and_locals(int x, Args... args) {
   }();
 }
 
+// CHECK: ![[PACK3:[0-9]+]] = distinct !DISubprogram(name: "capture_pack_and_locals<int>"
 // CHECK:      distinct !DICompositeType(tag: DW_TAG_class_type, scope: ![[PACK3]]
 // CHECK-SAME:                           elements: ![[PACK3_ELEMS:[0-9]+]]
 // CHECK:      ![[PACK3_ELEMS]] = !{![[PACK3_ARGS:[0-9]+]]
@@ -55,6 +50,7 @@ auto capture_pack_and_locals(int x, Args... args) {
 // CHECK-DAG:  ![[PACK3_W]] = !DIDerivedType(tag: DW_TAG_member, name: "w"
 // CHECK-NOT:  DW_TAG_member
 
+// CHECK: ![[PACK4:[0-9]+]] = distinct !DISubprogram(name: "capture_pack_and_locals<int, int>"
 // CHECK:      distinct !DICompositeType(tag: DW_TAG_class_type, scope: ![[PACK4]]
 // CHECK-SAME:                           elements: ![[PACK4_ELEMS:[0-9]+]]
 // CHECK:      ![[PACK4_ELEMS]] = !{![[PACK4_ARGS:[0-9]+]]
@@ -90,6 +86,7 @@ struct Foo {
   int w = 10;
 } f;
 
+// CHECK: ![[PACK5:[0-9]+]] = distinct !DISubprogram(name: "capture_pack_and_this<int>"
 // CHECK:      distinct !DICompositeType(tag: DW_TAG_class_type, scope: ![[PACK5]]
 // CHECK-SAME:                           elements: ![[PACK5a_ELEMS:[0-9]+]]
 // CHECK:      ![[PACK5a_ELEMS]] = !{![[PACK5a_THIS:[0-9]+]]
@@ -120,6 +117,7 @@ struct Foo {
 // CHECK-DAG:  ![[PACK5c_THIS]] = !DIDerivedType(tag: DW_TAG_member, name: "this"
 // CHECK-NOT:  DW_TAG_member
 
+// CHECK: ![[PACK6:[0-9]+]] = distinct !DISubprogram(name: "capture_pack_and_this<int, int>"
 // CHECK:      distinct !DICompositeType(tag: DW_TAG_class_type, scope: ![[PACK6]]
 // CHECK-SAME:                           elements: ![[PACK6a_ELEMS:[0-9]+]]
 // CHECK:      ![[PACK6a_ELEMS]] = !{![[PACK6a_THIS:[0-9]+]]
@@ -168,6 +166,7 @@ auto capture_binding_and_param_pack(Args... args) {
   }();
 }
 
+// CHECK: ![[PACK7:[0-9]+]] = distinct !DISubprogram(name: "capture_binding_and_param_pack<int, int>"
 // CHECK: distinct !DICompositeType(tag: DW_TAG_structure_type, name: "C"
 // CHECK:      distinct !DICompositeType(tag: DW_TAG_class_type, scope: ![[PACK7]]
 // CHECK-SAME:                           elements: ![[PACK7_ELEMS:[0-9]+]]
diff --git a/clang/test/DebugInfo/CXX/lambda-this.cpp b/clang/test/DebugInfo/CXX/lambda-this.cpp
index 019d09c48f858..1df210953ac2e 100644
--- a/clang/test/DebugInfo/CXX/lambda-this.cpp
+++ b/clang/test/DebugInfo/CXX/lambda-this.cpp
@@ -13,10 +13,10 @@ void D::d(int x) {
 }
 
 // CHECK: ![[D:[0-9]+]] = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "D",
-// CHECK: ![[POINTER:.*]] = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: ![[D]], size: 64)
 // CHECK: !DIDerivedType(tag: DW_TAG_member, name: "this",
 // CHECK-SAME:           line: 11
-// CHECK-SAME:           baseType: ![[POINTER]]
+// CHECK-SAME:           baseType: ![[POINTER:[0-9]+]]
 // CHECK-SAME:           size: 64
 // CHECK-NOT:            offset: 0
 // CHECK-SAME:           ){{$}}
+// CHECK: ![[POINTER]] = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: ![[D]], size: 64)
diff --git a/clang/test/DebugInfo/Generic/codeview-unnamed.c b/clang/test/DebugInfo/Generic/codeview-unnamed.c
index 0df6e1a0419bb..7b88f53a92e42 100644
--- a/clang/test/DebugInfo/Generic/codeview-unnamed.c
+++ b/clang/test/DebugInfo/Generic/codeview-unnamed.c
@@ -8,23 +8,23 @@ int main(int argc, char* argv[], char* arge[]) {
   //
   struct { int bar; } one = {42};
   //
-  // LINUX:      !{{[0-9]+}} = !DILocalVariable(name: "one"
-  // LINUX-SAME:     type: [[TYPE_OF_ONE:![0-9]+]]
-  // LINUX-SAME:     )
-  // LINUX:      [[TYPE_OF_ONE]] = distinct !DICompositeType(
+  // LINUX:      [[TYPE_OF_ONE:![0-9]+]] = distinct !DICompositeType(
   // LINUX-SAME:     tag: DW_TAG_structure_type
   // LINUX-NOT:      name:
   // LINUX-NOT:      identifier:
   // LINUX-SAME:     )
+  // LINUX:      !{{[0-9]+}} = !DILocalVariable(name: "one"
+  // LINUX-SAME:     type: [[TYPE_OF_ONE]]
+  // LINUX-SAME:     )
   //
-  // MSVC:       !{{[0-9]+}} = !DILocalVariable(name: "one"
-  // MSVC-SAME:      type: [[TYPE_OF_ONE:![0-9]+]]
-  // MSVC-SAME:      )
-  // MSVC:       [[TYPE_OF_ONE]] = distinct !DICompositeType
+  // MSVC:       [[TYPE_OF_ONE:![0-9]+]] = distinct !DICompositeType
   // MSVC-SAME:      tag: DW_TAG_structure_type
   // MSVC-NOT:       name:
   // MSVC-NOT:       identifier:
   // MSVC-SAME:      )
+  // MSVC:       !{{[0-9]+}} = !DILocalVariable(name: "one"
+  // MSVC-SAME:      type: [[TYPE_OF_ONE]]
+  // MSVC-SAME:      )
 
   return 0;
 }
diff --git a/clang/test/DebugInfo/Generic/unused-types.c b/clang/test/DebugInfo/Generic/unused-types.c
index 3e9f7b07658e3..31d608d92a06b 100644
--- a/clang/test/DebugInfo/Generic/unused-types.c
+++ b/clang/test/DebugInfo/Generic/unused-types.c
@@ -18,13 +18,15 @@ void quux(void) {
 // CHECK: !DICompileUnit{{.+}}retainedTypes: [[RETTYPES:![0-9]+]]
 // CHECK: [[TYPE0:![0-9]+]] = !DICompositeType(tag: DW_TAG_enumeration_type, name: "bar"
 // CHECK: [[TYPE1:![0-9]+]] = !DIEnumerator(name: "BAR"
-// CHECK: [[TYPE2:![0-9]+]] = !DICompositeType(tag: DW_TAG_enumeration_type, name: "z"
-// CHECK: [[TYPE3:![0-9]+]] = !DIEnumerator(name: "Z"
-// CHECK: [[RETTYPES]] = !{[[TYPE4:![0-9]+]], [[TYPE5:![0-9]+]], [[TYPE0]], [[TYPE6:![0-9]+]], {{![0-9]+}}, [[TYPE7:![0-9]+]], [[TYPE2]], [[TYPE8:![0-9]+]]}
-// CHECK: [[TYPE4]] = !DIDerivedType(tag: DW_TAG_typedef, name: "my_int"
-// CHECK: [[TYPE5]] = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "foo"
-// CHECK: [[TYPE6]] = distinct !DICompositeType(tag: DW_TAG_union_type, name: "baz"
-// CHECK: [[TYPE7]] = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "y"
+// CHECK: [[RETTYPES]] = !{[[TYPE2:![0-9]+]], [[TYPE3:![0-9]+]], [[TYPE0]], [[TYPE4:![0-9]+]], {{![0-9]+}}}
+// CHECK: [[TYPE2]] = !DIDerivedType(tag: DW_TAG_typedef, name: "my_int"
+// CHECK: [[TYPE3]] = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "foo"
+// CHECK: [[TYPE4]] = distinct !DICompositeType(tag: DW_TAG_union_type, name: "baz"
+// CHECK: [[SP:![0-9]+]] = distinct !DISubprogram(name: "quux", {{.*}}, retainedNodes: [[SPRETNODES:![0-9]+]]
+// CHECK: [[SPRETNODES]] = !{[[TYPE5:![0-9]+]], [[TYPE6:![0-9]+]], [[TYPE8:![0-9]+]]}
+// CHECK: [[TYPE5]] = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "y"
+// CHECK: [[TYPE6]] = !DICompositeType(tag: DW_TAG_enumeration_type, name: "z"
+// CHECK: [[TYPE7:![0-9]+]] = !DIEnumerator(name: "Z"
 // CHECK: [[TYPE8]] = distinct !DICompositeType(tag: DW_TAG_union_type, name: "w"
 
 // Check that debug info is not emitted for the typedef, struct, enum, and
diff --git a/clang/test/DebugInfo/Generic/unused-types.cpp b/clang/test/DebugInfo/Generic/unused-types.cpp
index 023cac159faa4..5b01c6dbb3941 100644
--- a/clang/test/DebugInfo/Generic/unused-types.cpp
+++ b/clang/test/DebugInfo/Generic/unused-types.cpp
@@ -13,12 +13,14 @@ void quux() {
 // CHECK: !DICompileUnit{{.+}}retainedTypes: [[RETTYPES:![0-9]+]]
 // CHECK: [[TYPE0:![0-9]+]] = !DICompositeType(tag: DW_TAG_enumeration_type, name: "baz"
 // CHECK: [[TYPE1:![0-9]+]] = !DIEnumerator(name: "BAZ"
-// CHECK: [[TYPE2:![0-9]+]] = !DICompositeType(tag: DW_TAG_enumeration_type, name: "z"
-// CHECK: [[TYPE3:![0-9]+]] = !DIEnumerator(name: "Z"
-// CHECK: [[RETTYPES]] = !{[[TYPE4:![0-9]+]], [[TYPE5:![0-9]+]], [[TYPE0]], {{![0-9]+}}, [[TYPE6:![0-9]+]], [[TYPE2]]}
-// CHECK: [[TYPE4]] = !DIDerivedType(tag: DW_TAG_typedef, name: "foo"
-// CHECK: [[TYPE5]] = distinct !DICompositeType(tag: DW_TAG_class_type, name: "bar"
-// CHECK: [[TYPE6]] = distinct !DICompositeType(tag: DW_TAG_class_type, name: "y"
+// CHECK: [[RETTYPES]] = !{[[TYPE2:![0-9]+]], [[TYPE3:![0-9]+]], [[TYPE0]], {{![0-9]+}}}
+// CHECK: [[TYPE2]] = !DIDerivedType(tag: DW_TAG_typedef, name: "foo"
+// CHECK: [[TYPE3]] = distinct !DICompositeType(tag: DW_TAG_class_type, name: "bar"
+// CHECK: [[SP:![0-9]+]] = distinct !DISubprogram(name: "quux", {{.*}}, retainedNodes: [[SPRETNODES:![0-9]+]]
+// CHECK: [[SPRETNODES]] = !{[[TYPE4:![0-9]+]], [[TYPE5:![0-9]+]]}
+// CHECK: [[TYPE4]] = distinct !DICompositeType(tag: DW_TAG_class_type, name: "y", scope: [[SP]]
+// CHECK: [[TYPE5]] = !DICompositeType(tag: DW_TAG_enumeration_type, name: "z", scope: [[SP]]
+// CHECK: [[TYPE6:![0-9]+]] = !DIEnumerator(name: "Z"
 
 // NODBG-NOT: !DI{{CompositeType|Enumerator|DerivedType}}
 
diff --git a/llvm/include/llvm/AsmParser/LLParser.h b/llvm/include/llvm/AsmParser/LLParser.h
index 9eb31d7e0a451..ae568f8ead039 100644
--- a/llvm/include/llvm/AsmParser/LLParser.h
+++ b/llvm/include/llvm/AsmParser/LLParser.h
@@ -181,6 +181,10 @@ namespace llvm {
     /// Keeps track of so...
[truncated]

@llvmbot
Copy link
Copy Markdown
Member

llvmbot commented Oct 24, 2025

@llvm/pr-subscribers-backend-arm

Author: Vladislav Dzhidzhoev (dzhidzhoev)

Changes

This is an attempt to merge https://reviews.llvm.org/D144006 with LTO fix.

The last merge attempt was #75385.
The issue with it was investigated in #75385 (comment).
If the same (in the sense of ODR identifier/ODR uniquing rules) local type is present in two modules, and these modules are linked together, the type gets uniqued. A DIType, that happens to be loaded first, survives linking, and the references on other types with the same ODR identifier from the modules loaded later are replaced with the references on the DIType loaded first. Since defintion subprograms, in scope of which these types are located, are not deduplicated, the linker output may contain multiple DISubprogram's having the same (uniqued) type in their retainedNodes lists.
Further compilation of such modules causes crashes.

To tackle that,

  • previous solution to handle LTO linking with local types in retainedNodes is removed (cloneLocalTypes() function),
  • for each loaded distinct (definition) DISubprogram, its retainedNodes list is scanned after loading, and DITypes with a scope of another subprogram are removed. If something from a Function corresponding to the DISubprogram references uniqued type, we rely on cross-CU links.

With this approach, clang builds without crashes in FullLTO (which is not the case for #75385).

Additionally:

Commit #75385 and the new changes are separate commits to simplify review process.


Patch is 112.61 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/165032.diff

40 Files Affected:

  • (modified) clang/test/Analysis/analyzeOneFunction.cpp (+2-2)
  • (modified) clang/test/DebugInfo/CXX/access.cpp (+1-1)
  • (modified) clang/test/DebugInfo/CXX/anon-union-vars.cpp (+6-6)
  • (modified) clang/test/DebugInfo/CXX/codeview-unnamed.cpp (+62-48)
  • (modified) clang/test/DebugInfo/CXX/gline-tables-only-codeview.cpp (+2-2)
  • (modified) clang/test/DebugInfo/CXX/lambda-capture-packs.cpp (+7-8)
  • (modified) clang/test/DebugInfo/CXX/lambda-this.cpp (+2-2)
  • (modified) clang/test/DebugInfo/Generic/codeview-unnamed.c (+8-8)
  • (modified) clang/test/DebugInfo/Generic/unused-types.c (+9-7)
  • (modified) clang/test/DebugInfo/Generic/unused-types.cpp (+8-6)
  • (modified) llvm/include/llvm/AsmParser/LLParser.h (+4)
  • (modified) llvm/include/llvm/IR/DIBuilder.h (+3-3)
  • (modified) llvm/include/llvm/IR/DebugInfoMetadata.h (+15)
  • (modified) llvm/lib/AsmParser/LLParser.cpp (+7)
  • (modified) llvm/lib/Bitcode/Reader/MetadataLoader.cpp (+65-33)
  • (modified) llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.cpp (+42-22)
  • (modified) llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.h (+8-8)
  • (modified) llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp (+10-3)
  • (modified) llvm/lib/IR/DIBuilder.cpp (+48-18)
  • (modified) llvm/lib/IR/DebugInfo.cpp (+2)
  • (modified) llvm/lib/IR/DebugInfoMetadata.cpp (+39)
  • (modified) llvm/lib/IR/Verifier.cpp (+13-3)
  • (modified) llvm/lib/Transforms/Utils/CloneFunction.cpp (+15-2)
  • (added) llvm/test/Bitcode/local-type-scope.ll (+48)
  • (modified) llvm/test/Bitcode/upgrade-cu-locals.ll (+41-27)
  • (modified) llvm/test/Bitcode/upgrade-cu-locals.ll.bc ()
  • (modified) llvm/test/CodeGen/ARM/call-graph-section-assembly.ll (+2-2)
  • (modified) llvm/test/CodeGen/X86/call-graph-section-assembly.ll (+2-2)
  • (added) llvm/test/DebugInfo/Generic/inlined-local-type.ll (+128)
  • (added) llvm/test/DebugInfo/Generic/lexical-block-retained-types.ll (+55)
  • (added) llvm/test/DebugInfo/Generic/lexical-block-types.ll (+425)
  • (modified) llvm/test/DebugInfo/Generic/verifier-invalid-disubprogram.ll (+1-1)
  • (added) llvm/test/DebugInfo/Inputs/cleanup-retained-nodes.ll (+17)
  • (added) llvm/test/DebugInfo/X86/cleanup-retained-nodes.ll (+46)
  • (added) llvm/test/DebugInfo/X86/llparser-cleanup-retained-nodes.ll (+37)
  • (added) llvm/test/DebugInfo/X86/local-type-as-template-parameter.ll (+161)
  • (renamed) llvm/test/DebugInfo/X86/split-dwarf-local-import.ll (-1)
  • (renamed) llvm/test/DebugInfo/X86/split-dwarf-local-import2.ll (-1)
  • (renamed) llvm/test/DebugInfo/X86/split-dwarf-local-import3.ll ()
  • (modified) llvm/unittests/Transforms/Utils/CloningTest.cpp (+105)
diff --git a/clang/test/Analysis/analyzeOneFunction.cpp b/clang/test/Analysis/analyzeOneFunction.cpp
index 3a362dfd9a08c..e4c96eb35f06b 100644
--- a/clang/test/Analysis/analyzeOneFunction.cpp
+++ b/clang/test/Analysis/analyzeOneFunction.cpp
@@ -5,9 +5,9 @@
 // RUN:   -analyze-function="c:@S@Window@F@overloaded#I#"
 
 // RUN: %clang_extdef_map %s | FileCheck %s
-// CHECK:      27:c:@S@Window@F@overloaded#I#
+// CHECK:      27:c:@S@Window@F@overloaded#d#
 // CHECK-NEXT: 27:c:@S@Window@F@overloaded#C#
-// CHECK-NEXT: 27:c:@S@Window@F@overloaded#d#
+// CHECK-NEXT: 27:c:@S@Window@F@overloaded#I#
 
 void clang_analyzer_warnIfReached();
 
diff --git a/clang/test/DebugInfo/CXX/access.cpp b/clang/test/DebugInfo/CXX/access.cpp
index 9f2c044843d0f..7c0bf8ccb0384 100644
--- a/clang/test/DebugInfo/CXX/access.cpp
+++ b/clang/test/DebugInfo/CXX/access.cpp
@@ -18,9 +18,9 @@ class B : public A {
   static int public_static;
 
 protected:
+  // CHECK-DAG: !DIDerivedType(tag: DW_TAG_typedef, name: "prot_using",{{.*}} line: [[@LINE+3]],{{.*}} flags: DIFlagProtected)
   // CHECK-DAG: !DIDerivedType(tag: DW_TAG_typedef, name: "prot_typedef",{{.*}} line: [[@LINE+1]],{{.*}} flags: DIFlagProtected)
   typedef int prot_typedef;
-  // CHECK-DAG: !DIDerivedType(tag: DW_TAG_typedef, name: "prot_using",{{.*}} line: [[@LINE+1]],{{.*}} flags: DIFlagProtected)
   using prot_using = prot_typedef;
   prot_using prot_member;
 
diff --git a/clang/test/DebugInfo/CXX/anon-union-vars.cpp b/clang/test/DebugInfo/CXX/anon-union-vars.cpp
index 3aca4e199ab8d..27e6c6850e013 100644
--- a/clang/test/DebugInfo/CXX/anon-union-vars.cpp
+++ b/clang/test/DebugInfo/CXX/anon-union-vars.cpp
@@ -51,13 +51,13 @@ void instantiate(int x) {
 // CHECK: !DIGlobalVariable(name: "b",{{.*}} file: [[FILE]], line: 6,{{.*}} isLocal: true, isDefinition: true
 // CHECK: !DIGlobalVariable(name: "result", {{.*}} isLocal: false, isDefinition: true
 // CHECK: !DIGlobalVariable(name: "value", {{.*}} isLocal: false, isDefinition: true
-// CHECK: !DILocalVariable(name: "i", {{.*}}, flags: DIFlagArtificial
-// CHECK: !DILocalVariable(name: "c", {{.*}}, flags: DIFlagArtificial
-// CHECK: !DILocalVariable(
-// CHECK-NOT: name:
-// CHECK: type: ![[UNION:[0-9]+]]
-// CHECK: ![[UNION]] = distinct !DICompositeType(tag: DW_TAG_union_type,
+// CHECK: ![[UNION:[0-9]+]] = distinct !DICompositeType(tag: DW_TAG_union_type,
 // CHECK-NOT: name:
 // CHECK: elements
 // CHECK: !DIDerivedType(tag: DW_TAG_member, name: "i", scope: ![[UNION]],
 // CHECK: !DIDerivedType(tag: DW_TAG_member, name: "c", scope: ![[UNION]],
+// CHECK: !DILocalVariable(name: "i", {{.*}}, flags: DIFlagArtificial
+// CHECK: !DILocalVariable(name: "c", {{.*}}, flags: DIFlagArtificial
+// CHECK: !DILocalVariable(
+// CHECK-NOT: name:
+// CHECK: type: ![[UNION]]
diff --git a/clang/test/DebugInfo/CXX/codeview-unnamed.cpp b/clang/test/DebugInfo/CXX/codeview-unnamed.cpp
index 30815bda020ea..b625a80313c38 100644
--- a/clang/test/DebugInfo/CXX/codeview-unnamed.cpp
+++ b/clang/test/DebugInfo/CXX/codeview-unnamed.cpp
@@ -4,6 +4,60 @@
 
 int main(int argc, char* argv[], char* arge[]) {
   //
+  // LINUX:      [[TYPE_OF_ONE:![0-9]+]] = distinct !DICompositeType(
+  // LINUX-SAME:     tag: DW_TAG_structure_type
+  // LINUX-NOT:      name:
+  // LINUX-NOT:      identifier:
+  // LINUX-SAME:     )
+  //
+  // MSVC:       [[TYPE_OF_ONE:![0-9]+]] = distinct !DICompositeType
+  // MSVC-SAME:      tag: DW_TAG_structure_type
+  // MSVC-SAME:      name: "<unnamed-type-one>"
+  // MSVC-SAME:      identifier: ".?AU<unnamed-type-one>@?1??main@@9@"
+  // MSVC-SAME:      )
+
+
+  //
+  // LINUX:      [[TYPE_OF_TWO:![0-9]+]] = distinct !DICompositeType(
+  // LINUX-SAME:     tag: DW_TAG_structure_type
+  // LINUX-NOT:      name:
+  // LINUX-NOT:      identifier:
+  // LINUX-SAME:     )
+  //
+  // MSVC:       [[TYPE_OF_TWO:![0-9]+]] = distinct !DICompositeType
+  // MSVC-SAME:      tag: DW_TAG_structure_type
+  // MSVC-SAME:      name: "<unnamed-type-two>"
+  // MSVC-SAME:      identifier: ".?AU<unnamed-type-two>@?2??main@@9@"
+  // MSVC-SAME:      )
+
+
+  //
+  // LINUX:      [[TYPE_OF_THREE:![0-9]+]] = distinct !DICompositeType(
+  // LINUX-SAME:     tag: DW_TAG_structure_type
+  // LINUX-SAME:     name: "named"
+  // LINUX-NOT:      identifier:
+  // LINUX-SAME:     )
+  //
+  // MSVC:       [[TYPE_OF_THREE:![0-9]+]] = distinct !DICompositeType
+  // MSVC-SAME:      tag: DW_TAG_structure_type
+  // MSVC-SAME:      name: "named"
+  // MSVC-SAME:      identifier: ".?AUnamed@?1??main@@9@"
+  // MSVC-SAME:      )
+
+  //
+  // LINUX:      [[TYPE_OF_FOUR:![0-9]+]] = distinct !DICompositeType(
+  // LINUX-SAME:     tag: DW_TAG_class_type
+  // LINUX-NOT:      name:
+  // LINUX-NOT:      identifier:
+  // LINUX-SAME:     )
+  //
+  // MSVC:       [[TYPE_OF_FOUR:![0-9]+]] = distinct !DICompositeType
+  // MSVC-SAME:      tag: DW_TAG_class_type
+  // MSVC-SAME:      name: "<lambda_0>"
+  // MSVC-SAME:      identifier: ".?AV<lambda_0>@?0??main@@9@"
+  // MSVC-SAME:      )
+
+
   // In CodeView, the LF_MFUNCTION entry for "bar()" refers to the forward
   // reference of the unnamed struct. Visual Studio requires a unique
   // identifier to match the LF_STRUCTURE forward reference to the definition.
@@ -11,21 +65,11 @@ int main(int argc, char* argv[], char* arge[]) {
   struct { void bar() {} } one;
   //
   // LINUX:      !{{[0-9]+}} = !DILocalVariable(name: "one"
-  // LINUX-SAME:     type: [[TYPE_OF_ONE:![0-9]+]]
-  // LINUX-SAME:     )
-  // LINUX:      [[TYPE_OF_ONE]] = distinct !DICompositeType(
-  // LINUX-SAME:     tag: DW_TAG_structure_type
-  // LINUX-NOT:      name:
-  // LINUX-NOT:      identifier:
+  // LINUX-SAME:     type: [[TYPE_OF_ONE]]
   // LINUX-SAME:     )
   //
   // MSVC:       !{{[0-9]+}} = !DILocalVariable(name: "one"
-  // MSVC-SAME:      type: [[TYPE_OF_ONE:![0-9]+]]
-  // MSVC-SAME:      )
-  // MSVC:       [[TYPE_OF_ONE]] = distinct !DICompositeType
-  // MSVC-SAME:      tag: DW_TAG_structure_type
-  // MSVC-SAME:      name: "<unnamed-type-one>"
-  // MSVC-SAME:      identifier: ".?AU<unnamed-type-one>@?1??main@@9@"
+  // MSVC-SAME:      type: [[TYPE_OF_ONE]]
   // MSVC-SAME:      )
 
 
@@ -37,21 +81,11 @@ int main(int argc, char* argv[], char* arge[]) {
   int decltype(two)::*ptr2unnamed = &decltype(two)::bar;
   //
   // LINUX:      !{{[0-9]+}} = !DILocalVariable(name: "two"
-  // LINUX-SAME:     type: [[TYPE_OF_TWO:![0-9]+]]
-  // LINUX-SAME:     )
-  // LINUX:      [[TYPE_OF_TWO]] = distinct !DICompositeType(
-  // LINUX-SAME:     tag: DW_TAG_structure_type
-  // LINUX-NOT:      name:
-  // LINUX-NOT:      identifier:
+  // LINUX-SAME:     type: [[TYPE_OF_TWO]]
   // LINUX-SAME:     )
   //
   // MSVC:       !{{[0-9]+}} = !DILocalVariable(name: "two"
-  // MSVC-SAME:      type: [[TYPE_OF_TWO:![0-9]+]]
-  // MSVC-SAME:      )
-  // MSVC:       [[TYPE_OF_TWO]] = distinct !DICompositeType
-  // MSVC-SAME:      tag: DW_TAG_structure_type
-  // MSVC-SAME:      name: "<unnamed-type-two>"
-  // MSVC-SAME:      identifier: ".?AU<unnamed-type-two>@?2??main@@9@"
+  // MSVC-SAME:      type: [[TYPE_OF_TWO]]
   // MSVC-SAME:      )
 
 
@@ -62,21 +96,11 @@ int main(int argc, char* argv[], char* arge[]) {
   struct named { int bar; int named::* p2mem; } three = { 42, &named::bar };
   //
   // LINUX:      !{{[0-9]+}} = !DILocalVariable(name: "three"
-  // LINUX-SAME:     type: [[TYPE_OF_THREE:![0-9]+]]
-  // LINUX-SAME:     )
-  // LINUX:      [[TYPE_OF_THREE]] = distinct !DICompositeType(
-  // LINUX-SAME:     tag: DW_TAG_structure_type
-  // LINUX-SAME:     name: "named"
-  // LINUX-NOT:      identifier:
+  // LINUX-SAME:     type: [[TYPE_OF_THREE]]
   // LINUX-SAME:     )
   //
   // MSVC:       !{{[0-9]+}} = !DILocalVariable(name: "three"
-  // MSVC-SAME:      type: [[TYPE_OF_THREE:![0-9]+]]
-  // MSVC-SAME:      )
-  // MSVC:       [[TYPE_OF_THREE]] = distinct !DICompositeType
-  // MSVC-SAME:      tag: DW_TAG_structure_type
-  // MSVC-SAME:      name: "named"
-  // MSVC-SAME:      identifier: ".?AUnamed@?1??main@@9@"
+  // MSVC-SAME:      type: [[TYPE_OF_THREE]]
   // MSVC-SAME:      )
 
 
@@ -88,21 +112,11 @@ int main(int argc, char* argv[], char* arge[]) {
   auto four = [argc](int i) -> int { return argc == i ? 1 : 0; };
   //
   // LINUX:      !{{[0-9]+}} = !DILocalVariable(name: "four"
-  // LINUX-SAME:     type: [[TYPE_OF_FOUR:![0-9]+]]
-  // LINUX-SAME:     )
-  // LINUX:      [[TYPE_OF_FOUR]] = distinct !DICompositeType(
-  // LINUX-SAME:     tag: DW_TAG_class_type
-  // LINUX-NOT:      name:
-  // LINUX-NOT:      identifier:
+  // LINUX-SAME:     type: [[TYPE_OF_FOUR]]
   // LINUX-SAME:     )
   //
   // MSVC:       !{{[0-9]+}} = !DILocalVariable(name: "four"
-  // MSVC-SAME:      type: [[TYPE_OF_FOUR:![0-9]+]]
-  // MSVC-SAME:      )
-  // MSVC:       [[TYPE_OF_FOUR]] = distinct !DICompositeType
-  // MSVC-SAME:      tag: DW_TAG_class_type
-  // MSVC-SAME:      name: "<lambda_0>"
-  // MSVC-SAME:      identifier: ".?AV<lambda_0>@?0??main@@9@"
+  // MSVC-SAME:      type: [[TYPE_OF_FOUR]]
   // MSVC-SAME:      )
 
   return 0;
diff --git a/clang/test/DebugInfo/CXX/gline-tables-only-codeview.cpp b/clang/test/DebugInfo/CXX/gline-tables-only-codeview.cpp
index 6b9c9a243decd..122e4aa62ea7d 100644
--- a/clang/test/DebugInfo/CXX/gline-tables-only-codeview.cpp
+++ b/clang/test/DebugInfo/CXX/gline-tables-only-codeview.cpp
@@ -51,9 +51,9 @@ void test() {
   // CHECK-SAME:                              name: "<lambda_2_1>",
   c.lambda_params();
 
-  // CHECK: !DISubprogram(name: "operator()", scope: ![[LAMBDA1:[0-9]+]],
-  // CHECK: ![[LAMBDA1]] = !DICompositeType(tag: DW_TAG_class_type,
+  // CHECK: ![[LAMBDA1:[0-9]+]] = !DICompositeType(tag: DW_TAG_class_type,
   // CHECK-SAME:                            name: "<lambda_1>",
   // CHECK-SAME:                            flags: DIFlagFwdDecl
+  // CHECK: !DISubprogram(name: "operator()", scope: ![[LAMBDA1]],
   c.lambda2();
 }
diff --git a/clang/test/DebugInfo/CXX/lambda-capture-packs.cpp b/clang/test/DebugInfo/CXX/lambda-capture-packs.cpp
index 021b85d4ce3a4..609a71c6ec015 100644
--- a/clang/test/DebugInfo/CXX/lambda-capture-packs.cpp
+++ b/clang/test/DebugInfo/CXX/lambda-capture-packs.cpp
@@ -2,14 +2,6 @@
 // RUN:   -debug-info-kind=standalone -std=c++26 %s -o - | FileCheck %s
 
 
-// CHECK: ![[PACK1:[0-9]+]] = distinct !DISubprogram(name: "capture_pack<int>"
-// CHECK: ![[PACK2:[0-9]+]] = distinct !DISubprogram(name: "capture_pack<int, int>"
-// CHECK: ![[PACK3:[0-9]+]] = distinct !DISubprogram(name: "capture_pack_and_locals<int>"
-// CHECK: ![[PACK4:[0-9]+]] = distinct !DISubprogram(name: "capture_pack_and_locals<int, int>"
-// CHECK: ![[PACK5:[0-9]+]] = distinct !DISubprogram(name: "capture_pack_and_this<int>"
-// CHECK: ![[PACK6:[0-9]+]] = distinct !DISubprogram(name: "capture_pack_and_this<int, int>"
-// CHECK: ![[PACK7:[0-9]+]] = distinct !DISubprogram(name: "capture_binding_and_param_pack<int, int>"
-
 template<typename... Args>
 auto capture_pack(Args... args) {
   return [args..., ...params = args] {
@@ -17,6 +9,7 @@ auto capture_pack(Args... args) {
   }();
 }
 
+// CHECK: ![[PACK1:[0-9]+]] = distinct !DISubprogram(name: "capture_pack<int>"
 // CHECK:      distinct !DICompositeType(tag: DW_TAG_class_type, scope: ![[PACK1]]
 // CHECK-SAME:                           elements: ![[PACK1_ELEMS:[0-9]+]]
 // CHECK-DAG:  ![[PACK1_ELEMS]] = !{![[PACK1_ARGS:[0-9]+]], ![[PACK1_PARAMS:[0-9]+]]}
@@ -24,6 +17,7 @@ auto capture_pack(Args... args) {
 // CHECK-DAG:  ![[PACK1_PARAMS]] = !DIDerivedType(tag: DW_TAG_member, name: "params"
 // CHECK-NOT:  DW_TAG_member
 
+// CHECK: ![[PACK2:[0-9]+]] = distinct !DISubprogram(name: "capture_pack<int, int>"
 // CHECK:      distinct !DICompositeType(tag: DW_TAG_class_type, scope: ![[PACK2]]
 // CHECK-SAME:                           elements: ![[PACK2_ELEMS:[0-9]+]]
 // CHECK:      ![[PACK2_ELEMS]] = !{![[PACK2_ARGS:[0-9]+]]
@@ -42,6 +36,7 @@ auto capture_pack_and_locals(int x, Args... args) {
   }();
 }
 
+// CHECK: ![[PACK3:[0-9]+]] = distinct !DISubprogram(name: "capture_pack_and_locals<int>"
 // CHECK:      distinct !DICompositeType(tag: DW_TAG_class_type, scope: ![[PACK3]]
 // CHECK-SAME:                           elements: ![[PACK3_ELEMS:[0-9]+]]
 // CHECK:      ![[PACK3_ELEMS]] = !{![[PACK3_ARGS:[0-9]+]]
@@ -55,6 +50,7 @@ auto capture_pack_and_locals(int x, Args... args) {
 // CHECK-DAG:  ![[PACK3_W]] = !DIDerivedType(tag: DW_TAG_member, name: "w"
 // CHECK-NOT:  DW_TAG_member
 
+// CHECK: ![[PACK4:[0-9]+]] = distinct !DISubprogram(name: "capture_pack_and_locals<int, int>"
 // CHECK:      distinct !DICompositeType(tag: DW_TAG_class_type, scope: ![[PACK4]]
 // CHECK-SAME:                           elements: ![[PACK4_ELEMS:[0-9]+]]
 // CHECK:      ![[PACK4_ELEMS]] = !{![[PACK4_ARGS:[0-9]+]]
@@ -90,6 +86,7 @@ struct Foo {
   int w = 10;
 } f;
 
+// CHECK: ![[PACK5:[0-9]+]] = distinct !DISubprogram(name: "capture_pack_and_this<int>"
 // CHECK:      distinct !DICompositeType(tag: DW_TAG_class_type, scope: ![[PACK5]]
 // CHECK-SAME:                           elements: ![[PACK5a_ELEMS:[0-9]+]]
 // CHECK:      ![[PACK5a_ELEMS]] = !{![[PACK5a_THIS:[0-9]+]]
@@ -120,6 +117,7 @@ struct Foo {
 // CHECK-DAG:  ![[PACK5c_THIS]] = !DIDerivedType(tag: DW_TAG_member, name: "this"
 // CHECK-NOT:  DW_TAG_member
 
+// CHECK: ![[PACK6:[0-9]+]] = distinct !DISubprogram(name: "capture_pack_and_this<int, int>"
 // CHECK:      distinct !DICompositeType(tag: DW_TAG_class_type, scope: ![[PACK6]]
 // CHECK-SAME:                           elements: ![[PACK6a_ELEMS:[0-9]+]]
 // CHECK:      ![[PACK6a_ELEMS]] = !{![[PACK6a_THIS:[0-9]+]]
@@ -168,6 +166,7 @@ auto capture_binding_and_param_pack(Args... args) {
   }();
 }
 
+// CHECK: ![[PACK7:[0-9]+]] = distinct !DISubprogram(name: "capture_binding_and_param_pack<int, int>"
 // CHECK: distinct !DICompositeType(tag: DW_TAG_structure_type, name: "C"
 // CHECK:      distinct !DICompositeType(tag: DW_TAG_class_type, scope: ![[PACK7]]
 // CHECK-SAME:                           elements: ![[PACK7_ELEMS:[0-9]+]]
diff --git a/clang/test/DebugInfo/CXX/lambda-this.cpp b/clang/test/DebugInfo/CXX/lambda-this.cpp
index 019d09c48f858..1df210953ac2e 100644
--- a/clang/test/DebugInfo/CXX/lambda-this.cpp
+++ b/clang/test/DebugInfo/CXX/lambda-this.cpp
@@ -13,10 +13,10 @@ void D::d(int x) {
 }
 
 // CHECK: ![[D:[0-9]+]] = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "D",
-// CHECK: ![[POINTER:.*]] = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: ![[D]], size: 64)
 // CHECK: !DIDerivedType(tag: DW_TAG_member, name: "this",
 // CHECK-SAME:           line: 11
-// CHECK-SAME:           baseType: ![[POINTER]]
+// CHECK-SAME:           baseType: ![[POINTER:[0-9]+]]
 // CHECK-SAME:           size: 64
 // CHECK-NOT:            offset: 0
 // CHECK-SAME:           ){{$}}
+// CHECK: ![[POINTER]] = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: ![[D]], size: 64)
diff --git a/clang/test/DebugInfo/Generic/codeview-unnamed.c b/clang/test/DebugInfo/Generic/codeview-unnamed.c
index 0df6e1a0419bb..7b88f53a92e42 100644
--- a/clang/test/DebugInfo/Generic/codeview-unnamed.c
+++ b/clang/test/DebugInfo/Generic/codeview-unnamed.c
@@ -8,23 +8,23 @@ int main(int argc, char* argv[], char* arge[]) {
   //
   struct { int bar; } one = {42};
   //
-  // LINUX:      !{{[0-9]+}} = !DILocalVariable(name: "one"
-  // LINUX-SAME:     type: [[TYPE_OF_ONE:![0-9]+]]
-  // LINUX-SAME:     )
-  // LINUX:      [[TYPE_OF_ONE]] = distinct !DICompositeType(
+  // LINUX:      [[TYPE_OF_ONE:![0-9]+]] = distinct !DICompositeType(
   // LINUX-SAME:     tag: DW_TAG_structure_type
   // LINUX-NOT:      name:
   // LINUX-NOT:      identifier:
   // LINUX-SAME:     )
+  // LINUX:      !{{[0-9]+}} = !DILocalVariable(name: "one"
+  // LINUX-SAME:     type: [[TYPE_OF_ONE]]
+  // LINUX-SAME:     )
   //
-  // MSVC:       !{{[0-9]+}} = !DILocalVariable(name: "one"
-  // MSVC-SAME:      type: [[TYPE_OF_ONE:![0-9]+]]
-  // MSVC-SAME:      )
-  // MSVC:       [[TYPE_OF_ONE]] = distinct !DICompositeType
+  // MSVC:       [[TYPE_OF_ONE:![0-9]+]] = distinct !DICompositeType
   // MSVC-SAME:      tag: DW_TAG_structure_type
   // MSVC-NOT:       name:
   // MSVC-NOT:       identifier:
   // MSVC-SAME:      )
+  // MSVC:       !{{[0-9]+}} = !DILocalVariable(name: "one"
+  // MSVC-SAME:      type: [[TYPE_OF_ONE]]
+  // MSVC-SAME:      )
 
   return 0;
 }
diff --git a/clang/test/DebugInfo/Generic/unused-types.c b/clang/test/DebugInfo/Generic/unused-types.c
index 3e9f7b07658e3..31d608d92a06b 100644
--- a/clang/test/DebugInfo/Generic/unused-types.c
+++ b/clang/test/DebugInfo/Generic/unused-types.c
@@ -18,13 +18,15 @@ void quux(void) {
 // CHECK: !DICompileUnit{{.+}}retainedTypes: [[RETTYPES:![0-9]+]]
 // CHECK: [[TYPE0:![0-9]+]] = !DICompositeType(tag: DW_TAG_enumeration_type, name: "bar"
 // CHECK: [[TYPE1:![0-9]+]] = !DIEnumerator(name: "BAR"
-// CHECK: [[TYPE2:![0-9]+]] = !DICompositeType(tag: DW_TAG_enumeration_type, name: "z"
-// CHECK: [[TYPE3:![0-9]+]] = !DIEnumerator(name: "Z"
-// CHECK: [[RETTYPES]] = !{[[TYPE4:![0-9]+]], [[TYPE5:![0-9]+]], [[TYPE0]], [[TYPE6:![0-9]+]], {{![0-9]+}}, [[TYPE7:![0-9]+]], [[TYPE2]], [[TYPE8:![0-9]+]]}
-// CHECK: [[TYPE4]] = !DIDerivedType(tag: DW_TAG_typedef, name: "my_int"
-// CHECK: [[TYPE5]] = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "foo"
-// CHECK: [[TYPE6]] = distinct !DICompositeType(tag: DW_TAG_union_type, name: "baz"
-// CHECK: [[TYPE7]] = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "y"
+// CHECK: [[RETTYPES]] = !{[[TYPE2:![0-9]+]], [[TYPE3:![0-9]+]], [[TYPE0]], [[TYPE4:![0-9]+]], {{![0-9]+}}}
+// CHECK: [[TYPE2]] = !DIDerivedType(tag: DW_TAG_typedef, name: "my_int"
+// CHECK: [[TYPE3]] = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "foo"
+// CHECK: [[TYPE4]] = distinct !DICompositeType(tag: DW_TAG_union_type, name: "baz"
+// CHECK: [[SP:![0-9]+]] = distinct !DISubprogram(name: "quux", {{.*}}, retainedNodes: [[SPRETNODES:![0-9]+]]
+// CHECK: [[SPRETNODES]] = !{[[TYPE5:![0-9]+]], [[TYPE6:![0-9]+]], [[TYPE8:![0-9]+]]}
+// CHECK: [[TYPE5]] = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "y"
+// CHECK: [[TYPE6]] = !DICompositeType(tag: DW_TAG_enumeration_type, name: "z"
+// CHECK: [[TYPE7:![0-9]+]] = !DIEnumerator(name: "Z"
 // CHECK: [[TYPE8]] = distinct !DICompositeType(tag: DW_TAG_union_type, name: "w"
 
 // Check that debug info is not emitted for the typedef, struct, enum, and
diff --git a/clang/test/DebugInfo/Generic/unused-types.cpp b/clang/test/DebugInfo/Generic/unused-types.cpp
index 023cac159faa4..5b01c6dbb3941 100644
--- a/clang/test/DebugInfo/Generic/unused-types.cpp
+++ b/clang/test/DebugInfo/Generic/unused-types.cpp
@@ -13,12 +13,14 @@ void quux() {
 // CHECK: !DICompileUnit{{.+}}retainedTypes: [[RETTYPES:![0-9]+]]
 // CHECK: [[TYPE0:![0-9]+]] = !DICompositeType(tag: DW_TAG_enumeration_type, name: "baz"
 // CHECK: [[TYPE1:![0-9]+]] = !DIEnumerator(name: "BAZ"
-// CHECK: [[TYPE2:![0-9]+]] = !DICompositeType(tag: DW_TAG_enumeration_type, name: "z"
-// CHECK: [[TYPE3:![0-9]+]] = !DIEnumerator(name: "Z"
-// CHECK: [[RETTYPES]] = !{[[TYPE4:![0-9]+]], [[TYPE5:![0-9]+]], [[TYPE0]], {{![0-9]+}}, [[TYPE6:![0-9]+]], [[TYPE2]]}
-// CHECK: [[TYPE4]] = !DIDerivedType(tag: DW_TAG_typedef, name: "foo"
-// CHECK: [[TYPE5]] = distinct !DICompositeType(tag: DW_TAG_class_type, name: "bar"
-// CHECK: [[TYPE6]] = distinct !DICompositeType(tag: DW_TAG_class_type, name: "y"
+// CHECK: [[RETTYPES]] = !{[[TYPE2:![0-9]+]], [[TYPE3:![0-9]+]], [[TYPE0]], {{![0-9]+}}}
+// CHECK: [[TYPE2]] = !DIDerivedType(tag: DW_TAG_typedef, name: "foo"
+// CHECK: [[TYPE3]] = distinct !DICompositeType(tag: DW_TAG_class_type, name: "bar"
+// CHECK: [[SP:![0-9]+]] = distinct !DISubprogram(name: "quux", {{.*}}, retainedNodes: [[SPRETNODES:![0-9]+]]
+// CHECK: [[SPRETNODES]] = !{[[TYPE4:![0-9]+]], [[TYPE5:![0-9]+]]}
+// CHECK: [[TYPE4]] = distinct !DICompositeType(tag: DW_TAG_class_type, name: "y", scope: [[SP]]
+// CHECK: [[TYPE5]] = !DICompositeType(tag: DW_TAG_enumeration_type, name: "z", scope: [[SP]]
+// CHECK: [[TYPE6:![0-9]+]] = !DIEnumerator(name: "Z"
 
 // NODBG-NOT: !DI{{CompositeType|Enumerator|DerivedType}}
 
diff --git a/llvm/include/llvm/AsmParser/LLParser.h b/llvm/include/llvm/AsmParser/LLParser.h
index 9eb31d7e0a451..ae568f8ead039 100644
--- a/llvm/include/llvm/AsmParser/LLParser.h
+++ b/llvm/include/llvm/AsmParser/LLParser.h
@@ -181,6 +181,10 @@ namespace llvm {
     /// Keeps track of so...
[truncated]

@github-actions
Copy link
Copy Markdown

github-actions bot commented Oct 24, 2025

✅ With the latest revision this PR passed the undef deprecator.

@dzhidzhoev
Copy link
Copy Markdown
Member Author

⚠️ undef deprecator found issues in your code. ⚠️

You can test this locally with the following command:
The following files introduce new uses of undef:

  • llvm/test/DebugInfo/X86/split-dwarf-local-import.ll

Seems to be a false alarm, triggered due to the file being moved.

@dzhidzhoev dzhidzhoev force-pushed the debuginfo/rfc-krisb/4/base branch 2 times, most recently from bfee696 to e97af0a Compare October 27, 2025 12:18
@dzhidzhoev
Copy link
Copy Markdown
Member Author

Fixed failing checks.

@dzhidzhoev
Copy link
Copy Markdown
Member Author

Gentle ping

dzhidzhoev added a commit to dzhidzhoev/llvm-project that referenced this pull request Nov 6, 2025
These checks ensure that retained nodes of a DISubprogram belong
to the subprogram.

This was helpful to find issues with https://reviews.llvm.org/D144004,
llvm#75385,
llvm#165032.

Also, interface for accessing DISubprogram's retained nodes is slightly
refactored. `DISubprogram::visitRetainedNodes` and
`DISubprogram::forEachRetainedNode` are added to avoid repeating
checks like
```
if (const auto *LV = dyn_cast<DILocalVariable>(N))
  ...
else if (const auto *L = dyn_cast<DILabel>(N))
  ...
else if (const auto *IE = dyn_cast<DIImportedEntity>(N))
  ...
```

Tests with wrongly scoped retained nodes are fixed.
@dzhidzhoev dzhidzhoev force-pushed the debuginfo/rfc-krisb/4/base branch from e97af0a to 99884a4 Compare November 11, 2025 15:27
@dzhidzhoev
Copy link
Copy Markdown
Member Author

Gentle ping

Copy link
Copy Markdown
Member

@jmorse jmorse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some shallow comments; I'm still getting my head around the major differences here and haven't looked at the tests, but see:

  • We're directly un-doing the effect of ODRUniqing by filtering for vulnerable retainedNodes lists on bitcode/IR load,
  • Types that have been ODRUnique'd get cross-scope and possibly cross-CU references to them,

Which seems like a proportionate solution: ORDUniquing is saving memory in this very direct and unstructured way, and we have to work around it. Which sucks, but it's the price of the design decision.

Is there any protection against a part of LLVM duplicating a type and silently getting an ODRUnique'd type back, which then leads to confusion later -- or is that what the createIdentityMDPredicate code is protecting against? I wonder whether this could lead to a situation where creating type metadata is vulnerable to accidents (however if it is, we should protect against it with lots of assertions, instead of trying to design around it).

The potential expansion of the types being cloned due to changing createIdentityMDPredicate might affect compile-time performance; we can look at that in due course though.

Thanks for sticking with this, it's been a rollercoaster, hopefully this will cover all the complications seen to date!

Comment on lines +129 to +130
const_iterator begin() { return MetadataPtrs.begin(); }
const_iterator end() { return MetadataPtrs.end(); }
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make the methods const too?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment on lines +455 to +456
/// retainedNodes of these subprograms should be cleaned up from incorrectly
/// scoped local types.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO to avoid confusing future readers, we need to signal something about the fact that it's LLVM/ORDUniquing that's causing things to be invalid, not the input. Possibly "See the comment on \ref DISubprogram::cleanupRetainedNodes"?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

NewEnums.push_back(Op);

// Find DISubprogram corresponding to each entity.
std::map<DISubprogram *, SmallVector<Metadata *>> SPToEntities;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that std::map is usually discouraged due to allocating for each new element; I think we can get away with this as it's on the autoupgrade path, which isn't going to be fast anyway.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Comment on lines +558 to +560
SetVector<Metadata *> MetadataToRemove;
// Collect imported entities to be moved.
if (CU->getRawImportedEntities())
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you give an overview of the algorithm being used in this function, as I can't piece it together: we've got the collection of metadata-to-remove from the CU, and the NewImports NewEnums collections are the remainder of retainedNodes elements that won't be moved to DISubprograms? If so, I think the term "New" is giving me slight confusion, could we use the term "remaining" or "KeepInCU" or something like that?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've rewritten this function to make it more clear, and to decrease the indentation level.
The algorithm is the following:

  1. For each CU:
    1.1. Collect local metadata nodes from CU's imports: and enums: lists in MetadataToRemove set.
    1.2. Remove metadata nodes of MetadataToRemove set from CU's imports: and enums: lists.
    1.3. Group MetadataToRemove items by their parent subprograms (in SPToEntities map).
    1.4. For each subprogram SP in SPToEntities:
    1.4.1. Append SP's local metadata nodes to SP's retainedNodes: list.

Comment on lines +758 to +759
void resolveLoadedMetadata(PlaceholderQueue &Placeholders,
bool IsModuleLevelDIUpgrade) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I can see this overload is to assist two call-sites that already have a bool? IMO it would be better to sink the selection of DebugInfoUpgradeMode into :MetadataLoaderImpl::parseMetadata rather than rely on this overload, on the principle of "explicit is better than implicit".

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment on lines +775 to +779
// FIXME: We may have a concrete DIE for this scope already created.
// This may happen when we emit local variables for an abstract tree of
// an inlined function: if a local variable has a templated type with
// a function-local type as a template parameter. See PR55680 for details
// (see also llvm/test/DebugInfo/Generic/local-type-as-template-parameter.ll).
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's unclear what's to be fixed here: the concrete DIE isn't supposed to exist already but we accept it temporarily, or we should intend on creating a duplicate, or placing it somewhere else?

Copy link
Copy Markdown
Member Author

@dzhidzhoev dzhidzhoev Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, this piece of code was added in the original PR https://reviews.llvm.org/D144006. I've been trying to understand why @chbessonova added it.

From my analysis, it seems that we will not encounter a situation where a concrete, out-of-line lexical block DIE for Scope already exists at the start of DwarfCompileUnit::getOrCreateLexicalBlockDIE.

It is possible for a concrete DIE for a subprogram to be created in the DwarfUnit before the corresponding abstract DIE exists (as described in the issue #55680). However, I don’t think this ever happens for concrete DIEs of lexical blocks. Concrete lexical scope DIEs are created only during the population of a concrete subprogram, specifically in DwarfCompileUnit::constructSubprogramScopeDIE, which is called from DwarfDebug::endFunctionImpl after all relevant abstract scopes are created.

In other words,
DwarfCompileUnit::getOrCreateContextDIE can create a concrete subprogram DIE.
DwarfCompileUnit::getOrCreateContextDIE never creates concrete lexical scope DIEs.
• Therefore, the code in question that handles already-existing concrete lexical scope DIEs is unnecessary.

I've reverted the change of DwarfCompileUnit::getOrCreateLexicalBlockDIE.

Copy link
Copy Markdown
Collaborator

@dwblaikie dwblaikie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a few thoughts - thanks @jmorse for taking a look at this too! (it's been a long term project and I'm more distant from LLVM over the course of it & getting a bit change-blind/struggling to keep up with the state of things)

Comment on lines +138 to +139
// Some types may be still unresolved. As they can't be cloned, keep
// references to the types from the base subprogram.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"some" feels concerningly vague - could you be more specific?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added specifics.

Comment on lines +140 to +141
// FIXME: As a result, variables of cloned subprogram may refer to local types
// from base subprogram. In such case, type locality information is damaged.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FIXME is a bit unclear about what should be fixed, and explains more about the problem?

Perhaps the explanation should go above, and the FIXME should be more specific about what should be fixed?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Comment on lines +549 to +550
/// Move function-local enums from DICompileUnit's enums
/// to DISubprogram's retainedNodes.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could probably go in a separate standalone patch?

Copy link
Copy Markdown
Member Author

@dzhidzhoev dzhidzhoev Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean separating out the non-functional, refactoring changes to upgradeCULocals?
If separating out upgradeCULocals change as a whole was meant, could you please clarify that? Should we allow local enums in both DISubprogram's retainedNodes: and DICompileUnit's enums: fields?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the functional change of moving function-local enums from the CU to the Subprogram seems like it could be a distinct change? It has observable effects (so it's not NFC) but they can be implemented/tested separately from the rest of this work.

Maybe worth discussing the tradeoffs of that change too (like what happens if that function-local enum is referenced from somewhere else, like a template instantiation but then the original function is optimized away (not sure if we could quite get the stars to align with all those things, but maybe)) - but I feel this has all drawn out far enough so if you just want to go ahead with this, you could separate out that work without it being in a separate patch/review/etc.

Copy link
Copy Markdown
Member Author

@dzhidzhoev dzhidzhoev Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the functional change of moving function-local enums from the CU to the Subprogram seems like it could be a distinct change? It has observable effects (so it's not NFC) but they can be implemented/tested separately from the rest of this work.

IMO we definitely want to keep all local enums either in DICompileUnit's enums: or in DISubprogram's retainedNodes:, to simplify local types handling.
And we want to postpone their emission to DwarfDebug::endModule(), where we know for sure whether a local scope will have an abstract DIE or only a concrete DIE (to get rid of the hack made in #160786, where emission of abstract subprogram DIEs is split in two stages, at the cost of maintaining SP->llvm::Function map).

I assume, initially @chbessonova was trying to avoid complexity by implementing both of the things in one patch.

But I agree that we could achieve them by first allowing local enums being present in both DICompileUnit's enums: and in DISubprogram's retainedNodes:, and then moving the ownership of local enums completely to DISubprogram's retainedNodes: (as a separate patch).

but I feel this has all drawn out far enough so if you just want to go ahead with this, you could separate out that work without it being in a separate patch/review/etc.

I think I will take the approach above if the current merge attempt fails (again :). I can try merging stripped version of this PR, without local enums ownership transfer (if this part will be a culprit).

Maybe worth discussing the tradeoffs of that change too (like what happens if that function-local enum is referenced from somewhere else, like a template instantiation but then the original function is optimized away (not sure if we could quite get the stars to align with all those things, but maybe))

Will it create a problem for DWARF emission? If I understand your concern right, this is covered in llvm/test/DebugInfo/Generic/inlined-local-type.ll.

When it comes to keeping local enums tracked only by DICompileUnit's enums: instead of transfering the ownership to DISubprogram, I think we can achieve the same as what can be done with this PR. However, we will have to keep some mapping between subprograms and their local enums, so as not to skip emitting lexical blocks where only a local enum DIE is present, without DIEs for anything else (see logic in skipLexicalScope lambda in DwarfCompileUnit::createAndAddScopeChildren).

@@ -703,13 +727,41 @@ class MetadataLoader::MetadataLoaderImpl {
return Error::success();
}

void upgradeDebugInfo(bool ModuleLevel) {
/// Specifies which kind of debug info upgrade should be performed.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe there could be some more details in this comment about why there are different kinds of debug info upgrade?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added more detailed comment.

Copy link
Copy Markdown
Member

@jmorse jmorse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looked at the tests; only thing I've missed now is the unittests (out of time today).

funcimport-debug-retained-nodes.ll is enjoyably thorough -- I wonder whether it's so specific that it'll easily lose coverage of what it's testing though. Sometimes we use -debug output to ensure we're taking the correct codepath, would it be feasible to check that to ensure we're hitting the lazy-metadata-loading path? (This is usually discouraged, but given how hard it is to debug this area, IMO it's worth doing to avoid future quiet loss of coverage)

Comment on lines +9 to +10
// This test checks that the varargs thunk is correctly created if types of
// DILocalVariables of the base function are unresolved at the cloning time.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you elaborate on the test criteria -- as I understand it this test is to avoid a crash in a tricky situation where types have no full definition? If so, please say that explicitly, and what kind of DILocalVariable information should be produced (currently none / incorrect information).

(I'm thinking ahead to if/when this test fails and someone has to determine the intention behind it).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clarified that in the comment.

static int public_static;

protected:
// CHECK-DAG: !DIDerivedType(tag: DW_TAG_typedef, name: "prot_using",{{.*}} line: [[@LINE+3]],{{.*}} flags: DIFlagProtected)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm interested in why this is failing / necessary -- the '-DAG' suffix should mean the check lines are re-orderable anyway, and as far as I read the diff you're only changing the CHECK-line position and updating the relative-line-num calculation accordingly?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, it's a rebasing artifact. Reverted this change.

// CHECK: !DILocalVariable(name: "c", {{.*}}, flags: DIFlagArtificial
// CHECK: !DILocalVariable(
// CHECK-NOT: name:
// CHECK: type: ![[UNION]]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be CHECK-SAME, to ensure that we're on the same line as the nameless DILocalVariable? Otherwise this (while unlikely) could match an unrelated later type.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment on lines +1 to +3
; RUN: llvm-as --disable-verify < %s -o %t0
; RUN: opt --passes=verify %t0 -o /dev/null
; RUN: llvm-dis %t0 -o - | FileCheck %s --implicit-check-not=DICompositeType
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good test; but isn't the retainedNodes field going to be normalised by the first llvm-as, meaning that opt and llvm-dis only see a normalised input? If so, perhaps a round-trip test is sufficient, and something else is needed to exercise the metadata-loader for bitcode?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ouch, when making this PR, I moved the logic of the test to llvm/test/DebugInfo/X86/cleanup-retained-nodes.ll, llvm/test/DebugInfo/X86/llparser-cleanup-retained-nodes.ll (and changed it a bit).
But I dind't remove llvm/test/Bitcode/local-type-scope.ll. Done now.
I've removed extra opt invocations from [llparser-]cleanup-retained-nodes.ll.

; CHECK: distinct !DICompositeType(tag: DW_TAG_class_type, {{.*}}, identifier: "type_global_in_another_module")
; CHECK: [[EMPTY:![0-9]+]] = !{}
; CHECK: [[BAR1:![0-9]+]] = distinct !DISubprogram(name: "bar", {{.*}}, retainedNodes: [[RN_BAR1:![0-9]+]])
; CHECK: [[RN_BAR1]] = !{[[T1:![0-9]+]], [[T1:![0-9]+]], [[T1:![0-9]+]], [[T2:![0-9]+]]}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each of these T1 variables capture, can the second and third just be [[T1]]? The fact that the same node appears multiple times is presumably something intended to be checked, to ensure type_global_in_another_module doesn't appear here.

Copy link
Copy Markdown
Member Author

@dzhidzhoev dzhidzhoev Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the catch!

Original DISubprogram(name: "bar", ...) (before loading) has repeating "local_type" ([[T1]] in check lines). I did it to ensure that all occurences of "local_type" are removed from the clone of DISubprogram "bar". It is checked at the line:

; CHECK: {{![0-9]+}} = distinct !DISubprogram(name: "bar", {{.*}}, retainedNodes: [[EMPTY:![0-9]+]])

; CHECK: DW_TAG_subprogram
; CHECK: DW_AT_abstract_origin {{.*}} "_Z3foov"

; FIXME: 'struct B' should be in the abstract tree below, not here.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could I request more indentation, or ';' characters at the start, to make this standout: otherwise it's the same width as CHECK: and could easily be mistaken for a check line.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

; CHECK: DW_AT_abstract_origin {{.*}} "objA"
; CHECK: NULL

%struct.B = type { i32 }
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could be misreading, but shouldn't we have some type information for "A" above, or otherwise something for the objA variable to refer to?

Copy link
Copy Markdown
Member Author

@dzhidzhoev dzhidzhoev Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added check lines for DW_TAG_structure DIE of "A".

Comment on lines +54 to +57
; CHECK: ![[EMPTY:[0-9]+]] = !{}
; CHECK: !DISubprogram(name: "inlined_out_clone", {{.*}}, retainedNodes: ![[EMPTY]]
; CHECK: !DICompositeType({{.*}}, identifier: "local_type"
; CHECK: !DISubprogram(name: "inlined_out_clone", {{.*}}, retainedNodes: ![[EMPTY]]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't say I completely follow the premise but -- shouldn't we also check that the DICompositeType is in the correct scope?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The primary purpose is to check that DISubprogram of the imported function has empty retained nodes list (cleaned up from the reference to the ODR-uniqued local type).
Indeed, DICompositeType should be in the scope of one of the "inlined_out_clone" subprograms. Added a check for scope and an explanatory comment.

Comment on lines +44 to +50
; RUN: opt --bitcode-mdindex-threshold=0 -module-summary %s -o %t.bc
; RUN: opt --bitcode-mdindex-threshold=0 -module-summary %p/Inputs/funcimport-debug-retained-nodes.ll -o %t2.bc
; RUN: llvm-lto -thinlto -o %t3 %t.bc %t2.bc
; RUN: llvm-lto --thinlto-action=run %t.bc %t2.bc --thinlto-save-temps=%t3 2>&1 | FileCheck --allow-empty --check-prefix=LTO %s
; RUN: llvm-dis %t30.3.imported.bc -o - | FileCheck %s \
; RUN: --implicit-check-not='DISubprogram(name: "inlined_out_clone"' \
; RUN: --implicit-check-not='DICompositeType({{.*}}, identifier: "local_type"'
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great explanation above; these RUN lines should be above it though to meet the LLVM convention.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Comment on lines +12 to +13
; Ensure that function-local types have the correct subprogram parent even if
; those subprograms are inlined.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we specify which is the correct subprogram parent for future readers; the abstract subprogram, right?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's right. Fixed.

jmorse and others added 6 commits January 29, 2026 23:42
…lock scopes (4/7)

RFC https://discourse.llvm.org/t/rfc-dwarfdebug-fix-and-improve-handling-imported-entities-types-and-static-local-in-subprogram-and-lexical-block-scopes/68544

Similar to imported declarations, the patch tracks function-local types in
DISubprogram's 'retainedNodes' field. DwarfDebug is adjusted in accordance with
the aforementioned metadata change and provided a support of function-local
types scoped within a lexical block.

The patch assumes that DICompileUnit's 'enums field' no longer tracks local
types and DwarfDebug would assert if any locally-scoped types get placed there.

Reviewed By: jmmartinez
Authored-by: Kristina Bessonova <[email protected]>
Differential Revision: https://reviews.llvm.org/D144006

(cherry picked from commit 2953d4a)
Signed-off-by: Jeremy Morse <[email protected]>

Conflicts:
	llvm/lib/IR/DIBuilder.cpp
	llvm/lib/IR/DebugInfo.cpp
        llvm/lib/Transforms/Utils/CloneFunction.cpp
…exical block scopes (4/7)"

This is an attempt to merge https://reviews.llvm.org/D144006 with LTO
fix.

The last merge attempt was llvm#75385.
The issue with it was investigated in
llvm#75385 (comment).
If the same (in the sense of ODR identifier/ODR uniquing rules) local type
is present in two modules, and these modules are linked together, the type
gets uniqued. A DIType, that happens to be loaded first, survives linking,
and the references on other types with the same ODR identifier from the modules
loaded later are replaced with the references on the DIType loaded first.
Since defintion subprograms, in scope of which these types are located,
are not deduplicated, the linker output may contain multiple
DISubprogram's having the same (uniqued) type in their retainedNodes
lists.
Further compilation of such modules causes crashes.

To tackle that,
* previous solution to handle LTO linking with local types in
retainedNodes is removed (cloneLocalTypes() function),
* for each loaded distinct (definition) DISubprogram, its retainedNodes
list is scanned after loading, and DITypes with a scope of another subprogram
are removed. If something from a Function corresponding to the
DISubprogram references uniqued type, we rely on cross-CU links.

With this approach, clang builds without crashes in FullLTO (which is not
the case for llvm#75385).

Additionally:
* a check is added to Verifier to report about local types located
in a wrong retainedNodes list,
* DIBuilder's methods for type creation are updated, as https://reviews.llvm.org/D144006
has gotten slightly out-of-date.

Commit llvm#75385 and the new changes
are placed in separate commits to simplify review process.
…e cloned

CGVTables: hide unresolved local types from ValueMapper
@dzhidzhoev dzhidzhoev force-pushed the debuginfo/rfc-krisb/4/base branch from 332fa01 to 5176fec Compare January 30, 2026 00:59
@dzhidzhoev
Copy link
Copy Markdown
Member Author

Thank you @jmorse and @dwblaikie for the rich and detailed feedback — this is very helpful!

Sorry for the delay, I was out of office for quite some time. Now I've updated the PR to address the comments.

@dzhidzhoev
Copy link
Copy Markdown
Member Author

I'm still getting my head around the major differences here and haven't looked at the tests, but see:

  • We're directly un-doing the effect of ODRUniqing by filtering for vulnerable retainedNodes lists on bitcode/IR load,
  • Types that have been ODRUnique'd get cross-scope and possibly cross-CU references to them,

Yes, that's right.

Is there any protection against a part of LLVM duplicating a type and silently getting an ODRUnique'd type back, which then leads to confusion later -- or is that what the createIdentityMDPredicate code is protecting against?

There is no such protection, as far as I understand. createIdentityMDPredicate was rather made to speed up function cloning/metadata values (re)mapping, not to protect against type cloning in particular.

I wonder whether this could lead to a situation where creating type metadata is vulnerable to accidents (however if it is, we should protect against it with lots of assertions, instead of trying to design around it).

The most concerning part for me is type units generation. LLVM uses "type identifier+CU" pair to generate type signature to distinguish different type units. Technically, we can get multiple type units emitted with the same signature for cloned types. But I'm not sure how bad it is, since such units essentially represent the same type (although placed in different scopes). I would appreciate any advice on that.

Probably, it will be easier (and still provide debug info improvement) if we choose to clean local types from retainedNodes of a cloned function in CloneFunctionInto(), and rely on cross-subprogram references (similarly to how this PR is trying to handle the case of ODR-uniquing during LTO), instead of dealing with consequences of mutliple local types having the same ID.

Copy link
Copy Markdown
Member

@jmorse jmorse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for sticking with this; github has done its favourite thing of chucking away half the comments, so it's a bit difficult to re-review. Looking at what I wrote last time, I think everything's been addressed, I would only add that you could put the explanation of the MetadataLoader.cpp upgradeCULocals function into a comment there for future reference.

This LGTM, modulo Davids comments.

@dzhidzhoev
Copy link
Copy Markdown
Member Author

Thank you! I've added upgradeCULocals algorithm description as a comment.

Will wait for David's response.

Copy link
Copy Markdown
Collaborator

@dwblaikie dwblaikie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some optional thoughts

@dzhidzhoev
Copy link
Copy Markdown
Member Author

Thank you!

@dzhidzhoev dzhidzhoev merged commit b9cecee into llvm:main Feb 3, 2026
8 of 10 checks passed
@llvm-ci
Copy link
Copy Markdown

llvm-ci commented Feb 4, 2026

LLVM Buildbot has detected a new failure on builder sanitizer-x86_64-linux running on sanitizer-buildbot1 while building clang,llvm at step 2 "annotate".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/66/builds/25891

Here is the relevant piece of the build log for the reference
Step 2 (annotate) failure: 'python ../sanitizer_buildbot/sanitizers/zorg/buildbot/builders/sanitizers/buildbot_selector.py' (failure)
...
llvm-lit: /home/b/sanitizer-x86_64-linux/build/llvm-project/compiler-rt/test/lit.common.cfg.py:255: warning: COMPILER_RT_TEST_STANDALONE_BUILD_LIBS=ON, but this test suite does not support testing the just-built runtime libraries when the test compiler is configured to use different runtime libraries. Either modify this test suite to support this test configuration, or set COMPILER_RT_TEST_STANDALONE_BUILD_LIBS=OFF to test the runtime libraries included in the compiler instead.
llvm-lit: /home/b/sanitizer-x86_64-linux/build/llvm-project/compiler-rt/test/lit.common.cfg.py:266: note: Testing using libraries in "/home/b/sanitizer-x86_64-linux/build/build_default/./lib/../lib/clang/23/lib/i386-unknown-linux-gnu"
llvm-lit: /home/b/sanitizer-x86_64-linux/build/llvm-project/compiler-rt/test/lit.common.cfg.py:244: warning: Compiler lib dir != compiler-rt lib dir
Compiler libdir:     "/home/b/sanitizer-x86_64-linux/build/build_default/lib/clang/23/lib/i386-unknown-linux-gnu"
compiler-rt libdir:  "/home/b/sanitizer-x86_64-linux/build/build_default/lib/clang/23/lib/x86_64-unknown-linux-gnu"
llvm-lit: /home/b/sanitizer-x86_64-linux/build/llvm-project/compiler-rt/test/lit.common.cfg.py:255: warning: COMPILER_RT_TEST_STANDALONE_BUILD_LIBS=ON, but this test suite does not support testing the just-built runtime libraries when the test compiler is configured to use different runtime libraries. Either modify this test suite to support this test configuration, or set COMPILER_RT_TEST_STANDALONE_BUILD_LIBS=OFF to test the runtime libraries included in the compiler instead.
llvm-lit: /home/b/sanitizer-x86_64-linux/build/llvm-project/compiler-rt/test/lit.common.cfg.py:266: note: Testing using libraries in "/home/b/sanitizer-x86_64-linux/build/build_default/./lib/../lib/clang/23/lib/x86_64-unknown-linux-gnu"
llvm-lit: /home/b/sanitizer-x86_64-linux/build/llvm-project/llvm/utils/lit/lit/main.py:74: note: The test suite configuration requested an individual test timeout of 0 seconds but a timeout of 900 seconds was requested on the command line. Forcing timeout to be 900 seconds.
-- Testing: 11142 tests, 64 workers --
Testing:  0.. 10.. 20.. 30.. 40.. 50.. 60.. 70.. 80.
FAIL: XRay-x86_64-linux :: TestCases/Posix/basic-filtering.cpp (9713 of 11142)
******************** TEST 'XRay-x86_64-linux :: TestCases/Posix/basic-filtering.cpp' FAILED ********************
Exit Code: 1

Command Output (stdout):
--
# RUN: at line 4
/home/b/sanitizer-x86_64-linux/build/build_default/./bin/clang  --driver-mode=g++ -fxray-instrument  -m64   -std=c++11 /home/b/sanitizer-x86_64-linux/build/llvm-project/compiler-rt/test/xray/TestCases/Posix/basic-filtering.cpp -o /home/b/sanitizer-x86_64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/xray/X86_64LinuxConfig/TestCases/Posix/Output/basic-filtering.cpp.tmp -g
# executed command: /home/b/sanitizer-x86_64-linux/build/build_default/./bin/clang --driver-mode=g++ -fxray-instrument -m64 -std=c++11 /home/b/sanitizer-x86_64-linux/build/llvm-project/compiler-rt/test/xray/TestCases/Posix/basic-filtering.cpp -o /home/b/sanitizer-x86_64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/xray/X86_64LinuxConfig/TestCases/Posix/Output/basic-filtering.cpp.tmp -g
# note: command had no output on stdout or stderr
# RUN: at line 5
rm -f basic-filtering-*
# executed command: rm -f 'basic-filtering-*'
# note: command had no output on stdout or stderr
# RUN: at line 6
env XRAY_OPTIONS="patch_premain=true xray_mode=xray-basic verbosity=1      xray_logfile_base=basic-filtering-      xray_naive_log_func_duration_threshold_us=1000      xray_naive_log_max_stack_depth=2"  /home/b/sanitizer-x86_64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/xray/X86_64LinuxConfig/TestCases/Posix/Output/basic-filtering.cpp.tmp 2>&1 |      FileCheck /home/b/sanitizer-x86_64-linux/build/llvm-project/compiler-rt/test/xray/TestCases/Posix/basic-filtering.cpp
# executed command: env 'XRAY_OPTIONS=patch_premain=true xray_mode=xray-basic verbosity=1      xray_logfile_base=basic-filtering-      xray_naive_log_func_duration_threshold_us=1000      xray_naive_log_max_stack_depth=2' /home/b/sanitizer-x86_64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/xray/X86_64LinuxConfig/TestCases/Posix/Output/basic-filtering.cpp.tmp
# note: command had no output on stdout or stderr
# executed command: FileCheck /home/b/sanitizer-x86_64-linux/build/llvm-project/compiler-rt/test/xray/TestCases/Posix/basic-filtering.cpp
# note: command had no output on stdout or stderr
# RUN: at line 11
ls basic-filtering-* | head -1 | tr -d '\n' > /home/b/sanitizer-x86_64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/xray/X86_64LinuxConfig/TestCases/Posix/Output/basic-filtering.cpp.tmp.log
# executed command: ls 'basic-filtering-*'
# note: command had no output on stdout or stderr
# executed command: head -1
# note: command had no output on stdout or stderr
# executed command: tr -d '\n'
# note: command had no output on stdout or stderr
# RUN: at line 12
/home/b/sanitizer-x86_64-linux/build/build_default/./bin/llvm-xray convert --symbolize --output-format=yaml -instr_map=/home/b/sanitizer-x86_64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/xray/X86_64LinuxConfig/TestCases/Posix/Output/basic-filtering.cpp.tmp      "/home/b/sanitizer-x86_64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/xray/X86_64LinuxConfig/TestCases/Posix/basic-filtering-basic-filtering.cpp.tmp.OLWxZV" |      FileCheck /home/b/sanitizer-x86_64-linux/build/llvm-project/compiler-rt/test/xray/TestCases/Posix/basic-filtering.cpp --check-prefix TRACE
# executed command: /home/b/sanitizer-x86_64-linux/build/build_default/./bin/llvm-xray convert --symbolize --output-format=yaml -instr_map=/home/b/sanitizer-x86_64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/xray/X86_64LinuxConfig/TestCases/Posix/Output/basic-filtering.cpp.tmp '%{readfile:/home/b/sanitizer-x86_64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/xray/X86_64LinuxConfig/TestCases/Posix/Output/basic-filtering.cpp.tmp.log}'
# note: command had no output on stdout or stderr
# executed command: FileCheck /home/b/sanitizer-x86_64-linux/build/llvm-project/compiler-rt/test/xray/TestCases/Posix/basic-filtering.cpp --check-prefix TRACE
# .---command stderr------------
# | /home/b/sanitizer-x86_64-linux/build/llvm-project/compiler-rt/test/xray/TestCases/Posix/basic-filtering.cpp:61:15: error: TRACE-NOT: excluded string found in input
# | // TRACE-NOT: - { type: 0, func-id: {{.*}}, function: {{.*filtered.*}}, {{.*}} }
# |               ^
# | <stdin>:10:2: note: found here
# |  - { type: 0, func-id: 1, function: 'filtered()', cpu: 0, thread: 309858, process: 309858, kind: function-enter, tsc: 1770165374751750411, data: '' }
Step 14 (test compiler-rt default) failure: test compiler-rt default (failure)
...
llvm-lit: /home/b/sanitizer-x86_64-linux/build/llvm-project/compiler-rt/test/lit.common.cfg.py:255: warning: COMPILER_RT_TEST_STANDALONE_BUILD_LIBS=ON, but this test suite does not support testing the just-built runtime libraries when the test compiler is configured to use different runtime libraries. Either modify this test suite to support this test configuration, or set COMPILER_RT_TEST_STANDALONE_BUILD_LIBS=OFF to test the runtime libraries included in the compiler instead.
llvm-lit: /home/b/sanitizer-x86_64-linux/build/llvm-project/compiler-rt/test/lit.common.cfg.py:266: note: Testing using libraries in "/home/b/sanitizer-x86_64-linux/build/build_default/./lib/../lib/clang/23/lib/i386-unknown-linux-gnu"
llvm-lit: /home/b/sanitizer-x86_64-linux/build/llvm-project/compiler-rt/test/lit.common.cfg.py:244: warning: Compiler lib dir != compiler-rt lib dir
Compiler libdir:     "/home/b/sanitizer-x86_64-linux/build/build_default/lib/clang/23/lib/i386-unknown-linux-gnu"
compiler-rt libdir:  "/home/b/sanitizer-x86_64-linux/build/build_default/lib/clang/23/lib/x86_64-unknown-linux-gnu"
llvm-lit: /home/b/sanitizer-x86_64-linux/build/llvm-project/compiler-rt/test/lit.common.cfg.py:255: warning: COMPILER_RT_TEST_STANDALONE_BUILD_LIBS=ON, but this test suite does not support testing the just-built runtime libraries when the test compiler is configured to use different runtime libraries. Either modify this test suite to support this test configuration, or set COMPILER_RT_TEST_STANDALONE_BUILD_LIBS=OFF to test the runtime libraries included in the compiler instead.
llvm-lit: /home/b/sanitizer-x86_64-linux/build/llvm-project/compiler-rt/test/lit.common.cfg.py:266: note: Testing using libraries in "/home/b/sanitizer-x86_64-linux/build/build_default/./lib/../lib/clang/23/lib/x86_64-unknown-linux-gnu"
llvm-lit: /home/b/sanitizer-x86_64-linux/build/llvm-project/llvm/utils/lit/lit/main.py:74: note: The test suite configuration requested an individual test timeout of 0 seconds but a timeout of 900 seconds was requested on the command line. Forcing timeout to be 900 seconds.
-- Testing: 11142 tests, 64 workers --
Testing:  0.. 10.. 20.. 30.. 40.. 50.. 60.. 70.. 80.
FAIL: XRay-x86_64-linux :: TestCases/Posix/basic-filtering.cpp (9713 of 11142)
******************** TEST 'XRay-x86_64-linux :: TestCases/Posix/basic-filtering.cpp' FAILED ********************
Exit Code: 1

Command Output (stdout):
--
# RUN: at line 4
/home/b/sanitizer-x86_64-linux/build/build_default/./bin/clang  --driver-mode=g++ -fxray-instrument  -m64   -std=c++11 /home/b/sanitizer-x86_64-linux/build/llvm-project/compiler-rt/test/xray/TestCases/Posix/basic-filtering.cpp -o /home/b/sanitizer-x86_64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/xray/X86_64LinuxConfig/TestCases/Posix/Output/basic-filtering.cpp.tmp -g
# executed command: /home/b/sanitizer-x86_64-linux/build/build_default/./bin/clang --driver-mode=g++ -fxray-instrument -m64 -std=c++11 /home/b/sanitizer-x86_64-linux/build/llvm-project/compiler-rt/test/xray/TestCases/Posix/basic-filtering.cpp -o /home/b/sanitizer-x86_64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/xray/X86_64LinuxConfig/TestCases/Posix/Output/basic-filtering.cpp.tmp -g
# note: command had no output on stdout or stderr
# RUN: at line 5
rm -f basic-filtering-*
# executed command: rm -f 'basic-filtering-*'
# note: command had no output on stdout or stderr
# RUN: at line 6
env XRAY_OPTIONS="patch_premain=true xray_mode=xray-basic verbosity=1      xray_logfile_base=basic-filtering-      xray_naive_log_func_duration_threshold_us=1000      xray_naive_log_max_stack_depth=2"  /home/b/sanitizer-x86_64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/xray/X86_64LinuxConfig/TestCases/Posix/Output/basic-filtering.cpp.tmp 2>&1 |      FileCheck /home/b/sanitizer-x86_64-linux/build/llvm-project/compiler-rt/test/xray/TestCases/Posix/basic-filtering.cpp
# executed command: env 'XRAY_OPTIONS=patch_premain=true xray_mode=xray-basic verbosity=1      xray_logfile_base=basic-filtering-      xray_naive_log_func_duration_threshold_us=1000      xray_naive_log_max_stack_depth=2' /home/b/sanitizer-x86_64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/xray/X86_64LinuxConfig/TestCases/Posix/Output/basic-filtering.cpp.tmp
# note: command had no output on stdout or stderr
# executed command: FileCheck /home/b/sanitizer-x86_64-linux/build/llvm-project/compiler-rt/test/xray/TestCases/Posix/basic-filtering.cpp
# note: command had no output on stdout or stderr
# RUN: at line 11
ls basic-filtering-* | head -1 | tr -d '\n' > /home/b/sanitizer-x86_64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/xray/X86_64LinuxConfig/TestCases/Posix/Output/basic-filtering.cpp.tmp.log
# executed command: ls 'basic-filtering-*'
# note: command had no output on stdout or stderr
# executed command: head -1
# note: command had no output on stdout or stderr
# executed command: tr -d '\n'
# note: command had no output on stdout or stderr
# RUN: at line 12
/home/b/sanitizer-x86_64-linux/build/build_default/./bin/llvm-xray convert --symbolize --output-format=yaml -instr_map=/home/b/sanitizer-x86_64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/xray/X86_64LinuxConfig/TestCases/Posix/Output/basic-filtering.cpp.tmp      "/home/b/sanitizer-x86_64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/xray/X86_64LinuxConfig/TestCases/Posix/basic-filtering-basic-filtering.cpp.tmp.OLWxZV" |      FileCheck /home/b/sanitizer-x86_64-linux/build/llvm-project/compiler-rt/test/xray/TestCases/Posix/basic-filtering.cpp --check-prefix TRACE
# executed command: /home/b/sanitizer-x86_64-linux/build/build_default/./bin/llvm-xray convert --symbolize --output-format=yaml -instr_map=/home/b/sanitizer-x86_64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/xray/X86_64LinuxConfig/TestCases/Posix/Output/basic-filtering.cpp.tmp '%{readfile:/home/b/sanitizer-x86_64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/xray/X86_64LinuxConfig/TestCases/Posix/Output/basic-filtering.cpp.tmp.log}'
# note: command had no output on stdout or stderr
# executed command: FileCheck /home/b/sanitizer-x86_64-linux/build/llvm-project/compiler-rt/test/xray/TestCases/Posix/basic-filtering.cpp --check-prefix TRACE
# .---command stderr------------
# | /home/b/sanitizer-x86_64-linux/build/llvm-project/compiler-rt/test/xray/TestCases/Posix/basic-filtering.cpp:61:15: error: TRACE-NOT: excluded string found in input
# | // TRACE-NOT: - { type: 0, func-id: {{.*}}, function: {{.*filtered.*}}, {{.*}} }
# |               ^
# | <stdin>:10:2: note: found here
# |  - { type: 0, func-id: 1, function: 'filtered()', cpu: 0, thread: 309858, process: 309858, kind: function-enter, tsc: 1770165374751750411, data: '' }

dzhidzhoev added a commit to dzhidzhoev/llvm-project that referenced this pull request Feb 6, 2026
…s in DIBuilder

Fix a regression introduced by llvm#165032
where DIBuilder could attach local metadata nodes to the wrong subprogram during finalization.

DIBuilder tracks local variables, labels, and types in
`DIBuilder::SubprogramTrackedNodes` and later attaches them to their parent
subprogram's retainedNodes in `finalizeSubprogram()`.

However, temporary local types created via `createReplaceableCompositeType()`
may later be replaced by a type with a different scope. DIBuilder does not
currently verify that the scopes of the original and replacement types match.

As a result, local types can be incorrectly attached to the retainedNodes of an
unrelated subprogram. This issue is observable in clang with limited debug info
mode (see `clang/test/DebugInfo/CXX/ctor-homing-local-type.cpp`).

This patch updates DIBuilder::finalizeSubprogram() to verify that tracked
metadata nodes still belong to the subprogram being finalized, and skips nodes
whose scopes no longer match.
@dzhidzhoev
Copy link
Copy Markdown
Member Author

dzhidzhoev commented Feb 6, 2026

This PR has introduced a clang regression in limited debug info mode. A fix is proposed in #180294.

rishabhmadan19 pushed a commit to rishabhmadan19/llvm-project that referenced this pull request Feb 9, 2026
…exical block scopes (4/7)" (llvm#165032)

This is an attempt to merge https://reviews.llvm.org/D144006 with LTO
fix.

The last merge attempt was
llvm#75385.
The issue with it was investigated in
llvm#75385 (comment).
The problem happens when 
1. Several modules are being linked.
2. There are several DISubprograms that initially belong to different
modules but represent the same source code function (for example, a
function included from the same source code file).
3. Some of such DISubprograms survive IR linking. It may happen if one
of them is inlined somewhere or if the functions that have these
DISubprograms attached have internal linkage.
4. Each of these DISubprograms has a local type that corresponds to the
same source code type. These types are initially from different modules,
but have the same ODR identifier.

If the same (in the sense of ODR identifier/ODR uniquing rules) local
type is present in two modules, and these modules are linked together,
the type gets uniqued. A DIType, that happens to be loaded first,
survives linking, and the references on other types with the same ODR
identifier from the modules loaded later are replaced with the
references on the DIType loaded first. Since defintion subprograms, in
scope of which these types are located, are not deduplicated, the linker
output may contain multiple DISubprogram's having the same (uniqued)
type in their retainedNodes lists.
Further compilation of such modules causes crashes.

To tackle that,
* previous solution to handle LTO linking with local types in
retainedNodes is removed (cloneLocalTypes() function),
* for each loaded distinct (definition) DISubprogram, its retainedNodes
list is scanned after loading, and DITypes with a scope of another
subprogram are removed. If something from a Function corresponding to
the DISubprogram references uniqued type, we rely on cross-CU links.

Additionally:
* a check is added to Verifier to report about local types located in a
wrong retainedNodes list,

Original commit message follows.
---------

RFC https://discourse.llvm.org/t/rfc-dwarfdebug-fix-and-improve-handling-imported-entities-types-and-static-local-in-subprogram-and-lexical-block-scopes/68544

Similar to imported declarations, the patch tracks function-local types in
DISubprogram's 'retainedNodes' field. DwarfDebug is adjusted in accordance with
the aforementioned metadata change and provided a support of function-local
types scoped within a lexical block.

The patch assumes that DICompileUnit's 'enums field' no longer tracks local
types and DwarfDebug would assert if any locally-scoped types get placed there.

Authored-by: Kristina Bessonova <[email protected]>
Co-authored-by: Jeremy Morse <[email protected]>
dzhidzhoev added a commit that referenced this pull request Feb 9, 2026
…rams in DIBuilder (#180294)

Fix a regression introduced by
#165032, where DIBuilder could
attach local metadata nodes to the wrong subprogram during finalization.

DIBuilder records freshly created local variables, labels, and types in
`DIBuilder::SubprogramTrackedNodes`, and later attaches them to their
parent subprogram's retainedNodes in `finalizeSubprogram()`.

However, a temporary local type created via
`createReplaceableCompositeType()` may later be replaced by a type with
a different scope.
DIBuilder does not currently verify that the scopes of the original and
replacement types match.

As a result, local types can be incorrectly attached to the
retainedNodes of an unrelated subprogram. This issue is observable in
clang with limited debug info mode (see
`clang/test/DebugInfo/CXX/ctor-homing-local-type.cpp`).

This patch updates `DIBuilder::finalizeSubprogram()` to verify that
tracked metadata nodes still belong to the subprogram being finalized,
and avoids adding nodes whose scopes no longer match to retainedNodes
field of an unrelated subprogram.
llvm-sync bot pushed a commit to arm/arm-toolchain that referenced this pull request Feb 9, 2026
…ted subprograms in DIBuilder (#180294)

Fix a regression introduced by
llvm/llvm-project#165032, where DIBuilder could
attach local metadata nodes to the wrong subprogram during finalization.

DIBuilder records freshly created local variables, labels, and types in
`DIBuilder::SubprogramTrackedNodes`, and later attaches them to their
parent subprogram's retainedNodes in `finalizeSubprogram()`.

However, a temporary local type created via
`createReplaceableCompositeType()` may later be replaced by a type with
a different scope.
DIBuilder does not currently verify that the scopes of the original and
replacement types match.

As a result, local types can be incorrectly attached to the
retainedNodes of an unrelated subprogram. This issue is observable in
clang with limited debug info mode (see
`clang/test/DebugInfo/CXX/ctor-homing-local-type.cpp`).

This patch updates `DIBuilder::finalizeSubprogram()` to verify that
tracked metadata nodes still belong to the subprogram being finalized,
and avoids adding nodes whose scopes no longer match to retainedNodes
field of an unrelated subprogram.
dzhidzhoev added a commit to dzhidzhoev/llvm-project that referenced this pull request Mar 7, 2026
Since llvm#165032, DwarfDebug asserts if function-local enums are present
in the enums field of DICompileUnit.
This patch adds a check to the Verifier to detect such invalid IR earlier.

Incorrect occurence of a local enum in DICompileUnit's enums field in
llvm/test/DebugInfo/COFF/enum-co.ll is fixed.

This change is extracted from https://reviews.llvm.org/D144008.
dzhidzhoev added a commit that referenced this pull request Mar 13, 2026
…185228)

Since #165032, DwarfDebug asserts if function-local enums are present in
the enums field of DICompileUnit.
This patch adds a check to the Verifier to detect such invalid IR
earlier.

Incorrect occurence of a local enum in DICompileUnit's enums field in
`llvm/test/DebugInfo/COFF/enum-co.ll` is fixed.

This change is extracted from https://reviews.llvm.org/D144008.
albertbolt1 pushed a commit to albertbolt1/llvm-project that referenced this pull request Mar 13, 2026
…lvm#185228)

Since llvm#165032, DwarfDebug asserts if function-local enums are present in
the enums field of DICompileUnit.
This patch adds a check to the Verifier to detect such invalid IR
earlier.

Incorrect occurence of a local enum in DICompileUnit's enums field in
`llvm/test/DebugInfo/COFF/enum-co.ll` is fixed.

This change is extracted from https://reviews.llvm.org/D144008.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend:ARM backend:X86 clang:codegen IR generation bugs: mangling, exceptions, etc. clang:static analyzer clang Clang issues not falling into any other category debuginfo llvm:codegen llvm:ir llvm:transforms

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants