[Clang] Implement the core language parts of P2786 - Trivial relocation (#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 #127609
This commit is contained in:
cor3ntin 2025-05-06 14:13:32 +02:00 committed by GitHub
parent 3b4f9c5442
commit 300d4026f7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 1126 additions and 65 deletions

View File

@ -1680,6 +1680,7 @@ Static assert with user-generated message __cpp_static_assert >= 202306L C
Pack Indexing __cpp_pack_indexing C++26 C++03
``= delete ("should have a reason");`` __cpp_deleted_function C++26 C++03
Variadic Friends __cpp_variadic_friend C++26 C++03
Trivial Relocatability __cpp_trivial_relocatability C++26 C++03
--------------------------------------------- -------------------------------- ------------- -------------
Designated initializers (N494) C99 C89
Array & element qualification (N2607) C23 C89
@ -1861,8 +1862,15 @@ 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 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
objects of the provided type is known to be equivalent to comparing their
object representations. Note that types containing padding bytes are never
@ -3722,6 +3730,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``
-----------------------------------

View File

@ -106,6 +106,8 @@ C++2c Feature Support
^^^^^^^^^^^^^^^^^^^^^
- Implemented `P1061R10 Structured Bindings can introduce a Pack <https://wg21.link/P1061R10>`_.
- Implemented `P2786R13 Trivial Relocatability <https://wg21.link/P2786R13>`_.
- Implemented `P0963R3 Structured binding declaration as a condition <https://wg21.link/P0963R3>`_.

View File

@ -617,6 +617,20 @@ private:
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;

View File

@ -1550,6 +1550,9 @@ public:
/// 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; }

View File

@ -1133,9 +1133,6 @@ public:
/// 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;
/// Returns true if it is a class and it might be dynamic.
bool mayBeDynamicClass() const;

View File

@ -1819,6 +1819,22 @@ def Final : InheritableAttr {
let Documentation = [InternalOnly];
}
def TriviallyRelocatable : InheritableAttr {
let Spellings = [CustomKeyword<"trivially_relocatable_if_eligible">];
let SemaHandler = 0;
// Omitted from docs, since this is language syntax, not an attribute, as far
// as users are concerned.
let Documentation = [InternalOnly];
}
def Replaceable : InheritableAttr {
let Spellings = [CustomKeyword<"replaceable_if_eligible">];
let SemaHandler = 0;
// Omitted from docs, since this is language syntax, not an attribute, as far
// as users are concerned.
let Documentation = [InternalOnly];
}
def MinSize : InheritableAttr {
let Spellings = [Clang<"minsize">];
let Subjects = SubjectList<[Function, ObjCMethod], ErrorDiag>;

View File

@ -2853,6 +2853,12 @@ def MemMove : LibBuiltin<"string.h"> {
let AddBuiltinPrefixedAlias = 1;
}
def BuiltinTriviallyRelocate : Builtin {
let Spellings = ["__builtin_trivially_relocate"];
let Attributes = [FunctionWithBuiltinPrefix, CustomTypeChecking, NoThrow];
let Prototype = "void*(void*, void*, size_t)";
}
def StrCpy : LibBuiltin<"string.h"> {
let Spellings = ["strcpy"];
let Attributes = [NoThrow];

View File

@ -1065,12 +1065,24 @@ def ext_ms_abstract_keyword : ExtWarn<
"'abstract' keyword is a Microsoft extension">,
InGroup<MicrosoftAbstract>;
def ext_relocatable_keyword : ExtWarn<
"'%select{trivially_relocatable_if_eligible|replaceable_if_eligible}0' "
"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">;
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'">;

View File

@ -12664,6 +12664,11 @@ def err_builtin_invalid_arg_type: Error<
"%plural{0:|: }3"
"%plural{[0,3]:type|:types}1 (was %4)">;
def err_builtin_trivially_relocate_invalid_arg_type: Error <
"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<

View File

@ -557,8 +557,12 @@ TYPE_TRAIT_2(__reference_converts_from_temporary, ReferenceConvertsFromTemporary
// is not exposed to users.
TYPE_TRAIT_2(/*EmptySpellingName*/, IsDeducible, KEYCXX)
TYPE_TRAIT_1(__is_bitwise_cloneable, IsBitwiseCloneable, KEYALL)
TYPE_TRAIT_1(__builtin_is_cpp_trivially_relocatable, IsCppTriviallyRelocatable, KEYCXX)
TYPE_TRAIT_1(__builtin_is_replaceable, IsReplaceable, KEYCXX)
TYPE_TRAIT_1(__builtin_structured_binding_size, StructuredBindingSize, KEYCXX)
// Embarcadero Expression Traits
EXPRESSION_TRAIT(__is_lvalue_expr, IsLValueExpr, KEYCXX)
EXPRESSION_TRAIT(__is_rvalue_expr, IsRValueExpr, KEYCXX)

View File

@ -264,6 +264,8 @@ class Parser : public CodeCompletionHandler {
mutable IdentifierInfo *Ident_final;
mutable IdentifierInfo *Ident_GNU_final;
mutable IdentifierInfo *Ident_override;
mutable IdentifierInfo *Ident_trivially_relocatable_if_eligible;
mutable IdentifierInfo *Ident_replaceable_if_eligible;
// C++2a contextual keywords.
mutable IdentifierInfo *Ident_import;
@ -3196,6 +3198,16 @@ private:
SourceLocation FriendLoc);
bool isCXX11FinalKeyword() const;
bool isCXX2CTriviallyRelocatableKeyword(Token Tok) const;
bool isCXX2CTriviallyRelocatableKeyword() const;
void ParseCXX2CTriviallyRelocatableSpecifier(SourceLocation &TRS);
bool isCXX2CReplaceableKeyword(Token Tok) const;
bool isCXX2CReplaceableKeyword() const;
void ParseCXX2CReplaceableSpecifier(SourceLocation &MRS);
bool isClassCompatibleKeyword(Token Tok) const;
bool isClassCompatibleKeyword() const;
/// DeclaratorScopeObj - RAII object used in Parser::ParseDirectDeclarator to

View File

@ -4316,6 +4316,8 @@ public:
SourceLocation FinalLoc,
bool IsFinalSpelledSealed,
bool IsAbstract,
SourceLocation TriviallyRelocatable,
SourceLocation Replaceable,
SourceLocation LBraceLoc);
/// ActOnTagFinishDefinition - Invoked once we have finished parsing
@ -4323,6 +4325,9 @@ public:
void ActOnTagFinishDefinition(Scope *S, Decl *TagDecl,
SourceRange BraceRange);
ASTContext::CXXRecordDeclRelocationInfo
CheckCXX2CRelocatableAndReplaceable(const clang::CXXRecordDecl *D);
void ActOnTagFinishSkippedDefinition(SkippedDefinitionContext Context);
/// ActOnTagDefinitionError - Invoked when there was an unrecoverable
@ -8637,6 +8642,18 @@ public:
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

View File

@ -1688,6 +1688,24 @@ void ASTContext::getOverriddenMethods(
Overridden.append(OverDecls.begin(), OverDecls.end());
}
std::optional<ASTContext::CXXRecordDeclRelocationInfo>
ASTContext::getRelocationInfoForCXXRecord(const CXXRecordDecl *RD) const {
assert(RD);
CXXRecordDecl *D = RD->getDefinition();
auto it = RelocatableClasses.find(D);
if (it != RelocatableClasses.end())
return it->getSecond();
return std::nullopt;
}
void ASTContext::setRelocationInfoForCXXRecord(
const CXXRecordDecl *RD, CXXRecordDeclRelocationInfo Info) {
assert(RD);
CXXRecordDecl *D = RD->getDefinition();
assert(RelocatableClasses.find(D) == RelocatableClasses.end());
RelocatableClasses.insert({D, Info});
}
void ASTContext::addedLocalImportDecl(ImportDecl *Import) {
assert(!Import->getNextLocalImport() &&
"Import declaration already in the chain");

View File

@ -4491,6 +4491,7 @@ unsigned FunctionDecl::getMemoryFunctionKind() const {
case Builtin::BImempcpy:
return Builtin::BImempcpy;
case Builtin::BI__builtin_trivially_relocate:
case Builtin::BI__builtin_memmove:
case Builtin::BI__builtin___memmove_chk:
case Builtin::BImemmove:

View File

@ -1556,7 +1556,6 @@ void CXXRecordDecl::addedEligibleSpecialMemberFunction(const CXXMethodDecl *MD,
if (DD->isNoReturn())
data().IsAnyDestructorNoReturn = true;
}
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
@ -2144,6 +2143,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())

View File

@ -2896,29 +2896,6 @@ bool QualType::isTriviallyCopyConstructibleType(
/*IsCopyConstructible=*/true);
}
bool QualType::isTriviallyRelocatableType(const ASTContext &Context) const {
QualType BaseElementType = Context.getBaseElementType(*this);
if (BaseElementType->isIncompleteType()) {
return false;
} else if (!BaseElementType->isObjectType()) {
return false;
} else if (const auto *RD = BaseElementType->getAsRecordDecl()) {
return RD->canPassInRegisters();
} else if (BaseElementType.isTriviallyCopyableType(Context)) {
return true;
} else {
switch (isNonTrivialToPrimitiveDestructiveMove()) {
case PCK_Trivial:
return !isDestructedType();
case PCK_ARCStrong:
return true;
default:
return false;
}
}
}
bool QualType::isNonWeakInMRRWithObjCWeak(const ASTContext &Context) const {
return !Context.getLangOpts().ObjCAutoRefCount &&
Context.getLangOpts().ObjCWeak &&

View File

@ -4210,6 +4210,7 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
return RValue::get(Dest, *this);
}
case Builtin::BI__builtin_trivially_relocate:
case Builtin::BImemmove:
case Builtin::BI__builtin_memmove: {
Address Dest = EmitPointerWithAlignment(E->getArg(0));

View File

@ -772,6 +772,7 @@ static void InitializeCPlusPlusFeatureTestMacros(const LangOptions &LangOpts,
Builder.defineMacro("__cpp_pack_indexing", "202311L");
Builder.defineMacro("__cpp_deleted_function", "202403L");
Builder.defineMacro("__cpp_variadic_friend", "202403L");
// Builder.defineMacro("__cpp_trivial_relocatability", "202502L");
if (LangOpts.Char8)
Builder.defineMacro("__cpp_char8_t", "202207L");

View File

@ -2027,7 +2027,8 @@ void Parser::ParseClassSpecifier(tok::TokenKind TagTokKind,
(DSC != DeclSpecContext::DSC_association &&
getLangOpts().CPlusPlus && Tok.is(tok::colon)) ||
(isClassCompatibleKeyword() &&
(NextToken().is(tok::l_brace) || NextToken().is(tok::colon)))) {
(NextToken().is(tok::l_brace) || NextToken().is(tok::colon) ||
isClassCompatibleKeyword(NextToken())))) {
if (DS.isFriendSpecified()) {
// C++ [class.friend]p2:
// A class shall not be defined in a friend declaration.
@ -2046,15 +2047,15 @@ void Parser::ParseClassSpecifier(tok::TokenKind TagTokKind,
(NextToken().is(tok::l_square) ||
NextToken().is(tok::kw_alignas) ||
NextToken().isRegularKeywordAttribute() ||
isCXX11VirtSpecifier(NextToken()) != VirtSpecifiers::VS_None)) {
isCXX11VirtSpecifier(NextToken()) != VirtSpecifiers::VS_None ||
isCXX2CTriviallyRelocatableKeyword())) {
// We can't tell if this is a definition or reference
// until we skipped the 'final' and C++11 attribute specifiers.
TentativeParsingAction PA(*this);
// Skip the 'final', abstract'... keywords.
while (isClassCompatibleKeyword()) {
while (isClassCompatibleKeyword())
ConsumeToken();
}
// Skip C++11 attribute specifiers.
while (true) {
@ -2696,16 +2697,76 @@ 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::ParseCXX2CTriviallyRelocatableSpecifier(SourceLocation &TRS) {
assert(isCXX2CTriviallyRelocatableKeyword() &&
"expected a trivially_relocatable specifier");
Diag(Tok.getLocation(), getLangOpts().CPlusPlus26
? diag::warn_relocatable_keyword
: diag::ext_relocatable_keyword)
<< /*relocatable*/ 0;
TRS = 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::ParseCXX2CReplaceableSpecifier(SourceLocation &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 = 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();
/// '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;
VirtSpecifiers::Specifier Specifier = isCXX11VirtSpecifier(Tok);
return Specifier == VirtSpecifiers::VS_Final ||
Specifier == VirtSpecifiers::VS_GNU_Final ||
Specifier == VirtSpecifiers::VS_Sealed ||
Specifier == VirtSpecifiers::VS_Abstract;
}
bool Parser::isClassCompatibleKeyword() const {
return isClassCompatibleKeyword(Tok);
}
/// Parse a C++ member-declarator up to, but not including, the optional
/// brace-or-equal-initializer or pure-specifier.
bool Parser::ParseCXXMemberDeclaratorBeforeInitializer(
@ -3584,21 +3645,19 @@ void Parser::SkipCXXMemberSpecification(SourceLocation RecordLoc,
SourceLocation AttrFixitLoc,
unsigned TagType, Decl *TagDecl) {
// Skip the optional 'final' keyword.
if (getLangOpts().CPlusPlus && Tok.is(tok::identifier)) {
assert(isCXX11FinalKeyword() && "not a class definition");
while (isClassCompatibleKeyword())
ConsumeToken();
// Diagnose any C++11 attributes after 'final' keyword.
// We deliberately discard these attributes.
ParsedAttributes Attrs(AttrFactory);
CheckMisplacedCXX11Attribute(Attrs, AttrFixitLoc);
// Diagnose any C++11 attributes after 'final' keyword.
// We deliberately discard these attributes.
ParsedAttributes Attrs(AttrFactory);
CheckMisplacedCXX11Attribute(Attrs, AttrFixitLoc);
// This can only happen if we had malformed misplaced attributes;
// we only get called if there is a colon or left-brace after the
// attributes.
if (Tok.isNot(tok::colon) && Tok.isNot(tok::l_brace))
return;
}
// This can only happen if we had malformed misplaced attributes;
// we only get called if there is a colon or left-brace after the
// attributes.
if (Tok.isNot(tok::colon) && Tok.isNot(tok::l_brace))
return;
// Skip the base clauses. This requires actually parsing them, because
// otherwise we can't be sure where they end (a left brace may appear
@ -3812,13 +3871,38 @@ void Parser::ParseCXXMemberSpecification(SourceLocation RecordLoc,
SourceLocation AbstractLoc;
bool IsFinalSpelledSealed = false;
bool IsAbstract = false;
SourceLocation TriviallyRelocatable;
SourceLocation Replaceable;
// Parse the optional 'final' keyword.
if (getLangOpts().CPlusPlus && Tok.is(tok::identifier)) {
while (true) {
VirtSpecifiers::Specifier Specifier = isCXX11VirtSpecifier(Tok);
if (Specifier == VirtSpecifiers::VS_None)
if (Specifier == VirtSpecifiers::VS_None) {
if (isCXX2CTriviallyRelocatableKeyword(Tok)) {
if (TriviallyRelocatable.isValid()) {
auto Skipped = Tok;
ConsumeToken();
Diag(Skipped, diag::err_duplicate_class_relocation_specifier)
<< /*trivial_relocatable*/ 0 << TriviallyRelocatable;
} else {
ParseCXX2CTriviallyRelocatableSpecifier(TriviallyRelocatable);
}
continue;
}
if (isCXX2CReplaceableKeyword(Tok)) {
if (Replaceable.isValid()) {
auto Skipped = Tok;
ConsumeToken();
Diag(Skipped, diag::err_duplicate_class_relocation_specifier)
<< /*replaceable*/ 1 << Replaceable;
} else {
ParseCXX2CReplaceableSpecifier(Replaceable);
}
continue;
}
break;
}
if (isCXX11FinalKeyword()) {
if (FinalLoc.isValid()) {
auto Skipped = ConsumeToken();
@ -3854,7 +3938,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.isValid() || Replaceable.isValid()) &&
"not a class definition");
// Parse any C++11 attributes after 'final' keyword.
@ -3927,9 +4012,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, 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

View File

@ -537,6 +537,8 @@ void Parser::Initialize() {
Ident_sealed = nullptr;
Ident_abstract = nullptr;
Ident_override = nullptr;
Ident_trivially_relocatable_if_eligible = nullptr;
Ident_replaceable_if_eligible = nullptr;
Ident_GNU_final = nullptr;
Ident_import = nullptr;
Ident_module = nullptr;

View File

@ -1919,6 +1919,54 @@ static ExprResult BuiltinIsWithinLifetime(Sema &S, CallExpr *TheCall) {
<< 0;
return ExprError();
}
return TheCall;
}
static ExprResult BuiltinTriviallyRelocate(Sema &S, CallExpr *TheCall) {
if (S.checkArgCount(TheCall, 3))
return ExprError();
QualType 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 = Dest->getPointeeType();
if (S.RequireCompleteType(TheCall->getBeginLoc(), T,
diag::err_incomplete_type))
return ExprError();
if (T.isConstQualified() || !S.IsCXXTriviallyRelocatableType(T) ||
T->isIncompleteArrayType()) {
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(Dest);
QualType Src = TheCall->getArg(1)->getType();
if (Src.getCanonicalType() != Dest.getCanonicalType()) {
S.Diag(TheCall->getArg(1)->getExprLoc(),
diag::err_builtin_trivially_relocate_invalid_arg_type)
<< /*the same*/ 3;
return ExprError();
}
Expr *SizeExpr = TheCall->getArg(2);
ExprResult Size = S.DefaultLvalueConversion(SizeExpr);
if (Size.isInvalid())
return ExprError();
Size = S.tryConvertExprToType(Size.get(), S.getASTContext().getSizeType());
if (Size.isInvalid())
return ExprError();
SizeExpr = Size.get();
TheCall->setArg(2, SizeExpr);
return TheCall;
}
@ -2384,6 +2432,9 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
return BuiltinLaunder(*this, TheCall);
case Builtin::BI__builtin_is_within_lifetime:
return BuiltinIsWithinLifetime(*this, TheCall);
case Builtin::BI__builtin_trivially_relocate:
return BuiltinTriviallyRelocate(*this, TheCall);
case Builtin::BI__sync_fetch_and_add:
case Builtin::BI__sync_fetch_and_add_1:
case Builtin::BI__sync_fetch_and_add_2:

View File

@ -18480,11 +18480,10 @@ bool Sema::ActOnDuplicateDefinition(Scope *S, Decl *Prev,
return true;
}
void Sema::ActOnStartCXXMemberDeclarations(Scope *S, Decl *TagD,
SourceLocation FinalLoc,
bool IsFinalSpelledSealed,
bool IsAbstract,
SourceLocation LBraceLoc) {
void Sema::ActOnStartCXXMemberDeclarations(
Scope *S, Decl *TagD, SourceLocation FinalLoc, bool IsFinalSpelledSealed,
bool IsAbstract, SourceLocation TriviallyRelocatable,
SourceLocation Replaceable, SourceLocation LBraceLoc) {
AdjustDeclIfTemplate(TagD);
CXXRecordDecl *Record = cast<CXXRecordDecl>(TagD);
@ -18502,6 +18501,14 @@ void Sema::ActOnStartCXXMemberDeclarations(Scope *S, Decl *TagD,
? FinalAttr::Keyword_sealed
: FinalAttr::Keyword_final));
}
if (TriviallyRelocatable.isValid())
Record->addAttr(
TriviallyRelocatableAttr::Create(Context, TriviallyRelocatable));
if (Replaceable.isValid())
Record->addAttr(ReplaceableAttr::Create(Context, 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

View File

@ -7369,6 +7369,279 @@ void Sema::CheckCompletedCXXClass(Scope *S, CXXRecordDecl *Record) {
CheckMismatchedTypeAwareAllocators(OO_Array_New, OO_Array_Delete);
}
static CXXMethodDecl *LookupSpecialMemberFromXValue(Sema &SemaRef,
const 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;
// 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 (NamedDecl *CandDecl : Candidates) {
if (CandDecl->isInvalidDecl())
continue;
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, const_cast<CXXRecordDecl *>(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, const_cast<CXXRecordDecl *>(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);
}
}
}
OverloadCandidateSet::iterator Best;
switch (OCS.BestViableFunction(SemaRef, LookupLoc, Best)) {
case OR_Success:
return cast<CXXMethodDecl>(Best->Function);
default:
return nullptr;
}
}
static bool hasSuitableConstructorForRelocation(Sema &SemaRef,
const CXXRecordDecl *D,
bool AllowUserDefined) {
assert(D->hasDefinition() && !D->isInvalidDecl());
if (D->hasSimpleMoveConstructor() || D->hasSimpleCopyConstructor())
return true;
CXXMethodDecl *Decl =
LookupSpecialMemberFromXValue(SemaRef, D, /*Assign=*/false);
return Decl && Decl->isUserProvided() == AllowUserDefined;
}
static bool hasSuitableMoveAssignmentOperatorForRelocation(
Sema &SemaRef, const CXXRecordDecl *D, bool AllowUserDefined) {
assert(D->hasDefinition() && !D->isInvalidDecl());
if (D->hasSimpleMoveAssignment() || D->hasSimpleCopyAssignment())
return true;
CXXMethodDecl *Decl =
LookupSpecialMemberFromXValue(SemaRef, D, /*Assign=*/true);
if (!Decl)
return false;
return Decl && Decl->isUserProvided() == AllowUserDefined;
}
// [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(Sema &SemaRef, const CXXRecordDecl *D) {
if (!hasSuitableConstructorForRelocation(SemaRef, D,
/*AllowUserDefined=*/false))
return false;
if (!hasSuitableMoveAssignmentOperatorForRelocation(
SemaRef, D, /*AllowUserDefined=*/false))
return false;
CXXDestructorDecl *Dtr = D->getDestructor();
if (!Dtr)
return true;
if (Dtr->isUserProvided() && (!Dtr->isDefaulted() || Dtr->isDeleted()))
return false;
return !Dtr->isDeleted();
}
// [C++26][class.prop]
// A class is eligible for trivial relocation unless it...
static bool IsEligibleForTrivialRelocation(Sema &SemaRef,
const 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() &&
!SemaRef.IsCXXTriviallyRelocatableType(B.getType())))
return false;
}
for (const FieldDecl *Field : D->fields()) {
if (Field->getType()->isDependentType())
continue;
if (Field->getType()->isReferenceType())
continue;
// ... has a non-static data member of an object type that is not
// of a trivially relocatable type
if (!SemaRef.IsCXXTriviallyRelocatableType(Field->getType()))
return false;
}
return !D->hasDeletedDestructor();
}
// [C++26][class.prop]
// A class C is eligible for replacement unless
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() &&
!SemaRef.IsCXXReplaceableType(B.getType()))
return false;
}
for (const FieldDecl *Field : D->fields()) {
if (Field->getType()->isDependentType())
continue;
// it has a non-static data member that is not of a replaceable type,
if (!SemaRef.IsCXXReplaceableType(Field->getType()))
return false;
}
return !D->hasDeletedDestructor();
}
ASTContext::CXXRecordDeclRelocationInfo
Sema::CheckCXX2CRelocatableAndReplaceable(const CXXRecordDecl *D) {
ASTContext::CXXRecordDeclRelocationInfo Info{false, false};
if (!getLangOpts().CPlusPlus || D->isInvalidDecl())
return Info;
assert(D->hasDefinition());
// This is part of "eligible for replacement", however we defer it
// to avoid extraneous computations.
auto HasSuitableSMP = [&] {
return hasSuitableConstructorForRelocation(*this, D,
/*AllowUserDefined=*/true) &&
hasSuitableMoveAssignmentOperatorForRelocation(
*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;
};
Info.IsRelocatable = [&] {
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 (D->hasAttr<TriviallyRelocatableAttr>())
return true;
// is a union with no user-declared special member functions, or
if (IsUnion())
return true;
// is default-movable.
return IsDefaultMovable();
}();
Info.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->hasAttr<ReplaceableAttr>())
return HasSuitableSMP();
// is a union with no user-declared special member functions, or
if (IsUnion())
return HasSuitableSMP();
// is default-movable.
return IsDefaultMovable();
}();
return Info;
}
/// Look up the special member function that would be called by a special
/// member function for a subobject of class type.
///

View File

@ -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"
@ -5444,6 +5445,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
@ -5588,6 +5591,100 @@ 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 (Type->isVariableArrayType())
return false;
if (BaseElementType.hasNonTrivialObjCLifetime())
return false;
if (BaseElementType.hasAddressDiscriminatedPointerAuth())
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;
if (Type->isVariableArrayType())
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 (BaseElementType->isIncompleteType())
return false;
if (!BaseElementType->isObjectType())
return false;
if (T.hasAddressDiscriminatedPointerAuth())
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();
case QualType::PCK_ARCStrong:
return true;
default:
return false;
}
}
static bool EvaluateUnaryTypeTrait(Sema &Self, TypeTrait UTT,
SourceLocation KeyLoc,
TypeSourceInfo *TInfo) {
@ -6007,9 +6104,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 Self.IsCXXTriviallyRelocatableType(T);
case UTT_IsReplaceable:
return Self.IsCXXReplaceableType(T);
case UTT_CanPassInRegs:
if (CXXRecordDecl *RD = T->getAsCXXRecordDecl(); RD && !T.hasQualifiers())
return RD->canPassInRegisters();

View File

@ -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);
};

View File

@ -0,0 +1,42 @@
// 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@-1 {{'trivially_relocatable_if_eligible' keyword is a C++2c extension}}
class E final trivially_relocatable_if_eligible {};
// cxx11-warning@-1 {{'trivially_relocatable_if_eligible' keyword is a C++2c extension}}
// cxx03-warning@-2 {{'final' keyword is a C++11 extension}}
class G trivially_relocatable_if_eligible final{};
// cxx11-warning@-1 {{'trivially_relocatable_if_eligible' keyword is a C++2c extension}}
// cxx03-warning@-2 {{'final' keyword is a C++11 extension}}
class I trivially_relocatable_if_eligible trivially_relocatable_if_eligible final {};
// expected-error@-1 {{class already marked 'trivially_relocatable_if_eligible'}}
// cxx11-warning@-2 {{'trivially_relocatable_if_eligible' keyword is a C++2c extension}}
// cxx03-warning@-3 {{'final' keyword is a C++11 extension}}
class trivially_relocatable_if_eligible trivially_relocatable_if_eligible {};
// cxx11-warning@-1 {{'trivially_relocatable_if_eligible' keyword is a C++2c extension}}
class J replaceable_if_eligible{};
// cxx11-warning@-1 {{'replaceable_if_eligible' keyword is a C++2c extension}}
class K replaceable_if_eligible replaceable_if_eligible {};
// expected-error@-1 {{class already marked 'replaceable_if_eligible'}}
// cxx11-warning@-2 {{'replaceable_if_eligible' keyword is a C++2c extension}}
class replaceable_if_eligible replaceable_if_eligible {};
// cxx11-warning@-1 {{'replaceable_if_eligible' keyword is a C++2c extension}}
class L replaceable_if_eligible trivially_relocatable_if_eligible final {};
// cxx11-warning@-1 {{'replaceable_if_eligible' keyword is a C++2c extension}}
// cxx11-warning@-2 {{'trivially_relocatable_if_eligible' keyword is a C++2c extension}}
// cxx03-warning@-3 {{'final' keyword is a C++11 extension}}
class M replaceable_if_eligible final trivially_relocatable_if_eligible {};
// cxx11-warning@-1 {{'trivially_relocatable_if_eligible' keyword is a C++2c extension}}
// cxx11-warning@-2 {{'replaceable_if_eligible' keyword is a C++2c extension}}
// cxx03-warning@-3 {{'final' keyword is a C++11 extension}}
class N final trivially_relocatable_if_eligible replaceable_if_eligible {};
// cxx11-warning@-1 {{'trivially_relocatable_if_eligible' keyword is a C++2c extension}}
// cxx11-warning@-2 {{'replaceable_if_eligible' keyword is a C++2c extension}}
// cxx03-warning@-3 {{'final' keyword is a C++11 extension}}
class O trivially_relocatable_if_eligible replaceable_if_eligible final {};
// cxx11-warning@-1 {{'trivially_relocatable_if_eligible' keyword is a C++2c extension}}
// cxx11-warning@-2 {{'replaceable_if_eligible' keyword is a C++2c extension}}
// cxx03-warning@-3 {{'final' keyword is a C++11 extension}}

View File

@ -81,7 +81,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 +94,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;

View File

@ -0,0 +1,365 @@
// 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;
int c[3];
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;
NonRelocatable b[1];
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));
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(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 WithConstExplicit trivially_relocatable_if_eligible {
const int i;
};
static_assert(__builtin_is_cpp_trivially_relocatable(WithConstExplicit));
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 UserMoveAssignDefault {
UserMoveAssignDefault(UserMoveAssignDefault&&) = default;
UserMoveAssignDefault& operator=(UserMoveAssignDefault&&) = default;
};
struct UserCopy{
UserCopy(const UserCopy&);
};
struct UserCopyDefault{
UserCopyDefault(const UserCopyDefault&) = default;
};
struct UserDeletedMove{
UserDeletedMove(UserDeletedMove&&) = delete;
UserDeletedMove(const UserDeletedMove&) = default;
};
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(UserMoveAssignDefault));
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 {};
TestDependentErrors<Trivial> Ok;
TestDependentErrors<NonRelocatable> Err;
struct DeletedMove {
DeletedMove(DeletedMove&&) = delete;
};
struct DeletedCopy {
DeletedCopy(const DeletedCopy&) = delete;
};
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;
};
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 {
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(DeletedDtr));
static_assert(!__builtin_is_replaceable(UserProvidedMove));
static_assert(!__builtin_is_replaceable(UserProvidedCopy));
static_assert(!__builtin_is_replaceable(UserProvidedMoveAssign));
struct DeletedCopyTpl {
template <typename U>
DeletedCopyTpl(const U&) = delete;
};
static_assert(__builtin_is_replaceable(DeletedCopyTpl));
using NotReplaceable = DeletedMove;
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));
int n = 4; // expected-note 2{{declared here}}
static_assert(!__builtin_is_cpp_trivially_relocatable(int[n]));
// expected-warning@-1 {{variable length arrays in C++ are a Clang extension}}
// expected-note@-2 {{read of non-const variable 'n' is not allowed in a constant expression}}
static_assert(!__builtin_is_replaceable(int[n]));
// expected-warning@-1 {{variable length arrays in C++ are a Clang extension}}
// expected-note@-2 {{read of non-const variable 'n' is not allowed in a constant expression}}
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>>));
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;
};
}
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-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);
}
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@-1 {{'test__builtin_trivially_relocate<S *, S *, int>' requested here}}
// expected-error@#reloc1 {{first argument to '__builtin_trivially_relocate' must be relocatable}}
}

