/* Callgraph based analysis of static variables.
- Copyright (C) 2004, 2005, 2007 Free Software Foundation, Inc.
+ Copyright (C) 2004, 2005, 2007, 2008, 2009 Free Software Foundation, Inc.
Contributed by Kenneth Zadeck <zadeck@naturalbridge.com>
This file is part of GCC.
along with GCC; see the file COPYING3. If not see
<http://www.gnu.org/licenses/>. */
-/* This file mark functions as being either const (TREE_READONLY) or
- pure (DECL_IS_PURE).
+/* This file marks functions as being either const (TREE_READONLY) or
+ pure (DECL_PURE_P). It can also set a variant of these that
+ are allowed to loop indefinitely (DECL_LOOPING_CONST_PURE_P).
This must be run after inlining decisions have been made since
otherwise, the local sets will not contain information that is
#include "pointer-set.h"
#include "ggc.h"
#include "ipa-utils.h"
-#include "c-common.h"
-#include "tree-gimple.h"
+#include "gimple.h"
#include "cgraph.h"
#include "output.h"
#include "flags.h"
#include "diagnostic.h"
#include "langhooks.h"
#include "target.h"
+#include "lto-streamer.h"
+#include "cfgloop.h"
+#include "tree-scalar-evolution.h"
static struct pointer_set_t *visited_nodes;
IPA_NEITHER
};
-/* Holder inserted into the ipa_dfs_info aux field to hold the
- const_state. */
+/* Holder for the const_state. There is one of these per function
+ decl. */
struct funct_state_d
{
+ /* See above. */
enum pure_const_state_e pure_const_state;
- bool state_set_in_source;
+ /* What user set here; we can be always sure about this. */
+ enum pure_const_state_e state_previously_known;
+ bool looping_previously_known;
+
+ /* True if the function could possibly infinite loop. There are a
+ lot of ways that this could be determined. We are pretty
+ conservative here. While it is possible to cse pure and const
+ calls, it is not legal to have dce get rid of the call if there
+ is a possibility that the call could infinite loop since this is
+ a behavioral change. */
+ bool looping;
+
+ bool can_throw;
};
typedef struct funct_state_d * funct_state;
+/* The storage of the funct_state is abstracted because there is the
+ possibility that it may be desirable to move this to the cgraph
+ local info. */
+
+/* Array, indexed by cgraph node uid, of function states. */
+
+DEF_VEC_P (funct_state);
+DEF_VEC_ALLOC_P (funct_state, heap);
+static VEC (funct_state, heap) *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;
+
+/* Init the function state. */
+
+static void
+finish_state (void)
+{
+ free (funct_state_vec);
+}
+
+
/* Return the function state from NODE. */
static inline funct_state
get_function_state (struct cgraph_node *node)
{
- struct ipa_dfs_info * info = (struct ipa_dfs_info *) node->aux;
- return (funct_state) info->aux;
+ if (!funct_state_vec
+ || VEC_length (funct_state, funct_state_vec) <= (unsigned int)node->uid)
+ return NULL;
+ return VEC_index (funct_state, funct_state_vec, node->uid);
+}
+
+/* Set the function state S for NODE. */
+
+static inline void
+set_function_state (struct cgraph_node *node, funct_state s)
+{
+ if (!funct_state_vec
+ || VEC_length (funct_state, funct_state_vec) <= (unsigned int)node->uid)
+ VEC_safe_grow_cleared (funct_state, heap, funct_state_vec, node->uid + 1);
+ VEC_replace (funct_state, funct_state_vec, node->uid, s);
}
-/* Check to see if the use (or definition when CHECHING_WRITE is true)
+/* Check to see if the use (or definition when CHECKING_WRITE is true)
variable T is legal in a function that is either pure or const. */
static inline void
check_decl (funct_state local,
tree t, bool checking_write)
{
- /* If the variable has the "used" attribute, treat it as if it had a
- been touched by the devil. */
- if (lookup_attribute ("used", DECL_ATTRIBUTES (t)))
- {
- local->pure_const_state = IPA_NEITHER;
- return;
- }
-
/* Do not want to do anything with volatile except mark any
function that uses one to be not const or pure. */
if (TREE_THIS_VOLATILE (t))
{
local->pure_const_state = IPA_NEITHER;
+ if (dump_file)
+ fprintf (dump_file, " Volatile operand is not const/pure");
return;
}
if (!TREE_STATIC (t) && !DECL_EXTERNAL (t))
return;
+ /* If the variable has the "used" attribute, treat it as if it had a
+ been touched by the devil. */
+ if (lookup_attribute ("used", DECL_ATTRIBUTES (t)))
+ {
+ local->pure_const_state = IPA_NEITHER;
+ if (dump_file)
+ fprintf (dump_file, " Used static/global variable is not const/pure\n");
+ return;
+ }
+
/* Since we have dealt with the locals and params cases above, if we
are CHECKING_WRITE, this cannot be a pure or constant
function. */
if (checking_write)
- local->pure_const_state = IPA_NEITHER;
+ {
+ local->pure_const_state = IPA_NEITHER;
+ if (dump_file)
+ fprintf (dump_file, " static/global memory write is not const/pure\n");
+ return;
+ }
if (DECL_EXTERNAL (t) || TREE_PUBLIC (t))
{
- /* If the front end set the variable to be READONLY and
- constant, we can allow this variable in pure or const
- functions but the scope is too large for our analysis to set
- these bits ourselves. */
-
- if (TREE_READONLY (t)
- && DECL_INITIAL (t)
- && is_gimple_min_invariant (DECL_INITIAL (t)))
- ; /* Read of a constant, do not change the function state. */
+ /* Readonly reads are safe. */
+ if (TREE_READONLY (t) && !TYPE_NEEDS_CONSTRUCTING (TREE_TYPE (t)))
+ return; /* Read of a constant, do not change the function state. */
else
{
+ if (dump_file)
+ fprintf (dump_file, " global memory read is not const\n");
/* Just a regular read. */
if (local->pure_const_state == IPA_CONST)
local->pure_const_state = IPA_PURE;
}
}
-
- /* Compilation level statics can be read if they are readonly
- variables. */
- if (TREE_READONLY (t))
- return;
-
- /* Just a regular read. */
- if (local->pure_const_state == IPA_CONST)
- local->pure_const_state = IPA_PURE;
+ else
+ {
+ /* Compilation level statics can be read if they are readonly
+ variables. */
+ if (TREE_READONLY (t))
+ return;
+
+ if (dump_file)
+ fprintf (dump_file, " static memory read is not const\n");
+ /* Just a regular read. */
+ if (local->pure_const_state == IPA_CONST)
+ local->pure_const_state = IPA_PURE;
+ }
}
-/* If T is a VAR_DECL check to see if it is an allowed reference. */
-
-static void
-check_operand (funct_state local,
- tree t, bool checking_write)
-{
- if (!t) return;
-
- if (TREE_CODE (t) == VAR_DECL)
- check_decl (local, t, checking_write);
-}
-/* Examine tree T for references. */
+/* Check to see if the use (or definition when CHECKING_WRITE is true)
+ variable T is legal in a function that is either pure or const. */
-static void
-check_tree (funct_state local, tree t, bool checking_write)
+static inline void
+check_op (funct_state local, tree t, bool checking_write)
{
- if ((TREE_CODE (t) == EXC_PTR_EXPR) || (TREE_CODE (t) == FILTER_EXPR)
- || TREE_CODE (t) == SSA_NAME)
- return;
-
- /* Any tree which is volatile disqualifies thie function from being
- const or pure. */
- if (TREE_THIS_VOLATILE (t))
+ t = get_base_address (t);
+ if (t && TREE_THIS_VOLATILE (t))
{
local->pure_const_state = IPA_NEITHER;
+ if (dump_file)
+ fprintf (dump_file, " Volatile indirect ref is not const/pure\n");
return;
}
-
- while (TREE_CODE (t) == REALPART_EXPR
- || TREE_CODE (t) == IMAGPART_EXPR
- || handled_component_p (t))
+ else if (t
+ && INDIRECT_REF_P (t)
+ && TREE_CODE (TREE_OPERAND (t, 0)) == SSA_NAME
+ && !ptr_deref_may_alias_global_p (TREE_OPERAND (t, 0)))
{
- if (TREE_CODE (t) == ARRAY_REF)
- check_operand (local, TREE_OPERAND (t, 1), false);
- t = TREE_OPERAND (t, 0);
- }
-
- /* The bottom of an indirect reference can only be read, not
- written. */
- if (INDIRECT_REF_P (t))
- {
- check_tree (local, TREE_OPERAND (t, 0), false);
-
- /* Any indirect reference that occurs on the lhs
- disqualifies the function from being pure or const. Any
- indirect reference that occurs on the rhs disqualifies the
- function from being const. */
- if (checking_write)
- {
- local->pure_const_state = IPA_NEITHER;
- return;
- }
- else if (local->pure_const_state == IPA_CONST)
- local->pure_const_state = IPA_PURE;
+ if (dump_file)
+ fprintf (dump_file, " Indirect ref to local memory is OK\n");
+ return;
}
-
- if (SSA_VAR_P (t))
- check_operand (local, t, checking_write);
-}
-
-/* Scan tree T to see if there are any addresses taken in within T. */
-
-static void
-look_for_address_of (funct_state local, tree t)
-{
- if (TREE_CODE (t) == ADDR_EXPR)
+ else if (checking_write)
{
- tree x = get_base_var (t);
- if (TREE_CODE (x) == VAR_DECL)
- {
- check_decl (local, x, false);
-
- /* Taking the address of something appears to be reasonable
- in PURE code. Not allowed in const. */
- if (local->pure_const_state == IPA_CONST)
- local->pure_const_state = IPA_PURE;
- }
- }
-}
-
-/* Check to see if T is a read or address of operation on a var we are
- interested in analyzing. LOCAL is passed in to get access to its
- bit vectors. */
-
-static void
-check_rhs_var (funct_state local, tree t)
-{
- look_for_address_of (local, t);
-
- /* Memcmp and strlen can both trap and they are declared pure. */
- if (tree_could_trap_p (t)
- && local->pure_const_state == IPA_CONST)
- local->pure_const_state = IPA_PURE;
-
- check_tree(local, t, false);
-}
-
-/* Check to see if T is an assignment to a var we are interested in
- analyzing. LOCAL is passed in to get access to its bit vectors. */
-
-static void
-check_lhs_var (funct_state local, tree t)
-{
- /* Memcmp and strlen can both trap and they are declared pure.
- Which seems to imply that we can apply the same rule here. */
- if (tree_could_trap_p (t)
- && local->pure_const_state == IPA_CONST)
- local->pure_const_state = IPA_PURE;
-
- check_tree(local, t, true);
-}
-
-/* This is a scaled down version of get_asm_expr_operands from
- tree_ssa_operands.c. The version there runs much later and assumes
- that aliasing information is already available. Here we are just
- trying to find if the set of inputs and outputs contain references
- or address of operations to local static variables. STMT is the
- actual asm statement. */
-
-static void
-get_asm_expr_operands (funct_state local, tree stmt)
-{
- int noutputs = list_length (ASM_OUTPUTS (stmt));
- const char **oconstraints
- = (const char **) alloca ((noutputs) * sizeof (const char *));
- int i;
- tree link;
- const char *constraint;
- bool allows_mem, allows_reg, is_inout;
-
- for (i=0, link = ASM_OUTPUTS (stmt); link; ++i, link = TREE_CHAIN (link))
- {
- oconstraints[i] = constraint
- = TREE_STRING_POINTER (TREE_VALUE (TREE_PURPOSE (link)));
- parse_output_constraint (&constraint, i, 0, 0,
- &allows_mem, &allows_reg, &is_inout);
-
- check_lhs_var (local, TREE_VALUE (link));
+ local->pure_const_state = IPA_NEITHER;
+ if (dump_file)
+ fprintf (dump_file, " Indirect ref write is not const/pure\n");
+ return;
}
-
- for (link = ASM_INPUTS (stmt); link; link = TREE_CHAIN (link))
+ else
{
- constraint
- = TREE_STRING_POINTER (TREE_VALUE (TREE_PURPOSE (link)));
- parse_input_constraint (&constraint, 0, 0, noutputs, 0,
- oconstraints, &allows_mem, &allows_reg);
-
- check_rhs_var (local, TREE_VALUE (link));
+ if (dump_file)
+ fprintf (dump_file, " Indirect ref read is not const\n");
+ if (local->pure_const_state == IPA_CONST)
+ local->pure_const_state = IPA_PURE;
}
-
- for (link = ASM_CLOBBERS (stmt); link; link = TREE_CHAIN (link))
- if (simple_cst_equal(TREE_VALUE (link), memory_identifier_string) == 1)
- /* Abandon all hope, ye who enter here. */
- local->pure_const_state = IPA_NEITHER;
-
- if (ASM_VOLATILE_P (stmt))
- local->pure_const_state = IPA_NEITHER;
}
/* Check the parameters of a function call to CALL_EXPR to see if
the entire call expression. */
static void
-check_call (funct_state local, tree call_expr)
+check_call (funct_state local, gimple call, bool ipa)
{
- int flags = call_expr_flags (call_expr);
- tree operand;
- call_expr_arg_iterator iter;
- tree callee_t = get_callee_fndecl (call_expr);
+ int flags = gimple_call_flags (call);
+ tree callee_t = gimple_call_fndecl (call);
struct cgraph_node* callee;
enum availability avail = AVAIL_NOT_AVAILABLE;
+ bool possibly_throws = stmt_could_throw_p (call);
+ bool possibly_throws_externally = (possibly_throws
+ && stmt_can_throw_external (call));
- FOR_EACH_CALL_EXPR_ARG (operand, iter, call_expr)
- check_rhs_var (local, operand);
+ if (possibly_throws)
+ {
+ unsigned int i;
+ for (i = 0; i < gimple_num_ops (call); i++)
+ if (gimple_op (call, i)
+ && tree_could_throw_p (gimple_op (call, i)))
+ {
+ if (possibly_throws && flag_non_call_exceptions)
+ {
+ if (dump_file)
+ fprintf (dump_file, " operand can throw; looping\n");
+ local->looping = true;
+ }
+ if (possibly_throws_externally)
+ {
+ if (dump_file)
+ fprintf (dump_file, " operand can throw externally\n");
+ local->can_throw = true;
+ }
+ }
+ }
/* The const and pure flags are set by a variety of places in the
compiler (including here). If someone has already set the flags
/* When bad things happen to bad functions, they cannot be const
or pure. */
if (setjmp_call_p (callee_t))
- local->pure_const_state = IPA_NEITHER;
+ {
+ if (dump_file)
+ fprintf (dump_file, " setjmp is not const/pure\n");
+ local->looping = true;
+ local->pure_const_state = IPA_NEITHER;
+ }
if (DECL_BUILT_IN_CLASS (callee_t) == BUILT_IN_NORMAL)
switch (DECL_FUNCTION_CODE (callee_t))
{
case BUILT_IN_LONGJMP:
case BUILT_IN_NONLOCAL_GOTO:
+ if (dump_file)
+ fprintf (dump_file, " longjmp and nonlocal goto is not const/pure\n");
local->pure_const_state = IPA_NEITHER;
+ local->looping = true;
break;
default:
break;
}
}
- /* The callee is either unknown (indirect call) or there is just no
- scannable code for it (external call) . We look to see if there
- are any bits available for the callee (such as by declaration or
- because it is builtin) and process solely on the basis of those
- bits. */
- if (avail == AVAIL_NOT_AVAILABLE || avail == AVAIL_OVERWRITABLE)
+ /* When not in IPA mode, we can still handle self recursion. */
+ if (!ipa && callee_t == current_function_decl)
+ local->looping = true;
+ /* Either calle is unknown or we are doing local analysis.
+ Look to see if there are any bits available for the callee (such as by
+ declaration or because it is builtin) and process solely on the basis of
+ those bits. */
+ else if (!ipa || !callee_t)
{
- if (flags & ECF_PURE)
+ if (possibly_throws && flag_non_call_exceptions)
+ {
+ if (dump_file)
+ fprintf (dump_file, " can throw; looping\n");
+ local->looping = true;
+ }
+ if (possibly_throws_externally)
+ {
+ if (dump_file)
+ {
+ fprintf (dump_file, " can throw externally to lp %i\n",
+ lookup_stmt_eh_lp (call));
+ if (callee_t)
+ fprintf (dump_file, " callee:%s\n",
+ IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (callee_t)));
+ }
+ local->can_throw = true;
+ }
+ if (flags & ECF_CONST)
+ {
+ if (callee_t && DECL_LOOPING_CONST_OR_PURE_P (callee_t))
+ local->looping = true;
+ }
+ else if (flags & ECF_PURE)
{
+ if (callee_t && DECL_LOOPING_CONST_OR_PURE_P (callee_t))
+ local->looping = true;
+ if (dump_file)
+ fprintf (dump_file, " pure function call in not const\n");
if (local->pure_const_state == IPA_CONST)
local->pure_const_state = IPA_PURE;
}
else
- local->pure_const_state = IPA_NEITHER;
- }
- else
- {
- /* We have the code and we will scan it for the effects. */
- if (flags & ECF_PURE)
{
- if (local->pure_const_state == IPA_CONST)
- local->pure_const_state = IPA_PURE;
+ if (dump_file)
+ fprintf (dump_file, " uknown function call is not const/pure\n");
+ local->pure_const_state = IPA_NEITHER;
+ local->looping = true;
}
}
+ /* Direct functions calls are handled by IPA propagation. */
}
-/* TP is the part of the tree currently under the microscope.
- WALK_SUBTREES is part of the walk_tree api but is unused here.
- DATA is cgraph_node of the function being walked. */
-
-/* FIXME: When this is converted to run over SSA form, this code
- should be converted to use the operand scanner. */
+/* Wrapper around check_decl for loads. */
-static tree
-scan_function (tree *tp,
- int *walk_subtrees,
- void *data)
+static bool
+check_load (gimple stmt ATTRIBUTE_UNUSED, tree op, void *data)
{
- struct cgraph_node *fn = (struct cgraph_node *) data;
- tree t = *tp;
- funct_state local = get_function_state (fn);
-
- switch (TREE_CODE (t))
- {
- case VAR_DECL:
- if (DECL_INITIAL (t))
- walk_tree (&DECL_INITIAL (t), scan_function, fn, visited_nodes);
- *walk_subtrees = 0;
- break;
+ if (DECL_P (op))
+ check_decl ((funct_state)data, op, false);
+ else
+ check_op ((funct_state)data, op, false);
+ return false;
+}
- case GIMPLE_MODIFY_STMT:
- {
- /* First look on the lhs and see what variable is stored to */
- tree lhs = GIMPLE_STMT_OPERAND (t, 0);
- tree rhs = GIMPLE_STMT_OPERAND (t, 1);
- check_lhs_var (local, lhs);
+/* Wrapper around check_decl for stores. */
- /* For the purposes of figuring out what the cast affects */
+static bool
+check_store (gimple stmt ATTRIBUTE_UNUSED, tree op, void *data)
+{
+ if (DECL_P (op))
+ check_decl ((funct_state)data, op, true);
+ else
+ check_op ((funct_state)data, op, true);
+ return false;
+}
- /* Next check the operands on the rhs to see if they are ok. */
- switch (TREE_CODE_CLASS (TREE_CODE (rhs)))
- {
- case tcc_binary:
- {
- tree op0 = TREE_OPERAND (rhs, 0);
- tree op1 = TREE_OPERAND (rhs, 1);
- check_rhs_var (local, op0);
- check_rhs_var (local, op1);
- }
- break;
- case tcc_unary:
- {
- tree op0 = TREE_OPERAND (rhs, 0);
- check_rhs_var (local, op0);
- }
+/* Look into pointer pointed to by GSIP and figure out what interesting side
+ effects it has. */
+static void
+check_stmt (gimple_stmt_iterator *gsip, funct_state local, bool ipa)
+{
+ gimple stmt = gsi_stmt (*gsip);
+ unsigned int i = 0;
- break;
- case tcc_reference:
- check_rhs_var (local, rhs);
- break;
- case tcc_declaration:
- check_rhs_var (local, rhs);
- break;
- case tcc_expression:
- switch (TREE_CODE (rhs))
- {
- case ADDR_EXPR:
- check_rhs_var (local, rhs);
- break;
- default:
- break;
- }
- break;
- case tcc_vl_exp:
- switch (TREE_CODE (rhs))
- {
- case CALL_EXPR:
- check_call (local, rhs);
- break;
- default:
- break;
- }
- break;
- default:
- break;
- }
- *walk_subtrees = 0;
- }
- break;
+ if (is_gimple_debug (stmt))
+ return;
- case ADDR_EXPR:
- /* This case is here to find addresses on rhs of constructors in
- decl_initial of static variables. */
- check_rhs_var (local, t);
- *walk_subtrees = 0;
- break;
+ if (dump_file)
+ {
+ fprintf (dump_file, " scanning: ");
+ print_gimple_stmt (dump_file, stmt, 0, 0);
+ }
- case LABEL_EXPR:
- if (DECL_NONLOCAL (TREE_OPERAND (t, 0)))
- /* Target of long jump. */
- local->pure_const_state = IPA_NEITHER;
- break;
+ /* Look for loads and stores. */
+ walk_stmt_load_store_ops (stmt, local, check_load, check_store);
- case CALL_EXPR:
- check_call (local, t);
- *walk_subtrees = 0;
+ if (gimple_code (stmt) != GIMPLE_CALL
+ && stmt_could_throw_p (stmt))
+ {
+ if (flag_non_call_exceptions)
+ {
+ if (dump_file)
+ fprintf (dump_file, " can throw; looping");
+ local->looping = true;
+ }
+ if (stmt_can_throw_external (stmt))
+ {
+ if (dump_file)
+ fprintf (dump_file, " can throw externally");
+ local->can_throw = true;
+ }
+ }
+ switch (gimple_code (stmt))
+ {
+ case GIMPLE_CALL:
+ check_call (local, stmt, ipa);
break;
-
- case ASM_EXPR:
- get_asm_expr_operands (local, t);
- *walk_subtrees = 0;
+ case GIMPLE_LABEL:
+ if (DECL_NONLOCAL (gimple_label_label (stmt)))
+ /* Target of long jump. */
+ {
+ if (dump_file)
+ fprintf (dump_file, " nonlocal label is not const/pure");
+ local->pure_const_state = IPA_NEITHER;
+ }
break;
-
+ case GIMPLE_ASM:
+ for (i = 0; i < gimple_asm_nclobbers (stmt); i++)
+ {
+ tree op = gimple_asm_clobber_op (stmt, i);
+ if (simple_cst_equal(TREE_VALUE (op), memory_identifier_string) == 1)
+ {
+ if (dump_file)
+ fprintf (dump_file, " memory asm clobber is not const/pure");
+ /* Abandon all hope, ye who enter here. */
+ local->pure_const_state = IPA_NEITHER;
+ }
+ }
+ if (gimple_asm_volatile_p (stmt))
+ {
+ if (dump_file)
+ fprintf (dump_file, " volatile is not const/pure");
+ /* Abandon all hope, ye who enter here. */
+ local->pure_const_state = IPA_NEITHER;
+ local->looping = true;
+ }
+ return;
default:
break;
}
- return NULL;
}
+
/* This is the main routine for finding the reference patterns for
global variables within a function FN. */
-static void
-analyze_function (struct cgraph_node *fn)
+static funct_state
+analyze_function (struct cgraph_node *fn, bool ipa)
{
- funct_state l = XCNEW (struct funct_state_d);
tree decl = fn->decl;
- struct ipa_dfs_info * w_info = (struct ipa_dfs_info *) fn->aux;
-
- w_info->aux = l;
+ tree old_decl = current_function_decl;
+ funct_state l;
+ basic_block this_block;
+ l = XCNEW (struct funct_state_d);
l->pure_const_state = IPA_CONST;
- l->state_set_in_source = false;
+ l->state_previously_known = IPA_NEITHER;
+ l->looping_previously_known = true;
+ l->looping = false;
+ l->can_throw = false;
- /* If this function does not return normally or does not bind local,
- do not touch this unless it has been marked as const or pure by the
- front end. */
- if (TREE_THIS_VOLATILE (decl)
- || !targetm.binds_local_p (decl))
+ if (dump_file)
{
- l->pure_const_state = IPA_NEITHER;
- return;
+ fprintf (dump_file, "\n\n local analysis of %s\n ",
+ cgraph_node_name (fn));
+ }
+
+ push_cfun (DECL_STRUCT_FUNCTION (decl));
+ current_function_decl = decl;
+
+ FOR_EACH_BB (this_block)
+ {
+ gimple_stmt_iterator gsi;
+ struct walk_stmt_info wi;
+
+ memset (&wi, 0, sizeof(wi));
+ for (gsi = gsi_start_bb (this_block);
+ !gsi_end_p (gsi);
+ gsi_next (&gsi))
+ {
+ check_stmt (&gsi, l, ipa);
+ if (l->pure_const_state == IPA_NEITHER && l->looping && l->can_throw)
+ goto end;
+ }
+ }
+
+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 lateches 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_NORMAL
+ | 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
+ {
+ loop_iterator li;
+ struct loop *loop;
+ scev_initialize ();
+ FOR_EACH_LOOP (li, loop, 0)
+ if (!finite_loop_p (loop))
+ {
+ if (dump_file)
+ fprintf (dump_file, " can not prove finiteness of loop %i\n", loop->num);
+ l->looping =true;
+ break;
+ }
+ scev_finalize ();
+ }
+ loop_optimizer_finalize ();
+ }
}
if (TREE_READONLY (decl))
{
l->pure_const_state = IPA_CONST;
- l->state_set_in_source = true;
+ l->state_previously_known = IPA_CONST;
+ if (!DECL_LOOPING_CONST_OR_PURE_P (decl))
+ l->looping = false, l->looping_previously_known = false;
}
- if (DECL_IS_PURE (decl))
+ if (DECL_PURE_P (decl))
{
- l->pure_const_state = IPA_PURE;
- l->state_set_in_source = true;
+ if (l->pure_const_state != IPA_CONST)
+ l->pure_const_state = IPA_PURE;
+ l->state_previously_known = IPA_PURE;
+ if (!DECL_LOOPING_CONST_OR_PURE_P (decl))
+ l->looping = false, l->looping_previously_known = false;
}
+ if (TREE_NOTHROW (decl))
+ l->can_throw = false;
+ pop_cfun ();
+ current_function_decl = old_decl;
if (dump_file)
{
- fprintf (dump_file, "\n local analysis of %s with initial value = %d\n ",
- cgraph_node_name (fn),
- l->pure_const_state);
+ if (l->looping)
+ fprintf (dump_file, "Function is locally looping.\n");
+ if (l->can_throw)
+ fprintf (dump_file, "Function is locally throwing.\n");
+ if (l->pure_const_state == IPA_CONST)
+ 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->state_set_in_source)
- {
- struct function *this_cfun = DECL_STRUCT_FUNCTION (decl);
- basic_block this_block;
-
- FOR_EACH_BB_FN (this_block, this_cfun)
- {
- block_stmt_iterator bsi;
- for (bsi = bsi_start (this_block); !bsi_end_p (bsi); bsi_next (&bsi))
- {
- walk_tree (bsi_stmt_ptr (bsi), scan_function,
- fn, visited_nodes);
- if (l->pure_const_state == IPA_NEITHER)
- goto end;
- }
- }
+ return l;
+}
- if (l->pure_const_state != IPA_NEITHER)
- {
- tree old_decl = current_function_decl;
- /* Const functions cannot have back edges (an
- indication of possible infinite loop side
- effect. */
-
- current_function_decl = fn->decl;
-
- /* The C++ front end, has a tendency to some times jerk away
- a function after it has created it. This should have
- been fixed. */
- gcc_assert (DECL_STRUCT_FUNCTION (fn->decl));
-
- push_cfun (DECL_STRUCT_FUNCTION (fn->decl));
-
- if (mark_dfs_back_edges ())
- l->pure_const_state = IPA_NEITHER;
-
- current_function_decl = old_decl;
- pop_cfun ();
- }
+/* Called when new function is inserted to callgraph late. */
+static void
+add_new_function (struct cgraph_node *node, void *data ATTRIBUTE_UNUSED)
+{
+ if (cgraph_function_body_availability (node) < AVAIL_OVERWRITABLE)
+ return;
+ /* There are some shared nodes, in particular the initializers on
+ static declarations. We do not need to scan them more than once
+ since all we would be interested in are the addressof
+ operations. */
+ visited_nodes = pointer_set_create ();
+ set_function_state (node, analyze_function (node, true));
+ pointer_set_destroy (visited_nodes);
+ visited_nodes = NULL;
+}
+
+/* Called when new clone is inserted to callgraph late. */
+
+static void
+duplicate_node_data (struct cgraph_node *src, struct cgraph_node *dst,
+ void *data ATTRIBUTE_UNUSED)
+{
+ if (get_function_state (src))
+ {
+ funct_state l = XNEW (struct funct_state_d);
+ gcc_assert (!get_function_state (dst));
+ memcpy (l, get_function_state (src), sizeof (*l));
+ set_function_state (dst, l);
}
+}
-end:
- if (dump_file)
+/* Called when new clone is inserted to callgraph late. */
+
+static void
+remove_node_data (struct cgraph_node *node, void *data ATTRIBUTE_UNUSED)
+{
+ if (get_function_state (node))
{
- fprintf (dump_file, "after local analysis of %s with initial value = %d\n ",
- cgraph_node_name (fn),
- l->pure_const_state);
+ free (get_function_state (node));
+ set_function_state (node, NULL);
}
}
\f
-/* Produce the global information by preforming a transitive closure
- on the local information that was produced by ipa_analyze_function
- and ipa_analyze_variable. */
+static void
+register_hooks (void)
+{
+ static bool init_p = false;
-static unsigned int
-static_execute (void)
+ if (init_p)
+ return;
+
+ init_p = true;
+
+ node_removal_hook_holder =
+ cgraph_add_node_removal_hook (&remove_node_data, NULL);
+ node_duplication_hook_holder =
+ cgraph_add_node_duplication_hook (&duplicate_node_data, NULL);
+ function_insertion_hook_holder =
+ cgraph_add_function_insertion_hook (&add_new_function, NULL);
+}
+
+
+/* Analyze each function in the cgraph to see if it is locally PURE or
+ CONST. */
+
+static void
+generate_summary (void)
{
struct cgraph_node *node;
- struct cgraph_node *w;
- struct cgraph_node **order =
- XCNEWVEC (struct cgraph_node *, cgraph_n_nodes);
- int order_pos = ipa_utils_reduced_inorder (order, true, false);
- int i;
- struct ipa_dfs_info * w_info;
- if (!memory_identifier_string)
- memory_identifier_string = build_string(7, "memory");
+ register_hooks ();
/* There are some shared nodes, in particular the initializers on
static declarations. We do not need to scan them more than once
/* Process all of the functions.
- We do not want to process any of the clones so we check that this
- is a master clone. However, we do NOT process any
- AVAIL_OVERWRITABLE functions (these are never clones) we cannot
- guarantee that what we learn about the one we see will be true
- for the one that overriders it.
- */
+ We process AVAIL_OVERWRITABLE functions. We can not use the results
+ by default, but the info can be used at LTO with -fwhole-program or
+ when function got clonned and the clone is AVAILABLE. */
+
for (node = cgraph_nodes; node; node = node->next)
- if (node->analyzed && cgraph_is_master_clone (node))
- analyze_function (node);
+ if (cgraph_function_body_availability (node) >= AVAIL_OVERWRITABLE)
+ set_function_state (node, analyze_function (node, true));
pointer_set_destroy (visited_nodes);
visited_nodes = NULL;
+}
+
+
+/* Serialize the ipa info for lto. */
+
+static void
+pure_const_write_summary (cgraph_node_set set)
+{
+ struct cgraph_node *node;
+ struct lto_simple_output_block *ob
+ = lto_create_simple_output_block (LTO_section_ipa_pure_const);
+ unsigned int count = 0;
+ cgraph_node_set_iterator csi;
+
+ for (csi = csi_start (set); !csi_end_p (csi); csi_next (&csi))
+ {
+ node = csi_node (csi);
+ if (node->analyzed && get_function_state (node) != NULL)
+ count++;
+ }
+
+ lto_output_uleb128_stream (ob->main_stream, count);
+
+ /* Process all of the functions. */
+ for (csi = csi_start (set); !csi_end_p (csi); csi_next (&csi))
+ {
+ node = csi_node (csi);
+ if (node->analyzed && get_function_state (node) != NULL)
+ {
+ struct bitpack_d *bp;
+ funct_state fs;
+ int node_ref;
+ lto_cgraph_encoder_t encoder;
+
+ fs = get_function_state (node);
+
+ encoder = ob->decl_state->cgraph_node_encoder;
+ node_ref = lto_cgraph_encoder_encode (encoder, node);
+ lto_output_uleb128_stream (ob->main_stream, node_ref);
+
+ /* Note that flags will need to be read in the opposite
+ order as we are pushing the bitflags into FLAGS. */
+ bp = bitpack_create ();
+ bp_pack_value (bp, fs->pure_const_state, 2);
+ bp_pack_value (bp, fs->state_previously_known, 2);
+ bp_pack_value (bp, fs->looping_previously_known, 1);
+ bp_pack_value (bp, fs->looping, 1);
+ bp_pack_value (bp, fs->can_throw, 1);
+ lto_output_bitpack (ob->main_stream, bp);
+ bitpack_delete (bp);
+ }
+ }
+
+ lto_destroy_simple_output_block (ob);
+}
+
+
+/* Deserialize the ipa info for lto. */
+
+static void
+pure_const_read_summary (void)
+{
+ struct lto_file_decl_data **file_data_vec = lto_get_file_decl_data ();
+ struct lto_file_decl_data *file_data;
+ unsigned int j = 0;
+
+ register_hooks ();
+ while ((file_data = file_data_vec[j++]))
+ {
+ const char *data;
+ size_t len;
+ struct lto_input_block *ib
+ = lto_create_simple_input_block (file_data,
+ LTO_section_ipa_pure_const,
+ &data, &len);
+ if (ib)
+ {
+ unsigned int i;
+ unsigned int count = lto_input_uleb128 (ib);
+
+ for (i = 0; i < count; i++)
+ {
+ unsigned int index;
+ struct cgraph_node *node;
+ struct bitpack_d *bp;
+ funct_state fs;
+ lto_cgraph_encoder_t encoder;
+
+ fs = XCNEW (struct funct_state_d);
+ index = lto_input_uleb128 (ib);
+ encoder = file_data->cgraph_node_encoder;
+ node = lto_cgraph_encoder_deref (encoder, index);
+ set_function_state (node, fs);
+
+ /* Note that the flags must be read in the opposite
+ order in which they were written (the bitflags were
+ pushed into FLAGS). */
+ bp = lto_input_bitpack (ib);
+ fs->pure_const_state
+ = (enum pure_const_state_e) bp_unpack_value (bp, 2);
+ fs->state_previously_known
+ = (enum pure_const_state_e) bp_unpack_value (bp, 2);
+ fs->looping_previously_known = bp_unpack_value (bp, 1);
+ fs->looping = bp_unpack_value (bp, 1);
+ fs->can_throw = bp_unpack_value (bp, 1);
+ bitpack_delete (bp);
+ }
+
+ lto_destroy_simple_input_block (file_data,
+ LTO_section_ipa_pure_const,
+ ib, data, len);
+ }
+ }
+}
+
+
+static bool
+ignore_edge (struct cgraph_edge *e)
+{
+ return (!e->can_throw_external);
+}
+
+/* Return true if NODE is self recursive function. */
+
+static bool
+self_recursive_p (struct cgraph_node *node)
+{
+ struct cgraph_edge *e;
+ for (e = node->callees; e; e = e->next_callee)
+ if (e->callee == node)
+ return true;
+ return false;
+}
+
+/* Produce the global information by preforming a transitive closure
+ on the local information that was produced by generate_summary.
+ Note that there is no function_transform pass since this only
+ updates the function_decl. */
+
+static unsigned int
+propagate (void)
+{
+ struct cgraph_node *node;
+ struct cgraph_node *w;
+ struct cgraph_node **order =
+ XCNEWVEC (struct cgraph_node *, cgraph_n_nodes);
+ int order_pos;
+ int i;
+ struct ipa_dfs_info * w_info;
+
+ 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);
+ order_pos = ipa_utils_reduced_inorder (order, true, false, NULL);
if (dump_file)
{
dump_cgraph (dump_file);
for (i = 0; i < order_pos; i++ )
{
enum pure_const_state_e pure_const_state = IPA_CONST;
+ bool looping = false;
+ int count = 0;
node = order[i];
/* Find the worst state for any node in the cycle. */
w = node;
while (w)
{
+ struct cgraph_edge *e;
funct_state w_l = get_function_state (w);
if (pure_const_state < w_l->pure_const_state)
pure_const_state = w_l->pure_const_state;
- if (pure_const_state == IPA_NEITHER)
+ if (w_l->looping)
+ looping = true;
+ if (cgraph_function_body_availability (w) == AVAIL_OVERWRITABLE)
+ {
+ looping |= w_l->looping_previously_known;
+ if (pure_const_state < w_l->state_previously_known)
+ pure_const_state = w_l->state_previously_known;
+ }
+
+ if (pure_const_state == IPA_NEITHER)
break;
- if (!w_l->state_set_in_source)
+ count++;
+
+ if (count > 1)
+ looping = true;
+
+ for (e = w->callees; e; e = e->next_callee)
{
- struct cgraph_edge *e;
- for (e = w->callees; e; e = e->next_callee)
+ struct cgraph_node *y = e->callee;
+
+ if (cgraph_function_body_availability (y) > AVAIL_OVERWRITABLE)
{
- struct cgraph_node *y = e->callee;
- /* Only look at the master nodes and skip external nodes. */
- y = cgraph_master_clone (y);
- if (y)
- {
- funct_state y_l = get_function_state (y);
- if (pure_const_state < y_l->pure_const_state)
- pure_const_state = y_l->pure_const_state;
- if (pure_const_state == IPA_NEITHER)
- break;
- }
+ funct_state y_l = get_function_state (y);
+ if (pure_const_state < y_l->pure_const_state)
+ pure_const_state = y_l->pure_const_state;
+ if (pure_const_state == IPA_NEITHER)
+ break;
+ if (y_l->looping)
+ looping = true;
+ }
+ else
+ {
+ int flags = flags_from_decl_or_type (y->decl);
+
+ if (flags & ECF_LOOPING_CONST_OR_PURE)
+ looping = true;
+ if (flags & ECF_CONST)
+ ;
+ else if ((flags & ECF_PURE) && pure_const_state == IPA_CONST)
+ pure_const_state = IPA_PURE;
+ else
+ pure_const_state = IPA_NEITHER, looping = true;
+
}
}
w_info = (struct ipa_dfs_info *) w->aux;
while (w)
{
funct_state w_l = get_function_state (w);
+ enum pure_const_state_e this_state = pure_const_state;
+ bool this_looping = looping;
+
+ if (w_l->state_previously_known != IPA_NEITHER
+ && this_state > w_l->state_previously_known)
+ this_state = w_l->state_previously_known;
+ if (!this_looping && self_recursive_p (w))
+ this_looping = true;
+ if (!w_l->looping_previously_known)
+ this_looping = false;
/* All nodes within a cycle share the same info. */
- if (!w_l->state_set_in_source)
+ w_l->pure_const_state = this_state;
+ w_l->looping = this_looping;
+
+ switch (this_state)
{
- w_l->pure_const_state = pure_const_state;
- switch (pure_const_state)
+ case IPA_CONST:
+ if (!TREE_READONLY (w->decl) && dump_file)
+ fprintf (dump_file, "Function found to be %sconst: %s\n",
+ this_looping ? "looping " : "",
+ cgraph_node_name (w));
+ TREE_READONLY (w->decl) = 1;
+ DECL_LOOPING_CONST_OR_PURE_P (w->decl) = this_looping;
+ break;
+
+ case IPA_PURE:
+ if (!DECL_PURE_P (w->decl) && dump_file)
+ fprintf (dump_file, "Function found to be %spure: %s\n",
+ this_looping ? "looping " : "",
+ cgraph_node_name (w));
+ DECL_PURE_P (w->decl) = 1;
+ DECL_LOOPING_CONST_OR_PURE_P (w->decl) = this_looping;
+ break;
+
+ default:
+ break;
+ }
+ w_info = (struct ipa_dfs_info *) w->aux;
+ w = w_info->next_cycle;
+ }
+ }
+
+ /* Cleanup. */
+ for (node = cgraph_nodes; node; node = node->next)
+ {
+ /* Get rid of the aux information. */
+ if (node->aux)
+ {
+ w_info = (struct ipa_dfs_info *) node->aux;
+ free (node->aux);
+ node->aux = NULL;
+ }
+ }
+ order_pos = ipa_utils_reduced_inorder (order, true, false, ignore_edge);
+ if (dump_file)
+ {
+ dump_cgraph (dump_file);
+ ipa_utils_print_order(dump_file, "reduced for nothrow", order, order_pos);
+ }
+ /* Propagate the local information thru 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_throw = false;
+ node = order[i];
+
+ /* Find the worst state for any node in the cycle. */
+ w = node;
+ while (w)
+ {
+ struct cgraph_edge *e;
+ funct_state w_l = get_function_state (w);
+
+ if (w_l->can_throw
+ || cgraph_function_body_availability (w) == AVAIL_OVERWRITABLE)
+ can_throw = true;
+
+ if (can_throw)
+ break;
+
+ for (e = w->callees; e; e = e->next_callee)
+ {
+ struct cgraph_node *y = e->callee;
+
+ if (cgraph_function_body_availability (y) > AVAIL_OVERWRITABLE)
{
- case IPA_CONST:
- TREE_READONLY (w->decl) = 1;
- if (dump_file)
- fprintf (dump_file, "Function found to be const: %s\n",
- lang_hooks.decl_printable_name(w->decl, 2));
- break;
-
- case IPA_PURE:
- DECL_IS_PURE (w->decl) = 1;
- if (dump_file)
- fprintf (dump_file, "Function found to be pure: %s\n",
- lang_hooks.decl_printable_name(w->decl, 2));
- break;
-
- default:
- break;
+ funct_state y_l = get_function_state (y);
+
+ if (can_throw)
+ break;
+ if (y_l->can_throw && !TREE_NOTHROW (w->decl)
+ && e->can_throw_external)
+ can_throw = true;
}
+ else if (e->can_throw_external && !TREE_NOTHROW (y->decl))
+ can_throw = 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;
+ while (w)
+ {
+ funct_state w_l = get_function_state (w);
+ if (!can_throw && !TREE_NOTHROW (w->decl))
+ {
+ struct cgraph_edge *e;
+ TREE_NOTHROW (w->decl) = true;
+ for (e = w->callers; e; e = e->next_caller)
+ e->can_throw_external = false;
+ if (dump_file)
+ fprintf (dump_file, "Function found to be nothrow: %s\n",
+ cgraph_node_name (w));
}
+ else if (can_throw && !TREE_NOTHROW (w->decl))
+ w_l->can_throw = true;
w_info = (struct ipa_dfs_info *) w->aux;
w = w_info->next_cycle;
}
/* Cleanup. */
for (node = cgraph_nodes; node; node = node->next)
- /* Get rid of the aux information. */
- if (node->aux)
- {
- w_info = (struct ipa_dfs_info *) node->aux;
- if (w_info->aux)
- free (w_info->aux);
- free (node->aux);
- node->aux = NULL;
- }
-
+ {
+ /* Get rid of the aux information. */
+ if (node->aux)
+ {
+ w_info = (struct ipa_dfs_info *) node->aux;
+ free (node->aux);
+ node->aux = NULL;
+ }
+ if (cgraph_function_body_availability (node) >= AVAIL_OVERWRITABLE)
+ free (get_function_state (node));
+ }
+
free (order);
+ VEC_free (funct_state, heap, funct_state_vec);
+ finish_state ();
return 0;
}
static bool
gate_pure_const (void)
{
- return (flag_unit_at_a_time != 0 && flag_ipa_pure_const
+ return (flag_ipa_pure_const
/* Don't bother doing anything if the program has errors. */
&& !(errorcount || sorrycount));
}
-struct tree_opt_pass pass_ipa_pure_const =
+struct ipa_opt_pass_d pass_ipa_pure_const =
{
+ {
+ IPA_PASS,
"pure-const", /* name */
gate_pure_const, /* gate */
- static_execute, /* execute */
+ propagate, /* execute */
NULL, /* sub */
NULL, /* next */
0, /* static_pass_number */
0, /* properties_provided */
0, /* properties_destroyed */
0, /* todo_flags_start */
- 0, /* todo_flags_finish */
- 0 /* letter */
+ 0 /* todo_flags_finish */
+ },
+ generate_summary, /* generate_summary */
+ pure_const_write_summary, /* write_summary */
+ pure_const_read_summary, /* read_summary */
+ NULL, /* function_read_summary */
+ 0, /* TODOs */
+ NULL, /* function_transform */
+ NULL /* variable_transform */
};
+/* 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. */
+
+static unsigned int
+local_pure_const (void)
+{
+ bool changed = false;
+ funct_state l;
+
+ /* Because we do not schedule pass_fixup_cfg over whole program after early optimizations
+ we must not promote functions that are called by already processed functions. */
+
+ if (function_called_by_processed_nodes_p ())
+ {
+ if (dump_file)
+ fprintf (dump_file, "Function called in recursive cycle; ignoring\n");
+ return 0;
+ }
+ if (cgraph_function_body_availability (cgraph_node (current_function_decl))
+ <= AVAIL_OVERWRITABLE)
+ {
+ if (dump_file)
+ fprintf (dump_file, "Function has wrong visibility; ignoring\n");
+ return 0;
+ }
+
+ l = analyze_function (cgraph_node (current_function_decl), false);
+
+ switch (l->pure_const_state)
+ {
+ case IPA_CONST:
+ if (!TREE_READONLY (current_function_decl))
+ {
+ TREE_READONLY (current_function_decl) = 1;
+ DECL_LOOPING_CONST_OR_PURE_P (current_function_decl) = l->looping;
+ changed = true;
+ if (dump_file)
+ fprintf (dump_file, "Function found to be %sconst: %s\n",
+ l->looping ? "looping " : "",
+ lang_hooks.decl_printable_name (current_function_decl,
+ 2));
+ }
+ else if (DECL_LOOPING_CONST_OR_PURE_P (current_function_decl)
+ && !l->looping)
+ {
+ DECL_LOOPING_CONST_OR_PURE_P (current_function_decl) = false;
+ changed = true;
+ if (dump_file)
+ fprintf (dump_file, "Function found to be non-looping: %s\n",
+ lang_hooks.decl_printable_name (current_function_decl,
+ 2));
+ }
+ break;
+ case IPA_PURE:
+ if (!TREE_READONLY (current_function_decl))
+ {
+ DECL_PURE_P (current_function_decl) = 1;
+ DECL_LOOPING_CONST_OR_PURE_P (current_function_decl) = l->looping;
+ changed = true;
+ if (dump_file)
+ fprintf (dump_file, "Function found to be %spure: %s\n",
+ l->looping ? "looping " : "",
+ lang_hooks.decl_printable_name (current_function_decl,
+ 2));
+ }
+ else if (DECL_LOOPING_CONST_OR_PURE_P (current_function_decl)
+ && !l->looping)
+ {
+ DECL_LOOPING_CONST_OR_PURE_P (current_function_decl) = false;
+ changed = true;
+ if (dump_file)
+ fprintf (dump_file, "Function found to be non-looping: %s\n",
+ lang_hooks.decl_printable_name (current_function_decl,
+ 2));
+ }
+ break;
+
+ default:
+ break;
+ }
+ if (!l->can_throw && !TREE_NOTHROW (current_function_decl))
+ {
+ struct cgraph_edge *e;
+
+ TREE_NOTHROW (current_function_decl) = true;
+ for (e = cgraph_node (current_function_decl)->callers;
+ e; e = e->next_caller)
+ e->can_throw_external = false;
+ changed = true;
+ if (dump_file)
+ fprintf (dump_file, "Function found to be nothrow: %s\n",
+ lang_hooks.decl_printable_name (current_function_decl,
+ 2));
+ }
+ if (l)
+ free (l);
+ if (changed)
+ return execute_fixup_cfg ();
+ else
+ return 0;
+}
+
+struct gimple_opt_pass pass_local_pure_const =
+{
+ {
+ GIMPLE_PASS,
+ "local-pure-const", /* name */
+ gate_pure_const, /* gate */
+ local_pure_const, /* execute */
+ NULL, /* sub */
+ NULL, /* next */
+ 0, /* static_pass_number */
+ TV_IPA_PURE_CONST, /* tv_id */
+ 0, /* properties_required */
+ 0, /* properties_provided */
+ 0, /* properties_destroyed */
+ 0, /* todo_flags_start */
+ 0 /* todo_flags_finish */
+ }
+};