middle-end, v3: IFN_ASSUME support [PR106654]

Message ID Y0e1QH++UvHO7MtJ@tucnak
State New, archived
Headers
Series middle-end, v3: IFN_ASSUME support [PR106654] |

Commit Message

Jakub Jelinek Oct. 13, 2022, 6:50 a.m. UTC
  On Wed, Oct 12, 2022 at 11:48:27AM -0400, Jason Merrill wrote:
> > --- gcc/cp/pt.cc.jj	2022-10-10 09:31:21.947480379 +0200
> > +++ gcc/cp/pt.cc	2022-10-10 09:59:49.299646482 +0200
> > @@ -21105,6 +21105,8 @@ tsubst_copy_and_build (tree t,
> >   		      ret = error_mark_node;
> >   		      break;
> >   		    }
> > +		  if (!processing_template_decl)
> > +		    arg = fold_build_cleanup_point_expr (TREE_TYPE (arg), arg);
> >   		  ret = build_call_expr_internal_loc (EXPR_LOCATION (t),
> >   						      IFN_ASSUME,
> >   						      void_type_node, 1,
> 
> This starts to seem worth factoring out a build_assume_call function.

Ok, below is an updated version of the patch that does that.
Bootstrapped/regtested on x86_64-linux and i686-linux.
> 
> I'll leave the middle-end review to others.

Ok.

2022-10-13  Jakub Jelinek  <jakub@redhat.com>

	PR c++/106654
gcc/
	* function.h (struct function): Add assume_function bitfield.
	* gimplify.cc (gimplify_call_expr): If the assumption isn't
	simple enough, expand it into IFN_ASSUME guarded block or
	for -O0 drop it.
	* gimple-low.cc (create_assumption_fn): New function.
	(struct lower_assumption_data): New type.
	(find_assumption_locals_r, assumption_copy_decl,
	adjust_assumption_stmt_r, adjust_assumption_stmt_op,
	lower_assumption): New functions.
	(lower_stmt): Handle IFN_ASSUME guarded block.
	* tree-ssa-ccp.cc (pass_fold_builtins::execute): Remove
	IFN_ASSUME calls.
	* lto-streamer-out.cc (output_struct_function_base): Pack
	assume_function bit.
	* lto-streamer-in.cc (input_struct_function_base): And unpack it.
	* cgraphunit.cc (cgraph_node::expand): Don't verify assume_function
	has TREE_ASM_WRITTEN set and don't release its body.
	* cfgexpand.cc (pass_expand::execute): Don't expand assume_function
	into RTL, just destroy loops and exit.
	* internal-fn.cc (expand_ASSUME): Remove gcc_unreachable.
	* passes.cc (pass_rest_of_compilation::gate): Return false also for
	fun->assume_function.
	* tree-vectorizer.cc (pass_vectorize::gate,
	pass_slp_vectorize::gate): Likewise.
	* ipa-icf.cc (sem_function::parse): Punt for func->assume_function.
gcc/cp/
	* cp-tree.h (build_assume_call): Declare.
	* parser.cc (cp_parser_omp_assumption_clauses): Use build_assume_call.
	* cp-gimplify.cc (build_assume_call): New function.
	(process_stmt_assume_attribute): Use build_assume_call.
	* pt.cc (tsubst_copy_and_build): Likewise.
gcc/testsuite/
	* g++.dg/cpp23/attr-assume5.C: New test.
	* g++.dg/cpp23/attr-assume6.C: New test.
	* g++.dg/cpp23/attr-assume7.C: New test.



	Jakub
  

Comments

Richard Biener Oct. 14, 2022, 11:27 a.m. UTC | #1
On Thu, 13 Oct 2022, Jakub Jelinek wrote:

> On Wed, Oct 12, 2022 at 11:48:27AM -0400, Jason Merrill wrote:
> > > --- gcc/cp/pt.cc.jj	2022-10-10 09:31:21.947480379 +0200
> > > +++ gcc/cp/pt.cc	2022-10-10 09:59:49.299646482 +0200
> > > @@ -21105,6 +21105,8 @@ tsubst_copy_and_build (tree t,
> > >   		      ret = error_mark_node;
> > >   		      break;
> > >   		    }
> > > +		  if (!processing_template_decl)
> > > +		    arg = fold_build_cleanup_point_expr (TREE_TYPE (arg), arg);
> > >   		  ret = build_call_expr_internal_loc (EXPR_LOCATION (t),
> > >   						      IFN_ASSUME,
> > >   						      void_type_node, 1,
> > 
> > This starts to seem worth factoring out a build_assume_call function.
> 
> Ok, below is an updated version of the patch that does that.
> Bootstrapped/regtested on x86_64-linux and i686-linux.
> > 
> > I'll leave the middle-end review to others.
> 
> Ok.
> 
> 2022-10-13  Jakub Jelinek  <jakub@redhat.com>
> 
> 	PR c++/106654
> gcc/
> 	* function.h (struct function): Add assume_function bitfield.
> 	* gimplify.cc (gimplify_call_expr): If the assumption isn't
> 	simple enough, expand it into IFN_ASSUME guarded block or
> 	for -O0 drop it.
> 	* gimple-low.cc (create_assumption_fn): New function.
> 	(struct lower_assumption_data): New type.
> 	(find_assumption_locals_r, assumption_copy_decl,
> 	adjust_assumption_stmt_r, adjust_assumption_stmt_op,
> 	lower_assumption): New functions.
> 	(lower_stmt): Handle IFN_ASSUME guarded block.
> 	* tree-ssa-ccp.cc (pass_fold_builtins::execute): Remove
> 	IFN_ASSUME calls.
> 	* lto-streamer-out.cc (output_struct_function_base): Pack
> 	assume_function bit.
> 	* lto-streamer-in.cc (input_struct_function_base): And unpack it.
> 	* cgraphunit.cc (cgraph_node::expand): Don't verify assume_function
> 	has TREE_ASM_WRITTEN set and don't release its body.
> 	* cfgexpand.cc (pass_expand::execute): Don't expand assume_function
> 	into RTL, just destroy loops and exit.
> 	* internal-fn.cc (expand_ASSUME): Remove gcc_unreachable.
> 	* passes.cc (pass_rest_of_compilation::gate): Return false also for
> 	fun->assume_function.
> 	* tree-vectorizer.cc (pass_vectorize::gate,
> 	pass_slp_vectorize::gate): Likewise.
> 	* ipa-icf.cc (sem_function::parse): Punt for func->assume_function.
> gcc/cp/
> 	* cp-tree.h (build_assume_call): Declare.
> 	* parser.cc (cp_parser_omp_assumption_clauses): Use build_assume_call.
> 	* cp-gimplify.cc (build_assume_call): New function.
> 	(process_stmt_assume_attribute): Use build_assume_call.
> 	* pt.cc (tsubst_copy_and_build): Likewise.
> gcc/testsuite/
> 	* g++.dg/cpp23/attr-assume5.C: New test.
> 	* g++.dg/cpp23/attr-assume6.C: New test.
> 	* g++.dg/cpp23/attr-assume7.C: New test.
> 
> --- gcc/function.h.jj	2022-10-10 11:57:40.163722972 +0200
> +++ gcc/function.h	2022-10-12 19:48:28.887554771 +0200
> @@ -438,6 +438,10 @@ struct GTY(()) function {
>  
>    /* Set if there are any OMP_TARGET regions in the function.  */
>    unsigned int has_omp_target : 1;
> +
> +  /* Set for artificial function created for [[assume (cond)]].
> +     These should be GIMPLE optimized, but not expanded to RTL.  */
> +  unsigned int assume_function : 1;

I wonder if we should have this along force_output in the symtab
node and let the symtab code decide whether to expand?

>  };
>  
>  /* Add the decl D to the local_decls list of FUN.  */
> --- gcc/gimplify.cc.jj	2022-10-10 11:57:40.165722944 +0200
> +++ gcc/gimplify.cc	2022-10-12 19:48:28.890554730 +0200
> @@ -3569,7 +3569,52 @@ gimplify_call_expr (tree *expr_p, gimple
>  						     fndecl, 0));
>  	      return GS_OK;
>  	    }
> -	  /* FIXME: Otherwise expand it specially.  */
> +	  /* If not optimizing, ignore the assumptions.  */
> +	  if (!optimize)
> +	    {
> +	      *expr_p = NULL_TREE;
> +	      return GS_ALL_DONE;
> +	    }
> +	  /* Temporarily, until gimple lowering, transform
> +	     .ASSUME (cond);
> +	     into:
> +	     guard = .ASSUME ();
> +	     if (guard) goto label_true; else label_false;
> +	     label_true:;
> +	     {
> +	       guard = cond;
> +	     }
> +	     label_false:;
> +	     .ASSUME (guard);
> +	     such that gimple lowering can outline the condition into
> +	     a separate function easily.  */

So the idea to use lambdas and/or nested functions (for OMP)
didn't work out or is more complicated?

I wonder if, instead of using the above intermediate form we
can have a new structued GIMPLE code with sub-statements

 .ASSUME
   {
     condition;
   }

?  There's gimple_statement_omp conveniently available as base and
IIRC you had the requirement to implement some OMP assume as well?
Of ocurse a different stmt class with body would work as well here,
maybe we can even use a gbind with a special flag.

The outlining code can then be ajusted to outline a single BIND?
It probably won't simplify much that way.

> +	  tree guard = create_tmp_var (boolean_type_node);
> +	  gcall *call = gimple_build_call_internal (ifn, 0);
> +	  gimple_call_set_nothrow (call, TREE_NOTHROW (*expr_p));
> +	  gimple_set_location (call, loc);
> +	  gimple_call_set_lhs (call, guard);
> +	  gimple_seq_add_stmt (pre_p, call);
> +	  *expr_p = build2 (MODIFY_EXPR, void_type_node, guard,
> +			    CALL_EXPR_ARG (*expr_p, 0));
> +	  *expr_p = build3 (BIND_EXPR, void_type_node, NULL, *expr_p, NULL);
> +	  tree label_false = create_artificial_label (UNKNOWN_LOCATION);
> +	  tree label_true = create_artificial_label (UNKNOWN_LOCATION);
> +	  gcond *cond_stmt = gimple_build_cond (NE_EXPR, guard,
> +						boolean_false_node,
> +						label_true, label_false);
> +	  gimplify_seq_add_stmt (pre_p, cond_stmt);
> +	  gimplify_seq_add_stmt (pre_p, gimple_build_label (label_true));
> +	  push_gimplify_context ();
> +	  gimple_seq body = NULL;
> +	  gimple *g = gimplify_and_return_first (*expr_p, &body);
> +	  pop_gimplify_context (g);
> +	  gimplify_seq_add_seq (pre_p, body);
> +	  gimplify_seq_add_stmt (pre_p, gimple_build_label (label_false));
> +	  call = gimple_build_call_internal (ifn, 1, guard);
> +	  gimple_call_set_nothrow (call, TREE_NOTHROW (*expr_p));
> +	  gimple_set_location (call, loc);
> +	  gimple_seq_add_stmt (pre_p, call);
> +	  *expr_p = NULL_TREE;
>  	  return GS_ALL_DONE;
>  	}
>  
> --- gcc/gimple-low.cc.jj	2022-10-10 11:57:40.163722972 +0200
> +++ gcc/gimple-low.cc	2022-10-12 19:48:28.890554730 +0200
> @@ -33,6 +33,13 @@ along with GCC; see the file COPYING3.
>  #include "predict.h"
>  #include "gimple-predict.h"
>  #include "gimple-fold.h"
> +#include "cgraph.h"
> +#include "tree-ssa.h"
> +#include "value-range.h"
> +#include "stringpool.h"
> +#include "tree-ssanames.h"
> +#include "tree-inline.h"
> +#include "gimple-walk.h"
>  
>  /* The differences between High GIMPLE and Low GIMPLE are the
>     following:
> @@ -237,6 +244,383 @@ lower_omp_directive (gimple_stmt_iterato
>    gsi_next (gsi);
>  }

comment missing

> +static tree
> +create_assumption_fn (location_t loc)
> +{
> +  tree name = clone_function_name_numbered (current_function_decl, "_assume");
> +  /* For now, will be changed later.  */

?

> +  tree type = TREE_TYPE (current_function_decl);
> +  tree decl = build_decl (loc, FUNCTION_DECL, name, type);
> +  TREE_STATIC (decl) = 1;
> +  TREE_USED (decl) = 1;
> +  DECL_ARTIFICIAL (decl) = 1;
> +  DECL_IGNORED_P (decl) = 1;
> +  DECL_NAMELESS (decl) = 1;
> +  TREE_PUBLIC (decl) = 0;
> +  DECL_UNINLINABLE (decl) = 1;
> +  DECL_EXTERNAL (decl) = 0;
> +  DECL_CONTEXT (decl) = NULL_TREE;
> +  DECL_INITIAL (decl) = make_node (BLOCK);
> +  BLOCK_SUPERCONTEXT (DECL_INITIAL (decl)) = decl;
> +  DECL_FUNCTION_SPECIFIC_OPTIMIZATION (decl)
> +    = DECL_FUNCTION_SPECIFIC_OPTIMIZATION (current_function_decl);
> +  DECL_FUNCTION_SPECIFIC_TARGET (decl)
> +    = DECL_FUNCTION_SPECIFIC_TARGET (current_function_decl);
> +  DECL_FUNCTION_VERSIONED (decl)
> +    = DECL_FUNCTION_VERSIONED (current_function_decl);

what does it mean to copy DECL_FUNCTION_VERSIONED here?

