diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -1794,6 +1794,13 @@ let SimpleHandler = 1; } +def NoUniqueAddressMSVC : InheritableAttr, TargetSpecificAttr { + let Spellings = [CXX11<"msvc", "no_unique_address", 201803>]; + let Subjects = SubjectList<[NonBitField], ErrorDiag>; + let Documentation = [NoUniqueAddressDocs]; + let SimpleHandler = 1; +} + def ReturnsTwice : InheritableAttr { let Spellings = [GCC<"returns_twice">]; let Subjects = SubjectList<[Function]>; diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -1405,6 +1405,9 @@ ``[[no_unique_address]]`` is a standard C++20 attribute. Clang supports its use in C++11 onwards. + + For Windows targets, ``[[no_unique_address]]`` is ignored, but there is a + similar attribute spelled ``[[msvc::no_unique_address]]``. }]; } diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -4486,7 +4486,7 @@ // C++2a [intro.object]p7: // An object has nonzero size if it // -- is not a potentially-overlapping subobject, or - if (!hasAttr()) + if (!(hasAttr() || hasAttr())) return false; // -- is not of class type, or @@ -4505,6 +4505,13 @@ if (!CXXRD->isEmpty()) return false; + if (Ctx.getTargetInfo().getCXXABI().isMicrosoft() && CXXRD->isUnion()) { + // Unions containing structs aren't zero sized? + for (const FieldDecl *Field : CXXRD->fields()) + if (Field->getType()->getAsCXXRecordDecl()) + return false; + } + // Otherwise, [...] the circumstances under which the object has zero size // are implementation-defined. // FIXME: This might be Itanium ABI specific; we don't yet know what the MS @@ -4513,7 +4520,9 @@ } bool FieldDecl::isPotentiallyOverlapping() const { - return hasAttr() && getType()->getAsCXXRecordDecl(); + return (hasAttr() || + hasAttr()) && + getType()->getAsCXXRecordDecl(); } unsigned FieldDecl::getFieldIndex() const { diff --git a/clang/lib/AST/RecordLayoutBuilder.cpp b/clang/lib/AST/RecordLayoutBuilder.cpp --- a/clang/lib/AST/RecordLayoutBuilder.cpp +++ b/clang/lib/AST/RecordLayoutBuilder.cpp @@ -474,7 +474,9 @@ // We are able to place the member variable at this offset. // Make sure to update the empty field subobject map. - UpdateEmptyFieldSubobjects(FD, Offset, FD->hasAttr()); + bool PlacingOverlappingField = FD->hasAttr() || + FD->hasAttr(); + UpdateEmptyFieldSubobjects(FD, Offset, PlacingOverlappingField); return true; } @@ -2545,7 +2547,10 @@ CharUnits Alignment; }; typedef llvm::DenseMap BaseOffsetsMapTy; - MicrosoftRecordLayoutBuilder(const ASTContext &Context) : Context(Context) {} + MicrosoftRecordLayoutBuilder(const ASTContext &Context, + EmptySubobjectMap *EmptySubobjects) + : Context(Context), EmptySubobjects(EmptySubobjects) {} + private: MicrosoftRecordLayoutBuilder(const MicrosoftRecordLayoutBuilder &) = delete; void operator=(const MicrosoftRecordLayoutBuilder &) = delete; @@ -2562,7 +2567,11 @@ void layoutNonVirtualBase(const CXXRecordDecl *RD, const CXXRecordDecl *BaseDecl, const ASTRecordLayout &BaseLayout, - const ASTRecordLayout *&PreviousBaseLayout); + const ASTRecordLayout *&PreviousBaseLayout, + BaseSubobjectInfo *Base); + BaseSubobjectInfo *computeBaseSubobjectInfo(const CXXRecordDecl *RD, + bool IsVirtual, + BaseSubobjectInfo *Derived); void injectVFPtr(const CXXRecordDecl *RD); void injectVBPtr(const CXXRecordDecl *RD); /// Lays out the fields of the record. Also rounds size up to @@ -2595,6 +2604,12 @@ llvm::SmallPtrSetImpl &HasVtorDispSet, const CXXRecordDecl *RD) const; const ASTContext &Context; + EmptySubobjectMap *EmptySubobjects; + llvm::SpecificBumpPtrAllocator BaseSubobjectInfoAllocator; + typedef llvm::DenseMap + BaseSubobjectInfoMapTy; + BaseSubobjectInfoMapTy VirtualBaseInfo; + /// The size of the record being laid out. CharUnits Size; /// The non-virtual size of the record layout. @@ -2818,6 +2833,8 @@ // Iterate through the bases and lay out the non-virtual ones. for (const CXXBaseSpecifier &Base : RD->bases()) { const CXXRecordDecl *BaseDecl = Base.getType()->getAsCXXRecordDecl(); + BaseSubobjectInfo *Info = + computeBaseSubobjectInfo(BaseDecl, Base.isVirtual(), nullptr); HasPolymorphicBaseClass |= BaseDecl->isPolymorphic(); const ASTRecordLayout &BaseLayout = Context.getASTRecordLayout(BaseDecl); // Mark and skip virtual bases. @@ -2839,7 +2856,7 @@ LeadsWithZeroSizedBase = BaseLayout.leadsWithZeroSizedBase(); } // Lay out the base. - layoutNonVirtualBase(RD, BaseDecl, BaseLayout, PreviousBaseLayout); + layoutNonVirtualBase(RD, BaseDecl, BaseLayout, PreviousBaseLayout, Info); } // Figure out if we need a fresh VFPtr for this class. if (RD->isPolymorphic()) { @@ -2880,7 +2897,9 @@ LeadsWithZeroSizedBase = BaseLayout.leadsWithZeroSizedBase(); } // Lay out the base. - layoutNonVirtualBase(RD, BaseDecl, BaseLayout, PreviousBaseLayout); + BaseSubobjectInfo *Info = + computeBaseSubobjectInfo(BaseDecl, Base.isVirtual(), nullptr); + layoutNonVirtualBase(RD, BaseDecl, BaseLayout, PreviousBaseLayout, Info); VBPtrOffset = Bases[BaseDecl] + BaseLayout.getNonVirtualSize(); } // Set our VBPtroffset if we know it at this point. @@ -2908,10 +2927,9 @@ } void MicrosoftRecordLayoutBuilder::layoutNonVirtualBase( - const CXXRecordDecl *RD, - const CXXRecordDecl *BaseDecl, + const CXXRecordDecl *RD, const CXXRecordDecl *BaseDecl, const ASTRecordLayout &BaseLayout, - const ASTRecordLayout *&PreviousBaseLayout) { + const ASTRecordLayout *&PreviousBaseLayout, BaseSubobjectInfo *Base) { // Insert padding between two bases if the left first one is zero sized or // contains a zero sized subobject and the right is zero sized or one leads // with a zero sized base. @@ -2938,13 +2956,93 @@ } else { // Otherwise, lay the base out at the end of the MDC. BaseOffset = Size = Size.alignTo(Info.Alignment); + while (!EmptySubobjects->CanPlaceBaseAtOffset(Base, BaseOffset)) + BaseOffset += Info.Alignment; } } + Bases.insert(std::make_pair(BaseDecl, BaseOffset)); Size += BaseLayout.getNonVirtualSize(); + DataSize = Size; PreviousBaseLayout = &BaseLayout; } +BaseSubobjectInfo *MicrosoftRecordLayoutBuilder::computeBaseSubobjectInfo( + const CXXRecordDecl *RD, bool IsVirtual, BaseSubobjectInfo *Derived) { + BaseSubobjectInfo *Info; + + if (IsVirtual) { + // Check if we already have info about this virtual base. + BaseSubobjectInfo *&InfoSlot = VirtualBaseInfo[RD]; + if (InfoSlot) { + assert(InfoSlot->Class == RD && "Wrong class for virtual base info!"); + return InfoSlot; + } + + // We don't, create it. + InfoSlot = new (BaseSubobjectInfoAllocator.Allocate()) BaseSubobjectInfo; + Info = InfoSlot; + } else { + Info = new (BaseSubobjectInfoAllocator.Allocate()) BaseSubobjectInfo; + } + + Info->Class = RD; + Info->IsVirtual = IsVirtual; + Info->Derived = nullptr; + Info->PrimaryVirtualBaseInfo = nullptr; + + const CXXRecordDecl *PrimaryVirtualBase = nullptr; + BaseSubobjectInfo *PrimaryVirtualBaseInfo = nullptr; + + // Check if this base has a primary virtual base. + if (RD->getNumVBases()) { + const ASTRecordLayout &Layout = Context.getASTRecordLayout(RD); + if (Layout.isPrimaryBaseVirtual()) { + // This base does have a primary virtual base. + PrimaryVirtualBase = Layout.getPrimaryBase(); + assert(PrimaryVirtualBase && "Didn't have a primary virtual base!"); + + // Now check if we have base subobject info about this primary base. + PrimaryVirtualBaseInfo = VirtualBaseInfo.lookup(PrimaryVirtualBase); + + if (PrimaryVirtualBaseInfo) { + if (PrimaryVirtualBaseInfo->Derived) { + // We did have info about this primary base, and it turns out that it + // has already been claimed as a primary virtual base for another + // base. + PrimaryVirtualBase = nullptr; + } else { + // We can claim this base as our primary base. + Info->PrimaryVirtualBaseInfo = PrimaryVirtualBaseInfo; + PrimaryVirtualBaseInfo->Derived = Info; + } + } + } + } + + // Now go through all direct bases. + for (const auto &I : RD->bases()) { + bool IsVirtual = I.isVirtual(); + + const CXXRecordDecl *BaseDecl = I.getType()->getAsCXXRecordDecl(); + + Info->Bases.push_back(computeBaseSubobjectInfo(BaseDecl, IsVirtual, Info)); + } + + if (PrimaryVirtualBase && !PrimaryVirtualBaseInfo) { + // Traversing the bases must have created the base info for our primary + // virtual base. + PrimaryVirtualBaseInfo = VirtualBaseInfo.lookup(PrimaryVirtualBase); + assert(PrimaryVirtualBaseInfo && "Did not create a primary virtual base!"); + + // Claim the primary virtual base as our primary virtual base. + Info->PrimaryVirtualBaseInfo = PrimaryVirtualBaseInfo; + PrimaryVirtualBaseInfo->Derived = Info; + } + + return Info; +} + void MicrosoftRecordLayoutBuilder::layoutFields(const RecordDecl *RD) { LastFieldIsNonZeroWidthBitfield = false; for (const FieldDecl *Field : RD->fields()) @@ -2956,19 +3054,40 @@ layoutBitField(FD); return; } + LastFieldIsNonZeroWidthBitfield = false; ElementInfo Info = getAdjustedElementInfo(FD); Alignment = std::max(Alignment, Info.Alignment); - CharUnits FieldOffset; - if (UseExternalLayout) + + auto *FieldClass = FD->getType()->getAsCXXRecordDecl(); + bool IsOverlappingEmptyField = + FD->isPotentiallyOverlapping() && FieldClass->isEmpty(); + CharUnits FieldOffset = CharUnits::Zero(); + + if (UseExternalLayout) { FieldOffset = Context.toCharUnitsFromBits(External.getExternalFieldOffset(FD)); - else if (IsUnion) + } else if (IsUnion) { FieldOffset = CharUnits::Zero(); - else + } else if (EmptySubobjects) { + if (IsOverlappingEmptyField) { + while (!EmptySubobjects->CanPlaceFieldAtOffset(FD, FieldOffset)) + FieldOffset += Info.Alignment; + } else { + FieldOffset = DataSize.alignTo(Info.Alignment); + } + } else { FieldOffset = Size.alignTo(Info.Alignment); + } + placeFieldAtOffset(FieldOffset); - Size = std::max(Size, FieldOffset + Info.Size); + + if (!IsOverlappingEmptyField) { + DataSize = std::max(DataSize, FieldOffset + Info.Size); + Size = std::max(Size, FieldOffset + Info.Size); + } else { + Size = std::max(Size, FieldOffset + Info.Size); + } } void MicrosoftRecordLayoutBuilder::layoutBitField(const FieldDecl *FD) { @@ -3003,13 +3122,13 @@ Alignment = std::max(Alignment, Info.Alignment); } else if (IsUnion) { placeFieldAtOffset(CharUnits::Zero()); - Size = std::max(Size, Info.Size); + DataSize = Size = std::max(Size, Info.Size); // TODO: Add a Sema warning that MS ignores bitfield alignment in unions. } else { // Allocate a new block of memory and place the bitfield in it. CharUnits FieldOffset = Size.alignTo(Info.Alignment); placeFieldAtOffset(FieldOffset); - Size = FieldOffset + Info.Size; + DataSize = Size = FieldOffset + Info.Size; Alignment = std::max(Alignment, Info.Alignment); RemainingBitsInField = Context.toBits(Info.Size) - Width; } @@ -3029,13 +3148,13 @@ ElementInfo Info = getAdjustedElementInfo(FD); if (IsUnion) { placeFieldAtOffset(CharUnits::Zero()); - Size = std::max(Size, Info.Size); + DataSize = Size = std::max(Size, Info.Size); // TODO: Add a Sema warning that MS ignores bitfield alignment in unions. } else { // Round up the current record size to the field's alignment boundary. CharUnits FieldOffset = Size.alignTo(Info.Alignment); placeFieldAtOffset(FieldOffset); - Size = FieldOffset; + DataSize = Size = FieldOffset; Alignment = std::max(Alignment, Info.Alignment); } } @@ -3103,6 +3222,7 @@ void MicrosoftRecordLayoutBuilder::layoutVirtualBases(const CXXRecordDecl *RD) { if (!HasVBPtr) return; + // Vtordisps are always 4 bytes (even in 64-bit mode) CharUnits VtorDispSize = CharUnits::fromQuantity(4); CharUnits VtorDispAlignment = VtorDispSize; @@ -3136,7 +3256,7 @@ if ((PreviousBaseLayout && PreviousBaseLayout->endsWithZeroSizedObject() && BaseLayout.leadsWithZeroSizedBase() && !recordUsesEBO(RD)) || HasVtordisp) { - Size = Size.alignTo(VtorDispAlignment) + VtorDispSize; + DataSize = Size = Size.alignTo(VtorDispAlignment) + VtorDispSize; Alignment = std::max(VtorDispAlignment, Alignment); } // Insert the virtual base. @@ -3304,8 +3424,9 @@ const ASTRecordLayout *NewEntry = nullptr; if (isMsLayout(*this)) { - MicrosoftRecordLayoutBuilder Builder(*this); if (const auto *RD = dyn_cast(D)) { + EmptySubobjectMap EmptySubobjects(*this, RD); + MicrosoftRecordLayoutBuilder Builder(*this, &EmptySubobjects); Builder.cxxLayout(RD); NewEntry = new (*this) ASTRecordLayout( *this, Builder.Size, Builder.Alignment, Builder.Alignment, @@ -3317,6 +3438,7 @@ Builder.EndsWithZeroSizedObject, Builder.LeadsWithZeroSizedBase, Builder.Bases, Builder.VBases); } else { + MicrosoftRecordLayoutBuilder Builder(*this, /*EmptySubobjects*/ nullptr); Builder.layout(D); NewEntry = new (*this) ASTRecordLayout( *this, Builder.Size, Builder.Alignment, Builder.Alignment, diff --git a/clang/lib/CodeGen/ABIInfoImpl.cpp b/clang/lib/CodeGen/ABIInfoImpl.cpp --- a/clang/lib/CodeGen/ABIInfoImpl.cpp +++ b/clang/lib/CodeGen/ABIInfoImpl.cpp @@ -280,7 +280,9 @@ // not arrays of records, so we must also check whether we stripped off an // array type above. if (isa(RT->getDecl()) && - (WasArray || (!AsIfNoUniqueAddr && !FD->hasAttr()))) + (WasArray || + (!AsIfNoUniqueAddr && !(FD->hasAttr() || + FD->hasAttr())))) return false; return isEmptyRecord(Context, FT, AllowArrays, AsIfNoUniqueAddr); diff --git a/clang/lib/CodeGen/CGExprAgg.cpp b/clang/lib/CodeGen/CGExprAgg.cpp --- a/clang/lib/CodeGen/CGExprAgg.cpp +++ b/clang/lib/CodeGen/CGExprAgg.cpp @@ -2061,7 +2061,9 @@ AggValueSlot::Overlap_t CodeGenFunction::getOverlapForFieldInit(const FieldDecl *FD) { - if (!FD->hasAttr() || !FD->getType()->isRecordType()) + if (!(FD->hasAttr() || + FD->hasAttr()) || + !FD->getType()->isRecordType()) return AggValueSlot::DoesNotOverlap; // If the field lies entirely within the enclosing class's nvsize, its tail diff --git a/clang/lib/CodeGen/CGExprConstant.cpp b/clang/lib/CodeGen/CGExprConstant.cpp --- a/clang/lib/CodeGen/CGExprConstant.cpp +++ b/clang/lib/CodeGen/CGExprConstant.cpp @@ -756,7 +756,8 @@ return false; // After emitting a non-empty field with [[no_unique_address]], we may // need to overwrite its tail padding. - if (Field->hasAttr()) + if (Field->hasAttr() || + Field->hasAttr()) AllowOverwrite = true; } else { // Otherwise we have a bitfield. @@ -857,7 +858,8 @@ return false; // After emitting a non-empty field with [[no_unique_address]], we may // need to overwrite its tail padding. - if (Field->hasAttr()) + if (Field->hasAttr() || + Field->hasAttr()) AllowOverwrite = true; } else { // Otherwise we have a bitfield. diff --git a/clang/lib/CodeGen/CGRecordLayoutBuilder.cpp b/clang/lib/CodeGen/CGRecordLayoutBuilder.cpp --- a/clang/lib/CodeGen/CGRecordLayoutBuilder.cpp +++ b/clang/lib/CodeGen/CGRecordLayoutBuilder.cpp @@ -756,7 +756,9 @@ Prior->Data = getByteArrayType(bitsToCharUnits(llvm::alignTo( cast(Prior->Data)->getIntegerBitWidth(), 8))); else { - assert(Prior->FD->hasAttr() && + Prior->FD->dump(); + assert((Prior->FD->hasAttr() || + Prior->FD->hasAttr()) && "should not have reused this field's tail padding"); Prior->Data = getByteArrayType( Context.getTypeInfoDataSizeInChars(Prior->FD->getType()).Width); diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp --- a/clang/lib/Parse/ParseDeclCXX.cpp +++ b/clang/lib/Parse/ParseDeclCXX.cpp @@ -4365,6 +4365,7 @@ case ParsedAttr::AT_FallThrough: case ParsedAttr::AT_CXX11NoReturn: case ParsedAttr::AT_NoUniqueAddress: + case ParsedAttr::AT_NoUniqueAddressMSVC: case ParsedAttr::AT_Likely: case ParsedAttr::AT_Unlikely: return true; diff --git a/clang/test/Layout/ms-no-unique-address.cpp b/clang/test/Layout/ms-no-unique-address.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Layout/ms-no-unique-address.cpp @@ -0,0 +1,254 @@ +// RUN: %clang_cc1 -std=c++2a -fsyntax-only -triple x86_64-pc-win32 -fdump-record-layouts %s | FileCheck %s + +namespace Empty { + struct A {}; + struct B { [[msvc::no_unique_address]] A a; char b; }; + static_assert(sizeof(B) == 1); + + // CHECK:*** Dumping AST Record Layout + // CHECK: 0 | struct Empty::B + // CHECK-NEXT: 0 | struct Empty::A a (empty) + // CHECK-NEXT: 0 | char b + // CHECK-NEXT: | [sizeof=1, align=1, + // CHECK-NEXT: | nvsize=1, nvalign=1] + + struct C {}; + struct D { + [[msvc::no_unique_address]] A a; + [[msvc::no_unique_address]] C c; + char d; + }; + static_assert(sizeof(D) == 1); + + // CHECK:*** Dumping AST Record Layout + // CHECK: 0 | struct Empty::D + // CHECK-NEXT: 0 | struct Empty::A a (empty) + // CHECK-NEXT: 0 | struct Empty::C c (empty) + // CHECK-NEXT: 0 | char d + // CHECK-NEXT: | [sizeof=1, align=1, + // CHECK-NEXT: | nvsize=1, nvalign=1] + + struct E { + [[msvc::no_unique_address]] A a1; + [[msvc::no_unique_address]] A a2; + char e; + }; + static_assert(sizeof(E) == 2); + + // CHECK:*** Dumping AST Record Layout + // CHECK: 0 | struct Empty::E + // CHECK-NEXT: 0 | struct Empty::A a1 (empty) + // CHECK-NEXT: 1 | struct Empty::A a2 (empty) + // CHECK-NEXT: 0 | char e + // CHECK-NEXT: | [sizeof=2, align=1, + // CHECK-NEXT: | nvsize=2, nvalign=1] + + struct F { + ~F(); + [[msvc::no_unique_address]] A a1; + [[msvc::no_unique_address]] A a2; + char f; + }; + static_assert(sizeof(F) == 2); + + // CHECK:*** Dumping AST Record Layout + // CHECK: 0 | struct Empty::F + // CHECK-NEXT: 0 | struct Empty::A a1 (empty) + // CHECK-NEXT: 1 | struct Empty::A a2 (empty) + // CHECK-NEXT: 0 | char f + // CHECK-NEXT: | [sizeof=2, align=1, + // CHECK-NEXT: | nvsize=2, nvalign=1] + + struct G { [[msvc::no_unique_address]] A a; ~G(); }; + static_assert(sizeof(G) == 1); + + // CHECK:*** Dumping AST Record Layout + // CHECK: 0 | struct Empty::G + // CHECK-NEXT: 0 | struct Empty::A a (empty) + // CHECK-NEXT: | [sizeof=1, align=1, + // CHECK-NEXT: | nvsize=1, nvalign=1] + + struct H { + [[msvc::no_unique_address]] A a; + [[msvc::no_unique_address]] A b; + ~H(); + }; + static_assert(sizeof(H) == 2); + + // CHECK:*** Dumping AST Record Layout + // CHECK: 0 | struct Empty::H + // CHECK-NEXT: 0 | struct Empty::A a (empty) + // CHECK-NEXT: 1 | struct Empty::A b (empty) + // CHECK-NEXT: | [sizeof=2, align=1, + // CHECK-NEXT: | nvsize=2, nvalign=1] + + struct OversizedEmpty : A { + ~OversizedEmpty(); + [[msvc::no_unique_address]] A a; + }; + static_assert(sizeof(OversizedEmpty) == 2); + + // CHECK:*** Dumping AST Record Layout + // CHECK: 0 | struct Empty::OversizedEmpty + // CHECK-NEXT: 0 | struct Empty::A (base) (empty) + // CHECK-NEXT: 1 | struct Empty::A a (empty) + // CHECK-NEXT: | [sizeof=2, align=1, + // CHECK-NEXT: | nvsize=2, nvalign=1] + + struct HasOversizedEmpty { + [[msvc::no_unique_address]] OversizedEmpty m; + }; + static_assert(sizeof(HasOversizedEmpty) == 2); + + // CHECK:*** Dumping AST Record Layout + // CHECK: 0 | struct Empty::HasOversizedEmpty + // CHECK-NEXT: 0 | struct Empty::OversizedEmpty m (empty) + // CHECK-NEXT: 0 | struct Empty::A (base) (empty) + // CHECK-NEXT: 1 | struct Empty::A a (empty) + // CHECK-NEXT: | [sizeof=2, align=1, + // CHECK-NEXT: | nvsize=2, nvalign=1] + + struct EmptyWithNonzeroDSize { + [[msvc::no_unique_address]] A a; + int x; + [[msvc::no_unique_address]] A b; + int y; + [[msvc::no_unique_address]] A c; + }; + static_assert(sizeof(EmptyWithNonzeroDSize) == 8); + + // CHECK:*** Dumping AST Record Layout + // CHECK: 0 | struct Empty::EmptyWithNonzeroDSize + // CHECK-NEXT: 0 | struct Empty::A a (empty) + // CHECK-NEXT: 0 | int x + // CHECK-NEXT: 1 | struct Empty::A b (empty) + // CHECK-NEXT: 4 | int y + // CHECK-NEXT: 2 | struct Empty::A c (empty) + // CHECK-NEXT: | [sizeof=8, align=4, + // CHECK-NEXT: | nvsize=8, nvalign=4] + + struct EmptyWithNonzeroDSizeNonPOD { + ~EmptyWithNonzeroDSizeNonPOD(); + [[msvc::no_unique_address]] A a; + int x; + [[msvc::no_unique_address]] A b; + int y; + [[msvc::no_unique_address]] A c; + }; + static_assert(sizeof(EmptyWithNonzeroDSizeNonPOD) == 8); + + // CHECK:*** Dumping AST Record Layout + // CHECK: 0 | struct Empty::EmptyWithNonzeroDSizeNonPOD + // CHECK-NEXT: 0 | struct Empty::A a (empty) + // CHECK-NEXT: 0 | int x + // CHECK-NEXT: 1 | struct Empty::A b (empty) + // CHECK-NEXT: 4 | int y + // CHECK-NEXT: 2 | struct Empty::A c (empty) + // CHECK-NEXT: | [sizeof=8, align=4, + // CHECK-NEXT: | nvsize=8, nvalign=4] +} + +namespace POD { + // Cannot reuse tail padding of a PDO type. + struct A { int n; char c[3]; }; + struct B { [[msvc::no_unique_address]] A a; char d; }; + static_assert(sizeof(B) == 12); + + // CHECK:*** Dumping AST Record Layout + // CHECK: 0 | struct POD::B + // CHECK-NEXT: 0 | struct POD::A a + // CHECK-NEXT: 0 | int n + // CHECK-NEXT: 4 | char[3] c + // CHECK-NEXT: 8 | char d + // CHECK-NEXT: | [sizeof=12, align=4, + // CHECK-NEXT: | nvsize=12, nvalign=4] +} + +namespace NonPOD { + struct A { int n; char c[3]; ~A(); }; + struct B { [[msvc::no_unique_address]] A a; char d; }; + static_assert(sizeof(B) == 12); + + // CHECK:*** Dumping AST Record Layout + // CHECK: 0 | struct NonPOD::B + // CHECK-NEXT: 0 | struct NonPOD::A a + // CHECK-NEXT: 0 | int n + // CHECK-NEXT: 4 | char[3] c + // CHECK-NEXT: 8 | char d + // CHECK-NEXT: | [sizeof=12, align=4, + // CHECK-NEXT: | nvsize=12, nvalign=4] +} + +namespace NVSizeGreaterThanDSize { + // The nvsize of an object includes the complete size of its empty subobjects + // (although it's unclear why). Ensure this corner case is handled properly. + struct alignas(8) A { ~A(); }; // dsize 0, nvsize 0, size 8 + struct B : A { char c; }; // dsize 1, nvsize 8, size 8 + static_assert(sizeof(B) == 8); + + // CHECK:*** Dumping AST Record Layout + // CHECK: 0 | struct NVSizeGreaterThanDSize::B + // CHECK-NEXT: 0 | struct NVSizeGreaterThanDSize::A (base) (empty) + // CHECK-NEXT: 0 | char c + // CHECK-NEXT: | [sizeof=8, align=8, + // CHECK-NEXT: | nvsize=8, nvalign=8] + + struct V { int n; }; + + // V is at offset 16, not offset 12, because B's tail padding is strangely not + // usable for virtual bases. + struct C : B, virtual V {}; + static_assert(sizeof(C) == 24); + + // CHECK:*** Dumping AST Record Layout + // CHECK: 0 | struct NVSizeGreaterThanDSize::C + // CHECK-NEXT: 0 | struct NVSizeGreaterThanDSize::B (base) + // CHECK-NEXT: 0 | struct NVSizeGreaterThanDSize::A (base) (empty) + // CHECK-NEXT: 0 | char c + // CHECK-NEXT: 8 | (C vbtable pointer) + // CHECK-NEXT: 16 | struct NVSizeGreaterThanDSize::V (virtual base) + // CHECK-NEXT: 16 | int n + // CHECK-NEXT: | [sizeof=24, align=8, + // CHECK-NEXT: | nvsize=16, nvalign=8] + + struct D : virtual V { + [[msvc::no_unique_address]] B b; + }; + static_assert(sizeof(D) == 24); + + // CHECK:*** Dumping AST Record Layout + // CHECK: 0 | struct NVSizeGreaterThanDSize::D + // CHECK-NEXT: 0 | (D vbtable pointer) + // CHECK-NEXT: 8 | struct NVSizeGreaterThanDSize::B b + // CHECK-NEXT: 8 | struct NVSizeGreaterThanDSize::A (base) (empty) + // CHECK-NEXT: 8 | char c + // CHECK-NEXT: 16 | struct NVSizeGreaterThanDSize::V (virtual base) + // CHECK-NEXT: 16 | int n + // CHECK-NEXT: | [sizeof=24, align=8, + // CHECK-NEXT: | nvsize=16, nvalign=8] + + struct X : virtual A { [[msvc::no_unique_address]] A a; }; + struct E : virtual A { + [[msvc::no_unique_address]] A a; + // Here, we arrange for X to hang over the end of the nvsize of E. This + // should force the A vbase to be laid out at offset 24, not 16. + [[msvc::no_unique_address]] X x; + }; + static_assert(sizeof(E) == 24); + + // FIXME this doesn't match what MSVC does + // In MSVC member a is at offset 16, x is at 24, + // and x.a is at 40, and the total size is 48 + // + // CHECK:*** Dumping AST Record Layout + // CHECK: 0 | struct NVSizeGreaterThanDSize::E + // CHECK-NEXT: 0 | (E vbtable pointer) + // CHECK-NEXT: 8 | struct NVSizeGreaterThanDSize::A a (empty) + // CHECK-NEXT: 8 | struct NVSizeGreaterThanDSize::X x + // CHECK-NEXT: 8 | (X vbtable pointer) + // CHECK-NEXT: 16 | struct NVSizeGreaterThanDSize::A a (empty) + // CHECK-NEXT: 24 | struct NVSizeGreaterThanDSize::A (virtual base) (empty) + // CHECK-NEXT: 24 | struct NVSizeGreaterThanDSize::A (virtual base) (empty) + // CHECK-NEXT: | [sizeof=24, align=8, + // CHECK-NEXT: | nvsize=24, nvalign=8] +}