middle-end, v2: IFN_ASSUME support [PR106654]

Message ID Y0VxcOxwjGbN6rKl@tucnak
State New, archived
Headers
Series middle-end, v2: IFN_ASSUME support [PR106654] |

Commit Message

Jakub Jelinek Oct. 11, 2022, 1:36 p.m. UTC
  On Mon, Oct 10, 2022 at 11:19:24PM +0200, Jakub Jelinek via Gcc-patches wrote:
> On Mon, Oct 10, 2022 at 05:09:29PM -0400, Jason Merrill wrote:
> > On 10/10/22 04:54, Jakub Jelinek via Gcc-patches wrote:
> > > My earlier patches gimplify the simplest non-side-effects assumptions
> > > into if (cond) ; else __builtin_unreachable (); and throw the rest
> > > on the floor.
> > > The following patch attempts to do something with the rest too.
> > > For -O0, it actually throws even the simplest assumptions on the floor,
> > > we don't expect optimizations and the assumptions are there to allow
> > > optimizations.
> > 
> > I'd think we should trap on failed assume at -O0 (i.e. with
> > -funreachable-traps).
> 
> For the simple conditions?  Perhaps.  But for the side-effects cases
> that doesn't seem to be easily possible.

Here is an updated patch which will trap on failed simple assume.

Bootstrapped/regtested successfully on x86_64-linux and i686-linux, the only
change was moving the !optimize handling from before the
if (cond); else __builtin_unreachable ();
gimplification to right after it.

2022-10-11  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/
	* parser.cc (cp_parser_omp_assumption_clauses): Wrap IFN_ASSUME
	argument with fold_build_cleanup_point_expr.
	* cp-gimplify.cc (process_stmt_assume_attribute): Likewise.
	* 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

