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}