| /* Gimple range inference implementation. |
| Copyright (C) 2022-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 "backend.h" |
| #include "insn-codes.h" |
| #include "tree.h" |
| #include "gimple.h" |
| #include "ssa.h" |
| #include "gimple-pretty-print.h" |
| #include "gimple-range.h" |
| #include "value-range-storage.h" |
| #include "tree-cfg.h" |
| #include "target.h" |
| #include "attribs.h" |
| #include "gimple-iterator.h" |
| #include "gimple-walk.h" |
| #include "cfganal.h" |
| #include "tree-dfa.h" |
| |
| // Create the global oracle. |
| |
| infer_range_oracle infer_oracle; |
| |
| // This class is merely an accessor which is granted internals to |
| // gimple_infer_range such that non_null_loadstore as a static callback can |
| // call the protected add_nonzero (). |
| // Static functions ccannot be friends, so we do it through a class wrapper. |
| |
| class non_null_wrapper |
| { |
| public: |
| inline non_null_wrapper (gimple_infer_range *infer) : m_infer (infer) { } |
| inline void add_nonzero (tree name) { m_infer->add_nonzero (name); } |
| inline void add_range (tree t, vrange &r) { m_infer->add_range (t, r); } |
| private: |
| gimple_infer_range *m_infer; |
| }; |
| |
| // 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. |
| |
| static bool |
| non_null_loadstore (gimple *, tree op, tree, void *data) |
| { |
| if (TREE_CODE (op) == MEM_REF || TREE_CODE (op) == TARGET_MEM_REF) |
| { |
| /* Some address spaces may legitimately dereference zero. */ |
| addr_space_t as = TYPE_ADDR_SPACE (TREE_TYPE (op)); |
| if (!targetm.addr_space.zero_address_valid (as)) |
| { |
| non_null_wrapper wrapper ((gimple_infer_range *)data); |
| wrapper.add_nonzero (TREE_OPERAND (op, 0)); |
| } |
| } |
| return false; |
| } |
| |
| // Process an ASSUME call to see if there are any inferred ranges available. |
| |
| void |
| gimple_infer_range::check_assume_func (gcall *call) |
| { |
| tree arg; |
| unsigned i; |
| tree assume_id = TREE_OPERAND (gimple_call_arg (call, 0), 0); |
| if (!assume_id) |
| return; |
| struct function *fun = DECL_STRUCT_FUNCTION (assume_id); |
| if (!fun) |
| return; |
| // Loop over arguments, matching them to the assume parameters. |
| for (arg = DECL_ARGUMENTS (assume_id), i = 1; |
| arg && i < gimple_call_num_args (call); |
| i++, arg = DECL_CHAIN (arg)) |
| { |
| tree op = gimple_call_arg (call, i); |
| tree type = TREE_TYPE (op); |
| if (gimple_range_ssa_p (op) && value_range::supports_type_p (type)) |
| { |
| tree default_def = ssa_default_def (fun, arg); |
| if (!default_def || type != TREE_TYPE (default_def)) |
| continue; |
| // Query the global range of the default def in the assume function. |
| value_range assume_range (type); |
| gimple_range_global (assume_range, default_def, fun); |
| // If there is a non-varying result, add it as an inferred range. |
| if (!assume_range.varying_p ()) |
| { |
| 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); |
| } |
| } |
| } |
| } |
| } |
| |
| // Add NAME and RANGE to the range inference summary. |
| |
| void |
| gimple_infer_range::add_range (tree name, vrange &range) |
| { |
| // Do not add an inferred range if it is VARYING. |
| if (range.varying_p ()) |
| return; |
| m_names[num_args] = name; |
| m_ranges[num_args] = range; |
| if (num_args < size_limit - 1) |
| num_args++; |
| } |
| |
| // Add a nonzero range for NAME to the range inference summary. |
| |
| void |
| gimple_infer_range::add_nonzero (tree name) |
| { |
| if (!gimple_range_ssa_p (name)) |
| return; |
| prange nz; |
| nz.set_nonzero (TREE_TYPE (name)); |
| add_range (name, nz); |
| } |
| |
| // Process S for range inference and fill in the summary list. |
| // This is the routine where any new inferred ranges should be added. |
| // If USE_RANGEOPS is true, invoke range-ops on stmts with a single |
| // ssa-name a constant to reflect an inferred range. ie |
| // x_2 = y_3 + 1 will provide an inferred range for y_3 of [-INF, +INF - 1]. |
| // This defaults to FALSE as it can be expensive., |
| |
| gimple_infer_range::gimple_infer_range (gimple *s, range_query *q, |
| bool use_rangeops) |
| { |
| num_args = 0; |
| |
| if (is_a<gphi *> (s)) |
| return; |
| |
| // Default to the global query if none provided. |
| if (!q) |
| q = get_global_range_query (); |
| |
| if (is_a<gcall *> (s) && flag_delete_null_pointer_checks) |
| { |
| tree fntype = gimple_call_fntype (s); |
| bitmap nonnullargs = get_nonnull_args (fntype); |
| // Process any non-null arguments |
| if (nonnullargs) |
| { |
| for (unsigned i = 0; i < gimple_call_num_args (s); i++) |
| { |
| if (bitmap_empty_p (nonnullargs) |
| || bitmap_bit_p (nonnullargs, i)) |
| { |
| tree op = gimple_call_arg (s, i); |
| if (POINTER_TYPE_P (TREE_TYPE (op))) |
| add_nonzero (op); |
| } |
| } |
| BITMAP_FREE (nonnullargs); |
| } |
| if (fntype) |
| for (tree attrs = TYPE_ATTRIBUTES (fntype); |
| (attrs = lookup_attribute ("nonnull_if_nonzero", attrs)); |
| attrs = TREE_CHAIN (attrs)) |
| { |
| tree args = TREE_VALUE (attrs); |
| unsigned int idx = TREE_INT_CST_LOW (TREE_VALUE (args)) - 1; |
| unsigned int idx2 |
| = TREE_INT_CST_LOW (TREE_VALUE (TREE_CHAIN (args))) - 1; |
| unsigned int idx3 = idx2; |
| if (tree chain2 = TREE_CHAIN (TREE_CHAIN (args))) |
| idx3 = TREE_INT_CST_LOW (TREE_VALUE (chain2)) - 1; |
| if (idx < gimple_call_num_args (s) |
| && idx2 < gimple_call_num_args (s) |
| && idx3 < gimple_call_num_args (s)) |
| { |
| tree arg = gimple_call_arg (s, idx); |
| tree arg2 = gimple_call_arg (s, idx2); |
| tree arg3 = gimple_call_arg (s, idx3); |
| if (!POINTER_TYPE_P (TREE_TYPE (arg)) |
| || !INTEGRAL_TYPE_P (TREE_TYPE (arg2)) |
| || !INTEGRAL_TYPE_P (TREE_TYPE (arg3)) |
| || integer_zerop (arg2) |
| || integer_zerop (arg3)) |
| continue; |
| if (integer_nonzerop (arg2) && integer_nonzerop (arg3)) |
| add_nonzero (arg); |
| else |
| { |
| value_range r (TREE_TYPE (arg2)); |
| if (q->range_of_expr (r, arg2, s) |
| && !r.contains_p (build_zero_cst (TREE_TYPE (arg2)))) |
| { |
| if (idx2 == idx3) |
| add_nonzero (arg); |
| else |
| { |
| value_range r2 (TREE_TYPE (arg3)); |
| tree zero3 = build_zero_cst (TREE_TYPE (arg3)); |
| if (q->range_of_expr (r2, arg3, s) |
| && !r2.contains_p (zero3)) |
| add_nonzero (arg); |
| } |
| } |
| } |
| } |
| } |
| // Fallthru and walk load/store ops now. |
| } |
| |
| // Check for inferred ranges from ASSUME calls. |
| if (is_a<gcall *> (s) && gimple_call_internal_p (s) |
| && gimple_call_internal_fn (s) == IFN_ASSUME) |
| check_assume_func (as_a<gcall *> (s)); |
| |
| // Look for possible non-null values. |
| if (flag_delete_null_pointer_checks && gimple_code (s) != GIMPLE_ASM |
| && !gimple_clobber_p (s)) |
| walk_stmt_load_store_ops (s, (void *)this, non_null_loadstore, |
| non_null_loadstore); |
| |
| // Gated by flag. |
| if (!use_rangeops) |
| return; |
| |
| // Check if there are any inferred ranges from range-ops. |
| gimple_range_op_handler handler (s); |
| if (!handler) |
| return; |
| |
| // Only proceed if ONE operand is an SSA_NAME, This may provide an |
| // inferred range for 'y + 3' , but will bypass expressions like |
| // 'y + z' as it depends on symbolic values. |
| tree ssa1 = gimple_range_ssa_p (handler.operand1 ()); |
| tree ssa2 = gimple_range_ssa_p (handler.operand2 ()); |
| if ((ssa1 != NULL) == (ssa2 != NULL)) |
| return; |
| |
| // The other operand should be a constant, so just use the global range |
| // query to pick up any other values. |
| if (ssa1) |
| { |
| value_range op1 (TREE_TYPE (ssa1)); |
| if (op1_range (op1, s, q) && !op1.varying_p ()) |
| add_range (ssa1, op1); |
| } |
| else |
| { |
| gcc_checking_assert (ssa2); |
| value_range op2 (TREE_TYPE (ssa2)); |
| if (op2_range (op2, s, q) && !op2.varying_p ()) |
| add_range (ssa2, op2); |
| } |
| } |
| |
| // Create an single inferred range for NAMe using range R. |
| |
| gimple_infer_range::gimple_infer_range (tree name, vrange &r) |
| { |
| num_args = 0; |
| add_range (name, r); |
| } |
| |
| // ------------------------------------------------------------------------- |
| |
| // This class is an element in the list of inferred ranges. |
| |
| class exit_range |
| { |
| public: |
| tree name; |
| gimple *stmt; |
| vrange_storage *range; |
| exit_range *next; |
| }; |
| |
| |
| // If there is an element which matches SSA, return a pointer to the element. |
| // Otherwise return NULL. |
| |
| exit_range * |
| infer_range_manager::exit_range_head::find_ptr (tree ssa) |
| { |
| // Return NULL if SSA is not in this list. |
| if (!m_names || !bitmap_bit_p (m_names, SSA_NAME_VERSION (ssa))) |
| return NULL; |
| for (exit_range *ptr = head; ptr != NULL; ptr = ptr->next) |
| if (ptr->name == ssa) |
| return ptr; |
| // Should be unreachable. |
| gcc_unreachable (); |
| return NULL; |
| } |
| |
| // Construct a range infer manager. DO_SEARCH indicates whether an immediate |
| // use scan should be made the first time a name is processed. This is for |
| // on-demand clients who may not visit every statement and may miss uses. |
| // Q is the range_query to use for any lookups. Default is NULL which maps |
| // to the global_range_query. |
| |
| infer_range_manager::infer_range_manager (bool do_search, range_query *q) |
| { |
| // Set the range query to use. |
| m_query = q ? q : get_global_range_query (); |
| |
| bitmap_obstack_initialize (&m_bitmaps); |
| m_on_exit.create (0); |
| m_on_exit.safe_grow_cleared (last_basic_block_for_fn (cfun) + 1); |
| // m_seen == NULL indicates no scanning. Otherwise the bit indicates a |
| // scan has been performed on NAME. |
| if (do_search) |
| m_seen = BITMAP_ALLOC (&m_bitmaps); |
| else |
| m_seen = NULL; |
| obstack_init (&m_list_obstack); |
| // Non-zero elements are very common, so cache them for each ssa-name. |
| m_nonzero.create (0); |
| m_nonzero.safe_grow_cleared (num_ssa_names + 1); |
| m_range_allocator = new vrange_allocator; |
| } |
| |
| // Destruct a range infer manager. |
| |
| infer_range_manager::~infer_range_manager () |
| { |
| m_nonzero.release (); |
| obstack_free (&m_list_obstack, NULL); |
| m_on_exit.release (); |
| bitmap_obstack_release (&m_bitmaps); |
| delete m_range_allocator; |
| } |
| |
| // Return a non-zero range value of the appropriate type for NAME from |
| // the cache, creating it if necessary. |
| |
| const vrange& |
| infer_range_manager::get_nonzero (tree name) |
| { |
| unsigned v = SSA_NAME_VERSION (name); |
| if (v >= m_nonzero.length ()) |
| m_nonzero.safe_grow_cleared (num_ssa_names + 20); |
| if (!m_nonzero[v]) |
| { |
| m_nonzero[v] |
| = (irange *) m_range_allocator->alloc (sizeof (int_range <2>)); |
| m_nonzero[v]->set_nonzero (TREE_TYPE (name)); |
| } |
| return *(m_nonzero[v]); |
| } |
| |
| // Return TRUE if NAME has a range inference in block BB. If NAME is NULL, |
| // return TRUE if there are any name sin BB. |
| |
| bool |
| infer_range_manager::has_range_p (basic_block bb, tree name) |
| { |
| // Check if this is an immediate use search model. |
| if (name && m_seen && !bitmap_bit_p (m_seen, SSA_NAME_VERSION (name))) |
| register_all_uses (name); |
| |
| if (bb->index >= (int)m_on_exit.length ()) |
| return false; |
| |
| bitmap b = m_on_exit[bb->index].m_names; |
| if (!b) |
| return false; |
| |
| if (name) |
| return bitmap_bit_p (m_on_exit[bb->index].m_names, SSA_NAME_VERSION (name)); |
| return !bitmap_empty_p (b); |
| } |
| |
| // Return TRUE if NAME has a range inference in block BB, and adjust range R |
| // to include it. |
| |
| bool |
| infer_range_manager::maybe_adjust_range (vrange &r, tree name, basic_block bb) |
| { |
| if (!has_range_p (bb, name)) |
| return false; |
| exit_range *ptr = m_on_exit[bb->index].find_ptr (name); |
| gcc_checking_assert (ptr); |
| // Return true if this exit range changes R, otherwise false. |
| tree type = TREE_TYPE (name); |
| value_range tmp (type); |
| ptr->range->get_vrange (tmp, type); |
| return r.intersect (tmp); |
| } |
| |
| // Add all inferred ranges in INFER at stmt S. |
| |
| void |
| infer_range_manager::add_ranges (gimple *s, gimple_infer_range &infer) |
| { |
| for (unsigned x = 0; x < infer.num (); x++) |
| { |
| tree arg = infer.name (x); |
| value_range r (TREE_TYPE (arg)); |
| m_query->range_of_expr (r, arg, s); |
| // Only add the inferred range if it changes the current range. |
| if (r.intersect (infer.range (x))) |
| add_range (arg, s, infer.range (x)); |
| } |
| } |
| |
| // Add range R as an inferred range for NAME on stmt S. |
| |
| void |
| infer_range_manager::add_range (tree name, gimple *s, const vrange &r) |
| { |
| basic_block bb = gimple_bb (s); |
| if (!bb) |
| return; |
| if (bb->index >= (int)m_on_exit.length ()) |
| m_on_exit.safe_grow_cleared (last_basic_block_for_fn (cfun) + 1); |
| |
| // Create the summary list bitmap if it doesn't exist. |
| if (!m_on_exit[bb->index].m_names) |
| m_on_exit[bb->index].m_names = BITMAP_ALLOC (&m_bitmaps); |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, " on-exit update "); |
| print_generic_expr (dump_file, name, TDF_SLIM); |
| fprintf (dump_file, " in BB%d : ",bb->index); |
| r.dump (dump_file); |
| fprintf (dump_file, "\n"); |
| } |
| |
| // If NAME already has a range, intersect them and done. |
| exit_range *ptr = m_on_exit[bb->index].find_ptr (name); |
| if (ptr) |
| { |
| tree type = TREE_TYPE (name); |
| value_range cur (r), name_range (type); |
| ptr->range->get_vrange (name_range, type); |
| // If no new info is added, just return. |
| if (!cur.intersect (name_range)) |
| return; |
| if (ptr->range->fits_p (cur)) |
| ptr->range->set_vrange (cur); |
| else |
| ptr->range = m_range_allocator->clone (cur); |
| ptr->stmt = s; |
| return; |
| } |
| |
| // Otherwise create a record. |
| bitmap_set_bit (m_on_exit[bb->index].m_names, SSA_NAME_VERSION (name)); |
| ptr = (exit_range *)obstack_alloc (&m_list_obstack, sizeof (exit_range)); |
| ptr->range = m_range_allocator->clone (r); |
| ptr->name = name; |
| ptr->stmt = s; |
| ptr->next = m_on_exit[bb->index].head; |
| m_on_exit[bb->index].head = ptr; |
| } |
| |
| // Add a non-zero inferred range for NAME at stmt S. |
| |
| void |
| infer_range_manager::add_nonzero (tree name, gimple *s) |
| { |
| add_range (name, s, get_nonzero (name)); |
| } |
| |
| // Follow immediate use chains and find all inferred ranges for NAME. |
| |
| void |
| infer_range_manager::register_all_uses (tree name) |
| { |
| gcc_checking_assert (m_seen); |
| |
| // Check if we've already processed this name. |
| unsigned v = SSA_NAME_VERSION (name); |
| if (bitmap_bit_p (m_seen, v)) |
| return; |
| bitmap_set_bit (m_seen, v); |
| |
| use_operand_p use_p; |
| imm_use_iterator iter; |
| |
| // Loop over each immediate use and see if it has an inferred range. |
| FOR_EACH_IMM_USE_FAST (use_p, iter, name) |
| { |
| gimple *s = USE_STMT (use_p); |
| gimple_infer_range infer (s, m_query); |
| for (unsigned x = 0; x < infer.num (); x++) |
| { |
| if (name == infer.name (x)) |
| add_range (name, s, infer.range (x)); |
| } |
| } |
| } |