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;
9
10/// Effective element width of a register operand at `SEW`
11const SEW_EEW<const SEW: Vsew>: Eew = SEW.as_eew();
12
13/// Check that `vreg` (`vd`/`vs`) is aligned to `group_regs` and fits within `[0, 32)`
14#[inline(always)]
15#[doc(hidden)]
16#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
17pub fn check_vreg_group_alignment<Reg, Memory, PC>(
18    program_counter: &PC,
19    vreg: VReg,
20    group_regs: VRegGroupSize,
21) -> Result<(), ExecutionError<Reg::Type>>
22where
23    Reg: Register,
24    PC: ProgramCounter<Reg::Type, Memory>,
25{
26    if !vreg.is_group_aligned(group_regs) || vreg.to_bits() + group_regs.get() > 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: VRegGroupSize,
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/// Write one mask bit (the comparison result for element `elem_i`) into register `vd`.
77///
78/// Bits are stored LSB-first: element `i` lives at byte `i / 8`, bit `i % 8`.
79/// Only the target bit is modified; all other bits are undisturbed (tail-undisturbed semantics
80/// required for mask destinations per spec §5.3).
81///
82/// # Safety
83/// `elem_i / 8 < VLEN.bytes()` must hold, i.e. `elem_i < VLEN`. This is guaranteed when
84/// `elem_i < vl <= VLMAX <= VLEN`.
85#[inline(always)]
86#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
87pub(in super::super) unsafe fn write_mask_bit<const VLEN: Vlen>(
88    vregs: &mut VectorRegisterFile<VLEN>,
89    vd: VReg,
90    elem_i: u16,
91    result: bool,
92) {
93    let byte_idx = usize::from(elem_i / u8::BITS as u16);
94    let bit_idx = elem_i % u8::BITS as u16;
95    // SAFETY: `byte_idx < VLEN.bytes()` by the caller's precondition
96    let byte = unsafe { vregs.get_mut(vd).get_unchecked_mut(byte_idx) };
97    if result {
98        *byte |= 1 << bit_idx;
99    } else {
100        *byte &= !(1 << bit_idx);
101    }
102}
103
104/// Operand source
105#[derive(Debug)]
106#[doc(hidden)]
107pub enum OpSrc {
108    /// Vector-vector: source register index
109    Vreg(VReg),
110    /// Vector-scalar: scalar value (sign- or zero-extended to u64)
111    Scalar(u64),
112}
113
114/// Execute a single-width element-wise arithmetic operation over `vstart..vl`.
115///
116/// `op` receives `(vs2_elem: u64, src_elem: u64, sew: Vsew)` and returns the `u64` result (only the
117/// low `sew.bits_width()` are written back).
118///
119/// # Safety
120/// - `vd.to_bits() % group_regs == 0` and `vd.to_bits() + group_regs <= 32` (verified by caller)
121/// - `src` register (when `OpSrc::Vreg`) satisfies the same alignment (verified by caller)
122/// - `vl <= group_regs * VLEN.bytes() / sew_bytes` (all `vl` elements fit within the register
123///   group)
124/// - When `vm=false`: `vd.to_bits() != 0` (vd does not overlap v0)
125#[inline(always)]
126#[doc(hidden)]
127#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
128pub unsafe fn execute_arith_op<Reg, Env, F>(
129    env: &mut Env,
130    vd: VReg,
131    vs2: VReg,
132    src: OpSrc,
133    vm: bool,
134    sew: Vsew,
135    op: F,
136) where
137    Reg: Register,
138    Env: VectorRegistersExt<Reg>,
139    [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
140    F: Fn(u64, u64, Vsew) -> u64,
141{
142    // Dispatch on the element width once, so that the loop below is compiled for each width
143    // separately, with element loads and stores of a constant size
144    //
145    // SAFETY: Guaranteed by the caller's precondition
146    unsafe {
147        match sew {
148            Vsew::E8 => {
149                execute_arith_op_const::<{ Vsew::E8 }, _, _, _>(env, vd, vs2, src, vm, op);
150            }
151            Vsew::E16 => {
152                execute_arith_op_const::<{ Vsew::E16 }, _, _, _>(env, vd, vs2, src, vm, op);
153            }
154            Vsew::E32 => {
155                execute_arith_op_const::<{ Vsew::E32 }, _, _, _>(env, vd, vs2, src, vm, op);
156            }
157            Vsew::E64 => {
158                execute_arith_op_const::<{ Vsew::E64 }, _, _, _>(env, vd, vs2, src, vm, op);
159            }
160        }
161    }
162}
163
164/// [`execute_arith_op()`] with the element width known at compile time
165///
166/// # Safety
167/// Same as [`execute_arith_op()`]
168#[inline(always)]
169#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
170unsafe fn execute_arith_op_const<const SEW: Vsew, Reg, Env, F>(
171    env: &mut Env,
172    vd: VReg,
173    vs2: VReg,
174    src: OpSrc,
175    vm: bool,
176    op: F,
177) where
178    Reg: Register,
179    Env: VectorRegistersExt<Reg>,
180    [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
181    F: Fn(u64, u64, Vsew) -> u64,
182{
183    let vl = env.vl();
184    let vstart = env.vstart();
185    let vregs = env.write_vregs();
186
187    for i in vstart.range_to(vl) {
188        // `vd` never overlaps `v0` when masked, so the mask can be read in place rather than
189        // snapshotted, no write below can modify it
190        if !vm && !mask_bit(vregs.get(VReg::V0), i) {
191            continue;
192        }
193
194        // SAFETY: `vs2 % group_regs == 0` and `i < vl <= group_regs * elems_per_reg`, so
195        // `vs2 + i / elems_per_reg < vs2 + group_regs <= 32`
196        let a = unsafe { vregs.read_element_const::<{ SEW_EEW::<SEW> }>(vs2, i) };
197
198        let b = match src {
199            OpSrc::Vreg(vs1_base) => {
200                // SAFETY: same argument as vs2
201                unsafe { vregs.read_element_const::<{ SEW_EEW::<SEW> }>(vs1_base, i) }
202            }
203            OpSrc::Scalar(val) => val,
204        };
205
206        let result = op(a, b, SEW);
207
208        // SAFETY: `vd % group_regs == 0` and `i < vl <= group_regs * elems_per_reg`, so
209        // `vd + i / elems_per_reg < vd + group_regs <= 32`
210        unsafe {
211            vregs.write_element_const::<{ SEW_EEW::<SEW> }>(vd, i, result);
212        }
213    }
214
215    env.mark_vs_dirty();
216    env.reset_vstart();
217}
218
219/// Execute a single-width element-wise integer compare over `vstart..vl`, writing one result
220/// bit per element into the mask register `vd`.
221///
222/// `op` receives `(vs2_elem: u64, src_elem: u64, sew: Vsew) -> bool`.
223///
224/// Mask destination tail bits (indices `>= vl`) are always left undisturbed per spec §5.3,
225/// regardless of `vta`. Only bits in `vstart..vl` are written.
226///
227/// # Safety
228/// - `vs2.to_bits() % group_regs == 0` and `vs2.to_bits() + group_regs <= 32` (verified by caller)
229/// - `src` register (when `OpSrc::Vreg`) satisfies the same alignment (verified by caller)
230/// - `vl <= group_regs * VLEN.bytes() / sew_bytes`
231/// - `vl <= VLEN` (so every element index fits within the mask register)
232#[inline(always)]
233#[doc(hidden)]
234#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
235pub unsafe fn execute_compare_op<Reg, Env, F>(
236    env: &mut Env,
237    vd: VReg,
238    vs2: VReg,
239    src: OpSrc,
240    vm: bool,
241    sew: Vsew,
242    op: F,
243) where
244    Reg: Register,
245    Env: VectorRegistersExt<Reg>,
246    [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
247    F: Fn(u64, u64, Vsew) -> bool,
248{
249    let vl = env.vl();
250    let vstart = env.vstart();
251    // SAFETY: `vl <= VLEN`, so `vl.div_ceil(8) <= VLEN.bytes()`.
252    let mask_buf = unsafe { snapshot_mask(env.read_vregs(), vm, vl) };
253
254    for i in vstart.range_to(vl) {
255        // When masked, inactive elements in the destination mask register are left undisturbed
256        // (spec §12.8: "mask register results follow mask-undisturbed policy")
257        if !mask_bit(&mask_buf, i) {
258            continue;
259        }
260
261        // SAFETY: same argument as in `execute_arith_op`
262        let a = unsafe { env.read_vregs().read_element(vs2, i, sew) };
263
264        let b = match src {
265            OpSrc::Vreg(vs1_base) => {
266                // SAFETY: same argument as vs2
267                unsafe { env.read_vregs().read_element(vs1_base, i, sew) }
268            }
269            OpSrc::Scalar(val) => val,
270        };
271
272        let result = op(a, b, sew);
273
274        // SAFETY: `i < vl <= VLMAX <= VLEN`, so `i / 8 < VLEN / 8 = VLEN.bytes()`
275        unsafe {
276            write_mask_bit(env.write_vregs(), vd, i, result);
277        }
278    }
279
280    env.mark_vs_dirty();
281    env.reset_vstart();
282}
283
284/// Sign-extend the low `sew.bits_width()` of `val` to a full `i64`
285#[inline(always)]
286#[doc(hidden)]
287#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
288pub fn sign_extend(val: u64, sew: Vsew) -> i64 {
289    let shift = u64::BITS - u32::from(sew.bits_width());
290    (val.cast_signed() << shift) >> shift
291}
292
293/// Mask off the upper bits of a `u64` to leave only the low `sew.bits_width()`.
294///
295/// Used for unsigned arithmetic and comparisons where only the SEW-wide portion is significant. For
296/// SEW = 64 this is a no-op (all bits are significant).
297#[inline(always)]
298#[doc(hidden)]
299#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
300pub fn sew_mask(sew: Vsew) -> u64 {
301    if u32::from(sew.bits_width()) == u64::BITS {
302        u64::MAX
303    } else {
304        (1u64 << sew.bits_width()) - 1
305    }
306}