[RFA] Implement basic range operators to enable floating point VRP.

Message ID 20220725184951.2202379-1-aldyh@redhat.com
State New, archived
Headers
Series [RFA] Implement basic range operators to enable floating point VRP. |

Commit Message

Aldy Hernandez July 25, 2022, 6:49 p.m. UTC
  Without further ado, here is the implementation for floating point
range operators, plus the switch to enable all ranger clients to
handle floats.

These are bare bone implementations good enough for relation operators
to work, while keeping the NAN bits up to date in the frange.  There
is also minimal support for keeping track of +-INF when it is obvious.

I have included some basic tests to help get a feel of what is
ultimately handled.

Since range-ops is the domain specific core of ranger, I think its
best if a global maintainer or an FP expert could review this.

OK for trunk?

Tested on x86-64 Linux.

p.s. I haven't done extensive testing in DOM, but with this we're mighty
close for the forward threader there to be replaceable with the backward
threader, thus removing the last use of the forward threader.

gcc/ChangeLog:

	* range-op-float.cc (finite_operands_p): New.
	(frelop_early_resolve): New.
	(default_frelop_fold_range): New.
	(class foperator_equal): New.
	(class foperator_not_equal): New.
	(class foperator_lt): New.
	(class foperator_le): New.
	(class foperator_gt): New.
	(class foperator_ge): New.
	(class foperator_unordered): New.
	(class foperator_ordered): New.
	(class foperator_relop_unknown): New.
	(floating_op_table::floating_op_table): Add above classes to
	floating op table.
	* value-range.h (frange::supports_p): Enable.

gcc/testsuite/ChangeLog:

	* g++.dg/opt/pr94589-2.C: Add notes.
	* gcc.dg/tree-ssa/vrp-float-1.c: New test.
	* gcc.dg/tree-ssa/vrp-float-11.c: New test.
	* gcc.dg/tree-ssa/vrp-float-3.c: New test.
	* gcc.dg/tree-ssa/vrp-float-4.c: New test.
	* gcc.dg/tree-ssa/vrp-float-6.c: New test.
	* gcc.dg/tree-ssa/vrp-float-7.c: New test.
	* gcc.dg/tree-ssa/vrp-float-8.c: New test.
---
 gcc/range-op-float.cc                        | 564 +++++++++++++++++++
 gcc/testsuite/g++.dg/opt/pr94589-2.C         |  25 +
 gcc/testsuite/gcc.dg/tree-ssa/vrp-float-1.c  |  19 +
 gcc/testsuite/gcc.dg/tree-ssa/vrp-float-11.c |  26 +
 gcc/testsuite/gcc.dg/tree-ssa/vrp-float-3.c  |  18 +
 gcc/testsuite/gcc.dg/tree-ssa/vrp-float-4.c  |  16 +
 gcc/testsuite/gcc.dg/tree-ssa/vrp-float-6.c  |  20 +
 gcc/testsuite/gcc.dg/tree-ssa/vrp-float-7.c  |  14 +
 gcc/testsuite/gcc.dg/tree-ssa/vrp-float-8.c  |  26 +
 gcc/value-range.h                            |   3 +-
 10 files changed, 729 insertions(+), 2 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vrp-float-1.c
 create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vrp-float-11.c
 create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vrp-float-3.c
 create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vrp-float-4.c
 create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vrp-float-6.c
 create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vrp-float-7.c
 create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vrp-float-8.c
  

Comments

Aldy Hernandez July 25, 2022, 6:53 p.m. UTC | #1
I forgot to mention.  There's a regression in g++.dg/opt/pr94589-2.C,
where an early optimization by evrp causes phiopt to no longer
optimize a spaceship operator because it no longer sees the exact
sequence of conditionals.  I have included the analysis in the patch.
Hopefully a phiopt expert can opine.

Aldy

