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}