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