2017-10-13 Prathamesh Kulkarni * cgraph.h (set_malloc_flag): Declare. * cgraph.c (set_malloc_flag_1): New function. (set_malloc_flag): Likewise. * ipa-fnsummary.h (ipa_call_summary): Add new field is_return_callee. * ipa-fnsummary.c (ipa_call_summary::reset): Set is_return_callee to false. (read_ipa_call_summary): Add support for reading is_return_callee. (write_ipa_call_summary): Stream is_return_callee. * ipa-inline.c (ipa_inline): Remove call to ipa_free_fn_summary. * ipa-pure-const.c: Add headers ssa.h, alloc-pool.h, symbol-summary.h, ipa-prop.h, ipa-fnsummary.h. (pure_const_names): Change to static. (malloc_state_e): Define. (malloc_state_names): Define. (funct_state_d): Add field malloc_state. (varying_state): Set malloc_state to STATE_MALLOC_BOTTOM. (check_retval_uses): New function. (malloc_candidate_p): Likewise. (analyze_function): Add support for malloc attribute. (pure_const_write_summary): Stream malloc_state. (pure_const_read_summary): Add support for reading malloc_state. (dump_malloc_lattice): New function. (propagate_malloc): New function. (warn_function_malloc): New function. (ipa_pure_const::execute): Call propagate_malloc and ipa_free_fn_summary. (pass_local_pure_const::execute): Add support for malloc attribute. * ssa-iterators.h (RETURN_FROM_IMM_USE_STMT): New macro. * doc/invoke.texi: Document Wsuggest-attribute=malloc. testsuite/ * gcc.dg/ipa/propmalloc-1.c: New test-case. * gcc.dg/ipa/propmalloc-2.c: Likewise. * gcc.dg/ipa/propmalloc-3.c: Likewise. diff --git a/gcc/cgraph.c b/gcc/cgraph.c index 3d0cefbd46b..0aad90d59ea 100644 --- a/gcc/cgraph.c +++ b/gcc/cgraph.c @@ -2530,6 +2530,53 @@ cgraph_node::set_nothrow_flag (bool nothrow) return changed; } +/* Worker to set malloc flag. */ +static void +set_malloc_flag_1 (cgraph_node *node, bool malloc_p, bool *changed) +{ + if (malloc_p && !DECL_IS_MALLOC (node->decl)) + { + DECL_IS_MALLOC (node->decl) = true; + *changed = true; + } + + ipa_ref *ref; + FOR_EACH_ALIAS (node, ref) + { + cgraph_node *alias = dyn_cast (ref->referring); + if (!malloc_p || alias->get_availability () > AVAIL_INTERPOSABLE) + set_malloc_flag_1 (alias, malloc_p, changed); + } + + for (cgraph_edge *e = node->callers; e; e = e->next_caller) + if (e->caller->thunk.thunk_p + && (!malloc_p || e->caller->get_availability () > AVAIL_INTERPOSABLE)) + set_malloc_flag_1 (e->caller, malloc_p, changed); +} + +/* Set DECL_IS_MALLOC on NODE's decl and on NODE's aliases if any. */ + +bool +cgraph_node::set_malloc_flag (bool malloc_p) +{ + bool changed = false; + + if (!malloc_p || get_availability () > AVAIL_INTERPOSABLE) + set_malloc_flag_1 (this, malloc_p, &changed); + else + { + ipa_ref *ref; + + FOR_EACH_ALIAS (this, ref) + { + cgraph_node *alias = dyn_cast (ref->referring); + if (!malloc_p || alias->get_availability () > AVAIL_INTERPOSABLE) + set_malloc_flag_1 (alias, malloc_p, &changed); + } + } + return changed; +} + /* Worker to set_const_flag. */ static void diff --git a/gcc/cgraph.h b/gcc/cgraph.h index 1758e8b08c1..84824e9f814 100644 --- a/gcc/cgraph.h +++ b/gcc/cgraph.h @@ -1151,6 +1151,10 @@ public: if any to NOTHROW. */ bool set_nothrow_flag (bool nothrow); + /* SET DECL_IS_MALLOC on cgraph_node's decl and on aliases of the node + if any. */ + bool set_malloc_flag (bool malloc_p); + /* If SET_CONST is true, mark function, aliases and thunks to be ECF_CONST. If SET_CONST if false, clear the flag. diff --git a/gcc/common.opt b/gcc/common.opt index dfde6adba91..00dfc97c4cb 100644 --- a/gcc/common.opt +++ b/gcc/common.opt @@ -733,6 +733,10 @@ Wsuggest-attribute=noreturn Common Var(warn_suggest_attribute_noreturn) Warning Warn about functions which might be candidates for __attribute__((noreturn)). +Wsuggest-attribute=malloc +Common Var(warn_suggest_attribute_malloc) Warning +Warn about functions which might be candidates for __attribute__((malloc)). + Wsuggest-final-types Common Var(warn_suggest_final_types) Warning Warn about C++ polymorphic types where adding final keyword would improve code quality. diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index 5e39c0efeb9..97b891d6d69 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -315,7 +315,7 @@ Objective-C and Objective-C++ Dialects}. -Wstack-protector -Wstack-usage=@var{len} -Wstrict-aliasing @gol -Wstrict-aliasing=n -Wstrict-overflow -Wstrict-overflow=@var{n} @gol -Wstringop-overflow=@var{n} @gol --Wsuggest-attribute=@r{[}pure@r{|}const@r{|}noreturn@r{|}format@r{]} @gol +-Wsuggest-attribute=@r{[}pure@r{|}const@r{|}noreturn@r{|}format@r{|}malloc@r{]} @gol -Wsuggest-final-types @gol -Wsuggest-final-methods -Wsuggest-override @gol -Wmissing-format-attribute -Wsubobject-linkage @gol -Wswitch -Wswitch-bool -Wswitch-default -Wswitch-enum @gol @@ -5190,7 +5190,7 @@ whether to issue a warning. Similarly to @option{-Wstringop-overflow=3} this setting of the option may result in warnings for benign code. @end table -@item -Wsuggest-attribute=@r{[}pure@r{|}const@r{|}noreturn@r{|}format@r{]} +@item -Wsuggest-attribute=@r{[}pure@r{|}const@r{|}noreturn@r{|}format@r{|}malloc@r{]} @opindex Wsuggest-attribute= @opindex Wno-suggest-attribute= Warn for cases where adding an attribute may be beneficial. The @@ -5200,21 +5200,25 @@ attributes currently supported are listed below. @item -Wsuggest-attribute=pure @itemx -Wsuggest-attribute=const @itemx -Wsuggest-attribute=noreturn +@itemx -Wsuggest-attribute=malloc @opindex Wsuggest-attribute=pure @opindex Wno-suggest-attribute=pure @opindex Wsuggest-attribute=const @opindex Wno-suggest-attribute=const @opindex Wsuggest-attribute=noreturn @opindex Wno-suggest-attribute=noreturn +@opindex Wsuggest-attribute=malloc +@opindex Wno-suggest-attribute=malloc Warn about functions that might be candidates for attributes -@code{pure}, @code{const} or @code{noreturn}. The compiler only warns for -functions visible in other compilation units or (in the case of @code{pure} and -@code{const}) if it cannot prove that the function returns normally. A function -returns normally if it doesn't contain an infinite loop or return abnormally -by throwing, calling @code{abort} or trapping. This analysis requires option -@option{-fipa-pure-const}, which is enabled by default at @option{-O} and -higher. Higher optimization levels improve the accuracy of the analysis. +@code{pure}, @code{const}, @code{noreturn} or @code{malloc}. The compiler only +warns for functions visible in other compilation units or (in the case of +@code{pure} and @code{const}) if it cannot prove that the function returns +normally. A function returns normally if it doesn't contain an infinite loop +or return abnormally by throwing, calling @code{abort} or trapping. This +analysis requires option @option{-fipa-pure-const}, which is enabled by default +at @option{-O} and higher. Higher optimization levels improve the accuracy of +the analysis. @item -Wsuggest-attribute=format @itemx -Wmissing-format-attribute diff --git a/gcc/ipa-fnsummary.c b/gcc/ipa-fnsummary.c index 076ccd40bd7..f71338e424e 100644 --- a/gcc/ipa-fnsummary.c +++ b/gcc/ipa-fnsummary.c @@ -542,6 +542,7 @@ void ipa_call_summary::reset () { call_stmt_size = call_stmt_time = 0; + is_return_callee_uncaptured = false; if (predicate) edge_predicate_pool.remove (predicate); predicate = NULL; @@ -3204,6 +3205,10 @@ read_ipa_call_summary (struct lto_input_block *ib, struct cgraph_edge *e) es->call_stmt_size = streamer_read_uhwi (ib); es->call_stmt_time = streamer_read_uhwi (ib); es->loop_depth = streamer_read_uhwi (ib); + + bitpack_d bp = streamer_read_bitpack (ib); + es->is_return_callee_uncaptured = bp_unpack_value (&bp, 1); + p.stream_in (ib); edge_set_predicate (e, &p); length = streamer_read_uhwi (ib); @@ -3360,6 +3365,11 @@ write_ipa_call_summary (struct output_block *ob, struct cgraph_edge *e) streamer_write_uhwi (ob, es->call_stmt_size); streamer_write_uhwi (ob, es->call_stmt_time); streamer_write_uhwi (ob, es->loop_depth); + + bitpack_d bp = bitpack_create (ob->main_stream); + bp_pack_value (&bp, es->is_return_callee_uncaptured, 1); + streamer_write_bitpack (&bp); + if (es->predicate) es->predicate->stream_out (ob); else diff --git a/gcc/ipa-fnsummary.h b/gcc/ipa-fnsummary.h index f50d6806e61..a794bd09318 100644 --- a/gcc/ipa-fnsummary.h +++ b/gcc/ipa-fnsummary.h @@ -197,7 +197,9 @@ struct ipa_call_summary int call_stmt_time; /* Depth of loop nest, 0 means no nesting. */ unsigned int loop_depth; - + /* Indicates whether the caller returns the value of it's callee. */ + bool is_return_callee_uncaptured; + /* Keep all field empty so summary dumping works during its computation. This is useful for debugging. */ ipa_call_summary () diff --git a/gcc/ipa-inline.c b/gcc/ipa-inline.c index dd46cb61362..b8e65e2fa7e 100644 --- a/gcc/ipa-inline.c +++ b/gcc/ipa-inline.c @@ -2526,9 +2526,6 @@ ipa_inline (void) if (dump_file) ipa_dump_fn_summaries (dump_file); - /* In WPA we use inline summaries for partitioning process. */ - if (!flag_wpa) - ipa_free_fn_summary (); return remove_functions ? TODO_remove_functions : 0; } diff --git a/gcc/ipa-pure-const.c b/gcc/ipa-pure-const.c index dac8f0d5f21..965fdd41975 100644 --- a/gcc/ipa-pure-const.c +++ b/gcc/ipa-pure-const.c @@ -56,6 +56,11 @@ along with GCC; see the file COPYING3. If not see #include "tree-scalar-evolution.h" #include "intl.h" #include "opts.h" +#include "ssa.h" +#include "alloc-pool.h" +#include "symbol-summary.h" +#include "ipa-prop.h" +#include "ipa-fnsummary.h" /* Lattice values for const and pure functions. Everything starts out being const, then may drop to pure and then neither depending on @@ -67,7 +72,16 @@ enum pure_const_state_e IPA_NEITHER }; -const char *pure_const_names[3] = {"const", "pure", "neither"}; +static const char *pure_const_names[3] = {"const", "pure", "neither"}; + +enum malloc_state_e +{ + STATE_MALLOC_TOP, + STATE_MALLOC, + STATE_MALLOC_BOTTOM +}; + +static const char *malloc_state_names[] = {"malloc_top", "malloc", "malloc_bottom"}; /* Holder for the const_state. There is one of these per function decl. */ @@ -92,11 +106,13 @@ struct funct_state_d /* If function can call free, munmap or otherwise make previously non-trapping memory accesses trapping. */ bool can_free; + + enum malloc_state_e malloc_state; }; /* State used when we know nothing about function. */ static struct funct_state_d varying_state - = { IPA_NEITHER, IPA_NEITHER, true, true, true, true }; + = { IPA_NEITHER, IPA_NEITHER, true, true, true, true, STATE_MALLOC_BOTTOM }; typedef struct funct_state_d * funct_state; @@ -215,6 +231,19 @@ warn_function_const (tree decl, bool known_finite) known_finite, warned_about, "const"); } +/* Emit suggestion about __attribute__((malloc)) for DECL. */ + +static void +warn_function_malloc (tree decl) +{ + static hash_set *warned_about; + warned_about + = suggest_attribute (OPT_Wsuggest_attribute_malloc, decl, + false, warned_about, "malloc"); +} + +/* Emit suggestion about __attribute__((noreturn)) for DECL. */ + static void warn_function_noreturn (tree decl) { @@ -812,6 +841,149 @@ check_stmt (gimple_stmt_iterator *gsip, funct_state local, bool ipa) } } +/* Check that RETVAL is used only in STMT and in comparisons against 0. + RETVAL is return value of the function and STMT is return stmt. */ + +static bool +check_retval_uses (tree retval, gimple *stmt) +{ + imm_use_iterator use_iter; + gimple *use_stmt; + + FOR_EACH_IMM_USE_STMT (use_stmt, use_iter, retval) + if (gcond *cond = dyn_cast (use_stmt)) + { + tree op2 = gimple_cond_rhs (cond); + if (!integer_zerop (op2)) + RETURN_FROM_IMM_USE_STMT (use_iter, false); + } + else if (gassign *ga = dyn_cast (use_stmt)) + { + enum tree_code code = gimple_assign_rhs_code (ga); + if (TREE_CODE_CLASS (code) != tcc_comparison) + RETURN_FROM_IMM_USE_STMT (use_iter, false); + if (!integer_zerop (gimple_assign_rhs2 (ga))) + RETURN_FROM_IMM_USE_STMT (use_iter, false); + } + else if (is_gimple_debug (use_stmt)) + ; + else if (use_stmt != stmt) + RETURN_FROM_IMM_USE_STMT (use_iter, false); + + return true; +} + +/* malloc_candidate_p() checks if FUN can possibly be annotated with malloc + attribute. Currently this function does a very conservative analysis. + FUN is considered to be a candidate if + 1) It returns a value of pointer type. + 2) SSA_NAME_DEF_STMT (return_value) is either a function call or + a phi, and element of phi is either NULL or + SSA_NAME_DEF_STMT(element) is function call. + 3) The return-value has immediate uses only within comparisons (gcond or gassign) + and return_stmt (and likewise a phi arg has immediate use only within comparison + or the phi stmt). */ + +static bool +malloc_candidate_p (function *fun, bool ipa) +{ + basic_block exit_block = EXIT_BLOCK_PTR_FOR_FN (fun); + edge e; + edge_iterator ei; + cgraph_node *node = cgraph_node::get_create (fun->decl); + +#define DUMP_AND_RETURN(reason) \ +{ \ + if (dump_file && (dump_flags & TDF_DETAILS)) \ + fprintf (dump_file, "%s", (reason)); \ + return false; \ +} + + if (EDGE_COUNT (exit_block->preds) == 0) + return false; + + FOR_EACH_EDGE (e, ei, exit_block->preds) + { + gimple_stmt_iterator gsi = gsi_last_bb (e->src); + greturn *ret_stmt = dyn_cast (gsi_stmt (gsi)); + + if (!ret_stmt) + return false; + + tree retval = gimple_return_retval (ret_stmt); + if (!retval) + DUMP_AND_RETURN("No return value.") + + if (TREE_CODE (retval) != SSA_NAME + || TREE_CODE (TREE_TYPE (retval)) != POINTER_TYPE) + DUMP_AND_RETURN("Return value is not SSA_NAME or not a pointer type.") + + if (!check_retval_uses (retval, ret_stmt)) + DUMP_AND_RETURN("Return value has uses outside return stmt" + " and comparisons against 0.") + + gimple *def = SSA_NAME_DEF_STMT (retval); + if (gcall *call_stmt = dyn_cast (def)) + { + tree callee_decl = gimple_call_fndecl (call_stmt); + if (!callee_decl) + return false; + + if (!ipa && !DECL_IS_MALLOC (callee_decl)) + DUMP_AND_RETURN("callee_decl does not have malloc attribute for" + " non-ipa mode.") + + cgraph_edge *cs = node->get_edge (call_stmt); + if (cs) + { + ipa_call_summary *es = ipa_call_summaries->get (cs); + gcc_assert (es); + es->is_return_callee_uncaptured = true; + } + } + + else if (gphi *phi = dyn_cast (def)) + for (unsigned i = 0; i < gimple_phi_num_args (phi); ++i) + { + tree arg = gimple_phi_arg_def (phi, i); + if (TREE_CODE (arg) != SSA_NAME) + DUMP_AND_RETURN("phi arg is not SSA_NAME.") + if (!(arg == null_pointer_node || check_retval_uses (arg, phi))) + DUMP_AND_RETURN("phi arg has uses outside phi" + " and comparisons against 0.") + + gimple *arg_def = SSA_NAME_DEF_STMT (arg); + gcall *call_stmt = dyn_cast (arg_def); + if (!call_stmt) + return false; + tree callee_decl = gimple_call_fndecl (call_stmt); + if (!callee_decl) + return false; + if (!ipa && !DECL_IS_MALLOC (callee_decl)) + DUMP_AND_RETURN("callee_decl does not have malloc attribute for" + " non-ipa mode.") + + cgraph_edge *cs = node->get_edge (call_stmt); + if (cs) + { + ipa_call_summary *es = ipa_call_summaries->get (cs); + gcc_assert (es); + es->is_return_callee_uncaptured = true; + } + } + + else + DUMP_AND_RETURN("def_stmt of return value is not a call or phi-stmt.") + } + + if (dump_file && (dump_flags & TDF_DETAILS)) + fprintf (dump_file, "\nFound %s to be candidate for malloc attribute\n", + IDENTIFIER_POINTER (DECL_NAME (fun->decl))); + return true; + +#undef DUMP_AND_RETURN +} + /* This is the main routine for finding the reference patterns for global variables within a function FN. */ @@ -921,6 +1093,14 @@ end: if (TREE_NOTHROW (decl)) l->can_throw = false; + l->malloc_state = STATE_MALLOC_BOTTOM; + if (DECL_IS_MALLOC (decl)) + l->malloc_state = STATE_MALLOC; + else if (ipa && malloc_candidate_p (DECL_STRUCT_FUNCTION (decl), true)) + l->malloc_state = STATE_MALLOC_TOP; + else if (malloc_candidate_p (DECL_STRUCT_FUNCTION (decl), false)) + l->malloc_state = STATE_MALLOC; + pop_cfun (); if (dump_file) { @@ -934,6 +1114,8 @@ end: fprintf (dump_file, "Function is locally pure.\n"); if (l->can_free) fprintf (dump_file, "Function can locally free.\n"); + if (l->malloc_state == STATE_MALLOC) + fprintf (dump_file, "Function is locally malloc.\n"); } return l; } @@ -1067,6 +1249,7 @@ pure_const_write_summary (void) bp_pack_value (&bp, fs->looping, 1); bp_pack_value (&bp, fs->can_throw, 1); bp_pack_value (&bp, fs->can_free, 1); + bp_pack_value (&bp, fs->malloc_state, 2); streamer_write_bitpack (&bp); } } @@ -1127,6 +1310,9 @@ pure_const_read_summary (void) fs->looping = bp_unpack_value (&bp, 1); fs->can_throw = bp_unpack_value (&bp, 1); fs->can_free = bp_unpack_value (&bp, 1); + fs->malloc_state + = (enum malloc_state_e) bp_unpack_value (&bp, 2); + if (dump_file) { int flags = flags_from_decl_or_type (node->decl); @@ -1149,6 +1335,8 @@ pure_const_read_summary (void) fprintf (dump_file," function is locally throwing\n"); if (fs->can_free) fprintf (dump_file," function can locally free\n"); + fprintf (dump_file, "\n malloc state: %s\n", + malloc_state_names[fs->malloc_state]); } } @@ -1659,6 +1847,131 @@ propagate_nothrow (void) free (order); } +/* Debugging function to dump state of malloc lattice. */ + +DEBUG_FUNCTION +static void +dump_malloc_lattice (FILE *dump_file, const char *s) +{ + if (!dump_file) + return; + + fprintf (dump_file, "\n\nMALLOC LATTICE %s:\n", s); + cgraph_node *node; + FOR_EACH_FUNCTION (node) + { + funct_state fs = get_function_state (node); + malloc_state_e state = fs->malloc_state; + fprintf (dump_file, "%s: %s\n", node->name (), malloc_state_names[state]); + } +} + +/* Propagate malloc attribute across the callgraph. */ + +static void +propagate_malloc (void) +{ + cgraph_node *node; + FOR_EACH_FUNCTION (node) + { + if (DECL_IS_MALLOC (node->decl)) + if (!has_function_state (node)) + { + funct_state l = XCNEW (struct funct_state_d); + *l = varying_state; + l->malloc_state = STATE_MALLOC; + set_function_state (node, l); + } + } + + dump_malloc_lattice (dump_file, "Initial"); + struct cgraph_node **order + = XNEWVEC (struct cgraph_node *, symtab->cgraph_count); + int order_pos = ipa_reverse_postorder (order); + bool changed = true; + + while (changed) + { + changed = false; + /* Walk in postorder. */ + for (int i = order_pos - 1; i >= 0; --i) + { + cgraph_node *node = order[i]; + if (node->alias + || !node->definition + || !has_function_state (node)) + continue; + + funct_state l = get_function_state (node); + + /* FIXME: add support for indirect-calls. */ + if (node->indirect_calls) + { + l->malloc_state = STATE_MALLOC_BOTTOM; + continue; + } + + if (node->get_availability () <= AVAIL_INTERPOSABLE) + { + l->malloc_state = STATE_MALLOC_BOTTOM; + continue; + } + + if (l->malloc_state == STATE_MALLOC_BOTTOM) + continue; + + vec callees = vNULL; + for (cgraph_edge *cs = node->callees; cs; cs = cs->next_callee) + { + ipa_call_summary *es = ipa_call_summaries->get (cs); + if (es && es->is_return_callee_uncaptured) + callees.safe_push (cs->callee); + } + + malloc_state_e new_state = l->malloc_state; + for (unsigned j = 0; j < callees.length (); j++) + { + cgraph_node *callee = callees[j]; + if (!has_function_state (callee)) + { + new_state = STATE_MALLOC_BOTTOM; + break; + } + malloc_state_e callee_state = get_function_state (callee)->malloc_state; + if (new_state < callee_state) + new_state = callee_state; + } + if (new_state != l->malloc_state) + { + changed = true; + l->malloc_state = new_state; + } + } + } + + FOR_EACH_DEFINED_FUNCTION (node) + if (has_function_state (node)) + { + funct_state l = get_function_state (node); + if (!node->alias + && l->malloc_state == STATE_MALLOC + && !node->global.inlined_to) + { + if (dump_file && (dump_flags & TDF_DETAILS)) + fprintf (dump_file, "Function %s found to be malloc\n", + node->name ()); + + bool malloc_decl_p = DECL_IS_MALLOC (node->decl); + node->set_malloc_flag (true); + if (!malloc_decl_p && warn_suggest_attribute_malloc) + warn_function_malloc (node->decl); + } + } + + dump_malloc_lattice (dump_file, "after propagation"); + 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. */ @@ -1677,6 +1990,7 @@ execute (function *) /* Nothrow makes more function to not lead to return and improve later analysis. */ propagate_nothrow (); + propagate_malloc (); remove_p = propagate_pure_const (); /* Cleanup. */ @@ -1684,6 +1998,10 @@ execute (function *) if (has_function_state (node)) free (get_function_state (node)); funct_state_vec.release (); + + /* In WPA we use inline summaries for partitioning process. */ + if (!flag_wpa) + ipa_free_fn_summary (); return remove_p ? TODO_remove_functions : 0; } @@ -1877,6 +2195,19 @@ pass_local_pure_const::execute (function *fun) fprintf (dump_file, "Function found to be nothrow: %s\n", current_function_name ()); } + + if (l->malloc_state == STATE_MALLOC + && !DECL_IS_MALLOC (current_function_decl)) + { + node->set_malloc_flag (true); + if (warn_suggest_attribute_malloc) + warn_function_malloc (node->decl); + changed = true; + if (dump_file) + fprintf (dump_file, "Function found to be malloc: %s\n", + node->name ()); + } + free (l); if (changed) return execute_fixup_cfg (); diff --git a/gcc/ssa-iterators.h b/gcc/ssa-iterators.h index c8aa77bd4f3..740cbf13cb2 100644 --- a/gcc/ssa-iterators.h +++ b/gcc/ssa-iterators.h @@ -93,6 +93,12 @@ struct imm_use_iterator break; \ } +/* Similarly for return. */ +#define RETURN_FROM_IMM_USE_STMT(ITER, VAL) \ + { \ + end_imm_use_stmt_traverse (&(ITER)); \ + return (VAL); \ + } /* Use this iterator in combination with FOR_EACH_IMM_USE_STMT to get access to each occurrence of ssavar on the stmt returned by diff --git a/gcc/testsuite/gcc.dg/ipa/propmalloc-1.c b/gcc/testsuite/gcc.dg/ipa/propmalloc-1.c new file mode 100644 index 00000000000..9a95f817079 --- /dev/null +++ b/gcc/testsuite/gcc.dg/ipa/propmalloc-1.c @@ -0,0 +1,21 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -fdump-ipa-pure-const-details" } */ + +__attribute__((noinline, no_icf, used)) +static void *f(__SIZE_TYPE__ n) +{ + void *p = __builtin_malloc (n); + if (p == 0) + __builtin_abort (); + return p; +} + +__attribute__((noinline, no_icf, used)) +static void *bar(__SIZE_TYPE__ n) +{ + void *p = f (n); + return p; +} + +/* { dg-final { scan-ipa-dump "Function f found to be malloc" "pure-const" } } */ +/* { dg-final { scan-ipa-dump "Function bar found to be malloc" "pure-const" } } */ diff --git a/gcc/testsuite/gcc.dg/ipa/propmalloc-2.c b/gcc/testsuite/gcc.dg/ipa/propmalloc-2.c new file mode 100644 index 00000000000..95b2fd74a7a --- /dev/null +++ b/gcc/testsuite/gcc.dg/ipa/propmalloc-2.c @@ -0,0 +1,23 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -fdump-ipa-pure-const-details" } */ + +__attribute__((noinline, used, no_icf)) +static void *foo (__SIZE_TYPE__ n) +{ + return __builtin_malloc (n * 10); +} + +__attribute__((noinline, used, no_icf)) +static void *bar(__SIZE_TYPE__ n, int cond) +{ + void *p; + if (cond) + p = foo (n); + else + p = __builtin_malloc (n); + + return p; +} + +/* { dg-final { scan-ipa-dump "Function foo found to be malloc" "pure-const" } } */ +/* { dg-final { scan-ipa-dump "Function bar found to be malloc" "pure-const" } } */ diff --git a/gcc/testsuite/gcc.dg/ipa/propmalloc-3.c b/gcc/testsuite/gcc.dg/ipa/propmalloc-3.c new file mode 100644 index 00000000000..13558ddd07d --- /dev/null +++ b/gcc/testsuite/gcc.dg/ipa/propmalloc-3.c @@ -0,0 +1,24 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -fdump-ipa-pure-const-details" } */ + +static void *foo(__SIZE_TYPE__, int) __attribute__((noinline, no_icf, used)); + +__attribute__((noinline, used, no_icf)) +static void *bar(__SIZE_TYPE__ n, int m) +{ + return foo (n, m); +} + +static void *foo(__SIZE_TYPE__ n, int m) +{ + void *p; + if (m > 0) + p = bar (n, --m); + else + p = __builtin_malloc (n); + + return p; +} + +/* { dg-final { scan-ipa-dump "Function foo found to be malloc" "pure-const" } } */ +/* { dg-final { scan-ipa-dump "Function bar found to be malloc" "pure-const" } } */