aboutsummaryrefslogtreecommitdiff
path: root/gcc/value-range.cc
diff options
context:
space:
mode:
authorAldy Hernandez <aldyh@redhat.com>2022-08-30 08:23:33 +0200
committerAldy Hernandez <aldyh@redhat.com>2022-08-30 11:26:24 +0200
commit8bb1df032cc080b751e00c0d7d86c31a630105f9 (patch)
tree109c86b64c96335dbdc87a78fa15028ba97eccd6 /gcc/value-range.cc
parentdf8fe4adb0721ab0e4486bc58482b501fe06287d (diff)
downloadgcc-8bb1df032cc080b751e00c0d7d86c31a630105f9.zip
gcc-8bb1df032cc080b751e00c0d7d86c31a630105f9.tar.gz
gcc-8bb1df032cc080b751e00c0d7d86c31a630105f9.tar.bz2
Add support for floating point endpoints to frange.
The current implementation of frange is just a type with some bits to represent NAN and INF. We can do better and represent endpoints to ultimately solve longstanding PRs such as PR24021. This patch adds these endpoints. In follow-up patches I will add support for a bare bones PLUS_EXPR range-op-float entry to solve the PR. I have chosen to use REAL_VALUE_TYPEs for the endpoints, since that's what we use underneath the trees. This will be somewhat analogous to our eventual use of wide-ints in the irange. No sense going through added levels of indirection if we can avoid it. That, plus real.* already has a nice API for dealing with floats. With this patch, ranges will be closed float point intervals, which make the implementation simpler, since we don't have to keep track of open/closed intervals. This is conservative enough for use in the ranger world, as we'd rather err on the side of more elements in a range, than less. For example, even though we cannot precisely represent the open interval (3.0, 5.0) with this approach, it is perfectably reasonable to represent it as [3.0, 5.0] since the closed interval is a super set of the open one. In the VRP/ranger world, it is always better to err on the side of more information in a range, than not. After all, when we don't know anything about a range, we just use VARYING which is a fancy term for a range spanning the entire domain. Since REAL_VALUE_TYPEs have properly defined infinity and NAN semantics, all the math can be made to work: [-INF, 3.0] !NAN => Numbers <= 3.0 (NAN cannot happen) [3.0, 3.0] => 3.0 or NAN. [3.0, +INF] => Numbers >= 3.0 (NAN is possible) [-INF, +INF] => VARYING (NAN is possible) [-INF, +INF] !NAN => Entire domain. NAN cannot happen. Also, since REAL_VALUE_TYPEs can represent the minimum and maximum representable values of a TYPE_MODE, we can disambiguate between them and negative and positive infinity (see get_max_float in real.cc). This also makes the math all work. For example, suppose we know nothing about x and y (VARYING). On the TRUE side of x > y, we can deduce that: (a) x cannot be NAN (b) y cannot be NAN (c) y cannot be +INF. (c) means that we can drop the upper bound of "y" from +INF to the maximum representable value for its type. Having endpoints with different representation for infinity and the maximum representable values, means we can drop the +-INF properties we currently have in the frange. gcc/ChangeLog: * range-op-float.cc (frange_set_nan): New. (frange_drop_inf): New. (frange_drop_ninf): New. (foperator_equal::op1_range): Adjust for endpoints. (foperator_lt::op1_range): Same. (foperator_lt::op2_range): Same. (foperator_gt::op1_range): Same. (foperator_gt::op2_range): Same. (foperator_unordered::op1_range): Same. * value-query.cc (range_query::get_tree_range): Same. * value-range-pretty-print.cc (vrange_printer::visit): Same. * value-range-storage.cc (frange_storage_slot::get_frange): Same. * value-range.cc (frange::set): Same. (frange::normalize_kind): Same. (frange::union_): Same. (frange::intersect): Same. (frange::operator=): Same. (early_nan_resolve): New. (frange::contains_p): New. (frange::singleton_p): New. (frange::set_nonzero): New. (frange::nonzero_p): New. (frange::set_zero): New. (frange::zero_p): New. (frange::set_nonnegative): New. (frange_float): New. (frange_nan): New. (range_tests_nan): New. (range_tests_signed_zeros): New. (range_tests_floats): New. (range_tests): New. * value-range.h (frange::lower_bound): New. (frange::upper_bound): New. (vrp_val_min): Use real_inf with a sign instead of negating inf. (frange::frange): New. (frange::set_varying): Adjust for endpoints. (real_max_representable): New. (real_min_representable): New.
Diffstat (limited to 'gcc/value-range.cc')
-rw-r--r--gcc/value-range.cc462
1 files changed, 417 insertions, 45 deletions
diff --git a/gcc/value-range.cc b/gcc/value-range.cc
index edd10bf..bcc6651 100644
--- a/gcc/value-range.cc
+++ b/gcc/value-range.cc
@@ -291,41 +291,20 @@ frange::set (tree min, tree max, value_range_kind kind)
m_kind = kind;
m_type = TREE_TYPE (min);
m_props.set_varying ();
+ m_min = *TREE_REAL_CST_PTR (min);
+ m_max = *TREE_REAL_CST_PTR (max);
- bool is_min = vrp_val_is_min (min);
- bool is_max = vrp_val_is_max (max);
bool is_nan = (real_isnan (TREE_REAL_CST_PTR (min))
|| real_isnan (TREE_REAL_CST_PTR (max)));
// Ranges with a NAN and a non-NAN endpoint are nonsensical.
gcc_checking_assert (!is_nan || operand_equal_p (min, max));
- // The properties for singletons can be all set ahead of time.
- if (operand_equal_p (min, max))
- {
- // Set INF properties.
- if (is_min)
- m_props.ninf_set_yes ();
- else
- m_props.ninf_set_no ();
- if (is_max)
- m_props.inf_set_yes ();
- else
- m_props.inf_set_no ();
- // Set NAN property.
- if (is_nan)
- m_props.nan_set_yes ();
- else
- m_props.nan_set_no ();
- }
- else
- {
- // Mark when the endpoints can't be +-INF.
- if (!is_min)
- m_props.ninf_set_no ();
- if (!is_max)
- m_props.inf_set_no ();
- }
+ // Set NAN property if we're absolutely sure.
+ if (is_nan && operand_equal_p (min, max))
+ m_props.nan_set_yes ();
+ else if (!HONOR_NANS (m_type))
+ m_props.nan_set_no ();
// Check for swapped ranges.
gcc_checking_assert (is_nan || tree_compare (LE_EXPR, min, max));
@@ -336,6 +315,16 @@ frange::set (tree min, tree max, value_range_kind kind)
verify_range ();
}
+// Setter for frange from REAL_VALUE_TYPE endpoints.
+
+void
+frange::set (tree type,
+ const REAL_VALUE_TYPE &min, const REAL_VALUE_TYPE &max,
+ value_range_kind kind)
+{
+ set (build_real (type, min), build_real (type, max), kind);
+}
+
// Normalize range to VARYING or UNDEFINED, or vice versa. Return
// TRUE if anything changed.
//
@@ -347,7 +336,15 @@ frange::set (tree min, tree max, value_range_kind kind)
bool
frange::normalize_kind ()
{
- if (m_kind == VR_RANGE)
+ // Undefined is viral.
+ if (m_props.nan_undefined_p ())
+ {
+ set_undefined ();
+ return true;
+ }
+ if (m_kind == VR_RANGE
+ && real_isinf (&m_min, 1)
+ && real_isinf (&m_max, 0))
{
// No FP properties set means varying.
if (m_props.varying_p ())
@@ -355,14 +352,6 @@ frange::normalize_kind ()
set_varying (m_type);
return true;
}
- // Undefined is viral.
- if (m_props.nan_undefined_p ()
- || m_props.inf_undefined_p ()
- || m_props.ninf_undefined_p ())
- {
- set_undefined ();
- return true;
- }
}
else if (m_kind == VR_VARYING)
{
@@ -370,12 +359,32 @@ frange::normalize_kind ()
if (!m_props.varying_p ())
{
m_kind = VR_RANGE;
+ real_inf (&m_min, 1);
+ real_inf (&m_max, 0);
return true;
}
}
return false;
}
+// If both operands are definitely NAN, do nothing as they combine
+// perfectly. If OTOH, only one is a NAN, set R to VARYING as they
+// can't be neither unioned nor intersected. Return TRUE if we
+// changed anything.
+
+static inline bool
+early_nan_resolve (frange &r, const frange &other)
+{
+ gcc_checking_assert (r.get_nan ().yes_p () || other.get_nan ().yes_p ());
+
+ // There's nothing to do for both NANs.
+ if (r.get_nan ().yes_p () == other.get_nan ().yes_p ())
+ return false;
+ // But only one NAN complicates things.
+ r.set_varying (r.type ());
+ return true;
+}
+
bool
frange::union_ (const vrange &v)
{
@@ -388,13 +397,34 @@ frange::union_ (const vrange &v)
*this = r;
return true;
}
+ // ?? We could do better here. [5,6] U NAN should be [5,6] with the
+ // NAN maybe bits set. For now, this is conservatively correct.
+ if (get_nan ().yes_p () || r.get_nan ().yes_p ())
+ return early_nan_resolve (*this, r);
- bool ret = m_props.union_ (r.m_props);
- ret |= normalize_kind ();
+ bool changed = m_props.union_ (r.m_props);
+
+ // Note: for the combination of zeros that differ in sign we keep
+ // the endpoints untouched. This means that for -0.0 U +0.0, the
+ // result will be -0.0. Ultimately this doesn't matter, because we
+ // keep singleton zeros ambiguous throughout to avoid propagating
+ // them. See frange::singleton_p().
+
+ if (real_less (&r.m_min, &m_min))
+ {
+ m_min = r.m_min;
+ changed = true;
+ }
+ if (real_less (&m_max, &r.m_max))
+ {
+ m_max = r.m_max;
+ changed = true;
+ }
+ changed |= normalize_kind ();
if (flag_checking)
verify_range ();
- return ret;
+ return changed;
}
bool
@@ -414,13 +444,38 @@ frange::intersect (const vrange &v)
*this = r;
return true;
}
+ if (get_nan ().yes_p () || r.get_nan ().yes_p ())
+ return early_nan_resolve (*this, r);
+
+ bool changed = m_props.intersect (r.m_props);
+
+ // Note: for the combination of zeros that differ in sign we keep
+ // the endpoints untouched. This means that for -0.0 ^ +0.0, the
+ // result will be -0.0. Ultimately this doesn't matter, because we
+ // keep singleton zeros ambiguous throughout to avoid propagating
+ // them. See frange::singleton_p().
- bool ret = m_props.intersect (r.m_props);
- ret |= normalize_kind ();
+ if (real_less (&m_min, &r.m_min))
+ {
+ m_min = r.m_min;
+ changed = true;
+ }
+ if (real_less (&r.m_max, &m_max))
+ {
+ m_max = r.m_max;
+ changed = true;
+ }
+ // If the endpoints are swapped, the ranges are disjoint.
+ if (real_less (&m_max, &m_min))
+ {
+ set_undefined ();
+ return true;
+ }
+ changed |= normalize_kind ();
if (flag_checking)
verify_range ();
- return ret;
+ return changed;
}
frange &
@@ -428,6 +483,8 @@ frange::operator= (const frange &src)
{
m_kind = src.m_kind;
m_type = src.m_type;
+ m_min = src.m_min;
+ m_max = src.m_max;
m_props = src.m_props;
if (flag_checking)
@@ -446,7 +503,59 @@ frange::operator== (const frange &src) const
if (varying_p ())
return types_compatible_p (m_type, src.m_type);
- return m_props == src.m_props;
+ if (m_props.get_nan ().yes_p ()
+ || src.m_props.get_nan ().yes_p ())
+ return false;
+
+ return (real_identical (&m_min, &src.m_min)
+ && real_identical (&m_max, &src.m_max)
+ && m_props == src.m_props
+ && types_compatible_p (m_type, src.m_type));
+ }
+ return false;
+}
+
+// Return TRUE if range contains the TREE_REAL_CST_PTR in CST.
+
+bool
+frange::contains_p (tree cst) const
+{
+ if (undefined_p ())
+ return false;
+
+ if (varying_p ())
+ return true;
+
+ gcc_checking_assert (m_kind == VR_RANGE);
+
+ return (real_compare (GE_EXPR, TREE_REAL_CST_PTR (cst), &m_min)
+ && real_compare (LE_EXPR, TREE_REAL_CST_PTR (cst), &m_max));
+}
+
+// If range is a singleton, place it in RESULT and return TRUE. If
+// RESULT is NULL, just return TRUE.
+//
+// A NAN can never be a singleton, and neither can a 0.0 if we're
+// honoring signed zeros because we have no way of telling which zero
+// it is.
+
+bool
+frange::singleton_p (tree *result) const
+{
+ if (m_kind == VR_RANGE && real_identical (&m_min, &m_max))
+ {
+ // If we're honoring signed zeros, fail because we don't know
+ // which zero we have. This avoids propagating the wrong zero.
+ if (HONOR_SIGNED_ZEROS (m_type) && zero_p ())
+ return false;
+
+ // Return false for any singleton that may be a NAN.
+ if (HONOR_NANS (m_type) && !get_nan ().no_p ())
+ return false;
+
+ if (result)
+ *result = build_real (m_type, m_min);
+ return true;
}
return false;
}
@@ -465,13 +574,82 @@ frange::verify_range ()
gcc_checking_assert (m_props.undefined_p ());
return;
}
+ gcc_checking_assert (!m_props.undefined_p ());
+
if (varying_p ())
{
gcc_checking_assert (m_props.varying_p ());
return;
}
+
+ // We don't support the inverse of an frange (yet).
gcc_checking_assert (m_kind == VR_RANGE);
- gcc_checking_assert (!m_props.varying_p () && !m_props.undefined_p ());
+
+ bool is_nan = real_isnan (&m_min) || real_isnan (&m_max);
+ if (is_nan)
+ {
+ // If either is a NAN, both must be a NAN.
+ gcc_checking_assert (real_identical (&m_min, &m_max));
+ gcc_checking_assert (get_nan ().yes_p ());
+ }
+ else
+ // Make sure we don't have swapped ranges.
+ gcc_checking_assert (!real_less (&m_max, &m_min));
+
+ // If we're absolutely sure we have a NAN, the endpoints should
+ // reflect this, otherwise we'd have more than one way to represent
+ // a NAN.
+ if (m_props.get_nan ().yes_p ())
+ {
+ gcc_checking_assert (real_isnan (&m_min));
+ gcc_checking_assert (real_isnan (&m_max));
+ }
+
+ // If all the properties are clear, we better not span the entire
+ // domain, because that would make us varying.
+ if (m_props.varying_p ())
+ gcc_checking_assert (!real_isinf (&m_min, 1) || !real_isinf (&m_max, 0));
+}
+
+// We can't do much with nonzeros yet.
+void
+frange::set_nonzero (tree type)
+{
+ set_varying (type);
+}
+
+// We can't do much with nonzeros yet.
+bool
+frange::nonzero_p () const
+{
+ return false;
+}
+
+// Set range to [+0.0, +0.0].
+
+void
+frange::set_zero (tree type)
+{
+ tree zero = build_zero_cst (type);
+ set (zero, zero);
+}
+
+// Return TRUE for any [0.0, 0.0] regardless of sign.
+
+bool
+frange::zero_p () const
+{
+ return (m_kind == VR_RANGE
+ && real_iszero (&m_min)
+ && real_iszero (&m_max));
+}
+
+void
+frange::set_nonnegative (tree type)
+{
+ tree zero = build_zero_cst (type);
+ tree inf = vrp_val_max (type);
+ set (zero, inf);
}
// Here we copy between any two irange's. The ranges can be legacy or
@@ -3304,6 +3482,199 @@ range_tests_nonzero_bits ()
ASSERT_TRUE (r0.varying_p ());
}
+// Build an frange from string endpoints.
+
+static inline frange
+frange_float (const char *lb, const char *ub, tree type = float_type_node)
+{
+ REAL_VALUE_TYPE min, max;
+ gcc_assert (real_from_string (&min, lb) == 0);
+ gcc_assert (real_from_string (&max, ub) == 0);
+ return frange (type, min, max);
+}
+
+// Build a NAN of type TYPE.
+
+static inline frange
+frange_nan (tree type = float_type_node)
+{
+ REAL_VALUE_TYPE r;
+
+ gcc_assert (real_nan (&r, "", 1, TYPE_MODE (type)));
+ return frange (type, r, r);
+}
+
+static void
+range_tests_nan ()
+{
+ frange r0, r1;
+
+ // Equal ranges but with differing NAN bits are not equal.
+ r1 = frange_float ("10", "12");
+ r0 = r1;
+ ASSERT_EQ (r0, r1);
+ r0.set_nan (fp_prop::NO);
+ ASSERT_NE (r0, r1);
+ r0.set_nan (fp_prop::YES);
+ ASSERT_NE (r0, r1);
+ r0.set_nan (fp_prop::VARYING);
+ ASSERT_EQ (r0, r1);
+
+ // NAN ranges are not equal to each other.
+ r0 = frange_nan ();
+ r1 = r0;
+ ASSERT_FALSE (r0 == r1);
+ ASSERT_FALSE (r0 == r0);
+ ASSERT_TRUE (r0 != r0);
+
+ // Make sure that combining NAN and INF doesn't give any crazy results.
+ r0 = frange_nan ();
+ ASSERT_TRUE (r0.get_nan ().yes_p ());
+ r1 = frange_float ("+Inf", "+Inf");
+ r0.union_ (r1);
+ // [INF, INF] U NAN = VARYING
+ ASSERT_TRUE (r0.varying_p ());
+
+ // [INF, INF] ^ NAN = VARYING
+ r0 = frange_nan ();
+ r1 = frange_float ("+Inf", "+Inf");
+ r0.intersect (r1);
+ ASSERT_TRUE (r0.varying_p ());
+
+ // NAN ^ NAN = NAN
+ r0 = frange_nan ();
+ r1 = frange_nan ();
+ r0.intersect (r1);
+ ASSERT_TRUE (r0.get_nan ().yes_p ());
+
+ // VARYING ^ NAN = NAN.
+ r0 = frange_nan ();
+ r1.set_varying (float_type_node);
+ r0.intersect (r1);
+ ASSERT_TRUE (r0.get_nan ().yes_p ());
+}
+
+static void
+range_tests_signed_zeros ()
+{
+ tree zero = build_zero_cst (float_type_node);
+ tree neg_zero = fold_build1 (NEGATE_EXPR, float_type_node, zero);
+ frange r0, r1;
+
+ // ?? If -0.0 == +0.0, then a range of [-0.0, -0.0] should
+ // contain +0.0 and vice versa. We probably need a property to
+ // track signed zeros in the frange like we do for NAN, to
+ // properly model all this.
+ r0 = frange (zero, zero);
+ r1 = frange (neg_zero, neg_zero);
+ ASSERT_TRUE (r0.contains_p (zero));
+ ASSERT_TRUE (r0.contains_p (neg_zero));
+ ASSERT_TRUE (r1.contains_p (zero));
+ ASSERT_TRUE (r1.contains_p (neg_zero));
+}
+
+static void
+range_tests_floats ()
+{
+ frange r0, r1;
+
+ range_tests_nan ();
+
+ if (HONOR_SIGNED_ZEROS (float_type_node))
+ range_tests_signed_zeros ();
+
+ // A range of [-INF,+INF] is actually VARYING...
+ r0 = frange_float ("-Inf", "+Inf");
+ ASSERT_TRUE (r0.varying_p ());
+ // ...unless it has some special property...
+ r0.set_nan (fp_prop::NO);
+ ASSERT_FALSE (r0.varying_p ());
+
+ // The endpoints of a VARYING are +-INF.
+ REAL_VALUE_TYPE inf, ninf;
+ real_inf (&inf, 0);
+ real_inf (&ninf, 1);
+ r0.set_varying (float_type_node);
+ ASSERT_TRUE (real_identical (&r0.lower_bound (), &ninf));
+ ASSERT_TRUE (real_identical (&r0.upper_bound (), &inf));
+
+ // The maximum representable range for a type is still a subset of VARYING.
+ REAL_VALUE_TYPE q, r;
+ real_min_representable (&q, float_type_node);
+ real_max_representable (&r, float_type_node);
+ r0 = frange (float_type_node, q, r);
+ // r0 is not a varying, because it does not include -INF/+INF.
+ ASSERT_FALSE (r0.varying_p ());
+ // The upper bound of r0 must be less than +INF.
+ ASSERT_TRUE (real_less (&r0.upper_bound (), &inf));
+ // The lower bound of r0 must be greater than -INF.
+ ASSERT_TRUE (real_less (&ninf, &r0.lower_bound ()));
+
+ // For most architectures, where float and double are different
+ // sizes, having the same endpoints does not necessarily mean the
+ // ranges are equal.
+ if (!types_compatible_p (float_type_node, double_type_node))
+ {
+ r0 = frange_float ("3.0", "3.0", float_type_node);
+ r1 = frange_float ("3.0", "3.0", double_type_node);
+ ASSERT_NE (r0, r1);
+ }
+
+ // [3,5] U [10,12] = [3,12].
+ r0 = frange_float ("3", "5");
+ r1 = frange_float ("10", "12");
+ r0.union_ (r1);
+ ASSERT_EQ (r0, frange_float ("3", "12"));
+
+ // [5,10] U [4,8] = [4,10]
+ r0 = frange_float ("5", "10");
+ r1 = frange_float ("4", "8");
+ r0.union_ (r1);
+ ASSERT_EQ (r0, frange_float ("4", "10"));
+
+ // [3,5] U [4,10] = [3,10]
+ r0 = frange_float ("3", "5");
+ r1 = frange_float ("4", "10");
+ r0.union_ (r1);
+ ASSERT_EQ (r0, frange_float ("3", "10"));
+
+ // [4,10] U [5,11] = [4,11]
+ r0 = frange_float ("4", "10");
+ r1 = frange_float ("5", "11");
+ r0.union_ (r1);
+ ASSERT_EQ (r0, frange_float ("4", "11"));
+
+ // [3,12] ^ [10,12] = [10,12].
+ r0 = frange_float ("3", "12");
+ r1 = frange_float ("10", "12");
+ r0.intersect (r1);
+ ASSERT_EQ (r0, frange_float ("10", "12"));
+
+ // [10,12] ^ [11,11] = [11,11]
+ r0 = frange_float ("10", "12");
+ r1 = frange_float ("11", "11");
+ r0.intersect (r1);
+ ASSERT_EQ (r0, frange_float ("11", "11"));
+
+ // [10,20] ^ [5,15] = [10,15]
+ r0 = frange_float ("10", "20");
+ r1 = frange_float ("5", "15");
+ r0.intersect (r1);
+ ASSERT_EQ (r0, frange_float ("10", "15"));
+
+ // [10,20] ^ [15,25] = [15,20]
+ r0 = frange_float ("10", "20");
+ r1 = frange_float ("15", "25");
+ r0.intersect (r1);
+ ASSERT_EQ (r0, frange_float ("15", "20"));
+
+ // [10,20] ^ [21,25] = []
+ r0 = frange_float ("10", "20");
+ r1 = frange_float ("21", "25");
+ r0.intersect (r1);
+ ASSERT_TRUE (r0.undefined_p ());
+}
+
void
range_tests ()
{
@@ -3312,6 +3683,7 @@ range_tests ()
range_tests_int_range_max ();
range_tests_strict_enum ();
range_tests_nonzero_bits ();
+ range_tests_floats ();
range_tests_misc ();
}