On Wed, 12 Aug 2026, Tamar Christina wrote:

> This adds support for matching FMS where the reassoc has left the top level
> operation as a PLUS_MINUS.
> 
> i.e. matching
> 
>        c.real + (a.imag * b.imag - a.real * b.real)
>        c.imag - (a.real * b.imag + a.imag * b.real)
> 
> which happens when the accumulator is not the same as the destination.
> i.e.
> 
> d[i] c[i] - (a[i] * b[i])
> 
> vs
> 
> c[i] -= a[i] * b[i]
> 
> Bootstrapped Regtested on aarch64-none-linux-gnu,
> arm-none-linux-gnueabihf, x86_64-pc-linux-gnu
> -m32, -m64 and no issues.
> 
> Any comments?

LGTM

> Thanks,
> Tamar
> 
> gcc/ChangeLog:
> 
>       * tree-vect-slp-patterns.cc (complex_fms_pattern::matches): Check
>       another form of FMS.
> 
> gcc/testsuite/ChangeLog:
> 
>       * gfortran.target/aarch64/pr122408_1.f90: update scans to check for FMS.
>       * gcc.dg/vect/complex/complex-mul2.c: New test.
>       * gcc.dg/vect/complex/fast-math-complex-mls-manual-invalid-float.c: New 
> test.
>       * gcc.dg/vect/complex/fast-math-complex-mls-outofplace-double.c: New 
> test.
>       * gcc.dg/vect/complex/fast-math-complex-mls-outofplace-float.c: New 
> test.
> 
> ---
> diff --git a/gcc/testsuite/gcc.dg/vect/complex/complex-mul2.c 
> b/gcc/testsuite/gcc.dg/vect/complex/complex-mul2.c
> new file mode 100644
> index 
> 0000000000000000000000000000000000000000..6a45ebdbec8c91dfae98f40f910c7a5d3f2ffbf5
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/vect/complex/complex-mul2.c
> @@ -0,0 +1,67 @@
> +/* { dg-do compile } */
> +/* { dg-additional-options "-O3 -fcx-limited-range -fno-signed-zeros" } */
> +/* { dg-require-effective-target vect_complex_add_double } */
> +/* { dg-add-options arm_v8_3a_complex_neon } */
> +
> +extern void abort (void);
> +
> +#define N 16
> +
> +#define DEF(NAME, TYPE, EXPR)                                  \
> +  __attribute__((noipa)) void                                  \
> +  NAME (_Complex TYPE *__restrict d, _Complex TYPE *__restrict c,\
> +       _Complex TYPE *__restrict a, _Complex TYPE *__restrict b,\
> +       int n)                                                  \
> +  {                                                            \
> +    for (int i = 0; i < n; i++)                                        \
> +      d[i] = EXPR;                                             \
> +  }                                                            \
> +                                                               \
> +  __attribute__((noipa, optimize ("no-tree-vectorize"))) void  \
> +  NAME##_ref (_Complex TYPE *__restrict d, _Complex TYPE *__restrict c,\
> +             _Complex TYPE *__restrict a, _Complex TYPE *__restrict b,\
> +             int n)                                            \
> +  {                                                            \
> +    for (int i = 0; i < n; i++)                                        \
> +      d[i] = EXPR;                                             \
> +  }
> +
> +DEF (fms_f, float, c[i] - a[i] * b[i])
> +DEF (fms_d, double, c[i] - a[i] * b[i])
> +DEF (fmsconj_f, float, c[i] - a[i] * ~b[i])
> +DEF (mul_f, float, a[i] * b[i])
> +
> +#define CHECK(NAME, TYPE)                                      \
> +  do {                                                         \
> +    _Complex TYPE a[N], b[N], c[N], d[N], ref[N];              \
> +    for (int i = 0; i < N; i++)                                        \
> +      {                                                                \
> +       __real__ a[i] = i + 1;                                  \
> +       __imag__ a[i] = 2 * i + 3;                              \
> +       __real__ b[i] = 3 * i - 1;                              \
> +       __imag__ b[i] = i + 5;                                  \
> +       __real__ c[i] = 100 + i;                                \
> +       __imag__ c[i] = 200 - i;                                \
> +      }                                                                \
> +    NAME (d, c, a, b, N);                                      \
> +    NAME##_ref (ref, c, a, b, N);                              \
> +    _Pragma("novect")                                               \
> +    for (int i = 0; i < N; i++)                                        \
> +      if (__real__ d[i] != __real__ ref[i]                     \
> +         || __imag__ d[i] != __imag__ ref[i])                  \
> +       abort ();                                               \
> +  } while (0)
> +
> +int
> +main (void)
> +{
> +  CHECK (fms_f, float);
> +  CHECK (fms_d, double);
> +  CHECK (fmsconj_f, float);
> +  CHECK (mul_f, float);
> +  return 0;
> +}
> +
> +/* { dg-final { scan-tree-dump "add new stmt: \[^\n\r]*COMPLEX_FMS \\(" 
> "vect" } } */
> +/* { dg-final { scan-tree-dump "add new stmt: \[^\n\r]*COMPLEX_FMS_CONJ" 
> "vect" } } */
> +/* { dg-final { scan-tree-dump "add new stmt: \[^\n\r]*COMPLEX_MUL \\(" 
> "vect" } } */
> diff --git 
> a/gcc/testsuite/gcc.dg/vect/complex/fast-math-complex-mls-manual-invalid-float.c
>  
> b/gcc/testsuite/gcc.dg/vect/complex/fast-math-complex-mls-manual-invalid-float.c
> new file mode 100644
> index 
> 0000000000000000000000000000000000000000..3062878edf6e7dea5b0b51be132d5506b274cb14
> --- /dev/null
> +++ 
> b/gcc/testsuite/gcc.dg/vect/complex/fast-math-complex-mls-manual-invalid-float.c
> @@ -0,0 +1,27 @@
> +/* { dg-do compile } */
> +/* { dg-additional-options "-O3 -fcx-limited-range -fno-signed-zeros" } */
> +/* { dg-require-effective-target vect_complex_add_float } */
> +/* { dg-add-options arm_v8_3a_complex_neon } */
> +
> +void
> +manual_invalid_fms (float *__restrict d, float *__restrict c,
> +                 float *__restrict a, float *__restrict b)
> +{
> +  for (int r = 0; r < 100; r += 2)
> +    {
> +      int i = r + 1;
> +      float cr = c[r];
> +      float ci = c[i];
> +      float ar = a[r];
> +      float ai = a[i];
> +      float br = b[r];
> +      float bi = b[i];
> +      d[r] = cr + ar * br - ai * bi;
> +      d[i] = ci - (ar * bi + ai * br);
> +    }
> +}
> +
> +/* { dg-final { scan-tree-dump-not "Found COMPLEX_FMS pattern" "vect" } } */
> +/* { dg-final { scan-tree-dump-not "Found COMPLEX_FMS_CONJ" "vect" } } */
> +/* { dg-final { scan-tree-dump-not "Found COMPLEX_MUL" "vect" } } */
> +/* { dg-final { scan-tree-dump-not "add new stmt: \[^\n\r]*COMPLEX_FMS" 
> "vect" } } */
> diff --git 
> a/gcc/testsuite/gcc.dg/vect/complex/fast-math-complex-mls-outofplace-double.c 
> b/gcc/testsuite/gcc.dg/vect/complex/fast-math-complex-mls-outofplace-double.c
> new file mode 100644
> index 
> 0000000000000000000000000000000000000000..3253b3b2d344f450daa96a38f0134328cad6ea92
> --- /dev/null
> +++ 
> b/gcc/testsuite/gcc.dg/vect/complex/fast-math-complex-mls-outofplace-double.c
> @@ -0,0 +1,17 @@
> +/* { dg-do compile } */
> +/* { dg-additional-options "-O3 -fcx-limited-range -fno-signed-zeros 
> -fdump-tree-vect-details" } */
> +/* { dg-require-effective-target vect_complex_add_double } */
> +/* { dg-add-options arm_v8_3a_complex_neon } */
> +
> +void
> +fms_out (_Complex double *__restrict d, _Complex double *__restrict c,
> +      _Complex double *__restrict a, _Complex double *__restrict b,
> +      int n)
> +{
> +  for (int i = 0; i < n; ++i)
> +    d[i] = c[i] - a[i] * b[i];
> +}
> +
> +/* { dg-final { scan-tree-dump "add new stmt: \[^\n\r]*COMPLEX_FMS \\(" 
> "vect" } } */
> +/* { dg-final { scan-tree-dump-not "Found COMPLEX_FMS_CONJ" "vect" } } */
> +/* { dg-final { scan-tree-dump-not "Found COMPLEX_MUL" "vect" } } */
> diff --git 
> a/gcc/testsuite/gcc.dg/vect/complex/fast-math-complex-mls-outofplace-float.c 
> b/gcc/testsuite/gcc.dg/vect/complex/fast-math-complex-mls-outofplace-float.c
> new file mode 100644
> index 
> 0000000000000000000000000000000000000000..51e6b0d8762842ab006115c7299305857fb3a362
> --- /dev/null
> +++ 
> b/gcc/testsuite/gcc.dg/vect/complex/fast-math-complex-mls-outofplace-float.c
> @@ -0,0 +1,17 @@
> +/* { dg-do compile } */
> +/* { dg-additional-options "-O3 -fcx-limited-range -fno-signed-zeros 
> -fdump-tree-vect-details" } */
> +/* { dg-require-effective-target vect_complex_add_float } */
> +/* { dg-add-options arm_v8_3a_complex_neon } */
> +
> +void
> +fms_out (_Complex float *__restrict d, _Complex float *__restrict c,
> +      _Complex float *__restrict a, _Complex float *__restrict b,
> +      int n)
> +{
> +  for (int i = 0; i < n; ++i)
> +    d[i] = c[i] - a[i] * b[i];
> +}
> +
> +/* { dg-final { scan-tree-dump "add new stmt: \[^\n\r]*COMPLEX_FMS \\(" 
> "vect" { xfail arm*-*-* } } } */
> +/* { dg-final { scan-tree-dump-not "Found COMPLEX_FMS_CONJ" "vect" } } */
> +/* { dg-final { scan-tree-dump-not "Found COMPLEX_MUL" "vect" } } */
> diff --git a/gcc/testsuite/gfortran.target/aarch64/pr122408_1.f90 
> b/gcc/testsuite/gfortran.target/aarch64/pr122408_1.f90
> index 
> b06950d1baae7d8aca2e1c96c8e6e942781b71bd..d62330d4a7760235bf25179b072d986bbe87c653
>  100644
> --- a/gcc/testsuite/gfortran.target/aarch64/pr122408_1.f90
> +++ b/gcc/testsuite/gfortran.target/aarch64/pr122408_1.f90
> @@ -57,10 +57,10 @@ subroutine c_sub_a_conjb(n, a, c, b)    ! C -= A * conj(B)
>    end do
>  end subroutine c_sub_a_conjb
>  
> -! The two plain multiplies form .COMPLEX_MUL (#0 + #90) and the two conjugate
> -! multiplies form .COMPLEX_MUL_CONJ (#0 + #270).  PR122408 is about detecting
> -! the conjugate form, so the #270 and #180 counts are the ones that guard it.
> -! { dg-final { scan-assembler-times {fcmla\s+v[0-9]+.2d, v[0-9]+.2d, 
> v[0-9]+.2d, #0} 4 } }
> +! The accumulations form .COMPLEX_FMA (#0 + #90) and .COMPLEX_FMS (#180 +
> +! #270), with the conjugate forms swapping the #90 and #270 rotations.  
> PR122408
> +! is about detecting the conjugate form, so the #90 and #270 counts guard it.
> +! { dg-final { scan-assembler-times {fcmla\s+v[0-9]+.2d, v[0-9]+.2d, 
> v[0-9]+.2d, #0} 2 } }
>  ! { dg-final { scan-assembler-times {fcmla\s+v[0-9]+.2d, v[0-9]+.2d, 
> v[0-9]+.2d, #90} 2 } }
>  ! { dg-final { scan-assembler-times {fcmla\s+v[0-9]+.2d, v[0-9]+.2d, 
> v[0-9]+.2d, #270} 2 } }
> -! { dg-final { scan-assembler-not {fcmla\s+v[0-9]+.2d, v[0-9]+.2d, 
> v[0-9]+.2d, #180} } }
> +! { dg-final { scan-assembler-times {fcmla\s+v[0-9]+.2d, v[0-9]+.2d, 
> v[0-9]+.2d, #180} 2 } }
> diff --git a/gcc/tree-vect-slp-patterns.cc b/gcc/tree-vect-slp-patterns.cc
> index 
> f77993100ad19ce5502900022a68b39201d24a54..aae000ee5292ccb1521928e43f514a14386a7827
>  100644
> --- a/gcc/tree-vect-slp-patterns.cc
> +++ b/gcc/tree-vect-slp-patterns.cc
> @@ -1305,7 +1305,94 @@ complex_fms_pattern::matches (complex_operation_t op,
>                             slp_compat_nodes_map_t *compat_cache,
>                             slp_tree * ref_node, vec<slp_tree> *ops)
>  {
> -  internal_fn ifn = IFN_LAST;
> +  /* It's only valid to form FMSs with -ffp-contract=fast.  */
> +  if (!SLP_TREE_VECTYPE (*ref_node)
> +      || (flag_fp_contract_mode != FP_CONTRACT_FAST
> +       && FLOAT_TYPE_P (SLP_TREE_VECTYPE (*ref_node))))
> +    return IFN_LAST;
> +
> +  /* Match c - a * b when SLP has built the result as:
> +
> +       c.real + (a.imag * b.imag - a.real * b.real)
> +       c.imag - (a.real * b.imag + a.imag * b.real)
> +
> +     This represents the same operation as the existing FMS matcher below,
> +     but with the accumulator outside the complex product node.  */
> +  if (op == PLUS_MINUS)
> +    {
> +      auto plus_ops = SLP_TREE_CHILDREN ((*ops)[0]);
> +      auto minus_ops = SLP_TREE_CHILDREN ((*ops)[1]);
> +      if (plus_ops.length () != 2 || minus_ops.length () != 2)
> +     return IFN_LAST;
> +
> +      slp_tree acc = minus_ops[0];
> +      slp_tree prod = minus_ops[1];
> +      if (!((plus_ops[0] == acc && plus_ops[1] == prod)
> +         || (plus_ops[1] == acc && plus_ops[0] == prod)))
> +     return IFN_LAST;
> +      if (linear_loads_p (perm_cache, acc) != PERM_EVENODD)
> +     return IFN_LAST;
> +
> +      auto_vec<slp_tree> prod_ops;
> +      if (vect_detect_pair_op (prod, true, &prod_ops) != MINUS_PLUS)
> +     return IFN_LAST;
> +      if (prod_ops.length () != 2)
> +     return IFN_LAST;
> +
> +      auto prod_left = SLP_TREE_CHILDREN (prod_ops[0]);
> +      auto prod_right = SLP_TREE_CHILDREN (prod_ops[1]);
> +      if (prod_left.length () != 2
> +       || prod_right.length () != 2
> +       || !vect_match_expression_p (prod_left[0], MULT_EXPR)
> +       || !vect_match_expression_p (prod_left[1], MULT_EXPR)
> +       || !vect_match_expression_p (prod_right[0], MULT_EXPR)
> +       || !vect_match_expression_p (prod_right[1], MULT_EXPR))
> +     return IFN_LAST;
> +
> +      auto_vec<slp_tree> left_op, right_op;
> +      left_op.safe_splice (SLP_TREE_CHILDREN (prod_left[0]));
> +      right_op.safe_splice (SLP_TREE_CHILDREN (prod_left[1]));
> +
> +      enum _conj_status status;
> +      auto_vec<slp_tree> res_ops;
> +      if (!vect_validate_multiplication_commutative (perm_cache, 
> compat_cache,
> +                                                  right_op, left_op, true,
> +                                                  res_ops, &status))
> +     return IFN_LAST;
> +
> +      internal_fn ifn = status == CONJ_NONE ? IFN_COMPLEX_FMS
> +                                         : IFN_COMPLEX_FMS_CONJ;
> +      if (!vect_pattern_validate_optab (ifn, *ref_node))
> +     return IFN_LAST;
> +
> +      ops->truncate (0);
> +      ops->create (4);
> +
> +      complex_perm_kinds_t kind = linear_loads_p (perm_cache, res_ops[0]);
> +      if (kind == PERM_EVENODD || kind == PERM_TOP)
> +     {
> +       ops->quick_push (acc);
> +       ops->quick_push (res_ops[0]);
> +       ops->quick_push (res_ops[1]);
> +       ops->quick_push (res_ops[3]);
> +     }
> +      else if (kind == PERM_EVENEVEN && status != CONJ_SND)
> +     {
> +       ops->quick_push (acc);
> +       ops->quick_push (res_ops[1]);
> +       ops->quick_push (res_ops[0]);
> +       ops->quick_push (res_ops[2]);
> +     }
> +      else
> +     {
> +       ops->quick_push (acc);
> +       ops->quick_push (res_ops[1]);
> +       ops->quick_push (res_ops[0]);
> +       ops->quick_push (res_ops[3]);
> +     }
> +
> +      return ifn;
> +    }
>  
>    /* We need to ignore the two_operands nodes that may also match,
>       for that we can check if they have any scalar statements and also
> @@ -1318,11 +1405,6 @@ complex_fms_pattern::matches (complex_operation_t op,
>    if (!vect_match_expression_p (root, MINUS_EXPR))
>      return IFN_LAST;
>  
> -  /* It's only valid to form FMSs with -ffp-contract=fast.  */
> -  if (flag_fp_contract_mode != FP_CONTRACT_FAST
> -      && FLOAT_TYPE_P (SLP_TREE_VECTYPE (*ref_node)))
> -    return IFN_LAST;
> -
>    /* TODO: Support invariants here, with the new layout CADD now
>          can match before we get a chance to try CFMS.  */
>    auto nodes = SLP_TREE_CHILDREN (root);
> @@ -1352,11 +1434,8 @@ complex_fms_pattern::matches (complex_operation_t op,
>                                                res_ops, &status))
>      return IFN_LAST;
>  
> -  if (status == CONJ_NONE)
> -    ifn = IFN_COMPLEX_FMS;
> -  else
> -    ifn = IFN_COMPLEX_FMS_CONJ;
> -
> +  internal_fn ifn = status == CONJ_NONE ? IFN_COMPLEX_FMS
> +                                     : IFN_COMPLEX_FMS_CONJ;
>    if (!vect_pattern_validate_optab (ifn, *ref_node))
>      return IFN_LAST;
>  
> 
> 
> 

-- 
Richard Biener <[email protected]>
SUSE Software Solutions Germany GmbH,
Frankenstrasse 146, 90461 Nuernberg, Germany;
GF: Jochen Jaser, Andrew McDonald, Abhinav Puri; (HRB 36809, AG Nuernberg)

Reply via email to