diff mbox series

c++: Implement for namespace statics CWG 2867 - Order of initialization for structured bindings [PR115769]

Message ID ZuCQJtnKdX4IzDI1@tucnak
State New
Headers show
Series c++: Implement for namespace statics CWG 2867 - Order of initialization for structured bindings [PR115769] | expand

Commit Message

Jakub Jelinek Sept. 10, 2024, 6:29 p.m. UTC
Hi!

The following patch on top of the
https://gcc.gnu.org/pipermail/gcc-patches/2024-September/662507.html
patch adds CWG 2867 support for namespace locals.

Those vars are just pushed into {static,tls}_aggregates chain, then
pruned from those lists, separated by priority and finally emitted into
the corresponding dynamic initialization functions.
The patch adds two flags used on the TREE_LIST nodes in those lists,
one marks the structured binding base variable and/or associated ref
extended temps, another marks the vars initialized using get methods.
The flags are preserved across the pruning, for splitting into by priority
all associated decls of a structured binding using tuple* are forced
into the same priority as the first one, and finally when actually emitting
code, CLEANUP_POINT_EXPRs are disabled in the base initializer(s) and
code from the bases and non-bases together is wrapped into a single
CLEANUP_POINT_EXPR.

Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?

Note, I haven't touched the module handling; from what I can see,
prune_vars_needing_no_initialization is destructive to the
{static,tls}_aggregates lists (keeps the list NULL at the end or if there
are errors or it contains some DECL_EXTERNAL decls, keeps in there just
those, not the actual vars that need dynamic initialization) and
the module writing is done only afterwards, so I think it could work
reasonably only if header_module_p ().  Can namespace scope structured
bindings appear in header_module_p () or !header_module_p () modules?
How would a testcase using them look like?  Especially when structured
bindings can't be extern nor templates nor inline there can be just one
definition, so the module would need to be included in a single file, no?
In any case, the patch shouldn't make the modules case any worse, it
just adds TREE_LIST flags which will not be streamed for modules and so
if one can use structured bindings in modules, possibly CWG 2867 would be
not fixed for those but nothing worse than that.

2024-09-10  Jakub Jelinek  <jakub@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.


	Jakub

Comments

Nathaniel Shead Sept. 11, 2024, 12:16 p.m. UTC | #1
On Tue, Sep 10, 2024 at 08:29:58PM +0200, Jakub Jelinek wrote:
> Hi!
> 
> The following patch on top of the
> https://gcc.gnu.org/pipermail/gcc-patches/2024-September/662507.html
> patch adds CWG 2867 support for namespace locals.
> 
> Those vars are just pushed into {static,tls}_aggregates chain, then
> pruned from those lists, separated by priority and finally emitted into
> the corresponding dynamic initialization functions.
> The patch adds two flags used on the TREE_LIST nodes in those lists,
> one marks the structured binding base variable and/or associated ref
> extended temps, another marks the vars initialized using get methods.
> The flags are preserved across the pruning, for splitting into by priority
> all associated decls of a structured binding using tuple* are forced
> into the same priority as the first one, and finally when actually emitting
> code, CLEANUP_POINT_EXPRs are disabled in the base initializer(s) and
> code from the bases and non-bases together is wrapped into a single
> CLEANUP_POINT_EXPR.
> 
> Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?
> 
> Note, I haven't touched the module handling; from what I can see,
> prune_vars_needing_no_initialization is destructive to the
> {static,tls}_aggregates lists (keeps the list NULL at the end or if there
> are errors or it contains some DECL_EXTERNAL decls, keeps in there just
> those, not the actual vars that need dynamic initialization) and
> the module writing is done only afterwards, so I think it could work
> reasonably only if header_module_p ().  Can namespace scope structured
> bindings appear in header_module_p () or !header_module_p () modules?
> How would a testcase using them look like?  Especially when structured
> bindings can't be extern nor templates nor inline there can be just one
> definition, so the module would need to be included in a single file, no?

In the header_module_p case, it is valid to have internal linkage
definitions (e.g. in an anonymous namespace), but in that case the
{static,tls}_aggregates lists should still be in place to be streamed
and everything should work as "normal".

(Note that it is 'valid' but not actually supported yet, I have a patch
series in progress to fix up all the various linkage issues.)

In the !header_module_p case, the modules streaming code doesn't use
those lists at all; namespace-scope definitions are attached to the
module TU directly and any initialization/destruction code is emitted
there.  Definitions would only be streamed if the variables are usable
in constant expressions.

So I don't think there's anything to do for modules here.

Yours,
Nathaniel

