> -----Original Message-----
> From: [email protected] <[email protected]>
> Sent: 09 September 2026 19:12
> To: [email protected]
> Cc: Tamar Christina <[email protected]>; [email protected];
> [email protected]; Ezra Sitorus <[email protected]>
> Subject: [RFC PATCH 2/5] vect: Add vect_recog_neg_dot_prod_pattern
>
> From: Ezra Sitorus <[email protected]>
>
> Currently, vect_recog_dot_prod_pattern can only match sums.
>
> This patch looks for:
>
> DPROD = X w* Y; #widen mult
> sum_1 = sum_0 - DPROD; #subtraction
>
> and replaces it with:
>
> sum_1 = 0 - sum_0;
> sum_2 = DOT_PROD<X, Y, sum_1>;
> sum_3 = 0 - sum2;
>
> It searches for a similar widening pattern but with subtraction instead.
> In particular, for loops such as:
>
> int acc, *a, *b, *c, *d;
>
> for (int i = 0; i < N; i++)
> acc += a[i] * b[i] - c[i] * d[i];
>
> This would ideally get transformed into something like this:
>
> acc_1 = DOT_PROD<c[i], d[i], acc_0>
> acc_2 = 0 - acc_2;
> acc_3 = DOT_PROD<a[i], b[i], acc_2>
> acc_4 = 0 - acc_3;
>
> A possible patch could be done later on to combine
> vect_recog_dot_prod_pattern and vect_recog_neg_dot_prod_pattern.
> However, since they result in different gimple emission then perhaps
> it's not as trivial as it seems.
>
> Note that there is another match.pd patch to aid in this as well.
>
> gcc/ChangeLog:
> * tree-vect-patterns.cc (vect_recog_neg_dot_prod_pattern): Add
> new vect pattern.
> (vect_vect_recog_func_ptrs): Add
> vect_recog_neg_dot_prod_pattern.
>
> gcc/testsuite/ChangeLog:
> * gcc.dg/vect/vect-reduc-neg-dot-1.c: Add new test.
> * gcc.dg/vect/vect-reduc-neg-dot-2.c: Likewise.
> ---
> .../gcc.dg/vect/vect-reduc-neg-dot-1.c | 46 +++++++
> .../gcc.dg/vect/vect-reduc-neg-dot-2.c | 45 +++++++
> gcc/tree-vect-patterns.cc | 124 ++++++++++++++++++
> 3 files changed, 215 insertions(+)
> create mode 100644 gcc/testsuite/gcc.dg/vect/vect-reduc-neg-dot-1.c
> create mode 100644 gcc/testsuite/gcc.dg/vect/vect-reduc-neg-dot-2.c
>
> diff --git a/gcc/testsuite/gcc.dg/vect/vect-reduc-neg-dot-1.c
> b/gcc/testsuite/gcc.dg/vect/vect-reduc-neg-dot-1.c
> new file mode 100644
> index 000000000000..17a2e1513294
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/vect/vect-reduc-neg-dot-1.c
> @@ -0,0 +1,46 @@
> +/* { dg-require-effective-target vect_int } */
> +
> +#include "tree-vect.h"
> +
> +#define N 50
> +
> +#ifndef SIGNEDNESS_1
> +#define SIGNEDNESS_1 signed
> +#define SIGNEDNESS_2 signed
> +#define SIGNEDNESS_3 signed
> +#endif
> +
> +SIGNEDNESS_1 int __attribute__ ((noipa))
> +f (SIGNEDNESS_1 short res, SIGNEDNESS_3 char *restrict a,
> + SIGNEDNESS_3 char *restrict b)
> +{
> + for (__INTPTR_TYPE__ i = 0; i < N; ++i)
> + {
> + res -= (SIGNEDNESS_2 short) (a[i] * b[i]);
> + }
> + return res;
> +}
> +
> +#define BASE ((SIGNEDNESS_3 short) -1 < 0 ? -126 : 4)
> +#define OFFSET 20
> +
> +int
> +main (void)
> +{
> + check_vect ();
> +
> + SIGNEDNESS_3 char a[N], b[N];
> + SIGNEDNESS_1 short expected = 0x1234;
> + for (int i = 0; i < N; ++i)
> + {
> + a[i] = BASE + i * 5;
> + b[i] = BASE + OFFSET + i * 4;
> + asm volatile ("" ::: "memory");
> + expected -= (SIGNEDNESS_2 short) (a[i] * b[i]);
> + }
> + if (f (0x1234, a, b) != expected)
> + __builtin_abort ();
> +}
> +
> +/* { dg-final { scan-tree-dump "vect_recog_neg_dot_prod_pattern: detected"
> "vect" } } */
> +/* { dg-final { scan-tree-dump-times "vectorized 1 loop" 1 "vect" { target
> vect_sdot_qi } } } */
> diff --git a/gcc/testsuite/gcc.dg/vect/vect-reduc-neg-dot-2.c
> b/gcc/testsuite/gcc.dg/vect/vect-reduc-neg-dot-2.c
> new file mode 100644
> index 000000000000..e6250d455284
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/vect/vect-reduc-neg-dot-2.c
> @@ -0,0 +1,45 @@
> +/* { dg-require-effective-target vect_int } */
> +
> +#include "tree-vect.h"
> +
> +#define N 50
> +
> +#ifndef SIGNEDNESS_1
> +#define SIGNEDNESS_1 signed
> +#define SIGNEDNESS_2 signed
> +#define SIGNEDNESS_3 signed
> +#endif
> +
> +SIGNEDNESS_1 char __attribute__ ((noipa))
> +f (SIGNEDNESS_1 char res, SIGNEDNESS_3 char *restrict a,
> + SIGNEDNESS_3 char *restrict b)
> +{
> + for (__INTPTR_TYPE__ i = 0; i < N; ++i)
> + {
> + res -= (SIGNEDNESS_2 char) (a[i] * b[i]);
> + }
> + return res;
> +}
> +
> +#define BASE ((SIGNEDNESS_3 char) -1 < 0 ? -126 : 4)
> +#define OFFSET 20
> +
> +int
> +main (void)
> +{
> + check_vect ();
> +
> + SIGNEDNESS_3 char a[N], b[N];
> + SIGNEDNESS_1 char expected = 0x12;
> + for (char i = 0; i < N; ++i)
> + {
> + a[i] = BASE + i * 5;
> + b[i] = BASE + OFFSET + i * 4;
> + asm volatile ("" ::: "memory");
> + expected -= (SIGNEDNESS_2 int) (a[i] * b[i]);
> + }
> + if (f (0x12, a, b) != expected)
> + __builtin_abort ();
> +}
> +
> +/* { dg-final { scan-tree-dump-not "vect_recog_neg_dot_prod_pattern:
> detected" "vect" } } */
> diff --git a/gcc/tree-vect-patterns.cc b/gcc/tree-vect-patterns.cc
> index 5fcc37f053c7..ea75ec7fece7 100644
> --- a/gcc/tree-vect-patterns.cc
> +++ b/gcc/tree-vect-patterns.cc
> @@ -1348,6 +1348,129 @@ vect_recog_dot_prod_pattern (vec_info *vinfo,
> return pattern_stmt;
> }
>
> +static gimple *
> +vect_recog_neg_dot_prod_pattern (vec_info *vinfo,
> + stmt_vec_info stmt_vinfo, tree *type_out)
> +{
> + tree oprnd0, oprnd1;
> + gimple *last_stmt = stmt_vinfo->stmt;
> + tree type, half_type;
> + gimple *neg_stmt, *dot_stmt, *pattern_stmt;
> + tree neg, dot, var;
> +
> + /* Look for the following pattern
> + DX = (TYPE1) X;
> + DY = (TYPE1) Y;
> + DPROD = DX * DY;
> + DDPROD = (TYPE2) DPROD;
> + sum_1 = sum_0 - DDPROD;
> + In which
> + - DX is double the size of X
> + - DY is double the size of Y
> + - DX, DY, DPROD all have the same type but the sign
> + between X, Y and DPROD can differ.
> + - sum is the same size of DPROD or bigger
> + - sum has been recognized as a reduction variable.
> +
> + This is equivalent to:
> + DPROD = X w* Y; #widen mult
> + sum_1 = sum_0 - DPROD; #subtraction
> +
> + Replace with:
> + neg = 0 - sum_0;
> + prod = DOT_PROD<X, Y, neg>;
> + sum_1 = 0 - prod;
> +
> + i.e -(X*Y + (-SUM)) = SUM - X*Y
> + */
> +
> + /* Starting from LAST_STMT, follow the defs of its uses in search
> + of the above pattern. */
> +
> + if (!vect_reassociating_reduction_p (vinfo, stmt_vinfo, MINUS_EXPR,
> + &oprnd0, &oprnd1))
> + return NULL;
> +
> + type = TREE_TYPE (gimple_get_lhs (last_stmt));
> +
> + vect_unpromoted_value unprom_mult;
> + oprnd1 = vect_look_through_possible_promotion (vinfo, oprnd1,
> &unprom_mult);
> +
> + /* So far so good. Since last_stmt was detected as a (subtraction)
> reduction,
> + we know that oprnd0 is the reduction variable (defined by a loop-header
> + phi), and oprnd1 is an ssa-name defined by a stmt in the loop body.
> + Left to check that oprnd1 is defined by a (widen_)mult_expr. */
> + if (!oprnd1)
> + return NULL;
> +
> + stmt_vec_info mult_vinfo = vect_get_internal_def (vinfo, oprnd1);
> + if (!mult_vinfo)
> + return NULL;
> +
> + /* FORNOW. Can continue analyzing the def-use chain when this stmt in a
> phi
> + inside the loop (in case we are analyzing an outer-loop). */
> + vect_unpromoted_value unprom0[2];
> + enum optab_subtype subtype = optab_vector;
> + if (!vect_widened_op_tree (vinfo, mult_vinfo, MULT_EXPR,
> WIDEN_MULT_EXPR,
> + false, 2, unprom0, &half_type, &subtype))
> + return NULL;
> +
> + /* If there are two widening operations, make sure they agree on the sign
> + of the extension. The result of an optab_vector_mixed_sign operation
> + is signed; otherwise, the result has the same sign as the operands. */
> + if (TYPE_PRECISION (unprom_mult.type) != TYPE_PRECISION (type)
> + && (subtype == optab_vector_mixed_sign
> + ? TYPE_UNSIGNED (unprom_mult.type)
> + : TYPE_SIGN (unprom_mult.type) != TYPE_SIGN (half_type)))
> + return NULL;
> +
> + vect_pattern_detected ("vect_recog_neg_dot_prod_pattern", last_stmt);
> +
> + /* If the inputs have mixed signs, canonicalize on using the signed
> + input type for analysis. This also helps when emulating mixed-sign
> + operations using signed operations. */
> + if (subtype == optab_vector_mixed_sign)
> + half_type = signed_type_for (half_type);
> +
> + tree half_vectype;
> + if (!vect_supportable_conv_optab_p (vinfo, type, DOT_PROD_EXPR,
> half_type,
> + type_out, &half_vectype, subtype))
> + {
> + /* We can emulate a mixed-sign dot-product using a sequence of
> + signed dot-products; see vect_emulate_mixed_dot_prod for details.
> */
> + if (subtype != optab_vector_mixed_sign
> + || !vect_supportable_conv_optab_p (vinfo, signed_type_for (type),
> + DOT_PROD_EXPR, half_type,
> + type_out, &half_vectype,
> + optab_vector))
> + return NULL;
> +
> + *type_out = signed_or_unsigned_type_for (TYPE_UNSIGNED (type),
> + *type_out);
> + }
> +
> + /* Get the inputs in the appropriate types. */
> + tree mult_oprnd[2];
> + vect_convert_inputs (vinfo, stmt_vinfo, 2, mult_oprnd, half_type,
> + unprom0, half_vectype, subtype);
> +
> + tree zero = build_zero_cst (type);
> + neg = vect_recog_temp_ssa_var (type, NULL);
> + neg_stmt = gimple_build_assign (neg, MINUS_EXPR, zero, oprnd0);
I think this isn't safe wrt to overflow since 0 - INT_MIN would overflow.
You want a check on TYPE_OVERFLOW_WRAPS (type).
However what you mentioned in the cover letter is correct though you
should be able to merge this into the normal dot-prod pattern pretty
easily.
Basically for the MINUX_EXPR case the product and the accumulator
should be flipped, since in vect_reassociating_reduction_p
the operands are flipped for commutative ops on REDUC_IDX (0).
and since you want acc - prod, and not prod - acc you also need to check
for the order of the operands.
So I think you can do
@@ -1276,9 +1277,20 @@ vect_recog_dot_prod_pattern (vec_info *vinfo,
if (!vect_reassociating_reduction_p (vinfo, stmt_vinfo, PLUS_EXPR,
&oprnd0, &oprnd1))
- return NULL;
+ {
+ if (!vect_reassociating_reduction_p (vinfo, stmt_vinfo, MINUS_EXPR,
+ &oprnd0, &oprnd1)
+ || STMT_VINFO_REDUC_IDX (stmt_vinfo) != 0)
+ return NULL;
+
+ subtract_p = true;
+ std::swap (oprnd0, oprnd1);
+ }
type = TREE_TYPE (gimple_get_lhs (last_stmt));
+ if (subtract_p
+ && (!ANY_INTEGRAL_TYPE_P (type) || !TYPE_OVERFLOW_WRAPS (type)))
+ return NULL;
and then when subtract_p at the end you build the expression below instead of
just the dotprod.
You should pull this out of the series too, it's perfectly fine standalone.
Thanks,
Tamar
> + append_pattern_def_seq (vinfo, stmt_vinfo, neg_stmt, *type_out);
> + gimple_set_location (neg_stmt, gimple_location (last_stmt));
> +
> + dot = vect_recog_temp_ssa_var (type, NULL);
> + dot_stmt = gimple_build_assign (dot, DOT_PROD_EXPR,
> + mult_oprnd[0], mult_oprnd[1], neg);
> + append_pattern_def_seq (vinfo, stmt_vinfo, dot_stmt, *type_out);
> + gimple_set_location (dot_stmt, gimple_location (last_stmt));
> +
> + var = vect_recog_temp_ssa_var (type, NULL);
> + pattern_stmt = gimple_build_assign (var, MINUS_EXPR, zero, dot);
> +
> + return pattern_stmt;
> +}
>
> /* Function vect_recog_sad_pattern
>
> @@ -7537,6 +7660,7 @@ static vect_recog_func
> vect_vect_recog_func_ptrs[] = {
> { vect_recog_mulhs_pattern, "mult_high" },
> { vect_recog_cast_forwprop_pattern, "cast_forwprop" },
> { vect_recog_widen_mult_pattern, "widen_mult" },
> + { vect_recog_neg_dot_prod_pattern, "neg_dot_prod" },
> { vect_recog_dot_prod_pattern, "dot_prod" },
> { vect_recog_sad_pattern, "sad" },
> { vect_recog_widen_sum_pattern, "widen_sum" },
> --
> 2.53.0