diff mbox series

middle-end: Fix PR middle-end/85811: Introduce tree_expr_maybe_nan_p et al.

Message ID 004901d672f4$b80b66f0$282234d0$@nextmovesoftware.com
State New
Headers show
Series middle-end: Fix PR middle-end/85811: Introduce tree_expr_maybe_nan_p et al. | expand

Commit Message

Roger Sayle Aug. 15, 2020, 11:10 a.m. UTC
The motivation for this patch is PR middle-end/85811, a wrong-code
regression entitled "Invalid optimization with fmax, fabs and nan".
The optimization involves assuming max(x,y) is non-negative if (say)
y is non-negative, i.e. max(x,2.0).  Unfortunately, this is an invalid
assumption in the presence of NaNs.  Hence max(x,+qNaN), with IEEE fmax
semantics will always return x even though the qNaN is non-negative.
Worse, max(x,2.0) may return a negative value if x is -sNaN.

I'll quote Joseph Myers (many thanks) who describes things clearly as:
> (a) When both arguments are NaNs, the return value should be a qNaN,
> but sometimes it is an sNaN if at least one argument is an sNaN.
> (b) Under TS 18661-1 semantics, if either argument is an sNaN then the
> result should be a qNaN (whereas if one argument is a qNaN and the
> other is not a NaN, the result should be the non-NaN argument).
> Various implementations treat sNaNs like qNaNs here.

Under this logic, the tree_expr_nonnegative_p for IEEE fmax should be:

    CASE_CFN_FMAX:
    CASE_CFN_FMAX_FN:
      /* Usually RECURSE (arg0) || RECURSE (arg1) but NaNs complicate
         things.  In the presence of sNaNs, we're only guaranteed to be
         non-negative if both operands are non-negative.  In the presence
         of qNaNs, we're non-negative if either operand is non-negative
         and can't be a qNaN, or if both operands are non-negative.  */
      if (tree_expr_maybe_signaling_nan_p (arg0) ||
          tree_expr_maybe_signaling_nan_p (arg1))
        return RECURSE (arg0) && RECURSE (arg1);
      return RECURSE (arg0) ? (!tree_expr_maybe_nan_p (arg0)
                              || RECURSE (arg1))
                            : (RECURSE (arg1)
                              && !tree_expr_maybe_nan_p (arg1));

Which indeed resolves the wrong code in the PR.  The infrastructure that
makes this possible are the two new functions tree_expr_maybe_nan_p and
tree_expr_maybe_signaling_nan_p which test whether a value may potentially
be a NaN or a signaling NaN respectively.  In fact, this patch adds seven
new predicates to the middle-end:

bool tree_expr_finite_p (const_tree);
bool tree_expr_infinite_p (const_tree);
bool tree_expr_maybe_infinite_p (const_tree);
bool tree_expr_signaling_nan_p (const_tree);
bool tree_expr_maybe_signaling_nan_p (const_tree);
bool tree_expr_nan_p (const_tree);
bool tree_expr_maybe_nan_p (const_tree);

These functions correspond to the "must" and "may" operators in modal logic,
and allow us to triage expressions in the middle-end; definitely a NaN,
definitely not a NaN, and unknown at compile-time, etc.  A prime example of
the utility of these functions is that a IEEE floating point value promoted
from an integer type can't be a NaN or infinite.  Hence (double)i+0.0 where
i is an integer can be simplified to (double)i even with -fsignaling-nans.
Currently in GCC optimizations are enabled/disabled based on whether the
expression's type supports NaNs or sNaNs; with these new predicates they
can be controlled by whether the actual operands may or may not be NaNs.

