GCC expands __builtin_isnan, __builtin_isinf, __builtin_isfinite and
__builtin_isnormal into FP comparisons on alpha, and by default those
comparisons carry no software-completion suffix:

  isnan_d:
        cmptun $f16,$f16,$f10
        fbne $f10,$L11

A subnormal operand raises a denormal-operand exception there, and the
outcome depends on the FPCR denormal-operand-disable bit: the operand is
flushed to zero, which makes a subnormal indistinguishable from a zero,
or the trap is delivered with no completion information, which is fatal:

  $ cat cls.c
  #include <math.h>
  int main (void)
  {
    volatile double sub = 0x1p-1050;
    return fpclassify (sub);
  }
  $ ./cls
  Floating point exception (core dumped)

-mieee is the documented way to get subnormal support, so this is a
matter of quality of implementation rather than conformance, but the
classification built-ins exist to detect exactly these values and should
not kill the process when given one.  Classify by inspecting the encoding
in an integer register instead, as the AArch64 isinf, isfinite and isnan
expanders do for PR66462.  Defining these patterns also stops
fold_builtin_interclass_mathfn from rewriting the built-ins into
comparisons in the first place.

With -mieee the comparisons are completed by the kernel and are cheaper,
so keep the generic expansions there, except with -fsignaling-nans: a
comparison raises an invalid-operation exception for a signaling NaN,
which is the PR66462 case that AArch64 and LoongArch handle the same way.

gcc/ChangeLog:

        PR middle-end/66462
        * config/alpha/alpha-protos.h (enum alpha_fp_class): New.
        (alpha_expand_fp_classify): Declare.
        * config/alpha/alpha.cc (alpha_expand_fp_classify): New function.
        * config/alpha/alpha.md (isfinite<mode>2, isinf<mode>2)
        (isnan<mode>2, isnormal<mode>2): New expanders.

gcc/testsuite/ChangeLog:

        PR middle-end/66462
        * gcc.target/alpha/fp-classify-1.c: New test.
        * gcc.target/alpha/fp-classify-2.c: New test.
        * gcc.target/alpha/fp-classify-3.c: New test.
        * gcc.target/alpha/fp-classify-4.c: New test.
---
 gcc/config/alpha/alpha-protos.h               | 11 +++
 gcc/config/alpha/alpha.cc                     | 71 +++++++++++++++++++
 gcc/config/alpha/alpha.md                     | 44 ++++++++++++
 .../gcc.target/alpha/fp-classify-1.c          | 17 +++++
 .../gcc.target/alpha/fp-classify-2.c          |  9 +++
 .../gcc.target/alpha/fp-classify-3.c          | 10 +++
 .../gcc.target/alpha/fp-classify-4.c          | 65 +++++++++++++++++
 7 files changed, 227 insertions(+)
 create mode 100644 gcc/testsuite/gcc.target/alpha/fp-classify-1.c
 create mode 100644 gcc/testsuite/gcc.target/alpha/fp-classify-2.c
 create mode 100644 gcc/testsuite/gcc.target/alpha/fp-classify-3.c
 create mode 100644 gcc/testsuite/gcc.target/alpha/fp-classify-4.c

diff --git ./gcc/config/alpha/alpha-protos.h ./gcc/config/alpha/alpha-protos.h
index c64121d1f3f..66a1b5a6a6b 100644
--- ./gcc/config/alpha/alpha-protos.h
+++ ./gcc/config/alpha/alpha-protos.h
@@ -48,6 +48,17 @@ extern bool alpha_expand_mov_nobwx (machine_mode, rtx *);
 extern void alpha_expand_movmisalign (machine_mode, rtx *);
 extern void alpha_emit_floatuns (rtx[]);
 extern rtx alpha_emit_conditional_move (rtx, machine_mode);
+
+/* Selects the test performed by alpha_expand_fp_classify.  */
+enum alpha_fp_class
+{
+  ALPHA_FPCLASS_FINITE,
+  ALPHA_FPCLASS_INF,
+  ALPHA_FPCLASS_NAN,
+  ALPHA_FPCLASS_NORMAL
+};
+
+extern void alpha_expand_fp_classify (rtx, rtx, enum alpha_fp_class);
 extern void alpha_split_tmode_pair (rtx[], machine_mode, bool);
 extern void alpha_split_tfmode_frobsign (rtx[], rtx (*)(rtx, rtx, rtx));
 extern void alpha_expand_unaligned_load (rtx, rtx, HOST_WIDE_INT,
diff --git ./gcc/config/alpha/alpha.cc ./gcc/config/alpha/alpha.cc
index f70eebc8f18..ed947e2d6bc 100644
--- ./gcc/config/alpha/alpha.cc
+++ ./gcc/config/alpha/alpha.cc
@@ -2803,6 +2803,77 @@ alpha_emit_setcc (rtx operands[], machine_mode cmp_mode)
 }
 
 
