diff options
author | Jan Hubicka <jh@suse.cz> | 2011-04-20 00:28:54 +0200 |
---|---|---|
committer | Jan Hubicka <hubicka@gcc.gnu.org> | 2011-04-19 22:28:54 +0000 |
commit | fee8b6dadd3847d6cdc5e48a467be48ce16fd71e (patch) | |
tree | 518ceb62ac7521423992dc5befe95e7c201bee2d /gcc/ipa-inline-transform.c | |
parent | 61eca8d71b276c84155b88d7fca656c49ff73b98 (diff) | |
download | gcc-fee8b6dadd3847d6cdc5e48a467be48ce16fd71e.zip gcc-fee8b6dadd3847d6cdc5e48a467be48ce16fd71e.tar.gz gcc-fee8b6dadd3847d6cdc5e48a467be48ce16fd71e.tar.bz2 |
cgraph.h (save_inline_function_body): Remove.
* cgraph.h (save_inline_function_body): Remove.
* ipa-inline-transform.c: New file, broke out of...
* ipa-inline.c: ... this one; Update toplevel comment.
(ncalls_inlined, nfunctions_inlined): Move to ipa-inline-transform.c;
make global.
(update_noncloned_frequencies): Move to ipa-inline-transform.c
(cgraph_mark_inline_edge): Rename to inline_call; move to
ipa-inline-transform.c.
(cgraph_clone_inlined_nodes): Rename to clone_inlined_nodes;
move to ipa-inline-transform.c
(recursive_inlining, inline_small_functions, flatten_function,
ipa_inline, inline_always_inline_functions,
early_inline_small_functions): Update.
(inline_transform): Move to ipa-inline-transform.c.
* ipa-inline.h (inline_call, inline_transform, clone_inlined_nodes):
Declare.
* Makefile.in (ipa-inline-transform.o): New file.
* cgraphunit.c (save_inline_function_body): Move to
ipa-inline-transform.c
From-SVN: r172739
Diffstat (limited to 'gcc/ipa-inline-transform.c')
-rw-r--r-- | gcc/ipa-inline-transform.c | 328 |
1 files changed, 328 insertions, 0 deletions
diff --git a/gcc/ipa-inline-transform.c b/gcc/ipa-inline-transform.c new file mode 100644 index 0000000..0fb2448 --- /dev/null +++ b/gcc/ipa-inline-transform.c @@ -0,0 +1,328 @@ +/* Callgraph transformations to handle inlining + Copyright (C) 2003, 2004, 2007, 2008, 2009, 2010, 2011 + 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 3, 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 COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +/* The inline decisions are stored in callgraph in "inline plan" and + applied later. + + To mark given call inline, use inline_call function. + The function marks the edge inlinable and, if necessary, produces + virtual clone in the callgraph representing the new copy of callee's + function body. + + The inline plan is applied on given function body by inline_transform. */ + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "tm.h" +#include "tree.h" +#include "langhooks.h" +#include "cgraph.h" +#include "timevar.h" +#include "output.h" +#include "intl.h" +#include "coverage.h" +#include "ggc.h" +#include "tree-flow.h" +#include "ipa-prop.h" +#include "ipa-inline.h" +#include "tree-inline.h" + +int ncalls_inlined; +int nfunctions_inlined; + +/* Scale frequency of NODE edges by FREQ_SCALE and increase loop nest + by NEST. */ + +static void +update_noncloned_frequencies (struct cgraph_node *node, + int freq_scale, int nest) +{ + struct cgraph_edge *e; + + /* We do not want to ignore high loop nest after freq drops to 0. */ + if (!freq_scale) + freq_scale = 1; + for (e = node->callees; e; e = e->next_callee) + { + e->loop_nest += nest; + e->frequency = e->frequency * (gcov_type) freq_scale / CGRAPH_FREQ_BASE; + if (e->frequency > CGRAPH_FREQ_MAX) + e->frequency = CGRAPH_FREQ_MAX; + if (!e->inline_failed) + update_noncloned_frequencies (e->callee, freq_scale, nest); + } +} + + +/* E is expected to be an edge being inlined. Clone destination node of + the edge and redirect it to the new clone. + DUPLICATE is used for bookkeeping on whether we are actually creating new + clones or re-using node originally representing out-of-line function call. + */ + +void +clone_inlined_nodes (struct cgraph_edge *e, bool duplicate, + bool update_original, int *overall_size) +{ + HOST_WIDE_INT peak; + struct inline_summary *caller_info, *callee_info; + + if (duplicate) + { + /* We may eliminate the need for out-of-line copy to be output. + In that case just go ahead and re-use it. This is not just an + memory optimization. Making offline copy of fuction disappear + from the program will improve future decisions on inlining. */ + if (!e->callee->callers->next_caller + /* Recursive inlining never wants the master clone to + be overwritten. */ + && update_original + /* FIXME: When address is taken of DECL_EXTERNAL function we still + can remove its offline copy, but we would need to keep unanalyzed + node in the callgraph so references can point to it. */ + && !e->callee->address_taken + && cgraph_can_remove_if_no_direct_calls_p (e->callee) + /* Inlining might enable more devirtualizing, so we want to remove + those only after all devirtualizable virtual calls are processed. + Lacking may edges in callgraph we just preserve them post + inlining. */ + && (!DECL_VIRTUAL_P (e->callee->decl) + || (!DECL_COMDAT (e->callee->decl) + && !DECL_EXTERNAL (e->callee->decl))) + /* Don't reuse if more than one function shares a comdat group. + If the other function(s) are needed, we need to emit even + this function out of line. */ + && !e->callee->same_comdat_group + /* During early inlining some unanalyzed cgraph nodes might be in the + callgraph and they might reffer the function in question. */ + && !cgraph_new_nodes) + { + gcc_assert (!e->callee->global.inlined_to); + if (e->callee->analyzed && !DECL_EXTERNAL (e->callee->decl)) + { + if (overall_size) + *overall_size -= inline_summary (e->callee)->size; + nfunctions_inlined++; + } + duplicate = false; + e->callee->local.externally_visible = false; + update_noncloned_frequencies (e->callee, e->frequency, e->loop_nest); + } + else + { + struct cgraph_node *n; + n = cgraph_clone_node (e->callee, e->callee->decl, + e->count, e->frequency, e->loop_nest, + update_original, NULL); + cgraph_redirect_edge_callee (e, n); + } + } + + callee_info = inline_summary (e->callee); + caller_info = inline_summary (e->caller); + + if (e->caller->global.inlined_to) + e->callee->global.inlined_to = e->caller->global.inlined_to; + else + e->callee->global.inlined_to = e->caller; + callee_info->stack_frame_offset + = caller_info->stack_frame_offset + + caller_info->estimated_self_stack_size; + peak = callee_info->stack_frame_offset + + callee_info->estimated_self_stack_size; + if (inline_summary (e->callee->global.inlined_to)->estimated_stack_size + < peak) + inline_summary (e->callee->global.inlined_to)->estimated_stack_size = peak; + cgraph_propagate_frequency (e->callee); + + /* Recursively clone all bodies. */ + for (e = e->callee->callees; e; e = e->next_callee) + if (!e->inline_failed) + clone_inlined_nodes (e, duplicate, update_original, overall_size); +} + + +/* Mark edge E as inlined and update callgraph accordingly. UPDATE_ORIGINAL + specify whether profile of original function should be updated. If any new + indirect edges are discovered in the process, add them to NEW_EDGES, unless + it is NULL. Return true iff any new callgraph edges were discovered as a + result of inlining. */ + +bool +inline_call (struct cgraph_edge *e, bool update_original, + VEC (cgraph_edge_p, heap) **new_edges, + int *overall_size) +{ + int old_size = 0, new_size = 0; + struct cgraph_node *to = NULL; + struct cgraph_edge *curr = e; + struct inline_summary *info; + + /* Don't inline inlined edges. */ + gcc_assert (e->inline_failed); + /* Don't even think of inlining inline clone. */ + gcc_assert (!e->callee->global.inlined_to); + + e->inline_failed = CIF_OK; + DECL_POSSIBLY_INLINED (e->callee->decl) = true; + + clone_inlined_nodes (e, true, update_original, overall_size); + + /* Now update size of caller and all functions caller is inlined into. */ + for (;e && !e->inline_failed; e = e->caller->callers) + { + to = e->caller; + info = inline_summary (to); + old_size = info->size; + new_size = estimate_size_after_inlining (to, curr); + info->size = new_size; + info->time = estimate_time_after_inlining (to, curr); + } + gcc_assert (curr->callee->global.inlined_to == to); + if (overall_size && new_size > old_size) + *overall_size += new_size - old_size; + ncalls_inlined++; + + if (flag_indirect_inlining && optimize) + return ipa_propagate_indirect_call_infos (curr, new_edges); + else + return false; +} + + +/* Copy function body of NODE and redirect all inline clones to it. + This is done before inline plan is applied to NODE when there are + still some inline clones if it. + + This is neccesary because inline decisions are not really transitive + and the other inline clones may have different bodies. */ + +static struct cgraph_node * +save_inline_function_body (struct cgraph_node *node) +{ + struct cgraph_node *first_clone, *n; + + if (dump_file) + fprintf (dump_file, "\nSaving body of %s for later reuse\n", + cgraph_node_name (node)); + + gcc_assert (node == cgraph_get_node (node->decl)); + + /* first_clone will be turned into real function. */ + first_clone = node->clones; + first_clone->decl = copy_node (node->decl); + cgraph_insert_node_to_hashtable (first_clone); + gcc_assert (first_clone == cgraph_get_node (first_clone->decl)); + + /* Now reshape the clone tree, so all other clones descends from + first_clone. */ + if (first_clone->next_sibling_clone) + { + for (n = first_clone->next_sibling_clone; n->next_sibling_clone; n = n->next_sibling_clone) + n->clone_of = first_clone; + n->clone_of = first_clone; + n->next_sibling_clone = first_clone->clones; + if (first_clone->clones) + first_clone->clones->prev_sibling_clone = n; + first_clone->clones = first_clone->next_sibling_clone; + first_clone->next_sibling_clone->prev_sibling_clone = NULL; + first_clone->next_sibling_clone = NULL; + gcc_assert (!first_clone->prev_sibling_clone); + } + first_clone->clone_of = NULL; + + /* Now node in question has no clones. */ + node->clones = NULL; + + if (first_clone->clones) + for (n = first_clone->clones; n != first_clone;) + { + gcc_assert (n->decl == node->decl); + n->decl = first_clone->decl; + if (n->clones) + n = n->clones; + else if (n->next_sibling_clone) + n = n->next_sibling_clone; + else + { + while (n != first_clone && !n->next_sibling_clone) + n = n->clone_of; + if (n != first_clone) + n = n->next_sibling_clone; + } + } + + /* Copy the OLD_VERSION_NODE function tree to the new version. */ + tree_function_versioning (node->decl, first_clone->decl, NULL, true, NULL, + NULL, NULL); + + DECL_EXTERNAL (first_clone->decl) = 0; + DECL_COMDAT_GROUP (first_clone->decl) = NULL_TREE; + TREE_PUBLIC (first_clone->decl) = 0; + DECL_COMDAT (first_clone->decl) = 0; + VEC_free (ipa_opt_pass, heap, + first_clone->ipa_transforms_to_apply); + first_clone->ipa_transforms_to_apply = NULL; + +#ifdef ENABLE_CHECKING + verify_cgraph_node (first_clone); +#endif + return first_clone; +} + + +/* Apply inline plan to function. */ + +unsigned int +inline_transform (struct cgraph_node *node) +{ + unsigned int todo = 0; + struct cgraph_edge *e; + bool inline_p = false; + + /* FIXME: Currently the pass manager is adding inline transform more than + once to some clones. This needs revisiting after WPA cleanups. */ + if (cfun->after_inlining) + return 0; + + /* We might need the body of this function so that we can expand + it inline somewhere else. */ + if (cgraph_preserve_function_body_p (node->decl)) + save_inline_function_body (node); + + for (e = node->callees; e; e = e->next_callee) + { + cgraph_redirect_edge_call_stmt_to_callee (e); + if (!e->inline_failed || warn_inline) + inline_p = true; + } + + if (inline_p) + { + timevar_push (TV_INTEGRATION); + todo = optimize_inline_calls (current_function_decl); + timevar_pop (TV_INTEGRATION); + } + cfun->always_inline_functions_inlined = true; + cfun->after_inlining = true; + return todo | execute_fixup_cfg (); +} |