match.pd: Eliminate dead operand in vector permute and insert patterns

Message ID 20260707150953.112241-1-Pengfei.Li2@arm.com
State New
Headers
Series match.pd: Eliminate dead operand in vector permute and insert patterns |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gcc_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_gcc_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_simplebootstrap_build--master-arm-bootstrap pending Build did not complete; will be retriggered
linaro-tcwg-bot/tcwg_gcc_check--master-arm success Test passed
linaro-tcwg-bot/tcwg_simplebootstrap_build--master-aarch64-bootstrap pending Build did not complete; will be retriggered

Commit Message

Pengfei Li July 7, 2026, 3:09 p.m. UTC
  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:

	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
  

Comments

Tamar Christina July 7, 2026, 3:28 p.m. UTC | #1
> -----Original Message-----
> From: Pengfei Li <Pengfei.Li2@arm.com>
> Sent: 07 July 2026 16:10
> To: gcc-patches@gcc.gnu.org
> Cc: rguenther@suse.de; andrew.pinski@oss.qualcomm.com;
> jakub@redhat.com; Tamar Christina <Tamar.Christina@arm.com>; Pengfei Li
> <Pengfei.Li2@arm.com>
> Subject: [PATCH] match.pd: Eliminate dead operand in vector permute and
> insert patterns
> 
> 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:
> 
> 	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

I don't think the bit_insert should be part of the match.  The operation should just be
to simplify VEC_PERM_EXPR that use only 1 vector to that one vector.  Since e.g.
https://godbolt.org/z/GTb3vYT6d is also a missed optimization. 

> +  {
> +    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))))
> +

Before you do the rewrite you have to check with the target if it can do it.
Some targets are deficient in not being able to do a permute with just 1 src
vector but can with 2.

I think you want to extend the existing patterns in

