Hi!

The following patch extends the CWG 2867 support to function scope
static structured bindings.
The reason why I'm sending this separately is that I'm afraid it is an
ABI change (admittedly for a C++20 P1091R3 feature, but we accept it with
warning already in C++11).
The current state in both GCC and clang is that e.g. on the dr2867-3.C
bar case, there are _ZGVZ3barvEDC1x1y1z1wE and _ZGVZ3barvE1{x,y,z,w}
guard variables, _ZGVZ3barvEDC1x1y1z1wE guards the initialization of
the base (e in the standard), temporaries in that initialization are
destroyed before the guard is released, followed by acquiring
_ZGVZ3barvE1x guard, doing get for x and perhaps destroying its temporaries,
releasing that guard, acquiring _ZGVZ3barvE1y guard, etc.
So, in a multi-threaded program one thread can initialize e, another thread
x, another thread y, another thread z and another thread w.  The
initialization is still correctly sequenced, other threads wait on the
prior guard variables before they can acquire another one, but if the
e initialization creates any temporaries (other than lifetime extended
reference binding which promotes the vars also to static), I wonder if it
is ok to initialize x etc. in a different thread.

The following patch takes the easy path and just does the cp_finish_decomp
initialization inside of a CLEANUP_POINT_EXPR which initializes e,
so guarded by _ZGVZ3barvEDC1x1y1z1wE.  The ABI change is that if some
other thread in different compiler compiled code initializes e and
releases _ZGVZ3barvEDC1x1y1z1wE but doesn't initialize x, y, z and w,
while the current thread would expect _ZGVZ3barvEDC1x1y1z1wE to also
initialize x, y, z and w, nothing will initialize it.

Though, now that I think about it again, perhaps what we could do instead
is just make sure the _ZGVZ3barvEDC1x1y1z1wE initialization doesn't have
a CLEANUP_POINT_EXPR in it and wrap both the _ZGVZ3barvEDC1x1y1z1wE
and cp_finish_decomp created stuff into a single CLEANUP_POINT_EXPR.
That way, perhaps _ZGVZ3barvEDC1x1y1z1wE could be initialized by one thread
and _ZGVZ3barvE1x by a different, but the temporaries from 
_ZGVZ3barvEDC1x1y1z1wE
initialization would be only destructed after the _ZGVZ3barvE1w guard
was released by the thread which initialized _ZGVZ3barvEDC1x1y1z1wE.

Anyway, the following passed bootstrap/regtest on x86_64-linux and
i686-linux, but I'll try the above variant next.

2024-08-14  Jakub Jelinek  <ja...@redhat.com>

        PR c++/115769
        * decl.cc: Partially implement CWG 2867 - Order of initialization
        for structured bindings.
        (expand_static_init): Add DECOMP argument.  If true, call
        cp_finish_decomp and if needed integrate it into the guarded
        sequence wrapped with CLEANUP_POINT_EXPR.
        (cp_finish_decl): Adjust expand_static_init caller, for
        tuple cases of structured bindings at function scope pass
        non-NULL decomp and ensure cp_finish_decomp isn't called later.

        * g++.dg/DRs/dr2867-3.C: New test.
        * g++.dg/DRs/dr2867-4.C: New test.

--- gcc/cp/decl.cc.jj   2024-08-13 19:18:42.170052535 +0200
+++ gcc/cp/decl.cc      2024-08-13 21:05:42.875446138 +0200
@@ -103,7 +103,7 @@ static tree push_cp_library_fn (enum tre
 static tree build_cp_library_fn (tree, enum tree_code, tree, int);
 static void store_parm_decls (tree);
 static void initialize_local_var (tree, tree, bool);
-static void expand_static_init (tree, tree);
+static void expand_static_init (tree, tree, cp_decomp *);
 static location_t smallest_type_location (const cp_decl_specifier_seq*);
 static bool identify_goto (tree, location_t, const location_t *,
                           diagnostic_t, bool);
@@ -9121,7 +9121,15 @@ cp_finish_decl (tree decl, tree init, bo
         initializer.  It is not legal to redeclare a static data
         member, so this issue does not arise in that case.  */
       else if (var_definition_p && TREE_STATIC (decl))
-       expand_static_init (decl, init);
+       {
+         if (need_decomp_init && init && DECL_FUNCTION_SCOPE_P (decl))
+           {
+             expand_static_init (decl, init, decomp);
+             need_decomp_init = false;
+           }
+         else
+           expand_static_init (decl, init, NULL);
+       }
     }
 
   /* If a CLEANUP_STMT was created to destroy a temporary bound to a
@@ -10183,7 +10191,7 @@ register_dtor_fn (tree decl)
    and destruction of DECL.  */
 
 static void
-expand_static_init (tree decl, tree init)
+expand_static_init (tree decl, tree init, cp_decomp *decomp)
 {
   gcc_assert (VAR_P (decl));
   gcc_assert (TREE_STATIC (decl));
@@ -10214,6 +10222,8 @@ expand_static_init (tree decl, tree init
                  "initialization and destruction");
          informed = true;
        }
+      if (decomp)
+       cp_finish_decomp (decl, decomp);
       return;
     }
 
