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