https://github.com/love1angel created https://github.com/llvm/llvm-project/pull/185830
None >From 8d74ffbc9f05f905d4987855c8756b8b916c712e Mon Sep 17 00:00:00 2001 From: Peng Xie <[email protected]> Date: Wed, 11 Mar 2026 14:45:40 +0800 Subject: [PATCH 1/2] [Clang] Implement P3074R7: trivial unions (C++26) In C++26, union default constructors and destructors are trivial by default, regardless of variant member triviality. Changes: - DeclCXX.cpp: Skip clearing triviality flags for union members. - SemaDeclCXX.cpp: New union dtor deletion rules (user-provided ctor or DMI+non-trivial-dtor). Trivial ctor/dtor skip member checks. - DiagnosticSemaKinds.td: Add note_deleted_dtor_user_provided_ctor. - InitPreprocessor.cpp: Define __cpp_trivial_union=202502L. - New test: cxx26-trivial-union.cpp (10 test cases). Omits implicit lifetime starting (P3074R7 p4) per P3726R1 revert. --- .../clang/Basic/DiagnosticSemaKinds.td | 3 + clang/lib/AST/DeclCXX.cpp | 23 ++- clang/lib/Frontend/InitPreprocessor.cpp | 4 + clang/lib/Sema/SemaDeclCXX.cpp | 72 +++++++++- clang/test/SemaCXX/cxx26-trivial-union.cpp | 133 ++++++++++++++++++ 5 files changed, 230 insertions(+), 5 deletions(-) create mode 100644 clang/test/SemaCXX/cxx26-trivial-union.cpp diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 0c25eb2443d5e..e85e4e0ea14e0 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -6406,6 +6406,9 @@ 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_user_provided_ctor : Note< + "destructor of %0 is implicitly deleted because it has a user-provided " + "default constructor">; 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|" diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp index 083c53e28cb91..54009b8e482cf 100644 --- a/clang/lib/AST/DeclCXX.cpp +++ b/clang/lib/AST/DeclCXX.cpp @@ -1239,7 +1239,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 +1271,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 +1314,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/Frontend/InitPreprocessor.cpp b/clang/lib/Frontend/InitPreprocessor.cpp index 1ccd74314f373..a00b34625510b 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", "202502L"); + if (LangOpts.Char8) Builder.defineMacro("__cpp_char8_t", "202207L"); Builder.defineMacro("__cpp_impl_destroying_delete", "201806L"); diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 2ae6e5de0e3ee..81605bd4e12eb 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; } @@ -10034,6 +10052,47 @@ bool Sema::ShouldDeleteSpecialMember(CXXMethodDecl *MD, SpecialMemberDeletionInfo SMI(*this, MD, CSM, ICI, Diagnose); + // P3074R7 [class.dtor]p7: + // In C++26, a defaulted destructor for a union X is defined as deleted if: + // (7.x.1) X has a user-provided default constructor or no default + // constructor (overload resolution fails or selects a deleted + // constructor), or + // (7.x.2) X has a variant member V of class type M (or possibly + // multi-dimensional array thereof) where V has a default member + // initializer and M has a destructor that is non-trivial. + // Otherwise, the destructor is trivial, regardless of whether variant + // members have non-trivial destructors. + if (getLangOpts().CPlusPlus26 && RD->isUnion() && + CSM == CXXSpecialMemberKind::Destructor) { + // Check (7.x.1): user-provided default constructor. + if (RD->hasUserProvidedDefaultConstructor()) { + if (Diagnose) + Diag(RD->getLocation(), diag::note_deleted_dtor_user_provided_ctor) + << RD; + return true; + } + // Check (7.x.2): any variant member with DMI and non-trivial dtor? + for (const auto *FD : RD->fields()) { + if (!FD->hasInClassInitializer()) + continue; + QualType FieldType = Context.getBaseElementType(FD->getType()); + if (CXXRecordDecl *FieldRec = FieldType->getAsCXXRecordDecl()) { + if (FieldRec->hasNonTrivialDestructor()) { + if (Diagnose) + Diag(FD->getLocation(), + diag::note_deleted_special_member_class_subobject) + << getSpecialMember(MD) << RD << /*IsField*/ true << FD + << /*NonTrivialDecl*/ 4 << /*IsDtorCallInCtor*/ false + << /*IsObjCPtr*/ false; + return true; + } + } + } + // Union destructor is not deleted — skip the normal per-member visit. + // Only check the virtual dtor + operator delete rule above and CUDA below. + goto AfterMemberVisit; + } + // Per DR1611, do not consider virtual bases of constructors of abstract // classes, since we are not going to construct them. // Per DR1658, do not consider virtual bases of destructors of abstract @@ -10047,6 +10106,7 @@ bool Sema::ShouldDeleteSpecialMember(CXXMethodDecl *MD, if (SMI.shouldDeleteForAllConstMembers()) return true; +AfterMemberVisit: if (getLangOpts().CUDA) { // We should delete the special member in CUDA mode if target inference // failed. @@ -10467,7 +10527,17 @@ bool Sema::SpecialMemberIsTrivial(CXXMethodDecl *MD, CXXSpecialMemberKind CSM, // -- for all of the non-static data members of its class that are of class // type (or array thereof), each such class has a trivial [default // constructor or destructor] - if (!checkTrivialClassMembers(*this, RD, CSM, ConstArg, TAH, Diagnose)) + // + // P3074R7 [class.default.ctor]p3, [class.dtor]p8: + // In C++26, "either X is a union or" the above member checks apply. + // For unions, default constructor and destructor are trivial regardless + // of member triviality. + if (RD->isUnion() && getLangOpts().CPlusPlus26 && + (CSM == CXXSpecialMemberKind::DefaultConstructor || + CSM == CXXSpecialMemberKind::Destructor)) { + // Union default ctor and destructor are trivial in C++26 per P3074. + // Skip member triviality checks. + } else if (!checkTrivialClassMembers(*this, RD, CSM, ConstArg, TAH, Diagnose)) return false; // C++11 [class.dtor]p5: diff --git a/clang/test/SemaCXX/cxx26-trivial-union.cpp b/clang/test/SemaCXX/cxx26-trivial-union.cpp new file mode 100644 index 0000000000000..ad5800f84fd8c --- /dev/null +++ b/clang/test/SemaCXX/cxx26-trivial-union.cpp @@ -0,0 +1,133 @@ +// RUN: %clang_cc1 -std=c++26 -fsyntax-only -verify=cxx26 %s +// RUN: %clang_cc1 -std=c++23 -fsyntax-only -verify=precxx26 %s + +// P3074R7: trivial unions + +struct NonTrivial { + NonTrivial(); + NonTrivial(const NonTrivial&); + NonTrivial& operator=(const NonTrivial&); + ~NonTrivial(); +}; + +struct NonTrivialDtor { + ~NonTrivialDtor(); +}; + +// ===== Test 1: Basic union with non-trivial member ===== +// P3074: default ctor and dtor should be trivial, not deleted. +union U1 { + NonTrivial nt; // precxx26-note 2{{non-trivial}} +}; + +#if __cplusplus > 202302L +static_assert(__is_trivially_constructible(U1)); +static_assert(__is_trivially_destructible(U1)); +U1 test_u1; +#else +U1 test_u1_pre; // precxx26-error {{deleted}} +void destroy_u1(U1 *p) { p->~U1(); } // precxx26-error {{deleted}} +#endif + +// ===== Test 2: Union with non-trivial member and int ===== +union U2 { + NonTrivial nt; + int k; +}; + +#if __cplusplus > 202302L +static_assert(__is_trivially_constructible(U2)); +static_assert(__is_trivially_destructible(U2)); +#endif + +// ===== Test 3: Union with DMI on member with non-trivial dtor ===== +// P3074: dtor is deleted because DMI + non-trivial dtor on same member. +union U3_deleted_dtor { + NonTrivialDtor ntd = {}; // cxx26-note {{non-trivial}} precxx26-note {{non-trivial}} +}; + +void test_u3_destroy(U3_deleted_dtor *p) { + p->~U3_deleted_dtor(); // cxx26-error {{deleted}} precxx26-error {{deleted}} +} + +// ===== Test 4: Union with DMI on non-class member ===== +// DMI on int, but NonTrivial has no DMI => dtor should NOT be deleted. +union U4 { + NonTrivial nt; // precxx26-note {{non-trivial}} + int k = 42; +}; + +#if __cplusplus > 202302L +// Despite non-trivial default ctor (due to DMI), destructor is NOT deleted +// because the member with DMI (k) is int (trivially destructible). +static_assert(__is_trivially_destructible(U4)); +#else +void destroy_u4(U4 *p) { p->~U4(); } // precxx26-error {{deleted}} +#endif + +// ===== Test 5: Union with user-provided default constructor ===== +union U5 { // cxx26-note {{user-provided}} + U5() : nt() {} + NonTrivialDtor nt; +}; + +#if __cplusplus > 202302L +// P3074 (7.x.1): user-provided default ctor => destructor is deleted. +void test_u5_destroy(U5 *p) { p->~U5(); } // cxx26-error {{deleted}} +#endif + +// ===== Test 6: Feature test macro ===== +#if __cplusplus > 202302L +static_assert(__cpp_trivial_union >= 202502L); +#else +#ifdef __cpp_trivial_union +#error "should not have __cpp_trivial_union in C++23" +#endif +#endif + +// ===== Test 7: Trivial union (no change from status quo) ===== +union U7 { + int a; + float b; +}; + +static_assert(__is_trivially_constructible(U7)); +static_assert(__is_trivially_destructible(U7)); + +// ===== Test 8: Array member in union ===== +union U8 { + NonTrivial arr[4]; +}; + +#if __cplusplus > 202302L +static_assert(__is_trivially_constructible(U8)); +static_assert(__is_trivially_destructible(U8)); +#endif + +// ===== Test 9: Paper example - string with DMI ===== +struct FakeString { + FakeString(const char*); + FakeString(const FakeString&); + FakeString& operator=(const FakeString&); + ~FakeString(); +}; + +union PaperU2 { + FakeString s = "hello"; // cxx26-note {{non-trivial}} precxx26-note {{non-trivial}} +}; + +void test_paper_u2(PaperU2 *p) { + p->~PaperU2(); // cxx26-error {{deleted}} precxx26-error {{deleted}} +} + +// ===== Test 10: Paper example U4 - DMI on pointer, non-trivial string ===== +union PaperU4 { + FakeString s; // precxx26-note {{non-trivial}} + PaperU4 *next = nullptr; +}; + +#if __cplusplus > 202302L +static_assert(__is_trivially_destructible(PaperU4)); +#else +void destroy_paper_u4(PaperU4 *p) { p->~PaperU4(); } // precxx26-error {{deleted}} +#endif >From a499a62488e5e7fe723a4b2404b1da2ffb3ecb91 Mon Sep 17 00:00:00 2001 From: Peng Xie <[email protected]> Date: Wed, 11 Mar 2026 15:26:16 +0800 Subject: [PATCH 2/2] [Clang] Implement P3726R1: Adjustments to Union Lifetime Rules Implement the core language and constexpr evaluator changes from P3726R1 (Adjustments to Union Lifetime Rules). This is a follow-up to P3074R7 (trivial unions) that: 1. Adds __builtin_start_lifetime(void*) consteval builtin 2. Extends constituent values rule for union array members 3. Bumps __cpp_trivial_union from 202502L to 202507L --- clang/include/clang/Basic/Builtins.td | 6 + .../include/clang/Basic/DiagnosticASTKinds.td | 5 + .../clang/Basic/DiagnosticSemaKinds.td | 4 + clang/lib/AST/ExprConstant.cpp | 120 +++++++++++++++++- clang/lib/CodeGen/CGBuiltin.cpp | 5 + clang/lib/Frontend/InitPreprocessor.cpp | 2 +- clang/lib/Sema/SemaChecking.cpp | 53 ++++++++ clang/test/SemaCXX/cxx26-start-lifetime.cpp | 115 +++++++++++++++++ clang/test/SemaCXX/cxx26-trivial-union.cpp | 2 +- 9 files changed, 305 insertions(+), 7 deletions(-) create mode 100644 clang/test/SemaCXX/cxx26-start-lifetime.cpp 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 e85e4e0ea14e0..55a022c0e93a8 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -13240,6 +13240,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/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 429fef0a1afa8..4cb349362ecad 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,56 @@ 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 a00b34625510b..d698769a8a89f 100644 --- a/clang/lib/Frontend/InitPreprocessor.cpp +++ b/clang/lib/Frontend/InitPreprocessor.cpp @@ -748,7 +748,7 @@ static void InitializeCPlusPlusFeatureTestMacros(const LangOptions &LangOpts, // C++26 features. if (LangOpts.CPlusPlus26) - Builder.defineMacro("__cpp_trivial_union", "202502L"); + Builder.defineMacro("__cpp_trivial_union", "202507L"); if (LangOpts.Char8) Builder.defineMacro("__cpp_char8_t", "202207L"); 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/test/SemaCXX/cxx26-start-lifetime.cpp b/clang/test/SemaCXX/cxx26-start-lifetime.cpp new file mode 100644 index 0000000000000..cb8a4baeaa1d2 --- /dev/null +++ b/clang/test/SemaCXX/cxx26-start-lifetime.cpp @@ -0,0 +1,115 @@ +// RUN: %clang_cc1 -std=c++26 -fsyntax-only -verify %s + +// P3726R1: __builtin_start_lifetime and constituent values tests + +namespace std { + using size_t = decltype(sizeof(0)); +} +void* operator new(std::size_t, void* p) noexcept { return p; } + +// ===== Type checking tests ===== + +struct Agg { int x; int y; }; +struct NonAgg { + NonAgg(int); + int x; +}; +struct AggWithUserDtor { + int x; + ~AggWithUserDtor(); +}; + +// type checking for __builtin_start_lifetime is done via consteval contexts. +consteval void check_agg() { + Agg a; + __builtin_start_lifetime(&a); // OK +} +consteval void check_array() { + int arr[4]; + __builtin_start_lifetime(&arr); // OK +} + +consteval void check_scalar() { + int x = 0; + __builtin_start_lifetime(&x); // expected-error {{pointer to a complete implicit-lifetime aggregate type}} +} + +consteval void check_non_agg() { + // NonAgg is not constructible without an argument, can't be a local here. + // Just test the pointer type check with an invalid construct. +} + +consteval void check_user_dtor() { + AggWithUserDtor awd; + __builtin_start_lifetime(&awd); // expected-error {{pointer to a complete implicit-lifetime aggregate type}} +} + +// ===== Constexpr evaluation tests ===== + +// Test: start_lifetime on array member of union, then placement new elements +consteval int test_start_lifetime_array() { + struct S { + union { int storage[4]; }; + int size = 0; + }; + S s; + __builtin_start_lifetime(&s.storage); + // Now storage is the active member, but no elements are within lifetime. + ::new (&s.storage[0]) int(10); + ::new (&s.storage[1]) int(20); + s.size = 2; + return s.storage[0] + s.storage[1]; // 30 +} +static_assert(test_start_lifetime_array() == 30); + +// Test: start_lifetime is no-op if already within lifetime +consteval int test_start_lifetime_noop() { + struct S { + union { int storage[2]; }; + }; + S s; + __builtin_start_lifetime(&s.storage); + ::new (&s.storage[0]) int(42); + // Call again - should be a no-op since storage is already active + __builtin_start_lifetime(&s.storage); + return s.storage[0]; // Still 42 +} +static_assert(test_start_lifetime_noop() == 42); + +// Test: start_lifetime on struct member of union +consteval int test_start_lifetime_struct() { + struct Inner { int a; int b; }; + union U { Inner inner; int x; }; + U u; + __builtin_start_lifetime(&u.inner); + // inner is now active but its members aren't initialized yet + ::new (&u.inner.a) int(1); + ::new (&u.inner.b) int(2); + return u.inner.a + u.inner.b; +} +static_assert(test_start_lifetime_struct() == 3); + +// ===== Constituent values: array with holes in union ===== +// P3726R1 [expr.const]p2: array elements not within their lifetime +// in a union are inactive union subobjects and should be skipped. + +struct CVResult { + union { int arr[4]; }; + int size; +}; + +consteval CVResult test_constituent_values() { + CVResult s; + s.size = 2; + __builtin_start_lifetime(&s.arr); + ::new (&s.arr[0]) int(100); + ::new (&s.arr[1]) int(200); + // arr[2] and arr[3] are not within their lifetime — that's OK per P3726R1. + return s; +} +// This should be a valid constexpr variable even though arr[2] and arr[3] +// are not initialized — they are inactive union subobjects per P3726R1. +constexpr auto cv_result = test_constituent_values(); +static_assert(cv_result.arr[0] == 100); +static_assert(cv_result.arr[1] == 200); +static_assert(cv_result.size == 2); diff --git a/clang/test/SemaCXX/cxx26-trivial-union.cpp b/clang/test/SemaCXX/cxx26-trivial-union.cpp index ad5800f84fd8c..e405a1922d1d0 100644 --- a/clang/test/SemaCXX/cxx26-trivial-union.cpp +++ b/clang/test/SemaCXX/cxx26-trivial-union.cpp @@ -78,7 +78,7 @@ void test_u5_destroy(U5 *p) { p->~U5(); } // cxx26-error {{deleted}} // ===== Test 6: Feature test macro ===== #if __cplusplus > 202302L -static_assert(__cpp_trivial_union >= 202502L); +static_assert(__cpp_trivial_union >= 202507L); #else #ifdef __cpp_trivial_union #error "should not have __cpp_trivial_union in C++23" _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
