[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 11:26:48 PST 2025


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

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

---
 .../clang/AST/CXXRecordDeclDefinitionBits.def |   8 +
 clang/include/clang/AST/DeclCXX.h             |  81 +++++-
 clang/include/clang/AST/Type.h                |   4 +
 clang/include/clang/Basic/Builtins.td         |   6 +
 .../clang/Basic/DiagnosticParseKinds.td       |   3 +
 .../clang/Basic/DiagnosticSemaKinds.td        |   4 +
 clang/include/clang/Basic/TokenKinds.def      |   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 51301d95e55b9..5c4450ea85f4c 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -12439,6 +12439,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 476abe86cb2d2..4c9e4aa0c8b59 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -4008,20 +4008,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 96d4e9732ed0e..38de0a9fb6a7b 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 f9926c6b4adab..d271f3973849c 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 285bd27a35a76..dd4ac096ed9bd 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -18321,11 +18321,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);
 
@@ -18343,6 +18351,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 209abb36a216db58a072295313eff71c73dc19db Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Thu, 20 Feb 2025 16:43:55 +0100
Subject: [PATCH 02/27] add sema tests for __builtin_trivially_relocate

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

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 5c4450ea85f4c..baccbc4a86648 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -12440,8 +12440,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 f73909e4d83bd229b889f80ae046a9d840622166 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Thu, 20 Feb 2025 16:53:56 +0100
Subject: [PATCH 03/27] rename __is_cpp_trivially_relocatable

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

diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def
index 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 c42070fd157344614df25f4205264080a486f2f6 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Thu, 20 Feb 2025 17:09:02 +0100
Subject: [PATCH 04/27] add docs and fix variable names in SemaChecking

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

diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index 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 d271f3973849c..0dce40d691366 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 36a303f224b86fefcad1eb96dd435437bce1e14c Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Thu, 20 Feb 2025 18:38:22 +0100
Subject: [PATCH 05/27] restore assert, add codegen test

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

diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h
index 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 23ac9f11309fbed2ed851e1c16905b5a1ad28dc0 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Fri, 21 Feb 2025 10:31:01 +0100
Subject: [PATCH 06/27] address comments, fix tests

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

diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 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 9f5b40c337b2f4506caa7f06e6f2dd9cff5c515d Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Fri, 21 Feb 2025 10:32:12 +0100
Subject: [PATCH 07/27] address comments

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

diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 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 e2a2bf959f68092d2d469d078c53819a7f4a2d55 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Fri, 21 Feb 2025 10:33:09 +0100
Subject: [PATCH 08/27] format

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

diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h
index 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 1cec0f730a9fab57ef1f6b7414e7f3c72316df24 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Fri, 21 Feb 2025 10:57:11 +0100
Subject: [PATCH 09/27] add extension warning and tests

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

diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td
index 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 9debd5e0c6a6809f4963886e17a5221219ec683e Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Fri, 21 Feb 2025 11:12:22 +0100
Subject: [PATCH 10/27] changelog, docs, C++03 tests

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

diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index 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 3a0eab66fd584..6d5308feab73d 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 c726dfe357b76ec32ef44b2b6d7179469cac1b34 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Fri, 21 Feb 2025 20:10:59 +0100
Subject: [PATCH 11/27] rewrite everything

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

diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index 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 0dce40d691366..6da2451a483ad 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 706154574c098bd87408d9e5df2d2500db2c6bc6 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Mon, 24 Feb 2025 13:48:16 +0100
Subject: [PATCH 12/27] address more feedback

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

diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index 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 6da2451a483ad..424db00a5417b 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 db621bae7d1968bd9933bfdf7f8610704016fafa Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Mon, 24 Feb 2025 14:06:10 +0100
Subject: [PATCH 13/27] Add comments

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

diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 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 16f2fefc4c62fdb4b322a2143ab07564bb36dd15 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Mon, 24 Feb 2025 14:10:41 +0100
Subject: [PATCH 14/27] Fix volatile test

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

diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 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 3f9b08e562198dd49c2b9615f10bcba3b24ff7c9 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Mon, 24 Feb 2025 16:39:47 +0100
Subject: [PATCH 15/27] * Use overload on special members * fix tests (class
 with a const member are not implicitly relocatable) * Factorize some code to
 only compute default-movable once

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

diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h
index 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 4c9e4aa0c8b59..0bb48820aed4a 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -4027,9 +4027,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 a84d37ebe2b229a9be48213c0a5de9ba3e4f8fb1 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Mon, 24 Feb 2025 16:58:55 +0100
Subject: [PATCH 16/27] Make hasDeletedDestructor a member

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

diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h
index 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 e12dd98b240d949e34072ef7dc88d63581c7117c Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Mon, 24 Feb 2025 17:15:22 +0100
Subject: [PATCH 17/27] __is_trivially_relocatable is true for c++26
 relocatable types

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

diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index 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 2123376b4806bbd0240fc5684dbe816c9dc352de Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Mon, 24 Feb 2025 18:23:43 +0100
Subject: [PATCH 18/27] Remove the specifier wrapper, stash the location in
 attributes

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

diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h
index 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 8bbd096bb1f72..e96fe2a06ef01 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 0bb48820aed4a..80d59be1e9939 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -4008,19 +4008,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 dd4ac096ed9bd..e035c37b3f1c0 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -18321,19 +18321,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);
 
@@ -18352,11 +18343,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 ee53387f591a1bb74dd228e06c21b15b89afef8d Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Tue, 25 Feb 2025 10:37:00 +0100
Subject: [PATCH 19/27] reduce destructor lookups

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

diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 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 94eb97852f97b9ca8b1df1a438a685b22989fc32 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Tue, 25 Feb 2025 11:10:48 +0100
Subject: [PATCH 20/27] cleanup

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

diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 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 424db00a5417b..bba5ddc3e79f3 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 fd884f13f6b6df5170e8476771c82988433e547e Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Tue, 25 Feb 2025 10:15:24 +0100
Subject: [PATCH 21/27] Defer the computation of relocatability/replaceability
 to when the trait is queried, in order to avoid a performance regression.

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

diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index 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 80d59be1e9939..000690750dc06 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -4024,7 +4024,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);
 
@@ -8439,6 +8440,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 bba5ddc3e79f3..faed9b14c3423 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 ff7273a9016f557ae63fc640f68af768bc548e4d Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Tue, 25 Feb 2025 13:43:37 +0100
Subject: [PATCH 22/27] remove dead code

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

diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h
index 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 102c1524ef76484134e75f6c970642417ce22660 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Tue, 25 Feb 2025 17:35:22 +0100
Subject: [PATCH 23/27] Add VLA tests

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

diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index 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;

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

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

diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index 4ad256a351b4a..1e8ca90f89d3e 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -5273,8 +5273,8 @@ bool Sema::IsCXXTriviallyRelocatableType(QualType Type) {
 
   QualType BaseElementType = getASTContext().getBaseElementType(Type);
 
-  if(Type->isVariableArrayType())
-      return false;
+  if (Type->isVariableArrayType())
+    return false;
 
   if (BaseElementType.hasNonTrivialObjCLifetime())
     return false;
@@ -5305,8 +5305,8 @@ bool Sema::IsCXXReplaceableType(QualType Type) {
   if (Type.isConstQualified() || Type.isVolatileQualified())
     return false;
 
-  if(Type->isVariableArrayType())
-      return false;
+  if (Type->isVariableArrayType())
+    return false;
 
   QualType BaseElementType =
       getASTContext().getBaseElementType(Type.getUnqualifiedType());

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

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

diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index 1e8ca90f89d3e..f55435200572a 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -5322,15 +5322,21 @@ bool Sema::IsCXXReplaceableType(QualType Type) {
 static bool IsTriviallyRelocatableType(Sema &SemaRef, QualType T) {
   QualType BaseElementType = SemaRef.getASTContext().getBaseElementType(T);
 
-  if (SemaRef.IsCXXTriviallyRelocatableType(T))
-    return true;
-
-  if (BaseElementType->isIncompleteType() || !BaseElementType->isObjectType())
+  if (BaseElementType->isIncompleteType())
+    return false;
+  if (!BaseElementType->isObjectType())
     return false;
 
+  if (const auto *RD = BaseElementType->getAsCXXRecordDecl();
+      RD && !RD->isPolymorphic() && IsCXXTriviallyRelocatableType(SemaRef, RD))
+    return true;
+
   if (const auto *RD = BaseElementType->getAsRecordDecl())
     return RD->canPassInRegisters();
 
+  if (BaseElementType.isTriviallyCopyableType(SemaRef.getASTContext()))
+    return true;
+
   switch (T.isNonTrivialToPrimitiveDestructiveMove()) {
   case QualType::PCK_Trivial:
     return !T.isDestructedType();
diff --git a/clang/test/SemaCXX/attr-trivial-abi.cpp b/clang/test/SemaCXX/attr-trivial-abi.cpp
index c2c2c22c95bb2..e018ccda2d8d9 100644
--- a/clang/test/SemaCXX/attr-trivial-abi.cpp
+++ b/clang/test/SemaCXX/attr-trivial-abi.cpp
@@ -28,18 +28,25 @@ static_assert(__is_trivially_relocatable(S1), "");
 struct __attribute__((trivial_abi)) S3 { // expected-warning {{'trivial_abi' cannot be applied to 'S3'}} expected-note {{is polymorphic}}
   virtual void m();
 };
-static_assert(__is_trivially_relocatable(S3), "");
+static_assert(!__is_trivially_relocatable(S3), "");
 
 struct S3_2 {
   virtual void m();
 } __attribute__((trivial_abi)); // expected-warning {{'trivial_abi' cannot be applied to 'S3_2'}} expected-note {{is polymorphic}}
-static_assert(__is_trivially_relocatable(S3_2), "");
+static_assert(!__is_trivially_relocatable(S3_2), "");
 
 struct __attribute__((trivial_abi)) S3_3 { // expected-warning {{'trivial_abi' cannot be applied to 'S3_3'}} expected-note {{has a field of a non-trivial class type}}
   S3_3(S3_3 &&);
   S3_2 s32;
 };
+#ifdef __ORBIS__
+// The ClangABI4OrPS4 calling convention kind passes classes in registers if the
+// copy constructor is trivial for calls *or deleted*, while other platforms do
+// not accept deleted constructors.
+static_assert(__is_trivially_relocatable(S3_3), "");
+#else
 static_assert(!__is_trivially_relocatable(S3_3), "");
+#endif
 
 // Diagnose invalid trivial_abi even when the type is templated because it has a non-trivial field.
 template <class T>
@@ -111,7 +118,11 @@ struct __attribute__((trivial_abi)) CopyMoveDeleted { // expected-warning {{'tri
   CopyMoveDeleted(const CopyMoveDeleted &) = delete;
   CopyMoveDeleted(CopyMoveDeleted &&) = delete;
 };
+#ifdef __ORBIS__
+static_assert(__is_trivially_relocatable(CopyMoveDeleted), "");
+#else
 static_assert(!__is_trivially_relocatable(CopyMoveDeleted), "");
+#endif
 
 struct __attribute__((trivial_abi)) S18 { // expected-warning {{'trivial_abi' cannot be applied to 'S18'}} expected-note {{copy constructors and move constructors are all deleted}}
   CopyMoveDeleted a;

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

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

diff --git a/clang/lib/Frontend/InitPreprocessor.cpp b/clang/lib/Frontend/InitPreprocessor.cpp
index b47c8fc027ab8..9d5e3df6d94c2 100644
--- a/clang/lib/Frontend/InitPreprocessor.cpp
+++ b/clang/lib/Frontend/InitPreprocessor.cpp
@@ -768,7 +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");
+  //Builder.defineMacro("__cpp_trivial_relocatability", "202502L");
 
   if (LangOpts.Char8)
     Builder.defineMacro("__cpp_char8_t", "202207L");
diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html
index eb10c750be8ed..e2053560ff61a 100755
--- a/clang/www/cxx_status.html
+++ b/clang/www/cxx_status.html
@@ -280,7 +280,12 @@ <h2 id="cxx26">C++2c implementation status</h2>
  <tr>
   <td>Trivial Relocatability</pre></td>
   <td><a href="https://wg21.link/P2786">P2786R13</a></td>
-  <td class="unreleased" align="center">Clang 21</td>
+  <td class="partial" align="center">
+    <details>
+      <summary>Clang 21 (Partial)</summary>
+      The feature test macro (<code>__cpp_trivial_relocatability</code>) has not yet been set.
+    </details>
+  </td>
  </tr>
  <tr>
   <td><pre>#embed</pre></td>

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

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

diff --git a/clang/lib/Frontend/InitPreprocessor.cpp b/clang/lib/Frontend/InitPreprocessor.cpp
index 9d5e3df6d94c2..2473bb42e20a8 100644
--- a/clang/lib/Frontend/InitPreprocessor.cpp
+++ b/clang/lib/Frontend/InitPreprocessor.cpp
@@ -768,7 +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");
+  // Builder.defineMacro("__cpp_trivial_relocatability", "202502L");
 
   if (LangOpts.Char8)
     Builder.defineMacro("__cpp_char8_t", "202207L");



More information about the cfe-commits mailing list