From ae043eb161fbeb21f18ac2f802580510f32d6c75 Mon Sep 17 00:00:00 2001 From: Corentin Jabot Date: Sun, 26 Feb 2023 13:47:02 +0100 Subject: [PATCH 01/32] [Clang] Implement P2786 - Trivially relocatable --- .../clang/AST/CXXRecordDeclDefinitionBits.def | 2 + clang/include/clang/AST/DeclCXX.h | 44 ++++++ clang/include/clang/AST/Type.h | 2 + .../clang/Basic/DiagnosticParseKinds.td | 3 + .../clang/Basic/DiagnosticSemaKinds.td | 12 +- clang/include/clang/Basic/TokenKinds.def | 1 + clang/include/clang/Parse/Parser.h | 9 ++ clang/include/clang/Sema/Sema.h | 17 ++- clang/lib/AST/DeclCXX.cpp | 3 +- clang/lib/AST/Type.cpp | 11 ++ clang/lib/Frontend/InitPreprocessor.cpp | 5 +- clang/lib/Parse/ParseDeclCXX.cpp | 123 ++++++++++++--- clang/lib/Parse/Parser.cpp | 1 + clang/lib/Sema/SemaDecl.cpp | 41 ++++- clang/lib/Sema/SemaDeclCXX.cpp | 94 ++++++++++++ clang/lib/Sema/SemaExprCXX.cpp | 3 + clang/lib/Sema/SemaOverload.cpp | 3 +- clang/lib/Sema/SemaTemplateInstantiate.cpp | 41 +++++ .../Parser/cxx2c-trivially-relocatable.cpp | 15 ++ .../SemaCXX/cxx2c-trivially-relocatable.cpp | 142 ++++++++++++++++++ 20 files changed, 534 insertions(+), 38 deletions(-) create mode 100644 clang/test/Parser/cxx2c-trivially-relocatable.cpp create mode 100644 clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp diff --git a/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def b/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def index 6620840df0ced..29346cb184565 100644 --- a/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def +++ b/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def @@ -253,4 +253,6 @@ FIELD(IsAnyDestructorNoReturn, 1, NO_MERGE) /// type that is intangible). HLSL only. FIELD(IsHLSLIntangible, 1, NO_MERGE) +FIELD(IsTriviallyRelocatable, 1, NO_MERGE) + #undef FIELD diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h index 252e6e9256414..a4d080f7160e2 100644 --- a/clang/include/clang/AST/DeclCXX.h +++ b/clang/include/clang/AST/DeclCXX.h @@ -127,6 +127,34 @@ class AccessSpecDecl : public Decl { static bool classofKind(Kind K) { return K == AccessSpec; } }; +class TriviallyRelocatableSpecifier { +public: + enum Kind { TRK_Invalid, TRK_True, TRK_False, TRK_Dependent }; + TriviallyRelocatableSpecifier() = default; + TriviallyRelocatableSpecifier(Kind TriviallyRelocatableKind, + SourceLocation Begin, Expr *E = nullptr) + : ExprAndKind(E, TriviallyRelocatableKind), Loc(Begin) {} + void SetTriviallyRelocatable(Kind TriviallyRelocatableKind, + SourceLocation Begin, Expr *E = nullptr) { + ExprAndKind.setPointerAndInt(E, TriviallyRelocatableKind); + Loc = Begin; + } + + bool isSet() const { return !Loc.isInvalid(); } + + bool isValid() const { return ExprAndKind.getInt() != TRK_Invalid; } + + Kind getKind() const { return ExprAndKind.getInt(); } + + SourceLocation getLocation() const { return Loc; } + + Expr *getCondition() const { return ExprAndKind.getPointer(); } + +private: + llvm::PointerIntPair ExprAndKind{nullptr, TRK_Invalid}; + SourceLocation Loc; +}; + /// Represents a base class of a C++ class. /// /// Each CXXBaseSpecifier represents a single, direct base class (or @@ -349,6 +377,8 @@ class CXXRecordDecl : public RecordDecl { /// This is actually currently stored in reverse order. LazyDeclPtr FirstFriend; + TriviallyRelocatableSpecifier TriviallyRelocatable; + DefinitionData(CXXRecordDecl *D); /// Retrieve the set of direct base classes. @@ -1464,6 +1494,16 @@ class CXXRecordDecl : public RecordDecl { return isLiteral() && data().StructuralIfLiteral; } + TriviallyRelocatableSpecifier getTriviallyRelocatableSpecifier() const { + return data().TriviallyRelocatable; + } + + bool isTriviallyRelocatable() const { return data().IsTriviallyRelocatable; } + + void setIsTriviallyRelocatable(bool Set) { + data().IsTriviallyRelocatable = Set; + } + /// Notify the class that this destructor is now selected. /// /// Important properties of the class depend on destructor properties. Since @@ -1898,6 +1938,10 @@ class CXXRecordDecl : public RecordDecl { return K >= firstCXXRecord && K <= lastCXXRecord; } void markAbstract() { data().Abstract = true; } + + void setTriviallyRelocatableSpecifier(TriviallyRelocatableSpecifier TRS) { + data().TriviallyRelocatable = TRS; + } }; /// Store information needed for an explicit specifier. diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h index ef36a73716454..55829066919e4 100644 --- a/clang/include/clang/AST/Type.h +++ b/clang/include/clang/AST/Type.h @@ -1142,6 +1142,8 @@ class QualType { /// Return true if this is a trivially relocatable type. bool isTriviallyRelocatableType(const ASTContext &Context) const; + bool isCppTriviallyRelocatableType(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/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td index 0aa2c4a70849a..53b96cab430b2 100644 --- a/clang/include/clang/Basic/DiagnosticParseKinds.td +++ b/clang/include/clang/Basic/DiagnosticParseKinds.td @@ -1049,6 +1049,9 @@ def err_access_specifier_interface : Error< def err_duplicate_class_virt_specifier : Error< "class already marked '%0'">; +def err_duplicate_class_trivially_relocatable : Error< + "class already marked 'trivially_relocatable'">; + 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 58819a64813fc..7c04c279b88e0 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -84,7 +84,8 @@ def err_typecheck_converted_constant_expression_indirect : Error< def err_expr_not_cce : Error< "%select{case value|enumerator value|non-type template argument|" "array size|explicit specifier argument|noexcept specifier argument|" - "call to 'size()'|call to 'data()'}0 is not a constant expression">; + "call to 'size()'|call to 'data()'|" + "trivially_relocatable specifier argument}0 is not a constant expression">; def ext_cce_narrowing : ExtWarn< "%select{case value|enumerator value|non-type template argument|" "array size|explicit specifier argument|noexcept specifier argument|" @@ -2680,6 +2681,15 @@ def warn_final_dtor_non_final_class : Warning< def note_final_dtor_non_final_class_silence : Note< "mark %0 as '%select{final|sealed}1' to silence this warning">; +def err_trivially_relocatable_specifier_on_non_relocatable_class : Error< + "invalid 'trivially_relocatable' specifier on non trivially-relocatable class %0">; + +def note_trivially_relocatable: Note< + "because it %select{" + "has a virtual base class|" + "inherits from a non trivially-relocatable class|" + "has a non trivially-relocatable member}0 %1">; + // C++11 attributes def err_repeat_attribute : Error<"%0 attribute cannot be repeated">; diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def index a82ff684b2ac7..a448175303d1e 100644 --- a/clang/include/clang/Basic/TokenKinds.def +++ b/clang/include/clang/Basic/TokenKinds.def @@ -557,6 +557,7 @@ TYPE_TRAIT_2(__reference_converts_from_temporary, ReferenceConvertsFromTemporary TYPE_TRAIT_2(/*EmptySpellingName*/, IsDeducible, KEYCXX) TYPE_TRAIT_1(__is_bitwise_cloneable, IsBitwiseCloneable, KEYALL) +TYPE_TRAIT_1(__is_cpp_trivially_relocatable, IsCppTriviallyRelocatable, KEYCXX) // 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 47f72135c97cf..5ad251ce3ef5f 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -164,6 +164,7 @@ class Parser : public CodeCompletionHandler { mutable IdentifierInfo *Ident_final; mutable IdentifierInfo *Ident_GNU_final; mutable IdentifierInfo *Ident_override; + mutable IdentifierInfo *Ident_trivially_relocatable; // C++2a contextual keywords. mutable IdentifierInfo *Ident_import; @@ -3164,6 +3165,14 @@ class Parser : public CodeCompletionHandler { SourceLocation FriendLoc); bool isCXX11FinalKeyword() const; + + bool isCXX2CTriviallyRelocatableKeyword(Token Tok) const; + bool isCXX2CTriviallyRelocatableKeyword() const; + void ParseOptionalCXX2CTriviallyRelocatableSpecifier( + TriviallyRelocatableSpecifier &TRS); + bool SkipCXX2CTriviallyRelocatableSpecifier(); + + 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 68c782a15c6f1..96ef4c905626a 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -3983,20 +3983,25 @@ 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, Expr *E); + /// 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, + 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 ActOnTagFinishSkippedDefinition(SkippedDefinitionContext Context); /// ActOnTagDefinitionError - Invoked when there was an unrecoverable @@ -10039,6 +10044,8 @@ class Sema final : public SemaBase { ///< message. CCEK_StaticAssertMessageData, ///< Call to data() in a static assert ///< message. + /// + CCEK_TriviallyRelocatable, }; ExprResult BuildConvertedConstantExpression(Expr *From, QualType T, diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp index 01143391edab4..8aa52ed237580 100644 --- a/clang/lib/AST/DeclCXX.cpp +++ b/clang/lib/AST/DeclCXX.cpp @@ -109,7 +109,8 @@ CXXRecordDecl::DefinitionData::DefinitionData(CXXRecordDecl *D) ImplicitCopyAssignmentHasConstParam(true), HasDeclaredCopyConstructorWithConstParam(false), HasDeclaredCopyAssignmentWithConstParam(false), - IsAnyDestructorNoReturn(false), IsHLSLIntangible(false), IsLambda(false), + IsAnyDestructorNoReturn(false), IsHLSLIntangible(false), + IsTriviallyRelocatable(false), IsLambda(false), IsParsingBaseSpecifiers(false), ComputedVisibleConversions(false), HasODRHash(false), Definition(D) {} diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp index a55e6c8bf0261..a30fb2bc409b3 100644 --- a/clang/lib/AST/Type.cpp +++ b/clang/lib/AST/Type.cpp @@ -2819,6 +2819,17 @@ 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::isNonWeakInMRRWithObjCWeak(const ASTContext &Context) const { return !Context.getLangOpts().ObjCAutoRefCount && Context.getLangOpts().ObjCWeak && diff --git a/clang/lib/Frontend/InitPreprocessor.cpp b/clang/lib/Frontend/InitPreprocessor.cpp index 9a0fdb175ff29..e2323106c1726 100644 --- a/clang/lib/Frontend/InitPreprocessor.cpp +++ b/clang/lib/Frontend/InitPreprocessor.cpp @@ -754,7 +754,10 @@ static void InitializeCPlusPlusFeatureTestMacros(const LangOptions &LangOpts, Builder.defineMacro("__cpp_auto_cast", "202110L"); } - // We provide those C++23 features as extensions in earlier language modes, so + // Experimental + Builder.defineMacro("__cpp_trivial_relocatability", "202302L"); + + // 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"); diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp index 6370da1fab004..82d951dc395b5 100644 --- a/clang/lib/Parse/ParseDeclCXX.cpp +++ b/clang/lib/Parse/ParseDeclCXX.cpp @@ -2026,7 +2026,10 @@ 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()) || + (isCXX2CTriviallyRelocatableKeyword() && + NextToken().is(tok::l_paren))))) { if (DS.isFriendSpecified()) { // C++ [class.friend]p2: // A class shall not be defined in a friend declaration. @@ -2045,14 +2048,19 @@ 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()) { - ConsumeToken(); + if (isCXX2CTriviallyRelocatableKeyword()) { + if (!SkipCXX2CTriviallyRelocatableSpecifier()) + break; + } else + ConsumeToken(); } // Skip C++11 attribute specifiers. @@ -2691,16 +2699,64 @@ 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) + Ident_trivially_relocatable = + &PP.getIdentifierTable().get("trivially_relocatable"); + IdentifierInfo *II = Tok.getIdentifierInfo(); + return II == Ident_trivially_relocatable; +} + +bool Parser::isCXX2CTriviallyRelocatableKeyword() const { + return isCXX2CTriviallyRelocatableKeyword(Tok); +} + +void Parser::ParseOptionalCXX2CTriviallyRelocatableSpecifier( + TriviallyRelocatableSpecifier &TRS) { + assert(isCXX2CTriviallyRelocatableKeyword() && + "expected a trivially_relocatable specifier"); + Expr *E = nullptr; + SourceLocation Loc = ConsumeToken(); + if (Tok.is(tok::l_paren)) { + BalancedDelimiterTracker T(*this, tok::l_paren); + T.consumeOpen(); + ExprResult Res = ParseConstantExpression(); + T.consumeClose(); + if (!Res.isInvalid()) + E = Res.get(); + } + TRS = Actions.ActOnTriviallyRelocatableSpecifier(Loc, E); +} + +bool Parser::SkipCXX2CTriviallyRelocatableSpecifier() { + assert(isCXX2CTriviallyRelocatableKeyword() && + "expected a trivially_relocatable specifier"); + ConsumeToken(); + if (Tok.is(tok::l_paren)) { + ConsumeParen(); + return SkipUntil(tok::r_paren, StopAtSemi); + } + return true; +} + /// isClassCompatibleKeyword - Determine whether the next token is a C++11 /// 'final' or Microsoft 'sealed' or 'abstract' contextual keywords. -bool Parser::isClassCompatibleKeyword() const { - VirtSpecifiers::Specifier Specifier = isCXX11VirtSpecifier(); +bool Parser::isClassCompatibleKeyword(Token Tok) const { + if (isCXX2CTriviallyRelocatableKeyword(Tok)) + 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( @@ -3577,21 +3633,24 @@ 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"); - ConsumeToken(); + while (isClassCompatibleKeyword()) { + if (isCXX2CTriviallyRelocatableKeyword()) { + if (!SkipCXX2CTriviallyRelocatableSpecifier()) + return; + } else + 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 @@ -3805,13 +3864,28 @@ void Parser::ParseCXXMemberSpecification(SourceLocation RecordLoc, SourceLocation AbstractLoc; bool IsFinalSpelledSealed = false; bool IsAbstract = false; + TriviallyRelocatableSpecifier TriviallyRelocatable; // Parse the optional 'final' keyword. if (getLangOpts().CPlusPlus && Tok.is(tok::identifier)) { while (true) { VirtSpecifiers::Specifier Specifier = isCXX11VirtSpecifier(Tok); - if (Specifier == VirtSpecifiers::VS_None) - break; + if (Specifier == VirtSpecifiers::VS_None) { + if (isCXX2CTriviallyRelocatableKeyword(Tok)) { + if (TriviallyRelocatable.isSet()) { + auto Skipped = Tok; + SkipCXX2CTriviallyRelocatableSpecifier(); + Diag(Skipped, diag::err_duplicate_class_trivially_relocatable) + << TriviallyRelocatable.getLocation(); + } else { + ParseOptionalCXX2CTriviallyRelocatableSpecifier( + TriviallyRelocatable); + continue; + } + } else { + break; + } + } if (isCXX11FinalKeyword()) { if (FinalLoc.isValid()) { auto Skipped = ConsumeToken(); @@ -3847,7 +3921,8 @@ void Parser::ParseCXXMemberSpecification(SourceLocation RecordLoc, else if (Specifier == VirtSpecifiers::VS_GNU_Final) Diag(FinalLoc, diag::ext_warn_gnu_final); } - assert((FinalLoc.isValid() || AbstractLoc.isValid()) && + assert((FinalLoc.isValid() || AbstractLoc.isValid() || + TriviallyRelocatable.isSet()) && "not a class definition"); // Parse any C++11 attributes after 'final' keyword. @@ -3920,9 +3995,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, T.getOpenLocation()); // C++ 11p3: Members of a class defined with the keyword class are private // by default. Members of a class defined with the keywords struct or union diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp index 04c2f1d380bc4..c1d72d249386c 100644 --- a/clang/lib/Parse/Parser.cpp +++ b/clang/lib/Parse/Parser.cpp @@ -513,6 +513,7 @@ void Parser::Initialize() { Ident_sealed = nullptr; Ident_abstract = nullptr; Ident_override = nullptr; + Ident_trivially_relocatable = nullptr; Ident_GNU_final = nullptr; Ident_import = nullptr; Ident_module = nullptr; diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 0f63c764536ec..4dead61e83c5d 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -18026,11 +18026,38 @@ bool Sema::ActOnDuplicateDefinition(Decl *Prev, SkipBodyInfo &SkipBody) { return true; } -void Sema::ActOnStartCXXMemberDeclarations(Scope *S, Decl *TagD, - SourceLocation FinalLoc, - bool IsFinalSpelledSealed, - bool IsAbstract, - SourceLocation LBraceLoc) { +TriviallyRelocatableSpecifier +Sema::ActOnTriviallyRelocatableSpecifier(SourceLocation Loc, Expr *E) { + TriviallyRelocatableSpecifier Spec; + llvm::APSInt Result; + TriviallyRelocatableSpecifier::Kind Kind = + TriviallyRelocatableSpecifier::TRK_True; + if (E) { + if (DiagnoseUnexpandedParameterPack(E)) + Kind = TriviallyRelocatableSpecifier::TRK_Invalid; + else if (E->isTypeDependent()) + Kind = TriviallyRelocatableSpecifier::TRK_Dependent; + else { + ExprResult Converted = CheckConvertedConstantExpression( + E, Context.BoolTy, Result, CCEK_TriviallyRelocatable); + if (!Converted.isUsable()) + Kind = TriviallyRelocatableSpecifier::TRK_Invalid; + else if (Converted.get()->isValueDependent()) + Kind = TriviallyRelocatableSpecifier::TRK_Dependent; + else + Kind = Result.getBoolValue() ? TriviallyRelocatableSpecifier::TRK_True + : TriviallyRelocatableSpecifier::TRK_False; + E = Converted.get(); + } + } + Spec.SetTriviallyRelocatable(Kind, Loc, E); + return Spec; +} + +void Sema::ActOnStartCXXMemberDeclarations( + Scope *S, Decl *TagD, SourceLocation FinalLoc, bool IsFinalSpelledSealed, + bool IsAbstract, TriviallyRelocatableSpecifier TriviallyRelocatable, + SourceLocation LBraceLoc) { AdjustDeclIfTemplate(TagD); CXXRecordDecl *Record = cast(TagD); @@ -18048,6 +18075,10 @@ void Sema::ActOnStartCXXMemberDeclarations(Scope *S, Decl *TagD, ? FinalAttr::Keyword_sealed : FinalAttr::Keyword_final)); } + + if (TriviallyRelocatable.isValid() && !Record->isInvalidDecl()) + Record->setTriviallyRelocatableSpecifier(TriviallyRelocatable); + // C++ [class]p2: // [...] The class-name is also inserted into the scope of the // class itself; this is known as the injected-class-name. For diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 6b6fa98bf394c..ee9b9dbdffea6 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -6920,6 +6920,8 @@ void Sema::CheckCompletedCXXClass(Scope *S, CXXRecordDecl *Record) { } } + CheckCXX2CTriviallyRelocatable(Record); + // See if trivial_abi has to be dropped. if (Record->hasAttr()) checkIllFormedTrivialABIStruct(*Record); @@ -7192,6 +7194,98 @@ void Sema::CheckCompletedCXXClass(Scope *S, CXXRecordDecl *Record) { } } +void Sema::CheckCXX2CTriviallyRelocatable(CXXRecordDecl *D) { + if (!D->hasDefinition() || D->isInvalidDecl()) + return; + + TriviallyRelocatableSpecifier::Kind TRK = + D->getTriviallyRelocatableSpecifier().getKind(); + SourceLocation Loc = D->getTriviallyRelocatableSpecifier().getLocation(); + if (TRK == TriviallyRelocatableSpecifier::TRK_Dependent) + return; + if (TRK == TriviallyRelocatableSpecifier::TRK_False) { + D->setIsTriviallyRelocatable(false); + return; + } + bool MarkedTriviallyRelocatable = + D->getTriviallyRelocatableSpecifier().isSet(); + auto DiagnosticInvalidExplicitSpecifier = [&, NotedError = false]() mutable { + if (NotedError) + return; + Diag(Loc, + diag::err_trivially_relocatable_specifier_on_non_relocatable_class) + << D; + NotedError = true; + }; + bool IsTriviallyRelocatable = true; + for (const CXXBaseSpecifier &B : D->bases()) { + const auto *BaseDecl = B.getType()->getAsCXXRecordDecl(); + if (!BaseDecl) + continue; + if (B.isVirtual() || + (!BaseDecl->isDependentType() && !BaseDecl->isTriviallyRelocatable())) { + if (MarkedTriviallyRelocatable) { + DiagnosticInvalidExplicitSpecifier(); + Diag(B.getBeginLoc(), diag::note_trivially_relocatable) + << (B.isVirtual() ? 0 : 1) << BaseDecl << B.getSourceRange(); + } + IsTriviallyRelocatable = false; + } + } + + for (const FieldDecl *Field : D->fields()) { + if (Field->getType()->isDependentType()) + continue; + if (Field->getType()->isReferenceType()) + continue; + QualType T = getASTContext().getBaseElementType(Field->getType()); + if (CXXRecordDecl *RD = T->getAsCXXRecordDecl()) { + if (RD->isTriviallyRelocatable()) + continue; + if (MarkedTriviallyRelocatable) { + DiagnosticInvalidExplicitSpecifier(); + Diag(Field->getBeginLoc(), diag::note_trivially_relocatable) + << /*member*/ 2 << Field << Field->getSourceRange(); + } + IsTriviallyRelocatable = false; + } + } + if (!D->isDependentType() && !MarkedTriviallyRelocatable) { + bool HasSuitableMoveCtr = D->needsImplicitMoveConstructor(); + bool HasSuitableCopyCtr = false; + if (D->hasUserDeclaredDestructor()) { + const auto *Dtr = D->getDestructor(); + if (Dtr && (!Dtr->isDefaulted() || Dtr->isDeleted())) + IsTriviallyRelocatable = false; + } + if (IsTriviallyRelocatable && !HasSuitableMoveCtr) { + for (const CXXConstructorDecl *CD : D->ctors()) { + if (CD->isMoveConstructor() && CD->isDefaulted() && + !CD->isIneligibleOrNotSelected()) { + HasSuitableMoveCtr = true; + break; + } + } + } + if (!HasSuitableMoveCtr && !D->hasMoveConstructor()) { + HasSuitableCopyCtr = D->needsImplicitCopyConstructor(); + if (!HasSuitableCopyCtr) { + for (const CXXConstructorDecl *CD : D->ctors()) { + if (CD->isCopyConstructor() && CD->isDefaulted() && + !CD->isIneligibleOrNotSelected()) { + HasSuitableCopyCtr = true; + break; + } + } + } + } + + if (!HasSuitableMoveCtr && !HasSuitableCopyCtr) + IsTriviallyRelocatable = false; + } + D->setIsTriviallyRelocatable(IsTriviallyRelocatable); +} + /// Look up the special member function that would be called by a special /// member function for a subobject of class type. /// diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index 14feafd1e6b17..fc8c4198dbdd3 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -5084,6 +5084,7 @@ static bool CheckUnaryTypeTraitTypeCompleteness(Sema &S, TypeTrait UTT, // impose the same constraints. case UTT_IsTriviallyRelocatable: case UTT_IsTriviallyEqualityComparable: + case UTT_IsCppTriviallyRelocatable: 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 @@ -5654,6 +5655,8 @@ static bool EvaluateUnaryTypeTrait(Sema &Self, TypeTrait UTT, return T.isTriviallyRelocatableType(C); case UTT_IsBitwiseCloneable: return T.isBitwiseCloneableType(C); + case UTT_IsCppTriviallyRelocatable: + return T.isCppTriviallyRelocatableType(C); case UTT_IsReferenceable: return T.isReferenceable(); case UTT_CanPassInRegs: diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp index 861b0a91240b3..9052e89024181 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -6084,7 +6084,8 @@ static ExprResult BuildConvertedConstantExpression(Sema &S, Expr *From, // expression is a constant expression and the implicit conversion // sequence contains only [... list of conversions ...]. ImplicitConversionSequence ICS = - (CCE == Sema::CCEK_ExplicitBool || CCE == Sema::CCEK_Noexcept) + (CCE == Sema::CCEK_ExplicitBool || CCE == Sema::CCEK_Noexcept || + CCE == Sema::CCEK_TriviallyRelocatable) ? TryContextuallyConvertToBool(S, From) : TryCopyInitialization(S, From, T, /*SuppressUserConversions=*/false, diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp index c42cc250bb904..418e371f7011d 100644 --- a/clang/lib/Sema/SemaTemplateInstantiate.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp @@ -3428,6 +3428,43 @@ namespace clang { } } +static TriviallyRelocatableSpecifier InstantiateTriviallyRelocatableSpecifier( + Sema &S, const TriviallyRelocatableSpecifier &Pattern, + const MultiLevelTemplateArgumentList &TemplateArgs) { + if (Pattern.getKind() != TriviallyRelocatableSpecifier::TRK_Dependent) + return Pattern; + Expr *PatternE = Pattern.getCondition(); + EnterExpressionEvaluationContext ConstantCtx( + S, Sema::ExpressionEvaluationContext::ConstantEvaluated); + ExprResult Res = S.SubstExpr(PatternE, TemplateArgs); + if (Res.isInvalid()) + return TriviallyRelocatableSpecifier( + TriviallyRelocatableSpecifier::TRK_Invalid, Pattern.getLocation()); + return S.ActOnTriviallyRelocatableSpecifier(Pattern.getLocation(), Res.get()); +} + +/// Instantiate the definition of a class from a given pattern. +/// +/// \param PointOfInstantiation The point of instantiation within the +/// source code. +/// +/// \param Instantiation is the declaration whose definition is being +/// instantiated. This will be either a class template specialization +/// or a member class of a class template specialization. +/// +/// \param Pattern is the pattern from which the instantiation +/// occurs. This will be either the declaration of a class template or +/// the declaration of a member class of a class template. +/// +/// \param TemplateArgs The template arguments to be substituted into +/// the pattern. +/// +/// \param TSK the kind of implicit or explicit instantiation to perform. +/// +/// \param Complain whether to complain if the class cannot be instantiated due +/// to the lack of a definition. +/// +/// \returns true if an error occurred, false otherwise. bool Sema::InstantiateClass(SourceLocation PointOfInstantiation, CXXRecordDecl *Instantiation, CXXRecordDecl *Pattern, @@ -3498,6 +3535,10 @@ Sema::InstantiateClass(SourceLocation PointOfInstantiation, // Start the definition of this instantiation. Instantiation->startDefinition(); + TriviallyRelocatableSpecifier TRS = InstantiateTriviallyRelocatableSpecifier( + *this, Pattern->getTriviallyRelocatableSpecifier(), TemplateArgs); + Instantiation->setTriviallyRelocatableSpecifier(TRS); + // The instantiation is visible here, even if it was first declared in an // unimported module. Instantiation->setVisibleDespiteOwningModule(); diff --git a/clang/test/Parser/cxx2c-trivially-relocatable.cpp b/clang/test/Parser/cxx2c-trivially-relocatable.cpp new file mode 100644 index 0000000000000..46c2fab65b6ef --- /dev/null +++ b/clang/test/Parser/cxx2c-trivially-relocatable.cpp @@ -0,0 +1,15 @@ +// RUN: %clang_cc1 -std=c++2b -verify -fsyntax-only %s + + +class A trivially_relocatable {}; +class B trivially_relocatable() {}; // expected-error {{expected expression}} +class C trivially_relocatable(true) {}; +class D trivially_relocatable true {}; // expected-error {{variable has incomplete type 'class D'}} \ + // expected-error {{expected ';' after top level declarator}} \ + // expected-note {{forward declaration of 'D'}} +class E final trivially_relocatable {}; +class F final trivially_relocatable(true) {}; +class G trivially_relocatable final{}; +class H trivially_relocatable(true) final {}; +class I trivially_relocatable trivially_relocatable(true) final {}; // expected-error {{class already marked 'trivially_relocatable'}} +class trivially_relocatable trivially_relocatable {}; diff --git a/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp b/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp new file mode 100644 index 0000000000000..21760fe05d8ef --- /dev/null +++ b/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp @@ -0,0 +1,142 @@ +// RUN: %clang_cc1 -std=c++2b -verify %s + +namespace Expressions { + +class D trivially_relocatable("") {}; +// expected-error@-1 {{conversion from 'const char[1]' to 'bool' is not allowed in a converted constant expression}}; +bool f(); //expected-note {{declared here}} +class E trivially_relocatable(f()) {}; // expected-error{{trivially_relocatable specifier argument is not a constant expression}}\ + // expected-note {{non-constexpr function 'f' cannot be used in a constant expression}}; +} + +class Trivial {}; +struct NonRelocatable { + ~NonRelocatable(); +}; +static NonRelocatable NonRelocatable_g; + +class A trivially_relocatable {}; +class B trivially_relocatable : Trivial{}; +class C trivially_relocatable { + int a; + void* b; + int c[3]; + Trivial d[3]; + NonRelocatable& e = NonRelocatable_g; +}; +class D trivially_relocatable : Trivial {}; +class E trivially_relocatable : virtual Trivial {}; +// expected-error@-1 {{invalid 'trivially_relocatable' specifier on non trivially-relocatable class 'E'}} +// expected-note@-2 {{because it has a virtual base class 'Trivial'}} + +class F trivially_relocatable : NonRelocatable {}; +// expected-error@-1 {{invalid 'trivially_relocatable' specifier on non trivially-relocatable class 'F'}} +// expected-note@-2 {{because it inherits from a non trivially-relocatable class 'NonRelocatable'}} + +class I trivially_relocatable { // expected-error{{invalid 'trivially_relocatable' specifier on non trivially-relocatable class 'I'}} + NonRelocatable a; // expected-note {{because it has a non trivially-relocatable member 'a'}} + NonRelocatable b[1]; // expected-note {{because it has a non trivially-relocatable member 'b'}} + const NonRelocatable c; // expected-note {{because it has a non trivially-relocatable member 'c'}} + const NonRelocatable d[1]; // expected-note {{because it has a non trivially-relocatable member 'd'}} +}; + +class J trivially_relocatable(false) : virtual Trivial, NonRelocatable { + NonRelocatable a; +}; + +class G trivially_relocatable { + G(G&&); +}; + +class H trivially_relocatable { + ~H(); +}; + +struct Incomplete; // expected-note {{forward declaration of 'Incomplete'}} +static_assert(__is_cpp_trivially_relocatable(Incomplete)); // expected-error {{incomplete type 'Incomplete' used in type trait expression}} +static_assert(__is_cpp_trivially_relocatable(Trivial)); +static_assert(__is_cpp_trivially_relocatable(G)); +static_assert(__is_cpp_trivially_relocatable(H)); +static_assert(__is_cpp_trivially_relocatable(int)); +static_assert(__is_cpp_trivially_relocatable(void*)); +static_assert(!__is_cpp_trivially_relocatable(int&)); +static_assert(!__is_cpp_trivially_relocatable(Trivial&)); +static_assert(__is_cpp_trivially_relocatable(const Trivial)); +static_assert(__is_cpp_trivially_relocatable(Trivial[1])); + +struct UserDtr { + ~UserDtr(); +}; + +struct DefaultedDtr { + ~DefaultedDtr() = default; +}; +struct UserMoveWithDefaultCopy { + UserMoveWithDefaultCopy(UserMoveWithDefaultCopy&&); + UserMoveWithDefaultCopy(const UserMoveWithDefaultCopy&) = default; +}; + +struct UserMove{ + UserMove(UserMove&&); +}; + +struct UserMoveDefault{ + UserMoveDefault(UserMoveDefault&&) = default; +}; + +struct UserCopy{ + UserCopy(const UserCopy&); +}; + +struct UserCopyDefault{ + UserCopyDefault(const UserCopyDefault&) = default; +}; + + +struct UserDeletedMove{ + UserDeletedMove(UserDeletedMove&) = delete; + UserDeletedMove(const UserDeletedMove&) = default; +}; + +static_assert(!__is_cpp_trivially_relocatable(UserDtr)); +static_assert(__is_cpp_trivially_relocatable(DefaultedDtr)); +static_assert(!__is_cpp_trivially_relocatable(UserMoveWithDefaultCopy)); +static_assert(!__is_cpp_trivially_relocatable(UserMove)); +static_assert(!__is_cpp_trivially_relocatable(UserCopy)); +static_assert(__is_cpp_trivially_relocatable(UserMoveDefault)); +static_assert(__is_cpp_trivially_relocatable(UserCopyDefault)); +static_assert(__is_cpp_trivially_relocatable(UserDeletedMove)); + +template +class UnexpendedPack trivially_relocatable(Args) // expected-error{{expression contains unexpanded parameter pack 'Args'}} +{}; + +namespace Tpls { + +template +class UnexpendedPack trivially_relocatable(Args) // expected-error{{expression contains unexpanded parameter pack 'Args'}} +{}; + +template +class TestDependentValue trivially_relocatable((Args && ...)) {}; + +static_assert(__is_cpp_trivially_relocatable(TestDependentValue<>)); +static_assert(__is_cpp_trivially_relocatable(TestDependentValue)); + +template +class TestDependent +trivially_relocatable(__is_cpp_trivially_relocatable(T)) : T +{}; + +TestDependent A; +TestDependent B; +static_assert(__is_cpp_trivially_relocatable(TestDependent)); +static_assert(__is_cpp_trivially_relocatable(TestDependent)); // expected-error{{static assertion failed due to requirement '__is_cpp_trivially_relocatable(Tpls::TestDependent)'}} + +template +class TestDependentErrors trivially_relocatable : T {}; +// expected-error@-1 {{invalid 'trivially_relocatable' specifier on non trivially-relocatable class 'TestDependentErrors'}} +// expected-note@-2 {{because it inherits from a non trivially-relocatable class 'NonRelocatable'}} +TestDependentErrors Ok; +TestDependentErrors Err; // expected-note {{in instantiation of template class}} +} From 6e2ac08566970816f74fcbb24df8694aefe0cbc0 Mon Sep 17 00:00:00 2001 From: Corentin Jabot Date: Thu, 11 Apr 2024 14:18:03 +0200 Subject: [PATCH 02/32] [Trivial relocation] Add a __builtin_trivially_relocate function + libc++ interface --- clang/include/clang/Basic/Builtins.td | 6 +++ clang/lib/AST/Decl.cpp | 1 + clang/lib/CodeGen/CGBuiltin.cpp | 1 + clang/lib/Sema/SemaChecking.cpp | 37 +++++++++++++++++++ libcxx/include/CMakeLists.txt | 1 + libcxx/include/__memory/relocate.h | 27 ++++++++++++++ .../__type_traits/is_trivially_relocatable.h | 16 ++++---- libcxx/include/memory | 1 + 8 files changed, 81 insertions(+), 9 deletions(-) create mode 100644 libcxx/include/__memory/relocate.h diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td index 92118418d9d45..2bd8a7bf1de5b 100644 --- a/clang/include/clang/Basic/Builtins.td +++ b/clang/include/clang/Basic/Builtins.td @@ -2756,6 +2756,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/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp index a14b1b33d35ef..d9c9d43cb47c2 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -4414,6 +4414,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/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp index da7a1a55da531..032b08a2d41dc 100644 --- a/clang/lib/CodeGen/CGBuiltin.cpp +++ b/clang/lib/CodeGen/CGBuiltin.cpp @@ -4277,6 +4277,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/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index 99500daca295c..f9a7f3852cc95 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -38,6 +38,7 @@ #include "clang/AST/TypeLoc.h" #include "clang/AST/UnresolvedSet.h" #include "clang/Basic/AddressSpaces.h" +#include "clang/Basic/Builtins.h" #include "clang/Basic/CharInfo.h" #include "clang/Basic/Diagnostic.h" #include "clang/Basic/IdentifierTable.h" @@ -1878,6 +1879,39 @@ static ExprResult BuiltinIsWithinLifetime(Sema &S, CallExpr *TheCall) { << 0; return ExprError(); } +static ExprResult BuiltinTriviallyRelocate(Sema &S, CallExpr *TheCall) { + if (checkArgCount(S, TheCall, 3)) + return ExprError(); + + QualType ArgTy = TheCall->getArg(0)->getType(); + if (!ArgTy->isPointerType() || ArgTy.getCVRQualifiers() != 0) + return ExprError(); // Todo : Diag + + QualType T = ArgTy->getPointeeType(); + if (S.RequireCompleteType(TheCall->getBeginLoc(), T, + diag::err_incomplete_type)) + return ExprError(); + + if (T.isConstQualified() || + !T.isCppTriviallyRelocatableType(S.getASTContext())) + return ExprError(); + + TheCall->setType(ArgTy); + + QualType Dest = TheCall->getArg(1)->getType(); + if (Dest.getCanonicalType() != ArgTy.getCanonicalType()) + 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; } @@ -2316,6 +2350,9 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID, return BuiltinLaunder(*this, TheCall); case Builtin::BI__builtin_is_within_lifetime: return BuiltinIsWithinLifetime(*this, TheCall); + case Builtin::BI__builtin_trivially_relocate: + return BuiltinTriviallyRelocate(*this, TheCall); + case Builtin::BI__sync_fetch_and_add: case Builtin::BI__sync_fetch_and_add_1: case Builtin::BI__sync_fetch_and_add_2: diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt index a571832ab724d..1f5372ea83bdb 100644 --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -548,6 +548,7 @@ set(files __memory/ranges_construct_at.h __memory/ranges_uninitialized_algorithms.h __memory/raw_storage_iterator.h + __memory/relocate.h __memory/shared_ptr.h __memory/swap_allocator.h __memory/temp_value.h diff --git a/libcxx/include/__memory/relocate.h b/libcxx/include/__memory/relocate.h new file mode 100644 index 0000000000000..7420ed147b4a5 --- /dev/null +++ b/libcxx/include/__memory/relocate.h @@ -0,0 +1,27 @@ +#ifndef _LIBCPP___MEMORY_RELOCATE_H +#define _LIBCPP___MEMORY_RELOCATE_H + +#include <__config> +#include <__type_traits/is_const.h> +#include <__type_traits/is_trivially_relocatable.h> +#include + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_BEGIN_NAMESPACE_STD + +#if _LIBCPP_STD_VER >= 20 + +template + requires(is_trivially_relocatable_v && !is_const_v) +_LIBCPP_EXPORTED_FROM_ABI T* trivially_relocate(T* begin, T* end, T* new_location) { + return __builtin_trivially_relocate(new_location, begin, end - begin); +} + +#endif + +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP___MEMORY_RELOCATE_H diff --git a/libcxx/include/__type_traits/is_trivially_relocatable.h b/libcxx/include/__type_traits/is_trivially_relocatable.h index c0871731cc001..a16d79eecc250 100644 --- a/libcxx/include/__type_traits/is_trivially_relocatable.h +++ b/libcxx/include/__type_traits/is_trivially_relocatable.h @@ -21,22 +21,20 @@ _LIBCPP_BEGIN_NAMESPACE_STD -// A type is trivially relocatable if a move construct + destroy of the original object is equivalent to -// `memcpy(dst, src, sizeof(T))`. - -#if __has_builtin(__is_trivially_relocatable) -template -struct __libcpp_is_trivially_relocatable : integral_constant {}; -#else template -struct __libcpp_is_trivially_relocatable : is_trivially_copyable<_Tp> {}; -#endif +struct __libcpp_is_trivially_relocatable : integral_constant {}; template struct __libcpp_is_trivially_relocatable<_Tp, __enable_if_t::value> > : true_type {}; +template +struct is_trivially_relocatable : integral_constant {}; + +template +inline constexpr bool is_trivially_relocatable_v = __is_cpp_trivially_relocatable(_Tp); + _LIBCPP_END_NAMESPACE_STD #endif // _LIBCPP___TYPE_TRAITS_IS_TRIVIALLY_RELOCATABLE_H diff --git a/libcxx/include/memory b/libcxx/include/memory index b940a32c3ebe6..d87149a663eb6 100644 --- a/libcxx/include/memory +++ b/libcxx/include/memory @@ -945,6 +945,7 @@ template #include <__memory/out_ptr.h> #include <__memory/pointer_traits.h> #include <__memory/raw_storage_iterator.h> +#include <__memory/relocate.h> #include <__memory/shared_ptr.h> #include <__memory/temporary_buffer.h> #include <__memory/uninitialized_algorithms.h> From 94c1d9fe194d6702817c05568d5ddb8549a5888a Mon Sep 17 00:00:00 2001 From: Marshall Clow Date: Wed, 8 May 2024 07:04:22 -0700 Subject: [PATCH 03/32] Make std::string trivially relocatable --- libcxx/include/string | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libcxx/include/string b/libcxx/include/string index 3480b57375c11..c0cd88ad6b340 100644 --- a/libcxx/include/string +++ b/libcxx/include/string @@ -750,7 +750,10 @@ struct __uninitialized_size_tag {}; struct __init_with_sentinel_tag {}; template -class basic_string { +class basic_string + trivially_relocatable(is_trivially_relocatable_v<_Allocator> && + is_trivially_relocatable_v::pointer>) { + private: using __default_allocator_type = allocator<_CharT>; From 6f28dd81622af9cc53624edfa4193d75e50c0c2e Mon Sep 17 00:00:00 2001 From: Marshall Clow Date: Wed, 8 May 2024 07:05:08 -0700 Subject: [PATCH 04/32] Uglify definition of trivially_relocate --- libcxx/include/__memory/relocate.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libcxx/include/__memory/relocate.h b/libcxx/include/__memory/relocate.h index 7420ed147b4a5..aa04003c162ac 100644 --- a/libcxx/include/__memory/relocate.h +++ b/libcxx/include/__memory/relocate.h @@ -14,10 +14,10 @@ _LIBCPP_BEGIN_NAMESPACE_STD #if _LIBCPP_STD_VER >= 20 -template - requires(is_trivially_relocatable_v && !is_const_v) -_LIBCPP_EXPORTED_FROM_ABI T* trivially_relocate(T* begin, T* end, T* new_location) { - return __builtin_trivially_relocate(new_location, begin, end - begin); +template + requires(is_trivially_relocatable_v<_Tp> && !is_const_v<_Tp>) +_LIBCPP_EXPORTED_FROM_ABI _Tp* trivially_relocate(_Tp* __begin, _Tp* __end, _Tp* __new_location) noexcept { + return __builtin_trivially_relocate(__new_location, __begin, sizeof(_Tp) * (__end - __begin)); } #endif From 968ead0711942a308ab45ace17e1390dbd82907d Mon Sep 17 00:00:00 2001 From: Marshall Clow Date: Wed, 8 May 2024 07:05:56 -0700 Subject: [PATCH 05/32] Make __uninitialized_allocator_relocate use trivial relocation; enables this for vector::xxx_back --- libcxx/include/__memory/uninitialized_algorithms.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/libcxx/include/__memory/uninitialized_algorithms.h b/libcxx/include/__memory/uninitialized_algorithms.h index 8ff87e28b3bb5..314e845541814 100644 --- a/libcxx/include/__memory/uninitialized_algorithms.h +++ b/libcxx/include/__memory/uninitialized_algorithms.h @@ -21,6 +21,7 @@ #include <__memory/allocator_traits.h> #include <__memory/construct_at.h> #include <__memory/pointer_traits.h> +#include <__memory/relocate.h> #include <__memory/voidify.h> #include <__type_traits/enable_if.h> #include <__type_traits/extent.h> @@ -615,11 +616,22 @@ struct __allocator_has_trivial_destroy, _Up> : true_type {}; // - is_nothrow_move_constructible<_Tp> // - is_copy_constructible<_Tp> // - __libcpp_is_trivially_relocatable<_Tp> +// - is_trivially_relocatable_v<_Tp> template _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 void __uninitialized_allocator_relocate(_Alloc& __alloc, _Tp* __first, _Tp* __last, _Tp* __result) { static_assert(__is_cpp17_move_insertable<_Alloc>::value, "The specified type does not meet the requirements of Cpp17MoveInsertable"); + +#if _LIBCPP_STD_VER >= 20 + if (!__libcpp_is_constant_evaluated()) { + if constexpr (is_trivially_relocatable_v<_Tp>) { + (void) trivially_relocate(__first, __last, const_cast<__remove_const_t<_Tp>*>(__result)); + return ; + } + } +#endif + if (__libcpp_is_constant_evaluated() || !__libcpp_is_trivially_relocatable<_Tp>::value || !__allocator_has_trivial_move_construct<_Alloc, _Tp>::value || !__allocator_has_trivial_destroy<_Alloc, _Tp>::value) { From 706b25de066a02752103e1567cddbdbbd119ab87 Mon Sep 17 00:00:00 2001 From: Corentin Jabot Date: Thu, 9 May 2024 15:40:36 +0200 Subject: [PATCH 06/32] Implement swap_uses_value_representation --- clang/include/clang/Basic/TokenKinds.def | 1 + clang/lib/Sema/SemaExprCXX.cpp | 67 +++++++++++++++++++ .../SemaCXX/cxx2c-trivially-relocatable.cpp | 59 +++++++++++++++- 3 files changed, 126 insertions(+), 1 deletion(-) diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def index a448175303d1e..67321f5339247 100644 --- a/clang/include/clang/Basic/TokenKinds.def +++ b/clang/include/clang/Basic/TokenKinds.def @@ -558,6 +558,7 @@ TYPE_TRAIT_2(/*EmptySpellingName*/, IsDeducible, KEYCXX) TYPE_TRAIT_1(__is_bitwise_cloneable, IsBitwiseCloneable, KEYALL) TYPE_TRAIT_1(__is_cpp_trivially_relocatable, IsCppTriviallyRelocatable, KEYCXX) +TYPE_TRAIT_1(__can_swap_using_value_representation, CanSwapUsingValueRepresentation, KEYCXX) // Embarcadero Expression Traits EXPRESSION_TRAIT(__is_lvalue_expr, IsLValueExpr, KEYCXX) diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index fc8c4198dbdd3..34a4f94f9d8a3 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -5085,6 +5085,7 @@ static bool CheckUnaryTypeTraitTypeCompleteness(Sema &S, TypeTrait UTT, case UTT_IsTriviallyRelocatable: case UTT_IsTriviallyEqualityComparable: case UTT_IsCppTriviallyRelocatable: + case UTT_CanSwapUsingValueRepresentation: 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 @@ -5121,6 +5122,42 @@ static bool CheckUnaryTypeTraitTypeCompleteness(Sema &S, TypeTrait UTT, } } +static bool lookupStdTypeTraitMember(Sema &S, LookupResult &TraitMemberLookup, + SourceLocation Loc, StringRef Trait, + TemplateArgumentListInfo &Args, + unsigned DiagID) { + + DeclarationName Value = S.PP.getIdentifierInfo("value"); + LookupResult R(S, Value, Loc, Sema::LookupOrdinaryName); + + NamespaceDecl *Std = S.getStdNamespace(); + if (!Std) + return false; + + LookupResult Result(S, &S.PP.getIdentifierTable().get(Trait), + Loc, Sema::LookupOrdinaryName); + if (!S.LookupQualifiedName(Result, Std) || Result.isAmbiguous()) + return false; + + ClassTemplateDecl *TraitTD = Result.getAsSingle(); + if (!TraitTD) { + Result.suppressDiagnostics(); + return false; + } + + // Build the template-id. + QualType TraitTy = S.CheckTemplateIdType(TemplateName(TraitTD), Loc, Args); + if (TraitTy.isNull() || !S.isCompleteType(Loc, TraitTy)) + return false; + + CXXRecordDecl *RD = TraitTy->getAsCXXRecordDecl(); + assert(RD && "specialization of class template is not a class?"); + + // Look up the member of the trait type. + S.LookupQualifiedName(TraitMemberLookup, RD); + return !TraitMemberLookup.isAmbiguous(); +} + static bool HasNoThrowOperator(const RecordType *RT, OverloadedOperatorKind Op, Sema &Self, SourceLocation KeyLoc, ASTContext &C, bool (CXXRecordDecl::*HasTrivial)() const, @@ -5657,6 +5694,36 @@ static bool EvaluateUnaryTypeTrait(Sema &Self, TypeTrait UTT, return T.isBitwiseCloneableType(C); case UTT_IsCppTriviallyRelocatable: return T.isCppTriviallyRelocatableType(C); + case UTT_CanSwapUsingValueRepresentation: { + if(T->isIncompleteType() || T.isConstQualified() + || !T.isCppTriviallyRelocatableType(C)) + return false; + CXXRecordDecl *RD = T->getAsCXXRecordDecl(); + if(!RD) + return true; + if(RD->isPolymorphic()) + return false; + + return llvm::all_of(RD->fields(), [&](const FieldDecl *FD) { + auto Type = FD->getType(); + DeclarationName Value = Self.PP.getIdentifierInfo("value"); + LookupResult R(Self, Value, KeyLoc, Sema::LookupOrdinaryName); + TemplateArgumentListInfo Args(KeyLoc, KeyLoc); + Args.addArgument(Self.getTrivialTemplateArgumentLoc(Type, QualType(), KeyLoc)); + if (!lookupStdTypeTraitMember(Self, R, KeyLoc, "swap_uses_value_representation", Args, /*DiagID*/ 0) || + R.empty()) + return false; + ExprResult E = Self.BuildDeclarationNameExpr(CXXScopeSpec(), R, /*NeedsADL*/false); + if (E.isInvalid()) + return false; + llvm::APSInt Result; + E = Self.VerifyIntegerConstantExpression(E.get(), &Result); + if(E.isInvalid()) + return false; + return Result.getBoolValue(); + }); + break; + } case UTT_IsReferenceable: return T.isReferenceable(); case UTT_CanPassInRegs: diff --git a/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp b/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp index 21760fe05d8ef..32de3333d7192 100644 --- a/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp +++ b/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -std=c++2b -verify %s +// RUN: %clang_cc1 -std=c++2c -verify %s namespace Expressions { @@ -140,3 +140,60 @@ class TestDependentErrors trivially_relocatable : T {}; TestDependentErrors Ok; TestDependentErrors Err; // expected-note {{in instantiation of template class}} } + + +namespace std { + +template +struct swap_uses_value_representation { + static constexpr bool value = __can_swap_using_value_representation(T); +}; + + +template +constexpr bool swap_uses_value_representation_v = swap_uses_value_representation::value; + +} + +class S1 {}; +class S2 { int m1; }; +class S3 { const int m1; int m2; }; // expected-warning {{does not declare any constructor to initialize its non-modifiable members}} \ + // expected-note {{const member 'm1' will never be initialized}} +class S4 { const int &m1; int m2; }; // expected-warning {{does not declare any constructor to initialize its non-modifiable members}} \ + // expected-note {{reference member 'm1' will never be initialized}} +class S5 { int &m1; }; // expected-warning {{does not declare any constructor to initialize its non-modifiable members}} \ + // expected-note {{reference member 'm1' will never be initialized}} + +class Bad { int m1; }; + +namespace std { +template <> +struct swap_uses_value_representation { + static constexpr bool value = false; +}; + +} + + +using std::swap_uses_value_representation_v; + +static_assert(swap_uses_value_representation_v); +static_assert(!swap_uses_value_representation_v); +static_assert(!swap_uses_value_representation_v); +static_assert(!swap_uses_value_representation_v); +static_assert( swap_uses_value_representation_v); +static_assert( swap_uses_value_representation_v); +static_assert(!swap_uses_value_representation_v); +static_assert(!swap_uses_value_representation_v); +static_assert(!swap_uses_value_representation_v); +static_assert(!swap_uses_value_representation_v); + +struct empty {}; +struct Test { + empty nothing [[no_unique_address]]; + int data; +}; + +static_assert( swap_uses_value_representation_v); +static_assert( swap_uses_value_representation_v); +static_assert( sizeof(Test) == sizeof(int)); From a45ae8ec6b861a03dfe26da4119e56e47260d3ca Mon Sep 17 00:00:00 2001 From: Marshall Clow Date: Tue, 14 May 2024 07:58:52 -0700 Subject: [PATCH 07/32] Implement vector::erase using trivial relocation --- libcxx/include/vector | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/libcxx/include/vector b/libcxx/include/vector index 2442852c764a6..8f6f7af091f8e 100644 --- a/libcxx/include/vector +++ b/libcxx/include/vector @@ -1575,6 +1575,21 @@ vector<_Tp, _Allocator>::erase(const_iterator __position) { __position != end(), "vector::erase(iterator) called with a non-dereferenceable iterator"); difference_type __ps = __position - cbegin(); pointer __p = this->__begin_ + __ps; +#if _LIBCPP_STD_VER >= 20 + if (!__libcpp_is_constant_evaluated()) { + if constexpr (is_trivially_relocatable_v<_Tp>) { + size_type __old_size = size(); + // destroy the element at __p + __alloc_traits::destroy(__alloc(), std::__to_address(__p)); + // move the rest down + (void) trivially_relocate(__p + 1, this->__end_, __p); + // update the end + this->__end_--; + __annotate_shrink(__old_size); + return __make_iter(__p); + } + } +#endif this->__destruct_at_end(std::move(__p + 1, this->__end_, __p)); return __make_iter(__p); } @@ -1585,6 +1600,24 @@ vector<_Tp, _Allocator>::erase(const_iterator __first, const_iterator __last) { _LIBCPP_ASSERT_VALID_INPUT_RANGE(__first <= __last, "vector::erase(first, last) called with invalid range"); pointer __p = this->__begin_ + (__first - begin()); if (__first != __last) { +#if _LIBCPP_STD_VER >= 20 + if (!__libcpp_is_constant_evaluated()) { + if constexpr (is_trivially_relocatable_v<_Tp>) { + size_type __old_size = size(); + size_type __count = __last - __first; + pointer __p0 = __p + __count; + // destroy the elements at [__p, __p0) + for (pointer __to_destroy = __p; __to_destroy < __p0; ++__to_destroy) + __alloc_traits::destroy(__alloc(), std::__to_address(__to_destroy)); + // move the rest down + (void) trivially_relocate(__p0, this->__end_, __p); + // update the end + this->__end_ -= __count; + __annotate_shrink(__old_size); + return __make_iter(__p); + } + } +#endif this->__destruct_at_end(std::move(__p + (__last - __first), this->__end_, __p)); } return __make_iter(__p); From ab671338d1434f5c6da0858fec177f3f425b60df Mon Sep 17 00:00:00 2001 From: Marshall Clow Date: Tue, 14 May 2024 08:15:32 -0700 Subject: [PATCH 08/32] Add missing include --- libcxx/include/vector | 1 + 1 file changed, 1 insertion(+) diff --git a/libcxx/include/vector b/libcxx/include/vector index 8f6f7af091f8e..d7359650bbd0a 100644 --- a/libcxx/include/vector +++ b/libcxx/include/vector @@ -336,6 +336,7 @@ template requires is-vector-bool-reference // Since C++ #include <__memory/allocator_traits.h> #include <__memory/noexcept_move_assign_container.h> #include <__memory/pointer_traits.h> +#include <__memory/relocate.h> #include <__memory/swap_allocator.h> #include <__memory/temp_value.h> #include <__memory/uninitialized_algorithms.h> From 1258c97e2d4f99ca704b4fdad4115cf66a0b402e Mon Sep 17 00:00:00 2001 From: Marshall Clow Date: Thu, 13 Jun 2024 09:08:09 -0700 Subject: [PATCH 09/32] Add 'unintialized_relocate' --- .../__memory/uninitialized_algorithms.h | 197 ++++++++++++++++++ 1 file changed, 197 insertions(+) diff --git a/libcxx/include/__memory/uninitialized_algorithms.h b/libcxx/include/__memory/uninitialized_algorithms.h index 314e845541814..def1aaa717ec5 100644 --- a/libcxx/include/__memory/uninitialized_algorithms.h +++ b/libcxx/include/__memory/uninitialized_algorithms.h @@ -655,6 +655,203 @@ __uninitialized_allocator_relocate(_Alloc& __alloc, _Tp* __first, _Tp* __last, _ } } +// Trivial relocation stuff +#if _LIBCPP_STD_VER >= 20 + +namespace { +// Implementation details --- using an anonymous namespace within this single TU prototype +template +concept nothrow_input_iterator = input_iterator<_Iter>; + +template +concept nothrow_forward_iterator = forward_iterator<_Iter>; + +// We need a concept to distinguish the "not" case at the end --- subsumption +// does not kick in, so could maybe be just a constexpr boolean variable +// template. + +template +concept relocatable_iterators + = std::same_as, std::iter_value_t<_OutIter>> + and std::is_lvalue_reference_v> + and std::is_lvalue_reference_v>; + + +template +concept no_fail_relocatable_iterators + = relocatable_iterators<_InIter, _OutIter> + and ( std::is_nothrow_move_constructible_v> + or std::is_trivially_relocatable_v> + ); +} // unnamed namespace + +#ifndef __cpp_contracts +# define contract_assert(...) _LIBCPP_ASSERT_INTERNAL(__VA_ARGS__) +#endif + +template + requires no_fail_relocatable_iterators<_InIter, _OutIter> +auto unintialized_relocate(_InIter __first, _InIter __last, _OutIter __result) noexcept + -> _OutIter { + // Note that by design, none of the operations in this implementation are + // potentially-throwing --- hence there are no concerns about exception + // safety + + // One implementation bug deferred from proof of concept is that we assume + // that the object pointer type can be implicitly converted to the + // contiguous output iterator when creating the return value. I have yet __result + // confirm that this is mandated by the relevant concepts + // Using pointers to relocate whole range at once, and remove + // no-throw-iterator concerns from support for contiguous iterators. There + // remains corner-case concerns to clean up for throwing `operator*` and + // dereferencing past-the-end iterators for empty ranges. + auto * __begin = std::addressof(*__first); + auto * __end = __begin + (__last - __first); + auto * __new_location = std::addressof(*__result); + + // This check is redundant if we follow all the branches below, but makes + // the design intent clear. + if (__begin == __new_location) + return __result; + + // For trivially relocatable type, just delegate to the core language primitive + if constexpr (is_trivially_relocatable_v>) { + (void) trivially_relocate(__begin, __end, __new_location); + return __result + (__last - __first); + } + + // For non-trivial relocation, we must detect and support overlapping ranges + // ourselves. At this point we no longer need to worry about trivial + // relocatable types but are not move-constructible. + + if (__less<>{}(__end, __new_location) || __less<>{}(__new_location + (__end - __begin), __begin)) { + // No overlap + _OutIter __result0 = uninitialized_move(__first, __last, __result); + destroy(__begin, __end); + return __result0; + } + else if (__less<>{}(__begin, __new_location)) { + // move-and-destroy each member, back to front + + // Implementation note: we could just modify `__new_location` rather than + // create the local `dest`, but want to clearly show the algorithm + // without micro-optimizing. + auto * __dest = __new_location + (__end - __begin); + while (__dest != __new_location) { + __dest = std::__construct_at(--__dest, std::move(*--__end)); + std::__destroy_at(__end); + } + return __result + (__last - __first); + + } + else if (__begin == __new_location) { + // Note that this is the same check redundantly hoisted out of the + // if/elif/else flow, but is NOT redundant here + return __result; + } + else { + // move-and-destroy each member, front to back + while (__first != __last) { + (void) __construct_at(std::addressof(*__result), std::move(*__first)); + std::__destroy_at(std::addressof(*__first)); + ++__result; + ++__first; + } + return __result; + } + + __libcpp_unreachable(); +} + +template + requires no_fail_relocatable_iterators<_InIter, _OutIter> +auto unintialized_relocate(_InIter __first, _InIter __last, _OutIter __result) noexcept + -> _OutIter { + // There is no support for overlapping ranges unless both iterator types are + // contiguous iterators, which would be subsumed by the contiguous itetator + // overload. + + // Note that there are trivially relocatable types that are not no-throw + // move constructible and vice-versa, so we need to handle both cases. In + // doing so, we should pick a preferred implementation for types that are + // both trivially relocatable and no-throw move constructible. + if (__first == __last) + return __result; + + // Think carefully about iterator invalidation when destroying elements. + // The post-increment on `__first` seems important, in case element + // destruction invalidates an iterator, which would be the case for a + // pointer. + while (__first != __last) { + auto * __begin = std::addressof(*__first++); + auto * __new_location = std::addressof(*__result++); + + if constexpr(is_trivially_relocatable_v>) { + (void) trivially_relocate(__begin, __begin + 1, __new_location); + } + else { + (void) std::__construct_at(__new_location, std::move(*__begin)); + std::__destroy_at(__begin); + } + } + + return __result; +} + +template + requires relocatable_iterators<_InIter, _OutIter> + and (!no_fail_relocatable_iterators<_InIter, _OutIter>) + and std::is_move_constructible_v> +auto unintialized_relocate(_InIter __first, _InIter __last, _OutIter __result) // NOT declared noexcept + -> _OutIter { + // The move-constructor can throw, so relocation is not guaranteed to + // succeed. The opt for the vector-like strong exception safety guarantee + // for this operation: if move can throw, but the type is + // copy-constructible, then *copy* the range, so that we can safely unwind + // if an exception is thrown without leaving the initially relocated + // elements in an unknown state. Otherwise, we will *move* all of the + // elements, and leave those elements in an unspecified valid state. Only + // if the copy/move operation succeeds are the original elements destroyed. + if (__first == __last) + return __result; + + if constexpr(std::contiguous_iterator<_InIter> and std::contiguous_iterator<_OutIter>) { + // Note that once we complete overload resolution, all viable iterators + // referring to trivially relocatable types should have directed to an + // overload above. We might still have contiguous iterators to + // move-constructible types that may throw, and are not trivially + // relocatable, which means we may be able to assert preconditions that + // overlapping ranges are not supported. + // check for overlapping range with a contract_assert +#if defined(__clang__) + contract_assert(!__is_pointer_in_range(std::addressof(*__first), std::addressof(*__last), + std::addressof(*__result)), "Overlapping range in unintialized_relocate"); +#else + const size_t __count = __last - __first; + auto * __begin = std::addressof(*__first); + auto * __end = __begin + __count; // `__last` is often not a derefencerable iterator + auto * __new_location = std::addressof(*__result); + auto __less_ = std::less<>{}; + // Note that __end == __new_location is fine + contract_assert(__less_(__end, __new_location) || __less_(__new_location + __count, __begin), "Overlapping range in unintialized_relocate"); +#endif + } + + // Remember original `__result` may be invalidated by this operation, so we need + // to capture the return value. + if constexpr(is_copy_constructible_v>) + __result = std::uninitialized_copy(__first, __last, __result); // cleans up new range if any copy throws + else + __result = std::uninitialized_move(__first, __last, __result); // cleans up new range if any move throws + + std::__destroy(__first, __last); // "Library UB" if destructor throws + return __result; + } + +// Need to add the ranges stuff here + +#endif + _LIBCPP_END_NAMESPACE_STD _LIBCPP_POP_MACROS From fdcaf90125f364c3f2634cad5c27a79dfa25fa71 Mon Sep 17 00:00:00 2001 From: Marshall Clow Date: Thu, 13 Jun 2024 09:08:49 -0700 Subject: [PATCH 10/32] Rework erase, insert, and emplace to use 'unintialized_relocate' --- libcxx/include/vector | 85 ++++++++++++++++++++++++++++++++----------- 1 file changed, 63 insertions(+), 22 deletions(-) diff --git a/libcxx/include/vector b/libcxx/include/vector index d7359650bbd0a..0e889cd23eee0 100644 --- a/libcxx/include/vector +++ b/libcxx/include/vector @@ -1577,19 +1577,17 @@ vector<_Tp, _Allocator>::erase(const_iterator __position) { difference_type __ps = __position - cbegin(); pointer __p = this->__begin_ + __ps; #if _LIBCPP_STD_VER >= 20 - if (!__libcpp_is_constant_evaluated()) { - if constexpr (is_trivially_relocatable_v<_Tp>) { - size_type __old_size = size(); + if constexpr (is_trivially_relocatable_v<_Tp>) { + size_type __old_size = size(); // destroy the element at __p - __alloc_traits::destroy(__alloc(), std::__to_address(__p)); + __alloc_traits::destroy(__alloc(), std::__to_address(__p)); // move the rest down - (void) trivially_relocate(__p + 1, this->__end_, __p); + (void) unintialized_relocate(__p + 1, this->__end_, __p); // update the end - this->__end_--; - __annotate_shrink(__old_size); - return __make_iter(__p); - } - } + this->__end_--; + __annotate_shrink(__old_size); + return __make_iter(__p); + } #endif this->__destruct_at_end(std::move(__p + 1, this->__end_, __p)); return __make_iter(__p); @@ -1602,22 +1600,20 @@ vector<_Tp, _Allocator>::erase(const_iterator __first, const_iterator __last) { pointer __p = this->__begin_ + (__first - begin()); if (__first != __last) { #if _LIBCPP_STD_VER >= 20 - if (!__libcpp_is_constant_evaluated()) { - if constexpr (is_trivially_relocatable_v<_Tp>) { - size_type __old_size = size(); - size_type __count = __last - __first; - pointer __p0 = __p + __count; + if constexpr (is_trivially_relocatable_v<_Tp>) { + size_type __old_size = size(); + size_type __count = __last - __first; + pointer __p0 = __p + __count; // destroy the elements at [__p, __p0) - for (pointer __to_destroy = __p; __to_destroy < __p0; ++__to_destroy) - __alloc_traits::destroy(__alloc(), std::__to_address(__to_destroy)); + for (pointer __to_destroy = __p; __to_destroy < __p0; ++__to_destroy) + __alloc_traits::destroy(__alloc(), std::__to_address(__to_destroy)); // move the rest down - (void) trivially_relocate(__p0, this->__end_, __p); + (void) unintialized_relocate(__p0, this->__end_, __p); // update the end - this->__end_ -= __count; - __annotate_shrink(__old_size); - return __make_iter(__p); + this->__end_ -= __count; + __annotate_shrink(__old_size); + return __make_iter(__p); } - } #endif this->__destruct_at_end(std::move(__p + (__last - __first), this->__end_, __p)); } @@ -1647,6 +1643,25 @@ vector<_Tp, _Allocator>::insert(const_iterator __position, const_reference __x) if (__p == this->__end_) { __construct_one_at_end(__x); } else { +#if _LIBCPP_STD_VER >= 20 + if (!__libcpp_is_constant_evaluated()) { + if constexpr (is_trivially_relocatable_v<_Tp>) { + // Make space by trivially relocating everything + _ConstructTransaction __tx(*this, 1); + (void) trivially_relocate(__p, this->__end_, __p + 1); + // construct the new element (not assign!) + const_pointer __xr = pointer_traits::pointer_to(__x); + if (std::__is_pointer_in_range(std::__to_address(__p), std::__to_address(__end_), std::addressof(__x))) + ++__xr; + __alloc_traits::construct(this->__alloc(), std::__to_address(__p), *__xr); + ++__tx.__pos_; + // Need to fix up upon an exception! + // update all the invariants. + // return an iterator to the new entry + return __make_iter(__p); + } + } +#endif __move_range(__p, this->__end_, __p + 1); const_pointer __xr = pointer_traits::pointer_to(__x); if (std::__is_pointer_in_range(std::__to_address(__p), std::__to_address(__end_), std::addressof(__x))) @@ -1670,8 +1685,21 @@ vector<_Tp, _Allocator>::insert(const_iterator __position, value_type&& __x) { if (__p == this->__end_) { __construct_one_at_end(std::move(__x)); } else { +#if _LIBCPP_STD_VER >= 20 + // Make space by trivially relocating everything + _ConstructTransaction __tx(*this, 1); + (void) unintialized_relocate(__p, this->__end_, __p + 1); + // construct the new element (not assign!) + __alloc_traits::construct(this->__alloc(), std::__to_address(__p), std::forward(__x)); + ++__tx.__pos_; + // Need to fix up upon an exception! + // update all the invariants. + // return an iterator to the new entry + return __make_iter(__p); +#else __move_range(__p, this->__end_, __p + 1); *__p = std::move(__x); +#endif } } else { allocator_type& __a = this->__alloc(); @@ -1691,9 +1719,22 @@ vector<_Tp, _Allocator>::emplace(const_iterator __position, _Args&&... __args) { if (__p == this->__end_) { __construct_one_at_end(std::forward<_Args>(__args)...); } else { +#if _LIBCPP_STD_VER >= 20 + // Make space by trivially relocating everything + _ConstructTransaction __tx(*this, 1); + (void) unintialized_relocate(__p, this->__end_, __p + 1); + // construct the new element + __alloc_traits::construct(this->__alloc(), std::__to_address(__p), std::forward<_Args>(__args)...); + ++__tx.__pos_; + // Need to fix up upon an exception! + // update all the invariants. + // return an iterator to the new entry + return __make_iter(__p); +#else __temp_value __tmp(this->__alloc(), std::forward<_Args>(__args)...); __move_range(__p, this->__end_, __p + 1); *__p = std::move(__tmp.get()); +#endif } } else { allocator_type& __a = this->__alloc(); From ffda9987208fa6fb9f5f3a1d4817ddf6587ada9b Mon Sep 17 00:00:00 2001 From: Marshall Clow Date: Fri, 14 Jun 2024 17:15:34 -0700 Subject: [PATCH 11/32] Replace the use of 'unintialized_relocate' with 'trivially_relocate' in the overlapping cases (emplace, insert, erase) --- .../__memory/uninitialized_algorithms.h | 10 ++-- libcxx/include/vector | 48 +++++++++---------- 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/libcxx/include/__memory/uninitialized_algorithms.h b/libcxx/include/__memory/uninitialized_algorithms.h index def1aaa717ec5..64294cec9a7af 100644 --- a/libcxx/include/__memory/uninitialized_algorithms.h +++ b/libcxx/include/__memory/uninitialized_algorithms.h @@ -39,6 +39,8 @@ #include <__utility/pair.h> #include +#include + #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) # pragma GCC system_header #endif @@ -685,10 +687,12 @@ concept no_fail_relocatable_iterators ); } // unnamed namespace +// Forward looking for asserting preconditions --- tune feature macro as needed #ifndef __cpp_contracts -# define contract_assert(...) _LIBCPP_ASSERT_INTERNAL(__VA_ARGS__) +# define contract_assert(...) assert(__VA_ARGS__) #endif + template requires no_fail_relocatable_iterators<_InIter, _OutIter> auto unintialized_relocate(_InIter __first, _InIter __last, _OutIter __result) noexcept @@ -825,7 +829,7 @@ auto unintialized_relocate(_InIter __first, _InIter __last, _OutIter __result) / // check for overlapping range with a contract_assert #if defined(__clang__) contract_assert(!__is_pointer_in_range(std::addressof(*__first), std::addressof(*__last), - std::addressof(*__result)), "Overlapping range in unintialized_relocate"); + std::addressof(*__result))); #else const size_t __count = __last - __first; auto * __begin = std::addressof(*__first); @@ -833,7 +837,7 @@ auto unintialized_relocate(_InIter __first, _InIter __last, _OutIter __result) / auto * __new_location = std::addressof(*__result); auto __less_ = std::less<>{}; // Note that __end == __new_location is fine - contract_assert(__less_(__end, __new_location) || __less_(__new_location + __count, __begin), "Overlapping range in unintialized_relocate"); + contract_assert(__less_(__end, __new_location) || __less_(__new_location + __count, __begin)); #endif } diff --git a/libcxx/include/vector b/libcxx/include/vector index 0e889cd23eee0..0cc66dc486535 100644 --- a/libcxx/include/vector +++ b/libcxx/include/vector @@ -1582,7 +1582,7 @@ vector<_Tp, _Allocator>::erase(const_iterator __position) { // destroy the element at __p __alloc_traits::destroy(__alloc(), std::__to_address(__p)); // move the rest down - (void) unintialized_relocate(__p + 1, this->__end_, __p); + (void) trivially_relocate(__p + 1, this->__end_, __p); // update the end this->__end_--; __annotate_shrink(__old_size); @@ -1608,7 +1608,7 @@ vector<_Tp, _Allocator>::erase(const_iterator __first, const_iterator __last) { for (pointer __to_destroy = __p; __to_destroy < __p0; ++__to_destroy) __alloc_traits::destroy(__alloc(), std::__to_address(__to_destroy)); // move the rest down - (void) unintialized_relocate(__p0, this->__end_, __p); + (void) trivially_relocate(__p0, this->__end_, __p); // update the end this->__end_ -= __count; __annotate_shrink(__old_size); @@ -1644,22 +1644,20 @@ vector<_Tp, _Allocator>::insert(const_iterator __position, const_reference __x) __construct_one_at_end(__x); } else { #if _LIBCPP_STD_VER >= 20 - if (!__libcpp_is_constant_evaluated()) { - if constexpr (is_trivially_relocatable_v<_Tp>) { - // Make space by trivially relocating everything - _ConstructTransaction __tx(*this, 1); - (void) trivially_relocate(__p, this->__end_, __p + 1); - // construct the new element (not assign!) - const_pointer __xr = pointer_traits::pointer_to(__x); - if (std::__is_pointer_in_range(std::__to_address(__p), std::__to_address(__end_), std::addressof(__x))) - ++__xr; - __alloc_traits::construct(this->__alloc(), std::__to_address(__p), *__xr); - ++__tx.__pos_; - // Need to fix up upon an exception! - // update all the invariants. - // return an iterator to the new entry - return __make_iter(__p); - } + if constexpr (is_trivially_relocatable_v<_Tp>) { + // Make space by trivially relocating everything + _ConstructTransaction __tx(*this, 1); + (void) trivially_relocate(__p, this->__end_, __p + 1); + // construct the new element (not assign!) + const_pointer __xr = pointer_traits::pointer_to(__x); + if (std::__is_pointer_in_range(std::__to_address(__p), std::__to_address(__end_), std::addressof(__x))) + ++__xr; + __alloc_traits::construct(this->__alloc(), std::__to_address(__p), *__xr); + ++__tx.__pos_; + // Need to fix up upon an exception! + // update all the invariants. + // return an iterator to the new entry + return __make_iter(__p); } #endif __move_range(__p, this->__end_, __p + 1); @@ -1686,9 +1684,10 @@ vector<_Tp, _Allocator>::insert(const_iterator __position, value_type&& __x) { __construct_one_at_end(std::move(__x)); } else { #if _LIBCPP_STD_VER >= 20 + if constexpr (is_trivially_relocatable_v<_Tp>) { // Make space by trivially relocating everything _ConstructTransaction __tx(*this, 1); - (void) unintialized_relocate(__p, this->__end_, __p + 1); + (void) trivially_relocate(__p, this->__end_, __p + 1); // construct the new element (not assign!) __alloc_traits::construct(this->__alloc(), std::__to_address(__p), std::forward(__x)); ++__tx.__pos_; @@ -1696,10 +1695,10 @@ vector<_Tp, _Allocator>::insert(const_iterator __position, value_type&& __x) { // update all the invariants. // return an iterator to the new entry return __make_iter(__p); -#else + } +#endif __move_range(__p, this->__end_, __p + 1); *__p = std::move(__x); -#endif } } else { allocator_type& __a = this->__alloc(); @@ -1720,9 +1719,10 @@ vector<_Tp, _Allocator>::emplace(const_iterator __position, _Args&&... __args) { __construct_one_at_end(std::forward<_Args>(__args)...); } else { #if _LIBCPP_STD_VER >= 20 + if constexpr (is_trivially_relocatable_v<_Tp>) { // Make space by trivially relocating everything _ConstructTransaction __tx(*this, 1); - (void) unintialized_relocate(__p, this->__end_, __p + 1); + (void) trivially_relocate(__p, this->__end_, __p + 1); // construct the new element __alloc_traits::construct(this->__alloc(), std::__to_address(__p), std::forward<_Args>(__args)...); ++__tx.__pos_; @@ -1730,11 +1730,11 @@ vector<_Tp, _Allocator>::emplace(const_iterator __position, _Args&&... __args) { // update all the invariants. // return an iterator to the new entry return __make_iter(__p); -#else + } +#endif __temp_value __tmp(this->__alloc(), std::forward<_Args>(__args)...); __move_range(__p, this->__end_, __p + 1); *__p = std::move(__tmp.get()); -#endif } } else { allocator_type& __a = this->__alloc(); From 3b23afc6a011bb44c3c3bcccb5b158a043f08dd5 Mon Sep 17 00:00:00 2001 From: Corentin Jabot Date: Sun, 4 Aug 2024 21:53:23 +0200 Subject: [PATCH 12/32] rough draft of memberwise_trivially_relocatable/memberwise_repleceable --- .../clang/AST/CXXRecordDeclDefinitionBits.def | 6 + clang/include/clang/AST/DeclCXX.h | 67 +++++++--- clang/include/clang/AST/Type.h | 2 + .../clang/Basic/DiagnosticParseKinds.td | 4 +- clang/include/clang/Basic/TokenKinds.def | 2 +- clang/include/clang/Parse/Parser.h | 12 +- clang/include/clang/Sema/Sema.h | 13 +- clang/lib/AST/DeclCXX.cpp | 20 ++- clang/lib/AST/Type.cpp | 14 ++ clang/lib/Parse/ParseDeclCXX.cpp | 82 ++++++++---- clang/lib/Parse/Parser.cpp | 1 + clang/lib/Sema/SemaChecking.cpp | 2 +- clang/lib/Sema/SemaDecl.cpp | 39 ++---- clang/lib/Sema/SemaDeclCXX.cpp | 124 ++++++++++++++--- clang/lib/Sema/SemaExprCXX.cpp | 70 +--------- clang/lib/Sema/SemaTemplateInstantiate.cpp | 15 +-- .../Parser/cxx2c-trivially-relocatable.cpp | 17 +-- .../SemaCXX/cxx2c-trivially-relocatable.cpp | 126 +++--------------- 18 files changed, 318 insertions(+), 298 deletions(-) diff --git a/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def b/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def index 29346cb184565..c89cc5311b739 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(ExplicitlyDeletedMoveAssignmentOrCtr, 1, NO_MERGE) + /// The special members which have been declared for this class, /// either by the user or implicitly. FIELD(DeclaredSpecialMembers, 6, MERGE_OR) @@ -255,4 +259,6 @@ FIELD(IsHLSLIntangible, 1, NO_MERGE) FIELD(IsTriviallyRelocatable, 1, NO_MERGE) +FIELD(IsMemberwiseReplaceable, 1, NO_MERGE) + #undef FIELD diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h index a4d080f7160e2..c4e36e5a50460 100644 --- a/clang/include/clang/AST/DeclCXX.h +++ b/clang/include/clang/AST/DeclCXX.h @@ -127,34 +127,33 @@ class AccessSpecDecl : public Decl { static bool classofKind(Kind K) { return K == AccessSpec; } }; -class TriviallyRelocatableSpecifier { +enum class MemberwiseRelocatableOrReplaceableKind { + Relocatable, + Replaceable +}; + +template +class BasicMemberwiseSpecifier { public: - enum Kind { TRK_Invalid, TRK_True, TRK_False, TRK_Dependent }; - TriviallyRelocatableSpecifier() = default; - TriviallyRelocatableSpecifier(Kind TriviallyRelocatableKind, - SourceLocation Begin, Expr *E = nullptr) - : ExprAndKind(E, TriviallyRelocatableKind), Loc(Begin) {} - void SetTriviallyRelocatable(Kind TriviallyRelocatableKind, - SourceLocation Begin, Expr *E = nullptr) { - ExprAndKind.setPointerAndInt(E, TriviallyRelocatableKind); + BasicMemberwiseSpecifier() = default; + BasicMemberwiseSpecifier(SourceLocation Begin) + : Loc(Begin) {} + void Set(SourceLocation Begin) { Loc = Begin; } bool isSet() const { return !Loc.isInvalid(); } - bool isValid() const { return ExprAndKind.getInt() != TRK_Invalid; } - - Kind getKind() const { return ExprAndKind.getInt(); } - SourceLocation getLocation() const { return Loc; } - Expr *getCondition() const { return ExprAndKind.getPointer(); } - private: - llvm::PointerIntPair ExprAndKind{nullptr, TRK_Invalid}; SourceLocation Loc; }; +using TriviallyRelocatableSpecifier = BasicMemberwiseSpecifier; +using MemberwiseReplaceableSpecifier = BasicMemberwiseSpecifier; + + /// Represents a base class of a C++ class. /// /// Each CXXBaseSpecifier represents a single, direct base class (or @@ -379,6 +378,8 @@ class CXXRecordDecl : public RecordDecl { TriviallyRelocatableSpecifier TriviallyRelocatable; + MemberwiseReplaceableSpecifier MemberwiseReplaceable; + DefinitionData(CXXRecordDecl *D); /// Retrieve the set of direct base classes. @@ -752,6 +753,14 @@ class CXXRecordDecl : public RecordDecl { return data().DefaultedMoveConstructorIsDeleted; } + bool defaultedMoveAssignmentIsDeleted() const { + assert((!needsOverloadResolutionForMoveAssignment() || + (data().DeclaredSpecialMembers & SMF_MoveAssignment)) && + "this property has not yet been computed by Sema"); + return data().DefaultedMoveAssignmentIsDeleted; + } + + /// \c true if a defaulted destructor for this class would be deleted. bool defaultedDestructorIsDeleted() const { assert((!needsOverloadResolutionForDestructor() || @@ -836,6 +845,18 @@ class CXXRecordDecl : public RecordDecl { return data().UserDeclaredSpecialMembers & SMF_CopyConstructor; } + bool hasUserProvidedCopyAssignment() const { + return data().UserProvidedCopyAssignment; + } + + bool hasUserProvidedMoveAssignment() const { + return data().UserProvidedCopyAssignment; + } + + bool hasExplicitlyDeletedMoveAssignmentOpOrCtr() const { + return data().ExplicitlyDeletedMoveAssignmentOrCtr; + } + /// Determine whether this class needs an implicit copy /// constructor to be lazily declared. bool needsImplicitCopyConstructor() const { @@ -1498,12 +1519,23 @@ class CXXRecordDecl : public RecordDecl { return data().TriviallyRelocatable; } + MemberwiseReplaceableSpecifier getMemberwiseReplaceableSpecifier() const { + return data().MemberwiseReplaceable; + } + bool isTriviallyRelocatable() const { return data().IsTriviallyRelocatable; } void setIsTriviallyRelocatable(bool Set) { data().IsTriviallyRelocatable = Set; } + + bool isMemberwiseReplaceable() const { return data().IsMemberwiseReplaceable; } + + void setIsMemberwiseReplaceable(bool Set) { + data().IsMemberwiseReplaceable = Set; + } + /// Notify the class that this destructor is now selected. /// /// Important properties of the class depend on destructor properties. Since @@ -1942,6 +1974,9 @@ class CXXRecordDecl : public RecordDecl { void setTriviallyRelocatableSpecifier(TriviallyRelocatableSpecifier TRS) { data().TriviallyRelocatable = TRS; } + void setMemberwiseReplaceableSpecifier(MemberwiseReplaceableSpecifier MRS) { + data().MemberwiseReplaceable = 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 55829066919e4..c84541fe4c738 100644 --- a/clang/include/clang/AST/Type.h +++ b/clang/include/clang/AST/Type.h @@ -1144,6 +1144,8 @@ class QualType { bool isCppTriviallyRelocatableType(const ASTContext &Context) const; + bool isMemberwiseReplaceableType(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/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td index 53b96cab430b2..fa61b218a22af 100644 --- a/clang/include/clang/Basic/DiagnosticParseKinds.td +++ b/clang/include/clang/Basic/DiagnosticParseKinds.td @@ -1049,8 +1049,8 @@ def err_access_specifier_interface : Error< def err_duplicate_class_virt_specifier : Error< "class already marked '%0'">; -def err_duplicate_class_trivially_relocatable : Error< - "class already marked 'trivially_relocatable'">; +def err_duplicate_class_memberwise_specifier : Error< + "class already marked %select{'memberwise_trivially_relocatable'|'memberwise_replaceable'}0">; def err_duplicate_virt_specifier : Error< "class member already marked '%0'">; diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def index 67321f5339247..ac9ce7bede33f 100644 --- a/clang/include/clang/Basic/TokenKinds.def +++ b/clang/include/clang/Basic/TokenKinds.def @@ -558,7 +558,7 @@ TYPE_TRAIT_2(/*EmptySpellingName*/, IsDeducible, KEYCXX) TYPE_TRAIT_1(__is_bitwise_cloneable, IsBitwiseCloneable, KEYALL) TYPE_TRAIT_1(__is_cpp_trivially_relocatable, IsCppTriviallyRelocatable, KEYCXX) -TYPE_TRAIT_1(__can_swap_using_value_representation, CanSwapUsingValueRepresentation, KEYCXX) +TYPE_TRAIT_1(__is_memberwise_replaceable, IsMemberwiseReplaceable, 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 5ad251ce3ef5f..ef329352d2ce5 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -13,6 +13,7 @@ #ifndef LLVM_CLANG_PARSE_PARSER_H #define LLVM_CLANG_PARSE_PARSER_H +#include "clang/AST/DeclCXX.h" #include "clang/Basic/OpenACCKinds.h" #include "clang/Basic/OperatorPrecedence.h" #include "clang/Lex/CodeCompletionHandler.h" @@ -165,6 +166,7 @@ class Parser : public CodeCompletionHandler { mutable IdentifierInfo *Ident_GNU_final; mutable IdentifierInfo *Ident_override; mutable IdentifierInfo *Ident_trivially_relocatable; + mutable IdentifierInfo *Ident_memberwise_replaceable; // C++2a contextual keywords. mutable IdentifierInfo *Ident_import; @@ -3168,9 +3170,15 @@ class Parser : public CodeCompletionHandler { bool isCXX2CTriviallyRelocatableKeyword(Token Tok) const; bool isCXX2CTriviallyRelocatableKeyword() const; - void ParseOptionalCXX2CTriviallyRelocatableSpecifier( - TriviallyRelocatableSpecifier &TRS); bool SkipCXX2CTriviallyRelocatableSpecifier(); + void ParseOptionalCXX2CTriviallyRelocatableSpecifier(TriviallyRelocatableSpecifier &TRS); + + + bool isCXX2CMemberwiseReplaceableKeyword(Token Tok) const; + bool isCXX2CMemberwiseReplaceableKeyword() const; + void ParseOptionalCXX2CMemberwiseReplaceableSpecifier(MemberwiseReplaceableSpecifier &MRS); + bool SkipCXX2CMemberwiseReplaceableSpecifier(); + bool isClassCompatibleKeyword(Token Tok) const; bool isClassCompatibleKeyword() const; diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 96ef4c905626a..44fff567b3b12 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -17,9 +17,14 @@ #include "clang/APINotes/APINotesManager.h" #include "clang/AST/ASTFwd.h" #include "clang/AST/Attr.h" +<<<<<<< HEAD #include "clang/AST/AttrIterator.h" #include "clang/AST/CharUnits.h" #include "clang/AST/DeclBase.h" +======= +#include "clang/AST/Availability.h" +#include "clang/AST/ComparisonCategories.h" +>>>>>>> dec76e3b17fb (rough draft of memberwise_trivially_relocatable/memberwise_repleceable) #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclTemplate.h" #include "clang/AST/DeclarationName.h" @@ -3983,8 +3988,9 @@ 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, Expr *E); + TriviallyRelocatableSpecifier ActOnTriviallyRelocatableSpecifier(SourceLocation Loc); + + MemberwiseReplaceableSpecifier ActOnMemberwiseReplaceableSpecifier(SourceLocation Loc); /// ActOnStartCXXMemberDeclarations - Invoked when we have parsed a /// C++ record definition's base-specifiers clause and are starting its @@ -3993,6 +3999,7 @@ class Sema final : public SemaBase { Scope *S, Decl *TagDecl, SourceLocation FinalLoc, bool IsFinalSpelledSealed, bool IsAbstract, TriviallyRelocatableSpecifier TriviallyRelocatable, + MemberwiseReplaceableSpecifier MemberwiseReplaceable, SourceLocation LBraceLoc); /// ActOnTagFinishDefinition - Invoked once we have finished parsing @@ -4002,6 +4009,8 @@ class Sema final : public SemaBase { void CheckCXX2CTriviallyRelocatable(CXXRecordDecl *D); + void CheckMemberwiseReplaceable(CXXRecordDecl *D); + void ActOnTagFinishSkippedDefinition(SkippedDefinitionContext Context); /// ActOnTagDefinitionError - Invoked when there was an unrecoverable diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp index 8aa52ed237580..ad3f3e77d5fc2 100644 --- a/clang/lib/AST/DeclCXX.cpp +++ b/clang/lib/AST/DeclCXX.cpp @@ -103,14 +103,18 @@ CXXRecordDecl::DefinitionData::DefinitionData(CXXRecordDecl *D) HasConstexprDefaultConstructor(false), DefaultedDestructorIsConstexpr(true), HasNonLiteralTypeFieldsOrBases(false), StructuralIfLiteral(true), - UserProvidedDefaultConstructor(false), DeclaredSpecialMembers(0), + UserProvidedDefaultConstructor(false), + UserProvidedMoveAssignment(false), + UserProvidedCopyAssignment(false), + ExplicitlyDeletedMoveAssignmentOrCtr(false), + DeclaredSpecialMembers(0), ImplicitCopyConstructorCanHaveConstParamForVBase(true), ImplicitCopyConstructorCanHaveConstParamForNonVBase(true), ImplicitCopyAssignmentHasConstParam(true), HasDeclaredCopyConstructorWithConstParam(false), HasDeclaredCopyAssignmentWithConstParam(false), IsAnyDestructorNoReturn(false), IsHLSLIntangible(false), - IsTriviallyRelocatable(false), IsLambda(false), + IsTriviallyRelocatable(false), IsMemberwiseReplaceable(false), IsLambda(false), IsParsingBaseSpecifiers(false), ComputedVisibleConversions(false), HasODRHash(false), Definition(D) {} @@ -894,15 +898,15 @@ void CXXRecordDecl::addedMember(Decl *D) { if (Method->isCopyAssignmentOperator()) { SMKind |= SMF_CopyAssignment; - const auto *ParamTy = Method->getNonObjectParameter(0)->getType()->getAs(); if (!ParamTy || ParamTy->getPointeeType().isConstQualified()) data().HasDeclaredCopyAssignmentWithConstParam = true; } - if (Method->isMoveAssignmentOperator()) + if (Method->isMoveAssignmentOperator()) { SMKind |= SMF_MoveAssignment; + } // Keep the list of conversion functions up-to-date. if (auto *Conversion = dyn_cast(D)) { @@ -1495,7 +1499,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 @@ -1517,6 +1524,9 @@ void CXXRecordDecl::addedEligibleSpecialMemberFunction(const CXXMethodDecl *MD, if (!MD->isUserProvided()) data().DeclaredNonTrivialSpecialMembersForCall |= SMKind; } + + if(MD->isDeleted() && (SMKind == SMF_MoveConstructor || SMKind == SMF_MoveAssignment)) + data().ExplicitlyDeletedMoveAssignmentOrCtr = true; } void CXXRecordDecl::finishedDefaultedOrDeletedMember(CXXMethodDecl *D) { diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp index a30fb2bc409b3..3a8b4fe233162 100644 --- a/clang/lib/AST/Type.cpp +++ b/clang/lib/AST/Type.cpp @@ -2830,6 +2830,20 @@ bool QualType::isCppTriviallyRelocatableType(const ASTContext &Context) const { return false; } +bool QualType::isMemberwiseReplaceableType(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->isTriviallyRelocatable(); + return false; +} + + bool QualType::isNonWeakInMRRWithObjCWeak(const ASTContext &Context) const { return !Context.getLangOpts().ObjCAutoRefCount && Context.getLangOpts().ObjCWeak && diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp index 82d951dc395b5..a98f3bb2153a9 100644 --- a/clang/lib/Parse/ParseDeclCXX.cpp +++ b/clang/lib/Parse/ParseDeclCXX.cpp @@ -11,12 +11,14 @@ //===----------------------------------------------------------------------===// #include "clang/AST/ASTContext.h" +#include "clang/AST/DeclCXX.h" #include "clang/AST/DeclTemplate.h" #include "clang/AST/PrettyDeclStackTrace.h" #include "clang/Basic/AttributeCommonInfo.h" #include "clang/Basic/Attributes.h" #include "clang/Basic/CharInfo.h" #include "clang/Basic/OperatorKinds.h" +#include "clang/Basic/SourceLocation.h" #include "clang/Basic/TargetInfo.h" #include "clang/Basic/TokenKinds.h" #include "clang/Lex/LiteralSupport.h" @@ -2027,9 +2029,7 @@ void Parser::ParseClassSpecifier(tok::TokenKind TagTokKind, getLangOpts().CPlusPlus && Tok.is(tok::colon)) || (isClassCompatibleKeyword() && (NextToken().is(tok::l_brace) || NextToken().is(tok::colon) || - isClassCompatibleKeyword(NextToken()) || - (isCXX2CTriviallyRelocatableKeyword() && - NextToken().is(tok::l_paren))))) { + isClassCompatibleKeyword(NextToken())))) { if (DS.isFriendSpecified()) { // C++ [class.friend]p2: // A class shall not be defined in a friend declaration. @@ -2704,7 +2704,7 @@ bool Parser::isCXX2CTriviallyRelocatableKeyword(Token Tok) const { return false; if (!Ident_trivially_relocatable) Ident_trivially_relocatable = - &PP.getIdentifierTable().get("trivially_relocatable"); + &PP.getIdentifierTable().get("memberwise_trivially_relocatable"); IdentifierInfo *II = Tok.getIdentifierInfo(); return II == Ident_trivially_relocatable; } @@ -2713,38 +2713,48 @@ bool Parser::isCXX2CTriviallyRelocatableKeyword() const { return isCXX2CTriviallyRelocatableKeyword(Tok); } -void Parser::ParseOptionalCXX2CTriviallyRelocatableSpecifier( - TriviallyRelocatableSpecifier &TRS) { +void Parser::ParseOptionalCXX2CTriviallyRelocatableSpecifier(TriviallyRelocatableSpecifier &TRS) { assert(isCXX2CTriviallyRelocatableKeyword() && "expected a trivially_relocatable specifier"); - Expr *E = nullptr; - SourceLocation Loc = ConsumeToken(); - if (Tok.is(tok::l_paren)) { - BalancedDelimiterTracker T(*this, tok::l_paren); - T.consumeOpen(); - ExprResult Res = ParseConstantExpression(); - T.consumeClose(); - if (!Res.isInvalid()) - E = Res.get(); - } - TRS = Actions.ActOnTriviallyRelocatableSpecifier(Loc, E); + TRS = Actions.ActOnTriviallyRelocatableSpecifier(ConsumeToken()); } bool Parser::SkipCXX2CTriviallyRelocatableSpecifier() { assert(isCXX2CTriviallyRelocatableKeyword() && "expected a trivially_relocatable specifier"); ConsumeToken(); - if (Tok.is(tok::l_paren)) { - ConsumeParen(); - return SkipUntil(tok::r_paren, StopAtSemi); - } + return true; +} + +bool Parser::isCXX2CMemberwiseReplaceableKeyword(Token Tok) const { + if (!getLangOpts().CPlusPlus || Tok.isNot(tok::identifier)) + return false; + if (!Ident_memberwise_replaceable) + Ident_memberwise_replaceable = + &PP.getIdentifierTable().get("memberwise_replaceable"); + IdentifierInfo *II = Tok.getIdentifierInfo(); + return II == Ident_memberwise_replaceable; +} + +bool Parser::isCXX2CMemberwiseReplaceableKeyword() const { + return isCXX2CMemberwiseReplaceableKeyword(Tok); +} +void Parser::ParseOptionalCXX2CMemberwiseReplaceableSpecifier(MemberwiseReplaceableSpecifier &MRS) { + assert(isCXX2CMemberwiseReplaceableKeyword() && + "expected a memberwise_replacable specifier"); + MRS = Actions.ActOnMemberwiseReplaceableSpecifier(ConsumeToken()); +} +bool Parser::SkipCXX2CMemberwiseReplaceableSpecifier() { + assert(isCXX2CMemberwiseReplaceableKeyword() && + "expected a memberwise_replacable specifier"); + ConsumeToken(); return true; } /// isClassCompatibleKeyword - Determine whether the next token is a C++11 /// 'final' or Microsoft 'sealed' or 'abstract' contextual keywords. bool Parser::isClassCompatibleKeyword(Token Tok) const { - if (isCXX2CTriviallyRelocatableKeyword(Tok)) + if (isCXX2CTriviallyRelocatableKeyword(Tok) || isCXX2CMemberwiseReplaceableKeyword(Tok)) return true; VirtSpecifiers::Specifier Specifier = isCXX11VirtSpecifier(Tok); return Specifier == VirtSpecifiers::VS_Final || @@ -3865,6 +3875,7 @@ void Parser::ParseCXXMemberSpecification(SourceLocation RecordLoc, bool IsFinalSpelledSealed = false; bool IsAbstract = false; TriviallyRelocatableSpecifier TriviallyRelocatable; + MemberwiseReplaceableSpecifier MemberwiseReplacable; // Parse the optional 'final' keyword. if (getLangOpts().CPlusPlus && Tok.is(tok::identifier)) { @@ -3875,13 +3886,26 @@ void Parser::ParseCXXMemberSpecification(SourceLocation RecordLoc, if (TriviallyRelocatable.isSet()) { auto Skipped = Tok; SkipCXX2CTriviallyRelocatableSpecifier(); - Diag(Skipped, diag::err_duplicate_class_trivially_relocatable) - << TriviallyRelocatable.getLocation(); - } else { + Diag(Skipped, diag::err_duplicate_class_memberwise_specifier) + << 0 << TriviallyRelocatable.getLocation(); + } + else { ParseOptionalCXX2CTriviallyRelocatableSpecifier( - TriviallyRelocatable); - continue; + TriviallyRelocatable); + } + continue; + } + else if (isCXX2CMemberwiseReplaceableKeyword(Tok)) { + if (MemberwiseReplacable.isSet()) { + auto Skipped = Tok; + SkipCXX2CMemberwiseReplaceableSpecifier(); + Diag(Skipped, diag::err_duplicate_class_memberwise_specifier) + << 1 << MemberwiseReplacable.getLocation(); + } + else { + ParseOptionalCXX2CMemberwiseReplaceableSpecifier(MemberwiseReplacable); } + continue; } else { break; } @@ -3922,7 +3946,7 @@ void Parser::ParseCXXMemberSpecification(SourceLocation RecordLoc, Diag(FinalLoc, diag::ext_warn_gnu_final); } assert((FinalLoc.isValid() || AbstractLoc.isValid() || - TriviallyRelocatable.isSet()) && + TriviallyRelocatable.isSet() || MemberwiseReplacable.isSet()) && "not a class definition"); // Parse any C++11 attributes after 'final' keyword. @@ -3997,7 +4021,7 @@ void Parser::ParseCXXMemberSpecification(SourceLocation RecordLoc, if (TagDecl) Actions.ActOnStartCXXMemberDeclarations( getCurScope(), TagDecl, FinalLoc, IsFinalSpelledSealed, IsAbstract, - TriviallyRelocatable, T.getOpenLocation()); + TriviallyRelocatable, MemberwiseReplacable, T.getOpenLocation()); // C++ 11p3: Members of a class defined with the keyword class are private // by default. Members of a class defined with the keywords struct or union diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp index c1d72d249386c..715ee8b2a1737 100644 --- a/clang/lib/Parse/Parser.cpp +++ b/clang/lib/Parse/Parser.cpp @@ -514,6 +514,7 @@ void Parser::Initialize() { Ident_abstract = nullptr; Ident_override = nullptr; Ident_trivially_relocatable = nullptr; + Ident_memberwise_replaceable = nullptr; Ident_GNU_final = nullptr; Ident_import = nullptr; Ident_module = nullptr; diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index f9a7f3852cc95..5a239c7fd2e8a 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -1880,7 +1880,7 @@ static ExprResult BuiltinIsWithinLifetime(Sema &S, CallExpr *TheCall) { return ExprError(); } static ExprResult BuiltinTriviallyRelocate(Sema &S, CallExpr *TheCall) { - if (checkArgCount(S, TheCall, 3)) + if (S.checkArgCount(TheCall, 3)) return ExprError(); QualType ArgTy = TheCall->getArg(0)->getType(); diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 4dead61e83c5d..030adf18c6fd5 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -32,6 +32,7 @@ #include "clang/Basic/Builtins.h" #include "clang/Basic/HLSLRuntime.h" #include "clang/Basic/PartialDiagnostic.h" +#include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" #include "clang/Basic/TargetInfo.h" #include "clang/Lex/HeaderSearch.h" // TODO: Sema shouldn't depend on Lex @@ -18026,37 +18027,18 @@ bool Sema::ActOnDuplicateDefinition(Decl *Prev, SkipBodyInfo &SkipBody) { return true; } -TriviallyRelocatableSpecifier -Sema::ActOnTriviallyRelocatableSpecifier(SourceLocation Loc, Expr *E) { - TriviallyRelocatableSpecifier Spec; - llvm::APSInt Result; - TriviallyRelocatableSpecifier::Kind Kind = - TriviallyRelocatableSpecifier::TRK_True; - if (E) { - if (DiagnoseUnexpandedParameterPack(E)) - Kind = TriviallyRelocatableSpecifier::TRK_Invalid; - else if (E->isTypeDependent()) - Kind = TriviallyRelocatableSpecifier::TRK_Dependent; - else { - ExprResult Converted = CheckConvertedConstantExpression( - E, Context.BoolTy, Result, CCEK_TriviallyRelocatable); - if (!Converted.isUsable()) - Kind = TriviallyRelocatableSpecifier::TRK_Invalid; - else if (Converted.get()->isValueDependent()) - Kind = TriviallyRelocatableSpecifier::TRK_Dependent; - else - Kind = Result.getBoolValue() ? TriviallyRelocatableSpecifier::TRK_True - : TriviallyRelocatableSpecifier::TRK_False; - E = Converted.get(); - } - } - Spec.SetTriviallyRelocatable(Kind, Loc, E); - return Spec; +TriviallyRelocatableSpecifier Sema::ActOnTriviallyRelocatableSpecifier(SourceLocation Loc) { + return {Loc}; +} + +MemberwiseReplaceableSpecifier Sema::ActOnMemberwiseReplaceableSpecifier(SourceLocation Loc) { + return {Loc}; } void Sema::ActOnStartCXXMemberDeclarations( Scope *S, Decl *TagD, SourceLocation FinalLoc, bool IsFinalSpelledSealed, bool IsAbstract, TriviallyRelocatableSpecifier TriviallyRelocatable, + MemberwiseReplaceableSpecifier MemberwiseReplaceable, SourceLocation LBraceLoc) { AdjustDeclIfTemplate(TagD); CXXRecordDecl *Record = cast(TagD); @@ -18076,9 +18058,12 @@ void Sema::ActOnStartCXXMemberDeclarations( : FinalAttr::Keyword_final)); } - if (TriviallyRelocatable.isValid() && !Record->isInvalidDecl()) + if (TriviallyRelocatable.isSet() && !Record->isInvalidDecl()) Record->setTriviallyRelocatableSpecifier(TriviallyRelocatable); + if (MemberwiseReplaceable.isSet() && !Record->isInvalidDecl()) + Record->setMemberwiseReplaceableSpecifier(MemberwiseReplaceable); + // C++ [class]p2: // [...] The class-name is also inserted into the scope of the // class itself; this is known as the injected-class-name. For diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index ee9b9dbdffea6..a1f6f6fffb677 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -7198,15 +7198,7 @@ void Sema::CheckCXX2CTriviallyRelocatable(CXXRecordDecl *D) { if (!D->hasDefinition() || D->isInvalidDecl()) return; - TriviallyRelocatableSpecifier::Kind TRK = - D->getTriviallyRelocatableSpecifier().getKind(); SourceLocation Loc = D->getTriviallyRelocatableSpecifier().getLocation(); - if (TRK == TriviallyRelocatableSpecifier::TRK_Dependent) - return; - if (TRK == TriviallyRelocatableSpecifier::TRK_False) { - D->setIsTriviallyRelocatable(false); - return; - } bool MarkedTriviallyRelocatable = D->getTriviallyRelocatableSpecifier().isSet(); auto DiagnosticInvalidExplicitSpecifier = [&, NotedError = false]() mutable { @@ -7238,18 +7230,14 @@ void Sema::CheckCXX2CTriviallyRelocatable(CXXRecordDecl *D) { continue; if (Field->getType()->isReferenceType()) continue; - QualType T = getASTContext().getBaseElementType(Field->getType()); + QualType T = getASTContext().getBaseElementType(Field->getType().getUnqualifiedType()); if (CXXRecordDecl *RD = T->getAsCXXRecordDecl()) { if (RD->isTriviallyRelocatable()) continue; - if (MarkedTriviallyRelocatable) { - DiagnosticInvalidExplicitSpecifier(); - Diag(Field->getBeginLoc(), diag::note_trivially_relocatable) - << /*member*/ 2 << Field << Field->getSourceRange(); - } IsTriviallyRelocatable = false; } } + if (!D->isDependentType() && !MarkedTriviallyRelocatable) { bool HasSuitableMoveCtr = D->needsImplicitMoveConstructor(); bool HasSuitableCopyCtr = false; @@ -7279,13 +7267,119 @@ void Sema::CheckCXX2CTriviallyRelocatable(CXXRecordDecl *D) { } } } - if (!HasSuitableMoveCtr && !HasSuitableCopyCtr) IsTriviallyRelocatable = false; + + if(IsTriviallyRelocatable + && ((!D->needsImplicitMoveAssignment() && D->hasUserProvidedMoveAssignment()) || (!D->hasMoveAssignment() && D->hasUserProvidedCopyAssignment()))) { + IsTriviallyRelocatable = false; + } } D->setIsTriviallyRelocatable(IsTriviallyRelocatable); } + +void Sema::CheckMemberwiseReplaceable(CXXRecordDecl *D) { + if (!D->hasDefinition() || D->isInvalidDecl()) + return; + + SourceLocation Loc = D->getMemberwiseReplaceableSpecifier().getLocation(); + bool MarkedMemberwiseReplaceable = + D->getMemberwiseReplaceableSpecifier().isSet(); + //auto DiagnosticInvalidExplicitSpecifier = [&, NotedError = false]() mutable { + // if (NotedError) + // return; + // Diag(Loc, + // diag::err_trivially_relocatable_specifier_on_non_relocatable_class) + // << D; + // NotedError = true; + //}; + bool IsMemberwiseReplaceable = true; + + /*for (const CXXBaseSpecifier &B : D->bases()) { + const auto *BaseDecl = B.getType()->getAsCXXRecordDecl(); + if (!BaseDecl) + continue; + if (B.isVirtual() || + (!BaseDecl->isDependentType() && !BaseDecl->isTriviallyRelocatable())) { + if (MarkedMemberwiseReplaceable) { + DiagnosticInvalidExplicitSpecifier(); + Diag(B.getBeginLoc(), diag::note_trivially_relocatable) + << (B.isVirtual() ? 0 : 1) << BaseDecl << B.getSourceRange(); + } + IsMemberwiseReplaceable = false; + } + }*/ + + if(D->hasExplicitlyDeletedMoveAssignmentOpOrCtr() || (D->needsImplicitMoveAssignment() && D->defaultedMoveAssignmentIsDeleted()) + || (D->needsImplicitMoveConstructor() && D->defaultedMoveConstructorIsDeleted())) { + IsMemberwiseReplaceable = false; + if(MarkedMemberwiseReplaceable) { + // Diag + } + } + + if (!D->isDependentType() && !MarkedMemberwiseReplaceable) { + bool HasSuitableMoveCtr = D->needsImplicitMoveConstructor() && !D->defaultedMoveConstructorIsDeleted(); + bool HasSuitableCopyCtr = false; + if (IsMemberwiseReplaceable && !HasSuitableMoveCtr) { + for (const CXXConstructorDecl *CD : D->ctors()) { + if (CD->isMoveConstructor() && CD->isDefaulted() && + !CD->isIneligibleOrNotSelected()) { + HasSuitableMoveCtr = true; + break; + } + } + } + if (!HasSuitableMoveCtr && !D->hasMoveConstructor()) { + HasSuitableCopyCtr = D->needsImplicitCopyConstructor() && !D->defaultedCopyConstructorIsDeleted(); + if (!HasSuitableCopyCtr) { + for (const CXXConstructorDecl *CD : D->ctors()) { + if (CD->isCopyConstructor() && CD->isDefaulted() && + !CD->isIneligibleOrNotSelected()) { + HasSuitableCopyCtr = true; + break; + } + } + } + } + if (!HasSuitableMoveCtr && !HasSuitableCopyCtr) + IsMemberwiseReplaceable = false; + + if(IsMemberwiseReplaceable + && !D->needsImplicitMoveAssignment() && D->hasUserProvidedMoveAssignment()) { + IsMemberwiseReplaceable = false; + } + + if(IsMemberwiseReplaceable + && ((!D->needsImplicitMoveAssignment() && D->hasUserProvidedMoveAssignment()) || (!D->hasMoveAssignment() && D->hasUserProvidedCopyAssignment()))) { + IsMemberwiseReplaceable = false; + } + } + + for (const FieldDecl *Field : D->fields()) { + if (Field->getType()->isDependentType()) + continue; + if (Field->getType()->isReferenceType()) { + IsMemberwiseReplaceable = false; + break; + } + if (Field->getType().isConstQualified()) { + IsMemberwiseReplaceable = false; + break; + } + QualType T = getASTContext().getBaseElementType(Field->getType().getUnqualifiedType()); + if (CXXRecordDecl *RD = T->getAsCXXRecordDecl()) { + if (RD->isMemberwiseReplaceable()) + continue; + IsMemberwiseReplaceable = false; + } + } + + D->setIsMemberwiseReplaceable(IsMemberwiseReplaceable); +} + + /// Look up the special member function that would be called by a special /// member function for a subobject of class type. /// diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index 34a4f94f9d8a3..938b4ea534106 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -5085,7 +5085,7 @@ static bool CheckUnaryTypeTraitTypeCompleteness(Sema &S, TypeTrait UTT, case UTT_IsTriviallyRelocatable: case UTT_IsTriviallyEqualityComparable: case UTT_IsCppTriviallyRelocatable: - case UTT_CanSwapUsingValueRepresentation: + case UTT_IsMemberwiseReplaceable: 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 @@ -5122,42 +5122,6 @@ static bool CheckUnaryTypeTraitTypeCompleteness(Sema &S, TypeTrait UTT, } } -static bool lookupStdTypeTraitMember(Sema &S, LookupResult &TraitMemberLookup, - SourceLocation Loc, StringRef Trait, - TemplateArgumentListInfo &Args, - unsigned DiagID) { - - DeclarationName Value = S.PP.getIdentifierInfo("value"); - LookupResult R(S, Value, Loc, Sema::LookupOrdinaryName); - - NamespaceDecl *Std = S.getStdNamespace(); - if (!Std) - return false; - - LookupResult Result(S, &S.PP.getIdentifierTable().get(Trait), - Loc, Sema::LookupOrdinaryName); - if (!S.LookupQualifiedName(Result, Std) || Result.isAmbiguous()) - return false; - - ClassTemplateDecl *TraitTD = Result.getAsSingle(); - if (!TraitTD) { - Result.suppressDiagnostics(); - return false; - } - - // Build the template-id. - QualType TraitTy = S.CheckTemplateIdType(TemplateName(TraitTD), Loc, Args); - if (TraitTy.isNull() || !S.isCompleteType(Loc, TraitTy)) - return false; - - CXXRecordDecl *RD = TraitTy->getAsCXXRecordDecl(); - assert(RD && "specialization of class template is not a class?"); - - // Look up the member of the trait type. - S.LookupQualifiedName(TraitMemberLookup, RD); - return !TraitMemberLookup.isAmbiguous(); -} - static bool HasNoThrowOperator(const RecordType *RT, OverloadedOperatorKind Op, Sema &Self, SourceLocation KeyLoc, ASTContext &C, bool (CXXRecordDecl::*HasTrivial)() const, @@ -5694,36 +5658,8 @@ static bool EvaluateUnaryTypeTrait(Sema &Self, TypeTrait UTT, return T.isBitwiseCloneableType(C); case UTT_IsCppTriviallyRelocatable: return T.isCppTriviallyRelocatableType(C); - case UTT_CanSwapUsingValueRepresentation: { - if(T->isIncompleteType() || T.isConstQualified() - || !T.isCppTriviallyRelocatableType(C)) - return false; - CXXRecordDecl *RD = T->getAsCXXRecordDecl(); - if(!RD) - return true; - if(RD->isPolymorphic()) - return false; - - return llvm::all_of(RD->fields(), [&](const FieldDecl *FD) { - auto Type = FD->getType(); - DeclarationName Value = Self.PP.getIdentifierInfo("value"); - LookupResult R(Self, Value, KeyLoc, Sema::LookupOrdinaryName); - TemplateArgumentListInfo Args(KeyLoc, KeyLoc); - Args.addArgument(Self.getTrivialTemplateArgumentLoc(Type, QualType(), KeyLoc)); - if (!lookupStdTypeTraitMember(Self, R, KeyLoc, "swap_uses_value_representation", Args, /*DiagID*/ 0) || - R.empty()) - return false; - ExprResult E = Self.BuildDeclarationNameExpr(CXXScopeSpec(), R, /*NeedsADL*/false); - if (E.isInvalid()) - return false; - llvm::APSInt Result; - E = Self.VerifyIntegerConstantExpression(E.get(), &Result); - if(E.isInvalid()) - return false; - return Result.getBoolValue(); - }); - break; - } + case UTT_IsMemberwiseReplaceable: + return T.isMemberwiseReplaceableType(C); case UTT_IsReferenceable: return T.isReferenceable(); case UTT_CanPassInRegs: diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp index 418e371f7011d..6e58dca8029e4 100644 --- a/clang/lib/Sema/SemaTemplateInstantiate.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp @@ -3430,17 +3430,8 @@ namespace clang { static TriviallyRelocatableSpecifier InstantiateTriviallyRelocatableSpecifier( Sema &S, const TriviallyRelocatableSpecifier &Pattern, - const MultiLevelTemplateArgumentList &TemplateArgs) { - if (Pattern.getKind() != TriviallyRelocatableSpecifier::TRK_Dependent) - return Pattern; - Expr *PatternE = Pattern.getCondition(); - EnterExpressionEvaluationContext ConstantCtx( - S, Sema::ExpressionEvaluationContext::ConstantEvaluated); - ExprResult Res = S.SubstExpr(PatternE, TemplateArgs); - if (Res.isInvalid()) - return TriviallyRelocatableSpecifier( - TriviallyRelocatableSpecifier::TRK_Invalid, Pattern.getLocation()); - return S.ActOnTriviallyRelocatableSpecifier(Pattern.getLocation(), Res.get()); + const MultiLevelTemplateArgumentList &) { + return Pattern; } /// Instantiate the definition of a class from a given pattern. @@ -3539,6 +3530,8 @@ Sema::InstantiateClass(SourceLocation PointOfInstantiation, *this, Pattern->getTriviallyRelocatableSpecifier(), TemplateArgs); Instantiation->setTriviallyRelocatableSpecifier(TRS); + Instantiation->setMemberwiseReplaceableSpecifier(Pattern->getMemberwiseReplaceableSpecifier()); + // The instantiation is visible here, even if it was first declared in an // unimported module. Instantiation->setVisibleDespiteOwningModule(); diff --git a/clang/test/Parser/cxx2c-trivially-relocatable.cpp b/clang/test/Parser/cxx2c-trivially-relocatable.cpp index 46c2fab65b6ef..0a2a9fee86ac8 100644 --- a/clang/test/Parser/cxx2c-trivially-relocatable.cpp +++ b/clang/test/Parser/cxx2c-trivially-relocatable.cpp @@ -1,15 +1,8 @@ // RUN: %clang_cc1 -std=c++2b -verify -fsyntax-only %s -class A trivially_relocatable {}; -class B trivially_relocatable() {}; // expected-error {{expected expression}} -class C trivially_relocatable(true) {}; -class D trivially_relocatable true {}; // expected-error {{variable has incomplete type 'class D'}} \ - // expected-error {{expected ';' after top level declarator}} \ - // expected-note {{forward declaration of 'D'}} -class E final trivially_relocatable {}; -class F final trivially_relocatable(true) {}; -class G trivially_relocatable final{}; -class H trivially_relocatable(true) final {}; -class I trivially_relocatable trivially_relocatable(true) final {}; // expected-error {{class already marked 'trivially_relocatable'}} -class trivially_relocatable trivially_relocatable {}; +class A memberwise_trivially_relocatable {}; +class E final memberwise_trivially_relocatable {}; +class G memberwise_trivially_relocatable final{}; +class I memberwise_trivially_relocatable memberwise_trivially_relocatable final {}; // expected-error {{class already marked 'memberwise_trivially_relocatable'}} +class memberwise_trivially_relocatable memberwise_trivially_relocatable {}; diff --git a/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp b/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp index 32de3333d7192..0ae40aada65f3 100644 --- a/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp +++ b/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp @@ -1,54 +1,48 @@ // RUN: %clang_cc1 -std=c++2c -verify %s -namespace Expressions { - -class D trivially_relocatable("") {}; -// expected-error@-1 {{conversion from 'const char[1]' to 'bool' is not allowed in a converted constant expression}}; -bool f(); //expected-note {{declared here}} -class E trivially_relocatable(f()) {}; // expected-error{{trivially_relocatable specifier argument is not a constant expression}}\ - // expected-note {{non-constexpr function 'f' cannot be used in a constant expression}}; -} - class Trivial {}; struct NonRelocatable { ~NonRelocatable(); }; static NonRelocatable NonRelocatable_g; -class A trivially_relocatable {}; -class B trivially_relocatable : Trivial{}; -class C trivially_relocatable { +class A memberwise_trivially_relocatable {}; +class B memberwise_trivially_relocatable : Trivial{}; +class C memberwise_trivially_relocatable { int a; void* b; int c[3]; Trivial d[3]; NonRelocatable& e = NonRelocatable_g; }; -class D trivially_relocatable : Trivial {}; -class E trivially_relocatable : virtual Trivial {}; +class D memberwise_trivially_relocatable : Trivial {}; +class E memberwise_trivially_relocatable : virtual Trivial {}; // expected-error@-1 {{invalid 'trivially_relocatable' specifier on non trivially-relocatable class 'E'}} // expected-note@-2 {{because it has a virtual base class 'Trivial'}} -class F trivially_relocatable : NonRelocatable {}; +class F memberwise_trivially_relocatable : NonRelocatable {}; // expected-error@-1 {{invalid 'trivially_relocatable' specifier on non trivially-relocatable class 'F'}} // expected-note@-2 {{because it inherits from a non trivially-relocatable class 'NonRelocatable'}} -class I trivially_relocatable { // expected-error{{invalid 'trivially_relocatable' specifier on non trivially-relocatable class 'I'}} - NonRelocatable a; // expected-note {{because it has a non trivially-relocatable member 'a'}} - NonRelocatable b[1]; // expected-note {{because it has a non trivially-relocatable member 'b'}} - const NonRelocatable c; // expected-note {{because it has a non trivially-relocatable member 'c'}} - const NonRelocatable d[1]; // expected-note {{because it has a non trivially-relocatable member 'd'}} +class I memberwise_trivially_relocatable { + NonRelocatable a; + NonRelocatable b[1]; + const NonRelocatable c; + const NonRelocatable d[1]; }; -class J trivially_relocatable(false) : virtual Trivial, NonRelocatable { +class J memberwise_trivially_relocatable: virtual Trivial, NonRelocatable { +// expected-error@-1 {{invalid 'trivially_relocatable' specifier on non trivially-relocatable class 'J'}} +// expected-note@-2 {{because it has a virtual base class 'Trivial'}} +// expected-note@-3 {{because it inherits from a non trivially-relocatable class 'NonRelocatable'}} NonRelocatable a; }; -class G trivially_relocatable { +class G memberwise_trivially_relocatable { G(G&&); }; -class H trivially_relocatable { +class H memberwise_trivially_relocatable { ~H(); }; @@ -107,93 +101,9 @@ static_assert(__is_cpp_trivially_relocatable(UserMoveDefault)); static_assert(__is_cpp_trivially_relocatable(UserCopyDefault)); static_assert(__is_cpp_trivially_relocatable(UserDeletedMove)); -template -class UnexpendedPack trivially_relocatable(Args) // expected-error{{expression contains unexpanded parameter pack 'Args'}} -{}; - -namespace Tpls { - -template -class UnexpendedPack trivially_relocatable(Args) // expected-error{{expression contains unexpanded parameter pack 'Args'}} -{}; - -template -class TestDependentValue trivially_relocatable((Args && ...)) {}; - -static_assert(__is_cpp_trivially_relocatable(TestDependentValue<>)); -static_assert(__is_cpp_trivially_relocatable(TestDependentValue)); - template -class TestDependent -trivially_relocatable(__is_cpp_trivially_relocatable(T)) : T -{}; - -TestDependent A; -TestDependent B; -static_assert(__is_cpp_trivially_relocatable(TestDependent)); -static_assert(__is_cpp_trivially_relocatable(TestDependent)); // expected-error{{static assertion failed due to requirement '__is_cpp_trivially_relocatable(Tpls::TestDependent)'}} - -template -class TestDependentErrors trivially_relocatable : T {}; +class TestDependentErrors memberwise_trivially_relocatable : T {}; // expected-error@-1 {{invalid 'trivially_relocatable' specifier on non trivially-relocatable class 'TestDependentErrors'}} // expected-note@-2 {{because it inherits from a non trivially-relocatable class 'NonRelocatable'}} TestDependentErrors Ok; TestDependentErrors Err; // expected-note {{in instantiation of template class}} -} - - -namespace std { - -template -struct swap_uses_value_representation { - static constexpr bool value = __can_swap_using_value_representation(T); -}; - - -template -constexpr bool swap_uses_value_representation_v = swap_uses_value_representation::value; - -} - -class S1 {}; -class S2 { int m1; }; -class S3 { const int m1; int m2; }; // expected-warning {{does not declare any constructor to initialize its non-modifiable members}} \ - // expected-note {{const member 'm1' will never be initialized}} -class S4 { const int &m1; int m2; }; // expected-warning {{does not declare any constructor to initialize its non-modifiable members}} \ - // expected-note {{reference member 'm1' will never be initialized}} -class S5 { int &m1; }; // expected-warning {{does not declare any constructor to initialize its non-modifiable members}} \ - // expected-note {{reference member 'm1' will never be initialized}} - -class Bad { int m1; }; - -namespace std { -template <> -struct swap_uses_value_representation { - static constexpr bool value = false; -}; - -} - - -using std::swap_uses_value_representation_v; - -static_assert(swap_uses_value_representation_v); -static_assert(!swap_uses_value_representation_v); -static_assert(!swap_uses_value_representation_v); -static_assert(!swap_uses_value_representation_v); -static_assert( swap_uses_value_representation_v); -static_assert( swap_uses_value_representation_v); -static_assert(!swap_uses_value_representation_v); -static_assert(!swap_uses_value_representation_v); -static_assert(!swap_uses_value_representation_v); -static_assert(!swap_uses_value_representation_v); - -struct empty {}; -struct Test { - empty nothing [[no_unique_address]]; - int data; -}; - -static_assert( swap_uses_value_representation_v); -static_assert( swap_uses_value_representation_v); -static_assert( sizeof(Test) == sizeof(int)); From decff9db389ea0d0d153fdd9bb080a30646265c3 Mon Sep 17 00:00:00 2001 From: Corentin Jabot Date: Thu, 8 Aug 2024 19:18:47 +0200 Subject: [PATCH 13/32] is_replaceable + swap --- .../clang/AST/CXXRecordDeclDefinitionBits.def | 4 +- clang/include/clang/AST/DeclCXX.h | 43 ++--- clang/include/clang/AST/Type.h | 2 +- .../clang/Basic/DiagnosticSemaKinds.td | 10 +- clang/include/clang/Basic/TokenKinds.def | 2 +- clang/include/clang/Parse/Parser.h | 8 +- clang/include/clang/Sema/Sema.h | 6 +- clang/lib/AST/DeclCXX.cpp | 21 +-- clang/lib/AST/Type.cpp | 7 +- clang/lib/Parse/ParseDeclCXX.cpp | 23 +-- clang/lib/Sema/SemaDecl.cpp | 10 +- clang/lib/Sema/SemaDeclCXX.cpp | 162 +++++++++++------- clang/lib/Sema/SemaExprCXX.cpp | 6 +- clang/lib/Sema/SemaTemplateInstantiate.cpp | 3 +- .../SemaCXX/cxx2c-trivially-relocatable.cpp | 122 +++++++++++++ .../__type_traits/is_trivially_relocatable.h | 6 + libcxx/include/__utility/swap.h | 59 +++++++ libcxx/include/string | 4 +- 18 files changed, 361 insertions(+), 137 deletions(-) diff --git a/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def b/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def index c89cc5311b739..7633a987673e9 100644 --- a/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def +++ b/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def @@ -226,7 +226,7 @@ FIELD(UserProvidedDefaultConstructor, 1, NO_MERGE) FIELD(UserProvidedMoveAssignment, 1, NO_MERGE) FIELD(UserProvidedCopyAssignment, 1, NO_MERGE) -FIELD(ExplicitlyDeletedMoveAssignmentOrCtr, 1, NO_MERGE) +FIELD(ExplicitlyDeletedMoveAssignment, 1, NO_MERGE) /// The special members which have been declared for this class, /// either by the user or implicitly. @@ -259,6 +259,6 @@ FIELD(IsHLSLIntangible, 1, NO_MERGE) FIELD(IsTriviallyRelocatable, 1, NO_MERGE) -FIELD(IsMemberwiseReplaceable, 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 c4e36e5a50460..98379b392bb0b 100644 --- a/clang/include/clang/AST/DeclCXX.h +++ b/clang/include/clang/AST/DeclCXX.h @@ -127,20 +127,14 @@ class AccessSpecDecl : public Decl { static bool classofKind(Kind K) { return K == AccessSpec; } }; -enum class MemberwiseRelocatableOrReplaceableKind { - Relocatable, - Replaceable -}; +enum class MemberwiseRelocatableOrReplaceableKind { Relocatable, Replaceable }; template class BasicMemberwiseSpecifier { public: BasicMemberwiseSpecifier() = default; - BasicMemberwiseSpecifier(SourceLocation Begin) - : Loc(Begin) {} - void Set(SourceLocation Begin) { - Loc = Begin; - } + BasicMemberwiseSpecifier(SourceLocation Begin) : Loc(Begin) {} + void Set(SourceLocation Begin) { Loc = Begin; } bool isSet() const { return !Loc.isInvalid(); } @@ -150,9 +144,10 @@ class BasicMemberwiseSpecifier { SourceLocation Loc; }; -using TriviallyRelocatableSpecifier = BasicMemberwiseSpecifier; -using MemberwiseReplaceableSpecifier = BasicMemberwiseSpecifier; - +using TriviallyRelocatableSpecifier = BasicMemberwiseSpecifier< + MemberwiseRelocatableOrReplaceableKind::Relocatable>; +using MemberwiseReplaceableSpecifier = BasicMemberwiseSpecifier< + MemberwiseRelocatableOrReplaceableKind::Replaceable>; /// Represents a base class of a C++ class. /// @@ -747,20 +742,19 @@ class CXXRecordDecl : public RecordDecl { /// \c true if a defaulted move constructor for this class would be /// deleted. bool defaultedMoveConstructorIsDeleted() const { - assert((!needsOverloadResolutionForMoveConstructor() || - (data().DeclaredSpecialMembers & SMF_MoveConstructor)) && - "this property has not yet been computed by Sema"); + // assert((!needsOverloadResolutionForMoveConstructor() || + // (data().DeclaredSpecialMembers & SMF_MoveConstructor)) && + // "this property has not yet been computed by Sema"); return data().DefaultedMoveConstructorIsDeleted; } bool defaultedMoveAssignmentIsDeleted() const { - assert((!needsOverloadResolutionForMoveAssignment() || - (data().DeclaredSpecialMembers & SMF_MoveAssignment)) && - "this property has not yet been computed by Sema"); + // 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() || @@ -853,8 +847,8 @@ class CXXRecordDecl : public RecordDecl { return data().UserProvidedCopyAssignment; } - bool hasExplicitlyDeletedMoveAssignmentOpOrCtr() const { - return data().ExplicitlyDeletedMoveAssignmentOrCtr; + bool hasExplicitlyDeletedMoveAssignment() const { + return data().ExplicitlyDeletedMoveAssignment; } /// Determine whether this class needs an implicit copy @@ -1529,12 +1523,9 @@ class CXXRecordDecl : public RecordDecl { data().IsTriviallyRelocatable = Set; } + bool isReplaceable() const { return data().IsReplaceable; } - bool isMemberwiseReplaceable() const { return data().IsMemberwiseReplaceable; } - - void setIsMemberwiseReplaceable(bool Set) { - data().IsMemberwiseReplaceable = Set; - } + void setIsReplaceable(bool Set) { data().IsReplaceable = Set; } /// Notify the class that this destructor is now selected. /// diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h index c84541fe4c738..641311a52093a 100644 --- a/clang/include/clang/AST/Type.h +++ b/clang/include/clang/AST/Type.h @@ -1144,7 +1144,7 @@ class QualType { bool isCppTriviallyRelocatableType(const ASTContext &Context) const; - bool isMemberwiseReplaceableType(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/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 7c04c279b88e0..baba389dbcf0a 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -2684,11 +2684,13 @@ def note_final_dtor_non_final_class_silence : Note< def err_trivially_relocatable_specifier_on_non_relocatable_class : Error< "invalid 'trivially_relocatable' specifier on non trivially-relocatable class %0">; +def err_memberwise_replaceable_specifier_on_non_relocatable_class : Error< + "invalid 'memberwise_replaceable' specifier on non memberwise replaceable class %0">; + def note_trivially_relocatable: Note< - "because it %select{" - "has a virtual base class|" - "inherits from a non trivially-relocatable class|" - "has a non trivially-relocatable member}0 %1">; + "because it %select{inherits from a non trivially-relocatable class %1|" + "has a virtual base class %1|" + "has a deleted move constructor or assignment operator}0">; // C++11 attributes def err_repeat_attribute : Error<"%0 attribute cannot be repeated">; diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def index ac9ce7bede33f..f60a9967c887a 100644 --- a/clang/include/clang/Basic/TokenKinds.def +++ b/clang/include/clang/Basic/TokenKinds.def @@ -558,7 +558,7 @@ TYPE_TRAIT_2(/*EmptySpellingName*/, IsDeducible, KEYCXX) TYPE_TRAIT_1(__is_bitwise_cloneable, IsBitwiseCloneable, KEYALL) TYPE_TRAIT_1(__is_cpp_trivially_relocatable, IsCppTriviallyRelocatable, KEYCXX) -TYPE_TRAIT_1(__is_memberwise_replaceable, IsMemberwiseReplaceable, 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 ef329352d2ce5..53433da608260 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -3171,15 +3171,15 @@ class Parser : public CodeCompletionHandler { bool isCXX2CTriviallyRelocatableKeyword(Token Tok) const; bool isCXX2CTriviallyRelocatableKeyword() const; bool SkipCXX2CTriviallyRelocatableSpecifier(); - void ParseOptionalCXX2CTriviallyRelocatableSpecifier(TriviallyRelocatableSpecifier &TRS); - + void ParseOptionalCXX2CTriviallyRelocatableSpecifier( + TriviallyRelocatableSpecifier &TRS); bool isCXX2CMemberwiseReplaceableKeyword(Token Tok) const; bool isCXX2CMemberwiseReplaceableKeyword() const; - void ParseOptionalCXX2CMemberwiseReplaceableSpecifier(MemberwiseReplaceableSpecifier &MRS); + void ParseOptionalCXX2CMemberwiseReplaceableSpecifier( + MemberwiseReplaceableSpecifier &MRS); bool SkipCXX2CMemberwiseReplaceableSpecifier(); - bool isClassCompatibleKeyword(Token Tok) const; bool isClassCompatibleKeyword() const; diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 44fff567b3b12..d77ac7c7fb719 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -3988,9 +3988,11 @@ 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); + TriviallyRelocatableSpecifier + ActOnTriviallyRelocatableSpecifier(SourceLocation Loc); - MemberwiseReplaceableSpecifier ActOnMemberwiseReplaceableSpecifier(SourceLocation Loc); + MemberwiseReplaceableSpecifier + ActOnMemberwiseReplaceableSpecifier(SourceLocation Loc); /// ActOnStartCXXMemberDeclarations - Invoked when we have parsed a /// C++ record definition's base-specifiers clause and are starting its diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp index ad3f3e77d5fc2..ce7e3e5354894 100644 --- a/clang/lib/AST/DeclCXX.cpp +++ b/clang/lib/AST/DeclCXX.cpp @@ -103,10 +103,8 @@ CXXRecordDecl::DefinitionData::DefinitionData(CXXRecordDecl *D) HasConstexprDefaultConstructor(false), DefaultedDestructorIsConstexpr(true), HasNonLiteralTypeFieldsOrBases(false), StructuralIfLiteral(true), - UserProvidedDefaultConstructor(false), - UserProvidedMoveAssignment(false), - UserProvidedCopyAssignment(false), - ExplicitlyDeletedMoveAssignmentOrCtr(false), + UserProvidedDefaultConstructor(false), UserProvidedMoveAssignment(false), + UserProvidedCopyAssignment(false), ExplicitlyDeletedMoveAssignment(false), DeclaredSpecialMembers(0), ImplicitCopyConstructorCanHaveConstParamForVBase(true), ImplicitCopyConstructorCanHaveConstParamForNonVBase(true), @@ -114,7 +112,7 @@ CXXRecordDecl::DefinitionData::DefinitionData(CXXRecordDecl *D) HasDeclaredCopyConstructorWithConstParam(false), HasDeclaredCopyAssignmentWithConstParam(false), IsAnyDestructorNoReturn(false), IsHLSLIntangible(false), - IsTriviallyRelocatable(false), IsMemberwiseReplaceable(false), IsLambda(false), + IsTriviallyRelocatable(false), IsReplaceable(false), IsLambda(false), IsParsingBaseSpecifiers(false), ComputedVisibleConversions(false), HasODRHash(false), Definition(D) {} @@ -1499,9 +1497,9 @@ void CXXRecordDecl::addedEligibleSpecialMemberFunction(const CXXMethodDecl *MD, if (DD->isNoReturn()) data().IsAnyDestructorNoReturn = true; } - if(SMKind == SMF_CopyAssignment) + if (SMKind == SMF_CopyAssignment) data().UserProvidedCopyAssignment = MD->isUserProvided(); - else if(SMKind == SMF_MoveAssignment) + 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 @@ -1525,8 +1523,8 @@ void CXXRecordDecl::addedEligibleSpecialMemberFunction(const CXXMethodDecl *MD, data().DeclaredNonTrivialSpecialMembersForCall |= SMKind; } - if(MD->isDeleted() && (SMKind == SMF_MoveConstructor || SMKind == SMF_MoveAssignment)) - data().ExplicitlyDeletedMoveAssignmentOrCtr = true; + if (MD->isDeleted() && SMKind == SMF_MoveAssignment) + data().ExplicitlyDeletedMoveAssignment = true; } void CXXRecordDecl::finishedDefaultedOrDeletedMember(CXXMethodDecl *D) { @@ -1554,8 +1552,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 3a8b4fe233162..7a8b53a6a05ed 100644 --- a/clang/lib/AST/Type.cpp +++ b/clang/lib/AST/Type.cpp @@ -2830,8 +2830,8 @@ bool QualType::isCppTriviallyRelocatableType(const ASTContext &Context) const { return false; } -bool QualType::isMemberwiseReplaceableType(const ASTContext &Context) const { - if(isConstQualified()) +bool QualType::isReplaceableType(const ASTContext &Context) const { + if (isConstQualified()) return false; QualType BaseElementType = Context.getBaseElementType(getUnqualifiedType()); if (BaseElementType->isIncompleteType()) @@ -2839,11 +2839,10 @@ bool QualType::isMemberwiseReplaceableType(const ASTContext &Context) const { if (BaseElementType->isScalarType()) return true; if (const auto *RD = BaseElementType->getAsCXXRecordDecl()) - return RD->isTriviallyRelocatable(); + return RD->isReplaceable(); return false; } - bool QualType::isNonWeakInMRRWithObjCWeak(const ASTContext &Context) const { return !Context.getLangOpts().ObjCAutoRefCount && Context.getLangOpts().ObjCWeak && diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp index a98f3bb2153a9..1d08bcb669064 100644 --- a/clang/lib/Parse/ParseDeclCXX.cpp +++ b/clang/lib/Parse/ParseDeclCXX.cpp @@ -2713,7 +2713,8 @@ bool Parser::isCXX2CTriviallyRelocatableKeyword() const { return isCXX2CTriviallyRelocatableKeyword(Tok); } -void Parser::ParseOptionalCXX2CTriviallyRelocatableSpecifier(TriviallyRelocatableSpecifier &TRS) { +void Parser::ParseOptionalCXX2CTriviallyRelocatableSpecifier( + TriviallyRelocatableSpecifier &TRS) { assert(isCXX2CTriviallyRelocatableKeyword() && "expected a trivially_relocatable specifier"); TRS = Actions.ActOnTriviallyRelocatableSpecifier(ConsumeToken()); @@ -2739,7 +2740,8 @@ bool Parser::isCXX2CMemberwiseReplaceableKeyword(Token Tok) const { bool Parser::isCXX2CMemberwiseReplaceableKeyword() const { return isCXX2CMemberwiseReplaceableKeyword(Tok); } -void Parser::ParseOptionalCXX2CMemberwiseReplaceableSpecifier(MemberwiseReplaceableSpecifier &MRS) { +void Parser::ParseOptionalCXX2CMemberwiseReplaceableSpecifier( + MemberwiseReplaceableSpecifier &MRS) { assert(isCXX2CMemberwiseReplaceableKeyword() && "expected a memberwise_replacable specifier"); MRS = Actions.ActOnMemberwiseReplaceableSpecifier(ConsumeToken()); @@ -2754,7 +2756,8 @@ bool Parser::SkipCXX2CMemberwiseReplaceableSpecifier() { /// isClassCompatibleKeyword - Determine whether the next token is a C++11 /// 'final' or Microsoft 'sealed' or 'abstract' contextual keywords. bool Parser::isClassCompatibleKeyword(Token Tok) const { - if (isCXX2CTriviallyRelocatableKeyword(Tok) || isCXX2CMemberwiseReplaceableKeyword(Tok)) + if (isCXX2CTriviallyRelocatableKeyword(Tok) || + isCXX2CMemberwiseReplaceableKeyword(Tok)) return true; VirtSpecifiers::Specifier Specifier = isCXX11VirtSpecifier(Tok); return Specifier == VirtSpecifiers::VS_Final || @@ -3888,22 +3891,20 @@ void Parser::ParseCXXMemberSpecification(SourceLocation RecordLoc, SkipCXX2CTriviallyRelocatableSpecifier(); Diag(Skipped, diag::err_duplicate_class_memberwise_specifier) << 0 << TriviallyRelocatable.getLocation(); - } - else { + } else { ParseOptionalCXX2CTriviallyRelocatableSpecifier( - TriviallyRelocatable); + TriviallyRelocatable); } continue; - } - else if (isCXX2CMemberwiseReplaceableKeyword(Tok)) { + } else if (isCXX2CMemberwiseReplaceableKeyword(Tok)) { if (MemberwiseReplacable.isSet()) { auto Skipped = Tok; SkipCXX2CMemberwiseReplaceableSpecifier(); Diag(Skipped, diag::err_duplicate_class_memberwise_specifier) << 1 << MemberwiseReplacable.getLocation(); - } - else { - ParseOptionalCXX2CMemberwiseReplaceableSpecifier(MemberwiseReplacable); + } else { + ParseOptionalCXX2CMemberwiseReplaceableSpecifier( + MemberwiseReplacable); } continue; } else { diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 030adf18c6fd5..cfada5097f00f 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -18027,12 +18027,14 @@ bool Sema::ActOnDuplicateDefinition(Decl *Prev, SkipBodyInfo &SkipBody) { return true; } -TriviallyRelocatableSpecifier Sema::ActOnTriviallyRelocatableSpecifier(SourceLocation Loc) { - return {Loc}; +TriviallyRelocatableSpecifier +Sema::ActOnTriviallyRelocatableSpecifier(SourceLocation Loc) { + return {Loc}; } -MemberwiseReplaceableSpecifier Sema::ActOnMemberwiseReplaceableSpecifier(SourceLocation Loc) { - return {Loc}; +MemberwiseReplaceableSpecifier +Sema::ActOnMemberwiseReplaceableSpecifier(SourceLocation Loc) { + return {Loc}; } void Sema::ActOnStartCXXMemberDeclarations( diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index a1f6f6fffb677..80cea6387ddee 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -17,6 +17,7 @@ #include "clang/AST/CXXInheritance.h" #include "clang/AST/CharUnits.h" #include "clang/AST/ComparisonCategories.h" +#include "clang/AST/DeclBase.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclTemplate.h" #include "clang/AST/EvaluatedExprVisitor.h" @@ -53,6 +54,7 @@ #include "llvm/ADT/ScopeExit.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringExtras.h" +#include "llvm/Support/Casting.h" #include "llvm/Support/ConvertUTF.h" #include "llvm/Support/SaveAndRestore.h" #include @@ -6920,8 +6922,6 @@ void Sema::CheckCompletedCXXClass(Scope *S, CXXRecordDecl *Record) { } } - CheckCXX2CTriviallyRelocatable(Record); - // See if trivial_abi has to be dropped. if (Record->hasAttr()) checkIllFormedTrivialABIStruct(*Record); @@ -7157,6 +7157,9 @@ void Sema::CheckCompletedCXXClass(Scope *S, CXXRecordDecl *Record) { checkClassLevelDLLAttribute(Record); checkClassLevelCodeSegAttribute(Record); + CheckCXX2CTriviallyRelocatable(Record); + CheckMemberwiseReplaceable(Record); + bool ClangABICompat4 = Context.getLangOpts().getClangABICompat() <= LangOptions::ClangABI::Ver4; TargetInfo::CallingConvKind CCK = @@ -7194,6 +7197,35 @@ void Sema::CheckCompletedCXXClass(Scope *S, CXXRecordDecl *Record) { } } +static bool hasDeletedMoveConstructor(CXXRecordDecl *D) { + assert(D->hasDefinition() && !D->isInvalidDecl()); + for (const CXXConstructorDecl *CD : D->ctors()) { + if (CD->isMoveConstructor() && CD->isDefaulted() && + !CD->isIneligibleOrNotSelected()) { + return CD->isDeleted(); + } + } + return false; + // return !D->needsImplicitMoveConstructor() || + // D->defaultedMoveConstructorIsDeleted(); +} + +static bool hasDeletedMoveAssignment(CXXRecordDecl *D) { + assert(D->hasDefinition() && !D->isInvalidDecl()); + if (D->hasExplicitlyDeletedMoveAssignment()) + return true; + for (const Decl *D : D->decls()) { + auto *MD = dyn_cast(D); + if (!MD || !MD->isMoveAssignmentOperator() || + MD->isIneligibleOrNotSelected()) + continue; + return MD->isDeleted(); + } + return false; + // return !D->needsImplicitMoveAssignment() || + // D->defaultedMoveAssignmentIsDeleted(); +} + void Sema::CheckCXX2CTriviallyRelocatable(CXXRecordDecl *D) { if (!D->hasDefinition() || D->isInvalidDecl()) return; @@ -7219,7 +7251,7 @@ void Sema::CheckCXX2CTriviallyRelocatable(CXXRecordDecl *D) { if (MarkedTriviallyRelocatable) { DiagnosticInvalidExplicitSpecifier(); Diag(B.getBeginLoc(), diag::note_trivially_relocatable) - << (B.isVirtual() ? 0 : 1) << BaseDecl << B.getSourceRange(); + << (B.isVirtual() ? 1 : 0) << BaseDecl << B.getSourceRange(); } IsTriviallyRelocatable = false; } @@ -7230,11 +7262,13 @@ void Sema::CheckCXX2CTriviallyRelocatable(CXXRecordDecl *D) { continue; if (Field->getType()->isReferenceType()) continue; - QualType T = getASTContext().getBaseElementType(Field->getType().getUnqualifiedType()); + QualType T = getASTContext().getBaseElementType( + Field->getType().getUnqualifiedType()); if (CXXRecordDecl *RD = T->getAsCXXRecordDecl()) { if (RD->isTriviallyRelocatable()) continue; IsTriviallyRelocatable = false; + break; } } @@ -7270,15 +7304,17 @@ void Sema::CheckCXX2CTriviallyRelocatable(CXXRecordDecl *D) { if (!HasSuitableMoveCtr && !HasSuitableCopyCtr) IsTriviallyRelocatable = false; - if(IsTriviallyRelocatable - && ((!D->needsImplicitMoveAssignment() && D->hasUserProvidedMoveAssignment()) || (!D->hasMoveAssignment() && D->hasUserProvidedCopyAssignment()))) { - IsTriviallyRelocatable = false; + if (IsTriviallyRelocatable && + ((!D->needsImplicitMoveAssignment() && + (D->hasUserProvidedMoveAssignment() || + D->hasExplicitlyDeletedMoveAssignment())) || + (!D->hasMoveAssignment() && D->hasUserProvidedCopyAssignment()))) { + IsTriviallyRelocatable = false; } } D->setIsTriviallyRelocatable(IsTriviallyRelocatable); } - void Sema::CheckMemberwiseReplaceable(CXXRecordDecl *D) { if (!D->hasDefinition() || D->isInvalidDecl()) return; @@ -7286,43 +7322,46 @@ void Sema::CheckMemberwiseReplaceable(CXXRecordDecl *D) { SourceLocation Loc = D->getMemberwiseReplaceableSpecifier().getLocation(); bool MarkedMemberwiseReplaceable = D->getMemberwiseReplaceableSpecifier().isSet(); - //auto DiagnosticInvalidExplicitSpecifier = [&, NotedError = false]() mutable { - // if (NotedError) - // return; - // Diag(Loc, - // diag::err_trivially_relocatable_specifier_on_non_relocatable_class) - // << D; - // NotedError = true; - //}; - bool IsMemberwiseReplaceable = true; - - /*for (const CXXBaseSpecifier &B : D->bases()) { + auto DiagnosticInvalidExplicitSpecifier = [&, NotedError = false]() mutable { + if (NotedError) + return false; + Diag(Loc, + diag::err_memberwise_replaceable_specifier_on_non_relocatable_class) + << D; + bool Old = NotedError; + NotedError = true; + return !Old; + }; + bool IsReplaceable = true; + + for (const CXXBaseSpecifier &B : D->bases()) { const auto *BaseDecl = B.getType()->getAsCXXRecordDecl(); if (!BaseDecl) continue; - if (B.isVirtual() || - (!BaseDecl->isDependentType() && !BaseDecl->isTriviallyRelocatable())) { + if (!BaseDecl->isDependentType() && !BaseDecl->isReplaceable()) { if (MarkedMemberwiseReplaceable) { - DiagnosticInvalidExplicitSpecifier(); - Diag(B.getBeginLoc(), diag::note_trivially_relocatable) - << (B.isVirtual() ? 0 : 1) << BaseDecl << B.getSourceRange(); + if (DiagnosticInvalidExplicitSpecifier()) + Diag(B.getBeginLoc(), diag::note_trivially_relocatable) + << 0 << BaseDecl << B.getSourceRange(); } - IsMemberwiseReplaceable = false; + IsReplaceable = false; } - }*/ + } - if(D->hasExplicitlyDeletedMoveAssignmentOpOrCtr() || (D->needsImplicitMoveAssignment() && D->defaultedMoveAssignmentIsDeleted()) - || (D->needsImplicitMoveConstructor() && D->defaultedMoveConstructorIsDeleted())) { - IsMemberwiseReplaceable = false; - if(MarkedMemberwiseReplaceable) { - // Diag - } + if (hasDeletedMoveAssignment(D) || hasDeletedMoveConstructor(D)) { + IsReplaceable = false; + if (MarkedMemberwiseReplaceable) { + if (DiagnosticInvalidExplicitSpecifier()) + Diag(D->getBeginLoc(), diag::note_trivially_relocatable) + << 2 << D->getSourceRange(); + } } - if (!D->isDependentType() && !MarkedMemberwiseReplaceable) { - bool HasSuitableMoveCtr = D->needsImplicitMoveConstructor() && !D->defaultedMoveConstructorIsDeleted(); + if (IsReplaceable && !D->isDependentType() && !MarkedMemberwiseReplaceable) { + bool HasSuitableMoveCtr = D->needsImplicitMoveConstructor() && + !D->defaultedMoveConstructorIsDeleted(); bool HasSuitableCopyCtr = false; - if (IsMemberwiseReplaceable && !HasSuitableMoveCtr) { + if (IsReplaceable && !HasSuitableMoveCtr) { for (const CXXConstructorDecl *CD : D->ctors()) { if (CD->isMoveConstructor() && CD->isDefaulted() && !CD->isIneligibleOrNotSelected()) { @@ -7332,7 +7371,8 @@ void Sema::CheckMemberwiseReplaceable(CXXRecordDecl *D) { } } if (!HasSuitableMoveCtr && !D->hasMoveConstructor()) { - HasSuitableCopyCtr = D->needsImplicitCopyConstructor() && !D->defaultedCopyConstructorIsDeleted(); + HasSuitableCopyCtr = D->needsImplicitCopyConstructor() && + !D->defaultedCopyConstructorIsDeleted(); if (!HasSuitableCopyCtr) { for (const CXXConstructorDecl *CD : D->ctors()) { if (CD->isCopyConstructor() && CD->isDefaulted() && @@ -7343,43 +7383,43 @@ void Sema::CheckMemberwiseReplaceable(CXXRecordDecl *D) { } } } - if (!HasSuitableMoveCtr && !HasSuitableCopyCtr) - IsMemberwiseReplaceable = false; - - if(IsMemberwiseReplaceable - && !D->needsImplicitMoveAssignment() && D->hasUserProvidedMoveAssignment()) { - IsMemberwiseReplaceable = false; + if (!HasSuitableMoveCtr && !HasSuitableCopyCtr) { + IsReplaceable = false; } - if(IsMemberwiseReplaceable - && ((!D->needsImplicitMoveAssignment() && D->hasUserProvidedMoveAssignment()) || (!D->hasMoveAssignment() && D->hasUserProvidedCopyAssignment()))) { - IsMemberwiseReplaceable = false; + if (IsReplaceable && + ((!D->needsImplicitMoveAssignment() && + D->hasUserProvidedMoveAssignment()) || + (!D->hasMoveAssignment() && D->hasUserProvidedCopyAssignment()))) { + IsReplaceable = false; } } - for (const FieldDecl *Field : D->fields()) { - if (Field->getType()->isDependentType()) - continue; - if (Field->getType()->isReferenceType()) { - IsMemberwiseReplaceable = false; + if (IsReplaceable) { + for (const FieldDecl *Field : D->fields()) { + if (Field->getType()->isDependentType()) + continue; + if (Field->getType()->isReferenceType()) { + IsReplaceable = false; break; - } - if (Field->getType().isConstQualified()) { - IsMemberwiseReplaceable = false; + } + if (Field->getType().isConstQualified()) { + IsReplaceable = false; break; - } - QualType T = getASTContext().getBaseElementType(Field->getType().getUnqualifiedType()); - if (CXXRecordDecl *RD = T->getAsCXXRecordDecl()) { - if (RD->isMemberwiseReplaceable()) - continue; - IsMemberwiseReplaceable = false; + } + QualType T = getASTContext().getBaseElementType( + Field->getType().getUnqualifiedType()); + if (CXXRecordDecl *RD = T->getAsCXXRecordDecl()) { + if (RD->isReplaceable()) + continue; + IsReplaceable = false; + } } } - D->setIsMemberwiseReplaceable(IsMemberwiseReplaceable); + D->setIsReplaceable(IsReplaceable); } - /// Look up the special member function that would be called by a special /// member function for a subobject of class type. /// diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index 938b4ea534106..f56ad8e51b55d 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -5085,7 +5085,7 @@ static bool CheckUnaryTypeTraitTypeCompleteness(Sema &S, TypeTrait UTT, case UTT_IsTriviallyRelocatable: case UTT_IsTriviallyEqualityComparable: case UTT_IsCppTriviallyRelocatable: - case UTT_IsMemberwiseReplaceable: + 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 @@ -5658,8 +5658,8 @@ static bool EvaluateUnaryTypeTrait(Sema &Self, TypeTrait UTT, return T.isBitwiseCloneableType(C); case UTT_IsCppTriviallyRelocatable: return T.isCppTriviallyRelocatableType(C); - case UTT_IsMemberwiseReplaceable: - return T.isMemberwiseReplaceableType(C); + case UTT_IsReplaceable: + return T.isReplaceableType(C); case UTT_IsReferenceable: return T.isReferenceable(); case UTT_CanPassInRegs: diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp index 6e58dca8029e4..90514df48b2bb 100644 --- a/clang/lib/Sema/SemaTemplateInstantiate.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp @@ -3530,7 +3530,8 @@ Sema::InstantiateClass(SourceLocation PointOfInstantiation, *this, Pattern->getTriviallyRelocatableSpecifier(), TemplateArgs); Instantiation->setTriviallyRelocatableSpecifier(TRS); - Instantiation->setMemberwiseReplaceableSpecifier(Pattern->getMemberwiseReplaceableSpecifier()); + Instantiation->setMemberwiseReplaceableSpecifier( + Pattern->getMemberwiseReplaceableSpecifier()); // The instantiation is visible here, even if it was first declared in an // unimported module. diff --git a/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp b/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp index 0ae40aada65f3..7c1f13a53866a 100644 --- a/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp +++ b/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp @@ -107,3 +107,125 @@ class TestDependentErrors memberwise_trivially_relocatable : T {}; // expected-note@-2 {{because it inherits from a non trivially-relocatable class 'NonRelocatable'}} TestDependentErrors Ok; TestDependentErrors Err; // expected-note {{in instantiation of template class}} + +struct DeletedMove { + DeletedMove(DeletedMove&&) = delete; +}; +struct DeletedCopy { + DeletedCopy(const DeletedCopy&) = delete; +}; +struct DeletedMoveAssign { + DeletedMoveAssign& operator=(DeletedMoveAssign&&) = delete; +}; + +static_assert(!__is_cpp_trivially_relocatable(DeletedMove)); +static_assert(!__is_cpp_trivially_relocatable(DeletedCopy)); +static_assert(!__is_cpp_trivially_relocatable(DeletedMoveAssign)); + + + +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 memberwise_replaceable{}; +static_assert(__builtin_is_replaceable(S1)); + +static_assert(__builtin_is_replaceable(DefaultedMove)); +static_assert(__builtin_is_replaceable(DefaultedCopy)); +static_assert(__builtin_is_replaceable(DefaultedMoveAssign)); + +static_assert(!__builtin_is_replaceable(DeletedMove)); +static_assert(!__builtin_is_replaceable(DeletedCopy)); +static_assert(!__builtin_is_replaceable(DeletedMoveAssign)); + +static_assert(!__builtin_is_replaceable(UserProvidedMove)); +static_assert(!__builtin_is_replaceable(UserProvidedCopy)); +static_assert(!__builtin_is_replaceable(UserProvidedMoveAssign)); + +using NotReplaceable = DeletedMove; + +template +struct S { + T t; +}; + +template +struct WithBase : T{}; + +template +struct WithVBase : virtual T{}; + +struct WithVirtual { + virtual ~WithVirtual() = default; + WithVirtual(WithVirtual&&) = default; + WithVirtual& operator=(WithVirtual&&) = default; +}; + +static_assert(__builtin_is_replaceable(S)); +static_assert(__builtin_is_replaceable(S)); +static_assert(!__builtin_is_replaceable(S)); +static_assert(!__builtin_is_replaceable(S)); +static_assert(!__builtin_is_replaceable(S)); +static_assert(__builtin_is_replaceable(S)); +static_assert(!__builtin_is_replaceable(S)); +static_assert(__builtin_is_replaceable(WithBase>)); +static_assert(!__builtin_is_replaceable(WithBase>)); +static_assert(!__builtin_is_replaceable(WithBase)); +static_assert(__builtin_is_replaceable(WithVBase>)); +static_assert(!__builtin_is_replaceable(WithVBase>)); +static_assert(!__builtin_is_replaceable(WithVBase)); +static_assert(__builtin_is_replaceable(WithVirtual)); + + +struct U1 memberwise_replaceable { + ~U1() = delete; + U1(U1&&) = default; + U1& operator=(U1&&) = default; + +}; +static_assert(__builtin_is_replaceable(U1)); + +struct U2 memberwise_replaceable { +// expected-error@-1 {{invalid 'memberwise_replaceable' specifier on non memberwise replaceable class 'U2'}} +// expected-note@-2 {{because it has a deleted move constructor or assignment operator}} + U2(const U2&) = delete; +}; +static_assert(!__builtin_is_replaceable(U2)); + + +} diff --git a/libcxx/include/__type_traits/is_trivially_relocatable.h b/libcxx/include/__type_traits/is_trivially_relocatable.h index a16d79eecc250..341f9fceb8e36 100644 --- a/libcxx/include/__type_traits/is_trivially_relocatable.h +++ b/libcxx/include/__type_traits/is_trivially_relocatable.h @@ -35,6 +35,12 @@ struct is_trivially_relocatable : integral_constant inline constexpr bool is_trivially_relocatable_v = __is_cpp_trivially_relocatable(_Tp); +template +struct is_replaceable : integral_constant {}; + +template +inline constexpr bool is_replaceable_v = __builtin_is_replaceable(_Tp); + _LIBCPP_END_NAMESPACE_STD #endif // _LIBCPP___TYPE_TRAITS_IS_TRIVIALLY_RELOCATABLE_H diff --git a/libcxx/include/__utility/swap.h b/libcxx/include/__utility/swap.h index ecfbdec75a2ae..3d4f0b67a3b1c 100644 --- a/libcxx/include/__utility/swap.h +++ b/libcxx/include/__utility/swap.h @@ -9,6 +9,7 @@ #ifndef _LIBCPP___UTILITY_SWAP_H #define _LIBCPP___UTILITY_SWAP_H +#include <__assert> #include <__config> #include <__type_traits/enable_if.h> #include <__type_traits/is_assignable.h> @@ -16,9 +17,12 @@ #include <__type_traits/is_nothrow_assignable.h> #include <__type_traits/is_nothrow_constructible.h> #include <__type_traits/is_swappable.h> +#include <__type_traits/is_trivially_relocatable.h> #include <__utility/declval.h> +#include <__utility/is_pointer_in_range.h> #include <__utility/move.h> #include +#include #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) # pragma GCC system_header @@ -37,9 +41,64 @@ template using __swap_result_t = void; #endif +#if _LIBCPP_STD_VER >= 26 +template + requires std::is_trivially_relocatable_v<_Tp> && std::is_replaceable_v<_Tp> +void swap_value_representations(_Tp& __a, _Tp& __b) noexcept { + char* __aptr = reinterpret_cast(&__a); + char* __bptr = reinterpret_cast(&__b); + + // If T is a polymorphic type, then T is the most derived type for the objects a and b. + _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(typeid(__a) == typeid(_Tp), "the dynamic type of a is not that of T"); + _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(typeid(__b) == typeid(_Tp), "the dynamic type of b is not that of T"); + + _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN( + !__is_pointer_in_range(__aptr, __aptr + __datasizeof(_Tp), __bptr) && + !__is_pointer_in_range(__bptr, __bptr + __datasizeof(_Tp), __aptr), + "Supporting overlapping objects would prevent optimizations"); + + // TODO: research better memswap algorithms + constexpr size_t __size = __datasizeof(_Tp) < 256 ? __datasizeof(_Tp) : 256; + constexpr size_t __chunk = __datasizeof(_Tp) / __size; + constexpr size_t __rem = __datasizeof(_Tp) % __size; + char __buffer[__size]; + if constexpr (__chunk) { + for (std::size_t n = 0; n < __chunk; n++, __aptr += __size, __bptr += __size) { + __builtin_memmove(__buffer, __aptr, __size); + __builtin_memmove(__aptr, __bptr, __size); + __builtin_memmove(__bptr, __buffer, __size); + } + } + if constexpr (__rem) { + __builtin_memmove(__buffer, __aptr, __rem); + __builtin_memmove(__aptr, __bptr, __rem); + __builtin_memmove(__bptr, __buffer, __rem); + } +} +#endif + template inline _LIBCPP_HIDE_FROM_ABI __swap_result_t<_Tp> _LIBCPP_CONSTEXPR_SINCE_CXX20 swap(_Tp& __x, _Tp& __y) _NOEXCEPT_(is_nothrow_move_constructible<_Tp>::value&& is_nothrow_move_assignable<_Tp>::value) { +#if _LIBCPP_STD_VER >= 26 + if !consteval { + if constexpr (!__is_scalar(_Tp) && std::is_trivially_relocatable_v<_Tp> && std::is_replaceable_v<_Tp> && + sizeof(_Tp) > sizeof(void*)) { + bool __is_eligible = true; + if constexpr (__is_polymorphic(_Tp)) { + if (typeid(__x) != typeid(_Tp) || typeid(__y) != typeid(_Tp)) + __is_eligible = false; + } + auto abs = [](auto a) { return a < ptrdiff_t(0) ? -a : a; }; + if (abs(&__x - &__y) < ptrdiff_t(__datasizeof(_Tp))) [[unlikely]] + __is_eligible = false; + if (__is_eligible) [[likely]] { + swap_value_representations(__x, __y); + return; + } + } + } +#endif _Tp __t(std::move(__x)); __x = std::move(__y); __y = std::move(__t); diff --git a/libcxx/include/string b/libcxx/include/string index c0cd88ad6b340..1fef17db545f6 100644 --- a/libcxx/include/string +++ b/libcxx/include/string @@ -750,9 +750,7 @@ struct __uninitialized_size_tag {}; struct __init_with_sentinel_tag {}; template -class basic_string - trivially_relocatable(is_trivially_relocatable_v<_Allocator> && - is_trivially_relocatable_v::pointer>) { +class basic_string memberwise_trivially_relocatable { private: using __default_allocator_type = allocator<_CharT>; From 286581a2aa55702b23fa37532c437094801ce886 Mon Sep 17 00:00:00 2001 From: Corentin Jabot Date: Thu, 8 Aug 2024 19:49:20 +0200 Subject: [PATCH 14/32] remove bogus overlapping ranges checks --- libcxx/include/__utility/swap.h | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/libcxx/include/__utility/swap.h b/libcxx/include/__utility/swap.h index 3d4f0b67a3b1c..a4662a9c9950a 100644 --- a/libcxx/include/__utility/swap.h +++ b/libcxx/include/__utility/swap.h @@ -41,10 +41,15 @@ template using __swap_result_t = void; #endif +//void __swap_overlapping(char* a, char* b, std::size_t n); +//{ +// _LIBCPP_ASSERT_NON_OVERLAPPING_RANGES(false, "swapping overlapping ranges"); +//} + #if _LIBCPP_STD_VER >= 26 template requires std::is_trivially_relocatable_v<_Tp> && std::is_replaceable_v<_Tp> -void swap_value_representations(_Tp& __a, _Tp& __b) noexcept { +void swap_value_representations(_Tp& __a, _Tp& __b) { char* __aptr = reinterpret_cast(&__a); char* __bptr = reinterpret_cast(&__b); @@ -52,10 +57,12 @@ void swap_value_representations(_Tp& __a, _Tp& __b) noexcept { _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(typeid(__a) == typeid(_Tp), "the dynamic type of a is not that of T"); _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(typeid(__b) == typeid(_Tp), "the dynamic type of b is not that of T"); - _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN( - !__is_pointer_in_range(__aptr, __aptr + __datasizeof(_Tp), __bptr) && - !__is_pointer_in_range(__bptr, __bptr + __datasizeof(_Tp), __aptr), - "Supporting overlapping objects would prevent optimizations"); + + //auto abs = [](auto __a) { return __a < ptrdiff_t(0) ? - __a : __a; }; + //if (abs(&__aptr - &__bptr) < ptrdiff_t(__datasizeof(_Tp))) [[unlikely]] { + // __swap_overlapping(__aptr, __bptr, __datasizeof(_Tp)); + // return; + //} // TODO: research better memswap algorithms constexpr size_t __size = __datasizeof(_Tp) < 256 ? __datasizeof(_Tp) : 256; @@ -89,9 +96,6 @@ inline _LIBCPP_HIDE_FROM_ABI __swap_result_t<_Tp> _LIBCPP_CONSTEXPR_SINCE_CXX20 if (typeid(__x) != typeid(_Tp) || typeid(__y) != typeid(_Tp)) __is_eligible = false; } - auto abs = [](auto a) { return a < ptrdiff_t(0) ? -a : a; }; - if (abs(&__x - &__y) < ptrdiff_t(__datasizeof(_Tp))) [[unlikely]] - __is_eligible = false; if (__is_eligible) [[likely]] { swap_value_representations(__x, __y); return; From 3195725a28c51a268b67a99c8a83bca4a3792150 Mon Sep 17 00:00:00 2001 From: Corentin Jabot Date: Thu, 8 Aug 2024 19:49:45 +0200 Subject: [PATCH 15/32] remove bogus overlapping ranges checks --- libcxx/include/__utility/swap.h | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/libcxx/include/__utility/swap.h b/libcxx/include/__utility/swap.h index a4662a9c9950a..8e625194f4247 100644 --- a/libcxx/include/__utility/swap.h +++ b/libcxx/include/__utility/swap.h @@ -41,11 +41,6 @@ template using __swap_result_t = void; #endif -//void __swap_overlapping(char* a, char* b, std::size_t n); -//{ -// _LIBCPP_ASSERT_NON_OVERLAPPING_RANGES(false, "swapping overlapping ranges"); -//} - #if _LIBCPP_STD_VER >= 26 template requires std::is_trivially_relocatable_v<_Tp> && std::is_replaceable_v<_Tp> @@ -58,12 +53,6 @@ void swap_value_representations(_Tp& __a, _Tp& __b) { _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(typeid(__b) == typeid(_Tp), "the dynamic type of b is not that of T"); - //auto abs = [](auto __a) { return __a < ptrdiff_t(0) ? - __a : __a; }; - //if (abs(&__aptr - &__bptr) < ptrdiff_t(__datasizeof(_Tp))) [[unlikely]] { - // __swap_overlapping(__aptr, __bptr, __datasizeof(_Tp)); - // return; - //} - // TODO: research better memswap algorithms constexpr size_t __size = __datasizeof(_Tp) < 256 ? __datasizeof(_Tp) : 256; constexpr size_t __chunk = __datasizeof(_Tp) / __size; From c4eb6131377222e20199dc20da1f0068801daefa Mon Sep 17 00:00:00 2001 From: Corentin Jabot Date: Thu, 8 Aug 2024 20:07:15 +0200 Subject: [PATCH 16/32] do not optimize swaps for trivial types --- libcxx/include/__utility/swap.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/libcxx/include/__utility/swap.h b/libcxx/include/__utility/swap.h index 8e625194f4247..17d101eaacb5b 100644 --- a/libcxx/include/__utility/swap.h +++ b/libcxx/include/__utility/swap.h @@ -14,6 +14,8 @@ #include <__type_traits/enable_if.h> #include <__type_traits/is_assignable.h> #include <__type_traits/is_constructible.h> +#include <__type_traits/is_trivially_assignable.h> +#include <__type_traits/is_trivially_constructible.h> #include <__type_traits/is_nothrow_assignable.h> #include <__type_traits/is_nothrow_constructible.h> #include <__type_traits/is_swappable.h> @@ -60,13 +62,13 @@ void swap_value_representations(_Tp& __a, _Tp& __b) { char __buffer[__size]; if constexpr (__chunk) { for (std::size_t n = 0; n < __chunk; n++, __aptr += __size, __bptr += __size) { - __builtin_memmove(__buffer, __aptr, __size); + __builtin_memcpy(__buffer, __aptr, __size); __builtin_memmove(__aptr, __bptr, __size); __builtin_memmove(__bptr, __buffer, __size); } } if constexpr (__rem) { - __builtin_memmove(__buffer, __aptr, __rem); + __builtin_memcpy(__buffer, __aptr, __rem); __builtin_memmove(__aptr, __bptr, __rem); __builtin_memmove(__bptr, __buffer, __rem); } @@ -78,8 +80,8 @@ inline _LIBCPP_HIDE_FROM_ABI __swap_result_t<_Tp> _LIBCPP_CONSTEXPR_SINCE_CXX20 _NOEXCEPT_(is_nothrow_move_constructible<_Tp>::value&& is_nothrow_move_assignable<_Tp>::value) { #if _LIBCPP_STD_VER >= 26 if !consteval { - if constexpr (!__is_scalar(_Tp) && std::is_trivially_relocatable_v<_Tp> && std::is_replaceable_v<_Tp> && - sizeof(_Tp) > sizeof(void*)) { + if constexpr (!(std::is_trivially_move_assignable_v<_Tp> && std::is_trivially_move_constructible_v<_Tp>) + && std::is_trivially_relocatable_v<_Tp> && std::is_replaceable_v<_Tp>) { bool __is_eligible = true; if constexpr (__is_polymorphic(_Tp)) { if (typeid(__x) != typeid(_Tp) || typeid(__y) != typeid(_Tp)) From f6f4a1838c822e7776956c5935a8dc9e31c5fbd5 Mon Sep 17 00:00:00 2001 From: Corentin Jabot Date: Sun, 18 Aug 2024 16:47:32 +0200 Subject: [PATCH 17/32] Add a __value_representation_begin builtin Returns the offset of the first subobject, if any. This allows us to skip the vtable in swap_value_representations --- clang/include/clang/AST/ASTContext.h | 2 ++ clang/include/clang/AST/Stmt.h | 2 +- clang/include/clang/Basic/TokenKinds.def | 1 + clang/lib/AST/ASTContext.cpp | 13 +++++++++ clang/lib/AST/ByteCode/Compiler.cpp | 7 +++-- clang/lib/AST/ExprConstant.cpp | 11 ++++++++ clang/lib/AST/ItaniumMangle.cpp | 8 ++++++ clang/lib/CodeGen/CGExprScalar.cpp | 5 ++-- clang/lib/Parse/ParseExpr.cpp | 18 ++++++++---- clang/lib/Sema/SemaExpr.cpp | 4 +-- libcxx/include/__utility/swap.h | 36 ++++++++++-------------- 11 files changed, 74 insertions(+), 33 deletions(-) diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h index 168bdca3c880b..960660b18b87a 100644 --- a/clang/include/clang/AST/ASTContext.h +++ b/clang/include/clang/AST/ASTContext.h @@ -2491,6 +2491,8 @@ class ASTContext : public RefCountedBase { TypeInfoChars getTypeInfoInChars(const Type *T) const; TypeInfoChars getTypeInfoInChars(QualType T) const; + CharUnits getStartOfValueRepresentation(QualType T) const; + /// Determine if the alignment the type has was required using an /// alignment attribute. bool isAlignmentRequired(const Type *T) const; diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h index f1a2aac0a8b2f..af39843dae3d4 100644 --- a/clang/include/clang/AST/Stmt.h +++ b/clang/include/clang/AST/Stmt.h @@ -530,7 +530,7 @@ class alignas(void *) Stmt { unsigned : NumExprBits; LLVM_PREFERRED_TYPE(UnaryExprOrTypeTrait) - unsigned Kind : 3; + unsigned Kind : 4; LLVM_PREFERRED_TYPE(bool) unsigned IsType : 1; // true if operand is a type, false if an expression. }; diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def index f60a9967c887a..dcf80fb32889c 100644 --- a/clang/include/clang/Basic/TokenKinds.def +++ b/clang/include/clang/Basic/TokenKinds.def @@ -320,6 +320,7 @@ KEYWORD(short , KEYALL) KEYWORD(signed , KEYALL) UNARY_EXPR_OR_TYPE_TRAIT(sizeof, SizeOf, KEYALL) UNARY_EXPR_OR_TYPE_TRAIT(__datasizeof, DataSizeOf, KEYCXX) +UNARY_EXPR_OR_TYPE_TRAIT(__value_representation_begin, ValueRepresentationBegin, KEYCXX) KEYWORD(static , KEYALL) KEYWORD(struct , KEYALL) KEYWORD(switch , KEYALL) diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index fa9cc38efc466..b206ce94d84de 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -1796,6 +1796,19 @@ TypeInfoChars ASTContext::getTypeInfoDataSizeInChars(QualType T) const { return Info; } +CharUnits ASTContext::getStartOfValueRepresentation(QualType T) const { + if (getLangOpts().CPlusPlus) { + if (const auto *RT = T->getAs(); + RT && !RT->getDecl()->isInvalidDecl()) { + const ASTRecordLayout &layout = getASTRecordLayout(RT->getDecl()); + if (layout.getFieldCount()) + return toCharUnitsFromBits(layout.getFieldOffset(0)); + return layout.getDataSize(); + } + } + return CharUnits::Zero(); +} + /// getConstantArrayInfoInChars - Performing the computation in CharUnits /// instead of in bits prevents overflowing the uint64_t for some large arrays. TypeInfoChars diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp index 43268bafc387b..b00fe70688946 100644 --- a/clang/lib/AST/ByteCode/Compiler.cpp +++ b/clang/lib/AST/ByteCode/Compiler.cpp @@ -1780,7 +1780,8 @@ bool Compiler::VisitUnaryExprOrTypeTraitExpr( UnaryExprOrTypeTrait Kind = E->getKind(); const ASTContext &ASTCtx = Ctx.getASTContext(); - if (Kind == UETT_SizeOf || Kind == UETT_DataSizeOf) { + if (Kind == UETT_SizeOf || Kind == UETT_DataSizeOf || + Kind == UETT_ValueRepresentationBegin) { QualType ArgType = E->getTypeOfArgument(); // C++ [expr.sizeof]p2: "When applied to a reference or a reference type, @@ -1797,8 +1798,10 @@ bool Compiler::VisitUnaryExprOrTypeTraitExpr( if (Kind == UETT_SizeOf) Size = ASTCtx.getTypeSizeInChars(ArgType); - else + else if (Kind == UETT_DataSizeOf) Size = ASTCtx.getTypeInfoDataSizeInChars(ArgType).Width; + else + Size = ASTCtx.getStartOfValueRepresentation(ArgType); } if (DiscardResult) diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 78d2500636004..10df142c798b4 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -14289,6 +14289,17 @@ bool IntExprEvaluator::VisitUnaryExprOrTypeTraitExpr( } return Success(Sizeof, E); } + case UETT_ValueRepresentationBegin: { + QualType SrcTy = E->getTypeOfArgument(); + if (const ReferenceType *Ref = SrcTy->getAs()) + SrcTy = Ref->getPointeeType(); + if (SrcTy->isDependentType() || SrcTy->isIncompleteType()) { + Info.FFDiag(E->getBeginLoc()); + return false; + } + return Success(Info.Ctx.getStartOfValueRepresentation(SrcTy), E); + } + case UETT_OpenMPRequiredSimdAlign: assert(E->isArgumentType()); return Success( diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp index 7d638befcbd3f..c5661eb8f6871 100644 --- a/clang/lib/AST/ItaniumMangle.cpp +++ b/clang/lib/AST/ItaniumMangle.cpp @@ -5215,6 +5215,14 @@ void CXXNameMangler::mangleExpression(const Expr *E, unsigned Arity, Diags.Report(DiagID); return; } + case UETT_ValueRepresentationBegin: { + DiagnosticsEngine &Diags = Context.getDiags(); + unsigned DiagID = Diags.getCustomDiagID( + DiagnosticsEngine::Error, + "cannot yet mangle __value_representation_begin expression"); + Diags.Report(DiagID); + return; + } case UETT_PtrAuthTypeDiscriminator: { DiagnosticsEngine &Diags = Context.getDiags(); unsigned DiagID = Diags.getCustomDiagID( diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp index 88fbbe6c4c965..818a7eac01687 100644 --- a/clang/lib/CodeGen/CGExprScalar.cpp +++ b/clang/lib/CodeGen/CGExprScalar.cpp @@ -3355,8 +3355,9 @@ Value * ScalarExprEmitter::VisitUnaryExprOrTypeTraitExpr( const UnaryExprOrTypeTraitExpr *E) { QualType TypeToSize = E->getTypeOfArgument(); - if (auto Kind = E->getKind(); - Kind == UETT_SizeOf || Kind == UETT_DataSizeOf) { + if (auto Kind = E->getKind(); Kind == UETT_SizeOf || + Kind == UETT_DataSizeOf || + Kind == clang::UETT_ValueRepresentationBegin) { if (const VariableArrayType *VAT = CGF.getContext().getAsVariableArrayType(TypeToSize)) { if (E->isArgumentType()) { diff --git a/clang/lib/Parse/ParseExpr.cpp b/clang/lib/Parse/ParseExpr.cpp index 64f284d78b24d..430e68fc973fa 100644 --- a/clang/lib/Parse/ParseExpr.cpp +++ b/clang/lib/Parse/ParseExpr.cpp @@ -1526,6 +1526,7 @@ ExprResult Parser::ParseCastExpression(CastParseKind ParseKind, // unary-expression: '__datasizeof' unary-expression // unary-expression: '__datasizeof' '(' type-name ')' case tok::kw___datasizeof: + case tok::kw___value_representation_begin: case tok::kw_vec_step: // unary-expression: OpenCL 'vec_step' expression // unary-expression: '__builtin_omp_required_simd_align' '(' type-name ')' case tok::kw___builtin_omp_required_simd_align: @@ -2437,8 +2438,9 @@ Parser::ParseExprAfterUnaryExprOrTypeTrait(const Token &OpTok, SourceRange &CastRange) { assert(OpTok.isOneOf(tok::kw_typeof, tok::kw_typeof_unqual, tok::kw_sizeof, - tok::kw___datasizeof, tok::kw___alignof, tok::kw_alignof, - tok::kw__Alignof, tok::kw_vec_step, + tok::kw___datasizeof, + tok::kw___value_representation_begin, tok::kw___alignof, + tok::kw_alignof, tok::kw__Alignof, tok::kw_vec_step, tok::kw___builtin_omp_required_simd_align, tok::kw___builtin_vectorelements) && "Not a typeof/sizeof/alignof/vec_step expression!"); @@ -2449,7 +2451,8 @@ Parser::ParseExprAfterUnaryExprOrTypeTrait(const Token &OpTok, if (Tok.isNot(tok::l_paren)) { // If construct allows a form without parenthesis, user may forget to put // pathenthesis around type name. - if (OpTok.isOneOf(tok::kw_sizeof, tok::kw___datasizeof, tok::kw___alignof, + if (OpTok.isOneOf(tok::kw_sizeof, tok::kw___datasizeof, + tok::kw___value_representation_begin, tok::kw___alignof, tok::kw_alignof, tok::kw__Alignof)) { if (isTypeIdUnambiguously()) { DeclSpec DS(AttrFactory); @@ -2486,7 +2489,8 @@ Parser::ParseExprAfterUnaryExprOrTypeTrait(const Token &OpTok, // followed by a non-parenthesized expression, BalancedDelimiterTracker // is not going to help when the nesting is too deep. In this corner case // we continue to parse with sufficient stack space to avoid crashing. - if (OpTok.isOneOf(tok::kw_sizeof, tok::kw___datasizeof, tok::kw___alignof, + if (OpTok.isOneOf(tok::kw_sizeof, tok::kw___datasizeof, + tok::kw___value_representation_begin, tok::kw___alignof, tok::kw_alignof, tok::kw__Alignof) && Tok.isOneOf(tok::kw_sizeof, tok::kw___datasizeof, tok::kw___alignof, tok::kw_alignof, tok::kw__Alignof)) @@ -2573,7 +2577,8 @@ ExprResult Parser::ParseSYCLUniqueStableNameExpression() { /// [C++11] 'alignof' '(' type-id ')' /// \endverbatim ExprResult Parser::ParseUnaryExprOrTypeTraitExpression() { - assert(Tok.isOneOf(tok::kw_sizeof, tok::kw___datasizeof, tok::kw___alignof, + assert(Tok.isOneOf(tok::kw_sizeof, tok::kw___datasizeof, + tok::kw___value_representation_begin, tok::kw___alignof, tok::kw_alignof, tok::kw__Alignof, tok::kw_vec_step, tok::kw___builtin_omp_required_simd_align, tok::kw___builtin_vectorelements) && @@ -2664,6 +2669,9 @@ ExprResult Parser::ParseUnaryExprOrTypeTraitExpression() { case tok::kw___datasizeof: ExprKind = UETT_DataSizeOf; break; + case tok::kw___value_representation_begin: + ExprKind = UETT_ValueRepresentationBegin; + break; case tok::kw___builtin_vectorelements: ExprKind = UETT_VectorElements; break; diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 32dac4440fb82..d7c6fe032eb20 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -4216,8 +4216,8 @@ bool Sema::CheckUnaryExprOrTypeTraitOperand(Expr *E, bool IsUnevaluatedOperand = (ExprKind == UETT_SizeOf || ExprKind == UETT_DataSizeOf || - ExprKind == UETT_AlignOf || ExprKind == UETT_PreferredAlignOf || - ExprKind == UETT_VecStep); + ExprKind == UETT_ValueRepresentationBegin || ExprKind == UETT_AlignOf || + ExprKind == UETT_PreferredAlignOf || ExprKind == UETT_VecStep); if (IsUnevaluatedOperand) { ExprResult Result = CheckUnevaluatedOperand(E); if (Result.isInvalid()) diff --git a/libcxx/include/__utility/swap.h b/libcxx/include/__utility/swap.h index 17d101eaacb5b..ed6ad3e5eb22f 100644 --- a/libcxx/include/__utility/swap.h +++ b/libcxx/include/__utility/swap.h @@ -9,7 +9,6 @@ #ifndef _LIBCPP___UTILITY_SWAP_H #define _LIBCPP___UTILITY_SWAP_H -#include <__assert> #include <__config> #include <__type_traits/enable_if.h> #include <__type_traits/is_assignable.h> @@ -21,7 +20,6 @@ #include <__type_traits/is_swappable.h> #include <__type_traits/is_trivially_relocatable.h> #include <__utility/declval.h> -#include <__utility/is_pointer_in_range.h> #include <__utility/move.h> #include #include @@ -47,18 +45,21 @@ using __swap_result_t = void; template requires std::is_trivially_relocatable_v<_Tp> && std::is_replaceable_v<_Tp> void swap_value_representations(_Tp& __a, _Tp& __b) { - char* __aptr = reinterpret_cast(&__a); - char* __bptr = reinterpret_cast(&__b); + // For now we assume the value representation correspond to the memory + // between the start of the first subobject and the end of the last subobject + // which holds for both itanium and MSVC. + constexpr size_t __value_size = __datasizeof(_Tp) - __value_representation_begin(_Tp); + if constexpr (__value_size == 0) + return; - // If T is a polymorphic type, then T is the most derived type for the objects a and b. - _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(typeid(__a) == typeid(_Tp), "the dynamic type of a is not that of T"); - _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(typeid(__b) == typeid(_Tp), "the dynamic type of b is not that of T"); + char* __aptr = reinterpret_cast(&__a) + __value_representation_begin(_Tp); + char* __bptr = reinterpret_cast(&__b) + __value_representation_begin(_Tp); + constexpr size_t __size = __value_size < 512 ? __datasizeof(_Tp) : 512; + constexpr size_t __chunk = __value_size / __size; + constexpr size_t __rem = __value_size % __size; // TODO: research better memswap algorithms - constexpr size_t __size = __datasizeof(_Tp) < 256 ? __datasizeof(_Tp) : 256; - constexpr size_t __chunk = __datasizeof(_Tp) / __size; - constexpr size_t __rem = __datasizeof(_Tp) % __size; char __buffer[__size]; if constexpr (__chunk) { for (std::size_t n = 0; n < __chunk; n++, __aptr += __size, __bptr += __size) { @@ -80,17 +81,10 @@ inline _LIBCPP_HIDE_FROM_ABI __swap_result_t<_Tp> _LIBCPP_CONSTEXPR_SINCE_CXX20 _NOEXCEPT_(is_nothrow_move_constructible<_Tp>::value&& is_nothrow_move_assignable<_Tp>::value) { #if _LIBCPP_STD_VER >= 26 if !consteval { - if constexpr (!(std::is_trivially_move_assignable_v<_Tp> && std::is_trivially_move_constructible_v<_Tp>) - && std::is_trivially_relocatable_v<_Tp> && std::is_replaceable_v<_Tp>) { - bool __is_eligible = true; - if constexpr (__is_polymorphic(_Tp)) { - if (typeid(__x) != typeid(_Tp) || typeid(__y) != typeid(_Tp)) - __is_eligible = false; - } - if (__is_eligible) [[likely]] { - swap_value_representations(__x, __y); - return; - } + if constexpr (!(std::is_trivially_move_assignable_v<_Tp> && std::is_trivially_move_constructible_v<_Tp>) && + std::is_trivially_relocatable_v<_Tp> && std::is_replaceable_v<_Tp>) { + swap_value_representations(__x, __y); + return; } } #endif From 7298bf1ba473c35fd3c0ae1aa724b68f13847e83 Mon Sep 17 00:00:00 2001 From: Corentin Jabot Date: Mon, 19 Aug 2024 10:07:49 +0200 Subject: [PATCH 18/32] Fix tests --- clang/lib/Sema/SemaDeclCXX.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 80cea6387ddee..06733dfed8968 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -7205,9 +7205,7 @@ static bool hasDeletedMoveConstructor(CXXRecordDecl *D) { return CD->isDeleted(); } } - return false; - // return !D->needsImplicitMoveConstructor() || - // D->defaultedMoveConstructorIsDeleted(); + return !D->needsImplicitMoveConstructor(); } static bool hasDeletedMoveAssignment(CXXRecordDecl *D) { @@ -7221,9 +7219,7 @@ static bool hasDeletedMoveAssignment(CXXRecordDecl *D) { continue; return MD->isDeleted(); } - return false; - // return !D->needsImplicitMoveAssignment() || - // D->defaultedMoveAssignmentIsDeleted(); + return !D->needsImplicitMoveAssignment(); } void Sema::CheckCXX2CTriviallyRelocatable(CXXRecordDecl *D) { From 1743ca46d0954ff61f59b8692c77afff5efdc501 Mon Sep 17 00:00:00 2001 From: Corentin Jabot Date: Mon, 19 Aug 2024 11:07:14 +0200 Subject: [PATCH 19/32] Diagnose virtual bases on memberwise_replaceable types --- clang/lib/Sema/SemaDeclCXX.cpp | 4 ++-- clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 06733dfed8968..c0a3fff5206ca 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -7334,11 +7334,11 @@ void Sema::CheckMemberwiseReplaceable(CXXRecordDecl *D) { const auto *BaseDecl = B.getType()->getAsCXXRecordDecl(); if (!BaseDecl) continue; - if (!BaseDecl->isDependentType() && !BaseDecl->isReplaceable()) { + if ((!BaseDecl->isDependentType() && !BaseDecl->isReplaceable()) || B.isVirtual()) { if (MarkedMemberwiseReplaceable) { if (DiagnosticInvalidExplicitSpecifier()) Diag(B.getBeginLoc(), diag::note_trivially_relocatable) - << 0 << BaseDecl << B.getSourceRange(); + << (B.isVirtual()? 1 : 0) << BaseDecl << B.getSourceRange(); } IsReplaceable = false; } diff --git a/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp b/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp index 7c1f13a53866a..6658f501d8d9e 100644 --- a/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp +++ b/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp @@ -206,7 +206,7 @@ static_assert(!__builtin_is_replaceable(S)); static_assert(__builtin_is_replaceable(WithBase>)); static_assert(!__builtin_is_replaceable(WithBase>)); static_assert(!__builtin_is_replaceable(WithBase)); -static_assert(__builtin_is_replaceable(WithVBase>)); +static_assert(!__builtin_is_replaceable(WithVBase>)); static_assert(!__builtin_is_replaceable(WithVBase>)); static_assert(!__builtin_is_replaceable(WithVBase)); static_assert(__builtin_is_replaceable(WithVirtual)); @@ -228,4 +228,11 @@ struct U2 memberwise_replaceable { static_assert(!__builtin_is_replaceable(U2)); +template +struct WithVBaseExplicit memberwise_replaceable : virtual T{}; +// expected-error@-1 {{invalid 'memberwise_replaceable' specifier on non memberwise replaceable class 'WithVBaseExplicit>'}} \ +// expected-note@-1 {{because it has a virtual base class 'S'}} +static_assert(__builtin_is_replaceable(WithVBaseExplicit>)); // expected-error {{failed}} \ + // expected-note {{requested here}} + } From c2a74ad7f73536357b46cd968ae6860884b976d1 Mon Sep 17 00:00:00 2001 From: Corentin Jabot Date: Mon, 19 Aug 2024 21:22:54 +0200 Subject: [PATCH 20/32] fix defaulted special member handling --- clang/lib/Sema/SemaDeclCXX.cpp | 7 +++---- clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp | 10 ++++++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index c0a3fff5206ca..2592747c8d08a 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -7200,12 +7200,11 @@ void Sema::CheckCompletedCXXClass(Scope *S, CXXRecordDecl *Record) { static bool hasDeletedMoveConstructor(CXXRecordDecl *D) { assert(D->hasDefinition() && !D->isInvalidDecl()); for (const CXXConstructorDecl *CD : D->ctors()) { - if (CD->isMoveConstructor() && CD->isDefaulted() && - !CD->isIneligibleOrNotSelected()) { + if (CD->isMoveConstructor() && !CD->isIneligibleOrNotSelected()) { return CD->isDeleted(); } } - return !D->needsImplicitMoveConstructor(); + return !D->needsImplicitMoveConstructor() || D->defaultedMoveConstructorIsDeleted(); } static bool hasDeletedMoveAssignment(CXXRecordDecl *D) { @@ -7219,7 +7218,7 @@ static bool hasDeletedMoveAssignment(CXXRecordDecl *D) { continue; return MD->isDeleted(); } - return !D->needsImplicitMoveAssignment(); + return !D->needsImplicitMoveAssignment() || D->defaultedMoveAssignmentIsDeleted(); } void Sema::CheckCXX2CTriviallyRelocatable(CXXRecordDecl *D) { diff --git a/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp b/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp index 6658f501d8d9e..baf5c148ce7a5 100644 --- a/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp +++ b/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp @@ -235,4 +235,14 @@ struct WithVBaseExplicit memberwise_replaceable : virtual T{}; static_assert(__builtin_is_replaceable(WithVBaseExplicit>)); // expected-error {{failed}} \ // expected-note {{requested here}} +struct S42 memberwise_trivially_relocatable memberwise_replaceable { + S42(S42&&); + S42& operator=(S42&&) = default; +}; +struct S43 memberwise_trivially_relocatable memberwise_replaceable { + S43(S43&&) = default; + S43& operator=(S43&&); +}; + + } From 79bda20c37ae238cc13961d12ccbc93be162af4a Mon Sep 17 00:00:00 2001 From: Corentin Jabot Date: Tue, 27 Aug 2024 12:52:22 +0200 Subject: [PATCH 21/32] fix conflicts --- clang/include/clang/Sema/Sema.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index d77ac7c7fb719..eccbe0baa8bd0 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -17,14 +17,11 @@ #include "clang/APINotes/APINotesManager.h" #include "clang/AST/ASTFwd.h" #include "clang/AST/Attr.h" -<<<<<<< HEAD #include "clang/AST/AttrIterator.h" #include "clang/AST/CharUnits.h" #include "clang/AST/DeclBase.h" -======= #include "clang/AST/Availability.h" #include "clang/AST/ComparisonCategories.h" ->>>>>>> dec76e3b17fb (rough draft of memberwise_trivially_relocatable/memberwise_repleceable) #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclTemplate.h" #include "clang/AST/DeclarationName.h" From 6fc10203b6de9800aee656bf81e2e5c01e4e301b Mon Sep 17 00:00:00 2001 From: Corentin Jabot Date: Tue, 27 Aug 2024 15:00:45 +0200 Subject: [PATCH 22/32] fix moduile tests --- libcxx/include/module.modulemap | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap index 65df579b8d6dd..9d15dc4764f7d 100644 --- a/libcxx/include/module.modulemap +++ b/libcxx/include/module.modulemap @@ -1504,6 +1504,7 @@ module std_private_memory_allocator_destructor [system] { header "__m module std_private_memory_allocator_traits [system] { header "__memory/allocator_traits.h" } module std_private_memory_assume_aligned [system] { header "__memory/assume_aligned.h" } module std_private_memory_auto_ptr [system] { header "__memory/auto_ptr.h" } +module std_private_memory_relocate [system] { header "__memory/relocate.h" } module std_private_memory_builtin_new_allocator [system] { header "__memory/builtin_new_allocator.h" export * @@ -2102,6 +2103,9 @@ module std_private_utility_small_buffer [system] { header "__utility/ module std_private_utility_swap [system] { header "__utility/swap.h" export std_private_type_traits_is_swappable + export std_private_type_traits_is_assignable + export std_private_type_traits_is_constructible + export std_private_concepts_movable } module std_private_utility_to_underlying [system] { header "__utility/to_underlying.h" } module std_private_utility_unreachable [system] { header "__utility/unreachable.h" } From caf0027d71c8b6595d0d3d03493e26985a5e045d Mon Sep 17 00:00:00 2001 From: Corentin Jabot Date: Wed, 28 Aug 2024 15:08:01 +0200 Subject: [PATCH 23/32] fix trivial_relocate in vector --- libcxx/include/vector | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libcxx/include/vector b/libcxx/include/vector index 0cc66dc486535..27d9ccc451d2b 100644 --- a/libcxx/include/vector +++ b/libcxx/include/vector @@ -1608,7 +1608,7 @@ vector<_Tp, _Allocator>::erase(const_iterator __first, const_iterator __last) { for (pointer __to_destroy = __p; __to_destroy < __p0; ++__to_destroy) __alloc_traits::destroy(__alloc(), std::__to_address(__to_destroy)); // move the rest down - (void) trivially_relocate(__p0, this->__end_, __p); + (void) trivially_relocate(std::__to_address(__p0), std::__to_address(this->__end_), std::__to_address(__p)); // update the end this->__end_ -= __count; __annotate_shrink(__old_size); @@ -1647,7 +1647,7 @@ vector<_Tp, _Allocator>::insert(const_iterator __position, const_reference __x) if constexpr (is_trivially_relocatable_v<_Tp>) { // Make space by trivially relocating everything _ConstructTransaction __tx(*this, 1); - (void) trivially_relocate(__p, this->__end_, __p + 1); + (void) trivially_relocate(std::__to_address(__p), std::__to_address(this->__end_), __p + 1); // construct the new element (not assign!) const_pointer __xr = pointer_traits::pointer_to(__x); if (std::__is_pointer_in_range(std::__to_address(__p), std::__to_address(__end_), std::addressof(__x))) @@ -1687,7 +1687,7 @@ vector<_Tp, _Allocator>::insert(const_iterator __position, value_type&& __x) { if constexpr (is_trivially_relocatable_v<_Tp>) { // Make space by trivially relocating everything _ConstructTransaction __tx(*this, 1); - (void) trivially_relocate(__p, this->__end_, __p + 1); + (void) trivially_relocate(std::__to_address(__p), std::__to_address(this->__end_), __p + 1); // construct the new element (not assign!) __alloc_traits::construct(this->__alloc(), std::__to_address(__p), std::forward(__x)); ++__tx.__pos_; @@ -1722,7 +1722,7 @@ vector<_Tp, _Allocator>::emplace(const_iterator __position, _Args&&... __args) { if constexpr (is_trivially_relocatable_v<_Tp>) { // Make space by trivially relocating everything _ConstructTransaction __tx(*this, 1); - (void) trivially_relocate(__p, this->__end_, __p + 1); + (void) trivially_relocate(std::__to_address(__p), std::__to_address(this->__end_), __p + 1); // construct the new element __alloc_traits::construct(this->__alloc(), std::__to_address(__p), std::forward<_Args>(__args)...); ++__tx.__pos_; From e8a1fa32f64c724c385907a420625d54a0bb2c04 Mon Sep 17 00:00:00 2001 From: Corentin Jabot Date: Fri, 30 Aug 2024 15:21:39 +0200 Subject: [PATCH 24/32] types with a defaulted copy constructor are relocatable --- clang/lib/Sema/SemaDeclCXX.cpp | 137 ++++++++++-------- .../SemaCXX/cxx2c-trivially-relocatable.cpp | 18 +++ 2 files changed, 92 insertions(+), 63 deletions(-) diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 2592747c8d08a..bf040755134c2 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -7197,28 +7197,76 @@ void Sema::CheckCompletedCXXClass(Scope *S, CXXRecordDecl *Record) { } } -static bool hasDeletedMoveConstructor(CXXRecordDecl *D) { - assert(D->hasDefinition() && !D->isInvalidDecl()); - for (const CXXConstructorDecl *CD : D->ctors()) { - if (CD->isMoveConstructor() && !CD->isIneligibleOrNotSelected()) { - return CD->isDeleted(); +static bool hasSuitableConstructorForReplaceability(CXXRecordDecl *D, bool Implicit) { + assert(D->hasDefinition() && !D->isInvalidDecl()); + + bool HasDeletedMoveConstructor = false; + bool HasDeletedCopyConstructor = false; + bool HasMoveConstructor = D->needsImplicitMoveConstructor(); + bool HasDefaultedMoveConstructor = D->needsImplicitMoveConstructor(); + bool HasDefaultedCopyConstructor = D->needsImplicitMoveConstructor(); + + for (const Decl *D : D->decls()) { + auto *MD = dyn_cast(D); + if (!MD || MD->isIneligibleOrNotSelected()) + continue; + + if(MD->isMoveConstructor()) { + HasMoveConstructor = true; + if(MD->isDefaulted()) + HasDefaultedMoveConstructor = true; + if(MD->isDeleted()) + HasDeletedMoveConstructor = true; + } + if(MD->isCopyConstructor()) { + if(MD->isDefaulted()) + HasDefaultedCopyConstructor = true; + if(MD->isDeleted()) + HasDeletedCopyConstructor = true; + } } - } - return !D->needsImplicitMoveConstructor() || D->defaultedMoveConstructorIsDeleted(); + + if(HasMoveConstructor) + return !HasDeletedMoveConstructor && (Implicit ? HasDefaultedMoveConstructor : true); + return !HasDeletedCopyConstructor && (Implicit ? HasDefaultedCopyConstructor : true);; } -static bool hasDeletedMoveAssignment(CXXRecordDecl *D) { - assert(D->hasDefinition() && !D->isInvalidDecl()); - if (D->hasExplicitlyDeletedMoveAssignment()) - return true; - for (const Decl *D : D->decls()) { - auto *MD = dyn_cast(D); - if (!MD || !MD->isMoveAssignmentOperator() || - MD->isIneligibleOrNotSelected()) - continue; - return MD->isDeleted(); - } - return !D->needsImplicitMoveAssignment() || D->defaultedMoveAssignmentIsDeleted(); + +static bool hasSuitableMoveAssignmentOperatorForReplaceability(CXXRecordDecl *D, bool Implicit) { + assert(D->hasDefinition() && !D->isInvalidDecl()); + + if (D->hasExplicitlyDeletedMoveAssignment()) + return false; + + bool HasDeletedMoveAssignment = false; + bool HasDeletedCopyAssignment = false; + bool HasMoveAssignment = D->needsImplicitMoveAssignment(); + bool HasDefaultedMoveAssignment = D->needsImplicitMoveAssignment(); + bool HasDefaultedCopyAssignment = D->needsImplicitCopyAssignment(); + + for (const Decl *D : D->decls()) { + auto *MD = dyn_cast(D); + if (!MD || MD->isIneligibleOrNotSelected()) + continue; + + if(MD->isMoveAssignmentOperator()) { + HasMoveAssignment = true; + if(MD->isDefaulted()) + HasDefaultedMoveAssignment = true; + if(MD->isDeleted()) + HasDeletedMoveAssignment = true; + } + if(MD->isCopyAssignmentOperator()) { + if(MD->isDefaulted()) + HasDefaultedCopyAssignment = true; + if(MD->isDeleted()) + HasDeletedCopyAssignment = true; + } + } + + if(HasMoveAssignment) + return !HasDeletedMoveAssignment && (Implicit ? HasDefaultedMoveAssignment : true); + return !HasDeletedCopyAssignment && (Implicit ? HasDefaultedCopyAssignment : true); } void Sema::CheckCXX2CTriviallyRelocatable(CXXRecordDecl *D) { @@ -7343,51 +7391,14 @@ void Sema::CheckMemberwiseReplaceable(CXXRecordDecl *D) { } } - if (hasDeletedMoveAssignment(D) || hasDeletedMoveConstructor(D)) { - IsReplaceable = false; - if (MarkedMemberwiseReplaceable) { - if (DiagnosticInvalidExplicitSpecifier()) - Diag(D->getBeginLoc(), diag::note_trivially_relocatable) - << 2 << D->getSourceRange(); - } - } - - if (IsReplaceable && !D->isDependentType() && !MarkedMemberwiseReplaceable) { - bool HasSuitableMoveCtr = D->needsImplicitMoveConstructor() && - !D->defaultedMoveConstructorIsDeleted(); - bool HasSuitableCopyCtr = false; - if (IsReplaceable && !HasSuitableMoveCtr) { - for (const CXXConstructorDecl *CD : D->ctors()) { - if (CD->isMoveConstructor() && CD->isDefaulted() && - !CD->isIneligibleOrNotSelected()) { - HasSuitableMoveCtr = true; - break; - } - } - } - if (!HasSuitableMoveCtr && !D->hasMoveConstructor()) { - HasSuitableCopyCtr = D->needsImplicitCopyConstructor() && - !D->defaultedCopyConstructorIsDeleted(); - if (!HasSuitableCopyCtr) { - for (const CXXConstructorDecl *CD : D->ctors()) { - if (CD->isCopyConstructor() && CD->isDefaulted() && - !CD->isIneligibleOrNotSelected()) { - HasSuitableCopyCtr = true; - break; - } - } - } - } - if (!HasSuitableMoveCtr && !HasSuitableCopyCtr) { - IsReplaceable = false; - } - - if (IsReplaceable && - ((!D->needsImplicitMoveAssignment() && - D->hasUserProvidedMoveAssignment()) || - (!D->hasMoveAssignment() && D->hasUserProvidedCopyAssignment()))) { + if(!hasSuitableConstructorForReplaceability(D, !MarkedMemberwiseReplaceable) + || !hasSuitableMoveAssignmentOperatorForReplaceability(D, !MarkedMemberwiseReplaceable)) { IsReplaceable = false; - } + if (MarkedMemberwiseReplaceable) { + if (DiagnosticInvalidExplicitSpecifier()) + Diag(D->getBeginLoc(), diag::note_trivially_relocatable) + << 2 << D->getSourceRange(); + } } if (IsReplaceable) { diff --git a/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp b/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp index baf5c148ce7a5..6ceba8f1a0021 100644 --- a/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp +++ b/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp @@ -245,4 +245,22 @@ struct S43 memberwise_trivially_relocatable memberwise_replaceable { }; +struct Copyable1Explicit memberwise_replaceable { + Copyable1Explicit(Copyable1Explicit const &) = default; +}; + +struct Copyable1 { + Copyable1(Copyable1 const &) = default; +}; + + +struct CopyAssign1Explicit memberwise_replaceable { + CopyAssign1Explicit & operator=(const CopyAssign1Explicit&) = default; +}; + +struct CopyAssign1 { + CopyAssign1 & operator=(CopyAssign1 const &) = default; +}; + + } From 97bfd981b75b484b267ca517e52ad7ece759209b Mon Sep 17 00:00:00 2001 From: Corentin Jabot Date: Thu, 5 Sep 2024 23:16:55 +0200 Subject: [PATCH 25/32] Haqmdle relocatable unions --- clang/lib/Sema/SemaDeclCXX.cpp | 13 +++++++++++-- clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp | 5 +++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index bf040755134c2..b75e9317ea539 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -7344,10 +7344,18 @@ void Sema::CheckCXX2CTriviallyRelocatable(CXXRecordDecl *D) { } } } - if (!HasSuitableMoveCtr && !HasSuitableCopyCtr) + + if(D->isUnion() && !D->hasUserDeclaredCopyConstructor() + && !D->hasUserDeclaredCopyAssignment() + && !D->hasUserDeclaredMoveOperation() + && !D->hasUserDeclaredDestructor()) { + // Do nothing + } + + else if (!HasSuitableMoveCtr && !HasSuitableCopyCtr) IsTriviallyRelocatable = false; - if (IsTriviallyRelocatable && + else if (IsTriviallyRelocatable && ((!D->needsImplicitMoveAssignment() && (D->hasUserProvidedMoveAssignment() || D->hasExplicitlyDeletedMoveAssignment())) || @@ -7355,6 +7363,7 @@ void Sema::CheckCXX2CTriviallyRelocatable(CXXRecordDecl *D) { IsTriviallyRelocatable = false; } } + D->setIsTriviallyRelocatable(IsTriviallyRelocatable); } diff --git a/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp b/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp index 6ceba8f1a0021..54878190cc81c 100644 --- a/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp +++ b/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp @@ -122,6 +122,11 @@ static_assert(!__is_cpp_trivially_relocatable(DeletedMove)); static_assert(!__is_cpp_trivially_relocatable(DeletedCopy)); static_assert(!__is_cpp_trivially_relocatable(DeletedMoveAssign)); +union U { + G g; +}; +static_assert(!__is_trivially_copyable(U)); +static_assert(__is_cpp_trivially_relocatable(U)); namespace replaceable { From b0624fd7ec60d45b24ca22e53f0bc03f994f18bb Mon Sep 17 00:00:00 2001 From: Corentin Jabot Date: Sun, 8 Sep 2024 12:33:18 +0200 Subject: [PATCH 26/32] fix rebase --- clang/lib/Sema/SemaChecking.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index 5a239c7fd2e8a..6a97c99179935 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -1879,6 +1879,9 @@ 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(); From 7859fceba5dbf0ab3902ff4c26b7ec9407c5953c Mon Sep 17 00:00:00 2001 From: Corentin Jabot Date: Thu, 12 Sep 2024 12:26:32 +0200 Subject: [PATCH 27/32] Downgrade invalid use of memberwise_ keywords errors into a warning --- clang/include/clang/Basic/DiagnosticSemaKinds.td | 14 +++++++++----- clang/lib/Sema/SemaDeclCXX.cpp | 4 ++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index baba389dbcf0a..29eeb40d5f9de 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -2681,11 +2681,15 @@ def warn_final_dtor_non_final_class : Warning< def note_final_dtor_non_final_class_silence : Note< "mark %0 as '%select{final|sealed}1' to silence this warning">; -def err_trivially_relocatable_specifier_on_non_relocatable_class : Error< - "invalid 'trivially_relocatable' specifier on non trivially-relocatable class %0">; - -def err_memberwise_replaceable_specifier_on_non_relocatable_class : Error< - "invalid 'memberwise_replaceable' specifier on non memberwise replaceable class %0">; +def warn_trivially_relocatable_specifier_on_non_relocatable_class : Warning< + "invalid 'trivially_relocatable' specifier on non trivially-relocatable class %0">, + InGroup>, + DefaultError; + +def warn_memberwise_replaceable_specifier_on_non_relocatable_class : Warning< + "invalid 'memberwise_replaceable' specifier on non memberwise replaceable class %0">, + InGroup>, + DefaultError; def note_trivially_relocatable: Note< "because it %select{inherits from a non trivially-relocatable class %1|" diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index b75e9317ea539..b933e1a8e5c7f 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -7280,7 +7280,7 @@ void Sema::CheckCXX2CTriviallyRelocatable(CXXRecordDecl *D) { if (NotedError) return; Diag(Loc, - diag::err_trivially_relocatable_specifier_on_non_relocatable_class) + diag::warn_trivially_relocatable_specifier_on_non_relocatable_class) << D; NotedError = true; }; @@ -7378,7 +7378,7 @@ void Sema::CheckMemberwiseReplaceable(CXXRecordDecl *D) { if (NotedError) return false; Diag(Loc, - diag::err_memberwise_replaceable_specifier_on_non_relocatable_class) + diag::warn_memberwise_replaceable_specifier_on_non_relocatable_class) << D; bool Old = NotedError; NotedError = true; From 4cdddbbe0be7213d6fff940ec9a414582548aa10 Mon Sep 17 00:00:00 2001 From: Marshall Clow Date: Fri, 27 Sep 2024 13:02:27 -0700 Subject: [PATCH 28/32] Remove 'unintialized_relocate', add 'relocate' --- libcxx/include/__memory/relocate.h | 67 +++++- .../__memory/uninitialized_algorithms.h | 199 ------------------ 2 files changed, 65 insertions(+), 201 deletions(-) diff --git a/libcxx/include/__memory/relocate.h b/libcxx/include/__memory/relocate.h index aa04003c162ac..f98c1acb3a0fd 100644 --- a/libcxx/include/__memory/relocate.h +++ b/libcxx/include/__memory/relocate.h @@ -2,6 +2,7 @@ #define _LIBCPP___MEMORY_RELOCATE_H #include <__config> +#include <__functional/operations.h> #include <__type_traits/is_const.h> #include <__type_traits/is_trivially_relocatable.h> #include @@ -15,9 +16,71 @@ _LIBCPP_BEGIN_NAMESPACE_STD #if _LIBCPP_STD_VER >= 20 template - requires(is_trivially_relocatable_v<_Tp> && !is_const_v<_Tp>) _LIBCPP_EXPORTED_FROM_ABI _Tp* trivially_relocate(_Tp* __begin, _Tp* __end, _Tp* __new_location) noexcept { - return __builtin_trivially_relocate(__new_location, __begin, sizeof(_Tp) * (__end - __begin)); + static_assert(is_trivially_relocatable_v<_Tp> && !is_const_v<_Tp>); + (void) __builtin_trivially_relocate(__new_location, __begin, sizeof(_Tp) * (__end - __begin)); + return __new_location + (__end - __begin); +} + +template +constexpr +T* relocate(T* __begin, T* __end, T* __new_location) +{ + static_assert(is_trivially_relocatable_v || is_nothrow_move_constructible_v); +// When relocating to the same location, do nothing. + if (__begin == __new_location || __begin == __end) + return __new_location + (__end - __begin); + +// Then, if we are not evaluating at compile time and the type supports +// trivial relocation, delegate to `trivially_relocate`. + if ! consteval { + if constexpr (is_trivially_relocatable_v) + return std::trivially_relocate(__begin, __end, __new_location); + } + + if constexpr (is_move_constructible_v) { + // For nontrivial relocatable types or any time during constant + // evaluation, we must detect overlapping ranges and act accordingly, + // which can be done only if the type is movable. + if ! consteval { + // At run time, when there is no overlap, we can, using other Standard + // Library algorithms, do all moves at once followed by all destructions. + if (less{}(__end,__new_location) || less{}(__new_location + (__end-__begin), __begin)) { + T* result = uninitialized_move(__begin, __end, __new_location); + std::destroy(__begin,__end); + return result; + } + } + + if (less{}(__new_location,__begin) || less{}(__end,__new_location)) { + // Any move to a lower address in memory or any nonoverlapping move can be + // done by iterating forward through the range. + T* next = __begin; + T* dest = __new_location; + while (next != __end) { + ::new(dest) T(std::move(*next)); + next->~T(); + ++next; ++dest; + } + } + else { + // When moving to a higher address that overlaps, we must go backward through + // the range. + T* next = __end; + T* dest = __new_location + (__end-__begin); + while (next != __begin) { + --next; --dest; + ::new(dest) T(std::move(*next)); + next->~T(); + } + } + return __new_location + (__end - __begin); + } + +// The only way to reach this point is during constant evaluation where type `T` +// is trivially relocatable but not move constructible. Such cases are not supported +// so we mark this branch as unreachable. + unreachable(); } #endif diff --git a/libcxx/include/__memory/uninitialized_algorithms.h b/libcxx/include/__memory/uninitialized_algorithms.h index 64294cec9a7af..bdf28d54570da 100644 --- a/libcxx/include/__memory/uninitialized_algorithms.h +++ b/libcxx/include/__memory/uninitialized_algorithms.h @@ -657,205 +657,6 @@ __uninitialized_allocator_relocate(_Alloc& __alloc, _Tp* __first, _Tp* __last, _ } } -// Trivial relocation stuff -#if _LIBCPP_STD_VER >= 20 - -namespace { -// Implementation details --- using an anonymous namespace within this single TU prototype -template -concept nothrow_input_iterator = input_iterator<_Iter>; - -template -concept nothrow_forward_iterator = forward_iterator<_Iter>; - -// We need a concept to distinguish the "not" case at the end --- subsumption -// does not kick in, so could maybe be just a constexpr boolean variable -// template. - -template -concept relocatable_iterators - = std::same_as, std::iter_value_t<_OutIter>> - and std::is_lvalue_reference_v> - and std::is_lvalue_reference_v>; - - -template -concept no_fail_relocatable_iterators - = relocatable_iterators<_InIter, _OutIter> - and ( std::is_nothrow_move_constructible_v> - or std::is_trivially_relocatable_v> - ); -} // unnamed namespace - -// Forward looking for asserting preconditions --- tune feature macro as needed -#ifndef __cpp_contracts -# define contract_assert(...) assert(__VA_ARGS__) -#endif - - -template - requires no_fail_relocatable_iterators<_InIter, _OutIter> -auto unintialized_relocate(_InIter __first, _InIter __last, _OutIter __result) noexcept - -> _OutIter { - // Note that by design, none of the operations in this implementation are - // potentially-throwing --- hence there are no concerns about exception - // safety - - // One implementation bug deferred from proof of concept is that we assume - // that the object pointer type can be implicitly converted to the - // contiguous output iterator when creating the return value. I have yet __result - // confirm that this is mandated by the relevant concepts - // Using pointers to relocate whole range at once, and remove - // no-throw-iterator concerns from support for contiguous iterators. There - // remains corner-case concerns to clean up for throwing `operator*` and - // dereferencing past-the-end iterators for empty ranges. - auto * __begin = std::addressof(*__first); - auto * __end = __begin + (__last - __first); - auto * __new_location = std::addressof(*__result); - - // This check is redundant if we follow all the branches below, but makes - // the design intent clear. - if (__begin == __new_location) - return __result; - - // For trivially relocatable type, just delegate to the core language primitive - if constexpr (is_trivially_relocatable_v>) { - (void) trivially_relocate(__begin, __end, __new_location); - return __result + (__last - __first); - } - - // For non-trivial relocation, we must detect and support overlapping ranges - // ourselves. At this point we no longer need to worry about trivial - // relocatable types but are not move-constructible. - - if (__less<>{}(__end, __new_location) || __less<>{}(__new_location + (__end - __begin), __begin)) { - // No overlap - _OutIter __result0 = uninitialized_move(__first, __last, __result); - destroy(__begin, __end); - return __result0; - } - else if (__less<>{}(__begin, __new_location)) { - // move-and-destroy each member, back to front - - // Implementation note: we could just modify `__new_location` rather than - // create the local `dest`, but want to clearly show the algorithm - // without micro-optimizing. - auto * __dest = __new_location + (__end - __begin); - while (__dest != __new_location) { - __dest = std::__construct_at(--__dest, std::move(*--__end)); - std::__destroy_at(__end); - } - return __result + (__last - __first); - - } - else if (__begin == __new_location) { - // Note that this is the same check redundantly hoisted out of the - // if/elif/else flow, but is NOT redundant here - return __result; - } - else { - // move-and-destroy each member, front to back - while (__first != __last) { - (void) __construct_at(std::addressof(*__result), std::move(*__first)); - std::__destroy_at(std::addressof(*__first)); - ++__result; - ++__first; - } - return __result; - } - - __libcpp_unreachable(); -} - -template - requires no_fail_relocatable_iterators<_InIter, _OutIter> -auto unintialized_relocate(_InIter __first, _InIter __last, _OutIter __result) noexcept - -> _OutIter { - // There is no support for overlapping ranges unless both iterator types are - // contiguous iterators, which would be subsumed by the contiguous itetator - // overload. - - // Note that there are trivially relocatable types that are not no-throw - // move constructible and vice-versa, so we need to handle both cases. In - // doing so, we should pick a preferred implementation for types that are - // both trivially relocatable and no-throw move constructible. - if (__first == __last) - return __result; - - // Think carefully about iterator invalidation when destroying elements. - // The post-increment on `__first` seems important, in case element - // destruction invalidates an iterator, which would be the case for a - // pointer. - while (__first != __last) { - auto * __begin = std::addressof(*__first++); - auto * __new_location = std::addressof(*__result++); - - if constexpr(is_trivially_relocatable_v>) { - (void) trivially_relocate(__begin, __begin + 1, __new_location); - } - else { - (void) std::__construct_at(__new_location, std::move(*__begin)); - std::__destroy_at(__begin); - } - } - - return __result; -} - -template - requires relocatable_iterators<_InIter, _OutIter> - and (!no_fail_relocatable_iterators<_InIter, _OutIter>) - and std::is_move_constructible_v> -auto unintialized_relocate(_InIter __first, _InIter __last, _OutIter __result) // NOT declared noexcept - -> _OutIter { - // The move-constructor can throw, so relocation is not guaranteed to - // succeed. The opt for the vector-like strong exception safety guarantee - // for this operation: if move can throw, but the type is - // copy-constructible, then *copy* the range, so that we can safely unwind - // if an exception is thrown without leaving the initially relocated - // elements in an unknown state. Otherwise, we will *move* all of the - // elements, and leave those elements in an unspecified valid state. Only - // if the copy/move operation succeeds are the original elements destroyed. - if (__first == __last) - return __result; - - if constexpr(std::contiguous_iterator<_InIter> and std::contiguous_iterator<_OutIter>) { - // Note that once we complete overload resolution, all viable iterators - // referring to trivially relocatable types should have directed to an - // overload above. We might still have contiguous iterators to - // move-constructible types that may throw, and are not trivially - // relocatable, which means we may be able to assert preconditions that - // overlapping ranges are not supported. - // check for overlapping range with a contract_assert -#if defined(__clang__) - contract_assert(!__is_pointer_in_range(std::addressof(*__first), std::addressof(*__last), - std::addressof(*__result))); -#else - const size_t __count = __last - __first; - auto * __begin = std::addressof(*__first); - auto * __end = __begin + __count; // `__last` is often not a derefencerable iterator - auto * __new_location = std::addressof(*__result); - auto __less_ = std::less<>{}; - // Note that __end == __new_location is fine - contract_assert(__less_(__end, __new_location) || __less_(__new_location + __count, __begin)); -#endif - } - - // Remember original `__result` may be invalidated by this operation, so we need - // to capture the return value. - if constexpr(is_copy_constructible_v>) - __result = std::uninitialized_copy(__first, __last, __result); // cleans up new range if any copy throws - else - __result = std::uninitialized_move(__first, __last, __result); // cleans up new range if any move throws - - std::__destroy(__first, __last); // "Library UB" if destructor throws - return __result; - } - -// Need to add the ranges stuff here - -#endif - _LIBCPP_END_NAMESPACE_STD _LIBCPP_POP_MACROS From ed148184a5ae59a26d9457789ee70d8e48127c1b Mon Sep 17 00:00:00 2001 From: Marshall Clow Date: Fri, 27 Sep 2024 14:29:00 -0700 Subject: [PATCH 29/32] Add underscores to local variable names. NFC --- libcxx/include/__memory/relocate.h | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/libcxx/include/__memory/relocate.h b/libcxx/include/__memory/relocate.h index f98c1acb3a0fd..10d8e2198920e 100644 --- a/libcxx/include/__memory/relocate.h +++ b/libcxx/include/__memory/relocate.h @@ -46,32 +46,32 @@ T* relocate(T* __begin, T* __end, T* __new_location) // At run time, when there is no overlap, we can, using other Standard // Library algorithms, do all moves at once followed by all destructions. if (less{}(__end,__new_location) || less{}(__new_location + (__end-__begin), __begin)) { - T* result = uninitialized_move(__begin, __end, __new_location); + T* __result = uninitialized_move(__begin, __end, __new_location); std::destroy(__begin,__end); - return result; + return __result; } } if (less{}(__new_location,__begin) || less{}(__end,__new_location)) { // Any move to a lower address in memory or any nonoverlapping move can be // done by iterating forward through the range. - T* next = __begin; - T* dest = __new_location; - while (next != __end) { - ::new(dest) T(std::move(*next)); - next->~T(); - ++next; ++dest; + T* __next = __begin; + T* __dest = __new_location; + while (__next != __end) { + ::new(__dest) T(std::move(*__next)); + __next->~T(); + ++__next; ++__dest; } } else { // When moving to a higher address that overlaps, we must go backward through // the range. - T* next = __end; - T* dest = __new_location + (__end-__begin); - while (next != __begin) { - --next; --dest; - ::new(dest) T(std::move(*next)); - next->~T(); + T* __next = __end; + T* __dest = __new_location + (__end - __begin); + while (__next != __begin) { + --__next; --__dest; + ::new(__dest) T(std::move(*__next)); + __next->~T(); } } return __new_location + (__end - __begin); From a1c64ccacd4b0e1f636b8ba831c7349a7e5d6db7 Mon Sep 17 00:00:00 2001 From: Marshall Clow Date: Tue, 1 Oct 2024 17:03:52 -0700 Subject: [PATCH 30/32] Use 'relocate' instead of 'trivially_relocate' --- libcxx/include/__memory/relocate.h | 61 ---------------- .../__memory/uninitialized_algorithms.h | 69 ++++++++++++++++++- libcxx/include/vector | 20 +++--- 3 files changed, 77 insertions(+), 73 deletions(-) diff --git a/libcxx/include/__memory/relocate.h b/libcxx/include/__memory/relocate.h index 10d8e2198920e..856c0d79db3e7 100644 --- a/libcxx/include/__memory/relocate.h +++ b/libcxx/include/__memory/relocate.h @@ -22,67 +22,6 @@ _LIBCPP_EXPORTED_FROM_ABI _Tp* trivially_relocate(_Tp* __begin, _Tp* __end, _Tp* return __new_location + (__end - __begin); } -template -constexpr -T* relocate(T* __begin, T* __end, T* __new_location) -{ - static_assert(is_trivially_relocatable_v || is_nothrow_move_constructible_v); -// When relocating to the same location, do nothing. - if (__begin == __new_location || __begin == __end) - return __new_location + (__end - __begin); - -// Then, if we are not evaluating at compile time and the type supports -// trivial relocation, delegate to `trivially_relocate`. - if ! consteval { - if constexpr (is_trivially_relocatable_v) - return std::trivially_relocate(__begin, __end, __new_location); - } - - if constexpr (is_move_constructible_v) { - // For nontrivial relocatable types or any time during constant - // evaluation, we must detect overlapping ranges and act accordingly, - // which can be done only if the type is movable. - if ! consteval { - // At run time, when there is no overlap, we can, using other Standard - // Library algorithms, do all moves at once followed by all destructions. - if (less{}(__end,__new_location) || less{}(__new_location + (__end-__begin), __begin)) { - T* __result = uninitialized_move(__begin, __end, __new_location); - std::destroy(__begin,__end); - return __result; - } - } - - if (less{}(__new_location,__begin) || less{}(__end,__new_location)) { - // Any move to a lower address in memory or any nonoverlapping move can be - // done by iterating forward through the range. - T* __next = __begin; - T* __dest = __new_location; - while (__next != __end) { - ::new(__dest) T(std::move(*__next)); - __next->~T(); - ++__next; ++__dest; - } - } - else { - // When moving to a higher address that overlaps, we must go backward through - // the range. - T* __next = __end; - T* __dest = __new_location + (__end - __begin); - while (__next != __begin) { - --__next; --__dest; - ::new(__dest) T(std::move(*__next)); - __next->~T(); - } - } - return __new_location + (__end - __begin); - } - -// The only way to reach this point is during constant evaluation where type `T` -// is trivially relocatable but not move constructible. Such cases are not supported -// so we mark this branch as unreachable. - unreachable(); -} - #endif _LIBCPP_END_NAMESPACE_STD diff --git a/libcxx/include/__memory/uninitialized_algorithms.h b/libcxx/include/__memory/uninitialized_algorithms.h index bdf28d54570da..afd6f6a8fdda6 100644 --- a/libcxx/include/__memory/uninitialized_algorithms.h +++ b/libcxx/include/__memory/uninitialized_algorithms.h @@ -539,6 +539,71 @@ class _AllocatorDestroyRangeReverse { _Iter& __last_; }; +#if _LIBCPP_STD_VER >= 20 + +template +constexpr +_Tp* relocate(_Tp* __begin, _Tp* __end, _Tp* __new_location) +{ + static_assert(is_trivially_relocatable_v<_Tp> || is_nothrow_move_constructible_v<_Tp>); +// When relocating to the same location, do nothing. + if (__begin == __new_location || __begin == __end) + return __new_location + (__end - __begin); + +// Then, if we are not evaluating at compile time and the type supports +// trivial relocation, delegate to `trivially_relocate`. + if ! consteval { + if constexpr (is_trivially_relocatable_v<_Tp>) + return std::trivially_relocate(__begin, __end, __new_location); + } + + if constexpr (is_move_constructible_v<_Tp>) { + // For nontrivial relocatable types or any time during constant + // evaluation, we must detect overlapping ranges and act accordingly, + // which can be done only if the type is movable. + if ! consteval { + // At run time, when there is no overlap, we can, using other Standard + // Library algorithms, do all moves at once followed by all destructions. + if (less{}(__end,__new_location) || less{}(__new_location + (__end-__begin), __begin)) { + _Tp* __result = uninitialized_move(__begin, __end, __new_location); + std::destroy(__begin,__end); + return __result; + } + } + + if (less{}(__new_location,__begin) || less{}(__end,__new_location)) { + // Any move to a lower address in memory or any nonoverlapping move can be + // done by iterating forward through the range. + _Tp* __next = __begin; + _Tp* __dest = __new_location; + while (__next != __end) { + ::new(__dest) _Tp(std::move(*__next)); + __next->~_Tp(); + ++__next; ++__dest; + } + } + else { + // When moving to a higher address that overlaps, we must go backward through + // the range. + _Tp* __next = __end; + _Tp* __dest = __new_location + (__end - __begin); + while (__next != __begin) { + --__next; --__dest; + ::new(__dest) _Tp(std::move(*__next)); + __next->~_Tp(); + } + } + return __new_location + (__end - __begin); + } + +// The only way to reach this point is during constant evaluation where type `T` +// is trivially relocatable but not move constructible. Such cases are not supported +// so we mark this branch as unreachable. + unreachable(); +} + +#endif + // Copy-construct [__first1, __last1) in [__first2, __first2 + N), where N is distance(__first1, __last1). // // The caller has to ensure that __first2 can hold at least N uninitialized elements. If an exception is thrown the @@ -627,8 +692,8 @@ __uninitialized_allocator_relocate(_Alloc& __alloc, _Tp* __first, _Tp* __last, _ #if _LIBCPP_STD_VER >= 20 if (!__libcpp_is_constant_evaluated()) { - if constexpr (is_trivially_relocatable_v<_Tp>) { - (void) trivially_relocate(__first, __last, const_cast<__remove_const_t<_Tp>*>(__result)); + if constexpr (is_trivially_relocatable_v<_Tp> || is_nothrow_move_constructible_v<_Tp>) { + (void) relocate(__first, __last, const_cast<__remove_const_t<_Tp>*>(__result)); return ; } } diff --git a/libcxx/include/vector b/libcxx/include/vector index 27d9ccc451d2b..4e756f7e0cbe4 100644 --- a/libcxx/include/vector +++ b/libcxx/include/vector @@ -1577,12 +1577,12 @@ vector<_Tp, _Allocator>::erase(const_iterator __position) { difference_type __ps = __position - cbegin(); pointer __p = this->__begin_ + __ps; #if _LIBCPP_STD_VER >= 20 - if constexpr (is_trivially_relocatable_v<_Tp>) { + if constexpr (is_trivially_relocatable_v || is_nothrow_move_constructible_v) { size_type __old_size = size(); // destroy the element at __p __alloc_traits::destroy(__alloc(), std::__to_address(__p)); // move the rest down - (void) trivially_relocate(__p + 1, this->__end_, __p); + (void) relocate(__p + 1, this->__end_, __p); // update the end this->__end_--; __annotate_shrink(__old_size); @@ -1600,7 +1600,7 @@ vector<_Tp, _Allocator>::erase(const_iterator __first, const_iterator __last) { pointer __p = this->__begin_ + (__first - begin()); if (__first != __last) { #if _LIBCPP_STD_VER >= 20 - if constexpr (is_trivially_relocatable_v<_Tp>) { + if constexpr (is_trivially_relocatable_v || is_nothrow_move_constructible_v) { size_type __old_size = size(); size_type __count = __last - __first; pointer __p0 = __p + __count; @@ -1608,7 +1608,7 @@ vector<_Tp, _Allocator>::erase(const_iterator __first, const_iterator __last) { for (pointer __to_destroy = __p; __to_destroy < __p0; ++__to_destroy) __alloc_traits::destroy(__alloc(), std::__to_address(__to_destroy)); // move the rest down - (void) trivially_relocate(std::__to_address(__p0), std::__to_address(this->__end_), std::__to_address(__p)); + (void) relocate(std::__to_address(__p0), std::__to_address(this->__end_), std::__to_address(__p)); // update the end this->__end_ -= __count; __annotate_shrink(__old_size); @@ -1644,10 +1644,10 @@ vector<_Tp, _Allocator>::insert(const_iterator __position, const_reference __x) __construct_one_at_end(__x); } else { #if _LIBCPP_STD_VER >= 20 - if constexpr (is_trivially_relocatable_v<_Tp>) { + if constexpr (is_trivially_relocatable_v || is_nothrow_move_constructible_v) { // Make space by trivially relocating everything _ConstructTransaction __tx(*this, 1); - (void) trivially_relocate(std::__to_address(__p), std::__to_address(this->__end_), __p + 1); + (void) relocate(std::__to_address(__p), std::__to_address(this->__end_), __p + 1); // construct the new element (not assign!) const_pointer __xr = pointer_traits::pointer_to(__x); if (std::__is_pointer_in_range(std::__to_address(__p), std::__to_address(__end_), std::addressof(__x))) @@ -1684,10 +1684,10 @@ vector<_Tp, _Allocator>::insert(const_iterator __position, value_type&& __x) { __construct_one_at_end(std::move(__x)); } else { #if _LIBCPP_STD_VER >= 20 - if constexpr (is_trivially_relocatable_v<_Tp>) { + if constexpr (is_trivially_relocatable_v || is_nothrow_move_constructible_v) { // Make space by trivially relocating everything _ConstructTransaction __tx(*this, 1); - (void) trivially_relocate(std::__to_address(__p), std::__to_address(this->__end_), __p + 1); + (void) relocate(std::__to_address(__p), std::__to_address(this->__end_), __p + 1); // construct the new element (not assign!) __alloc_traits::construct(this->__alloc(), std::__to_address(__p), std::forward(__x)); ++__tx.__pos_; @@ -1719,10 +1719,10 @@ vector<_Tp, _Allocator>::emplace(const_iterator __position, _Args&&... __args) { __construct_one_at_end(std::forward<_Args>(__args)...); } else { #if _LIBCPP_STD_VER >= 20 - if constexpr (is_trivially_relocatable_v<_Tp>) { + if constexpr (is_trivially_relocatable_v || is_nothrow_move_constructible_v) { // Make space by trivially relocating everything _ConstructTransaction __tx(*this, 1); - (void) trivially_relocate(std::__to_address(__p), std::__to_address(this->__end_), __p + 1); + (void) relocate(std::__to_address(__p), std::__to_address(this->__end_), __p + 1); // construct the new element __alloc_traits::construct(this->__alloc(), std::__to_address(__p), std::forward<_Args>(__args)...); ++__tx.__pos_; From 33f425367b62de0897cbf6bef1f9f1de5aa9f2e4 Mon Sep 17 00:00:00 2001 From: Marshall Clow Date: Tue, 1 Oct 2024 17:07:46 -0700 Subject: [PATCH 31/32] fix copy-pasta in --- libcxx/include/vector | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/libcxx/include/vector b/libcxx/include/vector index 4e756f7e0cbe4..002793484e356 100644 --- a/libcxx/include/vector +++ b/libcxx/include/vector @@ -1577,7 +1577,7 @@ vector<_Tp, _Allocator>::erase(const_iterator __position) { difference_type __ps = __position - cbegin(); pointer __p = this->__begin_ + __ps; #if _LIBCPP_STD_VER >= 20 - if constexpr (is_trivially_relocatable_v || is_nothrow_move_constructible_v) { + if constexpr (is_trivially_relocatable_v<_Tp> || is_nothrow_move_constructible_v<_Tp>) { size_type __old_size = size(); // destroy the element at __p __alloc_traits::destroy(__alloc(), std::__to_address(__p)); @@ -1600,7 +1600,7 @@ vector<_Tp, _Allocator>::erase(const_iterator __first, const_iterator __last) { pointer __p = this->__begin_ + (__first - begin()); if (__first != __last) { #if _LIBCPP_STD_VER >= 20 - if constexpr (is_trivially_relocatable_v || is_nothrow_move_constructible_v) { + if constexpr (is_trivially_relocatable_v<_Tp> || is_nothrow_move_constructible_v<_Tp>) { size_type __old_size = size(); size_type __count = __last - __first; pointer __p0 = __p + __count; @@ -1644,7 +1644,7 @@ vector<_Tp, _Allocator>::insert(const_iterator __position, const_reference __x) __construct_one_at_end(__x); } else { #if _LIBCPP_STD_VER >= 20 - if constexpr (is_trivially_relocatable_v || is_nothrow_move_constructible_v) { + if constexpr (is_trivially_relocatable_v<_Tp> || is_nothrow_move_constructible_v<_Tp>) { // Make space by trivially relocating everything _ConstructTransaction __tx(*this, 1); (void) relocate(std::__to_address(__p), std::__to_address(this->__end_), __p + 1); @@ -1684,7 +1684,7 @@ vector<_Tp, _Allocator>::insert(const_iterator __position, value_type&& __x) { __construct_one_at_end(std::move(__x)); } else { #if _LIBCPP_STD_VER >= 20 - if constexpr (is_trivially_relocatable_v || is_nothrow_move_constructible_v) { + if constexpr (is_trivially_relocatable_v<_Tp> || is_nothrow_move_constructible_v<_Tp>) { // Make space by trivially relocating everything _ConstructTransaction __tx(*this, 1); (void) relocate(std::__to_address(__p), std::__to_address(this->__end_), __p + 1); @@ -1719,7 +1719,7 @@ vector<_Tp, _Allocator>::emplace(const_iterator __position, _Args&&... __args) { __construct_one_at_end(std::forward<_Args>(__args)...); } else { #if _LIBCPP_STD_VER >= 20 - if constexpr (is_trivially_relocatable_v || is_nothrow_move_constructible_v) { + if constexpr (is_trivially_relocatable_v<_Tp> || is_nothrow_move_constructible_v<_Tp>) { // Make space by trivially relocating everything _ConstructTransaction __tx(*this, 1); (void) relocate(std::__to_address(__p), std::__to_address(this->__end_), __p + 1); From 1eb88fc46f0ac1f933c2aba15c3041aae557f9d9 Mon Sep 17 00:00:00 2001 From: Marshall Clow Date: Mon, 7 Oct 2024 12:36:28 -0700 Subject: [PATCH 32/32] Optimize swap_ranges for contigious, replaceable ranges --- libcxx/include/__algorithm/swap_ranges.h | 42 ++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/libcxx/include/__algorithm/swap_ranges.h b/libcxx/include/__algorithm/swap_ranges.h index 54b453b72360e..e3f62e371da9e 100644 --- a/libcxx/include/__algorithm/swap_ranges.h +++ b/libcxx/include/__algorithm/swap_ranges.h @@ -40,6 +40,48 @@ __swap_ranges(_ForwardIterator1 __first1, _Sentinel1 __last1, _ForwardIterator2 template _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 pair<_ForwardIterator1, _ForwardIterator2> __swap_ranges(_ForwardIterator1 __first1, _Sentinel1 __last1, _ForwardIterator2 __first2) { +#if _LIBCPP_STD_VER >= 26 + if ! consteval { + using _V1Type = iterator_traits<_ForwardIterator1>::value_type; + using _V2Type = iterator_traits<_ForwardIterator2>::value_type; + if constexpr(__libcpp_is_contiguous_iterator<_ForwardIterator1>::value && + __libcpp_is_contiguous_iterator<_ForwardIterator2>::value && + is_same_v<_V1Type, _V2Type> && is_replaceable_v<_V1Type>) { + size_t __distance = distance(__first1, __last1); + if (__distance == 1) { + using std::swap; + swap(*__first1, *__first2); + } + else if (__distance > 0) { + size_t __numBytes = __distance * sizeof(_V1Type); + byte *__aptr = reinterpret_cast (addressof(*__first1)); + byte *__bptr = reinterpret_cast (addressof(*__first2)); + + size_t __size = __numBytes < 512 ? __numBytes : 512; + size_t __chunk = __numBytes / __size; + size_t __rem = __numBytes % __size; + + char __buffer[__size]; + if (__chunk > 0) { + for (std::size_t __n = 0; __n < __chunk; __n++, __aptr += __size, __bptr += __size) { + __builtin_memcpy(__buffer, __aptr, __size); + __builtin_memcpy(__aptr, __bptr, __size); + __builtin_memcpy(__bptr, __buffer, __size); + } + } + + if (__rem > 0) { + __builtin_memcpy(__buffer, __aptr, __rem); + __builtin_memcpy(__aptr, __bptr, __rem); + __builtin_memcpy(__bptr, __buffer, __rem); + } + } + return pair<_ForwardIterator1, _ForwardIterator2>(__first1 + __distance, __first2 + __distance); + } + } +#endif + +// else not replaceable, not contiguous, or constexpr while (__first1 != __last1) { _IterOps<_AlgPolicy>::iter_swap(__first1, __first2); ++__first1;