Skip to content

Commit 2874932

Browse files
committed
c++: Implement dependent ADL for use with modules
[module.global.frag] p3.3 says "A declaration D is decl-reachable from a declaration S in the same translation unit if ... S contains a dependent call E ([temp.dep]) and D is found by any name lookup performed for an expression synthesized from E by replacing each type-dependent argument or operand with a value of a placeholder type with no associated namespaces or entities". This requires doing partial ADL ondependent calls, in case there are non-dependent arguments that would cause new functions to become decl-reachable. This patch implements this with an additional lookup during modules streaming to find any such entities. This causes us to do ADL in more circumstances; this means also that we might instantiate templates in cases we didn't use to. This could cause issues given we have already started our modules walk at this point, or break any otherwise valid existing code. To fix this patch adds a flag to do a "tentative" ADL pass which doesn't attempt to complete any types (and hence cause instantiations to occur); this means that we might miss some associated entities however. During a tentative walk we can also skip entities that we know won't contribute to the missing decl-reachable set, as an optimisation. One implementation limitation is that both modules tree walking and name lookup marks tree nodes as TREE_VISITED for different purposes; to avoid conflicts this patch caches calls that will require lookup in a separate worklist to be processed after the walk is done. PR c++/122712 gcc/cp/ChangeLog: * module.cc (depset::hash::dep_adl_info): New type. (depset::hash::dep_adl_entity_list): New work list. (depset::hash::hash): Create it. (depset::hash::~hash): Release it. (trees_out::tree_value): Cache possibly dependent calls during tree walk. (depset::hash::add_dependent_adl_entities): New function. (depset::hash::find_dependencies): Process cached entities. * name-lookup.cc (name_lookup::tentative): New member. (name_lookup::name_lookup): Initialize it. (name_lookup::preserve_state): Propagate tentative from previous lookup. (name_lookup::adl_namespace_fns): Don't search imported bindings during tentative lookup. (name_lookup::adl_class): Don't attempt to complete class types during tentative lookup. (name_lookup::search_adl): Skip type-dependent args and avoid unnecessary work during tentative lookup. (lookup_arg_dependent): Add tentative parameter. * name-lookup.h (lookup_arg_dependent): Likewise. gcc/testsuite/ChangeLog: * g++.dg/modules/adl-12_a.C: New test. * g++.dg/modules/adl-12_b.C: New test. Signed-off-by: Nathaniel Shead <[email protected]> Reviewed-by: Jason Merrill <[email protected]>
1 parent 7898e14 commit 2874932

File tree

5 files changed

+287
-9
lines changed

5 files changed

+287
-9
lines changed

gcc/cp/module.cc

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2628,17 +2628,33 @@ class depset {
26282628
bool ignore_tu_local; /* In a context where referencing a TU-local
26292629
entity is not an exposure. */
26302630

2631+
private:
2632+
/* Information needed to do dependent ADL for discovering
2633+
more decl-reachable entities. Cached during walking to
2634+
prevent tree marking from interfering with lookup. */
2635+
struct dep_adl_info {
2636+
/* The name of the call or operator. */
2637+
tree name = NULL_TREE;
2638+
/* If not ERROR_MARK, a rewrite candidate for this operator. */
2639+
tree_code rewrite = ERROR_MARK;
2640+
/* Argument list for the call. */
2641+
vec<tree, va_gc>* args = make_tree_vector ();
2642+
};
2643+
vec<dep_adl_info> dep_adl_entity_list;
2644+
26312645
public:
26322646
hash (size_t size, hash *c = NULL)
26332647
: parent (size), chain (c), current (NULL), section (0),
26342648
reached_unreached (false), writing_merge_key (false),
26352649
ignore_tu_local (false)
26362650
{
26372651
worklist.create (size);
2652+
dep_adl_entity_list.create (16);
26382653
}
26392654
~hash ()
26402655
{
26412656
worklist.release ();
2657+
dep_adl_entity_list.release ();
26422658
}
26432659

26442660
public:
@@ -2671,6 +2687,7 @@ class depset {
26712687
void add_specializations (bool decl_p);
26722688
void add_partial_entities (vec<tree, va_gc> *);
26732689
void add_class_entities (vec<tree, va_gc> *);
2690+
void add_dependent_adl_entities (tree expr);
26742691

26752692
private:
26762693
void add_deduction_guides (tree decl);
@@ -9803,6 +9820,9 @@ trees_out::tree_value (tree t)
98039820
&& !DECL_CONTEXT (t)))
98049821
&& TREE_CODE (t) != FUNCTION_DECL));
98059822

