This is the mail archive of the
gcc-patches@gcc.gnu.org
mailing list for the GCC project.
Re: Rewrite of pure/const pass and nothrow stuff
- From: Kenneth Zadeck <zadeck at naturalbridge dot com>
- To: Jan Hubicka <jh at suse dot cz>
- Cc: gcc-patches at gcc dot gnu dot org, dnovillo at google dot com
- Date: Thu, 13 Nov 2008 15:07:43 -0500
- Subject: Re: Rewrite of pure/const pass and nothrow stuff
- References: <20081113190834.GA11360@kam.mff.cuni.cz>
Jan Hubicka wrote:
> Hi,
> this patch teach pure/const about nothrow as discussed earlier. I eneded up
> rewriting the local analysis from scratch as after tuplification it ended up remarkably
> ugly.
>
> There are several new flasing features:
> 1) Local analysis should be more aggressive (I hope I did not miss anything important)
> 2) There is local variant of the pass. It can be done during early optimization
> and at the place original RTL pass executed. Especially in early
> optimization we are effective. Main motivation for doing so early is to strip down EH
> and dead calls to get early inliner more informed; main motivation for late pass is to avoid
> dwarf2out from unnecesarily outputting unwind info and being stupid.
> IPA pass is the only able to handle non-trivial recursion cycles.
>
> On tramp3d 4828 functions are marked nothrow in early pass, 0 in IPA pass, 36 in late pass.
> 7386 functions are found const in early pass, 283 in IPA pass, 45 in late pass.
> 1462 functions are found pure in early pass, 516 in IPA pass, 22 in late pass.
>
> Old implementation found 15 const and 12 pure functions.
>
> 3) There is readable debug output showing operation of local scanner
> 4) fixup_cfg is now pass that can be scheduled as needed that should help LTO too.
>
> Bootstrapping/regtesting x86_64-linux, will commit it once finished to pretty-ipa.
>
>
Assuming that this all checks out with the regression tests, this is
also ok whenever stage 1 opens again.
kenny
> Honza
>
> * tree-pass.h (pass_fixup_cfg, pass_local_pure_const): Declare.
> * ipa-pure-const.c (funct_state_d): Add can throw field; make
> state_set_in_source enum
> (check_decl): Ignore memory tags; do not set fake looping flags;
> dump diagnostics.
> (check_operand, check_tree, check_rhs_var, check_lhs_var,
> get_asm_expr_operands, scan_function_op, scan_function_stmt): Remove.
> (check_call, analyze_function): Rewrite.
> (check_stmt): New.
> (add_new_function): Update call of analyze_function.
> (generate_summary): Add call of analyze_function.
> (propagate): Propagate can_throw; handle state_set_in_source correctly.
> (local_pure_const): New function.
> (pass_local_pure_const): New pass.
> * ipa-inline.c (inline_transform): Set after_inlining.
> * tree-eh.c (stmt_can_throw_external): New.
> * tree-optimize.c (execute_fixup_cfg): Do not set after_inlining;
> work with aliasing built.
> * tree-flow.h (stmt_can_throw_external): New.
> * passes.c (init_optimization_passes): Schedule fixup_cfg pass early;
> and local pure/const pass in early and late optimization queue.
> Index: tree-pass.h
> ===================================================================
> *** tree-pass.h (revision 141798)
> --- tree-pass.h (working copy)
> *************** extern struct gimple_opt_pass pass_build
> *** 308,313 ****
> --- 308,314 ----
> extern struct gimple_opt_pass pass_tree_profile;
> extern struct gimple_opt_pass pass_early_tree_profile;
> extern struct gimple_opt_pass pass_cleanup_cfg;
> + extern struct gimple_opt_pass pass_fixup_cfg;
> extern struct gimple_opt_pass pass_referenced_vars;
> extern struct gimple_opt_pass pass_sra;
> extern struct gimple_opt_pass pass_sra_early;
> *************** extern struct gimple_opt_pass pass_reass
> *** 388,393 ****
> --- 389,395 ----
> extern struct gimple_opt_pass pass_rebuild_cgraph_edges;
> extern struct gimple_opt_pass pass_build_cgraph_edges;
> extern struct gimple_opt_pass pass_reset_cc_flags;
> + extern struct gimple_opt_pass pass_local_pure_const;
>
> /* IPA Passes */
> extern struct ipa_opt_pass pass_ipa_inline;
> Index: ipa-pure-const.c
> ===================================================================
> *** ipa-pure-const.c (revision 141798)
> --- ipa-pure-const.c (working copy)
> *************** struct funct_state_d
> *** 71,76 ****
> --- 71,78 ----
> {
> /* See above. */
> enum pure_const_state_e pure_const_state;
> + /* What user set here; we can be always sure about this. */
> + enum pure_const_state_e state_set_in_source;
>
> /* True if the function could possibly infinite loop. There are a
> lot of ways that this could be determined. We are pretty
> *************** struct funct_state_d
> *** 80,89 ****
> a behavioral change. */
> bool looping;
>
> ! /* If the state of the function was set in the source, then assume
> ! that it was done properly even if the analysis we do would be
> ! more pessimestic. */
> ! bool state_set_in_source;
> };
>
> typedef struct funct_state_d * funct_state;
> --- 82,88 ----
> a behavioral change. */
> bool looping;
>
> ! bool can_throw;
> };
>
> typedef struct funct_state_d * funct_state;
> *************** static inline void
> *** 141,152 ****
> check_decl (funct_state local,
> tree t, bool checking_write)
> {
> /* 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;
> ! local->looping = false;
> return;
> }
>
> --- 140,154 ----
> check_decl (funct_state local,
> tree t, bool checking_write)
> {
> + if (MTAG_P (t))
> + 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;
> }
>
> *************** check_decl (funct_state local,
> *** 159,165 ****
> if (lookup_attribute ("used", DECL_ATTRIBUTES (t)))
> {
> local->pure_const_state = IPA_NEITHER;
> ! local->looping = false;
> return;
> }
>
> --- 161,168 ----
> 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;
> }
>
> *************** check_decl (funct_state local,
> *** 169,175 ****
> if (checking_write)
> {
> local->pure_const_state = IPA_NEITHER;
> ! local->looping = false;
> return;
> }
>
> --- 172,179 ----
> if (checking_write)
> {
> local->pure_const_state = IPA_NEITHER;
> ! if (dump_file)
> ! fprintf (dump_file, " static/global memory write is not const/pure\n");
> return;
> }
>
> *************** check_decl (funct_state local,
> *** 177,333 ****
> {
> /* Readonly reads are safe. */
> if (TREE_READONLY (t) && !TYPE_NEEDS_CONSTRUCTING (TREE_TYPE (t)))
> ! ; /* Read of a constant, do not change the function state. */
> else
> {
> /* 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;
> }
>
> - /* 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. */
>
> ! static void
> ! check_tree (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 this function from being
> ! const or pure. */
> ! if (TREE_THIS_VOLATILE (t))
> {
> ! local->pure_const_state = IPA_NEITHER;
> ! local->looping = false;
> ! return;
> ! }
> !
> ! while (TREE_CODE (t) == REALPART_EXPR
> ! || TREE_CODE (t) == IMAGPART_EXPR
> ! || handled_component_p (t))
> ! {
> ! 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;
> ! local->looping = false;
> return;
> }
> ! else if (local->pure_const_state == IPA_CONST)
> ! local->pure_const_state = IPA_PURE;
> ! }
> !
> ! if (SSA_VAR_P (t))
> ! check_operand (local, t, checking_write);
> ! }
> !
> ! /* 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)
> ! {
> ! 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)
> ! {
> ! 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, gimple stmt)
> ! {
> ! size_t noutputs = gimple_asm_noutputs (stmt);
> ! const char **oconstraints
> ! = (const char **) alloca ((noutputs) * sizeof (const char *));
> ! size_t i;
> ! tree op;
> ! const char *constraint;
> ! bool allows_mem, allows_reg, is_inout;
> !
> ! for (i = 0; i < noutputs; i++)
> ! {
> ! op = gimple_asm_output_op (stmt, i);
> ! oconstraints[i] = constraint
> ! = TREE_STRING_POINTER (TREE_VALUE (TREE_PURPOSE (op)));
> ! parse_output_constraint (&constraint, i, 0, 0,
> ! &allows_mem, &allows_reg, &is_inout);
> !
> ! check_lhs_var (local, TREE_VALUE (op));
> ! }
> !
> ! for (i = 0; i < gimple_asm_ninputs (stmt); i++)
> ! {
> ! op = gimple_asm_input_op (stmt, i);
> ! constraint
> ! = TREE_STRING_POINTER (TREE_VALUE (TREE_PURPOSE (op)));
> ! parse_input_constraint (&constraint, 0, 0, noutputs, 0,
> ! oconstraints, &allows_mem, &allows_reg);
> !
> ! check_rhs_var (local, TREE_VALUE (op));
> ! }
> !
> ! for (i = 0; i < gimple_asm_nclobbers (stmt); i++)
> ! {
> ! op = gimple_asm_clobber_op (stmt, i);
> ! if (simple_cst_equal(TREE_VALUE (op), memory_identifier_string) == 1)
> ! /* Abandon all hope, ye who enter here. */
> ! local->pure_const_state = IPA_NEITHER;
> }
> -
> - if (gimple_asm_volatile_p (stmt))
> - local->pure_const_state = IPA_NEITHER;
> }
>
> /* Check the parameters of a function call to CALL_EXPR to see if
> --- 181,247 ----
> {
> /* 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;
> }
> }
> ! 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;
> ! }
> }
>
>
> ! /* 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_op (funct_state local,
> ! tree t, bool checking_write)
> {
> ! while (t && handled_component_p (t))
> ! t = TREE_OPERAND (t, 0);
> ! if (!t)
> return;
> ! if (TREE_CODE (t) == INDIRECT_REF || TREE_CODE (t) == TARGET_MEM_REF)
> {
> ! if (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;
> }
> ! else if (checking_write)
> ! {
> ! local->pure_const_state = IPA_NEITHER;
> ! if (dump_file)
> ! fprintf (dump_file, " Indirect ref write is not const/pure\n");
> ! return;
> ! }
> ! else
> ! {
> ! 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;
> ! }
> }
> }
>
> /* Check the parameters of a function call to CALL_EXPR to see if
> *************** get_asm_expr_operands (funct_state local
> *** 338,357 ****
> the entire call expression. */
>
> static void
> ! check_call (funct_state local, gimple call)
> {
> int flags = gimple_call_flags (call);
> ! tree lhs, callee_t = gimple_call_fndecl (call);
> struct cgraph_node* callee;
> enum availability avail = AVAIL_NOT_AVAILABLE;
> ! size_t i;
> !
> ! lhs = gimple_call_lhs (call);
> ! if (lhs)
> ! check_lhs_var (local, lhs);
>
> ! for (i = 0; i < gimple_call_num_args (call); i++)
> ! check_rhs_var (local, gimple_call_arg (call, i));
>
> /* The const and pure flags are set by a variety of places in the
> compiler (including here). If someone has already set the flags
> --- 252,286 ----
> the entire call expression. */
>
> static void
> ! check_call (funct_state local, gimple call, bool ipa)
> {
> 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_externally = stmt_can_throw_external (call);
> ! bool possibly_throws = stmt_could_throw_p (call);
>
> ! if (possibly_throws)
> ! {
> ! unsigned int i;
> ! for (i = 0; i < gimple_num_ops (call); i++)
> ! if (stmt_could_throw_p (call))
> ! {
> ! if (possibly_throws && flag_non_call_exceptions)
> ! {
> ! if (dump_file)
> ! fprintf (dump_file, " operand can throw; looping");
> ! local->looping = true;
> ! }
> ! if (possibly_throws_externally)
> ! {
> ! if (dump_file)
> ! fprintf (dump_file, " operand can throw externally");
> ! 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
> *************** check_call (funct_state local, gimple ca
> *** 372,379 ****
> or pure. */
> if (setjmp_call_p (callee_t))
> {
> local->pure_const_state = IPA_NEITHER;
> - local->looping = false;
> }
>
> if (DECL_BUILT_IN_CLASS (callee_t) == BUILT_IN_NORMAL)
> --- 301,310 ----
> or pure. */
> if (setjmp_call_p (callee_t))
> {
> + 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)
> *************** check_call (funct_state local, gimple ca
> *** 381,647 ****
> {
> case BUILT_IN_LONGJMP:
> case BUILT_IN_NONLOCAL_GOTO:
> local->pure_const_state = IPA_NEITHER;
> ! local->looping = false;
> 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)
> {
> ! if (flags & ECF_PURE)
> ! {
> 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;
> }
> }
> }
>
> ! /* 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. */
> !
> ! static tree
> ! scan_function_op (tree *tp, int *walk_subtrees, void *data)
> ! {
> ! struct walk_stmt_info *wi = (struct walk_stmt_info *) data;
> ! struct cgraph_node *fn = (struct cgraph_node *) wi->info;
> ! 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_op, data, visited_nodes);
> ! *walk_subtrees = 0;
> ! break;
> !
> ! 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;
> !
> ! default:
> ! break;
> ! }
> ! return NULL;
> ! }
> !
> ! static tree
> ! scan_function_stmt (gimple_stmt_iterator *gsi_p,
> ! bool *handled_ops_p,
> ! struct walk_stmt_info *wi)
> {
> ! struct cgraph_node *fn = (struct cgraph_node *) wi->info;
> ! gimple stmt = gsi_stmt (*gsi_p);
> ! funct_state local = get_function_state (fn);
>
> switch (gimple_code (stmt))
> {
> case GIMPLE_ASSIGN:
> ! {
> ! /* First look on the lhs and see what variable is stored to */
> ! tree lhs = gimple_assign_lhs (stmt);
> ! tree rhs1 = gimple_assign_rhs1 (stmt);
> ! tree rhs2 = gimple_assign_rhs2 (stmt);
> ! enum tree_code code = gimple_assign_rhs_code (stmt);
> !
> ! check_lhs_var (local, lhs);
> !
> ! /* For the purposes of figuring out what the cast affects */
> !
> ! /* Next check the operands on the rhs to see if they are ok. */
> ! switch (TREE_CODE_CLASS (code))
> ! {
> ! case tcc_binary:
> ! {
> ! check_rhs_var (local, rhs1);
> ! check_rhs_var (local, rhs2);
> ! }
> ! break;
> ! case tcc_unary:
> ! {
> ! check_rhs_var (local, rhs1);
> ! }
> !
> ! break;
> ! case tcc_reference:
> ! check_rhs_var (local, rhs1);
> ! break;
> ! case tcc_declaration:
> ! check_rhs_var (local, rhs1);
> ! break;
> ! case tcc_expression:
> ! switch (code)
> ! {
> ! case ADDR_EXPR:
> ! check_rhs_var (local, rhs1);
> ! break;
> ! default:
> ! break;
> ! }
> ! break;
> ! default:
> ! break;
> ! }
> ! *handled_ops_p = true;
> ! }
> break;
> -
> case GIMPLE_LABEL:
> if (DECL_NONLOCAL (gimple_label_label (stmt)))
> /* Target of long jump. */
> {
> local->pure_const_state = IPA_NEITHER;
> - local->looping = false;
> }
> break;
> -
> - case GIMPLE_CALL:
> - check_call (local, stmt);
> - *handled_ops_p = true;
> - break;
> -
> case GIMPLE_ASM:
> ! get_asm_expr_operands (local, stmt);
> ! *handled_ops_p = true;
> ! break;
> !
> 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)
> {
> tree decl = fn->decl;
> ! funct_state l = XCNEW (struct funct_state_d);
> !
> ! if (cgraph_function_body_availability (fn) <= AVAIL_OVERWRITABLE)
> ! return;
>
> ! set_function_state (fn, l);
>
> l->pure_const_state = IPA_CONST;
> ! l->state_set_in_source = false;
> ! if (DECL_LOOPING_CONST_OR_PURE_P (decl))
> ! l->looping = true;
> ! else
> ! l->looping = 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))
> {
> ! l->pure_const_state = IPA_NEITHER;
> ! return;
> }
> !
> ! if (TREE_READONLY (decl))
> {
> ! l->pure_const_state = IPA_CONST;
> ! l->state_set_in_source = true;
> }
> ! if (DECL_PURE_P (decl))
> {
> ! l->pure_const_state = IPA_PURE;
> ! l->state_set_in_source = true;
> }
>
> ! 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->state_set_in_source)
> {
> ! struct function *this_cfun = DECL_STRUCT_FUNCTION (decl);
> ! basic_block this_block;
> !
> ! FOR_EACH_BB_FN (this_block, this_cfun)
> ! {
> ! 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))
> ! {
> ! wi.info = fn;
> ! wi.pset = visited_nodes;
> ! walk_gimple_stmt (&gsi, scan_function_stmt, scan_function_op,
> ! &wi);
> ! if (l->pure_const_state == IPA_NEITHER)
> ! goto end;
> ! }
> ! }
> !
> ! 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 ();
> ! }
> }
>
> ! end:
> if (dump_file)
> {
> ! fprintf (dump_file, "after local analysis of %s with initial value = %d\n ",
> ! cgraph_node_name (fn),
> ! l->pure_const_state);
> }
> }
>
> /* Called when new function is inserted to callgraph late. */
> --- 312,551 ----
> {
> 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;
> }
> }
>
> + /* When not in IPA mode, we can still handle self recursion. */
> + if (!ipa && callee_t == current_function_decl)
> + local->looping = true;
> /* 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. */
> ! else if (avail <= AVAIL_OVERWRITABLE || !ipa)
> {
> ! if (possibly_throws && flag_non_call_exceptions)
> ! {
> ! if (dump_file)
> ! fprintf (dump_file, " can throw; looping");
> ! local->looping = true;
> ! }
> ! if (possibly_throws_externally)
> ! {
> ! if (dump_file)
> ! fprintf (dump_file, " can throw externally");
> ! 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
> {
> ! 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. */
> }
>
> ! static void
> ! check_stmt (gimple_stmt_iterator *gsip, funct_state local, bool ipa)
> {
> ! gimple stmt = gsi_stmt (*gsip);
> ! unsigned int i = 0;
> ! bitmap_iterator bi;
>
> + if (dump_file)
> + {
> + fprintf (dump_file, " scanning: ");
> + print_gimple_stmt (dump_file, stmt, 0, 0);
> + }
> + if (gimple_loaded_syms (stmt))
> + EXECUTE_IF_SET_IN_BITMAP (gimple_loaded_syms (stmt), 0, i, bi)
> + check_decl (local, referenced_var_lookup (i), false);
> + if (gimple_stored_syms (stmt))
> + EXECUTE_IF_SET_IN_BITMAP (gimple_stored_syms (stmt), 0, i, bi)
> + check_decl (local, referenced_var_lookup (i), true);
> +
> + 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_ASSIGN:
> ! check_op (local, gimple_assign_lhs (stmt), true);
> ! i = 1;
> ! break;
> ! case GIMPLE_CALL:
> ! check_op (local, gimple_call_lhs (stmt), true);
> ! i = 1;
> ! check_call (local, stmt, ipa);
> break;
> 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_noutputs (stmt); i++)
> ! check_op (local, gimple_asm_output_op (stmt, i), true);
> ! for (i = 0; i < gimple_asm_ninputs (stmt); i++)
> ! check_op (local, gimple_asm_input_op (stmt, i), false);
> ! 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;
> }
> !
> ! for (; i < gimple_num_ops (stmt); i++)
> ! check_op (local, gimple_op (stmt, i), false);
> }
>
>
> /* This is the main routine for finding the reference patterns for
> global variables within a function FN. */
>
> ! static funct_state
> ! analyze_function (struct cgraph_node *fn, bool ipa)
> {
> tree decl = fn->decl;
> ! tree old_decl = current_function_decl;
> ! funct_state l;
> ! basic_block this_block;
>
> ! if (cgraph_function_body_availability (fn) <= AVAIL_OVERWRITABLE)
> ! return NULL;
>
> + l = XCNEW (struct funct_state_d);
> l->pure_const_state = IPA_CONST;
> ! l->state_set_in_source = IPA_NEITHER;
> ! l->looping = false;
> ! l->can_throw = false;
>
> ! if (dump_file)
> {
> ! 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 ())
> ! l->looping = true;
> !
> }
>
> ! if (TREE_READONLY (decl))
> {
> ! l->pure_const_state = IPA_CONST;
> ! l->state_set_in_source = IPA_CONST;
> ! if (!DECL_LOOPING_CONST_OR_PURE_P (decl))
> ! l->looping = false;
> }
> ! if (DECL_PURE_P (decl))
> {
> ! if (l->pure_const_state != IPA_CONST)
> ! l->pure_const_state = IPA_PURE;
> ! l->state_set_in_source = IPA_PURE;
> ! if (!DECL_LOOPING_CONST_OR_PURE_P (decl))
> ! l->looping = false;
> }
> + if (TREE_NOTHROW (decl))
> + l->can_throw = false;
>
> ! pop_cfun ();
> ! current_function_decl = old_decl;
> if (dump_file)
> {
> ! 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");
> }
> + return l;
> }
>
> /* Called when new function is inserted to callgraph late. */
> *************** add_new_function (struct cgraph_node *no
> *** 655,661 ****
> since all we would be interested in are the addressof
> operations. */
> visited_nodes = pointer_set_create ();
> ! analyze_function (node);
> pointer_set_destroy (visited_nodes);
> visited_nodes = NULL;
> }
> --- 559,565 ----
> 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;
> }
> *************** generate_summary (void)
> *** 716,722 ****
> */
> for (node = cgraph_nodes; node; node = node->next)
> if (cgraph_function_body_availability (node) > AVAIL_OVERWRITABLE)
> ! analyze_function (node);
>
> pointer_set_destroy (visited_nodes);
> visited_nodes = NULL;
> --- 620,626 ----
> */
> for (node = cgraph_nodes; node; node = node->next)
> if (cgraph_function_body_availability (node) > AVAIL_OVERWRITABLE)
> ! set_function_state (node, analyze_function (node, true));
>
> pointer_set_destroy (visited_nodes);
> visited_nodes = NULL;
> *************** propagate (void)
> *** 756,761 ****
> --- 660,666 ----
> {
> enum pure_const_state_e pure_const_state = IPA_CONST;
> bool looping = false;
> + bool can_throw = false;
> int count = 0;
> node = order[i];
>
> *************** propagate (void)
> *** 763,800 ****
> w = node;
> while (w)
> {
> 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 (w_l->looping)
> looping = true;
>
> if (pure_const_state == IPA_NEITHER)
> break;
>
> ! if (!w_l->state_set_in_source)
> {
> ! struct cgraph_edge *e;
> ! count++;
>
> ! if (count > 1)
> ! looping = true;
> !
> ! for (e = w->callees; e; e = e->next_callee)
> {
> ! struct cgraph_node *y = e->callee;
> !
> ! if (cgraph_function_body_availability (y) > AVAIL_OVERWRITABLE)
> ! {
> ! 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;
> ! }
> }
> }
> w_info = (struct ipa_dfs_info *) w->aux;
> --- 668,709 ----
> 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 (w_l->can_throw)
> + can_throw = true;
> if (w_l->looping)
> looping = true;
>
> if (pure_const_state == IPA_NEITHER)
> break;
>
> ! count++;
> !
> ! if (count > 1)
> ! looping = true;
> !
> ! for (e = w->callees; e; e = e->next_callee)
> {
> ! struct cgraph_node *y = e->callee;
>
> ! if (cgraph_function_body_availability (y) > AVAIL_OVERWRITABLE)
> {
> ! 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;
> ! if (y_l->can_throw && !TREE_NOTHROW (w->decl)
> ! /* FIXME: We should check that the throw can get external.
> ! We also should handle only loops formed by can throw external
> ! edges. */)
> ! can_throw = true;
> }
> }
> w_info = (struct ipa_dfs_info *) w->aux;
> *************** propagate (void)
> *** 807,842 ****
> while (w)
> {
> funct_state w_l = get_function_state (w);
>
> ! /* All nodes within a cycle share the same info. */
> ! if (!w_l->state_set_in_source)
> {
> ! w_l->pure_const_state = pure_const_state;
> ! w_l->looping = looping;
>
> ! switch (pure_const_state)
> ! {
> ! case IPA_CONST:
> ! TREE_READONLY (w->decl) = 1;
> ! DECL_LOOPING_CONST_OR_PURE_P (w->decl) = looping;
> ! if (dump_file)
> ! fprintf (dump_file, "Function found to be %sconst: %s\n",
> ! looping ? "looping " : "",
> ! lang_hooks.decl_printable_name(w->decl, 2));
> ! break;
> !
> ! case IPA_PURE:
> ! DECL_PURE_P (w->decl) = 1;
> ! DECL_LOOPING_CONST_OR_PURE_P (w->decl) = looping;
> ! if (dump_file)
> ! fprintf (dump_file, "Function found to be %spure: %s\n",
> ! looping ? "looping " : "",
> ! lang_hooks.decl_printable_name(w->decl, 2));
> ! break;
> !
> ! default:
> ! break;
> ! }
> }
> w_info = (struct ipa_dfs_info *) w->aux;
> w = w_info->next_cycle;
> --- 716,763 ----
> 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_set_in_source != IPA_NEITHER)
> {
> ! if (this_state > w_l->state_set_in_source)
> ! this_state = w_l->state_set_in_source;
> ! this_looping = false;
> ! }
>
> ! /* All nodes within a cycle share the same info. */
> ! w_l->pure_const_state = this_state;
> ! w_l->looping = this_looping;
> !
> ! switch (this_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;
> ! }
> ! if (!can_throw && !TREE_NOTHROW (w->decl))
> ! {
> ! if (dump_file)
> ! fprintf (dump_file, "Function found to be nothrow: %s\n",
> ! cgraph_node_name (w));
> }
> w_info = (struct ipa_dfs_info *) w->aux;
> w = w_info->next_cycle;
> *************** struct ipa_opt_pass pass_ipa_pure_const
> *** 896,898 ****
> --- 817,917 ----
> NULL, /* function_transform */
> NULL /* variable_transform */
> };
> +
> + static unsigned int
> + local_pure_const (void)
> + {
> + bool changed = false;
> + funct_state l =
> + analyze_function (cgraph_node (current_function_decl), false);
> + if (!l)
> + return 0;
> +
> + 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))
> + {
> + TREE_NOTHROW (current_function_decl) = 1;
> + 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 */
> + }
> + };
> Index: ipa-inline.c
> ===================================================================
> *** ipa-inline.c (revision 141798)
> --- ipa-inline.c (working copy)
> *************** inline_transform (struct cgraph_node *no
> *** 1732,1737 ****
> --- 1732,1738 ----
> todo = optimize_inline_calls (current_function_decl);
> timevar_pop (TV_INTEGRATION);
> }
> + cfun->after_inlining = true;
> return todo | execute_fixup_cfg ();
> }
>
> Index: tree-eh.c
> ===================================================================
> *** tree-eh.c (revision 141798)
> --- tree-eh.c (working copy)
> *************** tree_could_throw_p (tree t)
> *** 2384,2389 ****
> --- 2384,2411 ----
> return false;
> }
>
> + /* Return true if STMT can throw an exception that is not caught within
> + the current function (CFUN). */
> +
> + bool
> + stmt_can_throw_external (gimple stmt)
> + {
> + int region_nr;
> + bool is_resx = false;
> +
> + if (gimple_code (stmt) == GIMPLE_RESX)
> + {
> + region_nr = gimple_resx_region (stmt);
> + is_resx = true;
> + }
> + else
> + region_nr = lookup_stmt_eh_region (stmt);
> +
> + if (region_nr < 0)
> + return true;
> +
> + return can_throw_external_1 (region_nr, is_resx);
> + }
>
> /* Return true if STMT can throw an exception that is caught within
> the current function (CFUN). */
> Index: tree-optimize.c
> ===================================================================
> *** tree-optimize.c (revision 141798)
> --- tree-optimize.c (working copy)
> *************** execute_fixup_cfg (void)
> *** 292,299 ****
> gimple_stmt_iterator gsi;
> int todo = gimple_in_ssa_p (cfun) ? TODO_verify_ssa : 0;
>
> - cfun->after_inlining = true;
> -
> if (cfun->eh)
> FOR_EACH_BB (bb)
> {
> --- 292,297 ----
> *************** execute_fixup_cfg (void)
> *** 312,322 ****
> if (gimple_in_ssa_p (cfun))
> {
> todo |= TODO_update_ssa | TODO_cleanup_cfg;
> update_stmt (stmt);
> }
> }
>
> ! if (!stmt_could_throw_p (stmt) && lookup_stmt_eh_region (stmt))
> remove_stmt_from_eh_region (stmt);
> }
>
> --- 310,321 ----
> if (gimple_in_ssa_p (cfun))
> {
> todo |= TODO_update_ssa | TODO_cleanup_cfg;
> + mark_symbols_for_renaming (stmt);
> update_stmt (stmt);
> }
> }
>
> ! if (!stmt_could_throw_p (stmt) && lookup_stmt_eh_region (stmt) != -2)
> remove_stmt_from_eh_region (stmt);
> }
>
> *************** execute_fixup_cfg (void)
> *** 331,336 ****
> --- 330,355 ----
> return todo;
> }
>
> + struct gimple_opt_pass pass_fixup_cfg =
> + {
> + {
> + GIMPLE_PASS,
> + NULL, /* name */
> + NULL, /* gate */
> + execute_fixup_cfg, /* execute */
> + NULL, /* sub */
> + NULL, /* next */
> + 0, /* static_pass_number */
> + 0, /* tv_id */
> + PROP_cfg, /* properties_required */
> + 0, /* properties_provided */
> + 0, /* properties_destroyed */
> + 0, /* todo_flags_start */
> + 0 /* todo_flags_finish */
> + }
> + };
> +
> +
> /* Do the actions required to initialize internal data structures used
> in tree-ssa optimization passes. */
>
> Index: tree-flow.h
> ===================================================================
> *** tree-flow.h (revision 141798)
> --- tree-flow.h (working copy)
> *************** extern bool operation_could_trap_p (enum
> *** 1071,1076 ****
> --- 1071,1077 ----
> extern bool stmt_could_throw_p (gimple);
> extern bool tree_could_throw_p (tree);
> extern bool stmt_can_throw_internal (gimple);
> + extern bool stmt_can_throw_external (gimple);
> extern void add_stmt_to_eh_region (gimple, int);
> extern bool remove_stmt_from_eh_region (gimple);
> extern bool maybe_clean_or_replace_eh_stmt (gimple, gimple);
> Index: passes.c
> ===================================================================
> *** passes.c (revision 141798)
> --- passes.c (working copy)
> *************** init_optimization_passes (void)
> *** 536,541 ****
> --- 536,542 ----
> NEXT_PASS (pass_early_local_passes);
> {
> struct opt_pass **p = &pass_early_local_passes.pass.sub;
> + NEXT_PASS (pass_fixup_cfg);
> NEXT_PASS (pass_tree_profile);
> NEXT_PASS (pass_cleanup_cfg);
> NEXT_PASS (pass_init_datastructures);
> *************** init_optimization_passes (void)
> *** 562,567 ****
> --- 563,569 ----
> NEXT_PASS (pass_tail_recursion);
> NEXT_PASS (pass_convert_switch);
> NEXT_PASS (pass_profile);
> + NEXT_PASS (pass_local_pure_const);
> }
> NEXT_PASS (pass_release_ssa_names);
> NEXT_PASS (pass_rebuild_cgraph_edges);
> *************** init_optimization_passes (void)
> *** 701,706 ****
> --- 703,709 ----
> NEXT_PASS (pass_tail_calls);
> NEXT_PASS (pass_rename_ssa_copies);
> NEXT_PASS (pass_uncprop);
> + NEXT_PASS (pass_local_pure_const);
> }
> NEXT_PASS (pass_del_ssa);
> NEXT_PASS (pass_nrv);
>