diff gcc/ipa-pure-const.c @ 0:a06113de4d67

first commit
author kent <kent@cr.ie.u-ryukyu.ac.jp>
date Fri, 17 Jul 2009 14:47:48 +0900
parents
children 77e2b8dfacca
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gcc/ipa-pure-const.c	Fri Jul 17 14:47:48 2009 +0900
@@ -0,0 +1,937 @@
+/* Callgraph based analysis of static variables.
+   Copyright (C) 2004, 2005, 2007, 2008 Free Software Foundation, Inc.
+   Contributed by Kenneth Zadeck <zadeck@naturalbridge.com>
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+/* 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
+   consistent with post inlined state.  The global sets are not prone
+   to this problem since they are by definition transitive.  */
+
+/* The code in this module is called by the ipa pass manager. It
+   should be one of the later passes since it's information is used by
+   the rest of the compilation. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tm.h"
+#include "tree.h"
+#include "tree-flow.h"
+#include "tree-inline.h"
+#include "tree-pass.h"
+#include "langhooks.h"
+#include "pointer-set.h"
+#include "ggc.h"
+#include "ipa-utils.h"
+#include "c-common.h"
+#include "gimple.h"
+#include "cgraph.h"
+#include "output.h"
+#include "flags.h"
+#include "timevar.h"
+#include "diagnostic.h"
+#include "langhooks.h"
+#include "target.h"
+
+static struct pointer_set_t *visited_nodes;
+
+/* Lattice values for const and pure functions.  Everything starts out
+   being const, then may drop to pure and then neither depending on
+   what is found.  */
+enum pure_const_state_e
+{
+  IPA_CONST,
+  IPA_PURE,
+  IPA_NEITHER
+};
+
+/* 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;
+
+  /* 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;
+
+  /* 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;
+
+/* 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)
+{
+  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 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;
+      local->looping = false;
+      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;
+      local->looping = false;
+      return;
+    }
+
+  /* Do not care about a local automatic that is not static.  */
+  if (!TREE_STATIC (t) && !DECL_EXTERNAL (t))
+    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->looping = false;
+      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.  */
+      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);
+}
+
+/* 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)
+    {
+      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, 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
+   there are any references in the parameters that are not allowed for
+   pure or const functions.  Also check to see if this is either an
+   indirect call, a call outside the compilation unit, or has special
+   attributes that may also effect the purity.  The CALL_EXPR node for
+   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
+     for the callee, (such as for some of the builtins) we will use
+     them, otherwise we will compute our own information. 
+  
+     Const and pure functions have less clobber effects than other
+     functions so we process these first.  Otherwise if it is a call
+     outside the compilation unit or an indirect call we punt.  This
+     leaves local calls which will be processed by following the call
+     graph.  */  
+  if (callee_t)
+    {
+      callee = cgraph_node(callee_t);
+      avail = cgraph_function_body_availability (callee);
+
+      /* 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;
+	  local->looping = false;
+	}
+
+      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:
+	    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.  */
+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 ();
+  analyze_function (node);
+  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);
+    }
+}
+
+/* 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))
+    {
+      free (get_function_state (node));
+      set_function_state (node, 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;
+
+  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);
+  /* 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 ();
+
+  /* Process all of the functions. 
+
+     We do NOT process any AVAIL_OVERWRITABLE functions, we cannot
+     guarantee that what we learn about the one we see will be true
+     for the one that overrides it.
+  */
+  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;
+}
+
+/* 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);
+  if (dump_file)
+    {
+      dump_cgraph (dump_file);
+      ipa_utils_print_order(dump_file, "reduced", 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++ )
+    {
+      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)
+	{
+	  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;
+	  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);
+
+	  /* 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;
+	}
+    }
+
+  /* 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;
+	}
+      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_ipa_pure_const
+	  /* Don't bother doing anything if the program has errors.  */
+	  && !(errorcount || sorrycount));
+}
+
+struct ipa_opt_pass pass_ipa_pure_const =
+{
+ {
+  IPA_PASS,
+  "pure-const",		                /* name */
+  gate_pure_const,			/* gate */
+  propagate,			        /* 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 */
+ },
+ generate_summary,		        /* generate_summary */
+ NULL,					/* write_summary */
+ NULL,					/* read_summary */
+ NULL,					/* function_read_summary */
+ 0,					/* TODOs */
+ NULL,			                /* function_transform */
+ NULL					/* variable_transform */
+};