Having added these extremely useful helper functions to the middle-end,
I couldn't help by use then in a few places in fold-const.c, builtins.c
and match.pd.  In the near term, these can/should be used in places
where the tree optimizers test for HONOR_NANS, HONOR_INFINITIES or
HONOR_SNANS, or explicitly test whether a REAL_CST is a NaN or Inf.
In the longer term (I'm not volunteering) these predicates could perhaps
be hooked into the middle-end's SSA chaining and/or VRP machinery,
allowing finiteness to propagated around the CFG, much like we
currently propagate value ranges.

This patch has been tested on x86_64-pc-linux-gnu with a "make bootstrap"
and "make -k check".
Ok for mainline?


2020-08-15  Roger Sayle  <roger@nextmovesoftware.com>

gcc/ChangeLog
	PR middle-end/85811
	* fold-const.c (tree_expr_finite_p): New function to test whether
	a tree expression must be finite, i.e. not a FP NaN or infinity.
	(tree_expr_infinite_p):  New function to test whether a tree
	expression must be infinite, i.e. a FP infinity.
	(tree_expr_maybe_infinite_p): New function to test whether a tree
	expression may be infinite, i.e. a FP infinity.
	(tree_expr_signaling_nan_p): New function to test whether a tree
	expression must evaluate to a signaling NaN (sNaN).
	(tree_expr_maybe_signaling_nan_p): New function to test whether a
	tree expression may be a signaling NaN (sNaN).
	(tree_expr_nan_p): New function to test whether a tree expression
	must evaluate to a (quiet or signaling) NaN.
	(tree_expr_maybe_nan_p): New function to test whether a tree
	expression me be a (quiet or signaling) NaN.

	(tree_binary_nonnegative_warnv_p) [MAX_EXPR]: In the presence
	of NaNs, MAX_EXPR is only guaranteed to be non-negative, if both
	operands are non-negative.
	(tree_call_nonnegative_warnv_p) [CASE_CFN_FMAX,CASE_CFN_FMAX_FN]:
	In the presence of signaling NaNs, fmax is only guaranteed to be
	non-negative if both operands are negative.  In the presence of
	quiet NaNs, fmax is non-negative if either operand is non-negative
	and not a qNaN, or both operands are non-negative.

	* fold-const.h (tree_expr_finite_p, tree_expr_infinite_p,
	tree_expr_maybe_infinite_p, tree_expr_signaling_nan_p,
	tree_expr_maybe_signaling_nan_p, tree_expr_nan_p,
	tree_expr_maybe_nan_p): Prototype new functions here.

	* builtins.c (fold_builtin_classify) [BUILT_IN_ISINF]: Fold to
	a constant if argument is known to be (or not to be) an Infinity.
	[BUILT_IN_ISFINITE]: Fold to a constant if argument is known to
	be (or not to be) finite.
	[BUILT_IN_ISNAN]: Fold to a constant if argument is known to be
	(or not to be) a NaN.
	(fold_builtin_fpclassify): Check tree_expr_maybe_infinite_p and
	tree_expr_maybe_nan_p instead of HONOR_INFINITIES and HONOR_NANS
	respectively.
	(fold_builtin_unordered_cmp): Fold UNORDERED_EXPR to a constant
	when its arguments are known to be (or not be) NaNs.  Check
	tree_expr_maybe_nan_p instead of HONOR_NANS when choosing between
	unordered and regular forms of comparison operators.

	* match.pd (ordered(x,y)->true/false): Constant fold ORDERED_EXPR
	if its operands are known to be (or not to be) NaNs.
	(unordered(x,y)->true/false): Constant fold UNORDERED_EXPR if its
	operands are known to be (or not to be) NaNs.
	(sqrt(x)*sqrt(x)->x): Check tree_expr_maybe_signaling_nan_p instead
	of HONOR_SNANS.

gcc/testsuite/ChangeLog
	PR middle-end/85811
	* gcc.dg/pr85811.c: New test.
	* gcc.dg/fold-isfinite-1.c: New test.
	* gcc.dg/fold-isfinite-2.c: New test.
	* gcc.dg/fold-isinf-1.c: New test.
	* gcc.dg/fold-isinf-2.c: New test.
	* gcc.dg/fold-isnan-1.c: New test.
	* gcc.dg/fold-isnan-2.c: New test.


Thanks in advance,
Roger
--
Roger Sayle
NextMove Software
Cambridge, UK

diff --git a/gcc/testsuite/gcc.dg/pr85811.c b/gcc/testsuite/gcc.dg/pr85811.c
new file mode 100644
index 0000000..868f66c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/pr85811.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-optimized" } */
+#include <stdio.h>
+
+int main() {
+  const double negval = -1.0;
+  const double nanval = 0.0 / 0.0;
+  const double val = __builtin_fmax(negval, nanval);
+  const double absval = __builtin_fabs(val);
+  printf("fabs(%.16e) = %.16e\n", val, absval);
+  return absval >= 0 ? 0 : 1;
+}
+
+/* We hope not to see:  printf ("fabs(%.16e) = %.16e\n", val_4, val_4); */
+/* { dg-final { scan-tree-dump-not "val_\[0-9\]*, val_\[0-9\]*" "optimized" } } */
diff --git a/gcc/testsuite/gcc.dg/fold-isfinite-1.c b/gcc/testsuite/gcc.dg/fold-isfinite-1.c
new file mode 100644
index 0000000..2ea0192
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/fold-isfinite-1.c
@@ -0,0 +1,21 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target inf } */
+/* { dg-options "-O2 -fdump-tree-optimized" } */
+
+int foo(int x)
+{
+  return __builtin_finite((double)x);
+}
+
+int foof(int x)
+{
+  return __builtin_finitef((float)x);
+}
+
+int fool(int x)
+{
+  return __builtin_finitel((long double)x);
+}
+
+/* { dg-final { scan-tree-dump-times "_finite" 0 "optimized" } } */
+/* { dg-final { scan-tree-dump-times " u> " 0 "optimized" } } */
diff --git a/gcc/testsuite/gcc.dg/fold-isfinite-2.c b/gcc/testsuite/gcc.dg/fold-isfinite-2.c
new file mode 100644
index 0000000..ff70d8d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/fold-isfinite-2.c
@@ -0,0 +1,21 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target inf } */
+/* { dg-options "-O2 -fdump-tree-optimized" } */
+
+int foo(unsigned int x)
+{
+  return __builtin_finite((double)x);
+}
+
+int foof(unsigned int x)
+{
+  return __builtin_finitef((float)x);
+}
+
+int fool(unsigned int x)
+{
+  return __builtin_finitel((long double)x);
+}
+
+/* { dg-final { scan-tree-dump-times "_finite" 0 "optimized" } } */
+/* { dg-final { scan-tree-dump-times " u> " 0 "optimized" } } */
diff --git a/gcc/testsuite/gcc.dg/fold-isinf-1.c b/gcc/testsuite/gcc.dg/fold-isinf-1.c
new file mode 100644
index 0000000..485816e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/fold-isinf-1.c
@@ -0,0 +1,21 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target inf } */
+/* { dg-options "-O2 -fdump-tree-optimized" } */
+
+int foo(int x)
+{
+  return __builtin_isinf((double)x);
+}
+
+int foof(int x)
+{
+  return __builtin_isinff((float)x);
+}
+
+int fool(int x)
+{
+  return __builtin_isinfl((long double)x);
+}
+
+/* { dg-final { scan-tree-dump-times "_isinf" 0 "optimized" } } */
+/* { dg-final { scan-tree-dump-times " u<= " 0 "optimized" } } */
diff --git a/gcc/testsuite/gcc.dg/fold-isinf-2.c b/gcc/testsuite/gcc.dg/fold-isinf-2.c
new file mode 100644
index 0000000..a236ca1
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/fold-isinf-2.c
@@ -0,0 +1,21 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target inf } */
+/* { dg-options "-O2 -fdump-tree-optimized" } */
+
+int foo(unsigned int x)
+{
+  return __builtin_isinf((double)x);
+}
+
+int foof(unsigned int x)
+{
+  return __builtin_isinff((float)x);
+}
+
+int fool(unsigned int x)
+{
+  return __builtin_isinfl((long double)x);
+}
+
+/* { dg-final { scan-tree-dump-times "_isinf" 0 "optimized" } } */
+/* { dg-final { scan-tree-dump-times " u<= " 0 "optimized" } } */
diff --git a/gcc/testsuite/gcc.dg/fold-isnan-1.c b/gcc/testsuite/gcc.dg/fold-isnan-1.c
new file mode 100644
index 0000000..05ee930
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/fold-isnan-1.c
@@ -0,0 +1,21 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target inf } */
+/* { dg-options "-O2 -fdump-tree-optimized" } */
+
+int foo(int x)
+{
+  return __builtin_isnan((double)x);
+}
+
+int foof(int x)
+{
+  return __builtin_isnanf((float)x);
+}
+
+int fool(int x)
+{
+  return __builtin_isnanl((long double)x);
+}
+
+/* { dg-final { scan-tree-dump-times "_isnan" 0 "optimized" } } */
+/* { dg-final { scan-tree-dump-times " unord " 0 "optimized" } } */
diff --git a/gcc/testsuite/gcc.dg/fold-isnan-2.c b/gcc/testsuite/gcc.dg/fold-isnan-2.c
new file mode 100644
index 0000000..32b8833
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/fold-isnan-2.c
@@ -0,0 +1,21 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target inf } */
+/* { dg-options "-O2 -fdump-tree-optimized" } */
+
+int foo(unsigned int x)
+{
+  return __builtin_isnan((double)x);
+}
+
+int foof(unsigned int x)
+{
+  return __builtin_isnanf((float)x);
+}
+
+int fool(unsigned int x)
+{
+  return __builtin_isnanl((long double)x);
+}
+
+/* { dg-final { scan-tree-dump-times "_isnan" 0 "optimized" } } */
+/* { dg-final { scan-tree-dump-times " unord " 0 "optimized" } } */

