diff options
author | Eli Friedman <eli.friedman@gmail.com> | 2011-09-27 19:12:27 +0000 |
---|---|---|
committer | Eli Friedman <eli.friedman@gmail.com> | 2011-09-27 19:12:27 +0000 |
commit | 2fe363622c32c471e8a68c68ba5cc372644f24fb (patch) | |
tree | 79d9bef36bcad57d97d3ab44458c38d84cecbf6c | |
parent | fb2a0c5a5e4497847dcdf3d4402b38f321bf89ef (diff) |
Some changes to improve compatibility for MSVC-style C++ struct layout. Patch from r4start at gmail.com (with some minor modifications by me).
git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@140623 91177308-0d34-0410-b5e6-96231b3b80d8
-rw-r--r-- | include/clang/AST/RecordLayout.h | 10 | ||||
-rw-r--r-- | lib/AST/RecordLayout.cpp | 12 | ||||
-rw-r--r-- | lib/AST/RecordLayoutBuilder.cpp | 245 | ||||
-rw-r--r-- | test/Sema/ms_class_layout.cpp | 176 |
4 files changed, 401 insertions, 42 deletions
diff --git a/include/clang/AST/RecordLayout.h b/include/clang/AST/RecordLayout.h index d7bab80afc..b0186cea29 100644 --- a/include/clang/AST/RecordLayout.h +++ b/include/clang/AST/RecordLayout.h @@ -63,6 +63,9 @@ class ASTRecordLayout { /// any empty subobjects. CharUnits SizeOfLargestEmptySubobject; + /// VBPtrOffset - Virtual base table offset. + CharUnits VBPtrOffset; + /// PrimaryBase - The primary base info for this record. llvm::PointerIntPair<const CXXRecordDecl *, 1, bool> PrimaryBase; @@ -89,7 +92,8 @@ class ASTRecordLayout { // Constructor for C++ records. typedef CXXRecordLayoutInfo::BaseOffsetsMapTy BaseOffsetsMapTy; ASTRecordLayout(const ASTContext &Ctx, - CharUnits size, CharUnits alignment, CharUnits datasize, + CharUnits size, CharUnits alignment, CharUnits vbptroffset, + CharUnits datasize, const uint64_t *fieldoffsets, unsigned fieldcount, CharUnits nonvirtualsize, CharUnits nonvirtualalign, CharUnits SizeOfLargestEmptySubobject, @@ -199,6 +203,10 @@ public: assert(CXXInfo && "Record layout does not have C++ specific info!"); return CXXInfo->SizeOfLargestEmptySubobject; } + + CharUnits getVBPtrOffset() const { + return CXXInfo->VBPtrOffset; + } }; } // end namespace clang diff --git a/lib/AST/RecordLayout.cpp b/lib/AST/RecordLayout.cpp index 035c48fd08..ccc591a28f 100644 --- a/lib/AST/RecordLayout.cpp +++ b/lib/AST/RecordLayout.cpp @@ -13,6 +13,7 @@ #include "clang/AST/ASTContext.h" #include "clang/AST/RecordLayout.h" +#include "clang/Basic/TargetInfo.h" using namespace clang; @@ -42,7 +43,7 @@ ASTRecordLayout::ASTRecordLayout(const ASTContext &Ctx, CharUnits size, // Constructor for C++ records. ASTRecordLayout::ASTRecordLayout(const ASTContext &Ctx, CharUnits size, CharUnits alignment, - CharUnits datasize, + CharUnits vbptroffset, CharUnits datasize, const uint64_t *fieldoffsets, unsigned fieldcount, CharUnits nonvirtualsize, @@ -67,15 +68,20 @@ ASTRecordLayout::ASTRecordLayout(const ASTContext &Ctx, CXXInfo->SizeOfLargestEmptySubobject = SizeOfLargestEmptySubobject; CXXInfo->BaseOffsets = BaseOffsets; CXXInfo->VBaseOffsets = VBaseOffsets; + CXXInfo->VBPtrOffset = vbptroffset; #ifndef NDEBUG if (const CXXRecordDecl *PrimaryBase = getPrimaryBase()) { - if (isPrimaryBaseVirtual()) + if (isPrimaryBaseVirtual()) { + // Microsoft ABI doesn't have primary virtual base + if (Ctx.getTargetInfo().getCXXABI() != CXXABI_Microsoft) { assert(getVBaseClassOffset(PrimaryBase).isZero() && "Primary virtual base must be at offset 0!"); - else + } + } else { assert(getBaseClassOffsetInBits(PrimaryBase) == 0 && "Primary base must be at offset 0!"); + } } #endif } diff --git a/lib/AST/RecordLayoutBuilder.cpp b/lib/AST/RecordLayoutBuilder.cpp index b33c8d730e..4ed80fa56e 100644 --- a/lib/AST/RecordLayoutBuilder.cpp +++ b/lib/AST/RecordLayoutBuilder.cpp @@ -592,6 +592,9 @@ protected: /// out is virtual. bool PrimaryBaseIsVirtual; + /// VBPtrOffset - Virtual base table offset. Only for MS layout. + CharUnits VBPtrOffset; + typedef llvm::DenseMap<const CXXRecordDecl *, CharUnits> BaseOffsetsMapTy; /// Bases - base classes and their offsets in the record. @@ -613,16 +616,17 @@ protected: llvm::SmallPtrSet<const CXXRecordDecl *, 4> VisitedVirtualBases; RecordLayoutBuilder(const ASTContext &Context, EmptySubobjectMap - *EmptySubobjects) + *EmptySubobjects, CharUnits Alignment) : Context(Context), EmptySubobjects(EmptySubobjects), Size(0), - Alignment(CharUnits::One()), UnpackedAlignment(Alignment), + Alignment(Alignment), UnpackedAlignment(Alignment), Packed(false), IsUnion(false), IsMac68kAlign(false), IsMsStruct(false), UnfilledBitsInLastByte(0), MaxFieldAlignment(CharUnits::Zero()), DataSize(0), NonVirtualSize(CharUnits::Zero()), NonVirtualAlignment(CharUnits::One()), ZeroLengthBitfield(0), PrimaryBase(0), - PrimaryBaseIsVirtual(false), FirstNearlyEmptyVBase(0) { } + PrimaryBaseIsVirtual(false), VBPtrOffset(CharUnits::fromQuantity(-1)), + FirstNearlyEmptyVBase(0) { } void Layout(const RecordDecl *D); void Layout(const CXXRecordDecl *D); @@ -633,6 +637,8 @@ protected: void LayoutWideBitField(uint64_t FieldSize, uint64_t TypeSize, bool FieldPacked, const FieldDecl *D); void LayoutBitField(const FieldDecl *D); + void MSLayoutVirtualBases(const CXXRecordDecl *RD); + void MSLayout(const CXXRecordDecl *RD); /// BaseSubobjectInfoAllocator - Allocator for BaseSubobjectInfo objects. llvm::SpecificBumpPtrAllocator<BaseSubobjectInfo> BaseSubobjectInfoAllocator; @@ -663,7 +669,7 @@ protected: void SelectPrimaryVBase(const CXXRecordDecl *RD); - virtual CharUnits GetVirtualPointersSize(const CXXRecordDecl *RD) const; + CharUnits GetVirtualPointersSize(const CXXRecordDecl *RD) const; /// LayoutNonVirtualBases - Determines the primary base class (if any) and /// lays it out. Will then proceed to lay out all non-virtual base clasess. @@ -713,6 +719,8 @@ protected: void setSize(CharUnits NewSize) { Size = Context.toBits(NewSize); } void setSize(uint64_t NewSize) { Size = NewSize; } + CharUnits getAligment() const { return Alignment; } + CharUnits getDataSize() const { assert(DataSize % Context.getCharWidth() == 0); return Context.toCharUnitsFromBits(DataSize); @@ -722,6 +730,11 @@ protected: void setDataSize(CharUnits NewSize) { DataSize = Context.toBits(NewSize); } void setDataSize(uint64_t NewSize) { DataSize = NewSize; } + bool HasVBPtr(const CXXRecordDecl *RD) const; + bool HasNewVirtualFunction(const CXXRecordDecl *RD) const; + + /// Add vbptr or vfptr to layout. + void AddVPointer(); RecordLayoutBuilder(const RecordLayoutBuilder&); // DO NOT IMPLEMENT void operator=(const RecordLayoutBuilder&); // DO NOT IMPLEMENT @@ -729,6 +742,8 @@ public: static const CXXMethodDecl *ComputeKeyFunction(const CXXRecordDecl *RD); virtual ~RecordLayoutBuilder() { } + + CharUnits GetVBPtrOffset() const { return VBPtrOffset; } }; } // end anonymous namespace @@ -1046,6 +1061,45 @@ RecordLayoutBuilder::AddPrimaryVirtualBaseOffsets(const BaseSubobjectInfo *Info, } } +void RecordLayoutBuilder::AddVPointer() { + CharUnits PtrWidth = + Context.toCharUnitsFromBits(Context.getTargetInfo().getPointerWidth(0)); + setSize(getSize() + PtrWidth); + setDataSize(getSize()); + + if (Alignment > PtrWidth) { + setSize(getSize() + (Alignment - PtrWidth)); + setDataSize(getSize()); + } +} + +bool +RecordLayoutBuilder::HasNewVirtualFunction(const CXXRecordDecl *RD) const { + for (CXXRecordDecl::method_iterator method = RD->method_begin(); + method != RD->method_end(); + ++method) { + if (method->isVirtual() && + !method->size_overridden_methods()) { + return true; + } + } + return false; +} + +bool +RecordLayoutBuilder::HasVBPtr(const CXXRecordDecl *RD) const { + if (!RD->getNumBases()) + return false; + + for (CXXRecordDecl::base_class_const_iterator I = RD->bases_begin(), + E = RD->bases_end(); I != E; ++I) { + if (!I->isVirtual()) { + return false; + } + } + return true; +} + void RecordLayoutBuilder::LayoutVirtualBases(const CXXRecordDecl *RD, const CXXRecordDecl *MostDerivedClass) { @@ -1184,6 +1238,11 @@ void RecordLayoutBuilder::Layout(const RecordDecl *D) { } void RecordLayoutBuilder::Layout(const CXXRecordDecl *RD) { + if (Context.getTargetInfo().getCXXABI() == CXXABI_Microsoft) { + MSLayout(RD); + return ; + } + InitializeLayout(RD); // Lay out the vtable and the non-virtual bases. @@ -1674,6 +1733,104 @@ void RecordLayoutBuilder::LayoutField(const FieldDecl *D) { UpdateAlignment(FieldAlign, UnpackedFieldAlign); } +void RecordLayoutBuilder::MSLayoutVirtualBases(const CXXRecordDecl *RD) { + + if (!RD->getNumVBases()) + return; + + for (CXXRecordDecl::base_class_const_iterator I = RD->vbases_begin(), + E = RD->vbases_end(); I != E; ++I) { + + const CXXRecordDecl* BaseDecl = I->getType()->getAsCXXRecordDecl(); + const BaseSubobjectInfo* BaseInfo = VirtualBaseInfo.lookup(BaseDecl); + + assert(BaseInfo && "Did not find virtual base info!"); + + LayoutVirtualBase(BaseInfo); + } +} + +void RecordLayoutBuilder::MSLayout(const CXXRecordDecl *RD) { + + bool IsVBPtrAddedToLayout = false; + + InitializeLayout(RD); + + if (HasVBPtr(RD)) { + // If all bases are virtual and the class declares a new virtual function, + // MSVC builds a vfptr. + if (HasNewVirtualFunction(RD)) { + AddVPointer(); + } + + VBPtrOffset = getSize(); + AddVPointer(); + IsVBPtrAddedToLayout = true; + + ComputeBaseSubobjectInfo(RD); + } else { + LayoutNonVirtualBases(RD); + } + + if (RD->getNumVBases() && + !IsVBPtrAddedToLayout) { + // Add vbptr. + VBPtrOffset = getSize(); + AddVPointer(); + } + + LayoutFields(RD); + + NonVirtualSize = Context.toCharUnitsFromBits( + llvm::RoundUpToAlignment(getSizeInBits(), + Context.getTargetInfo().getCharAlign())); + NonVirtualAlignment = Alignment; + + if (NonVirtualSize != NonVirtualSize.RoundUpToAlignment(Alignment)) { + CharUnits AlignMember = + NonVirtualSize.RoundUpToAlignment(Alignment) - NonVirtualSize; + + setSize(getSize() + AlignMember); + setDataSize(getSize()); + + NonVirtualSize = Context.toCharUnitsFromBits( + llvm::RoundUpToAlignment(getSizeInBits(), + Context.getTargetInfo().getCharAlign())); + } + + MSLayoutVirtualBases(RD); + + VisitedVirtualBases.clear(); + + // Finally, round the size of the total struct up to the alignment of the + // struct itself. + if (!RD->getNumVBases()) + FinishLayout(RD); + +#ifndef NDEBUG + // Check that we have base offsets for all bases. + for (CXXRecordDecl::base_class_const_iterator I = RD->bases_begin(), + E = RD->bases_end(); I != E; ++I) { + if (I->isVirtual()) + continue; + + const CXXRecordDecl *BaseDecl = + cast<CXXRecordDecl>(I->getType()->getAs<RecordType>()->getDecl()); + + assert(Bases.count(BaseDecl) && "Did not find base offset!"); + } + + // And all virtual bases. + for (CXXRecordDecl::base_class_const_iterator I = RD->vbases_begin(), + E = RD->vbases_end(); I != E; ++I) { + const CXXRecordDecl *BaseDecl = + cast<CXXRecordDecl>(I->getType()->getAs<RecordType>()->getDecl()); + + assert(VBases.count(BaseDecl) && "Did not find base offset!"); + } +#endif +} + void RecordLayoutBuilder::FinishLayout(const NamedDecl *D) { // In C++, records cannot be of size 0. if (Context.getLangOptions().CPlusPlus && getSizeInBits() == 0) { @@ -1840,29 +1997,6 @@ RecordLayoutBuilder::Diag(SourceLocation Loc, unsigned DiagID) { return Context.getDiagnostics().Report(Loc, DiagID); } -namespace { - // This class implements layout specific to the Microsoft ABI. - class MSRecordLayoutBuilder : public RecordLayoutBuilder { - public: - MSRecordLayoutBuilder(const ASTContext& Ctx, - EmptySubobjectMap *EmptySubobjects) : - RecordLayoutBuilder(Ctx, EmptySubobjects) {} - - virtual CharUnits GetVirtualPointersSize(const CXXRecordDecl *RD) const; - }; -} - -CharUnits -MSRecordLayoutBuilder::GetVirtualPointersSize(const CXXRecordDecl *RD) const { - // We should reserve space for two pointers if the class has both - // virtual functions and virtual bases. - CharUnits PointerWidth = - Context.toCharUnitsFromBits(Context.getTargetInfo().getPointerWidth(0)); - if (RD->isPolymorphic() && RD->getNumVBases() > 0) - return 2 * PointerWidth; - return PointerWidth; -} - /// getASTRecordLayout - Get or compute information about the layout of the /// specified record (struct/union/class), which indicates its size and field /// position information. @@ -1882,25 +2016,44 @@ ASTContext::getASTRecordLayout(const RecordDecl *D) const { if (const CXXRecordDecl *RD = dyn_cast<CXXRecordDecl>(D)) { EmptySubobjectMap EmptySubobjects(*this, RD); - // When compiling for Microsoft, use the special MS builder. llvm::OwningPtr<RecordLayoutBuilder> Builder; - switch (Target->getCXXABI()) { - default: - Builder.reset(new RecordLayoutBuilder(*this, &EmptySubobjects)); - break; - case CXXABI_Microsoft: - Builder.reset(new MSRecordLayoutBuilder(*this, &EmptySubobjects)); - } + CharUnits TargetAlign = CharUnits::One(); + + Builder.reset(new RecordLayoutBuilder(*this, + &EmptySubobjects, + TargetAlign)); + // Recover resources if we crash before exiting this method. llvm::CrashRecoveryContextCleanupRegistrar<RecordLayoutBuilder> RecordBuilderCleanup(Builder.get()); Builder->Layout(RD); + TargetAlign = Builder->getAligment(); + + if (getTargetInfo().getCXXABI() == CXXABI_Microsoft && + TargetAlign.getQuantity() > 4) { + // MSVC rounds the vtable pointer to the struct alignment in what must + // be a multi-pass operation. For now, let the builder figure out the + // alignment and recalculate the layout once its known. + Builder.reset(new RecordLayoutBuilder(*this, + &EmptySubobjects, + TargetAlign)); + + Builder->Layout(RD); + + // Recover resources if we crash before exiting this method. + llvm::CrashRecoveryContextCleanupRegistrar<RecordLayoutBuilder> + RecordBuilderCleanup(Builder.get()); + } + // FIXME: This is not always correct. See the part about bitfields at // http://www.codesourcery.com/public/cxx-abi/abi.html#POD for more info. // FIXME: IsPODForThePurposeOfLayout should be stored in the record layout. - bool IsPODForThePurposeOfLayout = cast<CXXRecordDecl>(D)->isPOD(); + // This does not affect the calculations of MSVC layouts + bool IsPODForThePurposeOfLayout = + (getTargetInfo().getCXXABI() == CXXABI_Microsoft) || + cast<CXXRecordDecl>(D)->isPOD(); // FIXME: This should be done in FinalizeLayout. CharUnits DataSize = @@ -1911,6 +2064,7 @@ ASTContext::getASTRecordLayout(const RecordDecl *D) const { NewEntry = new (*this) ASTRecordLayout(*this, Builder->getSize(), Builder->Alignment, + Builder->GetVBPtrOffset(), DataSize, Builder->FieldOffsets.data(), Builder->FieldOffsets.size(), @@ -1921,7 +2075,7 @@ ASTContext::getASTRecordLayout(const RecordDecl *D) const { Builder->PrimaryBaseIsVirtual, Builder->Bases, Builder->VBases); } else { - RecordLayoutBuilder Builder(*this, /*EmptySubobjects=*/0); + RecordLayoutBuilder Builder(*this, /*EmptySubobjects=*/0, CharUnits::One()); Builder.Layout(D); NewEntry = @@ -1980,7 +2134,7 @@ ASTContext::getObjCLayout(const ObjCInterfaceDecl *D, return getObjCLayout(D, 0); } - RecordLayoutBuilder Builder(*this, /*EmptySubobjects=*/0); + RecordLayoutBuilder Builder(*this, /*EmptySubobjects=*/0, CharUnits::One()); Builder.Layout(D); const ASTRecordLayout *NewEntry = @@ -2020,12 +2174,22 @@ static void DumpCXXRecordLayout(raw_ostream &OS, IndentLevel++; const CXXRecordDecl *PrimaryBase = Layout.getPrimaryBase(); + bool HasVbptr = Layout.getVBPtrOffset() != CharUnits::fromQuantity(-1); // Vtable pointer. if (RD->isDynamicClass() && !PrimaryBase) { PrintOffset(OS, Offset, IndentLevel); OS << '(' << RD << " vtable pointer)\n"; } + + if (HasVbptr && !PrimaryBase) { + PrintOffset(OS, Offset + Layout.getVBPtrOffset(), IndentLevel); + OS << '(' << RD << " vbtable pointer)\n"; + + // one vbtable per class + HasVbptr = false; + } + // Dump (non-virtual) bases for (CXXRecordDecl::base_class_const_iterator I = RD->bases_begin(), E = RD->bases_end(); I != E; ++I) { @@ -2043,6 +2207,11 @@ static void DumpCXXRecordLayout(raw_ostream &OS, Base == PrimaryBase ? "(primary base)" : "(base)", /*IncludeVirtualBases=*/false); } + // vbptr + if (HasVbptr) { + PrintOffset(OS, Offset + Layout.getVBPtrOffset(), IndentLevel); + OS << '(' << RD << " vbtable pointer)\n"; + } // Dump fields. uint64_t FieldNo = 0; diff --git a/test/Sema/ms_class_layout.cpp b/test/Sema/ms_class_layout.cpp new file mode 100644 index 0000000000..13c90b0c9e --- /dev/null +++ b/test/Sema/ms_class_layout.cpp @@ -0,0 +1,176 @@ +// RUN: %clang_cc1 -emit-llvm-only -triple i686-pc-win32 -fdump-record-layouts -cxx-abi microsoft %s 2>&1 \ +// RUN: | FileCheck %s + +#pragma pack(push, 8) + +class B { +public: + virtual void b(){} + int b_field; +protected: +private: +}; + +class A : public B { +public: + int a_field; + virtual void a(){} + char one; +protected: +private: +}; + +class D { +public: + virtual void b(){} + double a; +}; + +class C : public virtual A, + public D, public B { +public: + double c1_field; + int c2_field; + double c3_field; + int c4_field; + virtual void foo(){} + virtual void bar(){} +protected: +private: +}; + +struct BaseStruct +{ + BaseStruct(){} + double v0; + float v1; + C fg; +}; + +struct DerivedStruct : public BaseStruct { + int x; +}; + +struct G +{ + virtual ~G(){} + int a; + double b; +}; + +#pragma pack(pop) + +// This needs only for building layouts. +// Without this clang doesn`t dump record layouts. +int main() { + // This avoid "Can't yet mangle constructors!" for MS ABI. + C* c; + c->foo(); + DerivedStruct* v; + G* g; + BaseStruct* u; + return 0; +} + +// CHECK: 0 | class D +// CHECK-NEXT: 0 | (D vtable pointer) +// CHECK-NEXT: 8 | double a + +// CHECK-NEXT: sizeof=16, dsize=16, align=8 +// CHECK-NEXT: nvsize=16, nvalign=8 + +// CHECK: 0 | class B +// CHECK-NEXT: 0 | (B vtable pointer) +// CHECK-NEXT: 4 | int b_field + +// CHECK-NEXT: sizeof=8, dsize=8, align=4 +// CHECK-NEXT: nvsize=8, nvalign=4 + +// CHECK: 0 | class A +// CHECK-NEXT: 0 | class B (primary base) +// CHECK-NEXT: 0 | (B vtable pointer) +// CHECK-NEXT: 4 | int b_field +// CHECK-NEXT: 8 | int a_field +// CHECK-NEXT: 12 | char one + +// CHECK-NEXT: sizeof=16, dsize=16, align=4 +// CHECK-NEXT: nvsize=16, nvalign=4 + +// CHECK: 0 | class C +// CHECK-NEXT: 0 | class D (primary base) +// CHECK-NEXT: 0 | (D vtable pointer) +// CHECK-NEXT: 8 | double a +// CHECK-NEXT: 16 | class B (base) +// CHECK-NEXT: 16 | (B vtable pointer) +// CHECK-NEXT: 20 | int b_field +// CHECK-NEXT: 24 | (C vbtable pointer) +// CHECK-NEXT: 32 | double c1_field +// CHECK-NEXT: 40 | int c2_field +// CHECK-NEXT: 48 | double c3_field +// CHECK-NEXT: 56 | int c4_field +// CHECK-NEXT: 64 | class A (virtual base) +// CHECK-NEXT: 64 | class B (primary base) +// CHECK-NEXT: 64 | (B vtable pointer) +// CHECK-NEXT: 68 | int b_field +// CHECK-NEXT: 72 | int a_field +// CHECK-NEXT: 76 | char one + +// CHECK-NEXT: sizeof=80, dsize=80, align=8 +// CHECK-NEXT: nvsize=80, nvalign=8 + +// CHECK: 0 | struct BaseStruct +// CHECK-NEXT: 0 | double v0 +// CHECK-NEXT: 8 | float v1 +// CHECK-NEXT: 16 | class C fg +// CHECK-NEXT: 16 | class D (primary base) +// CHECK-NEXT: 16 | (D vtable pointer) +// CHECK-NEXT: 24 | double a +// CHECK-NEXT: 32 | class B (base) +// CHECK-NEXT: 32 | (B vtable pointer) +// CHECK-NEXT: 36 | int b_field +// CHECK-NEXT: 40 | (C vbtable pointer) +// CHECK-NEXT: 48 | double c1_field +// CHECK-NEXT: 56 | int c2_field +// CHECK-NEXT: 64 | double c3_field +// CHECK-NEXT: 72 | int c4_field +// CHECK-NEXT: 80 | class A (virtual base) +// CHECK-NEXT: 80 | class B (primary base) +// CHECK-NEXT: 80 | (B vtable pointer) +// CHECK-NEXT: 84 | int b_field +// CHECK-NEXT: 88 | int a_field +// CHECK-NEXT: 92 | char one + +// CHECK-NEXT: sizeof=80, dsize=80, align=8 +// CHECK-NEXT: nvsize=80, nvalign=8 + +// CHECK: sizeof=96, dsize=96, align=8 +// CHECK-NEXT: nvsize=96, nvalign=8 + +// CHECK: 0 | struct DerivedStruct +// CHECK-NEXT: 0 | struct BaseStruct (base) +// CHECK-NEXT: 0 | double v0 +// CHECK-NEXT: 8 | float v1 +// CHECK-NEXT: 16 | class C fg +// CHECK-NEXT: 16 | class D (primary base) +// CHECK-NEXT: 16 | (D vtable pointer) +// CHECK-NEXT: 24 | double a +// CHECK-NEXT: 32 | class B (base) +// CHECK-NEXT: 32 | (B vtable pointer) +// CHECK-NEXT: 36 | int b_field +// CHECK-NEXT: 40 | (C vbtable pointer) +// CHECK-NEXT: 48 | double c1_field +// CHECK-NEXT: 56 | int c2_field +// CHECK-NEXT: 64 | double c3_field +// CHECK-NEXT: 72 | int c4_field +// CHECK-NEXT: 80 | class A (virtual base) +// CHECK-NEXT: 80 | class B (primary base) +// CHECK-NEXT: 80 | (B vtable pointer) +// CHECK-NEXT: 84 | int b_field +// CHECK-NEXT: 88 | int a_field +// CHECK-NEXT: 92 | char one +// CHECK-NEXT: sizeof=80, dsize=80, align=8 +// CHECK-NEXT: nvsize=80, nvalign=8 + +// CHECK: 96 | int x +// CHECK-NEXT: sizeof=104, dsize=104, align=8 +// CHECK-NEXT: nvsize=104, nvalign=8 |