On Wed, Nov 12, 2014 at 02:49:30PM +0400, Maxim Ostapenko wrote: > We used this code for IPA propagation of nonfreeing_call_p. It implemented > with a separate pass, but it probably could be propagated in some existing > one. This analysis doesn't seem to be costly thought, we didn't see any > significant slowdown compiling big files.
Here it is rewritten using ipa-pure-const which is where Richard/Honza suggested it should be done in. I wonder if the nonfreeing_call_p function shouldn't be moved elsewhere though (suggestion where), so that gimple.c doesn't need the cgraph includes. In any case, bootstrapped/regtested on x86_64-linux and i686-linux. 2014-11-12 Jakub Jelinek <ja...@redhat.com> * ipa-pure-const.c (struct funct_state_d): Add can_free field. (varying_state): Add true for can_free. (check_call): For builtin or internal !nonfreeing_call_p set local->can_free. (check_stmt): For asm volatile and asm with "memory" set local->can_free. (analyze_function): Clear local->can_free initially, continue calling check_stmt until all flags are computed, dump can_free flag. (pure_const_write_summary): Write can_free flag. (pure_const_read_summary): Read it back. (propagate_can_free): New function. (pass_ipa_pure_const::execute): Call it. * cgraph.h (cgraph_node): Add nonfreeing_fn member. * gimple.c: Include ipa-ref.h, lto-streamer.h and cgraph.h. (nonfreeing_call_p): Return cgraph nonfreeing_fn flag if set. * cgraph.c (cgraph_node::dump): Dump nonfreeing_fn flag. * lto-cgraph.c (lto_output_node): Write nonfreeing_fn flag. (input_overwrite_node): Read it back. --- gcc/ipa-pure-const.c.jj 2014-11-12 18:32:56.351139726 +0100 +++ gcc/ipa-pure-const.c 2014-11-12 21:11:08.574354600 +0100 @@ -112,11 +112,15 @@ struct funct_state_d bool looping; bool can_throw; + + /* If function can call free, munmap or otherwise make previously + non-trapping memory accesses trapping. */ + bool can_free; }; /* State used when we know nothing about function. */ static struct funct_state_d varying_state - = { IPA_NEITHER, IPA_NEITHER, true, true, true }; + = { IPA_NEITHER, IPA_NEITHER, true, true, true, true }; typedef struct funct_state_d * funct_state; @@ -559,6 +563,10 @@ check_call (funct_state local, gimple ca 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)) { worse_state (&local->pure_const_state, &local->looping, @@ -589,6 +597,8 @@ check_call (funct_state local, gimple ca break; } } + else if (gimple_call_internal_p (call) && !nonfreeing_call_p (call)) + local->can_free = true; /* When not in IPA mode, we can still handle self recursion. */ if (!ipa && callee_t @@ -753,6 +763,7 @@ check_stmt (gimple_stmt_iterator *gsip, fprintf (dump_file, " memory asm clobber is not const/pure\n"); /* Abandon all hope, ye who enter here. */ local->pure_const_state = IPA_NEITHER; + local->can_free = true; } if (gimple_asm_volatile_p (stmt)) { @@ -761,6 +772,7 @@ check_stmt (gimple_stmt_iterator *gsip, /* Abandon all hope, ye who enter here. */ local->pure_const_state = IPA_NEITHER; local->looping = true; + local->can_free = true; } return; default: @@ -785,6 +797,7 @@ analyze_function (struct cgraph_node *fn l->looping_previously_known = true; l->looping = false; l->can_throw = false; + l->can_free = false; state_from_flags (&l->state_previously_known, &l->looping_previously_known, flags_from_decl_or_type (fn->decl), fn->cannot_return_p ()); @@ -815,7 +828,10 @@ analyze_function (struct cgraph_node *fn gsi_next (&gsi)) { check_stmt (&gsi, l, ipa); - if (l->pure_const_state == IPA_NEITHER && l->looping && l->can_throw) + if (l->pure_const_state == IPA_NEITHER + && l->looping + && l->can_throw + && l->can_free) goto end; } } @@ -882,6 +898,8 @@ end: fprintf (dump_file, "Function is locally const.\n"); if (l->pure_const_state == IPA_PURE) fprintf (dump_file, "Function is locally pure.\n"); + if (l->can_free) + fprintf (dump_file, "Function can locally free.\n"); } return l; } @@ -1021,6 +1039,7 @@ pure_const_write_summary (void) bp_pack_value (&bp, fs->looping_previously_known, 1); bp_pack_value (&bp, fs->looping, 1); bp_pack_value (&bp, fs->can_throw, 1); + bp_pack_value (&bp, fs->can_free, 1); streamer_write_bitpack (&bp); } } @@ -1080,6 +1099,7 @@ pure_const_read_summary (void) fs->looping_previously_known = bp_unpack_value (&bp, 1); fs->looping = bp_unpack_value (&bp, 1); fs->can_throw = bp_unpack_value (&bp, 1); + fs->can_free = bp_unpack_value (&bp, 1); if (dump_file) { int flags = flags_from_decl_or_type (node->decl); @@ -1102,6 +1122,8 @@ pure_const_read_summary (void) fprintf (dump_file," function is previously known looping\n"); if (fs->can_throw) fprintf (dump_file," function is locally throwing\n"); + if (fs->can_free) + fprintf (dump_file," function can locally free\n"); } } @@ -1510,6 +1532,82 @@ propagate_nothrow (void) free (order); } +/* Produce transitive closure over the callgraph and compute can_free + attributes. */ + +static void +propagate_can_free (void) +{ + struct cgraph_node *node; + struct cgraph_node *w; + struct cgraph_node **order + = XCNEWVEC (struct cgraph_node *, symtab->cgraph_count); + int order_pos; + int i; + struct ipa_dfs_info *w_info; + + order_pos = ipa_reduced_postorder (order, true, false, NULL); + if (dump_file) + { + cgraph_node::dump_cgraph (dump_file); + ipa_print_order (dump_file, "reduced", order, order_pos); + } + + /* Propagate the local information through the call graph to produce + the global information. All the nodes within a cycle will have + the same info so we collapse cycles first. Then we can do the + propagation in one pass from the leaves to the roots. */ + for (i = 0; i < order_pos; i++ ) + { + bool can_free = false; + node = order[i]; + + if (node->alias) + continue; + + /* Find the worst state for any node in the cycle. */ + w = node; + while (w && !can_free) + { + struct cgraph_edge *e; + funct_state w_l = get_function_state (w); + + if (w_l->can_free + || w->get_availability () == AVAIL_INTERPOSABLE + || w->indirect_calls) + can_free = true; + + for (e = w->callees; e && !can_free; e = e->next_callee) + { + enum availability avail; + struct cgraph_node *y = e->callee->function_symbol (&avail); + + if (avail > AVAIL_INTERPOSABLE) + can_free = get_function_state (y)->can_free; + else + can_free = true; + } + w_info = (struct ipa_dfs_info *) w->aux; + w = w_info->next_cycle; + } + + /* Copy back the region's can_free which is shared by + all nodes in the region. */ + w = node; + while (w) + { + funct_state w_l = get_function_state (w); + w_l->can_free = can_free; + w->nonfreeing_fn = !can_free; + w_info = (struct ipa_dfs_info *) w->aux; + w = w_info->next_cycle; + } + } + + ipa_free_postorder_info (); + free (order); +} + /* Produce the global information by preforming a transitive closure on the local information that was produced by generate_summary. */ @@ -1528,6 +1626,7 @@ execute (function *) later analysis. */ propagate_nothrow (); propagate_pure_const (); + propagate_can_free (); /* Cleanup. */ FOR_EACH_FUNCTION (node) --- gcc/cgraph.h.jj 2014-11-12 18:09:27.784014976 +0100 +++ gcc/cgraph.h 2014-11-12 21:05:53.851895859 +0100 @@ -1260,6 +1260,10 @@ public: /* True when function is clone created for Pointer Bounds Checker instrumentation. */ unsigned instrumentation_clone : 1; + /* True if call to node can't result in a call to free, munmap or + other operation that could make previously non-trapping memory + accesses trapping. */ + unsigned nonfreeing_fn : 1; }; /* A cgraph node set is a collection of cgraph nodes. A cgraph node --- gcc/gimple.c.jj 2014-11-12 21:01:52.532940986 +0100 +++ gcc/gimple.c 2014-11-12 21:05:53.849895526 +0100 @@ -58,6 +58,9 @@ along with GCC; see the file COPYING3. #include "bitmap.h" #include "stringpool.h" #include "tree-ssanames.h" +#include "ipa-ref.h" +#include "lto-streamer.h" +#include "cgraph.h" /* All the tuples have their operand vector (if present) at the very bottom @@ -2542,7 +2545,13 @@ nonfreeing_call_p (gimple call) && gimple_call_flags (call) & ECF_LEAF) return true; - return false; + tree fndecl = gimple_call_fndecl (call); + if (!fndecl) + return false; + struct cgraph_node *n = cgraph_node::get (fndecl); + if (!n || n->get_availability () <= AVAIL_INTERPOSABLE) + return false; + return n->nonfreeing_fn; } /* Callback for walk_stmt_load_store_ops. --- gcc/cgraph.c.jj 2014-11-11 00:05:42.000000000 +0100 +++ gcc/cgraph.c 2014-11-12 21:05:53.848895354 +0100 @@ -1986,6 +1986,8 @@ cgraph_node::dump (FILE *f) fprintf (f, " tm_clone"); if (icf_merged) fprintf (f, " icf_merged"); + if (nonfreeing_fn) + fprintf (f, " nonfreeing_fn"); if (DECL_STATIC_CONSTRUCTOR (decl)) fprintf (f," static_constructor (priority:%i)", get_init_priority ()); if (DECL_STATIC_DESTRUCTOR (decl)) --- gcc/lto-cgraph.c.jj 2014-11-11 00:06:11.000000000 +0100 +++ gcc/lto-cgraph.c 2014-11-12 21:05:53.846895004 +0100 @@ -549,6 +549,7 @@ lto_output_node (struct lto_simple_outpu bp_pack_value (&bp, node->tm_clone, 1); bp_pack_value (&bp, node->calls_comdat_local, 1); bp_pack_value (&bp, node->icf_merged, 1); + bp_pack_value (&bp, node->nonfreeing_fn, 1); bp_pack_value (&bp, node->thunk.thunk_p && !boundary_p, 1); bp_pack_enum (&bp, ld_plugin_symbol_resolution, LDPR_NUM_KNOWN, node->resolution); @@ -1097,6 +1098,7 @@ input_overwrite_node (struct lto_file_de node->tm_clone = bp_unpack_value (bp, 1); node->calls_comdat_local = bp_unpack_value (bp, 1); node->icf_merged = bp_unpack_value (bp, 1); + node->nonfreeing_fn = bp_unpack_value (bp, 1); node->thunk.thunk_p = bp_unpack_value (bp, 1); node->resolution = bp_unpack_enum (bp, ld_plugin_symbol_resolution, LDPR_NUM_KNOWN); Jakub