Comments

Segher Boessenkool Aug. 16, 2020, 1:16 a.m. UTC | #1
Hi!

On Sat, Aug 15, 2020 at 12:10:42PM +0100, Roger Sayle wrote:
> I'll quote Joseph Myers (many thanks) who describes things clearly as:
> > (a) When both arguments are NaNs, the return value should be a qNaN,
> > but sometimes it is an sNaN if at least one argument is an sNaN.

Where is this defined?  I can't find it in C11, in 18661, and of course
it isn't what GCC does (it requires -fsignaling to even acknowledge the
existence of signaling NaNs :-) )

> > (b) Under TS 18661-1 semantics, if either argument is an sNaN then the
> > result should be a qNaN (whereas if one argument is a qNaN and the
> > other is not a NaN, the result should be the non-NaN argument).

I cannot find that first part.

>       if (tree_expr_maybe_signaling_nan_p (arg0) ||
>           tree_expr_maybe_signaling_nan_p (arg1))
>         return RECURSE (arg0) && RECURSE (arg1);

This new function returns false if !HONOR_SNANS, so this looks good :-)

> +bool
> +tree_expr_maybe_signaling_nan_p (const_tree x)

> +    case MIN_EXPR:
> +    case MAX_EXPR:
> +      return tree_expr_maybe_signaling_nan_p (TREE_OPERAND (x, 0))
> +	     || tree_expr_maybe_signaling_nan_p (TREE_OPERAND (x, 1));

Can those ever return a SNaN?  What does GCC do for
FP_SNANS_ALWAYS_SIGNAL?

All looks good to me except the SNaN stuff (which may be just me not
understanding it).  I find "maybe_" stuff very hard to read and
understand btw, but there may be no escaping that :-/

Thanks,


Segher
Joseph Myers Aug. 17, 2020, 10:31 p.m. UTC | #2
On Sat, 15 Aug 2020, Segher Boessenkool wrote:

> Hi!
> 
> On Sat, Aug 15, 2020 at 12:10:42PM +0100, Roger Sayle wrote:
> > I'll quote Joseph Myers (many thanks) who describes things clearly as:
> > > (a) When both arguments are NaNs, the return value should be a qNaN,
> > > but sometimes it is an sNaN if at least one argument is an sNaN.
> 
> Where is this defined?  I can't find it in C11, in 18661, and of course
> it isn't what GCC does (it requires -fsignaling to even acknowledge the
> existence of signaling NaNs :-) )

The semantics of fmax and fmin are those of the maxNum and minNum 
operations in IEEE 754-2008 (that were removed in IEEE 754-2019); see the 
table of IEEE operation bindings that 18661-1 adds to Annex F.

  minNum(x, y) is the canonicalized number x if x < y, y if y < x, the 
  canonicalized number if one operand is a number and the other a quiet 
  NaN. Otherwise it is either x or y, canonicalized (this means results 
  might differ among implementations). When either x or y is a 
  signalingNaN, then the result is according to 6.2.

  maxNum(x, y) is the canonicalized number y if x < y, x if y < x, the 
  canonicalized number if one operand is a number and the other a quiet 
  NaN. Otherwise it is either x or y, canonicalized (this means results 
  might differ among implementations). When either x or y is a 
  signalingNaN, then the result is according to 6.2.

where the relevant wording from 6.2 is

  Under default exception handling, any operation signaling an invalid 
  operation exception and for which a floating-point result is to be 
  delivered shall deliver a quiet NaN.

  Signaling NaNs shall be reserved operands that, under default exception 
  handling, signal the invalid operation exception (see 7.2) for every 
  general-computational and signaling-computational operation except for 
  the conversions described in 5.12. For non-default treatment, see 8.

