ipa-pure-const.c (ignore_edge_for_pure_const): New function.
[gcc.git] / gcc / ipa-pure-const.c
index b9645022b59740b0d0b5b7d64b327244d434c04d..4cf09183f3a23b570bbee777b24f8c32afb54a1f 100644 (file)
@@ -1,5 +1,5 @@
 /* Callgraph based analysis of static variables.
-   Copyright (C) 2004-2014 Free Software Foundation, Inc.
+   Copyright (C) 2004-2015 Free Software Foundation, Inc.
    Contributed by Kenneth Zadeck <zadeck@naturalbridge.com>
 
 This file is part of GCC.
@@ -34,38 +34,28 @@ along with GCC; see the file COPYING3.  If not see
 #include "config.h"
 #include "system.h"
 #include "coretypes.h"
-#include "tm.h"
+#include "backend.h"
+#include "target.h"
 #include "tree.h"
-#include "print-tree.h"
+#include "gimple.h"
+#include "tree-pass.h"
+#include "tree-streamer.h"
+#include "cgraph.h"
+#include "diagnostic.h"
 #include "calls.h"
-#include "basic-block.h"
-#include "tree-ssa-alias.h"
-#include "internal-fn.h"
+#include "cfganal.h"
 #include "tree-eh.h"
-#include "gimple-expr.h"
-#include "is-a.h"
-#include "gimple.h"
 #include "gimple-iterator.h"
 #include "gimple-walk.h"
 #include "tree-cfg.h"
 #include "tree-ssa-loop-niter.h"
-#include "tree-inline.h"
-#include "tree-pass.h"
 #include "langhooks.h"
 #include "ipa-utils.h"
-#include "flags.h"
-#include "diagnostic.h"
 #include "gimple-pretty-print.h"
-#include "langhooks.h"
-#include "target.h"
-#include "lto-streamer.h"
-#include "data-streamer.h"
-#include "tree-streamer.h"
 #include "cfgloop.h"
 #include "tree-scalar-evolution.h"
 #include "intl.h"
 #include "opts.h"
-#include "hash-set.h"
 
 /* Lattice values for const and pure functions.  Everything starts out
    being const, then may drop to pure and then neither depending on
@@ -98,11 +88,15 @@ struct funct_state_d
   bool looping;
 
   bool can_throw;
+
+  /* If function can call free, munmap or otherwise make previously
+     non-trapping memory accesses trapping.  */
+  bool can_free;
 };
 
 /* State used when we know nothing about function.  */
 static struct funct_state_d varying_state
-   = { IPA_NEITHER, IPA_NEITHER, true, true, true };
+   = { IPA_NEITHER, IPA_NEITHER, true, true, true, true };
 
 
 typedef struct funct_state_d * funct_state;
@@ -115,10 +109,45 @@ typedef struct funct_state_d * funct_state;
 
 static vec<funct_state> funct_state_vec;
 
-/* Holders of ipa cgraph hooks: */
-static struct cgraph_node_hook_list *function_insertion_hook_holder;
-static struct cgraph_2node_hook_list *node_duplication_hook_holder;
-static struct cgraph_node_hook_list *node_removal_hook_holder;
+static bool gate_pure_const (void);
+
+namespace {
+
+const pass_data pass_data_ipa_pure_const =
+{
+  IPA_PASS, /* type */
+  "pure-const", /* name */
+  OPTGROUP_NONE, /* optinfo_flags */
+  TV_IPA_PURE_CONST, /* tv_id */
+  0, /* properties_required */
+  0, /* properties_provided */
+  0, /* properties_destroyed */
+  0, /* todo_flags_start */
+  0, /* todo_flags_finish */
+};
+
+class pass_ipa_pure_const : public ipa_opt_pass_d
+{
+public:
+  pass_ipa_pure_const(gcc::context *ctxt);
+
+  /* opt_pass methods: */
+  bool gate (function *) { return gate_pure_const (); }
+  unsigned int execute (function *fun);
+
+  void register_hooks (void);
+
+private:
+  bool init_p;
+
+  /* Holders of ipa cgraph hooks: */
+  struct cgraph_node_hook_list *function_insertion_hook_holder;
+  struct cgraph_2node_hook_list *node_duplication_hook_holder;
+  struct cgraph_node_hook_list *node_removal_hook_holder;
+
+}; // class pass_ipa_pure_const
+
+} // anon namespace
 
 /* Try to guess if function body will always be visible to compiler
    when compiling the call and whether compiler will be able
@@ -451,6 +480,8 @@ special_builtin_state (enum pure_const_state_e *state, bool *looping,
          *looping = true;
          *state = IPA_CONST;
          return true;
+       default:
+         break;
       }
   return false;
 }
@@ -463,7 +494,7 @@ special_builtin_state (enum pure_const_state_e *state, bool *looping,
    the entire call expression.  */
 
 static void
