llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-clang Author: Peng Xie (love1angel) <details> <summary>Changes</summary> --- Patch is 46.15 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/185830.diff 14 Files Affected: - (modified) clang/include/clang/Basic/Builtins.td (+6) - (modified) clang/include/clang/Basic/DiagnosticASTKinds.td (+5) - (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+10) - (modified) clang/lib/AST/DeclCXX.cpp (+24-5) - (modified) clang/lib/AST/ExprConstant.cpp (+114-5) - (modified) clang/lib/CodeGen/CGBuiltin.cpp (+5) - (modified) clang/lib/Frontend/InitPreprocessor.cpp (+4) - (modified) clang/lib/Sema/SemaChecking.cpp (+53) - (modified) clang/lib/Sema/SemaDeclCXX.cpp (+122-2) - (modified) clang/test/CXX/special/class.ctor/p5-0x.cpp (+9-7) - (modified) clang/test/CXX/special/class.ctor/p6-0x.cpp (+16-15) - (modified) clang/test/CXX/special/class.dtor/p5-0x.cpp (+17-16) - (added) clang/test/SemaCXX/cxx26-start-lifetime.cpp (+115) - (added) clang/test/SemaCXX/cxx26-trivial-union.cpp (+247) ``````````diff diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td index dd5bd689c08d2..4a8286a3db76c 100644 --- a/clang/include/clang/Basic/Builtins.td +++ b/clang/include/clang/Basic/Builtins.td @@ -1000,6 +1000,12 @@ def IsWithinLifetime : LangBuiltin<"CXX_LANG"> { let Prototype = "bool(void*)"; } +def StartLifetime : LangBuiltin<"CXX_LANG"> { + let Spellings = ["__builtin_start_lifetime"]; + let Attributes = [NoThrow, CustomTypeChecking, Consteval]; + let Prototype = "void(void*)"; +} + def GetVtablePointer : LangBuiltin<"CXX_LANG"> { let Spellings = ["__builtin_get_vtable_pointer"]; let Attributes = [CustomTypeChecking, NoThrow, Const]; diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td index bde418695f647..37261fc3d6407 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -437,6 +437,11 @@ def err_invalid_is_within_lifetime : Note< "a pointer to an object whose lifetime has not yet begun}1" >; +def err_invalid_start_lifetime : Note< + "'%0' cannot be called with " + "%select{a null pointer|a one-past-the-end pointer}1" +>; + // inline asm related. let CategoryName = "Inline Assembly Issue" in { def err_asm_invalid_escape : Error< diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 0c25eb2443d5e..b2f19513b6026 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -6406,6 +6406,12 @@ def note_enforce_read_only_placement : Note<"type was declared read-only here">; def note_deleted_dtor_no_operator_delete : Note< "virtual destructor requires an unambiguous, accessible 'operator delete'">; +def note_deleted_dtor_default_ctor : Note< + "destructor of union %0 is implicitly deleted because " + "%select{it has no default constructor|" + "its default constructor is a deleted function|" + "overload resolution to default-initialize it is ambiguous|" + "its default constructor is not trivial}1">; def note_deleted_special_member_class_subobject : Note< "%select{default constructor of|copy constructor of|move constructor of|" "copy assignment operator of|move assignment operator of|destructor of|" @@ -13237,6 +13243,10 @@ def err_builtin_is_within_lifetime_invalid_arg : Error< "%select{non-|function }0pointer argument to '__builtin_is_within_lifetime' " "is not allowed">; +def err_builtin_start_lifetime_invalid_arg : Error< + "'__builtin_start_lifetime' argument must be a pointer to a complete " + "implicit-lifetime aggregate type, but got %0">; + // A multi-component builtin type diagnostic. The first component broadly // selects a scalar or container type (scalar, vector or matrix). The second // component selects integer types and the third component selects diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp index 083c53e28cb91..6cb90e701fff4 100644 --- a/clang/lib/AST/DeclCXX.cpp +++ b/clang/lib/AST/DeclCXX.cpp @@ -1180,7 +1180,11 @@ void CXXRecordDecl::addedMember(Decl *D) { // C++11 [class]p5: // A default constructor is trivial if [...] no non-static data member // of its class has a brace-or-equal-initializer. - data().HasTrivialSpecialMembers &= ~SMF_DefaultConstructor; + // P3074R7 [class.default.ctor]p3: + // In C++26, a union's default constructor is always trivial, + // even with brace-or-equal-initializers. + if (!(isUnion() && getASTContext().getLangOpts().CPlusPlus26)) + data().HasTrivialSpecialMembers &= ~SMF_DefaultConstructor; // C++11 [dcl.init.aggr]p1: // An aggregate is a [...] class with [...] no @@ -1239,7 +1243,11 @@ void CXXRecordDecl::addedMember(Decl *D) { if (FieldRec->hasNonTrivialMoveAssignment()) data().DefaultedMoveAssignmentIsDeleted = true; if (FieldRec->hasNonTrivialDestructor()) { - data().DefaultedDestructorIsDeleted = true; + // P3074R7: In C++26, the destructor of a union is not deleted + // merely because a variant member has a non-trivial destructor. + // Deletion is determined later by Sema based on the new rules. + if (!Context.getLangOpts().CPlusPlus26) + data().DefaultedDestructorIsDeleted = true; // C++20 [dcl.constexpr]p5: // The definition of a constexpr destructor whose function-body is // not = delete shall additionally satisfy... @@ -1267,7 +1275,12 @@ void CXXRecordDecl::addedMember(Decl *D) { // -- for all the non-static data members of its class that are of // class type (or array thereof), each such class has a trivial // default constructor. - if (!FieldRec->hasTrivialDefaultConstructor()) + // P3074R7 [class.default.ctor]p3: + // In C++26, "either X is a union or" for all non-variant + // non-static data members [...] each such class has a trivial + // default constructor. + if (!FieldRec->hasTrivialDefaultConstructor() && + !(isUnion() && Context.getLangOpts().CPlusPlus26)) data().HasTrivialSpecialMembers &= ~SMF_DefaultConstructor; // C++0x [class.copy]p13: @@ -1305,9 +1318,15 @@ void CXXRecordDecl::addedMember(Decl *D) { if (!FieldRec->hasTrivialMoveAssignment()) data().HasTrivialSpecialMembers &= ~SMF_MoveAssignment; - if (!FieldRec->hasTrivialDestructor()) + // P3074R7 [class.dtor]p8: + // In C++26, "either X is a union or" for all non-variant + // non-static data members [...] each such class has a trivial + // destructor. + if (!FieldRec->hasTrivialDestructor() && + !(isUnion() && Context.getLangOpts().CPlusPlus26)) data().HasTrivialSpecialMembers &= ~SMF_Destructor; - if (!FieldRec->hasTrivialDestructorForCall()) + if (!FieldRec->hasTrivialDestructorForCall() && + !(isUnion() && Context.getLangOpts().CPlusPlus26)) data().HasTrivialSpecialMembersForCall &= ~SMF_Destructor; if (!FieldRec->hasIrrelevantDestructor()) data().HasIrrelevantDestructor = false; diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 429fef0a1afa8..f3f05e37ef49b 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -2452,17 +2452,30 @@ static bool CheckEvaluationResult(CheckEvaluationResultKind CERK, // expression. if (Value.isArray()) { QualType EltTy = Type->castAsArrayTypeUnsafe()->getElementType(); + + // P3726R1 [expr.const]p2: An inactive union subobject includes + // an element E of an array member of a union where E is not within + // its lifetime. Skip such elements during constituent values checking. + bool IsUnionArrayMember = + Info.getLangOpts().CPlusPlus26 && SubobjectDecl && + SubobjectDecl->getDeclContext()->isRecord() && + cast<RecordDecl>(SubobjectDecl->getDeclContext())->isUnion(); + for (unsigned I = 0, N = Value.getArrayInitializedElts(); I != N; ++I) { - if (!CheckEvaluationResult(CERK, Info, DiagLoc, EltTy, - Value.getArrayInitializedElt(I), Kind, + const APValue &Elt = Value.getArrayInitializedElt(I); + if (IsUnionArrayMember && !Elt.hasValue()) + continue; + if (!CheckEvaluationResult(CERK, Info, DiagLoc, EltTy, Elt, Kind, SubobjectDecl, CheckedTemps)) return false; } if (!Value.hasArrayFiller()) return true; - return CheckEvaluationResult(CERK, Info, DiagLoc, EltTy, - Value.getArrayFiller(), Kind, SubobjectDecl, - CheckedTemps); + const APValue &Filler = Value.getArrayFiller(); + if (IsUnionArrayMember && !Filler.hasValue()) + return true; + return CheckEvaluationResult(CERK, Info, DiagLoc, EltTy, Filler, Kind, + SubobjectDecl, CheckedTemps); } if (Value.isUnion() && Value.getUnionField()) { return CheckEvaluationResult( @@ -6795,6 +6808,45 @@ struct StartLifetimeOfUnionMemberHandler { const AccessKinds StartLifetimeOfUnionMemberHandler::AccessKind; +namespace { +/// P3726R1: Handler for __builtin_start_lifetime. +/// Starts the lifetime of the target object without initializing subobjects. +struct BuiltinStartLifetimeHandler { + EvalInfo &Info; + static const AccessKinds AccessKind = AK_Construct; + typedef bool result_type; + bool failed() { return false; } + bool found(APValue &Subobj, QualType SubobjType) { + // P3726R1 [obj.lifetime]: + // If the object referenced by r is already within its lifetime, + // no effects. + if (Subobj.hasValue()) + return true; + + // Begin the lifetime of the object without initializing subobjects. + if (auto *RD = SubobjType->getAsCXXRecordDecl()) { + if (RD->isUnion()) { + Subobj = APValue((const FieldDecl *)nullptr); + } else { + Subobj = APValue(APValue::UninitStruct(), RD->getNumBases(), + std::distance(RD->field_begin(), RD->field_end())); + } + } else if (auto *AT = dyn_cast_or_null<ConstantArrayType>( + SubobjType->getAsArrayTypeUnsafe())) { + Subobj = APValue(APValue::UninitArray(), 0, AT->getZExtSize()); + // Leave array filler absent — no element lifetimes started. + } else { + Subobj = APValue::IndeterminateValue(); + } + return true; + } + bool found(APSInt &, QualType) { return true; } + bool found(APFloat &, QualType) { return true; } +}; +} // end anonymous namespace + +const AccessKinds BuiltinStartLifetimeHandler::AccessKind; + /// Handle a builtin simple-assignment or a call to a trivial assignment /// operator whose left-hand side might involve a union member access. If it /// does, implicitly start the lifetime of any accessed union elements per @@ -20446,6 +20498,8 @@ static bool EvaluateAtomic(const Expr *E, const LValue *This, APValue &Result, // comma operator //===----------------------------------------------------------------------===// +static bool EvaluateBuiltinStartLifetime(EvalInfo &Info, const CallExpr *E); + namespace { class VoidExprEvaluator : public ExprEvaluatorBase<VoidExprEvaluator> { @@ -20479,6 +20533,9 @@ class VoidExprEvaluator case Builtin::BI__builtin_operator_delete: return HandleOperatorDeleteCall(Info, E); + case Builtin::BI__builtin_start_lifetime: + return EvaluateBuiltinStartLifetime(Info, E); + default: return false; } @@ -22186,3 +22243,55 @@ std::optional<bool> EvaluateBuiltinIsWithinLifetime(IntExprEvaluator &IEE, return findSubobject(Info, E, CO, Val.getLValueDesignator(), handler); } } // namespace + +/// P3726R1: Evaluate __builtin_start_lifetime(ptr). +/// Starts the lifetime of the object pointed to by ptr without initialization. +/// If the object is a union member, it becomes the active member. +static bool EvaluateBuiltinStartLifetime(EvalInfo &Info, const CallExpr *E) { + if (!Info.InConstantContext) + return false; + + assert(E->getBuiltinCallee() == Builtin::BI__builtin_start_lifetime); + const Expr *Arg = E->getArg(0); + if (Arg->isValueDependent()) + return false; + + LValue Val; + if (!EvaluatePointer(Arg, Val, Info)) + return false; + + auto Error = [&](int Diag) { + bool CalledFromStd = false; + const auto *Callee = Info.CurrentCall->getCallee(); + if (Callee && Callee->isInStdNamespace()) { + const IdentifierInfo *Identifier = Callee->getIdentifier(); + CalledFromStd = Identifier && Identifier->isStr("start_lifetime"); + } + Info.FFDiag(CalledFromStd ? Info.CurrentCall->getCallRange().getBegin() + : E->getExprLoc(), + diag::err_invalid_start_lifetime) + << (CalledFromStd ? "std::start_lifetime" : "__builtin_start_lifetime") + << Diag; + return false; + }; + + if (Val.isNullPointer() || Val.getLValueBase().isNull()) + return Error(0); + + if (Val.getLValueDesignator().isOnePastTheEnd()) + return Error(1); + + QualType T = Val.getLValueBase().getType(); + + // Find the complete object. + CompleteObject CO = + findCompleteObject(Info, E, AccessKinds::AK_Construct, Val, T); + if (!CO) + return false; + + // Navigate to the target subobject. Use AK_Construct so that + // findSubobject will activate inactive union members along the path. + // The handler starts the lifetime without initializing subobjects. + BuiltinStartLifetimeHandler Handler{Info}; + return findSubobject(Info, E, CO, Val.getLValueDesignator(), Handler); +} diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp index 6fb43d5cb0fbf..041079f0d3cf0 100644 --- a/clang/lib/CodeGen/CGBuiltin.cpp +++ b/clang/lib/CodeGen/CGBuiltin.cpp @@ -5642,6 +5642,11 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID, E->getCallee()->getType()->castAs<FunctionProtoType>(), E, true); return RValue::get(nullptr); + case Builtin::BI__builtin_start_lifetime: + // P3726R1: No-op at runtime. Lifetime of implicit-lifetime aggregates + // begins automatically with storage acquisition. + return RValue::get(nullptr); + case Builtin::BI__builtin_is_aligned: return EmitBuiltinIsAligned(E); case Builtin::BI__builtin_align_up: diff --git a/clang/lib/Frontend/InitPreprocessor.cpp b/clang/lib/Frontend/InitPreprocessor.cpp index 1ccd74314f373..d698769a8a89f 100644 --- a/clang/lib/Frontend/InitPreprocessor.cpp +++ b/clang/lib/Frontend/InitPreprocessor.cpp @@ -746,6 +746,10 @@ static void InitializeCPlusPlusFeatureTestMacros(const LangOptions &LangOpts, Builder.defineMacro("__cpp_variadic_friend", "202403L"); Builder.defineMacro("__cpp_trivial_relocatability", "202502L"); + // C++26 features. + if (LangOpts.CPlusPlus26) + Builder.defineMacro("__cpp_trivial_union", "202507L"); + if (LangOpts.Char8) Builder.defineMacro("__cpp_char8_t", "202207L"); Builder.defineMacro("__cpp_impl_destroying_delete", "201806L"); diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index 29add9d092e6b..ff63068279c31 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -2021,6 +2021,57 @@ static ExprResult BuiltinIsWithinLifetime(Sema &S, CallExpr *TheCall) { return TheCall; } +static ExprResult BuiltinStartLifetime(Sema &S, CallExpr *TheCall) { + if (S.checkArgCount(TheCall, 1)) + return ExprError(); + + ExprResult Arg = S.DefaultFunctionArrayLvalueConversion(TheCall->getArg(0)); + if (Arg.isInvalid()) + return ExprError(); + QualType ParamTy = Arg.get()->getType(); + TheCall->setArg(0, Arg.get()); + TheCall->setType(S.Context.VoidTy); + + const auto *PT = ParamTy->getAs<PointerType>(); + if (!PT) { + S.Diag(TheCall->getArg(0)->getExprLoc(), + diag::err_builtin_start_lifetime_invalid_arg) + << ParamTy; + return ExprError(); + } + + QualType PointeeTy = PT->getPointeeType(); + + // Mandates: T is a complete type + if (S.RequireCompleteType(TheCall->getArg(0)->getExprLoc(), PointeeTy, + diag::err_incomplete_type)) + return ExprError(); + + // Mandates: T is an implicit-lifetime aggregate type + // Check aggregate first + if (!PointeeTy->isAggregateType()) { + S.Diag(TheCall->getArg(0)->getExprLoc(), + diag::err_builtin_start_lifetime_invalid_arg) + << PointeeTy; + return ExprError(); + } + + // Check implicit-lifetime: for aggregates, destructor must not be + // user-provided + if (const auto *RD = PointeeTy->getAsCXXRecordDecl()) { + if (const auto *Dtor = RD->getDestructor()) { + if (Dtor->isUserProvided()) { + S.Diag(TheCall->getArg(0)->getExprLoc(), + diag::err_builtin_start_lifetime_invalid_arg) + << PointeeTy; + return ExprError(); + } + } + } + + return TheCall; +} + static ExprResult BuiltinTriviallyRelocate(Sema &S, CallExpr *TheCall) { if (S.checkArgCount(TheCall, 3)) return ExprError(); @@ -3071,6 +3122,8 @@ 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_start_lifetime: + return BuiltinStartLifetime(*this, TheCall); case Builtin::BI__builtin_trivially_relocate: return BuiltinTriviallyRelocate(*this, TheCall); diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 2ae6e5de0e3ee..86be35984f418 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -9615,6 +9615,12 @@ bool SpecialMemberDeletionInfo::shouldDeleteForSubobjectCall( if (SMOR.getKind() == Sema::SpecialMemberOverloadResult::NoMemberOrDeleted) { if (CSM == CXXSpecialMemberKind::DefaultConstructor && Field && Field->getParent()->isUnion()) { + // P3074R7: In C++26, a union's defaulted default constructor is never + // deleted due to a variant member with a non-trivial default + // constructor. The old [class.default.ctor]p2 union-specific bullets + // are removed. + if (S.getLangOpts().CPlusPlus26) + return false; // [class.default.ctor]p2: // A defaulted default constructor for class X is defined as deleted if // - X is a union that has a variant member with a non-trivial default @@ -9637,6 +9643,11 @@ bool SpecialMemberDeletionInfo::shouldDeleteForSubobjectCall( // destructor is never actually called, but is semantically checked as // if it were. if (CSM == CXXSpecialMemberKind::DefaultConstructor) { + // P3074R7: In C++26, a union's defaulted default constructor is never + // deleted due to a variant member with a non-trivial default + // constructor. + if (S.getLangOpts().CPlusPlus26) + return false; // [class.default.ctor]p2: // A defaulted default constructor for class X is defined as deleted if // - X is a union that has a variant member with a non-trivial default @@ -9645,6 +9656,13 @@ bool SpecialMemberDeletionInfo::shouldDeleteForSubobjectCall( const auto *RD = cast<CXXRecordDecl>(Field->getParent()); if (!RD->hasInClassInitializer()) DiagKind = NonTrivialDecl; + } else if (CSM == CXXSpecialMemberKind::Destructor && + S.getLangOpts().CPlusPlus26) { + // P3074R7 [class.dtor]p7: In C++26, a union's destructor is not + // deleted merely because a variant member has a non-trivial destructor. + // Deletion is determined by the new union-specific rules in + // ShouldDeleteSpecialMember. + return false; } else { DiagKind = NonTrivialDecl; } @@ -9806,6 +9824,12 @@ bool SpecialMemberDeletionInfo::shouldDeleteForField(FieldDecl *FD) { if (inUnion() && shouldDeleteForVariantPtrAuthMember(FD)) return true; + // P3074R7: In C++26, a union's defaulted default constructor is trivially + // defined and never deleted due to variant member properties. + if (inUnion() && S.getLangOpts().CPlusPlus26 && + CSM == CXXSpecialMemberKind::DefaultConstructor) + return false; + if (CSM == CXXSpecialMemberKind::DefaultConstructor) { // For a default constructor, all references must be initialized in-class // and, if a union, it must have a non-const member. @@ -9884,7 +9908,8 @@ bool SpecialMemberDeletionInfo::shouldDeleteForField(FieldDecl *FD) { // At least one member in each anonymous union must be non-const if (CSM == CXXSpecialMemberKind::DefaultConstructor && - AllVariantFieldsAreConst && !FieldRecord->field_empty()) { + AllVariantFieldsAreConst && !FieldRecord->field_empty() && + !S.getLangOpts().CPlusPlus26) { if (Diagnose) S.Diag(FieldRecord->getLocation(), diag::note_deleted_default_ctor_all_const) @@ -9914,6 +9939,8 @@ bool SpecialMemberDeletionInfo::shouldDeleteForAllConstMembers() { // default constructor. Don't do that. ... [truncated] `````````` </details> https://github.com/llvm/llvm-project/pull/185830 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
