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