-check_call (funct_state local, gimple call, bool ipa)
+check_call (funct_state local, gcall *call, bool ipa)
 {
   int flags = gimple_call_flags (call);
   tree callee_t = gimple_call_fndecl (call);
@@ -508,6 +539,10 @@ check_call (funct_state local, gimple call, bool ipa)
       enum pure_const_state_e call_state;
       bool call_looping;
 
+      if (gimple_call_builtin_p (call, BUILT_IN_NORMAL)
+         && !nonfreeing_call_p (call))
+       local->can_free = true;
+
       if (special_builtin_state (&call_state, &call_looping, callee_t))
        {
          worse_state (&local->pure_const_state, &local->looping,
@@ -538,6 +573,8 @@ check_call (funct_state local, gimple call, bool ipa)
            break;
          }
     }
+  else if (gimple_call_internal_p (call) && !nonfreeing_call_p (call))
+    local->can_free = true;
 
   /* When not in IPA mode, we can still handle self recursion.  */
   if (!ipa && callee_t
@@ -588,7 +625,7 @@ check_call (funct_state local, gimple call, bool ipa)
 /* Wrapper around check_decl for loads in local more.  */
 
 static bool
-check_load (gimple, tree op, tree, void *data)
+check_load (gimple *, tree op, tree, void *data)
 {
   if (DECL_P (op))
     check_decl ((funct_state)data, op, false, false);
@@ -600,7 +637,7 @@ check_load (gimple, tree op, tree, void *data)
 /* Wrapper around check_decl for stores in local more.  */
 
 static bool
-check_store (gimple, tree op, tree, void *data)
+check_store (gimple *, tree op, tree, void *data)
 {
   if (DECL_P (op))
     check_decl ((funct_state)data, op, true, false);
@@ -612,7 +649,7 @@ check_store (gimple, tree op, tree, void *data)
 /* Wrapper around check_decl for loads in ipa mode.  */
 
 static bool
-check_ipa_load (gimple, tree op, tree, void *data)
+check_ipa_load (gimple *, tree op, tree, void *data)
 {
   if (DECL_P (op))
     check_decl ((funct_state)data, op, false, true);
@@ -624,7 +661,7 @@ check_ipa_load (gimple, tree op, tree, void *data)
 /* Wrapper around check_decl for stores in ipa mode.  */
 
 static bool
-check_ipa_store (gimple, tree op, tree, void *data)
+check_ipa_store (gimple *, tree op, tree, void *data)
 {
   if (DECL_P (op))
     check_decl ((funct_state)data, op, true, true);
@@ -638,11 +675,21 @@ check_ipa_store (gimple, tree op, tree, void *data)
 static void
 check_stmt (gimple_stmt_iterator *gsip, funct_state local, bool ipa)
 {
-  gimple stmt = gsi_stmt (*gsip);
+  gimple *stmt = gsi_stmt (*gsip);
 
   if (is_gimple_debug (stmt))
     return;
 
+  /* Do consider clobber as side effects before IPA, so we rather inline
+     C++ destructors and keep clobber semantics than eliminate them.
+
+     TODO: We may get smarter during early optimizations on these and let
+     functions containing only clobbers to be optimized more.  This is a common
+     case of C++ destructors.  */
+
+  if ((ipa || cfun->after_inlining) && gimple_clobber_p (stmt))
+    return;
+
   if (dump_file)
     {
       fprintf (dump_file, "  scanning: ");
@@ -684,10 +731,10 @@ check_stmt (gimple_stmt_iterator *gsip, funct_state local, bool ipa)
   switch (gimple_code (stmt))
     {
     case GIMPLE_CALL:
-      check_call (local, stmt, ipa);
+      check_call (local, as_a <gcall *> (stmt), ipa);
       break;
     case GIMPLE_LABEL:
-      if (DECL_NONLOCAL (gimple_label_label (stmt)))
+      if (DECL_NONLOCAL (gimple_label_label (as_a <glabel *> (stmt))))
        /* Target of long jump. */
        {
           if (dump_file)
@@ -696,20 +743,22 @@ check_stmt (gimple_stmt_iterator *gsip, funct_state local, bool ipa)
        }
       break;
     case GIMPLE_ASM:
-      if (gimple_asm_clobbers_memory_p (stmt))
+      if (gimple_asm_clobbers_memory_p (as_a <gasm *> (stmt)))
        {
          if (dump_file)
            fprintf (dump_file, "    memory asm clobber is not const/pure\n");
          /* Abandon all hope, ye who enter here. */
          local->pure_const_state = IPA_NEITHER;
+         local->can_free = true;
        }
-      if (gimple_asm_volatile_p (stmt))
+      if (gimple_asm_volatile_p (as_a <gasm *> (stmt)))
        {
          if (dump_file)
            fprintf (dump_file, "    volatile is not const/pure\n");
          /* Abandon all hope, ye who enter here. */
          local->pure_const_state = IPA_NEITHER;
-          local->looping = true;
+         local->looping = true;
+         local->can_free = true;
        }
       return;
     default:
@@ -734,6 +783,7 @@ analyze_function (struct cgraph_node *fn, bool ipa)
   l->looping_previously_known = true;
   l->looping = false;
   l->can_throw = false;
+  l->can_free = false;
   state_from_flags (&l->state_previously_known, &l->looping_previously_known,
                    flags_from_decl_or_type (fn->decl),
                    fn->cannot_return_p ());
@@ -742,6 +792,8 @@ analyze_function (struct cgraph_node *fn, bool ipa)
     {
       /* Thunk gets propagated through, so nothing interesting happens.  */
       gcc_assert (ipa);
+      if (fn->thunk.thunk_p && fn->thunk.virtual_offset_p)
+       l->pure_const_state = IPA_NEITHER;
       return l;
     }
 
@@ -764,7 +816,10 @@ analyze_function (struct cgraph_node *fn, bool ipa)
           gsi_next (&gsi))
        {
          check_stmt (&gsi, l, ipa);
-         if (l->pure_const_state == IPA_NEITHER && l->looping && l->can_throw)
+         if (l->pure_const_state == IPA_NEITHER
+             && l->looping
+             && l->can_throw
+             && l->can_free)
            goto end;
        }
     }
@@ -831,6 +886,8 @@ end:
         fprintf (dump_file, "Function is locally const.\n");
       if (l->pure_const_state == IPA_PURE)
         fprintf (dump_file, "Function is locally pure.\n");
+      if (l->can_free)
+       fprintf (dump_file, "Function can locally free.\n");
     }
   return l;
 }
@@ -845,7 +902,8 @@ add_new_function (struct cgraph_node *node, void *data ATTRIBUTE_UNUSED)
      static declarations.  We do not need to scan them more than once
      since all we would be interested in are the addressof
      operations.  */
-  if (node->get_availability () > AVAIL_INTERPOSABLE)
+  if (node->get_availability () > AVAIL_INTERPOSABLE
+      && opt_for_fn (node->decl, flag_ipa_pure_const))
     set_function_state (node, analyze_function (node, true));
 }
 
@@ -879,22 +937,21 @@ remove_node_data (struct cgraph_node *node, void *data ATTRIBUTE_UNUSED)
 }
 
 \f
-static void
+void
+pass_ipa_pure_const::
 register_hooks (void)
 {
-  static bool init_p = false;
-
   if (init_p)
     return;
 
   init_p = true;
 
   node_removal_hook_holder =
-      cgraph_add_node_removal_hook (&remove_node_data, NULL);
+      symtab->add_cgraph_removal_hook (&remove_node_data, NULL);
   node_duplication_hook_holder =
-      cgraph_add_node_duplication_hook (&duplicate_node_data, NULL);
+      symtab->add_cgraph_duplication_hook (&duplicate_node_data, NULL);
   function_insertion_hook_holder =
-      cgraph_add_function_insertion_hook (&add_new_function, NULL);
+      symtab->add_cgraph_insertion_hook (&add_new_function, NULL);
 }
 
 
@@ -906,7 +963,8 @@ pure_const_generate_summary (void)
 {
   struct cgraph_node *node;
 
-  register_hooks ();
+  pass_ipa_pure_const *pass = static_cast <pass_ipa_pure_const *> (current_pass);
+  pass->register_hooks ();
 
   /* Process all of the functions.
 
@@ -915,7 +973,8 @@ pure_const_generate_summary (void)
      when function got cloned and the clone is AVAILABLE.  */
 
   FOR_EACH_DEFINED_FUNCTION (node)
-    if (node->get_availability () >= AVAIL_INTERPOSABLE)
+    if (node->get_availability () >= AVAIL_INTERPOSABLE
+        && opt_for_fn (node->decl, flag_ipa_pure_const))
       set_function_state (node, analyze_function (node, true));
 }
 
@@ -970,6 +1029,7 @@ pure_const_write_summary (void)
          bp_pack_value (&bp, fs->looping_previously_known, 1);
          bp_pack_value (&bp, fs->looping, 1);
          bp_pack_value (&bp, fs->can_throw, 1);
+         bp_pack_value (&bp, fs->can_free, 1);
          streamer_write_bitpack (&bp);
        }
     }
@@ -987,7 +1047,9 @@ pure_const_read_summary (void)
   struct lto_file_decl_data *file_data;
   unsigned int j = 0;
 
-  register_hooks ();
+  pass_ipa_pure_const *pass = static_cast <pass_ipa_pure_const *> (current_pass);
+  pass->register_hooks ();
+
   while ((file_data = file_data_vec[j++]))
     {
       const char *data;
@@ -1027,6 +1089,7 @@ pure_const_read_summary (void)
              fs->looping_previously_known = bp_unpack_value (&bp, 1);
              fs->looping = bp_unpack_value (&bp, 1);
              fs->can_throw = bp_unpack_value (&bp, 1);
+             fs->can_free = bp_unpack_value (&bp, 1);
              if (dump_file)
                {
                  int flags = flags_from_decl_or_type (node->decl);
@@ -1049,6 +1112,8 @@ pure_const_read_summary (void)
                    fprintf (dump_file,"  function is previously known looping\n");
                  if (fs->can_throw)
                    fprintf (dump_file,"  function is locally throwing\n");
+                 if (fs->can_free)
+                   fprintf (dump_file,"  function can locally free\n");
                }
            }
 
@@ -1059,11 +1124,18 @@ pure_const_read_summary (void)
     }
 }
 
