Skip to main content

ab_riscv_interpreter/v/zve64x/
load.rs

1//! Zve64x vector load instructions
2
3#[cfg(test)]
4mod tests;
5pub mod zve64x_load_helpers;
6
7use crate::v::vector_registers::VectorRegistersExt;
8use crate::v::zve64x::zve64x_helpers;
9use crate::{ExecutableInstruction, ExecutionError, ProgramCounter, RegisterFile, VirtualMemory};
10use ab_riscv_macros::instruction_execution;
11use ab_riscv_primitives::prelude::*;
12use core::fmt;
13use core::ops::ControlFlow;
14
15#[instruction_execution]
16impl<Reg, Regs, ExtState, Memory, PC, InstructionHandler, CustomError>
17    ExecutableInstruction<Regs, ExtState, Memory, PC, InstructionHandler, CustomError>
18    for Zve64xLoadInstruction<Reg>
19where
20    Reg: Register,
21    Regs: RegisterFile<Reg>,
22    ExtState: VectorRegistersExt<Reg, CustomError>,
23    [(); ExtState::ELEN as usize]:,
24    [(); ExtState::VLEN as usize]:,
25    [(); ExtState::VLENB as usize]:,
26    Memory: VirtualMemory,
27    PC: ProgramCounter<Reg::Type, Memory, CustomError>,
28    CustomError: fmt::Debug,
29{
30    #[inline(always)]
31    fn execute(
32        self,
33        regs: &mut Regs,
34        ext_state: &mut ExtState,
35        memory: &mut Memory,
36        program_counter: &mut PC,
37        _system_instruction_handler: &mut InstructionHandler,
38    ) -> Result<ControlFlow<()>, ExecutionError<Reg::Type, CustomError>> {
39        match self {
40            // Whole-register load: loads `nreg` consecutive registers starting at `vd` directly
41            // from memory. `vd` must be aligned to `nreg`. Ignores vtype, vl, vstart, masking.
42            Self::Vlr {
43                vd,
44                rs1,
45                nreg,
46                eew: _,
47            } => {
48                if !ext_state.vector_instructions_allowed() {
49                    Err(ExecutionError::IllegalInstruction {
50                        address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
51                    })?;
52                }
53                if u32::from(vd.bits()) % u32::from(nreg) != 0 {
54                    Err(ExecutionError::IllegalInstruction {
55                        address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
56                    })?;
57                }
58                let base = regs.read(rs1).as_u64();
59                let vlenb = u64::from(ExtState::VLENB);
60                for reg_off in 0..u64::from(nreg) {
61                    let reg_idx = u64::from(vd.bits()) + reg_off;
62                    let bytes = match memory.read_slice(base + reg_off * vlenb, ExtState::VLENB) {
63                        Ok(bytes) => bytes,
64                        Err(error) => {
65                            if reg_off > 0 {
66                                ext_state.mark_vs_dirty();
67                                ext_state.reset_vstart();
68                            }
69                            Err(ExecutionError::MemoryAccess(error))?
70                        }
71                    };
72                    // SAFETY: `reg_idx < 32` because the decoder guarantees nreg in {1,2,4,8}
73                    // and vd is nreg-aligned (checked above), so vd.bits() + nreg - 1 <= 31.
74                    // `read_slice` returns a slice of exactly `ExtState::VLENB` bytes on success,
75                    // matching `dst`'s length, so `copy_from_slice` cannot panic.
76                    let dst = unsafe { ext_state.write_vreg().get_unchecked_mut(reg_idx as usize) };
77                    dst.copy_from_slice(bytes);
78                }
79                ext_state.mark_vs_dirty();
80                ext_state.reset_vstart();
81            }
82
83            // Mask load: loads ceil(vl / 8) bytes from base into vd with no masking applied.
84            // Does not require a valid vtype: when vill is set vl is 0, so zero bytes are read.
85            Self::Vlm { vd, rs1 } => {
86                if !ext_state.vector_instructions_allowed() {
87                    Err(ExecutionError::IllegalInstruction {
88                        address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
89                    })?;
90                }
91                let vl = ext_state.vl();
92                let byte_count = vl.div_ceil(u8::BITS);
93                if byte_count > 0 {
94                    let base = regs.read(rs1).as_u64();
95                    let bytes = memory.read_slice(base, byte_count)?;
96                    // SAFETY: `vd.bits() < 32` is guaranteed by the `VReg` type.
97                    // `bytes.len() == byte_count = vl.div_ceil(8) <= VLEN / 8 = VLENB` because
98                    // `vl <= VLMAX <= VLEN`, so `..bytes.len()` is in bounds within the
99                    // `VLENB`-byte destination register.
100                    unsafe {
101                        ext_state
102                            .write_vreg()
103                            .get_unchecked_mut(usize::from(vd.bits()))
104                            .get_unchecked_mut(..bytes.len())
105                            .copy_from_slice(bytes);
106                    }
107                }
108                ext_state.mark_vs_dirty();
109                ext_state.reset_vstart();
110            }
111
112            // Unit-stride load.
113            //
114            // Destination EMUL = EEW/SEW * LMUL, computed via `index_register_count`. This
115            // gives `group_regs` such that `VLMAX = group_regs * VLENB / eew.bytes()` matches
116            // the architectural `vl`.
117            Self::Vle { vd, rs1, vm, eew } => {
118                if !ext_state.vector_instructions_allowed() {
119                    Err(ExecutionError::IllegalInstruction {
120                        address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
121                    })?;
122                }
123                let vtype = ext_state
124                    .vtype()
125                    .ok_or(ExecutionError::IllegalInstruction {
126                        address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
127                    })?;
128                let group_regs = vtype
129                    .vlmul()
130                    .index_register_count(eew, vtype.vsew())
131                    .ok_or(ExecutionError::IllegalInstruction {
132                        address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
133                    })?;
134                zve64x_load_helpers::check_register_group_alignment::<Reg, _, _, _>(
135                    program_counter,
136                    vd,
137                    group_regs,
138                )?;
139                if !vm && zve64x_load_helpers::groups_overlap(vd, group_regs, VReg::V0, 1) {
140                    Err(ExecutionError::IllegalInstruction {
141                        address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
142                    })?;
143                }
144                // SAFETY:
145                // - 1 <= MAX_NF
146                // - alignment: `check_register_group_alignment` verified `vd % group_regs == 0` and
147                //   `vd + group_regs <= 32`, satisfying both the alignment and nf=1 bounds
148                //   preconditions
149                // - `vl <= group_regs * VLENB / eew.bytes()`: `group_regs` is the EMUL computed for
150                //   this `eew` and `vtype`, so this VLMAX equals the architectural VLMAX that
151                //   bounds `vl`
152                // - mask overlap: checked above via `groups_overlap`
153                unsafe {
154                    zve64x_load_helpers::execute_unit_stride_load(
155                        ext_state,
156                        memory,
157                        vd,
158                        vm,
159                        ext_state.vl(),
160                        u32::from(ext_state.vstart()),
161                        regs.read(rs1).as_u64(),
162                        eew,
163                        group_regs,
164                        1,
165                        false,
166                    )?;
167                }
168            }
169
170            // Fault-only-first unit-stride load. Preconditions identical to `Vle`.
171            Self::Vleff { vd, rs1, vm, eew } => {
172                if !ext_state.vector_instructions_allowed() {
173                    Err(ExecutionError::IllegalInstruction {
174                        address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
175                    })?;
176                }
177                let vtype = ext_state
178                    .vtype()
179                    .ok_or(ExecutionError::IllegalInstruction {
180                        address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
181                    })?;
182                let group_regs = vtype
183                    .vlmul()
184                    .index_register_count(eew, vtype.vsew())
185                    .ok_or(ExecutionError::IllegalInstruction {
186                        address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
187                    })?;
188                zve64x_load_helpers::check_register_group_alignment::<Reg, _, _, _>(
189                    program_counter,
190                    vd,
191                    group_regs,
192                )?;
193                if !vm && zve64x_load_helpers::groups_overlap(vd, group_regs, VReg::V0, 1) {
194                    Err(ExecutionError::IllegalInstruction {
195                        address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
196                    })?;
197                }
198                // SAFETY: preconditions identical to `Vle`; see that arm for the full argument.
199                unsafe {
200                    zve64x_load_helpers::execute_unit_stride_load(
201                        ext_state,
202                        memory,
203                        vd,
204                        vm,
205                        ext_state.vl(),
206                        u32::from(ext_state.vstart()),
207                        regs.read(rs1).as_u64(),
208                        eew,
209                        group_regs,
210                        1,
211                        true,
212                    )?;
213                }
214            }
215
216            // Strided load. Destination EMUL = EEW/SEW * LMUL as for unit-stride.
217            Self::Vlse {
218                vd,
219                rs1,
220                rs2,
221                vm,
222                eew,
223            } => {
224                if !ext_state.vector_instructions_allowed() {
225                    Err(ExecutionError::IllegalInstruction {
226                        address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
227                    })?;
228                }
229                let vtype = ext_state
230                    .vtype()
231                    .ok_or(ExecutionError::IllegalInstruction {
232                        address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
233                    })?;
234                let group_regs = vtype
235                    .vlmul()
236                    .index_register_count(eew, vtype.vsew())
237                    .ok_or(ExecutionError::IllegalInstruction {
238                        address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
239                    })?;
240                zve64x_load_helpers::check_register_group_alignment::<Reg, _, _, _>(
241                    program_counter,
242                    vd,
243                    group_regs,
244                )?;
245                if !vm && zve64x_load_helpers::groups_overlap(vd, group_regs, VReg::V0, 1) {
246                    Err(ExecutionError::IllegalInstruction {
247                        address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
248                    })?;
249                }
250                // rs2 holds a signed stride; reinterpret the register value as signed.
251                let stride = regs.read(rs2).as_u64().cast_signed();
252                // SAFETY:
253                // - alignment and nf=1 bounds: `check_register_group_alignment` verified `vd %
254                //   group_regs == 0` and `vd + group_regs <= 32`
255                // - `vl <= group_regs * VLENB / eew.bytes()`: `group_regs` is the EMUL for this
256                //   `eew` and `vtype`, so this VLMAX equals the architectural VLMAX bounding `vl`
257                // - mask overlap: checked above via `groups_overlap`
258                unsafe {
259                    zve64x_load_helpers::execute_strided_load(
260                        ext_state,
261                        memory,
262                        vd,
263                        vm,
264                        ext_state.vl(),
265                        u32::from(ext_state.vstart()),
266                        regs.read(rs1).as_u64(),
267                        stride,
268                        eew,
269                        group_regs,
270                        1,
271                    )?;
272                }
273            }
274
275            // Indexed-unordered load: eew is the index EEW; data EEW comes from vtype.vsew().
276            // The data destination uses the base LMUL (data EEW = SEW for indexed loads).
277            Self::Vluxei {
278                vd,
279                rs1,
280                vs2,
281                vm,
282                eew: index_eew,
283            } => {
284                if !ext_state.vector_instructions_allowed() {
285                    Err(ExecutionError::IllegalInstruction {
286                        address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
287                    })?;
288                }
289                let vtype = ext_state
290                    .vtype()
291                    .ok_or(ExecutionError::IllegalInstruction {
292                        address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
293                    })?;
294                let data_group_regs = vtype.vlmul().register_count();
295                let index_group_regs = vtype
296                    .vlmul()
297                    .index_register_count(index_eew, vtype.vsew())
298                    .ok_or(ExecutionError::IllegalInstruction {
299                        address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
300                    })?;
301                zve64x_load_helpers::check_register_group_alignment::<Reg, _, _, _>(
302                    program_counter,
303                    vd,
304                    data_group_regs,
305                )?;
306                zve64x_load_helpers::check_register_group_alignment::<Reg, _, _, _>(
307                    program_counter,
308                    vs2,
309                    index_group_regs,
310                )?;
311                if zve64x_load_helpers::groups_overlap(vd, data_group_regs, vs2, index_group_regs) {
312                    Err(ExecutionError::IllegalInstruction {
313                        address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
314                    })?;
315                }
316                if !vm && zve64x_load_helpers::groups_overlap(vd, data_group_regs, VReg::V0, 1) {
317                    Err(ExecutionError::IllegalInstruction {
318                        address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
319                    })?;
320                }
321                // SAFETY:
322                // - data alignment/nf=1 bounds: `check_register_group_alignment` on `vd`
323                // - index alignment/bounds: `check_register_group_alignment` on `vs2`
324                // - `vl <= data_group_regs * VLENB / data_eew.bytes()`: data EEW = SEW and
325                //   `data_group_regs = LMUL`, so VLMAX = LMUL * VLEN / SEW, which bounds `vl`
326                // - `vl <= index_group_regs * VLENB / index_eew.bytes()`: `index_group_regs` is
327                //   EMUL_index defined so this VLMAX_index equals the architectural VLMAX
328                // - no overlap between data and index groups: checked above
329                // - mask overlap: checked above via `groups_overlap`
330                unsafe {
331                    zve64x_load_helpers::execute_indexed_load(
332                        ext_state,
333                        memory,
334                        vd,
335                        vs2,
336                        vm,
337                        ext_state.vl(),
338                        u32::from(ext_state.vstart()),
339                        regs.read(rs1).as_u64(),
340                        vtype.vsew().as_eew(),
341                        index_eew,
342                        data_group_regs,
343                        1,
344                    )?;
345                }
346            }
347
348            // Indexed-ordered load: functionally identical to `Vluxei` for a software
349            // interpreter; memory access ordering has no observable effect here.
350            Self::Vloxei {
351                vd,
352                rs1,
353                vs2,
354                vm,
355                eew: index_eew,
356            } => {
357                if !ext_state.vector_instructions_allowed() {
358                    Err(ExecutionError::IllegalInstruction {
359                        address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
360                    })?;
361                }
362                let vtype = ext_state
363                    .vtype()
364                    .ok_or(ExecutionError::IllegalInstruction {
365                        address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
366                    })?;
367                let data_group_regs = vtype.vlmul().register_count();
368                let index_group_regs = vtype
369                    .vlmul()
370                    .index_register_count(index_eew, vtype.vsew())
371                    .ok_or(ExecutionError::IllegalInstruction {
372                        address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
373                    })?;
374                zve64x_load_helpers::check_register_group_alignment::<Reg, _, _, _>(
375                    program_counter,
376                    vd,
377                    data_group_regs,
378                )?;
379                zve64x_load_helpers::check_register_group_alignment::<Reg, _, _, _>(
380                    program_counter,
381                    vs2,
382                    index_group_regs,
383                )?;
384                if zve64x_load_helpers::groups_overlap(vd, data_group_regs, vs2, index_group_regs) {
385                    Err(ExecutionError::IllegalInstruction {
386                        address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
387                    })?;
388                }
389                if !vm && zve64x_load_helpers::groups_overlap(vd, data_group_regs, VReg::V0, 1) {
390                    Err(ExecutionError::IllegalInstruction {
391                        address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
392                    })?;
393                }
394                // SAFETY: preconditions identical to `Vluxei`; see that arm for the full
395                // argument.
396                unsafe {
397                    zve64x_load_helpers::execute_indexed_load(
398                        ext_state,
399                        memory,
400                        vd,
401                        vs2,
402                        vm,
403                        ext_state.vl(),
404                        u32::from(ext_state.vstart()),
405                        regs.read(rs1).as_u64(),
406                        vtype.vsew().as_eew(),
407                        index_eew,
408                        data_group_regs,
409                        1,
410                    )?;
411                }
412            }
413
414            // Unit-stride segment load. EMUL = EEW/SEW * LMUL per field group.
415            Self::Vlseg {
416                vd,
417                rs1,
418                vm,
419                eew,
420                nf,
421            } => {
422                if !ext_state.vector_instructions_allowed() {
423                    Err(ExecutionError::IllegalInstruction {
424                        address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
425                    })?;
426                }
427                let vtype = ext_state
428                    .vtype()
429                    .ok_or(ExecutionError::IllegalInstruction {
430                        address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
431                    })?;
432                let group_regs = vtype
433                    .vlmul()
434                    .index_register_count(eew, vtype.vsew())
435                    .ok_or(ExecutionError::IllegalInstruction {
436                        address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
437                    })?;
438                zve64x_load_helpers::validate_segment_registers::<Reg, _, _, _>(
439                    program_counter,
440                    vd,
441                    vm,
442                    group_regs,
443                    nf,
444                )?;
445                if nf > zve64x_load_helpers::MAX_NF {
446                    Err(ExecutionError::IllegalInstruction {
447                        address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
448                    })?;
449                }
450                // SAFETY:
451                // - `nf <= MAX_NF` checked above
452                // - alignment and nf-group bounds: `validate_segment_registers` verified `vd %
453                //   group_regs == 0` and `vd + nf * group_regs <= 32`
454                // - `vl <= group_regs * VLENB / eew.bytes()`: `group_regs` is the EMUL for this
455                //   `eew` and `vtype`, so this VLMAX equals the architectural VLMAX bounding `vl`
456                // - mask overlap with v0: `validate_segment_registers` checked `vd.bits() != 0`
457                //   when `vm=false`, ensuring no field group contains v0
458                unsafe {
459                    zve64x_load_helpers::execute_unit_stride_load(
460                        ext_state,
461                        memory,
462                        vd,
463                        vm,
464                        ext_state.vl(),
465                        u32::from(ext_state.vstart()),
466                        regs.read(rs1).as_u64(),
467                        eew,
468                        group_regs,
469                        nf,
470                        false,
471                    )?;
472                }
473            }
474
475            // Fault-only-first segment load. Preconditions identical to `Vlseg`.
476            Self::Vlsegff {
477                vd,
478                rs1,
479                vm,
480                eew,
481                nf,
482            } => {
483                if !ext_state.vector_instructions_allowed() {
484                    Err(ExecutionError::IllegalInstruction {
485                        address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
486                    })?;
487                }
488                let vtype = ext_state
489                    .vtype()
490                    .ok_or(ExecutionError::IllegalInstruction {
491                        address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
492                    })?;
493                let group_regs = vtype
494                    .vlmul()
495                    .index_register_count(eew, vtype.vsew())
496                    .ok_or(ExecutionError::IllegalInstruction {
497                        address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
498                    })?;
499                zve64x_load_helpers::validate_segment_registers::<Reg, _, _, _>(
500                    program_counter,
501                    vd,
502                    vm,
503                    group_regs,
504                    nf,
505                )?;
506                if nf > zve64x_load_helpers::MAX_NF {
507                    Err(ExecutionError::IllegalInstruction {
508                        address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
509                    })?;
510                }
511                // SAFETY: preconditions identical to `Vlseg`; see that arm for the full argument.
512                unsafe {
513                    zve64x_load_helpers::execute_unit_stride_load(
514                        ext_state,
515                        memory,
516                        vd,
517                        vm,
518                        ext_state.vl(),
519                        u32::from(ext_state.vstart()),
520                        regs.read(rs1).as_u64(),
521                        eew,
522                        group_regs,
523                        nf,
524                        true,
525                    )?;
526                }
527            }
528
529            // Strided segment load. EMUL = EEW/SEW * LMUL as for `Vlse`.
530            Self::Vlsseg {
531                vd,
532                rs1,
533                rs2,
534                vm,
535                eew,
536                nf,
537            } => {
538                if !ext_state.vector_instructions_allowed() {
539                    Err(ExecutionError::IllegalInstruction {
540                        address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
541                    })?;
542                }
543                let vtype = ext_state
544                    .vtype()
545                    .ok_or(ExecutionError::IllegalInstruction {
546                        address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
547                    })?;
548                let group_regs = vtype
549                    .vlmul()
550                    .index_register_count(eew, vtype.vsew())
551                    .ok_or(ExecutionError::IllegalInstruction {
552                        address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
553                    })?;
554                zve64x_load_helpers::validate_segment_registers::<Reg, _, _, _>(
555                    program_counter,
556                    vd,
557                    vm,
558                    group_regs,
559                    nf,
560                )?;
561                let stride = regs.read(rs2).as_u64().cast_signed();
562                // SAFETY:
563                // - alignment and nf-group bounds: `validate_segment_registers` verified `vd %
564                //   group_regs == 0` and `vd + nf * group_regs <= 32`
565                // - `vl <= group_regs * VLENB / eew.bytes()`: `group_regs` is EMUL for this `eew`
566                //   and `vtype`
567                // - mask overlap: `validate_segment_registers` checked `vd.bits() != 0` when
568                //   `vm=false`
569                unsafe {
570                    zve64x_load_helpers::execute_strided_load(
571                        ext_state,
572                        memory,
573                        vd,
574                        vm,
575                        ext_state.vl(),
576                        u32::from(ext_state.vstart()),
577                        regs.read(rs1).as_u64(),
578                        stride,
579                        eew,
580                        group_regs,
581                        nf,
582                    )?;
583                }
584            }
585
586            // Indexed-unordered segment load
587            Self::Vluxseg {
588                vd,
589                rs1,
590                vs2,
591                vm,
592                eew: index_eew,
593                nf,
594            } => {
595                if !ext_state.vector_instructions_allowed() {
596                    Err(ExecutionError::IllegalInstruction {
597                        address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
598                    })?;
599                }
600                let vtype = ext_state
601                    .vtype()
602                    .ok_or(ExecutionError::IllegalInstruction {
603                        address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
604                    })?;
605                let data_group_regs = vtype.vlmul().register_count();
606                let index_group_regs = vtype
607                    .vlmul()
608                    .index_register_count(index_eew, vtype.vsew())
609                    .ok_or(ExecutionError::IllegalInstruction {
610                        address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
611                    })?;
612                // `validate_segment_registers` is called before the per-field overlap loop so
613                // that `vd.bits() + f * data_group_regs < 32` is established for all `f < nf`,
614                // which is required by the `VReg::from_bits` call inside the loop.
615                zve64x_load_helpers::validate_segment_registers::<Reg, _, _, _>(
616                    program_counter,
617                    vd,
618                    vm,
619                    data_group_regs,
620                    nf,
621                )?;
622                zve64x_load_helpers::check_register_group_alignment::<Reg, _, _, _>(
623                    program_counter,
624                    vs2,
625                    index_group_regs,
626                )?;
627                for f in 0..nf {
628                    // SAFETY: `vd.bits() + f * data_group_regs < 32` because
629                    // `validate_segment_registers` established `vd.bits() + nf * data_group_regs
630                    // <= 32` and `f < nf`. The value is in [0, 31], so it is a valid `VReg`
631                    // encoding.
632                    let field_vd = unsafe {
633                        VReg::from_bits(vd.bits() + f * data_group_regs).unwrap_unchecked()
634                    };
635                    if zve64x_load_helpers::groups_overlap(
636                        field_vd,
637                        data_group_regs,
638                        vs2,
639                        index_group_regs,
640                    ) {
641                        Err(ExecutionError::IllegalInstruction {
642                            address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
643                        })?;
644                    }
645                }
646                // SAFETY:
647                // - data alignment/nf-group bounds: `validate_segment_registers` verified `vd %
648                //   data_group_regs == 0` and `vd + nf * data_group_regs <= 32`
649                // - index alignment/bounds: `check_register_group_alignment` verified `vs2 %
650                //   EMUL_index == 0` and `vs2 + EMUL_index <= 32`
651                // - no field/index group overlap: verified by the loop above
652                // - `vl <= data_group_regs * VLENB / data_eew.bytes()`: data EEW = SEW and
653                //   `data_group_regs = LMUL`, so VLMAX = LMUL * VLEN / SEW bounds `vl`
654                // - `vl <= EMUL_index * VLENB / index_eew.bytes()`: `index_group_regs` (EMUL_index)
655                //   is defined so this VLMAX_index equals the architectural VLMAX
656                // - mask overlap: `validate_segment_registers` checked `vd.bits() != 0` when
657                //   `vm=false`, and no field group starts at 0 since groups are contiguous from
658                //   `vd` which is nonzero
659                unsafe {
660                    zve64x_load_helpers::execute_indexed_load(
661                        ext_state,
662                        memory,
663                        vd,
664                        vs2,
665                        vm,
666                        ext_state.vl(),
667                        u32::from(ext_state.vstart()),
668                        regs.read(rs1).as_u64(),
669                        vtype.vsew().as_eew(),
670                        index_eew,
671                        data_group_regs,
672                        nf,
673                    )?;
674                }
675            }
676
677            // Indexed-ordered segment load: functionally identical to `Vluxseg` for a software
678            // interpreter
679            Self::Vloxseg {
680                vd,
681                rs1,
682                vs2,
683                vm,
684                eew: index_eew,
685                nf,
686            } => {
687                if !ext_state.vector_instructions_allowed() {
688                    Err(ExecutionError::IllegalInstruction {
689                        address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
690                    })?;
691                }
692                let vtype = ext_state
693                    .vtype()
694                    .ok_or(ExecutionError::IllegalInstruction {
695                        address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
696                    })?;
697                let data_group_regs = vtype.vlmul().register_count();
698                let index_group_regs = vtype
699                    .vlmul()
700                    .index_register_count(index_eew, vtype.vsew())
701                    .ok_or(ExecutionError::IllegalInstruction {
702                        address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
703                    })?;
704                zve64x_load_helpers::validate_segment_registers::<Reg, _, _, _>(
705                    program_counter,
706                    vd,
707                    vm,
708                    data_group_regs,
709                    nf,
710                )?;
711                zve64x_load_helpers::check_register_group_alignment::<Reg, _, _, _>(
712                    program_counter,
713                    vs2,
714                    index_group_regs,
715                )?;
716                for f in 0..nf {
717                    // SAFETY: `vd.bits() + f * data_group_regs < 32` because
718                    // `validate_segment_registers` established `vd.bits() + nf * data_group_regs
719                    // <= 32` and `f < nf`. The value is in [0, 31], so it is a valid `VReg`
720                    // encoding.
721                    let field_vd = unsafe {
722                        VReg::from_bits(vd.bits() + f * data_group_regs).unwrap_unchecked()
723                    };
724                    if zve64x_load_helpers::groups_overlap(
725                        field_vd,
726                        data_group_regs,
727                        vs2,
728                        index_group_regs,
729                    ) {
730                        Err(ExecutionError::IllegalInstruction {
731                            address: program_counter.old_pc(zve64x_helpers::INSTRUCTION_SIZE),
732                        })?;
733                    }
734                }
735                // SAFETY: preconditions identical to `Vluxseg`; see that arm for the full
736                // argument
737                unsafe {
738                    zve64x_load_helpers::execute_indexed_load(
739                        ext_state,
740                        memory,
741                        vd,
742                        vs2,
743                        vm,
744                        ext_state.vl(),
745                        u32::from(ext_state.vstart()),
746                        regs.read(rs1).as_u64(),
747                        vtype.vsew().as_eew(),
748                        index_eew,
749                        data_group_regs,
750                        nf,
751                    )?;
752                }
753            }
754        }
755
756        Ok(ControlFlow::Continue(()))
757    }
758}