RISC-V: Support vector initialization from two halves

Message ID 20261002060635.3095-1-chen.zhongyao@zte.com.cn
State Changes Requested, archived
Delegated to: Robin Dapp
Headers
Series RISC-V: Support vector initialization from two halves |

Commit Message

Zhongyao Chen Oct. 2, 2026, 6:06 a.m. UTC
  This patch adds vec_concat and vec_init expanders to construct a full
vector from two half-width vectors (e.g. [0 1 2 3] + [4 5 6 7] ->
[0 1 2 3 4 5 6 7]) via vslideup.

Key changes:
- Implement @vec_concat and vec_init expanders to combine two halves via vslideup.
- Extend VLS_HAS_HALF to include Float16 (HF) and BFloat16 (BF) modes.
- Add VLA_HAS_HALF to support VLA modes.
- Unify them under V_HAS_HALF and generalize V_HALF/v_half (also updating
  vec_extract).
- Refactor shuffle_even_odd_patterns to use gen_vec_concat.

Regtested on rv64gcv.

gcc/ChangeLog:

	* config/riscv/autovec.md (vec_init<mode><v_half>): New expander.
	(vec_extract<mode><v_half>): Use <V_HALF>.
	* config/riscv/riscv-v.cc (shuffle_even_odd_patterns): Use
	gen_vec_concat.
	* config/riscv/vector.md (@vec_concat<mode>): New expander.
	* config/riscv/vector-iterators.md (VLS_HAS_HALF): Add HF and BF modes.
	(VLA_HAS_HALF, V_HAS_HALF): New mode iterators.
	(V_HALF, v_half): Rename from VLS_HALF and vls_half.

gcc/testsuite/ChangeLog:

	* gcc.target/riscv/rvv/autovec/vec-concat-1.c: New test.
	* gcc.target/riscv/rvv/autovec/vec-concat-2.c: Likewise.

Signed-off-by: Zhongyao Chen <chen.zhongyao@zte.com.cn>
---
 gcc/config/riscv/autovec.md                   |  29 ++-
 gcc/config/riscv/riscv-v.cc                   |  26 +--
 gcc/config/riscv/vector-iterators.md          | 201 +++++++++++++++++-
 gcc/config/riscv/vector.md                    |  23 ++
 .../riscv/rvv/autovec/vec-concat-1.c          |  68 ++++++
 .../riscv/rvv/autovec/vec-concat-2.c          |  16 ++
 6 files changed, 343 insertions(+), 20 deletions(-)
 create mode 100644 gcc/testsuite/gcc.target/riscv/rvv/autovec/vec-concat-1.c
 create mode 100644 gcc/testsuite/gcc.target/riscv/rvv/autovec/vec-concat-2.c
  

Comments

Robin Dapp Oct. 12, 2026, 12:12 p.m. UTC | #1
Hi Zhongyao,

Sorry for the long delay and thanks for working on this.

I assume you added the VLA modes to the iterator for 
-mrvv-vector-bitz=zvl?  I think that's a good thing, at least until we 
manage to represent vec_extract etc. differently in the middle end.  I'm 
pretty far with that patch but not yet finished.  Also it might be 
contentious so I wouldn't want your patch to depend on it.

Adding HF and BF is fine, they were just forgotten before.

> -      rtx ops[] = {d->target, d->target, tmp, gen_int_mode (vlen / 2, 
> Pmode)};
> -      icode = code_for_pred_slide (UNSPEC_VSLIDEUP, vmode);
> -      emit_vlmax_insn (icode, SLIDEUP_OP_MERGE, ops);
> +
> +      machine_mode imode = related_int_vector_mode (vmode).require ();
> +      emit_insn (gen_vec_concat (imode, gen_lowpart (imode, d->target),
> +				 lo, hi));

I did quick local testing and saw an ICE here with zvl256.  I think the 
issue is that with -mrvv-vector-bits=zvl we can get a mix of VLA "in 
name" modes with fixed size as well as real VLS modes.  And obviously we 
don't define vec_concat_rvvmf2si_v2si or something so gen_vec_concat 
will return NULL.  For the time being you'd need to guard against that 
and use the old code path.  We have been hitting this issue repeatedly 
with vec_extract.  A high-maintenance solution would be to split all our 
patterns into VLA and VLS but... :)

> +/* FIXME (missed optimization): Ideally each concat needs only 1 
> vslideup,
> +   but frontend lowering of __builtin_shufflevector currently zero-pads each
> +   half-width input first, resulting in 2 padding constructors and 1 concat
> +   per function (35 * 3 = 105).  */

It looks like we need more slideups than before for certain cases?  Is 
that only for LMUL>1?  Why is that?  Before we could just slideup one 
element while we now have two zero-padded vectors that are being 
combined?  If so, can we exclude those cases (everything <= 
REGMODE_NATURAL_SIZE)?
  
