ab_riscv_interpreter/v/zvexx/muldiv/zvexx_muldiv_helpers.rs
1//! Opaque helpers for ZveXx extension
2
3use crate::v::vector_registers::VectorRegistersExt;
4pub use crate::v::zvexx::arith::zvexx_arith_helpers::{
5 OpSrc, check_vreg_group_alignment, sew_mask, sign_extend,
6};
7use crate::v::zvexx::load::zvexx_load_helpers::{mask_bit, snapshot_mask};
8use crate::v::zvexx::zvexx_helpers::INSTRUCTION_SIZE;
9use crate::{ExecutionError, PackedAddress, ProgramCounter};
10use ab_riscv_primitives::prelude::*;
11use core::hint::cold_path;
12
13/// Whether a widening operation is representable for the given `SEW` and `ELEN`.
14///
15/// Widening instructions produce a `2*SEW` result, and an EEW greater than `ELEN` is reserved by
16/// the RISC-V "V" spec §3.4.2 for *every* implementation. This is therefore not an extension-level
17/// restriction like the one on the high-half multiplies in Zve64x: no vector extension makes
18/// `SEW == ELEN` legal for a widening instruction.
19///
20/// This also underpins the safety preconditions of [`execute_widening_op()`],
21/// [`execute_widening_muladd_op()`] and [`execute_widening_muladd_scalar_op()`], because
22/// `ELEN <= 64` means `2*SEW <= 64` and the wide element fits in a `u64`.
23#[inline(always)]
24#[doc(hidden)]
25#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
26pub fn widening_eew_supported(sew: Vsew, elen: Elen) -> bool {
27 u32::from(sew.bits_width()) * 2 <= u32::from(elen)
28}
29
30/// Check that a narrower source register group does not *illegally* overlap the wider destination
31/// group of a widening instruction.
32///
33/// For widening instructions `vd` occupies `dest_group_regs` registers (which is
34/// [`widening_dest_register_count()`] of the source LMUL); `vs` occupies `src_group_regs`.
35///
36/// Per the RISC-V "V" spec §5.2, because the destination EEW (`2*SEW`) is greater than the source
37/// EEW (`SEW`), the source group *may* overlap the destination group, but only when both of the
38/// following hold:
39/// - the source EMUL is at least 1, and
40/// - the overlap is in the highest-numbered part of the destination register group, i.e. the source
41/// occupies exactly the top `src_group_regs` registers of the destination group.
42///
43/// When the source EMUL is at least 1, `dest_group_regs == 2 * src_group_regs`, so
44/// `dest_group_regs > src_group_regs` is an equivalent test for "source EMUL >= 1": a fractional
45/// source EMUL (`< 1`) yields `dest_group_regs == src_group_regs == 1`, in which case no overlap is
46/// ever legal. Any overlap that is not the legal "source in the highest-numbered part" form is
47/// rejected as an illegal instruction.
48#[inline(always)]
49#[doc(hidden)]
50#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
51pub fn check_no_widening_overlap<Reg, Memory, PC>(
52 program_counter: &PC,
53 vd: VReg,
54 vs: VReg,
55 dest_group_regs: VRegGroupSize,
56 src_group_regs: VRegGroupSize,
57) -> Result<(), ExecutionError<Reg::Type>>
58where
59 Reg: Register,
60 PC: ProgramCounter<Reg::Type, Memory>,
61{
62 let dest_group_regs = dest_group_regs.get();
63 let src_group_regs = src_group_regs.get();
64 let vd_start = vd.to_bits();
65 let vd_end = vd_start + dest_group_regs;
66 let vs_start = vs.to_bits();
67 let vs_end = vs_start + src_group_regs;
68 // Disjoint register groups are always fine
69 if vs_start >= vd_end || vd_start >= vs_end {
70 return Ok(());
71 }
72 // The groups overlap. This is legal only when the source EMUL is at least 1
73 // (`dest_group_regs > src_group_regs`) and the source occupies exactly the highest-numbered
74 // part of the destination group (`vs_start == vd_end - src_group_regs`).
75 if dest_group_regs > src_group_regs && vs_start == vd_end - src_group_regs {
76 return Ok(());
77 }
78
79 cold_path();
80 Err(ExecutionError::IllegalInstruction {
81 address: PackedAddress::new(program_counter.old_pc(INSTRUCTION_SIZE)),
82 })
83}
84
85/// Execute a single-width element-wise arithmetic operation over `vstart..vl`.
86///
87/// `op` receives `(vs2_elem: u64, src_elem: u64, sew: Vsew)` and returns the `u64` result.
88/// Only the low `sew.bytes()` of the result are written back.
89///
90/// # Safety
91/// - `vd` and source register alignment verified by caller
92/// - `vl <= group_regs * VLEN.bytes() / sew_bytes`
93/// - When `vm=false`: `vd.to_bits() != 0`
94#[inline(always)]
95#[doc(hidden)]
96#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
97pub unsafe fn execute_arith_op<Reg, Env, F>(
98 env: &mut Env,
99 vd: VReg,
100 vs2: VReg,
101 src: OpSrc,
102 vm: bool,
103 sew: Vsew,
104 op: F,
105) where
106 Reg: Register,
107 Env: VectorRegistersExt<Reg>,
108 [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
109 F: Fn(u64, u64, Vsew) -> u64,
110{
111 let vl = env.vl();
112 let vstart = env.vstart();
113 // SAFETY: `vl <= VLMAX <= VLEN`
114 let mask_buf = unsafe { snapshot_mask(env.read_vregs(), vm, vl) };
115 for i in vstart.range_to(vl) {
116 if !mask_bit(&mask_buf, i) {
117 continue;
118 }
119 // SAFETY: register bounds verified by caller
120 let a = unsafe { env.read_vregs().read_element(vs2, i, sew) };
121 let b = match src {
122 // SAFETY: register bounds verified by caller
123 OpSrc::Vreg(vs1_base) => unsafe { env.read_vregs().read_element(vs1_base, i, sew) },
124 OpSrc::Scalar(val) => val,
125 };
126 let result = op(a, b, sew);
127 // SAFETY: register bounds verified by caller
128 unsafe {
129 env.write_vregs().write_element(vd, i, sew, result);
130 }
131 }
132 env.mark_vs_dirty();
133 env.reset_vstart();
134}
135
136/// Execute a single-width widening operation over `vstart..vl`.
137///
138/// Reads SEW-wide elements from `vs2` and `src`, computes `op`, and writes a 2*SEW-wide result
139/// into `vd`.
140///
141/// # Safety
142/// - `vd` uses `dest_group_regs` registers (result of `widening_dest_register_count()`); alignment
143/// and non-overlap verified by caller
144/// - `vl <= src_group_regs * VLEN.bytes() / sew_bytes`
145/// - `2*SEW <= ELEN` verified by caller via [`widening_eew_supported()`], so the 2*SEW result fits
146/// in a `u64`; this holds for every implementation because an EEW may not exceed ELEN
147/// - When `vm=false`: `vd.to_bits() != 0`
148#[inline(always)]
149#[doc(hidden)]
150#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
151pub unsafe fn execute_widening_op<Reg, Env, F>(
152 env: &mut Env,
153 vd: VReg,
154 vs2: VReg,
155 src: OpSrc,
156 vm: bool,
157 sew: Vsew,
158 op: F,
159) where
160 Reg: Register,
161 Env: VectorRegistersExt<Reg>,
162 [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
163 F: Fn(u64, u64, Vsew) -> u64,
164{
165 // SAFETY: `2 * SEW <= ELEN` is a precondition, so the double width exists
166 let wide_sew = unsafe { sew.double_width().unwrap_unchecked() };
167 let vl = env.vl();
168 let vstart = env.vstart();
169 // SAFETY: `vl <= VLMAX <= VLEN`
170 let mask_buf = unsafe { snapshot_mask(env.read_vregs(), vm, vl) };
171 for i in vstart.range_to(vl) {
172 if !mask_bit(&mask_buf, i) {
173 continue;
174 }
175 // SAFETY: register bounds verified by caller
176 let a = unsafe { env.read_vregs().read_element(vs2, i, sew) };
177 let b = match src {
178 // SAFETY: register bounds verified by caller
179 OpSrc::Vreg(vs1_base) => unsafe { env.read_vregs().read_element(vs1_base, i, sew) },
180 OpSrc::Scalar(val) => val,
181 };
182 let result = op(a, b, sew);
183 // SAFETY: vd has dest_group_regs registers; element `i` fits within them because
184 // `vl <= src_group_regs * VLEN.bytes() / sew_bytes` and dest stores at 2*SEW width so
185 // `i < dest_group_regs * VLEN.bytes() / (2*sew_bytes)`; `2*SEW <= ELEN <= 64` by caller
186 unsafe {
187 env.write_vregs().write_element(vd, i, wide_sew, result);
188 }
189 }
190 env.mark_vs_dirty();
191 env.reset_vstart();
192}
193
194/// Execute a single-width multiply-add where the first multiplier is a vector register group.
195///
196/// `op` receives `(acc: u64, a: u64, b: u64, sew: Vsew)` where `acc` is the current `vd[i]`,
197/// `a` is the element from `a_reg`, and `b` is the element from `src`. Returns the new `vd[i]`.
198///
199/// # Safety
200/// - `vd`, `a_reg`, and `src` register alignment verified by caller
201/// - `vl <= group_regs * VLEN.bytes() / sew_bytes`
202/// - When `vm=false`: `vd.to_bits() != 0`
203#[inline(always)]
204#[doc(hidden)]
205#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
206pub unsafe fn execute_muladd_op<Reg, Env, F>(
207 env: &mut Env,
208 vd: VReg,
209 a_reg: VReg,
210 src: OpSrc,
211 vm: bool,
212 sew: Vsew,
213 op: F,
214) where
215 Reg: Register,
216 Env: VectorRegistersExt<Reg>,
217 [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
218 F: Fn(u64, u64, u64, Vsew) -> u64,
219{
220 let vl = env.vl();
221 let vstart = env.vstart();
222 // SAFETY: `vl <= VLMAX <= VLEN`
223 let mask_buf = unsafe { snapshot_mask(env.read_vregs(), vm, vl) };
224 for i in vstart.range_to(vl) {
225 if !mask_bit(&mask_buf, i) {
226 continue;
227 }
228 // SAFETY: register bounds verified by caller
229 let acc = unsafe { env.read_vregs().read_element(vd, i, sew) };
230 // SAFETY: register bounds verified by caller
231 let a = unsafe { env.read_vregs().read_element(a_reg, i, sew) };
232 let b = match src {
233 // SAFETY: register bounds verified by caller
234 OpSrc::Vreg(b_reg) => unsafe { env.read_vregs().read_element(b_reg, i, sew) },
235 OpSrc::Scalar(val) => val,
236 };
237 let result = op(acc, a, b, sew);
238 // SAFETY: register bounds verified by caller
239 unsafe {
240 env.write_vregs().write_element(vd, i, sew, result);
241 }
242 }
243 env.mark_vs_dirty();
244 env.reset_vstart();
245}
246
247/// Execute a single-width multiply-add where the first multiplier is a scalar.
248///
249/// Analogous to [`execute_muladd_op`] but `a` is a fixed scalar instead of a register element.
250///
251/// # Safety
252/// Same as [`execute_muladd_op`], minus constraints on `a_reg`.
253#[inline(always)]
254#[doc(hidden)]
255#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
256pub unsafe fn execute_muladd_scalar_op<Reg, Env, F>(
257 env: &mut Env,
258 vd: VReg,
259 scalar: u64,
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, u64, Vsew) -> u64,
269{
270 let vl = env.vl();
271 let vstart = env.vstart();
272 // SAFETY: `vl <= VLMAX <= VLEN`
273 let mask_buf = unsafe { snapshot_mask(env.read_vregs(), vm, vl) };
274 for i in vstart.range_to(vl) {
275 if !mask_bit(&mask_buf, i) {
276 continue;
277 }
278 // SAFETY: register bounds verified by caller
279 let acc = unsafe { env.read_vregs().read_element(vd, i, sew) };
280 let b = match src {
281 // SAFETY: register bounds verified by caller
282 OpSrc::Vreg(b_reg) => unsafe { env.read_vregs().read_element(b_reg, i, sew) },
283 OpSrc::Scalar(val) => val,
284 };
285 let result = op(acc, scalar, b, sew);
286 // SAFETY: register bounds verified by caller
287 unsafe {
288 env.write_vregs().write_element(vd, i, sew, result);
289 }
290 }
291 env.mark_vs_dirty();
292 env.reset_vstart();
293}
294
295/// Execute a widening multiply-add where the first multiplier is a vector register group.
296///
297/// Reads SEW-wide `acc` from the widened `vd` group, SEW-wide `a` from `a_reg`, and SEW-wide
298/// `b` from `src`. Writes a 2*SEW-wide result back into `vd`.
299///
300/// `op` receives `(acc: u64, a: u64, b: u64, sew: Vsew)`.
301///
302/// # Safety
303/// - `vd` uses `dest_group_regs` registers (result of `widening_dest_register_count()`); alignment
304/// and non-overlap verified by caller
305/// - `2*SEW <= ELEN` verified by caller via [`widening_eew_supported()`], so both the accumulator
306/// and the result fit in a `u64`
307/// - When `vm=false`: `vd.to_bits() != 0`
308#[inline(always)]
309#[doc(hidden)]
310#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
311pub unsafe fn execute_widening_muladd_op<Reg, Env, F>(
312 env: &mut Env,
313 vd: VReg,
314 a_reg: VReg,
315 src: OpSrc,
316 vm: bool,
317 sew: Vsew,
318 op: F,
319) where
320 Reg: Register,
321 Env: VectorRegistersExt<Reg>,
322 [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
323 F: Fn(u64, u64, u64, Vsew) -> u64,
324{
325 // SAFETY: `2 * SEW <= ELEN` is a precondition, so the double width exists
326 let wide_sew = unsafe { sew.double_width().unwrap_unchecked() };
327 let vl = env.vl();
328 let vstart = env.vstart();
329 // SAFETY: `vl <= VLMAX <= VLEN`
330 let mask_buf = unsafe { snapshot_mask(env.read_vregs(), vm, vl) };
331 for i in vstart.range_to(vl) {
332 if !mask_bit(&mask_buf, i) {
333 continue;
334 }
335 // Read the existing 2*SEW accumulator from vd
336 // SAFETY: vd has dest_group_regs registers; element `i` fits within them (see
337 // `execute_widening_op` for the bound argument); `2*SEW <= ELEN <= 64` by caller
338 let acc = unsafe { env.read_vregs().read_element(vd, i, wide_sew) };
339 // SAFETY: register bounds verified by caller
340 let a = unsafe { env.read_vregs().read_element(a_reg, i, sew) };
341 let b = match src {
342 // SAFETY: register bounds verified by caller
343 OpSrc::Vreg(b_reg) => unsafe { env.read_vregs().read_element(b_reg, i, sew) },
344 OpSrc::Scalar(val) => val,
345 };
346 let result = op(acc, a, b, sew);
347 // SAFETY: same as acc read above
348 unsafe {
349 env.write_vregs().write_element(vd, i, wide_sew, result);
350 }
351 }
352 env.mark_vs_dirty();
353 env.reset_vstart();
354}
355
356/// Execute a widening multiply-add where the first multiplier is a scalar.
357///
358/// Analogous to [`execute_widening_muladd_op`] but `a` is a fixed scalar.
359///
360/// # Safety
361/// Same as [`execute_widening_muladd_op`], minus constraints on `a_reg`.
362#[inline(always)]
363#[doc(hidden)]
364#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
365pub unsafe fn execute_widening_muladd_scalar_op<Reg, Env, F>(
366 env: &mut Env,
367 vd: VReg,
368 scalar: u64,
369 src: OpSrc,
370 vm: bool,
371 sew: Vsew,
372 op: F,
373) where
374 Reg: Register,
375 Env: VectorRegistersExt<Reg>,
376 [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
377 F: Fn(u64, u64, u64, Vsew) -> u64,
378{
379 // SAFETY: `2 * SEW <= ELEN` is a precondition, so the double width exists
380 let wide_sew = unsafe { sew.double_width().unwrap_unchecked() };
381 let vl = env.vl();
382 let vstart = env.vstart();
383 // SAFETY: `vl <= VLMAX <= VLEN`
384 let mask_buf = unsafe { snapshot_mask(env.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 has dest_group_regs registers; element `i` fits within them (see
390 // `execute_widening_op` for the bound argument); `2*SEW <= ELEN <= 64` by caller
391 let acc = unsafe { env.read_vregs().read_element(vd, i, wide_sew) };
392 let b = match src {
393 // SAFETY: register bounds verified by caller
394 OpSrc::Vreg(b_reg) => unsafe { env.read_vregs().read_element(b_reg, i, sew) },
395 OpSrc::Scalar(val) => val,
396 };
397 let result = op(acc, scalar, b, sew);
398 // SAFETY: same as acc read above
399 unsafe {
400 env.write_vregs().write_element(vd, i, wide_sew, result);
401 }
402 }
403 env.mark_vs_dirty();
404 env.reset_vstart();
405}
406
407/// Signed × signed high half.
408///
409/// Both operands are sign-extended to i64, multiplied as i128, and the upper SEW bits of the
410/// 2*SEW product are returned (zero-extended to u64 for writeback into a SEW-wide element slot).
411///
412/// Valid for every SEW including 64, because the intermediate product is formed in i128. Whether
413/// SEW=64 is reachable at all is an extension-level decision made by the caller (Zve64x excludes
414/// it, the full "V" extension does not).
415#[inline(always)]
416#[doc(hidden)]
417#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
418pub fn mulh_ss(a: u64, b: u64, sew: Vsew) -> u64 {
419 let sa = sign_extend(a, sew);
420 let sb = sign_extend(b, sew);
421 let product = sa.widening_mul(sb);
422 // Extract bits [2*SEW-1 : SEW] of the product
423 let high = (product >> sew.bits_width()).cast_unsigned() as u64;
424 high & sew_mask(sew)
425}
426
427/// Unsigned × unsigned high half.
428///
429/// Valid for every SEW including 64; see [`mulh_ss()`] for the extension-level caveat.
430#[inline(always)]
431#[doc(hidden)]
432#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
433pub fn mulhu_uu(a: u64, b: u64, sew: Vsew) -> u64 {
434 let ua = a & sew_mask(sew);
435 let ub = b & sew_mask(sew);
436 let product = ua.widening_mul(ub);
437 let high = (product >> sew.bits_width()) as u64;
438 high & sew_mask(sew)
439}
440
441/// Signed × unsigned high half.
442///
443/// `a` (vs2) is the signed operand; `b` (vs1/rs1) is the unsigned operand.
444///
445/// Valid for every SEW including 64; see [`mulh_ss()`] for the extension-level caveat. Note that
446/// the unsigned operand is widened to i128 rather than i64, so a full-width unsigned value at
447/// SEW=64 keeps its magnitude instead of being reinterpreted as negative.
448#[inline(always)]
449#[doc(hidden)]
450#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
451pub fn mulhsu_su(a: u64, b: u64, sew: Vsew) -> u64 {
452 let sa = i128::from(sign_extend(a, sew));
453 let ub = i128::from(b & sew_mask(sew));
454 let product = sa.wrapping_mul(ub);
455 let high = (product >> sew.bits_width()).cast_unsigned() as u64;
456 high & sew_mask(sew)
457}
458
459/// Signed divide with division-by-zero and signed-overflow semantics from the RISC-V V spec §12.11.
460///
461/// - Division by zero: result = all-ones (i.e., −1 as signed SEW-wide integer)
462/// - Signed overflow (MIN / −1): result = MIN (i.e., `1 << (SEW-1)`)
463#[inline(always)]
464#[doc(hidden)]
465#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
466pub fn sdiv(a: u64, b: u64, sew: Vsew) -> u64 {
467 let sa = sign_extend(a, sew);
468 let sb = sign_extend(b, sew);
469 // Division by zero: return all-ones in the SEW-wide slot (= −1 signed)
470 if sb == 0 {
471 return sew_mask(sew);
472 }
473 sa.wrapping_div(sb).cast_unsigned() & sew_mask(sew)
474}
475
476/// Signed remainder with division-by-zero and signed-overflow semantics from the RISC-V V spec
477/// §12.11.
478///
479/// - Division by zero: remainder = dividend
480/// - Signed overflow (MIN % −1): remainder = 0
481#[inline(always)]
482#[doc(hidden)]
483#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
484pub fn srem(a: u64, b: u64, sew: Vsew) -> u64 {
485 let sa = sign_extend(a, sew);
486 let sb = sign_extend(b, sew);
487 // Division by zero: remainder = dividend
488 if sb == 0 {
489 return a & sew_mask(sew);
490 }
491 sa.wrapping_rem(sb).cast_unsigned() & sew_mask(sew)
492}