+/* We only propagate across edges that can throw externally and their callee
+   is not interposable.  */
 
 static bool
-ignore_edge (struct cgraph_edge *e)
+ignore_edge_for_nothrow (struct cgraph_edge *e)
 {
-  return (!e->can_throw_external);
+  if (!e->can_throw_external || TREE_NOTHROW (e->callee->decl))
+    return true;
+
+  enum availability avail;
+  cgraph_node *n = e->callee->function_or_virtual_thunk_symbol (&avail);
+  return (avail <= AVAIL_INTERPOSABLE || TREE_NOTHROW (n->decl));
 }
 
 /* Return true if NODE is self recursive function.
@@ -1081,21 +1153,45 @@ self_recursive_p (struct cgraph_node *node)
   return false;
 }
 
+/* Return true if N is cdtor that is not const or pure.  In this case we may
+   need to remove unreachable function if it is marked const/pure.  */
+
+static bool
+cdtor_p (cgraph_node *n, void *)
+{
+  if (DECL_STATIC_CONSTRUCTOR (n->decl) || DECL_STATIC_DESTRUCTOR (n->decl))
+    return !TREE_READONLY (n->decl) && !DECL_PURE_P (n->decl);
+  return false;
+}
+
+/* We only propagate across edges with non-interposable callee.  */
+
+static bool
+ignore_edge_for_pure_const (struct cgraph_edge *e)
+{
+  enum availability avail;
+  e->callee->function_or_virtual_thunk_symbol (&avail);
+  return (avail <= AVAIL_INTERPOSABLE);
+}
+
+
 /* Produce transitive closure over the callgraph and compute pure/const
    attributes.  */
 
