From 91bc34a94d157c64f82477e8cd44d55494e7b1b7 Mon Sep 17 00:00:00 2001 From: Jan Hubicka Date: Sat, 2 Aug 2014 17:13:41 +0200 Subject: [PATCH] invoke.texi (Wsuggest-final-types, [...]): Document. * doc/invoke.texi (Wsuggest-final-types, Wsuggest-final-methods): Document. * ipa-devirt.c: Include hash-map.h (struct polymorphic_call_target_d): Add type_warning and decl_warning. (clear_speculation): Break out of ... (get_class_context): ... here; speed up handling obviously useless speculations. (odr_type_warn_count, decl_warn_count): New structures. (final_warning_record): New structure. (final_warning_records): New static variable. (possible_polymorphic_call_targets): Cleanup handling of speculative info; do not build speculation when user do not care; record info about warnings when asked for. (add_decl_warning): New function. (type_warning_cmp): New function. (decl_warning_cmp): New function. (ipa_devirt): Handle -Wsuggest-final-methods and -Wsuggest-final-types. (gate): Enable pass when warnings are requested. * common.opt (Wsuggest-final-types, Wsuggest-final-methods): New options. * g++.dg/warn/Wsuggest-final.C: New testcase. * g++.dg/ipa/devirt-34.C: Fix. From-SVN: r213518 --- gcc/ChangeLog | 21 +++ gcc/common.opt | 8 + gcc/doc/invoke.texi | 22 ++- gcc/ipa-devirt.c | 288 ++++++++++++++++++++++++----- gcc/testsuite/ChangeLog | 5 + gcc/testsuite/g++.dg/ipa/devirt-34.C | 3 + gcc/testsuite/g++.dg/warn/Wsuggest-final.C | 14 ++ gcc/varpool.c | 12 +- 8 files changed, 327 insertions(+), 46 deletions(-) create mode 100644 gcc/testsuite/g++.dg/warn/Wsuggest-final.C diff --git a/gcc/ChangeLog b/gcc/ChangeLog index 7302bc8..832c89b 100644 --- a/gcc/ChangeLog +++ b/gcc/ChangeLog @@ -1,3 +1,24 @@ +2014-08-01 Jan Hubicka + + * doc/invoke.texi (Wsuggest-final-types, Wsuggest-final-methods): Document. + * ipa-devirt.c: Include hash-map.h + (struct polymorphic_call_target_d): Add type_warning and decl_warning. + (clear_speculation): Break out of ... + (get_class_context): ... here; speed up handling obviously useless + speculations. + (odr_type_warn_count, decl_warn_count): New structures. + (final_warning_record): New structure. + (final_warning_records): New static variable. + (possible_polymorphic_call_targets): Cleanup handling of speculative info; + do not build speculation when user do not care; record info about warnings + when asked for. + (add_decl_warning): New function. + (type_warning_cmp): New function. + (decl_warning_cmp): New function. + (ipa_devirt): Handle -Wsuggest-final-methods and -Wsuggest-final-types. + (gate): Enable pass when warnings are requested. + * common.opt (Wsuggest-final-types, Wsuggest-final-methods): New options. + 2014-08-02 Trevor Saunders * hash-map.h (default_hashmap_traits::mark_key_deleted): diff --git a/gcc/common.opt b/gcc/common.opt index 40c8b3c..0c4f86b 100644 --- a/gcc/common.opt +++ b/gcc/common.opt @@ -651,6 +651,14 @@ Wsuggest-attribute=noreturn Common Var(warn_suggest_attribute_noreturn) Warning Warn about functions which might be candidates for __attribute__((noreturn)) +Wsuggest-final-types +Common Var(warn_suggest_final_types) Warning +Warn about C++ polymorphic types where adding final keyword would improve code quality + +Wsuggest-final-methods +Common Var(warn_suggest_final_methods) Warning +Warn about C++ virtual methods where adding final keyword would improve code quality + Wsystem-headers Common Var(warn_system_headers) Warning Do not suppress warnings from system headers diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index 1eefb69..4f327df 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -271,6 +271,7 @@ Objective-C and Objective-C++ Dialects}. -Wstack-protector -Wstack-usage=@var{len} -Wstrict-aliasing @gol -Wstrict-aliasing=n @gol -Wstrict-overflow -Wstrict-overflow=@var{n} @gol -Wsuggest-attribute=@r{[}pure@r{|}const@r{|}noreturn@r{|}format@r{]} @gol +-Wsuggest-final-types @gol -Wsuggest-final-methods @gol -Wmissing-format-attribute @gol -Wswitch -Wswitch-default -Wswitch-enum -Wswitch-bool -Wsync-nand @gol -Wsystem-headers -Wtrampolines -Wtrigraphs -Wtype-limits -Wundef @gol @@ -4193,6 +4194,25 @@ case, and some functions for which @code{format} attributes are appropriate may not be detected. @end table +@item -Wsuggest-final-types +@opindex Wno-suggest-final-types +@opindex Wsuggest-final-types +Warn about types with virtual methods where code quality would be improved +if the type was declared with C++11 final specifier, or, if possible, +declared in anonymous namespace. This allows GCC to devritualize more aggressively +the polymorphic calls. This warning is more effective with link time optimization, +where the information about the class hiearchy graph is more complete. + +@item -Wsuggest-final-methods +@opindex Wno-suggest-final-methods +@opindex Wsuggest-final-methods +Warn about virtual methods where code quality would be improved if the method +was declared with C++11 final specifier, or, if possible, its type was declared +in the anonymous namespace or with final specifier. This warning is more +effective with link time optimization, where the information about the class +hiearchy graph is more complete. It is recommended to first consider suggestins +of @option{-Wsuggest-final-types} and then rebuild with new annotations. + @item -Warray-bounds @opindex Wno-array-bounds @opindex Warray-bounds @@ -9622,7 +9642,7 @@ before applying @option{--param inline-unit-growth}. The default is 10000. @item inline-unit-growth Specifies maximal overall growth of the compilation unit caused by inlining. The default value is 30 which limits unit growth to 1.3 times the original -size. Cold functions (either marked cold via an attribibute or by profile +size. Cold functions (either marked cold via an attribute or by profile feedback) are not accounted into the unit size. @item ipcp-unit-growth diff --git a/gcc/ipa-devirt.c b/gcc/ipa-devirt.c index 5484ccd..ca74d3b 100644 --- a/gcc/ipa-devirt.c +++ b/gcc/ipa-devirt.c @@ -133,6 +133,7 @@ along with GCC; see the file COPYING3. If not see #include "dbgcnt.h" #include "stor-layout.h" #include "intl.h" +#include "hash-map.h" static bool odr_types_equivalent_p (tree, tree, bool, bool *, hash_set *); @@ -1616,6 +1617,8 @@ struct polymorphic_call_target_d vec targets; int speculative_targets; bool complete; + int type_warning; + tree decl_warning; }; /* Polymorphic call target cache helpers. */ @@ -1734,6 +1737,16 @@ contains_polymorphic_type_p (const_tree type) return false; } +/* Clear speculative info from CONTEXT. */ + +static void +clear_speculation (ipa_polymorphic_call_context *context) +{ + context->speculative_outer_type = NULL; + context->speculative_offset = 0; + context->speculative_maybe_derived_type = false; +} + /* CONTEXT->OUTER_TYPE is a type of memory object where object of EXPECTED_TYPE is contained at CONTEXT->OFFSET. Walk the memory representation of CONTEXT->OUTER_TYPE and find the outermost class type that match @@ -1769,6 +1782,16 @@ get_class_context (ipa_polymorphic_call_context *context, type = context->outer_type = expected_type; context->offset = offset = 0; } + + if (context->speculative_outer_type == context->outer_type + && (!context->maybe_derived_type + || context->speculative_maybe_derived_type)) + { + context->speculative_outer_type = NULL; + context->speculative_offset = 0; + context->speculative_maybe_derived_type = false; + } + /* See if speculative type seem to be derrived from outer_type. Then speculation is valid only if it really is a derivate and derived types are allowed. @@ -1785,11 +1808,7 @@ get_class_context (ipa_polymorphic_call_context *context, context->outer_type)) speculation_valid = context->maybe_derived_type; else - { - context->speculative_outer_type = NULL; - context->speculative_offset = 0; - context->speculative_maybe_derived_type = false; - } + clear_speculation (context); /* Find the sub-object the constant actually refers to and mark whether it is an artificial one (as opposed to a user-defined one). @@ -1820,10 +1839,7 @@ get_class_context (ipa_polymorphic_call_context *context, context->outer_type) && (context->maybe_derived_type == context->speculative_maybe_derived_type))) - { - context->speculative_outer_type = NULL; - context->speculative_offset = 0; - } + clear_speculation (context); return true; } else @@ -1841,8 +1857,7 @@ get_class_context (ipa_polymorphic_call_context *context, if (!speculation_valid || !context->maybe_derived_type) { - context->speculative_outer_type = NULL; - context->speculative_offset = 0; + clear_speculation (context); return true; } /* Otherwise look into speculation now. */ @@ -1925,9 +1940,7 @@ get_class_context (ipa_polymorphic_call_context *context, /* If we failed to find subtype we look for, give up and fall back to the most generic query. */ give_up: - context->speculative_outer_type = NULL; - context->speculative_offset = 0; - context->speculative_maybe_derived_type = false; + clear_speculation (context); if (valid) return true; context->outer_type = expected_type; @@ -2502,6 +2515,30 @@ devirt_variable_node_removal_hook (varpool_node *n, free_polymorphic_call_targets_hash (); } +/* Record about how many calls would benefit from given type to be final. */ +struct odr_type_warn_count +{ + int count; + gcov_type dyn_count; +}; + +/* Record about how many calls would benefit from given method to be final. */ +struct decl_warn_count +{ + tree decl; + int count; + gcov_type dyn_count; +}; + +/* Information about type and decl warnings. */ +struct final_warning_record +{ + gcov_type dyn_count; + vec type_warnings; + hash_map decl_warnings; +}; +struct final_warning_record *final_warning_records; + /* Return vector containing possible targets of polymorphic call of type OTR_TYPE caling method OTR_TOKEN within type of OTR_OUTER_TYPE and OFFSET. If INCLUDE_BASES is true, walk also base types of OUTER_TYPES containig @@ -2573,6 +2610,10 @@ possible_polymorphic_call_targets (tree otr_type, return nodes; } + /* Do not bother to compute speculative info when user do not asks for it. */ + if (!speculative_targetsp || !context.speculative_outer_type) + clear_speculation (&context); + type = get_odr_type (otr_type, true); /* Recording type variants would wast results cache. */ @@ -2639,6 +2680,19 @@ possible_polymorphic_call_targets (tree otr_type, *completep = (*slot)->complete; if (speculative_targetsp) *speculative_targetsp = (*slot)->speculative_targets; + if ((*slot)->type_warning && final_warning_records) + { + final_warning_records->type_warnings[(*slot)->type_warning - 1].count++; + final_warning_records->type_warnings[(*slot)->type_warning - 1].dyn_count + += final_warning_records->dyn_count; + } + if ((*slot)->decl_warning && final_warning_records) + { + struct decl_warn_count *c = + final_warning_records->decl_warnings.get ((*slot)->decl_warning); + c->count++; + c->dyn_count += final_warning_records->dyn_count; + } return (*slot)->targets; } @@ -2657,9 +2711,13 @@ possible_polymorphic_call_targets (tree otr_type, hash_set inserted; hash_set matched_vtables; + /* First insert targets we speculatively identified as likely. */ if (context.speculative_outer_type) { odr_type speculative_outer_type; + bool speculation_complete = true; + + /* First insert target from type itself and check if it may have derived types. */ speculative_outer_type = get_odr_type (context.speculative_outer_type, true); if (TYPE_FINAL_P (speculative_outer_type->type)) context.speculative_maybe_derived_type = false; @@ -2671,35 +2729,27 @@ possible_polymorphic_call_targets (tree otr_type, else target = NULL; - if (target) - { - /* In the case we get complete method, we don't need - to walk derivations. */ - if (DECL_FINAL_P (target)) - context.speculative_maybe_derived_type = false; - } + /* In the case we get complete method, we don't need + to walk derivations. */ + if (target && DECL_FINAL_P (target)) + context.speculative_maybe_derived_type = false; if (type_possibly_instantiated_p (speculative_outer_type->type)) - maybe_record_node (nodes, target, &inserted, can_refer, &complete); + maybe_record_node (nodes, target, &inserted, can_refer, &speculation_complete); if (binfo) matched_vtables.add (BINFO_VTABLE (binfo)); + /* Next walk recursively all derived types. */ if (context.speculative_maybe_derived_type) - { - /* For anonymous namespace types we can attempt to build full type. - All derivations must be in this unit (unless we see partial unit). */ - if (!type->all_derivations_known) - complete = false; - for (i = 0; i < speculative_outer_type->derived_types.length(); i++) - possible_polymorphic_call_targets_1 (nodes, &inserted, - &matched_vtables, - otr_type, - speculative_outer_type->derived_types[i], - otr_token, speculative_outer_type->type, - context.speculative_offset, &complete, - bases_to_consider, - false); - } - /* Finally walk bases, if asked to. */ + for (i = 0; i < speculative_outer_type->derived_types.length(); i++) + possible_polymorphic_call_targets_1 (nodes, &inserted, + &matched_vtables, + otr_type, + speculative_outer_type->derived_types[i], + otr_token, speculative_outer_type->type, + context.speculative_offset, + &speculation_complete, + bases_to_consider, + false); (*slot)->speculative_targets = nodes.length(); } @@ -2743,10 +2793,6 @@ possible_polymorphic_call_targets (tree otr_type, /* Next walk recursively all derived types. */ if (context.maybe_derived_type) { - /* For anonymous namespace types we can attempt to build full type. - All derivations must be in this unit (unless we see partial unit). */ - if (!type->all_derivations_known) - complete = false; for (i = 0; i < outer_type->derived_types.length(); i++) possible_polymorphic_call_targets_1 (nodes, &inserted, &matched_vtables, @@ -2756,6 +2802,51 @@ possible_polymorphic_call_targets (tree otr_type, context.offset, &complete, bases_to_consider, context.maybe_in_construction); + + if (!outer_type->all_derivations_known) + { + if (final_warning_records) + { + if (complete + && nodes.length () == 1 + && warn_suggest_final_types + && !outer_type->derived_types.length ()) + { + if (outer_type->id >= (int)final_warning_records->type_warnings.length ()) + final_warning_records->type_warnings.safe_grow_cleared + (odr_types.length ()); + final_warning_records->type_warnings[outer_type->id].count++; + final_warning_records->type_warnings[outer_type->id].dyn_count + += final_warning_records->dyn_count; + (*slot)->type_warning = outer_type->id + 1; + } + if (complete + && warn_suggest_final_methods + && nodes.length () == 1 + && types_same_for_odr (DECL_CONTEXT (nodes[0]->decl), + outer_type->type)) + { + bool existed; + struct decl_warn_count &c = + final_warning_records->decl_warnings.get_or_insert + (nodes[0]->decl, &existed); + + if (existed) + { + c.count++; + c.dyn_count += final_warning_records->dyn_count; + } + else + { + c.count = 1; + c.dyn_count = final_warning_records->dyn_count; + c.decl = nodes[0]->decl; + } + (*slot)->decl_warning = nodes[0]->decl; + } + } + complete = false; + } } /* Finally walk bases, if asked to. */ @@ -2796,6 +2887,14 @@ possible_polymorphic_call_targets (tree otr_type, return nodes; } +bool +add_decl_warning (const tree &key ATTRIBUTE_UNUSED, const decl_warn_count &value, + vec *vec) +{ + vec->safe_push (&value); + return true; +} + /* Dump all possible targets of a polymorphic call. */ void @@ -2946,6 +3045,38 @@ likely_target_p (struct cgraph_node *n) return true; } +/* Compare type warning records P1 and P2 and chose one with larger count; + helper for qsort. */ + +int +type_warning_cmp (const void *p1, const void *p2) +{ + const odr_type_warn_count *t1 = (const odr_type_warn_count *)p1; + const odr_type_warn_count *t2 = (const odr_type_warn_count *)p2; + + if (t1->dyn_count < t2->dyn_count) + return 1; + if (t1->dyn_count > t2->dyn_count) + return -1; + return t2->count - t1->count; +} + +/* Compare decl warning records P1 and P2 and chose one with larger count; + helper for qsort. */ + +int +decl_warning_cmp (const void *p1, const void *p2) +{ + const decl_warn_count *t1 = *(const decl_warn_count * const *)p1; + const decl_warn_count *t2 = *(const decl_warn_count * const *)p2; + + if (t1->dyn_count < t2->dyn_count) + return 1; + if (t1->dyn_count > t2->dyn_count) + return -1; + return t2->count - t1->count; +} + /* The ipa-devirt pass. When polymorphic call has only one likely target in the unit, turn it into speculative call. */ @@ -2961,6 +3092,19 @@ ipa_devirt (void) int nmultiple = 0, noverwritable = 0, ndevirtualized = 0, nnotdefined = 0; int nwrong = 0, nok = 0, nexternal = 0, nartificial = 0; + /* We can output -Wsuggest-final-methods and -Wsuggest-final-types warnings. + This is implemented by setting up final_warning_records that are updated + by get_polymorphic_call_targets. + We need to clear cache in this case to trigger recomputation of all + entries. */ + if (warn_suggest_final_methods || warn_suggest_final_types) + { + final_warning_records = new (final_warning_record); + final_warning_records->type_warnings = vNULL; + final_warning_records->type_warnings.safe_grow_cleared (odr_types.length ()); + free_polymorphic_call_targets_hash (); + } + FOR_EACH_DEFINED_FUNCTION (n) { bool update = false; @@ -2974,6 +3118,10 @@ ipa_devirt (void) void *cache_token; bool final; int speculative_targets; + + if (final_warning_records) + final_warning_records->dyn_count = e->count; + vec targets = possible_polymorphic_call_targets (e, &final, &cache_token, &speculative_targets); @@ -2985,6 +3133,9 @@ ipa_devirt (void) npolymorphic++; + if (!flag_devirtualize_speculatively) + continue; + if (!cgraph_maybe_hot_edge_p (e)) { if (dump_file) @@ -3114,6 +3265,55 @@ ipa_devirt (void) if (update) inline_update_overall_summary (n); } + if (warn_suggest_final_methods || warn_suggest_final_types) + { + if (warn_suggest_final_types) + { + final_warning_records->type_warnings.qsort (type_warning_cmp); + for (unsigned int i = 0; + i < final_warning_records->type_warnings.length (); i++) + if (final_warning_records->type_warnings[i].count) + { + odr_type type = odr_types[i]; + warning_at (DECL_SOURCE_LOCATION (TYPE_NAME (type->type)), + OPT_Wsuggest_final_types, + "Declaring type %qD final " + "would enable devirtualization of %i calls", + type->type, + final_warning_records->type_warnings[i].count); + } + } + + if (warn_suggest_final_methods) + { + vec decl_warnings_vec = vNULL; + + final_warning_records->decl_warnings.traverse + *, add_decl_warning> (&decl_warnings_vec); + decl_warnings_vec.qsort (decl_warning_cmp); + for (unsigned int i = 0; i < decl_warnings_vec.length (); i++) + { + tree decl = decl_warnings_vec[i]->decl; + int count = decl_warnings_vec[i]->count; + + if (DECL_CXX_DESTRUCTOR_P (decl)) + warning_at (DECL_SOURCE_LOCATION (decl), + OPT_Wsuggest_final_methods, + "Declaring virtual destructor of %qD final " + "would enable devirtualization of %i calls", + DECL_CONTEXT (decl), count); + else + warning_at (DECL_SOURCE_LOCATION (decl), + OPT_Wsuggest_final_methods, + "Declaring method %qD final " + "would enable devirtualization of %i calls", + decl, count); + } + } + + delete (final_warning_records); + final_warning_records = 0; + } if (dump_file) fprintf (dump_file, @@ -3163,7 +3363,9 @@ public: virtual bool gate (function *) { return (flag_devirtualize - && flag_devirtualize_speculatively + && (flag_devirtualize_speculatively + || (warn_suggest_final_methods + || warn_suggest_final_types)) && optimize); } diff --git a/gcc/testsuite/ChangeLog b/gcc/testsuite/ChangeLog index 13b6323..aab6097 100644 --- a/gcc/testsuite/ChangeLog +++ b/gcc/testsuite/ChangeLog @@ -1,3 +1,8 @@ +2014-08-01 Jan Hubicka + + * g++.dg/warn/Wsuggest-final.C: New testcase. + * g++.dg/ipa/devirt-34.C: Fix. + 2014-08-02 Marek Polacek PR c/59855 diff --git a/gcc/testsuite/g++.dg/ipa/devirt-34.C b/gcc/testsuite/g++.dg/ipa/devirt-34.C index 258a2ad..5d56e1e 100644 --- a/gcc/testsuite/g++.dg/ipa/devirt-34.C +++ b/gcc/testsuite/g++.dg/ipa/devirt-34.C @@ -2,6 +2,9 @@ /* { dg-options "-O2 -fdump-ipa-devirt" } */ struct A {virtual int t(){return 42;}}; struct B:A {virtual int t(){return 1;}}; + +struct A aa; +struct B bb; int t(struct B *b) { diff --git a/gcc/testsuite/g++.dg/warn/Wsuggest-final.C b/gcc/testsuite/g++.dg/warn/Wsuggest-final.C new file mode 100644 index 0000000..5371063 --- /dev/null +++ b/gcc/testsuite/g++.dg/warn/Wsuggest-final.C @@ -0,0 +1,14 @@ +// { dg-do compile } +// { dg-options "-O2 -Wsuggest-final-types -Wsuggest-final-methods" } +struct A { // { dg-warning "final would enable devirtualization of 4 calls" } +virtual void a() {} // { dg-warning "final would enable devirtualization of 2 calls" } + virtual void b() {} // { dg-warning "final would enable devirtualization of 2 calls" } +}; +void +t(struct A *a) +{ + a->a(); + a->a(); + a->b(); + a->b(); +} diff --git a/gcc/varpool.c b/gcc/varpool.c index 41b83d7..8350adb 100644 --- a/gcc/varpool.c +++ b/gcc/varpool.c @@ -340,8 +340,16 @@ varpool_node::ctor_useable_for_folding_p (void) /* Variables declared 'const' without an initializer have zero as the initializer if they may not be - overridden at link or run time. */ - if (!DECL_INITIAL (real_node->decl) + overridden at link or run time. + + It is actually requirement for C++ compiler to optimize const variables + consistently. As a GNU extension, do not enfore this rule for user defined + weak variables, so we support interposition on: + static const int dummy = 0; + extern const int foo __attribute__((__weak__, __alias__("dummy"))); + */ + if ((!DECL_INITIAL (real_node->decl) + || (DECL_WEAK (decl) && !DECL_COMDAT (decl))) && (DECL_EXTERNAL (decl) || decl_replaceable_p (decl))) return false; -- 2.7.4