Message ID | 20220725184951.2202379-1-aldyh@redhat.com |
---|---|
State | New |
Headers | show |
Series | [RFA] Implement basic range operators to enable floating point VRP. | expand |
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 >
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 >
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 > >
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;