diff mbox

Discover nothorow functions before into_ssa

Message ID 20150326063251.GA29341@kam.mff.cuni.cz
State New
Headers show

Commit Message

Jan Hubicka March 26, 2015, 6:32 a.m. UTC
Hi,
this patch (as suggested by Richard) adds very simple discovery of
DECL_NOTHORW to build_ssa passes.

The reason is that in 4.9 we did build_ssa in parallel with early optimization
that does nothrow discovery as part of local pure const.  Bounds checking
patches broke the pass queue into multiple lasses and we produce a lot more
statements when notrhow is not identifier early.

I went with specialized pass because I do not want to pay the cost of
local pure const building loop structure to prove that function is pure/const.
We really care about this just later. I also tested a variant making this pass
part of early lowering passes.  This does not work that well, because these
are not run in topological order and C++ FE already does its own nothrow
discovery, so it handled only about 900 calls on tramp3d.
This pass handles about 3500 calls and additional 3000 calls are handled
by the followup ipa-pure-const pass (probably because of extra code removal).

Adding pass causes cgraph verifier to fail.  The reason is that now fixup_cfg
pass at begging of ssa passes actually does some dead code removal. This
makes cgraph edges out of date and they are not rebuild at the end of the
passes.

Instead of triggering yet another rebuild, which would be somewhat redundant
given that early passes rebuilds the edges again, I just changed cgraph
verifier to not compare calleers frequencies, but do callees.  This way
we reduce some work, too.

Doing this I removed one very old FIXME about verificatoin that pointed out
latent bug in set_edge_predicate. Fixed thus.

Bootstrapped/regtested x86_64-linux, OK?

	* cgraph.c (cgraph_edge::verify_count_and_frequency): Remove testing
	of frequency and bb match.
	(cgraph_node::verify_node): Do it here on callees only.
	* passes.def: Add pass_nothrow.
	* ipa-pure-const.c: (pass_data_nothrow): New.
	(pass_nothrow): New.
	(pass_nothrow::execute): New.
	(make_pass_nothrow): New.
	* tree-pass.h (make_pass_nothrow): Declare.
	* ipa-inline.c (set_edge_predicate): Also redirect indirect
	edges.

Comments

Richard Biener March 26, 2015, 9:04 a.m. UTC | #1
On Thu, 26 Mar 2015, Jan Hubicka wrote:

> Hi,
> this patch (as suggested by Richard) adds very simple discovery of
> DECL_NOTHORW to build_ssa passes.
> 
> The reason is that in 4.9 we did build_ssa in parallel with early optimization
> that does nothrow discovery as part of local pure const.  Bounds checking
> patches broke the pass queue into multiple lasses and we produce a lot more
> statements when notrhow is not identifier early.
> 
> I went with specialized pass because I do not want to pay the cost of
> local pure const building loop structure to prove that function is pure/const.
> We really care about this just later. I also tested a variant making this pass
> part of early lowering passes.  This does not work that well, because these
> are not run in topological order and C++ FE already does its own nothrow
> discovery, so it handled only about 900 calls on tramp3d.
> This pass handles about 3500 calls and additional 3000 calls are handled
> by the followup ipa-pure-const pass (probably because of extra code removal).
> 
> Adding pass causes cgraph verifier to fail.  The reason is that now fixup_cfg
> pass at begging of ssa passes actually does some dead code removal. This
> makes cgraph edges out of date and they are not rebuild at the end of the
> passes.
> 
> Instead of triggering yet another rebuild, which would be somewhat redundant
> given that early passes rebuilds the edges again, I just changed cgraph
> verifier to not compare calleers frequencies, but do callees.  This way
> we reduce some work, too.

Does that work on callgraph cycles?

> 
> Doing this I removed one very old FIXME about verificatoin that pointed out
> latent bug in set_edge_predicate. Fixed thus.

Can you commit this separately please?
 
> Bootstrapped/regtested x86_64-linux, OK?

See below.

