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

via cfe-commits cfe-commits at lists.llvm.org
Tue Feb 25 08:35:53 PST 2025


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

>From d9bed1aa5dba11f2a1b6ef261085289f1fed208c 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/23] [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      |   2 +
 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, 835 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 dbd02ef7f8011..95b40edf447fe 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 c3ff7ebd88516..7e0956c0b891a 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -1129,6 +1129,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 598ae171b1389..db05a4476ccc4 100644
--- a/clang/include/clang/Basic/Builtins.td
+++ b/clang/include/clang/Basic/Builtins.td
@@ -2829,6 +2829,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 c513dab810d1f..84ee963e48ff6 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -1063,6 +1063,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 f4f1bc67724a1..6f2b4d33919a9 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -12415,6 +12415,10 @@ def err_builtin_invalid_arg_type: Error <
   "an 'int'|"
   "a vector of floating points}1 (was %2)">;
 
+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 397a5d95709fb..72dc9cf4ef96e 100644
--- a/clang/include/clang/Basic/TokenKinds.def
+++ b/clang/include/clang/Basic/TokenKinds.def
@@ -555,6 +555,8 @@ TYPE_TRAIT_2(__reference_converts_from_temporary, ReferenceConvertsFromTemporary
 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)
 
 // Embarcadero Expression Traits
 EXPRESSION_TRAIT(__is_lvalue_expr, IsLValueExpr, KEYCXX)
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 335258d597028..304ad6dd25476 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -164,6 +164,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;
@@ -3169,6 +3171,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 093e9a06b00ce..3c79e7d10761e 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -3976,20 +3976,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 5a3be1690f335..c8dd60bbe3a13 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -4449,6 +4449,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 7eff776882629..b80c8827ee839 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) {}
 
@@ -1529,7 +1532,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
@@ -1551,6 +1557,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) {
@@ -1578,8 +1587,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 8c11ec2e1fe24..77700056d8952 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -2862,6 +2862,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 a4df7275f18dc..3d0207e29a1e9 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -4775,6 +4775,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 e1dc728558def..ababb4f695098 100644
--- a/clang/lib/Frontend/InitPreprocessor.cpp
+++ b/clang/lib/Frontend/InitPreprocessor.cpp
@@ -757,7 +757,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");
@@ -768,6 +768,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 43db715ac6d70..3ed94fdf02012 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) {
@@ -2692,16 +2693,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(
@@ -3580,21 +3628,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
@@ -3808,13 +3854,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();
@@ -3850,7 +3922,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.
@@ -3923,9 +3996,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 0710542f5e938..11e4c2913c93c 100644
--- a/clang/lib/Parse/Parser.cpp
+++ b/clang/lib/Parse/Parser.cpp
@@ -513,6 +513,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 74f425d32648f..41235059b4df0 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -1875,6 +1875,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;
 }
@@ -2317,6 +2365,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 4b5351410f459..9ac4fd96c28d6 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -18318,11 +18318,19 @@ bool Sema::ActOnDuplicateDefinition(Decl *Prev, SkipBodyInfo &SkipBody) {
   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);
 
@@ -18340,6 +18348,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 664d48ccbc382..8214ea137b6e0 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -7221,6 +7221,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 =
@@ -7258,6 +7261,217 @@ void Sema::CheckCompletedCXXClass(Scope *S, CXXRecordDecl *Record) {
   }
 }
 
+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 34219e0235a74..379f2456e08f3 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -5107,6 +5107,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
@@ -5678,6 +5680,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 fcb7671ed92f0..dc6df0f5e468f 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -3690,6 +3690,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 7c469c1e112fc5c805a463833baf89c2fce93ee8 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/23] 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 6f2b4d33919a9..ff86f6a742bac 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -12416,8 +12416,9 @@ def err_builtin_invalid_arg_type: Error <
   "a vector of floating points}1 (was %2)">;
 
 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 32c3212e8d57b0578cdd1d601e5826eb654c14ad 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/23] 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 72dc9cf4ef96e..5846acff120fc 100644
