/* Callgraph based analysis of static variables.
- Copyright (C) 2004-2014 Free Software Foundation, Inc.
+ Copyright (C) 2004-2015 Free Software Foundation, Inc.
Contributed by Kenneth Zadeck <zadeck@naturalbridge.com>
This file is part of GCC.
#include "config.h"
#include "system.h"
#include "coretypes.h"
-#include "tm.h"
+#include "backend.h"
+#include "target.h"
#include "tree.h"
-#include "print-tree.h"
+#include "gimple.h"
+#include "tree-pass.h"
+#include "tree-streamer.h"
+#include "cgraph.h"
+#include "diagnostic.h"
#include "calls.h"
-#include "basic-block.h"
-#include "tree-ssa-alias.h"
-#include "internal-fn.h"
+#include "cfganal.h"
#include "tree-eh.h"
-#include "gimple-expr.h"
-#include "is-a.h"
-#include "gimple.h"
#include "gimple-iterator.h"
#include "gimple-walk.h"
#include "tree-cfg.h"
#include "tree-ssa-loop-niter.h"
-#include "tree-inline.h"
-#include "tree-pass.h"
#include "langhooks.h"
#include "ipa-utils.h"
-#include "flags.h"
-#include "diagnostic.h"
#include "gimple-pretty-print.h"
-#include "langhooks.h"
-#include "target.h"
-#include "lto-streamer.h"
-#include "data-streamer.h"
-#include "tree-streamer.h"
#include "cfgloop.h"
#include "tree-scalar-evolution.h"
#include "intl.h"
#include "opts.h"
-#include "hash-set.h"
/* Lattice values for const and pure functions. Everything starts out
being const, then may drop to pure and then neither depending on
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;
static vec<funct_state> funct_state_vec;
-/* Holders of ipa cgraph hooks: */
-static struct cgraph_node_hook_list *function_insertion_hook_holder;
-static struct cgraph_2node_hook_list *node_duplication_hook_holder;
-static struct cgraph_node_hook_list *node_removal_hook_holder;
+static bool gate_pure_const (void);
+
+namespace {
+
+const pass_data pass_data_ipa_pure_const =
+{
+ IPA_PASS, /* type */
+ "pure-const", /* name */
+ OPTGROUP_NONE, /* optinfo_flags */
+ TV_IPA_PURE_CONST, /* tv_id */
+ 0, /* properties_required */
+ 0, /* properties_provided */
+ 0, /* properties_destroyed */
+ 0, /* todo_flags_start */
+ 0, /* todo_flags_finish */
+};
+
+class pass_ipa_pure_const : public ipa_opt_pass_d
+{
+public:
+ pass_ipa_pure_const(gcc::context *ctxt);
+
+ /* opt_pass methods: */
+ bool gate (function *) { return gate_pure_const (); }
+ unsigned int execute (function *fun);
+
+ void register_hooks (void);
+
+private:
+ bool init_p;
+
+ /* Holders of ipa cgraph hooks: */
+ struct cgraph_node_hook_list *function_insertion_hook_holder;
+ struct cgraph_2node_hook_list *node_duplication_hook_holder;
+ struct cgraph_node_hook_list *node_removal_hook_holder;
+
+}; // class pass_ipa_pure_const
+
+} // anon namespace
/* Try to guess if function body will always be visible to compiler
when compiling the call and whether compiler will be able
*looping = true;
*state = IPA_CONST;
return true;
+ default:
+ break;
}
return false;
}
the entire call expression. */
static void
-check_call (funct_state local, gimple call, bool ipa)
+check_call (funct_state local, gcall *call, bool ipa)
{
int flags = gimple_call_flags (call);
tree callee_t = gimple_call_fndecl (call);
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,
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
/* Wrapper around check_decl for loads in local more. */
static bool
-check_load (gimple, tree op, tree, void *data)
+check_load (gimple *, tree op, tree, void *data)
{
if (DECL_P (op))
check_decl ((funct_state)data, op, false, false);
/* Wrapper around check_decl for stores in local more. */
static bool
-check_store (gimple, tree op, tree, void *data)
+check_store (gimple *, tree op, tree, void *data)
{
if (DECL_P (op))
check_decl ((funct_state)data, op, true, false);
/* Wrapper around check_decl for loads in ipa mode. */
static bool
-check_ipa_load (gimple, tree op, tree, void *data)
+check_ipa_load (gimple *, tree op, tree, void *data)
{
if (DECL_P (op))
check_decl ((funct_state)data, op, false, true);
/* Wrapper around check_decl for stores in ipa mode. */
static bool
-check_ipa_store (gimple, tree op, tree, void *data)
+check_ipa_store (gimple *, tree op, tree, void *data)
{
if (DECL_P (op))
check_decl ((funct_state)data, op, true, true);
static void
check_stmt (gimple_stmt_iterator *gsip, funct_state local, bool ipa)
{
- gimple stmt = gsi_stmt (*gsip);
+ gimple *stmt = gsi_stmt (*gsip);
if (is_gimple_debug (stmt))
return;
+ /* Do consider clobber as side effects before IPA, so we rather inline
+ C++ destructors and keep clobber semantics than eliminate them.
+
+ TODO: We may get smarter during early optimizations on these and let
+ functions containing only clobbers to be optimized more. This is a common
+ case of C++ destructors. */
+
+ if ((ipa || cfun->after_inlining) && gimple_clobber_p (stmt))
+ return;
+
if (dump_file)
{
fprintf (dump_file, " scanning: ");
switch (gimple_code (stmt))
{
case GIMPLE_CALL:
- check_call (local, stmt, ipa);
+ check_call (local, as_a <gcall *> (stmt), ipa);
break;
case GIMPLE_LABEL:
- if (DECL_NONLOCAL (gimple_label_label (stmt)))
+ if (DECL_NONLOCAL (gimple_label_label (as_a <glabel *> (stmt))))
/* Target of long jump. */
{
if (dump_file)
}
break;
case GIMPLE_ASM:
- if (gimple_asm_clobbers_memory_p (stmt))
+ if (gimple_asm_clobbers_memory_p (as_a <gasm *> (stmt)))
{
if (dump_file)
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))
+ if (gimple_asm_volatile_p (as_a <gasm *> (stmt)))
{
if (dump_file)
fprintf (dump_file, " volatile is not const/pure\n");
/* Abandon all hope, ye who enter here. */
local->pure_const_state = IPA_NEITHER;
- local->looping = true;
+ local->looping = true;
+ local->can_free = true;
}
return;
default:
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 ());
{
/* Thunk gets propagated through, so nothing interesting happens. */
gcc_assert (ipa);
+ if (fn->thunk.thunk_p && fn->thunk.virtual_offset_p)
+ l->pure_const_state = IPA_NEITHER;
return l;
}
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;
}
}
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;
}
static declarations. We do not need to scan them more than once
since all we would be interested in are the addressof
operations. */
- if (node->get_availability () > AVAIL_INTERPOSABLE)
+ if (node->get_availability () > AVAIL_INTERPOSABLE
+ && opt_for_fn (node->decl, flag_ipa_pure_const))
set_function_state (node, analyze_function (node, true));
}
}
\f
-static void
+void
+pass_ipa_pure_const::
register_hooks (void)
{
- static bool init_p = false;
-
if (init_p)
return;
init_p = true;
node_removal_hook_holder =
- cgraph_add_node_removal_hook (&remove_node_data, NULL);
+ symtab->add_cgraph_removal_hook (&remove_node_data, NULL);
node_duplication_hook_holder =
- cgraph_add_node_duplication_hook (&duplicate_node_data, NULL);
+ symtab->add_cgraph_duplication_hook (&duplicate_node_data, NULL);
function_insertion_hook_holder =
- cgraph_add_function_insertion_hook (&add_new_function, NULL);
+ symtab->add_cgraph_insertion_hook (&add_new_function, NULL);
}
{
struct cgraph_node *node;
- register_hooks ();
+ pass_ipa_pure_const *pass = static_cast <pass_ipa_pure_const *> (current_pass);
+ pass->register_hooks ();
/* Process all of the functions.
when function got cloned and the clone is AVAILABLE. */
FOR_EACH_DEFINED_FUNCTION (node)
- if (node->get_availability () >= AVAIL_INTERPOSABLE)
+ if (node->get_availability () >= AVAIL_INTERPOSABLE
+ && opt_for_fn (node->decl, flag_ipa_pure_const))
set_function_state (node, analyze_function (node, true));
}
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);
}
}
struct lto_file_decl_data *file_data;
unsigned int j = 0;
- register_hooks ();
+ pass_ipa_pure_const *pass = static_cast <pass_ipa_pure_const *> (current_pass);
+ pass->register_hooks ();
+
while ((file_data = file_data_vec[j++]))
{
const char *data;
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);
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");
}
}
}
}
+/* We only propagate across edges that can throw externally and their callee
+ is not interposable. */
static bool
-ignore_edge (struct cgraph_edge *e)
+ignore_edge_for_nothrow (struct cgraph_edge *e)
{
- return (!e->can_throw_external);
+ if (!e->can_throw_external || TREE_NOTHROW (e->callee->decl))
+ return true;
+
+ enum availability avail;
+ cgraph_node *n = e->callee->function_or_virtual_thunk_symbol (&avail);
+ return (avail <= AVAIL_INTERPOSABLE || TREE_NOTHROW (n->decl));
}
/* Return true if NODE is self recursive function.
return false;
}
+/* Return true if N is cdtor that is not const or pure. In this case we may
+ need to remove unreachable function if it is marked const/pure. */
+
+static bool
+cdtor_p (cgraph_node *n, void *)
+{
+ if (DECL_STATIC_CONSTRUCTOR (n->decl) || DECL_STATIC_DESTRUCTOR (n->decl))
+ return !TREE_READONLY (n->decl) && !DECL_PURE_P (n->decl);
+ return false;
+}
+
+/* We only propagate across edges with non-interposable callee. */
+
+static bool
+ignore_edge_for_pure_const (struct cgraph_edge *e)
+{
+ enum availability avail;
+ e->callee->function_or_virtual_thunk_symbol (&avail);
+ return (avail <= AVAIL_INTERPOSABLE);
+}
+
+
/* Produce transitive closure over the callgraph and compute pure/const
attributes. */
-static void
+static bool
propagate_pure_const (void)
{
struct cgraph_node *node;
struct cgraph_node *w;
struct cgraph_node **order =
- XCNEWVEC (struct cgraph_node *, cgraph_n_nodes);
+ XCNEWVEC (struct cgraph_node *, symtab->cgraph_count);
int order_pos;
int i;
struct ipa_dfs_info * w_info;
+ bool remove_p = false;
- order_pos = ipa_reduced_postorder (order, true, false, NULL);
+ order_pos = ipa_reduced_postorder (order, true, false,
+ ignore_edge_for_pure_const);
if (dump_file)
{
cgraph_node::dump_cgraph (dump_file);
if (pure_const_state == IPA_NEITHER)
break;
- /* For overwritable nodes we can not assume anything. */
+ /* For interposable nodes we can not assume anything. */
if (w->get_availability () == AVAIL_INTERPOSABLE)
{
worse_state (&pure_const_state, &looping,
if (dump_file && (dump_flags & TDF_DETAILS))
{
fprintf (dump_file,
- " Overwritable. state %s looping %i\n",
+ " Interposable. state %s looping %i\n",
pure_const_names[w_l->state_previously_known],
w_l->looping_previously_known);
}
looping = true;
/* Now walk the edges and merge in callee properties. */
- for (e = w->callees; e; e = e->next_callee)
+ for (e = w->callees; e && pure_const_state != IPA_NEITHER;
+ e = e->next_callee)
{
enum availability avail;
- struct cgraph_node *y = e->callee->function_symbol (&avail);
+ struct cgraph_node *y = e->callee->
+ function_or_virtual_thunk_symbol (&avail);
enum pure_const_state_e edge_state = IPA_CONST;
bool edge_looping = false;
y_l->looping);
}
if (y_l->pure_const_state > IPA_PURE
- && cgraph_edge_cannot_lead_to_return (e))
+ && e->cannot_lead_to_return_p ())
{
if (dump_file && (dump_flags & TDF_DETAILS))
fprintf (dump_file,
else
state_from_flags (&edge_state, &edge_looping,
flags_from_decl_or_type (y->decl),
- cgraph_edge_cannot_lead_to_return (e));
+ e->cannot_lead_to_return_p ());
/* Merge the results with what we already know. */
better_state (&edge_state, &edge_looping,
if (pure_const_state == IPA_NEITHER)
break;
}
- if (pure_const_state == IPA_NEITHER)
- break;
/* Now process the indirect call. */
- for (ie = w->indirect_calls; ie; ie = ie->next_callee)
+ for (ie = w->indirect_calls;
+ ie && pure_const_state != IPA_NEITHER; ie = ie->next_callee)
{
enum pure_const_state_e edge_state = IPA_CONST;
bool edge_looping = false;
fprintf (dump_file, " Indirect call");
state_from_flags (&edge_state, &edge_looping,
ie->indirect_info->ecf_flags,
- cgraph_edge_cannot_lead_to_return (ie));
+ ie->cannot_lead_to_return_p ());
/* Merge the results with what we already know. */
better_state (&edge_state, &edge_looping,
w_l->state_previously_known,
if (pure_const_state == IPA_NEITHER)
break;
}
- if (pure_const_state == IPA_NEITHER)
- break;
/* And finally all loads and stores. */
- for (i = 0; w->iterate_reference (i, ref); i++)
+ for (i = 0; w->iterate_reference (i, ref)
+ && pure_const_state != IPA_NEITHER; i++)
{
enum pure_const_state_e ref_state = IPA_CONST;
bool ref_looping = false;
fprintf (dump_file, " global var write\n");
break;
case IPA_REF_ADDR:
+ case IPA_REF_CHKP:
break;
default:
gcc_unreachable ();
pure_const_names [pure_const_state],
looping);
+ /* Find the worst state of can_free for any node in the cycle. */
+ bool can_free = false;
+ 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_or_virtual_thunk_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 pure_const_state which is shared by
all nodes in the region. */
w = node;
enum pure_const_state_e this_state = pure_const_state;
bool this_looping = looping;
+ w_l->can_free = can_free;
+ w->nonfreeing_fn = !can_free;
+ if (!can_free && dump_file)
+ fprintf (dump_file, "Function found not to call free: %s\n",
+ w->name ());
+
if (w_l->state_previously_known != IPA_NEITHER
&& this_state > w_l->state_previously_known)
{
this_state = w_l->state_previously_known;
- this_looping |= w_l->looping_previously_known;
+ if (this_state == IPA_NEITHER)
+ this_looping = w_l->looping_previously_known;
}
if (!this_looping && self_recursive_p (w))
this_looping = true;
this_looping ? "looping " : "",
w->name ());
}
+ remove_p |= w->call_for_symbol_and_aliases (cdtor_p,
+ NULL, true);
w->set_const_flag (true, this_looping);
break;
this_looping ? "looping " : "",
w->name ());
}
+ remove_p |= w->call_for_symbol_and_aliases (cdtor_p,
+ NULL, true);
w->set_pure_flag (true, this_looping);
break;
ipa_free_postorder_info ();
free (order);
+ return remove_p;
}
/* Produce transitive closure over the callgraph and compute nothrow
struct cgraph_node *node;
struct cgraph_node *w;
struct cgraph_node **order =
- XCNEWVEC (struct cgraph_node *, cgraph_n_nodes);
+ 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, ignore_edge);
+ order_pos = ipa_reduced_postorder (order, true, false,
+ ignore_edge_for_nothrow);
if (dump_file)
{
cgraph_node::dump_cgraph (dump_file);
/* Find the worst state for any node in the cycle. */
w = node;
- while (w)
+ while (w && !can_throw)
{
struct cgraph_edge *e, *ie;
- funct_state w_l = get_function_state (w);
-
- if (w_l->can_throw
- || w->get_availability () == AVAIL_INTERPOSABLE)
- can_throw = true;
-
- if (can_throw)
- break;
- for (e = w->callees; e; e = e->next_callee)
+ if (!TREE_NOTHROW (w->decl))
{
- enum availability avail;
- struct cgraph_node *y = e->callee->function_symbol (&avail);
+ funct_state w_l = get_function_state (w);
- if (avail > AVAIL_INTERPOSABLE)
+ if (w_l->can_throw
+ || w->get_availability () == AVAIL_INTERPOSABLE)
+ can_throw = true;
+
+ for (e = w->callees; e && !can_throw; e = e->next_callee)
{
- funct_state y_l = get_function_state (y);
+ enum availability avail;
- if (can_throw)
- break;
- if (y_l->can_throw && !TREE_NOTHROW (w->decl)
- && e->can_throw_external)
+ if (!e->can_throw_external || TREE_NOTHROW (e->callee->decl))
+ continue;
+
+ struct cgraph_node *y = e->callee->
+ function_or_virtual_thunk_symbol (&avail);
+
+ /* We can use info about the callee only if we know it can
+ not be interposed. */
+ if (avail <= AVAIL_INTERPOSABLE
+ || (!TREE_NOTHROW (y->decl)
+ && get_function_state (y)->can_throw))
can_throw = true;
}
- else if (e->can_throw_external && !TREE_NOTHROW (y->decl))
- can_throw = true;
+ for (ie = w->indirect_calls; ie && !can_throw;
+ ie = ie->next_callee)
+ if (ie->can_throw_external
+ && !(ie->indirect_info->ecf_flags & ECF_NOTHROW))
+ can_throw = true;
}
- for (ie = node->indirect_calls; ie; ie = ie->next_callee)
- if (ie->can_throw_external)
- {
- can_throw = true;
- break;
- }
w_info = (struct ipa_dfs_info *) w->aux;
w = w_info->next_cycle;
}
/* Produce the global information by preforming a transitive closure
on the local information that was produced by generate_summary. */
-static unsigned int
-propagate (void)
+unsigned int
+pass_ipa_pure_const::
+execute (function *)
{
struct cgraph_node *node;
+ bool remove_p;
- cgraph_remove_function_insertion_hook (function_insertion_hook_holder);
- cgraph_remove_node_duplication_hook (node_duplication_hook_holder);
- cgraph_remove_node_removal_hook (node_removal_hook_holder);
+ symtab->remove_cgraph_insertion_hook (function_insertion_hook_holder);
+ symtab->remove_cgraph_duplication_hook (node_duplication_hook_holder);
+ symtab->remove_cgraph_removal_hook (node_removal_hook_holder);
/* Nothrow makes more function to not lead to return and improve
later analysis. */
propagate_nothrow ();
- propagate_pure_const ();
+ remove_p = propagate_pure_const ();
/* Cleanup. */
FOR_EACH_FUNCTION (node)
if (has_function_state (node))
free (get_function_state (node));
funct_state_vec.release ();
- return 0;
+ return remove_p ? TODO_remove_functions : 0;
}
static bool
gate_pure_const (void)
{
- return (flag_ipa_pure_const
- /* Don't bother doing anything if the program has errors. */
- && !seen_error ());
+ return flag_ipa_pure_const || in_lto_p;
}
-namespace {
-
-const pass_data pass_data_ipa_pure_const =
-{
- IPA_PASS, /* type */
- "pure-const", /* name */
- OPTGROUP_NONE, /* optinfo_flags */
- TV_IPA_PURE_CONST, /* tv_id */
- 0, /* properties_required */
- 0, /* properties_provided */
- 0, /* properties_destroyed */
- 0, /* todo_flags_start */
- 0, /* todo_flags_finish */
-};
-
-class pass_ipa_pure_const : public ipa_opt_pass_d
+pass_ipa_pure_const::pass_ipa_pure_const(gcc::context *ctxt)
+ : ipa_opt_pass_d(pass_data_ipa_pure_const, ctxt,
+ pure_const_generate_summary, /* generate_summary */
+ pure_const_write_summary, /* write_summary */
+ pure_const_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 */
+ init_p(false),
+ function_insertion_hook_holder(NULL),
+ node_duplication_hook_holder(NULL),
+ node_removal_hook_holder(NULL)
{
-public:
- pass_ipa_pure_const (gcc::context *ctxt)
- : ipa_opt_pass_d (pass_data_ipa_pure_const, ctxt,
- pure_const_generate_summary, /* generate_summary */
- pure_const_write_summary, /* write_summary */
- pure_const_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 gate_pure_const (); }
- virtual unsigned int execute (function *) { return propagate (); }
-
-}; // class pass_ipa_pure_const
-
-} // anon namespace
+}
ipa_opt_pass_d *
make_pass_ipa_pure_const (gcc::context *ctxt)
if (node->get_availability () <= AVAIL_INTERPOSABLE)
{
if (dump_file)
- fprintf (dump_file, "Function is not available or overwritable; not analyzing.\n");
+ fprintf (dump_file, "Function is not available or interposable; not analyzing.\n");
return true;
}
return false;
return new pass_warn_function_noreturn (ctxt);
}
+/* Simple local pass for pure const discovery reusing the analysis from
+ ipa_pure_const. This pass is effective when executed together with
+ other optimization passes in early optimization pass queue. */
+
+namespace {
+const pass_data pass_data_nothrow =
+{
+ GIMPLE_PASS, /* type */
+ "nothrow", /* name */
+ OPTGROUP_NONE, /* optinfo_flags */
+ TV_IPA_PURE_CONST, /* tv_id */
+ 0, /* properties_required */
+ 0, /* properties_provided */
+ 0, /* properties_destroyed */
+ 0, /* todo_flags_start */
+ 0, /* todo_flags_finish */
+};
+
+class pass_nothrow : public gimple_opt_pass
+{
+public:
+ pass_nothrow (gcc::context *ctxt)
+ : gimple_opt_pass (pass_data_nothrow, ctxt)
+ {}
+
+ /* opt_pass methods: */
+ opt_pass * clone () { return new pass_nothrow (m_ctxt); }
+ virtual bool gate (function *) { return optimize; }
+ virtual unsigned int execute (function *);
+
+}; // class pass_nothrow
+
+unsigned int
+pass_nothrow::execute (function *)
+{
+ struct cgraph_node *node;
+ basic_block this_block;
+
+ if (TREE_NOTHROW (current_function_decl))
+ return 0;
+
+ node = cgraph_node::get (current_function_decl);
+
+ /* We run during lowering, we can not really use availability yet. */
+ if (cgraph_node::get (current_function_decl)->get_availability ()
+ <= AVAIL_INTERPOSABLE)
+ {
+ if (dump_file)
+ fprintf (dump_file, "Function is interposable;"
+ " not analyzing.\n");
+ return true;
+ }
+
+ FOR_EACH_BB_FN (this_block, cfun)
+ {
+ for (gimple_stmt_iterator gsi = gsi_start_bb (this_block);
+ !gsi_end_p (gsi);
+ gsi_next (&gsi))
+ if (stmt_can_throw_external (gsi_stmt (gsi)))
+ {
+ if (is_gimple_call (gsi_stmt (gsi)))
+ {
+ tree callee_t = gimple_call_fndecl (gsi_stmt (gsi));
+ if (callee_t && recursive_call_p (current_function_decl,
+ callee_t))
+ continue;
+ }
+
+ if (dump_file)
+ {
+ fprintf (dump_file, "Statement can throw: ");
+ print_gimple_stmt (dump_file, gsi_stmt (gsi), 0, 0);
+ }
+ return 0;
+ }
+ }
+
+ node->set_nothrow_flag (true);
+ if (dump_file)
+ fprintf (dump_file, "Function found to be nothrow: %s\n",
+ current_function_name ());
+ return 0;
+}
+
+} // anon namespace
+
+gimple_opt_pass *
+make_pass_nothrow (gcc::context *ctxt)
+{
+ return new pass_nothrow (ctxt);
+}