On 1/21/25 10:52 AM, Jakub Jelinek wrote:
Hi!

On top of the
https://gcc.gnu.org/pipermail/gcc-patches/2024-September/662507.html
https://gcc.gnu.org/pipermail/gcc-patches/2024-September/662750.html
patches (where the first one implements CWG2867 for block scope static
or thread_local structured bindings and the latter for namespace scope
structured bindings; CWG2867 for automatic structured bindings is
already committed in r15-3513) the following patch implements the module
streaming of the new STATIC_INIT_DECOMP_BASE_P and
STATIC_INIT_DECOMP_NONBASE_P flags.  As I think namespace scope structured
bindings in the header modules will be pretty rare, I've tried to stream
something extra only when they actually appear, in that case it streams
extra INTEGER_CSTs which mark end of STATIC_INIT_DECOMP_*BASE_P (0),
start of STATIC_INIT_DECOMP_BASE_P for static_aggregates (1), start of
STATIC_INIT_DECOMP_NONBASE_P for static_aggregates (2) and ditto for
tls_aggregates (3 and 4).
The patch also copies with just small tweaks the testcases from the
second patch above as header modules.

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

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

        PR c++/115769
gcc/cp/
        * module.cc (module_state::write_inits): Verify
        STATIC_INIT_DECOMP_{,NON}BASE_P flags and stream changes in those
        out.
        (module_state::read_inits): Stream those flags in.
gcc/testsuite/
        * g++.dg/modules/dr2867-1_a.H: New test.
        * g++.dg/modules/dr2867-1_b.C: New test.
        * g++.dg/modules/dr2867-2_a.H: New test.
        * g++.dg/modules/dr2867-2_b.C: New test.
        * g++.dg/modules/dr2867-3_a.H: New test.
        * g++.dg/modules/dr2867-3_b.C: New test.
        * g++.dg/modules/dr2867-4_a.H: New test.
        * g++.dg/modules/dr2867-4_b.C: New test.

