[llvm] [DWARFLinker] Keep address-independent block attributes in the type unit (PR #215932)

Jonas Devlieghere via llvm-commits llvm-commits at lists.llvm.org
Wed Aug 12 18:18:45 PDT 2026


https://github.com/JDevlieghere created https://github.com/llvm/llvm-project/pull/215932

cloneBlockAttr dropped every block and exprloc attribute whose DIE was placed in the artificial type unit. The intent was to discard location expressions that resolve against a single origin compile unit, since a type unit is shared by every unit referencing the type, but the check was incorrectly applied to all block-form attributes.

Clang encodes DW_AT_data_member_location as DW_FORM_block1 at DWARF 2 and as a constant from DWARF 3 onward, so at DWARF 2 every member and inheritance offset of a deduplicated record collapsed to zero. This caused LLDB to crash when trying to build types using the wrong offsets, but that's a separate issue.

Drop only expressions that carry a target address or reference a base type DIE, which are the cases the type unit cannot resolve.

>From 0212ac2cdddb6a628d158338a002ed9c7053aae7 Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Wed, 12 Aug 2026 18:16:47 -0700
Subject: [PATCH] [DWARFLinker] Keep address-independent block attributes in
 the type unit

cloneBlockAttr dropped every block and exprloc attribute whose DIE was
placed in the artificial type unit. The intent was to discard location
expressions that resolve against a single origin compile unit, since a
type unit is shared by every unit referencing the type, but the check
was incorrectly applied to all block-form attributes.

Clang encodes DW_AT_data_member_location as DW_FORM_block1 at DWARF 2
and as a constant from DWARF 3 onward, so at DWARF 2 every member and
inheritance offset of a deduplicated record collapsed to zero. This
caused LLDB to crash when trying to build types using the wrong offsets,
but that's a separate issue.

Drop only expressions that carry a target address or reference a base
type DIE, which are the cases the type unit cannot resolve.
---
 .../Parallel/DIEAttributeCloner.cpp           |  29 +-
 .../dwarf2-member-location.s                  | 261 ++++++++++++++++++
 2 files changed, 287 insertions(+), 3 deletions(-)
 create mode 100644 llvm/test/tools/dsymutil/X86/DWARFLinkerParallel/dwarf2-member-location.s

diff --git a/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.cpp b/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.cpp
index c58d2937f3ed0..f58869d20291c 100644
--- a/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.cpp
+++ b/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.cpp
@@ -549,13 +549,30 @@ size_t DIEAttributeCloner::cloneScalarAttr(
   return Result.second;
 }
 
+static bool expressionDependsOnOriginUnit(const DWARFExpression &Expr) {
+  using Encoding = DWARFExpression::Operation::Encoding;
+
+  for (const DWARFExpression::Operation &Op : Expr) {
+    switch (Op.getCode()) {
+    case dwarf::DW_OP_addr:
+    case dwarf::DW_OP_addrx:
+    case dwarf::DW_OP_constx:
+      return true;
+    default:
+      break;
+    }
+
+    if (llvm::is_contained(Op.getDescription().Op, Encoding::BaseTypeRef))
+      return true;
+  }
+
+  return false;
+}
+
 size_t DIEAttributeCloner::cloneBlockAttr(
     const DWARFFormValue &Val,
     const DWARFAbbreviationDeclaration::AttributeSpec &AttrSpec) {
 
-  if (OutUnit.isTypeUnit())
-    return 0;
-
   size_t NumberOfPatchesAtStart = PatchesOffsets.size();
 
   // If the block is a DWARF Expression, clone it into the temporary
@@ -569,6 +586,12 @@ size_t DIEAttributeCloner::cloneBlockAttr(
     DWARFExpression Expr(Data, InUnit.getOrigUnit().getAddressByteSize(),
                          InUnit.getFormParams().Format);
 
+    // A type unit is shared by every compile unit that references the type, so
+    // an expression resolving against one origin unit has no single correct
+    // value there.
+    if (OutUnit.isTypeUnit() && expressionDependsOnOriginUnit(Expr))
+      return 0;
+
     InUnit.cloneDieAttrExpression(Expr, Buffer, DebugInfoOutputSection,
                                   VarAddressAdjustment, PatchesOffsets);
     Bytes = Buffer;
diff --git a/llvm/test/tools/dsymutil/X86/DWARFLinkerParallel/dwarf2-member-location.s b/llvm/test/tools/dsymutil/X86/DWARFLinkerParallel/dwarf2-member-location.s
new file mode 100644
index 0000000000000..81d5e5d28a976
--- /dev/null
+++ b/llvm/test/tools/dsymutil/X86/DWARFLinkerParallel/dwarf2-member-location.s
@@ -0,0 +1,261 @@
+# DWARF 2 encodes DW_AT_data_member_location as a location expression rather
+# than a constant, and DW_AT_vtable_elem_location is an expression at every
+# DWARF version. Both must survive placement in the artificial type unit.
+
+# 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: __Z4keepP7Derived, objAddr: 0x0, binAddr: 0x10000, size: 0x1 }' >> %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 \
+# RUN:   | FileCheck %s --check-prefix=PARALLEL
+
+# 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 \
+# RUN:   | FileCheck %s --check-prefix=CLASSIC
+
+# The parallel linker moves the deduplicated types into the artificial type unit
+# and the classic linker keeps them in the compile unit, so the two emit them in
+# different orders.
+
+# PARALLEL: DW_AT_name{{.*}}"__artificial_type_unit"
+# PARALLEL: DW_TAG_structure_type
+# PARALLEL:   DW_AT_name{{.*}}"Base"
+# PARALLEL:   DW_TAG_member
+# PARALLEL:     DW_AT_name{{.*}}"first"
+# PARALLEL:     DW_AT_data_member_location{{.*}}(DW_OP_plus_uconst 0x0)
+# PARALLEL:   DW_TAG_member
+# PARALLEL:     DW_AT_name{{.*}}"second"
+# PARALLEL:     DW_AT_data_member_location{{.*}}(DW_OP_plus_uconst 0x4)
+# PARALLEL: DW_TAG_structure_type
+# PARALLEL:   DW_AT_name{{.*}}"Derived"
+# PARALLEL:   DW_TAG_inheritance
+# PARALLEL:     DW_AT_data_member_location{{.*}}(DW_OP_plus_uconst 0x8)
+# PARALLEL:   DW_TAG_member
+# PARALLEL:     DW_AT_name{{.*}}"third"
+# PARALLEL:     DW_AT_data_member_location{{.*}}(DW_OP_plus_uconst 0x10)
+# PARALLEL:   DW_TAG_subprogram
+# PARALLEL:     DW_AT_name{{.*}}"virt"
+# PARALLEL:     DW_AT_vtable_elem_location{{.*}}(DW_OP_constu 0x0)
+
+# CLASSIC: DW_TAG_structure_type
+# CLASSIC:   DW_AT_name{{.*}}"Derived"
+# CLASSIC:   DW_TAG_inheritance
+# CLASSIC:     DW_AT_data_member_location{{.*}}(DW_OP_plus_uconst 0x8)
+# CLASSIC:   DW_TAG_member
+# CLASSIC:     DW_AT_name{{.*}}"third"
+# CLASSIC:     DW_AT_data_member_location{{.*}}(DW_OP_plus_uconst 0x10)
+# CLASSIC:   DW_TAG_subprogram
+# CLASSIC:     DW_AT_name{{.*}}"virt"
+# CLASSIC:     DW_AT_vtable_elem_location{{.*}}(DW_OP_constu 0x0)
+# CLASSIC: DW_TAG_structure_type
+# CLASSIC:   DW_AT_name{{.*}}"Base"
+# CLASSIC:   DW_TAG_member
+# CLASSIC:     DW_AT_name{{.*}}"first"
+# CLASSIC:     DW_AT_data_member_location{{.*}}(DW_OP_plus_uconst 0x0)
+# CLASSIC:   DW_TAG_member
+# CLASSIC:     DW_AT_name{{.*}}"second"
+# CLASSIC:     DW_AT_data_member_location{{.*}}(DW_OP_plus_uconst 0x4)
+
+	.section	__TEXT,__text,regular,pure_instructions
+	.globl	__Z4keepP7Derived
+__Z4keepP7Derived:
+Lfunc_begin0:
+	retq
+Lfunc_end0:
+
+	.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	0x87, 0x40              ## DW_AT_MIPS_linkage_name (0x2007)
+	.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	28                      ## DW_TAG_inheritance
+	.byte	0                       ## DW_CHILDREN_no
+	.byte	73                      ## DW_AT_type
+	.byte	19                      ## DW_FORM_ref4
+	.byte	56                      ## DW_AT_data_member_location
+	.byte	10                      ## DW_FORM_block1
+	.byte	0, 0
+
+	.byte	6                       ## 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	10                      ## DW_FORM_block1
+	.byte	0, 0
+
+	.byte	7                       ## Abbreviation Code
+	.byte	46                      ## DW_TAG_subprogram
+	.byte	0                       ## DW_CHILDREN_no
+	.byte	3                       ## DW_AT_name
+	.byte	8                       ## DW_FORM_string
+	.byte	0x87, 0x40              ## DW_AT_MIPS_linkage_name (0x2007)
+	.byte	8                       ## DW_FORM_string
+	.byte	76                      ## DW_AT_virtuality
+	.byte	11                      ## DW_FORM_data1
+	.byte	77                      ## DW_AT_vtable_elem_location
+	.byte	10                      ## DW_FORM_block1
+	.byte	60                      ## DW_AT_declaration
+	.byte	12                      ## DW_FORM_flag
+	.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:
+	.long	Lcu_end - Lcu_start     ## Length of Unit
+Lcu_start:
+	.short	2                       ## 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	"dwarf2-member-location.cpp" ## DW_AT_name
+
+	.byte	2                       ## Abbrev [2] DW_TAG_subprogram
+	.asciz	"keep"                  ## DW_AT_name
+	.asciz	"__Z4keepP7Derived"     ## DW_AT_MIPS_linkage_name
+	.quad	Lfunc_begin0            ## DW_AT_low_pc
+	.quad	Lfunc_end0              ## DW_AT_high_pc
+	.byte	1                       ## DW_AT_external
+
+	.byte	3                       ## Abbrev [3] DW_TAG_formal_parameter
+	.long	Lderived_ptr - Lsection_info ## DW_AT_type
+
+	.byte	0                       ## End Of Children Mark (keep)
+
+Lderived:
+	.byte	4                       ## Abbrev [4] DW_TAG_structure_type
+	.asciz	"Derived"               ## DW_AT_name
+	.byte	0x18                    ## DW_AT_byte_size
+
+	.byte	5                       ## Abbrev [5] DW_TAG_inheritance
+	.long	Lbase - Lsection_info   ## DW_AT_type
+	.byte	2                       ## DW_AT_data_member_location length
+	.byte	0x23                    ##   DW_OP_plus_uconst
+	.byte	0x08                    ##   0x8
+
+	.byte	6                       ## Abbrev [6] DW_TAG_member
+	.asciz	"third"                 ## DW_AT_name
+	.long	Lint - Lsection_info    ## DW_AT_type
+	.byte	2                       ## DW_AT_data_member_location length
+	.byte	0x23                    ##   DW_OP_plus_uconst
+	.byte	0x10                    ##   0x10
+
+	.byte	7                       ## Abbrev [7] DW_TAG_subprogram
+	.asciz	"virt"                  ## DW_AT_name
+	.asciz	"__ZN7Derived4virtEv"   ## DW_AT_MIPS_linkage_name
+	.byte	1                       ## DW_AT_virtuality (DW_VIRTUALITY_virtual)
+	.byte	2                       ## DW_AT_vtable_elem_location length
+	.byte	0x10                    ##   DW_OP_constu
+	.byte	0x00                    ##   0x0
+	.byte	1                       ## DW_AT_declaration
+
+	.byte	0                       ## End Of Children Mark (Derived)
+
+Lbase:
+	.byte	4                       ## Abbrev [4] DW_TAG_structure_type
+	.asciz	"Base"                  ## DW_AT_name
+	.byte	0x08                    ## DW_AT_byte_size
+
+	.byte	6                       ## Abbrev [6] DW_TAG_member
+	.asciz	"first"                 ## DW_AT_name
+	.long	Lint - Lsection_info    ## DW_AT_type
+	.byte	2                       ## DW_AT_data_member_location length
+	.byte	0x23                    ##   DW_OP_plus_uconst
+	.byte	0x00                    ##   0x0
+
+	.byte	6                       ## Abbrev [6] DW_TAG_member
+	.asciz	"second"                ## DW_AT_name
+	.long	Lint - Lsection_info    ## DW_AT_type
+	.byte	2                       ## DW_AT_data_member_location length
+	.byte	0x23                    ##   DW_OP_plus_uconst
+	.byte	0x04                    ##   0x4
+
+	.byte	0                       ## End Of Children Mark (Base)
+
+Lint:
+	.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)
+
+Lderived_ptr:
+	.byte	9                       ## Abbrev [9] DW_TAG_pointer_type
+	.long	Lderived - Lsection_info ## DW_AT_type
+
+	.byte	0                       ## End Of Children Mark (CU)
+Lcu_end:



More information about the llvm-commits mailing list