[clang] [Clang] Implement the core language parts of P2786 - Trivial relocation (PR #127636)

via cfe-commits cfe-commits at lists.llvm.org
Mon May 5 22:34:10 PDT 2025


https://github.com/cor3ntin updated https://github.com/llvm/llvm-project/pull/127636

>From fc2ddba14ffa43c10b6151fbd46dfd1301dc5da6 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Tue, 18 Feb 2025 14:37:32 +0100
Subject: [PATCH 01/27] [Clang][WIP] Trivial relocation

---
 .../clang/AST/CXXRecordDeclDefinitionBits.def |   8 +
 clang/include/clang/AST/DeclCXX.h             |  81 +++++-
 clang/include/clang/AST/Type.h                |   4 +
 clang/include/clang/Basic/Builtins.td         |   6 +
 .../clang/Basic/DiagnosticParseKinds.td       |   3 +
 .../clang/Basic/DiagnosticSemaKinds.td        |   4 +
 clang/include/clang/Basic/TokenKinds.def      |   4 +
 clang/include/clang/Parse/Parser.h            |  13 +
 clang/include/clang/Sema/Sema.h               |  19 +-
 clang/lib/AST/Decl.cpp                        |   1 +
 clang/lib/AST/DeclCXX.cpp                     |  20 +-
 clang/lib/AST/Type.cpp                        |  24 ++
 clang/lib/CodeGen/CGBuiltin.cpp               |   1 +
 clang/lib/Frontend/InitPreprocessor.cpp       |   3 +-
 clang/lib/Parse/ParseDeclCXX.cpp              | 121 +++++++--
 clang/lib/Parse/Parser.cpp                    |   2 +
 clang/lib/Sema/SemaChecking.cpp               |  51 ++++
 clang/lib/Sema/SemaDecl.cpp                   |  25 +-
 clang/lib/Sema/SemaDeclCXX.cpp                | 214 +++++++++++++++
 clang/lib/Sema/SemaExprCXX.cpp                |   6 +
 clang/lib/Sema/SemaTemplateInstantiate.cpp    |   4 +
 .../Parser/cxx2c-trivially-relocatable.cpp    |   8 +
 .../SemaCXX/cxx2c-trivially-relocatable.cpp   | 257 ++++++++++++++++++
 23 files changed, 837 insertions(+), 42 deletions(-)
 create mode 100644 clang/test/Parser/cxx2c-trivially-relocatable.cpp
 create mode 100644 clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp

diff --git a/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def b/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def
index 6620840df0ced..7633a987673e9 100644
--- a/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def
+++ b/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def
@@ -224,6 +224,10 @@ FIELD(StructuralIfLiteral, 1, NO_MERGE)
 /// explicitly deleted or defaulted).
 FIELD(UserProvidedDefaultConstructor, 1, NO_MERGE)
 
+FIELD(UserProvidedMoveAssignment, 1, NO_MERGE)
+FIELD(UserProvidedCopyAssignment, 1, NO_MERGE)
+FIELD(ExplicitlyDeletedMoveAssignment, 1, NO_MERGE)
+
 /// The special members which have been declared for this class,
 /// either by the user or implicitly.
 FIELD(DeclaredSpecialMembers, 6, MERGE_OR)
@@ -253,4 +257,8 @@ FIELD(IsAnyDestructorNoReturn, 1, NO_MERGE)
 /// type that is intangible). HLSL only.
 FIELD(IsHLSLIntangible, 1, NO_MERGE)
 
+FIELD(IsTriviallyRelocatable, 1, NO_MERGE)
+
+FIELD(IsReplaceable, 1, NO_MERGE)
+
 #undef FIELD
diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h
index dd325815ee28d..e5652f515fdcd 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -127,6 +127,33 @@ class AccessSpecDecl : public Decl {
   static bool classofKind(Kind K) { return K == AccessSpec; }
 };
 
+enum class RelocatableOrReplaceableClassSpecifierKind {
+  Relocatable,
+  Replaceable
+};
+
+template <RelocatableOrReplaceableClassSpecifierKind MK>
+class BasicRelocatableOrReplaceableClassSpecifier {
+public:
+  BasicRelocatableOrReplaceableClassSpecifier() = default;
+  BasicRelocatableOrReplaceableClassSpecifier(SourceLocation Begin)
+      : Loc(Begin) {}
+  void Set(SourceLocation Begin) { Loc = Begin; }
+
+  bool isSet() const { return !Loc.isInvalid(); }
+
+  SourceLocation getLocation() const { return Loc; }
+
+private:
+  SourceLocation Loc;
+};
+
+using TriviallyRelocatableSpecifier =
+    BasicRelocatableOrReplaceableClassSpecifier<
+        RelocatableOrReplaceableClassSpecifierKind::Relocatable>;
+using ReplaceableSpecifier = BasicRelocatableOrReplaceableClassSpecifier<
+    RelocatableOrReplaceableClassSpecifierKind::Replaceable>;
+
 /// Represents a base class of a C++ class.
 ///
 /// Each CXXBaseSpecifier represents a single, direct base class (or
@@ -349,6 +376,10 @@ class CXXRecordDecl : public RecordDecl {
     /// This is actually currently stored in reverse order.
     LazyDeclPtr FirstFriend;
 
+    TriviallyRelocatableSpecifier TriviallyRelocatableSpecifier;
+
+    ReplaceableSpecifier ReplaceableSpecifier;
+
     DefinitionData(CXXRecordDecl *D);
 
     /// Retrieve the set of direct base classes.
@@ -716,12 +747,19 @@ class CXXRecordDecl : public RecordDecl {
   /// \c true if a defaulted move constructor for this class would be
   /// deleted.
   bool defaultedMoveConstructorIsDeleted() const {
-    assert((!needsOverloadResolutionForMoveConstructor() ||
-            (data().DeclaredSpecialMembers & SMF_MoveConstructor)) &&
-           "this property has not yet been computed by Sema");
+    // assert((!needsOverloadResolutionForMoveConstructor() ||
+    //         (data().DeclaredSpecialMembers & SMF_MoveConstructor)) &&
+    //        "this property has not yet been computed by Sema");
     return data().DefaultedMoveConstructorIsDeleted;
   }
 
+  bool defaultedMoveAssignmentIsDeleted() const {
+    // assert((!needsOverloadResolutionForMoveAssignment() ||
+    //         (data().DeclaredSpecialMembers & SMF_MoveAssignment)) &&
+    //        "this property has not yet been computed by Sema");
+    return data().DefaultedMoveAssignmentIsDeleted;
+  }
+
   /// \c true if a defaulted destructor for this class would be deleted.
   bool defaultedDestructorIsDeleted() const {
     assert((!needsOverloadResolutionForDestructor() ||
@@ -806,6 +844,18 @@ class CXXRecordDecl : public RecordDecl {
     return data().UserDeclaredSpecialMembers & SMF_CopyConstructor;
   }
 
+  bool hasUserProvidedCopyAssignment() const {
+    return data().UserProvidedCopyAssignment;
+  }
+
+  bool hasUserProvidedMoveAssignment() const {
+    return data().UserProvidedCopyAssignment;
+  }
+
+  bool hasExplicitlyDeletedMoveAssignment() const {
+    return data().ExplicitlyDeletedMoveAssignment;
+  }
+
   /// Determine whether this class needs an implicit copy
   /// constructor to be lazily declared.
   bool needsImplicitCopyConstructor() const {
@@ -1471,6 +1521,24 @@ class CXXRecordDecl : public RecordDecl {
     return isLiteral() && data().StructuralIfLiteral;
   }
 
+  TriviallyRelocatableSpecifier getTriviallyRelocatableSpecifier() const {
+    return data().TriviallyRelocatableSpecifier;
+  }
+
+  ReplaceableSpecifier getReplaceableSpecifier() const {
+    return data().ReplaceableSpecifier;
+  }
+
+  bool isTriviallyRelocatable() const { return data().IsTriviallyRelocatable; }
+
+  void setIsTriviallyRelocatable(bool Set) {
+    data().IsTriviallyRelocatable = Set;
+  }
+
+  bool isReplaceable() const { return data().IsReplaceable; }
+
+  void setIsReplaceable(bool Set) { data().IsReplaceable = Set; }
+
   /// Notify the class that this destructor is now selected.
   ///
   /// Important properties of the class depend on destructor properties. Since
@@ -1897,6 +1965,13 @@ class CXXRecordDecl : public RecordDecl {
     return K >= firstCXXRecord && K <= lastCXXRecord;
   }
   void markAbstract() { data().Abstract = true; }
+
+  void setTriviallyRelocatableSpecifier(TriviallyRelocatableSpecifier TRS) {
+    data().TriviallyRelocatableSpecifier = TRS;
+  }
+  void setReplaceableSpecifier(ReplaceableSpecifier MRS) {
+    data().ReplaceableSpecifier = MRS;
+  }
 };
 
 /// Store information needed for an explicit specifier.
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 02a6fb5333538..841d6a669dbf8 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -1136,6 +1136,10 @@ class QualType {
   /// Return true if this is a trivially relocatable type.
   bool isTriviallyRelocatableType(const ASTContext &Context) const;
 
+  bool isCppTriviallyRelocatableType(const ASTContext &Context) const;
+
+  bool isReplaceableType(const ASTContext &Context) const;
+
   /// Returns true if it is a class and it might be dynamic.
   bool mayBeDynamicClass() const;
 
diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td
index 37bdac814a347..11b1e247237a7 100644
--- a/clang/include/clang/Basic/Builtins.td
+++ b/clang/include/clang/Basic/Builtins.td
@@ -2853,6 +2853,12 @@ def MemMove : LibBuiltin<"string.h"> {
   let AddBuiltinPrefixedAlias = 1;
 }
 
+def BuiltinTriviallyRelocate : Builtin {
+  let Spellings = ["__builtin_trivially_relocate"];
+  let Attributes = [FunctionWithBuiltinPrefix, CustomTypeChecking, NoThrow];
+  let Prototype = "void*(void*, void*, size_t)";
+}
+
 def StrCpy : LibBuiltin<"string.h"> {
   let Spellings = ["strcpy"];
   let Attributes = [NoThrow];
diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td
index 49ac27c915cbf..8392558a3db1b 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -1071,6 +1071,9 @@ def err_access_specifier_interface : Error<
 def err_duplicate_class_virt_specifier : Error<
   "class already marked '%0'">;
 
+def err_duplicate_class_relocation_specifier : Error<
+  "class already marked %select{'trivially_relocatable_if_eligible'|'replaceable_if_eligible'}0">;
+
 def err_duplicate_virt_specifier : Error<
   "class member already marked '%0'">;
 
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index e5a7cdc14a737..63806eeaea50b 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -12664,6 +12664,10 @@ def err_builtin_invalid_arg_type: Error<
   "%plural{0:|: }3"
   "%plural{[0,3]:type|:types}1 (was %4)">;
 
+def err_builtin_trivially_relocate_invalid_arg_type: Error <
+  "argument%select{s|||}0 to '__builtin_trivially_relocate' must be"
+  "%select{a pointer|non-const|relocatable|the same}0">;
+
 def err_builtin_matrix_disabled: Error<
   "matrix types extension is disabled. Pass -fenable-matrix to enable it">;
 def err_matrix_index_not_integer: Error<
diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def
index fb53d88deea4a..e829ff056e213 100644
--- a/clang/include/clang/Basic/TokenKinds.def
+++ b/clang/include/clang/Basic/TokenKinds.def
@@ -557,8 +557,12 @@ TYPE_TRAIT_2(__reference_converts_from_temporary, ReferenceConvertsFromTemporary
 // is not exposed to users.
 TYPE_TRAIT_2(/*EmptySpellingName*/, IsDeducible, KEYCXX)
 TYPE_TRAIT_1(__is_bitwise_cloneable, IsBitwiseCloneable, KEYALL)
+TYPE_TRAIT_1(__is_cpp_trivially_relocatable, IsCppTriviallyRelocatable, KEYCXX)
+TYPE_TRAIT_1(__builtin_is_replaceable, IsReplaceable, KEYCXX)
 TYPE_TRAIT_1(__builtin_structured_binding_size, StructuredBindingSize, KEYCXX)
 
+
+
 // Embarcadero Expression Traits
 EXPRESSION_TRAIT(__is_lvalue_expr, IsLValueExpr, KEYCXX)
 EXPRESSION_TRAIT(__is_rvalue_expr, IsRValueExpr, KEYCXX)
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 46a2d26beb7f9..e8a1e14661dbb 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -264,6 +264,8 @@ class Parser : public CodeCompletionHandler {
   mutable IdentifierInfo *Ident_final;
   mutable IdentifierInfo *Ident_GNU_final;
   mutable IdentifierInfo *Ident_override;
+  mutable IdentifierInfo *Ident_trivially_relocatable_if_eligible;
+  mutable IdentifierInfo *Ident_replaceable_if_eligible;
 
   // C++2a contextual keywords.
   mutable IdentifierInfo *Ident_import;
@@ -3196,6 +3198,17 @@ class Parser : public CodeCompletionHandler {
                                           SourceLocation FriendLoc);
 
   bool isCXX11FinalKeyword() const;
+
+  bool isCXX2CTriviallyRelocatableKeyword(Token Tok) const;
+  bool isCXX2CTriviallyRelocatableKeyword() const;
+  void ParseOptionalCXX2CTriviallyRelocatableSpecifier(
+      TriviallyRelocatableSpecifier &TRS);
+
+  bool isCXX2CReplaceableKeyword(Token Tok) const;
+  bool isCXX2CReplaceableKeyword() const;
+  void ParseOptionalCXX2CReplaceableSpecifier(ReplaceableSpecifier &MRS);
+
+  bool isClassCompatibleKeyword(Token Tok) const;
   bool isClassCompatibleKeyword() const;
 
   /// DeclaratorScopeObj - RAII object used in Parser::ParseDirectDeclarator to
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 759c6f99b3d85..66333900d18b1 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -4309,20 +4309,29 @@ class Sema final : public SemaBase {
   /// Invoked when we enter a tag definition that we're skipping.
   SkippedDefinitionContext ActOnTagStartSkippedDefinition(Scope *S, Decl *TD);
 
+  TriviallyRelocatableSpecifier
+  ActOnTriviallyRelocatableSpecifier(SourceLocation Loc);
+
+  ReplaceableSpecifier ActOnReplaceableSpecifier(SourceLocation Loc);
+
   /// ActOnStartCXXMemberDeclarations - Invoked when we have parsed a
   /// C++ record definition's base-specifiers clause and are starting its
   /// member declarations.
-  void ActOnStartCXXMemberDeclarations(Scope *S, Decl *TagDecl,
-                                       SourceLocation FinalLoc,
-                                       bool IsFinalSpelledSealed,
-                                       bool IsAbstract,
-                                       SourceLocation LBraceLoc);
+  void ActOnStartCXXMemberDeclarations(
+      Scope *S, Decl *TagDecl, SourceLocation FinalLoc,
+      bool IsFinalSpelledSealed, bool IsAbstract,
+      TriviallyRelocatableSpecifier TriviallyRelocatable,
+      ReplaceableSpecifier Replaceable, SourceLocation LBraceLoc);
 
   /// ActOnTagFinishDefinition - Invoked once we have finished parsing
   /// the definition of a tag (enumeration, class, struct, or union).
   void ActOnTagFinishDefinition(Scope *S, Decl *TagDecl,
                                 SourceRange BraceRange);
 
+  void CheckCXX2CTriviallyRelocatable(CXXRecordDecl *D);
+
+  void CheckCXX2CReplaceable(CXXRecordDecl *D);
+
   void ActOnTagFinishSkippedDefinition(SkippedDefinitionContext Context);
 
   /// ActOnTagDefinitionError - Invoked when there was an unrecoverable
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index 61d497999b669..07b4d77bd2ab7 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -4491,6 +4491,7 @@ unsigned FunctionDecl::getMemoryFunctionKind() const {
   case Builtin::BImempcpy:
     return Builtin::BImempcpy;
 
+  case Builtin::BI__builtin_trivially_relocate:
   case Builtin::BI__builtin_memmove:
   case Builtin::BI__builtin___memmove_chk:
   case Builtin::BImemmove:
diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp
index a3b3478e4b26a..4c3d9a01466aa 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -103,13 +103,16 @@ CXXRecordDecl::DefinitionData::DefinitionData(CXXRecordDecl *D)
       HasConstexprDefaultConstructor(false),
       DefaultedDestructorIsConstexpr(true),
       HasNonLiteralTypeFieldsOrBases(false), StructuralIfLiteral(true),
-      UserProvidedDefaultConstructor(false), DeclaredSpecialMembers(0),
+      UserProvidedDefaultConstructor(false), UserProvidedMoveAssignment(false),
+      UserProvidedCopyAssignment(false), ExplicitlyDeletedMoveAssignment(false),
+      DeclaredSpecialMembers(0),
       ImplicitCopyConstructorCanHaveConstParamForVBase(true),
       ImplicitCopyConstructorCanHaveConstParamForNonVBase(true),
       ImplicitCopyAssignmentHasConstParam(true),
       HasDeclaredCopyConstructorWithConstParam(false),
       HasDeclaredCopyAssignmentWithConstParam(false),
-      IsAnyDestructorNoReturn(false), IsHLSLIntangible(false), IsLambda(false),
+      IsAnyDestructorNoReturn(false), IsHLSLIntangible(false),
+      IsTriviallyRelocatable(false), IsReplaceable(false), IsLambda(false),
       IsParsingBaseSpecifiers(false), ComputedVisibleConversions(false),
       HasODRHash(false), Definition(D) {}
 
@@ -1556,7 +1559,10 @@ void CXXRecordDecl::addedEligibleSpecialMemberFunction(const CXXMethodDecl *MD,
     if (DD->isNoReturn())
       data().IsAnyDestructorNoReturn = true;
   }
-
+  if (SMKind == SMF_CopyAssignment)
+    data().UserProvidedCopyAssignment = MD->isUserProvided();
+  else if (SMKind == SMF_MoveAssignment)
+    data().UserProvidedMoveAssignment = MD->isUserProvided();
   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
@@ -1578,6 +1584,9 @@ void CXXRecordDecl::addedEligibleSpecialMemberFunction(const CXXMethodDecl *MD,
     if (!MD->isUserProvided())
       data().DeclaredNonTrivialSpecialMembersForCall |= SMKind;
   }
+
+  if (MD->isDeleted() && SMKind == SMF_MoveAssignment)
+    data().ExplicitlyDeletedMoveAssignment = true;
 }
 
 void CXXRecordDecl::finishedDefaultedOrDeletedMember(CXXMethodDecl *D) {
@@ -1605,8 +1614,11 @@ void CXXRecordDecl::finishedDefaultedOrDeletedMember(CXXMethodDecl *D) {
       data().HasIrrelevantDestructor = false;
   } else if (D->isCopyAssignmentOperator())
     SMKind |= SMF_CopyAssignment;
-  else if (D->isMoveAssignmentOperator())
+  else if (D->isMoveAssignmentOperator()) {
     SMKind |= SMF_MoveAssignment;
+    if (!D->isIneligibleOrNotSelected() && D->isDeleted())
+      data().ExplicitlyDeletedMoveAssignment = true;
+  }
 
   // Update which trivial / non-trivial special members we have.
   // addedMember will have skipped this step for this member.
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index fbd09141bc541..330342028569e 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -2919,6 +2919,30 @@ bool QualType::isTriviallyRelocatableType(const ASTContext &Context) const {
   }
 }
 
+bool QualType::isCppTriviallyRelocatableType(const ASTContext &Context) const {
+  QualType BaseElementType = Context.getBaseElementType(*this);
+  if (BaseElementType->isIncompleteType())
+    return false;
+  else if (BaseElementType->isScalarType())
+    return true;
+  else if (const auto *RD = BaseElementType->getAsCXXRecordDecl())
+    return RD->isTriviallyRelocatable();
+  return false;
+}
+
+bool QualType::isReplaceableType(const ASTContext &Context) const {
+  if (isConstQualified())
+    return false;
+  QualType BaseElementType = Context.getBaseElementType(getUnqualifiedType());
+  if (BaseElementType->isIncompleteType())
+    return false;
+  if (BaseElementType->isScalarType())
+    return true;
+  if (const auto *RD = BaseElementType->getAsCXXRecordDecl())
+    return RD->isReplaceable();
+  return false;
+}
+
 bool QualType::isNonWeakInMRRWithObjCWeak(const ASTContext &Context) const {
   return !Context.getLangOpts().ObjCAutoRefCount &&
          Context.getLangOpts().ObjCWeak &&
diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp
index 6235473d19a31..e6816736412b8 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -4210,6 +4210,7 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
     return RValue::get(Dest, *this);
   }
 
+  case Builtin::BI__builtin_trivially_relocate:
   case Builtin::BImemmove:
   case Builtin::BI__builtin_memmove: {
     Address Dest = EmitPointerWithAlignment(E->getArg(0));
diff --git a/clang/lib/Frontend/InitPreprocessor.cpp b/clang/lib/Frontend/InitPreprocessor.cpp
index e083c1a896804..341cdb8c4b220 100644
--- a/clang/lib/Frontend/InitPreprocessor.cpp
+++ b/clang/lib/Frontend/InitPreprocessor.cpp
@@ -761,7 +761,7 @@ static void InitializeCPlusPlusFeatureTestMacros(const LangOptions &LangOpts,
     Builder.defineMacro("__cpp_explicit_this_parameter", "202110L");
   }
 
-  // We provide those C++23 features as extensions in earlier language modes, so
+  // We provide those C++2b features as extensions in earlier language modes, so
   // we also define their feature test macros.
   if (LangOpts.CPlusPlus11)
     Builder.defineMacro("__cpp_static_call_operator", "202207L");
@@ -772,6 +772,7 @@ static void InitializeCPlusPlusFeatureTestMacros(const LangOptions &LangOpts,
   Builder.defineMacro("__cpp_pack_indexing", "202311L");
   Builder.defineMacro("__cpp_deleted_function", "202403L");
   Builder.defineMacro("__cpp_variadic_friend", "202403L");
+  Builder.defineMacro("__cpp_trivial_relocatability", "202502L");
 
   if (LangOpts.Char8)
     Builder.defineMacro("__cpp_char8_t", "202207L");
diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp
index 6ee2cd31311ef..9a9d519f34d5d 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -2027,7 +2027,8 @@ void Parser::ParseClassSpecifier(tok::TokenKind TagTokKind,
            (DSC != DeclSpecContext::DSC_association &&
             getLangOpts().CPlusPlus && Tok.is(tok::colon)) ||
            (isClassCompatibleKeyword() &&
-            (NextToken().is(tok::l_brace) || NextToken().is(tok::colon)))) {
+            (NextToken().is(tok::l_brace) || NextToken().is(tok::colon) ||
+             isClassCompatibleKeyword(NextToken())))) {
     if (DS.isFriendSpecified()) {
       // C++ [class.friend]p2:
       //   A class shall not be defined in a friend declaration.
@@ -2046,15 +2047,15 @@ void Parser::ParseClassSpecifier(tok::TokenKind TagTokKind,
              (NextToken().is(tok::l_square) ||
               NextToken().is(tok::kw_alignas) ||
               NextToken().isRegularKeywordAttribute() ||
-              isCXX11VirtSpecifier(NextToken()) != VirtSpecifiers::VS_None)) {
+              isCXX11VirtSpecifier(NextToken()) != VirtSpecifiers::VS_None ||
+              isCXX2CTriviallyRelocatableKeyword())) {
     // We can't tell if this is a definition or reference
     // until we skipped the 'final' and C++11 attribute specifiers.
     TentativeParsingAction PA(*this);
 
     // Skip the 'final', abstract'... keywords.
-    while (isClassCompatibleKeyword()) {
+    while (isClassCompatibleKeyword())
       ConsumeToken();
-    }
 
     // Skip C++11 attribute specifiers.
     while (true) {
@@ -2696,16 +2697,63 @@ bool Parser::isCXX11FinalKeyword() const {
          Specifier == VirtSpecifiers::VS_Sealed;
 }
 
+bool Parser::isCXX2CTriviallyRelocatableKeyword(Token Tok) const {
+  if (!getLangOpts().CPlusPlus || Tok.isNot(tok::identifier))
+    return false;
+  if (!Ident_trivially_relocatable_if_eligible)
+    Ident_trivially_relocatable_if_eligible =
+        &PP.getIdentifierTable().get("trivially_relocatable_if_eligible");
+  IdentifierInfo *II = Tok.getIdentifierInfo();
+  return II == Ident_trivially_relocatable_if_eligible;
+}
+
+bool Parser::isCXX2CTriviallyRelocatableKeyword() const {
+  return isCXX2CTriviallyRelocatableKeyword(Tok);
+}
+
+void Parser::ParseOptionalCXX2CTriviallyRelocatableSpecifier(
+    TriviallyRelocatableSpecifier &TRS) {
+  assert(isCXX2CTriviallyRelocatableKeyword() &&
+         "expected a trivially_relocatable specifier");
+  TRS = Actions.ActOnTriviallyRelocatableSpecifier(ConsumeToken());
+}
+
+bool Parser::isCXX2CReplaceableKeyword(Token Tok) const {
+  if (!getLangOpts().CPlusPlus || Tok.isNot(tok::identifier))
+    return false;
+  if (!Ident_replaceable_if_eligible)
+    Ident_replaceable_if_eligible =
+        &PP.getIdentifierTable().get("replaceable_if_eligible");
+  IdentifierInfo *II = Tok.getIdentifierInfo();
+  return II == Ident_replaceable_if_eligible;
+}
+
+bool Parser::isCXX2CReplaceableKeyword() const {
+  return isCXX2CReplaceableKeyword(Tok);
+}
+
+void Parser::ParseOptionalCXX2CReplaceableSpecifier(ReplaceableSpecifier &MRS) {
+  assert(isCXX2CReplaceableKeyword() &&
+         "expected a replaceable_if_eligible specifier");
+  MRS = Actions.ActOnReplaceableSpecifier(ConsumeToken());
+}
+
 /// isClassCompatibleKeyword - Determine whether the next token is a C++11
 /// 'final' or Microsoft 'sealed' or 'abstract' contextual keywords.
-bool Parser::isClassCompatibleKeyword() const {
-  VirtSpecifiers::Specifier Specifier = isCXX11VirtSpecifier();
+bool Parser::isClassCompatibleKeyword(Token Tok) const {
+  if (isCXX2CTriviallyRelocatableKeyword(Tok) || isCXX2CReplaceableKeyword(Tok))
+    return true;
+  VirtSpecifiers::Specifier Specifier = isCXX11VirtSpecifier(Tok);
   return Specifier == VirtSpecifiers::VS_Final ||
          Specifier == VirtSpecifiers::VS_GNU_Final ||
          Specifier == VirtSpecifiers::VS_Sealed ||
          Specifier == VirtSpecifiers::VS_Abstract;
 }
 
+bool Parser::isClassCompatibleKeyword() const {
+  return isClassCompatibleKeyword(Tok);
+}
+
 /// Parse a C++ member-declarator up to, but not including, the optional
 /// brace-or-equal-initializer or pure-specifier.
 bool Parser::ParseCXXMemberDeclaratorBeforeInitializer(
@@ -3584,21 +3632,19 @@ void Parser::SkipCXXMemberSpecification(SourceLocation RecordLoc,
                                         SourceLocation AttrFixitLoc,
                                         unsigned TagType, Decl *TagDecl) {
   // Skip the optional 'final' keyword.
-  if (getLangOpts().CPlusPlus && Tok.is(tok::identifier)) {
-    assert(isCXX11FinalKeyword() && "not a class definition");
+  while (isClassCompatibleKeyword())
     ConsumeToken();
 
-    // Diagnose any C++11 attributes after 'final' keyword.
-    // We deliberately discard these attributes.
-    ParsedAttributes Attrs(AttrFactory);
-    CheckMisplacedCXX11Attribute(Attrs, AttrFixitLoc);
+  // Diagnose any C++11 attributes after 'final' keyword.
+  // We deliberately discard these attributes.
+  ParsedAttributes Attrs(AttrFactory);
+  CheckMisplacedCXX11Attribute(Attrs, AttrFixitLoc);
 
-    // This can only happen if we had malformed misplaced attributes;
-    // we only get called if there is a colon or left-brace after the
-    // attributes.
-    if (Tok.isNot(tok::colon) && Tok.isNot(tok::l_brace))
-      return;
-  }
+  // This can only happen if we had malformed misplaced attributes;
+  // we only get called if there is a colon or left-brace after the
+  // attributes.
+  if (Tok.isNot(tok::colon) && Tok.isNot(tok::l_brace))
+    return;
 
   // Skip the base clauses. This requires actually parsing them, because
   // otherwise we can't be sure where they end (a left brace may appear
@@ -3812,13 +3858,39 @@ void Parser::ParseCXXMemberSpecification(SourceLocation RecordLoc,
   SourceLocation AbstractLoc;
   bool IsFinalSpelledSealed = false;
   bool IsAbstract = false;
+  TriviallyRelocatableSpecifier TriviallyRelocatable;
+  ReplaceableSpecifier Replacable;
 
   // Parse the optional 'final' keyword.
   if (getLangOpts().CPlusPlus && Tok.is(tok::identifier)) {
     while (true) {
       VirtSpecifiers::Specifier Specifier = isCXX11VirtSpecifier(Tok);
-      if (Specifier == VirtSpecifiers::VS_None)
-        break;
+      if (Specifier == VirtSpecifiers::VS_None) {
+        if (isCXX2CTriviallyRelocatableKeyword(Tok)) {
+          if (TriviallyRelocatable.isSet()) {
+            auto Skipped = Tok;
+            ConsumeToken();
+            Diag(Skipped, diag::err_duplicate_class_relocation_specifier)
+                << 0 << TriviallyRelocatable.getLocation();
+          } else {
+            ParseOptionalCXX2CTriviallyRelocatableSpecifier(
+                TriviallyRelocatable);
+          }
+          continue;
+        } else if (isCXX2CReplaceableKeyword(Tok)) {
+          if (Replacable.isSet()) {
+            auto Skipped = Tok;
+            ConsumeToken();
+            Diag(Skipped, diag::err_duplicate_class_relocation_specifier)
+                << 1 << Replacable.getLocation();
+          } else {
+            ParseOptionalCXX2CReplaceableSpecifier(Replacable);
+          }
+          continue;
+        } else {
+          break;
+        }
+      }
       if (isCXX11FinalKeyword()) {
         if (FinalLoc.isValid()) {
           auto Skipped = ConsumeToken();
@@ -3854,7 +3926,8 @@ void Parser::ParseCXXMemberSpecification(SourceLocation RecordLoc,
       else if (Specifier == VirtSpecifiers::VS_GNU_Final)
         Diag(FinalLoc, diag::ext_warn_gnu_final);
     }
-    assert((FinalLoc.isValid() || AbstractLoc.isValid()) &&
+    assert((FinalLoc.isValid() || AbstractLoc.isValid() ||
+            TriviallyRelocatable.isSet() || Replacable.isSet()) &&
            "not a class definition");
 
     // Parse any C++11 attributes after 'final' keyword.
@@ -3927,9 +4000,9 @@ void Parser::ParseCXXMemberSpecification(SourceLocation RecordLoc,
   T.consumeOpen();
 
   if (TagDecl)
-    Actions.ActOnStartCXXMemberDeclarations(getCurScope(), TagDecl, FinalLoc,
-                                            IsFinalSpelledSealed, IsAbstract,
-                                            T.getOpenLocation());
+    Actions.ActOnStartCXXMemberDeclarations(
+        getCurScope(), TagDecl, FinalLoc, IsFinalSpelledSealed, IsAbstract,
+        TriviallyRelocatable, Replacable, T.getOpenLocation());
 
   // C++ 11p3: Members of a class defined with the keyword class are private
   // by default. Members of a class defined with the keywords struct or union
diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp
index cab0604821c03..9117971ce212f 100644
--- a/clang/lib/Parse/Parser.cpp
+++ b/clang/lib/Parse/Parser.cpp
@@ -537,6 +537,8 @@ void Parser::Initialize() {
   Ident_sealed = nullptr;
   Ident_abstract = nullptr;
   Ident_override = nullptr;
+  Ident_trivially_relocatable_if_eligible = nullptr;
+  Ident_replaceable_if_eligible = nullptr;
   Ident_GNU_final = nullptr;
   Ident_import = nullptr;
   Ident_module = nullptr;
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 97f623f61a405..4cca875777f50 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -1919,6 +1919,54 @@ static ExprResult BuiltinIsWithinLifetime(Sema &S, CallExpr *TheCall) {
         << 0;
     return ExprError();
   }
+  return TheCall;
+}
+
+static ExprResult BuiltinTriviallyRelocate(Sema &S, CallExpr *TheCall) {
+  if (S.checkArgCount(TheCall, 3))
+    return ExprError();
+
+  QualType ArgTy = TheCall->getArg(0)->getType();
+  if (!ArgTy->isPointerType() || ArgTy.getCVRQualifiers() != 0) {
+    S.Diag(TheCall->getArg(0)->getExprLoc(),
+           diag::err_builtin_trivially_relocate_invalid_arg_type)
+        << /*a pointer*/ 0;
+    return ExprError();
+  }
+
+  QualType T = ArgTy->getPointeeType();
+  if (S.RequireCompleteType(TheCall->getBeginLoc(), T,
+                            diag::err_incomplete_type))
+    return ExprError();
+
+  if (T.isConstQualified() ||
+      !T.isCppTriviallyRelocatableType(S.getASTContext())) {
+    S.Diag(TheCall->getArg(0)->getExprLoc(),
+           diag::err_builtin_trivially_relocate_invalid_arg_type)
+        << (T.isConstQualified() ? /*non-const*/ 1 : /*relocatable*/ 2);
+    return ExprError();
+  }
+
+  TheCall->setType(ArgTy);
+
+  QualType Dest = TheCall->getArg(1)->getType();
+  if (Dest.getCanonicalType() != ArgTy.getCanonicalType()) {
+    S.Diag(TheCall->getArg(0)->getExprLoc(),
+           diag::err_builtin_trivially_relocate_invalid_arg_type)
+        << /*the same*/ 3;
+    return ExprError();
+  }
+
+  Expr *SizeExpr = TheCall->getArg(2);
+  ExprResult Size = S.DefaultLvalueConversion(SizeExpr);
+  if (Size.isInvalid())
+    return ExprError();
+
+  Size = S.tryConvertExprToType(Size.get(), S.getASTContext().getSizeType());
+  if (Size.isInvalid())
+    return ExprError();
+  SizeExpr = Size.get();
+  TheCall->setArg(2, SizeExpr);
 
   return TheCall;
 }
@@ -2384,6 +2432,9 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
     return BuiltinLaunder(*this, TheCall);
   case Builtin::BI__builtin_is_within_lifetime:
     return BuiltinIsWithinLifetime(*this, TheCall);
+  case Builtin::BI__builtin_trivially_relocate:
+    return BuiltinTriviallyRelocate(*this, TheCall);
+
   case Builtin::BI__sync_fetch_and_add:
   case Builtin::BI__sync_fetch_and_add_1:
   case Builtin::BI__sync_fetch_and_add_2:
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index d07101d122a2d..f84ccc3181474 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -18480,11 +18480,19 @@ bool Sema::ActOnDuplicateDefinition(Scope *S, Decl *Prev,
   return true;
 }
 
-void Sema::ActOnStartCXXMemberDeclarations(Scope *S, Decl *TagD,
-                                           SourceLocation FinalLoc,
-                                           bool IsFinalSpelledSealed,
-                                           bool IsAbstract,
-                                           SourceLocation LBraceLoc) {
+TriviallyRelocatableSpecifier
+Sema::ActOnTriviallyRelocatableSpecifier(SourceLocation Loc) {
+  return {Loc};
+}
+
+ReplaceableSpecifier Sema::ActOnReplaceableSpecifier(SourceLocation Loc) {
+  return {Loc};
+}
+
+void Sema::ActOnStartCXXMemberDeclarations(
+    Scope *S, Decl *TagD, SourceLocation FinalLoc, bool IsFinalSpelledSealed,
+    bool IsAbstract, TriviallyRelocatableSpecifier TriviallyRelocatable,
+    ReplaceableSpecifier Replaceable, SourceLocation LBraceLoc) {
   AdjustDeclIfTemplate(TagD);
   CXXRecordDecl *Record = cast<CXXRecordDecl>(TagD);
 
@@ -18502,6 +18510,13 @@ void Sema::ActOnStartCXXMemberDeclarations(Scope *S, Decl *TagD,
                                           ? FinalAttr::Keyword_sealed
                                           : FinalAttr::Keyword_final));
   }
+
+  if (TriviallyRelocatable.isSet() && !Record->isInvalidDecl())
+    Record->setTriviallyRelocatableSpecifier(TriviallyRelocatable);
+
+  if (Replaceable.isSet() && !Record->isInvalidDecl())
+    Record->setReplaceableSpecifier(Replaceable);
+
   // C++ [class]p2:
   //   [...] The class-name is also inserted into the scope of the
   //   class itself; this is known as the injected-class-name. For
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 22e21524c54be..b9ce4e7785844 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -7290,6 +7290,9 @@ void Sema::CheckCompletedCXXClass(Scope *S, CXXRecordDecl *Record) {
   checkClassLevelDLLAttribute(Record);
   checkClassLevelCodeSegAttribute(Record);
 
+  CheckCXX2CTriviallyRelocatable(Record);
+  CheckCXX2CReplaceable(Record);
+
   bool ClangABICompat4 =
       Context.getLangOpts().getClangABICompat() <= LangOptions::ClangABI::Ver4;
   TargetInfo::CallingConvKind CCK =
@@ -7369,6 +7372,217 @@ void Sema::CheckCompletedCXXClass(Scope *S, CXXRecordDecl *Record) {
   CheckMismatchedTypeAwareAllocators(OO_Array_New, OO_Array_Delete);
 }
 
+static bool hasSuitableConstructorForReplaceability(CXXRecordDecl *D,
+                                                    bool Implicit) {
+  assert(D->hasDefinition() && !D->isInvalidDecl());
+
+  bool HasDeletedMoveConstructor = false;
+  bool HasDeletedCopyConstructor = false;
+  bool HasMoveConstructor = D->needsImplicitMoveConstructor();
+  bool HasDefaultedMoveConstructor = D->needsImplicitMoveConstructor();
+  bool HasDefaultedCopyConstructor = D->needsImplicitMoveConstructor();
+
+  for (const Decl *D : D->decls()) {
+    auto *MD = dyn_cast<CXXConstructorDecl>(D);
+    if (!MD || MD->isIneligibleOrNotSelected())
+      continue;
+
+    if (MD->isMoveConstructor()) {
+      HasMoveConstructor = true;
+      if (MD->isDefaulted())
+        HasDefaultedMoveConstructor = true;
+      if (MD->isDeleted())
+        HasDeletedMoveConstructor = true;
+    }
+    if (MD->isCopyConstructor()) {
+      if (MD->isDefaulted())
+        HasDefaultedCopyConstructor = true;
+      if (MD->isDeleted())
+        HasDeletedCopyConstructor = true;
+    }
+  }
+
+  if (HasMoveConstructor)
+    return !HasDeletedMoveConstructor &&
+           (Implicit ? HasDefaultedMoveConstructor : true);
+  return !HasDeletedCopyConstructor &&
+         (Implicit ? HasDefaultedCopyConstructor : true);
+  ;
+}
+
+static bool hasSuitableMoveAssignmentOperatorForReplaceability(CXXRecordDecl *D,
+                                                               bool Implicit) {
+  assert(D->hasDefinition() && !D->isInvalidDecl());
+
+  if (D->hasExplicitlyDeletedMoveAssignment())
+    return false;
+
+  bool HasDeletedMoveAssignment = false;
+  bool HasDeletedCopyAssignment = false;
+  bool HasMoveAssignment = D->needsImplicitMoveAssignment();
+  bool HasDefaultedMoveAssignment = D->needsImplicitMoveAssignment();
+  bool HasDefaultedCopyAssignment = D->needsImplicitCopyAssignment();
+
+  for (const Decl *D : D->decls()) {
+    auto *MD = dyn_cast<CXXMethodDecl>(D);
+    if (!MD || MD->isIneligibleOrNotSelected())
+      continue;
+
+    if (MD->isMoveAssignmentOperator()) {
+      HasMoveAssignment = true;
+      if (MD->isDefaulted())
+        HasDefaultedMoveAssignment = true;
+      if (MD->isDeleted())
+        HasDeletedMoveAssignment = true;
+    }
+    if (MD->isCopyAssignmentOperator()) {
+      if (MD->isDefaulted())
+        HasDefaultedCopyAssignment = true;
+      if (MD->isDeleted())
+        HasDeletedCopyAssignment = true;
+    }
+  }
+
+  if (HasMoveAssignment)
+    return !HasDeletedMoveAssignment &&
+           (Implicit ? HasDefaultedMoveAssignment : true);
+  return !HasDeletedCopyAssignment &&
+         (Implicit ? HasDefaultedCopyAssignment : true);
+}
+
+void Sema::CheckCXX2CTriviallyRelocatable(CXXRecordDecl *D) {
+  if (!D->hasDefinition() || D->isInvalidDecl())
+    return;
+
+  bool MarkedTriviallyRelocatable =
+      D->getTriviallyRelocatableSpecifier().isSet();
+
+  bool IsTriviallyRelocatable = true;
+  for (const CXXBaseSpecifier &B : D->bases()) {
+    const auto *BaseDecl = B.getType()->getAsCXXRecordDecl();
+    if (!BaseDecl)
+      continue;
+    if (B.isVirtual() ||
+        (!BaseDecl->isDependentType() && !BaseDecl->isTriviallyRelocatable())) {
+      IsTriviallyRelocatable = false;
+    }
+  }
+
+  for (const FieldDecl *Field : D->fields()) {
+    if (Field->getType()->isDependentType())
+      continue;
+    if (Field->getType()->isReferenceType())
+      continue;
+    QualType T = getASTContext().getBaseElementType(
+        Field->getType().getUnqualifiedType());
+    if (CXXRecordDecl *RD = T->getAsCXXRecordDecl()) {
+      if (RD->isTriviallyRelocatable())
+        continue;
+      IsTriviallyRelocatable = false;
+      break;
+    }
+  }
+
+  if (!D->isDependentType() && !MarkedTriviallyRelocatable) {
+    bool HasSuitableMoveCtr = D->needsImplicitMoveConstructor();
+    bool HasSuitableCopyCtr = false;
+    if (D->hasUserDeclaredDestructor()) {
+      const auto *Dtr = D->getDestructor();
+      if (Dtr && (!Dtr->isDefaulted() || Dtr->isDeleted()))
+        IsTriviallyRelocatable = false;
+    }
+    if (IsTriviallyRelocatable && !HasSuitableMoveCtr) {
+      for (const CXXConstructorDecl *CD : D->ctors()) {
+        if (CD->isMoveConstructor() && CD->isDefaulted() &&
+            !CD->isIneligibleOrNotSelected()) {
+          HasSuitableMoveCtr = true;
+          break;
+        }
+      }
+    }
+    if (!HasSuitableMoveCtr && !D->hasMoveConstructor()) {
+      HasSuitableCopyCtr = D->needsImplicitCopyConstructor();
+      if (!HasSuitableCopyCtr) {
+        for (const CXXConstructorDecl *CD : D->ctors()) {
+          if (CD->isCopyConstructor() && CD->isDefaulted() &&
+              !CD->isIneligibleOrNotSelected()) {
+            HasSuitableCopyCtr = true;
+            break;
+          }
+        }
+      }
+    }
+
+    if (D->isUnion() && !D->hasUserDeclaredCopyConstructor() &&
+        !D->hasUserDeclaredCopyAssignment() &&
+        !D->hasUserDeclaredMoveOperation() && !D->hasUserDeclaredDestructor()) {
+      // Do nothing
+    }
+
+    else if (!HasSuitableMoveCtr && !HasSuitableCopyCtr)
+      IsTriviallyRelocatable = false;
+
+    else if (IsTriviallyRelocatable &&
+             ((!D->needsImplicitMoveAssignment() &&
+               (D->hasUserProvidedMoveAssignment() ||
+                D->hasExplicitlyDeletedMoveAssignment())) ||
+              (!D->hasMoveAssignment() &&
+               D->hasUserProvidedCopyAssignment()))) {
+      IsTriviallyRelocatable = false;
+    }
+  }
+
+  D->setIsTriviallyRelocatable(IsTriviallyRelocatable);
+}
+
+void Sema::CheckCXX2CReplaceable(CXXRecordDecl *D) {
+  if (!D->hasDefinition() || D->isInvalidDecl())
+    return;
+
+  bool MarkedCXX2CReplaceable = D->getReplaceableSpecifier().isSet();
+
+  bool IsReplaceable = true;
+
+  for (const CXXBaseSpecifier &B : D->bases()) {
+    const auto *BaseDecl = B.getType()->getAsCXXRecordDecl();
+    if (!BaseDecl)
+      continue;
+    if ((!BaseDecl->isDependentType() && !BaseDecl->isReplaceable()) ||
+        B.isVirtual())
+      IsReplaceable = false;
+  }
+
+  if (!hasSuitableConstructorForReplaceability(D, !MarkedCXX2CReplaceable) ||
+      !hasSuitableMoveAssignmentOperatorForReplaceability(
+          D, !MarkedCXX2CReplaceable)) {
+    IsReplaceable = false;
+  }
+
+  if (IsReplaceable) {
+    for (const FieldDecl *Field : D->fields()) {
+      if (Field->getType()->isDependentType())
+        continue;
+      if (Field->getType()->isReferenceType()) {
+        IsReplaceable = false;
+        break;
+      }
+      if (Field->getType().isConstQualified()) {
+        IsReplaceable = false;
+        break;
+      }
+      QualType T = getASTContext().getBaseElementType(
+          Field->getType().getUnqualifiedType());
+      if (CXXRecordDecl *RD = T->getAsCXXRecordDecl()) {
+        if (RD->isReplaceable())
+          continue;
+        IsReplaceable = false;
+      }
+    }
+  }
+
+  D->setIsReplaceable(IsReplaceable);
+}
+
 /// Look up the special member function that would be called by a special
 /// member function for a subobject of class type.
 ///
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index 235ea1529b0b8..af308077b21fe 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -5439,6 +5439,8 @@ static bool CheckUnaryTypeTraitTypeCompleteness(Sema &S, TypeTrait UTT,
   // impose the same constraints.
   case UTT_IsTriviallyRelocatable:
   case UTT_IsTriviallyEqualityComparable:
+  case UTT_IsCppTriviallyRelocatable:
+  case UTT_IsReplaceable:
   case UTT_CanPassInRegs:
   // Per the GCC type traits documentation, T shall be a complete type, cv void,
   // or an array of unknown bound. But GCC actually imposes the same constraints
@@ -6005,6 +6007,10 @@ static bool EvaluateUnaryTypeTrait(Sema &Self, TypeTrait UTT,
     return T.isTriviallyRelocatableType(C);
   case UTT_IsBitwiseCloneable:
     return T.isBitwiseCloneableType(C);
+  case UTT_IsCppTriviallyRelocatable:
+    return T.isCppTriviallyRelocatableType(C);
+  case UTT_IsReplaceable:
+    return T.isReplaceableType(C);
   case UTT_CanPassInRegs:
     if (CXXRecordDecl *RD = T->getAsCXXRecordDecl(); RD && !T.hasQualifiers())
       return RD->canPassInRegisters();
diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp
index fb490bcac6e91..4d6bec48f76c3 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -3581,6 +3581,10 @@ Sema::InstantiateClass(SourceLocation PointOfInstantiation,
   // Start the definition of this instantiation.
   Instantiation->startDefinition();
 
+  Instantiation->setTriviallyRelocatableSpecifier(
+      Pattern->getTriviallyRelocatableSpecifier());
+  Instantiation->setReplaceableSpecifier(Pattern->getReplaceableSpecifier());
+
   // The instantiation is visible here, even if it was first declared in an
   // unimported module.
   Instantiation->setVisibleDespiteOwningModule();
diff --git a/clang/test/Parser/cxx2c-trivially-relocatable.cpp b/clang/test/Parser/cxx2c-trivially-relocatable.cpp
new file mode 100644
index 0000000000000..7a575124a3b35
--- /dev/null
+++ b/clang/test/Parser/cxx2c-trivially-relocatable.cpp
@@ -0,0 +1,8 @@
+// RUN: %clang_cc1 -std=c++2b -verify -fsyntax-only %s
+
+
+class A trivially_relocatable_if_eligible {};
+class E final trivially_relocatable_if_eligible {};
+class G trivially_relocatable_if_eligible final{};
+class I trivially_relocatable_if_eligible trivially_relocatable_if_eligible final {}; // expected-error {{class already marked 'trivially_relocatable_if_eligible'}}
+class trivially_relocatable_if_eligible trivially_relocatable_if_eligible {};
diff --git a/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp b/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp
new file mode 100644
index 0000000000000..7d300dfa4847c
--- /dev/null
+++ b/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp
@@ -0,0 +1,257 @@
+// RUN: %clang_cc1 -std=c++2c -verify %s
+
+class Trivial {};
+struct NonRelocatable {
+    ~NonRelocatable();
+};
+static NonRelocatable NonRelocatable_g;
+
+class A trivially_relocatable_if_eligible {};
+class B trivially_relocatable_if_eligible : Trivial{};
+class C trivially_relocatable_if_eligible {
+    int a;
+    void* b;
+    int c[3];
+    Trivial d[3];
+    NonRelocatable& e = NonRelocatable_g;
+};
+class D trivially_relocatable_if_eligible : Trivial {};
+class E trivially_relocatable_if_eligible : virtual Trivial {};
+
+class F trivially_relocatable_if_eligible : NonRelocatable {};
+
+class I trivially_relocatable_if_eligible {
+    NonRelocatable a;
+    NonRelocatable b[1];
+    const NonRelocatable c;
+    const NonRelocatable d[1];
+};
+
+class J trivially_relocatable_if_eligible:  virtual Trivial, NonRelocatable {
+    NonRelocatable a;
+};
+
+class G trivially_relocatable_if_eligible {
+    G(G&&);
+};
+
+class H trivially_relocatable_if_eligible {
+    ~H();
+};
+
+struct Incomplete; // expected-note {{forward declaration of 'Incomplete'}}
+static_assert(__is_cpp_trivially_relocatable(Incomplete));  // expected-error {{incomplete type 'Incomplete' used in type trait expression}}
+static_assert(__is_cpp_trivially_relocatable(Trivial));
+static_assert(__is_cpp_trivially_relocatable(G));
+static_assert(__is_cpp_trivially_relocatable(H));
+static_assert(__is_cpp_trivially_relocatable(int));
+static_assert(__is_cpp_trivially_relocatable(void*));
+static_assert(!__is_cpp_trivially_relocatable(int&));
+static_assert(!__is_cpp_trivially_relocatable(Trivial&));
+static_assert(__is_cpp_trivially_relocatable(const Trivial));
+static_assert(__is_cpp_trivially_relocatable(Trivial[1]));
+
+struct UserDtr {
+    ~UserDtr();
+};
+
+struct DefaultedDtr {
+    ~DefaultedDtr() = default;
+};
+struct UserMoveWithDefaultCopy {
+    UserMoveWithDefaultCopy(UserMoveWithDefaultCopy&&);
+    UserMoveWithDefaultCopy(const UserMoveWithDefaultCopy&) = default;
+};
+
+struct UserMove{
+    UserMove(UserMove&&);
+};
+
+struct UserMoveDefault{
+    UserMoveDefault(UserMoveDefault&&) = default;
+};
+
+struct UserCopy{
+    UserCopy(const UserCopy&);
+};
+
+struct UserCopyDefault{
+    UserCopyDefault(const UserCopyDefault&) = default;
+};
+
+
+struct UserDeletedMove{
+    UserDeletedMove(UserDeletedMove&) = delete;
+    UserDeletedMove(const UserDeletedMove&) = default;
+};
+
+static_assert(!__is_cpp_trivially_relocatable(UserDtr));
+static_assert(__is_cpp_trivially_relocatable(DefaultedDtr));
+static_assert(!__is_cpp_trivially_relocatable(UserMoveWithDefaultCopy));
+static_assert(!__is_cpp_trivially_relocatable(UserMove));
+static_assert(!__is_cpp_trivially_relocatable(UserCopy));
+static_assert(__is_cpp_trivially_relocatable(UserMoveDefault));
+static_assert(__is_cpp_trivially_relocatable(UserCopyDefault));
+static_assert(__is_cpp_trivially_relocatable(UserDeletedMove));
+
+template <typename T>
+class TestDependentErrors trivially_relocatable_if_eligible : T {};
+TestDependentErrors<Trivial> Ok;
+TestDependentErrors<NonRelocatable> Err;
+
+struct DeletedMove {
+    DeletedMove(DeletedMove&&) = delete;
+};
+struct DeletedCopy {
+    DeletedCopy(const DeletedCopy&) = delete;
+};
+struct DeletedMoveAssign {
+    DeletedMoveAssign& operator=(DeletedMoveAssign&&) = delete;
+};
+
+static_assert(!__is_cpp_trivially_relocatable(DeletedMove));
+static_assert(!__is_cpp_trivially_relocatable(DeletedCopy));
+static_assert(!__is_cpp_trivially_relocatable(DeletedMoveAssign));
+
+union U {
+    G g;
+};
+static_assert(!__is_trivially_copyable(U));
+static_assert(__is_cpp_trivially_relocatable(U));
+
+
+namespace replaceable {
+
+struct DeletedMove {
+    DeletedMove(DeletedMove&&) = delete;
+};
+struct DeletedCopy {
+    DeletedCopy(const DeletedCopy&) = delete;
+};
+struct DeletedMoveAssign {
+    DeletedMoveAssign& operator=(DeletedMoveAssign&&) = delete;
+};
+
+struct DefaultedMove {
+    DefaultedMove(DefaultedMove&&) = default;
+    DefaultedMove& operator=(DefaultedMove&&) = default;
+};
+struct DefaultedCopy {
+    DefaultedCopy(const DefaultedCopy&) = default;
+    DefaultedCopy(DefaultedCopy&&) = default;
+    DefaultedCopy& operator=(DefaultedCopy&&) = default;
+};
+struct DefaultedMoveAssign {
+    DefaultedMoveAssign(DefaultedMoveAssign&&) = default;
+    DefaultedMoveAssign& operator=(DefaultedMoveAssign&&) = default;
+};
+
+struct UserProvidedMove {
+    UserProvidedMove(UserProvidedMove&&){};
+};
+struct UserProvidedCopy {
+    UserProvidedCopy(const UserProvidedCopy&) {};
+};
+struct UserProvidedMoveAssign {
+    UserProvidedMoveAssign& operator=(const UserProvidedMoveAssign&){return *this;};
+};
+
+struct Empty{};
+static_assert(__builtin_is_replaceable(Empty));
+struct S1 replaceable_if_eligible{};
+static_assert(__builtin_is_replaceable(S1));
+
+static_assert(__builtin_is_replaceable(DefaultedMove));
+static_assert(__builtin_is_replaceable(DefaultedCopy));
+static_assert(__builtin_is_replaceable(DefaultedMoveAssign));
+
+static_assert(!__builtin_is_replaceable(DeletedMove));
+static_assert(!__builtin_is_replaceable(DeletedCopy));
+static_assert(!__builtin_is_replaceable(DeletedMoveAssign));
+
+static_assert(!__builtin_is_replaceable(UserProvidedMove));
+static_assert(!__builtin_is_replaceable(UserProvidedCopy));
+static_assert(!__builtin_is_replaceable(UserProvidedMoveAssign));
+
+using NotReplaceable = DeletedMove;
+
+template <typename T>
+struct S {
+    T t;
+};
+
+template <typename T>
+struct WithBase : T{};
+
+template <typename T>
+struct WithVBase : virtual T{};
+
+struct WithVirtual {
+    virtual ~WithVirtual() = default;
+    WithVirtual(WithVirtual&&) = default;
+    WithVirtual& operator=(WithVirtual&&) = default;
+};
+
+static_assert(__builtin_is_replaceable(S<int>));
+static_assert(__builtin_is_replaceable(S<volatile int>));
+static_assert(!__builtin_is_replaceable(S<const int>));
+static_assert(!__builtin_is_replaceable(S<const int&>));
+static_assert(!__builtin_is_replaceable(S<int&>));
+static_assert(__builtin_is_replaceable(S<int[2]>));
+static_assert(!__builtin_is_replaceable(S<const int[2]>));
+static_assert(__builtin_is_replaceable(WithBase<S<int>>));
+static_assert(!__builtin_is_replaceable(WithBase<S<const int>>));
+static_assert(!__builtin_is_replaceable(WithBase<UserProvidedMove>));
+static_assert(!__builtin_is_replaceable(WithVBase<S<int>>));
+static_assert(!__builtin_is_replaceable(WithVBase<S<const int>>));
+static_assert(!__builtin_is_replaceable(WithVBase<UserProvidedMove>));
+static_assert(__builtin_is_replaceable(WithVirtual));
+
+
+struct U1 replaceable_if_eligible {
+    ~U1() = delete;
+    U1(U1&&) = default;
+    U1& operator=(U1&&) = default;
+
+};
+static_assert(__builtin_is_replaceable(U1));
+
+struct U2 replaceable_if_eligible {
+    U2(const U2&) = delete;
+};
+static_assert(!__builtin_is_replaceable(U2));
+
+
+template <typename T>
+struct WithVBaseExplicit replaceable_if_eligible : virtual T{};
+static_assert(__builtin_is_replaceable(WithVBaseExplicit<S<int>>)); // expected-error {{failed}}
+
+struct S42 trivially_relocatable_if_eligible replaceable_if_eligible {
+    S42(S42&&);
+    S42& operator=(S42&&) = default;
+};
+struct S43 trivially_relocatable_if_eligible replaceable_if_eligible {
+    S43(S43&&) = default;
+    S43& operator=(S43&&);
+};
+
+
+struct Copyable1Explicit replaceable_if_eligible {
+   Copyable1Explicit(Copyable1Explicit const &) = default;
+};
+
+struct Copyable1 {
+   Copyable1(Copyable1 const &) = default;
+};
+
+
+struct CopyAssign1Explicit replaceable_if_eligible {
+   CopyAssign1Explicit & operator=(const CopyAssign1Explicit&) = default;
+};
+
+struct CopyAssign1 {
+   CopyAssign1 & operator=(CopyAssign1 const &) = default;
+};
+
+
+}

>From ab57929b6c2c59a350a34344428c944fcc3e8afe Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Thu, 20 Feb 2025 16:43:55 +0100
Subject: [PATCH 02/27] add sema tests for __builtin_trivially_relocate

---
 clang/include/clang/Basic/DiagnosticSemaKinds.td  |  5 +++--
 .../test/SemaCXX/cxx2c-trivially-relocatable.cpp  | 15 +++++++++++++++
 2 files changed, 18 insertions(+), 2 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 63806eeaea50b..be768636d0b49 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -12665,8 +12665,9 @@ def err_builtin_invalid_arg_type: Error<
   "%plural{[0,3]:type|:types}1 (was %4)">;
 
 def err_builtin_trivially_relocate_invalid_arg_type: Error <
-  "argument%select{s|||}0 to '__builtin_trivially_relocate' must be"
-  "%select{a pointer|non-const|relocatable|the same}0">;
+  "first%select{||| and second}0 argument%select{|||s}0 to "
+  "'__builtin_trivially_relocate' must be"
+  " %select{a pointer|non-const|relocatable|of the same type}0">;
 
 def err_builtin_matrix_disabled: Error<
   "matrix types extension is disabled. Pass -fenable-matrix to enable it">;
diff --git a/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp b/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp
index 7d300dfa4847c..1ca641226a292 100644
--- a/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp
+++ b/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp
@@ -253,5 +253,20 @@ struct CopyAssign1 {
    CopyAssign1 & operator=(CopyAssign1 const &) = default;
 };
 
+}
+
+
+void test__builtin_trivially_relocate() {
+    struct S{ ~S();};
+    struct R {};
+    __builtin_trivially_relocate(); //expected-error {{too few arguments to function call, expected 3, have 0}}
+    __builtin_trivially_relocate(0, 0, 0, 0); //expected-error {{too many arguments to function call, expected 3, have 4}}
+    __builtin_trivially_relocate(0, 0, 0); //expected-error {{argument to '__builtin_trivially_relocate' must be a pointer}}
+    __builtin_trivially_relocate((const int*)0, 0, 0); //expected-error {{argument to '__builtin_trivially_relocate' must be non-const}}
+    __builtin_trivially_relocate((S*)0, 0, 0); //expected-error {{argument to '__builtin_trivially_relocate' must be relocatable}}
+    __builtin_trivially_relocate((int*)0, 0, 0); //expected-error {{first and second arguments to '__builtin_trivially_relocate' must be of the same type}}
 
+    __builtin_trivially_relocate((int*)0, (int*)0, (int*)0); // expected-error{{cannot initialize a value of type 'unsigned long' with an rvalue of type 'int *'}}
+    __builtin_trivially_relocate((int*)0, (int*)0, 0);
+    __builtin_trivially_relocate((R*)0, (R*)0, 0);
 }

>From 350f7d7c3709db80c75b493f0b4e9a114cfb0e59 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Thu, 20 Feb 2025 16:53:56 +0100
Subject: [PATCH 03/27] rename __is_cpp_trivially_relocatable

---
 clang/include/clang/Basic/TokenKinds.def      |  2 +-
 .../SemaCXX/cxx2c-trivially-relocatable.cpp   | 44 +++++++++----------
 2 files changed, 23 insertions(+), 23 deletions(-)

diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def
index e829ff056e213..9bc63689d1363 100644
--- a/clang/include/clang/Basic/TokenKinds.def
+++ b/clang/include/clang/Basic/TokenKinds.def
@@ -557,7 +557,7 @@ TYPE_TRAIT_2(__reference_converts_from_temporary, ReferenceConvertsFromTemporary
 // is not exposed to users.
 TYPE_TRAIT_2(/*EmptySpellingName*/, IsDeducible, KEYCXX)
 TYPE_TRAIT_1(__is_bitwise_cloneable, IsBitwiseCloneable, KEYALL)
-TYPE_TRAIT_1(__is_cpp_trivially_relocatable, IsCppTriviallyRelocatable, KEYCXX)
+TYPE_TRAIT_1(__builtin_is_cpp_trivially_relocatable, IsCppTriviallyRelocatable, KEYCXX)
 TYPE_TRAIT_1(__builtin_is_replaceable, IsReplaceable, KEYCXX)
 TYPE_TRAIT_1(__builtin_structured_binding_size, StructuredBindingSize, KEYCXX)
 
diff --git a/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp b/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp
index 1ca641226a292..4b2eabda47a70 100644
--- a/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp
+++ b/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp
@@ -40,16 +40,16 @@ class H trivially_relocatable_if_eligible {
 };
 
 struct Incomplete; // expected-note {{forward declaration of 'Incomplete'}}
-static_assert(__is_cpp_trivially_relocatable(Incomplete));  // expected-error {{incomplete type 'Incomplete' used in type trait expression}}
-static_assert(__is_cpp_trivially_relocatable(Trivial));
-static_assert(__is_cpp_trivially_relocatable(G));
-static_assert(__is_cpp_trivially_relocatable(H));
-static_assert(__is_cpp_trivially_relocatable(int));
-static_assert(__is_cpp_trivially_relocatable(void*));
-static_assert(!__is_cpp_trivially_relocatable(int&));
-static_assert(!__is_cpp_trivially_relocatable(Trivial&));
-static_assert(__is_cpp_trivially_relocatable(const Trivial));
-static_assert(__is_cpp_trivially_relocatable(Trivial[1]));
+static_assert(__builtin_is_cpp_trivially_relocatable(Incomplete));  // expected-error {{incomplete type 'Incomplete' used in type trait expression}}
+static_assert(__builtin_is_cpp_trivially_relocatable(Trivial));
+static_assert(__builtin_is_cpp_trivially_relocatable(G));
+static_assert(__builtin_is_cpp_trivially_relocatable(H));
+static_assert(__builtin_is_cpp_trivially_relocatable(int));
+static_assert(__builtin_is_cpp_trivially_relocatable(void*));
+static_assert(!__builtin_is_cpp_trivially_relocatable(int&));
+static_assert(!__builtin_is_cpp_trivially_relocatable(Trivial&));
+static_assert(__builtin_is_cpp_trivially_relocatable(const Trivial));
+static_assert(__builtin_is_cpp_trivially_relocatable(Trivial[1]));
 
 struct UserDtr {
     ~UserDtr();
@@ -85,14 +85,14 @@ struct UserDeletedMove{
     UserDeletedMove(const UserDeletedMove&) = default;
 };
 
-static_assert(!__is_cpp_trivially_relocatable(UserDtr));
-static_assert(__is_cpp_trivially_relocatable(DefaultedDtr));
-static_assert(!__is_cpp_trivially_relocatable(UserMoveWithDefaultCopy));
-static_assert(!__is_cpp_trivially_relocatable(UserMove));
-static_assert(!__is_cpp_trivially_relocatable(UserCopy));
-static_assert(__is_cpp_trivially_relocatable(UserMoveDefault));
-static_assert(__is_cpp_trivially_relocatable(UserCopyDefault));
-static_assert(__is_cpp_trivially_relocatable(UserDeletedMove));
+static_assert(!__builtin_is_cpp_trivially_relocatable(UserDtr));
+static_assert(__builtin_is_cpp_trivially_relocatable(DefaultedDtr));
+static_assert(!__builtin_is_cpp_trivially_relocatable(UserMoveWithDefaultCopy));
+static_assert(!__builtin_is_cpp_trivially_relocatable(UserMove));
+static_assert(!__builtin_is_cpp_trivially_relocatable(UserCopy));
+static_assert(__builtin_is_cpp_trivially_relocatable(UserMoveDefault));
+static_assert(__builtin_is_cpp_trivially_relocatable(UserCopyDefault));
+static_assert(__builtin_is_cpp_trivially_relocatable(UserDeletedMove));
 
 template <typename T>
 class TestDependentErrors trivially_relocatable_if_eligible : T {};
@@ -109,15 +109,15 @@ struct DeletedMoveAssign {
     DeletedMoveAssign& operator=(DeletedMoveAssign&&) = delete;
 };
 
-static_assert(!__is_cpp_trivially_relocatable(DeletedMove));
-static_assert(!__is_cpp_trivially_relocatable(DeletedCopy));
-static_assert(!__is_cpp_trivially_relocatable(DeletedMoveAssign));
+static_assert(!__builtin_is_cpp_trivially_relocatable(DeletedMove));
+static_assert(!__builtin_is_cpp_trivially_relocatable(DeletedCopy));
+static_assert(!__builtin_is_cpp_trivially_relocatable(DeletedMoveAssign));
 
 union U {
     G g;
 };
 static_assert(!__is_trivially_copyable(U));
-static_assert(__is_cpp_trivially_relocatable(U));
+static_assert(__builtin_is_cpp_trivially_relocatable(U));
 
 
 namespace replaceable {

>From d5d3bdd21f22e87701c8ee993c770ecd96120cad Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Thu, 20 Feb 2025 17:09:02 +0100
Subject: [PATCH 04/27] add docs and fix variable names in SemaChecking

---
 clang/docs/LanguageExtensions.rst | 21 +++++++++++++++++++++
 clang/lib/Sema/SemaChecking.cpp   | 12 ++++++------
 2 files changed, 27 insertions(+), 6 deletions(-)

diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index 7835eceadf660..98d3128d81914 100644
--- a/clang/docs/LanguageExtensions.rst
+++ b/clang/docs/LanguageExtensions.rst
@@ -1863,6 +1863,12 @@ The following type trait primitives are supported by Clang. Those traits marked
   functionally equivalent to copying the underlying bytes and then dropping the
   source object on the floor. This is true of trivial types and types which
   were made trivially relocatable via the ``clang::trivial_abi`` attribute.
+* ``__builtin_is_cpp_trivially_relocatable`` (C++): Returns true if and object
+  is trivially relocatable, as defined by the C++26 standard.
+  Note that the caller code should ensure that if the object is polymorphic,
+  the dynamic type is of the most derived type.
+* ``__builtin_is_replaceable`` (C++): Returns true if and object
+  is replaceable, as defined by the C++26 standard.
 * ``__is_trivially_equality_comparable`` (Clang): Returns true if comparing two
   objects of the provided type is known to be equivalent to comparing their
   object representations. Note that types containing padding bytes are never
@@ -3722,6 +3728,21 @@ Query for this feature with ``__has_builtin(__builtin_operator_new)`` or
     replaceable global (de)allocation functions, but do support calling at least
     ``::operator new(size_t)`` and ``::operator delete(void*)``.
 
+
+``__builtin_trivially_relocate``
+-----------------------------------
+
+**Syntax**:
+
+.. code-block:: c
+
+  T* __builtin_trivially_relocate(T* dest, T* src, size_t count)
+
+Trivially relocates ``count`` objects of relocatable, complete type ``T``
+from ``src`` to ``dest`` and returns ``dest``.
+This builtin is used to implement ``std::trivially_relocate``.
+
+
 ``__builtin_preserve_access_index``
 -----------------------------------
 
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 4cca875777f50..aa39dd62b1c08 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -1926,15 +1926,15 @@ static ExprResult BuiltinTriviallyRelocate(Sema &S, CallExpr *TheCall) {
   if (S.checkArgCount(TheCall, 3))
     return ExprError();
 
-  QualType ArgTy = TheCall->getArg(0)->getType();
-  if (!ArgTy->isPointerType() || ArgTy.getCVRQualifiers() != 0) {
+  QualType Dest = TheCall->getArg(0)->getType();
+  if (!Dest->isPointerType() || Dest.getCVRQualifiers() != 0) {
     S.Diag(TheCall->getArg(0)->getExprLoc(),
            diag::err_builtin_trivially_relocate_invalid_arg_type)
         << /*a pointer*/ 0;
     return ExprError();
   }
 
-  QualType T = ArgTy->getPointeeType();
+  QualType T = Dest->getPointeeType();
   if (S.RequireCompleteType(TheCall->getBeginLoc(), T,
                             diag::err_incomplete_type))
     return ExprError();
@@ -1947,10 +1947,10 @@ static ExprResult BuiltinTriviallyRelocate(Sema &S, CallExpr *TheCall) {
     return ExprError();
   }
 
-  TheCall->setType(ArgTy);
+  TheCall->setType(Dest);
 
-  QualType Dest = TheCall->getArg(1)->getType();
-  if (Dest.getCanonicalType() != ArgTy.getCanonicalType()) {
+  QualType Src = TheCall->getArg(1)->getType();
+  if (Src.getCanonicalType() != Dest.getCanonicalType()) {
     S.Diag(TheCall->getArg(0)->getExprLoc(),
            diag::err_builtin_trivially_relocate_invalid_arg_type)
         << /*the same*/ 3;

>From 546d1090f1cd86f912bb13548bca21004526c9d4 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Thu, 20 Feb 2025 18:38:22 +0100
Subject: [PATCH 05/27] restore assert, add codegen test

---
 clang/include/clang/AST/DeclCXX.h                | 12 ++++++------
 clang/lib/Frontend/InitPreprocessor.cpp          |  2 +-
 .../CodeGenCXX/cxx2c-trivially-relocatable.cpp   | 16 ++++++++++++++++
 3 files changed, 23 insertions(+), 7 deletions(-)
 create mode 100644 clang/test/CodeGenCXX/cxx2c-trivially-relocatable.cpp

diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h
index e5652f515fdcd..42467183d8a08 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -747,16 +747,16 @@ class CXXRecordDecl : public RecordDecl {
   /// \c true if a defaulted move constructor for this class would be
   /// deleted.
   bool defaultedMoveConstructorIsDeleted() const {
-    // assert((!needsOverloadResolutionForMoveConstructor() ||
-    //         (data().DeclaredSpecialMembers & SMF_MoveConstructor)) &&
-    //        "this property has not yet been computed by Sema");
+    assert((!needsOverloadResolutionForMoveConstructor() ||
+             (data().DeclaredSpecialMembers & SMF_MoveConstructor)) &&
+            "this property has not yet been computed by Sema");
     return data().DefaultedMoveConstructorIsDeleted;
   }
 
   bool defaultedMoveAssignmentIsDeleted() const {
-    // assert((!needsOverloadResolutionForMoveAssignment() ||
-    //         (data().DeclaredSpecialMembers & SMF_MoveAssignment)) &&
-    //        "this property has not yet been computed by Sema");
+    assert((!needsOverloadResolutionForMoveAssignment() ||
+             (data().DeclaredSpecialMembers & SMF_MoveAssignment)) &&
+            "this property has not yet been computed by Sema");
     return data().DefaultedMoveAssignmentIsDeleted;
   }
 
diff --git a/clang/lib/Frontend/InitPreprocessor.cpp b/clang/lib/Frontend/InitPreprocessor.cpp
index 341cdb8c4b220..60fbd605e5568 100644
--- a/clang/lib/Frontend/InitPreprocessor.cpp
+++ b/clang/lib/Frontend/InitPreprocessor.cpp
@@ -761,7 +761,7 @@ static void InitializeCPlusPlusFeatureTestMacros(const LangOptions &LangOpts,
     Builder.defineMacro("__cpp_explicit_this_parameter", "202110L");
   }
 
-  // We provide those C++2b features as extensions in earlier language modes, so
+  // We provide those C++23 features as extensions in earlier language modes, so
   // we also define their feature test macros.
   if (LangOpts.CPlusPlus11)
     Builder.defineMacro("__cpp_static_call_operator", "202207L");
diff --git a/clang/test/CodeGenCXX/cxx2c-trivially-relocatable.cpp b/clang/test/CodeGenCXX/cxx2c-trivially-relocatable.cpp
new file mode 100644
index 0000000000000..17144cffb6476
--- /dev/null
+++ b/clang/test/CodeGenCXX/cxx2c-trivially-relocatable.cpp
@@ -0,0 +1,16 @@
+// RUN: %clang_cc1 -std=c++26 -triple x86_64-linux-gnu -emit-llvm -o - %s | FileCheck %s
+
+struct S trivially_relocatable_if_eligible {
+    S(const S&);
+    ~S();
+    int a;
+    int b;
+};
+
+// CHECK: @_Z4testP1SS0_
+// CHECK: call void @llvm.memmove.p0.p0.i64
+// CHECK-NOT: __builtin
+// CHECK: ret
+void test(S* source, S* dest) {
+    __builtin_trivially_relocate(dest, source, 1);
+};

>From d6a51fb2e2adeef86f14da91715fe3f6a56ef59b Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Fri, 21 Feb 2025 10:31:01 +0100
Subject: [PATCH 06/27] address comments, fix tests

---
 clang/lib/Sema/SemaDeclCXX.cpp                     | 8 ++++++--
 clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp | 2 +-
 2 files changed, 7 insertions(+), 3 deletions(-)

diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index b9ce4e7785844..a1d69e3843567 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -7451,9 +7451,11 @@ static bool hasSuitableMoveAssignmentOperatorForReplaceability(CXXRecordDecl *D,
 }
 
 void Sema::CheckCXX2CTriviallyRelocatable(CXXRecordDecl *D) {
-  if (!D->hasDefinition() || D->isInvalidDecl())
+  if (D->isInvalidDecl())
     return;
 
+  assert(D->hasDefinition());
+
   bool MarkedTriviallyRelocatable =
       D->getTriviallyRelocatableSpecifier().isSet();
 
@@ -7536,9 +7538,11 @@ void Sema::CheckCXX2CTriviallyRelocatable(CXXRecordDecl *D) {
 }
 
 void Sema::CheckCXX2CReplaceable(CXXRecordDecl *D) {
-  if (!D->hasDefinition() || D->isInvalidDecl())
+  if (D->isInvalidDecl())
     return;
 
+  assert(D->hasDefinition());
+
   bool MarkedCXX2CReplaceable = D->getReplaceableSpecifier().isSet();
 
   bool IsReplaceable = true;
diff --git a/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp b/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp
index 4b2eabda47a70..3ffa88e7c954f 100644
--- a/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp
+++ b/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp
@@ -266,7 +266,7 @@ void test__builtin_trivially_relocate() {
     __builtin_trivially_relocate((S*)0, 0, 0); //expected-error {{argument to '__builtin_trivially_relocate' must be relocatable}}
     __builtin_trivially_relocate((int*)0, 0, 0); //expected-error {{first and second arguments to '__builtin_trivially_relocate' must be of the same type}}
 
-    __builtin_trivially_relocate((int*)0, (int*)0, (int*)0); // expected-error{{cannot initialize a value of type 'unsigned long' with an rvalue of type 'int *'}}
+    __builtin_trivially_relocate((int*)0, (int*)0, (int*)0); // expected-error-re {{cannot initialize a value of type '{{.*}}' with an rvalue of type 'int *'}}
     __builtin_trivially_relocate((int*)0, (int*)0, 0);
     __builtin_trivially_relocate((R*)0, (R*)0, 0);
 }

>From 96a6c9e1ce7e22d9511bfc23da0941f44c251004 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Fri, 21 Feb 2025 10:32:12 +0100
Subject: [PATCH 07/27] address comments

---
 clang/lib/AST/Type.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 330342028569e..0f6ecbb307040 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -2923,9 +2923,9 @@ bool QualType::isCppTriviallyRelocatableType(const ASTContext &Context) const {
   QualType BaseElementType = Context.getBaseElementType(*this);
   if (BaseElementType->isIncompleteType())
     return false;
-  else if (BaseElementType->isScalarType())
+  if (BaseElementType->isScalarType())
     return true;
-  else if (const auto *RD = BaseElementType->getAsCXXRecordDecl())
+  if (const auto *RD = BaseElementType->getAsCXXRecordDecl())
     return RD->isTriviallyRelocatable();
   return false;
 }

>From cd783112e909bf468fadce5d5a3f1d91fd272731 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Fri, 21 Feb 2025 10:33:09 +0100
Subject: [PATCH 08/27] format

---
 clang/include/clang/AST/DeclCXX.h | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h
index 42467183d8a08..499d68bc4fcd5 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -748,15 +748,15 @@ class CXXRecordDecl : public RecordDecl {
   /// deleted.
   bool defaultedMoveConstructorIsDeleted() const {
     assert((!needsOverloadResolutionForMoveConstructor() ||
-             (data().DeclaredSpecialMembers & SMF_MoveConstructor)) &&
-            "this property has not yet been computed by Sema");
+            (data().DeclaredSpecialMembers & SMF_MoveConstructor)) &&
+           "this property has not yet been computed by Sema");
     return data().DefaultedMoveConstructorIsDeleted;
   }
 
   bool defaultedMoveAssignmentIsDeleted() const {
     assert((!needsOverloadResolutionForMoveAssignment() ||
-             (data().DeclaredSpecialMembers & SMF_MoveAssignment)) &&
-            "this property has not yet been computed by Sema");
+            (data().DeclaredSpecialMembers & SMF_MoveAssignment)) &&
+           "this property has not yet been computed by Sema");
     return data().DefaultedMoveAssignmentIsDeleted;
   }
 

>From 8ee2d739531afe245618a1e136fe61fe164608c5 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Fri, 21 Feb 2025 10:57:11 +0100
Subject: [PATCH 09/27] add extension warning and tests

---
 .../clang/Basic/DiagnosticParseKinds.td       |  9 ++++++
 clang/lib/Parse/ParseDeclCXX.cpp              | 12 ++++++++
 .../Parser/cxx2c-trivially-relocatable.cpp    | 28 ++++++++++++++++++-
 clang/www/cxx_status.html                     |  2 +-
 4 files changed, 49 insertions(+), 2 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td
index 8392558a3db1b..08d57e62f4f40 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -1065,6 +1065,15 @@ def ext_ms_abstract_keyword : ExtWarn<
   "'abstract' keyword is a Microsoft extension">,
   InGroup<MicrosoftAbstract>;
 
+def ext_relocatable_keyword : ExtWarn<
+  "'%select{trivially_relocatable|replaceable}0_if_eligible' "
+  "keyword is a C++2c extension">,
+  InGroup<CXX26>;
+def warn_relocatable_keyword  : Warning<
+  "'%select{trivially_relocatable|replaceable}0_if_eligible' "
+  "keyword is incompatible with standards before C++2c">,
+  DefaultIgnore, InGroup<CXXPre26Compat>;
+
 def err_access_specifier_interface : Error<
   "interface types cannot specify '%select{private|protected}0' access">;
 
diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp
index 9a9d519f34d5d..05b24f62c91ce 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -2715,6 +2715,12 @@ void Parser::ParseOptionalCXX2CTriviallyRelocatableSpecifier(
     TriviallyRelocatableSpecifier &TRS) {
   assert(isCXX2CTriviallyRelocatableKeyword() &&
          "expected a trivially_relocatable specifier");
+
+  Diag(Tok.getLocation(), getLangOpts().CPlusPlus26
+                              ? diag::warn_relocatable_keyword
+                              : diag::ext_relocatable_keyword)
+      << /*relocatable*/ 0;
+
   TRS = Actions.ActOnTriviallyRelocatableSpecifier(ConsumeToken());
 }
 
@@ -2735,6 +2741,12 @@ bool Parser::isCXX2CReplaceableKeyword() const {
 void Parser::ParseOptionalCXX2CReplaceableSpecifier(ReplaceableSpecifier &MRS) {
   assert(isCXX2CReplaceableKeyword() &&
          "expected a replaceable_if_eligible specifier");
+
+  Diag(Tok.getLocation(), getLangOpts().CPlusPlus26
+                              ? diag::warn_relocatable_keyword
+                              : diag::ext_relocatable_keyword)
+      << /*replaceable*/ 1;
+
   MRS = Actions.ActOnReplaceableSpecifier(ConsumeToken());
 }
 
diff --git a/clang/test/Parser/cxx2c-trivially-relocatable.cpp b/clang/test/Parser/cxx2c-trivially-relocatable.cpp
index 7a575124a3b35..1bb1c536f8b36 100644
--- a/clang/test/Parser/cxx2c-trivially-relocatable.cpp
+++ b/clang/test/Parser/cxx2c-trivially-relocatable.cpp
@@ -1,8 +1,34 @@
-// RUN: %clang_cc1 -std=c++2b -verify -fsyntax-only %s
+// RUN: %clang_cc1 -std=c++2c -verify=expected -fsyntax-only %s
+// RUN: %clang_cc1 -std=c++11 -verify=expected,cxx11 -fsyntax-only %s
+
 
 
 class A trivially_relocatable_if_eligible {};
+// cxx11-warning at -1 {{'trivially_relocatable_if_eligible' keyword is a C++2c extension}}
 class E final trivially_relocatable_if_eligible {};
+// cxx11-warning at -1 {{'trivially_relocatable_if_eligible' keyword is a C++2c extension}}
 class G trivially_relocatable_if_eligible final{};
+// cxx11-warning at -1 {{'trivially_relocatable_if_eligible' keyword is a C++2c extension}}
 class I trivially_relocatable_if_eligible trivially_relocatable_if_eligible final {}; // expected-error {{class already marked 'trivially_relocatable_if_eligible'}}
+// cxx11-warning at -1 {{'trivially_relocatable_if_eligible' keyword is a C++2c extension}}
 class trivially_relocatable_if_eligible trivially_relocatable_if_eligible {};
+// cxx11-warning at -1 {{'trivially_relocatable_if_eligible' keyword is a C++2c extension}}
+
+class J replaceable_if_eligible{};
+// cxx11-warning at -1 {{'replaceable_if_eligible' keyword is a C++2c extension}}
+class K replaceable_if_eligible replaceable_if_eligible {}; // expected-error {{class already marked 'replaceable_if_eligible'}}
+// cxx11-warning at -1 {{'replaceable_if_eligible' keyword is a C++2c extension}}
+class replaceable_if_eligible replaceable_if_eligible {};
+// cxx11-warning at -1 {{'replaceable_if_eligible' keyword is a C++2c extension}}
+class L replaceable_if_eligible trivially_relocatable_if_eligible final {};
+// cxx11-warning at -1 {{'replaceable_if_eligible' keyword is a C++2c extension}}
+// cxx11-warning at -2 {{'trivially_relocatable_if_eligible' keyword is a C++2c extension}}
+class M replaceable_if_eligible final trivially_relocatable_if_eligible {};
+// cxx11-warning at -1 {{'trivially_relocatable_if_eligible' keyword is a C++2c extension}}
+// cxx11-warning at -2 {{'replaceable_if_eligible' keyword is a C++2c extension}}
+class N final trivially_relocatable_if_eligible replaceable_if_eligible {};
+// cxx11-warning at -1 {{'trivially_relocatable_if_eligible' keyword is a C++2c extension}}
+// cxx11-warning at -2 {{'replaceable_if_eligible' keyword is a C++2c extension}}
+class O trivially_relocatable_if_eligible replaceable_if_eligible final {};
+// cxx11-warning at -1 {{'trivially_relocatable_if_eligible' keyword is a C++2c extension}}
+// cxx11-warning at -2 {{'replaceable_if_eligible' keyword is a C++2c extension}}
diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html
index 64b731c19c517..cbcf462970b7f 100755
--- a/clang/www/cxx_status.html
+++ b/clang/www/cxx_status.html
@@ -280,7 +280,7 @@ <h2 id="cxx26">C++2c implementation status</h2>
  <tr>
   <td>Trivial Relocatability</pre></td>
   <td><a href="https://wg21.link/P2786">P2786R13</a></td>
-  <td class="none" align="center">No</td>
+  <td class="unreleased" align="center">Clang 21</td>
  </tr>
  <tr>
   <td><pre>#embed</pre></td>

>From a26fa6f8665324fdbad6c1353828efeb94aabb20 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Fri, 21 Feb 2025 11:12:22 +0100
Subject: [PATCH 10/27] changelog, docs, C++03 tests

---
 clang/docs/LanguageExtensions.rst             |  1 +
 clang/docs/ReleaseNotes.rst                   |  2 ++
 .../Parser/cxx2c-trivially-relocatable.cpp    | 22 +++++++++++++------
 3 files changed, 18 insertions(+), 7 deletions(-)

diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index 98d3128d81914..8dcd0774f1ac0 100644
--- a/clang/docs/LanguageExtensions.rst
+++ b/clang/docs/LanguageExtensions.rst
@@ -1680,6 +1680,7 @@ Static assert with user-generated message     __cpp_static_assert >= 202306L   C
 Pack Indexing                                 __cpp_pack_indexing              C++26         C++03
 ``= delete ("should have a reason");``        __cpp_deleted_function           C++26         C++03
 Variadic Friends                              __cpp_variadic_friend            C++26         C++03
+Trivial Relocatability                        __cpp_trivial_relocatability     C++26         C++03
 --------------------------------------------- -------------------------------- ------------- -------------
 Designated initializers (N494)                                                 C99           C89
 Array & element qualification (N2607)                                          C23           C89
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 203958dab7430..a4107bfaf913d 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -106,6 +106,8 @@ C++2c Feature Support
 ^^^^^^^^^^^^^^^^^^^^^
 
 - Implemented `P1061R10 Structured Bindings can introduce a Pack <https://wg21.link/P1061R10>`_.
+- Implemented `P2786R13 Trivial Relocatability <https://wg21.link/P2786R13>`_.
+
 
 - Implemented `P0963R3 Structured binding declaration as a condition <https://wg21.link/P0963R3>`_.
 
diff --git a/clang/test/Parser/cxx2c-trivially-relocatable.cpp b/clang/test/Parser/cxx2c-trivially-relocatable.cpp
index 1bb1c536f8b36..255e3e4f4460d 100644
--- a/clang/test/Parser/cxx2c-trivially-relocatable.cpp
+++ b/clang/test/Parser/cxx2c-trivially-relocatable.cpp
@@ -1,34 +1,42 @@
-// RUN: %clang_cc1 -std=c++2c -verify=expected -fsyntax-only %s
+// RUN: %clang_cc1 -std=c++03 -verify=expected,cxx11,cxx03 -fsyntax-only %s
 // RUN: %clang_cc1 -std=c++11 -verify=expected,cxx11 -fsyntax-only %s
-
+// RUN: %clang_cc1 -std=c++2c -verify=expected -fsyntax-only %s
 
 
 class A trivially_relocatable_if_eligible {};
 // cxx11-warning at -1 {{'trivially_relocatable_if_eligible' keyword is a C++2c extension}}
 class E final trivially_relocatable_if_eligible {};
 // cxx11-warning at -1 {{'trivially_relocatable_if_eligible' keyword is a C++2c extension}}
+// cxx03-warning at -2 {{'final' keyword is a C++11 extension}}
 class G trivially_relocatable_if_eligible final{};
 // cxx11-warning at -1 {{'trivially_relocatable_if_eligible' keyword is a C++2c extension}}
-class I trivially_relocatable_if_eligible trivially_relocatable_if_eligible final {}; // expected-error {{class already marked 'trivially_relocatable_if_eligible'}}
-// cxx11-warning at -1 {{'trivially_relocatable_if_eligible' keyword is a C++2c extension}}
+// cxx03-warning at -2 {{'final' keyword is a C++11 extension}}
+class I trivially_relocatable_if_eligible trivially_relocatable_if_eligible final {};
+// expected-error at -1 {{class already marked 'trivially_relocatable_if_eligible'}}
+// cxx11-warning at -2 {{'trivially_relocatable_if_eligible' keyword is a C++2c extension}}
+// cxx03-warning at -3 {{'final' keyword is a C++11 extension}}
 class trivially_relocatable_if_eligible trivially_relocatable_if_eligible {};
 // cxx11-warning at -1 {{'trivially_relocatable_if_eligible' keyword is a C++2c extension}}
-
 class J replaceable_if_eligible{};
 // cxx11-warning at -1 {{'replaceable_if_eligible' keyword is a C++2c extension}}
-class K replaceable_if_eligible replaceable_if_eligible {}; // expected-error {{class already marked 'replaceable_if_eligible'}}
-// cxx11-warning at -1 {{'replaceable_if_eligible' keyword is a C++2c extension}}
+class K replaceable_if_eligible replaceable_if_eligible {};
+// expected-error at -1 {{class already marked 'replaceable_if_eligible'}}
+// cxx11-warning at -2 {{'replaceable_if_eligible' keyword is a C++2c extension}}
 class replaceable_if_eligible replaceable_if_eligible {};
 // cxx11-warning at -1 {{'replaceable_if_eligible' keyword is a C++2c extension}}
 class L replaceable_if_eligible trivially_relocatable_if_eligible final {};
 // cxx11-warning at -1 {{'replaceable_if_eligible' keyword is a C++2c extension}}
 // cxx11-warning at -2 {{'trivially_relocatable_if_eligible' keyword is a C++2c extension}}
+// cxx03-warning at -3 {{'final' keyword is a C++11 extension}}
 class M replaceable_if_eligible final trivially_relocatable_if_eligible {};
 // cxx11-warning at -1 {{'trivially_relocatable_if_eligible' keyword is a C++2c extension}}
 // cxx11-warning at -2 {{'replaceable_if_eligible' keyword is a C++2c extension}}
+// cxx03-warning at -3 {{'final' keyword is a C++11 extension}}
 class N final trivially_relocatable_if_eligible replaceable_if_eligible {};
 // cxx11-warning at -1 {{'trivially_relocatable_if_eligible' keyword is a C++2c extension}}
 // cxx11-warning at -2 {{'replaceable_if_eligible' keyword is a C++2c extension}}
+// cxx03-warning at -3 {{'final' keyword is a C++11 extension}}
 class O trivially_relocatable_if_eligible replaceable_if_eligible final {};
 // cxx11-warning at -1 {{'trivially_relocatable_if_eligible' keyword is a C++2c extension}}
 // cxx11-warning at -2 {{'replaceable_if_eligible' keyword is a C++2c extension}}
+// cxx03-warning at -3 {{'final' keyword is a C++11 extension}}

>From b0539c0c2c0f37a1ad6c340df9ec9eabc50e8c64 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Fri, 21 Feb 2025 20:10:59 +0100
Subject: [PATCH 11/27] rewrite everything

---
 clang/docs/LanguageExtensions.rst             |   4 +-
 .../clang/Basic/DiagnosticParseKinds.td       |   2 +-
 clang/lib/AST/Type.cpp                        |   2 +-
 clang/lib/Parse/ParseDeclCXX.cpp              |   4 +-
 clang/lib/Sema/SemaChecking.cpp               |   2 +-
 clang/lib/Sema/SemaDeclCXX.cpp                | 217 +++++++++---------
 .../SemaCXX/cxx2c-trivially-relocatable.cpp   |  16 +-
 7 files changed, 131 insertions(+), 116 deletions(-)

diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index 8dcd0774f1ac0..086e9c85a469f 100644
--- a/clang/docs/LanguageExtensions.rst
+++ b/clang/docs/LanguageExtensions.rst
@@ -1864,11 +1864,11 @@ The following type trait primitives are supported by Clang. Those traits marked
   functionally equivalent to copying the underlying bytes and then dropping the
   source object on the floor. This is true of trivial types and types which
   were made trivially relocatable via the ``clang::trivial_abi`` attribute.
-* ``__builtin_is_cpp_trivially_relocatable`` (C++): Returns true if and object
+* ``__builtin_is_cpp_trivially_relocatable`` (C++): Returns true if an object
   is trivially relocatable, as defined by the C++26 standard.
   Note that the caller code should ensure that if the object is polymorphic,
   the dynamic type is of the most derived type.
-* ``__builtin_is_replaceable`` (C++): Returns true if and object
+* ``__builtin_is_replaceable`` (C++): Returns true if an object
   is replaceable, as defined by the C++26 standard.
 * ``__is_trivially_equality_comparable`` (Clang): Returns true if comparing two
   objects of the provided type is known to be equivalent to comparing their
diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td
index 08d57e62f4f40..6ebf3b4533c1e 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -1081,7 +1081,7 @@ def err_duplicate_class_virt_specifier : Error<
   "class already marked '%0'">;
 
 def err_duplicate_class_relocation_specifier : Error<
-  "class already marked %select{'trivially_relocatable_if_eligible'|'replaceable_if_eligible'}0">;
+  "class already marked '%select{trivially_relocatable_if_eligible|replaceable_if_eligible}0'">;
 
 def err_duplicate_virt_specifier : Error<
   "class member already marked '%0'">;
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 0f6ecbb307040..7bd9565f093ec 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -2931,7 +2931,7 @@ bool QualType::isCppTriviallyRelocatableType(const ASTContext &Context) const {
 }
 
 bool QualType::isReplaceableType(const ASTContext &Context) const {
-  if (isConstQualified())
+  if (isConstQualified() || isVolatileQualified())
     return false;
   QualType BaseElementType = Context.getBaseElementType(getUnqualifiedType());
   if (BaseElementType->isIncompleteType())
diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp
index 05b24f62c91ce..aa2b0246ce23b 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -2751,7 +2751,9 @@ void Parser::ParseOptionalCXX2CReplaceableSpecifier(ReplaceableSpecifier &MRS) {
 }
 
 /// isClassCompatibleKeyword - Determine whether the next token is a C++11
-/// 'final' or Microsoft 'sealed' or 'abstract' contextual keywords.
+/// 'final', a C++26 'trivially_relocatable_if_eligible',
+/// 'replaceable_if_eligible', or Microsoft 'sealed' or 'abstract' contextual
+/// keyword.
 bool Parser::isClassCompatibleKeyword(Token Tok) const {
   if (isCXX2CTriviallyRelocatableKeyword(Tok) || isCXX2CReplaceableKeyword(Tok))
     return true;
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index aa39dd62b1c08..bac338e649fb4 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -1951,7 +1951,7 @@ static ExprResult BuiltinTriviallyRelocate(Sema &S, CallExpr *TheCall) {
 
   QualType Src = TheCall->getArg(1)->getType();
   if (Src.getCanonicalType() != Dest.getCanonicalType()) {
-    S.Diag(TheCall->getArg(0)->getExprLoc(),
+    S.Diag(TheCall->getArg(1)->getExprLoc(),
            diag::err_builtin_trivially_relocate_invalid_arg_type)
         << /*the same*/ 3;
     return ExprError();
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index a1d69e3843567..ecbd9074d1020 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -7372,15 +7372,16 @@ void Sema::CheckCompletedCXXClass(Scope *S, CXXRecordDecl *Record) {
   CheckMismatchedTypeAwareAllocators(OO_Array_New, OO_Array_Delete);
 }
 
-static bool hasSuitableConstructorForReplaceability(CXXRecordDecl *D,
-                                                    bool Implicit) {
+static bool hasSuitableConstructorForRelocation(CXXRecordDecl *D,
+                                                bool AllowUserDefined) {
   assert(D->hasDefinition() && !D->isInvalidDecl());
 
   bool HasDeletedMoveConstructor = false;
   bool HasDeletedCopyConstructor = false;
   bool HasMoveConstructor = D->needsImplicitMoveConstructor();
+  bool HasCopyConstructor = D->needsImplicitCopyConstructor();
   bool HasDefaultedMoveConstructor = D->needsImplicitMoveConstructor();
-  bool HasDefaultedCopyConstructor = D->needsImplicitMoveConstructor();
+  bool HasDefaultedCopyConstructor = D->needsImplicitCopyConstructor();
 
   for (const Decl *D : D->decls()) {
     auto *MD = dyn_cast<CXXConstructorDecl>(D);
@@ -7393,8 +7394,8 @@ static bool hasSuitableConstructorForReplaceability(CXXRecordDecl *D,
         HasDefaultedMoveConstructor = true;
       if (MD->isDeleted())
         HasDeletedMoveConstructor = true;
-    }
-    if (MD->isCopyConstructor()) {
+    } else if (MD->isCopyConstructor()) {
+      HasCopyConstructor = true;
       if (MD->isDefaulted())
         HasDefaultedCopyConstructor = true;
       if (MD->isDeleted())
@@ -7404,14 +7405,14 @@ static bool hasSuitableConstructorForReplaceability(CXXRecordDecl *D,
 
   if (HasMoveConstructor)
     return !HasDeletedMoveConstructor &&
-           (Implicit ? HasDefaultedMoveConstructor : true);
-  return !HasDeletedCopyConstructor &&
-         (Implicit ? HasDefaultedCopyConstructor : true);
-  ;
+           (AllowUserDefined ? true : HasDefaultedMoveConstructor);
+  return HasCopyConstructor && !HasDeletedCopyConstructor &&
+         (AllowUserDefined ? true : HasDefaultedCopyConstructor);
 }
 
-static bool hasSuitableMoveAssignmentOperatorForReplaceability(CXXRecordDecl *D,
-                                                               bool Implicit) {
+static bool
+hasSuitableMoveAssignmentOperatorForRelocation(CXXRecordDecl *D,
+                                               bool AllowUserDefined) {
   assert(D->hasDefinition() && !D->isInvalidDecl());
 
   if (D->hasExplicitlyDeletedMoveAssignment())
@@ -7434,8 +7435,7 @@ static bool hasSuitableMoveAssignmentOperatorForReplaceability(CXXRecordDecl *D,
         HasDefaultedMoveAssignment = true;
       if (MD->isDeleted())
         HasDeletedMoveAssignment = true;
-    }
-    if (MD->isCopyAssignmentOperator()) {
+    } else if (MD->isCopyAssignmentOperator()) {
       if (MD->isDefaulted())
         HasDefaultedCopyAssignment = true;
       if (MD->isDeleted())
@@ -7445,29 +7445,45 @@ static bool hasSuitableMoveAssignmentOperatorForReplaceability(CXXRecordDecl *D,
 
   if (HasMoveAssignment)
     return !HasDeletedMoveAssignment &&
-           (Implicit ? HasDefaultedMoveAssignment : true);
+           (AllowUserDefined ? true : HasDefaultedMoveAssignment);
   return !HasDeletedCopyAssignment &&
-         (Implicit ? HasDefaultedCopyAssignment : true);
+         (AllowUserDefined ? true : HasDefaultedCopyAssignment);
 }
 
-void Sema::CheckCXX2CTriviallyRelocatable(CXXRecordDecl *D) {
-  if (D->isInvalidDecl())
-    return;
+static bool isDefaultMovable(CXXRecordDecl *D) {
+  if (!hasSuitableConstructorForRelocation(D, /*AllowUserDefined*/ false))
+    return false;
 
-  assert(D->hasDefinition());
+  if (!hasSuitableMoveAssignmentOperatorForRelocation(
+          D, /*AllowUserDefined*/ false))
+    return false;
 
-  bool MarkedTriviallyRelocatable =
-      D->getTriviallyRelocatableSpecifier().isSet();
+  const auto *Dtr = D->getDestructor();
+  if (!Dtr)
+    return true;
+
+  if (Dtr->isUserProvided() && (!Dtr->isDefaulted() || Dtr->isDeleted()))
+    return false;
+
+  return !Dtr->isDeleted();
+}
+
+static bool hasDeletedDestructor(CXXRecordDecl *D) {
+  const auto *Dtr = D->getDestructor();
+  if (Dtr)
+    return Dtr->isDeleted();
+  return false;
+}
+
+static bool isEligibleForTrivialRelocation(Sema &SemaRef, CXXRecordDecl *D) {
 
-  bool IsTriviallyRelocatable = true;
   for (const CXXBaseSpecifier &B : D->bases()) {
     const auto *BaseDecl = B.getType()->getAsCXXRecordDecl();
     if (!BaseDecl)
       continue;
     if (B.isVirtual() ||
-        (!BaseDecl->isDependentType() && !BaseDecl->isTriviallyRelocatable())) {
-      IsTriviallyRelocatable = false;
-    }
+        (!BaseDecl->isDependentType() && !BaseDecl->isTriviallyRelocatable()))
+      return false;
   }
 
   for (const FieldDecl *Field : D->fields()) {
@@ -7475,114 +7491,105 @@ void Sema::CheckCXX2CTriviallyRelocatable(CXXRecordDecl *D) {
       continue;
     if (Field->getType()->isReferenceType())
       continue;
-    QualType T = getASTContext().getBaseElementType(
+    QualType T = SemaRef.getASTContext().getBaseElementType(
         Field->getType().getUnqualifiedType());
     if (CXXRecordDecl *RD = T->getAsCXXRecordDecl()) {
-      if (RD->isTriviallyRelocatable())
-        continue;
-      IsTriviallyRelocatable = false;
-      break;
+      if (!RD->isTriviallyRelocatable())
+        return false;
     }
   }
 
-  if (!D->isDependentType() && !MarkedTriviallyRelocatable) {
-    bool HasSuitableMoveCtr = D->needsImplicitMoveConstructor();
-    bool HasSuitableCopyCtr = false;
-    if (D->hasUserDeclaredDestructor()) {
-      const auto *Dtr = D->getDestructor();
-      if (Dtr && (!Dtr->isDefaulted() || Dtr->isDeleted()))
-        IsTriviallyRelocatable = false;
-    }
-    if (IsTriviallyRelocatable && !HasSuitableMoveCtr) {
-      for (const CXXConstructorDecl *CD : D->ctors()) {
-        if (CD->isMoveConstructor() && CD->isDefaulted() &&
-            !CD->isIneligibleOrNotSelected()) {
-          HasSuitableMoveCtr = true;
-          break;
-        }
-      }
-    }
-    if (!HasSuitableMoveCtr && !D->hasMoveConstructor()) {
-      HasSuitableCopyCtr = D->needsImplicitCopyConstructor();
-      if (!HasSuitableCopyCtr) {
-        for (const CXXConstructorDecl *CD : D->ctors()) {
-          if (CD->isCopyConstructor() && CD->isDefaulted() &&
-              !CD->isIneligibleOrNotSelected()) {
-            HasSuitableCopyCtr = true;
-            break;
-          }
-        }
-      }
-    }
+  return !hasDeletedDestructor(D);
+}
+
+void Sema::CheckCXX2CTriviallyRelocatable(CXXRecordDecl *D) {
+  if (!getLangOpts().CPlusPlus || D->isInvalidDecl())
+    return;
+
+  assert(D->hasDefinition());
+
+  bool MarkedTriviallyRelocatable =
+      D->getTriviallyRelocatableSpecifier().isSet();
+
+  bool IsTriviallyRelocatable = [&] {
+    if (!isEligibleForTrivialRelocation(*this, D))
+      return false;
+
+    if (D->isDependentType() || MarkedTriviallyRelocatable)
+      return true;
 
     if (D->isUnion() && !D->hasUserDeclaredCopyConstructor() &&
         !D->hasUserDeclaredCopyAssignment() &&
         !D->hasUserDeclaredMoveOperation() && !D->hasUserDeclaredDestructor()) {
-      // Do nothing
+      return true;
     }
 
-    else if (!HasSuitableMoveCtr && !HasSuitableCopyCtr)
-      IsTriviallyRelocatable = false;
+    return isDefaultMovable(D);
+  }();
+
+  D->setIsTriviallyRelocatable(IsTriviallyRelocatable);
+}
+
+static bool isEligibleForReplacement(Sema &SemaRef, CXXRecordDecl *D) {
+
+  for (const CXXBaseSpecifier &B : D->bases()) {
+    const auto *BaseDecl = B.getType()->getAsCXXRecordDecl();
+    if (!BaseDecl)
+      continue;
+    if ((!BaseDecl->isDependentType() && !BaseDecl->isReplaceable()))
+      return false;
+  }
+
+  for (const FieldDecl *Field : D->fields()) {
+    if (Field->getType()->isDependentType())
+      continue;
+
+    if (Field->getType()->isReferenceType())
+      return false;
+
+    if (Field->getType().isConstQualified())
+      return false;
 
-    else if (IsTriviallyRelocatable &&
-             ((!D->needsImplicitMoveAssignment() &&
-               (D->hasUserProvidedMoveAssignment() ||
-                D->hasExplicitlyDeletedMoveAssignment())) ||
-              (!D->hasMoveAssignment() &&
-               D->hasUserProvidedCopyAssignment()))) {
-      IsTriviallyRelocatable = false;
+    QualType T = SemaRef.getASTContext().getBaseElementType(
+        Field->getType().getUnqualifiedType());
+    if (CXXRecordDecl *RD = T->getAsCXXRecordDecl()) {
+      if (!RD->isReplaceable())
+        return false;
     }
   }
 
-  D->setIsTriviallyRelocatable(IsTriviallyRelocatable);
+  return !hasDeletedDestructor(D);
 }
 
 void Sema::CheckCXX2CReplaceable(CXXRecordDecl *D) {
-  if (D->isInvalidDecl())
+  if (!getLangOpts().CPlusPlus || D->isInvalidDecl())
     return;
 
   assert(D->hasDefinition());
 
   bool MarkedCXX2CReplaceable = D->getReplaceableSpecifier().isSet();
 
-  bool IsReplaceable = true;
+  auto HasSuitableSMP = [&] {
+    return hasSuitableConstructorForRelocation(D, /*AllowUserDefined=*/true) &&
+           hasSuitableMoveAssignmentOperatorForRelocation(
+               D, /*AllowUserDefined=*/true);
+  };
 
-  for (const CXXBaseSpecifier &B : D->bases()) {
-    const auto *BaseDecl = B.getType()->getAsCXXRecordDecl();
-    if (!BaseDecl)
-      continue;
-    if ((!BaseDecl->isDependentType() && !BaseDecl->isReplaceable()) ||
-        B.isVirtual())
-      IsReplaceable = false;
-  }
+  bool IsReplaceable = [&] {
+    if (!isEligibleForReplacement(*this, D))
+      return false;
 
-  if (!hasSuitableConstructorForReplaceability(D, !MarkedCXX2CReplaceable) ||
-      !hasSuitableMoveAssignmentOperatorForReplaceability(
-          D, !MarkedCXX2CReplaceable)) {
-    IsReplaceable = false;
-  }
+    if (D->isDependentType() || MarkedCXX2CReplaceable)
+      return HasSuitableSMP();
 
-  if (IsReplaceable) {
-    for (const FieldDecl *Field : D->fields()) {
-      if (Field->getType()->isDependentType())
-        continue;
-      if (Field->getType()->isReferenceType()) {
-        IsReplaceable = false;
-        break;
-      }
-      if (Field->getType().isConstQualified()) {
-        IsReplaceable = false;
-        break;
-      }
-      QualType T = getASTContext().getBaseElementType(
-          Field->getType().getUnqualifiedType());
-      if (CXXRecordDecl *RD = T->getAsCXXRecordDecl()) {
-        if (RD->isReplaceable())
-          continue;
-        IsReplaceable = false;
-      }
+    if (D->isUnion() && !D->hasUserDeclaredCopyConstructor() &&
+        !D->hasUserDeclaredCopyAssignment() &&
+        !D->hasUserDeclaredMoveOperation() && !D->hasUserDeclaredDestructor()) {
+      return HasSuitableSMP();
     }
-  }
+
+    return isDefaultMovable(D);
+  }();
 
   D->setIsReplaceable(IsReplaceable);
 }
diff --git a/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp b/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp
index 3ffa88e7c954f..509ef2167d432 100644
--- a/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp
+++ b/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp
@@ -71,6 +71,11 @@ struct UserMoveDefault{
     UserMoveDefault(UserMoveDefault&&) = default;
 };
 
+struct UserMoveAssignDefault {
+    UserMoveAssignDefault(UserMoveAssignDefault&&) = default;
+    UserMoveAssignDefault& operator=(UserMoveAssignDefault&&) = default;
+};
+
 struct UserCopy{
     UserCopy(const UserCopy&);
 };
@@ -90,9 +95,10 @@ static_assert(__builtin_is_cpp_trivially_relocatable(DefaultedDtr));
 static_assert(!__builtin_is_cpp_trivially_relocatable(UserMoveWithDefaultCopy));
 static_assert(!__builtin_is_cpp_trivially_relocatable(UserMove));
 static_assert(!__builtin_is_cpp_trivially_relocatable(UserCopy));
-static_assert(__builtin_is_cpp_trivially_relocatable(UserMoveDefault));
+static_assert(!__builtin_is_cpp_trivially_relocatable(UserMoveDefault));
+static_assert(__builtin_is_cpp_trivially_relocatable(UserMoveAssignDefault));
 static_assert(__builtin_is_cpp_trivially_relocatable(UserCopyDefault));
-static_assert(__builtin_is_cpp_trivially_relocatable(UserDeletedMove));
+static_assert(!__builtin_is_cpp_trivially_relocatable(UserDeletedMove));
 
 template <typename T>
 class TestDependentErrors trivially_relocatable_if_eligible : T {};
@@ -202,7 +208,7 @@ static_assert(!__builtin_is_replaceable(S<const int[2]>));
 static_assert(__builtin_is_replaceable(WithBase<S<int>>));
 static_assert(!__builtin_is_replaceable(WithBase<S<const int>>));
 static_assert(!__builtin_is_replaceable(WithBase<UserProvidedMove>));
-static_assert(!__builtin_is_replaceable(WithVBase<S<int>>));
+static_assert(__builtin_is_replaceable(WithVBase<S<int>>));
 static_assert(!__builtin_is_replaceable(WithVBase<S<const int>>));
 static_assert(!__builtin_is_replaceable(WithVBase<UserProvidedMove>));
 static_assert(__builtin_is_replaceable(WithVirtual));
@@ -214,7 +220,7 @@ struct U1 replaceable_if_eligible {
     U1& operator=(U1&&) = default;
 
 };
-static_assert(__builtin_is_replaceable(U1));
+static_assert(!__builtin_is_replaceable(U1));
 
 struct U2 replaceable_if_eligible {
     U2(const U2&) = delete;
@@ -224,7 +230,7 @@ static_assert(!__builtin_is_replaceable(U2));
 
 template <typename T>
 struct WithVBaseExplicit replaceable_if_eligible : virtual T{};
-static_assert(__builtin_is_replaceable(WithVBaseExplicit<S<int>>)); // expected-error {{failed}}
+static_assert(__builtin_is_replaceable(WithVBaseExplicit<S<int>>));
 
 struct S42 trivially_relocatable_if_eligible replaceable_if_eligible {
     S42(S42&&);

>From ce36a344e883398ed6478270d9d553a50187cd04 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Mon, 24 Feb 2025 13:48:16 +0100
Subject: [PATCH 12/27] address more feedback

---
 clang/docs/LanguageExtensions.rst             |  4 +-
 .../clang/Basic/DiagnosticParseKinds.td       |  2 +-
 clang/include/clang/Parse/Parser.h            |  6 +-
 clang/lib/Parse/ParseDeclCXX.cpp              | 14 ++--
 clang/lib/Sema/SemaChecking.cpp               |  3 +-
 .../SemaCXX/cxx2c-trivially-relocatable.cpp   | 64 ++++++++++++++++---
 6 files changed, 69 insertions(+), 24 deletions(-)

diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index 086e9c85a469f..2d2c5e383780e 100644
--- a/clang/docs/LanguageExtensions.rst
+++ b/clang/docs/LanguageExtensions.rst
@@ -1865,11 +1865,11 @@ The following type trait primitives are supported by Clang. Those traits marked
   source object on the floor. This is true of trivial types and types which
   were made trivially relocatable via the ``clang::trivial_abi`` attribute.
 * ``__builtin_is_cpp_trivially_relocatable`` (C++): Returns true if an object
-  is trivially relocatable, as defined by the C++26 standard.
+  is trivially relocatable, as defined by the C++26 standard [meta.unary.prop].
   Note that the caller code should ensure that if the object is polymorphic,
   the dynamic type is of the most derived type.
 * ``__builtin_is_replaceable`` (C++): Returns true if an object
-  is replaceable, as defined by the C++26 standard.
+  is replaceable, as defined by the C++26 standard [meta.unary.prop].
 * ``__is_trivially_equality_comparable`` (Clang): Returns true if comparing two
   objects of the provided type is known to be equivalent to comparing their
   object representations. Note that types containing padding bytes are never
diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td
index 6ebf3b4533c1e..33e9296f39eeb 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -1066,7 +1066,7 @@ def ext_ms_abstract_keyword : ExtWarn<
   InGroup<MicrosoftAbstract>;
 
 def ext_relocatable_keyword : ExtWarn<
-  "'%select{trivially_relocatable|replaceable}0_if_eligible' "
+  "'%select{trivially_relocatable_if_eligible|replaceable_if_eligible}0' "
   "keyword is a C++2c extension">,
   InGroup<CXX26>;
 def warn_relocatable_keyword  : Warning<
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index e8a1e14661dbb..2fbe60764636c 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -3201,12 +3201,12 @@ class Parser : public CodeCompletionHandler {
 
   bool isCXX2CTriviallyRelocatableKeyword(Token Tok) const;
   bool isCXX2CTriviallyRelocatableKeyword() const;
-  void ParseOptionalCXX2CTriviallyRelocatableSpecifier(
-      TriviallyRelocatableSpecifier &TRS);
+  void
+  ParseCXX2CTriviallyRelocatableSpecifier(TriviallyRelocatableSpecifier &TRS);
 
   bool isCXX2CReplaceableKeyword(Token Tok) const;
   bool isCXX2CReplaceableKeyword() const;
-  void ParseOptionalCXX2CReplaceableSpecifier(ReplaceableSpecifier &MRS);
+  void ParseCXX2CReplaceableSpecifier(ReplaceableSpecifier &MRS);
 
   bool isClassCompatibleKeyword(Token Tok) const;
   bool isClassCompatibleKeyword() const;
diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp
index aa2b0246ce23b..287ee8da62556 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -2711,7 +2711,7 @@ bool Parser::isCXX2CTriviallyRelocatableKeyword() const {
   return isCXX2CTriviallyRelocatableKeyword(Tok);
 }
 
-void Parser::ParseOptionalCXX2CTriviallyRelocatableSpecifier(
+void Parser::ParseCXX2CTriviallyRelocatableSpecifier(
     TriviallyRelocatableSpecifier &TRS) {
   assert(isCXX2CTriviallyRelocatableKeyword() &&
          "expected a trivially_relocatable specifier");
@@ -2738,7 +2738,7 @@ bool Parser::isCXX2CReplaceableKeyword() const {
   return isCXX2CReplaceableKeyword(Tok);
 }
 
-void Parser::ParseOptionalCXX2CReplaceableSpecifier(ReplaceableSpecifier &MRS) {
+void Parser::ParseCXX2CReplaceableSpecifier(ReplaceableSpecifier &MRS) {
   assert(isCXX2CReplaceableKeyword() &&
          "expected a replaceable_if_eligible specifier");
 
@@ -3885,10 +3885,10 @@ void Parser::ParseCXXMemberSpecification(SourceLocation RecordLoc,
             auto Skipped = Tok;
             ConsumeToken();
             Diag(Skipped, diag::err_duplicate_class_relocation_specifier)
-                << 0 << TriviallyRelocatable.getLocation();
+                << /*trivial_relocatable*/ 0
+                << TriviallyRelocatable.getLocation();
           } else {
-            ParseOptionalCXX2CTriviallyRelocatableSpecifier(
-                TriviallyRelocatable);
+            ParseCXX2CTriviallyRelocatableSpecifier(TriviallyRelocatable);
           }
           continue;
         } else if (isCXX2CReplaceableKeyword(Tok)) {
@@ -3896,9 +3896,9 @@ void Parser::ParseCXXMemberSpecification(SourceLocation RecordLoc,
             auto Skipped = Tok;
             ConsumeToken();
             Diag(Skipped, diag::err_duplicate_class_relocation_specifier)
-                << 1 << Replacable.getLocation();
+                << /*replaceable*/ 1 << Replacable.getLocation();
           } else {
-            ParseOptionalCXX2CReplaceableSpecifier(Replacable);
+            ParseCXX2CReplaceableSpecifier(Replacable);
           }
           continue;
         } else {
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index bac338e649fb4..f1add3244d02d 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -1940,7 +1940,8 @@ static ExprResult BuiltinTriviallyRelocate(Sema &S, CallExpr *TheCall) {
     return ExprError();
 
   if (T.isConstQualified() ||
-      !T.isCppTriviallyRelocatableType(S.getASTContext())) {
+      !T.isCppTriviallyRelocatableType(S.getASTContext()) ||
+      T->isIncompleteArrayType()) {
     S.Diag(TheCall->getArg(0)->getExprLoc(),
            diag::err_builtin_trivially_relocate_invalid_arg_type)
         << (T.isConstQualified() ? /*non-const*/ 1 : /*relocatable*/ 2);
diff --git a/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp b/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp
index 509ef2167d432..15640bd549372 100644
--- a/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp
+++ b/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp
@@ -1,13 +1,19 @@
 // RUN: %clang_cc1 -std=c++2c -verify %s
 
 class Trivial {};
+static_assert(__builtin_is_cpp_trivially_relocatable(Trivial));
 struct NonRelocatable {
     ~NonRelocatable();
 };
 static NonRelocatable NonRelocatable_g;
 
 class A trivially_relocatable_if_eligible {};
+static_assert(__builtin_is_cpp_trivially_relocatable(A));
+
+
 class B trivially_relocatable_if_eligible : Trivial{};
+static_assert(__builtin_is_cpp_trivially_relocatable(B));
+
 class C trivially_relocatable_if_eligible {
     int a;
     void* b;
@@ -15,10 +21,29 @@ class C trivially_relocatable_if_eligible {
     Trivial d[3];
     NonRelocatable& e = NonRelocatable_g;
 };
+static_assert(__builtin_is_cpp_trivially_relocatable(C));
+
+
 class D trivially_relocatable_if_eligible : Trivial {};
+static_assert(__builtin_is_cpp_trivially_relocatable(D));
+
+
 class E trivially_relocatable_if_eligible : virtual Trivial {};
+static_assert(!__builtin_is_cpp_trivially_relocatable(E));
+
 
 class F trivially_relocatable_if_eligible : NonRelocatable {};
+static_assert(!__builtin_is_cpp_trivially_relocatable(F));
+
+class G trivially_relocatable_if_eligible {
+    G(G&&);
+};
+static_assert(__builtin_is_cpp_trivially_relocatable(G));
+
+class H trivially_relocatable_if_eligible {
+    ~H();
+};
+static_assert(__builtin_is_cpp_trivially_relocatable(H));
 
 class I trivially_relocatable_if_eligible {
     NonRelocatable a;
@@ -26,30 +51,29 @@ class I trivially_relocatable_if_eligible {
     const NonRelocatable c;
     const NonRelocatable d[1];
 };
+static_assert(!__builtin_is_cpp_trivially_relocatable(I));
+
 
 class J trivially_relocatable_if_eligible:  virtual Trivial, NonRelocatable {
     NonRelocatable a;
 };
+static_assert(!__builtin_is_cpp_trivially_relocatable(J));
 
-class G trivially_relocatable_if_eligible {
-    G(G&&);
-};
-
-class H trivially_relocatable_if_eligible {
-    ~H();
-};
 
 struct Incomplete; // expected-note {{forward declaration of 'Incomplete'}}
 static_assert(__builtin_is_cpp_trivially_relocatable(Incomplete));  // expected-error {{incomplete type 'Incomplete' used in type trait expression}}
-static_assert(__builtin_is_cpp_trivially_relocatable(Trivial));
-static_assert(__builtin_is_cpp_trivially_relocatable(G));
-static_assert(__builtin_is_cpp_trivially_relocatable(H));
 static_assert(__builtin_is_cpp_trivially_relocatable(int));
 static_assert(__builtin_is_cpp_trivially_relocatable(void*));
 static_assert(!__builtin_is_cpp_trivially_relocatable(int&));
 static_assert(!__builtin_is_cpp_trivially_relocatable(Trivial&));
 static_assert(__builtin_is_cpp_trivially_relocatable(const Trivial));
 static_assert(__builtin_is_cpp_trivially_relocatable(Trivial[1]));
+static_assert(__builtin_is_cpp_trivially_relocatable(Trivial[]));
+
+struct WithConst {
+    const int i;
+};
+static_assert(__builtin_is_cpp_trivially_relocatable(WithConst));
 
 struct UserDtr {
     ~UserDtr();
@@ -115,9 +139,15 @@ struct DeletedMoveAssign {
     DeletedMoveAssign& operator=(DeletedMoveAssign&&) = delete;
 };
 
+struct DeletedDtr {
+    ~DeletedDtr() = delete;
+};
+
 static_assert(!__builtin_is_cpp_trivially_relocatable(DeletedMove));
 static_assert(!__builtin_is_cpp_trivially_relocatable(DeletedCopy));
 static_assert(!__builtin_is_cpp_trivially_relocatable(DeletedMoveAssign));
+static_assert(!__builtin_is_cpp_trivially_relocatable(DeletedDtr));
+
 
 union U {
     G g;
@@ -174,6 +204,7 @@ static_assert(__builtin_is_replaceable(DefaultedMoveAssign));
 static_assert(!__builtin_is_replaceable(DeletedMove));
 static_assert(!__builtin_is_replaceable(DeletedCopy));
 static_assert(!__builtin_is_replaceable(DeletedMoveAssign));
+static_assert(!__builtin_is_replaceable(DeletedDtr));
 
 static_assert(!__builtin_is_replaceable(UserProvidedMove));
 static_assert(!__builtin_is_replaceable(UserProvidedCopy));
@@ -276,3 +307,16 @@ void test__builtin_trivially_relocate() {
     __builtin_trivially_relocate((int*)0, (int*)0, 0);
     __builtin_trivially_relocate((R*)0, (R*)0, 0);
 }
+
+void test__builtin_trivially_relocate(auto&& src, auto&&dest, auto size) {
+    __builtin_trivially_relocate(src, dest, size); // #reloc1
+}
+
+void do_test__builtin_trivially_relocate() {
+    struct S{ ~S();};
+    struct R {};
+    test__builtin_trivially_relocate((R*)0, (R*)0, 0);
+    test__builtin_trivially_relocate((S*)0, (S*)0, 0);
+    // expected-note at -1 {{'test__builtin_trivially_relocate<S *, S *, int>' requested here}}
+    // expected-error@#reloc1 {{first argument to '__builtin_trivially_relocate' must be relocatable}}
+}

>From f6aa238ca952a09ed9cd97c62ea0790adcd4b309 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Mon, 24 Feb 2025 14:06:10 +0100
Subject: [PATCH 13/27] Add comments

---
 clang/lib/Sema/SemaDeclCXX.cpp | 51 +++++++++++++++++++++++++---------
 1 file changed, 38 insertions(+), 13 deletions(-)

diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index ecbd9074d1020..56bc1b9f731e1 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -7450,12 +7450,21 @@ hasSuitableMoveAssignmentOperatorForRelocation(CXXRecordDecl *D,
          (AllowUserDefined ? true : HasDefaultedCopyAssignment);
 }
 
+// [C++26][class.prop]
+// A class C is default-movable if
+// - overload resolution for direct-initializing an object of type C
+// from an xvalue of type C selects a constructor that is a direct member of C
+// and is neither user-provided nor deleted,
+// - overload resolution for assigning to an lvalue of type C from an xvalue of
+// type C selects an assignment operator function that is a direct member of C
+// and is neither user-provided nor deleted, and C has a destructor that is
+// neither user-provided nor deleted.
 static bool isDefaultMovable(CXXRecordDecl *D) {
-  if (!hasSuitableConstructorForRelocation(D, /*AllowUserDefined*/ false))
+  if (!hasSuitableConstructorForRelocation(D, /*AllowUserDefined=*/false))
     return false;
 
   if (!hasSuitableMoveAssignmentOperatorForRelocation(
-          D, /*AllowUserDefined*/ false))
+          D, /*AllowUserDefined=*/false))
     return false;
 
   const auto *Dtr = D->getDestructor();
@@ -7475,12 +7484,16 @@ static bool hasDeletedDestructor(CXXRecordDecl *D) {
   return false;
 }
 
+// [C++26][class.prop]
+// A class is eligible for trivial relocation unless it...
 static bool isEligibleForTrivialRelocation(Sema &SemaRef, CXXRecordDecl *D) {
 
   for (const CXXBaseSpecifier &B : D->bases()) {
     const auto *BaseDecl = B.getType()->getAsCXXRecordDecl();
     if (!BaseDecl)
       continue;
+    // ... has any virtual base classes
+    // ... has a base class that is not a trivially relocatable class
     if (B.isVirtual() ||
         (!BaseDecl->isDependentType() && !BaseDecl->isTriviallyRelocatable()))
       return false;
@@ -7491,6 +7504,8 @@ static bool isEligibleForTrivialRelocation(Sema &SemaRef, CXXRecordDecl *D) {
       continue;
     if (Field->getType()->isReferenceType())
       continue;
+    // ... has a non-static data member of an object type that is not
+    // of a trivially relocatable type
     QualType T = SemaRef.getASTContext().getBaseElementType(
         Field->getType().getUnqualifiedType());
     if (CXXRecordDecl *RD = T->getAsCXXRecordDecl()) {
@@ -7499,9 +7514,12 @@ static bool isEligibleForTrivialRelocation(Sema &SemaRef, CXXRecordDecl *D) {
     }
   }
 
+  // ...has a deleted destructor
   return !hasDeletedDestructor(D);
 }
 
+// [C++26][class.prop]
+// A class C is a trivially relocatable class if
 void Sema::CheckCXX2CTriviallyRelocatable(CXXRecordDecl *D) {
   if (!getLangOpts().CPlusPlus || D->isInvalidDecl())
     return;
@@ -7512,30 +7530,38 @@ void Sema::CheckCXX2CTriviallyRelocatable(CXXRecordDecl *D) {
       D->getTriviallyRelocatableSpecifier().isSet();
 
   bool IsTriviallyRelocatable = [&] {
+    // if it is eligible for trivial relocation
+
     if (!isEligibleForTrivialRelocation(*this, D))
       return false;
 
+    // has the trivially_relocatable_if_eligible class-property-specifier,
     if (D->isDependentType() || MarkedTriviallyRelocatable)
       return true;
 
+    // is a union with no user-declared special member functions, or
     if (D->isUnion() && !D->hasUserDeclaredCopyConstructor() &&
         !D->hasUserDeclaredCopyAssignment() &&
         !D->hasUserDeclaredMoveOperation() && !D->hasUserDeclaredDestructor()) {
       return true;
     }
 
+    // is default-movable.
     return isDefaultMovable(D);
   }();
 
   D->setIsTriviallyRelocatable(IsTriviallyRelocatable);
 }
 
+// [C++26][class.prop]
+// A class C is eligible for replacement unless
 static bool isEligibleForReplacement(Sema &SemaRef, CXXRecordDecl *D) {
 
   for (const CXXBaseSpecifier &B : D->bases()) {
     const auto *BaseDecl = B.getType()->getAsCXXRecordDecl();
     if (!BaseDecl)
       continue;
+    // it has a base class that is not a replaceable class
     if ((!BaseDecl->isDependentType() && !BaseDecl->isReplaceable()))
       return false;
   }
@@ -7544,23 +7570,16 @@ static bool isEligibleForReplacement(Sema &SemaRef, CXXRecordDecl *D) {
     if (Field->getType()->isDependentType())
       continue;
 
-    if (Field->getType()->isReferenceType())
+    // it has a non-static data member that is not of a replaceable type,
+    if (Field->getType().isReplaceableType(SemaRef.getASTContext()))
       return false;
-
-    if (Field->getType().isConstQualified())
-      return false;
-
-    QualType T = SemaRef.getASTContext().getBaseElementType(
-        Field->getType().getUnqualifiedType());
-    if (CXXRecordDecl *RD = T->getAsCXXRecordDecl()) {
-      if (!RD->isReplaceable())
-        return false;
-    }
   }
 
+  // it has a deleted destructor.
   return !hasDeletedDestructor(D);
 }
 
+// [C++26][class.prop] A class C is a replaceable class if...
 void Sema::CheckCXX2CReplaceable(CXXRecordDecl *D) {
   if (!getLangOpts().CPlusPlus || D->isInvalidDecl())
     return;
@@ -7569,6 +7588,8 @@ void Sema::CheckCXX2CReplaceable(CXXRecordDecl *D) {
 
   bool MarkedCXX2CReplaceable = D->getReplaceableSpecifier().isSet();
 
+  // This is part of "eligible for replacement", however we defer it
+  // to avoid extraneous computations.
   auto HasSuitableSMP = [&] {
     return hasSuitableConstructorForRelocation(D, /*AllowUserDefined=*/true) &&
            hasSuitableMoveAssignmentOperatorForRelocation(
@@ -7576,18 +7597,22 @@ void Sema::CheckCXX2CReplaceable(CXXRecordDecl *D) {
   };
 
   bool IsReplaceable = [&] {
+    // A class C is a replaceable class if it is eligible for replacement
     if (!isEligibleForReplacement(*this, D))
       return false;
 
+    // has the replaceable_if_eligible class-property-specifier
     if (D->isDependentType() || MarkedCXX2CReplaceable)
       return HasSuitableSMP();
 
+    // is a union with no user-declared special member functions, or
     if (D->isUnion() && !D->hasUserDeclaredCopyConstructor() &&
         !D->hasUserDeclaredCopyAssignment() &&
         !D->hasUserDeclaredMoveOperation() && !D->hasUserDeclaredDestructor()) {
       return HasSuitableSMP();
     }
 
+    // is default-movable.
     return isDefaultMovable(D);
   }();
 

>From 705ff2bb06a064be9c1171c50eb232f8964b10f9 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Mon, 24 Feb 2025 14:10:41 +0100
Subject: [PATCH 14/27] Fix volatile test

---
 clang/lib/Sema/SemaDeclCXX.cpp                     | 2 +-
 clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 56bc1b9f731e1..c914381d62bc0 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -7571,7 +7571,7 @@ static bool isEligibleForReplacement(Sema &SemaRef, CXXRecordDecl *D) {
       continue;
 
     // it has a non-static data member that is not of a replaceable type,
-    if (Field->getType().isReplaceableType(SemaRef.getASTContext()))
+    if (!Field->getType().isReplaceableType(SemaRef.getASTContext()))
       return false;
   }
 
diff --git a/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp b/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp
index 15640bd549372..3b891b075cab0 100644
--- a/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp
+++ b/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp
@@ -230,7 +230,7 @@ struct WithVirtual {
 };
 
 static_assert(__builtin_is_replaceable(S<int>));
-static_assert(__builtin_is_replaceable(S<volatile int>));
+static_assert(!__builtin_is_replaceable(S<volatile int>));
 static_assert(!__builtin_is_replaceable(S<const int>));
 static_assert(!__builtin_is_replaceable(S<const int&>));
 static_assert(!__builtin_is_replaceable(S<int&>));

>From e4016f00d2ccb43f5c5ec0a061f0196957807d43 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Mon, 24 Feb 2025 16:39:47 +0100
Subject: [PATCH 15/27] * Use overload on special members * fix tests (class
 with a const member are not implicitly relocatable) * Factorize some code to
 only compute default-movable once

---
 clang/include/clang/AST/DeclCXX.h             |   7 +
 clang/include/clang/Sema/Sema.h               |   4 +-
 clang/lib/Sema/SemaDeclCXX.cpp                | 268 ++++++++++--------
 .../SemaCXX/cxx2c-trivially-relocatable.cpp   |  49 +++-
 4 files changed, 202 insertions(+), 126 deletions(-)

diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h
index 499d68bc4fcd5..bf14a7750ae35 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -760,6 +760,13 @@ class CXXRecordDecl : public RecordDecl {
     return data().DefaultedMoveAssignmentIsDeleted;
   }
 
+  bool defaultedCopyAssignmentIsDeleted() const {
+    assert((!needsOverloadResolutionForCopyAssignment() ||
+            (data().DeclaredSpecialMembers & SMF_CopyAssignment)) &&
+           "this property has not yet been computed by Sema");
+    return data().DefaultedCopyAssignmentIsDeleted;
+  }
+
   /// \c true if a defaulted destructor for this class would be deleted.
   bool defaultedDestructorIsDeleted() const {
     assert((!needsOverloadResolutionForDestructor() ||
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 66333900d18b1..6113d78528c60 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -4328,9 +4328,7 @@ class Sema final : public SemaBase {
   void ActOnTagFinishDefinition(Scope *S, Decl *TagDecl,
                                 SourceRange BraceRange);
 
-  void CheckCXX2CTriviallyRelocatable(CXXRecordDecl *D);
-
-  void CheckCXX2CReplaceable(CXXRecordDecl *D);
+  void CheckCXX2CRelocatableAndReplaceable(CXXRecordDecl *D);
 
   void ActOnTagFinishSkippedDefinition(SkippedDefinitionContext Context);
 
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index c914381d62bc0..f1e56d18b185b 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -7290,8 +7290,7 @@ void Sema::CheckCompletedCXXClass(Scope *S, CXXRecordDecl *Record) {
   checkClassLevelDLLAttribute(Record);
   checkClassLevelCodeSegAttribute(Record);
 
-  CheckCXX2CTriviallyRelocatable(Record);
-  CheckCXX2CReplaceable(Record);
+  CheckCXX2CRelocatableAndReplaceable(Record);
 
   bool ClangABICompat4 =
       Context.getLangOpts().getClangABICompat() <= LangOptions::ClangABI::Ver4;
@@ -7372,82 +7371,116 @@ void Sema::CheckCompletedCXXClass(Scope *S, CXXRecordDecl *Record) {
   CheckMismatchedTypeAwareAllocators(OO_Array_New, OO_Array_Delete);
 }
 
-static bool hasSuitableConstructorForRelocation(CXXRecordDecl *D,
-                                                bool AllowUserDefined) {
-  assert(D->hasDefinition() && !D->isInvalidDecl());
+static CXXMethodDecl *
+LookupSpecialMemberFromXValue(Sema &SemaRef, CXXRecordDecl *RD, bool Assign) {
+  RD = RD->getDefinition();
+  SourceLocation LookupLoc = RD->getLocation();
+
+  CanQualType CanTy = SemaRef.getASTContext().getCanonicalType(
+      SemaRef.getASTContext().getTagDeclType(RD));
+  DeclarationName Name;
+  Expr *Arg = nullptr;
+  unsigned NumArgs;
+
+  QualType ArgType = CanTy;
+  ExprValueKind VK = clang::VK_XValue;
+
+  if (Assign)
+    Name =
+        SemaRef.getASTContext().DeclarationNames.getCXXOperatorName(OO_Equal);
+  else
+    Name =
+        SemaRef.getASTContext().DeclarationNames.getCXXConstructorName(CanTy);
+
+  OpaqueValueExpr FakeArg(LookupLoc, ArgType, VK);
+  NumArgs = 1;
+  Arg = &FakeArg;
+
+  // Create the object argument
+  QualType ThisTy = CanTy;
+  Expr::Classification Classification =
+      OpaqueValueExpr(LookupLoc, ThisTy, VK_LValue)
+          .Classify(SemaRef.getASTContext());
+
+  // Now we perform lookup on the name we computed earlier and do overload
+  // resolution. Lookup is only performed directly into the class since there
+  // will always be a (possibly implicit) declaration to shadow any others.
+  OverloadCandidateSet OCS(LookupLoc, OverloadCandidateSet::CSK_Normal);
+  DeclContext::lookup_result R = RD->lookup(Name);
+
+  if (R.empty())
+    return nullptr;
 
-  bool HasDeletedMoveConstructor = false;
-  bool HasDeletedCopyConstructor = false;
-  bool HasMoveConstructor = D->needsImplicitMoveConstructor();
-  bool HasCopyConstructor = D->needsImplicitCopyConstructor();
-  bool HasDefaultedMoveConstructor = D->needsImplicitMoveConstructor();
-  bool HasDefaultedCopyConstructor = D->needsImplicitCopyConstructor();
+  // Copy the candidates as our processing of them may load new declarations
+  // from an external source and invalidate lookup_result.
+  SmallVector<NamedDecl *, 8> Candidates(R.begin(), R.end());
 
-  for (const Decl *D : D->decls()) {
-    auto *MD = dyn_cast<CXXConstructorDecl>(D);
-    if (!MD || MD->isIneligibleOrNotSelected())
+  for (NamedDecl *CandDecl : Candidates) {
+    if (CandDecl->isInvalidDecl())
       continue;
 
-    if (MD->isMoveConstructor()) {
-      HasMoveConstructor = true;
-      if (MD->isDefaulted())
-        HasDefaultedMoveConstructor = true;
-      if (MD->isDeleted())
-        HasDeletedMoveConstructor = true;
-    } else if (MD->isCopyConstructor()) {
-      HasCopyConstructor = true;
-      if (MD->isDefaulted())
-        HasDefaultedCopyConstructor = true;
-      if (MD->isDeleted())
-        HasDeletedCopyConstructor = true;
+    DeclAccessPair Cand = DeclAccessPair::make(CandDecl, clang::AS_none);
+    auto CtorInfo = getConstructorInfo(Cand);
+    if (CXXMethodDecl *M = dyn_cast<CXXMethodDecl>(Cand->getUnderlyingDecl())) {
+      if (Assign)
+        SemaRef.AddMethodCandidate(M, Cand, RD, ThisTy, Classification,
+                                   llvm::ArrayRef(&Arg, NumArgs), OCS, true);
+      else {
+        assert(CtorInfo);
+        SemaRef.AddOverloadCandidate(CtorInfo.Constructor, CtorInfo.FoundDecl,
+                                     llvm::ArrayRef(&Arg, NumArgs), OCS,
+                                     /*SuppressUserConversions*/ true);
+      }
+    } else if (FunctionTemplateDecl *Tmpl =
+                   dyn_cast<FunctionTemplateDecl>(Cand->getUnderlyingDecl())) {
+      if (Assign)
+        SemaRef.AddMethodTemplateCandidate(
+            Tmpl, Cand, RD, nullptr, ThisTy, Classification,
+            llvm::ArrayRef(&Arg, NumArgs), OCS, true);
+      else {
+        assert(CtorInfo);
+        SemaRef.AddTemplateOverloadCandidate(
+            CtorInfo.ConstructorTmpl, CtorInfo.FoundDecl, nullptr,
+            llvm::ArrayRef(&Arg, NumArgs), OCS, true);
+      }
     }
   }
 
-  if (HasMoveConstructor)
-    return !HasDeletedMoveConstructor &&
-           (AllowUserDefined ? true : HasDefaultedMoveConstructor);
-  return HasCopyConstructor && !HasDeletedCopyConstructor &&
-         (AllowUserDefined ? true : HasDefaultedCopyConstructor);
+  OverloadCandidateSet::iterator Best;
+  switch (OCS.BestViableFunction(SemaRef, LookupLoc, Best)) {
+  case OR_Success:
+    return cast<CXXMethodDecl>(Best->Function);
+  default:
+    return nullptr;
+  }
 }
 
-static bool
-hasSuitableMoveAssignmentOperatorForRelocation(CXXRecordDecl *D,
-                                               bool AllowUserDefined) {
+static bool hasSuitableConstructorForRelocation(Sema &SemaRef, CXXRecordDecl *D,
+                                                bool AllowUserDefined) {
   assert(D->hasDefinition() && !D->isInvalidDecl());
 
-  if (D->hasExplicitlyDeletedMoveAssignment())
-    return false;
+  if (D->hasSimpleMoveConstructor() || D->hasSimpleCopyConstructor())
+    return true;
 
-  bool HasDeletedMoveAssignment = false;
-  bool HasDeletedCopyAssignment = false;
-  bool HasMoveAssignment = D->needsImplicitMoveAssignment();
-  bool HasDefaultedMoveAssignment = D->needsImplicitMoveAssignment();
-  bool HasDefaultedCopyAssignment = D->needsImplicitCopyAssignment();
+  CXXMethodDecl *Decl =
+      LookupSpecialMemberFromXValue(SemaRef, D, /*Assign=*/false);
+  return Decl && Decl->isUserProvided() == AllowUserDefined;
+}
 
-  for (const Decl *D : D->decls()) {
-    auto *MD = dyn_cast<CXXMethodDecl>(D);
-    if (!MD || MD->isIneligibleOrNotSelected())
-      continue;
+static bool
+hasSuitableMoveAssignmentOperatorForRelocation(Sema &SemaRef, CXXRecordDecl *D,
+                                               bool AllowUserDefined) {
+  assert(D->hasDefinition() && !D->isInvalidDecl());
 
-    if (MD->isMoveAssignmentOperator()) {
-      HasMoveAssignment = true;
-      if (MD->isDefaulted())
-        HasDefaultedMoveAssignment = true;
-      if (MD->isDeleted())
-        HasDeletedMoveAssignment = true;
-    } else if (MD->isCopyAssignmentOperator()) {
-      if (MD->isDefaulted())
-        HasDefaultedCopyAssignment = true;
-      if (MD->isDeleted())
-        HasDeletedCopyAssignment = true;
-    }
-  }
+  if (D->hasSimpleMoveAssignment() || D->hasSimpleCopyAssignment())
+    return true;
 
-  if (HasMoveAssignment)
-    return !HasDeletedMoveAssignment &&
-           (AllowUserDefined ? true : HasDefaultedMoveAssignment);
-  return !HasDeletedCopyAssignment &&
-         (AllowUserDefined ? true : HasDefaultedCopyAssignment);
+  CXXMethodDecl *Decl =
+      LookupSpecialMemberFromXValue(SemaRef, D, /*Assign=*/true);
+  if (!Decl)
+    return false;
+
+  return Decl && Decl->isUserProvided() == AllowUserDefined;
 }
 
 // [C++26][class.prop]
@@ -7459,12 +7492,13 @@ hasSuitableMoveAssignmentOperatorForRelocation(CXXRecordDecl *D,
 // type C selects an assignment operator function that is a direct member of C
 // and is neither user-provided nor deleted, and C has a destructor that is
 // neither user-provided nor deleted.
-static bool isDefaultMovable(CXXRecordDecl *D) {
-  if (!hasSuitableConstructorForRelocation(D, /*AllowUserDefined=*/false))
+static bool isDefaultMovable(Sema &SemaRef, CXXRecordDecl *D) {
+  if (!hasSuitableConstructorForRelocation(SemaRef, D,
+                                           /*AllowUserDefined=*/false))
     return false;
 
   if (!hasSuitableMoveAssignmentOperatorForRelocation(
-          D, /*AllowUserDefined=*/false))
+          SemaRef, D, /*AllowUserDefined=*/false))
     return false;
 
   const auto *Dtr = D->getDestructor();
@@ -7506,53 +7540,15 @@ static bool isEligibleForTrivialRelocation(Sema &SemaRef, CXXRecordDecl *D) {
       continue;
     // ... has a non-static data member of an object type that is not
     // of a trivially relocatable type
-    QualType T = SemaRef.getASTContext().getBaseElementType(
-        Field->getType().getUnqualifiedType());
-    if (CXXRecordDecl *RD = T->getAsCXXRecordDecl()) {
-      if (!RD->isTriviallyRelocatable())
-        return false;
-    }
+    if (!Field->getType().isCppTriviallyRelocatableType(
+            SemaRef.getASTContext()))
+      return false;
   }
 
   // ...has a deleted destructor
   return !hasDeletedDestructor(D);
 }
 
-// [C++26][class.prop]
-// A class C is a trivially relocatable class if
-void Sema::CheckCXX2CTriviallyRelocatable(CXXRecordDecl *D) {
-  if (!getLangOpts().CPlusPlus || D->isInvalidDecl())
-    return;
-
-  assert(D->hasDefinition());
-
-  bool MarkedTriviallyRelocatable =
-      D->getTriviallyRelocatableSpecifier().isSet();
-
-  bool IsTriviallyRelocatable = [&] {
-    // if it is eligible for trivial relocation
-
-    if (!isEligibleForTrivialRelocation(*this, D))
-      return false;
-
-    // has the trivially_relocatable_if_eligible class-property-specifier,
-    if (D->isDependentType() || MarkedTriviallyRelocatable)
-      return true;
-
-    // is a union with no user-declared special member functions, or
-    if (D->isUnion() && !D->hasUserDeclaredCopyConstructor() &&
-        !D->hasUserDeclaredCopyAssignment() &&
-        !D->hasUserDeclaredMoveOperation() && !D->hasUserDeclaredDestructor()) {
-      return true;
-    }
-
-    // is default-movable.
-    return isDefaultMovable(D);
-  }();
-
-  D->setIsTriviallyRelocatable(IsTriviallyRelocatable);
-}
-
 // [C++26][class.prop]
 // A class C is eligible for replacement unless
 static bool isEligibleForReplacement(Sema &SemaRef, CXXRecordDecl *D) {
@@ -7579,41 +7575,81 @@ static bool isEligibleForReplacement(Sema &SemaRef, CXXRecordDecl *D) {
   return !hasDeletedDestructor(D);
 }
 
-// [C++26][class.prop] A class C is a replaceable class if...
-void Sema::CheckCXX2CReplaceable(CXXRecordDecl *D) {
+void Sema::CheckCXX2CRelocatableAndReplaceable(CXXRecordDecl *D) {
   if (!getLangOpts().CPlusPlus || D->isInvalidDecl())
     return;
 
   assert(D->hasDefinition());
 
   bool MarkedCXX2CReplaceable = D->getReplaceableSpecifier().isSet();
+  bool MarkedTriviallyRelocatable =
+      D->getTriviallyRelocatableSpecifier().isSet();
 
   // This is part of "eligible for replacement", however we defer it
   // to avoid extraneous computations.
   auto HasSuitableSMP = [&] {
-    return hasSuitableConstructorForRelocation(D, /*AllowUserDefined=*/true) &&
+    return hasSuitableConstructorForRelocation(*this, D,
+                                               /*AllowUserDefined=*/true) &&
            hasSuitableMoveAssignmentOperatorForRelocation(
-               D, /*AllowUserDefined=*/true);
+               *this, D, /*AllowUserDefined=*/true);
+  };
+
+  auto IsUnion = [&, Is = std::optional<bool>{}]() mutable {
+    if (!Is.has_value())
+      Is = D->isUnion() && !D->hasUserDeclaredCopyConstructor() &&
+           !D->hasUserDeclaredCopyAssignment() &&
+           !D->hasUserDeclaredMoveOperation() &&
+           !D->hasUserDeclaredDestructor();
+    return *Is;
   };
 
+  auto IsDefaultMovable = [&, Is = std::optional<bool>{}]() mutable {
+    if (!Is.has_value())
+      Is = isDefaultMovable(*this, D);
+    return *Is;
+  };
+
+  bool IsTriviallyRelocatable = [&] {
+    if (D->isDependentType())
+      return false;
+
+    // if it is eligible for trivial relocation
+    if (!isEligibleForTrivialRelocation(*this, D))
+      return false;
+
+    // has the trivially_relocatable_if_eligible class-property-specifier,
+    if (MarkedTriviallyRelocatable)
+      return true;
+
+    // is a union with no user-declared special member functions, or
+    if (IsUnion()) {
+      return true;
+    }
+    // is default-movable.
+    return IsDefaultMovable();
+  }();
+
+  D->setIsTriviallyRelocatable(IsTriviallyRelocatable);
+
   bool IsReplaceable = [&] {
+    if (D->isDependentType())
+      return false;
+
     // A class C is a replaceable class if it is eligible for replacement
     if (!isEligibleForReplacement(*this, D))
       return false;
 
     // has the replaceable_if_eligible class-property-specifier
-    if (D->isDependentType() || MarkedCXX2CReplaceable)
+    if (MarkedCXX2CReplaceable)
       return HasSuitableSMP();
 
     // is a union with no user-declared special member functions, or
-    if (D->isUnion() && !D->hasUserDeclaredCopyConstructor() &&
-        !D->hasUserDeclaredCopyAssignment() &&
-        !D->hasUserDeclaredMoveOperation() && !D->hasUserDeclaredDestructor()) {
+    if (IsUnion()) {
       return HasSuitableSMP();
     }
 
     // is default-movable.
-    return isDefaultMovable(D);
+    return IsDefaultMovable();
   }();
 
   D->setIsReplaceable(IsReplaceable);
diff --git a/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp b/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp
index 3b891b075cab0..e744a3ef20ad0 100644
--- a/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp
+++ b/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp
@@ -73,7 +73,12 @@ static_assert(__builtin_is_cpp_trivially_relocatable(Trivial[]));
 struct WithConst {
     const int i;
 };
-static_assert(__builtin_is_cpp_trivially_relocatable(WithConst));
+static_assert(!__builtin_is_cpp_trivially_relocatable(WithConst));
+
+struct WithConstExplicit trivially_relocatable_if_eligible {
+    const int i;
+};
+static_assert(__builtin_is_cpp_trivially_relocatable(WithConstExplicit));
 
 struct UserDtr {
     ~UserDtr();
@@ -110,7 +115,7 @@ struct UserCopyDefault{
 
 
 struct UserDeletedMove{
-    UserDeletedMove(UserDeletedMove&) = delete;
+    UserDeletedMove(UserDeletedMove&&) = delete;
     UserDeletedMove(const UserDeletedMove&) = default;
 };
 
@@ -156,6 +161,34 @@ static_assert(!__is_trivially_copyable(U));
 static_assert(__builtin_is_cpp_trivially_relocatable(U));
 
 
+template <typename T>
+struct S {
+    T t;
+};
+static_assert(__builtin_is_cpp_trivially_relocatable(S<int>));
+static_assert(__builtin_is_cpp_trivially_relocatable(S<volatile int>));
+static_assert(!__builtin_is_cpp_trivially_relocatable(S<const int>));
+static_assert(!__builtin_is_cpp_trivially_relocatable(S<const int&>));
+static_assert(!__builtin_is_cpp_trivially_relocatable(S<int&>));
+static_assert(__builtin_is_cpp_trivially_relocatable(S<int[2]>));
+static_assert(!__builtin_is_cpp_trivially_relocatable(S<const int[2]>));
+static_assert(__builtin_is_cpp_trivially_relocatable(S<int[]>));
+
+
+template <typename T>
+struct SExplicit trivially_relocatable_if_eligible{
+    T t;
+};
+static_assert(__builtin_is_cpp_trivially_relocatable(SExplicit<int>));
+static_assert(__builtin_is_cpp_trivially_relocatable(SExplicit<volatile int>));
+static_assert(__builtin_is_cpp_trivially_relocatable(SExplicit<const int>));
+static_assert(__builtin_is_cpp_trivially_relocatable(SExplicit<const int&>));
+static_assert(__builtin_is_cpp_trivially_relocatable(SExplicit<int&>));
+static_assert(__builtin_is_cpp_trivially_relocatable(SExplicit<int[2]>));
+static_assert(__builtin_is_cpp_trivially_relocatable(SExplicit<const int[2]>));
+static_assert(__builtin_is_cpp_trivially_relocatable(SExplicit<int[]>));
+
+
 namespace replaceable {
 
 struct DeletedMove {
@@ -210,12 +243,14 @@ static_assert(!__builtin_is_replaceable(UserProvidedMove));
 static_assert(!__builtin_is_replaceable(UserProvidedCopy));
 static_assert(!__builtin_is_replaceable(UserProvidedMoveAssign));
 
-using NotReplaceable = DeletedMove;
-
-template <typename T>
-struct S {
-    T t;
+struct DeletedCopyTpl {
+    template <typename U>
+    DeletedCopyTpl(const U&) = delete;
 };
+static_assert(__builtin_is_replaceable(DeletedCopyTpl));
+
+
+using NotReplaceable = DeletedMove;
 
 template <typename T>
 struct WithBase : T{};

>From 9b9043ab7d6a0e78ade89e1901cf9dc7b015c698 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Mon, 24 Feb 2025 16:58:55 +0100
Subject: [PATCH 16/27] Make hasDeletedDestructor a member

---
 clang/include/clang/AST/DeclCXX.h |  3 +++
 clang/lib/AST/DeclCXX.cpp         |  6 ++++++
 clang/lib/Sema/SemaDeclCXX.cpp    | 18 +++++-------------
 3 files changed, 14 insertions(+), 13 deletions(-)

diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h
index bf14a7750ae35..5f9d454d64e9d 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -1625,6 +1625,9 @@ class CXXRecordDecl : public RecordDecl {
   /// Returns the destructor decl for this class.
   CXXDestructorDecl *getDestructor() const;
 
+  /// Returns the destructor decl for this class.
+  bool hasDeletedDestructor() const;
+
   /// Returns true if the class destructor, or any implicitly invoked
   /// destructors are marked noreturn.
   bool isAnyDestructorNoReturn() const { return data().IsAnyDestructorNoReturn; }
diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp
index 4c3d9a01466aa..eb87928b2d865 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -2156,6 +2156,12 @@ CXXDestructorDecl *CXXRecordDecl::getDestructor() const {
   return nullptr;
 }
 
+bool CXXRecordDecl::hasDeletedDestructor() const {
+  if (const CXXDestructorDecl *D = getDestructor())
+    return D->isDeleted();
+  return false;
+}
+
 static bool isDeclContextInNamespace(const DeclContext *DC) {
   while (!DC->isTranslationUnit()) {
     if (DC->isNamespace())
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index f1e56d18b185b..97f36a1fe6287 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -7511,13 +7511,6 @@ static bool isDefaultMovable(Sema &SemaRef, CXXRecordDecl *D) {
   return !Dtr->isDeleted();
 }
 
-static bool hasDeletedDestructor(CXXRecordDecl *D) {
-  const auto *Dtr = D->getDestructor();
-  if (Dtr)
-    return Dtr->isDeleted();
-  return false;
-}
-
 // [C++26][class.prop]
 // A class is eligible for trivial relocation unless it...
 static bool isEligibleForTrivialRelocation(Sema &SemaRef, CXXRecordDecl *D) {
@@ -7546,7 +7539,7 @@ static bool isEligibleForTrivialRelocation(Sema &SemaRef, CXXRecordDecl *D) {
   }
 
   // ...has a deleted destructor
-  return !hasDeletedDestructor(D);
+  return !D->hasDeletedDestructor();
 }
 
 // [C++26][class.prop]
@@ -7572,7 +7565,7 @@ static bool isEligibleForReplacement(Sema &SemaRef, CXXRecordDecl *D) {
   }
 
   // it has a deleted destructor.
-  return !hasDeletedDestructor(D);
+  return !D->hasDeletedDestructor();
 }
 
 void Sema::CheckCXX2CRelocatableAndReplaceable(CXXRecordDecl *D) {
@@ -7622,9 +7615,9 @@ void Sema::CheckCXX2CRelocatableAndReplaceable(CXXRecordDecl *D) {
       return true;
 
     // is a union with no user-declared special member functions, or
-    if (IsUnion()) {
+    if (IsUnion())
       return true;
-    }
+
     // is default-movable.
     return IsDefaultMovable();
   }();
@@ -7644,9 +7637,8 @@ void Sema::CheckCXX2CRelocatableAndReplaceable(CXXRecordDecl *D) {
       return HasSuitableSMP();
 
     // is a union with no user-declared special member functions, or
-    if (IsUnion()) {
+    if (IsUnion())
       return HasSuitableSMP();
-    }
 
     // is default-movable.
     return IsDefaultMovable();

>From 2b4df98f7b16a1b465ffe120825e10798b0a4db1 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Mon, 24 Feb 2025 17:15:22 +0100
Subject: [PATCH 17/27] __is_trivially_relocatable is true for c++26
 relocatable types

---
 clang/docs/LanguageExtensions.rst |  7 ++++---
 clang/lib/AST/Type.cpp            | 10 ++++++++--
 2 files changed, 12 insertions(+), 5 deletions(-)

diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index 2d2c5e383780e..ebcad44197ce4 100644
--- a/clang/docs/LanguageExtensions.rst
+++ b/clang/docs/LanguageExtensions.rst
@@ -1862,12 +1862,13 @@ The following type trait primitives are supported by Clang. Those traits marked
 * ``__is_trivially_relocatable`` (Clang): Returns true if moving an object
   of the given type, and then destroying the source object, is known to be
   functionally equivalent to copying the underlying bytes and then dropping the
-  source object on the floor. This is true of trivial types and types which
+  source object on the floor. This is true of trivial types,
+  C++26 relocatable types, and types which
   were made trivially relocatable via the ``clang::trivial_abi`` attribute.
 * ``__builtin_is_cpp_trivially_relocatable`` (C++): Returns true if an object
   is trivially relocatable, as defined by the C++26 standard [meta.unary.prop].
-  Note that the caller code should ensure that if the object is polymorphic,
-  the dynamic type is of the most derived type.
+  Note that when relocating the caller code should ensure that if the object is polymorphic,
+  the dynamic type is of the most derived type. Padding bytes should not be copied.
 * ``__builtin_is_replaceable`` (C++): Returns true if an object
   is replaceable, as defined by the C++26 standard [meta.unary.prop].
 * ``__is_trivially_equality_comparable`` (Clang): Returns true if comparing two
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 7bd9565f093ec..fc749b991f01f 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -2905,7 +2905,7 @@ bool QualType::isTriviallyRelocatableType(const ASTContext &Context) const {
     return false;
   } else if (const auto *RD = BaseElementType->getAsRecordDecl()) {
     return RD->canPassInRegisters();
-  } else if (BaseElementType.isTriviallyCopyableType(Context)) {
+  } else if (BaseElementType.isCppTriviallyRelocatableType(Context)) {
     return true;
   } else {
     switch (isNonTrivialToPrimitiveDestructiveMove()) {
@@ -2921,10 +2921,16 @@ bool QualType::isTriviallyRelocatableType(const ASTContext &Context) const {
 
 bool QualType::isCppTriviallyRelocatableType(const ASTContext &Context) const {
   QualType BaseElementType = Context.getBaseElementType(*this);
+
+  if (hasNonTrivialObjCLifetime())
+    return false;
+
   if (BaseElementType->isIncompleteType())
     return false;
-  if (BaseElementType->isScalarType())
+
+  if (BaseElementType->isScalarType() || BaseElementType->isVectorType())
     return true;
+
   if (const auto *RD = BaseElementType->getAsCXXRecordDecl())
     return RD->isTriviallyRelocatable();
   return false;

>From 5a3ef1e9962357f767cd6464fded2698c7ca3d03 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Mon, 24 Feb 2025 18:23:43 +0100
Subject: [PATCH 18/27] Remove the specifier wrapper, stash the location in
 attributes

---
 clang/include/clang/AST/DeclCXX.h          | 46 ----------------------
 clang/include/clang/Basic/Attr.td          | 16 ++++++++
 clang/include/clang/Parse/Parser.h         |  5 +--
 clang/include/clang/Sema/Sema.h            | 17 ++++----
 clang/lib/Parse/ParseDeclCXX.cpp           | 24 ++++++-----
 clang/lib/Sema/SemaDecl.cpp                | 22 ++++-------
 clang/lib/Sema/SemaDeclCXX.cpp             |  5 +--
 clang/lib/Sema/SemaTemplateInstantiate.cpp |  4 --
 8 files changed, 45 insertions(+), 94 deletions(-)

diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h
index 5f9d454d64e9d..a6043c28b5713 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -127,33 +127,6 @@ class AccessSpecDecl : public Decl {
   static bool classofKind(Kind K) { return K == AccessSpec; }
 };
 
-enum class RelocatableOrReplaceableClassSpecifierKind {
-  Relocatable,
-  Replaceable
-};
-
-template <RelocatableOrReplaceableClassSpecifierKind MK>
-class BasicRelocatableOrReplaceableClassSpecifier {
-public:
-  BasicRelocatableOrReplaceableClassSpecifier() = default;
-  BasicRelocatableOrReplaceableClassSpecifier(SourceLocation Begin)
-      : Loc(Begin) {}
-  void Set(SourceLocation Begin) { Loc = Begin; }
-
-  bool isSet() const { return !Loc.isInvalid(); }
-
-  SourceLocation getLocation() const { return Loc; }
-
-private:
-  SourceLocation Loc;
-};
-
-using TriviallyRelocatableSpecifier =
-    BasicRelocatableOrReplaceableClassSpecifier<
-        RelocatableOrReplaceableClassSpecifierKind::Relocatable>;
-using ReplaceableSpecifier = BasicRelocatableOrReplaceableClassSpecifier<
-    RelocatableOrReplaceableClassSpecifierKind::Replaceable>;
-
 /// Represents a base class of a C++ class.
 ///
 /// Each CXXBaseSpecifier represents a single, direct base class (or
@@ -376,10 +349,6 @@ class CXXRecordDecl : public RecordDecl {
     /// This is actually currently stored in reverse order.
     LazyDeclPtr FirstFriend;
 
-    TriviallyRelocatableSpecifier TriviallyRelocatableSpecifier;
-
-    ReplaceableSpecifier ReplaceableSpecifier;
-
     DefinitionData(CXXRecordDecl *D);
 
     /// Retrieve the set of direct base classes.
@@ -1528,14 +1497,6 @@ class CXXRecordDecl : public RecordDecl {
     return isLiteral() && data().StructuralIfLiteral;
   }
 
-  TriviallyRelocatableSpecifier getTriviallyRelocatableSpecifier() const {
-    return data().TriviallyRelocatableSpecifier;
-  }
-
-  ReplaceableSpecifier getReplaceableSpecifier() const {
-    return data().ReplaceableSpecifier;
-  }
-
   bool isTriviallyRelocatable() const { return data().IsTriviallyRelocatable; }
 
   void setIsTriviallyRelocatable(bool Set) {
@@ -1975,13 +1936,6 @@ class CXXRecordDecl : public RecordDecl {
     return K >= firstCXXRecord && K <= lastCXXRecord;
   }
   void markAbstract() { data().Abstract = true; }
-
-  void setTriviallyRelocatableSpecifier(TriviallyRelocatableSpecifier TRS) {
-    data().TriviallyRelocatableSpecifier = TRS;
-  }
-  void setReplaceableSpecifier(ReplaceableSpecifier MRS) {
-    data().ReplaceableSpecifier = MRS;
-  }
 };
 
 /// Store information needed for an explicit specifier.
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index a734eb6658c3d..df7bba094fce6 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -1819,6 +1819,22 @@ def Final : InheritableAttr {
   let Documentation = [InternalOnly];
 }
 
+def TriviallyRelocatable : InheritableAttr {
+  let Spellings = [CustomKeyword<"trivially_relocatable_if_eligible">];
+  let SemaHandler = 0;
+  // Omitted from docs, since this is language syntax, not an attribute, as far
+  // as users are concerned.
+  let Documentation = [InternalOnly];
+}
+
+def Replaceable : InheritableAttr {
+  let Spellings = [CustomKeyword<"replaceable_if_eligible">];
+  let SemaHandler = 0;
+  // Omitted from docs, since this is language syntax, not an attribute, as far
+  // as users are concerned.
+  let Documentation = [InternalOnly];
+}
+
 def MinSize : InheritableAttr {
   let Spellings = [Clang<"minsize">];
   let Subjects = SubjectList<[Function, ObjCMethod], ErrorDiag>;
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 2fbe60764636c..e0b8850493b49 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -3201,12 +3201,11 @@ class Parser : public CodeCompletionHandler {
 
   bool isCXX2CTriviallyRelocatableKeyword(Token Tok) const;
   bool isCXX2CTriviallyRelocatableKeyword() const;
-  void
-  ParseCXX2CTriviallyRelocatableSpecifier(TriviallyRelocatableSpecifier &TRS);
+  void ParseCXX2CTriviallyRelocatableSpecifier(SourceLocation &TRS);
 
   bool isCXX2CReplaceableKeyword(Token Tok) const;
   bool isCXX2CReplaceableKeyword() const;
-  void ParseCXX2CReplaceableSpecifier(ReplaceableSpecifier &MRS);
+  void ParseCXX2CReplaceableSpecifier(SourceLocation &MRS);
 
   bool isClassCompatibleKeyword(Token Tok) const;
   bool isClassCompatibleKeyword() const;
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 6113d78528c60..965da321c6604 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -4309,19 +4309,16 @@ class Sema final : public SemaBase {
   /// Invoked when we enter a tag definition that we're skipping.
   SkippedDefinitionContext ActOnTagStartSkippedDefinition(Scope *S, Decl *TD);
 
-  TriviallyRelocatableSpecifier
-  ActOnTriviallyRelocatableSpecifier(SourceLocation Loc);
-
-  ReplaceableSpecifier ActOnReplaceableSpecifier(SourceLocation Loc);
-
   /// ActOnStartCXXMemberDeclarations - Invoked when we have parsed a
   /// C++ record definition's base-specifiers clause and are starting its
   /// member declarations.
-  void ActOnStartCXXMemberDeclarations(
-      Scope *S, Decl *TagDecl, SourceLocation FinalLoc,
-      bool IsFinalSpelledSealed, bool IsAbstract,
-      TriviallyRelocatableSpecifier TriviallyRelocatable,
-      ReplaceableSpecifier Replaceable, SourceLocation LBraceLoc);
+  void ActOnStartCXXMemberDeclarations(Scope *S, Decl *TagDecl,
+                                       SourceLocation FinalLoc,
+                                       bool IsFinalSpelledSealed,
+                                       bool IsAbstract,
+                                       SourceLocation TriviallyRelocatable,
+                                       SourceLocation Replaceable,
+                                       SourceLocation LBraceLoc);
 
   /// ActOnTagFinishDefinition - Invoked once we have finished parsing
   /// the definition of a tag (enumeration, class, struct, or union).
diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp
index 287ee8da62556..53f6a1d36565c 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -2711,8 +2711,7 @@ bool Parser::isCXX2CTriviallyRelocatableKeyword() const {
   return isCXX2CTriviallyRelocatableKeyword(Tok);
 }
 
-void Parser::ParseCXX2CTriviallyRelocatableSpecifier(
-    TriviallyRelocatableSpecifier &TRS) {
+void Parser::ParseCXX2CTriviallyRelocatableSpecifier(SourceLocation &TRS) {
   assert(isCXX2CTriviallyRelocatableKeyword() &&
          "expected a trivially_relocatable specifier");
 
@@ -2721,7 +2720,7 @@ void Parser::ParseCXX2CTriviallyRelocatableSpecifier(
                               : diag::ext_relocatable_keyword)
       << /*relocatable*/ 0;
 
-  TRS = Actions.ActOnTriviallyRelocatableSpecifier(ConsumeToken());
+  TRS = ConsumeToken();
 }
 
 bool Parser::isCXX2CReplaceableKeyword(Token Tok) const {
@@ -2738,7 +2737,7 @@ bool Parser::isCXX2CReplaceableKeyword() const {
   return isCXX2CReplaceableKeyword(Tok);
 }
 
-void Parser::ParseCXX2CReplaceableSpecifier(ReplaceableSpecifier &MRS) {
+void Parser::ParseCXX2CReplaceableSpecifier(SourceLocation &MRS) {
   assert(isCXX2CReplaceableKeyword() &&
          "expected a replaceable_if_eligible specifier");
 
@@ -2747,7 +2746,7 @@ void Parser::ParseCXX2CReplaceableSpecifier(ReplaceableSpecifier &MRS) {
                               : diag::ext_relocatable_keyword)
       << /*replaceable*/ 1;
 
-  MRS = Actions.ActOnReplaceableSpecifier(ConsumeToken());
+  MRS = ConsumeToken();
 }
 
 /// isClassCompatibleKeyword - Determine whether the next token is a C++11
@@ -3872,8 +3871,8 @@ void Parser::ParseCXXMemberSpecification(SourceLocation RecordLoc,
   SourceLocation AbstractLoc;
   bool IsFinalSpelledSealed = false;
   bool IsAbstract = false;
-  TriviallyRelocatableSpecifier TriviallyRelocatable;
-  ReplaceableSpecifier Replacable;
+  SourceLocation TriviallyRelocatable;
+  SourceLocation Replacable;
 
   // Parse the optional 'final' keyword.
   if (getLangOpts().CPlusPlus && Tok.is(tok::identifier)) {
@@ -3881,22 +3880,21 @@ void Parser::ParseCXXMemberSpecification(SourceLocation RecordLoc,
       VirtSpecifiers::Specifier Specifier = isCXX11VirtSpecifier(Tok);
       if (Specifier == VirtSpecifiers::VS_None) {
         if (isCXX2CTriviallyRelocatableKeyword(Tok)) {
-          if (TriviallyRelocatable.isSet()) {
+          if (TriviallyRelocatable.isValid()) {
             auto Skipped = Tok;
             ConsumeToken();
             Diag(Skipped, diag::err_duplicate_class_relocation_specifier)
-                << /*trivial_relocatable*/ 0
-                << TriviallyRelocatable.getLocation();
+                << /*trivial_relocatable*/ 0 << TriviallyRelocatable;
           } else {
             ParseCXX2CTriviallyRelocatableSpecifier(TriviallyRelocatable);
           }
           continue;
         } else if (isCXX2CReplaceableKeyword(Tok)) {
-          if (Replacable.isSet()) {
+          if (Replacable.isValid()) {
             auto Skipped = Tok;
             ConsumeToken();
             Diag(Skipped, diag::err_duplicate_class_relocation_specifier)
-                << /*replaceable*/ 1 << Replacable.getLocation();
+                << /*replaceable*/ 1 << Replacable;
           } else {
             ParseCXX2CReplaceableSpecifier(Replacable);
           }
@@ -3941,7 +3939,7 @@ void Parser::ParseCXXMemberSpecification(SourceLocation RecordLoc,
         Diag(FinalLoc, diag::ext_warn_gnu_final);
     }
     assert((FinalLoc.isValid() || AbstractLoc.isValid() ||
-            TriviallyRelocatable.isSet() || Replacable.isSet()) &&
+            TriviallyRelocatable.isValid() || Replacable.isValid()) &&
            "not a class definition");
 
     // Parse any C++11 attributes after 'final' keyword.
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index f84ccc3181474..6b561d7bfc6e7 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -18480,19 +18480,10 @@ bool Sema::ActOnDuplicateDefinition(Scope *S, Decl *Prev,
   return true;
 }
 
-TriviallyRelocatableSpecifier
-Sema::ActOnTriviallyRelocatableSpecifier(SourceLocation Loc) {
-  return {Loc};
-}
-
-ReplaceableSpecifier Sema::ActOnReplaceableSpecifier(SourceLocation Loc) {
-  return {Loc};
-}
-
 void Sema::ActOnStartCXXMemberDeclarations(
     Scope *S, Decl *TagD, SourceLocation FinalLoc, bool IsFinalSpelledSealed,
-    bool IsAbstract, TriviallyRelocatableSpecifier TriviallyRelocatable,
-    ReplaceableSpecifier Replaceable, SourceLocation LBraceLoc) {
+    bool IsAbstract, SourceLocation TriviallyRelocatable,
+    SourceLocation Replaceable, SourceLocation LBraceLoc) {
   AdjustDeclIfTemplate(TagD);
   CXXRecordDecl *Record = cast<CXXRecordDecl>(TagD);
 
@@ -18511,11 +18502,12 @@ void Sema::ActOnStartCXXMemberDeclarations(
                                           : FinalAttr::Keyword_final));
   }
 
-  if (TriviallyRelocatable.isSet() && !Record->isInvalidDecl())
-    Record->setTriviallyRelocatableSpecifier(TriviallyRelocatable);
+  if (TriviallyRelocatable.isValid())
+    Record->addAttr(
+        TriviallyRelocatableAttr::Create(Context, TriviallyRelocatable));
 
-  if (Replaceable.isSet() && !Record->isInvalidDecl())
-    Record->setReplaceableSpecifier(Replaceable);
+  if (Replaceable.isValid())
+    Record->addAttr(ReplaceableAttr::Create(Context, Replaceable));
 
   // C++ [class]p2:
   //   [...] The class-name is also inserted into the scope of the
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 97f36a1fe6287..3bf4f717396b9 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -7574,9 +7574,8 @@ void Sema::CheckCXX2CRelocatableAndReplaceable(CXXRecordDecl *D) {
 
   assert(D->hasDefinition());
 
-  bool MarkedCXX2CReplaceable = D->getReplaceableSpecifier().isSet();
-  bool MarkedTriviallyRelocatable =
-      D->getTriviallyRelocatableSpecifier().isSet();
+  bool MarkedCXX2CReplaceable = D->hasAttr<ReplaceableAttr>();
+  bool MarkedTriviallyRelocatable = D->hasAttr<TriviallyRelocatableAttr>();
 
   // This is part of "eligible for replacement", however we defer it
   // to avoid extraneous computations.
diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp
index 4d6bec48f76c3..fb490bcac6e91 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -3581,10 +3581,6 @@ Sema::InstantiateClass(SourceLocation PointOfInstantiation,
   // Start the definition of this instantiation.
   Instantiation->startDefinition();
 
-  Instantiation->setTriviallyRelocatableSpecifier(
-      Pattern->getTriviallyRelocatableSpecifier());
-  Instantiation->setReplaceableSpecifier(Pattern->getReplaceableSpecifier());
-
   // The instantiation is visible here, even if it was first declared in an
   // unimported module.
   Instantiation->setVisibleDespiteOwningModule();

>From 14302d9274651cb4e62743e08d375db32f677fcd Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Tue, 25 Feb 2025 10:37:00 +0100
Subject: [PATCH 19/27] reduce destructor lookups

---
 clang/lib/Sema/SemaDeclCXX.cpp | 26 +++++++++++++++++---------
 1 file changed, 17 insertions(+), 9 deletions(-)

diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 3bf4f717396b9..744d2c88962da 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -7492,7 +7492,7 @@ hasSuitableMoveAssignmentOperatorForRelocation(Sema &SemaRef, CXXRecordDecl *D,
 // type C selects an assignment operator function that is a direct member of C
 // and is neither user-provided nor deleted, and C has a destructor that is
 // neither user-provided nor deleted.
-static bool isDefaultMovable(Sema &SemaRef, CXXRecordDecl *D) {
+static bool isDefaultMovable(Sema &SemaRef, CXXRecordDecl *D, CXXDestructorDecl* Dtr) {
   if (!hasSuitableConstructorForRelocation(SemaRef, D,
                                            /*AllowUserDefined=*/false))
     return false;
@@ -7501,7 +7501,6 @@ static bool isDefaultMovable(Sema &SemaRef, CXXRecordDecl *D) {
           SemaRef, D, /*AllowUserDefined=*/false))
     return false;
 
-  const auto *Dtr = D->getDestructor();
   if (!Dtr)
     return true;
 
@@ -7537,9 +7536,7 @@ static bool isEligibleForTrivialRelocation(Sema &SemaRef, CXXRecordDecl *D) {
             SemaRef.getASTContext()))
       return false;
   }
-
-  // ...has a deleted destructor
-  return !D->hasDeletedDestructor();
+  return true;
 }
 
 // [C++26][class.prop]
@@ -7563,9 +7560,7 @@ static bool isEligibleForReplacement(Sema &SemaRef, CXXRecordDecl *D) {
     if (!Field->getType().isReplaceableType(SemaRef.getASTContext()))
       return false;
   }
-
-  // it has a deleted destructor.
-  return !D->hasDeletedDestructor();
+  return true;
 }
 
 void Sema::CheckCXX2CRelocatableAndReplaceable(CXXRecordDecl *D) {
@@ -7577,6 +7572,7 @@ void Sema::CheckCXX2CRelocatableAndReplaceable(CXXRecordDecl *D) {
   bool MarkedCXX2CReplaceable = D->hasAttr<ReplaceableAttr>();
   bool MarkedTriviallyRelocatable = D->hasAttr<TriviallyRelocatableAttr>();
 
+
   // This is part of "eligible for replacement", however we defer it
   // to avoid extraneous computations.
   auto HasSuitableSMP = [&] {
@@ -7595,9 +7591,15 @@ void Sema::CheckCXX2CRelocatableAndReplaceable(CXXRecordDecl *D) {
     return *Is;
   };
 
+  auto GetDestructor = [&, Dtr = static_cast<CXXDestructorDecl*>(nullptr)]() mutable {
+      if(!Dtr)
+          Dtr = D->getDestructor();
+      return Dtr;
+  };
+
   auto IsDefaultMovable = [&, Is = std::optional<bool>{}]() mutable {
     if (!Is.has_value())
-      Is = isDefaultMovable(*this, D);
+      Is = isDefaultMovable(*this, D, GetDestructor());
     return *Is;
   };
 
@@ -7609,6 +7611,9 @@ void Sema::CheckCXX2CRelocatableAndReplaceable(CXXRecordDecl *D) {
     if (!isEligibleForTrivialRelocation(*this, D))
       return false;
 
+    if(auto* Dtr = GetDestructor(); Dtr && Dtr->isDeleted())
+        return false;
+
     // has the trivially_relocatable_if_eligible class-property-specifier,
     if (MarkedTriviallyRelocatable)
       return true;
@@ -7631,6 +7636,9 @@ void Sema::CheckCXX2CRelocatableAndReplaceable(CXXRecordDecl *D) {
     if (!isEligibleForReplacement(*this, D))
       return false;
 
+    if(auto* Dtr = GetDestructor(); Dtr && Dtr->isDeleted())
+        return false;
+
     // has the replaceable_if_eligible class-property-specifier
     if (MarkedCXX2CReplaceable)
       return HasSuitableSMP();

>From b623ed7dcaf16f4ddfb2e811c601a3af95034848 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Tue, 25 Feb 2025 11:10:48 +0100
Subject: [PATCH 20/27] cleanup

---
 clang/include/clang/AST/Type.h   |  2 +-
 clang/lib/AST/Type.cpp           |  4 ++--
 clang/lib/Parse/ParseDeclCXX.cpp | 18 +++++++++---------
 clang/lib/Sema/SemaChecking.cpp  |  2 +-
 clang/lib/Sema/SemaDeclCXX.cpp   | 10 +++-------
 clang/lib/Sema/SemaExprCXX.cpp   |  2 +-
 6 files changed, 17 insertions(+), 21 deletions(-)

diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 841d6a669dbf8..7ad94c48524a8 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -1136,7 +1136,7 @@ class QualType {
   /// Return true if this is a trivially relocatable type.
   bool isTriviallyRelocatableType(const ASTContext &Context) const;
 
-  bool isCppTriviallyRelocatableType(const ASTContext &Context) const;
+  bool isCXXTriviallyRelocatableType(const ASTContext &Context) const;
 
   bool isReplaceableType(const ASTContext &Context) const;
 
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index fc749b991f01f..e5dd00719e0a3 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -2905,7 +2905,7 @@ bool QualType::isTriviallyRelocatableType(const ASTContext &Context) const {
     return false;
   } else if (const auto *RD = BaseElementType->getAsRecordDecl()) {
     return RD->canPassInRegisters();
-  } else if (BaseElementType.isCppTriviallyRelocatableType(Context)) {
+  } else if (BaseElementType.isCXXTriviallyRelocatableType(Context)) {
     return true;
   } else {
     switch (isNonTrivialToPrimitiveDestructiveMove()) {
@@ -2919,7 +2919,7 @@ bool QualType::isTriviallyRelocatableType(const ASTContext &Context) const {
   }
 }
 
-bool QualType::isCppTriviallyRelocatableType(const ASTContext &Context) const {
+bool QualType::isCXXTriviallyRelocatableType(const ASTContext &Context) const {
   QualType BaseElementType = Context.getBaseElementType(*this);
 
   if (hasNonTrivialObjCLifetime())
diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp
index 53f6a1d36565c..ec080bf9dc97d 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -3872,7 +3872,7 @@ void Parser::ParseCXXMemberSpecification(SourceLocation RecordLoc,
   bool IsFinalSpelledSealed = false;
   bool IsAbstract = false;
   SourceLocation TriviallyRelocatable;
-  SourceLocation Replacable;
+  SourceLocation Replaceable;
 
   // Parse the optional 'final' keyword.
   if (getLangOpts().CPlusPlus && Tok.is(tok::identifier)) {
@@ -3889,19 +3889,19 @@ void Parser::ParseCXXMemberSpecification(SourceLocation RecordLoc,
             ParseCXX2CTriviallyRelocatableSpecifier(TriviallyRelocatable);
           }
           continue;
-        } else if (isCXX2CReplaceableKeyword(Tok)) {
-          if (Replacable.isValid()) {
+        }
+        if (isCXX2CReplaceableKeyword(Tok)) {
+          if (Replaceable.isValid()) {
             auto Skipped = Tok;
             ConsumeToken();
             Diag(Skipped, diag::err_duplicate_class_relocation_specifier)
-                << /*replaceable*/ 1 << Replacable;
+                << /*replaceable*/ 1 << Replaceable;
           } else {
-            ParseCXX2CReplaceableSpecifier(Replacable);
+            ParseCXX2CReplaceableSpecifier(Replaceable);
           }
           continue;
-        } else {
-          break;
         }
+        break;
       }
       if (isCXX11FinalKeyword()) {
         if (FinalLoc.isValid()) {
@@ -3939,7 +3939,7 @@ void Parser::ParseCXXMemberSpecification(SourceLocation RecordLoc,
         Diag(FinalLoc, diag::ext_warn_gnu_final);
     }
     assert((FinalLoc.isValid() || AbstractLoc.isValid() ||
-            TriviallyRelocatable.isValid() || Replacable.isValid()) &&
+            TriviallyRelocatable.isValid() || Replaceable.isValid()) &&
            "not a class definition");
 
     // Parse any C++11 attributes after 'final' keyword.
@@ -4014,7 +4014,7 @@ void Parser::ParseCXXMemberSpecification(SourceLocation RecordLoc,
   if (TagDecl)
     Actions.ActOnStartCXXMemberDeclarations(
         getCurScope(), TagDecl, FinalLoc, IsFinalSpelledSealed, IsAbstract,
-        TriviallyRelocatable, Replacable, T.getOpenLocation());
+        TriviallyRelocatable, Replaceable, T.getOpenLocation());
 
   // C++ 11p3: Members of a class defined with the keyword class are private
   // by default. Members of a class defined with the keywords struct or union
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index f1add3244d02d..b662a8e321fe3 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -1940,7 +1940,7 @@ static ExprResult BuiltinTriviallyRelocate(Sema &S, CallExpr *TheCall) {
     return ExprError();
 
   if (T.isConstQualified() ||
-      !T.isCppTriviallyRelocatableType(S.getASTContext()) ||
+      !T.isCXXTriviallyRelocatableType(S.getASTContext()) ||
       T->isIncompleteArrayType()) {
     S.Diag(TheCall->getArg(0)->getExprLoc(),
            diag::err_builtin_trivially_relocate_invalid_arg_type)
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 744d2c88962da..580307102943c 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -7532,7 +7532,7 @@ static bool isEligibleForTrivialRelocation(Sema &SemaRef, CXXRecordDecl *D) {
       continue;
     // ... has a non-static data member of an object type that is not
     // of a trivially relocatable type
-    if (!Field->getType().isCppTriviallyRelocatableType(
+    if (!Field->getType().isCXXTriviallyRelocatableType(
             SemaRef.getASTContext()))
       return false;
   }
@@ -7569,10 +7569,6 @@ void Sema::CheckCXX2CRelocatableAndReplaceable(CXXRecordDecl *D) {
 
   assert(D->hasDefinition());
 
-  bool MarkedCXX2CReplaceable = D->hasAttr<ReplaceableAttr>();
-  bool MarkedTriviallyRelocatable = D->hasAttr<TriviallyRelocatableAttr>();
-
-
   // This is part of "eligible for replacement", however we defer it
   // to avoid extraneous computations.
   auto HasSuitableSMP = [&] {
@@ -7615,7 +7611,7 @@ void Sema::CheckCXX2CRelocatableAndReplaceable(CXXRecordDecl *D) {
         return false;
 
     // has the trivially_relocatable_if_eligible class-property-specifier,
-    if (MarkedTriviallyRelocatable)
+    if (D->hasAttr<TriviallyRelocatableAttr>())
       return true;
 
     // is a union with no user-declared special member functions, or
@@ -7640,7 +7636,7 @@ void Sema::CheckCXX2CRelocatableAndReplaceable(CXXRecordDecl *D) {
         return false;
 
     // has the replaceable_if_eligible class-property-specifier
-    if (MarkedCXX2CReplaceable)
+    if (D->hasAttr<ReplaceableAttr>())
       return HasSuitableSMP();
 
     // is a union with no user-declared special member functions, or
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index af308077b21fe..341fc754f88fd 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -6008,7 +6008,7 @@ static bool EvaluateUnaryTypeTrait(Sema &Self, TypeTrait UTT,
   case UTT_IsBitwiseCloneable:
     return T.isBitwiseCloneableType(C);
   case UTT_IsCppTriviallyRelocatable:
-    return T.isCppTriviallyRelocatableType(C);
+    return T.isCXXTriviallyRelocatableType(C);
   case UTT_IsReplaceable:
     return T.isReplaceableType(C);
   case UTT_CanPassInRegs:

>From 6a1a53727888a773167abf5ddc6a1cdf1d34585e Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Tue, 25 Feb 2025 10:15:24 +0100
Subject: [PATCH 21/27] Defer the computation of relocatability/replaceability
 to when the trait is queried, in order to avoid a performance regression.

Unfortunately, this move the queries from ASTContext to Sema.
---
 clang/include/clang/AST/ASTContext.h          | 14 ++++
 .../clang/AST/CXXRecordDeclDefinitionBits.def |  8 --
 clang/include/clang/AST/DeclCXX.h             | 22 -----
 clang/include/clang/AST/Type.h                |  7 --
 clang/include/clang/Sema/Sema.h               | 15 +++-
 clang/lib/AST/ASTContext.cpp                  | 18 ++++
 clang/lib/AST/DeclCXX.cpp                     | 19 +----
 clang/lib/AST/Type.cpp                        | 53 ------------
 clang/lib/Sema/SemaChecking.cpp               |  3 +-
 clang/lib/Sema/SemaDeclCXX.cpp                | 80 ++++++++----------
 clang/lib/Sema/SemaExprCXX.cpp                | 82 ++++++++++++++++++-
 clang/test/SemaCXX/attr-trivial-abi.cpp       | 21 ++---
 12 files changed, 170 insertions(+), 172 deletions(-)

diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index 50083b055199e..8bc2406f51e63 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -617,6 +617,20 @@ class ASTContext : public RefCountedBase<ASTContext> {
   using ParameterIndexTable = llvm::DenseMap<const VarDecl *, unsigned>;
   ParameterIndexTable ParamIndices;
 
+public:
+  struct CXXRecordDeclRelocationInfo {
+    unsigned IsRelocatable;
+    unsigned IsReplaceable;
+  };
+  std::optional<CXXRecordDeclRelocationInfo>
+  getRelocationInfoForCXXRecord(const CXXRecordDecl *) const;
+  void setRelocationInfoForCXXRecord(const CXXRecordDecl *,
+                                     CXXRecordDeclRelocationInfo);
+
+private:
+  llvm::DenseMap<const CXXRecordDecl *, CXXRecordDeclRelocationInfo>
+      RelocatableClasses;
+
   ImportDecl *FirstLocalImport = nullptr;
   ImportDecl *LastLocalImport = nullptr;
 
diff --git a/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def b/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def
index 7633a987673e9..6620840df0ced 100644
--- a/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def
+++ b/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def
@@ -224,10 +224,6 @@ FIELD(StructuralIfLiteral, 1, NO_MERGE)
 /// explicitly deleted or defaulted).
 FIELD(UserProvidedDefaultConstructor, 1, NO_MERGE)
 
-FIELD(UserProvidedMoveAssignment, 1, NO_MERGE)
-FIELD(UserProvidedCopyAssignment, 1, NO_MERGE)
-FIELD(ExplicitlyDeletedMoveAssignment, 1, NO_MERGE)
-
 /// The special members which have been declared for this class,
 /// either by the user or implicitly.
 FIELD(DeclaredSpecialMembers, 6, MERGE_OR)
@@ -257,8 +253,4 @@ FIELD(IsAnyDestructorNoReturn, 1, NO_MERGE)
 /// type that is intangible). HLSL only.
 FIELD(IsHLSLIntangible, 1, NO_MERGE)
 
-FIELD(IsTriviallyRelocatable, 1, NO_MERGE)
-
-FIELD(IsReplaceable, 1, NO_MERGE)
-
 #undef FIELD
diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h
index a6043c28b5713..583d7e2da988e 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -820,18 +820,6 @@ class CXXRecordDecl : public RecordDecl {
     return data().UserDeclaredSpecialMembers & SMF_CopyConstructor;
   }
 
-  bool hasUserProvidedCopyAssignment() const {
-    return data().UserProvidedCopyAssignment;
-  }
-
-  bool hasUserProvidedMoveAssignment() const {
-    return data().UserProvidedCopyAssignment;
-  }
-
-  bool hasExplicitlyDeletedMoveAssignment() const {
-    return data().ExplicitlyDeletedMoveAssignment;
-  }
-
   /// Determine whether this class needs an implicit copy
   /// constructor to be lazily declared.
   bool needsImplicitCopyConstructor() const {
@@ -1497,16 +1485,6 @@ class CXXRecordDecl : public RecordDecl {
     return isLiteral() && data().StructuralIfLiteral;
   }
 
-  bool isTriviallyRelocatable() const { return data().IsTriviallyRelocatable; }
-
-  void setIsTriviallyRelocatable(bool Set) {
-    data().IsTriviallyRelocatable = Set;
-  }
-
-  bool isReplaceable() const { return data().IsReplaceable; }
-
-  void setIsReplaceable(bool Set) { data().IsReplaceable = Set; }
-
   /// Notify the class that this destructor is now selected.
   ///
   /// Important properties of the class depend on destructor properties. Since
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 7ad94c48524a8..773796a55eaa1 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -1133,13 +1133,6 @@ class QualType {
   /// Return true if this is a trivially copyable type
   bool isTriviallyCopyConstructibleType(const ASTContext &Context) const;
 
-  /// Return true if this is a trivially relocatable type.
-  bool isTriviallyRelocatableType(const ASTContext &Context) const;
-
-  bool isCXXTriviallyRelocatableType(const ASTContext &Context) const;
-
-  bool isReplaceableType(const ASTContext &Context) const;
-
   /// Returns true if it is a class and it might be dynamic.
   bool mayBeDynamicClass() const;
 
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 965da321c6604..c02e827773341 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -4325,7 +4325,8 @@ class Sema final : public SemaBase {
   void ActOnTagFinishDefinition(Scope *S, Decl *TagDecl,
                                 SourceRange BraceRange);
 
-  void CheckCXX2CRelocatableAndReplaceable(CXXRecordDecl *D);
+  ASTContext::CXXRecordDeclRelocationInfo
+  CheckCXX2CRelocatableAndReplaceable(const clang::CXXRecordDecl *D);
 
   void ActOnTagFinishSkippedDefinition(SkippedDefinitionContext Context);
 
@@ -8641,6 +8642,18 @@ class Sema final : public SemaBase {
                                                ExprResult &LHS, ExprResult &RHS,
                                                SourceLocation QuestionLoc);
 
+  //// Determines if a type is trivially relocatable
+  /// according to the C++26 rules.
+  // FIXME: This is in Sema because it requires
+  // overload resolution, can we move to ASTContext?
+  bool IsCXXTriviallyRelocatableType(QualType T);
+
+  //// Determines if a type is replaceable
+  /// according to the C++26 rules.
+  // FIXME: This is in Sema because it requires
+  // overload resolution, can we move to ASTContext?
+  bool IsCXXReplaceableType(QualType T);
+
   /// Check the operands of ?: under C++ semantics.
   ///
   /// See C++ [expr.cond]. Note that LHS is never null, even for the GNU x ?: y
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index ae136ae271882..a4c89d66f17b2 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -1688,6 +1688,24 @@ void ASTContext::getOverriddenMethods(
   Overridden.append(OverDecls.begin(), OverDecls.end());
 }
 
+std::optional<ASTContext::CXXRecordDeclRelocationInfo>
+ASTContext::getRelocationInfoForCXXRecord(const CXXRecordDecl *RD) const {
+  assert(RD);
+  CXXRecordDecl *D = RD->getDefinition();
+  auto it = RelocatableClasses.find(D);
+  if (it != RelocatableClasses.end())
+    return it->getSecond();
+  return std::nullopt;
+}
+
+void ASTContext::setRelocationInfoForCXXRecord(
+    const CXXRecordDecl *RD, CXXRecordDeclRelocationInfo Info) {
+  assert(RD);
+  CXXRecordDecl *D = RD->getDefinition();
+  assert(RelocatableClasses.find(D) == RelocatableClasses.end());
+  RelocatableClasses.insert({D, Info});
+}
+
 void ASTContext::addedLocalImportDecl(ImportDecl *Import) {
   assert(!Import->getNextLocalImport() &&
          "Import declaration already in the chain");
diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp
index eb87928b2d865..f24ea815768a6 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -103,16 +103,13 @@ CXXRecordDecl::DefinitionData::DefinitionData(CXXRecordDecl *D)
       HasConstexprDefaultConstructor(false),
       DefaultedDestructorIsConstexpr(true),
       HasNonLiteralTypeFieldsOrBases(false), StructuralIfLiteral(true),
-      UserProvidedDefaultConstructor(false), UserProvidedMoveAssignment(false),
-      UserProvidedCopyAssignment(false), ExplicitlyDeletedMoveAssignment(false),
-      DeclaredSpecialMembers(0),
+      UserProvidedDefaultConstructor(false), DeclaredSpecialMembers(0),
       ImplicitCopyConstructorCanHaveConstParamForVBase(true),
       ImplicitCopyConstructorCanHaveConstParamForNonVBase(true),
       ImplicitCopyAssignmentHasConstParam(true),
       HasDeclaredCopyConstructorWithConstParam(false),
       HasDeclaredCopyAssignmentWithConstParam(false),
-      IsAnyDestructorNoReturn(false), IsHLSLIntangible(false),
-      IsTriviallyRelocatable(false), IsReplaceable(false), IsLambda(false),
+      IsAnyDestructorNoReturn(false), IsHLSLIntangible(false), IsLambda(false),
       IsParsingBaseSpecifiers(false), ComputedVisibleConversions(false),
       HasODRHash(false), Definition(D) {}
 
@@ -1559,10 +1556,6 @@ void CXXRecordDecl::addedEligibleSpecialMemberFunction(const CXXMethodDecl *MD,
     if (DD->isNoReturn())
       data().IsAnyDestructorNoReturn = true;
   }
-  if (SMKind == SMF_CopyAssignment)
-    data().UserProvidedCopyAssignment = MD->isUserProvided();
-  else if (SMKind == SMF_MoveAssignment)
-    data().UserProvidedMoveAssignment = MD->isUserProvided();
   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
@@ -1584,9 +1577,6 @@ void CXXRecordDecl::addedEligibleSpecialMemberFunction(const CXXMethodDecl *MD,
     if (!MD->isUserProvided())
       data().DeclaredNonTrivialSpecialMembersForCall |= SMKind;
   }
-
-  if (MD->isDeleted() && SMKind == SMF_MoveAssignment)
-    data().ExplicitlyDeletedMoveAssignment = true;
 }
 
 void CXXRecordDecl::finishedDefaultedOrDeletedMember(CXXMethodDecl *D) {
@@ -1614,11 +1604,8 @@ void CXXRecordDecl::finishedDefaultedOrDeletedMember(CXXMethodDecl *D) {
       data().HasIrrelevantDestructor = false;
   } else if (D->isCopyAssignmentOperator())
     SMKind |= SMF_CopyAssignment;
-  else if (D->isMoveAssignmentOperator()) {
+  else if (D->isMoveAssignmentOperator())
     SMKind |= SMF_MoveAssignment;
-    if (!D->isIneligibleOrNotSelected() && D->isDeleted())
-      data().ExplicitlyDeletedMoveAssignment = true;
-  }
 
   // Update which trivial / non-trivial special members we have.
   // addedMember will have skipped this step for this member.
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index e5dd00719e0a3..52b922e2138fd 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -2896,59 +2896,6 @@ bool QualType::isTriviallyCopyConstructibleType(
                                      /*IsCopyConstructible=*/true);
 }
 
-bool QualType::isTriviallyRelocatableType(const ASTContext &Context) const {
-  QualType BaseElementType = Context.getBaseElementType(*this);
-
-  if (BaseElementType->isIncompleteType()) {
-    return false;
-  } else if (!BaseElementType->isObjectType()) {
-    return false;
-  } else if (const auto *RD = BaseElementType->getAsRecordDecl()) {
-    return RD->canPassInRegisters();
-  } else if (BaseElementType.isCXXTriviallyRelocatableType(Context)) {
-    return true;
-  } else {
-    switch (isNonTrivialToPrimitiveDestructiveMove()) {
-    case PCK_Trivial:
-      return !isDestructedType();
-    case PCK_ARCStrong:
-      return true;
-    default:
-      return false;
-    }
-  }
-}
-
-bool QualType::isCXXTriviallyRelocatableType(const ASTContext &Context) const {
-  QualType BaseElementType = Context.getBaseElementType(*this);
-
-  if (hasNonTrivialObjCLifetime())
-    return false;
-
-  if (BaseElementType->isIncompleteType())
-    return false;
-
-  if (BaseElementType->isScalarType() || BaseElementType->isVectorType())
-    return true;
-
-  if (const auto *RD = BaseElementType->getAsCXXRecordDecl())
-    return RD->isTriviallyRelocatable();
-  return false;
-}
-
-bool QualType::isReplaceableType(const ASTContext &Context) const {
-  if (isConstQualified() || isVolatileQualified())
-    return false;
-  QualType BaseElementType = Context.getBaseElementType(getUnqualifiedType());
-  if (BaseElementType->isIncompleteType())
-    return false;
-  if (BaseElementType->isScalarType())
-    return true;
-  if (const auto *RD = BaseElementType->getAsCXXRecordDecl())
-    return RD->isReplaceable();
-  return false;
-}
-
 bool QualType::isNonWeakInMRRWithObjCWeak(const ASTContext &Context) const {
   return !Context.getLangOpts().ObjCAutoRefCount &&
          Context.getLangOpts().ObjCWeak &&
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index b662a8e321fe3..7f45533713bae 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -1939,8 +1939,7 @@ static ExprResult BuiltinTriviallyRelocate(Sema &S, CallExpr *TheCall) {
                             diag::err_incomplete_type))
     return ExprError();
 
-  if (T.isConstQualified() ||
-      !T.isCXXTriviallyRelocatableType(S.getASTContext()) ||
+  if (T.isConstQualified() || !S.IsCXXTriviallyRelocatableType(T) ||
       T->isIncompleteArrayType()) {
     S.Diag(TheCall->getArg(0)->getExprLoc(),
            diag::err_builtin_trivially_relocate_invalid_arg_type)
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 580307102943c..7cce7ed1fa054 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -7290,8 +7290,6 @@ void Sema::CheckCompletedCXXClass(Scope *S, CXXRecordDecl *Record) {
   checkClassLevelDLLAttribute(Record);
   checkClassLevelCodeSegAttribute(Record);
 
-  CheckCXX2CRelocatableAndReplaceable(Record);
-
   bool ClangABICompat4 =
       Context.getLangOpts().getClangABICompat() <= LangOptions::ClangABI::Ver4;
   TargetInfo::CallingConvKind CCK =
@@ -7371,8 +7369,9 @@ void Sema::CheckCompletedCXXClass(Scope *S, CXXRecordDecl *Record) {
   CheckMismatchedTypeAwareAllocators(OO_Array_New, OO_Array_Delete);
 }
 
-static CXXMethodDecl *
-LookupSpecialMemberFromXValue(Sema &SemaRef, CXXRecordDecl *RD, bool Assign) {
+static CXXMethodDecl *LookupSpecialMemberFromXValue(Sema &SemaRef,
+                                                    const CXXRecordDecl *RD,
+                                                    bool Assign) {
   RD = RD->getDefinition();
   SourceLocation LookupLoc = RD->getLocation();
 
@@ -7423,7 +7422,8 @@ LookupSpecialMemberFromXValue(Sema &SemaRef, CXXRecordDecl *RD, bool Assign) {
     auto CtorInfo = getConstructorInfo(Cand);
     if (CXXMethodDecl *M = dyn_cast<CXXMethodDecl>(Cand->getUnderlyingDecl())) {
       if (Assign)
-        SemaRef.AddMethodCandidate(M, Cand, RD, ThisTy, Classification,
+        SemaRef.AddMethodCandidate(M, Cand, const_cast<CXXRecordDecl *>(RD),
+                                   ThisTy, Classification,
                                    llvm::ArrayRef(&Arg, NumArgs), OCS, true);
       else {
         assert(CtorInfo);
@@ -7435,8 +7435,8 @@ LookupSpecialMemberFromXValue(Sema &SemaRef, CXXRecordDecl *RD, bool Assign) {
                    dyn_cast<FunctionTemplateDecl>(Cand->getUnderlyingDecl())) {
       if (Assign)
         SemaRef.AddMethodTemplateCandidate(
-            Tmpl, Cand, RD, nullptr, ThisTy, Classification,
-            llvm::ArrayRef(&Arg, NumArgs), OCS, true);
+            Tmpl, Cand, const_cast<CXXRecordDecl *>(RD), nullptr, ThisTy,
+            Classification, llvm::ArrayRef(&Arg, NumArgs), OCS, true);
       else {
         assert(CtorInfo);
         SemaRef.AddTemplateOverloadCandidate(
@@ -7455,7 +7455,8 @@ LookupSpecialMemberFromXValue(Sema &SemaRef, CXXRecordDecl *RD, bool Assign) {
   }
 }
 
-static bool hasSuitableConstructorForRelocation(Sema &SemaRef, CXXRecordDecl *D,
+static bool hasSuitableConstructorForRelocation(Sema &SemaRef,
+                                                const CXXRecordDecl *D,
                                                 bool AllowUserDefined) {
   assert(D->hasDefinition() && !D->isInvalidDecl());
 
@@ -7467,9 +7468,8 @@ static bool hasSuitableConstructorForRelocation(Sema &SemaRef, CXXRecordDecl *D,
   return Decl && Decl->isUserProvided() == AllowUserDefined;
 }
 
-static bool
-hasSuitableMoveAssignmentOperatorForRelocation(Sema &SemaRef, CXXRecordDecl *D,
-                                               bool AllowUserDefined) {
+static bool hasSuitableMoveAssignmentOperatorForRelocation(
+    Sema &SemaRef, const CXXRecordDecl *D, bool AllowUserDefined) {
   assert(D->hasDefinition() && !D->isInvalidDecl());
 
   if (D->hasSimpleMoveAssignment() || D->hasSimpleCopyAssignment())
@@ -7492,7 +7492,7 @@ hasSuitableMoveAssignmentOperatorForRelocation(Sema &SemaRef, CXXRecordDecl *D,
 // type C selects an assignment operator function that is a direct member of C
 // and is neither user-provided nor deleted, and C has a destructor that is
 // neither user-provided nor deleted.
-static bool isDefaultMovable(Sema &SemaRef, CXXRecordDecl *D, CXXDestructorDecl* Dtr) {
+static bool IsDefaultMovable(Sema &SemaRef, const CXXRecordDecl *D) {
   if (!hasSuitableConstructorForRelocation(SemaRef, D,
                                            /*AllowUserDefined=*/false))
     return false;
@@ -7501,6 +7501,8 @@ static bool isDefaultMovable(Sema &SemaRef, CXXRecordDecl *D, CXXDestructorDecl*
           SemaRef, D, /*AllowUserDefined=*/false))
     return false;
 
+  CXXDestructorDecl *Dtr = D->getDestructor();
+
   if (!Dtr)
     return true;
 
@@ -7512,7 +7514,8 @@ static bool isDefaultMovable(Sema &SemaRef, CXXRecordDecl *D, CXXDestructorDecl*
 
 // [C++26][class.prop]
 // A class is eligible for trivial relocation unless it...
-static bool isEligibleForTrivialRelocation(Sema &SemaRef, CXXRecordDecl *D) {
+static bool IsEligibleForTrivialRelocation(Sema &SemaRef,
+                                           const CXXRecordDecl *D) {
 
   for (const CXXBaseSpecifier &B : D->bases()) {
     const auto *BaseDecl = B.getType()->getAsCXXRecordDecl();
@@ -7520,8 +7523,8 @@ static bool isEligibleForTrivialRelocation(Sema &SemaRef, CXXRecordDecl *D) {
       continue;
     // ... has any virtual base classes
     // ... has a base class that is not a trivially relocatable class
-    if (B.isVirtual() ||
-        (!BaseDecl->isDependentType() && !BaseDecl->isTriviallyRelocatable()))
+    if (B.isVirtual() || (!BaseDecl->isDependentType() &&
+                          !SemaRef.IsCXXTriviallyRelocatableType(B.getType())))
       return false;
   }
 
@@ -7532,23 +7535,23 @@ static bool isEligibleForTrivialRelocation(Sema &SemaRef, CXXRecordDecl *D) {
       continue;
     // ... has a non-static data member of an object type that is not
     // of a trivially relocatable type
-    if (!Field->getType().isCXXTriviallyRelocatableType(
-            SemaRef.getASTContext()))
+    if (!SemaRef.IsCXXTriviallyRelocatableType(Field->getType()))
       return false;
   }
-  return true;
+  return !D->hasDeletedDestructor();
 }
 
 // [C++26][class.prop]
 // A class C is eligible for replacement unless
-static bool isEligibleForReplacement(Sema &SemaRef, CXXRecordDecl *D) {
+static bool IsEligibleForReplacement(Sema &SemaRef, const CXXRecordDecl *D) {
 
   for (const CXXBaseSpecifier &B : D->bases()) {
     const auto *BaseDecl = B.getType()->getAsCXXRecordDecl();
     if (!BaseDecl)
       continue;
     // it has a base class that is not a replaceable class
-    if ((!BaseDecl->isDependentType() && !BaseDecl->isReplaceable()))
+    if (!BaseDecl->isDependentType() &&
+        !SemaRef.IsCXXReplaceableType(B.getType()))
       return false;
   }
 
@@ -7557,15 +7560,18 @@ static bool isEligibleForReplacement(Sema &SemaRef, CXXRecordDecl *D) {
       continue;
 
     // it has a non-static data member that is not of a replaceable type,
-    if (!Field->getType().isReplaceableType(SemaRef.getASTContext()))
+    if (!SemaRef.IsCXXReplaceableType(Field->getType()))
       return false;
   }
-  return true;
+  return !D->hasDeletedDestructor();
 }
 
-void Sema::CheckCXX2CRelocatableAndReplaceable(CXXRecordDecl *D) {
+ASTContext::CXXRecordDeclRelocationInfo
+Sema::CheckCXX2CRelocatableAndReplaceable(const CXXRecordDecl *D) {
+  ASTContext::CXXRecordDeclRelocationInfo Info{false, false};
+
   if (!getLangOpts().CPlusPlus || D->isInvalidDecl())
-    return;
+    return Info;
 
   assert(D->hasDefinition());
 
@@ -7587,29 +7593,20 @@ void Sema::CheckCXX2CRelocatableAndReplaceable(CXXRecordDecl *D) {
     return *Is;
   };
 
-  auto GetDestructor = [&, Dtr = static_cast<CXXDestructorDecl*>(nullptr)]() mutable {
-      if(!Dtr)
-          Dtr = D->getDestructor();
-      return Dtr;
-  };
-
   auto IsDefaultMovable = [&, Is = std::optional<bool>{}]() mutable {
     if (!Is.has_value())
-      Is = isDefaultMovable(*this, D, GetDestructor());
+      Is = ::IsDefaultMovable(*this, D);
     return *Is;
   };
 
-  bool IsTriviallyRelocatable = [&] {
+  Info.IsRelocatable = [&] {
     if (D->isDependentType())
       return false;
 
     // if it is eligible for trivial relocation
-    if (!isEligibleForTrivialRelocation(*this, D))
+    if (!IsEligibleForTrivialRelocation(*this, D))
       return false;
 
-    if(auto* Dtr = GetDestructor(); Dtr && Dtr->isDeleted())
-        return false;
-
     // has the trivially_relocatable_if_eligible class-property-specifier,
     if (D->hasAttr<TriviallyRelocatableAttr>())
       return true;
@@ -7622,19 +7619,14 @@ void Sema::CheckCXX2CRelocatableAndReplaceable(CXXRecordDecl *D) {
     return IsDefaultMovable();
   }();
 
-  D->setIsTriviallyRelocatable(IsTriviallyRelocatable);
-
-  bool IsReplaceable = [&] {
+  Info.IsReplaceable = [&] {
     if (D->isDependentType())
       return false;
 
     // A class C is a replaceable class if it is eligible for replacement
-    if (!isEligibleForReplacement(*this, D))
+    if (!IsEligibleForReplacement(*this, D))
       return false;
 
-    if(auto* Dtr = GetDestructor(); Dtr && Dtr->isDeleted())
-        return false;
-
     // has the replaceable_if_eligible class-property-specifier
     if (D->hasAttr<ReplaceableAttr>())
       return HasSuitableSMP();
@@ -7647,7 +7639,7 @@ void Sema::CheckCXX2CRelocatableAndReplaceable(CXXRecordDecl *D) {
     return IsDefaultMovable();
   }();
 
-  D->setIsReplaceable(IsReplaceable);
+  return Info;
 }
 
 /// Look up the special member function that would be called by a special
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index 341fc754f88fd..e4b96c2cea50c 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -17,6 +17,7 @@
 #include "clang/AST/ASTLambda.h"
 #include "clang/AST/CXXInheritance.h"
 #include "clang/AST/CharUnits.h"
+#include "clang/AST/DeclCXX.h"
 #include "clang/AST/DeclObjC.h"
 #include "clang/AST/DynamicRecursiveASTVisitor.h"
 #include "clang/AST/ExprCXX.h"
@@ -5585,6 +5586,81 @@ static bool isTriviallyEqualityComparableType(Sema &S, QualType Type, SourceLoca
       CanonicalType, /*CheckIfTriviallyCopyable=*/false);
 }
 
+static bool IsCXXTriviallyRelocatableType(Sema &S, const CXXRecordDecl *RD) {
+  if (std::optional<ASTContext::CXXRecordDeclRelocationInfo> Info =
+          S.getASTContext().getRelocationInfoForCXXRecord(RD))
+    return Info->IsRelocatable;
+  ASTContext::CXXRecordDeclRelocationInfo Info =
+      S.CheckCXX2CRelocatableAndReplaceable(RD);
+  S.getASTContext().setRelocationInfoForCXXRecord(RD, Info);
+  return Info.IsRelocatable;
+}
+
+bool Sema::IsCXXTriviallyRelocatableType(QualType Type) {
+
+  QualType BaseElementType = getASTContext().getBaseElementType(Type);
+
+  if (BaseElementType.hasNonTrivialObjCLifetime())
+    return false;
+
+  if (BaseElementType->isIncompleteType())
+    return false;
+
+  if (BaseElementType->isScalarType() || BaseElementType->isVectorType())
+    return true;
+
+  if (const auto *RD = BaseElementType->getAsCXXRecordDecl())
+    return ::IsCXXTriviallyRelocatableType(*this, RD);
+
+  return false;
+}
+
+static bool IsCXXReplaceableType(Sema &S, const CXXRecordDecl *RD) {
+  if (std::optional<ASTContext::CXXRecordDeclRelocationInfo> Info =
+          S.getASTContext().getRelocationInfoForCXXRecord(RD))
+    return Info->IsReplaceable;
+  ASTContext::CXXRecordDeclRelocationInfo Info =
+      S.CheckCXX2CRelocatableAndReplaceable(RD);
+  S.getASTContext().setRelocationInfoForCXXRecord(RD, Info);
+  return Info.IsReplaceable;
+}
+
+bool Sema::IsCXXReplaceableType(QualType Type) {
+  if (Type.isConstQualified() || Type.isVolatileQualified())
+    return false;
+  QualType BaseElementType =
+      getASTContext().getBaseElementType(Type.getUnqualifiedType());
+  if (BaseElementType->isIncompleteType())
+    return false;
+  if (BaseElementType->isScalarType())
+    return true;
+  if (const auto *RD = BaseElementType->getAsCXXRecordDecl())
+    return ::IsCXXReplaceableType(*this, RD);
+  return false;
+}
+
+static bool IsTriviallyRelocatableType(Sema &SemaRef, QualType T) {
+  QualType BaseElementType = SemaRef.getASTContext().getBaseElementType(T);
+
+  if (SemaRef.IsCXXTriviallyRelocatableType(T))
+    return true;
+
+  if (BaseElementType->isIncompleteType() || !BaseElementType->isObjectType())
+    return false;
+
+  if (const auto *RD = BaseElementType->getAsRecordDecl())
+    return RD->canPassInRegisters();
+
+  switch (T.isNonTrivialToPrimitiveDestructiveMove()) {
+  case QualType::PCK_Trivial:
+    return !T.isDestructedType();
+  case QualType::PCK_ARCStrong:
+    return true;
+  default:
+    return false;
+  }
+}
+
 static bool EvaluateUnaryTypeTrait(Sema &Self, TypeTrait UTT,
                                    SourceLocation KeyLoc,
                                    TypeSourceInfo *TInfo) {
@@ -6004,13 +6080,13 @@ static bool EvaluateUnaryTypeTrait(Sema &Self, TypeTrait UTT,
   case UTT_HasUniqueObjectRepresentations:
     return C.hasUniqueObjectRepresentations(T);
   case UTT_IsTriviallyRelocatable:
-    return T.isTriviallyRelocatableType(C);
+    return IsTriviallyRelocatableType(Self, T);
   case UTT_IsBitwiseCloneable:
     return T.isBitwiseCloneableType(C);
   case UTT_IsCppTriviallyRelocatable:
-    return T.isCXXTriviallyRelocatableType(C);
+    return Self.IsCXXTriviallyRelocatableType(T);
   case UTT_IsReplaceable:
-    return T.isReplaceableType(C);
+    return Self.IsCXXReplaceableType(T);
   case UTT_CanPassInRegs:
     if (CXXRecordDecl *RD = T->getAsCXXRecordDecl(); RD && !T.hasQualifiers())
       return RD->canPassInRegisters();
diff --git a/clang/test/SemaCXX/attr-trivial-abi.cpp b/clang/test/SemaCXX/attr-trivial-abi.cpp
index c215f90eb124c..c2c2c22c95bb2 100644
--- a/clang/test/SemaCXX/attr-trivial-abi.cpp
+++ b/clang/test/SemaCXX/attr-trivial-abi.cpp
@@ -28,25 +28,18 @@ static_assert(__is_trivially_relocatable(S1), "");
 struct __attribute__((trivial_abi)) S3 { // expected-warning {{'trivial_abi' cannot be applied to 'S3'}} expected-note {{is polymorphic}}
   virtual void m();
 };
-static_assert(!__is_trivially_relocatable(S3), "");
+static_assert(__is_trivially_relocatable(S3), "");
 
 struct S3_2 {
   virtual void m();
 } __attribute__((trivial_abi)); // expected-warning {{'trivial_abi' cannot be applied to 'S3_2'}} expected-note {{is polymorphic}}
-static_assert(!__is_trivially_relocatable(S3_2), "");
+static_assert(__is_trivially_relocatable(S3_2), "");
 
 struct __attribute__((trivial_abi)) S3_3 { // expected-warning {{'trivial_abi' cannot be applied to 'S3_3'}} expected-note {{has a field of a non-trivial class type}}
   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>
@@ -81,7 +74,7 @@ struct __attribute__((trivial_abi)) S10 {
 
 S10<int *> p1;
 static_assert(__is_trivially_relocatable(S10<int>), "");
-static_assert(!__is_trivially_relocatable(S10<S3>), "");
+static_assert(__is_trivially_relocatable(S10<S3>), "");
 
 template <class T>
 struct S14 {
@@ -94,14 +87,14 @@ struct __attribute__((trivial_abi)) S15 : S14<T> {
 
 S15<int> s15;
 static_assert(__is_trivially_relocatable(S15<int>), "");
-static_assert(!__is_trivially_relocatable(S15<S3>), "");
+static_assert(__is_trivially_relocatable(S15<S3>), "");
 
 template <class T>
 struct __attribute__((trivial_abi)) S16 {
   S14<T> a;
 };
 static_assert(__is_trivially_relocatable(S16<int>), "");
-static_assert(!__is_trivially_relocatable(S16<S3>), "");
+static_assert(__is_trivially_relocatable(S16<S3>), "");
 
 S16<int> s16;
 
@@ -118,11 +111,7 @@ 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;

>From fdf7aa17b09d88545dbf784f47d7dca789815e50 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Tue, 25 Feb 2025 13:43:37 +0100
Subject: [PATCH 22/27] remove dead code

---
 clang/include/clang/AST/DeclCXX.h | 14 --------------
 1 file changed, 14 deletions(-)

diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h
index 583d7e2da988e..fc84e04dcc398 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -722,20 +722,6 @@ class CXXRecordDecl : public RecordDecl {
     return data().DefaultedMoveConstructorIsDeleted;
   }
 
-  bool defaultedMoveAssignmentIsDeleted() const {
-    assert((!needsOverloadResolutionForMoveAssignment() ||
-            (data().DeclaredSpecialMembers & SMF_MoveAssignment)) &&
-           "this property has not yet been computed by Sema");
-    return data().DefaultedMoveAssignmentIsDeleted;
-  }
-
-  bool defaultedCopyAssignmentIsDeleted() const {
-    assert((!needsOverloadResolutionForCopyAssignment() ||
-            (data().DeclaredSpecialMembers & SMF_CopyAssignment)) &&
-           "this property has not yet been computed by Sema");
-    return data().DefaultedCopyAssignmentIsDeleted;
-  }
-
   /// \c true if a defaulted destructor for this class would be deleted.
   bool defaultedDestructorIsDeleted() const {
     assert((!needsOverloadResolutionForDestructor() ||

>From cfde8a5de8ad0038e1d3813a11dd27fc3dda5d93 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Tue, 25 Feb 2025 17:35:22 +0100
Subject: [PATCH 23/27] Add VLA tests

---
 clang/lib/Sema/SemaExprCXX.cpp                     | 7 +++++++
 clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp | 8 ++++++++
 2 files changed, 15 insertions(+)

diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index e4b96c2cea50c..59d09574923a9 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -5600,6 +5600,9 @@ bool Sema::IsCXXTriviallyRelocatableType(QualType Type) {
 
   QualType BaseElementType = getASTContext().getBaseElementType(Type);
 
+  if(Type->isVariableArrayType())
+      return false;
+
   if (BaseElementType.hasNonTrivialObjCLifetime())
     return false;
 
@@ -5628,6 +5631,10 @@ static bool IsCXXReplaceableType(Sema &S, const CXXRecordDecl *RD) {
 bool Sema::IsCXXReplaceableType(QualType Type) {
   if (Type.isConstQualified() || Type.isVolatileQualified())
     return false;
+
+  if(Type->isVariableArrayType())
+      return false;
+
   QualType BaseElementType =
       getASTContext().getBaseElementType(Type.getUnqualifiedType());
   if (BaseElementType->isIncompleteType())
diff --git a/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp b/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp
index e744a3ef20ad0..062becd4f5776 100644
--- a/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp
+++ b/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp
@@ -279,6 +279,14 @@ static_assert(!__builtin_is_replaceable(WithVBase<S<const int>>));
 static_assert(!__builtin_is_replaceable(WithVBase<UserProvidedMove>));
 static_assert(__builtin_is_replaceable(WithVirtual));
 
+int n = 4; // expected-note 2{{declared here}}
+static_assert(!__builtin_is_cpp_trivially_relocatable(int[n]));
+// expected-warning at -1 {{variable length arrays in C++ are a Clang extension}}
+// expected-note at -2 {{read of non-const variable 'n' is not allowed in a constant expression}}
+static_assert(!__builtin_is_replaceable(int[n]));
+// expected-warning at -1 {{variable length arrays in C++ are a Clang extension}}
+// expected-note at -2 {{read of non-const variable 'n' is not allowed in a constant expression}}
+
 
 struct U1 replaceable_if_eligible {
     ~U1() = delete;

>From a473debd42928608f952fbdf3ed8243df6e82595 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Tue, 25 Feb 2025 18:15:38 +0100
Subject: [PATCH 24/27] Format

---
 clang/lib/Sema/SemaExprCXX.cpp | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index 59d09574923a9..2555647e20047 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -5600,8 +5600,8 @@ bool Sema::IsCXXTriviallyRelocatableType(QualType Type) {
 
   QualType BaseElementType = getASTContext().getBaseElementType(Type);
 
-  if(Type->isVariableArrayType())
-      return false;
+  if (Type->isVariableArrayType())
+    return false;
 
   if (BaseElementType.hasNonTrivialObjCLifetime())
     return false;
@@ -5632,8 +5632,8 @@ bool Sema::IsCXXReplaceableType(QualType Type) {
   if (Type.isConstQualified() || Type.isVolatileQualified())
     return false;
 
-  if(Type->isVariableArrayType())
-      return false;
+  if (Type->isVariableArrayType())
+    return false;
 
   QualType BaseElementType =
       getASTContext().getBaseElementType(Type.getUnqualifiedType());

>From 0f1aa038210558e76ee1cab225b08c7175a107fe Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Tue, 25 Feb 2025 20:10:30 +0100
Subject: [PATCH 25/27] Make sure __is_trivially_relocatable is false for
 polymorphic types

---
 clang/lib/Sema/SemaExprCXX.cpp          | 14 ++++++++++----
 clang/test/SemaCXX/attr-trivial-abi.cpp | 15 +++++++++++++--
 2 files changed, 23 insertions(+), 6 deletions(-)

diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index 2555647e20047..2a1575e20a298 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -5649,15 +5649,21 @@ bool Sema::IsCXXReplaceableType(QualType Type) {
 static bool IsTriviallyRelocatableType(Sema &SemaRef, QualType T) {
   QualType BaseElementType = SemaRef.getASTContext().getBaseElementType(T);
 
-  if (SemaRef.IsCXXTriviallyRelocatableType(T))
-    return true;
-
-  if (BaseElementType->isIncompleteType() || !BaseElementType->isObjectType())
+  if (BaseElementType->isIncompleteType())
+    return false;
+  if (!BaseElementType->isObjectType())
     return false;
 
+  if (const auto *RD = BaseElementType->getAsCXXRecordDecl();
+      RD && !RD->isPolymorphic() && IsCXXTriviallyRelocatableType(SemaRef, RD))
+    return true;
+
   if (const auto *RD = BaseElementType->getAsRecordDecl())
     return RD->canPassInRegisters();
 
+  if (BaseElementType.isTriviallyCopyableType(SemaRef.getASTContext()))
+    return true;
+
   switch (T.isNonTrivialToPrimitiveDestructiveMove()) {
   case QualType::PCK_Trivial:
     return !T.isDestructedType();
diff --git a/clang/test/SemaCXX/attr-trivial-abi.cpp b/clang/test/SemaCXX/attr-trivial-abi.cpp
index c2c2c22c95bb2..e018ccda2d8d9 100644
--- a/clang/test/SemaCXX/attr-trivial-abi.cpp
+++ b/clang/test/SemaCXX/attr-trivial-abi.cpp
@@ -28,18 +28,25 @@ static_assert(__is_trivially_relocatable(S1), "");
 struct __attribute__((trivial_abi)) S3 { // expected-warning {{'trivial_abi' cannot be applied to 'S3'}} expected-note {{is polymorphic}}
   virtual void m();
 };
-static_assert(__is_trivially_relocatable(S3), "");
+static_assert(!__is_trivially_relocatable(S3), "");
 
 struct S3_2 {
   virtual void m();
 } __attribute__((trivial_abi)); // expected-warning {{'trivial_abi' cannot be applied to 'S3_2'}} expected-note {{is polymorphic}}
-static_assert(__is_trivially_relocatable(S3_2), "");
+static_assert(!__is_trivially_relocatable(S3_2), "");
 
 struct __attribute__((trivial_abi)) S3_3 { // expected-warning {{'trivial_abi' cannot be applied to 'S3_3'}} expected-note {{has a field of a non-trivial class type}}
   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>
@@ -111,7 +118,11 @@ 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;

>From b5a8057857ea49aa8226273c8d5e7bdc81bb28e7 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Tue, 25 Feb 2025 20:13:44 +0100
Subject: [PATCH 26/27] Do not yet set the featurte test macro

---
 clang/lib/Frontend/InitPreprocessor.cpp | 2 +-
 clang/www/cxx_status.html               | 7 ++++++-
 2 files changed, 7 insertions(+), 2 deletions(-)

diff --git a/clang/lib/Frontend/InitPreprocessor.cpp b/clang/lib/Frontend/InitPreprocessor.cpp
index 60fbd605e5568..39823959fbfe5 100644
--- a/clang/lib/Frontend/InitPreprocessor.cpp
+++ b/clang/lib/Frontend/InitPreprocessor.cpp
@@ -772,7 +772,7 @@ static void InitializeCPlusPlusFeatureTestMacros(const LangOptions &LangOpts,
   Builder.defineMacro("__cpp_pack_indexing", "202311L");
   Builder.defineMacro("__cpp_deleted_function", "202403L");
   Builder.defineMacro("__cpp_variadic_friend", "202403L");
-  Builder.defineMacro("__cpp_trivial_relocatability", "202502L");
+  //Builder.defineMacro("__cpp_trivial_relocatability", "202502L");
 
   if (LangOpts.Char8)
     Builder.defineMacro("__cpp_char8_t", "202207L");
diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html
index cbcf462970b7f..dff57689e84b9 100755
--- a/clang/www/cxx_status.html
+++ b/clang/www/cxx_status.html
@@ -280,7 +280,12 @@ <h2 id="cxx26">C++2c implementation status</h2>
  <tr>
   <td>Trivial Relocatability</pre></td>
   <td><a href="https://wg21.link/P2786">P2786R13</a></td>
-  <td class="unreleased" align="center">Clang 21</td>
+  <td class="partial" align="center">
+    <details>
+      <summary>Clang 21 (Partial)</summary>
+      The feature test macro (<code>__cpp_trivial_relocatability</code>) has not yet been set.
+    </details>
+  </td>
  </tr>
  <tr>
   <td><pre>#embed</pre></td>

>From 256b8c8d59c6fff5beb86bd56cb7c54f35a7eb88 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Tue, 25 Feb 2025 20:25:56 +0100
Subject: [PATCH 27/27] format

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

diff --git a/clang/lib/Frontend/InitPreprocessor.cpp b/clang/lib/Frontend/InitPreprocessor.cpp
index 39823959fbfe5..906b0faf44067 100644
--- a/clang/lib/Frontend/InitPreprocessor.cpp
+++ b/clang/lib/Frontend/InitPreprocessor.cpp
@@ -772,7 +772,7 @@ static void InitializeCPlusPlusFeatureTestMacros(const LangOptions &LangOpts,
   Builder.defineMacro("__cpp_pack_indexing", "202311L");
   Builder.defineMacro("__cpp_deleted_function", "202403L");
   Builder.defineMacro("__cpp_variadic_friend", "202403L");
-  //Builder.defineMacro("__cpp_trivial_relocatability", "202502L");
+  // Builder.defineMacro("__cpp_trivial_relocatability", "202502L");
 
   if (LangOpts.Char8)
     Builder.defineMacro("__cpp_char8_t", "202207L");



More information about the cfe-commits mailing list