[clang] 0faee97 - Recommit "Implement [[msvc::no_unique_address]] (#65675)" (#67199)

via cfe-commits cfe-commits at lists.llvm.org
Thu Sep 28 14:29:35 PDT 2023


Author: Amy Huang
Date: 2023-09-28T14:29:32-07:00
New Revision: 0faee97a924adec76d5c7cd680c289ced51e6b5a

URL: https://github.com/llvm/llvm-project/commit/0faee97a924adec76d5c7cd680c289ced51e6b5a
DIFF: https://github.com/llvm/llvm-project/commit/0faee97a924adec76d5c7cd680c289ced51e6b5a.diff

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

This implements the [[msvc::no_unique_address]] attribute.

There is not ABI compatibility in this patch because the attribute is
relatively new and there's still some uncertainty in the MSVC version.

The recommit changes the attribute definitions so that instead of making
two separate attributes for no_unique_address
and msvc::no_unique_address, it modifies the attributes tablegen emitter
to allow spellings to be target-specific.

This reverts commit 71f9e7695b87298f9855d8890f0e6a3b89381eb5.

Added: 
    clang/test/Layout/ms-no-unique-address.cpp
    clang/test/SemaCXX/cxx2a-ms-no-unique-address.cpp

Modified: 
    clang/include/clang/Basic/Attr.td
    clang/include/clang/Basic/AttrDocs.td
    clang/include/clang/Basic/ParsedAttrInfo.h
    clang/lib/AST/Decl.cpp
    clang/lib/AST/RecordLayoutBuilder.cpp
    clang/lib/Parse/ParseDeclCXX.cpp
    clang/lib/Sema/ParsedAttr.cpp
    clang/lib/Sema/SemaDeclAttr.cpp
    clang/test/Preprocessor/has_attribute.cpp
    clang/utils/TableGen/ClangAttrEmitter.cpp

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index dd4d45171db4899..da41045766077e5 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<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 +582,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
@@ -1798,11 +1806,14 @@ 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>;
+  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];
-  let SimpleHandler = 1;
 }
 
 def ReturnsTwice : InheritableAttr {

diff  --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 2f9d4d1b7907b20..8d928dcc146b254 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 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/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index 82d1ddadeac92ba..dc02706f6499675 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<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 cebcfbbd6cb6e33..35b1a93a54a6aab 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 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 090a54eedaa07d0..8c5eb5bf36360d6 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,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 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}}
+};

diff  --git a/clang/utils/TableGen/ClangAttrEmitter.cpp b/clang/utils/TableGen/ClangAttrEmitter.cpp
index adaee8cc92c1507..ffada02ac4d30a5 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<FlattenedSpelling> 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<int>(
             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<Record *> 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<StringRef> 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<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 (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]));
+      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.
@@ -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);


        


More information about the cfe-commits mailing list