From: Ezra Sitorus <[email protected]>

Recognise complex integer dot product patterns and try to emit complex
dot product instructions if supported. In particular, recognise loops
like this:

struct cint16_t
{
  int8_t re;
  int8_t im;
};

for (int i = 0; i < N; i++)
  res += (a[i].re * b[i].re - a[i].im * b[i].im);

which would be transformed into this from previous patches:

acc_1 = DOT_PROD<a_re, b_re, acc_0>;
acc_2 = 0 - acc_1;
acc_3 = DOT_PROD<a_im, b_im, acc_2>;
acc_4 = 0 - acc_3;

and then try to emit:

CDOT (a, b, acc)

in the case of AArch64 with SVE2 support.

gcc/ChangeLog:
        * tree-vect-slp-patterns.cc
          (slp_tree_zero_p, vect_build_complex_dot_prod_load, 
complex_re_im_pair_p,
          class complex_dot_prod_pattern): New.
          (complex_operations_pattern::recognize): Add support for
          complex_dot_prod_pattern::matches.

gcc/testsuite/ChangeLog:
        * testsuite/gcc.dg/vect/complex/bb-slp-complex-dot-int8.c: New
          test.

Here are some questions I have:

* Why (not) not use a VEC_PERM_EXPR for vect_build_complex_dot_prod_load?

Something like this?
static slp_tree
vect_build_complex_dot_prod_load (slp_tree re, slp_tree im)
{
  lane_permutation_t perm;
  perm.create (2);
  perm.quick_push (std::make_pair (0, 0));
  perm.quick_push (std::make_pair (1, 0));

  slp_tree complex = vect_create_new_slp_node (2, VEC_PERM_EXPR);
  SLP_TREE_LANE_PERMUTATION (complex) = perm;

  SLP_TREE_VECTYPE (complex) = SLP_TREE_VECTYPE (re);
  SLP_TREE_REF_COUNT (complex) = 1;
  SLP_TREE_LANES (complex) = 2;
  SLP_TREE_REPRESENTATIVE (complex) = NULL;

  SLP_TREE_CHILDREN (complex).quick_push (re);
  SLP_TREE_CHILDREN (complex).quick_push (im);
  SLP_TREE_REF_COUNT (re)++;
  SLP_TREE_REF_COUNT (im)++;

  gcc_assert (STMT_VINFO_GROUPED_ACCESS (SLP_TREE_REPRESENTATIVE (re)));
  gcc_assert (STMT_VINFO_GROUPED_ACCESS (SLP_TREE_REPRESENTATIVE (im)));
  SLP_TREE_SCALAR_STMTS (complex).safe_push (SLP_TREE_SCALAR_STMTS (re)[0]);
  SLP_TREE_SCALAR_STMTS (complex).safe_push (SLP_TREE_SCALAR_STMTS (im)[0]);

  return complex;
}

It seems to generate the same codegen as the current patch.
---
 .../vect/complex/bb-slp-complex-dot-int8.c    |  25 ++
 gcc/tree-vect-slp-patterns.cc                 | 358 ++++++++++++++++++
 2 files changed, 383 insertions(+)
 create mode 100644 gcc/testsuite/gcc.dg/vect/complex/bb-slp-complex-dot-int8.c

diff --git a/gcc/testsuite/gcc.dg/vect/complex/bb-slp-complex-dot-int8.c 
b/gcc/testsuite/gcc.dg/vect/complex/bb-slp-complex-dot-int8.c
new file mode 100644
index 000000000000..861b790158f6
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/vect/complex/bb-slp-complex-dot-int8.c
@@ -0,0 +1,25 @@
+/* { dg-do compile { target aarch64-*-* } } */
+/* { dg-additional-options "-O3 -fdump-tree-vect-details 
-march=armv8.2-a+sve2" } */
+
+#include <stdint.h>
+
+#define N 512
+
+struct cint16_t
+{
+  int8_t re;
+  int8_t im;
+};
+
+int32_t
+complex_dot_prod_rot0 (const struct cint16_t *restrict a,
+                 const struct cint16_t *restrict b)
+{
+  int32_t res = 0;
+  for (int i = 0; i < N; i++)
+    res += (a[i].re * b[i].re - a[i].im * b[i].im);
+  return res;
+}
+
+/* { dg-final { scan-tree-dump "Found COMPLEX_DOT_PROD_ROT0" "vect" } } */
+/* { dg-final { scan-tree-dump "add new stmt: .*COMPLEX_DOT_PROD_ROT0" "vect" 
} } */
diff --git a/gcc/tree-vect-slp-patterns.cc b/gcc/tree-vect-slp-patterns.cc
index c53325fecdc7..582d0473f3ca 100644
--- a/gcc/tree-vect-slp-patterns.cc
+++ b/gcc/tree-vect-slp-patterns.cc
@@ -108,6 +108,40 @@ vect_pattern_validate_optab (internal_fn ifn, slp_tree 
node)
   return true;
 }
 