-static void
+static bool
 propagate_pure_const (void)
 {
   struct cgraph_node *node;
   struct cgraph_node *w;
   struct cgraph_node **order =
-    XCNEWVEC (struct cgraph_node *, cgraph_n_nodes);
+    XCNEWVEC (struct cgraph_node *, symtab->cgraph_count);
   int order_pos;
   int i;
   struct ipa_dfs_info * w_info;
+  bool remove_p = false;
 
-  order_pos = ipa_reduced_postorder (order, true, false, NULL);
+  order_pos = ipa_reduced_postorder (order, true, false,
+                                    ignore_edge_for_pure_const);
   if (dump_file)
     {
       cgraph_node::dump_cgraph (dump_file);
@@ -1142,7 +1238,7 @@ propagate_pure_const (void)
          if (pure_const_state == IPA_NEITHER)
            break;
 
-         /* For overwritable nodes we can not assume anything.  */
+         /* For interposable nodes we can not assume anything.  */
          if (w->get_availability () == AVAIL_INTERPOSABLE)
            {
              worse_state (&pure_const_state, &looping,
@@ -1151,7 +1247,7 @@ propagate_pure_const (void)
              if (dump_file && (dump_flags & TDF_DETAILS))
                {
                  fprintf (dump_file,
-                          "    Overwritable. state %s looping %i\n",
+                          "    Interposable. state %s looping %i\n",
                           pure_const_names[w_l->state_previously_known],
                           w_l->looping_previously_known);
                }
@@ -1167,10 +1263,12 @@ propagate_pure_const (void)
            looping = true;
 
          /* Now walk the edges and merge in callee properties.  */
-         for (e = w->callees; e; e = e->next_callee)
+         for (e = w->callees; e && pure_const_state != IPA_NEITHER;
+              e = e->next_callee)
            {
              enum availability avail;
-             struct cgraph_node *y = e->callee->function_symbol (&avail);
+             struct cgraph_node *y = e->callee->
+                               function_or_virtual_thunk_symbol (&avail);
              enum pure_const_state_e edge_state = IPA_CONST;
              bool edge_looping = false;
 
@@ -1192,7 +1290,7 @@ propagate_pure_const (void)
                               y_l->looping);
                    }
                  if (y_l->pure_const_state > IPA_PURE
-                     && cgraph_edge_cannot_lead_to_return (e))
+                     && e->cannot_lead_to_return_p ())
                    {
                      if (dump_file && (dump_flags & TDF_DETAILS))
                        fprintf (dump_file,
@@ -1213,7 +1311,7 @@ propagate_pure_const (void)
              else
                state_from_flags (&edge_state, &edge_looping,
                                  flags_from_decl_or_type (y->decl),
-                                 cgraph_edge_cannot_lead_to_return (e));
+                                 e->cannot_lead_to_return_p ());
 
              /* Merge the results with what we already know.  */
              better_state (&edge_state, &edge_looping,
@@ -1224,11 +1322,10 @@ propagate_pure_const (void)
              if (pure_const_state == IPA_NEITHER)
                break;
            }
-         if (pure_const_state == IPA_NEITHER)
-           break;
 
          /* Now process the indirect call.  */
-          for (ie = w->indirect_calls; ie; ie = ie->next_callee)
+          for (ie = w->indirect_calls;
+              ie && pure_const_state != IPA_NEITHER; ie = ie->next_callee)
            {
              enum pure_const_state_e edge_state = IPA_CONST;
              bool edge_looping = false;
@@ -1237,7 +1334,7 @@ propagate_pure_const (void)
                fprintf (dump_file, "    Indirect call");
              state_from_flags (&edge_state, &edge_looping,
                                ie->indirect_info->ecf_flags,
-                               cgraph_edge_cannot_lead_to_return (ie));
+                               ie->cannot_lead_to_return_p ());
              /* Merge the results with what we already know.  */
              better_state (&edge_state, &edge_looping,
                            w_l->state_previously_known,
@@ -1247,11 +1344,10 @@ propagate_pure_const (void)
              if (pure_const_state == IPA_NEITHER)
                break;
            }
-         if (pure_const_state == IPA_NEITHER)
-           break;
 
          /* And finally all loads and stores.  */
-         for (i = 0; w->iterate_reference (i, ref); i++)
+         for (i = 0; w->iterate_reference (i, ref)
+              && pure_const_state != IPA_NEITHER; i++)
            {
              enum pure_const_state_e ref_state = IPA_CONST;
              bool ref_looping = false;
@@ -1273,6 +1369,7 @@ propagate_pure_const (void)
                    fprintf (dump_file, "    global var write\n");
                  break;
                case IPA_REF_ADDR:
+               case IPA_REF_CHKP:
                  break;
                default:
                  gcc_unreachable ();
@@ -1293,6 +1390,34 @@ propagate_pure_const (void)
                 pure_const_names [pure_const_state],
                 looping);
 
+      /* Find the worst state of can_free for any node in the cycle.  */
+      bool can_free = false;
+      w = node;
+      while (w && !can_free)
+       {
+         struct cgraph_edge *e;
+         funct_state w_l = get_function_state (w);
+
+         if (w_l->can_free
+             || w->get_availability () == AVAIL_INTERPOSABLE
+             || w->indirect_calls)
+           can_free = true;
+
+         for (e = w->callees; e && !can_free; e = e->next_callee)
+           {
+             enum availability avail;
+             struct cgraph_node *y = e->callee->
+                               function_or_virtual_thunk_symbol (&avail);
+
+             if (avail > AVAIL_INTERPOSABLE)
+               can_free = get_function_state (y)->can_free;
+             else
+               can_free = true;
+           }
+         w_info = (struct ipa_dfs_info *) w->aux;
+         w = w_info->next_cycle;
+       }
+
       /* Copy back the region's pure_const_state which is shared by
         all nodes in the region.  */
       w = node;
@@ -1302,11 +1427,18 @@ propagate_pure_const (void)
          enum pure_const_state_e this_state = pure_const_state;
          bool this_looping = looping;
 
+         w_l->can_free = can_free;
+         w->nonfreeing_fn = !can_free;
+         if (!can_free && dump_file)
+           fprintf (dump_file, "Function found not to call free: %s\n",
+                    w->name ());
+
          if (w_l->state_previously_known != IPA_NEITHER
              && this_state > w_l->state_previously_known)
            {
               this_state = w_l->state_previously_known;
-             this_looping |= w_l->looping_previously_known;
+             if (this_state == IPA_NEITHER)
+               this_looping = w_l->looping_previously_known;
            }
          if (!this_looping && self_recursive_p (w))
            this_looping = true;
@@ -1332,6 +1464,8 @@ propagate_pure_const (void)
                               this_looping ? "looping " : "",
                               w->name ());
                  }
+               remove_p |= w->call_for_symbol_and_aliases (cdtor_p,
+                                                           NULL, true);
                w->set_const_flag (true, this_looping);
                break;
 
@@ -1344,6 +1478,8 @@ propagate_pure_const (void)
                               this_looping ? "looping " : "",
                               w->name ());
                  }
