On Mon, 25 May 2026, [email protected] wrote:

> From: Reshma Roy <[email protected]>
> 
> Implement analysis using SCEV information to detect when gather/scatter
> offset expressions evaluate to the same value for all vector lanes within
> each vector iteration. For example, if maximum VF=8, iterations 0-7 of
> "i >> 6" all evaluate to 0, enabling optimization to broadcast. If the
> offset is uniform at max VF, it is uniform at any smaller VF, so we
> only need to check once. The uniformity information is propagated to
> SLP nodes for use in the transformation phase.
> 
> Example: For "M[i >> 6]" with VF=8:
> - Iterations 0-7: i >> 6 = 0 (uniform)
> - Iterations 64-71: i >> 6 = 1 (uniform)
> Here we can optimize each vector iteration to load once + broadcast
> 
> gcc/ChangeLog:
> 
>       * tree-vect-data-refs.cc (vect_describe_gather_scatter_call): Initialize
>       STMT_VINFO_GATHER_UNIFORM_P in stmt_vec_info to false.
>       (vect_check_gather_load_offset_uniform): New.  Return whether the
>       gather offset is the same for every lane at the given VF.
>       (vect_check_gather_scatter): Run the uniformity check, cache by offset
>       to avoid recomputing for the same offset, and set 
>       STMT_VINFO_GATHER_UNIFORM_P.
>       * tree-vect-loop.cc (vect_analyze_loop): Compute maximum VF over 
>       candidate vector modes for the uniformity check.
>       * tree-vect-slp.cc (_slp_tree::_slp_tree): Initialize and propagate
>       gs_offset_uniform_p on SLP trees.
>       * tree-vect-stmts.cc (get_load_store_type): Copy gather uniform flag
>       from stmt_vec_info to SLP nodes.
>       * tree-vectorizer.cc (vec_info_shared::vec_info_shared): Initialize
>       max_autovectorize_vf, which is the maximum VF over all candidate
>       vector modes.
>       (vec_info::new_stmt_vec_info): Initialize gather_offset_uniform_p.
>       * tree-vectorizer.h (struct _slp_tree): Add uniformity fields, cache,
>       and accessors for emulated gather offset analysis.
>       (SLP_TREE_GS_OFFSET_UNIFORM_P): New. Uniformity flag for SLP trees.
>       (struct gather_scatter_info): Added STMT_VINFO_GATHER_UNIFORM_P field.
>       (STMT_VINFO_GATHER_UNIFORM_P): New. Uniformity flag for stmt_vec_info.
>       (gs_offset_uniform_p): New field in _slp_tree for uniformity flag.
> 
> ---
>  gcc/tree-vect-data-refs.cc | 138 ++++++++++++++++++++++++++++++++++++-
>  gcc/tree-vect-loop.cc      |  14 ++++
>  gcc/tree-vect-slp.cc       |   4 ++
>  gcc/tree-vect-stmts.cc     |  10 +++
>  gcc/tree-vectorizer.cc     |   5 +-
>  gcc/tree-vectorizer.h      |  15 ++++
>  6 files changed, 184 insertions(+), 2 deletions(-)
> 
> diff --git a/gcc/tree-vect-data-refs.cc b/gcc/tree-vect-data-refs.cc
> index da65f1d652c..fe7fc34aeac 100644
> --- a/gcc/tree-vect-data-refs.cc
> +++ b/gcc/tree-vect-data-refs.cc
> @@ -4764,6 +4764,7 @@ void
>  vect_describe_gather_scatter_call (stmt_vec_info stmt_info,
>                                  gather_scatter_info *info)
>  {
> +  STMT_VINFO_GATHER_UNIFORM_P (stmt_info) = false;
>    gcall *call = as_a <gcall *> (stmt_info->stmt);
>    tree vectype = STMT_VINFO_VECTYPE (stmt_info);
>    data_reference *dr = STMT_VINFO_DATA_REF (stmt_info);
> @@ -4781,17 +4782,119 @@ vect_describe_gather_scatter_call (stmt_vec_info 
> stmt_info,
>    info->element_type = TREE_TYPE (vectype);
>    info->memory_type = TREE_TYPE (DR_REF (dr));
>  }
> +/* Check whether the offset in gather load is uniform across all the VF 
> lanes.
> +   If true then  If uniform, record it in STMT_VINFO_GATHER_UNIFORM_P
> +   (stmt_info) so later gather-load lowering can use scalar-load + broadcast
> +   instead of full emulated gather.  */
> +static bool
> +vect_check_gather_load_offset_uniform (class loop *loop,
> +                                    HOST_WIDE_INT const_vf,
> +                                    tree off)
> +{
> +  /* Check ensures that the offset is an SSA_NAME which is qualified for
> +     uniform detection.  */
> +  if (!off || TREE_CODE (off) != SSA_NAME)
> +    return false;
> +  tree off_scev = analyze_scalar_evolution (loop, off);
> +  if (off_scev && off_scev != chrec_dont_know
> +      && TREE_CODE (off_scev) != SSA_NAME)
> +    off_scev = instantiate_parameters (loop, off_scev);

As you are looking for an 'off' that evaluates to the same value
for N iterations it can never be an affine evolution, so no need
to analyze 'off' itself.

Instead you are looking for BIT_AND_EXPR or TRUNC_DIV_EXPR,
both with a constant 2nd operand.

> +  tree chrec1 = NULL_TREE, chrec2 = NULL_TREE;
> +  tree_code op_code = ERROR_MARK;
> +  tree op_type = TREE_TYPE (off);
> +  if (!off_scev || off_scev == chrec_dont_know
> +      || TREE_CODE (off_scev) == SSA_NAME)
> +    {
> +      gimple *def_stmt = SSA_NAME_DEF_STMT (off);
> +      if (is_gimple_assign (def_stmt))
> +     {
> +       op_code = gimple_assign_rhs_code (def_stmt);
> +       tree rhs2 = gimple_assign_rhs2 (def_stmt);
> +       if (rhs2 && TREE_CODE_CLASS (op_code) == tcc_binary)
> +         {
> +           tree rhs1 = gimple_assign_rhs1 (def_stmt);
> +           chrec1 = analyze_scalar_evolution (loop, rhs1);

so only analyzing RHS1 is required (and RHS2 can be used to
pre-filter interesting cases).

> +           chrec2 = analyze_scalar_evolution (loop, rhs2);
> +           chrec1 = instantiate_parameters (loop, chrec1);
> +           chrec2 = instantiate_parameters (loop, chrec2);
> +           if (dump_enabled_p ())
> +             {
> +               dump_printf_loc (MSG_NOTE, vect_location, "chrec 1: ");
> +               dump_generic_expr (MSG_NOTE, TDF_SLIM, chrec1);
> +               dump_printf (MSG_NOTE, "\nchrec2: ");
> +               dump_generic_expr (MSG_NOTE, TDF_SLIM, chrec2);
> +               dump_printf (MSG_NOTE, "\n");
> +             }
> +         }
> +     }
> +    }
> +  if (!chrec1 || !chrec2 || chrec1 == chrec_dont_know
> +      || chrec2 ==chrec_dont_know)
> +    return false;
> +
> +  /* Strict check for operand 1 to be poly rec and
> +      operand 2 to be constant.  */
> +  if (!(TREE_CODE (chrec1) == POLYNOMIAL_CHREC)
> +      || TREE_CODE (chrec2) != INTEGER_CST)
> +    return false;
> +  tree scev_base = CHREC_LEFT (chrec1);
> +  tree scev_step = CHREC_RIGHT (chrec1);
> +  /* PoC valid for {0, +, 1} induction pattern now.
> +     TODO Extend to handle general case.  */
> +  if ( TREE_CODE (scev_base) != INTEGER_CST
> +      || TREE_CODE (scev_step) != INTEGER_CST
> +      || !integer_zerop (scev_base) || !integer_onep (scev_step))
> +    {
> +      if (dump_enabled_p ())
> +     dump_printf_loc (MSG_NOTE, vect_location,
> +                      "returning because of non-zero base or"
> +                      "non-one step\n");
> +      return false;
> +    }
> +  /* Iterate from scev_base advancing scev_step each lane, for the VF lanes.
> +     Then evaluate each operand at the current iteration value,
> +     fold and compare.  */
> +  tree first_val = NULL_TREE;
> +  tree scev_end = fold_build2 (PLUS_EXPR, TREE_TYPE (scev_base),
> +                            scev_base,
> +                            build_int_cst (TREE_TYPE (scev_base),
> +                                           const_vf));
> +  for (tree iter_val = scev_base;
> +       tree_int_cst_lt (iter_val, scev_end);
> +       iter_val = fold_build2 (PLUS_EXPR, TREE_TYPE (iter_val),
> +                            iter_val, scev_step))
> +    {
> +      tree chrec1_at_iter = (TREE_CODE (chrec1) == POLYNOMIAL_CHREC
> +                          ? chrec_apply (loop->num, chrec1, iter_val)
> +                          : chrec1);
> +      tree concrete_val = fold_build2 (op_code, op_type, chrec1_at_iter,
> +                                    chrec2);
> +      if (!first_val)
> +     first_val = concrete_val;
> +      else if (!operand_equal_p (concrete_val, first_val, 0))
> +     return false;
> +      if (first_val && TREE_CODE (first_val) == SSA_NAME
> +       && !expr_invariant_in_loop_p (loop, first_val))
> +     return false;

Uh.  I think we want something more "programmatic", given we
have a binop with constant operand and the CHREC_RIGHT is constant
as well we should be able to comptute arithmetically whether
(CHREC_LEFT + N * CHREC_RIGHT) <op> RHS2 is zero for all N in [0, VF].

> +    }
> +  if (dump_enabled_p ())
> +    dump_printf_loc (MSG_OPTIMIZED_LOCATIONS, vect_location,
> +                  "gather offset is uniform across VF=%d, "
> +                  "broadcast optimization possible\n",
> +                  (int) const_vf);
> +  return true;
> +}
>  
>  /* Return true if a non-affine read or write in STMT_INFO is suitable for a
>     gather load or scatter store with VECTYPE.  Describe the operation in 
> *INFO
>     if so.  If it is suitable and ELSVALS is nonzero store the supported else
>     values in the vector it points to.  */
> -
>  bool
>  vect_check_gather_scatter (stmt_vec_info stmt_info, tree vectype,
>                          loop_vec_info loop_vinfo,
>                          gather_scatter_info *info, vec<int> *elsvals)
>  {
> +  STMT_VINFO_GATHER_UNIFORM_P (stmt_info) = false;

please do not record into stmt_info, instead record into ...

>    HOST_WIDE_INT scale = 1;
>    poly_int64 pbitpos, pbitsize;
>    class loop *loop = LOOP_VINFO_LOOP (loop_vinfo);
> @@ -5114,6 +5217,39 @@ vect_check_gather_scatter (stmt_vec_info stmt_info, 
> tree vectype,
>    info->scale = scale;
>    info->element_type = TREE_TYPE (vectype);
>    info->memory_type = memory_type;

... 'info'.

> +  /* Check whether uniform gather load for all the vector lanes for
> +     maximum VF.  */
> +  unsigned HOST_WIDE_INT max_vf = loop_vinfo->shared->max_autovectorize_vf;

Not sure why you need a max_vf here, you want to perform the analysis
when 'vectype' is not NULL and for TYPE_VECTOR_SUBPARTS (vectype)
I think.

> +  if (max_vf == 0)
> +    {
> +      /* If not set then skip the uniforme check.  */
> +      return true;
> +    }
> +  HOST_WIDE_INT const_vf = (HOST_WIDE_INT) max_vf;
> +  bool uniform = false;
> +  if (const_vf > 0)
> +    {
> +      /* If the uniformity is true for one VF, we do not have to try another
> +     VF.  We always establish uniformity for max_vfs.  Uniformity (max_vf)
> +      implies uniformity (lower_vfs). So reuse cached result for this offset.
> +      re-do the same check.  */
> +      bool *cached = loop_vinfo->offset_uniformity_cache.get (off);
> +      if (cached)
> +     uniform = *cached;
> +      else
> +     {
> +       if (dump_enabled_p ())
> +         {
> +           dump_printf_loc (MSG_NOTE, vect_location,
> +                            "offset uniformity check at max VF=%wd\n",
> +                            const_vf);
> +         }
> +       uniform = vect_check_gather_load_offset_uniform (loop, const_vf, off);
> +       loop_vinfo->offset_uniformity_cache.put (off, uniform);
> +     }
> +    }
> +  /* The information is stored in stm_vinfo for subsequent stages.  */
> +  STMT_VINFO_GATHER_UNIFORM_P (stmt_info) = uniform;
>    return true;
>  }
>  
> diff --git a/gcc/tree-vect-loop.cc b/gcc/tree-vect-loop.cc
> index ac7e08cf205..899934306c1 100644
> --- a/gcc/tree-vect-loop.cc
> +++ b/gcc/tree-vect-loop.cc
> @@ -2965,6 +2965,20 @@ vect_analyze_loop (class loop *loop, gimple 
> *loop_vectorized_call,
>    unsigned int autovec_flags
>      = targetm.vectorize.autovectorize_vector_modes (&vector_modes,
>                                                   loop->simdlen != 0);
> +  /* Get the maximum VF from all the vector_modes to use in checking whether
> +     offset is uniform.  */
> +  unsigned HOST_WIDE_INT max_munits = 0;
> +  for (unsigned i = 0; i < vector_modes.length (); i++)
> +    {
> +      machine_mode mode = vector_modes[i];
> +      if (mode == VOIDmode)
> +     continue;
> +      poly_uint64 m_units = GET_MODE_NUNITS (mode);
> +      unsigned HOST_WIDE_INT c;
> +      if (m_units.is_constant (&c) && c > 0 && c > max_munits)
> +     max_munits = c;
> +    }
> +  shared->max_autovectorize_vf = max_munits;
>    bool pick_lowest_cost_p = ((autovec_flags & VECT_COMPARE_COSTS)
>                            && !unlimited_cost_model (loop));
>    machine_mode autodetected_vector_mode = VOIDmode;
> diff --git a/gcc/tree-vect-slp.cc b/gcc/tree-vect-slp.cc
> index ea49c32b780..7224d6c77db 100644
> --- a/gcc/tree-vect-slp.cc
> +++ b/gcc/tree-vect-slp.cc
> @@ -123,6 +123,7 @@ _slp_tree::_slp_tree ()
>    SLP_TREE_CODE (this) = ERROR_MARK;
>    SLP_TREE_GS_SCALE (this) = 0;
>    SLP_TREE_GS_BASE (this) = NULL_TREE;
> +  this->gs_offset_uniform_p = false;

SLP_TREE_GS_OFFSET_UNIFORM_P

>    this->ldst_lanes = false;
>    this->avoid_stlf_fail = false;
>    SLP_TREE_VECTYPE (this) = NULL_TREE;
> @@ -2809,6 +2810,7 @@ out:
>    int reduc_idx = -1;
>    int gs_scale = 0;
>    tree gs_base = NULL_TREE;
> +  bool gs_offset_uniform_p = false;
>  
>    /* Create SLP_TREE nodes for the definition node/s.  */
>    FOR_EACH_VEC_ELT (oprnds_info, i, oprnd_info)
> @@ -2836,6 +2838,7 @@ out:
>       {
>         gs_scale = oprnd_info->first_gs_info.scale;
>         gs_base = oprnd_info->first_gs_info.base;
> +       gs_offset_uniform_p = STMT_VINFO_GATHER_UNIFORM_P (stmt_info);
>       }
>  
>        if (is_a <bb_vec_info> (vinfo)
> @@ -3256,6 +3259,7 @@ fail:
>    SLP_TREE_CHILDREN (node).splice (children);
>    SLP_TREE_GS_SCALE (node) = gs_scale;
>    SLP_TREE_GS_BASE (node) = gs_base;
> +  SLP_TREE_GS_OFFSET_UNIFORM_P (node) = gs_offset_uniform_p;
>    if (reduc_idx != -1)
>      {
>        gcc_assert (STMT_VINFO_REDUC_IDX (stmt_info) != -1
> diff --git a/gcc/tree-vect-stmts.cc b/gcc/tree-vect-stmts.cc
> index da87b329715..b68fc5072af 100644
> --- a/gcc/tree-vect-stmts.cc
> +++ b/gcc/tree-vect-stmts.cc
> @@ -2167,6 +2167,12 @@ get_load_store_type (vec_info  *vinfo, stmt_vec_info 
> stmt_info,
>      }
>    else if (STMT_VINFO_GATHER_SCATTER_P (stmt_info))
>      {
> +      slp_node->gs_offset_uniform_p
> +     = STMT_VINFO_GATHER_UNIFORM_P (stmt_info);
> +      if (dump_enabled_p ())
> +     dump_printf_loc (MSG_NOTE, vect_location,
> +                      "gs_offset_uniform_p is set to: %d \n",
> +                      STMT_VINFO_GATHER_UNIFORM_P (stmt_info));
>        slp_tree offset_node = SLP_TREE_CHILDREN (slp_node)[0];
>        tree offset_vectype = SLP_TREE_VECTYPE (offset_node);
>        int scale = SLP_TREE_GS_SCALE (slp_node);
> @@ -2474,6 +2480,8 @@ get_load_store_type (vec_info  *vinfo, stmt_vec_info 
> stmt_info,
>  
>         SLP_TREE_GS_SCALE (slp_node) = gs_info.scale;
>         SLP_TREE_GS_BASE (slp_node) = error_mark_node;
> +       SLP_TREE_GS_OFFSET_UNIFORM_P (slp_node)
> +         = STMT_VINFO_GATHER_UNIFORM_P (stmt_info);
>         ls->gs.ifn = gs_info.ifn;
>         ls->strided_offset_vectype = gs_info.offset_vectype;
>         *memory_access_type = VMAT_GATHER_SCATTER_IFN;
> @@ -2488,6 +2496,8 @@ get_load_store_type (vec_info  *vinfo, stmt_vec_info 
> stmt_info,
>       {
>         SLP_TREE_GS_SCALE (slp_node) = gs_info.scale;
>         SLP_TREE_GS_BASE (slp_node) = error_mark_node;
> +       SLP_TREE_GS_OFFSET_UNIFORM_P (slp_node)
> +         = STMT_VINFO_GATHER_UNIFORM_P (stmt_info);
>         grouped_gather_fallback = *memory_access_type;
>         *memory_access_type = VMAT_GATHER_SCATTER_IFN;
>         ls->gs.ifn = gs_info.ifn;
> diff --git a/gcc/tree-vectorizer.cc b/gcc/tree-vectorizer.cc
> index 205a07b0be5..d4c915ddd8b 100644
> --- a/gcc/tree-vectorizer.cc
> +++ b/gcc/tree-vectorizer.cc
> @@ -483,7 +483,9 @@ vec_info::~vec_info ()
>  vec_info_shared::vec_info_shared ()
>    : datarefs (vNULL),
>      datarefs_copy (vNULL),
> -    ddrs (vNULL)
> +    ddrs (vNULL),
> +    max_autovectorize_vf (0)
> +
>  {
>  }
>  
> @@ -718,6 +720,7 @@ vec_info::new_stmt_vec_info (gimple *stmt)
>  
>    STMT_VINFO_RELEVANT (res) = vect_unused_in_scope;
>    STMT_VINFO_VECTORIZABLE (res) = true;
> +  STMT_VINFO_GATHER_UNIFORM_P (res) = false;
>    STMT_VINFO_REDUC_TYPE (res) = TREE_CODE_REDUCTION;
>    STMT_VINFO_REDUC_CODE (res) = ERROR_MARK;
>    STMT_VINFO_REDUC_IDX (res) = -1;
> diff --git a/gcc/tree-vectorizer.h b/gcc/tree-vectorizer.h
> index 3a01e1be0f1..b2a55f21421 100644
> --- a/gcc/tree-vectorizer.h
> +++ b/gcc/tree-vectorizer.h
> @@ -369,6 +369,8 @@ struct _slp_tree {
>    /* For gather/scatter memory operations the scale each offset element
>       should be multiplied by before being added to the base.  */
>    int gs_scale;
> +  /* For gather/scatter, when the offset is uniform across VF iterations.  */
> +  bool gs_offset_uniform_p;
>    /* For gather/scatter memory operations the loop-invariant base value.  */
>    tree gs_base;
>    /* Whether uses of this load or feeders of this store are suitable
> @@ -474,6 +476,7 @@ public:
>  #define SLP_TREE_TYPE(S)                      (S)->type
>  #define SLP_TREE_GS_SCALE(S)                  (S)->gs_scale
>  #define SLP_TREE_GS_BASE(S)                   (S)->gs_base
> +#define SLP_TREE_GS_OFFSET_UNIFORM_P(S)   (S)->gs_offset_uniform_p
>  #define SLP_TREE_REDUC_IDX(S)                         
> (S)->cycle_info.reduc_idx
>  #define SLP_TREE_PERMUTE_P(S)                         ((S)->code == 
> VEC_PERM_EXPR)
>  
> @@ -612,6 +615,10 @@ public:
>    /* All data dependences.  Freed by free_dependence_relations, so not
>       an auto_vec.  */
>    vec<ddr_p> ddrs;
> +
> +  /* Maximum VF over all autovectorize vector modes for the loop.  */
> +  unsigned HOST_WIDE_INT max_autovectorize_vf;
> +
>  };
>  
>  /* Vectorizer state common between loop and basic-block vectorization.  */
> @@ -1117,6 +1124,9 @@ public:
>       rhs of the store of the initializer.  */
>    hash_map<tree, tree> *scan_map;
>  
> +  /* Cache for uniformity in gather/scatter to avoid recomputation.  */
> +  hash_map<tree_operand_hash, bool> offset_uniformity_cache;
> +
>    /* The factor used to over weight those statements in an inner loop
>       relative to the loop being vectorized.  */
>    unsigned int inner_loop_cost_factor;
> @@ -1595,6 +1605,9 @@ public:
>    /* For loads if this is a gather, for stores if this is a scatter.  */
>    bool gather_scatter_p;
>  
> +  /* For loads if this is a uniform broadcast.  */
> +  bool gather_offset_uniform_p;
> +
>    /* True if this is an access with loop-invariant stride.  */
>    bool strided_p;
>  
> @@ -1685,6 +1698,7 @@ struct gather_scatter_info {
>  
>    /* The type of the scalar elements being loaded or stored.  */
>    tree memory_type;
> +
>  };
>  
>  /* Access Functions.  */
> @@ -1695,6 +1709,7 @@ struct gather_scatter_info {
>  #define STMT_VINFO_VECTORIZABLE(S)         (S)->vectorizable
>  #define STMT_VINFO_DATA_REF(S)             ((S)->dr_aux.dr + 0)
>  #define STMT_VINFO_GATHER_SCATTER_P(S)          (S)->gather_scatter_p
> +#define STMT_VINFO_GATHER_UNIFORM_P(S)     (S)->gather_offset_uniform_p
>  #define STMT_VINFO_STRIDED_P(S)                 (S)->strided_p
>  #define STMT_VINFO_SIMD_LANE_ACCESS_P(S)   (S)->simd_lane_access_p
>  #define STMT_VINFO_REDUC_IDX(S)                 (S)->reduc_idx
> 

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

Reply via email to