diff --git a/gcc/doc/tm.texi b/gcc/doc/tm.texi
index 4deb3d2c283a2964972b94f434370a6f57ea816a..e8192590ac14005bf7cb5f731c16ee7eacb78143 100644
--- a/gcc/doc/tm.texi
+++ b/gcc/doc/tm.texi
@@ -6480,6 +6480,15 @@ type @code{internal_fn}) should be considered expensive when the mask is
 all zeros.  GCC can then try to branch around the instruction instead.
 @end deftypefn
 
+@deftypefn {Target Hook} bool TARGET_VECTORIZE_USE_PERMUTE_FOR_PROMOTION (const_tree @var{in_type}, const_tree @var{out_type})
+This hook returns true if the operation promoting @var{in_type} to
+@var{out_type} should be done as a vector permute.  If @var{out_type} is
+a signed type the operation will be done as the related unsigned type and
+converted to @var{out_type}.  If the target supports the needed permute,
+is able to convert unsigned(@var{out_type}) to @var{out_type} and it is
+beneficial to the hook should return true, else false should be returned.
+@end deftypefn
+
 @deftypefn {Target Hook} {class vector_costs *} TARGET_VECTORIZE_CREATE_COSTS (vec_info *@var{vinfo}, bool @var{costing_for_scalar})
 This hook should initialize target-specific data structures in preparation
 for modeling the costs of vectorizing a loop or basic block.  The default
diff --git a/gcc/doc/tm.texi.in b/gcc/doc/tm.texi.in
index 9f147ccb95cc6d4e79cdf5b265666ad502492145..c007bc707372dd374e8effc52d29b76f5bc283a1 100644
--- a/gcc/doc/tm.texi.in
+++ b/gcc/doc/tm.texi.in
@@ -4303,6 +4303,8 @@ address;  but often a machine-dependent strategy can generate better code.
 
 @hook TARGET_VECTORIZE_EMPTY_MASK_IS_EXPENSIVE
 
+@hook TARGET_VECTORIZE_USE_PERMUTE_FOR_PROMOTION
+
 @hook TARGET_VECTORIZE_CREATE_COSTS
 
 @hook TARGET_VECTORIZE_BUILTIN_GATHER
diff --git a/gcc/target.def b/gcc/target.def
index b31550108883c5c3f5ffc7e46a1e8a7b839ebe83..58545d5ef4248da5850edec8f4db9f2636973598 100644
--- a/gcc/target.def
+++ b/gcc/target.def
@@ -2056,6 +2056,20 @@ all zeros.  GCC can then try to branch around the instruction instead.",
  (unsigned ifn),
  default_empty_mask_is_expensive)
 