9823+
if (is_initial_scan () && EXPR_P (t))
9824+
dep_hash->add_dependent_adl_entities (t);
9825+
98069826
if (streaming_p ())
98079827
{
98089828
/* A new node -> tt_node. */
@@ -14980,6 +15000,90 @@ depset::hash::add_class_entities (vec<tree, va_gc> *class_members)
1498015000
}
1498115001
}
1498215002

15003+
/* Add any entities found via dependent ADL. */
15004+
15005+
void
15006+
depset::hash::add_dependent_adl_entities (tree expr)
15007+
{
15008+
gcc_checking_assert (!is_key_order ());
15009+
if (TREE_CODE (current->get_entity ()) != TEMPLATE_DECL)
15010+
return;
15011+
15012+
dep_adl_info info;
15013+
switch (TREE_CODE (expr))
15014+
{
15015+
case CALL_EXPR:
15016+
if (!KOENIG_LOOKUP_P (expr))
15017+
return;
15018+
info.name = CALL_EXPR_FN (expr);
15019+
if (!info.name)
15020+
return;
15021+
if (TREE_CODE (info.name) == TEMPLATE_ID_EXPR)
15022+
info.name = TREE_OPERAND (info.name, 0);
15023+
if (TREE_CODE (info.name) == TU_LOCAL_ENTITY)
15024+
return;
15025+
if (!identifier_p (info.name))
15026+
info.name = OVL_NAME (info.name);
15027+
for (int ix = 0; ix < call_expr_nargs (expr); ix++)
15028+
vec_safe_push (info.args, CALL_EXPR_ARG (expr, ix));
15029+
break;
15030+
15031+
case LE_EXPR:
15032+
case GE_EXPR:
15033+
case LT_EXPR:
15034+
case GT_EXPR:
15035+
info.rewrite = SPACESHIP_EXPR;
15036+
goto overloadable_expr;
15037+
15038+
case NE_EXPR:
15039+
info.rewrite = EQ_EXPR;
15040+
goto overloadable_expr;
15041+
15042+
case EQ_EXPR:
15043+
/* Not strictly a rewrite candidate, but we need to ensure
15044+
that lookup of a matching NE_EXPR can succeed if that
15045+
would inhibit a rewrite with reversed parameters. */
15046+
info.rewrite = NE_EXPR;
15047+
goto overloadable_expr;
15048+
15049+
case COMPOUND_EXPR:
15050+
case MEMBER_REF:
15051+
case MULT_EXPR:
15052+
case TRUNC_DIV_EXPR:
15053+
case TRUNC_MOD_EXPR:
15054+
case PLUS_EXPR:
15055+
case MINUS_EXPR:
15056+
case LSHIFT_EXPR:
15057+
case RSHIFT_EXPR:
15058+
case SPACESHIP_EXPR:
15059+
case BIT_AND_EXPR:
15060+
case BIT_XOR_EXPR:
15061+
case BIT_IOR_EXPR:
15062+
case TRUTH_ANDIF_EXPR:
15063+
case TRUTH_ORIF_EXPR:
15064+
overloadable_expr:
15065+
info.name = ovl_op_identifier (TREE_CODE (expr));
15066+
gcc_checking_assert (tree_operand_length (expr) == 2);
15067+
vec_safe_push (info.args, TREE_OPERAND (expr, 0));
15068+
vec_safe_push (info.args, TREE_OPERAND (expr, 1));
15069+
break;
15070+
15071+
default:
15072+
return;
15073+
}
15074+
15075+
/* If all arguments are type-dependent we don't need to do
15076+
anything further, we won't find new entities. */
15077+
processing_template_decl_sentinel ptds;
15078+
++processing_template_decl;
15079+
if (!any_type_dependent_arguments_p (info.args))
15080+
return;
15081+
15082+
/* We need to defer name lookup until after walking, otherwise
15083+
we get confused by stray TREE_VISITEDs. */
15084+
dep_adl_entity_list.safe_push (info);
15085+
}
15086+
1498315087
/* We add the partial & explicit specializations, and the explicit
1498415088
instantiations. */
1498515089

