Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[Clang] Implement the core language parts of P2786 - Trivial relocation #127636

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 30 commits into from
May 6, 2025

Conversation

cor3ntin
Copy link
Contributor

@cor3ntin cor3ntin commented Feb 18, 2025

This adds

  • The parsing of trivially_relocatable_if_eligible, replaceable_if_eligible keywords

  • __builtin_trivially_relocate, implemented in terms of memmove. In the future this should

    • Add the appropriate start/end lifetime markers that llvm does not have (start_lifetime_as)
    • Add support for ptrauth when that's upstreamed
  • the __builtin_is_cpp_trivially_relocatable and __builtin_is_replaceable traits

Fixes #127609

Copy link

github-actions bot commented Feb 18, 2025

⚠️ C/C++ code formatter, clang-format found issues in your code. ⚠️

You can test this locally with the following command:
git-clang-format --diff HEAD~1 HEAD --extensions h,cpp -- clang/test/CodeGenCXX/cxx2c-trivially-relocatable.cpp clang/test/Parser/cxx2c-trivially-relocatable.cpp clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp clang/include/clang/AST/ASTContext.h clang/include/clang/AST/DeclCXX.h clang/include/clang/AST/Type.h clang/include/clang/Parse/Parser.h clang/include/clang/Sema/Sema.h clang/lib/AST/ASTContext.cpp clang/lib/AST/Decl.cpp clang/lib/AST/DeclCXX.cpp clang/lib/AST/Type.cpp clang/lib/CodeGen/CGBuiltin.cpp clang/lib/Frontend/InitPreprocessor.cpp clang/lib/Parse/ParseDeclCXX.cpp clang/lib/Parse/Parser.cpp clang/lib/Sema/SemaChecking.cpp clang/lib/Sema/SemaDecl.cpp clang/lib/Sema/SemaDeclCXX.cpp clang/lib/Sema/SemaExprCXX.cpp clang/test/SemaCXX/attr-trivial-abi.cpp clang/test/SemaCXX/ptrauth-triviality.cpp
View the diff from clang-format here.
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index 4a828e0b8..8bdc2300b 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -5662,8 +5662,8 @@ static bool IsTriviallyRelocatableType(Sema &SemaRef, QualType T) {
   if (!BaseElementType->isObjectType())
     return false;
 
-  if(T.hasAddressDiscriminatedPointerAuth())
-      return false;
+  if (T.hasAddressDiscriminatedPointerAuth())
+    return false;
 
   if (const auto *RD = BaseElementType->getAsCXXRecordDecl();
       RD && !RD->isPolymorphic() && IsCXXTriviallyRelocatableType(SemaRef, RD))

@cor3ntin cor3ntin force-pushed the P2786_cleanup branch 3 times, most recently from 4fb6c28 to 9815133 Compare February 20, 2025 17:41
@cor3ntin
Copy link
Contributor Author

cor3ntin commented Feb 20, 2025

TODO

  • extension warnings for the keywords
  • changelog
  • cxx_status page

@cor3ntin cor3ntin changed the title [Clang][WIP] Trivial relocation [Clang] Implement the core language parts of P2786 - Trivial relocation Feb 20, 2025
@cor3ntin cor3ntin marked this pull request as ready for review February 20, 2025 17:48
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:codegen IR generation bugs: mangling, exceptions, etc. labels Feb 20, 2025
@cor3ntin cor3ntin added c++26 and removed clang Clang issues not falling into any other category clang:codegen IR generation bugs: mangling, exceptions, etc. labels Feb 20, 2025
@llvmbot
Copy link
Member

llvmbot commented Feb 20, 2025

@llvm/pr-subscribers-clang-codegen

@llvm/pr-subscribers-clang

Author: cor3ntin (cor3ntin)

Changes

This adds

  • The parsing of trivially_relocatable_if_eligible, replaceable_if_eligible keywords

  • __builtin_trivially_relocate, implemented in terms of memmove. In the future this should

    • Add the appropriate start/end lifetime markers that llvm does not have (start_lifetime_as)
    • Add support for ptrauth when that's upstreamed
  • the __builtin_is_cpp_trivially_relocatable and __builtin_is_replaceable traits


Patch is 52.91 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/127636.diff

25 Files Affected:

  • (modified) clang/docs/LanguageExtensions.rst (+21)
  • (modified) clang/include/clang/AST/CXXRecordDeclDefinitionBits.def (+8)
  • (modified) clang/include/clang/AST/DeclCXX.h (+77-2)
  • (modified) clang/include/clang/AST/Type.h (+4)
  • (modified) clang/include/clang/Basic/Builtins.td (+6)
  • (modified) clang/include/clang/Basic/DiagnosticParseKinds.td (+3)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+5)
  • (modified) clang/include/clang/Basic/TokenKinds.def (+2)
  • (modified) clang/include/clang/Parse/Parser.h (+13)
  • (modified) clang/include/clang/Sema/Sema.h (+14-5)
  • (modified) clang/lib/AST/Decl.cpp (+1)
  • (modified) clang/lib/AST/DeclCXX.cpp (+16-4)
  • (modified) clang/lib/AST/Type.cpp (+24)
  • (modified) clang/lib/CodeGen/CGBuiltin.cpp (+1)
  • (modified) clang/lib/Frontend/InitPreprocessor.cpp (+2-1)
  • (modified) clang/lib/Parse/ParseDeclCXX.cpp (+97-24)
  • (modified) clang/lib/Parse/Parser.cpp (+2)
  • (modified) clang/lib/Sema/SemaChecking.cpp (+51)
  • (modified) clang/lib/Sema/SemaDecl.cpp (+20-5)
  • (modified) clang/lib/Sema/SemaDeclCXX.cpp (+214)
  • (modified) clang/lib/Sema/SemaExprCXX.cpp (+6)
  • (modified) clang/lib/Sema/SemaTemplateInstantiate.cpp (+4)
  • (added) clang/test/CodeGenCXX/cxx2c-trivially-relocatable.cpp (+16)
  • (added) clang/test/Parser/cxx2c-trivially-relocatable.cpp (+8)
  • (added) clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp (+272)
diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index 2a956ad5b2909..7e4e71299eb80 100644
--- a/clang/docs/LanguageExtensions.rst
+++ b/clang/docs/LanguageExtensions.rst
@@ -1825,6 +1825,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
@@ -3624,6 +3630,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/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 266b93a64a390..4d578e3401456 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.
@@ -717,11 +748,18 @@ 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");
+    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
@@ -1905,6 +1973,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 1d9743520654e..8eb373cdd942a 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 0e5df338dd2e5..237fa3a46f8c8 100644
--- a/clang/include/clang/Basic/Builtins.td
+++ b/clang/include/clang/Basic/Builtins.td
@@ -2823,6 +2823,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 f10af8f5bd6b2..2292afe5f9ecb 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -12427,6 +12427,11 @@ 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 <
+  "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">;
 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..5846acff120fc 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(__builtin_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 c55b964650323..4fd14d2b93636 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -3960,20 +3960,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 610207cf8b9a4..dd45aa9c8d5dc 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -4445,6 +4445,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 d57f491a20c8e..8e7e714960321 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -4762,6 +4762,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 77833f5d1defb..48140a6375eb0 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..3...
[truncated]

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:codegen IR generation bugs: mangling, exceptions, etc. labels Feb 21, 2025
@@ -2862,6 +2862,30 @@ bool QualType::isTriviallyRelocatableType(const ASTContext &Context) const {
}
}

bool QualType::isCppTriviallyRelocatableType(const ASTContext &Context) const {
QualType BaseElementType = Context.getBaseElementType(*this);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Iirc getBaseElementTypeUnsafe() does less work, and we don’t care about cv qualifiers anyway here from what I can tell.

}

