[clang] Recommit "Implement [[msvc::no_unique_address]] (#65675)" (PR #67199)
Amy Huang via cfe-commits
cfe-commits at lists.llvm.org
Wed Sep 27 13:03:33 PDT 2023
https://github.com/amykhuang updated https://github.com/llvm/llvm-project/pull/67199
>From b1f0feeaed93edfdd692858e9d63ab6bbb94c0a5 Mon Sep 17 00:00:00 2001
From: Amy Huang <akhuang at google.com>
Date: Fri, 22 Sep 2023 14:40:14 -0700
Subject: [PATCH 1/2] Recommit "Implement [[msvc::no_unique_address]] (#65675)"
Change the attribute docs so that there is a separate one for the MSVC
attribute.
This reverts commit 71f9e7695b87298f9855d8890f0e6a3b89381eb5.
---
clang/include/clang/Basic/Attr.td | 19 +-
clang/include/clang/Basic/AttrDocs.td | 9 +
clang/lib/AST/Decl.cpp | 11 +-
clang/lib/AST/RecordLayoutBuilder.cpp | 53 ++-
clang/lib/Parse/ParseDeclCXX.cpp | 3 +-
clang/lib/Sema/SemaDeclAttr.cpp | 10 +
clang/test/Layout/ms-no-unique-address.cpp | 381 ++++++++++++++++++
clang/test/Preprocessor/has_attribute.cpp | 5 +-
.../SemaCXX/cxx2a-ms-no-unique-address.cpp | 19 +
9 files changed, 494 insertions(+), 16 deletions(-)
create mode 100644 clang/test/Layout/ms-no-unique-address.cpp
create mode 100644 clang/test/SemaCXX/cxx2a-ms-no-unique-address.cpp
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 21a3b5226623cf2..3da91d25ec97347 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -1798,11 +1798,24 @@ def ArmMveStrictPolymorphism : TypeAttr, TargetSpecificAttr<TargetARM> {
let Documentation = [ArmMveStrictPolymorphismDocs];
}
-def NoUniqueAddress : InheritableAttr, TargetSpecificAttr<TargetItaniumCXXABI> {
- let Spellings = [CXX11<"", "no_unique_address", 201803>];
+def NoUniqueAddress : InheritableAttr {
+ let Subjects = SubjectList<[NonBitField], ErrorDiag>;
+ // No spellings because instances of this attribute are created by
+ // MSNoUniqueAddress and ItaniumNoUniqueAddress
+ let Spellings = [];
+ let Documentation = [InternalOnly];
+}
+
+def MSNoUniqueAddress : InheritableAttr, TargetSpecificAttr<TargetMicrosoftCXXABI> {
let Subjects = SubjectList<[NonBitField], ErrorDiag>;
+ let Spellings = [CXX11<"msvc", "no_unique_address", 201803>];
+ let Documentation = [MSNoUniqueAddressDocs];
+}
+
+def ItaniumNoUniqueAddress : InheritableAttr, TargetSpecificAttr<TargetItaniumCXXABI> {
+ let Subjects = SubjectList<[NonBitField], ErrorDiag>;
+ let Spellings = [CXX11<"", "no_unique_address", 201803>];
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 b13baa46754cfd4..d16ce23c067982d 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -1408,6 +1408,15 @@ in C++11 onwards.
}];
}
+def MSNoUniqueAddressDocs : Documentation {
+ let Category = DocCatField;
+ let Content = [{
+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.
+ }];
+}
+
def ObjCRequiresSuperDocs : Documentation {
let Category = DocCatFunction;
let Content = [{
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index 08ae2087cfe70eb..07aee0d87c835a8 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -4507,9 +4507,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<RecordType>();
+ });
}
bool FieldDecl::isPotentiallyOverlapping() const {
diff --git a/clang/lib/AST/RecordLayoutBuilder.cpp b/clang/lib/AST/RecordLayoutBuilder.cpp
index 8afd88ae7be27b3..f1f2275da44dcad 100644
--- a/clang/lib/AST/RecordLayoutBuilder.cpp
+++ b/clang/lib/AST/RecordLayoutBuilder.cpp
@@ -2545,7 +2545,10 @@ struct MicrosoftRecordLayoutBuilder {
CharUnits Alignment;
};
typedef llvm::DenseMap<const CXXRecordDecl *, CharUnits> 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<const CXXRecordDecl *> &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<CXXRecordDecl>(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<CXXRecordDecl>(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 5a6b5efbf6c1223..cc93fe7d5461967 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -4374,7 +4374,8 @@ static bool IsBuiltInOrStandardCXX11Attribute(IdentifierInfo *AttrName,
case ParsedAttr::AT_Deprecated:
case ParsedAttr::AT_FallThrough:
case ParsedAttr::AT_CXX11NoReturn:
- case ParsedAttr::AT_NoUniqueAddress:
+ case ParsedAttr::AT_MSNoUniqueAddress:
+ case ParsedAttr::AT_ItaniumNoUniqueAddress:
case ParsedAttr::AT_Likely:
case ParsedAttr::AT_Unlikely:
return true;
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 090a54eedaa07d0..f3839b2c59e0dcf 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<FunctionDecl>(D);
@@ -9277,6 +9281,12 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
case ParsedAttr::AT_NoMerge:
handleNoMergeAttr(S, D, AL);
break;
+ case ParsedAttr::AT_MSNoUniqueAddress:
+ handleNoUniqueAddressAttr(S, D, AL);
+ break;
+ case ParsedAttr::AT_ItaniumNoUniqueAddress:
+ 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 000000000000000..51cfd9a6ae3b77f
--- /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 bf0f9b3bc4a8f08..3fb99eda699b3cf 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 000000000000000..42058559a087a53
--- /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 at -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}}
+};
>From db1873ffeb9cc48499b8e1d0829ab759f6cda9b2 Mon Sep 17 00:00:00 2001
From: Amy Huang <akhuang at google.com>
Date: Mon, 25 Sep 2023 17:15:23 -0700
Subject: [PATCH 2/2] Make it so that attribute spellings can be restricted to
certain targets.
---
clang/include/clang/Basic/Attr.td | 29 ++++++-------
clang/include/clang/Basic/AttrDocs.td | 5 ---
clang/include/clang/Basic/ParsedAttrInfo.h | 8 ++++
clang/lib/Parse/ParseDeclCXX.cpp | 3 +-
clang/lib/Sema/ParsedAttr.cpp | 4 +-
clang/lib/Sema/SemaDeclAttr.cpp | 5 +--
clang/utils/TableGen/ClangAttrEmitter.cpp | 50 ++++++++++++++++++++++
7 files changed, 77 insertions(+), 27 deletions(-)
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 3da91d25ec97347..fad650b24334ca0 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -417,6 +417,7 @@ class TargetSpec {
class TargetArch<list<string> arches> : TargetSpec {
let Arches = arches;
}
+
def TargetARM : TargetArch<["arm", "thumb", "armeb", "thumbeb"]>;
def TargetAArch64 : TargetArch<["aarch64", "aarch64_be", "aarch64_32"]>;
def TargetAnyArm : TargetArch<!listconcat(TargetARM.Arches, TargetAArch64.Arches)>;
@@ -450,6 +451,12 @@ def TargetELF : TargetSpec {
def TargetSupportsInitPriority : TargetSpec {
let CustomCode = [{ !Target.getTriple().isOSzOS() }];
}
+
+class TargetSpecificSpelling<TargetSpec target, list<Spelling> spellings> {
+ TargetSpec Target = target;
+ list<Spelling> Spellings = spellings;
+}
+
// Attribute subject match rules that are used for #pragma clang attribute.
//
// A instance of AttrSubjectMatcherRule represents an individual match rule.
@@ -576,6 +583,8 @@ class Attr {
list<Argument> Args = [];
// Accessors which should be generated for the attribute.
list<Accessor> Accessors = [];
+ // Specify targets for spellings.
+ list<TargetSpecificSpelling> 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
@@ -1800,21 +1809,11 @@ def ArmMveStrictPolymorphism : TypeAttr, TargetSpecificAttr<TargetARM> {
def NoUniqueAddress : InheritableAttr {
let Subjects = SubjectList<[NonBitField], ErrorDiag>;
- // No spellings because instances of this attribute are created by
- // MSNoUniqueAddress and ItaniumNoUniqueAddress
- let Spellings = [];
- let Documentation = [InternalOnly];
-}
-
-def MSNoUniqueAddress : InheritableAttr, TargetSpecificAttr<TargetMicrosoftCXXABI> {
- let Subjects = SubjectList<[NonBitField], ErrorDiag>;
- let Spellings = [CXX11<"msvc", "no_unique_address", 201803>];
- let Documentation = [MSNoUniqueAddressDocs];
-}
-
-def ItaniumNoUniqueAddress : InheritableAttr, TargetSpecificAttr<TargetItaniumCXXABI> {
- let Subjects = SubjectList<[NonBitField], ErrorDiag>;
- let Spellings = [CXX11<"", "no_unique_address", 201803>];
+ let Spellings = [CXX11<"", "no_unique_address", 201803>, CXX11<"msvc", "no_unique_address", 201803>];
+ let TargetSpecificSpellings = [
+ TargetSpecificSpelling<TargetItaniumCXXABI, [CXX11<"", "no_unique_address", 201803>]>,
+ TargetSpecificSpelling<TargetMicrosoftCXXABI, [CXX11<"msvc", "no_unique_address", 201803>]>,
+ ];
let Documentation = [NoUniqueAddressDocs];
}
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index d16ce23c067982d..c858d7f7db7a39d 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -1405,12 +1405,7 @@ Example usage:
``[[no_unique_address]]`` is a standard C++20 attribute. Clang supports its use
in C++11 onwards.
- }];
-}
-def MSNoUniqueAddressDocs : Documentation {
- let Category = DocCatField;
- let Content = [{
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 4444f12de9ce9a9..3950d484ffd32bc 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/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp
index cc93fe7d5461967..5a6b5efbf6c1223 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -4374,8 +4374,7 @@ static bool IsBuiltInOrStandardCXX11Attribute(IdentifierInfo *AttrName,
case ParsedAttr::AT_Deprecated:
case ParsedAttr::AT_FallThrough:
case ParsedAttr::AT_CXX11NoReturn:
- case ParsedAttr::AT_MSNoUniqueAddress:
- case ParsedAttr::AT_ItaniumNoUniqueAddress:
+ case ParsedAttr::AT_NoUniqueAddress:
case ParsedAttr::AT_Likely:
case ParsedAttr::AT_Unlikely:
return true;
diff --git a/clang/lib/Sema/ParsedAttr.cpp b/clang/lib/Sema/ParsedAttr.cpp
index d7acb589172b5c4..f59b01efe7ed8f4 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 f3839b2c59e0dcf..8c5eb5bf36360d6 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -9281,10 +9281,7 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
case ParsedAttr::AT_NoMerge:
handleNoMergeAttr(S, D, AL);
break;
- case ParsedAttr::AT_MSNoUniqueAddress:
- handleNoUniqueAddressAttr(S, D, AL);
- break;
- case ParsedAttr::AT_ItaniumNoUniqueAddress:
+ case ParsedAttr::AT_NoUniqueAddress:
handleNoUniqueAddressAttr(S, D, AL);
break;
diff --git a/clang/utils/TableGen/ClangAttrEmitter.cpp b/clang/utils/TableGen/ClangAttrEmitter.cpp
index 7ea09058c3d39f2..b156ba0d37e04f4 100644
--- a/clang/utils/TableGen/ClangAttrEmitter.cpp
+++ b/clang/utils/TableGen/ClangAttrEmitter.cpp
@@ -4251,6 +4251,54 @@ static void GenerateTargetRequirements(const Record &Attr,
OS << "}\n\n";
}
+static void
+GenerateSpellingTargetRequirements(const Record &Attr,
+ const std::vector<Record *> &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<FlattenedSpelling> SpellingList =
+ GetFlattenedSpellings(Attr);
+ // for (const auto &TargetSpelling : TargetSpellings) {
+ for (unsigned TargetIndex = 0; TargetIndex < TargetSpellings.size();
+ ++TargetIndex) {
+ const auto &TargetSpelling = TargetSpellings[TargetIndex];
+ std::vector<FlattenedSpelling> Spellings =
+ GetFlattenedSpellings(*TargetSpelling);
+
+ Test += "((SpellingListIndex == ";
+ for (unsigned Index = 0; Index < Spellings.size(); ++Index) {
+ Test +=
+ llvm::itostr(getSpellingListIndex(SpellingList, Spellings[Index]));
+ // fprintf(stderr, " -- %d\n", getSpellingListIndex(SpellingList,
+ // Spellings[Index]));
+ if (Index != Spellings.size() - 1)
+ Test += " ||\n SpellingListIndex == ";
+ else
+ Test += ") && ";
+ }
+
+ const Record *Target = TargetSpelling->getValueAsDef("Target");
+ std::vector<StringRef> 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.
@@ -4465,6 +4513,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);
More information about the cfe-commits
mailing list