+/* Expand a floating-point classification of OP, storing 0 or 1 into
+   TARGET.  KIND selects the test performed.
+
+   The FP comparison instructions cannot be used for this.  Without
+   software completion -- which the compiler only arranges for -mieee,
+   i.e. alpha_fptm >= ALPHA_FPTM_SU -- a subnormal operand is either
+   flushed to zero, which makes it indistinguishable from a zero, or
+   traps with no completion information, which is fatal.  They also
+   raise an invalid-operation exception for a signaling NaN.  Examine
+   the encoding in an integer register instead.  */
+
+void
+alpha_expand_fp_classify (rtx target, rtx op, enum alpha_fp_class kind)
+{
+  machine_mode mode = GET_MODE (op);
+  const struct real_format *fmt = REAL_MODE_FORMAT (mode);
+  int width = GET_MODE_BITSIZE (as_a <scalar_float_mode> (mode));
+  int mant_bits = fmt->p - 1;
+  int exp_bits = width - mant_bits - 1;
+  HOST_WIDE_INT exp_max = (HOST_WIDE_INT_1 << exp_bits) - 1;
+  rtx bits, mag, exp, mant, res, tmp;
+
+  /* Move the raw encoding into an integer register, zero extended to
+     DImode so that the shifts below are independent of MODE.  */
+  bits = force_lowpart_subreg (int_mode_for_mode (mode).require (), op, mode);
+  bits = convert_to_mode (DImode, bits, 1);
+
+  /* Shifting out the sign bit leaves the magnitude left justified.  */
+  mag = expand_shift (LSHIFT_EXPR, DImode, bits, 65 - width, NULL_RTX, 1);
+  exp = expand_shift (RSHIFT_EXPR, DImode, mag, 64 - exp_bits, NULL_RTX, 1);
+
+  switch (kind)
+    {
+    case ALPHA_FPCLASS_FINITE:
+      /* The exponent field is not all ones.  */
+      res = emit_store_flag_force (NULL_RTX, NE, exp, GEN_INT (exp_max),
+                                  DImode, 0, 1);
+      break;
+
+    case ALPHA_FPCLASS_NORMAL:
+      /* The exponent field is neither zero nor all ones, i.e.
+        (unsigned) (exp - 1) < exp_max - 1.  */
+      tmp = expand_binop (DImode, sub_optab, exp, const1_rtx, NULL_RTX,
+                         1, OPTAB_LIB_WIDEN);
+      res = emit_store_flag_force (NULL_RTX, LTU, tmp, GEN_INT (exp_max - 1),
+                                  DImode, 1, 1);
+      break;
+
+    case ALPHA_FPCLASS_INF:
+    case ALPHA_FPCLASS_NAN:
+      /* The exponent field is all ones, and the significand field is zero
+        for an infinity, nonzero for a NaN.  */
+      mant = expand_shift (LSHIFT_EXPR, DImode, bits, 64 - mant_bits,
+                          NULL_RTX, 1);
+      tmp = emit_store_flag_force (NULL_RTX, EQ, exp, GEN_INT (exp_max),
+                                  DImode, 0, 1);
+      res = emit_store_flag_force (NULL_RTX,
+                                  kind == ALPHA_FPCLASS_NAN ? NE : EQ,
+                                  mant, const0_rtx, DImode, 0, 1);
+      res = expand_binop (DImode, and_optab, tmp, res, NULL_RTX,
+                         1, OPTAB_LIB_WIDEN);
+      break;
+
+    default:
+      gcc_unreachable ();
+    }
+
+  emit_move_insn (target, gen_lowpart (GET_MODE (target), res));
+}
+
+
 /* Rewrite a comparison against zero CMP of the form
    (CODE (cc0) (const_int 0)) so it can be written validly in
    a conditional move (if_then_else CMP ...).
diff --git ./gcc/config/alpha/alpha.md ./gcc/config/alpha/alpha.md
index a0235c61325..4e67436294a 100644
--- ./gcc/config/alpha/alpha.md
+++ ./gcc/config/alpha/alpha.md
@@ -2855,6 +2855,50 @@
     FAIL;
 })
 
+;; Floating-point classification.  Defining these patterns also stops
+;; fold_builtin_interclass_mathfn from rewriting the built-ins into FP
+;; comparisons, which are unusable here; see alpha_expand_fp_classify.
+
+(define_expand "isfinite<mode>2"
+  [(match_operand:SI 0 "register_operand")
+   (match_operand:FMODE 1 "register_operand")]
+  "TARGET_FP && !TARGET_FLOAT_VAX
+   && (alpha_fptm < ALPHA_FPTM_SU || flag_signaling_nans)"
+{
+  alpha_expand_fp_classify (operands[0], operands[1], ALPHA_FPCLASS_FINITE);
+  DONE;
+})
+
+(define_expand "isinf<mode>2"
+  [(match_operand:SI 0 "register_operand")
+   (match_operand:FMODE 1 "register_operand")]
+  "TARGET_FP && !TARGET_FLOAT_VAX
+   && (alpha_fptm < ALPHA_FPTM_SU || flag_signaling_nans)"
+{
+  alpha_expand_fp_classify (operands[0], operands[1], ALPHA_FPCLASS_INF);
+  DONE;
+})
+
+(define_expand "isnan<mode>2"
+  [(match_operand:SI 0 "register_operand")
+   (match_operand:FMODE 1 "register_operand")]
+  "TARGET_FP && !TARGET_FLOAT_VAX
+   && (alpha_fptm < ALPHA_FPTM_SU || flag_signaling_nans)"
+{
+  alpha_expand_fp_classify (operands[0], operands[1], ALPHA_FPCLASS_NAN);
+  DONE;
+})
+
+(define_expand "isnormal<mode>2"
+  [(match_operand:SI 0 "register_operand")
+   (match_operand:FMODE 1 "register_operand")]
+  "TARGET_FP && !TARGET_FLOAT_VAX
+   && (alpha_fptm < ALPHA_FPTM_SU || flag_signaling_nans)"
+{
+  alpha_expand_fp_classify (operands[0], operands[1], ALPHA_FPCLASS_NORMAL);
+  DONE;
+})
+
 (define_expand "cstoretf4"
   [(use (match_operator:DI 1 "alpha_cbranch_operator"
          [(match_operand:TF 2 "general_operand")
diff --git ./gcc/testsuite/gcc.target/alpha/fp-classify-1.c 
./gcc/testsuite/gcc.target/alpha/fp-classify-1.c
new file mode 100644
index 00000000000..b1bac48cf76
--- /dev/null
+++ ./gcc/testsuite/gcc.target/alpha/fp-classify-1.c
@@ -0,0 +1,17 @@
+/* The floating-point classification built-ins must not be expanded into FP
+   comparisons: without software completion (-mieee) a comparison against a
+   subnormal operand either traps or has the operand flushed to zero.  */
+
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+
+int isnan_f (float x) { return __builtin_isnan (x); }
+int isnan_d (double x) { return __builtin_isnan (x); }
+int isinf_f (float x) { return __builtin_isinf (x); }
+int isinf_d (double x) { return __builtin_isinf (x); }
+int isfinite_f (float x) { return __builtin_isfinite (x); }
+int isfinite_d (double x) { return __builtin_isfinite (x); }
+int isnormal_f (float x) { return __builtin_isnormal (x); }
+int isnormal_d (double x) { return __builtin_isnormal (x); }
+
+/* { dg-final { scan-assembler-not "\tcmpt" } } */
diff --git ./gcc/testsuite/gcc.target/alpha/fp-classify-2.c 
./gcc/testsuite/gcc.target/alpha/fp-classify-2.c
new file mode 100644
index 00000000000..9bcf2401234
--- /dev/null
+++ ./gcc/testsuite/gcc.target/alpha/fp-classify-2.c
@@ -0,0 +1,9 @@
+/* With -mieee the comparisons carry the software-completion suffix and are
+   usable, so the generic expansions are kept.  */
+
+/* { dg-do compile } */
+/* { dg-options "-O2 -mieee" } */
+
+int isnan_d (double x) { return __builtin_isnan (x); }
+
+/* { dg-final { scan-assembler "cmptun/su" } } */
diff --git ./gcc/testsuite/gcc.target/alpha/fp-classify-3.c 
./gcc/testsuite/gcc.target/alpha/fp-classify-3.c
new file mode 100644
index 00000000000..47272887359
--- /dev/null
+++ ./gcc/testsuite/gcc.target/alpha/fp-classify-3.c
@@ -0,0 +1,10 @@
+/* With -fsignaling-nans the comparisons are unusable even with -mieee,
+   since cmptun raises an invalid-operation exception for a signaling NaN.  */
+
+/* { dg-do compile } */
+/* { dg-options "-O2 -mieee -fsignaling-nans" } */
+
+int isnan_f (float x) { return __builtin_isnan (x); }
+int isnan_d (double x) { return __builtin_isnan (x); }
+
+/* { dg-final { scan-assembler-not "\tcmpt" } } */
diff --git ./gcc/testsuite/gcc.target/alpha/fp-classify-4.c 
./gcc/testsuite/gcc.target/alpha/fp-classify-4.c
new file mode 100644
index 00000000000..e32ea1d486d
--- /dev/null
+++ ./gcc/testsuite/gcc.target/alpha/fp-classify-4.c
@@ -0,0 +1,65 @@
+/* Without -mieee the isnan, isinf, isfinite and isnormal built-ins 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_isnan (x) != (class == NAN_))
+    __builtin_abort ();
+  if (__builtin_isinf (x) != (class == INF_))
+    __builtin_abort ();
+  if (__builtin_isfinite (x) != (class != NAN_ && class != INF_))
+    __builtin_abort ();
+  if (__builtin_isnormal (x) != (class == NORMAL_))
+    __builtin_abort ();
+}
+
+static void
+t_d (double x, int class)
+{
+  if (__builtin_isnan (x) != (class == NAN_))
+    __builtin_abort ();
+  if (__builtin_isinf (x) != (class == INF_))
+    __builtin_abort ();
+  if (__builtin_isfinite (x) != (class != NAN_ && class != INF_))
+    __builtin_abort ();
+  if (__builtin_isnormal (x) != (class == NORMAL_))
+    __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