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}