[clang] Recommit "Implement [[msvc::no_unique_address]] (#65675)" (PR #67199)

Amy Huang via cfe-commits cfe-commits at lists.llvm.org
Fri Sep 22 14:45:54 PDT 2023


https://github.com/amykhuang created https://github.com/llvm/llvm-project/pull/67199

Change the attribute docs so that there is a separate one for the MSVC
attribute.

This reverts commit 71f9e7695b87298f9855d8890f0e6a3b89381eb5.


>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] 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}}
+};



More information about the cfe-commits mailing list