Skip to main content

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}