]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
c++: Handle CWG2867 even in namespace scope structured bindings in header modules...
authorJakub Jelinek <jakub@redhat.com>
Mon, 27 Jan 2025 16:17:17 +0000 (17:17 +0100)
committerJakub Jelinek <jakub@gcc.gnu.org>
Mon, 27 Jan 2025 16:17:17 +0000 (17:17 +0100)
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
namespace scope structured binding CWG2867 patch.

2025-01-27  Jakub Jelinek  <jakub@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.

17 files changed:
gcc/cp/module.cc
gcc/testsuite/g++.dg/modules/dr2867-1_a.H [new file with mode: 0644]
gcc/testsuite/g++.dg/modules/dr2867-1_a.H.jj1 [new file with mode: 0644]
gcc/testsuite/g++.dg/modules/dr2867-1_b.C [new file with mode: 0644]
gcc/testsuite/g++.dg/modules/dr2867-1_b.C.jj1 [new file with mode: 0644]
gcc/testsuite/g++.dg/modules/dr2867-2_a.H [new file with mode: 0644]
gcc/testsuite/g++.dg/modules/dr2867-2_a.H.jj1 [new file with mode: 0644]
gcc/testsuite/g++.dg/modules/dr2867-2_b.C [new file with mode: 0644]
gcc/testsuite/g++.dg/modules/dr2867-2_b.C.jj1 [new file with mode: 0644]
gcc/testsuite/g++.dg/modules/dr2867-3_a.H [new file with mode: 0644]
gcc/testsuite/g++.dg/modules/dr2867-3_a.H.jj1 [new file with mode: 0644]
gcc/testsuite/g++.dg/modules/dr2867-3_b.C [new file with mode: 0644]
gcc/testsuite/g++.dg/modules/dr2867-3_b.C.jj1 [new file with mode: 0644]
gcc/testsuite/g++.dg/modules/dr2867-4_a.H [new file with mode: 0644]
gcc/testsuite/g++.dg/modules/dr2867-4_a.H.jj1 [new file with mode: 0644]
gcc/testsuite/g++.dg/modules/dr2867-4_b.C [new file with mode: 0644]
gcc/testsuite/g++.dg/modules/dr2867-4_b.C.jj1 [new file with mode: 0644]

index c89834c1abdf81e7dba9491cfcc36676e4cc6025..312ff6687508effd89ec121d54cd9c8ee03d1892 100644 (file)
@@ -18727,6 +18727,65 @@ module_state::write_inits (elf_out *to, depset::hash &table, unsigned *crc_ptr)
       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);
@@ -18797,16 +18856,54 @@ module_state::read_inits (unsigned count)
   dump.indent ();
 
   lazy_snum = ~0u;
+  int decomp_phase = 0;
+  tree *aggrp = NULL;
   for (unsigned ix = 0; ix != count; ix++)
     {
+      tree last = NULL_TREE;
+      if (decomp_phase)
+       last = *aggrp;
       /* Merely referencing the decl causes its initializer to be read
         and added to the correct list.  */
       tree decl = sec.tree_node ();
+      /* module_state::write_inits can add special INTEGER_CST markers in
+        between the decls.  1 means STATIC_INIT_DECOMP_BASE_P entries
+        follow in static_aggregates, 2 means STATIC_INIT_DECOMP_NONBASE_P
+        entries follow in static_aggregates, 3 means
+        STATIC_INIT_DECOMP_BASE_P entries follow in tls_aggregates,
+        4 means STATIC_INIT_DECOMP_NONBASE_P follow in tls_aggregates,
+        0 means end of STATIC_INIT_DECOMP_{,NON}BASE_P sequence.  */
+      if (tree_fits_shwi_p (decl))
+       {
+         if (sec.get_overrun ())
+           break;
+         decomp_phase = tree_to_shwi (decl);
+         if (decomp_phase)
+           {
+             aggrp = decomp_phase > 2 ? &tls_aggregates : &static_aggregates;
+             last = *aggrp;
+           }
+         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 = *aggrp;
+         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 ();
diff --git a/gcc/testsuite/g++.dg/modules/dr2867-1_a.H b/gcc/testsuite/g++.dg/modules/dr2867-1_a.H
new file mode 100644 (file)
index 0000000..0d76407
--- /dev/null
@@ -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);
+}
diff --git a/gcc/testsuite/g++.dg/modules/dr2867-1_a.H.jj1 b/gcc/testsuite/g++.dg/modules/dr2867-1_a.H.jj1
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/gcc/testsuite/g++.dg/modules/dr2867-1_b.C b/gcc/testsuite/g++.dg/modules/dr2867-1_b.C
new file mode 100644 (file)
index 0000000..f31f7ab
--- /dev/null
@@ -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);
+}
diff --git a/gcc/testsuite/g++.dg/modules/dr2867-1_b.C.jj1 b/gcc/testsuite/g++.dg/modules/dr2867-1_b.C.jj1
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/gcc/testsuite/g++.dg/modules/dr2867-2_a.H b/gcc/testsuite/g++.dg/modules/dr2867-2_a.H
new file mode 100644 (file)
index 0000000..f9cb388
--- /dev/null
@@ -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);
+}
diff --git a/gcc/testsuite/g++.dg/modules/dr2867-2_a.H.jj1 b/gcc/testsuite/g++.dg/modules/dr2867-2_a.H.jj1
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/gcc/testsuite/g++.dg/modules/dr2867-2_b.C b/gcc/testsuite/g++.dg/modules/dr2867-2_b.C
new file mode 100644 (file)
index 0000000..2de9f19
--- /dev/null
@@ -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);
+}
diff --git a/gcc/testsuite/g++.dg/modules/dr2867-2_b.C.jj1 b/gcc/testsuite/g++.dg/modules/dr2867-2_b.C.jj1
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/gcc/testsuite/g++.dg/modules/dr2867-3_a.H b/gcc/testsuite/g++.dg/modules/dr2867-3_a.H
new file mode 100644 (file)
index 0000000..50d182b
--- /dev/null
@@ -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);
+}
diff --git a/gcc/testsuite/g++.dg/modules/dr2867-3_a.H.jj1 b/gcc/testsuite/g++.dg/modules/dr2867-3_a.H.jj1
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/gcc/testsuite/g++.dg/modules/dr2867-3_b.C b/gcc/testsuite/g++.dg/modules/dr2867-3_b.C
new file mode 100644 (file)
index 0000000..261ee3c
--- /dev/null
@@ -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);
+}
diff --git a/gcc/testsuite/g++.dg/modules/dr2867-3_b.C.jj1 b/gcc/testsuite/g++.dg/modules/dr2867-3_b.C.jj1
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/gcc/testsuite/g++.dg/modules/dr2867-4_a.H b/gcc/testsuite/g++.dg/modules/dr2867-4_a.H
new file mode 100644 (file)
index 0000000..e5eba55
--- /dev/null
@@ -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);
+}
diff --git a/gcc/testsuite/g++.dg/modules/dr2867-4_a.H.jj1 b/gcc/testsuite/g++.dg/modules/dr2867-4_a.H.jj1
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/gcc/testsuite/g++.dg/modules/dr2867-4_b.C b/gcc/testsuite/g++.dg/modules/dr2867-4_b.C
new file mode 100644 (file)
index 0000000..5fbf476
--- /dev/null
@@ -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);
+}
diff --git a/gcc/testsuite/g++.dg/modules/dr2867-4_b.C.jj1 b/gcc/testsuite/g++.dg/modules/dr2867-4_b.C.jj1
new file mode 100644 (file)
index 0000000..e69de29