Skip to main content

ab_riscv_interpreter/v/zvexx/
perm.rs

1//! ZveXx permutation instructions
2
3#[cfg(test)]
4mod tests;
5pub mod zvexx_perm_helpers;
6
7use crate::v::vector_registers::VectorRegistersExt;
8use crate::v::zvexx::zvexx_helpers;
9use crate::{
10    ExecutableInstruction, ExecutableInstructionCsr, ExecutableInstructionOperands, ExecutionError,
11    ProgramCounter, RegisterFile, Rs1Rs2OperandValues, Rs1Rs2Operands, VirtualMemory,
12};
13use ab_riscv_macros::instruction_execution;
14use ab_riscv_primitives::prelude::*;
15use core::fmt;
16use core::ops::ControlFlow;
17
18#[instruction_execution]
19impl<Reg> ExecutableInstructionOperands for ZveXxPermInstruction<Reg> where Reg: Register {}
20
21#[instruction_execution]
22impl<Reg, ExtState, CustomError> ExecutableInstructionCsr<ExtState, CustomError>
23    for ZveXxPermInstruction<Reg>
24where
25    Reg: Register,
26{
27}
28
29#[instruction_execution]
30impl<Reg, Regs, ExtState, Memory, PC, InstructionHandler, CustomError>
31    ExecutableInstruction<Regs, ExtState, Memory, PC, InstructionHandler, CustomError>
32    for ZveXxPermInstruction<Reg>
33where
34    Reg: Register,
35    Regs: RegisterFile<Reg>,
36    ExtState: VectorRegistersExt<Reg, CustomError>,
37    [(); SUPPORTED_ELEN_VLEN::<{ ExtState::ELEN }, { ExtState::VLEN }>]:,
38    Memory: VirtualMemory,
39    PC: ProgramCounter<Reg::Type, Memory, CustomError>,
40    CustomError: fmt::Debug,
41{
42    #[inline(always)]
43    fn execute(
44        self,
45        Rs1Rs2OperandValues {
46            rs1_value,
47            rs2_value: _,
48        }: Rs1Rs2OperandValues<<Self::Reg as Register>::Type>,
49        _regs: &mut Regs,
50        ext_state: &mut ExtState,
51        _memory: &mut Memory,
52        program_counter: &mut PC,
53        _system_instruction_handler: &mut InstructionHandler,
54    ) -> Result<
55        ControlFlow<(), (Self::Reg, <Self::Reg as Register>::Type)>,
56        ExecutionError<Reg::Type, CustomError>,
57    > {
58        match self {
59            // vmv.x.s rd, vs2
60            // Copies sign-extended element 0 of vs2 (at current SEW) to GPR rd.
61            // Requires valid vtype (needs SEW to know element width).
62            // Does not use vl or masking; always reads element 0.
63            // Resets vstart per spec §6.3.
64            Self::VmvXS { rd, vs2 } => {
65                if !ext_state.vector_instructions_allowed() {
66                    ::core::hint::cold_path();
67                    return Err(ExecutionError::IllegalInstruction {
68                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
69                    });
70                }
71                let Some(vtype) = ext_state.vtype() else {
72                    ::core::hint::cold_path();
73                    return Err(ExecutionError::IllegalInstruction {
74                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
75                    });
76                };
77                let sew = vtype.vsew();
78                // SAFETY: element 0 is always within register vs2, byte offset 0;
79                // VLEN.bytes() >= sew.bytes() for all legal vtype configurations.
80                let raw = unsafe {
81                    zvexx_perm_helpers::read_element_0_u64(ext_state.read_vregs(), vs2, sew)
82                };
83                let sign_extended = zvexx_perm_helpers::sign_extend_to_reg::<Reg>(raw, sew);
84                ext_state.mark_vs_dirty();
85                ext_state.reset_vstart();
86
87                return Ok(ControlFlow::Continue((rd, sign_extended)));
88            }
89            // vmv.s.x vd, rs1
90            // Copies scalar GPR rs1 (zero-extended / truncated to SEW) into element 0 of vd.
91            // When vl == 0, the write is suppressed but vstart is still reset.
92            // Resets vstart per spec §6.3.
93            Self::VmvSX { vd, rs1: _ } => {
94                if !ext_state.vector_instructions_allowed() {
95                    ::core::hint::cold_path();
96                    return Err(ExecutionError::IllegalInstruction {
97                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
98                    });
99                }
100                let Some(vtype) = ext_state.vtype() else {
101                    ::core::hint::cold_path();
102                    return Err(ExecutionError::IllegalInstruction {
103                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
104                    });
105                };
106                let sew = vtype.vsew();
107                let vl = ext_state.vl();
108                let vstart = ext_state.vstart();
109                // Per spec §16.1: update only when vstart < vl.
110                if vstart < vl {
111                    let scalar = rs1_value.as_i64().cast_unsigned();
112                    // SAFETY: element 0 always fits.
113                    unsafe {
114                        zvexx_perm_helpers::write_element_0_u64(
115                            ext_state.write_vregs(),
116                            vd,
117                            sew,
118                            scalar,
119                        );
120                    }
121                }
122                ext_state.mark_vs_dirty();
123                ext_state.reset_vstart();
124            }
125            // vslideup.vx vd, vs2, rs1: _, vm
126            // Slides elements of vs2 up by the scalar offset in rs1.
127            // Elements vd[0..offset] are unchanged (tail-undisturbed for those positions).
128            // Elements vd[i] for offset <= i < vl get vs2[i - offset].
129            // Per spec §16.3.1: vd must not overlap vs2.
130            Self::VslideupVx {
131                vd,
132                vs2,
133                rs1: _,
134                vm,
135            } => {
136                if !ext_state.vector_instructions_allowed() {
137                    ::core::hint::cold_path();
138                    return Err(ExecutionError::IllegalInstruction {
139                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
140                    });
141                }
142                let Some(vtype) = ext_state.vtype() else {
143                    ::core::hint::cold_path();
144                    return Err(ExecutionError::IllegalInstruction {
145                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
146                    });
147                };
148                let group_regs = vtype.vlmul().register_count();
149                zvexx_perm_helpers::check_vreg_group_alignment::<Reg, _, _, _>(
150                    program_counter,
151                    vd,
152                    group_regs,
153                )?;
154                zvexx_perm_helpers::check_vreg_group_alignment::<Reg, _, _, _>(
155                    program_counter,
156                    vs2,
157                    group_regs,
158                )?;
159                // vd must not overlap vs2
160                zvexx_perm_helpers::check_no_overlap::<Reg, _, _, _>(
161                    program_counter,
162                    vd,
163                    vs2,
164                    group_regs,
165                )?;
166                if !vm && vd == VReg::V0 {
167                    ::core::hint::cold_path();
168                    return Err(ExecutionError::IllegalInstruction {
169                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
170                    });
171                }
172                let sew = vtype.vsew();
173                let offset = rs1_value.as_u64();
174                // SAFETY: alignment and no-overlap verified above; vl <= VLMAX.
175                unsafe {
176                    zvexx_perm_helpers::execute_slideup(ext_state, vd, vs2, vm, sew, offset);
177                }
178            }
179            // vslideup.vi vd, vs2, uimm, vm
180            // Same as vslideup.vx but offset is a 5-bit unsigned immediate.
181            Self::VslideupVi { vd, vs2, uimm, vm } => {
182                if !ext_state.vector_instructions_allowed() {
183                    ::core::hint::cold_path();
184                    return Err(ExecutionError::IllegalInstruction {
185                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
186                    });
187                }
188                let Some(vtype) = ext_state.vtype() else {
189                    ::core::hint::cold_path();
190                    return Err(ExecutionError::IllegalInstruction {
191                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
192                    });
193                };
194                let group_regs = vtype.vlmul().register_count();
195                zvexx_perm_helpers::check_vreg_group_alignment::<Reg, _, _, _>(
196                    program_counter,
197                    vd,
198                    group_regs,
199                )?;
200                zvexx_perm_helpers::check_vreg_group_alignment::<Reg, _, _, _>(
201                    program_counter,
202                    vs2,
203                    group_regs,
204                )?;
205                zvexx_perm_helpers::check_no_overlap::<Reg, _, _, _>(
206                    program_counter,
207                    vd,
208                    vs2,
209                    group_regs,
210                )?;
211                if !vm && vd == VReg::V0 {
212                    ::core::hint::cold_path();
213                    return Err(ExecutionError::IllegalInstruction {
214                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
215                    });
216                }
217                let sew = vtype.vsew();
218                let offset = u64::from(uimm);
219                // SAFETY: same as VslideupVx.
220                unsafe {
221                    zvexx_perm_helpers::execute_slideup(ext_state, vd, vs2, vm, sew, offset);
222                }
223            }
224            // vslidedown.vx vd, vs2, rs1: _, vm
225            // Element vd[i] = vs2[i + offset] if i + offset < VLMAX, else 0.
226            // vd may overlap vs2 for slidedown.
227            Self::VslidedownVx {
228                vd,
229                vs2,
230                rs1: _,
231                vm,
232            } => {
233                if !ext_state.vector_instructions_allowed() {
234                    ::core::hint::cold_path();
235                    return Err(ExecutionError::IllegalInstruction {
236                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
237                    });
238                }
239                let Some(vtype) = ext_state.vtype() else {
240                    ::core::hint::cold_path();
241                    return Err(ExecutionError::IllegalInstruction {
242                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
243                    });
244                };
245                let group_regs = vtype.vlmul().register_count();
246                zvexx_perm_helpers::check_vreg_group_alignment::<Reg, _, _, _>(
247                    program_counter,
248                    vd,
249                    group_regs,
250                )?;
251                zvexx_perm_helpers::check_vreg_group_alignment::<Reg, _, _, _>(
252                    program_counter,
253                    vs2,
254                    group_regs,
255                )?;
256                if !vm && vd == VReg::V0 {
257                    ::core::hint::cold_path();
258                    return Err(ExecutionError::IllegalInstruction {
259                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
260                    });
261                }
262                let sew = vtype.vsew();
263                let vlmax = ext_state.vlmax_for_vtype(vtype);
264                let offset = rs1_value.as_u64();
265                // SAFETY: alignment verified above; vl <= VLMAX; offset clamped in helper.
266                unsafe {
267                    zvexx_perm_helpers::execute_slidedown(
268                        ext_state, vd, vs2, vm, sew, vlmax, offset,
269                    );
270                }
271            }
272            // vslidedown.vi vd, vs2, uimm, vm
273            // Same as vslidedown.vx but offset is a 5-bit unsigned immediate.
274            Self::VslidedownVi { vd, vs2, uimm, vm } => {
275                if !ext_state.vector_instructions_allowed() {
276                    ::core::hint::cold_path();
277                    return Err(ExecutionError::IllegalInstruction {
278                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
279                    });
280                }
281                let Some(vtype) = ext_state.vtype() else {
282                    ::core::hint::cold_path();
283                    return Err(ExecutionError::IllegalInstruction {
284                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
285                    });
286                };
287                let group_regs = vtype.vlmul().register_count();
288                zvexx_perm_helpers::check_vreg_group_alignment::<Reg, _, _, _>(
289                    program_counter,
290                    vd,
291                    group_regs,
292                )?;
293                zvexx_perm_helpers::check_vreg_group_alignment::<Reg, _, _, _>(
294                    program_counter,
295                    vs2,
296                    group_regs,
297                )?;
298                if !vm && vd == VReg::V0 {
299                    ::core::hint::cold_path();
300                    return Err(ExecutionError::IllegalInstruction {
301                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
302                    });
303                }
304                let sew = vtype.vsew();
305                let vlmax = ext_state.vlmax_for_vtype(vtype);
306                let offset = u64::from(uimm);
307                // SAFETY: same as VslidedownVx.
308                unsafe {
309                    zvexx_perm_helpers::execute_slidedown(
310                        ext_state, vd, vs2, vm, sew, vlmax, offset,
311                    );
312                }
313            }
314            // vslide1up.vx vd, vs2, rs1: _, vm
315            // Element 0 of vd gets the scalar value rs1 (written at SEW width).
316            // Elements vd[i] for 1 <= i < vl get vs2[i - 1].
317            // vd must not overlap vs2.
318            Self::Vslide1upVx {
319                vd,
320                vs2,
321                rs1: _,
322                vm,
323            } => {
324                if !ext_state.vector_instructions_allowed() {
325                    ::core::hint::cold_path();
326                    return Err(ExecutionError::IllegalInstruction {
327                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
328                    });
329                }
330                let Some(vtype) = ext_state.vtype() else {
331                    ::core::hint::cold_path();
332                    return Err(ExecutionError::IllegalInstruction {
333                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
334                    });
335                };
336                let group_regs = vtype.vlmul().register_count();
337                zvexx_perm_helpers::check_vreg_group_alignment::<Reg, _, _, _>(
338                    program_counter,
339                    vd,
340                    group_regs,
341                )?;
342                zvexx_perm_helpers::check_vreg_group_alignment::<Reg, _, _, _>(
343                    program_counter,
344                    vs2,
345                    group_regs,
346                )?;
347                zvexx_perm_helpers::check_no_overlap::<Reg, _, _, _>(
348                    program_counter,
349                    vd,
350                    vs2,
351                    group_regs,
352                )?;
353                if !vm && vd == VReg::V0 {
354                    ::core::hint::cold_path();
355                    return Err(ExecutionError::IllegalInstruction {
356                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
357                    });
358                }
359                let sew = vtype.vsew();
360                let scalar = rs1_value.as_i64().cast_unsigned();
361                // SAFETY: alignment and no-overlap verified; vl <= VLMAX.
362                unsafe {
363                    zvexx_perm_helpers::execute_slide1up(ext_state, vd, vs2, vm, sew, scalar);
364                }
365            }
366            // vslide1down.vx vd, vs2, rs1: _, vm
367            // Element vd[i] = vs2[i + 1] for 0 <= i < vl - 1.
368            // Element vd[vl - 1] gets the scalar value rs1.
369            // vd may overlap vs2 for slide1down.
370            Self::Vslide1downVx {
371                vd,
372                vs2,
373                rs1: _,
374                vm,
375            } => {
376                if !ext_state.vector_instructions_allowed() {
377                    ::core::hint::cold_path();
378                    return Err(ExecutionError::IllegalInstruction {
379                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
380                    });
381                }
382                let Some(vtype) = ext_state.vtype() else {
383                    ::core::hint::cold_path();
384                    return Err(ExecutionError::IllegalInstruction {
385                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
386                    });
387                };
388                let group_regs = vtype.vlmul().register_count();
389                zvexx_perm_helpers::check_vreg_group_alignment::<Reg, _, _, _>(
390                    program_counter,
391                    vd,
392                    group_regs,
393                )?;
394                zvexx_perm_helpers::check_vreg_group_alignment::<Reg, _, _, _>(
395                    program_counter,
396                    vs2,
397                    group_regs,
398                )?;
399                if !vm && vd == VReg::V0 {
400                    ::core::hint::cold_path();
401                    return Err(ExecutionError::IllegalInstruction {
402                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
403                    });
404                }
405                let sew = vtype.vsew();
406                let scalar = rs1_value.as_i64().cast_unsigned();
407                // SAFETY: alignment verified; vl <= VLMAX; overlap permitted by spec.
408                unsafe {
409                    zvexx_perm_helpers::execute_slide1down(ext_state, vd, vs2, vm, sew, scalar);
410                }
411            }
412            // vrgather.vv vd, vs2, vs1, vm
413            // vd[i] = (vs1[i] < VLMAX) ? vs2[vs1[i]] : 0
414            // vd must not overlap vs1 or vs2.
415            Self::VrgatherVv { vd, vs2, vs1, vm } => {
416                if !ext_state.vector_instructions_allowed() {
417                    ::core::hint::cold_path();
418                    return Err(ExecutionError::IllegalInstruction {
419                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
420                    });
421                }
422                let Some(vtype) = ext_state.vtype() else {
423                    ::core::hint::cold_path();
424                    return Err(ExecutionError::IllegalInstruction {
425                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
426                    });
427                };
428                let group_regs = vtype.vlmul().register_count();
429                zvexx_perm_helpers::check_vreg_group_alignment::<Reg, _, _, _>(
430                    program_counter,
431                    vd,
432                    group_regs,
433                )?;
434                zvexx_perm_helpers::check_vreg_group_alignment::<Reg, _, _, _>(
435                    program_counter,
436                    vs2,
437                    group_regs,
438                )?;
439                zvexx_perm_helpers::check_vreg_group_alignment::<Reg, _, _, _>(
440                    program_counter,
441                    vs1,
442                    group_regs,
443                )?;
444                zvexx_perm_helpers::check_no_overlap::<Reg, _, _, _>(
445                    program_counter,
446                    vd,
447                    vs2,
448                    group_regs,
449                )?;
450                zvexx_perm_helpers::check_no_overlap::<Reg, _, _, _>(
451                    program_counter,
452                    vd,
453                    vs1,
454                    group_regs,
455                )?;
456                if !vm && vd == VReg::V0 {
457                    ::core::hint::cold_path();
458                    return Err(ExecutionError::IllegalInstruction {
459                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
460                    });
461                }
462                let sew = vtype.vsew();
463                let vlmax = ext_state.vlmax_for_vtype(vtype);
464                // SAFETY: all alignment and overlap constraints verified above; vl <= VLMAX.
465                unsafe {
466                    zvexx_perm_helpers::execute_rgather_vv(ext_state, vd, vs2, vs1, vm, sew, vlmax);
467                }
468            }
469            // vrgather.vx vd, vs2, rs1: _, vm
470            // All active elements of vd get vs2[rs1] if rs1 < VLMAX, else 0.
471            // vd must not overlap vs2.
472            Self::VrgatherVx {
473                vd,
474                vs2,
475                rs1: _,
476                vm,
477            } => {
478                if !ext_state.vector_instructions_allowed() {
479                    ::core::hint::cold_path();
480                    return Err(ExecutionError::IllegalInstruction {
481                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
482                    });
483                }
484                let Some(vtype) = ext_state.vtype() else {
485                    ::core::hint::cold_path();
486                    return Err(ExecutionError::IllegalInstruction {
487                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
488                    });
489                };
490                let group_regs = vtype.vlmul().register_count();
491                zvexx_perm_helpers::check_vreg_group_alignment::<Reg, _, _, _>(
492                    program_counter,
493                    vd,
494                    group_regs,
495                )?;
496                zvexx_perm_helpers::check_vreg_group_alignment::<Reg, _, _, _>(
497                    program_counter,
498                    vs2,
499                    group_regs,
500                )?;
501                zvexx_perm_helpers::check_no_overlap::<Reg, _, _, _>(
502                    program_counter,
503                    vd,
504                    vs2,
505                    group_regs,
506                )?;
507                if !vm && vd == VReg::V0 {
508                    ::core::hint::cold_path();
509                    return Err(ExecutionError::IllegalInstruction {
510                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
511                    });
512                }
513                let sew = vtype.vsew();
514                let vlmax = ext_state.vlmax_for_vtype(vtype);
515                let index = rs1_value.as_u64();
516                // SAFETY: alignment and no-overlap verified; vl <= VLMAX.
517                unsafe {
518                    zvexx_perm_helpers::execute_rgather_scalar(
519                        ext_state, vd, vs2, vm, sew, vlmax, index,
520                    );
521                }
522            }
523            // vrgather.vi vd, vs2, uimm, vm
524            // Same as vrgather.vx but index is a 5-bit unsigned immediate.
525            Self::VrgatherVi { vd, vs2, uimm, vm } => {
526                if !ext_state.vector_instructions_allowed() {
527                    ::core::hint::cold_path();
528                    return Err(ExecutionError::IllegalInstruction {
529                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
530                    });
531                }
532                let Some(vtype) = ext_state.vtype() else {
533                    ::core::hint::cold_path();
534                    return Err(ExecutionError::IllegalInstruction {
535                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
536                    });
537                };
538                let group_regs = vtype.vlmul().register_count();
539                zvexx_perm_helpers::check_vreg_group_alignment::<Reg, _, _, _>(
540                    program_counter,
541                    vd,
542                    group_regs,
543                )?;
544                zvexx_perm_helpers::check_vreg_group_alignment::<Reg, _, _, _>(
545                    program_counter,
546                    vs2,
547                    group_regs,
548                )?;
549                zvexx_perm_helpers::check_no_overlap::<Reg, _, _, _>(
550                    program_counter,
551                    vd,
552                    vs2,
553                    group_regs,
554                )?;
555                if !vm && vd == VReg::V0 {
556                    ::core::hint::cold_path();
557                    return Err(ExecutionError::IllegalInstruction {
558                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
559                    });
560                }
561                let sew = vtype.vsew();
562                let vlmax = ext_state.vlmax_for_vtype(vtype);
563                let index = u64::from(uimm);
564                // SAFETY: same as VrgatherVx.
565                unsafe {
566                    zvexx_perm_helpers::execute_rgather_scalar(
567                        ext_state, vd, vs2, vm, sew, vlmax, index,
568                    );
569                }
570            }
571            // vrgatherei16.vv vd, vs2, vs1, vm
572            // Like vrgather.vv but vs1 always uses EEW=16 (regardless of SEW).
573            // EMUL_vs1 = (16 / SEW) * LMUL; must be in [1/8, 8] else illegal.
574            // vd must not overlap vs1 or vs2.
575            Self::Vrgatherei16Vv { vd, vs2, vs1, vm } => {
576                if !ext_state.vector_instructions_allowed() {
577                    ::core::hint::cold_path();
578                    return Err(ExecutionError::IllegalInstruction {
579                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
580                    });
581                }
582                let Some(vtype) = ext_state.vtype() else {
583                    ::core::hint::cold_path();
584                    return Err(ExecutionError::IllegalInstruction {
585                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
586                    });
587                };
588                let group_regs = vtype.vlmul().register_count();
589                zvexx_perm_helpers::check_vreg_group_alignment::<Reg, _, _, _>(
590                    program_counter,
591                    vd,
592                    group_regs,
593                )?;
594                zvexx_perm_helpers::check_vreg_group_alignment::<Reg, _, _, _>(
595                    program_counter,
596                    vs2,
597                    group_regs,
598                )?;
599                // Compute EMUL for vs1 index register (EEW=16).
600                let index_group_regs = vtype
601                    .vlmul()
602                    .index_register_count(
603                        ab_riscv_primitives::instructions::v::Eew::E16,
604                        vtype.vsew(),
605                    )
606                    .ok_or(ExecutionError::IllegalInstruction {
607                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
608                    })?;
609                zvexx_perm_helpers::check_vreg_group_alignment::<Reg, _, _, _>(
610                    program_counter,
611                    vs1,
612                    index_group_regs,
613                )?;
614                zvexx_perm_helpers::check_no_overlap::<Reg, _, _, _>(
615                    program_counter,
616                    vd,
617                    vs2,
618                    group_regs,
619                )?;
620                // vd and vs1 have different group sizes (group_regs vs index_group_regs),
621                // so the symmetric helper would use the wrong size for one of the intervals.
622                zvexx_perm_helpers::check_no_overlap_asymmetric::<Reg, _, _, _>(
623                    program_counter,
624                    vd,
625                    group_regs,
626                    vs1,
627                    index_group_regs,
628                )?;
629                if !vm && vd == VReg::V0 {
630                    ::core::hint::cold_path();
631                    return Err(ExecutionError::IllegalInstruction {
632                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
633                    });
634                }
635                let sew = vtype.vsew();
636                let vlmax = ext_state.vlmax_for_vtype(vtype);
637                // SAFETY: all alignment and overlap constraints verified; vl <= VLMAX;
638                // vs1 uses EEW=16 with computed index_group_regs.
639                unsafe {
640                    zvexx_perm_helpers::execute_rgatherei16(
641                        ext_state,
642                        vd,
643                        vs2,
644                        vs1,
645                        vm,
646                        sew,
647                        vlmax,
648                        index_group_regs,
649                    );
650                }
651            }
652            // vmerge.vvm / vmv.v.v
653            // When vm=true: vmv.v.v vd, vs1 - broadcast all active elements from vs1.
654            //   vs2 is ignored; no overlap restriction on vd/vs2.
655            // When vm=false: vmerge.vvm vd, vs2, vs1, v0
656            //   vd[i] = v0[i] ? vs1[i] : vs2[i]
657            //   vd must not overlap v0 (mask source).
658            Self::VmergeVvm { vd, vs2, vs1, vm } => {
659                if !ext_state.vector_instructions_allowed() {
660                    ::core::hint::cold_path();
661                    return Err(ExecutionError::IllegalInstruction {
662                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
663                    });
664                }
665                let Some(vtype) = ext_state.vtype() else {
666                    ::core::hint::cold_path();
667                    return Err(ExecutionError::IllegalInstruction {
668                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
669                    });
670                };
671                let group_regs = vtype.vlmul().register_count();
672                zvexx_perm_helpers::check_vreg_group_alignment::<Reg, _, _, _>(
673                    program_counter,
674                    vd,
675                    group_regs,
676                )?;
677                zvexx_perm_helpers::check_vreg_group_alignment::<Reg, _, _, _>(
678                    program_counter,
679                    vs1,
680                    group_regs,
681                )?;
682                if !vm {
683                    // vmerge: vs2 is read, vd must not overlap v0
684                    zvexx_perm_helpers::check_vreg_group_alignment::<Reg, _, _, _>(
685                        program_counter,
686                        vs2,
687                        group_regs,
688                    )?;
689                    zvexx_perm_helpers::check_no_overlap::<Reg, _, _, _>(
690                        program_counter,
691                        vd,
692                        VReg::V0,
693                        group_regs,
694                    )?;
695                }
696                let sew = vtype.vsew();
697                // SAFETY: alignment and overlap verified above; vl <= VLMAX.
698                unsafe {
699                    zvexx_perm_helpers::execute_merge_vv(ext_state, vd, vs2, vs1, vm, sew);
700                }
701            }
702            // vmerge.vxm / vmv.v.x
703            // When vm=true: vmv.v.x vd, rs1 - broadcast scalar to all active elements.
704            // When vm=false: vmerge.vxm - vd[i] = v0[i] ? rs1 : vs2[i]
705            Self::VmergeVxm {
706                vd,
707                vs2,
708                rs1: _,
709                vm,
710            } => {
711                if !ext_state.vector_instructions_allowed() {
712                    ::core::hint::cold_path();
713                    return Err(ExecutionError::IllegalInstruction {
714                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
715                    });
716                }
717                let Some(vtype) = ext_state.vtype() else {
718                    ::core::hint::cold_path();
719                    return Err(ExecutionError::IllegalInstruction {
720                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
721                    });
722                };
723                let group_regs = vtype.vlmul().register_count();
724                zvexx_perm_helpers::check_vreg_group_alignment::<Reg, _, _, _>(
725                    program_counter,
726                    vd,
727                    group_regs,
728                )?;
729                if !vm {
730                    zvexx_perm_helpers::check_vreg_group_alignment::<Reg, _, _, _>(
731                        program_counter,
732                        vs2,
733                        group_regs,
734                    )?;
735                    zvexx_perm_helpers::check_no_overlap::<Reg, _, _, _>(
736                        program_counter,
737                        vd,
738                        VReg::V0,
739                        group_regs,
740                    )?;
741                }
742                let sew = vtype.vsew();
743                let scalar = rs1_value.as_i64().cast_unsigned();
744                // SAFETY: alignment and overlap verified above; vl <= VLMAX.
745                unsafe {
746                    zvexx_perm_helpers::execute_merge_scalar(ext_state, vd, vs2, vm, sew, scalar);
747                }
748            }
749            // vmerge.vim / vmv.v.i
750            // When vm=true: vmv.v.i vd, simm5 - broadcast sign-extended immediate.
751            // When vm=false: vmerge.vim - vd[i] = v0[i] ? simm5 : vs2[i]
752            Self::VmergeVim { vd, vs2, simm5, vm } => {
753                if !ext_state.vector_instructions_allowed() {
754                    ::core::hint::cold_path();
755                    return Err(ExecutionError::IllegalInstruction {
756                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
757                    });
758                }
759                let Some(vtype) = ext_state.vtype() else {
760                    ::core::hint::cold_path();
761                    return Err(ExecutionError::IllegalInstruction {
762                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
763                    });
764                };
765                let group_regs = vtype.vlmul().register_count();
766                zvexx_perm_helpers::check_vreg_group_alignment::<Reg, _, _, _>(
767                    program_counter,
768                    vd,
769                    group_regs,
770                )?;
771                if !vm {
772                    zvexx_perm_helpers::check_vreg_group_alignment::<Reg, _, _, _>(
773                        program_counter,
774                        vs2,
775                        group_regs,
776                    )?;
777                    zvexx_perm_helpers::check_no_overlap::<Reg, _, _, _>(
778                        program_counter,
779                        vd,
780                        VReg::V0,
781                        group_regs,
782                    )?;
783                }
784                let sew = vtype.vsew();
785                // Sign-extend imm to u64 so the low sew_bytes are correct for all SEW.
786                let scalar = i64::from(simm5).cast_unsigned();
787                // SAFETY: alignment and overlap verified above; vl <= VLMAX.
788                unsafe {
789                    zvexx_perm_helpers::execute_merge_scalar(ext_state, vd, vs2, vm, sew, scalar);
790                }
791            }
792            // vcompress.vm vd, vs2, vs1
793            // Packs active elements of vs2 (where vs1 mask bit is set) sequentially into vd.
794            // Always unmasked (vm=1 in encoding); vs1 is the explicit mask operand.
795            // vd must not overlap vs1 or vs2.
796            Self::VcompressVm { vd, vs2, vs1 } => {
797                if !ext_state.vector_instructions_allowed() {
798                    ::core::hint::cold_path();
799                    return Err(ExecutionError::IllegalInstruction {
800                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
801                    });
802                }
803                let Some(vtype) = ext_state.vtype() else {
804                    ::core::hint::cold_path();
805                    return Err(ExecutionError::IllegalInstruction {
806                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
807                    });
808                };
809                // Spec §16.5: vstart must be zero.
810                if ext_state.vstart() != Vstart::ZERO {
811                    ::core::hint::cold_path();
812                    return Err(ExecutionError::IllegalInstruction {
813                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
814                    });
815                }
816                let group_regs = vtype.vlmul().register_count();
817                zvexx_perm_helpers::check_vreg_group_alignment::<Reg, _, _, _>(
818                    program_counter,
819                    vd,
820                    group_regs,
821                )?;
822                zvexx_perm_helpers::check_vreg_group_alignment::<Reg, _, _, _>(
823                    program_counter,
824                    vs2,
825                    group_regs,
826                )?;
827                // vs1 is always a single mask register (no LMUL grouping)
828                zvexx_perm_helpers::check_no_overlap::<Reg, _, _, _>(
829                    program_counter,
830                    vd,
831                    vs2,
832                    group_regs,
833                )?;
834                // vs1 is a mask register; check it doesn't overlap vd
835                zvexx_perm_helpers::check_no_overlap::<Reg, _, _, _>(
836                    program_counter,
837                    vd,
838                    vs1,
839                    ::core::num::NonZeroU8::new(1).expect("Not zero; qed"),
840                )?;
841                let sew = vtype.vsew();
842                let vl = ext_state.vl();
843                unsafe {
844                    zvexx_perm_helpers::execute_compress(ext_state, vd, vs2, vs1, vl, sew);
845                }
846            }
847            // vmv1r.v vd, vs2
848            // Whole register move: copies 1 register.
849            // No masking, no vtype/vl dependency.
850            Self::Vmv1rV { vd, vs2 } => {
851                if !ext_state.vector_instructions_allowed() {
852                    ::core::hint::cold_path();
853                    return Err(ExecutionError::IllegalInstruction {
854                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
855                    });
856                }
857                // SAFETY: both vd.to_bits() and vs2.to_bits() are always in [0, 32) by VReg
858                // invariant; copying 1 register always fits.
859                unsafe {
860                    zvexx_perm_helpers::execute_whole_reg_move::<1, _>(
861                        ext_state.write_vregs(),
862                        vd,
863                        vs2,
864                    );
865                }
866                ext_state.mark_vs_dirty();
867                ext_state.reset_vstart();
868            }
869            // vmv2r.v vd, vs2
870            // Whole register move: copies 2 registers.
871            // vd and vs2 must be aligned to 2 (checked here per spec §17.6).
872            Self::Vmv2rV { vd, vs2 } => {
873                if !ext_state.vector_instructions_allowed() {
874                    ::core::hint::cold_path();
875                    return Err(ExecutionError::IllegalInstruction {
876                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
877                    });
878                }
879                if !vd.to_bits().is_multiple_of(2) || !vs2.to_bits().is_multiple_of(2) {
880                    ::core::hint::cold_path();
881                    return Err(ExecutionError::IllegalInstruction {
882                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
883                    });
884                }
885                // SAFETY: alignment verified; 2 registers from aligned base always stay in [0, 32).
886                unsafe {
887                    zvexx_perm_helpers::execute_whole_reg_move::<2, _>(
888                        ext_state.write_vregs(),
889                        vd,
890                        vs2,
891                    );
892                }
893                ext_state.mark_vs_dirty();
894                ext_state.reset_vstart();
895            }
896            // vmv4r.v vd, vs2
897            // Whole register move: copies 4 registers.
898            Self::Vmv4rV { vd, vs2 } => {
899                if !ext_state.vector_instructions_allowed() {
900                    ::core::hint::cold_path();
901                    return Err(ExecutionError::IllegalInstruction {
902                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
903                    });
904                }
905                if !vd.to_bits().is_multiple_of(4) || !vs2.to_bits().is_multiple_of(4) {
906                    ::core::hint::cold_path();
907                    return Err(ExecutionError::IllegalInstruction {
908                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
909                    });
910                }
911                // SAFETY: alignment verified; 4 registers from aligned base always stay in [0, 32).
912                unsafe {
913                    zvexx_perm_helpers::execute_whole_reg_move::<4, _>(
914                        ext_state.write_vregs(),
915                        vd,
916                        vs2,
917                    );
918                }
919                ext_state.mark_vs_dirty();
920                ext_state.reset_vstart();
921            }
922            // vmv8r.v vd, vs2
923            // Whole register move: copies 8 registers.
924            Self::Vmv8rV { vd, vs2 } => {
925                if !ext_state.vector_instructions_allowed() {
926                    ::core::hint::cold_path();
927                    return Err(ExecutionError::IllegalInstruction {
928                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
929                    });
930                }
931                if !vd.to_bits().is_multiple_of(8) || !vs2.to_bits().is_multiple_of(8) {
932                    ::core::hint::cold_path();
933                    return Err(ExecutionError::IllegalInstruction {
934                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
935                    });
936                }
937                // SAFETY: alignment verified; 8 registers from aligned base always stay in [0, 32).
938                unsafe {
939                    zvexx_perm_helpers::execute_whole_reg_move::<8, _>(
940                        ext_state.write_vregs(),
941                        vd,
942                        vs2,
943                    );
944                }
945                ext_state.mark_vs_dirty();
946                ext_state.reset_vstart();
947            }
948        }
949
950        Ok(ControlFlow::Continue(Default::default()))
951    }
952}