Implement relational operators for frange with endpoints.

This is the implementation of the relational range operators for
frange.  These are the core operations that require specific FP domain
knowledge.

gcc/ChangeLog:

	* range-op-float.cc (finite_operand_p): New.
	(build_le): New.
	(build_lt): New.
	(build_ge): New.
	(build_gt): New.
	(foperator_equal::fold_range): New implementation with endpoints.
	(foperator_equal::op1_range): Same.
	(foperator_not_equal::fold_range): Same.
	(foperator_not_equal::op1_range): Same.
	(foperator_lt::fold_range): Same.
	(foperator_lt::op1_range): Same.
	(foperator_lt::op2_range): Same.
	(foperator_le::fold_range): Same.
	(foperator_le::op1_range): Same.
	(foperator_le::op2_range): Same.
	(foperator_gt::fold_range): Same.
	(foperator_gt::op1_range): Same.
	(foperator_gt::op2_range): Same.
	(foperator_ge::fold_range): Same.
	(foperator_ge::op1_range): Same.
	(foperator_ge::op2_range): Same.

gcc/testsuite/ChangeLog:

	* gcc.dg/tree-ssa/recip-3.c: Avoid premature optimization so test
	has a chance to succeed.
This commit is contained in:
Aldy Hernandez 2022-08-30 08:23:51 +02:00
parent 8bb1df032c
commit 4fbe3e6aa7
2 changed files with 309 additions and 54 deletions

View file

