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