[llvm] 030551e - [DWARFLinker] Unique a static data member independent of its tag (#217751)

via llvm-commits llvm-commits at lists.llvm.org
Fri Aug 21 14:01:54 PDT 2026


Author: Jonas Devlieghere
Date: 2026-08-21T14:01:50-07:00
New Revision: 030551e95ed23f4e295607390d15c7758f258431

URL: https://github.com/llvm/llvm-project/commit/030551e95ed23f4e295607390d15c7758f258431
DIFF: https://github.com/llvm/llvm-project/commit/030551e95ed23f4e295607390d15c7758f258431.diff

LOG: [DWARFLinker] Unique a static data member independent of its tag (#217751)

A C++ static data member is a DW_TAG_member with DW_AT_declaration in
DWARF 4 and a DW_TAG_variable in DWARF 5 (DWARF 5 section 5.7.6). Clang
chooses between the two from -gdwarf-version alone, so both reach the
linker whenever objects built at different versions are linked.

Uniquing in the parallel linker identifies a data member by its index
among the record's DW_TAG_member children, and DW_TAG_variable children
are not counted.

```
struct S {
  static const int kMask = 1;
  int field;
};
```

In the example above, kMask is counted in the DWARF 4 unit and takes
index 0, pushing field to index 1, while in the DWARF 5 unit field takes
index 0. field is therefore uniqued under two different names, so the
merged record gains a member for each.

Leave a static data member out of the index numbering and unique it
under the DW_TAG_variable prefix, so both tags yield one name and the
merged record holds a single declaration.

rdar://185224997

Assisted-by: Claude

Added: 
    llvm/test/tools/dsymutil/X86/odr-static-member-mixed-spelling.s

Modified: 
    llvm/lib/DWARFLinker/Parallel/SyntheticTypeNameBuilder.cpp
    llvm/lib/DWARFLinker/Parallel/SyntheticTypeNameBuilder.h

Removed: 
    


################################################################################
diff  --git a/llvm/lib/DWARFLinker/Parallel/SyntheticTypeNameBuilder.cpp b/llvm/lib/DWARFLinker/Parallel/SyntheticTypeNameBuilder.cpp
index c3d8a2489de9a..1ac7f59a9f1fc 100644
--- a/llvm/lib/DWARFLinker/Parallel/SyntheticTypeNameBuilder.cpp
+++ b/llvm/lib/DWARFLinker/Parallel/SyntheticTypeNameBuilder.cpp
@@ -412,7 +412,7 @@ Error SyntheticTypeNameBuilder::addDIETypeName(
     // deterministic deduplication.
     if (Error Err = addParentName(*UnitEntryPair))
       return Err;
-    addTypePrefix(UnitEntryPair->DieEntry);
+    addTypePrefix(*UnitEntryPair->CU, UnitEntryPair->DieEntry);
 
     if (ChildIndex) {
       addOrderedName(*ChildIndex);
@@ -433,8 +433,28 @@ Error SyntheticTypeNameBuilder::addDIETypeName(
   return Error::success();
 }
 
+/// A C++ static data member is spelled as a DW_TAG_member carrying
+/// DW_AT_declaration in DWARF 4 and as a DW_TAG_variable in DWARF 5 (DWARF 5
+/// section 5.7.6). Returns true when this declares a static data member instead
+/// of describing storage in the record.
+static bool isStaticDataMember(CompileUnit &CU,
+                               const DWARFDebugInfoEntry *DieEntry) {
+  assert(DieEntry->getTag() == dwarf::DW_TAG_member &&
+         "expected a DW_TAG_member");
+
+  if (!dwarf::toUnsigned(CU.find(DieEntry, dwarf::DW_AT_declaration), 0))
+    return false;
+
+  // A member that has a location occupies storage in the record regardless of
+  // how else it is marked, so it is the absence of a location that makes this a
+  // pure declaration. Zero is a valid offset, so only the attribute's presence
+  // matters, not its value.
+  return !CU.find(DieEntry, {dwarf::DW_AT_data_member_location,
+                             dwarf::DW_AT_data_bit_offset});
+}
+
 void SyntheticTypeNameBuilder::addTypePrefix(
-    const DWARFDebugInfoEntry *DieEntry) {
+    CompileUnit &CU, const DWARFDebugInfoEntry *DieEntry) {
   switch (DieEntry->getTag()) {
   case dwarf::DW_TAG_base_type: {
     SyntheticName += "{0}";
@@ -478,7 +498,12 @@ void SyntheticTypeNameBuilder::addTypePrefix(
     SyntheticName += "{A}";
   } break;
   case dwarf::DW_TAG_member: {
-    SyntheticName += "{B}";
+    // Treat a static data member the same way regardless of whether it's
+    // described as a DW_TAG_member or a DW_TAG_variable.
+    if (isStaticDataMember(CU, DieEntry))
+      SyntheticName += "{d}";
+    else
+      SyntheticName += "{B}";
   } break;
   case dwarf::DW_TAG_pointer_type: {
     SyntheticName += "{C}";
@@ -746,6 +771,10 @@ std::optional<size_t> OrderedChildrenIndexAssigner::tagToArrayIndex(
   case dwarf::DW_TAG_namelist_item:
     return 6;
   case dwarf::DW_TAG_member:
+    // Treat a static data member the same way regardless of whether it's
+    // described as a DW_TAG_member or a DW_TAG_variable.
+    if (isStaticDataMember(CU, DieEntry))
+      return std::nullopt;
     return 7;
   default:
     return std::nullopt;

diff  --git a/llvm/lib/DWARFLinker/Parallel/SyntheticTypeNameBuilder.h b/llvm/lib/DWARFLinker/Parallel/SyntheticTypeNameBuilder.h
index 8465c0d77b9cb..3a7ad0a3058ef 100644
--- a/llvm/lib/DWARFLinker/Parallel/SyntheticTypeNameBuilder.h
+++ b/llvm/lib/DWARFLinker/Parallel/SyntheticTypeNameBuilder.h
@@ -91,7 +91,7 @@ class SyntheticTypeNameBuilder {
                                          bool &HasDeclFileName);
 
   /// Add type prefix to the built name.
-  void addTypePrefix(const DWARFDebugInfoEntry *DieEntry);
+  void addTypePrefix(CompileUnit &CU, const DWARFDebugInfoEntry *DieEntry);
 
   /// Add type name to the built name.
   Error addTypeName(UnitEntryPairTy InputUnitEntryPair, bool AddParentNames);

diff  --git a/llvm/test/tools/dsymutil/X86/odr-static-member-mixed-spelling.s b/llvm/test/tools/dsymutil/X86/odr-static-member-mixed-spelling.s
new file mode 100644
index 0000000000000..1677f82f9115f
--- /dev/null
+++ b/llvm/test/tools/dsymutil/X86/odr-static-member-mixed-spelling.s
@@ -0,0 +1,320 @@
+## DWARF 4 producers spell a C++ static data member as a DW_TAG_member with
+## DW_AT_declaration, DWARF 5 producers spell it as a DW_TAG_variable, so both
+## spellings show up whenever objects built at 
diff erent DWARF versions, or by
+## 
diff erent producers, are linked together.
+##
+## The parallel linker derives the name of a data member from its position among
+## the record's data members. A static data member has no storage in the record
+## and therefore no such position, and it has to derive the same name from either
+## spelling. Otherwise units disagreeing on the spelling derive 
diff erent names
+## for the same entity, and the deduplicated record gains a member per name.
+##
+## The hand-written DWARF below describes
+##   struct S {
+##     static const int kMask = 1;
+##     int field;
+##   };
+## referenced from useA() in a.cpp (DWARF 4) and useB() in b.cpp (DWARF 5).
+
+# RUN: llvm-mc -triple x86_64-apple-darwin -filetype=obj %s -o %t.o
+# RUN: llvm-dwarfdump --verify %t.o
+
+# RUN: echo '---' > %t.map
+# RUN: echo "triple:          'x86_64-apple-darwin'" >> %t.map
+# RUN: echo 'objects:'  >> %t.map
+# RUN: echo " -  filename: '%t.o'" >> %t.map
+# RUN: echo '    symbols:' >> %t.map
+# RUN: echo '      - { sym: __Z4useAP1S, objAddr: 0x0, binAddr: 0x10000, size: 0x4 }' >> %t.map
+# RUN: echo '      - { sym: __Z4useBP1S, objAddr: 0x4, binAddr: 0x10010, size: 0x4 }' >> %t.map
+# RUN: echo '...' >> %t.map
+
+# RUN: dsymutil --linker=parallel -y %t.map -f -o %t.parallel.dSYM
+# RUN: llvm-dwarfdump --verify %t.parallel.dSYM
+# RUN: llvm-dwarfdump --debug-info %t.parallel.dSYM > %t.parallel.txt
+# RUN: FileCheck %s --check-prefix=PARALLEL --input-file %t.parallel.txt
+# RUN: FileCheck %s --check-prefix=MASK --input-file %t.parallel.txt \
+# RUN:   --implicit-check-not='DW_AT_name{{.*}}"kMask"'
+# RUN: FileCheck %s --check-prefix=FIELD --input-file %t.parallel.txt \
+# RUN:   --implicit-check-not='DW_AT_name{{.*}}"field"'
+
+## The parallel linker moves S into the artificial type unit, where the static
+## declaration survives alongside the real member. Both spellings are valid
+## output, and the surviving one comes from the unit that wins the type slot.
+
+# PARALLEL: DW_AT_name{{.*}}"__artificial_type_unit"
+# PARALLEL: DW_TAG_structure_type
+# PARALLEL-NEXT: DW_AT_name{{.*}}"S"
+# PARALLEL: DW_TAG_member
+# PARALLEL-NEXT: DW_AT_name{{.*}}"kMask"
+# PARALLEL-NEXT: DW_AT_type
+# PARALLEL-NEXT: DW_AT_external
+# PARALLEL-NEXT: DW_AT_declaration
+
+## Each implicit-check-not lets its member be named exactly once in the whole
+## output, so the two spellings collapsed into a single DIE and neither member
+## displaced the other.
+
+# MASK: DW_AT_name{{.*}}"kMask"
+# MASK-NEXT: DW_AT_type
+# MASK-NEXT: DW_AT_external
+# MASK-NEXT: DW_AT_declaration
+
+# FIELD: DW_AT_name{{.*}}"field"
+# FIELD-NEXT: DW_AT_type
+# FIELD-NEXT: DW_AT_data_member_location{{.*}}(0x00)
+
+## The classic linker derives no names from child positions, so it is
+## unaffected. It runs here to guard against regressions and to confirm the
+## inputs aren't pathological.
+
+# RUN: dsymutil --linker=classic -y %t.map -f -o %t.classic.dSYM
+# RUN: llvm-dwarfdump --verify %t.classic.dSYM
+# RUN: llvm-dwarfdump --debug-info %t.classic.dSYM | FileCheck %s --check-prefix=CLASSIC
+
+# CLASSIC: DW_TAG_structure_type
+# CLASSIC-NEXT: DW_AT_name{{.*}}"S"
+# CLASSIC: DW_AT_name{{.*}}"kMask"
+# CLASSIC: DW_AT_name{{.*}}"field"
+# CLASSIC-NEXT: DW_AT_type
+# CLASSIC-NEXT: DW_AT_data_member_location{{.*}}(0x00)
+
+	.section	__TEXT,__text,regular,pure_instructions
+	.globl	__Z4useAP1S
+__Z4useAP1S:
+LfuncA_begin:
+	retq
+	nop
+	nop
+	nop
+LfuncA_end:
+
+	.globl	__Z4useBP1S
+__Z4useBP1S:
+LfuncB_begin:
+	retq
+	nop
+	nop
+	nop
+LfuncB_end:
+
+	.section	__DWARF,__debug_abbrev,regular,debug
+Lsection_abbrev:
+	.byte	1                       ## Abbreviation Code
+	.byte	17                      ## DW_TAG_compile_unit
+	.byte	1                       ## DW_CHILDREN_yes
+	.byte	37                      ## DW_AT_producer
+	.byte	8                       ## DW_FORM_string
+	.byte	19                      ## DW_AT_language
+	.byte	5                       ## DW_FORM_data2
+	.byte	3                       ## DW_AT_name
+	.byte	8                       ## DW_FORM_string
+	.byte	0, 0
+
+	.byte	2                       ## Abbreviation Code
+	.byte	46                      ## DW_TAG_subprogram
+	.byte	1                       ## DW_CHILDREN_yes
+	.byte	3                       ## DW_AT_name
+	.byte	8                       ## DW_FORM_string
+	.byte	110                     ## DW_AT_linkage_name
+	.byte	8                       ## DW_FORM_string
+	.byte	17                      ## DW_AT_low_pc
+	.byte	1                       ## DW_FORM_addr
+	.byte	18                      ## DW_AT_high_pc
+	.byte	1                       ## DW_FORM_addr
+	.byte	63                      ## DW_AT_external
+	.byte	12                      ## DW_FORM_flag
+	.byte	0, 0
+
+	.byte	3                       ## Abbreviation Code
+	.byte	5                       ## DW_TAG_formal_parameter
+	.byte	0                       ## DW_CHILDREN_no
+	.byte	73                      ## DW_AT_type
+	.byte	19                      ## DW_FORM_ref4
+	.byte	0, 0
+
+	.byte	4                       ## Abbreviation Code
+	.byte	19                      ## DW_TAG_structure_type
+	.byte	1                       ## DW_CHILDREN_yes
+	.byte	3                       ## DW_AT_name
+	.byte	8                       ## DW_FORM_string
+	.byte	11                      ## DW_AT_byte_size
+	.byte	11                      ## DW_FORM_data1
+	.byte	0, 0
+
+	.byte	5                       ## Abbreviation Code
+	.byte	13                      ## DW_TAG_member
+	.byte	0                       ## DW_CHILDREN_no
+	.byte	3                       ## DW_AT_name
+	.byte	8                       ## DW_FORM_string
+	.byte	73                      ## DW_AT_type
+	.byte	19                      ## DW_FORM_ref4
+	.byte	56                      ## DW_AT_data_member_location
+	.byte	11                      ## DW_FORM_data1
+	.byte	0, 0
+
+	.byte	6                       ## Abbreviation Code
+	.byte	13                      ## DW_TAG_member (static data member, DWARF 4)
+	.byte	0                       ## DW_CHILDREN_no
+	.byte	3                       ## DW_AT_name
+	.byte	8                       ## DW_FORM_string
+	.byte	73                      ## DW_AT_type
+	.byte	19                      ## DW_FORM_ref4
+	.byte	63                      ## DW_AT_external
+	.byte	12                      ## DW_FORM_flag
+	.byte	60                      ## DW_AT_declaration
+	.byte	12                      ## DW_FORM_flag
+	.byte	28                      ## DW_AT_const_value
+	.byte	11                      ## DW_FORM_data1
+	.byte	0, 0
+
+	.byte	7                       ## Abbreviation Code
+	.byte	52                      ## DW_TAG_variable (static data member, DWARF 5)
+	.byte	0                       ## DW_CHILDREN_no
+	.byte	3                       ## DW_AT_name
+	.byte	8                       ## DW_FORM_string
+	.byte	73                      ## DW_AT_type
+	.byte	19                      ## DW_FORM_ref4
+	.byte	63                      ## DW_AT_external
+	.byte	12                      ## DW_FORM_flag
+	.byte	60                      ## DW_AT_declaration
+	.byte	12                      ## DW_FORM_flag
+	.byte	28                      ## DW_AT_const_value
+	.byte	11                      ## DW_FORM_data1
+	.byte	0, 0
+
+	.byte	8                       ## Abbreviation Code
+	.byte	36                      ## DW_TAG_base_type
+	.byte	0                       ## DW_CHILDREN_no
+	.byte	3                       ## DW_AT_name
+	.byte	8                       ## DW_FORM_string
+	.byte	11                      ## DW_AT_byte_size
+	.byte	11                      ## DW_FORM_data1
+	.byte	62                      ## DW_AT_encoding
+	.byte	11                      ## DW_FORM_data1
+	.byte	0, 0
+
+	.byte	9                       ## Abbreviation Code
+	.byte	15                      ## DW_TAG_pointer_type
+	.byte	0                       ## DW_CHILDREN_no
+	.byte	73                      ## DW_AT_type
+	.byte	19                      ## DW_FORM_ref4
+	.byte	0, 0
+
+	.byte	0                       ## EOM(3)
+
+	.section	__DWARF,__debug_info,regular,debug
+Lsection_info:
+## DWARF 4 compile unit: the static data member is a DW_TAG_member.
+Lcu1_begin:
+	.long	Lcu1_end - Lcu1_start   ## Length of Unit
+Lcu1_start:
+	.short	4                       ## DWARF version number
+	.long	0                       ## Offset Into Abbrev. Section
+	.byte	8                       ## Address Size (in bytes)
+
+	.byte	1                       ## Abbrev [1] DW_TAG_compile_unit
+	.asciz	"hand-written"          ## DW_AT_producer
+	.short	0x0004                  ## DW_AT_language (DW_LANG_C_plus_plus)
+	.asciz	"a.cpp"                 ## DW_AT_name
+
+	.byte	2                       ## Abbrev [2] DW_TAG_subprogram
+	.asciz	"useA"                  ## DW_AT_name
+	.asciz	"__Z4useAP1S"           ## DW_AT_linkage_name
+	.quad	LfuncA_begin            ## DW_AT_low_pc
+	.quad	LfuncA_end              ## DW_AT_high_pc
+	.byte	1                       ## DW_AT_external
+
+	.byte	3                       ## Abbrev [3] DW_TAG_formal_parameter
+	.long	Lcu1_s_ptr - Lcu1_begin ## DW_AT_type
+
+	.byte	0                       ## End Of Children Mark (useA)
+
+Lcu1_s:
+	.byte	4                       ## Abbrev [4] DW_TAG_structure_type
+	.asciz	"S"                     ## DW_AT_name
+	.byte	4                       ## DW_AT_byte_size
+
+	.byte	6                       ## Abbrev [6] DW_TAG_member
+	.asciz	"kMask"                 ## DW_AT_name
+	.long	Lcu1_int - Lcu1_begin   ## DW_AT_type
+	.byte	1                       ## DW_AT_external
+	.byte	1                       ## DW_AT_declaration
+	.byte	1                       ## DW_AT_const_value
+
+	.byte	5                       ## Abbrev [5] DW_TAG_member
+	.asciz	"field"                 ## DW_AT_name
+	.long	Lcu1_int - Lcu1_begin   ## DW_AT_type
+	.byte	0                       ## DW_AT_data_member_location
+
+	.byte	0                       ## End Of Children Mark (S)
+
+Lcu1_int:
+	.byte	8                       ## Abbrev [8] DW_TAG_base_type
+	.asciz	"int"                   ## DW_AT_name
+	.byte	4                       ## DW_AT_byte_size
+	.byte	5                       ## DW_AT_encoding (DW_ATE_signed)
+
+Lcu1_s_ptr:
+	.byte	9                       ## Abbrev [9] DW_TAG_pointer_type
+	.long	Lcu1_s - Lcu1_begin     ## DW_AT_type
+
+	.byte	0                       ## End Of Children Mark (CU)
+Lcu1_end:
+
+## DWARF 5 compile unit: the static data member is a DW_TAG_variable.
+Lcu2_begin:
+	.long	Lcu2_end - Lcu2_start   ## Length of Unit
+Lcu2_start:
+	.short	5                       ## DWARF version number
+	.byte	1                       ## DW_UT_compile
+	.byte	8                       ## Address Size (in bytes)
+	.long	0                       ## Offset Into Abbrev. Section
+
+	.byte	1                       ## Abbrev [1] DW_TAG_compile_unit
+	.asciz	"hand-written"          ## DW_AT_producer
+	.short	0x0004                  ## DW_AT_language (DW_LANG_C_plus_plus)
+	.asciz	"b.cpp"                 ## DW_AT_name
+
+	.byte	2                       ## Abbrev [2] DW_TAG_subprogram
+	.asciz	"useB"                  ## DW_AT_name
+	.asciz	"__Z4useBP1S"           ## DW_AT_linkage_name
+	.quad	LfuncB_begin            ## DW_AT_low_pc
+	.quad	LfuncB_end              ## DW_AT_high_pc
+	.byte	1                       ## DW_AT_external
+
+	.byte	3                       ## Abbrev [3] DW_TAG_formal_parameter
+	.long	Lcu2_s_ptr - Lcu2_begin ## DW_AT_type
+
+	.byte	0                       ## End Of Children Mark (useB)
+
+Lcu2_s:
+	.byte	4                       ## Abbrev [4] DW_TAG_structure_type
+	.asciz	"S"                     ## DW_AT_name
+	.byte	4                       ## DW_AT_byte_size
+
+	.byte	7                       ## Abbrev [7] DW_TAG_variable
+	.asciz	"kMask"                 ## DW_AT_name
+	.long	Lcu2_int - Lcu2_begin   ## DW_AT_type
+	.byte	1                       ## DW_AT_external
+	.byte	1                       ## DW_AT_declaration
+	.byte	1                       ## DW_AT_const_value
+
+	.byte	5                       ## Abbrev [5] DW_TAG_member
+	.asciz	"field"                 ## DW_AT_name
+	.long	Lcu2_int - Lcu2_begin   ## DW_AT_type
+	.byte	0                       ## DW_AT_data_member_location
+
+	.byte	0                       ## End Of Children Mark (S)
+
+Lcu2_int:
+	.byte	8                       ## Abbrev [8] DW_TAG_base_type
+	.asciz	"int"                   ## DW_AT_name
+	.byte	4                       ## DW_AT_byte_size
+	.byte	5                       ## DW_AT_encoding (DW_ATE_signed)
+
+Lcu2_s_ptr:
+	.byte	9                       ## Abbrev [9] DW_TAG_pointer_type
+	.long	Lcu2_s - Lcu2_begin     ## DW_AT_type
+
+	.byte	0                       ## End Of Children Mark (CU)
+Lcu2_end:


        


More information about the llvm-commits mailing list