[Lldb-commits] [clang] [lldb] [clang][RecordLayoutBuilder] Be stricter about inferring packed-ness in ExternalLayouts (PR #97443)

Michael Buch via lldb-commits lldb-commits at lists.llvm.org
Wed Oct 2 07:19:15 PDT 2024


https://github.com/Michael137 updated https://github.com/llvm/llvm-project/pull/97443

>From 3a718c75d0458b7aece72f2ba8e5aa5a68815237 Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Tue, 2 Jul 2024 18:43:34 +0200
Subject: [PATCH 1/3] [clang][RecordLayoutBuilder] Be stricter about inferring
 packed-ness in ExternalLayouts

This patch is motivated by the LLDB support required for:
https://github.com/llvm/llvm-project/issues/93069

In the presence of `[[no_unique_address]]`, LLDB may ask Clang
to lay out types with overlapping field offsets. Because we don't
have attributes such as `packed` or `no_unique_address` in the LLDB
AST, the `RecordLayoutBuilder` supports an `InferAlignment` mode, which,
in the past, attempted to detect layouts which came from packed
structures in order to provide a conservative estimate of alignment for
it (since `DW_AT_alignment` isn't emitted unless explicitly changed with
`alignas`, etc.).

However, in the presence of overlapping fields due to `no_unique_address`,
`InferAlignment` would set the alignment of structures to `1` for which
that's incorrect. This poses issues in some LLDB formatters that
synthesize new Clang types and rely on the layout builder to get the
`FieldOffset` of structures right that we did have DWARF offsets for.
The result of this is that if we get the alignment wrong, LLDB reads
out garbage data from the wrong field offsets.

There are a couple of solutions to this that we considered:
1. Make LLDB formatters not do the above, and make them more robust
   to inaccurate alignment.
2. Remove `InferAlignment` entirely and rely on Clang emitting
   `DW_AT_alignment` for packed structures.
3. Remove `InferAlignment` and detect packedness from within LLDB.
4. Make the `InferAlignment` logic account for overlapping fields.

Option (1) turned out quite hairy and it's not clear we can achieve
this with the tools available for certain STL formatters (particularly
`std::map`). But I would still very much like to simplify this if we
can.

Option (2) wouldn't help with GCC-compiled binaries, and if we can
get away with LLDB not needing the alignment, then we wouldn't need
to increase debug-info size.

Option (3), AFAICT, would require us to reimplement some of the layout
logic in the layout builder. Would be great if we can avoid this added
complexity.

Option (4) seemed like the best option in the interim. As part of this
change I also removed one of the `InferAlignment` blocks. The test-cases
associated with this code-path pass regardless, and from the description
of the change that introduced it it's not clear why specifically the
base offsets would influence the `Alignment` field, and how it would
imply packedness. But happy to be proven wrong. Ultimately it would be
great if we can get rid of the `InferAlignment` infrastructure and
support our use-cases in LLDB or DWARF instead.
---
 clang/lib/AST/RecordLayoutBuilder.cpp         | 34 +++++++++----------
 .../DWARF/no_unique_address-alignment.cpp     |  2 --
 .../no_unique_address-base-alignment.cpp      |  2 --
 3 files changed, 17 insertions(+), 21 deletions(-)

diff --git a/clang/lib/AST/RecordLayoutBuilder.cpp b/clang/lib/AST/RecordLayoutBuilder.cpp
index d9bf62c2bbb04a..8dbf69c310cbb4 100644
--- a/clang/lib/AST/RecordLayoutBuilder.cpp
+++ b/clang/lib/AST/RecordLayoutBuilder.cpp
@@ -802,7 +802,8 @@ class ItaniumRecordLayoutBuilder {
   /// \param Field The field whose offset is being queried.
   /// \param ComputedOffset The offset that we've computed for this field.
   uint64_t updateExternalFieldOffset(const FieldDecl *Field,
-                                     uint64_t ComputedOffset);
+                                     uint64_t ComputedOffset,
+                                     uint64_t PreviousOffset);
 
   void CheckFieldPadding(uint64_t Offset, uint64_t UnpaddedOffset,
                           uint64_t UnpackedOffset, unsigned UnpackedAlign,
@@ -1296,13 +1297,6 @@ ItaniumRecordLayoutBuilder::LayoutBase(const BaseSubobjectInfo *Base) {
     bool Allowed = EmptySubobjects->CanPlaceBaseAtOffset(Base, Offset);
     (void)Allowed;
     assert(Allowed && "Base subobject externally placed at overlapping offset");
-
-    if (InferAlignment && Offset < getDataSize().alignTo(AlignTo)) {
-      // The externally-supplied base offset is before the base offset we
-      // computed. Assume that the structure is packed.
-      Alignment = CharUnits::One();
-      InferAlignment = false;
-    }
   }
 
   if (!Base->Class->isEmpty()) {
@@ -1770,7 +1764,8 @@ void ItaniumRecordLayoutBuilder::LayoutBitField(const FieldDecl *D) {
   // If we're using external layout, give the external layout a chance
   // to override this information.
   if (UseExternalLayout)
-    FieldOffset = updateExternalFieldOffset(D, FieldOffset);
+    FieldOffset = updateExternalFieldOffset(
+        D, FieldOffset, FieldOffsets.empty() ? 0 : FieldOffsets.back());
 
   // Okay, place the bitfield at the calculated offset.
   FieldOffsets.push_back(FieldOffset);
@@ -2063,8 +2058,9 @@ void ItaniumRecordLayoutBuilder::LayoutField(const FieldDecl *D,
   UnpackedFieldOffset = UnpackedFieldOffset.alignTo(UnpackedFieldAlign);
 
   if (UseExternalLayout) {
-    FieldOffset = Context.toCharUnitsFromBits(
-        updateExternalFieldOffset(D, Context.toBits(FieldOffset)));
+    FieldOffset = Context.toCharUnitsFromBits(updateExternalFieldOffset(
+        D, Context.toBits(FieldOffset),
+        FieldOffsets.empty() ? 0 : FieldOffsets.back()));
 
     if (!IsUnion && EmptySubobjects) {
       // Record the fact that we're placing a field at this offset.
@@ -2250,14 +2246,18 @@ void ItaniumRecordLayoutBuilder::UpdateAlignment(
   }
 }
 
-uint64_t
-ItaniumRecordLayoutBuilder::updateExternalFieldOffset(const FieldDecl *Field,
-                                                      uint64_t ComputedOffset) {
+uint64_t ItaniumRecordLayoutBuilder::updateExternalFieldOffset(
+    const FieldDecl *Field, uint64_t ComputedOffset, uint64_t PreviousOffset) {
   uint64_t ExternalFieldOffset = External.getExternalFieldOffset(Field);
 
-  if (InferAlignment && ExternalFieldOffset < ComputedOffset) {
-    // The externally-supplied field offset is before the field offset we
-    // computed. Assume that the structure is packed.
+  // If the externally-supplied field offset is before the field offset we
+  // computed. Check against the previous field offset to make sure we don't
+  // misinterpret overlapping fields as packedness of the structure.
+  const bool assume_packed = ExternalFieldOffset > 0 &&
+                             ExternalFieldOffset < ComputedOffset &&
+                             ExternalFieldOffset > PreviousOffset;
+
+  if (InferAlignment && assume_packed) {
     Alignment = CharUnits::One();
     PreferredAlignment = CharUnits::One();
     InferAlignment = false;
diff --git a/lldb/test/Shell/SymbolFile/DWARF/no_unique_address-alignment.cpp b/lldb/test/Shell/SymbolFile/DWARF/no_unique_address-alignment.cpp
index 1488199a3ad2d3..ecefa664747727 100644
--- a/lldb/test/Shell/SymbolFile/DWARF/no_unique_address-alignment.cpp
+++ b/lldb/test/Shell/SymbolFile/DWARF/no_unique_address-alignment.cpp
@@ -1,5 +1,3 @@
-// XFAIL: *
-
 // RUN: %clangxx_host -gdwarf -o %t %s
 // RUN: %lldb %t \
 // RUN:   -o "expr alignof(OverlappingFields)" \
diff --git a/lldb/test/Shell/SymbolFile/DWARF/no_unique_address-base-alignment.cpp b/lldb/test/Shell/SymbolFile/DWARF/no_unique_address-base-alignment.cpp
index 15d8de0e3ee988..9f574f9846e358 100644
--- a/lldb/test/Shell/SymbolFile/DWARF/no_unique_address-base-alignment.cpp
+++ b/lldb/test/Shell/SymbolFile/DWARF/no_unique_address-base-alignment.cpp
@@ -1,5 +1,3 @@
-// XFAIL: *
-
 // RUN: %clangxx_host -gdwarf -o %t %s
 // RUN: %lldb %t \
 // RUN:   -o "expr alignof(OverlappingDerived)" \

>From 7d54c5ce1a7a028e41d6756869c785a49c645356 Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Wed, 2 Oct 2024 13:42:18 +0100
Subject: [PATCH 2/3] fixup! ignore empty fields instead of checking overlap

---
 clang/lib/AST/RecordLayoutBuilder.cpp         | 37 ++++++++++++-------
 .../no_unique_address-packed-alignment.cpp    | 23 ++++++++++++
 2 files changed, 47 insertions(+), 13 deletions(-)
 create mode 100644 lldb/test/Shell/SymbolFile/DWARF/no_unique_address-packed-alignment.cpp

diff --git a/clang/lib/AST/RecordLayoutBuilder.cpp b/clang/lib/AST/RecordLayoutBuilder.cpp
index 8dbf69c310cbb4..76e5007357e4de 100644
--- a/clang/lib/AST/RecordLayoutBuilder.cpp
+++ b/clang/lib/AST/RecordLayoutBuilder.cpp
@@ -802,8 +802,7 @@ class ItaniumRecordLayoutBuilder {
   /// \param Field The field whose offset is being queried.
   /// \param ComputedOffset The offset that we've computed for this field.
   uint64_t updateExternalFieldOffset(const FieldDecl *Field,
-                                     uint64_t ComputedOffset,
-                                     uint64_t PreviousOffset);
+                                     uint64_t ComputedOffset);
 
   void CheckFieldPadding(uint64_t Offset, uint64_t UnpaddedOffset,
                           uint64_t UnpackedOffset, unsigned UnpackedAlign,
@@ -1764,8 +1763,7 @@ void ItaniumRecordLayoutBuilder::LayoutBitField(const FieldDecl *D) {
   // If we're using external layout, give the external layout a chance
   // to override this information.
   if (UseExternalLayout)
-    FieldOffset = updateExternalFieldOffset(
-        D, FieldOffset, FieldOffsets.empty() ? 0 : FieldOffsets.back());
+    FieldOffset = updateExternalFieldOffset(D, FieldOffset);
 
   // Okay, place the bitfield at the calculated offset.
   FieldOffsets.push_back(FieldOffset);
@@ -2058,9 +2056,8 @@ void ItaniumRecordLayoutBuilder::LayoutField(const FieldDecl *D,
   UnpackedFieldOffset = UnpackedFieldOffset.alignTo(UnpackedFieldAlign);
 
   if (UseExternalLayout) {
-    FieldOffset = Context.toCharUnitsFromBits(updateExternalFieldOffset(
-        D, Context.toBits(FieldOffset),
-        FieldOffsets.empty() ? 0 : FieldOffsets.back()));
+    FieldOffset = Context.toCharUnitsFromBits(
+        updateExternalFieldOffset(D, Context.toBits(FieldOffset)));
 
     if (!IsUnion && EmptySubobjects) {
       // Record the fact that we're placing a field at this offset.
@@ -2246,16 +2243,30 @@ void ItaniumRecordLayoutBuilder::UpdateAlignment(
   }
 }
 
-uint64_t ItaniumRecordLayoutBuilder::updateExternalFieldOffset(
-    const FieldDecl *Field, uint64_t ComputedOffset, uint64_t PreviousOffset) {
+uint64_t
+ItaniumRecordLayoutBuilder::updateExternalFieldOffset(const FieldDecl *Field,
+                                                      uint64_t ComputedOffset) {
   uint64_t ExternalFieldOffset = External.getExternalFieldOffset(Field);
 
-  // If the externally-supplied field offset is before the field offset we
-  // computed. Check against the previous field offset to make sure we don't
-  // misinterpret overlapping fields as packedness of the structure.
+  // DWARF doesn't tell us whether a structure was declared as packed.
+  // So we try to figure out if the supplied Field is at a packed offset
+  // (i.e., the externally-supplied offset is less than the layout builder
+  // expected).
+  //
+  // There are cases where fields are placed at overlapping offsets (e.g.,
+  // as a result of [[no_unique_address]]). In those cases we don't want
+  // to incorrectly deduce that they are placed at packed offsets. Hence,
+  // ignore empty fields (which are the only fields that can overlap).
+  //
+  // FIXME: emit enough information in DWARF to get rid of InferAlignment.
+  //
+  CXXRecordDecl *CXX = nullptr;
+  if (auto *RT = dyn_cast<RecordType>(Field->getType()))
+    CXX = RT->getAsCXXRecordDecl();
+
   const bool assume_packed = ExternalFieldOffset > 0 &&
                              ExternalFieldOffset < ComputedOffset &&
-                             ExternalFieldOffset > PreviousOffset;
+                             !(CXX && CXX->isEmpty());
 
   if (InferAlignment && assume_packed) {
     Alignment = CharUnits::One();
diff --git a/lldb/test/Shell/SymbolFile/DWARF/no_unique_address-packed-alignment.cpp b/lldb/test/Shell/SymbolFile/DWARF/no_unique_address-packed-alignment.cpp
new file mode 100644
index 00000000000000..80fb699ddafa3f
--- /dev/null
+++ b/lldb/test/Shell/SymbolFile/DWARF/no_unique_address-packed-alignment.cpp
@@ -0,0 +1,23 @@
+// RUN: %clangxx_host -gdwarf -o %t %s
+// RUN: %lldb %t \
+// RUN:   -o "expr alignof(PackedNUA)" \
+// RUN:   -o "expr sizeof(PackedNUA)" \
+// RUN:   -o exit | FileCheck %s
+
+struct Empty {};
+struct __attribute((packed)) PackedNUA {
+  [[no_unique_address]] Empty a,b,c,d;
+  char x;
+  int y;
+};
+static_assert(alignof(PackedNUA) == 1);
+static_assert(sizeof(PackedNUA) == 5);
+
+PackedNUA packed;
+
+// CHECK:      (lldb) expr alignof(PackedNUA)
+// CHECK-NEXT: ${{.*}} = 1
+// CHECK:      (lldb) expr sizeof(PackedNUA)
+// CHECK-NEXT: ${{.*}} = 5
+
+int main() { PackedNUA d; }

>From 5d4634f0d05c003f16d0ba21684393008e8c44dc Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Wed, 2 Oct 2024 15:18:59 +0100
Subject: [PATCH 3/3] fixup! clang-format

---
 .../SymbolFile/DWARF/no_unique_address-packed-alignment.cpp     | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lldb/test/Shell/SymbolFile/DWARF/no_unique_address-packed-alignment.cpp b/lldb/test/Shell/SymbolFile/DWARF/no_unique_address-packed-alignment.cpp
index 80fb699ddafa3f..e47dab45ff7b79 100644
--- a/lldb/test/Shell/SymbolFile/DWARF/no_unique_address-packed-alignment.cpp
+++ b/lldb/test/Shell/SymbolFile/DWARF/no_unique_address-packed-alignment.cpp
@@ -6,7 +6,7 @@
 
 struct Empty {};
 struct __attribute((packed)) PackedNUA {
-  [[no_unique_address]] Empty a,b,c,d;
+  [[no_unique_address]] Empty a, b, c, d;
   char x;
   int y;
 };



More information about the lldb-commits mailing list