@@ -15341,6 +15445,31 @@ depset::hash::find_dependencies (module_state *module)
1534115445
&& deduction_guide_p (decl))
1534215446
add_deduction_guides (TYPE_NAME (TREE_TYPE (TREE_TYPE (decl))));
1534315447

15448+
/* Handle dependent ADL for [module.global.frag] p3.3. */
15449+
if (!is_key_order () && !dep_adl_entity_list.is_empty ())
15450+
{
15451+
processing_template_decl_sentinel ptds;
15452+
++processing_template_decl;
15453+
for (auto &info : dep_adl_entity_list)
15454+
{
15455+
tree lookup = lookup_arg_dependent (info.name, NULL_TREE,
15456+
info.args, true);
15457+
for (tree fn : lkp_range (lookup))
15458+
add_dependency (make_dependency (fn, EK_DECL));
15459+
15460+
if (info.rewrite)
15461+
{
15462+
tree rewrite_name = ovl_op_identifier (info.rewrite);
15463+
lookup = lookup_arg_dependent (rewrite_name, NULL_TREE,
15464+
info.args, true);
15465+
for (tree fn : lkp_range (lookup))
15466+
add_dependency (make_dependency (fn, EK_DECL));
15467+
}
15468+
release_tree_vector (info.args);
15469+
}
15470+
dep_adl_entity_list.truncate (0);
15471+
}
15472+
1534415473
if (!is_key_order ()
1534515474
&& TREE_CODE (decl) == TEMPLATE_DECL
1534615475
&& !DECL_UNINSTANTIATED_TEMPLATE_FRIEND_P (decl))

gcc/cp/name-lookup.cc

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,8 @@ class name_lookup
449449

450450
LOOK_want want; /* What kind of entity we want. */
451451

452+
bool tentative; /* The lookup is just to find additional decl-reachable
453+
entities in this TU during modules streaming. */
452454
bool deduping; /* Full deduping is needed because using declarations
453455
are in play. */
454456
vec<tree, va_heap, vl_embed> *scopes;
@@ -463,7 +465,7 @@ class name_lookup
463465
public:
464466
name_lookup (tree n, LOOK_want w = LOOK_want::NORMAL)
465467
: name (n), value (NULL_TREE), type (NULL_TREE),
466-
want (w),
468+
want (w), tentative (false),
467469
deduping (false), scopes (NULL), previous (NULL)
468470
{
469471
preserve_state ();
@@ -602,6 +604,8 @@ name_lookup::preserve_state ()
602604
}
603605
}
604606

607+
tentative = previous->tentative;
608+
605609
/* Unmark the outer partial lookup. */
606610
if (previous->deduping)
607611
lookup_mark (previous->value, false);
@@ -1252,6 +1256,11 @@ name_lookup::adl_namespace_fns (tree scope, bitmap imports,
12521256
add_fns (ovl_skip_hidden (MAYBE_STAT_DECL (bind)));
12531257
}
12541258

1259+
/* When doing tentative name lookup we only care about entities
1260+
in the current TU. */
1261+
if (tentative)
1262+
return;
1263+
12551264
/* Scan the imported bindings. */
12561265
unsigned ix = BINDING_VECTOR_NUM_CLUSTERS (val);
12571266
if (BINDING_VECTOR_SLOTS_PER_CLUSTER == BINDING_SLOTS_FIXED)
@@ -1484,7 +1493,12 @@ name_lookup::adl_class (tree type)
14841493
if (found_p (type))
14851494
return;
14861495