> +  tree t = build_decl (DECL_SOURCE_LOCATION (decl),
> +		       RESULT_DECL, NULL_TREE, boolean_type_node);
> +  DECL_ARTIFICIAL (t) = 1;
> +  DECL_IGNORED_P (t) = 1;
> +  DECL_CONTEXT (t) = decl;
> +  DECL_RESULT (decl) = t;
> +  push_struct_function (decl);
> +  cfun->function_end_locus = loc;
> +  init_tree_ssa (cfun);
> +  return decl;
> +}
> +
> +struct lower_assumption_data
> +{
> +  copy_body_data id;
> +  tree return_false_label;
> +  tree guard_copy;
> +  auto_vec<tree> decls;
> +};
> +
> +/* Helper function for lower_assumptions.  Find local vars and labels
> +   in the assumption sequence and remove debug stmts.  */
> +
> +static tree
> +find_assumption_locals_r (gimple_stmt_iterator *gsi_p, bool *,
> +			  struct walk_stmt_info *wi)
> +{
> +  lower_assumption_data *data = (lower_assumption_data *) wi->info;
> +  gimple *stmt = gsi_stmt (*gsi_p);
> +  tree lhs = gimple_get_lhs (stmt);
> +  if (lhs && TREE_CODE (lhs) == SSA_NAME)
> +    {
> +      gcc_assert (SSA_NAME_VAR (lhs) == NULL_TREE);
> +      data->id.decl_map->put (lhs, NULL_TREE);
> +      data->decls.safe_push (lhs);
> +    }
> +  switch (gimple_code (stmt))
> +    {
> +    case GIMPLE_BIND:
> +      for (tree var = gimple_bind_vars (as_a <gbind *> (stmt));
> +	   var; var = DECL_CHAIN (var))
> +	if (VAR_P (var)
> +	    && !DECL_EXTERNAL (var)
> +	    && DECL_CONTEXT (var) == data->id.src_fn)
> +	  {
> +	    data->id.decl_map->put (var, var);
> +	    data->decls.safe_push (var);
> +	  }
> +      break;
> +    case GIMPLE_LABEL:
> +      {
> +	tree label = gimple_label_label (as_a <glabel *> (stmt));
> +	data->id.decl_map->put (label, label);
> +	break;
> +      }
> +    case GIMPLE_RETURN:
> +      /* If something in assumption tries to return from parent function,
> +	 if it would be reached in hypothetical evaluation, it would be UB,
> +	 so transform such returns into return false;  */
> +      {
> +	gimple *g = gimple_build_assign (data->guard_copy, boolean_false_node);
> +	gsi_insert_before (gsi_p, g, GSI_SAME_STMT);
> +	gimple_return_set_retval (as_a <greturn *> (stmt), data->guard_copy);
> +	break;
> +      }
> +    case GIMPLE_DEBUG:
> +      /* As assumptions won't be emitted, debug info stmts in them
> +	 are useless.  */
> +      gsi_remove (gsi_p, true);
> +      wi->removed_stmt = true;
> +      break;
> +    default:
> +      break;
> +    }
> +  return NULL_TREE;
> +}
> +
> +/* Create a new PARM_DECL that is indentical in all respect to DECL except that
> +   DECL can be either a VAR_DECL, a PARM_DECL or RESULT_DECL.  The original
> +   DECL must come from ID->src_fn and the copy will be part of ID->dst_fn.  */
> +
> +static tree
> +assumption_copy_decl (tree decl, copy_body_data *id)
> +{
> +  tree type = TREE_TYPE (decl);
> +
> +  if (is_global_var (decl))
> +    return decl;
> +
> +  gcc_assert (VAR_P (decl)
> +	      || TREE_CODE (decl) == PARM_DECL
> +	      || TREE_CODE (decl) == RESULT_DECL);
> +  tree copy = build_decl (DECL_SOURCE_LOCATION (decl),
> +			  PARM_DECL, DECL_NAME (decl), type);
> +  if (DECL_PT_UID_SET_P (decl))
> +    SET_DECL_PT_UID (copy, DECL_PT_UID (decl));
> +  TREE_ADDRESSABLE (copy) = TREE_ADDRESSABLE (decl);
> +  TREE_READONLY (copy) = TREE_READONLY (decl);
> +  TREE_THIS_VOLATILE (copy) = TREE_THIS_VOLATILE (decl);
> +  DECL_NOT_GIMPLE_REG_P (copy) = DECL_NOT_GIMPLE_REG_P (decl);
> +  DECL_BY_REFERENCE (copy) = DECL_BY_REFERENCE (decl);
> +  DECL_ARG_TYPE (copy) = type;
> +  ((lower_assumption_data *) id)->decls.safe_push (decl);
> +  return copy_decl_for_dup_finish (id, decl, copy);
> +}
> +
> +/* Transform gotos out of the assumption into return false.  */
> +
> +static tree
> +adjust_assumption_stmt_r (gimple_stmt_iterator *gsi_p, bool *,
> +			  struct walk_stmt_info *wi)
> +{
> +  lower_assumption_data *data = (lower_assumption_data *) wi->info;
> +  gimple *stmt = gsi_stmt (*gsi_p);
> +  tree lab = NULL_TREE;
> +  unsigned int idx = 0;
> +  if (gimple_code (stmt) == GIMPLE_GOTO)
> +    lab = gimple_goto_dest (stmt);
> +  else if (gimple_code (stmt) == GIMPLE_COND)
> +    {
> +     repeat:
> +      if (idx == 0)
> +	lab = gimple_cond_true_label (as_a <gcond *> (stmt));
> +      else
> +	lab = gimple_cond_false_label (as_a <gcond *> (stmt));
> +    }
> +  else if (gimple_code (stmt) == GIMPLE_LABEL)
> +    {
> +      tree label = gimple_label_label (as_a <glabel *> (stmt));
> +      DECL_CONTEXT (label) = current_function_decl;
> +    }
> +  if (lab)
> +    {
> +      if (!data->id.decl_map->get (lab))
> +	{
> +	  if (!data->return_false_label)
> +	    data->return_false_label
> +	      = create_artificial_label (UNKNOWN_LOCATION);
> +	  if (gimple_code (stmt) == GIMPLE_GOTO)
> +	    gimple_goto_set_dest (as_a <ggoto *> (stmt),
> +				  data->return_false_label);
> +	  else if (idx == 0)
> +	    gimple_cond_set_true_label (as_a <gcond *> (stmt),
> +					data->return_false_label);
> +	  else
> +	    gimple_cond_set_false_label (as_a <gcond *> (stmt),
> +					 data->return_false_label);
> +	}
> +      if (gimple_code (stmt) == GIMPLE_COND && idx == 0)
> +	{
> +	  idx = 1;
> +	  goto repeat;
> +	}
> +    }
> +  return NULL_TREE;
> +}
> +
> +/* Adjust trees in the assumption body.  Called through walk_tree.  */
> +
> +static tree
> +adjust_assumption_stmt_op (tree *tp, int *, void *datap)
> +{
> +  struct walk_stmt_info *wi = (struct walk_stmt_info *) datap;
> +  lower_assumption_data *data = (lower_assumption_data *) wi->info;
> +  tree t = *tp;
> +  tree *newt;
> +  switch (TREE_CODE (t))
> +    {
> +    case SSA_NAME:
> +      newt = data->id.decl_map->get (t);
> +      /* There shouldn't be SSA_NAMEs other than ones defined in the
> +	 assumption's body.  */
> +      gcc_assert (newt);
> +      *tp = *newt;
> +      break;
> +    case LABEL_DECL:
> +      newt = data->id.decl_map->get (t);
> +      if (newt)
> +	*tp = *newt;
> +      break;
> +    case VAR_DECL:
> +    case PARM_DECL:
> +    case RESULT_DECL:
> +      *tp = remap_decl (t, &data->id);
> +      break;
> +    default:
> +      break;
> +    }
> +  return NULL_TREE;
> +}
> +
> +/* Lower assumption.
> +   The gimplifier transformed:
> +   .ASSUME (cond);
> +   into:
> +   guard = .ASSUME ();
> +   if (guard) goto label_true; else label_false;
> +   label_true:;
> +   {
> +     guard = cond;
> +   }
> +   label_false:;
> +   .ASSUME (guard);
> +   which we should transform into:
> +   .ASSUME (&artificial_fn, args...);
> +   where artificial_fn will look like:
> +   bool artificial_fn (args...)
> +   {
> +     guard = cond;
> +     return guard;
> +   }
> +   with any debug stmts in the block removed and jumps out of
> +   the block or return stmts replaced with return false;  */
> +
> +static void
> +lower_assumption (gimple_stmt_iterator *gsi, struct lower_data *data)
> +{
> +  gimple *stmt = gsi_stmt (*gsi);
> +  tree guard = gimple_call_lhs (stmt);
> +  location_t loc = gimple_location (stmt);
> +  gcc_assert (guard);
> +  gsi_remove (gsi, true);
> +  stmt = gsi_stmt (*gsi);
> +  gcond *cond = as_a <gcond *> (stmt);
> +  gcc_assert (gimple_cond_lhs (cond) == guard
> +	      || gimple_cond_rhs (cond) == guard);
> +  tree l1 = gimple_cond_true_label (cond);
> +  tree l2 = gimple_cond_false_label (cond);
> +  gsi_remove (gsi, true);
> +  stmt = gsi_stmt (*gsi);
> +  glabel *lab = as_a <glabel *> (stmt);
> +  gcc_assert (gimple_label_label (lab) == l1
> +	      || gimple_label_label (lab) == l2);
> +  gsi_remove (gsi, true);
> +  gimple *bind = gsi_stmt (*gsi);
> +  gcc_assert (gimple_code (bind) == GIMPLE_BIND);
> +
> +  lower_assumption_data lad;
> +  hash_map<tree, tree> decl_map;
> +  memset (&lad.id, 0, sizeof (lad.id));
> +  lad.return_false_label = NULL_TREE;
> +  lad.id.src_fn = current_function_decl;
> +  lad.id.dst_fn = create_assumption_fn (loc);
> +  lad.id.src_cfun = DECL_STRUCT_FUNCTION (lad.id.src_fn);
> +  lad.id.decl_map = &decl_map;
> +  lad.id.copy_decl = assumption_copy_decl;
> +  lad.id.transform_call_graph_edges = CB_CGE_DUPLICATE;
> +  lad.id.transform_parameter = true;
> +  lad.id.do_not_unshare = true;
> +  lad.id.do_not_fold = true;
> +  cfun->curr_properties = lad.id.src_cfun->curr_properties;
> +  lad.guard_copy = create_tmp_var (boolean_type_node);
> +  decl_map.put (lad.guard_copy, lad.guard_copy);
> +  decl_map.put (guard, lad.guard_copy);
> +  cfun->assume_function = 1;
> +
> +  struct walk_stmt_info wi;
> +  memset (&wi, 0, sizeof (wi));
> +  wi.info = (void *) &lad;
> +  walk_gimple_stmt (gsi, find_assumption_locals_r, NULL, &wi);
> +  unsigned int sz = lad.decls.length ();
> +  for (unsigned i = 0; i < sz; ++i)
> +    {
> +      tree v = lad.decls[i];
> +      tree newv;
> +      if (TREE_CODE (v) == SSA_NAME)
> +	{
> +	  newv = make_ssa_name (remap_type (TREE_TYPE (v), &lad.id));
> +	  decl_map.put (v, newv);
> +	}
> +      else if (VAR_P (v))
> +	{
> +	  if (is_global_var (v) && !DECL_ASSEMBLER_NAME_SET_P (v))
> +	    DECL_ASSEMBLER_NAME (v);
> +	  TREE_TYPE (v) = remap_type (TREE_TYPE (v), &lad.id);
> +	  DECL_CONTEXT (v) = current_function_decl;
> +	}
> +    }
> +  memset (&wi, 0, sizeof (wi));
> +  wi.info = (void *) &lad;
> +  walk_gimple_stmt (gsi, adjust_assumption_stmt_r,
> +		    adjust_assumption_stmt_op, &wi);
> +  gsi_remove (gsi, false);
> +
> +  gimple_seq body = NULL;
> +  gimple *g = gimple_build_assign (lad.guard_copy, boolean_false_node);
> +  gimple_seq_add_stmt (&body, g);
> +  gimple_seq_add_stmt (&body, bind);
> +  greturn *gr = gimple_build_return (lad.guard_copy);
> +  gimple_seq_add_stmt (&body, gr);
> +  if (lad.return_false_label)
> +    {
> +      g = gimple_build_label (lad.return_false_label);
> +      gimple_seq_add_stmt (&body, g);
> +      g = gimple_build_assign (lad.guard_copy, boolean_false_node);
> +      gimple_seq_add_stmt (&body, g);
> +      gr = gimple_build_return (lad.guard_copy);
> +      gimple_seq_add_stmt (&body, gr);
> +    }
> +  bind = gimple_build_bind (NULL_TREE, body, NULL_TREE);
> +  body = NULL;
> +  gimple_seq_add_stmt (&body, bind);
> +  gimple_set_body (current_function_decl, body);
> +  pop_cfun ();
> +
> +  tree parms = NULL_TREE;
> +  tree parmt = void_list_node;
> +  auto_vec<tree, 8> vargs;
> +  vargs.safe_grow (1 + (lad.decls.length () - sz), true);
> +  vargs[0] = build_fold_addr_expr (lad.id.dst_fn);
> +  for (unsigned i = lad.decls.length (); i > sz; --i)
> +    {
> +      tree *v = decl_map.get (lad.decls[i - 1]);
> +      gcc_assert (v && TREE_CODE (*v) == PARM_DECL);
> +      DECL_CHAIN (*v) = parms;
> +      parms = *v;
> +      parmt = tree_cons (NULL_TREE, TREE_TYPE (*v), parmt);
> +      vargs[i - sz] = lad.decls[i - 1];
> +      if (is_gimple_reg_type (TREE_TYPE (vargs[i - sz]))
> +	  && !is_gimple_val (vargs[i - sz]))

a few comments might be helpful here

> +	{
> +	  tree t = make_ssa_name (TREE_TYPE (vargs[i - sz]));
> +	  g = gimple_build_assign (t, vargs[i - sz]);
> +	  gsi_insert_before (gsi, g, GSI_SAME_STMT);
> +	  vargs[i - sz] = t;
> +	}
> +    }
> +  DECL_ARGUMENTS (lad.id.dst_fn) = parms;
> +  TREE_TYPE (lad.id.dst_fn) = build_function_type (boolean_type_node, parmt);

ah, here's the type.  Maybe use error_mark_node as transitional type?

> +  cgraph_node::add_new_function (lad.id.dst_fn, false);
> +
> +  for (unsigned i = 0; i < sz; ++i)
> +    {
> +      tree v = lad.decls[i];
> +      if (TREE_CODE (v) == SSA_NAME)
> +	release_ssa_name (v);
> +    }
> +
> +  stmt = gsi_stmt (*gsi);
> +  lab = as_a <glabel *> (stmt);
> +  gcc_assert (gimple_label_label (lab) == l1
> +	      || gimple_label_label (lab) == l2);
> +  gsi_remove (gsi, true);
> +  stmt = gsi_stmt (*gsi);
> +  gcc_assert (gimple_call_internal_p (stmt, IFN_ASSUME)
> +	      && gimple_call_num_args (stmt) == 1
> +	      && gimple_call_arg (stmt, 0) == guard);
> +  data->cannot_fallthru = false;
> +  gcall *call = gimple_build_call_internal_vec (IFN_ASSUME, vargs);
> +  gimple_set_location (call, loc);
> +  gsi_replace (gsi, call, true);
> +}
>  
>  /* Lower statement GSI.  DATA is passed through the recursion.  We try to
>     track the fallthruness of statements and get rid of unreachable return
> @@ -354,6 +738,13 @@ lower_stmt (gimple_stmt_iterator *gsi, s
>  	tree decl = gimple_call_fndecl (stmt);
>  	unsigned i;
>  
> +	if (gimple_call_internal_p (stmt, IFN_ASSUME)
> +	    && gimple_call_num_args (stmt) == 0)
> +	  {
> +	    lower_assumption (gsi, data);
> +	    return;
> +	  }
> +
>  	for (i = 0; i < gimple_call_num_args (stmt); i++)
>  	  {
>  	    tree arg = gimple_call_arg (stmt, i);
> --- gcc/tree-ssa-ccp.cc.jj	2022-10-10 11:57:40.203722414 +0200
> +++ gcc/tree-ssa-ccp.cc	2022-10-12 19:48:28.891554716 +0200
> @@ -4253,6 +4253,12 @@ pass_fold_builtins::execute (function *f
>  	    }
>  
>  	  callee = gimple_call_fndecl (stmt);
> +	  if (!callee
> +	      && gimple_call_internal_p (stmt, IFN_ASSUME))
> +	    {
> +	      gsi_remove (&i, true);
> +	      continue;
> +	    }
>  	  if (!callee || !fndecl_built_in_p (callee, BUILT_IN_NORMAL))
>  	    {
>  	      gsi_next (&i);
> --- gcc/lto-streamer-out.cc.jj	2022-10-10 11:57:40.202722428 +0200
> +++ gcc/lto-streamer-out.cc	2022-10-12 19:48:28.891554716 +0200
> @@ -2278,6 +2278,7 @@ output_struct_function_base (struct outp
>    bp_pack_value (&bp, fn->calls_eh_return, 1);
>    bp_pack_value (&bp, fn->has_force_vectorize_loops, 1);
>    bp_pack_value (&bp, fn->has_simduid_loops, 1);
> +  bp_pack_value (&bp, fn->assume_function, 1);
>    bp_pack_value (&bp, fn->va_list_fpr_size, 8);
>    bp_pack_value (&bp, fn->va_list_gpr_size, 8);
>    bp_pack_value (&bp, fn->last_clique, sizeof (short) * 8);
> --- gcc/lto-streamer-in.cc.jj	2022-10-10 11:57:40.201722442 +0200
> +++ gcc/lto-streamer-in.cc	2022-10-12 19:48:28.891554716 +0200
> @@ -1318,6 +1318,7 @@ input_struct_function_base (struct funct
>    fn->calls_eh_return = bp_unpack_value (&bp, 1);
>    fn->has_force_vectorize_loops = bp_unpack_value (&bp, 1);
>    fn->has_simduid_loops = bp_unpack_value (&bp, 1);
> +  fn->assume_function = bp_unpack_value (&bp, 1);
>    fn->va_list_fpr_size = bp_unpack_value (&bp, 8);
>    fn->va_list_gpr_size = bp_unpack_value (&bp, 8);
>    fn->last_clique = bp_unpack_value (&bp, sizeof (short) * 8);
> --- gcc/cgraphunit.cc.jj	2022-10-10 11:57:40.152723125 +0200
> +++ gcc/cgraphunit.cc	2022-10-12 19:48:28.892554703 +0200
> @@ -1882,6 +1882,16 @@ cgraph_node::expand (void)
>    ggc_collect ();
>    timevar_pop (TV_REST_OF_COMPILATION);
>  
> +  if (DECL_STRUCT_FUNCTION (decl)
> +      && DECL_STRUCT_FUNCTION (decl)->assume_function)
> +    {
> +      /* Assume functions aren't expanded into RTL, on the other side
> +	 we don't want to release their body.  */
> +      if (cfun)
> +	pop_cfun ();
> +      return;
> +    }
> +
>    /* Make sure that BE didn't give up on compiling.  */
>    gcc_assert (TREE_ASM_WRITTEN (decl));
>    if (cfun)
> --- gcc/cfgexpand.cc.jj	2022-10-10 11:57:40.152723125 +0200
> +++ gcc/cfgexpand.cc	2022-10-12 19:48:28.893554689 +0200
> @@ -6597,6 +6597,14 @@ pass_expand::execute (function *fun)
>    rtx_insn *var_seq, *var_ret_seq;
>    unsigned i;
>  
> +  if (cfun->assume_function)
> +    {
> +      /* Assume functions should not be expanded to RTL.  */

can we avoid getting here in the first place?  I think we don't need
any of the post-pass_all_optimizations[_g] passes?

> +      cfun->curr_properties &= ~PROP_loops;
> +      loop_optimizer_finalize ();
> +      return 0;
> +    }
> +
>    timevar_push (TV_OUT_OF_SSA);
>    rewrite_out_of_ssa (&SA);
>    timevar_pop (TV_OUT_OF_SSA);
> --- gcc/internal-fn.cc.jj	2022-10-10 11:57:40.166722930 +0200
> +++ gcc/internal-fn.cc	2022-10-12 19:48:28.893554689 +0200
> @@ -4526,5 +4526,4 @@ expand_TRAP (internal_fn, gcall *)
>  void
>  expand_ASSUME (internal_fn, gcall *)
>  {
> -  gcc_unreachable ();
>  }
> --- gcc/passes.cc.jj	2022-10-10 11:57:40.202722428 +0200
> +++ gcc/passes.cc	2022-10-12 19:48:28.893554689 +0200
> @@ -647,11 +647,12 @@ public:
>    {}
>  
>    /* opt_pass methods: */
> -  bool gate (function *) final override
> +  bool gate (function *fun) final override
>      {
>        /* Early return if there were errors.  We can run afoul of our
>  	 consistency checks, and there's not really much point in fixing them.  */
> -      return !(rtl_dump_and_exit || flag_syntax_only || seen_error ());
> +      return !(rtl_dump_and_exit || fun->assume_function
> +	       || flag_syntax_only || seen_error ());
>      }
>  
>  }; // class pass_rest_of_compilation
> --- gcc/tree-vectorizer.cc.jj	2022-10-10 11:57:40.204722400 +0200
> +++ gcc/tree-vectorizer.cc	2022-10-12 19:48:28.894554675 +0200
> @@ -1213,6 +1213,10 @@ public:
>    /* opt_pass methods: */
>    bool gate (function *fun) final override
>      {
> +      /* Vectorization makes range analysis of assume functions
> +	 harder, not easier.  */
> +      if (fun->assume_function)
> +	return false;
>        return flag_tree_loop_vectorize || fun->has_force_vectorize_loops;
>      }
>  
> @@ -1490,7 +1494,14 @@ public:
>  
>    /* opt_pass methods: */
>    opt_pass * clone () final override { return new pass_slp_vectorize (m_ctxt); }
> -  bool gate (function *) final override { return flag_tree_slp_vectorize != 0; }
> +  bool gate (function *fun) final override
> +  {
> +    /* Vectorization makes range analysis of assume functions harder,
> +       not easier.  */

Can we split out these kind of considerations from the initial patch?

> +    if (fun->assume_function)
> +      return false;
> +    return flag_tree_slp_vectorize != 0;
> +  }
>    unsigned int execute (function *) final override;
>  
>  }; // class pass_slp_vectorize
> --- gcc/ipa-icf.cc.jj	2022-10-10 11:57:40.201722442 +0200
> +++ gcc/ipa-icf.cc	2022-10-12 19:48:28.894554675 +0200
> @@ -1517,6 +1517,9 @@ sem_function::parse (cgraph_node *node,
>    if (!func || (!node->has_gimple_body_p () && !node->thunk))
>      return NULL;
>  
> +  if (func->assume_function)
> +    return NULL;
> +

Do we want implicit noipa attribute on the assume functions?  Or do
we need to IPA-CP into them?  I suppose the ranger code can use
contextual code from the .ASSUME call for things like
assume (side-effect(), i == 1);

>    if (lookup_attribute_by_prefix ("omp ", DECL_ATTRIBUTES (node->decl)) != NULL)
>      return NULL;
>  
> --- gcc/cp/cp-tree.h.jj	2022-10-12 17:51:00.911944744 +0200
> +++ gcc/cp/cp-tree.h	2022-10-12 19:53:17.072615254 +0200
> @@ -8284,6 +8284,7 @@ extern tree predeclare_vla			(tree);
>  extern void clear_fold_cache			(void);
>  extern tree lookup_hotness_attribute		(tree);
>  extern tree process_stmt_hotness_attribute	(tree, location_t);
> +extern tree build_assume_call			(location_t, tree);
>  extern tree process_stmt_assume_attribute	(tree, tree, location_t);
>  extern bool simple_empty_class_p		(tree, tree, tree_code);
>  extern tree fold_builtin_source_location	(location_t);
> --- gcc/cp/parser.cc.jj	2022-10-12 17:51:00.951944199 +0200
> +++ gcc/cp/parser.cc	2022-10-12 19:52:15.855452024 +0200
> @@ -46006,11 +46006,7 @@ cp_parser_omp_assumption_clauses (cp_par
>  	      if (!type_dependent_expression_p (t))
>  		t = contextual_conv_bool (t, tf_warning_or_error);
>  	      if (is_assume && !error_operand_p (t))
> -		{
> -		  t = build_call_expr_internal_loc (eloc, IFN_ASSUME,
> -						    void_type_node, 1, t);
> -		  finish_expr_stmt (t);
> -		}
> +		finish_expr_stmt (build_assume_call (eloc, t));
>  	      if (!parens.require_close (parser))
>  		cp_parser_skip_to_closing_parenthesis (parser,
>  						       /*recovering=*/true,
> --- gcc/cp/cp-gimplify.cc.jj	2022-10-12 17:51:00.909944772 +0200
> +++ gcc/cp/cp-gimplify.cc	2022-10-12 19:51:47.140844525 +0200
> @@ -3096,6 +3096,17 @@ process_stmt_hotness_attribute (tree std
>    return std_attrs;
>  }
>  
> +/* Build IFN_ASSUME internal call for assume condition ARG.  */
> +
> +tree
> +build_assume_call (location_t loc, tree arg)
> +{
> +  if (!processing_template_decl)
> +    arg = fold_build_cleanup_point_expr (TREE_TYPE (arg), arg);
> +  return build_call_expr_internal_loc (loc, IFN_ASSUME, void_type_node,
> +				       1, arg);
> +}
> +
>  /* If [[assume (cond)]] appears on this statement, handle it.  */
>  
>  tree
> @@ -3132,9 +3143,7 @@ process_stmt_assume_attribute (tree std_
>  	    arg = contextual_conv_bool (arg, tf_warning_or_error);
>  	  if (error_operand_p (arg))
>  	    continue;
> -	  statement = build_call_expr_internal_loc (attrs_loc, IFN_ASSUME,
> -						    void_type_node, 1, arg);
> -	  finish_expr_stmt (statement);
> +	  finish_expr_stmt (build_assume_call (attrs_loc, arg));
>  	}
>      }
>    return remove_attribute ("gnu", "assume", std_attrs);
> --- gcc/cp/pt.cc.jj	2022-10-12 17:51:00.957944117 +0200
> +++ gcc/cp/pt.cc	2022-10-12 19:52:45.204050862 +0200
> @@ -21116,10 +21116,7 @@ tsubst_copy_and_build (tree t,
>  		      ret = error_mark_node;
>  		      break;
>  		    }
> -		  ret = build_call_expr_internal_loc (EXPR_LOCATION (t),
> -						      IFN_ASSUME,
> -						      void_type_node, 1,
> -						      arg);
> +		  ret = build_assume_call (EXPR_LOCATION (t), arg);
>  		  RETURN (ret);
>  		}
>  	      break;
> --- gcc/testsuite/g++.dg/cpp23/attr-assume5.C.jj	2022-10-12 19:48:28.903554553 +0200
> +++ gcc/testsuite/g++.dg/cpp23/attr-assume5.C	2022-10-12 19:48:28.903554553 +0200
> @@ -0,0 +1,5 @@
> +// P1774R8 - Portable assumptions
> +// { dg-do run { target c++11 } }
> +// { dg-options "-O2" }
> +
> +#include "attr-assume1.C"
> --- gcc/testsuite/g++.dg/cpp23/attr-assume6.C.jj	2022-10-12 19:48:28.903554553 +0200
> +++ gcc/testsuite/g++.dg/cpp23/attr-assume6.C	2022-10-12 19:48:28.903554553 +0200
> @@ -0,0 +1,5 @@
> +// P1774R8 - Portable assumptions
> +// { dg-do run { target c++11 } }
> +// { dg-options "-O2" }
> +
> +#include "attr-assume3.C"
> --- gcc/testsuite/g++.dg/cpp23/attr-assume7.C.jj	2022-10-12 19:48:28.903554553 +0200
> +++ gcc/testsuite/g++.dg/cpp23/attr-assume7.C	2022-10-12 19:48:28.903554553 +0200
> @@ -0,0 +1,42 @@
> +// P1774R8 - Portable assumptions
> +// { dg-do compile { target c++11 } }
> +// { dg-options "-O2" }
> +
> +int
> +foo (int x)
> +{
> +  [[assume (x == 42)]];
> +  return x;
> +}
> +
> +int
> +bar (int x)
> +{
> +  [[assume (++x == 43)]];
> +  return x;
> +}
> +
> +int
> +baz (int x)
> +{
> +  [[assume (({ int z = ++x; static int w; ++w; if (z == 51) return -1; if (z == 53) goto lab1; if (z == 64) throw 1; z == 43; }))]];
> +lab1:
> +  return x;
> +}
> +
> +struct S { S (); S (const S &); ~S (); int a, b; int foo (); };
> +
> +int
> +qux ()
> +{
> +  S s;
> +  [[assume (s.a == 42 && s.b == 43)]];
> +  return s.a + s.b;
> +}
> +
> +int
> +S::foo ()
> +{
> +  [[assume (a == 42 && b == 43)]];
> +  return a + b;
> +}

Reading some of the patch I guessed you wanted to handle nested
assumes.  So - is

[[assume (a == 4 && ([[assume(b == 3)]], b != 2))]]

a thing?

Thanks,
Richard.
  
Jakub Jelinek Oct. 14, 2022, 6:33 p.m. UTC | #2
On Fri, Oct 14, 2022 at 11:27:07AM +0000, Richard Biener wrote:
> > --- gcc/function.h.jj	2022-10-10 11:57:40.163722972 +0200
> > +++ gcc/function.h	2022-10-12 19:48:28.887554771 +0200
> > @@ -438,6 +438,10 @@ struct GTY(()) function {
> >  
> >    /* Set if there are any OMP_TARGET regions in the function.  */
> >    unsigned int has_omp_target : 1;
> > +
> > +  /* Set for artificial function created for [[assume (cond)]].
> > +     These should be GIMPLE optimized, but not expanded to RTL.  */
> > +  unsigned int assume_function : 1;
> 
> I wonder if we should have this along force_output in the symtab
> node and let the symtab code decide whether to expand?

I actually first had a flag on the symtab node but as the patch shows,
when it needs to be tested, more frequently I have access to struct function
than to cgraph node.

> > --- gcc/gimplify.cc.jj	2022-10-10 11:57:40.165722944 +0200
> > +++ gcc/gimplify.cc	2022-10-12 19:48:28.890554730 +0200
> > @@ -3569,7 +3569,52 @@ gimplify_call_expr (tree *expr_p, gimple
> >  						     fndecl, 0));
> >  	      return GS_OK;
> >  	    }
> > -	  /* FIXME: Otherwise expand it specially.  */
> > +	  /* If not optimizing, ignore the assumptions.  */
> > +	  if (!optimize)
> > +	    {
> > +	      *expr_p = NULL_TREE;
> > +	      return GS_ALL_DONE;
> > +	    }
> > +	  /* Temporarily, until gimple lowering, transform
> > +	     .ASSUME (cond);
> > +	     into:
> > +	     guard = .ASSUME ();
> > +	     if (guard) goto label_true; else label_false;
> > +	     label_true:;
> > +	     {
> > +	       guard = cond;
> > +	     }
> > +	     label_false:;
> > +	     .ASSUME (guard);
> > +	     such that gimple lowering can outline the condition into
> > +	     a separate function easily.  */
> 
> So the idea to use lambdas and/or nested functions (for OMP)
> didn't work out or is more complicated?