On Mon, Jul 25, 2022 at 8:50 PM Aldy Hernandez <aldyh@redhat.com> wrote:
>
> Without further ado, here is the implementation for floating point
> range operators, plus the switch to enable all ranger clients to
> handle floats.
>
> These are bare bone implementations good enough for relation operators
> to work, while keeping the NAN bits up to date in the frange.  There
> is also minimal support for keeping track of +-INF when it is obvious.
>
> I have included some basic tests to help get a feel of what is
> ultimately handled.
>
> Since range-ops is the domain specific core of ranger, I think its
> best if a global maintainer or an FP expert could review this.
>
> OK for trunk?
>
> Tested on x86-64 Linux.
>
> p.s. I haven't done extensive testing in DOM, but with this we're mighty
> close for the forward threader there to be replaceable with the backward
> threader, thus removing the last use of the forward threader.
>
> gcc/ChangeLog:
>
>         * range-op-float.cc (finite_operands_p): New.
>         (frelop_early_resolve): New.
>         (default_frelop_fold_range): New.
>         (class foperator_equal): New.
>         (class foperator_not_equal): New.
>         (class foperator_lt): New.
>         (class foperator_le): New.
>         (class foperator_gt): New.
>         (class foperator_ge): New.
>         (class foperator_unordered): New.
>         (class foperator_ordered): New.
>         (class foperator_relop_unknown): New.
>         (floating_op_table::floating_op_table): Add above classes to
>         floating op table.
>         * value-range.h (frange::supports_p): Enable.
>
> gcc/testsuite/ChangeLog:
>
>         * g++.dg/opt/pr94589-2.C: Add notes.
>         * gcc.dg/tree-ssa/vrp-float-1.c: New test.
>         * gcc.dg/tree-ssa/vrp-float-11.c: New test.
>         * gcc.dg/tree-ssa/vrp-float-3.c: New test.
>         * gcc.dg/tree-ssa/vrp-float-4.c: New test.
>         * gcc.dg/tree-ssa/vrp-float-6.c: New test.
>         * gcc.dg/tree-ssa/vrp-float-7.c: New test.
>         * gcc.dg/tree-ssa/vrp-float-8.c: New test.
> ---
>  gcc/range-op-float.cc                        | 564 +++++++++++++++++++
>  gcc/testsuite/g++.dg/opt/pr94589-2.C         |  25 +
>  gcc/testsuite/gcc.dg/tree-ssa/vrp-float-1.c  |  19 +
>  gcc/testsuite/gcc.dg/tree-ssa/vrp-float-11.c |  26 +
>  gcc/testsuite/gcc.dg/tree-ssa/vrp-float-3.c  |  18 +
>  gcc/testsuite/gcc.dg/tree-ssa/vrp-float-4.c  |  16 +
>  gcc/testsuite/gcc.dg/tree-ssa/vrp-float-6.c  |  20 +
>  gcc/testsuite/gcc.dg/tree-ssa/vrp-float-7.c  |  14 +
>  gcc/testsuite/gcc.dg/tree-ssa/vrp-float-8.c  |  26 +
>  gcc/value-range.h                            |   3 +-
>  10 files changed, 729 insertions(+), 2 deletions(-)
>  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vrp-float-1.c
>  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vrp-float-11.c
>  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vrp-float-3.c
>  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vrp-float-4.c
>  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vrp-float-6.c
>  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vrp-float-7.c
>  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vrp-float-8.c
>
> diff --git a/gcc/range-op-float.cc b/gcc/range-op-float.cc
> index 8e9d83e3827..d94ff6f915a 100644
> --- a/gcc/range-op-float.cc
> +++ b/gcc/range-op-float.cc
> @@ -150,6 +150,50 @@ range_operator_float::op1_op2_relation (const irange &lhs ATTRIBUTE_UNUSED) cons
>    return VREL_VARYING;
>  }
>
> +// Return TRUE if OP1 and OP2 are known to be free of NANs.
> +
> +static inline bool
> +finite_operands_p (const frange &op1, const frange &op2)
> +{
> +  return (flag_finite_math_only
> +         || (op1.get_nan ().no_p ()
> +             && op2.get_nan ().no_p ()));
> +}
> +
> +// Floating version of relop_early_resolve that takes into account NAN
> +// and -ffinite-math-only.
> +
> +inline bool
> +frelop_early_resolve (irange &r, tree type,
> +                     const frange &op1, const frange &op2,
> +                     relation_kind rel, relation_kind my_rel)
> +{
> +  // If either operand is undefined, return VARYING.
> +  if (empty_range_varying (r, type, op1, op2))
> +    return true;
> +
> +  // We can fold relations from the oracle when we know both operands
> +  // are free of NANs, or when -ffinite-math-only.
> +  return (finite_operands_p (op1, op2)
> +         && relop_early_resolve (r, type, op1, op2, rel, my_rel));
> +}
> +
> +// Default implementation of fold_range for relational operators.
> +// This amounts to passing on any known relations from the oracle, iff
> +// we know the operands are not NAN or -ffinite-math-only holds.
> +
> +static inline bool
> +default_frelop_fold_range (irange &r, tree type,
> +                         const frange &op1, const frange &op2,
> +                         relation_kind rel, relation_kind my_rel)
> +{
> +  if (frelop_early_resolve (r, type, op1, op2, rel, my_rel))
> +    return true;
> +
> +  r.set_varying (type);
> +  return true;
> +}
> +
>  class foperator_identity : public range_operator_float
>  {
>    using range_operator_float::fold_range;
> @@ -172,6 +216,509 @@ class foperator_identity : public range_operator_float
>  public:
>  } fop_identity;
>
> +class foperator_equal : public range_operator_float
> +{
> +  using range_operator_float::fold_range;
> +  using range_operator_float::op1_range;
> +  using range_operator_float::op2_range;
> +
> +  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 op1_op2_relation (const irange &lhs) const final override
> +  {
> +    return equal_op1_op2_relation (lhs);
> +  }
> +  bool op1_range (frange &r, tree type,
> +                 const irange &lhs, const frange &op2,
> +                 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);
> +  }
> +} fop_equal;
> +
> +bool
> +foperator_equal::op1_range (frange &r, tree type,
> +                           const irange &lhs,
> +                           const frange &op2 ATTRIBUTE_UNUSED,
> +                           relation_kind rel) 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.
> +      r.set_nan (fp_prop::NO);
> +      break;
> +
> +    case BRS_FALSE:
> +      r.set_varying (type);
> +      // The FALSE side of op1 ORDERED op1 implies op1 is a NAN.
> +      if (rel == VREL_EQ)
> +       r.set_nan (fp_prop::YES);
> +      break;
> +
> +    default:
> +      break;
> +    }
> +  return true;
> +}
> +
> +class foperator_not_equal : public range_operator_float
> +{
> +  using range_operator_float::fold_range;
> +  using range_operator_float::op1_range;
> +
> +  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 op1_op2_relation (const irange &lhs) const final override
> +  {
> +    return not_equal_op1_op2_relation (lhs);
> +  }
> +  bool op1_range (frange &r, tree type,
> +                 const irange &lhs, const frange &op2,
> +                 relation_kind rel) const final override;
> +} fop_not_equal;
> +
> +bool
> +foperator_not_equal::op1_range (frange &r, tree type,
> +                               const irange &lhs,
> +                               const frange &op2 ATTRIBUTE_UNUSED,
> +                               relation_kind) const
> +{
> +  switch (get_bool_state (r, lhs, type))
> +    {
> +    case BRS_TRUE:
> +      r.set_varying (type);
> +      break;
> +
> +    case BRS_FALSE:
> +      r.set_varying (type);
> +      // The FALSE side of op1 != op2 implies op1 is !NAN.
> +      r.set_nan (fp_prop::NO);
> +      break;
> +
> +    default:
> +      break;
> +    }
> +  return true;
> +}
> +
> +class foperator_lt : public range_operator_float
> +{
> +  using range_operator_float::fold_range;
> +  using range_operator_float::op1_range;
> +  using range_operator_float::op2_range;
> +
> +  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 op1_op2_relation (const irange &lhs) const final override
> +  {
> +    return lt_op1_op2_relation (lhs);
> +  }
> +  bool op1_range (frange &r, tree type,
> +                 const irange &lhs, const frange &op2,
> +                 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;
> +} fop_lt;
> +
> +bool
> +foperator_lt::op1_range (frange &r,
> +                        tree type,
> +                        const irange &lhs,
> +                        const frange &op2 ATTRIBUTE_UNUSED,
> +                        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 and !INF.
> +      r.set_nan (fp_prop::NO);
> +      r.set_inf (fp_prop::NO);
> +      break;
> +
> +    case BRS_FALSE:
> +      r.set_varying (type);
> +      break;
> +
> +    default:
> +      break;
> +    }
> +  return true;
> +}
> +
> +bool
> +foperator_lt::op2_range (frange &r,
> +                        tree type,
> +                        const irange &lhs,
> +                        const frange &op1 ATTRIBUTE_UNUSED,
> +                        relation_kind) const
> +{
> +  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.
> +      r.set_nan (fp_prop::NO);
> +      r.set_ninf (fp_prop::NO);
> +      break;
> +
> +    case BRS_FALSE:
> +      r.set_varying (type);
> +      break;
> +
> +    default:
> +      break;
> +    }
> +  return true;
> +}
> +
> +class foperator_le : public range_operator_float
> +{
> +  using range_operator_float::fold_range;
> +  using range_operator_float::op1_range;
> +  using range_operator_float::op2_range;
> +
> +  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 op1_op2_relation (const irange &lhs) const final override
> +  {
> +    return le_op1_op2_relation (lhs);
> +  }
> +  bool op1_range (frange &r, tree type,
> +                 const irange &lhs, const frange &op2,
> +                 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);
> +  }
> +} fop_le;
> +
> +bool
> +foperator_le::op1_range (frange &r,
> +                        tree type,
> +                        const irange &lhs,
> +                        const frange &op2 ATTRIBUTE_UNUSED,
> +                        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.
> +      r.set_nan (fp_prop::NO);
> +      break;
> +
> +    case BRS_FALSE:
> +      r.set_varying (type);
> +      break;
> +
> +    default:
> +      break;
> +    }
> +  return true;
> +}
> +
> +class foperator_gt : public range_operator_float
> +{
> +  using range_operator_float::fold_range;
> +  using range_operator_float::op1_range;
> +  using range_operator_float::op2_range;
> +
> +  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 op1_op2_relation (const irange &lhs) const final override
> +  {
> +    return gt_op1_op2_relation (lhs);
> +  }
> +  bool op1_range (frange &r, tree type,
> +                 const irange &lhs, const frange &op2,
> +                 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;
> +} fop_gt;
> +
> +bool
> +foperator_gt::op1_range (frange &r,
> +                        tree type,
> +                        const irange &lhs,
> +                        const frange &op2 ATTRIBUTE_UNUSED,
> +                        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 and !NINF.
> +      r.set_nan (fp_prop::NO);
> +      r.set_ninf (fp_prop::NO);
> +      break;
> +
> +    case BRS_FALSE:
> +      r.set_varying (type);
> +      break;
> +
> +    default:
> +      break;
> +    }
> +  return true;
> +}
> +
> +bool
> +foperator_gt::op2_range (frange &r,
> +                        tree type,
> +                        const irange &lhs,
> +                        const frange &op1 ATTRIBUTE_UNUSED,
> +                        relation_kind) const
> +{
> +  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.
> +      r.set_nan (fp_prop::NO);
> +      r.set_inf (fp_prop::NO);
> +      break;
> +
> +    case BRS_FALSE:
> +      r.set_varying (type);
> +      break;
> +
> +    default:
> +      break;
> +    }
> +  return true;
> +}
> +
> +class foperator_ge : public range_operator_float
> +{
> +  using range_operator_float::fold_range;
> +  using range_operator_float::op1_range;
> +  using range_operator_float::op2_range;
> +
> +  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 op1_op2_relation (const irange &lhs) const final override
> +  {
> +    return ge_op1_op2_relation (lhs);
> +  }
> +  bool op1_range (frange &r, tree type,
> +                 const irange &lhs, const frange &op2,
> +                 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);
> +  }
> +} fop_ge;
> +
> +bool
> +foperator_ge::op1_range (frange &r,
> +                        tree type,
> +                        const irange &lhs,
> +                        const frange &op2 ATTRIBUTE_UNUSED,
> +                        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.
> +      r.set_nan (fp_prop::NO);
> +      break;
> +
> +    case BRS_FALSE:
> +      r.set_varying (type);
> +      break;
> +
> +    default:
> +      break;
> +    }
> +  return true;
> +}
> +
> +// UNORDERED_EXPR comparison.
> +
> +class foperator_unordered : public range_operator_float
> +{
> +  using range_operator_float::fold_range;
> +  using range_operator_float::op1_range;
> +  using range_operator_float::op2_range;
> +
> +public:
> +  bool fold_range (irange &r, tree type,
> +                  const frange &op1, const frange &op2,
> +                  relation_kind rel) const final override;
> +  bool op1_range (frange &r, tree type,
> +                 const irange &lhs, const frange &op2,
> +                 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);
> +  }
> +} fop_unordered;
> +
> +bool
> +foperator_unordered::fold_range (irange &r, tree type,
> +                                const frange &op1, const frange &op2,
> +                                relation_kind) const
> +{
> +  // UNORDERED is TRUE if either operand is a NAN.
> +  if (op1.get_nan ().yes_p () || op2.get_nan ().yes_p ())
> +    r = range_true (type);
> +  // UNORDERED is FALSE if neither operand is a NAN.
> +  else if (op1.get_nan ().no_p () && op2.get_nan ().no_p ())
> +    r = range_false (type);
> +  else
> +    r = range_true_and_false (type);
> +  return true;
> +}
> +
> +bool
> +foperator_unordered::op1_range (frange &r, tree type,
> +                               const irange &lhs,
> +                               const frange &op2 ATTRIBUTE_UNUSED,
> +                               relation_kind) const
> +{
> +  switch (get_bool_state (r, lhs, type))
> +    {
> +    case BRS_TRUE:
> +      r.set_varying (type);
> +      // Since at least one operand must be NAN, if one of them is
> +      // not, the other must be.
> +      if (op2.get_nan ().no_p ())
> +       r.set_nan (fp_prop::YES);
> +      break;
> +
> +    case BRS_FALSE:
> +      r.set_varying (type);
> +      // A false UNORDERED means both operands are !NAN.
> +      r.set_nan (fp_prop::NO);
> +      break;
> +
> +    default:
> +      break;
> +    }
> +  return true;
> +}
> +
> +// ORDERED_EXPR comparison.
> +
> +class foperator_ordered : public range_operator_float
> +{
> +  using range_operator_float::fold_range;
> +  using range_operator_float::op1_range;
> +  using range_operator_float::op2_range;
> +
> +public:
> +  bool fold_range (irange &r, tree type,
> +                  const frange &op1, const frange &op2,
> +                  relation_kind rel) const final override;
> +  bool op1_range (frange &r, tree type,
> +                 const irange &lhs, const frange &op2,
> +                 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);
> +  }
> +} fop_ordered;
> +
> +bool
> +foperator_ordered::fold_range (irange &r, tree type,
> +                              const frange &op1, const frange &op2,
> +                              relation_kind) const
> +{
> +  // ORDERED is TRUE if neither operand is a NAN.
> +  if (op1.get_nan ().no_p () && op2.get_nan ().no_p ())
> +    r = range_true (type);
> +  // ORDERED is FALSE if either operand is a NAN.
> +  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_ordered::op1_range (frange &r, tree type,
> +                             const irange &lhs,
> +                             const frange &op2 ATTRIBUTE_UNUSED,
> +                             relation_kind rel) const
> +{
> +  switch (get_bool_state (r, lhs, type))
> +    {
> +    case BRS_TRUE:
> +      r.set_varying (type);
> +      // The TRUE side of op1 UNORDERED op2 implies op1 is !NAN.
> +      r.set_nan (fp_prop::NO);
> +      break;
> +
> +    case BRS_FALSE:
> +      r.set_varying (type);
> +      // The FALSE side of op1 UNORDERED op1 implies op1 is !NAN.
> +      if (rel == VREL_EQ)
> +       r.set_nan (fp_prop::NO);
> +      break;
> +
> +    default:
> +      break;
> +    }
> +  return true;
> +}
> +
> +// Placeholder for unimplemented relational operators.
> +
> +class foperator_relop_unknown : public range_operator_float
> +{
> +  using range_operator_float::fold_range;
> +
> +public:
> +  bool fold_range (irange &r, tree type,
> +                  const frange &, const frange &,
> +                  relation_kind) const final override
> +  {
> +    r.set_varying (type);
> +    return true;
> +  }
> +} fop_relop_unknown;
> +
>
>  // Instantiate a range_op_table for floating point operations.
>  static floating_op_table global_floating_table;
> @@ -185,6 +732,23 @@ floating_op_table::floating_op_table ()
>    set (PAREN_EXPR, fop_identity);
>    set (OBJ_TYPE_REF, fop_identity);
>    set (REAL_CST, fop_identity);
> +
> +  // All the relational operators are expected to work, because the
> +  // calculation of ranges on outgoing edges expect the handlers to be
> +  // present.
> +  set (EQ_EXPR, fop_equal);
> +  set (NE_EXPR, fop_not_equal);
> +  set (LT_EXPR, fop_lt);
> +  set (LE_EXPR, fop_le);
> +  set (GT_EXPR, fop_gt);
> +  set (GE_EXPR, fop_ge);
> +  set (UNLE_EXPR, fop_relop_unknown);
> +  set (UNLT_EXPR, fop_relop_unknown);
> +  set (UNGE_EXPR, fop_relop_unknown);
> +  set (UNGT_EXPR, fop_relop_unknown);
> +  set (UNEQ_EXPR, fop_relop_unknown);
> +  set (ORDERED_EXPR, fop_ordered);
> +  set (UNORDERED_EXPR, fop_unordered);
>  }
>
>  // Return a pointer to the range_operator_float instance, if there is
> diff --git a/gcc/testsuite/g++.dg/opt/pr94589-2.C b/gcc/testsuite/g++.dg/opt/pr94589-2.C
> index e9ef84b1912..1caa725061e 100644
> --- a/gcc/testsuite/g++.dg/opt/pr94589-2.C
> +++ b/gcc/testsuite/g++.dg/opt/pr94589-2.C
> @@ -4,6 +4,31 @@
>  // { dg-final { scan-tree-dump-times "\[ij]_\[0-9]+\\(D\\) (?:<|<=|==|!=|>|>=) \[ij]_\[0-9]+\\(D\\)" 12 "optimized" } }
>  // { dg-final { scan-tree-dump-times "i_\[0-9]+\\(D\\) (?:<|<=|==|!=|>|>=) 5\\.0" 12 "optimized" } }
>
> +/* This is failing on f5() because the spaceship operator is no longer
> +   folded.  What happens is that evrp folds away the final condition
> +   here as always true.
> +
> +  <bb 2> :
> +  if (i_2(D) != j_4(D))
> +    goto <bb 3>; [INV]
> +  else
> +    goto <bb 6>; [INV]
> +
> +  <bb 3> :
> +  if (i_2(D) >= j_4(D))
> +    goto <bb 4>; [INV]
> +  else
> +    goto <bb 6>; [INV]
> +
> +  <bb 4> :
> +  if (i_2(D) > j_4(D))   <<== ALWAYS TRUE
> +    goto <bb 6>; [INV]
> +  else
> +    goto <bb 5>; [INV]
> +
> +  This causes phiopt to no longer be able to fold the total sequence
> +  into i_2 >= j_4.  */
> +
>  #include <compare>
>
>  #define A __attribute__((noipa))
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-1.c b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-1.c
> new file mode 100644
> index 00000000000..88faf72ac42
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-1.c
> @@ -0,0 +1,19 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -fdisable-tree-ethread -fdisable-tree-fre1 -fdump-tree-evrp" }
> +
> +void bar ();
> +void george ();
> +
> +float
> +foo (float x, float y)
> +{
> +  if (x == x)
> +    {
> +      if (x > y)
> +        bar();
> +      if (x == x)
> +        george();
> +    }
> +}
> +
> +// { dg-final { scan-tree-dump-times "Folding predicate x_*to 1" "evrp" 1 } }
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-11.c b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-11.c
> new file mode 100644
> index 00000000000..2f4dc8757a3
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-11.c
> @@ -0,0 +1,26 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -fno-thread-jumps -fdump-tree-evrp" }
> +
> +extern void link_error ();
> +
> +void fast_sqrt (float);
> +
> +float test (float x)
> +{
> +  float y = x*x;
> +  if (y >= 0.f)
> +    {
> +      if (__builtin_isnan (y))
> +       link_error ();
> +      else
> +       fast_sqrt (y);
> +
> +      if (!__builtin_isnan (y))
> +       fast_sqrt (y);
> +      else
> +       link_error ();
> +    }
> +}
> +
> +// { dg-final { scan-tree-dump-times "fast_sqrt" 2 "evrp" } }
> +// { dg-final { scan-tree-dump-not "link_error" "evrp" } }
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-3.c b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-3.c
> new file mode 100644
> index 00000000000..c659abb6cc0
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-3.c
> @@ -0,0 +1,18 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -fdisable-tree-ethread -fdump-tree-evrp" }
> +
> +void link_error ();
> +
> +void
> +foo (double x, double y)
> +{
> +  if (x == y)
> +    {
> +      if (__builtin_isnan (x))
> +        link_error ();
> +      if (__builtin_isnan (y))
> +        link_error ();
> +    }
> +}
> +
> +// { dg-final { scan-tree-dump-not "link_error" "evrp" } }
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-4.c b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-4.c
> new file mode 100644
> index 00000000000..86436742e0a
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-4.c
> @@ -0,0 +1,16 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -fdisable-tree-ethread -fdump-tree-evrp" }
> +
> +void link_error ();
> +
> +void
> +foo (double x, double y)
> +{
> +  if (x > y)
> +    {
> +      if (__builtin_isnan (x) || __builtin_isnan (y))
> +        link_error ();
> +    }
> +}
> +
> +// { dg-final { scan-tree-dump-not "link_error" "evrp" } }
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-6.c b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-6.c
> new file mode 100644
> index 00000000000..145d1861804
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-6.c
> @@ -0,0 +1,20 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -fdisable-tree-ethread -fdump-tree-evrp" }
> +
> +void bar ();
> +
> +void
> +foo (double x, double y)
> +{
> +      if (x > y)
> +       ;
> +      else if (!__builtin_isnan (x) && !__builtin_isnan (y))
> +       {
> +         // If x and y are not NAN, the x <= y relationship holds, and the
> +         // following conditional can be folded away.
> +         if (x <= y)
> +           bar ();
> +       }
> +}
> +
> +// { dg-final { scan-tree-dump-times "Folding predicate x_.* <= y_.* to 1" 1 "evrp" } }
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-7.c b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-7.c
> new file mode 100644
> index 00000000000..92af87091a8
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-7.c
> @@ -0,0 +1,14 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -fno-tree-forwprop -fno-tree-ccp -fno-tree-fre -fdump-tree-evrp" }
> +
> +extern void link_error ();
> +
> +void
> +foo ()
> +{
> +  float z = 0.0;
> +  if (__builtin_isnan (z))
> +    link_error ();
> +}
> +
> +// { dg-final { scan-tree-dump-not "link_error" "evrp" } }
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-8.c b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-8.c
> new file mode 100644
> index 00000000000..9170150d453
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-8.c
> @@ -0,0 +1,26 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -fno-thread-jumps -fdump-tree-evrp" }
> +
> +extern void link_error ();
> +
> +void fast_sqrt (float);
> +
> +float test (float x)
> +{
> +    float y = x*x;
> +    if (y >= 0.f)
> +      {
> +        if (__builtin_isnan (y))
> +         link_error ();
> +        else
> +          fast_sqrt (y);
> +
> +       if (!__builtin_isnan (y))
> +         fast_sqrt (y);
> +       else
> +         link_error ();
> +      }
> +}
> +
> +// { dg-final { scan-tree-dump-times "fast_sqrt" 2 "evrp" } }
> +// { dg-final { scan-tree-dump-not "link_error" "evrp" } }
> diff --git a/gcc/value-range.h b/gcc/value-range.h
> index e43fbe30f27..478f165e02b 100644
> --- a/gcc/value-range.h
> +++ b/gcc/value-range.h
> @@ -338,8 +338,7 @@ public:
>    frange (const frange &);
>    static bool supports_p (tree type)
>    {
> -    // Disabled until floating point range-ops come live.
> -    return 0 && SCALAR_FLOAT_TYPE_P (type);
> +    return SCALAR_FLOAT_TYPE_P (type);
>    }
>    virtual tree type () const override;
>    virtual void set (tree, tree, value_range_kind = VR_RANGE) override;
> --
> 2.36.1
>
  