+static bool
+vect_pattern_validate_optab (internal_fn ifn, slp_tree, tree_pair types)
+{
+  tree type0 = types.first;
+  tree type1 = types.second;
+  if (ifn == IFN_LAST || !type0 || !type1)
+    return false;
+
+  if (dump_enabled_p ())
+    dump_printf_loc (MSG_NOTE, vect_location, "Found %s pattern in SLP tree\n",
+                    internal_fn_name (ifn));
+
+  if (direct_internal_fn_supported_p (ifn, types, OPTIMIZE_FOR_SPEED))
+    {
+      if (dump_enabled_p ())
+       dump_printf_loc (
+           MSG_NOTE, vect_location,
+           "Target supports %s vectorization with types %T and %T\n",
+           internal_fn_name (ifn), type0, type1);
+    }
+  else
+    {
+      if (dump_enabled_p ())
+       {
+         dump_printf_loc (MSG_NOTE, vect_location,
+                          "Target does not support %s for vector types "
+                          "%T and %T\n",
+                          internal_fn_name (ifn), type0, type1);
+       }
+      return false;
+    }
+  return true;
+}
+
 
/*******************************************************************************
  * General helper types
  
******************************************************************************/
@@ -1508,6 +1542,326 @@ complex_fms_pattern::build (vec_info *vinfo)
   build_common (vinfo);
 }
 
