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" }
+ 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