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