1487-
complete_type (type);
1496+
/* Don't instantiate if we don't have to so we don't unnecessarily error
1497+
on incomplete types during modules streaming. This does however mean
1498+
we incorrectly miss some decl-reachable entities (PR c++/123235). */
1499+
if (!tentative)
1500+
complete_type (type);
1501+
14881502
adl_bases (type);
14891503
mark_found (type);
14901504

@@ -1679,7 +1693,10 @@ name_lookup::search_adl (tree fns, vec<tree, va_gc> *args)
16791693
first arg. */
16801694
if (TYPE_P (arg))
16811695
adl_type (arg);
1682-
else
1696+
/* When processing a module CMI we might get a type-dependent
1697+
argument: treat as a placeholder with no associated namespace
1698+
or entities. */
1699+
else if (!tentative || !type_dependent_expression_p (arg))
16831700
adl_expr (arg);
16841701

16851702
if (vec_safe_length (scopes))
@@ -1690,10 +1707,11 @@ name_lookup::search_adl (tree fns, vec<tree, va_gc> *args)
16901707
dedup (true);
16911708

16921709
/* First get the attached modules for each innermost non-inline
1693-
namespace of an associated entity. */
1710+
namespace of an associated entity. This isn't needed for
1711+
tentative lookup, as we're only interested in the current TU. */
16941712
bitmap_obstack_initialize (NULL);
16951713
hash_map<tree, bitmap> ns_mod_assocs;
1696-
if (modules_p ())
1714+
if (modules_p () && !tentative)
16971715
{
16981716
for (tree scope : scopes)
16991717
if (TYPE_P (scope))
@@ -1732,7 +1750,10 @@ name_lookup::search_adl (tree fns, vec<tree, va_gc> *args)
17321750
adl_namespace_fns (scope, visible, inst_path,
17331751
assocs ? *assocs : NULL);
17341752
}
1735-
else if (RECORD_OR_UNION_TYPE_P (scope))
1753+
else if (RECORD_OR_UNION_TYPE_P (scope) && !tentative)
1754+
/* We don't need to look at friends when searching for
1755+
new decl-reachable entities as they will already be
1756+
considered reachable by importers. */
17361757
adl_class_fns (scope);
17371758
}
17381759