--- a/clang/include/clang/Basic/TokenKinds.def
+++ b/clang/include/clang/Basic/TokenKinds.def
@@ -555,7 +555,7 @@ TYPE_TRAIT_2(__reference_converts_from_temporary, ReferenceConvertsFromTemporary
 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)
 
 // Embarcadero Expression Traits
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 1520d692059c52bc01255983afcc0f4e7d17693f 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/23] 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 86295a3146510..d9f1f56cb2ed8 100644
--- a/clang/docs/LanguageExtensions.rst
+++ b/clang/docs/LanguageExtensions.rst
@@ -1826,6 +1826,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
@@ -3625,6 +3631,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 41235059b4df0..a9b0615c1c2ab 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -1882,15 +1882,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();
@@ -1903,10 +1903,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 1cd0511aebca4df0751756a3ac794b3ec22f047f 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/23] 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 95b40edf447fe..266b8e7fdd33e 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 ababb4f695098..b47c8fc027ab8 100644
--- a/clang/lib/Frontend/InitPreprocessor.cpp
+++ b/clang/lib/Frontend/InitPreprocessor.cpp
@@ -757,7 +757,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 40d2733d18c29d5eacd38383f2e94a177152c45c 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/23] 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 8214ea137b6e0..aa681389f613e 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -7340,9 +7340,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();
 
@@ -7425,9 +7427,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 da8829488679965d8418d0dca2d2a6b93479d3bd 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/23] 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 77700056d8952..cdaa12ba205bb 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -2866,9 +2866,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 6bf15963698b4e71ddc1664fab2e6770bc04d7c6 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/23] 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 266b8e7fdd33e..ebee5d1d981d1 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 47ef5235eff8775eef3aa84ef1f5ebea362a1a6b 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/23] 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 84ee963e48ff6..a1d4478d58829 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -1057,6 +1057,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 3ed94fdf02012..2fbeaa705a35f 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -2711,6 +2711,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());
 }
 
