/* Callgraph handling code.
- Copyright (C) 2003 Free Software Foundation, Inc.
+ Copyright (C) 2003, 2004 Free Software Foundation, Inc.
Contributed by Jan Hubicka
This file is part of GCC.
#include "coretypes.h"
#include "tm.h"
#include "tree.h"
-#include "tree-inline.h"
#include "langhooks.h"
#include "hashtab.h"
#include "toplev.h"
#include "debug.h"
#include "target.h"
#include "cgraph.h"
+#include "varray.h"
+#include "output.h"
+#include "intl.h"
+
/* Hash table used to convert declarations into nodes. */
-static htab_t cgraph_hash = 0;
+static GTY((param_is (struct cgraph_node))) htab_t cgraph_hash;
/* The linked list of cgraph nodes. */
struct cgraph_node *cgraph_nodes;
+/* Queue of cgraph nodes scheduled to be lowered. */
+struct cgraph_node *cgraph_nodes_queue;
+
/* Number of nodes in existence. */
int cgraph_n_nodes;
+/* Maximal uid used in cgraph nodes. */
+int cgraph_max_uid;
+
/* Set when whole unit has been analyzed so we can access global info. */
bool cgraph_global_info_ready = false;
-static struct cgraph_edge *create_edge PARAMS ((struct cgraph_node *,
- struct cgraph_node *));
-static void remove_edge PARAMS ((struct cgraph_node *, struct cgraph_node *));
-static hashval_t hash_node PARAMS ((const PTR));
-static int eq_node PARAMS ((const PTR, const PTR));
+/* Hash table used to convert declarations into nodes. */
+static GTY((param_is (struct cgraph_varpool_node))) htab_t cgraph_varpool_hash;
+
+/* Queue of cgraph nodes scheduled to be lowered and output. */
+struct cgraph_varpool_node *cgraph_varpool_nodes_queue;
+
+/* Number of nodes in existence. */
+int cgraph_varpool_n_nodes;
+
+/* The linked list of cgraph varpool nodes. */
+static GTY(()) struct cgraph_varpool_node *cgraph_varpool_nodes;
+
+static struct cgraph_edge *create_edge (struct cgraph_node *,
+ struct cgraph_node *);
+static hashval_t hash_node (const void *);
+static int eq_node (const void *, const void *);
/* Returns a hash code for P. */
static hashval_t
-hash_node (p)
- const PTR p;
+hash_node (const void *p)
{
- return (hashval_t)
- htab_hash_pointer (DECL_ASSEMBLER_NAME
- (((struct cgraph_node *) p)->decl));
+ return ((hashval_t)
+ IDENTIFIER_HASH_VALUE (DECL_ASSEMBLER_NAME
+ (((struct cgraph_node *) p)->decl)));
}
-/* Returns non-zero if P1 and P2 are equal. */
+/* Returns nonzero if P1 and P2 are equal. */
static int
-eq_node (p1, p2)
- const PTR p1;
- const PTR p2;
+eq_node (const void *p1, const void *p2)
{
return ((DECL_ASSEMBLER_NAME (((struct cgraph_node *) p1)->decl)) ==
- DECL_ASSEMBLER_NAME ((tree) p2));
+ (tree) p2);
}
/* Return cgraph node assigned to DECL. Create new one when needed. */
struct cgraph_node *
-cgraph_node (decl)
- tree decl;
+cgraph_node (tree decl)
{
struct cgraph_node *node;
struct cgraph_node **slot;
+ if (TREE_CODE (decl) != FUNCTION_DECL)
+ abort ();
+
if (!cgraph_hash)
- cgraph_hash = htab_create (10, hash_node, eq_node, NULL);
+ cgraph_hash = htab_create_ggc (10, hash_node, eq_node, NULL);
- slot =
- (struct cgraph_node **) htab_find_slot_with_hash (cgraph_hash, decl,
- htab_hash_pointer
- (DECL_ASSEMBLER_NAME
- (decl)), 1);
+ slot = (struct cgraph_node **)
+ htab_find_slot_with_hash (cgraph_hash, DECL_ASSEMBLER_NAME (decl),
+ IDENTIFIER_HASH_VALUE
+ (DECL_ASSEMBLER_NAME (decl)), INSERT);
if (*slot)
return *slot;
- node = xcalloc (sizeof (*node), 1);
+ node = ggc_alloc_cleared (sizeof (*node));
node->decl = decl;
node->next = cgraph_nodes;
+ node->uid = cgraph_max_uid++;
+ if (cgraph_nodes)
+ cgraph_nodes->previous = node;
+ node->previous = NULL;
cgraph_nodes = node;
cgraph_n_nodes++;
*slot = node;
- if (DECL_CONTEXT (decl))
+ if (DECL_CONTEXT (decl) && TREE_CODE (DECL_CONTEXT (decl)) == FUNCTION_DECL)
{
node->origin = cgraph_node (DECL_CONTEXT (decl));
node->next_nested = node->origin->nested;
return node;
}
+/* Try to find existing function for identifier ID. */
+struct cgraph_node *
+cgraph_node_for_identifier (tree id)
+{
+ struct cgraph_node **slot;
+
+ if (TREE_CODE (id) != IDENTIFIER_NODE)
+ abort ();
+
+ if (!cgraph_hash)
+ return NULL;
+
+ slot = (struct cgraph_node **)
+ htab_find_slot_with_hash (cgraph_hash, id,
+ IDENTIFIER_HASH_VALUE (id), NO_INSERT);
+ if (!slot)
+ return NULL;
+ return *slot;
+}
+
/* Create edge from CALLER to CALLEE in the cgraph. */
static struct cgraph_edge *
-create_edge (caller, callee)
- struct cgraph_node *caller, *callee;
+create_edge (struct cgraph_node *caller, struct cgraph_node *callee)
{
- struct cgraph_edge *edge = xmalloc (sizeof (struct cgraph_edge));
+ struct cgraph_edge *edge = ggc_alloc (sizeof (struct cgraph_edge));
+ struct cgraph_edge *edge2;
+
+ if (!DECL_SAVED_TREE (callee->decl))
+ edge->inline_failed = N_("function body not available");
+ else if (callee->local.redefined_extern_inline)
+ edge->inline_failed = N_("redefined extern inline functions are not "
+ "considered for inlining");
+ else if (callee->local.inlinable)
+ edge->inline_failed = N_("function not considered for inlining");
+ else
+ edge->inline_failed = N_("function not inlinable");
+
+ /* At the moment we don't associate calls with specific CALL_EXPRs
+ as we probably ought to, so we must preserve inline_call flags to
+ be the same in all copies of the same edge. */
+ if (cgraph_global_info_ready)
+ for (edge2 = caller->callees; edge2; edge2 = edge2->next_callee)
+ if (edge2->callee == callee)
+ {
+ edge->inline_failed = edge2->inline_failed;
+ break;
+ }
edge->caller = caller;
edge->callee = callee;
/* Remove the edge from CALLER to CALLEE in the cgraph. */
-static void
-remove_edge (caller, callee)
- struct cgraph_node *caller, *callee;
+void
+cgraph_remove_edge (struct cgraph_node *caller, struct cgraph_node *callee)
{
struct cgraph_edge **edge, **edge2;
*edge2 = (*edge2)->next_callee;
}
-/* Record call from CALLER to CALLEE */
+/* Remove the node from cgraph. */
+
+void
+cgraph_remove_node (struct cgraph_node *node)
+{
+ void **slot;
+ while (node->callers)
+ cgraph_remove_edge (node->callers->caller, node);
+ while (node->callees)
+ cgraph_remove_edge (node, node->callees->callee);
+ while (node->nested)
+ cgraph_remove_node (node->nested);
+ if (node->origin)
+ {
+ struct cgraph_node **node2 = &node->origin->nested;
+
+ while (*node2 != node)
+ node2 = &(*node2)->next_nested;
+ *node2 = node->next_nested;
+ }
+ if (node->previous)
+ node->previous->next = node->next;
+ else
+ cgraph_nodes = node->next;
+ if (node->next)
+ node->next->previous = node->previous;
+ DECL_SAVED_TREE (node->decl) = NULL;
+ DECL_SAVED_INSNS (node->decl) = NULL;
+ DECL_ARGUMENTS (node->decl) = NULL;
+ DECL_INITIAL (node->decl) = error_mark_node;
+ slot =
+ htab_find_slot_with_hash (cgraph_hash, DECL_ASSEMBLER_NAME (node->decl),
+ IDENTIFIER_HASH_VALUE (DECL_ASSEMBLER_NAME
+ (node->decl)), NO_INSERT);
+ htab_clear_slot (cgraph_hash, slot);
+ /* Do not free the structure itself so the walk over chain can continue. */
+}
+
+/* Notify finalize_compilation_unit that given node is reachable. */
+
+void
+cgraph_mark_reachable_node (struct cgraph_node *node)
+{
+ if (!node->reachable && node->local.finalized)
+ {
+ notice_global_symbol (node->decl);
+ node->reachable = 1;
+
+ node->next_needed = cgraph_nodes_queue;
+ cgraph_nodes_queue = node;
+
+ /* At the moment frontend automatically emits all nested functions. */
+ if (node->nested)
+ {
+ struct cgraph_node *node2;
+
+ for (node2 = node->nested; node2; node2 = node2->next_nested)
+ if (!node2->reachable)
+ cgraph_mark_reachable_node (node2);
+ }
+ }
+}
+
+/* Likewise indicate that a node is needed, i.e. reachable via some
+ external means. */
+
+void
+cgraph_mark_needed_node (struct cgraph_node *node)
+{
+ node->needed = 1;
+ cgraph_mark_reachable_node (node);
+}
+
+/* Record call from CALLER to CALLEE. */
struct cgraph_edge *
-cgraph_record_call (caller, callee)
- tree caller, callee;
+cgraph_record_call (tree caller, tree callee)
{
return create_edge (cgraph_node (caller), cgraph_node (callee));
}
void
-cgraph_remove_call (caller, callee)
- tree caller, callee;
+cgraph_remove_call (tree caller, tree callee)
{
- remove_edge (cgraph_node (caller), cgraph_node (callee));
+ cgraph_remove_edge (cgraph_node (caller), cgraph_node (callee));
}
/* Return true when CALLER_DECL calls CALLEE_DECL. */
bool
-cgraph_calls_p (caller_decl, callee_decl)
- tree caller_decl, callee_decl;
+cgraph_calls_p (tree caller_decl, tree callee_decl)
{
struct cgraph_node *caller = cgraph_node (caller_decl);
struct cgraph_node *callee = cgraph_node (callee_decl);
/* Return local info for the compiled function. */
struct cgraph_local_info *
-cgraph_local_info (decl)
- tree decl;
+cgraph_local_info (tree decl)
{
struct cgraph_node *node;
if (TREE_CODE (decl) != FUNCTION_DECL)
/* Return local info for the compiled function. */
struct cgraph_global_info *
-cgraph_global_info (decl)
- tree decl;
+cgraph_global_info (tree decl)
{
struct cgraph_node *node;
if (TREE_CODE (decl) != FUNCTION_DECL || !cgraph_global_info_ready)
/* Return local info for the compiled function. */
struct cgraph_rtl_info *
-cgraph_rtl_info (decl)
- tree decl;
+cgraph_rtl_info (tree decl)
{
struct cgraph_node *node;
if (TREE_CODE (decl) != FUNCTION_DECL)
return &node->rtl;
}
+/* Return name of the node used in debug output. */
+const char *
+cgraph_node_name (struct cgraph_node *node)
+{
+ return (*lang_hooks.decl_printable_name) (node->decl, 2);
+}
/* Dump the callgraph. */
void
-dump_cgraph (f)
- FILE *f;
+dump_cgraph (FILE *f)
{
struct cgraph_node *node;
- fprintf (f, "\nCallgraph:\n\n");
+ fprintf (f, "callgraph:\n\n");
for (node = cgraph_nodes; node; node = node->next)
{
struct cgraph_edge *edge;
- fprintf (f, "%s", IDENTIFIER_POINTER (DECL_NAME (node->decl)));
+ fprintf (f, "%s:", cgraph_node_name (node));
+ if (node->local.self_insns)
+ fprintf (f, " %i insns", node->local.self_insns);
+ if (node->global.insns && node->global.insns != node->local.self_insns)
+ fprintf (f, " (%i after inlining)", node->global.insns);
if (node->origin)
- fprintf (f, " nested in: %s",
- IDENTIFIER_POINTER (DECL_NAME (node->origin->decl)));
+ fprintf (f, " nested in: %s", cgraph_node_name (node->origin));
if (node->needed)
fprintf (f, " needed");
else if (node->reachable)
if (DECL_SAVED_TREE (node->decl))
fprintf (f, " tree");
- fprintf (f, "\n called by :");
+ if (node->local.local)
+ fprintf (f, " local");
+ if (node->local.disregard_inline_limits)
+ fprintf (f, " always_inline");
+ else if (node->local.inlinable)
+ fprintf (f, " inlinable");
+ if (node->global.cloned_times > 1)
+ fprintf (f, " cloned %ix", node->global.cloned_times);
+
+ fprintf (f, "\n called by: ");
for (edge = node->callers; edge; edge = edge->next_caller)
- fprintf (f, "%s ",
- IDENTIFIER_POINTER (DECL_NAME (edge->caller->decl)));
+ {
+ fprintf (f, "%s ", cgraph_node_name (edge->caller));
+ if (!edge->inline_failed)
+ fprintf(f, "(inlined) ");
+ }
fprintf (f, "\n calls: ");
for (edge = node->callees; edge; edge = edge->next_callee)
- fprintf (f, "%s ",
- IDENTIFIER_POINTER (DECL_NAME (edge->callee->decl)));
+ {
+ fprintf (f, "%s ", cgraph_node_name (edge->callee));
+ if (!edge->inline_failed)
+ fprintf(f, "(inlined) ");
+ }
fprintf (f, "\n");
}
}
+
+/* Returns a hash code for P. */
+
+static hashval_t
+cgraph_varpool_hash_node (const void *p)
+{
+ return ((hashval_t)
+ IDENTIFIER_HASH_VALUE (DECL_ASSEMBLER_NAME
+ (((struct cgraph_varpool_node *) p)->decl)));
+}
+
+/* Returns nonzero if P1 and P2 are equal. */
+
+static int
+eq_cgraph_varpool_node (const void *p1, const void *p2)
+{
+ return ((DECL_ASSEMBLER_NAME (((struct cgraph_varpool_node *) p1)->decl)) ==
+ (tree) p2);
+}
+
+/* Return cgraph_varpool node assigned to DECL. Create new one when needed. */
+struct cgraph_varpool_node *
+cgraph_varpool_node (tree decl)
+{
+ struct cgraph_varpool_node *node;
+ struct cgraph_varpool_node **slot;
+
+ if (!DECL_P (decl) || TREE_CODE (decl) == FUNCTION_DECL)
+ abort ();
+
+ if (!cgraph_varpool_hash)
+ cgraph_varpool_hash = htab_create_ggc (10, cgraph_varpool_hash_node,
+ eq_cgraph_varpool_node, NULL);
+ slot = (struct cgraph_varpool_node **)
+ htab_find_slot_with_hash (cgraph_varpool_hash, DECL_ASSEMBLER_NAME (decl),
+ IDENTIFIER_HASH_VALUE (DECL_ASSEMBLER_NAME (decl)),
+ INSERT);
+ if (*slot)
+ return *slot;
+ node = ggc_alloc_cleared (sizeof (*node));
+ node->decl = decl;
+ cgraph_varpool_n_nodes++;
+ cgraph_varpool_nodes = node;
+ *slot = node;
+ return node;
+}
+
+/* Set the DECL_ASSEMBLER_NAME and update cgraph hashtables. */
+void
+change_decl_assembler_name (tree decl, tree name)
+{
+ struct cgraph_node *node = NULL;
+ struct cgraph_varpool_node *vnode = NULL;
+ void **slot;
+
+ if (!DECL_ASSEMBLER_NAME_SET_P (decl))
+ {
+ SET_DECL_ASSEMBLER_NAME (decl, name);
+ return;
+ }
+ if (name == DECL_ASSEMBLER_NAME (decl))
+ return;
+
+ if (TREE_SYMBOL_REFERENCED (DECL_ASSEMBLER_NAME (decl))
+ && DECL_RTL_SET_P (decl))
+ warning ("%D renamed after being referenced in assembly", decl);
+
+ if (TREE_CODE (decl) == FUNCTION_DECL && cgraph_hash)
+ {
+ /* Take a look whether declaration is in the cgraph structure. */
+ slot =
+ htab_find_slot_with_hash (cgraph_hash, DECL_ASSEMBLER_NAME (decl),
+ IDENTIFIER_HASH_VALUE (DECL_ASSEMBLER_NAME
+ (decl)), NO_INSERT);
+ if (slot)
+ node = *slot;
+
+ /* It is, verify that we are the canonical node for this decl. */
+ if (node && node->decl == decl)
+ {
+ node = *slot;
+ htab_clear_slot (cgraph_hash, slot);
+ }
+ else
+ node = NULL;
+ }
+ if (TREE_CODE (decl) == VAR_DECL && TREE_STATIC (decl) && cgraph_varpool_hash)
+ {
+ /* Take a look whether declaration is in the cgraph structure. */
+ slot =
+ htab_find_slot_with_hash (cgraph_varpool_hash, DECL_ASSEMBLER_NAME (decl),
+ IDENTIFIER_HASH_VALUE (DECL_ASSEMBLER_NAME
+ (decl)), NO_INSERT);
+ if (slot)
+ vnode = *slot;
+
+ /* It is, verify that we are the canonical vnode for this decl. */
+ if (vnode && vnode->decl == decl)
+ {
+ vnode = *slot;
+ htab_clear_slot (cgraph_varpool_hash, slot);
+ }
+ else
+ vnode = NULL;
+ }
+ SET_DECL_ASSEMBLER_NAME (decl, name);
+ if (node)
+ {
+ slot =
+ htab_find_slot_with_hash (cgraph_hash, name,
+ IDENTIFIER_HASH_VALUE (name), INSERT);
+ if (*slot)
+ abort ();
+ *slot = node;
+ }
+ if (vnode)
+ {
+ slot =
+ htab_find_slot_with_hash (cgraph_varpool_hash, name,
+ IDENTIFIER_HASH_VALUE (name), INSERT);
+ if (*slot)
+ abort ();
+ *slot = vnode;
+ }
+}
+
+/* Try to find existing function for identifier ID. */
+struct cgraph_varpool_node *
+cgraph_varpool_node_for_identifier (tree id)
+{
+ struct cgraph_varpool_node **slot;
+
+ if (TREE_CODE (id) != IDENTIFIER_NODE)
+ abort ();
+
+ if (!cgraph_varpool_hash)
+ return NULL;
+
+ slot = (struct cgraph_varpool_node **)
+ htab_find_slot_with_hash (cgraph_varpool_hash, id,
+ IDENTIFIER_HASH_VALUE (id), NO_INSERT);
+ if (!slot)
+ return NULL;
+ return *slot;
+}
+
+/* Notify finalize_compilation_unit that given node is reachable
+ or needed. */
+void
+cgraph_varpool_mark_needed_node (struct cgraph_varpool_node *node)
+{
+ if (!node->needed && node->finalized)
+ {
+ node->next_needed = cgraph_varpool_nodes_queue;
+ cgraph_varpool_nodes_queue = node;
+ notice_global_symbol (node->decl);
+ }
+ node->needed = 1;
+}
+
+void
+cgraph_varpool_finalize_decl (tree decl)
+{
+ struct cgraph_varpool_node *node = cgraph_varpool_node (decl);
+
+ /* The first declaration of a variable that comes through this function
+ decides whether it is global (in C, has external linkage)
+ or local (in C, has internal linkage). So do nothing more
+ if this function has already run. */
+ if (node->finalized)
+ return;
+ if (node->needed)
+ {
+ node->next_needed = cgraph_varpool_nodes_queue;
+ cgraph_varpool_nodes_queue = node;
+ notice_global_symbol (decl);
+ }
+ node->finalized = true;
+
+ if (/* Externally visible variables must be output. The exception are
+ COMDAT functions that must be output only when they are needed. */
+ (TREE_PUBLIC (decl) && !DECL_COMDAT (decl))
+ /* Function whose name is output to the assembler file must be produced.
+ It is possible to assemble the name later after finalizing the function
+ and the fact is noticed in assemble_name then. */
+ || (DECL_ASSEMBLER_NAME_SET_P (decl)
+ && TREE_SYMBOL_REFERENCED (DECL_ASSEMBLER_NAME (decl))))
+ {
+ cgraph_varpool_mark_needed_node (node);
+ }
+}
+
+bool
+cgraph_varpool_assemble_pending_decls (void)
+{
+ bool changed = false;
+
+ while (cgraph_varpool_nodes_queue)
+ {
+ tree decl = cgraph_varpool_nodes_queue->decl;
+ struct cgraph_varpool_node *node = cgraph_varpool_nodes_queue;
+
+ cgraph_varpool_nodes_queue = cgraph_varpool_nodes_queue->next_needed;
+ if (!TREE_ASM_WRITTEN (decl))
+ {
+ assemble_variable (decl, 0, 1, 0);
+ changed = true;
+ }
+ node->next_needed = NULL;
+ }
+ return changed;
+}
+
+/* Return true when the DECL can possibly be inlined. */
+bool
+cgraph_function_possibly_inlined_p (tree decl)
+{
+ if (!cgraph_global_info_ready)
+ return (DECL_INLINE (decl)
+ && (!flag_really_no_inline
+ || (*lang_hooks.tree_inlining.disregard_inline_limits) (decl)));
+ return cgraph_node (decl)->global.inlined;
+}
+
+#include "gt-cgraph.h"