Aldy Hernandez July 31, 2022, 6:11 p.m. UTC | #2
PING

Andrew, anyone, would you mind giving this a once over?  I realize
reviewing ranger's range-op code is not on anyone's list of
priorities, but I could use a sanity check.

The patch is sufficiently self-contained to easily catch anything
caused by it, and I'd like to commit earlier in the week to have
enough time to field any possible fallout before I take a few days off
next week.

Updated patch attached.

Thanks.
Aldy

On Mon, Jul 25, 2022 at 8:50 PM Aldy Hernandez <aldyh@redhat.com> wrote:
>
> Without further ado, here is the implementation for floating point
> range operators, plus the switch to enable all ranger clients to
> handle floats.
>
> These are bare bone implementations good enough for relation operators
> to work, while keeping the NAN bits up to date in the frange.  There
> is also minimal support for keeping track of +-INF when it is obvious.
>
> I have included some basic tests to help get a feel of what is
> ultimately handled.
>
> Since range-ops is the domain specific core of ranger, I think its
> best if a global maintainer or an FP expert could review this.
>
> OK for trunk?
>
> Tested on x86-64 Linux.
>
> p.s. I haven't done extensive testing in DOM, but with this we're mighty
> close for the forward threader there to be replaceable with the backward
> threader, thus removing the last use of the forward threader.
>
> gcc/ChangeLog:
>
>         * range-op-float.cc (finite_operands_p): New.
>         (frelop_early_resolve): New.
>         (default_frelop_fold_range): New.
>         (class foperator_equal): New.
>         (class foperator_not_equal): New.
>         (class foperator_lt): New.
>         (class foperator_le): New.
>         (class foperator_gt): New.
>         (class foperator_ge): New.
>         (class foperator_unordered): New.
>         (class foperator_ordered): New.
>         (class foperator_relop_unknown): New.
>         (floating_op_table::floating_op_table): Add above classes to
>         floating op table.
>         * value-range.h (frange::supports_p): Enable.
>
> gcc/testsuite/ChangeLog:
>
>         * g++.dg/opt/pr94589-2.C: Add notes.
>         * gcc.dg/tree-ssa/vrp-float-1.c: New test.
>         * gcc.dg/tree-ssa/vrp-float-11.c: New test.
>         * gcc.dg/tree-ssa/vrp-float-3.c: New test.
>         * gcc.dg/tree-ssa/vrp-float-4.c: New test.
>         * gcc.dg/tree-ssa/vrp-float-6.c: New test.
>         * gcc.dg/tree-ssa/vrp-float-7.c: New test.
>         * gcc.dg/tree-ssa/vrp-float-8.c: New test.
> ---
>  gcc/range-op-float.cc                        | 564 +++++++++++++++++++
>  gcc/testsuite/g++.dg/opt/pr94589-2.C         |  25 +
>  gcc/testsuite/gcc.dg/tree-ssa/vrp-float-1.c  |  19 +
>  gcc/testsuite/gcc.dg/tree-ssa/vrp-float-11.c |  26 +
>  gcc/testsuite/gcc.dg/tree-ssa/vrp-float-3.c  |  18 +
>  gcc/testsuite/gcc.dg/tree-ssa/vrp-float-4.c  |  16 +
>  gcc/testsuite/gcc.dg/tree-ssa/vrp-float-6.c  |  20 +
>  gcc/testsuite/gcc.dg/tree-ssa/vrp-float-7.c  |  14 +
>  gcc/testsuite/gcc.dg/tree-ssa/vrp-float-8.c  |  26 +
>  gcc/value-range.h                            |   3 +-
>  10 files changed, 729 insertions(+), 2 deletions(-)
>  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vrp-float-1.c
>  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vrp-float-11.c
>  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vrp-float-3.c
>  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vrp-float-4.c
>  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vrp-float-6.c
>  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vrp-float-7.c
>  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vrp-float-8.c
>
> diff --git a/gcc/range-op-float.cc b/gcc/range-op-float.cc
> index 8e9d83e3827..d94ff6f915a 100644
> --- a/gcc/range-op-float.cc
> +++ b/gcc/range-op-float.cc
> @@ -150,6 +150,50 @@ range_operator_float::op1_op2_relation (const irange &lhs ATTRIBUTE_UNUSED) cons
>    return VREL_VARYING;
>  }
>
> +// Return TRUE if OP1 and OP2 are known to be free of NANs.
> +
> +static inline bool
> +finite_operands_p (const frange &op1, const frange &op2)
> +{
> +  return (flag_finite_math_only
> +         || (op1.get_nan ().no_p ()
> +             && op2.get_nan ().no_p ()));
> +}
> +
> +// Floating version of relop_early_resolve that takes into account NAN
> +// and -ffinite-math-only.
> +
> +inline bool
> +frelop_early_resolve (irange &r, tree type,
> +                     const frange &op1, const frange &op2,
> +                     relation_kind rel, relation_kind my_rel)
> +{
> +  // If either operand is undefined, return VARYING.
> +  if (empty_range_varying (r, type, op1, op2))
> +    return true;
> +
> +  // We can fold relations from the oracle when we know both operands
> +  // are free of NANs, or when -ffinite-math-only.
> +  return (finite_operands_p (op1, op2)
> +         && relop_early_resolve (r, type, op1, op2, rel, my_rel));
> +}
> +
> +// Default implementation of fold_range for relational operators.
> +// This amounts to passing on any known relations from the oracle, iff
> +// we know the operands are not NAN or -ffinite-math-only holds.
> +
> +static inline bool
> +default_frelop_fold_range (irange &r, tree type,
> +                         const frange &op1, const frange &op2,
> +                         relation_kind rel, relation_kind my_rel)
> +{
> +  if (frelop_early_resolve (r, type, op1, op2, rel, my_rel))
> +    return true;
> +
> +  r.set_varying (type);
> +  return true;
> +}
> +
>  class foperator_identity : public range_operator_float
>  {
>    using range_operator_float::fold_range;
> @@ -172,6 +216,509 @@ class foperator_identity : public range_operator_float
>  public:
>  } fop_identity;
>
> +class foperator_equal : public range_operator_float
> +{
> +  using range_operator_float::fold_range;
> +  using range_operator_float::op1_range;
> +  using range_operator_float::op2_range;
> +
> +  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 op1_op2_relation (const irange &lhs) const final override
> +  {
> +    return equal_op1_op2_relation (lhs);
> +  }
> +  bool op1_range (frange &r, tree type,
> +                 const irange &lhs, const frange &op2,
> +                 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);
> +  }
> +} fop_equal;
> +
> +bool
> +foperator_equal::op1_range (frange &r, tree type,
> +                           const irange &lhs,
> +                           const frange &op2 ATTRIBUTE_UNUSED,
> +                           relation_kind rel) 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.
> +      r.set_nan (fp_prop::NO);
> +      break;
> +
> +    case BRS_FALSE:
> +      r.set_varying (type);
> +      // The FALSE side of op1 ORDERED op1 implies op1 is a NAN.
> +      if (rel == VREL_EQ)
> +       r.set_nan (fp_prop::YES);
> +      break;
> +
> +    default:
> +      break;
> +    }
> +  return true;
> +}
> +
> +class foperator_not_equal : public range_operator_float
> +{
> +  using range_operator_float::fold_range;
> +  using range_operator_float::op1_range;
> +
> +  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 op1_op2_relation (const irange &lhs) const final override
> +  {
> +    return not_equal_op1_op2_relation (lhs);
> +  }
> +  bool op1_range (frange &r, tree type,
> +                 const irange &lhs, const frange &op2,
> +                 relation_kind rel) const final override;
> +} fop_not_equal;
> +
> +bool
> +foperator_not_equal::op1_range (frange &r, tree type,
> +                               const irange &lhs,
> +                               const frange &op2 ATTRIBUTE_UNUSED,
> +                               relation_kind) const
> +{
> +  switch (get_bool_state (r, lhs, type))
> +    {
> +    case BRS_TRUE:
> +      r.set_varying (type);
> +      break;
> +
> +    case BRS_FALSE:
> +      r.set_varying (type);
> +      // The FALSE side of op1 != op2 implies op1 is !NAN.
> +      r.set_nan (fp_prop::NO);
> +      break;
> +
> +    default:
> +      break;
> +    }
> +  return true;
> +}
> +
> +class foperator_lt : public range_operator_float
> +{
> +  using range_operator_float::fold_range;
> +  using range_operator_float::op1_range;
> +  using range_operator_float::op2_range;
> +
> +  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 op1_op2_relation (const irange &lhs) const final override
> +  {
> +    return lt_op1_op2_relation (lhs);
> +  }
> +  bool op1_range (frange &r, tree type,
> +                 const irange &lhs, const frange &op2,
> +                 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;
> +} fop_lt;
> +
> +bool
> +foperator_lt::op1_range (frange &r,
> +                        tree type,
> +                        const irange &lhs,
> +                        const frange &op2 ATTRIBUTE_UNUSED,
> +                        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 and !INF.
> +      r.set_nan (fp_prop::NO);
> +      r.set_inf (fp_prop::NO);
> +      break;
> +
> +    case BRS_FALSE:
> +      r.set_varying (type);
> +      break;
> +
> +    default:
> +      break;
> +    }
> +  return true;
> +}
> +
> +bool
> +foperator_lt::op2_range (frange &r,
> +                        tree type,
> +                        const irange &lhs,
> +                        const frange &op1 ATTRIBUTE_UNUSED,
> +                        relation_kind) const
> +{
> +  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.
> +      r.set_nan (fp_prop::NO);
> +      r.set_ninf (fp_prop::NO);
> +      break;
> +
> +    case BRS_FALSE:
> +      r.set_varying (type);
> +      break;
> +
> +    default:
> +      break;
> +    }
> +  return true;
> +}
> +
> +class foperator_le : public range_operator_float
> +{
> +  using range_operator_float::fold_range;
> +  using range_operator_float::op1_range;
> +  using range_operator_float::op2_range;
> +
> +  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 op1_op2_relation (const irange &lhs) const final override
> +  {
> +    return le_op1_op2_relation (lhs);
> +  }
> +  bool op1_range (frange &r, tree type,
> +                 const irange &lhs, const frange &op2,
> +                 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);
> +  }
> +} fop_le;
> +
> +bool
> +foperator_le::op1_range (frange &r,
> +                        tree type,
> +                        const irange &lhs,
> +                        const frange &op2 ATTRIBUTE_UNUSED,
> +                        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.
> +      r.set_nan (fp_prop::NO);
> +      break;
> +
> +    case BRS_FALSE:
> +      r.set_varying (type);
> +      break;
> +
> +    default:
> +      break;
> +    }
> +  return true;
> +}
> +
> +class foperator_gt : public range_operator_float
> +{
> +  using range_operator_float::fold_range;
> +  using range_operator_float::op1_range;
> +  using range_operator_float::op2_range;
> +
> +  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 op1_op2_relation (const irange &lhs) const final override
> +  {
> +    return gt_op1_op2_relation (lhs);
> +  }
> +  bool op1_range (frange &r, tree type,
> +                 const irange &lhs, const frange &op2,
> +                 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;
> +} fop_gt;
> +
> +bool
> +foperator_gt::op1_range (frange &r,
> +                        tree type,
> +                        const irange &lhs,
> +                        const frange &op2 ATTRIBUTE_UNUSED,
> +                        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 and !NINF.
> +      r.set_nan (fp_prop::NO);
> +      r.set_ninf (fp_prop::NO);
> +      break;
> +
> +    case BRS_FALSE:
> +      r.set_varying (type);
> +      break;
> +
> +    default:
> +      break;
> +    }
> +  return true;
> +}
> +
> +bool
> +foperator_gt::op2_range (frange &r,
> +                        tree type,
> +                        const irange &lhs,
> +                        const frange &op1 ATTRIBUTE_UNUSED,
> +                        relation_kind) const
> +{
> +  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.
> +      r.set_nan (fp_prop::NO);
> +      r.set_inf (fp_prop::NO);
> +      break;
> +
> +    case BRS_FALSE:
> +      r.set_varying (type);
> +      break;
> +
> +    default:
> +      break;
> +    }
> +  return true;
> +}
> +
> +class foperator_ge : public range_operator_float
> +{
> +  using range_operator_float::fold_range;
> +  using range_operator_float::op1_range;
> +  using range_operator_float::op2_range;
> +
> +  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 op1_op2_relation (const irange &lhs) const final override
> +  {
> +    return ge_op1_op2_relation (lhs);
> +  }
> +  bool op1_range (frange &r, tree type,
> +                 const irange &lhs, const frange &op2,
> +                 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);
> +  }
> +} fop_ge;
> +
> +bool
> +foperator_ge::op1_range (frange &r,
> +                        tree type,
> +                        const irange &lhs,
> +                        const frange &op2 ATTRIBUTE_UNUSED,
> +                        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.
> +      r.set_nan (fp_prop::NO);
> +      break;
> +
> +    case BRS_FALSE:
> +      r.set_varying (type);
> +      break;
> +
> +    default:
> +      break;
> +    }
> +  return true;
> +}
> +
> +// UNORDERED_EXPR comparison.
> +
> +class foperator_unordered : public range_operator_float
> +{
> +  using range_operator_float::fold_range;
> +  using range_operator_float::op1_range;
> +  using range_operator_float::op2_range;
> +
> +public:
> +  bool fold_range (irange &r, tree type,
> +                  const frange &op1, const frange &op2,
> +                  relation_kind rel) const final override;
> +  bool op1_range (frange &r, tree type,
> +                 const irange &lhs, const frange &op2,
> +                 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);
> +  }
> +} fop_unordered;
> +
> +bool
> +foperator_unordered::fold_range (irange &r, tree type,
> +                                const frange &op1, const frange &op2,
> +                                relation_kind) const
> +{
> +  // UNORDERED is TRUE if either operand is a NAN.
> +  if (op1.get_nan ().yes_p () || op2.get_nan ().yes_p ())
> +    r = range_true (type);
> +  // UNORDERED is FALSE if neither operand is a NAN.
> +  else if (op1.get_nan ().no_p () && op2.get_nan ().no_p ())
> +    r = range_false (type);
> +  else
> +    r = range_true_and_false (type);
> +  return true;
> +}
> +
> +bool
> +foperator_unordered::op1_range (frange &r, tree type,
> +                               const irange &lhs,
> +                               const frange &op2 ATTRIBUTE_UNUSED,
> +                               relation_kind) const
> +{
> +  switch (get_bool_state (r, lhs, type))
> +    {
> +    case BRS_TRUE:
> +      r.set_varying (type);
> +      // Since at least one operand must be NAN, if one of them is
> +      // not, the other must be.
> +      if (op2.get_nan ().no_p ())
> +       r.set_nan (fp_prop::YES);
> +      break;
> +
> +    case BRS_FALSE:
> +      r.set_varying (type);
> +      // A false UNORDERED means both operands are !NAN.
> +      r.set_nan (fp_prop::NO);
> +      break;
> +
> +    default:
> +      break;
> +    }
> +  return true;
> +}
> +
> +// ORDERED_EXPR comparison.
> +
> +class foperator_ordered : public range_operator_float
> +{
> +  using range_operator_float::fold_range;
> +  using range_operator_float::op1_range;
> +  using range_operator_float::op2_range;
> +
> +public:
> +  bool fold_range (irange &r, tree type,
> +                  const frange &op1, const frange &op2,
> +                  relation_kind rel) const final override;
> +  bool op1_range (frange &r, tree type,
> +                 const irange &lhs, const frange &op2,
> +                 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);
> +  }
> +} fop_ordered;
> +
> +bool
> +foperator_ordered::fold_range (irange &r, tree type,
> +                              const frange &op1, const frange &op2,
> +                              relation_kind) const
> +{
> +  // ORDERED is TRUE if neither operand is a NAN.
> +  if (op1.get_nan ().no_p () && op2.get_nan ().no_p ())
> +    r = range_true (type);
> +  // ORDERED is FALSE if either operand is a NAN.
> +  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_ordered::op1_range (frange &r, tree type,
> +                             const irange &lhs,
> +                             const frange &op2 ATTRIBUTE_UNUSED,
> +                             relation_kind rel) const
> +{
> +  switch (get_bool_state (r, lhs, type))
> +    {
> +    case BRS_TRUE:
> +      r.set_varying (type);
> +      // The TRUE side of op1 UNORDERED op2 implies op1 is !NAN.
> +      r.set_nan (fp_prop::NO);
> +      break;
> +
> +    case BRS_FALSE:
> +      r.set_varying (type);
> +      // The FALSE side of op1 UNORDERED op1 implies op1 is !NAN.
> +      if (rel == VREL_EQ)
> +       r.set_nan (fp_prop::NO);
> +      break;
> +
> +    default:
> +      break;
> +    }
> +  return true;
> +}
> +
> +// Placeholder for unimplemented relational operators.
> +
> +class foperator_relop_unknown : public range_operator_float
> +{
> +  using range_operator_float::fold_range;
> +
> +public:
> +  bool fold_range (irange &r, tree type,
> +                  const frange &, const frange &,
> +                  relation_kind) const final override
> +  {
> +    r.set_varying (type);
> +    return true;
> +  }
> +} fop_relop_unknown;
> +
>
>  // Instantiate a range_op_table for floating point operations.
>  static floating_op_table global_floating_table;
> @@ -185,6 +732,23 @@ floating_op_table::floating_op_table ()
>    set (PAREN_EXPR, fop_identity);
>    set (OBJ_TYPE_REF, fop_identity);
>    set (REAL_CST, fop_identity);
> +
> +  // All the relational operators are expected to work, because the
> +  // calculation of ranges on outgoing edges expect the handlers to be
> +  // present.
> +  set (EQ_EXPR, fop_equal);
> +  set (NE_EXPR, fop_not_equal);
> +  set (LT_EXPR, fop_lt);
> +  set (LE_EXPR, fop_le);
> +  set (GT_EXPR, fop_gt);
> +  set (GE_EXPR, fop_ge);
> +  set (UNLE_EXPR, fop_relop_unknown);
> +  set (UNLT_EXPR, fop_relop_unknown);
> +  set (UNGE_EXPR, fop_relop_unknown);
> +  set (UNGT_EXPR, fop_relop_unknown);
> +  set (UNEQ_EXPR, fop_relop_unknown);
> +  set (ORDERED_EXPR, fop_ordered);
> +  set (UNORDERED_EXPR, fop_unordered);
>  }
>
>  // Return a pointer to the range_operator_float instance, if there is
> diff --git a/gcc/testsuite/g++.dg/opt/pr94589-2.C b/gcc/testsuite/g++.dg/opt/pr94589-2.C
> index e9ef84b1912..1caa725061e 100644
> --- a/gcc/testsuite/g++.dg/opt/pr94589-2.C
> +++ b/gcc/testsuite/g++.dg/opt/pr94589-2.C
> @@ -4,6 +4,31 @@
>  // { dg-final { scan-tree-dump-times "\[ij]_\[0-9]+\\(D\\) (?:<|<=|==|!=|>|>=) \[ij]_\[0-9]+\\(D\\)" 12 "optimized" } }
>  // { dg-final { scan-tree-dump-times "i_\[0-9]+\\(D\\) (?:<|<=|==|!=|>|>=) 5\\.0" 12 "optimized" } }
>
> +/* This is failing on f5() because the spaceship operator is no longer
> +   folded.  What happens is that evrp folds away the final condition
> +   here as always true.
> +
> +  <bb 2> :
> +  if (i_2(D) != j_4(D))
> +    goto <bb 3>; [INV]
> +  else
> +    goto <bb 6>; [INV]
> +
> +  <bb 3> :
> +  if (i_2(D) >= j_4(D))
> +    goto <bb 4>; [INV]
> +  else
> +    goto <bb 6>; [INV]
> +
> +  <bb 4> :
> +  if (i_2(D) > j_4(D))   <<== ALWAYS TRUE
> +    goto <bb 6>; [INV]
> +  else
> +    goto <bb 5>; [INV]
> +
> +  This causes phiopt to no longer be able to fold the total sequence
> +  into i_2 >= j_4.  */
> +
>  #include <compare>
>
>  #define A __attribute__((noipa))
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-1.c b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-1.c
> new file mode 100644
> index 00000000000..88faf72ac42
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-1.c
> @@ -0,0 +1,19 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -fdisable-tree-ethread -fdisable-tree-fre1 -fdump-tree-evrp" }
> +
> +void bar ();
> +void george ();
> +
> +float
> +foo (float x, float y)
> +{
> +  if (x == x)
> +    {
> +      if (x > y)
> +        bar();
> +      if (x == x)
> +        george();
> +    }
> +}
> +
> +// { dg-final { scan-tree-dump-times "Folding predicate x_*to 1" "evrp" 1 } }
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-11.c b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-11.c
> new file mode 100644
> index 00000000000..2f4dc8757a3
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-11.c
> @@ -0,0 +1,26 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -fno-thread-jumps -fdump-tree-evrp" }
> +
> +extern void link_error ();
> +
> +void fast_sqrt (float);
> +
> +float test (float x)
> +{
> +  float y = x*x;
> +  if (y >= 0.f)
> +    {
> +      if (__builtin_isnan (y))
> +       link_error ();
> +      else
> +       fast_sqrt (y);
> +
> +      if (!__builtin_isnan (y))
> +       fast_sqrt (y);
> +      else
> +       link_error ();
> +    }
> +}
> +
> +// { dg-final { scan-tree-dump-times "fast_sqrt" 2 "evrp" } }
> +// { dg-final { scan-tree-dump-not "link_error" "evrp" } }
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-3.c b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-3.c
> new file mode 100644
> index 00000000000..c659abb6cc0
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-3.c
> @@ -0,0 +1,18 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -fdisable-tree-ethread -fdump-tree-evrp" }
> +
> +void link_error ();
> +
> +void
> +foo (double x, double y)
> +{
> +  if (x == y)
> +    {
> +      if (__builtin_isnan (x))
> +        link_error ();
> +      if (__builtin_isnan (y))
> +        link_error ();
> +    }
> +}
> +
> +// { dg-final { scan-tree-dump-not "link_error" "evrp" } }
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-4.c b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-4.c
> new file mode 100644
> index 00000000000..86436742e0a
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-4.c
> @@ -0,0 +1,16 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -fdisable-tree-ethread -fdump-tree-evrp" }
> +
> +void link_error ();
> +
> +void
> +foo (double x, double y)
> +{
> +  if (x > y)
> +    {
> +      if (__builtin_isnan (x) || __builtin_isnan (y))
> +        link_error ();
> +    }
> +}
> +
> +// { dg-final { scan-tree-dump-not "link_error" "evrp" } }
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-6.c b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-6.c
> new file mode 100644
> index 00000000000..145d1861804
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-6.c
> @@ -0,0 +1,20 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -fdisable-tree-ethread -fdump-tree-evrp" }
> +
> +void bar ();
> +
> +void
> +foo (double x, double y)
> +{
> +      if (x > y)
> +       ;
> +      else if (!__builtin_isnan (x) && !__builtin_isnan (y))
> +       {
> +         // If x and y are not NAN, the x <= y relationship holds, and the
> +         // following conditional can be folded away.
> +         if (x <= y)
> +           bar ();
> +       }
> +}
> +
> +// { dg-final { scan-tree-dump-times "Folding predicate x_.* <= y_.* to 1" 1 "evrp" } }
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-7.c b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-7.c
> new file mode 100644
> index 00000000000..92af87091a8
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-7.c
> @@ -0,0 +1,14 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -fno-tree-forwprop -fno-tree-ccp -fno-tree-fre -fdump-tree-evrp" }
> +
> +extern void link_error ();
> +
> +void
> +foo ()
> +{
> +  float z = 0.0;
> +  if (__builtin_isnan (z))
> +    link_error ();
> +}
> +
> +// { dg-final { scan-tree-dump-not "link_error" "evrp" } }
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-8.c b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-8.c
> new file mode 100644
> index 00000000000..9170150d453
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-8.c
> @@ -0,0 +1,26 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -fno-thread-jumps -fdump-tree-evrp" }
> +
> +extern void link_error ();
> +
> +void fast_sqrt (float);
> +
> +float test (float x)
> +{
> +    float y = x*x;
> +    if (y >= 0.f)
> +      {
> +        if (__builtin_isnan (y))
> +         link_error ();
> +        else
> +          fast_sqrt (y);
> +
> +       if (!__builtin_isnan (y))
> +         fast_sqrt (y);
> +       else
> +         link_error ();
> +      }
> +}
> +
> +// { dg-final { scan-tree-dump-times "fast_sqrt" 2 "evrp" } }
> +// { dg-final { scan-tree-dump-not "link_error" "evrp" } }
> diff --git a/gcc/value-range.h b/gcc/value-range.h
> index e43fbe30f27..478f165e02b 100644
> --- a/gcc/value-range.h
> +++ b/gcc/value-range.h
> @@ -338,8 +338,7 @@ public:
>    frange (const frange &);
>    static bool supports_p (tree type)
>    {
> -    // Disabled until floating point range-ops come live.
> -    return 0 && SCALAR_FLOAT_TYPE_P (type);
> +    return SCALAR_FLOAT_TYPE_P (type);
>    }
>    virtual tree type () const override;
>    virtual void set (tree, tree, value_range_kind = VR_RANGE) override;
> --
> 2.36.1
>
  
