On Tue, 7 Jul 2026, Pengfei Li wrote:

> When shifting a vector by one element and then inserting a new element
> into the vacated lane, GIMPLE can contain a VEC_PERM_EXPR followed by a
> BIT_INSERT_EXPR. For example, the testcase in this patch has:
> 
>       _1 = VEC_PERM_EXPR <v_2(D), { 0, 0, 0, 0 }, { 1, 2, 3, 4 }>;
>       shifted_4 = BIT_INSERT_EXPR <_1, val_3(D), 96 (32 bits)>;
> 
> On AArch64, GCC currently generates:
> 
>       movi    v31.4s, 0
>       ext     v0.16b, v0.16b, v31.16b, #4
>       ins     v0.s[3], w0
> 
> Here the zero vector is only used to fill the lane that is immediately
> overwritten by the insertion.
> 
> This patch adds a match.pd pattern to detect when all live lanes of the
> VEC_PERM_EXPR result come from a single source operand, and replaces the
> other operand in that case. This helps avoid materializing a zero filler
> vector. After this patch, GCC will generate:

I think you need to guard this transform against the case the
target can do the permutation with a whole-vector shift but
not with a vec_perm_const.  You want to use the new
tree_to_vec_perm_indices to detect the zero vector and be
able to have can_vec_perm_const_p to assess whether a shift can be
used.

>       ext     v0.16b, v0.16b, v0.16b, #4
>       ins     v0.s[3], w0
> 
> Bootstrapped and tested on aarch64-linux-gnu and x86_64-linux-gnu.
> 
> gcc/ChangeLog:
> 
>       * match.pd: Simplify vector permute and insert patterns.
> 
> gcc/testsuite/ChangeLog:
> 
>       * gcc.dg/fold-vecperm-insert-1.c: New test.
> ---
>  gcc/match.pd                                 | 55 ++++++++++++++++++++
>  gcc/testsuite/gcc.dg/fold-vecperm-insert-1.c | 19 +++++++
>  2 files changed, 74 insertions(+)
>  create mode 100644 gcc/testsuite/gcc.dg/fold-vecperm-insert-1.c
> 
> diff --git a/gcc/match.pd b/gcc/match.pd
> index a7cec25dbad..ca6b63a3395 100644
> --- a/gcc/match.pd
> +++ b/gcc/match.pd
> @@ -12275,6 +12275,61 @@ and,
>    (if (can_fold)
>      (view_convert (vec_perm @0 @1 @2)))))
>  
> +/* Simplify
> +     v = VEC_PERM_EXPR <op0, op1, sel>;
> +     res = BIT_INSERT_EXPR <v, new_elt, bit_pos>;
> +   by replacing either op0 or op1 with the other one when
> +     1) BIT_INSERT_EXPR inserts exactly one full lane of v.
> +     2) All other lanes come from a single source, either op0 or op1.
> +
> +   The vector operand that contributes only to the overwritten lane is dead.
> +   Replacing it avoids materializing a filler vector.  */
> +
> +(simplify
> + (bit_insert (vec_perm @0 @1 VECTOR_CST@2) @3 INTEGER_CST@4)
> + (with
> +  {
> +    bool from0 = false;
> +    bool from1 = false;
> +    tree single_src = NULL_TREE;
> +    unsigned elt_size = vector_element_bits (type);
> +
> +    unsigned HOST_WIDE_INT nelts;
> +    vec_perm_builder builder;
> +    unsigned ins_lane_idx;
> +
> +    /* Require fixed-length vectors, different operands to VEC_PERM_EXPR and
> +       BIT_INSERT_EXPR to insert exactly one full lane.  */
> +    if (TYPE_VECTOR_SUBPARTS (type).is_constant (&nelts)
> +     && @0 != @1
> +     && tree_to_vec_perm_builder (&builder, @2)
> +     && tree_to_uhwi (TYPE_SIZE (TREE_TYPE (@3))) == elt_size
> +     && multiple_p (tree_to_uhwi (@4), elt_size, &ins_lane_idx))
> +      {
> +     vec_perm_indices sel (builder, 2, nelts);
> +     for (unsigned i = 0; i < nelts; i++)
> +       {
> +         /* Skip the overwritten lane.  */
> +         if (i == ins_lane_idx)
> +           continue;
> +
> +         /* Set FROM0 / FROM1 if current lane comes from @0 / @1.  */
> +         unsigned HOST_WIDE_INT elt = sel[i].to_constant ();
> +         if (elt < nelts)
> +           from0 = true;
> +         else
> +           from1 = true;
> +       }
> +
> +     /* If only one of FROM0 and FROM1 is true, all live lanes come from
> +        a single source.  */
> +     if (from0 ^ from1)
> +       single_src = from0 ? @0 : @1;
> +      }
> +  }
> +  (if (single_src != NULL_TREE)
> +    (bit_insert (vec_perm { single_src; } { single_src; } @2) @3 @4))))
> +
>  #if GIMPLE
>  /* Simplify (a >> 1) + (b >> 1) + ((a | b) & 1) to .AVG_CEIL (a, b).
>     Similar for (a | b) - ((a ^ b) >> 1).  */
> diff --git a/gcc/testsuite/gcc.dg/fold-vecperm-insert-1.c 
> b/gcc/testsuite/gcc.dg/fold-vecperm-insert-1.c
> new file mode 100644
> index 00000000000..121f9999d89
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/fold-vecperm-insert-1.c
> @@ -0,0 +1,19 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -fdump-tree-forwprop3" } */
> +
> +typedef int __attribute__((vector_size(16))) v4si;
> +
> +/* Shift vector v by one element and insert the value val.  The vector shift
> +   typically requires a zero vector operand in VEC_PERM_EXPR, but it can be
> +   optimized away in this case.  */
> +
> +v4si shift_and_insert (v4si v, int val)
> +{
> +  v4si zero = { 0, 0, 0, 0 };
> +  v4si sel = { 1, 2, 3, 4 };
> +  v4si shifted = __builtin_shuffle (v, zero, sel);
> +  shifted[3] = val;
> +  return shifted;
> +}
> +
> +/* { dg-final { scan-tree-dump-times "VEC_PERM_EXPR.*v_\[0-9\]+.*v_\[0-9\]+" 
> 1 "forwprop3" } } */
> 

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

Reply via email to