+/* Function to say whether a target supports and prefers to use permutes for
+   zero extensions or truncates.  */
+DEFHOOK
+(use_permute_for_promotion,
+ "This hook returns true if the operation promoting @var{in_type} to\n\
+@var{out_type} should be done as a vector permute.  If @var{out_type} is\n\
+a signed type the operation will be done as the related unsigned type and\n\
+converted to @var{out_type}.  If the target supports the needed permute,\n\
+is able to convert unsigned(@var{out_type}) to @var{out_type} and it is\n\
+beneficial to the hook should return true, else false should be returned.",
+ bool,
+ (const_tree in_type, const_tree out_type),
+ default_use_permute_for_promotion)
+
 /* Target builtin that implements vector gather operation.  */
 DEFHOOK
 (builtin_gather,
diff --git a/gcc/targhooks.h b/gcc/targhooks.h
index 2704d6008f14d2aa65671f002af886d3b802effa..723f8f4fda7808b6899f10f8b3fafad74d3c536f 100644
--- a/gcc/targhooks.h
+++ b/gcc/targhooks.h
@@ -124,6 +124,7 @@ extern opt_machine_mode default_vectorize_related_mode (machine_mode,
 extern opt_machine_mode default_get_mask_mode (machine_mode);
 extern bool default_empty_mask_is_expensive (unsigned);
 extern bool default_conditional_operation_is_expensive (unsigned);
+extern bool default_use_permute_for_promotion (const_tree, const_tree);
 extern vector_costs *default_vectorize_create_costs (vec_info *, bool);
 
 /* OpenACC hooks.  */
diff --git a/gcc/targhooks.cc b/gcc/targhooks.cc
index dc040df9fcd1182b62d83088ee7fb3a248c99f51..a487eab794fe9f1089ecb58fdfc881fdb19d28f3 100644
--- a/gcc/targhooks.cc
+++ b/gcc/targhooks.cc
@@ -1615,6 +1615,14 @@ default_conditional_operation_is_expensive (unsigned ifn)
   return ifn == IFN_MASK_STORE;
 }
 
+/* By default no targets prefer permutes over multi step extension.  */
+
+bool
+default_use_permute_for_promotion (const_tree, const_tree)
+{
+  return false;
+}
+
 /* By default consider masked stores to be expensive.  */
 
 bool
diff --git a/gcc/tree-vect-stmts.cc b/gcc/tree-vect-stmts.cc
index 4f6905f15417f90c6f36e1711a7a25071f0f507c..f2939655e4ec34111baa8894eaf769d29b1c5b82 100644
--- a/gcc/tree-vect-stmts.cc
+++ b/gcc/tree-vect-stmts.cc
@@ -5129,6 +5129,111 @@ vect_create_vectorized_promotion_stmts (vec_info *vinfo,
   gimple *new_stmt1, *new_stmt2;
   vec<tree> vec_tmp = vNULL;
 
+  /* If we're using a VEC_PERM_EXPR then we're widening to the final type in
+     one go.  */
+  if (ch1 == VEC_PERM_EXPR
+      && op_type == unary_op)
+    {
+      vec_tmp.create (vec_oprnds0->length () * 2);
+      bool failed_p = false;
+
+      /* Extending with a vec-perm requires 2 instructions per step.  */
+      FOR_EACH_VEC_ELT (*vec_oprnds0, i, vop0)
+	{
+	  tree vectype_in = TREE_TYPE (vop0);
+	  tree vectype_out = TREE_TYPE (vec_dest);
+	  machine_mode mode_in = TYPE_MODE (vectype_in);
+	  machine_mode mode_out = TYPE_MODE (vectype_out);
+	  unsigned bitsize_in = element_precision (vectype_in);
+	  unsigned tot_in, tot_out;
+	  unsigned HOST_WIDE_INT count;
+
+	  /* We can't really support VLA here as the indexes depend on the VL.
+	     VLA should really use widening instructions like widening
+	     loads.  */
+	  if (!GET_MODE_BITSIZE (mode_in).is_constant (&tot_in)
+	      || !GET_MODE_BITSIZE (mode_out).is_constant (&tot_out)
+	      || !TYPE_VECTOR_SUBPARTS (vectype_in).is_constant (&count)
+	      || !TYPE_UNSIGNED (vectype_in)
+	      || !targetm.vectorize.use_permute_for_promotion (vectype_in,
+							       vectype_out))
+	    {
+	      failed_p = true;
+	      break;
+	    }
+
+	  unsigned steps = tot_out / bitsize_in;
+	  tree zero = build_zero_cst (vectype_in);
+
+	  unsigned chunk_size
+	    = exact_div (TYPE_VECTOR_SUBPARTS (vectype_in),
+			 TYPE_VECTOR_SUBPARTS (vectype_out)).to_constant ();
+	  unsigned step_size = chunk_size * (tot_out / tot_in);
+	  unsigned nunits = tot_out / bitsize_in;
+
+	  vec_perm_builder sel (steps, 1, 1);
+	  sel.quick_grow (steps);
+
+	  /* Flood fill with the out of range value first.  */
+	  for (unsigned long i = 0; i < steps; ++i)
+	    sel[i] = count;
+
+	  tree var;
+	  tree elem_in = TREE_TYPE (vectype_in);
+	  machine_mode elem_mode_in = TYPE_MODE (elem_in);
+	  unsigned long idx = 0;
+	  tree vc_in = get_related_vectype_for_scalar_type (elem_mode_in,
+							    elem_in, nunits);
+
+	  for (unsigned long j = 0; j < chunk_size; j++)
+	    {
+	      if (WORDS_BIG_ENDIAN)
+		for (int i = steps - 1; i >= 0; i -= step_size, idx++)
+		  sel[i] = idx;
+	      else
+		for (int i = 0; i < (int)steps; i += step_size, idx++)
+		  sel[i] = idx;
+
+	      vec_perm_indices indices (sel, 2, steps);
+
+	      tree perm_mask = vect_gen_perm_mask_checked (vc_in, indices);
+	      auto vec_oprnd = make_ssa_name (vc_in);
+	      auto new_stmt = gimple_build_assign (vec_oprnd, VEC_PERM_EXPR,
+						   vop0, zero, perm_mask);
+	      vect_finish_stmt_generation (vinfo, stmt_info, new_stmt, gsi);
+
+	      tree intvect_out = unsigned_type_for (vectype_out);
+	      var = make_ssa_name (intvect_out);
+	      new_stmt = gimple_build_assign (var, build1 (VIEW_CONVERT_EXPR,
+							   intvect_out,
+							   vec_oprnd));
+	      vect_finish_stmt_generation (vinfo, stmt_info, new_stmt, gsi);
+
+	      gcc_assert (ch2.is_tree_code ());
+
+	      var = make_ssa_name (vectype_out);
+	      if (ch2 == VIEW_CONVERT_EXPR)
+		  new_stmt = gimple_build_assign (var,
+						  build1 (VIEW_CONVERT_EXPR,
+							  vectype_out,
+							  vec_oprnd));
+	      else
+		  new_stmt = gimple_build_assign (var, (tree_code)ch2,
+						  vec_oprnd);
+
+	      vect_finish_stmt_generation (vinfo, stmt_info, new_stmt, gsi);
+	      vec_tmp.safe_push (var);
+	    }
+	}
+
+      if (!failed_p)
+	{
+	  vec_oprnds0->release ();
+	  *vec_oprnds0 = vec_tmp;
+	  return;
+	}
+    }
+
   vec_tmp.create (vec_oprnds0->length () * 2);
   FOR_EACH_VEC_ELT (*vec_oprnds0, i, vop0)
     {
@@ -5495,6 +5600,20 @@ vectorizable_conversion (vec_info *vinfo,
 	  || GET_MODE_SIZE (lhs_mode) <= GET_MODE_SIZE (rhs_mode))
 	goto unsupported;
 
+      /* Check to see if the target can use a permute to perform the zero
+	 extension.  */
+      intermediate_type = unsigned_type_for (vectype_out);
+      if (TYPE_UNSIGNED (vectype_in)
+	  && VECTOR_TYPE_P (intermediate_type)
+	  && TYPE_VECTOR_SUBPARTS (intermediate_type).is_constant ()
+	  && targetm.vectorize.use_permute_for_promotion (vectype_in,
+							  intermediate_type))
+	{
+	  code1 = VEC_PERM_EXPR;
+	  code2 = FLOAT_EXPR;
+	  break;
+	}
+
       fltsz = GET_MODE_SIZE (lhs_mode);
       FOR_EACH_2XWIDER_MODE (rhs_mode_iter, rhs_mode)
 	{
@@ -9804,7 +9923,8 @@ vect_gen_perm_mask_any (tree vectype, const vec_perm_indices &sel)
   tree mask_type;
 
   poly_uint64 nunits = sel.length ();
-  gcc_assert (known_eq (nunits, TYPE_VECTOR_SUBPARTS (vectype)));
+  gcc_assert (known_eq (nunits, TYPE_VECTOR_SUBPARTS (vectype))
+	      || known_eq (nunits, TYPE_VECTOR_SUBPARTS (vectype) * 2));
 
   mask_type = build_vector_type (ssizetype, nunits);
   return vec_perm_indices_to_tree (mask_type, sel);
@@ -14397,8 +14517,20 @@ supportable_widening_operation (vec_info *vinfo,
       break;
 
     CASE_CONVERT:
-      c1 = VEC_UNPACK_LO_EXPR;
-      c2 = VEC_UNPACK_HI_EXPR;
+      {
+	tree cvt_type = unsigned_type_for (vectype_out);
+	if (TYPE_UNSIGNED (vectype_in)
+	  && VECTOR_TYPE_P (cvt_type)
+	  && TYPE_VECTOR_SUBPARTS (cvt_type).is_constant ()
+	  && targetm.vectorize.use_permute_for_promotion (vectype_in, cvt_type))
+	  {
+	    *code1 = VEC_PERM_EXPR;
+	    *code2 = VIEW_CONVERT_EXPR;
+	    return true;
+	  }
+	c1 = VEC_UNPACK_LO_EXPR;
+	c2 = VEC_UNPACK_HI_EXPR;
+      }
       break;
 
     case FLOAT_EXPR:



