mirror of
https://forge.sourceware.org/marek/gcc.git
synced 2026-02-21 19:35:36 -05:00
524 lines
15 KiB
C++
524 lines
15 KiB
C++
/* Gimple range inference implementation.
|
|
Copyright (C) 2022-2026 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));
|
|
}
|
|
}
|
|
}
|