Rewrite of pure/const pass and nothrow stuff

Jan Hubicka jh@suse.cz
Thu Nov 13 20:08:00 GMT 2008


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.

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);



More information about the Gcc-patches mailing list