+               remove_p |= w->call_for_symbol_and_aliases (cdtor_p,
+                                                           NULL, true);
                w->set_pure_flag (true, this_looping);
                break;
 
@@ -1357,6 +1493,7 @@ propagate_pure_const (void)
 
   ipa_free_postorder_info ();
   free (order);
+  return remove_p;
 }
 
 /* Produce transitive closure over the callgraph and compute nothrow
@@ -1368,12 +1505,13 @@ propagate_nothrow (void)
   struct cgraph_node *node;
   struct cgraph_node *w;
   struct cgraph_node **order =
-    XCNEWVEC (struct cgraph_node *, cgraph_n_nodes);
+    XCNEWVEC (struct cgraph_node *, symtab->cgraph_count);
   int order_pos;
   int i;
   struct ipa_dfs_info * w_info;
 
-  order_pos = ipa_reduced_postorder (order, true, false, ignore_edge);
+  order_pos = ipa_reduced_postorder (order, true, false,
+                                    ignore_edge_for_nothrow);
   if (dump_file)
     {
       cgraph_node::dump_cgraph (dump_file);
@@ -1394,42 +1532,41 @@ propagate_nothrow (void)
 
       /* Find the worst state for any node in the cycle.  */
       w = node;
-      while (w)
+      while (w && !can_throw)
        {
          struct cgraph_edge *e, *ie;
-         funct_state w_l = get_function_state (w);
-
-         if (w_l->can_throw
-             || w->get_availability () == AVAIL_INTERPOSABLE)
-           can_throw = true;
-
-         if (can_throw)
-           break;
 
-         for (e = w->callees; e; e = e->next_callee)
+         if (!TREE_NOTHROW (w->decl))
            {
-             enum availability avail;
-             struct cgraph_node *y = e->callee->function_symbol (&avail);
+             funct_state w_l = get_function_state (w);
 
-             if (avail > AVAIL_INTERPOSABLE)
+             if (w_l->can_throw
+                 || w->get_availability () == AVAIL_INTERPOSABLE)
+               can_throw = true;
+
+             for (e = w->callees; e && !can_throw; e = e->next_callee)
                {
-                 funct_state y_l = get_function_state (y);
+                 enum availability avail;
 
-                 if (can_throw)
-                   break;
-                 if (y_l->can_throw && !TREE_NOTHROW (w->decl)
-                     && e->can_throw_external)
+                 if (!e->can_throw_external || TREE_NOTHROW (e->callee->decl))
+                   continue;
+
+                 struct cgraph_node *y = e->callee->
+                                   function_or_virtual_thunk_symbol (&avail);
+
+                 /* We can use info about the callee only if we know it can
+                    not be interposed.  */
+                 if (avail <= AVAIL_INTERPOSABLE
+                     || (!TREE_NOTHROW (y->decl)
+                         && get_function_state (y)->can_throw))
                    can_throw = true;
                }
-             else if (e->can_throw_external && !TREE_NOTHROW (y->decl))
-               can_throw = true;
+             for (ie = w->indirect_calls; ie && !can_throw;
+                  ie = ie->next_callee)
+               if (ie->can_throw_external
+                   && !(ie->indirect_info->ecf_flags & ECF_NOTHROW))
+                 can_throw = true;
            }
-          for (ie = node->indirect_calls; ie; ie = ie->next_callee)
-           if (ie->can_throw_external)
-             {
-               can_throw = true;
-               break;
-             }
          w_info = (struct ipa_dfs_info *) w->aux;
          w = w_info->next_cycle;
        }
