From a04d9035ec03996fd6f96a1ece21bc8d4c54cd40 Mon Sep 17 00:00:00 2001 From: Jan Hubicka Date: Mon, 19 May 2014 02:58:43 +0200 Subject: [PATCH] tree-pass.h (make_pass_ipa_comdats): New pass. * tree-pass.h (make_pass_ipa_comdats): New pass. * timevar.def (TV_IPA_COMDATS): New timevar. * passes.def (pass_ipa_comdats): Add. * Makefile.in (OBJS): Add ipa-comdats.o * ipa-comdats.c: New file. * g++.dg/ipa/comdat.C: New file. From-SVN: r210597 --- gcc/ChangeLog | 8 + gcc/Makefile.in | 1 + gcc/ipa-comdats.c | 387 ++++++++++++++++++++++++++++++++++++++ gcc/passes.def | 4 + gcc/testsuite/ChangeLog | 4 + gcc/testsuite/g++.dg/ipa/comdat.C | 15 ++ gcc/timevar.def | 1 + gcc/tree-pass.h | 1 + 8 files changed, 421 insertions(+) create mode 100644 gcc/ipa-comdats.c create mode 100644 gcc/testsuite/g++.dg/ipa/comdat.C diff --git a/gcc/ChangeLog b/gcc/ChangeLog index 56bf5ce..759110d 100644 --- a/gcc/ChangeLog +++ b/gcc/ChangeLog @@ -1,5 +1,13 @@ 2014-05-17 Jan Hubicka + * tree-pass.h (make_pass_ipa_comdats): New pass. + * timevar.def (TV_IPA_COMDATS): New timevar. + * passes.def (pass_ipa_comdats): Add. + * Makefile.in (OBJS): Add ipa-comdats.o + * ipa-comdats.c: New file. + +2014-05-17 Jan Hubicka + * ipa.c (update_visibility_by_resolution_info): New function. (function_and_variable_visibility): Use it. diff --git a/gcc/Makefile.in b/gcc/Makefile.in index 04c046c..e74bb67 100644 --- a/gcc/Makefile.in +++ b/gcc/Makefile.in @@ -1269,6 +1269,7 @@ OBJS = \ ipa-devirt.o \ ipa-split.o \ ipa-inline.o \ + ipa-comdats.o \ ipa-inline-analysis.o \ ipa-inline-transform.o \ ipa-profile.o \ diff --git a/gcc/ipa-comdats.c b/gcc/ipa-comdats.c new file mode 100644 index 0000000..d684064 --- /dev/null +++ b/gcc/ipa-comdats.c @@ -0,0 +1,387 @@ +/* Localize comdats. + Copyright (C) 2014 Free Software Foundation, Inc. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 3, or (at your option) any later +version. + +GCC is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +. */ + +/* This is very simple pass that looks for static symbols that are used + exlusively by symbol within one comdat group. In this case it makes + sense to bring the symbol itself into the group to avoid dead code + that would arrise when the comdat group from current unit is replaced + by a different copy. Consider for example: + + static int q(void) + { + .... + } + inline int t(void) + { + return q(); + } + + if Q is used only by T, it makes sense to put Q into T's comdat group. + + The pass solve simple dataflow across the callgraph trying to prove what + symbols are used exclusively from a given comdat group. + + The implementation maintains a queue linked by AUX pointer terminated by + pointer value 1. Lattice values are NULL for TOP, actual comdat group, or + ERROR_MARK_NODE for bottom. + + TODO: When symbol is used only by comdat symbols, but from different groups, + it would make sense to produce a new comdat group for it with anonymous name. + + TODO2: We can't mix variables and functions within one group. Currently + we just give up on references of symbols of different types. We also should + handle this by anonymous comdat group section. */ + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "tm.h" +#include "tree.h" +#include "cgraph.h" +#include "tree-pass.h" +#include "pointer-set.h" + +/* Main dataflow loop propagating comdat groups across + the symbol table. All references to SYMBOL are examined + and NEWGROUP is updated accordingly. MAP holds current lattice + values for individual symbols. */ + +tree +propagate_comdat_group (struct symtab_node *symbol, + tree newgroup, pointer_map &map) +{ + int i; + struct ipa_ref *ref; + + /* Walk all references to SYMBOL, recursively dive into aliases. */ + + for (i = 0; + ipa_ref_list_referring_iterate (&symbol->ref_list, i, ref) + && newgroup != error_mark_node; i++) + { + struct symtab_node *symbol2 = ref->referring; + + if (ref->use == IPA_REF_ALIAS) + { + newgroup = propagate_comdat_group (symbol2, newgroup, map); + continue; + } + + /* One COMDAT group can not hold both variables and functions at + a same time. For now we just go to BOTTOM, in future we may + invent special comdat groups for this case. */ + + if (symbol->type != symbol2->type) + { + newgroup = error_mark_node; + break; + } + + /* If we see inline clone, its comdat group actually + corresponds to the comdat group of the function it is inlined + to. */ + + if (cgraph_node * cn = dyn_cast (symbol2)) + { + if (cn->global.inlined_to) + symbol2 = cn->global.inlined_to; + } + + /* The actual merge operation. */ + + tree *val2 = map.contains (symbol2); + + if (val2 && *val2 != newgroup) + { + if (!newgroup) + newgroup = *val2; + else + newgroup = error_mark_node; + } + } + + /* If we analyze function, walk also callers. */ + + cgraph_node *cnode = dyn_cast (symbol); + + if (cnode) + for (struct cgraph_edge * edge = cnode->callers; + edge && newgroup != error_mark_node; edge = edge->next_caller) + { + struct symtab_node *symbol2 = edge->caller; + + /* If we see inline clone, its comdat group actually + corresponds to the comdat group of the function it is inlined + to. */ + + if (cgraph_node * cn = dyn_cast (symbol2)) + { + if (cn->global.inlined_to) + symbol2 = cn->global.inlined_to; + } + + /* The actual merge operation. */ + + tree *val2 = map.contains (symbol2); + + if (val2 && *val2 != newgroup) + { + if (!newgroup) + newgroup = *val2; + else + newgroup = error_mark_node; + } + } + return newgroup; +} + + +/* Add all references of SYMBOL that are defined into queue started by FIRST + and linked by AUX pointer (unless they are already enqueued). + Walk recursively inlined functions. */ + +void +enqueue_references (symtab_node **first, + symtab_node *symbol) +{ + int i; + struct ipa_ref *ref; + + for (i = 0; ipa_ref_list_reference_iterate (&symbol->ref_list, i, ref); i++) + { + symtab_node *node = symtab_alias_ultimate_target (ref->referred, NULL); + if (!node->aux && node->definition) + { + node->aux = *first; + *first = node; + } + } + + if (cgraph_node *cnode = dyn_cast (symbol)) + { + struct cgraph_edge *edge; + + for (edge = cnode->callees; edge; edge = edge->next_callee) + if (!edge->inline_failed) + enqueue_references (first, edge->callee); + else + { + symtab_node *node = symtab_alias_ultimate_target (edge->callee, + NULL); + if (!node->aux && node->definition) + { + node->aux = *first; + *first = node; + } + } + } +} + +/* Set comdat group of SYMBOL to GROUP. + Callback for symtab_for_node_and_aliases. */ + +bool +set_comdat_group (symtab_node *symbol, + void *head_p) +{ + symtab_node *head = (symtab_node *)head_p; + + gcc_assert (!DECL_COMDAT_GROUP (symbol->decl)); + DECL_COMDAT_GROUP (symbol->decl) = DECL_COMDAT_GROUP (head->decl); + symtab_add_to_same_comdat_group (symbol, head); + return false; +} + +/* The actual pass with the main dataflow loop. */ + +static unsigned int +ipa_comdats (void) +{ + pointer_map map; + pointer_map comdat_head_map; + symtab_node *symbol; + bool comdat_group_seen = false; + symtab_node *first = (symtab_node *) (void *) 1; + + /* Start the dataflow by assigning comdat group to symbols that are in comdat + groups already. All other externally visible symbols must stay, we use + ERROR_MARK_NODE as bottom for the propagation. */ + + FOR_EACH_DEFINED_SYMBOL (symbol) + if (!symtab_real_symbol_p (symbol)) + ; + else if (DECL_COMDAT_GROUP (symbol->decl)) + { + *map.insert (symbol) = DECL_COMDAT_GROUP (symbol->decl); + *comdat_head_map.insert (DECL_COMDAT_GROUP (symbol->decl)) = symbol; + comdat_group_seen = true; + + /* Mark the symbol so we won't waste time visiting it for dataflow. */ + symbol->aux = (symtab_node *) (void *) 1; + } + /* See symbols that can not be privatized to comdats; that is externally + visible symbols or otherwise used ones. We also do not want to mangle + user section names. */ + else if (symbol->externally_visible + || symbol->force_output + || symbol->used_from_other_partition + || TREE_THIS_VOLATILE (symbol->decl) + || DECL_SECTION_NAME (symbol->decl) + || (TREE_CODE (symbol->decl) == FUNCTION_DECL + && (DECL_STATIC_CONSTRUCTOR (symbol->decl) + || DECL_STATIC_DESTRUCTOR (symbol->decl)))) + { + *map.insert (symtab_alias_ultimate_target (symbol, NULL)) = error_mark_node; + + /* Mark the symbol so we won't waste time visiting it for dataflow. */ + symbol->aux = (symtab_node *) (void *) 1; + } + else + { + /* Enqueue symbol for dataflow. */ + symbol->aux = first; + first = symbol; + } + + if (!comdat_group_seen) + { + FOR_EACH_DEFINED_SYMBOL (symbol) + symbol->aux = NULL; + return 0; + } + + /* The actual dataflow. */ + + while (first != (void *) 1) + { + tree group = NULL; + tree newgroup, *val; + + symbol = first; + first = (symtab_node *)first->aux; + + /* Get current lattice value of SYMBOL. */ + val = map.contains (symbol); + if (val) + group = *val; + + /* If it is bottom, there is nothing to do; do not clear AUX + so we won't re-queue the symbol. */ + if (group == error_mark_node) + continue; + + newgroup = propagate_comdat_group (symbol, group, map); + + /* If nothing changed, proceed to next symbol. */ + if (newgroup == group) + { + symbol->aux = NULL; + continue; + } + + /* Update lattice value and enqueue all references for re-visiting. */ + gcc_assert (newgroup); + if (val) + *val = newgroup; + else + *map.insert (symbol) = newgroup; + enqueue_references (&first, symbol); + + /* We may need to revisit the symbol unless it is BOTTOM. */ + if (newgroup != error_mark_node) + symbol->aux = NULL; + } + + /* Finally assign symbols to the sections. */ + + FOR_EACH_DEFINED_SYMBOL (symbol) + { + symbol->aux = NULL; + if (!DECL_COMDAT_GROUP (symbol->decl) + && !symbol->alias + && symtab_real_symbol_p (symbol)) + { + tree group = *map.contains (symbol); + + if (group == error_mark_node) + continue; + if (dump_file) + { + fprintf (dump_file, "Localizing symbol\n"); + dump_symtab_node (dump_file, symbol); + fprintf (dump_file, "To group: %s\n", IDENTIFIER_POINTER (group)); + } + symtab_for_node_and_aliases (symbol, set_comdat_group, + *comdat_head_map.contains (group), true); + } + } + return 0; +} + +namespace { + +const pass_data pass_data_ipa_comdats = +{ + IPA_PASS, /* type */ + "comdats", /* name */ + OPTGROUP_NONE, /* optinfo_flags */ + true, /* has_execute */ + TV_IPA_COMDATS, /* tv_id */ + 0, /* properties_required */ + 0, /* properties_provided */ + 0, /* properties_destroyed */ + 0, /* todo_flags_start */ + 0, /* todo_flags_finish */ +}; + +class pass_ipa_comdats : public ipa_opt_pass_d +{ +public: + pass_ipa_comdats (gcc::context *ctxt) + : ipa_opt_pass_d (pass_data_ipa_comdats, ctxt, + NULL, /* generate_summary */ + NULL, /* write_summary */ + NULL, /* read_summary */ + NULL, /* write_optimization_summary */ + NULL, /* read_optimization_summary */ + NULL, /* stmt_fixup */ + 0, /* function_transform_todo_flags_start */ + NULL, /* function_transform */ + NULL) /* variable_transform */ + {} + + /* opt_pass methods: */ + virtual bool gate (function *); + virtual unsigned int execute (function *) { return ipa_comdats (); } + +}; // class pass_ipa_comdats + +bool +pass_ipa_comdats::gate (function *) +{ + return optimize; +} + +} // anon namespace + +ipa_opt_pass_d * +make_pass_ipa_comdats (gcc::context *ctxt) +{ + return new pass_ipa_comdats (ctxt); +} diff --git a/gcc/passes.def b/gcc/passes.def index 506b657..2f889e8 100644 --- a/gcc/passes.def +++ b/gcc/passes.def @@ -110,6 +110,10 @@ along with GCC; see the file COPYING3. If not see NEXT_PASS (pass_ipa_inline); NEXT_PASS (pass_ipa_pure_const); NEXT_PASS (pass_ipa_reference); + /* Comdat privatization come last, as direct references to comdat local + symbols are not allowed outside of the comdat group. Privatizing early + would result in missed optimizations due to this restriction. */ + NEXT_PASS (pass_ipa_comdats); TERMINATE_PASS_LIST () /* Simple IPA passes executed after the regular passes. In WHOPR mode the diff --git a/gcc/testsuite/ChangeLog b/gcc/testsuite/ChangeLog index fcb47f4..e58cb17 100644 --- a/gcc/testsuite/ChangeLog +++ b/gcc/testsuite/ChangeLog @@ -1,3 +1,7 @@ +2014-05-17 Jan Hubicka + + * g++.dg/ipa/comdat.C: New file. + 2014-05-18 Eric Botcazou * gnat.dg/volatile12.ad[sb]: New test. diff --git a/gcc/testsuite/g++.dg/ipa/comdat.C b/gcc/testsuite/g++.dg/ipa/comdat.C new file mode 100644 index 0000000..40a607f --- /dev/null +++ b/gcc/testsuite/g++.dg/ipa/comdat.C @@ -0,0 +1,15 @@ +/* { dg-do compile { target *-*-linux* *-*-gnu* } } */ +/* { dg-options "-O2 -fdump-ipa-comdats" } */ +#include +__attribute__ ((noinline)) +static int q(void) +{ + return printf ("test"); +} +inline int t(void) +{ + return q(); +} +int (*f)()=t; +/* { dg-final { scan-ipa-dump-times "Localizing symbol" 1 "comdats" } } */ +/* { dg-final { cleanup-ipa-dump "comdats" } } */ diff --git a/gcc/timevar.def b/gcc/timevar.def index 2db1943..cbb64d5 100644 --- a/gcc/timevar.def +++ b/gcc/timevar.def @@ -71,6 +71,7 @@ DEFTIMEVAR (TV_IPA_DEVIRT , "ipa devirtualization") DEFTIMEVAR (TV_IPA_CONSTANT_PROP , "ipa cp") DEFTIMEVAR (TV_IPA_INLINING , "ipa inlining heuristics") DEFTIMEVAR (TV_IPA_FNSPLIT , "ipa function splitting") +DEFTIMEVAR (TV_IPA_COMDATS , "ipa comdats") DEFTIMEVAR (TV_IPA_OPT , "ipa various optimizations") DEFTIMEVAR (TV_IPA_LTO_GIMPLE_IN , "ipa lto gimple in") DEFTIMEVAR (TV_IPA_LTO_GIMPLE_OUT , "ipa lto gimple out") diff --git a/gcc/tree-pass.h b/gcc/tree-pass.h index c025b1f..3888bb6 100644 --- a/gcc/tree-pass.h +++ b/gcc/tree-pass.h @@ -472,6 +472,7 @@ extern simple_ipa_opt_pass *make_pass_ipa_tm (gcc::context *ctxt); extern simple_ipa_opt_pass *make_pass_omp_simd_clone (gcc::context *ctxt); extern ipa_opt_pass_d *make_pass_ipa_profile (gcc::context *ctxt); extern ipa_opt_pass_d *make_pass_ipa_cdtor_merge (gcc::context *ctxt); +extern ipa_opt_pass_d *make_pass_ipa_comdats (gcc::context *ctxt); extern gimple_opt_pass *make_pass_cleanup_cfg_post_optimizing (gcc::context *ctxt); -- 2.7.4