ab_riscv_interpreter/v/zvexx/widen_narrow/zvexx_widen_narrow_helpers.rs
1//! Opaque helpers for ZveXx extension
2
3use crate::v::vector_registers::VectorRegistersExt;
4pub use crate::v::zvexx::arith::zvexx_arith_helpers::{OpSrc, check_vreg_group_alignment};
5use crate::v::zvexx::load::zvexx_load_helpers::{mask_bit, snapshot_mask};
6use crate::v::zvexx::zvexx_helpers::INSTRUCTION_SIZE;
7use crate::{ExecutionError, PackedAddress, ProgramCounter};
8use ab_riscv_primitives::instructions::v::Vsew;
9use ab_riscv_primitives::prelude::*;
10use core::hint::cold_path;
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: VRegGroupSize,
21) -> Result<(), ExecutionError<Reg::Type>>
22where
23 Reg: Register,
24 PC: ProgramCounter<Reg::Type, Memory>,
25{
26 if !vd.is_group_aligned(wide_group_regs) || vd.to_bits() + wide_group_regs.get() > 32 {
27 cold_path();
28 return Err(ExecutionError::IllegalInstruction {
29 address: PackedAddress::new(program_counter.old_pc(INSTRUCTION_SIZE)),
30 });
31 }
32 Ok(())
33}
34
35/// Check that an extension source `vs2` is aligned to `src_group_regs`, fits in `[0,32)`, and only
36/// overlaps `vd` (which occupies `group_regs` registers) in a manner permitted by the spec.
37///
38/// Per the vector spec §5.2, the destination EEW (SEW) of an extension is greater than the source
39/// EEW (SEW/factor), so the destination may overlap the source only when the source EMUL is at
40/// least 1 and the overlap is in the highest-numbered part of the destination register group (e.g.
41/// `vzext.vf4 v0, v6` with LMUL=8, where the narrow source `{v6,v7}` aliases the high registers of
42/// the wide `{v0..v7}` destination). Any other overlap is illegal.
43#[inline(always)]
44#[doc(hidden)]
45#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
46pub fn check_vs_ext_alignment<Reg, Memory, PC>(
47 program_counter: &PC,
48 vs2: VReg,
49 src_group_regs: VRegGroupSize,
50 vd: VReg,
51 group_regs: VRegGroupSize,
52) -> Result<(), ExecutionError<Reg::Type>>
53where
54 Reg: Register,
55 PC: ProgramCounter<Reg::Type, Memory>,
56{
57 let aligned = vs2.is_group_aligned(src_group_regs);
58 let src_group_regs = src_group_regs.get();
59 let group_regs = group_regs.get();
60 let vs2_idx = vs2.to_bits();
61 if !aligned || vs2_idx + src_group_regs > 32 {
62 cold_path();
63 return Err(ExecutionError::IllegalInstruction {
64 address: PackedAddress::new(program_counter.old_pc(INSTRUCTION_SIZE)),
65 });
66 }
67 // The wide destination (group_regs) may overlap the narrow source (src_group_regs) only in the
68 // highest-numbered part of the destination group, and only when the source EMUL >= 1.
69 if widen_src_overlap_illegal(vd.to_bits(), group_regs, vs2_idx, src_group_regs) {
70 cold_path();
71 return Err(ExecutionError::IllegalInstruction {
72 address: PackedAddress::new(program_counter.old_pc(INSTRUCTION_SIZE)),
73 });
74 }
75 Ok(())
76}
77
78/// Check that a widening destination `vd` is aligned to `wide_group_regs`, fits within `[0, 32)`,
79/// and only overlaps the `group_regs`-register narrow source(s) starting at `vs_a`/`vs_b` in a
80/// manner permitted by the spec.
81///
82/// `wide_group_regs` is the pre-computed register count for the wide EMUL (2*LMUL), obtained via
83/// `Vlmul::index_register_count(wide_eew, sew)`. `group_regs` is the narrow LMUL register count.
84///
85/// Per the vector spec §5.2, a destination whose EEW (2*SEW) is greater than a source's EEW (SEW)
86/// may overlap that source only when the source EMUL is at least 1 and the overlap is in the
87/// highest-numbered part of the destination register group (e.g. `vwsubu.wv v2, v14, v3` with
88/// LMUL=1, where the narrow `v3` aliases the high register of the wide `{v2, v3}` destination).
89/// Any other overlap is illegal.
90#[inline(always)]
91#[doc(hidden)]
92#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
93pub fn check_vd_widen_alignment<Reg, Memory, PC>(
94 program_counter: &PC,
95 vd: VReg,
96 vs_a: VReg,
97 vs_b_opt: Option<VReg>,
98 group_regs: VRegGroupSize,
99 wide_group_regs: VRegGroupSize,
100) -> Result<(), ExecutionError<Reg::Type>>
101where
102 Reg: Register,
103 PC: ProgramCounter<Reg::Type, Memory>,
104{
105 let aligned = vd.is_group_aligned(wide_group_regs);
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 !aligned || 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: VRegGroupSize,
160) -> Result<(), ExecutionError<Reg::Type>>
161where
162 Reg: Register,
163 PC: ProgramCounter<Reg::Type, Memory>,
164{
165 if !vs.is_group_aligned(wide_group_regs) || vs.to_bits() + wide_group_regs.get() > 32 {
166 cold_path();
167 return Err(ExecutionError::IllegalInstruction {
168 address: PackedAddress::new(program_counter.old_pc(INSTRUCTION_SIZE)),
169 });
170 }
171 Ok(())
172}
173
174/// Check that a narrowing destination `vd` is aligned to `group_regs`, fits within `[0, 32)`, and
175/// only overlaps the wide source `vs2` (which occupies `wide_group_regs` registers starting at
176/// `vs2`) in a manner permitted by the spec.
177///
178/// Per spec §11.7, narrowing instructions permit `vd` to alias the *low* half of the wide `vs2`
179/// register group (i.e. `vd == vs2`) - any other overlap (e.g. `vd` aliasing only the high part of
180/// `vs2`'s group) is illegal.
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: VRegGroupSize,
188 vs2: VReg,
189 wide_group_regs: VRegGroupSize,
190) -> Result<(), ExecutionError<Reg::Type>>
191where
192 Reg: Register,
193 PC: ProgramCounter<Reg::Type, Memory>,
194{
195 let aligned = vd.is_group_aligned(group_regs);
196 let group_regs = group_regs.get();
197 let vd_idx = vd.to_bits();
198 let vs2_idx = vs2.to_bits();
199 let overlaps = ranges_overlap(vd_idx, group_regs, vs2_idx, wide_group_regs.get());
200 if !aligned || vd_idx + group_regs > 32 || (overlaps && vd_idx != vs2_idx) {
201 cold_path();
202 return Err(ExecutionError::IllegalInstruction {
203 address: PackedAddress::new(program_counter.old_pc(INSTRUCTION_SIZE)),
204 });
205 }
206 Ok(())
207}
208
209/// Returns `true` when `[a_start, a_start+a_len)` overlaps `[b_start, b_start+b_len)`.
210#[inline(always)]
211#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
212fn ranges_overlap(a_start: u8, a_len: u8, b_start: u8, b_len: u8) -> bool {
213 a_start < b_start + b_len && b_start < a_start + a_len
214}
215
216/// Sign-extend the low `sew.bits_width()` of `val` to `i64`.
217#[inline(always)]
218#[doc(hidden)]
219#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
220pub fn sign_extend_bits(val: u64, sew: Vsew) -> i64 {
221 let shift = u64::BITS - u32::from(sew.bits_width());
222 (val.cast_signed() << shift) >> shift
223}
224
225/// Interpret a scalar operand as an unsigned SEW-wide value.
226///
227/// RVV widening scalar instructions (.vx/.wx) conceptually use a scalar
228/// operand whose width matches the current SEW, not the full XLEN width.
229///
230/// For example on RV64:
231///
232/// SEW=8:
233/// val = 0x0000_0000_0000_01ff
234/// result = 0x0000_0000_0000_00ff
235///
236/// SEW=16:
237/// val = 0x0000_0000_0000_01ff
238/// result = 0x0000_0000_0000_01ff
239///
240/// SEW=32:
241/// val = 0xffff_ffff_1234_5678
242/// result = 0x0000_0000_1234_5678
243///
244/// This helper performs that SEW-width truncation without sign extension.
245#[inline(always)]
246#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
247fn scalar_unsigned_for_sew(val: u64, sew: Vsew) -> u64 {
248 val & (u64::MAX >> (u64::BITS - u32::from(sew.bits_width())))
249}
250
251/// Interpret a scalar operand as a signed SEW-wide value.
252///
253/// The scalar is first truncated to SEW bits, then sign-extended back to
254/// 64 bits.
255///
256/// For example on RV64:
257///
258/// SEW=8:
259/// val = 0x0000_0000_0000_00ff
260/// result = 0xffff_ffff_ffff_ffff (-1)
261///
262/// SEW=8:
263/// val = 0x0000_0000_0000_007f
264/// result = 0x0000_0000_0000_007f (+127)
265///
266/// SEW=16:
267/// val = 0x0000_0000_0000_ffff
268/// result = 0xffff_ffff_ffff_ffff (-1)
269///
270/// This matches the signed widening behavior required by instructions such
271/// as vwadd.vx and vwsub.vx.
272#[inline(always)]
273#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
274fn scalar_signed_for_sew(val: u64, sew: Vsew) -> u64 {
275 sign_extend_bits(val, sew).cast_unsigned()
276}
277
278/// Execute a widening integer add/subtract.
279///
280/// Each source element is SEW-wide; the destination element is 2×SEW-wide.
281/// `ZERO_EXTEND_AB` selects unsigned or signed widening for sources (unsigned = zero-extend,
282/// signed = sign-extend).
283///
284/// `op` receives `(wide_a: u64, wide_b: u64) -> u64`.
285///
286/// # Safety
287/// - `vd` aligned to `2*group_regs`, fits in `[0,32)`, does not overlap `vs2` or `src` (verified by
288/// caller)
289/// - `vs2` aligned to `group_regs`, fits in `[0,32)` (verified by caller)
290/// - `src` register (when `WidenSrc::Vreg`) aligned to `group_regs`, fits in `[0,32)` (verified by
291/// caller)
292/// - `vl <= group_regs * VLEN.bytes() / sew.bytes_width()` (all elements fit)
293/// - SEW < 64
294/// - When `vm=false`: `vd.to_bits() != 0`
295#[inline(always)]
296#[doc(hidden)]
297#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
298pub unsafe fn execute_widen_op<const ZERO_EXTEND_AB: bool, Reg, Env, F>(
299 env: &mut Env,
300 vd: VReg,
301 vs2: VReg,
302 src: OpSrc,
303 vm: bool,
304 sew: Vsew,
305 op: F,
306) where
307 Reg: Register,
308 Env: VectorRegistersExt<Reg>,
309 [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
310 F: Fn(u64, u64) -> u64,
311{
312 let vl = env.vl();
313 let vstart = env.vstart();
314 // SAFETY: Caller guarantees SEW < 64, hence this is always valid
315 let wide_sew = unsafe { sew.double_width().unwrap_unchecked() };
316
317 // SAFETY: `vl <= VLMAX <= VLEN`
318 let mask_buf = unsafe { snapshot_mask(env.read_vregs(), vm, vl) };
319
320 for i in vstart.range_to(vl) {
321 if !mask_bit(&mask_buf, i) {
322 continue;
323 }
324 // SAFETY: `vs2` aligned to `group_regs`;
325 // `i < vl <= group_regs * (VLEN.bytes() / sew.bytes_width())`
326 let raw_a = unsafe { env.read_vregs().read_element(vs2, i, sew) };
327 let wide_a = if ZERO_EXTEND_AB {
328 raw_a
329 } else {
330 sign_extend_bits(raw_a, sew).cast_unsigned()
331 };
332 let wide_b = match src {
333 OpSrc::Vreg(vs1_base) => {
334 // SAFETY: same argument as vs2
335 let raw_b = unsafe { env.read_vregs().read_element(vs1_base, i, sew) };
336 if ZERO_EXTEND_AB {
337 raw_b
338 } else {
339 sign_extend_bits(raw_b, sew).cast_unsigned()
340 }
341 }
342 OpSrc::Scalar(val) => {
343 if ZERO_EXTEND_AB {
344 scalar_unsigned_for_sew(val, sew)
345 } else {
346 scalar_signed_for_sew(val, sew)
347 }
348 }
349 };
350 let result = op(wide_a, wide_b);
351 // SAFETY: `vd` aligned to `2*group_regs`;
352 // `i < vl <= group_regs * (VLEN.bytes() / sew.bytes_width())` so
353 // `i < 2*group_regs * (VLEN.bytes() / wide_sew.bytes_width())` - element fits in the wide
354 // group
355 unsafe {
356 env.write_vregs().write_element(vd, i, wide_sew, result);
357 }
358 }
359 env.mark_vs_dirty();
360 env.reset_vstart();
361}
362
363/// Execute a widening add/subtract where `vs2` is already 2×SEW wide.
364///
365/// `vs2` is read at `wide_sew.bytes_width()`; `src` (narrow) is read at `sew.bytes_width()` and
366/// widened. `ZERO_EXTEND_B` selects unsigned vs signed widening for the narrow source operand.
367///
368/// # Safety
369/// - `vd` aligned to `2*group_regs`, fits in `[0,32)`, does not overlap `vs2` or `src`
370/// - `vs2` aligned to `2*group_regs`, fits in `[0,32)` (wide source)
371/// - `src` register (when `WidenSrc::Vreg`) aligned to `group_regs`, fits in `[0,32)`
372/// - `vl <= group_regs * VLEN.bytes() / sew.bytes_width()`
373/// - SEW < 64
374/// - When `vm=false`: `vd.to_bits() != 0`
375#[inline(always)]
376#[doc(hidden)]
377#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
378pub unsafe fn execute_widen_w_op<const ZERO_EXTEND_B: bool, Reg, Env, F>(
379 env: &mut Env,
380 vd: VReg,
381 vs2: VReg,
382 src: OpSrc,
383 vm: bool,
384 sew: Vsew,
385 op: F,
386) where
387 Reg: Register,
388 Env: VectorRegistersExt<Reg>,
389 [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
390 F: Fn(u64, u64) -> u64,
391{
392 let vl = env.vl();
393 let vstart = env.vstart();
394 // SAFETY: Caller guarantees SEW < 64, hence this is always valid
395 let wide_sew = unsafe { sew.double_width().unwrap_unchecked() };
396
397 // SAFETY: `vl <= VLEN`
398 let mask_buf = unsafe { snapshot_mask(env.read_vregs(), vm, vl) };
399
400 for i in vstart.range_to(vl) {
401 if !mask_bit(&mask_buf, i) {
402 continue;
403 }
404 // vs2 is already 2×SEW; read at wide width
405 // SAFETY: `vs2` aligned to `2*group_regs`; element `i` fits within it
406 let wide_a = unsafe { env.read_vregs().read_element(vs2, i, wide_sew) };
407 let wide_b = match src {
408 OpSrc::Vreg(vs1) => {
409 // SAFETY: `vs1` is aligned to `group_regs` and fits within `[0, 32)`,
410 // verified by caller; `i < vl <= group_regs * (VLEN.bytes() / sew.bytes_width())`,
411 // so `vs1_base + i / elems_per_reg < vs1_base + group_regs <= 32`
412 let raw_b = unsafe { env.read_vregs().read_element(vs1, i, sew) };
413 if ZERO_EXTEND_B {
414 raw_b
415 } else {
416 sign_extend_bits(raw_b, sew).cast_unsigned()
417 }
418 }
419 OpSrc::Scalar(val) => {
420 if ZERO_EXTEND_B {
421 scalar_unsigned_for_sew(val, sew)
422 } else {
423 scalar_signed_for_sew(val, sew)
424 }
425 }
426 };
427 let result = op(wide_a, wide_b);
428 // SAFETY: same as `execute_widen_op` for vd
429 unsafe {
430 env.write_vregs().write_element(vd, i, wide_sew, result);
431 }
432 }
433 env.mark_vs_dirty();
434 env.reset_vstart();
435}
436
437/// Execute a narrowing right-shift.
438///
439/// `vs2` is 2×SEW wide; the shift amount comes from `src` (SEW-wide or scalar).
440/// The shift amount is masked to `log2(2*SEW)` bits per spec §12.6.
441/// `ARITHMETIC` selects sign-extending (true) vs zero-extending (false) before shifting.
442///
443/// # Safety
444/// - `vd` aligned to `group_regs`, fits in `[0,32)`
445/// - `vs2` aligned to `wide_group_regs`, fits in `[0,32)`; aliasing with the low half of `vs2` is
446/// permitted per spec §11.7 - reads complete before writes to any overlapping element since the
447/// destination SEW is half the source SEW
448/// - `src` register (when `OpSrc::Vreg`) aligned to `group_regs`, fits in `[0,32)`
449/// - `vl <= group_regs * VLEN.bytes() / sew.bytes_width()`
450/// - SEW < 64
451/// - When `vm=false`: `vd.to_bits() != 0`
452#[inline(always)]
453#[doc(hidden)]
454#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
455pub unsafe fn execute_narrow_shift<const ARITHMETIC: bool, Reg, Env>(
456 env: &mut Env,
457 vd: VReg,
458 vs2: VReg,
459 src: OpSrc,
460 vm: bool,
461 sew: Vsew,
462) where
463 Reg: Register,
464 Env: VectorRegistersExt<Reg>,
465 [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
466{
467 let vl = env.vl();
468 let vstart = env.vstart();
469 // SAFETY: Caller guarantees SEW < 64, hence this is always valid
470 let wide_sew = unsafe { sew.double_width().unwrap_unchecked() };
471 // Shift amount mask: log2(2*SEW) bits = log2(SEW) + 1 bits
472 let shamt_mask = u64::from(wide_sew.bits_width() - 1);
473
474 // SAFETY: `vl <= VLEN`
475 let mask_buf = unsafe { snapshot_mask(env.read_vregs(), vm, vl) };
476
477 for i in vstart.range_to(vl) {
478 if !mask_bit(&mask_buf, i) {
479 continue;
480 }
481 // SAFETY: `vs2` is the wide source group
482 let wide_val = unsafe { env.read_vregs().read_element(vs2, i, wide_sew) };
483 let shamt = match src {
484 OpSrc::Vreg(vs1_base) => {
485 // SAFETY: `vs1` is aligned to `group_regs` and fits within `[0, 32)`,
486 // verified by caller; `i < vl <= group_regs * (VLEN.bytes() / sew.bytes_width())`,
487 // so `vs1_base + i / elems_per_reg < vs1_base + group_regs <= 32`
488 let raw = unsafe { env.read_vregs().read_element(vs1_base, i, sew) };
489 raw & shamt_mask
490 }
491 // Scalar shift amount: only the low log2(2*SEW) bits are used per spec
492 OpSrc::Scalar(val) => val & shamt_mask,
493 };
494 let result_wide = if ARITHMETIC {
495 // Sign-extend to i64 first, then shift arithmetically as i64 to
496 // preserve sign bits, then cast back. Shifting u64 after cast_unsigned()
497 // would be a logical shift and lose sign bits.
498 (sign_extend_bits(wide_val, wide_sew) >> shamt).cast_unsigned()
499 } else {
500 wide_val >> shamt
501 };
502 // Truncate to SEW bits
503 let result = result_wide & ((1u64 << sew.bits_width()) - 1);
504 // SAFETY: `vd` is the narrow destination group
505 unsafe {
506 env.write_vregs().write_element(vd, i, sew, result);
507 }
508 }
509 env.mark_vs_dirty();
510 env.reset_vstart();
511}
512
513/// Execute an integer extension (vzext/vsext).
514///
515/// Source element width is `sew.divide_by_factor(factor).bytes_width()`; destination is
516/// `sew.bytes_width()`. `SIGN` selects sign- or zero-extension.
517///
518/// The source EMUL = LMUL / factor; the source register group is `max(1, group_regs / factor)`
519/// registers.
520///
521/// # Safety
522/// - `vd` aligned to `group_regs`, fits in `[0,32)`
523/// - `vs2` aligned to `src_group_regs`, fits in `[0,32)`, does not overlap `vd`
524/// - `vl <= group_regs * VLEN.bytes() / sew.bytes_width()`
525/// - `sew.divide_by_factor(factor).is_some()`
526/// - When `vm=false`: `vd.to_bits() != 0`
527#[inline(always)]
528#[doc(hidden)]
529#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
530pub unsafe fn execute_extension<const SIGN: bool, Reg, Env>(
531 env: &mut Env,
532 vd: VReg,
533 vs2: VReg,
534 vm: bool,
535 sew: Vsew,
536 factor: VsewFactor,
537) where
538 Reg: Register,
539 Env: VectorRegistersExt<Reg>,
540 [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
541{
542 let vl = env.vl();
543 let vstart = env.vstart();
544 // SAFETY: Caller guarantees SEW >= factor*8 and valid according to function contract
545 let src_sew = unsafe { sew.divide_by_factor(factor).unwrap_unchecked() };
546
547 // SAFETY: `vl <= VLEN`
548 let mask_buf = unsafe { snapshot_mask(env.read_vregs(), vm, vl) };
549
550 for i in vstart.range_to(vl) {
551 if !mask_bit(&mask_buf, i) {
552 continue;
553 }
554 // SAFETY: vs2 group covers `vl` narrow elements
555 let raw = unsafe { env.read_vregs().read_element(vs2, i, src_sew) };
556 let result = if SIGN {
557 sign_extend_bits(raw, src_sew).cast_unsigned()
558 } else {
559 raw
560 };
561 // SAFETY: vd group covers `vl` wide elements
562 unsafe {
563 env.write_vregs().write_element(vd, i, sew, result);
564 }
565 }
566 env.mark_vs_dirty();
567 env.reset_vstart();
568}