Skip to main content

ab_riscv_interpreter/v/zvexx/perm/
zvexx_perm_helpers.rs

1//! Opaque helpers for ZveXx extension
2
3use crate::v::vector_registers::{VLENB_USIZE, VectorRegisterFile, VectorRegistersExt};
4pub use crate::v::zvexx::arith::zvexx_arith_helpers::check_vreg_group_alignment;
5use crate::v::zvexx::arith::zvexx_arith_helpers::{read_element_u64, write_element_u64};
6use crate::v::zvexx::load::zvexx_load_helpers::{mask_bit, snapshot_mask};
7use crate::v::zvexx::zvexx_helpers::INSTRUCTION_SIZE;
8use crate::{ExecutionError, PackedAddress, ProgramCounter};
9use ab_riscv_primitives::prelude::*;
10use core::hint::cold_path;
11use core::num::NonZeroU8;
12
13/// Check that register groups `[a, a+count)` and `[b, b+count)` do not overlap.
14///
15/// Both groups must have the same size `count`. For groups of different sizes use
16/// [`check_no_overlap_asymmetric`].
17#[inline(always)]
18#[doc(hidden)]
19#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
20pub fn check_no_overlap<Reg, Memory, PC>(
21    program_counter: &PC,
22    a: VReg,
23    b: VReg,
24    count: NonZeroU8,
25) -> Result<(), ExecutionError<Reg::Type>>
26where
27    Reg: Register,
28    PC: ProgramCounter<Reg::Type, Memory>,
29{
30    let a_start = u16::from(a.to_bits());
31    let b_start = u16::from(b.to_bits());
32    let count = u16::from(count.get());
33    // Intervals [a_start, a_start+count) and [b_start, b_start+count) overlap iff
34    // each starts before the other ends. Arithmetic is widened to u16 to avoid u8 overflow
35    // (e.g., b_start=30 + count=8 = 38, which overflows u8).
36    if a_start < b_start + count && b_start < a_start + count {
37        cold_path();
38        return Err(ExecutionError::IllegalInstruction {
39            address: PackedAddress::new(program_counter.old_pc(INSTRUCTION_SIZE)),
40        });
41    }
42    Ok(())
43}
44
45/// Check that register group `[a, a+a_count)` does not overlap `[b, b+b_count)`.
46///
47/// Unlike [`check_no_overlap`], the two groups are allowed to have different sizes.
48/// Used for `vrgatherei16.vv` where vd/vs2 use LMUL-derived `group_regs` and vs1
49/// uses EEW=16-derived `index_group_regs`.
50#[inline(always)]
51#[doc(hidden)]
52#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
53pub fn check_no_overlap_asymmetric<Reg, Memory, PC>(
54    program_counter: &PC,
55    a: VReg,
56    a_count: NonZeroU8,
57    b: VReg,
58    b_count: NonZeroU8,
59) -> Result<(), ExecutionError<Reg::Type>>
60where
61    Reg: Register,
62    PC: ProgramCounter<Reg::Type, Memory>,
63{
64    let a_start = u16::from(a.to_bits());
65    let b_start = u16::from(b.to_bits());
66    let a_count = u16::from(a_count.get());
67    let b_count = u16::from(b_count.get());
68    // Intervals [a_start, a_start+a_count) and [b_start, b_start+b_count) overlap iff
69    // each starts before the other ends.
70    if a_start < b_start + b_count && b_start < a_start + a_count {
71        cold_path();
72        return Err(ExecutionError::IllegalInstruction {
73            address: PackedAddress::new(program_counter.old_pc(INSTRUCTION_SIZE)),
74        });
75    }
76    Ok(())
77}
78
79/// Read element 0 of register `base_reg` as `u64`, zero-extended.
80///
81/// # Safety
82/// `sew.bytes() <= VLEN.bytes()`
83#[inline(always)]
84#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
85pub unsafe fn read_element_0_u64<const VLEN: Vlen>(
86    vregs: &VectorRegisterFile<VLEN>,
87    base_reg: VReg,
88    sew: Vsew,
89) -> u64 {
90    let sew_bytes = usize::from(sew.bytes_width());
91    let reg = vregs.get(base_reg);
92    let mut buf = [0u8; 8];
93    // SAFETY: `sew_bytes <= VLEN.bytes()` for all legal vtype; `sew_bytes <= 8`
94    unsafe {
95        buf.get_unchecked_mut(..sew_bytes)
96            .copy_from_slice(reg.get_unchecked(..sew_bytes));
97    }
98    u64::from_le_bytes(buf)
99}
100
101/// Write element 0 of register `base_reg` from the low `sew_bytes` of `value`.
102///
103/// # Safety
104/// `sew.bytes() <= VLEN.bytes()`
105#[inline(always)]
106#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
107pub unsafe fn write_element_0_u64<const VLEN: Vlen>(
108    vregs: &mut VectorRegisterFile<VLEN>,
109    base_reg: VReg,
110    sew: Vsew,
111    value: u64,
112) {
113    let sew_bytes = usize::from(sew.bytes_width());
114    let buf = value.to_le_bytes();
115    let reg = vregs.get_mut(base_reg);
116    // SAFETY: `sew_bytes <= VLEN.bytes()`; `sew_bytes <= 8`
117    unsafe {
118        reg.get_unchecked_mut(..sew_bytes)
119            .copy_from_slice(buf.get_unchecked(..sew_bytes));
120    }
121}
122
123/// Sign-extend the low `sew.bits_width()` of `val` to the register type width.
124///
125/// The arithmetic is performed entirely in 64-bit signed integer space: we shift the SEW-wide
126/// value left to place its sign bit at bit 63, then arithmetic-right-shift back to propagate it.
127/// The resulting `u64` is then narrowed to `Reg::Type` (32 or 64 bits) by combining via
128/// `From<u32>` - the only integer conversion in the `Register::Type` trait bounds.
129///
130/// For RV32 (`Reg::XLEN == 32`) the low 32 bits are already the correct sign-extended result
131/// because the arithmetic shift propagates the sign across all 64 bits and then we discard the
132/// upper half.
133///
134/// For RV64 (`Reg::XLEN == 64`) we must preserve all 64 bits. Since `Reg::Type: From<u32>` and
135/// `Reg::Type: Shl<u8>`, we reconstruct the 64-bit value by OR-ing two 32-bit halves shifted
136/// into position.
137#[inline(always)]
138#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
139pub fn sign_extend_to_reg<Reg>(val: u64, sew: Vsew) -> Reg::Type
140where
141    Reg: Register,
142{
143    let sew_bits = u32::from(sew.bits_width());
144    // `shift` is in [0, 64). When sew_bits == 64, shift == 0 and the value is unchanged.
145    let shift = u64::BITS - sew_bits;
146    // Cast to i64 so the right-shift is arithmetic (sign-extending).
147    let sign_extended = (val.cast_signed() << shift) >> shift;
148    let raw = sign_extended.cast_unsigned();
149    if Reg::XLEN == u64::BITS as u8 {
150        // RV64: preserve all 64 bits by splitting into two u32 halves.
151        let lo = Reg::Type::from(raw as u32);
152        let hi = Reg::Type::from((raw >> u32::BITS) as u32);
153        lo | (hi << 32u8)
154    } else {
155        // RV32: the low 32 bits are the correctly truncated result.
156        Reg::Type::from(raw as u32)
157    }
158}
159
160/// Execute a vslideup operation.
161///
162/// Elements `vstart..min(offset, vl)` in vd are unchanged.
163/// Elements `max(vstart, offset)..vl` where mask is active get vs2[i - offset].
164///
165/// # Safety
166/// - `vd` and `vs2` are validly aligned and non-overlapping (verified by caller).
167/// - `vl <= group_regs * VLEN.bytes() / sew_bytes`.
168/// - When `vm=false`: `vd.to_bits() != 0`.
169#[inline(always)]
170#[doc(hidden)]
171#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
172pub unsafe fn execute_slideup<Reg, Env>(
173    env: &mut Env,
174    vd: VReg,
175    vs2: VReg,
176    vm: bool,
177    sew: Vsew,
178    offset: u64,
179) where
180    Reg: Register,
181    Env: VectorRegistersExt<Reg>,
182    [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
183{
184    let vl = env.vl();
185    let vstart = env.vstart();
186    // SAFETY: `vl <= VLEN`
187    let mask_buf = unsafe { snapshot_mask(env.read_vregs(), vm, vl) };
188    // Per spec ยง16.3.1: elements 0..offset are never written (vd keeps its value).
189    // The active range starts at max(vstart, offset).
190    for i in vstart
191        .max(Vstart::from(offset.saturating_truncate::<u16>()))
192        .range_to(vl)
193    {
194        if !mask_bit(&mask_buf, i) {
195            continue;
196        }
197        let src_idx = i - offset.saturating_truncate::<u16>();
198        // SAFETY: src_idx < vl <= group_regs * elems_per_reg, so source element is in range
199        let val = unsafe { read_element_u64(env.read_vregs(), vs2, src_idx, sew) };
200        // SAFETY: i < vl <= group_regs * elems_per_reg, so dest element is in range
201        unsafe {
202            write_element_u64(env.write_vregs(), vd, i, sew, val);
203        }
204    }
205    env.mark_vs_dirty();
206    env.reset_vstart();
207}
208
209/// Execute a vslidedown operation.
210///
211/// Element `vd[i] = vs2[i + offset]` if `i + offset < vlmax`, else `0`.
212///
213/// # Safety
214/// - `vd` and `vs2` are validly aligned (verified by caller); overlap is permitted.
215/// - `vl <= vlmax`.
216/// - When `vm=false`: `vd.to_bits() != 0`.
217#[inline(always)]
218#[doc(hidden)]
219#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
220pub unsafe fn execute_slidedown<Reg, Env>(
221    env: &mut Env,
222    vd: VReg,
223    vs2: VReg,
224    vm: bool,
225    sew: Vsew,
226    vlmax: Vl,
227    offset: u64,
228) where
229    Reg: Register,
230    Env: VectorRegistersExt<Reg>,
231    [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
232{
233    let vl = env.vl();
234    let vstart = env.vstart();
235    // SAFETY: `vl <= VLEN`
236    let mask_buf = unsafe { snapshot_mask(env.read_vregs(), vm, vl) };
237    for i in vstart.range_to(vl) {
238        if !mask_bit(&mask_buf, i) {
239            continue;
240        }
241        // Use checked_add to guard against offset being so large that i + offset overflows u64.
242        // Any value that wraps past u64::MAX is trivially >= vlmax, so the spec requires vd[i]=0.
243        let val = if let Some(src_idx) = u64::from(i).checked_add(offset)
244            && src_idx < u64::from(vlmax)
245        {
246            // SAFETY: src_idx < vlmax <= group_regs * elems_per_reg, so element is in range
247            unsafe { read_element_u64(env.read_vregs(), vs2, src_idx as u16, sew) }
248        } else {
249            0
250        };
251        // SAFETY: i < vl <= vlmax <= group_regs * elems_per_reg
252        unsafe {
253            write_element_u64(env.write_vregs(), vd, i, sew, val);
254        }
255    }
256    env.mark_vs_dirty();
257    env.reset_vstart();
258}
259
260/// Execute a vslide1up operation.
261///
262/// Element 0 of vd gets `scalar` (when active and vl > 0).
263/// Element `i` for `1 <= i < vl` gets `vs2[i - 1]`.
264/// vd must not overlap vs2.
265///
266/// # Safety
267/// - `vd` and `vs2` are validly aligned and non-overlapping (verified by caller).
268/// - `vl <= group_regs * VLEN.bytes() / sew_bytes`.
269/// - When `vm=false`: `vd.to_bits() != 0`.
270#[inline(always)]
271#[doc(hidden)]
272#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
273pub unsafe fn execute_slide1up<Reg, Env>(
274    env: &mut Env,
275    vd: VReg,
276    vs2: VReg,
277    vm: bool,
278    sew: Vsew,
279    scalar: u64,
280) where
281    Reg: Register,
282    Env: VectorRegistersExt<Reg>,
283    [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
284{
285    let vl = env.vl();
286    let vstart = env.vstart();
287    // SAFETY: `vl <= VLEN`
288    let mask_buf = unsafe { snapshot_mask(env.read_vregs(), vm, vl) };
289    for i in vstart.range_to(vl) {
290        if !mask_bit(&mask_buf, i) {
291            continue;
292        }
293        let val = if i == 0 {
294            scalar
295        } else {
296            // SAFETY: i - 1 < vl <= group_regs * elems_per_reg
297            unsafe { read_element_u64(env.read_vregs(), vs2, i - 1, sew) }
298        };
299        // SAFETY: i < vl <= group_regs * elems_per_reg
300        unsafe {
301            write_element_u64(env.write_vregs(), vd, i, sew, val);
302        }
303    }
304    env.mark_vs_dirty();
305    env.reset_vstart();
306}
307
308/// Execute a vslide1down operation.
309///
310/// Element `vd[i] = vs2[i + 1]` for `i < vl - 1`; element `vd[vl - 1]` gets `scalar`.
311///
312/// Overlap between `vd` and `vs2` is permitted by the spec. When they share the same register
313/// group base (exact overlap), ascending iteration is still correct: each write goes to byte range
314/// `[i*sew, (i+1)*sew)` while the subsequent read comes from `[(i+1)*sew, (i+2)*sew)`. These
315/// ranges are adjacent and non-overlapping, so writing element `i` never corrupts the source bytes
316/// of element `i+1`.
317///
318/// # Safety
319/// - `vd` and `vs2` are validly aligned (verified by caller); overlap is permitted.
320/// - `vl <= group_regs * VLEN.bytes() / sew_bytes`.
321/// - When `vm=false`: `vd.to_bits() != 0`.
322#[inline(always)]
323#[doc(hidden)]
324#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
325pub unsafe fn execute_slide1down<Reg, Env>(
326    env: &mut Env,
327    vd: VReg,
328    vs2: VReg,
329    vm: bool,
330    sew: Vsew,
331    scalar: u64,
332) where
333    Reg: Register,
334    Env: VectorRegistersExt<Reg>,
335    [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
336{
337    let vl = env.vl();
338    let vstart = env.vstart();
339    // SAFETY: `vl <= VLEN`
340    let mask_buf = unsafe { snapshot_mask(env.read_vregs(), vm, vl) };
341    let range = vstart.range_to(vl);
342    for i in range.clone() {
343        if !mask_bit(&mask_buf, i) {
344            continue;
345        }
346        let val = if i < *range.end() {
347            // SAFETY: i + 1 < vl <= group_regs * elems_per_reg
348            unsafe { read_element_u64(env.read_vregs(), vs2, i + 1, sew) }
349        } else {
350            scalar
351        };
352        // SAFETY: i < vl <= group_regs * elems_per_reg
353        unsafe {
354            write_element_u64(env.write_vregs(), vd, i, sew, val);
355        }
356    }
357    env.mark_vs_dirty();
358    env.reset_vstart();
359}
360
361/// Execute vrgather.vv: `vd[i] = (vs1[i] < vlmax) ? vs2[vs1[i]] : 0`.
362///
363/// # Safety
364/// - `vd`, `vs2`, and `vs1` are validly aligned and mutually non-overlapping (verified by caller).
365/// - `vl <= vlmax`.
366/// - When `vm=false`: `vd.to_bits() != 0`.
367#[inline(always)]
368#[doc(hidden)]
369#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
370pub unsafe fn execute_rgather_vv<Reg, Env>(
371    env: &mut Env,
372    vd: VReg,
373    vs2: VReg,
374    vs1: VReg,
375    vm: bool,
376    sew: Vsew,
377    vlmax: Vl,
378) where
379    Reg: Register,
380    Env: VectorRegistersExt<Reg>,
381    [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
382{
383    let vl = env.vl();
384    let vstart = env.vstart();
385    // SAFETY: `vl <= VLEN`
386    let mask_buf = unsafe { snapshot_mask(env.read_vregs(), vm, vl) };
387    for i in vstart.range_to(vl) {
388        if !mask_bit(&mask_buf, i) {
389            continue;
390        }
391        // SAFETY: i < vl <= group_regs * elems_per_reg for vs1
392        let index = unsafe { read_element_u64(env.read_vregs(), vs1, i, sew) };
393        let val = if index < u64::from(vlmax) {
394            // SAFETY: index < vlmax <= group_regs * elems_per_reg for vs2
395            unsafe { read_element_u64(env.read_vregs(), vs2, index as u16, sew) }
396        } else {
397            0u64
398        };
399        // SAFETY: i < vl <= group_regs * elems_per_reg for vd
400        unsafe {
401            write_element_u64(env.write_vregs(), vd, i, sew, val);
402        }
403    }
404    env.mark_vs_dirty();
405    env.reset_vstart();
406}
407
408/// Execute vrgather.vx / vrgather.vi: all active elements get `vs2[index]` or `0`.
409///
410/// # Safety
411/// - `vd` and `vs2` are validly aligned and non-overlapping (verified by caller).
412/// - `vl <= vlmax`.
413/// - When `vm=false`: `vd.to_bits() != 0`.
414#[inline(always)]
415#[doc(hidden)]
416#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
417pub unsafe fn execute_rgather_scalar<Reg, Env>(
418    env: &mut Env,
419    vd: VReg,
420    vs2: VReg,
421    vm: bool,
422    sew: Vsew,
423    vlmax: Vl,
424    index: u64,
425) where
426    Reg: Register,
427    Env: VectorRegistersExt<Reg>,
428    [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
429{
430    let vl = env.vl();
431    let vstart = env.vstart();
432    // SAFETY: `vl <= VLEN`
433    let mask_buf = unsafe { snapshot_mask(env.read_vregs(), vm, vl) };
434    // Pre-compute the gathered value; it's the same for all elements.
435    let val = if index < u64::from(vlmax) {
436        // SAFETY: index < vlmax <= group_regs * elems_per_reg for vs2
437        unsafe { read_element_u64(env.read_vregs(), vs2, index as u16, sew) }
438    } else {
439        0u64
440    };
441    for i in vstart.range_to(vl) {
442        if !mask_bit(&mask_buf, i) {
443            continue;
444        }
445        // SAFETY: i < vl <= group_regs * elems_per_reg for vd
446        unsafe {
447            write_element_u64(env.write_vregs(), vd, i, sew, val);
448        }
449    }
450    env.mark_vs_dirty();
451    env.reset_vstart();
452}
453
454/// Execute vrgatherei16.vv: `vd[i] = (vs1_16[i] < vlmax) ? vs2[vs1_16[i]] : 0`.
455///
456/// `vs1` always uses EEW=16 regardless of SEW. `vl` must not exceed the index register group
457/// capacity, i.e. `vl <= index_group_regs * VLEN.bytes() / 2` (VLEN.bytes() / 2 = elems per
458/// register at EEW=16).
459///
460/// # Safety
461/// - `vd`, `vs2`, and `vs1` are validly aligned and mutually non-overlapping (verified by caller).
462/// - `vl <= vlmax` (for the data register group) AND `vl <= index_group_regs * VLEN.bytes() / 2`
463///   (for the index register group).
464/// - When `vm=false`: `vd.to_bits() != 0`.
465#[inline(always)]
466#[expect(clippy::too_many_arguments, reason = "Internal API")]
467#[doc(hidden)]
468#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
469pub unsafe fn execute_rgatherei16<Reg, Env>(
470    env: &mut Env,
471    vd: VReg,
472    vs2: VReg,
473    vs1: VReg,
474    vm: bool,
475    sew: Vsew,
476    vlmax: Vl,
477    index_group_regs: NonZeroU8,
478) where
479    Reg: Register,
480    Env: VectorRegistersExt<Reg>,
481    [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
482{
483    let index_group_regs = index_group_regs.get();
484    let vl = env.vl();
485    let vstart = env.vstart();
486    // Maximum number of EEW=16 elements the index register group can hold.
487    // Each register holds VLEN.bytes() / 2 elements at EEW=16.
488    let index_capacity = u32::from(index_group_regs) * (Env::VLEN.bytes() / 2);
489    // `vl` must not exceed either the data VLMAX or the index register group capacity.
490    // Both bounds are guaranteed by the caller; this debug assertion catches misuse early.
491    debug_assert!(
492        vl <= vlmax && u32::from(vl) <= index_capacity,
493        "vl={vl} exceeds vlmax={vlmax} or index_capacity={index_capacity}"
494    );
495    // SAFETY: `vl <= VLEN`
496    let mask_buf = unsafe { snapshot_mask(env.read_vregs(), vm, vl) };
497    for i in vstart.range_to(vl) {
498        if !mask_bit(&mask_buf, i) {
499            continue;
500        }
501        // Read 16-bit index from vs1; EEW=16 always.
502        // SAFETY: i < vl <= index_capacity = index_group_regs * (VLEN.bytes() / 2), so element i
503        // fits within the index register group.
504        let index = unsafe { read_element_u64(env.read_vregs(), vs1, i, Vsew::E16) };
505        let val = if index < u64::from(vlmax) {
506            // SAFETY: index < vlmax <= group_regs * elems_per_reg for vs2
507            unsafe { read_element_u64(env.read_vregs(), vs2, index as u16, sew) }
508        } else {
509            0u64
510        };
511        // SAFETY: i < vl <= group_regs * elems_per_reg for vd
512        unsafe {
513            write_element_u64(env.write_vregs(), vd, i, sew, val);
514        }
515    }
516    env.mark_vs_dirty();
517    env.reset_vstart();
518}
519
520/// Execute vmerge.vvm / vmv.v.v.
521///
522/// When `vm=true` (vmv.v.v): all active elements `vstart..vl` get `vs1[i]`; vs2 unused.
523/// When `vm=false` (vmerge.vvm): active elements where `v0[i]=1` get `vs1[i]`,
524/// inactive elements get `vs2[i]`.
525///
526/// # Safety
527/// - `vd` and `vs1` are validly aligned (verified by caller).
528/// - When `vm=false`: `vs2` is validly aligned and `vd` does not overlap v0 (verified by caller).
529/// - `vl <= group_regs * VLEN.bytes() / sew_bytes`.
530#[inline(always)]
531#[doc(hidden)]
532#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
533pub unsafe fn execute_merge_vv<Reg, Env>(
534    env: &mut Env,
535    vd: VReg,
536    vs2: VReg,
537    vs1: VReg,
538    vm: bool,
539    sew: Vsew,
540) where
541    Reg: Register,
542    Env: VectorRegistersExt<Reg>,
543    [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
544{
545    let vl = env.vl();
546    let vstart = env.vstart();
547    // SAFETY: `vl <= VLEN`
548    // For vmv.v.v (vm=true) the mask is all-ones so snapshot_mask is still valid.
549    let mask_buf = unsafe { snapshot_mask(env.read_vregs(), vm, vl) };
550    for i in vstart.range_to(vl) {
551        let mask_set = mask_bit(&mask_buf, i);
552        let val = if mask_set {
553            // SAFETY: i < vl <= group_regs * elems_per_reg for vs1
554            unsafe { read_element_u64(env.read_vregs(), vs1, i, sew) }
555        } else {
556            // mask_set=false only reachable when vm=false (vmerge path).
557            // SAFETY: i < vl <= group_regs * elems_per_reg for vs2
558            unsafe { read_element_u64(env.read_vregs(), vs2, i, sew) }
559        };
560        // SAFETY: i < vl <= group_regs * elems_per_reg for vd
561        unsafe {
562            write_element_u64(env.write_vregs(), vd, i, sew, val);
563        }
564    }
565    env.mark_vs_dirty();
566    env.reset_vstart();
567}
568
569/// Execute vmerge.vxm / vmerge.vim / vmv.v.x / vmv.v.i.
570///
571/// When `vm=true`: all active elements `vstart..vl` get `scalar`; vs2 unused.
572/// When `vm=false`: active elements where `v0[i]=1` get `scalar`,
573/// inactive elements get `vs2[i]`.
574///
575/// # Safety
576/// - `vd` is validly aligned (verified by caller).
577/// - When `vm=false`: `vs2` is validly aligned and `vd` does not overlap v0 (verified by caller).
578/// - `vl <= group_regs * VLEN.bytes() / sew_bytes`.
579#[inline(always)]
580#[doc(hidden)]
581#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
582pub unsafe fn execute_merge_scalar<Reg, Env>(
583    env: &mut Env,
584    vd: VReg,
585    vs2: VReg,
586    vm: bool,
587    sew: Vsew,
588    scalar: u64,
589) where
590    Reg: Register,
591    Env: VectorRegistersExt<Reg>,
592    [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
593{
594    let vl = env.vl();
595    let vstart = env.vstart();
596    // SAFETY: `vl <= VLEN`
597    let mask_buf = unsafe { snapshot_mask(env.read_vregs(), vm, vl) };
598
599    for i in vstart.range_to(vl) {
600        let val = if mask_bit(&mask_buf, i) {
601            scalar
602        } else {
603            // SAFETY: i < vl <= group_regs * elems_per_reg for vs2
604            unsafe { read_element_u64(env.read_vregs(), vs2, i, sew) }
605        };
606        // SAFETY: i < vl <= group_regs * elems_per_reg for vd
607        unsafe {
608            write_element_u64(env.write_vregs(), vd, i, sew, val);
609        }
610    }
611    env.mark_vs_dirty();
612    env.reset_vstart();
613}
614
615/// Execute vcompress.vm: pack active elements of vs2 (under vs1 mask) sequentially into vd.
616///
617/// `vs1` is treated as an explicit mask register (single register, not LMUL-grouped).
618/// The output write index increments only for elements where `vs1[i]` is set.
619/// vd must not overlap vs1 or vs2.
620///
621/// # Safety
622/// - `vd`, `vs2` are validly aligned and non-overlapping (verified by caller).
623/// - `vs1` does not overlap `vd` (verified by caller).
624/// - `vl <= VLMAX`.
625#[inline(always)]
626#[doc(hidden)]
627#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
628pub unsafe fn execute_compress<Reg, Env>(
629    env: &mut Env,
630    vd: VReg,
631    vs2: VReg,
632    vs1: VReg,
633    vl: Vl,
634    sew: Vsew,
635) where
636    Reg: Register,
637    Env: VectorRegistersExt<Reg>,
638    [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
639{
640    let mask_bytes = usize::from(vl.bytes());
641    let vreg = env.read_vregs();
642    let mut vs1_buf = [0u8; VLENB_USIZE::<{ Env::VLEN }>];
643    // SAFETY: mask_bytes <= VLEN.bytes() since vl <= VLEN; vs1_base < 32
644    unsafe {
645        vs1_buf
646            .get_unchecked_mut(..mask_bytes)
647            .copy_from_slice(vreg.get(vs1).get_unchecked(..mask_bytes));
648    }
649    let mut out_idx = 0;
650    for i in Vstart::ZERO.range_to(vl) {
651        if !mask_bit(&vs1_buf, i) {
652            continue;
653        }
654        // SAFETY: i < vl <= group_regs * elems_per_reg
655        let val = unsafe { read_element_u64(env.read_vregs(), vs2, i, sew) };
656        // SAFETY: out_idx <= popcount(vs1[0..vl)) <= vl
657        unsafe {
658            write_element_u64(env.write_vregs(), vd, out_idx, sew, val);
659        }
660        out_idx += 1;
661    }
662    env.mark_vs_dirty();
663    env.reset_vstart();
664}
665
666/// Copy `COUNT` whole vector registers from `src_base` to `dst_base`.
667///
668/// No masking, no vtype dependency. Uses snapshot semantics: all source registers are read into
669/// a stack buffer before any destination registers are written, giving correct memmove-style
670/// behaviour for all overlap patterns (including partial overlap such as src=V0, dst=V1, count=2).
671///
672/// # Safety
673/// - `dst_base + COUNT <= 32` and `src_base + COUNT <= 32` (verified by caller via alignment
674///   checks).
675/// - `dst_base % COUNT == 0` and `src_base % COUNT == 0` (verified by caller).
676#[inline(always)]
677#[doc(hidden)]
678#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
679pub unsafe fn execute_whole_reg_move<const COUNT: usize, const VLEN: Vlen>(
680    vregs: &mut VectorRegisterFile<VLEN>,
681    dst_base: VReg,
682    src_base: VReg,
683) {
684    // Snapshot all source registers before writing any destination registers.
685    // This is correct for all overlap patterns without direction-dependent logic.
686    let mut tmp = [[0u8; _]; COUNT];
687    for (k, item) in tmp.iter_mut().enumerate() {
688        // SAFETY: Guaranteed by function contract
689        let src = unsafe { VReg::from_bits(src_base.to_bits() + k as u8).unwrap_unchecked() };
690        *item = *vregs.get(src);
691    }
692    for (k, item) in tmp.iter().enumerate() {
693        // SAFETY: Guaranteed by function contract
694        let dst = unsafe { VReg::from_bits(dst_base.to_bits() + k as u8).unwrap_unchecked() };
695        *vregs.get_mut(dst) = *item;
696    }
697}