Yes, that didn't work out.  Both lambda creation and nested function
handling produce big structures with everything while for the assumptions
it is better to have separate scalars if possible, lambda creation has
various language imposed restrictions, diagnostics etc. and isn't
available in C and I think the outlining in the patch is pretty simple and
short.

> I wonder if, instead of using the above intermediate form we
> can have a new structued GIMPLE code with sub-statements
> 
>  .ASSUME
>    {
>      condition;
>    }

That is what I wrote in the patch description as alternative:
"with the condition wrapped into a GIMPLE_BIND (I admit the above isn't                                                                                                                
extra clean but it is just something to hold it from gimplifier until                                                                                                                 
gimple low pass; it reassembles if (condition_never_true) { cond; };                                                                                                                  
an alternative would be introduce GOMP_ASSUME statement that would have                                                                                                               
the guard var as operand and the GIMPLE_BIND as body, but for the                                                                                                                     
few passes (tree-nested and omp lowering) in between that looked like                                                                                                                 
an overkill to me)"
I can certainly implement that easily.

> ?  There's gimple_statement_omp conveniently available as base and
> IIRC you had the requirement to implement some OMP assume as well?

For OpenMP assumptions we right now implement just the holds clause
of assume and implement it the same way as assume/gnu::assume attributes.

> Of ocurse a different stmt class with body would work as well here,
> maybe we can even use a gbind with a special flag.
> 
> The outlining code can then be ajusted to outline a single BIND?

