| /* Utilities for ipa analysis. |
| Copyright (C) 2005-2022 Free Software Foundation, Inc. |
| Contributed by Kenneth Zadeck <zadeck@naturalbridge.com> |
| |
| 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/>. */ |
| |
| #include "config.h" |
| #include "system.h" |
| #include "coretypes.h" |
| #include "backend.h" |
| #include "tree.h" |
| #include "gimple.h" |
| #include "predict.h" |
| #include "alloc-pool.h" |
| #include "cgraph.h" |
| #include "lto-streamer.h" |
| #include "dumpfile.h" |
| #include "splay-tree.h" |
| #include "ipa-utils.h" |
| #include "symbol-summary.h" |
| #include "tree-vrp.h" |
| #include "ipa-prop.h" |
| #include "ipa-fnsummary.h" |
| |
| /* Debugging function for postorder and inorder code. NOTE is a string |
| that is printed before the nodes are printed. ORDER is an array of |
| cgraph_nodes that has COUNT useful nodes in it. */ |
| |
| void |
| ipa_print_order (FILE* out, |
| const char * note, |
| struct cgraph_node** order, |
| int count) |
| { |
| int i; |
| fprintf (out, "\n\n ordered call graph: %s\n", note); |
| |
| for (i = count - 1; i >= 0; i--) |
| order[i]->dump (out); |
| fprintf (out, "\n"); |
| fflush (out); |
| } |
| |
| |
| struct searchc_env { |
| struct cgraph_node **stack; |
| struct cgraph_node **result; |
| int stack_size; |
| int order_pos; |
| splay_tree nodes_marked_new; |
| bool reduce; |
| int count; |
| }; |
| |
| /* This is an implementation of Tarjan's strongly connected region |
| finder as reprinted in Aho Hopcraft and Ullman's The Design and |
| Analysis of Computer Programs (1975) pages 192-193. This version |
| has been customized for cgraph_nodes. The env parameter is because |
| it is recursive and there are no nested functions here. This |
| function should only be called from itself or |
| ipa_reduced_postorder. ENV is a stack env and would be |
| unnecessary if C had nested functions. V is the node to start |
| searching from. */ |
| |
| static void |
| searchc (struct searchc_env* env, struct cgraph_node *v, |
| bool (*ignore_edge) (struct cgraph_edge *)) |
| { |
| struct cgraph_edge *edge; |
| struct ipa_dfs_info *v_info = (struct ipa_dfs_info *) v->aux; |
| |
| /* mark node as old */ |
| v_info->new_node = false; |
| splay_tree_remove (env->nodes_marked_new, v->get_uid ()); |
| |
| v_info->dfn_number = env->count; |
| v_info->low_link = env->count; |
| env->count++; |
| env->stack[(env->stack_size)++] = v; |
| v_info->on_stack = true; |
| |
| for (edge = v->callees; edge; edge = edge->next_callee) |
| { |
| struct ipa_dfs_info * w_info; |
| enum availability avail; |
| struct cgraph_node *w = edge->callee->ultimate_alias_target (&avail); |
| |
| if (!w || (ignore_edge && ignore_edge (edge))) |
| continue; |
| |
| if (w->aux |
| && (avail >= AVAIL_INTERPOSABLE)) |
| { |
| w_info = (struct ipa_dfs_info *) w->aux; |
| if (w_info->new_node) |
| { |
| searchc (env, w, ignore_edge); |
| v_info->low_link = |
| (v_info->low_link < w_info->low_link) ? |
| v_info->low_link : w_info->low_link; |
| } |
| else |
| if ((w_info->dfn_number < v_info->dfn_number) |
| && (w_info->on_stack)) |
| v_info->low_link = |
| (w_info->dfn_number < v_info->low_link) ? |
| w_info->dfn_number : v_info->low_link; |
| } |
| } |
| |
| |
| if (v_info->low_link == v_info->dfn_number) |
| { |
| struct cgraph_node *last = NULL; |
| struct cgraph_node *x; |
| struct ipa_dfs_info *x_info; |
| do { |
| x = env->stack[--(env->stack_size)]; |
| x_info = (struct ipa_dfs_info *) x->aux; |
| x_info->on_stack = false; |
| x_info->scc_no = v_info->dfn_number; |
| |
| if (env->reduce) |
| { |
| x_info->next_cycle = last; |
| last = x; |
| } |
| else |
| env->result[env->order_pos++] = x; |
| } |
| while (v != x); |
| if (env->reduce) |
| env->result[env->order_pos++] = v; |
| } |
| } |
| |
| /* Topsort the call graph by caller relation. Put the result in ORDER. |
| |
| The REDUCE flag is true if you want the cycles reduced to single nodes. |
| You can use ipa_get_nodes_in_cycle to obtain a vector containing all real |
| call graph nodes in a reduced node. |
| |
| Set ALLOW_OVERWRITABLE if nodes with such availability should be included. |
| IGNORE_EDGE, if non-NULL is a hook that may make some edges insignificant |
| for the topological sort. */ |
| |
| int |
| ipa_reduced_postorder (struct cgraph_node **order, |
| bool reduce, |
| bool (*ignore_edge) (struct cgraph_edge *)) |
| { |
| struct cgraph_node *node; |
| struct searchc_env env; |
| splay_tree_node result; |
| env.stack = XCNEWVEC (struct cgraph_node *, symtab->cgraph_count); |
| env.stack_size = 0; |
| env.result = order; |
| env.order_pos = 0; |
| env.nodes_marked_new = splay_tree_new (splay_tree_compare_ints, 0, 0); |
| env.count = 1; |
| env.reduce = reduce; |
| |
| FOR_EACH_DEFINED_FUNCTION (node) |
| { |
| enum availability avail = node->get_availability (); |
| |
| if (avail > AVAIL_INTERPOSABLE |
| || avail == AVAIL_INTERPOSABLE) |
| { |
| /* Reuse the info if it is already there. */ |
| struct ipa_dfs_info *info = (struct ipa_dfs_info *) node->aux; |
| if (!info) |
| info = XCNEW (struct ipa_dfs_info); |
| info->new_node = true; |
| info->on_stack = false; |
| info->next_cycle = NULL; |
| node->aux = info; |
| |
| splay_tree_insert (env.nodes_marked_new, |
| (splay_tree_key)node->get_uid (), |
| (splay_tree_value)node); |
| } |
| else |
| node->aux = NULL; |
| } |
| result = splay_tree_min (env.nodes_marked_new); |
| while (result) |
| { |
| node = (struct cgraph_node *)result->value; |
| searchc (&env, node, ignore_edge); |
| result = splay_tree_min (env.nodes_marked_new); |
| } |
| splay_tree_delete (env.nodes_marked_new); |
| free (env.stack); |
| |
| return env.order_pos; |
| } |
| |
| /* Deallocate all ipa_dfs_info structures pointed to by the aux pointer of call |
| graph nodes. */ |
| |
| void |
| ipa_free_postorder_info (void) |
| { |
| struct cgraph_node *node; |
| FOR_EACH_DEFINED_FUNCTION (node) |
| { |
| /* Get rid of the aux information. */ |
| if (node->aux) |
| { |
| free (node->aux); |
| node->aux = NULL; |
| } |
| } |
| } |
| |
| /* Get the set of nodes for the cycle in the reduced call graph starting |
| from NODE. */ |
| |
| vec<cgraph_node *> |
| ipa_get_nodes_in_cycle (struct cgraph_node *node) |
| { |
| vec<cgraph_node *> v = vNULL; |
| struct ipa_dfs_info *node_dfs_info; |
| while (node) |
| { |
| v.safe_push (node); |
| node_dfs_info = (struct ipa_dfs_info *) node->aux; |
| node = node_dfs_info->next_cycle; |
| } |
| return v; |
| } |
| |
| /* Return true iff the CS is an edge within a strongly connected component as |
| computed by ipa_reduced_postorder. */ |
| |
| bool |
| ipa_edge_within_scc (struct cgraph_edge *cs) |
| { |
| struct ipa_dfs_info *caller_dfs = (struct ipa_dfs_info *) cs->caller->aux; |
| struct ipa_dfs_info *callee_dfs; |
| struct cgraph_node *callee = cs->callee->function_symbol (); |
| |
| callee_dfs = (struct ipa_dfs_info *) callee->aux; |
| return (caller_dfs |
| && callee_dfs |
| && caller_dfs->scc_no == callee_dfs->scc_no); |
| } |
| |
| struct postorder_stack |
| { |
| struct cgraph_node *node; |
| struct cgraph_edge *edge; |
| int ref; |
| }; |
| |
| /* Fill array order with all nodes with output flag set in the reverse |
| topological order. Return the number of elements in the array. |
| FIXME: While walking, consider aliases, too. */ |
| |
| int |
| ipa_reverse_postorder (struct cgraph_node **order) |
| { |
| struct cgraph_node *node, *node2; |
| int stack_size = 0; |
| int order_pos = 0; |
| struct cgraph_edge *edge; |
| int pass; |
| struct ipa_ref *ref = NULL; |
| |
| struct postorder_stack *stack = |
| XCNEWVEC (struct postorder_stack, symtab->cgraph_count); |
| |
| /* We have to deal with cycles nicely, so use a depth first traversal |
| output algorithm. Ignore the fact that some functions won't need |
| to be output and put them into order as well, so we get dependencies |
| right through inline functions. */ |
| FOR_EACH_FUNCTION (node) |
| node->aux = NULL; |
| for (pass = 0; pass < 2; pass++) |
| FOR_EACH_FUNCTION (node) |
| if (!node->aux |
| && (pass |
| || (!node->address_taken |
| && !node->inlined_to |
| && !node->alias && !node->thunk |
| && !node->only_called_directly_p ()))) |
| { |
| stack_size = 0; |
| stack[stack_size].node = node; |
| stack[stack_size].edge = node->callers; |
| stack[stack_size].ref = 0; |
| node->aux = (void *)(size_t)1; |
| while (stack_size >= 0) |
| { |
| while (true) |
| { |
| node2 = NULL; |
| while (stack[stack_size].edge && !node2) |
| { |
| edge = stack[stack_size].edge; |
| node2 = edge->caller; |
| stack[stack_size].edge = edge->next_caller; |
| /* Break possible cycles involving always-inline |
| functions by ignoring edges from always-inline |
| functions to non-always-inline functions. */ |
| if (DECL_DISREGARD_INLINE_LIMITS (edge->caller->decl) |
| && !DECL_DISREGARD_INLINE_LIMITS |
| (edge->callee->function_symbol ()->decl)) |
| node2 = NULL; |
| } |
| for (; stack[stack_size].node->iterate_referring ( |
| stack[stack_size].ref, |
| ref) && !node2; |
| stack[stack_size].ref++) |
| { |
| if (ref->use == IPA_REF_ALIAS) |
| node2 = dyn_cast <cgraph_node *> (ref->referring); |
| } |
| if (!node2) |
| break; |
| if (!node2->aux) |
| { |
| stack[++stack_size].node = node2; |
| stack[stack_size].edge = node2->callers; |
| stack[stack_size].ref = 0; |
| node2->aux = (void *)(size_t)1; |
| } |
| } |
| order[order_pos++] = stack[stack_size--].node; |
| } |
| } |
| free (stack); |
| FOR_EACH_FUNCTION (node) |
| node->aux = NULL; |
| return order_pos; |
| } |
| |
| |
| |
| /* Given a memory reference T, will return the variable at the bottom |
| of the access. Unlike get_base_address, this will recurse through |
| INDIRECT_REFS. */ |
| |
| tree |
| get_base_var (tree t) |
| { |
| while (!SSA_VAR_P (t) |
| && (!CONSTANT_CLASS_P (t)) |
| && TREE_CODE (t) != LABEL_DECL |
| && TREE_CODE (t) != FUNCTION_DECL |
| && TREE_CODE (t) != CONST_DECL |
| && TREE_CODE (t) != CONSTRUCTOR) |
| { |
| t = TREE_OPERAND (t, 0); |
| } |
| return t; |
| } |
| |
| /* Scale function of calls in NODE by ratio ORIG_COUNT/NODE->count. */ |
| |
| void |
| scale_ipa_profile_for_fn (struct cgraph_node *node, profile_count orig_count) |
| { |
| profile_count to = node->count; |
| profile_count::adjust_for_ipa_scaling (&to, &orig_count); |
| struct cgraph_edge *e; |
| |
| for (e = node->callees; e; e = e->next_callee) |
| e->count = e->count.apply_scale (to, orig_count); |
| for (e = node->indirect_calls; e; e = e->next_callee) |
| e->count = e->count.apply_scale (to, orig_count); |
| } |
| |
| /* SRC and DST are going to be merged. Take SRC's profile and merge it into |
| DST so it is not going to be lost. Possibly destroy SRC's body on the way |
| unless PRESERVE_BODY is set. */ |
| |
| void |
| ipa_merge_profiles (struct cgraph_node *dst, |
| struct cgraph_node *src, |
| bool preserve_body) |
| { |
| tree oldsrcdecl = src->decl; |
| struct function *srccfun, *dstcfun; |
| bool match = true; |
| bool copy_counts = false; |
| |
| if (!src->definition |
| || !dst->definition) |
| return; |
| |
| if (src->frequency < dst->frequency) |
| src->frequency = dst->frequency; |
| |
| /* Time profiles are merged. */ |
| if (dst->tp_first_run > src->tp_first_run && src->tp_first_run) |
| dst->tp_first_run = src->tp_first_run; |
| |
| if (src->profile_id && !dst->profile_id) |
| dst->profile_id = src->profile_id; |
| |
| /* Merging zero profile to dst is no-op. */ |
| if (src->count.ipa () == profile_count::zero ()) |
| return; |
| |
| /* FIXME when we merge in unknown profile, we ought to set counts as |
| unsafe. */ |
| if (!src->count.initialized_p () |
| || !(src->count.ipa () == src->count)) |
| return; |
| profile_count orig_count = dst->count; |
| |
| /* Either sum the profiles if both are IPA and not global0, or |
| pick more informative one (that is nonzero IPA if other is |
| uninitialized, guessed or global0). */ |
| |
| if ((dst->count.ipa ().nonzero_p () |
| || src->count.ipa ().nonzero_p ()) |
| && dst->count.ipa ().initialized_p () |
| && src->count.ipa ().initialized_p ()) |
| dst->count = dst->count.ipa () + src->count.ipa (); |
| else if (dst->count.ipa ().initialized_p ()) |
| ; |
| else if (src->count.ipa ().initialized_p ()) |
| { |
| copy_counts = true; |
| dst->count = src->count.ipa (); |
| } |
| |
| /* If no updating needed return early. */ |
| if (dst->count == orig_count) |
| return; |
| |
| if (symtab->dump_file) |
| { |
| fprintf (symtab->dump_file, "Merging profiles of %s count:", |
| src->dump_name ()); |
| src->count.dump (symtab->dump_file); |
| fprintf (symtab->dump_file, " to %s count:", |
| dst->dump_name ()); |
| orig_count.dump (symtab->dump_file); |
| fprintf (symtab->dump_file, " resulting count:"); |
| dst->count.dump (symtab->dump_file); |
| fprintf (symtab->dump_file, "\n"); |
| } |
| |
| /* First handle functions with no gimple body. */ |
| if (dst->thunk || dst->alias |
| || src->thunk || src->alias) |
| { |
| scale_ipa_profile_for_fn (dst, orig_count); |
| return; |
| } |
| |
| /* This is ugly. We need to get both function bodies into memory. |
| If declaration is merged, we need to duplicate it to be able |
| to load body that is being replaced. This makes symbol table |
| temporarily inconsistent. */ |
| if (src->decl == dst->decl) |
| { |
| struct lto_in_decl_state temp; |
| struct lto_in_decl_state *state; |
| |
| /* We are going to move the decl, we want to remove its file decl data. |
| and link these with the new decl. */ |
| temp.fn_decl = src->decl; |
| lto_in_decl_state **slot |
| = src->lto_file_data->function_decl_states->find_slot (&temp, |
| NO_INSERT); |
| state = *slot; |
| src->lto_file_data->function_decl_states->clear_slot (slot); |
| gcc_assert (state); |
| |
| /* Duplicate the decl and be sure it does not link into body of DST. */ |
| src->decl = copy_node (src->decl); |
| DECL_STRUCT_FUNCTION (src->decl) = NULL; |
| DECL_ARGUMENTS (src->decl) = NULL; |
| DECL_INITIAL (src->decl) = NULL; |
| DECL_RESULT (src->decl) = NULL; |
| |
| /* Associate the decl state with new declaration, so LTO streamer |
| can look it up. */ |
| state->fn_decl = src->decl; |
| slot |
| = src->lto_file_data->function_decl_states->find_slot (state, INSERT); |
| gcc_assert (!*slot); |
| *slot = state; |
| } |
| src->get_untransformed_body (); |
| dst->get_untransformed_body (); |
| srccfun = DECL_STRUCT_FUNCTION (src->decl); |
| dstcfun = DECL_STRUCT_FUNCTION (dst->decl); |
| if (n_basic_blocks_for_fn (srccfun) |
| != n_basic_blocks_for_fn (dstcfun)) |
| { |
| if (symtab->dump_file) |
| fprintf (symtab->dump_file, |
| "Giving up; number of basic block mismatch.\n"); |
| match = false; |
| } |
| else if (last_basic_block_for_fn (srccfun) |
| != last_basic_block_for_fn (dstcfun)) |
| { |
| if (symtab->dump_file) |
| fprintf (symtab->dump_file, |
| "Giving up; last block mismatch.\n"); |
| match = false; |
| } |
| else |
| { |
| basic_block srcbb, dstbb; |
| struct cgraph_edge *e, *e2; |
| |
| for (e = dst->callees, e2 = src->callees; e && e2 && match; |
| e2 = e2->next_callee, e = e->next_callee) |
| { |
| if (gimple_bb (e->call_stmt)->index |
| != gimple_bb (e2->call_stmt)->index) |
| { |
| if (symtab->dump_file) |
| fprintf (symtab->dump_file, |
| "Giving up; call stmt mismatch.\n"); |
| match = false; |
| } |
| } |
| if (e || e2) |
| { |
| if (symtab->dump_file) |
| fprintf (symtab->dump_file, |
| "Giving up; number of calls differs.\n"); |
| match = false; |
| } |
| for (e = dst->indirect_calls, e2 = src->indirect_calls; e && e2 && match; |
| e2 = e2->next_callee, e = e->next_callee) |
| { |
| if (gimple_bb (e->call_stmt)->index |
| != gimple_bb (e2->call_stmt)->index) |
| { |
| if (symtab->dump_file) |
| fprintf (symtab->dump_file, |
| "Giving up; indirect call stmt mismatch.\n"); |
| match = false; |
| } |
| } |
| if (e || e2) |
| { |
| if (symtab->dump_file) |
| fprintf (symtab->dump_file, |
| "Giving up; number of indirect calls differs.\n"); |
| match=false; |
| } |
| |
| if (match) |
| FOR_ALL_BB_FN (srcbb, srccfun) |
| { |
| unsigned int i; |
| |
| dstbb = BASIC_BLOCK_FOR_FN (dstcfun, srcbb->index); |
| if (dstbb == NULL) |
| { |
| if (symtab->dump_file) |
| fprintf (symtab->dump_file, |
| "No matching block for bb %i.\n", |
| srcbb->index); |
| match = false; |
| break; |
| } |
| if (EDGE_COUNT (srcbb->succs) != EDGE_COUNT (dstbb->succs)) |
| { |
| if (symtab->dump_file) |
| fprintf (symtab->dump_file, |
| "Edge count mismatch for bb %i.\n", |
| srcbb->index); |
| match = false; |
| break; |
| } |
| for (i = 0; i < EDGE_COUNT (srcbb->succs); i++) |
| { |
| edge srce = EDGE_SUCC (srcbb, i); |
| edge dste = EDGE_SUCC (dstbb, i); |
| if (srce->dest->index != dste->dest->index) |
| { |
| if (symtab->dump_file) |
| fprintf (symtab->dump_file, |
| "Succ edge mismatch for bb %i.\n", |
| srce->dest->index); |
| match = false; |
| break; |
| } |
| } |
| } |
| } |
| if (match) |
| { |
| struct cgraph_edge *e, *e2; |
| basic_block srcbb, dstbb; |
| |
| /* Function and global profile may be out of sync. First scale it same |
| way as fixup_cfg would. */ |
| profile_count srcnum = src->count; |
| profile_count srcden = ENTRY_BLOCK_PTR_FOR_FN (srccfun)->count; |
| bool srcscale = srcnum.initialized_p () && !(srcnum == srcden); |
| profile_count dstnum = orig_count; |
| profile_count dstden = ENTRY_BLOCK_PTR_FOR_FN (dstcfun)->count; |
| bool dstscale = !copy_counts |
| && dstnum.initialized_p () && !(dstnum == dstden); |
| |
| /* TODO: merge also statement histograms. */ |
| FOR_ALL_BB_FN (srcbb, srccfun) |
| { |
| unsigned int i; |
| |
| dstbb = BASIC_BLOCK_FOR_FN (dstcfun, srcbb->index); |
| |
| profile_count srccount = srcbb->count; |
| if (srcscale) |
| srccount = srccount.apply_scale (srcnum, srcden); |
| if (dstscale) |
| dstbb->count = dstbb->count.apply_scale (dstnum, dstden); |
| |
| if (copy_counts) |
| { |
| dstbb->count = srccount; |
| for (i = 0; i < EDGE_COUNT (srcbb->succs); i++) |
| { |
| edge srce = EDGE_SUCC (srcbb, i); |
| edge dste = EDGE_SUCC (dstbb, i); |
| if (srce->probability.initialized_p ()) |
| dste->probability = srce->probability; |
| } |
| } |
| else |
| { |
| for (i = 0; i < EDGE_COUNT (srcbb->succs); i++) |
| { |
| edge srce = EDGE_SUCC (srcbb, i); |
| edge dste = EDGE_SUCC (dstbb, i); |
| dste->probability = |
| dste->probability * dstbb->count.ipa ().probability_in |
| (dstbb->count.ipa () |
| + srccount.ipa ()) |
| + srce->probability * srcbb->count.ipa ().probability_in |
| (dstbb->count.ipa () |
| + srccount.ipa ()); |
| } |
| dstbb->count = dstbb->count.ipa () + srccount.ipa (); |
| } |
| } |
| push_cfun (dstcfun); |
| update_max_bb_count (); |
| compute_function_frequency (); |
| pop_cfun (); |
| for (e = dst->callees; e; e = e->next_callee) |
| { |
| if (e->speculative) |
| continue; |
| e->count = gimple_bb (e->call_stmt)->count; |
| } |
| for (e = dst->indirect_calls, e2 = src->indirect_calls; e; |
| e2 = (e2 ? e2->next_callee : NULL), e = e->next_callee) |
| { |
| if (!e->speculative && !e2->speculative) |
| { |
| /* FIXME: we need to also merge ipa-profile histograms |
| because with LTO merging happens from lto-symtab before |
| these are converted to indirect edges. */ |
| e->count = gimple_bb (e->call_stmt)->count; |
| continue; |
| } |
| |
| /* When copying just remove all speuclations on dst and then copy |
| one from src. */ |
| if (copy_counts) |
| { |
| while (e->speculative) |
| cgraph_edge::resolve_speculation (e, NULL); |
| e->count = gimple_bb (e->call_stmt)->count; |
| if (e2->speculative) |
| { |
| for (cgraph_edge *e3 = e2->first_speculative_call_target (); |
| e3; |
| e3 = e3->next_speculative_call_target ()) |
| { |
| cgraph_edge *ns; |
| ns = e->make_speculative |
| (dyn_cast <cgraph_node *> |
| (e3->speculative_call_target_ref ()->referred), |
| e3->count, e3->speculative_id); |
| /* Target may differ from ref (for example it may be |
| redirected to local alias. */ |
| ns->redirect_callee (e3->callee); |
| } |
| } |
| continue; |
| } |
| |
| /* Iterate all speculations in SRC, see if corresponding ones exist |
| int DST and if so, sum the counts. Otherwise create new |
| speculation. */ |
| int max_spec = 0; |
| for (cgraph_edge *e3 = e->first_speculative_call_target (); |
| e3; |
| e3 = e3->next_speculative_call_target ()) |
| if (e3->speculative_id > max_spec) |
| max_spec = e3->speculative_id; |
| for (cgraph_edge *e3 = e2->first_speculative_call_target (); |
| e3; |
| e3 = e3->next_speculative_call_target ()) |
| { |
| cgraph_edge *te |
| = e->speculative_call_for_target |
| (dyn_cast <cgraph_node *> |
| (e3->speculative_call_target_ref ()->referred)); |
| if (te) |
| te->count = te->count + e3->count; |
| else |
| { |
| e->count = e->count + e3->count; |
| cgraph_edge *ns; |
| ns = e->make_speculative |
| (dyn_cast <cgraph_node *> |
| (e3->speculative_call_target_ref () |
| ->referred), |
| e3->count, |
| e3->speculative_id + max_spec + 1); |
| /* Target may differ from ref (for example it may be |
| redirected to local alias. */ |
| ns->redirect_callee (e3->callee); |
| } |
| } |
| } |
| if (!preserve_body) |
| src->release_body (); |
| /* Update summary. */ |
| compute_fn_summary (dst, 0); |
| } |
| /* We can't update CFG profile, but we can scale IPA profile. CFG |
| will be scaled according to dst->count after IPA passes. */ |
| else |
| scale_ipa_profile_for_fn (dst, orig_count); |
| src->decl = oldsrcdecl; |
| } |
| |
| /* Return true if call to DEST is known to be self-recusive |
| call withing FUNC. */ |
| |
| bool |
| recursive_call_p (tree func, tree dest) |
| { |
| struct cgraph_node *dest_node = cgraph_node::get_create (dest); |
| struct cgraph_node *cnode = cgraph_node::get_create (func); |
| ipa_ref *alias; |
| enum availability avail; |
| |
| gcc_assert (!cnode->alias); |
| if (cnode != dest_node->ultimate_alias_target (&avail)) |
| return false; |
| if (avail >= AVAIL_AVAILABLE) |
| return true; |
| if (!dest_node->semantically_equivalent_p (cnode)) |
| return false; |
| /* If there is only one way to call the fuction or we know all of them |
| are semantically equivalent, we still can consider call recursive. */ |
| FOR_EACH_ALIAS (cnode, alias) |
| if (!dest_node->semantically_equivalent_p (alias->referring)) |
| return false; |
| return true; |
| } |