From: Kyrylo Tkachov <[email protected]>

FCMLA only accumulates, so a plain complex multiply was built by seeding the
accumulator with +0.0.  That turns a -0.0 product into +0.0, so the fix for
PR126589 gave the cmul optabs a !HONOR_SIGNED_ZEROS requirement, which means
FCMLA is only reached with -ffast-math and not with -fcx-limited-range alone.

The first rotation of the pair computes

  Vd.re = Vd.re + Vn.re * Vm.re
  Vd.im = Vd.im + Vn.re * Vm.im

that is, a multiply of Vm by the real part of Vn broadcast over its own
pair.  TRN1 of Vn with itself is that broadcast, so the accumulator is not
needed at all:

  trn1  vt.4s, vn.4s, vn.4s
  fmul  vd.4s, vt.4s, vm.4s
  fcmla vd.4s, vn.4s, vm.4s, #90

The sign of a zero now survives, and there is one fewer accumulate.  Where
signed zeros are not honoured the previous sequence is kept: MOVI with a zero
immediate is a zeroing idiom, so seeding the accumulator there is free, while
materialising any other constant is not.

The same applies to the SVE expander.

On Neoverse V2, over arrays of 4096 _Complex float at -O3 -mcpu=neoverse-v2
-fcx-limited-range, c[i] = a[i] * b[i] drops 5.9% and y[i] += alpha * x[i]
drops 21.8% in run time.  -ffast-math is unchanged.

Bootstrapped and tested on aarch64-none-linux-gnu.
Ok for trunk?
Thanks,
Kyrill

gcc/ChangeLog:

        PR target/126589
        * config/aarch64/aarch64-protos.h (aarch64_emit_cmul_first_half):
        Declare.
        * config/aarch64/aarch64.cc (aarch64_emit_cmul_first_half): New
        function.
        * config/aarch64/aarch64-simd.md (cmul<conj_op><mode>3): Use it when
        signed zeros are honoured, and drop the requirement.
        * config/aarch64/aarch64-sve.md (cmul<conj_op><mode>3): Likewise.

gcc/testsuite/ChangeLog:

        PR target/126589
        * gcc.target/aarch64/complex-mul-signed-zeros.c: New test.
        * gcc.target/aarch64/sve/complex-mul-signed-zeros.c: New test.
        * gcc.dg/vect/complex/complex-mul-signed-zero-run.c: New test.

Signed-off-by: Kyrylo Tkachov <[email protected]>
---
 gcc/config/aarch64/aarch64-protos.h           |   1 +
 gcc/config/aarch64/aarch64-simd.md            |  19 +++-
 gcc/config/aarch64/aarch64-sve.md             |  19 +++-
 gcc/config/aarch64/aarch64.cc                 |  24 ++++
 .../complex/complex-mul-signed-zero-run.c     | 105 ++++++++++++++++++
 .../aarch64/complex-mul-signed-zeros.c        |  39 +++++++
 .../aarch64/sve/complex-mul-signed-zeros.c    |  27 +++++
 7 files changed, 224 insertions(+), 10 deletions(-)
 create mode 100644 
gcc/testsuite/gcc.dg/vect/complex/complex-mul-signed-zero-run.c
 create mode 100644 gcc/testsuite/gcc.target/aarch64/complex-mul-signed-zeros.c
 create mode 100644 
gcc/testsuite/gcc.target/aarch64/sve/complex-mul-signed-zeros.c

diff --git a/gcc/config/aarch64/aarch64-protos.h 
b/gcc/config/aarch64/aarch64-protos.h
index bcc833cfaa1..24668aca125 100644
--- a/gcc/config/aarch64/aarch64-protos.h
+++ b/gcc/config/aarch64/aarch64-protos.h
@@ -1047,6 +1047,7 @@ rtx aarch64_convert_sve_data_to_pred (rtx, rtx);
 rtx aarch64_expand_sve_dupq (rtx, machine_mode, rtx);
 void aarch64_expand_mov_immediate (rtx, rtx);
 rtx aarch64_stack_protect_canary_mem (machine_mode, rtx, aarch64_salt_type);
+void aarch64_emit_cmul_first_half (rtx, rtx, rtx);
 rtx aarch64_ptrue_reg (machine_mode);
 rtx aarch64_ptrue_reg (machine_mode, unsigned int);
 rtx aarch64_ptrue_reg (machine_mode, machine_mode);
diff --git a/gcc/config/aarch64/aarch64-simd.md 
b/gcc/config/aarch64/aarch64-simd.md
index 8ad1ec20f46..48ca6b10d51 100644
--- a/gcc/config/aarch64/aarch64-simd.md
+++ b/gcc/config/aarch64/aarch64-simd.md
@@ -681,12 +681,23 @@
        (unspec:VHSDF [(match_operand:VHSDF 1 "register_operand")
                       (match_operand:VHSDF 2 "register_operand")]
                       FCMUL_OP))]
