Skip to main content

ab_riscv_interpreter/v/zvexx/
load.rs

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