@@ -2731,6 +2737,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 2e2fecc418504..eb10c750be8ed 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 765b0b6c2bd59834a61c414b99873f9d5ce5613f 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/23] 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 d9f1f56cb2ed8..31feefa154e4b 100644
--- a/clang/docs/LanguageExtensions.rst
+++ b/clang/docs/LanguageExtensions.rst
@@ -1645,6 +1645,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 699cbb17edca7..135436f8c365d 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -72,6 +72,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>`_.
+
 
 C++23 Feature Support
 ^^^^^^^^^^^^^^^^^^^^^
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 1694a69652dbb4e60d44325490f4ca2134fa5bdf 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/23] 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 31feefa154e4b..775c887c6841d 100644
--- a/clang/docs/LanguageExtensions.rst
+++ b/clang/docs/LanguageExtensions.rst
@@ -1827,11 +1827,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 a1d4478d58829..757682b84fc92 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -1073,7 +1073,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 cdaa12ba205bb..72fcb2fdc924f 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -2874,7 +2874,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 2fbeaa705a35f..b6be6e0c4fa7c 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -2747,7 +2747,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 a9b0615c1c2ab..fa25e08e7223a 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -1907,7 +1907,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 aa681389f613e..f7d2eb2b0b9f1 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -7261,15 +7261,16 @@ void Sema::CheckCompletedCXXClass(Scope *S, CXXRecordDecl *Record) {
   }
 }
 
-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);
@@ -7282,8 +7283,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())
@@ -7293,14 +7294,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())
@@ -7323,8 +7324,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())
@@ -7334,29 +7334,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()) {
@@ -7364,114 +7380,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 8c229166d8870b928a5f3d6e56ea48a316623427 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/23] 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 775c887c6841d..5f5f3700b7f9d 100644
--- a/clang/docs/LanguageExtensions.rst
+++ b/clang/docs/LanguageExtensions.rst
@@ -1828,11 +1828,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 757682b84fc92..1b6de40d75dfa 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -1058,7 +1058,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 304ad6dd25476..61a896106351b 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -3174,12 +3174,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 b6be6e0c4fa7c..8ad660bd061e2 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -2707,7 +2707,7 @@ bool Parser::isCXX2CTriviallyRelocatableKeyword() const {
   return isCXX2CTriviallyRelocatableKeyword(Tok);
 }
 
-void Parser::ParseOptionalCXX2CTriviallyRelocatableSpecifier(
+void Parser::ParseCXX2CTriviallyRelocatableSpecifier(
     TriviallyRelocatableSpecifier &TRS) {
   assert(isCXX2CTriviallyRelocatableKeyword() &&
          "expected a trivially_relocatable specifier");
@@ -2734,7 +2734,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");
 
@@ -3881,10 +3881,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)) {
@@ -3892,9 +3892,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 fa25e08e7223a..bda61a79740c8 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -1896,7 +1896,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 b577ebd213e53c513a7a64ccd24930df37d7d5a6 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/23] 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 f7d2eb2b0b9f1..b9af532e0e614 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -7339,12 +7339,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();
@@ -7364,12 +7373,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;
@@ -7380,6 +7393,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()) {
@@ -7388,9 +7403,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;
@@ -7401,30 +7419,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;
   }
@@ -7433,23 +7459,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;
@@ -7458,6 +7477,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(
@@ -7465,18 +7486,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 bc047659b98902e6e4ae16356581136c31686433 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/23] 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 b9af532e0e614..316f8b54703ca 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -7460,7 +7460,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 ebdea9cc7e64186178230e7d0dc47bc123fba84a 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/23] * 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 ebee5d1d981d1..0c9f7f4d4bafc 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 3c79e7d10761e..2c20defe868ff 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -3995,9 +3995,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 316f8b54703ca..04c29accceacf 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -7221,8 +7221,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;
@@ -7261,82 +7260,116 @@ void Sema::CheckCompletedCXXClass(Scope *S, CXXRecordDecl *Record) {
   }
 }
 
-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]
@@ -7348,12 +7381,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();
@@ -7395,53 +7429,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) {
@@ -7468,41 +7464,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 d66d43757c5ecdeb81812a03a39750e9345ec268 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/23] 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 0c9f7f4d4bafc..b1126def3e09a 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 b80c8827ee839..9f5d9934ae3de 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -2124,6 +2124,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 04c29accceacf..fd985a2282cab 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -7400,13 +7400,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) {
@@ -7435,7 +7428,7 @@ static bool isEligibleForTrivialRelocation(Sema &SemaRef, CXXRecordDecl *D) {
   }
 
   // ...has a deleted destructor
-  return !hasDeletedDestructor(D);
+  return !D->hasDeletedDestructor();
 }
 
 // [C++26][class.prop]
@@ -7461,7 +7454,7 @@ static bool isEligibleForReplacement(Sema &SemaRef, CXXRecordDecl *D) {
   }
 
   // it has a deleted destructor.
-  return !hasDeletedDestructor(D);
+  return !D->hasDeletedDestructor();
 }
 
 void Sema::CheckCXX2CRelocatableAndReplaceable(CXXRecordDecl *D) {
@@ -7511,9 +7504,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();
   }();
@@ -7533,9 +7526,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 ca4798631f09ac74c20adc62093a05b37c539df6 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/23] __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 5f5f3700b7f9d..01c1b55e8cd90 100644
--- a/clang/docs/LanguageExtensions.rst
+++ b/clang/docs/LanguageExtensions.rst
@@ -1825,12 +1825,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 72fcb2fdc924f..27c35ff738728 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -2848,7 +2848,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()) {
@@ -2864,10 +2864,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 4d21645dbc3599e041dc5a717ce0e0f79ddea9c1 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/23] 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 b1126def3e09a..0b2ef091230da 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 65c91ccd75ecc..23240c65b436c 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -1798,6 +1798,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 61a896106351b..44c58cf361cf6 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -3174,12 +3174,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 2c20defe868ff..c14decc0eb7ac 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -3976,19 +3976,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 8ad660bd061e2..7907a66305097 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -2707,8 +2707,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");
 
@@ -2717,7 +2716,7 @@ void Parser::ParseCXX2CTriviallyRelocatableSpecifier(
                               : diag::ext_relocatable_keyword)
       << /*relocatable*/ 0;
 
-  TRS = Actions.ActOnTriviallyRelocatableSpecifier(ConsumeToken());
+  TRS = ConsumeToken();
 }
 
 bool Parser::isCXX2CReplaceableKeyword(Token Tok) const {
@@ -2734,7 +2733,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");
 
@@ -2743,7 +2742,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
@@ -3868,8 +3867,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)) {
@@ -3877,22 +3876,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);
           }
@@ -3937,7 +3935,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 9ac4fd96c28d6..da6f9e75d571d 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -18318,19 +18318,10 @@ bool Sema::ActOnDuplicateDefinition(Decl *Prev, SkipBodyInfo &SkipBody) {
   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);
 
@@ -18349,11 +18340,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 fd985a2282cab..93a631978f6fb 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -7463,9 +7463,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 dc6df0f5e468f..fcb7671ed92f0 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -3690,10 +3690,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 aa89603bc8fca5f93aefb7afc8cd2a4004b61a06 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/23] 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 93a631978f6fb..c7080322838c7 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -7381,7 +7381,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;
@@ -7390,7 +7390,6 @@ static bool isDefaultMovable(Sema &SemaRef, CXXRecordDecl *D) {
           SemaRef, D, /*AllowUserDefined=*/false))
     return false;
 
-  const auto *Dtr = D->getDestructor();
   if (!Dtr)
     return true;
 
@@ -7426,9 +7425,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]
@@ -7452,9 +7449,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) {
@@ -7466,6 +7461,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 = [&] {
@@ -7484,9 +7480,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;
   };
 
@@ -7498,6 +7500,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;
@@ -7520,6 +7525,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 53bcf2ec029d2a842c8b18c000ec75288438d1e4 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/23] 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 7e0956c0b891a..0c793497a9a64 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -1129,7 +1129,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 27c35ff738728..13ecf8c7af873 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -2848,7 +2848,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()) {
@@ -2862,7 +2862,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 7907a66305097..fe58d8606cbd3 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -3868,7 +3868,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)) {
@@ -3885,19 +3885,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()) {
@@ -3935,7 +3935,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.
@@ -4010,7 +4010,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 bda61a79740c8..7c5c13718db7d 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -1896,7 +1896,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 c7080322838c7..98e44225507fe 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -7421,7 +7421,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;
   }
@@ -7458,10 +7458,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 = [&] {
@@ -7504,7 +7500,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
@@ -7529,7 +7525,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 379f2456e08f3..4d7458185795b 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -5681,7 +5681,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 f2c2fbc0a5806f58cee1a7a62b8b3192300e38ea 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/23] 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 d275873651786..f332f647cf588 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -624,6 +624,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 0b2ef091230da..0ed253c28d546 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 0c793497a9a64..9708b01dc6de6 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -1126,13 +1126,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 c14decc0eb7ac..7f1d6fbc0a78f 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -3992,7 +3992,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);
 
@@ -8402,6 +8403,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 4a791316a6269..19d1e122836f2 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -1681,6 +1681,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 9f5d9934ae3de..150a99fb16756 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) {}
 
@@ -1532,10 +1529,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
@@ -1557,9 +1550,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) {
@@ -1587,11 +1577,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 13ecf8c7af873..65441561b13fd 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -2839,59 +2839,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 7c5c13718db7d..76c5c4c4e8328 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -1895,8 +1895,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 98e44225507fe..22fb327a65f23 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -7221,8 +7221,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 =
@@ -7260,8 +7258,9 @@ void Sema::CheckCompletedCXXClass(Scope *S, CXXRecordDecl *Record) {
   }
 }
 
-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();
 
@@ -7312,7 +7311,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);
@@ -7324,8 +7324,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(
@@ -7344,7 +7344,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());
 
@@ -7356,9 +7357,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())
@@ -7381,7 +7381,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;
@@ -7390,6 +7390,8 @@ static bool isDefaultMovable(Sema &SemaRef, CXXRecordDecl *D, CXXDestructorDecl*
           SemaRef, D, /*AllowUserDefined=*/false))
     return false;
 
+  CXXDestructorDecl *Dtr = D->getDestructor();
+
   if (!Dtr)
     return true;
 
@@ -7401,7 +7403,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();
@@ -7409,8 +7412,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;
   }
 
@@ -7421,23 +7424,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;
   }
 
@@ -7446,15 +7449,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());
 
@@ -7476,29 +7482,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;
@@ -7511,19 +7508,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();
@@ -7536,7 +7528,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 4d7458185795b..563ee98603f30 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"
@@ -5258,6 +5259,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) {
@@ -5677,13 +5753,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 6c6eee7308cf2a135e3a9cd3a820b103ef10c8e2 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/23] 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 0ed253c28d546..0466e5fc07851 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 ea0b7e4ba06720a8843f79f1aed537ce993bb96d 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/23] 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 563ee98603f30..4ad256a351b4a 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -5273,6 +5273,9 @@ bool Sema::IsCXXTriviallyRelocatableType(QualType Type) {
 
   QualType BaseElementType = getASTContext().getBaseElementType(Type);
 
+  if(Type->isVariableArrayType())
+      return false;
+
   if (BaseElementType.hasNonTrivialObjCLifetime())
     return false;
 
@@ -5301,6 +5304,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;



More information about the cfe-commits mailing list