In some cases we can access members of a namespace-scope class without
ever having performed name-lookup on it; this can occur when a
forward-declaration of the class is used as a return type, for
instance, or with PIMPL.
One possible approach would be to do name lookup in complete_type to
force lazy loading to occur, but this seems overly expensive for a
relatively rare case. Instead, this patch generalises the existing
pending-entity support to handle this case as well.
Unfortunately this does mean that almost every class definition will be
added to the pending-entity table, and almost always unnecessarily, but
I don't see a good way to avoid this.
gcc/cp/ChangeLog:
* module.cc (depset::DB_IS_MEMBER_BIT): Rename to...
(depset::DB_IS_PENDING_BIT): ...this.
(depset::is_member): Remove.
(depset::is_pending_entity): New function.
(depset::hash::make_dependency): Mark definitions of
namespace-scope types as maybe-pending entities.
(depset::hash::add_class_entities): Rename DB_IS_MEMBER_BIT to
DB_IS_PENDING_BIT.
(depset::hash::find_dependencies): Use is_pending_entity
instead of is_member.
(module_state::write_pendings): Likewise; adjust comment.
gcc/testsuite/ChangeLog:
* g++.dg/modules/inst-4_b.C: Adjust pending-entity count.
* g++.dg/modules/member-def-1_c.C: Likewise.
* g++.dg/modules/member-def-2_c.C: Likewise.
* g++.dg/modules/tpl-spec-3_b.C: Likewise.
* g++.dg/modules/tpl-spec-4_b.C: Likewise.
* g++.dg/modules/tpl-spec-5_b.C: Likewise.
* g++.dg/modules/class-9_a.H: New test.
* g++.dg/modules/class-9_b.H: New test.
* g++.dg/modules/class-9_c.C: New test.
Signed-off-by: Nathaniel Shead <nathanielosh...@gmail.com>
---
gcc/cp/module.cc | 54 +++++++++++--------
gcc/testsuite/g++.dg/modules/class-9_a.H | 8 +++
gcc/testsuite/g++.dg/modules/class-9_b.H | 7 +++
gcc/testsuite/g++.dg/modules/class-9_c.C | 10 ++++
gcc/testsuite/g++.dg/modules/inst-4_b.C | 2 +-
gcc/testsuite/g++.dg/modules/member-def-1_c.C | 4 +-
gcc/testsuite/g++.dg/modules/member-def-2_c.C | 2 +-
gcc/testsuite/g++.dg/modules/tpl-spec-3_b.C | 3 +-
gcc/testsuite/g++.dg/modules/tpl-spec-4_b.C | 2 +-
gcc/testsuite/g++.dg/modules/tpl-spec-5_b.C | 2 +-
10 files changed, 64 insertions(+), 30 deletions(-)
create mode 100644 gcc/testsuite/g++.dg/modules/class-9_a.H
create mode 100644 gcc/testsuite/g++.dg/modules/class-9_b.H
create mode 100644 gcc/testsuite/g++.dg/modules/class-9_c.C
diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
index 2dc59ce8a12..fd9b1d3bf2e 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -2329,7 +2329,7 @@ private:
DB_KIND_BIT, /* Kind of the entity. */
DB_KIND_BITS = EK_BITS,
DB_DEFN_BIT = DB_KIND_BIT + DB_KIND_BITS,
- DB_IS_MEMBER_BIT, /* Is an out-of-class member. */
+ DB_IS_PENDING_BIT, /* Is a maybe-pending entity. */
DB_IS_INTERNAL_BIT, /* It is an (erroneous)
internal-linkage entity. */
DB_REFS_INTERNAL_BIT, /* Refers to an internal-linkage
@@ -2407,11 +2407,14 @@ public:
}
public:
- /* This class-member is defined here, but the class was imported. */
- bool is_member () const
+ /* This entity might be found other than by namespace-scope lookup;
+ see module_state::write_pendings for more details. */
+ bool is_pending_entity () const
{
- gcc_checking_assert (get_entity_kind () == EK_DECL);
- return get_flag_bit<DB_IS_MEMBER_BIT> ();
+ return (get_entity_kind () == EK_SPECIALIZATION
+ || get_entity_kind () == EK_PARTIAL
+ || (get_entity_kind () == EK_DECL
+ && get_flag_bit<DB_IS_PENDING_BIT> ()));
}
public:
bool is_internal () const
@@ -13031,6 +13034,18 @@ depset::hash::make_dependency (tree decl, entity_kind
ek)
dep->set_flag_bit<DB_IS_INTERNAL_BIT> ();
}
}
+
+ /* A namespace-scope type may be declared in one module unit
+ and defined in another; make sure that we're found when
+ completing the class. */
+ if (ek == EK_DECL
+ && !dep->is_import ()
+ && dep->has_defn ()
+ && DECL_NAMESPACE_SCOPE_P (not_tmpl)
+ && DECL_IMPLICIT_TYPEDEF_P (not_tmpl)
+ /* Anonymous types can't be forward-declared. */
+ && !IDENTIFIER_ANON_P (DECL_NAME (not_tmpl)))
+ dep->set_flag_bit<DB_IS_PENDING_BIT> ();
}
if (!dep->is_import ())
@@ -13383,9 +13398,9 @@ depset::hash::add_class_entities (vec<tree, va_gc>
*class_members)
if (dep->get_entity_kind () == EK_REDIRECT)
dep = dep->deps[0];
- /* Only non-instantiations need marking as members. */
+ /* Only non-instantiations need marking as pendings. */
if (dep->get_entity_kind () == EK_DECL)
- dep->set_flag_bit <DB_IS_MEMBER_BIT> ();
+ dep->set_flag_bit <DB_IS_PENDING_BIT> ();
}
}
@@ -13711,10 +13726,7 @@ depset::hash::find_dependencies (module_state *module)
walker.mark_declaration (decl, current->has_defn ());
if (!walker.is_key_order ()
- && (item->get_entity_kind () == EK_SPECIALIZATION
- || item->get_entity_kind () == EK_PARTIAL
- || (item->get_entity_kind () == EK_DECL
- && item->is_member ())))
+ && item->is_pending_entity ())
{
tree ns = find_pending_key (decl, nullptr);
add_namespace_context (item, ns);
@@ -15939,15 +15951,13 @@ module_state::read_entities (unsigned count, unsigned
lwm, unsigned hwm)
'instantiated' in one module, and it'd be nice to not have to
reinstantiate it in another.
- (c) A member classes completed elsewhere. A member class could be
- declared in one header and defined in another. We need to know to
- load the class definition before looking in it. This turns out to
- be a specific case of #b, so we can treat these the same. But it
- does highlight an issue -- there could be an intermediate import
- between the outermost containing namespace-scope class and the
- innermost being-defined member class. This is actually possible
- with all of these cases, so be aware -- we're not just talking of
- one level of import to get to the innermost namespace.
+ (c) Classes completed elsewhere. A class could be declared in one
+ header and defined in another. We need to know to load the class
+ definition before looking in it. It does highlight an issue --
+ there could be an intermediate import between the outermost containing
+ namespace-scope class and the innermost being-defined class. This is
+ actually possible with all of these cases, so be aware -- we're not
+ just talking of one level of import to get to the innermost namespace.
This gets complicated fast, it took me multiple attempts to even
get something remotely working. Partially because I focussed on
@@ -16067,9 +16077,7 @@ module_state::write_pendings (elf_out *to, vec<depset
*> depsets,
if (d->is_import ())
continue;
- if (!(d->get_entity_kind () == depset::EK_SPECIALIZATION
- || d->get_entity_kind () == depset::EK_PARTIAL
- || (d->get_entity_kind () == depset::EK_DECL && d->is_member ())))
+ if (!d->is_pending_entity ())
continue;
tree key_decl = nullptr;
diff --git a/gcc/testsuite/g++.dg/modules/class-9_a.H
b/gcc/testsuite/g++.dg/modules/class-9_a.H
new file mode 100644
index 00000000000..9a0bf6323f7
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/class-9_a.H
@@ -0,0 +1,8 @@
+// { dg-additional-options "-fmodule-header" }
+// { dg-module-cmi {} }
+
+struct A;
+A* foo();
+
+template <typename T> struct B;
+template <typename T> B<T>* bar();
diff --git a/gcc/testsuite/g++.dg/modules/class-9_b.H
b/gcc/testsuite/g++.dg/modules/class-9_b.H
new file mode 100644
index 00000000000..931adf66081
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/class-9_b.H
@@ -0,0 +1,7 @@
+// { dg-additional-options "-fmodule-header -fdump-lang-module" }
+// { dg-module-cmi {} }
+
+struct A { int a; };
+template <typename T> struct B { int b; };
+
+// { dg-final { scan-lang-dump {Pendings 2} module } }
diff --git a/gcc/testsuite/g++.dg/modules/class-9_c.C
b/gcc/testsuite/g++.dg/modules/class-9_c.C
new file mode 100644
index 00000000000..de34efdae0b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/class-9_c.C
@@ -0,0 +1,10 @@
+// { dg-additional-options "-fmodules-ts -fmodule-lazy" }
+
+import "class-9_a.H";
+import "class-9_b.H";
+
+int main() {
+ // Lazy loading should still find the definitions of A and B.
+ int a = foo()->a;
+ int b = bar<int>()->b;
+}
diff --git a/gcc/testsuite/g++.dg/modules/inst-4_b.C
b/gcc/testsuite/g++.dg/modules/inst-4_b.C
index c7b02b470bd..40f9ba976db 100644
--- a/gcc/testsuite/g++.dg/modules/inst-4_b.C
+++ b/gcc/testsuite/g++.dg/modules/inst-4_b.C
@@ -9,5 +9,5 @@ int main ()
return 0;
}
-// { dg-final { scan-lang-dump {Reading 1 pending entities keyed to '::TPL'} module } }
+// { dg-final { scan-lang-dump {Reading 2 pending entities keyed to '::TPL'}
module } }
// { dg-final { scan-lang-dump {Read:-[0-9]*'s type spec merge key \(new\)
type_decl:'::TPL'} module } }
diff --git a/gcc/testsuite/g++.dg/modules/member-def-1_c.C
b/gcc/testsuite/g++.dg/modules/member-def-1_c.C
index d4190a84d58..fee6f4207e3 100644
--- a/gcc/testsuite/g++.dg/modules/member-def-1_c.C
+++ b/gcc/testsuite/g++.dg/modules/member-def-1_c.C
@@ -11,6 +11,6 @@ export auto foo ()
return frob::inner ();
}
-// { dg-final { scan-lang-dump {Reading 1 pending entities keyed to '::frob'} module } }
+// { dg-final { scan-lang-dump {Reading 2 pending entities keyed to '::frob'}
module } }
// { dg-final { scan-lang-dump { Cluster members:\n \[0\]=decl definition
'::frob@foo:part1:1'\n \[1\]=decl definition
'::frob@foo:part1:1::inner@foo:part1:1'\n \[2\]=decl declaration
'::frob@foo:part1:1::inner@foo:part1:1::__dt '\n( \[.\]=decl declaration
'::frob@foo:part1:1::inner@foo:part1:1::__ct '\n)* \[6\]=decl declaration
'::frob@foo:part1:1::inner@foo:part1:1::inner@foo:part2:2'\n \[7\]=decl
declaration '::frob@foo:part1:1::frob@foo:part1:1'\n \[8\]=decl declaration
'::frob@foo:part1:1::__as_base @foo:part1:1'\n \[9\]=binding '::frob'\n}
module } }
-// { dg-final { scan-lang-dump {Pendings 0} module } }
+// { dg-final { scan-lang-dump {Pendings 1} module } }
diff --git a/gcc/testsuite/g++.dg/modules/member-def-2_c.C
b/gcc/testsuite/g++.dg/modules/member-def-2_c.C
index f0a193f34ce..33b1b9fe9d2 100644
--- a/gcc/testsuite/g++.dg/modules/member-def-2_c.C
+++ b/gcc/testsuite/g++.dg/modules/member-def-2_c.C
@@ -9,7 +9,7 @@ export import :part1;
// { dg-final { scan-lang-dump { Cluster members:\n \[0\]=decl definition '::frob@foo:part1:1'\n \[1\]=decl declaration '::frob@foo:part1:1::frob@foo:part1:1'\n \[2\]=decl definition '::frob@foo:part1:1::member@foo:part1:1'\n \[3\]=decl declaration '::frob@foo:part1:1::__as_base @foo:part1:1'\n \[4\]=binding '::frob'\n} module } }
// { dg-final { scan-lang-dump {Bindings 1} module } }
-// { dg-final { scan-lang-dump {Pendings 0} module } }
+// { dg-final { scan-lang-dump {Pendings 1} module } }
// { dg-final { scan-lang-dump {Read:-[0-9]*'s named merge key .matched.
function_decl:'::frob@foo:part1:1::member'} module } }
// { dg-final { scan-assembler-not {_ZN4frob6memberEv:} } }
diff --git a/gcc/testsuite/g++.dg/modules/tpl-spec-3_b.C
b/gcc/testsuite/g++.dg/modules/tpl-spec-3_b.C
index a7105ae6759..80534ff64a5 100644
--- a/gcc/testsuite/g++.dg/modules/tpl-spec-3_b.C
+++ b/gcc/testsuite/g++.dg/modules/tpl-spec-3_b.C
@@ -17,7 +17,8 @@ int main ()
return 0;
}
-// { dg-final { scan-lang-dump {Reading 1 pending entities keyed to '::frob'} module } }
+// We read a pending for both '::frob' and '::frob::store'.
+// { dg-final { scan-lang-dump {Reading 2 pending entities keyed to '::frob'}
module } }
// { dg-final { scan-lang-dump-not {Reading definition function_decl
'::frob@TPL:.::store@TPL:.<int>'} module } }
// { dg-final { scan-assembler-not {_ZN4frob5storeIiEEvT_:} } }
diff --git a/gcc/testsuite/g++.dg/modules/tpl-spec-4_b.C
b/gcc/testsuite/g++.dg/modules/tpl-spec-4_b.C
index 97aa251d3e0..ea8d014e88b 100644
--- a/gcc/testsuite/g++.dg/modules/tpl-spec-4_b.C
+++ b/gcc/testsuite/g++.dg/modules/tpl-spec-4_b.C
@@ -14,4 +14,4 @@ int main ()
return 0;
}
-// { dg-final { scan-lang-dump {Reading 1 pending entities keyed to '::X'} module } }
+// { dg-final { scan-lang-dump {Reading 2 pending entities keyed to '::X'}
module } }
diff --git a/gcc/testsuite/g++.dg/modules/tpl-spec-5_b.C
b/gcc/testsuite/g++.dg/modules/tpl-spec-5_b.C
index ff3d84c1384..300f649ac27 100644
--- a/gcc/testsuite/g++.dg/modules/tpl-spec-5_b.C
+++ b/gcc/testsuite/g++.dg/modules/tpl-spec-5_b.C
@@ -14,4 +14,4 @@ int main ()
return 0;
}
-// { dg-final { scan-lang-dump {Reading 1 pending entities keyed to '::X'} module } }
+// { dg-final { scan-lang-dump {Reading 2 pending entities keyed to '::X'}
module } }