It already is adjusting a single bind (of course with everything nested in
it).

> It probably won't simplify much that way.

> > +static tree
> > +create_assumption_fn (location_t loc)
> > +{
> > +  tree name = clone_function_name_numbered (current_function_decl, "_assume");
> > +  /* For now, will be changed later.  */
> 
> ?

I need to create the FUNCTION_DECL early and only later on discover
the used automatic vars (for which I need the destination function)
and only once those are discovered I can create the right
function type for the function.

> > +  tree type = TREE_TYPE (current_function_decl);

> > +  DECL_FUNCTION_SPECIFIC_OPTIMIZATION (decl)
> > +    = DECL_FUNCTION_SPECIFIC_OPTIMIZATION (current_function_decl);
> > +  DECL_FUNCTION_SPECIFIC_TARGET (decl)
> > +    = DECL_FUNCTION_SPECIFIC_TARGET (current_function_decl);
> > +  DECL_FUNCTION_VERSIONED (decl)
> > +    = DECL_FUNCTION_VERSIONED (current_function_decl);
> 
> what does it mean to copy DECL_FUNCTION_VERSIONED here?

This was a copy and paste from elsewhere (I think OpenMP code).
I guess I can nuke it and even better add some testcase coverage
for various nasty things like assume in multi-versioned functions.
> > +  DECL_ARGUMENTS (lad.id.dst_fn) = parms;
> > +  TREE_TYPE (lad.id.dst_fn) = build_function_type (boolean_type_node, parmt);
> 
> ah, here's the type.  Maybe use error_mark_node as transitional type?

Will see if that works.
> 
> > +  cgraph_node::add_new_function (lad.id.dst_fn, false);
> > +
> > +  for (unsigned i = 0; i < sz; ++i)
> > +    {
> > +      tree v = lad.decls[i];
> > +      if (TREE_CODE (v) == SSA_NAME)
> > +	release_ssa_name (v);
> > +    }
> > +
> > +  stmt = gsi_stmt (*gsi);
> > +  lab = as_a <glabel *> (stmt);
> > +  gcc_assert (gimple_label_label (lab) == l1
> > +	      || gimple_label_label (lab) == l2);
> > +  gsi_remove (gsi, true);
> > +  stmt = gsi_stmt (*gsi);
> > +  gcc_assert (gimple_call_internal_p (stmt, IFN_ASSUME)
> > +	      && gimple_call_num_args (stmt) == 1
> > +	      && gimple_call_arg (stmt, 0) == guard);
> > +  data->cannot_fallthru = false;
> > +  gcall *call = gimple_build_call_internal_vec (IFN_ASSUME, vargs);
> > +  gimple_set_location (call, loc);
> > +  gsi_replace (gsi, call, true);
> > +}
> >  
> >  /* Lower statement GSI.  DATA is passed through the recursion.  We try to
> >     track the fallthruness of statements and get rid of unreachable return
> > @@ -354,6 +738,13 @@ lower_stmt (gimple_stmt_iterator *gsi, s
> >  	tree decl = gimple_call_fndecl (stmt);
> >  	unsigned i;
> >  
> > +	if (gimple_call_internal_p (stmt, IFN_ASSUME)
> > +	    && gimple_call_num_args (stmt) == 0)
> > +	  {
> > +	    lower_assumption (gsi, data);
> > +	    return;
> > +	  }
> > +
> >  	for (i = 0; i < gimple_call_num_args (stmt); i++)
> >  	  {
> >  	    tree arg = gimple_call_arg (stmt, i);
> > --- gcc/tree-ssa-ccp.cc.jj	2022-10-10 11:57:40.203722414 +0200
> > +++ gcc/tree-ssa-ccp.cc	2022-10-12 19:48:28.891554716 +0200
> > @@ -4253,6 +4253,12 @@ pass_fold_builtins::execute (function *f
> >  	    }
> >  
> >  	  callee = gimple_call_fndecl (stmt);
> > +	  if (!callee
> > +	      && gimple_call_internal_p (stmt, IFN_ASSUME))
> > +	    {
> > +	      gsi_remove (&i, true);
> > +	      continue;
> > +	    }
> >  	  if (!callee || !fndecl_built_in_p (callee, BUILT_IN_NORMAL))
> >  	    {
> >  	      gsi_next (&i);
> > --- gcc/lto-streamer-out.cc.jj	2022-10-10 11:57:40.202722428 +0200
> > +++ gcc/lto-streamer-out.cc	2022-10-12 19:48:28.891554716 +0200
> > @@ -2278,6 +2278,7 @@ output_struct_function_base (struct outp
> >    bp_pack_value (&bp, fn->calls_eh_return, 1);
> >    bp_pack_value (&bp, fn->has_force_vectorize_loops, 1);
> >    bp_pack_value (&bp, fn->has_simduid_loops, 1);
> > +  bp_pack_value (&bp, fn->assume_function, 1);
> >    bp_pack_value (&bp, fn->va_list_fpr_size, 8);
> >    bp_pack_value (&bp, fn->va_list_gpr_size, 8);
> >    bp_pack_value (&bp, fn->last_clique, sizeof (short) * 8);
> > --- gcc/lto-streamer-in.cc.jj	2022-10-10 11:57:40.201722442 +0200
> > +++ gcc/lto-streamer-in.cc	2022-10-12 19:48:28.891554716 +0200
> > @@ -1318,6 +1318,7 @@ input_struct_function_base (struct funct
> >    fn->calls_eh_return = bp_unpack_value (&bp, 1);
> >    fn->has_force_vectorize_loops = bp_unpack_value (&bp, 1);
> >    fn->has_simduid_loops = bp_unpack_value (&bp, 1);
> > +  fn->assume_function = bp_unpack_value (&bp, 1);
> >    fn->va_list_fpr_size = bp_unpack_value (&bp, 8);
> >    fn->va_list_gpr_size = bp_unpack_value (&bp, 8);
> >    fn->last_clique = bp_unpack_value (&bp, sizeof (short) * 8);
> > --- gcc/cgraphunit.cc.jj	2022-10-10 11:57:40.152723125 +0200
> > +++ gcc/cgraphunit.cc	2022-10-12 19:48:28.892554703 +0200
> > @@ -1882,6 +1882,16 @@ cgraph_node::expand (void)
> >    ggc_collect ();
> >    timevar_pop (TV_REST_OF_COMPILATION);
> >  
> > +  if (DECL_STRUCT_FUNCTION (decl)
> > +      && DECL_STRUCT_FUNCTION (decl)->assume_function)
> > +    {
> > +      /* Assume functions aren't expanded into RTL, on the other side
> > +	 we don't want to release their body.  */
> > +      if (cfun)
> > +	pop_cfun ();
> > +      return;
> > +    }
> > +
> >    /* Make sure that BE didn't give up on compiling.  */
> >    gcc_assert (TREE_ASM_WRITTEN (decl));
> >    if (cfun)
> > --- gcc/cfgexpand.cc.jj	2022-10-10 11:57:40.152723125 +0200
> > +++ gcc/cfgexpand.cc	2022-10-12 19:48:28.893554689 +0200
> > @@ -6597,6 +6597,14 @@ pass_expand::execute (function *fun)
> >    rtx_insn *var_seq, *var_ret_seq;
> >    unsigned i;
> >  
> > +  if (cfun->assume_function)
> > +    {
> > +      /* Assume functions should not be expanded to RTL.  */
> 
> can we avoid getting here in the first place?  I think we don't need
> any of the post-pass_all_optimizations[_g] passes?

I'm afraid not without revamping passes.def, because
to easily cat the pass queue from certain point onwards, we
need all the remaining passes to be wrapped with
PUSH_INSERT_PASSES_WITHIN.
So, if we e.g. wanted to cut out everything from pass_tm_init
onwards, we'd need to wrap:
  NEXT_PASS (pass_tm_init);
  PUSH_INSERT_PASSES_WITHIN (pass_tm_init)
      NEXT_PASS (pass_tm_mark);
      NEXT_PASS (pass_tm_memopt);
      NEXT_PASS (pass_tm_edges);
  POP_INSERT_PASSES ()
  NEXT_PASS (pass_simduid_cleanup);
  NEXT_PASS (pass_vtable_verify);
  NEXT_PASS (pass_lower_vaarg);
  NEXT_PASS (pass_lower_vector);
  NEXT_PASS (pass_lower_complex_O0);
  NEXT_PASS (pass_sancov_O0);
  NEXT_PASS (pass_lower_switch_O0);
  NEXT_PASS (pass_asan_O0);
  NEXT_PASS (pass_tsan_O0);
  NEXT_PASS (pass_sanopt);
  NEXT_PASS (pass_cleanup_eh);
  NEXT_PASS (pass_lower_resx);
  NEXT_PASS (pass_nrv);
  NEXT_PASS (pass_gimple_isel);
  NEXT_PASS (pass_harden_conditional_branches);
  NEXT_PASS (pass_harden_compares);
  NEXT_PASS (pass_warn_access, /*early=*/false);
  NEXT_PASS (pass_cleanup_cfg_post_optimizing);
  NEXT_PASS (pass_warn_function_noreturn);

  NEXT_PASS (pass_expand);
in some wrapper pass with a gate (either also including
pass_rest_of_compilation but that would mean undesirable
reindentation of everything there, or just
the above ones and have assume_function punt in the 2
or 1 gates).

What I had in the patch was just skip pass_expand
and pass_rest_of_compilation, perhaps another possibility
to do the former would be to define a gate on pass_expand.
> > --- gcc/tree-vectorizer.cc.jj	2022-10-10 11:57:40.204722400 +0200
> > +++ gcc/tree-vectorizer.cc	2022-10-12 19:48:28.894554675 +0200
> > @@ -1213,6 +1213,10 @@ public:
> >    /* opt_pass methods: */
> >    bool gate (function *fun) final override
> >      {
> > +      /* Vectorization makes range analysis of assume functions
> > +	 harder, not easier.  */
> > +      if (fun->assume_function)
> > +	return false;
> >        return flag_tree_loop_vectorize || fun->has_force_vectorize_loops;
> >      }
> >  
> > @@ -1490,7 +1494,14 @@ public:
> >  
> >    /* opt_pass methods: */
> >    opt_pass * clone () final override { return new pass_slp_vectorize (m_ctxt); }
> > -  bool gate (function *) final override { return flag_tree_slp_vectorize != 0; }
> > +  bool gate (function *fun) final override
> > +  {
> > +    /* Vectorization makes range analysis of assume functions harder,
> > +       not easier.  */
> 
> Can we split out these kind of considerations from the initial patch?

Sure.
> 
> > +    if (fun->assume_function)
> > +      return false;
> > +    return flag_tree_slp_vectorize != 0;
> > +  }
> >    unsigned int execute (function *) final override;
> >  
> >  }; // class pass_slp_vectorize
> > --- gcc/ipa-icf.cc.jj	2022-10-10 11:57:40.201722442 +0200
> > +++ gcc/ipa-icf.cc	2022-10-12 19:48:28.894554675 +0200
> > @@ -1517,6 +1517,9 @@ sem_function::parse (cgraph_node *node,
> >    if (!func || (!node->has_gimple_body_p () && !node->thunk))
> >      return NULL;
> >  
> > +  if (func->assume_function)
> > +    return NULL;
> > +
> 
> Do we want implicit noipa attribute on the assume functions?  Or do
> we need to IPA-CP into them?  I suppose the ranger code can use
> contextual code from the .ASSUME call for things like
> assume (side-effect(), i == 1);

Most of normal IPA optimizations are disabled for them because
they aren't normally called, all we do is take their address
and pass it to .ASSUME.  IPA-ICF was an exception and I had to
disable it because when it triggered it decided to create a thunk
which failed to assemble.
But implicit noipa surely is an option; though of course we want
inlining etc. to happen into those functions.
And eventually some kind of IPA SRA of their arguments but with
different behavior from normal IPA-SRA.

> Reading some of the patch I guessed you wanted to handle nested
> assumes.  So - is
> 
> [[assume (a == 4 && ([[assume(b == 3)]], b != 2))]]
> 
> a thing?

This is not valid, assume can be just on an empty statement.
But with GNU statement expressions it is a thing and I should add it to
testsuite coverage.

void
foo (int a, int b)
{
  [[assume (a == 4 && ({ [[assume (b == 3)]]; b != 2 }))]];
}
is valid.
I think the code should handle it fine (outline the outer and the new
outlined function enters pass queue with all_lowering_passes
and so will see pass_lower_cf again and hopefully work.  Will see
how it goes when I tweak the patch.

	Jakub
  
Richard Biener Oct. 17, 2022, 6:55 a.m. UTC | #3
On Fri, 14 Oct 2022, Jakub Jelinek wrote:

> On Fri, Oct 14, 2022 at 11:27:07AM +0000, Richard Biener wrote:
> > > --- gcc/function.h.jj	2022-10-10 11:57:40.163722972 +0200
> > > +++ gcc/function.h	2022-10-12 19:48:28.887554771 +0200
> > > @@ -438,6 +438,10 @@ struct GTY(()) function {
> > >  
> > >    /* Set if there are any OMP_TARGET regions in the function.  */
> > >    unsigned int has_omp_target : 1;
> > > +
> > > +  /* Set for artificial function created for [[assume (cond)]].
> > > +     These should be GIMPLE optimized, but not expanded to RTL.  */
> > > +  unsigned int assume_function : 1;
> > 
> > I wonder if we should have this along force_output in the symtab
> > node and let the symtab code decide whether to expand?
> 
> I actually first had a flag on the symtab node but as the patch shows,
> when it needs to be tested, more frequently I have access to struct function
> than to cgraph node.

I see.

> > > --- gcc/gimplify.cc.jj	2022-10-10 11:57:40.165722944 +0200
> > > +++ gcc/gimplify.cc	2022-10-12 19:48:28.890554730 +0200
> > > @@ -3569,7 +3569,52 @@ gimplify_call_expr (tree *expr_p, gimple
> > >  						     fndecl, 0));
> > >  	      return GS_OK;
> > >  	    }
> > > -	  /* FIXME: Otherwise expand it specially.  */
> > > +	  /* If not optimizing, ignore the assumptions.  */
> > > +	  if (!optimize)
> > > +	    {
> > > +	      *expr_p = NULL_TREE;
> > > +	      return GS_ALL_DONE;
> > > +	    }
> > > +	  /* Temporarily, until gimple lowering, transform
> > > +	     .ASSUME (cond);
> > > +	     into:
> > > +	     guard = .ASSUME ();
> > > +	     if (guard) goto label_true; else label_false;
> > > +	     label_true:;
> > > +	     {
> > > +	       guard = cond;
> > > +	     }
> > > +	     label_false:;
> > > +	     .ASSUME (guard);
> > > +	     such that gimple lowering can outline the condition into
> > > +	     a separate function easily.  */
> > 
> > So the idea to use lambdas and/or nested functions (for OMP)
> > didn't work out or is more complicated?
> 
> Yes, that didn't work out.  Both lambda creation and nested function
> handling produce big structures with everything while for the assumptions
> it is better to have separate scalars if possible, lambda creation has
> various language imposed restrictions, diagnostics etc. and isn't
> available in C and I think the outlining in the patch is pretty simple and
> short.
> 
> > I wonder if, instead of using the above intermediate form we
> > can have a new structued GIMPLE code with sub-statements
> > 
> >  .ASSUME
> >    {
> >      condition;
> >    }
> 
> That is what I wrote in the patch description as alternative:
> "with the condition wrapped into a GIMPLE_BIND (I admit the above isn't                                                                                                                
> extra clean but it is just something to hold it from gimplifier until                                                                                                                 
> gimple low pass; it reassembles if (condition_never_true) { cond; };                                                                                                                  
> an alternative would be introduce GOMP_ASSUME statement that would have                                                                                                               
> the guard var as operand and the GIMPLE_BIND as body, but for the                                                                                                                     
> few passes (tree-nested and omp lowering) in between that looked like                                                                                                                 
> an overkill to me)"
> I can certainly implement that easily.

