On Fri, Sep 4, 2026 at 10:44 PM Jason Merrill <[email protected]> wrote:
> On 9/4/26 9:57 AM, Jakub Jelinek wrote: > > Hi! > > > > Here is an updated version of the P3726R2 paper implementation, > originally > > posted in May together with the other trivial union paper and one CWG: > > https://gcc.gnu.org/pipermail/gcc-patches/2026-May/thread.html#717349 > > That other paper and CWG has been committed in the meantime, this one > > was deferred so that more accurate lifetime tracking is implemented > (which > > was partially done in the recently committed std::is_within_lifetime > patch). > > > > The major change from the earlier patch is that a new CONSTRUCTOR flag > > CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P is added, which represents > > both the C++20 std::allocator<T>::allocate required behavior as well as > > P3726R2 std::start_lifetime, where the CONSTRUCTOR itself is considered > > to be within lifetime, but none of its subobjects. > > This flag is then tested in reduced_constant_expression_p to determine > > which union elemental subobjects are actually inactive and only those are > > allowed to be omitted, and in the recently added > > __builtin_is_within_lifetime builtin constant evaluation. > > > > So far lightly tested, with: > > GXX_TESTSUITE_STDS=98,11,14,17,20,23,26,29 make check-g++ > RUNTESTFLAGS="dg.exp='within-lifetime* trivial-union* feat-cxx2* > constexpr-life*'" > > ok for trunk if it passes full bootstrap/regtest? > > > > 2026-09-04 Jakub Jelinek <[email protected]> > > > > P3726R2 > > PR c++/125845 > > gcc/c-family/ > > * c-cppbuiltin.cc (c_cpp_builtins): For C++26 predefine > > __cpp_trivial_union to 202603L. > > gcc/cp/ > > * cp-tree.h: Implement C++26 P3726R2 - Adjustments to Union > Lifetime > > Rules. > > (CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P): Define. > > (enum cp_built_in_function): Add CP_BUILT_IN_START_LIFETIME. > > (reduced_constant_expression_p): Add a bool argument defaulted to > > false. > > * tree.cc (builtin_valid_in_constant_expr_p): Handle > > CP_BUILT_IN_START_LIFETIME. > > * decl.cc (cxx_init_decl_processing): Create > __builtin_start_lifetime > > decl. > > * cp-gimplify.cc (cp_gimplify_expr): Handle > > CP_BUILT_IN_START_LIFETIME. > > * semantics.cc (finish_call_expr): Likewise. > > Let's move all the builtin handling out to another function at this point. > > > * constexpr.cc (cxx_eval_is_within_lifetime): For subobjects of > > CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P return > boolean_false_node. > > (cxx_eval_start_lifetime): New function. > > (cxx_eval_builtin_function_call): Handle > CP_BUILT_IN_START_LIFETIME. > > (reduced_constant_expression_p): Add union_elemental_subobject > > argument and propagate it to recursive calls. If set, ignore holes > > in array initializers which aren't within lifetime. > > (cxx_eval_store_expression): Add start_lifetime argument. Don't > > preevaluate init in tha tcase, return void_node early if *valp > > that case > > > is already non-NULL/void_node and within lifetime. > > (cxx_eval_constant_expression): For std::allocator<T>::allocate > > start lifetime of the created array but not of its subobjects. > > gcc/testsuite/ > > * g++.dg/cpp26/feat-cxx26.C: Adjust __cpp_trivial_union checking. > > * g++.dg/cpp29/feat-cxx29.C: Adjust __cpp_trivial_union checking. > > * g++.dg/cpp26/trivial-union3.C: New test. > > * g++.dg/cpp26/trivial-union4.C: New test. > > * g++.dg/cpp26/trivial-union5.C: New test. > > * g++.dg/cpp26/trivial-union6.C: New test. > > * g++.dg/cpp26/within-lifetime3.C: Remove #if 0 and #endif. > > * g++.dg/cpp29/feat-cxx29.C: Adjust __cpp_trivial_union checking. > > Duplicate entry. > > > libstdc++-v3/ > > * include/bits/version.def (start_lifetime): New. > > * include/bits/version.h: Regenerate. > > * include/bits/stl_construct.h (std::start_lifetime): New function > > template. > > * include/std/memory: Define __glibcxx_want_start_lifetime before > > including bits/version.h. > > * src/c++23/std.cc.in: Add export std::start_lifetime. > > > > --- a/gcc/cp/cp-tree.h 2026-09-03 09:45:43.310945741 +0200 > > +++ b/gcc/cp/cp-tree.h 2026-09-04 14:11:50.412816957 +0200 > > @@ -5098,6 +5098,10 @@ get_vec_init_expr (tree t) > > #define CONSTRUCTOR_BRACES_ELIDED_P(NODE) \ > > (CONSTRUCTOR_CHECK (NODE)->base.protected_flag) > > > > +/* True if omitted fields are considered to be not within lifetime. */ > > +#define CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P(NODE) \ > > + (CONSTRUCTOR_CHECK (NODE)->base.deprecated_flag) > > Let's document the use of these bitfields in the "Usage of > language-independent fields in a language-dependent manner" section at > the top of the file. > > > --- a/gcc/cp/decl.cc 2026-09-03 09:45:43.308945768 +0200 > > +++ b/gcc/cp/decl.cc 2026-09-04 13:54:14.077036599 +0200 > > @@ -5679,6 +5679,13 @@ cxx_init_decl_processing (void) > > set_call_expr_flags (decl, ECF_NOTHROW | ECF_LEAF); > > SET_DECL_IMMEDIATE_FUNCTION_P (decl); > > > > + tree void_vaftype = build_varargs_function_type_list > (boolean_type_node, > > "void_vaftype" with bool return type seems contradictory. > > > --- a/gcc/cp/semantics.cc 2026-09-03 09:45:43.312945713 +0200 > > +++ b/gcc/cp/semantics.cc 2026-09-04 14:10:43.423660866 +0200 > > @@ -3541,30 +3541,74 @@ finish_call_expr (tree fn, vec<tree, va_ > > } > > > > if (TREE_CODE (fn) == FUNCTION_DECL > > + && fndecl_built_in_p (fn, BUILT_IN_FRONTEND)) > > + switch (DECL_UNCHECKED_FUNCTION_CODE (fn)) > > + { > > + tree arg, ptype, type; > > + case CP_BUILT_IN_IS_WITHIN_LIFETIME: > > + /* Unless users call the builtin directly, the following 2 > checks > > + should be ensured from std::is_within_lifetime > template. */ > > + if (vec_safe_length (*args) != 1) > > + { > > + if (complain & tf_error) > > + error ("%qs needs a single argument", > > + "__builtin_is_within_lifetime"); > > + return error_mark_node; > > + } > > + arg = (**args)[0]; > > + if (error_operand_p (arg)) > > return error_mark_node; > > + ptype = TREE_TYPE (arg); > > + if (!POINTER_TYPE_P (ptype)) > > + { > > + if (complain & tf_error) > > + error ("%qs argument type %qT is not pointer type", > > + "__builtin_is_within_lifetime", ptype); > > + return error_mark_node; > > + } > > + break; > > + case CP_BUILT_IN_START_LIFETIME: > > + /* Unless users call the builtin directly, the following 2 > checks > > + should be ensured from std::start_lifetime template. */ > > + if (vec_safe_length (*args) != 1) > > + { > > + if (complain & tf_error) > > + error ("%qs needs a single argument", > > + "__builtin_start_lifetime"); > > + return error_mark_node; > > + } > > + arg = (**args)[0]; > > + if (error_operand_p (arg)) > > return error_mark_node; > > + ptype = TREE_TYPE (arg); > > + if (!POINTER_TYPE_P (ptype)) > > + { > > + if (complain & tf_error) > > + error ("%qs argument type %qT is not pointer type", > > + "__builtin_start_lifetime", ptype); > > + return error_mark_node; > > + } > > + type = TREE_TYPE (ptype); > > + if (!complete_type_or_else (type, NULL_TREE)) > > + return error_mark_node; > > + if (!CP_AGGREGATE_TYPE_P (type)) > > + { > > + if (complain & tf_error) > > + error ("%qs argument type %qT is not a pointer to " > > + "aggregate type", "__builtin_start_lifetime", > > + ptype); > > + return error_mark_node; > > + } > > + if (!implicit_lifetime_type_p (type)) > > + { > > + if (complain & tf_error) > > + error ("%qs argument type %qT is not a pointer to " > > + "implicit-lifetime type", > > + "__builtin_start_lifetime", ptype); > > + return error_mark_node; > > + } > > + break; > > We probably want a default: case, to avoid warnings if nothing else. > > > + } > > > > /* A call to a namespace-scope function. */ > > result = build_new_function_call (fn, args, orig_complain); > > --- a/gcc/cp/constexpr.cc 2026-09-03 09:43:50.766477808 +0200 > > +++ b/gcc/cp/constexpr.cc 2026-09-04 14:32:59.700779280 +0200 > > @@ -5107,10 +5154,13 @@ cxx_eval_call_expression (const constexp > > initializes all the members, the CONSTRUCTOR_NO_CLEARING flag will > be > > cleared. If called recursively on a FIELD_DECL's CONSTRUCTOR, SZ > > is DECL_SIZE of the FIELD_DECL, otherwise NULL. > > + UNION_ELEMENTAL_SUBOBJECT is true when recursing on C++ >= 26 union > > + elemental subobjects. > > FIXME speed this up, it's taking 16% of compile time on sieve > testcase. */ > > > > bool > > -reduced_constant_expression_p (tree t, tree sz /* = NULL_TREE */) > > +reduced_constant_expression_p (tree t, tree sz /* = NULL_TREE */, > > + bool union_elemental_subobject /* = false > */) > > { > > if (t == NULL_TREE) > > return false; > > @@ -5134,7 +5184,11 @@ reduced_constant_expression_p (tree t, t > > return false; > > if (CONSTRUCTOR_NO_CLEARING (t)) > > { > > - if (TREE_CODE (TREE_TYPE (t)) == ARRAY_TYPE) > > + if (union_elemental_subobject > > + && TREE_CODE (TREE_TYPE (t)) == ARRAY_TYPE > > + && CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P (t)) > > + field = NULL_TREE; > > + else if (TREE_CODE (TREE_TYPE (t)) == ARRAY_TYPE) > > { > > /* There must be a valid constant initializer at every array > > index. */ > > @@ -5168,12 +5222,25 @@ reduced_constant_expression_p (tree t, t > > active member has no constituent values, so all > constituent > > values are constant. */ > > field = NULL_TREE; > > + union_elemental_subobject = cxx_dialect >= cxx26; > > I'd prefer to compute union_elemental_subobject regardless of > cxx_dialect, and only check cxx_dialect when applying it above. > > > } > > else > > - field = next_subobject_field (TYPE_FIELDS (TREE_TYPE (t))); > > + { > > + field = next_subobject_field (TYPE_FIELDS (TREE_TYPE (t))); > > + union_elemental_subobject = false; > > + } > > } > > else > > - field = NULL_TREE; > > + { > > + field = NULL_TREE; > > + if (cxx_dialect >= cxx26) > > + { > > + if (TREE_CODE (TREE_TYPE (t)) == UNION_TYPE) > > + union_elemental_subobject = true; > > + else if (TREE_CODE (TREE_TYPE (t)) != ARRAY_TYPE) > > + union_elemental_subobject = false; > > + } > > + } > > for (auto &e: CONSTRUCTOR_ELTS (t)) > > { > > /* If VAL is null, we're in the middle of initializing this > > @@ -5183,7 +5250,8 @@ reduced_constant_expression_p (tree t, t > > && (TREE_CODE (e.index) > > == FIELD_DECL)) > > ? DECL_SIZE (e.index) > > - : NULL_TREE)) > > + : NULL_TREE, > > + union_elemental_subobject)) > > return false; > > /* We want to remove initializers for empty fields in a struct to > > avoid confusing output_constructor. */ > > @@ -8213,13 +8281,14 @@ modifying_const_object_p (tree_code code > > return false; > > } > > > > -/* Evaluate an INIT_EXPR or MODIFY_EXPR. */ > > +/* Evaluate an INIT_EXPR or MODIFY_EXPR. START_LIFETIME is true > > + when evaluating __builtin_start_lifetime. */ > > > > static tree > > cxx_eval_store_expression (const constexpr_ctx *ctx, tree t, > > value_cat lval, > > bool *non_constant_p, bool *overflow_p, > > - tree *jump_target) > > + tree *jump_target, bool > start_lifetime/*=false*/) > > Hmm, I don't love adding a parameter for this case, but I guess a > clobber won't work because this is a nop for an object that's already in > lifetime. > > > { > > constexpr_ctx new_ctx = *ctx; > > > > @@ -8232,7 +8301,7 @@ cxx_eval_store_expression (const constex > > > > tree type = TREE_TYPE (target); > > bool preeval = SCALAR_TYPE_P (type) || TREE_CODE (t) == MODIFY_EXPR; > > - if (preeval && !TREE_CLOBBER_P (init)) > > + if (preeval && !TREE_CLOBBER_P (init) && !start_lifetime) > > { > > /* Ignore var = .DEFERRED_INIT (); for now, until PR121965 is > fixed. */ > > if (flag_auto_var_init > AUTO_INIT_UNINITIALIZED > > @@ -8573,12 +8642,13 @@ cxx_eval_store_expression (const constex > > index); > > *non_constant_p = true; > > } > > - else if (!is_access_expr > > - || (TREE_CLOBBER_P (init) > > - && CLOBBER_KIND (init) >= CLOBBER_OBJECT_END) > > - || (TREE_CODE (t) == MODIFY_EXPR > > - && CLASS_TYPE_P (inner) > > - && !type_has_non_deleted_trivial_default_ctor > (inner))) > > + else if ((!is_access_expr > > + || (TREE_CLOBBER_P (init) > > + && CLOBBER_KIND (init) >= CLOBBER_OBJECT_END) > > + || (TREE_CODE (t) == MODIFY_EXPR > > + && CLASS_TYPE_P (inner) > > + && !type_has_non_deleted_trivial_default_ctor > (inner))) > > + && !start_lifetime) > > { > > /* Diagnose changing active union member after initialization > > without a valid member access expression, as described in > > @@ -8675,6 +8745,11 @@ cxx_eval_store_expression (const constex > > empty_base = true; > > } > > > > + /* For __builtin_start_lifetime, if it is already within lifetime, > > + don't do anything. */ > > + if (start_lifetime && *valp != NULL_TREE && *valp != void_node) > > + return void_node; > > + > > /* Detect modifying a constant object in constexpr evaluation. > > We have found a const object that is being modified. Figure out > > if we need to issue an error. Consider > > @@ -8780,7 +8855,7 @@ cxx_eval_store_expression (const constex > > || *valp == void_node > > || (same_type_ignoring_top_level_qualifiers_p > > (TREE_TYPE (*valp), type))); > > - if (empty_base) > > + if (empty_base && !start_lifetime) > > I'm wondering about the empty_base && start_lifetime case. empty_base > means valp is pointing to the initializer for a containing class at this > point. But if that object had a value we would have returned early > above -- and not done anything to start the subobject lifetime. So this > change only affects the case where the enclosing class is uninitialized. > With this change, we end up at *valp = init below, setting the *valp > for the enclosing class to a value of the wrong type. > > So I don't think this change is right. Which test hits this? I don't > see any empty classes in the new tests. > > > { > > /* Just evaluate the initializer and return, since there's no > actual data > > to store, and we didn't build a CONSTRUCTOR. */ > > @@ -10730,6 +10805,17 @@ cxx_eval_constant_expression (const cons > > TREE_TYPE (var) = new_type; > > TREE_TYPE (TREE_OPERAND (op, 0)) > > = build_pointer_type (TREE_TYPE (var)); > > + if (is_std_allocator_allocate (ctx->call) > > + && id_equal (DECL_NAME (ctx->call->fundef->decl), > "allocate")) > > It's surprising that a function named is_std_allocator_allocate also > returns true for "deallocate". > > > --- a/gcc/testsuite/g++.dg/cpp26/trivial-union6.C 2026-09-04 > 13:54:14.081215755 +0200 > > +++ b/gcc/testsuite/g++.dg/cpp26/trivial-union6.C 2026-09-04 > 13:54:14.081215755 +0200 > > @@ -0,0 +1,70 @@ > > +// P3074R7 - trivial unions (was std::uninitialized<T>) > > +// P3726R2 - Adjustments to Union Lifetime Rules > > +// { dg-do compile { target c++20 } } > > + > > +struct A { int a[2]; }; > > +union B { int a; int b[2]; }; > > +union C { int a; int b[2][2]; }; > > +struct D { B a; }; > > +struct E { C a; }; > > +union F { int a; A b; }; > > +union G { int a; A b[2]; }; > > + > > +constexpr A fA () { A a; a.a[0] = 1; return a; } > > +constexpr A a = fA (); // { dg-error "is not a constant > expression" "" { xfail *-*-* } } > > I don't see any discussion of this xfail above (or a comment here). Is > there a PR? > Because A is struct, so a.a[1] is consitutient value of a. and is erronous, so I would expect that error. Zero-initialization of global variables seems irrelevant here, as a is copy-initialized from such value > Jason > >
