https://gcc.gnu.org/g:f4d772356725ba7fd5e17835607d967d90cdcdaa

commit r15-7231-gf4d772356725ba7fd5e17835607d967d90cdcdaa
Author: Jakub Jelinek <ja...@redhat.com>
Date:   Mon Jan 27 17:17:17 2025 +0100

    c++: Handle CWG2867 even in namespace scope structured bindings in header 
modules [PR115769]
    
    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  <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.

Diff:
---
 gcc/cp/module.cc                              | 99 ++++++++++++++++++++++++++-
 gcc/testsuite/g++.dg/modules/dr2867-1_a.H     | 88 ++++++++++++++++++++++++
 gcc/testsuite/g++.dg/modules/dr2867-1_a.H.jj1 |  0
 gcc/testsuite/g++.dg/modules/dr2867-1_b.C     | 13 ++++
 gcc/testsuite/g++.dg/modules/dr2867-1_b.C.jj1 |  0
 gcc/testsuite/g++.dg/modules/dr2867-2_a.H     | 79 +++++++++++++++++++++
 gcc/testsuite/g++.dg/modules/dr2867-2_a.H.jj1 |  0
 gcc/testsuite/g++.dg/modules/dr2867-2_b.C     | 13 ++++
 gcc/testsuite/g++.dg/modules/dr2867-2_b.C.jj1 |  0
 gcc/testsuite/g++.dg/modules/dr2867-3_a.H     | 91 ++++++++++++++++++++++++
 gcc/testsuite/g++.dg/modules/dr2867-3_a.H.jj1 |  0
 gcc/testsuite/g++.dg/modules/dr2867-3_b.C     | 19 +++++
 gcc/testsuite/g++.dg/modules/dr2867-3_b.C.jj1 |  0
 gcc/testsuite/g++.dg/modules/dr2867-4_a.H     | 82 ++++++++++++++++++++++
 gcc/testsuite/g++.dg/modules/dr2867-4_a.H.jj1 |  0
 gcc/testsuite/g++.dg/modules/dr2867-4_b.C     | 16 +++++
 gcc/testsuite/g++.dg/modules/dr2867-4_b.C.jj1 |  0
 17 files changed, 499 insertions(+), 1 deletion(-)

diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
index c89834c1abdf..312ff6687508 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -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
index 000000000000..0d76407815d3
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/dr2867-1_a.H
@@ -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
index 000000000000..e69de29bb2d1
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
index 000000000000..f31f7abdb4f2
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/dr2867-1_b.C
@@ -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
index 000000000000..e69de29bb2d1
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
index 000000000000..f9cb388de1cb
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/dr2867-2_a.H
@@ -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
index 000000000000..e69de29bb2d1
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
index 000000000000..2de9f19bdf78
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/dr2867-2_b.C
@@ -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
index 000000000000..e69de29bb2d1
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
index 000000000000..50d182b802f8
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/dr2867-3_a.H
@@ -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
index 000000000000..e69de29bb2d1
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
index 000000000000..261ee3cbc092
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/dr2867-3_b.C
@@ -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
index 000000000000..e69de29bb2d1
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
index 000000000000..e5eba5518c99
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/dr2867-4_a.H
@@ -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
index 000000000000..e69de29bb2d1
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
index 000000000000..5fbf4762687e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/dr2867-4_b.C
@@ -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
index 000000000000..e69de29bb2d1

Reply via email to