(simplify
 (vec_perm @0 @1 VECTOR_CST@2)
 (with
  {
    tree op0 = @0, op1 = @1, op2 = @2;
    machine_mode result_mode = TYPE_MODE (type);
    machine_mode op_mode = TYPE_MODE (TREE_TYPE (op0));

here, which already tries to do similar optimizations and already has code to detect
whether only one vector is needed.

Thanks,
Tamar

>  #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" } } */
> --
> 2.43.0
  
Pengfei Li July 7, 2026, 4:22 p.m. UTC | #2
Hi Tamar,

On 07/07/2026 16:28, Tamar Christina wrote:
>> -----Original Message-----
>> From: Pengfei Li <Pengfei.Li2@arm.com>
>> Sent: 07 July 2026 16:10
>> To: gcc-patches@gcc.gnu.org
>> Cc: rguenther@suse.de; andrew.pinski@oss.qualcomm.com;
>> jakub@redhat.com; Tamar Christina <Tamar.Christina@arm.com>; Pengfei Li
>> <Pengfei.Li2@arm.com>
>> Subject: [PATCH] match.pd: Eliminate dead operand in vector permute and
>> insert patterns
>>
>> 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:
>>
>> 	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
> I don't think the bit_insert should be part of the match.  The operation should just be
> to simplify VEC_PERM_EXPR that use only 1 vector to that one vector.  Since e.g.
> https://godbolt.org/z/GTb3vYT6d is also a missed optimization.
I may be missing something. But this case isn't a VEC_PERM_EXPR that 
uses only 1 vector. Without the following lane overwrite by 
BIT_INSERT_EXPR, the two permutes are not equivalent.

__builtin_shuffle (v, zero, { 1, 2, 3, 4 } ) -> { v[1], v[2], v[3], 
zero[0] }

__builtin_shuffle (v, v, { 1, 2, 3, 4 } ) -> { v[1], v[2], v[3], v[0] }

So I don't see missed optimization in your godbolt. The selector { 1, 2, 
3, 4 } still selects one element from the second operand. It only 
becomes dead because of the following BIT_INSERT_EXPR?

Am I misunderstanding your point?

Thanks,
Pengfei


>> +  {
>> +    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))))
>> +
> Before you do the rewrite you have to check with the target if it can do it.
> Some targets are deficient in not being able to do a permute with just 1 src
> vector but can with 2.
>
> I think you want to extend the existing patterns in
>
> (simplify
>   (vec_perm @0 @1 VECTOR_CST@2)
>   (with
>    {
>      tree op0 = @0, op1 = @1, op2 = @2;
>      machine_mode result_mode = TYPE_MODE (type);
>      machine_mode op_mode = TYPE_MODE (TREE_TYPE (op0));
>
> here, which already tries to do similar optimizations and already has code to detect
> whether only one vector is needed.
>
> Thanks,
> Tamar
>
>>   #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" } } */
>> --
>> 2.43.0
  
Tamar Christina July 7, 2026, 4:28 p.m. UTC | #3
> -----Original Message-----
> From: Pengfei Li <Pengfei.Li2@arm.com>
> Sent: 07 July 2026 17:22
> To: Tamar Christina <Tamar.Christina@arm.com>; gcc-patches@gcc.gnu.org
> Cc: rguenther@suse.de; andrew.pinski@oss.qualcomm.com;
> jakub@redhat.com
> Subject: Re: [PATCH] match.pd: Eliminate dead operand in vector permute and
> insert patterns
> 
> Hi Tamar,
> 
> On 07/07/2026 16:28, Tamar Christina wrote:
> >> -----Original Message-----
> >> From: Pengfei Li <Pengfei.Li2@arm.com>
> >> Sent: 07 July 2026 16:10
> >> To: gcc-patches@gcc.gnu.org
> >> Cc: rguenther@suse.de; andrew.pinski@oss.qualcomm.com;
> >> jakub@redhat.com; Tamar Christina <Tamar.Christina@arm.com>; Pengfei
> Li
> >> <Pengfei.Li2@arm.com>
> >> Subject: [PATCH] match.pd: Eliminate dead operand in vector permute and
> >> insert patterns
> >>
> >> 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:
> >>
> >> 	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
> > I don't think the bit_insert should be part of the match.  The operation
> should just be
> > to simplify VEC_PERM_EXPR that use only 1 vector to that one vector.  Since
> e.g.
> > https://godbolt.org/z/GTb3vYT6d is also a missed optimization.
> I may be missing something. But this case isn't a VEC_PERM_EXPR that
> uses only 1 vector. Without the following lane overwrite by
> BIT_INSERT_EXPR, the two permutes are not equivalent.
> 
> __builtin_shuffle (v, zero, { 1, 2, 3, 4 } ) -> { v[1], v[2], v[3],
> zero[0] }
> 
> __builtin_shuffle (v, v, { 1, 2, 3, 4 } ) -> { v[1], v[2], v[3], v[0] }
> 
> So I don't see missed optimization in your godbolt. The selector { 1, 2,
> 3, 4 } still selects one element from the second operand. It only
> becomes dead because of the following BIT_INSERT_EXPR?
> 
> Am I misunderstanding your point?

Ah you're right. I forgot that the indexes to the shuffle are zero based.

Sorry for the noise!

Tamar

> 
> Thanks,
> Pengfei
> 
> 
> >> +  {
> >> +    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))))
> >> +
> > Before you do the rewrite you have to check with the target if it can do it.
> > Some targets are deficient in not being able to do a permute with just 1 src
> > vector but can with 2.
> >
> > I think you want to extend the existing patterns in
> >
> > (simplify
> >   (vec_perm @0 @1 VECTOR_CST@2)
> >   (with
> >    {
> >      tree op0 = @0, op1 = @1, op2 = @2;
> >      machine_mode result_mode = TYPE_MODE (type);
> >      machine_mode op_mode = TYPE_MODE (TREE_TYPE (op0));
> >
> > here, which already tries to do similar optimizations and already has code to
> detect
> > whether only one vector is needed.
> >
> > Thanks,
> > Tamar
> >
> >>   #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" } } */
> >> --
> >> 2.43.0
  
Richard Biener July 8, 2026, 8:29 a.m. UTC | #4
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" } } */
>
  

Patch

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" } } */