
implement very basic propapgation of return value ranges from VRP pass. This helps std::vector's push_back since we work out value range of allocated block. This propagates only within single translation unit. I hoped we will also do the propagation at WPA stage, but that needs more work on ipa-cp side. I also added code auto-detecting return_nonnull and corresponding -Wsuggest-attribute. gcc/ChangeLog: * cgraph.cc (add_detected_attribute_1): New function. (cgraph_node::add_detected_attribute): Likewise. * cgraph.h (cgraph_node::add_detected_attribute): Declare. * common.opt: Add -Wsuggest-attribute=returns_nonnull. * doc/invoke.texi: Document new flag. * gimple-range-fold.cc (fold_using_range::range_of_call): Use known reutrn value ranges. * ipa-prop.cc (struct ipa_return_value_summary): New type. (class ipa_return_value_sum_t): New type. (ipa_return_value_sum): New summary. (ipa_record_return_value_range): New function. (ipa_return_value_range): New function. * ipa-prop.h (ipa_return_value_range): Declare. (ipa_record_return_value_range): Declare. * ipa-pure-const.cc (warn_function_returns_nonnull): New funcion. * ipa-utils.h (warn_function_returns_nonnull): Declare. * symbol-summary.h: Fix comment. * tree-vrp.cc (execute_ranger_vrp): Record return values. gcc/testsuite/ChangeLog: * g++.dg/ipa/devirt-2.C: Add noipa attribute to prevent ipa-vrp. * g++.dg/ipa/devirt-7.C: Disable ipa-vrp. * g++.dg/ipa/ipa-icf-2.C: Disable ipa-vrp. * g++.dg/ipa/ipa-icf-3.C: Disable ipa-vrp. * g++.dg/ipa/ivinline-1.C: Disable ipa-vrp. * g++.dg/ipa/ivinline-3.C: Disable ipa-vrp. * g++.dg/ipa/ivinline-5.C: Disable ipa-vrp. * g++.dg/ipa/ivinline-8.C: Disable ipa-vrp. * g++.dg/ipa/nothrow-1.C: Disable ipa-vrp. * g++.dg/ipa/pure-const-1.C: Disable ipa-vrp. * g++.dg/ipa/pure-const-2.C: Disable ipa-vrp. * g++.dg/lto/inline-crossmodule-1_0.C: Disable ipa-vrp. * gcc.c-torture/compile/pr106433.c: Add noipa attribute to prevent ipa-vrp. * gcc.c-torture/execute/frame-address.c: Likewise. * gcc.dg/vla-1.c: Add noipa attribute to prevent ipa-vrp. * gcc.dg/ipa/fopt-info-inline-1.c: Disable ipa-vrp. * gcc.dg/ipa/ipa-icf-25.c: Disable ipa-vrp. * gcc.dg/ipa/ipa-icf-38.c: Disable ipa-vrp. * gcc.dg/ipa/pure-const-1.c: Disable ipa-vrp. * gcc.dg/ipa/remref-0.c: Add noipa attribute to prevent ipa-vrp. * gcc.dg/tree-prof/time-profiler-1.c: Disable ipa-vrp. * gcc.dg/tree-prof/time-profiler-2.c: Disable ipa-vrp. * gcc.dg/tree-ssa/pr110269.c: Disable ipa-vrp. * gcc.dg/tree-ssa/pr20701.c: Disable ipa-vrp. * gcc.dg/tree-ssa/vrp05.c: Disable ipa-vrp. * gcc.dg/tree-ssa/return-value-range-1.c: New test.
1402 lines
41 KiB
C++
1402 lines
41 KiB
C++
/* Support routines for Value Range Propagation (VRP).
|
|
Copyright (C) 2005-2023 Free Software Foundation, Inc.
|
|
Contributed by Diego Novillo <dnovillo@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 "sbitmap.h"
|
|
#include "options.h"
|
|
#include "dominance.h"
|
|
#include "function.h"
|
|
#include "cfg.h"
|
|
#include "tree.h"
|
|
#include "gimple.h"
|
|
#include "tree-pass.h"
|
|
#include "ssa.h"
|
|
#include "gimple-pretty-print.h"
|
|
#include "fold-const.h"
|
|
#include "cfganal.h"
|
|
#include "gimple-iterator.h"
|
|
#include "tree-cfg.h"
|
|
#include "tree-ssa-loop-manip.h"
|
|
#include "tree-ssa-loop-niter.h"
|
|
#include "tree-into-ssa.h"
|
|
#include "cfgloop.h"
|
|
#include "tree-scalar-evolution.h"
|
|
#include "tree-ssa-propagate.h"
|
|
#include "domwalk.h"
|
|
#include "vr-values.h"
|
|
#include "gimple-array-bounds.h"
|
|
#include "gimple-range.h"
|
|
#include "gimple-range-path.h"
|
|
#include "value-pointer-equiv.h"
|
|
#include "gimple-fold.h"
|
|
#include "tree-dfa.h"
|
|
#include "tree-ssa-dce.h"
|
|
#include "alloc-pool.h"
|
|
#include "cgraph.h"
|
|
#include "symbol-summary.h"
|
|
#include "ipa-utils.h"
|
|
#include "ipa-prop.h"
|
|
#include "attribs.h"
|
|
|
|
// This class is utilized by VRP and ranger to remove __builtin_unreachable
|
|
// calls, and reflect any resulting global ranges.
|
|
//
|
|
// maybe_register() is called on condition statements , and if that
|
|
// matches the pattern of one branch being a builtin_unreachable, either check
|
|
// for early removal or register the resulting executable edge in a list.
|
|
//
|
|
// During early/non-final processing, we check to see if ALL exports from the
|
|
// block can be safely updated with a new global value. If they can, then
|
|
// we rewrite the condition and update those values immediately. Otherwise
|
|
// the unreachable condition is left in the IL until the final pass.
|
|
//
|
|
// During final processing, after all blocks have been registered,
|
|
// remove_and_update_globals() will
|
|
// - check all exports from registered blocks
|
|
// - ensure the cache entry of each export is set with the appropriate range
|
|
// - rewrite the conditions to take the executable edge
|
|
// - perform DCE on any feeding instructions to those rewritten conditions
|
|
//
|
|
// Then each of the immediate use chain of each export is walked, and a new
|
|
// global range created by unioning the ranges at all remaining use locations.
|
|
|
|
class remove_unreachable {
|
|
public:
|
|
remove_unreachable (gimple_ranger &r, bool all) : m_ranger (r), final_p (all)
|
|
{ m_list.create (30); }
|
|
~remove_unreachable () { m_list.release (); }
|
|
void handle_early (gimple *s, edge e);
|
|
void maybe_register (gimple *s);
|
|
bool remove_and_update_globals ();
|
|
vec<std::pair<int, int> > m_list;
|
|
gimple_ranger &m_ranger;
|
|
bool final_p;
|
|
};
|
|
|
|
// Check if block BB has a __builtin_unreachable () call on one arm, and
|
|
// register the executable edge if so.
|
|
|
|
void
|
|
remove_unreachable::maybe_register (gimple *s)
|
|
{
|
|
gcc_checking_assert (gimple_code (s) == GIMPLE_COND);
|
|
basic_block bb = gimple_bb (s);
|
|
|
|
edge e0 = EDGE_SUCC (bb, 0);
|
|
basic_block bb0 = e0->dest;
|
|
bool un0 = EDGE_COUNT (bb0->succs) == 0
|
|
&& gimple_seq_unreachable_p (bb_seq (bb0));
|
|
edge e1 = EDGE_SUCC (bb, 1);
|
|
basic_block bb1 = e1->dest;
|
|
bool un1 = EDGE_COUNT (bb1->succs) == 0
|
|
&& gimple_seq_unreachable_p (bb_seq (bb1));
|
|
|
|
// If the 2 blocks are not different, ignore.
|
|
if (un0 == un1)
|
|
return;
|
|
|
|
// Constant expressions are ignored.
|
|
if (TREE_CODE (gimple_cond_lhs (s)) != SSA_NAME
|
|
&& TREE_CODE (gimple_cond_rhs (s)) != SSA_NAME)
|
|
return;
|
|
|
|
edge e = un0 ? e1 : e0;
|
|
|
|
if (!final_p)
|
|
handle_early (s, e);
|
|
else
|
|
m_list.safe_push (std::make_pair (e->src->index, e->dest->index));
|
|
}
|
|
|
|
// Return true if all uses of NAME are dominated by by block BB. 1 use
|
|
// is allowed in block BB, This is one we hope to remove.
|
|
// ie
|
|
// _2 = _1 & 7;
|
|
// if (_2 != 0)
|
|
// goto <bb 3>; [0.00%]
|
|
// Any additional use of _1 or _2 in this block invalidates early replacement.
|
|
|
|
static bool
|
|
fully_replaceable (tree name, basic_block bb)
|
|
{
|
|
use_operand_p use_p;
|
|
imm_use_iterator iter;
|
|
bool saw_in_bb = false;
|
|
|
|
// If a name loads from memory, we may lose information used in
|
|
// commoning opportunities later. See tree-ssa/ssa-pre-34.c.
|
|
gimple *def_stmt = SSA_NAME_DEF_STMT (name);
|
|
if (gimple_vuse (def_stmt))
|
|
return false;
|
|
|
|
FOR_EACH_IMM_USE_FAST (use_p, iter, name)
|
|
{
|
|
gimple *use_stmt = USE_STMT (use_p);
|
|
// Ignore debug stmts and the branch.
|
|
if (is_gimple_debug (use_stmt))
|
|
continue;
|
|
basic_block use_bb = gimple_bb (use_stmt);
|
|
// Only one use in the block allowed to avoid complicated cases.
|
|
if (use_bb == bb)
|
|
{
|
|
if (saw_in_bb)
|
|
return false;
|
|
else
|
|
saw_in_bb = true;
|
|
}
|
|
else if (!dominated_by_p (CDI_DOMINATORS, use_bb, bb))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// This routine is called to check builtin_unreachable calls during any
|
|
// time before final removal. The only way we can be sure it does not
|
|
// provide any additional information is to expect that we can update the
|
|
// global values of all exports from a block. This means the branch
|
|
// to the unreachable call must dominate all uses of those ssa-names, with
|
|
// the exception that there can be a single use in the block containing
|
|
// the branch. IF the name used in the branch is defined in the block, it may
|
|
// contain the name of something else that will be an export. And likewise
|
|
// that may also use another name that is an export etc.
|
|
//
|
|
// As long as there is only a single use, we can be sure that there are no other
|
|
// side effects (like being passed to a call, or stored to a global, etc.
|
|
// This means we will miss cases where there are 2 or more uses that have
|
|
// no interveneing statements that may had side effects, but it catches most
|
|
// of the caes we care about, and prevents expensive in depth analysis.
|
|
//
|
|
// Ranger will still reflect the proper ranges at other places in these missed
|
|
// cases, we simply will not remove/set globals early.
|
|
|
|
void
|
|
remove_unreachable::handle_early (gimple *s, edge e)
|
|
{
|
|
bool lhs_p = TREE_CODE (gimple_cond_lhs (s)) == SSA_NAME;
|
|
bool rhs_p = TREE_CODE (gimple_cond_rhs (s)) == SSA_NAME;
|
|
// Do not remove __builtin_unreachable if it confers a relation, or
|
|
// that relation may be lost in subsequent passes.
|
|
if (lhs_p && rhs_p)
|
|
return;
|
|
// Do not remove addresses early. ie if (x == &y)
|
|
if (lhs_p && TREE_CODE (gimple_cond_rhs (s)) == ADDR_EXPR)
|
|
return;
|
|
|
|
gcc_checking_assert (gimple_outgoing_range_stmt_p (e->src) == s);
|
|
gcc_checking_assert (!final_p);
|
|
|
|
// Check if every export use is dominated by this branch.
|
|
tree name;
|
|
FOR_EACH_GORI_EXPORT_NAME (m_ranger.gori (), e->src, name)
|
|
{
|
|
if (!fully_replaceable (name, e->src))
|
|
return;
|
|
}
|
|
|
|
// Set the global value for each.
|
|
FOR_EACH_GORI_EXPORT_NAME (m_ranger.gori (), e->src, name)
|
|
{
|
|
Value_Range r (TREE_TYPE (name));
|
|
m_ranger.range_on_entry (r, e->dest, name);
|
|
// Nothing at this late stage we can do if the write fails.
|
|
if (!set_range_info (name, r))
|
|
continue;
|
|
if (dump_file)
|
|
{
|
|
fprintf (dump_file, "Global Exported (via early unreachable): ");
|
|
print_generic_expr (dump_file, name, TDF_SLIM);
|
|
fprintf (dump_file, " = ");
|
|
gimple_range_global (r, name);
|
|
r.dump (dump_file);
|
|
fputc ('\n', dump_file);
|
|
}
|
|
}
|
|
|
|
tree ssa = lhs_p ? gimple_cond_lhs (s) : gimple_cond_rhs (s);
|
|
|
|
// Rewrite the condition.
|
|
if (e->flags & EDGE_TRUE_VALUE)
|
|
gimple_cond_make_true (as_a<gcond *> (s));
|
|
else
|
|
gimple_cond_make_false (as_a<gcond *> (s));
|
|
update_stmt (s);
|
|
|
|
// If the name on S is defined in this block, see if there is DCE work to do.
|
|
if (gimple_bb (SSA_NAME_DEF_STMT (ssa)) == e->src)
|
|
{
|
|
auto_bitmap dce;
|
|
bitmap_set_bit (dce, SSA_NAME_VERSION (ssa));
|
|
simple_dce_from_worklist (dce);
|
|
}
|
|
}
|
|
|
|
|
|
// Process the edges in the list, change the conditions and removing any
|
|
// dead code feeding those conditions. Calculate the range of any
|
|
// names that may have been exported from those blocks, and determine if
|
|
// there is any updates to their global ranges..
|
|
// Return true if any builtin_unreachables/globals eliminated/updated.
|
|
|
|
bool
|
|
remove_unreachable::remove_and_update_globals ()
|
|
{
|
|
if (m_list.length () == 0)
|
|
return false;
|
|
|
|
// Ensure the cache in SCEV has been cleared before processing
|
|
// globals to be removed.
|
|
scev_reset ();
|
|
|
|
bool change = false;
|
|
tree name;
|
|
unsigned i;
|
|
bitmap_iterator bi;
|
|
auto_bitmap all_exports;
|
|
for (i = 0; i < m_list.length (); i++)
|
|
{
|
|
auto eb = m_list[i];
|
|
basic_block src = BASIC_BLOCK_FOR_FN (cfun, eb.first);
|
|
basic_block dest = BASIC_BLOCK_FOR_FN (cfun, eb.second);
|
|
if (!src || !dest)
|
|
continue;
|
|
edge e = find_edge (src, dest);
|
|
gimple *s = gimple_outgoing_range_stmt_p (e->src);
|
|
gcc_checking_assert (gimple_code (s) == GIMPLE_COND);
|
|
|
|
bool dominate_exit_p = true;
|
|
FOR_EACH_GORI_EXPORT_NAME (m_ranger.gori (), e->src, name)
|
|
{
|
|
// Ensure the cache is set for NAME in the succ block.
|
|
Value_Range r(TREE_TYPE (name));
|
|
Value_Range ex(TREE_TYPE (name));
|
|
m_ranger.range_on_entry (r, e->dest, name);
|
|
m_ranger.range_on_entry (ex, EXIT_BLOCK_PTR_FOR_FN (cfun), name);
|
|
// If the range produced by this __builtin_unreachacble expression
|
|
// is not fully reflected in the range at exit, then it does not
|
|
// dominate the exit of the function.
|
|
if (ex.intersect (r))
|
|
dominate_exit_p = false;
|
|
}
|
|
|
|
// If the exit is dominated, add to the export list. Otherwise if this
|
|
// isn't the final VRP pass, leave the call in the IL.
|
|
if (dominate_exit_p)
|
|
bitmap_ior_into (all_exports, m_ranger.gori ().exports (e->src));
|
|
else if (!final_p)
|
|
continue;
|
|
|
|
change = true;
|
|
// Rewrite the condition.
|
|
if (e->flags & EDGE_TRUE_VALUE)
|
|
gimple_cond_make_true (as_a<gcond *> (s));
|
|
else
|
|
gimple_cond_make_false (as_a<gcond *> (s));
|
|
update_stmt (s);
|
|
}
|
|
|
|
if (bitmap_empty_p (all_exports))
|
|
return false;
|
|
// Invoke DCE on all exported names to eliminate dead feeding defs.
|
|
auto_bitmap dce;
|
|
bitmap_copy (dce, all_exports);
|
|
// Don't attempt to DCE parameters.
|
|
EXECUTE_IF_SET_IN_BITMAP (all_exports, 0, i, bi)
|
|
if (!ssa_name (i) || SSA_NAME_IS_DEFAULT_DEF (ssa_name (i)))
|
|
bitmap_clear_bit (dce, i);
|
|
simple_dce_from_worklist (dce);
|
|
|
|
// Loop over all uses of each name and find maximal range. This is the
|
|
// new global range.
|
|
use_operand_p use_p;
|
|
imm_use_iterator iter;
|
|
EXECUTE_IF_SET_IN_BITMAP (all_exports, 0, i, bi)
|
|
{
|
|
name = ssa_name (i);
|
|
if (!name || SSA_NAME_IN_FREE_LIST (name))
|
|
continue;
|
|
Value_Range r (TREE_TYPE (name));
|
|
Value_Range exp_range (TREE_TYPE (name));
|
|
r.set_undefined ();
|
|
FOR_EACH_IMM_USE_FAST (use_p, iter, name)
|
|
{
|
|
gimple *use_stmt = USE_STMT (use_p);
|
|
if (is_gimple_debug (use_stmt))
|
|
continue;
|
|
if (!m_ranger.range_of_expr (exp_range, name, use_stmt))
|
|
exp_range.set_varying (TREE_TYPE (name));
|
|
r.union_ (exp_range);
|
|
if (r.varying_p ())
|
|
break;
|
|
}
|
|
// Include the on-exit range to ensure non-dominated unreachables
|
|
// don't incorrectly impact the global range.
|
|
m_ranger.range_on_entry (exp_range, EXIT_BLOCK_PTR_FOR_FN (cfun), name);
|
|
r.union_ (exp_range);
|
|
if (r.varying_p () || r.undefined_p ())
|
|
continue;
|
|
if (!set_range_info (name, r))
|
|
continue;
|
|
change = true;
|
|
if (dump_file)
|
|
{
|
|
fprintf (dump_file, "Global Exported (via unreachable): ");
|
|
print_generic_expr (dump_file, name, TDF_SLIM);
|
|
fprintf (dump_file, " = ");
|
|
gimple_range_global (r, name);
|
|
r.dump (dump_file);
|
|
fputc ('\n', dump_file);
|
|
}
|
|
}
|
|
return change;
|
|
}
|
|
|
|
/* VR_TYPE describes a range with minimum value *MIN and maximum
|
|
value *MAX. Restrict the range to the set of values that have
|
|
no bits set outside NONZERO_BITS. Update *MIN and *MAX and
|
|
return the new range type.
|
|
|
|
SGN gives the sign of the values described by the range. */
|
|
|
|
enum value_range_kind
|
|
intersect_range_with_nonzero_bits (enum value_range_kind vr_type,
|
|
wide_int *min, wide_int *max,
|
|
const wide_int &nonzero_bits,
|
|
signop sgn)
|
|
{
|
|
if (vr_type == VR_ANTI_RANGE)
|
|
{
|
|
/* The VR_ANTI_RANGE is equivalent to the union of the ranges
|
|
A: [-INF, *MIN) and B: (*MAX, +INF]. First use NONZERO_BITS
|
|
to create an inclusive upper bound for A and an inclusive lower
|
|
bound for B. */
|
|
wide_int a_max = wi::round_down_for_mask (*min - 1, nonzero_bits);
|
|
wide_int b_min = wi::round_up_for_mask (*max + 1, nonzero_bits);
|
|
|
|
/* If the calculation of A_MAX wrapped, A is effectively empty
|
|
and A_MAX is the highest value that satisfies NONZERO_BITS.
|
|
Likewise if the calculation of B_MIN wrapped, B is effectively
|
|
empty and B_MIN is the lowest value that satisfies NONZERO_BITS. */
|
|
bool a_empty = wi::ge_p (a_max, *min, sgn);
|
|
bool b_empty = wi::le_p (b_min, *max, sgn);
|
|
|
|
/* If both A and B are empty, there are no valid values. */
|
|
if (a_empty && b_empty)
|
|
return VR_UNDEFINED;
|
|
|
|
/* If exactly one of A or B is empty, return a VR_RANGE for the
|
|
other one. */
|
|
if (a_empty || b_empty)
|
|
{
|
|
*min = b_min;
|
|
*max = a_max;
|
|
gcc_checking_assert (wi::le_p (*min, *max, sgn));
|
|
return VR_RANGE;
|
|
}
|
|
|
|
/* Update the VR_ANTI_RANGE bounds. */
|
|
*min = a_max + 1;
|
|
*max = b_min - 1;
|
|
gcc_checking_assert (wi::le_p (*min, *max, sgn));
|
|
|
|
/* Now check whether the excluded range includes any values that
|
|
satisfy NONZERO_BITS. If not, switch to a full VR_RANGE. */
|
|
if (wi::round_up_for_mask (*min, nonzero_bits) == b_min)
|
|
{
|
|
unsigned int precision = min->get_precision ();
|
|
*min = wi::min_value (precision, sgn);
|
|
*max = wi::max_value (precision, sgn);
|
|
vr_type = VR_RANGE;
|
|
}
|
|
}
|
|
if (vr_type == VR_RANGE || vr_type == VR_VARYING)
|
|
{
|
|
*max = wi::round_down_for_mask (*max, nonzero_bits);
|
|
|
|
/* Check that the range contains at least one valid value. */
|
|
if (wi::gt_p (*min, *max, sgn))
|
|
return VR_UNDEFINED;
|
|
|
|
*min = wi::round_up_for_mask (*min, nonzero_bits);
|
|
gcc_checking_assert (wi::le_p (*min, *max, sgn));
|
|
}
|
|
return vr_type;
|
|
}
|
|
|
|
/* Return the single symbol (an SSA_NAME) contained in T if any, or NULL_TREE
|
|
otherwise. We only handle additive operations and set NEG to true if the
|
|
symbol is negated and INV to the invariant part, if any. */
|
|
|
|
static tree
|
|
get_single_symbol (tree t, bool *neg, tree *inv)
|
|
{
|
|
bool neg_;
|
|
tree inv_;
|
|
|
|
*inv = NULL_TREE;
|
|
*neg = false;
|
|
|
|
if (TREE_CODE (t) == PLUS_EXPR
|
|
|| TREE_CODE (t) == POINTER_PLUS_EXPR
|
|
|| TREE_CODE (t) == MINUS_EXPR)
|
|
{
|
|
if (is_gimple_min_invariant (TREE_OPERAND (t, 0)))
|
|
{
|
|
neg_ = (TREE_CODE (t) == MINUS_EXPR);
|
|
inv_ = TREE_OPERAND (t, 0);
|
|
t = TREE_OPERAND (t, 1);
|
|
}
|
|
else if (is_gimple_min_invariant (TREE_OPERAND (t, 1)))
|
|
{
|
|
neg_ = false;
|
|
inv_ = TREE_OPERAND (t, 1);
|
|
t = TREE_OPERAND (t, 0);
|
|
}
|
|
else
|
|
return NULL_TREE;
|
|
}
|
|
else
|
|
{
|
|
neg_ = false;
|
|
inv_ = NULL_TREE;
|
|
}
|
|
|
|
if (TREE_CODE (t) == NEGATE_EXPR)
|
|
{
|
|
t = TREE_OPERAND (t, 0);
|
|
neg_ = !neg_;
|
|
}
|
|
|
|
if (TREE_CODE (t) != SSA_NAME)
|
|
return NULL_TREE;
|
|
|
|
if (inv_ && TREE_OVERFLOW_P (inv_))
|
|
inv_ = drop_tree_overflow (inv_);
|
|
|
|
*neg = neg_;
|
|
*inv = inv_;
|
|
return t;
|
|
}
|
|
|
|
/* Compare two values VAL1 and VAL2. Return
|
|
|
|
-2 if VAL1 and VAL2 cannot be compared at compile-time,
|
|
-1 if VAL1 < VAL2,
|
|
0 if VAL1 == VAL2,
|
|
+1 if VAL1 > VAL2, and
|
|
+2 if VAL1 != VAL2
|
|
|
|
This is similar to tree_int_cst_compare but supports pointer values
|
|
and values that cannot be compared at compile time.
|
|
|
|
If STRICT_OVERFLOW_P is not NULL, then set *STRICT_OVERFLOW_P to
|
|
true if the return value is only valid if we assume that signed
|
|
overflow is undefined. */
|
|
|
|
int
|
|
compare_values_warnv (tree val1, tree val2, bool *strict_overflow_p)
|
|
{
|
|
if (val1 == val2)
|
|
return 0;
|
|
|
|
/* Below we rely on the fact that VAL1 and VAL2 are both pointers or
|
|
both integers. */
|
|
gcc_assert (POINTER_TYPE_P (TREE_TYPE (val1))
|
|
== POINTER_TYPE_P (TREE_TYPE (val2)));
|
|
|
|
/* Convert the two values into the same type. This is needed because
|
|
sizetype causes sign extension even for unsigned types. */
|
|
if (!useless_type_conversion_p (TREE_TYPE (val1), TREE_TYPE (val2)))
|
|
val2 = fold_convert (TREE_TYPE (val1), val2);
|
|
|
|
const bool overflow_undefined
|
|
= INTEGRAL_TYPE_P (TREE_TYPE (val1))
|
|
&& TYPE_OVERFLOW_UNDEFINED (TREE_TYPE (val1));
|
|
tree inv1, inv2;
|
|
bool neg1, neg2;
|
|
tree sym1 = get_single_symbol (val1, &neg1, &inv1);
|
|
tree sym2 = get_single_symbol (val2, &neg2, &inv2);
|
|
|
|
/* If VAL1 and VAL2 are of the form '[-]NAME [+ CST]', return -1 or +1
|
|
accordingly. If VAL1 and VAL2 don't use the same name, return -2. */
|
|
if (sym1 && sym2)
|
|
{
|
|
/* Both values must use the same name with the same sign. */
|
|
if (sym1 != sym2 || neg1 != neg2)
|
|
return -2;
|
|
|
|
/* [-]NAME + CST == [-]NAME + CST. */
|
|
if (inv1 == inv2)
|
|
return 0;
|
|
|
|
/* If overflow is defined we cannot simplify more. */
|
|
if (!overflow_undefined)
|
|
return -2;
|
|
|
|
if (strict_overflow_p != NULL
|
|
/* Symbolic range building sets the no-warning bit to declare
|
|
that overflow doesn't happen. */
|
|
&& (!inv1 || !warning_suppressed_p (val1, OPT_Woverflow))
|
|
&& (!inv2 || !warning_suppressed_p (val2, OPT_Woverflow)))
|
|
*strict_overflow_p = true;
|
|
|
|
if (!inv1)
|
|
inv1 = build_int_cst (TREE_TYPE (val1), 0);
|
|
if (!inv2)
|
|
inv2 = build_int_cst (TREE_TYPE (val2), 0);
|
|
|
|
return wi::cmp (wi::to_wide (inv1), wi::to_wide (inv2),
|
|
TYPE_SIGN (TREE_TYPE (val1)));
|
|
}
|
|
|
|
const bool cst1 = is_gimple_min_invariant (val1);
|
|
const bool cst2 = is_gimple_min_invariant (val2);
|
|
|
|
/* If one is of the form '[-]NAME + CST' and the other is constant, then
|
|
it might be possible to say something depending on the constants. */
|
|
if ((sym1 && inv1 && cst2) || (sym2 && inv2 && cst1))
|
|
{
|
|
if (!overflow_undefined)
|
|
return -2;
|
|
|
|
if (strict_overflow_p != NULL
|
|
/* Symbolic range building sets the no-warning bit to declare
|
|
that overflow doesn't happen. */
|
|
&& (!sym1 || !warning_suppressed_p (val1, OPT_Woverflow))
|
|
&& (!sym2 || !warning_suppressed_p (val2, OPT_Woverflow)))
|
|
*strict_overflow_p = true;
|
|
|
|
const signop sgn = TYPE_SIGN (TREE_TYPE (val1));
|
|
tree cst = cst1 ? val1 : val2;
|
|
tree inv = cst1 ? inv2 : inv1;
|
|
|
|
/* Compute the difference between the constants. If it overflows or
|
|
underflows, this means that we can trivially compare the NAME with
|
|
it and, consequently, the two values with each other. */
|
|
wide_int diff = wi::to_wide (cst) - wi::to_wide (inv);
|
|
if (wi::cmp (0, wi::to_wide (inv), sgn)
|
|
!= wi::cmp (diff, wi::to_wide (cst), sgn))
|
|
{
|
|
const int res = wi::cmp (wi::to_wide (cst), wi::to_wide (inv), sgn);
|
|
return cst1 ? res : -res;
|
|
}
|
|
|
|
return -2;
|
|
}
|
|
|
|
/* We cannot say anything more for non-constants. */
|
|
if (!cst1 || !cst2)
|
|
return -2;
|
|
|
|
if (!POINTER_TYPE_P (TREE_TYPE (val1)))
|
|
{
|
|
/* We cannot compare overflowed values. */
|
|
if (TREE_OVERFLOW (val1) || TREE_OVERFLOW (val2))
|
|
return -2;
|
|
|
|
if (TREE_CODE (val1) == INTEGER_CST
|
|
&& TREE_CODE (val2) == INTEGER_CST)
|
|
return tree_int_cst_compare (val1, val2);
|
|
|
|
if (poly_int_tree_p (val1) && poly_int_tree_p (val2))
|
|
{
|
|
if (known_eq (wi::to_poly_widest (val1),
|
|
wi::to_poly_widest (val2)))
|
|
return 0;
|
|
if (known_lt (wi::to_poly_widest (val1),
|
|
wi::to_poly_widest (val2)))
|
|
return -1;
|
|
if (known_gt (wi::to_poly_widest (val1),
|
|
wi::to_poly_widest (val2)))
|
|
return 1;
|
|
}
|
|
|
|
return -2;
|
|
}
|
|
else
|
|
{
|
|
if (TREE_CODE (val1) == INTEGER_CST && TREE_CODE (val2) == INTEGER_CST)
|
|
{
|
|
/* We cannot compare overflowed values. */
|
|
if (TREE_OVERFLOW (val1) || TREE_OVERFLOW (val2))
|
|
return -2;
|
|
|
|
return tree_int_cst_compare (val1, val2);
|
|
}
|
|
|
|
/* First see if VAL1 and VAL2 are not the same. */
|
|
if (operand_equal_p (val1, val2, 0))
|
|
return 0;
|
|
|
|
fold_defer_overflow_warnings ();
|
|
|
|
/* If VAL1 is a lower address than VAL2, return -1. */
|
|
tree t = fold_binary_to_constant (LT_EXPR, boolean_type_node, val1, val2);
|
|
if (t && integer_onep (t))
|
|
{
|
|
fold_undefer_and_ignore_overflow_warnings ();
|
|
return -1;
|
|
}
|
|
|
|
/* If VAL1 is a higher address than VAL2, return +1. */
|
|
t = fold_binary_to_constant (LT_EXPR, boolean_type_node, val2, val1);
|
|
if (t && integer_onep (t))
|
|
{
|
|
fold_undefer_and_ignore_overflow_warnings ();
|
|
return 1;
|
|
}
|
|
|
|
/* If VAL1 is different than VAL2, return +2. */
|
|
t = fold_binary_to_constant (NE_EXPR, boolean_type_node, val1, val2);
|
|
fold_undefer_and_ignore_overflow_warnings ();
|
|
if (t && integer_onep (t))
|
|
return 2;
|
|
|
|
return -2;
|
|
}
|
|
}
|
|
|
|
/* Compare values like compare_values_warnv. */
|
|
|
|
int
|
|
compare_values (tree val1, tree val2)
|
|
{
|
|
bool sop;
|
|
return compare_values_warnv (val1, val2, &sop);
|
|
}
|
|
|
|
/* Helper for overflow_comparison_p
|
|
|
|
OP0 CODE OP1 is a comparison. Examine the comparison and potentially
|
|
OP1's defining statement to see if it ultimately has the form
|
|
OP0 CODE (OP0 PLUS INTEGER_CST)
|
|
|
|
If so, return TRUE indicating this is an overflow test and store into
|
|
*NEW_CST an updated constant that can be used in a narrowed range test.
|
|
|
|
REVERSED indicates if the comparison was originally:
|
|
|
|
OP1 CODE' OP0.
|
|
|
|
This affects how we build the updated constant. */
|
|
|
|
static bool
|
|
overflow_comparison_p_1 (enum tree_code code, tree op0, tree op1,
|
|
bool reversed, tree *new_cst)
|
|
{
|
|
/* See if this is a relational operation between two SSA_NAMES with
|
|
unsigned, overflow wrapping values. If so, check it more deeply. */
|
|
if ((code == LT_EXPR || code == LE_EXPR
|
|
|| code == GE_EXPR || code == GT_EXPR)
|
|
&& TREE_CODE (op0) == SSA_NAME
|
|
&& TREE_CODE (op1) == SSA_NAME
|
|
&& INTEGRAL_TYPE_P (TREE_TYPE (op0))
|
|
&& TYPE_UNSIGNED (TREE_TYPE (op0))
|
|
&& TYPE_OVERFLOW_WRAPS (TREE_TYPE (op0)))
|
|
{
|
|
gimple *op1_def = SSA_NAME_DEF_STMT (op1);
|
|
|
|
/* Now look at the defining statement of OP1 to see if it adds
|
|
or subtracts a nonzero constant from another operand. */
|
|
if (op1_def
|
|
&& is_gimple_assign (op1_def)
|
|
&& gimple_assign_rhs_code (op1_def) == PLUS_EXPR
|
|
&& TREE_CODE (gimple_assign_rhs2 (op1_def)) == INTEGER_CST
|
|
&& !integer_zerop (gimple_assign_rhs2 (op1_def)))
|
|
{
|
|
tree target = gimple_assign_rhs1 (op1_def);
|
|
|
|
/* If we did not find our target SSA_NAME, then this is not
|
|
an overflow test. */
|
|
if (op0 != target)
|
|
return false;
|
|
|
|
tree type = TREE_TYPE (op0);
|
|
wide_int max = wi::max_value (TYPE_PRECISION (type), UNSIGNED);
|
|
tree inc = gimple_assign_rhs2 (op1_def);
|
|
if (reversed)
|
|
*new_cst = wide_int_to_tree (type, max + wi::to_wide (inc));
|
|
else
|
|
*new_cst = wide_int_to_tree (type, max - wi::to_wide (inc));
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* OP0 CODE OP1 is a comparison. Examine the comparison and potentially
|
|
OP1's defining statement to see if it ultimately has the form
|
|
OP0 CODE (OP0 PLUS INTEGER_CST)
|
|
|
|
If so, return TRUE indicating this is an overflow test and store into
|
|
*NEW_CST an updated constant that can be used in a narrowed range test.
|
|
|
|
These statements are left as-is in the IL to facilitate discovery of
|
|
{ADD,SUB}_OVERFLOW sequences later in the optimizer pipeline. But
|
|
the alternate range representation is often useful within VRP. */
|
|
|
|
bool
|
|
overflow_comparison_p (tree_code code, tree name, tree val, tree *new_cst)
|
|
{
|
|
if (overflow_comparison_p_1 (code, name, val, false, new_cst))
|
|
return true;
|
|
return overflow_comparison_p_1 (swap_tree_comparison (code), val, name,
|
|
true, new_cst);
|
|
}
|
|
|
|
/* Searches the case label vector VEC for the index *IDX of the CASE_LABEL
|
|
that includes the value VAL. The search is restricted to the range
|
|
[START_IDX, n - 1] where n is the size of VEC.
|
|
|
|
If there is a CASE_LABEL for VAL, its index is placed in IDX and true is
|
|
returned.
|
|
|
|
If there is no CASE_LABEL for VAL and there is one that is larger than VAL,
|
|
it is placed in IDX and false is returned.
|
|
|
|
If VAL is larger than any CASE_LABEL, n is placed on IDX and false is
|
|
returned. */
|
|
|
|
bool
|
|
find_case_label_index (gswitch *stmt, size_t start_idx, tree val, size_t *idx)
|
|
{
|
|
size_t n = gimple_switch_num_labels (stmt);
|
|
size_t low, high;
|
|
|
|
/* Find case label for minimum of the value range or the next one.
|
|
At each iteration we are searching in [low, high - 1]. */
|
|
|
|
for (low = start_idx, high = n; high != low; )
|
|
{
|
|
tree t;
|
|
int cmp;
|
|
/* Note that i != high, so we never ask for n. */
|
|
size_t i = (high + low) / 2;
|
|
t = gimple_switch_label (stmt, i);
|
|
|
|
/* Cache the result of comparing CASE_LOW and val. */
|
|
cmp = tree_int_cst_compare (CASE_LOW (t), val);
|
|
|
|
if (cmp == 0)
|
|
{
|
|
/* Ranges cannot be empty. */
|
|
*idx = i;
|
|
return true;
|
|
}
|
|
else if (cmp > 0)
|
|
high = i;
|
|
else
|
|
{
|
|
low = i + 1;
|
|
if (CASE_HIGH (t) != NULL
|
|
&& tree_int_cst_compare (CASE_HIGH (t), val) >= 0)
|
|
{
|
|
*idx = i;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
*idx = high;
|
|
return false;
|
|
}
|
|
|
|
/* Searches the case label vector VEC for the range of CASE_LABELs that is used
|
|
for values between MIN and MAX. The first index is placed in MIN_IDX. The
|
|
last index is placed in MAX_IDX. If the range of CASE_LABELs is empty
|
|
then MAX_IDX < MIN_IDX.
|
|
Returns true if the default label is not needed. */
|
|
|
|
bool
|
|
find_case_label_range (gswitch *stmt, tree min, tree max, size_t *min_idx,
|
|
size_t *max_idx)
|
|
{
|
|
size_t i, j;
|
|
bool min_take_default = !find_case_label_index (stmt, 1, min, &i);
|
|
bool max_take_default = !find_case_label_index (stmt, i, max, &j);
|
|
|
|
if (i == j
|
|
&& min_take_default
|
|
&& max_take_default)
|
|
{
|
|
/* Only the default case label reached.
|
|
Return an empty range. */
|
|
*min_idx = 1;
|
|
*max_idx = 0;
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
bool take_default = min_take_default || max_take_default;
|
|
tree low, high;
|
|
size_t k;
|
|
|
|
if (max_take_default)
|
|
j--;
|
|
|
|
/* If the case label range is continuous, we do not need
|
|
the default case label. Verify that. */
|
|
high = CASE_LOW (gimple_switch_label (stmt, i));
|
|
if (CASE_HIGH (gimple_switch_label (stmt, i)))
|
|
high = CASE_HIGH (gimple_switch_label (stmt, i));
|
|
for (k = i + 1; k <= j; ++k)
|
|
{
|
|
low = CASE_LOW (gimple_switch_label (stmt, k));
|
|
if (!integer_onep (int_const_binop (MINUS_EXPR, low, high)))
|
|
{
|
|
take_default = true;
|
|
break;
|
|
}
|
|
high = low;
|
|
if (CASE_HIGH (gimple_switch_label (stmt, k)))
|
|
high = CASE_HIGH (gimple_switch_label (stmt, k));
|
|
}
|
|
|
|
*min_idx = i;
|
|
*max_idx = j;
|
|
return !take_default;
|
|
}
|
|
}
|
|
|
|
/* Given a SWITCH_STMT, return the case label that encompasses the
|
|
known possible values for the switch operand. RANGE_OF_OP is a
|
|
range for the known values of the switch operand. */
|
|
|
|
tree
|
|
find_case_label_range (gswitch *switch_stmt, const irange *range_of_op)
|
|
{
|
|
if (range_of_op->undefined_p ()
|
|
|| range_of_op->varying_p ())
|
|
return NULL_TREE;
|
|
|
|
size_t i, j;
|
|
tree op = gimple_switch_index (switch_stmt);
|
|
tree type = TREE_TYPE (op);
|
|
tree tmin = wide_int_to_tree (type, range_of_op->lower_bound ());
|
|
tree tmax = wide_int_to_tree (type, range_of_op->upper_bound ());
|
|
find_case_label_range (switch_stmt, tmin, tmax, &i, &j);
|
|
if (i == j)
|
|
{
|
|
/* Look for exactly one label that encompasses the range of
|
|
the operand. */
|
|
tree label = gimple_switch_label (switch_stmt, i);
|
|
tree case_high
|
|
= CASE_HIGH (label) ? CASE_HIGH (label) : CASE_LOW (label);
|
|
wide_int wlow = wi::to_wide (CASE_LOW (label));
|
|
wide_int whigh = wi::to_wide (case_high);
|
|
int_range_max label_range (TREE_TYPE (case_high), wlow, whigh);
|
|
if (!types_compatible_p (label_range.type (), range_of_op->type ()))
|
|
range_cast (label_range, range_of_op->type ());
|
|
label_range.intersect (*range_of_op);
|
|
if (label_range == *range_of_op)
|
|
return label;
|
|
}
|
|
else if (i > j)
|
|
{
|
|
/* If there are no labels at all, take the default. */
|
|
return gimple_switch_label (switch_stmt, 0);
|
|
}
|
|
else
|
|
{
|
|
/* Otherwise, there are various labels that can encompass
|
|
the range of operand. In which case, see if the range of
|
|
the operand is entirely *outside* the bounds of all the
|
|
(non-default) case labels. If so, take the default. */
|
|
unsigned n = gimple_switch_num_labels (switch_stmt);
|
|
tree min_label = gimple_switch_label (switch_stmt, 1);
|
|
tree max_label = gimple_switch_label (switch_stmt, n - 1);
|
|
tree case_high = CASE_HIGH (max_label);
|
|
if (!case_high)
|
|
case_high = CASE_LOW (max_label);
|
|
int_range_max label_range (TREE_TYPE (CASE_LOW (min_label)),
|
|
wi::to_wide (CASE_LOW (min_label)),
|
|
wi::to_wide (case_high));
|
|
if (!types_compatible_p (label_range.type (), range_of_op->type ()))
|
|
range_cast (label_range, range_of_op->type ());
|
|
label_range.intersect (*range_of_op);
|
|
if (label_range.undefined_p ())
|
|
return gimple_switch_label (switch_stmt, 0);
|
|
}
|
|
return NULL_TREE;
|
|
}
|
|
|
|
struct case_info
|
|
{
|
|
tree expr;
|
|
basic_block bb;
|
|
};
|
|
|
|
// This is a ranger based folder which continues to use the dominator
|
|
// walk to access the substitute and fold machinery. Ranges are calculated
|
|
// on demand.
|
|
|
|
class rvrp_folder : public substitute_and_fold_engine
|
|
{
|
|
public:
|
|
|
|
rvrp_folder (gimple_ranger *r, bool all)
|
|
: substitute_and_fold_engine (),
|
|
m_unreachable (*r, all),
|
|
m_simplifier (r, r->non_executable_edge_flag)
|
|
{
|
|
m_ranger = r;
|
|
m_pta = new pointer_equiv_analyzer (m_ranger);
|
|
m_last_bb_stmt = NULL;
|
|
}
|
|
|
|
~rvrp_folder ()
|
|
{
|
|
delete m_pta;
|
|
}
|
|
|
|
tree value_of_expr (tree name, gimple *s = NULL) override
|
|
{
|
|
// Shortcircuit subst_and_fold callbacks for abnormal ssa_names.
|
|
if (TREE_CODE (name) == SSA_NAME && SSA_NAME_OCCURS_IN_ABNORMAL_PHI (name))
|
|
return NULL;
|
|
tree ret = m_ranger->value_of_expr (name, s);
|
|
if (!ret && supported_pointer_equiv_p (name))
|
|
ret = m_pta->get_equiv (name);
|
|
return ret;
|
|
}
|
|
|
|
tree value_on_edge (edge e, tree name) override
|
|
{
|
|
// Shortcircuit subst_and_fold callbacks for abnormal ssa_names.
|
|
if (TREE_CODE (name) == SSA_NAME && SSA_NAME_OCCURS_IN_ABNORMAL_PHI (name))
|
|
return NULL;
|
|
tree ret = m_ranger->value_on_edge (e, name);
|
|
if (!ret && supported_pointer_equiv_p (name))
|
|
ret = m_pta->get_equiv (name);
|
|
return ret;
|
|
}
|
|
|
|
tree value_of_stmt (gimple *s, tree name = NULL) override
|
|
{
|
|
// Shortcircuit subst_and_fold callbacks for abnormal ssa_names.
|
|
if (TREE_CODE (name) == SSA_NAME && SSA_NAME_OCCURS_IN_ABNORMAL_PHI (name))
|
|
return NULL;
|
|
return m_ranger->value_of_stmt (s, name);
|
|
}
|
|
|
|
void pre_fold_bb (basic_block bb) override
|
|
{
|
|
m_pta->enter (bb);
|
|
for (gphi_iterator gsi = gsi_start_phis (bb); !gsi_end_p (gsi);
|
|
gsi_next (&gsi))
|
|
m_ranger->register_inferred_ranges (gsi.phi ());
|
|
m_last_bb_stmt = last_nondebug_stmt (bb);
|
|
}
|
|
|
|
void post_fold_bb (basic_block bb) override
|
|
{
|
|
m_pta->leave (bb);
|
|
}
|
|
|
|
void pre_fold_stmt (gimple *stmt) override
|
|
{
|
|
m_pta->visit_stmt (stmt);
|
|
// If this is the last stmt and there are inferred ranges, reparse the
|
|
// block for transitive inferred ranges that occur earlier in the block.
|
|
if (stmt == m_last_bb_stmt)
|
|
{
|
|
m_ranger->register_transitive_inferred_ranges (gimple_bb (stmt));
|
|
// Also check for builtin_unreachable calls.
|
|
if (cfun->after_inlining && gimple_code (stmt) == GIMPLE_COND)
|
|
m_unreachable.maybe_register (stmt);
|
|
}
|
|
}
|
|
|
|
bool fold_stmt (gimple_stmt_iterator *gsi) override
|
|
{
|
|
bool ret = m_simplifier.simplify (gsi);
|
|
if (!ret)
|
|
ret = m_ranger->fold_stmt (gsi, follow_single_use_edges);
|
|
m_ranger->register_inferred_ranges (gsi_stmt (*gsi));
|
|
return ret;
|
|
}
|
|
|
|
remove_unreachable m_unreachable;
|
|
private:
|
|
DISABLE_COPY_AND_ASSIGN (rvrp_folder);
|
|
gimple_ranger *m_ranger;
|
|
simplify_using_ranges m_simplifier;
|
|
pointer_equiv_analyzer *m_pta;
|
|
gimple *m_last_bb_stmt;
|
|
};
|
|
|
|
/* Main entry point for a VRP pass using just ranger. This can be called
|
|
from anywhere to perform a VRP pass, including from EVRP. */
|
|
|
|
unsigned int
|
|
execute_ranger_vrp (struct function *fun, bool warn_array_bounds_p,
|
|
bool final_p)
|
|
{
|
|
loop_optimizer_init (LOOPS_NORMAL | LOOPS_HAVE_RECORDED_EXITS);
|
|
rewrite_into_loop_closed_ssa (NULL, TODO_update_ssa);
|
|
scev_initialize ();
|
|
calculate_dominance_info (CDI_DOMINATORS);
|
|
|
|
set_all_edges_as_executable (fun);
|
|
gimple_ranger *ranger = enable_ranger (fun, false);
|
|
rvrp_folder folder (ranger, final_p);
|
|
phi_analysis_initialize (ranger->const_query ());
|
|
folder.substitute_and_fold ();
|
|
// Remove tagged builtin-unreachable and maybe update globals.
|
|
folder.m_unreachable.remove_and_update_globals ();
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
ranger->dump (dump_file);
|
|
|
|
if ((warn_array_bounds || warn_strict_flex_arrays) && warn_array_bounds_p)
|
|
{
|
|
// Set all edges as executable, except those ranger says aren't.
|
|
int non_exec_flag = ranger->non_executable_edge_flag;
|
|
basic_block bb;
|
|
FOR_ALL_BB_FN (bb, fun)
|
|
{
|
|
edge_iterator ei;
|
|
edge e;
|
|
FOR_EACH_EDGE (e, ei, bb->succs)
|
|
if (e->flags & non_exec_flag)
|
|
e->flags &= ~EDGE_EXECUTABLE;
|
|
else
|
|
e->flags |= EDGE_EXECUTABLE;
|
|
}
|
|
scev_reset ();
|
|
array_bounds_checker array_checker (fun, ranger);
|
|
array_checker.check ();
|
|
}
|
|
|
|
|
|
if (Value_Range::supports_type_p (TREE_TYPE
|
|
(TREE_TYPE (current_function_decl)))
|
|
&& flag_ipa_vrp
|
|
&& !lookup_attribute ("noipa", DECL_ATTRIBUTES (current_function_decl)))
|
|
{
|
|
edge e;
|
|
edge_iterator ei;
|
|
bool found = false;
|
|
Value_Range return_range (TREE_TYPE (TREE_TYPE (current_function_decl)));
|
|
FOR_EACH_EDGE (e, ei, EXIT_BLOCK_PTR_FOR_FN (cfun)->preds)
|
|
if (greturn *ret = dyn_cast <greturn *> (*gsi_last_bb (e->src)))
|
|
{
|
|
tree retval = gimple_return_retval (ret);
|
|
if (!retval)
|
|
{
|
|
return_range.set_varying (TREE_TYPE (TREE_TYPE (current_function_decl)));
|
|
found = true;
|
|
continue;
|
|
}
|
|
Value_Range r (TREE_TYPE (retval));
|
|
if (ranger->range_of_expr (r, retval, ret)
|
|
&& !r.undefined_p ()
|
|
&& !r.varying_p ())
|
|
{
|
|
if (!found)
|
|
return_range = r;
|
|
else
|
|
return_range.union_ (r);
|
|
}
|
|
else
|
|
return_range.set_varying (TREE_TYPE (retval));
|
|
found = true;
|
|
}
|
|
if (found && !return_range.varying_p ())
|
|
{
|
|
ipa_record_return_value_range (return_range);
|
|
if (POINTER_TYPE_P (TREE_TYPE (TREE_TYPE (current_function_decl)))
|
|
&& return_range.nonzero_p ()
|
|
&& cgraph_node::get (current_function_decl)
|
|
->add_detected_attribute ("returns_nonnull"))
|
|
warn_function_returns_nonnull (current_function_decl);
|
|
}
|
|
}
|
|
|
|
phi_analysis_finalize ();
|
|
disable_ranger (fun);
|
|
scev_finalize ();
|
|
loop_optimizer_finalize ();
|
|
return 0;
|
|
}
|
|
|
|
// Implement a Fast VRP folder. Not quite as effective but faster.
|
|
|
|
class fvrp_folder : public substitute_and_fold_engine
|
|
{
|
|
public:
|
|
fvrp_folder (dom_ranger *dr) : substitute_and_fold_engine (),
|
|
m_simplifier (dr)
|
|
{ m_dom_ranger = dr; }
|
|
|
|
~fvrp_folder () { }
|
|
|
|
tree value_of_expr (tree name, gimple *s = NULL) override
|
|
{
|
|
// Shortcircuit subst_and_fold callbacks for abnormal ssa_names.
|
|
if (TREE_CODE (name) == SSA_NAME && SSA_NAME_OCCURS_IN_ABNORMAL_PHI (name))
|
|
return NULL;
|
|
return m_dom_ranger->value_of_expr (name, s);
|
|
}
|
|
|
|
tree value_on_edge (edge e, tree name) override
|
|
{
|
|
// Shortcircuit subst_and_fold callbacks for abnormal ssa_names.
|
|
if (TREE_CODE (name) == SSA_NAME && SSA_NAME_OCCURS_IN_ABNORMAL_PHI (name))
|
|
return NULL;
|
|
return m_dom_ranger->value_on_edge (e, name);
|
|
}
|
|
|
|
tree value_of_stmt (gimple *s, tree name = NULL) override
|
|
{
|
|
// Shortcircuit subst_and_fold callbacks for abnormal ssa_names.
|
|
if (TREE_CODE (name) == SSA_NAME && SSA_NAME_OCCURS_IN_ABNORMAL_PHI (name))
|
|
return NULL;
|
|
return m_dom_ranger->value_of_stmt (s, name);
|
|
}
|
|
|
|
void pre_fold_bb (basic_block bb) override
|
|
{
|
|
m_dom_ranger->pre_bb (bb);
|
|
// Now process the PHIs in advance.
|
|
gphi_iterator psi = gsi_start_phis (bb);
|
|
for ( ; !gsi_end_p (psi); gsi_next (&psi))
|
|
{
|
|
tree name = gimple_range_ssa_p (PHI_RESULT (psi.phi ()));
|
|
if (name)
|
|
{
|
|
Value_Range vr(TREE_TYPE (name));
|
|
m_dom_ranger->range_of_stmt (vr, psi.phi (), name);
|
|
}
|
|
}
|
|
}
|
|
|
|
void post_fold_bb (basic_block bb) override
|
|
{
|
|
m_dom_ranger->post_bb (bb);
|
|
}
|
|
|
|
void pre_fold_stmt (gimple *s) override
|
|
{
|
|
// Ensure range_of_stmt has been called.
|
|
tree type = gimple_range_type (s);
|
|
if (type)
|
|
{
|
|
Value_Range vr(type);
|
|
m_dom_ranger->range_of_stmt (vr, s);
|
|
}
|
|
}
|
|
|
|
bool fold_stmt (gimple_stmt_iterator *gsi) override
|
|
{
|
|
bool ret = m_simplifier.simplify (gsi);
|
|
if (!ret)
|
|
ret = ::fold_stmt (gsi, follow_single_use_edges);
|
|
return ret;
|
|
}
|
|
|
|
private:
|
|
DISABLE_COPY_AND_ASSIGN (fvrp_folder);
|
|
simplify_using_ranges m_simplifier;
|
|
dom_ranger *m_dom_ranger;
|
|
};
|
|
|
|
|
|
// Main entry point for a FAST VRP pass using a dom ranger.
|
|
|
|
unsigned int
|
|
execute_fast_vrp (struct function *fun)
|
|
{
|
|
calculate_dominance_info (CDI_DOMINATORS);
|
|
dom_ranger dr;
|
|
fvrp_folder folder (&dr);
|
|
|
|
gcc_checking_assert (!fun->x_range_query);
|
|
fun->x_range_query = &dr;
|
|
|
|
folder.substitute_and_fold ();
|
|
|
|
fun->x_range_query = NULL;
|
|
return 0;
|
|
}
|
|
|
|
namespace {
|
|
|
|
const pass_data pass_data_vrp =
|
|
{
|
|
GIMPLE_PASS, /* type */
|
|
"vrp", /* name */
|
|
OPTGROUP_NONE, /* optinfo_flags */
|
|
TV_TREE_VRP, /* tv_id */
|
|
PROP_ssa, /* properties_required */
|
|
0, /* properties_provided */
|
|
0, /* properties_destroyed */
|
|
0, /* todo_flags_start */
|
|
( TODO_cleanup_cfg | TODO_update_ssa ), /* todo_flags_finish */
|
|
};
|
|
|
|
const pass_data pass_data_early_vrp =
|
|
{
|
|
GIMPLE_PASS, /* type */
|
|
"evrp", /* name */
|
|
OPTGROUP_NONE, /* optinfo_flags */
|
|
TV_TREE_EARLY_VRP, /* tv_id */
|
|
PROP_ssa, /* properties_required */
|
|
0, /* properties_provided */
|
|
0, /* properties_destroyed */
|
|
0, /* todo_flags_start */
|
|
( TODO_cleanup_cfg | TODO_update_ssa | TODO_verify_all ),
|
|
};
|
|
|
|
const pass_data pass_data_fast_vrp =
|
|
{
|
|
GIMPLE_PASS, /* type */
|
|
"fvrp", /* name */
|
|
OPTGROUP_NONE, /* optinfo_flags */
|
|
TV_TREE_FAST_VRP, /* tv_id */
|
|
PROP_ssa, /* properties_required */
|
|
0, /* properties_provided */
|
|
0, /* properties_destroyed */
|
|
0, /* todo_flags_start */
|
|
( TODO_cleanup_cfg | TODO_update_ssa | TODO_verify_all ),
|
|
};
|
|
|
|
|
|
class pass_vrp : public gimple_opt_pass
|
|
{
|
|
public:
|
|
pass_vrp (gcc::context *ctxt, const pass_data &data_, bool warn_p)
|
|
: gimple_opt_pass (data_, ctxt), data (data_),
|
|
warn_array_bounds_p (warn_p), final_p (false)
|
|
{ }
|
|
|
|
/* opt_pass methods: */
|
|
opt_pass * clone () final override
|
|
{ return new pass_vrp (m_ctxt, data, false); }
|
|
void set_pass_param (unsigned int n, bool param) final override
|
|
{
|
|
gcc_assert (n == 0);
|
|
final_p = param;
|
|
}
|
|
bool gate (function *) final override { return flag_tree_vrp != 0; }
|
|
unsigned int execute (function *fun) final override
|
|
{
|
|
// Check for fast vrp.
|
|
if (&data == &pass_data_fast_vrp)
|
|
return execute_fast_vrp (fun);
|
|
|
|
return execute_ranger_vrp (fun, warn_array_bounds_p, final_p);
|
|
}
|
|
|
|
private:
|
|
const pass_data &data;
|
|
bool warn_array_bounds_p;
|
|
bool final_p;
|
|
}; // 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
|
|
{
|
|
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 the global range of NAME to anything calculated.
|
|
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 & ~TDF_DETAILS);
|
|
if (dump_flags & TDF_DETAILS)
|
|
query.dump (dump_file);
|
|
}
|
|
return TODO_discard_function;
|
|
}
|
|
|
|
}; // class pass_assumptions
|
|
|
|
} // anon namespace
|
|
|
|
gimple_opt_pass *
|
|
make_pass_vrp (gcc::context *ctxt)
|
|
{
|
|
return new pass_vrp (ctxt, pass_data_vrp, true);
|
|
}
|
|
|
|
gimple_opt_pass *
|
|
make_pass_early_vrp (gcc::context *ctxt)
|
|
{
|
|
return new pass_vrp (ctxt, pass_data_early_vrp, false);
|
|
}
|
|
|
|
gimple_opt_pass *
|
|
make_pass_fast_vrp (gcc::context *ctxt)
|
|
{
|
|
return new pass_vrp (ctxt, pass_data_fast_vrp, false);
|
|
}
|
|
|
|
gimple_opt_pass *
|
|
make_pass_assumptions (gcc::context *ctx)
|
|
{
|
|
return new pass_assumptions (ctx);
|
|
}
|