Aldy Hernandez Aug. 2, 2022, 12:57 p.m. UTC | #3
Alright, I know everyon'es pretty busy and I am blocking myself here,
so I hereby approve my own patch, since I am the maintainer ;-).

After all, I'd like to have 5-6 days to field any possible fallout
before I disappear off to a tropical island to drink mojitos and beer.
Ok, not really-- just changing diapers, but without a laptop.

Feedback still welcome; I'll just address it as a follow-up.

Pushed.
Aldy

On Sun, Jul 31, 2022 at 8:11 PM Aldy Hernandez <aldyh@redhat.com> wrote:
>
> PING
>
> Andrew, anyone, would you mind giving this a once over?  I realize
> reviewing ranger's range-op code is not on anyone's list of
> priorities, but I could use a sanity check.
>
> The patch is sufficiently self-contained to easily catch anything
> caused by it, and I'd like to commit earlier in the week to have
> enough time to field any possible fallout before I take a few days off
> next week.
>
> Updated patch attached.
>
> Thanks.
> Aldy
>
> On Mon, Jul 25, 2022 at 8:50 PM Aldy Hernandez <aldyh@redhat.com> wrote:
> >
> > Without further ado, here is the implementation for floating point
> > range operators, plus the switch to enable all ranger clients to
> > handle floats.
> >
> > These are bare bone implementations good enough for relation operators
> > to work, while keeping the NAN bits up to date in the frange.  There
> > is also minimal support for keeping track of +-INF when it is obvious.
> >
> > I have included some basic tests to help get a feel of what is
> > ultimately handled.
> >
> > Since range-ops is the domain specific core of ranger, I think its
> > best if a global maintainer or an FP expert could review this.
> >
> > OK for trunk?
> >
> > Tested on x86-64 Linux.
> >
> > p.s. I haven't done extensive testing in DOM, but with this we're mighty
> > close for the forward threader there to be replaceable with the backward
> > threader, thus removing the last use of the forward threader.
> >
> > gcc/ChangeLog:
> >
> >         * range-op-float.cc (finite_operands_p): New.
> >         (frelop_early_resolve): New.
> >         (default_frelop_fold_range): New.
> >         (class foperator_equal): New.
> >         (class foperator_not_equal): New.
> >         (class foperator_lt): New.
> >         (class foperator_le): New.
> >         (class foperator_gt): New.
> >         (class foperator_ge): New.
> >         (class foperator_unordered): New.
> >         (class foperator_ordered): New.
> >         (class foperator_relop_unknown): New.
> >         (floating_op_table::floating_op_table): Add above classes to
> >         floating op table.
> >         * value-range.h (frange::supports_p): Enable.
> >
> > gcc/testsuite/ChangeLog:
> >
> >         * g++.dg/opt/pr94589-2.C: Add notes.
> >         * gcc.dg/tree-ssa/vrp-float-1.c: New test.
> >         * gcc.dg/tree-ssa/vrp-float-11.c: New test.
> >         * gcc.dg/tree-ssa/vrp-float-3.c: New test.
> >         * gcc.dg/tree-ssa/vrp-float-4.c: New test.
> >         * gcc.dg/tree-ssa/vrp-float-6.c: New test.
> >         * gcc.dg/tree-ssa/vrp-float-7.c: New test.
> >         * gcc.dg/tree-ssa/vrp-float-8.c: New test.
> > ---
> >  gcc/range-op-float.cc                        | 564 +++++++++++++++++++
> >  gcc/testsuite/g++.dg/opt/pr94589-2.C         |  25 +
> >  gcc/testsuite/gcc.dg/tree-ssa/vrp-float-1.c  |  19 +
> >  gcc/testsuite/gcc.dg/tree-ssa/vrp-float-11.c |  26 +
> >  gcc/testsuite/gcc.dg/tree-ssa/vrp-float-3.c  |  18 +
> >  gcc/testsuite/gcc.dg/tree-ssa/vrp-float-4.c  |  16 +
> >  gcc/testsuite/gcc.dg/tree-ssa/vrp-float-6.c  |  20 +
> >  gcc/testsuite/gcc.dg/tree-ssa/vrp-float-7.c  |  14 +
> >  gcc/testsuite/gcc.dg/tree-ssa/vrp-float-8.c  |  26 +
> >  gcc/value-range.h                            |   3 +-
> >  10 files changed, 729 insertions(+), 2 deletions(-)
> >  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vrp-float-1.c
> >  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vrp-float-11.c
> >  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vrp-float-3.c
> >  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vrp-float-4.c
> >  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vrp-float-6.c
> >  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vrp-float-7.c
> >  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vrp-float-8.c
> >
> > diff --git a/gcc/range-op-float.cc b/gcc/range-op-float.cc
> > index 8e9d83e3827..d94ff6f915a 100644
> > --- a/gcc/range-op-float.cc
> > +++ b/gcc/range-op-float.cc
> > @@ -150,6 +150,50 @@ range_operator_float::op1_op2_relation (const irange &lhs ATTRIBUTE_UNUSED) cons
> >    return VREL_VARYING;
> >  }
> >
> > +// Return TRUE if OP1 and OP2 are known to be free of NANs.
> > +
> > +static inline bool
> > +finite_operands_p (const frange &op1, const frange &op2)
> > +{
> > +  return (flag_finite_math_only
> > +         || (op1.get_nan ().no_p ()
> > +             && op2.get_nan ().no_p ()));
> > +}
> > +
> > +// Floating version of relop_early_resolve that takes into account NAN
> > +// and -ffinite-math-only.
> > +
> > +inline bool
> > +frelop_early_resolve (irange &r, tree type,
> > +                     const frange &op1, const frange &op2,
> > +                     relation_kind rel, relation_kind my_rel)
> > +{
> > +  // If either operand is undefined, return VARYING.
> > +  if (empty_range_varying (r, type, op1, op2))
> > +    return true;
> > +
> > +  // We can fold relations from the oracle when we know both operands
> > +  // are free of NANs, or when -ffinite-math-only.
> > +  return (finite_operands_p (op1, op2)
> > +         && relop_early_resolve (r, type, op1, op2, rel, my_rel));
> > +}
> > +
> > +// Default implementation of fold_range for relational operators.
> > +// This amounts to passing on any known relations from the oracle, iff
> > +// we know the operands are not NAN or -ffinite-math-only holds.
> > +
> > +static inline bool
> > +default_frelop_fold_range (irange &r, tree type,
> > +                         const frange &op1, const frange &op2,
> > +                         relation_kind rel, relation_kind my_rel)
> > +{
> > +  if (frelop_early_resolve (r, type, op1, op2, rel, my_rel))
> > +    return true;
> > +
> > +  r.set_varying (type);
> > +  return true;
> > +}
> > +
> >  class foperator_identity : public range_operator_float
> >  {
> >    using range_operator_float::fold_range;
> > @@ -172,6 +216,509 @@ class foperator_identity : public range_operator_float
> >  public:
> >  } fop_identity;
> >
> > +class foperator_equal : public range_operator_float
> > +{
> > +  using range_operator_float::fold_range;
> > +  using range_operator_float::op1_range;
> > +  using range_operator_float::op2_range;
> > +
> > +  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 op1_op2_relation (const irange &lhs) const final override
> > +  {
> > +    return equal_op1_op2_relation (lhs);
> > +  }
> > +  bool op1_range (frange &r, tree type,
> > +                 const irange &lhs, const frange &op2,
> > +                 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);
> > +  }
> > +} fop_equal;
> > +
> > +bool
> > +foperator_equal::op1_range (frange &r, tree type,
> > +                           const irange &lhs,
> > +                           const frange &op2 ATTRIBUTE_UNUSED,
> > +                           relation_kind rel) 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.
> > +      r.set_nan (fp_prop::NO);
> > +      break;
> > +
> > +    case BRS_FALSE:
> > +      r.set_varying (type);
> > +      // The FALSE side of op1 ORDERED op1 implies op1 is a NAN.
> > +      if (rel == VREL_EQ)
> > +       r.set_nan (fp_prop::YES);
> > +      break;
> > +
> > +    default:
> > +      break;
> > +    }
> > +  return true;
> > +}
> > +
> > +class foperator_not_equal : public range_operator_float
> > +{
> > +  using range_operator_float::fold_range;
> > +  using range_operator_float::op1_range;
> > +
> > +  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 op1_op2_relation (const irange &lhs) const final override
> > +  {
> > +    return not_equal_op1_op2_relation (lhs);
> > +  }
> > +  bool op1_range (frange &r, tree type,
> > +                 const irange &lhs, const frange &op2,
> > +                 relation_kind rel) const final override;
> > +} fop_not_equal;
> > +
> > +bool
> > +foperator_not_equal::op1_range (frange &r, tree type,
> > +                               const irange &lhs,
> > +                               const frange &op2 ATTRIBUTE_UNUSED,
> > +                               relation_kind) const
> > +{
> > +  switch (get_bool_state (r, lhs, type))
> > +    {
> > +    case BRS_TRUE:
> > +      r.set_varying (type);
> > +      break;
> > +
> > +    case BRS_FALSE:
> > +      r.set_varying (type);
> > +      // The FALSE side of op1 != op2 implies op1 is !NAN.
> > +      r.set_nan (fp_prop::NO);
> > +      break;
> > +
> > +    default:
> > +      break;
> > +    }
> > +  return true;
> > +}
> > +
> > +class foperator_lt : public range_operator_float
> > +{
> > +  using range_operator_float::fold_range;
> > +  using range_operator_float::op1_range;
> > +  using range_operator_float::op2_range;
> > +
> > +  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 op1_op2_relation (const irange &lhs) const final override
> > +  {
> > +    return lt_op1_op2_relation (lhs);
> > +  }
> > +  bool op1_range (frange &r, tree type,
> > +                 const irange &lhs, const frange &op2,
> > +                 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;
> > +} fop_lt;
> > +
> > +bool
> > +foperator_lt::op1_range (frange &r,
> > +                        tree type,
> > +                        const irange &lhs,
> > +                        const frange &op2 ATTRIBUTE_UNUSED,
> > +                        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 and !INF.
> > +      r.set_nan (fp_prop::NO);
> > +      r.set_inf (fp_prop::NO);
> > +      break;
> > +
> > +    case BRS_FALSE:
> > +      r.set_varying (type);
> > +      break;
> > +
> > +    default:
> > +      break;
> > +    }
> > +  return true;
> > +}
> > +
> > +bool
> > +foperator_lt::op2_range (frange &r,
> > +                        tree type,
> > +                        const irange &lhs,
> > +                        const frange &op1 ATTRIBUTE_UNUSED,
> > +                        relation_kind) const
> > +{
> > +  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.
> > +      r.set_nan (fp_prop::NO);
> > +      r.set_ninf (fp_prop::NO);
> > +      break;
> > +
> > +    case BRS_FALSE:
> > +      r.set_varying (type);
> > +      break;
> > +
> > +    default:
> > +      break;
> > +    }
> > +  return true;
> > +}
> > +
> > +class foperator_le : public range_operator_float
> > +{
> > +  using range_operator_float::fold_range;
> > +  using range_operator_float::op1_range;
> > +  using range_operator_float::op2_range;
> > +
> > +  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 op1_op2_relation (const irange &lhs) const final override
> > +  {
> > +    return le_op1_op2_relation (lhs);
> > +  }
> > +  bool op1_range (frange &r, tree type,
> > +                 const irange &lhs, const frange &op2,
> > +                 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);
> > +  }
> > +} fop_le;
> > +
> > +bool
> > +foperator_le::op1_range (frange &r,
> > +                        tree type,
> > +                        const irange &lhs,
> > +                        const frange &op2 ATTRIBUTE_UNUSED,
> > +                        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.
> > +      r.set_nan (fp_prop::NO);
> > +      break;
> > +
> > +    case BRS_FALSE:
> > +      r.set_varying (type);
> > +      break;
> > +
> > +    default:
> > +      break;
> > +    }
> > +  return true;
> > +}
> > +
> > +class foperator_gt : public range_operator_float
> > +{
> > +  using range_operator_float::fold_range;
> > +  using range_operator_float::op1_range;
> > +  using range_operator_float::op2_range;
> > +
> > +  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 op1_op2_relation (const irange &lhs) const final override
> > +  {
> > +    return gt_op1_op2_relation (lhs);
> > +  }
> > +  bool op1_range (frange &r, tree type,
> > +                 const irange &lhs, const frange &op2,
> > +                 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;
> > +} fop_gt;
> > +
> > +bool
> > +foperator_gt::op1_range (frange &r,
> > +                        tree type,
> > +                        const irange &lhs,
> > +                        const frange &op2 ATTRIBUTE_UNUSED,
> > +                        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 and !NINF.
> > +      r.set_nan (fp_prop::NO);
> > +      r.set_ninf (fp_prop::NO);
> > +      break;
> > +
> > +    case BRS_FALSE:
> > +      r.set_varying (type);
> > +      break;
> > +
> > +    default:
> > +      break;
> > +    }
> > +  return true;
> > +}
> > +
> > +bool
> > +foperator_gt::op2_range (frange &r,
> > +                        tree type,
> > +                        const irange &lhs,
> > +                        const frange &op1 ATTRIBUTE_UNUSED,
> > +                        relation_kind) const
> > +{
> > +  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.
> > +      r.set_nan (fp_prop::NO);
> > +      r.set_inf (fp_prop::NO);
> > +      break;
> > +
> > +    case BRS_FALSE:
> > +      r.set_varying (type);
> > +      break;
> > +
> > +    default:
> > +      break;
> > +    }
> > +  return true;
> > +}
> > +
> > +class foperator_ge : public range_operator_float
> > +{
> > +  using range_operator_float::fold_range;
> > +  using range_operator_float::op1_range;
> > +  using range_operator_float::op2_range;
> > +
> > +  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 op1_op2_relation (const irange &lhs) const final override
> > +  {
> > +    return ge_op1_op2_relation (lhs);
> > +  }
> > +  bool op1_range (frange &r, tree type,
> > +                 const irange &lhs, const frange &op2,
> > +                 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);
> > +  }
> > +} fop_ge;
> > +
> > +bool
> > +foperator_ge::op1_range (frange &r,
> > +                        tree type,
> > +                        const irange &lhs,
> > +                        const frange &op2 ATTRIBUTE_UNUSED,
> > +                        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.
> > +      r.set_nan (fp_prop::NO);
> > +      break;
> > +
> > +    case BRS_FALSE:
> > +      r.set_varying (type);
> > +      break;
> > +
> > +    default:
> > +      break;
> > +    }
> > +  return true;
> > +}
> > +
> > +// UNORDERED_EXPR comparison.
> > +
> > +class foperator_unordered : public range_operator_float
> > +{
> > +  using range_operator_float::fold_range;
> > +  using range_operator_float::op1_range;
> > +  using range_operator_float::op2_range;
> > +
> > +public:
> > +  bool fold_range (irange &r, tree type,
> > +                  const frange &op1, const frange &op2,
> > +                  relation_kind rel) const final override;
> > +  bool op1_range (frange &r, tree type,
> > +                 const irange &lhs, const frange &op2,
> > +                 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);
> > +  }
> > +} fop_unordered;
> > +
> > +bool
> > +foperator_unordered::fold_range (irange &r, tree type,
> > +                                const frange &op1, const frange &op2,
> > +                                relation_kind) const
> > +{
> > +  // UNORDERED is TRUE if either operand is a NAN.
> > +  if (op1.get_nan ().yes_p () || op2.get_nan ().yes_p ())
> > +    r = range_true (type);
> > +  // UNORDERED is FALSE if neither operand is a NAN.
> > +  else if (op1.get_nan ().no_p () && op2.get_nan ().no_p ())
> > +    r = range_false (type);
> > +  else
> > +    r = range_true_and_false (type);
> > +  return true;
> > +}
> > +
> > +bool
> > +foperator_unordered::op1_range (frange &r, tree type,
> > +                               const irange &lhs,
> > +                               const frange &op2 ATTRIBUTE_UNUSED,
> > +                               relation_kind) const
> > +{
> > +  switch (get_bool_state (r, lhs, type))
> > +    {
> > +    case BRS_TRUE:
> > +      r.set_varying (type);
> > +      // Since at least one operand must be NAN, if one of them is
> > +      // not, the other must be.
> > +      if (op2.get_nan ().no_p ())
> > +       r.set_nan (fp_prop::YES);
> > +      break;
> > +
> > +    case BRS_FALSE:
> > +      r.set_varying (type);
> > +      // A false UNORDERED means both operands are !NAN.
> > +      r.set_nan (fp_prop::NO);
> > +      break;
> > +
> > +    default:
> > +      break;
> > +    }
> > +  return true;
> > +}
> > +
> > +// ORDERED_EXPR comparison.
> > +
> > +class foperator_ordered : public range_operator_float
> > +{
> > +  using range_operator_float::fold_range;
> > +  using range_operator_float::op1_range;
> > +  using range_operator_float::op2_range;
> > +
> > +public:
> > +  bool fold_range (irange &r, tree type,
> > +                  const frange &op1, const frange &op2,
> > +                  relation_kind rel) const final override;
> > +  bool op1_range (frange &r, tree type,
> > +                 const irange &lhs, const frange &op2,
> > +                 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);
> > +  }
> > +} fop_ordered;
> > +
> > +bool
> > +foperator_ordered::fold_range (irange &r, tree type,
> > +                              const frange &op1, const frange &op2,
> > +                              relation_kind) const
> > +{
> > +  // ORDERED is TRUE if neither operand is a NAN.
> > +  if (op1.get_nan ().no_p () && op2.get_nan ().no_p ())
> > +    r = range_true (type);
> > +  // ORDERED is FALSE if either operand is a NAN.
> > +  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_ordered::op1_range (frange &r, tree type,
> > +                             const irange &lhs,
> > +                             const frange &op2 ATTRIBUTE_UNUSED,
> > +                             relation_kind rel) const
> > +{
> > +  switch (get_bool_state (r, lhs, type))
> > +    {
> > +    case BRS_TRUE:
> > +      r.set_varying (type);
> > +      // The TRUE side of op1 UNORDERED op2 implies op1 is !NAN.
> > +      r.set_nan (fp_prop::NO);
> > +      break;
> > +
> > +    case BRS_FALSE:
> > +      r.set_varying (type);
> > +      // The FALSE side of op1 UNORDERED op1 implies op1 is !NAN.
> > +      if (rel == VREL_EQ)
> > +       r.set_nan (fp_prop::NO);
> > +      break;
> > +
> > +    default:
> > +      break;
> > +    }
> > +  return true;
> > +}
> > +
> > +// Placeholder for unimplemented relational operators.
> > +
> > +class foperator_relop_unknown : public range_operator_float
> > +{
> > +  using range_operator_float::fold_range;
> > +
> > +public:
> > +  bool fold_range (irange &r, tree type,
> > +                  const frange &, const frange &,
> > +                  relation_kind) const final override
> > +  {
> > +    r.set_varying (type);
> > +    return true;
> > +  }
> > +} fop_relop_unknown;
> > +
> >
> >  // Instantiate a range_op_table for floating point operations.
> >  static floating_op_table global_floating_table;
> > @@ -185,6 +732,23 @@ floating_op_table::floating_op_table ()
> >    set (PAREN_EXPR, fop_identity);
> >    set (OBJ_TYPE_REF, fop_identity);
> >    set (REAL_CST, fop_identity);
> > +
> > +  // All the relational operators are expected to work, because the
> > +  // calculation of ranges on outgoing edges expect the handlers to be
> > +  // present.
> > +  set (EQ_EXPR, fop_equal);
> > +  set (NE_EXPR, fop_not_equal);
> > +  set (LT_EXPR, fop_lt);
> > +  set (LE_EXPR, fop_le);
> > +  set (GT_EXPR, fop_gt);
> > +  set (GE_EXPR, fop_ge);
> > +  set (UNLE_EXPR, fop_relop_unknown);
> > +  set (UNLT_EXPR, fop_relop_unknown);
> > +  set (UNGE_EXPR, fop_relop_unknown);
> > +  set (UNGT_EXPR, fop_relop_unknown);
> > +  set (UNEQ_EXPR, fop_relop_unknown);
> > +  set (ORDERED_EXPR, fop_ordered);
> > +  set (UNORDERED_EXPR, fop_unordered);
> >  }
> >
> >  // Return a pointer to the range_operator_float instance, if there is
> > diff --git a/gcc/testsuite/g++.dg/opt/pr94589-2.C b/gcc/testsuite/g++.dg/opt/pr94589-2.C
> > index e9ef84b1912..1caa725061e 100644
> > --- a/gcc/testsuite/g++.dg/opt/pr94589-2.C
> > +++ b/gcc/testsuite/g++.dg/opt/pr94589-2.C
> > @@ -4,6 +4,31 @@
> >  // { dg-final { scan-tree-dump-times "\[ij]_\[0-9]+\\(D\\) (?:<|<=|==|!=|>|>=) \[ij]_\[0-9]+\\(D\\)" 12 "optimized" } }
> >  // { dg-final { scan-tree-dump-times "i_\[0-9]+\\(D\\) (?:<|<=|==|!=|>|>=) 5\\.0" 12 "optimized" } }
> >
> > +/* This is failing on f5() because the spaceship operator is no longer
> > +   folded.  What happens is that evrp folds away the final condition
> > +   here as always true.
> > +
> > +  <bb 2> :
> > +  if (i_2(D) != j_4(D))
> > +    goto <bb 3>; [INV]
> > +  else
> > +    goto <bb 6>; [INV]
> > +
> > +  <bb 3> :
> > +  if (i_2(D) >= j_4(D))
> > +    goto <bb 4>; [INV]
> > +  else
> > +    goto <bb 6>; [INV]
> > +
> > +  <bb 4> :
> > +  if (i_2(D) > j_4(D))   <<== ALWAYS TRUE
> > +    goto <bb 6>; [INV]
> > +  else
> > +    goto <bb 5>; [INV]
> > +
> > +  This causes phiopt to no longer be able to fold the total sequence
> > +  into i_2 >= j_4.  */
> > +
> >  #include <compare>
> >
> >  #define A __attribute__((noipa))
> > diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-1.c b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-1.c
> > new file mode 100644
> > index 00000000000..88faf72ac42
> > --- /dev/null
> > +++ b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-1.c
> > @@ -0,0 +1,19 @@
> > +// { dg-do compile }
> > +// { dg-options "-O2 -fdisable-tree-ethread -fdisable-tree-fre1 -fdump-tree-evrp" }
> > +
> > +void bar ();
> > +void george ();
> > +
> > +float
> > +foo (float x, float y)
> > +{
> > +  if (x == x)
> > +    {
> > +      if (x > y)
> > +        bar();
> > +      if (x == x)
> > +        george();
> > +    }
> > +}
> > +
> > +// { dg-final { scan-tree-dump-times "Folding predicate x_*to 1" "evrp" 1 } }
> > diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-11.c b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-11.c
> > new file mode 100644
> > index 00000000000..2f4dc8757a3
> > --- /dev/null
> > +++ b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-11.c
> > @@ -0,0 +1,26 @@
> > +// { dg-do compile }
> > +// { dg-options "-O2 -fno-thread-jumps -fdump-tree-evrp" }
> > +
> > +extern void link_error ();
> > +
> > +void fast_sqrt (float);
> > +
> > +float test (float x)
> > +{
> > +  float y = x*x;
> > +  if (y >= 0.f)
> > +    {
> > +      if (__builtin_isnan (y))
> > +       link_error ();
> > +      else
> > +       fast_sqrt (y);
> > +
> > +      if (!__builtin_isnan (y))
> > +       fast_sqrt (y);
> > +      else
> > +       link_error ();
> > +    }
> > +}
> > +
> > +// { dg-final { scan-tree-dump-times "fast_sqrt" 2 "evrp" } }
> > +// { dg-final { scan-tree-dump-not "link_error" "evrp" } }
> > diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-3.c b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-3.c
> > new file mode 100644
> > index 00000000000..c659abb6cc0
> > --- /dev/null
> > +++ b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-3.c
> > @@ -0,0 +1,18 @@
> > +// { dg-do compile }
> > +// { dg-options "-O2 -fdisable-tree-ethread -fdump-tree-evrp" }
> > +
> > +void link_error ();
> > +
> > +void
> > +foo (double x, double y)
> > +{
> > +  if (x == y)
> > +    {
> > +      if (__builtin_isnan (x))
> > +        link_error ();
> > +      if (__builtin_isnan (y))
> > +        link_error ();
> > +    }
> > +}
> > +
> > +// { dg-final { scan-tree-dump-not "link_error" "evrp" } }
> > diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-4.c b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-4.c
> > new file mode 100644
> > index 00000000000..86436742e0a
> > --- /dev/null
> > +++ b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-4.c
> > @@ -0,0 +1,16 @@
> > +// { dg-do compile }
> > +// { dg-options "-O2 -fdisable-tree-ethread -fdump-tree-evrp" }
> > +
> > +void link_error ();
> > +
> > +void
> > +foo (double x, double y)
> > +{
> > +  if (x > y)
> > +    {
> > +      if (__builtin_isnan (x) || __builtin_isnan (y))
> > +        link_error ();
> > +    }
> > +}
> > +
> > +// { dg-final { scan-tree-dump-not "link_error" "evrp" } }
> > diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-6.c b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-6.c
> > new file mode 100644
> > index 00000000000..145d1861804
> > --- /dev/null
> > +++ b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-6.c
> > @@ -0,0 +1,20 @@
> > +// { dg-do compile }
> > +// { dg-options "-O2 -fdisable-tree-ethread -fdump-tree-evrp" }
> > +
> > +void bar ();
> > +
> > +void
> > +foo (double x, double y)
> > +{
> > +      if (x > y)
> > +       ;
> > +      else if (!__builtin_isnan (x) && !__builtin_isnan (y))
> > +       {
> > +         // If x and y are not NAN, the x <= y relationship holds, and the
> > +         // following conditional can be folded away.
> > +         if (x <= y)
> > +           bar ();
> > +       }
> > +}
> > +
> > +// { dg-final { scan-tree-dump-times "Folding predicate x_.* <= y_.* to 1" 1 "evrp" } }
> > diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-7.c b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-7.c
> > new file mode 100644
> > index 00000000000..92af87091a8
> > --- /dev/null
> > +++ b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-7.c
> > @@ -0,0 +1,14 @@
> > +// { dg-do compile }
> > +// { dg-options "-O2 -fno-tree-forwprop -fno-tree-ccp -fno-tree-fre -fdump-tree-evrp" }
> > +
> > +extern void link_error ();
> > +
> > +void
> > +foo ()
> > +{
> > +  float z = 0.0;
> > +  if (__builtin_isnan (z))
> > +    link_error ();
> > +}
> > +
> > +// { dg-final { scan-tree-dump-not "link_error" "evrp" } }
> > diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-8.c b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-8.c
> > new file mode 100644
> > index 00000000000..9170150d453
> > --- /dev/null
> > +++ b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-8.c
> > @@ -0,0 +1,26 @@
> > +// { dg-do compile }
> > +// { dg-options "-O2 -fno-thread-jumps -fdump-tree-evrp" }
> > +
> > +extern void link_error ();
> > +
> > +void fast_sqrt (float);
> > +
> > +float test (float x)
> > +{
> > +    float y = x*x;
> > +    if (y >= 0.f)
> > +      {
> > +        if (__builtin_isnan (y))
> > +         link_error ();
> > +        else
> > +          fast_sqrt (y);
> > +
> > +       if (!__builtin_isnan (y))
> > +         fast_sqrt (y);
> > +       else
> > +         link_error ();
> > +      }
> > +}
> > +
> > +// { dg-final { scan-tree-dump-times "fast_sqrt" 2 "evrp" } }
> > +// { dg-final { scan-tree-dump-not "link_error" "evrp" } }
> > diff --git a/gcc/value-range.h b/gcc/value-range.h
> > index e43fbe30f27..478f165e02b 100644
> > --- a/gcc/value-range.h
> > +++ b/gcc/value-range.h
> > @@ -338,8 +338,7 @@ public:
> >    frange (const frange &);
> >    static bool supports_p (tree type)
> >    {
> > -    // Disabled until floating point range-ops come live.
> > -    return 0 && SCALAR_FLOAT_TYPE_P (type);
> > +    return SCALAR_FLOAT_TYPE_P (type);
> >    }
> >    virtual tree type () const override;
> >    virtual void set (tree, tree, value_range_kind = VR_RANGE) override;
> > --
> > 2.36.1
> >
  

