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::{VRegGroup, VectorRegisterFile, VectorRegistersExt};
4use crate::v::zvexx::load::zvexx_load_helpers::{mask_bit, snapshot_mask};
5use ab_riscv_primitives::prelude::*;
6use core::hint::cold_path;
7
8/// Sign-extend the low `sew.bits_width()` of `val` to the register type width.
9///
10/// The arithmetic is performed entirely in 64-bit signed integer space: we shift the SEW-wide
11/// value left to place its sign bit at bit 63, then arithmetic-right-shift back to propagate it.
12/// The resulting `u64` is then narrowed to `Reg::Type` (32 or 64 bits) by combining via
13/// `From<u32>` - the only integer conversion in the `Register::Type` trait bounds.
14///
15/// For RV32 (`Reg::XLEN == 32`) the low 32 bits are already the correct sign-extended result
16/// because the arithmetic shift propagates the sign across all 64 bits and then we discard the
17/// upper half.
18///
19/// For RV64 (`Reg::XLEN == 64`) we must preserve all 64 bits. Since `Reg::Type: From<u32>` and
20/// `Reg::Type: Shl<u8>`, we reconstruct the 64-bit value by OR-ing two 32-bit halves shifted
21/// into position.
22#[inline(always)]
23#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
24pub fn sign_extend_to_reg<Reg>(val: u64, sew: Vsew) -> Reg::Type
25where
26    Reg: Register,
27{
28    let sew_bits = u32::from(sew.bits_width());
29    // `shift` is in [0, 64). When sew_bits == 64, shift == 0 and the value is unchanged.
30    let shift = u64::BITS - sew_bits;
31    // Cast to i64 so the right-shift is arithmetic (sign-extending).
32    let sign_extended = (val.cast_signed() << shift) >> shift;
33    let raw = sign_extended.cast_unsigned();
34    if Reg::XLEN == u64::BITS as u8 {
35        // RV64: preserve all 64 bits by splitting into two u32 halves.
36        let lo = Reg::Type::from(raw as u32);
37        let hi = Reg::Type::from((raw >> u32::BITS) as u32);
38        lo | (hi << 32u8)
39    } else {
40        // RV32: the low 32 bits are the correctly truncated result.
41        Reg::Type::from(raw as u32)
42    }
43}
44
45/// Execute a vslideup operation.
46///
47/// Elements `0..min(offset, vl)` in vd are unchanged.
48/// Elements `offset..vl` where mask is active get vs2[i - offset].
49///
50/// Both register groups must have the same `vl`, which holds for groups created from the same
51/// configuration, nothing is written otherwise.
52#[inline(always)]
53#[doc(hidden)]
54#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
55pub fn execute_slideup<Reg, Env>(
56    env: &mut Env,
57    vd: VRegGroup<Env::Hart>,
58    vs2: VRegGroup<Env::Hart>,
59    vm: bool,
60    offset: u64,
61) where
62    Reg: Register,
63    Env: VectorRegistersExt<Hart: HartConfig<Reg = Reg>>,
64{
65    let vl = vd.vl();
66    let Some(vs2) = vs2.with_same_vl(vl) else {
67        cold_path();
68        return;
69    };
70    // Unmasked slide is a copy of a contiguous element range between two contiguous register
71    // groups, elements `offset..vl` of `vd` get elements `0..vl - offset` of `vs2`
72    if vm {
73        if let Some(count) = u64::from(u32::from(vl.get())).checked_sub(offset)
74            && let Ok(dst_first) = u16::try_from(offset)
75            && let Ok(count) = u32::try_from(count)
76            && !env
77                .write_vregs()
78                .copy_elements(vd, dst_first, vs2, 0, count)
79        {
80            // Can't happen for groups of the same instruction, see
81            // `VectorRegisterFile::copy_elements()`
82            cold_path();
83        }
84        env.mark_vs_dirty();
85        return;
86    }
87    // Per spec ยง16.3.1: elements `0..offset` are never written (vd keeps its value)
88    let Ok(offset) = u16::try_from(offset) else {
89        env.mark_vs_dirty();
90        return;
91    };
92
93    let mask_buf = snapshot_mask(env.read_vregs(), vm);
94    for i in vl.indices() {
95        let Some(src_i) = i.checked_sub(offset) else {
96            continue;
97        };
98        if !mask_bit(&mask_buf, i) {
99            continue;
100        }
101        let val = env
102            .read_vregs()
103            .read(vs2, src_i)
104            .expect("`src_i <= i < vl`, which is the same for `vs2`, checked above; qed");
105        env.write_vregs()
106            .write(vd, i, val)
107            .expect("`i < vl` of `vd`; qed");
108    }
109    env.mark_vs_dirty();
110}
111
112/// Execute a vslidedown operation.
113///
114/// Element `vd[i] = vs2[i + offset]` if `i + offset < vlmax`, else `0`.
115///
116/// Both register groups must have the same `vl`, which holds for groups created from the same
117/// configuration, nothing is written otherwise.
118#[inline(always)]
119#[doc(hidden)]
120#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
121pub fn execute_slidedown<Reg, Env>(
122    env: &mut Env,
123    vd: VRegGroup<Env::Hart>,
124    vs2: VRegGroup<Env::Hart>,
125    vm: bool,
126    offset: u64,
127) where
128    Reg: Register,
129    Env: VectorRegistersExt<Hart: HartConfig<Reg = Reg>>,
130{
131    let vl = vd.vl();
132    let Some(vs2) = vs2.with_same_vl(vl) else {
133        cold_path();
134        return;
135    };
136
137    // Unmasked slide is a copy of a contiguous element range between two contiguous register
138    // groups (or within one), followed by zeroing of the elements whose source is beyond `vlmax`
139    if vm {
140        let vl_len = u32::from(vl.get());
141        // Elements `i` with `i + offset < vlmax` have a source, the rest are zeroed
142        let copied = u64::from(u32::from(vs2.vlmax()))
143            .saturating_sub(offset)
144            .min(u64::from(vl_len));
145        let copied = u32::try_from(copied).unwrap_or(vl_len);
146        let vregs = env.write_vregs();
147        if copied > 0
148            && let Ok(src_first) = u16::try_from(offset)
149            && !vregs.copy_elements(vd, 0, vs2, src_first, copied)
150        {
151            // Can't happen for groups of the same instruction, see
152            // `VectorRegisterFile::copy_elements()`
153            cold_path();
154        }
155        if let Ok(first_zeroed) = u16::try_from(copied)
156            && let Some(bytes) = vregs.elements_bytes_mut(vd, first_zeroed, vl_len - copied)
157        {
158            bytes.fill(0);
159        }
160        env.mark_vs_dirty();
161        return;
162    }
163
164    let mask_buf = snapshot_mask(env.read_vregs(), vm);
165    for i in vl.indices() {
166        if !mask_bit(&mask_buf, i) {
167            continue;
168        }
169        // Any source index that doesn't fit is beyond `vlmax`, where the spec requires `vd[i] = 0`
170        let val = u64::from(i)
171            .checked_add(offset)
172            .and_then(|src_idx| u16::try_from(src_idx).ok())
173            .and_then(|src_idx| env.read_vregs().read_up_to_vlmax(vs2, src_idx))
174            .unwrap_or_default();
175        env.write_vregs()
176            .write(vd, i, val)
177            .expect("`i < vl` of `vd`; qed");
178    }
179    env.mark_vs_dirty();
180}
181
182/// Execute a vslide1up operation.
183///
184/// Element 0 of vd gets `scalar` (when active and vl > 0).
185/// Element `i` for `1 <= i < vl` gets `vs2[i - 1]`.
186/// vd must not overlap vs2.
187///
188/// Both register groups must have the same `vl`, which holds for groups created from the same
189/// configuration, nothing is written otherwise.
190#[inline(always)]
191#[doc(hidden)]
192#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
193pub fn execute_slide1up<Reg, Env>(
194    env: &mut Env,
195    vd: VRegGroup<Env::Hart>,
196    vs2: VRegGroup<Env::Hart>,
197    vm: bool,
198    scalar: u64,
199) where
200    Reg: Register,
201    Env: VectorRegistersExt<Hart: HartConfig<Reg = Reg>>,
202{
203    let vl = vd.vl();
204    let Some(vs2) = vs2.with_same_vl(vl) else {
205        cold_path();
206        return;
207    };
208    let mask_buf = snapshot_mask(env.read_vregs(), vm);
209    for i in vl.indices() {
210        if !mask_bit(&mask_buf, i) {
211            continue;
212        }
213        let val = match i.checked_sub(1) {
214            Some(src_idx) => env
215                .read_vregs()
216                .read(vs2, src_idx)
217                .expect("`i - 1 < vl`, which is the same for `vs2`, checked above; qed"),
218            None => scalar,
219        };
220        env.write_vregs()
221            .write(vd, i, val)
222            .expect("`i < vl` of `vd`; qed");
223    }
224    env.mark_vs_dirty();
225}
226
227/// Execute a vslide1down operation.
228///
229/// Element `vd[i] = vs2[i + 1]` for `i < vl - 1`; element `vd[vl - 1]` gets `scalar`.
230///
231/// Overlap between `vd` and `vs2` is permitted by the spec. When they share the same register
232/// group base (exact overlap), ascending iteration is still correct: each write goes to byte range
233/// `[i*sew, (i+1)*sew)` while the subsequent read comes from `[(i+1)*sew, (i+2)*sew)`. These
234/// ranges are adjacent and non-overlapping, so writing element `i` never corrupts the source bytes
235/// of element `i+1`.
236///
237/// Both register groups must have the same `vl`, which holds for groups created from the same
238/// configuration, nothing is written otherwise.
239#[inline(always)]
240#[doc(hidden)]
241#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
242pub fn execute_slide1down<Reg, Env>(
243    env: &mut Env,
244    vd: VRegGroup<Env::Hart>,
245    vs2: VRegGroup<Env::Hart>,
246    vm: bool,
247    scalar: u64,
248) where
249    Reg: Register,
250    Env: VectorRegistersExt<Hart: HartConfig<Reg = Reg>>,
251{
252    let vl = vd.vl();
253    let Some(vs2) = vs2.with_same_vl(vl) else {
254        cold_path();
255        return;
256    };
257    let mask_buf = snapshot_mask(env.read_vregs(), vm);
258    for i in vl.indices() {
259        if !mask_bit(&mask_buf, i) {
260            continue;
261        }
262        // The last element has no source in `vs2`, which `read()` reports as `None`
263        let val = i
264            .checked_add(1)
265            .and_then(|src_idx| env.read_vregs().read(vs2, src_idx))
266            .unwrap_or(scalar);
267        env.write_vregs()
268            .write(vd, i, val)
269            .expect("`i < vl` of `vd`; qed");
270    }
271    env.mark_vs_dirty();
272}
273
274/// Execute vrgather.vv: `vd[i] = (vs1[i] < vlmax) ? vs2[vs1[i]] : 0`.
275///
276/// All register groups must have the same `vl`, which holds for groups created from the same
277/// configuration, nothing is written otherwise.
278#[inline(always)]
279#[doc(hidden)]
280#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
281pub fn execute_rgather_vv<Reg, Env>(
282    env: &mut Env,
283    vd: VRegGroup<Env::Hart>,
284    vs2: VRegGroup<Env::Hart>,
285    vs1: VRegGroup<Env::Hart>,
286    vm: bool,
287) where
288    Reg: Register,
289    Env: VectorRegistersExt<Hart: HartConfig<Reg = Reg>>,
290{
291    let vl = vd.vl();
292    let Some(vs1) = vs1.with_same_vl(vl) else {
293        cold_path();
294        return;
295    };
296    let mask_buf = snapshot_mask(env.read_vregs(), vm);
297    for i in vl.indices() {
298        if !mask_bit(&mask_buf, i) {
299            continue;
300        }
301        let index = env
302            .read_vregs()
303            .read(vs1, i)
304            .expect("`vs1` has the same `vl` as `vd`, checked above; qed");
305        let val = gather_element(env.read_vregs(), vs2, index);
306        env.write_vregs()
307            .write(vd, i, val)
308            .expect("`i < vl` of `vd`; qed");
309    }
310    env.mark_vs_dirty();
311}
312
313/// Execute vrgather.vx / vrgather.vi: all active elements get `vs2[index]` or `0`
314#[inline(always)]
315#[doc(hidden)]
316#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
317pub fn execute_rgather_scalar<Reg, Env>(
318    env: &mut Env,
319    vd: VRegGroup<Env::Hart>,
320    vs2: VRegGroup<Env::Hart>,
321    vm: bool,
322    index: u64,
323) where
324    Reg: Register,
325    Env: VectorRegistersExt<Hart: HartConfig<Reg = Reg>>,
326{
327    let mask_buf = snapshot_mask(env.read_vregs(), vm);
328    // Pre-compute the gathered value; it's the same for all elements.
329    let val = gather_element(env.read_vregs(), vs2, index);
330    for i in vd.vl().indices() {
331        if !mask_bit(&mask_buf, i) {
332            continue;
333        }
334        env.write_vregs()
335            .write(vd, i, val)
336            .expect("`i < vl` of `vd`; qed");
337    }
338    env.mark_vs_dirty();
339}
340
341/// Execute vrgatherei16.vv: `vd[i] = (vs1_16[i] < vlmax) ? vs2[vs1_16[i]] : 0`.
342///
343/// `vs1` always uses EEW=16 regardless of SEW.
344///
345/// All register groups must have the same `vl`, which holds for groups created from the same
346/// configuration, nothing is written otherwise.
347#[inline(always)]
348#[doc(hidden)]
349#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
350pub fn execute_rgatherei16<Reg, Env>(
351    env: &mut Env,
352    vd: VRegGroup<Env::Hart>,
353    vs2: VRegGroup<Env::Hart>,
354    vs1: VRegGroup<Env::Hart>,
355    vm: bool,
356) where
357    Reg: Register,
358    Env: VectorRegistersExt<Hart: HartConfig<Reg = Reg>>,
359{
360    execute_rgather_vv::<Reg, Env>(env, vd, vs2, vs1, vm);
361}
362
363/// Element `index` of `vs2` if it is below `VLMAX`, `0` otherwise, which is what `vrgather`
364/// produces
365#[inline(always)]
366#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
367fn gather_element<Hart>(vregs: &VectorRegisterFile<Hart>, vs2: VRegGroup<Hart>, index: u64) -> u64
368where
369    Hart: VectorHartConfig,
370{
371    u16::try_from(index)
372        .ok()
373        .and_then(|index| vregs.read_up_to_vlmax(vs2, index))
374        .unwrap_or_default()
375}
376
377/// Execute vmerge.vvm / vmv.v.v.
378///
379/// When `vm=true` (vmv.v.v): all active elements `0..vl` get `vs1[i]`; vs2 unused.
380/// When `vm=false` (vmerge.vvm): active elements where `v0[i]=1` get `vs1[i]`,
381/// inactive elements get `vs2[i]`.
382///
383/// All register groups must have the same `vl`, which holds for groups created from the same
384/// configuration, nothing is written otherwise.
385#[inline(always)]
386#[doc(hidden)]
387#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
388pub fn execute_merge_vv<Reg, Env>(
389    env: &mut Env,
390    vd: VRegGroup<Env::Hart>,
391    vs2: VRegGroup<Env::Hart>,
392    vs1: VRegGroup<Env::Hart>,
393    vm: bool,
394) where
395    Reg: Register,
396    Env: VectorRegistersExt<Hart: HartConfig<Reg = Reg>>,
397{
398    let vl = vd.vl();
399    let (Some(vs2), Some(vs1)) = (vs2.with_same_vl(vl), vs1.with_same_vl(vl)) else {
400        cold_path();
401        return;
402    };
403    // For vmv.v.v (vm=true) the mask is all-ones so snapshot_mask is still valid.
404    let mask_buf = snapshot_mask(env.read_vregs(), vm);
405    for i in vl.indices() {
406        // mask_set=false only reachable when vm=false (vmerge path)
407        let source = if mask_bit(&mask_buf, i) { vs1 } else { vs2 };
408        let val = env
409            .read_vregs()
410            .read(source, i)
411            .expect("`vs1` and `vs2` have the same `vl` as `vd`, checked above; qed");
412        env.write_vregs()
413            .write(vd, i, val)
414            .expect("`i < vl` of `vd`; qed");
415    }
416    env.mark_vs_dirty();
417}
418
419/// Execute vmerge.vxm / vmerge.vim / vmv.v.x / vmv.v.i.
420///
421/// When `vm=true`: all active elements `0..vl` get `scalar`; vs2 unused.
422/// When `vm=false`: active elements where `v0[i]=1` get `scalar`,
423/// inactive elements get `vs2[i]`.
424///
425/// Both register groups must have the same `vl`, which holds for groups created from the same
426/// configuration, nothing is written otherwise.
427#[inline(always)]
428#[doc(hidden)]
429#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
430pub fn execute_merge_scalar<Reg, Env>(
431    env: &mut Env,
432    vd: VRegGroup<Env::Hart>,
433    vs2: VRegGroup<Env::Hart>,
434    vm: bool,
435    scalar: u64,
436) where
437    Reg: Register,
438    Env: VectorRegistersExt<Hart: HartConfig<Reg = Reg>>,
439{
440    let vl = vd.vl();
441    let Some(vs2) = vs2.with_same_vl(vl) else {
442        cold_path();
443        return;
444    };
445    let mask_buf = snapshot_mask(env.read_vregs(), vm);
446
447    for i in vl.indices() {
448        let val = if mask_bit(&mask_buf, i) {
449            scalar
450        } else {
451            env.read_vregs()
452                .read(vs2, i)
453                .expect("`vs2` has the same `vl` as `vd`, checked above; qed")
454        };
455        env.write_vregs()
456            .write(vd, i, val)
457            .expect("`i < vl` of `vd`; qed");
458    }
459    env.mark_vs_dirty();
460}
461
462/// Execute vcompress.vm: pack active elements of vs2 (under vs1 mask) sequentially into vd.
463///
464/// `vs1` is treated as an explicit mask register (single register, not LMUL-grouped).
465/// The output write index increments only for elements where `vs1[i]` is set.
466/// vd must not overlap vs1 or vs2.
467///
468/// Both register groups must have the same `vl`, which holds for groups created from the same
469/// configuration, nothing is written otherwise.
470#[inline(always)]
471#[doc(hidden)]
472#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
473pub fn execute_compress<Reg, Env>(
474    env: &mut Env,
475    vd: VRegGroup<Env::Hart>,
476    vs2: VRegGroup<Env::Hart>,
477    vs1: VReg,
478) where
479    Reg: Register,
480    Env: VectorRegistersExt<Hart: HartConfig<Reg = Reg>>,
481{
482    let vl = vd.vl();
483    let Some(vs2) = vs2.with_same_vl(vl) else {
484        cold_path();
485        return;
486    };
487    // The whole register is copied, which needs no bounds check, only bits below `vl` are read
488    let vs1_buf = *env.read_vregs().get(vs1);
489    let mut out_indices = vl.indices();
490    for i in vl.indices() {
491        if !mask_bit(&vs1_buf, i) {
492            continue;
493        }
494        let val = env
495            .read_vregs()
496            .read(vs2, i)
497            .expect("`vs2` has the same `vl` as `vd`, checked above; qed");
498        // There are as many output indices as input indices, and output only advances when
499        // input does
500        let Some(out_idx) = out_indices.next() else {
501            break;
502        };
503        env.write_vregs()
504            .write(vd, out_idx, val)
505            .expect("`out_idx < vl` of `vd`; qed");
506    }
507    env.mark_vs_dirty();
508}
509
510/// Copy `COUNT` whole vector registers from `src_base` to `dst_base`.
511///
512/// No masking, no vtype dependency. Uses snapshot semantics: all source registers are read into
513/// a stack buffer before any destination registers are written, giving correct memmove-style
514/// behaviour for all overlap patterns (including partial overlap such as src=V0, dst=V1, count=2).
515///
516/// Returns `None` if either register group doesn't fit into the register file, which doesn't
517/// happen for groups aligned to `COUNT`.
518#[inline(always)]
519#[doc(hidden)]
520#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
521pub fn execute_whole_reg_move<const COUNT: usize, Hart>(
522    vregs: &mut VectorRegisterFile<Hart>,
523    dst_base: VReg,
524    src_base: VReg,
525) -> Option<()>
526where
527    Hart: VectorHartConfig,
528{
529    let registers = vregs.as_bytes_mut();
530    // Snapshot all source registers before writing any destination registers.
531    // This is correct for all overlap patterns without direction-dependent logic.
532    let src = *registers
533        .get(usize::from(src_base.to_bits())..)?
534        .first_chunk::<COUNT>()?;
535    *registers
536        .get_mut(usize::from(dst_base.to_bits())..)?
537        .first_chunk_mut::<COUNT>()? = src;
538    Some(())
539}