Skip to main content

ab_riscv_interpreter/v/zvexx/arith/
zvexx_arith_helpers.rs

1//! Opaque helpers for ZveXx extension
2
3use crate::v::vector_registers::{VectorRegisterFile, VectorRegistersExt};
4use crate::v::zvexx::load::zvexx_load_helpers::{mask_bit, snapshot_mask};
5use crate::v::zvexx::zvexx_helpers::INSTRUCTION_SIZE;
6use crate::{ExecutionError, PackedAddress, ProgramCounter};
7use ab_riscv_primitives::prelude::*;
8use core::hint::cold_path;
9use core::num::NonZeroU8;
10
11/// Check that `vreg` (`vd`/`vs`) is aligned to `group_regs` and fits within `[0, 32)`
12#[inline(always)]
13#[doc(hidden)]
14#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
15pub fn check_vreg_group_alignment<Reg, Memory, PC>(
16    program_counter: &PC,
17    vreg: VReg,
18    group_regs: NonZeroU8,
19) -> Result<(), ExecutionError<Reg::Type>>
20where
21    Reg: Register,
22    PC: ProgramCounter<Reg::Type, Memory>,
23{
24    let group_regs = group_regs.get();
25    let vreg_idx = vreg.to_bits();
26    if !vreg_idx.is_multiple_of(group_regs) || vreg_idx + group_regs > 32 {
27        cold_path();
28        return Err(ExecutionError::IllegalInstruction {
29            address: PackedAddress::new(program_counter.old_pc(INSTRUCTION_SIZE)),
30        });
31    }
32    Ok(())
33}
34
35/// Check mask-destination / source overlap constraint for compare/carry instructions.
36///
37/// Per RVV §5.2's narrowing-destination overlap rule (a mask destination has EEW=1, narrower
38/// than any source EEW), `vd` may overlap a multi-register source group only in the
39/// lowest-numbered register of that group. Overlapping any other register in the group is
40/// reserved: `execute_compare_op`/`execute_carry_*_mask` process elements in increasing index
41/// order and write one mask bit per element into `vd`. When `vd` is the group's base register,
42/// every mask byte written during the processing of register `base_reg` targets bytes that hold
43/// data from elements at or before the one just read (byte `b` can only be touched while
44/// processing elements `>= 8*b`, and it stores raw data for element `b / sew_bytes <= 8*b`, which
45/// is always read first). When `vd` is any later register in the group, that guarantee no longer
46/// holds: early elements from the group's *first* register write mask bits into byte 0 of `vd`,
47/// which is where the not-yet-read data of a later element (stored in `vd` itself) lives,
48/// corrupting it before it is read.
49#[inline(always)]
50#[doc(hidden)]
51#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
52pub fn check_mask_dest_overlap<Reg, Memory, PC>(
53    program_counter: &PC,
54    vd: VReg,
55    src_base: VReg,
56    group_regs: NonZeroU8,
57) -> Result<(), ExecutionError<Reg::Type>>
58where
59    Reg: Register,
60    PC: ProgramCounter<Reg::Type, Memory>,
61{
62    let group_regs = group_regs.get();
63    if group_regs > 1 {
64        let vd_idx = vd.to_bits();
65        let src = src_base.to_bits();
66        if vd_idx > src && vd_idx < src + group_regs {
67            cold_path();
68            return Err(ExecutionError::IllegalInstruction {
69                address: PackedAddress::new(program_counter.old_pc(INSTRUCTION_SIZE)),
70            });
71        }
72    }
73    Ok(())
74}
75
76/// Read a SEW-wide element from register group `[base_reg, base_reg + group_regs)` as `u64`.
77///
78/// Element `elem_i` occupies bytes at:
79///   - register `base_reg + elem_i / elems_per_reg`
80///   - byte offset `(elem_i % elems_per_reg) * sew_bytes`
81///
82/// The value is zero-extended to `u64`.
83///
84/// # Safety
85/// `base_reg + elem_i / (VLEN.bytes() / sew_bytes) < 32` must hold.
86#[inline(always)]
87#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
88pub(crate) unsafe fn read_element_u64<const VLEN: Vlen>(
89    vregs: &VectorRegisterFile<VLEN>,
90    base_reg: VReg,
91    elem_i: u16,
92    sew: Vsew,
93) -> u64 {
94    let sew_bytes = u32::from(sew.bytes_width());
95    let elems_per_reg = VLEN.bytes() / sew_bytes;
96    let reg_off = u32::from(elem_i) / elems_per_reg;
97    let byte_off = (u32::from(elem_i) % elems_per_reg) * sew_bytes;
98    // SAFETY: `base_reg + reg_off < 32` by caller's precondition
99    let reg = vregs
100        .get(unsafe { VReg::from_bits(base_reg.to_bits() + reg_off as u8).unwrap_unchecked() });
101    // SAFETY: `byte_off + sew_bytes <= VLEN.bytes()` because `byte_off` is at most
102    // `(elems_per_reg - 1) * sew_bytes = VLEN.bytes() - sew_bytes`
103    let src = unsafe { reg.get_unchecked(byte_off as usize..(byte_off + sew_bytes) as usize) };
104    let mut buf = [0u8; 8];
105    // SAFETY: `sew_bytes <= 8` for all `Vsew` variants
106    unsafe { buf.get_unchecked_mut(..sew_bytes as usize) }.copy_from_slice(src);
107    u64::from_le_bytes(buf)
108}
109
110/// Write a SEW-wide element (low `sew_bytes` of `value`) into register group
111/// `[base_reg, base_reg + group_regs)` at element index `elem_i`.
112///
113/// # Safety
114/// `base_reg + elem_i / (VLEN.bytes() / sew_bytes) < 32` must hold.
115#[inline(always)]
116#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
117pub(crate) unsafe fn write_element_u64<const VLEN: Vlen>(
118    vregs: &mut VectorRegisterFile<VLEN>,
119    base_reg: VReg,
120    elem_i: u16,
121    sew: Vsew,
122    value: u64,
123) {
124    let sew_bytes = u32::from(sew.bytes_width());
125    let elems_per_reg = VLEN.bytes() / sew_bytes;
126    let reg_off = u32::from(elem_i) / elems_per_reg;
127    let byte_off = (u32::from(elem_i) % elems_per_reg) * sew_bytes;
128    let buf = value.to_le_bytes();
129    // SAFETY: `base_reg + reg_off < 32` by caller's precondition
130    let reg = vregs
131        .get_mut(unsafe { VReg::from_bits(base_reg.to_bits() + reg_off as u8).unwrap_unchecked() });
132    // SAFETY: `byte_off + sew_bytes <= VLEN.bytes()` - same argument as `read_element_u64`.
133    // `sew_bytes <= 8` for all `Vsew` variants.
134    let dst = unsafe { reg.get_unchecked_mut(byte_off as usize..(byte_off + sew_bytes) as usize) };
135    // SAFETY: `sew_bytes <= 8` for all `Vsew` variants
136    dst.copy_from_slice(unsafe { buf.get_unchecked(..sew_bytes as usize) });
137}
138
139/// Write one mask bit (the comparison result for element `elem_i`) into register `vd`.
140///
141/// Bits are stored LSB-first: element `i` lives at byte `i / 8`, bit `i % 8`.
142/// Only the target bit is modified; all other bits are undisturbed (tail-undisturbed semantics
143/// required for mask destinations per spec §5.3).
144///
145/// # Safety
146/// `elem_i / 8 < VLEN.bytes()` must hold, i.e. `elem_i < VLEN`. This is guaranteed when
147/// `elem_i < vl <= VLMAX <= VLEN`.
148#[inline(always)]
149#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
150pub(in super::super) unsafe fn write_mask_bit<const VLEN: Vlen>(
151    vregs: &mut VectorRegisterFile<VLEN>,
152    vd: VReg,
153    elem_i: u16,
154    result: bool,
155) {
156    let byte_idx = usize::from(elem_i / u8::BITS as u16);
157    let bit_idx = elem_i % u8::BITS as u16;
158    // SAFETY: `byte_idx < VLEN.bytes()` by the caller's precondition
159    let byte = unsafe { vregs.get_mut(vd).get_unchecked_mut(byte_idx) };
160    if result {
161        *byte |= 1 << bit_idx;
162    } else {
163        *byte &= !(1 << bit_idx);
164    }
165}
166
167/// Operand source
168#[derive(Debug)]
169#[doc(hidden)]
170pub enum OpSrc {
171    /// Vector-vector: source register index
172    Vreg(VReg),
173    /// Vector-scalar: scalar value (sign- or zero-extended to u64)
174    Scalar(u64),
175}
176
177/// Execute a single-width element-wise arithmetic operation over `vstart..vl`.
178///
179/// `op` receives `(vs2_elem: u64, src_elem: u64, sew: Vsew)` and returns the `u64` result (only the
180/// low `sew.bits_width()` are written back).
181///
182/// # Safety
183/// - `vd.to_bits() % group_regs == 0` and `vd.to_bits() + group_regs <= 32` (verified by caller)
184/// - `src` register (when `OpSrc::Vreg`) satisfies the same alignment (verified by caller)
185/// - `vl <= group_regs * VLEN.bytes() / sew_bytes` (all `vl` elements fit within the register
186///   group)
187/// - When `vm=false`: `vd.to_bits() != 0` (vd does not overlap v0)
188#[inline(always)]
189#[doc(hidden)]
190#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
191pub unsafe fn execute_arith_op<Reg, Env, F>(
192    env: &mut Env,
193    vd: VReg,
194    vs2: VReg,
195    src: OpSrc,
196    vm: bool,
197    sew: Vsew,
198    op: F,
199) where
200    Reg: Register,
201    Env: VectorRegistersExt<Reg>,
202    [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
203    F: Fn(u64, u64, Vsew) -> u64,
204{
205    let vl = env.vl();
206    let vstart = env.vstart();
207    // SAFETY: `vl <= VLMAX <= VLEN`, so `vl.div_ceil(8) <= VLEN.bytes()`
208    let mask_buf = unsafe { snapshot_mask(env.read_vregs(), vm, vl) };
209
210    for i in vstart.range_to(vl) {
211        if !mask_bit(&mask_buf, i) {
212            continue;
213        }
214
215        // SAFETY: `vs2 % group_regs == 0` and `i < vl <= group_regs * elems_per_reg`, so
216        // `vs2 + i / elems_per_reg < vs2 + group_regs <= 32`
217        let a = unsafe { read_element_u64(env.read_vregs(), vs2, i, sew) };
218
219        let b = match src {
220            OpSrc::Vreg(vs1_base) => {
221                // SAFETY: same argument as vs2
222                unsafe { read_element_u64(env.read_vregs(), vs1_base, i, sew) }
223            }
224            OpSrc::Scalar(val) => val,
225        };
226
227        let result = op(a, b, sew);
228
229        // SAFETY: `vd % group_regs == 0` and `i < vl <= group_regs * elems_per_reg`, so
230        // `vd + i / elems_per_reg < vd + group_regs <= 32`
231        unsafe {
232            write_element_u64(env.write_vregs(), vd, i, sew, result);
233        }
234    }
235
236    env.mark_vs_dirty();
237    env.reset_vstart();
238}
239
240/// Execute a single-width element-wise integer compare over `vstart..vl`, writing one result
241/// bit per element into the mask register `vd`.
242///
243/// `op` receives `(vs2_elem: u64, src_elem: u64, sew: Vsew) -> bool`.
244///
245/// Mask destination tail bits (indices `>= vl`) are always left undisturbed per spec §5.3,
246/// regardless of `vta`. Only bits in `vstart..vl` are written.
247///
248/// # Safety
249/// - `vs2.to_bits() % group_regs == 0` and `vs2.to_bits() + group_regs <= 32` (verified by caller)
250/// - `src` register (when `OpSrc::Vreg`) satisfies the same alignment (verified by caller)
251/// - `vl <= group_regs * VLEN.bytes() / sew_bytes`
252/// - `vl <= VLEN` (so every element index fits within the mask register)
253#[inline(always)]
254#[doc(hidden)]
255#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
256pub unsafe fn execute_compare_op<Reg, Env, F>(
257    env: &mut Env,
258    vd: VReg,
259    vs2: VReg,
260    src: OpSrc,
261    vm: bool,
262    sew: Vsew,
263    op: F,
264) where
265    Reg: Register,
266    Env: VectorRegistersExt<Reg>,
267    [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
268    F: Fn(u64, u64, Vsew) -> bool,
269{
270    let vl = env.vl();
271    let vstart = env.vstart();
272    // SAFETY: `vl <= VLEN`, so `vl.div_ceil(8) <= VLEN.bytes()`.
273    let mask_buf = unsafe { snapshot_mask(env.read_vregs(), vm, vl) };
274
275    for i in vstart.range_to(vl) {
276        // When masked, inactive elements in the destination mask register are left undisturbed
277        // (spec §12.8: "mask register results follow mask-undisturbed policy")
278        if !mask_bit(&mask_buf, i) {
279            continue;
280        }
281
282        // SAFETY: same argument as in `execute_arith_op`
283        let a = unsafe { read_element_u64(env.read_vregs(), vs2, i, sew) };
284
285        let b = match src {
286            OpSrc::Vreg(vs1_base) => {
287                // SAFETY: same argument as vs2
288                unsafe { read_element_u64(env.read_vregs(), vs1_base, i, sew) }
289            }
290            OpSrc::Scalar(val) => val,
291        };
292
293        let result = op(a, b, sew);
294
295        // SAFETY: `i < vl <= VLMAX <= VLEN`, so `i / 8 < VLEN / 8 = VLEN.bytes()`
296        unsafe {
297            write_mask_bit(env.write_vregs(), vd, i, result);
298        }
299    }
300
301    env.mark_vs_dirty();
302    env.reset_vstart();
303}
304
305/// Sign-extend the low `sew.bits_width()` of `val` to a full `i64`
306#[inline(always)]
307#[doc(hidden)]
308#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
309pub fn sign_extend(val: u64, sew: Vsew) -> i64 {
310    let shift = u64::BITS - u32::from(sew.bits_width());
311    (val.cast_signed() << shift) >> shift
312}
313
314/// Mask off the upper bits of a `u64` to leave only the low `sew.bits_width()`.
315///
316/// Used for unsigned arithmetic and comparisons where only the SEW-wide portion is significant. For
317/// SEW = 64 this is a no-op (all bits are significant).
318#[inline(always)]
319#[doc(hidden)]
320#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
321pub fn sew_mask(sew: Vsew) -> u64 {
322    if u32::from(sew.bits_width()) == u64::BITS {
323        u64::MAX
324    } else {
325        (1u64 << sew.bits_width()) - 1
326    }
327}