View File

@ -75,7 +75,7 @@ static_assert(!__is_trivially_constructible(Holder<S3>, const Holder<S3>&));
static_assert(!__is_trivially_assignable(Holder<S3>, const Holder<S3>&));
static_assert(__is_trivially_destructible(Holder<S3>));
static_assert(!__is_trivially_copyable(Holder<S3>));
static_assert(!__is_trivially_relocatable(Holder<S3>));
static_assert(__is_trivially_relocatable(Holder<S3>));
static_assert(!__is_trivially_equality_comparable(Holder<S3>));
struct IA S4 {
@ -97,7 +97,7 @@ static_assert(!__is_trivially_constructible(Holder<S4>, const Holder<S4>&));
static_assert(!__is_trivially_assignable(Holder<S4>, const Holder<S4>&));
static_assert(__is_trivially_destructible(Holder<S4>));
static_assert(!__is_trivially_copyable(Holder<S4>));
static_assert(!__is_trivially_relocatable(Holder<S4>));
static_assert(__is_trivially_relocatable(Holder<S4>));
static_assert(!__is_trivially_equality_comparable(Holder<S4>));
struct PA S5 {
@ -119,5 +119,5 @@ static_assert(!__is_trivially_constructible(Holder<S5>, const Holder<S5>&));
static_assert(!__is_trivially_assignable(Holder<S5>, const Holder<S5>&));
static_assert(__is_trivially_destructible(Holder<S5>));
static_assert(!__is_trivially_copyable(Holder<S5>));
static_assert(!__is_trivially_relocatable(Holder<S5>));
static_assert(__is_trivially_relocatable(Holder<S5>));
static_assert(!__is_trivially_equality_comparable(Holder<S5>));

View File

@ -280,7 +280,12 @@ C++23, informally referred to as C++26.</p>
<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="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>