---

Hi GCC Team,

This RFC proposes to combines multiple independent
horizontal reductions (.REDUC_PLUS / IFN_REDUC_PLUS) found in a single
basic block into one shared intra-lane reduction sequence followed by a 
narrowing path.

On wide vectors a single horizontal reduction expands into a
chain of cross-lane extract+add steps (512 -> 256 -> 128 -> 64 -> 32).
When N such reductions sit in the same epilogue, this expensive
cross-lane work is repeated N times.

Rather than reducing each vector to a scalar independently,  interleaves 
the lanes of the grouped reduction vectors using unpack-lo / unpack-hi + add.
This places the partial sums of all the reductions side by side, after which 
a single narrowing path brings the data down to one 128-bit lane.
Each reduction's scalar result is read out separately. The cross-lane narrowing
is thus shared across the whole group instead of being repeated per reduction.

Example:

  int A[128], B0[128], B1[128], B2[128], B3[128];
  int C[4];
  void reduc (void) {
    int s0 = 0, s1 = 0, s2 = 0, s3 = 0;
    for (int k = 0; k < 128; ++k) {
      int a = A[k];
      s0 += a * B0[k]; s1 += a * B1[k];
      s2 += a * B2[k]; s3 += a * B3[k];
    }
    C[0] = s0; C[1] = s1; C[2] = s2; C[3] = s3;
  }

At -O3 -mavx512f each accumulator is a vector(16) int and the epilogue
emits four independent .REDUC_PLUS calls:

  _77 = .REDUC_PLUS (vect_s0);
  _66 = .REDUC_PLUS (vect_s1);
  _10 = .REDUC_PLUS (vect_s2);
  _35 = .REDUC_PLUS (vect_s3);

The pass replaces them with one shared unpack/add tree, a single
narrowing path to 128 bits, and four scalar extracts:

  /* interleave + add, shared by all four reductions */
  _124 = unpack_lo (s1, s0) + unpack_hi (s1, s0);
  _159 = unpack_lo (s3, s2) + unpack_hi (s3, s2);
  _194 = unpack_lo (_124, _159) + unpack_hi (_124, _159);
  /* single narrowing path 512 -> 256 -> 128, giving vector(4) int */
  ... _247 ...
  /* per-reduction scalar extraction */
  _77 = BIT_FIELD_REF <_247, 32, 0>;
  _35 = BIT_FIELD_REF <_247, 32, 32>;
  _66 = BIT_FIELD_REF <_247, 32, 64>;
  _10 = BIT_FIELD_REF <_247, 32, 96>;

On x86 this lowers the epilogue from four independent
vextracti*/vpsrldq/vpaddd chains (~28 insns) to a single shared
vpunpckldq/vpunpckhdq/vpaddd sequence plus vpextrd extracts
(~17 insns).

Bootstrapped and tested on x86.

Please let me know if any modifications are needed.

Thank you,
Dipesh

gcc/ChangeLog:

        * Makefile.in:
        * common.opt:
        * opts.cc:
        * passes.def:
        * timevar.def (TV_TREE_COMBINE_REDUC_PLUS_OPT):
        * tree-pass.h (make_pass_combine_reduc_plus_opt):
        * tree-combine-reduc-plus-opt.cc: New file.

gcc/testsuite/ChangeLog:

        * gcc.dg/vect/vect-reduc-plus-1.c: New test.


 gcc/Makefile.in                               |   1 +
 gcc/common.opt                                |   4 +
 gcc/opts.cc                                   |   1 +
 gcc/passes.def                                |   3 +
 gcc/testsuite/gcc.dg/vect/vect-reduc-plus-1.c | 211 ++++++
 gcc/timevar.def                               |   1 +
 gcc/tree-combine-reduc-plus-opt.cc            | 662 ++++++++++++++++++
 gcc/tree-pass.h                               |   1 +
 8 files changed, 884 insertions(+)
 create mode 100644 gcc/testsuite/gcc.dg/vect/vect-reduc-plus-1.c
 create mode 100644 gcc/tree-combine-reduc-plus-opt.cc

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 536e2faaaf8..432750d86fc 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1843,6 +1843,7 @@ OBJS = \
        tree-vect-slp.o \
        tree-vect-slp-patterns.o \
        tree-vectorizer.o \
+       tree-combine-reduc-plus-opt.o \
        tree-vector-builder.o \
        tree-vrp.o \
        tree.o \
diff --git a/gcc/common.opt b/gcc/common.opt
index 218dddf5dfe..49d981fb2dc 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -3527,6 +3527,10 @@ ftree-slp-vectorize
 Common Var(flag_tree_slp_vectorize) Optimization EnabledBy(ftree-vectorize)
 Enable basic block vectorization (SLP) on trees.
 
+ftree-combine-reduc-plus-opt
+Common Var(flag_tree_combine_reduc_plus_opt) Optimization 
EnabledBy(ftree-vectorize)
+Enable GIMPLE pass that optimizes REDUC_PLUS patterns.
+
 fvect-cost-model=
 Common Joined RejectNegative Enum(vect_cost_model) Var(flag_vect_cost_model) 
Init(VECT_COST_MODEL_DEFAULT) Optimization
 -fvect-cost-model=[unlimited|dynamic|cheap|very-cheap] Specifies the cost 
model for vectorization.
diff --git a/gcc/opts.cc b/gcc/opts.cc
index 342517528e8..102b73443e5 100644
--- a/gcc/opts.cc
+++ b/gcc/opts.cc
@@ -690,6 +690,7 @@ static const struct default_options default_options_table[] 
=
       REORDER_BLOCKS_ALGORITHM_STC },
     { OPT_LEVELS_2_PLUS_SPEED_ONLY, OPT_ftree_loop_vectorize, NULL, 1 },
     { OPT_LEVELS_2_PLUS_SPEED_ONLY, OPT_ftree_slp_vectorize, NULL, 1 },
