summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gcc/ChangeLog20
-rw-r--r--gcc/cgraphunit.c14
-rw-r--r--gcc/ipa-devirt.c72
-rw-r--r--gcc/ipa.c100
-rw-r--r--gcc/testsuite/ChangeLog7
-rw-r--r--gcc/testsuite/g++.dg/ipa/devirt-11.C1
-rw-r--r--gcc/testsuite/g++.dg/ipa/devirt-16.C39
-rw-r--r--gcc/testsuite/g++.dg/ipa/devirt-17.C44
-rw-r--r--gcc/testsuite/g++.dg/ipa/devirt-18.C37
-rw-r--r--gcc/timevar.def1
10 files changed, 297 insertions, 38 deletions
diff --git a/gcc/ChangeLog b/gcc/ChangeLog
index 7d69e19bb51..df3d29e530d 100644
--- a/gcc/ChangeLog
+++ b/gcc/ChangeLog
@@ -1,5 +1,25 @@
2013-09-08 Jan Hubicka <jh@suse.cz>
+ * cgraphunit.c (walk_polymorphic_call_targets): Permit 0 possible
+ targets and devirtualize to BUILT_IN_UNREACHABLE.
+ * timevar.def (TV_IPA_UNREACHABLE): New timevar.
+ * ipa.c (walk_polymorphic_call_targets): New function.
+ (symtab_remove_unreachable_nodes): Use it; do not keep all virtual
+ functions; use the new timevar.
+ * ipa-devirt.c (maybe_record_node): Do not insert static nodes that
+ was removed from the program.
+ (record_binfo): If BINFO corresponds to an anonymous namespace, we may
+ not consider it in the walk when its vtable is dead.
+ (possible_polymorphic_call_targets_1): Pass anonymous flag to
+ record_binfo.
+ (devirt_variable_node_removal_hook): New function.
+ (possible_polymorphic_call_targets): Also register
+ devirt_variable_node_removal_hook.
+ (ipa_devirt): Do not do non-speculative devirtualization.
+ (gate_ipa_devirt): One execute if devirtualizing speculatively.
+
+2013-09-08 Jan Hubicka <jh@suse.cz>
+
* cgraph.h (varpool_node_hook, varpool_node_hook_list,
varpool_add_node_removal_hook, varpool_add_variable_insertion_hook,
varpool_remove_variable_insertion_hook): Declare.
diff --git a/gcc/cgraphunit.c b/gcc/cgraphunit.c
index 1afdd439dda..9681df518cd 100644
--- a/gcc/cgraphunit.c
+++ b/gcc/cgraphunit.c
@@ -866,9 +866,15 @@ walk_polymorphic_call_targets (pointer_set_t *reachable_call_targets,
make the edge direct. */
if (final)
{
- gcc_assert (targets.length());
- if (targets.length() == 1)
+ if (targets.length() <= 1)
{
+ cgraph_node *target;
+ if (targets.length () == 1)
+ target = targets[0];
+ else
+ target = cgraph_get_create_node
+ (builtin_decl_implicit (BUILT_IN_UNREACHABLE));
+
if (cgraph_dump_file)
{
fprintf (cgraph_dump_file,
@@ -877,7 +883,7 @@ walk_polymorphic_call_targets (pointer_set_t *reachable_call_targets,
edge->call_stmt, 0,
TDF_SLIM);
}
- cgraph_make_edge_direct (edge, targets[0]);
+ cgraph_make_edge_direct (edge, target);
cgraph_redirect_edge_call_stmt_to_callee (edge);
if (cgraph_dump_file)
{
@@ -1092,7 +1098,7 @@ analyze_functions (void)
mangling and same body alias creation before we free DECL_ARGUMENTS
used by it. */
if (!seen_error ())
- symtab_initialize_asm_name_hash ();
+ symtab_initialize_asm_name_hash ();
}
/* Translate the ugly representation of aliases as alias pairs into nice
diff --git a/gcc/ipa-devirt.c b/gcc/ipa-devirt.c
index 2a50284c9b6..96622b25b1b 100644
--- a/gcc/ipa-devirt.c
+++ b/gcc/ipa-devirt.c
@@ -570,6 +570,8 @@ maybe_record_node (vec <cgraph_node *> &nodes,
&& fcode != BUILT_IN_TRAP
&& !pointer_set_insert (inserted, target)
&& (target_node = cgraph_get_node (target)) != NULL
+ && (TREE_PUBLIC (target)
+ || target_node->symbol.definition)
&& symtab_real_symbol_p ((symtab_node)target_node))
{
pointer_set_insert (cached_polymorphic_call_targets,
@@ -591,6 +593,8 @@ maybe_record_node (vec <cgraph_node *> &nodes,
MATCHED_VTABLES tracks virtual tables we already did lookup
for virtual function in.
+
+ ANONYMOUS is true if BINFO is part of anonymous namespace.
*/
static void
@@ -600,7 +604,8 @@ record_binfo (vec <cgraph_node *> &nodes,
tree type_binfo,
HOST_WIDE_INT otr_token,
pointer_set_t *inserted,
- pointer_set_t *matched_vtables)
+ pointer_set_t *matched_vtables,
+ bool anonymous)
{
tree type = BINFO_TYPE (binfo);
int i;
@@ -611,6 +616,19 @@ record_binfo (vec <cgraph_node *> &nodes,
if (types_same_for_odr (type, otr_type)
&& !pointer_set_insert (matched_vtables, BINFO_VTABLE (type_binfo)))
{
+ /* For types in anonymous namespace first check if the respective vtable
+ is alive. If not, we know the type can't be called. */
+ if (!flag_ltrans && anonymous)
+ {
+ tree vtable = BINFO_VTABLE (type_binfo);
+ struct varpool_node *vnode;
+
+ if (TREE_CODE (vtable) == POINTER_PLUS_EXPR)
+ vtable = TREE_OPERAND (TREE_OPERAND (vtable, 0), 0);
+ vnode = varpool_get_node (vtable);
+ if (!vnode || !vnode->symbol.definition)
+ return;
+ }
tree target = gimple_get_virt_method_for_binfo (otr_token, type_binfo);
if (target)
maybe_record_node (nodes, target, inserted);
@@ -626,7 +644,7 @@ record_binfo (vec <cgraph_node *> &nodes,
is shared with the outer type. */
BINFO_VTABLE (base_binfo) ? base_binfo : type_binfo,
otr_token, inserted,
- matched_vtables);
+ matched_vtables, anonymous);
}
/* Lookup virtual methods matching OTR_TYPE (with OFFSET and OTR_TOKEN)
@@ -646,7 +664,7 @@ possible_polymorphic_call_targets_1 (vec <cgraph_node *> &nodes,
unsigned int i;
record_binfo (nodes, binfo, otr_type, binfo, otr_token, inserted,
- matched_vtables);
+ matched_vtables, type->anonymous_namespace);
for (i = 0; i < type->derived_types.length(); i++)
possible_polymorphic_call_targets_1 (nodes, inserted,
matched_vtables,
@@ -735,6 +753,18 @@ devirt_node_removal_hook (struct cgraph_node *n, void *d ATTRIBUTE_UNUSED)
free_polymorphic_call_targets_hash ();
}
+/* When virtual table is removed, we may need to flush the cache. */
+
+static void
+devirt_variable_node_removal_hook (struct varpool_node *n,
+ void *d ATTRIBUTE_UNUSED)
+{
+ if (cached_polymorphic_call_targets
+ && DECL_VIRTUAL_P (n->symbol.decl)
+ && type_in_anonymous_namespace_p (DECL_CONTEXT (n->symbol.decl)))
+ free_polymorphic_call_targets_hash ();
+}
+
/* Return vector containing possible targets of polymorphic call of type
OTR_TYPE caling method OTR_TOKEN with OFFSET. If FINALp is non-NULL,
store true if the list is complette.
@@ -782,8 +812,12 @@ possible_polymorphic_call_targets (tree otr_type,
cached_polymorphic_call_targets = pointer_set_create ();
polymorphic_call_target_hash.create (23);
if (!node_removal_hook_holder)
- node_removal_hook_holder =
- cgraph_add_node_removal_hook (&devirt_node_removal_hook, NULL);
+ {
+ node_removal_hook_holder =
+ cgraph_add_node_removal_hook (&devirt_node_removal_hook, NULL);
+ varpool_add_node_removal_hook (&devirt_variable_node_removal_hook,
+ NULL);
+ }
}
/* Lookup cached answer. */
@@ -928,11 +962,8 @@ likely_target_p (struct cgraph_node *n)
}
/* The ipa-devirt pass.
- This performs very trivial devirtualization:
- 1) when polymorphic call is known to have precisely one target,
- turn it into direct call
- 2) when polymorphic call has only one likely target in the unit,
- turn it into speculative call. */
+ When polymorphic call has only one likely target in the unit,
+ turn it into speculative call. */
static unsigned int
ipa_devirt (void)
@@ -965,26 +996,9 @@ ipa_devirt (void)
if (dump_file)
dump_possible_polymorphic_call_targets
(dump_file, e);
+
npolymorphic++;
- if (final)
- {
- gcc_assert (targets.length());
- if (targets.length() == 1)
- {
- if (dump_file)
- fprintf (dump_file,
- "Devirtualizing call in %s/%i to %s/%i\n",
- cgraph_node_name (n), n->symbol.order,
- cgraph_node_name (targets[0]), targets[0]->symbol.order);
- cgraph_make_edge_direct (e, targets[0]);
- ndevirtualized++;
- update = true;
- continue;
- }
- }
- if (!flag_devirtualize_speculatively)
- continue;
if (!cgraph_maybe_hot_edge_p (e))
{
if (dump_file)
@@ -1114,7 +1128,7 @@ ipa_devirt (void)
static bool
gate_ipa_devirt (void)
{
- return flag_devirtualize && !in_lto_p && optimize;
+ return flag_devirtualize_speculatively && !in_lto_p && optimize;
}
namespace {
diff --git a/gcc/ipa.c b/gcc/ipa.c
index 37b6629b206..4b82d1d4bc5 100644
--- a/gcc/ipa.c
+++ b/gcc/ipa.c
@@ -149,6 +149,84 @@ process_references (struct ipa_ref_list *list,
}
}
+/* EDGE is an polymorphic call. If BEFORE_INLINING_P is set, mark
+ all its potential targets as reachable to permit later inlining if
+ devirtualization happens. After inlining still keep their declarations
+ around, so we can devirtualize to a direct call.
+
+ Also try to make trivial devirutalization when no or only one target is
+ possible. */
+
+static void
+walk_polymorphic_call_targets (pointer_set_t *reachable_call_targets,
+ struct cgraph_edge *edge,
+ symtab_node *first,
+ pointer_set_t *reachable, bool before_inlining_p)
+{
+ unsigned int i;
+ void *cache_token;
+ bool final;
+ vec <cgraph_node *>targets
+ = possible_polymorphic_call_targets
+ (edge, &final, &cache_token);
+
+ if (!pointer_set_insert (reachable_call_targets,
+ cache_token))
+ {
+ for (i = 0; i < targets.length(); i++)
+ {
+ struct cgraph_node *n = targets[i];
+
+ /* Do not bother to mark virtual methods in anonymous namespace;
+ either we will find use of virtual table defining it, or it is
+ unused. */
+ if (TREE_CODE (TREE_TYPE (n->symbol.decl)) == METHOD_TYPE
+ && type_in_anonymous_namespace_p
+ (method_class_type (TREE_TYPE (n->symbol.decl))))
+ continue;
+
+ /* Prior inlining, keep alive bodies of possible targets for
+ devirtualization. */
+ if (n->symbol.definition
+ && before_inlining_p)
+ pointer_set_insert (reachable, 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. */
+ enqueue_node ((symtab_node) n, first, reachable);
+ }
+ }
+
+ /* Very trivial devirtualization; when the type is
+ final or anonymous (so we know all its derivation)
+ and there is only one possible virtual call target,
+ make the edge direct. */
+ if (final)
+ {
+ if (targets.length() <= 1)
+ {
+ cgraph_node *target;
+ if (targets.length () == 1)
+ target = targets[0];
+ else
+ target = cgraph_get_create_node
+ (builtin_decl_implicit (BUILT_IN_UNREACHABLE));
+
+ if (dump_file)
+ fprintf (dump_file,
+ "Devirtualizing call in %s/%i to %s/%i\n",
+ cgraph_node_name (edge->caller),
+ edge->caller->symbol.order,
+ cgraph_node_name (target), target->symbol.order);
+ edge = cgraph_make_edge_direct (edge, target);
+ if (cgraph_state != CGRAPH_STATE_IPA_SSA)
+ cgraph_redirect_edge_call_stmt_to_callee (edge);
+ else
+ inline_update_overall_summary (edge->caller);
+ }
+ }
+}
/* Perform reachability analysis and reclaim all unreachable nodes.
@@ -214,7 +292,9 @@ symtab_remove_unreachable_nodes (bool before_inlining_p, FILE *file)
bool changed = false;
struct pointer_set_t *reachable = pointer_set_create ();
struct pointer_set_t *body_needed_for_clonning = pointer_set_create ();
+ struct pointer_set_t *reachable_call_targets = pointer_set_create ();
+ timevar_push (TV_IPA_UNREACHABLE);
#ifdef ENABLE_CHECKING
verify_symtab ();
#endif
@@ -238,10 +318,7 @@ symtab_remove_unreachable_nodes (bool before_inlining_p, FILE *file)
if (node->symbol.definition
&& !node->global.inlined_to
&& !node->symbol.in_other_partition
- && (!cgraph_can_remove_if_no_direct_calls_and_refs_p (node)
- /* Keep around virtual functions for possible devirtualization. */
- || (before_inlining_p
- && DECL_VIRTUAL_P (node->symbol.decl))))
+ && !cgraph_can_remove_if_no_direct_calls_and_refs_p (node))
{
gcc_assert (!node->global.inlined_to);
pointer_set_insert (reachable, node);
@@ -304,6 +381,19 @@ symtab_remove_unreachable_nodes (bool before_inlining_p, FILE *file)
if (!in_boundary_p)
{
struct cgraph_edge *e;
+ /* Keep alive possible targets for devirtualization. */
+ if (optimize && flag_devirtualize)
+ {
+ struct cgraph_edge *next;
+ for (e = cnode->indirect_calls; e; e = next)
+ {
+ next = e->next_callee;
+ if (e->indirect_info->polymorphic)
+ walk_polymorphic_call_targets (reachable_call_targets,
+ e, &first, reachable,
+ before_inlining_p);
+ }
+ }
for (e = cnode->callees; e; e = e->next_callee)
{
if (e->callee->symbol.definition
@@ -449,6 +539,7 @@ symtab_remove_unreachable_nodes (bool before_inlining_p, FILE *file)
pointer_set_destroy (reachable);
pointer_set_destroy (body_needed_for_clonning);
+ pointer_set_destroy (reachable_call_targets);
/* Now update address_taken flags and try to promote functions to be local. */
if (file)
@@ -483,6 +574,7 @@ symtab_remove_unreachable_nodes (bool before_inlining_p, FILE *file)
FOR_EACH_DEFINED_FUNCTION (node)
ipa_propagate_frequency (node);
+ timevar_pop (TV_IPA_UNREACHABLE);
return changed;
}
diff --git a/gcc/testsuite/ChangeLog b/gcc/testsuite/ChangeLog
index 21b761cdee4..9289831ced8 100644
--- a/gcc/testsuite/ChangeLog
+++ b/gcc/testsuite/ChangeLog
@@ -1,3 +1,10 @@
+2013-09-08 Jan Hubicka <jh@suse.cz>
+
+ * testsuite/g++.dg/ipa/devirt-11.C: Update template.
+ * testsuite/g++.dg/ipa/devirt-16.C: New testcase.
+ * testsuite/g++.dg/ipa/devirt-17.C: New testcase.
+ * testsuite/g++.dg/ipa/devirt-18.C: New testcase.
+
2013-09-08 Paolo Carlini <paolo.carlini@oracle.com>
PR c++/54941
diff --git a/gcc/testsuite/g++.dg/ipa/devirt-11.C b/gcc/testsuite/g++.dg/ipa/devirt-11.C
index b888935ff30..d30d56cff24 100644
--- a/gcc/testsuite/g++.dg/ipa/devirt-11.C
+++ b/gcc/testsuite/g++.dg/ipa/devirt-11.C
@@ -46,5 +46,4 @@ bar ()
and two to fn3. While doing so the new symbol for fn2 needs to be
introduced. */
/* { dg-final { scan-ipa-dump-times "Discovered a virtual call to a known target" 3 "inline" } } */
-/* { dg-final { scan-ipa-dump-times "and turned into root of the clone tree" 1 "inline" } } */
/* { dg-final { cleanup-ipa-dump "inline" } } */
diff --git a/gcc/testsuite/g++.dg/ipa/devirt-16.C b/gcc/testsuite/g++.dg/ipa/devirt-16.C
new file mode 100644
index 00000000000..85567867ff1
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ipa/devirt-16.C
@@ -0,0 +1,39 @@
+/* We shall devirtualize to unreachable. No anonymous type method should surivve
+ reachability. */
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-ipa-whole-program" } */
+namespace {
+class B {
+public:
+ virtual int foo(void)
+{
+ return 0;
+}
+};
+class A : public B {
+public:
+ virtual int foo(void)
+{
+ return 1;
+}
+};
+}
+class B *b;
+main()
+{
+ int c;
+ if (c)
+ {
+ class A a;
+ a.foo();
+ class B b;
+ b.foo();
+ }
+ return b->foo();
+}
+
+/* { dg-final { scan-ipa-dump "Devirtualizing" "whole-program"} } */
+/* { dg-final { scan-ipa-dump "builtin_unreachable" "whole-program"} } */
+/* { dg-final { scan-ipa-dump-not "A::foo" "whole-program"} } */
+/* { dg-final { scan-ipa-dump-not "A::foo" "whole-program"} } */
+/* { dg-final { cleanup-ipa-dump "whole-program" } } */
diff --git a/gcc/testsuite/g++.dg/ipa/devirt-17.C b/gcc/testsuite/g++.dg/ipa/devirt-17.C
new file mode 100644
index 00000000000..9edfd73af56
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ipa/devirt-17.C
@@ -0,0 +1,44 @@
+/* We shall devirtualize to B::foo since it is the only live candidate of an
+ anonymous type. */
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-ipa-whole-program" } */
+namespace {
+class B {
+public:
+ virtual int foo(void)
+{
+ return 0;
+}
+};
+class A : public B {
+public:
+ virtual int foo(void)
+{
+ return 1;
+}
+};
+}
+class B *b;
+void get_me_lost (void *);
+main()
+{
+ int c;
+ if (c)
+ {
+ class A a;
+ a.foo();
+ }
+ else
+ {
+ b = new (class B);
+ b->foo();
+ get_me_lost ((void *)&b);
+ }
+ return b->foo();
+}
+
+/* { dg-final { scan-ipa-dump "Devirtualizing" "whole-program"} } */
+/* { dg-final { scan-ipa-dump-not "builtin_unreachable" "whole-program"} } */
+/* { dg-final { scan-ipa-dump "B::foo" "whole-program"} } */
+/* { dg-final { scan-ipa-dump-not "A::foo" "whole-program"} } */
+/* { dg-final { cleanup-ipa-dump "whole-program" } } */
diff --git a/gcc/testsuite/g++.dg/ipa/devirt-18.C b/gcc/testsuite/g++.dg/ipa/devirt-18.C
new file mode 100644
index 00000000000..dbbe597c92c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ipa/devirt-18.C
@@ -0,0 +1,37 @@
+/* We shall devirtualize to unreachable. No anonymous type method should surivve
+ reachability. */
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-ssa" } */
+namespace {
+class B {
+public:
+ virtual int foo(void)
+{
+ return 0;
+}
+};
+class A : public B {
+public:
+ virtual int foo(void)
+{
+ return 1;
+}
+};
+}
+class B *b;
+main()
+{
+ if (0)
+ {
+ class A a;
+ a.foo();
+ class B b;
+ b.foo();
+ }
+ return b->foo();
+}
+
+/* { dg-final { scan-tree-dump-not "A::foo" "ssa"} } */
+/* { dg-final { scan-tree-dump-not "B::foo" "ssa"} } */
+/* { dg-final { scan-tree-dump "builtin_unreachable" "ssa"} } */
+/* { dg-final { cleanup-tree-dump "ssa" } } */
diff --git a/gcc/timevar.def b/gcc/timevar.def
index 5fe90953235..5a880a8a364 100644
--- a/gcc/timevar.def
+++ b/gcc/timevar.def
@@ -64,6 +64,7 @@ DEFTIMEVAR (TV_PCH_CPP_RESTORE , "PCH preprocessor state restore")
DEFTIMEVAR (TV_CGRAPH , "callgraph construction")
DEFTIMEVAR (TV_CGRAPHOPT , "callgraph optimization")
+DEFTIMEVAR (TV_IPA_UNREACHABLE , "ipa dead code removal")
DEFTIMEVAR (TV_IPA_INHERITANCE , "ipa inheritance graph")
DEFTIMEVAR (TV_IPA_VIRTUAL_CALL , "ipa virtual call target")
DEFTIMEVAR (TV_IPA_DEVIRT , "ipa devirtualization")