Hi, this patch fixes ICE in ipa-cp that detects local function that is dead. The issue is that virutal functions may have no address taken but still may be virtual call targets. This patch makes remove_unreachable_nodes to handle this and not mark them local (since they are not)
Honza * cgraph.c (cgraph_node::dump): Dump split_part and indirect_call_target. * cgraph.h (cgraph_node): Add indirect_call_target flag. * ipa.c (has_addr_references_p): Cleanup. (is_indirect_call_target_p): New. (walk_polymorphic_call_targets): Do not mark virtuals that may be called indirectly as local. (symbol_table::remove_unreachable_nodes): Compute indirect_call_target. PR lto/69589 * g++.dg/lto/pr69589_0.C: New testcase * g++.dg/lto/pr69589_1.C: New testcase Index: cgraph.c =================================================================== --- cgraph.c (revision 234108) +++ cgraph.c (working copy) @@ -2061,6 +2061,10 @@ cgraph_node::dump (FILE *f) fprintf (f, " icf_merged"); if (merged_comdat) fprintf (f, " merged_comdat"); + if (split_part) + fprintf (f, " split_part"); + if (indirect_call_target) + fprintf (f, " indirect_call_target"); if (nonfreeing_fn) fprintf (f, " nonfreeing_fn"); if (DECL_STATIC_CONSTRUCTOR (decl)) Index: cgraph.h =================================================================== --- cgraph.h (revision 234108) +++ cgraph.h (working copy) @@ -1366,6 +1366,8 @@ public: unsigned parallelized_function : 1; /* True if function is part split out by ipa-split. */ unsigned split_part : 1; + /* True if the function appears as possible target of indirect call. */ + unsigned indirect_call_target : 1; private: /* Worker for call_for_symbol_and_aliases. */ Index: ipa.c =================================================================== --- ipa.c (revision 234108) +++ ipa.c (working copy) @@ -41,7 +41,7 @@ along with GCC; see the file COPYING3. static bool has_addr_references_p (struct cgraph_node *node, - void *data ATTRIBUTE_UNUSED) + void *) { int i; struct ipa_ref *ref = NULL; @@ -52,6 +52,14 @@ has_addr_references_p (struct cgraph_nod return false; } +/* Return true when NODE can be target of an indirect call. */ + +static bool +is_indirect_call_target_p (struct cgraph_node *node, void *) +{ + return node->indirect_call_target; +} + /* Look for all functions inlined to NODE and update their inlined_to pointers to INLINED_TO. */ @@ -172,23 +180,24 @@ walk_polymorphic_call_targets (hash_set< (TYPE_METHOD_BASETYPE (TREE_TYPE (n->decl)))) continue; - symtab_node *body = n->function_symbol (); + n->indirect_call_target = true; + symtab_node *body = n->function_symbol (); /* Prior inlining, keep alive bodies of possible targets for devirtualization. */ - if (n->definition - && (before_inlining_p - && opt_for_fn (body->decl, optimize) - && opt_for_fn (body->decl, flag_devirtualize))) - { - /* Be sure that we will not optimize out alias target - body. */ - if (DECL_EXTERNAL (n->decl) - && n->alias - && before_inlining_p) - reachable->add (body); - reachable->add (n); - } + if (n->definition + && (before_inlining_p + && opt_for_fn (body->decl, optimize) + && opt_for_fn (body->decl, flag_devirtualize))) + { + /* Be sure that we will not optimize out alias target + body. */ + if (DECL_EXTERNAL (n->decl) + && n->alias + && before_inlining_p) + reachable->add (body); + reachable->add (n); + } /* Even after inlining we want to keep the possible targets in the boundary, so late passes can still produce direct call even if the chance for inlining is lost. */ @@ -323,6 +332,7 @@ symbol_table::remove_unreachable_nodes ( FOR_EACH_FUNCTION (node) { node->used_as_abstract_origin = false; + node->indirect_call_target = false; if (node->definition && !node->global.inlined_to && !node->in_other_partition @@ -659,7 +669,14 @@ symbol_table::remove_unreachable_nodes ( fprintf (file, " %s", node->name ()); node->address_taken = false; changed = true; - if (node->local_p ()) + if (node->local_p () + /* Virtual functions may be kept in cgraph just because + of possible later devirtualization. Do not mark them as + local too early so we won't optimize them out before + we are done with polymorphic call analysis. */ + && (!before_inlining_p + || !node->call_for_symbol_and_aliases + (is_indirect_call_target_p, NULL, true))) { node->local.local = true; if (file) Index: testsuite/g++.dg/lto/pr69589_0.C =================================================================== --- testsuite/g++.dg/lto/pr69589_0.C (revision 0) +++ testsuite/g++.dg/lto/pr69589_0.C (working copy) @@ -0,0 +1,26 @@ +// { dg-lto-do link } +// { dg-lto-options "-O2 -rdynamic" } +// { dg-extra-ld-options "-r -nostdlib" } +#pragma GCC visibility push(hidden) +struct A { int &operator[] (long); }; +template <typename> struct B; +template <typename T, typename = B<T> > +using Z = int; +template <typename> struct C; +struct S { + int e; + virtual ~S () {} +}; +struct D : S { + A a; + long i; + D() { { e ? &a[i] : nullptr; } } +}; +template <> +struct C<int> { Z<S> m8 () const; }; +Z<S> +C<int>::m8 () const +{ + D (); +} + Index: testsuite/g++.dg/lto/pr69589_1.C =================================================================== --- testsuite/g++.dg/lto/pr69589_1.C (revision 0) +++ testsuite/g++.dg/lto/pr69589_1.C (working copy) @@ -0,0 +1,61 @@ +struct A; +template <class T> +struct Q { Q (T); }; +template<typename T, class D> +struct U { + ~U () { m1 (nullptr); } + D m2 (); + T *u; + void m1 (T *) { m2 () (u); } +}; +struct F { F (int *); }; +template <class, class T = F> +using W = Q<T>; +int a, b; +void fn1 (void *); +template <class T> +void +fn2 (T *x) +{ + if (x) + x->~T(); + fn1 (x); +} +template <typename T> +struct C { + void operator() (T *x) { fn2 (x); } +}; +struct D; +template <typename T, typename D = C<T> > +using V = U<T, D>; +struct A { + A (int *); +}; +struct S; +struct G { + V<S> m3 (); +}; +struct S { + int e; + virtual ~S () {} +}; +template<typename T> +struct H { + H (int, T x, int) : h(x) {} + G g; + void m4 () { g.m3 (); } + T h; +}; +struct I { + I(A, W<D>); +}; +void +test () +{ + A c (&b); + W<D> d (&b); + I e (c, d); + H<I> f (0, e, a); + f.m4 (); +} +