Skip to main content

ab_riscv_interpreter/v/zvexx/mask/
zvexx_mask_helpers.rs

1//! Opaque helpers for ZveXx extension
2
3use crate::v::vector_registers::VectorRegistersExt;
4use crate::v::zvexx::arith::zvexx_arith_helpers::{write_element_u64, write_mask_bit};
5use crate::v::zvexx::load::zvexx_load_helpers::{mask_bit, snapshot_mask};
6use ab_riscv_primitives::prelude::*;
7use core::fmt;
8
9/// Execute a mask-register logical operation (§16.1).
10///
11/// Computes the result for the body elements `[vstart, vl)` only. Prestart bits `[0, vstart)`
12/// are left undisturbed, and tail bits `[vl, VLEN)` follow the tail-agnostic policy, realised
13/// here as undisturbed (a permitted agnostic implementation and the one the reference model
14/// produces). `op` receives `(vs2_bit: bool, vs1_bit: bool) -> bool`.
15///
16/// # Safety
17/// `vd`, `vs2`, and `vs1` are valid register indices (guaranteed by `VReg`).
18/// `vl <= VLEN`, so `(vl - 1) / 8 < VLEN.bytes()`; `vstart <= vl` by the architectural invariant.
19/// The operation snapshots both sources before writing, so `vd` may safely overlap either source.
20#[inline(always)]
21#[doc(hidden)]
22// TODO: #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
23pub unsafe fn execute_mask_logical_op<Reg, ExtState, CustomError, F>(
24    ext_state: &mut ExtState,
25    vd: VReg,
26    vs2: VReg,
27    vs1: VReg,
28    op: F,
29) where
30    Reg: Register,
31    ExtState: VectorRegistersExt<Reg, CustomError>,
32    [(); SUPPORTED_ELEN_VLEN::<{ ExtState::ELEN }, { ExtState::VLEN }>]:,
33    CustomError: fmt::Debug,
34    F: Fn(bool, bool) -> bool,
35{
36    let vl = ext_state.vl();
37    let vstart = ext_state.vstart();
38    // Snapshot both sources before writing to handle vd overlapping vs2 or vs1
39    let vs2_snap = *ext_state.read_vregs().get(vs2);
40    let vs1_snap = *ext_state.read_vregs().get(vs1);
41    // Body elements [vstart, vl): compute the logical operation bit-by-bit. Prestart bits
42    // [0, vstart) and tail bits [vl, VLEN) are left undisturbed.
43    for i in vstart.range_to(vl) {
44        let a = mask_bit(&vs2_snap, i);
45        let b = mask_bit(&vs1_snap, i);
46        // SAFETY: `i < vl <= VLEN`
47        unsafe {
48            write_mask_bit(ext_state.write_vregs(), vd, i, op(a, b));
49        }
50    }
51    ext_state.mark_vs_dirty();
52    ext_state.reset_vstart();
53}
54
55/// Execute `vcpop.m`: count set bits in vs2 for active elements `Vstart::ZERO.range_to(vl)`, write
56/// result to `rd`.
57///
58/// Per spec §16.2: `rd` receives the number of mask bits set in `vs2`, considering only elements
59/// `vstart..vl` that are active under the mask. For elements `< vstart`, they are not counted.
60///
61/// # Safety
62/// - `vl <= VLEN`
63/// - `vstart <= vl`
64///
65/// Returns `rd_value`.
66#[inline(always)]
67#[doc(hidden)]
68// TODO: #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
69pub unsafe fn execute_vcpop<Reg, ExtState, CustomError>(
70    ext_state: &mut ExtState,
71    vs2: VReg,
72    vm: bool,
73) -> Reg::Type
74where
75    Reg: Register,
76    ExtState: VectorRegistersExt<Reg, CustomError>,
77    [(); SUPPORTED_ELEN_VLEN::<{ ExtState::ELEN }, { ExtState::VLEN }>]:,
78    CustomError: fmt::Debug,
79{
80    let vl = ext_state.vl();
81    let vstart = ext_state.vstart();
82    // SAFETY: `vl <= VLEN`
83    let mask_buf = unsafe { snapshot_mask(ext_state.read_vregs(), vm, vl) };
84    let vs2_reg = *ext_state.read_vregs().get(vs2);
85    let mut count = 0u32;
86    for i in vstart.range_to(vl) {
87        if !mask_bit(&mask_buf, i) {
88            continue;
89        }
90        if mask_bit(&vs2_reg, i) {
91            count += 1;
92        }
93    }
94
95    ext_state.mark_vs_dirty();
96    ext_state.reset_vstart();
97
98    Reg::Type::from(count)
99}
100
101/// Execute `vfirst.m`: find the index of the first set bit in vs2 for active elements
102/// `Vstart::ZERO.range_to(vl)`, write result (or -1 if none) to `rd`.
103///
104/// Per spec §16.3: `rd` receives the element index of the lowest-numbered active set bit, or
105/// `-1` (all-ones) if no active element of vs2 is set.
106///
107/// # Safety
108/// - `vl <= VLEN`
109/// - `vstart <= vl`
110///
111/// Returns `rd_value`.
112#[inline(always)]
113#[doc(hidden)]
114// TODO: #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
115pub unsafe fn execute_vfirst<Reg, ExtState, CustomError>(
116    ext_state: &mut ExtState,
117    vs2: VReg,
118    vm: bool,
119) -> Reg::Type
120where
121    Reg: Register,
122    ExtState: VectorRegistersExt<Reg, CustomError>,
123    [(); SUPPORTED_ELEN_VLEN::<{ ExtState::ELEN }, { ExtState::VLEN }>]:,
124    CustomError: fmt::Debug,
125{
126    let vl = ext_state.vl();
127    let vstart = ext_state.vstart();
128    // SAFETY: `vl <= VLEN`
129    let mask_buf = unsafe { snapshot_mask(ext_state.read_vregs(), vm, vl) };
130    let vs2_reg = *ext_state.read_vregs().get(vs2);
131    // -1 encoded as all-ones for the register width; `Into<u64>` on XLEN-wide type then back
132    let not_found = u64::MAX;
133    let mut result = not_found;
134    for i in vstart.range_to(vl) {
135        if !mask_bit(&mask_buf, i) {
136            continue;
137        }
138        if mask_bit(&vs2_reg, i) {
139            result = u64::from(i);
140            break;
141        }
142    }
143    // Write -1 (all-ones for XLEN bits) or the found index.
144    // The spec requires -1 as a signed XLEN-wide value, meaning all bits set.
145    // `!Reg::Type::from(0)` produces all-ones for both u32 (RV32) and u64 (RV64)
146    // without depending on `From<u64>` (which is not in the `Register` trait bounds).
147    // For the found index, element indices fit in u32 since vl <= VLEN <= 2^32.
148    let rd_value = if result == not_found {
149        !Reg::Type::from(0u8)
150    } else {
151        Reg::Type::from(result as u32)
152    };
153    ext_state.mark_vs_dirty();
154    ext_state.reset_vstart();
155
156    rd_value
157}
158
159/// Execute `vmsbf.m`: set all mask bits before (not including) the first set bit of vs2.
160///
161/// Per spec §16.4: for each element `i` in `vstart..vl`, if no prior active set bit exists in
162/// vs2, the destination bit is set; once the first set bit in vs2 is encountered, all subsequent
163/// destination bits are cleared.
164///
165/// Inactive elements (masked off) are left undisturbed. Tail elements are undisturbed.
166///
167/// # Safety
168/// - `vd` does not overlap `vs2` (checked by caller)
169/// - `vm=false` implies `vd != v0` (checked by caller)
170/// - `vl <= VLEN`
171#[inline(always)]
172#[doc(hidden)]
173#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
174pub unsafe fn execute_vmsbf<Reg, ExtState, CustomError>(
175    ext_state: &mut ExtState,
176    vd: VReg,
177    vs2: VReg,
178    vm: bool,
179    vl: Vl,
180) where
181    Reg: Register,
182    ExtState: VectorRegistersExt<Reg, CustomError>,
183    [(); SUPPORTED_ELEN_VLEN::<{ ExtState::ELEN }, { ExtState::VLEN }>]:,
184    CustomError: fmt::Debug,
185{
186    // SAFETY: `vl <= VLEN`
187    let mask_buf = unsafe { snapshot_mask(ext_state.read_vregs(), vm, vl) };
188    let vs2_snap = *ext_state.read_vregs().get(vs2);
189    let mut found_first = false;
190    for i in Vstart::ZERO.range_to(vl) {
191        // Inactive elements: undisturbed
192        if !mask_bit(&mask_buf, i) {
193            continue;
194        }
195        let vs2_bit = mask_bit(&vs2_snap, i);
196        // vmsbf: set bits strictly *before* the first set bit; clear from first set bit onward
197        let result = !found_first && !vs2_bit;
198        if vs2_bit {
199            found_first = true;
200        }
201        // SAFETY: `i < vl <= VLEN`
202        unsafe {
203            write_mask_bit(ext_state.write_vregs(), vd, i, result);
204        }
205    }
206    ext_state.mark_vs_dirty();
207    // vstart is already zero, doesn't need to be reset
208}
209
210/// Execute `vmsof.m`: set only the first set bit position of vs2, clear all others.
211///
212/// Per spec §16.5: the destination bit is set only at the lowest-numbered active element where
213/// vs2 has a set bit. All other active destination bits are cleared.
214///
215/// # Safety
216/// Same as [`execute_vmsbf`].
217#[inline(always)]
218#[doc(hidden)]
219#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
220pub unsafe fn execute_vmsof<Reg, ExtState, CustomError>(
221    ext_state: &mut ExtState,
222    vd: VReg,
223    vs2: VReg,
224    vm: bool,
225    vl: Vl,
226) where
227    Reg: Register,
228    ExtState: VectorRegistersExt<Reg, CustomError>,
229    [(); SUPPORTED_ELEN_VLEN::<{ ExtState::ELEN }, { ExtState::VLEN }>]:,
230    CustomError: fmt::Debug,
231{
232    // SAFETY: `vl <= VLEN`
233    let mask_buf = unsafe { snapshot_mask(ext_state.read_vregs(), vm, vl) };
234    let vs2_snap = *ext_state.read_vregs().get(vs2);
235    let mut found_first = false;
236    for i in Vstart::ZERO.range_to(vl) {
237        if !mask_bit(&mask_buf, i) {
238            continue;
239        }
240        let vs2_bit = mask_bit(&vs2_snap, i);
241        // vmsof: set only the first set bit position; clear all others (including after first)
242        let result = !found_first && vs2_bit;
243        if vs2_bit && !found_first {
244            found_first = true;
245        }
246        // SAFETY: `i < vl <= VLEN`
247        unsafe {
248            write_mask_bit(ext_state.write_vregs(), vd, i, result);
249        }
250    }
251    ext_state.mark_vs_dirty();
252    // vstart is already zero, doesn't need to be reset
253}
254
255/// Execute `vmsif.m`: set all mask bits up to and including the first set bit of vs2.
256///
257/// Per spec §16.6: for each active element, the destination bit is set if no prior active set bit
258/// in vs2 has been seen yet *or* the current element itself is set; it is cleared once a set bit
259/// has been seen and the current element is past it.
260///
261/// # Safety
262/// Same as [`execute_vmsbf`].
263#[inline(always)]
264#[doc(hidden)]
265#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
266pub unsafe fn execute_vmsif<Reg, ExtState, CustomError>(
267    ext_state: &mut ExtState,
268    vd: VReg,
269    vs2: VReg,
270    vm: bool,
271    vl: Vl,
272) where
273    Reg: Register,
274    ExtState: VectorRegistersExt<Reg, CustomError>,
275    [(); SUPPORTED_ELEN_VLEN::<{ ExtState::ELEN }, { ExtState::VLEN }>]:,
276    CustomError: fmt::Debug,
277{
278    // SAFETY: `vl <= VLEN`
279    let mask_buf = unsafe { snapshot_mask(ext_state.read_vregs(), vm, vl) };
280    let vs2_snap = *ext_state.read_vregs().get(vs2);
281    let mut found_first = false;
282    for i in Vstart::ZERO.range_to(vl) {
283        if !mask_bit(&mask_buf, i) {
284            continue;
285        }
286        let vs2_bit = mask_bit(&vs2_snap, i);
287        // vmsif: set bits up to *and including* the first set bit; clear elements past it
288        let result = !found_first;
289        if vs2_bit {
290            found_first = true;
291        }
292        // SAFETY: `i < vl <= VLEN`
293        unsafe {
294            write_mask_bit(ext_state.write_vregs(), vd, i, result);
295        }
296    }
297    ext_state.mark_vs_dirty();
298    // vstart is already zero, doesn't need to be reset
299}
300
301/// Execute `viota.m`: for each active element `i`, write the popcount of set bits in vs2 at
302/// positions `0..i` (strictly before `i`) as a SEW-wide integer into `vd[i]`.
303///
304/// Per spec §16.8: this instruction honors the source mask; inactive mask elements of vs2 are
305/// treated as zero for the prefix sum. Inactive destination elements follow the mask-agnostic
306/// policy (here implemented as undisturbed, which is a permitted realisation).
307///
308/// If SEW is too narrow to hold the prefix count, the value wraps (truncates to SEW) via
309/// [`write_element_u64()`]; the spec does not raise an exception for this case.
310///
311/// The caller must reject `vstart != 0` before invocation (spec §16.8 mandatory trap).
312///
313/// # Safety
314/// - `vd` does not overlap `vs2` (checked by caller)
315/// - `vm=false` implies `vd != v0` (checked by caller)
316/// - `vd.to_bits() % group_regs == 0` and `vd.to_bits() + group_regs <= 32` (checked by caller)
317/// - `vl <= VLMAX`; `vl <= VLEN`
318#[inline(always)]
319#[doc(hidden)]
320// TODO: #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
321pub unsafe fn execute_viota<Reg, ExtState, CustomError>(
322    ext_state: &mut ExtState,
323    vd: VReg,
324    vs2: VReg,
325    vm: bool,
326    vl: Vl,
327    sew: Vsew,
328) where
329    Reg: Register,
330    ExtState: VectorRegistersExt<Reg, CustomError>,
331    [(); SUPPORTED_ELEN_VLEN::<{ ExtState::ELEN }, { ExtState::VLEN }>]:,
332    CustomError: fmt::Debug,
333{
334    // SAFETY: `vl <= VLEN`
335    let mask_buf = unsafe { snapshot_mask(ext_state.read_vregs(), vm, vl) };
336    let vs2_snap = *ext_state.read_vregs().get(vs2);
337    // Per spec §16.8: inactive vs2 elements are treated as zero for the prefix sum.
338    // The prefix count advances only when the execution mask is active AND the
339    // corresponding vs2 bit is set.
340    let mut prefix_count = 0u64;
341    for i in Vstart::ZERO.range_to(vl) {
342        if !mask_bit(&mask_buf, i) {
343            continue;
344        }
345        // SAFETY: `vd + i / elems_per_reg < 32` by caller's alignment + vl preconditions
346        unsafe {
347            write_element_u64(ext_state.write_vregs(), vd, i, sew, prefix_count);
348        }
349        if mask_bit(&vs2_snap, i) {
350            prefix_count += 1;
351        }
352    }
353    ext_state.mark_vs_dirty();
354    ext_state.reset_vstart();
355}
356
357/// Execute `vid.v`: write the element index `i` as a SEW-wide integer into `vd[i]` for each
358/// active element in `vstart..vl`.
359///
360/// Per spec §16.9: inactive elements are left undisturbed (mask-undisturbed policy).
361///
362/// # Safety
363/// - `vm=false` implies `vd != v0` (checked by caller)
364/// - `vd.to_bits() % group_regs == 0` and `vd.to_bits() + group_regs <= 32` (checked by caller)
365/// - `vl <= group_regs * VLEN.bytes() / sew_bytes`
366/// - `vl <= VLEN`
367#[inline(always)]
368#[doc(hidden)]
369// TODO: #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
370pub unsafe fn execute_vid<Reg, ExtState, CustomError>(
371    ext_state: &mut ExtState,
372    vd: VReg,
373    vm: bool,
374    sew: Vsew,
375) where
376    Reg: Register,
377    ExtState: VectorRegistersExt<Reg, CustomError>,
378    [(); SUPPORTED_ELEN_VLEN::<{ ExtState::ELEN }, { ExtState::VLEN }>]:,
379    CustomError: fmt::Debug,
380{
381    let vl = ext_state.vl();
382    let vstart = ext_state.vstart();
383    // SAFETY: `vl <= VLEN`
384    let mask_buf = unsafe { snapshot_mask(ext_state.read_vregs(), vm, vl) };
385    for i in vstart.range_to(vl) {
386        if !mask_bit(&mask_buf, i) {
387            continue;
388        }
389        // SAFETY: `vd + i / elems_per_reg < 32` by caller's alignment + vl preconditions
390        unsafe {
391            write_element_u64(ext_state.write_vregs(), vd, i, sew, u64::from(i));
392        }
393    }
394    ext_state.mark_vs_dirty();
395    ext_state.reset_vstart();
396}