+/*******************************************************************************
+ * complex_dot_prod_pattern class
+ 
******************************************************************************/
+
+static bool
+slp_tree_zero_p (slp_tree node)
+{
+  if (!node)
+    return false;
+
+  if (SLP_TREE_SCALAR_OPS (node).is_empty ())
+    return false;
+
+  tree op;
+  unsigned i;
+
+  FOR_EACH_VEC_ELT (SLP_TREE_SCALAR_OPS (node), i, op)
+    if (!zerop (op))
+      return false;
+
+  return true;
+}
+
+/*  Build a 2-lane SLP load node for the consecutive members represented by RE
+    and IM. This is used in complex_dot_prod_pattern::build, and
+    complex_dot_prod_pattern::matches verifies using complex_re_im_pair_p that
+    they are the adjacent members of the same load group. See below.
+ */
+static slp_tree
+vect_build_complex_dot_prod_load (slp_tree re, slp_tree im)
+{
+  slp_tree complex = vect_create_new_slp_node (0, SLP_TREE_CODE (re));
+  SLP_TREE_VECTYPE (complex) = SLP_TREE_VECTYPE (re);
+  SLP_TREE_REF_COUNT (complex) = 1;
+  SLP_TREE_LANES (complex) = 2;
+  SLP_TREE_REPRESENTATIVE (complex) = SLP_TREE_REPRESENTATIVE (re);
+
+  auto rep = SLP_TREE_REPRESENTATIVE (re);
+  gcc_assert (STMT_VINFO_GROUPED_ACCESS (rep));
+  SLP_TREE_SCALAR_STMTS (complex).safe_push (SLP_TREE_SCALAR_STMTS (re)[0]);
+  SLP_TREE_SCALAR_STMTS (complex).safe_push (SLP_TREE_SCALAR_STMTS (im)[0]);
+
+  return complex;
+}
+
+/*  Returns true if RE and IM are one-lane load nodes for consecutive members
+   of the same { real, imaginary } load group.  */
+static bool
+complex_re_im_pair_p (slp_tree re, slp_tree im)
+{
+  if (!re || !im || SLP_TREE_PERMUTE_P (re) || SLP_TREE_PERMUTE_P (im)
+      || SLP_TREE_SCALAR_STMTS (re).length () != 1
+      || SLP_TREE_SCALAR_STMTS (im).length () != 1)
+    return false;
+
+  stmt_vec_info re_rep = SLP_TREE_REPRESENTATIVE (re);
+  stmt_vec_info im_rep = SLP_TREE_REPRESENTATIVE (im);
+
+  if (!re_rep || !im_rep || !STMT_VINFO_GROUPED_ACCESS (re_rep)
+      || !STMT_VINFO_GROUPED_ACCESS (im_rep))
+    return false;
+
+  stmt_vec_info first = DR_GROUP_FIRST_ELEMENT (re_rep);
+  if (first != re_rep || DR_GROUP_SIZE (first) != 2
+      || DR_GROUP_NEXT_ELEMENT (first) != im_rep || DR_GROUP_GAP (im_rep) != 1)
+    return false;
+
+  return true;
+}
+
+class complex_dot_prod_pattern : public complex_pattern
+{
+protected:
+  complex_dot_prod_pattern (slp_tree *node, vec<slp_tree> *m_ops, internal_fn 
ifn)
+      : complex_pattern (node, m_ops, ifn)
+  {
+    this->m_num_args = 3;
+  }
+
+public:
+  void build (vec_info *) final override;
+  static internal_fn matches (complex_operation_t op,
+                             slp_tree_to_load_perm_map_t *,
+                             slp_compat_nodes_map_t *, slp_tree *,
+                             vec<slp_tree> *);
+
+  static vect_pattern *recognize (slp_tree_to_load_perm_map_t *,
+                                 slp_compat_nodes_map_t *, slp_tree *);
+
+  static vect_pattern *
+  mkInstance (slp_tree *node, vec<slp_tree> *m_ops, internal_fn ifn)
+  {
+    return new complex_dot_prod_pattern (node, m_ops, ifn);
+  }
+};
+
+void
+complex_dot_prod_pattern::build (vec_info *vinfo)
+{
+  slp_tree node;
+  slp_tree a_re = this->m_ops[0];
+  slp_tree a_im = this->m_ops[1];
+  slp_tree b_re = this->m_ops[2];
+  slp_tree b_im = this->m_ops[3];
+  slp_tree acc = this->m_ops[4];
+
+  slp_tree a_complex = vect_build_complex_dot_prod_load (a_re, a_im);
+  slp_tree b_complex = vect_build_complex_dot_prod_load (b_re, b_im);
+  SLP_TREE_REF_COUNT (acc)++;
+
+  unsigned i;
+  FOR_EACH_VEC_ELT (SLP_TREE_CHILDREN (*this->m_node), i, node)
+    vect_free_slp_tree (node);
+
+  SLP_TREE_CHILDREN (*this->m_node).release ();
+  SLP_TREE_CHILDREN (*this->m_node).safe_grow (3);
+
+  SLP_TREE_CHILDREN (*this->m_node)[0] = a_complex;
+  SLP_TREE_CHILDREN (*this->m_node)[1] = b_complex;
+  SLP_TREE_CHILDREN (*this->m_node)[2] = acc;
+  SLP_TREE_REDUC_IDX (*this->m_node) = 2;
+
+  /* This is taken from complex_pattern::build_common. There are some changes
+     which are required for this specific case:
+
+     * input operands 0 and 1 have the same type, operand 2 (acc) has a 
different type.
+     * operand 2 is the reduction cycle operand.
+     * the live reduction status needs to be preserved.
+     * loop relevant classification should be preserved (vect_used_only_live).
+
+     For diagnostics only, the temp_ssa_name is cdot_slp_patt.  */
+
+  stmt_vec_info stmt_info;
+  auto_vec<tree> args;
+  args.create (this->m_num_args);
+  args.quick_grow_cleared (this->m_num_args);
+  unsigned ix;
+  stmt_vec_info call_stmt_info;
+  gcall *call_stmt = NULL;
+
+  /* Now modify the nodes themselves.  */
+  FOR_EACH_VEC_ELT (this->m_workset, ix, node)
+  {
+    /* Calculate the location of the statement in NODE to replace.  */
+    stmt_info = SLP_TREE_REPRESENTATIVE (node);
+    gimple *old_stmt = STMT_VINFO_STMT (stmt_info);
+    tree lhs_old_stmt = gimple_get_lhs (old_stmt);
+    tree type = TREE_TYPE (lhs_old_stmt);
+
+    /* Create the argument set for use by gimple_build_call_internal_vec.  */
+    for (unsigned i = 0; i < this->m_num_args; i++)
+      args[i] = gimple_get_lhs (STMT_VINFO_STMT (
+         SLP_TREE_REPRESENTATIVE (SLP_TREE_CHILDREN (node)[i])));
+
+    /* Create the new pattern statements.  */
+    call_stmt = gimple_build_call_internal_vec (this->m_ifn, args);
+    tree var = make_temp_ssa_name (type, call_stmt, "cdot_slp_patt");
+    gimple_call_set_lhs (call_stmt, var);
+    gimple_set_location (call_stmt, gimple_location (old_stmt));
+    gimple_call_set_nothrow (call_stmt, true);
+
+    /* Adjust the book-keeping for the new and old statements for use during
+       SLP.  This is required to get the right VF and statement during SLP
+       analysis.  These changes are created after relevancy has been set for
+       the nodes as such we need to manually update them.  Any changes will be
+       undone if SLP is cancelled.  */
+    call_stmt_info
+       = vinfo->add_pattern_stmt (call_stmt, vect_orig_stmt (stmt_info));
+
+    STMT_VINFO_REDUC_IDX (call_stmt_info) = 2;
+    STMT_VINFO_LIVE_P (call_stmt_info) = STMT_VINFO_LIVE_P (stmt_info);
+
+    /* Make sure to mark the representative statement pure_slp and
+       relevant and transfer reduction info. */
+    STMT_VINFO_RELEVANT (call_stmt_info) = STMT_VINFO_RELEVANT (stmt_info);
+    STMT_SLP_TYPE (call_stmt_info) = pure_slp;
+
+    gimple_set_bb (call_stmt, gimple_bb (stmt_info->stmt));
+    STMT_VINFO_VECTYPE (call_stmt_info) = SLP_TREE_VECTYPE (node);
+
+    /* Since we are replacing all the statements in the group with the same
+       thing it doesn't really matter.  So just set it every time a new stmt
+       is created.  */
+    SLP_TREE_REPRESENTATIVE (node) = call_stmt_info;
+    SLP_TREE_LANE_PERMUTATION (node).release ();
+    SLP_TREE_CODE (node) = ERROR_MARK;
+  }
+}
+
+/* Pattern matcher for trying to match complex integer dot product pattern
+   in SLP tree.  If the operation matches then IFN is set to the operation
+   IFN_COMPLEX_DOT_PROD_ROT0 and the arguments to the replacement statement are
+   put in m_ops.
+
+   If no match is found then IFN is set to IFN_LAST and m_ops is unchanged.
+
+   This function matches the patterns shaped as:
+
+   acc_1 = DOT_PROD_EXPR (a_re, b_re, acc_0);
+   acc_2 = 0 - acc_1;
+   acc_3 = DOT_PROD_EXPR (a_im, b_im, acc_2);
+   acc_4 = 0 - acc_3;
+
+   which comes from the dot_prod and neg_dot_prod pattern that comes from 
statements
+   like this:
+
+   acc = a[i] * b[i] - a[i + 1] * b[i + 1] + acc;
+
+   On success, store { A_RE, A_IM, B_RE, B_IM, ACC } in OPS and return
+   IFN_COMPLEX_DOT_PROD_ROT0. On failure, return IFN_LAST with OPS unmodified.
+    */
+
+internal_fn
+complex_dot_prod_pattern::matches (complex_operation_t op,
+                             slp_tree_to_load_perm_map_t *,
+                             slp_compat_nodes_map_t *,
+                             slp_tree *ref_node, vec<slp_tree> *ops)
+{
+  /* 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
+     check that it's not a permute node as we're looking for a normal
+     MINUS_EXPR operation.  */
+  if (op != CMPLX_NONE)
+    return IFN_LAST;
+
+  slp_tree root = *ref_node;
+  if (!vect_match_expression_p (root, MINUS_EXPR))
+    return IFN_LAST;
+
+  /* Check that root is of the form 0 - root1.  */
+  auto root_children = SLP_TREE_CHILDREN (root);
+  if (root_children.length () != 2)
+    return IFN_LAST;
+  slp_tree zero1 = root_children[0];
+  slp_tree root1 = root_children[1];
+
+  if (!slp_tree_zero_p (zero1)
+      || !vect_match_expression_p (root1, DOT_PROD_EXPR))
+    return IFN_LAST;
+
+  auto root1_children = SLP_TREE_CHILDREN (root1);
+  if (root1_children.length () != 3)
+    return IFN_LAST;
+  slp_tree a_im = root1_children[0];
+  slp_tree b_im = root1_children[1];
+  slp_tree acc_1 = root1_children[2];
+
+  /* Check that the imaginary dot product accumulates 0 - root2.  */
+  if (!vect_match_expression_p (acc_1, MINUS_EXPR))
+    return IFN_LAST;
+
+  auto acc1_children = SLP_TREE_CHILDREN (acc_1);
+  if (acc1_children.length () != 2)
+    return IFN_LAST;
+  slp_tree zero2 = acc1_children[0];
+  slp_tree root2 = acc1_children[1];
+
+  if (!slp_tree_zero_p (zero2)
+      || !vect_match_expression_p (root2, DOT_PROD_EXPR))
+    return IFN_LAST;
+
+  auto root2_children = SLP_TREE_CHILDREN (root2);
+  if (root2_children.length () != 3)
+    return IFN_LAST;
+  slp_tree a_re = root2_children[0];
+  slp_tree b_re = root2_children[1];
+  slp_tree acc = root2_children[2];
+
+  /* It's only valid to form complex dot product with integer types.  */
+  tree acc_vectype = SLP_TREE_VECTYPE (root);
+  if (!acc_vectype || !INTEGRAL_TYPE_P (TREE_TYPE (acc_vectype)))
+    return IFN_LAST;
+
+  tree input_vectype = SLP_TREE_VECTYPE (a_re);
+  if (!input_vectype || !INTEGRAL_TYPE_P (TREE_TYPE (input_vectype)))
+    return IFN_LAST;
+
+  /* Check that it is widening: 8 -> 32 bit.  */
+  if (!(TYPE_PRECISION (TREE_TYPE (acc_vectype)) == 32
+       && TYPE_PRECISION (TREE_TYPE (input_vectype)) == 8))
+    return IFN_LAST;
+
+  if (TYPE_UNSIGNED (TREE_TYPE (input_vectype)))
+    return IFN_LAST;
+
+  /* The two dot products and the original accumulator must have the same
+     result type.  */
+  if (SLP_TREE_VECTYPE (root1) != acc_vectype
+      || SLP_TREE_VECTYPE (root2) != acc_vectype
+      || !acc || SLP_TREE_VECTYPE (acc) != acc_vectype)
+    return IFN_LAST;
+
+  /* If we got here, it means we've matched the pattern.
+     Check that a_re and a_im are adjacent. Likewise for b_re and b_im.  */
+  if (!complex_re_im_pair_p (a_re, a_im))
+    return IFN_LAST;
+
+  if (!complex_re_im_pair_p (b_re, b_im))
+    return IFN_LAST;
+
+  if (input_vectype != SLP_TREE_VECTYPE (a_im)
+      || input_vectype != SLP_TREE_VECTYPE (b_re)
+      || SLP_TREE_VECTYPE (b_re) != SLP_TREE_VECTYPE (b_im))
+    return IFN_LAST;
+
+  if (!vect_pattern_validate_optab (IFN_COMPLEX_DOT_PROD_ROT0, *ref_node,
+                                   tree_pair (acc_vectype, input_vectype)))
+    return IFN_LAST;
+
+  ops->truncate (0);
+  ops->create (5);
+  ops->quick_push (a_re);
+  ops->quick_push (a_im);
+  ops->quick_push (b_re);
+  ops->quick_push (b_im);
+  ops->quick_push (acc);
+
+  return IFN_COMPLEX_DOT_PROD_ROT0;
+}
+
 
/*******************************************************************************
  * complex_operations_pattern class
  
******************************************************************************/
@@ -1573,6 +1927,10 @@ complex_operations_pattern::recognize 
(slp_tree_to_load_perm_map_t *perm_cache,
   if (ifn != IFN_LAST)
     return complex_add_pattern::mkInstance (node, &ops, ifn);
 
+  ifn  = complex_dot_prod_pattern::matches (op, perm_cache, ccache, node, 
&ops);
+  if (ifn != IFN_LAST)
+    return complex_dot_prod_pattern::mkInstance (node, &ops, ifn);
+
   return NULL;
 }
 
-- 
2.53.0

Reply via email to