| /* Support for C++23 ASSUME keyword functionailty. |
| Copyright (C) 2023-2025 Free Software Foundation, Inc. |
| Contributed by Andrew MacLeod <amacleod@redhat.com>. |
| |
| This file is part of GCC. |
| |
| GCC is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 3, or (at your option) |
| any later version. |
| |
| GCC is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GCC; see the file COPYING3. If not see |
| <http://www.gnu.org/licenses/>. */ |
| |
| #include "config.h" |
| #include "system.h" |
| #include "coretypes.h" |
| #include "basic-block.h" |
| #include "bitmap.h" |
| #include "options.h" |
| #include "function.h" |
| #include "cfg.h" |
| #include "tree.h" |
| #include "gimple.h" |
| #include "tree-pass.h" |
| #include "ssa.h" |
| #include "gimple-iterator.h" |
| #include "gimple-range.h" |
| #include "tree-dfa.h" |
| #include "tree-cfg.h" |
| #include "gimple-pretty-print.h" |
| |
| // An assume query utilizes the current range query to implement the assume |
| // keyword. |
| // For any return value of 1 from the function, it attempts to determine |
| // which paths lead to a 1 value being returned. On those paths, it determines |
| // the ranges of any ssa_names listed in bitmap P (usually the parm list for |
| // the function), and combines them all. |
| // These ranges are then set as the global ranges for those parms in this |
| // function. |
| // Other functions which refer to this function in an assume builtin |
| // will then pick up these ranges for the parameters via the inferred range |
| // mechanism. |
| // See gimple-range-infer.cc::gimple_infer_range::check_assume_func () |
| // |
| // my_func (int x) |
| // { |
| // <...> |
| // assume [[(x == 1 || x ==4))]] |
| // if (x ==3) |
| // |
| // a small temporary assume function consisting of |
| // assume_f1 (int x) { return x == 1 || x == 4; } |
| // is constructed by the front end, and optimized, at the very end of |
| // optimization, instead of generating code, we instead invoke the assume pass |
| // which uses this query to set the the global value of parm x to [1,1][4,4] |
| // |
| // Meanwhile., my_func has been rewritten to be: |
| // |
| // my_func (int x_2) |
| // { |
| // <...> |
| // assume_builtin_call assume_f1 (x_2); |
| // if (x_2 == 3) |
| // |
| // When ranger is processing the assume_builtin_call, it looks up the global |
| // value of the parameter in assume_f1, which is [1,1][4,4]. It then registers |
| // and inferred range at this statement setting the value x_2 to [1,1][4,4] |
| // |
| // Any uses of x_2 after this statement will now utilize this inferred range. |
| // |
| // When VRP processes if (x_2 == 3), it picks up the inferred range, and |
| // determines that x_2 can never be 3, and will rewrite the branch to |
| // if (0 != 0) |
| |
| class assume_query |
| { |
| public: |
| assume_query (function *f, bitmap p); |
| protected: |
| inline void process_stmts (gimple *s, vrange &lhs_range) |
| { |
| fur_depend src (s, get_range_query (m_func)); |
| calculate_stmt (s, lhs_range, src); |
| update_parms (src); |
| } |
| void update_parms (fur_source &src); |
| 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); |
| |
| ssa_lazy_cache m_path; // Values found on path |
| ssa_lazy_cache m_parms; // Cumulative parameter value calculated |
| bitmap m_parm_list; // Parameter ssa-names list. |
| function *m_func; |
| }; |
| |
| // If function F returns a integral value, and has a single return |
| // statement, try to calculate the range of each value in P that leads |
| // to the return statement returning TRUE. |
| |
| assume_query::assume_query (function *f, bitmap p) : m_parm_list (p), |
| m_func (f) |
| { |
| basic_block exit_bb = EXIT_BLOCK_PTR_FOR_FN (f); |
| // If there is more than one predecessor to the exit block, bail. |
| if (!single_pred_p (exit_bb)) |
| return; |
| |
| 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; |
| |
| // Check if the single return value is a symbolic and supported type. |
| 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; |
| |
| // Only values of interest are when the return value is 1. The definition |
| // of the return value must be in the same block, or we have |
| // complicated flow control we don't understand, and just return. |
| unsigned prec = TYPE_PRECISION (lhs_type); |
| int_range<2> lhs_range (lhs_type, wi::one (prec), wi::one (prec)); |
| |
| gimple *def = SSA_NAME_DEF_STMT (op); |
| if (!def || gimple_get_lhs (def) != op || gimple_bb (def) != bb) |
| return; |
| |
| // Determine if this is a PHI or a linear sequence to deal with. |
| if (is_a<gphi *> (def)) |
| calculate_phi (as_a<gphi *> (def), lhs_range); |
| else |
| process_stmts (def, lhs_range); |
| |
| if (dump_file) |
| fprintf (dump_file, "\n\nAssumptions :\n--------------\n"); |
| |
| // Now export any interesting values that were found. |
| bitmap_iterator bi; |
| unsigned x; |
| EXECUTE_IF_SET_IN_BITMAP (m_parm_list, 0, x, bi) |
| { |
| tree name = ssa_name (x); |
| tree type = TREE_TYPE (name); |
| value_range assume_range (type); |
| // Set the global range of NAME to anything calculated. |
| if (m_parms.get_range (assume_range, name) && !assume_range.varying_p ()) |
| set_range_info (name, assume_range); |
| } |
| |
| if (dump_file) |
| { |
| fputc ('\n', dump_file); |
| gimple_dump_cfg (dump_file, dump_flags & ~TDF_DETAILS); |
| } |
| } |
| |
| // This function will update all the current values of interesting parameters. |
| // It tries, in order: |
| // a) a range found via path calculations. |
| // b) range of the parm at SRC point in the IL. (either edge or stmt) |
| // c) VARYING if those options fail. |
| // The value is then unioned with any existing value, allowing for the |
| // cumulation of all ranges leading to the return that return 1. |
| |
| void |
| assume_query::update_parms (fur_source &src) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, "\nupdate parameters\n"); |
| |
| // Merge any parameter values. |
| bitmap_iterator bi; |
| unsigned x; |
| EXECUTE_IF_SET_IN_BITMAP (m_parm_list, 0, x, bi) |
| { |
| tree name = ssa_name (x); |
| tree type = TREE_TYPE (name); |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "PARAMETER "); |
| print_generic_expr (dump_file, name, TDF_SLIM); |
| } |
| value_range glob_range (type); |
| // Find a value from calculations. |
| // There will be a value in m_path if GORI calculated an operand value. |
| if (m_path.get_range (glob_range, name)) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "\n Calculated path range:"); |
| glob_range.dump (dump_file); |
| } |
| } |
| // Otherwise, let ranger determine the range at the SRC location. |
| else if (src.get_operand (glob_range, name)) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "\n Ranger Computes path range:"); |
| glob_range.dump (dump_file); |
| } |
| } |
| else |
| glob_range.set_varying (type); |
| |
| // Find any current saved value of parm, and combine them. |
| value_range parm_range (type); |
| if (m_parms.get_range (parm_range, name)) |
| glob_range.union_ (parm_range); |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "\n Combine with previous range:"); |
| parm_range.dump (dump_file); |
| fputc ('\n', dump_file); |
| print_generic_expr (dump_file, name, TDF_SLIM); |
| fprintf (dump_file, " = "); |
| glob_range.dump (dump_file); |
| fputc ('\n', dump_file); |
| } |
| // Set this new value. |
| m_parms.set_range (name, glob_range); |
| } |
| // Now reset the path values for the next path. |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, "---------------------\n"); |
| m_path.clear (); |
| } |
| |
| |
| // Evaluate PHI statement, using the provided LHS range. |
| // Only process edge that are taken and return the LHS of the PHI. |
| |
| void |
| assume_query::calculate_phi (gphi *phi, vrange &lhs_range) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Processing PHI feeding return value:\n"); |
| print_gimple_stmt (dump_file, phi, 0, TDF_SLIM); |
| } |
| 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)); |
| edge e = gimple_phi_arg_edge (phi, x); |
| value_range edge_range (TREE_TYPE (arg)); |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "\nArgument %d (bb%d->bb%d): ", x, e->src->index, |
| e->dest->index); |
| print_generic_expr (dump_file, arg, TDF_SLIM); |
| fputc ('\n', dump_file); |
| } |
| // If we can't get an edge range, be conservative and assume the |
| // edge can be taken. |
| if (get_range_query (m_func)->range_on_edge (edge_range, e, arg)) |
| { |
| if (gimple_range_ssa_p (arg)) |
| { |
| arg_range = lhs_range; |
| range_cast (arg_range, TREE_TYPE (arg)); |
| |
| // An SSA_NAME arg will start with the LHS value. |
| // Check the range of ARG on the edge leading here. If that range |
| // cannot be any value from the LHS of the PHI, then this branch |
| // will not be taken to return the LHS value and can be ignored. |
| arg_range.intersect (edge_range); |
| if (arg_range.undefined_p ()) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, " IGNORE edge : LHS range :"); |
| lhs_range.dump (dump_file); |
| fprintf (dump_file, " Edge produces : "); |
| edge_range.dump (dump_file); |
| fputc ('\n', dump_file); |
| } |
| continue; |
| } |
| |
| // If the def is in the immediate preceeding block, process it |
| // with GORI to determine what values can produce this |
| // argument value. Otherwise there is more CFG flow, so query |
| // the edge for parm ranges. This is conservative. |
| gimple *def_stmt = SSA_NAME_DEF_STMT (arg); |
| if (def_stmt && gimple_get_lhs (def_stmt) == arg |
| && gimple_bb (def_stmt) == e->src) |
| { |
| process_stmts (def_stmt, arg_range); |
| continue; |
| } |
| // Fall through to process the parameter values on the edge. |
| } |
| else |
| { |
| // If this is a constant value that differs from LHS, this |
| // edge cannot be taken and we can ignore it. Otherwise fall |
| // thorugh and process the parameters on the edge. |
| edge_range.intersect (lhs_range); |
| if (edge_range.undefined_p ()) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, " IGNORE : const edge not taken\n"); |
| continue; |
| } |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, |
| " Const edge executed, compute incoming ranges.\n"); |
| |
| } |
| } |
| // The parameters on the edge now need calculating. |
| fur_edge src (e, get_range_query (m_func)); |
| update_parms (src); |
| } |
| } |
| |
| // Evaluate operand OP on statement S, using the provided LHS range. |
| // If successful, set the range in path table, then visit OP's def stmt |
| // if it is in the same BB. |
| |
| void |
| assume_query::calculate_op (tree op, gimple *s, vrange &lhs, fur_source &src) |
| { |
| basic_block bb = gimple_bb (s); |
| value_range op_range (TREE_TYPE (op)); |
| if (src.gori () && |
| src.gori ()->compute_operand_range (op_range, s, lhs, op, src) |
| && !op_range.varying_p ()) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, " Operand "); |
| print_generic_expr (dump_file, op, TDF_SLIM); |
| fprintf (dump_file, " calculated as "); |
| op_range.dump (dump_file); |
| } |
| // Set the global range, merging if there is already a range. |
| m_path.merge_range (op, op_range); |
| m_path.get_range (op_range, op); |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, " New path range :"); |
| op_range.dump (dump_file); |
| fputc ('\n', dump_file); |
| } |
| gimple *def_stmt = SSA_NAME_DEF_STMT (op); |
| // Terminate if the pathway leads to a different block as we |
| // are not dealing with flow. Ranger will make those queries. |
| if (def_stmt && gimple_get_lhs (def_stmt) == op |
| && gimple_bb (def_stmt) == bb) |
| calculate_stmt (def_stmt, op_range, src); |
| } |
| } |
| |
| // Evaluate statement S which produces range LHS_RANGE. Use GORI to |
| // determine what values the operands can have to produce the LHS, |
| // and set these in the M_PATH table. |
| |
| void |
| assume_query::calculate_stmt (gimple *s, vrange &lhs_range, fur_source &src) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, " Processing stmt with LHS = "); |
| lhs_range.dump (dump_file); |
| fprintf (dump_file, " : "); |
| print_gimple_stmt (dump_file, s, 0, TDF_SLIM); |
| } |
| 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); |
| } |
| } |
| |
| namespace { |
| |
| 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 *fun) final override |
| { |
| // Create a bitmap of all the parameters in this function. |
| // Invoke the assume_query to determine what values these parameters |
| // have when the function returns TRUE, and set the global values of |
| // those parameters in this function based on that. This will later be |
| // utilized by ranger when processing builtin IFN_ASSUME function calls. |
| // See gimple-range-infer.cc::check_assume_func (). |
| auto_bitmap decls; |
| for (tree arg = DECL_ARGUMENTS (fun->decl); arg; arg = DECL_CHAIN (arg)) |
| { |
| tree name = ssa_default_def (fun, arg); |
| if (!name || !gimple_range_ssa_p (name)) |
| continue; |
| tree type = TREE_TYPE (name); |
| if (!value_range::supports_type_p (type)) |
| continue; |
| bitmap_set_bit (decls, SSA_NAME_VERSION (name)); |
| } |
| // If there are no parameters to map, simply return; |
| if (bitmap_empty_p (decls)) |
| return TODO_discard_function; |
| |
| enable_ranger (fun); |
| |
| // This assume query will set any global values required. |
| assume_query query (fun, decls); |
| |
| disable_ranger (fun); |
| return TODO_discard_function; |
| } |
| |
| }; // class pass_assumptions |
| |
| } // anon namespace |
| |
| gimple_opt_pass * |
| make_pass_assumptions (gcc::context *ctx) |
| { |
| return new pass_assumptions (ctx); |
| } |