@@ -10323,11 +10333,25 @@ expand_static_init (tree decl, tree init
             variable.  Do this before calling __cxa_guard_release.  */
          init = add_stmt_to_compound (init, register_dtor_fn (decl));
 
+         if (decomp)
+           {
+             tree sl = push_stmt_list ();
+             cp_finish_decomp (decl, decomp);
+             init = add_stmt_to_compound (init, pop_stmt_list (sl));
+           }
+
          init = add_stmt_to_compound (init, build_call_n (release_fn, 1,
                                                           guard_addr));
        }
       else
        {
+         if (decomp)
+           {
+             tree sl = push_stmt_list ();
+             cp_finish_decomp (decl, decomp);
+             init = add_stmt_to_compound (init, pop_stmt_list (sl));
+           }
+
          init = add_stmt_to_compound (init, set_guard (guard));
 
          /* Use atexit to register a function for destroying this static
--- gcc/testsuite/g++.dg/DRs/dr2867-3.C.jj      2024-08-13 21:05:42.876446125 
+0200
+++ gcc/testsuite/g++.dg/DRs/dr2867-3.C 2024-08-13 21:05:42.876446125 +0200
@@ -0,0 +1,159 @@
+// 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
+foo (const int &, const int &)
+{
+  assert (false);
+}
+
+inline void
+bar ()
+{
+  c = 1;
+  static const auto &[x, y, z, w] = foo (B {}, B {});  // { dg-warning 
"structured bindings only available with" "" { target c++14_down } }
+  assert (c == 11);                                    // { dg-warning 
"structured binding declaration can be 'static' only in" "" { target c++17_down 
} .-1 }
+  ++c;
+  d = 1;
+  static const auto &[s, t, u] = foo (C {}, C {});     // { dg-warning 
"structured bindings only available with" "" { target c++14_down } }
+  assert (d == 4);                                     // { dg-warning 
"structured binding declaration can be 'static' only in" "" { target c++17_down 
} .-1 }
+}
+
+template <int N>
+inline void
+baz ()
+{
+  c = 1;
+  static const auto &[x, y, z, w] = foo (B {}, B {});  // { dg-warning 
"structured bindings only available with" "" { target c++14_down } }
+  assert (c == 11);                                    // { dg-warning 
"structured binding declaration can be 'static' only in" "" { target c++17_down 
} .-1 }
+  ++c;
+  d = 1;
+  static const auto &[s, t, u] = foo (C {}, C {});     // { dg-warning 
"structured bindings only available with" "" { target c++14_down } }
+  assert (d == 4);                                     // { dg-warning 
"structured binding declaration can be 'static' only in" "" { target c++17_down 
} .-1 }
+}
+
+template <typename T, typename U>
+inline void
+qux ()
+{
+  c = 1;
+  static const auto &[x, y, z, w] = foo (T {}, T {});  // { dg-warning 
"structured bindings only available with" "" { target c++14_down } }
+  assert (c == 11);                                    // { dg-warning 
"structured binding declaration can be 'static' only in" "" { target c++17_down 
} .-1 }
+  ++c;
+  d = 1;
+  static const auto &[s, t, u] = foo (U {}, U {});     // { dg-warning 
"structured bindings only available with" "" { target c++14_down } }
+  assert (d == 4);                                     // { dg-warning 
"structured binding declaration can be 'static' only in" "" { target c++17_down 
} .-1 }
+}
+
+inline void
+corge ()
+{
+  c = 1;
+  static auto [x, y, z, w] = foo (B {}, B {}); // { dg-warning "structured 
bindings only available with" "" { target c++14_down } }
+  assert (c == 11);                            // { dg-warning "structured 
binding declaration can be 'static' only in" "" { target c++17_down } .-1 }
+  ++c;
+  d = 1;
+  static auto [s, t, u] = foo (C {}, C {});    // { dg-warning "structured 
bindings only available with" "" { target c++14_down } }
+  assert (d == 4);                             // { dg-warning "structured 
binding declaration can be 'static' only in" "" { target c++17_down } .-1 }
+}
+
+template <int N>
+inline void
+garply ()
+{
+  c = 1;
+  static auto [x, y, z, w] = foo (B {}, B {}); // { dg-warning "structured 
bindings only available with" "" { target c++14_down } }
+  assert (c == 11);                            // { dg-warning "structured 
binding declaration can be 'static' only in" "" { target c++17_down } .-1 }
+  ++c;
+  d = 1;
+  static auto [s, t, u] = foo (C {}, C {});    // { dg-warning "structured 
bindings only available with" "" { target c++14_down } }
+  assert (d == 4);                             // { dg-warning "structured 
binding declaration can be 'static' only in" "" { target c++17_down } .-1 }
+}
+
+template <typename T, typename U>
+inline void
+freddy ()
+{
+  c = 1;
+  static auto [x, y, z, w] = foo (T {}, T {}); // { dg-warning "structured 
bindings only available with" "" { target c++14_down } }
+  assert (c == 11);                            // { dg-warning "structured 
binding declaration can be 'static' only in" "" { target c++17_down } .-1 }
+  ++c;
+  d = 1;
+  static auto [s, t, u] = foo (U {}, U {});    // { dg-warning "structured 
bindings only available with" "" { target c++14_down } }
+  assert (d == 4);                             // { dg-warning "structured 
binding declaration can be 'static' only in" "" { target c++17_down } .-1 }
+}
+
+struct E {
+  ~E () { assert (a == 6); }
+};
+
+int
+main ()
+{
+  static E e;
+  bar ();
+  assert (c == 12);
+  baz <0> ();
+  assert (c == 12);
+  qux <B, C> ();
+  assert (c == 12);
+  corge ();
+  assert (c == 12);
+  garply <42> ();
+  assert (c == 12);
+  freddy <B, C> ();
+  assert (c == 12);
+  assert (a == 0);
+}
--- gcc/testsuite/g++.dg/DRs/dr2867-4.C.jj      2024-08-13 21:05:42.876446125 
+0200
+++ gcc/testsuite/g++.dg/DRs/dr2867-4.C 2024-08-13 21:05:42.876446125 +0200
@@ -0,0 +1,108 @@
+// 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
+foo (const int &, const int &)
+{
+  assert (false);
+}
+
+inline void
+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, then the following 2 user
+  // statements.
+  // At exit time D::~D () is invoked 4 times, then A::~A (), repeated 3
+  // times.
+  static const auto &[x, y, z, w] = foo (B {}, B {});  // { dg-warning 
"structured bindings only available with" "" { target c++14_down } }
+  assert (c == 23);                                    // { dg-warning 
"structured binding declaration can be 'static' only in" "" { target c++17_down 
} .-1 }
+  ++c;
+}
+
+template <int N>
+inline void
+baz ()
+{
+  c = 1;
+  static const auto &[x, y, z, w] = foo (B {}, B {});  // { dg-warning 
"structured bindings only available with" "" { target c++14_down } }
+  assert (c == 23);                                    // { dg-warning 
"structured binding declaration can be 'static' only in" "" { target c++17_down 
} .-1 }
+  ++c;
+}
+
+template <typename T>
+inline void
+qux ()
+{
+  c = 1;
+  static const auto &[x, y, z, w] = foo (T {}, T {});  // { dg-warning 
"structured bindings only available with" "" { target c++14_down } }
+  assert (c == 23);                                    // { dg-warning 
"structured binding declaration can be 'static' only in" "" { target c++17_down 
} .-1 }
+  ++c;
+}
+
+struct E {
+  ~E () { assert (a == 15); }
+};
+
+int
+main ()
+{
+  static E e;
+  bar ();
+  assert (c == 24);
+  baz <42> ();
+  assert (c == 24);
+  qux <B> ();
+  assert (c == 24);
+  assert (a == 0);
+}

        Jakub

Reply via email to