middle-end, v2: IFN_ASSUME support [PR106654]
Commit Message
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
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
>
@@ -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. */
@@ -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;
}
@@ -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);
@@ -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);
@@ -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);
@@ -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);
@@ -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)
@@ -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);
@@ -4526,5 +4526,4 @@ expand_TRAP (internal_fn, gcall *)
void
expand_ASSUME (internal_fn, gcall *)
{
- gcc_unreachable ();
}
@@ -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
@@ -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
@@ -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;
@@ -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);
@@ -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);
@@ -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,
@@ -0,0 +1,5 @@
+// P1774R8 - Portable assumptions
+// { dg-do run { target c++11 } }
+// { dg-options "-O2" }
+
+#include "attr-assume1.C"
@@ -0,0 +1,5 @@
+// P1774R8 - Portable assumptions
+// { dg-do run { target c++11 } }
+// { dg-options "-O2" }
+
+#include "attr-assume3.C"
@@ -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;
+}