@@ -1468,74 +1605,53 @@ propagate_nothrow (void)
 /* Produce the global information by preforming a transitive closure
    on the local information that was produced by generate_summary.  */
 
-static unsigned int
-propagate (void)
+unsigned int
+pass_ipa_pure_const::
+execute (function *)
 {
   struct cgraph_node *node;
+  bool remove_p;
 
-  cgraph_remove_function_insertion_hook (function_insertion_hook_holder);
-  cgraph_remove_node_duplication_hook (node_duplication_hook_holder);
-  cgraph_remove_node_removal_hook (node_removal_hook_holder);
+  symtab->remove_cgraph_insertion_hook (function_insertion_hook_holder);
+  symtab->remove_cgraph_duplication_hook (node_duplication_hook_holder);
+  symtab->remove_cgraph_removal_hook (node_removal_hook_holder);
 
   /* Nothrow makes more function to not lead to return and improve
      later analysis.  */
   propagate_nothrow ();
-  propagate_pure_const ();
+  remove_p = propagate_pure_const ();
 
   /* Cleanup. */
   FOR_EACH_FUNCTION (node)
     if (has_function_state (node))
       free (get_function_state (node));
   funct_state_vec.release ();
-  return 0;
+  return remove_p ? TODO_remove_functions : 0;
 }
 
 static bool
 gate_pure_const (void)
 {
-  return (flag_ipa_pure_const
-         /* Don't bother doing anything if the program has errors.  */
-         && !seen_error ());
+  return flag_ipa_pure_const || in_lto_p;
 }
 