Jason Merrill Oct. 12, 2022, 3:48 p.m. UTC | #1
On 10/11/22 09:36, Jakub Jelinek wrote:
> On Mon, Oct 10, 2022 at 11:19:24PM +0200, Jakub Jelinek via Gcc-patches wrote:
>> On Mon, Oct 10, 2022 at 05:09:29PM -0400, Jason Merrill wrote:
>>> On 10/10/22 04:54, Jakub Jelinek via Gcc-patches wrote:
>>>> My earlier patches gimplify the simplest non-side-effects assumptions
>>>> into if (cond) ; else __builtin_unreachable (); and throw the rest
>>>> on the floor.
>>>> The following patch attempts to do something with the rest too.
>>>> For -O0, it actually throws even the simplest assumptions on the floor,
>>>> we don't expect optimizations and the assumptions are there to allow
>>>> optimizations.
>>>
>>> I'd think we should trap on failed assume at -O0 (i.e. with
>>> -funreachable-traps).
>>
>> For the simple conditions?  Perhaps.  But for the side-effects cases
>> that doesn't seem to be easily possible.
> 
> Here is an updated patch which will trap on failed simple assume.
> 
> Bootstrapped/regtested successfully on x86_64-linux and i686-linux, the only
> change was moving the !optimize handling from before the
> if (cond); else __builtin_unreachable ();
> gimplification to right after it.
> 
> 2022-10-11  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/
> 	* parser.cc (cp_parser_omp_assumption_clauses): Wrap IFN_ASSUME
> 	argument with fold_build_cleanup_point_expr.
> 	* cp-gimplify.cc (process_stmt_assume_attribute): Likewise.
> 	* 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 09:31:22.051478926 +0200
> +++ gcc/function.h	2022-10-10 09:59:49.283646705 +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 09:31:57.518983613 +0200
> +++ gcc/gimplify.cc	2022-10-10 09:59:49.285646677 +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 09:31:22.107478144 +0200
> +++ gcc/gimple-low.cc	2022-10-10 10:22:05.891999132 +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 09:31:22.472473047 +0200
> +++ gcc/tree-ssa-ccp.cc	2022-10-10 09:59:49.286646663 +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 09:31:22.331475016 +0200
> +++ gcc/lto-streamer-out.cc	2022-10-10 09:59:49.287646649 +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 09:31:22.329475044 +0200
> +++ gcc/lto-streamer-in.cc	2022-10-10 09:59:49.287646649 +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 09:31:21.647484568 +0200
> +++ gcc/cgraphunit.cc	2022-10-10 09:59:49.288646635 +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 09:31:21.554485867 +0200
> +++ gcc/cfgexpand.cc	2022-10-10 09:59:49.288646635 +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 09:31:22.246476203 +0200
> +++ gcc/internal-fn.cc	2022-10-10 09:59:49.289646621 +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 09:31:22.379474346 +0200
> +++ gcc/passes.cc	2022-10-10 09:59:49.289646621 +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 09:31:22.516472432 +0200
> +++ gcc/tree-vectorizer.cc	2022-10-10 09:59:49.290646607 +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-06-28 13:03:30.834690968 +0200
> +++ gcc/ipa-icf.cc	2022-10-10 10:29:31.187766299 +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/parser.cc.jj	2022-10-10 09:31:57.405985191 +0200
> +++ gcc/cp/parser.cc	2022-10-10 09:59:49.295646537 +0200
> @@ -46031,6 +46031,8 @@ cp_parser_omp_assumption_clauses (cp_par
>   		t = contextual_conv_bool (t, tf_warning_or_error);
>   	      if (is_assume && !error_operand_p (t))
>   		{
> +		  if (!processing_template_decl)
> +		    t = fold_build_cleanup_point_expr (TREE_TYPE (t), t);
>   		  t = build_call_expr_internal_loc (eloc, IFN_ASSUME,
>   						    void_type_node, 1, t);
>   		  finish_expr_stmt (t);
> --- gcc/cp/cp-gimplify.cc.jj	2022-10-10 09:31:57.309986531 +0200
> +++ gcc/cp/cp-gimplify.cc	2022-10-10 09:59:49.296646524 +0200
> @@ -3139,6 +3139,8 @@ process_stmt_assume_attribute (tree std_
>   	    arg = contextual_conv_bool (arg, tf_warning_or_error);
>   	  if (error_operand_p (arg))
>   	    continue;
> +	  if (!processing_template_decl)
> +	    arg = fold_build_cleanup_point_expr (TREE_TYPE (arg), arg);
>   	  statement = build_call_expr_internal_loc (attrs_loc, IFN_ASSUME,
>   						    void_type_node, 1, arg);
>   	  finish_expr_stmt (statement);
> --- 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.

I'll leave the middle-end review to others.

> --- gcc/testsuite/g++.dg/cpp23/attr-assume5.C.jj	2022-10-10 09:59:49.299646482 +0200
> +++ gcc/testsuite/g++.dg/cpp23/attr-assume5.C	2022-10-10 09:59:49.299646482 +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-10 09:59:49.300646468 +0200
> +++ gcc/testsuite/g++.dg/cpp23/attr-assume6.C	2022-10-10 09:59:49.300646468 +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-10 09:59:49.300646468 +0200
> +++ gcc/testsuite/g++.dg/cpp23/attr-assume7.C	2022-10-10 10:05:51.472593600 +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;
> +}
> 
> 
> 	Jakub
>
  

Patch

--- gcc/function.h.jj	2022-10-10 09:31:22.051478926 +0200
+++ gcc/function.h	2022-10-10 09:59:49.283646705 +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 09:31:57.518983613 +0200
+++ gcc/gimplify.cc	2022-10-10 09:59:49.285646677 +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 09:31:22.107478144 +0200
+++ gcc/gimple-low.cc	2022-10-10 10:22:05.891999132 +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 09:31:22.472473047 +0200
+++ gcc/tree-ssa-ccp.cc	2022-10-10 09:59:49.286646663 +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 09:31:22.331475016 +0200
+++ gcc/lto-streamer-out.cc	2022-10-10 09:59:49.287646649 +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 09:31:22.329475044 +0200
+++ gcc/lto-streamer-in.cc	2022-10-10 09:59:49.287646649 +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 09:31:21.647484568 +0200
+++ gcc/cgraphunit.cc	2022-10-10 09:59:49.288646635 +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 09:31:21.554485867 +0200
+++ gcc/cfgexpand.cc	2022-10-10 09:59:49.288646635 +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 09:31:22.246476203 +0200
+++ gcc/internal-fn.cc	2022-10-10 09:59:49.289646621 +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 09:31:22.379474346 +0200
+++ gcc/passes.cc	2022-10-10 09:59:49.289646621 +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 09:31:22.516472432 +0200
+++ gcc/tree-vectorizer.cc	2022-10-10 09:59:49.290646607 +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-06-28 13:03:30.834690968 +0200
+++ gcc/ipa-icf.cc	2022-10-10 10:29:31.187766299 +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/parser.cc.jj	2022-10-10 09:31:57.405985191 +0200
+++ gcc/cp/parser.cc	2022-10-10 09:59:49.295646537 +0200
@@ -46031,6 +46031,8 @@  cp_parser_omp_assumption_clauses (cp_par
 		t = contextual_conv_bool (t, tf_warning_or_error);
 	      if (is_assume && !error_operand_p (t))
 		{
+		  if (!processing_template_decl)
+		    t = fold_build_cleanup_point_expr (TREE_TYPE (t), t);
 		  t = build_call_expr_internal_loc (eloc, IFN_ASSUME,
 						    void_type_node, 1, t);
 		  finish_expr_stmt (t);
--- gcc/cp/cp-gimplify.cc.jj	2022-10-10 09:31:57.309986531 +0200
+++ gcc/cp/cp-gimplify.cc	2022-10-10 09:59:49.296646524 +0200
@@ -3139,6 +3139,8 @@  process_stmt_assume_attribute (tree std_
 	    arg = contextual_conv_bool (arg, tf_warning_or_error);
 	  if (error_operand_p (arg))
 	    continue;
+	  if (!processing_template_decl)
+	    arg = fold_build_cleanup_point_expr (TREE_TYPE (arg), arg);
 	  statement = build_call_expr_internal_loc (attrs_loc, IFN_ASSUME,
 						    void_type_node, 1, arg);
 	  finish_expr_stmt (statement);
--- 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,
--- gcc/testsuite/g++.dg/cpp23/attr-assume5.C.jj	2022-10-10 09:59:49.299646482 +0200
+++ gcc/testsuite/g++.dg/cpp23/attr-assume5.C	2022-10-10 09:59:49.299646482 +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-10 09:59:49.300646468 +0200
+++ gcc/testsuite/g++.dg/cpp23/attr-assume6.C	2022-10-10 09:59:49.300646468 +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-10 09:59:49.300646468 +0200
+++ gcc/testsuite/g++.dg/cpp23/attr-assume7.C	2022-10-10 10:05:51.472593600 +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;
+}