> In any case, the patch shouldn't make the modules case any worse, it
> just adds TREE_LIST flags which will not be streamed for modules and so
> if one can use structured bindings in modules, possibly CWG 2867 would be
> not fixed for those but nothing worse than that.
> 
> 2024-09-10  Jakub Jelinek  <jakub@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	2024-09-06 13:43:37.759302079 +0200
> +++ gcc/cp/decl2.cc	2024-09-10 13:04:05.475783972 +0200
> @@ -4424,6 +4424,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.  */
> @@ -4453,12 +4502,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
> @@ -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))
> +	{
> +	  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;
> +      /* Ensure that in the returned result chain if the
> +	 STATIC_INIT_DECOMP_*BASE_P flags are set, there is always
> +	 one or more STATIC_INIT_DECOMP_BASE_P TREE_LIST followed by
> +	 one or more STATIC_INIT_DECOMP_NONBASE_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;
> +      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;
> +	}
> +
>        /* This variable is going to need initialization and/or
>  	 finalization, so we add it to the list.  */
>        *var = TREE_CHAIN (t);
> @@ -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;
>    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 == 1 && STATIC_INIT_DECOMP_NONBASE_P (node))
> +	decomp_state = 2;
> +      else if (decomp_state == 2 && !STATIC_INIT_DECOMP_NONBASE_P (node))
> +	decomp_state = 0;
> +      if (!decomp_state)
> +	priority = DECL_EFFECTIVE_INIT_PRIORITY (decl);
>  
>        if (init || (flag_use_cxa_atexit && has_cleanup))
>  	{
> @@ -4574,6 +4673,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 == 0)
> +	    {
> +	      /* 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 = 1;
> +			break;
> +		      }
> +		    else
> +		      continue;
> +		  }
> +		else
> +		  break;
> +	    }
> +	  if (init && decomp_state == 1)
> +	    STATIC_INIT_DECOMP_BASE_P (slot) = 1;
> +	  else if (decomp_state == 2)
> +	    STATIC_INIT_DECOMP_NONBASE_P (slot) = 1;
>  	}
>  
>        if (!flag_use_cxa_atexit && has_cleanup)
> @@ -4586,7 +4713,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;
>  
> @@ -4597,6 +4724,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 == 1)
> +		STATIC_INIT_DECOMP_BASE_P (slot) = 1;
> +	      else if (decomp_state == 2)
> +		STATIC_INIT_DECOMP_NONBASE_P (slot) = 1;
>  	    }
>  
>  	  if (!flag_use_cxa_atexit && has_cleanup)
> @@ -4983,10 +5114,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.  */
> @@ -5001,6 +5137,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
>
Jakub Jelinek Sept. 11, 2024, 12:26 p.m. UTC | #2
On Wed, Sep 11, 2024 at 10:16:18PM +1000, Nathaniel Shead wrote:
> In the header_module_p case, it is valid to have internal linkage
> definitions (e.g. in an anonymous namespace), but in that case the
> {static,tls}_aggregates lists should still be in place to be streamed
> and everything should work as "normal".

As the patch doesn't touch the streaming of {static,tls}_aggregates
in that case, I guess that means CWG 2867 will not be fixed for those
cases (i.e. temporaries from the structured binding base initialization
will be destructed at the end of that initialization, rather than at the
end of subsequent get initializers); perhaps we should stream the
STATIC_INIT_DECOMP_*BASE_P flags say by streaming there integer_zero_node
or integer_one_node right before the decls and on streaming it back set
the flags again.  For the !header_module_p case, we'll need a testcase too
to make sure it works properly.

	Jakub
diff mbox series

Patch

--- 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	2024-09-06 13:43:37.759302079 +0200
+++ gcc/cp/decl2.cc	2024-09-10 13:04:05.475783972 +0200
@@ -4424,6 +4424,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.  */
@@ -4453,12 +4502,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
@@ -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))
+	{
+	  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;
+      /* Ensure that in the returned result chain if the
+	 STATIC_INIT_DECOMP_*BASE_P flags are set, there is always
+	 one or more STATIC_INIT_DECOMP_BASE_P TREE_LIST followed by
+	 one or more STATIC_INIT_DECOMP_NONBASE_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;
+      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;
+	}
+
       /* This variable is going to need initialization and/or
 	 finalization, so we add it to the list.  */
       *var = TREE_CHAIN (t);
@@ -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;
   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 == 1 && STATIC_INIT_DECOMP_NONBASE_P (node))
+	decomp_state = 2;
+      else if (decomp_state == 2 && !STATIC_INIT_DECOMP_NONBASE_P (node))
+	decomp_state = 0;
+      if (!decomp_state)
+	priority = DECL_EFFECTIVE_INIT_PRIORITY (decl);
 
       if (init || (flag_use_cxa_atexit && has_cleanup))
 	{
@@ -4574,6 +4673,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 == 0)
+	    {
+	      /* 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 = 1;
+			break;
+		      }
+		    else
+		      continue;
+		  }
+		else
+		  break;
+	    }
+	  if (init && decomp_state == 1)
+	    STATIC_INIT_DECOMP_BASE_P (slot) = 1;
+	  else if (decomp_state == 2)
+	    STATIC_INIT_DECOMP_NONBASE_P (slot) = 1;
 	}
 
       if (!flag_use_cxa_atexit && has_cleanup)
@@ -4586,7 +4713,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;
 
@@ -4597,6 +4724,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 == 1)
+		STATIC_INIT_DECOMP_BASE_P (slot) = 1;
+	      else if (decomp_state == 2)
+		STATIC_INIT_DECOMP_NONBASE_P (slot) = 1;
 	    }
 
 	  if (!flag_use_cxa_atexit && has_cleanup)
@@ -4983,10 +5114,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.  */
@@ -5001,6 +5137,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);
+}