bool QualType::isReplaceableType(const ASTContext &Context) const {
if (isConstQualified())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don’t we also need to check isVolatileQualified() here?

struct UserDtr {
~UserDtr();
};

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A test w/ a deleted dtor would be nice


void test__builtin_trivially_relocate() {
struct S{ ~S();};
struct R {};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a test w/ __builtin_trivially_relocate() in a template, particularly w/ a dependent src/dest argument.

SourceLocation getLocation() const { return Loc; }

private:
SourceLocation Loc;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there intent for this to 'have' more to it? At the moment, this seems like it has weak justification over a SourceLocation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It used to have more. But I kept it because I felt a string type was cleaner than a bunch of lone SourceLocations. I'd rather keep it, unless you feel strongly about it

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like a lot of 'overhead' mentally to have a type here that all it does is contain a single thing? But I guess I don't feel super strongly.

Comment on lines 1830 to 1833
* ``__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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we introduce another builtin for this instead of fixing the existing one?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like a built in that has the standard behavior.
I think there is an interesting question as to whether the existing builtin should be modified to return true
if either a type is trivial_abi or relocatable by the standard. I think this would make sense

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it make a lot more sense to deprecate marking types as trivial_abi that aren't trivially relocatable? AFIAICT that's a requirement anyways, which is why it has been added in the first place. Then we can just say "__is_trivially_relocatable returns only true for types that are according to the standard" instead of introducing a new builtin just for the old builtin to turn out to be more useful in the end anyways.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would be happy doing that but I'm afraid it would break existing code.
Right now

__is_cpp_trivially_relocatable => standard behavior
__is_trivially_relocatable => standard behavior + trivial_abi

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand what breakage you're afraid of. Sure, a deprecation warning would break stuff with -Werror, but that's nothing new. I'm not saying we should make __is_trivially_relocatable return false if only [[clang::trivial_abi]] is added. I'm saying we should fix the users code and warn (and eventually error) if they're not giving us the guarantee. I just don't see when we'd ever want to use __is_cpp_trivially_relocatable over __is_trivially_relocatable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

std::is_trivially_relocatable should not return true for a type that is not relocatable per the standard.

This gives us a few options, none of them great

  • Introduce a new trait so that we can model the standard behavior
  • Modify the existing trait to ignore [[clang::trivial_abi]] which will break code
  • Be not conforming

Having a warning does not solve the conformance issue.
I agree with you that we probably want

  • Restrict the set of types that can be [[clang::trivial_abi]] (ie warn if [[clang::trivial_abi]] is added to a non-relocatable type)
  • Converge to a single trait by deprecating the old trait.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corentin is right. Any attempt we make to modify __is_trivially_relocatable is going to either be non-standard or break [[clang::trivial_abi]].

SO, we'd want to choose which we'd want to do. I very strongly don't believe we can be non-standard here, this is an important standardized feature, and clang::trivial_abi isn't any better.

I am ok with us modifying clang::trivial_abi over a number of releases(like 2-3 releases!) to make it be the same as the standards feature, but only if we can come to an agreement with the folks who are already using it.

SO in short, I think combining these builtins right now is untenable. Long-term, it is possible but only if we are willing/able to significantly break clang::trivial_abi.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made an issue for that #128725

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

std::is_trivially_relocatable should not return true for a type that is not relocatable per the standard.

Why? It's our extension to define. Am I missing something that makes annotating a type [[clang::trivial_abi]] instead of trivially_relocatable_if_eligible [[clang::trivial_abi]] significantly different? AFAIK having a type marked [[clang::trivial_abi]] requires it to be trivially relocatable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK having a type marked [[clang::trivial_abi]] requires it to be trivially relocatable.

It's not exactly the same semantics.

In particular, the standard traits allow for polymorphic types, any code that would do a memcpy without checking for that would lead to nasty UB.

How special members contribute to relocatability is also different, the standard requires non-deleted assignment.
That would also break code or be forever non-conforming and portable.

return !Dtr->isDeleted();
}

static bool hasDeletedDestructor(CXXRecordDecl *D) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to make hasDeletedDestructor a method of CXXRecordDecl? Maybe it will end up bein useful around codebase.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 3879 to 3902
if (isCXX2CTriviallyRelocatableKeyword(Tok)) {
if (TriviallyRelocatable.isSet()) {
auto Skipped = Tok;
ConsumeToken();
Diag(Skipped, diag::err_duplicate_class_relocation_specifier)
<< /*trivial_relocatable*/ 0
<< TriviallyRelocatable.getLocation();
} else {
ParseCXX2CTriviallyRelocatableSpecifier(TriviallyRelocatable);
}
continue;
} else if (isCXX2CReplaceableKeyword(Tok)) {
if (Replacable.isSet()) {
auto Skipped = Tok;
ConsumeToken();
Diag(Skipped, diag::err_duplicate_class_relocation_specifier)
<< /*replaceable*/ 1 << Replacable.getLocation();
} else {
ParseCXX2CReplaceableSpecifier(Replacable);
}
continue;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a feeling that if TriviallyRelocatableSpecifier/ReplaceableSpecifier were not templated and instead had some enum "Kind" field, the following two almost identical branches could be rewritten into:

BasicRelocatableOrReplaceableClassSpecifier ToComplain;


if (isCXX2CTriviallyRelocatableKeyword(Tok))
  ToComplain = TriviallyRelocatable;
else if (isCXX2CReplaceableKeyword(Tok))
  ToComplain = Replacable;

if (ToComplain.isSet()) {
    auto Skipped = Tok;
    ConsumeToken();
    Diag(Skipped, diag::err_duplicate_class_relocation_specifier)
    << /*trivial_relocatable*/ ToComplain.getKind()
    << TriviallyRelocatable.getLocation();
} else if (ToComplain.isNotEmpty()) {
  (ToComplain.getKind)? ParseCXX2CTriviallyRelocatableSpecifier(ToComplain) : ParseCXX2CReplaceableSpecifier(ToComplain);
}

that is an entirely optional suggestion but that amount of else/if/{} for a technically same code just makes it look sad.



class A trivially_relocatable_if_eligible {};
// cxx11-warning@-1 {{'trivially_relocatable_if_eligible' keyword is a C++2c extension}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm just curious, why don't we say and test C++26 instead of C++2c?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C++26 isn't 'official' yet, so our outward messaging is 2c at least for another few meetings.

@cor3ntin cor3ntin merged commit 300d402 into llvm:main May 6, 2025
5 of 6 checks passed
cor3ntin added a commit to cor3ntin/llvm-project that referenced this pull request May 7, 2025
The C++26 standard relocatable type traits has slightly different
semantics, so we introduced a new ``__builtin_is_cpp_trivially_relocatable``
when implementing trivial relocation in llvm#127636.

However, having multiple relocatable traits would be confusing
in the long run, so we deprecate the old trait.

As discussed in llvm#127636

`__builtin_is_cpp_trivially_relocatable` should be used instead.
GeorgeARM pushed a commit to GeorgeARM/llvm-project that referenced this pull request May 7, 2025
…on (llvm#127636)

This adds

- The parsing of `trivially_relocatable_if_eligible`,
`replaceable_if_eligible` keywords
- `__builtin_trivially_relocate`, implemented in terms of memmove. In
the future this should
- Add the appropriate start/end lifetime markers that llvm does not have
(`start_lifetime_as`)
     - Add support for ptrauth when that's upstreamed

- the `__builtin_is_cpp_trivially_relocatable` and
`__builtin_is_replaceable` traits


Fixes llvm#127609
cor3ntin added a commit that referenced this pull request May 7, 2025
The C++26 standard relocatable type traits has slightly different
semantics, so we introduced a new
``__builtin_is_cpp_trivially_relocatable`` when implementing trivial
relocation in #127636.

However, having multiple relocatable traits would be confusing in the
long run, so we deprecate the old trait.

As discussed in #127636

`__builtin_is_cpp_trivially_relocatable` should be used instead.

---------

Co-authored-by: Aaron Ballman <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c++26 clang:codegen IR generation bugs: mangling, exceptions, etc. clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[clang] Implement P2786R13 - Trivial Relocatability
9 participants