(and maxNum and minNum are in 5.3 "Homogeneous general-computational 
operations").
Segher Boessenkool Aug. 17, 2020, 11:12 p.m. UTC | #3
On Mon, Aug 17, 2020 at 10:31:08PM +0000, Joseph Myers wrote:
> On Sat, 15 Aug 2020, Segher Boessenkool wrote:
> > On Sat, Aug 15, 2020 at 12:10:42PM +0100, Roger Sayle wrote:
> > > I'll quote Joseph Myers (many thanks) who describes things clearly as:
> > > > (a) When both arguments are NaNs, the return value should be a qNaN,
> > > > but sometimes it is an sNaN if at least one argument is an sNaN.
> > 
> > Where is this defined?  I can't find it in C11, in 18661, and of course
> > it isn't what GCC does (it requires -fsignaling to even acknowledge the
> > existence of signaling NaNs :-) )
> 
> The semantics of fmax and fmin are those of the maxNum and minNum 
> operations in IEEE 754-2008 (that were removed in IEEE 754-2019); see the 
> table of IEEE operation bindings that 18661-1 adds to Annex F.
> 
>   minNum(x, y) is the canonicalized number x if x < y, y if y < x, the 
>   canonicalized number if one operand is a number and the other a quiet 
>   NaN. Otherwise it is either x or y, canonicalized (this means results 
>   might differ among implementations). When either x or y is a 
>   signalingNaN, then the result is according to 6.2.
> 
>   maxNum(x, y) is the canonicalized number y if x < y, x if y < x, the 
>   canonicalized number if one operand is a number and the other a quiet 
>   NaN. Otherwise it is either x or y, canonicalized (this means results 
>   might differ among implementations). When either x or y is a 
>   signalingNaN, then the result is according to 6.2.
> 
> where the relevant wording from 6.2 is
> 
>   Under default exception handling, any operation signaling an invalid 
>   operation exception and for which a floating-point result is to be 
>   delivered shall deliver a quiet NaN.
> 
>   Signaling NaNs shall be reserved operands that, under default exception 
>   handling, signal the invalid operation exception (see 7.2) for every 
>   general-computational and signaling-computational operation except for 
>   the conversions described in 5.12. For non-default treatment, see 8.
> 
> (and maxNum and minNum are in 5.3 "Homogeneous general-computational 
> operations").

Ah, so "When both arguments are NaNs, the return value should be a qNaN"
means the QNaN corresponding to eother x or y.  I see, thanks!


Segher
Joseph Myers Aug. 18, 2020, 3:51 p.m. UTC | #4
On Mon, 17 Aug 2020, Segher Boessenkool wrote:

> Ah, so "When both arguments are NaNs, the return value should be a qNaN"
> means the QNaN corresponding to eother x or y.  I see, thanks!

Yes.  (The precise choice of NaN result given a NaN input is the subject 
of various "should"s, in 6.2.3 NaN propagation, with the choice between 
multiple input NaNs to provide the payload unspecified.  E.g. RISC-V 
doesn't propagate NaN payloads at all.  On x86, the rules for choosing a 
NaN result with more than one NaN input are different between x87 and SSE 
arithmetic.  The compiler can ignore these issues, as long as it gets the 
choice between quiet and signaling NaN correct.)

The IEEE 754-2008 min/max operations also do not specify the precise 
choice of result when the arguments are +0 and -0 in some order, or when 
they are equal decimal floating-point values with different quantum 
exponent.  The new operations in IEEE 754-2019 treat -0 as less than +0, 
but still leave the quantum exponent case unspecified.
Jeff Law Nov. 18, 2020, 10:11 p.m. UTC | #5
On 8/15/20 5:10 AM, Roger Sayle wrote:
> The motivation for this patch is PR middle-end/85811, a wrong-code
> regression entitled "Invalid optimization with fmax, fabs and nan".
> The optimization involves assuming max(x,y) is non-negative if (say)
> y is non-negative, i.e. max(x,2.0).  Unfortunately, this is an invalid
> assumption in the presence of NaNs.  Hence max(x,+qNaN), with IEEE fmax
> semantics will always return x even though the qNaN is non-negative.
> Worse, max(x,2.0) may return a negative value if x is -sNaN.
>
> I'll quote Joseph Myers (many thanks) who describes things clearly as:
>> (a) When both arguments are NaNs, the return value should be a qNaN,
>> but sometimes it is an sNaN if at least one argument is an sNaN.
>> (b) Under TS 18661-1 semantics, if either argument is an sNaN then the
>> result should be a qNaN (whereas if one argument is a qNaN and the
>> other is not a NaN, the result should be the non-NaN argument).
>> Various implementations treat sNaNs like qNaNs here.
> Under this logic, the tree_expr_nonnegative_p for IEEE fmax should be:
>
>     CASE_CFN_FMAX:
>     CASE_CFN_FMAX_FN:
>       /* Usually RECURSE (arg0) || RECURSE (arg1) but NaNs complicate
>          things.  In the presence of sNaNs, we're only guaranteed to be
>          non-negative if both operands are non-negative.  In the presence
>          of qNaNs, we're non-negative if either operand is non-negative
>          and can't be a qNaN, or if both operands are non-negative.  */
>       if (tree_expr_maybe_signaling_nan_p (arg0) ||
>           tree_expr_maybe_signaling_nan_p (arg1))
>         return RECURSE (arg0) && RECURSE (arg1);
>       return RECURSE (arg0) ? (!tree_expr_maybe_nan_p (arg0)
>                               || RECURSE (arg1))
>                             : (RECURSE (arg1)
>                               && !tree_expr_maybe_nan_p (arg1));
>
> Which indeed resolves the wrong code in the PR.  The infrastructure that
> makes this possible are the two new functions tree_expr_maybe_nan_p and
> tree_expr_maybe_signaling_nan_p which test whether a value may potentially
> be a NaN or a signaling NaN respectively.  In fact, this patch adds seven
> new predicates to the middle-end:
>
> bool tree_expr_finite_p (const_tree);
> bool tree_expr_infinite_p (const_tree);
> bool tree_expr_maybe_infinite_p (const_tree);
> bool tree_expr_signaling_nan_p (const_tree);
> bool tree_expr_maybe_signaling_nan_p (const_tree);
> bool tree_expr_nan_p (const_tree);
> bool tree_expr_maybe_nan_p (const_tree);
>
> These functions correspond to the "must" and "may" operators in modal logic,
> and allow us to triage expressions in the middle-end; definitely a NaN,
> definitely not a NaN, and unknown at compile-time, etc.  A prime example of
> the utility of these functions is that a IEEE floating point value promoted
> from an integer type can't be a NaN or infinite.  Hence (double)i+0.0 where
> i is an integer can be simplified to (double)i even with -fsignaling-nans.
> Currently in GCC optimizations are enabled/disabled based on whether the
> expression's type supports NaNs or sNaNs; with these new predicates they
> can be controlled by whether the actual operands may or may not be NaNs.
>
> Having added these extremely useful helper functions to the middle-end,
> I couldn't help by use then in a few places in fold-const.c, builtins.c
> and match.pd.  In the near term, these can/should be used in places
> where the tree optimizers test for HONOR_NANS, HONOR_INFINITIES or
> HONOR_SNANS, or explicitly test whether a REAL_CST is a NaN or Inf.
> In the longer term (I'm not volunteering) these predicates could perhaps
> be hooked into the middle-end's SSA chaining and/or VRP machinery,
> allowing finiteness to propagated around the CFG, much like we
> currently propagate value ranges.
>
> This patch has been tested on x86_64-pc-linux-gnu with a "make bootstrap"
> and "make -k check".
> Ok for mainline?
>
>
> 2020-08-15  Roger Sayle  <roger@nextmovesoftware.com>
>
> gcc/ChangeLog
> 	PR middle-end/85811
> 	* fold-const.c (tree_expr_finite_p): New function to test whether
> 	a tree expression must be finite, i.e. not a FP NaN or infinity.
> 	(tree_expr_infinite_p):  New function to test whether a tree
> 	expression must be infinite, i.e. a FP infinity.
> 	(tree_expr_maybe_infinite_p): New function to test whether a tree
> 	expression may be infinite, i.e. a FP infinity.
> 	(tree_expr_signaling_nan_p): New function to test whether a tree
> 	expression must evaluate to a signaling NaN (sNaN).
> 	(tree_expr_maybe_signaling_nan_p): New function to test whether a
> 	tree expression may be a signaling NaN (sNaN).
> 	(tree_expr_nan_p): New function to test whether a tree expression
> 	must evaluate to a (quiet or signaling) NaN.
> 	(tree_expr_maybe_nan_p): New function to test whether a tree
> 	expression me be a (quiet or signaling) NaN.
>
> 	(tree_binary_nonnegative_warnv_p) [MAX_EXPR]: In the presence
> 	of NaNs, MAX_EXPR is only guaranteed to be non-negative, if both
> 	operands are non-negative.
> 	(tree_call_nonnegative_warnv_p) [CASE_CFN_FMAX,CASE_CFN_FMAX_FN]:
> 	In the presence of signaling NaNs, fmax is only guaranteed to be
> 	non-negative if both operands are negative.  In the presence of
> 	quiet NaNs, fmax is non-negative if either operand is non-negative
> 	and not a qNaN, or both operands are non-negative.
>
> 	* fold-const.h (tree_expr_finite_p, tree_expr_infinite_p,
> 	tree_expr_maybe_infinite_p, tree_expr_signaling_nan_p,
> 	tree_expr_maybe_signaling_nan_p, tree_expr_nan_p,
> 	tree_expr_maybe_nan_p): Prototype new functions here.
>
> 	* builtins.c (fold_builtin_classify) [BUILT_IN_ISINF]: Fold to
> 	a constant if argument is known to be (or not to be) an Infinity.
> 	[BUILT_IN_ISFINITE]: Fold to a constant if argument is known to
> 	be (or not to be) finite.
> 	[BUILT_IN_ISNAN]: Fold to a constant if argument is known to be
> 	(or not to be) a NaN.
> 	(fold_builtin_fpclassify): Check tree_expr_maybe_infinite_p and
> 	tree_expr_maybe_nan_p instead of HONOR_INFINITIES and HONOR_NANS
> 	respectively.
> 	(fold_builtin_unordered_cmp): Fold UNORDERED_EXPR to a constant
> 	when its arguments are known to be (or not be) NaNs.  Check
> 	tree_expr_maybe_nan_p instead of HONOR_NANS when choosing between
> 	unordered and regular forms of comparison operators.
>
> 	* match.pd (ordered(x,y)->true/false): Constant fold ORDERED_EXPR
> 	if its operands are known to be (or not to be) NaNs.
> 	(unordered(x,y)->true/false): Constant fold UNORDERED_EXPR if its
> 	operands are known to be (or not to be) NaNs.
> 	(sqrt(x)*sqrt(x)->x): Check tree_expr_maybe_signaling_nan_p instead
> 	of HONOR_SNANS.
>
> gcc/testsuite/ChangeLog
> 	PR middle-end/85811
> 	* gcc.dg/pr85811.c: New test.
> 	* gcc.dg/fold-isfinite-1.c: New test.
> 	* gcc.dg/fold-isfinite-2.c: New test.
> 	* gcc.dg/fold-isinf-1.c: New test.
> 	* gcc.dg/fold-isinf-2.c: New test.
> 	* gcc.dg/fold-isnan-1.c: New test.
> 	* gcc.dg/fold-isnan-2.c: New test.
So as you noted in your comments, it'd be be extremely useful to be able
to track state of special FP values nan, zero, inf and the like in VRP. 
It's something we discussed repeatedly internally and we think it's
possible in the ranger infrastructure that's recently landed.  That
information opens up all kinds of interesting possibilities that you've
started to touch on in your patch.  But that's all for another day :-)


I'll pushed this to the trunk.

Thanks!  And sorry for the long delay.

jeff
diff mbox series

Patch

diff --git a/gcc/builtins.c b/gcc/builtins.c
index beb56e0..682e61b 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -9857,9 +9857,10 @@  fold_builtin_classify (location_t loc, tree fndecl, tree arg, int builtin_index)
   switch (builtin_index)
     {
     case BUILT_IN_ISINF:
-      if (!HONOR_INFINITIES (arg))
+      if (tree_expr_infinite_p (arg))
+	return omit_one_operand_loc (loc, type, integer_one_node, arg);
+      if (!tree_expr_maybe_infinite_p (arg))
 	return omit_one_operand_loc (loc, type, integer_zero_node, arg);
-
       return NULL_TREE;
 
     case BUILT_IN_ISINF_SIGN:
@@ -9895,14 +9896,16 @@  fold_builtin_classify (location_t loc, tree fndecl, tree arg, int builtin_index)
       }
 
     case BUILT_IN_ISFINITE:
-      if (!HONOR_NANS (arg)
-	  && !HONOR_INFINITIES (arg))
+      if (tree_expr_finite_p (arg))
 	return omit_one_operand_loc (loc, type, integer_one_node, arg);
-
+      if (tree_expr_nan_p (arg) || tree_expr_infinite_p (arg))
+	return omit_one_operand_loc (loc, type, integer_zero_node, arg);
       return NULL_TREE;
 
     case BUILT_IN_ISNAN:
-      if (!HONOR_NANS (arg))
+      if (tree_expr_nan_p (arg))
+	return omit_one_operand_loc (loc, type, integer_one_node, arg);
+      if (!tree_expr_maybe_nan_p (arg))
 	return omit_one_operand_loc (loc, type, integer_zero_node, arg);
 
       {
@@ -9976,7 +9979,7 @@  fold_builtin_fpclassify (location_t loc, tree *args, int nargs)
 		     arg, build_real (type, r));
   res = fold_build3_loc (loc, COND_EXPR, integer_type_node, tmp, fp_normal, res);
 
-  if (HONOR_INFINITIES (mode))
+  if (tree_expr_maybe_infinite_p (arg))
     {
       real_inf (&r);
       tmp = fold_build2_loc (loc, EQ_EXPR, integer_type_node, arg,
@@ -9985,7 +9988,7 @@  fold_builtin_fpclassify (location_t loc, tree *args, int nargs)
 			 fp_infinite, res);
     }
 
-  if (HONOR_NANS (mode))
+  if (tree_expr_maybe_nan_p (arg))
     {
       tmp = fold_build2_loc (loc, ORDERED_EXPR, integer_type_node, arg, arg);
       res = fold_build3_loc (loc, COND_EXPR, integer_type_node, tmp, res, fp_nan);
@@ -10033,12 +10036,15 @@  fold_builtin_unordered_cmp (location_t loc, tree fndecl, tree arg0, tree arg1,
 
   if (unordered_code == UNORDERED_EXPR)
     {
-      if (!HONOR_NANS (arg0))
+      if (tree_expr_nan_p (arg0) || tree_expr_nan_p (arg1))
+	return omit_two_operands_loc (loc, type, integer_one_node, arg0, arg1);
+      if (!tree_expr_maybe_nan_p (arg0) && !tree_expr_maybe_nan_p (arg1))
 	return omit_two_operands_loc (loc, type, integer_zero_node, arg0, arg1);
       return fold_build2_loc (loc, UNORDERED_EXPR, type, arg0, arg1);
     }
 
-  code = HONOR_NANS (arg0) ? unordered_code : ordered_code;
+  code = (tree_expr_maybe_nan_p (arg0) || tree_expr_maybe_nan_p (arg1))
+	 ? unordered_code : ordered_code;
   return fold_build1_loc (loc, TRUTH_NOT_EXPR, type,
 		      fold_build2_loc (loc, code, type, arg0, arg1));
 }
diff --git a/gcc/fold-const.c b/gcc/fold-const.c
index 5d27927..34167f2 100644
--- a/gcc/fold-const.c
+++ b/gcc/fold-const.c
@@ -13639,6 +13639,248 @@  multiple_of_p (tree type, const_tree top, const_tree bottom)
     }
 }
 
+/* Return true if expression X cannot be (or contain) a NaN or infinity.
+   This function returns true for integer expressions, and returns
+   false if uncertain.  */
+
+bool
+tree_expr_finite_p (const_tree x)
+{
+  machine_mode mode = element_mode (x);
+  if (!HONOR_NANS (mode) && !HONOR_INFINITIES (mode))
+    return true;
+  switch (TREE_CODE (x))
+    {
+    case REAL_CST:
+      return real_isfinite (TREE_REAL_CST_PTR (x));
+    case COMPLEX_CST:
+      return tree_expr_finite_p (TREE_REALPART (x))
+	     && tree_expr_finite_p (TREE_IMAGPART (x));
+    case FLOAT_EXPR:
+      return true;
+    case ABS_EXPR:
+    case CONVERT_EXPR:
+    case NON_LVALUE_EXPR:
+    case NEGATE_EXPR:
+    case SAVE_EXPR:
+      return tree_expr_finite_p (TREE_OPERAND (x, 0));
+    case MIN_EXPR:
+    case MAX_EXPR:
+      return tree_expr_finite_p (TREE_OPERAND (x, 0))
+	     && tree_expr_finite_p (TREE_OPERAND (x, 1));
+    case COND_EXPR:
+      return tree_expr_finite_p (TREE_OPERAND (x, 1))
+	     && tree_expr_finite_p (TREE_OPERAND (x, 2));
+    case CALL_EXPR:
+      switch (get_call_combined_fn (x))
+	{
+	CASE_CFN_FABS:
+	  return tree_expr_finite_p (CALL_EXPR_ARG (x, 0));
+	CASE_CFN_FMAX:
+	CASE_CFN_FMIN:
+	  return tree_expr_finite_p (CALL_EXPR_ARG (x, 0))
+		 && tree_expr_finite_p (CALL_EXPR_ARG (x, 1));
+	default:
+	  return false;
+	}
+
+    default:
+      return false;
+    }
+}
+
+/* Return true if expression X evaluates to an infinity.
+   This function returns false for integer expressions.  */
+
+bool
+tree_expr_infinite_p (const_tree x)
+{
+  if (!HONOR_INFINITIES (x))
+    return false;
+  switch (TREE_CODE (x))
+    {
+    case REAL_CST:
+      return real_isinf (TREE_REAL_CST_PTR (x));
+    case ABS_EXPR:
+    case NEGATE_EXPR:
+    case NON_LVALUE_EXPR:
+    case SAVE_EXPR:
+      return tree_expr_infinite_p (TREE_OPERAND (x, 0));
+    case COND_EXPR:
+      return tree_expr_infinite_p (TREE_OPERAND (x, 1))
+	     && tree_expr_infinite_p (TREE_OPERAND (x, 2));
+    default:
+      return false;
+    }
+}
+
+/* Return true if expression X could evaluate to an infinity.
+   This function returns false for integer expressions, and returns
+   true if uncertain.  */
+
+bool
+tree_expr_maybe_infinite_p (const_tree x)
+{
+  if (!HONOR_INFINITIES (x))
+    return false;
+  switch (TREE_CODE (x))
+    {
+    case REAL_CST:
+      return real_isinf (TREE_REAL_CST_PTR (x));
+    case FLOAT_EXPR:
+      return false;
+    case ABS_EXPR:
+    case NEGATE_EXPR:
+      return tree_expr_maybe_infinite_p (TREE_OPERAND (x, 0));
+    case COND_EXPR:
+      return tree_expr_maybe_infinite_p (TREE_OPERAND (x, 1))
+	     || tree_expr_maybe_infinite_p (TREE_OPERAND (x, 2));
+    default:
+      return true;
+    }
+}
+
+/* Return true if expression X evaluates to a signaling NaN.
+   This function returns false for integer expressions.  */
+
+bool
+tree_expr_signaling_nan_p (const_tree x)
+{
+  if (!HONOR_SNANS (x))
+    return false;
+  switch (TREE_CODE (x))
+    {
+    case REAL_CST:
+      return real_issignaling_nan (TREE_REAL_CST_PTR (x));
+    case NON_LVALUE_EXPR:
+    case SAVE_EXPR:
+      return tree_expr_signaling_nan_p (TREE_OPERAND (x, 0));
+    case COND_EXPR:
+      return tree_expr_signaling_nan_p (TREE_OPERAND (x, 1))
+	     && tree_expr_signaling_nan_p (TREE_OPERAND (x, 2));
+    default:
+      return false;
+    }
+}
+
+/* Return true if expression X could evaluate to a signaling NaN.
+   This function returns false for integer expressions, and returns
+   true if uncertain.  */
+
+bool
+tree_expr_maybe_signaling_nan_p (const_tree x)
+{
+  if (!HONOR_SNANS (x))
+    return false;
+  switch (TREE_CODE (x))
+    {
+    case REAL_CST:
+      return real_issignaling_nan (TREE_REAL_CST_PTR (x));
+    case FLOAT_EXPR:
+      return false;
+    case ABS_EXPR:
+    case CONVERT_EXPR:
+    case NEGATE_EXPR:
+    case NON_LVALUE_EXPR:
+    case SAVE_EXPR:
+      return tree_expr_maybe_signaling_nan_p (TREE_OPERAND (x, 0));
+    case MIN_EXPR:
+    case MAX_EXPR:
+      return tree_expr_maybe_signaling_nan_p (TREE_OPERAND (x, 0))
+	     || tree_expr_maybe_signaling_nan_p (TREE_OPERAND (x, 1));
+    case COND_EXPR:
+      return tree_expr_maybe_signaling_nan_p (TREE_OPERAND (x, 1))
+	     || tree_expr_maybe_signaling_nan_p (TREE_OPERAND (x, 2));
+    case CALL_EXPR:
+      switch (get_call_combined_fn (x))
+	{
+	CASE_CFN_FABS:
+	  return tree_expr_maybe_signaling_nan_p (CALL_EXPR_ARG (x, 0));
+	CASE_CFN_FMAX:
+	CASE_CFN_FMIN:
+	  return tree_expr_maybe_signaling_nan_p (CALL_EXPR_ARG (x, 0))
+		 || tree_expr_maybe_signaling_nan_p (CALL_EXPR_ARG (x, 1));
+	default:
+	  return true;
+	}
+    default:
+      return true;
+    }
+}
+
+/* Return true if expression X evaluates to a NaN.
+   This function returns false for integer expressions.  */
+
+bool
+tree_expr_nan_p (const_tree x)
+{
+  if (!HONOR_NANS (x))
+    return false;
+  switch (TREE_CODE (x))
+    {
+    case REAL_CST:
+      return real_isnan (TREE_REAL_CST_PTR (x));
+    case NON_LVALUE_EXPR:
+    case SAVE_EXPR:
+      return tree_expr_nan_p (TREE_OPERAND (x, 0));
+    case COND_EXPR:
+      return tree_expr_nan_p (TREE_OPERAND (x, 1))
+	     && tree_expr_nan_p (TREE_OPERAND (x, 2));
+    default:
+      return false;
+    }
+}
+
+/* Return true if expression X could evaluate to a NaN.
+   This function returns false for integer expressions, and returns
+   true if uncertain.  */
+
+bool
+tree_expr_maybe_nan_p (const_tree x)
+{
+  if (!HONOR_NANS (x))
+    return false;
+  switch (TREE_CODE (x))
+    {
+    case REAL_CST:
+      return real_isnan (TREE_REAL_CST_PTR (x));
+    case FLOAT_EXPR:
+      return false;
+    case PLUS_EXPR:
+    case MINUS_EXPR:
+    case MULT_EXPR:
+      return !tree_expr_finite_p (TREE_OPERAND (x, 0))
+	     || !tree_expr_finite_p (TREE_OPERAND (x, 1));
+    case ABS_EXPR:
+    case CONVERT_EXPR:
+    case NEGATE_EXPR:
+    case NON_LVALUE_EXPR:
+    case SAVE_EXPR:
+      return tree_expr_maybe_nan_p (TREE_OPERAND (x, 0));
+    case MIN_EXPR:
+    case MAX_EXPR:
+      return tree_expr_maybe_nan_p (TREE_OPERAND (x, 0))
+	     || tree_expr_maybe_nan_p (TREE_OPERAND (x, 1));
+    case COND_EXPR:
+      return tree_expr_maybe_nan_p (TREE_OPERAND (x, 1))
+	     || tree_expr_maybe_nan_p (TREE_OPERAND (x, 2));
+    case CALL_EXPR:
+      switch (get_call_combined_fn (x))
+	{
+	CASE_CFN_FABS:
+	  return tree_expr_maybe_nan_p (CALL_EXPR_ARG (x, 0));
+	CASE_CFN_FMAX:
+	CASE_CFN_FMIN:
+	  return tree_expr_maybe_nan_p (CALL_EXPR_ARG (x, 0))
+		 || tree_expr_maybe_nan_p (CALL_EXPR_ARG (x, 1));
+	default:
+	  return true;
+	}
+    default:
+      return true;
+    }
+}
+
 #define tree_expr_nonnegative_warnv_p(X, Y) \
   _Pragma ("GCC error \"Use RECURSE for recursive calls\"") 0
 
@@ -13816,7 +14058,13 @@  tree_binary_nonnegative_warnv_p (enum tree_code code, tree type, tree op0,
       return false;
 
     case BIT_AND_EXPR:
+      return RECURSE (op0) || RECURSE (op1);
+
     case MAX_EXPR:
+      /* Usually RECURSE (op0) || RECURSE (op1) but NaNs complicate
+	 things.  */
+      if (tree_expr_maybe_nan_p (op0) || tree_expr_maybe_nan_p (op1))
+	return RECURSE (op0) && RECURSE (op1);
       return RECURSE (op0) || RECURSE (op1);
 
     case BIT_IOR_EXPR:
@@ -13976,8 +14224,18 @@  tree_call_nonnegative_warnv_p (tree type, combined_fn fn, tree arg0, tree arg1,
 
     CASE_CFN_FMAX:
     CASE_CFN_FMAX_FN:
-      /* True if the 1st OR 2nd arguments are nonnegative.  */
-      return RECURSE (arg0) || RECURSE (arg1);
+      /* Usually RECURSE (arg0) || RECURSE (arg1) but NaNs complicate
+	 things.  In the presence of sNaNs, we're only guaranteed to be
+	 non-negative if both operands are non-negative.  In the presence
+	 of qNaNs, we're non-negative if either operand is non-negative
+	 and can't be a qNaN, or if both operands are non-negative.  */
+      if (tree_expr_maybe_signaling_nan_p (arg0) ||
+	  tree_expr_maybe_signaling_nan_p (arg1))
+        return RECURSE (arg0) && RECURSE (arg1);
+      return RECURSE (arg0) ? (!tree_expr_maybe_nan_p (arg0)
+			       || RECURSE (arg1))
+			    : (RECURSE (arg1)
+			       && !tree_expr_maybe_nan_p (arg1));
 
     CASE_CFN_FMIN:
     CASE_CFN_FMIN_FN:
diff --git a/gcc/fold-const.h b/gcc/fold-const.h
index 0f788a4..dfb98b1 100644
--- a/gcc/fold-const.h
+++ b/gcc/fold-const.h
@@ -186,6 +186,13 @@  extern tree non_lvalue_loc (location_t, tree);
 extern bool tree_expr_nonzero_p (tree);
 extern bool tree_expr_nonnegative_p (tree);
 extern bool tree_expr_nonnegative_warnv_p (tree, bool *, int = 0);
+extern bool tree_expr_finite_p (const_tree);
+extern bool tree_expr_infinite_p (const_tree);
+extern bool tree_expr_maybe_infinite_p (const_tree);
+extern bool tree_expr_signaling_nan_p (const_tree);
+extern bool tree_expr_maybe_signaling_nan_p (const_tree);
+extern bool tree_expr_nan_p (const_tree);
+extern bool tree_expr_maybe_nan_p (const_tree);
 extern tree make_range (tree, int *, tree *, tree *, bool *);
 extern tree make_range_step (location_t, enum tree_code, tree, tree, tree,
 			     tree *, tree *, int *, bool *);
diff --git a/gcc/match.pd b/gcc/match.pd
index c3b8816..48e266e 100644
--- a/gcc/match.pd
+++ b/gcc/match.pd
@@ -4874,6 +4874,24 @@  DEFINE_INT_AND_FLOAT_ROUND_FN (RINT)
    { constant_boolean_node (cmp == ORDERED_EXPR || cmp == LTGT_EXPR
 			    ? false : true, type); })))
 
+/* Fold UNORDERED if either operand must be NaN, or neither can be.  */
+(simplify
+  (unordered @0 @1)
+  (switch
+    (if (tree_expr_nan_p (@0) || tree_expr_nan_p (@1))
+	{ constant_boolean_node (true, type); })
+    (if (!tree_expr_maybe_nan_p (@0) && !tree_expr_maybe_nan_p (@1))
+	{ constant_boolean_node (false, type); })))
+
+/* Fold ORDERED if either operand must be NaN, or neither can be.  */
+(simplify
+  (ordered @0 @1)
+  (switch
+    (if (tree_expr_nan_p (@0) || tree_expr_nan_p (@1))
+	{ constant_boolean_node (false, type); })
+    (if (!tree_expr_maybe_nan_p (@0) && !tree_expr_maybe_nan_p (@1))
+	{ constant_boolean_node (true, type); })))
+
 /* bool_var != 0 becomes bool_var.  */
 (simplify
  (ne @0 integer_zerop)
@@ -5001,7 +5019,7 @@  DEFINE_INT_AND_FLOAT_ROUND_FN (RINT)
  /* Simplify sqrt(x) * sqrt(x) -> x.  */
  (simplify
   (mult (SQRT_ALL@1 @0) @1)
-  (if (!HONOR_SNANS (type))
+  (if (!tree_expr_maybe_signaling_nan_p (@0))
    @0))
 
  (for op (plus minus)