Message ID | 66b0d0a6.170a0220.150af6.1381@mx.google.com |
---|---|
State | New |
Headers | show |
Series | c++/modules: Fix merging of GM entities in partitions [PR114950] | expand |
On 8/5/24 9:16 AM, Nathaniel Shead wrote: > Bootstrapped and regtested (so far just modules.exp) on > x86_64-pc-linux-gnu, OK for trunk if full regtest passes? OK. > -- >8 -- > > Currently name lookup generally seems to assume that all entities > declared within a named module (partition) are attached to said module, > which is not true for GM entities (e.g. via extern "C++"), and causes > issues with deduplication. > > This patch fixes the issue by ensuring that module attachment of a > declaration is consistently used to handling merging. Handling this > exposes some issues with deduplicating temploid friends; to resolve this > we always create the BINDING_SLOT_PARTITION slot so that we have > somewhere to place attached names (from any module). > > PR c++/114950 > > gcc/cp/ChangeLog: > > * module.cc (trees_out::decl_value): Stream bit indicating > imported temploid friends early. > (trees_in::decl_value): Use this bit with key_mergeable. > (trees_in::key_mergeable): Allow merging attached declarations > if they're imported temploid friends. > (module_state::read_cluster): Check for GM entities that may > require merging even when importing from partitions. > * name-lookup.cc (enum binding_slots): Adjust comment. > (get_fixed_binding_slot): Always create partition slot. > (name_lookup::search_namespace_only): Support binding vectors > with both partition and GM entities to dedup. > (walk_module_binding): Likewise. > (name_lookup::adl_namespace_fns): Likewise. > (set_module_binding): Likewise. > (check_module_override): Use attachment of the decl when > checking overrides rather than named_module_p. > (lookup_imported_hidden_friend): Use partition slot for finding > mergeable template bindings. > * name-lookup.h (set_module_binding): Split mod_glob_flag > parameter into separate global_p and partition_p params. > > gcc/testsuite/ChangeLog: > > * g++.dg/modules/tpl-friend-13_e.C: Adjust error message. > * g++.dg/modules/ambig-2_a.C: New test. > * g++.dg/modules/ambig-2_b.C: New test. > * g++.dg/modules/part-9_a.C: New test. > * g++.dg/modules/part-9_b.C: New test. > * g++.dg/modules/part-9_c.C: New test. > * g++.dg/modules/tpl-friend-15.h: New test. > * g++.dg/modules/tpl-friend-15_a.C: New test. > * g++.dg/modules/tpl-friend-15_b.C: New test. > * g++.dg/modules/tpl-friend-15_c.C: New test. > > Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com> > --- > gcc/cp/module.cc | 55 ++++++++++------ > gcc/cp/name-lookup.cc | 65 ++++++++++--------- > gcc/cp/name-lookup.h | 2 +- > gcc/testsuite/g++.dg/modules/ambig-2_a.C | 7 ++ > gcc/testsuite/g++.dg/modules/ambig-2_b.C | 10 +++ > gcc/testsuite/g++.dg/modules/part-9_a.C | 10 +++ > gcc/testsuite/g++.dg/modules/part-9_b.C | 10 +++ > gcc/testsuite/g++.dg/modules/part-9_c.C | 8 +++ > .../g++.dg/modules/tpl-friend-13_e.C | 4 +- > gcc/testsuite/g++.dg/modules/tpl-friend-15.h | 11 ++++ > .../g++.dg/modules/tpl-friend-15_a.C | 8 +++ > .../g++.dg/modules/tpl-friend-15_b.C | 8 +++ > .../g++.dg/modules/tpl-friend-15_c.C | 7 ++ > 13 files changed, 150 insertions(+), 55 deletions(-) > create mode 100644 gcc/testsuite/g++.dg/modules/ambig-2_a.C > create mode 100644 gcc/testsuite/g++.dg/modules/ambig-2_b.C > create mode 100644 gcc/testsuite/g++.dg/modules/part-9_a.C > create mode 100644 gcc/testsuite/g++.dg/modules/part-9_b.C > create mode 100644 gcc/testsuite/g++.dg/modules/part-9_c.C > create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-15.h > create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-15_a.C > create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-15_b.C > create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-15_c.C > > diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc > index d1607a06757..e6b569ebca5 100644 > --- a/gcc/cp/module.cc > +++ b/gcc/cp/module.cc > @@ -2955,7 +2955,8 @@ private: > public: > tree decl_container (); > tree key_mergeable (int tag, merge_kind, tree decl, tree inner, tree type, > - tree container, bool is_attached); > + tree container, bool is_attached, > + bool is_imported_temploid_friend); > unsigned binfo_mergeable (tree *); > > private: > @@ -7803,6 +7804,7 @@ trees_out::decl_value (tree decl, depset *dep) > || !TYPE_PTRMEMFUNC_P (TREE_TYPE (decl))); > > merge_kind mk = get_merge_kind (decl, dep); > + bool is_imported_temploid_friend = imported_temploid_friends->get (decl); > > if (CHECKING_P) > { > @@ -7838,13 +7840,11 @@ trees_out::decl_value (tree decl, depset *dep) > && DECL_MODULE_ATTACH_P (not_tmpl)) > is_attached = true; > > - /* But don't consider imported temploid friends as attached, > - since importers will need to merge this decl even if it was > - attached to a different module. */ > - if (imported_temploid_friends->get (decl)) > - is_attached = false; > - > bits.b (is_attached); > + > + /* Also tell the importer whether this is an imported temploid > + friend, which has implications for merging. */ > + bits.b (is_imported_temploid_friend); > } > bits.b (dep && dep->has_defn ()); > } > @@ -8021,13 +8021,12 @@ trees_out::decl_value (tree decl, depset *dep) > } > } > > - if (TREE_CODE (inner) == FUNCTION_DECL > - || TREE_CODE (inner) == TYPE_DECL) > + if (is_imported_temploid_friend) > { > /* Write imported temploid friends so that importers can reconstruct > this information on stream-in. */ > tree* slot = imported_temploid_friends->get (decl); > - tree_node (slot ? *slot : NULL_TREE); > + tree_node (*slot); > } > > bool is_typedef = false; > @@ -8106,6 +8105,7 @@ trees_in::decl_value () > { > int tag = 0; > bool is_attached = false; > + bool is_imported_temploid_friend = false; > bool has_defn = false; > unsigned mk_u = u (); > if (mk_u >= MK_hwm || !merge_kind_name[mk_u]) > @@ -8126,7 +8126,10 @@ trees_in::decl_value () > { > bits_in bits = stream_bits (); > if (!(mk & MK_template_mask) && !state->is_header ()) > - is_attached = bits.b (); > + { > + is_attached = bits.b (); > + is_imported_temploid_friend = bits.b (); > + } > > has_defn = bits.b (); > } > @@ -8231,7 +8234,7 @@ trees_in::decl_value () > parm_tag = fn_parms_init (inner); > > tree existing = key_mergeable (tag, mk, decl, inner, type, container, > - is_attached); > + is_attached, is_imported_temploid_friend); > tree existing_inner = existing; > if (existing) > { > @@ -8336,8 +8339,7 @@ trees_in::decl_value () > } > } > > - if (TREE_CODE (inner) == FUNCTION_DECL > - || TREE_CODE (inner) == TYPE_DECL) > + if (is_imported_temploid_friend) > if (tree owner = tree_node ()) > if (is_new) > imported_temploid_friends->put (decl, owner); > @@ -11174,7 +11176,8 @@ check_mergeable_decl (merge_kind mk, tree decl, tree ovl, merge_key const &key) > > tree > trees_in::key_mergeable (int tag, merge_kind mk, tree decl, tree inner, > - tree type, tree container, bool is_attached) > + tree type, tree container, bool is_attached, > + bool is_imported_temploid_friend) > { > const char *kind = "new"; > tree existing = NULL_TREE; > @@ -11316,6 +11319,7 @@ trees_in::key_mergeable (int tag, merge_kind mk, tree decl, tree inner, > > case NAMESPACE_DECL: > if (is_attached > + && !is_imported_temploid_friend How can a namespace be an imported temploid friend? > && !(state->is_module () || state->is_partition ())) > kind = "unique"; > else > @@ -11347,7 +11351,9 @@ trees_in::key_mergeable (int tag, merge_kind mk, tree decl, tree inner, > break; > > case TYPE_DECL: > - if (is_attached && !(state->is_module () || state->is_partition ()) > + if (is_attached > + && !is_imported_temploid_friend > + && !(state->is_module () || state->is_partition ()) > /* Implicit member functions can come from > anywhere. */ > && !(DECL_ARTIFICIAL (decl) > @@ -15291,6 +15297,7 @@ module_state::read_cluster (unsigned snum) > tree visible = NULL_TREE; > tree type = NULL_TREE; > bool dedup = false; > + bool global_p = false; > > /* We rely on the bindings being in the reverse order of > the resulting overload set. */ > @@ -15308,6 +15315,16 @@ module_state::read_cluster (unsigned snum) > if (sec.get_overrun ()) > break; > > + if (!global_p) > + { > + /* Check if the decl could require GM merging. */ > + tree orig = get_originating_module_decl (decl); > + tree inner = STRIP_TEMPLATE (orig); > + if (!DECL_LANG_SPECIFIC (inner) > + || !DECL_MODULE_ATTACH_P (inner)) > + global_p = true; > + } > + > if (decls && TREE_CODE (decl) == TYPE_DECL) > { > /* Stat hack. */ > @@ -15394,10 +15411,8 @@ module_state::read_cluster (unsigned snum) > break; /* Bail. */ > > dump () && dump ("Binding of %P", ns, name); > - if (!set_module_binding (ns, name, mod, > - is_header () ? -1 > - : is_module () || is_partition () ? 1 > - : 0, > + if (!set_module_binding (ns, name, mod, global_p, > + is_module () || is_partition (), > decls, type, visible)) > sec.set_overrun (); > } > diff --git a/gcc/cp/name-lookup.cc b/gcc/cp/name-lookup.cc > index 8823ab71c60..872f1af0b2e 100644 > --- a/gcc/cp/name-lookup.cc > +++ b/gcc/cp/name-lookup.cc > @@ -51,8 +51,8 @@ enum binding_slots > { > BINDING_SLOT_CURRENT, /* Slot for current TU. */ > BINDING_SLOT_GLOBAL, /* Slot for merged global module. */ > - BINDING_SLOT_PARTITION, /* Slot for merged partition entities > - (optional). */ > + BINDING_SLOT_PARTITION, /* Slot for merged partition entities or > + imported friends. */ > > /* Number of always-allocated slots. */ > BINDING_SLOTS_FIXED = BINDING_SLOT_GLOBAL + 1 > @@ -248,9 +248,10 @@ get_fixed_binding_slot (tree *slot, tree name, unsigned ix, int create) > if (!create) > return NULL; > > - /* The partition slot is only needed when we're a named > - module. */ > - bool partition_slot = named_module_p (); > + /* The partition slot is always needed, in case we have imported > + temploid friends with attachment different from the module we > + imported them from. */ > + bool partition_slot = true; > unsigned want = ((BINDING_SLOTS_FIXED + partition_slot + (create < 0) > + BINDING_VECTOR_SLOTS_PER_CLUSTER - 1) > / BINDING_VECTOR_SLOTS_PER_CLUSTER); > @@ -937,7 +938,6 @@ name_lookup::search_namespace_only (tree scope) > stat_hack, then everything was exported. */ > tree type = NULL_TREE; > > - > /* If STAT_HACK_P is false, everything is visible, and > there's no duplication possibilities. */ > if (STAT_HACK_P (bind)) > @@ -947,9 +947,9 @@ name_lookup::search_namespace_only (tree scope) > /* Do we need to engage deduplication? */ > int dup = 0; > if (MODULE_BINDING_GLOBAL_P (bind)) > - dup = 1; > - else if (MODULE_BINDING_PARTITION_P (bind)) > - dup = 2; > + dup |= 1; > + if (MODULE_BINDING_PARTITION_P (bind)) > + dup |= 2; > if (unsigned hit = dup_detect & dup) > { > if ((hit & 1 && BINDING_VECTOR_GLOBAL_DUPS_P (val)) > @@ -1275,9 +1275,9 @@ name_lookup::adl_namespace_fns (tree scope, bitmap imports) > /* Do we need to engage deduplication? */ > int dup = 0; > if (MODULE_BINDING_GLOBAL_P (bind)) > - dup = 1; > - else if (MODULE_BINDING_PARTITION_P (bind)) > - dup = 2; > + dup |= 1; > + if (MODULE_BINDING_PARTITION_P (bind)) > + dup |= 2; > if (unsigned hit = dup_detect & dup) > if ((hit & 1 && BINDING_VECTOR_GLOBAL_DUPS_P (val)) > || (hit & 2 > @@ -3758,6 +3758,9 @@ check_module_override (tree decl, tree mvec, bool hiding, > binding_cluster *cluster = BINDING_VECTOR_CLUSTER_BASE (mvec); > unsigned ix = BINDING_VECTOR_NUM_CLUSTERS (mvec); > > + tree nontmpl = STRIP_TEMPLATE (decl); > + bool attached = DECL_LANG_SPECIFIC (nontmpl) && DECL_MODULE_ATTACH_P (nontmpl); > + > if (BINDING_VECTOR_SLOTS_PER_CLUSTER == BINDING_SLOTS_FIXED) > { > cluster++; > @@ -3819,7 +3822,7 @@ check_module_override (tree decl, tree mvec, bool hiding, > { > /* Look in the appropriate mergeable decl slot. */ > tree mergeable = NULL_TREE; > - if (named_module_p ()) > + if (attached) > mergeable = BINDING_VECTOR_CLUSTER (mvec, BINDING_SLOT_PARTITION > / BINDING_VECTOR_SLOTS_PER_CLUSTER) > .slots[BINDING_SLOT_PARTITION % BINDING_VECTOR_SLOTS_PER_CLUSTER]; > @@ -3839,15 +3842,13 @@ check_module_override (tree decl, tree mvec, bool hiding, > matched: > if (match != error_mark_node) > { > - if (named_module_p ()) > + if (attached) > BINDING_VECTOR_PARTITION_DUPS_P (mvec) = true; > else > BINDING_VECTOR_GLOBAL_DUPS_P (mvec) = true; > } > > return match; > - > - > } > > /* Record DECL as belonging to the current lexical scope. Check for > @@ -4300,7 +4301,9 @@ walk_module_binding (tree binding, bitmap partitions, > cluster++; > } > > - bool maybe_dups = BINDING_VECTOR_PARTITION_DUPS_P (binding); > + /* There could be duplicate module or GMF entries. */ > + bool maybe_dups = (BINDING_VECTOR_PARTITION_DUPS_P (binding) > + || BINDING_VECTOR_GLOBAL_DUPS_P (binding)); > > for (; ix--; cluster++) > for (unsigned jx = 0; jx != BINDING_VECTOR_SLOTS_PER_CLUSTER; jx++) > @@ -4394,14 +4397,14 @@ import_module_binding (tree ns, tree name, unsigned mod, unsigned snum) > } > > /* An import of MODULE is binding NS::NAME. There should be no > - existing binding for >= MODULE. MOD_GLOB indicates whether MODULE > - is a header_unit (-1) or part of the current module (+1). VALUE > - and TYPE are the value and type bindings. VISIBLE are the value > - bindings being exported. */ > + existing binding for >= MODULE. GLOBAL_P indicates whether the > + bindings include global module entities. PARTITION_P is true if > + it is part of the current module. VALUE and TYPE are the value > + and type bindings. VISIBLE are the value bindings being exported. */ > > bool > -set_module_binding (tree ns, tree name, unsigned mod, int mod_glob, > - tree value, tree type, tree visible) > +set_module_binding (tree ns, tree name, unsigned mod, bool global_p, > + bool partition_p, tree value, tree type, tree visible) > { > if (!value) > /* Bogus BMIs could give rise to nothing to bind. */ > @@ -4419,19 +4422,19 @@ set_module_binding (tree ns, tree name, unsigned mod, int mod_glob, > return false; > > tree bind = value; > - if (type || visible != bind || mod_glob) > + if (type || visible != bind || partition_p || global_p) > { > bind = stat_hack (bind, type); > STAT_VISIBLE (bind) = visible; > - if ((mod_glob > 0 && TREE_PUBLIC (ns)) > + if ((partition_p && TREE_PUBLIC (ns)) > || (type && DECL_MODULE_EXPORT_P (type))) > STAT_TYPE_VISIBLE_P (bind) = true; > } > > - /* Note if this is this-module or global binding. */ > - if (mod_glob > 0) > + /* Note if this is this-module and/or global binding. */ > + if (partition_p) > MODULE_BINDING_PARTITION_P (bind) = true; > - else if (mod_glob < 0) > + if (global_p) > MODULE_BINDING_GLOBAL_P (bind) = true; > > *mslot = bind; > @@ -4540,10 +4543,8 @@ lookup_imported_hidden_friend (tree friend_tmpl) > || !DECL_MODULE_IMPORT_P (inner)) > return NULL_TREE; > > - /* Imported temploid friends are not considered as attached to this > - module for merging purposes. */ > - tree bind = get_mergeable_namespace_binding (current_namespace, > - DECL_NAME (inner), false); > + tree bind = get_mergeable_namespace_binding > + (current_namespace, DECL_NAME (inner), DECL_MODULE_ATTACH_P (inner)); > if (!bind) > return NULL_TREE; > > diff --git a/gcc/cp/name-lookup.h b/gcc/cp/name-lookup.h > index 5cf6ae6374a..7c4193444dd 100644 > --- a/gcc/cp/name-lookup.h > +++ b/gcc/cp/name-lookup.h > @@ -484,7 +484,7 @@ extern tree lookup_class_binding (tree ctx, tree name); > extern bool import_module_binding (tree ctx, tree name, unsigned mod, > unsigned snum); > extern bool set_module_binding (tree ctx, tree name, unsigned mod, > - int mod_glob_flag, > + bool global_p, bool partition_p, > tree value, tree type, tree visible); > extern void add_module_namespace_decl (tree ns, tree decl); > > diff --git a/gcc/testsuite/g++.dg/modules/ambig-2_a.C b/gcc/testsuite/g++.dg/modules/ambig-2_a.C > new file mode 100644 > index 00000000000..d5dcc93584a > --- /dev/null > +++ b/gcc/testsuite/g++.dg/modules/ambig-2_a.C > @@ -0,0 +1,7 @@ > +// { dg-additional-options "-fmodules-ts" } > +// { dg-module-cmi A } > + > +export module A; > + > +extern "C++" int foo (); > +extern "C++" char bar (); > diff --git a/gcc/testsuite/g++.dg/modules/ambig-2_b.C b/gcc/testsuite/g++.dg/modules/ambig-2_b.C > new file mode 100644 > index 00000000000..b94416aabbf > --- /dev/null > +++ b/gcc/testsuite/g++.dg/modules/ambig-2_b.C > @@ -0,0 +1,10 @@ > +// { dg-additional-options "-fmodules-ts" } > +// { dg-module-cmi !B } > + > +export module B; > +import A; > + > +extern "C++" int foo (); > +extern "C++" int bar (); // { dg-error "ambiguating new declaration" } > + > +// { dg-prune-output "not writing module" } > diff --git a/gcc/testsuite/g++.dg/modules/part-9_a.C b/gcc/testsuite/g++.dg/modules/part-9_a.C > new file mode 100644 > index 00000000000..dc033d301ee > --- /dev/null > +++ b/gcc/testsuite/g++.dg/modules/part-9_a.C > @@ -0,0 +1,10 @@ > +// PR c++/114950 > +// { dg-additional-options "-fmodules-ts" } > +// { dg-module-cmi M:a } > + > +module M:a; > + > +struct A {}; > +extern "C++" struct B {}; > +void f(int) {} > +extern "C++" void f(double) {} > diff --git a/gcc/testsuite/g++.dg/modules/part-9_b.C b/gcc/testsuite/g++.dg/modules/part-9_b.C > new file mode 100644 > index 00000000000..7339da22d97 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/modules/part-9_b.C > @@ -0,0 +1,10 @@ > +// PR c++/114950 > +// { dg-additional-options "-fmodules-ts" } > +// { dg-module-cmi M:b } > + > +module M:b; > + > +struct A {}; > +extern "C++" struct B {}; > +void f(int) {} > +extern "C++" void f(double) {} > diff --git a/gcc/testsuite/g++.dg/modules/part-9_c.C b/gcc/testsuite/g++.dg/modules/part-9_c.C > new file mode 100644 > index 00000000000..26ac6777b7a > --- /dev/null > +++ b/gcc/testsuite/g++.dg/modules/part-9_c.C > @@ -0,0 +1,8 @@ > +// PR c++/114950 > +// { dg-additional-options "-fmodules-ts" } > +// { dg-module-cmi M } > +// Handle merging definitions of extern "C++" decls across partitions > + > +export module M; > +import :a; > +import :b; > diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C > index afbd0a39c23..b32fd98b756 100644 > --- a/gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C > +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C > @@ -1,8 +1,8 @@ > // { dg-additional-options "-fmodules-ts" } > > // 'import X' does not correctly notice that S has already been declared. > -struct S {}; // { dg-message "previously declared" "" { xfail *-*-* } } > -template <typename> struct T {}; // { dg-message "previously declared" } > +struct S {}; // { dg-message "previous declaration" "" { xfail *-*-* } } > +template <typename> struct T {}; // { dg-message "previous declaration" } > void f() {} // { dg-message "previously declared" } > template <typename T> void g() {} // { dg-message "previously declared" } > > diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-15.h b/gcc/testsuite/g++.dg/modules/tpl-friend-15.h > new file mode 100644 > index 00000000000..e4d3fff4445 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-15.h > @@ -0,0 +1,11 @@ > +// PR c++/114950 > + > +template <typename T> > +struct A { > + friend void x(); > +}; > +template <typename T> > +struct B { > + virtual void f() { A<T> r; } > +}; > +template struct B<int>; > diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-15_a.C b/gcc/testsuite/g++.dg/modules/tpl-friend-15_a.C > new file mode 100644 > index 00000000000..04c800875f4 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-15_a.C > @@ -0,0 +1,8 @@ > +// PR c++/114950 > +// { dg-additional-options "-fmodules-ts" } > +// { dg-module-cmi M:a } > + > +module M:a; > +extern "C++" { > + #include "tpl-friend-15.h" > +} > diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-15_b.C b/gcc/testsuite/g++.dg/modules/tpl-friend-15_b.C > new file mode 100644 > index 00000000000..781882f97bc > --- /dev/null > +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-15_b.C > @@ -0,0 +1,8 @@ > +// PR c++/114950 > +// { dg-additional-options "-fmodules-ts" } > +// { dg-module-cmi M:b } > + > +module M:b; > +extern "C++" { > + #include "tpl-friend-15.h" > +} > diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-15_c.C b/gcc/testsuite/g++.dg/modules/tpl-friend-15_c.C > new file mode 100644 > index 00000000000..ced7e87d993 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-15_c.C > @@ -0,0 +1,7 @@ > +// PR c++/114950 > +// { dg-additional-options "-fmodules-ts" } > +// { dg-module-cmi M } > + > +export module M; > +import :a; > +import :b;
On Wed, Aug 07, 2024 at 04:18:47PM -0400, Jason Merrill wrote: > On 8/5/24 9:16 AM, Nathaniel Shead wrote: > > Bootstrapped and regtested (so far just modules.exp) on > > x86_64-pc-linux-gnu, OK for trunk if full regtest passes? > > OK. > > > -- >8 -- > > > > Currently name lookup generally seems to assume that all entities > > declared within a named module (partition) are attached to said module, > > which is not true for GM entities (e.g. via extern "C++"), and causes > > issues with deduplication. > > > > This patch fixes the issue by ensuring that module attachment of a > > declaration is consistently used to handling merging. Handling this > > exposes some issues with deduplicating temploid friends; to resolve this > > we always create the BINDING_SLOT_PARTITION slot so that we have > > somewhere to place attached names (from any module). > > > > PR c++/114950 > > > > gcc/cp/ChangeLog: > > > > * module.cc (trees_out::decl_value): Stream bit indicating > > imported temploid friends early. > > (trees_in::decl_value): Use this bit with key_mergeable. > > (trees_in::key_mergeable): Allow merging attached declarations > > if they're imported temploid friends. > > (module_state::read_cluster): Check for GM entities that may > > require merging even when importing from partitions. > > * name-lookup.cc (enum binding_slots): Adjust comment. > > (get_fixed_binding_slot): Always create partition slot. > > (name_lookup::search_namespace_only): Support binding vectors > > with both partition and GM entities to dedup. > > (walk_module_binding): Likewise. > > (name_lookup::adl_namespace_fns): Likewise. > > (set_module_binding): Likewise. > > (check_module_override): Use attachment of the decl when > > checking overrides rather than named_module_p. > > (lookup_imported_hidden_friend): Use partition slot for finding > > mergeable template bindings. > > * name-lookup.h (set_module_binding): Split mod_glob_flag > > parameter into separate global_p and partition_p params. > > > > gcc/testsuite/ChangeLog: > > > > * g++.dg/modules/tpl-friend-13_e.C: Adjust error message. > > * g++.dg/modules/ambig-2_a.C: New test. > > * g++.dg/modules/ambig-2_b.C: New test. > > * g++.dg/modules/part-9_a.C: New test. > > * g++.dg/modules/part-9_b.C: New test. > > * g++.dg/modules/part-9_c.C: New test. > > * g++.dg/modules/tpl-friend-15.h: New test. > > * g++.dg/modules/tpl-friend-15_a.C: New test. > > * g++.dg/modules/tpl-friend-15_b.C: New test. > > * g++.dg/modules/tpl-friend-15_c.C: New test. > > > > Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com> > > --- > > gcc/cp/module.cc | 55 ++++++++++------ > > gcc/cp/name-lookup.cc | 65 ++++++++++--------- > > gcc/cp/name-lookup.h | 2 +- > > gcc/testsuite/g++.dg/modules/ambig-2_a.C | 7 ++ > > gcc/testsuite/g++.dg/modules/ambig-2_b.C | 10 +++ > > gcc/testsuite/g++.dg/modules/part-9_a.C | 10 +++ > > gcc/testsuite/g++.dg/modules/part-9_b.C | 10 +++ > > gcc/testsuite/g++.dg/modules/part-9_c.C | 8 +++ > > .../g++.dg/modules/tpl-friend-13_e.C | 4 +- > > gcc/testsuite/g++.dg/modules/tpl-friend-15.h | 11 ++++ > > .../g++.dg/modules/tpl-friend-15_a.C | 8 +++ > > .../g++.dg/modules/tpl-friend-15_b.C | 8 +++ > > .../g++.dg/modules/tpl-friend-15_c.C | 7 ++ > > 13 files changed, 150 insertions(+), 55 deletions(-) > > create mode 100644 gcc/testsuite/g++.dg/modules/ambig-2_a.C > > create mode 100644 gcc/testsuite/g++.dg/modules/ambig-2_b.C > > create mode 100644 gcc/testsuite/g++.dg/modules/part-9_a.C > > create mode 100644 gcc/testsuite/g++.dg/modules/part-9_b.C > > create mode 100644 gcc/testsuite/g++.dg/modules/part-9_c.C > > create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-15.h > > create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-15_a.C > > create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-15_b.C > > create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-15_c.C > > > > diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc > > index d1607a06757..e6b569ebca5 100644 > > --- a/gcc/cp/module.cc > > +++ b/gcc/cp/module.cc > > @@ -2955,7 +2955,8 @@ private: > > public: > > tree decl_container (); > > tree key_mergeable (int tag, merge_kind, tree decl, tree inner, tree type, > > - tree container, bool is_attached); > > + tree container, bool is_attached, > > + bool is_imported_temploid_friend); > > unsigned binfo_mergeable (tree *); > > private: > > @@ -7803,6 +7804,7 @@ trees_out::decl_value (tree decl, depset *dep) > > || !TYPE_PTRMEMFUNC_P (TREE_TYPE (decl))); > > merge_kind mk = get_merge_kind (decl, dep); > > + bool is_imported_temploid_friend = imported_temploid_friends->get (decl); > > if (CHECKING_P) > > { > > @@ -7838,13 +7840,11 @@ trees_out::decl_value (tree decl, depset *dep) > > && DECL_MODULE_ATTACH_P (not_tmpl)) > > is_attached = true; > > - /* But don't consider imported temploid friends as attached, > > - since importers will need to merge this decl even if it was > > - attached to a different module. */ > > - if (imported_temploid_friends->get (decl)) > > - is_attached = false; > > - > > bits.b (is_attached); > > + > > + /* Also tell the importer whether this is an imported temploid > > + friend, which has implications for merging. */ > > + bits.b (is_imported_temploid_friend); > > } > > bits.b (dep && dep->has_defn ()); > > } > > @@ -8021,13 +8021,12 @@ trees_out::decl_value (tree decl, depset *dep) > > } > > } > > - if (TREE_CODE (inner) == FUNCTION_DECL > > - || TREE_CODE (inner) == TYPE_DECL) > > + if (is_imported_temploid_friend) > > { > > /* Write imported temploid friends so that importers can reconstruct > > this information on stream-in. */ > > tree* slot = imported_temploid_friends->get (decl); > > - tree_node (slot ? *slot : NULL_TREE); > > + tree_node (*slot); > > } > > bool is_typedef = false; > > @@ -8106,6 +8105,7 @@ trees_in::decl_value () > > { > > int tag = 0; > > bool is_attached = false; > > + bool is_imported_temploid_friend = false; > > bool has_defn = false; > > unsigned mk_u = u (); > > if (mk_u >= MK_hwm || !merge_kind_name[mk_u]) > > @@ -8126,7 +8126,10 @@ trees_in::decl_value () > > { > > bits_in bits = stream_bits (); > > if (!(mk & MK_template_mask) && !state->is_header ()) > > - is_attached = bits.b (); > > + { > > + is_attached = bits.b (); > > + is_imported_temploid_friend = bits.b (); > > + } > > has_defn = bits.b (); > > } > > @@ -8231,7 +8234,7 @@ trees_in::decl_value () > > parm_tag = fn_parms_init (inner); > > tree existing = key_mergeable (tag, mk, decl, inner, type, container, > > - is_attached); > > + is_attached, is_imported_temploid_friend); > > tree existing_inner = existing; > > if (existing) > > { > > @@ -8336,8 +8339,7 @@ trees_in::decl_value () > > } > > } > > - if (TREE_CODE (inner) == FUNCTION_DECL > > - || TREE_CODE (inner) == TYPE_DECL) > > + if (is_imported_temploid_friend) > > if (tree owner = tree_node ()) > > if (is_new) > > imported_temploid_friends->put (decl, owner); > > @@ -11174,7 +11176,8 @@ check_mergeable_decl (merge_kind mk, tree decl, tree ovl, merge_key const &key) > > tree > > trees_in::key_mergeable (int tag, merge_kind mk, tree decl, tree inner, > > - tree type, tree container, bool is_attached) > > + tree type, tree container, bool is_attached, > > + bool is_imported_temploid_friend) > > { > > const char *kind = "new"; > > tree existing = NULL_TREE; > > @@ -11316,6 +11319,7 @@ trees_in::key_mergeable (int tag, merge_kind mk, tree decl, tree inner, > > case NAMESPACE_DECL: > > if (is_attached > > + && !is_imported_temploid_friend > > How can a namespace be an imported temploid friend? > Cut off by context, but this is switch (TREE_CODE (container)) { default: gcc_unreachable (); case NAMESPACE_DECL: if (is_attached && !is_imported_temploid_friend && !(state->is_module () || state->is_partition ())) kind = "unique"; i.e. the NAMESPACE_DECL is referring to the container that the decl is attached to for merging purposes. > > && !(state->is_module () || state->is_partition ())) > > kind = "unique"; > > else > > @@ -11347,7 +11351,9 @@ trees_in::key_mergeable (int tag, merge_kind mk, tree decl, tree inner, > > break; > > case TYPE_DECL: > > - if (is_attached && !(state->is_module () || state->is_partition ()) > > + if (is_attached > > + && !is_imported_temploid_friend This is the one that may perhaps be unnecessary (on thinking over this again I would expect any class-scope friends to not be redeclared outside of their named module, even for imported templates?), so I'll actually re-test this patch without this hunk. > > + && !(state->is_module () || state->is_partition ()) > > /* Implicit member functions can come from > > anywhere. */ > > && !(DECL_ARTIFICIAL (decl) > > @@ -15291,6 +15297,7 @@ module_state::read_cluster (unsigned snum) > > tree visible = NULL_TREE; > > tree type = NULL_TREE; > > bool dedup = false; > > + bool global_p = false; > > /* We rely on the bindings being in the reverse order of > > the resulting overload set. */ > > @@ -15308,6 +15315,16 @@ module_state::read_cluster (unsigned snum) > > if (sec.get_overrun ()) > > break; > > + if (!global_p) > > + { > > + /* Check if the decl could require GM merging. */ > > + tree orig = get_originating_module_decl (decl); > > + tree inner = STRIP_TEMPLATE (orig); > > + if (!DECL_LANG_SPECIFIC (inner) > > + || !DECL_MODULE_ATTACH_P (inner)) > > + global_p = true; > > + } > > + > > if (decls && TREE_CODE (decl) == TYPE_DECL) > > { > > /* Stat hack. */ > > @@ -15394,10 +15411,8 @@ module_state::read_cluster (unsigned snum) > > break; /* Bail. */ > > dump () && dump ("Binding of %P", ns, name); > > - if (!set_module_binding (ns, name, mod, > > - is_header () ? -1 > > - : is_module () || is_partition () ? 1 > > - : 0, > > + if (!set_module_binding (ns, name, mod, global_p, > > + is_module () || is_partition (), > > decls, type, visible)) > > sec.set_overrun (); > > } > > diff --git a/gcc/cp/name-lookup.cc b/gcc/cp/name-lookup.cc > > index 8823ab71c60..872f1af0b2e 100644 > > --- a/gcc/cp/name-lookup.cc > > +++ b/gcc/cp/name-lookup.cc > > @@ -51,8 +51,8 @@ enum binding_slots > > { > > BINDING_SLOT_CURRENT, /* Slot for current TU. */ > > BINDING_SLOT_GLOBAL, /* Slot for merged global module. */ > > - BINDING_SLOT_PARTITION, /* Slot for merged partition entities > > - (optional). */ > > + BINDING_SLOT_PARTITION, /* Slot for merged partition entities or > > + imported friends. */ > > /* Number of always-allocated slots. */ > > BINDING_SLOTS_FIXED = BINDING_SLOT_GLOBAL + 1 > > @@ -248,9 +248,10 @@ get_fixed_binding_slot (tree *slot, tree name, unsigned ix, int create) > > if (!create) > > return NULL; > > - /* The partition slot is only needed when we're a named > > - module. */ > > - bool partition_slot = named_module_p (); > > + /* The partition slot is always needed, in case we have imported > > + temploid friends with attachment different from the module we > > + imported them from. */ > > + bool partition_slot = true; > > unsigned want = ((BINDING_SLOTS_FIXED + partition_slot + (create < 0) > > + BINDING_VECTOR_SLOTS_PER_CLUSTER - 1) > > / BINDING_VECTOR_SLOTS_PER_CLUSTER); > > @@ -937,7 +938,6 @@ name_lookup::search_namespace_only (tree scope) > > stat_hack, then everything was exported. */ > > tree type = NULL_TREE; > > - > > /* If STAT_HACK_P is false, everything is visible, and > > there's no duplication possibilities. */ > > if (STAT_HACK_P (bind)) > > @@ -947,9 +947,9 @@ name_lookup::search_namespace_only (tree scope) > > /* Do we need to engage deduplication? */ > > int dup = 0; > > if (MODULE_BINDING_GLOBAL_P (bind)) > > - dup = 1; > > - else if (MODULE_BINDING_PARTITION_P (bind)) > > - dup = 2; > > + dup |= 1; > > + if (MODULE_BINDING_PARTITION_P (bind)) > > + dup |= 2; > > if (unsigned hit = dup_detect & dup) > > { > > if ((hit & 1 && BINDING_VECTOR_GLOBAL_DUPS_P (val)) > > @@ -1275,9 +1275,9 @@ name_lookup::adl_namespace_fns (tree scope, bitmap imports) > > /* Do we need to engage deduplication? */ > > int dup = 0; > > if (MODULE_BINDING_GLOBAL_P (bind)) > > - dup = 1; > > - else if (MODULE_BINDING_PARTITION_P (bind)) > > - dup = 2; > > + dup |= 1; > > + if (MODULE_BINDING_PARTITION_P (bind)) > > + dup |= 2; > > if (unsigned hit = dup_detect & dup) > > if ((hit & 1 && BINDING_VECTOR_GLOBAL_DUPS_P (val)) > > || (hit & 2 > > @@ -3758,6 +3758,9 @@ check_module_override (tree decl, tree mvec, bool hiding, > > binding_cluster *cluster = BINDING_VECTOR_CLUSTER_BASE (mvec); > > unsigned ix = BINDING_VECTOR_NUM_CLUSTERS (mvec); > > + tree nontmpl = STRIP_TEMPLATE (decl); > > + bool attached = DECL_LANG_SPECIFIC (nontmpl) && DECL_MODULE_ATTACH_P (nontmpl); > > + > > if (BINDING_VECTOR_SLOTS_PER_CLUSTER == BINDING_SLOTS_FIXED) > > { > > cluster++; > > @@ -3819,7 +3822,7 @@ check_module_override (tree decl, tree mvec, bool hiding, > > { > > /* Look in the appropriate mergeable decl slot. */ > > tree mergeable = NULL_TREE; > > - if (named_module_p ()) > > + if (attached) > > mergeable = BINDING_VECTOR_CLUSTER (mvec, BINDING_SLOT_PARTITION > > / BINDING_VECTOR_SLOTS_PER_CLUSTER) > > .slots[BINDING_SLOT_PARTITION % BINDING_VECTOR_SLOTS_PER_CLUSTER]; > > @@ -3839,15 +3842,13 @@ check_module_override (tree decl, tree mvec, bool hiding, > > matched: > > if (match != error_mark_node) > > { > > - if (named_module_p ()) > > + if (attached) > > BINDING_VECTOR_PARTITION_DUPS_P (mvec) = true; > > else > > BINDING_VECTOR_GLOBAL_DUPS_P (mvec) = true; > > } > > return match; > > - > > - > > } > > /* Record DECL as belonging to the current lexical scope. Check for > > @@ -4300,7 +4301,9 @@ walk_module_binding (tree binding, bitmap partitions, > > cluster++; > > } > > - bool maybe_dups = BINDING_VECTOR_PARTITION_DUPS_P (binding); > > + /* There could be duplicate module or GMF entries. */ > > + bool maybe_dups = (BINDING_VECTOR_PARTITION_DUPS_P (binding) > > + || BINDING_VECTOR_GLOBAL_DUPS_P (binding)); > > for (; ix--; cluster++) > > for (unsigned jx = 0; jx != BINDING_VECTOR_SLOTS_PER_CLUSTER; jx++) > > @@ -4394,14 +4397,14 @@ import_module_binding (tree ns, tree name, unsigned mod, unsigned snum) > > } > > /* An import of MODULE is binding NS::NAME. There should be no > > - existing binding for >= MODULE. MOD_GLOB indicates whether MODULE > > - is a header_unit (-1) or part of the current module (+1). VALUE > > - and TYPE are the value and type bindings. VISIBLE are the value > > - bindings being exported. */ > > + existing binding for >= MODULE. GLOBAL_P indicates whether the > > + bindings include global module entities. PARTITION_P is true if > > + it is part of the current module. VALUE and TYPE are the value > > + and type bindings. VISIBLE are the value bindings being exported. */ > > bool > > -set_module_binding (tree ns, tree name, unsigned mod, int mod_glob, > > - tree value, tree type, tree visible) > > +set_module_binding (tree ns, tree name, unsigned mod, bool global_p, > > + bool partition_p, tree value, tree type, tree visible) > > { > > if (!value) > > /* Bogus BMIs could give rise to nothing to bind. */ > > @@ -4419,19 +4422,19 @@ set_module_binding (tree ns, tree name, unsigned mod, int mod_glob, > > return false; > > tree bind = value; > > - if (type || visible != bind || mod_glob) > > + if (type || visible != bind || partition_p || global_p) > > { > > bind = stat_hack (bind, type); > > STAT_VISIBLE (bind) = visible; > > - if ((mod_glob > 0 && TREE_PUBLIC (ns)) > > + if ((partition_p && TREE_PUBLIC (ns)) > > || (type && DECL_MODULE_EXPORT_P (type))) > > STAT_TYPE_VISIBLE_P (bind) = true; > > } > > - /* Note if this is this-module or global binding. */ > > - if (mod_glob > 0) > > + /* Note if this is this-module and/or global binding. */ > > + if (partition_p) > > MODULE_BINDING_PARTITION_P (bind) = true; > > - else if (mod_glob < 0) > > + if (global_p) > > MODULE_BINDING_GLOBAL_P (bind) = true; > > *mslot = bind; > > @@ -4540,10 +4543,8 @@ lookup_imported_hidden_friend (tree friend_tmpl) > > || !DECL_MODULE_IMPORT_P (inner)) > > return NULL_TREE; > > - /* Imported temploid friends are not considered as attached to this > > - module for merging purposes. */ > > - tree bind = get_mergeable_namespace_binding (current_namespace, > > - DECL_NAME (inner), false); > > + tree bind = get_mergeable_namespace_binding > > + (current_namespace, DECL_NAME (inner), DECL_MODULE_ATTACH_P (inner)); > > if (!bind) > > return NULL_TREE; > > diff --git a/gcc/cp/name-lookup.h b/gcc/cp/name-lookup.h > > index 5cf6ae6374a..7c4193444dd 100644 > > --- a/gcc/cp/name-lookup.h > > +++ b/gcc/cp/name-lookup.h > > @@ -484,7 +484,7 @@ extern tree lookup_class_binding (tree ctx, tree name); > > extern bool import_module_binding (tree ctx, tree name, unsigned mod, > > unsigned snum); > > extern bool set_module_binding (tree ctx, tree name, unsigned mod, > > - int mod_glob_flag, > > + bool global_p, bool partition_p, > > tree value, tree type, tree visible); > > extern void add_module_namespace_decl (tree ns, tree decl); > > diff --git a/gcc/testsuite/g++.dg/modules/ambig-2_a.C b/gcc/testsuite/g++.dg/modules/ambig-2_a.C > > new file mode 100644 > > index 00000000000..d5dcc93584a > > --- /dev/null > > +++ b/gcc/testsuite/g++.dg/modules/ambig-2_a.C > > @@ -0,0 +1,7 @@ > > +// { dg-additional-options "-fmodules-ts" } > > +// { dg-module-cmi A } > > + > > +export module A; > > + > > +extern "C++" int foo (); > > +extern "C++" char bar (); > > diff --git a/gcc/testsuite/g++.dg/modules/ambig-2_b.C b/gcc/testsuite/g++.dg/modules/ambig-2_b.C > > new file mode 100644 > > index 00000000000..b94416aabbf > > --- /dev/null > > +++ b/gcc/testsuite/g++.dg/modules/ambig-2_b.C > > @@ -0,0 +1,10 @@ > > +// { dg-additional-options "-fmodules-ts" } > > +// { dg-module-cmi !B } > > + > > +export module B; > > +import A; > > + > > +extern "C++" int foo (); > > +extern "C++" int bar (); // { dg-error "ambiguating new declaration" } > > + > > +// { dg-prune-output "not writing module" } > > diff --git a/gcc/testsuite/g++.dg/modules/part-9_a.C b/gcc/testsuite/g++.dg/modules/part-9_a.C > > new file mode 100644 > > index 00000000000..dc033d301ee > > --- /dev/null > > +++ b/gcc/testsuite/g++.dg/modules/part-9_a.C > > @@ -0,0 +1,10 @@ > > +// PR c++/114950 > > +// { dg-additional-options "-fmodules-ts" } > > +// { dg-module-cmi M:a } > > + > > +module M:a; > > + > > +struct A {}; > > +extern "C++" struct B {}; > > +void f(int) {} > > +extern "C++" void f(double) {} > > diff --git a/gcc/testsuite/g++.dg/modules/part-9_b.C b/gcc/testsuite/g++.dg/modules/part-9_b.C > > new file mode 100644 > > index 00000000000..7339da22d97 > > --- /dev/null > > +++ b/gcc/testsuite/g++.dg/modules/part-9_b.C > > @@ -0,0 +1,10 @@ > > +// PR c++/114950 > > +// { dg-additional-options "-fmodules-ts" } > > +// { dg-module-cmi M:b } > > + > > +module M:b; > > + > > +struct A {}; > > +extern "C++" struct B {}; > > +void f(int) {} > > +extern "C++" void f(double) {} > > diff --git a/gcc/testsuite/g++.dg/modules/part-9_c.C b/gcc/testsuite/g++.dg/modules/part-9_c.C > > new file mode 100644 > > index 00000000000..26ac6777b7a > > --- /dev/null > > +++ b/gcc/testsuite/g++.dg/modules/part-9_c.C > > @@ -0,0 +1,8 @@ > > +// PR c++/114950 > > +// { dg-additional-options "-fmodules-ts" } > > +// { dg-module-cmi M } > > +// Handle merging definitions of extern "C++" decls across partitions > > + > > +export module M; > > +import :a; > > +import :b; > > diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C > > index afbd0a39c23..b32fd98b756 100644 > > --- a/gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C > > +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C > > @@ -1,8 +1,8 @@ > > // { dg-additional-options "-fmodules-ts" } > > // 'import X' does not correctly notice that S has already been declared. > > -struct S {}; // { dg-message "previously declared" "" { xfail *-*-* } } > > -template <typename> struct T {}; // { dg-message "previously declared" } > > +struct S {}; // { dg-message "previous declaration" "" { xfail *-*-* } } > > +template <typename> struct T {}; // { dg-message "previous declaration" } > > void f() {} // { dg-message "previously declared" } > > template <typename T> void g() {} // { dg-message "previously declared" } > > diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-15.h b/gcc/testsuite/g++.dg/modules/tpl-friend-15.h > > new file mode 100644 > > index 00000000000..e4d3fff4445 > > --- /dev/null > > +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-15.h > > @@ -0,0 +1,11 @@ > > +// PR c++/114950 > > + > > +template <typename T> > > +struct A { > > + friend void x(); > > +}; > > +template <typename T> > > +struct B { > > + virtual void f() { A<T> r; } > > +}; > > +template struct B<int>; > > diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-15_a.C b/gcc/testsuite/g++.dg/modules/tpl-friend-15_a.C > > new file mode 100644 > > index 00000000000..04c800875f4 > > --- /dev/null > > +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-15_a.C > > @@ -0,0 +1,8 @@ > > +// PR c++/114950 > > +// { dg-additional-options "-fmodules-ts" } > > +// { dg-module-cmi M:a } > > + > > +module M:a; > > +extern "C++" { > > + #include "tpl-friend-15.h" > > +} > > diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-15_b.C b/gcc/testsuite/g++.dg/modules/tpl-friend-15_b.C > > new file mode 100644 > > index 00000000000..781882f97bc > > --- /dev/null > > +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-15_b.C > > @@ -0,0 +1,8 @@ > > +// PR c++/114950 > > +// { dg-additional-options "-fmodules-ts" } > > +// { dg-module-cmi M:b } > > + > > +module M:b; > > +extern "C++" { > > + #include "tpl-friend-15.h" > > +} > > diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-15_c.C b/gcc/testsuite/g++.dg/modules/tpl-friend-15_c.C > > new file mode 100644 > > index 00000000000..ced7e87d993 > > --- /dev/null > > +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-15_c.C > > @@ -0,0 +1,7 @@ > > +// PR c++/114950 > > +// { dg-additional-options "-fmodules-ts" } > > +// { dg-module-cmi M } > > + > > +export module M; > > +import :a; > > +import :b; >
On 8/7/24 7:22 PM, Nathaniel Shead wrote: > On Wed, Aug 07, 2024 at 04:18:47PM -0400, Jason Merrill wrote: >> On 8/5/24 9:16 AM, Nathaniel Shead wrote: >>> Bootstrapped and regtested (so far just modules.exp) on >>> x86_64-pc-linux-gnu, OK for trunk if full regtest passes? >> >> OK. >> >>> @@ -11316,6 +11319,7 @@ trees_in::key_mergeable (int tag, merge_kind mk, tree decl, tree inner, >>> case NAMESPACE_DECL: >>> if (is_attached >>> + && !is_imported_temploid_friend >> >> How can a namespace be an imported temploid friend? > > Cut off by context, but this is > > switch (TREE_CODE (container)) > { > default: > gcc_unreachable (); > > case NAMESPACE_DECL: > if (is_attached > && !is_imported_temploid_friend > && !(state->is_module () || state->is_partition ())) > kind = "unique"; > > i.e. the NAMESPACE_DECL is referring to the container that the decl is > attached to for merging purposes. Oops, yes, I figured that out but forgot to delete that comment. :) >>> && !(state->is_module () || state->is_partition ())) >>> kind = "unique"; >>> else >>> @@ -11347,7 +11351,9 @@ trees_in::key_mergeable (int tag, merge_kind mk, tree decl, tree inner, >>> break; >>> case TYPE_DECL: >>> - if (is_attached && !(state->is_module () || state->is_partition ()) >>> + if (is_attached >>> + && !is_imported_temploid_friend > > This is the one that may perhaps be unnecessary (on thinking over this > again I would expect any class-scope friends to not be redeclared > outside of their named module, even for imported templates?), so I'll > actually re-test this patch without this hunk. Sounds good. Jason
On Wed, Aug 07, 2024 at 08:45:03PM -0400, Jason Merrill wrote: > On 8/7/24 7:22 PM, Nathaniel Shead wrote: > > On Wed, Aug 07, 2024 at 04:18:47PM -0400, Jason Merrill wrote: > > > On 8/5/24 9:16 AM, Nathaniel Shead wrote: > > > > Bootstrapped and regtested (so far just modules.exp) on > > > > x86_64-pc-linux-gnu, OK for trunk if full regtest passes? > > > > > > OK. > > > > > > > @@ -11316,6 +11319,7 @@ trees_in::key_mergeable (int tag, merge_kind mk, tree decl, tree inner, > > > > case NAMESPACE_DECL: > > > > if (is_attached > > > > + && !is_imported_temploid_friend > > > > > > How can a namespace be an imported temploid friend? > > > > Cut off by context, but this is > > > > switch (TREE_CODE (container)) > > { > > default: > > gcc_unreachable (); > > > > case NAMESPACE_DECL: > > if (is_attached > > && !is_imported_temploid_friend > > && !(state->is_module () || state->is_partition ())) > > kind = "unique"; > > > > i.e. the NAMESPACE_DECL is referring to the container that the decl is > > attached to for merging purposes. > > Oops, yes, I figured that out but forgot to delete that comment. :) > > > > > && !(state->is_module () || state->is_partition ())) > > > > kind = "unique"; > > > > else > > > > @@ -11347,7 +11351,9 @@ trees_in::key_mergeable (int tag, merge_kind mk, tree decl, tree inner, > > > > break; > > > > case TYPE_DECL: > > > > - if (is_attached && !(state->is_module () || state->is_partition ()) > > > > + if (is_attached > > > > + && !is_imported_temploid_friend > > > > This is the one that may perhaps be unnecessary (on thinking over this > > again I would expect any class-scope friends to not be redeclared > > outside of their named module, even for imported templates?), so I'll > > actually re-test this patch without this hunk. > > Sounds good. > > Jason > This is what I'll push if full bootstrap+regtest succeeds, replacing that hunk with a 'gcc_checking_assert (!is_imported_temploid_friend)' just to be extra clear. -- >8 -- Currently name lookup generally seems to assume that all entities declared within a named module (partition) are attached to said module, which is not true for GM entities (e.g. via extern "C++"), and causes issues with deduplication. This patch fixes the issue by ensuring that module attachment of a declaration is consistently used to handling merging. Handling this exposes some issues with deduplicating temploid friends; to resolve this we always create the BINDING_SLOT_PARTITION slot so that we have somewhere to place attached names (from any module). This doesn't yet completely handle issues with allowing otherwise conflicting temploid friends from different modules to co-exist in the same module if neither are reachable from the other via name lookup. PR c++/114950 gcc/cp/ChangeLog: * module.cc (trees_out::decl_value): Stream bit indicating imported temploid friends early. (trees_in::decl_value): Use this bit with key_mergeable. (trees_in::key_mergeable): Allow merging attached declarations if they're imported temploid friends (which must be namespace scope). (module_state::read_cluster): Check for GM entities that may require merging even when importing from partitions. * name-lookup.cc (enum binding_slots): Adjust comment. (get_fixed_binding_slot): Always create partition slot. (name_lookup::search_namespace_only): Support binding vectors with both partition and GM entities to dedup. (walk_module_binding): Likewise. (name_lookup::adl_namespace_fns): Likewise. (set_module_binding): Likewise. (check_module_override): Use attachment of the decl when checking overrides rather than named_module_p. (lookup_imported_hidden_friend): Use partition slot for finding mergeable template bindings. * name-lookup.h (set_module_binding): Split mod_glob_flag parameter into separate global_p and partition_p params. gcc/testsuite/ChangeLog: * g++.dg/modules/tpl-friend-13_e.C: Adjust error message. * g++.dg/modules/ambig-2_a.C: New test. * g++.dg/modules/ambig-2_b.C: New test. * g++.dg/modules/part-9_a.C: New test. * g++.dg/modules/part-9_b.C: New test. * g++.dg/modules/part-9_c.C: New test. * g++.dg/modules/tpl-friend-15.h: New test. * g++.dg/modules/tpl-friend-15_a.C: New test. * g++.dg/modules/tpl-friend-15_b.C: New test. * g++.dg/modules/tpl-friend-15_c.C: New test. Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com> --- gcc/cp/module.cc | 52 +++++++++------ gcc/cp/name-lookup.cc | 65 ++++++++++--------- gcc/cp/name-lookup.h | 2 +- gcc/testsuite/g++.dg/modules/ambig-2_a.C | 7 ++ gcc/testsuite/g++.dg/modules/ambig-2_b.C | 10 +++ gcc/testsuite/g++.dg/modules/part-9_a.C | 10 +++ gcc/testsuite/g++.dg/modules/part-9_b.C | 10 +++ gcc/testsuite/g++.dg/modules/part-9_c.C | 8 +++ .../g++.dg/modules/tpl-friend-13_e.C | 4 +- gcc/testsuite/g++.dg/modules/tpl-friend-15.h | 11 ++++ .../g++.dg/modules/tpl-friend-15_a.C | 8 +++ .../g++.dg/modules/tpl-friend-15_b.C | 8 +++ .../g++.dg/modules/tpl-friend-15_c.C | 7 ++ 13 files changed, 148 insertions(+), 54 deletions(-) create mode 100644 gcc/testsuite/g++.dg/modules/ambig-2_a.C create mode 100644 gcc/testsuite/g++.dg/modules/ambig-2_b.C create mode 100644 gcc/testsuite/g++.dg/modules/part-9_a.C create mode 100644 gcc/testsuite/g++.dg/modules/part-9_b.C create mode 100644 gcc/testsuite/g++.dg/modules/part-9_c.C create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-15.h create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-15_a.C create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-15_b.C create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-15_c.C diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc index 723f0890d96..1a6f7a2186b 100644 --- a/gcc/cp/module.cc +++ b/gcc/cp/module.cc @@ -2958,7 +2958,8 @@ private: public: tree decl_container (); tree key_mergeable (int tag, merge_kind, tree decl, tree inner, tree type, - tree container, bool is_attached); + tree container, bool is_attached, + bool is_imported_temploid_friend); unsigned binfo_mergeable (tree *); private: @@ -7806,6 +7807,7 @@ trees_out::decl_value (tree decl, depset *dep) || !TYPE_PTRMEMFUNC_P (TREE_TYPE (decl))); merge_kind mk = get_merge_kind (decl, dep); + bool is_imported_temploid_friend = imported_temploid_friends->get (decl); if (CHECKING_P) { @@ -7841,13 +7843,11 @@ trees_out::decl_value (tree decl, depset *dep) && DECL_MODULE_ATTACH_P (not_tmpl)) is_attached = true; - /* But don't consider imported temploid friends as attached, - since importers will need to merge this decl even if it was - attached to a different module. */ - if (imported_temploid_friends->get (decl)) - is_attached = false; - bits.b (is_attached); + + /* Also tell the importer whether this is an imported temploid + friend, which has implications for merging. */ + bits.b (is_imported_temploid_friend); } bits.b (dep && dep->has_defn ()); } @@ -8024,13 +8024,12 @@ trees_out::decl_value (tree decl, depset *dep) } } - if (TREE_CODE (inner) == FUNCTION_DECL - || TREE_CODE (inner) == TYPE_DECL) + if (is_imported_temploid_friend) { /* Write imported temploid friends so that importers can reconstruct this information on stream-in. */ tree* slot = imported_temploid_friends->get (decl); - tree_node (slot ? *slot : NULL_TREE); + tree_node (*slot); } bool is_typedef = false; @@ -8109,6 +8108,7 @@ trees_in::decl_value () { int tag = 0; bool is_attached = false; + bool is_imported_temploid_friend = false; bool has_defn = false; unsigned mk_u = u (); if (mk_u >= MK_hwm || !merge_kind_name[mk_u]) @@ -8129,7 +8129,10 @@ trees_in::decl_value () { bits_in bits = stream_bits (); if (!(mk & MK_template_mask) && !state->is_header ()) - is_attached = bits.b (); + { + is_attached = bits.b (); + is_imported_temploid_friend = bits.b (); + } has_defn = bits.b (); } @@ -8234,7 +8237,7 @@ trees_in::decl_value () parm_tag = fn_parms_init (inner); tree existing = key_mergeable (tag, mk, decl, inner, type, container, - is_attached); + is_attached, is_imported_temploid_friend); tree existing_inner = existing; if (existing) { @@ -8339,8 +8342,7 @@ trees_in::decl_value () } } - if (TREE_CODE (inner) == FUNCTION_DECL - || TREE_CODE (inner) == TYPE_DECL) + if (is_imported_temploid_friend) if (tree owner = tree_node ()) if (is_new) imported_temploid_friends->put (decl, owner); @@ -11177,7 +11179,8 @@ check_mergeable_decl (merge_kind mk, tree decl, tree ovl, merge_key const &key) tree trees_in::key_mergeable (int tag, merge_kind mk, tree decl, tree inner, - tree type, tree container, bool is_attached) + tree type, tree container, bool is_attached, + bool is_imported_temploid_friend) { const char *kind = "new"; tree existing = NULL_TREE; @@ -11319,6 +11322,7 @@ trees_in::key_mergeable (int tag, merge_kind mk, tree decl, tree inner, case NAMESPACE_DECL: if (is_attached + && !is_imported_temploid_friend && !(state->is_module () || state->is_partition ())) kind = "unique"; else @@ -11350,6 +11354,7 @@ trees_in::key_mergeable (int tag, merge_kind mk, tree decl, tree inner, break; case TYPE_DECL: + gcc_checking_assert (!is_imported_temploid_friend); if (is_attached && !(state->is_module () || state->is_partition ()) /* Implicit member functions can come from anywhere. */ @@ -15356,6 +15361,7 @@ module_state::read_cluster (unsigned snum) tree visible = NULL_TREE; tree type = NULL_TREE; bool dedup = false; + bool global_p = false; /* We rely on the bindings being in the reverse order of the resulting overload set. */ @@ -15373,6 +15379,16 @@ module_state::read_cluster (unsigned snum) if (sec.get_overrun ()) break; + if (!global_p) + { + /* Check if the decl could require GM merging. */ + tree orig = get_originating_module_decl (decl); + tree inner = STRIP_TEMPLATE (orig); + if (!DECL_LANG_SPECIFIC (inner) + || !DECL_MODULE_ATTACH_P (inner)) + global_p = true; + } + if (decls && TREE_CODE (decl) == TYPE_DECL) { /* Stat hack. */ @@ -15459,10 +15475,8 @@ module_state::read_cluster (unsigned snum) break; /* Bail. */ dump () && dump ("Binding of %P", ns, name); - if (!set_module_binding (ns, name, mod, - is_header () ? -1 - : is_module () || is_partition () ? 1 - : 0, + if (!set_module_binding (ns, name, mod, global_p, + is_module () || is_partition (), decls, type, visible)) sec.set_overrun (); } diff --git a/gcc/cp/name-lookup.cc b/gcc/cp/name-lookup.cc index 8823ab71c60..872f1af0b2e 100644 --- a/gcc/cp/name-lookup.cc +++ b/gcc/cp/name-lookup.cc @@ -51,8 +51,8 @@ enum binding_slots { BINDING_SLOT_CURRENT, /* Slot for current TU. */ BINDING_SLOT_GLOBAL, /* Slot for merged global module. */ - BINDING_SLOT_PARTITION, /* Slot for merged partition entities - (optional). */ + BINDING_SLOT_PARTITION, /* Slot for merged partition entities or + imported friends. */ /* Number of always-allocated slots. */ BINDING_SLOTS_FIXED = BINDING_SLOT_GLOBAL + 1 @@ -248,9 +248,10 @@ get_fixed_binding_slot (tree *slot, tree name, unsigned ix, int create) if (!create) return NULL; - /* The partition slot is only needed when we're a named - module. */ - bool partition_slot = named_module_p (); + /* The partition slot is always needed, in case we have imported + temploid friends with attachment different from the module we + imported them from. */ + bool partition_slot = true; unsigned want = ((BINDING_SLOTS_FIXED + partition_slot + (create < 0) + BINDING_VECTOR_SLOTS_PER_CLUSTER - 1) / BINDING_VECTOR_SLOTS_PER_CLUSTER); @@ -937,7 +938,6 @@ name_lookup::search_namespace_only (tree scope) stat_hack, then everything was exported. */ tree type = NULL_TREE; - /* If STAT_HACK_P is false, everything is visible, and there's no duplication possibilities. */ if (STAT_HACK_P (bind)) @@ -947,9 +947,9 @@ name_lookup::search_namespace_only (tree scope) /* Do we need to engage deduplication? */ int dup = 0; if (MODULE_BINDING_GLOBAL_P (bind)) - dup = 1; - else if (MODULE_BINDING_PARTITION_P (bind)) - dup = 2; + dup |= 1; + if (MODULE_BINDING_PARTITION_P (bind)) + dup |= 2; if (unsigned hit = dup_detect & dup) { if ((hit & 1 && BINDING_VECTOR_GLOBAL_DUPS_P (val)) @@ -1275,9 +1275,9 @@ name_lookup::adl_namespace_fns (tree scope, bitmap imports) /* Do we need to engage deduplication? */ int dup = 0; if (MODULE_BINDING_GLOBAL_P (bind)) - dup = 1; - else if (MODULE_BINDING_PARTITION_P (bind)) - dup = 2; + dup |= 1; + if (MODULE_BINDING_PARTITION_P (bind)) + dup |= 2; if (unsigned hit = dup_detect & dup) if ((hit & 1 && BINDING_VECTOR_GLOBAL_DUPS_P (val)) || (hit & 2 @@ -3758,6 +3758,9 @@ check_module_override (tree decl, tree mvec, bool hiding, binding_cluster *cluster = BINDING_VECTOR_CLUSTER_BASE (mvec); unsigned ix = BINDING_VECTOR_NUM_CLUSTERS (mvec); + tree nontmpl = STRIP_TEMPLATE (decl); + bool attached = DECL_LANG_SPECIFIC (nontmpl) && DECL_MODULE_ATTACH_P (nontmpl); + if (BINDING_VECTOR_SLOTS_PER_CLUSTER == BINDING_SLOTS_FIXED) { cluster++; @@ -3819,7 +3822,7 @@ check_module_override (tree decl, tree mvec, bool hiding, { /* Look in the appropriate mergeable decl slot. */ tree mergeable = NULL_TREE; - if (named_module_p ()) + if (attached) mergeable = BINDING_VECTOR_CLUSTER (mvec, BINDING_SLOT_PARTITION / BINDING_VECTOR_SLOTS_PER_CLUSTER) .slots[BINDING_SLOT_PARTITION % BINDING_VECTOR_SLOTS_PER_CLUSTER]; @@ -3839,15 +3842,13 @@ check_module_override (tree decl, tree mvec, bool hiding, matched: if (match != error_mark_node) { - if (named_module_p ()) + if (attached) BINDING_VECTOR_PARTITION_DUPS_P (mvec) = true; else BINDING_VECTOR_GLOBAL_DUPS_P (mvec) = true; } return match; - - } /* Record DECL as belonging to the current lexical scope. Check for @@ -4300,7 +4301,9 @@ walk_module_binding (tree binding, bitmap partitions, cluster++; } - bool maybe_dups = BINDING_VECTOR_PARTITION_DUPS_P (binding); + /* There could be duplicate module or GMF entries. */ + bool maybe_dups = (BINDING_VECTOR_PARTITION_DUPS_P (binding) + || BINDING_VECTOR_GLOBAL_DUPS_P (binding)); for (; ix--; cluster++) for (unsigned jx = 0; jx != BINDING_VECTOR_SLOTS_PER_CLUSTER; jx++) @@ -4394,14 +4397,14 @@ import_module_binding (tree ns, tree name, unsigned mod, unsigned snum) } /* An import of MODULE is binding NS::NAME. There should be no - existing binding for >= MODULE. MOD_GLOB indicates whether MODULE - is a header_unit (-1) or part of the current module (+1). VALUE - and TYPE are the value and type bindings. VISIBLE are the value - bindings being exported. */ + existing binding for >= MODULE. GLOBAL_P indicates whether the + bindings include global module entities. PARTITION_P is true if + it is part of the current module. VALUE and TYPE are the value + and type bindings. VISIBLE are the value bindings being exported. */ bool -set_module_binding (tree ns, tree name, unsigned mod, int mod_glob, - tree value, tree type, tree visible) +set_module_binding (tree ns, tree name, unsigned mod, bool global_p, + bool partition_p, tree value, tree type, tree visible) { if (!value) /* Bogus BMIs could give rise to nothing to bind. */ @@ -4419,19 +4422,19 @@ set_module_binding (tree ns, tree name, unsigned mod, int mod_glob, return false; tree bind = value; - if (type || visible != bind || mod_glob) + if (type || visible != bind || partition_p || global_p) { bind = stat_hack (bind, type); STAT_VISIBLE (bind) = visible; - if ((mod_glob > 0 && TREE_PUBLIC (ns)) + if ((partition_p && TREE_PUBLIC (ns)) || (type && DECL_MODULE_EXPORT_P (type))) STAT_TYPE_VISIBLE_P (bind) = true; } - /* Note if this is this-module or global binding. */ - if (mod_glob > 0) + /* Note if this is this-module and/or global binding. */ + if (partition_p) MODULE_BINDING_PARTITION_P (bind) = true; - else if (mod_glob < 0) + if (global_p) MODULE_BINDING_GLOBAL_P (bind) = true; *mslot = bind; @@ -4540,10 +4543,8 @@ lookup_imported_hidden_friend (tree friend_tmpl) || !DECL_MODULE_IMPORT_P (inner)) return NULL_TREE; - /* Imported temploid friends are not considered as attached to this - module for merging purposes. */ - tree bind = get_mergeable_namespace_binding (current_namespace, - DECL_NAME (inner), false); + tree bind = get_mergeable_namespace_binding + (current_namespace, DECL_NAME (inner), DECL_MODULE_ATTACH_P (inner)); if (!bind) return NULL_TREE; diff --git a/gcc/cp/name-lookup.h b/gcc/cp/name-lookup.h index 5cf6ae6374a..7c4193444dd 100644 --- a/gcc/cp/name-lookup.h +++ b/gcc/cp/name-lookup.h @@ -484,7 +484,7 @@ extern tree lookup_class_binding (tree ctx, tree name); extern bool import_module_binding (tree ctx, tree name, unsigned mod, unsigned snum); extern bool set_module_binding (tree ctx, tree name, unsigned mod, - int mod_glob_flag, + bool global_p, bool partition_p, tree value, tree type, tree visible); extern void add_module_namespace_decl (tree ns, tree decl); diff --git a/gcc/testsuite/g++.dg/modules/ambig-2_a.C b/gcc/testsuite/g++.dg/modules/ambig-2_a.C new file mode 100644 index 00000000000..d5dcc93584a --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/ambig-2_a.C @@ -0,0 +1,7 @@ +// { dg-additional-options "-fmodules-ts" } +// { dg-module-cmi A } + +export module A; + +extern "C++" int foo (); +extern "C++" char bar (); diff --git a/gcc/testsuite/g++.dg/modules/ambig-2_b.C b/gcc/testsuite/g++.dg/modules/ambig-2_b.C new file mode 100644 index 00000000000..b94416aabbf --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/ambig-2_b.C @@ -0,0 +1,10 @@ +// { dg-additional-options "-fmodules-ts" } +// { dg-module-cmi !B } + +export module B; +import A; + +extern "C++" int foo (); +extern "C++" int bar (); // { dg-error "ambiguating new declaration" } + +// { dg-prune-output "not writing module" } diff --git a/gcc/testsuite/g++.dg/modules/part-9_a.C b/gcc/testsuite/g++.dg/modules/part-9_a.C new file mode 100644 index 00000000000..dc033d301ee --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/part-9_a.C @@ -0,0 +1,10 @@ +// PR c++/114950 +// { dg-additional-options "-fmodules-ts" } +// { dg-module-cmi M:a } + +module M:a; + +struct A {}; +extern "C++" struct B {}; +void f(int) {} +extern "C++" void f(double) {} diff --git a/gcc/testsuite/g++.dg/modules/part-9_b.C b/gcc/testsuite/g++.dg/modules/part-9_b.C new file mode 100644 index 00000000000..7339da22d97 --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/part-9_b.C @@ -0,0 +1,10 @@ +// PR c++/114950 +// { dg-additional-options "-fmodules-ts" } +// { dg-module-cmi M:b } + +module M:b; + +struct A {}; +extern "C++" struct B {}; +void f(int) {} +extern "C++" void f(double) {} diff --git a/gcc/testsuite/g++.dg/modules/part-9_c.C b/gcc/testsuite/g++.dg/modules/part-9_c.C new file mode 100644 index 00000000000..26ac6777b7a --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/part-9_c.C @@ -0,0 +1,8 @@ +// PR c++/114950 +// { dg-additional-options "-fmodules-ts" } +// { dg-module-cmi M } +// Handle merging definitions of extern "C++" decls across partitions + +export module M; +import :a; +import :b; diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C index afbd0a39c23..b32fd98b756 100644 --- a/gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C @@ -1,8 +1,8 @@ // { dg-additional-options "-fmodules-ts" } // 'import X' does not correctly notice that S has already been declared. -struct S {}; // { dg-message "previously declared" "" { xfail *-*-* } } -template <typename> struct T {}; // { dg-message "previously declared" } +struct S {}; // { dg-message "previous declaration" "" { xfail *-*-* } } +template <typename> struct T {}; // { dg-message "previous declaration" } void f() {} // { dg-message "previously declared" } template <typename T> void g() {} // { dg-message "previously declared" } diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-15.h b/gcc/testsuite/g++.dg/modules/tpl-friend-15.h new file mode 100644 index 00000000000..e4d3fff4445 --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-15.h @@ -0,0 +1,11 @@ +// PR c++/114950 + +template <typename T> +struct A { + friend void x(); +}; +template <typename T> +struct B { + virtual void f() { A<T> r; } +}; +template struct B<int>; diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-15_a.C b/gcc/testsuite/g++.dg/modules/tpl-friend-15_a.C new file mode 100644 index 00000000000..04c800875f4 --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-15_a.C @@ -0,0 +1,8 @@ +// PR c++/114950 +// { dg-additional-options "-fmodules-ts" } +// { dg-module-cmi M:a } + +module M:a; +extern "C++" { + #include "tpl-friend-15.h" +} diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-15_b.C b/gcc/testsuite/g++.dg/modules/tpl-friend-15_b.C new file mode 100644 index 00000000000..781882f97bc --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-15_b.C @@ -0,0 +1,8 @@ +// PR c++/114950 +// { dg-additional-options "-fmodules-ts" } +// { dg-module-cmi M:b } + +module M:b; +extern "C++" { + #include "tpl-friend-15.h" +} diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-15_c.C b/gcc/testsuite/g++.dg/modules/tpl-friend-15_c.C new file mode 100644 index 00000000000..ced7e87d993 --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-15_c.C @@ -0,0 +1,7 @@ +// PR c++/114950 +// { dg-additional-options "-fmodules-ts" } +// { dg-module-cmi M } + +export module M; +import :a; +import :b;
diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc index d1607a06757..e6b569ebca5 100644 --- a/gcc/cp/module.cc +++ b/gcc/cp/module.cc @@ -2955,7 +2955,8 @@ private: public: tree decl_container (); tree key_mergeable (int tag, merge_kind, tree decl, tree inner, tree type, - tree container, bool is_attached); + tree container, bool is_attached, + bool is_imported_temploid_friend); unsigned binfo_mergeable (tree *); private: @@ -7803,6 +7804,7 @@ trees_out::decl_value (tree decl, depset *dep) || !TYPE_PTRMEMFUNC_P (TREE_TYPE (decl))); merge_kind mk = get_merge_kind (decl, dep); + bool is_imported_temploid_friend = imported_temploid_friends->get (decl); if (CHECKING_P) { @@ -7838,13 +7840,11 @@ trees_out::decl_value (tree decl, depset *dep) && DECL_MODULE_ATTACH_P (not_tmpl)) is_attached = true; - /* But don't consider imported temploid friends as attached, - since importers will need to merge this decl even if it was - attached to a different module. */ - if (imported_temploid_friends->get (decl)) - is_attached = false; - bits.b (is_attached); + + /* Also tell the importer whether this is an imported temploid + friend, which has implications for merging. */ + bits.b (is_imported_temploid_friend); } bits.b (dep && dep->has_defn ()); } @@ -8021,13 +8021,12 @@ trees_out::decl_value (tree decl, depset *dep) } } - if (TREE_CODE (inner) == FUNCTION_DECL - || TREE_CODE (inner) == TYPE_DECL) + if (is_imported_temploid_friend) { /* Write imported temploid friends so that importers can reconstruct this information on stream-in. */ tree* slot = imported_temploid_friends->get (decl); - tree_node (slot ? *slot : NULL_TREE); + tree_node (*slot); } bool is_typedef = false; @@ -8106,6 +8105,7 @@ trees_in::decl_value () { int tag = 0; bool is_attached = false; + bool is_imported_temploid_friend = false; bool has_defn = false; unsigned mk_u = u (); if (mk_u >= MK_hwm || !merge_kind_name[mk_u]) @@ -8126,7 +8126,10 @@ trees_in::decl_value () { bits_in bits = stream_bits (); if (!(mk & MK_template_mask) && !state->is_header ()) - is_attached = bits.b (); + { + is_attached = bits.b (); + is_imported_temploid_friend = bits.b (); + } has_defn = bits.b (); } @@ -8231,7 +8234,7 @@ trees_in::decl_value () parm_tag = fn_parms_init (inner); tree existing = key_mergeable (tag, mk, decl, inner, type, container, - is_attached); + is_attached, is_imported_temploid_friend); tree existing_inner = existing; if (existing) { @@ -8336,8 +8339,7 @@ trees_in::decl_value () } } - if (TREE_CODE (inner) == FUNCTION_DECL - || TREE_CODE (inner) == TYPE_DECL) + if (is_imported_temploid_friend) if (tree owner = tree_node ()) if (is_new) imported_temploid_friends->put (decl, owner); @@ -11174,7 +11176,8 @@ check_mergeable_decl (merge_kind mk, tree decl, tree ovl, merge_key const &key) tree trees_in::key_mergeable (int tag, merge_kind mk, tree decl, tree inner, - tree type, tree container, bool is_attached) + tree type, tree container, bool is_attached, + bool is_imported_temploid_friend) { const char *kind = "new"; tree existing = NULL_TREE; @@ -11316,6 +11319,7 @@ trees_in::key_mergeable (int tag, merge_kind mk, tree decl, tree inner, case NAMESPACE_DECL: if (is_attached + && !is_imported_temploid_friend && !(state->is_module () || state->is_partition ())) kind = "unique"; else @@ -11347,7 +11351,9 @@ trees_in::key_mergeable (int tag, merge_kind mk, tree decl, tree inner, break; case TYPE_DECL: - if (is_attached && !(state->is_module () || state->is_partition ()) + if (is_attached + && !is_imported_temploid_friend + && !(state->is_module () || state->is_partition ()) /* Implicit member functions can come from anywhere. */ && !(DECL_ARTIFICIAL (decl) @@ -15291,6 +15297,7 @@ module_state::read_cluster (unsigned snum) tree visible = NULL_TREE; tree type = NULL_TREE; bool dedup = false; + bool global_p = false; /* We rely on the bindings being in the reverse order of the resulting overload set. */ @@ -15308,6 +15315,16 @@ module_state::read_cluster (unsigned snum) if (sec.get_overrun ()) break; + if (!global_p) + { + /* Check if the decl could require GM merging. */ + tree orig = get_originating_module_decl (decl); + tree inner = STRIP_TEMPLATE (orig); + if (!DECL_LANG_SPECIFIC (inner) + || !DECL_MODULE_ATTACH_P (inner)) + global_p = true; + } + if (decls && TREE_CODE (decl) == TYPE_DECL) { /* Stat hack. */ @@ -15394,10 +15411,8 @@ module_state::read_cluster (unsigned snum) break; /* Bail. */ dump () && dump ("Binding of %P", ns, name); - if (!set_module_binding (ns, name, mod, - is_header () ? -1 - : is_module () || is_partition () ? 1 - : 0, + if (!set_module_binding (ns, name, mod, global_p, + is_module () || is_partition (), decls, type, visible)) sec.set_overrun (); } diff --git a/gcc/cp/name-lookup.cc b/gcc/cp/name-lookup.cc index 8823ab71c60..872f1af0b2e 100644 --- a/gcc/cp/name-lookup.cc +++ b/gcc/cp/name-lookup.cc @@ -51,8 +51,8 @@ enum binding_slots { BINDING_SLOT_CURRENT, /* Slot for current TU. */ BINDING_SLOT_GLOBAL, /* Slot for merged global module. */ - BINDING_SLOT_PARTITION, /* Slot for merged partition entities - (optional). */ + BINDING_SLOT_PARTITION, /* Slot for merged partition entities or + imported friends. */ /* Number of always-allocated slots. */ BINDING_SLOTS_FIXED = BINDING_SLOT_GLOBAL + 1 @@ -248,9 +248,10 @@ get_fixed_binding_slot (tree *slot, tree name, unsigned ix, int create) if (!create) return NULL; - /* The partition slot is only needed when we're a named - module. */ - bool partition_slot = named_module_p (); + /* The partition slot is always needed, in case we have imported + temploid friends with attachment different from the module we + imported them from. */ + bool partition_slot = true; unsigned want = ((BINDING_SLOTS_FIXED + partition_slot + (create < 0) + BINDING_VECTOR_SLOTS_PER_CLUSTER - 1) / BINDING_VECTOR_SLOTS_PER_CLUSTER); @@ -937,7 +938,6 @@ name_lookup::search_namespace_only (tree scope) stat_hack, then everything was exported. */ tree type = NULL_TREE; - /* If STAT_HACK_P is false, everything is visible, and there's no duplication possibilities. */ if (STAT_HACK_P (bind)) @@ -947,9 +947,9 @@ name_lookup::search_namespace_only (tree scope) /* Do we need to engage deduplication? */ int dup = 0; if (MODULE_BINDING_GLOBAL_P (bind)) - dup = 1; - else if (MODULE_BINDING_PARTITION_P (bind)) - dup = 2; + dup |= 1; + if (MODULE_BINDING_PARTITION_P (bind)) + dup |= 2; if (unsigned hit = dup_detect & dup) { if ((hit & 1 && BINDING_VECTOR_GLOBAL_DUPS_P (val)) @@ -1275,9 +1275,9 @@ name_lookup::adl_namespace_fns (tree scope, bitmap imports) /* Do we need to engage deduplication? */ int dup = 0; if (MODULE_BINDING_GLOBAL_P (bind)) - dup = 1; - else if (MODULE_BINDING_PARTITION_P (bind)) - dup = 2; + dup |= 1; + if (MODULE_BINDING_PARTITION_P (bind)) + dup |= 2; if (unsigned hit = dup_detect & dup) if ((hit & 1 && BINDING_VECTOR_GLOBAL_DUPS_P (val)) || (hit & 2 @@ -3758,6 +3758,9 @@ check_module_override (tree decl, tree mvec, bool hiding, binding_cluster *cluster = BINDING_VECTOR_CLUSTER_BASE (mvec); unsigned ix = BINDING_VECTOR_NUM_CLUSTERS (mvec); + tree nontmpl = STRIP_TEMPLATE (decl); + bool attached = DECL_LANG_SPECIFIC (nontmpl) && DECL_MODULE_ATTACH_P (nontmpl); + if (BINDING_VECTOR_SLOTS_PER_CLUSTER == BINDING_SLOTS_FIXED) { cluster++; @@ -3819,7 +3822,7 @@ check_module_override (tree decl, tree mvec, bool hiding, { /* Look in the appropriate mergeable decl slot. */ tree mergeable = NULL_TREE; - if (named_module_p ()) + if (attached) mergeable = BINDING_VECTOR_CLUSTER (mvec, BINDING_SLOT_PARTITION / BINDING_VECTOR_SLOTS_PER_CLUSTER) .slots[BINDING_SLOT_PARTITION % BINDING_VECTOR_SLOTS_PER_CLUSTER]; @@ -3839,15 +3842,13 @@ check_module_override (tree decl, tree mvec, bool hiding, matched: if (match != error_mark_node) { - if (named_module_p ()) + if (attached) BINDING_VECTOR_PARTITION_DUPS_P (mvec) = true; else BINDING_VECTOR_GLOBAL_DUPS_P (mvec) = true; } return match; - - } /* Record DECL as belonging to the current lexical scope. Check for @@ -4300,7 +4301,9 @@ walk_module_binding (tree binding, bitmap partitions, cluster++; } - bool maybe_dups = BINDING_VECTOR_PARTITION_DUPS_P (binding); + /* There could be duplicate module or GMF entries. */ + bool maybe_dups = (BINDING_VECTOR_PARTITION_DUPS_P (binding) + || BINDING_VECTOR_GLOBAL_DUPS_P (binding)); for (; ix--; cluster++) for (unsigned jx = 0; jx != BINDING_VECTOR_SLOTS_PER_CLUSTER; jx++) @@ -4394,14 +4397,14 @@ import_module_binding (tree ns, tree name, unsigned mod, unsigned snum) } /* An import of MODULE is binding NS::NAME. There should be no - existing binding for >= MODULE. MOD_GLOB indicates whether MODULE - is a header_unit (-1) or part of the current module (+1). VALUE - and TYPE are the value and type bindings. VISIBLE are the value - bindings being exported. */ + existing binding for >= MODULE. GLOBAL_P indicates whether the + bindings include global module entities. PARTITION_P is true if + it is part of the current module. VALUE and TYPE are the value + and type bindings. VISIBLE are the value bindings being exported. */ bool -set_module_binding (tree ns, tree name, unsigned mod, int mod_glob, - tree value, tree type, tree visible) +set_module_binding (tree ns, tree name, unsigned mod, bool global_p, + bool partition_p, tree value, tree type, tree visible) { if (!value) /* Bogus BMIs could give rise to nothing to bind. */ @@ -4419,19 +4422,19 @@ set_module_binding (tree ns, tree name, unsigned mod, int mod_glob, return false; tree bind = value; - if (type || visible != bind || mod_glob) + if (type || visible != bind || partition_p || global_p) { bind = stat_hack (bind, type); STAT_VISIBLE (bind) = visible; - if ((mod_glob > 0 && TREE_PUBLIC (ns)) + if ((partition_p && TREE_PUBLIC (ns)) || (type && DECL_MODULE_EXPORT_P (type))) STAT_TYPE_VISIBLE_P (bind) = true; } - /* Note if this is this-module or global binding. */ - if (mod_glob > 0) + /* Note if this is this-module and/or global binding. */ + if (partition_p) MODULE_BINDING_PARTITION_P (bind) = true; - else if (mod_glob < 0) + if (global_p) MODULE_BINDING_GLOBAL_P (bind) = true; *mslot = bind; @@ -4540,10 +4543,8 @@ lookup_imported_hidden_friend (tree friend_tmpl) || !DECL_MODULE_IMPORT_P (inner)) return NULL_TREE; - /* Imported temploid friends are not considered as attached to this - module for merging purposes. */ - tree bind = get_mergeable_namespace_binding (current_namespace, - DECL_NAME (inner), false); + tree bind = get_mergeable_namespace_binding + (current_namespace, DECL_NAME (inner), DECL_MODULE_ATTACH_P (inner)); if (!bind) return NULL_TREE; diff --git a/gcc/cp/name-lookup.h b/gcc/cp/name-lookup.h index 5cf6ae6374a..7c4193444dd 100644 --- a/gcc/cp/name-lookup.h +++ b/gcc/cp/name-lookup.h @@ -484,7 +484,7 @@ extern tree lookup_class_binding (tree ctx, tree name); extern bool import_module_binding (tree ctx, tree name, unsigned mod, unsigned snum); extern bool set_module_binding (tree ctx, tree name, unsigned mod, - int mod_glob_flag, + bool global_p, bool partition_p, tree value, tree type, tree visible); extern void add_module_namespace_decl (tree ns, tree decl); diff --git a/gcc/testsuite/g++.dg/modules/ambig-2_a.C b/gcc/testsuite/g++.dg/modules/ambig-2_a.C new file mode 100644 index 00000000000..d5dcc93584a --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/ambig-2_a.C @@ -0,0 +1,7 @@ +// { dg-additional-options "-fmodules-ts" } +// { dg-module-cmi A } + +export module A; + +extern "C++" int foo (); +extern "C++" char bar (); diff --git a/gcc/testsuite/g++.dg/modules/ambig-2_b.C b/gcc/testsuite/g++.dg/modules/ambig-2_b.C new file mode 100644 index 00000000000..b94416aabbf --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/ambig-2_b.C @@ -0,0 +1,10 @@ +// { dg-additional-options "-fmodules-ts" } +// { dg-module-cmi !B } + +export module B; +import A; + +extern "C++" int foo (); +extern "C++" int bar (); // { dg-error "ambiguating new declaration" } + +// { dg-prune-output "not writing module" } diff --git a/gcc/testsuite/g++.dg/modules/part-9_a.C b/gcc/testsuite/g++.dg/modules/part-9_a.C new file mode 100644 index 00000000000..dc033d301ee --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/part-9_a.C @@ -0,0 +1,10 @@ +// PR c++/114950 +// { dg-additional-options "-fmodules-ts" } +// { dg-module-cmi M:a } + +module M:a; + +struct A {}; +extern "C++" struct B {}; +void f(int) {} +extern "C++" void f(double) {} diff --git a/gcc/testsuite/g++.dg/modules/part-9_b.C b/gcc/testsuite/g++.dg/modules/part-9_b.C new file mode 100644 index 00000000000..7339da22d97 --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/part-9_b.C @@ -0,0 +1,10 @@ +// PR c++/114950 +// { dg-additional-options "-fmodules-ts" } +// { dg-module-cmi M:b } + +module M:b; + +struct A {}; +extern "C++" struct B {}; +void f(int) {} +extern "C++" void f(double) {} diff --git a/gcc/testsuite/g++.dg/modules/part-9_c.C b/gcc/testsuite/g++.dg/modules/part-9_c.C new file mode 100644 index 00000000000..26ac6777b7a --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/part-9_c.C @@ -0,0 +1,8 @@ +// PR c++/114950 +// { dg-additional-options "-fmodules-ts" } +// { dg-module-cmi M } +// Handle merging definitions of extern "C++" decls across partitions + +export module M; +import :a; +import :b; diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C index afbd0a39c23..b32fd98b756 100644 --- a/gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C @@ -1,8 +1,8 @@ // { dg-additional-options "-fmodules-ts" } // 'import X' does not correctly notice that S has already been declared. -struct S {}; // { dg-message "previously declared" "" { xfail *-*-* } } -template <typename> struct T {}; // { dg-message "previously declared" } +struct S {}; // { dg-message "previous declaration" "" { xfail *-*-* } } +template <typename> struct T {}; // { dg-message "previous declaration" } void f() {} // { dg-message "previously declared" } template <typename T> void g() {} // { dg-message "previously declared" } diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-15.h b/gcc/testsuite/g++.dg/modules/tpl-friend-15.h new file mode 100644 index 00000000000..e4d3fff4445 --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-15.h @@ -0,0 +1,11 @@ +// PR c++/114950 + +template <typename T> +struct A { + friend void x(); +}; +template <typename T> +struct B { + virtual void f() { A<T> r; } +}; +template struct B<int>; diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-15_a.C b/gcc/testsuite/g++.dg/modules/tpl-friend-15_a.C new file mode 100644 index 00000000000..04c800875f4 --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-15_a.C @@ -0,0 +1,8 @@ +// PR c++/114950 +// { dg-additional-options "-fmodules-ts" } +// { dg-module-cmi M:a } + +module M:a; +extern "C++" { + #include "tpl-friend-15.h" +} diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-15_b.C b/gcc/testsuite/g++.dg/modules/tpl-friend-15_b.C new file mode 100644 index 00000000000..781882f97bc --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-15_b.C @@ -0,0 +1,8 @@ +// PR c++/114950 +// { dg-additional-options "-fmodules-ts" } +// { dg-module-cmi M:b } + +module M:b; +extern "C++" { + #include "tpl-friend-15.h" +} diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-15_c.C b/gcc/testsuite/g++.dg/modules/tpl-friend-15_c.C new file mode 100644 index 00000000000..ced7e87d993 --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-15_c.C @@ -0,0 +1,7 @@ +// PR c++/114950 +// { dg-additional-options "-fmodules-ts" } +// { dg-module-cmi M } + +export module M; +import :a; +import :b;
Bootstrapped and regtested (so far just modules.exp) on x86_64-pc-linux-gnu, OK for trunk if full regtest passes? -- >8 -- Currently name lookup generally seems to assume that all entities declared within a named module (partition) are attached to said module, which is not true for GM entities (e.g. via extern "C++"), and causes issues with deduplication. This patch fixes the issue by ensuring that module attachment of a declaration is consistently used to handling merging. Handling this exposes some issues with deduplicating temploid friends; to resolve this we always create the BINDING_SLOT_PARTITION slot so that we have somewhere to place attached names (from any module). PR c++/114950 gcc/cp/ChangeLog: * module.cc (trees_out::decl_value): Stream bit indicating imported temploid friends early. (trees_in::decl_value): Use this bit with key_mergeable. (trees_in::key_mergeable): Allow merging attached declarations if they're imported temploid friends. (module_state::read_cluster): Check for GM entities that may require merging even when importing from partitions. * name-lookup.cc (enum binding_slots): Adjust comment. (get_fixed_binding_slot): Always create partition slot. (name_lookup::search_namespace_only): Support binding vectors with both partition and GM entities to dedup. (walk_module_binding): Likewise. (name_lookup::adl_namespace_fns): Likewise. (set_module_binding): Likewise. (check_module_override): Use attachment of the decl when checking overrides rather than named_module_p. (lookup_imported_hidden_friend): Use partition slot for finding mergeable template bindings. * name-lookup.h (set_module_binding): Split mod_glob_flag parameter into separate global_p and partition_p params. gcc/testsuite/ChangeLog: * g++.dg/modules/tpl-friend-13_e.C: Adjust error message. * g++.dg/modules/ambig-2_a.C: New test. * g++.dg/modules/ambig-2_b.C: New test. * g++.dg/modules/part-9_a.C: New test. * g++.dg/modules/part-9_b.C: New test. * g++.dg/modules/part-9_c.C: New test. * g++.dg/modules/tpl-friend-15.h: New test. * g++.dg/modules/tpl-friend-15_a.C: New test. * g++.dg/modules/tpl-friend-15_b.C: New test. * g++.dg/modules/tpl-friend-15_c.C: New test. Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com> --- gcc/cp/module.cc | 55 ++++++++++------ gcc/cp/name-lookup.cc | 65 ++++++++++--------- gcc/cp/name-lookup.h | 2 +- gcc/testsuite/g++.dg/modules/ambig-2_a.C | 7 ++ gcc/testsuite/g++.dg/modules/ambig-2_b.C | 10 +++ gcc/testsuite/g++.dg/modules/part-9_a.C | 10 +++ gcc/testsuite/g++.dg/modules/part-9_b.C | 10 +++ gcc/testsuite/g++.dg/modules/part-9_c.C | 8 +++ .../g++.dg/modules/tpl-friend-13_e.C | 4 +- gcc/testsuite/g++.dg/modules/tpl-friend-15.h | 11 ++++ .../g++.dg/modules/tpl-friend-15_a.C | 8 +++ .../g++.dg/modules/tpl-friend-15_b.C | 8 +++ .../g++.dg/modules/tpl-friend-15_c.C | 7 ++ 13 files changed, 150 insertions(+), 55 deletions(-) create mode 100644 gcc/testsuite/g++.dg/modules/ambig-2_a.C create mode 100644 gcc/testsuite/g++.dg/modules/ambig-2_b.C create mode 100644 gcc/testsuite/g++.dg/modules/part-9_a.C create mode 100644 gcc/testsuite/g++.dg/modules/part-9_b.C create mode 100644 gcc/testsuite/g++.dg/modules/part-9_c.C create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-15.h create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-15_a.C create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-15_b.C create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-15_c.C