* [PATCH] middle-end IFN_ASSUME support [PR106654] @ 2022-10-10 8:54 Jakub Jelinek 2022-10-10 21:09 ` Jason Merrill ` (2 more replies) 0 siblings, 3 replies; 35+ messages in thread From: Jakub Jelinek @ 2022-10-10 8:54 UTC (permalink / raw) To: Richard Biener, Jan Hubicka, Aldy Hernandez; +Cc: gcc-patches Hi! 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. Otherwise, it keeps the handling of the simplest assumptions as is, and otherwise arranges for the assumptions to be visible in the IL as .ASSUME (_Z2f4i._assume.0, i_1(D)); call where there is an artificial function like: bool _Z2f4i._assume.0 (int i) { bool _2; <bb 2> [local count: 1073741824]: _2 = i_1(D) == 43; return _2; } with the semantics that there is UB unless the assumption function would return true. Aldy, could ranger handle this? If it sees .ASSUME call, walk the body of such function from the edge(s) to exit with the assumption that the function returns true, so above set _2 [true, true] and from there derive that i_1(D) [43, 43] and then map the argument in the assumption function to argument passed to IFN_ASSUME (note, args there are shifted by 1)? During gimplification it actually gimplifies it into D.2591 = .ASSUME (); if (D.2591 != 0) goto <D.2593>; else goto <D.2592>; <D.2593>: { i = i + 1; D.2591 = i == 44; } <D.2592>: .ASSUME (D.2591); with the condition wrapped into a GIMPLE_BIND (I admit the above isn't extra clean but it is just something to hold it from gimplifier until gimple low pass; it reassembles if (condition_never_true) { cond; }; an alternative would be introduce GOMP_ASSUME statement that would have the guard var as operand and the GIMPLE_BIND as body, but for the few passes (tree-nested and omp lowering) in between that looked like an overkill to me) which is then pattern matched during gimple lowering and outlined into a separate function. Variables declared inside of the condition (both static and automatic) just change context, automatic variables from the caller are turned into parameters (note, as the code is never executed, I handle this way even non-POD types, we don't need to bother pretending there would be user copy constructors etc. involved). The assume_function artificial functions are then optimized until RTL expansion, which isn't done for them nor any later pass, on the other side bodies are not freed when done. Earlier version of the patch has been successfully bootstrapped/regtested on x86_64-linux and i686-linux and all assume tests have passed even with this version. Ok for trunk if it succeeds on another bootstrap/regtest? There are a few further changes I'd like to do, like ignoring the .ASSUME calls in inlining size estimations (but haven't figured out where it is done), or for LTO arrange for the assume functions to be emitted in all partitions that reference those (usually there will be just one, unless code with the assumption got inlined, versioned etc.). 2022-10-10 Jakub Jelinek <jakub@redhat.com> PR c++/106654 gcc/ * function.h (struct function): Add assume_function bitfield. * gimplify.cc (gimplify_call_expr): For -O0, always throw away assumptions on the floor immediately. Otherwise if the assumption isn't simple enough, expand it into IFN_ASSUME guarded block. * 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 @@ -3556,6 +3556,12 @@ gimplify_call_expr (tree *expr_p, gimple if (ifn == IFN_ASSUME) { + /* If not optimizing, ignore the assumptions. */ + if (!optimize) + { + *expr_p = NULL_TREE; + return GS_ALL_DONE; + } if (simple_condition_p (CALL_EXPR_ARG (*expr_p, 0))) { /* If the [[assume (cond)]]; condition is simple @@ -3569,7 +3575,46 @@ gimplify_call_expr (tree *expr_p, gimple fndecl, 0)); return GS_OK; } - /* FIXME: Otherwise expand it specially. */ + /* 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; +} Jakub ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH] middle-end IFN_ASSUME support [PR106654] 2022-10-10 8:54 [PATCH] middle-end IFN_ASSUME support [PR106654] Jakub Jelinek @ 2022-10-10 21:09 ` Jason Merrill 2022-10-10 21:19 ` Jakub Jelinek 2022-10-11 18:05 ` [PATCH] middle-end " Andrew MacLeod 2022-10-14 20:43 ` Martin Uecker 2 siblings, 1 reply; 35+ messages in thread From: Jason Merrill @ 2022-10-10 21:09 UTC (permalink / raw) To: Jakub Jelinek, Richard Biener, Jan Hubicka, Aldy Hernandez; +Cc: gcc-patches On 10/10/22 04:54, Jakub Jelinek via Gcc-patches wrote: > Hi! > > 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). Jason ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH] middle-end IFN_ASSUME support [PR106654] 2022-10-10 21:09 ` Jason Merrill @ 2022-10-10 21:19 ` Jakub Jelinek 2022-10-11 13:36 ` [PATCH] middle-end, v2: " Jakub Jelinek 0 siblings, 1 reply; 35+ messages in thread From: Jakub Jelinek @ 2022-10-10 21:19 UTC (permalink / raw) To: Jason Merrill; +Cc: Richard Biener, Jan Hubicka, Aldy Hernandez, gcc-patches 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. Jakub ^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH] middle-end, v2: IFN_ASSUME support [PR106654] 2022-10-10 21:19 ` Jakub Jelinek @ 2022-10-11 13:36 ` Jakub Jelinek 2022-10-12 15:48 ` Jason Merrill 0 siblings, 1 reply; 35+ messages in thread From: Jakub Jelinek @ 2022-10-11 13:36 UTC (permalink / raw) To: Jason Merrill, Jan Hubicka, Richard Biener, gcc-patches 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, --- 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 ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH] middle-end, v2: IFN_ASSUME support [PR106654] 2022-10-11 13:36 ` [PATCH] middle-end, v2: " Jakub Jelinek @ 2022-10-12 15:48 ` Jason Merrill 2022-10-13 6:50 ` [PATCH] middle-end, v3: " Jakub Jelinek 0 siblings, 1 reply; 35+ messages in thread From: Jason Merrill @ 2022-10-12 15:48 UTC (permalink / raw) To: Jakub Jelinek, Jan Hubicka, Richard Biener, gcc-patches 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 > ^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH] middle-end, v3: IFN_ASSUME support [PR106654] 2022-10-12 15:48 ` Jason Merrill @ 2022-10-13 6:50 ` Jakub Jelinek 2022-10-14 11:27 ` Richard Biener 0 siblings, 1 reply; 35+ messages in thread From: Jakub Jelinek @ 2022-10-13 6:50 UTC (permalink / raw) To: Jason Merrill, Jan Hubicka, Richard Biener; +Cc: gcc-patches On Wed, Oct 12, 2022 at 11:48:27AM -0400, Jason Merrill wrote: > > --- gcc/cp/pt.cc.jj 2022-10-10 09:31:21.947480379 +0200 > > +++ gcc/cp/pt.cc 2022-10-10 09:59:49.299646482 +0200 > > @@ -21105,6 +21105,8 @@ tsubst_copy_and_build (tree t, > > ret = error_mark_node; > > break; > > } > > + if (!processing_template_decl) > > + arg = fold_build_cleanup_point_expr (TREE_TYPE (arg), arg); > > ret = build_call_expr_internal_loc (EXPR_LOCATION (t), > > IFN_ASSUME, > > void_type_node, 1, > > This starts to seem worth factoring out a build_assume_call function. Ok, below is an updated version of the patch that does that. Bootstrapped/regtested on x86_64-linux and i686-linux. > > I'll leave the middle-end review to others. Ok. 2022-10-13 Jakub Jelinek <jakub@redhat.com> PR c++/106654 gcc/ * function.h (struct function): Add assume_function bitfield. * gimplify.cc (gimplify_call_expr): If the assumption isn't simple enough, expand it into IFN_ASSUME guarded block or for -O0 drop it. * gimple-low.cc (create_assumption_fn): New function. (struct lower_assumption_data): New type. (find_assumption_locals_r, assumption_copy_decl, adjust_assumption_stmt_r, adjust_assumption_stmt_op, lower_assumption): New functions. (lower_stmt): Handle IFN_ASSUME guarded block. * tree-ssa-ccp.cc (pass_fold_builtins::execute): Remove IFN_ASSUME calls. * lto-streamer-out.cc (output_struct_function_base): Pack assume_function bit. * lto-streamer-in.cc (input_struct_function_base): And unpack it. * cgraphunit.cc (cgraph_node::expand): Don't verify assume_function has TREE_ASM_WRITTEN set and don't release its body. * cfgexpand.cc (pass_expand::execute): Don't expand assume_function into RTL, just destroy loops and exit. * internal-fn.cc (expand_ASSUME): Remove gcc_unreachable. * passes.cc (pass_rest_of_compilation::gate): Return false also for fun->assume_function. * tree-vectorizer.cc (pass_vectorize::gate, pass_slp_vectorize::gate): Likewise. * ipa-icf.cc (sem_function::parse): Punt for func->assume_function. gcc/cp/ * cp-tree.h (build_assume_call): Declare. * parser.cc (cp_parser_omp_assumption_clauses): Use build_assume_call. * cp-gimplify.cc (build_assume_call): New function. (process_stmt_assume_attribute): Use build_assume_call. * pt.cc (tsubst_copy_and_build): Likewise. gcc/testsuite/ * g++.dg/cpp23/attr-assume5.C: New test. * g++.dg/cpp23/attr-assume6.C: New test. * g++.dg/cpp23/attr-assume7.C: New test. --- gcc/function.h.jj 2022-10-10 11:57:40.163722972 +0200 +++ gcc/function.h 2022-10-12 19:48:28.887554771 +0200 @@ -438,6 +438,10 @@ struct GTY(()) function { /* Set if there are any OMP_TARGET regions in the function. */ unsigned int has_omp_target : 1; + + /* Set for artificial function created for [[assume (cond)]]. + These should be GIMPLE optimized, but not expanded to RTL. */ + unsigned int assume_function : 1; }; /* Add the decl D to the local_decls list of FUN. */ --- gcc/gimplify.cc.jj 2022-10-10 11:57:40.165722944 +0200 +++ gcc/gimplify.cc 2022-10-12 19:48:28.890554730 +0200 @@ -3569,7 +3569,52 @@ gimplify_call_expr (tree *expr_p, gimple fndecl, 0)); return GS_OK; } - /* FIXME: Otherwise expand it specially. */ + /* If not optimizing, ignore the assumptions. */ + if (!optimize) + { + *expr_p = NULL_TREE; + return GS_ALL_DONE; + } + /* Temporarily, until gimple lowering, transform + .ASSUME (cond); + into: + guard = .ASSUME (); + if (guard) goto label_true; else label_false; + label_true:; + { + guard = cond; + } + label_false:; + .ASSUME (guard); + such that gimple lowering can outline the condition into + a separate function easily. */ + tree guard = create_tmp_var (boolean_type_node); + gcall *call = gimple_build_call_internal (ifn, 0); + gimple_call_set_nothrow (call, TREE_NOTHROW (*expr_p)); + gimple_set_location (call, loc); + gimple_call_set_lhs (call, guard); + gimple_seq_add_stmt (pre_p, call); + *expr_p = build2 (MODIFY_EXPR, void_type_node, guard, + CALL_EXPR_ARG (*expr_p, 0)); + *expr_p = build3 (BIND_EXPR, void_type_node, NULL, *expr_p, NULL); + tree label_false = create_artificial_label (UNKNOWN_LOCATION); + tree label_true = create_artificial_label (UNKNOWN_LOCATION); + gcond *cond_stmt = gimple_build_cond (NE_EXPR, guard, + boolean_false_node, + label_true, label_false); + gimplify_seq_add_stmt (pre_p, cond_stmt); + gimplify_seq_add_stmt (pre_p, gimple_build_label (label_true)); + push_gimplify_context (); + gimple_seq body = NULL; + gimple *g = gimplify_and_return_first (*expr_p, &body); + pop_gimplify_context (g); + gimplify_seq_add_seq (pre_p, body); + gimplify_seq_add_stmt (pre_p, gimple_build_label (label_false)); + call = gimple_build_call_internal (ifn, 1, guard); + gimple_call_set_nothrow (call, TREE_NOTHROW (*expr_p)); + gimple_set_location (call, loc); + gimple_seq_add_stmt (pre_p, call); + *expr_p = NULL_TREE; return GS_ALL_DONE; } --- gcc/gimple-low.cc.jj 2022-10-10 11:57:40.163722972 +0200 +++ gcc/gimple-low.cc 2022-10-12 19:48:28.890554730 +0200 @@ -33,6 +33,13 @@ along with GCC; see the file COPYING3. #include "predict.h" #include "gimple-predict.h" #include "gimple-fold.h" +#include "cgraph.h" +#include "tree-ssa.h" +#include "value-range.h" +#include "stringpool.h" +#include "tree-ssanames.h" +#include "tree-inline.h" +#include "gimple-walk.h" /* The differences between High GIMPLE and Low GIMPLE are the following: @@ -237,6 +244,383 @@ lower_omp_directive (gimple_stmt_iterato gsi_next (gsi); } +static tree +create_assumption_fn (location_t loc) +{ + tree name = clone_function_name_numbered (current_function_decl, "_assume"); + /* For now, will be changed later. */ + tree type = TREE_TYPE (current_function_decl); + tree decl = build_decl (loc, FUNCTION_DECL, name, type); + TREE_STATIC (decl) = 1; + TREE_USED (decl) = 1; + DECL_ARTIFICIAL (decl) = 1; + DECL_IGNORED_P (decl) = 1; + DECL_NAMELESS (decl) = 1; + TREE_PUBLIC (decl) = 0; + DECL_UNINLINABLE (decl) = 1; + DECL_EXTERNAL (decl) = 0; + DECL_CONTEXT (decl) = NULL_TREE; + DECL_INITIAL (decl) = make_node (BLOCK); + BLOCK_SUPERCONTEXT (DECL_INITIAL (decl)) = decl; + DECL_FUNCTION_SPECIFIC_OPTIMIZATION (decl) + = DECL_FUNCTION_SPECIFIC_OPTIMIZATION (current_function_decl); + DECL_FUNCTION_SPECIFIC_TARGET (decl) + = DECL_FUNCTION_SPECIFIC_TARGET (current_function_decl); + DECL_FUNCTION_VERSIONED (decl) + = DECL_FUNCTION_VERSIONED (current_function_decl); + tree t = build_decl (DECL_SOURCE_LOCATION (decl), + RESULT_DECL, NULL_TREE, boolean_type_node); + DECL_ARTIFICIAL (t) = 1; + DECL_IGNORED_P (t) = 1; + DECL_CONTEXT (t) = decl; + DECL_RESULT (decl) = t; + push_struct_function (decl); + cfun->function_end_locus = loc; + init_tree_ssa (cfun); + return decl; +} + +struct lower_assumption_data +{ + copy_body_data id; + tree return_false_label; + tree guard_copy; + auto_vec<tree> decls; +}; + +/* Helper function for lower_assumptions. Find local vars and labels + in the assumption sequence and remove debug stmts. */ + +static tree +find_assumption_locals_r (gimple_stmt_iterator *gsi_p, bool *, + struct walk_stmt_info *wi) +{ + lower_assumption_data *data = (lower_assumption_data *) wi->info; + gimple *stmt = gsi_stmt (*gsi_p); + tree lhs = gimple_get_lhs (stmt); + if (lhs && TREE_CODE (lhs) == SSA_NAME) + { + gcc_assert (SSA_NAME_VAR (lhs) == NULL_TREE); + data->id.decl_map->put (lhs, NULL_TREE); + data->decls.safe_push (lhs); + } + switch (gimple_code (stmt)) + { + case GIMPLE_BIND: + for (tree var = gimple_bind_vars (as_a <gbind *> (stmt)); + var; var = DECL_CHAIN (var)) + if (VAR_P (var) + && !DECL_EXTERNAL (var) + && DECL_CONTEXT (var) == data->id.src_fn) + { + data->id.decl_map->put (var, var); + data->decls.safe_push (var); + } + break; + case GIMPLE_LABEL: + { + tree label = gimple_label_label (as_a <glabel *> (stmt)); + data->id.decl_map->put (label, label); + break; + } + case GIMPLE_RETURN: + /* If something in assumption tries to return from parent function, + if it would be reached in hypothetical evaluation, it would be UB, + so transform such returns into return false; */ + { + gimple *g = gimple_build_assign (data->guard_copy, boolean_false_node); + gsi_insert_before (gsi_p, g, GSI_SAME_STMT); + gimple_return_set_retval (as_a <greturn *> (stmt), data->guard_copy); + break; + } + case GIMPLE_DEBUG: + /* As assumptions won't be emitted, debug info stmts in them + are useless. */ + gsi_remove (gsi_p, true); + wi->removed_stmt = true; + break; + default: + break; + } + return NULL_TREE; +} + +/* Create a new PARM_DECL that is indentical in all respect to DECL except that + DECL can be either a VAR_DECL, a PARM_DECL or RESULT_DECL. The original + DECL must come from ID->src_fn and the copy will be part of ID->dst_fn. */ + +static tree +assumption_copy_decl (tree decl, copy_body_data *id) +{ + tree type = TREE_TYPE (decl); + + if (is_global_var (decl)) + return decl; + + gcc_assert (VAR_P (decl) + || TREE_CODE (decl) == PARM_DECL + || TREE_CODE (decl) == RESULT_DECL); + tree copy = build_decl (DECL_SOURCE_LOCATION (decl), + PARM_DECL, DECL_NAME (decl), type); + if (DECL_PT_UID_SET_P (decl)) + SET_DECL_PT_UID (copy, DECL_PT_UID (decl)); + TREE_ADDRESSABLE (copy) = TREE_ADDRESSABLE (decl); + TREE_READONLY (copy) = TREE_READONLY (decl); + TREE_THIS_VOLATILE (copy) = TREE_THIS_VOLATILE (decl); + DECL_NOT_GIMPLE_REG_P (copy) = DECL_NOT_GIMPLE_REG_P (decl); + DECL_BY_REFERENCE (copy) = DECL_BY_REFERENCE (decl); + DECL_ARG_TYPE (copy) = type; + ((lower_assumption_data *) id)->decls.safe_push (decl); + return copy_decl_for_dup_finish (id, decl, copy); +} + +/* Transform gotos out of the assumption into return false. */ + +static tree +adjust_assumption_stmt_r (gimple_stmt_iterator *gsi_p, bool *, + struct walk_stmt_info *wi) +{ + lower_assumption_data *data = (lower_assumption_data *) wi->info; + gimple *stmt = gsi_stmt (*gsi_p); + tree lab = NULL_TREE; + unsigned int idx = 0; + if (gimple_code (stmt) == GIMPLE_GOTO) + lab = gimple_goto_dest (stmt); + else if (gimple_code (stmt) == GIMPLE_COND) + { + repeat: + if (idx == 0) + lab = gimple_cond_true_label (as_a <gcond *> (stmt)); + else + lab = gimple_cond_false_label (as_a <gcond *> (stmt)); + } + else if (gimple_code (stmt) == GIMPLE_LABEL) + { + tree label = gimple_label_label (as_a <glabel *> (stmt)); + DECL_CONTEXT (label) = current_function_decl; + } + if (lab) + { + if (!data->id.decl_map->get (lab)) + { + if (!data->return_false_label) + data->return_false_label + = create_artificial_label (UNKNOWN_LOCATION); + if (gimple_code (stmt) == GIMPLE_GOTO) + gimple_goto_set_dest (as_a <ggoto *> (stmt), + data->return_false_label); + else if (idx == 0) + gimple_cond_set_true_label (as_a <gcond *> (stmt), + data->return_false_label); + else + gimple_cond_set_false_label (as_a <gcond *> (stmt), + data->return_false_label); + } + if (gimple_code (stmt) == GIMPLE_COND && idx == 0) + { + idx = 1; + goto repeat; + } + } + return NULL_TREE; +} + +/* Adjust trees in the assumption body. Called through walk_tree. */ + +static tree +adjust_assumption_stmt_op (tree *tp, int *, void *datap) +{ + struct walk_stmt_info *wi = (struct walk_stmt_info *) datap; + lower_assumption_data *data = (lower_assumption_data *) wi->info; + tree t = *tp; + tree *newt; + switch (TREE_CODE (t)) + { + case SSA_NAME: + newt = data->id.decl_map->get (t); + /* There shouldn't be SSA_NAMEs other than ones defined in the + assumption's body. */ + gcc_assert (newt); + *tp = *newt; + break; + case LABEL_DECL: + newt = data->id.decl_map->get (t); + if (newt) + *tp = *newt; + break; + case VAR_DECL: + case PARM_DECL: + case RESULT_DECL: + *tp = remap_decl (t, &data->id); + break; + default: + break; + } + return NULL_TREE; +} + +/* Lower assumption. + The gimplifier transformed: + .ASSUME (cond); + into: + guard = .ASSUME (); + if (guard) goto label_true; else label_false; + label_true:; + { + guard = cond; + } + label_false:; + .ASSUME (guard); + which we should transform into: + .ASSUME (&artificial_fn, args...); + where artificial_fn will look like: + bool artificial_fn (args...) + { + guard = cond; + return guard; + } + with any debug stmts in the block removed and jumps out of + the block or return stmts replaced with return false; */ + +static void +lower_assumption (gimple_stmt_iterator *gsi, struct lower_data *data) +{ + gimple *stmt = gsi_stmt (*gsi); + tree guard = gimple_call_lhs (stmt); + location_t loc = gimple_location (stmt); + gcc_assert (guard); + gsi_remove (gsi, true); + stmt = gsi_stmt (*gsi); + gcond *cond = as_a <gcond *> (stmt); + gcc_assert (gimple_cond_lhs (cond) == guard + || gimple_cond_rhs (cond) == guard); + tree l1 = gimple_cond_true_label (cond); + tree l2 = gimple_cond_false_label (cond); + gsi_remove (gsi, true); + stmt = gsi_stmt (*gsi); + glabel *lab = as_a <glabel *> (stmt); + gcc_assert (gimple_label_label (lab) == l1 + || gimple_label_label (lab) == l2); + gsi_remove (gsi, true); + gimple *bind = gsi_stmt (*gsi); + gcc_assert (gimple_code (bind) == GIMPLE_BIND); + + lower_assumption_data lad; + hash_map<tree, tree> decl_map; + memset (&lad.id, 0, sizeof (lad.id)); + lad.return_false_label = NULL_TREE; + lad.id.src_fn = current_function_decl; + lad.id.dst_fn = create_assumption_fn (loc); + lad.id.src_cfun = DECL_STRUCT_FUNCTION (lad.id.src_fn); + lad.id.decl_map = &decl_map; + lad.id.copy_decl = assumption_copy_decl; + lad.id.transform_call_graph_edges = CB_CGE_DUPLICATE; + lad.id.transform_parameter = true; + lad.id.do_not_unshare = true; + lad.id.do_not_fold = true; + cfun->curr_properties = lad.id.src_cfun->curr_properties; + lad.guard_copy = create_tmp_var (boolean_type_node); + decl_map.put (lad.guard_copy, lad.guard_copy); + decl_map.put (guard, lad.guard_copy); + cfun->assume_function = 1; + + struct walk_stmt_info wi; + memset (&wi, 0, sizeof (wi)); + wi.info = (void *) &lad; + walk_gimple_stmt (gsi, find_assumption_locals_r, NULL, &wi); + unsigned int sz = lad.decls.length (); + for (unsigned i = 0; i < sz; ++i) + { + tree v = lad.decls[i]; + tree newv; + if (TREE_CODE (v) == SSA_NAME) + { + newv = make_ssa_name (remap_type (TREE_TYPE (v), &lad.id)); + decl_map.put (v, newv); + } + else if (VAR_P (v)) + { + if (is_global_var (v) && !DECL_ASSEMBLER_NAME_SET_P (v)) + DECL_ASSEMBLER_NAME (v); + TREE_TYPE (v) = remap_type (TREE_TYPE (v), &lad.id); + DECL_CONTEXT (v) = current_function_decl; + } + } + memset (&wi, 0, sizeof (wi)); + wi.info = (void *) &lad; + walk_gimple_stmt (gsi, adjust_assumption_stmt_r, + adjust_assumption_stmt_op, &wi); + gsi_remove (gsi, false); + + gimple_seq body = NULL; + gimple *g = gimple_build_assign (lad.guard_copy, boolean_false_node); + gimple_seq_add_stmt (&body, g); + gimple_seq_add_stmt (&body, bind); + greturn *gr = gimple_build_return (lad.guard_copy); + gimple_seq_add_stmt (&body, gr); + if (lad.return_false_label) + { + g = gimple_build_label (lad.return_false_label); + gimple_seq_add_stmt (&body, g); + g = gimple_build_assign (lad.guard_copy, boolean_false_node); + gimple_seq_add_stmt (&body, g); + gr = gimple_build_return (lad.guard_copy); + gimple_seq_add_stmt (&body, gr); + } + bind = gimple_build_bind (NULL_TREE, body, NULL_TREE); + body = NULL; + gimple_seq_add_stmt (&body, bind); + gimple_set_body (current_function_decl, body); + pop_cfun (); + + tree parms = NULL_TREE; + tree parmt = void_list_node; + auto_vec<tree, 8> vargs; + vargs.safe_grow (1 + (lad.decls.length () - sz), true); + vargs[0] = build_fold_addr_expr (lad.id.dst_fn); + for (unsigned i = lad.decls.length (); i > sz; --i) + { + tree *v = decl_map.get (lad.decls[i - 1]); + gcc_assert (v && TREE_CODE (*v) == PARM_DECL); + DECL_CHAIN (*v) = parms; + parms = *v; + parmt = tree_cons (NULL_TREE, TREE_TYPE (*v), parmt); + vargs[i - sz] = lad.decls[i - 1]; + if (is_gimple_reg_type (TREE_TYPE (vargs[i - sz])) + && !is_gimple_val (vargs[i - sz])) + { + tree t = make_ssa_name (TREE_TYPE (vargs[i - sz])); + g = gimple_build_assign (t, vargs[i - sz]); + gsi_insert_before (gsi, g, GSI_SAME_STMT); + vargs[i - sz] = t; + } + } + DECL_ARGUMENTS (lad.id.dst_fn) = parms; + TREE_TYPE (lad.id.dst_fn) = build_function_type (boolean_type_node, parmt); + + cgraph_node::add_new_function (lad.id.dst_fn, false); + + for (unsigned i = 0; i < sz; ++i) + { + tree v = lad.decls[i]; + if (TREE_CODE (v) == SSA_NAME) + release_ssa_name (v); + } + + stmt = gsi_stmt (*gsi); + lab = as_a <glabel *> (stmt); + gcc_assert (gimple_label_label (lab) == l1 + || gimple_label_label (lab) == l2); + gsi_remove (gsi, true); + stmt = gsi_stmt (*gsi); + gcc_assert (gimple_call_internal_p (stmt, IFN_ASSUME) + && gimple_call_num_args (stmt) == 1 + && gimple_call_arg (stmt, 0) == guard); + data->cannot_fallthru = false; + gcall *call = gimple_build_call_internal_vec (IFN_ASSUME, vargs); + gimple_set_location (call, loc); + gsi_replace (gsi, call, true); +} /* Lower statement GSI. DATA is passed through the recursion. We try to track the fallthruness of statements and get rid of unreachable return @@ -354,6 +738,13 @@ lower_stmt (gimple_stmt_iterator *gsi, s tree decl = gimple_call_fndecl (stmt); unsigned i; + if (gimple_call_internal_p (stmt, IFN_ASSUME) + && gimple_call_num_args (stmt) == 0) + { + lower_assumption (gsi, data); + return; + } + for (i = 0; i < gimple_call_num_args (stmt); i++) { tree arg = gimple_call_arg (stmt, i); --- gcc/tree-ssa-ccp.cc.jj 2022-10-10 11:57:40.203722414 +0200 +++ gcc/tree-ssa-ccp.cc 2022-10-12 19:48:28.891554716 +0200 @@ -4253,6 +4253,12 @@ pass_fold_builtins::execute (function *f } callee = gimple_call_fndecl (stmt); + if (!callee + && gimple_call_internal_p (stmt, IFN_ASSUME)) + { + gsi_remove (&i, true); + continue; + } if (!callee || !fndecl_built_in_p (callee, BUILT_IN_NORMAL)) { gsi_next (&i); --- gcc/lto-streamer-out.cc.jj 2022-10-10 11:57:40.202722428 +0200 +++ gcc/lto-streamer-out.cc 2022-10-12 19:48:28.891554716 +0200 @@ -2278,6 +2278,7 @@ output_struct_function_base (struct outp bp_pack_value (&bp, fn->calls_eh_return, 1); bp_pack_value (&bp, fn->has_force_vectorize_loops, 1); bp_pack_value (&bp, fn->has_simduid_loops, 1); + bp_pack_value (&bp, fn->assume_function, 1); bp_pack_value (&bp, fn->va_list_fpr_size, 8); bp_pack_value (&bp, fn->va_list_gpr_size, 8); bp_pack_value (&bp, fn->last_clique, sizeof (short) * 8); --- gcc/lto-streamer-in.cc.jj 2022-10-10 11:57:40.201722442 +0200 +++ gcc/lto-streamer-in.cc 2022-10-12 19:48:28.891554716 +0200 @@ -1318,6 +1318,7 @@ input_struct_function_base (struct funct fn->calls_eh_return = bp_unpack_value (&bp, 1); fn->has_force_vectorize_loops = bp_unpack_value (&bp, 1); fn->has_simduid_loops = bp_unpack_value (&bp, 1); + fn->assume_function = bp_unpack_value (&bp, 1); fn->va_list_fpr_size = bp_unpack_value (&bp, 8); fn->va_list_gpr_size = bp_unpack_value (&bp, 8); fn->last_clique = bp_unpack_value (&bp, sizeof (short) * 8); --- gcc/cgraphunit.cc.jj 2022-10-10 11:57:40.152723125 +0200 +++ gcc/cgraphunit.cc 2022-10-12 19:48:28.892554703 +0200 @@ -1882,6 +1882,16 @@ cgraph_node::expand (void) ggc_collect (); timevar_pop (TV_REST_OF_COMPILATION); + if (DECL_STRUCT_FUNCTION (decl) + && DECL_STRUCT_FUNCTION (decl)->assume_function) + { + /* Assume functions aren't expanded into RTL, on the other side + we don't want to release their body. */ + if (cfun) + pop_cfun (); + return; + } + /* Make sure that BE didn't give up on compiling. */ gcc_assert (TREE_ASM_WRITTEN (decl)); if (cfun) --- gcc/cfgexpand.cc.jj 2022-10-10 11:57:40.152723125 +0200 +++ gcc/cfgexpand.cc 2022-10-12 19:48:28.893554689 +0200 @@ -6597,6 +6597,14 @@ pass_expand::execute (function *fun) rtx_insn *var_seq, *var_ret_seq; unsigned i; + if (cfun->assume_function) + { + /* Assume functions should not be expanded to RTL. */ + cfun->curr_properties &= ~PROP_loops; + loop_optimizer_finalize (); + return 0; + } + timevar_push (TV_OUT_OF_SSA); rewrite_out_of_ssa (&SA); timevar_pop (TV_OUT_OF_SSA); --- gcc/internal-fn.cc.jj 2022-10-10 11:57:40.166722930 +0200 +++ gcc/internal-fn.cc 2022-10-12 19:48:28.893554689 +0200 @@ -4526,5 +4526,4 @@ expand_TRAP (internal_fn, gcall *) void expand_ASSUME (internal_fn, gcall *) { - gcc_unreachable (); } --- gcc/passes.cc.jj 2022-10-10 11:57:40.202722428 +0200 +++ gcc/passes.cc 2022-10-12 19:48:28.893554689 +0200 @@ -647,11 +647,12 @@ public: {} /* opt_pass methods: */ - bool gate (function *) final override + bool gate (function *fun) final override { /* Early return if there were errors. We can run afoul of our consistency checks, and there's not really much point in fixing them. */ - return !(rtl_dump_and_exit || flag_syntax_only || seen_error ()); + return !(rtl_dump_and_exit || fun->assume_function + || flag_syntax_only || seen_error ()); } }; // class pass_rest_of_compilation --- gcc/tree-vectorizer.cc.jj 2022-10-10 11:57:40.204722400 +0200 +++ gcc/tree-vectorizer.cc 2022-10-12 19:48:28.894554675 +0200 @@ -1213,6 +1213,10 @@ public: /* opt_pass methods: */ bool gate (function *fun) final override { + /* Vectorization makes range analysis of assume functions + harder, not easier. */ + if (fun->assume_function) + return false; return flag_tree_loop_vectorize || fun->has_force_vectorize_loops; } @@ -1490,7 +1494,14 @@ public: /* opt_pass methods: */ opt_pass * clone () final override { return new pass_slp_vectorize (m_ctxt); } - bool gate (function *) final override { return flag_tree_slp_vectorize != 0; } + bool gate (function *fun) final override + { + /* Vectorization makes range analysis of assume functions harder, + not easier. */ + if (fun->assume_function) + return false; + return flag_tree_slp_vectorize != 0; + } unsigned int execute (function *) final override; }; // class pass_slp_vectorize --- gcc/ipa-icf.cc.jj 2022-10-10 11:57:40.201722442 +0200 +++ gcc/ipa-icf.cc 2022-10-12 19:48:28.894554675 +0200 @@ -1517,6 +1517,9 @@ sem_function::parse (cgraph_node *node, if (!func || (!node->has_gimple_body_p () && !node->thunk)) return NULL; + if (func->assume_function) + return NULL; + if (lookup_attribute_by_prefix ("omp ", DECL_ATTRIBUTES (node->decl)) != NULL) return NULL; --- gcc/cp/cp-tree.h.jj 2022-10-12 17:51:00.911944744 +0200 +++ gcc/cp/cp-tree.h 2022-10-12 19:53:17.072615254 +0200 @@ -8284,6 +8284,7 @@ extern tree predeclare_vla (tree); extern void clear_fold_cache (void); extern tree lookup_hotness_attribute (tree); extern tree process_stmt_hotness_attribute (tree, location_t); +extern tree build_assume_call (location_t, tree); extern tree process_stmt_assume_attribute (tree, tree, location_t); extern bool simple_empty_class_p (tree, tree, tree_code); extern tree fold_builtin_source_location (location_t); --- gcc/cp/parser.cc.jj 2022-10-12 17:51:00.951944199 +0200 +++ gcc/cp/parser.cc 2022-10-12 19:52:15.855452024 +0200 @@ -46006,11 +46006,7 @@ cp_parser_omp_assumption_clauses (cp_par if (!type_dependent_expression_p (t)) t = contextual_conv_bool (t, tf_warning_or_error); if (is_assume && !error_operand_p (t)) - { - t = build_call_expr_internal_loc (eloc, IFN_ASSUME, - void_type_node, 1, t); - finish_expr_stmt (t); - } + finish_expr_stmt (build_assume_call (eloc, t)); if (!parens.require_close (parser)) cp_parser_skip_to_closing_parenthesis (parser, /*recovering=*/true, --- gcc/cp/cp-gimplify.cc.jj 2022-10-12 17:51:00.909944772 +0200 +++ gcc/cp/cp-gimplify.cc 2022-10-12 19:51:47.140844525 +0200 @@ -3096,6 +3096,17 @@ process_stmt_hotness_attribute (tree std return std_attrs; } +/* Build IFN_ASSUME internal call for assume condition ARG. */ + +tree +build_assume_call (location_t loc, tree arg) +{ + if (!processing_template_decl) + arg = fold_build_cleanup_point_expr (TREE_TYPE (arg), arg); + return build_call_expr_internal_loc (loc, IFN_ASSUME, void_type_node, + 1, arg); +} + /* If [[assume (cond)]] appears on this statement, handle it. */ tree @@ -3132,9 +3143,7 @@ process_stmt_assume_attribute (tree std_ arg = contextual_conv_bool (arg, tf_warning_or_error); if (error_operand_p (arg)) continue; - statement = build_call_expr_internal_loc (attrs_loc, IFN_ASSUME, - void_type_node, 1, arg); - finish_expr_stmt (statement); + finish_expr_stmt (build_assume_call (attrs_loc, arg)); } } return remove_attribute ("gnu", "assume", std_attrs); --- gcc/cp/pt.cc.jj 2022-10-12 17:51:00.957944117 +0200 +++ gcc/cp/pt.cc 2022-10-12 19:52:45.204050862 +0200 @@ -21116,10 +21116,7 @@ tsubst_copy_and_build (tree t, ret = error_mark_node; break; } - ret = build_call_expr_internal_loc (EXPR_LOCATION (t), - IFN_ASSUME, - void_type_node, 1, - arg); + ret = build_assume_call (EXPR_LOCATION (t), arg); RETURN (ret); } break; --- gcc/testsuite/g++.dg/cpp23/attr-assume5.C.jj 2022-10-12 19:48:28.903554553 +0200 +++ gcc/testsuite/g++.dg/cpp23/attr-assume5.C 2022-10-12 19:48:28.903554553 +0200 @@ -0,0 +1,5 @@ +// P1774R8 - Portable assumptions +// { dg-do run { target c++11 } } +// { dg-options "-O2" } + +#include "attr-assume1.C" --- gcc/testsuite/g++.dg/cpp23/attr-assume6.C.jj 2022-10-12 19:48:28.903554553 +0200 +++ gcc/testsuite/g++.dg/cpp23/attr-assume6.C 2022-10-12 19:48:28.903554553 +0200 @@ -0,0 +1,5 @@ +// P1774R8 - Portable assumptions +// { dg-do run { target c++11 } } +// { dg-options "-O2" } + +#include "attr-assume3.C" --- gcc/testsuite/g++.dg/cpp23/attr-assume7.C.jj 2022-10-12 19:48:28.903554553 +0200 +++ gcc/testsuite/g++.dg/cpp23/attr-assume7.C 2022-10-12 19:48:28.903554553 +0200 @@ -0,0 +1,42 @@ +// P1774R8 - Portable assumptions +// { dg-do compile { target c++11 } } +// { dg-options "-O2" } + +int +foo (int x) +{ + [[assume (x == 42)]]; + return x; +} + +int +bar (int x) +{ + [[assume (++x == 43)]]; + return x; +} + +int +baz (int x) +{ + [[assume (({ int z = ++x; static int w; ++w; if (z == 51) return -1; if (z == 53) goto lab1; if (z == 64) throw 1; z == 43; }))]]; +lab1: + return x; +} + +struct S { S (); S (const S &); ~S (); int a, b; int foo (); }; + +int +qux () +{ + S s; + [[assume (s.a == 42 && s.b == 43)]]; + return s.a + s.b; +} + +int +S::foo () +{ + [[assume (a == 42 && b == 43)]]; + return a + b; +} Jakub ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH] middle-end, v3: IFN_ASSUME support [PR106654] 2022-10-13 6:50 ` [PATCH] middle-end, v3: " Jakub Jelinek @ 2022-10-14 11:27 ` Richard Biener 2022-10-14 18:33 ` Jakub Jelinek 2022-10-17 15:44 ` [PATCH] middle-end, v4: " Jakub Jelinek 0 siblings, 2 replies; 35+ messages in thread From: Richard Biener @ 2022-10-14 11:27 UTC (permalink / raw) To: Jakub Jelinek; +Cc: Jason Merrill, Jan Hubicka, gcc-patches On Thu, 13 Oct 2022, Jakub Jelinek wrote: > On Wed, Oct 12, 2022 at 11:48:27AM -0400, Jason Merrill wrote: > > > --- gcc/cp/pt.cc.jj 2022-10-10 09:31:21.947480379 +0200 > > > +++ gcc/cp/pt.cc 2022-10-10 09:59:49.299646482 +0200 > > > @@ -21105,6 +21105,8 @@ tsubst_copy_and_build (tree t, > > > ret = error_mark_node; > > > break; > > > } > > > + if (!processing_template_decl) > > > + arg = fold_build_cleanup_point_expr (TREE_TYPE (arg), arg); > > > ret = build_call_expr_internal_loc (EXPR_LOCATION (t), > > > IFN_ASSUME, > > > void_type_node, 1, > > > > This starts to seem worth factoring out a build_assume_call function. > > Ok, below is an updated version of the patch that does that. > Bootstrapped/regtested on x86_64-linux and i686-linux. > > > > I'll leave the middle-end review to others. > > Ok. > > 2022-10-13 Jakub Jelinek <jakub@redhat.com> > > PR c++/106654 > gcc/ > * function.h (struct function): Add assume_function bitfield. > * gimplify.cc (gimplify_call_expr): If the assumption isn't > simple enough, expand it into IFN_ASSUME guarded block or > for -O0 drop it. > * gimple-low.cc (create_assumption_fn): New function. > (struct lower_assumption_data): New type. > (find_assumption_locals_r, assumption_copy_decl, > adjust_assumption_stmt_r, adjust_assumption_stmt_op, > lower_assumption): New functions. > (lower_stmt): Handle IFN_ASSUME guarded block. > * tree-ssa-ccp.cc (pass_fold_builtins::execute): Remove > IFN_ASSUME calls. > * lto-streamer-out.cc (output_struct_function_base): Pack > assume_function bit. > * lto-streamer-in.cc (input_struct_function_base): And unpack it. > * cgraphunit.cc (cgraph_node::expand): Don't verify assume_function > has TREE_ASM_WRITTEN set and don't release its body. > * cfgexpand.cc (pass_expand::execute): Don't expand assume_function > into RTL, just destroy loops and exit. > * internal-fn.cc (expand_ASSUME): Remove gcc_unreachable. > * passes.cc (pass_rest_of_compilation::gate): Return false also for > fun->assume_function. > * tree-vectorizer.cc (pass_vectorize::gate, > pass_slp_vectorize::gate): Likewise. > * ipa-icf.cc (sem_function::parse): Punt for func->assume_function. > gcc/cp/ > * cp-tree.h (build_assume_call): Declare. > * parser.cc (cp_parser_omp_assumption_clauses): Use build_assume_call. > * cp-gimplify.cc (build_assume_call): New function. > (process_stmt_assume_attribute): Use build_assume_call. > * pt.cc (tsubst_copy_and_build): Likewise. > gcc/testsuite/ > * g++.dg/cpp23/attr-assume5.C: New test. > * g++.dg/cpp23/attr-assume6.C: New test. > * g++.dg/cpp23/attr-assume7.C: New test. > > --- gcc/function.h.jj 2022-10-10 11:57:40.163722972 +0200 > +++ gcc/function.h 2022-10-12 19:48:28.887554771 +0200 > @@ -438,6 +438,10 @@ struct GTY(()) function { > > /* Set if there are any OMP_TARGET regions in the function. */ > unsigned int has_omp_target : 1; > + > + /* Set for artificial function created for [[assume (cond)]]. > + These should be GIMPLE optimized, but not expanded to RTL. */ > + unsigned int assume_function : 1; I wonder if we should have this along force_output in the symtab node and let the symtab code decide whether to expand? > }; > > /* Add the decl D to the local_decls list of FUN. */ > --- gcc/gimplify.cc.jj 2022-10-10 11:57:40.165722944 +0200 > +++ gcc/gimplify.cc 2022-10-12 19:48:28.890554730 +0200 > @@ -3569,7 +3569,52 @@ gimplify_call_expr (tree *expr_p, gimple > fndecl, 0)); > return GS_OK; > } > - /* FIXME: Otherwise expand it specially. */ > + /* If not optimizing, ignore the assumptions. */ > + if (!optimize) > + { > + *expr_p = NULL_TREE; > + return GS_ALL_DONE; > + } > + /* Temporarily, until gimple lowering, transform > + .ASSUME (cond); > + into: > + guard = .ASSUME (); > + if (guard) goto label_true; else label_false; > + label_true:; > + { > + guard = cond; > + } > + label_false:; > + .ASSUME (guard); > + such that gimple lowering can outline the condition into > + a separate function easily. */ So the idea to use lambdas and/or nested functions (for OMP) didn't work out or is more complicated? I wonder if, instead of using the above intermediate form we can have a new structued GIMPLE code with sub-statements .ASSUME { condition; } ? There's gimple_statement_omp conveniently available as base and IIRC you had the requirement to implement some OMP assume as well? Of ocurse a different stmt class with body would work as well here, maybe we can even use a gbind with a special flag. The outlining code can then be ajusted to outline a single BIND? It probably won't simplify much that way. > + tree guard = create_tmp_var (boolean_type_node); > + gcall *call = gimple_build_call_internal (ifn, 0); > + gimple_call_set_nothrow (call, TREE_NOTHROW (*expr_p)); > + gimple_set_location (call, loc); > + gimple_call_set_lhs (call, guard); > + gimple_seq_add_stmt (pre_p, call); > + *expr_p = build2 (MODIFY_EXPR, void_type_node, guard, > + CALL_EXPR_ARG (*expr_p, 0)); > + *expr_p = build3 (BIND_EXPR, void_type_node, NULL, *expr_p, NULL); > + tree label_false = create_artificial_label (UNKNOWN_LOCATION); > + tree label_true = create_artificial_label (UNKNOWN_LOCATION); > + gcond *cond_stmt = gimple_build_cond (NE_EXPR, guard, > + boolean_false_node, > + label_true, label_false); > + gimplify_seq_add_stmt (pre_p, cond_stmt); > + gimplify_seq_add_stmt (pre_p, gimple_build_label (label_true)); > + push_gimplify_context (); > + gimple_seq body = NULL; > + gimple *g = gimplify_and_return_first (*expr_p, &body); > + pop_gimplify_context (g); > + gimplify_seq_add_seq (pre_p, body); > + gimplify_seq_add_stmt (pre_p, gimple_build_label (label_false)); > + call = gimple_build_call_internal (ifn, 1, guard); > + gimple_call_set_nothrow (call, TREE_NOTHROW (*expr_p)); > + gimple_set_location (call, loc); > + gimple_seq_add_stmt (pre_p, call); > + *expr_p = NULL_TREE; > return GS_ALL_DONE; > } > > --- gcc/gimple-low.cc.jj 2022-10-10 11:57:40.163722972 +0200 > +++ gcc/gimple-low.cc 2022-10-12 19:48:28.890554730 +0200 > @@ -33,6 +33,13 @@ along with GCC; see the file COPYING3. > #include "predict.h" > #include "gimple-predict.h" > #include "gimple-fold.h" > +#include "cgraph.h" > +#include "tree-ssa.h" > +#include "value-range.h" > +#include "stringpool.h" > +#include "tree-ssanames.h" > +#include "tree-inline.h" > +#include "gimple-walk.h" > > /* The differences between High GIMPLE and Low GIMPLE are the > following: > @@ -237,6 +244,383 @@ lower_omp_directive (gimple_stmt_iterato > gsi_next (gsi); > } comment missing > +static tree > +create_assumption_fn (location_t loc) > +{ > + tree name = clone_function_name_numbered (current_function_decl, "_assume"); > + /* For now, will be changed later. */ ? > + tree type = TREE_TYPE (current_function_decl); > + tree decl = build_decl (loc, FUNCTION_DECL, name, type); > + TREE_STATIC (decl) = 1; > + TREE_USED (decl) = 1; > + DECL_ARTIFICIAL (decl) = 1; > + DECL_IGNORED_P (decl) = 1; > + DECL_NAMELESS (decl) = 1; > + TREE_PUBLIC (decl) = 0; > + DECL_UNINLINABLE (decl) = 1; > + DECL_EXTERNAL (decl) = 0; > + DECL_CONTEXT (decl) = NULL_TREE; > + DECL_INITIAL (decl) = make_node (BLOCK); > + BLOCK_SUPERCONTEXT (DECL_INITIAL (decl)) = decl; > + DECL_FUNCTION_SPECIFIC_OPTIMIZATION (decl) > + = DECL_FUNCTION_SPECIFIC_OPTIMIZATION (current_function_decl); > + DECL_FUNCTION_SPECIFIC_TARGET (decl) > + = DECL_FUNCTION_SPECIFIC_TARGET (current_function_decl); > + DECL_FUNCTION_VERSIONED (decl) > + = DECL_FUNCTION_VERSIONED (current_function_decl); what does it mean to copy DECL_FUNCTION_VERSIONED here? > + tree t = build_decl (DECL_SOURCE_LOCATION (decl), > + RESULT_DECL, NULL_TREE, boolean_type_node); > + DECL_ARTIFICIAL (t) = 1; > + DECL_IGNORED_P (t) = 1; > + DECL_CONTEXT (t) = decl; > + DECL_RESULT (decl) = t; > + push_struct_function (decl); > + cfun->function_end_locus = loc; > + init_tree_ssa (cfun); > + return decl; > +} > + > +struct lower_assumption_data > +{ > + copy_body_data id; > + tree return_false_label; > + tree guard_copy; > + auto_vec<tree> decls; > +}; > + > +/* Helper function for lower_assumptions. Find local vars and labels > + in the assumption sequence and remove debug stmts. */ > + > +static tree > +find_assumption_locals_r (gimple_stmt_iterator *gsi_p, bool *, > + struct walk_stmt_info *wi) > +{ > + lower_assumption_data *data = (lower_assumption_data *) wi->info; > + gimple *stmt = gsi_stmt (*gsi_p); > + tree lhs = gimple_get_lhs (stmt); > + if (lhs && TREE_CODE (lhs) == SSA_NAME) > + { > + gcc_assert (SSA_NAME_VAR (lhs) == NULL_TREE); > + data->id.decl_map->put (lhs, NULL_TREE); > + data->decls.safe_push (lhs); > + } > + switch (gimple_code (stmt)) > + { > + case GIMPLE_BIND: > + for (tree var = gimple_bind_vars (as_a <gbind *> (stmt)); > + var; var = DECL_CHAIN (var)) > + if (VAR_P (var) > + && !DECL_EXTERNAL (var) > + && DECL_CONTEXT (var) == data->id.src_fn) > + { > + data->id.decl_map->put (var, var); > + data->decls.safe_push (var); > + } > + break; > + case GIMPLE_LABEL: > + { > + tree label = gimple_label_label (as_a <glabel *> (stmt)); > + data->id.decl_map->put (label, label); > + break; > + } > + case GIMPLE_RETURN: > + /* If something in assumption tries to return from parent function, > + if it would be reached in hypothetical evaluation, it would be UB, > + so transform such returns into return false; */ > + { > + gimple *g = gimple_build_assign (data->guard_copy, boolean_false_node); > + gsi_insert_before (gsi_p, g, GSI_SAME_STMT); > + gimple_return_set_retval (as_a <greturn *> (stmt), data->guard_copy); > + break; > + } > + case GIMPLE_DEBUG: > + /* As assumptions won't be emitted, debug info stmts in them > + are useless. */ > + gsi_remove (gsi_p, true); > + wi->removed_stmt = true; > + break; > + default: > + break; > + } > + return NULL_TREE; > +} > + > +/* Create a new PARM_DECL that is indentical in all respect to DECL except that > + DECL can be either a VAR_DECL, a PARM_DECL or RESULT_DECL. The original > + DECL must come from ID->src_fn and the copy will be part of ID->dst_fn. */ > + > +static tree > +assumption_copy_decl (tree decl, copy_body_data *id) > +{ > + tree type = TREE_TYPE (decl); > + > + if (is_global_var (decl)) > + return decl; > + > + gcc_assert (VAR_P (decl) > + || TREE_CODE (decl) == PARM_DECL > + || TREE_CODE (decl) == RESULT_DECL); > + tree copy = build_decl (DECL_SOURCE_LOCATION (decl), > + PARM_DECL, DECL_NAME (decl), type); > + if (DECL_PT_UID_SET_P (decl)) > + SET_DECL_PT_UID (copy, DECL_PT_UID (decl)); > + TREE_ADDRESSABLE (copy) = TREE_ADDRESSABLE (decl); > + TREE_READONLY (copy) = TREE_READONLY (decl); > + TREE_THIS_VOLATILE (copy) = TREE_THIS_VOLATILE (decl); > + DECL_NOT_GIMPLE_REG_P (copy) = DECL_NOT_GIMPLE_REG_P (decl); > + DECL_BY_REFERENCE (copy) = DECL_BY_REFERENCE (decl); > + DECL_ARG_TYPE (copy) = type; > + ((lower_assumption_data *) id)->decls.safe_push (decl); > + return copy_decl_for_dup_finish (id, decl, copy); > +} > + > +/* Transform gotos out of the assumption into return false. */ > + > +static tree > +adjust_assumption_stmt_r (gimple_stmt_iterator *gsi_p, bool *, > + struct walk_stmt_info *wi) > +{ > + lower_assumption_data *data = (lower_assumption_data *) wi->info; > + gimple *stmt = gsi_stmt (*gsi_p); > + tree lab = NULL_TREE; > + unsigned int idx = 0; > + if (gimple_code (stmt) == GIMPLE_GOTO) > + lab = gimple_goto_dest (stmt); > + else if (gimple_code (stmt) == GIMPLE_COND) > + { > + repeat: > + if (idx == 0) > + lab = gimple_cond_true_label (as_a <gcond *> (stmt)); > + else > + lab = gimple_cond_false_label (as_a <gcond *> (stmt)); > + } > + else if (gimple_code (stmt) == GIMPLE_LABEL) > + { > + tree label = gimple_label_label (as_a <glabel *> (stmt)); > + DECL_CONTEXT (label) = current_function_decl; > + } > + if (lab) > + { > + if (!data->id.decl_map->get (lab)) > + { > + if (!data->return_false_label) > + data->return_false_label > + = create_artificial_label (UNKNOWN_LOCATION); > + if (gimple_code (stmt) == GIMPLE_GOTO) > + gimple_goto_set_dest (as_a <ggoto *> (stmt), > + data->return_false_label); > + else if (idx == 0) > + gimple_cond_set_true_label (as_a <gcond *> (stmt), > + data->return_false_label); > + else > + gimple_cond_set_false_label (as_a <gcond *> (stmt), > + data->return_false_label); > + } > + if (gimple_code (stmt) == GIMPLE_COND && idx == 0) > + { > + idx = 1; > + goto repeat; > + } > + } > + return NULL_TREE; > +} > + > +/* Adjust trees in the assumption body. Called through walk_tree. */ > + > +static tree > +adjust_assumption_stmt_op (tree *tp, int *, void *datap) > +{ > + struct walk_stmt_info *wi = (struct walk_stmt_info *) datap; > + lower_assumption_data *data = (lower_assumption_data *) wi->info; > + tree t = *tp; > + tree *newt; > + switch (TREE_CODE (t)) > + { > + case SSA_NAME: > + newt = data->id.decl_map->get (t); > + /* There shouldn't be SSA_NAMEs other than ones defined in the > + assumption's body. */ > + gcc_assert (newt); > + *tp = *newt; > + break; > + case LABEL_DECL: > + newt = data->id.decl_map->get (t); > + if (newt) > + *tp = *newt; > + break; > + case VAR_DECL: > + case PARM_DECL: > + case RESULT_DECL: > + *tp = remap_decl (t, &data->id); > + break; > + default: > + break; > + } > + return NULL_TREE; > +} > + > +/* Lower assumption. > + The gimplifier transformed: > + .ASSUME (cond); > + into: > + guard = .ASSUME (); > + if (guard) goto label_true; else label_false; > + label_true:; > + { > + guard = cond; > + } > + label_false:; > + .ASSUME (guard); > + which we should transform into: > + .ASSUME (&artificial_fn, args...); > + where artificial_fn will look like: > + bool artificial_fn (args...) > + { > + guard = cond; > + return guard; > + } > + with any debug stmts in the block removed and jumps out of > + the block or return stmts replaced with return false; */ > + > +static void > +lower_assumption (gimple_stmt_iterator *gsi, struct lower_data *data) > +{ > + gimple *stmt = gsi_stmt (*gsi); > + tree guard = gimple_call_lhs (stmt); > + location_t loc = gimple_location (stmt); > + gcc_assert (guard); > + gsi_remove (gsi, true); > + stmt = gsi_stmt (*gsi); > + gcond *cond = as_a <gcond *> (stmt); > + gcc_assert (gimple_cond_lhs (cond) == guard > + || gimple_cond_rhs (cond) == guard); > + tree l1 = gimple_cond_true_label (cond); > + tree l2 = gimple_cond_false_label (cond); > + gsi_remove (gsi, true); > + stmt = gsi_stmt (*gsi); > + glabel *lab = as_a <glabel *> (stmt); > + gcc_assert (gimple_label_label (lab) == l1 > + || gimple_label_label (lab) == l2); > + gsi_remove (gsi, true); > + gimple *bind = gsi_stmt (*gsi); > + gcc_assert (gimple_code (bind) == GIMPLE_BIND); > + > + lower_assumption_data lad; > + hash_map<tree, tree> decl_map; > + memset (&lad.id, 0, sizeof (lad.id)); > + lad.return_false_label = NULL_TREE; > + lad.id.src_fn = current_function_decl; > + lad.id.dst_fn = create_assumption_fn (loc); > + lad.id.src_cfun = DECL_STRUCT_FUNCTION (lad.id.src_fn); > + lad.id.decl_map = &decl_map; > + lad.id.copy_decl = assumption_copy_decl; > + lad.id.transform_call_graph_edges = CB_CGE_DUPLICATE; > + lad.id.transform_parameter = true; > + lad.id.do_not_unshare = true; > + lad.id.do_not_fold = true; > + cfun->curr_properties = lad.id.src_cfun->curr_properties; > + lad.guard_copy = create_tmp_var (boolean_type_node); > + decl_map.put (lad.guard_copy, lad.guard_copy); > + decl_map.put (guard, lad.guard_copy); > + cfun->assume_function = 1; > + > + struct walk_stmt_info wi; > + memset (&wi, 0, sizeof (wi)); > + wi.info = (void *) &lad; > + walk_gimple_stmt (gsi, find_assumption_locals_r, NULL, &wi); > + unsigned int sz = lad.decls.length (); > + for (unsigned i = 0; i < sz; ++i) > + { > + tree v = lad.decls[i]; > + tree newv; > + if (TREE_CODE (v) == SSA_NAME) > + { > + newv = make_ssa_name (remap_type (TREE_TYPE (v), &lad.id)); > + decl_map.put (v, newv); > + } > + else if (VAR_P (v)) > + { > + if (is_global_var (v) && !DECL_ASSEMBLER_NAME_SET_P (v)) > + DECL_ASSEMBLER_NAME (v); > + TREE_TYPE (v) = remap_type (TREE_TYPE (v), &lad.id); > + DECL_CONTEXT (v) = current_function_decl; > + } > + } > + memset (&wi, 0, sizeof (wi)); > + wi.info = (void *) &lad; > + walk_gimple_stmt (gsi, adjust_assumption_stmt_r, > + adjust_assumption_stmt_op, &wi); > + gsi_remove (gsi, false); > + > + gimple_seq body = NULL; > + gimple *g = gimple_build_assign (lad.guard_copy, boolean_false_node); > + gimple_seq_add_stmt (&body, g); > + gimple_seq_add_stmt (&body, bind); > + greturn *gr = gimple_build_return (lad.guard_copy); > + gimple_seq_add_stmt (&body, gr); > + if (lad.return_false_label) > + { > + g = gimple_build_label (lad.return_false_label); > + gimple_seq_add_stmt (&body, g); > + g = gimple_build_assign (lad.guard_copy, boolean_false_node); > + gimple_seq_add_stmt (&body, g); > + gr = gimple_build_return (lad.guard_copy); > + gimple_seq_add_stmt (&body, gr); > + } > + bind = gimple_build_bind (NULL_TREE, body, NULL_TREE); > + body = NULL; > + gimple_seq_add_stmt (&body, bind); > + gimple_set_body (current_function_decl, body); > + pop_cfun (); > + > + tree parms = NULL_TREE; > + tree parmt = void_list_node; > + auto_vec<tree, 8> vargs; > + vargs.safe_grow (1 + (lad.decls.length () - sz), true); > + vargs[0] = build_fold_addr_expr (lad.id.dst_fn); > + for (unsigned i = lad.decls.length (); i > sz; --i) > + { > + tree *v = decl_map.get (lad.decls[i - 1]); > + gcc_assert (v && TREE_CODE (*v) == PARM_DECL); > + DECL_CHAIN (*v) = parms; > + parms = *v; > + parmt = tree_cons (NULL_TREE, TREE_TYPE (*v), parmt); > + vargs[i - sz] = lad.decls[i - 1]; > + if (is_gimple_reg_type (TREE_TYPE (vargs[i - sz])) > + && !is_gimple_val (vargs[i - sz])) a few comments might be helpful here > + { > + tree t = make_ssa_name (TREE_TYPE (vargs[i - sz])); > + g = gimple_build_assign (t, vargs[i - sz]); > + gsi_insert_before (gsi, g, GSI_SAME_STMT); > + vargs[i - sz] = t; > + } > + } > + DECL_ARGUMENTS (lad.id.dst_fn) = parms; > + TREE_TYPE (lad.id.dst_fn) = build_function_type (boolean_type_node, parmt); ah, here's the type. Maybe use error_mark_node as transitional type? > + cgraph_node::add_new_function (lad.id.dst_fn, false); > + > + for (unsigned i = 0; i < sz; ++i) > + { > + tree v = lad.decls[i]; > + if (TREE_CODE (v) == SSA_NAME) > + release_ssa_name (v); > + } > + > + stmt = gsi_stmt (*gsi); > + lab = as_a <glabel *> (stmt); > + gcc_assert (gimple_label_label (lab) == l1 > + || gimple_label_label (lab) == l2); > + gsi_remove (gsi, true); > + stmt = gsi_stmt (*gsi); > + gcc_assert (gimple_call_internal_p (stmt, IFN_ASSUME) > + && gimple_call_num_args (stmt) == 1 > + && gimple_call_arg (stmt, 0) == guard); > + data->cannot_fallthru = false; > + gcall *call = gimple_build_call_internal_vec (IFN_ASSUME, vargs); > + gimple_set_location (call, loc); > + gsi_replace (gsi, call, true); > +} > > /* Lower statement GSI. DATA is passed through the recursion. We try to > track the fallthruness of statements and get rid of unreachable return > @@ -354,6 +738,13 @@ lower_stmt (gimple_stmt_iterator *gsi, s > tree decl = gimple_call_fndecl (stmt); > unsigned i; > > + if (gimple_call_internal_p (stmt, IFN_ASSUME) > + && gimple_call_num_args (stmt) == 0) > + { > + lower_assumption (gsi, data); > + return; > + } > + > for (i = 0; i < gimple_call_num_args (stmt); i++) > { > tree arg = gimple_call_arg (stmt, i); > --- gcc/tree-ssa-ccp.cc.jj 2022-10-10 11:57:40.203722414 +0200 > +++ gcc/tree-ssa-ccp.cc 2022-10-12 19:48:28.891554716 +0200 > @@ -4253,6 +4253,12 @@ pass_fold_builtins::execute (function *f > } > > callee = gimple_call_fndecl (stmt); > + if (!callee > + && gimple_call_internal_p (stmt, IFN_ASSUME)) > + { > + gsi_remove (&i, true); > + continue; > + } > if (!callee || !fndecl_built_in_p (callee, BUILT_IN_NORMAL)) > { > gsi_next (&i); > --- gcc/lto-streamer-out.cc.jj 2022-10-10 11:57:40.202722428 +0200 > +++ gcc/lto-streamer-out.cc 2022-10-12 19:48:28.891554716 +0200 > @@ -2278,6 +2278,7 @@ output_struct_function_base (struct outp > bp_pack_value (&bp, fn->calls_eh_return, 1); > bp_pack_value (&bp, fn->has_force_vectorize_loops, 1); > bp_pack_value (&bp, fn->has_simduid_loops, 1); > + bp_pack_value (&bp, fn->assume_function, 1); > bp_pack_value (&bp, fn->va_list_fpr_size, 8); > bp_pack_value (&bp, fn->va_list_gpr_size, 8); > bp_pack_value (&bp, fn->last_clique, sizeof (short) * 8); > --- gcc/lto-streamer-in.cc.jj 2022-10-10 11:57:40.201722442 +0200 > +++ gcc/lto-streamer-in.cc 2022-10-12 19:48:28.891554716 +0200 > @@ -1318,6 +1318,7 @@ input_struct_function_base (struct funct > fn->calls_eh_return = bp_unpack_value (&bp, 1); > fn->has_force_vectorize_loops = bp_unpack_value (&bp, 1); > fn->has_simduid_loops = bp_unpack_value (&bp, 1); > + fn->assume_function = bp_unpack_value (&bp, 1); > fn->va_list_fpr_size = bp_unpack_value (&bp, 8); > fn->va_list_gpr_size = bp_unpack_value (&bp, 8); > fn->last_clique = bp_unpack_value (&bp, sizeof (short) * 8); > --- gcc/cgraphunit.cc.jj 2022-10-10 11:57:40.152723125 +0200 > +++ gcc/cgraphunit.cc 2022-10-12 19:48:28.892554703 +0200 > @@ -1882,6 +1882,16 @@ cgraph_node::expand (void) > ggc_collect (); > timevar_pop (TV_REST_OF_COMPILATION); > > + if (DECL_STRUCT_FUNCTION (decl) > + && DECL_STRUCT_FUNCTION (decl)->assume_function) > + { > + /* Assume functions aren't expanded into RTL, on the other side > + we don't want to release their body. */ > + if (cfun) > + pop_cfun (); > + return; > + } > + > /* Make sure that BE didn't give up on compiling. */ > gcc_assert (TREE_ASM_WRITTEN (decl)); > if (cfun) > --- gcc/cfgexpand.cc.jj 2022-10-10 11:57:40.152723125 +0200 > +++ gcc/cfgexpand.cc 2022-10-12 19:48:28.893554689 +0200 > @@ -6597,6 +6597,14 @@ pass_expand::execute (function *fun) > rtx_insn *var_seq, *var_ret_seq; > unsigned i; > > + if (cfun->assume_function) > + { > + /* Assume functions should not be expanded to RTL. */ can we avoid getting here in the first place? I think we don't need any of the post-pass_all_optimizations[_g] passes? > + cfun->curr_properties &= ~PROP_loops; > + loop_optimizer_finalize (); > + return 0; > + } > + > timevar_push (TV_OUT_OF_SSA); > rewrite_out_of_ssa (&SA); > timevar_pop (TV_OUT_OF_SSA); > --- gcc/internal-fn.cc.jj 2022-10-10 11:57:40.166722930 +0200 > +++ gcc/internal-fn.cc 2022-10-12 19:48:28.893554689 +0200 > @@ -4526,5 +4526,4 @@ expand_TRAP (internal_fn, gcall *) > void > expand_ASSUME (internal_fn, gcall *) > { > - gcc_unreachable (); > } > --- gcc/passes.cc.jj 2022-10-10 11:57:40.202722428 +0200 > +++ gcc/passes.cc 2022-10-12 19:48:28.893554689 +0200 > @@ -647,11 +647,12 @@ public: > {} > > /* opt_pass methods: */ > - bool gate (function *) final override > + bool gate (function *fun) final override > { > /* Early return if there were errors. We can run afoul of our > consistency checks, and there's not really much point in fixing them. */ > - return !(rtl_dump_and_exit || flag_syntax_only || seen_error ()); > + return !(rtl_dump_and_exit || fun->assume_function > + || flag_syntax_only || seen_error ()); > } > > }; // class pass_rest_of_compilation > --- gcc/tree-vectorizer.cc.jj 2022-10-10 11:57:40.204722400 +0200 > +++ gcc/tree-vectorizer.cc 2022-10-12 19:48:28.894554675 +0200 > @@ -1213,6 +1213,10 @@ public: > /* opt_pass methods: */ > bool gate (function *fun) final override > { > + /* Vectorization makes range analysis of assume functions > + harder, not easier. */ > + if (fun->assume_function) > + return false; > return flag_tree_loop_vectorize || fun->has_force_vectorize_loops; > } > > @@ -1490,7 +1494,14 @@ public: > > /* opt_pass methods: */ > opt_pass * clone () final override { return new pass_slp_vectorize (m_ctxt); } > - bool gate (function *) final override { return flag_tree_slp_vectorize != 0; } > + bool gate (function *fun) final override > + { > + /* Vectorization makes range analysis of assume functions harder, > + not easier. */ Can we split out these kind of considerations from the initial patch? > + if (fun->assume_function) > + return false; > + return flag_tree_slp_vectorize != 0; > + } > unsigned int execute (function *) final override; > > }; // class pass_slp_vectorize > --- gcc/ipa-icf.cc.jj 2022-10-10 11:57:40.201722442 +0200 > +++ gcc/ipa-icf.cc 2022-10-12 19:48:28.894554675 +0200 > @@ -1517,6 +1517,9 @@ sem_function::parse (cgraph_node *node, > if (!func || (!node->has_gimple_body_p () && !node->thunk)) > return NULL; > > + if (func->assume_function) > + return NULL; > + Do we want implicit noipa attribute on the assume functions? Or do we need to IPA-CP into them? I suppose the ranger code can use contextual code from the .ASSUME call for things like assume (side-effect(), i == 1); > if (lookup_attribute_by_prefix ("omp ", DECL_ATTRIBUTES (node->decl)) != NULL) > return NULL; > > --- gcc/cp/cp-tree.h.jj 2022-10-12 17:51:00.911944744 +0200 > +++ gcc/cp/cp-tree.h 2022-10-12 19:53:17.072615254 +0200 > @@ -8284,6 +8284,7 @@ extern tree predeclare_vla (tree); > extern void clear_fold_cache (void); > extern tree lookup_hotness_attribute (tree); > extern tree process_stmt_hotness_attribute (tree, location_t); > +extern tree build_assume_call (location_t, tree); > extern tree process_stmt_assume_attribute (tree, tree, location_t); > extern bool simple_empty_class_p (tree, tree, tree_code); > extern tree fold_builtin_source_location (location_t); > --- gcc/cp/parser.cc.jj 2022-10-12 17:51:00.951944199 +0200 > +++ gcc/cp/parser.cc 2022-10-12 19:52:15.855452024 +0200 > @@ -46006,11 +46006,7 @@ cp_parser_omp_assumption_clauses (cp_par > if (!type_dependent_expression_p (t)) > t = contextual_conv_bool (t, tf_warning_or_error); > if (is_assume && !error_operand_p (t)) > - { > - t = build_call_expr_internal_loc (eloc, IFN_ASSUME, > - void_type_node, 1, t); > - finish_expr_stmt (t); > - } > + finish_expr_stmt (build_assume_call (eloc, t)); > if (!parens.require_close (parser)) > cp_parser_skip_to_closing_parenthesis (parser, > /*recovering=*/true, > --- gcc/cp/cp-gimplify.cc.jj 2022-10-12 17:51:00.909944772 +0200 > +++ gcc/cp/cp-gimplify.cc 2022-10-12 19:51:47.140844525 +0200 > @@ -3096,6 +3096,17 @@ process_stmt_hotness_attribute (tree std > return std_attrs; > } > > +/* Build IFN_ASSUME internal call for assume condition ARG. */ > + > +tree > +build_assume_call (location_t loc, tree arg) > +{ > + if (!processing_template_decl) > + arg = fold_build_cleanup_point_expr (TREE_TYPE (arg), arg); > + return build_call_expr_internal_loc (loc, IFN_ASSUME, void_type_node, > + 1, arg); > +} > + > /* If [[assume (cond)]] appears on this statement, handle it. */ > > tree > @@ -3132,9 +3143,7 @@ process_stmt_assume_attribute (tree std_ > arg = contextual_conv_bool (arg, tf_warning_or_error); > if (error_operand_p (arg)) > continue; > - statement = build_call_expr_internal_loc (attrs_loc, IFN_ASSUME, > - void_type_node, 1, arg); > - finish_expr_stmt (statement); > + finish_expr_stmt (build_assume_call (attrs_loc, arg)); > } > } > return remove_attribute ("gnu", "assume", std_attrs); > --- gcc/cp/pt.cc.jj 2022-10-12 17:51:00.957944117 +0200 > +++ gcc/cp/pt.cc 2022-10-12 19:52:45.204050862 +0200 > @@ -21116,10 +21116,7 @@ tsubst_copy_and_build (tree t, > ret = error_mark_node; > break; > } > - ret = build_call_expr_internal_loc (EXPR_LOCATION (t), > - IFN_ASSUME, > - void_type_node, 1, > - arg); > + ret = build_assume_call (EXPR_LOCATION (t), arg); > RETURN (ret); > } > break; > --- gcc/testsuite/g++.dg/cpp23/attr-assume5.C.jj 2022-10-12 19:48:28.903554553 +0200 > +++ gcc/testsuite/g++.dg/cpp23/attr-assume5.C 2022-10-12 19:48:28.903554553 +0200 > @@ -0,0 +1,5 @@ > +// P1774R8 - Portable assumptions > +// { dg-do run { target c++11 } } > +// { dg-options "-O2" } > + > +#include "attr-assume1.C" > --- gcc/testsuite/g++.dg/cpp23/attr-assume6.C.jj 2022-10-12 19:48:28.903554553 +0200 > +++ gcc/testsuite/g++.dg/cpp23/attr-assume6.C 2022-10-12 19:48:28.903554553 +0200 > @@ -0,0 +1,5 @@ > +// P1774R8 - Portable assumptions > +// { dg-do run { target c++11 } } > +// { dg-options "-O2" } > + > +#include "attr-assume3.C" > --- gcc/testsuite/g++.dg/cpp23/attr-assume7.C.jj 2022-10-12 19:48:28.903554553 +0200 > +++ gcc/testsuite/g++.dg/cpp23/attr-assume7.C 2022-10-12 19:48:28.903554553 +0200 > @@ -0,0 +1,42 @@ > +// P1774R8 - Portable assumptions > +// { dg-do compile { target c++11 } } > +// { dg-options "-O2" } > + > +int > +foo (int x) > +{ > + [[assume (x == 42)]]; > + return x; > +} > + > +int > +bar (int x) > +{ > + [[assume (++x == 43)]]; > + return x; > +} > + > +int > +baz (int x) > +{ > + [[assume (({ int z = ++x; static int w; ++w; if (z == 51) return -1; if (z == 53) goto lab1; if (z == 64) throw 1; z == 43; }))]]; > +lab1: > + return x; > +} > + > +struct S { S (); S (const S &); ~S (); int a, b; int foo (); }; > + > +int > +qux () > +{ > + S s; > + [[assume (s.a == 42 && s.b == 43)]]; > + return s.a + s.b; > +} > + > +int > +S::foo () > +{ > + [[assume (a == 42 && b == 43)]]; > + return a + b; > +} Reading some of the patch I guessed you wanted to handle nested assumes. So - is [[assume (a == 4 && ([[assume(b == 3)]], b != 2))]] a thing? Thanks, Richard. ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH] middle-end, v3: IFN_ASSUME support [PR106654] 2022-10-14 11:27 ` Richard Biener @ 2022-10-14 18:33 ` Jakub Jelinek 2022-10-17 6:55 ` Richard Biener 2022-10-17 15:44 ` [PATCH] middle-end, v4: " Jakub Jelinek 1 sibling, 1 reply; 35+ messages in thread From: Jakub Jelinek @ 2022-10-14 18:33 UTC (permalink / raw) To: Richard Biener; +Cc: Jason Merrill, Jan Hubicka, gcc-patches On Fri, Oct 14, 2022 at 11:27:07AM +0000, Richard Biener wrote: > > --- gcc/function.h.jj 2022-10-10 11:57:40.163722972 +0200 > > +++ gcc/function.h 2022-10-12 19:48:28.887554771 +0200 > > @@ -438,6 +438,10 @@ struct GTY(()) function { > > > > /* Set if there are any OMP_TARGET regions in the function. */ > > unsigned int has_omp_target : 1; > > + > > + /* Set for artificial function created for [[assume (cond)]]. > > + These should be GIMPLE optimized, but not expanded to RTL. */ > > + unsigned int assume_function : 1; > > I wonder if we should have this along force_output in the symtab > node and let the symtab code decide whether to expand? I actually first had a flag on the symtab node but as the patch shows, when it needs to be tested, more frequently I have access to struct function than to cgraph node. > > --- gcc/gimplify.cc.jj 2022-10-10 11:57:40.165722944 +0200 > > +++ gcc/gimplify.cc 2022-10-12 19:48:28.890554730 +0200 > > @@ -3569,7 +3569,52 @@ gimplify_call_expr (tree *expr_p, gimple > > fndecl, 0)); > > return GS_OK; > > } > > - /* FIXME: Otherwise expand it specially. */ > > + /* If not optimizing, ignore the assumptions. */ > > + if (!optimize) > > + { > > + *expr_p = NULL_TREE; > > + return GS_ALL_DONE; > > + } > > + /* Temporarily, until gimple lowering, transform > > + .ASSUME (cond); > > + into: > > + guard = .ASSUME (); > > + if (guard) goto label_true; else label_false; > > + label_true:; > > + { > > + guard = cond; > > + } > > + label_false:; > > + .ASSUME (guard); > > + such that gimple lowering can outline the condition into > > + a separate function easily. */ > > So the idea to use lambdas and/or nested functions (for OMP) > didn't work out or is more complicated? Yes, that didn't work out. Both lambda creation and nested function handling produce big structures with everything while for the assumptions it is better to have separate scalars if possible, lambda creation has various language imposed restrictions, diagnostics etc. and isn't available in C and I think the outlining in the patch is pretty simple and short. > I wonder if, instead of using the above intermediate form we > can have a new structued GIMPLE code with sub-statements > > .ASSUME > { > condition; > } That is what I wrote in the patch description as alternative: "with the condition wrapped into a GIMPLE_BIND (I admit the above isn't extra clean but it is just something to hold it from gimplifier until gimple low pass; it reassembles if (condition_never_true) { cond; }; an alternative would be introduce GOMP_ASSUME statement that would have the guard var as operand and the GIMPLE_BIND as body, but for the few passes (tree-nested and omp lowering) in between that looked like an overkill to me)" I can certainly implement that easily. > ? There's gimple_statement_omp conveniently available as base and > IIRC you had the requirement to implement some OMP assume as well? For OpenMP assumptions we right now implement just the holds clause of assume and implement it the same way as assume/gnu::assume attributes. > Of ocurse a different stmt class with body would work as well here, > maybe we can even use a gbind with a special flag. > > The outlining code can then be ajusted to outline a single BIND? It already is adjusting a single bind (of course with everything nested in it). > It probably won't simplify much that way. > > +static tree > > +create_assumption_fn (location_t loc) > > +{ > > + tree name = clone_function_name_numbered (current_function_decl, "_assume"); > > + /* For now, will be changed later. */ > > ? I need to create the FUNCTION_DECL early and only later on discover the used automatic vars (for which I need the destination function) and only once those are discovered I can create the right function type for the function. > > + tree type = TREE_TYPE (current_function_decl); > > + DECL_FUNCTION_SPECIFIC_OPTIMIZATION (decl) > > + = DECL_FUNCTION_SPECIFIC_OPTIMIZATION (current_function_decl); > > + DECL_FUNCTION_SPECIFIC_TARGET (decl) > > + = DECL_FUNCTION_SPECIFIC_TARGET (current_function_decl); > > + DECL_FUNCTION_VERSIONED (decl) > > + = DECL_FUNCTION_VERSIONED (current_function_decl); > > what does it mean to copy DECL_FUNCTION_VERSIONED here? This was a copy and paste from elsewhere (I think OpenMP code). I guess I can nuke it and even better add some testcase coverage for various nasty things like assume in multi-versioned functions. > > + DECL_ARGUMENTS (lad.id.dst_fn) = parms; > > + TREE_TYPE (lad.id.dst_fn) = build_function_type (boolean_type_node, parmt); > > ah, here's the type. Maybe use error_mark_node as transitional type? Will see if that works. > > > + cgraph_node::add_new_function (lad.id.dst_fn, false); > > + > > + for (unsigned i = 0; i < sz; ++i) > > + { > > + tree v = lad.decls[i]; > > + if (TREE_CODE (v) == SSA_NAME) > > + release_ssa_name (v); > > + } > > + > > + stmt = gsi_stmt (*gsi); > > + lab = as_a <glabel *> (stmt); > > + gcc_assert (gimple_label_label (lab) == l1 > > + || gimple_label_label (lab) == l2); > > + gsi_remove (gsi, true); > > + stmt = gsi_stmt (*gsi); > > + gcc_assert (gimple_call_internal_p (stmt, IFN_ASSUME) > > + && gimple_call_num_args (stmt) == 1 > > + && gimple_call_arg (stmt, 0) == guard); > > + data->cannot_fallthru = false; > > + gcall *call = gimple_build_call_internal_vec (IFN_ASSUME, vargs); > > + gimple_set_location (call, loc); > > + gsi_replace (gsi, call, true); > > +} > > > > /* Lower statement GSI. DATA is passed through the recursion. We try to > > track the fallthruness of statements and get rid of unreachable return > > @@ -354,6 +738,13 @@ lower_stmt (gimple_stmt_iterator *gsi, s > > tree decl = gimple_call_fndecl (stmt); > > unsigned i; > > > > + if (gimple_call_internal_p (stmt, IFN_ASSUME) > > + && gimple_call_num_args (stmt) == 0) > > + { > > + lower_assumption (gsi, data); > > + return; > > + } > > + > > for (i = 0; i < gimple_call_num_args (stmt); i++) > > { > > tree arg = gimple_call_arg (stmt, i); > > --- gcc/tree-ssa-ccp.cc.jj 2022-10-10 11:57:40.203722414 +0200 > > +++ gcc/tree-ssa-ccp.cc 2022-10-12 19:48:28.891554716 +0200 > > @@ -4253,6 +4253,12 @@ pass_fold_builtins::execute (function *f > > } > > > > callee = gimple_call_fndecl (stmt); > > + if (!callee > > + && gimple_call_internal_p (stmt, IFN_ASSUME)) > > + { > > + gsi_remove (&i, true); > > + continue; > > + } > > if (!callee || !fndecl_built_in_p (callee, BUILT_IN_NORMAL)) > > { > > gsi_next (&i); > > --- gcc/lto-streamer-out.cc.jj 2022-10-10 11:57:40.202722428 +0200 > > +++ gcc/lto-streamer-out.cc 2022-10-12 19:48:28.891554716 +0200 > > @@ -2278,6 +2278,7 @@ output_struct_function_base (struct outp > > bp_pack_value (&bp, fn->calls_eh_return, 1); > > bp_pack_value (&bp, fn->has_force_vectorize_loops, 1); > > bp_pack_value (&bp, fn->has_simduid_loops, 1); > > + bp_pack_value (&bp, fn->assume_function, 1); > > bp_pack_value (&bp, fn->va_list_fpr_size, 8); > > bp_pack_value (&bp, fn->va_list_gpr_size, 8); > > bp_pack_value (&bp, fn->last_clique, sizeof (short) * 8); > > --- gcc/lto-streamer-in.cc.jj 2022-10-10 11:57:40.201722442 +0200 > > +++ gcc/lto-streamer-in.cc 2022-10-12 19:48:28.891554716 +0200 > > @@ -1318,6 +1318,7 @@ input_struct_function_base (struct funct > > fn->calls_eh_return = bp_unpack_value (&bp, 1); > > fn->has_force_vectorize_loops = bp_unpack_value (&bp, 1); > > fn->has_simduid_loops = bp_unpack_value (&bp, 1); > > + fn->assume_function = bp_unpack_value (&bp, 1); > > fn->va_list_fpr_size = bp_unpack_value (&bp, 8); > > fn->va_list_gpr_size = bp_unpack_value (&bp, 8); > > fn->last_clique = bp_unpack_value (&bp, sizeof (short) * 8); > > --- gcc/cgraphunit.cc.jj 2022-10-10 11:57:40.152723125 +0200 > > +++ gcc/cgraphunit.cc 2022-10-12 19:48:28.892554703 +0200 > > @@ -1882,6 +1882,16 @@ cgraph_node::expand (void) > > ggc_collect (); > > timevar_pop (TV_REST_OF_COMPILATION); > > > > + if (DECL_STRUCT_FUNCTION (decl) > > + && DECL_STRUCT_FUNCTION (decl)->assume_function) > > + { > > + /* Assume functions aren't expanded into RTL, on the other side > > + we don't want to release their body. */ > > + if (cfun) > > + pop_cfun (); > > + return; > > + } > > + > > /* Make sure that BE didn't give up on compiling. */ > > gcc_assert (TREE_ASM_WRITTEN (decl)); > > if (cfun) > > --- gcc/cfgexpand.cc.jj 2022-10-10 11:57:40.152723125 +0200 > > +++ gcc/cfgexpand.cc 2022-10-12 19:48:28.893554689 +0200 > > @@ -6597,6 +6597,14 @@ pass_expand::execute (function *fun) > > rtx_insn *var_seq, *var_ret_seq; > > unsigned i; > > > > + if (cfun->assume_function) > > + { > > + /* Assume functions should not be expanded to RTL. */ > > can we avoid getting here in the first place? I think we don't need > any of the post-pass_all_optimizations[_g] passes? I'm afraid not without revamping passes.def, because to easily cat the pass queue from certain point onwards, we need all the remaining passes to be wrapped with PUSH_INSERT_PASSES_WITHIN. So, if we e.g. wanted to cut out everything from pass_tm_init onwards, we'd need to wrap: NEXT_PASS (pass_tm_init); PUSH_INSERT_PASSES_WITHIN (pass_tm_init) NEXT_PASS (pass_tm_mark); NEXT_PASS (pass_tm_memopt); NEXT_PASS (pass_tm_edges); POP_INSERT_PASSES () NEXT_PASS (pass_simduid_cleanup); NEXT_PASS (pass_vtable_verify); NEXT_PASS (pass_lower_vaarg); NEXT_PASS (pass_lower_vector); NEXT_PASS (pass_lower_complex_O0); NEXT_PASS (pass_sancov_O0); NEXT_PASS (pass_lower_switch_O0); NEXT_PASS (pass_asan_O0); NEXT_PASS (pass_tsan_O0); NEXT_PASS (pass_sanopt); NEXT_PASS (pass_cleanup_eh); NEXT_PASS (pass_lower_resx); NEXT_PASS (pass_nrv); NEXT_PASS (pass_gimple_isel); NEXT_PASS (pass_harden_conditional_branches); NEXT_PASS (pass_harden_compares); NEXT_PASS (pass_warn_access, /*early=*/false); NEXT_PASS (pass_cleanup_cfg_post_optimizing); NEXT_PASS (pass_warn_function_noreturn); NEXT_PASS (pass_expand); in some wrapper pass with a gate (either also including pass_rest_of_compilation but that would mean undesirable reindentation of everything there, or just the above ones and have assume_function punt in the 2 or 1 gates). What I had in the patch was just skip pass_expand and pass_rest_of_compilation, perhaps another possibility to do the former would be to define a gate on pass_expand. > > --- gcc/tree-vectorizer.cc.jj 2022-10-10 11:57:40.204722400 +0200 > > +++ gcc/tree-vectorizer.cc 2022-10-12 19:48:28.894554675 +0200 > > @@ -1213,6 +1213,10 @@ public: > > /* opt_pass methods: */ > > bool gate (function *fun) final override > > { > > + /* Vectorization makes range analysis of assume functions > > + harder, not easier. */ > > + if (fun->assume_function) > > + return false; > > return flag_tree_loop_vectorize || fun->has_force_vectorize_loops; > > } > > > > @@ -1490,7 +1494,14 @@ public: > > > > /* opt_pass methods: */ > > opt_pass * clone () final override { return new pass_slp_vectorize (m_ctxt); } > > - bool gate (function *) final override { return flag_tree_slp_vectorize != 0; } > > + bool gate (function *fun) final override > > + { > > + /* Vectorization makes range analysis of assume functions harder, > > + not easier. */ > > Can we split out these kind of considerations from the initial patch? Sure. > > > + if (fun->assume_function) > > + return false; > > + return flag_tree_slp_vectorize != 0; > > + } > > unsigned int execute (function *) final override; > > > > }; // class pass_slp_vectorize > > --- gcc/ipa-icf.cc.jj 2022-10-10 11:57:40.201722442 +0200 > > +++ gcc/ipa-icf.cc 2022-10-12 19:48:28.894554675 +0200 > > @@ -1517,6 +1517,9 @@ sem_function::parse (cgraph_node *node, > > if (!func || (!node->has_gimple_body_p () && !node->thunk)) > > return NULL; > > > > + if (func->assume_function) > > + return NULL; > > + > > Do we want implicit noipa attribute on the assume functions? Or do > we need to IPA-CP into them? I suppose the ranger code can use > contextual code from the .ASSUME call for things like > assume (side-effect(), i == 1); Most of normal IPA optimizations are disabled for them because they aren't normally called, all we do is take their address and pass it to .ASSUME. IPA-ICF was an exception and I had to disable it because when it triggered it decided to create a thunk which failed to assemble. But implicit noipa surely is an option; though of course we want inlining etc. to happen into those functions. And eventually some kind of IPA SRA of their arguments but with different behavior from normal IPA-SRA. > Reading some of the patch I guessed you wanted to handle nested > assumes. So - is > > [[assume (a == 4 && ([[assume(b == 3)]], b != 2))]] > > a thing? This is not valid, assume can be just on an empty statement. But with GNU statement expressions it is a thing and I should add it to testsuite coverage. void foo (int a, int b) { [[assume (a == 4 && ({ [[assume (b == 3)]]; b != 2 }))]]; } is valid. I think the code should handle it fine (outline the outer and the new outlined function enters pass queue with all_lowering_passes and so will see pass_lower_cf again and hopefully work. Will see how it goes when I tweak the patch. Jakub ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH] middle-end, v3: IFN_ASSUME support [PR106654] 2022-10-14 18:33 ` Jakub Jelinek @ 2022-10-17 6:55 ` Richard Biener 0 siblings, 0 replies; 35+ messages in thread From: Richard Biener @ 2022-10-17 6:55 UTC (permalink / raw) To: Jakub Jelinek; +Cc: Jason Merrill, Jan Hubicka, gcc-patches On Fri, 14 Oct 2022, Jakub Jelinek wrote: > On Fri, Oct 14, 2022 at 11:27:07AM +0000, Richard Biener wrote: > > > --- gcc/function.h.jj 2022-10-10 11:57:40.163722972 +0200 > > > +++ gcc/function.h 2022-10-12 19:48:28.887554771 +0200 > > > @@ -438,6 +438,10 @@ struct GTY(()) function { > > > > > > /* Set if there are any OMP_TARGET regions in the function. */ > > > unsigned int has_omp_target : 1; > > > + > > > + /* Set for artificial function created for [[assume (cond)]]. > > > + These should be GIMPLE optimized, but not expanded to RTL. */ > > > + unsigned int assume_function : 1; > > > > I wonder if we should have this along force_output in the symtab > > node and let the symtab code decide whether to expand? > > I actually first had a flag on the symtab node but as the patch shows, > when it needs to be tested, more frequently I have access to struct function > than to cgraph node. I see. > > > --- gcc/gimplify.cc.jj 2022-10-10 11:57:40.165722944 +0200 > > > +++ gcc/gimplify.cc 2022-10-12 19:48:28.890554730 +0200 > > > @@ -3569,7 +3569,52 @@ gimplify_call_expr (tree *expr_p, gimple > > > fndecl, 0)); > > > return GS_OK; > > > } > > > - /* FIXME: Otherwise expand it specially. */ > > > + /* If not optimizing, ignore the assumptions. */ > > > + if (!optimize) > > > + { > > > + *expr_p = NULL_TREE; > > > + return GS_ALL_DONE; > > > + } > > > + /* Temporarily, until gimple lowering, transform > > > + .ASSUME (cond); > > > + into: > > > + guard = .ASSUME (); > > > + if (guard) goto label_true; else label_false; > > > + label_true:; > > > + { > > > + guard = cond; > > > + } > > > + label_false:; > > > + .ASSUME (guard); > > > + such that gimple lowering can outline the condition into > > > + a separate function easily. */ > > > > So the idea to use lambdas and/or nested functions (for OMP) > > didn't work out or is more complicated? > > Yes, that didn't work out. Both lambda creation and nested function > handling produce big structures with everything while for the assumptions > it is better to have separate scalars if possible, lambda creation has > various language imposed restrictions, diagnostics etc. and isn't > available in C and I think the outlining in the patch is pretty simple and > short. > > > I wonder if, instead of using the above intermediate form we > > can have a new structued GIMPLE code with sub-statements > > > > .ASSUME > > { > > condition; > > } > > That is what I wrote in the patch description as alternative: > "with the condition wrapped into a GIMPLE_BIND (I admit the above isn't > extra clean but it is just something to hold it from gimplifier until > gimple low pass; it reassembles if (condition_never_true) { cond; }; > an alternative would be introduce GOMP_ASSUME statement that would have > the guard var as operand and the GIMPLE_BIND as body, but for the > few passes (tree-nested and omp lowering) in between that looked like > an overkill to me)" > I can certainly implement that easily. I'd prefer that, it looks possibly less messy. > > ? There's gimple_statement_omp conveniently available as base and > > IIRC you had the requirement to implement some OMP assume as well? > > For OpenMP assumptions we right now implement just the holds clause > of assume and implement it the same way as assume/gnu::assume attributes. > > > Of ocurse a different stmt class with body would work as well here, > > maybe we can even use a gbind with a special flag. > > > > The outlining code can then be ajusted to outline a single BIND? > > It already is adjusting a single bind (of course with everything nested in > it). > > > It probably won't simplify much that way. > > > > +static tree > > > +create_assumption_fn (location_t loc) > > > +{ > > > + tree name = clone_function_name_numbered (current_function_decl, "_assume"); > > > + /* For now, will be changed later. */ > > > > ? > > I need to create the FUNCTION_DECL early and only later on discover > the used automatic vars (for which I need the destination function) > and only once those are discovered I can create the right > function type for the function. > > > > + tree type = TREE_TYPE (current_function_decl); > > > > + DECL_FUNCTION_SPECIFIC_OPTIMIZATION (decl) > > > + = DECL_FUNCTION_SPECIFIC_OPTIMIZATION (current_function_decl); > > > + DECL_FUNCTION_SPECIFIC_TARGET (decl) > > > + = DECL_FUNCTION_SPECIFIC_TARGET (current_function_decl); > > > + DECL_FUNCTION_VERSIONED (decl) > > > + = DECL_FUNCTION_VERSIONED (current_function_decl); > > > > what does it mean to copy DECL_FUNCTION_VERSIONED here? > > This was a copy and paste from elsewhere (I think OpenMP code). > I guess I can nuke it and even better add some testcase coverage > for various nasty things like assume in multi-versioned functions. > > > > + DECL_ARGUMENTS (lad.id.dst_fn) = parms; > > > + TREE_TYPE (lad.id.dst_fn) = build_function_type (boolean_type_node, parmt); > > > > ah, here's the type. Maybe use error_mark_node as transitional type? > > Will see if that works. > > > > > + cgraph_node::add_new_function (lad.id.dst_fn, false); > > > + > > > + for (unsigned i = 0; i < sz; ++i) > > > + { > > > + tree v = lad.decls[i]; > > > + if (TREE_CODE (v) == SSA_NAME) > > > + release_ssa_name (v); > > > + } > > > + > > > + stmt = gsi_stmt (*gsi); > > > + lab = as_a <glabel *> (stmt); > > > + gcc_assert (gimple_label_label (lab) == l1 > > > + || gimple_label_label (lab) == l2); > > > + gsi_remove (gsi, true); > > > + stmt = gsi_stmt (*gsi); > > > + gcc_assert (gimple_call_internal_p (stmt, IFN_ASSUME) > > > + && gimple_call_num_args (stmt) == 1 > > > + && gimple_call_arg (stmt, 0) == guard); > > > + data->cannot_fallthru = false; > > > + gcall *call = gimple_build_call_internal_vec (IFN_ASSUME, vargs); > > > + gimple_set_location (call, loc); > > > + gsi_replace (gsi, call, true); > > > +} > > > > > > /* Lower statement GSI. DATA is passed through the recursion. We try to > > > track the fallthruness of statements and get rid of unreachable return > > > @@ -354,6 +738,13 @@ lower_stmt (gimple_stmt_iterator *gsi, s > > > tree decl = gimple_call_fndecl (stmt); > > > unsigned i; > > > > > > + if (gimple_call_internal_p (stmt, IFN_ASSUME) > > > + && gimple_call_num_args (stmt) == 0) > > > + { > > > + lower_assumption (gsi, data); > > > + return; > > > + } > > > + > > > for (i = 0; i < gimple_call_num_args (stmt); i++) > > > { > > > tree arg = gimple_call_arg (stmt, i); > > > --- gcc/tree-ssa-ccp.cc.jj 2022-10-10 11:57:40.203722414 +0200 > > > +++ gcc/tree-ssa-ccp.cc 2022-10-12 19:48:28.891554716 +0200 > > > @@ -4253,6 +4253,12 @@ pass_fold_builtins::execute (function *f > > > } > > > > > > callee = gimple_call_fndecl (stmt); > > > + if (!callee > > > + && gimple_call_internal_p (stmt, IFN_ASSUME)) > > > + { > > > + gsi_remove (&i, true); > > > + continue; > > > + } > > > if (!callee || !fndecl_built_in_p (callee, BUILT_IN_NORMAL)) > > > { > > > gsi_next (&i); > > > --- gcc/lto-streamer-out.cc.jj 2022-10-10 11:57:40.202722428 +0200 > > > +++ gcc/lto-streamer-out.cc 2022-10-12 19:48:28.891554716 +0200 > > > @@ -2278,6 +2278,7 @@ output_struct_function_base (struct outp > > > bp_pack_value (&bp, fn->calls_eh_return, 1); > > > bp_pack_value (&bp, fn->has_force_vectorize_loops, 1); > > > bp_pack_value (&bp, fn->has_simduid_loops, 1); > > > + bp_pack_value (&bp, fn->assume_function, 1); > > > bp_pack_value (&bp, fn->va_list_fpr_size, 8); > > > bp_pack_value (&bp, fn->va_list_gpr_size, 8); > > > bp_pack_value (&bp, fn->last_clique, sizeof (short) * 8); > > > --- gcc/lto-streamer-in.cc.jj 2022-10-10 11:57:40.201722442 +0200 > > > +++ gcc/lto-streamer-in.cc 2022-10-12 19:48:28.891554716 +0200 > > > @@ -1318,6 +1318,7 @@ input_struct_function_base (struct funct > > > fn->calls_eh_return = bp_unpack_value (&bp, 1); > > > fn->has_force_vectorize_loops = bp_unpack_value (&bp, 1); > > > fn->has_simduid_loops = bp_unpack_value (&bp, 1); > > > + fn->assume_function = bp_unpack_value (&bp, 1); > > > fn->va_list_fpr_size = bp_unpack_value (&bp, 8); > > > fn->va_list_gpr_size = bp_unpack_value (&bp, 8); > > > fn->last_clique = bp_unpack_value (&bp, sizeof (short) * 8); > > > --- gcc/cgraphunit.cc.jj 2022-10-10 11:57:40.152723125 +0200 > > > +++ gcc/cgraphunit.cc 2022-10-12 19:48:28.892554703 +0200 > > > @@ -1882,6 +1882,16 @@ cgraph_node::expand (void) > > > ggc_collect (); > > > timevar_pop (TV_REST_OF_COMPILATION); > > > > > > + if (DECL_STRUCT_FUNCTION (decl) > > > + && DECL_STRUCT_FUNCTION (decl)->assume_function) > > > + { > > > + /* Assume functions aren't expanded into RTL, on the other side > > > + we don't want to release their body. */ > > > + if (cfun) > > > + pop_cfun (); > > > + return; > > > + } > > > + > > > /* Make sure that BE didn't give up on compiling. */ > > > gcc_assert (TREE_ASM_WRITTEN (decl)); > > > if (cfun) > > > --- gcc/cfgexpand.cc.jj 2022-10-10 11:57:40.152723125 +0200 > > > +++ gcc/cfgexpand.cc 2022-10-12 19:48:28.893554689 +0200 > > > @@ -6597,6 +6597,14 @@ pass_expand::execute (function *fun) > > > rtx_insn *var_seq, *var_ret_seq; > > > unsigned i; > > > > > > + if (cfun->assume_function) > > > + { > > > + /* Assume functions should not be expanded to RTL. */ > > > > can we avoid getting here in the first place? I think we don't need > > any of the post-pass_all_optimizations[_g] passes? > > I'm afraid not without revamping passes.def, because > to easily cat the pass queue from certain point onwards, we > need all the remaining passes to be wrapped with > PUSH_INSERT_PASSES_WITHIN. > So, if we e.g. wanted to cut out everything from pass_tm_init > onwards, we'd need to wrap: > NEXT_PASS (pass_tm_init); > PUSH_INSERT_PASSES_WITHIN (pass_tm_init) > NEXT_PASS (pass_tm_mark); > NEXT_PASS (pass_tm_memopt); > NEXT_PASS (pass_tm_edges); > POP_INSERT_PASSES () > NEXT_PASS (pass_simduid_cleanup); > NEXT_PASS (pass_vtable_verify); > NEXT_PASS (pass_lower_vaarg); > NEXT_PASS (pass_lower_vector); > NEXT_PASS (pass_lower_complex_O0); > NEXT_PASS (pass_sancov_O0); > NEXT_PASS (pass_lower_switch_O0); > NEXT_PASS (pass_asan_O0); > NEXT_PASS (pass_tsan_O0); > NEXT_PASS (pass_sanopt); > NEXT_PASS (pass_cleanup_eh); > NEXT_PASS (pass_lower_resx); > NEXT_PASS (pass_nrv); > NEXT_PASS (pass_gimple_isel); > NEXT_PASS (pass_harden_conditional_branches); > NEXT_PASS (pass_harden_compares); > NEXT_PASS (pass_warn_access, /*early=*/false); > NEXT_PASS (pass_cleanup_cfg_post_optimizing); > NEXT_PASS (pass_warn_function_noreturn); > > NEXT_PASS (pass_expand); > in some wrapper pass with a gate (either also including > pass_rest_of_compilation but that would mean undesirable > reindentation of everything there, or just > the above ones and have assume_function punt in the 2 > or 1 gates). Ah, they are all in all_passes :/ Maybe we can add something like TODO_discard_function (or a property) that will not discard the function but stop compiling it? I wonder if all cleanup is properly done for the function - I suppose we want to keep the body around for callers indefinitely. > What I had in the patch was just skip pass_expand > and pass_rest_of_compilation, perhaps another possibility > to do the former would be to define a gate on pass_expand. Or some gate in the pass manager, like in override_gate_status check fun->properties & PROP_suspended and have some pass_suspend_assume add that property for all assume function bodies. In case you like any of the above give it a shot, otherwise what you have isn't too bad, I just wondered if there's a nicer way. > > > --- gcc/tree-vectorizer.cc.jj 2022-10-10 11:57:40.204722400 +0200 > > > +++ gcc/tree-vectorizer.cc 2022-10-12 19:48:28.894554675 +0200 > > > @@ -1213,6 +1213,10 @@ public: > > > /* opt_pass methods: */ > > > bool gate (function *fun) final override > > > { > > > + /* Vectorization makes range analysis of assume functions > > > + harder, not easier. */ > > > + if (fun->assume_function) > > > + return false; > > > return flag_tree_loop_vectorize || fun->has_force_vectorize_loops; > > > } > > > > > > @@ -1490,7 +1494,14 @@ public: > > > > > > /* opt_pass methods: */ > > > opt_pass * clone () final override { return new pass_slp_vectorize (m_ctxt); } > > > - bool gate (function *) final override { return flag_tree_slp_vectorize != 0; } > > > + bool gate (function *fun) final override > > > + { > > > + /* Vectorization makes range analysis of assume functions harder, > > > + not easier. */ > > > > Can we split out these kind of considerations from the initial patch? > > Sure. > > > > > + if (fun->assume_function) > > > + return false; > > > + return flag_tree_slp_vectorize != 0; > > > + } > > > unsigned int execute (function *) final override; > > > > > > }; // class pass_slp_vectorize > > > --- gcc/ipa-icf.cc.jj 2022-10-10 11:57:40.201722442 +0200 > > > +++ gcc/ipa-icf.cc 2022-10-12 19:48:28.894554675 +0200 > > > @@ -1517,6 +1517,9 @@ sem_function::parse (cgraph_node *node, > > > if (!func || (!node->has_gimple_body_p () && !node->thunk)) > > > return NULL; > > > > > > + if (func->assume_function) > > > + return NULL; > > > + > > > > Do we want implicit noipa attribute on the assume functions? Or do > > we need to IPA-CP into them? I suppose the ranger code can use > > contextual code from the .ASSUME call for things like > > assume (side-effect(), i == 1); > > Most of normal IPA optimizations are disabled for them because > they aren't normally called, all we do is take their address > and pass it to .ASSUME. IPA-ICF was an exception and I had to > disable it because when it triggered it decided to create a thunk > which failed to assemble. > But implicit noipa surely is an option; though of course we want > inlining etc. to happen into those functions. > And eventually some kind of IPA SRA of their arguments but with > different behavior from normal IPA-SRA. I suppose for now adding noipa is easiest, we'd still inline into the body of course. > > Reading some of the patch I guessed you wanted to handle nested > > assumes. So - is > > > > [[assume (a == 4 && ([[assume(b == 3)]], b != 2))]] > > > > a thing? > > This is not valid, assume can be just on an empty statement. > But with GNU statement expressions it is a thing and I should add it to > testsuite coverage. > > void > foo (int a, int b) > { > [[assume (a == 4 && ({ [[assume (b == 3)]]; b != 2 }))]]; > } > is valid. > I think the code should handle it fine (outline the outer and the new > outlined function enters pass queue with all_lowering_passes > and so will see pass_lower_cf again and hopefully work. Will see > how it goes when I tweak the patch. Thanks, Richard. ^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH] middle-end, v4: IFN_ASSUME support [PR106654] 2022-10-14 11:27 ` Richard Biener 2022-10-14 18:33 ` Jakub Jelinek @ 2022-10-17 15:44 ` Jakub Jelinek 2022-10-18 7:00 ` Richard Biener 2022-10-18 21:31 ` Andrew MacLeod 1 sibling, 2 replies; 35+ messages in thread From: Jakub Jelinek @ 2022-10-17 15:44 UTC (permalink / raw) To: Richard Biener; +Cc: Jason Merrill, Jan Hubicka, gcc-patches Hi! On Mon, Oct 17, 2022 at 06:55:40AM +0000, Richard Biener wrote: > > That is what I wrote in the patch description as alternative: > > "with the condition wrapped into a GIMPLE_BIND (I admit the above isn't > > extra clean but it is just something to hold it from gimplifier until > > gimple low pass; it reassembles if (condition_never_true) { cond; }; > > an alternative would be introduce GOMP_ASSUME statement that would have > > the guard var as operand and the GIMPLE_BIND as body, but for the > > few passes (tree-nested and omp lowering) in between that looked like > > an overkill to me)" > > I can certainly implement that easily. > > I'd prefer that, it looks possibly less messy. Ok, introduced GIMPLE_ASSUME for this then. > Ah, they are all in all_passes :/ Maybe we can add something > like TODO_discard_function (or a property) that will not discard > the function but stop compiling it? I wonder if all cleanup > is properly done for the function - I suppose we want to keep the > body around for callers indefinitely. > > > What I had in the patch was just skip pass_expand > > and pass_rest_of_compilation, perhaps another possibility > > to do the former would be to define a gate on pass_expand. > > Or some gate in the pass manager, like in override_gate_status > check fun->properties & PROP_suspended and have some > pass_suspend_assume add that property for all assume function > bodies. > > In case you like any of the above give it a shot, otherwise what > you have isn't too bad, I just wondered if there's a nicer way. Turns out we already had TODO_discard_function, so I've reused it with the detail that assume function's bodies aren't actually dropped, and a new pass which returns that and where I'd like to have the backwards range walk implemented. > I suppose for now adding noipa is easiest, we'd still inline into > the body of course. Ok, done. On Fri, Oct 14, 2022 at 11:27:07AM +0000, Richard Biener wrote: > > @@ -237,6 +244,383 @@ lower_omp_directive (gimple_stmt_iterato > > gsi_next (gsi); > > } > > comment missing Added. > > +static tree > > +create_assumption_fn (location_t loc) > > +{ > > + tree name = clone_function_name_numbered (current_function_decl, "_assume"); > > + /* For now, will be changed later. */ > > ? I've tried error_mark_node as the type, but that didn't work out, get various ICEs with it even for the short time before it is fixed up. But changed that to tree type = build_varargs_function_type_list (boolean_type_node, NULL_TREE); which is closer to what it ends up later. > > + DECL_FUNCTION_VERSIONED (decl) > > + = DECL_FUNCTION_VERSIONED (current_function_decl); > > what does it mean to copy DECL_FUNCTION_VERSIONED here? Dropped. > > + && !is_gimple_val (vargs[i - sz])) > > a few comments might be helpful here Added some to various places in that function. > > @@ -1490,7 +1494,14 @@ public: > > > > /* opt_pass methods: */ > > opt_pass * clone () final override { return new pass_slp_vectorize (m_ctxt); } > > - bool gate (function *) final override { return flag_tree_slp_vectorize != 0; } > > + bool gate (function *fun) final override > > + { > > + /* Vectorization makes range analysis of assume functions harder, > > + not easier. */ > > Can we split out these kind of considerations from the initial patch? Dropped for now. > Reading some of the patch I guessed you wanted to handle nested > assumes. So - is > > [[assume (a == 4 && ([[assume(b == 3)]], b != 2))]] > > a thing? Added 2 tests for nested assumptions (one with a simple assumption nested in complex one, one with side-effects one nested in complex one). So far lightly tested, will test fully overnight. 2022-10-17 Jakub Jelinek <jakub@redhat.com> PR c++/106654 gcc/ * gimple.def (GIMPLE_ASSUME): New statement kind. * gimple.h (struct gimple_statement_assume): New type. (is_a_helper <gimple_statement_assume *>::test, is_a_helper <const gimple_statement_assume *>::test): New. (gimple_build_assume): Declare. (gimple_has_substatements): Return true for GIMPLE_ASSUME. (gimple_assume_guard, gimple_assume_set_guard, gimple_assume_guard_ptr, gimple_assume_body_ptr, gimple_assume_body): New inline functions. * gsstruct.def (GSS_ASSUME): New. * gimple.cc (gimple_build_assume): New function. (gimple_copy): Handle GIMPLE_ASSUME. * gimple-pretty-print.cc (dump_gimple_assume): New function. (pp_gimple_stmt_1): Handle GIMPLE_ASSUME. * gimple-walk.cc (walk_gimple_op): Handle GIMPLE_ASSUME. * omp-low.cc (WALK_SUBSTMTS): Likewise. (lower_omp_1): Likewise. * omp-oacc-kernels-decompose.cc (adjust_region_code_walk_stmt_fn): Likewise. * tree-cfg.cc (verify_gimple_stmt, verify_gimple_in_seq_2): Likewise. * function.h (struct function): Add assume_function bitfield. * gimplify.cc (gimplify_call_expr): If the assumption isn't simple enough, expand it into GIMPLE_ASSUME wrapped block or for -O0 drop it. * gimple-low.cc: Include attribs.h. (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 GIMPLE_ASSUME. * 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. (symbol_table::compile): Allow assume functions not to have released body. * internal-fn.cc (expand_ASSUME): Remove gcc_unreachable. * passes.cc (execute_one_pass): For TODO_discard_function don't release body of assume functions. * cgraph.cc (cgraph_node::verify_node): Don't verify cgraph nodes of PROP_assumptions_done functions. * tree-pass.h (PROP_assumptions_done): Define. (TODO_discard_function): Adjust comment. (make_pass_assumptions): Declare. * passes.def (pass_assumptions): Add. * tree-vrp.cc (pass_data_assumptions): New variable. (pass_assumptions): New class. (make_pass_assumptions): New function. gcc/cp/ * cp-tree.h (build_assume_call): Declare. * parser.cc (cp_parser_omp_assumption_clauses): Use build_assume_call. * cp-gimplify.cc (build_assume_call): New function. (process_stmt_assume_attribute): Use build_assume_call. * pt.cc (tsubst_copy_and_build): Likewise. gcc/testsuite/ * g++.dg/cpp23/attr-assume5.C: New test. * g++.dg/cpp23/attr-assume6.C: New test. * g++.dg/cpp23/attr-assume7.C: New test. --- gcc/gimple.def.jj 2022-05-30 14:07:01.989306541 +0200 +++ gcc/gimple.def 2022-10-17 12:47:55.323049825 +0200 @@ -406,3 +406,8 @@ DEFGSCODE(GIMPLE_PREDICT, "gimple_predic This tuple should not exist outside of the gimplifier proper. */ DEFGSCODE(GIMPLE_WITH_CLEANUP_EXPR, "gimple_with_cleanup_expr", GSS_WCE) + +/* GIMPLE_ASSUME <GUARD, BODY> represents [[assume(cond)]]. + BODY is the GIMPLE_BIND with the condition which sets GUARD to true + (otherwise UB). */ +DEFGSCODE(GIMPLE_ASSUME, "gimple_assume", GSS_ASSUME) --- gcc/gimple.h.jj 2022-09-06 09:19:14.718561564 +0200 +++ gcc/gimple.h 2022-10-17 14:11:16.142167827 +0200 @@ -825,6 +825,20 @@ struct GTY((tag("GSS_OMP_ATOMIC_STORE_LA stmt->code == GIMPLE_OMP_RETURN. */ }; +/* Assumptions. */ + +struct GTY((tag("GSS_ASSUME"))) + gimple_statement_assume : public gimple +{ + /* [ WORD 1-6 ] : base class */ + + /* [ WORD 7 ] */ + tree guard; + + /* [ WORD 8 ] */ + gimple_seq body; +}; + /* GIMPLE_TRANSACTION. */ /* Bits to be stored in the GIMPLE_TRANSACTION subcode. */ @@ -1271,6 +1285,14 @@ is_a_helper <const gswitch *>::test (con template <> template <> inline bool +is_a_helper <gimple_statement_assume *>::test (gimple *gs) +{ + return gs->code == GIMPLE_ASSUME; +} + +template <> +template <> +inline bool is_a_helper <gtransaction *>::test (gimple *gs) { return gs->code == GIMPLE_TRANSACTION; @@ -1497,6 +1519,14 @@ is_a_helper <const greturn *>::test (con template <> template <> inline bool +is_a_helper <const gimple_statement_assume *>::test (const gimple *gs) +{ + return gs->code == GIMPLE_ASSUME; +} + +template <> +template <> +inline bool is_a_helper <const gtransaction *>::test (const gimple *gs) { return gs->code == GIMPLE_TRANSACTION; @@ -1577,6 +1607,7 @@ gomp_teams *gimple_build_omp_teams (gimp gomp_atomic_load *gimple_build_omp_atomic_load (tree, tree, enum omp_memory_order); gomp_atomic_store *gimple_build_omp_atomic_store (tree, enum omp_memory_order); +gimple *gimple_build_assume (tree, gimple_seq); gtransaction *gimple_build_transaction (gimple_seq); extern void gimple_seq_add_stmt (gimple_seq *, gimple *); extern void gimple_seq_add_stmt_without_update (gimple_seq *, gimple *); @@ -1835,6 +1866,7 @@ gimple_has_substatements (gimple *g) { switch (gimple_code (g)) { + case GIMPLE_ASSUME: case GIMPLE_BIND: case GIMPLE_CATCH: case GIMPLE_EH_FILTER: @@ -6520,6 +6552,52 @@ gimple_omp_continue_set_control_use (gom cont_stmt->control_use = use; } +/* Return the guard associated with the GIMPLE_ASSUME statement GS. */ + +static inline tree +gimple_assume_guard (const gimple *gs) +{ + const gimple_statement_assume *assume_stmt + = as_a <const gimple_statement_assume *> (gs); + return assume_stmt->guard; +} + +/* Set the guard associated with the GIMPLE_ASSUME statement GS. */ + +static inline void +gimple_assume_set_guard (gimple *gs, tree guard) +{ + gimple_statement_assume *assume_stmt = as_a <gimple_statement_assume *> (gs); + assume_stmt->guard = guard; +} + +static inline tree * +gimple_assume_guard_ptr (gimple *gs) +{ + gimple_statement_assume *assume_stmt = as_a <gimple_statement_assume *> (gs); + return &assume_stmt->guard; +} + +/* Return the address of the GIMPLE sequence contained in the GIMPLE_ASSUME + statement GS. */ + +static inline gimple_seq * +gimple_assume_body_ptr (gimple *gs) +{ + gimple_statement_assume *assume_stmt = as_a <gimple_statement_assume *> (gs); + return &assume_stmt->body; +} + +/* Return the GIMPLE sequence contained in the GIMPLE_ASSUME statement GS. */ + +static inline gimple_seq +gimple_assume_body (const gimple *gs) +{ + const gimple_statement_assume *assume_stmt + = as_a <const gimple_statement_assume *> (gs); + return assume_stmt->body; +} + /* Return a pointer to the body for the GIMPLE_TRANSACTION statement TRANSACTION_STMT. */ --- gcc/gsstruct.def.jj 2022-05-30 14:07:02.054305846 +0200 +++ gcc/gsstruct.def 2022-10-17 14:12:35.775087795 +0200 @@ -50,4 +50,5 @@ DEFGSSTRUCT(GSS_OMP_SINGLE_LAYOUT, gimpl DEFGSSTRUCT(GSS_OMP_CONTINUE, gomp_continue, false) DEFGSSTRUCT(GSS_OMP_ATOMIC_LOAD, gomp_atomic_load, false) DEFGSSTRUCT(GSS_OMP_ATOMIC_STORE_LAYOUT, gomp_atomic_store, false) +DEFGSSTRUCT(GSS_ASSUME, gimple_statement_assume, false) DEFGSSTRUCT(GSS_TRANSACTION, gtransaction, false) --- gcc/gimple.cc.jj 2022-09-08 20:22:07.765184796 +0200 +++ gcc/gimple.cc 2022-10-17 14:13:06.372672818 +0200 @@ -1290,6 +1290,18 @@ gimple_build_omp_atomic_store (tree val, return p; } +/* Build a GIMPLE_ASSUME statement. */ + +gimple * +gimple_build_assume (tree guard, gimple_seq body) +{ + gimple_statement_assume *p + = as_a <gimple_statement_assume *> (gimple_alloc (GIMPLE_ASSUME, 0)); + gimple_assume_set_guard (p, guard); + *gimple_assume_body_ptr (p) = body; + return p; +} + /* Build a GIMPLE_TRANSACTION statement. */ gtransaction * @@ -2135,6 +2147,13 @@ gimple_copy (gimple *stmt) gimple_omp_masked_set_clauses (copy, t); goto copy_omp_body; + case GIMPLE_ASSUME: + new_seq = gimple_seq_copy (gimple_assume_body (stmt)); + *gimple_assume_body_ptr (copy) = new_seq; + gimple_assume_set_guard (copy, + unshare_expr (gimple_assume_guard (stmt))); + break; + case GIMPLE_TRANSACTION: new_seq = gimple_seq_copy (gimple_transaction_body ( as_a <gtransaction *> (stmt))); --- gcc/gimple-pretty-print.cc.jj 2022-09-29 09:13:31.256642073 +0200 +++ gcc/gimple-pretty-print.cc 2022-10-17 14:14:37.488437047 +0200 @@ -2052,6 +2052,31 @@ dump_gimple_omp_return (pretty_printer * } } +/* Dump a GIMPLE_ASSUME tuple on the pretty_printer BUFFER. */ + +static void +dump_gimple_assume (pretty_printer *buffer, const gimple *gs, + int spc, dump_flags_t flags) +{ + if (flags & TDF_RAW) + dump_gimple_fmt (buffer, spc, flags, + "%G [GUARD=%T] <%+BODY <%S> >", + gs, gimple_assume_guard (gs), + gimple_assume_body (gs)); + else + { + pp_string (buffer, "[[assume ("); + dump_generic_node (buffer, gimple_assume_guard (gs), spc, flags, false); + pp_string (buffer, ")]]"); + newline_and_indent (buffer, spc + 2); + pp_left_brace (buffer); + pp_newline (buffer); + dump_gimple_seq (buffer, gimple_assume_body (gs), spc + 4, flags); + newline_and_indent (buffer, spc + 2); + pp_right_brace (buffer); + } +} + /* Dump a GIMPLE_TRANSACTION tuple on the pretty_printer BUFFER. */ static void @@ -2841,6 +2866,10 @@ pp_gimple_stmt_1 (pretty_printer *buffer pp_string (buffer, " predictor."); break; + case GIMPLE_ASSUME: + dump_gimple_assume (buffer, gs, spc, flags); + break; + case GIMPLE_TRANSACTION: dump_gimple_transaction (buffer, as_a <const gtransaction *> (gs), spc, flags); --- gcc/gimple-walk.cc.jj 2022-05-30 14:07:01.936307108 +0200 +++ gcc/gimple-walk.cc 2022-10-17 13:43:10.796032556 +0200 @@ -485,6 +485,12 @@ walk_gimple_op (gimple *stmt, walk_tree_ } break; + case GIMPLE_ASSUME: + ret = walk_tree (gimple_assume_guard_ptr (stmt), callback_op, wi, pset); + if (ret) + return ret; + break; + case GIMPLE_TRANSACTION: { gtransaction *txn = as_a <gtransaction *> (stmt); @@ -706,6 +712,13 @@ walk_gimple_stmt (gimple_stmt_iterator * if (ret) return wi->callback_result; break; + + case GIMPLE_ASSUME: + ret = walk_gimple_seq_mod (gimple_assume_body_ptr (stmt), + callback_stmt, callback_op, wi); + if (ret) + return wi->callback_result; + break; case GIMPLE_TRANSACTION: ret = walk_gimple_seq_mod (gimple_transaction_body_ptr ( --- gcc/omp-low.cc.jj 2022-09-26 18:47:27.056348383 +0200 +++ gcc/omp-low.cc 2022-10-17 13:45:48.271895674 +0200 @@ -202,6 +202,7 @@ static bool omp_maybe_offloaded_ctx (omp case GIMPLE_TRY: \ case GIMPLE_CATCH: \ case GIMPLE_EH_FILTER: \ + case GIMPLE_ASSUME: \ case GIMPLE_TRANSACTION: \ /* The sub-statements for these should be walked. */ \ *handled_ops_p = false; \ @@ -14413,6 +14414,9 @@ lower_omp_1 (gimple_stmt_iterator *gsi_p lower_omp (gimple_try_eval_ptr (stmt), ctx); lower_omp (gimple_try_cleanup_ptr (stmt), ctx); break; + case GIMPLE_ASSUME: + lower_omp (gimple_assume_body_ptr (stmt), ctx); + break; case GIMPLE_TRANSACTION: lower_omp (gimple_transaction_body_ptr (as_a <gtransaction *> (stmt)), ctx); --- gcc/omp-oacc-kernels-decompose.cc.jj 2022-06-28 13:03:30.951689423 +0200 +++ gcc/omp-oacc-kernels-decompose.cc 2022-10-17 13:47:10.470780243 +0200 @@ -189,6 +189,7 @@ adjust_region_code_walk_stmt_fn (gimple_ case GIMPLE_GOTO: case GIMPLE_SWITCH: case GIMPLE_ASM: + case GIMPLE_ASSUME: case GIMPLE_TRANSACTION: case GIMPLE_RETURN: /* Statement that might constitute some looping/control flow pattern. */ --- gcc/tree-cfg.cc.jj 2022-10-17 13:48:32.782663305 +0200 +++ gcc/tree-cfg.cc 2022-10-17 14:38:37.473858786 +0200 @@ -5139,6 +5139,9 @@ verify_gimple_stmt (gimple *stmt) how to setup the parallel iteration. */ return false; + case GIMPLE_ASSUME: + return false; + case GIMPLE_DEBUG: return verify_gimple_debug (stmt); @@ -5252,6 +5255,10 @@ verify_gimple_in_seq_2 (gimple_seq stmts as_a <gcatch *> (stmt))); break; + case GIMPLE_ASSUME: + err |= verify_gimple_in_seq_2 (gimple_assume_body (stmt)); + break; + case GIMPLE_TRANSACTION: err |= verify_gimple_transaction (as_a <gtransaction *> (stmt)); break; --- gcc/function.h.jj 2022-10-13 08:40:38.046532404 +0200 +++ gcc/function.h 2022-10-17 12:40:45.574886412 +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-13 08:40:38.169530712 +0200 +++ gcc/gimplify.cc 2022-10-17 14:15:05.616055561 +0200 @@ -3569,7 +3569,33 @@ 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: + [[assume (guard)]] + { + guard = cond; + } + such that gimple lowering can outline the condition into + a separate function easily. */ + tree guard = create_tmp_var (boolean_type_node); + *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); + push_gimplify_context (); + gimple_seq body = NULL; + gimple *g = gimplify_and_return_first (*expr_p, &body); + pop_gimplify_context (g); + g = gimple_build_assume (guard, body); + gimple_set_location (g, loc); + gimplify_seq_add_stmt (pre_p, g); + *expr_p = NULL_TREE; return GS_ALL_DONE; } --- gcc/gimple-low.cc.jj 2022-10-13 08:40:38.091531785 +0200 +++ gcc/gimple-low.cc 2022-10-17 16:17:42.154113603 +0200 @@ -33,6 +33,14 @@ 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" +#include "attribs.h" /* The differences between High GIMPLE and Low GIMPLE are the following: @@ -237,6 +245,389 @@ lower_omp_directive (gimple_stmt_iterato gsi_next (gsi); } +/* Create an artificial FUNCTION_DECL for assumption at LOC. */ + +static tree +create_assumption_fn (location_t loc) +{ + tree name = clone_function_name_numbered (current_function_decl, "_assume"); + /* Temporarily, until we determine all the arguments. */ + tree type = build_varargs_function_type_list (boolean_type_node, NULL_TREE); + 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); + tree attributes = DECL_ATTRIBUTES (current_function_decl); + if (lookup_attribute ("noipa", attributes) == NULL) + { + attributes = tree_cons (get_identifier ("noipa"), NULL, attributes); + if (lookup_attribute ("noinline", attributes) == NULL) + attributes = tree_cons (get_identifier ("noinline"), NULL, attributes); + if (lookup_attribute ("noclone", attributes) == NULL) + attributes = tree_cons (get_identifier ("noclone"), NULL, attributes); + if (lookup_attribute ("no_icf", attributes) == NULL) + attributes = tree_cons (get_identifier ("no_icf"), NULL, attributes); + } + DECL_ATTRIBUTES (decl) = attributes; + 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); + 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: + [[assume (guard)]] + { + guard = cond; + } + 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_assume_guard (stmt); + gimple *bind = gimple_assume_body (stmt); + location_t loc = gimple_location (stmt); + 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; + + /* Find variables, labels and SSA_NAMEs local to the assume GIMPLE_BIND. */ + gimple_stmt_iterator gsi2 = gsi_start (*gimple_assume_body_ptr (stmt)); + struct walk_stmt_info wi; + memset (&wi, 0, sizeof (wi)); + wi.info = (void *) &lad; + walk_gimple_stmt (&gsi2, 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; + /* SSA_NAMEs defined in the assume condition should be replaced + by new SSA_NAMEs in the artificial function. */ + if (TREE_CODE (v) == SSA_NAME) + { + newv = make_ssa_name (remap_type (TREE_TYPE (v), &lad.id)); + decl_map.put (v, newv); + } + /* Local vars should have context and type adjusted to the + new artificial function. */ + 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; + } + } + /* References to other automatic vars should be replaced by + PARM_DECLs to the artificial function. */ + memset (&wi, 0, sizeof (wi)); + wi.info = (void *) &lad; + walk_gimple_stmt (&gsi2, adjust_assumption_stmt_r, + adjust_assumption_stmt_op, &wi); + + /* At the start prepend guard = 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); + /* At the end add return guard; */ + greturn *gr = gimple_build_return (lad.guard_copy); + gimple_seq_add_stmt (&body, gr); + /* If there were any jumps to labels outside of the condition, + replace them with a jump to + return_false_label: + guard = false; + return guard; */ + 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); + /* First argument to IFN_ASSUME will be address of the + artificial function. */ + 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); + /* Remaining arguments will be the variables/parameters + mentioned in the condition. */ + vargs[i - sz] = lad.decls[i - 1]; + /* If they have gimple types, we might need to regimplify + them to make the IFN_ASSUME call valid. */ + 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); + } + + data->cannot_fallthru = false; + /* Replace GIMPLE_ASSUME statement with IFN_ASSUME call. */ + 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 @@ -403,6 +794,10 @@ lower_stmt (gimple_stmt_iterator *gsi, s data->cannot_fallthru = false; return; + case GIMPLE_ASSUME: + lower_assumption (gsi, data); + return; + case GIMPLE_TRANSACTION: lower_sequence (gimple_transaction_body_ptr ( as_a <gtransaction *> (stmt)), --- gcc/tree-ssa-ccp.cc.jj 2022-10-13 08:40:38.478526460 +0200 +++ gcc/tree-ssa-ccp.cc 2022-10-17 12:40:45.696884755 +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-13 08:40:38.373527904 +0200 +++ gcc/lto-streamer-out.cc 2022-10-17 12:40:45.720884429 +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-13 08:40:38.371527932 +0200 +++ gcc/lto-streamer-in.cc 2022-10-17 12:40:45.741884143 +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-13 08:40:37.868534853 +0200 +++ gcc/cgraphunit.cc 2022-10-17 16:45:46.995270327 +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) @@ -2373,6 +2383,10 @@ symbol_table::compile (void) if (node->inlined_to || gimple_has_body_p (node->decl)) { + if (DECL_STRUCT_FUNCTION (node->decl) + && (DECL_STRUCT_FUNCTION (node->decl)->curr_properties + & PROP_assumptions_done) != 0) + continue; error_found = true; node->debug (); } --- gcc/internal-fn.cc.jj 2022-10-13 08:40:38.218530037 +0200 +++ gcc/internal-fn.cc 2022-10-17 12:40:45.782883587 +0200 @@ -4526,5 +4526,4 @@ expand_TRAP (internal_fn, gcall *) void expand_ASSUME (internal_fn, gcall *) { - gcc_unreachable (); } --- gcc/passes.cc.jj 2022-10-13 08:40:38.419527272 +0200 +++ gcc/passes.cc 2022-10-17 16:38:23.325283213 +0200 @@ -2660,6 +2660,15 @@ execute_one_pass (opt_pass *pass) if (dom_info_available_p (CDI_POST_DOMINATORS)) free_dominance_info (CDI_POST_DOMINATORS); + if (cfun->assume_function) + { + /* For assume functions, don't release body, keep it around. */ + cfun->curr_properties |= PROP_assumptions_done; + pop_cfun (); + current_pass = NULL; + return true; + } + tree fn = cfun->decl; pop_cfun (); gcc_assert (!cfun); --- gcc/cgraph.cc.jj 2022-06-27 11:18:02.047066621 +0200 +++ gcc/cgraph.cc 2022-10-17 16:40:24.319643418 +0200 @@ -3751,7 +3751,9 @@ cgraph_node::verify_node (void) && (!DECL_EXTERNAL (decl) || inlined_to) && !flag_wpa) { - if (this_cfun->cfg) + if ((this_cfun->curr_properties & PROP_assumptions_done) != 0) + ; + else if (this_cfun->cfg) { hash_set<gimple *> stmts; --- gcc/tree-pass.h.jj 2022-07-26 10:32:24.020267414 +0200 +++ gcc/tree-pass.h 2022-10-17 16:36:12.800052172 +0200 @@ -227,6 +227,8 @@ protected: #define PROP_rtl_split_insns (1 << 17) /* RTL has insns split. */ #define PROP_loop_opts_done (1 << 18) /* SSA loop optimizations have completed. */ +#define PROP_assumptions_done (1 << 19) /* Assume function kept + around. */ #define PROP_gimple \ (PROP_gimple_any | PROP_gimple_lcf | PROP_gimple_leh | PROP_gimple_lomp) @@ -301,7 +303,8 @@ protected: /* Rebuild the callgraph edges. */ #define TODO_rebuild_cgraph_edges (1 << 22) -/* Release function body and stop pass manager. */ +/* Release function body (unless assumption function) + and stop pass manager. */ #define TODO_discard_function (1 << 23) /* Internally used in execute_function_todo(). */ @@ -465,6 +468,7 @@ extern gimple_opt_pass *make_pass_copy_p extern gimple_opt_pass *make_pass_isolate_erroneous_paths (gcc::context *ctxt); extern gimple_opt_pass *make_pass_early_vrp (gcc::context *ctxt); extern gimple_opt_pass *make_pass_vrp (gcc::context *ctxt); +extern gimple_opt_pass *make_pass_assumptions (gcc::context *ctxt); extern gimple_opt_pass *make_pass_uncprop (gcc::context *ctxt); extern gimple_opt_pass *make_pass_return_slot (gcc::context *ctxt); extern gimple_opt_pass *make_pass_reassoc (gcc::context *ctxt); --- gcc/passes.def.jj 2022-09-23 09:02:56.876313524 +0200 +++ gcc/passes.def 2022-10-17 16:11:58.526770578 +0200 @@ -407,6 +407,7 @@ along with GCC; see the file COPYING3. and thus it should be run last. */ NEXT_PASS (pass_uncprop); POP_INSERT_PASSES () + NEXT_PASS (pass_assumptions); NEXT_PASS (pass_tm_init); PUSH_INSERT_PASSES_WITHIN (pass_tm_init) NEXT_PASS (pass_tm_mark); --- gcc/timevar.def.jj 2022-09-03 09:35:41.334986488 +0200 +++ gcc/timevar.def 2022-10-17 16:06:57.336852424 +0200 @@ -226,6 +226,7 @@ DEFTIMEVAR (TV_TREE_WIDEN_MUL , " DEFTIMEVAR (TV_TRANS_MEM , "transactional memory") DEFTIMEVAR (TV_TREE_STRLEN , "tree strlen optimization") DEFTIMEVAR (TV_TREE_MODREF , "tree modref") +DEFTIMEVAR (TV_TREE_ASSUMPTIONS , "tree assumptions") DEFTIMEVAR (TV_CGRAPH_VERIFY , "callgraph verifier") DEFTIMEVAR (TV_DOM_FRONTIERS , "dominance frontiers") DEFTIMEVAR (TV_DOMINANCE , "dominance computation") --- gcc/tree-inline.cc.jj 2022-10-07 09:08:43.887133549 +0200 +++ gcc/tree-inline.cc 2022-10-17 13:50:11.755320277 +0200 @@ -1736,6 +1736,11 @@ remap_gimple_stmt (gimple *stmt, copy_bo (as_a <gomp_critical *> (stmt))); break; + case GIMPLE_ASSUME: + s1 = remap_gimple_seq (gimple_assume_body (stmt), id); + copy = gimple_build_assume (gimple_assume_guard (stmt), s1); + break; + case GIMPLE_TRANSACTION: { gtransaction *old_trans_stmt = as_a <gtransaction *> (stmt); --- gcc/tree-vrp.cc.jj 2022-09-23 09:02:57.099310450 +0200 +++ gcc/tree-vrp.cc 2022-10-17 16:37:10.623268518 +0200 @@ -4441,6 +4441,35 @@ public: int my_pass; }; // class pass_vrp +const pass_data pass_data_assumptions = +{ + GIMPLE_PASS, /* type */ + "assumptions", /* name */ + OPTGROUP_NONE, /* optinfo_flags */ + TV_TREE_ASSUMPTIONS, /* tv_id */ + PROP_ssa, /* properties_required */ + PROP_assumptions_done, /* properties_provided */ + 0, /* properties_destroyed */ + 0, /* todo_flags_start */ + 0, /* todo_flags_end */ +}; + +class pass_assumptions : public gimple_opt_pass +{ +public: + pass_assumptions (gcc::context *ctxt) + : gimple_opt_pass (pass_data_assumptions, ctxt) + {} + + /* opt_pass methods: */ + bool gate (function *fun) final override { return fun->assume_function; } + unsigned int execute (function *) final override + { + return TODO_discard_function; + } + +}; // class pass_assumptions + } // anon namespace gimple_opt_pass * @@ -4454,3 +4483,9 @@ make_pass_early_vrp (gcc::context *ctxt) { return new pass_vrp (ctxt, pass_data_early_vrp); } + +gimple_opt_pass * +make_pass_assumptions (gcc::context *ctx) +{ + return new pass_assumptions (ctx); +} --- gcc/cp/cp-tree.h.jj 2022-10-14 09:35:56.201990233 +0200 +++ gcc/cp/cp-tree.h 2022-10-17 12:40:45.842882772 +0200 @@ -8280,6 +8280,7 @@ extern tree predeclare_vla (tree); extern void clear_fold_cache (void); extern tree lookup_hotness_attribute (tree); extern tree process_stmt_hotness_attribute (tree, location_t); +extern tree build_assume_call (location_t, tree); extern tree process_stmt_assume_attribute (tree, tree, location_t); extern bool simple_empty_class_p (tree, tree, tree_code); extern tree fold_builtin_source_location (location_t); --- gcc/cp/parser.cc.jj 2022-10-14 09:28:28.006164065 +0200 +++ gcc/cp/parser.cc 2022-10-17 12:40:45.897882025 +0200 @@ -46012,11 +46012,7 @@ cp_parser_omp_assumption_clauses (cp_par if (!type_dependent_expression_p (t)) t = contextual_conv_bool (t, tf_warning_or_error); if (is_assume && !error_operand_p (t)) - { - t = build_call_expr_internal_loc (eloc, IFN_ASSUME, - void_type_node, 1, t); - finish_expr_stmt (t); - } + finish_expr_stmt (build_assume_call (eloc, t)); if (!parens.require_close (parser)) cp_parser_skip_to_closing_parenthesis (parser, /*recovering=*/true, --- gcc/cp/cp-gimplify.cc.jj 2022-10-14 09:28:28.154162026 +0200 +++ gcc/cp/cp-gimplify.cc 2022-10-17 12:40:45.931881563 +0200 @@ -3101,6 +3101,17 @@ process_stmt_hotness_attribute (tree std return std_attrs; } +/* Build IFN_ASSUME internal call for assume condition ARG. */ + +tree +build_assume_call (location_t loc, tree arg) +{ + if (!processing_template_decl) + arg = fold_build_cleanup_point_expr (TREE_TYPE (arg), arg); + return build_call_expr_internal_loc (loc, IFN_ASSUME, void_type_node, + 1, arg); +} + /* If [[assume (cond)]] appears on this statement, handle it. */ tree @@ -3137,9 +3148,7 @@ process_stmt_assume_attribute (tree std_ arg = contextual_conv_bool (arg, tf_warning_or_error); if (error_operand_p (arg)) continue; - statement = build_call_expr_internal_loc (attrs_loc, IFN_ASSUME, - void_type_node, 1, arg); - finish_expr_stmt (statement); + finish_expr_stmt (build_assume_call (attrs_loc, arg)); } } return remove_attribute ("gnu", "assume", std_attrs); --- gcc/cp/pt.cc.jj 2022-10-14 09:28:28.135162288 +0200 +++ gcc/cp/pt.cc 2022-10-17 12:40:45.985880829 +0200 @@ -21140,10 +21140,7 @@ tsubst_copy_and_build (tree t, ret = error_mark_node; break; } - ret = build_call_expr_internal_loc (EXPR_LOCATION (t), - IFN_ASSUME, - void_type_node, 1, - arg); + ret = build_assume_call (EXPR_LOCATION (t), arg); RETURN (ret); } break; --- gcc/testsuite/g++.dg/cpp23/attr-assume5.C.jj 2022-10-17 12:40:45.985880829 +0200 +++ gcc/testsuite/g++.dg/cpp23/attr-assume5.C 2022-10-17 12:40:45.985880829 +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-17 12:40:45.986880816 +0200 +++ gcc/testsuite/g++.dg/cpp23/attr-assume6.C 2022-10-17 12:40:45.986880816 +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-17 12:40:45.986880816 +0200 +++ gcc/testsuite/g++.dg/cpp23/attr-assume7.C 2022-10-17 17:39:43.992411442 +0200 @@ -0,0 +1,56 @@ +// 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; +} + +int +corge (int x) +{ + [[assume (({ [[assume (x < 42)]]; x > -42; }))]]; + return x < 42; +} + +int +garply (int x) +{ + [[assume (({ [[assume (++x < 43)]]; x > -42; }))]]; + return x < 42; +} Jakub ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH] middle-end, v4: IFN_ASSUME support [PR106654] 2022-10-17 15:44 ` [PATCH] middle-end, v4: " Jakub Jelinek @ 2022-10-18 7:00 ` Richard Biener 2022-10-18 21:31 ` Andrew MacLeod 1 sibling, 0 replies; 35+ messages in thread From: Richard Biener @ 2022-10-18 7:00 UTC (permalink / raw) To: Jakub Jelinek; +Cc: Jason Merrill, Jan Hubicka, gcc-patches On Mon, 17 Oct 2022, Jakub Jelinek wrote: > Hi! > > On Mon, Oct 17, 2022 at 06:55:40AM +0000, Richard Biener wrote: > > > That is what I wrote in the patch description as alternative: > > > "with the condition wrapped into a GIMPLE_BIND (I admit the above isn't > > > extra clean but it is just something to hold it from gimplifier until > > > gimple low pass; it reassembles if (condition_never_true) { cond; }; > > > an alternative would be introduce GOMP_ASSUME statement that would have > > > the guard var as operand and the GIMPLE_BIND as body, but for the > > > few passes (tree-nested and omp lowering) in between that looked like > > > an overkill to me)" > > > I can certainly implement that easily. > > > > I'd prefer that, it looks possibly less messy. > > Ok, introduced GIMPLE_ASSUME for this then. > > > Ah, they are all in all_passes :/ Maybe we can add something > > like TODO_discard_function (or a property) that will not discard > > the function but stop compiling it? I wonder if all cleanup > > is properly done for the function - I suppose we want to keep the > > body around for callers indefinitely. > > > > > What I had in the patch was just skip pass_expand > > > and pass_rest_of_compilation, perhaps another possibility > > > to do the former would be to define a gate on pass_expand. > > > > Or some gate in the pass manager, like in override_gate_status > > check fun->properties & PROP_suspended and have some > > pass_suspend_assume add that property for all assume function > > bodies. > > > > In case you like any of the above give it a shot, otherwise what > > you have isn't too bad, I just wondered if there's a nicer way. > > Turns out we already had TODO_discard_function, so I've reused it > with the detail that assume function's bodies aren't actually dropped, > and a new pass which returns that and where I'd like to have the > backwards range walk implemented. > > > I suppose for now adding noipa is easiest, we'd still inline into > > the body of course. > > Ok, done. > > On Fri, Oct 14, 2022 at 11:27:07AM +0000, Richard Biener wrote: > > > @@ -237,6 +244,383 @@ lower_omp_directive (gimple_stmt_iterato > > > gsi_next (gsi); > > > } > > > > comment missing > > Added. > > > > +static tree > > > +create_assumption_fn (location_t loc) > > > +{ > > > + tree name = clone_function_name_numbered (current_function_decl, "_assume"); > > > + /* For now, will be changed later. */ > > > > ? > > I've tried error_mark_node as the type, but that didn't work out, get > various ICEs with it even for the short time before it is fixed up. > But changed that to > tree type = build_varargs_function_type_list (boolean_type_node, NULL_TREE); > which is closer to what it ends up later. > > > > + DECL_FUNCTION_VERSIONED (decl) > > > + = DECL_FUNCTION_VERSIONED (current_function_decl); > > > > what does it mean to copy DECL_FUNCTION_VERSIONED here? > > Dropped. > > > > + && !is_gimple_val (vargs[i - sz])) > > > > a few comments might be helpful here > > Added some to various places in that function. > > > @@ -1490,7 +1494,14 @@ public: > > > > > > /* opt_pass methods: */ > > > opt_pass * clone () final override { return new pass_slp_vectorize (m_ctxt); } > > > - bool gate (function *) final override { return flag_tree_slp_vectorize != 0; } > > > + bool gate (function *fun) final override > > > + { > > > + /* Vectorization makes range analysis of assume functions harder, > > > + not easier. */ > > > > Can we split out these kind of considerations from the initial patch? > > Dropped for now. > > > Reading some of the patch I guessed you wanted to handle nested > > assumes. So - is > > > > [[assume (a == 4 && ([[assume(b == 3)]], b != 2))]] > > > > a thing? > > Added 2 tests for nested assumptions (one with a simple assumption > nested in complex one, one with side-effects one nested in complex one). > > So far lightly tested, will test fully overnight. Looks good to me now. Thanks, Richard. > 2022-10-17 Jakub Jelinek <jakub@redhat.com> > > PR c++/106654 > gcc/ > * gimple.def (GIMPLE_ASSUME): New statement kind. > * gimple.h (struct gimple_statement_assume): New type. > (is_a_helper <gimple_statement_assume *>::test, > is_a_helper <const gimple_statement_assume *>::test): New. > (gimple_build_assume): Declare. > (gimple_has_substatements): Return true for GIMPLE_ASSUME. > (gimple_assume_guard, gimple_assume_set_guard, > gimple_assume_guard_ptr, gimple_assume_body_ptr, gimple_assume_body): > New inline functions. > * gsstruct.def (GSS_ASSUME): New. > * gimple.cc (gimple_build_assume): New function. > (gimple_copy): Handle GIMPLE_ASSUME. > * gimple-pretty-print.cc (dump_gimple_assume): New function. > (pp_gimple_stmt_1): Handle GIMPLE_ASSUME. > * gimple-walk.cc (walk_gimple_op): Handle GIMPLE_ASSUME. > * omp-low.cc (WALK_SUBSTMTS): Likewise. > (lower_omp_1): Likewise. > * omp-oacc-kernels-decompose.cc (adjust_region_code_walk_stmt_fn): > Likewise. > * tree-cfg.cc (verify_gimple_stmt, verify_gimple_in_seq_2): Likewise. > * function.h (struct function): Add assume_function bitfield. > * gimplify.cc (gimplify_call_expr): If the assumption isn't > simple enough, expand it into GIMPLE_ASSUME wrapped block or > for -O0 drop it. > * gimple-low.cc: Include attribs.h. > (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 GIMPLE_ASSUME. > * 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. > (symbol_table::compile): Allow assume functions not to have released > body. > * internal-fn.cc (expand_ASSUME): Remove gcc_unreachable. > * passes.cc (execute_one_pass): For TODO_discard_function don't > release body of assume functions. > * cgraph.cc (cgraph_node::verify_node): Don't verify cgraph nodes > of PROP_assumptions_done functions. > * tree-pass.h (PROP_assumptions_done): Define. > (TODO_discard_function): Adjust comment. > (make_pass_assumptions): Declare. > * passes.def (pass_assumptions): Add. > * tree-vrp.cc (pass_data_assumptions): New variable. > (pass_assumptions): New class. > (make_pass_assumptions): New function. > gcc/cp/ > * cp-tree.h (build_assume_call): Declare. > * parser.cc (cp_parser_omp_assumption_clauses): Use build_assume_call. > * cp-gimplify.cc (build_assume_call): New function. > (process_stmt_assume_attribute): Use build_assume_call. > * pt.cc (tsubst_copy_and_build): Likewise. > gcc/testsuite/ > * g++.dg/cpp23/attr-assume5.C: New test. > * g++.dg/cpp23/attr-assume6.C: New test. > * g++.dg/cpp23/attr-assume7.C: New test. > > --- gcc/gimple.def.jj 2022-05-30 14:07:01.989306541 +0200 > +++ gcc/gimple.def 2022-10-17 12:47:55.323049825 +0200 > @@ -406,3 +406,8 @@ DEFGSCODE(GIMPLE_PREDICT, "gimple_predic > > This tuple should not exist outside of the gimplifier proper. */ > DEFGSCODE(GIMPLE_WITH_CLEANUP_EXPR, "gimple_with_cleanup_expr", GSS_WCE) > + > +/* GIMPLE_ASSUME <GUARD, BODY> represents [[assume(cond)]]. > + BODY is the GIMPLE_BIND with the condition which sets GUARD to true > + (otherwise UB). */ > +DEFGSCODE(GIMPLE_ASSUME, "gimple_assume", GSS_ASSUME) > --- gcc/gimple.h.jj 2022-09-06 09:19:14.718561564 +0200 > +++ gcc/gimple.h 2022-10-17 14:11:16.142167827 +0200 > @@ -825,6 +825,20 @@ struct GTY((tag("GSS_OMP_ATOMIC_STORE_LA > stmt->code == GIMPLE_OMP_RETURN. */ > }; > > +/* Assumptions. */ > + > +struct GTY((tag("GSS_ASSUME"))) > + gimple_statement_assume : public gimple > +{ > + /* [ WORD 1-6 ] : base class */ > + > + /* [ WORD 7 ] */ > + tree guard; > + > + /* [ WORD 8 ] */ > + gimple_seq body; > +}; > + > /* GIMPLE_TRANSACTION. */ > > /* Bits to be stored in the GIMPLE_TRANSACTION subcode. */ > @@ -1271,6 +1285,14 @@ is_a_helper <const gswitch *>::test (con > template <> > template <> > inline bool > +is_a_helper <gimple_statement_assume *>::test (gimple *gs) > +{ > + return gs->code == GIMPLE_ASSUME; > +} > + > +template <> > +template <> > +inline bool > is_a_helper <gtransaction *>::test (gimple *gs) > { > return gs->code == GIMPLE_TRANSACTION; > @@ -1497,6 +1519,14 @@ is_a_helper <const greturn *>::test (con > template <> > template <> > inline bool > +is_a_helper <const gimple_statement_assume *>::test (const gimple *gs) > +{ > + return gs->code == GIMPLE_ASSUME; > +} > + > +template <> > +template <> > +inline bool > is_a_helper <const gtransaction *>::test (const gimple *gs) > { > return gs->code == GIMPLE_TRANSACTION; > @@ -1577,6 +1607,7 @@ gomp_teams *gimple_build_omp_teams (gimp > gomp_atomic_load *gimple_build_omp_atomic_load (tree, tree, > enum omp_memory_order); > gomp_atomic_store *gimple_build_omp_atomic_store (tree, enum omp_memory_order); > +gimple *gimple_build_assume (tree, gimple_seq); > gtransaction *gimple_build_transaction (gimple_seq); > extern void gimple_seq_add_stmt (gimple_seq *, gimple *); > extern void gimple_seq_add_stmt_without_update (gimple_seq *, gimple *); > @@ -1835,6 +1866,7 @@ gimple_has_substatements (gimple *g) > { > switch (gimple_code (g)) > { > + case GIMPLE_ASSUME: > case GIMPLE_BIND: > case GIMPLE_CATCH: > case GIMPLE_EH_FILTER: > @@ -6520,6 +6552,52 @@ gimple_omp_continue_set_control_use (gom > cont_stmt->control_use = use; > } > > +/* Return the guard associated with the GIMPLE_ASSUME statement GS. */ > + > +static inline tree > +gimple_assume_guard (const gimple *gs) > +{ > + const gimple_statement_assume *assume_stmt > + = as_a <const gimple_statement_assume *> (gs); > + return assume_stmt->guard; > +} > + > +/* Set the guard associated with the GIMPLE_ASSUME statement GS. */ > + > +static inline void > +gimple_assume_set_guard (gimple *gs, tree guard) > +{ > + gimple_statement_assume *assume_stmt = as_a <gimple_statement_assume *> (gs); > + assume_stmt->guard = guard; > +} > + > +static inline tree * > +gimple_assume_guard_ptr (gimple *gs) > +{ > + gimple_statement_assume *assume_stmt = as_a <gimple_statement_assume *> (gs); > + return &assume_stmt->guard; > +} > + > +/* Return the address of the GIMPLE sequence contained in the GIMPLE_ASSUME > + statement GS. */ > + > +static inline gimple_seq * > +gimple_assume_body_ptr (gimple *gs) > +{ > + gimple_statement_assume *assume_stmt = as_a <gimple_statement_assume *> (gs); > + return &assume_stmt->body; > +} > + > +/* Return the GIMPLE sequence contained in the GIMPLE_ASSUME statement GS. */ > + > +static inline gimple_seq > +gimple_assume_body (const gimple *gs) > +{ > + const gimple_statement_assume *assume_stmt > + = as_a <const gimple_statement_assume *> (gs); > + return assume_stmt->body; > +} > + > /* Return a pointer to the body for the GIMPLE_TRANSACTION statement > TRANSACTION_STMT. */ > > --- gcc/gsstruct.def.jj 2022-05-30 14:07:02.054305846 +0200 > +++ gcc/gsstruct.def 2022-10-17 14:12:35.775087795 +0200 > @@ -50,4 +50,5 @@ DEFGSSTRUCT(GSS_OMP_SINGLE_LAYOUT, gimpl > DEFGSSTRUCT(GSS_OMP_CONTINUE, gomp_continue, false) > DEFGSSTRUCT(GSS_OMP_ATOMIC_LOAD, gomp_atomic_load, false) > DEFGSSTRUCT(GSS_OMP_ATOMIC_STORE_LAYOUT, gomp_atomic_store, false) > +DEFGSSTRUCT(GSS_ASSUME, gimple_statement_assume, false) > DEFGSSTRUCT(GSS_TRANSACTION, gtransaction, false) > --- gcc/gimple.cc.jj 2022-09-08 20:22:07.765184796 +0200 > +++ gcc/gimple.cc 2022-10-17 14:13:06.372672818 +0200 > @@ -1290,6 +1290,18 @@ gimple_build_omp_atomic_store (tree val, > return p; > } > > +/* Build a GIMPLE_ASSUME statement. */ > + > +gimple * > +gimple_build_assume (tree guard, gimple_seq body) > +{ > + gimple_statement_assume *p > + = as_a <gimple_statement_assume *> (gimple_alloc (GIMPLE_ASSUME, 0)); > + gimple_assume_set_guard (p, guard); > + *gimple_assume_body_ptr (p) = body; > + return p; > +} > + > /* Build a GIMPLE_TRANSACTION statement. */ > > gtransaction * > @@ -2135,6 +2147,13 @@ gimple_copy (gimple *stmt) > gimple_omp_masked_set_clauses (copy, t); > goto copy_omp_body; > > + case GIMPLE_ASSUME: > + new_seq = gimple_seq_copy (gimple_assume_body (stmt)); > + *gimple_assume_body_ptr (copy) = new_seq; > + gimple_assume_set_guard (copy, > + unshare_expr (gimple_assume_guard (stmt))); > + break; > + > case GIMPLE_TRANSACTION: > new_seq = gimple_seq_copy (gimple_transaction_body ( > as_a <gtransaction *> (stmt))); > --- gcc/gimple-pretty-print.cc.jj 2022-09-29 09:13:31.256642073 +0200 > +++ gcc/gimple-pretty-print.cc 2022-10-17 14:14:37.488437047 +0200 > @@ -2052,6 +2052,31 @@ dump_gimple_omp_return (pretty_printer * > } > } > > +/* Dump a GIMPLE_ASSUME tuple on the pretty_printer BUFFER. */ > + > +static void > +dump_gimple_assume (pretty_printer *buffer, const gimple *gs, > + int spc, dump_flags_t flags) > +{ > + if (flags & TDF_RAW) > + dump_gimple_fmt (buffer, spc, flags, > + "%G [GUARD=%T] <%+BODY <%S> >", > + gs, gimple_assume_guard (gs), > + gimple_assume_body (gs)); > + else > + { > + pp_string (buffer, "[[assume ("); > + dump_generic_node (buffer, gimple_assume_guard (gs), spc, flags, false); > + pp_string (buffer, ")]]"); > + newline_and_indent (buffer, spc + 2); > + pp_left_brace (buffer); > + pp_newline (buffer); > + dump_gimple_seq (buffer, gimple_assume_body (gs), spc + 4, flags); > + newline_and_indent (buffer, spc + 2); > + pp_right_brace (buffer); > + } > +} > + > /* Dump a GIMPLE_TRANSACTION tuple on the pretty_printer BUFFER. */ > > static void > @@ -2841,6 +2866,10 @@ pp_gimple_stmt_1 (pretty_printer *buffer > pp_string (buffer, " predictor."); > break; > > + case GIMPLE_ASSUME: > + dump_gimple_assume (buffer, gs, spc, flags); > + break; > + > case GIMPLE_TRANSACTION: > dump_gimple_transaction (buffer, as_a <const gtransaction *> (gs), spc, > flags); > --- gcc/gimple-walk.cc.jj 2022-05-30 14:07:01.936307108 +0200 > +++ gcc/gimple-walk.cc 2022-10-17 13:43:10.796032556 +0200 > @@ -485,6 +485,12 @@ walk_gimple_op (gimple *stmt, walk_tree_ > } > break; > > + case GIMPLE_ASSUME: > + ret = walk_tree (gimple_assume_guard_ptr (stmt), callback_op, wi, pset); > + if (ret) > + return ret; > + break; > + > case GIMPLE_TRANSACTION: > { > gtransaction *txn = as_a <gtransaction *> (stmt); > @@ -706,6 +712,13 @@ walk_gimple_stmt (gimple_stmt_iterator * > if (ret) > return wi->callback_result; > break; > + > + case GIMPLE_ASSUME: > + ret = walk_gimple_seq_mod (gimple_assume_body_ptr (stmt), > + callback_stmt, callback_op, wi); > + if (ret) > + return wi->callback_result; > + break; > > case GIMPLE_TRANSACTION: > ret = walk_gimple_seq_mod (gimple_transaction_body_ptr ( > --- gcc/omp-low.cc.jj 2022-09-26 18:47:27.056348383 +0200 > +++ gcc/omp-low.cc 2022-10-17 13:45:48.271895674 +0200 > @@ -202,6 +202,7 @@ static bool omp_maybe_offloaded_ctx (omp > case GIMPLE_TRY: \ > case GIMPLE_CATCH: \ > case GIMPLE_EH_FILTER: \ > + case GIMPLE_ASSUME: \ > case GIMPLE_TRANSACTION: \ > /* The sub-statements for these should be walked. */ \ > *handled_ops_p = false; \ > @@ -14413,6 +14414,9 @@ lower_omp_1 (gimple_stmt_iterator *gsi_p > lower_omp (gimple_try_eval_ptr (stmt), ctx); > lower_omp (gimple_try_cleanup_ptr (stmt), ctx); > break; > + case GIMPLE_ASSUME: > + lower_omp (gimple_assume_body_ptr (stmt), ctx); > + break; > case GIMPLE_TRANSACTION: > lower_omp (gimple_transaction_body_ptr (as_a <gtransaction *> (stmt)), > ctx); > --- gcc/omp-oacc-kernels-decompose.cc.jj 2022-06-28 13:03:30.951689423 +0200 > +++ gcc/omp-oacc-kernels-decompose.cc 2022-10-17 13:47:10.470780243 +0200 > @@ -189,6 +189,7 @@ adjust_region_code_walk_stmt_fn (gimple_ > case GIMPLE_GOTO: > case GIMPLE_SWITCH: > case GIMPLE_ASM: > + case GIMPLE_ASSUME: > case GIMPLE_TRANSACTION: > case GIMPLE_RETURN: > /* Statement that might constitute some looping/control flow pattern. */ > --- gcc/tree-cfg.cc.jj 2022-10-17 13:48:32.782663305 +0200 > +++ gcc/tree-cfg.cc 2022-10-17 14:38:37.473858786 +0200 > @@ -5139,6 +5139,9 @@ verify_gimple_stmt (gimple *stmt) > how to setup the parallel iteration. */ > return false; > > + case GIMPLE_ASSUME: > + return false; > + > case GIMPLE_DEBUG: > return verify_gimple_debug (stmt); > > @@ -5252,6 +5255,10 @@ verify_gimple_in_seq_2 (gimple_seq stmts > as_a <gcatch *> (stmt))); > break; > > + case GIMPLE_ASSUME: > + err |= verify_gimple_in_seq_2 (gimple_assume_body (stmt)); > + break; > + > case GIMPLE_TRANSACTION: > err |= verify_gimple_transaction (as_a <gtransaction *> (stmt)); > break; > --- gcc/function.h.jj 2022-10-13 08:40:38.046532404 +0200 > +++ gcc/function.h 2022-10-17 12:40:45.574886412 +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-13 08:40:38.169530712 +0200 > +++ gcc/gimplify.cc 2022-10-17 14:15:05.616055561 +0200 > @@ -3569,7 +3569,33 @@ 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: > + [[assume (guard)]] > + { > + guard = cond; > + } > + such that gimple lowering can outline the condition into > + a separate function easily. */ > + tree guard = create_tmp_var (boolean_type_node); > + *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); > + push_gimplify_context (); > + gimple_seq body = NULL; > + gimple *g = gimplify_and_return_first (*expr_p, &body); > + pop_gimplify_context (g); > + g = gimple_build_assume (guard, body); > + gimple_set_location (g, loc); > + gimplify_seq_add_stmt (pre_p, g); > + *expr_p = NULL_TREE; > return GS_ALL_DONE; > } > > --- gcc/gimple-low.cc.jj 2022-10-13 08:40:38.091531785 +0200 > +++ gcc/gimple-low.cc 2022-10-17 16:17:42.154113603 +0200 > @@ -33,6 +33,14 @@ 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" > +#include "attribs.h" > > /* The differences between High GIMPLE and Low GIMPLE are the > following: > @@ -237,6 +245,389 @@ lower_omp_directive (gimple_stmt_iterato > gsi_next (gsi); > } > > +/* Create an artificial FUNCTION_DECL for assumption at LOC. */ > + > +static tree > +create_assumption_fn (location_t loc) > +{ > + tree name = clone_function_name_numbered (current_function_decl, "_assume"); > + /* Temporarily, until we determine all the arguments. */ > + tree type = build_varargs_function_type_list (boolean_type_node, NULL_TREE); > + 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); > + tree attributes = DECL_ATTRIBUTES (current_function_decl); > + if (lookup_attribute ("noipa", attributes) == NULL) > + { > + attributes = tree_cons (get_identifier ("noipa"), NULL, attributes); > + if (lookup_attribute ("noinline", attributes) == NULL) > + attributes = tree_cons (get_identifier ("noinline"), NULL, attributes); > + if (lookup_attribute ("noclone", attributes) == NULL) > + attributes = tree_cons (get_identifier ("noclone"), NULL, attributes); > + if (lookup_attribute ("no_icf", attributes) == NULL) > + attributes = tree_cons (get_identifier ("no_icf"), NULL, attributes); > + } > + DECL_ATTRIBUTES (decl) = attributes; > + 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); > + 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: > + [[assume (guard)]] > + { > + guard = cond; > + } > + 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_assume_guard (stmt); > + gimple *bind = gimple_assume_body (stmt); > + location_t loc = gimple_location (stmt); > + 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; > + > + /* Find variables, labels and SSA_NAMEs local to the assume GIMPLE_BIND. */ > + gimple_stmt_iterator gsi2 = gsi_start (*gimple_assume_body_ptr (stmt)); > + struct walk_stmt_info wi; > + memset (&wi, 0, sizeof (wi)); > + wi.info = (void *) &lad; > + walk_gimple_stmt (&gsi2, 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; > + /* SSA_NAMEs defined in the assume condition should be replaced > + by new SSA_NAMEs in the artificial function. */ > + if (TREE_CODE (v) == SSA_NAME) > + { > + newv = make_ssa_name (remap_type (TREE_TYPE (v), &lad.id)); > + decl_map.put (v, newv); > + } > + /* Local vars should have context and type adjusted to the > + new artificial function. */ > + 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; > + } > + } > + /* References to other automatic vars should be replaced by > + PARM_DECLs to the artificial function. */ > + memset (&wi, 0, sizeof (wi)); > + wi.info = (void *) &lad; > + walk_gimple_stmt (&gsi2, adjust_assumption_stmt_r, > + adjust_assumption_stmt_op, &wi); > + > + /* At the start prepend guard = 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); > + /* At the end add return guard; */ > + greturn *gr = gimple_build_return (lad.guard_copy); > + gimple_seq_add_stmt (&body, gr); > + /* If there were any jumps to labels outside of the condition, > + replace them with a jump to > + return_false_label: > + guard = false; > + return guard; */ > + 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); > + /* First argument to IFN_ASSUME will be address of the > + artificial function. */ > + 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); > + /* Remaining arguments will be the variables/parameters > + mentioned in the condition. */ > + vargs[i - sz] = lad.decls[i - 1]; > + /* If they have gimple types, we might need to regimplify > + them to make the IFN_ASSUME call valid. */ > + 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); > + } > + > + data->cannot_fallthru = false; > + /* Replace GIMPLE_ASSUME statement with IFN_ASSUME call. */ > + 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 > @@ -403,6 +794,10 @@ lower_stmt (gimple_stmt_iterator *gsi, s > data->cannot_fallthru = false; > return; > > + case GIMPLE_ASSUME: > + lower_assumption (gsi, data); > + return; > + > case GIMPLE_TRANSACTION: > lower_sequence (gimple_transaction_body_ptr ( > as_a <gtransaction *> (stmt)), > --- gcc/tree-ssa-ccp.cc.jj 2022-10-13 08:40:38.478526460 +0200 > +++ gcc/tree-ssa-ccp.cc 2022-10-17 12:40:45.696884755 +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-13 08:40:38.373527904 +0200 > +++ gcc/lto-streamer-out.cc 2022-10-17 12:40:45.720884429 +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-13 08:40:38.371527932 +0200 > +++ gcc/lto-streamer-in.cc 2022-10-17 12:40:45.741884143 +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-13 08:40:37.868534853 +0200 > +++ gcc/cgraphunit.cc 2022-10-17 16:45:46.995270327 +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) > @@ -2373,6 +2383,10 @@ symbol_table::compile (void) > if (node->inlined_to > || gimple_has_body_p (node->decl)) > { > + if (DECL_STRUCT_FUNCTION (node->decl) > + && (DECL_STRUCT_FUNCTION (node->decl)->curr_properties > + & PROP_assumptions_done) != 0) > + continue; > error_found = true; > node->debug (); > } > --- gcc/internal-fn.cc.jj 2022-10-13 08:40:38.218530037 +0200 > +++ gcc/internal-fn.cc 2022-10-17 12:40:45.782883587 +0200 > @@ -4526,5 +4526,4 @@ expand_TRAP (internal_fn, gcall *) > void > expand_ASSUME (internal_fn, gcall *) > { > - gcc_unreachable (); > } > --- gcc/passes.cc.jj 2022-10-13 08:40:38.419527272 +0200 > +++ gcc/passes.cc 2022-10-17 16:38:23.325283213 +0200 > @@ -2660,6 +2660,15 @@ execute_one_pass (opt_pass *pass) > if (dom_info_available_p (CDI_POST_DOMINATORS)) > free_dominance_info (CDI_POST_DOMINATORS); > > + if (cfun->assume_function) > + { > + /* For assume functions, don't release body, keep it around. */ > + cfun->curr_properties |= PROP_assumptions_done; > + pop_cfun (); > + current_pass = NULL; > + return true; > + } > + > tree fn = cfun->decl; > pop_cfun (); > gcc_assert (!cfun); > --- gcc/cgraph.cc.jj 2022-06-27 11:18:02.047066621 +0200 > +++ gcc/cgraph.cc 2022-10-17 16:40:24.319643418 +0200 > @@ -3751,7 +3751,9 @@ cgraph_node::verify_node (void) > && (!DECL_EXTERNAL (decl) || inlined_to) > && !flag_wpa) > { > - if (this_cfun->cfg) > + if ((this_cfun->curr_properties & PROP_assumptions_done) != 0) > + ; > + else if (this_cfun->cfg) > { > hash_set<gimple *> stmts; > > --- gcc/tree-pass.h.jj 2022-07-26 10:32:24.020267414 +0200 > +++ gcc/tree-pass.h 2022-10-17 16:36:12.800052172 +0200 > @@ -227,6 +227,8 @@ protected: > #define PROP_rtl_split_insns (1 << 17) /* RTL has insns split. */ > #define PROP_loop_opts_done (1 << 18) /* SSA loop optimizations > have completed. */ > +#define PROP_assumptions_done (1 << 19) /* Assume function kept > + around. */ > > #define PROP_gimple \ > (PROP_gimple_any | PROP_gimple_lcf | PROP_gimple_leh | PROP_gimple_lomp) > @@ -301,7 +303,8 @@ protected: > /* Rebuild the callgraph edges. */ > #define TODO_rebuild_cgraph_edges (1 << 22) > > -/* Release function body and stop pass manager. */ > +/* Release function body (unless assumption function) > + and stop pass manager. */ > #define TODO_discard_function (1 << 23) > > /* Internally used in execute_function_todo(). */ > @@ -465,6 +468,7 @@ extern gimple_opt_pass *make_pass_copy_p > extern gimple_opt_pass *make_pass_isolate_erroneous_paths (gcc::context *ctxt); > extern gimple_opt_pass *make_pass_early_vrp (gcc::context *ctxt); > extern gimple_opt_pass *make_pass_vrp (gcc::context *ctxt); > +extern gimple_opt_pass *make_pass_assumptions (gcc::context *ctxt); > extern gimple_opt_pass *make_pass_uncprop (gcc::context *ctxt); > extern gimple_opt_pass *make_pass_return_slot (gcc::context *ctxt); > extern gimple_opt_pass *make_pass_reassoc (gcc::context *ctxt); > --- gcc/passes.def.jj 2022-09-23 09:02:56.876313524 +0200 > +++ gcc/passes.def 2022-10-17 16:11:58.526770578 +0200 > @@ -407,6 +407,7 @@ along with GCC; see the file COPYING3. > and thus it should be run last. */ > NEXT_PASS (pass_uncprop); > POP_INSERT_PASSES () > + NEXT_PASS (pass_assumptions); > NEXT_PASS (pass_tm_init); > PUSH_INSERT_PASSES_WITHIN (pass_tm_init) > NEXT_PASS (pass_tm_mark); > --- gcc/timevar.def.jj 2022-09-03 09:35:41.334986488 +0200 > +++ gcc/timevar.def 2022-10-17 16:06:57.336852424 +0200 > @@ -226,6 +226,7 @@ DEFTIMEVAR (TV_TREE_WIDEN_MUL , " > DEFTIMEVAR (TV_TRANS_MEM , "transactional memory") > DEFTIMEVAR (TV_TREE_STRLEN , "tree strlen optimization") > DEFTIMEVAR (TV_TREE_MODREF , "tree modref") > +DEFTIMEVAR (TV_TREE_ASSUMPTIONS , "tree assumptions") > DEFTIMEVAR (TV_CGRAPH_VERIFY , "callgraph verifier") > DEFTIMEVAR (TV_DOM_FRONTIERS , "dominance frontiers") > DEFTIMEVAR (TV_DOMINANCE , "dominance computation") > --- gcc/tree-inline.cc.jj 2022-10-07 09:08:43.887133549 +0200 > +++ gcc/tree-inline.cc 2022-10-17 13:50:11.755320277 +0200 > @@ -1736,6 +1736,11 @@ remap_gimple_stmt (gimple *stmt, copy_bo > (as_a <gomp_critical *> (stmt))); > break; > > + case GIMPLE_ASSUME: > + s1 = remap_gimple_seq (gimple_assume_body (stmt), id); > + copy = gimple_build_assume (gimple_assume_guard (stmt), s1); > + break; > + > case GIMPLE_TRANSACTION: > { > gtransaction *old_trans_stmt = as_a <gtransaction *> (stmt); > --- gcc/tree-vrp.cc.jj 2022-09-23 09:02:57.099310450 +0200 > +++ gcc/tree-vrp.cc 2022-10-17 16:37:10.623268518 +0200 > @@ -4441,6 +4441,35 @@ public: > int my_pass; > }; // class pass_vrp > > +const pass_data pass_data_assumptions = > +{ > + GIMPLE_PASS, /* type */ > + "assumptions", /* name */ > + OPTGROUP_NONE, /* optinfo_flags */ > + TV_TREE_ASSUMPTIONS, /* tv_id */ > + PROP_ssa, /* properties_required */ > + PROP_assumptions_done, /* properties_provided */ > + 0, /* properties_destroyed */ > + 0, /* todo_flags_start */ > + 0, /* todo_flags_end */ > +}; > + > +class pass_assumptions : public gimple_opt_pass > +{ > +public: > + pass_assumptions (gcc::context *ctxt) > + : gimple_opt_pass (pass_data_assumptions, ctxt) > + {} > + > + /* opt_pass methods: */ > + bool gate (function *fun) final override { return fun->assume_function; } > + unsigned int execute (function *) final override > + { > + return TODO_discard_function; > + } > + > +}; // class pass_assumptions > + > } // anon namespace > > gimple_opt_pass * > @@ -4454,3 +4483,9 @@ make_pass_early_vrp (gcc::context *ctxt) > { > return new pass_vrp (ctxt, pass_data_early_vrp); > } > + > +gimple_opt_pass * > +make_pass_assumptions (gcc::context *ctx) > +{ > + return new pass_assumptions (ctx); > +} > --- gcc/cp/cp-tree.h.jj 2022-10-14 09:35:56.201990233 +0200 > +++ gcc/cp/cp-tree.h 2022-10-17 12:40:45.842882772 +0200 > @@ -8280,6 +8280,7 @@ extern tree predeclare_vla (tree); > extern void clear_fold_cache (void); > extern tree lookup_hotness_attribute (tree); > extern tree process_stmt_hotness_attribute (tree, location_t); > +extern tree build_assume_call (location_t, tree); > extern tree process_stmt_assume_attribute (tree, tree, location_t); > extern bool simple_empty_class_p (tree, tree, tree_code); > extern tree fold_builtin_source_location (location_t); > --- gcc/cp/parser.cc.jj 2022-10-14 09:28:28.006164065 +0200 > +++ gcc/cp/parser.cc 2022-10-17 12:40:45.897882025 +0200 > @@ -46012,11 +46012,7 @@ cp_parser_omp_assumption_clauses (cp_par > if (!type_dependent_expression_p (t)) > t = contextual_conv_bool (t, tf_warning_or_error); > if (is_assume && !error_operand_p (t)) > - { > - t = build_call_expr_internal_loc (eloc, IFN_ASSUME, > - void_type_node, 1, t); > - finish_expr_stmt (t); > - } > + finish_expr_stmt (build_assume_call (eloc, t)); > if (!parens.require_close (parser)) > cp_parser_skip_to_closing_parenthesis (parser, > /*recovering=*/true, > --- gcc/cp/cp-gimplify.cc.jj 2022-10-14 09:28:28.154162026 +0200 > +++ gcc/cp/cp-gimplify.cc 2022-10-17 12:40:45.931881563 +0200 > @@ -3101,6 +3101,17 @@ process_stmt_hotness_attribute (tree std > return std_attrs; > } > > +/* Build IFN_ASSUME internal call for assume condition ARG. */ > + > +tree > +build_assume_call (location_t loc, tree arg) > +{ > + if (!processing_template_decl) > + arg = fold_build_cleanup_point_expr (TREE_TYPE (arg), arg); > + return build_call_expr_internal_loc (loc, IFN_ASSUME, void_type_node, > + 1, arg); > +} > + > /* If [[assume (cond)]] appears on this statement, handle it. */ > > tree > @@ -3137,9 +3148,7 @@ process_stmt_assume_attribute (tree std_ > arg = contextual_conv_bool (arg, tf_warning_or_error); > if (error_operand_p (arg)) > continue; > - statement = build_call_expr_internal_loc (attrs_loc, IFN_ASSUME, > - void_type_node, 1, arg); > - finish_expr_stmt (statement); > + finish_expr_stmt (build_assume_call (attrs_loc, arg)); > } > } > return remove_attribute ("gnu", "assume", std_attrs); > --- gcc/cp/pt.cc.jj 2022-10-14 09:28:28.135162288 +0200 > +++ gcc/cp/pt.cc 2022-10-17 12:40:45.985880829 +0200 > @@ -21140,10 +21140,7 @@ tsubst_copy_and_build (tree t, > ret = error_mark_node; > break; > } > - ret = build_call_expr_internal_loc (EXPR_LOCATION (t), > - IFN_ASSUME, > - void_type_node, 1, > - arg); > + ret = build_assume_call (EXPR_LOCATION (t), arg); > RETURN (ret); > } > break; > --- gcc/testsuite/g++.dg/cpp23/attr-assume5.C.jj 2022-10-17 12:40:45.985880829 +0200 > +++ gcc/testsuite/g++.dg/cpp23/attr-assume5.C 2022-10-17 12:40:45.985880829 +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-17 12:40:45.986880816 +0200 > +++ gcc/testsuite/g++.dg/cpp23/attr-assume6.C 2022-10-17 12:40:45.986880816 +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-17 12:40:45.986880816 +0200 > +++ gcc/testsuite/g++.dg/cpp23/attr-assume7.C 2022-10-17 17:39:43.992411442 +0200 > @@ -0,0 +1,56 @@ > +// 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; > +} > + > +int > +corge (int x) > +{ > + [[assume (({ [[assume (x < 42)]]; x > -42; }))]]; > + return x < 42; > +} > + > +int > +garply (int x) > +{ > + [[assume (({ [[assume (++x < 43)]]; x > -42; }))]]; > + return x < 42; > +} > > > Jakub > > -- Richard Biener <rguenther@suse.de> SUSE Software Solutions Germany GmbH, Frankenstrasse 146, 90461 Nuernberg, Germany; GF: Ivo Totev, Andrew Myers, Andrew McDonald, Boudien Moerman; HRB 36809 (AG Nuernberg) ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH] middle-end, v4: IFN_ASSUME support [PR106654] 2022-10-17 15:44 ` [PATCH] middle-end, v4: " Jakub Jelinek 2022-10-18 7:00 ` Richard Biener @ 2022-10-18 21:31 ` Andrew MacLeod 2022-10-19 16:06 ` Jakub Jelinek 1 sibling, 1 reply; 35+ messages in thread From: Andrew MacLeod @ 2022-10-18 21:31 UTC (permalink / raw) To: Jakub Jelinek, Richard Biener; +Cc: Jan Hubicka, gcc-patches, hernandez, aldy [-- Attachment #1.1: Type: text/plain, Size: 6575 bytes --] On 10/17/22 11:44, Jakub Jelinek via Gcc-patches wrote: > > Added 2 tests for nested assumptions (one with a simple assumption > nested in complex one, one with side-effects one nested in complex one). > > So far lightly tested, will test fully overnight. > > 2022-10-17 Jakub Jelinek<jakub@redhat.com> > OK, new prototype update. I have moved the code to a new "class assume_query" which is still in gimple-range.h We don't need all the heavy lifting of a full on ranger, just an ssa-name table and a gori_compute module. GORI requires a range_query class, so assume_query derived form that. patch1 is the same/similar infer processing that looks for assume calls and adds any ranges as a side effect. I removed the hack and now its simply returns whetever the global value is, so its no net effect. patch2 is the assume_query class. When created, it will "process" the current function up front. It currently only works on function with a single integral return statement. It should never fail, it'll simpy return VARYING for anything it cant determine a value for, or if its an inappropriate function. When its create, you can then query using bool assume_range_p (vrange &r, tree name); the value of any ssa-name in the function. They are all pre-calculated, so this is simply picking up any value that was saved. So the good stuff! *Whats now supported?* It begins with the return statement having a value of [1,1] and begins going back to defs, and evaluating any operands on that statement. PHIs are now processed. - If an argument is symbolic, we try substituting the LHS for the argument, and go to its def. continuing the process. - If an argument is a constant, we check if its compatible with the LHS. - If the intersection is undefined, then that edge cannot be executed, and it is ignored - if the is defined, then we examine the predecessor block to see if the exit condition which took this edge provides any useful into. And finally, when the def chain terminates and we cant go any further, it also checks to see if this block has a single predecessor, and if so, if taking the edge to get here was the result of a condition that supplies yet more range info. Caveats. Im not doing a lot of error checking. If I encounter the definition of an ssa-name a second time, I simply stop. This avoid infinite loops, and re-doing the same work. Its unclear to me in a complex case if we might need to do some value merging at merge points, but at this point, we make no attempt. I'd need to find a case where this was problematic, and I see none yet. *What can you expect? *Using a testcase sample attr-assume-7.C (and patch 3 which hacks VRP to spit out what values it would find) it provides the following values: bar() <bb 2> : x_3 = x_2(D) + 1; _4 = x_2(D) <= 41; return _4; for an assume function, x_2(D) would have a range of [irange] int [-INF, 41] for an assume function, _4 would have a range of [irange] bool [1, 1] baz() |<bb 2> : x_4 = x_3(D) + 1; w.0_7 = w; w.1_8 = w.0_7 + 1; w = w.1_8; if (x_4 == 51) goto <bb 3>; [INV] else goto <bb 4>; [INV] <bb 3> : // predicted unlikely by early return (on trees) predictor. goto <bb 9>; [INV] <bb 4> : if (x_4 == 53) goto <bb 5>; [INV] else goto <bb 6>; [INV] <bb 5> : // predicted unlikely by goto predictor. goto <bb 9>; [INV] <bb 6> : if (x_4 == 64) goto <bb 7>; [INV] else goto <bb 8>; [INV] <bb 7> : _12 = __cxa_allocate_exception (4); MEM[(int *)_12] = 1; __cxa_throw (_12, &_ZTIi, 0B); <bb 8> : _10 = x_4 == 43; <bb 9> : # _1 = PHI <0(3), _10(8), 0(5)> return _1; for an assume function, _1 would have a range of [irange] bool [1, 1] for an assume function, x_3(D) would have a range of [irange] int [42, 42] NONZERO 0x2a for an assume function, x_4 would have a range of [irange] int [43, 43] NONZERO 0x2b for an assume function, _10 would have a range of [irange] bool [1, 1] qux() <bb 2> : _4 = s.a; if (_4 == 42) goto <bb 3>; [INV] else goto <bb 4>; [INV] <bb 3> : _5 = s.b; if (_5 == 43) goto <bb 5>; [INV] else goto <bb 4>; [INV] <bb 4> : <bb 5> : # iftmp.2_1 = PHI <1(3), 0(4)> return iftmp.2_1; for an assume function, iftmp.2_1 would have a range of [irange] bool [1, 1] for an assume function, _4 would have a range of [irange] int [42, 42] NONZERO 0x2a for an assume function, _5 would have a range of [irange] int [43, 43] NONZERO 0x2b S::foo() <bb 2> : _5 = this_4(D)->a; if (_5 == 42) goto <bb 3>; [INV] else goto <bb 4>; [INV] <bb 3> : _6 = this_4(D)->b; if (_6 == 43) goto <bb 5>; [INV] else goto <bb 4>; [INV] <bb 4> : <bb 5> : # iftmp.3_1 = PHI <1(3), 0(4)> return iftmp.3_1; for an assume function, iftmp.3_1 would have a range of [irange] bool [1, 1] for an assume function, _5 would have a range of [irange] int [42, 42] NONZERO 0x2a for an assume function, _6 would have a range of [irange] int [43, 43] NONZERO 0x2b corge() <bb 2> : if (x_2(D) <= 41) goto <bb 4>; [INV] else goto <bb 3>; [INV] <bb 3> : __builtin_unreachable (); <bb 4> : _3 = x_2(D) >= -41; return _3; for an assume function, x_2(D) would have a range of [irange] int [-41, +INF] for an assume function, _3 would have a range of [irange] bool [1, 1] --------------------------------------------------------------------------------------------------------- THis seems to provide a reasonable amount of functionality. As you can see by the qux() and S:foo() cases, if you can figure out how to map structures and pointers as parameters, we can produce the ranges "produced" when they are loaded. ie we know _4 is [42,42] and that it was loaded from s.a: _4 = s.a; Presumably what is needed is a pass (which can be anywhere you want) which invokes an assume_query, and then tries to map the parameter values to ssa-names. Anyway, gives you something to experiement with. If you would find a different interface useful, let me know, or if there are limitations or other expansions we might need. This seems like something reasonable for you to start working with? Let me know what you think. This all bootstraps fine, I could check it into the code base if it helps. Andrew [-- Attachment #2: 0001-Infer-support.patch --] [-- Type: text/x-patch, Size: 2531 bytes --] From 20805fb54aee0d0e337d53facf77a5c66f9f0c9c Mon Sep 17 00:00:00 2001 From: Andrew MacLeod <amacleod@redhat.com> Date: Tue, 18 Oct 2022 16:29:49 -0400 Subject: [PATCH 1/3] Infer support --- gcc/gimple-range-infer.cc | 48 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/gcc/gimple-range-infer.cc b/gcc/gimple-range-infer.cc index f0d66d047a6..95839445155 100644 --- a/gcc/gimple-range-infer.cc +++ b/gcc/gimple-range-infer.cc @@ -36,6 +36,25 @@ along with GCC; see the file COPYING3. If not see #include "gimple-walk.h" #include "cfganal.h" + +// This routine needs to be provided to look up any ASSUME values +// for name in ASSUME_ID. Return TRUE if it has a value. + +bool +query_assume_call (vrange &r, tree assume_id, tree name) +{ + if (dump_file) + fprintf (dump_file, "query_assume_call injection\n"); + if (assume_id != NULL_TREE) + { + // Just return the global value until this can be provided. + gimple_range_global (r, name); + return true; + } + return false; +} + + // Adapted from infer_nonnull_range_by_dereference and check_loadstore // to process nonnull ssa_name OP in S. DATA contains a pointer to a // stmt range inference instance. @@ -111,6 +130,35 @@ gimple_infer_range::gimple_infer_range (gimple *s) // Fallthru and walk load/store ops now. } + // Look for ASSUME calls, and call query_assume_call for each argument + // to determine if there is any inferred range to be had. + if (is_a<gcall *> (s) && gimple_call_internal_p (s) + && gimple_call_internal_fn (s) == IFN_ASSUME) + { + tree assume_id = gimple_call_arg (s, 0); + for (unsigned i = 1; i < gimple_call_num_args (s); i++) + { + tree op = gimple_call_arg (s, i); + tree type = TREE_TYPE (op); + if (gimple_range_ssa_p (op) && Value_Range::supports_type_p (type)) + { + Value_Range assume_range (type); + if (query_assume_call (assume_range, assume_id, op)) + { + add_range (op, assume_range); + if (dump_file) + { + print_generic_expr (dump_file, assume_id, TDF_SLIM); + fprintf (dump_file, " assume inferred range of "); + print_generic_expr (dump_file, op, TDF_SLIM); + fprintf (dump_file, " to "); + assume_range.dump (dump_file); + fputc ('\n', dump_file); + } + } + } + } + } // Look for possible non-null values. if (flag_delete_null_pointer_checks && gimple_code (s) != GIMPLE_ASM && !gimple_clobber_p (s)) -- 2.37.3 [-- Attachment #3: 0002-assume_query-support.patch --] [-- Type: text/x-patch, Size: 7704 bytes --] From 3f3246d5f3297f61e96998a6bc87ec1e409238e5 Mon Sep 17 00:00:00 2001 From: Andrew MacLeod <amacleod@redhat.com> Date: Tue, 18 Oct 2022 16:30:04 -0400 Subject: [PATCH 2/3] assume_query support --- gcc/gimple-range-gori.h | 6 +- gcc/gimple-range.cc | 164 ++++++++++++++++++++++++++++++++++++++++ gcc/gimple-range.h | 17 +++++ 3 files changed, 184 insertions(+), 3 deletions(-) diff --git a/gcc/gimple-range-gori.h b/gcc/gimple-range-gori.h index c7a32162a1b..6cc533b58b2 100644 --- a/gcc/gimple-range-gori.h +++ b/gcc/gimple-range-gori.h @@ -165,15 +165,15 @@ public: bool has_edge_range_p (tree name, basic_block bb = NULL); bool has_edge_range_p (tree name, edge e); void dump (FILE *f); + bool compute_operand_range (vrange &r, gimple *stmt, const vrange &lhs, + tree name, class fur_source &src, + value_relation *rel = NULL); private: bool refine_using_relation (tree op1, vrange &op1_range, tree op2, vrange &op2_range, fur_source &src, relation_kind k); bool may_recompute_p (tree name, edge e); bool may_recompute_p (tree name, basic_block bb = NULL); - bool compute_operand_range (vrange &r, gimple *stmt, const vrange &lhs, - tree name, class fur_source &src, - value_relation *rel = NULL); bool compute_operand_range_switch (vrange &r, gswitch *s, const vrange &lhs, tree name, fur_source &src); bool compute_operand1_range (vrange &r, gimple_range_op_handler &handler, diff --git a/gcc/gimple-range.cc b/gcc/gimple-range.cc index d67d6499c78..4befe724e38 100644 --- a/gcc/gimple-range.cc +++ b/gcc/gimple-range.cc @@ -645,3 +645,167 @@ disable_ranger (struct function *fun) delete fun->x_range_query; fun->x_range_query = NULL; } + +// ------------------------------------------------------------------------ + +// If there is a non-varying value associated with NAME, return true and the +// range in R. + +bool +assume_query::assume_range_p (vrange &r, tree name) +{ + if (global.get_global_range (r, name)) + return !r.varying_p (); + return false; +} + +// Query used by GORI to pick up any known value on entry to a block. + +bool +assume_query::range_of_expr (vrange &r, tree expr, gimple *stmt) +{ + if (!gimple_range_ssa_p (expr)) + return get_tree_range (r, expr, stmt); + + if (!global.get_global_range (r, expr)) + r.set_varying (TREE_TYPE (expr)); + return true; +} + +// If the current function returns an integral value, and has a single return +// statement, it will calculate any SSA_NAMES is can determine ranges forr +// assuming the function returns 1. + +assume_query::assume_query () +{ + basic_block exit_bb = EXIT_BLOCK_PTR_FOR_FN (cfun); + if (single_pred_p (exit_bb)) + { + basic_block bb = single_pred (exit_bb); + gimple_stmt_iterator gsi = gsi_last_nondebug_bb (bb); + if (gsi_end_p (gsi)) + return; + gimple *s = gsi_stmt (gsi); + if (!is_a<greturn *> (s)) + return; + greturn *gret = as_a<greturn *> (s); + tree op = gimple_return_retval (gret); + if (!gimple_range_ssa_p (op)) + return; + tree lhs_type = TREE_TYPE (op); + if (!irange::supports_p (lhs_type)) + return; + + unsigned prec = TYPE_PRECISION (lhs_type); + int_range<2> lhs_range (lhs_type, wi::one (prec), wi::one (prec)); + global.set_global_range (op, lhs_range); + + gimple *def = SSA_NAME_DEF_STMT (op); + if (!def || gimple_get_lhs (def) != op) + return; + fur_stmt src (gret, this); + calculate_stmt (def, lhs_range, src); + } +} + +// Evaluate operand OP on statement S, using the provided LHS range. +// If successful, set the range in the global table, then visit OP's def stmt. + +void +assume_query::calculate_op (tree op, gimple *s, vrange &lhs, fur_source &src) +{ + Value_Range op_range (TREE_TYPE (op)); + if (!global.get_global_range (op_range, op) + && m_gori.compute_operand_range (op_range, s, lhs, op, src) + && !op_range.varying_p ()) + { + global.set_global_range (op, op_range); + gimple *def_stmt = SSA_NAME_DEF_STMT (op); + if (def_stmt && gimple_get_lhs (def_stmt) == op) + calculate_stmt (def_stmt, op_range, src); + } +} + +// Evaluate PHI statement, using the provided LHS range. +// Check each constant argument predecessor if it can be taken +// provide LHS to any symbolic argmeuents, and process their def statements. + +void +assume_query::calculate_phi (gphi *phi, vrange &lhs_range, fur_source &src) +{ + for (unsigned x= 0; x < gimple_phi_num_args (phi); x++) + { + tree arg = gimple_phi_arg_def (phi, x); + Value_Range arg_range (TREE_TYPE (arg)); + if (gimple_range_ssa_p (arg)) + { + // A symbol arg will be the LHS value. + arg_range = lhs_range; + range_cast (arg_range, TREE_TYPE (arg)); + if (!global.get_global_range (arg_range, arg)) + { + global.set_global_range (arg, arg_range); + gimple *def_stmt = SSA_NAME_DEF_STMT (arg); + if (def_stmt && gimple_get_lhs (def_stmt) == arg) + calculate_stmt (def_stmt, arg_range, src); + } + } + else + { + if (get_tree_range (arg_range, arg, NULL)) + { + // If this is a constant value that differs from LHS, this + // edge cannot be taken. + arg_range.intersect (lhs_range); + if (arg_range.undefined_p ()) + continue; + // Otherwise Check the condition feeding this edge + edge e = gimple_phi_arg_edge (phi, x); + check_taken_edge (e, src); + } + } + } +} + +// If an edge is known to be taken, examine the outgoing edge to see +// if it carries any range information that can also be evaluated. + +void +assume_query::check_taken_edge (edge e, fur_source &src) +{ + gimple *stmt = gimple_outgoing_range_stmt_p (e->src); + if (stmt && is_a<gcond *> (stmt)) + { + int_range<2> cond; + gcond_edge_range (cond, e); + calculate_stmt (stmt, cond, src); + } +} + +// Evaluate statement S which produces range LHS_RANGE. + +void +assume_query::calculate_stmt (gimple *s, vrange &lhs_range, fur_source &src) +{ + gimple_range_op_handler handler (s); + if (handler) + { + tree op = gimple_range_ssa_p (handler.operand1 ()); + if (op) + calculate_op (op, s, lhs_range, src); + op = gimple_range_ssa_p (handler.operand2 ()); + if (op) + calculate_op (op, s, lhs_range, src); + } + else if (is_a<gphi *> (s)) + { + calculate_phi (as_a<gphi *> (s), lhs_range, src); + // Don't further check predecessors of blocks with PHIs. + return; + } + + // Even if the walk back terminates before the top, if this is a single + // predecessor block, see if the predecessor provided any ranges to get here. + if (single_pred_p (gimple_bb (s))) + check_taken_edge (single_pred_edge (gimple_bb (s)), src); +} diff --git a/gcc/gimple-range.h b/gcc/gimple-range.h index 8b2ff5685e5..4dc7bc33c5f 100644 --- a/gcc/gimple-range.h +++ b/gcc/gimple-range.h @@ -80,4 +80,21 @@ extern gimple_ranger *enable_ranger (struct function *m, bool use_imm_uses = true); extern void disable_ranger (struct function *); +class assume_query : public range_query +{ +public: + assume_query (); + bool assume_range_p (vrange &r, tree name); + virtual bool range_of_expr (vrange &r, tree expr, gimple * = NULL); +protected: + void calculate_stmt (gimple *s, vrange &lhs_range, fur_source &src); + void calculate_op (tree op, gimple *s, vrange &lhs, fur_source &src); + void calculate_phi (gphi *phi, vrange &lhs_range, fur_source &src); + void check_taken_edge (edge e, fur_source &src); + + ssa_global_cache global; + gori_compute m_gori; +}; + + #endif // GCC_GIMPLE_RANGE_H -- 2.37.3 [-- Attachment #4: 0003-Show-output.patch --] [-- Type: text/x-patch, Size: 1388 bytes --] From 4d920bd7ae68f04cb387875f71695503f4db86ae Mon Sep 17 00:00:00 2001 From: Andrew MacLeod <amacleod@redhat.com> Date: Mon, 17 Oct 2022 12:28:21 -0400 Subject: [PATCH 3/3] Show output --- gcc/tree-vrp.cc | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/gcc/tree-vrp.cc b/gcc/tree-vrp.cc index 1adb15c9934..3c0d5c43215 100644 --- a/gcc/tree-vrp.cc +++ b/gcc/tree-vrp.cc @@ -4345,6 +4345,30 @@ execute_ranger_vrp (struct function *fun, bool warn_array_bounds_p) scev_initialize (); calculate_dominance_info (CDI_DOMINATORS); + assume_query *r2 = new assume_query (); + for (unsigned i = 0; i < num_ssa_names; i++) + { + tree name = ssa_name (i); + if (!name || !gimple_range_ssa_p (name)) + continue; + tree type = TREE_TYPE (name); + if (!Value_Range::supports_type_p (type)) + continue; + Value_Range assume_range (type); + if (r2->assume_range_p (assume_range, name)) + { + if (dump_file) + { + fprintf (dump_file, "for an assume function, "); + print_generic_expr (dump_file, name, TDF_SLIM); + fprintf (dump_file, " would have a range of "); + assume_range.dump (dump_file); + fputc ('\n', dump_file); + } + } + } + delete r2; + set_all_edges_as_executable (fun); gimple_ranger *ranger = enable_ranger (fun, false); rvrp_folder folder (ranger); -- 2.37.3 ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH] middle-end, v4: IFN_ASSUME support [PR106654] 2022-10-18 21:31 ` Andrew MacLeod @ 2022-10-19 16:06 ` Jakub Jelinek 2022-10-19 16:55 ` Andrew MacLeod 2022-10-19 17:14 ` Andrew MacLeod 0 siblings, 2 replies; 35+ messages in thread From: Jakub Jelinek @ 2022-10-19 16:06 UTC (permalink / raw) To: Andrew MacLeod; +Cc: Richard Biener, Jan Hubicka, gcc-patches, hernandez, aldy Hi! On Tue, Oct 18, 2022 at 05:31:58PM -0400, Andrew MacLeod wrote: > Anyway, gives you something to experiement with. If you would find a > different interface useful, let me know, or if there are limitations or > other expansions we might need. This seems like something reasonable for > you to start working with? Thanks for working on this. > + // Look for ASSUME calls, and call query_assume_call for each argument > + // to determine if there is any inferred range to be had. > + if (is_a<gcall *> (s) && gimple_call_internal_p (s) > + && gimple_call_internal_fn (s) == IFN_ASSUME) > + { > + tree assume_id = gimple_call_arg (s, 0); > + for (unsigned i = 1; i < gimple_call_num_args (s); i++) > + { > + tree op = gimple_call_arg (s, i); > + tree type = TREE_TYPE (op); > + if (gimple_range_ssa_p (op) && Value_Range::supports_type_p (type)) > + { > + Value_Range assume_range (type); > + if (query_assume_call (assume_range, assume_id, op)) > + { > + add_range (op, assume_range); > + if (dump_file) > + { > + print_generic_expr (dump_file, assume_id, TDF_SLIM); > + fprintf (dump_file, " assume inferred range of "); > + print_generic_expr (dump_file, op, TDF_SLIM); > + fprintf (dump_file, " to "); > + assume_range.dump (dump_file); > + fputc ('\n', dump_file); > + } > + } Not sure I understand this part. op is whatever we pass as the ith argument to IFN_ASSUME. I'd expect that at this point one needs to remap that to the (i-1)th PARM_DECL of assume_id (so e.g. when you have the above loop you could as well start with DECL_ARGUMENTS and move that to DECL_CHAIN at the end of every iteration. And then query ssa_default_def (DECL_STRUCT_FUNCTION (assume_id), parm) in each case and get global range of what that returns. > + for (unsigned x= 0; x < gimple_phi_num_args (phi); x++) for (unsigned x = 0; ... ? > @@ -4345,6 +4345,30 @@ execute_ranger_vrp (struct function *fun, bool warn_array_bounds_p) > scev_initialize (); > calculate_dominance_info (CDI_DOMINATORS); > > + assume_query *r2 = new assume_query (); > + for (unsigned i = 0; i < num_ssa_names; i++) > + { > + tree name = ssa_name (i); > + if (!name || !gimple_range_ssa_p (name)) > + continue; > + tree type = TREE_TYPE (name); > + if (!Value_Range::supports_type_p (type)) > + continue; > + Value_Range assume_range (type); > + if (r2->assume_range_p (assume_range, name)) > + { > + if (dump_file) > + { > + fprintf (dump_file, "for an assume function, "); > + print_generic_expr (dump_file, name, TDF_SLIM); > + fprintf (dump_file, " would have a range of "); > + assume_range.dump (dump_file); > + fputc ('\n', dump_file); > + } > + } > + } > + delete r2; I have expected (but tell me if that isn't possible) this could be something done in the new pass_assumptions::execute () rather than vrp and you'd create the assume_query there (i.e. just for assume_functions) and then query it solely for ssa_default_def of the parameters and save in SSA_NAME_RANGE_INFO. But my knowledge about ranger is fairly limited... Jakub ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH] middle-end, v4: IFN_ASSUME support [PR106654] 2022-10-19 16:06 ` Jakub Jelinek @ 2022-10-19 16:55 ` Andrew MacLeod 2022-10-19 17:39 ` Jakub Jelinek 2022-10-19 17:14 ` Andrew MacLeod 1 sibling, 1 reply; 35+ messages in thread From: Andrew MacLeod @ 2022-10-19 16:55 UTC (permalink / raw) To: Jakub Jelinek; +Cc: Richard Biener, Jan Hubicka, gcc-patches, hernandez, aldy [-- Attachment #1: Type: text/plain, Size: 4347 bytes --] On 10/19/22 12:06, Jakub Jelinek wrote: > Hi! > > On Tue, Oct 18, 2022 at 05:31:58PM -0400, Andrew MacLeod wrote: >> Anyway, gives you something to experiement with. If you would find a >> different interface useful, let me know, or if there are limitations or >> other expansions we might need. This seems like something reasonable for >> you to start working with? > Thanks for working on this. > >> + // Look for ASSUME calls, and call query_assume_call for each argument >> + // to determine if there is any inferred range to be had. >> + if (is_a<gcall *> (s) && gimple_call_internal_p (s) >> + && gimple_call_internal_fn (s) == IFN_ASSUME) >> + { >> + tree assume_id = gimple_call_arg (s, 0); >> + for (unsigned i = 1; i < gimple_call_num_args (s); i++) >> + { >> + tree op = gimple_call_arg (s, i); >> + tree type = TREE_TYPE (op); >> + if (gimple_range_ssa_p (op) && Value_Range::supports_type_p (type)) >> + { >> + Value_Range assume_range (type); >> + if (query_assume_call (assume_range, assume_id, op)) >> + { >> + add_range (op, assume_range); >> + if (dump_file) >> + { >> + print_generic_expr (dump_file, assume_id, TDF_SLIM); >> + fprintf (dump_file, " assume inferred range of "); >> + print_generic_expr (dump_file, op, TDF_SLIM); >> + fprintf (dump_file, " to "); >> + assume_range.dump (dump_file); >> + fputc ('\n', dump_file); >> + } >> + } > Not sure I understand this part. op is whatever we pass as the ith > argument to IFN_ASSUME. I'd expect that at this point one needs to > remap that to the (i-1)th PARM_DECL of assume_id (so e.g. when you > have the above loop you could as well start with DECL_ARGUMENTS and move > that to DECL_CHAIN at the end of every iteration. And then > query ssa_default_def (DECL_STRUCT_FUNCTION (assume_id), parm) > in each case and get global range of what that returns. OK, this is the bit of code I dont know how to write :-) yes, op is the name of the value within this current function, and yes, that needs to be mapped to the argument decl in the assume function. Then we need to query what range was given to that name during the assume pass. when that is returned, the add_range (op, range) will inject it as a side effect. Can you write that loop? > >> + for (unsigned x= 0; x < gimple_phi_num_args (phi); x++) > for (unsigned x = 0; ... > ? > >> @@ -4345,6 +4345,30 @@ execute_ranger_vrp (struct function *fun, bool warn_array_bounds_p) >> scev_initialize (); >> calculate_dominance_info (CDI_DOMINATORS); >> >> + assume_query *r2 = new assume_query (); >> + for (unsigned i = 0; i < num_ssa_names; i++) >> + { >> + tree name = ssa_name (i); >> + if (!name || !gimple_range_ssa_p (name)) >> + continue; >> + tree type = TREE_TYPE (name); >> + if (!Value_Range::supports_type_p (type)) >> + continue; >> + Value_Range assume_range (type); >> + if (r2->assume_range_p (assume_range, name)) >> + { >> + if (dump_file) >> + { >> + fprintf (dump_file, "for an assume function, "); >> + print_generic_expr (dump_file, name, TDF_SLIM); >> + fprintf (dump_file, " would have a range of "); >> + assume_range.dump (dump_file); >> + fputc ('\n', dump_file); >> + } >> + } >> + } >> + delete r2; > I have expected (but tell me if that isn't possible) this could be something > done in the new pass_assumptions::execute () rather than vrp and you'd > create the assume_query there (i.e. just for assume_functions) and then > query it solely for ssa_default_def of the parameters and save in > SSA_NAME_RANGE_INFO. I just discovered the assumption pass, and I have moved it to there. I dont know much about managing the parameters, but presumably yes, we'd only query it for the parameters........... I was showing the query for every name just to show what its producing. As for storing it, SSA_NAME_RANGE_INFO is for the current function, that woud be easy. if we store it there, how do we look up that range from another functi\on when we have the ASSUME_ID ? That was unclear to me., I've attached the replacement version of 0003* which uses the assume pass, and writes the global values out. It still loops over all names at the moment Andrew > [-- Attachment #2: 0003-Show-output-in-assume-pass.patch --] [-- Type: text/x-patch, Size: 1439 bytes --] From 655627144b8d8c02842d15ec83d720180231564a Mon Sep 17 00:00:00 2001 From: Andrew MacLeod <amacleod@redhat.com> Date: Mon, 17 Oct 2022 12:28:21 -0400 Subject: [PATCH 3/3] Show output in assume pass --- gcc/tree-vrp.cc | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/gcc/tree-vrp.cc b/gcc/tree-vrp.cc index 1adb15c9934..3ec3d7daa74 100644 --- a/gcc/tree-vrp.cc +++ b/gcc/tree-vrp.cc @@ -4465,6 +4465,35 @@ public: bool gate (function *fun) final override { return fun->assume_function; } unsigned int execute (function *) final override { + assume_query query; + if (dump_file) + fprintf (dump_file, "Assumptions :\n--------------\n"); + for (unsigned i = 0; i < num_ssa_names; i++) + { + tree name = ssa_name (i); + if (!name || !gimple_range_ssa_p (name)) + continue; + tree type = TREE_TYPE (name); + if (!Value_Range::supports_type_p (type)) + continue; + Value_Range assume_range (type); + if (query.assume_range_p (assume_range, name)) + { + set_range_info (name, assume_range); + if (dump_file) + { + print_generic_expr (dump_file, name, TDF_SLIM); + fprintf (dump_file, " -> "); + assume_range.dump (dump_file); + fputc ('\n', dump_file); + } + } + } + if (dump_file) + { + fputc ('\n', dump_file); + gimple_dump_cfg (dump_file, dump_flags); + } return TODO_discard_function; } -- 2.37.3 ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH] middle-end, v4: IFN_ASSUME support [PR106654] 2022-10-19 16:55 ` Andrew MacLeod @ 2022-10-19 17:39 ` Jakub Jelinek 2022-10-19 17:41 ` Jakub Jelinek 0 siblings, 1 reply; 35+ messages in thread From: Jakub Jelinek @ 2022-10-19 17:39 UTC (permalink / raw) To: Andrew MacLeod; +Cc: Jan Hubicka, Richard Biener, gcc-patches On Wed, Oct 19, 2022 at 12:55:12PM -0400, Andrew MacLeod via Gcc-patches wrote: > > Not sure I understand this part. op is whatever we pass as the ith > > argument to IFN_ASSUME. I'd expect that at this point one needs to > > remap that to the (i-1)th PARM_DECL of assume_id (so e.g. when you > > have the above loop you could as well start with DECL_ARGUMENTS and move > > that to DECL_CHAIN at the end of every iteration. And then > > query ssa_default_def (DECL_STRUCT_FUNCTION (assume_id), parm) > > in each case and get global range of what that returns. > > OK, this is the bit of code I dont know how to write :-) > > yes, op is the name of the value within this current function, and yes, that > needs to be mapped to the argument decl in the assume function. Then we > need to query what range was given to that name during the assume pass. > when that is returned, the add_range (op, range) will inject it as a side > effect. > > Can you write that loop? I meant something like (untested code): && gimple_call_internal_fn (s) == IFN_ASSUME) { tree assume_id = gimple_call_arg (s, 0); - for (unsigned i = 1; i < gimple_call_num_args (s); i++) + tree parm = DECL_ARGUMENTS (assume_id); + struct function *fun = DECL_STRUCT_FUNCTION (assume_id); + for (unsigned i = 1; + i < gimple_call_num_args (s) && parm; + i++, parm = DECL_CHAIN (parm)) { tree op = gimple_call_arg (s, i); tree type = TREE_TYPE (op); + tree arg = ssa_default_def (fun, parm); + if (arg == NULL_TREE) + continue; if (gimple_range_ssa_p (op) && Value_Range::supports_type_p (type)) { Value_Range assume_range (type); and querying SSA_NAME_RANGE_INFO of arg rather than op. > > > > > > + for (unsigned x= 0; x < gimple_phi_num_args (phi); x++) > > for (unsigned x = 0; ... > > ? > > > > > @@ -4345,6 +4345,30 @@ execute_ranger_vrp (struct function *fun, bool warn_array_bounds_p) > > > scev_initialize (); > > > calculate_dominance_info (CDI_DOMINATORS); > > > + assume_query *r2 = new assume_query (); > > > + for (unsigned i = 0; i < num_ssa_names; i++) > > > + { > > > + tree name = ssa_name (i); > > > + if (!name || !gimple_range_ssa_p (name)) > > > + continue; > > > + tree type = TREE_TYPE (name); > > > + if (!Value_Range::supports_type_p (type)) > > > + continue; > > > + Value_Range assume_range (type); > > > + if (r2->assume_range_p (assume_range, name)) > > > + { > > > + if (dump_file) > > > + { > > > + fprintf (dump_file, "for an assume function, "); > > > + print_generic_expr (dump_file, name, TDF_SLIM); > > > + fprintf (dump_file, " would have a range of "); > > > + assume_range.dump (dump_file); > > > + fputc ('\n', dump_file); > > > + } > > > + } > > > + } > > > + delete r2; > > I have expected (but tell me if that isn't possible) this could be something > > done in the new pass_assumptions::execute () rather than vrp and you'd > > create the assume_query there (i.e. just for assume_functions) and then > > query it solely for ssa_default_def of the parameters and save in > > SSA_NAME_RANGE_INFO. > > I just discovered the assumption pass, and I have moved it to there. > > I dont know much about managing the parameters, but presumably yes, we'd > only query it for the parameters........... I was showing the query for > every name just to show what its producing. Inside of the assume function (cfun->assume_function being true) one could again walk DECL_ARGUMENTS and for arguments with types which ranger is able to cope with and they are reg types, ssa_default_def (cfun, parm) to get the SSA_NAME of the default def (if any). > --- a/gcc/tree-vrp.cc > +++ b/gcc/tree-vrp.cc > @@ -4465,6 +4465,35 @@ public: > bool gate (function *fun) final override { return fun->assume_function; } Regarding your second mail, see above gate, this pass is only run for assume functions and nothing else. > unsigned int execute (function *) final override > { > + assume_query query; > + if (dump_file) > + fprintf (dump_file, "Assumptions :\n--------------\n"); > + for (unsigned i = 0; i < num_ssa_names; i++) > + { > + tree name = ssa_name (i); > + if (!name || !gimple_range_ssa_p (name)) > + continue; > + tree type = TREE_TYPE (name); > + if (!Value_Range::supports_type_p (type)) > + continue; > + Value_Range assume_range (type); > + if (query.assume_range_p (assume_range, name)) > + { > + set_range_info (name, assume_range); > + if (dump_file) > + { > + print_generic_expr (dump_file, name, TDF_SLIM); > + fprintf (dump_file, " -> "); > + assume_range.dump (dump_file); > + fputc ('\n', dump_file); > + } > + } > + } > + if (dump_file) > + { > + fputc ('\n', dump_file); > + gimple_dump_cfg (dump_file, dump_flags); > + } > return TODO_discard_function; > } Jakub ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH] middle-end, v4: IFN_ASSUME support [PR106654] 2022-10-19 17:39 ` Jakub Jelinek @ 2022-10-19 17:41 ` Jakub Jelinek 2022-10-19 18:25 ` Andrew MacLeod 0 siblings, 1 reply; 35+ messages in thread From: Jakub Jelinek @ 2022-10-19 17:41 UTC (permalink / raw) To: Andrew MacLeod, Jan Hubicka, Richard Biener, gcc-patches On Wed, Oct 19, 2022 at 07:39:05PM +0200, Jakub Jelinek via Gcc-patches wrote: > On Wed, Oct 19, 2022 at 12:55:12PM -0400, Andrew MacLeod via Gcc-patches wrote: > > > Not sure I understand this part. op is whatever we pass as the ith > > > argument to IFN_ASSUME. I'd expect that at this point one needs to > > > remap that to the (i-1)th PARM_DECL of assume_id (so e.g. when you > > > have the above loop you could as well start with DECL_ARGUMENTS and move > > > that to DECL_CHAIN at the end of every iteration. And then > > > query ssa_default_def (DECL_STRUCT_FUNCTION (assume_id), parm) > > > in each case and get global range of what that returns. > > > > OK, this is the bit of code I dont know how to write :-) > > > > yes, op is the name of the value within this current function, and yes, that > > needs to be mapped to the argument decl in the assume function. Then we > > need to query what range was given to that name during the assume pass. > > when that is returned, the add_range (op, range) will inject it as a side > > effect. > > > > Can you write that loop? > > I meant something like (untested code): > && gimple_call_internal_fn (s) == IFN_ASSUME) > { > tree assume_id = gimple_call_arg (s, 0); > - for (unsigned i = 1; i < gimple_call_num_args (s); i++) > + tree parm = DECL_ARGUMENTS (assume_id); > + struct function *fun = DECL_STRUCT_FUNCTION (assume_id); > + for (unsigned i = 1; > + i < gimple_call_num_args (s) && parm; > + i++, parm = DECL_CHAIN (parm)) > { > tree op = gimple_call_arg (s, i); > tree type = TREE_TYPE (op); > + tree arg = ssa_default_def (fun, parm); > + if (arg == NULL_TREE) > + continue; > if (gimple_range_ssa_p (op) && Value_Range::supports_type_p (type)) > { > Value_Range assume_range (type); > and querying SSA_NAME_RANGE_INFO of arg rather than op. Oops, the tree arg = ... stuff would need to be moved into the if (gimple_range...) body, it won't work for aggregate PARM_DECLs etc., only PARM_DECLs with is_gimple_reg_type (TREE_TYPE (arg)). Jakub ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH] middle-end, v4: IFN_ASSUME support [PR106654] 2022-10-19 17:41 ` Jakub Jelinek @ 2022-10-19 18:25 ` Andrew MacLeod 0 siblings, 0 replies; 35+ messages in thread From: Andrew MacLeod @ 2022-10-19 18:25 UTC (permalink / raw) To: Jakub Jelinek, Jan Hubicka, Richard Biener, gcc-patches [-- Attachment #1: Type: text/plain, Size: 3184 bytes --] On 10/19/22 13:41, Jakub Jelinek wrote: > On Wed, Oct 19, 2022 at 07:39:05PM +0200, Jakub Jelinek via Gcc-patches wrote: >> On Wed, Oct 19, 2022 at 12:55:12PM -0400, Andrew MacLeod via Gcc-patches wrote: >>>> Not sure I understand this part. op is whatever we pass as the ith >>>> argument to IFN_ASSUME. I'd expect that at this point one needs to >>>> remap that to the (i-1)th PARM_DECL of assume_id (so e.g. when you >>>> have the above loop you could as well start with DECL_ARGUMENTS and move >>>> that to DECL_CHAIN at the end of every iteration. And then >>>> query ssa_default_def (DECL_STRUCT_FUNCTION (assume_id), parm) >>>> in each case and get global range of what that returns. >>> OK, this is the bit of code I dont know how to write :-) >>> >>> yes, op is the name of the value within this current function, and yes, that >>> needs to be mapped to the argument decl in the assume function. Then we >>> need to query what range was given to that name during the assume pass. >>> when that is returned, the add_range (op, range) will inject it as a side >>> effect. >>> >>> Can you write that loop? >> I meant something like (untested code): >> && gimple_call_internal_fn (s) == IFN_ASSUME) >> { >> tree assume_id = gimple_call_arg (s, 0); >> - for (unsigned i = 1; i < gimple_call_num_args (s); i++) >> + tree parm = DECL_ARGUMENTS (assume_id); >> + struct function *fun = DECL_STRUCT_FUNCTION (assume_id); >> + for (unsigned i = 1; >> + i < gimple_call_num_args (s) && parm; >> + i++, parm = DECL_CHAIN (parm)) >> { >> tree op = gimple_call_arg (s, i); >> tree type = TREE_TYPE (op); >> + tree arg = ssa_default_def (fun, parm); >> + if (arg == NULL_TREE) >> + continue; >> if (gimple_range_ssa_p (op) && Value_Range::supports_type_p (type)) >> { >> Value_Range assume_range (type); >> and querying SSA_NAME_RANGE_INFO of arg rather than op. Thanks, I had actually come to most of those conclusions already, except for the DECL_STRUCT_FUNCTION bit.. I was stuck on that :-) So the SSA_NAMEs for default_defs are never disposed of? so I can query them even though they are not in the current function decl? huh. I did not know that. OK, attached is the latest set of patches. not bootstrapped or anything, but very interesting. from.assumption pass: ;; Function _Z3bari._assume.0 (_Z3bari._assume.0, funcdef_no=5, decl_uid=2184, cgraph_uid=7, symbol_order=7) Assumptions : -------------- x_1(D) -> [irange] int [42, 42] NONZERO 0x2a __attribute__((no_icf, noclone, noinline, noipa)) bool _Z3bari._assume.0 (int x) { bool _2; ;; basic block 2, loop depth 0 ;; pred: ENTRY _2 = x_1(D) == 42; return _2; ;; succ: EXIT } and in the VRP2 pass, I see: _Z3bari._assume.0 assume inferred range of x_1(D) (param x) = [irange] int [42, 42] NONZERO 0x2a int bar (int x) { <bb 2> [local count: 1073741824]: .ASSUME (_Z3bari._assume.0, x_1(D)); return 42; } Huh. lookit that..... Anyway, let me clean this up a bit more, but so far so good. I'll try bootstrapping and such Andrew [-- Attachment #2: 0001-Infer-support.patch --] [-- Type: text/x-patch, Size: 2456 bytes --] From 62cc63ca8d5cce3593cdb4b35e5fe96b0ecc77a8 Mon Sep 17 00:00:00 2001 From: Andrew MacLeod <amacleod@redhat.com> Date: Tue, 18 Oct 2022 16:29:49 -0400 Subject: [PATCH 1/3] Infer support --- gcc/gimple-range-infer.cc | 40 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/gcc/gimple-range-infer.cc b/gcc/gimple-range-infer.cc index f0d66d047a6..3e376740796 100644 --- a/gcc/gimple-range-infer.cc +++ b/gcc/gimple-range-infer.cc @@ -35,6 +35,7 @@ along with GCC; see the file COPYING3. If not see #include "gimple-iterator.h" #include "gimple-walk.h" #include "cfganal.h" +#include "tree-dfa.h" // Adapted from infer_nonnull_range_by_dereference and check_loadstore // to process nonnull ssa_name OP in S. DATA contains a pointer to a @@ -111,6 +112,45 @@ gimple_infer_range::gimple_infer_range (gimple *s) // Fallthru and walk load/store ops now. } + // Look for ASSUME calls, and call query_assume_call for each argument + // to determine if there is any inferred range to be had. + if (is_a<gcall *> (s) && gimple_call_internal_p (s) + && gimple_call_internal_fn (s) == IFN_ASSUME) + { + tree arg; + unsigned i; + tree assume_id = TREE_OPERAND (gimple_call_arg (s, 0), 0); + struct function *fun = DECL_STRUCT_FUNCTION (assume_id); + for (arg = DECL_ARGUMENTS (assume_id), i = 1; + arg && i < gimple_call_num_args (s); + i++, arg = DECL_CHAIN (arg)) + { + tree op = gimple_call_arg (s, i); + tree type = TREE_TYPE (op); + tree def = ssa_default_def (fun, arg); + if (type != TREE_TYPE (arg) || !def) + continue; + if (gimple_range_ssa_p (op) && Value_Range::supports_type_p (type)) + { + Value_Range assume_range (type); + global_ranges.range_of_expr (assume_range, def); + { + add_range (op, assume_range); + if (dump_file) + { + print_generic_expr (dump_file, assume_id, TDF_SLIM); + fprintf (dump_file, " assume inferred range of "); + print_generic_expr (dump_file, op, TDF_SLIM); + fprintf (dump_file, " (param "); + print_generic_expr (dump_file, arg, TDF_SLIM); + fprintf (dump_file, ") = "); + assume_range.dump (dump_file); + fputc ('\n', dump_file); + } + } + } + } + } // Look for possible non-null values. if (flag_delete_null_pointer_checks && gimple_code (s) != GIMPLE_ASM && !gimple_clobber_p (s)) -- 2.37.3 [-- Attachment #3: 0002-assume_query-support.patch --] [-- Type: text/x-patch, Size: 7659 bytes --] From f163826f0e2b266c668e1500b3feb9a210569a0d Mon Sep 17 00:00:00 2001 From: Andrew MacLeod <amacleod@redhat.com> Date: Tue, 18 Oct 2022 16:30:04 -0400 Subject: [PATCH 2/3] assume_query support --- gcc/gimple-range-gori.h | 6 +- gcc/gimple-range.cc | 161 ++++++++++++++++++++++++++++++++++++++++ gcc/gimple-range.h | 17 +++++ 3 files changed, 181 insertions(+), 3 deletions(-) diff --git a/gcc/gimple-range-gori.h b/gcc/gimple-range-gori.h index c7a32162a1b..6cc533b58b2 100644 --- a/gcc/gimple-range-gori.h +++ b/gcc/gimple-range-gori.h @@ -165,15 +165,15 @@ public: bool has_edge_range_p (tree name, basic_block bb = NULL); bool has_edge_range_p (tree name, edge e); void dump (FILE *f); + bool compute_operand_range (vrange &r, gimple *stmt, const vrange &lhs, + tree name, class fur_source &src, + value_relation *rel = NULL); private: bool refine_using_relation (tree op1, vrange &op1_range, tree op2, vrange &op2_range, fur_source &src, relation_kind k); bool may_recompute_p (tree name, edge e); bool may_recompute_p (tree name, basic_block bb = NULL); - bool compute_operand_range (vrange &r, gimple *stmt, const vrange &lhs, - tree name, class fur_source &src, - value_relation *rel = NULL); bool compute_operand_range_switch (vrange &r, gswitch *s, const vrange &lhs, tree name, fur_source &src); bool compute_operand1_range (vrange &r, gimple_range_op_handler &handler, diff --git a/gcc/gimple-range.cc b/gcc/gimple-range.cc index d67d6499c78..0990c1ca01e 100644 --- a/gcc/gimple-range.cc +++ b/gcc/gimple-range.cc @@ -645,3 +645,164 @@ disable_ranger (struct function *fun) delete fun->x_range_query; fun->x_range_query = NULL; } + +// ------------------------------------------------------------------------ + +// If there is a non-varying value associated with NAME, return true and the +// range in R. + +bool +assume_query::assume_range_p (vrange &r, tree name) +{ + if (global.get_global_range (r, name)) + return !r.varying_p (); + return false; +} + +// Query used by GORI to pick up any known value on entry to a block. + +bool +assume_query::range_of_expr (vrange &r, tree expr, gimple *stmt) +{ + if (!gimple_range_ssa_p (expr)) + return get_tree_range (r, expr, stmt); + + if (!global.get_global_range (r, expr)) + r.set_varying (TREE_TYPE (expr)); + return true; +} + +// If the current function returns an integral value, and has a single return +// statement, it will calculate any SSA_NAMES is can determine ranges forr +// assuming the function returns 1. + +assume_query::assume_query () +{ + basic_block exit_bb = EXIT_BLOCK_PTR_FOR_FN (cfun); + if (single_pred_p (exit_bb)) + { + basic_block bb = single_pred (exit_bb); + gimple_stmt_iterator gsi = gsi_last_nondebug_bb (bb); + if (gsi_end_p (gsi)) + return; + gimple *s = gsi_stmt (gsi); + if (!is_a<greturn *> (s)) + return; + greturn *gret = as_a<greturn *> (s); + tree op = gimple_return_retval (gret); + if (!gimple_range_ssa_p (op)) + return; + tree lhs_type = TREE_TYPE (op); + if (!irange::supports_p (lhs_type)) + return; + + unsigned prec = TYPE_PRECISION (lhs_type); + int_range<2> lhs_range (lhs_type, wi::one (prec), wi::one (prec)); + global.set_global_range (op, lhs_range); + + gimple *def = SSA_NAME_DEF_STMT (op); + if (!def || gimple_get_lhs (def) != op) + return; + fur_stmt src (gret, this); + calculate_stmt (def, lhs_range, src); + } +} + +// Evaluate operand OP on statement S, using the provided LHS range. +// If successful, set the range in the global table, then visit OP's def stmt. + +void +assume_query::calculate_op (tree op, gimple *s, vrange &lhs, fur_source &src) +{ + Value_Range op_range (TREE_TYPE (op)); + if (!global.get_global_range (op_range, op) + && m_gori.compute_operand_range (op_range, s, lhs, op, src) + && !op_range.varying_p ()) + { + global.set_global_range (op, op_range); + gimple *def_stmt = SSA_NAME_DEF_STMT (op); + if (def_stmt && gimple_get_lhs (def_stmt) == op) + calculate_stmt (def_stmt, op_range, src); + } +} + +// Evaluate PHI statement, using the provided LHS range. +// Check each constant argument predecessor if it can be taken +// provide LHS to any symbolic argmeuents, and process their def statements. + +void +assume_query::calculate_phi (gphi *phi, vrange &lhs_range, fur_source &src) +{ + for (unsigned x= 0; x < gimple_phi_num_args (phi); x++) + { + tree arg = gimple_phi_arg_def (phi, x); + Value_Range arg_range (TREE_TYPE (arg)); + if (gimple_range_ssa_p (arg)) + { + // A symbol arg will be the LHS value. + arg_range = lhs_range; + range_cast (arg_range, TREE_TYPE (arg)); + if (!global.get_global_range (arg_range, arg)) + { + global.set_global_range (arg, arg_range); + gimple *def_stmt = SSA_NAME_DEF_STMT (arg); + if (def_stmt && gimple_get_lhs (def_stmt) == arg) + calculate_stmt (def_stmt, arg_range, src); + } + } + else if (get_tree_range (arg_range, arg, NULL)) + { + // If this is a constant value that differs from LHS, this + // edge cannot be taken. + arg_range.intersect (lhs_range); + if (arg_range.undefined_p ()) + continue; + // Otherwise check the condition feeding this edge. + edge e = gimple_phi_arg_edge (phi, x); + check_taken_edge (e, src); + } + } +} + +// If an edge is known to be taken, examine the outgoing edge to see +// if it carries any range information that can also be evaluated. + +void +assume_query::check_taken_edge (edge e, fur_source &src) +{ + gimple *stmt = gimple_outgoing_range_stmt_p (e->src); + if (stmt && is_a<gcond *> (stmt)) + { + int_range<2> cond; + gcond_edge_range (cond, e); + calculate_stmt (stmt, cond, src); + } +} + +// Evaluate statement S which produces range LHS_RANGE. + +void +assume_query::calculate_stmt (gimple *s, vrange &lhs_range, fur_source &src) +{ + gimple_range_op_handler handler (s); + if (handler) + { + tree op = gimple_range_ssa_p (handler.operand1 ()); + if (op) + calculate_op (op, s, lhs_range, src); + op = gimple_range_ssa_p (handler.operand2 ()); + if (op) + calculate_op (op, s, lhs_range, src); + } + else if (is_a<gphi *> (s)) + { + calculate_phi (as_a<gphi *> (s), lhs_range, src); + // Don't further check predecessors of blocks with PHIs. + return; + } + + // Even if the walk back terminates before the top, if this is a single + // predecessor block, see if the predecessor provided any ranges to get here. + if (single_pred_p (gimple_bb (s))) + check_taken_edge (single_pred_edge (gimple_bb (s)), src); +} diff --git a/gcc/gimple-range.h b/gcc/gimple-range.h index 8b2ff5685e5..4dc7bc33c5f 100644 --- a/gcc/gimple-range.h +++ b/gcc/gimple-range.h @@ -80,4 +80,21 @@ extern gimple_ranger *enable_ranger (struct function *m, bool use_imm_uses = true); extern void disable_ranger (struct function *); +class assume_query : public range_query +{ +public: + assume_query (); + bool assume_range_p (vrange &r, tree name); + virtual bool range_of_expr (vrange &r, tree expr, gimple * = NULL); +protected: + void calculate_stmt (gimple *s, vrange &lhs_range, fur_source &src); + void calculate_op (tree op, gimple *s, vrange &lhs, fur_source &src); + void calculate_phi (gphi *phi, vrange &lhs_range, fur_source &src); + void check_taken_edge (edge e, fur_source &src); + + ssa_global_cache global; + gori_compute m_gori; +}; + + #endif // GCC_GIMPLE_RANGE_H -- 2.37.3 [-- Attachment #4: 0003-Show-output-in-assume-pass.patch --] [-- Type: text/x-patch, Size: 1785 bytes --] From 41a98cf507b96d9c64eed7c0f3b4daec5357e5a3 Mon Sep 17 00:00:00 2001 From: Andrew MacLeod <amacleod@redhat.com> Date: Mon, 17 Oct 2022 12:28:21 -0400 Subject: [PATCH 3/3] Show output in assume pass --- gcc/tree-vrp.cc | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/gcc/tree-vrp.cc b/gcc/tree-vrp.cc index 1adb15c9934..b7c59cfa057 100644 --- a/gcc/tree-vrp.cc +++ b/gcc/tree-vrp.cc @@ -50,6 +50,7 @@ along with GCC; see the file COPYING3. If not see #include "gimple-range-path.h" #include "value-pointer-equiv.h" #include "gimple-fold.h" +#include "tree-dfa.h" /* Set of SSA names found live during the RPO traversal of the function for still active basic-blocks. */ @@ -4465,6 +4466,36 @@ public: bool gate (function *fun) final override { return fun->assume_function; } unsigned int execute (function *) final override { + assume_query query; + if (dump_file) + fprintf (dump_file, "Assumptions :\n--------------\n"); + + for (tree arg = DECL_ARGUMENTS (cfun->decl); arg; arg = DECL_CHAIN (arg)) + { + tree name = ssa_default_def (cfun, arg); + if (!name || !gimple_range_ssa_p (name)) + continue; + tree type = TREE_TYPE (name); + if (!Value_Range::supports_type_p (type)) + continue; + Value_Range assume_range (type); + if (query.assume_range_p (assume_range, name)) + { + set_range_info (name, assume_range); + if (dump_file) + { + print_generic_expr (dump_file, name, TDF_SLIM); + fprintf (dump_file, " -> "); + assume_range.dump (dump_file); + fputc ('\n', dump_file); + } + } + } + if (dump_file) + { + fputc ('\n', dump_file); + gimple_dump_cfg (dump_file, dump_flags); + } return TODO_discard_function; } -- 2.37.3 ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH] middle-end, v4: IFN_ASSUME support [PR106654] 2022-10-19 16:06 ` Jakub Jelinek 2022-10-19 16:55 ` Andrew MacLeod @ 2022-10-19 17:14 ` Andrew MacLeod 1 sibling, 0 replies; 35+ messages in thread From: Andrew MacLeod @ 2022-10-19 17:14 UTC (permalink / raw) To: Jakub Jelinek; +Cc: Richard Biener, Jan Hubicka, gcc-patches, hernandez, aldy On 10/19/22 12:06, Jakub Jelinek wrote: > > I have expected (but tell me if that isn't possible) this could be something > done in the new pass_assumptions::execute () rather than vrp and you'd > create the assume_query there (i.e. just for assume_functions) and then > query it solely for ssa_default_def of the parameters and save in > SSA_NAME_RANGE_INFO. > Oh, how do I know I'm processing an assume function? presumable we could gate the pass on that so its not even invoked on non-assume functions? Andrew ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH] middle-end IFN_ASSUME support [PR106654] 2022-10-10 8:54 [PATCH] middle-end IFN_ASSUME support [PR106654] Jakub Jelinek 2022-10-10 21:09 ` Jason Merrill @ 2022-10-11 18:05 ` Andrew MacLeod 2022-10-12 10:15 ` Jakub Jelinek 2022-10-14 20:43 ` Martin Uecker 2 siblings, 1 reply; 35+ messages in thread From: Andrew MacLeod @ 2022-10-11 18:05 UTC (permalink / raw) To: Jakub Jelinek, Richard Biener, Jan Hubicka, Aldy Hernandez; +Cc: gcc-patches On 10/10/22 04:54, Jakub Jelinek via Gcc-patches wrote: > Hi! > > 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. Otherwise, it keeps the handling of the simplest > assumptions as is, and otherwise arranges for the assumptions to be > visible in the IL as > .ASSUME (_Z2f4i._assume.0, i_1(D)); > call where there is an artificial function like: > bool _Z2f4i._assume.0 (int i) > { > bool _2; > > <bb 2> [local count: 1073741824]: > _2 = i_1(D) == 43; > return _2; > > } > with the semantics that there is UB unless the assumption function > would return true. > > Aldy, could ranger handle this? If it sees .ASSUME call, > walk the body of such function from the edge(s) to exit with the > assumption that the function returns true, so above set _2 [true, true] > and from there derive that i_1(D) [43, 43] and then map the argument > in the assumption function to argument passed to IFN_ASSUME (note, > args there are shifted by 1)? Ranger GORI component could assume the return value is [1,1] and work backwards from there. Single basic blocks would be trivial. The problem becomes when there are multiple blocks. The gori engine has no real restriction other than it works from within a basic block only I see no reason we couldn't wire something up that continues propagating values out the top of the block evaluating things for more complicated cases. you would end up with a set of ranges for names which are the "maximal" possible range based on the restriction that the return value is [1,1]. > During gimplification it actually gimplifies it into > D.2591 = .ASSUME (); > if (D.2591 != 0) goto <D.2593>; else goto <D.2592>; > <D.2593>: > { > i = i + 1; > D.2591 = i == 44; > } > <D.2592>: > .ASSUME (D.2591); > with the condition wrapped into a GIMPLE_BIND (I admit the above isn't > extra clean but it is just something to hold it from gimplifier until > gimple low pass; it reassembles if (condition_never_true) { cond; }; What we really care about is what the SSA form looks like.. thats what ranger will deal with. Is this function inlined? If it isn't then you'd need LTO/IPA to propagate the ranges we calculated above for the function. Or some special pass that reads assumes, does the processing you mention above and applies it? Is that what you are thinking? Looking at assume7.C, I see: int bar (int x) { <bb 2> [local count: 1073741824]: .ASSUME (_Z3bari._assume.0, x_1(D)); return x_1(D); And: bool _Z3bari._assume.0 (int x) { bool _2; <bb 2> [local count: 1073741824]: _2 = x_1(D) == 42; return _2; Using the above approach, GORI could tell you that if _2 is [1,1] that x_1 must be [42,42]. If you are parsing that ASSUME, you could presumably match things pu and we could make x_1 have a range of [42,42] in bar() at that call. this would require a bit of processing in fold_using_range for handling function calls, checking for this case and so on, but quite doable. looking at the more complicated case for bool _Z3bazi._assume.0 (int x) it seems that the answer is determines without processing most of the function. ie:, work from the bottom up: <bb 5> [local count: 670631318]: _8 = x_3 == 43; x_3 = [43,43] <bb 6> [local count: 1073741824]: # _1 = PHI <0(2), _8(5)> _8 = [1,1] 2->6 cant happen return _1; _1 = [1,1] you only care about x, so as soon as you find a result that that, you'd actually be done. However, I can imagine cases where you do need to go all the way back to the top of the assume function.. and combine values. Ie bool assume (int x, int y) { if (y > 10) return x == 2; return x > 20; } <bb 2> [local count: 1073741824]: if (y_2(D) > 10) goto <bb 3>; [34.00%] else goto <bb 4>; [66.00%] <bb 3> [local count: 365072224]: _5 = x_3(D) == 2; x_3 = [2,2] goto <bb 5>; [100.00%] <bb 4> [local count: 708669601]: _4 = x_3(D) > 20; x_3 = [21, +INF] <bb 5> [local count: 1073741824]: # _1 = PHI <_5(3), _4(4)> _5 = [1,1], _4 = [1,1] return _1; And we'd have a range of [2,2][21, +INF] if you wanted to be able to plug values of Y in, things would get more complicated, but the framework would all be there. OK, enough pontificating, it wouldn't be ranger, but yes, you could wire GORI into a pass which evaluates what we think the range of a set of inputs would be if we return 1. I don't think It would be very difficult, just a bit of work to work the IL in reverse. And then you should be able to wire in a range update for those parameters in fold_using_range (or wherever else I suppose) with a little more work. It seems to me that if you were to "optimize" the function via this new pass, assuming 1 was returned, you could probably eliminate a lot of the extraneous code.. for what thats worth. Can the assume function produce more than one assumption you care about? Andrew ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH] middle-end IFN_ASSUME support [PR106654] 2022-10-11 18:05 ` [PATCH] middle-end " Andrew MacLeod @ 2022-10-12 10:15 ` Jakub Jelinek 2022-10-12 14:31 ` Andrew MacLeod 2022-10-17 17:53 ` Andrew MacLeod 0 siblings, 2 replies; 35+ messages in thread From: Jakub Jelinek @ 2022-10-12 10:15 UTC (permalink / raw) To: Andrew MacLeod; +Cc: Richard Biener, Jan Hubicka, Aldy Hernandez, gcc-patches On Tue, Oct 11, 2022 at 02:05:52PM -0400, Andrew MacLeod wrote: > > Aldy, could ranger handle this? If it sees .ASSUME call, > > walk the body of such function from the edge(s) to exit with the > > assumption that the function returns true, so above set _2 [true, true] > > and from there derive that i_1(D) [43, 43] and then map the argument > > in the assumption function to argument passed to IFN_ASSUME (note, > > args there are shifted by 1)? > > > Ranger GORI component could assume the return value is [1,1] and work > backwards from there. Single basic blocks would be trivial. The problem > becomes when there are multiple blocks. The gori engine has no real > restriction other than it works from within a basic block only > > I see no reason we couldn't wire something up that continues propagating > values out the top of the block evaluating things for more complicated > cases. you would end up with a set of ranges for names which are the > "maximal" possible range based on the restriction that the return value is > [1,1]. > > > > During gimplification it actually gimplifies it into > > D.2591 = .ASSUME (); > > if (D.2591 != 0) goto <D.2593>; else goto <D.2592>; > > <D.2593>: > > { > > i = i + 1; > > D.2591 = i == 44; > > } > > <D.2592>: > > .ASSUME (D.2591); > > with the condition wrapped into a GIMPLE_BIND (I admit the above isn't > > extra clean but it is just something to hold it from gimplifier until > > gimple low pass; it reassembles if (condition_never_true) { cond; }; > > > What we really care about is what the SSA form looks like.. thats what > ranger will deal with. Sure. > Is this function inlined? If it isn't then you'd need LTO/IPA to propagate Never (the code is not supposed to be actually executed at runtime ever, it is purely as if, if this function would be executed, then it would return true, otherwise it would be UB). But the goal is of course to inline stuff into it and optimize the function even post IPA. > the ranges we calculated above for the function. Or some special pass that > reads assumes, does the processing you mention above and applies it? Is > that what you are thinking? The options would be to evaluate it each time ranger processes .ASSUME, or to perform this backwards range propagation somewhere late during post IPA optimizations of the cfun->assume_function and remember it somewhere (e.g. in SSA_NAME_RANGE_INFO of the default defs of the params) and then when visiting .ASSUME just look those up. I think the latter is better, we'd do it only once - the assumption that the function returns true after the assume function itself is optimized will always be the same. It could be a separate pass (gated on fun->assume_function, so done only for them) somewhere shortly before expansion to RTL (which is what isn't done and nothing later for those), or could be done say in VRP2 or some other existing late pass. > Looking at assume7.C, I see: > > int bar (int x) > { > <bb 2> [local count: 1073741824]: > .ASSUME (_Z3bari._assume.0, x_1(D)); > return x_1(D); > > And: > > bool _Z3bari._assume.0 (int x) > { > bool _2; > > <bb 2> [local count: 1073741824]: > _2 = x_1(D) == 42; > return _2; > > > Using the above approach, GORI could tell you that if _2 is [1,1] that x_1 > must be [42,42]. > > If you are parsing that ASSUME, you could presumably match things pu and we > could make x_1 have a range of [42,42] in bar() at that call. If we cache the range info for the assume_function arguments the above way on SSA_NAME_RANGE_INFO, then you'd just see .ASSUME call and for (n+1)th argument find nth argument of the 1st argument FUNCTION_DECL's DECL_ARGUMENTS, ssa_default_def (DECL_STRUCT_FUNCTION (assume_fndecl), parm) and just union the current range of (n+1)th argument with SSA_NAME_RANGE_INFO of the ssa_default_def (if non-NULL). > > this would require a bit of processing in fold_using_range for handling > function calls, checking for this case and so on, but quite doable. > > looking at the more complicated case for > > bool _Z3bazi._assume.0 (int x) > > it seems that the answer is determines without processing most of the > function. ie:, work from the bottom up: > > <bb 5> [local count: 670631318]: > _8 = x_3 == 43; x_3 = [43,43] > > <bb 6> [local count: 1073741824]: > # _1 = PHI <0(2), _8(5)> _8 = [1,1] 2->6 cant happen > return _1; _1 = [1,1] > > you only care about x, so as soon as you find a result that that, you'd > actually be done. However, I can imagine cases where you do need to go all > the way back to the top of the assume function.. and combine values. Ie > > bool assume (int x, int y) > { > if (y > 10) > return x == 2; > return x > 20; > } > > <bb 2> [local count: 1073741824]: > if (y_2(D) > 10) > goto <bb 3>; [34.00%] > else > goto <bb 4>; [66.00%] > > <bb 3> [local count: 365072224]: > _5 = x_3(D) == 2; x_3 = [2,2] > goto <bb 5>; [100.00%] > > <bb 4> [local count: 708669601]: > _4 = x_3(D) > 20; x_3 = [21, +INF] > > <bb 5> [local count: 1073741824]: > # _1 = PHI <_5(3), _4(4)> _5 = [1,1], _4 = [1,1] > > return _1; > > And we'd have a range of [2,2][21, +INF] > if you wanted to be able to plug values of Y in, things would get more > complicated, but the framework would all be there. Yeah. Note, it is fine to handle say single block assume functions (after optimizations) first and improve incrementally later, the goal is that people actually see useful optimizations with simpler (but not simplest) assume conditions, so they don't say they aren't completely useless, and if they come up with something more complex that we don't handle yet, they can file enhancement requests. Of course, trying to walk all the bbs backwards would be nicer, though even then it is important to be primarily correct and so punting on anything we can't handle is fine (e.g. if there are loops etc.). > It seems to me that if you were to "optimize" the function via this new > pass, assuming 1 was returned, you could probably eliminate a lot of the > extraneous code.. for what thats worth. > > Can the assume function produce more than one assumption you care about? The assume function can have many arguments (one is created for each automatic var referenced or set by the condition), so it would be nice to track all of them rather than just hardcoding the first. And, the argument doesn't necessarily have to be a scalar, so perhaps later on we could derive ranges even for structure members etc. if needed. Or just handle assume_function in IPA-SRA somehow at least. The C++23 paper mentions [[assume(size > 0)]]; [[assume(size % 32 == 0)]]; [[assume(std::isfinite(data[i]))]]; [[assume(*pn >= 1)]]; [[assume(i == 42)]]; [[assume(++i == 43)]]; [[assume((std::cin >> i, i == 42))]]; [[assume(++almost_last == last)]]; among other things, the first and fifth are already handled the if (!cond) __builtin_unreachable (); way and so work even without this patch, the (std::cin >> i, i == 42) case is not worth bothering for now and the rest would be single block assumptions that can be handled easily (except the last one which would have record type arguments and so we'd need SRA). Jakub ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH] middle-end IFN_ASSUME support [PR106654] 2022-10-12 10:15 ` Jakub Jelinek @ 2022-10-12 14:31 ` Andrew MacLeod 2022-10-12 14:39 ` Jakub Jelinek 2022-10-17 17:53 ` Andrew MacLeod 1 sibling, 1 reply; 35+ messages in thread From: Andrew MacLeod @ 2022-10-12 14:31 UTC (permalink / raw) To: Jakub Jelinek; +Cc: Richard Biener, Jan Hubicka, Aldy Hernandez, gcc-patches On 10/12/22 06:15, Jakub Jelinek wrote: > >> the ranges we calculated above for the function. Or some special pass that >> reads assumes, does the processing you mention above and applies it? Is >> that what you are thinking? > The options would be to evaluate it each time ranger processes .ASSUME, > or to perform this backwards range propagation somewhere late during post > IPA optimizations of the cfun->assume_function and remember it somewhere > (e.g. in SSA_NAME_RANGE_INFO of the default defs of the params) and then > when visiting .ASSUME just look those up. I think the latter is better, > we'd do it only once - the assumption that the function returns true after > the assume function itself is optimized will always be the same. > It could be a separate pass (gated on fun->assume_function, so done only > for them) somewhere shortly before expansion to RTL (which is what isn't > done and nothing later for those), or could be done say in VRP2 or some > other existing late pass. > I agree, I think it would be better to process once, and store the results some where. I could provide a routine which attempts the evaluation of the current function, and returns a "safe" range for each of the parameters. By safe, I mean it would assume VARYING for every unknown value in the function, reduced by whatever the backward walk determined. We can refine that later by wiring this call in after a full ranger walk of VRP for instance to get more precise values, but that is not necessary at the moment. I can also make it so that we always try to look up values from the .ASSUME in fold_using_range, which means any VRP or other ranger client will pick up the results. If there is nothing available, it would return VARYING as the default. Any current range would be intersected with what the ASSUME query returns. I presume you are looking to get this working for this release, making the priority high? :-) >> Looking at assume7.C, I see: >> >> int bar (int x) >> { >> <bb 2> [local count: 1073741824]: >> .ASSUME (_Z3bari._assume.0, x_1(D)); >> return x_1(D); >> >> And: >> >> bool _Z3bari._assume.0 (int x) >> { >> bool _2; >> >> <bb 2> [local count: 1073741824]: >> _2 = x_1(D) == 42; >> return _2; >> >> >> Using the above approach, GORI could tell you that if _2 is [1,1] that x_1 >> must be [42,42]. >> >> If you are parsing that ASSUME, you could presumably match things pu and we >> could make x_1 have a range of [42,42] in bar() at that call. > If we cache the range info for the assume_function arguments the above way > on SSA_NAME_RANGE_INFO, then you'd just see .ASSUME call and for (n+1)th > argument find nth argument of the 1st argument FUNCTION_DECL's > DECL_ARGUMENTS, ssa_default_def (DECL_STRUCT_FUNCTION (assume_fndecl), parm) > and just union the current range of (n+1)th argument with > SSA_NAME_RANGE_INFO of the ssa_default_def (if non-NULL). Intersection I believe...? I think the value from the assume's should add restrictions to the range.. >> this would require a bit of processing in fold_using_range for handling >> function calls, checking for this case and so on, but quite doable. >> >> looking at the more complicated case for >> >> bool _Z3bazi._assume.0 (int x) >> >> it seems that the answer is determines without processing most of the >> function. ie:, work from the bottom up: >> >> <bb 5> [local count: 670631318]: >> _8 = x_3 == 43; x_3 = [43,43] >> >> <bb 6> [local count: 1073741824]: >> # _1 = PHI <0(2), _8(5)> _8 = [1,1] 2->6 cant happen >> return _1; _1 = [1,1] >> >> you only care about x, so as soon as you find a result that that, you'd >> actually be done. However, I can imagine cases where you do need to go all >> the way back to the top of the assume function.. and combine values. Ie >> >> bool assume (int x, int y) >> { >> if (y > 10) >> return x == 2; >> return x > 20; >> } >> >> <bb 2> [local count: 1073741824]: >> if (y_2(D) > 10) >> goto <bb 3>; [34.00%] >> else >> goto <bb 4>; [66.00%] >> >> <bb 3> [local count: 365072224]: >> _5 = x_3(D) == 2; x_3 = [2,2] >> goto <bb 5>; [100.00%] >> >> <bb 4> [local count: 708669601]: >> _4 = x_3(D) > 20; x_3 = [21, +INF] >> >> <bb 5> [local count: 1073741824]: >> # _1 = PHI <_5(3), _4(4)> _5 = [1,1], _4 = [1,1] >> >> return _1; >> >> And we'd have a range of [2,2][21, +INF] >> if you wanted to be able to plug values of Y in, things would get more >> complicated, but the framework would all be there. > Yeah. Note, it is fine to handle say single block assume functions (after > optimizations) first and improve incrementally later, the goal is that > people actually see useful optimizations with simpler (but not simplest) > assume conditions, so they don't say they aren't completely useless, and if > they come up with something more complex that we don't handle yet, they > can file enhancement requests. Of course, trying to walk all the bbs > backwards would be nicer, though even then it is important to be primarily > correct and so punting on anything we can't handle is fine (e.g. if there > are loops etc.). Single blocks for the first cut and POC. easier. then look at expansion >> It seems to me that if you were to "optimize" the function via this new >> pass, assuming 1 was returned, you could probably eliminate a lot of the >> extraneous code.. for what thats worth. >> >> Can the assume function produce more than one assumption you care about? > The assume function can have many arguments (one is created for each > automatic var referenced or set by the condition), so it would be nice to > track all of them rather than just hardcoding the first. And, the argument > doesn't necessarily have to be a scalar, so perhaps later on we could derive > ranges even for structure members etc. if needed. Or just handle > assume_function in IPA-SRA somehow at least. > > The C++23 paper mentions > [[assume(size > 0)]]; > [[assume(size % 32 == 0)]]; > [[assume(std::isfinite(data[i]))]]; > [[assume(*pn >= 1)]]; > [[assume(i == 42)]]; > [[assume(++i == 43)]]; > [[assume((std::cin >> i, i == 42))]]; > [[assume(++almost_last == last)]]; > among other things, the first and fifth are already handled the > if (!cond) __builtin_unreachable (); way and so work even without this > patch, the (std::cin >> i, i == 42) case is not worth bothering for now > and the rest would be single block assumptions that can be handled easily > (except the last one which would have record type arguments and so we'd need > SRA). I figured as much, I was just wondering if there might be some way to "simplify" certain things by processing it and turning each parameter query into a smaller function returning the range we determined from the main one... but perhaps that is more complicated. Andrew ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH] middle-end IFN_ASSUME support [PR106654] 2022-10-12 14:31 ` Andrew MacLeod @ 2022-10-12 14:39 ` Jakub Jelinek 2022-10-12 16:12 ` Andrew MacLeod 0 siblings, 1 reply; 35+ messages in thread From: Jakub Jelinek @ 2022-10-12 14:39 UTC (permalink / raw) To: Andrew MacLeod; +Cc: Richard Biener, Jan Hubicka, Aldy Hernandez, gcc-patches On Wed, Oct 12, 2022 at 10:31:00AM -0400, Andrew MacLeod wrote: > I presume you are looking to get this working for this release, making the > priority high? :-) Yes. So that we can claim we actually support C++23 Portable Assumptions and OpenMP assume directive's hold clauses for something non-trivial so people won't be afraid to actually use it. Of course, first the posted patch needs to be reviewed and only once it gets in, the ranger/GORI part can follow. As the latter is only an optimization, it can be done incrementally. > Intersection I believe...? I think the value from the assume's should add > restrictions to the range.. Sure, sorry. > I figured as much, I was just wondering if there might be some way to > "simplify" certain things by processing it and turning each parameter query > into a smaller function returning the range we determined from the main > one... but perhaps that is more complicated. We don't really know what the condition is, it can be pretty arbitrary expression (well, e.g. for C++ conditional expression, so say [[assume (var = foo ())]]; is not valid but [[assume ((var = foo ()))]]; is. And with GNU statement expressions it can do a lot of stuff and until we e.g. inline into it and optimize it a little, we don't really know what it will be like. Jakub ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH] middle-end IFN_ASSUME support [PR106654] 2022-10-12 14:39 ` Jakub Jelinek @ 2022-10-12 16:12 ` Andrew MacLeod 2022-10-13 8:11 ` Richard Biener 2022-10-13 9:57 ` Jakub Jelinek 0 siblings, 2 replies; 35+ messages in thread From: Andrew MacLeod @ 2022-10-12 16:12 UTC (permalink / raw) To: Jakub Jelinek; +Cc: Richard Biener, Jan Hubicka, Aldy Hernandez, gcc-patches On 10/12/22 10:39, Jakub Jelinek wrote: > On Wed, Oct 12, 2022 at 10:31:00AM -0400, Andrew MacLeod wrote: >> I presume you are looking to get this working for this release, making the >> priority high? :-) > Yes. So that we can claim we actually support C++23 Portable Assumptions > and OpenMP assume directive's hold clauses for something non-trivial so > people won't be afraid to actually use it. > Of course, first the posted patch needs to be reviewed and only once it gets > in, the ranger/GORI part can follow. As the latter is only an optimization, > it can be done incrementally. I will start poking at something to find ranges for parameters from the return backwards. >> Intersection I believe...? I think the value from the assume's should add >> restrictions to the range.. > Sure, sorry. > >> I figured as much, I was just wondering if there might be some way to >> "simplify" certain things by processing it and turning each parameter query >> into a smaller function returning the range we determined from the main >> one... but perhaps that is more complicated. > We don't really know what the condition is, it can be pretty arbitrary > expression (well, e.g. for C++ conditional expression, so say > [[assume (var = foo ())]]; > is not valid but > [[assume ((var = foo ()))]]; > is. And with GNU statement expressions it can do a lot of stuff and until > we e.g. inline into it and optimize it a little, we don't really know what > it will be like. > > No, I just meant that once we finally process the complicated function, and decide the final range we are storing is for x_1 is say [20,30], we could replace the assume call site with something like int assume03_x (x) { if (x>= 20 || x <= 30) return x; gcc_unreachable(); } then at call sites: x_5 = assume03_x(x_3); For that matter, once all the assume functions have been processed, we could textually replace the assume call with an expression which represents the determined range... Kind of our own mini inlining? Maybe thats even better than adding any kind of support in fold_using_range.. just let things naturally fall into place? .ASSUME_blah ( , , x_4); where if x is determined to be [20, 30][50,60] could be textually "expanded" in the IL with if (x<20 || x>60 || (x>30 && x < 50)) gcc_unreachcable(); for each of the parameters? If we processed this like early inlining, we could maybe expose the entire thing to optimization that way? Andrew ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH] middle-end IFN_ASSUME support [PR106654] 2022-10-12 16:12 ` Andrew MacLeod @ 2022-10-13 8:11 ` Richard Biener 2022-10-13 9:53 ` Jakub Jelinek 2022-10-13 9:57 ` Jakub Jelinek 1 sibling, 1 reply; 35+ messages in thread From: Richard Biener @ 2022-10-13 8:11 UTC (permalink / raw) To: Andrew MacLeod; +Cc: Jakub Jelinek, Jan Hubicka, Aldy Hernandez, gcc-patches [-- Attachment #1: Type: text/plain, Size: 3113 bytes --] On Wed, 12 Oct 2022, Andrew MacLeod wrote: > > On 10/12/22 10:39, Jakub Jelinek wrote: > > On Wed, Oct 12, 2022 at 10:31:00AM -0400, Andrew MacLeod wrote: > >> I presume you are looking to get this working for this release, making the > >> priority high? :-) > > Yes. So that we can claim we actually support C++23 Portable Assumptions > > and OpenMP assume directive's hold clauses for something non-trivial so > > people won't be afraid to actually use it. > > Of course, first the posted patch needs to be reviewed and only once it gets > > in, the ranger/GORI part can follow. As the latter is only an optimization, > > it can be done incrementally. > > I will start poking at something to find ranges for parameters from the return > backwards. If the return were if (return_val) return return_val; you could use path-ranger with the parameter SSA default defs as "interesting". So you "only" need to somehow interpret the return statement as such and do path rangers compute_ranges () > > >> Intersection I believe...? I think the value from the assume's should add > >> restrictions to the range.. > > Sure, sorry. > > > >> I figured as much, I was just wondering if there might be some way to > >> "simplify" certain things by processing it and turning each parameter query > >> into a smaller function returning the range we determined from the main > >> one... but perhaps that is more complicated. > > We don't really know what the condition is, it can be pretty arbitrary > > expression (well, e.g. for C++ conditional expression, so say > > [[assume (var = foo ())]]; > > is not valid but > > [[assume ((var = foo ()))]]; > > is. And with GNU statement expressions it can do a lot of stuff and until > > we e.g. inline into it and optimize it a little, we don't really know what > > it will be like. > > > > > > No, I just meant that once we finally process the complicated function, and > decide the final range we are storing is for x_1 is say [20,30], we could > replace the assume call site with something like > > int assume03_x (x) { if (x>= 20 || x <= 30) return x; gcc_unreachable(); } > > then at call sites: > > x_5 = assume03_x(x_3); > > For that matter, once all the assume functions have been processed, we could > textually replace the assume call with an expression which represents the > determined range... Kind of our own mini inlining? Maybe thats even better > than adding any kind of support in fold_using_range.. just let things > naturally fall into place? > > .ASSUME_blah ( , , x_4); > > where if x is determined to be [20, 30][50,60] could be textually "expanded" > in the IL with > > if (x<20 || x>60 || (x>30 && x < 50)) gcc_unreachcable(); > > for each of the parameters? If we processed this like early inlining, we > could maybe expose the entire thing to optimization that way? > > Andrew > > > -- Richard Biener <rguenther@suse.de> SUSE Software Solutions Germany GmbH, Frankenstrasse 146, 90461 Nuernberg, Germany; GF: Ivo Totev, Andrew Myers, Andrew McDonald, Boudien Moerman; HRB 36809 (AG Nuernberg) ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH] middle-end IFN_ASSUME support [PR106654] 2022-10-13 8:11 ` Richard Biener @ 2022-10-13 9:53 ` Jakub Jelinek 2022-10-13 13:16 ` Andrew MacLeod 0 siblings, 1 reply; 35+ messages in thread From: Jakub Jelinek @ 2022-10-13 9:53 UTC (permalink / raw) To: Richard Biener; +Cc: Andrew MacLeod, Jan Hubicka, Aldy Hernandez, gcc-patches On Thu, Oct 13, 2022 at 08:11:53AM +0000, Richard Biener wrote: > On Wed, 12 Oct 2022, Andrew MacLeod wrote: > > > > > On 10/12/22 10:39, Jakub Jelinek wrote: > > > On Wed, Oct 12, 2022 at 10:31:00AM -0400, Andrew MacLeod wrote: > > >> I presume you are looking to get this working for this release, making the > > >> priority high? :-) > > > Yes. So that we can claim we actually support C++23 Portable Assumptions > > > and OpenMP assume directive's hold clauses for something non-trivial so > > > people won't be afraid to actually use it. > > > Of course, first the posted patch needs to be reviewed and only once it gets > > > in, the ranger/GORI part can follow. As the latter is only an optimization, > > > it can be done incrementally. > > > > I will start poking at something to find ranges for parameters from the return > > backwards. > > If the return were > > if (return_val) > return return_val; > > you could use path-ranger with the parameter SSA default defs as > "interesting". So you "only" need to somehow interpret the return > statement as such and do path rangers compute_ranges () If it was easier for handling, another possible representation of the assume_function could be not that it returns a bool where [1,1] returned means defined behavior, otherwise UB, but that the function returns void and the assumption is that it returns, the other paths would be __builtin_unreachable (). But still in both cases it needs a specialized backwards walk from the assumption that either it returns [1,1] or that it returns through GIMPLE_RETURN to be defined behavior. In either case, external exceptions, or infinite loops or other reasons why the function might not return normally (exit/abort/longjmp/non-local goto etc.) are still UB for assumptions. Say normally, if we have: extern void foo (int); bool assume1 (int x) { foo (x); if (x != 42) __builtin_unreachable (); return true; } we can't through backwards ranger walk determine that x_1(D) at the start of the function has [42,42] range, we can just say it is true at the end of the function, because foo could do if (x != 42) exit (0); or if (x != 42) throw 1; or if (x != 42) longjmp (buf, 1); or while (x != 42) ; or if (x != 42) abort (); But with assumption functions we actually can and stick [42, 42] on the parameters even when we know nothing about foo function. Of course, perhaps initially, we can choose to ignore those extra guarantees. Jakub ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH] middle-end IFN_ASSUME support [PR106654] 2022-10-13 9:53 ` Jakub Jelinek @ 2022-10-13 13:16 ` Andrew MacLeod 0 siblings, 0 replies; 35+ messages in thread From: Andrew MacLeod @ 2022-10-13 13:16 UTC (permalink / raw) To: Jakub Jelinek, Richard Biener; +Cc: Jan Hubicka, Aldy Hernandez, gcc-patches On 10/13/22 05:53, Jakub Jelinek wrote: > On Thu, Oct 13, 2022 at 08:11:53AM +0000, Richard Biener wrote: >> On Wed, 12 Oct 2022, Andrew MacLeod wrote: >> >>> On 10/12/22 10:39, Jakub Jelinek wrote: >>>> On Wed, Oct 12, 2022 at 10:31:00AM -0400, Andrew MacLeod wrote: >>>>> I presume you are looking to get this working for this release, making the >>>>> priority high? :-) >>>> Yes. So that we can claim we actually support C++23 Portable Assumptions >>>> and OpenMP assume directive's hold clauses for something non-trivial so >>>> people won't be afraid to actually use it. >>>> Of course, first the posted patch needs to be reviewed and only once it gets >>>> in, the ranger/GORI part can follow. As the latter is only an optimization, >>>> it can be done incrementally. >>> I will start poking at something to find ranges for parameters from the return >>> backwards. >> If the return were >> >> if (return_val) >> return return_val; >> >> you could use path-ranger with the parameter SSA default defs as >> "interesting". So you "only" need to somehow interpret the return >> statement as such and do path rangers compute_ranges () > If it was easier for handling, another possible representation of the > assume_function could be not that it returns a bool where [1,1] returned > means defined behavior, otherwise UB, but that the function returns void > and the assumption is that it returns, the other paths would be > __builtin_unreachable (). But still in both cases it needs a specialized > backwards walk from the assumption that either it returns [1,1] or that it > returns through GIMPLE_RETURN to be defined behavior. In either case, > external exceptions, or infinite loops or other reasons why the function > might not return normally (exit/abort/longjmp/non-local goto etc.) are still > UB for assumptions. > Say normally, if we have: > extern void foo (int); > > bool > assume1 (int x) > { > foo (x); > if (x != 42) > __builtin_unreachable (); > return true; > } > we can't through backwards ranger walk determine that x_1(D) at the start of > the function has [42,42] range, we can just say it is true at the end of the > function, because foo could do if (x != 42) exit (0); or if (x != 42) throw > 1; or if (x != 42) longjmp (buf, 1); or while (x != 42) ; or if (x != 42) > abort (); > But with assumption functions we actually can and stick [42, 42] on the > parameters even when we know nothing about foo function. > > Of course, perhaps initially, we can choose to ignore those extra > guarantees. > I dont think we need to change anything. All I intend to do is provide something that looks for the returns, wire GORI in, and reuse a global range table in to a reverse dependency walk to starting with a range of [1,1] for whatever is on the return stmt. From GORIs point of view, thats all outgoing_edge_range_p () does, except it picks up the initial value of [0,0] or [1,1] from the specified edge instead. Initially It'll stop at the top of the block, but I don't think its too much work beyond that provide "simple" processing of PHIs and edges coming into the block.. In the absence of loops it should be pretty straightforward. "All" you do is feed the value of the phi argument to the previous block. Of course it'll probably be a little more complicated than that, but the basic premise seems pretty straightforward. The result produced would be vector over the ssa-names in the function with any ranges that were determined. You could use that to more efficiently store just the values of the parameters somewhere and somehow associate them with the assume function decl. I'll try to get to this shortly. Andrew ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH] middle-end IFN_ASSUME support [PR106654] 2022-10-12 16:12 ` Andrew MacLeod 2022-10-13 8:11 ` Richard Biener @ 2022-10-13 9:57 ` Jakub Jelinek 1 sibling, 0 replies; 35+ messages in thread From: Jakub Jelinek @ 2022-10-13 9:57 UTC (permalink / raw) To: Andrew MacLeod; +Cc: Richard Biener, Jan Hubicka, Aldy Hernandez, gcc-patches On Wed, Oct 12, 2022 at 12:12:38PM -0400, Andrew MacLeod wrote: > No, I just meant that once we finally process the complicated function, and > decide the final range we are storing is for x_1 is say [20,30], we could > replace the assume call site with something like > > int assume03_x (x) { if (x>= 20 || x <= 30) return x; gcc_unreachable(); } > > then at call sites: > > x_5 = assume03_x(x_3); > > For that matter, once all the assume functions have been processed, we could > textually replace the assume call with an expression which represents the > determined range... Kind of our own mini inlining? Maybe thats even better > than adding any kind of support in fold_using_range.. just let things > naturally fall into place? > > .ASSUME_blah ( , , x_4); > > where if x is determined to be [20, 30][50,60] could be textually "expanded" > in the IL with > > if (x<20 || x>60 || (x>30 && x < 50)) gcc_unreachcable(); > > for each of the parameters? If we processed this like early inlining, we > could maybe expose the entire thing to optimization that way? That could work for integral parameters, but doesn't work for floating point nor when builtins are involved. We do not want to put floating point comparisons into the IL as __builtin_unreachable (); guards because they have observable side-effects (floating point exceptions/traps) and we wouldn't DCE them for those reasons. Similarly, if there are builtins involved we don't want to call the corresponding library functions because something wasn't DCEd. Jakub ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH] middle-end IFN_ASSUME support [PR106654] 2022-10-12 10:15 ` Jakub Jelinek 2022-10-12 14:31 ` Andrew MacLeod @ 2022-10-17 17:53 ` Andrew MacLeod 1 sibling, 0 replies; 35+ messages in thread From: Andrew MacLeod @ 2022-10-17 17:53 UTC (permalink / raw) To: Jakub Jelinek; +Cc: Richard Biener, Jan Hubicka, Aldy Hernandez, gcc-patches [-- Attachment #1.1: Type: text/plain, Size: 5059 bytes --] > The assume function can have many arguments (one is created for each > automatic var referenced or set by the condition), so it would be nice to > track all of them rather than just hardcoding the first. And, the argument > doesn't necessarily have to be a scalar, so perhaps later on we could derive > ranges even for structure members etc. if needed. Or just handle > assume_function in IPA-SRA somehow at least. > > The C++23 paper mentions > [[assume(size > 0)]]; > [[assume(size % 32 == 0)]]; > [[assume(std::isfinite(data[i]))]]; > [[assume(*pn >= 1)]]; > [[assume(i == 42)]]; > [[assume(++i == 43)]]; > [[assume((std::cin >> i, i == 42))]]; > [[assume(++almost_last == last)]]; > among other things, the first and fifth are already handled the > if (!cond) __builtin_unreachable (); way and so work even without this > patch, the (std::cin >> i, i == 42) case is not worth bothering for now > and the rest would be single block assumptions that can be handled easily > (except the last one which would have record type arguments and so we'd need > SRA). > > Jakub I put together an initial prototype, attached is the 2 patches so far. I applied this on top of one of your sets of patches to try it out. The first patch has the initial simple version, and the second patch hacks VRP to add a loop over all the ssa-names in the function and show what assume_range_p would return for them. First, I added another routine to ranger: *bool gimple_ranger::assume_range_p (vrange &r, tree name)* This is the routine that is called to determine what the range of NAME is at the end of the function if the function returns [1,1]. It is painfully simple, only working on names in the definition chain of the return variable. It returns TRUE if it finds a non-varying result. I will next expand on this to look back in the CFG and be more flexible. To apply any assumed values, I added a routine to be called *bool query_assume_call (vrange &r, tree assume_id, tree name);* This routine would be what is called to lookup if there is any range associated with NAME in the assume function ASSUME_ID. I hacked one up to return [42, 42] for any integer query just for POC. You'd need to supply this routine somewhere instead. As the ASSUME function has no defs, we can't produce results for the parameters in normal ways, so I leverage the inferred range code. When doing a stmt walk, when VRP is done processing a stmt, it applies any side effects of the statement going forward. The gimple_inferred_range constructor now also looks for assume calls, and calls query_assume_call () on each argument, and if it gets back TRUE, applies an inferred range record for that range at that stmt. (This also means those ASSUME ranges will only show up in a VRP walk.) These seems like it might be functional enough for you to experiment with. For the simple int bar (int x) { [[assume (++x == 43)]]; return x; } The VRP hack for ther assume function shows : for an assume function, x_2(D) would have a range of [irange] int [42, 42] NONZERO 0x2a. I also tried it for bool foo1 (int x, int y) { return x < 10 || x > 20 || x == 12; } or an assume function, x_5(D) would have a range of [irange] int [-INF, 9][12, 12][21, +INF] bool foo2 (int x, int y) { return (x >= 10 && x <= 20) || x == 2; } for an assume function, x_5(D) would have a range of [irange] int [2, 2][10, 20] NONZERO 0x1f for: int bar (int x) { [[assume (++x == 43)]]; return x; } As for propagating assumed values, the hacked up version returning 42 shows it propagates into the return: query_assume_call injection _Z3bari._assume.0 assume inferred range of x_1(D) to [irange] int [42, 42] NONZERO 0x2a int bar (int x) { <bb 2> : .ASSUME (_Z3bari._assume.0, x_1(D)); return 42; } So in principle, I think theres enough infrastructure there to get going. You can query parameter ranges by creating a ranger, and querying the parameters via *assume_range_p () *. You can do that anywhere, as the hack I put in vrp shows, it creates a new ranger, simply queries each SSA_NAME, then disposes of the ranger before invoking VRP on a fresh ranger. The you just wire up a proper *query_assume_call(*) to return those ranges. Thats the basic APi to deal with... call one function, supply another. Does this model seem like it would work OK for you? Or do we need to tweak it? I am planning to extend assume_range_p to handle other basic blocks, as well as pick up a few of the extra things that outgoing_edge_range_p does. Andrew PS. It also seems to me that the assume_range_p() infrastructure may have some other uses when it comes to inlining or LTO or IPA. This particular version works with a return value of [1,1], but that value is manually supplied to GORI by the routine. If any other pass has some reason to know that the return value was within a certain range, we could use that and query what the incoming ranges of any parameter might have to be. Just a thought. [-- Attachment #2: 0002-Inferred-support-of-ASSUME.patch --] [-- Type: text/x-patch, Size: 6264 bytes --] From 0689fa587ed870e886ec5d6a0404ba20771b93c3 Mon Sep 17 00:00:00 2001 From: Andrew MacLeod <amacleod@redhat.com> Date: Mon, 17 Oct 2022 10:23:55 -0400 Subject: [PATCH 2/3] Inferred support of ASSUME --- gcc/gimple-range-gori.h | 6 ++--- gcc/gimple-range-infer.cc | 50 +++++++++++++++++++++++++++++++++++++++ gcc/gimple-range.cc | 48 +++++++++++++++++++++++++++++++++++++ gcc/gimple-range.h | 1 + 4 files changed, 102 insertions(+), 3 deletions(-) diff --git a/gcc/gimple-range-gori.h b/gcc/gimple-range-gori.h index c7a32162a1b..6cc533b58b2 100644 --- a/gcc/gimple-range-gori.h +++ b/gcc/gimple-range-gori.h @@ -165,15 +165,15 @@ public: bool has_edge_range_p (tree name, basic_block bb = NULL); bool has_edge_range_p (tree name, edge e); void dump (FILE *f); + bool compute_operand_range (vrange &r, gimple *stmt, const vrange &lhs, + tree name, class fur_source &src, + value_relation *rel = NULL); private: bool refine_using_relation (tree op1, vrange &op1_range, tree op2, vrange &op2_range, fur_source &src, relation_kind k); bool may_recompute_p (tree name, edge e); bool may_recompute_p (tree name, basic_block bb = NULL); - bool compute_operand_range (vrange &r, gimple *stmt, const vrange &lhs, - tree name, class fur_source &src, - value_relation *rel = NULL); bool compute_operand_range_switch (vrange &r, gswitch *s, const vrange &lhs, tree name, fur_source &src); bool compute_operand1_range (vrange &r, gimple_range_op_handler &handler, diff --git a/gcc/gimple-range-infer.cc b/gcc/gimple-range-infer.cc index f0d66d047a6..46a781c7826 100644 --- a/gcc/gimple-range-infer.cc +++ b/gcc/gimple-range-infer.cc @@ -36,6 +36,29 @@ along with GCC; see the file COPYING3. If not see #include "gimple-walk.h" #include "cfganal.h" + +// This routine should be provided to properly look up any +// values of NAME that has been determined to have a value specified by +// the function ASSUME_ID. Return TRUE if it has a value and is NOT VARYING. + +// Given an ASSUME_ID name, return any range evaluated for NAME. + +bool +query_assume_call (vrange &r, tree assume_id, tree name) +{ + if (dump_file) + fprintf (dump_file, "query_assume_call injection\n"); + if (assume_id != NULL_TREE && irange::supports_p (TREE_TYPE (name))) + { + int_range<2> f (build_int_cst (TREE_TYPE (name), 42), + build_int_cst (TREE_TYPE (name), 42)); + r = f; + return true; + } + return false; +} + + // Adapted from infer_nonnull_range_by_dereference and check_loadstore // to process nonnull ssa_name OP in S. DATA contains a pointer to a // stmt range inference instance. @@ -111,6 +134,33 @@ gimple_infer_range::gimple_infer_range (gimple *s) // Fallthru and walk load/store ops now. } + if (is_a<gcall *> (s) && gimple_call_internal_p (s) + && gimple_call_internal_fn (s) == IFN_ASSUME) + { + tree assume_id = gimple_call_arg (s, 0); + for (unsigned i = 1; i < gimple_call_num_args (s); i++) + { + tree op = gimple_call_arg (s, i); + tree type = TREE_TYPE (op); + if (gimple_range_ssa_p (op) && Value_Range::supports_type_p (type)) + { + Value_Range assume_range (type); + if (query_assume_call (assume_range, assume_id, op)) + { + add_range (op, assume_range); + if (dump_file) + { + print_generic_expr (dump_file, assume_id, TDF_SLIM); + fprintf (dump_file, " assume inferred range of "); + print_generic_expr (dump_file, op, TDF_SLIM); + fprintf (dump_file, " to "); + assume_range.dump (dump_file); + fputc ('\n', dump_file); + } + } + } + } + } // Look for possible non-null values. if (flag_delete_null_pointer_checks && gimple_code (s) != GIMPLE_ASM && !gimple_clobber_p (s)) diff --git a/gcc/gimple-range.cc b/gcc/gimple-range.cc index d67d6499c78..f9d6b73e4e8 100644 --- a/gcc/gimple-range.cc +++ b/gcc/gimple-range.cc @@ -483,6 +483,54 @@ gimple_ranger::register_inferred_ranges (gimple *s) m_cache.apply_inferred_ranges (s); } +// This function is used to support the ASSUME keyword. If the current +// function returns an integral value, it will attempt to determine what +// the range of NAME is if the funciton returns 1. It will return TRUE +// if it finds a non-varying range, otherwise FALSE. + +bool +gimple_ranger::assume_range_p (vrange &r, tree name) +{ + bool result_p = false; + edge e; + edge_iterator ei; + basic_block exit_bb = EXIT_BLOCK_PTR_FOR_FN (cfun); + Value_Range tmp (TREE_TYPE (name)); + r.set_undefined (); + + FOR_EACH_EDGE (e, ei, exit_bb->preds) + { + result_p = false; + gimple_stmt_iterator gsi = gsi_last_nondebug_bb (e->src); + if (gsi_end_p (gsi)) + break; + gimple *s = gsi_stmt (gsi); + if (!is_a<greturn *> (s)) + break; + greturn *gret = as_a<greturn *> (s); + tree op = gimple_return_retval (gret); + if (!gimple_range_ssa_p (op)) + break; + gimple *def = SSA_NAME_DEF_STMT (op); + if (!def || gimple_get_lhs (def) != op) + break; + tree lhs_type = TREE_TYPE (op); + if (!irange::supports_p (lhs_type)) + break; + unsigned prec = TYPE_PRECISION (lhs_type); + int_range<2> lhs_range (lhs_type, wi::one (prec), wi::one (prec)); + fur_stmt src (s, this); + if (gori ().compute_operand_range (tmp, def, lhs_range, name, src)) + { + r.union_ (tmp); + result_p = true; + } + } + // If every exit predecessor does not calculate a value, we can + // assume nothing. + return result_p && !r.varying_p (); +} + // This routine will export whatever global ranges are known to GCC // SSA_RANGE_NAME_INFO and SSA_NAME_PTR_INFO fields. diff --git a/gcc/gimple-range.h b/gcc/gimple-range.h index 8b2ff5685e5..3a6fdd9dbe0 100644 --- a/gcc/gimple-range.h +++ b/gcc/gimple-range.h @@ -61,6 +61,7 @@ public: auto_edge_flag non_executable_edge_flag; bool fold_stmt (gimple_stmt_iterator *gsi, tree (*) (tree)); void register_inferred_ranges (gimple *s); + bool assume_range_p (vrange &r, tree name); protected: bool fold_range_internal (vrange &r, gimple *s, tree name); void prefill_name (vrange &r, tree name); -- 2.37.3 [-- Attachment #3: 0003-Show-output.patch --] [-- Type: text/x-patch, Size: 1399 bytes --] From e55fecddff5154116387e8f5ed678dd4fda81146 Mon Sep 17 00:00:00 2001 From: Andrew MacLeod <amacleod@redhat.com> Date: Mon, 17 Oct 2022 12:28:21 -0400 Subject: [PATCH 3/3] Show output --- gcc/tree-vrp.cc | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/gcc/tree-vrp.cc b/gcc/tree-vrp.cc index 93482e5d102..120b9611bc7 100644 --- a/gcc/tree-vrp.cc +++ b/gcc/tree-vrp.cc @@ -4345,6 +4345,30 @@ execute_ranger_vrp (struct function *fun, bool warn_array_bounds_p) scev_initialize (); calculate_dominance_info (CDI_DOMINATORS); + gimple_ranger *r2= enable_ranger (fun); + for (unsigned i = 0; i < num_ssa_names; i++) + { + tree name = ssa_name (i); + if (!name || !gimple_range_ssa_p (name)) + continue; + tree type = TREE_TYPE (name); + if (!Value_Range::supports_type_p (type)) + continue; + Value_Range assume_range (type); + if (r2->assume_range_p (assume_range, name)) + { + if (dump_file) + { + fprintf (dump_file, "for an assume function, "); + print_generic_expr (dump_file, name, TDF_SLIM); + fprintf (dump_file, " would have a range of "); + assume_range.dump (dump_file); + fputc ('\n', dump_file); + } + } + } + disable_ranger (fun); + set_all_edges_as_executable (fun); gimple_ranger *ranger = enable_ranger (fun, false); rvrp_folder folder (ranger); -- 2.37.3 ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH] middle-end IFN_ASSUME support [PR106654] 2022-10-10 8:54 [PATCH] middle-end IFN_ASSUME support [PR106654] Jakub Jelinek 2022-10-10 21:09 ` Jason Merrill 2022-10-11 18:05 ` [PATCH] middle-end " Andrew MacLeod @ 2022-10-14 20:43 ` Martin Uecker 2022-10-14 21:20 ` Jakub Jelinek 2 siblings, 1 reply; 35+ messages in thread From: Martin Uecker @ 2022-10-14 20:43 UTC (permalink / raw) To: Jakub Jelinek; +Cc: gcc-patches Am Montag, den 10.10.2022, 10:54 +0200 schrieb Jakub Jelinek: > Hi! > > 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. My recommendation would be to only process side-effect-free assumptions and warn about the rest (clang does this for __builtin_assume). I do not think this is worth the complexity and I am not so sure the semantics of a hypothetical evaluation are terribly well defined. That you can not verify this properly by turning it into traps in debug mode (as this would execute the side effects) also makes this a terrible feature IMHO. MSVC which this feature was based does not seem to make much to sense to me: https://godbolt.org/z/4Ebar3G9b Martin > 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. Otherwise, it keeps the handling of the simplest > assumptions as is, and otherwise arranges for the assumptions to be > visible in the IL as > .ASSUME (_Z2f4i._assume.0, i_1(D)); > call where there is an artificial function like: > bool _Z2f4i._assume.0 (int i) > { > bool _2; > > <bb 2> [local count: 1073741824]: > _2 = i_1(D) == 43; > return _2; > > } > with the semantics that there is UB unless the assumption function > would return true. > > Aldy, could ranger handle this? If it sees .ASSUME call, > walk the body of such function from the edge(s) to exit with the > assumption that the function returns true, so above set _2 [true, true] > and from there derive that i_1(D) [43, 43] and then map the argument > in the assumption function to argument passed to IFN_ASSUME (note, > args there are shifted by 1)? > > During gimplification it actually gimplifies it into > D.2591 = .ASSUME (); > if (D.2591 != 0) goto <D.2593>; else goto <D.2592>; > <D.2593>: > { > i = i + 1; > D.2591 = i == 44; > } > <D.2592>: > .ASSUME (D.2591); > with the condition wrapped into a GIMPLE_BIND (I admit the above isn't > extra clean but it is just something to hold it from gimplifier until > gimple low pass; it reassembles if (condition_never_true) { cond; }; > an alternative would be introduce GOMP_ASSUME statement that would have > the guard var as operand and the GIMPLE_BIND as body, but for the > few passes (tree-nested and omp lowering) in between that looked like > an overkill to me) which is then pattern matched during gimple lowering > and outlined into a separate function. Variables declared inside of the > condition (both static and automatic) just change context, automatic > variables from the caller are turned into parameters (note, as the code > is never executed, I handle this way even non-POD types, we don't need to > bother pretending there would be user copy constructors etc. involved). > > The assume_function artificial functions are then optimized until RTL > expansion, which isn't done for them nor any later pass, on the other > side bodies are not freed when done. > > Earlier version of the patch has been successfully bootstrapped/regtested > on x86_64-linux and i686-linux and all assume tests have passed even with > this version. Ok for trunk if it succeeds on another bootstrap/regtest? > > There are a few further changes I'd like to do, like ignoring the > .ASSUME calls in inlining size estimations (but haven't figured out where > it is done), or for LTO arrange for the assume functions to be emitted > in all partitions that reference those (usually there will be just one, > unless code with the assumption got inlined, versioned etc.). > > 2022-10-10 Jakub Jelinek <jakub@redhat.com> > > PR c++/106654 > gcc/ > * function.h (struct function): Add assume_function bitfield. > * gimplify.cc (gimplify_call_expr): For -O0, always throw > away assumptions on the floor immediately. Otherwise if the > assumption isn't simple enough, expand it into IFN_ASSUME > guarded block. > * 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 > @@ -3556,6 +3556,12 @@ gimplify_call_expr (tree *expr_p, gimple > > if (ifn == IFN_ASSUME) > { > + /* If not optimizing, ignore the assumptions. */ > + if (!optimize) > + { > + *expr_p = NULL_TREE; > + return GS_ALL_DONE; > + } > if (simple_condition_p (CALL_EXPR_ARG (*expr_p, 0))) > { > /* If the [[assume (cond)]]; condition is simple > @@ -3569,7 +3575,46 @@ gimplify_call_expr (tree *expr_p, gimple > fndecl, 0)); > return GS_OK; > } > - /* FIXME: Otherwise expand it specially. */ > + /* 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; > +} > > Jakub > ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH] middle-end IFN_ASSUME support [PR106654] 2022-10-14 20:43 ` Martin Uecker @ 2022-10-14 21:20 ` Jakub Jelinek 2022-10-15 8:07 ` Martin Uecker 0 siblings, 1 reply; 35+ messages in thread From: Jakub Jelinek @ 2022-10-14 21:20 UTC (permalink / raw) To: Martin Uecker; +Cc: gcc-patches On Fri, Oct 14, 2022 at 10:43:16PM +0200, Martin Uecker wrote: > Am Montag, den 10.10.2022, 10:54 +0200 schrieb Jakub Jelinek: > > Hi! > > > > 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. > > My recommendation would be to only process side-effect-free > assumptions and warn about the rest (clang does this for > __builtin_assume). I do not think this is worth the I think that is bad choice and makes it useless. > complexity and I am not so sure the semantics of a > hypothetical evaluation are terribly well defined. I think the C++23 paper is quite clear. Yes, you can't verify it in debug mode, but there are many other UBs that are hard to verify through runtime instrumentation. And, OpenMP has a similar feature (though, in that case it is even a stronger guarantee where something is guaranteed to hold across a whole region rather than just on its entry. > That you can not verify this properly by turning it > into traps in debug mode (as this would execute the > side effects) also makes this a terrible feature IMHO. > > MSVC which this feature was based does not seem to make > much to sense to me: https://godbolt.org/z/4Ebar3G9b So maybe their feature is different from what is in C++23, or is badly implemented? I think with what we have in the works for GCC we'll be able to optimize in int f(int i) { [[assume(1 == i++)]]; return (1 == i++); } int g(int i) { [[assume(1 == ++i)]]; return (1 == ++i); } extern int i; int h(void) { [[assume(1 == ++i)]]; return (1 == ++i); } int k(int i) { [[assume(42 == ++i)]]; return i; } at least f/g to return 1; and k to return 41; The h case might take a while to take advantage of. Jakub ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH] middle-end IFN_ASSUME support [PR106654] 2022-10-14 21:20 ` Jakub Jelinek @ 2022-10-15 8:07 ` Martin Uecker 2022-10-15 8:53 ` Jakub Jelinek 0 siblings, 1 reply; 35+ messages in thread From: Martin Uecker @ 2022-10-15 8:07 UTC (permalink / raw) To: Jakub Jelinek; +Cc: gcc-patches Am Freitag, den 14.10.2022, 23:20 +0200 schrieb Jakub Jelinek: > On Fri, Oct 14, 2022 at 10:43:16PM +0200, Martin Uecker wrote: > > Am Montag, den 10.10.2022, 10:54 +0200 schrieb Jakub Jelinek: > > > Hi! > > > > > > 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. > > > > My recommendation would be to only process side-effect-free > > assumptions and warn about the rest (clang does this for > > __builtin_assume). I do not think this is worth the > > I think that is bad choice and makes it useless. I think [[assume(10 == i + 1)]] could be useful as it is nicer syntax than if (10 != i + 1) unreachable(); but [[assume(10 == i++)]] is confusing / untestable and therefor seems a bit dangerous. But we do not have to agree, I just stating my opinion here. I would personally then suggest to have an option for warning about side effects in assume. > > complexity and I am not so sure the semantics of a > > hypothetical evaluation are terribly well defined. > > I think the C++23 paper is quite clear. Yes, you can't verify it > in debug mode, but there are many other UBs that are hard to verify > through runtime instrumentation. And none are good. Some are very useful though (or even required in the context of C/C++). But I think there should be a very good reason for adding more. Personally, I do not see [[assume]] how side effects is useful enough to justify adding another kind of untestable UB. Especially also because it has somewhat vaguely defined semantics. I don't know any formalization the proposed semantics and the normative wording "the converted expression would evaluate to true" is probably good starting point for a PhD thesis. > And, OpenMP has a similar feature (though, in that case it is even > a stronger guarantee where something is guaranteed to hold across > a whole region rather than just on its entry. > > > That you can not verify this properly by turning it > > into traps in debug mode (as this would execute the > > side effects) also makes this a terrible feature IMHO. > > > > MSVC which this feature was based does not seem to make > > much to sense to me: https://godbolt.org/z/4Ebar3G9b > > So maybe their feature is different from what is in C++23, > or is badly implemented? The paper says as the first sentence in the abstract: "We propose a standard facility providing the semantics of existing compiler built-ins such as __builtin_assume (Clang) and __assume (MSVC, ICC)." But Clang does not support side effects and MSVC is broken. But yes, the paper then describes these extended semantics for expression with side effects. According to the author this was based on discussions with MSVC developers, but - to me - the fact that MSVC still implements something else which does not seem to make sense speaks against this feature. > I think with what we have in the works for GCC we'll be able to optimize > in > int f(int i) > { > [[assume(1 == i++)]]; > return (1 == i++); > } > > int g(int i) > { > [[assume(1 == ++i)]]; > return (1 == ++i); > } > > extern int i; > > int h(void) > { > [[assume(1 == ++i)]]; > return (1 == ++i); > } > > > int k(int i) > { > [[assume(42 == ++i)]]; > return i; > } > at least f/g to return 1; and k to return 41; > The h case might take a while to take advantage of. But why? Do we really want to encourage people to write such code? Martin ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH] middle-end IFN_ASSUME support [PR106654] 2022-10-15 8:07 ` Martin Uecker @ 2022-10-15 8:53 ` Jakub Jelinek 2022-10-17 5:52 ` Martin Uecker 0 siblings, 1 reply; 35+ messages in thread From: Jakub Jelinek @ 2022-10-15 8:53 UTC (permalink / raw) To: Martin Uecker; +Cc: gcc-patches On Sat, Oct 15, 2022 at 10:07:46AM +0200, Martin Uecker wrote: > But why? Do we really want to encourage people to > write such code? Of course these ++ cases inside of expressions are just obfuscation. But the point is to support using predicates that can be inlined and simplified into something really simple the optimizers can understand. The paper shows as useful e.g. being able to assert something is finite: [[assume (std::isfinite (x)]]; and with the recent changes on the GCC side it is now or shortly will be possible to take advantage of such predicates. It is true that [[assume (__builtin_isfinite (x)]]; could work if we check TREE_SIDE_EFFECTS on the GCC side because it is a const function, but that is a GNU extension, so the standard can't count with that. std::isfinite isn't even marked const in libstdc++ and one can figure that out during IPA propagation only. There are many similar predicates, or user could have some that are useful to his program. And either in the end it wouldn't have side-effects but the compiler doesn't know, or would but those side-effects would be unimportant to the optimizations the compiler can derive from those. As the spec defines it well what happens with the side-effects and it is an attribute, not a function and the languages have non-evaluated contexts in other places, I don't see where a user confusion could come. We don't warn for sizeof (i++) and similar either. __builtin_assume (i++) is a bad choice because it looks like a function call (after all, the compilers have many similar builtins) and its argument looks like normal argument to the function, so it is certainly unexpected that the side-effects aren't evaluated. Jakub ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH] middle-end IFN_ASSUME support [PR106654] 2022-10-15 8:53 ` Jakub Jelinek @ 2022-10-17 5:52 ` Martin Uecker 0 siblings, 0 replies; 35+ messages in thread From: Martin Uecker @ 2022-10-17 5:52 UTC (permalink / raw) To: Jakub Jelinek; +Cc: gcc-patches Am Samstag, den 15.10.2022, 10:53 +0200 schrieb Jakub Jelinek: > On Sat, Oct 15, 2022 at 10:07:46AM +0200, Martin Uecker wrote: > > But why? Do we really want to encourage people to > > write such code? > > Of course these ++ cases inside of expressions are just obfuscation. > But the point is to support using predicates that can be inlined and > simplified into something really simple the optimizers can understand. This makes sense,. > The paper shows as useful e.g. being able to assert something is finite: > [[assume (std::isfinite (x)]]; > and with the recent changes on the GCC side it is now or shortly will be > possible to take advantage of such predicates. > It is true that > [[assume (__builtin_isfinite (x)]]; > could work if we check TREE_SIDE_EFFECTS on the GCC side because > it is a const function, but that is a GNU extension, so the standard > can't count with that. std::isfinite isn't even marked const in libstdc++ > and one can figure that out during IPA propagation only. Hm, that already seems to work with if (!std::isfinite(x)) __builtin_unreachable(); https://godbolt.org/z/hj3WrEhjb > There are many similar predicates, or user could have some that are useful > to his program. And either in the end it wouldn't have side-effects > but the compiler doesn't know, or would but those side-effects would be > unimportant to the optimizations the compiler can derive from those. I still have the feeling that relying on something such as the pure and const attributes might then be a better approach for this. From the standards point of view, this is OK as GCC can just set its own rules as long as it is a subset of what the standard allows. > As the spec defines it well what happens with the side-effects and it > is an attribute, not a function and the languages have non-evaluated > contexts in other places, I don't see where a user confusion could come. The user confusion might come when somebody writes something such as [[assume(1 == ++i)]] and I expect that people will start doing this once this works. But I am also a a bit worried about the slipperly slope of exploiting this more because what "would evaluate to true" implies in case of I/O, atomic accesses, volatile accesses etc. does not seem clear to me. But maybe I am worrying too much. > We don't warn for sizeof (i++) and similar either. Which is also confusing and clang does indeed warn about it outside of macros and I think GCC should too. > __builtin_assume (i++) is a bad choice because it looks like a function > call (after all, the compilers have many similar builtins) and its argument > looks like normal argument to the function, so it is certainly unexpected > that the side-effects aren't evaluated. I agree. Best Martin ^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH] middle-end IFN_ASSUME support [PR106654]
@ 2022-11-08 9:19 Pilar Latiesa
2022-11-08 12:10 ` Jakub Jelinek
0 siblings, 1 reply; 35+ messages in thread
From: Pilar Latiesa @ 2022-11-08 9:19 UTC (permalink / raw)
To: ma.uecker, gcc-patches
[-- Attachment #1: Type: text/plain, Size: 315 bytes --]
On Mon, Oct 17, 2022 at 05:32:32AM +0200, Martin Uecker wrote:
> Hm, that already seems to work with
>
> if (!std::isfinite(x))
> __builtin_unreachable();
>
> https://godbolt.org/z/hj3WrEhjb
Not anymore. Perhaps after making ranger the VRP default, because I get the
mentioned outcome with --param=vrp1-mode=vrp
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH] middle-end IFN_ASSUME support [PR106654] 2022-11-08 9:19 Pilar Latiesa @ 2022-11-08 12:10 ` Jakub Jelinek 0 siblings, 0 replies; 35+ messages in thread From: Jakub Jelinek @ 2022-11-08 12:10 UTC (permalink / raw) To: Pilar Latiesa; +Cc: ma.uecker, gcc-patches On Tue, Nov 08, 2022 at 10:19:50AM +0100, Pilar Latiesa via Gcc-patches wrote: > On Mon, Oct 17, 2022 at 05:32:32AM +0200, Martin Uecker wrote: > > Hm, that already seems to work with > > > > if (!std::isfinite(x)) > > __builtin_unreachable(); > > > > https://godbolt.org/z/hj3WrEhjb > > Not anymore. Perhaps after making ranger the VRP default, because I get the > mentioned outcome with --param=vrp1-mode=vrp I've filed https://gcc.gnu.org/PR107569 for this. Jakub ^ permalink raw reply [flat|nested] 35+ messages in thread
end of thread, other threads:[~2022-11-08 12:11 UTC | newest] Thread overview: 35+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2022-10-10 8:54 [PATCH] middle-end IFN_ASSUME support [PR106654] Jakub Jelinek 2022-10-10 21:09 ` Jason Merrill 2022-10-10 21:19 ` Jakub Jelinek 2022-10-11 13:36 ` [PATCH] middle-end, v2: " Jakub Jelinek 2022-10-12 15:48 ` Jason Merrill 2022-10-13 6:50 ` [PATCH] middle-end, v3: " Jakub Jelinek 2022-10-14 11:27 ` Richard Biener 2022-10-14 18:33 ` Jakub Jelinek 2022-10-17 6:55 ` Richard Biener 2022-10-17 15:44 ` [PATCH] middle-end, v4: " Jakub Jelinek 2022-10-18 7:00 ` Richard Biener 2022-10-18 21:31 ` Andrew MacLeod 2022-10-19 16:06 ` Jakub Jelinek 2022-10-19 16:55 ` Andrew MacLeod 2022-10-19 17:39 ` Jakub Jelinek 2022-10-19 17:41 ` Jakub Jelinek 2022-10-19 18:25 ` Andrew MacLeod 2022-10-19 17:14 ` Andrew MacLeod 2022-10-11 18:05 ` [PATCH] middle-end " Andrew MacLeod 2022-10-12 10:15 ` Jakub Jelinek 2022-10-12 14:31 ` Andrew MacLeod 2022-10-12 14:39 ` Jakub Jelinek 2022-10-12 16:12 ` Andrew MacLeod 2022-10-13 8:11 ` Richard Biener 2022-10-13 9:53 ` Jakub Jelinek 2022-10-13 13:16 ` Andrew MacLeod 2022-10-13 9:57 ` Jakub Jelinek 2022-10-17 17:53 ` Andrew MacLeod 2022-10-14 20:43 ` Martin Uecker 2022-10-14 21:20 ` Jakub Jelinek 2022-10-15 8:07 ` Martin Uecker 2022-10-15 8:53 ` Jakub Jelinek 2022-10-17 5:52 ` Martin Uecker 2022-11-08 9:19 Pilar Latiesa 2022-11-08 12:10 ` Jakub Jelinek
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).