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}