-  "TARGET_COMPLEX && !BYTES_BIG_ENDIAN && !HONOR_SIGNED_ZEROS (<MODE>mode)"
+  "TARGET_COMPLEX && !BYTES_BIG_ENDIAN"
 {
-  rtx tmp = force_reg (<MODE>mode, CONST0_RTX (<MODE>mode));
   rtx res1 = gen_reg_rtx (<MODE>mode);
-  emit_insn (gen_aarch64_fcmla<rotsplit1><mode> (res1, tmp,
-                                                operands[2], operands[1]));
+  if (HONOR_SIGNED_ZEROS (<MODE>mode))
+    /* The first rotation multiplies the real part of operand 2 by the whole
+       of operand 1, so express it as a multiply rather than as an accumulate
+       into zero.  Accumulating into +0.0 would turn a -0.0 product into
+       +0.0.  */
+    aarch64_emit_cmul_first_half (res1, operands[2], operands[1]);
+  else
+    {
+      /* MOVI with a zero immediate is a zeroing idiom, so seeding the
+        accumulator here is free.  */
+      rtx zero = force_reg (<MODE>mode, CONST0_RTX (<MODE>mode));
+      emit_insn (gen_aarch64_fcmla<rotsplit1><mode> (res1, zero,
+                                                    operands[2], operands[1]));
+    }
   emit_insn (gen_aarch64_fcmla<rotsplit2><mode> (operands[0], res1,
                                                 operands[2], operands[1]));
   DONE;
diff --git a/gcc/config/aarch64/aarch64-sve.md 
b/gcc/config/aarch64/aarch64-sve.md
index 5f9a19c42e9..539030ab1bc 100644
--- a/gcc/config/aarch64/aarch64-sve.md
+++ b/gcc/config/aarch64/aarch64-sve.md
@@ -8300,16 +8300,23 @@
           [(match_operand:SVE_FULL_F 1 "register_operand")
            (match_operand:SVE_FULL_F 2 "register_operand")]
          FCMUL_OP))]