> 	* cgraph.c (cgraph_edge::verify_count_and_frequency): Remove testing
> 	of frequency and bb match.
> 	(cgraph_node::verify_node): Do it here on callees only.
> 	* passes.def: Add pass_nothrow.
> 	* ipa-pure-const.c: (pass_data_nothrow): New.
> 	(pass_nothrow): New.
> 	(pass_nothrow::execute): New.
> 	(make_pass_nothrow): New.
> 	* tree-pass.h (make_pass_nothrow): Declare.
> 	* ipa-inline.c (set_edge_predicate): Also redirect indirect
> 	edges.
> Index: cgraph.c
> ===================================================================
> --- cgraph.c	(revision 221682)
> +++ cgraph.c	(working copy)
> @@ -2661,25 +2661,6 @@ cgraph_edge::verify_count_and_frequency
>        error ("caller edge frequency is too large");
>        error_found = true;
>      }
> -  if (gimple_has_body_p (caller->decl)
> -      && !caller->global.inlined_to
> -      && !speculative
> -      /* FIXME: Inline-analysis sets frequency to 0 when edge is optimized out.
> -	 Remove this once edges are actually removed from the function at that time.  */
> -      && (frequency
> -	  || (inline_edge_summary_vec.exists ()
> -	      && ((inline_edge_summary_vec.length () <= (unsigned) uid)
> -	          || !inline_edge_summary (this)->predicate)))
> -      && (frequency
> -	  != compute_call_stmt_bb_frequency (caller->decl,
> -					     gimple_bb (call_stmt))))
> -    {
> -      error ("caller edge frequency %i does not match BB frequency %i",
> -	     frequency,
> -	     compute_call_stmt_bb_frequency (caller->decl,
> -					     gimple_bb (call_stmt)));
> -      error_found = true;
> -    }
>    return error_found;
>  }
>  
> @@ -2848,9 +2829,46 @@ cgraph_node::verify_node (void)
>  	    error_found = true;
>  	  }
>      }
> +  for (e = callees; e; e = e->next_callee)
> +    {
> +      if (e->verify_count_and_frequency ())
> +	error_found = true;
> +      if (gimple_has_body_p (e->caller->decl)
> +	  && !e->caller->global.inlined_to
> +	  && !e->speculative
> +	  /* Optimized out calls are redirected to __builtin_unreachable.  */
> +	  && (e->frequency
> +	      || e->callee->decl
> +		 != builtin_decl_implicit (BUILT_IN_UNREACHABLE))
> +	  && (e->frequency
> +	      != compute_call_stmt_bb_frequency (e->caller->decl,
> +						 gimple_bb (e->call_stmt))))
> +	{
> +	  error ("caller edge frequency %i does not match BB frequency %i",
> +		 e->frequency,
> +		 compute_call_stmt_bb_frequency (e->caller->decl,
> +						 gimple_bb (e->call_stmt)));
> +	  error_found = true;
> +	}
> +    }
>    for (e = indirect_calls; e; e = e->next_callee)
> -    if (e->verify_count_and_frequency ())
> -      error_found = true;
> +    {
> +      if (e->verify_count_and_frequency ())
> +	error_found = true;
> +      if (gimple_has_body_p (e->caller->decl)
> +	  && !e->caller->global.inlined_to
> +	  && !e->speculative
> +	  && (e->frequency
> +	      != compute_call_stmt_bb_frequency (e->caller->decl,
> +						 gimple_bb (e->call_stmt))))
> +	{
> +	  error ("caller edge frequency %i does not match BB frequency %i",
> +		 e->frequency,
> +		 compute_call_stmt_bb_frequency (e->caller->decl,
> +						 gimple_bb (e->call_stmt)));
> +	  error_found = true;
> +	}
> +    }

This should be committed separately.

