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

Reply via email to