Skip to main content

ab_riscv_interpreter/v/zvexx/load/
zvexx_load_helpers.rs

1//! Opaque helpers for ZveXx extension
2
3#[cfg(test)]
4mod tests;
5
6use crate::v::vector_config::VectorConfig;
7use crate::v::vector_registers::{VLENB_USIZE, VectorRegisterFile, VectorRegistersExt};
8pub use crate::v::vector_registers::{VRegGroup, VRegSegmentGroup};
9use crate::{ExecutionError, VirtualMemory, VirtualMemoryError};
10use ab_riscv_primitives::prelude::*;
11use core::hint::cold_path;
12
13/// Effective address `base + offset`, wrapping around at the end of the `XLEN`-bit address space
14/// like every memory address computation
15#[inline(always)]
16#[doc(hidden)]
17#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
18pub fn effective_address<Reg>(base: u64, offset: u64) -> u64
19where
20    Reg: Register,
21{
22    Reg::Type::truncate_from_u64(base.wrapping_add(offset)).as_u64()
23}
24
25/// Number of bytes of a `len`-byte access at `address` that come before the end of the `XLEN`-bit
26/// address space, the rest of the access wraps around to address zero
27#[inline(always)]
28#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
29pub(crate) fn bytes_before_wrap<Reg>(address: u64, len: usize) -> usize
30where
31    Reg: Register,
32{
33    let remaining = (1u128 << Reg::XLEN).saturating_sub(u128::from(address));
34    usize::try_from(remaining).map_or(len, |remaining| remaining.min(len))
35}
36
37/// Whether a `len`-byte access at `address` wraps around the end of the `XLEN`-bit address space
38#[inline(always)]
39#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
40pub(crate) fn access_wraps<Reg>(address: u64, len: usize) -> bool
41where
42    Reg: Register,
43{
44    bytes_before_wrap::<Reg>(address, len) < len
45}
46
47/// Read exactly `dst.len()` bytes at `address` with a single `read_slice()` call
48#[inline(always)]
49#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
50fn read_exact(
51    memory: &impl VirtualMemory,
52    address: u64,
53    dst: &mut [u8],
54) -> Result<(), VirtualMemoryError> {
55    let Ok(len) = u32::try_from(dst.len()) else {
56        cold_path();
57        return Err(VirtualMemoryError::OutOfBoundsRead { address });
58    };
59    // Only the requested bytes are used, even if memory returned more
60    let Some(src) = memory.read_slice(address, len)?.get(..dst.len()) else {
61        cold_path();
62        return Err(VirtualMemoryError::OutOfBoundsRead { address });
63    };
64    // Same as `copy_from_slice()`, but without a length check the compiler may fail to prove
65    // redundant
66    for (dst, src) in dst.iter_mut().zip(src) {
67        *dst = *src;
68    }
69    Ok(())
70}
71
72/// Read `dst.len()` bytes starting at effective address `address`, wrapping around at the end of
73/// the `XLEN`-bit address space
74#[inline(always)]
75#[doc(hidden)]
76#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
77pub fn read_bytes<Reg, Memory>(
78    memory: &Memory,
79    address: u64,
80    dst: &mut [u8],
81) -> Result<(), VirtualMemoryError>
82where
83    Reg: Register,
84    Memory: VirtualMemory,
85{
86    let before_wrap = bytes_before_wrap::<Reg>(address, dst.len());
87    let Some((head, tail)) = dst.split_at_mut_checked(before_wrap) else {
88        cold_path();
89        return Err(VirtualMemoryError::OutOfBoundsRead { address });
90    };
91    read_exact(memory, address, head)?;
92    if !tail.is_empty() {
93        cold_path();
94        read_exact(memory, 0, tail)?;
95    }
96    Ok(())
97}
98
99/// Return whether mask bit `i` is set in the mask byte slice.
100///
101/// Bits are stored LSB-first within each byte: bit `i` is at byte `i / 8`, position `i % 8`.
102/// Returns `false` for any `i` outside the slice bounds.
103#[inline(always)]
104#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
105pub(crate) fn mask_bit(mask: &[u8], i: u16) -> bool {
106    mask.get(usize::from(i / u8::BITS as u16))
107        .is_some_and(|b| (b >> (i % u8::BITS as u16)) & 1 != 0)
108}
109
110/// Copy mask register `v0` into a stack buffer and return it. The copy releases the shared borrow
111/// on the register file so the caller can immediately take an exclusive borrow for writes.
112///
113/// The whole register is copied regardless of `vl`: a fixed-size copy needs no bounds check, and
114/// callers only read mask bits of elements below `vl`.
115///
116/// When `vm=true` (unmasked), the buffer is filled with `0xff` so that every mask bit reads as `1`.
117/// This means callers can unconditionally call [`mask_bit()`] on the returned buffer without
118/// branching on `vm`. Current callers short-circuit with `!vm &&` before calling [`mask_bit()`] as
119/// a micro-optimization on the common unmasked path, but correctness does not depend on that guard:
120/// if it were removed, the `0xff` fill ensures [`mask_bit()`] would return `true` for every
121/// element, preserving the unmasked semantics.
122#[inline(always)]
123#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
124pub(in super::super) fn snapshot_mask<Hart>(
125    vregs: &VectorRegisterFile<Hart>,
126    vm: bool,
127) -> [u8; VLENB_USIZE::<Hart>]
128where
129    Hart: VectorHartConfig,
130{
131    if vm {
132        // All-ones: every element active
133        [0xffu8; _]
134    } else {
135        *vregs.get(VReg::V0)
136    }
137}
138
139/// Read `eew`-sized data from memory at `addr` into a `[u8; Eew::MAX_BYTES]` buffer
140/// (little-endian)
141#[inline(always)]
142#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
143fn read_mem_element<Reg>(
144    memory: &impl VirtualMemory,
145    addr: u64,
146    eew: Eew,
147) -> Result<[u8; const { usize::from(Eew::MAX_BYTES) }], VirtualMemoryError>
148where
149    Reg: Register,
150{
151    let mut out = [0; _];
152    let element = out
153        .get_mut(..usize::from(eew.bytes_width()))
154        .expect("Element width never exceeds `Eew::MAX_BYTES`; qed");
155    if let Err(err) = read_bytes::<Reg, _>(memory, addr, element) {
156        cold_path();
157        return Err(err);
158    }
159    Ok(out)
160}
161
162/// Execute a unit-stride or unit-stride segment load (including fault-only-first variants).
163///
164/// Segment stride between elements is `nf * eew.bytes()`. Field `f` for element `i` is at
165/// `base + i * nf * eew.bytes() + f * eew.bytes()`. When `nf == 1` this degenerates to a
166/// plain unit-stride load.
167///
168/// When `fault_only_first` is set to the configuration the instruction executes with: a memory
169/// error at element `i > 0` truncates `vl` to `i` and returns `Ok`. An error at element `0` always
170/// propagates.
171///
172/// Elements `vstart..vl` of `vd` are loaded, where `vl` is the architectural one for regular loads.
173#[inline(always)]
174#[doc(hidden)]
175#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
176pub fn execute_unit_stride_load<Reg, Env, Memory>(
177    env: &mut Env,
178    memory: &Memory,
179    vd: VRegSegmentGroup<Env::Hart>,
180    vm: bool,
181    base: u64,
182    fault_only_first: Option<VectorConfig<Env::Hart>>,
183) -> Result<(), ExecutionError<Reg::Type>>
184where
185    Reg: Register,
186    Env: VectorRegistersExt<Hart: HartConfig<Reg = Reg>>,
187    Memory: VirtualMemory,
188{
189    let vl = vd.first().vl();
190    let vstart = env.vstart();
191    let eew = vd.first().eew();
192    let elem_bytes = eew.bytes_width();
193    let nf = vd.nf();
194
195    // Unmasked non-segment load is a plain copy of a contiguous memory range into the contiguous
196    // element range of the register group, as long as the whole range is readable. If it is not,
197    // the element-wise path below is what determines the faulting element and everything before
198    // it, exactly as if the copy was never attempted (nothing was written).
199    if vm
200        && nf.fields_per_segment() == 1
201        && let Some(count) = u32::from(vl.get()).checked_sub(u32::from(u16::from(vstart)))
202        && count > 0
203    {
204        let first = u16::from(vstart);
205        let addr = effective_address::<Reg>(base, u64::from(first) * u64::from(elem_bytes));
206        // At most 8 registers worth of bytes
207        let len = count * u32::from(elem_bytes);
208        // A range that wraps around the end of the address space is not contiguous in memory
209        if let Ok(len_usize) = usize::try_from(len)
210            && !access_wraps::<Reg>(addr, len_usize)
211            && let Ok(bytes) = memory.read_slice(addr, len)
212            && let Some(dst) = env
213                .write_vregs()
214                .elements_bytes_mut(vd.first(), first, count)
215            && bytes.len() >= dst.len()
216        {
217            // Same as `copy_from_slice()`, but without a length check the compiler may fail to
218            // prove redundant
219            for (dst, src) in dst.iter_mut().zip(bytes) {
220                *dst = *src;
221            }
222            env.mark_vs_dirty();
223            env.reset_vstart();
224            return Ok(());
225        }
226        cold_path();
227    }
228
229    let segment_stride = u64::from(nf.fields_per_segment()) * u64::from(elem_bytes);
230
231    let mask_buf = snapshot_mask(env.read_vregs(), vm);
232
233    for element in vd.elements(u16::from(vstart)) {
234        let i = element.index();
235        if !vm && !mask_bit(&mask_buf, i) {
236            continue;
237        }
238
239        let elem_base = effective_address::<Reg>(base, u64::from(i) * segment_stride);
240
241        // Read all nf fields into a stack buffer before writing any of them.
242        // This ensures a fault on field f>0 leaves the destination registers untouched for the
243        // faulting element, so only elements with index new_vl are ever written (fault-only-first
244        // semantics).
245        //
246        // Sized by `Nf::MAX * Eew::MAX_BYTES`: the V spec allows at most 8 fields (nf in 1..=8)
247        // each is at most 8 bytes (E64), giving 64 bytes.
248        let mut field_buf = [0u64; const { usize::from(Nf::MAX.fields_per_segment()) }];
249
250        // `nf <= Nf::MAX`, which is exactly the length of `field_buf`, so every field has a slot
251        for (f, field) in (0..nf.fields_per_segment()).zip(&mut field_buf) {
252            let addr = effective_address::<Reg>(elem_base, u64::from(f * elem_bytes));
253            match read_mem_element::<Reg>(memory, addr, eew) {
254                Ok(data) => {
255                    *field = u64::from_le_bytes(data);
256                }
257                Err(mem_err) => {
258                    cold_path();
259                    if let Some(config) = fault_only_first
260                        && i > 0
261                    {
262                        env.set_vector_config(Some(config.with_vl_at_most(Vl::from(i))));
263                        env.mark_vs_dirty();
264                        env.reset_vstart();
265                        return Ok(());
266                    }
267                    if i > u16::from(vstart) {
268                        // Elements [vstart, i) were committed; VS is now dirty.
269                        env.mark_vs_dirty();
270                        // vstart records the faulting element for restartability.
271                        env.set_vstart(Vstart::from(i));
272                    }
273                    return Err(ExecutionError::from(mem_err));
274                }
275            }
276        }
277
278        // All nf fields for element i were read successfully; commit to the register file.
279        element.write_fields(env.write_vregs(), &field_buf);
280    }
281
282    env.mark_vs_dirty();
283    env.reset_vstart();
284    Ok(())
285}
286
287/// Execute a strided or strided segment load.
288///
289/// `addr[i] = base + i * stride` where `stride` is a signed XLEN-wide value. Field `f` of
290/// element `i` is at `addr[i] + f * eew.bytes()`.
291#[inline(always)]
292#[doc(hidden)]
293#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
294pub fn execute_strided_load<Reg, Env, Memory>(
295    env: &mut Env,
296    memory: &Memory,
297    vd: VRegSegmentGroup<Env::Hart>,
298    vm: bool,
299    base: u64,
300    stride: i64,
301) -> Result<(), ExecutionError<Reg::Type>>
302where
303    Reg: Register,
304    Env: VectorRegistersExt<Hart: HartConfig<Reg = Reg>>,
305    Memory: VirtualMemory,
306{
307    let vstart = env.vstart();
308    let eew = vd.first().eew();
309    let elem_bytes = eew.bytes_width();
310
311    let mask_buf = snapshot_mask(env.read_vregs(), vm);
312
313    for element in vd.elements(u16::from(vstart)) {
314        let i = element.index();
315        if !vm && !mask_bit(&mask_buf, i) {
316            continue;
317        }
318
319        let elem_base =
320            effective_address::<Reg>(base, i64::from(i).wrapping_mul(stride).cast_unsigned());
321
322        for field in element.fields() {
323            let f = field.index();
324            let addr = effective_address::<Reg>(elem_base, u64::from(f * elem_bytes));
325            let data = match read_mem_element::<Reg>(memory, addr, eew) {
326                Ok(data) => data,
327                Err(mem_err) => {
328                    cold_path();
329                    if f > 0 || i > u16::from(vstart) {
330                        env.mark_vs_dirty();
331                        env.set_vstart(Vstart::from(i));
332                    }
333                    return Err(ExecutionError::from(mem_err));
334                }
335            };
336            field.write(env.write_vregs(), u64::from_le_bytes(data));
337        }
338    }
339
340    env.mark_vs_dirty();
341    env.reset_vstart();
342    Ok(())
343}
344
345/// Execute an indexed (unordered or ordered) or indexed segment load.
346///
347/// For element `i`, reads the index element `i` of `vs2` to obtain a zero-extended byte offset,
348/// then loads `nf` data fields from `base + offset + f * data_eew.bytes()`. Unordered vs ordered is
349/// functionally identical in a software interpreter.
350///
351/// `vs2` must have the same `vl` as `vd`, which holds for groups created from the same
352/// configuration, nothing is loaded otherwise.
353#[inline(always)]
354#[doc(hidden)]
355#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
356pub fn execute_indexed_load<Reg, Env, Memory>(
357    env: &mut Env,
358    memory: &Memory,
359    vd: VRegSegmentGroup<Env::Hart>,
360    vs2: VRegGroup<Env::Hart>,
361    vm: bool,
362    base: u64,
363) -> Result<(), ExecutionError<Reg::Type>>
364where
365    Reg: Register,
366    Env: VectorRegistersExt<Hart: HartConfig<Reg = Reg>>,
367    Memory: VirtualMemory,
368{
369    let Some(indexed) = vd.with_index(vs2) else {
370        cold_path();
371        return Ok(());
372    };
373    let vstart = env.vstart();
374    let data_eew = vd.first().eew();
375    let data_elem_bytes = data_eew.bytes_width();
376
377    let mask_buf = snapshot_mask(env.read_vregs(), vm);
378
379    for indexed in indexed.elements(u16::from(vstart)) {
380        let element = indexed.element();
381        let i = element.index();
382        if !vm && !mask_bit(&mask_buf, i) {
383            continue;
384        }
385
386        let offset = indexed.read_index(env.read_vregs());
387        let elem_addr = effective_address::<Reg>(base, offset);
388
389        for field in element.fields() {
390            let f = field.index();
391            let addr =
392                effective_address::<Reg>(elem_addr, u64::from(f) * u64::from(data_elem_bytes));
393            let data = match read_mem_element::<Reg>(memory, addr, data_eew) {
394                Ok(data) => data,
395                Err(mem_err) => {
396                    cold_path();
397                    if f > 0 || i > u16::from(vstart) {
398                        env.mark_vs_dirty();
399                        env.set_vstart(Vstart::from(i));
400                    }
401                    return Err(ExecutionError::from(mem_err));
402                }
403            };
404            field.write(env.write_vregs(), u64::from_le_bytes(data));
405        }
406    }
407
408    env.mark_vs_dirty();
409    env.reset_vstart();
410    Ok(())
411}