Normalize VARYING for -fstrict-enums.

The problem here is that the representation for VARYING in
-fstrict-enums is different between value_range and irange.

The helper function irange::normalize_min_max() will normalize to
VARYING only if setting the range to the entire domain of the
underlying type.  That is, [0, 0xff..ff], not the domain as defined by
-fstrict-enums.  This causes problems because the multi-range version
of varying_p() will return true if the range is the domain as defined
by -fstrict-enums.  Thus, normalize_min_max and varying_p have
different concepts of varying for multi-ranges.

(BTW, legacy ranges are different because they never look at the
extremes of a range to determine varying-ness.  They only look at the
kind field.)

One approach is to change all the code to limit ranges to the domain
in the -fstrict-enums world, but this won't work because there are
various instances of gimple where the values assigned or compared are
beyond the limits of TYPE_{MIN,MAX}_VALUE.  One example is the
addition of 0xffffffff to represent subtraction.

This patch fixes multi-range varying_p() and set_varying() to agree
with the normalization code, using the extremes of the underlying type,
to represent varying.

gcc/ChangeLog:

	PR tree-optimization/97767
	* value-range.cc (dump_bound_with_infinite_markers): Use
	wi::min_value and wi::max_value.
	(range_tests_strict_enum): New.
	(range_tests): Call range_tests_strict_enum.
	* value-range.h (irange::varying_p): Use wi::min_value
	and wi::max_value.
	(irange::set_varying): Same.
	(irange::normalize_min_max): Remove comment.

gcc/testsuite/ChangeLog:

	* g++.dg/opt/pr97767.C: New test.
This commit is contained in:
Aldy Hernandez 2020-11-09 20:35:25 +01:00
parent fb54c3032b
commit 3d3470e239
3 changed files with 60 additions and 15 deletions

View File

@ -0,0 +1,10 @@
// { dg-do compile }
// { dg-options "-O -fstrict-enums -ftree-vrp -w" }
enum { E0 = 0, E1 = 1, E2 = 2 } e;
int
foo (void)
{
return __builtin_popcount ((int) e);
}

View File

@ -1866,12 +1866,17 @@ static void
dump_bound_with_infinite_markers (FILE *file, tree bound)
{
tree type = TREE_TYPE (bound);
wide_int type_min = wi::min_value (TYPE_PRECISION (type), TYPE_SIGN (type));
wide_int type_max = wi::max_value (TYPE_PRECISION (type), TYPE_SIGN (type));
if (INTEGRAL_TYPE_P (type)
&& !TYPE_UNSIGNED (type)
&& vrp_val_is_min (bound)
&& TREE_CODE (bound) == INTEGER_CST
&& wi::to_wide (bound) == type_min
&& TYPE_PRECISION (type) != 1)
fprintf (file, "-INF");
else if (vrp_val_is_max (bound)
else if (TREE_CODE (bound) == INTEGER_CST
&& wi::to_wide (bound) == type_max
&& TYPE_PRECISION (type) != 1)
fprintf (file, "+INF");
else
@ -2241,6 +2246,38 @@ range_tests_legacy ()
}
}
// Simulate -fstrict-enums where the domain of a type is less than the
// underlying type.
static void
range_tests_strict_enum ()
{
// The enum can only hold [0, 3].
tree rtype = copy_node (unsigned_type_node);
TYPE_MIN_VALUE (rtype) = build_int_cstu (rtype, 0);
TYPE_MAX_VALUE (rtype) = build_int_cstu (rtype, 3);
// Test that even though vr1 covers the strict enum domain ([0, 3]),
// it does not cover the domain of the underlying type.
int_range<1> vr1 (build_int_cstu (rtype, 0), build_int_cstu (rtype, 1));
int_range<1> vr2 (build_int_cstu (rtype, 2), build_int_cstu (rtype, 3));
vr1.union_ (vr2);
ASSERT_TRUE (vr1 == int_range<1> (build_int_cstu (rtype, 0),
build_int_cstu (rtype, 3)));
ASSERT_FALSE (vr1.varying_p ());
// Test that copying to a multi-range does not change things.
int_range<2> ir1 (vr1);
ASSERT_TRUE (ir1 == vr1);
ASSERT_FALSE (ir1.varying_p ());
// The same test as above, but using TYPE_{MIN,MAX}_VALUE instead of [0,3].
vr1 = int_range<1> (TYPE_MIN_VALUE (rtype), TYPE_MAX_VALUE (rtype));
ir1 = vr1;
ASSERT_TRUE (ir1 == vr1);
ASSERT_FALSE (ir1.varying_p ());
}
static void
range_tests_misc ()
{
@ -2442,6 +2479,7 @@ range_tests ()
range_tests_legacy ();
range_tests_irange3 ();
range_tests_int_range_max ();
range_tests_strict_enum ();
range_tests_misc ();
}

View File

@ -280,12 +280,14 @@ irange::varying_p () const
tree l = m_base[0];
tree u = m_base[1];
tree t = TREE_TYPE (l);
unsigned prec = TYPE_PRECISION (t);
signop sign = TYPE_SIGN (t);
if (INTEGRAL_TYPE_P (t))
return l == TYPE_MIN_VALUE (t) && u == TYPE_MAX_VALUE (t);
return (wi::to_wide (l) == wi::min_value (prec, sign)
&& wi::to_wide (u) == wi::max_value (prec, sign));
if (POINTER_TYPE_P (t))
return wi::to_wide (l) == 0
&& wi::to_wide (u) == wi::max_value (TYPE_PRECISION (t),
TYPE_SIGN (t));
return (wi::to_wide (l) == 0
&& wi::to_wide (u) == wi::max_value (prec, sign));
return true;
}
@ -469,8 +471,10 @@ irange::set_varying (tree type)
m_num_ranges = 1;
if (INTEGRAL_TYPE_P (type))
{
m_base[0] = TYPE_MIN_VALUE (type);
m_base[1] = TYPE_MAX_VALUE (type);
wide_int min = wi::min_value (TYPE_PRECISION (type), TYPE_SIGN (type));
wide_int max = wi::max_value (TYPE_PRECISION (type), TYPE_SIGN (type));
m_base[0] = wide_int_to_tree (type, min);
m_base[1] = wide_int_to_tree (type, max);
}
else if (POINTER_TYPE_P (type))
{
@ -566,13 +570,6 @@ irange::set_zero (tree type)
}
// Normalize a range to VARYING or UNDEFINED if possible.
//
// Avoid using TYPE_{MIN,MAX}_VALUE because -fstrict-enums can
// restrict those to a subset of what actually fits in the type.
// Instead use the extremes of the type precision which will allow
// compare_range_with_value() to check if a value is inside a range,
// whereas if we used TYPE_*_VAL, said function would just punt upon
// seeing a VARYING.
inline void
irange::normalize_min_max ()