Patch

diff --git a/gcc/range-op-float.cc b/gcc/range-op-float.cc
index 8e9d83e3827..d94ff6f915a 100644
--- a/gcc/range-op-float.cc
+++ b/gcc/range-op-float.cc
@@ -150,6 +150,50 @@  range_operator_float::op1_op2_relation (const irange &lhs ATTRIBUTE_UNUSED) cons
   return VREL_VARYING;
 }
 
+// Return TRUE if OP1 and OP2 are known to be free of NANs.
+
+static inline bool
+finite_operands_p (const frange &op1, const frange &op2)
+{
+  return (flag_finite_math_only
+	  || (op1.get_nan ().no_p ()
+	      && op2.get_nan ().no_p ()));
+}
+
+// Floating version of relop_early_resolve that takes into account NAN
+// and -ffinite-math-only.
+
+inline bool
+frelop_early_resolve (irange &r, tree type,
+		      const frange &op1, const frange &op2,
+		      relation_kind rel, relation_kind my_rel)
+{
+  // If either operand is undefined, return VARYING.
+  if (empty_range_varying (r, type, op1, op2))
+    return true;
+
+  // We can fold relations from the oracle when we know both operands
+  // are free of NANs, or when -ffinite-math-only.
+  return (finite_operands_p (op1, op2)
+	  && relop_early_resolve (r, type, op1, op2, rel, my_rel));
+}
+
+// Default implementation of fold_range for relational operators.
+// This amounts to passing on any known relations from the oracle, iff
+// we know the operands are not NAN or -ffinite-math-only holds.
+
+static inline bool
+default_frelop_fold_range (irange &r, tree type,
+			  const frange &op1, const frange &op2,
+			  relation_kind rel, relation_kind my_rel)
+{
+  if (frelop_early_resolve (r, type, op1, op2, rel, my_rel))
+    return true;
+
+  r.set_varying (type);
+  return true;
+}
+
 class foperator_identity : public range_operator_float
 {
   using range_operator_float::fold_range;
@@ -172,6 +216,509 @@  class foperator_identity : public range_operator_float
 public:
 } fop_identity;
 
