diff --git a/gcc/ChangeLog b/gcc/ChangeLog index 7d69e19bb51..df3d29e530d 100644 --- a/gcc/ChangeLog +++ b/gcc/ChangeLog @@ -1,3 +1,23 @@ +2013-09-08 Jan Hubicka + + * 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 * cgraph.h (varpool_node_hook, varpool_node_hook_list, 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 &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 &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 &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 &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 &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 &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 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 + + * 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 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")