On Wed, Sep 9, 2026 at 11:27 AM Jakub Jelinek <[email protected]> wrote:
> On Wed, Sep 09, 2026 at 08:42:32AM +0200, Tomasz Kaminski wrote:
> > I would much more preffer the start_lifetime(a.x) to be non-constant
> > expressions
> > if `a` (or any containing object) is not within its lifetime.
> >
> > I believe this function should start a lifetime of at most a single
> object.
> > Otherwise
> > it would be unclear, if the enclosing objects should be created (with all
> > members)
> > or only have their lifetimes started. This becomes even more complicated
> if
> > we
> > consider non-trivial enclosing objects.
> >
> > Also, I want to avoid orphaned in-lifetime objects, when we have some
> > subobject
> > in lifetime, while some of the containing objects are not. This is why
> the
> > paper hasn't
> > proposed end_lifetime function, as it would naturally lead to such
> > situations, and
> > instead relies on storage reuse to end lieftime of any objects that have
> > they lifetime started.
>
> Here is an incremental patch on top of the patch posted today, which
> essentially performs a __builtin_is_within_lifetime call on the containing
> (sub)object if any before trying to evaluate the store.
>
> --- a/gcc/cp/constexpr.cc 2026-09-08 22:26:48.000000000 +0200
> +++ b/gcc/cp/constexpr.cc 2026-09-09 10:47:26.825464921 +0200
> @@ -2541,62 +2541,13 @@ cxx_eval_constexpr_diag (const constexpr
> static tree eval_and_check_array_index (const constexpr_ctx *, tree, bool,
> bool *, bool *, tree *);
>
> -/* Attempt to evaluate T which represents a call to
> - __builtin_is_within_lifetime. */
> +/* Helper function for cxx_eval_is_within_lifetime and
> + cxx_eval_start_lifetime. Check if ARG is within lifetime. */
>
> static tree
> -cxx_eval_is_within_lifetime (const constexpr_ctx *ctx, tree t,
> - bool *non_constant_p, bool *overflow_p,
> - tree *jump_target)
> +is_within_lifetime (location_t loc, const constexpr_ctx *ctx, tree t,
> tree arg,
> + bool *non_constant_p, bool *overflow_p, tree
> *jump_target)
> {
> - location_t loc = EXPR_LOCATION (t);
> - tree arg = CALL_EXPR_ARG (t, 0);
> - arg = cxx_eval_constant_expression (ctx, arg, vc_prvalue,
> - non_constant_p, overflow_p,
> - jump_target);
> - if (*jump_target)
> - return NULL_TREE;
> - if (*non_constant_p)
> - return t;
> - /* Need to strip casts to pointers to void, but should preserve
> - pointer casts within the innermost pointer to void cast if
> - any, so that e.g. pointers to heap allocations are handled
> - correctly. */
> - tree p = arg;
> - while (CONVERT_EXPR_P (p) || TREE_CODE (p) == NON_LVALUE_EXPR)
> - {
> - if (!TYPE_PTR_P (TREE_TYPE (p)))
> - break;
> - if (VOID_TYPE_P (TREE_TYPE (TREE_TYPE (p))))
> - arg = TREE_OPERAND (p, 0);
> - p = TREE_OPERAND (p, 0);
> - }
> - if (integer_zerop (arg))
> - {
> - if (!ctx->quiet)
> - error_at (loc, "%qs called with a null pointer",
> - "__builtin_is_within_lifetime");
> - *non_constant_p = true;
> - return t;
> - }
> - if (POINTER_TYPE_P (TREE_TYPE (arg))
> - && (TREE_CODE (TREE_TYPE (TREE_TYPE (arg))) == FUNCTION_TYPE
> - || TREE_CODE (TREE_TYPE (TREE_TYPE (arg))) == METHOD_TYPE))
> - {
> - if (!ctx->quiet)
> - error_at (loc, "%qs called with pointer to function",
> - "__builtin_is_within_lifetime");
> - *non_constant_p = true;
> - return t;
> - }
> - arg = build1 (INDIRECT_REF, TREE_TYPE (TREE_TYPE (arg)), arg);
> - arg = cxx_eval_constant_expression (ctx, arg, vc_glvalue,
> - non_constant_p, overflow_p,
> - jump_target);
> - if (*jump_target)
> - return NULL_TREE;
> - if (*non_constant_p)
> - return t;
> auto_vec <tree, 4> refs;
> tree obj;
> for (obj = arg; ; obj = TREE_OPERAND (obj, 0))
> @@ -2793,6 +2744,66 @@ cxx_eval_is_within_lifetime (const const
> return boolean_true_node;
> }
>
> +/* Attempt to evaluate T which represents a call to
> + __builtin_is_within_lifetime. */
> +
> +static tree
> +cxx_eval_is_within_lifetime (const constexpr_ctx *ctx, tree t,
> + bool *non_constant_p, bool *overflow_p,
> + tree *jump_target)
> +{
> + location_t loc = EXPR_LOCATION (t);
> + tree arg = CALL_EXPR_ARG (t, 0);
> + arg = cxx_eval_constant_expression (ctx, arg, vc_prvalue,
> + non_constant_p, overflow_p,
> + jump_target);
> + if (*jump_target)
> + return NULL_TREE;
> + if (*non_constant_p)
> + return t;
> + /* Need to strip casts to pointers to void, but should preserve
> + pointer casts within the innermost pointer to void cast if
> + any, so that e.g. pointers to heap allocations are handled
> + correctly. */
> + tree p = arg;
> + while (CONVERT_EXPR_P (p) || TREE_CODE (p) == NON_LVALUE_EXPR)
> + {
> + if (!TYPE_PTR_P (TREE_TYPE (p)))
> + break;
> + if (VOID_TYPE_P (TREE_TYPE (TREE_TYPE (p))))
> + arg = TREE_OPERAND (p, 0);
> + p = TREE_OPERAND (p, 0);
> + }
> + if (integer_zerop (arg))
> + {
> + if (!ctx->quiet)
> + error_at (loc, "%qs called with a null pointer",
> + "__builtin_is_within_lifetime");
> + *non_constant_p = true;
> + return t;
> + }
> + if (POINTER_TYPE_P (TREE_TYPE (arg))
> + && (TREE_CODE (TREE_TYPE (TREE_TYPE (arg))) == FUNCTION_TYPE
> + || TREE_CODE (TREE_TYPE (TREE_TYPE (arg))) == METHOD_TYPE))
> + {
> + if (!ctx->quiet)
> + error_at (loc, "%qs called with pointer to function",
> + "__builtin_is_within_lifetime");
> + *non_constant_p = true;
> + return t;
> + }
> + arg = build1 (INDIRECT_REF, TREE_TYPE (TREE_TYPE (arg)), arg);
> + arg = cxx_eval_constant_expression (ctx, arg, vc_glvalue,
> + non_constant_p, overflow_p,
> + jump_target);
> + if (*jump_target)
> + return NULL_TREE;
> + if (*non_constant_p)
> + return t;
> + return is_within_lifetime (loc, ctx, t, arg, non_constant_p, overflow_p,
> + jump_target);
> +}
> +
> static tree cxx_eval_store_expression (const constexpr_ctx *, tree,
> value_cat, bool *, bool *,
> tree *);
> @@ -2805,7 +2816,14 @@ cxx_eval_start_lifetime (const constexpr
> bool *overflow_p, tree *jump_target)
> {
> location_t loc = EXPR_LOCATION (t);
> - tree arg = CALL_EXPR_ARG (t, 0);
> + tree arg = CALL_EXPR_ARG (t, 0), within_lifetime;
> + arg = cxx_eval_constant_expression (ctx, arg, vc_prvalue,
> + non_constant_p, overflow_p,
> + jump_target);
> + if (*jump_target)
> + return NULL_TREE;
> + if (*non_constant_p)
> + return t;
> arg = build1 (INDIRECT_REF, TREE_TYPE (TREE_TYPE (arg)), arg);
> arg = cxx_eval_constant_expression (ctx, arg, vc_glvalue,
> non_constant_p, overflow_p,
> @@ -2814,6 +2832,31 @@ cxx_eval_start_lifetime (const constexpr
> return NULL_TREE;
> if (*non_constant_p)
> return t;
> + switch (TREE_CODE (arg))
> + {
> + case COMPONENT_REF:
> + case ARRAY_REF:
> + case REALPART_EXPR:
> + case IMAGPART_EXPR:
> + within_lifetime
> + = is_within_lifetime (loc, ctx, t, TREE_OPERAND (arg, 0),
> + non_constant_p, overflow_p, jump_target);
> + if (*jump_target)
> + return NULL_TREE;
> + if (*non_constant_p)
> + return t;
> + if (within_lifetime != boolean_true_node)
> + {
> + if (!ctx->quiet)
> + error_at (loc, "%qs with containing object not within
> lifetime",
> + "__builtin_is_within_lifetime");
> + *non_constant_p = true;
> + return t;
> + }
> + break;
> + default:
> + break;
> + }
> tree ctor = build_constructor (TREE_TYPE (arg), NULL);
> CONSTRUCTOR_NO_CLEARING (ctor) = 1;
> CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P (ctor) = 1;
> --- a/gcc/testsuite/g++.dg/cpp26/trivial-union6.C 2026-09-08
> 22:19:17.810884355 +0200
> +++ b/gcc/testsuite/g++.dg/cpp26/trivial-union6.C 2026-09-09
> 11:15:08.446464272 +0200
> @@ -76,12 +76,92 @@ constexpr E
> fE3 (bool x)
> {
> E a;
> - __builtin_start_lifetime (&a.a.b[0]);
> + __builtin_start_lifetime (&a.a.b[0]); // { dg-error
> "'__builtin_is_within_lifetime' with containing object not within lifetime"
> }
> if (x)
> __builtin_start_lifetime (&a.a.b[1]);
> a.a.b[0][0] = 1;
> a.a.b[1][1] = 3;
> return a;
> }
> -constexpr E e4 = fE3 (false); // { dg-error "is not a constant
> expression" }
> -constexpr E e5 = fE3 (true); // { dg-error "is not a constant
> expression" "" { target c++23_down } }
> +constexpr E e4 = fE3 (false);
> +constexpr E e5 = fE3 (true);
> +union H { int a; A b[2][2]; };
> +union I { int a; H b[2]; };
> +constexpr bool
> +fH (int x)
> +{
> + G a;
> + I b;
> + switch (x)
> + {
> + case 0:
> + __builtin_start_lifetime (&a.b);
> + break;
> + case 1:
> + __builtin_start_lifetime (&a.b[0]); // { dg-error
> "'__builtin_is_within_lifetime' with containing object not within lifetime"
> }
> + break;
> + case 2:
> + a.a = 42;
> + __builtin_start_lifetime (&a.b);
> + break;
> + case 3:
> + a.a = 42;
> + __builtin_start_lifetime (&a.b[1]); // { dg-error
> "'__builtin_is_within_lifetime' with containing object not within lifetime"
> }
> + break;
> + case 4:
> + __builtin_start_lifetime (&b.b);
> + break;
> + case 5:
> + __builtin_start_lifetime (&b.b[0]); // { dg-error
> "'__builtin_is_within_lifetime' with containing object not within lifetime"
> }
> + break;
> + case 6:
> + a.a = 42;
> + __builtin_start_lifetime (&b.b);
> + break;
> + case 7:
> + a.a = 42;
> + __builtin_start_lifetime (&b.b[1]); // { dg-error
> "'__builtin_is_within_lifetime' with containing object not within lifetime"
> }
> + break;
> + case 8:
> + __builtin_start_lifetime (&b.b[1].b[0][1]); // { dg-error
> "'__builtin_is_within_lifetime' with containing object not within lifetime"
> }
> + break;
> + case 9:
> + __builtin_start_lifetime (&b.b);
> + __builtin_start_lifetime (&b.b[1]);
> + __builtin_start_lifetime (&b.b[1].b);
> + __builtin_start_lifetime (&b.b[1].b[0]);
> + __builtin_start_lifetime (&b.b[1].b[0][1]);
> + break;
> + case 10:
> + __builtin_start_lifetime (&b.b);
> + __builtin_start_lifetime (&b.b[1]);
> + __builtin_start_lifetime (&b.b[1].b);
> + __builtin_start_lifetime (&b.b[1].b[0][1]); // { dg-error
> "'__builtin_is_within_lifetime' with containing object not within lifetime"
> }
>
Should the error say, __builtin_start_lifetime with containing object not
withing lifetime?
> + break;
> + case 11:
> + __builtin_start_lifetime (&b.b);
> + __builtin_start_lifetime (&b.b[1]);
> + __builtin_start_lifetime (&b.b[1].b[0]); // { dg-error
> "'__builtin_is_within_lifetime' with containing object not within lifetime"
> }
> + break;
> + case 12:
> + __builtin_start_lifetime (&b.b);
> + __builtin_start_lifetime (&b.b[1].b); // { dg-error
> "'__builtin_is_within_lifetime' with containing object not within lifetime"
> }
> + break;
> + default:
> + break;
> + }
> + return true;
> +}
> +static_assert (fH (0));
> +constexpr bool h1 = fH (1);
> +static_assert (fH (2));
> +constexpr bool h3 = fH (3);
> +static_assert (fH (4));
> +constexpr bool h5 = fH (5);
> +static_assert (fH (6));
> +constexpr bool h7 = fH (7);
> +constexpr bool h8 = fH (8);
> +static_assert (fH (9));
> +constexpr bool h10 = fH (10);
> +constexpr bool h11 = fH (11);
> +constexpr bool h12 = fH (12);
> --- a/gcc/testsuite/g++.dg/cpp26/within-lifetime8.C 2026-09-08
> 22:19:17.811118455 +0200
> +++ b/gcc/testsuite/g++.dg/cpp26/within-lifetime8.C 2026-09-09
> 10:53:15.851010501 +0200
> @@ -59,6 +59,7 @@ foo ()
> if (std::is_within_lifetime (&l.j.i[1].h.d[0].a))
> return __LINE__;
> std::start_lifetime (l.j);
> + std::start_lifetime (l.j.i);
> std::start_lifetime (l.j.i[1]);
> if (!std::is_within_lifetime (&l.j.i[1]))
> return __LINE__;
>
>
> Jakub
>
>