Since GCC 14 a VEC_PERM_EXPR with a constant selector may have inputs
with a different number of elements than its result. The match.pd
patterns that merge a VEC_PERM_EXPR into a VEC_PERM_EXPR using it, and
forwprop's identity check for two consecutive permutes, still assume
that every permute and input involved has the same number of elements.
On aarch64 this happens for plain SVE intrinsics: svld1rq folds to a
permute from a 128-bit vector to the SVE vector, and with
-msve-vector-bits=256 a following svrev, svzip1, svuzp1 or svtrn2 of it
is merged into an invalid permute. That fails GIMPLE verification, and
without checking it is miscompiled or ICEs in expand_vec_perm_const.
- Merging c = <a, b> into d = <c, c> builds the new selector with d's
element count as the number of elements per input, and emits the new
permute with a's type but d's selector length. That is invalid
GIMPLE: the verifier rejects it, and without checking it is
miscompiled or ICEs during expansion.
- Merging c = <a, b> into d = <x, c> or <c, x> builds the new selector
with d's element count as the number of elements per input too, so
when d is narrower than c, lanes of c are taken from x: silent wrong
code. When c and a differ in length, the new permute mixes inputs x
and a of different widths.
- is_combined_permutation_identity folds the outer mask through the
inner one with the inner mask's type and reduces the result by its
length, so mixed widths ICE in fold_vec_perm or wrongly look like an
identity (a V2HI permute of a V2HI permute of two V4HI vectors gets
replaced by one of the V4HI inputs).
Build each selector with the element count of its own inputs. In the
<c, c> merge, emit the new permute as a vector of a's element type with
d's length (view-converted to d's type as before), and when the widths
differ only merge if the target supports the result. In the <x, c>
and <c, x> merges, require a and b to have x's type and query the
inner permute with its own result mode. In forwprop, leave permutes of
different widths to match.pd.
PR middle-end/127862
gcc/ChangeLog:
* match.pd (vec_perm (vec_perm) merges): Handle inputs and results
with different numbers of elements.
* tree-ssa-forwprop.cc (simplify_permutation): Don't check for an
identity when the permutes have different widths.
gcc/testsuite/ChangeLog:
* gcc.dg/torture/vec-perm-merge-wide-input-1.c: New test.
* gcc.dg/torture/vec-perm-merge-wide-input-2.c: New test.
* gcc.target/aarch64/sve/pr127862.c: New test.
* gcc.target/aarch64/sve/pr127862_run.c: New test.
Signed-off-by: Igor Shevlyakov <[email protected]>
---
Bootstrapped and regtested on x86_64-pc-linux-gnu (-m32/-m64), no new
regressions.
gcc/match.pd | 61 ++++--
.../torture/vec-perm-merge-wide-input-1.c | 193 ++++++++++++++++++
.../torture/vec-perm-merge-wide-input-2.c | 64 ++++++
.../gcc.target/aarch64/sve/pr127862.c | 53 +++++
.../gcc.target/aarch64/sve/pr127862_run.c | 46 +++++
gcc/tree-ssa-forwprop.cc | 9 +
6 files changed, 411 insertions(+), 15 deletions(-)
create mode 100644 gcc/testsuite/gcc.dg/torture/vec-perm-merge-wide-input-1.c
create mode 100644 gcc/testsuite/gcc.dg/torture/vec-perm-merge-wide-input-2.c
create mode 100644 gcc/testsuite/gcc.target/aarch64/sve/pr127862.c
create mode 100644 gcc/testsuite/gcc.target/aarch64/sve/pr127862_run.c
diff --git a/gcc/match.pd b/gcc/match.pd
index 6984af44582..11c1c6e6765 100644
--- a/gcc/match.pd
+++ b/gcc/match.pd
@@ -12868,16 +12868,22 @@ and,
c = VEC_PERM_EXPR <a, b, VCST0>;
d = VEC_PERM_EXPR <c, c, VCST1>;
to
- d = VEC_PERM_EXPR <a, b, NEW_VCST>; */
+ d = VEC_PERM_EXPR <a, b, NEW_VCST>;
+ a, b, c and d may differ in their number of elements, so index
+ each selector by the element count of its own inputs. */
(simplify
(vec_perm (view_convert?@0 (vec_perm@1 @2 @3 VECTOR_CST@4)) @0 VECTOR_CST@5)
- (if (TYPE_VECTOR_SUBPARTS (type).is_constant ())
+ (if (TYPE_VECTOR_SUBPARTS (type).is_constant ()
+ && TYPE_VECTOR_SUBPARTS (TREE_TYPE (@0)).is_constant ()
+ && TYPE_VECTOR_SUBPARTS (TREE_TYPE (@2)).is_constant ())
(with
{
machine_mode result_mode = TYPE_MODE (type);
machine_mode op_mode = TYPE_MODE (TREE_TYPE (@2));
int nelts = TYPE_VECTOR_SUBPARTS (type).to_constant ();
+ int c_nelts = TYPE_VECTOR_SUBPARTS (TREE_TYPE (@0)).to_constant ();
+ int op_nelts = TYPE_VECTOR_SUBPARTS (TREE_TYPE (@2)).to_constant ();
vec_perm_indices sel0, sel1;
}
(if (tree_to_vec_perm_indices (&sel0, @2, @3, @4)
@@ -12890,23 +12896,38 @@ and,
for (int i = 0; i < nelts; i++)
builder2.quick_push (sel0[sel1[i].to_constant ()]);
- vec_perm_indices sel2 (builder2, 2, nelts);
+ vec_perm_indices sel2 (builder2, 2, op_nelts);
+
+ /* The new permute selects elements of a and b, so it has their
+ element type; the VIEW_CONVERT_EXPR below gives it d's type. */
+ tree perm_type = TREE_TYPE (@2);
+ if (nelts != op_nelts)
+ perm_type = build_vector_type (TREE_TYPE (perm_type), nelts);
tree op0 = NULL_TREE;
+ if (nelts != op_nelts || c_nelts != op_nelts)
+ {
+ /* Only merge permutes of different widths if the target
+ handles the result. */
+ if (can_vec_perm_const_p (TYPE_MODE (perm_type), op_mode, sel2,
+ false))
+ op0 = vec_perm_indices_to_tree (TREE_TYPE (@5), sel2);
+ }
/* If the new VEC_PERM_EXPR can't be handled but both
original VEC_PERM_EXPRs can, punt.
If one or both of the original VEC_PERM_EXPRs can't be
handled and the new one can't be either, don't increase
number of VEC_PERM_EXPRs that can't be handled. */
- if (can_vec_perm_const_p (result_mode, op_mode, sel2, false)
- || (single_use (@0)
- ? (!can_vec_perm_const_p (result_mode, op_mode, sel0, true)
- || !can_vec_perm_const_p (result_mode, op_mode, sel1, true))
- : !can_vec_perm_const_p (result_mode, op_mode, sel1, true)))
+ else if (can_vec_perm_const_p (result_mode, op_mode, sel2, false)
+ || (single_use (@0)
+ ? (!can_vec_perm_const_p (result_mode, op_mode, sel0, true)
+ || !can_vec_perm_const_p (result_mode, op_mode, sel1,
+ true))
+ : !can_vec_perm_const_p (result_mode, op_mode, sel1, true)))
op0 = vec_perm_indices_to_tree (TREE_TYPE (@5), sel2);
}
(if (op0)
- (view_convert (vec_perm @2 @3 { op0; }))))))))
+ (view_convert (vec_perm:perm_type @2 @3 { op0; }))))))))
/* Merge
c = VEC_PERM_EXPR <a, b, VCST0>;
@@ -12918,11 +12939,16 @@ and,
(simplify
(vec_perm @5 (vec_perm@0 @1 @2 VECTOR_CST@3) VECTOR_CST@4)
- (if (TYPE_VECTOR_SUBPARTS (type).is_constant ())
+ /* The new permute replaces c by a or b, which therefore must have the
+ type of x. c may have more elements than d. */
+ (if (TYPE_VECTOR_SUBPARTS (type).is_constant ()
+ && TYPE_VECTOR_SUBPARTS (TREE_TYPE (@0)).is_constant ()
+ && types_match (@1, @5))
(with
{
machine_mode result_mode = TYPE_MODE (type);
machine_mode op_mode = TYPE_MODE (TREE_TYPE (@1));
+ machine_mode c_mode = TYPE_MODE (TREE_TYPE (@0));
int nelts = TYPE_VECTOR_SUBPARTS (type).to_constant ();
vec_perm_indices sel0, sel1;
}
@@ -12955,7 +12981,7 @@ and,
(if (use_1 ^ use_2)
(with
{
- vec_perm_indices sel2 (builder2, 2, nelts);
+ vec_perm_indices sel2 (builder2, 2, sel1.nelts_per_input ());
tree op0 = NULL_TREE;
/* If the new VEC_PERM_EXPR can't be handled but both
original VEC_PERM_EXPRs can, punt.
@@ -12964,7 +12990,7 @@ and,
number of VEC_PERM_EXPRs that can't be handled. */
if (can_vec_perm_const_p (result_mode, op_mode, sel2, false)
|| (single_use (@0)
- ? (!can_vec_perm_const_p (result_mode, op_mode, sel0, true)
+ ? (!can_vec_perm_const_p (c_mode, op_mode, sel0, true)
|| !can_vec_perm_const_p (result_mode, op_mode, sel1, true))
: !can_vec_perm_const_p (result_mode, op_mode, sel1, true)))
op0 = vec_perm_indices_to_tree (TREE_TYPE (@4), sel2);
@@ -12980,11 +13006,16 @@ and,
(simplify
(vec_perm (vec_perm@0 @1 @2 VECTOR_CST@3) @5 VECTOR_CST@4)
- (if (TYPE_VECTOR_SUBPARTS (type).is_constant ())
+ /* The new permute replaces c by a or b, which therefore must have the
+ type of x. c may have more elements than d. */
+ (if (TYPE_VECTOR_SUBPARTS (type).is_constant ()
+ && TYPE_VECTOR_SUBPARTS (TREE_TYPE (@0)).is_constant ()
+ && types_match (@1, @5))
(with
{
machine_mode result_mode = TYPE_MODE (type);
machine_mode op_mode = TYPE_MODE (TREE_TYPE (@1));
+ machine_mode c_mode = TYPE_MODE (TREE_TYPE (@0));
int nelts = TYPE_VECTOR_SUBPARTS (type).to_constant ();
vec_perm_indices sel0, sel1;
}
@@ -13016,7 +13047,7 @@ and,
(if (use_1 ^ use_2)
(with
{
- vec_perm_indices sel2 (builder2, 2, nelts);
+ vec_perm_indices sel2 (builder2, 2, sel1.nelts_per_input ());
tree op0 = NULL_TREE;
/* If the new VEC_PERM_EXPR can't be handled but both
original VEC_PERM_EXPRs can, punt.
@@ -13025,7 +13056,7 @@ and,
number of VEC_PERM_EXPRs that can't be handled. */
if (can_vec_perm_const_p (result_mode, op_mode, sel2, false)
|| (single_use (@0)
- ? (!can_vec_perm_const_p (result_mode, op_mode, sel0, true)
+ ? (!can_vec_perm_const_p (c_mode, op_mode, sel0, true)
|| !can_vec_perm_const_p (result_mode, op_mode, sel1, true))
: !can_vec_perm_const_p (result_mode, op_mode, sel1, true)))
op0 = vec_perm_indices_to_tree (TREE_TYPE (@4), sel2);
diff --git a/gcc/testsuite/gcc.dg/torture/vec-perm-merge-wide-input-1.c
b/gcc/testsuite/gcc.dg/torture/vec-perm-merge-wide-input-1.c
new file mode 100644
index 00000000000..b047c05bc9a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/vec-perm-merge-wide-input-1.c
@@ -0,0 +1,193 @@
+/* VEC_PERM_EXPR merges with inputs wider than the result. */
+/* { dg-do run } */
+/* { dg-additional-options "-fgimple -Wno-psabi" } */
+
+typedef int v4si __attribute__((vector_size(16)));
+typedef int v8si __attribute__((vector_size(32)));
+typedef int v16si __attribute__((vector_size(64)));
+typedef float v4sf __attribute__((vector_size(16)));
+typedef float v8sf __attribute__((vector_size(32)));
+
+v8si a8, b8, x8;
+v16si a16, b16;
+v4si r4;
+v8si r8;
+v4sf f4;
+
+/* c = <a, b> v8si; d = <c, c> v4si. */
+void __attribute__((noipa)) __GIMPLE() fa1 ()
+{
+ v8si a_; v8si b_; v8si c_; v4si d_;
+ a_ = a8; b_ = b8;
+ c_ = __VEC_PERM (a_, b_, _Literal (v8si) { 8, 0, 9, 1, 10, 2, 11, 3 });
+ d_ = __VEC_PERM (c_, c_, _Literal (v4si) { 1, 2, 3, 4 });
+ r4 = d_;
+}
+
+/* c = <a, b> v8si from v16si; d = <c, c> v8si. */
+void __attribute__((noipa)) __GIMPLE() fa2 ()
+{
+ v16si a_; v16si b_; v8si c_; v8si d_;
+ a_ = a16; b_ = b16;
+ c_ = __VEC_PERM (a_, b_, _Literal (v8si) { 31, 0, 17, 14, 16, 15, 1, 30 });
+ d_ = __VEC_PERM (c_, c_, _Literal (v8si) { 7, 6, 5, 4, 3, 2, 1, 0 });
+ r8 = d_;
+}
+
+/* As fa1, through a VIEW_CONVERT_EXPR. */
+void __attribute__((noipa)) __GIMPLE() fa3 ()
+{
+ v8si a_; v8si b_; v8si c_; v8sf e_; v4sf d_;
+ a_ = a8; b_ = b8;
+ c_ = __VEC_PERM (a_, b_, _Literal (v8si) { 8, 0, 9, 1, 10, 2, 11, 3 });
+ e_ = __VIEW_CONVERT <v8sf> (c_);
+ d_ = __VEC_PERM (e_, e_, _Literal (v4si) { 7, 0, 5, 2 });
+ f4 = d_;
+}
+
+/* c = <a, b> v8si; d = <x, c> v4si; only a used from c. */
+void __attribute__((noipa)) __GIMPLE() fb1 ()
+{
+ v8si a_; v8si b_; v8si x_; v8si c_; v4si d_;
+ a_ = a8; b_ = b8; x_ = x8;
+ c_ = __VEC_PERM (a_, b_, _Literal (v8si) { 7, 6, 5, 4, 8, 9, 10, 11 });
+ d_ = __VEC_PERM (x_, c_, _Literal (v4si) { 0, 9, 7, 11 });
+ r4 = d_;
+}
+
+/* c = <a, b> v8si from v16si; d = <x, c> v8si; only b used from c. */
+void __attribute__((noipa)) __GIMPLE() fb2 ()
+{
+ v16si a_; v16si b_; v8si x_; v8si c_; v8si d_;
+ a_ = a16; b_ = b16; x_ = x8;
+ c_ = __VEC_PERM (a_, b_, _Literal (v8si) { 31, 1, 17, 2, 16, 3, 20, 4 });
+ d_ = __VEC_PERM (x_, c_, _Literal (v8si) { 0, 8, 1, 10, 2, 12, 3, 14 });
+ r8 = d_;
+}
+
+/* c = <a, b> v8si; d = <c, x> v4si; only b used from c. */
+void __attribute__((noipa)) __GIMPLE() fc1 ()
+{
+ v8si a_; v8si b_; v8si x_; v8si c_; v4si d_;
+ a_ = a8; b_ = b8; x_ = x8;
+ c_ = __VEC_PERM (a_, b_, _Literal (v8si) { 0, 1, 2, 3, 15, 14, 13, 12 });
+ d_ = __VEC_PERM (c_, x_, _Literal (v4si) { 4, 9, 6, 15 });
+ r4 = d_;
+}
+
+/* c = <a, b> v8si from v16si; d = <c, x> v8si; only a used from c. */
+void __attribute__((noipa)) __GIMPLE() fc2 ()
+{
+ v16si a_; v16si b_; v8si x_; v8si c_; v8si d_;
+ a_ = a16; b_ = b16; x_ = x8;
+ c_ = __VEC_PERM (a_, b_, _Literal (v8si) { 15, 0, 13, 2, 11, 4, 9, 6 });
+ d_ = __VEC_PERM (c_, x_, _Literal (v8si) { 0, 8, 2, 9, 4, 10, 6, 11 });
+ r8 = d_;
+}
+
+/* Reference VEC_PERM_EXPR: OUT[i] = concat (X, Y)[SEL[i] % (2 * NX)]. */
+static void
+perm (int *out, const int *x, const int *y, int nx, const int *sel, int n)
+{
+ int tmp[16];
+ for (int i = 0; i < n; i++)
+ {
+ int j = sel[i] % (2 * nx);
+ tmp[i] = j < nx ? x[j] : y[j - nx];
+ }
+ for (int i = 0; i < n; i++)
+ out[i] = tmp[i];
+}
+
+static int fails;
+
+static void
+check (const char *name, const int *got, const int *want, int n)
+{
+ for (int i = 0; i < n; i++)
+ if (got[i] != want[i])
+ {
+ __builtin_printf ("%s: lane %d got %d want %d\n", name, i, got[i],
+ want[i]);
+ fails++;
+ return;
+ }
+}
+
+int
+main ()
+{
+ int c[8], d[8];
+ for (int i = 0; i < 8; i++)
+ {
+ a8[i] = i + 1;
+ b8[i] = i + 101;
+ x8[i] = i + 201;
+ }
+ for (int i = 0; i < 16; i++)
+ {
+ a16[i] = i + 301;
+ b16[i] = i + 401;
+ }
+
+ {
+ static const int s0[8] = { 8, 0, 9, 1, 10, 2, 11, 3 };
+ static const int s1[4] = { 1, 2, 3, 4 };
+ perm (c, (int *) &a8, (int *) &b8, 8, s0, 8);
+ perm (d, c, c, 8, s1, 4);
+ fa1 ();
+ check ("fa1", (int *) &r4, d, 4);
+ }
+ {
+ static const int s0[8] = { 31, 0, 17, 14, 16, 15, 1, 30 };
+ static const int s1[8] = { 7, 6, 5, 4, 3, 2, 1, 0 };
+ perm (c, (int *) &a16, (int *) &b16, 16, s0, 8);
+ perm (d, c, c, 8, s1, 8);
+ fa2 ();
+ check ("fa2", (int *) &r8, d, 8);
+ }
+ {
+ static const int s0[8] = { 8, 0, 9, 1, 10, 2, 11, 3 };
+ static const int s1[4] = { 7, 0, 5, 2 };
+ perm (c, (int *) &a8, (int *) &b8, 8, s0, 8);
+ perm (d, c, c, 8, s1, 4);
+ fa3 ();
+ check ("fa3", (int *) &f4, d, 4);
+ }
+ {
+ static const int s0[8] = { 7, 6, 5, 4, 8, 9, 10, 11 };
+ static const int s1[4] = { 0, 9, 7, 11 };
+ perm (c, (int *) &a8, (int *) &b8, 8, s0, 8);
+ perm (d, (int *) &x8, c, 8, s1, 4);
+ fb1 ();
+ check ("fb1", (int *) &r4, d, 4);
+ }
+ {
+ static const int s0[8] = { 31, 1, 17, 2, 16, 3, 20, 4 };
+ static const int s1[8] = { 0, 8, 1, 10, 2, 12, 3, 14 };
+ perm (c, (int *) &a16, (int *) &b16, 16, s0, 8);
+ perm (d, (int *) &x8, c, 8, s1, 8);
+ fb2 ();
+ check ("fb2", (int *) &r8, d, 8);
+ }
+ {
+ static const int s0[8] = { 0, 1, 2, 3, 15, 14, 13, 12 };
+ static const int s1[4] = { 4, 9, 6, 15 };
+ perm (c, (int *) &a8, (int *) &b8, 8, s0, 8);
+ perm (d, c, (int *) &x8, 8, s1, 4);
+ fc1 ();
+ check ("fc1", (int *) &r4, d, 4);
+ }
+ {
+ static const int s0[8] = { 15, 0, 13, 2, 11, 4, 9, 6 };
+ static const int s1[8] = { 0, 8, 2, 9, 4, 10, 6, 11 };
+ perm (c, (int *) &a16, (int *) &b16, 16, s0, 8);
+ perm (d, c, (int *) &x8, 8, s1, 8);
+ fc2 ();
+ check ("fc2", (int *) &r8, d, 8);
+ }
+
+ if (fails)
+ __builtin_abort ();
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/torture/vec-perm-merge-wide-input-2.c
b/gcc/testsuite/gcc.dg/torture/vec-perm-merge-wide-input-2.c
new file mode 100644
index 00000000000..6df62d3347d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/vec-perm-merge-wide-input-2.c
@@ -0,0 +1,64 @@
+/* forwprop must not treat permutes of different widths as an identity. */
+/* { dg-do run } */
+/* { dg-additional-options "-fgimple -Wno-psabi" } */
+
+typedef float v8sf __attribute__((vector_size(32)));
+typedef float v4sf __attribute__((vector_size(16)));
+typedef float v2sf __attribute__((vector_size(8)));
+typedef int v4si __attribute__((vector_size(16)));
+typedef int v2si __attribute__((vector_size(8)));
+typedef short v4hi __attribute__((vector_size(8)));
+typedef short v2hi __attribute__((vector_size(4)));
+
+v8sf fa, fb;
+v2sf fr;
+v4hi ha, hb;
+v2hi hr;
+
+/* The composed mask { 3, 4 } has fewer elements than the inner one. */
+void __attribute__((noipa)) __GIMPLE() f1 ()
+{
+ v8sf a_; v8sf b_; v4sf c_; v2sf d_;
+ a_ = fa; b_ = fb;
+ c_ = __VEC_PERM (a_, b_, _Literal (v4si) { 4, 6, 12, 3 });
+ d_ = __VEC_PERM (c_, c_, _Literal (v2si) { 6, 0 });
+ fr = d_;
+}
+
+/* { 1, 0 } of { 7, 6 } looks like an identity on b if the indices are
+ reduced by the length of c rather than of b. */
+void __attribute__((noipa)) __GIMPLE() f2 ()
+{
+ v4hi a_; v4hi b_; v2hi c_; v2hi d_;
+ a_ = ha; b_ = hb;
+ c_ = __VEC_PERM (a_, b_, _Literal (v2hi) { 7, 6 });
+ d_ = __VEC_PERM (c_, c_, _Literal (v2hi) { 1, 2 });
+ hr = d_;
+}
+
+int
+main ()
+{
+ for (int i = 0; i < 8; i++)
+ {
+ fa[i] = i + 1;
+ fb[i] = i + 101;
+ }
+ for (int i = 0; i < 4; i++)
+ {
+ ha[i] = i + 1;
+ hb[i] = i + 101;
+ }
+
+ f1 ();
+ /* c = { a4, a6, b4, a3 }, d = { c2, c0 }. */
+ if (fr[0] != 105 || fr[1] != 5)
+ __builtin_abort ();
+
+ f2 ();
+ /* c = { b3, b2 }, d = { c1, c0 }. */
+ if (hr[0] != 103 || hr[1] != 104)
+ __builtin_abort ();
+
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.target/aarch64/sve/pr127862.c
b/gcc/testsuite/gcc.target/aarch64/sve/pr127862.c
new file mode 100644
index 00000000000..4b088391fa7
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/sve/pr127862.c
@@ -0,0 +1,53 @@
+/* VEC_PERM_EXPR merges with a result wider than the inputs. */
+/* { dg-do compile } */
+/* { dg-options "-O2 -msve-vector-bits=256" } */
+
+#include <arm_sve.h>
+
+/* svld1rq is c = VEC_PERM <a, a> from a 128-bit vector; d = <c, c>. */
+
+svint32_t
+f1 (int32_t *p)
+{
+ svint32_t c = svld1rq_s32 (svptrue_b8 (), p);
+ return svrev_s32 (c);
+}
+
+svint32_t
+f2 (int32_t *p)
+{
+ svint32_t c = svld1rq_s32 (svptrue_b8 (), p);
+ return svzip1_s32 (c, c);
+}
+
+svint32_t
+f3 (int32_t *p)
+{
+ svint32_t c = svld1rq_s32 (svptrue_b8 (), p);
+ return svuzp2_s32 (c, c);
+}
+
+/* d = <x, c>. */
+
+svint32_t
+f4 (int32_t *p, svint32_t x)
+{
+ svint32_t c = svld1rq_s32 (svptrue_b8 (), p);
+ return svzip1_s32 (x, c);
+}
+
+svint32_t
+f5 (int32_t *p, svint32_t x)
+{
+ svint32_t c = svld1rq_s32 (svptrue_b8 (), p);
+ return svuzp1_s32 (x, c);
+}
+
+/* d = <c, x>. */
+
+svint32_t
+f6 (int32_t *p, svint32_t x)
+{
+ svint32_t c = svld1rq_s32 (svptrue_b8 (), p);
+ return svtrn2_s32 (c, x);
+}
diff --git a/gcc/testsuite/gcc.target/aarch64/sve/pr127862_run.c
b/gcc/testsuite/gcc.target/aarch64/sve/pr127862_run.c
new file mode 100644
index 00000000000..abbec284cb8
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/sve/pr127862_run.c
@@ -0,0 +1,46 @@
+/* VEC_PERM_EXPR merges with a result wider than the inputs. */
+/* { dg-do run { target aarch64_sve256_hw } } */
+/* { dg-options "-O2 -msve-vector-bits=256" } */
+
+#include <arm_sve.h>
+
+int32_t p[4] = { 1, 2, 3, 4 };
+int32_t xs[8] = { 11, 12, 13, 14, 15, 16, 17, 18 };
+int32_t r[8];
+
+/* c = VEC_PERM <a, a>, d = VEC_PERM <c, c>. */
+void __attribute__((noipa))
+f1 (int32_t *p)
+{
+ svint32_t c = svld1rq_s32 (svptrue_b8 (), p);
+ svst1_s32 (svptrue_b8 (), r, svrev_s32 (c));
+}
+
+/* c = VEC_PERM <a, a>, d = VEC_PERM <c, x>. */
+void __attribute__((noipa))
+f2 (int32_t *p, svint32_t x)
+{
+ svint32_t c = svld1rq_s32 (svptrue_b8 (), p);
+ svst1_s32 (svptrue_b8 (), r, svtrn2_s32 (c, x));
+}
+
+static void
+check (const int32_t *want)
+{
+ for (int i = 0; i < 8; i++)
+ if (r[i] != want[i])
+ __builtin_abort ();
+}
+
+int
+main (void)
+{
+ static const int32_t want1[8] = { 4, 3, 2, 1, 4, 3, 2, 1 };
+ static const int32_t want2[8] = { 2, 12, 4, 14, 2, 16, 4, 18 };
+
+ f1 (p);
+ check (want1);
+ f2 (p, svld1_s32 (svptrue_b8 (), xs));
+ check (want2);
+ return 0;
+}
diff --git a/gcc/tree-ssa-forwprop.cc b/gcc/tree-ssa-forwprop.cc
index fba4387f503..942b5df666e 100644
--- a/gcc/tree-ssa-forwprop.cc
+++ b/gcc/tree-ssa-forwprop.cc
@@ -5030,6 +5030,15 @@ simplify_permutation (gimple_stmt_iterator *gsi)
op3 = gimple_assign_rhs3 (def_stmt);
if (TREE_CODE (op3) != VECTOR_CST)
return false;
+ /* is_combined_permutation_identity assumes that both permutes and
+ their inputs have the same number of elements. Mixed widths are
+ left to the match.pd VEC_PERM merge patterns. */
+ if (maybe_ne (TYPE_VECTOR_SUBPARTS (TREE_TYPE (op2)),
+ TYPE_VECTOR_SUBPARTS (TREE_TYPE (op3)))
+ || maybe_ne (TYPE_VECTOR_SUBPARTS (TREE_TYPE (op0)),
+ TYPE_VECTOR_SUBPARTS
+ (TREE_TYPE (gimple_assign_rhs1 (def_stmt)))))
+ return false;
ident = is_combined_permutation_identity (op3, op2);
if (!ident)
return false;
--
2.55.0