I'd prefer that, it looks possibly less messy.

> > ?  There's gimple_statement_omp conveniently available as base and
> > IIRC you had the requirement to implement some OMP assume as well?
> 
> For OpenMP assumptions we right now implement just the holds clause
> of assume and implement it the same way as assume/gnu::assume attributes.
> 
> > Of ocurse a different stmt class with body would work as well here,
> > maybe we can even use a gbind with a special flag.
> > 
> > The outlining code can then be ajusted to outline a single BIND?
> 
> It already is adjusting a single bind (of course with everything nested in
> it).
> 
> > It probably won't simplify much that way.
> 
> > > +static tree
> > > +create_assumption_fn (location_t loc)
> > > +{
> > > +  tree name = clone_function_name_numbered (current_function_decl, "_assume");
> > > +  /* For now, will be changed later.  */
> > 
> > ?
> 
> I need to create the FUNCTION_DECL early and only later on discover
> the used automatic vars (for which I need the destination function)
> and only once those are discovered I can create the right
> function type for the function.
> 
> > > +  tree type = TREE_TYPE (current_function_decl);
> 
> > > +  DECL_FUNCTION_SPECIFIC_OPTIMIZATION (decl)
> > > +    = DECL_FUNCTION_SPECIFIC_OPTIMIZATION (current_function_decl);
> > > +  DECL_FUNCTION_SPECIFIC_TARGET (decl)
> > > +    = DECL_FUNCTION_SPECIFIC_TARGET (current_function_decl);
> > > +  DECL_FUNCTION_VERSIONED (decl)
> > > +    = DECL_FUNCTION_VERSIONED (current_function_decl);
> > 
> > what does it mean to copy DECL_FUNCTION_VERSIONED here?
> 
> This was a copy and paste from elsewhere (I think OpenMP code).
> I guess I can nuke it and even better add some testcase coverage
> for various nasty things like assume in multi-versioned functions.
>
> > > +  DECL_ARGUMENTS (lad.id.dst_fn) = parms;
> > > +  TREE_TYPE (lad.id.dst_fn) = build_function_type (boolean_type_node, parmt);
> > 
> > ah, here's the type.  Maybe use error_mark_node as transitional type?
> 
> Will see if that works.
> > 
> > > +  cgraph_node::add_new_function (lad.id.dst_fn, false);
> > > +
> > > +  for (unsigned i = 0; i < sz; ++i)
> > > +    {
> > > +      tree v = lad.decls[i];
> > > +      if (TREE_CODE (v) == SSA_NAME)
> > > +	release_ssa_name (v);
> > > +    }
> > > +
> > > +  stmt = gsi_stmt (*gsi);
> > > +  lab = as_a <glabel *> (stmt);
> > > +  gcc_assert (gimple_label_label (lab) == l1
> > > +	      || gimple_label_label (lab) == l2);
> > > +  gsi_remove (gsi, true);
> > > +  stmt = gsi_stmt (*gsi);
> > > +  gcc_assert (gimple_call_internal_p (stmt, IFN_ASSUME)
> > > +	      && gimple_call_num_args (stmt) == 1
> > > +	      && gimple_call_arg (stmt, 0) == guard);
> > > +  data->cannot_fallthru = false;
> > > +  gcall *call = gimple_build_call_internal_vec (IFN_ASSUME, vargs);
> > > +  gimple_set_location (call, loc);
> > > +  gsi_replace (gsi, call, true);
> > > +}
> > >  
> > >  /* Lower statement GSI.  DATA is passed through the recursion.  We try to
> > >     track the fallthruness of statements and get rid of unreachable return
> > > @@ -354,6 +738,13 @@ lower_stmt (gimple_stmt_iterator *gsi, s
> > >  	tree decl = gimple_call_fndecl (stmt);
> > >  	unsigned i;
> > >  
> > > +	if (gimple_call_internal_p (stmt, IFN_ASSUME)
> > > +	    && gimple_call_num_args (stmt) == 0)
> > > +	  {
> > > +	    lower_assumption (gsi, data);
> > > +	    return;
> > > +	  }
> > > +
> > >  	for (i = 0; i < gimple_call_num_args (stmt); i++)
> > >  	  {
> > >  	    tree arg = gimple_call_arg (stmt, i);
> > > --- gcc/tree-ssa-ccp.cc.jj	2022-10-10 11:57:40.203722414 +0200
> > > +++ gcc/tree-ssa-ccp.cc	2022-10-12 19:48:28.891554716 +0200
> > > @@ -4253,6 +4253,12 @@ pass_fold_builtins::execute (function *f
> > >  	    }
> > >  
> > >  	  callee = gimple_call_fndecl (stmt);
> > > +	  if (!callee
> > > +	      && gimple_call_internal_p (stmt, IFN_ASSUME))
> > > +	    {
> > > +	      gsi_remove (&i, true);
> > > +	      continue;
> > > +	    }
> > >  	  if (!callee || !fndecl_built_in_p (callee, BUILT_IN_NORMAL))
> > >  	    {
> > >  	      gsi_next (&i);
> > > --- gcc/lto-streamer-out.cc.jj	2022-10-10 11:57:40.202722428 +0200
> > > +++ gcc/lto-streamer-out.cc	2022-10-12 19:48:28.891554716 +0200
> > > @@ -2278,6 +2278,7 @@ output_struct_function_base (struct outp
> > >    bp_pack_value (&bp, fn->calls_eh_return, 1);
> > >    bp_pack_value (&bp, fn->has_force_vectorize_loops, 1);
> > >    bp_pack_value (&bp, fn->has_simduid_loops, 1);
> > > +  bp_pack_value (&bp, fn->assume_function, 1);
> > >    bp_pack_value (&bp, fn->va_list_fpr_size, 8);
> > >    bp_pack_value (&bp, fn->va_list_gpr_size, 8);
> > >    bp_pack_value (&bp, fn->last_clique, sizeof (short) * 8);
> > > --- gcc/lto-streamer-in.cc.jj	2022-10-10 11:57:40.201722442 +0200
> > > +++ gcc/lto-streamer-in.cc	2022-10-12 19:48:28.891554716 +0200
> > > @@ -1318,6 +1318,7 @@ input_struct_function_base (struct funct
> > >    fn->calls_eh_return = bp_unpack_value (&bp, 1);
> > >    fn->has_force_vectorize_loops = bp_unpack_value (&bp, 1);
> > >    fn->has_simduid_loops = bp_unpack_value (&bp, 1);
> > > +  fn->assume_function = bp_unpack_value (&bp, 1);
> > >    fn->va_list_fpr_size = bp_unpack_value (&bp, 8);
> > >    fn->va_list_gpr_size = bp_unpack_value (&bp, 8);
> > >    fn->last_clique = bp_unpack_value (&bp, sizeof (short) * 8);
> > > --- gcc/cgraphunit.cc.jj	2022-10-10 11:57:40.152723125 +0200
> > > +++ gcc/cgraphunit.cc	2022-10-12 19:48:28.892554703 +0200
> > > @@ -1882,6 +1882,16 @@ cgraph_node::expand (void)
> > >    ggc_collect ();
> > >    timevar_pop (TV_REST_OF_COMPILATION);
> > >  
> > > +  if (DECL_STRUCT_FUNCTION (decl)
> > > +      && DECL_STRUCT_FUNCTION (decl)->assume_function)
> > > +    {
> > > +      /* Assume functions aren't expanded into RTL, on the other side
> > > +	 we don't want to release their body.  */
> > > +      if (cfun)
> > > +	pop_cfun ();
> > > +      return;
> > > +    }
> > > +
> > >    /* Make sure that BE didn't give up on compiling.  */
> > >    gcc_assert (TREE_ASM_WRITTEN (decl));
> > >    if (cfun)
> > > --- gcc/cfgexpand.cc.jj	2022-10-10 11:57:40.152723125 +0200
> > > +++ gcc/cfgexpand.cc	2022-10-12 19:48:28.893554689 +0200
> > > @@ -6597,6 +6597,14 @@ pass_expand::execute (function *fun)
> > >    rtx_insn *var_seq, *var_ret_seq;
> > >    unsigned i;
> > >  
> > > +  if (cfun->assume_function)
> > > +    {
> > > +      /* Assume functions should not be expanded to RTL.  */
> > 
> > can we avoid getting here in the first place?  I think we don't need
> > any of the post-pass_all_optimizations[_g] passes?
> 
> I'm afraid not without revamping passes.def, because
> to easily cat the pass queue from certain point onwards, we
> need all the remaining passes to be wrapped with
> PUSH_INSERT_PASSES_WITHIN.
> So, if we e.g. wanted to cut out everything from pass_tm_init
> onwards, we'd need to wrap:
>   NEXT_PASS (pass_tm_init);
>   PUSH_INSERT_PASSES_WITHIN (pass_tm_init)
>       NEXT_PASS (pass_tm_mark);
>       NEXT_PASS (pass_tm_memopt);
>       NEXT_PASS (pass_tm_edges);
>   POP_INSERT_PASSES ()
>   NEXT_PASS (pass_simduid_cleanup);
>   NEXT_PASS (pass_vtable_verify);
>   NEXT_PASS (pass_lower_vaarg);
>   NEXT_PASS (pass_lower_vector);
>   NEXT_PASS (pass_lower_complex_O0);
>   NEXT_PASS (pass_sancov_O0);
>   NEXT_PASS (pass_lower_switch_O0);
>   NEXT_PASS (pass_asan_O0);
>   NEXT_PASS (pass_tsan_O0);
>   NEXT_PASS (pass_sanopt);
>   NEXT_PASS (pass_cleanup_eh);
>   NEXT_PASS (pass_lower_resx);
>   NEXT_PASS (pass_nrv);
>   NEXT_PASS (pass_gimple_isel);
>   NEXT_PASS (pass_harden_conditional_branches);
>   NEXT_PASS (pass_harden_compares);
>   NEXT_PASS (pass_warn_access, /*early=*/false);
>   NEXT_PASS (pass_cleanup_cfg_post_optimizing);
>   NEXT_PASS (pass_warn_function_noreturn);
> 
>   NEXT_PASS (pass_expand);
> in some wrapper pass with a gate (either also including
> pass_rest_of_compilation but that would mean undesirable
> reindentation of everything there, or just
> the above ones and have assume_function punt in the 2
> or 1 gates).

Ah, they are all in all_passes :/  Maybe we can add something
like TODO_discard_function (or a property) that will not discard
the function but stop compiling it?  I wonder if all cleanup
is properly done for the function -  I suppose we want to keep the
body around for callers indefinitely.

> What I had in the patch was just skip pass_expand
> and pass_rest_of_compilation, perhaps another possibility
> to do the former would be to define a gate on pass_expand.

Or some gate in the pass manager, like in override_gate_status
check fun->properties & PROP_suspended and have some
pass_suspend_assume add that property for all assume function
bodies.

In case you like any of the above give it a shot, otherwise what
you have isn't too bad, I just wondered if there's a nicer way.



> > > --- gcc/tree-vectorizer.cc.jj	2022-10-10 11:57:40.204722400 +0200
> > > +++ gcc/tree-vectorizer.cc	2022-10-12 19:48:28.894554675 +0200
> > > @@ -1213,6 +1213,10 @@ public:
> > >    /* opt_pass methods: */
> > >    bool gate (function *fun) final override
> > >      {
> > > +      /* Vectorization makes range analysis of assume functions
> > > +	 harder, not easier.  */
> > > +      if (fun->assume_function)
> > > +	return false;
> > >        return flag_tree_loop_vectorize || fun->has_force_vectorize_loops;
> > >      }
> > >  
> > > @@ -1490,7 +1494,14 @@ public:
> > >  
> > >    /* opt_pass methods: */
> > >    opt_pass * clone () final override { return new pass_slp_vectorize (m_ctxt); }
> > > -  bool gate (function *) final override { return flag_tree_slp_vectorize != 0; }
> > > +  bool gate (function *fun) final override
> > > +  {
> > > +    /* Vectorization makes range analysis of assume functions harder,
> > > +       not easier.  */
> > 
> > Can we split out these kind of considerations from the initial patch?
> 
> Sure.
> > 
> > > +    if (fun->assume_function)
> > > +      return false;
> > > +    return flag_tree_slp_vectorize != 0;
> > > +  }
> > >    unsigned int execute (function *) final override;
> > >  
> > >  }; // class pass_slp_vectorize
> > > --- gcc/ipa-icf.cc.jj	2022-10-10 11:57:40.201722442 +0200
> > > +++ gcc/ipa-icf.cc	2022-10-12 19:48:28.894554675 +0200
> > > @@ -1517,6 +1517,9 @@ sem_function::parse (cgraph_node *node,
> > >    if (!func || (!node->has_gimple_body_p () && !node->thunk))
> > >      return NULL;
> > >  
> > > +  if (func->assume_function)
> > > +    return NULL;
> > > +
> > 
> > Do we want implicit noipa attribute on the assume functions?  Or do
> > we need to IPA-CP into them?  I suppose the ranger code can use
> > contextual code from the .ASSUME call for things like
> > assume (side-effect(), i == 1);
> 
> Most of normal IPA optimizations are disabled for them because
> they aren't normally called, all we do is take their address
> and pass it to .ASSUME.  IPA-ICF was an exception and I had to
> disable it because when it triggered it decided to create a thunk
> which failed to assemble.
> But implicit noipa surely is an option; though of course we want
> inlining etc. to happen into those functions.
> And eventually some kind of IPA SRA of their arguments but with
> different behavior from normal IPA-SRA.

I suppose for now adding noipa is easiest, we'd still inline into
the body of course.

> > Reading some of the patch I guessed you wanted to handle nested
> > assumes.  So - is
> > 
> > [[assume (a == 4 && ([[assume(b == 3)]], b != 2))]]
> > 
> > a thing?
> 
> This is not valid, assume can be just on an empty statement.
> But with GNU statement expressions it is a thing and I should add it to
> testsuite coverage.
> 
> void
> foo (int a, int b)
> {
>   [[assume (a == 4 && ({ [[assume (b == 3)]]; b != 2 }))]];
> }
> is valid.
> I think the code should handle it fine (outline the outer and the new
> outlined function enters pass queue with all_lowering_passes
> and so will see pass_lower_cf again and hopefully work.  Will see
> how it goes when I tweak the patch.

Thanks,
Richard.
  

Patch

--- gcc/function.h.jj	2022-10-10 11:57:40.163722972 +0200
+++ gcc/function.h	2022-10-12 19:48:28.887554771 +0200
@@ -438,6 +438,10 @@  struct GTY(()) function {
 
   /* Set if there are any OMP_TARGET regions in the function.  */
   unsigned int has_omp_target : 1;
+
+  /* Set for artificial function created for [[assume (cond)]].
+     These should be GIMPLE optimized, but not expanded to RTL.  */
+  unsigned int assume_function : 1;
 };
 
 /* Add the decl D to the local_decls list of FUN.  */
--- gcc/gimplify.cc.jj	2022-10-10 11:57:40.165722944 +0200
+++ gcc/gimplify.cc	2022-10-12 19:48:28.890554730 +0200
@@ -3569,7 +3569,52 @@  gimplify_call_expr (tree *expr_p, gimple
 						     fndecl, 0));
 	      return GS_OK;
 	    }
