[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