Zhongyao Chen Oct. 12, 2026, 3:28 p.m. UTC | #2
Thanks for review.

> I assume you added the VLA modes to the iterator for
> -mrvv-vector-bitz=zvl?  I think that's a good thing, at least until we
> manage to represent vec_extract etc. differently in the middle end.  I'm
> pretty far with that patch but not yet finished.  Also it might be
> contentious so I wouldn't want your patch to depend on it.
>
> Adding HF and BF is fine, they were just forgotten before.
>

I saw only VLS was supported, can't find why, so add VLA as well.

> > -      rtx ops[] = {d->target, d->target, tmp, gen_int_mode (vlen / 2,
> > Pmode)};
> > -      icode = code_for_pred_slide (UNSPEC_VSLIDEUP, vmode);
> > -      emit_vlmax_insn (icode, SLIDEUP_OP_MERGE, ops);
> > +
> > +      machine_mode imode = related_int_vector_mode (vmode).require ();
> > +      emit_insn (gen_vec_concat (imode, gen_lowpart (imode, d->target),
> > +                              lo, hi));
>
> I did quick local testing and saw an ICE here with zvl256.  I think the
> issue is that with -mrvv-vector-bits=zvl we can get a mix of VLA "in
> name" modes with fixed size as well as real VLS modes.  And obviously we
> don't define vec_concat_rvvmf2si_v2si or something so gen_vec_concat
> will return NULL.  For the time being you'd need to guard against that
> and use the old code path.  We have been hitting this issue repeatedly
> with vec_extract.  A high-maintenance solution would be to split all our
> patterns into VLA and VLS but... :)

my bad for missing that. I'll guard this in v2 and fall back to the
old path when modes don't match.
BTW, how did you test with zvl256? I didn't see that failure in my
regtest. did you just add -mrvv-vector-bits=zvl to my testcase?

>
> > +/* FIXME (missed optimization): Ideally each concat needs only 1
> > vslideup,
> > +   but frontend lowering of __builtin_shufflevector currently zero-pads each
> > +   half-width input first, resulting in 2 padding constructors and 1 concat
> > +   per function (35 * 3 = 105).  */
>
> It looks like we need more slideups than before for certain cases?  Is
> that only for LMUL>1?  Why is that?  Before we could just slideup one
> element while we now have two zero-padded vectors that are being
> combined?  If so, can we exclude those cases (everything <=
> REGMODE_NATURAL_SIZE)?