@@ -1756,13 +1777,17 @@ static void consider_binding_level (tree name,
17561777

17571778
/* ADL lookup of NAME. FNS is the result of regular lookup, and we
17581779
don't add duplicates to it. ARGS is the vector of call
1759-
arguments (which will not be empty). */
1780+
arguments (which will not be empty). TENTATIVE is true when
1781+
this is early lookup only for the purpose of finding more
1782+
decl-reachable declarations. */
17601783

17611784
tree
1762-
lookup_arg_dependent (tree name, tree fns, vec<tree, va_gc> *args)
1785+
lookup_arg_dependent (tree name, tree fns, vec<tree, va_gc> *args,
1786+
bool tentative/*=false*/)
17631787
{
17641788
auto_cond_timevar tv (TV_NAME_LOOKUP);
17651789
name_lookup lookup (name);
1790+
lookup.tentative = tentative;
17661791
return lookup.search_adl (fns, args);
17671792
}
17681793

gcc/cp/name-lookup.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,8 @@ extern void push_decl_namespace (tree);
461461
extern void pop_decl_namespace (void);
462462
extern void do_namespace_alias (location_t, tree, tree);
463463
extern tree do_class_using_decl (tree, tree);
464-
extern tree lookup_arg_dependent (tree, tree, vec<tree, va_gc> *);
464+
extern tree lookup_arg_dependent (tree, tree, vec<tree, va_gc> *,
465+
bool tentative = false);
465466
extern tree search_anon_aggr (tree, tree, bool = false);
466467
extern tree get_class_binding_direct (tree, tree, bool want_type = false);
467468
extern tree get_class_binding (tree, tree, bool want_type = false);
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// { dg-additional-options "-fmodules -Wno-global-module -fdump-lang-module" }
2+
// { dg-module-cmi M }
3+
4+
// Check we implement [module.global.frag] p2.3:
5+
// A declaration D is decl-reachable from a declaration S in the same
6+
// translation unit if: [...] S contains a dependent call E ([temp.dep]) and
7+
// D is found by any name lookup performed for an expression synthesized from
8+
// E by replacing each type-dependent argument or operand with a value of a
9+
// placeholder type with no associated namespaces or entities.
10+
11+
module;
12+
namespace Q {
13+
struct X {};
14+
void g_impl(X, X);
15+
void operator-(X, X);
16+
void go_partial(X, int);
17+
void operator/(X, int);
18+
}
19+
namespace P {
20+
struct X {};
21+
}
22+
#if __cpp_impl_three_way_comparison >= 201907L
23+
namespace ops1 {
24+
struct Y {};
25+
int operator<=>(Y, int);
26+
bool operator==(Y, int);
27+
}
28+
namespace ops2 {
29+
struct Y {};
30+
bool operator==(Y, int);
31+
bool operator!=(Y, int);
32+
}
33+
#endif
34+
namespace Incomplete {
35+
template <typename T> struct Holder { T t; };
36+
struct Z;
37+
void go(int, void*);
38+
}
39+
namespace C {
40+
struct G {};
41+
void qux(int, void*);
42+
}
43+
template <typename T> struct H : C::G {};
44+
export module M;
45+
46+
export template <typename T>
47+
void g(T t) {
48+
g_impl(t, Q::X{}); // ADL in definition finds Q::g_impl, g_impl not discarded
49+
// { dg-final { scan-lang-dump "Bindings '::Q::g_impl'" module } }
50+
51+
t - Q::X{}; // Similarly for Q::operator-
52+
// { dg-final { scan-lang-dump "Bindings '::Q::operator-'" module } }
53+
}
54+
55+
export template <typename T> struct Partial {
56+
template <typename U> static decltype(go_partial(T{}, U{})) f();
57+
template <typename U> static decltype(T{} / U{}) o();
58+
};
59+
// The instantantiation of Partial<Q::X> should emit go_partial and operator/
60+
template struct Partial<Q::X>;
61+
// { dg-final { scan-lang-dump "Bindings '::Q::go_partial'" module } }
62+
// { dg-final { scan-lang-dump "Bindings '::Q::operator/'" module } }
63+
64+
#if __cpp_impl_three_way_comparison >= 201907L
65+
export template <typename T>
66+
void rewrite_ops(T t) {
67+
// Rewritten operators should also look for the ops they rewrite to.
68+
t < ops1::Y{};
69+
t != ops1::Y{};
70+
// { dg-final { scan-lang-dump {Bindings '::ops1::operator<=>'} module { target c++20 } } }
71+
// { dg-final { scan-lang-dump {Bindings '::ops1::operator=='} module { target c++20 } } }
72+
}
73+
export template <typename T>
74+
void rewrite_ops_error(T t) {
75+
// Test we bind != to prevent considering == as a rewrite target.
76+
t == ops2::Y{};
77+
// { dg-final { scan-lang-dump "Bindings '::ops2::operator!='" module { target c++20 } } }
78+
}
79+
#endif
80+
81+
export template <typename T>
82+
void incomplete(T t) {
83+
Incomplete::Holder<Incomplete::Z>* holder;
84+
go(t, holder); // Shouldn't attempt to instantiate unnecessarily here
85+
}
86+
87+
export template <typename T>
88+
void needs_completion(T t) {
89+
H<int>* holder;
90+
// C::qux should be found via H<T>'s base, C::G.
91+
// But we don't see this because we don't attempt to complete the type.
92+
qux(t, holder);
93+
// { dg-final { scan-lang-dump "Bindings '::C::qux'" module { xfail *-*-* } } }
94+
}

0 commit comments

Comments
 (0)