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