-	  /* FIXME: Otherwise expand it specially.  */
+	  /* If not optimizing, ignore the assumptions.  */
+	  if (!optimize)
+	    {
+	      *expr_p = NULL_TREE;
+	      return GS_ALL_DONE;
+	    }
+	  /* Temporarily, until gimple lowering, transform
+	     .ASSUME (cond);
+	     into:
+	     guard = .ASSUME ();
+	     if (guard) goto label_true; else label_false;
+	     label_true:;
+	     {
+	       guard = cond;
+	     }
+	     label_false:;
+	     .ASSUME (guard);
+	     such that gimple lowering can outline the condition into
+	     a separate function easily.  */
+	  tree guard = create_tmp_var (boolean_type_node);
+	  gcall *call = gimple_build_call_internal (ifn, 0);
+	  gimple_call_set_nothrow (call, TREE_NOTHROW (*expr_p));
+	  gimple_set_location (call, loc);
+	  gimple_call_set_lhs (call, guard);
+	  gimple_seq_add_stmt (pre_p, call);
+	  *expr_p = build2 (MODIFY_EXPR, void_type_node, guard,
+			    CALL_EXPR_ARG (*expr_p, 0));
+	  *expr_p = build3 (BIND_EXPR, void_type_node, NULL, *expr_p, NULL);
+	  tree label_false = create_artificial_label (UNKNOWN_LOCATION);
+	  tree label_true = create_artificial_label (UNKNOWN_LOCATION);
+	  gcond *cond_stmt = gimple_build_cond (NE_EXPR, guard,
+						boolean_false_node,
+						label_true, label_false);
+	  gimplify_seq_add_stmt (pre_p, cond_stmt);
+	  gimplify_seq_add_stmt (pre_p, gimple_build_label (label_true));
+	  push_gimplify_context ();
+	  gimple_seq body = NULL;
+	  gimple *g = gimplify_and_return_first (*expr_p, &body);
+	  pop_gimplify_context (g);
+	  gimplify_seq_add_seq (pre_p, body);
+	  gimplify_seq_add_stmt (pre_p, gimple_build_label (label_false));
+	  call = gimple_build_call_internal (ifn, 1, guard);
+	  gimple_call_set_nothrow (call, TREE_NOTHROW (*expr_p));
+	  gimple_set_location (call, loc);
+	  gimple_seq_add_stmt (pre_p, call);
+	  *expr_p = NULL_TREE;
 	  return GS_ALL_DONE;
 	}
 
--- gcc/gimple-low.cc.jj	2022-10-10 11:57:40.163722972 +0200
+++ gcc/gimple-low.cc	2022-10-12 19:48:28.890554730 +0200
@@ -33,6 +33,13 @@  along with GCC; see the file COPYING3.
 #include "predict.h"
 #include "gimple-predict.h"
 #include "gimple-fold.h"
