#include "config.h"
#include "system.h"
#include "coretypes.h"
#include "tm.h"
#include "tree.h"
#include "rtl.h"
#include "tree-flow.h"
#include "tree-inline.h"
#include "langhooks.h"
#include "pointer-set.h"
#include "toplev.h"
#include "flags.h"
#include "ggc.h"
#include "debug.h"
#include "target.h"
#include "cgraph.h"
#include "diagnostic.h"
#include "timevar.h"
#include "params.h"
#include "fibheap.h"
#include "c-common.h"
#include "intl.h"
#include "function.h"
#include "ipa-prop.h"
#include "tree-gimple.h"
#include "tree-pass.h"
#include "output.h"
#ifdef ENABLE_LLVM
#include "llvm.h"
#endif
static void cgraph_expand_all_functions (void);
static void cgraph_mark_functions_to_output (void);
static void cgraph_expand_function (struct cgraph_node *);
static tree record_reference (tree *, int *, void *);
static void cgraph_output_pending_asms (void);
static void cgraph_increase_alignment (void);
static GTY(()) struct cgraph_varpool_node *cgraph_varpool_assembled_nodes_queue;
static struct pointer_set_t *visited_nodes;
static FILE *cgraph_dump_file;
static bool
decide_is_function_needed (struct cgraph_node *node, tree decl)
{
tree origin;
if (MAIN_NAME_P (DECL_NAME (decl))
&& TREE_PUBLIC (decl))
{
node->local.externally_visible = true;
return true;
}
if (node->local.externally_visible)
return true;
if (!flag_unit_at_a_time && lookup_attribute ("used", DECL_ATTRIBUTES (decl)))
return true;
if (DECL_ASSEMBLER_NAME_SET_P (decl)
&& TREE_SYMBOL_REFERENCED (DECL_ASSEMBLER_NAME (decl)))
return true;
if (node->needed)
return true;
if (((TREE_PUBLIC (decl)
#ifndef ENABLE_LLVM
|| (!optimize && !node->local.disregard_inline_limits
&& !DECL_DECLARED_INLINE_P (decl)
&& !node->origin)
#endif
)
&& !flag_whole_program)
&& !DECL_COMDAT (decl) && !DECL_EXTERNAL (decl))
return true;
if (DECL_STATIC_CONSTRUCTOR (decl) || DECL_STATIC_DESTRUCTOR (decl))
return true;
if (flag_unit_at_a_time)
return false;
if (DECL_EXTERNAL (decl))
return false;
for (origin = decl_function_context (decl); origin;
origin = decl_function_context (origin))
if (DECL_EXTERNAL (origin))
return false;
if (DECL_COMDAT (decl))
return false;
if (!DECL_INLINE (decl)
|| (!node->local.disregard_inline_limits
&& !DECL_DECLARED_INLINE_P (decl)
&& (!node->local.inlinable || !cgraph_default_inline_p (node, NULL))))
return true;
return false;
}
static bool
cgraph_varpool_analyze_pending_decls (void)
{
bool changed = false;
timevar_push (TV_CGRAPH);
while (cgraph_varpool_first_unanalyzed_node)
{
tree decl = cgraph_varpool_first_unanalyzed_node->decl;
cgraph_varpool_first_unanalyzed_node->analyzed = true;
cgraph_varpool_first_unanalyzed_node = cgraph_varpool_first_unanalyzed_node->next_needed;
align_variable (decl, 0);
if (DECL_INITIAL (decl))
{
visited_nodes = pointer_set_create ();
walk_tree (&DECL_INITIAL (decl), record_reference, NULL, visited_nodes);
pointer_set_destroy (visited_nodes);
visited_nodes = NULL;
}
changed = true;
}
timevar_pop (TV_CGRAPH);
return changed;
}
static void
cgraph_varpool_remove_unreferenced_decls (void)
{
struct cgraph_varpool_node *next, *node = cgraph_varpool_nodes_queue;
cgraph_varpool_reset_queue ();
if (errorcount || sorrycount)
return;
while (node)
{
tree decl = node->decl;
next = node->next_needed;
node->needed = 0;
if (node->finalized
&& ((DECL_ASSEMBLER_NAME_SET_P (decl)
&& TREE_SYMBOL_REFERENCED (DECL_ASSEMBLER_NAME (decl)))
|| node->force_output
|| decide_is_variable_needed (node, decl)
|| DECL_RTL_SET_P (decl)))
cgraph_varpool_mark_needed_node (node);
node = next;
}
finish_aliases_1 ();
cgraph_varpool_analyze_pending_decls ();
}
bool
cgraph_assemble_pending_functions (void)
{
bool output = false;
if (flag_unit_at_a_time)
return false;
cgraph_output_pending_asms ();
while (cgraph_nodes_queue)
{
struct cgraph_node *n = cgraph_nodes_queue;
cgraph_nodes_queue = cgraph_nodes_queue->next_needed;
n->next_needed = NULL;
if (!n->global.inlined_to
&& !n->alias
&& !IS_EXTERN_NOINLINE (n->decl))
{
cgraph_expand_function (n);
output = true;
}
}
while (cgraph_expand_queue)
{
struct cgraph_node *n = cgraph_expand_queue;
cgraph_expand_queue = cgraph_expand_queue->next_needed;
n->next_needed = NULL;
cgraph_finalize_function (n->decl, false);
output = true;
}
return output;
}
static void
cgraph_reset_node (struct cgraph_node *node)
{
gcc_assert (!node->output);
memset (&node->local, 0, sizeof (node->local));
memset (&node->global, 0, sizeof (node->global));
memset (&node->rtl, 0, sizeof (node->rtl));
node->analyzed = false;
node->local.redefined_extern_inline = true;
node->local.finalized = false;
if (!flag_unit_at_a_time)
{
struct cgraph_node *n, *next;
for (n = cgraph_nodes; n; n = next)
{
next = n->next;
if (n->global.inlined_to == node)
cgraph_remove_node (n);
}
}
cgraph_node_remove_callees (node);
if (node->reachable && !flag_unit_at_a_time)
{
struct cgraph_node *n;
for (n = cgraph_nodes_queue; n; n = n->next_needed)
if (n == node)
break;
if (!n)
node->reachable = 0;
}
}
static void
cgraph_lower_function (struct cgraph_node *node)
{
if (node->lowered)
return;
tree_lowering_passes (node->decl);
node->lowered = true;
}
void
lower_if_nested_functions (tree decl)
{
lower_nested_functions (decl, true);
}
void
cgraph_finalize_function (tree decl, bool nested)
{
struct cgraph_node *node = cgraph_node (decl);
#if TARGET_MACHO && defined TARGET_SSE2
ix86_darwin_handle_regparmandstackparm (decl);
#endif
if (node->local.finalized)
cgraph_reset_node (node);
notice_global_symbol (decl);
node->decl = decl;
node->local.finalized = true;
node->lowered = DECL_STRUCT_FUNCTION (decl)->cfg != NULL;
if (node->nested)
lower_nested_functions (decl, false);
gcc_assert (!node->nested);
if (!flag_unit_at_a_time)
{
cgraph_analyze_function (node);
cgraph_decide_inlining_incrementally (node, false);
}
if (decide_is_function_needed (node, decl))
cgraph_mark_needed_node (node);
if ((TREE_PUBLIC (decl) && !DECL_COMDAT (decl) && !DECL_EXTERNAL (decl)))
cgraph_mark_reachable_node (node);
if (!nested)
{
if (!cgraph_assemble_pending_functions ())
ggc_collect ();
}
if (!TREE_ASM_WRITTEN (decl))
(*debug_hooks->deferred_inline_function) (decl);
if (warn_unused_parameter)
do_warn_unused_parameter (decl);
}
static bool
fndecl_uses_vector_p (tree fndecl)
{
tree arg, vars, var;
struct function *my_cfun = DECL_STRUCT_FUNCTION (fndecl);
if (TREE_CODE (TREE_TYPE (fndecl)) == VECTOR_TYPE)
return true;
arg = DECL_ARGUMENTS (fndecl);
while (arg)
{
if (TREE_CODE (TREE_TYPE (arg)) == VECTOR_TYPE)
return true;
arg = TREE_CHAIN (arg);
}
for (vars = my_cfun->unexpanded_var_list; vars; vars = TREE_CHAIN (vars))
{
var = TREE_VALUE (vars);
if (TREE_CODE (TREE_TYPE (var)) == VECTOR_TYPE)
return true;
}
return false;
}
static tree
record_reference (tree *tp, int *walk_subtrees, void *data)
{
tree t = *tp;
switch (TREE_CODE (t))
{
case VAR_DECL:
{
struct cgraph_node *node = (struct cgraph_node *)data;
if (node)
{
struct function *my_cfun = DECL_STRUCT_FUNCTION (node->decl);
if (TREE_CODE (TREE_TYPE (t)) == VECTOR_TYPE)
my_cfun->uses_vector = 1;
}
}
if (TREE_STATIC (t) || DECL_EXTERNAL (t))
{
cgraph_varpool_mark_needed_node (cgraph_varpool_node (t));
if (lang_hooks.callgraph.analyze_expr)
return lang_hooks.callgraph.analyze_expr (tp, walk_subtrees,
data);
}
break;
case FDESC_EXPR:
case ADDR_EXPR:
if (flag_unit_at_a_time)
{
tree decl = TREE_OPERAND (*tp, 0);
if (TREE_CODE (decl) == FUNCTION_DECL)
cgraph_mark_needed_node (cgraph_node (decl));
}
break;
default:
if (IS_TYPE_OR_DECL_P (*tp))
{
*walk_subtrees = 0;
break;
}
if ((unsigned int) TREE_CODE (t) >= LAST_AND_UNUSED_TREE_CODE)
return lang_hooks.callgraph.analyze_expr (tp, walk_subtrees, data);
break;
}
return NULL;
}
static void
cgraph_create_edges (struct cgraph_node *node, tree body)
{
basic_block bb;
struct function *this_cfun = DECL_STRUCT_FUNCTION (body);
block_stmt_iterator bsi;
tree step;
if (!DECL_STRUCT_FUNCTION (node->decl)->uses_vector)
DECL_STRUCT_FUNCTION (node->decl)->uses_vector = fndecl_uses_vector_p (node->decl);
visited_nodes = pointer_set_create ();
FOR_EACH_BB_FN (bb, this_cfun)
for (bsi = bsi_start (bb); !bsi_end_p (bsi); bsi_next (&bsi))
{
tree stmt = bsi_stmt (bsi);
tree call = get_call_expr_in (stmt);
tree decl;
if (call && (decl = get_callee_fndecl (call)))
{
cgraph_create_edge (node, cgraph_node (decl), stmt,
bb->count,
bb->loop_depth);
walk_tree (&TREE_OPERAND (call, 1),
record_reference, node, visited_nodes);
if (TREE_CODE (stmt) == MODIFY_EXPR)
walk_tree (&TREE_OPERAND (stmt, 0),
record_reference, node, visited_nodes);
}
else
walk_tree (bsi_stmt_ptr (bsi), record_reference, node, visited_nodes);
}
for (step = DECL_STRUCT_FUNCTION (body)->unexpanded_var_list;
step;
step = TREE_CHAIN (step))
{
tree decl = TREE_VALUE (step);
if (TREE_CODE (decl) == VAR_DECL
&& (TREE_STATIC (decl) && !DECL_EXTERNAL (decl))
&& flag_unit_at_a_time)
cgraph_varpool_finalize_decl (decl);
else if (TREE_CODE (decl) == VAR_DECL && DECL_INITIAL (decl))
walk_tree (&DECL_INITIAL (decl), record_reference, node, visited_nodes);
}
pointer_set_destroy (visited_nodes);
visited_nodes = NULL;
}
static void
initialize_inline_failed (struct cgraph_node *node)
{
struct cgraph_edge *e;
for (e = node->callers; e; e = e->next_caller)
{
gcc_assert (!e->callee->global.inlined_to);
gcc_assert (e->inline_failed);
if (node->local.redefined_extern_inline)
e->inline_failed = N_("redefined extern inline functions are not "
"considered for inlining");
else if (!node->local.inlinable)
e->inline_failed = N_("function not inlinable");
else
e->inline_failed = N_("function not considered for inlining");
}
}
static unsigned int
rebuild_cgraph_edges (void)
{
basic_block bb;
struct cgraph_node *node = cgraph_node (current_function_decl);
block_stmt_iterator bsi;
cgraph_node_remove_callees (node);
node->count = ENTRY_BLOCK_PTR->count;
FOR_EACH_BB (bb)
for (bsi = bsi_start (bb); !bsi_end_p (bsi); bsi_next (&bsi))
{
tree stmt = bsi_stmt (bsi);
tree call = get_call_expr_in (stmt);
tree decl;
if (call && (decl = get_callee_fndecl (call)))
cgraph_create_edge (node, cgraph_node (decl), stmt,
bb->count,
bb->loop_depth);
}
initialize_inline_failed (node);
gcc_assert (!node->global.inlined_to);
return 0;
}
struct tree_opt_pass pass_rebuild_cgraph_edges =
{
NULL,
NULL,
rebuild_cgraph_edges,
NULL,
NULL,
0,
0,
PROP_cfg,
0,
0,
0,
0,
0
};
void
verify_cgraph_node (struct cgraph_node *node)
{
struct cgraph_edge *e;
struct cgraph_node *main_clone;
struct function *this_cfun = DECL_STRUCT_FUNCTION (node->decl);
basic_block this_block;
block_stmt_iterator bsi;
bool error_found = false;
if (errorcount || sorrycount)
return;
timevar_push (TV_CGRAPH_VERIFY);
for (e = node->callees; e; e = e->next_callee)
if (e->aux)
{
error ("aux field set for edge %s->%s",
cgraph_node_name (e->caller), cgraph_node_name (e->callee));
error_found = true;
}
if (node->count < 0)
{
error ("Execution count is negative");
error_found = true;
}
for (e = node->callers; e; e = e->next_caller)
{
if (e->count < 0)
{
error ("caller edge count is negative");
error_found = true;
}
if (!e->inline_failed)
{
if (node->global.inlined_to
!= (e->caller->global.inlined_to
? e->caller->global.inlined_to : e->caller))
{
error ("inlined_to pointer is wrong");
error_found = true;
}
if (node->callers->next_caller)
{
error ("multiple inline callers");
error_found = true;
}
}
else
if (node->global.inlined_to)
{
error ("inlined_to pointer set for noninline callers");
error_found = true;
}
}
if (!node->callers && node->global.inlined_to)
{
error ("inlined_to pointer is set but no predecessors found");
error_found = true;
}
if (node->global.inlined_to == node)
{
error ("inlined_to pointer refers to itself");
error_found = true;
}
for (main_clone = cgraph_node (node->decl); main_clone;
main_clone = main_clone->next_clone)
if (main_clone == node)
break;
if (!cgraph_node (node->decl))
{
error ("node not found in cgraph_hash");
error_found = true;
}
if (node->analyzed
&& DECL_SAVED_TREE (node->decl) && !TREE_ASM_WRITTEN (node->decl)
&& (!IS_EXTERN_NOINLINE (node->decl) || node->global.inlined_to))
{
if (this_cfun->cfg)
{
visited_nodes = pointer_set_create ();
FOR_EACH_BB_FN (this_block, this_cfun)
for (bsi = bsi_start (this_block); !bsi_end_p (bsi); bsi_next (&bsi))
{
tree stmt = bsi_stmt (bsi);
tree call = get_call_expr_in (stmt);
tree decl;
if (call && (decl = get_callee_fndecl (call)))
{
struct cgraph_edge *e = cgraph_edge (node, stmt);
if (e)
{
if (e->aux)
{
error ("shared call_stmt:");
debug_generic_stmt (stmt);
error_found = true;
}
if (e->callee->decl != cgraph_node (decl)->decl
&& e->inline_failed)
{
error ("edge points to wrong declaration:");
debug_tree (e->callee->decl);
fprintf (stderr," Instead of:");
debug_tree (decl);
}
e->aux = (void *)1;
}
else
{
error ("missing callgraph edge for call stmt:");
debug_generic_stmt (stmt);
error_found = true;
}
}
}
pointer_set_destroy (visited_nodes);
visited_nodes = NULL;
}
else
gcc_unreachable ();
for (e = node->callees; e; e = e->next_callee)
{
if (!e->aux)
{
error ("edge %s->%s has no corresponding call_stmt",
cgraph_node_name (e->caller),
cgraph_node_name (e->callee));
debug_generic_stmt (e->call_stmt);
error_found = true;
}
e->aux = 0;
}
}
if (error_found)
{
dump_cgraph_node (stderr, node);
internal_error ("verify_cgraph_node failed");
}
timevar_pop (TV_CGRAPH_VERIFY);
}
void
verify_cgraph (void)
{
struct cgraph_node *node;
if (sorrycount || errorcount)
return;
for (node = cgraph_nodes; node; node = node->next)
verify_cgraph_node (node);
}
static bool
cgraph_varpool_assemble_decl (struct cgraph_varpool_node *node)
{
tree decl = node->decl;
if (!TREE_ASM_WRITTEN (decl)
&& !node->alias
&& !DECL_EXTERNAL (decl)
&& (TREE_CODE (decl) != VAR_DECL || !DECL_HAS_VALUE_EXPR_P (decl)))
{
assemble_variable (decl, 0, 1, 0);
return TREE_ASM_WRITTEN (decl);
}
return false;
}
bool
cgraph_varpool_assemble_pending_decls (void)
{
bool changed = false;
if (errorcount || sorrycount)
return false;
cgraph_varpool_analyze_pending_decls ();
while (cgraph_varpool_nodes_queue)
{
struct cgraph_varpool_node *node = cgraph_varpool_nodes_queue;
cgraph_varpool_nodes_queue = cgraph_varpool_nodes_queue->next_needed;
if (cgraph_varpool_assemble_decl (node))
{
changed = true;
node->next_needed = cgraph_varpool_assembled_nodes_queue;
cgraph_varpool_assembled_nodes_queue = node;
node->finalized = 1;
}
else
node->next_needed = NULL;
}
cgraph_varpool_last_needed_node = NULL;
return changed;
}
static void
cgraph_varpool_output_debug_info (void)
{
timevar_push (TV_SYMOUT);
if (errorcount == 0 && sorrycount == 0)
while (cgraph_varpool_assembled_nodes_queue)
{
struct cgraph_varpool_node *node = cgraph_varpool_assembled_nodes_queue;
if (DECL_CONTEXT (node->decl)
&& (TREE_CODE (DECL_CONTEXT (node->decl)) == BLOCK
|| TREE_CODE (DECL_CONTEXT (node->decl)) == FUNCTION_DECL)
&& errorcount == 0 && sorrycount == 0)
(*debug_hooks->global_decl) (node->decl);
cgraph_varpool_assembled_nodes_queue = node->next_needed;
node->next_needed = 0;
}
timevar_pop (TV_SYMOUT);
}
static void
cgraph_output_pending_asms (void)
{
struct cgraph_asm_node *can;
if (errorcount || sorrycount)
return;
for (can = cgraph_asm_nodes; can; can = can->next)
assemble_asm (can->asm_str);
cgraph_asm_nodes = NULL;
}
void
cgraph_analyze_function (struct cgraph_node *node)
{
tree decl = node->decl;
current_function_decl = decl;
push_cfun (DECL_STRUCT_FUNCTION (decl));
cgraph_lower_function (node);
cgraph_create_edges (node, decl);
node->local.inlinable = tree_inlinable_function_p (decl);
if (!flag_unit_at_a_time)
node->local.self_insns = estimate_num_insns (decl);
if (node->local.inlinable)
node->local.disregard_inline_limits
= lang_hooks.tree_inlining.disregard_inline_limits (decl);
initialize_inline_failed (node);
if (flag_really_no_inline && !node->local.disregard_inline_limits)
node->local.inlinable = 0;
node->global.insns = node->local.self_insns;
node->analyzed = true;
pop_cfun ();
current_function_decl = NULL;
}
static void
process_function_and_variable_attributes (struct cgraph_node *first,
struct cgraph_varpool_node *first_var)
{
struct cgraph_node *node;
struct cgraph_varpool_node *vnode;
for (node = cgraph_nodes; node != first; node = node->next)
{
tree decl = node->decl;
if (lookup_attribute ("used", DECL_ATTRIBUTES (decl)))
{
mark_decl_referenced (decl);
if (node->local.finalized)
cgraph_mark_needed_node (node);
}
if (lookup_attribute ("externally_visible", DECL_ATTRIBUTES (decl)))
{
if (! TREE_PUBLIC (node->decl))
warning (OPT_Wattributes,
"%J%<externally_visible%> attribute have effect only on public objects",
node->decl);
else
{
if (node->local.finalized)
cgraph_mark_needed_node (node);
node->local.externally_visible = true;
}
}
}
for (vnode = cgraph_varpool_nodes; vnode != first_var; vnode = vnode->next)
{
tree decl = vnode->decl;
if (lookup_attribute ("used", DECL_ATTRIBUTES (decl)))
{
mark_decl_referenced (decl);
if (vnode->finalized)
cgraph_varpool_mark_needed_node (vnode);
}
if (lookup_attribute ("externally_visible", DECL_ATTRIBUTES (decl)))
{
if (! TREE_PUBLIC (vnode->decl))
warning (OPT_Wattributes,
"%J%<externally_visible%> attribute have effect only on public objects",
vnode->decl);
else
{
if (vnode->finalized)
cgraph_varpool_mark_needed_node (vnode);
vnode->externally_visible = true;
}
}
}
}
void
cgraph_finalize_compilation_unit (void)
{
struct cgraph_node *node, *next;
static struct cgraph_node *first_analyzed;
struct cgraph_node *first_processed = first_analyzed;
static struct cgraph_varpool_node *first_analyzed_var;
if (errorcount || sorrycount)
return;
finish_aliases_1 ();
if (!flag_unit_at_a_time)
{
cgraph_output_pending_asms ();
cgraph_assemble_pending_functions ();
cgraph_varpool_output_debug_info ();
return;
}
if (!quiet_flag)
{
fprintf (stderr, "\nAnalyzing compilation unit");
fflush (stderr);
}
timevar_push (TV_CGRAPH);
process_function_and_variable_attributes (first_processed,
first_analyzed_var);
first_processed = cgraph_nodes;
first_analyzed_var = cgraph_varpool_nodes;
cgraph_varpool_analyze_pending_decls ();
if (cgraph_dump_file)
{
fprintf (cgraph_dump_file, "Initial entry points:");
for (node = cgraph_nodes; node != first_analyzed; node = node->next)
if (node->needed && DECL_SAVED_TREE (node->decl))
fprintf (cgraph_dump_file, " %s", cgraph_node_name (node));
fprintf (cgraph_dump_file, "\n");
}
while (cgraph_nodes_queue)
{
struct cgraph_edge *edge;
tree decl = cgraph_nodes_queue->decl;
node = cgraph_nodes_queue;
cgraph_nodes_queue = cgraph_nodes_queue->next_needed;
node->next_needed = NULL;
if (!DECL_SAVED_TREE (decl))
{
cgraph_reset_node (node);
continue;
}
gcc_assert (!node->analyzed && node->reachable);
gcc_assert (DECL_SAVED_TREE (decl));
cgraph_analyze_function (node);
for (edge = node->callees; edge; edge = edge->next_callee)
if (!edge->callee->reachable)
cgraph_mark_reachable_node (edge->callee);
process_function_and_variable_attributes (first_processed,
first_analyzed_var);
first_processed = cgraph_nodes;
first_analyzed_var = cgraph_varpool_nodes;
cgraph_varpool_analyze_pending_decls ();
}
#if TARGET_MACHO && defined TARGET_SSE2
ix86_darwin_redirect_calls ();
#endif
if (cgraph_dump_file)
{
fprintf (cgraph_dump_file, "Unit entry points:");
for (node = cgraph_nodes; node != first_analyzed; node = node->next)
if (node->needed && DECL_SAVED_TREE (node->decl))
fprintf (cgraph_dump_file, " %s", cgraph_node_name (node));
fprintf (cgraph_dump_file, "\n\nInitial ");
dump_cgraph (cgraph_dump_file);
}
if (cgraph_dump_file)
fprintf (cgraph_dump_file, "\nReclaiming functions:");
for (node = cgraph_nodes; node != first_analyzed; node = next)
{
tree decl = node->decl;
next = node->next;
if (node->local.finalized && !DECL_SAVED_TREE (decl))
cgraph_reset_node (node);
if (!node->reachable && DECL_SAVED_TREE (decl))
{
if (cgraph_dump_file)
fprintf (cgraph_dump_file, " %s", cgraph_node_name (node));
cgraph_remove_node (node);
continue;
}
else
node->next_needed = NULL;
gcc_assert (!node->local.finalized || DECL_SAVED_TREE (decl));
gcc_assert (node->analyzed == node->local.finalized);
}
if (cgraph_dump_file)
{
fprintf (cgraph_dump_file, "\n\nReclaimed ");
dump_cgraph (cgraph_dump_file);
}
first_analyzed = cgraph_nodes;
ggc_collect ();
timevar_pop (TV_CGRAPH);
}
static void
cgraph_mark_functions_to_output (void)
{
struct cgraph_node *node;
for (node = cgraph_nodes; node; node = node->next)
{
tree decl = node->decl;
struct cgraph_edge *e;
gcc_assert (!node->output);
for (e = node->callers; e; e = e->next_caller)
if (e->inline_failed)
break;
if (DECL_SAVED_TREE (decl)
&& !node->global.inlined_to
&& (node->needed
|| (e && node->reachable))
&& !TREE_ASM_WRITTEN (decl)
&& !IS_EXTERN_NOINLINE (decl))
node->output = 1;
else
{
#ifdef ENABLE_CHECKING
if (!node->global.inlined_to && DECL_SAVED_TREE (decl)
&& !IS_EXTERN_NOINLINE (decl))
{
dump_cgraph_node (stderr, node);
internal_error ("failed to reclaim unneeded function");
}
#endif
gcc_assert (node->global.inlined_to || !DECL_SAVED_TREE (decl)
|| IS_EXTERN_NOINLINE (decl));
}
}
}
static void
cgraph_expand_function (struct cgraph_node *node)
{
tree decl = node->decl;
gcc_assert (!node->global.inlined_to);
if (flag_unit_at_a_time)
announce_function (decl);
cgraph_lower_function (node);
lang_hooks.callgraph.expand_function (decl);
gcc_assert (TREE_ASM_WRITTEN (node->decl));
current_function_decl = NULL;
if (!cgraph_preserve_function_body_p (node->decl))
{
DECL_SAVED_TREE (node->decl) = NULL;
DECL_STRUCT_FUNCTION (node->decl) = NULL;
DECL_INITIAL (node->decl) = error_mark_node;
cgraph_node_remove_callees (node);
}
cgraph_function_flags_ready = true;
}
bool
cgraph_inline_p (struct cgraph_edge *e, const char **reason)
{
*reason = e->inline_failed;
return !e->inline_failed;
}
static void
cgraph_expand_all_functions (void)
{
struct cgraph_node *node;
struct cgraph_node **order = XCNEWVEC (struct cgraph_node *, cgraph_n_nodes);
int order_pos = 0, new_order_pos = 0;
int i;
order_pos = cgraph_postorder (order);
gcc_assert (order_pos == cgraph_n_nodes);
for (i = 0; i < order_pos; i++)
if (order[i]->output)
order[new_order_pos++] = order[i];
for (i = new_order_pos - 1; i >= 0; i--)
{
node = order[i];
if (node->output)
{
gcc_assert (node->reachable);
node->output = 0;
cgraph_expand_function (node);
}
}
free (order);
while (cgraph_expand_queue)
{
node = cgraph_expand_queue;
cgraph_expand_queue = cgraph_expand_queue->next_needed;
node->next_needed = NULL;
node->output = 0;
node->lowered = DECL_STRUCT_FUNCTION (node->decl)->cfg != NULL;
cgraph_expand_function (node);
}
}
struct cgraph_order_sort
{
enum { ORDER_UNDEFINED = 0, ORDER_FUNCTION, ORDER_VAR, ORDER_ASM } kind;
union
{
struct cgraph_node *f;
struct cgraph_varpool_node *v;
struct cgraph_asm_node *a;
} u;
};
static void
cgraph_output_in_order (void)
{
int max;
size_t size;
struct cgraph_order_sort *nodes;
int i;
struct cgraph_node *pf;
struct cgraph_varpool_node *pv;
struct cgraph_asm_node *pa;
max = cgraph_order;
size = max * sizeof (struct cgraph_order_sort);
nodes = (struct cgraph_order_sort *) alloca (size);
memset (nodes, 0, size);
cgraph_varpool_analyze_pending_decls ();
for (pf = cgraph_nodes; pf; pf = pf->next)
{
if (pf->output)
{
i = pf->order;
gcc_assert (nodes[i].kind == ORDER_UNDEFINED);
nodes[i].kind = ORDER_FUNCTION;
nodes[i].u.f = pf;
}
}
for (pv = cgraph_varpool_nodes_queue; pv; pv = pv->next_needed)
{
i = pv->order;
gcc_assert (nodes[i].kind == ORDER_UNDEFINED);
nodes[i].kind = ORDER_VAR;
nodes[i].u.v = pv;
}
for (pa = cgraph_asm_nodes; pa; pa = pa->next)
{
i = pa->order;
gcc_assert (nodes[i].kind == ORDER_UNDEFINED);
nodes[i].kind = ORDER_ASM;
nodes[i].u.a = pa;
}
for (i = 0; i < max; ++i)
{
switch (nodes[i].kind)
{
case ORDER_FUNCTION:
nodes[i].u.f->output = 0;
cgraph_expand_function (nodes[i].u.f);
break;
case ORDER_VAR:
cgraph_varpool_assemble_decl (nodes[i].u.v);
break;
case ORDER_ASM:
assemble_asm (nodes[i].u.a->asm_str);
break;
case ORDER_UNDEFINED:
break;
default:
gcc_unreachable ();
}
}
cgraph_asm_nodes = NULL;
}
static void
cgraph_function_and_variable_visibility (void)
{
struct cgraph_node *node;
struct cgraph_varpool_node *vnode;
for (node = cgraph_nodes; node; node = node->next)
{
if (node->reachable
&& (DECL_COMDAT (node->decl)
|| (!flag_whole_program
&& TREE_PUBLIC (node->decl) && !DECL_EXTERNAL (node->decl))))
node->local.externally_visible = true;
if (!node->local.externally_visible && node->analyzed
&& !DECL_EXTERNAL (node->decl))
{
gcc_assert (flag_whole_program || !TREE_PUBLIC (node->decl));
TREE_PUBLIC (node->decl) = 0;
}
node->local.local = (!node->needed
&& node->analyzed
&& !DECL_EXTERNAL (node->decl)
&& !node->local.externally_visible);
}
for (vnode = cgraph_varpool_nodes_queue; vnode; vnode = vnode->next_needed)
{
if (vnode->needed
&& !flag_whole_program
&& (DECL_COMDAT (vnode->decl) || TREE_PUBLIC (vnode->decl)))
vnode->externally_visible = 1;
if (!vnode->externally_visible)
{
gcc_assert (flag_whole_program || !TREE_PUBLIC (vnode->decl));
TREE_PUBLIC (vnode->decl) = 0;
}
gcc_assert (TREE_STATIC (vnode->decl));
}
cgraph_remove_unreachable_nodes (true, cgraph_dump_file);
if (cgraph_dump_file)
{
fprintf (cgraph_dump_file, "\nMarking local functions:");
for (node = cgraph_nodes; node; node = node->next)
if (node->local.local)
fprintf (cgraph_dump_file, " %s", cgraph_node_name (node));
fprintf (cgraph_dump_file, "\n\n");
fprintf (cgraph_dump_file, "\nMarking externally visible functions:");
for (node = cgraph_nodes; node; node = node->next)
if (node->local.externally_visible)
fprintf (cgraph_dump_file, " %s", cgraph_node_name (node));
fprintf (cgraph_dump_file, "\n\n");
}
cgraph_function_flags_ready = true;
}
bool
cgraph_preserve_function_body_p (tree decl)
{
struct cgraph_node *node;
if (!cgraph_global_info_ready)
return (flag_really_no_inline
? lang_hooks.tree_inlining.disregard_inline_limits (decl)
: DECL_INLINE (decl));
for (node = cgraph_node (decl); node; node = node->next_clone)
if (node->global.inlined_to)
return true;
return false;
}
#ifndef ENABLE_LLVM
static void
ipa_passes (void)
{
cfun = NULL;
tree_register_cfg_hooks ();
bitmap_obstack_initialize (NULL);
execute_ipa_pass_list (all_ipa_passes);
bitmap_obstack_release (NULL);
}
#endif
void
cgraph_optimize (void)
{
if (errorcount || sorrycount)
return;
#ifdef ENABLE_CHECKING
verify_cgraph ();
#endif
if (!flag_unit_at_a_time)
{
cgraph_output_pending_asms ();
cgraph_varpool_assemble_pending_decls ();
cgraph_varpool_output_debug_info ();
return;
}
process_pending_assemble_externals ();
cgraph_varpool_analyze_pending_decls ();
timevar_push (TV_CGRAPHOPT);
if (!quiet_flag)
fprintf (stderr, "Performing interprocedural optimizations\n");
cgraph_function_and_variable_visibility ();
if (cgraph_dump_file)
{
fprintf (cgraph_dump_file, "Marked ");
dump_cgraph (cgraph_dump_file);
}
#ifndef ENABLE_LLVM
if (errorcount == 0 && sorrycount == 0)
ipa_passes ();
#endif
cgraph_remove_unreachable_nodes (false, dump_file);
cgraph_increase_alignment ();
cgraph_global_info_ready = true;
if (cgraph_dump_file)
{
fprintf (cgraph_dump_file, "Optimized ");
dump_cgraph (cgraph_dump_file);
dump_varpool (cgraph_dump_file);
}
timevar_pop (TV_CGRAPHOPT);
if (!quiet_flag)
fprintf (stderr, "Assembling functions:\n");
#ifdef ENABLE_CHECKING
verify_cgraph ();
#endif
cgraph_mark_functions_to_output ();
if (!flag_toplevel_reorder)
cgraph_output_in_order ();
else
{
cgraph_output_pending_asms ();
cgraph_expand_all_functions ();
cgraph_varpool_remove_unreferenced_decls ();
cgraph_varpool_assemble_pending_decls ();
cgraph_varpool_output_debug_info ();
}
if (cgraph_dump_file)
{
fprintf (cgraph_dump_file, "\nFinal ");
dump_cgraph (cgraph_dump_file);
}
#ifdef ENABLE_CHECKING
verify_cgraph ();
if (flag_unit_at_a_time
&& !(sorrycount || errorcount))
{
struct cgraph_node *node;
bool error_found = false;
for (node = cgraph_nodes; node; node = node->next)
if (node->analyzed
&& (node->global.inlined_to
|| DECL_SAVED_TREE (node->decl)))
{
error_found = true;
dump_cgraph_node (stderr, node);
}
if (error_found)
internal_error ("nodes with no released memory found");
}
#endif
}
static void
cgraph_increase_alignment (void)
{
if (flag_section_anchors && flag_tree_vectorize)
{
struct cgraph_varpool_node *vnode;
for (vnode = cgraph_varpool_nodes_queue;
vnode;
vnode = vnode->next_needed)
{
tree vectype, decl = vnode->decl;
unsigned int alignment;
if (TREE_CODE (TREE_TYPE (decl)) != ARRAY_TYPE)
continue;
vectype = get_vectype_for_scalar_type (TREE_TYPE (TREE_TYPE (decl)));
if (!vectype)
continue;
alignment = TYPE_ALIGN (vectype);
if (DECL_ALIGN (decl) >= alignment)
continue;
if (vect_can_force_dr_alignment_p (decl, alignment))
{
DECL_ALIGN (decl) = TYPE_ALIGN (vectype);
DECL_USER_ALIGN (decl) = 1;
if (cgraph_dump_file)
{
fprintf (cgraph_dump_file, "Increasing alignment of decl: ");
print_generic_expr (cgraph_dump_file, decl, TDF_SLIM);
}
}
}
}
}
void
cgraph_build_static_cdtor (char which, tree body, int priority)
{
static int counter = 0;
char which_buf[16];
tree decl, name, resdecl;
sprintf (which_buf, "%c_%d", which, counter++);
\
name = get_file_function_name (which_buf);
\
decl = build_decl (FUNCTION_DECL, name,
build_function_type (void_type_node, void_list_node));
current_function_decl = decl;
resdecl = build_decl (RESULT_DECL, NULL_TREE, void_type_node);
DECL_ARTIFICIAL (resdecl) = 1;
DECL_IGNORED_P (resdecl) = 1;
DECL_RESULT (decl) = resdecl;
allocate_struct_function (decl);
TREE_STATIC (decl) = 1;
TREE_USED (decl) = 1;
DECL_ARTIFICIAL (decl) = 1;
DECL_IGNORED_P (decl) = 1;
DECL_NO_INSTRUMENT_FUNCTION_ENTRY_EXIT (decl) = 1;
DECL_SAVED_TREE (decl) = body;
TREE_PUBLIC (decl) = ! targetm.have_ctors_dtors;
DECL_UNINLINABLE (decl) = 1;
DECL_INITIAL (decl) = make_node (BLOCK);
TREE_USED (DECL_INITIAL (decl)) = 1;
DECL_SOURCE_LOCATION (decl) = input_location;
cfun->function_end_locus = input_location;
switch (which)
{
case 'I':
DECL_STATIC_CONSTRUCTOR (decl) = 1;
break;
case 'D':
DECL_STATIC_DESTRUCTOR (decl) = 1;
break;
default:
gcc_unreachable ();
}
gimplify_function_tree (decl);
if (cgraph_global_info_ready)
{
tree_lowering_passes (decl);
tree_rest_of_compilation (decl);
}
else
cgraph_finalize_function (decl, 0);
#ifdef ENABLE_LLVM
llvm_emit_ctor_dtor (decl, priority, which == 'I');
return;
#endif
if (targetm.have_ctors_dtors)
{
void (*fn) (rtx, int);
if (which == 'I')
fn = targetm.asm_out.constructor;
else
fn = targetm.asm_out.destructor;
fn (XEXP (DECL_RTL (decl), 0), priority);
}
}
void
init_cgraph (void)
{
cgraph_dump_file = dump_begin (TDI_cgraph, NULL);
}
static void
update_call_expr (struct cgraph_node *new_version)
{
struct cgraph_edge *e;
gcc_assert (new_version);
for (e = new_version->callers; e; e = e->next_caller)
TREE_OPERAND (TREE_OPERAND (get_call_expr_in (e->call_stmt), 0), 0) = new_version->decl;
}
static struct cgraph_node *
cgraph_copy_node_for_versioning (struct cgraph_node *old_version,
tree new_decl,
VEC(cgraph_edge_p,heap) *redirect_callers)
{
struct cgraph_node *new_version;
struct cgraph_edge *e, *new_e;
struct cgraph_edge *next_callee;
unsigned i;
gcc_assert (old_version);
new_version = cgraph_node (new_decl);
new_version->analyzed = true;
new_version->local = old_version->local;
new_version->global = old_version->global;
new_version->rtl = new_version->rtl;
new_version->reachable = true;
new_version->count = old_version->count;
for (e = old_version->callees;e; e=e->next_callee)
{
new_e = cgraph_clone_edge (e, new_version, e->call_stmt, 0, e->loop_nest, true);
new_e->count = e->count;
}
for (e = new_version->callees ; e; e = next_callee)
{
next_callee = e->next_callee;
if (e->callee == old_version)
cgraph_redirect_edge_callee (e, new_version);
if (!next_callee)
break;
}
for (i = 0; VEC_iterate (cgraph_edge_p, redirect_callers, i, e); i++)
{
cgraph_redirect_edge_callee (e, new_version);
}
return new_version;
}
struct cgraph_node *
cgraph_function_versioning (struct cgraph_node *old_version_node,
VEC(cgraph_edge_p,heap) *redirect_callers,
varray_type tree_map)
{
tree old_decl = old_version_node->decl;
struct cgraph_node *new_version_node = NULL;
tree new_decl;
if (!tree_versionable_function_p (old_decl))
return NULL;
new_decl = copy_node (old_decl);
new_version_node =
cgraph_copy_node_for_versioning (old_version_node, new_decl,
redirect_callers);
tree_function_versioning (old_decl, new_decl, tree_map, false);
update_call_expr (new_version_node);
DECL_EXTERNAL (new_version_node->decl) = 0;
DECL_ONE_ONLY (new_version_node->decl) = 0;
TREE_PUBLIC (new_version_node->decl) = 0;
DECL_COMDAT (new_version_node->decl) = 0;
new_version_node->local.externally_visible = 0;
new_version_node->local.local = 1;
new_version_node->lowered = true;
return new_version_node;
}
struct cgraph_node *
save_inline_function_body (struct cgraph_node *node)
{
struct cgraph_node *first_clone;
gcc_assert (node == cgraph_node (node->decl));
cgraph_lower_function (node);
if (!flag_unit_at_a_time)
{
struct cgraph_edge *e;
first_clone = cgraph_clone_node (node, node->count, 0, false);
first_clone->needed = 0;
first_clone->reachable = 1;
for (e = first_clone->callees; e; e = e->next_callee)
if (!e->inline_failed)
cgraph_clone_inlined_nodes (e, true, false);
}
else
first_clone = node->next_clone;
first_clone->decl = copy_node (node->decl);
node->next_clone = NULL;
if (!flag_unit_at_a_time)
node->inline_decl = first_clone->decl;
first_clone->prev_clone = NULL;
cgraph_insert_node_to_hashtable (first_clone);
gcc_assert (first_clone == cgraph_node (first_clone->decl));
tree_function_versioning (node->decl, first_clone->decl, NULL, true);
DECL_EXTERNAL (first_clone->decl) = 0;
DECL_ONE_ONLY (first_clone->decl) = 0;
TREE_PUBLIC (first_clone->decl) = 0;
DECL_COMDAT (first_clone->decl) = 0;
for (node = first_clone->next_clone; node; node = node->next_clone)
node->decl = first_clone->decl;
#ifdef ENABLE_CHECKING
verify_cgraph_node (first_clone);
#endif
return first_clone;
}
#include "gt-cgraphunit.h"