+class foperator_equal : public range_operator_float
+{
+  using range_operator_float::fold_range;
+  using range_operator_float::op1_range;
+  using range_operator_float::op2_range;
+
+  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 op1_op2_relation (const irange &lhs) const final override
+  {
+    return equal_op1_op2_relation (lhs);
+  }
+  bool op1_range (frange &r, tree type,
+		  const irange &lhs, const frange &op2,
+		  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);
+  }
+} fop_equal;
+
+bool
+foperator_equal::op1_range (frange &r, tree type,
+			    const irange &lhs,
+			    const frange &op2 ATTRIBUTE_UNUSED,
+			    relation_kind rel) 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.
+      r.set_nan (fp_prop::NO);
+      break;
+
+    case BRS_FALSE:
+      r.set_varying (type);
+      // The FALSE side of op1 ORDERED op1 implies op1 is a NAN.
+      if (rel == VREL_EQ)
+	r.set_nan (fp_prop::YES);
+      break;
+
+    default:
+      break;
+    }
+  return true;
+}
+
+class foperator_not_equal : public range_operator_float
+{
+  using range_operator_float::fold_range;
+  using range_operator_float::op1_range;
+
+  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 op1_op2_relation (const irange &lhs) const final override
+  {
+    return not_equal_op1_op2_relation (lhs);
+  }
+  bool op1_range (frange &r, tree type,
+		  const irange &lhs, const frange &op2,
+		  relation_kind rel) const final override;
+} fop_not_equal;
+
+bool
+foperator_not_equal::op1_range (frange &r, tree type,
+				const irange &lhs,
+				const frange &op2 ATTRIBUTE_UNUSED,
+				relation_kind) const
+{
+  switch (get_bool_state (r, lhs, type))
+    {
+    case BRS_TRUE:
+      r.set_varying (type);
+      break;
+
+    case BRS_FALSE:
+      r.set_varying (type);
+      // The FALSE side of op1 != op2 implies op1 is !NAN.
+      r.set_nan (fp_prop::NO);
+      break;
+
+    default:
+      break;
+    }
+  return true;
+}
+
+class foperator_lt : public range_operator_float
+{
+  using range_operator_float::fold_range;
+  using range_operator_float::op1_range;
+  using range_operator_float::op2_range;
+
+  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 op1_op2_relation (const irange &lhs) const final override
+  {
+    return lt_op1_op2_relation (lhs);
+  }
+  bool op1_range (frange &r, tree type,
+		  const irange &lhs, const frange &op2,
+		  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;
+} fop_lt;
+
+bool
+foperator_lt::op1_range (frange &r,
+			 tree type,
+			 const irange &lhs,
+			 const frange &op2 ATTRIBUTE_UNUSED,
+			 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 and !INF.
+      r.set_nan (fp_prop::NO);
+      r.set_inf (fp_prop::NO);
+      break;
+
+    case BRS_FALSE:
+      r.set_varying (type);
+      break;
+
+    default:
+      break;
+    }
+  return true;
+}
+
+bool
+foperator_lt::op2_range (frange &r,
+			 tree type,
+			 const irange &lhs,
+			 const frange &op1 ATTRIBUTE_UNUSED,
+			 relation_kind) const
+{
+  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.
+      r.set_nan (fp_prop::NO);
+      r.set_ninf (fp_prop::NO);
+      break;
+
+    case BRS_FALSE:
+      r.set_varying (type);
+      break;
+
+    default:
+      break;
+    }
+  return true;
+}
+
+class foperator_le : public range_operator_float
+{
+  using range_operator_float::fold_range;
+  using range_operator_float::op1_range;
+  using range_operator_float::op2_range;
+
+  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 op1_op2_relation (const irange &lhs) const final override
+  {
+    return le_op1_op2_relation (lhs);
+  }
+  bool op1_range (frange &r, tree type,
+		  const irange &lhs, const frange &op2,
+		  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);
+  }
+} fop_le;
+
+bool
+foperator_le::op1_range (frange &r,
+			 tree type,
+			 const irange &lhs,
+			 const frange &op2 ATTRIBUTE_UNUSED,
+			 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.
+      r.set_nan (fp_prop::NO);
+      break;
+
+    case BRS_FALSE:
+      r.set_varying (type);
+      break;
+
+    default:
+      break;
+    }
+  return true;
+}
+
+class foperator_gt : public range_operator_float
+{
+  using range_operator_float::fold_range;
+  using range_operator_float::op1_range;
+  using range_operator_float::op2_range;
+
+  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 op1_op2_relation (const irange &lhs) const final override
+  {
+    return gt_op1_op2_relation (lhs);
+  }
+  bool op1_range (frange &r, tree type,
+		  const irange &lhs, const frange &op2,
+		  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;
+} fop_gt;
+
+bool
+foperator_gt::op1_range (frange &r,
+			 tree type,
+			 const irange &lhs,
+			 const frange &op2 ATTRIBUTE_UNUSED,
+			 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 and !NINF.
+      r.set_nan (fp_prop::NO);
+      r.set_ninf (fp_prop::NO);
+      break;
+
+    case BRS_FALSE:
+      r.set_varying (type);
+      break;
+
+    default:
+      break;
+    }
+  return true;
+}
+
+bool
+foperator_gt::op2_range (frange &r,
+			 tree type,
+			 const irange &lhs,
+			 const frange &op1 ATTRIBUTE_UNUSED,
+			 relation_kind) const
+{
+  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.
+      r.set_nan (fp_prop::NO);
+      r.set_inf (fp_prop::NO);
+      break;
+
+    case BRS_FALSE:
+      r.set_varying (type);
+      break;
+
+    default:
+      break;
+    }
+  return true;
+}
+
+class foperator_ge : public range_operator_float
+{
+  using range_operator_float::fold_range;
+  using range_operator_float::op1_range;
+  using range_operator_float::op2_range;
+
+  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 op1_op2_relation (const irange &lhs) const final override
+  {
+    return ge_op1_op2_relation (lhs);
+  }
+  bool op1_range (frange &r, tree type,
+		  const irange &lhs, const frange &op2,
+		  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);
+  }
+} fop_ge;
+
+bool
+foperator_ge::op1_range (frange &r,
+			 tree type,
+			 const irange &lhs,
+			 const frange &op2 ATTRIBUTE_UNUSED,
+			 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.
+      r.set_nan (fp_prop::NO);
+      break;
+
+    case BRS_FALSE:
+      r.set_varying (type);
+      break;
+
+    default:
+      break;
+    }
+  return true;
+}
+
+// UNORDERED_EXPR comparison.
+
+class foperator_unordered : public range_operator_float
+{
+  using range_operator_float::fold_range;
+  using range_operator_float::op1_range;
+  using range_operator_float::op2_range;
+
+public:
+  bool fold_range (irange &r, tree type,
+		   const frange &op1, const frange &op2,
+		   relation_kind rel) const final override;
+  bool op1_range (frange &r, tree type,
+		  const irange &lhs, const frange &op2,
+		  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);
+  }
+} fop_unordered;
+
+bool
+foperator_unordered::fold_range (irange &r, tree type,
+				 const frange &op1, const frange &op2,
+				 relation_kind) const
+{
+  // UNORDERED is TRUE if either operand is a NAN.
+  if (op1.get_nan ().yes_p () || op2.get_nan ().yes_p ())
+    r = range_true (type);
+  // UNORDERED is FALSE if neither operand is a NAN.
+  else if (op1.get_nan ().no_p () && op2.get_nan ().no_p ())
+    r = range_false (type);
+  else
+    r = range_true_and_false (type);
+  return true;
+}
+
+bool
+foperator_unordered::op1_range (frange &r, tree type,
+				const irange &lhs,
+				const frange &op2 ATTRIBUTE_UNUSED,
+				relation_kind) const
+{
+  switch (get_bool_state (r, lhs, type))
+    {
+    case BRS_TRUE:
+      r.set_varying (type);
+      // Since at least one operand must be NAN, if one of them is
+      // not, the other must be.
+      if (op2.get_nan ().no_p ())
+	r.set_nan (fp_prop::YES);
+      break;
+
+    case BRS_FALSE:
+      r.set_varying (type);
+      // A false UNORDERED means both operands are !NAN.
+      r.set_nan (fp_prop::NO);
+      break;
+
+    default:
+      break;
+    }
+  return true;
+}
+
+// ORDERED_EXPR comparison.
+
+class foperator_ordered : public range_operator_float
+{
+  using range_operator_float::fold_range;
+  using range_operator_float::op1_range;
+  using range_operator_float::op2_range;
+
+public:
+  bool fold_range (irange &r, tree type,
+		   const frange &op1, const frange &op2,
+		   relation_kind rel) const final override;
+  bool op1_range (frange &r, tree type,
+		  const irange &lhs, const frange &op2,
+		  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);
+  }
+} fop_ordered;
+
+bool
+foperator_ordered::fold_range (irange &r, tree type,
+			       const frange &op1, const frange &op2,
+			       relation_kind) const
+{
+  // ORDERED is TRUE if neither operand is a NAN.
+  if (op1.get_nan ().no_p () && op2.get_nan ().no_p ())
+    r = range_true (type);
+  // ORDERED is FALSE if either operand is a NAN.
+  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_ordered::op1_range (frange &r, tree type,
+			      const irange &lhs,
+			      const frange &op2 ATTRIBUTE_UNUSED,
+			      relation_kind rel) const
+{
+  switch (get_bool_state (r, lhs, type))
+    {
+    case BRS_TRUE:
+      r.set_varying (type);
+      // The TRUE side of op1 UNORDERED op2 implies op1 is !NAN.
+      r.set_nan (fp_prop::NO);
+      break;
+
+    case BRS_FALSE:
+      r.set_varying (type);
+      // The FALSE side of op1 UNORDERED op1 implies op1 is !NAN.
+      if (rel == VREL_EQ)
+	r.set_nan (fp_prop::NO);
+      break;
+
+    default:
+      break;
+    }
+  return true;
+}
+
+// Placeholder for unimplemented relational operators.
+
+class foperator_relop_unknown : public range_operator_float
+{
+  using range_operator_float::fold_range;
+
+public:
+  bool fold_range (irange &r, tree type,
+		   const frange &, const frange &,
+		   relation_kind) const final override
+  {
+    r.set_varying (type);
+    return true;
+  }
+} fop_relop_unknown;
+
 
 // Instantiate a range_op_table for floating point operations.
 static floating_op_table global_floating_table;
@@ -185,6 +732,23 @@  floating_op_table::floating_op_table ()
   set (PAREN_EXPR, fop_identity);
   set (OBJ_TYPE_REF, fop_identity);
   set (REAL_CST, fop_identity);
+
+  // All the relational operators are expected to work, because the
+  // calculation of ranges on outgoing edges expect the handlers to be
+  // present.
+  set (EQ_EXPR, fop_equal);
+  set (NE_EXPR, fop_not_equal);
+  set (LT_EXPR, fop_lt);
+  set (LE_EXPR, fop_le);
+  set (GT_EXPR, fop_gt);
+  set (GE_EXPR, fop_ge);
+  set (UNLE_EXPR, fop_relop_unknown);
+  set (UNLT_EXPR, fop_relop_unknown);
+  set (UNGE_EXPR, fop_relop_unknown);
+  set (UNGT_EXPR, fop_relop_unknown);
+  set (UNEQ_EXPR, fop_relop_unknown);
+  set (ORDERED_EXPR, fop_ordered);
+  set (UNORDERED_EXPR, fop_unordered);
 }
 
 // Return a pointer to the range_operator_float instance, if there is
