2665 lines
68 KiB
C
2665 lines
68 KiB
C
/* Interprocedural Identical Code Folding pass
|
|
Copyright (C) 2014-2015 Free Software Foundation, Inc.
|
|
|
|
Contributed by Jan Hubicka <hubicka@ucw.cz> and Martin Liska <mliska@suse.cz>
|
|
|
|
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
|
|
<http://www.gnu.org/licenses/>. */
|
|
|
|
/* Interprocedural Identical Code Folding for functions and
|
|
read-only variables.
|
|
|
|
The goal of this transformation is to discover functions and read-only
|
|
variables which do have exactly the same semantics.
|
|
|
|
In case of functions,
|
|
we could either create a virtual clone or do a simple function wrapper
|
|
that will call equivalent function. If the function is just locally visible,
|
|
all function calls can be redirected. For read-only variables, we create
|
|
aliases if possible.
|
|
|
|
Optimization pass arranges as follows:
|
|
1) All functions and read-only variables are visited and internal
|
|
data structure, either sem_function or sem_variables is created.
|
|
2) For every symbol from the previous step, VAR_DECL and FUNCTION_DECL are
|
|
saved and matched to corresponding sem_items.
|
|
3) These declaration are ignored for equality check and are solved
|
|
by Value Numbering algorithm published by Alpert, Zadeck in 1992.
|
|
4) We compute hash value for each symbol.
|
|
5) Congruence classes are created based on hash value. If hash value are
|
|
equal, equals function is called and symbols are deeply compared.
|
|
We must prove that all SSA names, declarations and other items
|
|
correspond.
|
|
6) Value Numbering is executed for these classes. At the end of the process
|
|
all symbol members in remaining classes can be merged.
|
|
7) Merge operation creates alias in case of read-only variables. For
|
|
callgraph node, we must decide if we can redirect local calls,
|
|
create an alias or a thunk.
|
|
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include "system.h"
|
|
#include "coretypes.h"
|
|
#include "hash-set.h"
|
|
#include "machmode.h"
|
|
#include "vec.h"
|
|
#include "double-int.h"
|
|
#include "input.h"
|
|
#include "alias.h"
|
|
#include "symtab.h"
|
|
#include "options.h"
|
|
#include "wide-int.h"
|
|
#include "inchash.h"
|
|
#include "tree.h"
|
|
#include "fold-const.h"
|
|
#include "predict.h"
|
|
#include "tm.h"
|
|
#include "hard-reg-set.h"
|
|
#include "function.h"
|
|
#include "dominance.h"
|
|
#include "cfg.h"
|
|
#include "basic-block.h"
|
|
#include "tree-ssa-alias.h"
|
|
#include "internal-fn.h"
|
|
#include "gimple-expr.h"
|
|
#include "is-a.h"
|
|
#include "gimple.h"
|
|
#include "hashtab.h"
|
|
#include "rtl.h"
|
|
#include "flags.h"
|
|
#include "statistics.h"
|
|
#include "real.h"
|
|
#include "fixed-value.h"
|
|
#include "insn-config.h"
|
|
#include "expmed.h"
|
|
#include "dojump.h"
|
|
#include "explow.h"
|
|
#include "calls.h"
|
|
#include "emit-rtl.h"
|
|
#include "varasm.h"
|
|
#include "stmt.h"
|
|
#include "expr.h"
|
|
#include "gimple-iterator.h"
|
|
#include "gimple-ssa.h"
|
|
#include "tree-cfg.h"
|
|
#include "tree-phinodes.h"
|
|
#include "stringpool.h"
|
|
#include "tree-ssanames.h"
|
|
#include "tree-dfa.h"
|
|
#include "tree-pass.h"
|
|
#include "gimple-pretty-print.h"
|
|
#include "hash-map.h"
|
|
#include "plugin-api.h"
|
|
#include "ipa-ref.h"
|
|
#include "cgraph.h"
|
|
#include "alloc-pool.h"
|
|
#include "symbol-summary.h"
|
|
#include "ipa-prop.h"
|
|
#include "ipa-inline.h"
|
|
#include "cfgloop.h"
|
|
#include "except.h"
|
|
#include "hash-table.h"
|
|
#include "coverage.h"
|
|
#include "attribs.h"
|
|
#include "print-tree.h"
|
|
#include "lto-streamer.h"
|
|
#include "data-streamer.h"
|
|
#include "ipa-utils.h"
|
|
#include <list>
|
|
#include "ipa-icf-gimple.h"
|
|
#include "ipa-icf.h"
|
|
|
|
using namespace ipa_icf_gimple;
|
|
|
|
namespace ipa_icf {
|
|
|
|
/* Constructor. */
|
|
|
|
symbol_compare_collection::symbol_compare_collection (symtab_node *node)
|
|
{
|
|
m_references.create (0);
|
|
m_interposables.create (0);
|
|
|
|
ipa_ref *ref;
|
|
|
|
if (is_a <varpool_node *> (node) && DECL_VIRTUAL_P (node->decl))
|
|
return;
|
|
|
|
for (unsigned i = 0; i < node->num_references (); i++)
|
|
{
|
|
ref = node->iterate_reference (i, ref);
|
|
if (ref->address_matters_p ())
|
|
m_references.safe_push (ref->referred);
|
|
|
|
if (ref->referred->get_availability () <= AVAIL_INTERPOSABLE)
|
|
{
|
|
if (ref->use == IPA_REF_ADDR)
|
|
m_references.safe_push (ref->referred);
|
|
else
|
|
m_interposables.safe_push (ref->referred);
|
|
}
|
|
}
|
|
|
|
if (is_a <cgraph_node *> (node))
|
|
{
|
|
cgraph_node *cnode = dyn_cast <cgraph_node *> (node);
|
|
|
|
for (cgraph_edge *e = cnode->callees; e; e = e->next_callee)
|
|
if (e->callee->get_availability () <= AVAIL_INTERPOSABLE)
|
|
m_interposables.safe_push (e->callee);
|
|
}
|
|
}
|
|
|
|
/* Constructor for key value pair, where _ITEM is key and _INDEX is a target. */
|
|
|
|
sem_usage_pair::sem_usage_pair (sem_item *_item, unsigned int _index):
|
|
item (_item), index (_index)
|
|
{
|
|
}
|
|
|
|
/* Semantic item constructor for a node of _TYPE, where STACK is used
|
|
for bitmap memory allocation. */
|
|
|
|
sem_item::sem_item (sem_item_type _type,
|
|
bitmap_obstack *stack): type(_type), hash(0)
|
|
{
|
|
setup (stack);
|
|
}
|
|
|
|
/* Semantic item constructor for a node of _TYPE, where STACK is used
|
|
for bitmap memory allocation. The item is based on symtab node _NODE
|
|
with computed _HASH. */
|
|
|
|
sem_item::sem_item (sem_item_type _type, symtab_node *_node,
|
|
hashval_t _hash, bitmap_obstack *stack): type(_type),
|
|
node (_node), hash (_hash)
|
|
{
|
|
decl = node->decl;
|
|
setup (stack);
|
|
}
|
|
|
|
/* Add reference to a semantic TARGET. */
|
|
|
|
void
|
|
sem_item::add_reference (sem_item *target)
|
|
{
|
|
refs.safe_push (target);
|
|
unsigned index = refs.length ();
|
|
target->usages.safe_push (new sem_usage_pair(this, index));
|
|
bitmap_set_bit (target->usage_index_bitmap, index);
|
|
refs_set.add (target->node);
|
|
}
|
|
|
|
/* Initialize internal data structures. Bitmap STACK is used for
|
|
bitmap memory allocation process. */
|
|
|
|
void
|
|
sem_item::setup (bitmap_obstack *stack)
|
|
{
|
|
gcc_checking_assert (node);
|
|
|
|
refs.create (0);
|
|
tree_refs.create (0);
|
|
usages.create (0);
|
|
usage_index_bitmap = BITMAP_ALLOC (stack);
|
|
}
|
|
|
|
sem_item::~sem_item ()
|
|
{
|
|
for (unsigned i = 0; i < usages.length (); i++)
|
|
delete usages[i];
|
|
|
|
refs.release ();
|
|
tree_refs.release ();
|
|
usages.release ();
|
|
|
|
BITMAP_FREE (usage_index_bitmap);
|
|
}
|
|
|
|
/* Dump function for debugging purpose. */
|
|
|
|
DEBUG_FUNCTION void
|
|
sem_item::dump (void)
|
|
{
|
|
if (dump_file)
|
|
{
|
|
fprintf (dump_file, "[%s] %s (%u) (tree:%p)\n", type == FUNC ? "func" : "var",
|
|
name(), node->order, (void *) node->decl);
|
|
fprintf (dump_file, " hash: %u\n", get_hash ());
|
|
fprintf (dump_file, " references: ");
|
|
|
|
for (unsigned i = 0; i < refs.length (); i++)
|
|
fprintf (dump_file, "%s%s ", refs[i]->name (),
|
|
i < refs.length() - 1 ? "," : "");
|
|
|
|
fprintf (dump_file, "\n");
|
|
}
|
|
}
|
|
|
|
/* Return true if target supports alias symbols. */
|
|
|
|
bool
|
|
sem_item::target_supports_symbol_aliases_p (void)
|
|
{
|
|
#if !defined (ASM_OUTPUT_DEF) || (!defined(ASM_OUTPUT_WEAK_ALIAS) && !defined (ASM_WEAKEN_DECL))
|
|
return false;
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
/* Semantic function constructor that uses STACK as bitmap memory stack. */
|
|
|
|
sem_function::sem_function (bitmap_obstack *stack): sem_item (FUNC, stack),
|
|
m_checker (NULL), m_compared_func (NULL)
|
|
{
|
|
arg_types.create (0);
|
|
bb_sizes.create (0);
|
|
bb_sorted.create (0);
|
|
}
|
|
|
|
/* Constructor based on callgraph node _NODE with computed hash _HASH.
|
|
Bitmap STACK is used for memory allocation. */
|
|
sem_function::sem_function (cgraph_node *node, hashval_t hash,
|
|
bitmap_obstack *stack):
|
|
sem_item (FUNC, node, hash, stack),
|
|
m_checker (NULL), m_compared_func (NULL)
|
|
{
|
|
arg_types.create (0);
|
|
bb_sizes.create (0);
|
|
bb_sorted.create (0);
|
|
}
|
|
|
|
sem_function::~sem_function ()
|
|
{
|
|
for (unsigned i = 0; i < bb_sorted.length (); i++)
|
|
delete (bb_sorted[i]);
|
|
|
|
arg_types.release ();
|
|
bb_sizes.release ();
|
|
bb_sorted.release ();
|
|
}
|
|
|
|
/* Calculates hash value based on a BASIC_BLOCK. */
|
|
|
|
hashval_t
|
|
sem_function::get_bb_hash (const sem_bb *basic_block)
|
|
{
|
|
inchash::hash hstate;
|
|
|
|
hstate.add_int (basic_block->nondbg_stmt_count);
|
|
hstate.add_int (basic_block->edge_count);
|
|
|
|
return hstate.end ();
|
|
}
|
|
|
|
/* References independent hash function. */
|
|
|
|
hashval_t
|
|
sem_function::get_hash (void)
|
|
{
|
|
if(!hash)
|
|
{
|
|
inchash::hash hstate;
|
|
hstate.add_int (177454); /* Random number for function type. */
|
|
|
|
hstate.add_int (arg_count);
|
|
hstate.add_int (cfg_checksum);
|
|
hstate.add_int (gcode_hash);
|
|
|
|
for (unsigned i = 0; i < bb_sorted.length (); i++)
|
|
hstate.merge_hash (get_bb_hash (bb_sorted[i]));
|
|
|
|
for (unsigned i = 0; i < bb_sizes.length (); i++)
|
|
hstate.add_int (bb_sizes[i]);
|
|
|
|
hash = hstate.end ();
|
|
}
|
|
|
|
return hash;
|
|
}
|
|
|
|
/* For a given symbol table nodes N1 and N2, we check that FUNCTION_DECLs
|
|
point to a same function. Comparison can be skipped if IGNORED_NODES
|
|
contains these nodes. */
|
|
|
|
bool
|
|
sem_function::compare_cgraph_references (hash_map <symtab_node *, sem_item *>
|
|
&ignored_nodes,
|
|
symtab_node *n1, symtab_node *n2)
|
|
{
|
|
if (n1 == n2 || (ignored_nodes.get (n1) && ignored_nodes.get (n2)))
|
|
return true;
|
|
|
|
/* TODO: add more precise comparison for weakrefs, etc. */
|
|
|
|
return return_false_with_msg ("different references");
|
|
}
|
|
|
|
/* If cgraph edges E1 and E2 are indirect calls, verify that
|
|
ECF flags are the same. */
|
|
|
|
bool sem_function::compare_edge_flags (cgraph_edge *e1, cgraph_edge *e2)
|
|
{
|
|
if (e1->indirect_info && e2->indirect_info)
|
|
{
|
|
int e1_flags = e1->indirect_info->ecf_flags;
|
|
int e2_flags = e2->indirect_info->ecf_flags;
|
|
|
|
if (e1_flags != e2_flags)
|
|
return return_false_with_msg ("ICF flags are different");
|
|
}
|
|
else if (e1->indirect_info || e2->indirect_info)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Fast equality function based on knowledge known in WPA. */
|
|
|
|
bool
|
|
sem_function::equals_wpa (sem_item *item,
|
|
hash_map <symtab_node *, sem_item *> &ignored_nodes)
|
|
{
|
|
gcc_assert (item->type == FUNC);
|
|
|
|
m_compared_func = static_cast<sem_function *> (item);
|
|
|
|
if (arg_types.length () != m_compared_func->arg_types.length ())
|
|
return return_false_with_msg ("different number of arguments");
|
|
|
|
/* Checking types of arguments. */
|
|
for (unsigned i = 0; i < arg_types.length (); i++)
|
|
{
|
|
/* This guard is here for function pointer with attributes (pr59927.c). */
|
|
if (!arg_types[i] || !m_compared_func->arg_types[i])
|
|
return return_false_with_msg ("NULL argument type");
|
|
|
|
/* Polymorphic comparison is executed just for non-leaf functions. */
|
|
bool is_not_leaf = get_node ()->callees != NULL
|
|
|| get_node ()->indirect_calls != NULL;
|
|
|
|
if (!func_checker::compatible_types_p (arg_types[i],
|
|
m_compared_func->arg_types[i],
|
|
is_not_leaf, i == 0))
|
|
return return_false_with_msg ("argument type is different");
|
|
}
|
|
|
|
/* Result type checking. */
|
|
if (!func_checker::compatible_types_p (result_type,
|
|
m_compared_func->result_type))
|
|
return return_false_with_msg ("result types are different");
|
|
|
|
if (node->num_references () != item->node->num_references ())
|
|
return return_false_with_msg ("different number of references");
|
|
|
|
ipa_ref *ref = NULL, *ref2 = NULL;
|
|
for (unsigned i = 0; node->iterate_reference (i, ref); i++)
|
|
{
|
|
item->node->iterate_reference (i, ref2);
|
|
|
|
if (!compare_cgraph_references (ignored_nodes, ref->referred, ref2->referred))
|
|
return false;
|
|
}
|
|
|
|
cgraph_edge *e1 = dyn_cast <cgraph_node *> (node)->callees;
|
|
cgraph_edge *e2 = dyn_cast <cgraph_node *> (item->node)->callees;
|
|
|
|
while (e1 && e2)
|
|
{
|
|
if (!compare_cgraph_references (ignored_nodes, e1->callee, e2->callee))
|
|
return false;
|
|
|
|
e1 = e1->next_callee;
|
|
e2 = e2->next_callee;
|
|
}
|
|
|
|
if (e1 || e2)
|
|
return return_false_with_msg ("different number of edges");
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Returns true if the item equals to ITEM given as argument. */
|
|
|
|
bool
|
|
sem_function::equals (sem_item *item,
|
|
hash_map <symtab_node *, sem_item *> &ignored_nodes)
|
|
{
|
|
gcc_assert (item->type == FUNC);
|
|
bool eq = equals_private (item, ignored_nodes);
|
|
|
|
if (m_checker != NULL)
|
|
{
|
|
delete m_checker;
|
|
m_checker = NULL;
|
|
}
|
|
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
fprintf (dump_file,
|
|
"Equals called for:%s:%s (%u:%u) (%s:%s) with result: %s\n\n",
|
|
name(), item->name (), node->order, item->node->order, asm_name (),
|
|
item->asm_name (), eq ? "true" : "false");
|
|
|
|
return eq;
|
|
}
|
|
|
|
/* Processes function equality comparison. */
|
|
|
|
bool
|
|
sem_function::equals_private (sem_item *item,
|
|
hash_map <symtab_node *, sem_item *> &ignored_nodes)
|
|
{
|
|
if (item->type != FUNC)
|
|
return false;
|
|
|
|
basic_block bb1, bb2;
|
|
edge e1, e2;
|
|
edge_iterator ei1, ei2;
|
|
bool result = true;
|
|
tree arg1, arg2;
|
|
|
|
m_compared_func = static_cast<sem_function *> (item);
|
|
|
|
gcc_assert (decl != item->decl);
|
|
|
|
if (bb_sorted.length () != m_compared_func->bb_sorted.length ()
|
|
|| edge_count != m_compared_func->edge_count
|
|
|| cfg_checksum != m_compared_func->cfg_checksum)
|
|
return return_false ();
|
|
|
|
if (!equals_wpa (item, ignored_nodes))
|
|
return false;
|
|
|
|
/* Checking function TARGET and OPTIMIZATION flags. */
|
|
cl_target_option *tar1 = target_opts_for_fn (decl);
|
|
cl_target_option *tar2 = target_opts_for_fn (m_compared_func->decl);
|
|
|
|
if (tar1 != NULL && tar2 != NULL)
|
|
{
|
|
if (!cl_target_option_eq (tar1, tar2))
|
|
{
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, "target flags difference");
|
|
cl_target_option_print_diff (dump_file, 2, tar1, tar2);
|
|
}
|
|
|
|
return return_false_with_msg ("Target flags are different");
|
|
}
|
|
}
|
|
else if (tar1 != NULL || tar2 != NULL)
|
|
return return_false_with_msg ("Target flags are different");
|
|
|
|
cl_optimization *opt1 = opts_for_fn (decl);
|
|
cl_optimization *opt2 = opts_for_fn (m_compared_func->decl);
|
|
|
|
if (opt1 != NULL && opt2 != NULL)
|
|
{
|
|
if (memcmp (opt1, opt2, sizeof(cl_optimization)))
|
|
{
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, "optimization flags difference");
|
|
cl_optimization_print_diff (dump_file, 2, opt1, opt2);
|
|
}
|
|
|
|
return return_false_with_msg ("optimization flags are different");
|
|
}
|
|
}
|
|
else if (opt1 != NULL || opt2 != NULL)
|
|
return return_false_with_msg ("optimization flags are different");
|
|
|
|
/* Checking function arguments. */
|
|
tree decl1 = DECL_ATTRIBUTES (decl);
|
|
tree decl2 = DECL_ATTRIBUTES (m_compared_func->decl);
|
|
|
|
m_checker = new func_checker (decl, m_compared_func->decl,
|
|
compare_polymorphic_p (),
|
|
false,
|
|
&refs_set,
|
|
&m_compared_func->refs_set);
|
|
while (decl1)
|
|
{
|
|
if (decl2 == NULL)
|
|
return return_false ();
|
|
|
|
if (get_attribute_name (decl1) != get_attribute_name (decl2))
|
|
return return_false ();
|
|
|
|
tree attr_value1 = TREE_VALUE (decl1);
|
|
tree attr_value2 = TREE_VALUE (decl2);
|
|
|
|
if (attr_value1 && attr_value2)
|
|
{
|
|
bool ret = m_checker->compare_operand (TREE_VALUE (attr_value1),
|
|
TREE_VALUE (attr_value2));
|
|
if (!ret)
|
|
return return_false_with_msg ("attribute values are different");
|
|
}
|
|
else if (!attr_value1 && !attr_value2)
|
|
{}
|
|
else
|
|
return return_false ();
|
|
|
|
decl1 = TREE_CHAIN (decl1);
|
|
decl2 = TREE_CHAIN (decl2);
|
|
}
|
|
|
|
if (decl1 != decl2)
|
|
return return_false();
|
|
|
|
|
|
for (arg1 = DECL_ARGUMENTS (decl),
|
|
arg2 = DECL_ARGUMENTS (m_compared_func->decl);
|
|
arg1; arg1 = DECL_CHAIN (arg1), arg2 = DECL_CHAIN (arg2))
|
|
if (!m_checker->compare_decl (arg1, arg2))
|
|
return return_false ();
|
|
|
|
/* Fill-up label dictionary. */
|
|
for (unsigned i = 0; i < bb_sorted.length (); ++i)
|
|
{
|
|
m_checker->parse_labels (bb_sorted[i]);
|
|
m_checker->parse_labels (m_compared_func->bb_sorted[i]);
|
|
}
|
|
|
|
/* Checking all basic blocks. */
|
|
for (unsigned i = 0; i < bb_sorted.length (); ++i)
|
|
if(!m_checker->compare_bb (bb_sorted[i], m_compared_func->bb_sorted[i]))
|
|
return return_false();
|
|
|
|
dump_message ("All BBs are equal\n");
|
|
|
|
auto_vec <int> bb_dict;
|
|
|
|
/* Basic block edges check. */
|
|
for (unsigned i = 0; i < bb_sorted.length (); ++i)
|
|
{
|
|
bb1 = bb_sorted[i]->bb;
|
|
bb2 = m_compared_func->bb_sorted[i]->bb;
|
|
|
|
ei2 = ei_start (bb2->preds);
|
|
|
|
for (ei1 = ei_start (bb1->preds); ei_cond (ei1, &e1); ei_next (&ei1))
|
|
{
|
|
ei_cond (ei2, &e2);
|
|
|
|
if (e1->flags != e2->flags)
|
|
return return_false_with_msg ("flags comparison returns false");
|
|
|
|
if (!bb_dict_test (&bb_dict, e1->src->index, e2->src->index))
|
|
return return_false_with_msg ("edge comparison returns false");
|
|
|
|
if (!bb_dict_test (&bb_dict, e1->dest->index, e2->dest->index))
|
|
return return_false_with_msg ("BB comparison returns false");
|
|
|
|
if (!m_checker->compare_edge (e1, e2))
|
|
return return_false_with_msg ("edge comparison returns false");
|
|
|
|
ei_next (&ei2);
|
|
}
|
|
}
|
|
|
|
/* Basic block PHI nodes comparison. */
|
|
for (unsigned i = 0; i < bb_sorted.length (); i++)
|
|
if (!compare_phi_node (bb_sorted[i]->bb, m_compared_func->bb_sorted[i]->bb))
|
|
return return_false_with_msg ("PHI node comparison returns false");
|
|
|
|
return result;
|
|
}
|
|
|
|
/* Set LOCAL_P of NODE to true if DATA is non-NULL.
|
|
Helper for call_for_symbol_thunks_and_aliases. */
|
|
|
|
static bool
|
|
set_local (cgraph_node *node, void *data)
|
|
{
|
|
node->local.local = data != NULL;
|
|
return false;
|
|
}
|
|
|
|
/* Merges instance with an ALIAS_ITEM, where alias, thunk or redirection can
|
|
be applied. */
|
|
bool
|
|
sem_function::merge (sem_item *alias_item)
|
|
{
|
|
gcc_assert (alias_item->type == FUNC);
|
|
|
|
sem_function *alias_func = static_cast<sem_function *> (alias_item);
|
|
|
|
cgraph_node *original = get_node ();
|
|
cgraph_node *local_original = original;
|
|
cgraph_node *alias = alias_func->get_node ();
|
|
bool original_address_matters;
|
|
bool alias_address_matters;
|
|
|
|
bool create_thunk = false;
|
|
bool create_alias = false;
|
|
bool redirect_callers = false;
|
|
bool original_discardable = false;
|
|
|
|
/* Do not attempt to mix functions from different user sections;
|
|
we do not know what user intends with those. */
|
|
if (((DECL_SECTION_NAME (original->decl) && !original->implicit_section)
|
|
|| (DECL_SECTION_NAME (alias->decl) && !alias->implicit_section))
|
|
&& DECL_SECTION_NAME (original->decl) != DECL_SECTION_NAME (alias->decl))
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file,
|
|
"Not unifying; original and alias are in different sections.\n\n");
|
|
return false;
|
|
}
|
|
|
|
/* See if original is in a section that can be discarded if the main
|
|
symbol is not used. */
|
|
if (DECL_EXTERNAL (original->decl))
|
|
original_discardable = true;
|
|
if (original->resolution == LDPR_PREEMPTED_REG
|
|
|| original->resolution == LDPR_PREEMPTED_IR)
|
|
original_discardable = true;
|
|
if (original->can_be_discarded_p ())
|
|
original_discardable = true;
|
|
|
|
/* See if original and/or alias address can be compared for equality. */
|
|
original_address_matters
|
|
= (!DECL_VIRTUAL_P (original->decl)
|
|
&& (original->externally_visible
|
|
|| original->address_taken_from_non_vtable_p ()));
|
|
alias_address_matters
|
|
= (!DECL_VIRTUAL_P (alias->decl)
|
|
&& (alias->externally_visible
|
|
|| alias->address_taken_from_non_vtable_p ()));
|
|
|
|
/* If alias and original can be compared for address equality, we need
|
|
to create a thunk. Also we can not create extra aliases into discardable
|
|
section (or we risk link failures when section is discarded). */
|
|
if ((original_address_matters
|
|
&& alias_address_matters)
|
|
|| original_discardable
|
|
|| DECL_COMDAT_GROUP (alias->decl)
|
|
|| !sem_item::target_supports_symbol_aliases_p ())
|
|
{
|
|
create_thunk = !stdarg_p (TREE_TYPE (alias->decl));
|
|
create_alias = false;
|
|
/* When both alias and original are not overwritable, we can save
|
|
the extra thunk wrapper for direct calls. */
|
|
redirect_callers
|
|
= (!original_discardable
|
|
&& !DECL_COMDAT_GROUP (alias->decl)
|
|
&& alias->get_availability () > AVAIL_INTERPOSABLE
|
|
&& original->get_availability () > AVAIL_INTERPOSABLE
|
|
&& !alias->instrumented_version);
|
|
}
|
|
else
|
|
{
|
|
create_alias = true;
|
|
create_thunk = false;
|
|
redirect_callers = false;
|
|
}
|
|
|
|
/* We want thunk to always jump to the local function body
|
|
unless the body is comdat and may be optimized out. */
|
|
if ((create_thunk || redirect_callers)
|
|
&& (!original_discardable
|
|
|| (DECL_COMDAT_GROUP (original->decl)
|
|
&& (DECL_COMDAT_GROUP (original->decl)
|
|
== DECL_COMDAT_GROUP (alias->decl)))))
|
|
local_original
|
|
= dyn_cast <cgraph_node *> (original->noninterposable_alias ());
|
|
|
|
if (!local_original)
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, "Noninterposable alias cannot be created.\n\n");
|
|
|
|
return false;
|
|
}
|
|
|
|
if (!decl_binds_to_current_def_p (alias->decl))
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, "Declaration does not bind to currect definition.\n\n");
|
|
return false;
|
|
}
|
|
|
|
if (redirect_callers)
|
|
{
|
|
/* If alias is non-overwritable then
|
|
all direct calls are safe to be redirected to the original. */
|
|
bool redirected = false;
|
|
while (alias->callers)
|
|
{
|
|
cgraph_edge *e = alias->callers;
|
|
e->redirect_callee (local_original);
|
|
push_cfun (DECL_STRUCT_FUNCTION (e->caller->decl));
|
|
|
|
if (e->call_stmt)
|
|
e->redirect_call_stmt_to_callee ();
|
|
|
|
pop_cfun ();
|
|
redirected = true;
|
|
}
|
|
|
|
alias->icf_merged = true;
|
|
if (local_original->lto_file_data
|
|
&& alias->lto_file_data
|
|
&& local_original->lto_file_data != alias->lto_file_data)
|
|
local_original->merged = true;
|
|
|
|
/* The alias function is removed if symbol address
|
|
does not matter. */
|
|
if (!alias_address_matters)
|
|
alias->remove ();
|
|
|
|
if (dump_file && redirected)
|
|
fprintf (dump_file, "Callgraph local calls have been redirected.\n\n");
|
|
}
|
|
/* If the condtion above is not met, we are lucky and can turn the
|
|
function into real alias. */
|
|
else if (create_alias)
|
|
{
|
|
alias->icf_merged = true;
|
|
if (local_original->lto_file_data
|
|
&& alias->lto_file_data
|
|
&& local_original->lto_file_data != alias->lto_file_data)
|
|
local_original->merged = true;
|
|
|
|
/* Remove the function's body. */
|
|
ipa_merge_profiles (original, alias);
|
|
alias->release_body (true);
|
|
alias->reset ();
|
|
|
|
/* Create the alias. */
|
|
cgraph_node::create_alias (alias_func->decl, decl);
|
|
alias->resolve_alias (original);
|
|
|
|
original->call_for_symbol_thunks_and_aliases
|
|
(set_local, (void *)(size_t) original->local_p (), true);
|
|
|
|
if (dump_file)
|
|
fprintf (dump_file, "Callgraph alias has been created.\n\n");
|
|
}
|
|
else if (create_thunk)
|
|
{
|
|
if (DECL_COMDAT_GROUP (alias->decl))
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, "Callgraph thunk cannot be created because of COMDAT\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
if (DECL_STATIC_CHAIN (alias->decl))
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, "Thunk creation is risky for static-chain functions.\n\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
alias->icf_merged = true;
|
|
if (local_original->lto_file_data
|
|
&& alias->lto_file_data
|
|
&& local_original->lto_file_data != alias->lto_file_data)
|
|
local_original->merged = true;
|
|
ipa_merge_profiles (local_original, alias, true);
|
|
alias->create_wrapper (local_original);
|
|
|
|
if (dump_file)
|
|
fprintf (dump_file, "Callgraph thunk has been created.\n\n");
|
|
}
|
|
else if (dump_file)
|
|
fprintf (dump_file, "Callgraph merge operation cannot be performed.\n\n");
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Semantic item initialization function. */
|
|
|
|
void
|
|
sem_function::init (void)
|
|
{
|
|
if (in_lto_p)
|
|
get_node ()->get_untransformed_body ();
|
|
|
|
tree fndecl = node->decl;
|
|
function *func = DECL_STRUCT_FUNCTION (fndecl);
|
|
|
|
gcc_assert (func);
|
|
gcc_assert (SSANAMES (func));
|
|
|
|
ssa_names_size = SSANAMES (func)->length ();
|
|
node = node;
|
|
|
|
decl = fndecl;
|
|
region_tree = func->eh->region_tree;
|
|
|
|
/* iterating all function arguments. */
|
|
arg_count = count_formal_params (fndecl);
|
|
|
|
edge_count = n_edges_for_fn (func);
|
|
cfg_checksum = coverage_compute_cfg_checksum (func);
|
|
|
|
inchash::hash hstate;
|
|
|
|
basic_block bb;
|
|
FOR_EACH_BB_FN (bb, func)
|
|
{
|
|
unsigned nondbg_stmt_count = 0;
|
|
|
|
edge e;
|
|
for (edge_iterator ei = ei_start (bb->preds); ei_cond (ei, &e); ei_next (&ei))
|
|
cfg_checksum = iterative_hash_host_wide_int (e->flags,
|
|
cfg_checksum);
|
|
|
|
for (gimple_stmt_iterator gsi = gsi_start_bb (bb); !gsi_end_p (gsi);
|
|
gsi_next (&gsi))
|
|
{
|
|
gimple stmt = gsi_stmt (gsi);
|
|
|
|
if (gimple_code (stmt) != GIMPLE_DEBUG)
|
|
{
|
|
hash_stmt (&hstate, stmt);
|
|
nondbg_stmt_count++;
|
|
}
|
|
}
|
|
|
|
gcode_hash = hstate.end ();
|
|
bb_sizes.safe_push (nondbg_stmt_count);
|
|
|
|
/* Inserting basic block to hash table. */
|
|
sem_bb *semantic_bb = new sem_bb (bb, nondbg_stmt_count,
|
|
EDGE_COUNT (bb->preds) + EDGE_COUNT (bb->succs));
|
|
|
|
bb_sorted.safe_push (semantic_bb);
|
|
}
|
|
|
|
parse_tree_args ();
|
|
}
|
|
|
|
/* Improve accumulated hash for HSTATE based on a gimple statement STMT. */
|
|
|
|
void
|
|
sem_function::hash_stmt (inchash::hash *hstate, gimple stmt)
|
|
{
|
|
enum gimple_code code = gimple_code (stmt);
|
|
|
|
hstate->add_int (code);
|
|
|
|
if (code == GIMPLE_CALL)
|
|
{
|
|
/* Checking of argument. */
|
|
for (unsigned i = 0; i < gimple_call_num_args (stmt); ++i)
|
|
{
|
|
tree argument = gimple_call_arg (stmt, i);
|
|
|
|
switch (TREE_CODE (argument))
|
|
{
|
|
case INTEGER_CST:
|
|
if (tree_fits_shwi_p (argument))
|
|
hstate->add_wide_int (tree_to_shwi (argument));
|
|
else if (tree_fits_uhwi_p (argument))
|
|
hstate->add_wide_int (tree_to_uhwi (argument));
|
|
break;
|
|
case REAL_CST:
|
|
REAL_VALUE_TYPE c;
|
|
HOST_WIDE_INT n;
|
|
|
|
c = TREE_REAL_CST (argument);
|
|
n = real_to_integer (&c);
|
|
|
|
hstate->add_wide_int (n);
|
|
break;
|
|
case ADDR_EXPR:
|
|
{
|
|
tree addr_operand = TREE_OPERAND (argument, 0);
|
|
|
|
if (TREE_CODE (addr_operand) == STRING_CST)
|
|
hstate->add (TREE_STRING_POINTER (addr_operand),
|
|
TREE_STRING_LENGTH (addr_operand));
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* Return true if polymorphic comparison must be processed. */
|
|
|
|
bool
|
|
sem_function::compare_polymorphic_p (void)
|
|
{
|
|
return get_node ()->callees != NULL
|
|
|| get_node ()->indirect_calls != NULL
|
|
|| m_compared_func->get_node ()->callees != NULL
|
|
|| m_compared_func->get_node ()->indirect_calls != NULL;
|
|
}
|
|
|
|
/* For a given call graph NODE, the function constructs new
|
|
semantic function item. */
|
|
|
|
sem_function *
|
|
sem_function::parse (cgraph_node *node, bitmap_obstack *stack)
|
|
{
|
|
tree fndecl = node->decl;
|
|
function *func = DECL_STRUCT_FUNCTION (fndecl);
|
|
|
|
/* TODO: add support for thunks and aliases. */
|
|
|
|
if (!func || !node->has_gimple_body_p ())
|
|
return NULL;
|
|
|
|
if (lookup_attribute_by_prefix ("omp ", DECL_ATTRIBUTES (node->decl)) != NULL)
|
|
return NULL;
|
|
|
|
sem_function *f = new sem_function (node, 0, stack);
|
|
|
|
f->init ();
|
|
|
|
return f;
|
|
}
|
|
|
|
/* Parses function arguments and result type. */
|
|
|
|
void
|
|
sem_function::parse_tree_args (void)
|
|
{
|
|
tree result;
|
|
|
|
if (arg_types.exists ())
|
|
arg_types.release ();
|
|
|
|
arg_types.create (4);
|
|
tree fnargs = DECL_ARGUMENTS (decl);
|
|
|
|
for (tree parm = fnargs; parm; parm = DECL_CHAIN (parm))
|
|
arg_types.safe_push (DECL_ARG_TYPE (parm));
|
|
|
|
/* Function result type. */
|
|
result = DECL_RESULT (decl);
|
|
result_type = result ? TREE_TYPE (result) : NULL;
|
|
|
|
/* During WPA, we can get arguments by following method. */
|
|
if (!fnargs)
|
|
{
|
|
tree type = TYPE_ARG_TYPES (TREE_TYPE (decl));
|
|
for (tree parm = type; parm; parm = TREE_CHAIN (parm))
|
|
arg_types.safe_push (TYPE_CANONICAL (TREE_VALUE (parm)));
|
|
|
|
result_type = TREE_TYPE (TREE_TYPE (decl));
|
|
}
|
|
}
|
|
|
|
/* For given basic blocks BB1 and BB2 (from functions FUNC1 and FUNC),
|
|
return true if phi nodes are semantically equivalent in these blocks . */
|
|
|
|
bool
|
|
sem_function::compare_phi_node (basic_block bb1, basic_block bb2)
|
|
{
|
|
gphi_iterator si1, si2;
|
|
gphi *phi1, *phi2;
|
|
unsigned size1, size2, i;
|
|
tree t1, t2;
|
|
edge e1, e2;
|
|
|
|
gcc_assert (bb1 != NULL);
|
|
gcc_assert (bb2 != NULL);
|
|
|
|
si2 = gsi_start_phis (bb2);
|
|
for (si1 = gsi_start_phis (bb1); !gsi_end_p (si1);
|
|
gsi_next (&si1))
|
|
{
|
|
gsi_next_nonvirtual_phi (&si1);
|
|
gsi_next_nonvirtual_phi (&si2);
|
|
|
|
if (gsi_end_p (si1) && gsi_end_p (si2))
|
|
break;
|
|
|
|
if (gsi_end_p (si1) || gsi_end_p (si2))
|
|
return return_false();
|
|
|
|
phi1 = si1.phi ();
|
|
phi2 = si2.phi ();
|
|
|
|
tree phi_result1 = gimple_phi_result (phi1);
|
|
tree phi_result2 = gimple_phi_result (phi2);
|
|
|
|
if (!m_checker->compare_operand (phi_result1, phi_result2))
|
|
return return_false_with_msg ("PHI results are different");
|
|
|
|
size1 = gimple_phi_num_args (phi1);
|
|
size2 = gimple_phi_num_args (phi2);
|
|
|
|
if (size1 != size2)
|
|
return return_false ();
|
|
|
|
for (i = 0; i < size1; ++i)
|
|
{
|
|
t1 = gimple_phi_arg (phi1, i)->def;
|
|
t2 = gimple_phi_arg (phi2, i)->def;
|
|
|
|
if (!m_checker->compare_operand (t1, t2))
|
|
return return_false ();
|
|
|
|
e1 = gimple_phi_arg_edge (phi1, i);
|
|
e2 = gimple_phi_arg_edge (phi2, i);
|
|
|
|
if (!m_checker->compare_edge (e1, e2))
|
|
return return_false ();
|
|
}
|
|
|
|
gsi_next (&si2);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Returns true if tree T can be compared as a handled component. */
|
|
|
|
bool
|
|
sem_function::icf_handled_component_p (tree t)
|
|
{
|
|
tree_code tc = TREE_CODE (t);
|
|
|
|
return ((handled_component_p (t))
|
|
|| tc == ADDR_EXPR || tc == MEM_REF || tc == REALPART_EXPR
|
|
|| tc == IMAGPART_EXPR || tc == OBJ_TYPE_REF);
|
|
}
|
|
|
|
/* Basic blocks dictionary BB_DICT returns true if SOURCE index BB
|
|
corresponds to TARGET. */
|
|
|
|
bool
|
|
sem_function::bb_dict_test (vec<int> *bb_dict, int source, int target)
|
|
{
|
|
source++;
|
|
target++;
|
|
|
|
if (bb_dict->length () <= (unsigned)source)
|
|
bb_dict->safe_grow_cleared (source + 1);
|
|
|
|
if ((*bb_dict)[source] == 0)
|
|
{
|
|
(*bb_dict)[source] = target;
|
|
return true;
|
|
}
|
|
else
|
|
return (*bb_dict)[source] == target;
|
|
}
|
|
|
|
/* Iterates all tree types in T1 and T2 and returns true if all types
|
|
are compatible. If COMPARE_POLYMORPHIC is set to true,
|
|
more strict comparison is executed. */
|
|
|
|
bool
|
|
sem_function::compare_type_list (tree t1, tree t2, bool compare_polymorphic)
|
|
{
|
|
tree tv1, tv2;
|
|
tree_code tc1, tc2;
|
|
|
|
if (!t1 && !t2)
|
|
return true;
|
|
|
|
while (t1 != NULL && t2 != NULL)
|
|
{
|
|
tv1 = TREE_VALUE (t1);
|
|
tv2 = TREE_VALUE (t2);
|
|
|
|
tc1 = TREE_CODE (tv1);
|
|
tc2 = TREE_CODE (tv2);
|
|
|
|
if (tc1 == NOP_EXPR && tc2 == NOP_EXPR)
|
|
{}
|
|
else if (tc1 == NOP_EXPR || tc2 == NOP_EXPR)
|
|
return false;
|
|
else if (!func_checker::compatible_types_p (tv1, tv2, compare_polymorphic))
|
|
return false;
|
|
|
|
t1 = TREE_CHAIN (t1);
|
|
t2 = TREE_CHAIN (t2);
|
|
}
|
|
|
|
return !(t1 || t2);
|
|
}
|
|
|
|
|
|
/* Semantic variable constructor that uses STACK as bitmap memory stack. */
|
|
|
|
sem_variable::sem_variable (bitmap_obstack *stack): sem_item (VAR, stack)
|
|
{
|
|
}
|
|
|
|
/* Constructor based on varpool node _NODE with computed hash _HASH.
|
|
Bitmap STACK is used for memory allocation. */
|
|
|
|
sem_variable::sem_variable (varpool_node *node, hashval_t _hash,
|
|
bitmap_obstack *stack): sem_item(VAR,
|
|
node, _hash, stack)
|
|
{
|
|
gcc_checking_assert (node);
|
|
gcc_checking_assert (get_node ());
|
|
}
|
|
|
|
/* Returns true if the item equals to ITEM given as argument. */
|
|
|
|
bool
|
|
sem_variable::equals (sem_item *item,
|
|
hash_map <symtab_node *, sem_item *> & ARG_UNUSED (ignored_nodes))
|
|
{
|
|
gcc_assert (item->type == VAR);
|
|
|
|
sem_variable *v = static_cast<sem_variable *>(item);
|
|
|
|
if (!ctor || !v->ctor)
|
|
return return_false_with_msg ("ctor is missing for semantic variable");
|
|
|
|
return sem_variable::equals (ctor, v->ctor);
|
|
}
|
|
|
|
/* Compares trees T1 and T2 for semantic equality. */
|
|
|
|
bool
|
|
sem_variable::equals (tree t1, tree t2)
|
|
{
|
|
tree_code tc1 = TREE_CODE (t1);
|
|
tree_code tc2 = TREE_CODE (t2);
|
|
|
|
if (tc1 != tc2)
|
|
return false;
|
|
|
|
switch (tc1)
|
|
{
|
|
case CONSTRUCTOR:
|
|
{
|
|
unsigned len1 = vec_safe_length (CONSTRUCTOR_ELTS (t1));
|
|
unsigned len2 = vec_safe_length (CONSTRUCTOR_ELTS (t2));
|
|
|
|
if (len1 != len2)
|
|
return false;
|
|
|
|
for (unsigned i = 0; i < len1; i++)
|
|
if (!sem_variable::equals (CONSTRUCTOR_ELT (t1, i)->value,
|
|
CONSTRUCTOR_ELT (t2, i)->value)
|
|
|| CONSTRUCTOR_ELT (t1, i)->index != CONSTRUCTOR_ELT (t2, i)->index)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
case MEM_REF:
|
|
{
|
|
tree x1 = TREE_OPERAND (t1, 0);
|
|
tree x2 = TREE_OPERAND (t2, 0);
|
|
tree y1 = TREE_OPERAND (t1, 1);
|
|
tree y2 = TREE_OPERAND (t2, 1);
|
|
|
|
if (!func_checker::compatible_types_p (TREE_TYPE (x1), TREE_TYPE (x2),
|
|
true))
|
|
return return_false ();
|
|
|
|
/* Type of the offset on MEM_REF does not matter. */
|
|
return sem_variable::equals (x1, x2)
|
|
&& wi::to_offset (y1) == wi::to_offset (y2);
|
|
}
|
|
case NOP_EXPR:
|
|
case ADDR_EXPR:
|
|
{
|
|
tree op1 = TREE_OPERAND (t1, 0);
|
|
tree op2 = TREE_OPERAND (t2, 0);
|
|
return sem_variable::equals (op1, op2);
|
|
}
|
|
case FUNCTION_DECL:
|
|
case VAR_DECL:
|
|
case FIELD_DECL:
|
|
case LABEL_DECL:
|
|
return t1 == t2;
|
|
case INTEGER_CST:
|
|
return func_checker::compatible_types_p (TREE_TYPE (t1), TREE_TYPE (t2),
|
|
true)
|
|
&& wi::to_offset (t1) == wi::to_offset (t2);
|
|
case STRING_CST:
|
|
case REAL_CST:
|
|
case COMPLEX_CST:
|
|
return operand_equal_p (t1, t2, OEP_ONLY_CONST);
|
|
case COMPONENT_REF:
|
|
case ARRAY_REF:
|
|
case POINTER_PLUS_EXPR:
|
|
{
|
|
tree x1 = TREE_OPERAND (t1, 0);
|
|
tree x2 = TREE_OPERAND (t2, 0);
|
|
tree y1 = TREE_OPERAND (t1, 1);
|
|
tree y2 = TREE_OPERAND (t2, 1);
|
|
|
|
return sem_variable::equals (x1, x2) && sem_variable::equals (y1, y2);
|
|
}
|
|
case ERROR_MARK:
|
|
return return_false_with_msg ("ERROR_MARK");
|
|
default:
|
|
return return_false_with_msg ("Unknown TREE code reached");
|
|
}
|
|
}
|
|
|
|
/* Parser function that visits a varpool NODE. */
|
|
|
|
sem_variable *
|
|
sem_variable::parse (varpool_node *node, bitmap_obstack *stack)
|
|
{
|
|
tree decl = node->decl;
|
|
|
|
bool readonly = TYPE_P (decl) ? TYPE_READONLY (decl) : TREE_READONLY (decl);
|
|
if (!readonly)
|
|
return NULL;
|
|
|
|
bool can_handle = DECL_VIRTUAL_P (decl)
|
|
|| flag_merge_constants >= 2
|
|
|| (!TREE_ADDRESSABLE (decl) && !node->externally_visible);
|
|
|
|
if (!can_handle || DECL_EXTERNAL (decl))
|
|
return NULL;
|
|
|
|
tree ctor = ctor_for_folding (decl);
|
|
if (!ctor)
|
|
return NULL;
|
|
|
|
sem_variable *v = new sem_variable (node, 0, stack);
|
|
|
|
v->init ();
|
|
|
|
return v;
|
|
}
|
|
|
|
/* References independent hash function. */
|
|
|
|
hashval_t
|
|
sem_variable::get_hash (void)
|
|
{
|
|
if (hash)
|
|
return hash;
|
|
|
|
inchash::hash hstate;
|
|
|
|
hstate.add_int (456346417);
|
|
hstate.add_int (TREE_CODE (ctor));
|
|
|
|
if (TREE_CODE (ctor) == CONSTRUCTOR)
|
|
{
|
|
unsigned length = vec_safe_length (CONSTRUCTOR_ELTS (ctor));
|
|
hstate.add_int (length);
|
|
}
|
|
|
|
hash = hstate.end ();
|
|
|
|
return hash;
|
|
}
|
|
|
|
/* Merges instance with an ALIAS_ITEM, where alias, thunk or redirection can
|
|
be applied. */
|
|
|
|
bool
|
|
sem_variable::merge (sem_item *alias_item)
|
|
{
|
|
gcc_assert (alias_item->type == VAR);
|
|
|
|
if (!sem_item::target_supports_symbol_aliases_p ())
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, "Symbol aliases are not supported by target\n\n");
|
|
return false;
|
|
}
|
|
|
|
sem_variable *alias_var = static_cast<sem_variable *> (alias_item);
|
|
|
|
varpool_node *original = get_node ();
|
|
varpool_node *alias = alias_var->get_node ();
|
|
bool original_discardable = false;
|
|
|
|
/* See if original is in a section that can be discarded if the main
|
|
symbol is not used. */
|
|
if (DECL_EXTERNAL (original->decl))
|
|
original_discardable = true;
|
|
if (original->resolution == LDPR_PREEMPTED_REG
|
|
|| original->resolution == LDPR_PREEMPTED_IR)
|
|
original_discardable = true;
|
|
if (original->can_be_discarded_p ())
|
|
original_discardable = true;
|
|
|
|
gcc_assert (!TREE_ASM_WRITTEN (alias->decl));
|
|
|
|
if (original_discardable || DECL_EXTERNAL (alias_var->decl) ||
|
|
!compare_sections (alias_var))
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, "Varpool alias cannot be created\n\n");
|
|
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
// alias cycle creation check
|
|
varpool_node *n = original;
|
|
|
|
while (n->alias)
|
|
{
|
|
n = n->get_alias_target ();
|
|
if (n == alias)
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, "Varpool alias cannot be created (alias cycle).\n\n");
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
alias->analyzed = false;
|
|
|
|
DECL_INITIAL (alias->decl) = NULL;
|
|
alias->need_bounds_init = false;
|
|
alias->remove_all_references ();
|
|
|
|
varpool_node::create_alias (alias_var->decl, decl);
|
|
alias->resolve_alias (original);
|
|
|
|
if (dump_file)
|
|
fprintf (dump_file, "Varpool alias has been created.\n\n");
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool
|
|
sem_variable::compare_sections (sem_variable *alias)
|
|
{
|
|
const char *source = node->get_section ();
|
|
const char *target = alias->node->get_section();
|
|
|
|
if (source == NULL && target == NULL)
|
|
return true;
|
|
else if(!source || !target)
|
|
return false;
|
|
else
|
|
return strcmp (source, target) == 0;
|
|
}
|
|
|
|
/* Dump symbol to FILE. */
|
|
|
|
void
|
|
sem_variable::dump_to_file (FILE *file)
|
|
{
|
|
gcc_assert (file);
|
|
|
|
print_node (file, "", decl, 0);
|
|
fprintf (file, "\n\n");
|
|
}
|
|
|
|
/* Iterates though a constructor and identifies tree references
|
|
we are interested in semantic function equality. */
|
|
|
|
void
|
|
sem_variable::parse_tree_refs (tree t)
|
|
{
|
|
switch (TREE_CODE (t))
|
|
{
|
|
case CONSTRUCTOR:
|
|
{
|
|
unsigned length = vec_safe_length (CONSTRUCTOR_ELTS (t));
|
|
|
|
for (unsigned i = 0; i < length; i++)
|
|
parse_tree_refs(CONSTRUCTOR_ELT (t, i)->value);
|
|
|
|
break;
|
|
}
|
|
case NOP_EXPR:
|
|
case ADDR_EXPR:
|
|
{
|
|
tree op = TREE_OPERAND (t, 0);
|
|
parse_tree_refs (op);
|
|
break;
|
|
}
|
|
case FUNCTION_DECL:
|
|
{
|
|
tree_refs.safe_push (t);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
unsigned int sem_item_optimizer::class_id = 0;
|
|
|
|
sem_item_optimizer::sem_item_optimizer (): worklist (0), m_classes (0),
|
|
m_classes_count (0), m_cgraph_node_hooks (NULL), m_varpool_node_hooks (NULL)
|
|
{
|
|
m_items.create (0);
|
|
bitmap_obstack_initialize (&m_bmstack);
|
|
}
|
|
|
|
sem_item_optimizer::~sem_item_optimizer ()
|
|
{
|
|
for (unsigned int i = 0; i < m_items.length (); i++)
|
|
delete m_items[i];
|
|
|
|
for (hash_table<congruence_class_group_hash>::iterator it = m_classes.begin ();
|
|
it != m_classes.end (); ++it)
|
|
{
|
|
for (unsigned int i = 0; i < (*it)->classes.length (); i++)
|
|
delete (*it)->classes[i];
|
|
|
|
(*it)->classes.release ();
|
|
free (*it);
|
|
}
|
|
|
|
m_items.release ();
|
|
|
|
bitmap_obstack_release (&m_bmstack);
|
|
}
|
|
|
|
/* Write IPA ICF summary for symbols. */
|
|
|
|
void
|
|
sem_item_optimizer::write_summary (void)
|
|
{
|
|
unsigned int count = 0;
|
|
|
|
output_block *ob = create_output_block (LTO_section_ipa_icf);
|
|
lto_symtab_encoder_t encoder = ob->decl_state->symtab_node_encoder;
|
|
ob->symbol = NULL;
|
|
|
|
/* Calculate number of symbols to be serialized. */
|
|
for (lto_symtab_encoder_iterator lsei = lsei_start_in_partition (encoder);
|
|
!lsei_end_p (lsei);
|
|
lsei_next_in_partition (&lsei))
|
|
{
|
|
symtab_node *node = lsei_node (lsei);
|
|
|
|
if (m_symtab_node_map.get (node))
|
|
count++;
|
|
}
|
|
|
|
streamer_write_uhwi (ob, count);
|
|
|
|
/* Process all of the symbols. */
|
|
for (lto_symtab_encoder_iterator lsei = lsei_start_in_partition (encoder);
|
|
!lsei_end_p (lsei);
|
|
lsei_next_in_partition (&lsei))
|
|
{
|
|
symtab_node *node = lsei_node (lsei);
|
|
|
|
sem_item **item = m_symtab_node_map.get (node);
|
|
|
|
if (item && *item)
|
|
{
|
|
int node_ref = lto_symtab_encoder_encode (encoder, node);
|
|
streamer_write_uhwi_stream (ob->main_stream, node_ref);
|
|
|
|
streamer_write_uhwi (ob, (*item)->get_hash ());
|
|
}
|
|
}
|
|
|
|
streamer_write_char_stream (ob->main_stream, 0);
|
|
produce_asm (ob, NULL);
|
|
destroy_output_block (ob);
|
|
}
|
|
|
|
/* Reads a section from LTO stream file FILE_DATA. Input block for DATA
|
|
contains LEN bytes. */
|
|
|
|
void
|
|
sem_item_optimizer::read_section (lto_file_decl_data *file_data,
|
|
const char *data, size_t len)
|
|
{
|
|
const lto_function_header *header =
|
|
(const lto_function_header *) data;
|
|
const int cfg_offset = sizeof (lto_function_header);
|
|
const int main_offset = cfg_offset + header->cfg_size;
|
|
const int string_offset = main_offset + header->main_size;
|
|
data_in *data_in;
|
|
unsigned int i;
|
|
unsigned int count;
|
|
|
|
lto_input_block ib_main ((const char *) data + main_offset, 0,
|
|
header->main_size, file_data->mode_table);
|
|
|
|
data_in =
|
|
lto_data_in_create (file_data, (const char *) data + string_offset,
|
|
header->string_size, vNULL);
|
|
|
|
count = streamer_read_uhwi (&ib_main);
|
|
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
unsigned int index;
|
|
symtab_node *node;
|
|
lto_symtab_encoder_t encoder;
|
|
|
|
index = streamer_read_uhwi (&ib_main);
|
|
encoder = file_data->symtab_node_encoder;
|
|
node = lto_symtab_encoder_deref (encoder, index);
|
|
|
|
hashval_t hash = streamer_read_uhwi (&ib_main);
|
|
|
|
gcc_assert (node->definition);
|
|
|
|
if (dump_file)
|
|
fprintf (dump_file, "Symbol added:%s (tree: %p, uid:%u)\n", node->asm_name (),
|
|
(void *) node->decl, node->order);
|
|
|
|
if (is_a<cgraph_node *> (node))
|
|
{
|
|
cgraph_node *cnode = dyn_cast <cgraph_node *> (node);
|
|
|
|
m_items.safe_push (new sem_function (cnode, hash, &m_bmstack));
|
|
}
|
|
else
|
|
{
|
|
varpool_node *vnode = dyn_cast <varpool_node *> (node);
|
|
|
|
m_items.safe_push (new sem_variable (vnode, hash, &m_bmstack));
|
|
}
|
|
}
|
|
|
|
lto_free_section_data (file_data, LTO_section_ipa_icf, NULL, data,
|
|
len);
|
|
lto_data_in_delete (data_in);
|
|
}
|
|
|
|
/* Read IPA IPA ICF summary for symbols. */
|
|
|
|
void
|
|
sem_item_optimizer::read_summary (void)
|
|
{
|
|
lto_file_decl_data **file_data_vec = lto_get_file_decl_data ();
|
|
lto_file_decl_data *file_data;
|
|
unsigned int j = 0;
|
|
|
|
while ((file_data = file_data_vec[j++]))
|
|
{
|
|
size_t len;
|
|
const char *data = lto_get_section_data (file_data,
|
|
LTO_section_ipa_icf, NULL, &len);
|
|
|
|
if (data)
|
|
read_section (file_data, data, len);
|
|
}
|
|
}
|
|
|
|
/* Register callgraph and varpool hooks. */
|
|
|
|
void
|
|
sem_item_optimizer::register_hooks (void)
|
|
{
|
|
if (!m_cgraph_node_hooks)
|
|
m_cgraph_node_hooks = symtab->add_cgraph_removal_hook
|
|
(&sem_item_optimizer::cgraph_removal_hook, this);
|
|
|
|
if (!m_varpool_node_hooks)
|
|
m_varpool_node_hooks = symtab->add_varpool_removal_hook
|
|
(&sem_item_optimizer::varpool_removal_hook, this);
|
|
}
|
|
|
|
/* Unregister callgraph and varpool hooks. */
|
|
|
|
void
|
|
sem_item_optimizer::unregister_hooks (void)
|
|
{
|
|
if (m_cgraph_node_hooks)
|
|
symtab->remove_cgraph_removal_hook (m_cgraph_node_hooks);
|
|
|
|
if (m_varpool_node_hooks)
|
|
symtab->remove_varpool_removal_hook (m_varpool_node_hooks);
|
|
}
|
|
|
|
/* Adds a CLS to hashtable associated by hash value. */
|
|
|
|
void
|
|
sem_item_optimizer::add_class (congruence_class *cls)
|
|
{
|
|
gcc_assert (cls->members.length ());
|
|
|
|
congruence_class_group *group = get_group_by_hash (
|
|
cls->members[0]->get_hash (),
|
|
cls->members[0]->type);
|
|
group->classes.safe_push (cls);
|
|
}
|
|
|
|
/* Gets a congruence class group based on given HASH value and TYPE. */
|
|
|
|
congruence_class_group *
|
|
sem_item_optimizer::get_group_by_hash (hashval_t hash, sem_item_type type)
|
|
{
|
|
congruence_class_group *item = XNEW (congruence_class_group);
|
|
item->hash = hash;
|
|
item->type = type;
|
|
|
|
congruence_class_group **slot = m_classes.find_slot (item, INSERT);
|
|
|
|
if (*slot)
|
|
free (item);
|
|
else
|
|
{
|
|
item->classes.create (1);
|
|
*slot = item;
|
|
}
|
|
|
|
return *slot;
|
|
}
|
|
|
|
/* Callgraph removal hook called for a NODE with a custom DATA. */
|
|
|
|
void
|
|
sem_item_optimizer::cgraph_removal_hook (cgraph_node *node, void *data)
|
|
{
|
|
sem_item_optimizer *optimizer = (sem_item_optimizer *) data;
|
|
optimizer->remove_symtab_node (node);
|
|
}
|
|
|
|
/* Varpool removal hook called for a NODE with a custom DATA. */
|
|
|
|
void
|
|
sem_item_optimizer::varpool_removal_hook (varpool_node *node, void *data)
|
|
{
|
|
sem_item_optimizer *optimizer = (sem_item_optimizer *) data;
|
|
optimizer->remove_symtab_node (node);
|
|
}
|
|
|
|
/* Remove symtab NODE triggered by symtab removal hooks. */
|
|
|
|
void
|
|
sem_item_optimizer::remove_symtab_node (symtab_node *node)
|
|
{
|
|
gcc_assert (!m_classes.elements());
|
|
|
|
m_removed_items_set.add (node);
|
|
}
|
|
|
|
void
|
|
sem_item_optimizer::remove_item (sem_item *item)
|
|
{
|
|
if (m_symtab_node_map.get (item->node))
|
|
m_symtab_node_map.remove (item->node);
|
|
delete item;
|
|
}
|
|
|
|
/* Removes all callgraph and varpool nodes that are marked by symtab
|
|
as deleted. */
|
|
|
|
void
|
|
sem_item_optimizer::filter_removed_items (void)
|
|
{
|
|
auto_vec <sem_item *> filtered;
|
|
|
|
for (unsigned int i = 0; i < m_items.length(); i++)
|
|
{
|
|
sem_item *item = m_items[i];
|
|
|
|
if (m_removed_items_set.contains (item->node))
|
|
{
|
|
remove_item (item);
|
|
continue;
|
|
}
|
|
|
|
if (item->type == FUNC)
|
|
{
|
|
cgraph_node *cnode = static_cast <sem_function *>(item)->get_node ();
|
|
|
|
bool no_body_function = in_lto_p && (cnode->alias || cnode->body_removed);
|
|
if (no_body_function || !opt_for_fn (item->decl, flag_ipa_icf_functions)
|
|
|| DECL_CXX_CONSTRUCTOR_P (item->decl)
|
|
|| DECL_CXX_DESTRUCTOR_P (item->decl))
|
|
remove_item (item);
|
|
else
|
|
filtered.safe_push (item);
|
|
}
|
|
else /* VAR. */
|
|
{
|
|
if (!flag_ipa_icf_variables)
|
|
remove_item (item);
|
|
else
|
|
filtered.safe_push (item);
|
|
}
|
|
}
|
|
|
|
/* Clean-up of released semantic items. */
|
|
|
|
m_items.release ();
|
|
for (unsigned int i = 0; i < filtered.length(); i++)
|
|
m_items.safe_push (filtered[i]);
|
|
}
|
|
|
|
/* Optimizer entry point. */
|
|
|
|
void
|
|
sem_item_optimizer::execute (void)
|
|
{
|
|
filter_removed_items ();
|
|
unregister_hooks ();
|
|
|
|
build_hash_based_classes ();
|
|
|
|
if (dump_file)
|
|
fprintf (dump_file, "Dump after hash based groups\n");
|
|
dump_cong_classes ();
|
|
|
|
for (unsigned int i = 0; i < m_items.length(); i++)
|
|
m_items[i]->init_wpa ();
|
|
|
|
build_graph ();
|
|
|
|
subdivide_classes_by_equality (true);
|
|
|
|
if (dump_file)
|
|
fprintf (dump_file, "Dump after WPA based types groups\n");
|
|
|
|
dump_cong_classes ();
|
|
|
|
process_cong_reduction ();
|
|
verify_classes ();
|
|
|
|
if (dump_file)
|
|
fprintf (dump_file, "Dump after callgraph-based congruence reduction\n");
|
|
|
|
dump_cong_classes ();
|
|
|
|
parse_nonsingleton_classes ();
|
|
subdivide_classes_by_equality ();
|
|
|
|
if (dump_file)
|
|
fprintf (dump_file, "Dump after full equality comparison of groups\n");
|
|
|
|
dump_cong_classes ();
|
|
|
|
unsigned int prev_class_count = m_classes_count;
|
|
|
|
process_cong_reduction ();
|
|
dump_cong_classes ();
|
|
verify_classes ();
|
|
merge_classes (prev_class_count);
|
|
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
symtab_node::dump_table (dump_file);
|
|
}
|
|
|
|
/* Function responsible for visiting all potential functions and
|
|
read-only variables that can be merged. */
|
|
|
|
void
|
|
sem_item_optimizer::parse_funcs_and_vars (void)
|
|
{
|
|
cgraph_node *cnode;
|
|
|
|
if (flag_ipa_icf_functions)
|
|
FOR_EACH_DEFINED_FUNCTION (cnode)
|
|
{
|
|
sem_function *f = sem_function::parse (cnode, &m_bmstack);
|
|
if (f)
|
|
{
|
|
m_items.safe_push (f);
|
|
m_symtab_node_map.put (cnode, f);
|
|
|
|
if (dump_file)
|
|
fprintf (dump_file, "Parsed function:%s\n", f->asm_name ());
|
|
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
f->dump_to_file (dump_file);
|
|
}
|
|
else if (dump_file)
|
|
fprintf (dump_file, "Not parsed function:%s\n", cnode->asm_name ());
|
|
}
|
|
|
|
varpool_node *vnode;
|
|
|
|
if (flag_ipa_icf_variables)
|
|
FOR_EACH_DEFINED_VARIABLE (vnode)
|
|
{
|
|
sem_variable *v = sem_variable::parse (vnode, &m_bmstack);
|
|
|
|
if (v)
|
|
{
|
|
m_items.safe_push (v);
|
|
m_symtab_node_map.put (vnode, v);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Makes pairing between a congruence class CLS and semantic ITEM. */
|
|
|
|
void
|
|
sem_item_optimizer::add_item_to_class (congruence_class *cls, sem_item *item)
|
|
{
|
|
item->index_in_class = cls->members.length ();
|
|
cls->members.safe_push (item);
|
|
item->cls = cls;
|
|
}
|
|
|
|
/* Congruence classes are built by hash value. */
|
|
|
|
void
|
|
sem_item_optimizer::build_hash_based_classes (void)
|
|
{
|
|
for (unsigned i = 0; i < m_items.length (); i++)
|
|
{
|
|
sem_item *item = m_items[i];
|
|
|
|
congruence_class_group *group = get_group_by_hash (item->get_hash (),
|
|
item->type);
|
|
|
|
if (!group->classes.length ())
|
|
{
|
|
m_classes_count++;
|
|
group->classes.safe_push (new congruence_class (class_id++));
|
|
}
|
|
|
|
add_item_to_class (group->classes[0], item);
|
|
}
|
|
}
|
|
|
|
/* Build references according to call graph. */
|
|
|
|
void
|
|
sem_item_optimizer::build_graph (void)
|
|
{
|
|
for (unsigned i = 0; i < m_items.length (); i++)
|
|
{
|
|
sem_item *item = m_items[i];
|
|
m_symtab_node_map.put (item->node, item);
|
|
}
|
|
|
|
for (unsigned i = 0; i < m_items.length (); i++)
|
|
{
|
|
sem_item *item = m_items[i];
|
|
|
|
if (item->type == FUNC)
|
|
{
|
|
cgraph_node *cnode = dyn_cast <cgraph_node *> (item->node);
|
|
|
|
cgraph_edge *e = cnode->callees;
|
|
while (e)
|
|
{
|
|
sem_item **slot = m_symtab_node_map.get (e->callee);
|
|
if (slot)
|
|
item->add_reference (*slot);
|
|
|
|
e = e->next_callee;
|
|
}
|
|
}
|
|
|
|
ipa_ref *ref = NULL;
|
|
for (unsigned i = 0; item->node->iterate_reference (i, ref); i++)
|
|
{
|
|
sem_item **slot = m_symtab_node_map.get (ref->referred);
|
|
if (slot)
|
|
item->add_reference (*slot);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Semantic items in classes having more than one element and initialized.
|
|
In case of WPA, we load function body. */
|
|
|
|
void
|
|
sem_item_optimizer::parse_nonsingleton_classes (void)
|
|
{
|
|
unsigned int init_called_count = 0;
|
|
|
|
for (unsigned i = 0; i < m_items.length (); i++)
|
|
if (m_items[i]->cls->members.length () > 1)
|
|
{
|
|
m_items[i]->init ();
|
|
init_called_count++;
|
|
}
|
|
|
|
if (dump_file)
|
|
fprintf (dump_file, "Init called for %u items (%.2f%%).\n", init_called_count,
|
|
m_items.length () ? 100.0f * init_called_count / m_items.length (): 0.0f);
|
|
}
|
|
|
|
/* Equality function for semantic items is used to subdivide existing
|
|
classes. If IN_WPA, fast equality function is invoked. */
|
|
|
|
void
|
|
sem_item_optimizer::subdivide_classes_by_equality (bool in_wpa)
|
|
{
|
|
for (hash_table <congruence_class_group_hash>::iterator it = m_classes.begin ();
|
|
it != m_classes.end (); ++it)
|
|
{
|
|
unsigned int class_count = (*it)->classes.length ();
|
|
|
|
for (unsigned i = 0; i < class_count; i++)
|
|
{
|
|
congruence_class *c = (*it)->classes [i];
|
|
|
|
if (c->members.length() > 1)
|
|
{
|
|
auto_vec <sem_item *> new_vector;
|
|
|
|
sem_item *first = c->members[0];
|
|
new_vector.safe_push (first);
|
|
|
|
unsigned class_split_first = (*it)->classes.length ();
|
|
|
|
for (unsigned j = 1; j < c->members.length (); j++)
|
|
{
|
|
sem_item *item = c->members[j];
|
|
|
|
bool equals = in_wpa ? first->equals_wpa (item,
|
|
m_symtab_node_map) : first->equals (item, m_symtab_node_map);
|
|
|
|
if (equals)
|
|
new_vector.safe_push (item);
|
|
else
|
|
{
|
|
bool integrated = false;
|
|
|
|
for (unsigned k = class_split_first; k < (*it)->classes.length (); k++)
|
|
{
|
|
sem_item *x = (*it)->classes[k]->members[0];
|
|
bool equals = in_wpa ? x->equals_wpa (item,
|
|
m_symtab_node_map) : x->equals (item, m_symtab_node_map);
|
|
|
|
if (equals)
|
|
{
|
|
integrated = true;
|
|
add_item_to_class ((*it)->classes[k], item);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!integrated)
|
|
{
|
|
congruence_class *c = new congruence_class (class_id++);
|
|
m_classes_count++;
|
|
add_item_to_class (c, item);
|
|
|
|
(*it)->classes.safe_push (c);
|
|
}
|
|
}
|
|
}
|
|
|
|
// we replace newly created new_vector for the class we've just splitted
|
|
c->members.release ();
|
|
c->members.create (new_vector.length ());
|
|
|
|
for (unsigned int j = 0; j < new_vector.length (); j++)
|
|
add_item_to_class (c, new_vector[j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
verify_classes ();
|
|
}
|
|
|
|
/* Subdivide classes by address references that members of the class
|
|
reference. Example can be a pair of functions that have an address
|
|
taken from a function. If these addresses are different the class
|
|
is split. */
|
|
|
|
unsigned
|
|
sem_item_optimizer::subdivide_classes_by_sensitive_refs ()
|
|
{
|
|
unsigned newly_created_classes = 0;
|
|
|
|
for (hash_table <congruence_class_group_hash>::iterator it = m_classes.begin ();
|
|
it != m_classes.end (); ++it)
|
|
{
|
|
unsigned int class_count = (*it)->classes.length ();
|
|
auto_vec<congruence_class *> new_classes;
|
|
|
|
for (unsigned i = 0; i < class_count; i++)
|
|
{
|
|
congruence_class *c = (*it)->classes [i];
|
|
|
|
if (c->members.length() > 1)
|
|
{
|
|
hash_map <symbol_compare_collection *, vec <sem_item *>,
|
|
symbol_compare_hashmap_traits> split_map;
|
|
|
|
for (unsigned j = 0; j < c->members.length (); j++)
|
|
{
|
|
sem_item *source_node = c->members[j];
|
|
|
|
symbol_compare_collection *collection = new symbol_compare_collection (source_node->node);
|
|
|
|
vec <sem_item *> *slot = &split_map.get_or_insert (collection);
|
|
gcc_checking_assert (slot);
|
|
|
|
slot->safe_push (source_node);
|
|
}
|
|
|
|
/* If the map contains more than one key, we have to split the map
|
|
appropriately. */
|
|
if (split_map.elements () != 1)
|
|
{
|
|
bool first_class = true;
|
|
|
|
hash_map <symbol_compare_collection *, vec <sem_item *>,
|
|
symbol_compare_hashmap_traits>::iterator it2 = split_map.begin ();
|
|
for (; it2 != split_map.end (); ++it2)
|
|
{
|
|
congruence_class *new_cls;
|
|
new_cls = new congruence_class (class_id++);
|
|
|
|
for (unsigned k = 0; k < (*it2).second.length (); k++)
|
|
add_item_to_class (new_cls, (*it2).second[k]);
|
|
|
|
worklist_push (new_cls);
|
|
newly_created_classes++;
|
|
|
|
if (first_class)
|
|
{
|
|
(*it)->classes[i] = new_cls;
|
|
first_class = false;
|
|
}
|
|
else
|
|
{
|
|
new_classes.safe_push (new_cls);
|
|
m_classes_count++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (unsigned i = 0; i < new_classes.length (); i++)
|
|
(*it)->classes.safe_push (new_classes[i]);
|
|
}
|
|
|
|
return newly_created_classes;
|
|
}
|
|
|
|
/* Verify congruence classes if checking is enabled. */
|
|
|
|
void
|
|
sem_item_optimizer::verify_classes (void)
|
|
{
|
|
#if ENABLE_CHECKING
|
|
for (hash_table <congruence_class_group_hash>::iterator it = m_classes.begin ();
|
|
it != m_classes.end (); ++it)
|
|
{
|
|
for (unsigned int i = 0; i < (*it)->classes.length (); i++)
|
|
{
|
|
congruence_class *cls = (*it)->classes[i];
|
|
|
|
gcc_checking_assert (cls);
|
|
gcc_checking_assert (cls->members.length () > 0);
|
|
|
|
for (unsigned int j = 0; j < cls->members.length (); j++)
|
|
{
|
|
sem_item *item = cls->members[j];
|
|
|
|
gcc_checking_assert (item);
|
|
gcc_checking_assert (item->cls == cls);
|
|
|
|
for (unsigned k = 0; k < item->usages.length (); k++)
|
|
{
|
|
sem_usage_pair *usage = item->usages[k];
|
|
gcc_checking_assert (usage->item->index_in_class <
|
|
usage->item->cls->members.length ());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* Disposes split map traverse function. CLS_PTR is pointer to congruence
|
|
class, BSLOT is bitmap slot we want to release. DATA is mandatory,
|
|
but unused argument. */
|
|
|
|
bool
|
|
sem_item_optimizer::release_split_map (congruence_class * const &,
|
|
bitmap const &b, traverse_split_pair *)
|
|
{
|
|
bitmap bmp = b;
|
|
|
|
BITMAP_FREE (bmp);
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Process split operation for a class given as pointer CLS_PTR,
|
|
where bitmap B splits congruence class members. DATA is used
|
|
as argument of split pair. */
|
|
|
|
bool
|
|
sem_item_optimizer::traverse_congruence_split (congruence_class * const &cls,
|
|
bitmap const &b, traverse_split_pair *pair)
|
|
{
|
|
sem_item_optimizer *optimizer = pair->optimizer;
|
|
const congruence_class *splitter_cls = pair->cls;
|
|
|
|
/* If counted bits are greater than zero and less than the number of members
|
|
a group will be splitted. */
|
|
unsigned popcount = bitmap_count_bits (b);
|
|
|
|
if (popcount > 0 && popcount < cls->members.length ())
|
|
{
|
|
congruence_class* newclasses[2] = { new congruence_class (class_id++), new congruence_class (class_id++) };
|
|
|
|
for (unsigned int i = 0; i < cls->members.length (); i++)
|
|
{
|
|
int target = bitmap_bit_p (b, i);
|
|
congruence_class *tc = newclasses[target];
|
|
|
|
add_item_to_class (tc, cls->members[i]);
|
|
}
|
|
|
|
#ifdef ENABLE_CHECKING
|
|
for (unsigned int i = 0; i < 2; i++)
|
|
gcc_checking_assert (newclasses[i]->members.length ());
|
|
#endif
|
|
|
|
if (splitter_cls == cls)
|
|
optimizer->splitter_class_removed = true;
|
|
|
|
/* Remove old class from worklist if presented. */
|
|
bool in_worklist = cls->in_worklist;
|
|
|
|
if (in_worklist)
|
|
cls->in_worklist = false;
|
|
|
|
congruence_class_group g;
|
|
g.hash = cls->members[0]->get_hash ();
|
|
g.type = cls->members[0]->type;
|
|
|
|
congruence_class_group *slot = optimizer->m_classes.find(&g);
|
|
|
|
for (unsigned int i = 0; i < slot->classes.length (); i++)
|
|
if (slot->classes[i] == cls)
|
|
{
|
|
slot->classes.ordered_remove (i);
|
|
break;
|
|
}
|
|
|
|
/* New class will be inserted and integrated to work list. */
|
|
for (unsigned int i = 0; i < 2; i++)
|
|
optimizer->add_class (newclasses[i]);
|
|
|
|
/* Two classes replace one, so that increment just by one. */
|
|
optimizer->m_classes_count++;
|
|
|
|
/* If OLD class was presented in the worklist, we remove the class
|
|
and replace it will both newly created classes. */
|
|
if (in_worklist)
|
|
for (unsigned int i = 0; i < 2; i++)
|
|
optimizer->worklist_push (newclasses[i]);
|
|
else /* Just smaller class is inserted. */
|
|
{
|
|
unsigned int smaller_index = newclasses[0]->members.length () <
|
|
newclasses[1]->members.length () ?
|
|
0 : 1;
|
|
optimizer->worklist_push (newclasses[smaller_index]);
|
|
}
|
|
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, " congruence class splitted:\n");
|
|
cls->dump (dump_file, 4);
|
|
|
|
fprintf (dump_file, " newly created groups:\n");
|
|
for (unsigned int i = 0; i < 2; i++)
|
|
newclasses[i]->dump (dump_file, 4);
|
|
}
|
|
|
|
/* Release class if not presented in work list. */
|
|
if (!in_worklist)
|
|
delete cls;
|
|
}
|
|
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Tests if a class CLS used as INDEXth splits any congruence classes.
|
|
Bitmap stack BMSTACK is used for bitmap allocation. */
|
|
|
|
void
|
|
sem_item_optimizer::do_congruence_step_for_index (congruence_class *cls,
|
|
unsigned int index)
|
|
{
|
|
hash_map <congruence_class *, bitmap> split_map;
|
|
|
|
for (unsigned int i = 0; i < cls->members.length (); i++)
|
|
{
|
|
sem_item *item = cls->members[i];
|
|
|
|
/* Iterate all usages that have INDEX as usage of the item. */
|
|
for (unsigned int j = 0; j < item->usages.length (); j++)
|
|
{
|
|
sem_usage_pair *usage = item->usages[j];
|
|
|
|
if (usage->index != index)
|
|
continue;
|
|
|
|
bitmap *slot = split_map.get (usage->item->cls);
|
|
bitmap b;
|
|
|
|
if(!slot)
|
|
{
|
|
b = BITMAP_ALLOC (&m_bmstack);
|
|
split_map.put (usage->item->cls, b);
|
|
}
|
|
else
|
|
b = *slot;
|
|
|
|
#if ENABLE_CHECKING
|
|
gcc_checking_assert (usage->item->cls);
|
|
gcc_checking_assert (usage->item->index_in_class <
|
|
usage->item->cls->members.length ());
|
|
#endif
|
|
|
|
bitmap_set_bit (b, usage->item->index_in_class);
|
|
}
|
|
}
|
|
|
|
traverse_split_pair pair;
|
|
pair.optimizer = this;
|
|
pair.cls = cls;
|
|
|
|
splitter_class_removed = false;
|
|
split_map.traverse
|
|
<traverse_split_pair *, sem_item_optimizer::traverse_congruence_split> (&pair);
|
|
|
|
/* Bitmap clean-up. */
|
|
split_map.traverse
|
|
<traverse_split_pair *, sem_item_optimizer::release_split_map> (NULL);
|
|
}
|
|
|
|
/* Every usage of a congruence class CLS is a candidate that can split the
|
|
collection of classes. Bitmap stack BMSTACK is used for bitmap
|
|
allocation. */
|
|
|
|
void
|
|
sem_item_optimizer::do_congruence_step (congruence_class *cls)
|
|
{
|
|
bitmap_iterator bi;
|
|
unsigned int i;
|
|
|
|
bitmap usage = BITMAP_ALLOC (&m_bmstack);
|
|
|
|
for (unsigned int i = 0; i < cls->members.length (); i++)
|
|
bitmap_ior_into (usage, cls->members[i]->usage_index_bitmap);
|
|
|
|
EXECUTE_IF_SET_IN_BITMAP (usage, 0, i, bi)
|
|
{
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
fprintf (dump_file, " processing congruece step for class: %u, index: %u\n",
|
|
cls->id, i);
|
|
|
|
do_congruence_step_for_index (cls, i);
|
|
|
|
if (splitter_class_removed)
|
|
break;
|
|
}
|
|
|
|
BITMAP_FREE (usage);
|
|
}
|
|
|
|
/* Adds a newly created congruence class CLS to worklist. */
|
|
|
|
void
|
|
sem_item_optimizer::worklist_push (congruence_class *cls)
|
|
{
|
|
/* Return if the class CLS is already presented in work list. */
|
|
if (cls->in_worklist)
|
|
return;
|
|
|
|
cls->in_worklist = true;
|
|
worklist.push_back (cls);
|
|
}
|
|
|
|
/* Pops a class from worklist. */
|
|
|
|
congruence_class *
|
|
sem_item_optimizer::worklist_pop (void)
|
|
{
|
|
congruence_class *cls;
|
|
|
|
while (!worklist.empty ())
|
|
{
|
|
cls = worklist.front ();
|
|
worklist.pop_front ();
|
|
if (cls->in_worklist)
|
|
{
|
|
cls->in_worklist = false;
|
|
|
|
return cls;
|
|
}
|
|
else
|
|
{
|
|
/* Work list item was already intended to be removed.
|
|
The only reason for doing it is to split a class.
|
|
Thus, the class CLS is deleted. */
|
|
delete cls;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* Iterative congruence reduction function. */
|
|
|
|
void
|
|
sem_item_optimizer::process_cong_reduction (void)
|
|
{
|
|
for (hash_table<congruence_class_group_hash>::iterator it = m_classes.begin ();
|
|
it != m_classes.end (); ++it)
|
|
for (unsigned i = 0; i < (*it)->classes.length (); i++)
|
|
if ((*it)->classes[i]->is_class_used ())
|
|
worklist_push ((*it)->classes[i]);
|
|
|
|
if (dump_file)
|
|
fprintf (dump_file, "Worklist has been filled with: %lu\n",
|
|
(unsigned long) worklist.size ());
|
|
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
fprintf (dump_file, "Congruence class reduction\n");
|
|
|
|
congruence_class *cls;
|
|
|
|
/* Process complete congruence reduction. */
|
|
while ((cls = worklist_pop ()) != NULL)
|
|
do_congruence_step (cls);
|
|
|
|
/* Subdivide newly created classes according to references. */
|
|
unsigned new_classes = subdivide_classes_by_sensitive_refs ();
|
|
|
|
if (dump_file)
|
|
fprintf (dump_file, "Address reference subdivision created: %u "
|
|
"new classes.\n", new_classes);
|
|
}
|
|
|
|
/* Debug function prints all informations about congruence classes. */
|
|
|
|
void
|
|
sem_item_optimizer::dump_cong_classes (void)
|
|
{
|
|
if (!dump_file)
|
|
return;
|
|
|
|
fprintf (dump_file,
|
|
"Congruence classes: %u (unique hash values: %lu), with total: %u items\n",
|
|
m_classes_count, (unsigned long) m_classes.elements(), m_items.length ());
|
|
|
|
/* Histogram calculation. */
|
|
unsigned int max_index = 0;
|
|
unsigned int* histogram = XCNEWVEC (unsigned int, m_items.length () + 1);
|
|
|
|
for (hash_table<congruence_class_group_hash>::iterator it = m_classes.begin ();
|
|
it != m_classes.end (); ++it)
|
|
|
|
for (unsigned i = 0; i < (*it)->classes.length (); i++)
|
|
{
|
|
unsigned int c = (*it)->classes[i]->members.length ();
|
|
histogram[c]++;
|
|
|
|
if (c > max_index)
|
|
max_index = c;
|
|
}
|
|
|
|
fprintf (dump_file,
|
|
"Class size histogram [num of members]: number of classe number of classess\n");
|
|
|
|
for (unsigned int i = 0; i <= max_index; i++)
|
|
if (histogram[i])
|
|
fprintf (dump_file, "[%u]: %u classes\n", i, histogram[i]);
|
|
|
|
fprintf (dump_file, "\n\n");
|
|
|
|
|
|
if (dump_flags & TDF_DETAILS)
|
|
for (hash_table<congruence_class_group_hash>::iterator it = m_classes.begin ();
|
|
it != m_classes.end (); ++it)
|
|
{
|
|
fprintf (dump_file, " group: with %u classes:\n", (*it)->classes.length ());
|
|
|
|
for (unsigned i = 0; i < (*it)->classes.length (); i++)
|
|
{
|
|
(*it)->classes[i]->dump (dump_file, 4);
|
|
|
|
if(i < (*it)->classes.length () - 1)
|
|
fprintf (dump_file, " ");
|
|
}
|
|
}
|
|
|
|
free (histogram);
|
|
}
|
|
|
|
/* After reduction is done, we can declare all items in a group
|
|
to be equal. PREV_CLASS_COUNT is start number of classes
|
|
before reduction. */
|
|
|
|
void
|
|
sem_item_optimizer::merge_classes (unsigned int prev_class_count)
|
|
{
|
|
unsigned int item_count = m_items.length ();
|
|
unsigned int class_count = m_classes_count;
|
|
unsigned int equal_items = item_count - class_count;
|
|
|
|
unsigned int non_singular_classes_count = 0;
|
|
unsigned int non_singular_classes_sum = 0;
|
|
|
|
for (hash_table<congruence_class_group_hash>::iterator it = m_classes.begin ();
|
|
it != m_classes.end (); ++it)
|
|
for (unsigned int i = 0; i < (*it)->classes.length (); i++)
|
|
{
|
|
congruence_class *c = (*it)->classes[i];
|
|
if (c->members.length () > 1)
|
|
{
|
|
non_singular_classes_count++;
|
|
non_singular_classes_sum += c->members.length ();
|
|
}
|
|
}
|
|
|
|
if (dump_file)
|
|
{
|
|
fprintf (dump_file, "\nItem count: %u\n", item_count);
|
|
fprintf (dump_file, "Congruent classes before: %u, after: %u\n",
|
|
prev_class_count, class_count);
|
|
fprintf (dump_file, "Average class size before: %.2f, after: %.2f\n",
|
|
prev_class_count ? 1.0f * item_count / prev_class_count : 0.0f,
|
|
class_count ? 1.0f * item_count / class_count : 0.0f);
|
|
fprintf (dump_file, "Average non-singular class size: %.2f, count: %u\n",
|
|
non_singular_classes_count ? 1.0f * non_singular_classes_sum /
|
|
non_singular_classes_count : 0.0f,
|
|
non_singular_classes_count);
|
|
fprintf (dump_file, "Equal symbols: %u\n", equal_items);
|
|
fprintf (dump_file, "Fraction of visited symbols: %.2f%%\n\n",
|
|
item_count ? 100.0f * equal_items / item_count : 0.0f);
|
|
}
|
|
|
|
for (hash_table<congruence_class_group_hash>::iterator it = m_classes.begin ();
|
|
it != m_classes.end (); ++it)
|
|
for (unsigned int i = 0; i < (*it)->classes.length (); i++)
|
|
{
|
|
congruence_class *c = (*it)->classes[i];
|
|
|
|
if (c->members.length () == 1)
|
|
continue;
|
|
|
|
gcc_assert (c->members.length ());
|
|
|
|
sem_item *source = c->members[0];
|
|
|
|
for (unsigned int j = 1; j < c->members.length (); j++)
|
|
{
|
|
sem_item *alias = c->members[j];
|
|
|
|
if (dump_file)
|
|
{
|
|
fprintf (dump_file, "Semantic equality hit:%s->%s\n",
|
|
source->name (), alias->name ());
|
|
fprintf (dump_file, "Assembler symbol names:%s->%s\n",
|
|
source->asm_name (), alias->asm_name ());
|
|
}
|
|
|
|
if (lookup_attribute ("no_icf", DECL_ATTRIBUTES (alias->decl)))
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file,
|
|
"Merge operation is skipped due to no_icf "
|
|
"attribute.\n\n");
|
|
|
|
continue;
|
|
}
|
|
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
source->dump_to_file (dump_file);
|
|
alias->dump_to_file (dump_file);
|
|
}
|
|
|
|
source->merge (alias);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Dump function prints all class members to a FILE with an INDENT. */
|
|
|
|
void
|
|
congruence_class::dump (FILE *file, unsigned int indent) const
|
|
{
|
|
FPRINTF_SPACES (file, indent, "class with id: %u, hash: %u, items: %u\n",
|
|
id, members[0]->get_hash (), members.length ());
|
|
|
|
FPUTS_SPACES (file, indent + 2, "");
|
|
for (unsigned i = 0; i < members.length (); i++)
|
|
fprintf (file, "%s(%p/%u) ", members[i]->asm_name (), (void *) members[i]->decl,
|
|
members[i]->node->order);
|
|
|
|
fprintf (file, "\n");
|
|
}
|
|
|
|
/* Returns true if there's a member that is used from another group. */
|
|
|
|
bool
|
|
congruence_class::is_class_used (void)
|
|
{
|
|
for (unsigned int i = 0; i < members.length (); i++)
|
|
if (members[i]->usages.length ())
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Initialization and computation of symtab node hash, there data
|
|
are propagated later on. */
|
|
|
|
static sem_item_optimizer *optimizer = NULL;
|
|
|
|
/* Generate pass summary for IPA ICF pass. */
|
|
|
|
static void
|
|
ipa_icf_generate_summary (void)
|
|
{
|
|
if (!optimizer)
|
|
optimizer = new sem_item_optimizer ();
|
|
|
|
optimizer->register_hooks ();
|
|
optimizer->parse_funcs_and_vars ();
|
|
}
|
|
|
|
/* Write pass summary for IPA ICF pass. */
|
|
|
|
static void
|
|
ipa_icf_write_summary (void)
|
|
{
|
|
gcc_assert (optimizer);
|
|
|
|
optimizer->write_summary ();
|
|
}
|
|
|
|
/* Read pass summary for IPA ICF pass. */
|
|
|
|
static void
|
|
ipa_icf_read_summary (void)
|
|
{
|
|
if (!optimizer)
|
|
optimizer = new sem_item_optimizer ();
|
|
|
|
optimizer->read_summary ();
|
|
optimizer->register_hooks ();
|
|
}
|
|
|
|
/* Semantic equality exection function. */
|
|
|
|
static unsigned int
|
|
ipa_icf_driver (void)
|
|
{
|
|
gcc_assert (optimizer);
|
|
|
|
optimizer->execute ();
|
|
|
|
delete optimizer;
|
|
optimizer = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
const pass_data pass_data_ipa_icf =
|
|
{
|
|
IPA_PASS, /* type */
|
|
"icf", /* name */
|
|
OPTGROUP_IPA, /* optinfo_flags */
|
|
TV_IPA_ICF, /* tv_id */
|
|
0, /* properties_required */
|
|
0, /* properties_provided */
|
|
0, /* properties_destroyed */
|
|
0, /* todo_flags_start */
|
|
0, /* todo_flags_finish */
|
|
};
|
|
|
|
class pass_ipa_icf : public ipa_opt_pass_d
|
|
{
|
|
public:
|
|
pass_ipa_icf (gcc::context *ctxt)
|
|
: ipa_opt_pass_d (pass_data_ipa_icf, ctxt,
|
|
ipa_icf_generate_summary, /* generate_summary */
|
|
ipa_icf_write_summary, /* write_summary */
|
|
ipa_icf_read_summary, /* 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 *)
|
|
{
|
|
return in_lto_p || flag_ipa_icf_variables || flag_ipa_icf_functions;
|
|
}
|
|
|
|
virtual unsigned int execute (function *)
|
|
{
|
|
return ipa_icf_driver();
|
|
}
|
|
}; // class pass_ipa_icf
|
|
|
|
} // ipa_icf namespace
|
|
|
|
ipa_opt_pass_d *
|
|
make_pass_ipa_icf (gcc::context *ctxt)
|
|
{
|
|
return new ipa_icf::pass_ipa_icf (ctxt);
|
|
}
|