-namespace {
-
-const pass_data pass_data_ipa_pure_const =
-{
-  IPA_PASS, /* type */
-  "pure-const", /* name */
-  OPTGROUP_NONE, /* optinfo_flags */
-  TV_IPA_PURE_CONST, /* tv_id */
-  0, /* properties_required */
-  0, /* properties_provided */
-  0, /* properties_destroyed */
-  0, /* todo_flags_start */
-  0, /* todo_flags_finish */
-};
-
-class pass_ipa_pure_const : public ipa_opt_pass_d
+pass_ipa_pure_const::pass_ipa_pure_const(gcc::context *ctxt)
+    : ipa_opt_pass_d(pass_data_ipa_pure_const, ctxt,
+                    pure_const_generate_summary, /* generate_summary */
+                    pure_const_write_summary, /* write_summary */
+                    pure_const_read_summary, /* read_summary */
+                    NULL, /* write_optimization_summary */
+                    NULL, /* read_optimization_summary */
+                    NULL, /* stmt_fixup */
+                    0, /* function_transform_todo_flags_start */
+                    NULL, /* function_transform */
+                    NULL), /* variable_transform */
+  init_p(false),
+  function_insertion_hook_holder(NULL),
+  node_duplication_hook_holder(NULL),
+  node_removal_hook_holder(NULL)
 {
-public:
-  pass_ipa_pure_const (gcc::context *ctxt)
-    : ipa_opt_pass_d (pass_data_ipa_pure_const, ctxt,
-                     pure_const_generate_summary, /* generate_summary */
-                     pure_const_write_summary, /* write_summary */
-                     pure_const_read_summary, /* read_summary */
-                     NULL, /* write_optimization_summary */
-                     NULL, /* read_optimization_summary */
-                     NULL, /* stmt_fixup */
-                     0, /* function_transform_todo_flags_start */
-                     NULL, /* function_transform */
-                     NULL) /* variable_transform */
-  {}
-
-  /* opt_pass methods: */
-  virtual bool gate (function *) { return gate_pure_const (); }
-  virtual unsigned int execute (function *) { return propagate (); }
-
-}; // class pass_ipa_pure_const
-
-} // anon namespace
+}
 
 ipa_opt_pass_d *
 make_pass_ipa_pure_const (gcc::context *ctxt)