diff --git a/gcc/testsuite/g++.dg/opt/pr94589-2.C b/gcc/testsuite/g++.dg/opt/pr94589-2.C
index e9ef84b1912..1caa725061e 100644
--- a/gcc/testsuite/g++.dg/opt/pr94589-2.C
+++ b/gcc/testsuite/g++.dg/opt/pr94589-2.C
@@ -4,6 +4,31 @@ 
 // { dg-final { scan-tree-dump-times "\[ij]_\[0-9]+\\(D\\) (?:<|<=|==|!=|>|>=) \[ij]_\[0-9]+\\(D\\)" 12 "optimized" } }
 // { dg-final { scan-tree-dump-times "i_\[0-9]+\\(D\\) (?:<|<=|==|!=|>|>=) 5\\.0" 12 "optimized" } }
 
+/* This is failing on f5() because the spaceship operator is no longer
+   folded.  What happens is that evrp folds away the final condition
+   here as always true.
+
+  <bb 2> :
+  if (i_2(D) != j_4(D))
+    goto <bb 3>; [INV]
+  else
+    goto <bb 6>; [INV]
+
+  <bb 3> :
+  if (i_2(D) >= j_4(D))
+    goto <bb 4>; [INV]
+  else
+    goto <bb 6>; [INV]
+
+  <bb 4> :
+  if (i_2(D) > j_4(D))   <<== ALWAYS TRUE
+    goto <bb 6>; [INV]
+  else
+    goto <bb 5>; [INV]
+
+  This causes phiopt to no longer be able to fold the total sequence
+  into i_2 >= j_4.  */
+
 #include <compare>
 
 #define A __attribute__((noipa))
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-1.c b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-1.c
new file mode 100644
index 00000000000..88faf72ac42
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-1.c
@@ -0,0 +1,19 @@ 
+// { dg-do compile }
+// { dg-options "-O2 -fdisable-tree-ethread -fdisable-tree-fre1 -fdump-tree-evrp" }
+
+void bar ();
+void george ();
+
+float
+foo (float x, float y)
+{
+  if (x == x)
+    {
+      if (x > y)
+        bar();
+      if (x == x)
+        george();
+    }
+}
+
+// { dg-final { scan-tree-dump-times "Folding predicate x_*to 1" "evrp" 1 } }
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-11.c b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-11.c
new file mode 100644
index 00000000000..2f4dc8757a3
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-11.c
@@ -0,0 +1,26 @@ 
+// { dg-do compile }
+// { dg-options "-O2 -fno-thread-jumps -fdump-tree-evrp" }
+
+extern void link_error ();
+
+void fast_sqrt (float);
+
+float test (float x)
+{
+  float y = x*x;
+  if (y >= 0.f)
+    { 
+      if (__builtin_isnan (y))
+	link_error ();
+      else
+	fast_sqrt (y);
+
+      if (!__builtin_isnan (y))
+	fast_sqrt (y);
+      else
+	link_error ();
+    }
+}
+
+// { dg-final { scan-tree-dump-times "fast_sqrt" 2 "evrp" } }
+// { dg-final { scan-tree-dump-not "link_error" "evrp" } }
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-3.c b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-3.c
new file mode 100644
index 00000000000..c659abb6cc0
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-3.c
@@ -0,0 +1,18 @@ 
+// { dg-do compile }
+// { dg-options "-O2 -fdisable-tree-ethread -fdump-tree-evrp" }
+
+void link_error ();
+
+void
+foo (double x, double y)
+{
+  if (x == y)
+    {
+      if (__builtin_isnan (x))
+        link_error ();
+      if (__builtin_isnan (y))
+        link_error ();
+    }
+}
+
+// { dg-final { scan-tree-dump-not "link_error" "evrp" } }
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-4.c b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-4.c
new file mode 100644
index 00000000000..86436742e0a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-4.c
@@ -0,0 +1,16 @@ 
+// { dg-do compile }
+// { dg-options "-O2 -fdisable-tree-ethread -fdump-tree-evrp" }
+
+void link_error ();
+
+void
+foo (double x, double y)
+{
+  if (x > y)
+    {
+      if (__builtin_isnan (x) || __builtin_isnan (y))
+        link_error ();
+    }
+}
+
+// { dg-final { scan-tree-dump-not "link_error" "evrp" } }
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-6.c b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-6.c
new file mode 100644
index 00000000000..145d1861804
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-6.c
@@ -0,0 +1,20 @@ 
+// { dg-do compile }
+// { dg-options "-O2 -fdisable-tree-ethread -fdump-tree-evrp" }
+
+void bar ();
+
+void
+foo (double x, double y)
+{
+      if (x > y)
+	;
+      else if (!__builtin_isnan (x) && !__builtin_isnan (y))
+	{
+	  // If x and y are not NAN, the x <= y relationship holds, and the
+	  // following conditional can be folded away.
+	  if (x <= y)
+	    bar ();
+	}
+}
+
+// { dg-final { scan-tree-dump-times "Folding predicate x_.* <= y_.* to 1" 1 "evrp" } }
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-7.c b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-7.c
new file mode 100644
index 00000000000..92af87091a8
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-7.c
@@ -0,0 +1,14 @@ 
+// { dg-do compile }
+// { dg-options "-O2 -fno-tree-forwprop -fno-tree-ccp -fno-tree-fre -fdump-tree-evrp" }
+
+extern void link_error ();
+
+void
+foo ()
+{
+  float z = 0.0;
+  if (__builtin_isnan (z))
+    link_error ();
+}
+
+// { dg-final { scan-tree-dump-not "link_error" "evrp" } }
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-8.c b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-8.c
new file mode 100644
index 00000000000..9170150d453
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/vrp-float-8.c
@@ -0,0 +1,26 @@ 
+// { dg-do compile }
+// { dg-options "-O2 -fno-thread-jumps -fdump-tree-evrp" }
+
+extern void link_error ();
+
+void fast_sqrt (float);
+
+float test (float x)
+{
+    float y = x*x;
+    if (y >= 0.f)
+      { 
+        if (__builtin_isnan (y))
+         link_error ();
+        else
+          fast_sqrt (y);
+
+       if (!__builtin_isnan (y))
+         fast_sqrt (y);
+       else
+         link_error ();
+      }
+}
+
+// { dg-final { scan-tree-dump-times "fast_sqrt" 2 "evrp" } }
+// { dg-final { scan-tree-dump-not "link_error" "evrp" } }
diff --git a/gcc/value-range.h b/gcc/value-range.h
index e43fbe30f27..478f165e02b 100644
--- a/gcc/value-range.h
+++ b/gcc/value-range.h
@@ -338,8 +338,7 @@  public:
   frange (const frange &);
   static bool supports_p (tree type)
   {
-    // Disabled until floating point range-ops come live.
-    return 0 && SCALAR_FLOAT_TYPE_P (type);
+    return SCALAR_FLOAT_TYPE_P (type);
   }
   virtual tree type () const override;
   virtual void set (tree, tree, value_range_kind = VR_RANGE) override;