@ -162,6 +162,14 @@ frange_set_nan (frange &r, tree type)
r.set (type, rv, rv);
}
// Return TRUE if OP1 is known to be free of NANs.
static inline bool
finite_operand_p (const frange &op1)
{
return flag_finite_math_only || op1.get_nan ().no_p ();
}
// Return TRUE if OP1 and OP2 are known to be free of NANs.
static inline bool
@ -240,6 +248,45 @@ default_frelop_fold_range (irange &r, tree type,
return true;
}
// (X <= VAL) produces the range of [MIN, VAL].
static void
build_le (frange &r, tree type, const REAL_VALUE_TYPE &val)
{
REAL_VALUE_TYPE min;
real_inf (&min, 1);
r.set (type, min, val);
}
// (X < VAL) produces the range of [MIN, VAL).
static void
build_lt (frange &r, tree type, const REAL_VALUE_TYPE &val)
{
// Hijack LE because we only support closed intervals.
build_le (r, type, val);
}
// (X >= VAL) produces the range of [VAL, MAX].
static void
build_ge (frange &r, tree type, const REAL_VALUE_TYPE &val)
{
REAL_VALUE_TYPE max;
real_inf (&max, 0);
r.set (type, val, max);
}
// (X > VAL) produces the range of (VAL, MAX].
static void
build_gt (frange &r, tree type, const REAL_VALUE_TYPE &val)
{
// Hijack GE because we only support closed intervals.
build_ge (r, type, val);
}
class foperator_identity : public range_operator_float
{
using range_operator_float::fold_range;
@ -270,10 +317,7 @@ class foperator_equal : public range_operator_float
bool fold_range (irange &r, tree type,
const frange &op1, const frange &op2,
relation_kind rel) const final override
{
return default_frelop_fold_range (r, type, op1, op2, rel, VREL_EQ);
}
relation_kind rel) const final override;
relation_kind op1_op2_relation (const irange &lhs) const final override
{
return equal_op1_op2_relation (lhs);
@ -289,6 +333,39 @@ class foperator_equal : public range_operator_float
}
} fop_equal;
bool
foperator_equal::fold_range (irange &r, tree type,
const frange &op1, const frange &op2,
relation_kind rel) const
{
if (frelop_early_resolve (r, type, op1, op2, rel, VREL_EQ))
return true;
// We can be sure the values are always equal or not if both ranges
// consist of a single value, and then compare them.
if (op1.singleton_p () && op2.singleton_p ())
{
if (op1 == op2)
r = range_true (type);
else
r = range_false (type);
}
else if (finite_operands_p (op1, op2))
{
// If ranges do not intersect, we know the range is not equal,
// otherwise we don't know anything for sure.
frange tmp = op1;
tmp.intersect (op2);
if (tmp.undefined_p ())
r = range_false (type);
else
r = range_true_and_false (type);
}
else
r = range_true_and_false (type);
return true;
}
bool
foperator_equal::op1_range (frange &r, tree type,
const irange &lhs,
@ -309,6 +386,14 @@ foperator_equal::op1_range (frange &r, tree type,
// The FALSE side of op1 == op1 implies op1 is a NAN.
if (rel == VREL_EQ)
frange_set_nan (r, type);
// If the result is false, the only time we know anything is
// if OP2 is a constant.
else if (op2.singleton_p ()
|| (finite_operand_p (op2) && op2.zero_p ()))
{
REAL_VALUE_TYPE tmp = op2.lower_bound ();
r.set (type, tmp, tmp, VR_ANTI_RANGE);
}
break;
default:
@ -324,10 +409,7 @@ class foperator_not_equal : public range_operator_float
bool fold_range (irange &r, tree type,
const frange &op1, const frange &op2,
relation_kind rel) const final override
{
return default_frelop_fold_range (r, type, op1, op2, rel, VREL_NE);
}
relation_kind rel) const final override;
relation_kind op1_op2_relation (const irange &lhs) const final override
{
return not_equal_op1_op2_relation (lhs);
@ -337,6 +419,39 @@ class foperator_not_equal : public range_operator_float
relation_kind rel) const final override;
} fop_not_equal;
bool
foperator_not_equal::fold_range (irange &r, tree type,
const frange &op1, const frange &op2,
relation_kind rel) const
{
if (frelop_early_resolve (r, type, op1, op2, rel, VREL_NE))
return true;
// We can be sure the values are always equal or not if both ranges
// consist of a single value, and then compare them.
if (op1.singleton_p () && op2.singleton_p ())
{
if (op1 != op2)
r = range_true (type);
else
r = range_false (type);
}
else if (finite_operands_p (op1, op2))
{
// If ranges do not intersect, we know the range is not equal,
// otherwise we don't know anything for sure.
frange tmp = op1;
tmp.intersect (op2);
if (tmp.undefined_p ())
r = range_true (type);
else
r = range_true_and_false (type);
}
else
r = range_true_and_false (type);
return true;
}
bool
foperator_not_equal::op1_range (frange &r, tree type,
const irange &lhs,
@ -346,11 +461,23 @@ foperator_not_equal::op1_range (frange &r, tree type,
switch (get_bool_state (r, lhs, type))
{
case BRS_TRUE:
r.set_varying (type);
// If the result is true, the only time we know anything is if
// OP2 is a constant.
if (op2.singleton_p ())
{
// This is correct even if op1 is NAN, because the following
// range would be ~[tmp, tmp] with the NAN property set to
// maybe (VARYING).
REAL_VALUE_TYPE tmp = op2.lower_bound ();
r.set (type, tmp, tmp, VR_ANTI_RANGE);
}
else
r.set_varying (type);
break;
case BRS_FALSE:
r.set_varying (type);
// If it's false, the result is the same as OP2.
r = op2;
// The FALSE side of op1 != op2 implies op1 is !NAN.
r.set_nan (fp_prop::NO);
break;
@ -369,10 +496,7 @@ class foperator_lt : public range_operator_float
bool fold_range (irange &r, tree type,
const frange &op1, const frange &op2,
relation_kind rel) const final override
{
return default_frelop_fold_range (r, type, op1, op2, rel, VREL_LT);
}
relation_kind rel) const final override;
relation_kind op1_op2_relation (const irange &lhs) const final override
{
return lt_op1_op2_relation (lhs);
@ -385,6 +509,31 @@ class foperator_lt : public range_operator_float
relation_kind rel) const final override;
} fop_lt;
bool
foperator_lt::fold_range (irange &r, tree type,
const frange &op1, const frange &op2,
relation_kind rel) const
{
if (frelop_early_resolve (r, type, op1, op2, rel, VREL_LT))
return true;
if (finite_operands_p (op1, op2))
{
if (real_less (&op1.upper_bound (), &op2.lower_bound ()))
r = range_true (type);
else if (finite_operands_p (op1, op2)
&& !real_less (&op1.lower_bound (), &op2.upper_bound ()))
r = range_false (type);
else
r = range_true_and_false (type);
}
else if (op1.get_nan ().yes_p () || op2.get_nan ().yes_p ())
r = range_false (type);
else
r = range_true_and_false (type);
return true;
}
bool
foperator_lt::op1_range (frange &r,
tree type,
@ -395,15 +544,14 @@ foperator_lt::op1_range (frange &r,
switch (get_bool_state (r, lhs, type))
{
case BRS_TRUE:
r.set_varying (type);
// The TRUE side of op1 < op2 implies op1 is !NAN and !INF.
build_lt (r, type, op2.upper_bound ());
r.set_nan (fp_prop::NO);
// x < y implies x is not +INF.
frange_drop_inf (r, type);
break;
case BRS_FALSE:
r.set_varying (type);
build_ge (r, type, op2.lower_bound ());
break;
default:
@ -422,15 +570,14 @@ foperator_lt::op2_range (frange &r,
switch (get_bool_state (r, lhs, type))
{
case BRS_TRUE:
r.set_varying (type);
// The TRUE side of op1 < op2 implies op2 is !NAN and !NINF.
build_gt (r, type, op1.lower_bound ());
r.set_nan (fp_prop::NO);
// x < y implies y is not -INF.
frange_drop_ninf (r, type);
break;
case BRS_FALSE:
r.set_varying (type);
build_le (r, type, op1.upper_bound ());
break;
default:
@ -447,10 +594,7 @@ class foperator_le : public range_operator_float
bool fold_range (irange &r, tree type,
const frange &op1, const frange &op2,
relation_kind rel) const final override
{
return default_frelop_fold_range (r, type, op1, op2, rel, VREL_LE);
}
relation_kind rel) const final override;
relation_kind op1_op2_relation (const irange &lhs) const final override
{
return le_op1_op2_relation (lhs);
@ -460,29 +604,74 @@ class foperator_le : public range_operator_float
relation_kind rel) const final override;
bool op2_range (frange &r, tree type,
const irange &lhs, const frange &op1,
relation_kind rel) const final override
{
return op1_range (r, type, lhs, op1, rel);
}
relation_kind rel) const final override;
} fop_le;
bool
foperator_le::fold_range (irange &r, tree type,
const frange &op1, const frange &op2,
relation_kind rel) const
{
if (frelop_early_resolve (r, type, op1, op2, rel, VREL_LE))
return true;
if (finite_operands_p (op1, op2))
{
if (real_compare (LE_EXPR, &op1.upper_bound (), &op2.lower_bound ()))
r = range_true (type);
else if (finite_operands_p (op1, op2)
&& !real_compare (LE_EXPR, &op1.lower_bound (), &op2.upper_bound ()))
r = range_false (type);
else
r = range_true_and_false (type);
}
else if (op1.get_nan ().yes_p () || op2.get_nan ().yes_p ())
r = range_false (type);
else
r = range_true_and_false (type);
return true;
}
bool
foperator_le::op1_range (frange &r,
tree type,
const irange &lhs,
const frange &op2 ATTRIBUTE_UNUSED,
const frange &op2,
relation_kind) const
{
switch (get_bool_state (r, lhs, type))
{
case BRS_TRUE:
r.set_varying (type);
// The TRUE side of op1 <= op2 implies op1 is !NAN.
build_le (r, type, op2.upper_bound ());
r.set_nan (fp_prop::NO);
break;
case BRS_FALSE:
r.set_varying (type);
build_gt (r, type, op2.lower_bound ());
break;
default:
break;
}
return true;
}
bool
foperator_le::op2_range (frange &r,
tree type,
const irange &lhs,
const frange &op1,
relation_kind) const
{
switch (get_bool_state (r, lhs, type))
{
case BRS_TRUE:
build_ge (r, type, op1.lower_bound ());
r.set_nan (fp_prop::NO);
break;
case BRS_FALSE:
build_lt (r, type, op1.upper_bound ());
break;
default:
@ -499,10 +688,7 @@ class foperator_gt : public range_operator_float
bool fold_range (irange &r, tree type,
const frange &op1, const frange &op2,
relation_kind rel) const final override
{
return default_frelop_fold_range (r, type, op1, op2, rel, VREL_GT);
}
relation_kind rel) const final override;
relation_kind op1_op2_relation (const irange &lhs) const final override
{
return gt_op1_op2_relation (lhs);
@ -515,6 +701,31 @@ class foperator_gt : public range_operator_float
relation_kind rel) const final override;
} fop_gt;
bool
foperator_gt::fold_range (irange &r, tree type,
const frange &op1, const frange &op2,
relation_kind rel) const
{
if (frelop_early_resolve (r, type, op1, op2, rel, VREL_GT))
return true;
if (finite_operands_p (op1, op2))
{
if (real_compare (GT_EXPR, &op1.lower_bound (), &op2.upper_bound ()))
r = range_true (type);
else if (finite_operands_p (op1, op2)
&& !real_compare (GT_EXPR, &op1.upper_bound (), &op2.lower_bound ()))
r = range_false (type);
else
r = range_true_and_false (type);
}
else if (op1.get_nan ().yes_p () || op2.get_nan ().yes_p ())
r = range_false (type);
else
r = range_true_and_false (type);
return true;
}
bool
foperator_gt::op1_range (frange &r,
tree type,
@ -525,15 +736,14 @@ foperator_gt::op1_range (frange &r,
switch (get_bool_state (r, lhs, type))
{
case BRS_TRUE:
r.set_varying (type);
// The TRUE side of op1 > op2 implies op1 is !NAN and !NINF.
build_gt (r, type, op2.lower_bound ());
r.set_nan (fp_prop::NO);
// x > y implies x is not -INF.
frange_drop_ninf (r, type);
break;
case BRS_FALSE:
r.set_varying (type);
build_le (r, type, op2.upper_bound ());
break;
default:
@ -552,15 +762,14 @@ foperator_gt::op2_range (frange &r,
switch (get_bool_state (r, lhs, type))
{
case BRS_TRUE:
r.set_varying (type);
// The TRUE side of op1 > op2 implies op2 is !NAN and !INF.
build_lt (r, type, op1.upper_bound ());
r.set_nan (fp_prop::NO);
// x > y implies y is not +INF.
frange_drop_inf (r, type);
break;
case BRS_FALSE:
r.set_varying (type);
build_ge (r, type, op1.lower_bound ());
break;
default:
@ -577,10 +786,7 @@ class foperator_ge : public range_operator_float
bool fold_range (irange &r, tree type,
const frange &op1, const frange &op2,
relation_kind rel) const final override
{
return default_frelop_fold_range (r, type, op1, op2, rel, VREL_GE);
}
relation_kind rel) const final override;
relation_kind op1_op2_relation (const irange &lhs) const final override
{
return ge_op1_op2_relation (lhs);
@ -590,29 +796,73 @@ class foperator_ge : public range_operator_float
relation_kind rel) const final override;
bool op2_range (frange &r, tree type,
const irange &lhs, const frange &op1,
relation_kind rel) const final override
{
return op1_range (r, type, lhs, op1, rel);
}
relation_kind rel) const final override;
} fop_ge;
bool
foperator_ge::fold_range (irange &r, tree type,
const frange &op1, const frange &op2,
relation_kind rel) const
{
if (frelop_early_resolve (r, type, op1, op2, rel, VREL_GE))
return true;
if (finite_operands_p (op1, op2))
{
if (real_compare (GE_EXPR, &op1.lower_bound (), &op2.upper_bound ()))
r = range_true (type);
else if (finite_operands_p (op1, op2)
&& !real_compare (GE_EXPR, &op1.upper_bound (), &op2.lower_bound ()))
r = range_false (type);
else
r = range_true_and_false (type);
}
else if (op1.get_nan ().yes_p () || op2.get_nan ().yes_p ())
r = range_false (type);
else
r = range_true_and_false (type);
return true;
}
bool
foperator_ge::op1_range (frange &r,
tree type,
const irange &lhs,
const frange &op2 ATTRIBUTE_UNUSED,
const frange &op2,
relation_kind) const
{
switch (get_bool_state (r, lhs, type))
{
case BRS_TRUE:
r.set_varying (type);
// The TRUE side of op1 >= op2 implies op1 is !NAN.
build_ge (r, type, op2.lower_bound ());
r.set_nan (fp_prop::NO);
break;
case BRS_FALSE:
r.set_varying (type);
build_lt (r, type, op2.upper_bound ());
break;
default:
break;
}
return true;
}
bool
foperator_ge::op2_range (frange &r, tree type,
const irange &lhs,
const frange &op1,
relation_kind) const
{
switch (get_bool_state (r, lhs, type))
{
case BRS_FALSE:
build_gt (r, type, op1.lower_bound ());
break;
case BRS_TRUE:
build_le (r, type, op1.upper_bound ());
r.set_nan (fp_prop::NO);
break;
default:

View file

@ -1,6 +1,11 @@
/* { dg-do compile } */
/* { dg-options "-O1 -fno-trapping-math -funsafe-math-optimizations -fdump-tree-recip" } */
/* The recip pass has a threshold of 3 reciprocal operations before it attempts
to optimize a sequence. With a FP enabled ranger, we eliminate one of them
earlier, causing the pass to skip this optimization. */
/* { dg-additional-options "-fno-thread-jumps -fno-tree-dominator-opts" } */
double F[5] = { 0.0, 0.0 }, e;
/* In this case the optimization is interesting. */