/* Callgraph handling code.
- Copyright (C) 2003-2015 Free Software Foundation, Inc.
+ Copyright (C) 2003-2018 Free Software Foundation, Inc.
Contributed by Jan Hubicka
This file is part of GCC.
/* This file contains basic routines manipulating call graph
- The call-graph is a data structure designed for intra-procedural optimization.
- It represents a multi-graph where nodes are functions and edges are call sites. */
+ The call-graph is a data structure designed for inter-procedural
+ optimization. It represents a multi-graph where nodes are functions
+ (symbols within symbol table) and edges are call sites. */
#include "config.h"
#include "system.h"
#include "coretypes.h"
-#include "tm.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 "wide-int.h"
-#include "inchash.h"
+#include "backend.h"
+#include "target.h"
+#include "rtl.h"
#include "tree.h"
+#include "gimple.h"
+#include "predict.h"
+#include "alloc-pool.h"
+#include "gimple-ssa.h"
+#include "cgraph.h"
+#include "lto-streamer.h"
#include "fold-const.h"
#include "varasm.h"
#include "calls.h"
#include "print-tree.h"
-#include "tree-inline.h"
#include "langhooks.h"
-#include "hashtab.h"
-#include "toplev.h"
-#include "flags.h"
-#include "debug.h"
-#include "target.h"
-#include "predict.h"
-#include "dominance.h"
-#include "cfg.h"
-#include "basic-block.h"
-#include "hash-map.h"
-#include "is-a.h"
-#include "plugin-api.h"
-#include "hard-reg-set.h"
-#include "function.h"
-#include "ipa-ref.h"
-#include "cgraph.h"
#include "intl.h"
-#include "tree-ssa-alias.h"
-#include "internal-fn.h"
#include "tree-eh.h"
-#include "gimple-expr.h"
-#include "gimple.h"
#include "gimple-iterator.h"
-#include "timevar.h"
-#include "dumpfile.h"
-#include "gimple-ssa.h"
#include "tree-cfg.h"
#include "tree-ssa.h"
#include "value-prof.h"
-#include "except.h"
-#include "diagnostic-core.h"
-#include "rtl.h"
#include "ipa-utils.h"
-#include "lto-streamer.h"
-#include "alloc-pool.h"
#include "symbol-summary.h"
+#include "tree-vrp.h"
#include "ipa-prop.h"
-#include "ipa-inline.h"
+#include "ipa-fnsummary.h"
#include "cfgloop.h"
#include "gimple-pretty-print.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 "emit-rtl.h"
-#include "stmt.h"
-#include "expr.h"
#include "tree-dfa.h"
#include "profile.h"
#include "params.h"
#include "tree-chkp.h"
#include "context.h"
+#include "gimplify.h"
+#include "stringpool.h"
+#include "attribs.h"
/* FIXME: Only for PROP_loops, but cgraph shouldn't have to know about this. */
#include "tree-pass.h"
/* Hash descriptor for cgraph_function_version_info. */
-struct function_version_hasher : ggc_hasher<cgraph_function_version_info *>
+struct function_version_hasher : ggc_ptr_hash<cgraph_function_version_info>
{
static hashval_t hash (cgraph_function_version_info *);
static bool equal (cgraph_function_version_info *,
static GTY(()) struct cgraph_function_version_info *
version_info_node = NULL;
+/* Return true if NODE's address can be compared. */
+
+bool
+symtab_node::address_can_be_compared_p ()
+{
+ /* Address of virtual tables and functions is never compared. */
+ if (DECL_VIRTUAL_P (decl))
+ return false;
+ /* Address of C++ cdtors is never compared. */
+ if (is_a <cgraph_node *> (this)
+ && (DECL_CXX_CONSTRUCTOR_P (decl)
+ || DECL_CXX_DESTRUCTOR_P (decl)))
+ return false;
+ /* Constant pool symbols addresses are never compared.
+ flag_merge_constants permits us to assume the same on readonly vars. */
+ if (is_a <varpool_node *> (this)
+ && (DECL_IN_CONSTANT_POOL (decl)
+ || (flag_merge_constants >= 2
+ && TREE_READONLY (decl) && !TREE_THIS_VOLATILE (decl))))
+ return false;
+ return true;
+}
+
/* Get the cgraph_function_version_info node corresponding to node. */
cgraph_function_version_info *
cgraph_node::function_version (void)
return version_info_node;
}
-/* Remove the cgraph_function_version_info and cgraph_node for DECL. This
- DECL is a duplicate declaration. */
-void
-cgraph_node::delete_function_version (tree decl)
+/* Remove the cgraph_function_version_info node given by DECL_V. */
+static void
+delete_function_version (cgraph_function_version_info *decl_v)
{
- cgraph_node *decl_node = cgraph_node::get (decl);
- cgraph_function_version_info *decl_v = NULL;
-
- if (decl_node == NULL)
- return;
-
- decl_v = decl_node->function_version ();
-
if (decl_v == NULL)
return;
if (decl_v->prev != NULL)
- decl_v->prev->next = decl_v->next;
+ decl_v->prev->next = decl_v->next;
if (decl_v->next != NULL)
decl_v->next->prev = decl_v->prev;
if (cgraph_fnver_htab != NULL)
cgraph_fnver_htab->remove_elt (decl_v);
+}
+
+/* Remove the cgraph_function_version_info and cgraph_node for DECL. This
+ DECL is a duplicate declaration. */
+void
+cgraph_node::delete_function_version_by_decl (tree decl)
+{
+ cgraph_node *decl_node = cgraph_node::get (decl);
+
+ if (decl_node == NULL)
+ return;
+
+ delete_function_version (decl_node->function_version ());
decl_node->remove ();
}
{
if (!dump_file)
dump_file = dump_begin (TDI_cgraph, NULL);
+
+ if (!ipa_clones_dump_file)
+ ipa_clones_dump_file = dump_begin (TDI_clones, NULL);
}
/* Allocate new callgraph node and insert it into basic data structures. */
node->decl = decl;
+ node->count = profile_count::uninitialized ();
+
if ((flag_openacc || flag_openmp)
&& lookup_attribute ("omp declare target", DECL_ATTRIBUTES (decl)))
{
node->offloadable = 1;
-#ifdef ENABLE_OFFLOADING
- g->have_offload = true;
-#endif
+ if (ENABLE_OFFLOADING)
+ g->have_offload = true;
}
node->register_symbol ();
node->decl->decl_with_vis.symtab_node = node;
if (dump_file)
fprintf (dump_file, "Introduced new external node "
- "(%s/%i) and turned into root of the clone tree.\n",
- node->name (), node->order);
+ "(%s) and turned into root of the clone tree.\n",
+ node->dump_name ());
}
else if (dump_file)
fprintf (dump_file, "Introduced new external node "
- "(%s/%i).\n", node->name (), node->order);
+ "(%s).\n", node->dump_name ());
return node;
}
alias_node->definition = true;
alias_node->alias = true;
if (lookup_attribute ("weakref", DECL_ATTRIBUTES (alias)) != NULL)
- alias_node->weakref = true;
+ alias_node->transparent_alias = alias_node->weakref = true;
return alias_node;
}
cgraph_node::create_same_body_alias (tree alias, tree decl)
{
cgraph_node *n;
-#ifndef ASM_OUTPUT_DEF
+
/* If aliases aren't supported by the assembler, fail. */
- return NULL;
-#endif
+ if (!TARGET_SUPPORTS_ALIASES)
+ return NULL;
+
/* Langhooks can create same body aliases of symbols not defined.
Those are useless. Drop them on the floor. */
if (symtab->global_info_ready)
/* Add thunk alias into callgraph. The alias declaration is ALIAS and it
aliases DECL with an adjustments made into the first parameter.
- See comments in thunk_adjust for detail on the parameters. */
+ See comments in struct cgraph_thunk_info for detail on the parameters. */
cgraph_node *
cgraph_node::create_thunk (tree alias, tree, bool this_adjusting,
node->reset ();
else
node = cgraph_node::create (alias);
- gcc_checking_assert (!virtual_offset
- || wi::eq_p (virtual_offset, virtual_value));
+
+ /* Make sure that if VIRTUAL_OFFSET is in sync with VIRTUAL_VALUE. */
+ gcc_checking_assert (virtual_offset
+ ? virtual_value == wi::to_wide (virtual_offset)
+ : virtual_value == 0);
+
node->thunk.fixed_offset = fixed_offset;
- node->thunk.this_adjusting = this_adjusting;
node->thunk.virtual_value = virtual_value;
- node->thunk.virtual_offset_p = virtual_offset != NULL;
node->thunk.alias = real_alias;
+ node->thunk.this_adjusting = this_adjusting;
+ node->thunk.virtual_offset_p = virtual_offset != NULL;
node->thunk.thunk_p = true;
node->definition = true;
/* Returns a hash value for X (which really is a cgraph_edge). */
hashval_t
-cgraph_edge_hasher::hash (gimple call_stmt)
+cgraph_edge_hasher::hash (gimple *call_stmt)
{
/* This is a really poor hash function, but it is what htab_hash_pointer
uses. */
return (hashval_t) ((intptr_t)call_stmt >> 3);
}
-/* Return nonzero if the call_stmt of of cgraph_edge X is stmt *Y. */
+/* Return nonzero if the call_stmt of cgraph_edge X is stmt *Y. */
inline bool
-cgraph_edge_hasher::equal (cgraph_edge *x, gimple y)
+cgraph_edge_hasher::equal (cgraph_edge *x, gimple *y)
{
return x->call_stmt == y;
}
static inline void
cgraph_update_edge_in_call_site_hash (cgraph_edge *e)
{
- gimple call = e->call_stmt;
+ gimple *call = e->call_stmt;
*e->caller->call_site_hash->find_slot_with_hash
(call, cgraph_edge_hasher::hash (call), INSERT) = e;
}
CALL_STMT. */
cgraph_edge *
-cgraph_node::get_edge (gimple call_stmt)
+cgraph_node::get_edge (gimple *call_stmt)
{
cgraph_edge *e, *e2;
int n = 0;
cgraph_edge *
symbol_table::create_edge (cgraph_node *caller, cgraph_node *callee,
- gcall *call_stmt, gcov_type count, int freq,
+ gcall *call_stmt, profile_count count,
bool indir_unknown_callee)
{
cgraph_edge *edge;
{
/* This is a rather expensive check possibly triggering
construction of call stmt hashtable. */
-#ifdef ENABLE_CHECKING
cgraph_edge *e;
- gcc_checking_assert (
- !(e = caller->get_edge (call_stmt)) || e->speculative);
-#endif
+ gcc_checking_assert (!(e = caller->get_edge (call_stmt))
+ || e->speculative);
gcc_assert (is_gimple_call (call_stmt));
}
edge->lto_stmt_uid = 0;
edge->count = count;
- gcc_assert (count >= 0);
- edge->frequency = freq;
- gcc_assert (freq >= 0);
- gcc_assert (freq <= CGRAPH_FREQ_MAX);
edge->call_stmt = call_stmt;
push_cfun (DECL_STRUCT_FUNCTION (caller->decl));
&& callee && callee->decl
&& !gimple_check_call_matching_types (call_stmt, callee->decl,
false))
- edge->call_stmt_cannot_inline_p = true;
+ {
+ edge->inline_failed = CIF_MISMATCHED_ARGUMENTS;
+ edge->call_stmt_cannot_inline_p = true;
+ }
else
- edge->call_stmt_cannot_inline_p = false;
+ {
+ edge->inline_failed = CIF_FUNCTION_NOT_CONSIDERED;
+ edge->call_stmt_cannot_inline_p = false;
+ }
edge->indirect_info = NULL;
edge->indirect_inlining_edge = 0;
cgraph_edge *
cgraph_node::create_edge (cgraph_node *callee,
- gcall *call_stmt, gcov_type count, int freq)
+ gcall *call_stmt, profile_count count)
{
cgraph_edge *edge = symtab->create_edge (this, callee, call_stmt, count,
- freq, false);
+ false);
initialize_inline_failed (edge);
cgraph_edge *
cgraph_node::create_indirect_edge (gcall *call_stmt, int ecf_flags,
- gcov_type count, int freq,
+ profile_count count,
bool compute_indirect_info)
{
cgraph_edge *edge = symtab->create_edge (this, NULL, call_stmt,
- count, freq, true);
+ count, true);
tree target;
initialize_inline_failed (edge);
Return direct edge created. */
cgraph_edge *
-cgraph_edge::make_speculative (cgraph_node *n2, gcov_type direct_count,
- int direct_frequency)
+cgraph_edge::make_speculative (cgraph_node *n2, profile_count direct_count)
{
cgraph_node *n = caller;
ipa_ref *ref = NULL;
cgraph_edge *e2;
if (dump_file)
- {
- fprintf (dump_file, "Indirect call -> speculative call"
- " %s/%i => %s/%i\n",
- xstrdup_for_dump (n->name ()), n->order,
- xstrdup_for_dump (n2->name ()), n2->order);
- }
+ fprintf (dump_file, "Indirect call -> speculative call %s => %s\n",
+ n->dump_name (), n2->dump_name ());
speculative = true;
- e2 = n->create_edge (n2, call_stmt, direct_count, direct_frequency);
+ e2 = n->create_edge (n2, call_stmt, direct_count);
initialize_inline_failed (e2);
e2->speculative = true;
if (TREE_NOTHROW (n2->decl))
e2->lto_stmt_uid = lto_stmt_uid;
e2->in_polymorphic_cdtor = in_polymorphic_cdtor;
count -= e2->count;
- frequency -= e2->frequency;
symtab->call_edge_duplication_hooks (this, e2);
ref = n->create_reference (n2, IPA_REF_ADDR, call_stmt);
ref->lto_stmt_uid = lto_stmt_uid;
{
if (callee_decl)
{
- fprintf (dump_file, "Speculative indirect call %s/%i => %s/%i has "
+ fprintf (dump_file, "Speculative indirect call %s => %s has "
"turned out to have contradicting known target ",
- xstrdup_for_dump (edge->caller->name ()),
- edge->caller->order,
- xstrdup_for_dump (e2->callee->name ()),
- e2->callee->order);
- print_generic_expr (dump_file, callee_decl, 0);
+ edge->caller->dump_name (),
+ e2->callee->dump_name ());
+ print_generic_expr (dump_file, callee_decl);
fprintf (dump_file, "\n");
}
else
{
- fprintf (dump_file, "Removing speculative call %s/%i => %s/%i\n",
- xstrdup_for_dump (edge->caller->name ()),
- edge->caller->order,
- xstrdup_for_dump (e2->callee->name ()),
- e2->callee->order);
+ fprintf (dump_file, "Removing speculative call %s => %s\n",
+ edge->caller->dump_name (),
+ e2->callee->dump_name ());
}
}
}
in the functions inlined through it. */
}
edge->count += e2->count;
- edge->frequency += e2->frequency;
- if (edge->frequency > CGRAPH_FREQ_MAX)
- edge->frequency = CGRAPH_FREQ_MAX;
edge->speculative = false;
e2->speculative = false;
ref->remove_reference ();
/* Insert to callers list of the new callee. */
edge->set_callee (callee);
- if (call_stmt)
- call_stmt_cannot_inline_p
- = !gimple_check_call_matching_types (call_stmt, callee->decl,
- false);
+ if (call_stmt
+ && !gimple_check_call_matching_types (call_stmt, callee->decl, false))
+ {
+ call_stmt_cannot_inline_p = true;
+ inline_failed = CIF_MISMATCHED_ARGUMENTS;
+ }
/* We need to re-determine the inlining status of the edge. */
initialize_inline_failed (edge);
/* If necessary, change the function declaration in the call statement
associated with E so that it corresponds to the edge callee. */
-gimple
+gimple *
cgraph_edge::redirect_call_stmt_to_callee (void)
{
cgraph_edge *e = this;
tree decl = gimple_call_fndecl (e->call_stmt);
- tree lhs = gimple_call_lhs (e->call_stmt);
gcall *new_stmt;
gimple_stmt_iterator gsi;
-#ifdef ENABLE_CHECKING
- cgraph_node *node;
-#endif
+ bool skip_bounds = false;
if (e->speculative)
{
if (decl)
e = e->resolve_speculation (decl);
/* If types do not match, speculation was likely wrong.
- The direct edge was posisbly redirected to the clone with a different
+ The direct edge was possibly redirected to the clone with a different
signature. We did not update the call statement yet, so compare it
with the reference that still points to the proper type. */
else if (!gimple_check_call_matching_types (e->call_stmt,
true))
{
if (dump_file)
- fprintf (dump_file, "Not expanding speculative call of %s/%i -> %s/%i\n"
+ fprintf (dump_file, "Not expanding speculative call of %s -> %s\n"
"Type mismatch.\n",
- xstrdup_for_dump (e->caller->name ()),
- e->caller->order,
- xstrdup_for_dump (e->callee->name ()),
- e->callee->order);
+ e->caller->dump_name (),
+ e->callee->dump_name ());
e = e->resolve_speculation ();
/* We are producing the final function body and will throw away the
callgraph edges really soon. Reset the counts/frequencies to
keep verifier happy in the case of roundoff errors. */
e->count = gimple_bb (e->call_stmt)->count;
- e->frequency = compute_call_stmt_bb_frequency
- (e->caller->decl, gimple_bb (e->call_stmt));
}
/* Expand speculation into GIMPLE code. */
else
{
if (dump_file)
- fprintf (dump_file,
- "Expanding speculative call of %s/%i -> %s/%i count:"
- "%"PRId64"\n",
- xstrdup_for_dump (e->caller->name ()),
- e->caller->order,
- xstrdup_for_dump (e->callee->name ()),
- e->callee->order,
- (int64_t)e->count);
+ {
+ fprintf (dump_file,
+ "Expanding speculative call of %s -> %s count: ",
+ e->caller->dump_name (),
+ e->callee->dump_name ());
+ e->count.dump (dump_file);
+ fprintf (dump_file, "\n");
+ }
gcc_assert (e2->speculative);
push_cfun (DECL_STRUCT_FUNCTION (e->caller->decl));
+
+ profile_probability prob = e->count.probability_in (e->count
+ + e2->count);
+ if (!prob.initialized_p ())
+ prob = profile_probability::even ();
new_stmt = gimple_ic (e->call_stmt,
dyn_cast<cgraph_node *> (ref->referred),
- e->count || e2->count
- ? RDIV (e->count * REG_BR_PROB_BASE,
- e->count + e2->count)
- : e->frequency || e2->frequency
- ? RDIV (e->frequency * REG_BR_PROB_BASE,
- e->frequency + e2->frequency)
- : REG_BR_PROB_BASE / 2,
- e->count, e->count + e2->count);
+ prob);
e->speculative = false;
e->caller->set_call_stmt_including_clones (e->call_stmt, new_stmt,
false);
+ e->count = gimple_bb (e->call_stmt)->count;
/* Fix edges for BUILT_IN_CHKP_BNDRET calls attached to the
processed call stmt. */
gcall *ibndret = chkp_retbnd_call_by_val (iresult);
struct cgraph_edge *iedge
= e2->caller->cgraph_node::get_edge (ibndret);
- struct cgraph_edge *dedge;
if (dbndret)
- {
- dedge = iedge->caller->create_edge (iedge->callee,
- dbndret, e->count,
- e->frequency);
- dedge->frequency = compute_call_stmt_bb_frequency
- (dedge->caller->decl, gimple_bb (dedge->call_stmt));
- }
- iedge->frequency = compute_call_stmt_bb_frequency
- (iedge->caller->decl, gimple_bb (iedge->call_stmt));
+ iedge->caller->create_edge (iedge->callee, dbndret, e->count);
}
- e->frequency = compute_call_stmt_bb_frequency
- (e->caller->decl, gimple_bb (e->call_stmt));
- e2->frequency = compute_call_stmt_bb_frequency
- (e2->caller->decl, gimple_bb (e2->call_stmt));
e2->speculative = false;
+ e2->count = gimple_bb (e2->call_stmt)->count;
ref->speculative = false;
ref->stmt = NULL;
/* Indirect edges are not both in the call site hash.
}
}
+ /* We might propagate instrumented function pointer into
+ not instrumented function and vice versa. In such a
+ case we need to either fix function declaration or
+ remove bounds from call statement. */
+ if (flag_check_pointer_bounds && e->callee)
+ skip_bounds = chkp_redirect_edge (e);
+
if (e->indirect_unknown_callee
- || decl == e->callee->decl)
+ || (decl == e->callee->decl
+ && !skip_bounds))
return e->call_stmt;
-#ifdef ENABLE_CHECKING
- if (decl)
+ if (flag_checking && decl)
{
- node = cgraph_node::get (decl);
+ cgraph_node *node = cgraph_node::get (decl);
gcc_assert (!node || !node->clone.combined_args_to_skip);
}
-#endif
if (symtab->dump_file)
{
- fprintf (symtab->dump_file, "updating call of %s/%i -> %s/%i: ",
- xstrdup_for_dump (e->caller->name ()), e->caller->order,
- xstrdup_for_dump (e->callee->name ()), e->callee->order);
+ fprintf (symtab->dump_file, "updating call of %s -> %s: ",
+ e->caller->dump_name (), e->callee->dump_name ());
print_gimple_stmt (symtab->dump_file, e->call_stmt, 0, dump_flags);
if (e->callee->clone.combined_args_to_skip)
{
}
}
- if (e->callee->clone.combined_args_to_skip)
+ if (e->callee->clone.combined_args_to_skip
+ || skip_bounds)
{
int lp_nr;
- new_stmt
- = gimple_call_copy_skip_args (e->call_stmt,
- e->callee->clone.combined_args_to_skip);
+ new_stmt = e->call_stmt;
+ if (e->callee->clone.combined_args_to_skip)
+ new_stmt
+ = gimple_call_copy_skip_args (new_stmt,
+ e->callee->clone.combined_args_to_skip);
+ if (skip_bounds)
+ new_stmt = chkp_copy_call_skip_bounds (new_stmt);
+
+ tree old_fntype = gimple_call_fntype (e->call_stmt);
gimple_call_set_fndecl (new_stmt, e->callee->decl);
- gimple_call_set_fntype (new_stmt, gimple_call_fntype (e->call_stmt));
+ cgraph_node *origin = e->callee;
+ while (origin->clone_of)
+ origin = origin->clone_of;
+
+ if ((origin->former_clone_of
+ && old_fntype == TREE_TYPE (origin->former_clone_of))
+ || old_fntype == TREE_TYPE (origin->decl))
+ gimple_call_set_fntype (new_stmt, TREE_TYPE (e->callee->decl));
+ else
+ {
+ bitmap skip = e->callee->clone.combined_args_to_skip;
+ tree t = cgraph_build_function_type_skip_args (old_fntype, skip,
+ false);
+ gimple_call_set_fntype (new_stmt, t);
+ }
if (gimple_vdef (new_stmt)
&& TREE_CODE (gimple_vdef (new_stmt)) == SSA_NAME)
SSA_NAME_DEF_STMT (gimple_vdef (new_stmt)) = new_stmt;
gsi = gsi_for_stmt (e->call_stmt);
+
+ /* For optimized away parameters, add on the caller side
+ before the call
+ DEBUG D#X => parm_Y(D)
+ stmts and associate D#X with parm in decl_debug_args_lookup
+ vector to say for debug info that if parameter parm had been passed,
+ it would have value parm_Y(D). */
+ if (e->callee->clone.combined_args_to_skip && MAY_HAVE_DEBUG_BIND_STMTS)
+ {
+ vec<tree, va_gc> **debug_args
+ = decl_debug_args_lookup (e->callee->decl);
+ tree old_decl = gimple_call_fndecl (e->call_stmt);
+ if (debug_args && old_decl)
+ {
+ tree parm;
+ unsigned i = 0, num;
+ unsigned len = vec_safe_length (*debug_args);
+ unsigned nargs = gimple_call_num_args (e->call_stmt);
+ for (parm = DECL_ARGUMENTS (old_decl), num = 0;
+ parm && num < nargs;
+ parm = DECL_CHAIN (parm), num++)
+ if (bitmap_bit_p (e->callee->clone.combined_args_to_skip, num)
+ && is_gimple_reg (parm))
+ {
+ unsigned last = i;
+
+ while (i < len && (**debug_args)[i] != DECL_ORIGIN (parm))
+ i += 2;
+ if (i >= len)
+ {
+ i = 0;
+ while (i < last
+ && (**debug_args)[i] != DECL_ORIGIN (parm))
+ i += 2;
+ if (i >= last)
+ continue;
+ }
+ tree ddecl = (**debug_args)[i + 1];
+ tree arg = gimple_call_arg (e->call_stmt, num);
+ if (!useless_type_conversion_p (TREE_TYPE (ddecl),
+ TREE_TYPE (arg)))
+ {
+ tree rhs1;
+ if (!fold_convertible_p (TREE_TYPE (ddecl), arg))
+ continue;
+ if (TREE_CODE (arg) == SSA_NAME
+ && gimple_assign_cast_p (SSA_NAME_DEF_STMT (arg))
+ && (rhs1
+ = gimple_assign_rhs1 (SSA_NAME_DEF_STMT (arg)))
+ && useless_type_conversion_p (TREE_TYPE (ddecl),
+ TREE_TYPE (rhs1)))
+ arg = rhs1;
+ else
+ arg = fold_convert (TREE_TYPE (ddecl), arg);
+ }
+
+ gimple *def_temp
+ = gimple_build_debug_bind (ddecl, unshare_expr (arg),
+ e->call_stmt);
+ gsi_insert_before (&gsi, def_temp, GSI_SAME_STMT);
+ }
+ }
+ }
+
gsi_replace (&gsi, new_stmt, false);
/* We need to defer cleaning EH info on the new statement to
fixup-cfg. We may not have dominator information at this point
update_stmt_fn (DECL_STRUCT_FUNCTION (e->caller->decl), new_stmt);
}
- /* If the call becomes noreturn, remove the lhs. */
- if (lhs && (gimple_call_flags (new_stmt) & ECF_NORETURN))
+ /* If changing the call to __cxa_pure_virtual or similar noreturn function,
+ adjust gimple_call_fntype too. */
+ if (gimple_call_noreturn_p (new_stmt)
+ && VOID_TYPE_P (TREE_TYPE (TREE_TYPE (e->callee->decl)))
+ && TYPE_ARG_TYPES (TREE_TYPE (e->callee->decl))
+ && (TREE_VALUE (TYPE_ARG_TYPES (TREE_TYPE (e->callee->decl)))
+ == void_type_node))
+ gimple_call_set_fntype (new_stmt, TREE_TYPE (e->callee->decl));
+
+ /* If the call becomes noreturn, remove the LHS if possible. */
+ tree lhs = gimple_call_lhs (new_stmt);
+ if (lhs
+ && gimple_call_noreturn_p (new_stmt)
+ && (VOID_TYPE_P (TREE_TYPE (gimple_call_fntype (new_stmt)))
+ || should_remove_lhs_p (lhs)))
{
if (TREE_CODE (lhs) == SSA_NAME)
{
TREE_TYPE (lhs), NULL);
var = get_or_create_ssa_default_def
(DECL_STRUCT_FUNCTION (e->caller->decl), var);
- gimple set_stmt = gimple_build_assign (lhs, var);
+ gimple *set_stmt = gimple_build_assign (lhs, var);
gsi = gsi_for_stmt (new_stmt);
gsi_insert_before_without_update (&gsi, set_stmt, GSI_SAME_STMT);
update_stmt_fn (DECL_STRUCT_FUNCTION (e->caller->decl), set_stmt);
static void
cgraph_update_edges_for_call_stmt_node (cgraph_node *node,
- gimple old_stmt, tree old_call,
- gimple new_stmt)
+ gimple *old_stmt, tree old_call,
+ gimple *new_stmt)
{
tree new_call = (new_stmt && is_gimple_call (new_stmt))
? gimple_call_fndecl (new_stmt) : 0;
{
cgraph_edge *e = node->get_edge (old_stmt);
cgraph_edge *ne = NULL;
- gcov_type count;
- int frequency;
+ profile_count count;
if (e)
{
/* Keep calls marked as dead dead. */
- if (e->callee
+ if (new_stmt && is_gimple_call (new_stmt) && e->callee
&& DECL_BUILT_IN_CLASS (e->callee->decl) == BUILT_IN_NORMAL
&& DECL_FUNCTION_CODE (e->callee->decl) == BUILT_IN_UNREACHABLE)
{
since function has changed, so inline plan and other information
attached to edge is invalid. */
count = e->count;
- frequency = e->frequency;
if (e->indirect_unknown_callee || e->inline_failed)
e->remove ();
else
/* We are seeing new direct call; compute profile info based on BB. */
basic_block bb = gimple_bb (new_stmt);
count = bb->count;
- frequency = compute_call_stmt_bb_frequency (current_function_decl,
- bb);
}
if (new_call)
{
ne = node->create_edge (cgraph_node::get_create (new_call),
- as_a <gcall *> (new_stmt), count,
- frequency);
+ as_a <gcall *> (new_stmt), count);
gcc_assert (ne->inline_failed);
}
}
of OLD_STMT before it was updated (updating can happen inplace). */
void
-cgraph_update_edges_for_call_stmt (gimple old_stmt, tree old_decl, gimple new_stmt)
+cgraph_update_edges_for_call_stmt (gimple *old_stmt, tree old_decl,
+ gimple *new_stmt)
{
cgraph_node *orig = cgraph_node::get (cfun->decl);
cgraph_node *node;
void
release_function_body (tree decl)
{
- if (DECL_STRUCT_FUNCTION (decl))
+ function *fn = DECL_STRUCT_FUNCTION (decl);
+ if (fn)
{
- if (DECL_STRUCT_FUNCTION (decl)->cfg
- || DECL_STRUCT_FUNCTION (decl)->gimple_df)
+ if (fn->cfg
+ && loops_for_fn (fn))
{
- push_cfun (DECL_STRUCT_FUNCTION (decl));
- if (cfun->cfg
- && current_loops)
- {
- cfun->curr_properties &= ~PROP_loops;
- loop_optimizer_finalize ();
- }
- if (cfun->gimple_df)
- {
- delete_tree_ssa ();
- delete_tree_cfg_annotations ();
- cfun->eh = NULL;
- }
- if (cfun->cfg)
- {
- gcc_assert (!dom_info_available_p (CDI_DOMINATORS));
- gcc_assert (!dom_info_available_p (CDI_POST_DOMINATORS));
- clear_edges ();
- cfun->cfg = NULL;
- }
- if (cfun->value_histograms)
- free_histograms ();
- pop_cfun ();
+ fn->curr_properties &= ~PROP_loops;
+ loop_optimizer_finalize (fn);
+ }
+ if (fn->gimple_df)
+ {
+ delete_tree_ssa (fn);
+ fn->eh = NULL;
}
+ if (fn->cfg)
+ {
+ gcc_assert (!dom_info_available_p (fn, CDI_DOMINATORS));
+ gcc_assert (!dom_info_available_p (fn, CDI_POST_DOMINATORS));
+ delete_tree_cfg_annotations (fn);
+ clear_edges (fn);
+ fn->cfg = NULL;
+ }
+ if (fn->value_histograms)
+ free_histograms (fn);
gimple_set_body (decl, NULL);
/* Struct function hangs a lot of data that would leak if we didn't
removed all pointers to it. */
- ggc_free (DECL_STRUCT_FUNCTION (decl));
+ ggc_free (fn);
DECL_STRUCT_FUNCTION (decl) = NULL;
}
DECL_SAVED_TREE (decl) = NULL;
if (!keep_arguments)
DECL_ARGUMENTS (decl) = NULL;
}
- /* If the node is abstract and needed, then do not clear DECL_INITIAL
- of its associated function function declaration because it's
+ /* If the node is abstract and needed, then do not clear
+ DECL_INITIAL of its associated function declaration because it's
needed to emit debug info later. */
if (!used_as_abstract_origin && DECL_INITIAL (decl))
DECL_INITIAL (decl) = error_mark_node;
cgraph_node *n;
int uid = this->uid;
+ if (symtab->ipa_clones_dump_file && symtab->cloned_nodes.contains (this))
+ fprintf (symtab->ipa_clones_dump_file,
+ "Callgraph removal;%s;%d;%s;%d;%d\n", asm_name (), order,
+ DECL_SOURCE_FILE (decl), DECL_SOURCE_LINE (decl),
+ DECL_SOURCE_COLUMN (decl));
+
symtab->call_cgraph_removal_hooks (this);
remove_callers ();
remove_callees ();
ipa_transforms_to_apply.release ();
+ delete_function_version (function_version ());
/* Incremental inlining access removed nodes stored in the postorder list.
*/
cgraph_node *node = get (decl);
if (!node)
return NULL;
- node = node->ultimate_alias_target ();
- if (node->decl != current_function_decl
- && !TREE_ASM_WRITTEN (node->decl))
+ enum availability avail;
+ node = node->ultimate_alias_target (&avail);
+ if (decl != current_function_decl
+ && (avail < AVAIL_AVAILABLE
+ || (node->decl != current_function_decl
+ && !TREE_ASM_WRITTEN (node->decl))))
return NULL;
- return &node->ultimate_alias_target ()->rtl;
+ /* Allocate if it doesn't exist. */
+ if (node->rtl == NULL)
+ node->rtl = ggc_cleared_alloc<cgraph_rtl_info> ();
+ return node->rtl;
}
/* Return a string describing the failure REASON. */
fprintf (f, "(speculative) ");
if (!inline_failed)
fprintf (f, "(inlined) ");
+ if (call_stmt_cannot_inline_p)
+ fprintf (f, "(call_stmt_cannot_inline_p) ");
if (indirect_inlining_edge)
fprintf (f, "(indirect_inlining) ");
- if (count)
- fprintf (f, "(%"PRId64"x) ", (int64_t)count);
- if (frequency)
- fprintf (f, "(%.2f per call) ", frequency / (double)CGRAPH_FREQ_BASE);
+ if (count.initialized_p ())
+ {
+ fprintf (f, "(");
+ count.dump (f);
+ fprintf (f, ",");
+ fprintf (f, "%.2f per call) ", sreal_frequency ().to_double ());
+ }
if (can_throw_external)
fprintf (f, "(can throw external) ");
}
dump_base (f);
if (global.inlined_to)
- fprintf (f, " Function %s/%i is inline copy in %s/%i\n",
- xstrdup_for_dump (name ()),
- order,
- xstrdup_for_dump (global.inlined_to->name ()),
- global.inlined_to->order);
+ fprintf (f, " Function %s is inline copy in %s\n",
+ dump_name (),
+ global.inlined_to->dump_name ());
if (clone_of)
- fprintf (f, " Clone of %s/%i\n",
- clone_of->asm_name (),
- clone_of->order);
+ fprintf (f, " Clone of %s\n", clone_of->dump_asm_name ());
if (symtab->function_flags_ready)
fprintf (f, " Availability: %s\n",
cgraph_availability_names [get_availability ()]);
fprintf (f, " Profile id: %i\n",
profile_id);
fprintf (f, " First run: %i\n", tp_first_run);
+ cgraph_function_version_info *vi = function_version ();
+ if (vi != NULL)
+ {
+ fprintf (f, " Version info: ");
+ if (vi->prev != NULL)
+ {
+ fprintf (f, "prev: ");
+ fprintf (f, "%s ", vi->prev->this_node->dump_asm_name ());
+ }
+ if (vi->next != NULL)
+ {
+ fprintf (f, "next: ");
+ fprintf (f, "%s ", vi->next->this_node->dump_asm_name ());
+ }
+ if (vi->dispatcher_resolver != NULL_TREE)
+ fprintf (f, "dispatcher: %s",
+ lang_hooks.decl_printable_name (vi->dispatcher_resolver, 2));
+
+ fprintf (f, "\n");
+ }
fprintf (f, " Function flags:");
- if (count)
- fprintf (f, " executed %"PRId64"x",
- (int64_t)count);
+ if (count.initialized_p ())
+ {
+ fprintf (f, " count: ");
+ count.dump (f);
+ }
if (origin)
fprintf (f, " nested in: %s", origin->asm_name ());
if (gimple_has_body_p (decl))
fprintf (f, " only_called_at_exit");
if (tm_clone)
fprintf (f, " tm_clone");
+ if (calls_comdat_local)
+ fprintf (f, " calls_comdat_local");
if (icf_merged)
fprintf (f, " icf_merged");
+ if (merged_comdat)
+ fprintf (f, " merged_comdat");
+ if (split_part)
+ fprintf (f, " split_part");
+ if (indirect_call_target)
+ fprintf (f, " indirect_call_target");
if (nonfreeing_fn)
fprintf (f, " nonfreeing_fn");
if (DECL_STATIC_CONSTRUCTOR (decl))
fprintf (f, " Called by: ");
+ profile_count sum = profile_count::zero ();
for (edge = callers; edge; edge = edge->next_caller)
{
- fprintf (f, "%s/%i ", edge->caller->asm_name (),
- edge->caller->order);
+ fprintf (f, "%s ", edge->caller->dump_name ());
edge->dump_edge_flags (f);
+ if (edge->count.initialized_p ())
+ sum += edge->count.ipa ();
}
fprintf (f, "\n Calls: ");
for (edge = callees; edge; edge = edge->next_callee)
{
- fprintf (f, "%s/%i ", edge->callee->asm_name (),
- edge->callee->order);
+ fprintf (f, "%s ", edge->callee->dump_name ());
edge->dump_edge_flags (f);
}
fprintf (f, "\n");
+ if (count.ipa ().initialized_p ())
+ {
+ bool ok = true;
+ bool min = false;
+ ipa_ref *ref;
+
+ FOR_EACH_ALIAS (this, ref)
+ if (dyn_cast <cgraph_node *> (ref->referring)->count.initialized_p ())
+ sum += dyn_cast <cgraph_node *> (ref->referring)->count.ipa ();
+
+ if (global.inlined_to
+ || (symtab->state < EXPANSION
+ && ultimate_alias_target () == this && only_called_directly_p ()))
+ ok = !count.ipa ().differs_from_p (sum);
+ else if (count.ipa () > profile_count::from_gcov_type (100)
+ && count.ipa () < sum.apply_scale (99, 100))
+ ok = false, min = true;
+ if (!ok)
+ {
+ fprintf (f, " Invalid sum of caller counts ");
+ sum.dump (f);
+ if (min)
+ fprintf (f, ", should be at most ");
+ else
+ fprintf (f, ", should be ");
+ count.ipa ().dump (f);
+ fprintf (f, "\n");
+ }
+ }
+
for (edge = indirect_calls; edge; edge = edge->next_callee)
{
if (edge->indirect_info->polymorphic)
/* Return function availability. See cgraph.h for description of individual
return values. */
enum availability
-cgraph_node::get_availability (void)
+cgraph_node::get_availability (symtab_node *ref)
{
+ if (ref)
+ {
+ cgraph_node *cref = dyn_cast <cgraph_node *> (ref);
+ if (cref)
+ ref = cref->global.inlined_to;
+ }
enum availability avail;
if (!analyzed)
avail = AVAIL_NOT_AVAILABLE;
else if (local.local)
avail = AVAIL_LOCAL;
- else if (alias && weakref)
- ultimate_alias_target (&avail);
- else if (lookup_attribute ("ifunc", DECL_ATTRIBUTES (decl)))
+ else if (global.inlined_to)
+ avail = AVAIL_AVAILABLE;
+ else if (transparent_alias)
+ ultimate_alias_target (&avail, ref);
+ else if (lookup_attribute ("ifunc", DECL_ATTRIBUTES (decl))
+ || lookup_attribute ("noipa", DECL_ATTRIBUTES (decl)))
avail = AVAIL_INTERPOSABLE;
else if (!externally_visible)
avail = AVAIL_AVAILABLE;
+ /* If this is a reference from symbol itself and there are no aliases, we
+ may be sure that the symbol was not interposed by something else because
+ the symbol itself would be unreachable otherwise.
+
+ Also comdat groups are always resolved in groups. */
+ else if ((this == ref && !has_aliases_p ())
+ || (ref && get_comdat_group ()
+ && get_comdat_group () == ref->get_comdat_group ()))
+ avail = AVAIL_AVAILABLE;
/* Inline functions are safe to be analyzed even if their symbol can
be overwritten at runtime. It is not meaningful to enforce any sane
- behaviour on replacing inline function by different body. */
+ behavior on replacing inline function by different body. */
else if (DECL_DECLARED_INLINE_P (decl))
avail = AVAIL_AVAILABLE;
care at least of two notable extensions - the COMDAT functions
used to share template instantiations in C++ (this is symmetric
to code cp_cannot_inline_tree_fn and probably shall be shared and
- the inlinability hooks completely eliminated).
-
- ??? Does the C++ one definition rule allow us to always return
- AVAIL_AVAILABLE here? That would be good reason to preserve this
- bit. */
+ the inlinability hooks completely eliminated). */
else if (decl_replaceable_p (decl) && !DECL_EXTERNAL (decl))
avail = AVAIL_INTERPOSABLE;
}
/* Call callback on cgraph_node, thunks and aliases associated to cgraph_node.
- When INCLUDE_OVERWRITABLE is false, overwritable aliases and thunks are
+ When INCLUDE_OVERWRITABLE is false, overwritable symbols are
skipped. When EXCLUDE_VIRTUAL_THUNKS is true, virtual thunks are
skipped. */
bool
{
cgraph_edge *e;
ipa_ref *ref;
+ enum availability avail = AVAIL_AVAILABLE;
- if (callback (this, data))
- return true;
+ if (include_overwritable
+ || (avail = get_availability ()) > AVAIL_INTERPOSABLE)
+ {
+ if (callback (this, data))
+ return true;
+ }
FOR_EACH_ALIAS (this, ref)
{
cgraph_node *alias = dyn_cast <cgraph_node *> (ref->referring);
exclude_virtual_thunks))
return true;
}
+ if (avail <= AVAIL_INTERPOSABLE)
+ return false;
for (e = callers; e; e = e->next_caller)
if (e->caller->thunk.thunk_p
&& (include_overwritable
node->forced_by_abi = false;
node->local.local = true;
node->set_section (NULL);
- node->unique_name = (node->resolution == LDPR_PREVAILING_DEF_IRONLY
- || node->resolution == LDPR_PREVAILING_DEF_IRONLY_EXP);
+ node->unique_name = ((node->resolution == LDPR_PREVAILING_DEF_IRONLY
+ || node->resolution == LDPR_PREVAILING_DEF_IRONLY_EXP)
+ && !flag_incremental_link);
node->resolution = LDPR_PREVAILING_DEF_IRONLY;
gcc_assert (node->get_availability () == AVAIL_LOCAL);
}
/* Worker to set nothrow flag. */
-static bool
-cgraph_set_nothrow_flag_1 (cgraph_node *node, void *data)
+static void
+set_nothrow_flag_1 (cgraph_node *node, bool nothrow, bool non_call,
+ bool *changed)
{
cgraph_edge *e;
- TREE_NOTHROW (node->decl) = data != NULL;
-
- if (data != NULL)
- for (e = node->callers; e; e = e->next_caller)
- e->can_throw_external = false;
- return false;
+ if (nothrow && !TREE_NOTHROW (node->decl))
+ {
+ /* With non-call exceptions we can't say for sure if other function body
+ was not possibly optimized to stil throw. */
+ if (!non_call || node->binds_to_current_def_p ())
+ {
+ TREE_NOTHROW (node->decl) = true;
+ *changed = true;
+ for (e = node->callers; e; e = e->next_caller)
+ e->can_throw_external = false;
+ }
+ }
+ else if (!nothrow && TREE_NOTHROW (node->decl))
+ {
+ TREE_NOTHROW (node->decl) = false;
+ *changed = true;
+ }
+ ipa_ref *ref;
+ FOR_EACH_ALIAS (node, ref)
+ {
+ cgraph_node *alias = dyn_cast <cgraph_node *> (ref->referring);
+ if (!nothrow || alias->get_availability () > AVAIL_INTERPOSABLE)
+ set_nothrow_flag_1 (alias, nothrow, non_call, changed);
+ }
+ for (cgraph_edge *e = node->callers; e; e = e->next_caller)
+ if (e->caller->thunk.thunk_p
+ && (!nothrow || e->caller->get_availability () > AVAIL_INTERPOSABLE))
+ set_nothrow_flag_1 (e->caller, nothrow, non_call, changed);
}
/* Set TREE_NOTHROW on NODE's decl and on aliases of NODE
if any to NOTHROW. */
-void
+bool
cgraph_node::set_nothrow_flag (bool nothrow)
{
- call_for_symbol_thunks_and_aliases (cgraph_set_nothrow_flag_1,
- (void *)(size_t)nothrow, false);
+ bool changed = false;
+ bool non_call = opt_for_fn (decl, flag_non_call_exceptions);
+
+ if (!nothrow || get_availability () > AVAIL_INTERPOSABLE)
+ set_nothrow_flag_1 (this, nothrow, non_call, &changed);
+ else
+ {
+ ipa_ref *ref;
+
+ FOR_EACH_ALIAS (this, ref)
+ {
+ cgraph_node *alias = dyn_cast <cgraph_node *> (ref->referring);
+ if (!nothrow || alias->get_availability () > AVAIL_INTERPOSABLE)
+ set_nothrow_flag_1 (alias, nothrow, non_call, &changed);
+ }
+ }
+ return changed;
}
-/* Worker to set const flag. */
+/* Worker to set malloc flag. */
+static void
+set_malloc_flag_1 (cgraph_node *node, bool malloc_p, bool *changed)
+{
+ if (malloc_p && !DECL_IS_MALLOC (node->decl))
+ {
+ DECL_IS_MALLOC (node->decl) = true;
+ *changed = true;
+ }
-static bool
-cgraph_set_const_flag_1 (cgraph_node *node, void *data)
+ ipa_ref *ref;
+ FOR_EACH_ALIAS (node, ref)
+ {
+ cgraph_node *alias = dyn_cast<cgraph_node *> (ref->referring);
+ if (!malloc_p || alias->get_availability () > AVAIL_INTERPOSABLE)
+ set_malloc_flag_1 (alias, malloc_p, changed);
+ }
+
+ for (cgraph_edge *e = node->callers; e; e = e->next_caller)
+ if (e->caller->thunk.thunk_p
+ && (!malloc_p || e->caller->get_availability () > AVAIL_INTERPOSABLE))
+ set_malloc_flag_1 (e->caller, malloc_p, changed);
+}
+
+/* Set DECL_IS_MALLOC on NODE's decl and on NODE's aliases if any. */
+
+bool
+cgraph_node::set_malloc_flag (bool malloc_p)
+{
+ bool changed = false;
+
+ if (!malloc_p || get_availability () > AVAIL_INTERPOSABLE)
+ set_malloc_flag_1 (this, malloc_p, &changed);
+ else
+ {
+ ipa_ref *ref;
+
+ FOR_EACH_ALIAS (this, ref)
+ {
+ cgraph_node *alias = dyn_cast<cgraph_node *> (ref->referring);
+ if (!malloc_p || alias->get_availability () > AVAIL_INTERPOSABLE)
+ set_malloc_flag_1 (alias, malloc_p, &changed);
+ }
+ }
+ return changed;
+}
+
+/* Worker to set_const_flag. */
+
+static void
+set_const_flag_1 (cgraph_node *node, bool set_const, bool looping,
+ bool *changed)
{
/* Static constructors and destructors without a side effect can be
optimized out. */
- if (data && !((size_t)data & 2))
+ if (set_const && !looping)
{
if (DECL_STATIC_CONSTRUCTOR (node->decl))
- DECL_STATIC_CONSTRUCTOR (node->decl) = 0;
+ {
+ DECL_STATIC_CONSTRUCTOR (node->decl) = 0;
+ *changed = true;
+ }
if (DECL_STATIC_DESTRUCTOR (node->decl))
- DECL_STATIC_DESTRUCTOR (node->decl) = 0;
+ {
+ DECL_STATIC_DESTRUCTOR (node->decl) = 0;
+ *changed = true;
+ }
}
- TREE_READONLY (node->decl) = data != NULL;
- DECL_LOOPING_CONST_OR_PURE_P (node->decl) = ((size_t)data & 2) != 0;
- return false;
+ if (!set_const)
+ {
+ if (TREE_READONLY (node->decl))
+ {
+ TREE_READONLY (node->decl) = 0;
+ DECL_LOOPING_CONST_OR_PURE_P (node->decl) = false;
+ *changed = true;
+ }
+ }
+ else
+ {
+ /* Consider function:
+
+ bool a(int *p)
+ {
+ return *p==*p;
+ }
+
+ During early optimization we will turn this into:
+
+ bool a(int *p)
+ {
+ return true;
+ }
+
+ Now if this function will be detected as CONST however when interposed
+ it may end up being just pure. We always must assume the worst
+ scenario here. */
+ if (TREE_READONLY (node->decl))
+ {
+ if (!looping && DECL_LOOPING_CONST_OR_PURE_P (node->decl))
+ {
+ DECL_LOOPING_CONST_OR_PURE_P (node->decl) = false;
+ *changed = true;
+ }
+ }
+ else if (node->binds_to_current_def_p ())
+ {
+ TREE_READONLY (node->decl) = true;
+ DECL_LOOPING_CONST_OR_PURE_P (node->decl) = looping;
+ DECL_PURE_P (node->decl) = false;
+ *changed = true;
+ }
+ else
+ {
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file, "Dropping state to PURE because function does "
+ "not bind to current def.\n");
+ if (!DECL_PURE_P (node->decl))
+ {
+ DECL_PURE_P (node->decl) = true;
+ DECL_LOOPING_CONST_OR_PURE_P (node->decl) = looping;
+ *changed = true;
+ }
+ else if (!looping && DECL_LOOPING_CONST_OR_PURE_P (node->decl))
+ {
+ DECL_LOOPING_CONST_OR_PURE_P (node->decl) = false;
+ *changed = true;
+ }
+ }
+ }
+
+ ipa_ref *ref;
+ FOR_EACH_ALIAS (node, ref)
+ {
+ cgraph_node *alias = dyn_cast <cgraph_node *> (ref->referring);
+ if (!set_const || alias->get_availability () > AVAIL_INTERPOSABLE)
+ set_const_flag_1 (alias, set_const, looping, changed);
+ }
+ for (cgraph_edge *e = node->callers; e; e = e->next_caller)
+ if (e->caller->thunk.thunk_p
+ && (!set_const || e->caller->get_availability () > AVAIL_INTERPOSABLE))
+ {
+ /* Virtual thunks access virtual offset in the vtable, so they can
+ only be pure, never const. */
+ if (set_const
+ && (e->caller->thunk.virtual_offset_p
+ || !node->binds_to_current_def_p (e->caller)))
+ *changed |= e->caller->set_pure_flag (true, looping);
+ else
+ set_const_flag_1 (e->caller, set_const, looping, changed);
+ }
}
-/* Set TREE_READONLY on cgraph_node's decl and on aliases of the node
- if any to READONLY. */
+/* If SET_CONST is true, mark function, aliases and thunks to be ECF_CONST.
+ If SET_CONST if false, clear the flag.
-void
-cgraph_node::set_const_flag (bool readonly, bool looping)
+ When setting the flag be careful about possible interposition and
+ do not set the flag for functions that can be interposet and set pure
+ flag for functions that can bind to other definition.
+
+ Return true if any change was done. */
+
+bool
+cgraph_node::set_const_flag (bool set_const, bool looping)
{
- call_for_symbol_thunks_and_aliases (cgraph_set_const_flag_1,
- (void *)(size_t)(readonly + (int)looping * 2),
- false, true);
+ bool changed = false;
+ if (!set_const || get_availability () > AVAIL_INTERPOSABLE)
+ set_const_flag_1 (this, set_const, looping, &changed);
+ else
+ {
+ ipa_ref *ref;
+
+ FOR_EACH_ALIAS (this, ref)
+ {
+ cgraph_node *alias = dyn_cast <cgraph_node *> (ref->referring);
+ if (!set_const || alias->get_availability () > AVAIL_INTERPOSABLE)
+ set_const_flag_1 (alias, set_const, looping, &changed);
+ }
+ }
+ return changed;
}
-/* Worker to set pure flag. */
+/* Info used by set_pure_flag_1. */
+
+struct set_pure_flag_info
+{
+ bool pure;
+ bool looping;
+ bool changed;
+};
+
+/* Worker to set_pure_flag. */
static bool
-cgraph_set_pure_flag_1 (cgraph_node *node, void *data)
+set_pure_flag_1 (cgraph_node *node, void *data)
{
+ struct set_pure_flag_info *info = (struct set_pure_flag_info *)data;
/* Static constructors and destructors without a side effect can be
optimized out. */
- if (data && !((size_t)data & 2))
+ if (info->pure && !info->looping)
{
if (DECL_STATIC_CONSTRUCTOR (node->decl))
- DECL_STATIC_CONSTRUCTOR (node->decl) = 0;
+ {
+ DECL_STATIC_CONSTRUCTOR (node->decl) = 0;
+ info->changed = true;
+ }
if (DECL_STATIC_DESTRUCTOR (node->decl))
- DECL_STATIC_DESTRUCTOR (node->decl) = 0;
+ {
+ DECL_STATIC_DESTRUCTOR (node->decl) = 0;
+ info->changed = true;
+ }
+ }
+ if (info->pure)
+ {
+ if (!DECL_PURE_P (node->decl) && !TREE_READONLY (node->decl))
+ {
+ DECL_PURE_P (node->decl) = true;
+ DECL_LOOPING_CONST_OR_PURE_P (node->decl) = info->looping;
+ info->changed = true;
+ }
+ else if (DECL_LOOPING_CONST_OR_PURE_P (node->decl)
+ && !info->looping)
+ {
+ DECL_LOOPING_CONST_OR_PURE_P (node->decl) = false;
+ info->changed = true;
+ }
+ }
+ else
+ {
+ if (DECL_PURE_P (node->decl))
+ {
+ DECL_PURE_P (node->decl) = false;
+ DECL_LOOPING_CONST_OR_PURE_P (node->decl) = false;
+ info->changed = true;
+ }
}
- DECL_PURE_P (node->decl) = data != NULL;
- DECL_LOOPING_CONST_OR_PURE_P (node->decl) = ((size_t)data & 2) != 0;
return false;
}
/* Set DECL_PURE_P on cgraph_node's decl and on aliases of the node
- if any to PURE. */
+ if any to PURE.
-void
+ When setting the flag, be careful about possible interposition.
+ Return true if any change was done. */
+
+bool
cgraph_node::set_pure_flag (bool pure, bool looping)
{
- call_for_symbol_thunks_and_aliases (cgraph_set_pure_flag_1,
- (void *)(size_t)(pure + (int)looping * 2),
- false, true);
+ struct set_pure_flag_info info = {pure, looping, false};
+ if (!pure)
+ looping = false;
+ call_for_symbol_thunks_and_aliases (set_pure_flag_1, &info, !pure, true);
+ return info.changed;
}
/* Return true when cgraph_node can not return or throw and thus
bool
cgraph_edge::maybe_hot_p (void)
{
- /* TODO: Export profile_status from cfun->cfg to cgraph_node. */
- if (profile_info
- && opt_for_fn (caller->decl, flag_branch_probabilities)
- && !maybe_hot_count_p (NULL, count))
+ if (!maybe_hot_count_p (NULL, count.ipa ()))
return false;
if (caller->frequency == NODE_FREQUENCY_UNLIKELY_EXECUTED
|| (callee
return false;
if (caller->frequency == NODE_FREQUENCY_HOT)
return true;
+ /* If profile is now known yet, be conservative.
+ FIXME: this predicate is used by early inliner and can do better there. */
+ if (symtab->state < IPA_SSA)
+ return true;
if (caller->frequency == NODE_FREQUENCY_EXECUTED_ONCE
- && frequency < CGRAPH_FREQ_BASE * 3 / 2)
+ && sreal_frequency () * 2 < 3)
+ return false;
+ if (PARAM_VALUE (HOT_BB_FREQUENCY_FRACTION) == 0
+ || sreal_frequency () * PARAM_VALUE (HOT_BB_FREQUENCY_FRACTION) <= 1)
return false;
- if (opt_for_fn (caller->decl, flag_guess_branch_prob))
- {
- if (PARAM_VALUE (HOT_BB_FREQUENCY_FRACTION) == 0
- || frequency <= (CGRAPH_FREQ_BASE
- / PARAM_VALUE (HOT_BB_FREQUENCY_FRACTION)))
- return false;
- }
return true;
}
if (avail > AVAIL_INTERPOSABLE)
for (cs = node->callers; cs != NULL; cs = cs->next_caller)
- if (!cs->indirect_inlining_edge)
+ if (!cs->indirect_inlining_edge
+ && !cs->caller->thunk.thunk_p)
redirect_callers->safe_push (cs);
return false;
}
/* Verify edge count and frequency. */
bool
-cgraph_edge::verify_count_and_frequency ()
+cgraph_edge::verify_count ()
{
bool error_found = false;
- if (count < 0)
- {
- error ("caller edge count is negative");
- error_found = true;
- }
- if (frequency < 0)
- {
- error ("caller edge frequency is negative");
- error_found = true;
- }
- if (frequency > CGRAPH_FREQ_MAX)
+ if (!count.verify ())
{
- error ("caller edge frequency is too large");
+ error ("caller edge count invalid");
error_found = true;
}
return error_found;
/* Switch to THIS_CFUN if needed and print STMT to stderr. */
static void
-cgraph_debug_gimple_stmt (function *this_cfun, gimple stmt)
+cgraph_debug_gimple_stmt (function *this_cfun, gimple *stmt)
{
bool fndecl_was_null = false;
/* debug_gimple_stmt needs correct cfun */
node = node->ultimate_alias_target ();
/* Optimizers can redirect unreachable calls or calls triggering undefined
- behaviour to builtin_unreachable. */
+ behavior to builtin_unreachable. */
if (DECL_BUILT_IN_CLASS (callee->decl) == BUILT_IN_NORMAL
&& DECL_FUNCTION_CODE (callee->decl) == BUILT_IN_UNREACHABLE)
return false;
identifier_to_locale (e->callee->name ()));
error_found = true;
}
- if (count < 0)
+ if (!count.verify ())
{
- error ("execution count is negative");
+ error ("cgraph count invalid");
error_found = true;
}
if (global.inlined_to && same_comdat_group)
bool check_comdat = comdat_local_p ();
for (e = callers; e; e = e->next_caller)
{
- if (e->verify_count_and_frequency ())
+ if (e->verify_count ())
error_found = true;
if (check_comdat
&& !in_same_comdat_group_p (e->caller))
}
for (e = callees; e; e = e->next_callee)
{
- if (e->verify_count_and_frequency ())
+ if (e->verify_count ())
error_found = true;
if (gimple_has_body_p (e->caller->decl)
&& !e->caller->global.inlined_to
&& !e->speculative
/* Optimized out calls are redirected to __builtin_unreachable. */
- && (e->frequency
- || e->callee->decl
- != builtin_decl_implicit (BUILT_IN_UNREACHABLE))
- && (e->frequency
- != compute_call_stmt_bb_frequency (e->caller->decl,
- gimple_bb (e->call_stmt))))
+ && (e->count.nonzero_p ()
+ || ! e->callee->decl
+ || DECL_BUILT_IN_CLASS (e->callee->decl) != BUILT_IN_NORMAL
+ || DECL_FUNCTION_CODE (e->callee->decl) != BUILT_IN_UNREACHABLE)
+ && count
+ == ENTRY_BLOCK_PTR_FOR_FN (DECL_STRUCT_FUNCTION (decl))->count
+ && (!e->count.ipa_p ()
+ && e->count.differs_from_p (gimple_bb (e->call_stmt)->count)))
{
- error ("caller edge frequency %i does not match BB frequency %i",
- e->frequency,
- compute_call_stmt_bb_frequency (e->caller->decl,
- gimple_bb (e->call_stmt)));
+ error ("caller edge count does not match BB count");
+ fprintf (stderr, "edge count: ");
+ e->count.dump (stderr);
+ fprintf (stderr, "\n bb count: ");
+ gimple_bb (e->call_stmt)->count.dump (stderr);
+ fprintf (stderr, "\n");
error_found = true;
}
}
for (e = indirect_calls; e; e = e->next_callee)
{
- if (e->verify_count_and_frequency ())
+ if (e->verify_count ())
error_found = true;
if (gimple_has_body_p (e->caller->decl)
&& !e->caller->global.inlined_to
&& !e->speculative
- && (e->frequency
- != compute_call_stmt_bb_frequency (e->caller->decl,
- gimple_bb (e->call_stmt))))
+ && e->count.ipa_p ()
+ && count
+ == ENTRY_BLOCK_PTR_FOR_FN (DECL_STRUCT_FUNCTION (decl))->count
+ && (!e->count.ipa_p ()
+ && e->count.differs_from_p (gimple_bb (e->call_stmt)->count)))
{
- error ("indirect call frequency %i does not match BB frequency %i",
- e->frequency,
- compute_call_stmt_bb_frequency (e->caller->decl,
- gimple_bb (e->call_stmt)));
+ error ("indirect call count does not match BB count");
+ fprintf (stderr, "edge count: ");
+ e->count.dump (stderr);
+ fprintf (stderr, "\n bb count: ");
+ gimple_bb (e->call_stmt)->count.dump (stderr);
+ fprintf (stderr, "\n");
error_found = true;
}
}
}
else
ref_found = true;
- if (!ref_found)
- {
- error ("Analyzed alias has no reference");
- error_found = true;
- }
+ if (!ref_found)
+ {
+ error ("Analyzed alias has no reference");
+ error_found = true;
+ }
}
/* Check instrumented version reference. */
}
}
+ if (instrumentation_clone
+ && DECL_BUILT_IN_CLASS (decl) == NOT_BUILT_IN)
+ {
+ tree name = DECL_ASSEMBLER_NAME (decl);
+ tree orig_name = DECL_ASSEMBLER_NAME (orig_decl);
+
+ if (!IDENTIFIER_TRANSPARENT_ALIAS (name)
+ || TREE_CHAIN (name) != orig_name)
+ {
+ error ("Alias chain for instrumented node is broken");
+ error_found = true;
+ }
+ }
+
if (analyzed && thunk.thunk_p)
{
if (!callees)
error ("More than one edge out of thunk node");
error_found = true;
}
- if (gimple_has_body_p (decl))
+ if (gimple_has_body_p (decl) && !global.inlined_to)
{
error ("Thunk is not supposed to have body");
error_found = true;
{
if (this_cfun->cfg)
{
- hash_set<gimple> stmts;
+ hash_set<gimple *> stmts;
int i;
ipa_ref *ref = NULL;
!gsi_end_p (gsi);
gsi_next (&gsi))
{
- gimple stmt = gsi_stmt (gsi);
+ gimple *stmt = gsi_stmt (gsi);
stmts.add (stmt);
if (is_gimple_call (stmt))
{
/* Walk the alias chain to return the function cgraph_node is alias of.
Walk through thunks, too.
- When AVAILABILITY is non-NULL, get minimal availability in the chain. */
+ When AVAILABILITY is non-NULL, get minimal availability in the chain.
+ When REF is non-NULL, assume that reference happens in symbol REF
+ when determining the availability. */
cgraph_node *
-cgraph_node::function_symbol (enum availability *availability)
+cgraph_node::function_symbol (enum availability *availability,
+ struct symtab_node *ref)
{
- cgraph_node *node = ultimate_alias_target (availability);
+ cgraph_node *node = ultimate_alias_target (availability, ref);
while (node->thunk.thunk_p)
{
+ ref = node;
node = node->callees->callee;
if (availability)
{
enum availability a;
- a = node->get_availability ();
+ a = node->get_availability (ref);
if (a < *availability)
*availability = a;
}
- node = node->ultimate_alias_target (availability);
+ node = node->ultimate_alias_target (availability, ref);
}
return node;
}
/* Walk the alias chain to return the function cgraph_node is alias of.
Walk through non virtual thunks, too. Thus we return either a function
or a virtual thunk node.
- When AVAILABILITY is non-NULL, get minimal availability in the chain. */
+ When AVAILABILITY is non-NULL, get minimal availability in the chain.
+ When REF is non-NULL, assume that reference happens in symbol REF
+ when determining the availability. */
cgraph_node *
cgraph_node::function_or_virtual_thunk_symbol
- (enum availability *availability)
+ (enum availability *availability,
+ struct symtab_node *ref)
{
- cgraph_node *node = ultimate_alias_target (availability);
+ cgraph_node *node = ultimate_alias_target (availability, ref);
while (node->thunk.thunk_p && !node->thunk.virtual_offset_p)
{
+ ref = node;
node = node->callees->callee;
if (availability)
{
enum availability a;
- a = node->get_availability ();
+ a = node->get_availability (ref);
if (a < *availability)
*availability = a;
}
- node = node->ultimate_alias_target (availability);
+ node = node->ultimate_alias_target (availability, ref);
}
return node;
}
size_t len;
tree decl = this->decl;
- if (DECL_RESULT (decl))
+ /* Check if body is already there. Either we have gimple body or
+ the function is thunk and in that case we set DECL_ARGUMENTS. */
+ if (DECL_ARGUMENTS (decl) || gimple_has_body_p (decl))
return false;
- gcc_assert (in_lto_p);
+ gcc_assert (in_lto_p && !DECL_RESULT (decl));
timevar_push (TV_IPA_LTO_GIMPLE_IN);
/* We may have renamed the declaration, e.g., a static function. */
name = lto_get_decl_name_mapping (file_data, name);
+ struct lto_in_decl_state *decl_state
+ = lto_get_function_in_decl_state (file_data, decl);
data = lto_get_section_data (file_data, LTO_section_function_body,
- name, &len);
+ name, &len, decl_state->compressed);
if (!data)
fatal_error (input_location, "%s: section %s is missing",
file_data->file_name,
lto_input_function_body (file_data, this, data);
lto_stats.num_function_bodies++;
lto_free_section_data (file_data, LTO_section_function_body, name,
- data, len);
+ data, len, decl_state->compressed);
lto_free_function_in_decl_state_for_node (this);
/* Keep lto file data so ipa-inline-analysis knows about cross module
inlining. */
updated = get_untransformed_body ();
/* Getting transformed body makes no sense for inline clones;
- we should never use this on real clones becuase they are materialized
+ we should never use this on real clones because they are materialized
early.
TODO: Materializing clones here will likely lead to smaller LTRANS
footprint. */
{
opt_pass *saved_current_pass = current_pass;
FILE *saved_dump_file = dump_file;
- int saved_dump_flags = dump_flags;
+ const char *saved_dump_file_name = dump_file_name;
+ dump_flags_t saved_dump_flags = dump_flags;
+ dump_file_name = NULL;
+ dump_file = NULL;
push_cfun (DECL_STRUCT_FUNCTION (decl));
execute_all_ipa_transforms ();
current_pass = saved_current_pass;
dump_file = saved_dump_file;
+ dump_file_name = saved_dump_file_name;
dump_flags = saved_dump_flags;
}
return updated;
return false. */
static bool
-gimple_check_call_args (gimple stmt, tree fndecl, bool args_count_match)
+gimple_check_call_args (gimple *stmt, tree fndecl, bool args_count_match)
{
tree parms, p;
unsigned int i, nargs;
If we cannot verify this or there is a mismatch, return false. */
bool
-gimple_check_call_matching_types (gimple call_stmt, tree callee,
+gimple_check_call_matching_types (gimple *call_stmt, tree callee,
bool args_count_match)
{
tree lhs;
return false;
}
+/* Expected frequency of executions within the function. */
+
+sreal
+cgraph_edge::sreal_frequency ()
+{
+ return count.to_sreal_scale (caller->global.inlined_to
+ ? caller->global.inlined_to->count
+ : caller->count);
+}
+
#include "gt-cgraph.h"