diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index dd4d45171db48..da41045766077 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -450,6 +450,12 @@ def TargetELF : TargetSpec { def TargetSupportsInitPriority : TargetSpec { let CustomCode = [{ !Target.getTriple().isOSzOS() }]; } + +class TargetSpecificSpelling spellings> { + TargetSpec Target = target; + list Spellings = spellings; +} + // Attribute subject match rules that are used for #pragma clang attribute. // // A instance of AttrSubjectMatcherRule represents an individual match rule. @@ -576,6 +582,8 @@ class Attr { list Args = []; // Accessors which should be generated for the attribute. list Accessors = []; + // Specify targets for spellings. + list TargetSpecificSpellings = []; // Set to true for attributes with arguments which require delayed parsing. bit LateParsed = 0; // Set to false to prevent an attribute from being propagated from a template @@ -1798,11 +1806,14 @@ def ArmMveStrictPolymorphism : TypeAttr, TargetSpecificAttr { let Documentation = [ArmMveStrictPolymorphismDocs]; } -def NoUniqueAddress : InheritableAttr, TargetSpecificAttr { - let Spellings = [CXX11<"", "no_unique_address", 201803>]; +def NoUniqueAddress : InheritableAttr { let Subjects = SubjectList<[NonBitField], ErrorDiag>; + let Spellings = [CXX11<"", "no_unique_address", 201803>, CXX11<"msvc", "no_unique_address", 201803>]; + let TargetSpecificSpellings = [ + TargetSpecificSpelling]>, + TargetSpecificSpelling]>, + ]; let Documentation = [NoUniqueAddressDocs]; - let SimpleHandler = 1; } def ReturnsTwice : InheritableAttr { diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index 2f9d4d1b7907b..8d928dcc146b2 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -1405,6 +1405,10 @@ Example usage: ``[[no_unique_address]]`` is a standard C++20 attribute. Clang supports its use in C++11 onwards. + +On MSVC targets, ``[[no_unique_address]]`` is ignored; use +``[[msvc::no_unique_address]]`` instead. Currently there is no guarantee of ABI +compatibility or stability with MSVC. }]; } diff --git a/clang/include/clang/Basic/ParsedAttrInfo.h b/clang/include/clang/Basic/ParsedAttrInfo.h index 4444f12de9ce9..3950d484ffd32 100644 --- a/clang/include/clang/Basic/ParsedAttrInfo.h +++ b/clang/include/clang/Basic/ParsedAttrInfo.h @@ -116,6 +116,14 @@ struct ParsedAttrInfo { /// Check if this attribute is allowed when compiling for the given target. virtual bool existsInTarget(const TargetInfo &Target) const { return true; } + + /// Check if this attribute's spelling is allowed when compiling for the given + /// target. + virtual bool spellingExistsInTarget(const TargetInfo &Target, + const unsigned SpellingListIndex) const { + return true; + } + /// Convert the spelling index of Attr to a semantic spelling enum value. virtual unsigned spellingIndexToSemanticSpelling(const ParsedAttr &Attr) const { diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp index 82d1ddadeac92..dc02706f64996 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -4513,9 +4513,14 @@ bool FieldDecl::isZeroSize(const ASTContext &Ctx) const { // 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 - // ABI will do. - return true; + if (!Ctx.getTargetInfo().getCXXABI().isMicrosoft()) + return true; + + // MS ABI: has nonzero size if it is a class type with class type fields, + // whether or not they have nonzero size + return !llvm::any_of(CXXRD->fields(), [](const FieldDecl *Field) { + return Field->getType()->getAs(); + }); } bool FieldDecl::isPotentiallyOverlapping() const { diff --git a/clang/lib/AST/RecordLayoutBuilder.cpp b/clang/lib/AST/RecordLayoutBuilder.cpp index 8afd88ae7be27..f1f2275da44dc 100644 --- a/clang/lib/AST/RecordLayoutBuilder.cpp +++ b/clang/lib/AST/RecordLayoutBuilder.cpp @@ -2545,7 +2545,10 @@ struct MicrosoftRecordLayoutBuilder { 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; @@ -2595,6 +2598,8 @@ struct MicrosoftRecordLayoutBuilder { llvm::SmallPtrSetImpl &HasVtorDispSet, const CXXRecordDecl *RD) const; const ASTContext &Context; + EmptySubobjectMap *EmptySubobjects; + /// The size of the record being laid out. CharUnits Size; /// The non-virtual size of the record layout. @@ -2908,8 +2913,7 @@ static bool recordUsesEBO(const RecordDecl *RD) { } void MicrosoftRecordLayoutBuilder::layoutNonVirtualBase( - const CXXRecordDecl *RD, - const CXXRecordDecl *BaseDecl, + const CXXRecordDecl *RD, const CXXRecordDecl *BaseDecl, const ASTRecordLayout &BaseLayout, const ASTRecordLayout *&PreviousBaseLayout) { // Insert padding between two bases if the left first one is zero sized or @@ -2942,6 +2946,7 @@ void MicrosoftRecordLayoutBuilder::layoutNonVirtualBase( } Bases.insert(std::make_pair(BaseDecl, BaseOffset)); Size += BaseLayout.getNonVirtualSize(); + DataSize = Size; PreviousBaseLayout = &BaseLayout; } @@ -2959,15 +2964,43 @@ void MicrosoftRecordLayoutBuilder::layoutField(const FieldDecl *FD) { LastFieldIsNonZeroWidthBitfield = false; ElementInfo Info = getAdjustedElementInfo(FD); Alignment = std::max(Alignment, Info.Alignment); - CharUnits FieldOffset; - if (UseExternalLayout) + + const CXXRecordDecl *FieldClass = FD->getType()->getAsCXXRecordDecl(); + bool IsOverlappingEmptyField = FD->isPotentiallyOverlapping() && + FieldClass->isEmpty() && + FieldClass->fields().empty(); + 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) + FieldOffset = DataSize.alignTo(Info.Alignment); + + while (!EmptySubobjects->CanPlaceFieldAtOffset(FD, FieldOffset)) { + const CXXRecordDecl *ParentClass = cast(FD->getParent()); + bool HasBases = ParentClass && (!ParentClass->bases().empty() || + !ParentClass->vbases().empty()); + if (FieldOffset == CharUnits::Zero() && DataSize != CharUnits::Zero() && + HasBases) { + // MSVC appears to only do this when there are base classes; + // otherwise it overlaps no_unique_address fields in non-zero offsets. + FieldOffset = DataSize.alignTo(Info.Alignment); + } else { + FieldOffset += Info.Alignment; + } + } + } else { FieldOffset = Size.alignTo(Info.Alignment); + } placeFieldAtOffset(FieldOffset); + + if (!IsOverlappingEmptyField) + DataSize = std::max(DataSize, FieldOffset + Info.Size); + Size = std::max(Size, FieldOffset + Info.Size); } @@ -3013,6 +3046,7 @@ void MicrosoftRecordLayoutBuilder::layoutBitField(const FieldDecl *FD) { Alignment = std::max(Alignment, Info.Alignment); RemainingBitsInField = Context.toBits(Info.Size) - Width; } + DataSize = Size; } void @@ -3038,6 +3072,7 @@ MicrosoftRecordLayoutBuilder::layoutZeroWidthBitField(const FieldDecl *FD) { Size = FieldOffset; Alignment = std::max(Alignment, Info.Alignment); } + DataSize = Size; } void MicrosoftRecordLayoutBuilder::injectVBPtr(const CXXRecordDecl *RD) { @@ -3304,8 +3339,9 @@ ASTContext::getASTRecordLayout(const RecordDecl *D) const { 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 +3353,7 @@ ASTContext::getASTRecordLayout(const RecordDecl *D) const { 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/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp index cebcfbbd6cb6e..35b1a93a54a6a 100644 --- a/clang/lib/Parse/ParseDeclCXX.cpp +++ b/clang/lib/Parse/ParseDeclCXX.cpp @@ -4466,6 +4466,14 @@ bool Parser::ParseCXX11AttributeArgs( if (!Attrs.empty() && IsBuiltInOrStandardCXX11Attribute(AttrName, ScopeName)) { ParsedAttr &Attr = Attrs.back(); + + // Ignore attributes that don't exist for the target. + if (!Attr.existsInTarget(getTargetInfo())) { + Diag(LParenLoc, diag::warn_unknown_attribute_ignored) << AttrName; + Attr.setInvalid(true); + return true; + } + // If the attribute is a standard or built-in attribute and we are // parsing an argument list, we need to determine whether this attribute // was allowed to have an argument list (such as [[deprecated]]), and how diff --git a/clang/lib/Sema/ParsedAttr.cpp b/clang/lib/Sema/ParsedAttr.cpp index d7acb589172b5..f59b01efe7ed8 100644 --- a/clang/lib/Sema/ParsedAttr.cpp +++ b/clang/lib/Sema/ParsedAttr.cpp @@ -193,7 +193,9 @@ bool ParsedAttr::isTypeAttr() const { return getInfo().IsType; } bool ParsedAttr::isStmtAttr() const { return getInfo().IsStmt; } bool ParsedAttr::existsInTarget(const TargetInfo &Target) const { - return getInfo().existsInTarget(Target); + return getInfo().existsInTarget(Target) && + getInfo().spellingExistsInTarget(Target, + getAttributeSpellingListIndex()); } bool ParsedAttr::isKnownToGCC() const { return getInfo().IsKnownToGCC; } diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index 090a54eedaa07..8c5eb5bf36360 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -8372,6 +8372,10 @@ static void handleNoMergeAttr(Sema &S, Decl *D, const ParsedAttr &AL) { D->addAttr(NoMergeAttr::Create(S.Context, AL)); } +static void handleNoUniqueAddressAttr(Sema &S, Decl *D, const ParsedAttr &AL) { + D->addAttr(NoUniqueAddressAttr::Create(S.Context, AL)); +} + static void handleSYCLKernelAttr(Sema &S, Decl *D, const ParsedAttr &AL) { // The 'sycl_kernel' attribute applies only to function templates. const auto *FD = cast(D); @@ -9277,6 +9281,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL, case ParsedAttr::AT_NoMerge: handleNoMergeAttr(S, D, AL); break; + case ParsedAttr::AT_NoUniqueAddress: + handleNoUniqueAddressAttr(S, D, AL); + break; case ParsedAttr::AT_AvailableOnlyInDefaultEvalMethod: handleAvailableOnlyInDefaultEvalMethod(S, D, AL); diff --git a/clang/test/Layout/ms-no-unique-address.cpp b/clang/test/Layout/ms-no-unique-address.cpp new file mode 100644 index 0000000000000..51cfd9a6ae3b7 --- /dev/null +++ b/clang/test/Layout/ms-no-unique-address.cpp @@ -0,0 +1,381 @@ +// RUN: %clang_cc1 -std=c++2a -fsyntax-only -triple x86_64-windows-msvc -fms-compatibility -fdump-record-layouts %s | FileCheck %s + +namespace Empty { + struct A {}; + struct A2 {}; + struct A3 { [[msvc::no_unique_address]] A a; }; + struct alignas(8) A4 {}; + + 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 { + [[msvc::no_unique_address]] A a; + [[msvc::no_unique_address]] A2 a2; + char c; + }; + static_assert(sizeof(C) == 1); + + // CHECK:*** Dumping AST Record Layout + // CHECK: 0 | struct Empty::C + // CHECK-NEXT: 0 | struct Empty::A a (empty) + // CHECK-NEXT: 0 | struct Empty::A2 a2 (empty) + // CHECK-NEXT: 0 | char c + // CHECK-NEXT: | [sizeof=1, align=1, + // CHECK-NEXT: | nvsize=1, nvalign=1] + + struct D { + [[msvc::no_unique_address]] A3 a; + int i; + }; + static_assert(sizeof(D) == 8); + + // CHECK:*** Dumping AST Record Layout + // CHECK: 0 | struct Empty::D + // CHECK-NEXT: 0 | struct Empty::A3 a (empty) + // CHECK-NEXT: 0 | struct Empty::A a (empty) + // CHECK-NEXT: 4 | int i + // CHECK-NEXT: | [sizeof=8, align=4, + // CHECK-NEXT: | nvsize=8, nvalign=4] + + 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 I { + [[msvc::no_unique_address]] A4 a; + [[msvc::no_unique_address]] A4 b; + }; + static_assert(sizeof(I) == 16); + + // CHECK:*** Dumping AST Record Layout + // CHECK: 0 | struct Empty::I + // CHECK-NEXT: 0 | struct Empty::A4 a (empty) + // CHECK-NEXT: 8 | struct Empty::A4 b (empty) + // CHECK-NEXT: | [sizeof=16, align=8, + // CHECK-NEXT: | nvsize=16, nvalign=8] + + struct J { + [[msvc::no_unique_address]] A4 a; + A4 b; + }; + static_assert(sizeof(J) == 16); + + // MSVC puts a and b at the same offset. + // CHECK:*** Dumping AST Record Layout + // CHECK: 0 | struct Empty::J + // CHECK-NEXT: 0 | struct Empty::A4 a (empty) + // CHECK-NEXT: 8 | struct Empty::A4 b (empty) + // CHECK-NEXT: | [sizeof=16, align=8, + // CHECK-NEXT: | nvsize=16, nvalign=8] + + struct K { + [[msvc::no_unique_address]] A4 a; + [[msvc::no_unique_address]] char c; + [[msvc::no_unique_address]] A4 b; + }; + static_assert(sizeof(K) == 16); + + // CHECK:*** Dumping AST Record Layout + // CHECK: 0 | struct Empty::K + // CHECK-NEXT: 0 | struct Empty::A4 a (empty) + // CHECK-NEXT: 0 | char c + // CHECK-NEXT: 8 | struct Empty::A4 b (empty) + // CHECK-NEXT: | [sizeof=16, align=8, + // CHECK-NEXT: | nvsize=16, nvalign=8] + + struct OversizedEmpty : A { + ~OversizedEmpty(); + [[msvc::no_unique_address]] A a; + }; + static_assert(sizeof(OversizedEmpty) == 1); + + // CHECK:*** Dumping AST Record Layout + // CHECK: 0 | struct Empty::OversizedEmpty + // CHECK-NEXT: 0 | struct Empty::A (base) (empty) + // CHECK-NEXT: 0 | struct Empty::A a (empty) + // CHECK-NEXT: | [sizeof=1, align=1, + // CHECK-NEXT: | nvsize=1, nvalign=1] + + struct HasOversizedEmpty { + [[msvc::no_unique_address]] OversizedEmpty m; + }; + static_assert(sizeof(HasOversizedEmpty) == 1); + + // 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: 0 | struct Empty::A a (empty) + // CHECK-NEXT: | [sizeof=1, align=1, + // CHECK-NEXT: | nvsize=1, 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 { + 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 VBases { + // 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 Empty {}; + struct alignas(8) 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 VBases::B + // CHECK-NEXT: 0 | struct VBases::A (base) (empty) + // CHECK-NEXT: 0 | char c + // CHECK-NEXT: | [sizeof=8, align=8, + // CHECK-NEXT: | nvsize=8, nvalign=8] + + struct V { int n; }; + + struct C : B, virtual V {}; + static_assert(sizeof(C) == 24); + + // CHECK:*** Dumping AST Record Layout + // CHECK: 0 | struct VBases::C + // CHECK-NEXT: 0 | struct VBases::B (base) + // CHECK-NEXT: 0 | struct VBases::A (base) (empty) + // CHECK-NEXT: 0 | char c + // CHECK-NEXT: 8 | (C vbtable pointer) + // CHECK-NEXT: 16 | struct VBases::V (virtual base) + // CHECK-NEXT: 16 | int n + // CHECK-NEXT: | [sizeof=24, align=8, + // CHECK-NEXT: | nvsize=16, nvalign=8] + + struct D : virtual Empty { + [[msvc::no_unique_address]] Empty a; + }; + static_assert(sizeof(D) == 16); + + // CHECK:*** Dumping AST Record Layout + // CHECK: 0 | struct VBases::D + // CHECK-NEXT: 0 | (D vbtable pointer) + // CHECK-NEXT: 8 | struct VBases::Empty a + // CHECK-NEXT: 16 | struct VBases::Empty (virtual base) (empty) + // CHECK-NEXT: | [sizeof=16, align=8, + // CHECK-NEXT: | nvsize=16, nvalign=8] + + struct E : virtual V { + [[msvc::no_unique_address]] B b; + }; + static_assert(sizeof(E) == 24); + + // CHECK:*** Dumping AST Record Layout + // CHECK: 0 | struct VBases::E + // CHECK-NEXT: 0 | (E vbtable pointer) + // CHECK-NEXT: 8 | struct VBases::B b + // CHECK-NEXT: 8 | struct VBases::A (base) (empty) + // CHECK-NEXT: 8 | char c + // CHECK-NEXT: 16 | struct VBases::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 F : virtual A { + [[msvc::no_unique_address]] A a; + [[msvc::no_unique_address]] X x; + }; + static_assert(sizeof(F) == 24); + + // MSVC places x after a and the total size is 48. + // CHECK:*** Dumping AST Record Layout + // CHECK: 0 | struct VBases::F + // CHECK-NEXT: 0 | (F vbtable pointer) + // CHECK-NEXT: 8 | struct VBases::A a (empty) + // CHECK-NEXT: 8 | struct VBases::X x + // CHECK-NEXT: 8 | (X vbtable pointer) + // CHECK-NEXT: 16 | struct VBases::A a (empty) + // CHECK-NEXT: 24 | struct VBases::A (virtual base) (empty) + // CHECK-NEXT: 24 | struct VBases::A (virtual base) (empty) + // CHECK-NEXT: | [sizeof=24, align=8, + // CHECK-NEXT: | nvsize=24, nvalign=8] + + struct G : virtual Empty { + int i; + [[msvc::no_unique_address]] A a; + }; + static_assert(sizeof(G) == 16); + + // MSVC places a at offset 12. + // CHECK:*** Dumping AST Record Layout + // CHECK: 0 | struct VBases::G + // CHECK-NEXT: 0 | (G vbtable pointer) + // CHECK-NEXT: 8 | int i + // CHECK-NEXT: 8 | struct VBases::A a (empty) + // CHECK-NEXT: 16 | struct VBases::Empty (virtual base) (empty) + // CHECK-NEXT: | [sizeof=16, align=8, + // CHECK-NEXT: | nvsize=16, nvalign=8] +} + +namespace ZeroSize { + struct empty {}; + + union empty_union {}; + + struct empty_union_container { + [[msvc::no_unique_address]] empty_union x; + }; + + union union_of_empty { + [[msvc::no_unique_address]] empty x; + }; + + struct struct_of_empty { + [[msvc::no_unique_address]] empty x; + }; + + struct union_of_empty_container { + [[msvc::no_unique_address]] union_of_empty x; + }; + static_assert(sizeof(union_of_empty_container) == 1); + // CHECK:*** Dumping AST Record Layout + // CHECK: 0 | struct ZeroSize::union_of_empty_container + // CHECK-NOT: (empty) + // CHECK: 0 | union ZeroSize::union_of_empty x (empty) + // CHECK: 0 | struct ZeroSize::empty x (empty) + // CHECK: | [sizeof=1, align=1, + // CHECK: | nvsize=1, nvalign=1] + + struct struct_of_empty_container { + [[msvc::no_unique_address]] struct_of_empty x; + }; + static_assert(sizeof(struct_of_empty_container) == 1); + // CHECK:*** Dumping AST Record Layout + // CHECK: 0 | struct ZeroSize::struct_of_empty_container + // CHECK-NOT: (empty) + // CHECK: 0 | struct ZeroSize::struct_of_empty x (empty) + // CHECK: 0 | struct ZeroSize::empty x (empty) + // CHECK: | [sizeof=1, align=1, + // CHECK: | nvsize=1, nvalign=1] + +} diff --git a/clang/test/Preprocessor/has_attribute.cpp b/clang/test/Preprocessor/has_attribute.cpp index bf0f9b3bc4a8f..3fb99eda699b3 100644 --- a/clang/test/Preprocessor/has_attribute.cpp +++ b/clang/test/Preprocessor/has_attribute.cpp @@ -43,6 +43,7 @@ CXX11(fallthrough) CXX11(likely) CXX11(maybe_unused) CXX11(no_unique_address) +CXX11(msvc::no_unique_address) CXX11(nodiscard) CXX11(noreturn) CXX11(unlikely) @@ -55,7 +56,9 @@ CXX11(unlikely) // CHECK: likely: 201803L // CHECK: maybe_unused: 201603L // ITANIUM: no_unique_address: 201803L -// WINDOWS: no_unique_address: 0 +// WINDOWS: no_unique_address: 0 +// ITANIUM: msvc::no_unique_address: 0 +// WINDOWS: msvc::no_unique_address: 201803L // CHECK: nodiscard: 201907L // CHECK: noreturn: 200809L // CHECK: unlikely: 201803L diff --git a/clang/test/SemaCXX/cxx2a-ms-no-unique-address.cpp b/clang/test/SemaCXX/cxx2a-ms-no-unique-address.cpp new file mode 100644 index 0000000000000..42058559a087a --- /dev/null +++ b/clang/test/SemaCXX/cxx2a-ms-no-unique-address.cpp @@ -0,0 +1,19 @@ +// RUN: %clang_cc1 -std=c++2a %s -verify=unsupported -triple x86_64-linux-gnu +// RUN: %clang_cc1 -std=c++2a %s -verify -triple x86_64-windows -fms-compatibility + +[[msvc::no_unique_address]] int a; // expected-error {{only applies to non-bit-field non-static data members}} unsupported-warning {{unknown}} +[[msvc::no_unique_address]] void f(); // expected-error {{only applies to non-bit-field non-static data members}} unsupported-warning {{unknown}} +struct [[msvc::no_unique_address]] S { // expected-error {{only applies to non-bit-field non-static data members}} unsupported-warning {{unknown}} + [[msvc::no_unique_address]] int a; // unsupported-warning {{unknown}} + [[msvc::no_unique_address]] void f(); // expected-error {{only applies to non-bit-field non-static data members}} unsupported-warning {{unknown}} + [[msvc::no_unique_address]] static int sa;// expected-error {{only applies to non-bit-field non-static data members}} unsupported-warning {{unknown}} + [[msvc::no_unique_address]] static void sf(); // expected-error {{only applies to non-bit-field non-static data members}} unsupported-warning {{unknown}} + [[msvc::no_unique_address]] int b : 3; // expected-error {{only applies to non-bit-field non-static data members}} unsupported-warning {{unknown}} + + [[msvc::no_unique_address, msvc::no_unique_address]] int duplicated; // ok + // unsupported-warning@-1 2{{unknown}} + [[msvc::no_unique_address]] [[msvc::no_unique_address]] int duplicated2; // unsupported-warning 2{{unknown}} + [[msvc::no_unique_address()]] int arglist; // expected-error {{cannot have an argument list}} unsupported-warning {{unknown}} + + int [[msvc::no_unique_address]] c; // expected-error {{cannot be applied to types}} unsupported-error {{cannot be applied to types}} +}; diff --git a/clang/utils/TableGen/ClangAttrEmitter.cpp b/clang/utils/TableGen/ClangAttrEmitter.cpp index adaee8cc92c15..ffada02ac4d30 100644 --- a/clang/utils/TableGen/ClangAttrEmitter.cpp +++ b/clang/utils/TableGen/ClangAttrEmitter.cpp @@ -3451,9 +3451,11 @@ static void GenerateHasAttrSpellingStringSwitch( int Version = 1; std::vector Spellings = GetFlattenedSpellings(*Attr); + std::string Name = ""; for (const auto &Spelling : Spellings) { if (Spelling.variety() == Variety && (Spelling.nameSpace().empty() || Scope == Spelling.nameSpace())) { + Name = Spelling.name(); Version = static_cast( Spelling.getSpellingRecord().getValueAsInt("Version")); // Verify that explicitly specified CXX11 and C23 spellings (i.e. @@ -3477,6 +3479,26 @@ static void GenerateHasAttrSpellingStringSwitch( GenerateTargetSpecificAttrChecks(R, Arches, Test, nullptr); // If this is the C++11 variety, also add in the LangOpts test. + if (Variety == "CXX11") + Test += " && LangOpts.CPlusPlus11"; + } else if (!Attr->getValueAsListOfDefs("TargetSpecificSpellings").empty()) { + // Add target checks if this spelling is target-specific. + const std::vector TargetSpellings = + Attr->getValueAsListOfDefs("TargetSpecificSpellings"); + for (const auto &TargetSpelling : TargetSpellings) { + // Find spelling that matches current scope and name. + for (const auto &Spelling : GetFlattenedSpellings(*TargetSpelling)) { + if (Scope == Spelling.nameSpace() && Name == Spelling.name()) { + const Record *Target = TargetSpelling->getValueAsDef("Target"); + std::vector Arches = + Target->getValueAsListOfStrings("Arches"); + GenerateTargetSpecificAttrChecks(Target, Arches, Test, + /*FnName=*/nullptr); + break; + } + } + } + if (Variety == "CXX11") Test += " && LangOpts.CPlusPlus11"; } else if (Variety == "CXX11") @@ -4267,6 +4289,51 @@ static void GenerateTargetRequirements(const Record &Attr, OS << "}\n\n"; } +static void +GenerateSpellingTargetRequirements(const Record &Attr, + const std::vector &TargetSpellings, + raw_ostream &OS) { + // If there are no target specific spellings, use the default target handler. + if (TargetSpellings.empty()) + return; + + std::string Test; + bool UsesT = false; + const std::vector SpellingList = + GetFlattenedSpellings(Attr); + for (unsigned TargetIndex = 0; TargetIndex < TargetSpellings.size(); + ++TargetIndex) { + const auto &TargetSpelling = TargetSpellings[TargetIndex]; + std::vector Spellings = + GetFlattenedSpellings(*TargetSpelling); + + Test += "((SpellingListIndex == "; + for (unsigned Index = 0; Index < Spellings.size(); ++Index) { + Test += + llvm::itostr(getSpellingListIndex(SpellingList, Spellings[Index])); + if (Index != Spellings.size() - 1) + Test += " ||\n SpellingListIndex == "; + else + Test += ") && "; + } + + const Record *Target = TargetSpelling->getValueAsDef("Target"); + std::vector Arches = Target->getValueAsListOfStrings("Arches"); + std::string FnName = "isTargetSpelling"; + UsesT |= GenerateTargetSpecificAttrChecks(Target, Arches, Test, &FnName); + Test += ")"; + if (TargetIndex != TargetSpellings.size() - 1) + Test += " || "; + } + + OS << "bool spellingExistsInTarget(const TargetInfo &Target,\n"; + OS << " const unsigned SpellingListIndex) const " + "override {\n"; + if (UsesT) + OS << " const llvm::Triple &T = Target.getTriple(); (void)T;\n"; + OS << " return " << Test << ";\n", OS << "}\n\n"; +} + static void GenerateSpellingIndexToSemanticSpelling(const Record &Attr, raw_ostream &OS) { // If the attribute does not have a semantic form, we can bail out early. @@ -4481,6 +4548,8 @@ void EmitClangAttrParsedAttrImpl(RecordKeeper &Records, raw_ostream &OS) { GenerateMutualExclusionsChecks(Attr, Records, OS, MergeDeclOS, MergeStmtOS); GenerateLangOptRequirements(Attr, OS); GenerateTargetRequirements(Attr, Dupes, OS); + GenerateSpellingTargetRequirements( + Attr, Attr.getValueAsListOfDefs("TargetSpecificSpellings"), OS); GenerateSpellingIndexToSemanticSpelling(Attr, OS); PragmaAttributeSupport.generateStrictConformsTo(*I->second, OS); GenerateHandleDeclAttribute(Attr, OS);