+    { OPT_LEVELS_2_PLUS_SPEED_ONLY, OPT_ftree_combine_reduc_plus_opt, NULL, 1 
},
     { OPT_LEVELS_2_PLUS_SPEED_ONLY, OPT_fopenmp_target_simd_clone_, NULL,
       OMP_TARGET_SIMD_CLONE_NOHOST },
 #ifdef INSN_SCHEDULING
diff --git a/gcc/passes.def b/gcc/passes.def
index 1fc867fae51..af6233dd188 100644
--- a/gcc/passes.def
+++ b/gcc/passes.def
@@ -331,6 +331,8 @@ along with GCC; see the file COPYING3.  If not see
              NEXT_PASS (pass_dse);
          POP_INSERT_PASSES ()
          NEXT_PASS (pass_slp_vectorize);
+         /* Optimize .REDUC_PLUS patterns after vectorizer prologue/epilogue.  
*/
+         NEXT_PASS (pass_combine_reduc_plus_opt);
          NEXT_PASS (pass_loop_prefetch);
          /* Run IVOPTs after the last pass that uses data-reference analysis
             as that doesn't handle TARGET_MEM_REFs.  */
@@ -343,6 +345,7 @@ along with GCC; see the file COPYING3.  If not see
       NEXT_PASS (pass_tree_no_loop);
       PUSH_INSERT_PASSES_WITHIN (pass_tree_no_loop)
          NEXT_PASS (pass_slp_vectorize);
+         NEXT_PASS (pass_combine_reduc_plus_opt);
       POP_INSERT_PASSES ()
       NEXT_PASS (pass_simduid_cleanup);
       NEXT_PASS (pass_lower_vector_ssa);
