match.pd: sink a unary operation through a vector permute
Checks
| Context |
Check |
Description |
| linaro-tcwg-bot/tcwg_simplebootstrap_build--master-arm-bootstrap |
success
|
Build passed
|
| linaro-tcwg-bot/tcwg_simplebootstrap_build--master-aarch64-bootstrap |
success
|
Build passed
|
| linaro-tcwg-bot/tcwg_gcc_build--master-aarch64 |
success
|
Build passed
|
Commit Message
From: Kyrylo Tkachov <ktkachov@nvidia.com>
A permute of two results of the same unary operation needs only one such
operation, applied to the permuted vector. The selector and both vector
types are unchanged, so the permute itself costs the same, and the lanes
that the permute drops are no longer computed at all.
typedef float v4f __attribute__((vector_size (16)));
typedef int v4i __attribute__((vector_size (16)));
v4f f (v4f a, v4f b)
{
v4i m = { 3, 6, 1, 4 };
return __builtin_shuffle (-a, -b, m);
}
aarch64 -O2 before:
fneg v0.4s, v0.4s
adrp x0, .LANCHOR0
fneg v1.4s, v1.4s
ldr q29, [x0, #:lo12:.LANCHOR0]
mov v30.16b, v0.16b
mov v31.16b, v1.16b
tbl v0.16b, {v30.16b - v31.16b}, v29.16b
after:
adrp x0, .LANCHOR0
ldr q31, [x0, #:lo12:.LANCHOR0]
tbl v0.16b, {v0.16b - v1.16b}, v31.16b
fneg v0.4s, v0.4s
The two register-pair moves also go away, because the permute can now
take the incoming argument registers directly.
Bootstrapped and tested on aarch64-none-linux-gnu.
Ok for trunk?
Thanks,
Kyrill
gcc/ChangeLog:
* match.pd (vec_perm of two identical unary operations): New
simplification sinking the operation through the permute.
gcc/testsuite/ChangeLog:
* gcc.dg/tree-ssa/vec-perm-unary-1.c: New test.
Signed-off-by: Kyrylo Tkachov <ktkachov@nvidia.com>
---
gcc/match.pd | 9 ++++
.../gcc.dg/tree-ssa/vec-perm-unary-1.c | 44 +++++++++++++++++++
2 files changed, 53 insertions(+)
create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vec-perm-unary-1.c
Comments
On Tue, Aug 4, 2026 at 2:50 AM <ktkachov@nvidia.com> wrote:
>
> From: Kyrylo Tkachov <ktkachov@nvidia.com>
>
> A permute of two results of the same unary operation needs only one such
> operation, applied to the permuted vector. The selector and both vector
> types are unchanged, so the permute itself costs the same, and the lanes
> that the permute drops are no longer computed at all.
>
> typedef float v4f __attribute__((vector_size (16)));
> typedef int v4i __attribute__((vector_size (16)));
> v4f f (v4f a, v4f b)
> {
> v4i m = { 3, 6, 1, 4 };
> return __builtin_shuffle (-a, -b, m);
> }
>
> aarch64 -O2 before:
>
> fneg v0.4s, v0.4s
> adrp x0, .LANCHOR0
> fneg v1.4s, v1.4s
> ldr q29, [x0, #:lo12:.LANCHOR0]
> mov v30.16b, v0.16b
> mov v31.16b, v1.16b
> tbl v0.16b, {v30.16b - v31.16b}, v29.16b
>
> after:
>
> adrp x0, .LANCHOR0
> ldr q31, [x0, #:lo12:.LANCHOR0]
> tbl v0.16b, {v0.16b - v1.16b}, v31.16b
> fneg v0.4s, v0.4s
>
> The two register-pair moves also go away, because the permute can now
> take the incoming argument registers directly.
>
> Bootstrapped and tested on aarch64-none-linux-gnu.
> Ok for trunk?
> Thanks,
> Kyrill
>
> gcc/ChangeLog:
>
> * match.pd (vec_perm of two identical unary operations): New
> simplification sinking the operation through the permute.
>
> gcc/testsuite/ChangeLog:
>
> * gcc.dg/tree-ssa/vec-perm-unary-1.c: New test.
>
> Signed-off-by: Kyrylo Tkachov <ktkachov@nvidia.com>
> ---
> gcc/match.pd | 9 ++++
> .../gcc.dg/tree-ssa/vec-perm-unary-1.c | 44 +++++++++++++++++++
> 2 files changed, 53 insertions(+)
> create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/vec-perm-unary-1.c
>
> diff --git a/gcc/match.pd b/gcc/match.pd
> index ec00347a968..4fca75d6fb6 100644
> --- a/gcc/match.pd
> +++ b/gcc/match.pd
> @@ -12532,6 +12532,15 @@ and,
> && !TYPE_OVERFLOW_SANITIZED (TREE_TYPE (@0)))
> (convert @0)))
>
> +/* VEC_PERM_EXPR of two results of the same unary operation needs only one
> + such operation, applied to the permuted vector. The selector and both
> + vector types are unchanged, so the permute itself costs the same, and
> + the lanes that the permute drops are no longer computed. */
> +(for uop (negate bit_not abs absu)
> + (simplify
> + (vec_perm (uop:s @0) (uop:s @1) @2)
> + (uop (vec_perm @0 @1 @2))))
Hmm, abs and negate might introduce new undefined behavior here for
the signed integer case.
(abs and negate is fine for fp since they don't trap).
So this needs more handling for that case. See the fp handling.
Note I think the already existing pattern:
```
(for op (plus minus mult bit_and bit_ior bit_xor
lshift rshift)
(simplify
(op (vec_perm @0 @0 @2) (vec_perm @1 @1 @2))
(if (VECTOR_INTEGER_TYPE_P (type))
(vec_perm (op@3 @0 @1) @3 @2))))
```
Has the same issue of maybe introducing undefined behavior.
This means we should fix that too.
> +
> /* Optimize
> c1 = VEC_PERM_EXPR (a, a, mask)
> c2 = VEC_PERM_EXPR (b, b, mask)
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/vec-perm-unary-1.c b/gcc/testsuite/gcc.dg/tree-ssa/vec-perm-unary-1.c
> new file mode 100644
> index 00000000000..7ff2498a6ce
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/vec-perm-unary-1.c
> @@ -0,0 +1,44 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -fdump-tree-optimized" } */
> +
> +/* A permute of two results of the same unary operation needs only one such
> + operation, applied to the permuted vector. */
> +
> +typedef float v4f __attribute__((vector_size (16)));
> +typedef int v4i __attribute__((vector_size (16)));
> +
> +v4f
> +f (v4f a, v4f b)
> +{
> + v4i m = { 3, 6, 1, 4 };
> + return __builtin_shuffle (-a, -b, m);
> +}
> +
> +v4i
> +g (v4i a, v4i b)
> +{
> + v4i m = { 0, 4, 1, 5 };
> + return __builtin_shuffle (~a, ~b, m);
> +}
> +
> +v4i
> +h (v4i a, v4i b)
> +{
> + v4i m = { 0, 4, 1, 5 };
> + return __builtin_shuffle (-a, -b, m);
> +}
> +
> +/* The absolute value written as a sign mask folds to ABS_EXPR first, so
> + this exercises the abs case of the same rule. */
> +v4i
> +k (v4i a, v4i b)
> +{
> + v4i m = { 0, 4, 1, 5 };
> + v4i sa = a >> 31, sb = b >> 31;
> + return __builtin_shuffle ((a ^ sa) - sa, (b ^ sb) - sb, m);
> +}
> +
> +/* { dg-final { scan-tree-dump-times "VEC_PERM_EXPR" 4 "optimized" } } */
> +/* { dg-final { scan-tree-dump-times "ABS_EXPR" 1 "optimized" } } */
> +/* { dg-final { scan-tree-dump-not " = -a" "optimized" } } */
> +/* { dg-final { scan-tree-dump-not " = ~a" "optimized" } } */
> --
> 2.50.1 (Apple Git-155)
>
@@ -12532,6 +12532,15 @@ and,
&& !TYPE_OVERFLOW_SANITIZED (TREE_TYPE (@0)))
(convert @0)))
+/* VEC_PERM_EXPR of two results of the same unary operation needs only one
+ such operation, applied to the permuted vector. The selector and both
+ vector types are unchanged, so the permute itself costs the same, and
+ the lanes that the permute drops are no longer computed. */
+(for uop (negate bit_not abs absu)
+ (simplify
+ (vec_perm (uop:s @0) (uop:s @1) @2)
+ (uop (vec_perm @0 @1 @2))))
+
/* Optimize
c1 = VEC_PERM_EXPR (a, a, mask)
c2 = VEC_PERM_EXPR (b, b, mask)
new file mode 100644
@@ -0,0 +1,44 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-optimized" } */
+
+/* A permute of two results of the same unary operation needs only one such
+ operation, applied to the permuted vector. */
+
+typedef float v4f __attribute__((vector_size (16)));
+typedef int v4i __attribute__((vector_size (16)));
+
+v4f
+f (v4f a, v4f b)
+{
+ v4i m = { 3, 6, 1, 4 };
+ return __builtin_shuffle (-a, -b, m);
+}
+
+v4i
+g (v4i a, v4i b)
+{
+ v4i m = { 0, 4, 1, 5 };
+ return __builtin_shuffle (~a, ~b, m);
+}
+
+v4i
+h (v4i a, v4i b)
+{
+ v4i m = { 0, 4, 1, 5 };
+ return __builtin_shuffle (-a, -b, m);
+}
+
+/* The absolute value written as a sign mask folds to ABS_EXPR first, so
+ this exercises the abs case of the same rule. */
+v4i
+k (v4i a, v4i b)
+{
+ v4i m = { 0, 4, 1, 5 };
+ v4i sa = a >> 31, sb = b >> 31;
+ return __builtin_shuffle ((a ^ sa) - sa, (b ^ sb) - sb, m);
+}
+
+/* { dg-final { scan-tree-dump-times "VEC_PERM_EXPR" 4 "optimized" } } */
+/* { dg-final { scan-tree-dump-times "ABS_EXPR" 1 "optimized" } } */
+/* { dg-final { scan-tree-dump-not " = -a" "optimized" } } */
+/* { dg-final { scan-tree-dump-not " = ~a" "optimized" } } */