aboutsummaryrefslogtreecommitdiff
path: root/gcc/cgraph.cc
diff options
context:
space:
mode:
Diffstat (limited to 'gcc/cgraph.cc')
-rw-r--r--gcc/cgraph.cc290
1 files changed, 279 insertions, 11 deletions
diff --git a/gcc/cgraph.cc b/gcc/cgraph.cc
index 07966a6..d1b2e2a 100644
--- a/gcc/cgraph.cc
+++ b/gcc/cgraph.cc
@@ -69,6 +69,7 @@ along with GCC; see the file COPYING3. If not see
#include "tree-nested.h"
#include "symtab-thunks.h"
#include "symtab-clones.h"
+#include "attr-callback.h"
/* FIXME: Only for PROP_loops, but cgraph shouldn't have to know about this. */
#include "tree-pass.h"
@@ -871,11 +872,22 @@ cgraph_add_edge_to_call_site_hash (cgraph_edge *e)
one indirect); always hash the direct one. */
if (e->speculative && e->indirect_unknown_callee)
return;
+ /* We always want to hash the carrying edge of a callback, not the edges
+ pointing to the callbacks themselves, as their call statement doesn't
+ exist. */
+ if (e->callback)
+ return;
cgraph_edge **slot = e->caller->call_site_hash->find_slot_with_hash
(e->call_stmt, cgraph_edge_hasher::hash (e->call_stmt), INSERT);
if (*slot)
{
- gcc_assert (((cgraph_edge *)*slot)->speculative);
+ cgraph_edge *edge = (cgraph_edge *) *slot;
+ gcc_assert (edge->speculative || edge->has_callback);
+ if (edge->has_callback)
+ /* If the slot is already occupied, then the hashed edge is the
+ callback-carrying edge, which is desired behavior, so we can safely
+ return. */
+ gcc_checking_assert (edge == e);
if (e->callee && (!e->prev_callee
|| !e->prev_callee->speculative
|| e->prev_callee->call_stmt != e->call_stmt))
@@ -919,6 +931,13 @@ cgraph_node::get_edge (gimple *call_stmt)
n++;
}
+ /* We want to work with the callback-carrying edge whenever possible. When it
+ comes to callback edges, a call statement might have multiple callback
+ edges attached to it. These can be easily obtained from the carrying edge
+ instead. */
+ if (e && e->callback)
+ e = e->get_callback_carrying_edge ();
+
if (n > 100)
{
call_site_hash = hash_table<cgraph_edge_hasher>::create_ggc (120);
@@ -931,15 +950,16 @@ cgraph_node::get_edge (gimple *call_stmt)
return e;
}
-
-/* Change field call_stmt of edge E to NEW_STMT. If UPDATE_SPECULATIVE and E
+/* Change field call_stmt of edge E to NEW_STMT. If UPDATE_DERIVED_EDGES and E
is any component of speculative edge, then update all components.
- Speculations can be resolved in the process and EDGE can be removed and
- deallocated. Return the edge that now represents the call. */
+ speculations can be resolved in the process and edge can be removed and
+ deallocated. if update_derived_edges and e is a part of a callback pair,
+ update all associated edges and return their carrying edge. return the edge
+ that now represents the call. */
cgraph_edge *
cgraph_edge::set_call_stmt (cgraph_edge *e, gcall *new_stmt,
- bool update_speculative)
+ bool update_derived_edges)
{
tree decl;
@@ -955,7 +975,7 @@ cgraph_edge::set_call_stmt (cgraph_edge *e, gcall *new_stmt,
/* Speculative edges has three component, update all of them
when asked to. */
- if (update_speculative && e->speculative
+ if (update_derived_edges && e->speculative
/* If we are about to resolve the speculation by calling make_direct
below, do not bother going over all the speculative edges now. */
&& !new_direct_callee)
@@ -991,6 +1011,27 @@ cgraph_edge::set_call_stmt (cgraph_edge *e, gcall *new_stmt,
if (new_direct_callee)
e = make_direct (e, new_direct_callee);
+ /* When updating a callback or a callback-carrying edge, update every edge
+ involved. */
+ if (update_derived_edges && (e->callback || e->has_callback))
+ {
+ cgraph_edge *current, *next, *carrying;
+ carrying = e->has_callback ? e : e->get_callback_carrying_edge ();
+
+ current = e->first_callback_edge ();
+ if (current)
+ {
+ for (cgraph_edge *d = current; d; d = next)
+ {
+ next = d->next_callback_edge ();
+ cgraph_edge *d2 = set_call_stmt (d, new_stmt, false);
+ gcc_assert (d2 == d);
+ }
+ }
+ carrying = set_call_stmt (carrying, new_stmt, false);
+ return carrying;
+ }
+
/* Only direct speculative edges go to call_site_hash. */
if (e->caller->call_site_hash
&& (!e->speculative || !e->indirect_unknown_callee)
@@ -1036,7 +1077,7 @@ symbol_table::create_edge (cgraph_node *caller, cgraph_node *callee,
construction of call stmt hashtable. */
cgraph_edge *e;
gcc_checking_assert (!(e = caller->get_edge (call_stmt))
- || e->speculative);
+ || e->speculative || e->has_callback || e->callback);
gcc_assert (is_gimple_call (call_stmt));
}
@@ -1063,6 +1104,9 @@ symbol_table::create_edge (cgraph_node *caller, cgraph_node *callee,
edge->indirect_info = NULL;
edge->indirect_inlining_edge = 0;
edge->speculative = false;
+ edge->has_callback = false;
+ edge->callback = false;
+ edge->callback_id = 0;
edge->indirect_unknown_callee = indir_unknown_callee;
if (call_stmt && caller->call_site_hash)
cgraph_add_edge_to_call_site_hash (edge);
@@ -1286,6 +1330,119 @@ cgraph_edge::make_speculative (cgraph_node *n2, profile_count direct_count,
return e2;
}
+/* Create a callback edge calling N2. Callback edges
+ never get turned into actual calls, they are just used
+ as clues and allow for optimizing functions which do not
+ have any callsites during compile time, e.g. functions
+ passed to standard library functions.
+
+ The edge will be attached to the same call statement as
+ the callback-carrying edge, which is the instance this method
+ is called on.
+
+ callback_id is used to pair the returned edge with the attribute that
+ originated it.
+
+ Return the resulting callback edge. */
+
+cgraph_edge *
+cgraph_edge::make_callback (cgraph_node *n2, unsigned int callback_id)
+{
+ cgraph_node *n = caller;
+ cgraph_edge *e2;
+
+ has_callback = true;
+ e2 = n->create_edge (n2, call_stmt, count);
+ if (dump_file)
+ fprintf (
+ dump_file,
+ "Created callback edge %s -> %s belonging to carrying edge %s -> %s\n",
+ e2->caller->dump_name (), e2->callee->dump_name (), caller->dump_name (),
+ callee->dump_name ());
+ e2->inline_failed = CIF_CALLBACK_EDGE;
+ e2->callback = true;
+ e2->callback_id = callback_id;
+ if (TREE_NOTHROW (n2->decl))
+ e2->can_throw_external = false;
+ else
+ e2->can_throw_external = can_throw_external;
+ e2->lto_stmt_uid = lto_stmt_uid;
+ n2->mark_address_taken ();
+ return e2;
+}
+
+/* Returns the callback_carrying edge of a callback edge on which
+ it is called on or NULL when no such edge can be found.
+
+ An edge is taken to be the callback-carrying if it has it's has_callback
+ flag set and the edges share their call statements. */
+
+cgraph_edge *
+cgraph_edge::get_callback_carrying_edge ()
+{
+ gcc_checking_assert (callback);
+ cgraph_edge *e;
+ for (e = caller->callees; e; e = e->next_callee)
+ {
+ if (e->has_callback && e->call_stmt == call_stmt
+ && e->lto_stmt_uid == lto_stmt_uid)
+ break;
+ }
+ return e;
+}
+
+/* Returns the first callback edge in the list of callees of the caller node.
+ Note that the edges might be in arbitrary order. Must be called on a
+ callback or callback-carrying edge. */
+
+cgraph_edge *
+cgraph_edge::first_callback_edge ()
+{
+ gcc_checking_assert (has_callback || callback);
+ cgraph_edge *e = NULL;
+ for (e = caller->callees; e; e = e->next_callee)
+ {
+ if (e->callback && e->call_stmt == call_stmt
+ && e->lto_stmt_uid == lto_stmt_uid)
+ break;
+ }
+ return e;
+}
+
+/* Given a callback edge, returns the next callback edge belonging to the same
+ carrying edge. Must be called on a callback edge, not the callback-carrying
+ edge. */
+
+cgraph_edge *
+cgraph_edge::next_callback_edge ()
+{
+ gcc_checking_assert (callback);
+ cgraph_edge *e = NULL;
+ for (e = next_callee; e; e = e->next_callee)
+ {
+ if (e->callback && e->call_stmt == call_stmt
+ && e->lto_stmt_uid == lto_stmt_uid)
+ break;
+ }
+ return e;
+}
+
+/* When called on a callback-carrying edge, removes all of its attached callback
+ edges and sets has_callback to FALSE. */
+
+void
+cgraph_edge::purge_callback_edges ()
+{
+ gcc_checking_assert (has_callback);
+ cgraph_edge *e, *next;
+ for (e = first_callback_edge (); e; e = next)
+ {
+ next = e->next_callback_edge ();
+ cgraph_edge::remove (e);
+ }
+ has_callback = false;
+}
+
/* Speculative call consists of an indirect edge and one or more
direct edge+ref pairs.
@@ -1521,12 +1678,27 @@ void
cgraph_edge::redirect_callee (cgraph_node *n)
{
bool loc = callee->comdat_local_p ();
+ cgraph_node *old_callee = callee;
+
/* Remove from callers list of the current callee. */
remove_callee ();
/* Insert to callers list of the new callee. */
set_callee (n);
+ if (callback)
+ {
+ /* When redirecting a callback callee, redirect its ref as well. */
+ ipa_ref *old_ref = caller->find_reference (old_callee, call_stmt,
+ lto_stmt_uid, IPA_REF_ADDR);
+ gcc_checking_assert(old_ref);
+ old_ref->remove_reference ();
+ ipa_ref *new_ref = caller->create_reference (n, IPA_REF_ADDR, call_stmt);
+ new_ref->lto_stmt_uid = lto_stmt_uid;
+ if (!old_callee->referred_to_p ())
+ old_callee->address_taken = 0;
+ }
+
if (!inline_failed)
return;
if (!loc && n->comdat_local_p ())
@@ -1643,6 +1815,27 @@ cgraph_edge::redirect_call_stmt_to_callee (cgraph_edge *e,
|| decl == e->callee->decl)
return e->call_stmt;
+ /* When redirecting a callback edge, all we need to do is replace
+ the original address with the address of the function we are
+ redirecting to. */
+ if (e->callback)
+ {
+ cgraph_edge *carrying = e->get_callback_carrying_edge ();
+ if (!callback_is_special_cased (carrying->callee->decl, e->call_stmt)
+ && !lookup_attribute (CALLBACK_ATTR_IDENT,
+ DECL_ATTRIBUTES (carrying->callee->decl)))
+ /* Callback attribute is removed if the dispatching function changes
+ signature, as the indices wouldn't be correct anymore. These edges
+ will get cleaned up later, ignore their redirection for now. */
+ return e->call_stmt;
+ int fn_idx = callback_fetch_fn_position (e, carrying);
+ tree previous_arg = gimple_call_arg (e->call_stmt, fn_idx);
+ location_t loc = EXPR_LOCATION (previous_arg);
+ tree new_addr = build_fold_addr_expr_loc (loc, e->callee->decl);
+ gimple_call_set_arg (e->call_stmt, fn_idx, new_addr);
+ return e->call_stmt;
+ }
+
if (decl && ipa_saved_clone_sources)
{
tree *p = ipa_saved_clone_sources->get (e->callee);
@@ -1752,7 +1945,9 @@ cgraph_edge::redirect_call_stmt_to_callee (cgraph_edge *e,
maybe_remove_unused_call_args (DECL_STRUCT_FUNCTION (e->caller->decl),
new_stmt);
- e->caller->set_call_stmt_including_clones (e->call_stmt, new_stmt, false);
+ /* Update callback edges if setting the carrying edge's statement, or else
+ their pairing would fall apart. */
+ e->caller->set_call_stmt_including_clones (e->call_stmt, new_stmt, e->has_callback);
if (symtab->dump_file)
{
@@ -1944,6 +2139,17 @@ cgraph_node::remove_callers (void)
for (e = callers; e; e = f)
{
f = e->next_caller;
+ /* When removing a callback-carrying edge, remove all its attached edges
+ as well. */
+ if (e->has_callback)
+ {
+ cgraph_edge *cbe, *next_cbe = NULL;
+ for (cbe = e->first_callback_edge (); cbe; cbe = next_cbe)
+ {
+ next_cbe = cbe->next_callback_edge ();
+ cgraph_edge::remove (cbe);
+ }
+ }
symtab->call_edge_removal_hooks (e);
e->remove_caller ();
symtab->free_edge (e);
@@ -2253,6 +2459,10 @@ cgraph_edge::dump_edge_flags (FILE *f)
{
if (speculative)
fprintf (f, "(speculative) ");
+ if (callback)
+ fprintf (f, "(callback) ");
+ if (has_callback)
+ fprintf (f, "(has_callback) ");
if (!inline_failed)
fprintf (f, "(inlined) ");
if (call_stmt_cannot_inline_p)
@@ -3866,6 +4076,8 @@ cgraph_node::verify_node (void)
if (gimple_has_body_p (e->caller->decl)
&& !e->caller->inlined_to
&& !e->speculative
+ && !e->callback
+ && !e->has_callback
/* Optimized out calls are redirected to __builtin_unreachable. */
&& (e->count.nonzero_p ()
|| ! e->callee->decl
@@ -4071,7 +4283,12 @@ cgraph_node::verify_node (void)
}
if (!e->indirect_unknown_callee)
{
- if (e->verify_corresponds_to_fndecl (decl))
+ /* Callback edges violate this assertion
+ because their call statement doesn't exist,
+ their associated statement belongs to the
+ callback-dispatching function. */
+ if (!e->callback
+ && e->verify_corresponds_to_fndecl (decl))
{
error ("edge points to wrong declaration:");
debug_tree (e->callee->decl);
@@ -4113,7 +4330,58 @@ cgraph_node::verify_node (void)
for (e = callees; e; e = e->next_callee)
{
- if (!e->aux && !e->speculative)
+ if (!e->callback && e->callback_id)
+ {
+ error ("non-callback edge has callback_id set");
+ error_found = true;
+ }
+
+ if (e->callback && e->has_callback)
+ {
+ error ("edge has both callback and has_callback set");
+ error_found = true;
+ }
+
+ if (e->callback)
+ {
+ if (!e->get_callback_carrying_edge ())
+ {
+ error ("callback edge %s->%s has no callback-carrying",
+ identifier_to_locale (e->caller->name ()),
+ identifier_to_locale (e->callee->name ()));
+ error_found = true;
+ }
+ }
+
+ if (e->has_callback
+ && !callback_is_special_cased (e->callee->decl, e->call_stmt))
+ {
+ int ncallbacks = 0;
+ int nfound_edges = 0;
+ for (tree cb = lookup_attribute (CALLBACK_ATTR_IDENT, DECL_ATTRIBUTES (
+ e->callee->decl));
+ cb; cb = lookup_attribute (CALLBACK_ATTR_IDENT, TREE_CHAIN (cb)),
+ ncallbacks++)
+ ;
+ for (cgraph_edge *cbe = callees; cbe; cbe = cbe->next_callee)
+ {
+ if (cbe->callback && cbe->call_stmt == e->call_stmt
+ && cbe->lto_stmt_uid == e->lto_stmt_uid)
+ {
+ nfound_edges++;
+ }
+ }
+ if (ncallbacks < nfound_edges)
+ {
+ error ("callback edge %s->%s callback edge count mismatch, "
+ "expected at most %d, found %d",
+ identifier_to_locale (e->caller->name ()),
+ identifier_to_locale (e->callee->name ()), ncallbacks,
+ nfound_edges);
+ }
+ }
+
+ if (!e->aux && !e->speculative && !e->callback && !e->has_callback)
{
error ("edge %s->%s has no corresponding call_stmt",
identifier_to_locale (e->caller->name ()),