diff --git a/gcc/testsuite/gcc.dg/vect/vect-reduc-plus-1.c 
b/gcc/testsuite/gcc.dg/vect/vect-reduc-plus-1.c
new file mode 100644
index 00000000000..67536b462d5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/vect/vect-reduc-plus-1.c
@@ -0,0 +1,211 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target vect_int } */
+/* { dg-additional-options "-O3 -mavx512f -fdump-tree-reduc_plus_opt1" { target
+ * { x86_64-*-* i?86-*-* } } } */
+
+/*
+   Test contiguous .REDUC_PLUS chains for the reduc_plus_opt pass.
+   Each kernel produces a specific number of back-to-back REDUC_PLUS
+   IFN calls that the optimization can merge via unpack-interleave + add.
+
+   With 512-bit SIMD (AVX-512):
+     i32: vector(16) int   -> 4 parallel reductions  (4 contiguous .REDUC_PLUS)
+     i64: vector(8) long   -> 4 parallel reductions  (4 contiguous .REDUC_PLUS)
+     i16: vector(32) short -> 8 parallel reductions  (8 contiguous .REDUC_PLUS)
+     i8:  vector(64) char  -> 16 parallel reductions (16 contiguous
+   .REDUC_PLUS)
+
+   For i16/i8 kernels the accumulators are int (not short/char) to prevent
+   the vectorizer from inserting narrow casts between each .REDUC_PLUS,
+   which would break the contiguity required by verify_contiguous_reduction.
+*/
+
+#define N 128
+
+/* ------- i32 x 16 vector, 4 contiguous REDUC_PLUS ------- */
+
+int A32[N], B32_0[N], B32_1[N], B32_2[N], B32_3[N];
+int C32[7];
+
+__attribute__ ((noinline)) void
+reduc_plus_i32_v16_r4 (void)
+{
+  int s0 = 0, s1 = 0, s2 = 0, s3 = 0;
+  for (int k = 0; k < N; ++k)
+    {
+      int a = A32[k];
+      s0 += a * B32_0[k];
+      s1 += a * B32_1[k];
+      s2 += a * B32_2[k];
+      s3 += a * B32_3[k];
+    }
+  C32[0] = s0;
+  C32[1] = s1;
+  C32[2] = s2;
+  C32[3] = s3;
+}
+
+/* ------- i32 x 16 vector, 7 contiguous REDUC_PLUS ------- */
+/* ------- 4 are convertes to unpack trees and rest three are not------- */
+
+int B32_4[N], B32_5[N], B32_6[N];
+__attribute__ ((noinline)) void
+reduc_plus_i32_v16_r7 (void)
+{
+  int s0 = 0, s1 = 0, s2 = 0, s3 = 0, s4 = 0, s5 = 0, s6 = 0;
+  for (int k = 0; k < N; ++k)
+    {
+      int a = A32[k];
+      s0 += a * B32_0[k];
+      s1 += a * B32_1[k];
+      s2 += a * B32_2[k];
+      s3 += a * B32_3[k];
+      s4 += a * B32_4[k];
+      s5 += a * B32_5[k];
+      s6 += a * B32_6[k];
+    }
+  C32[0] = s0;
+  C32[1] = s1;
+  C32[2] = s2;
+  C32[3] = s3;
+  C32[4] = s4;
+  C32[5] = s5;
+  C32[6] = s6;
+}
+
+/* ------- i64 x 8 vector, 4 contiguous REDUC_PLUS ------- */
+
+long long A64[N], B64_0[N], B64_1[N], B64_2[N], B64_3[N];
+long long C64[4];
+
+__attribute__ ((noinline)) void
+reduc_plus_i64_v8_r4 (void)
+{
+  long long s0 = 0, s1 = 0, s2 = 0, s3 = 0;
+  for (int k = 0; k < N; ++k)
+    {
+      long long a = A64[k];
+      s0 += a * B64_0[k];
+      s1 += a * B64_1[k];
+      s2 += a * B64_2[k];
+      s3 += a * B64_3[k];
+    }
+  C64[0] = s0;
+  C64[1] = s1;
+  C64[2] = s2;
+  C64[3] = s3;
+}
+
+/* ------- i16 x 32 vector, 8 contiguous REDUC_PLUS ------- */
+
+short A16[N], B16_0[N], B16_1[N], B16_2[N], B16_3[N];
+short B16_4[N], B16_5[N], B16_6[N], B16_7[N];
+short C16[8];
+
+__attribute__ ((noinline)) void
+reduc_plus_i16_v32_r8 (void)
+{
+  int s0 = 0, s1 = 0, s2 = 0, s3 = 0;
+  int s4 = 0, s5 = 0, s6 = 0, s7 = 0;
+  for (int k = 0; k < N; ++k)
+    {
+      int a = A16[k];
+      s0 += a * B16_0[k];
+      s1 += a * B16_1[k];
+      s2 += a * B16_2[k];
+      s3 += a * B16_3[k];
+      s4 += a * B16_4[k];
+      s5 += a * B16_5[k];
+      s6 += a * B16_6[k];
+      s7 += a * B16_7[k];
+    }
+  C16[0] = s0;
+  C16[1] = s1;
+  C16[2] = s2;
+  C16[3] = s3;
+  C16[4] = s4;
+  C16[5] = s5;
+  C16[6] = s6;
+  C16[7] = s7;
+}
+
+/* ------- i8 x 64 vector, 16 contiguous REDUC_PLUS ------- */
+
+signed char A8[N];
+signed char B8_0[N], B8_1[N], B8_2[N], B8_3[N];
+signed char B8_4[N], B8_5[N], B8_6[N], B8_7[N];
+signed char B8_8[N], B8_9[N], B8_10[N], B8_11[N];
+signed char B8_12[N], B8_13[N], B8_14[N], B8_15[N];
+signed char C8[16];
+
+__attribute__ ((noinline)) void
+reduc_plus_i8_v64_r16 (void)
+{
+  int s0 = 0, s1 = 0, s2 = 0, s3 = 0;
+  int s4 = 0, s5 = 0, s6 = 0, s7 = 0;
+  int s8 = 0, s9 = 0, s10 = 0, s11 = 0;
+  int s12 = 0, s13 = 0, s14 = 0, s15 = 0;
+  for (int k = 0; k < N; ++k)
+    {
+      int a = A8[k];
+      s0 += a * B8_0[k];
+      s1 += a * B8_1[k];
+      s2 += a * B8_2[k];
+      s3 += a * B8_3[k];
+      s4 += a * B8_4[k];
+      s5 += a * B8_5[k];
+      s6 += a * B8_6[k];
+      s7 += a * B8_7[k];
+      s8 += a * B8_8[k];
+      s9 += a * B8_9[k];
+      s10 += a * B8_10[k];
+      s11 += a * B8_11[k];
+      s12 += a * B8_12[k];
+      s13 += a * B8_13[k];
+      s14 += a * B8_14[k];
+      s15 += a * B8_15[k];
+    }
+  C8[0] = s0;
+  C8[1] = s1;
+  C8[2] = s2;
+  C8[3] = s3;
+  C8[4] = s4;
+  C8[5] = s5;
+  C8[6] = s6;
+  C8[7] = s7;
+  C8[8] = s8;
+  C8[9] = s9;
+  C8[10] = s10;
+  C8[11] = s11;
+  C8[12] = s12;
+  C8[13] = s13;
+  C8[14] = s14;
+  C8[15] = s15;
+}
+
+/* Scan the reduc_plus_opt1 dump for the expected chains.
+
+   The vect.exp harness compiles at -O2 with -fno-vect-cost-model.
+   With AVX-512 (512-bit vectors) the vectorizer uses vector(16) int
+   for most kernels (i16/i8 widen to int accumulators).  The pass
+   groups contiguous REDUC_PLUS by vector type and builds unpack trees
+   in groups of lane_elts (= 128 / elt_bits) elements.
+
+     i32 r4:  4 REDUC_PLUS -> 1 group  -> 1 "Reduced to single lane"
+     i32 r7:  7 REDUC_PLUS -> 1 group  -> 1 "Reduced to single lane", 3
+   .REDUC_PLUS remain i64 r4:  4 REDUC_PLUS -> 2 groups -> 2 "Reduced to single
+   lane" i16 r8:  8 REDUC_PLUS -> 2 groups -> 2 "Reduced to single lane" i8
+   r16: 16 REDUC_PLUS -> 4 groups -> 4 "Reduced to single lane" Total at -O2:
+   10 "Reduced to single lane", 39 "Adding REDUC_PLUS", 3 remaining .REDUC_PLUS
+   (from the r7 leftover).
+
+   The transformation replaces REDUC_PLUS with VEC_PERM_EXPR + PLUS_EXPR.  */
+
+/* { dg-final { scan-tree-dump-times "Reduced to single lane" 10
+ * "reduc_plus_opt1" { target { x86_64-*-* i?86-*-* } } } } */
+/* { dg-final { scan-tree-dump-times ". = .REDUC_PLUS (." 3 "reduc_plus_opt1" {
+ * target { x86_64-*-* i?86-*-* } } } } */
+/* { dg-final { scan-tree-dump-times "Adding REDUC_PLUS stmt to chain" 39
+ * "reduc_plus_opt1" { target { x86_64-*-* i?86-*-* } } } } */
+/* { dg-final { scan-tree-dump "VEC_PERM_EXPR" "reduc_plus_opt1" { target {
+ * x86_64-*-* i?86-*-* } } } } */
diff --git a/gcc/timevar.def b/gcc/timevar.def
index 588d987ec35..265f80e4f03 100644
--- a/gcc/timevar.def
+++ b/gcc/timevar.def
@@ -207,6 +207,7 @@ DEFTIMEVAR (TV_SCALAR_CLEANUP        , "scalar cleanup")
 DEFTIMEVAR (TV_TREE_PARALLELIZE_LOOPS, "tree parallelize loops")
 DEFTIMEVAR (TV_TREE_VECTORIZATION    , "tree vectorization")
 DEFTIMEVAR (TV_TREE_SLP_VECTORIZATION, "tree slp vectorization")