--- gcc/cp/module.cc.jj 2025-01-21 09:04:18.085457077 +0100
+++ gcc/cp/module.cc    2025-01-21 13:31:40.670938455 +0100
@@ -18723,6 +18723,65 @@ module_state::write_inits (elf_out *to,
        for (tree init = list; init; init = TREE_CHAIN (init))
        if (TREE_LANG_FLAG_0 (init))
          {
+           if (STATIC_INIT_DECOMP_BASE_P (init))
+             {
+               /* 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.  */
+               int phase = 0;
+               tree last = NULL_TREE;
+               for (tree init2 = TREE_CHAIN (init);
+                    init2; init2 = TREE_CHAIN (init2))
+                 {
+                   if (phase == 0 && STATIC_INIT_DECOMP_BASE_P (init2))
+                     ;
+                   else if (phase == 0
+                            && STATIC_INIT_DECOMP_NONBASE_P (init2))
+                     {
+                       phase = TREE_LANG_FLAG_0 (init2) ? 2 : 1;
+                       last = init2;
+                     }
+                   else if (IN_RANGE (phase, 1, 2)
+                            && STATIC_INIT_DECOMP_NONBASE_P (init2))
+                     {
+                       if (TREE_LANG_FLAG_0 (init2))
+                         phase = 2;
+                       last = init2;
+                     }
+                   else
+                     break;
+                 }
+               if (phase == 2)
+                 {
+                   /* In that case, add markers about it so that the
+                      STATIC_INIT_DECOMP_BASE_P and
+                      STATIC_INIT_DECOMP_NONBASE_P flags can be restored.  */
+                   sec.tree_node (build_int_cst (integer_type_node,
+                                                 2 * passes + 1));
+                   phase = 1;
+                   for (tree init2 = init; init2 != TREE_CHAIN (last);
+                        init2 = TREE_CHAIN (init2))
+                     if (TREE_LANG_FLAG_0 (init2))
+                       {
+                         tree decl = TREE_VALUE (init2);
+                         if (phase == 1
+                             && STATIC_INIT_DECOMP_NONBASE_P (init2))
+                           {
+                             sec.tree_node (build_int_cst (integer_type_node,
+                                                           2 * passes + 2));
+                             phase = 2;
+                           }
+                         dump ("Initializer:%u for %N", count, decl);
+                         sec.tree_node (decl);
+                         ++count;
+                       }
+                   sec.tree_node (integer_zero_node);
+                   init = last;
+                   continue;
+                 }
+             }
+
            tree decl = TREE_VALUE (init);
dump ("Initializer:%u for %N", count, decl);
@@ -18793,16 +18852,43 @@ module_state::read_inits (unsigned count
    dump.indent ();
lazy_snum = ~0u;
+  int decomp_phase = 0;

I wonder about making this a tree* pointing to *_aggregates so we don't need to repeat the ?: three times.

    for (unsigned ix = 0; ix != count; ix++)
      {
+      tree last = NULL_TREE;
+      if (decomp_phase)
+       last = decomp_phase > 2 ? tls_aggregates : static_aggregates;
        /* Merely referencing the decl causes its initializer to be read
         and added to the correct list.  */
        tree decl = sec.tree_node ();
+      if (tree_fits_shwi_p (decl))
+       {

Please add a comment explaining what an INTEGER_CST means here.

OK with that addition, and optionally the earlier suggestion.

+         if (sec.get_overrun ())
+           break;
+         decomp_phase = tree_to_shwi (decl);
+         if (decomp_phase)
+           last = decomp_phase > 2 ? tls_aggregates : static_aggregates;
+         decl = sec.tree_node ();
+       }
if (sec.get_overrun ())
        break;
        if (decl)
-       dump ("Initializer:%u for %N", count, decl);
+       dump ("Initializer:%u for %N", ix, decl);
+      if (decomp_phase)
+       {
+         tree init = decomp_phase > 2 ? tls_aggregates : static_aggregates;
+         gcc_assert (TREE_VALUE (init) == decl && TREE_CHAIN (init) == last);
+         if ((decomp_phase & 1) != 0)
+           STATIC_INIT_DECOMP_BASE_P (init) = 1;
+         else
+           STATIC_INIT_DECOMP_NONBASE_P (init) = 1;
+       }
+    }
+  if (decomp_phase && !sec.get_overrun ())
+    {
+      tree decl = sec.tree_node ();
+      gcc_assert (integer_zerop (decl));
      }
    lazy_snum = 0;
    post_load_processing ();
--- gcc/testsuite/g++.dg/modules/dr2867-1_a.H.jj        2025-01-21 
10:53:50.426106945 +0100
+++ gcc/testsuite/g++.dg/modules/dr2867-1_a.H   2025-01-21 10:53:50.426106945 
+0100
@@ -0,0 +1,88 @@
+// CWG2867 - Order of initialization for structured bindings.
+// { dg-additional-options -fmodule-header }
+// { dg-module-cmi {} }
+
+#define assert(X) do { if (!(X)) __builtin_abort(); } while (0)
+
+namespace std {
+  template<typename T> struct tuple_size;
+  template<int, typename> struct tuple_element;
+}
+
+extern 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; };
+
+inline A
+foo (const B &, const B &)
+{
+  A a;
+  assert (c == 4);
+  ++c;
+  return a;
+}
+
+constexpr C
+foo (const C &, const C &)
+{
+  return C {};
+}
+
+inline int
+bar (int &x, int y)
+{
+  x = y;
+  return y;
+}
+
+inline int
+baz (int &x, int y)
+{
+  assert (x == y);
+  return y;
+}
+
+struct E {
+  ~E () { assert (a == 2); }
+};
+
+namespace {
+E e;
+int c1 = bar (c, 1);
+const auto &[x, y, z, w] = foo (B {}, B {});
+int c2 = baz (c, 11);
+int d1 = bar (d, 1);
+const auto &[s, t, u] = foo (C {}, C {});
+int d2 = baz (d, 4);
+int c3 = bar (c, 1);
+auto [x2, y2, z2, w2] = foo (B {}, B {});
+int c4 = baz (c, 11);
+int d3 = bar (d, 1);
+auto [s2, t2, u2] = foo (C {}, C {});
+int d4 = baz (d, 4);
+}
--- gcc/testsuite/g++.dg/modules/dr2867-1_b.C.jj        2025-01-21 
10:53:50.426106945 +0100
+++ gcc/testsuite/g++.dg/modules/dr2867-1_b.C   2025-01-21 10:53:50.426106945 
+0100
@@ -0,0 +1,13 @@
+// CWG2867 - Order of initialization for structured bindings.
+// { dg-do run }
+// { dg-additional-options "-fmodules-ts" }
+
+import "dr2867-1_a.H";
+
+int a, c, d, i;
+
+int
+main ()
+{
+  assert (a == 0);
+}
--- gcc/testsuite/g++.dg/modules/dr2867-2_a.H.jj        2025-01-21 
13:46:28.994587192 +0100
+++ gcc/testsuite/g++.dg/modules/dr2867-2_a.H   2025-01-21 13:47:55.035390907 
+0100
@@ -0,0 +1,79 @@
+// CWG2867 - Order of initialization for structured bindings.
+// { dg-additional-options -fmodule-header }
+// { dg-module-cmi {} }
+
+#define assert(X) do { if (!(X)) __builtin_abort(); } while (0)
+
+namespace std {
+  template<typename T> struct tuple_size;
+  template<int, typename> struct tuple_element;
+}
+
+extern 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; }
+};
+
+inline A
+foo (const B &, const B &)
+{
+  A a;
+  assert (c == 4);
+  ++c;
+  return a;
+}
+
+inline int
+bar (int &x, int y)
+{
+  x = y;
+  return y;
+}
+
+inline int
+baz (int &x, int y)
+{
+  assert (x == y);
+  return y;
+}
+
+struct E {
+  ~E () { assert (a == 5); }
+};
+
+namespace {
+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 {});
+int c2 = baz (c, 23);
+}
--- gcc/testsuite/g++.dg/modules/dr2867-2_b.C.jj        2025-01-21 
13:48:38.889781164 +0100
+++ gcc/testsuite/g++.dg/modules/dr2867-2_b.C   2025-01-21 13:48:33.295858937 
+0100
@@ -0,0 +1,13 @@
+// CWG2867 - Order of initialization for structured bindings.
+// { dg-do run }
+// { dg-additional-options "-fmodules-ts" }
+
+import "dr2867-2_a.H";
+
+int a, c;
+
+int
+main ()
+{
+  assert (a == 0);
+}
--- gcc/testsuite/g++.dg/modules/dr2867-3_a.H.jj        2025-01-21 
13:53:26.897776305 +0100
+++ gcc/testsuite/g++.dg/modules/dr2867-3_a.H   2025-01-21 14:01:56.025695002 
+0100
@@ -0,0 +1,91 @@
+// CWG2867 - Order of initialization for structured bindings.
+// { dg-require-effective-target c++20 }
+// { dg-additional-options -fmodule-header }
+// { dg-module-cmi {} }
+// { 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;
+}
+
+extern 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; };
+
+inline A
+foo (const B &, const B &)
+{
+  A a;
+  assert (c == 4);
+  ++c;
+  return a;
+}
+
+constexpr C
+foo (const C &, const C &)
+{
+  return C {};
+}
+
+inline int
+bar (int &x, int y)
+{
+  x = y;
+  return y;
+}
+
+inline int
+baz (int &x, int y)
+{
+  assert (x == y);
+  return y;
+}
+
+struct E {
+  ~E () { assert (a == 2); }
+};
+
+namespace {
+thread_local E e;
+thread_local int c1 = bar (c, 1);
+thread_local const auto &[x, y, z, w] = foo (B {}, B {});
+thread_local int c2 = baz (c, 11);
+thread_local int d1 = bar (d, 1);
+thread_local const auto &[s, t, u] = foo (C {}, C {});
+thread_local int d2 = baz (d, 4);
+thread_local int c3 = bar (c, 1);
+thread_local auto [x2, y2, z2, w2] = foo (B {}, B {});
+thread_local int c4 = baz (c, 11);
+thread_local int d3 = bar (d, 1);
+thread_local auto [s2, t2, u2] = foo (C {}, C {});
+thread_local int d4 = baz (d, 4);
+}
--- gcc/testsuite/g++.dg/modules/dr2867-3_b.C.jj        2025-01-21 
13:56:51.387932118 +0100
+++ gcc/testsuite/g++.dg/modules/dr2867-3_b.C   2025-01-21 14:02:25.521284755 
+0100
@@ -0,0 +1,19 @@
+// CWG2867 - Order of initialization for structured bindings.
+// { dg-do run { target c++20 } }
+// { dg-additional-options "-fmodules-ts" }
+// { dg-add-options tls }
+// { dg-require-effective-target tls_runtime }
+
+import "dr2867-3_a.H";
+
+int a, c, d, i;
+
+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/modules/dr2867-4_a.H.jj        2025-01-21 
13:53:30.007733050 +0100
+++ gcc/testsuite/g++.dg/modules/dr2867-4_a.H   2025-01-21 14:02:08.828516934 
+0100
@@ -0,0 +1,82 @@
+// CWG2867 - Order of initialization for structured bindings.
+// { dg-require-effective-target c++20 }
+// { dg-additional-options -fmodule-header }
+// { dg-module-cmi {} }
+// { 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;
+}
+
+extern 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; }
+};
+
+inline A
+foo (const B &, const B &)
+{
+  A a;
+  assert (c == 4);
+  ++c;
+  return a;
+}
+
+inline int
+bar (int &x, int y)
+{
+  x = y;
+  return y;
+}
+
+inline int
+baz (int &x, int y)
+{
+  assert (x == y);
+  return y;
+}
+
+struct E {
+  ~E () { assert (a == 5); }
+};
+
+namespace {
+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 {});
+thread_local int c2 = baz (c, 23);
+}
--- gcc/testsuite/g++.dg/modules/dr2867-4_b.C.jj        2025-01-21 
13:59:54.693382577 +0100
+++ gcc/testsuite/g++.dg/modules/dr2867-4_b.C   2025-01-21 14:02:54.089887406 
+0100
@@ -0,0 +1,16 @@
+// CWG2867 - Order of initialization for structured bindings.
+// { dg-do run { target c++20 } }
+// { dg-additional-options "-fmodules-ts" }
+// { dg-add-options tls }
+// { dg-require-effective-target tls_runtime }
+
+import "dr2867-4_a.H";
+
+int a, c;
+
+int
+main ()
+{
+  volatile int u = c1 + c2;
+  assert (a == 0);
+}

        Jakub


Reply via email to