+#include "cgraph.h"
+#include "tree-ssa.h"
+#include "value-range.h"
+#include "stringpool.h"
+#include "tree-ssanames.h"
+#include "tree-inline.h"
+#include "gimple-walk.h"
 
 /* The differences between High GIMPLE and Low GIMPLE are the
    following:
@@ -237,6 +244,383 @@  lower_omp_directive (gimple_stmt_iterato
   gsi_next (gsi);
 }
 
+static tree
+create_assumption_fn (location_t loc)
+{
+  tree name = clone_function_name_numbered (current_function_decl, "_assume");
+  /* For now, will be changed later.  */
+  tree type = TREE_TYPE (current_function_decl);
+  tree decl = build_decl (loc, FUNCTION_DECL, name, type);
+  TREE_STATIC (decl) = 1;
+  TREE_USED (decl) = 1;
+  DECL_ARTIFICIAL (decl) = 1;
+  DECL_IGNORED_P (decl) = 1;
+  DECL_NAMELESS (decl) = 1;
+  TREE_PUBLIC (decl) = 0;
+  DECL_UNINLINABLE (decl) = 1;
+  DECL_EXTERNAL (decl) = 0;
+  DECL_CONTEXT (decl) = NULL_TREE;
+  DECL_INITIAL (decl) = make_node (BLOCK);
+  BLOCK_SUPERCONTEXT (DECL_INITIAL (decl)) = decl;
+  DECL_FUNCTION_SPECIFIC_OPTIMIZATION (decl)
+    = DECL_FUNCTION_SPECIFIC_OPTIMIZATION (current_function_decl);
+  DECL_FUNCTION_SPECIFIC_TARGET (decl)
+    = DECL_FUNCTION_SPECIFIC_TARGET (current_function_decl);
+  DECL_FUNCTION_VERSIONED (decl)
+    = DECL_FUNCTION_VERSIONED (current_function_decl);
+  tree t = build_decl (DECL_SOURCE_LOCATION (decl),
+		       RESULT_DECL, NULL_TREE, boolean_type_node);
+  DECL_ARTIFICIAL (t) = 1;
+  DECL_IGNORED_P (t) = 1;
+  DECL_CONTEXT (t) = decl;
+  DECL_RESULT (decl) = t;
+  push_struct_function (decl);
+  cfun->function_end_locus = loc;
+  init_tree_ssa (cfun);
+  return decl;
+}
+
+struct lower_assumption_data
+{
+  copy_body_data id;
+  tree return_false_label;
+  tree guard_copy;
+  auto_vec<tree> decls;
+};
+
+/* Helper function for lower_assumptions.  Find local vars and labels
+   in the assumption sequence and remove debug stmts.  */
+
+static tree
+find_assumption_locals_r (gimple_stmt_iterator *gsi_p, bool *,
+			  struct walk_stmt_info *wi)
+{
+  lower_assumption_data *data = (lower_assumption_data *) wi->info;
+  gimple *stmt = gsi_stmt (*gsi_p);
+  tree lhs = gimple_get_lhs (stmt);
+  if (lhs && TREE_CODE (lhs) == SSA_NAME)
+    {
+      gcc_assert (SSA_NAME_VAR (lhs) == NULL_TREE);
+      data->id.decl_map->put (lhs, NULL_TREE);
+      data->decls.safe_push (lhs);
+    }
+  switch (gimple_code (stmt))
+    {
+    case GIMPLE_BIND:
+      for (tree var = gimple_bind_vars (as_a <gbind *> (stmt));
+	   var; var = DECL_CHAIN (var))
+	if (VAR_P (var)
+	    && !DECL_EXTERNAL (var)
+	    && DECL_CONTEXT (var) == data->id.src_fn)
+	  {
+	    data->id.decl_map->put (var, var);
+	    data->decls.safe_push (var);
+	  }
+      break;
+    case GIMPLE_LABEL:
+      {
+	tree label = gimple_label_label (as_a <glabel *> (stmt));
+	data->id.decl_map->put (label, label);
+	break;
+      }
+    case GIMPLE_RETURN:
+      /* If something in assumption tries to return from parent function,
+	 if it would be reached in hypothetical evaluation, it would be UB,
+	 so transform such returns into return false;  */
+      {
+	gimple *g = gimple_build_assign (data->guard_copy, boolean_false_node);
+	gsi_insert_before (gsi_p, g, GSI_SAME_STMT);
+	gimple_return_set_retval (as_a <greturn *> (stmt), data->guard_copy);
+	break;
+      }
+    case GIMPLE_DEBUG:
+      /* As assumptions won't be emitted, debug info stmts in them
+	 are useless.  */
+      gsi_remove (gsi_p, true);
+      wi->removed_stmt = true;
+      break;
+    default:
+      break;
+    }
+  return NULL_TREE;
+}
+
+/* Create a new PARM_DECL that is indentical in all respect to DECL except that
+   DECL can be either a VAR_DECL, a PARM_DECL or RESULT_DECL.  The original
+   DECL must come from ID->src_fn and the copy will be part of ID->dst_fn.  */
+
+static tree
+assumption_copy_decl (tree decl, copy_body_data *id)
+{
+  tree type = TREE_TYPE (decl);
+
+  if (is_global_var (decl))
+    return decl;
+
+  gcc_assert (VAR_P (decl)
+	      || TREE_CODE (decl) == PARM_DECL
+	      || TREE_CODE (decl) == RESULT_DECL);
+  tree copy = build_decl (DECL_SOURCE_LOCATION (decl),
+			  PARM_DECL, DECL_NAME (decl), type);
+  if (DECL_PT_UID_SET_P (decl))
+    SET_DECL_PT_UID (copy, DECL_PT_UID (decl));
+  TREE_ADDRESSABLE (copy) = TREE_ADDRESSABLE (decl);
+  TREE_READONLY (copy) = TREE_READONLY (decl);
+  TREE_THIS_VOLATILE (copy) = TREE_THIS_VOLATILE (decl);
+  DECL_NOT_GIMPLE_REG_P (copy) = DECL_NOT_GIMPLE_REG_P (decl);
+  DECL_BY_REFERENCE (copy) = DECL_BY_REFERENCE (decl);
+  DECL_ARG_TYPE (copy) = type;
+  ((lower_assumption_data *) id)->decls.safe_push (decl);
+  return copy_decl_for_dup_finish (id, decl, copy);
+}
+
+/* Transform gotos out of the assumption into return false.  */
+
+static tree
+adjust_assumption_stmt_r (gimple_stmt_iterator *gsi_p, bool *,
+			  struct walk_stmt_info *wi)
+{
+  lower_assumption_data *data = (lower_assumption_data *) wi->info;
+  gimple *stmt = gsi_stmt (*gsi_p);
+  tree lab = NULL_TREE;
+  unsigned int idx = 0;
+  if (gimple_code (stmt) == GIMPLE_GOTO)
+    lab = gimple_goto_dest (stmt);
+  else if (gimple_code (stmt) == GIMPLE_COND)
+    {
+     repeat:
+      if (idx == 0)
+	lab = gimple_cond_true_label (as_a <gcond *> (stmt));
+      else
+	lab = gimple_cond_false_label (as_a <gcond *> (stmt));
+    }
+  else if (gimple_code (stmt) == GIMPLE_LABEL)
+    {
+      tree label = gimple_label_label (as_a <glabel *> (stmt));
+      DECL_CONTEXT (label) = current_function_decl;
+    }
+  if (lab)
+    {
+      if (!data->id.decl_map->get (lab))
+	{
+	  if (!data->return_false_label)
+	    data->return_false_label
+	      = create_artificial_label (UNKNOWN_LOCATION);
+	  if (gimple_code (stmt) == GIMPLE_GOTO)
+	    gimple_goto_set_dest (as_a <ggoto *> (stmt),
+				  data->return_false_label);
+	  else if (idx == 0)
+	    gimple_cond_set_true_label (as_a <gcond *> (stmt),
+					data->return_false_label);
+	  else
+	    gimple_cond_set_false_label (as_a <gcond *> (stmt),
+					 data->return_false_label);
+	}
+      if (gimple_code (stmt) == GIMPLE_COND && idx == 0)
+	{
+	  idx = 1;
+	  goto repeat;
+	}
+    }
+  return NULL_TREE;
+}
+
+/* Adjust trees in the assumption body.  Called through walk_tree.  */
+
+static tree
+adjust_assumption_stmt_op (tree *tp, int *, void *datap)
+{
+  struct walk_stmt_info *wi = (struct walk_stmt_info *) datap;
+  lower_assumption_data *data = (lower_assumption_data *) wi->info;
+  tree t = *tp;
+  tree *newt;
+  switch (TREE_CODE (t))
+    {
+    case SSA_NAME:
+      newt = data->id.decl_map->get (t);
+      /* There shouldn't be SSA_NAMEs other than ones defined in the
+	 assumption's body.  */
+      gcc_assert (newt);
+      *tp = *newt;
+      break;
+    case LABEL_DECL:
+      newt = data->id.decl_map->get (t);
+      if (newt)
+	*tp = *newt;
+      break;
+    case VAR_DECL:
+    case PARM_DECL:
+    case RESULT_DECL:
+      *tp = remap_decl (t, &data->id);
+      break;
+    default:
+      break;
+    }
+  return NULL_TREE;
+}
+
+/* Lower assumption.
+   The gimplifier transformed:
+   .ASSUME (cond);
+   into:
+   guard = .ASSUME ();
+   if (guard) goto label_true; else label_false;
+   label_true:;
+   {
+     guard = cond;
+   }
+   label_false:;
+   .ASSUME (guard);
+   which we should transform into:
+   .ASSUME (&artificial_fn, args...);
+   where artificial_fn will look like:
+   bool artificial_fn (args...)
+   {
+     guard = cond;
+     return guard;
+   }
+   with any debug stmts in the block removed and jumps out of
+   the block or return stmts replaced with return false;  */
+
+static void
+lower_assumption (gimple_stmt_iterator *gsi, struct lower_data *data)
+{
+  gimple *stmt = gsi_stmt (*gsi);
+  tree guard = gimple_call_lhs (stmt);
+  location_t loc = gimple_location (stmt);
+  gcc_assert (guard);
+  gsi_remove (gsi, true);
+  stmt = gsi_stmt (*gsi);
+  gcond *cond = as_a <gcond *> (stmt);
+  gcc_assert (gimple_cond_lhs (cond) == guard
+	      || gimple_cond_rhs (cond) == guard);
+  tree l1 = gimple_cond_true_label (cond);
+  tree l2 = gimple_cond_false_label (cond);
+  gsi_remove (gsi, true);
+  stmt = gsi_stmt (*gsi);
+  glabel *lab = as_a <glabel *> (stmt);
+  gcc_assert (gimple_label_label (lab) == l1
+	      || gimple_label_label (lab) == l2);
+  gsi_remove (gsi, true);
+  gimple *bind = gsi_stmt (*gsi);
+  gcc_assert (gimple_code (bind) == GIMPLE_BIND);
+
+  lower_assumption_data lad;
+  hash_map<tree, tree> decl_map;
+  memset (&lad.id, 0, sizeof (lad.id));
+  lad.return_false_label = NULL_TREE;
+  lad.id.src_fn = current_function_decl;
+  lad.id.dst_fn = create_assumption_fn (loc);
+  lad.id.src_cfun = DECL_STRUCT_FUNCTION (lad.id.src_fn);
+  lad.id.decl_map = &decl_map;
+  lad.id.copy_decl = assumption_copy_decl;
+  lad.id.transform_call_graph_edges = CB_CGE_DUPLICATE;
+  lad.id.transform_parameter = true;
+  lad.id.do_not_unshare = true;
+  lad.id.do_not_fold = true;
+  cfun->curr_properties = lad.id.src_cfun->curr_properties;
+  lad.guard_copy = create_tmp_var (boolean_type_node);
+  decl_map.put (lad.guard_copy, lad.guard_copy);
+  decl_map.put (guard, lad.guard_copy);
+  cfun->assume_function = 1;
+
+  struct walk_stmt_info wi;
+  memset (&wi, 0, sizeof (wi));
+  wi.info = (void *) &lad;
+  walk_gimple_stmt (gsi, find_assumption_locals_r, NULL, &wi);
+  unsigned int sz = lad.decls.length ();
+  for (unsigned i = 0; i < sz; ++i)
+    {
+      tree v = lad.decls[i];
+      tree newv;
+      if (TREE_CODE (v) == SSA_NAME)
+	{
+	  newv = make_ssa_name (remap_type (TREE_TYPE (v), &lad.id));
+	  decl_map.put (v, newv);
+	}
+      else if (VAR_P (v))
+	{
+	  if (is_global_var (v) && !DECL_ASSEMBLER_NAME_SET_P (v))
+	    DECL_ASSEMBLER_NAME (v);
+	  TREE_TYPE (v) = remap_type (TREE_TYPE (v), &lad.id);
+	  DECL_CONTEXT (v) = current_function_decl;
+	}
+    }
+  memset (&wi, 0, sizeof (wi));
+  wi.info = (void *) &lad;
+  walk_gimple_stmt (gsi, adjust_assumption_stmt_r,
+		    adjust_assumption_stmt_op, &wi);
+  gsi_remove (gsi, false);
+
+  gimple_seq body = NULL;
+  gimple *g = gimple_build_assign (lad.guard_copy, boolean_false_node);
+  gimple_seq_add_stmt (&body, g);
+  gimple_seq_add_stmt (&body, bind);
+  greturn *gr = gimple_build_return (lad.guard_copy);
+  gimple_seq_add_stmt (&body, gr);
+  if (lad.return_false_label)
+    {
+      g = gimple_build_label (lad.return_false_label);
+      gimple_seq_add_stmt (&body, g);
+      g = gimple_build_assign (lad.guard_copy, boolean_false_node);
+      gimple_seq_add_stmt (&body, g);
+      gr = gimple_build_return (lad.guard_copy);
+      gimple_seq_add_stmt (&body, gr);
+    }
+  bind = gimple_build_bind (NULL_TREE, body, NULL_TREE);
+  body = NULL;
+  gimple_seq_add_stmt (&body, bind);
+  gimple_set_body (current_function_decl, body);
+  pop_cfun ();
+
+  tree parms = NULL_TREE;
+  tree parmt = void_list_node;
+  auto_vec<tree, 8> vargs;
+  vargs.safe_grow (1 + (lad.decls.length () - sz), true);
+  vargs[0] = build_fold_addr_expr (lad.id.dst_fn);
+  for (unsigned i = lad.decls.length (); i > sz; --i)
+    {
+      tree *v = decl_map.get (lad.decls[i - 1]);
+      gcc_assert (v && TREE_CODE (*v) == PARM_DECL);
+      DECL_CHAIN (*v) = parms;
+      parms = *v;
+      parmt = tree_cons (NULL_TREE, TREE_TYPE (*v), parmt);
+      vargs[i - sz] = lad.decls[i - 1];
+      if (is_gimple_reg_type (TREE_TYPE (vargs[i - sz]))
+	  && !is_gimple_val (vargs[i - sz]))
+	{
+	  tree t = make_ssa_name (TREE_TYPE (vargs[i - sz]));
+	  g = gimple_build_assign (t, vargs[i - sz]);
+	  gsi_insert_before (gsi, g, GSI_SAME_STMT);
+	  vargs[i - sz] = t;
+	}
+    }
+  DECL_ARGUMENTS (lad.id.dst_fn) = parms;
+  TREE_TYPE (lad.id.dst_fn) = build_function_type (boolean_type_node, parmt);
+
+  cgraph_node::add_new_function (lad.id.dst_fn, false);
+
+  for (unsigned i = 0; i < sz; ++i)
+    {
+      tree v = lad.decls[i];
+      if (TREE_CODE (v) == SSA_NAME)
+	release_ssa_name (v);
+    }
+
+  stmt = gsi_stmt (*gsi);
+  lab = as_a <glabel *> (stmt);
+  gcc_assert (gimple_label_label (lab) == l1
+	      || gimple_label_label (lab) == l2);
+  gsi_remove (gsi, true);
+  stmt = gsi_stmt (*gsi);
+  gcc_assert (gimple_call_internal_p (stmt, IFN_ASSUME)
+	      && gimple_call_num_args (stmt) == 1
+	      && gimple_call_arg (stmt, 0) == guard);
+  data->cannot_fallthru = false;
+  gcall *call = gimple_build_call_internal_vec (IFN_ASSUME, vargs);
+  gimple_set_location (call, loc);
+  gsi_replace (gsi, call, true);
+}
 
 /* Lower statement GSI.  DATA is passed through the recursion.  We try to
    track the fallthruness of statements and get rid of unreachable return
@@ -354,6 +738,13 @@  lower_stmt (gimple_stmt_iterator *gsi, s
 	tree decl = gimple_call_fndecl (stmt);
 	unsigned i;
 
+	if (gimple_call_internal_p (stmt, IFN_ASSUME)
+	    && gimple_call_num_args (stmt) == 0)
+	  {
+	    lower_assumption (gsi, data);
+	    return;
+	  }
+
 	for (i = 0; i < gimple_call_num_args (stmt); i++)
 	  {
 	    tree arg = gimple_call_arg (stmt, i);
--- gcc/tree-ssa-ccp.cc.jj	2022-10-10 11:57:40.203722414 +0200
+++ gcc/tree-ssa-ccp.cc	2022-10-12 19:48:28.891554716 +0200
@@ -4253,6 +4253,12 @@  pass_fold_builtins::execute (function *f
 	    }
 
 	  callee = gimple_call_fndecl (stmt);
+	  if (!callee
+	      && gimple_call_internal_p (stmt, IFN_ASSUME))
+	    {
+	      gsi_remove (&i, true);
+	      continue;
+	    }
 	  if (!callee || !fndecl_built_in_p (callee, BUILT_IN_NORMAL))
 	    {
 	      gsi_next (&i);
--- gcc/lto-streamer-out.cc.jj	2022-10-10 11:57:40.202722428 +0200
+++ gcc/lto-streamer-out.cc	2022-10-12 19:48:28.891554716 +0200
@@ -2278,6 +2278,7 @@  output_struct_function_base (struct outp
   bp_pack_value (&bp, fn->calls_eh_return, 1);
   bp_pack_value (&bp, fn->has_force_vectorize_loops, 1);
   bp_pack_value (&bp, fn->has_simduid_loops, 1);
+  bp_pack_value (&bp, fn->assume_function, 1);
   bp_pack_value (&bp, fn->va_list_fpr_size, 8);
   bp_pack_value (&bp, fn->va_list_gpr_size, 8);
   bp_pack_value (&bp, fn->last_clique, sizeof (short) * 8);
--- gcc/lto-streamer-in.cc.jj	2022-10-10 11:57:40.201722442 +0200
+++ gcc/lto-streamer-in.cc	2022-10-12 19:48:28.891554716 +0200
@@ -1318,6 +1318,7 @@  input_struct_function_base (struct funct
   fn->calls_eh_return = bp_unpack_value (&bp, 1);
   fn->has_force_vectorize_loops = bp_unpack_value (&bp, 1);
   fn->has_simduid_loops = bp_unpack_value (&bp, 1);
+  fn->assume_function = bp_unpack_value (&bp, 1);
   fn->va_list_fpr_size = bp_unpack_value (&bp, 8);
   fn->va_list_gpr_size = bp_unpack_value (&bp, 8);
   fn->last_clique = bp_unpack_value (&bp, sizeof (short) * 8);
--- gcc/cgraphunit.cc.jj	2022-10-10 11:57:40.152723125 +0200
+++ gcc/cgraphunit.cc	2022-10-12 19:48:28.892554703 +0200
@@ -1882,6 +1882,16 @@  cgraph_node::expand (void)
   ggc_collect ();
   timevar_pop (TV_REST_OF_COMPILATION);
 
+  if (DECL_STRUCT_FUNCTION (decl)
+      && DECL_STRUCT_FUNCTION (decl)->assume_function)
+    {
+      /* Assume functions aren't expanded into RTL, on the other side
+	 we don't want to release their body.  */
+      if (cfun)
+	pop_cfun ();
+      return;
+    }
+
   /* Make sure that BE didn't give up on compiling.  */
   gcc_assert (TREE_ASM_WRITTEN (decl));
   if (cfun)