-  "TARGET_SVE && !HONOR_SIGNED_ZEROS (<MODE>mode)"
+  "TARGET_SVE"
 {
   rtx pred_reg = aarch64_ptrue_reg (<VPRED>mode);
   rtx gp_mode = gen_int_mode (SVE_RELAXED_GP, SImode);
-  rtx accum = force_reg (<MODE>mode, CONST0_RTX (<MODE>mode));
   rtx tmp = gen_reg_rtx (<MODE>mode);
-  emit_insn
-    (gen_aarch64_pred_fcmla<sve_rot1><mode> (tmp, pred_reg,
-                                            operands[2], operands[1],
-                                            accum, gp_mode));
+  if (HONOR_SIGNED_ZEROS (<MODE>mode))
+    /* Accumulating into +0.0 would turn a -0.0 product into +0.0, so do the
+       first rotation as the equivalent explicit multiply.  */
+    aarch64_emit_cmul_first_half (tmp, operands[2], operands[1]);
+  else
+    {
+      rtx accum = force_reg (<MODE>mode, CONST0_RTX (<MODE>mode));
+      emit_insn
+       (gen_aarch64_pred_fcmla<sve_rot1><mode> (tmp, pred_reg,
+                                                operands[2], operands[1],
+                                                accum, gp_mode));
+    }
   emit_insn
     (gen_aarch64_pred_fcmla<sve_rot2><mode> (operands[0], pred_reg,
                                             operands[2], operands[1],
diff --git a/gcc/config/aarch64/aarch64.cc b/gcc/config/aarch64/aarch64.cc
index 3041a6ee62a..1a2c578381c 100644
--- a/gcc/config/aarch64/aarch64.cc
+++ b/gcc/config/aarch64/aarch64.cc
@@ -4229,6 +4229,30 @@ aarch64_ptrue_reg (machine_mode pred_mode, machine_mode 
data_mode)
   return aarch64_ptrue_reg (pred_mode, size);
 }
 
+/* Emit into TARGET the first half of the complex multiplication of A by B,
+   that is the pair (Are * Bre, Are * Bim) for each complex element.  This is
+   what FCMLA with a rotation of #0 computes, but as a plain multiply, so no
+   additive identity is needed and the sign of a zero product survives.
+   TARGET, A and B all have the same floating-point vector mode.  */
+
+void
+aarch64_emit_cmul_first_half (rtx target, rtx a, rtx b)
+{
+  machine_mode mode = GET_MODE (target);
+  rtx dup_real = gen_reg_rtx (mode);
+  /* TRN1 of a vector with itself broadcasts each even element over the
+     odd one, which for complex data is the real part over its own pair.
+     The two inputs are the same register, so no big-endian correction is
+     needed here.  */
+  emit_set_insn (dup_real,
+                gen_rtx_UNSPEC (mode, gen_rtvec (2, a, a), UNSPEC_TRN1));
+  rtx res = expand_binop (mode, smul_optab, dup_real, b, target, 0,
+                         OPTAB_DIRECT);
+  gcc_assert (res);
+  if (res != target)
+    emit_move_insn (target, res);
+}
+
 /* Return an all-false predicate register of mode MODE.  */
 
 rtx
diff --git a/gcc/testsuite/gcc.dg/vect/complex/complex-mul-signed-zero-run.c 
b/gcc/testsuite/gcc.dg/vect/complex/complex-mul-signed-zero-run.c
new file mode 100644
index 00000000000..0e5f98144c6
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/vect/complex/complex-mul-signed-zero-run.c
@@ -0,0 +1,105 @@
+/* { dg-do run } */
+/* { dg-require-effective-target vect_complex_add_double } */
+/* { dg-additional-options "-fcx-limited-range" } */
+/* { dg-add-options arm_v8_3a_complex_neon } */
+
+/* Vectorised complex multiplication must keep the sign of a zero result
+   when -fno-signed-zeros is not in effect.  */
+
+#include <math.h>
+
+#define N 64
+
+_Complex float af[N], bf[N], cf[N], reff[N];
+_Complex double ad[N], bd[N], cd[N], refd[N];
+
+__attribute__((noipa)) void
+mulf (_Complex float *__restrict c, _Complex float *__restrict a,
+      _Complex float *__restrict b, int n)
+{
+  for (int i = 0; i < n; i++)
+    c[i] = a[i] * b[i];
+}
+
+__attribute__((noipa, optimize ("no-tree-vectorize"))) void
+mulf_ref (_Complex float *__restrict c, _Complex float *__restrict a,
+         _Complex float *__restrict b, int n)
+{
+  for (int i = 0; i < n; i++)
+    c[i] = a[i] * b[i];
+}
+
+__attribute__((noipa)) void
+muld (_Complex double *__restrict c, _Complex double *__restrict a,
+      _Complex double *__restrict b, int n)
+{
+  for (int i = 0; i < n; i++)
+    c[i] = a[i] * b[i];
+}
+
+__attribute__((noipa, optimize ("no-tree-vectorize"))) void
+muld_ref (_Complex double *__restrict c, _Complex double *__restrict a,
+         _Complex double *__restrict b, int n)
+{
+  for (int i = 0; i < n; i++)
+    c[i] = a[i] * b[i];
+}
+
+/* Compare bit patterns so that the sign of a zero matters.  Any two NaNs
+   are equal, their sign and payload are unspecified.  */
+
+static int
+eqf (float x, float y)
+{
+  if (isnan (x) && isnan (y))
+    return 1;
+  return __builtin_memcmp (&x, &y, sizeof x) == 0;
+}
+
+static int
+eqd (double x, double y)
+{
+  if (isnan (x) && isnan (y))
+    return 1;
+  return __builtin_memcmp (&x, &y, sizeof x) == 0;
+}
+
+static const float vals[]
+  = { 0.0f, -0.0f, 1.0f, -1.0f, 3.5f, -2.25f,
+      __builtin_inff (), -__builtin_inff (), __builtin_nanf ("") };
+#define NV ((int) (sizeof (vals) / sizeof (vals[0])))
+
+int
+main (void)
+{
+  for (int i0 = 0; i0 < NV; i0++)
+    for (int i1 = 0; i1 < NV; i1++)
+      for (int i2 = 0; i2 < NV; i2++)
+       for (int i3 = 0; i3 < NV; i3++)
+         {
+           for (int k = 0; k < N; k++)
+             {
+               __real__ af[k] = vals[i0];
+               __imag__ af[k] = vals[i1];
+               __real__ bf[k] = vals[i2];
+               __imag__ bf[k] = vals[i3];
+               __real__ ad[k] = vals[i0];
+               __imag__ ad[k] = vals[i1];
+               __real__ bd[k] = vals[i2];
+               __imag__ bd[k] = vals[i3];
+             }
+
+           mulf (cf, af, bf, N);
+           mulf_ref (reff, af, bf, N);
+           muld (cd, ad, bd, N);
+           muld_ref (refd, ad, bd, N);
+
+           for (int k = 0; k < N; k++)
+             if (!eqf (__real__ cf[k], __real__ reff[k])
+                 || !eqf (__imag__ cf[k], __imag__ reff[k])
+                 || !eqd (__real__ cd[k], __real__ refd[k])
+                 || !eqd (__imag__ cd[k], __imag__ refd[k]))
+               __builtin_abort ();
+         }
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.target/aarch64/complex-mul-signed-zeros.c 
b/gcc/testsuite/gcc.target/aarch64/complex-mul-signed-zeros.c
new file mode 100644
index 00000000000..a15accf8e07
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/complex-mul-signed-zeros.c
@@ -0,0 +1,39 @@
+/* { dg-do compile } */
+/* { dg-options "-O3 -march=armv8.3-a+nosve -fcx-limited-range" } */
+
+/* FCMLA only accumulates, so seeding it with +0.0 to get a plain complex
+   multiply would turn a -0.0 product into +0.0.  Doing the first rotation as
+   an explicit multiply keeps the sign of a zero and needs no accumulator, so
+   this works without -fno-signed-zeros.  */
+
+void
+mulf (_Complex float *__restrict c, _Complex float *__restrict a,
+      _Complex float *__restrict b, int n)
+{
+  for (int i = 0; i < n; i++)
+    c[i] = a[i] * b[i];
+}
+
+void
+muld (_Complex double *__restrict c, _Complex double *__restrict a,
+      _Complex double *__restrict b, int n)
+{
+  for (int i = 0; i < n; i++)
+    c[i] = a[i] * b[i];
+}
+
+void
+mulconjf (_Complex float *__restrict c, _Complex float *__restrict a,
+         _Complex float *__restrict b, int n)
+{
+  for (int i = 0; i < n; i++)
+    c[i] = a[i] * ~b[i];
+}
+
+/* { dg-final { scan-assembler {trn1\tv[0-9]+\.4s, v[0-9]+\.4s, v[0-9]+\.4s} } 
} */
+/* { dg-final { scan-assembler {trn1\tv[0-9]+\.2d, v[0-9]+\.2d, v[0-9]+\.2d} } 
} */
+/* { dg-final { scan-assembler {fcmla\tv[0-9]+\.4s, v[0-9]+\.4s, v[0-9]+\.4s, 
#90} } } */
+/* { dg-final { scan-assembler {fcmla\tv[0-9]+\.2d, v[0-9]+\.2d, v[0-9]+\.2d, 
#90} } } */
+/* { dg-final { scan-assembler {fcmla\tv[0-9]+\.4s, v[0-9]+\.4s, v[0-9]+\.4s, 
#270} } } */
+/* No accumulator is needed, so no zero constant is materialised.  */
+/* { dg-final { scan-assembler-not {fcmla\tv[0-9]+\.[24][sd], 
v[0-9]+\.[24][sd], v[0-9]+\.[24][sd], #0} } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/sve/complex-mul-signed-zeros.c 
b/gcc/testsuite/gcc.target/aarch64/sve/complex-mul-signed-zeros.c
new file mode 100644
index 00000000000..a79332460f5
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/sve/complex-mul-signed-zeros.c
@@ -0,0 +1,27 @@
+/* { dg-do compile } */
+/* { dg-options "-O3 -march=armv8.3-a+sve -msve-vector-bits=scalable 
-fcx-limited-range" } */
+
+/* The SVE complex multiply also does the first rotation as an explicit
+   multiply, so it does not need -fno-signed-zeros either.  */
+
+void
+mulf (_Complex float *__restrict c, _Complex float *__restrict a,
+      _Complex float *__restrict b, int n)
+{
+  for (int i = 0; i < n; i++)
+    c[i] = a[i] * b[i];
+}
+
+void
+muld (_Complex double *__restrict c, _Complex double *__restrict a,
+      _Complex double *__restrict b, int n)
+{
+  for (int i = 0; i < n; i++)
+    c[i] = a[i] * b[i];
+}
+
+/* { dg-final { scan-assembler {trn1\tz[0-9]+\.s, z[0-9]+\.s, z[0-9]+\.s} } } 
*/
+/* { dg-final { scan-assembler {trn1\tz[0-9]+\.d, z[0-9]+\.d, z[0-9]+\.d} } } 
*/
+/* { dg-final { scan-assembler {fcmla\tz[0-9]+\.s, p[0-9]+/m, z[0-9]+\.s, 
z[0-9]+\.s, #90} } } */
+/* { dg-final { scan-assembler {fcmla\tz[0-9]+\.d, p[0-9]+/m, z[0-9]+\.d, 
z[0-9]+\.d, #90} } } */
+/* { dg-final { scan-assembler-not {fcmla\tz[0-9]+\.[sd], p[0-9]+/m, 
z[0-9]+\.[sd], z[0-9]+\.[sd], #0} } } */
-- 
2.50.1 (Apple Git-155)

Reply via email to