@@ -1560,7 +1676,7 @@ skip_function_for_local_pure_const (struct cgraph_node *node)
   if (node->get_availability () <= AVAIL_INTERPOSABLE)
     {
       if (dump_file)
-        fprintf (dump_file, "Function is not available or overwritable; not analyzing.\n");
+        fprintf (dump_file, "Function is not available or interposable; not analyzing.\n");
       return true;
     }
   return false;
@@ -1761,4 +1877,95 @@ make_pass_warn_function_noreturn (gcc::context *ctxt)
   return new pass_warn_function_noreturn (ctxt);
 }
 
+/* Simple local pass for pure const discovery reusing the analysis from
+   ipa_pure_const.   This pass is effective when executed together with
+   other optimization passes in early optimization pass queue.  */
+
+namespace {
 
+const pass_data pass_data_nothrow =
+{
+  GIMPLE_PASS, /* type */
+  "nothrow", /* name */
+  OPTGROUP_NONE, /* optinfo_flags */
+  TV_IPA_PURE_CONST, /* tv_id */
+  0, /* properties_required */
+  0, /* properties_provided */
+  0, /* properties_destroyed */
+  0, /* todo_flags_start */
+  0, /* todo_flags_finish */
+};
+
+class pass_nothrow : public gimple_opt_pass
+{
+public:
+  pass_nothrow (gcc::context *ctxt)
+    : gimple_opt_pass (pass_data_nothrow, ctxt)
+  {}
+
+  /* opt_pass methods: */
+  opt_pass * clone () { return new pass_nothrow (m_ctxt); }
+  virtual bool gate (function *) { return optimize; }
+  virtual unsigned int execute (function *);
+
+}; // class pass_nothrow
+
+unsigned int
+pass_nothrow::execute (function *)
+{
+  struct cgraph_node *node;
+  basic_block this_block;
+
+  if (TREE_NOTHROW (current_function_decl))
+    return 0;
+
+  node = cgraph_node::get (current_function_decl);
+
+  /* We run during lowering, we can not really use availability yet.  */
+  if (cgraph_node::get (current_function_decl)->get_availability ()
+      <= AVAIL_INTERPOSABLE)
+    {
+      if (dump_file)
+        fprintf (dump_file, "Function is interposable;"
+                " not analyzing.\n");
+      return true;
+    }
+
+  FOR_EACH_BB_FN (this_block, cfun)
+    {
+      for (gimple_stmt_iterator gsi = gsi_start_bb (this_block);
+          !gsi_end_p (gsi);
+          gsi_next (&gsi))
+        if (stmt_can_throw_external (gsi_stmt (gsi)))
+         {
+           if (is_gimple_call (gsi_stmt (gsi)))
+             {
+               tree callee_t = gimple_call_fndecl (gsi_stmt (gsi));
+               if (callee_t && recursive_call_p (current_function_decl,
+                                                 callee_t))
+                 continue;
+             }
+       
+           if (dump_file)
+             {
+               fprintf (dump_file, "Statement can throw: ");
+               print_gimple_stmt (dump_file, gsi_stmt (gsi), 0, 0);
+             }
+           return 0;
+         }
+    }
+
+  node->set_nothrow_flag (true);
+  if (dump_file)
+    fprintf (dump_file, "Function found to be nothrow: %s\n",
+            current_function_name ());
+  return 0;
+}
+
+} // anon namespace
+
+gimple_opt_pass *
+make_pass_nothrow (gcc::context *ctxt)
+{
+  return new pass_nothrow (ctxt);
+}