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}