[clang] [clang][Sema] Track trivial-relocatability as a type trait (PR #84621)

Amirreza Ashouri via cfe-commits cfe-commits at lists.llvm.org
Sun Mar 17 07:36:31 PDT 2024


https://github.com/AMP999 updated https://github.com/llvm/llvm-project/pull/84621

>From 6e127e5794efafaabf82b6c3d5e0634ddcfee977 Mon Sep 17 00:00:00 2001
From: Amirreza Ashouri <ar.ashouri999 at gmail.com>
Date: Sat, 2 Mar 2024 15:37:33 +0330
Subject: [PATCH 1/5] [clang][Sema] Track trivial-relocatability as a type
 trait

To resolve llvm#69394, this patch separates trivial-relocatability's logic from `canPassInRegisters` to decide if a type is trivial-relocatable. A type passed in registers doesn't necessarily mean trivial-relocatability of that type(e.g. on Windows) i.e. it gives us an unintended false positive. This change would be beneficial for Abseil since they rely upon these semantics.
By these changes now:
User-provided special members prevent natural trivial-relocatabilitiy.
    It's important because Abseil and maybe others assume the assignment operator doesn't have an impact on the trivial-relocatability of a type.
    In fact, it does have an effect, and with a user-provided assignment operator, the compiler should only accept it as trivial-relocatable if it's implied by the `[[clang::trivial_abi]]` attribute.
Just because a type can pass in registers doesn't necessarily mean it's trivial-relocatable.
The `[[clang::trivial_abi]]` attribute always implies trivial-relocatability, even if it can't pass in registers.
The trait has extensive tests for both old and new behaviors. Test aggregation of
both kinds of types as data members; inheritance; virtual member functions
and virtual bases; const and reference data members; and reference types.

Fixes llvm#69394
---
 .../clang/AST/CXXRecordDeclDefinitionBits.def |   5 +
 clang/include/clang/AST/DeclCXX.h             |   3 +
 clang/lib/AST/DeclCXX.cpp                     |  45 +++++++-
 clang/lib/AST/Type.cpp                        |   4 +-
 clang/test/SemaCXX/attr-trivial-abi.cpp       |  35 ------
 .../test/SemaCXX/is-trivially-relocatable.cpp | 106 ++++++++++++++++++
 clang/test/SemaCXX/type-traits.cpp            |  56 +++++++++
 7 files changed, 214 insertions(+), 40 deletions(-)
 create mode 100644 clang/test/SemaCXX/is-trivially-relocatable.cpp

diff --git a/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def b/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def
index cdf0804680ad0a..36d3cfb7dfe85b 100644
--- a/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def
+++ b/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def
@@ -189,6 +189,11 @@ FIELD(DeclaredNonTrivialSpecialMembers, 6, MERGE_OR)
 /// SMF_MoveConstructor, and SMF_Destructor are meaningful here.
 FIELD(DeclaredNonTrivialSpecialMembersForCall, 6, MERGE_OR)
 
+/// True when this class's bases and fields are all trivially relocatable
+/// or references, and the class itself has no user-provided special
+/// member functions.
+FIELD(IsNaturallyTriviallyRelocatable, 1, NO_MERGE)
+
 /// True when this class has a destructor with no semantic effect.
 FIELD(HasIrrelevantDestructor, 1, NO_MERGE)
 
diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h
index 9cebaff63bb0db..a58126c98597b0 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -1386,6 +1386,9 @@ class CXXRecordDecl : public RecordDecl {
         (SMF_CopyConstructor | SMF_MoveConstructor | SMF_Destructor);
   }
 
+  /// Determine whether this class is trivially relocatable
+  bool isTriviallyRelocatable() const;
+
   /// Determine whether declaring a const variable with this type is ok
   /// per core issue 253.
   bool allowConstDefaultInit() const {
diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp
index 1c3dcf63465c68..5cd1e6d8d720ef 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -95,7 +95,8 @@ CXXRecordDecl::DefinitionData::DefinitionData(CXXRecordDecl *D)
       DefaultedDestructorIsDeleted(false), HasTrivialSpecialMembers(SMF_All),
       HasTrivialSpecialMembersForCall(SMF_All),
       DeclaredNonTrivialSpecialMembers(0),
-      DeclaredNonTrivialSpecialMembersForCall(0), HasIrrelevantDestructor(true),
+      DeclaredNonTrivialSpecialMembersForCall(0),
+      IsNaturallyTriviallyRelocatable(true), HasIrrelevantDestructor(true),
       HasConstexprNonCopyMoveConstructor(false),
       HasDefaultedDefaultConstructor(false),
       DefaultedDefaultConstructorIsConstexpr(true),
@@ -279,6 +280,10 @@ CXXRecordDecl::setBases(CXXBaseSpecifier const * const *Bases,
 
       //   An aggregate is a class with [...] no virtual functions.
       data().Aggregate = false;
+
+      // A trivially relocatable class is a class:
+      // -- which has no virtual member functions or virtual base classes
+      data().IsNaturallyTriviallyRelocatable = false;
     }
 
     // C++0x [class]p7:
@@ -293,6 +298,9 @@ CXXRecordDecl::setBases(CXXBaseSpecifier const * const *Bases,
     if (!hasNonLiteralTypeFieldsOrBases() && !BaseType->isLiteralType(C))
       data().HasNonLiteralTypeFieldsOrBases = true;
 
+    if (Base->isVirtual() || !BaseClassDecl->isTriviallyRelocatable())
+      data().IsNaturallyTriviallyRelocatable = false;
+
     // Now go through all virtual bases of this base and add them.
     for (const auto &VBase : BaseClassDecl->vbases()) {
       // Add this base if it's not already in the list.
@@ -572,6 +580,10 @@ bool CXXRecordDecl::hasAnyDependentBases() const {
   return !forallBases([](const CXXRecordDecl *) { return true; });
 }
 
+bool CXXRecordDecl::isTriviallyRelocatable() const {
+  return (data().IsNaturallyTriviallyRelocatable || hasAttr<TrivialABIAttr>());
+}
+
 bool CXXRecordDecl::isTriviallyCopyable() const {
   // C++0x [class]p5:
   //   A trivially copyable class is a class that:
@@ -760,6 +772,10 @@ void CXXRecordDecl::addedMember(Decl *D) {
       //    -- has no virtual functions
       data().IsStandardLayout = false;
       data().IsCXX11StandardLayout = false;
+
+      // A trivially relocatable class is a class:
+      // -- which has no virtual member functions or virtual base classes
+      data().IsNaturallyTriviallyRelocatable = false;
     }
   }
 
@@ -826,6 +842,14 @@ void CXXRecordDecl::addedMember(Decl *D) {
               ? !Constructor->isImplicit()
               : (Constructor->isUserProvided() || Constructor->isExplicit()))
         data().Aggregate = false;
+
+      // A trivially relocatable class is a class:
+      // -- where no eligible copy constructor, move constructor, copy
+      // assignment operator, move assignment operator, or destructor is
+      // user-provided,
+      if (Constructor->isUserProvided() && (Constructor->isCopyConstructor() ||
+                                            Constructor->isMoveConstructor()))
+        data().IsNaturallyTriviallyRelocatable = false;
     }
   }
 
@@ -855,11 +879,18 @@ void CXXRecordDecl::addedMember(Decl *D) {
           Method->getNonObjectParameter(0)->getType()->getAs<ReferenceType>();
       if (!ParamTy || ParamTy->getPointeeType().isConstQualified())
         data().HasDeclaredCopyAssignmentWithConstParam = true;
+
+      if (Method->isUserProvided())
+        data().IsNaturallyTriviallyRelocatable = false;
     }
 
-    if (Method->isMoveAssignmentOperator())
+    if (Method->isMoveAssignmentOperator()) {
       SMKind |= SMF_MoveAssignment;
 
+      if (Method->isUserProvided())
+        data().IsNaturallyTriviallyRelocatable = false;
+    }
+
     // Keep the list of conversion functions up-to-date.
     if (auto *Conversion = dyn_cast<CXXConversionDecl>(D)) {
       // FIXME: We use the 'unsafe' accessor for the access specifier here,
@@ -1068,6 +1099,12 @@ void CXXRecordDecl::addedMember(Decl *D) {
     } else if (!T.isCXX98PODType(Context))
       data().PlainOldData = false;
 
+    // A trivially relocatable class is a class:
+    // -- all of whose members are either of reference type or of trivially
+    // relocatable type
+    if (!T->isReferenceType() && !T.isTriviallyRelocatableType(Context))
+      data().IsNaturallyTriviallyRelocatable = false;
+
     if (T->isReferenceType()) {
       if (!Field->hasInClassInitializer())
         data().HasUninitializedReferenceMember = true;
@@ -1423,8 +1460,10 @@ void CXXRecordDecl::addedEligibleSpecialMemberFunction(const CXXMethodDecl *MD,
   // See https://github.com/llvm/llvm-project/issues/59206
 
   if (const auto *DD = dyn_cast<CXXDestructorDecl>(MD)) {
-    if (DD->isUserProvided())
+    if (DD->isUserProvided()) {
       data().HasIrrelevantDestructor = false;
+      data().IsNaturallyTriviallyRelocatable = false;
+    }
     // If the destructor is explicitly defaulted and not trivial or not public
     // or if the destructor is deleted, we clear HasIrrelevantDestructor in
     // finishedDefaultedOrDeletedMember.
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 22666184c56ccf..073d3b68f6d730 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -2680,8 +2680,8 @@ bool QualType::isTriviallyRelocatableType(const ASTContext &Context) const {
     return false;
   } else if (!BaseElementType->isObjectType()) {
     return false;
-  } else if (const auto *RD = BaseElementType->getAsRecordDecl()) {
-    return RD->canPassInRegisters();
+  } else if (CXXRecordDecl *RD = BaseElementType->getAsCXXRecordDecl()) {
+    return RD->isTriviallyRelocatable();
   } else if (BaseElementType.isTriviallyCopyableType(Context)) {
     return true;
   } else {
diff --git a/clang/test/SemaCXX/attr-trivial-abi.cpp b/clang/test/SemaCXX/attr-trivial-abi.cpp
index c215f90eb124ce..c1f39047f8af84 100644
--- a/clang/test/SemaCXX/attr-trivial-abi.cpp
+++ b/clang/test/SemaCXX/attr-trivial-abi.cpp
@@ -5,15 +5,7 @@ void __attribute__((trivial_abi)) foo(); // expected-warning {{'trivial_abi' att
 // Should not crash.
 template <class>
 class __attribute__((trivial_abi)) a { a(a &&); };
-#if defined(_WIN64) && !defined(__MINGW32__)
-// On Windows/MSVC, to be trivial-for-calls, an object must be trivially copyable.
-// (And it is only trivially relocatable, currently, if it is trivial for calls.)
-// In this case, it is suppressed by an explicitly defined move constructor.
-// Similar concerns apply to later tests that have #if defined(_WIN64) && !defined(__MINGW32__)
-static_assert(!__is_trivially_relocatable(a<int>), "");
-#else
 static_assert(__is_trivially_relocatable(a<int>), "");
-#endif
 
 struct [[clang::trivial_abi]] S0 {
   int a;
@@ -39,14 +31,7 @@ struct __attribute__((trivial_abi)) S3_3 { // expected-warning {{'trivial_abi' c
   S3_3(S3_3 &&);
   S3_2 s32;
 };
-#ifdef __ORBIS__
-// The ClangABI4OrPS4 calling convention kind passes classes in registers if the
-// copy constructor is trivial for calls *or deleted*, while other platforms do
-// not accept deleted constructors.
-static_assert(__is_trivially_relocatable(S3_3), "");
-#else
 static_assert(!__is_trivially_relocatable(S3_3), "");
-#endif
 
 // Diagnose invalid trivial_abi even when the type is templated because it has a non-trivial field.
 template <class T>
@@ -118,30 +103,18 @@ struct __attribute__((trivial_abi)) CopyMoveDeleted { // expected-warning {{'tri
   CopyMoveDeleted(const CopyMoveDeleted &) = delete;
   CopyMoveDeleted(CopyMoveDeleted &&) = delete;
 };
-#ifdef __ORBIS__
 static_assert(__is_trivially_relocatable(CopyMoveDeleted), "");
-#else
-static_assert(!__is_trivially_relocatable(CopyMoveDeleted), "");
-#endif
 
 struct __attribute__((trivial_abi)) S18 { // expected-warning {{'trivial_abi' cannot be applied to 'S18'}} expected-note {{copy constructors and move constructors are all deleted}}
   CopyMoveDeleted a;
 };
-#ifdef __ORBIS__
 static_assert(__is_trivially_relocatable(S18), "");
-#else
-static_assert(!__is_trivially_relocatable(S18), "");
-#endif
 
 struct __attribute__((trivial_abi)) CopyDeleted {
   CopyDeleted(const CopyDeleted &) = delete;
   CopyDeleted(CopyDeleted &&) = default;
 };
-#if defined(_WIN64) && !defined(__MINGW32__)
-static_assert(!__is_trivially_relocatable(CopyDeleted), "");
-#else
 static_assert(__is_trivially_relocatable(CopyDeleted), "");
-#endif
 
 struct __attribute__((trivial_abi)) MoveDeleted {
   MoveDeleted(const MoveDeleted &) = default;
@@ -153,19 +126,11 @@ struct __attribute__((trivial_abi)) S19 { // expected-warning {{'trivial_abi' ca
   CopyDeleted a;
   MoveDeleted b;
 };
-#ifdef __ORBIS__
 static_assert(__is_trivially_relocatable(S19), "");
-#else
-static_assert(!__is_trivially_relocatable(S19), "");
-#endif
 
 // This is fine since the move constructor isn't deleted.
 struct __attribute__((trivial_abi)) S20 {
   int &&a; // a member of rvalue reference type deletes the copy constructor.
 };
-#if defined(_WIN64) && !defined(__MINGW32__)
-static_assert(!__is_trivially_relocatable(S20), "");
-#else
 static_assert(__is_trivially_relocatable(S20), "");
-#endif
 } // namespace deletedCopyMoveConstructor
diff --git a/clang/test/SemaCXX/is-trivially-relocatable.cpp b/clang/test/SemaCXX/is-trivially-relocatable.cpp
new file mode 100644
index 00000000000000..3fd620792b69ce
--- /dev/null
+++ b/clang/test/SemaCXX/is-trivially-relocatable.cpp
@@ -0,0 +1,106 @@
+// RUN: %clang_cc1 -std=c++03 -fsyntax-only -verify %s -triple x86_64-windows-msvc
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify %s -triple x86_64-windows-msvc
+// RUN: %clang_cc1 -std=c++03 -fsyntax-only -verify %s -triple x86_64-apple-darwin10
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify %s -triple x86_64-apple-darwin10
+
+// expected-no-diagnostics
+
+#if __cplusplus < 201103L
+#define static_assert(...) __extension__ _Static_assert(__VA_ARGS__, "")
+// cxx98-error at -1 {{variadic macros are a C99 feature}}
+#endif
+
+template <class T>
+struct Agg {
+  T t_;
+};
+
+template <class T>
+struct Der : T {
+};
+
+template <class T>
+struct Mut {
+  mutable T t_;
+};
+
+template <class T>
+struct Non {
+  Non(); // make it a non-aggregate
+  T t_;
+};
+
+struct CompletelyTrivial {
+};
+static_assert(__is_trivially_relocatable(CompletelyTrivial));
+static_assert(__is_trivially_relocatable(Agg<CompletelyTrivial>));
+static_assert(__is_trivially_relocatable(Der<CompletelyTrivial>));
+static_assert(__is_trivially_relocatable(Mut<CompletelyTrivial>));
+static_assert(__is_trivially_relocatable(Non<CompletelyTrivial>));
+
+struct NonTrivialDtor {
+  ~NonTrivialDtor();
+};
+static_assert(!__is_trivially_relocatable(NonTrivialDtor));
+static_assert(!__is_trivially_relocatable(Agg<NonTrivialDtor>));
+static_assert(!__is_trivially_relocatable(Der<NonTrivialDtor>));
+static_assert(!__is_trivially_relocatable(Mut<NonTrivialDtor>));
+static_assert(!__is_trivially_relocatable(Non<NonTrivialDtor>));
+
+struct NonTrivialCopyCtor {
+  NonTrivialCopyCtor(const NonTrivialCopyCtor&);
+};
+static_assert(!__is_trivially_relocatable(NonTrivialCopyCtor));
+static_assert(!__is_trivially_relocatable(Agg<NonTrivialCopyCtor>));
+static_assert(!__is_trivially_relocatable(Der<NonTrivialCopyCtor>));
+static_assert(!__is_trivially_relocatable(Mut<NonTrivialCopyCtor>));
+static_assert(!__is_trivially_relocatable(Non<NonTrivialCopyCtor>));
+
+struct NonTrivialMutableCopyCtor {
+  NonTrivialMutableCopyCtor(NonTrivialMutableCopyCtor&);
+};
+static_assert(!__is_trivially_relocatable(NonTrivialMutableCopyCtor));
+static_assert(!__is_trivially_relocatable(Agg<NonTrivialMutableCopyCtor>));
+static_assert(!__is_trivially_relocatable(Der<NonTrivialMutableCopyCtor>));
+static_assert(!__is_trivially_relocatable(Mut<NonTrivialMutableCopyCtor>));
+static_assert(!__is_trivially_relocatable(Non<NonTrivialMutableCopyCtor>));
+
+#if __cplusplus >= 201103L
+struct NonTrivialMoveCtor {
+  NonTrivialMoveCtor(NonTrivialMoveCtor&&);
+};
+static_assert(!__is_trivially_relocatable(NonTrivialMoveCtor));
+static_assert(!__is_trivially_relocatable(Agg<NonTrivialMoveCtor>));
+static_assert(!__is_trivially_relocatable(Der<NonTrivialMoveCtor>));
+static_assert(!__is_trivially_relocatable(Mut<NonTrivialMoveCtor>));
+static_assert(!__is_trivially_relocatable(Non<NonTrivialMoveCtor>));
+#endif
+
+struct NonTrivialCopyAssign {
+  NonTrivialCopyAssign& operator=(const NonTrivialCopyAssign&);
+};
+static_assert(!__is_trivially_relocatable(NonTrivialCopyAssign));
+static_assert(!__is_trivially_relocatable(Agg<NonTrivialCopyAssign>));
+static_assert(!__is_trivially_relocatable(Der<NonTrivialCopyAssign>));
+static_assert(!__is_trivially_relocatable(Mut<NonTrivialCopyAssign>));
+static_assert(!__is_trivially_relocatable(Non<NonTrivialCopyAssign>));
+
+struct NonTrivialMutableCopyAssign {
+  NonTrivialMutableCopyAssign& operator=(NonTrivialMutableCopyAssign&);
+};
+static_assert(!__is_trivially_relocatable(NonTrivialMutableCopyAssign));
+static_assert(!__is_trivially_relocatable(Agg<NonTrivialMutableCopyAssign>));
+static_assert(!__is_trivially_relocatable(Der<NonTrivialMutableCopyAssign>));
+static_assert(!__is_trivially_relocatable(Mut<NonTrivialMutableCopyAssign>));
+static_assert(!__is_trivially_relocatable(Non<NonTrivialMutableCopyAssign>));
+
+#if __cplusplus >= 201103L
+struct NonTrivialMoveAssign {
+  NonTrivialMoveAssign& operator=(NonTrivialMoveAssign&&);
+};
+static_assert(!__is_trivially_relocatable(NonTrivialMoveAssign));
+static_assert(!__is_trivially_relocatable(Agg<NonTrivialMoveAssign>));
+static_assert(!__is_trivially_relocatable(Der<NonTrivialMoveAssign>));
+static_assert(!__is_trivially_relocatable(Mut<NonTrivialMoveAssign>));
+static_assert(!__is_trivially_relocatable(Non<NonTrivialMoveAssign>));
+#endif
diff --git a/clang/test/SemaCXX/type-traits.cpp b/clang/test/SemaCXX/type-traits.cpp
index 14ec17989ec7c7..4f9c7719446147 100644
--- a/clang/test/SemaCXX/type-traits.cpp
+++ b/clang/test/SemaCXX/type-traits.cpp
@@ -3342,14 +3342,34 @@ static_assert(__is_trivially_relocatable(int));
 static_assert(__is_trivially_relocatable(int[]));
 static_assert(__is_trivially_relocatable(const int));
 static_assert(__is_trivially_relocatable(volatile int));
+static_assert(__is_trivially_relocatable(AggregateTemplate<int>));
+static_assert(__is_trivially_relocatable(AggregateTemplate<int[2]>));
+static_assert(__is_trivially_relocatable(AggregateTemplate<const int>));
+static_assert(__is_trivially_relocatable(AggregateTemplate<const int[2]>));
+static_assert(__is_trivially_relocatable(AggregateTemplate<volatile int>));
+static_assert(__is_trivially_relocatable(AggregateTemplate<volatile int[2]>));
+static_assert(!__is_trivially_relocatable(int&));
+static_assert(!__is_trivially_relocatable(const int&));
+static_assert(__is_trivially_relocatable(AggregateTemplate<int&>));
+static_assert(__is_trivially_relocatable(AggregateTemplate<const int&>));
+
+static_assert(!__is_trivially_relocatable(Polymorph));
+static_assert(!__is_trivially_relocatable(InheritPolymorph));
+static_assert(!__is_trivially_relocatable(AggregateTemplate<Polymorph>));
+
+static_assert(__is_trivially_relocatable(HasPrivateBase));
+static_assert(__is_trivially_relocatable(HasProtectedBase));
+static_assert(!__is_trivially_relocatable(HasVirtBase));
 
 enum Enum {};
 static_assert(__is_trivially_relocatable(Enum));
 static_assert(__is_trivially_relocatable(Enum[]));
+static_assert(__is_trivially_relocatable(AggregateTemplate<Enum>));
 
 union Union {int x;};
 static_assert(__is_trivially_relocatable(Union));
 static_assert(__is_trivially_relocatable(Union[]));
+static_assert(__is_trivially_relocatable(AggregateTemplate<Union>));
 
 struct Trivial {};
 static_assert(__is_trivially_relocatable(Trivial));
@@ -3359,6 +3379,7 @@ static_assert(__is_trivially_relocatable(volatile Trivial));
 static_assert(__is_trivially_relocatable(Trivial[]));
 static_assert(__is_trivially_relocatable(const Trivial[]));
 static_assert(__is_trivially_relocatable(volatile Trivial[]));
+static_assert(__is_trivially_relocatable(AggregateTemplate<Trivial>));
 
 static_assert(__is_trivially_relocatable(int[10]));
 static_assert(__is_trivially_relocatable(const int[10]));
@@ -3386,18 +3407,21 @@ static_assert(!__is_trivially_relocatable(NontrivialDtor));
 static_assert(!__is_trivially_relocatable(NontrivialDtor[]));
 static_assert(!__is_trivially_relocatable(const NontrivialDtor));
 static_assert(!__is_trivially_relocatable(volatile NontrivialDtor));
+static_assert(!__is_trivially_relocatable(AggregateTemplate<NontrivialDtor>));
 
 struct NontrivialCopyCtor {
   NontrivialCopyCtor(const NontrivialCopyCtor&) {}
 };
 static_assert(!__is_trivially_relocatable(NontrivialCopyCtor));
 static_assert(!__is_trivially_relocatable(NontrivialCopyCtor[]));
+static_assert(!__is_trivially_relocatable(AggregateTemplate<NontrivialCopyCtor>));
 
 struct NontrivialMoveCtor {
   NontrivialMoveCtor(NontrivialMoveCtor&&) {}
 };
 static_assert(!__is_trivially_relocatable(NontrivialMoveCtor));
 static_assert(!__is_trivially_relocatable(NontrivialMoveCtor[]));
+static_assert(!__is_trivially_relocatable(AggregateTemplate<NontrivialMoveCtor>));
 
 struct [[clang::trivial_abi]] TrivialAbiNontrivialDtor {
   ~TrivialAbiNontrivialDtor() {}
@@ -3406,6 +3430,8 @@ static_assert(__is_trivially_relocatable(TrivialAbiNontrivialDtor));
 static_assert(__is_trivially_relocatable(TrivialAbiNontrivialDtor[]));
 static_assert(__is_trivially_relocatable(const TrivialAbiNontrivialDtor));
 static_assert(__is_trivially_relocatable(volatile TrivialAbiNontrivialDtor));
+static_assert(__is_trivially_relocatable(AggregateTemplate<TrivialAbiNontrivialDtor>));
+static_assert(__is_trivially_relocatable(NonAggregateTemplate<TrivialAbiNontrivialDtor>));
 
 struct [[clang::trivial_abi]] TrivialAbiNontrivialCopyCtor {
   TrivialAbiNontrivialCopyCtor(const TrivialAbiNontrivialCopyCtor&) {}
@@ -3414,6 +3440,8 @@ static_assert(__is_trivially_relocatable(TrivialAbiNontrivialCopyCtor));
 static_assert(__is_trivially_relocatable(TrivialAbiNontrivialCopyCtor[]));
 static_assert(__is_trivially_relocatable(const TrivialAbiNontrivialCopyCtor));
 static_assert(__is_trivially_relocatable(volatile TrivialAbiNontrivialCopyCtor));
+static_assert(__is_trivially_relocatable(AggregateTemplate<TrivialAbiNontrivialCopyCtor>));
+static_assert(__is_trivially_relocatable(NonAggregateTemplate<TrivialAbiNontrivialCopyCtor>));
 
 // A more complete set of tests for the behavior of trivial_abi can be found in
 // clang/test/SemaCXX/attr-trivial-abi.cpp
@@ -3424,6 +3452,34 @@ static_assert(__is_trivially_relocatable(TrivialAbiNontrivialMoveCtor));
 static_assert(__is_trivially_relocatable(TrivialAbiNontrivialMoveCtor[]));
 static_assert(__is_trivially_relocatable(const TrivialAbiNontrivialMoveCtor));
 static_assert(__is_trivially_relocatable(volatile TrivialAbiNontrivialMoveCtor));
+static_assert(__is_trivially_relocatable(AggregateTemplate<TrivialAbiNontrivialMoveCtor>));
+static_assert(__is_trivially_relocatable(NonAggregateTemplate<TrivialAbiNontrivialMoveCtor>));
+
+struct NontrivialNonConstCopyConstructor {
+  NontrivialNonConstCopyConstructor();
+  NontrivialNonConstCopyConstructor(NontrivialNonConstCopyConstructor&);
+  NontrivialNonConstCopyConstructor(const NontrivialNonConstCopyConstructor&) = default;
+  NontrivialNonConstCopyConstructor& operator=(const NontrivialNonConstCopyConstructor&) = default;
+  ~NontrivialNonConstCopyConstructor() = default;
+};
+static_assert(!__is_trivially_relocatable(NontrivialNonConstCopyConstructor));
+static_assert(!__is_trivially_relocatable(AggregateTemplate<NontrivialNonConstCopyConstructor>));
+
+struct NontrivialCopyAssignment {
+  NontrivialCopyAssignment(const NontrivialCopyAssignment&) = default;
+  NontrivialCopyAssignment& operator=(const NontrivialCopyAssignment&);
+  ~NontrivialCopyAssignment() = default;
+};
+static_assert(!__is_trivially_relocatable(NontrivialCopyAssignment));
+static_assert(!__is_trivially_relocatable(AggregateTemplate<NontrivialCopyAssignment>));
+
+struct NontrivialMoveAssignment {
+  NontrivialMoveAssignment(NontrivialMoveAssignment&&) = default;
+  NontrivialMoveAssignment& operator=(NontrivialMoveAssignment&&);
+  ~NontrivialMoveAssignment() = default;
+};
+static_assert(!__is_trivially_relocatable(NontrivialMoveAssignment));
+static_assert(!__is_trivially_relocatable(AggregateTemplate<NontrivialMoveAssignment>));
 
 } // namespace is_trivially_relocatable
 

>From a7ff71da99cd158e9bc608b43577cb854dfc7fae Mon Sep 17 00:00:00 2001
From: Amirreza Ashouri <ar.ashouri999 at gmail.com>
Date: Tue, 12 Mar 2024 12:38:44 +0330
Subject: [PATCH 2/5] Pull together two similar checks into one `if` statement.

---
 clang/lib/AST/DeclCXX.cpp | 9 +++------
 1 file changed, 3 insertions(+), 6 deletions(-)

diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp
index 5cd1e6d8d720ef..5811e746b0be85 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -879,18 +879,15 @@ void CXXRecordDecl::addedMember(Decl *D) {
           Method->getNonObjectParameter(0)->getType()->getAs<ReferenceType>();
       if (!ParamTy || ParamTy->getPointeeType().isConstQualified())
         data().HasDeclaredCopyAssignmentWithConstParam = true;
-
-      if (Method->isUserProvided())
-        data().IsNaturallyTriviallyRelocatable = false;
     }
 
     if (Method->isMoveAssignmentOperator()) {
       SMKind |= SMF_MoveAssignment;
-
-      if (Method->isUserProvided())
-        data().IsNaturallyTriviallyRelocatable = false;
     }
 
+    if (Method->isUserProvided() && (Method->isCopyAssignment() || Method->isMoveAssignment()))
+      data().IsNaturallyTriviallyRelocatable = false;
+
     // Keep the list of conversion functions up-to-date.
     if (auto *Conversion = dyn_cast<CXXConversionDecl>(D)) {
       // FIXME: We use the 'unsafe' accessor for the access specifier here,

>From da761edd8144c176ed8850d4b8b77eafc3ff4064 Mon Sep 17 00:00:00 2001
From: Amirreza Ashouri <ar.ashouri999 at gmail.com>
Date: Tue, 12 Mar 2024 16:17:55 +0330
Subject: [PATCH 3/5] Fix formatting

---
 clang/lib/AST/DeclCXX.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp
index 5811e746b0be85..c6e38ba1d2b158 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -885,7 +885,8 @@ void CXXRecordDecl::addedMember(Decl *D) {
       SMKind |= SMF_MoveAssignment;
     }
 
-    if (Method->isUserProvided() && (Method->isCopyAssignment() || Method->isMoveAssignment()))
+    if (Method->isUserProvided() && (Method->isCopyAssignmentOperator() ||
+                                     Method->isMoveAssignmentOperator()))
       data().IsNaturallyTriviallyRelocatable = false;
 
     // Keep the list of conversion functions up-to-date.

>From 75f1f4a96a3e38ad19731129f7926b5cedba9d82 Mon Sep 17 00:00:00 2001
From: Amirreza Ashouri <ar.ashouri999 at gmail.com>
Date: Sun, 17 Mar 2024 01:13:21 +0330
Subject: [PATCH 4/5] Consider a class as trivially relocatable when the
 user-provided special member is ineligible

---
 clang/lib/AST/DeclCXX.cpp                     |  27 +++--
 .../test/SemaCXX/is-trivially-relocatable.cpp | 100 ++++++++++++++++++
 2 files changed, 112 insertions(+), 15 deletions(-)

diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp
index c6e38ba1d2b158..263db64c330b4d 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -842,14 +842,6 @@ void CXXRecordDecl::addedMember(Decl *D) {
               ? !Constructor->isImplicit()
               : (Constructor->isUserProvided() || Constructor->isExplicit()))
         data().Aggregate = false;
-
-      // A trivially relocatable class is a class:
-      // -- where no eligible copy constructor, move constructor, copy
-      // assignment operator, move assignment operator, or destructor is
-      // user-provided,
-      if (Constructor->isUserProvided() && (Constructor->isCopyConstructor() ||
-                                            Constructor->isMoveConstructor()))
-        data().IsNaturallyTriviallyRelocatable = false;
     }
   }
 
@@ -881,13 +873,8 @@ void CXXRecordDecl::addedMember(Decl *D) {
         data().HasDeclaredCopyAssignmentWithConstParam = true;
     }
 
-    if (Method->isMoveAssignmentOperator()) {
+    if (Method->isMoveAssignmentOperator())
       SMKind |= SMF_MoveAssignment;
-    }
-
-    if (Method->isUserProvided() && (Method->isCopyAssignmentOperator() ||
-                                     Method->isMoveAssignmentOperator()))
-      data().IsNaturallyTriviallyRelocatable = false;
 
     // Keep the list of conversion functions up-to-date.
     if (auto *Conversion = dyn_cast<CXXConversionDecl>(D)) {
@@ -1460,7 +1447,6 @@ void CXXRecordDecl::addedEligibleSpecialMemberFunction(const CXXMethodDecl *MD,
   if (const auto *DD = dyn_cast<CXXDestructorDecl>(MD)) {
     if (DD->isUserProvided()) {
       data().HasIrrelevantDestructor = false;
-      data().IsNaturallyTriviallyRelocatable = false;
     }
     // If the destructor is explicitly defaulted and not trivial or not public
     // or if the destructor is deleted, we clear HasIrrelevantDestructor in
@@ -1477,6 +1463,17 @@ void CXXRecordDecl::addedEligibleSpecialMemberFunction(const CXXMethodDecl *MD,
       data().IsAnyDestructorNoReturn = true;
   }
 
+  // A trivially relocatable class is a class:
+  // -- where no eligible copy constructor, move constructor, copy
+  // assignment operator, move assignment operator, or destructor is
+  // user-provided,
+  if (MD->isUserProvided() &&
+      (SMKind & (SMF_CopyConstructor | SMF_MoveConstructor |
+                 SMF_CopyAssignment | SMF_MoveAssignment | SMF_Destructor)) !=
+          0u) {
+    data().IsNaturallyTriviallyRelocatable = false;
+  }
+
   if (!MD->isImplicit() && !MD->isUserProvided()) {
     // This method is user-declared but not user-provided. We can't work
     // out whether it's trivial yet (not until we get to the end of the
diff --git a/clang/test/SemaCXX/is-trivially-relocatable.cpp b/clang/test/SemaCXX/is-trivially-relocatable.cpp
index 3fd620792b69ce..5198bf40d7c828 100644
--- a/clang/test/SemaCXX/is-trivially-relocatable.cpp
+++ b/clang/test/SemaCXX/is-trivially-relocatable.cpp
@@ -104,3 +104,103 @@ static_assert(!__is_trivially_relocatable(Der<NonTrivialMoveAssign>));
 static_assert(!__is_trivially_relocatable(Mut<NonTrivialMoveAssign>));
 static_assert(!__is_trivially_relocatable(Non<NonTrivialMoveAssign>));
 #endif
+
+#if __cplusplus >= 202002L
+template<bool B>
+struct EligibleNonTrivialDefaultCtor {
+    EligibleNonTrivialDefaultCtor() requires B;
+    EligibleNonTrivialDefaultCtor() = default;
+};
+// Only the Rule of 5 members (not default ctor) affect trivial relocatability.
+static_assert(__is_trivially_relocatable(EligibleNonTrivialDefaultCtor<true>));
+static_assert(__is_trivially_relocatable(EligibleNonTrivialDefaultCtor<false>));
+
+template<bool B>
+struct IneligibleNonTrivialDefaultCtor {
+    IneligibleNonTrivialDefaultCtor();
+    IneligibleNonTrivialDefaultCtor() requires B = default;
+};
+// Only the Rule of 5 members (not default ctor) affect trivial relocatability.
+static_assert(__is_trivially_relocatable(IneligibleNonTrivialDefaultCtor<true>));
+static_assert(__is_trivially_relocatable(IneligibleNonTrivialDefaultCtor<false>));
+
+template<bool B>
+struct EligibleNonTrivialCopyCtor {
+    EligibleNonTrivialCopyCtor(const EligibleNonTrivialCopyCtor&) requires B;
+    EligibleNonTrivialCopyCtor(const EligibleNonTrivialCopyCtor&) = default;
+};
+static_assert(!__is_trivially_relocatable(EligibleNonTrivialCopyCtor<true>));
+static_assert(__is_trivially_relocatable(EligibleNonTrivialCopyCtor<false>));
+
+template<bool B>
+struct IneligibleNonTrivialCopyCtor {
+    IneligibleNonTrivialCopyCtor(const IneligibleNonTrivialCopyCtor&);
+    IneligibleNonTrivialCopyCtor(const IneligibleNonTrivialCopyCtor&) requires B = default;
+};
+static_assert(__is_trivially_relocatable(IneligibleNonTrivialCopyCtor<true>));
+static_assert(!__is_trivially_relocatable(IneligibleNonTrivialCopyCtor<false>));
+
+template<bool B>
+struct EligibleNonTrivialMoveCtor {
+    EligibleNonTrivialMoveCtor(EligibleNonTrivialMoveCtor&&) requires B;
+    EligibleNonTrivialMoveCtor(EligibleNonTrivialMoveCtor&&) = default;
+};
+static_assert(!__is_trivially_relocatable(EligibleNonTrivialMoveCtor<true>));
+static_assert(__is_trivially_relocatable(EligibleNonTrivialMoveCtor<false>));
+
+template<bool B>
+struct IneligibleNonTrivialMoveCtor {
+    IneligibleNonTrivialMoveCtor(IneligibleNonTrivialMoveCtor&&);
+    IneligibleNonTrivialMoveCtor(IneligibleNonTrivialMoveCtor&&) requires B = default;
+};
+static_assert(__is_trivially_relocatable(IneligibleNonTrivialMoveCtor<true>));
+static_assert(!__is_trivially_relocatable(IneligibleNonTrivialMoveCtor<false>));
+
+template<bool B>
+struct EligibleNonTrivialCopyAssign {
+    EligibleNonTrivialCopyAssign& operator=(const EligibleNonTrivialCopyAssign&) requires B;
+    EligibleNonTrivialCopyAssign& operator=(const EligibleNonTrivialCopyAssign&) = default;
+};
+static_assert(!__is_trivially_relocatable(EligibleNonTrivialCopyAssign<true>));
+static_assert(__is_trivially_relocatable(EligibleNonTrivialCopyAssign<false>));
+
+template<bool B>
+struct IneligibleNonTrivialCopyAssign {
+    IneligibleNonTrivialCopyAssign& operator=(const IneligibleNonTrivialCopyAssign&);
+    IneligibleNonTrivialCopyAssign& operator=(const IneligibleNonTrivialCopyAssign&) requires B = default;
+};
+static_assert(__is_trivially_relocatable(IneligibleNonTrivialCopyAssign<true>));
+static_assert(!__is_trivially_relocatable(IneligibleNonTrivialCopyAssign<false>));
+
+template<bool B>
+struct EligibleNonTrivialMoveAssign {
+    EligibleNonTrivialMoveAssign& operator=(EligibleNonTrivialMoveAssign&&) requires B;
+    EligibleNonTrivialMoveAssign& operator=(EligibleNonTrivialMoveAssign&&) = default;
+};
+static_assert(!__is_trivially_relocatable(EligibleNonTrivialMoveAssign<true>));
+static_assert(__is_trivially_relocatable(EligibleNonTrivialMoveAssign<false>));
+
+template<bool B>
+struct IneligibleNonTrivialMoveAssign {
+    IneligibleNonTrivialMoveAssign& operator=(IneligibleNonTrivialMoveAssign&&);
+    IneligibleNonTrivialMoveAssign& operator=(IneligibleNonTrivialMoveAssign&&) requires B = default;
+};
+static_assert(__is_trivially_relocatable(IneligibleNonTrivialMoveAssign<true>));
+static_assert(!__is_trivially_relocatable(IneligibleNonTrivialMoveAssign<false>));
+
+template<bool B>
+struct EligibleNonTrivialDtor {
+    ~EligibleNonTrivialDtor() requires B;
+    ~EligibleNonTrivialDtor() = default;
+};
+static_assert(!__is_trivially_relocatable(EligibleNonTrivialDtor<true>));
+static_assert(__is_trivially_relocatable(EligibleNonTrivialDtor<false>));
+
+template<bool B>
+struct IneligibleNonTrivialDtor {
+    ~IneligibleNonTrivialDtor();
+    ~IneligibleNonTrivialDtor() requires B = default;
+};
+static_assert(__is_trivially_relocatable(IneligibleNonTrivialDtor<true>));
+static_assert(!__is_trivially_relocatable(IneligibleNonTrivialDtor<false>));
+#endif

>From 7ea6d2ccfcff225d7703893ff782e9a068f5e6b2 Mon Sep 17 00:00:00 2001
From: Amirreza Ashouri <ar.ashouri999 at gmail.com>
Date: Sun, 17 Mar 2024 18:04:37 +0330
Subject: [PATCH 5/5] Change `CXXRecordDecl *` to `const auto *`

---
 clang/lib/AST/Type.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 073d3b68f6d730..58f15ad0ca870f 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -2680,7 +2680,7 @@ bool QualType::isTriviallyRelocatableType(const ASTContext &Context) const {
     return false;
   } else if (!BaseElementType->isObjectType()) {
     return false;
-  } else if (CXXRecordDecl *RD = BaseElementType->getAsCXXRecordDecl()) {
+  } else if (const auto *RD = BaseElementType->getAsCXXRecordDecl()) {
     return RD->isTriviallyRelocatable();
   } else if (BaseElementType.isTriviallyCopyableType(Context)) {
     return true;



More information about the cfe-commits mailing list