>    if (!callers && global.inlined_to)
>      {
>        error ("inlined_to pointer is set but no predecessors found");
> Index: passes.def
> ===================================================================
> --- passes.def	(revision 221682)
> +++ passes.def	(working copy)
> @@ -58,6 +58,7 @@ along with GCC; see the file COPYING3.
>        NEXT_PASS (pass_build_ssa);
>        NEXT_PASS (pass_ubsan);
>        NEXT_PASS (pass_early_warn_uninitialized);
> +      NEXT_PASS (pass_nothrow);
>    POP_INSERT_PASSES ()
>  
>    NEXT_PASS (pass_chkp_instrumentation_passes);
> Index: ipa-pure-const.c
> ===================================================================
> --- ipa-pure-const.c	(revision 221682)
> +++ ipa-pure-const.c	(working copy)
> @@ -1870,3 +1881,90 @@ make_pass_warn_function_noreturn (gcc::c
>  {
>    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)
> +    {
> +      gimple_stmt_iterator gsi;
> +      for (gsi = gsi_start_bb (this_block);
> +	   !gsi_end_p (gsi);
> +	   gsi_next (&gsi))
> +        if (stmt_could_throw_p (gsi_stmt (gsi))
> +	    && stmt_can_throw_external (gsi_stmt (gsi)))

The stmt_could_throw_p call is redundant.

> +	  {
> +	    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 execute_fixup_cfg ();

Err - this doesn't make sense - you'd need to call this for
all _callers_.  That is, this is dealt with by

  PUSH_INSERT_PASSES_WITHIN (pass_build_ssa_passes)
      NEXT_PASS (pass_fixup_cfg);
^^^^^^^

?  If not then this may paper over the issue I saw (dangling
call_stmt on a cgraph edge?).

So please re-check with the above execute_fixup_cfg removed
(your simple patch doesn't handle direct recursion optimistically).

Thanks,
Richard.

> +}
> +
> +} // anon namespace
> +
> +gimple_opt_pass *
> +make_pass_nothrow (gcc::context *ctxt)
> +{
> +  return new pass_nothrow (ctxt);
> +}
> Index: tree-pass.h
> ===================================================================
> --- tree-pass.h	(revision 221682)
> +++ tree-pass.h	(working copy)
> @@ -436,6 +436,7 @@ extern gimple_opt_pass *make_pass_remove
>  							      *ctxt);
>  extern gimple_opt_pass *make_pass_build_cgraph_edges (gcc::context *ctxt);
>  extern gimple_opt_pass *make_pass_local_pure_const (gcc::context *ctxt);
> +extern gimple_opt_pass *make_pass_nothrow (gcc::context *ctxt);
>  extern gimple_opt_pass *make_pass_tracer (gcc::context *ctxt);
>  extern gimple_opt_pass *make_pass_warn_unused_result (gcc::context *ctxt);
>  extern gimple_opt_pass *make_pass_diagnose_tm_blocks (gcc::context *ctxt);
> Index: ipa-inline-analysis.c
> ===================================================================
> --- ipa-inline-analysis.c	(revision 221682)
> +++ ipa-inline-analysis.c	(working copy)
> @@ -769,11 +769,16 @@ edge_set_predicate (struct cgraph_edge *
>  
>    /* If the edge is determined to be never executed, redirect it
>       to BUILTIN_UNREACHABLE to save inliner from inlining into it.  */
> -  if (predicate && false_predicate_p (predicate) && e->callee)
> +  if (predicate && false_predicate_p (predicate))
>      {
>        struct cgraph_node *callee = !e->inline_failed ? e->callee : NULL;
> -
> -      e->redirect_callee (cgraph_node::get_create
> +      if (e->speculative)
> +	e->resolve_speculation (builtin_decl_implicit (BUILT_IN_UNREACHABLE));
> +      if (!e->callee)
> +	e->make_direct (cgraph_node::get_create
> +			  (builtin_decl_implicit (BUILT_IN_UNREACHABLE)));
> +      else
> +	e->redirect_callee (cgraph_node::get_create
>  			    (builtin_decl_implicit (BUILT_IN_UNREACHABLE)));
>        e->inline_failed = CIF_UNREACHABLE;
>        es->call_stmt_size = 0;
> 
>
Jan Hubicka March 26, 2015, 2:41 p.m. UTC | #2
> 
> Does that work on callgraph cycles?

Yes. Because ...
> > +
> > +  node->set_nothrow_flag (true);
> > +  if (dump_file)
> > +    fprintf (dump_file, "Function found to be nothrow: %s\n",
> > +	     current_function_name ());
> > +  return execute_fixup_cfg ();
> 
> Err - this doesn't make sense - you'd need to call this for
> all _callers_.  That is, this is dealt with by
> 
>   PUSH_INSERT_PASSES_WITHIN (pass_build_ssa_passes)
>       NEXT_PASS (pass_fixup_cfg);
> ^^^^^^^
> 
> ?  If not then this may paper over the issue I saw (dangling
> call_stmt on a cgraph edge?).

... this one is unnecesary. Fixup happens in the following 
local optimization pass queue, so we do not need fixing here.
I remember I removed this at one point, but apparently accidentally reverted
the change.  Will re-test with return 0.

OK with these changes (and commiting separately the checking bits)?
> 
> So please re-check with the above execute_fixup_cfg removed
> (your simple patch doesn't handle direct recursion optimistically).

Ah, you are right. I will add it - that is easy enoug to check.

Thanks,
Honza
> 
> Thanks,
> Richard.
> 
> > +}
> > +
> > +} // anon namespace
> > +
> > +gimple_opt_pass *
> > +make_pass_nothrow (gcc::context *ctxt)
> > +{
> > +  return new pass_nothrow (ctxt);
> > +}
> > Index: tree-pass.h
> > ===================================================================
> > --- tree-pass.h	(revision 221682)
> > +++ tree-pass.h	(working copy)
> > @@ -436,6 +436,7 @@ extern gimple_opt_pass *make_pass_remove
> >  							      *ctxt);
> >  extern gimple_opt_pass *make_pass_build_cgraph_edges (gcc::context *ctxt);
> >  extern gimple_opt_pass *make_pass_local_pure_const (gcc::context *ctxt);
> > +extern gimple_opt_pass *make_pass_nothrow (gcc::context *ctxt);
> >  extern gimple_opt_pass *make_pass_tracer (gcc::context *ctxt);
> >  extern gimple_opt_pass *make_pass_warn_unused_result (gcc::context *ctxt);
> >  extern gimple_opt_pass *make_pass_diagnose_tm_blocks (gcc::context *ctxt);
> > Index: ipa-inline-analysis.c
> > ===================================================================
> > --- ipa-inline-analysis.c	(revision 221682)
> > +++ ipa-inline-analysis.c	(working copy)
> > @@ -769,11 +769,16 @@ edge_set_predicate (struct cgraph_edge *
> >  
> >    /* If the edge is determined to be never executed, redirect it
> >       to BUILTIN_UNREACHABLE to save inliner from inlining into it.  */
> > -  if (predicate && false_predicate_p (predicate) && e->callee)
> > +  if (predicate && false_predicate_p (predicate))
> >      {
> >        struct cgraph_node *callee = !e->inline_failed ? e->callee : NULL;
> > -
> > -      e->redirect_callee (cgraph_node::get_create
> > +      if (e->speculative)
> > +	e->resolve_speculation (builtin_decl_implicit (BUILT_IN_UNREACHABLE));
> > +      if (!e->callee)
> > +	e->make_direct (cgraph_node::get_create
> > +			  (builtin_decl_implicit (BUILT_IN_UNREACHABLE)));
> > +      else
> > +	e->redirect_callee (cgraph_node::get_create
> >  			    (builtin_decl_implicit (BUILT_IN_UNREACHABLE)));
> >        e->inline_failed = CIF_UNREACHABLE;
> >        es->call_stmt_size = 0;
> > 
> > 
> 
> -- 
> Richard Biener <rguenther@suse.de>
> SUSE LINUX GmbH, GF: Felix Imendoerffer, Jane Smithard, Jennifer Guild,
> Dilip Upmanyu, Graham Norton HRB 21284 (AG Nuernberg)
Richard Biener March 26, 2015, 4:33 p.m. UTC | #3
On March 26, 2015 3:41:12 PM GMT+01:00, Jan Hubicka <hubicka@ucw.cz> wrote:
>> 
>> Does that work on callgraph cycles?
>
>Yes. Because ...
>> > +
>> > +  node->set_nothrow_flag (true);
>> > +  if (dump_file)
>> > +    fprintf (dump_file, "Function found to be nothrow: %s\n",
>> > +	     current_function_name ());
>> > +  return execute_fixup_cfg ();
>> 
>> Err - this doesn't make sense - you'd need to call this for
>> all _callers_.  That is, this is dealt with by
>> 
>>   PUSH_INSERT_PASSES_WITHIN (pass_build_ssa_passes)
>>       NEXT_PASS (pass_fixup_cfg);
>> ^^^^^^^
>> 
>> ?  If not then this may paper over the issue I saw (dangling
>> call_stmt on a cgraph edge?).
>
>... this one is unnecesary. Fixup happens in the following 
>local optimization pass queue, so we do not need fixing here.

But the fixup is required to benefit from the notgrow discovery in the callers. Just doing it on the current function body does not help into-SSA

>I remember I removed this at one point, but apparently accidentally
>reverted
>the change.  Will re-test with return 0.
>
>OK with these changes (and commiting separately the checking bits)?

See above.

>> 
>> So please re-check with the above execute_fixup_cfg removed
>> (your simple patch doesn't handle direct recursion optimistically).
>
>Ah, you are right. I will add it - that is easy enoug to check.

But then you need to do the fixup or remember the direct recursion sites and fix them up manually.

>Thanks,
>Honza
>> 
>> Thanks,
>> Richard.
>> 
>> > +}
>> > +
>> > +} // anon namespace
>> > +
>> > +gimple_opt_pass *
>> > +make_pass_nothrow (gcc::context *ctxt)
>> > +{
>> > +  return new pass_nothrow (ctxt);
>> > +}
>> > Index: tree-pass.h
>> > ===================================================================
>> > --- tree-pass.h	(revision 221682)
>> > +++ tree-pass.h	(working copy)
>> > @@ -436,6 +436,7 @@ extern gimple_opt_pass *make_pass_remove
>> >  							      *ctxt);
>> >  extern gimple_opt_pass *make_pass_build_cgraph_edges (gcc::context
>*ctxt);
>> >  extern gimple_opt_pass *make_pass_local_pure_const (gcc::context
>*ctxt);
>> > +extern gimple_opt_pass *make_pass_nothrow (gcc::context *ctxt);
>> >  extern gimple_opt_pass *make_pass_tracer (gcc::context *ctxt);
>> >  extern gimple_opt_pass *make_pass_warn_unused_result (gcc::context
>*ctxt);
>> >  extern gimple_opt_pass *make_pass_diagnose_tm_blocks (gcc::context
>*ctxt);
>> > Index: ipa-inline-analysis.c
>> > ===================================================================
>> > --- ipa-inline-analysis.c	(revision 221682)
>> > +++ ipa-inline-analysis.c	(working copy)
>> > @@ -769,11 +769,16 @@ edge_set_predicate (struct cgraph_edge *
>> >  
>> >    /* If the edge is determined to be never executed, redirect it
>> >       to BUILTIN_UNREACHABLE to save inliner from inlining into it.
> */
>> > -  if (predicate && false_predicate_p (predicate) && e->callee)
>> > +  if (predicate && false_predicate_p (predicate))
>> >      {
>> >        struct cgraph_node *callee = !e->inline_failed ? e->callee :
>NULL;
>> > -
>> > -      e->redirect_callee (cgraph_node::get_create
>> > +      if (e->speculative)
>> > +	e->resolve_speculation (builtin_decl_implicit
>(BUILT_IN_UNREACHABLE));
>> > +      if (!e->callee)
>> > +	e->make_direct (cgraph_node::get_create
>> > +			  (builtin_decl_implicit (BUILT_IN_UNREACHABLE)));
>> > +      else
>> > +	e->redirect_callee (cgraph_node::get_create
>> >  			    (builtin_decl_implicit (BUILT_IN_UNREACHABLE)));
>> >        e->inline_failed = CIF_UNREACHABLE;
>> >        es->call_stmt_size = 0;
>> > 
>> > 
>> 
>> -- 
>> Richard Biener <rguenther@suse.de>
>> SUSE LINUX GmbH, GF: Felix Imendoerffer, Jane Smithard, Jennifer
>Guild,
>> Dilip Upmanyu, Graham Norton HRB 21284 (AG Nuernberg)
Jan Hubicka March 26, 2015, 4:48 p.m. UTC | #4
> >> 
> >> So please re-check with the above execute_fixup_cfg removed
> >> (your simple patch doesn't handle direct recursion optimistically).
> >
> >Ah, you are right. I will add it - that is easy enoug to check.
> 
> But then you need to do the fixup or remember the direct recursion sites and fix them up manually.

We do all fixups needed:
  PUSH_INSERT_PASSES_WITHIN (pass_build_ssa_passes)
      NEXT_PASS (pass_fixup_cfg);
      NEXT_PASS (pass_init_datastructures);
      NEXT_PASS (pass_build_ssa);
      NEXT_PASS (pass_ubsan);
      NEXT_PASS (pass_early_warn_uninitialized);
  POP_INSERT_PASSES ()

  NEXT_PASS (pass_chkp_instrumentation_passes);
  PUSH_INSERT_PASSES_WITHIN (pass_chkp_instrumentation_passes)
      NEXT_PASS (pass_fixup_cfg);
      NEXT_PASS (pass_chkp);
      NEXT_PASS (pass_rebuild_cgraph_edges);
  POP_INSERT_PASSES ()

  NEXT_PASS (pass_local_optimization_passes);
  PUSH_INSERT_PASSES_WITHIN (pass_local_optimization_passes)
      NEXT_PASS (pass_fixup_cfg);

for recursive calls and calls going backwards in the topological order the
fixup will happen as first thing of pass_local_optimization_passes.
build_ssa won't benefit because it is already done.

For calls going forward the fixup will happen at beggining of pass_build_ssa_passes
and thus build_ssa will benefit from it.

Honza
diff mbox

Patch

Index: cgraph.c
===================================================================
--- cgraph.c	(revision 221682)
+++ cgraph.c	(working copy)
@@ -2661,25 +2661,6 @@  cgraph_edge::verify_count_and_frequency
       error ("caller edge frequency is too large");
       error_found = true;
     }
-  if (gimple_has_body_p (caller->decl)
-      && !caller->global.inlined_to
-      && !speculative
-      /* FIXME: Inline-analysis sets frequency to 0 when edge is optimized out.
-	 Remove this once edges are actually removed from the function at that time.  */
-      && (frequency
-	  || (inline_edge_summary_vec.exists ()
-	      && ((inline_edge_summary_vec.length () <= (unsigned) uid)
-	          || !inline_edge_summary (this)->predicate)))
-      && (frequency
-	  != compute_call_stmt_bb_frequency (caller->decl,
-					     gimple_bb (call_stmt))))
-    {
-      error ("caller edge frequency %i does not match BB frequency %i",
-	     frequency,
-	     compute_call_stmt_bb_frequency (caller->decl,
-					     gimple_bb (call_stmt)));
-      error_found = true;
-    }
   return error_found;
 }
 
@@ -2848,9 +2829,46 @@  cgraph_node::verify_node (void)
 	    error_found = true;
 	  }
     }
+  for (e = callees; e; e = e->next_callee)
+    {
+      if (e->verify_count_and_frequency ())
+	error_found = true;
+      if (gimple_has_body_p (e->caller->decl)
+	  && !e->caller->global.inlined_to
+	  && !e->speculative
+	  /* Optimized out calls are redirected to __builtin_unreachable.  */
+	  && (e->frequency
+	      || e->callee->decl
+		 != builtin_decl_implicit (BUILT_IN_UNREACHABLE))
+	  && (e->frequency
+	      != compute_call_stmt_bb_frequency (e->caller->decl,
+						 gimple_bb (e->call_stmt))))
+	{
+	  error ("caller edge frequency %i does not match BB frequency %i",
+		 e->frequency,
+		 compute_call_stmt_bb_frequency (e->caller->decl,
+						 gimple_bb (e->call_stmt)));
+	  error_found = true;
+	}
+    }
   for (e = indirect_calls; e; e = e->next_callee)
-    if (e->verify_count_and_frequency ())
-      error_found = true;
+    {
+      if (e->verify_count_and_frequency ())
+	error_found = true;
+      if (gimple_has_body_p (e->caller->decl)
+	  && !e->caller->global.inlined_to
+	  && !e->speculative
+	  && (e->frequency
+	      != compute_call_stmt_bb_frequency (e->caller->decl,
+						 gimple_bb (e->call_stmt))))
+	{
+	  error ("caller edge frequency %i does not match BB frequency %i",
+		 e->frequency,
+		 compute_call_stmt_bb_frequency (e->caller->decl,
+						 gimple_bb (e->call_stmt)));
+	  error_found = true;
+	}
+    }
   if (!callers && global.inlined_to)
     {
       error ("inlined_to pointer is set but no predecessors found");
Index: passes.def
===================================================================
--- passes.def	(revision 221682)
+++ passes.def	(working copy)
@@ -58,6 +58,7 @@  along with GCC; see the file COPYING3.
       NEXT_PASS (pass_build_ssa);
       NEXT_PASS (pass_ubsan);
       NEXT_PASS (pass_early_warn_uninitialized);
+      NEXT_PASS (pass_nothrow);
   POP_INSERT_PASSES ()
 
   NEXT_PASS (pass_chkp_instrumentation_passes);
Index: ipa-pure-const.c
===================================================================
--- ipa-pure-const.c	(revision 221682)
+++ ipa-pure-const.c	(working copy)
@@ -1870,3 +1881,90 @@  make_pass_warn_function_noreturn (gcc::c
 {
   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)
+    {
+      gimple_stmt_iterator gsi;
+      for (gsi = gsi_start_bb (this_block);
+	   !gsi_end_p (gsi);
+	   gsi_next (&gsi))
+        if (stmt_could_throw_p (gsi_stmt (gsi))
+	    && stmt_can_throw_external (gsi_stmt (gsi)))
+	  {
+	    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 execute_fixup_cfg ();
+}
+
+} // anon namespace
+
+gimple_opt_pass *
+make_pass_nothrow (gcc::context *ctxt)
+{
+  return new pass_nothrow (ctxt);
+}
Index: tree-pass.h
===================================================================
--- tree-pass.h	(revision 221682)
+++ tree-pass.h	(working copy)
@@ -436,6 +436,7 @@  extern gimple_opt_pass *make_pass_remove
 							      *ctxt);
 extern gimple_opt_pass *make_pass_build_cgraph_edges (gcc::context *ctxt);
 extern gimple_opt_pass *make_pass_local_pure_const (gcc::context *ctxt);
+extern gimple_opt_pass *make_pass_nothrow (gcc::context *ctxt);
 extern gimple_opt_pass *make_pass_tracer (gcc::context *ctxt);
 extern gimple_opt_pass *make_pass_warn_unused_result (gcc::context *ctxt);
 extern gimple_opt_pass *make_pass_diagnose_tm_blocks (gcc::context *ctxt);
Index: ipa-inline-analysis.c
===================================================================
--- ipa-inline-analysis.c	(revision 221682)
+++ ipa-inline-analysis.c	(working copy)
@@ -769,11 +769,16 @@  edge_set_predicate (struct cgraph_edge *
 
   /* If the edge is determined to be never executed, redirect it
      to BUILTIN_UNREACHABLE to save inliner from inlining into it.  */
-  if (predicate && false_predicate_p (predicate) && e->callee)
+  if (predicate && false_predicate_p (predicate))
     {
       struct cgraph_node *callee = !e->inline_failed ? e->callee : NULL;
-
-      e->redirect_callee (cgraph_node::get_create
+      if (e->speculative)
+	e->resolve_speculation (builtin_decl_implicit (BUILT_IN_UNREACHABLE));
+      if (!e->callee)
+	e->make_direct (cgraph_node::get_create
+			  (builtin_decl_implicit (BUILT_IN_UNREACHABLE)));
+      else
+	e->redirect_callee (cgraph_node::get_create
 			    (builtin_decl_implicit (BUILT_IN_UNREACHABLE)));
       e->inline_failed = CIF_UNREACHABLE;
       es->call_stmt_size = 0;