Skip to main content

ab_riscv_interpreter/v/zvexx/load/
zvexx_load_helpers.rs

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