No, that's not a regression.
On trunk now (https://godbolt.org/z/bjE5b7qPP) there are spills;
with this patch, it turns out to have no spills and 3 slideups.
What I meant here was just a hint for future work, ideally it should
only need 1 slideup and no spills, but that's not this patch's job.
  
Robin Dapp Oct. 13, 2026, 10:23 a.m. UTC | #3
>> It looks like we need more slideups than before for certain cases?  
>> Is
>> that only for LMUL>1?  Why is that?  Before we could just slideup one
>> element while we now have two zero-padded vectors that are being
>> combined?  If so, can we exclude those cases (everything <=
>> REGMODE_NATURAL_SIZE)?
>
> No, that's not a regression.
> On trunk now (https://godbolt.org/z/bjE5b7qPP) there are spills;
> with this patch, it turns out to have no spills and 3 slideups.
> What I meant here was just a hint for future work, ideally it should
> only need 1 slideup and no spills, but that's not this patch's job.

I meant for LMUL > 1 like https://godbolt.org/z/b31vf8Wf4
To me it looked like we spill twice as much with the patch.  Or did I 
mix something up?
  
Zhongyao Chen Oct. 14, 2026, 1:59 a.m. UTC | #4
> >> It looks like we need more slideups than before for certain cases?
> >> Is
> >> that only for LMUL>1?  Why is that?  Before we could just slideup one
> >> element while we now have two zero-padded vectors that are being
> >> combined?  If so, can we exclude those cases (everything <=
> >> REGMODE_NATURAL_SIZE)?
> >
> > No, that's not a regression.
> > On trunk now (https://godbolt.org/z/bjE5b7qPP) there are spills;
> > with this patch, it turns out to have no spills and 3 slideups.
> > What I meant here was just a hint for future work, ideally it should
> > only need 1 slideup and no spills, but that's not this patch's job.
>
> I meant for LMUL > 1 like https://godbolt.org/z/b31vf8Wf4
> To me it looked like we spill twice as much with the patch.  Or did I
> mix something up?
>

Hi Robin,

You didn't mix anything up, I just checked the asm, and it's on me.

I only checked the LMUL=1 case on eyes before and forgot to scan for
spills in the testcase,
so when regtest showed all PASS, I just assumed all LMUL were gone..... My bad!

I'll follow your suggestion to exclude LMUL>1. Patch v2 on flight.
  

Patch

diff --git a/gcc/config/riscv/autovec.md b/gcc/config/riscv/autovec.md
index 12538c8f162..90c4766a4c1 100644
--- a/gcc/config/riscv/autovec.md
+++ b/gcc/config/riscv/autovec.md
@@ -452,6 +452,25 @@ 
   }
 )
 
+;; -------------------------------------------------------------------------
+;; ---- [INT,FP] Initialize from two half-width vectors
+;; -------------------------------------------------------------------------
+
+(define_expand "vec_init<mode><v_half>"
+  [(match_operand:V_HAS_HALF 0 "register_operand")
+   (match_operand 1 "")]
+  "TARGET_VECTOR"
+{
+  gcc_assert (GET_CODE (operands[1]) == PARALLEL);
+  gcc_assert (XVECLEN (operands[1], 0) == 2);
+
+  rtx low = force_reg (<V_HALF>mode, XVECEXP (operands[1], 0, 0));
+  rtx high = force_reg (<V_HALF>mode, XVECEXP (operands[1], 0, 1));
+
+  emit_insn (gen_vec_concat (<MODE>mode, operands[0], low, high));
+  DONE;
+})
+
 ;; Slide an RVV vector left and insert a scalar into element 0.
 (define_expand "vec_shl_insert_<mode>"
   [(match_operand:V_VLSI 0 "register_operand")
@@ -1523,15 +1542,15 @@ 
 ;; For now this helps optimize VLS subregs like (subreg:V2DI (reg:V4DI) 16)
 ;; that would otherwise need to go via memory.
 
-(define_expand "vec_extract<mode><vls_half>"
-  [(set (match_operand:<VLS_HALF>	 0 "nonimmediate_operand")
-     (vec_select:<VLS_HALF>
+(define_expand "vec_extract<mode><v_half>"
+  [(set (match_operand:<V_HALF>	 0 "nonimmediate_operand")
+     (vec_select:<V_HALF>
        (match_operand:VLS_HAS_HALF	 1 "register_operand")
        (parallel
 	 [(match_operand		 2 "immediate_operand")])))]
   "TARGET_VECTOR"
 {
-  int sz = GET_MODE_NUNITS (<VLS_HALF>mode).to_constant ();
+  int sz = GET_MODE_NUNITS (<V_HALF>mode).to_constant ();
   int part = INTVAL (operands[2]);
 
   rtx start = GEN_INT (part * sz);
@@ -1547,7 +1566,7 @@ 
 	 riscv_vector::BINARY_OP, ops);
     }
 
-  emit_move_insn (operands[0], gen_lowpart (<VLS_HALF>mode, tmp));
+  emit_move_insn (operands[0], gen_lowpart (<V_HALF>mode, tmp));
   DONE;
 })
 
diff --git a/gcc/config/riscv/riscv-v.cc b/gcc/config/riscv/riscv-v.cc
index dd453aa320b..33002a3c088 100644
--- a/gcc/config/riscv/riscv-v.cc
+++ b/gcc/config/riscv/riscv-v.cc
@@ -4056,11 +4056,11 @@  shuffle_even_odd_patterns (struct expand_vec_perm_d *d)
 
   /* When the element width is smaller than the greatest ELEN, we can use two
      vnsrl instructions, each extracting the even/odd elements of one source,
-     and a vslideup instruction to merge them into one vector.
+     and vec_concat to merge them into one vector.
 
-     Until we have a "widening" vector concat pattern (just like slideup here
-     but with the proper modes) we still need the natural-size check for
-     LMUL > 1 cases.  */
+     We still need the natural-size check for LMUL > 1 cases because
+     vec_concat relies on lowpart-subreg moves, which would force memory
+     spills for multi-register VLS vectors.  */
   unsigned int max_elen = TARGET_VECTOR_ELEN_64 ? 64 : 32;
   if (GET_MODE_BITSIZE (GET_MODE_INNER (vmode)) * 2 <= max_elen
       && known_le (GET_MODE_SIZE (vmode), riscv_regmode_natural_size (vmode)))
@@ -4074,20 +4074,20 @@  shuffle_even_odd_patterns (struct expand_vec_perm_d *d)
       machine_mode vmode_half = get_vector_mode (smode, vlen / 2).require ();
       unsigned int shift_amt = even ? 0 : elen;
       insn_code icode = code_for_pred_narrow_scalar (LSHIFTRT, vmode_elen2x);
-      /* TODO these lowpart subreg workarounds should go, this is actually a
-	 simple concatenation of two "half"-sized vectors.  */
-      rtx tmp = gen_reg_rtx (vmode);
+      rtx lo = gen_reg_rtx (vmode_half);
+      rtx hi = gen_reg_rtx (vmode_half);
       rtx ops_shift1[]
-	= {gen_lowpart (vmode_half, d->target),
-	   gen_lowpart (vmode_elen2x, d->op0), gen_int_mode (shift_amt, Pmode)};
+	= {lo, gen_lowpart (vmode_elen2x, d->op0),
+	   gen_int_mode (shift_amt, Pmode)};
       rtx ops_shift2[]
-	= {gen_lowpart (vmode_half, tmp), gen_lowpart (vmode_elen2x, d->op1),
+	= {hi, gen_lowpart (vmode_elen2x, d->op1),
 	   gen_int_mode (shift_amt, Pmode)};
       emit_vlmax_insn (icode, BINARY_OP, ops_shift1);
       emit_vlmax_insn (icode, BINARY_OP, ops_shift2);
-      rtx ops[] = {d->target, d->target, tmp, gen_int_mode (vlen / 2, Pmode)};
-      icode = code_for_pred_slide (UNSPEC_VSLIDEUP, vmode);
-      emit_vlmax_insn (icode, SLIDEUP_OP_MERGE, ops);
+
+      machine_mode imode = related_int_vector_mode (vmode).require ();
+      emit_insn (gen_vec_concat (imode, gen_lowpart (imode, d->target),
+				 lo, hi));
       return true;
     }
 
diff --git a/gcc/config/riscv/vector-iterators.md b/gcc/config/riscv/vector-iterators.md
index f236e71c67c..c0475b6b72a 100644
--- a/gcc/config/riscv/vector-iterators.md
+++ b/gcc/config/riscv/vector-iterators.md
@@ -6012,6 +6012,14 @@ 
   (V4DI "riscv_vector::vls_mode_valid_p (V4DImode) && TARGET_VECTOR_ELEN_64")
   (V8DI "riscv_vector::vls_mode_valid_p (V8DImode) && TARGET_VECTOR_ELEN_64 && TARGET_MIN_VLEN >= 64")
   (V16DI "riscv_vector::vls_mode_valid_p (V16DImode) && TARGET_VECTOR_ELEN_64 && TARGET_MIN_VLEN >= 128")
+  (V2HF "riscv_vector::vls_mode_valid_p (V2HFmode) && TARGET_VECTOR_ELEN_FP_16")
+  (V4HF "riscv_vector::vls_mode_valid_p (V4HFmode) && TARGET_VECTOR_ELEN_FP_16")
+  (V8HF "riscv_vector::vls_mode_valid_p (V8HFmode) && TARGET_VECTOR_ELEN_FP_16")
+  (V16HF "riscv_vector::vls_mode_valid_p (V16HFmode) && TARGET_VECTOR_ELEN_FP_16")
+  (V2BF "riscv_vector::vls_mode_valid_p (V2BFmode) && TARGET_VECTOR_ELEN_BF_16")
+  (V4BF "riscv_vector::vls_mode_valid_p (V4BFmode) && TARGET_VECTOR_ELEN_BF_16")
+  (V8BF "riscv_vector::vls_mode_valid_p (V8BFmode) && TARGET_VECTOR_ELEN_BF_16")
+  (V16BF "riscv_vector::vls_mode_valid_p (V16BFmode) && TARGET_VECTOR_ELEN_BF_16")
   (V2SF "riscv_vector::vls_mode_valid_p (V2SFmode) && TARGET_VECTOR_ELEN_FP_32")
   (V4SF "riscv_vector::vls_mode_valid_p (V4SFmode) && TARGET_VECTOR_ELEN_FP_32")
   (V8SF "riscv_vector::vls_mode_valid_p (V8SFmode) && TARGET_VECTOR_ELEN_FP_32")
@@ -6046,6 +6054,20 @@ 
   (V128DI "riscv_vector::vls_mode_valid_p (V128DImode) && TARGET_VECTOR_ELEN_64 && TARGET_MIN_VLEN >= 1024")
   (V256DI "riscv_vector::vls_mode_valid_p (V256DImode) && TARGET_VECTOR_ELEN_64 && TARGET_MIN_VLEN >= 2048")
   (V512DI "riscv_vector::vls_mode_valid_p (V512DImode) && TARGET_VECTOR_ELEN_64 && TARGET_MIN_VLEN >= 4096")
+  (V32HF "riscv_vector::vls_mode_valid_p (V32HFmode) && TARGET_VECTOR_ELEN_FP_16 && TARGET_MIN_VLEN >= 64")
+  (V64HF "riscv_vector::vls_mode_valid_p (V64HFmode) && TARGET_VECTOR_ELEN_FP_16 && TARGET_MIN_VLEN >= 128")
+  (V128HF "riscv_vector::vls_mode_valid_p (V128HFmode) && TARGET_VECTOR_ELEN_FP_16 && TARGET_MIN_VLEN >= 256")
+  (V256HF "riscv_vector::vls_mode_valid_p (V256HFmode) && TARGET_VECTOR_ELEN_FP_16 && TARGET_MIN_VLEN >= 512")
+  (V512HF "riscv_vector::vls_mode_valid_p (V512HFmode) && TARGET_VECTOR_ELEN_FP_16 && TARGET_MIN_VLEN >= 1024")
+  (V1024HF "riscv_vector::vls_mode_valid_p (V1024HFmode) && TARGET_VECTOR_ELEN_FP_16 && TARGET_MIN_VLEN >= 2048")
+  (V2048HF "riscv_vector::vls_mode_valid_p (V2048HFmode) && TARGET_VECTOR_ELEN_FP_16 && TARGET_MIN_VLEN >= 4096")
+  (V32BF "riscv_vector::vls_mode_valid_p (V32BFmode) && TARGET_VECTOR_ELEN_BF_16 && TARGET_MIN_VLEN >= 64")
+  (V64BF "riscv_vector::vls_mode_valid_p (V64BFmode) && TARGET_VECTOR_ELEN_BF_16 && TARGET_MIN_VLEN >= 128")
+  (V128BF "riscv_vector::vls_mode_valid_p (V128BFmode) && TARGET_VECTOR_ELEN_BF_16 && TARGET_MIN_VLEN >= 256")
+  (V256BF "riscv_vector::vls_mode_valid_p (V256BFmode) && TARGET_VECTOR_ELEN_BF_16 && TARGET_MIN_VLEN >= 512")
+  (V512BF "riscv_vector::vls_mode_valid_p (V512BFmode) && TARGET_VECTOR_ELEN_BF_16 && TARGET_MIN_VLEN >= 1024")
+  (V1024BF "riscv_vector::vls_mode_valid_p (V1024BFmode) && TARGET_VECTOR_ELEN_BF_16 && TARGET_MIN_VLEN >= 2048")
+  (V2048BF "riscv_vector::vls_mode_valid_p (V2048BFmode) && TARGET_VECTOR_ELEN_BF_16 && TARGET_MIN_VLEN >= 4096")
   (V32SF "riscv_vector::vls_mode_valid_p (V32SFmode) && TARGET_VECTOR_ELEN_FP_32 && TARGET_MIN_VLEN >= 128")
   (V64SF "riscv_vector::vls_mode_valid_p (V64SFmode) && TARGET_VECTOR_ELEN_FP_32 && TARGET_MIN_VLEN >= 256")
   (V128SF "riscv_vector::vls_mode_valid_p (V128SFmode) && TARGET_VECTOR_ELEN_FP_32 && TARGET_MIN_VLEN >= 512")
@@ -6059,7 +6081,98 @@ 
   (V512DF "riscv_vector::vls_mode_valid_p (V512DFmode) && TARGET_VECTOR_ELEN_FP_64 && TARGET_MIN_VLEN >= 4096")
 ])
 
-(define_mode_attr VLS_HALF [
+;; Scalable (VLA) vector modes with a supported half-width vector.
+(define_mode_iterator VLA_HAS_HALF [
+  RVVM8QI
+  RVVM4QI
+  RVVM2QI
+  (RVVM1QI "!TARGET_XTHEADVECTOR")
+  (RVVMF2QI "!TARGET_XTHEADVECTOR")
+  (RVVMF4QI "!TARGET_XTHEADVECTOR && TARGET_VECTOR_ELEN_64")
+
+  RVVM8HI
+  RVVM4HI
+  RVVM2HI
+  (RVVM1HI "!TARGET_XTHEADVECTOR")
+  (RVVMF2HI "!TARGET_XTHEADVECTOR && TARGET_VECTOR_ELEN_64")
+
+  RVVM8SI
+  RVVM4SI
+  RVVM2SI
+  (RVVM1SI "!TARGET_XTHEADVECTOR && TARGET_VECTOR_ELEN_64")
+
+  (RVVM8DI "TARGET_VECTOR_ELEN_64")
+  (RVVM4DI "TARGET_VECTOR_ELEN_64")
+  (RVVM2DI "TARGET_VECTOR_ELEN_64")
+
+  (RVVM8HF "TARGET_VECTOR_ELEN_FP_16")
+  (RVVM4HF "TARGET_VECTOR_ELEN_FP_16")
+  (RVVM2HF "TARGET_VECTOR_ELEN_FP_16")
+  (RVVM1HF "TARGET_VECTOR_ELEN_FP_16 && !TARGET_XTHEADVECTOR")
+  (RVVMF2HF "TARGET_VECTOR_ELEN_FP_16 && !TARGET_XTHEADVECTOR
+	     && TARGET_VECTOR_ELEN_64")
+
+  (RVVM8BF "TARGET_VECTOR_ELEN_BF_16")
+  (RVVM4BF "TARGET_VECTOR_ELEN_BF_16")
+  (RVVM2BF "TARGET_VECTOR_ELEN_BF_16")
+  (RVVM1BF "TARGET_VECTOR_ELEN_BF_16 && !TARGET_XTHEADVECTOR")
+  (RVVMF2BF "TARGET_VECTOR_ELEN_BF_16 && !TARGET_XTHEADVECTOR
+	     && TARGET_VECTOR_ELEN_64")
+
+  (RVVM8SF "TARGET_VECTOR_ELEN_FP_32")
+  (RVVM4SF "TARGET_VECTOR_ELEN_FP_32")
+  (RVVM2SF "TARGET_VECTOR_ELEN_FP_32")
+  (RVVM1SF "TARGET_VECTOR_ELEN_FP_32 && !TARGET_XTHEADVECTOR
+	     && TARGET_VECTOR_ELEN_64")
+
+  (RVVM8DF "TARGET_VECTOR_ELEN_FP_64")
+  (RVVM4DF "TARGET_VECTOR_ELEN_FP_64")
+  (RVVM2DF "TARGET_VECTOR_ELEN_FP_64")
+])
+
+;; All vector modes (VLS and VLA) with a supported half-width vector.
+(define_mode_iterator V_HAS_HALF [
+  VLS_HAS_HALF
+  VLA_HAS_HALF
+])
+
+(define_mode_attr V_HALF [
+  (RVVM8QI "RVVM4QI")
+  (RVVM4QI "RVVM2QI")
+  (RVVM2QI "RVVM1QI")
+  (RVVM1QI "RVVMF2QI")
+  (RVVMF2QI "RVVMF4QI")
+  (RVVMF4QI "RVVMF8QI")
+  (RVVM8HI "RVVM4HI")
+  (RVVM4HI "RVVM2HI")
+  (RVVM2HI "RVVM1HI")
+  (RVVM1HI "RVVMF2HI")
+  (RVVMF2HI "RVVMF4HI")
+  (RVVM8SI "RVVM4SI")
+  (RVVM4SI "RVVM2SI")
+  (RVVM2SI "RVVM1SI")
+  (RVVM1SI "RVVMF2SI")
+  (RVVM8DI "RVVM4DI")
+  (RVVM4DI "RVVM2DI")
+  (RVVM2DI "RVVM1DI")
+  (RVVM8HF "RVVM4HF")
+  (RVVM4HF "RVVM2HF")
+  (RVVM2HF "RVVM1HF")
+  (RVVM1HF "RVVMF2HF")
+  (RVVMF2HF "RVVMF4HF")
+  (RVVM8BF "RVVM4BF")
+  (RVVM4BF "RVVM2BF")
+  (RVVM2BF "RVVM1BF")
+  (RVVM1BF "RVVMF2BF")
+  (RVVMF2BF "RVVMF4BF")
+  (RVVM8SF "RVVM4SF")
+  (RVVM4SF "RVVM2SF")
+  (RVVM2SF "RVVM1SF")
+  (RVVM1SF "RVVMF2SF")
+  (RVVM8DF "RVVM4DF")
+  (RVVM4DF "RVVM2DF")
+  (RVVM2DF "RVVM1DF")
+
   (V2QI "V1QI")
   (V4QI "V2QI")
   (V8QI "V4QI")
@@ -6106,6 +6219,30 @@ 
   (V256DI "V128DI")
   (V512DI "V256DI")
 
+  (V2HF "V1HF")
+  (V4HF "V2HF")
+  (V8HF "V4HF")
+  (V16HF "V8HF")
+  (V32HF "V16HF")
+  (V64HF "V32HF")
+  (V128HF "V64HF")
+  (V256HF "V128HF")
+  (V512HF "V256HF")
+  (V1024HF "V512HF")
+  (V2048HF "V1024HF")
+
+  (V2BF "V1BF")
+  (V4BF "V2BF")
+  (V8BF "V4BF")
+  (V16BF "V8BF")
+  (V32BF "V16BF")
+  (V64BF "V32BF")
+  (V128BF "V64BF")
+  (V256BF "V128BF")
+  (V512BF "V256BF")
+  (V1024BF "V512BF")
+  (V2048BF "V1024BF")
+
   (V2SF "V1SF")
   (V4SF "V2SF")
   (V8SF "V4SF")
@@ -6128,7 +6265,43 @@ 
   (V512DF "V256DF")
 ])
 
-(define_mode_attr vls_half [
+(define_mode_attr v_half [
+  (RVVM8QI "rvvm4qi")
+  (RVVM4QI "rvvm2qi")
+  (RVVM2QI "rvvm1qi")
+  (RVVM1QI "rvvmf2qi")
+  (RVVMF2QI "rvvmf4qi")
+  (RVVMF4QI "rvvmf8qi")
+  (RVVM8HI "rvvm4hi")
+  (RVVM4HI "rvvm2hi")
+  (RVVM2HI "rvvm1hi")
+  (RVVM1HI "rvvmf2hi")
+  (RVVMF2HI "rvvmf4hi")
+  (RVVM8SI "rvvm4si")
+  (RVVM4SI "rvvm2si")
+  (RVVM2SI "rvvm1si")
+  (RVVM1SI "rvvmf2si")
+  (RVVM8DI "rvvm4di")
+  (RVVM4DI "rvvm2di")
+  (RVVM2DI "rvvm1di")
+  (RVVM8HF "rvvm4hf")
+  (RVVM4HF "rvvm2hf")
+  (RVVM2HF "rvvm1hf")
+  (RVVM1HF "rvvmf2hf")
+  (RVVMF2HF "rvvmf4hf")
+  (RVVM8BF "rvvm4bf")
+  (RVVM4BF "rvvm2bf")
+  (RVVM2BF "rvvm1bf")
+  (RVVM1BF "rvvmf2bf")
+  (RVVMF2BF "rvvmf4bf")
+  (RVVM8SF "rvvm4sf")
+  (RVVM4SF "rvvm2sf")
+  (RVVM2SF "rvvm1sf")
+  (RVVM1SF "rvvmf2sf")
+  (RVVM8DF "rvvm4df")
+  (RVVM4DF "rvvm2df")
+  (RVVM2DF "rvvm1df")
+
   (V2QI "v1qi")
   (V4QI "v2qi")
   (V8QI "v4qi")
@@ -6175,6 +6348,30 @@ 
   (V256DI "v128di")
   (V512DI "v256di")
 
+  (V2HF "v1hf")
+  (V4HF "v2hf")
+  (V8HF "v4hf")
+  (V16HF "v8hf")
+  (V32HF "v16hf")
+  (V64HF "v32hf")
+  (V128HF "v64hf")
+  (V256HF "v128hf")
+  (V512HF "v256hf")
+  (V1024HF "v512hf")
+  (V2048HF "v1024hf")
+
+  (V2BF "v1bf")
+  (V4BF "v2bf")
+  (V8BF "v4bf")
+  (V16BF "v8bf")
+  (V32BF "v16bf")
+  (V64BF "v32bf")
+  (V128BF "v64bf")
+  (V256BF "v128bf")
+  (V512BF "v256bf")
+  (V1024BF "v512bf")
+  (V2048BF "v1024bf")
+
   (V2SF "v1sf")
   (V4SF "v2sf")
   (V8SF "v4sf")
diff --git a/gcc/config/riscv/vector.md b/gcc/config/riscv/vector.md
index 37f7b94604d..11d913d4a9c 100644
--- a/gcc/config/riscv/vector.md
+++ b/gcc/config/riscv/vector.md
@@ -8371,6 +8371,29 @@ 
   [(set_attr "type" "vfmovvf")
    (set_attr "mode" "<MODE>")])
 
+;; Concatenate two half-width vectors.  The merge supplies the low half
+;; and vslideup inserts the high half.
+(define_expand "@vec_concat<mode>"
+  [(set (match_operand:V_HAS_HALF 0 "register_operand")
+	(vec_concat:V_HAS_HALF
+	  (match_operand:<V_HALF> 1 "register_operand")
+	  (match_operand:<V_HALF> 2 "register_operand")))]
+  "TARGET_VECTOR"
+{
+  rtx low = gen_reg_rtx (<MODE>mode);
+  rtx high = gen_reg_rtx (<MODE>mode);
+  emit_clobber (low);
+  emit_move_insn (gen_lowpart (<V_HALF>mode, low), operands[1]);
+  emit_clobber (high);
+  emit_move_insn (gen_lowpart (<V_HALF>mode, high), operands[2]);
+  rtx offset = gen_int_mode (GET_MODE_NUNITS (<V_HALF>mode), Pmode);
+  rtx ops[] = {operands[0], low, high, offset};
+  insn_code icode = code_for_pred_slide (UNSPEC_VSLIDEUP, <MODE>mode);
+  riscv_vector::emit_vlmax_insn (icode,
+				 riscv_vector::SLIDEUP_OP_MERGE, ops);
+  DONE;
+})
+
 ;; vslide instructions
 (define_insn "@pred_slide<ud><mode>"
   [(set (match_operand:V_VLS 0 "register_operand"             "<ud_constraint>")
diff --git a/gcc/testsuite/gcc.target/riscv/rvv/autovec/vec-concat-1.c b/gcc/testsuite/gcc.target/riscv/rvv/autovec/vec-concat-1.c
new file mode 100644
index 00000000000..841e32fc7f4
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/rvv/autovec/vec-concat-1.c
@@ -0,0 +1,68 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2 -march=rv64gcv_zvfhmin_zvfbfmin -mabi=lp64d -mrvv-vector-bits=scalable -mrvv-max-lmul=m8" } */
+
+/* Test VLS vector concatenation via shufflevector across all data types.  */
+
+#define INDICES_2(x) (x), (x + 1)
+#define INDICES_4(x) INDICES_2 (x), INDICES_2 (x + 2)
+#define INDICES_8(x) INDICES_4 (x), INDICES_4 (x + 4)
+#define INDICES_16(x) INDICES_8 (x), INDICES_8 (x + 8)
+#define INDICES_32(x) INDICES_16 (x), INDICES_16 (x + 16)
+#define INDICES_64(x) INDICES_32 (x), INDICES_32 (x + 32)
+
+#define TEST_MODES(T) \
+  T (signed char, qi, 2) \
+  T (signed char, qi, 4) \
+  T (signed char, qi, 8) \
+  T (signed char, qi, 16) \
+  T (signed char, qi, 32) \
+  T (signed char, qi, 64) \
+  T (short, hi, 2) \
+  T (short, hi, 4) \
+  T (short, hi, 8) \
+  T (short, hi, 16) \
+  T (short, hi, 32) \
+  T (int, si, 2) \
+  T (int, si, 4) \
+  T (int, si, 8) \
+  T (int, si, 16) \
+  T (long long, di, 2) \
+  T (long long, di, 4) \
+  T (long long, di, 8) \
+  T (float, sf, 2) \
+  T (float, sf, 4) \
+  T (float, sf, 8) \
+  T (float, sf, 16) \
+  T (double, df, 2) \
+  T (double, df, 4) \
+  T (double, df, 8) \
+  T (_Float16, hf, 2) \
+  T (_Float16, hf, 4) \
+  T (_Float16, hf, 8) \
+  T (_Float16, hf, 16) \
+  T (_Float16, hf, 32) \
+  T (__bf16, bf, 2) \
+  T (__bf16, bf, 4) \
+  T (__bf16, bf, 8) \
+  T (__bf16, bf, 16) \
+  T (__bf16, bf, 32)
+
+#define DEF_CONCAT(TYPE, ID, N) \
+  typedef TYPE half_##ID##N \
+    __attribute__ ((vector_size (N * sizeof (TYPE)))); \
+  typedef TYPE full_##ID##N \
+    __attribute__ ((vector_size (2 * N * sizeof (TYPE)))); \
+  __attribute__ ((noipa)) full_##ID##N \
+  concat_##ID##N (half_##ID##N a, half_##ID##N b) \
+  { \
+    return __builtin_shufflevector (a, b, INDICES_##N (0), INDICES_##N (N)); \
+  }
+
+TEST_MODES (DEF_CONCAT)
+
+/* FIXME (missed optimization): Ideally each concat needs only 1 vslideup,
+   but frontend lowering of __builtin_shufflevector currently zero-pads each
+   half-width input first, resulting in 2 padding constructors and 1 concat
+   per function (35 * 3 = 105).  */
+/* { dg-final { scan-assembler-times "vslideup" 105 } } */
+/* { dg-final { scan-assembler-not "vslide1down" } } */
diff --git a/gcc/testsuite/gcc.target/riscv/rvv/autovec/vec-concat-2.c b/gcc/testsuite/gcc.target/riscv/rvv/autovec/vec-concat-2.c
new file mode 100644
index 00000000000..deab622faec
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/rvv/autovec/vec-concat-2.c
@@ -0,0 +1,16 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O3 -march=rv64gcv -mabi=lp64d -mrvv-vector-bits=scalable -mrvv-max-lmul=m1 -fno-vect-cost-model -fdump-tree-vect-details" } */
+
+__attribute__ ((noipa)) void
+test_strided_concat (int *restrict dst, const int *restrict src, long stride)
+{
+#pragma GCC unroll 1
+  for (int i = 0; i < 32; ++i)
+    {
+      dst[2 * i] = src[i * stride];
+      dst[2 * i + 1] = src[i * stride + 1];
+    }
+}
+
+/* { dg-final { scan-tree-dump-times "vectorized 1 loops" 1 "vect" } } */
+/* { dg-final { scan-assembler-times "vslideup" 1 } } */