A target defines the isnan, isinf and isnormal optabs when FP comparisons
are unsuitable for classification, and fold_builtin_interclass_mathfn
leaves those built-ins alone when it does.  fold_builtin_fpclassify does
not: it always expands into comparisons, so __builtin_fpclassify keeps
the problems the individual built-ins were fixed to avoid.  On
AArch64 with -fsignaling-nans, where all three optabs are now available,
fpclassify still raises FE_INVALID for a signaling NaN, as it does on
LoongArch, which defines the same three optabs under that flag; on Alpha
without -mieee a comparison against a subnormal operand traps or flushes
it to zero, so FP_SUBNORMAL cannot be told from FP_ZERO.

When all three optabs are available for the mode, build fpclassify on the
corresponding built-ins instead, and test zero versus subnormal by shifting
the sign bit out of the encoding rather than by comparing against zero,
which would reintroduce the same problem.  That shift is only valid for a binary
format whose sign is a single bit at the top of the matching integer mode,
so formats like XFmode, whose encoding is narrower than that mode, and the
composite formats, whose sign bit cannot be shifted out, keep the generic
expansion.

With -fsignaling-nans on AArch64, fpclassify of a double becomes:

        fmov    x1, d0
        mov     x2, -9007199254740992
        mov     w0, 0
        cmp     x2, x1, lsl 1
        bcc     .L1
        mov     w0, 1
        cmp     x2, x1, lsl 1
        beq     .L1
        ubfx    x2, x1, 52, 11
        mov     w0, 4
        sub     x2, x2, #1
        cmp     x2, 2045
        bls     .L1
        cmp     xzr, x1, lsl 1
        cset    w0, ne
        add     w0, w0, 2
.L1:
        ret

gcc/ChangeLog:

        PR middle-end/66462
        * builtins.cc (fold_builtin_fpclassify): Expand using the
        classification built-ins when their optabs are available.

gcc/testsuite/ChangeLog:

        PR middle-end/66462
        * gcc.target/aarch64/pr66462-2.c: New test.
        * gcc.target/alpha/fp-classify-5.c: New test.
        * gcc.target/alpha/fp-classify-6.c: New test.
---
 gcc/builtins.cc                               | 73 +++++++++++++++
 gcc/testsuite/gcc.target/aarch64/pr66462-2.c  | 91 +++++++++++++++++++
 .../gcc.target/alpha/fp-classify-5.c          | 20 ++++
 .../gcc.target/alpha/fp-classify-6.c          | 55 +++++++++++
 4 files changed, 239 insertions(+)
 create mode 100644 gcc/testsuite/gcc.target/aarch64/pr66462-2.c
 create mode 100644 gcc/testsuite/gcc.target/alpha/fp-classify-5.c
 create mode 100644 gcc/testsuite/gcc.target/alpha/fp-classify-6.c

diff --git ./gcc/builtins.cc ./gcc/builtins.cc
index 056fd20c4c6..8f4c323417c 100644
--- ./gcc/builtins.cc
+++ ./gcc/builtins.cc
@@ -10012,6 +10012,79 @@ fold_builtin_fpclassify (location_t loc, tree *args, 
int nargs)
   arg = args[5];
   type = TREE_TYPE (arg);
   mode = TYPE_MODE (type);
+
+  /* A target defines the classification optabs when FP comparisons are
+     unsuitable for classification, for instance because they raise
+     FE_INVALID for a signaling NaN (PR middle-end/66462).  Build on the
+     built-ins in that case rather than on comparisons.  */
+  if (optab_handler (isnan_optab, mode) != CODE_FOR_nothing
+      && optab_handler (isinf_optab, mode) != CODE_FOR_nothing
+      && optab_handler (isnormal_optab, mode) != CODE_FOR_nothing)
+    {
+      tree isnan_fn = builtin_decl_explicit (BUILT_IN_ISNAN);
+      tree isinf_fn = builtin_decl_explicit (BUILT_IN_ISINF);
+      tree isnormal_fn = builtin_decl_explicit (BUILT_IN_ISNORMAL);
+      const struct real_format *fmt = REAL_MODE_FORMAT (mode);
+      tree itype = NULL_TREE;
+      scalar_int_mode imode;
+
+      /* The remaining zero versus subnormal test must not use a comparison
+        either.  Testing the magnitude of the encoding for zero works
+        whenever the format is binary, has a matching integer mode and
+        stores its sign in the high bit of it; that excludes e.g. XFmode,
+        whose encoding is narrower than its integer mode, and the composite
+        formats, whose sign is not a single bit that can be shifted out.  */
+      if (fmt->b == 2
+         && int_mode_for_mode (mode).exists (&imode)
+         && fmt->signbit_rw == fmt->signbit_ro
+         && fmt->signbit_rw == (int) GET_MODE_PRECISION (imode) - 1)
+       {
+         itype = lang_hooks.types.type_for_mode (imode, 1);
+         if (itype && TYPE_PRECISION (itype) != GET_MODE_PRECISION (imode))
+           itype = NULL_TREE;
+       }
+
+      if (isnan_fn && isinf_fn && isnormal_fn && itype)
+       {
+         arg = builtin_save_expr (arg);
+
+         /* Shifting the sign bit out leaves zero iff ARG is +-0.  */
+         tmp = fold_build1_loc (loc, VIEW_CONVERT_EXPR, itype, arg);
+         tmp = fold_build2_loc (loc, LSHIFT_EXPR, itype, tmp,
+                                build_int_cst (integer_type_node, 1));
+         tmp = fold_build2_loc (loc, EQ_EXPR, integer_type_node, tmp,
+                                build_int_cst (itype, 0));
+         res = fold_build3_loc (loc, COND_EXPR, integer_type_node,
+                                tmp, fp_zero, fp_subnormal);
+
+         tmp = build_call_expr_loc (loc, isnormal_fn, 1, arg);
+         tmp = fold_build2_loc (loc, NE_EXPR, integer_type_node, tmp,
+                                integer_zero_node);
+         res = fold_build3_loc (loc, COND_EXPR, integer_type_node, tmp,
+                                fp_normal, res);
+
+         if (tree_expr_maybe_infinite_p (arg))
+           {
+             tmp = build_call_expr_loc (loc, isinf_fn, 1, arg);
+             tmp = fold_build2_loc (loc, NE_EXPR, integer_type_node, tmp,
+                                    integer_zero_node);
+             res = fold_build3_loc (loc, COND_EXPR, integer_type_node, tmp,
+                                    fp_infinite, res);
+           }
+
+         if (tree_expr_maybe_nan_p (arg))
+           {
+             tmp = build_call_expr_loc (loc, isnan_fn, 1, arg);
+             tmp = fold_build2_loc (loc, NE_EXPR, integer_type_node, tmp,
+                                    integer_zero_node);
+             res = fold_build3_loc (loc, COND_EXPR, integer_type_node, tmp,
+                                    fp_nan, res);
+           }
+
+         return res;
+       }
+    }
+
   arg = builtin_save_expr (fold_build1_loc (loc, ABS_EXPR, type, arg));
 
   /* fpclassify(x) ->
diff --git ./gcc/testsuite/gcc.target/aarch64/pr66462-2.c 
./gcc/testsuite/gcc.target/aarch64/pr66462-2.c
new file mode 100644
index 00000000000..6a2cb559fdb
--- /dev/null
+++ ./gcc/testsuite/gcc.target/aarch64/pr66462-2.c
@@ -0,0 +1,91 @@
+/* { dg-do run } */
+/* { dg-options "-O2 -fsignaling-nans -fno-inline" } */
+/* { dg-require-effective-target fenv_exceptions } */
+
+/* With -fsignaling-nans the isnan, isinf and isnormal optabs are all
+   available, so __builtin_fpclassify must be built on them rather than on
+   FP comparisons, which raise FE_INVALID for a signaling NaN.  */
+
+#include <fenv.h>
+
+enum { NAN_, INF_, NORMAL_, SUBNORMAL_, ZERO_ };
+
+static void t_fpclassf (float x, int res)
+{
+  if (__builtin_fpclassify (NAN_, INF_, NORMAL_, SUBNORMAL_, ZERO_, x) != res)
+    __builtin_abort ();
+  if (__builtin_fpclassify (NAN_, INF_, NORMAL_, SUBNORMAL_, ZERO_, -x) != res)
+    __builtin_abort ();
+  if (fetestexcept (FE_INVALID))
+    __builtin_abort ();
+}
+
+static void t_fpclass (double x, int res)
+{
+  if (__builtin_fpclassify (NAN_, INF_, NORMAL_, SUBNORMAL_, ZERO_, x) != res)
+    __builtin_abort ();
+  if (__builtin_fpclassify (NAN_, INF_, NORMAL_, SUBNORMAL_, ZERO_, -x) != res)
+    __builtin_abort ();
+  if (fetestexcept (FE_INVALID))
+    __builtin_abort ();
+}
+
+#define DEF_TEST(NAME, TYPE)                                           \
+static void NAME (TYPE x, int res)                                     \
+{                                                                      \
+  if (__builtin_fpclassify (NAN_, INF_, NORMAL_, SUBNORMAL_, ZERO_, x) \
+      != res)                                                          \
+    __builtin_abort ();                                                        
\
+  if (__builtin_fpclassify (NAN_, INF_, NORMAL_, SUBNORMAL_, ZERO_, -x)        
\
+      != res)                                                          \
+    __builtin_abort ();                                                        
\
+  if (fetestexcept (FE_INVALID))                                       \
+    __builtin_abort ();                                                        
\
+}
+
+DEF_TEST (t_fpclass16, _Float16)
+DEF_TEST (t_fpclassbf, __bf16)
+
+int
+main ()
+{
+  feclearexcept (FE_INVALID);
+
+  t_fpclassf (0.0f, ZERO_);
+  t_fpclassf (1.0f, NORMAL_);
+  t_fpclassf (__FLT_MIN__, NORMAL_);
+  t_fpclassf (__FLT_MAX__, NORMAL_);
+  t_fpclassf (__FLT_DENORM_MIN__, SUBNORMAL_);
+  t_fpclassf (__builtin_inff (), INF_);
+  t_fpclassf (__builtin_nansf (""), NAN_);
+  t_fpclassf (__builtin_nanf (""), NAN_);
+
+  t_fpclass (0.0, ZERO_);
+  t_fpclass (1.0, NORMAL_);
+  t_fpclass (__DBL_MIN__, NORMAL_);
+  t_fpclass (__DBL_MAX__, NORMAL_);
+  t_fpclass (__DBL_DENORM_MIN__, SUBNORMAL_);
+  t_fpclass (__builtin_inf (), INF_);
+  t_fpclass (__builtin_nans (""), NAN_);
+  t_fpclass (__builtin_nan (""), NAN_);
+
+  t_fpclass16 (0.0f16, ZERO_);
+  t_fpclass16 (1.0f16, NORMAL_);
+  t_fpclass16 (__FLT16_MIN__, NORMAL_);
+  t_fpclass16 (__FLT16_MAX__, NORMAL_);
+  t_fpclass16 (__FLT16_DENORM_MIN__, SUBNORMAL_);
+  t_fpclass16 ((_Float16) __builtin_inff (), INF_);
+  t_fpclass16 (__builtin_nansf16 (""), NAN_);
+  t_fpclass16 (__builtin_nanf16 (""), NAN_);
+
+  t_fpclassbf (0.0bf16, ZERO_);
+  t_fpclassbf (1.0bf16, NORMAL_);
+  t_fpclassbf (__BFLT16_MIN__, NORMAL_);
+  t_fpclassbf (__BFLT16_MAX__, NORMAL_);
+  t_fpclassbf (__BFLT16_DENORM_MIN__, SUBNORMAL_);
+  t_fpclassbf ((__bf16) __builtin_inff (), INF_);
+  t_fpclassbf (__builtin_nansf16b (""), NAN_);
+  t_fpclassbf ((__bf16) __builtin_nanf (""), NAN_);
+
+  return 0;
+}
diff --git ./gcc/testsuite/gcc.target/alpha/fp-classify-5.c 
./gcc/testsuite/gcc.target/alpha/fp-classify-5.c
new file mode 100644
index 00000000000..d47bb716ee7
--- /dev/null
+++ ./gcc/testsuite/gcc.target/alpha/fp-classify-5.c
@@ -0,0 +1,20 @@
+/* __builtin_fpclassify must not be expanded into FP comparisons either: a
+   comparison cannot tell FP_SUBNORMAL from FP_ZERO when the subnormal
+   operand is flushed to zero, and traps when it is not.  */
+
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+
+int
+fpclassify_f (float x)
+{
+  return __builtin_fpclassify (0, 1, 4, 3, 2, x);
+}
+
+int
+fpclassify_d (double x)
+{
+  return __builtin_fpclassify (0, 1, 4, 3, 2, x);
+}
+
+/* { dg-final { scan-assembler-not "\tcmpt" } } */
diff --git ./gcc/testsuite/gcc.target/alpha/fp-classify-6.c 
./gcc/testsuite/gcc.target/alpha/fp-classify-6.c
new file mode 100644
index 00000000000..45812b2357e
--- /dev/null
+++ ./gcc/testsuite/gcc.target/alpha/fp-classify-6.c
@@ -0,0 +1,55 @@
+/* Without -mieee __builtin_fpclassify must classify subnormals, infinities
+   and NaNs correctly, without trapping.  */
+
+/* { dg-do run } */
+/* { dg-options "-O2 -fno-inline" } */
+
+enum { NAN_, INF_, NORMAL_, SUBNORMAL_, ZERO_ };
+
+static void
+t_f (float x, int class)
+{
+  if (__builtin_fpclassify (NAN_, INF_, NORMAL_, SUBNORMAL_, ZERO_, x)
+      != class)
+    __builtin_abort ();
+}
+
+static void
+t_d (double x, int class)
+{
+  if (__builtin_fpclassify (NAN_, INF_, NORMAL_, SUBNORMAL_, ZERO_, x)
+      != class)
+    __builtin_abort ();
+}
+
+/* Keep the operands away from the constant folders.  */
+volatile float vf[] = {
+  0.0f, -0.0f, 1.0f, -1.0f, __FLT_MIN__, __FLT_MAX__, -__FLT_MAX__,
+  __FLT_DENORM_MIN__, -__FLT_DENORM_MIN__, __FLT_MIN__ / 2,
+  __builtin_inff (), -__builtin_inff (), __builtin_nanf (""),
+  -__builtin_nanf ("")
+};
+
+volatile double vd[] = {
+  0.0, -0.0, 1.0, -1.0, __DBL_MIN__, __DBL_MAX__, -__DBL_MAX__,
+  __DBL_DENORM_MIN__, -__DBL_DENORM_MIN__, __DBL_MIN__ / 2,
+  __builtin_inf (), -__builtin_inf (), __builtin_nan (""),
+  -__builtin_nan ("")
+};
+
+const int classes[] = {
+  ZERO_, ZERO_, NORMAL_, NORMAL_, NORMAL_, NORMAL_, NORMAL_,
+  SUBNORMAL_, SUBNORMAL_, SUBNORMAL_,
+  INF_, INF_, NAN_, NAN_
+};
+
+int
+main ()
+{
+  for (unsigned i = 0; i < sizeof classes / sizeof classes[0]; i++)
+    {
+      t_f (vf[i], classes[i]);
+      t_d (vd[i], classes[i]);
+    }
+  return 0;
+}
-- 
2.54.0

Reply via email to