Hi, this patch makes modref to also collect info whether function has side effects. This allows pure/const function detection and also handling functions which do store some memory in similar way as we handle pure/consts now.
The code is symmetric to what ipa-pure-const does. Modref is actually more capable on proving that a given function is pure/const (since it understands that non-pure function can be called when it only modifies data on stack) so we could retire ipa-pure-const's pure-const discovery at some point. However this patch only does the anlaysis - the consumers of this flag will come next. Bootstrapped/regtested x86_64-linux. I plan to commit it later today if there are no complains. gcc/ChangeLog: * ipa-modref.c: Include tree-eh.h (modref_summary::modref_summary): Initialize side_effects. (struct modref_summary_lto): New bool field side_effects. (modref_summary_lto::modref_summary_lto): Initialize side_effects. (modref_summary::dump): Dump side_effects. (modref_summary_lto::dump): Dump side_effects. (merge_call_side_effects): Merge side effects. (process_fnspec): Calls to non-const/pure or looping function is a side effect. (analyze_call): Self-recursion is a side-effect; handle special builtins. (analyze_load): Watch for volatile and throwing memory. (analyze_store): Likewise. (analyze_stmt): Watch for volatitle asm. (analyze_function): Handle side_effects. (modref_summaries::duplicate): Duplicate side_effects. (modref_summaries_lto::duplicate): Likewise. (modref_write): Stream side_effects. (read_section): Likewise. (update_signature): Update. (propagate_unknown_call): Handle side_effects. (modref_propagate_in_scc): Likewise. * ipa-modref.h (struct modref_summary): Add side_effects. * ipa-pure-const.c (special_builtin_state): Rename to ... (builtin_safe_for_const_function_p): ... this one. (check_call): Update. (finite_function_p): Break out from ... (propagate_pure_const): ... here * ipa-utils.h (finite_function): Declare. diff --git a/gcc/ipa-modref.c b/gcc/ipa-modref.c index 22efc06c583..d14f9e52f62 100644 --- a/gcc/ipa-modref.c +++ b/gcc/ipa-modref.c @@ -87,6 +87,7 @@ along with GCC; see the file COPYING3. If not see #include "tree-ssanames.h" #include "attribs.h" #include "tree-cfg.h" +#include "tree-eh.h" namespace { @@ -273,7 +274,7 @@ static GTY(()) fast_function_summary <modref_summary_lto *, va_gc> modref_summary::modref_summary () : loads (NULL), stores (NULL), retslot_flags (0), static_chain_flags (0), - writes_errno (false) + writes_errno (false), side_effects (false) { } @@ -371,6 +372,7 @@ struct GTY(()) modref_summary_lto eaf_flags_t retslot_flags; eaf_flags_t static_chain_flags; bool writes_errno; + bool side_effects; modref_summary_lto (); ~modref_summary_lto (); @@ -382,7 +384,7 @@ struct GTY(()) modref_summary_lto modref_summary_lto::modref_summary_lto () : loads (NULL), stores (NULL), retslot_flags (0), static_chain_flags (0), - writes_errno (false) + writes_errno (false), side_effects (false) { } @@ -615,6 +617,8 @@ modref_summary::dump (FILE *out) } if (writes_errno) fprintf (out, " Writes errno\n"); + if (side_effects) + fprintf (out, " Side effects\n"); if (arg_flags.length ()) { for (unsigned int i = 0; i < arg_flags.length (); i++) @@ -647,6 +651,8 @@ modref_summary_lto::dump (FILE *out) dump_lto_records (stores, out); if (writes_errno) fprintf (out, " Writes errno\n"); + if (side_effects) + fprintf (out, " Side effects\n"); if (arg_flags.length ()) { for (unsigned int i = 0; i < arg_flags.length (); i++) @@ -980,6 +986,12 @@ merge_call_side_effects (modref_summary *cur_summary, changed = true; } } + if (!cur_summary->side_effects + && callee_summary->side_effects) + { + cur_summary->side_effects = true; + changed = true; + } return changed; } @@ -1075,6 +1087,18 @@ process_fnspec (modref_summary *cur_summary, gcall *call, bool ignore_stores) { attr_fnspec fnspec = gimple_call_fnspec (call); + int flags = gimple_call_flags (call); + + if (!(flags & (ECF_CONST | ECF_NOVOPS)) + || (flags & ECF_LOOPING_CONST_OR_PURE) + || (cfun->can_throw_non_call_exceptions + && stmt_could_throw_p (cfun, call))) + { + if (cur_summary) + cur_summary->side_effects = true; + if (cur_summary_lto) + cur_summary_lto->side_effects = true; + } if (!fnspec.known_p ()) { if (dump_file && gimple_call_builtin_p (call, BUILT_IN_NORMAL)) @@ -1212,6 +1236,10 @@ analyze_call (modref_summary *cur_summary, modref_summary_lto *cur_summary_lto, if (recursive_call_p (current_function_decl, callee)) { recursive_calls->safe_push (stmt); + if (cur_summary) + cur_summary->side_effects = true; + if (cur_summary_lto) + cur_summary_lto->side_effects = true; if (dump_file) fprintf (dump_file, " - Skipping recursive call.\n"); return true; @@ -1222,6 +1250,20 @@ analyze_call (modref_summary *cur_summary, modref_summary_lto *cur_summary_lto, /* Get the function symbol and its availability. */ enum availability avail; callee_node = callee_node->function_symbol (&avail); + bool looping; + if (builtin_safe_for_const_function_p (&looping, callee)) + { + if (looping) + { + if (cur_summary) + cur_summary->side_effects = true; + if (cur_summary_lto) + cur_summary_lto->side_effects = true; + } + if (dump_file) + fprintf (dump_file, " - Bulitin is safe for const.\n"); + return true; + } if (avail <= AVAIL_INTERPOSABLE) { if (dump_file) @@ -1268,6 +1310,18 @@ analyze_load (gimple *, tree, tree op, void *data) fprintf (dump_file, "\n"); } + if (TREE_THIS_VOLATILE (op) + || (cfun->can_throw_non_call_exceptions + && tree_could_throw_p (op))) + { + if (dump_file) + fprintf (dump_file, " (volatile or can throw; marking side effects) "); + if (summary) + summary->side_effects = true; + if (summary_lto) + summary_lto->side_effects = true; + } + if (!record_access_p (op)) return false; @@ -1296,6 +1350,18 @@ analyze_store (gimple *, tree, tree op, void *data) fprintf (dump_file, "\n"); } + if (TREE_THIS_VOLATILE (op) + || (cfun->can_throw_non_call_exceptions + && tree_could_throw_p (op))) + { + if (dump_file) + fprintf (dump_file, " (volatile or can throw; marking side effects) "); + if (summary) + summary->side_effects = true; + if (summary_lto) + summary_lto->side_effects = true; + } + if (!record_access_p (op)) return false; @@ -1332,6 +1398,15 @@ analyze_stmt (modref_summary *summary, modref_summary_lto *summary_lto, switch (gimple_code (stmt)) { case GIMPLE_ASM: + if (gimple_asm_volatile_p (as_a <gasm *> (stmt)) + || (cfun->can_throw_non_call_exceptions + && stmt_could_throw_p (cfun, stmt))) + { + if (summary) + summary->side_effects = true; + if (summary_lto) + summary_lto->side_effects = true; + } /* If the ASM statement does not read nor write memory, there's nothing to do. Otherwise just give up. */ if (!gimple_asm_clobbers_memory_p (as_a <gasm *> (stmt))) @@ -1363,7 +1438,14 @@ analyze_stmt (modref_summary *summary, modref_summary_lto *summary_lto, } return true; default: - /* Nothing to do for other types of statements. */ + if (cfun->can_throw_non_call_exceptions + && stmt_could_throw_p (cfun, stmt)) + { + if (summary) + summary->side_effects = true; + if (summary_lto) + summary_lto->side_effects = true; + } return true; } } @@ -2606,6 +2688,7 @@ analyze_function (function *f, bool ipa) param_modref_max_refs, param_modref_max_accesses); summary->writes_errno = false; + summary->side_effects = false; } if (lto) { @@ -2620,6 +2703,7 @@ analyze_function (function *f, bool ipa) param_modref_max_refs, param_modref_max_accesses); summary_lto->writes_errno = false; + summary_lto->side_effects = false; } analyze_parms (summary, summary_lto, ipa, @@ -2690,6 +2774,12 @@ analyze_function (function *f, bool ipa) summaries_lto->remove (fnode); summary_lto = NULL; } + if (summary && !summary->global_memory_written_p () && !summary->side_effects + && !finite_function_p ()) + summary->side_effects = true; + if (summary_lto && !summary_lto->side_effects && !finite_function_p ()) + summary_lto->side_effects = true; + if (ipa && !summary && !summary_lto) remove_modref_edge_summaries (fnode); @@ -2886,6 +2976,7 @@ modref_summaries::duplicate (cgraph_node *, cgraph_node *dst, src_data->loads->max_accesses); dst_data->loads->copy_from (src_data->loads); dst_data->writes_errno = src_data->writes_errno; + dst_data->side_effects = src_data->side_effects; if (src_data->arg_flags.length ()) dst_data->arg_flags = src_data->arg_flags.copy (); dst_data->retslot_flags = src_data->retslot_flags; @@ -2913,6 +3004,7 @@ modref_summaries_lto::duplicate (cgraph_node *, cgraph_node *, src_data->loads->max_accesses); dst_data->loads->copy_from (src_data->loads); dst_data->writes_errno = src_data->writes_errno; + dst_data->side_effects = src_data->side_effects; if (src_data->arg_flags.length ()) dst_data->arg_flags = src_data->arg_flags.copy (); dst_data->retslot_flags = src_data->retslot_flags; @@ -3241,6 +3333,7 @@ modref_write () struct bitpack_d bp = bitpack_create (ob->main_stream); bp_pack_value (&bp, r->writes_errno, 1); + bp_pack_value (&bp, r->side_effects, 1); if (!flag_wpa) { for (cgraph_edge *e = cnode->indirect_calls; @@ -3310,9 +3403,15 @@ read_section (struct lto_file_decl_data *file_data, const char *data, modref_sum = optimization_summaries->get_create (node); if (modref_sum) - modref_sum->writes_errno = false; + { + modref_sum->writes_errno = false; + modref_sum->side_effects = false; + } if (modref_sum_lto) - modref_sum_lto->writes_errno = false; + { + modref_sum_lto->writes_errno = false; + modref_sum_lto->side_effects = false; + } gcc_assert (!modref_sum || (!modref_sum->loads && !modref_sum->stores)); @@ -3357,6 +3456,13 @@ read_section (struct lto_file_decl_data *file_data, const char *data, if (modref_sum_lto) modref_sum_lto->writes_errno = true; } + if (bp_unpack_value (&bp, 1)) + { + if (modref_sum) + modref_sum->side_effects = true; + if (modref_sum_lto) + modref_sum_lto->side_effects = true; + } if (!flag_ltrans) { for (cgraph_edge *e = node->indirect_calls; e; e = e->next_callee) @@ -3505,7 +3611,7 @@ update_signature (struct cgraph_node *node) map.reserve (max + 1); for (i = 0; i <= max; i++) - map.quick_push (-1); + map.quick_push (MODREF_UNKNOWN_PARM); FOR_EACH_VEC_SAFE_ELT (info->param_adjustments->m_adj_params, i, p) { int idx = info->param_adjustments->get_original_index (i); @@ -3844,6 +3950,39 @@ propagate_unknown_call (cgraph_node *node, bool changed = false; class fnspec_summary *fnspec_sum = fnspec_summaries->get (e); auto_vec <modref_parm_map, 32> parm_map; + bool looping; + + if (e->callee + && builtin_safe_for_const_function_p (&looping, e->callee->decl)) + { + if (cur_summary && !cur_summary->side_effects) + { + cur_summary->side_effects = true; + changed = true; + } + if (cur_summary_lto && !cur_summary_lto->side_effects) + { + cur_summary_lto->side_effects = true; + changed = true; + } + return changed; + } + + if (!(ecf_flags & (ECF_CONST | ECF_NOVOPS)) + || (ecf_flags & ECF_LOOPING_CONST_OR_PURE)) + { + if (cur_summary && !cur_summary->side_effects) + { + cur_summary->side_effects = true; + changed = true; + } + if (cur_summary_lto && !cur_summary_lto->side_effects) + { + cur_summary_lto->side_effects = true; + changed = true; + } + } + if (fnspec_sum && compute_parm_map (e, &parm_map)) { @@ -4108,6 +4247,12 @@ modref_propagate_in_scc (cgraph_node *component_node) changed |= cur_summary->loads->merge (callee_summary->loads, &parm_map, &chain_map, !first); + if (!cur_summary->side_effects + && callee_summary->side_effects) + { + cur_summary->side_effects = true; + changed = true; + } if (!ignore_stores) { changed |= cur_summary->stores->merge @@ -4126,6 +4271,12 @@ modref_propagate_in_scc (cgraph_node *component_node) changed |= cur_summary_lto->loads->merge (callee_summary_lto->loads, &parm_map, &chain_map, !first); + if (!cur_summary_lto->side_effects + && callee_summary_lto->side_effects) + { + cur_summary_lto->side_effects = true; + changed = true; + } if (!ignore_stores) { changed |= cur_summary_lto->stores->merge diff --git a/gcc/ipa-modref.h b/gcc/ipa-modref.h index 20170a65ded..0c3c81d1d43 100644 --- a/gcc/ipa-modref.h +++ b/gcc/ipa-modref.h @@ -34,6 +34,7 @@ struct GTY(()) modref_summary eaf_flags_t retslot_flags; eaf_flags_t static_chain_flags; bool writes_errno; + bool side_effects; modref_summary (); ~modref_summary (); diff --git a/gcc/ipa-pure-const.c b/gcc/ipa-pure-const.c index e5048092939..505ed4f8a3b 100644 --- a/gcc/ipa-pure-const.c +++ b/gcc/ipa-pure-const.c @@ -506,11 +506,10 @@ worse_state (enum pure_const_state_e *state, bool *looping, *looping = MAX (*looping, looping2); } -/* Recognize special cases of builtins that are by themselves not pure or const +/* Recognize special cases of builtins that are by themselves not const but function using them is. */ -static bool -special_builtin_state (enum pure_const_state_e *state, bool *looping, - tree callee) +bool +builtin_safe_for_const_function_p (bool *looping, tree callee) { if (DECL_BUILT_IN_CLASS (callee) == BUILT_IN_NORMAL) switch (DECL_FUNCTION_CODE (callee)) @@ -532,11 +531,9 @@ special_builtin_state (enum pure_const_state_e *state, bool *looping, case BUILT_IN_DWARF_CFA: case BUILT_IN_RETURN_ADDRESS: *looping = false; - *state = IPA_CONST; return true; case BUILT_IN_PREFETCH: *looping = true; - *state = IPA_CONST; return true; default: break; @@ -594,17 +591,16 @@ check_call (funct_state local, gcall *call, bool ipa) graph. */ if (callee_t) { - enum pure_const_state_e call_state; bool call_looping; if (gimple_call_builtin_p (call, BUILT_IN_NORMAL) && !nonfreeing_call_p (call)) local->can_free = true; - if (special_builtin_state (&call_state, &call_looping, callee_t)) + if (builtin_safe_for_const_function_p (&call_looping, callee_t)) { worse_state (&local->pure_const_state, &local->looping, - call_state, call_looping, + IPA_CONST, call_looping, NULL, NULL); return; } @@ -1007,6 +1003,51 @@ malloc_candidate_p (function *fun, bool ipa) #undef DUMP_AND_RETURN +/* Return true if function is known to be finite. */ + +bool +finite_function_p () +{ + /* Const functions cannot have back edges (an + indication of possible infinite loop side + effect. */ + bool finite = true; + if (mark_dfs_back_edges ()) + { + /* Preheaders are needed for SCEV to work. + Simple latches and recorded exits improve chances that loop will + proved to be finite in testcases such as in loop-15.c + and loop-24.c */ + loop_optimizer_init (LOOPS_HAVE_PREHEADERS + | LOOPS_HAVE_SIMPLE_LATCHES + | LOOPS_HAVE_RECORDED_EXITS); + if (dump_file && (dump_flags & TDF_DETAILS)) + flow_loops_dump (dump_file, NULL, 0); + if (mark_irreducible_loops ()) + { + if (dump_file) + fprintf (dump_file, " has irreducible loops\n"); + finite = false; + } + else + { + scev_initialize (); + for (auto loop : loops_list (cfun, 0)) + if (!finite_loop_p (loop)) + { + if (dump_file) + fprintf (dump_file, " cannot prove finiteness of " + "loop %i\n", loop->num); + finite =false; + break; + } + scev_finalize (); + } + loop_optimizer_finalize (); + } + return finite; +} + /* This is the main routine for finding the reference patterns for global variables within a function FN. */ @@ -1065,45 +1106,10 @@ analyze_function (struct cgraph_node *fn, bool ipa) } end: - if (l->pure_const_state != IPA_NEITHER) - { - /* Const functions cannot have back edges (an - indication of possible infinite loop side - effect. */ - if (mark_dfs_back_edges ()) - { - /* Preheaders are needed for SCEV to work. - Simple latches and recorded exits improve chances that loop will - proved to be finite in testcases such as in loop-15.c - and loop-24.c */ - loop_optimizer_init (LOOPS_HAVE_PREHEADERS - | LOOPS_HAVE_SIMPLE_LATCHES - | LOOPS_HAVE_RECORDED_EXITS); - if (dump_file && (dump_flags & TDF_DETAILS)) - flow_loops_dump (dump_file, NULL, 0); - if (mark_irreducible_loops ()) - { - if (dump_file) - fprintf (dump_file, " has irreducible loops\n"); - l->looping = true; - } - else - { - scev_initialize (); - for (auto loop : loops_list (cfun, 0)) - if (!finite_loop_p (loop)) - { - if (dump_file) - fprintf (dump_file, " cannot prove finiteness of " - "loop %i\n", loop->num); - l->looping =true; - break; - } - scev_finalize (); - } - loop_optimizer_finalize (); - } - } + if (l->pure_const_state != IPA_NEITHER + && !l->looping + && !finite_function_p ()) + l->looping = true; if (dump_file && (dump_flags & TDF_DETAILS)) fprintf (dump_file, " checking previously known:"); @@ -1539,9 +1545,9 @@ propagate_pure_const (void) edge_looping = y_l->looping; } } - else if (special_builtin_state (&edge_state, &edge_looping, - y->decl)) - ; + else if (builtin_safe_for_const_function_p (&edge_looping, + y->decl)) + edge_state = IPA_CONST; else state_from_flags (&edge_state, &edge_looping, flags_from_decl_or_type (y->decl), diff --git a/gcc/ipa-utils.h b/gcc/ipa-utils.h index 3cfaf2d2737..824780f562a 100644 --- a/gcc/ipa-utils.h +++ b/gcc/ipa-utils.h @@ -47,6 +47,10 @@ void ipa_merge_profiles (struct cgraph_node *dst, struct cgraph_node *src, bool preserve_body = false); bool recursive_call_p (tree, tree); +/* In ipa-pure-const.c */ +bool finite_function_p (); +bool builtin_safe_for_const_function_p (bool *, tree); + /* In ipa-profile.c */ bool ipa_propagate_frequency (struct cgraph_node *node);