Skip to content

Commit 38b7837

Browse files
committed
[clang][RecordLayoutBuilder] Be stricter about inferring packed-ness in ExternalLayouts
This patch is motivated by the LLDB support required for: #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.
1 parent 159f253 commit 38b7837

File tree

3 files changed

+17
-21
lines changed

3 files changed

+17
-21
lines changed

clang/lib/AST/RecordLayoutBuilder.cpp

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -802,7 +802,8 @@ class ItaniumRecordLayoutBuilder {
802802
/// \param Field The field whose offset is being queried.
803803
/// \param ComputedOffset The offset that we've computed for this field.
804804
uint64_t updateExternalFieldOffset(const FieldDecl *Field,
805-
uint64_t ComputedOffset);
805+
uint64_t ComputedOffset,
806+
uint64_t PreviousOffset);
806807

807808
void CheckFieldPadding(uint64_t Offset, uint64_t UnpaddedOffset,
808809
uint64_t UnpackedOffset, unsigned UnpackedAlign,
@@ -1296,13 +1297,6 @@ ItaniumRecordLayoutBuilder::LayoutBase(const BaseSubobjectInfo *Base) {
12961297
bool Allowed = EmptySubobjects->CanPlaceBaseAtOffset(Base, Offset);
12971298
(void)Allowed;
12981299
assert(Allowed && "Base subobject externally placed at overlapping offset");
1299-
1300-
if (InferAlignment && Offset < getDataSize().alignTo(AlignTo)) {
1301-
// The externally-supplied base offset is before the base offset we
1302-
// computed. Assume that the structure is packed.
1303-
Alignment = CharUnits::One();
1304-
InferAlignment = false;
1305-
}
13061300
}
13071301

13081302
if (!Base->Class->isEmpty()) {
@@ -1770,7 +1764,8 @@ void ItaniumRecordLayoutBuilder::LayoutBitField(const FieldDecl *D) {
17701764
// If we're using external layout, give the external layout a chance
17711765
// to override this information.
17721766
if (UseExternalLayout)
1773-
FieldOffset = updateExternalFieldOffset(D, FieldOffset);
1767+
FieldOffset = updateExternalFieldOffset(
1768+
D, FieldOffset, FieldOffsets.empty() ? 0 : FieldOffsets.back());
17741769

17751770
// Okay, place the bitfield at the calculated offset.
17761771
FieldOffsets.push_back(FieldOffset);
@@ -2063,8 +2058,9 @@ void ItaniumRecordLayoutBuilder::LayoutField(const FieldDecl *D,
20632058
UnpackedFieldOffset = UnpackedFieldOffset.alignTo(UnpackedFieldAlign);
20642059

20652060
if (UseExternalLayout) {
2066-
FieldOffset = Context.toCharUnitsFromBits(
2067-
updateExternalFieldOffset(D, Context.toBits(FieldOffset)));
2061+
FieldOffset = Context.toCharUnitsFromBits(updateExternalFieldOffset(
2062+
D, Context.toBits(FieldOffset),
2063+
FieldOffsets.empty() ? 0 : FieldOffsets.back()));
20682064

20692065
if (!IsUnion && EmptySubobjects) {
20702066
// Record the fact that we're placing a field at this offset.
@@ -2250,14 +2246,18 @@ void ItaniumRecordLayoutBuilder::UpdateAlignment(
22502246
}
22512247
}
22522248

2253-
uint64_t
2254-
ItaniumRecordLayoutBuilder::updateExternalFieldOffset(const FieldDecl *Field,
2255-
uint64_t ComputedOffset) {
2249+
uint64_t ItaniumRecordLayoutBuilder::updateExternalFieldOffset(
2250+
const FieldDecl *Field, uint64_t ComputedOffset, uint64_t PreviousOffset) {
22562251
uint64_t ExternalFieldOffset = External.getExternalFieldOffset(Field);
22572252

2258-
if (InferAlignment && ExternalFieldOffset < ComputedOffset) {
2259-
// The externally-supplied field offset is before the field offset we
2260-
// computed. Assume that the structure is packed.
2253+
// If the externally-supplied field offset is before the field offset we
2254+
// computed. Check against the previous field offset to make sure we don't
2255+
// misinterpret overlapping fields as packedness of the structure.
2256+
const bool assume_packed = ExternalFieldOffset > 0 &&
2257+
ExternalFieldOffset < ComputedOffset &&
2258+
ExternalFieldOffset > PreviousOffset;
2259+
2260+
if (InferAlignment && assume_packed) {
22612261
Alignment = CharUnits::One();
22622262
PreferredAlignment = CharUnits::One();
22632263
InferAlignment = false;

lldb/test/Shell/SymbolFile/DWARF/x86/no_unique_address-alignment.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
// XFAIL: *
2-
31
// RUN: %clang --target=x86_64-apple-macosx -c -gdwarf -o %t %s
42
// RUN: %lldb %t \
53
// RUN: -o "expr alignof(OverlappingFields)" \

lldb/test/Shell/SymbolFile/DWARF/x86/no_unique_address-base-alignment.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
// XFAIL: *
2-
31
// RUN: %clang --target=x86_64-apple-macosx -c -gdwarf -o %t %s
42
// RUN: %lldb %t \
53
// RUN: -o "expr alignof(OverlappingDerived)" \

0 commit comments

Comments
 (0)