diff options
Diffstat (limited to 'contrib/gcc/cgraph.c')
-rw-r--r-- | contrib/gcc/cgraph.c | 644 |
1 files changed, 644 insertions, 0 deletions
diff --git a/contrib/gcc/cgraph.c b/contrib/gcc/cgraph.c new file mode 100644 index 0000000..775f0a3 --- /dev/null +++ b/contrib/gcc/cgraph.c @@ -0,0 +1,644 @@ +/* Callgraph handling code. + Copyright (C) 2003, 2004 Free Software Foundation, Inc. + Contributed by Jan Hubicka + +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 2, 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 COPYING. If not, write to the Free +Software Foundation, 59 Temple Place - Suite 330, Boston, MA +02111-1307, USA. */ + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "tm.h" +#include "tree.h" +#include "langhooks.h" +#include "hashtab.h" +#include "toplev.h" +#include "flags.h" +#include "ggc.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 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; + +/* 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 (const void *p) +{ + return ((hashval_t) + IDENTIFIER_HASH_VALUE (DECL_ASSEMBLER_NAME + (((struct cgraph_node *) p)->decl))); +} + +/* Returns nonzero if P1 and P2 are equal. */ + +static int +eq_node (const void *p1, const void *p2) +{ + return ((DECL_ASSEMBLER_NAME (((struct cgraph_node *) p1)->decl)) == + (tree) p2); +} + +/* Return cgraph node assigned to DECL. Create new one when needed. */ +struct cgraph_node * +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_ggc (10, hash_node, eq_node, NULL); + + 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 = 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) && TREE_CODE (DECL_CONTEXT (decl)) == FUNCTION_DECL) + { + node->origin = cgraph_node (DECL_CONTEXT (decl)); + node->next_nested = node->origin->nested; + node->origin->nested = node; + } + 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 (struct cgraph_node *caller, struct cgraph_node *callee) +{ + 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; + edge->next_caller = callee->callers; + edge->next_callee = caller->callees; + caller->callees = edge; + callee->callers = edge; + return edge; +} + +/* Remove the edge from CALLER to CALLEE in the cgraph. */ + +void +cgraph_remove_edge (struct cgraph_node *caller, struct cgraph_node *callee) +{ + struct cgraph_edge **edge, **edge2; + + for (edge = &callee->callers; *edge && (*edge)->caller != caller; + edge = &((*edge)->next_caller)) + continue; + if (!*edge) + abort (); + *edge = (*edge)->next_caller; + for (edge2 = &caller->callees; *edge2 && (*edge2)->callee != callee; + edge2 = &(*edge2)->next_callee) + continue; + if (!*edge2) + abort (); + *edge2 = (*edge2)->next_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); + if (slot == 0) + { + /* We use DECL_ASSEMBLER_NAME as key, which may not work in + all cases. See PR/15666. Gcc 3.5 uses DECL_UID as key, + which doesn't have this problem. */ + if (!DECL_BUILT_IN (node->decl)) + abort (); + } + else + 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 (tree caller, tree callee) +{ + return create_edge (cgraph_node (caller), cgraph_node (callee)); +} + +void +cgraph_remove_call (tree caller, tree callee) +{ + cgraph_remove_edge (cgraph_node (caller), cgraph_node (callee)); +} + +/* Return true when CALLER_DECL calls CALLEE_DECL. */ + +bool +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); + struct cgraph_edge *edge; + + for (edge = callee->callers; edge && (edge)->caller != caller; + edge = (edge->next_caller)) + continue; + return edge != NULL; +} + +/* Return local info for the compiled function. */ + +struct cgraph_local_info * +cgraph_local_info (tree decl) +{ + struct cgraph_node *node; + if (TREE_CODE (decl) != FUNCTION_DECL) + abort (); + node = cgraph_node (decl); + return &node->local; +} + +/* Return local info for the compiled function. */ + +struct cgraph_global_info * +cgraph_global_info (tree decl) +{ + struct cgraph_node *node; + if (TREE_CODE (decl) != FUNCTION_DECL || !cgraph_global_info_ready) + abort (); + node = cgraph_node (decl); + return &node->global; +} + +/* Return local info for the compiled function. */ + +struct cgraph_rtl_info * +cgraph_rtl_info (tree decl) +{ + struct cgraph_node *node; + if (TREE_CODE (decl) != FUNCTION_DECL) + abort (); + node = cgraph_node (decl); + if (decl != current_function_decl + && !TREE_ASM_WRITTEN (node->decl)) + return NULL; + 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 (FILE *f) +{ + struct cgraph_node *node; + + fprintf (f, "callgraph:\n\n"); + for (node = cgraph_nodes; node; node = node->next) + { + struct cgraph_edge *edge; + 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", cgraph_node_name (node->origin)); + if (node->needed) + fprintf (f, " needed"); + else if (node->reachable) + fprintf (f, " reachable"); + if (DECL_SAVED_TREE (node->decl)) + fprintf (f, " tree"); + + 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 ", 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 ", 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" |