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