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

Reply via email to