On Wed, Jan 22, 2025 at 04:19:38PM -0500, Jason Merrill wrote:
> > @@ -4482,6 +4536,7 @@ emit_partial_init_fini_fn (bool initp, u
> >         /* Do one initialization or destruction.  */
> >         one_static_initialization_or_destruction (initp, decl, init);
> >       }
> > +  decomp_finalize_var_list (sl, save_stmts_are_full_exprs_p);
> >     if (omp_target)
> >       {
> > @@ -4510,6 +4565,7 @@ prune_vars_needing_no_initialization (tr
> >   {
> >     tree *var = vars;
> >     tree result = NULL_TREE;
> > +  bool clear_nonbase = false;
> >     while (*var)
> >       {
> > @@ -4517,6 +4573,20 @@ prune_vars_needing_no_initialization (tr
> >         tree decl = TREE_VALUE (t);
> >         tree init = TREE_PURPOSE (t);
> > +      if (STATIC_INIT_DECOMP_BASE_P (t)
> > +     && result != NULL_TREE
> > +     && STATIC_INIT_DECOMP_NONBASE_P (result))
> > +   clear_nonbase = true;
> > +      else if (clear_nonbase && !STATIC_INIT_DECOMP_BASE_P (t))
> > +   {
> 
> I don't see how we can ever get here...
> 
> > +     clear_nonbase = false;
> > +     for (tree r = result; r; r = TREE_CHAIN (r))
> > +       if (STATIC_INIT_DECOMP_NONBASE_P (r))
> > +         STATIC_INIT_DECOMP_NONBASE_P (r) = 0;
> > +       else
> > +         break;
> > +   }
> > +
> >         /* Deal gracefully with error.  */
> >         if (error_operand_p (decl))
> >     {
> > @@ -4544,6 +4614,28 @@ prune_vars_needing_no_initialization (tr
> >       continue;
> >     }
> > +      clear_nonbase = false;
> 
> ...if we always clear the flag here?  What situation is the code in this
> function trying to correct?  This needs a lot more rationale.  Would it be
> simpler to do a second loop over result after the main loop?

The purpose of the function is to prune some vars for whatever reason.
What cp_finish_decl ensures is that if there are namespace scope structured
bindings that need the CWG2867 special handling there is one or more
STATIC_INIT_DECOMP_BASE_P {static,tls}_aggregates TREE_LIST (where those
correspond to either the artificial base variable or some lifetime extended
helpers) followed by (note, here "followed by" really depends on whether the
list is reversed or not, e.g. for this function the argument list is reversed
and the returned one is not) one or more STATIC_INIT_DECOMP_NONBASE_P
TREE_LISTs (these are either the variables corresponding to user identifiers
and/or their lifetime extended helpers).

As there are only 2 single-bit flags, there is no clear marking which of these
base/nonbase VAR_DECLs correspond to structured binding xyz and which
correspond to another one.  Sure, if those are DECL_DECOMPOSITION_P
VAR_DECLs, we can look at base (or self if it is base), but if it is the
life extended VAR_DECLs, maybe we could guess something from the mangling,
but it becomes horrible.

So, all we have is
zero or more non-STATIC_INIT_DECOMP*P entries, followed by one or more
STATIC_INIT_DECOMP_BASE_P entries, followed by one or more
STATIC_INIT_DECOMP_NONBASE_P entries, followed by zero or more
non-STATIC_INIT_DECOMP*P entries, perhaps followed by one or more
STATIC_INIT_DECOMP_BASE_P entries etc.
As there could be zero normal entries in between, the boundary is
when STATIC_INIT_DECOMP_NONBASE_P is followed by normal or
STATIC_INIT_DECOMP_BASE_P entry.

Now if we prune some entries from this list (and sure, reverse it),
we could loose the original properties.
E.g. if the whole series of consecutive STATIC_INIT_DECOMP_BASE_P
entries is pruned and previously it was preceded and followed by
STATIC_INIT_DECOMP_NONBASE_P entries, all of sudden the two
STATIC_INIT_DECOMP_NONBASE_P sequences are indistinguishable from one
and so the cleanups of the STATIC_INIT_DECOMP_BASE_P that are kept
could be extended even over the STATIC_INIT_DECOMP_NONBASE_P initialization
over which it should not be extended.
Similarly, if the whole series of consecutive STATIC_INIT_DECOMP_NONBASE_P
entries is pruned and was in between two STATIC_INIT_DECOMP_BASE_P
sequences, in the pruned lists those might appear as something for a single
structured binding and have all cleanups extended over something it
shouldn't be.
If from entries corresponding to a single structured binding we drop
all STATIC_INIT_DECOMP_BASE_P or all STATIC_INIT_DECOMP_NONBASE_P
(or both entries), we just shouldn't have those flags set in any of the
remaining entries for that structured binding in the pruned list,
because either there are no base VAR_DECLs (so no cleanups to extend
across the non-bases) or there are no nonbase VAR_DECLs (and so the base
cleanups can just end at the end of the bases).

The clear_nonbase flag is part of this pruning.  It is not always cleared,
in particular it is not cleared in any of the cases where we prune some
entries (i.e. if (whatever) { ...; continue; }).

      if (STATIC_INIT_DECOMP_BASE_P (t)
          && result != NULL_TREE
          && STATIC_INIT_DECOMP_NONBASE_P (result))
        clear_nonbase = true;
sets it to true if we are going to process a base entry and the last
entry we've processed was nonbase.  The {static,tls}_aggregates ordering
is nonbase -> nonbase -> base and we process it in the reverse order as
well.  Now, if the current entry (t) is not pruned, clear_nonbase is set
to false again and nothing happens.  Only if we kept a
STATIC_INIT_DECOMP_NONBASE_P and then prune one or more
STATIC_INIT_DECOMP_BASE_P entries and the next one after all those pruned
is not a STATIC_INIT_DECOMP_BASE_P, we do
      else if (clear_nonbase && !STATIC_INIT_DECOMP_BASE_P (t))
        {
          clear_nonbase = false;
          for (tree r = result; r; r = TREE_CHAIN (r))
            if (STATIC_INIT_DECOMP_NONBASE_P (r))
              STATIC_INIT_DECOMP_NONBASE_P (r) = 0;
            else
              break;
        }
and in that case clear all the adjacent STATIC_INIT_DECOMP_NONBASE_P
from the non-pruned list.  So, the clear_nonbase stuff is about
dropping STATIC_INIT_DECOMP_NONBASE_P flags in case all the
STATIC_INIT_DECOMP_BASE_P entries were pruned.

I've added an extra comment and tweaked another one.

> > +      else if (!STATIC_INIT_DECOMP_BASE_P (t)
> > +          && !STATIC_INIT_DECOMP_NONBASE_P (t)
> > +          && result != NULL_TREE
> > +          && STATIC_INIT_DECOMP_NONBASE_P (result))
> > +   {
> > +     for (tree r = result; r; r = TREE_CHAIN (r))
> > +       if (STATIC_INIT_DECOMP_NONBASE_P (r))
> > +         STATIC_INIT_DECOMP_NONBASE_P (r) = 0;
> > +       else
> > +         break;
> > +   }

Now that I read it once again, I think the above chunk is not needed.
Because, in the original chain, STATIC_INIT_DECOMP_NONBASE_P
should have been always followed by STATIC_INIT_DECOMP_NONBASE_P or
STATIC_INIT_DECOMP_BASE_P, so if we have a normal non-pruned elt
and the previous non-pruned elt is STATIC_INIT_DECOMP_NONBASE_P,
it means we had to prune at least one STATIC_INIT_DECOMP_BASE_P
in between, so clear_nonbase should have been set and not cleared
when it was pruned and then we should have encountered with clear_nonbase
this t (or something before it that didn't have STATIC_INIT_DECOMP_BASE_P
set and should have cleared the flags already).
So I've dropped this chunk.

Note, unfortunately it is hard to come up with a testcase that actually
prunes something on purpose...

> > @@ -4560,12 +4652,19 @@ prune_vars_needing_no_initialization (tr
> >   void
> >   partition_vars_for_init_fini (tree var_list, priority_map_t *(&parts)[4])
> >   {
> > +  unsigned priority = 0;
> > +  unsigned decomp_state = 0;
> 
> How about enum { none, base, nonbase } instead of unsigned?

Sure, adjusted in the lightly tested patch below (also with the extra
comments and one chunk removed).

2025-01-23  Jakub Jelinek  <ja...@redhat.com>

        PR c++/115769
gcc/cp/
        * cp-tree.h (STATIC_INIT_DECOMP_BASE_P): Define.
        (STATIC_INIT_DECOMP_NONBASE_P): Define.
        * decl.cc (cp_finish_decl): Mark nodes in {static,tls}_aggregates
        with 
        * decl2.cc (decomp_handle_one_var, decomp_finalize_var_list): New
        functions.
        (emit_partial_init_fini_fn): Use them.
        (prune_vars_needing_no_initialization): Clear
        STATIC_INIT_DECOMP_*BASE_P flags if needed.
        (partition_vars_for_init_fini): Use same priority for
        consecutive STATIC_INIT_DECOMP_*BASE_P vars and propagate
        those flags to new TREE_LISTs when possible.  Formatting fix.
        (handle_tls_init): Use decomp_handle_one_var and
        decomp_finalize_var_list functions.
gcc/testsuite/
        * g++.dg/DRs/dr2867-5.C: New test.
        * g++.dg/DRs/dr2867-6.C: New test.
        * g++.dg/DRs/dr2867-7.C: New test.
        * g++.dg/DRs/dr2867-8.C: New test.

--- gcc/cp/cp-tree.h.jj 2024-09-07 09:31:20.601484156 +0200
+++ gcc/cp/cp-tree.h    2024-09-09 15:53:44.924112247 +0200
@@ -470,6 +470,7 @@ extern GTY(()) tree cp_global_trees[CPTI
       BASELINK_FUNCTIONS_MAYBE_INCOMPLETE_P (in BASELINK)
       BIND_EXPR_VEC_DTOR (in BIND_EXPR)
       ATOMIC_CONSTR_EXPR_FROM_CONCEPT_P (in ATOMIC_CONSTR)
+      STATIC_INIT_DECOMP_BASE_P (in the TREE_LIST for {static,tls}_aggregates)
    2: IDENTIFIER_KIND_BIT_2 (in IDENTIFIER_NODE)
       ICS_THIS_FLAG (in _CONV)
       DECL_INITIALIZED_BY_CONSTANT_EXPRESSION_P (in VAR_DECL)
@@ -489,6 +490,8 @@ extern GTY(()) tree cp_global_trees[CPTI
       IMPLICIT_CONV_EXPR_BRACED_INIT (in IMPLICIT_CONV_EXPR)
       PACK_EXPANSION_AUTO_P (in *_PACK_EXPANSION)
       contract_semantic (in ASSERTION_, PRECONDITION_, POSTCONDITION_STMT)
+      STATIC_INIT_DECOMP_NONBASE_P (in the TREE_LIST
+                                   for {static,tls}_aggregates)
    3: IMPLICIT_RVALUE_P (in NON_LVALUE_EXPR or STATIC_CAST_EXPR)
       ICS_BAD_FLAG (in _CONV)
       FN_TRY_BLOCK_P (in TRY_BLOCK)
@@ -5947,6 +5950,21 @@ extern bool defer_mangling_aliases;
 
 extern bool flag_noexcept_type;
 
+/* True if this TREE_LIST in {static,tls}_aggregates is a for dynamic
+   initialization of namespace scope structured binding base or related
+   extended ref init temps.  Temporaries from the initialization of
+   STATIC_INIT_DECOMP_BASE_P dynamic initializers should be destroyed only
+   after the last STATIC_INIT_DECOMP_NONBASE_P dynamic initializer following
+   it.  */
+#define STATIC_INIT_DECOMP_BASE_P(NODE) \
+  TREE_LANG_FLAG_1 (TREE_LIST_CHECK (NODE))
+
+/* True if this TREE_LIST in {static,tls}_aggregates is a for dynamic
+   initialization of namespace scope structured binding non-base
+   variable using get.  */
+#define STATIC_INIT_DECOMP_NONBASE_P(NODE) \
+  TREE_LANG_FLAG_2 (TREE_LIST_CHECK (NODE))
+
 /* A list of namespace-scope objects which have constructors or
    destructors which reside in the global scope.  The decl is stored
    in the TREE_VALUE slot and the initializer is stored in the
--- gcc/cp/decl.cc.jj   2024-09-09 11:50:07.146394047 +0200
+++ gcc/cp/decl.cc      2024-09-09 17:16:26.459094150 +0200
@@ -8485,6 +8485,7 @@ cp_finish_decl (tree decl, tree init, bo
   bool var_definition_p = false;
   tree auto_node;
   auto_vec<tree> extra_cleanups;
+  tree aggregates1 = NULL_TREE;
   struct decomp_cleanup {
     tree decl;
     cp_decomp *&decomp;
@@ -8872,7 +8873,16 @@ cp_finish_decl (tree decl, tree init, bo
        }
 
       if (decomp)
-       cp_maybe_mangle_decomp (decl, decomp);
+       {
+         cp_maybe_mangle_decomp (decl, decomp);
+         if (TREE_STATIC (decl) && !DECL_FUNCTION_SCOPE_P (decl))
+           {
+             if (CP_DECL_THREAD_LOCAL_P (decl))
+               aggregates1 = tls_aggregates;
+             else
+               aggregates1 = static_aggregates;
+           }
+       }
 
       /* If this is a local variable that will need a mangled name,
         register it now.  We must do this before processing the
@@ -9210,6 +9220,32 @@ cp_finish_decl (tree decl, tree init, bo
   if (decomp_init)
     add_stmt (decomp_init);
 
+  if (decomp
+      && var_definition_p
+      && TREE_STATIC (decl)
+      && !DECL_FUNCTION_SCOPE_P (decl))
+    {
+      tree &aggregates3 = (CP_DECL_THREAD_LOCAL_P (decl)
+                          ? tls_aggregates : static_aggregates);
+      tree aggregates2 = aggregates3;
+      if (aggregates2 != aggregates1)
+       {
+         cp_finish_decomp (decl, decomp);
+         decomp = NULL;
+         if (aggregates3 != aggregates2)
+           {
+             /* If there are dynamic initializers for the structured
+                binding base or associated extended ref temps and also
+                dynamic initializers for the structured binding non-base
+                vars, mark them.  */
+             for (tree t = aggregates3; t != aggregates2; t = TREE_CHAIN (t))
+               STATIC_INIT_DECOMP_NONBASE_P (t) = 1;
+             for (tree t = aggregates2; t != aggregates1; t = TREE_CHAIN (t))
+               STATIC_INIT_DECOMP_BASE_P (t) = 1;
+           }
+       }
+    }
+
   if (was_readonly)
     TREE_READONLY (decl) = 1;
 
--- gcc/cp/decl2.cc.jj  2025-01-23 11:10:47.867729601 +0100
+++ gcc/cp/decl2.cc     2025-01-23 15:43:33.515476018 +0100
@@ -4504,6 +4504,55 @@ one_static_initialization_or_destruction
   DECL_STATIC_FUNCTION_P (current_function_decl) = 0;
 }
 
+/* Helper function for emit_partial_init_fini_fn and handle_tls_init.
+   For structured bindings, disable stmts_are_full_exprs_p ()
+   on STATIC_INIT_DECOMP_BASE_P nodes, reenable it on the
+   first STATIC_INIT_DECOMP_NONBASE_P node and emit all the
+   STATIC_INIT_DECOMP_BASE_P and STATIC_INIT_DECOMP_NONBASE_P
+   consecutive nodes in a single STATEMENT_LIST wrapped with
+   CLEANUP_POINT_EXPR.  */
+
+static inline tree
+decomp_handle_one_var (tree node, tree sl, bool *saw_nonbase,
+                      int save_stmts_are_full_exprs_p)
+{
+  if (sl && !*saw_nonbase && STATIC_INIT_DECOMP_NONBASE_P (node))
+    {
+      *saw_nonbase = true;
+      current_stmt_tree ()->stmts_are_full_exprs_p
+       = save_stmts_are_full_exprs_p;
+    }
+  else if (sl && *saw_nonbase && !STATIC_INIT_DECOMP_NONBASE_P (node))
+    {
+      sl = pop_stmt_list (sl);
+      sl = maybe_cleanup_point_expr_void (sl);
+      add_stmt (sl);
+      sl = NULL_TREE;
+    }
+  if (sl == NULL_TREE && STATIC_INIT_DECOMP_BASE_P (node))
+    {
+      sl = push_stmt_list ();
+      *saw_nonbase = false;
+      current_stmt_tree ()->stmts_are_full_exprs_p = 0;
+    }
+  return sl;
+}
+
+/* Similarly helper called when the whole var list is processed.  */
+
+static inline void
+decomp_finalize_var_list (tree sl, int save_stmts_are_full_exprs_p)
+{
+  if (sl)
+    {
+      current_stmt_tree ()->stmts_are_full_exprs_p
+       = save_stmts_are_full_exprs_p;
+      sl = pop_stmt_list (sl);
+      sl = maybe_cleanup_point_expr_void (sl);
+      add_stmt (sl);
+    }
+}
+
 /* Generate code to do the initialization or destruction of the decls in VARS,
    a TREE_LIST of VAR_DECL with static storage duration.
    Whether initialization or destruction is performed is specified by INITP.  
*/
@@ -4533,12 +4582,17 @@ emit_partial_init_fini_fn (bool initp, u
       finish_if_stmt_cond (target_dev_p, nonhost_if_stmt);
     }
 
+  tree sl = NULL_TREE;
+  int save_stmts_are_full_exprs_p = stmts_are_full_exprs_p ();
+  bool saw_nonbase = false;
   for (tree node = vars; node; node = TREE_CHAIN (node))
     {
       tree decl = TREE_VALUE (node);
       tree init = TREE_PURPOSE (node);
-       /* We will emit 'init' twice, and it is modified in-place during
-          gimplification.  Make a copy here.  */
+      sl = decomp_handle_one_var (node, sl, &saw_nonbase,
+                                 save_stmts_are_full_exprs_p);
+      /* We will emit 'init' twice, and it is modified in-place during
+        gimplification.  Make a copy here.  */
       if (omp_target)
        {
          /* We've already emitted INIT in the host version of the ctor/dtor
@@ -4562,6 +4616,7 @@ emit_partial_init_fini_fn (bool initp, u
       /* Do one initialization or destruction.  */
       one_static_initialization_or_destruction (initp, decl, init);
     }
+  decomp_finalize_var_list (sl, save_stmts_are_full_exprs_p);
 
   if (omp_target)
     {
@@ -4590,6 +4645,7 @@ prune_vars_needing_no_initialization (tr
 {
   tree *var = vars;
   tree result = NULL_TREE;
+  bool clear_nonbase = false;
 
   while (*var)
     {
@@ -4597,6 +4653,33 @@ prune_vars_needing_no_initialization (tr
       tree decl = TREE_VALUE (t);
       tree init = TREE_PURPOSE (t);
 
+      /* cp_finish_decl will for structured bindings which since CWG2867
+        need to extend lifetime of the base across the non-base vars
+        have one or more STATIC_INIT_DECOMP_NONBASE_P elts (across whose
+        initialization lifetime should be extended) followed by one
+        or more STATIC_INIT_DECOMP_BASE_P elts (whose lifetime should be
+        extended).  If either all adjacent STATIC_INIT_DECOMP_NONBASE_P
+        or all adjacent STATIC_INIT_DECOMP_BASE_P elts are pruned, we
+        need to clear the flags on the rest, as no lifetime extension
+        is needed.
+        The following code handles the case where all the adjacent
+        STATIC_INIT_DECOMP_BASE_P elts are pruned and clears the
+        STATIC_INIT_DECOMP_NONBASE_P flag on the adjacent elts we've
+        processed.  */
+      if (STATIC_INIT_DECOMP_BASE_P (t)
+         && result != NULL_TREE
+         && STATIC_INIT_DECOMP_NONBASE_P (result))
+       clear_nonbase = true;
+      else if (clear_nonbase && !STATIC_INIT_DECOMP_BASE_P (t))
+       {
+         clear_nonbase = false;
+         for (tree r = result; r; r = TREE_CHAIN (r))
+           if (STATIC_INIT_DECOMP_NONBASE_P (r))
+             STATIC_INIT_DECOMP_NONBASE_P (r) = 0;
+           else
+             break;
+       }
+
       /* Deal gracefully with error.  */
       if (error_operand_p (decl))
        {
@@ -4624,6 +4707,17 @@ prune_vars_needing_no_initialization (tr
          continue;
        }
 
+      clear_nonbase = false;
+      /* T will not be pruned.  If it is STATIC_INIT_DECOMP_BASE_P
+        and it is not preceded by STATIC_INIT_DECOMP_*BASE_P, it means
+        all the STATIC_INIT_DECOMP_NONBASE_P entries before it have
+        been pruned, so clear STATIC_INIT_DECOMP_BASE_P.  */
+      if (STATIC_INIT_DECOMP_BASE_P (t)
+         && !(result != NULL_TREE
+              && (STATIC_INIT_DECOMP_BASE_P (result)
+                  || STATIC_INIT_DECOMP_NONBASE_P (result))))
+       STATIC_INIT_DECOMP_BASE_P (t) = 0;
+
       /* This variable is going to need initialization and/or
         finalization, so we add it to the list.  */
       *var = TREE_CHAIN (t);
@@ -4640,12 +4734,19 @@ prune_vars_needing_no_initialization (tr
 void
 partition_vars_for_init_fini (tree var_list, priority_map_t *(&parts)[4])
 {
+  unsigned priority = 0;
+  enum { none, base, nonbase } decomp_state = none;
   for (auto node = var_list; node; node = TREE_CHAIN (node))
     {
       tree decl = TREE_VALUE (node);
       tree init = TREE_PURPOSE (node);
       bool has_cleanup = !TYPE_HAS_TRIVIAL_DESTRUCTOR (TREE_TYPE (decl));
-      unsigned priority = DECL_EFFECTIVE_INIT_PRIORITY (decl);
+      if (decomp_state == base && STATIC_INIT_DECOMP_NONBASE_P (node))
+       decomp_state = nonbase;
+      else if (decomp_state == nonbase && !STATIC_INIT_DECOMP_NONBASE_P (node))
+       decomp_state = none;
+      if (decomp_state == none)
+       priority = DECL_EFFECTIVE_INIT_PRIORITY (decl);
 
       if (init || (flag_use_cxa_atexit && has_cleanup))
        {
@@ -4654,6 +4755,34 @@ partition_vars_for_init_fini (tree var_l
            parts[true] = priority_map_t::create_ggc ();
          auto &slot = parts[true]->get_or_insert (priority);
          slot = tree_cons (init, decl, slot);
+         if (init
+             && STATIC_INIT_DECOMP_BASE_P (node)
+             && decomp_state == none)
+           {
+             /* If one or more STATIC_INIT_DECOMP_BASE_P with at least
+                one init is followed by at least one
+                STATIC_INIT_DECOMP_NONBASE_P with init, mark it in the
+                resulting chain as well.  */
+             for (tree n = TREE_CHAIN (node); n; n = TREE_CHAIN (n))
+               if (STATIC_INIT_DECOMP_BASE_P (n))
+                 continue;
+               else if (STATIC_INIT_DECOMP_NONBASE_P (n))
+                 {
+                   if (TREE_PURPOSE (n))
+                     {
+                       decomp_state = base;
+                       break;
+                     }
+                   else
+                     continue;
+                 }
+               else
+                 break;
+           }
+         if (init && decomp_state == base)
+           STATIC_INIT_DECOMP_BASE_P (slot) = 1;
+         else if (decomp_state == nonbase)
+           STATIC_INIT_DECOMP_NONBASE_P (slot) = 1;
        }
 
       if (!flag_use_cxa_atexit && has_cleanup)
@@ -4666,7 +4795,7 @@ partition_vars_for_init_fini (tree var_l
        }
 
       if (flag_openmp
-          && lookup_attribute ("omp declare target", DECL_ATTRIBUTES (decl)))
+         && lookup_attribute ("omp declare target", DECL_ATTRIBUTES (decl)))
        {
          priority_map_t **omp_parts = parts + 2;
 
@@ -4677,6 +4806,10 @@ partition_vars_for_init_fini (tree var_l
                omp_parts[true] = priority_map_t::create_ggc ();
              auto &slot = omp_parts[true]->get_or_insert (priority);
              slot = tree_cons (init, decl, slot);
+             if (init && decomp_state == base)
+               STATIC_INIT_DECOMP_BASE_P (slot) = 1;
+             else if (decomp_state == nonbase)
+               STATIC_INIT_DECOMP_NONBASE_P (slot) = 1;
            }
 
          if (!flag_use_cxa_atexit && has_cleanup)
@@ -5063,10 +5196,15 @@ handle_tls_init (void)
   finish_expr_stmt (cp_build_modify_expr (loc, guard, NOP_EXPR,
                                          boolean_true_node,
                                          tf_warning_or_error));
+  tree sl = NULL_TREE;
+  int save_stmts_are_full_exprs_p = stmts_are_full_exprs_p ();
+  bool saw_nonbase = false;
   for (; vars; vars = TREE_CHAIN (vars))
     {
       tree var = TREE_VALUE (vars);
       tree init = TREE_PURPOSE (vars);
+      sl = decomp_handle_one_var (vars, sl, &saw_nonbase,
+                                 save_stmts_are_full_exprs_p);
       one_static_initialization_or_destruction (/*initp=*/true, var, init);
 
       /* Output init aliases even with -fno-extern-tls-init.  */
@@ -5081,6 +5219,7 @@ handle_tls_init (void)
          gcc_assert (alias != NULL);
        }
     }
+  decomp_finalize_var_list (sl, save_stmts_are_full_exprs_p);
 
   finish_then_clause (if_stmt);
   finish_if_stmt (if_stmt);
--- gcc/testsuite/g++.dg/DRs/dr2867-5.C.jj      2024-09-09 14:09:22.181185411 
+0200
+++ gcc/testsuite/g++.dg/DRs/dr2867-5.C 2024-09-10 10:44:40.859421538 +0200
@@ -0,0 +1,92 @@
+// CWG2867 - Order of initialization for structured bindings.
+// { dg-do run { target c++11 } }
+// { dg-options "" }
+
+#define assert(X) do { if (!(X)) __builtin_abort(); } while (0)
+
+namespace std {
+  template<typename T> struct tuple_size;
+  template<int, typename> struct tuple_element;
+}
+
+int a, c, d, i;
+
+struct A {
+  A () { assert (c == 3); ++c; }
+  ~A () { ++a; }
+  template <int I> int &get () const { assert (c == 5 + I); ++c; return i; }
+};
+
+template <> struct std::tuple_size <A> { static const int value = 4; };
+template <int I> struct std::tuple_element <I, A> { using type = int; };
+template <> struct std::tuple_size <const A> { static const int value = 4; };
+template <int I> struct std::tuple_element <I, const A> { using type = int; };
+
+struct B {
+  B () { assert (c >= 1 && c <= 2); ++c; }
+  ~B () { assert (c >= 9 && c <= 10); ++c; }
+};
+
+struct C {
+  constexpr C () {}
+  constexpr C (const C &) {}
+  template <int I> int &get () const { assert (d == 1 + I); ++d; return i; }
+};
+
+template <> struct std::tuple_size <C> { static const int value = 3; };
+template <int I> struct std::tuple_element <I, C> { using type = int; };
+template <> struct std::tuple_size <const C> { static const int value = 3; };
+template <int I> struct std::tuple_element <I, const C> { using type = int; };
+
+A
+foo (const B &, const B &)
+{
+  A a;
+  assert (c == 4);
+  ++c;
+  return a;
+}
+
+constexpr C
+foo (const C &, const C &)
+{
+  return C {};
+}
+
+int
+bar (int &x, int y)
+{
+  x = y;
+  return y;
+}
+
+int
+baz (int &x, int y)
+{
+  assert (x == y);
+  return y;
+}
+
+struct E {
+  ~E () { assert (a == 2); }
+};
+
+E e;
+int c1 = bar (c, 1);
+const auto &[x, y, z, w] = foo (B {}, B {});   // { dg-warning "structured 
bindings only available with" "" { target c++14_down } }
+int c2 = baz (c, 11);
+int d1 = bar (d, 1);
+const auto &[s, t, u] = foo (C {}, C {});      // { dg-warning "structured 
bindings only available with" "" { target c++14_down } }
+int d2 = baz (d, 4);
+int c3 = bar (c, 1);
+auto [x2, y2, z2, w2] = foo (B {}, B {});      // { dg-warning "structured 
bindings only available with" "" { target c++14_down } }
+int c4 = baz (c, 11);
+int d3 = bar (d, 1);
+auto [s2, t2, u2] = foo (C {}, C {});          // { dg-warning "structured 
bindings only available with" "" { target c++14_down } }
+int d4 = baz (d, 4);
+
+int
+main ()
+{
+  assert (a == 0);
+}
--- gcc/testsuite/g++.dg/DRs/dr2867-6.C.jj      2024-09-09 14:19:56.455059937 
+0200
+++ gcc/testsuite/g++.dg/DRs/dr2867-6.C 2024-09-09 14:56:22.572568526 +0200
@@ -0,0 +1,83 @@
+// CWG2867 - Order of initialization for structured bindings.
+// { dg-do run { target c++11 } }
+// { dg-options "" }
+
+#define assert(X) do { if (!(X)) __builtin_abort(); } while (0)
+
+namespace std {
+  template<typename T> struct tuple_size;
+  template<int, typename> struct tuple_element;
+}
+
+int a, c;
+
+struct C {
+  C () { assert (c >= 5 && c <= 17 && (c - 5) % 4 == 0); ++c; }
+  ~C () { assert (c >= 8 && c <= 20 && c % 4 == 0); ++c; }
+};
+
+struct D {
+  D () { assert (c >= 7 && c <= 19 && (c - 7) % 4 == 0); ++c; }
+  ~D () { assert (a % 5 != 4); ++a; }
+};
+
+struct A {
+  A () { assert (c == 3); ++c; }
+  ~A () { assert (a % 5 == 4); ++a; }
+  template <int I> D get (const C & = C{}) const { assert (c == 6 + 4 * I); 
++c; return D {}; }
+};
+
+template <> struct std::tuple_size <A> { static const int value = 4; };
+template <int I> struct std::tuple_element <I, A> { using type = D; };
+template <> struct std::tuple_size <const A> { static const int value = 4; };
+template <int I> struct std::tuple_element <I, const A> { using type = D; };
+
+struct B {
+  B () { assert (c >= 1 && c <= 2); ++c; }
+  ~B () { assert (c >= 21 && c <= 22); ++c; }
+};
+
+A
+foo (const B &, const B &)
+{
+  A a;
+  assert (c == 4);
+  ++c;
+  return a;
+}
+
+int
+bar (int &x, int y)
+{
+  x = y;
+  return y;
+}
+
+int
+baz (int &x, int y)
+{
+  assert (x == y);
+  return y;
+}
+
+struct E {
+  ~E () { assert (a == 5); }
+};
+
+E e;
+int c1 = bar (c, 1);
+// First B::B () is invoked twice, then foo called, which invokes A::A ().
+// e is reference bound to the A::A () constructed temporary.
+// Then 4 times (in increasing I):
+//   C::C () is invoked, get is called, D::D () is invoked, C::~C () is
+//   invoked.
+// After that B::~B () is invoked twice.
+// At exit time D::~D () is invoked 4 times, then A::~A ().
+const auto &[x, y, z, w] = foo (B {}, B {});   // { dg-warning "structured 
bindings only available with" "" { target c++14_down } }
+int c2 = baz (c, 23);
+
+int
+main ()
+{
+  assert (a == 0);
+}
--- gcc/testsuite/g++.dg/DRs/dr2867-7.C.jj      2024-09-10 12:08:07.770933520 
+0200
+++ gcc/testsuite/g++.dg/DRs/dr2867-7.C 2024-09-10 12:19:48.730462845 +0200
@@ -0,0 +1,98 @@
+// CWG2867 - Order of initialization for structured bindings.
+// { dg-do run { target c++11 } }
+// { dg-options "" }
+// { dg-add-options tls }
+// { dg-require-effective-target tls_runtime }
+
+#define assert(X) do { if (!(X)) __builtin_abort(); } while (0)
+
+namespace std {
+  template<typename T> struct tuple_size;
+  template<int, typename> struct tuple_element;
+}
+
+int a, c, d, i;
+
+struct A {
+  A () { assert (c == 3); ++c; }
+  ~A () { ++a; }
+  template <int I> int &get () const { assert (c == 5 + I); ++c; return i; }
+};
+
+template <> struct std::tuple_size <A> { static const int value = 4; };
+template <int I> struct std::tuple_element <I, A> { using type = int; };
+template <> struct std::tuple_size <const A> { static const int value = 4; };
+template <int I> struct std::tuple_element <I, const A> { using type = int; };
+
+struct B {
+  B () { assert (c >= 1 && c <= 2); ++c; }
+  ~B () { assert (c >= 9 && c <= 10); ++c; }
+};
+
+struct C {
+  constexpr C () {}
+  constexpr C (const C &) {}
+  template <int I> int &get () const { assert (d == 1 + I); ++d; return i; }
+};
+
+template <> struct std::tuple_size <C> { static const int value = 3; };
+template <int I> struct std::tuple_element <I, C> { using type = int; };
+template <> struct std::tuple_size <const C> { static const int value = 3; };
+template <int I> struct std::tuple_element <I, const C> { using type = int; };
+
+A
+foo (const B &, const B &)
+{
+  A a;
+  assert (c == 4);
+  ++c;
+  return a;
+}
+
+constexpr C
+foo (const C &, const C &)
+{
+  return C {};
+}
+
+int
+bar (int &x, int y)
+{
+  x = y;
+  return y;
+}
+
+int
+baz (int &x, int y)
+{
+  assert (x == y);
+  return y;
+}
+
+struct E {
+  ~E () { assert (a == 2); }
+};
+
+thread_local E e;
+thread_local int c1 = bar (c, 1);
+thread_local const auto &[x, y, z, w] = foo (B {}, B {});      // { dg-warning 
"structured bindings only available with" "" { target c++14_down } }
+thread_local int c2 = baz (c, 11);                             // { dg-warning 
"structured binding declaration can be 'thread_local' only in" "" { target 
c++17_down } .-1 }
+thread_local int d1 = bar (d, 1);
+thread_local const auto &[s, t, u] = foo (C {}, C {});         // { dg-warning 
"structured bindings only available with" "" { target c++14_down } }
+thread_local int d2 = baz (d, 4);                              // { dg-warning 
"structured binding declaration can be 'thread_local' only in" "" { target 
c++17_down } .-1 }
+thread_local int c3 = bar (c, 1);
+thread_local auto [x2, y2, z2, w2] = foo (B {}, B {});         // { dg-warning 
"structured bindings only available with" "" { target c++14_down } }
+thread_local int c4 = baz (c, 11);                             // { dg-warning 
"structured binding declaration can be 'thread_local' only in" "" { target 
c++17_down } .-1 }
+thread_local int d3 = bar (d, 1);
+thread_local auto [s2, t2, u2] = foo (C {}, C {});             // { dg-warning 
"structured bindings only available with" "" { target c++14_down } }
+thread_local int d4 = baz (d, 4);                              // { dg-warning 
"structured binding declaration can be 'thread_local' only in" "" { target 
c++17_down } .-1 }
+
+int
+main ()
+{
+  volatile int u = c1 + x + y + z + w + c2;
+  u += d1 + s + t + u + d2;
+  u += c3 + x2 + y2 + z2 + w2 + c4;
+  u += d3 + s2 + t2 + u2 + d4;
+  assert (a == 0);
+}
--- gcc/testsuite/g++.dg/DRs/dr2867-8.C.jj      2024-09-10 12:09:28.773839087 
+0200
+++ gcc/testsuite/g++.dg/DRs/dr2867-8.C 2024-09-10 12:34:06.556878510 +0200
@@ -0,0 +1,86 @@
+// CWG2867 - Order of initialization for structured bindings.
+// { dg-do run { target c++11 } }
+// { dg-options "" }
+// { dg-add-options tls }
+// { dg-require-effective-target tls_runtime }
+
+#define assert(X) do { if (!(X)) __builtin_abort(); } while (0)
+
+namespace std {
+  template<typename T> struct tuple_size;
+  template<int, typename> struct tuple_element;
+}
+
+int a, c;
+
+struct C {
+  C () { assert (c >= 5 && c <= 17 && (c - 5) % 4 == 0); ++c; }
+  ~C () { assert (c >= 8 && c <= 20 && c % 4 == 0); ++c; }
+};
+
+struct D {
+  D () { assert (c >= 7 && c <= 19 && (c - 7) % 4 == 0); ++c; }
+  ~D () { assert (a % 5 != 4); ++a; }
+};
+
+struct A {
+  A () { assert (c == 3); ++c; }
+  ~A () { assert (a % 5 == 4); ++a; }
+  template <int I> D get (const C & = C{}) const { assert (c == 6 + 4 * I); 
++c; return D {}; }
+};
+
+template <> struct std::tuple_size <A> { static const int value = 4; };
+template <int I> struct std::tuple_element <I, A> { using type = D; };
+template <> struct std::tuple_size <const A> { static const int value = 4; };
+template <int I> struct std::tuple_element <I, const A> { using type = D; };
+
+struct B {
+  B () { assert (c >= 1 && c <= 2); ++c; }
+  ~B () { assert (c >= 21 && c <= 22); ++c; }
+};
+
+A
+foo (const B &, const B &)
+{
+  A a;
+  assert (c == 4);
+  ++c;
+  return a;
+}
+
+int
+bar (int &x, int y)
+{
+  x = y;
+  return y;
+}
+
+int
+baz (int &x, int y)
+{
+  assert (x == y);
+  return y;
+}
+
+struct E {
+  ~E () { assert (a == 5); }
+};
+
+thread_local E e;
+thread_local int c1 = bar (c, 1);
+// First B::B () is invoked twice, then foo called, which invokes A::A ().
+// e is reference bound to the A::A () constructed temporary.
+// Then 4 times (in increasing I):
+//   C::C () is invoked, get is called, D::D () is invoked, C::~C () is
+//   invoked.
+// After that B::~B () is invoked twice.
+// At exit time D::~D () is invoked 4 times, then A::~A ().
+thread_local const auto &[x, y, z, w] = foo (B {}, B {});      // { dg-warning 
"structured bindings only available with" "" { target c++14_down } }
+thread_local int c2 = baz (c, 23);                             // { dg-warning 
"structured binding declaration can be 'thread_local' only in" "" { target 
c++17_down } .-1 }
+
+int
+main ()
+{
+  volatile int u = c1 + c2;
+  assert (a == 0);
+}

        Jakub


Reply via email to