--- gcc/cfgexpand.cc.jj	2022-10-10 11:57:40.152723125 +0200
+++ gcc/cfgexpand.cc	2022-10-12 19:48:28.893554689 +0200
@@ -6597,6 +6597,14 @@  pass_expand::execute (function *fun)
   rtx_insn *var_seq, *var_ret_seq;
   unsigned i;
 
+  if (cfun->assume_function)
+    {
+      /* Assume functions should not be expanded to RTL.  */
+      cfun->curr_properties &= ~PROP_loops;
+      loop_optimizer_finalize ();
+      return 0;
+    }
+
   timevar_push (TV_OUT_OF_SSA);
   rewrite_out_of_ssa (&SA);
   timevar_pop (TV_OUT_OF_SSA);
--- gcc/internal-fn.cc.jj	2022-10-10 11:57:40.166722930 +0200
+++ gcc/internal-fn.cc	2022-10-12 19:48:28.893554689 +0200
@@ -4526,5 +4526,4 @@  expand_TRAP (internal_fn, gcall *)
 void
 expand_ASSUME (internal_fn, gcall *)
 {
-  gcc_unreachable ();
 }
--- gcc/passes.cc.jj	2022-10-10 11:57:40.202722428 +0200
+++ gcc/passes.cc	2022-10-12 19:48:28.893554689 +0200
@@ -647,11 +647,12 @@  public:
   {}
 
   /* opt_pass methods: */
-  bool gate (function *) final override
+  bool gate (function *fun) final override
     {
       /* Early return if there were errors.  We can run afoul of our
 	 consistency checks, and there's not really much point in fixing them.  */
-      return !(rtl_dump_and_exit || flag_syntax_only || seen_error ());
+      return !(rtl_dump_and_exit || fun->assume_function
+	       || flag_syntax_only || seen_error ());
     }
 
 }; // class pass_rest_of_compilation
--- gcc/tree-vectorizer.cc.jj	2022-10-10 11:57:40.204722400 +0200
+++ gcc/tree-vectorizer.cc	2022-10-12 19:48:28.894554675 +0200
@@ -1213,6 +1213,10 @@  public:
   /* opt_pass methods: */
   bool gate (function *fun) final override
     {
+      /* Vectorization makes range analysis of assume functions
+	 harder, not easier.  */
+      if (fun->assume_function)
+	return false;
       return flag_tree_loop_vectorize || fun->has_force_vectorize_loops;
     }
 
@@ -1490,7 +1494,14 @@  public:
 
   /* opt_pass methods: */
   opt_pass * clone () final override { return new pass_slp_vectorize (m_ctxt); }
-  bool gate (function *) final override { return flag_tree_slp_vectorize != 0; }
+  bool gate (function *fun) final override
+  {
+    /* Vectorization makes range analysis of assume functions harder,
+       not easier.  */
+    if (fun->assume_function)
+      return false;
+    return flag_tree_slp_vectorize != 0;
+  }
   unsigned int execute (function *) final override;
 
 }; // class pass_slp_vectorize
--- gcc/ipa-icf.cc.jj	2022-10-10 11:57:40.201722442 +0200
+++ gcc/ipa-icf.cc	2022-10-12 19:48:28.894554675 +0200
@@ -1517,6 +1517,9 @@  sem_function::parse (cgraph_node *node,
   if (!func || (!node->has_gimple_body_p () && !node->thunk))
     return NULL;
 
+  if (func->assume_function)
+    return NULL;
+
   if (lookup_attribute_by_prefix ("omp ", DECL_ATTRIBUTES (node->decl)) != NULL)
     return NULL;
 
--- gcc/cp/cp-tree.h.jj	2022-10-12 17:51:00.911944744 +0200
+++ gcc/cp/cp-tree.h	2022-10-12 19:53:17.072615254 +0200
@@ -8284,6 +8284,7 @@  extern tree predeclare_vla			(tree);
 extern void clear_fold_cache			(void);
 extern tree lookup_hotness_attribute		(tree);
 extern tree process_stmt_hotness_attribute	(tree, location_t);
+extern tree build_assume_call			(location_t, tree);
 extern tree process_stmt_assume_attribute	(tree, tree, location_t);
 extern bool simple_empty_class_p		(tree, tree, tree_code);
 extern tree fold_builtin_source_location	(location_t);
--- gcc/cp/parser.cc.jj	2022-10-12 17:51:00.951944199 +0200
+++ gcc/cp/parser.cc	2022-10-12 19:52:15.855452024 +0200
@@ -46006,11 +46006,7 @@  cp_parser_omp_assumption_clauses (cp_par
 	      if (!type_dependent_expression_p (t))
 		t = contextual_conv_bool (t, tf_warning_or_error);
 	      if (is_assume && !error_operand_p (t))
-		{
-		  t = build_call_expr_internal_loc (eloc, IFN_ASSUME,
-						    void_type_node, 1, t);
-		  finish_expr_stmt (t);
-		}
+		finish_expr_stmt (build_assume_call (eloc, t));
 	      if (!parens.require_close (parser))
 		cp_parser_skip_to_closing_parenthesis (parser,
 						       /*recovering=*/true,
--- gcc/cp/cp-gimplify.cc.jj	2022-10-12 17:51:00.909944772 +0200
+++ gcc/cp/cp-gimplify.cc	2022-10-12 19:51:47.140844525 +0200
@@ -3096,6 +3096,17 @@  process_stmt_hotness_attribute (tree std
   return std_attrs;
 }
 
+/* Build IFN_ASSUME internal call for assume condition ARG.  */
+
+tree
+build_assume_call (location_t loc, tree arg)
+{
+  if (!processing_template_decl)
+    arg = fold_build_cleanup_point_expr (TREE_TYPE (arg), arg);
+  return build_call_expr_internal_loc (loc, IFN_ASSUME, void_type_node,
+				       1, arg);
+}
+
 /* If [[assume (cond)]] appears on this statement, handle it.  */
 
 tree
@@ -3132,9 +3143,7 @@  process_stmt_assume_attribute (tree std_
 	    arg = contextual_conv_bool (arg, tf_warning_or_error);
 	  if (error_operand_p (arg))
 	    continue;
-	  statement = build_call_expr_internal_loc (attrs_loc, IFN_ASSUME,
-						    void_type_node, 1, arg);
-	  finish_expr_stmt (statement);
+	  finish_expr_stmt (build_assume_call (attrs_loc, arg));
 	}
     }
   return remove_attribute ("gnu", "assume", std_attrs);
--- gcc/cp/pt.cc.jj	2022-10-12 17:51:00.957944117 +0200
+++ gcc/cp/pt.cc	2022-10-12 19:52:45.204050862 +0200
@@ -21116,10 +21116,7 @@  tsubst_copy_and_build (tree t,
 		      ret = error_mark_node;
 		      break;
 		    }
-		  ret = build_call_expr_internal_loc (EXPR_LOCATION (t),
-						      IFN_ASSUME,
-						      void_type_node, 1,
-						      arg);
+		  ret = build_assume_call (EXPR_LOCATION (t), arg);
 		  RETURN (ret);
 		}
 	      break;
--- gcc/testsuite/g++.dg/cpp23/attr-assume5.C.jj	2022-10-12 19:48:28.903554553 +0200
+++ gcc/testsuite/g++.dg/cpp23/attr-assume5.C	2022-10-12 19:48:28.903554553 +0200
@@ -0,0 +1,5 @@ 
+// P1774R8 - Portable assumptions
+// { dg-do run { target c++11 } }
+// { dg-options "-O2" }
+
+#include "attr-assume1.C"
--- gcc/testsuite/g++.dg/cpp23/attr-assume6.C.jj	2022-10-12 19:48:28.903554553 +0200
+++ gcc/testsuite/g++.dg/cpp23/attr-assume6.C	2022-10-12 19:48:28.903554553 +0200
@@ -0,0 +1,5 @@ 
+// P1774R8 - Portable assumptions
+// { dg-do run { target c++11 } }
+// { dg-options "-O2" }
+
+#include "attr-assume3.C"
--- gcc/testsuite/g++.dg/cpp23/attr-assume7.C.jj	2022-10-12 19:48:28.903554553 +0200
+++ gcc/testsuite/g++.dg/cpp23/attr-assume7.C	2022-10-12 19:48:28.903554553 +0200
@@ -0,0 +1,42 @@ 
+// P1774R8 - Portable assumptions
+// { dg-do compile { target c++11 } }
+// { dg-options "-O2" }
+
+int
+foo (int x)
+{
+  [[assume (x == 42)]];
+  return x;
+}
+
+int
+bar (int x)
+{
+  [[assume (++x == 43)]];
+  return x;
+}
+
+int
+baz (int x)
+{
+  [[assume (({ int z = ++x; static int w; ++w; if (z == 51) return -1; if (z == 53) goto lab1; if (z == 64) throw 1; z == 43; }))]];
+lab1:
+  return x;
+}
+
+struct S { S (); S (const S &); ~S (); int a, b; int foo (); };
+
+int
+qux ()
+{
+  S s;
+  [[assume (s.a == 42 && s.b == 43)]];
+  return s.a + s.b;
+}
+
+int
+S::foo ()
+{
+  [[assume (a == 42 && b == 43)]];
+  return a + b;
+}