Skip to main content

ab_riscv_interpreter/v/zvexx/load/
zvexx_load_helpers.rs

1//! Opaque helpers for ZveXx extension
2
3use crate::v::vector_registers::{VLENB_USIZE, VectorRegisterFile, VectorRegistersExt};
4use crate::v::zvexx::zvexx_helpers::INSTRUCTION_SIZE;
5use crate::{ExecutionError, PackedAddress, ProgramCounter, VirtualMemory, VirtualMemoryError};
6use ab_riscv_primitives::prelude::*;
7use core::cmp::Ordering;
8use core::hint::cold_path;
9use core::num::NonZeroU8;
10
11/// Return whether mask bit `i` is set in the mask byte slice.
12///
13/// Bits are stored LSB-first within each byte: bit `i` is at byte `i / 8`, position `i % 8`.
14/// Returns `false` for any `i` outside the slice bounds.
15#[inline(always)]
16#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
17pub(crate) fn mask_bit(mask: &[u8], i: u16) -> bool {
18    mask.get(usize::from(i / u8::BITS as u16))
19        .is_some_and(|b| (b >> (i % u8::BITS as u16)) & 1 != 0)
20}
21
22/// Copy the mask bytes needed to cover `vl` elements from `v0` into a stack buffer and return
23/// it. The copy releases the shared borrow on the register file so the caller can immediately
24/// take an exclusive borrow for writes.
25///
26/// When `vm=true` (unmasked), the buffer is filled with `0xff` so that every mask bit reads as `1`.
27/// This means callers can unconditionally call [`mask_bit()`] on the returned buffer without
28/// branching on `vm`. Current callers short-circuit with `!vm &&` before calling [`mask_bit()`] as
29/// a micro-optimization on the common unmasked path, but correctness does not depend on that guard:
30/// if it were removed, the `0xff` fill ensures [`mask_bit()`] would return `true` for every
31/// element, preserving the unmasked semantics.
32///
33/// # Safety
34/// `vl` must be `<= VLEN`, which is always true when `vl` is the current architectural `vl`
35/// (bounded by `VLMAX <= VLEN`).
36#[inline(always)]
37#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
38pub(in super::super) unsafe fn snapshot_mask<const VLEN: Vlen>(
39    vregs: &VectorRegisterFile<VLEN>,
40    vm: bool,
41    vl: Vl,
42) -> [u8; VLENB_USIZE::<VLEN>] {
43    let mut buf = [0u8; _];
44    if vm {
45        // All-ones: every element active
46        buf = [0xffu8; _];
47    } else {
48        let mask_bytes = usize::from(vl.bytes());
49        // SAFETY: `mask_bytes <= VLEN.bytes()` by the caller's precondition
50        unsafe {
51            buf.get_unchecked_mut(..mask_bytes)
52                .copy_from_slice(vregs.get(VReg::V0).get_unchecked(..mask_bytes));
53        }
54    }
55    buf
56}
57
58/// Return whether register groups `[a, a+a_regs)` and `[b, b+b_regs)` overlap.
59#[inline(always)]
60#[doc(hidden)]
61#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
62pub fn groups_overlap(a: VReg, a_regs: NonZeroU8, b: VReg, b_regs: NonZeroU8) -> bool {
63    let (a, b) = (a.to_bits(), b.to_bits());
64    a < b + b_regs.get() && b < a + a_regs.get()
65}
66
67/// Return whether a *non-segment* indexed load's data destination group
68/// `[vd, vd + data_regs)` may legally overlap its index source group `[vs2, vs2 + index_regs)`.
69///
70/// The data EEW equals `sew` (indexed loads take their data width from `vtype.vsew()`) with
71/// `EMUL = LMUL`, whereas the index group has EEW `index_eew` and `EMUL = (index_eew / sew) *
72/// LMUL`. Because the two groups can have different EEW, the general vector register overlap
73/// constraint applies: a destination group may overlap a source group only when one of the
74/// following holds:
75///
76/// - the EEWs are equal (the groups coincide); or
77/// - the destination EEW is smaller and the overlap is in the lowest-numbered part of the source
78///   group, i.e. the destination starts at the source's base register (`vd == vs2`); or
79/// - the destination EEW is larger, the source EMUL is at least one register, and the overlap is in
80///   the highest-numbered part of the destination group, i.e. both groups end at the same register
81///   (`vd + data_regs == vs2 + index_regs`).
82///
83/// Groups that do not overlap at all are always permitted. Any other overlap is reserved.
84///
85/// Unlike indexed *segment* loads (which forbid any `vd`/`vs2` overlap to remain restartable),
86/// these relaxed rules are what allow encodings such as `vluxei32.v v16, (s2), v16` when the data
87/// and index EEW match.
88#[inline(always)]
89#[doc(hidden)]
90#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
91pub fn indexed_load_overlap_allowed(
92    vd: VReg,
93    data_regs: NonZeroU8,
94    vs2: VReg,
95    index_regs: NonZeroU8,
96    index_eew: Eew,
97    sew: Vsew,
98    vlmul: Vlmul,
99) -> bool {
100    if !groups_overlap(vd, data_regs, vs2, index_regs) {
101        return true;
102    }
103
104    match sew.bytes_width().cmp(&index_eew.bytes_width()) {
105        // Equal EEW: the two groups coincide, overlap is permitted.
106        Ordering::Equal => true,
107        // Smaller data EEW: overlap must be in the lowest-numbered part of the index group, which
108        // (given both groups are alignment-checked) means the data group starts at the index base.
109        Ordering::Less => vd == vs2,
110        // Larger data EEW: overlap must be in the highest-numbered part of the data group, and the
111        // index EMUL must be at least one full register. `index_regs` alone cannot distinguish a
112        // whole-register EMUL from a fractional one clamped to a single register, so the EMUL is
113        // recomputed here as `(index_eew / sew) * LMUL >= 1`.
114        Ordering::Greater => {
115            let (lmul_num, lmul_den) = vlmul.as_fraction();
116            let index_emul_at_least_one = u16::from(index_eew.bits_width())
117                * u16::from(lmul_num.get())
118                >= u16::from(sew.bits_width()) * u16::from(lmul_den.get());
119            let (vd, vs2) = (vd.to_bits(), vs2.to_bits());
120            index_emul_at_least_one && vd + data_regs.get() == vs2 + index_regs.get()
121        }
122    }
123}
124
125/// Check that `vd` is aligned to `group_regs` and that the group fits within `[0, 32)`.
126///
127/// Per spec, the base register of every register group must be a multiple of the group size.
128#[inline(always)]
129#[doc(hidden)]
130#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
131pub fn check_register_group_alignment<Reg, Memory, PC>(
132    program_counter: &PC,
133    vd: VReg,
134    group_regs: NonZeroU8,
135) -> Result<(), ExecutionError<Reg::Type>>
136where
137    Reg: Register,
138    PC: ProgramCounter<Reg::Type, Memory>,
139{
140    let group_regs = group_regs.get();
141    let vd = vd.to_bits();
142    if !vd.is_multiple_of(group_regs) || vd + group_regs > 32 {
143        cold_path();
144        return Err(ExecutionError::IllegalInstruction {
145            address: PackedAddress::new(program_counter.old_pc(INSTRUCTION_SIZE)),
146        });
147    }
148    Ok(())
149}
150
151/// Validate segment register layout: all `nf` field groups fit within `[0, 32)`, the base
152/// register is group-aligned, and the first field group does not include `v0` when masked.
153///
154/// Field `f` occupies registers `[vd + f * group_regs, vd + f * group_regs + group_regs)`.
155/// On `Ok`, `vd.to_bits() + nf * group_regs <= 32` is guaranteed.
156#[inline(always)]
157#[doc(hidden)]
158#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
159pub fn validate_segment_registers<Reg, Memory, PC>(
160    program_counter: &PC,
161    vd: VReg,
162    vm: bool,
163    group_regs: NonZeroU8,
164    nf: Nf,
165) -> Result<(), ExecutionError<Reg::Type>>
166where
167    Reg: Register,
168    PC: ProgramCounter<Reg::Type, Memory>,
169{
170    let group_regs = u32::from(group_regs.get());
171    let nf = u32::from(nf.fields_per_segment());
172    let vd_idx = u32::from(vd.to_bits());
173    if vd_idx % group_regs != 0 || vd_idx + nf * group_regs > 32 {
174        cold_path();
175        return Err(ExecutionError::IllegalInstruction {
176            address: PackedAddress::new(program_counter.old_pc(INSTRUCTION_SIZE)),
177        });
178    }
179    // When masked, no field group may contain v0 (index 0). Since groups are laid out
180    // contiguously from vd and vd is group-aligned, only the first field (f=0) could contain
181    // v0, which happens exactly when vd == 0.
182    if !vm && vd_idx == 0 {
183        cold_path();
184        return Err(ExecutionError::IllegalInstruction {
185            address: PackedAddress::new(program_counter.old_pc(INSTRUCTION_SIZE)),
186        });
187    }
188    Ok(())
189}
190
191/// Read element `elem_i` from register group `[base_reg, base_reg + group_regs)` into a
192/// `[u8; Eew::MAX_BYTES]` buffer.
193///
194/// The in-register position of element `elem_i` is:
195///   - register `base_reg + elem_i / (VLEN.bytes() / eew.bytes())`
196///   - byte offset `(elem_i % (VLEN.bytes() / eew.bytes())) * eew.bytes()`
197///
198/// The result is placed in `buf[..eew.bytes()]`; the remaining bytes are zero.
199///
200/// # Safety
201/// `base_reg + elem_i / (VLEN.bytes() / eew.bytes())` must be less than 32, i.e. `elem_i` must be
202/// a valid element index within the register group.
203#[inline(always)]
204#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
205pub(in super::super) unsafe fn read_group_element<const VLEN: Vlen>(
206    vregs: &VectorRegisterFile<VLEN>,
207    base_reg: VReg,
208    elem_i: u16,
209    eew: Eew,
210) -> [u8; const { usize::from(Eew::MAX_BYTES) }] {
211    let elem_bytes = u32::from(eew.bytes_width());
212    let elems_per_reg = VLEN.bytes() / elem_bytes;
213    let reg_off = u32::from(elem_i) / elems_per_reg;
214    let byte_off = (u32::from(elem_i) % elems_per_reg) * elem_bytes;
215    // SAFETY: `base_reg + reg_off < 32` by the caller's precondition
216    let reg = unsafe {
217        vregs.get(VReg::from_bits(base_reg.to_bits() + reg_off as u8).unwrap_unchecked())
218    };
219    // SAFETY: `byte_off + elem_bytes <= VLEN.bytes()`: the maximum `byte_off` is
220    // `(elems_per_reg - 1) * elem_bytes = VLEN.bytes() - elem_bytes`, so
221    // `byte_off + elem_bytes <= VLEN.bytes() - elem_bytes + elem_bytes = VLEN.bytes()`.
222    // `elem_bytes <= Eew::MAX_BYTES`: all `Eew` variants are at most E64.
223    let src = unsafe { reg.get_unchecked(byte_off as usize..(byte_off + elem_bytes) as usize) };
224    let mut buf = [0; _];
225    // SAFETY: `elem_bytes <= Eew::MAX_BYTES` as established above, so `..elem_bytes` is in bounds
226    // for `buf`
227    unsafe { buf.get_unchecked_mut(..elem_bytes as usize) }.copy_from_slice(src);
228    buf
229}
230
231/// Write `eew`-sized data from `buf[..eew.bytes()]` into element `elem_i` of register group
232/// `[base_reg, base_reg + group_regs)`.
233///
234/// The in-register position follows the same layout as [`read_group_element`].
235///
236/// # Safety
237/// `base_reg + elem_i / (VLEN.bytes() / eew.bytes())` must be less than 32, i.e. `elem_i` must be
238/// a valid element index within the register group.
239#[inline(always)]
240#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
241unsafe fn write_group_element<const VLEN: Vlen>(
242    vregs: &mut VectorRegisterFile<VLEN>,
243    base_reg: VReg,
244    elem_i: u16,
245    eew: Eew,
246    buf: [u8; const { usize::from(Eew::MAX_BYTES) }],
247) {
248    let elem_bytes = u32::from(eew.bytes_width());
249    let elems_per_reg = VLEN.bytes() / elem_bytes;
250    let reg_off = u32::from(elem_i) / elems_per_reg;
251    let byte_off = (u32::from(elem_i) % elems_per_reg) * elem_bytes;
252    // SAFETY: `base_reg + reg_off < 32` by the caller's precondition
253    let reg = unsafe {
254        vregs.get_mut(VReg::from_bits(base_reg.to_bits() + reg_off as u8).unwrap_unchecked())
255    };
256    // SAFETY: `byte_off + elem_bytes <= VLEN.bytes()` and `elem_bytes <= Eew::MAX_BYTES`: same
257    // argument as in `read_group_element`
258    let dst = unsafe { reg.get_unchecked_mut(byte_off as usize..(byte_off + elem_bytes) as usize) };
259    // SAFETY: `elem_bytes <= Eew::MAX_BYTES` as established above, so `..elem_bytes` is in bounds
260    // for `buf`
261    dst.copy_from_slice(unsafe { buf.get_unchecked(..elem_bytes as usize) });
262}
263
264/// Read `eew`-sized data from memory at `addr` into a `[u8; Eew::MAX_BYTES]` buffer
265/// (little-endian)
266#[inline(always)]
267#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
268fn read_mem_element(
269    memory: &impl VirtualMemory,
270    addr: u64,
271    eew: Eew,
272) -> Result<[u8; const { usize::from(Eew::MAX_BYTES) }], VirtualMemoryError> {
273    let source = match memory.read_slice(addr, u32::from(eew.bytes_width())) {
274        Ok(source) => source,
275        Err(err) => {
276            cold_path();
277            return Err(err);
278        }
279    };
280    let mut out = [0; _];
281    out[..usize::from(eew.bytes_width())].copy_from_slice(source);
282    Ok(out)
283}
284
285/// Execute a unit-stride or unit-stride segment load (including fault-only-first variants).
286///
287/// Segment stride between elements is `nf * eew.bytes()`. Field `f` for element `i` is at
288/// `base + i * nf * eew.bytes() + f * eew.bytes()`. When `nf == 1` this degenerates to a
289/// plain unit-stride load.
290///
291/// When `fault_only_first` is set: a memory error at element `i > 0` truncates `vl` to `i`
292/// and returns `Ok`. An error at element `0` always propagates.
293///
294/// # Safety
295/// - `vd.to_bits() % group_regs == 0`
296/// - `vd.to_bits() + nf * group_regs <= 32`
297/// - `vl <= group_regs * VLEN.bytes() / eew.bytes()` (all `vl` elements fit within the destination
298///   register group; this holds when `vl` is the architectural `vl` and `group_regs` is the EMUL
299///   register count for the given `eew` and `vtype`)
300/// - When `vm=false`: `vd` does not overlap `v0` (i.e. `vd.to_bits() != 0`)
301#[inline(always)]
302#[expect(clippy::too_many_arguments, reason = "Internal API")]
303#[doc(hidden)]
304#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
305pub unsafe fn execute_unit_stride_load<const FAULT_ONLY_FIRST: bool, Reg, Env, Memory>(
306    env: &mut Env,
307    memory: &Memory,
308    vd: VReg,
309    vm: bool,
310    base: u64,
311    eew: Eew,
312    group_regs: NonZeroU8,
313    nf: Nf,
314) -> Result<(), ExecutionError<Reg::Type>>
315where
316    Reg: Register,
317    Env: VectorRegistersExt<Reg>,
318    [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
319    Memory: VirtualMemory,
320{
321    let group_regs = group_regs.get();
322    let vl = env.vl();
323    let vstart = env.vstart();
324    let elem_bytes = eew.bytes_width();
325    let segment_stride = u64::from(nf.fields_per_segment()) * u64::from(elem_bytes);
326
327    // SAFETY: `vl <= VLMAX <= VLEN`
328    let mask_buf = unsafe { snapshot_mask(env.read_vregs(), vm, vl) };
329
330    for i in vstart.range_to(vl) {
331        if !vm && !mask_bit(&mask_buf, i) {
332            continue;
333        }
334
335        let elem_base = base.wrapping_add(u64::from(i) * segment_stride);
336
337        // Read all nf fields into a stack buffer before writing any of them.
338        // This ensures a fault on field f>0 leaves the destination registers untouched for the
339        // faulting element, so only elements with index new_vl are ever written (fault-only-first
340        // semantics).
341        //
342        // Sized by `Nf::MAX * Eew::MAX_BYTES`: the V spec allows at most 8 fields (nf in 1..=8)
343        // each is at most 8 bytes (E64), giving 64 bytes.
344        let mut field_buf = [[0u8; const { usize::from(Eew::MAX_BYTES) }]; const {
345            usize::from(Nf::MAX.fields_per_segment())
346        }];
347
348        for f in 0..nf.fields_per_segment() {
349            let addr = elem_base.wrapping_add(u64::from(f * elem_bytes));
350            match read_mem_element(memory, addr, eew) {
351                Ok(data) => {
352                    // SAFETY: `f < nf` and the precondition on this function requires
353                    // `nf <= Nf::MAX` (the V spec encodes nf in 3 bits giving 1..=Nf::MAX, and the
354                    // decoder enforces this before constructing the instruction). Therefore, `f as
355                    // usize < nf as usize <= Nf::MAX`, which is exactly the length of `field_buf`.
356                    unsafe {
357                        *field_buf.get_unchecked_mut(f as usize) = data;
358                    }
359                }
360                Err(mem_err) => {
361                    cold_path();
362                    if FAULT_ONLY_FIRST && i > 0 {
363                        env.set_vl(Vl::from(i));
364                        env.mark_vs_dirty();
365                        env.reset_vstart();
366                        return Ok(());
367                    }
368                    if i > u16::from(vstart) {
369                        // Elements [vstart, i) were committed; VS is now dirty.
370                        env.mark_vs_dirty();
371                        // vstart records the faulting element for restartability.
372                        env.set_vstart(Vstart::from(i));
373                    }
374                    return Err(ExecutionError::from(mem_err));
375                }
376            }
377        }
378
379        // All nf fields for element i were read successfully; commit to the register file.
380        for f in 0..nf.fields_per_segment() {
381            // SAFETY: Guaranteed by function contract
382            let field_base_reg =
383                unsafe { VReg::from_bits(vd.to_bits() + f * group_regs).unwrap_unchecked() };
384            // SAFETY: need `field_base_reg + i / (VLEN.bytes() / elem_bytes) < 32`.
385            //
386            // Let `elems_per_reg = VLEN.bytes() / elem_bytes`.
387            // `i < vl <= group_regs * elems_per_reg` (precondition), so
388            // `i / elems_per_reg < group_regs`.
389            //
390            // `field_base_reg = vd.to_bits() + f * group_regs`. Since `f < nf` and the
391            // precondition guarantees `vd.to_bits() + nf * group_regs <= 32`:
392            // `field_base_reg + group_regs <= vd.to_bits() + (f+1) * group_regs
393            //                             <= vd.to_bits() + nf * group_regs <= 32`.
394            //
395            // Therefore, `field_base_reg + i / elems_per_reg
396            //            < field_base_reg + group_regs <= 32`.
397            //
398            // For `field_buf`: `f < nf <= Nf::MAX` (the same argument as in the read loop
399            // above), so `f as usize < Nf::MAX = field_buf.len()`.
400            unsafe {
401                write_group_element(
402                    env.write_vregs(),
403                    field_base_reg,
404                    i,
405                    eew,
406                    *field_buf.get_unchecked(f as usize),
407                );
408            }
409        }
410    }
411
412    env.mark_vs_dirty();
413    env.reset_vstart();
414    Ok(())
415}
416
417/// Execute a strided or strided segment load.
418///
419/// `addr[i] = base + i * stride` where `stride` is a signed XLEN-wide value. Field `f` of
420/// element `i` is at `addr[i] + f * eew.bytes()`.
421///
422/// # Safety
423/// - `vd.to_bits() % group_regs == 0`
424/// - `vd.to_bits() + nf * group_regs <= 32`
425/// - `vl <= group_regs * VLEN.bytes() / eew.bytes()`
426/// - When `vm=false`: `vd` does not overlap `v0` (i.e. `vd.to_bits() != 0`)
427#[inline(always)]
428#[expect(clippy::too_many_arguments, reason = "Internal API")]
429#[doc(hidden)]
430#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
431pub unsafe fn execute_strided_load<Reg, Env, Memory>(
432    env: &mut Env,
433    memory: &Memory,
434    vd: VReg,
435    vm: bool,
436    base: u64,
437    stride: i64,
438    eew: Eew,
439    group_regs: NonZeroU8,
440    nf: Nf,
441) -> Result<(), ExecutionError<Reg::Type>>
442where
443    Reg: Register,
444    Env: VectorRegistersExt<Reg>,
445    [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
446    Memory: VirtualMemory,
447{
448    let group_regs = group_regs.get();
449    let vl = env.vl();
450    let vstart = env.vstart();
451    let elem_bytes = eew.bytes_width();
452
453    // SAFETY: `vl <= VLMAX <= VLEN` (precondition)
454    let mask_buf = unsafe { snapshot_mask(env.read_vregs(), vm, vl) };
455
456    for i in vstart.range_to(vl) {
457        if !vm && !mask_bit(&mask_buf, i) {
458            continue;
459        }
460
461        let elem_base = base.wrapping_add(i64::from(i).wrapping_mul(stride).cast_unsigned());
462
463        for f in 0..nf.fields_per_segment() {
464            let addr = elem_base.wrapping_add(u64::from(f * elem_bytes));
465            let data = match read_mem_element(memory, addr, eew) {
466                Ok(data) => data,
467                Err(mem_err) => {
468                    cold_path();
469                    if f > 0 || i > u16::from(vstart) {
470                        env.mark_vs_dirty();
471                        env.set_vstart(Vstart::from(i));
472                    }
473                    return Err(ExecutionError::from(mem_err));
474                }
475            };
476            // SAFETY: Guaranteed by function contract
477            let field_base_reg =
478                unsafe { VReg::from_bits(vd.to_bits() + f * group_regs).unwrap_unchecked() };
479            // SAFETY: need `field_base_reg + i / (VLEN.bytes() / elem_bytes) < 32`.
480            //
481            // Let `elems_per_reg = VLEN.bytes() / elem_bytes`.
482            // `i < vl <= group_regs * elems_per_reg` (precondition), so
483            // `i / elems_per_reg < group_regs`.
484            //
485            // `field_base_reg = vd.to_bits() + f * group_regs`. Since `f < nf` and
486            // `vd.to_bits() + nf * group_regs <= 32` (precondition):
487            // `field_base_reg + group_regs <= vd.to_bits() + (f+1) * group_regs
488            //                             <= vd.to_bits() + nf * group_regs <= 32`.
489            //
490            // Therefore, `field_base_reg + i / elems_per_reg < field_base_reg + group_regs <= 32`.
491            unsafe {
492                write_group_element(env.write_vregs(), field_base_reg, i, eew, data);
493            }
494        }
495    }
496
497    env.mark_vs_dirty();
498    env.reset_vstart();
499    Ok(())
500}
501
502/// Execute an indexed (unordered or ordered) or indexed segment load.
503///
504/// For element `i`, reads `index_eew`-sized bytes from register group `vs2` at element `i`
505/// to obtain a zero-extended byte offset, then loads `nf` data fields from
506/// `base + offset + f * data_eew.bytes()`. Unordered vs ordered is functionally identical in
507/// a software interpreter.
508///
509/// # Safety
510/// - `vd.to_bits() % data_group_regs == 0`
511/// - `vd.to_bits() + nf * data_group_regs <= 32`
512/// - `vs2.to_bits() + (vl - 1) / (VLEN.bytes() / index_eew.bytes()) < 32` (all `vl` index elements
513///   fit within the register file; satisfied when `vs2` is alignment-checked against `EMUL_index`
514///   and `vl` is the architectural `vl` bounded by `VLMAX`)
515/// - `vl <= data_group_regs * VLEN.bytes() / data_eew.bytes()` (all `vl` elements fit in a data
516///   group)
517/// - When `vm=false`: `vd` does not overlap `v0` (i.e. `vd.to_bits() != 0`)
518#[inline(always)]
519#[expect(clippy::too_many_arguments, reason = "Internal API")]
520#[doc(hidden)]
521#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
522pub unsafe fn execute_indexed_load<Reg, Env, Memory>(
523    env: &mut Env,
524    memory: &Memory,
525    vd: VReg,
526    vs2: VReg,
527    vm: bool,
528    base: u64,
529    data_eew: Eew,
530    index_eew: Eew,
531    data_group_regs: NonZeroU8,
532    nf: Nf,
533) -> Result<(), ExecutionError<Reg::Type>>
534where
535    Reg: Register,
536    Env: VectorRegistersExt<Reg>,
537    [(); SUPPORTED_ELEN_VLEN::<{ Env::ELEN }, { Env::VLEN }>]:,
538    Memory: VirtualMemory,
539{
540    let data_group_regs = data_group_regs.get();
541    let vl = env.vl();
542    let vstart = env.vstart();
543    let index_base_reg = vs2;
544
545    // SAFETY: `vl <= VLMAX <= VLEN` (precondition)
546    let mask_buf = unsafe { snapshot_mask(env.read_vregs(), vm, vl) };
547
548    for i in vstart.range_to(vl) {
549        if !vm && !mask_bit(&mask_buf, i) {
550            continue;
551        }
552
553        // SAFETY: need `index_base_reg + i / (VLEN.bytes() / index_eew.bytes()) < 32`.
554        //
555        // The caller verified `vs2` is aligned to `EMUL_index` registers and that
556        // `vs2.to_bits() + EMUL_index <= 32`. `EMUL_index` is defined so that
557        // `EMUL_index * (VLEN.bytes() / index_eew.bytes()) = VLMAX`. Since `i < vl <= VLMAX`,
558        // `i / (VLEN.bytes() / index_eew.bytes()) < EMUL_index`, and therefore
559        // `index_base_reg + i / (VLEN.bytes() / index_eew.bytes()) < index_base_reg + EMUL_index <=
560        // 32`.
561        let index_buf =
562            unsafe { read_group_element(env.read_vregs(), index_base_reg, i, index_eew) };
563        let offset = u64::from_le_bytes(index_buf);
564        let elem_addr = base.wrapping_add(offset);
565
566        let data_elem_bytes = data_eew.bytes_width();
567        for f in 0..nf.fields_per_segment() {
568            let addr = elem_addr.wrapping_add(u64::from(f) * u64::from(data_elem_bytes));
569            let data = match read_mem_element(memory, addr, data_eew) {
570                Ok(data) => data,
571                Err(mem_err) => {
572                    cold_path();
573                    if f > 0 || i > u16::from(vstart) {
574                        env.mark_vs_dirty();
575                        env.set_vstart(Vstart::from(i));
576                    }
577                    return Err(ExecutionError::from(mem_err));
578                }
579            };
580            // SAFETY: Guaranteed by function contract
581            let field_base_reg =
582                unsafe { VReg::from_bits(vd.to_bits() + f * data_group_regs).unwrap_unchecked() };
583            // SAFETY: need `field_base_reg + i / (VLEN.bytes() / data_eew.bytes()) < 32`.
584            //
585            // Let `data_elems_per_reg = VLEN.bytes() / data_eew.bytes()`.
586            // `i < vl <= data_group_regs * data_elems_per_reg` (precondition), so
587            // `i / data_elems_per_reg < data_group_regs`.
588            //
589            // `field_base_reg = vd.to_bits() + f * data_group_regs`. Since `f < nf` and
590            // `vd.to_bits() + nf * data_group_regs <= 32` (precondition):
591            // `field_base_reg + data_group_regs <= vd.to_bits() + (f+1) * data_group_regs
592            //                                  <= vd.to_bits() + nf * data_group_regs <= 32`.
593            //
594            // Therefore,
595            // `field_base_reg + i / data_elems_per_reg < field_base_reg + data_group_regs <= 32`.
596            unsafe {
597                write_group_element(env.write_vregs(), field_base_reg, i, data_eew, data);
598            }
599        }
600    }
601
602    env.mark_vs_dirty();
603    env.reset_vstart();
604    Ok(())
605}