+DEFTIMEVAR (TV_TREE_COMBINE_REDUC_PLUS_OPT, "tree combine reduc_plus 
optimization")
 DEFTIMEVAR (TV_GRAPHITE              , "Graphite")
 DEFTIMEVAR (TV_GRAPHITE_TRANSFORMS   , "Graphite loop transforms")
 DEFTIMEVAR (TV_GRAPHITE_DATA_DEPS    , "Graphite data dep analysis")
diff --git a/gcc/tree-combine-reduc-plus-opt.cc 
b/gcc/tree-combine-reduc-plus-opt.cc
new file mode 100644
index 00000000000..6114e8baf54
--- /dev/null
+++ b/gcc/tree-combine-reduc-plus-opt.cc
@@ -0,0 +1,662 @@
+/* GIMPLE pass: optimize REDUC_PLUS patterns after vectorization.
+   Copyright (C) 2026 Free Software Foundation, Inc.
+
+   This file is part of GCC.
+
+   GCC is free software; you can redistribute it and/or modify it under
+   the terms of the GNU General Public License as published by the Free
+   Software Foundation; either version 3, or (at your option) any later
+   version.
+
+   GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+   WARRANTY; without even the implied warranty of MERCHANTABILITY or
+   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+   for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with GCC; see the file COPYING3.  If not see
+   <http://www.gnu.org/licenses/>.  */
+
+/* This pass runs after the vectorizer has finished prologue and epilogue
+   loop vectorization (i.e.  after pass_vectorize and pass_slp_vectorize).
+   It detects and optimizes REDUC_PLUS (IFN_REDUC_PLUS) patterns on
+   GIMPLE.  Pattern detection and transformation can be implemented in
+   reduc_plus_opt::execute and the helper functions below.  */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "backend.h"
+#include "tree.h"
+#include "gimple.h"
+#include "tree-pass.h"
+#include "ssa.h"
+#include "fold-const.h"
+#include "tree-cfg.h"
+#include "tree-into-ssa.h"
+#include "tree-ssa.h"
+#include "cfgloop.h"
+#include "gimple-iterator.h"
+#include "internal-fn.h"
+#include "cfg.h"
+#include "hash-map.h"
+#include "hash-traits.h"
+#include "vec.h"
+#include "vec-perm-indices.h"
+#include "tree-vectorizer.h"
+#include "optabs-query.h"
+
+namespace
+{
+
+#define LANE_WIDTH_BITS 128
+typedef int_hash<int, -1, -2> reduc_int_hash_t;
+
+/* Unpack node for GIMPLE: binary tree node representing one or more
+   REDUC_PLUS operations that can be optimized together via unpack
+   (interleave low/high) and add.  */
+struct unpack_node
+{
+  bool leaf_node;
+  auto_vec<int> red_map;       /* Reduction index mapping.  */
+  auto_vec<gimple *> red_values;
+  unpack_node *left;
+  unpack_node *right;
+
+  unpack_node () : leaf_node (true), left (nullptr), right (nullptr) {}
+
+  ~unpack_node ()
+  {
+    delete left;
+    delete right;
+  }
+
+  unpack_node (unpack_node *left, unpack_node *right)
+  {
+    gcc_assert (left != nullptr && right != nullptr && left != right);
+    this->leaf_node = false;
+    this->left = left;
+    this->right = right;
+    unsigned left_size = left->red_map.length ();
+    unsigned right_size = right->red_map.length ();
+    unsigned max_sz = std::max (left_size, right_size);
+    this->red_map.safe_grow_cleared (2 * max_sz);
+    for (auto val : left->red_values)
+      this->red_values.safe_push (val);
+    for (auto val : right->red_values)
+      this->red_values.safe_push (val);
+    for (unsigned i = 0; i < max_sz; i++)
+      {
+       this->red_map[2 * i] = left->red_map[i % left_size];
+       this->red_map[2 * i + 1]
+           = left->red_values.length () + right->red_map[i % right_size];
+      }
+  }
+
+  /* Return a map from reduction index to vector of positions in red_map.
+     Keys are indices into red_values (invariant: every value stored in
+     red_map is a valid index into red_values, maintained by the merging
+     constructor which concatenates left/right red_values and offsets
+     right's red_map entries by left->red_values.length()).  */
+  hash_map<reduc_int_hash_t, auto_vec<int> > *
+  get_reduction_index ()
+  {
+    gcc_assert (this->red_map.length () > 0);
+    int max_elt = this->red_map[0];
+    for (unsigned i = 1; i < this->red_map.length (); i++)
+      if (this->red_map[i] > max_elt)
+       max_elt = this->red_map[i];
+    gcc_assert (
+       max_elt + 1 >= 2 && max_elt + 1 <= 16
+       && "Maximum number of elements in a lane must be 2, 4, 8 or 16");
+    hash_map<reduc_int_hash_t, auto_vec<int> > *result
+       = new hash_map<reduc_int_hash_t, auto_vec<int> > ();
+    for (unsigned i = 0; i < this->red_map.length (); i++)
+      result->get_or_insert (this->red_map[i]).safe_push (i);
+    return result;
+  }
+};
+
+/* Build vec_perm masks for unpack lo and unpack hi of two vectors Va, Vb.
+   Va has indices 0..nunits-1, Vb has nunits..2*nunits-1.
+   We use groups of 4 lanes:
+   - Unpack lo: out = Va[0],Vb[0],Va[1],Vb[1], Va[4],Vb[4],Va[5],Vb[5], ...
+   - Unpack hi: out = Va[2],Vb[2],Va[3],Vb[3], Va[6],Vb[6],Va[7],Vb[7], ...
+   Return true if the target supports both permutations; then MASK_LO and
+   MASK_HI are set to the VECTOR_CST mask trees for use in VEC_PERM_EXPR.  */
+static bool
+build_unpack_lo_hi_masks (tree vectype, poly_uint64 nunits, tree *mask_lo,
+                         tree *mask_hi)
+{
+  unsigned HOST_WIDE_INT n;
+  unsigned int elt_bits = TYPE_PRECISION (TREE_TYPE (vectype));
+  unsigned int lane_elts = LANE_WIDTH_BITS / elt_bits;
+  if (!nunits.is_constant (&n) || (n % lane_elts) != 0)
+    return false;
+  unsigned int ngroups = n / lane_elts;
+  machine_mode mode = TYPE_MODE (vectype);
+
+  /* Builder: nunits elements, lane_elts interleaved patterns,
+     ngroups groups per pattern.  */
+  vec_perm_builder sel_lo (nunits, lane_elts, ngroups);
+  vec_perm_builder sel_hi (nunits, lane_elts, ngroups);
+  for (unsigned int e = 0; e < ngroups; e++)
+    {
+      for (unsigned int i = 0; i < lane_elts / 2; i++)
+       {
+         sel_lo.quick_push (e * lane_elts + i);
+         sel_lo.quick_push (n + e * lane_elts + i);
+       }
+      for (unsigned int i = lane_elts / 2; i < lane_elts; i++)
+       {
+         sel_hi.quick_push (e * lane_elts + i);
+         sel_hi.quick_push (n + e * lane_elts + i);
+       }
+    }
+  vec_perm_indices indices_lo (sel_lo, 2, nunits);
+  vec_perm_indices indices_hi (sel_hi, 2, nunits);
+  if (!can_vec_perm_const_p (mode, mode, indices_lo)
+      || !can_vec_perm_const_p (mode, mode, indices_hi))
+    return false;
+  *mask_lo = vect_gen_perm_mask_checked (vectype, indices_lo);
+  *mask_hi = vect_gen_perm_mask_checked (vectype, indices_hi);
+  return true;
+}
+
+/* Return true if STMT is a REDUC_PLUS internal call.  */
+static bool
+is_reduc_plus_call (gimple *stmt)
+{
+  if (!is_gimple_call (stmt) || !gimple_call_internal_p (stmt))
+    return false;
+  return gimple_call_internal_fn (stmt) == IFN_REDUC_PLUS;
+}
+
+/* Collect REDUC_PLUS stmts in BB and group them by vector type into CHAINS.
+   Each leaf unpack_node holds one REDUC_PLUS stmt.  */
+static void
+detect_reduction_for_bb (basic_block bb,
+                        hash_map<tree, auto_vec<unpack_node *> > *chains)
+{
+  gimple_stmt_iterator gsi;
+  gimple *prev = nullptr;
+  for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
+    {
+      gimple *stmt = gsi_stmt (gsi);
+      if (!is_reduc_plus_call (stmt))
+       continue;
+      if (prev != nullptr && prev->next != stmt)
+       continue;
+      prev = stmt;
+      gcc_assert (gimple_call_num_args (stmt) == 1);
+      tree arg = gimple_call_arg (stmt, 0);
+      /* Group by full VECTOR_TYPE (element type and lane count).  */
+      tree vectype = TREE_TYPE (arg);
+      if (!VECTOR_TYPE_P (vectype))
+       continue;
+      unpack_node *node = new unpack_node ();
+      node->red_values.safe_push (stmt);
+      node->red_map.safe_push (0);
+      chains->get_or_insert (vectype).safe_push (node);
+      if (dump_file)
+       {
+         fprintf (dump_file, "Adding REDUC_PLUS stmt to chain with type %d\n",
+                  TREE_CODE (vectype));
+         dump_gimple_stmt (MSG_NOTE, TDF_SLIM, stmt, 0);
+       }
+    }
+}
+
+/* Iterating on the unpack binary tree recursively and building a
+   sequence of vec_perm and add statements for each node in the unpack
+   tree to SEQ for the unpacking of two vectors Va, Vb for a node.
+   VEC_PERM_EXPR (Va, Vb, mask_lo) -> low_half
+   VEC_PERM_EXPR (Va, Vb, mask_hi) -> high_half
+   PLUS_EXPR (low_half, high_half) -> sum = low_half + high_half
+   Return the sum vector.  */
+static tree
+prepare_vec_perm_seq (unpack_node *node, tree vectype, gimple_seq *seq)
+{
+  if (node->leaf_node)
+    {
+      gimple *stmt = node->red_values[0];
+      gcc_assert (node->red_values.length () == 1
+                 && gimple_call_num_args (stmt) == 1);
+      return gimple_call_arg (stmt, 0);
+    }
+  gimple_seq left_seq = NULL;
+  gimple_seq right_seq = NULL;
+  tree left = prepare_vec_perm_seq (node->left, vectype, &left_seq);
+  tree right = prepare_vec_perm_seq (node->right, vectype, &right_seq);
+  if (left == NULL_TREE || right == NULL_TREE)
+    {
+      gimple_seq_discard (left_seq);
+      gimple_seq_discard (right_seq);
+      return NULL_TREE;
+    }
+  poly_uint64 nunits = TYPE_VECTOR_SUBPARTS (vectype);
+  tree mask_lo = NULL_TREE, mask_hi = NULL_TREE;
+  /* Build the vec_perm masks for the unpacking of the low and high
+     halves of the vectors.  */
+  if (!build_unpack_lo_hi_masks (vectype, nunits, &mask_lo, &mask_hi))
+    {
+      gimple_seq_discard (left_seq);
+      gimple_seq_discard (right_seq);
+      return NULL_TREE;
+    }
+  /* Add the sequences for the left and right children to the current
+     sequence.  */
+  gimple_seq_add_seq (seq, left_seq);
+  gimple_seq_add_seq (seq, right_seq);
+  tree unpack_lo_result = make_ssa_name (vectype);
+  gimple *perm_lo = gimple_build_assign (unpack_lo_result, VEC_PERM_EXPR, left,
+                                        right, mask_lo);
+  gimple_seq_add_stmt (seq, perm_lo);
+  tree unpack_hi_result = make_ssa_name (vectype);
+  gimple *perm_hi = gimple_build_assign (unpack_hi_result, VEC_PERM_EXPR, left,
+                                        right, mask_hi);
+  gimple_seq_add_stmt (seq, perm_hi);
+  tree output = make_ssa_name (vectype);
+  gimple *add_stmt = gimple_build_assign (output, PLUS_EXPR, unpack_lo_result,
+                                         unpack_hi_result);
+  gimple_seq_add_stmt (seq, add_stmt);
+  return output;
+}
+
+/* Reduce the vector from wide (e.g.  ZMM) down to 128-bit lane by
+   repeatedly extracting low/high halves and adding them.  E.g.
+   V16int32 -> extract low V8int32 and high V8int32, add -> V8int32;
+   then V8int32 -> V4int32; stop at 128 bits (V4int32 for 32-bit
+   elements).  Appends generated statements to SEQ and returns the
+   final vector (single 128-bit lane).  */
+static tree
+reduce_to_single_lane (tree vec_result, gimple_seq *seq)
+{
+  tree vectype = TREE_TYPE (vec_result);
+  tree elt_type = TREE_TYPE (vectype);
+  unsigned elt_bits = TYPE_PRECISION (elt_type);
+  poly_uint64 nunits = TYPE_VECTOR_SUBPARTS (vectype);
+  unsigned HOST_WIDE_INT n;
+  if (!nunits.is_constant (&n) || n < 2)
+    return vec_result;
+
+  unsigned total_bits = n * elt_bits;
+  if (total_bits <= LANE_WIDTH_BITS)
+    return vec_result;
+
+  tree current = vec_result;
+
+  while (true)
+    {
+      nunits = TYPE_VECTOR_SUBPARTS (vectype);
+      if (!nunits.is_constant (&n) || n < 2)
+       break;
+      total_bits = n * elt_bits;
+      if (total_bits <= LANE_WIDTH_BITS)
+       break;
+
+      unsigned half_n = n / 2;
+      tree half_vectype = build_vector_type (elt_type, half_n);
+      machine_mode full_mode = TYPE_MODE (vectype);
+      machine_mode half_mode = TYPE_MODE (half_vectype);
+
+      vec_perm_builder sel_lo (half_n, 1, half_n);
+      vec_perm_builder sel_hi (half_n, 1, half_n);
+      for (unsigned i = 0; i < half_n; i++)
+       {
+         sel_lo.quick_push (i);
+         sel_hi.quick_push (half_n + i);
+       }
+      vec_perm_indices indices_lo (sel_lo, 1, n);
+      vec_perm_indices indices_hi (sel_hi, 1, n);
+
+      if (!can_vec_extract (full_mode, half_mode))
+       return NULL_TREE;
+
+      tree mask_lo = vect_gen_perm_mask_checked (half_vectype, indices_lo);
+      tree mask_hi = vect_gen_perm_mask_checked (half_vectype, indices_hi);
+
+      tree low_half = make_ssa_name (half_vectype);
+      gimple *g_lo = gimple_build_assign (low_half, VEC_PERM_EXPR, current,
+                                         current, mask_lo);
+      gimple_seq_add_stmt (seq, g_lo);
+
+      tree high_half = make_ssa_name (half_vectype);
+      gimple *g_hi = gimple_build_assign (high_half, VEC_PERM_EXPR, current,
+                                         current, mask_hi);
+      gimple_seq_add_stmt (seq, g_hi);
+
+      tree sum = make_ssa_name (half_vectype);
+      gimple *g_add
+         = gimple_build_assign (sum, PLUS_EXPR, low_half, high_half);
+      gimple_seq_add_stmt (seq, g_add);
+      current = sum;
+      vectype = half_vectype;
+    }
+  if (dump_file)
+    fprintf (dump_file, "Reduced to single lane\n");
+  return current;
+}
+
+/* Return true if the target supports whole-vector shifts for MODE,
+   either via vec_shr_optab or via vec_perm_const for every needed
+   shift amount.  Mirrors have_whole_vector_shift in tree-vect-loop.cc.  */
+static bool
+have_whole_vector_shift (machine_mode mode)
+{
+  if (can_implement_p (vec_shr_optab, mode))
+    return true;
+
+  unsigned int nelt;
+  if (!GET_MODE_NUNITS (mode).is_constant (&nelt))
+    return false;
+
+  for (unsigned int i = nelt / 2; i >= 1; i /= 2)
+    {
+      /* Build the shift-by-i permutation mask:
+        a single stepped pattern {i, i+1, i+2}.  */
+      vec_perm_builder sel (nelt, 1, 3);
+      for (unsigned int j = 0; j < 3; j++)
+       sel.quick_push (j + i);
+      vec_perm_indices indices (sel, 2, nelt);
+      if (!can_vec_perm_const_p (mode, mode, indices, false))
+       return false;
+    }
+  return true;
+}
+
+/* Estimate the target cost of the replacement sequence SEQ.  The vectype
+   for costing each stmt is derived from its LHS so that lane-reduction
+   stmts (which operate on narrower vectors) are costed correctly.  */
+static int
+estimate_seq_cost (gimple_seq seq)
+{
+  int cost = 0;
+  for (gimple_stmt_iterator si = gsi_start (seq); !gsi_end_p (si);
+       gsi_next (&si))
+    {
+      gimple *stmt = gsi_stmt (si);
+      if (!is_gimple_assign (stmt))
+       continue;
+      tree lhs = gimple_assign_lhs (stmt);
+      tree stmt_type = TREE_TYPE (lhs);
+      tree cost_vectype = VECTOR_TYPE_P (stmt_type) ? stmt_type : NULL_TREE;
+      enum tree_code code = gimple_assign_rhs_code (stmt);
+      if (code == VEC_PERM_EXPR)
+       cost += builtin_vectorization_cost (vec_perm, cost_vectype, 0);
+      else if (code == BIT_FIELD_REF)
+       cost += builtin_vectorization_cost (vec_to_scalar, cost_vectype, 0);
+      else
+       cost += builtin_vectorization_cost (vector_stmt, cost_vectype, 0);
+    }
+  return cost;
+}
+
+/* Estimate the target cost of N_REDUCTIONS original REDUC_PLUS stmts.
+   Each horizontal reduction is lowered as a series of halving steps
+   (extract-high, extract-low, add) on progressively narrower vector
+   types, followed by a final scalar extract.  Cost each step at its
+   actual vector width so targets where narrower ops are cheaper get
+   an accurate estimate.  */
+static int
+estimate_orig_reduc_cost (unsigned n_reductions, tree vectype)
+{
+  tree elt_type = TREE_TYPE (vectype);
+  unsigned elt_bits = TYPE_PRECISION (elt_type);
+  poly_uint64 nunits = TYPE_VECTOR_SUBPARTS (vectype);
+  unsigned HOST_WIDE_INT n;
+  if (!nunits.is_constant (&n) || n < 2)
+    return 0;
+
+  int per_reduc = 0;
+  tree cur_vectype = vectype;
+  unsigned cur_n = n;
+
+  unsigned lane_elts = LANE_WIDTH_BITS / elt_bits;
+
+  /* Cross-lane halving: reduce from full width down to a single
+     128-bit lane.  Each step extracts low/high halves (two vec_perm
+     at the current width) and adds them (one vector_stmt at the
+     narrower width).  */
+  while (cur_n > lane_elts && cur_n >= 2)
+    {
+      unsigned half_n = cur_n / 2;
+      tree half_vectype = build_vector_type (elt_type, half_n);
+      per_reduc += 2 * builtin_vectorization_cost (vec_perm, cur_vectype, 0);
+      per_reduc += builtin_vectorization_cost (vector_stmt, half_vectype, 0);
+      cur_vectype = half_vectype;
+      cur_n = half_n;
+    }
+
+  /* Intra-lane reduction within the 128-bit lane.  */
+  if (cur_n >= 2)
+    {
+      machine_mode mode = TYPE_MODE (cur_vectype);
+      if (VECTOR_MODE_P (mode) && have_whole_vector_shift (mode))
+       {
+         int nshifts = exact_log2 (cur_n);
+         per_reduc
+             += nshifts * 2
+                * builtin_vectorization_cost (vector_stmt, cur_vectype, 0);
+         per_reduc
+             += builtin_vectorization_cost (vec_to_scalar, cur_vectype, 0);
+       }
+      else
+       per_reduc
+           += (2 * cur_n - 1)
+              * builtin_vectorization_cost (vector_stmt, cur_vectype, 0);
+    }
+
+  return n_reductions * per_reduc;
+}
+
+bool
+may_build_unpack_tree (auto_vec<unpack_node *> &UnpackBinTree, tree vectype)
+{
+  /* Trim trailing reductions so the count is a multiple of lane
+      elements.  Dropped nodes are not optimized; their original
+      REDUC_PLUS stmts remain untouched in the BB.  */
+  int elt_bits = TYPE_PRECISION (TREE_TYPE (vectype));
+  int num_elts = LANE_WIDTH_BITS / elt_bits;
+  int max_h = floor_log2 (num_elts);
+  int tree_height = 0;
+  while (UnpackBinTree.length () % num_elts != 0
+        && UnpackBinTree.length () > 2)
+    {
+      if (dump_file)
+       fprintf (dump_file,
+                "Dropping trailing REDUC_PLUS to align count"
+                " to %d-element lane boundary (%u remaining)\n",
+                num_elts, UnpackBinTree.length () - 1);
+      delete UnpackBinTree.pop ();
+    }
+  if (dump_file)
+    {
+      fprintf (dump_file, "eltsize: %d, num_elts: %d, max_h: %d\n", elt_bits,
+              num_elts, max_h);
+      fprintf (
+         dump_file,
+         "number of reductions %d must be multiple of lane elements %d\n",
+         UnpackBinTree.length (), num_elts);
+    }
+  if (num_elts < 2 || UnpackBinTree.length () % num_elts != 0
+      || UnpackBinTree.length () < 2)
+    {
+      for (auto node : UnpackBinTree)
+       delete node;
+      return false;
+    }
+  auto_vec<unpack_node *> TempUnpackBinTree;
+  while (true)
+    {
+      if (UnpackBinTree.length () == 1)
+       {
+         TempUnpackBinTree.safe_push (UnpackBinTree.pop ());
+       }
+      if (UnpackBinTree.length () == 0)
+       {
+         UnpackBinTree = TempUnpackBinTree.copy ();
+         TempUnpackBinTree.release ();
+         if (++tree_height >= max_h || UnpackBinTree.length () == 1)
+           break;
+         continue;
+       }
+      unpack_node *left = UnpackBinTree.pop ();
+      unpack_node *right = UnpackBinTree.pop ();
+      TempUnpackBinTree.safe_push (new unpack_node (left, right));
+    }
+  return true;
+}
+
+/* Main pass execution: detect REDUC_PLUS chains per BB,
+   build unpack tree.  */
+static unsigned int
+efficient_multiple_reduction (function *fun)
+{
+  basic_block bb;
+  FOR_EACH_BB_FN (bb, fun)
+  {
+    hash_map<tree, auto_vec<unpack_node *> > reduction_groups;
+    /* Collect the REDUC_PLUS stmts in the BB and group them by vector
+       type.  */
+    detect_reduction_for_bb (bb, &reduction_groups);
+    for (auto reduction : reduction_groups)
+      {
+       tree vectype = reduction.first;
+       auto_vec<unpack_node *> &UnpackBinTree = reduction.second;
+       if (!may_build_unpack_tree (UnpackBinTree, vectype))
+         continue;
+       for (auto node : UnpackBinTree)
+         {
+           if (node->leaf_node)
+             continue;
+           gimple_seq seq = NULL;
+           /* Build the sequence of vec_perm and add statements for
+              the unpacking of the low and high halves.  */
+           tree reduced_output = prepare_vec_perm_seq (node, vectype, &seq);
+           if (reduced_output == NULL_TREE)
+             continue;
+           /* Reduce the vector from wide (e.g.  ZMM) down to
+              128-bit lane by repeatedly halving.  */
+           tree single_lane = reduce_to_single_lane (reduced_output, &seq);
+           if (single_lane == NULL_TREE || seq == NULL)
+             {
+               gimple_seq_discard (seq);
+               continue;
+             }
+           hash_map<reduc_int_hash_t, auto_vec<int> > *idx_map
+               = node->get_reduction_index ();
+           /* Need to skip the get_reduc_index map logic.  Can we avoid
+              using hashmap?  Generate the extracts of reduced value
+              from the reduced lane vector.  */
+           for (auto idx : *idx_map)
+             {
+               auto_vec<int> &idx_list = idx.second;
+               gcc_assert (idx.first >= 0
+                           && (unsigned)idx.first
+                                  < node->red_values.length ());
+               gcc_assert (idx_list.length () >= 1);
+               gimple *reduc_stmt = node->red_values[idx.first];
+               tree reduc_lhs = gimple_call_lhs (reduc_stmt);
+               tree elt_type = TREE_TYPE (vectype);
+               int elt_bits = TYPE_PRECISION (elt_type);
+               tree size_bits = bitsize_int (elt_bits);
+               tree offset_bits = bitsize_int (idx_list[0] * elt_bits);
+               tree bf_ref = build3 (BIT_FIELD_REF, elt_type, single_lane,
+                                     size_bits, offset_bits);
+               gimple *extract_result
+                   = gimple_build_assign (reduc_lhs, bf_ref);
+               gimple_seq_add_stmt (&seq, extract_result);
+             }
+           /* Estimate the cost of new sequence to be replaced
+              for the target.  */
+           int new_cost = estimate_seq_cost (seq);
+           /* Estimate the cost of the original REDUC_PLUS stmts.  */
+           int orig_cost = estimate_orig_reduc_cost (
+               node->red_values.length (), vectype);
+           if (dump_file)
+             fprintf (dump_file,
+                      "Costing: seq_cost=%d, orig_cost=%d (%u reductions)\n",
+                      new_cost, orig_cost, node->red_values.length ());
+           /* If the new sequence is not cheaper than the original,
+              skip the replacement.  */
+           if (new_cost >= orig_cost)
+             {
+               if (dump_file)
+                 fprintf (dump_file,
+                          "  -> not profitable, skipping replacement\n");
+               gimple_seq_discard (seq);
+               delete idx_map;
+               continue;
+             }
+           if (dump_file)
+             fprintf (dump_file, "  -> profitable, applying replacement\n");
+           /* Insert the new sequence of gimple statements after
+              the last REDUC_PLUS stmt.  */
+           gimple *last_reduc
+               = node->red_values[node->red_values.length () - 1];
+           gimple_stmt_iterator gsi = gsi_for_stmt (last_reduc);
+           gsi_insert_seq_after (&gsi, seq, GSI_SAME_STMT);
+           /* Delete the original REDUC_PLUS stmts.  */
+           for (unsigned i = node->red_values.length (); i > 0; i--)
+             {
+               gimple *reduc_stmt = node->red_values[i - 1];
+               gsi = gsi_for_stmt (reduc_stmt);
+               gsi_remove (&gsi, true);
+             }
+           delete idx_map;
+         }
+       /* Cleaning up the memory for allocated nodes.  */
+       for (auto node : UnpackBinTree)
+         delete node;
+      }
+  }
+  return 0;
+}
+
+const pass_data pass_data_combine_reduc_plus_opt = {
+  GIMPLE_PASS,
+  "combine_reduc_plus_opt",
+  OPTGROUP_LOOP | OPTGROUP_VEC,
+  TV_TREE_COMBINE_REDUC_PLUS_OPT,
+  (PROP_cfg | PROP_ssa),
+  0,
+  0,
+  0,
+  TODO_update_ssa,
+};
+
+class pass_combine_reduc_plus_opt : public gimple_opt_pass
+{
+public:
+  pass_combine_reduc_plus_opt (gcc::context *ctxt)
+      : gimple_opt_pass (pass_data_combine_reduc_plus_opt, ctxt)
+  {
+  }
+
+  opt_pass *
+  clone () final override
+  {
+    return new pass_combine_reduc_plus_opt (m_ctxt);
+  }
+
+  bool
+  gate (function *) final override
+  {
+    return flag_tree_combine_reduc_plus_opt;
+  }
+
+  unsigned int
+  execute (function *fun) final override
+  {
+    return efficient_multiple_reduction (fun);
+  }
+};
+
+} // anon namespace
+
+gimple_opt_pass *
+make_pass_combine_reduc_plus_opt (gcc::context *ctxt)
+{
+  return new pass_combine_reduc_plus_opt (ctxt);
+}
diff --git a/gcc/tree-pass.h b/gcc/tree-pass.h
index b3c97658a8f..d4f11a86771 100644
--- a/gcc/tree-pass.h
+++ b/gcc/tree-pass.h
@@ -393,6 +393,7 @@ extern gimple_opt_pass *make_pass_crc_optimization 
(gcc::context *ctxt);
 extern gimple_opt_pass *make_pass_vectorize (gcc::context *ctxt);
 extern gimple_opt_pass *make_pass_simduid_cleanup (gcc::context *ctxt);
 extern gimple_opt_pass *make_pass_slp_vectorize (gcc::context *ctxt);
+extern gimple_opt_pass *make_pass_combine_reduc_plus_opt (gcc::context *ctxt);
 extern gimple_opt_pass *make_pass_complete_unroll (gcc::context *ctxt);
 extern gimple_opt_pass *make_pass_complete_unrolli (gcc::context *ctxt);
 extern gimple_opt_pass *make_pass_pre_slp_scalar_cleanup (gcc::context *ctxt);
-- 
2.34.1

Reply via email to