| /* Basic IPA optimizations based on profile. |
| Copyright (C) 2003-2021 Free Software Foundation, Inc. |
| |
| 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/>. */ |
| |
| /* ipa-profile pass implements the following analysis propagating profille |
| inter-procedurally. |
| |
| - Count histogram construction. This is a histogram analyzing how much |
| time is spent executing statements with a given execution count read |
| from profile feedback. This histogram is complete only with LTO, |
| otherwise it contains information only about the current unit. |
| |
| The information is used to set hot/cold thresholds. |
| - Next speculative indirect call resolution is performed: the local |
| profile pass assigns profile-id to each function and provide us with a |
| histogram specifying the most common target. We look up the callgraph |
| node corresponding to the target and produce a speculative call. |
| |
| This call may or may not survive through IPA optimization based on decision |
| of inliner. |
| - Finally we propagate the following flags: unlikely executed, executed |
| once, executed at startup and executed at exit. These flags are used to |
| control code size/performance threshold and code placement (by producing |
| .text.unlikely/.text.hot/.text.startup/.text.exit subsections). */ |
| #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 "tree-pass.h" |
| #include "cgraph.h" |
| #include "data-streamer.h" |
| #include "gimple-iterator.h" |
| #include "ipa-utils.h" |
| #include "profile.h" |
| #include "value-prof.h" |
| #include "tree-inline.h" |
| #include "symbol-summary.h" |
| #include "tree-vrp.h" |
| #include "ipa-prop.h" |
| #include "ipa-fnsummary.h" |
| |
| /* Entry in the histogram. */ |
| |
| struct histogram_entry |
| { |
| gcov_type count; |
| int time; |
| int size; |
| }; |
| |
| /* Histogram of profile values. |
| The histogram is represented as an ordered vector of entries allocated via |
| histogram_pool. During construction a separate hashtable is kept to lookup |
| duplicate entries. */ |
| |
| vec<histogram_entry *> histogram; |
| static object_allocator<histogram_entry> histogram_pool ("IPA histogram"); |
| |
| /* Hashtable support for storing SSA names hashed by their SSA_NAME_VAR. */ |
| |
| struct histogram_hash : nofree_ptr_hash <histogram_entry> |
| { |
| static inline hashval_t hash (const histogram_entry *); |
| static inline int equal (const histogram_entry *, const histogram_entry *); |
| }; |
| |
| inline hashval_t |
| histogram_hash::hash (const histogram_entry *val) |
| { |
| return val->count; |
| } |
| |
| inline int |
| histogram_hash::equal (const histogram_entry *val, const histogram_entry *val2) |
| { |
| return val->count == val2->count; |
| } |
| |
| /* Account TIME and SIZE executed COUNT times into HISTOGRAM. |
| HASHTABLE is the on-side hash kept to avoid duplicates. */ |
| |
| static void |
| account_time_size (hash_table<histogram_hash> *hashtable, |
| vec<histogram_entry *> &histogram, |
| gcov_type count, int time, int size) |
| { |
| histogram_entry key = {count, 0, 0}; |
| histogram_entry **val = hashtable->find_slot (&key, INSERT); |
| |
| if (!*val) |
| { |
| *val = histogram_pool.allocate (); |
| **val = key; |
| histogram.safe_push (*val); |
| } |
| (*val)->time += time; |
| (*val)->size += size; |
| } |
| |
| int |
| cmp_counts (const void *v1, const void *v2) |
| { |
| const histogram_entry *h1 = *(const histogram_entry * const *)v1; |
| const histogram_entry *h2 = *(const histogram_entry * const *)v2; |
| if (h1->count < h2->count) |
| return 1; |
| if (h1->count > h2->count) |
| return -1; |
| return 0; |
| } |
| |
| /* Dump HISTOGRAM to FILE. */ |
| |
| static void |
| dump_histogram (FILE *file, vec<histogram_entry *> histogram) |
| { |
| unsigned int i; |
| gcov_type overall_time = 0, cumulated_time = 0, cumulated_size = 0, |
| overall_size = 0; |
| |
| fprintf (dump_file, "Histogram:\n"); |
| for (i = 0; i < histogram.length (); i++) |
| { |
| overall_time += histogram[i]->count * histogram[i]->time; |
| overall_size += histogram[i]->size; |
| } |
| if (!overall_time) |
| overall_time = 1; |
| if (!overall_size) |
| overall_size = 1; |
| for (i = 0; i < histogram.length (); i++) |
| { |
| cumulated_time += histogram[i]->count * histogram[i]->time; |
| cumulated_size += histogram[i]->size; |
| fprintf (file, " %" PRId64": time:%i (%2.2f) size:%i (%2.2f)\n", |
| (int64_t) histogram[i]->count, |
| histogram[i]->time, |
| cumulated_time * 100.0 / overall_time, |
| histogram[i]->size, |
| cumulated_size * 100.0 / overall_size); |
| } |
| } |
| |
| /* Structure containing speculative target information from profile. */ |
| |
| struct speculative_call_target |
| { |
| speculative_call_target (unsigned int id = 0, int prob = 0) |
| : target_id (id), target_probability (prob) |
| { |
| } |
| |
| /* Profile_id of target obtained from profile. */ |
| unsigned int target_id; |
| /* Probability that call will land in function with target_id. */ |
| unsigned int target_probability; |
| }; |
| |
| class speculative_call_summary |
| { |
| public: |
| speculative_call_summary () : speculative_call_targets () |
| {} |
| |
| auto_vec<speculative_call_target> speculative_call_targets; |
| |
| void dump (FILE *f); |
| |
| }; |
| |
| /* Class to manage call summaries. */ |
| |
| class ipa_profile_call_summaries |
| : public call_summary<speculative_call_summary *> |
| { |
| public: |
| ipa_profile_call_summaries (symbol_table *table) |
| : call_summary<speculative_call_summary *> (table) |
| {} |
| |
| /* Duplicate info when an edge is cloned. */ |
| virtual void duplicate (cgraph_edge *, cgraph_edge *, |
| speculative_call_summary *old_sum, |
| speculative_call_summary *new_sum); |
| }; |
| |
| static ipa_profile_call_summaries *call_sums = NULL; |
| |
| /* Dump all information in speculative call summary to F. */ |
| |
| void |
| speculative_call_summary::dump (FILE *f) |
| { |
| cgraph_node *n2; |
| |
| unsigned spec_count = speculative_call_targets.length (); |
| for (unsigned i = 0; i < spec_count; i++) |
| { |
| speculative_call_target item = speculative_call_targets[i]; |
| n2 = find_func_by_profile_id (item.target_id); |
| if (n2) |
| fprintf (f, " The %i speculative target is %s with prob %3.2f\n", i, |
| n2->dump_name (), |
| item.target_probability / (float) REG_BR_PROB_BASE); |
| else |
| fprintf (f, " The %i speculative target is %u with prob %3.2f\n", i, |
| item.target_id, |
| item.target_probability / (float) REG_BR_PROB_BASE); |
| } |
| } |
| |
| /* Duplicate info when an edge is cloned. */ |
| |
| void |
| ipa_profile_call_summaries::duplicate (cgraph_edge *, cgraph_edge *, |
| speculative_call_summary *old_sum, |
| speculative_call_summary *new_sum) |
| { |
| if (!old_sum) |
| return; |
| |
| unsigned old_count = old_sum->speculative_call_targets.length (); |
| if (!old_count) |
| return; |
| |
| new_sum->speculative_call_targets.reserve_exact (old_count); |
| new_sum->speculative_call_targets.quick_grow_cleared (old_count); |
| |
| for (unsigned i = 0; i < old_count; i++) |
| { |
| new_sum->speculative_call_targets[i] |
| = old_sum->speculative_call_targets[i]; |
| } |
| } |
| |
| /* Collect histogram and speculative target summaries from CFG profiles. */ |
| |
| static void |
| ipa_profile_generate_summary (void) |
| { |
| struct cgraph_node *node; |
| gimple_stmt_iterator gsi; |
| basic_block bb; |
| |
| hash_table<histogram_hash> hashtable (10); |
| |
| gcc_checking_assert (!call_sums); |
| call_sums = new ipa_profile_call_summaries (symtab); |
| |
| FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node) |
| if (ENTRY_BLOCK_PTR_FOR_FN |
| (DECL_STRUCT_FUNCTION (node->decl))->count.ipa_p ()) |
| FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl)) |
| { |
| int time = 0; |
| int size = 0; |
| for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi)) |
| { |
| gimple *stmt = gsi_stmt (gsi); |
| if (gimple_code (stmt) == GIMPLE_CALL |
| && !gimple_call_fndecl (stmt)) |
| { |
| histogram_value h; |
| h = gimple_histogram_value_of_type |
| (DECL_STRUCT_FUNCTION (node->decl), |
| stmt, HIST_TYPE_INDIR_CALL); |
| /* No need to do sanity check: gimple_ic_transform already |
| takes away bad histograms. */ |
| if (h) |
| { |
| gcov_type val, count, all; |
| struct cgraph_edge *e = node->get_edge (stmt); |
| if (e && !e->indirect_unknown_callee) |
| continue; |
| |
| speculative_call_summary *csum |
| = call_sums->get_create (e); |
| |
| for (unsigned j = 0; j < GCOV_TOPN_MAXIMUM_TRACKED_VALUES; |
| j++) |
| { |
| if (!get_nth_most_common_value (NULL, "indirect call", |
| h, &val, &count, &all, |
| j)) |
| continue; |
| |
| if (val == 0 || count == 0) |
| continue; |
| |
| if (count > all) |
| { |
| if (dump_file) |
| fprintf (dump_file, |
| "Probability capped to 1\n"); |
| count = all; |
| } |
| speculative_call_target item ( |
| val, GCOV_COMPUTE_SCALE (count, all)); |
| csum->speculative_call_targets.safe_push (item); |
| } |
| |
| gimple_remove_histogram_value |
| (DECL_STRUCT_FUNCTION (node->decl), stmt, h); |
| } |
| } |
| time += estimate_num_insns (stmt, &eni_time_weights); |
| size += estimate_num_insns (stmt, &eni_size_weights); |
| } |
| if (bb->count.ipa_p () && bb->count.initialized_p ()) |
| account_time_size (&hashtable, histogram, |
| bb->count.ipa ().to_gcov_type (), |
| time, size); |
| } |
| histogram.qsort (cmp_counts); |
| } |
| |
| /* Serialize the speculative summary info for LTO. */ |
| |
| static void |
| ipa_profile_write_edge_summary (lto_simple_output_block *ob, |
| speculative_call_summary *csum) |
| { |
| unsigned len = 0; |
| |
| len = csum->speculative_call_targets.length (); |
| |
| gcc_assert (len <= GCOV_TOPN_MAXIMUM_TRACKED_VALUES); |
| |
| streamer_write_hwi_stream (ob->main_stream, len); |
| |
| if (len) |
| { |
| unsigned spec_count = csum->speculative_call_targets.length (); |
| for (unsigned i = 0; i < spec_count; i++) |
| { |
| speculative_call_target item = csum->speculative_call_targets[i]; |
| gcc_assert (item.target_id); |
| streamer_write_hwi_stream (ob->main_stream, item.target_id); |
| streamer_write_hwi_stream (ob->main_stream, item.target_probability); |
| } |
| } |
| } |
| |
| /* Serialize the ipa info for lto. */ |
| |
| static void |
| ipa_profile_write_summary (void) |
| { |
| struct lto_simple_output_block *ob |
| = lto_create_simple_output_block (LTO_section_ipa_profile); |
| unsigned int i; |
| |
| streamer_write_uhwi_stream (ob->main_stream, histogram.length ()); |
| for (i = 0; i < histogram.length (); i++) |
| { |
| streamer_write_gcov_count_stream (ob->main_stream, histogram[i]->count); |
| streamer_write_uhwi_stream (ob->main_stream, histogram[i]->time); |
| streamer_write_uhwi_stream (ob->main_stream, histogram[i]->size); |
| } |
| |
| if (!call_sums) |
| return; |
| |
| /* Serialize speculative targets information. */ |
| unsigned int count = 0; |
| lto_symtab_encoder_t encoder = ob->decl_state->symtab_node_encoder; |
| lto_symtab_encoder_iterator lsei; |
| cgraph_node *node; |
| |
| for (lsei = lsei_start_function_in_partition (encoder); !lsei_end_p (lsei); |
| lsei_next_function_in_partition (&lsei)) |
| { |
| node = lsei_cgraph_node (lsei); |
| if (node->definition && node->has_gimple_body_p () |
| && node->indirect_calls) |
| count++; |
| } |
| |
| streamer_write_uhwi_stream (ob->main_stream, count); |
| |
| /* Process all of the functions. */ |
| for (lsei = lsei_start_function_in_partition (encoder); |
| !lsei_end_p (lsei) && count; lsei_next_function_in_partition (&lsei)) |
| { |
| cgraph_node *node = lsei_cgraph_node (lsei); |
| if (node->definition && node->has_gimple_body_p () |
| && node->indirect_calls) |
| { |
| int node_ref = lto_symtab_encoder_encode (encoder, node); |
| streamer_write_uhwi_stream (ob->main_stream, node_ref); |
| |
| for (cgraph_edge *e = node->indirect_calls; e; e = e->next_callee) |
| { |
| speculative_call_summary *csum = call_sums->get_create (e); |
| ipa_profile_write_edge_summary (ob, csum); |
| } |
| } |
| } |
| |
| lto_destroy_simple_output_block (ob); |
| } |
| |
| /* Dump all profile summary data for all cgraph nodes and edges to file F. */ |
| |
| static void |
| ipa_profile_dump_all_summaries (FILE *f) |
| { |
| fprintf (dump_file, |
| "\n========== IPA-profile speculative targets: ==========\n"); |
| cgraph_node *node; |
| FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node) |
| { |
| fprintf (f, "\nSummary for node %s:\n", node->dump_name ()); |
| for (cgraph_edge *e = node->indirect_calls; e; e = e->next_callee) |
| { |
| fprintf (f, " Summary for %s of indirect edge %d:\n", |
| e->caller->dump_name (), e->lto_stmt_uid); |
| speculative_call_summary *csum = call_sums->get_create (e); |
| csum->dump (f); |
| } |
| } |
| fprintf (f, "\n\n"); |
| } |
| |
| /* Read speculative targets information about edge for LTO WPA. */ |
| |
| static void |
| ipa_profile_read_edge_summary (class lto_input_block *ib, cgraph_edge *edge) |
| { |
| unsigned i, len; |
| |
| len = streamer_read_hwi (ib); |
| gcc_assert (len <= GCOV_TOPN_MAXIMUM_TRACKED_VALUES); |
| speculative_call_summary *csum = call_sums->get_create (edge); |
| |
| for (i = 0; i < len; i++) |
| { |
| unsigned int target_id = streamer_read_hwi (ib); |
| int target_probability = streamer_read_hwi (ib); |
| speculative_call_target item (target_id, target_probability); |
| csum->speculative_call_targets.safe_push (item); |
| } |
| } |
| |
| /* Read profile speculative targets section information for LTO WPA. */ |
| |
| static void |
| ipa_profile_read_summary_section (struct lto_file_decl_data *file_data, |
| class lto_input_block *ib) |
| { |
| if (!ib) |
| return; |
| |
| lto_symtab_encoder_t encoder = file_data->symtab_node_encoder; |
| |
| unsigned int count = streamer_read_uhwi (ib); |
| |
| unsigned int i; |
| unsigned int index; |
| cgraph_node * node; |
| |
| for (i = 0; i < count; i++) |
| { |
| index = streamer_read_uhwi (ib); |
| encoder = file_data->symtab_node_encoder; |
| node |
| = dyn_cast<cgraph_node *> (lto_symtab_encoder_deref (encoder, index)); |
| |
| for (cgraph_edge *e = node->indirect_calls; e; e = e->next_callee) |
| ipa_profile_read_edge_summary (ib, e); |
| } |
| } |
| |
| /* Deserialize the IPA histogram and speculative targets summary info for LTO. |
| */ |
| |
| static void |
| ipa_profile_read_summary (void) |
| { |
| struct lto_file_decl_data ** file_data_vec |
| = lto_get_file_decl_data (); |
| struct lto_file_decl_data * file_data; |
| int j = 0; |
| |
| hash_table<histogram_hash> hashtable (10); |
| |
| gcc_checking_assert (!call_sums); |
| call_sums = new ipa_profile_call_summaries (symtab); |
| |
| while ((file_data = file_data_vec[j++])) |
| { |
| const char *data; |
| size_t len; |
| class lto_input_block *ib |
| = lto_create_simple_input_block (file_data, |
| LTO_section_ipa_profile, |
| &data, &len); |
| if (ib) |
| { |
| unsigned int num = streamer_read_uhwi (ib); |
| unsigned int n; |
| for (n = 0; n < num; n++) |
| { |
| gcov_type count = streamer_read_gcov_count (ib); |
| int time = streamer_read_uhwi (ib); |
| int size = streamer_read_uhwi (ib); |
| account_time_size (&hashtable, histogram, |
| count, time, size); |
| } |
| |
| ipa_profile_read_summary_section (file_data, ib); |
| |
| lto_destroy_simple_input_block (file_data, |
| LTO_section_ipa_profile, |
| ib, data, len); |
| } |
| } |
| histogram.qsort (cmp_counts); |
| } |
| |
| /* Data used by ipa_propagate_frequency. */ |
| |
| struct ipa_propagate_frequency_data |
| { |
| cgraph_node *function_symbol; |
| bool maybe_unlikely_executed; |
| bool maybe_executed_once; |
| bool only_called_at_startup; |
| bool only_called_at_exit; |
| }; |
| |
| /* Worker for ipa_propagate_frequency_1. */ |
| |
| static bool |
| ipa_propagate_frequency_1 (struct cgraph_node *node, void *data) |
| { |
| struct ipa_propagate_frequency_data *d; |
| struct cgraph_edge *edge; |
| |
| d = (struct ipa_propagate_frequency_data *)data; |
| for (edge = node->callers; |
| edge && (d->maybe_unlikely_executed || d->maybe_executed_once |
| || d->only_called_at_startup || d->only_called_at_exit); |
| edge = edge->next_caller) |
| { |
| if (edge->caller != d->function_symbol) |
| { |
| d->only_called_at_startup &= edge->caller->only_called_at_startup; |
| /* It makes sense to put main() together with the static constructors. |
| It will be executed for sure, but rest of functions called from |
| main are definitely not at startup only. */ |
| if (MAIN_NAME_P (DECL_NAME (edge->caller->decl))) |
| d->only_called_at_startup = 0; |
| d->only_called_at_exit &= edge->caller->only_called_at_exit; |
| } |
| |
| /* When profile feedback is available, do not try to propagate too hard; |
| counts are already good guide on function frequencies and roundoff |
| errors can make us to push function into unlikely section even when |
| it is executed by the train run. Transfer the function only if all |
| callers are unlikely executed. */ |
| if (profile_info |
| && !(edge->callee->count.ipa () == profile_count::zero ()) |
| && (edge->caller->frequency != NODE_FREQUENCY_UNLIKELY_EXECUTED |
| || (edge->caller->inlined_to |
| && edge->caller->inlined_to->frequency |
| != NODE_FREQUENCY_UNLIKELY_EXECUTED))) |
| d->maybe_unlikely_executed = false; |
| if (edge->count.ipa ().initialized_p () |
| && !edge->count.ipa ().nonzero_p ()) |
| continue; |
| switch (edge->caller->frequency) |
| { |
| case NODE_FREQUENCY_UNLIKELY_EXECUTED: |
| break; |
| case NODE_FREQUENCY_EXECUTED_ONCE: |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, " Called by %s that is executed once\n", |
| edge->caller->dump_name ()); |
| d->maybe_unlikely_executed = false; |
| ipa_call_summary *s = ipa_call_summaries->get (edge); |
| if (s != NULL && s->loop_depth) |
| { |
| d->maybe_executed_once = false; |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, " Called in loop\n"); |
| } |
| break; |
| } |
| case NODE_FREQUENCY_HOT: |
| case NODE_FREQUENCY_NORMAL: |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, " Called by %s that is normal or hot\n", |
| edge->caller->dump_name ()); |
| d->maybe_unlikely_executed = false; |
| d->maybe_executed_once = false; |
| break; |
| } |
| } |
| return edge != NULL; |
| } |
| |
| /* Return ture if NODE contains hot calls. */ |
| |
| bool |
| contains_hot_call_p (struct cgraph_node *node) |
| { |
| struct cgraph_edge *e; |
| for (e = node->callees; e; e = e->next_callee) |
| if (e->maybe_hot_p ()) |
| return true; |
| else if (!e->inline_failed |
| && contains_hot_call_p (e->callee)) |
| return true; |
| for (e = node->indirect_calls; e; e = e->next_callee) |
| if (e->maybe_hot_p ()) |
| return true; |
| return false; |
| } |
| |
| /* See if the frequency of NODE can be updated based on frequencies of its |
| callers. */ |
| bool |
| ipa_propagate_frequency (struct cgraph_node *node) |
| { |
| struct ipa_propagate_frequency_data d = {node, true, true, true, true}; |
| bool changed = false; |
| |
| /* We cannot propagate anything useful about externally visible functions |
| nor about virtuals. */ |
| if (!node->local |
| || node->alias |
| || (opt_for_fn (node->decl, flag_devirtualize) |
| && DECL_VIRTUAL_P (node->decl))) |
| return false; |
| gcc_assert (node->analyzed); |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, "Processing frequency %s\n", node->dump_name ()); |
| |
| node->call_for_symbol_and_aliases (ipa_propagate_frequency_1, &d, |
| true); |
| |
| if ((d.only_called_at_startup && !d.only_called_at_exit) |
| && !node->only_called_at_startup) |
| { |
| node->only_called_at_startup = true; |
| if (dump_file) |
| fprintf (dump_file, "Node %s promoted to only called at startup.\n", |
| node->dump_name ()); |
| changed = true; |
| } |
| if ((d.only_called_at_exit && !d.only_called_at_startup) |
| && !node->only_called_at_exit) |
| { |
| node->only_called_at_exit = true; |
| if (dump_file) |
| fprintf (dump_file, "Node %s promoted to only called at exit.\n", |
| node->dump_name ()); |
| changed = true; |
| } |
| |
| /* With profile we can decide on hot/normal based on count. */ |
| if (node->count. ipa().initialized_p ()) |
| { |
| bool hot = false; |
| if (!(node->count. ipa() == profile_count::zero ()) |
| && node->count. ipa() >= get_hot_bb_threshold ()) |
| hot = true; |
| if (!hot) |
| hot |= contains_hot_call_p (node); |
| if (hot) |
| { |
| if (node->frequency != NODE_FREQUENCY_HOT) |
| { |
| if (dump_file) |
| fprintf (dump_file, "Node %s promoted to hot.\n", |
| node->dump_name ()); |
| node->frequency = NODE_FREQUENCY_HOT; |
| return true; |
| } |
| return false; |
| } |
| else if (node->frequency == NODE_FREQUENCY_HOT) |
| { |
| if (dump_file) |
| fprintf (dump_file, "Node %s reduced to normal.\n", |
| node->dump_name ()); |
| node->frequency = NODE_FREQUENCY_NORMAL; |
| changed = true; |
| } |
| } |
| /* These come either from profile or user hints; never update them. */ |
| if (node->frequency == NODE_FREQUENCY_HOT |
| || node->frequency == NODE_FREQUENCY_UNLIKELY_EXECUTED) |
| return changed; |
| if (d.maybe_unlikely_executed) |
| { |
| node->frequency = NODE_FREQUENCY_UNLIKELY_EXECUTED; |
| if (dump_file) |
| fprintf (dump_file, "Node %s promoted to unlikely executed.\n", |
| node->dump_name ()); |
| changed = true; |
| } |
| else if (d.maybe_executed_once && node->frequency != NODE_FREQUENCY_EXECUTED_ONCE) |
| { |
| node->frequency = NODE_FREQUENCY_EXECUTED_ONCE; |
| if (dump_file) |
| fprintf (dump_file, "Node %s promoted to executed once.\n", |
| node->dump_name ()); |
| changed = true; |
| } |
| return changed; |
| } |
| |
| /* Check that number of arguments of N agrees with E. |
| Be conservative when summaries are not present. */ |
| |
| static bool |
| check_argument_count (struct cgraph_node *n, struct cgraph_edge *e) |
| { |
| if (!ipa_node_params_sum || !ipa_edge_args_sum) |
| return true; |
| class ipa_node_params *info = IPA_NODE_REF (n->function_symbol ()); |
| if (!info) |
| return true; |
| ipa_edge_args *e_info = IPA_EDGE_REF (e); |
| if (!e_info) |
| return true; |
| if (ipa_get_param_count (info) != ipa_get_cs_argument_count (e_info) |
| && (ipa_get_param_count (info) >= ipa_get_cs_argument_count (e_info) |
| || !stdarg_p (TREE_TYPE (n->decl)))) |
| return false; |
| return true; |
| } |
| |
| /* Simple ipa profile pass propagating frequencies across the callgraph. */ |
| |
| static unsigned int |
| ipa_profile (void) |
| { |
| struct cgraph_node **order; |
| struct cgraph_edge *e; |
| int order_pos; |
| bool something_changed = false; |
| int i; |
| gcov_type overall_time = 0, cutoff = 0, cumulated = 0, overall_size = 0; |
| struct cgraph_node *n,*n2; |
| int nindirect = 0, ncommon = 0, nunknown = 0, nuseless = 0, nconverted = 0; |
| int nmismatch = 0, nimpossible = 0; |
| bool node_map_initialized = false; |
| gcov_type threshold; |
| |
| if (dump_file) |
| dump_histogram (dump_file, histogram); |
| for (i = 0; i < (int)histogram.length (); i++) |
| { |
| overall_time += histogram[i]->count * histogram[i]->time; |
| overall_size += histogram[i]->size; |
| } |
| threshold = 0; |
| if (overall_time) |
| { |
| gcc_assert (overall_size); |
| |
| cutoff = (overall_time * param_hot_bb_count_ws_permille + 500) / 1000; |
| for (i = 0; cumulated < cutoff; i++) |
| { |
| cumulated += histogram[i]->count * histogram[i]->time; |
| threshold = histogram[i]->count; |
| } |
| if (!threshold) |
| threshold = 1; |
| if (dump_file) |
| { |
| gcov_type cumulated_time = 0, cumulated_size = 0; |
| |
| for (i = 0; |
| i < (int)histogram.length () && histogram[i]->count >= threshold; |
| i++) |
| { |
| cumulated_time += histogram[i]->count * histogram[i]->time; |
| cumulated_size += histogram[i]->size; |
| } |
| fprintf (dump_file, "Determined min count: %" PRId64 |
| " Time:%3.2f%% Size:%3.2f%%\n", |
| (int64_t)threshold, |
| cumulated_time * 100.0 / overall_time, |
| cumulated_size * 100.0 / overall_size); |
| } |
| |
| if (in_lto_p) |
| { |
| if (dump_file) |
| fprintf (dump_file, "Setting hotness threshold in LTO mode.\n"); |
| set_hot_bb_threshold (threshold); |
| } |
| } |
| histogram.release (); |
| histogram_pool.release (); |
| |
| /* Produce speculative calls: we saved common target from profiling into |
| e->target_id. Now, at link time, we can look up corresponding |
| function node and produce speculative call. */ |
| |
| gcc_checking_assert (call_sums); |
| |
| if (dump_file) |
| { |
| if (!node_map_initialized) |
| init_node_map (false); |
| node_map_initialized = true; |
| |
| ipa_profile_dump_all_summaries (dump_file); |
| } |
| |
| FOR_EACH_DEFINED_FUNCTION (n) |
| { |
| bool update = false; |
| |
| if (!opt_for_fn (n->decl, flag_ipa_profile)) |
| continue; |
| |
| for (e = n->indirect_calls; e; e = e->next_callee) |
| { |
| if (n->count.initialized_p ()) |
| nindirect++; |
| |
| speculative_call_summary *csum = call_sums->get_create (e); |
| unsigned spec_count = csum->speculative_call_targets.length (); |
| if (spec_count) |
| { |
| if (!node_map_initialized) |
| init_node_map (false); |
| node_map_initialized = true; |
| ncommon++; |
| |
| if (in_lto_p) |
| { |
| if (dump_file) |
| { |
| fprintf (dump_file, |
| "Updating hotness threshold in LTO mode.\n"); |
| fprintf (dump_file, "Updated min count: %" PRId64 "\n", |
| (int64_t) threshold / spec_count); |
| } |
| set_hot_bb_threshold (threshold / spec_count); |
| } |
| |
| unsigned speculative_id = 0; |
| profile_count orig = e->count; |
| for (unsigned i = 0; i < spec_count; i++) |
| { |
| speculative_call_target item |
| = csum->speculative_call_targets[i]; |
| n2 = find_func_by_profile_id (item.target_id); |
| if (n2) |
| { |
| if (dump_file) |
| { |
| fprintf (dump_file, |
| "Indirect call -> direct call from" |
| " other module %s => %s, prob %3.2f\n", |
| n->dump_name (), |
| n2->dump_name (), |
| item.target_probability |
| / (float) REG_BR_PROB_BASE); |
| } |
| if (item.target_probability < REG_BR_PROB_BASE / 2) |
| { |
| nuseless++; |
| if (dump_file) |
| fprintf (dump_file, |
| "Not speculating: " |
| "probability is too low.\n"); |
| } |
| else if (!e->maybe_hot_p ()) |
| { |
| nuseless++; |
| if (dump_file) |
| fprintf (dump_file, |
| "Not speculating: call is cold.\n"); |
| } |
| else if (n2->get_availability () <= AVAIL_INTERPOSABLE |
| && n2->can_be_discarded_p ()) |
| { |
| nuseless++; |
| if (dump_file) |
| fprintf (dump_file, |
| "Not speculating: target is overwritable " |
| "and can be discarded.\n"); |
| } |
| else if (!check_argument_count (n2, e)) |
| { |
| nmismatch++; |
| if (dump_file) |
| fprintf (dump_file, |
| "Not speculating: " |
| "parameter count mismatch\n"); |
| } |
| else if (e->indirect_info->polymorphic |
| && !opt_for_fn (n->decl, flag_devirtualize) |
| && !possible_polymorphic_call_target_p (e, n2)) |
| { |
| nimpossible++; |
| if (dump_file) |
| fprintf (dump_file, |
| "Not speculating: " |
| "function is not in the polymorphic " |
| "call target list\n"); |
| } |
| else |
| { |
| /* Target may be overwritable, but profile says that |
| control flow goes to this particular implementation |
| of N2. Speculate on the local alias to allow |
| inlining. */ |
| if (!n2->can_be_discarded_p ()) |
| { |
| cgraph_node *alias; |
| alias = dyn_cast<cgraph_node *> |
| (n2->noninterposable_alias ()); |
| if (alias) |
| n2 = alias; |
| } |
| nconverted++; |
| profile_probability prob |
| = profile_probability::from_reg_br_prob_base |
| (item.target_probability).adjusted (); |
| e->make_speculative (n2, |
| orig.apply_probability (prob), |
| speculative_id); |
| update = true; |
| speculative_id++; |
| } |
| } |
| else |
| { |
| if (dump_file) |
| fprintf (dump_file, |
| "Function with profile-id %i not found.\n", |
| item.target_id); |
| nunknown++; |
| } |
| } |
| } |
| } |
| if (update) |
| ipa_update_overall_fn_summary (n); |
| } |
| if (node_map_initialized) |
| del_node_map (); |
| if (dump_file && nindirect) |
| fprintf (dump_file, |
| "%i indirect calls trained.\n" |
| "%i (%3.2f%%) have common target.\n" |
| "%i (%3.2f%%) targets was not found.\n" |
| "%i (%3.2f%%) targets had parameter count mismatch.\n" |
| "%i (%3.2f%%) targets was not in polymorphic call target list.\n" |
| "%i (%3.2f%%) speculations seems useless.\n" |
| "%i (%3.2f%%) speculations produced.\n", |
| nindirect, |
| ncommon, ncommon * 100.0 / nindirect, |
| nunknown, nunknown * 100.0 / nindirect, |
| nmismatch, nmismatch * 100.0 / nindirect, |
| nimpossible, nimpossible * 100.0 / nindirect, |
| nuseless, nuseless * 100.0 / nindirect, |
| nconverted, nconverted * 100.0 / nindirect); |
| |
| order = XCNEWVEC (struct cgraph_node *, symtab->cgraph_count); |
| order_pos = ipa_reverse_postorder (order); |
| for (i = order_pos - 1; i >= 0; i--) |
| { |
| if (order[i]->local |
| && opt_for_fn (order[i]->decl, flag_ipa_profile) |
| && ipa_propagate_frequency (order[i])) |
| { |
| for (e = order[i]->callees; e; e = e->next_callee) |
| if (e->callee->local && !e->callee->aux) |
| { |
| something_changed = true; |
| e->callee->aux = (void *)1; |
| } |
| } |
| order[i]->aux = NULL; |
| } |
| |
| while (something_changed) |
| { |
| something_changed = false; |
| for (i = order_pos - 1; i >= 0; i--) |
| { |
| if (order[i]->aux |
| && opt_for_fn (order[i]->decl, flag_ipa_profile) |
| && ipa_propagate_frequency (order[i])) |
| { |
| for (e = order[i]->callees; e; e = e->next_callee) |
| if (e->callee->local && !e->callee->aux) |
| { |
| something_changed = true; |
| e->callee->aux = (void *)1; |
| } |
| } |
| order[i]->aux = NULL; |
| } |
| } |
| free (order); |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| symtab->dump (dump_file); |
| |
| delete call_sums; |
| call_sums = NULL; |
| |
| return 0; |
| } |
| |
| namespace { |
| |
| const pass_data pass_data_ipa_profile = |
| { |
| IPA_PASS, /* type */ |
| "profile_estimate", /* name */ |
| OPTGROUP_NONE, /* optinfo_flags */ |
| TV_IPA_PROFILE, /* tv_id */ |
| 0, /* properties_required */ |
| 0, /* properties_provided */ |
| 0, /* properties_destroyed */ |
| 0, /* todo_flags_start */ |
| 0, /* todo_flags_finish */ |
| }; |
| |
| class pass_ipa_profile : public ipa_opt_pass_d |
| { |
| public: |
| pass_ipa_profile (gcc::context *ctxt) |
| : ipa_opt_pass_d (pass_data_ipa_profile, ctxt, |
| ipa_profile_generate_summary, /* generate_summary */ |
| ipa_profile_write_summary, /* write_summary */ |
| ipa_profile_read_summary, /* read_summary */ |
| NULL, /* write_optimization_summary */ |
| NULL, /* read_optimization_summary */ |
| NULL, /* stmt_fixup */ |
| 0, /* function_transform_todo_flags_start */ |
| NULL, /* function_transform */ |
| NULL) /* variable_transform */ |
| {} |
| |
| /* opt_pass methods: */ |
| virtual bool gate (function *) { return flag_ipa_profile || in_lto_p; } |
| virtual unsigned int execute (function *) { return ipa_profile (); } |
| |
| }; // class pass_ipa_profile |
| |
| } // anon namespace |
| |
| ipa_opt_pass_d * |
| make_pass_ipa_profile (gcc::context *ctxt) |
| { |
| return new pass_ipa_profile (ctxt); |
| } |