Skip to main content

ab_riscv_interpreter/v/zvexx/widen_narrow/
zvexx_widen_narrow_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::{OpSrc, check_vreg_group_alignment};
5use crate::v::zvexx::zvexx_helpers::INSTRUCTION_SIZE;
6use crate::{ExecutionError, PackedAddress, ProgramCounter};
7use ab_riscv_primitives::instructions::v::Vsew;
8use ab_riscv_primitives::prelude::*;
9use core::hint::cold_path;
10use core::num::NonZeroU8;
11
12/// Check that a widening destination `vd` is aligned to `wide_group_regs` and fits within
13/// `[0,32)`, without any source overlap check
14#[inline(always)]
15#[doc(hidden)]
16#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
17pub fn check_vd_widen_no_src_check<Reg, Memory, PC>(
18    program_counter: &PC,
19    vd: VReg,
20    wide_group_regs: NonZeroU8,
21) -> Result<(), ExecutionError<Reg::Type>>
22where
23    Reg: Register,
24    PC: ProgramCounter<Reg::Type, Memory>,
25{
26    let wide_group_regs = wide_group_regs.get();
27    let vd_idx = vd.to_bits();
28    if !vd_idx.is_multiple_of(wide_group_regs) || vd_idx + wide_group_regs > 32 {
29        cold_path();
30        return Err(ExecutionError::IllegalInstruction {
31            address: PackedAddress::new(program_counter.old_pc(INSTRUCTION_SIZE)),
32        });
33    }
34    Ok(())
35}
36
37/// Check that an extension source `vs2` is aligned to `src_group_regs`, fits in `[0,32)`, and only
38/// overlaps `vd` (which occupies `group_regs` registers) in a manner permitted by the spec.
39///
40/// Per the vector spec §5.2, the destination EEW (SEW) of an extension is greater than the source
41/// EEW (SEW/factor), so the destination may overlap the source only when the source EMUL is at
42/// least 1 and the overlap is in the highest-numbered part of the destination register group (e.g.
43/// `vzext.vf4 v0, v6` with LMUL=8, where the narrow source `{v6,v7}` aliases the high registers of
44/// the wide `{v0..v7}` destination). Any other overlap is illegal.
45#[inline(always)]
46#[doc(hidden)]
47#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
48pub fn check_vs_ext_alignment<Reg, Memory, PC>(
49    program_counter: &PC,
50    vs2: VReg,
51    src_group_regs: NonZeroU8,
52    vd: VReg,
53    group_regs: NonZeroU8,
54) -> Result<(), ExecutionError<Reg::Type>>
55where
56    Reg: Register,
57    PC: ProgramCounter<Reg::Type, Memory>,
58{
59    let src_group_regs = src_group_regs.get();
60    let group_regs = group_regs.get();
61    let vs2_idx = vs2.to_bits();
62    if !vs2_idx.is_multiple_of(src_group_regs) || vs2_idx + src_group_regs > 32 {
63        cold_path();
64        return Err(ExecutionError::IllegalInstruction {
65            address: PackedAddress::new(program_counter.old_pc(INSTRUCTION_SIZE)),
66        });
67    }
68    // The wide destination (group_regs) may overlap the narrow source (src_group_regs) only in the
69    // highest-numbered part of the destination group, and only when the source EMUL >= 1.
70    if widen_src_overlap_illegal(vd.to_bits(), group_regs, vs2_idx, src_group_regs) {
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/// Check that a widening destination `vd` is aligned to `wide_group_regs`, fits within `[0, 32)`,
80/// and only overlaps the `group_regs`-register narrow source(s) starting at `vs_a`/`vs_b` in a
81/// manner permitted by the spec.
82///
83/// `wide_group_regs` is the pre-computed register count for the wide EMUL (2*LMUL), obtained via
84/// `Vlmul::index_register_count(wide_eew, sew)`. `group_regs` is the narrow LMUL register count.
85///
86/// Per the vector spec §5.2, a destination whose EEW (2*SEW) is greater than a source's EEW (SEW)
87/// may overlap that source only when the source EMUL is at least 1 and the overlap is in the
88/// highest-numbered part of the destination register group (e.g. `vwsubu.wv v2, v14, v3` with
89/// LMUL=1, where the narrow `v3` aliases the high register of the wide `{v2, v3}` destination).
90/// Any other overlap is illegal.
91#[inline(always)]
92#[doc(hidden)]
93#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
94pub fn check_vd_widen_alignment<Reg, Memory, PC>(
95    program_counter: &PC,
96    vd: VReg,
97    vs_a: VReg,
98    vs_b_opt: Option<VReg>,
99    group_regs: NonZeroU8,
100    wide_group_regs: NonZeroU8,
101) -> Result<(), ExecutionError<Reg::Type>>
102where
103    Reg: Register,
104    PC: ProgramCounter<Reg::Type, Memory>,
105{
106    let wide_group_regs = wide_group_regs.get();
107    let group_regs = group_regs.get();
108    let vd_idx = vd.to_bits();
109    if !vd_idx.is_multiple_of(wide_group_regs) || vd_idx + wide_group_regs > 32 {
110        cold_path();
111        return Err(ExecutionError::IllegalInstruction {
112            address: PackedAddress::new(program_counter.old_pc(INSTRUCTION_SIZE)),
113        });
114    }
115    if widen_src_overlap_illegal(vd_idx, wide_group_regs, vs_a.to_bits(), group_regs) {
116        cold_path();
117        return Err(ExecutionError::IllegalInstruction {
118            address: PackedAddress::new(program_counter.old_pc(INSTRUCTION_SIZE)),
119        });
120    }
121    if let Some(vs_b) = vs_b_opt
122        && widen_src_overlap_illegal(vd_idx, wide_group_regs, vs_b.to_bits(), group_regs)
123    {
124        cold_path();
125        return Err(ExecutionError::IllegalInstruction {
126            address: PackedAddress::new(program_counter.old_pc(INSTRUCTION_SIZE)),
127        });
128    }
129    Ok(())
130}
131
132/// Returns `true` when a narrow source group of `group_regs` registers starting at `vs_idx`
133/// overlaps the wide destination group (`wide_group_regs` registers starting at `vd_idx`) in a way
134/// that is *not* permitted by the spec.
135///
136/// Overlap is only legal when the source EMUL is at least 1 - which, on widening, is exactly when
137/// the destination register count strictly exceeds the narrow source count (for fractional LMUL
138/// both counts collapse to 1) - and the source occupies the highest-numbered registers of the
139/// destination group.
140#[inline(always)]
141#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
142fn widen_src_overlap_illegal(vd_idx: u8, wide_group_regs: u8, vs_idx: u8, group_regs: u8) -> bool {
143    if !ranges_overlap(vd_idx, wide_group_regs, vs_idx, group_regs) {
144        return false;
145    }
146    let high_part_overlap =
147        wide_group_regs > group_regs && vs_idx == vd_idx + wide_group_regs - group_regs;
148    !high_part_overlap
149}
150
151/// Check that a widening source `vs2` that is already 2×SEW wide is aligned to `wide_group_regs`
152/// and fits within `[0, 32)`.
153#[inline(always)]
154#[doc(hidden)]
155#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
156pub fn check_vs_wide_alignment<Reg, Memory, PC>(
157    program_counter: &PC,
158    vs: VReg,
159    wide_group_regs: NonZeroU8,
160) -> Result<(), ExecutionError<Reg::Type>>
161where
162    Reg: Register,
163    PC: ProgramCounter<Reg::Type, Memory>,
164{
165    let wide_group_regs = wide_group_regs.get();
166    let vs_idx = vs.to_bits();
167    if !vs_idx.is_multiple_of(wide_group_regs) || vs_idx + wide_group_regs > 32 {
168        cold_path();
169        return Err(ExecutionError::IllegalInstruction {
170            address: PackedAddress::new(program_counter.old_pc(INSTRUCTION_SIZE)),
171        });
172    }
173    Ok(())
174}
175
176/// Check that a narrowing destination `vd` is aligned to `group_regs` and fits
177/// within `[0, 32)`.
178///
179/// No overlap check against `vs2` is performed here because narrowing instructions
180/// permit `vd` to alias the low half of the wide `vs2` register group per spec §11.7.
181#[inline(always)]
182#[doc(hidden)]
183#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
184pub fn check_vd_narrow_alignment<Reg, Memory, PC>(
185    program_counter: &PC,
186    vd: VReg,
187    group_regs: NonZeroU8,
188) -> Result<(), ExecutionError<Reg::Type>>
189where
190    Reg: Register,
191    PC: ProgramCounter<Reg::Type, Memory>,
192{
193    let group_regs = group_regs.get();
194    let vd_idx = vd.to_bits();
195    if !vd_idx.is_multiple_of(group_regs) || vd_idx + group_regs > 32 {
196        cold_path();
197        return Err(ExecutionError::IllegalInstruction {
198            address: PackedAddress::new(program_counter.old_pc(INSTRUCTION_SIZE)),
199        });
200    }
201    Ok(())
202}
203
204/// Returns `true` when `[a_start, a_start+a_len)` overlaps `[b_start, b_start+b_len)`.
205#[inline(always)]
206#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
207fn ranges_overlap(a_start: u8, a_len: u8, b_start: u8, b_len: u8) -> bool {
208    a_start < b_start + b_len && b_start < a_start + a_len
209}
210
211/// Return whether mask bit `i` is set in the mask byte slice (LSB-first within each byte).
212#[inline(always)]
213#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
214fn mask_bit(mask: &[u8], i: u16) -> bool {
215    mask.get(usize::from(i / u8::BITS as u16))
216        .is_some_and(|b| (b >> (i % u8::BITS as u16)) & 1 != 0)
217}
218
219/// Snapshot the mask register into a stack buffer.
220///
221/// When `vm=true` (unmasked), all bytes are `0xff`.
222///
223/// # Safety
224/// `vl <= VLEN` must hold
225#[inline(always)]
226#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
227unsafe fn snapshot_mask<const VLEN: Vlen>(
228    vregs: &VectorRegisterFile<VLEN>,
229    vm: bool,
230    vl: Vl,
231) -> [u8; VLENB_USIZE::<VLEN>] {
232    let mut buf = [0u8; _];
233    if vm {
234        buf = [0xffu8; _];
235    } else {
236        let mask_bytes = usize::from(vl.bytes());
237        // SAFETY: `mask_bytes <= VLEN.bytes()` by precondition
238        unsafe {
239            buf.get_unchecked_mut(..mask_bytes)
240                .copy_from_slice(vregs.get(VReg::V0).get_unchecked(..mask_bytes));
241        }
242    }
243    buf
244}
245
246/// Read the low `sew.bytes_width()` of the element `elem_i` from the register group `base_reg`,
247/// zero-extended to `u64`.
248///
249/// # Safety
250/// `base_reg + elem_i / (VLEN.bytes() / sew.bytes_width()) < 32`
251#[inline(always)]
252#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
253unsafe fn read_element_u64<const VLEN: Vlen>(
254    vregs: &VectorRegisterFile<VLEN>,
255    base_reg: VReg,
256    elem_i: u16,
257    sew: Vsew,
258) -> u64 {
259    let sew_bytes = u32::from(sew.bytes_width());
260    let elems_per_reg = VLEN.bytes() / sew_bytes;
261    let reg_off = u32::from(elem_i) / elems_per_reg;
262    let byte_off = (u32::from(elem_i) % elems_per_reg) * sew_bytes;
263    // SAFETY: `base_reg + reg_off < 32` by caller's precondition
264    let reg = unsafe {
265        vregs.get(VReg::from_bits(base_reg.to_bits() + reg_off as u8).unwrap_unchecked())
266    };
267    // SAFETY: `byte_off + sew_bytes <= VLEN.bytes()`
268    let src = unsafe { reg.get_unchecked(byte_off as usize..(byte_off + sew_bytes) as usize) };
269    let mut buf = [0u8; 8];
270    // SAFETY: `sew_bytes <= 8`
271    unsafe { buf.get_unchecked_mut(..sew_bytes as usize) }.copy_from_slice(src);
272    u64::from_le_bytes(buf)
273}
274
275/// Write the low `sew.bytes_width()` of `value` into element `elem_i` in register group `base_reg`.
276///
277/// # Safety
278/// `base_reg + elem_i / (VLEN.bytes() / sew.bytes_width()) < 32`
279#[inline(always)]
280#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
281unsafe fn write_element_u64<const VLEN: Vlen>(
282    vregs: &mut VectorRegisterFile<VLEN>,
283    base_reg: VReg,
284    elem_i: u16,
285    sew: Vsew,
286    value: u64,
287) {
288    let sew_bytes = u32::from(sew.bytes_width());
289    let elems_per_reg = VLEN.bytes() / sew_bytes;
290    let reg_off = u32::from(elem_i) / elems_per_reg;
291    let byte_off = (u32::from(elem_i) % elems_per_reg) * sew_bytes;
292    let buf = value.to_le_bytes();
293    // SAFETY: `base_reg + reg_off < 32` by caller's precondition
294    let reg = unsafe {
295        vregs.get_mut(VReg::from_bits(base_reg.to_bits() + reg_off as u8).unwrap_unchecked())
296    };
297    // SAFETY: `byte_off + sew_bytes <= VLEN.bytes()`
298    let dst = unsafe { reg.get_unchecked_mut(byte_off as usize..(byte_off + sew_bytes) as usize) };
299    // SAFETY: `sew_bytes <= 8`
300    dst.copy_from_slice(unsafe { buf.get_unchecked(..sew_bytes as usize) });
301}
302
303/// Sign-extend the low `sew.bits_width()` of `val` to `i64`.
304#[inline(always)]
305#[doc(hidden)]
306#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
307pub fn sign_extend_bits(val: u64, sew: Vsew) -> i64 {
308    let shift = u64::BITS - u32::from(sew.bits_width());
309    (val.cast_signed() << shift) >> shift
310}
311
312/// Interpret a scalar operand as an unsigned SEW-wide value.
313///
314/// RVV widening scalar instructions (.vx/.wx) conceptually use a scalar
315/// operand whose width matches the current SEW, not the full XLEN width.
316///
317/// For example on RV64:
318///
319/// SEW=8:
320///     val = 0x0000_0000_0000_01ff
321///     result = 0x0000_0000_0000_00ff
322///
323/// SEW=16:
324///     val = 0x0000_0000_0000_01ff
325///     result = 0x0000_0000_0000_01ff
326///
327/// SEW=32:
328///     val = 0xffff_ffff_1234_5678
329///     result = 0x0000_0000_1234_5678
330///
331/// This helper performs that SEW-width truncation without sign extension.
332#[inline(always)]
333#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
334fn scalar_unsigned_for_sew(val: u64, sew: Vsew) -> u64 {
335    val & (u64::MAX >> (u64::BITS - u32::from(sew.bits_width())))
336}
337
338/// Interpret a scalar operand as a signed SEW-wide value.
339///
340/// The scalar is first truncated to SEW bits, then sign-extended back to
341/// 64 bits.
342///
343/// For example on RV64:
344///
345/// SEW=8:
346///     val = 0x0000_0000_0000_00ff
347///     result = 0xffff_ffff_ffff_ffff (-1)
348///
349/// SEW=8:
350///     val = 0x0000_0000_0000_007f
351///     result = 0x0000_0000_0000_007f (+127)
352///
353/// SEW=16:
354///     val = 0x0000_0000_0000_ffff
355///     result = 0xffff_ffff_ffff_ffff (-1)
356///
357/// This matches the signed widening behavior required by instructions such
358/// as vwadd.vx and vwsub.vx.
359#[inline(always)]
360#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
361fn scalar_signed_for_sew(val: u64, sew: Vsew) -> u64 {
362    sign_extend_bits(val, sew).cast_unsigned()
363}
364
365/// Execute a widening integer add/subtract.
366///
367/// Each source element is SEW-wide; the destination element is 2×SEW-wide.
368/// `ZERO_EXTEND_AB` selects unsigned or signed widening for sources (unsigned = zero-extend,
369/// signed = sign-extend).
370///
371/// `op` receives `(wide_a: u64, wide_b: u64) -> u64`.
372///
373/// # Safety
374/// - `vd` aligned to `2*group_regs`, fits in `[0,32)`, does not overlap `vs2` or `src` (verified by
375///   caller)
376/// - `vs2` aligned to `group_regs`, fits in `[0,32)` (verified by caller)
377/// - `src` register (when `WidenSrc::Vreg`) aligned to `group_regs`, fits in `[0,32)` (verified by
378///   caller)
379/// - `vl <= group_regs * VLEN.bytes() / sew.bytes_width()` (all elements fit)
380/// - SEW < 64
381/// - When `vm=false`: `vd.to_bits() != 0`
382#[inline(always)]
383#[doc(hidden)]
384#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
385pub unsafe fn execute_widen_op<const ZERO_EXTEND_AB: bool, Reg, Env, F>(
386    env: &mut Env,
387    vd: VReg,
388    vs2: VReg,
389    src: OpSrc,
390    vm: bool,
391    sew: Vsew,
392    op: F,
393) where
394    Reg: Register,
395    Env: VectorRegistersExt<Reg>,
396    [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
397    F: Fn(u64, u64) -> u64,
398{
399    let vl = env.vl();
400    let vstart = env.vstart();
401    // SAFETY: Caller guarantees SEW < 64, hence this is always valid
402    let wide_sew = unsafe { sew.double_width().unwrap_unchecked() };
403
404    // SAFETY: `vl <= VLMAX <= VLEN`
405    let mask_buf = unsafe { snapshot_mask(env.read_vregs(), vm, vl) };
406
407    for i in vstart.range_to(vl) {
408        if !mask_bit(&mask_buf, i) {
409            continue;
410        }
411        // SAFETY: `vs2` aligned to `group_regs`;
412        // `i < vl <= group_regs * (VLEN.bytes() / sew.bytes_width())`
413        let raw_a = unsafe { read_element_u64(env.read_vregs(), vs2, i, sew) };
414        let wide_a = if ZERO_EXTEND_AB {
415            raw_a
416        } else {
417            sign_extend_bits(raw_a, sew).cast_unsigned()
418        };
419        let wide_b = match src {
420            OpSrc::Vreg(vs1_base) => {
421                // SAFETY: same argument as vs2
422                let raw_b = unsafe { read_element_u64(env.read_vregs(), vs1_base, i, sew) };
423                if ZERO_EXTEND_AB {
424                    raw_b
425                } else {
426                    sign_extend_bits(raw_b, sew).cast_unsigned()
427                }
428            }
429            OpSrc::Scalar(val) => {
430                if ZERO_EXTEND_AB {
431                    scalar_unsigned_for_sew(val, sew)
432                } else {
433                    scalar_signed_for_sew(val, sew)
434                }
435            }
436        };
437        let result = op(wide_a, wide_b);
438        // SAFETY: `vd` aligned to `2*group_regs`;
439        // `i < vl <= group_regs * (VLEN.bytes() / sew.bytes_width())` so
440        // `i < 2*group_regs * (VLEN.bytes() / wide_sew.bytes_width())` - element fits in the wide
441        // group
442        unsafe {
443            write_element_u64(env.write_vregs(), vd, i, wide_sew, result);
444        }
445    }
446    env.mark_vs_dirty();
447    env.reset_vstart();
448}
449
450/// Execute a widening add/subtract where `vs2` is already 2×SEW wide.
451///
452/// `vs2` is read at `wide_sew.bytes_width()`; `src` (narrow) is read at `sew.bytes_width()` and
453/// widened. `ZERO_EXTEND_B` selects unsigned vs signed widening for the narrow source operand.
454///
455/// # Safety
456/// - `vd` aligned to `2*group_regs`, fits in `[0,32)`, does not overlap `vs2` or `src`
457/// - `vs2` aligned to `2*group_regs`, fits in `[0,32)` (wide source)
458/// - `src` register (when `WidenSrc::Vreg`) aligned to `group_regs`, fits in `[0,32)`
459/// - `vl <= group_regs * VLEN.bytes() / sew.bytes_width()`
460/// - SEW < 64
461/// - When `vm=false`: `vd.to_bits() != 0`
462#[inline(always)]
463#[doc(hidden)]
464#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
465pub unsafe fn execute_widen_w_op<const ZERO_EXTEND_B: bool, Reg, Env, F>(
466    env: &mut Env,
467    vd: VReg,
468    vs2: VReg,
469    src: OpSrc,
470    vm: bool,
471    sew: Vsew,
472    op: F,
473) where
474    Reg: Register,
475    Env: VectorRegistersExt<Reg>,
476    [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
477    F: Fn(u64, u64) -> u64,
478{
479    let vl = env.vl();
480    let vstart = env.vstart();
481    // SAFETY: Caller guarantees SEW < 64, hence this is always valid
482    let wide_sew = unsafe { sew.double_width().unwrap_unchecked() };
483
484    // SAFETY: `vl <= VLEN`
485    let mask_buf = unsafe { snapshot_mask(env.read_vregs(), vm, vl) };
486
487    for i in vstart.range_to(vl) {
488        if !mask_bit(&mask_buf, i) {
489            continue;
490        }
491        // vs2 is already 2×SEW; read at wide width
492        // SAFETY: `vs2` aligned to `2*group_regs`; element `i` fits within it
493        let wide_a = unsafe { read_element_u64(env.read_vregs(), vs2, i, wide_sew) };
494        let wide_b = match src {
495            OpSrc::Vreg(vs1) => {
496                // SAFETY: `vs1` is aligned to `group_regs` and fits within `[0, 32)`,
497                // verified by caller; `i < vl <= group_regs * (VLEN.bytes() / sew.bytes_width())`,
498                // so `vs1_base + i / elems_per_reg < vs1_base + group_regs <= 32`
499                let raw_b = unsafe { read_element_u64(env.read_vregs(), vs1, i, sew) };
500                if ZERO_EXTEND_B {
501                    raw_b
502                } else {
503                    sign_extend_bits(raw_b, sew).cast_unsigned()
504                }
505            }
506            OpSrc::Scalar(val) => {
507                if ZERO_EXTEND_B {
508                    scalar_unsigned_for_sew(val, sew)
509                } else {
510                    scalar_signed_for_sew(val, sew)
511                }
512            }
513        };
514        let result = op(wide_a, wide_b);
515        // SAFETY: same as `execute_widen_op` for vd
516        unsafe {
517            write_element_u64(env.write_vregs(), vd, i, wide_sew, result);
518        }
519    }
520    env.mark_vs_dirty();
521    env.reset_vstart();
522}
523
524/// Execute a narrowing right-shift.
525///
526/// `vs2` is 2×SEW wide; the shift amount comes from `src` (SEW-wide or scalar).
527/// The shift amount is masked to `log2(2*SEW)` bits per spec §12.6.
528/// `ARITHMETIC` selects sign-extending (true) vs zero-extending (false) before shifting.
529///
530/// # Safety
531/// - `vd` aligned to `group_regs`, fits in `[0,32)`
532/// - `vs2` aligned to `wide_group_regs`, fits in `[0,32)`; aliasing with the low half of `vs2` is
533///   permitted per spec §11.7 - reads complete before writes to any overlapping element since the
534///   destination SEW is half the source SEW
535/// - `src` register (when `OpSrc::Vreg`) aligned to `group_regs`, fits in `[0,32)`
536/// - `vl <= group_regs * VLEN.bytes() / sew.bytes_width()`
537/// - SEW < 64
538/// - When `vm=false`: `vd.to_bits() != 0`
539#[inline(always)]
540#[doc(hidden)]
541#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
542pub unsafe fn execute_narrow_shift<const ARITHMETIC: bool, Reg, Env>(
543    env: &mut Env,
544    vd: VReg,
545    vs2: VReg,
546    src: OpSrc,
547    vm: bool,
548    sew: Vsew,
549) where
550    Reg: Register,
551    Env: VectorRegistersExt<Reg>,
552    [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
553{
554    let vl = env.vl();
555    let vstart = env.vstart();
556    // SAFETY: Caller guarantees SEW < 64, hence this is always valid
557    let wide_sew = unsafe { sew.double_width().unwrap_unchecked() };
558    // Shift amount mask: log2(2*SEW) bits = log2(SEW) + 1 bits
559    let shamt_mask = u64::from(wide_sew.bits_width() - 1);
560
561    // SAFETY: `vl <= VLEN`
562    let mask_buf = unsafe { snapshot_mask(env.read_vregs(), vm, vl) };
563
564    for i in vstart.range_to(vl) {
565        if !mask_bit(&mask_buf, i) {
566            continue;
567        }
568        // SAFETY: `vs2` is the wide source group
569        let wide_val = unsafe { read_element_u64(env.read_vregs(), vs2, i, wide_sew) };
570        let shamt = match src {
571            OpSrc::Vreg(vs1_base) => {
572                // SAFETY: `vs1` is aligned to `group_regs` and fits within `[0, 32)`,
573                // verified by caller; `i < vl <= group_regs * (VLEN.bytes() / sew.bytes_width())`,
574                // so `vs1_base + i / elems_per_reg < vs1_base + group_regs <= 32`
575                let raw = unsafe { read_element_u64(env.read_vregs(), vs1_base, i, sew) };
576                raw & shamt_mask
577            }
578            // Scalar shift amount: only the low log2(2*SEW) bits are used per spec
579            OpSrc::Scalar(val) => val & shamt_mask,
580        };
581        let result_wide = if ARITHMETIC {
582            // Sign-extend to i64 first, then shift arithmetically as i64 to
583            // preserve sign bits, then cast back. Shifting u64 after cast_unsigned()
584            // would be a logical shift and lose sign bits.
585            (sign_extend_bits(wide_val, wide_sew) >> shamt).cast_unsigned()
586        } else {
587            wide_val >> shamt
588        };
589        // Truncate to SEW bits
590        let result = result_wide & ((1u64 << sew.bits_width()) - 1);
591        // SAFETY: `vd` is the narrow destination group
592        unsafe {
593            write_element_u64(env.write_vregs(), vd, i, sew, result);
594        }
595    }
596    env.mark_vs_dirty();
597    env.reset_vstart();
598}
599
600/// Execute an integer extension (vzext/vsext).
601///
602/// Source element width is `sew.divide_by_factor(factor).bytes_width()`; destination is
603/// `sew.bytes_width()`. `SIGN` selects sign- or zero-extension.
604///
605/// The source EMUL = LMUL / factor; the source register group is `max(1, group_regs / factor)`
606/// registers.
607///
608/// # Safety
609/// - `vd` aligned to `group_regs`, fits in `[0,32)`
610/// - `vs2` aligned to `src_group_regs`, fits in `[0,32)`, does not overlap `vd`
611/// - `vl <= group_regs * VLEN.bytes() / sew.bytes_width()`
612/// - `sew.divide_by_factor(factor).is_some()`
613/// - When `vm=false`: `vd.to_bits() != 0`
614#[inline(always)]
615#[doc(hidden)]
616#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
617pub unsafe fn execute_extension<const SIGN: bool, Reg, Env>(
618    env: &mut Env,
619    vd: VReg,
620    vs2: VReg,
621    vm: bool,
622    sew: Vsew,
623    factor: VsewFactor,
624) where
625    Reg: Register,
626    Env: VectorRegistersExt<Reg>,
627    [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
628{
629    let vl = env.vl();
630    let vstart = env.vstart();
631    // SAFETY: Caller guarantees SEW >= factor*8 and valid according to function contract
632    let src_sew = unsafe { sew.divide_by_factor(factor).unwrap_unchecked() };
633
634    // SAFETY: `vl <= VLEN`
635    let mask_buf = unsafe { snapshot_mask(env.read_vregs(), vm, vl) };
636
637    for i in vstart.range_to(vl) {
638        if !mask_bit(&mask_buf, i) {
639            continue;
640        }
641        // SAFETY: vs2 group covers `vl` narrow elements
642        let raw = unsafe { read_element_u64(env.read_vregs(), vs2, i, src_sew) };
643        let result = if SIGN {
644            sign_extend_bits(raw, src_sew).cast_unsigned()
645        } else {
646            raw
647        };
648        // SAFETY: vd group covers `vl` wide elements
649        unsafe {
650            write_element_u64(env.write_vregs(), vd, i, sew, result);
651        }
652    }
653    env.mark_vs_dirty();
654    env.reset_vstart();
655}