Skip to main content

ab_riscv_interpreter/v/zvexx/
store.rs

1//! ZveXx vector store instructions
2
3#[cfg(test)]
4mod tests;
5pub mod zvexx_store_helpers;
6
7use crate::v::vector_registers::VectorRegistersExt;
8use crate::v::zvexx::load::zvexx_load_helpers;
9use crate::v::zvexx::zvexx_helpers;
10use crate::{
11    ExecutableInstruction, ExecutableInstructionCsr, ExecutableInstructionOperands, ExecutionError,
12    ProgramCounter, RegisterFile, Rs1Rs2OperandValues, Rs1Rs2Operands, VirtualMemory,
13};
14use ab_riscv_macros::instruction_execution;
15use ab_riscv_primitives::prelude::*;
16use core::fmt;
17use core::ops::ControlFlow;
18
19#[instruction_execution]
20impl<Reg> ExecutableInstructionOperands for ZveXxStoreInstruction<Reg> where Reg: Register {}
21
22#[instruction_execution]
23impl<Reg, ExtState, CustomError> ExecutableInstructionCsr<ExtState, CustomError>
24    for ZveXxStoreInstruction<Reg>
25where
26    Reg: Register,
27{
28}
29
30#[instruction_execution]
31impl<Reg, Regs, ExtState, Memory, PC, InstructionHandler, CustomError>
32    ExecutableInstruction<Regs, ExtState, Memory, PC, InstructionHandler, CustomError>
33    for ZveXxStoreInstruction<Reg>
34where
35    Reg: Register,
36    Regs: RegisterFile<Reg>,
37    ExtState: VectorRegistersExt<Reg, CustomError>,
38    [(); SUPPORTED_ELEN_VLEN::<{ ExtState::ELEN }, { ExtState::VLEN }>]:,
39    Memory: VirtualMemory,
40    PC: ProgramCounter<Reg::Type, Memory, CustomError>,
41    CustomError: fmt::Debug,
42{
43    #[inline(always)]
44    fn execute(
45        self,
46        Rs1Rs2OperandValues {
47            rs1_value,
48            rs2_value,
49        }: Rs1Rs2OperandValues<<Self::Reg as Register>::Type>,
50        _regs: &mut Regs,
51        ext_state: &mut ExtState,
52        memory: &mut Memory,
53        program_counter: &mut PC,
54        _system_instruction_handler: &mut InstructionHandler,
55    ) -> Result<
56        ControlFlow<(), (Self::Reg, <Self::Reg as Register>::Type)>,
57        ExecutionError<Reg::Type, CustomError>,
58    > {
59        match self {
60            // Whole-register store: stores `nreg` consecutive registers starting at `vs3` directly
61            // to memory as a flat byte array of `EVL = nreg * VLEN.bytes()` bytes. `vs3` must be
62            // aligned to `nreg`. Ignores vtype, vl, masking. Honors `vstart` in byte
63            // units: the first `vstart` bytes are skipped. If `vstart >= EVL`, the
64            // instruction is a no-op.
65            Self::Vsr { vs3, rs1: _, nreg } => {
66                let nreg = nreg.num_registers();
67                if !ext_state.vector_instructions_allowed() {
68                    ::core::hint::cold_path();
69                    return Err(ExecutionError::IllegalInstruction {
70                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
71                    });
72                }
73                if vs3.to_bits() % nreg != 0 {
74                    ::core::hint::cold_path();
75                    return Err(ExecutionError::IllegalInstruction {
76                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
77                    });
78                }
79                let vlenb = u64::from(ExtState::VLEN.bytes());
80                let evl = u64::from(nreg) * vlenb;
81                let vstart = ext_state.vstart();
82                if u64::from(u16::from(vstart)) < evl {
83                    let base = rs1_value.as_u64();
84                    let mut byte_off = u64::from(u16::from(vstart));
85                    while byte_off < evl {
86                        let reg_off = byte_off / vlenb;
87                        let in_reg = (byte_off % vlenb) as usize;
88                        // SAFETY: the decoder guarantees `nreg` in {1,2,4,8} and `vs3` is
89                        // `nreg`-aligned (checked above), so `vs3.to_bits() + nreg - 1 <= 31`
90                        let reg = unsafe {
91                            VReg::from_bits(vs3.to_bits() + reg_off as u8).unwrap_unchecked()
92                        };
93                        // SAFETY: `in_reg < VLEN.bytes()` by construction
94                        let src =
95                            unsafe { ext_state.read_vregs().get(reg).get_unchecked(in_reg..) };
96                        if let Err(error) = memory.write_slice(base + byte_off, src) {
97                            ext_state.set_vstart(Vstart::from(byte_off as u16));
98                            return Err(ExecutionError::MemoryAccess(error));
99                        }
100                        byte_off += src.len() as u64;
101                    }
102                }
103                ext_state.reset_vstart();
104            }
105            // Mask store: stores `ceil(vl / 8)` bytes from `vs3` to memory with no masking.
106            // Does not require a valid vtype: when vill is set vl is 0, so zero bytes are written.
107            // Honors `vstart` at byte granularity: the first `vstart / 8` bytes are skipped.
108            Self::Vsm { vs3, rs1: _ } => {
109                if !ext_state.vector_instructions_allowed() {
110                    ::core::hint::cold_path();
111                    return Err(ExecutionError::IllegalInstruction {
112                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
113                    });
114                }
115                let vl = ext_state.vl();
116                let evl_bytes = vl.bytes();
117                let start_byte = ext_state.vstart();
118                if u16::from(start_byte) < evl_bytes {
119                    let base = rs1_value.as_u64();
120                    // SAFETY: `evl_bytes = vl.div_ceil(8) <= VLEN / 8 = VLEN.bytes()` because
121                    // `vl <= VLMAX <= VLEN`, so the slice `start_byte..evl_bytes` is in bounds of
122                    // the `VLEN.bytes()`-byte source register
123                    let src = unsafe {
124                        ext_state.read_vregs().get(vs3).get_unchecked(
125                            usize::from(u16::from(start_byte))..usize::from(evl_bytes),
126                        )
127                    };
128                    memory
129                        .write_slice(base + u64::from(u16::from(start_byte)), src)
130                        .map_err(ExecutionError::MemoryAccess)?;
131                }
132                ext_state.reset_vstart();
133            }
134            // Unit-stride store.
135            //
136            // Source EMUL = EEW/SEW * LMUL, computed via `data_register_count`. This gives
137            // `group_regs` such that `VLMAX = group_regs * VLEN.bytes() / eew.bytes()` matches the
138            // architectural `vl`.
139            Self::Vse {
140                vs3,
141                rs1: _,
142                vm,
143                eew,
144            } => {
145                if !ext_state.vector_instructions_allowed() {
146                    ::core::hint::cold_path();
147                    return Err(ExecutionError::IllegalInstruction {
148                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
149                    });
150                }
151                let Some(vtype) = ext_state.vtype() else {
152                    ::core::hint::cold_path();
153                    return Err(ExecutionError::IllegalInstruction {
154                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
155                    });
156                };
157                let group_regs = vtype.vlmul().data_register_count(eew, vtype.vsew()).ok_or(
158                    ExecutionError::IllegalInstruction {
159                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
160                    },
161                )?;
162                zvexx_load_helpers::check_register_group_alignment::<Reg, _, _, _>(
163                    program_counter,
164                    vs3,
165                    group_regs,
166                )?;
167                // SAFETY:
168                // - alignment: `check_register_group_alignment` verified `vs3 % group_regs == 0`
169                //   and `vs3 + group_regs <= 32`
170                // - `vl <= group_regs * VLEN.bytes() / eew.bytes()`: `group_regs` is the EMUL
171                //   computed for this `eew` and `vtype`, so this VLMAX equals the architectural
172                //   VLMAX that bounds `vl`
173                // - vs3/v0 overlap: stores read vs3 as a source; the spec does not restrict
174                //   source/v0 overlap
175                unsafe {
176                    zvexx_store_helpers::execute_unit_stride_store(
177                        ext_state,
178                        memory,
179                        vs3,
180                        vm,
181                        rs1_value.as_u64(),
182                        eew,
183                        group_regs,
184                        Nf::N1,
185                    )?;
186                }
187            }
188            // Strided store
189            Self::Vsse {
190                vs3,
191                rs1: _,
192                rs2: _,
193                vm,
194                eew,
195            } => {
196                if !ext_state.vector_instructions_allowed() {
197                    ::core::hint::cold_path();
198                    return Err(ExecutionError::IllegalInstruction {
199                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
200                    });
201                }
202                let Some(vtype) = ext_state.vtype() else {
203                    ::core::hint::cold_path();
204                    return Err(ExecutionError::IllegalInstruction {
205                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
206                    });
207                };
208                let group_regs = vtype.vlmul().data_register_count(eew, vtype.vsew()).ok_or(
209                    ExecutionError::IllegalInstruction {
210                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
211                    },
212                )?;
213                zvexx_load_helpers::check_register_group_alignment::<Reg, _, _, _>(
214                    program_counter,
215                    vs3,
216                    group_regs,
217                )?;
218                let stride = rs2_value.as_i64();
219                // SAFETY: same preconditions as `Vse`.
220                unsafe {
221                    zvexx_store_helpers::execute_strided_store(
222                        ext_state,
223                        memory,
224                        vs3,
225                        vm,
226                        rs1_value.as_u64(),
227                        stride,
228                        eew,
229                        group_regs,
230                        Nf::N1,
231                    )?;
232                }
233            }
234            // Indexed-unordered store. Ordering between elements is not guaranteed.
235            Self::Vsuxei {
236                vs3,
237                rs1: _,
238                vs2,
239                vm,
240                eew: index_eew,
241            } => {
242                if !ext_state.vector_instructions_allowed() {
243                    ::core::hint::cold_path();
244                    return Err(ExecutionError::IllegalInstruction {
245                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
246                    });
247                }
248                let Some(vtype) = ext_state.vtype() else {
249                    ::core::hint::cold_path();
250                    return Err(ExecutionError::IllegalInstruction {
251                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
252                    });
253                };
254                let data_eew = vtype.vsew().as_eew();
255                let data_group_regs = vtype.vlmul().register_count();
256                let index_group_regs = vtype
257                    .vlmul()
258                    .index_register_count(index_eew, vtype.vsew())
259                    .ok_or(ExecutionError::IllegalInstruction {
260                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
261                    })?;
262                zvexx_load_helpers::check_register_group_alignment::<Reg, _, _, _>(
263                    program_counter,
264                    vs3,
265                    data_group_regs,
266                )?;
267                zvexx_load_helpers::check_register_group_alignment::<Reg, _, _, _>(
268                    program_counter,
269                    vs2,
270                    index_group_regs,
271                )?;
272                // SAFETY:
273                // - `vs3` alignment/bounds: `check_register_group_alignment` verified both
274                // - `vs2` alignment/bounds: `check_register_group_alignment` verified both
275                // - `vl <= data_group_regs * VLEN.bytes() / data_eew.bytes()`: `data_group_regs` is
276                //   the EMUL that bounds `vl`
277                // - `vl <= index_group_regs * VLEN.bytes() / index_eew.bytes()`:
278                //   `index_register_count` returns the EMUL for the index group, which by the same
279                //   argument bounds `vl`
280                // - vs3/v0 overlap: stores read vs3 as a source; no restriction
281                unsafe {
282                    zvexx_store_helpers::execute_indexed_store(
283                        ext_state,
284                        memory,
285                        vs3,
286                        vs2,
287                        vm,
288                        rs1_value.as_u64(),
289                        data_eew,
290                        index_eew,
291                        data_group_regs,
292                        Nf::N1,
293                    )?;
294                }
295            }
296            // Indexed-ordered store. Elements must be written in element order.
297            // The ordering constraint is visible only to other harts/devices; the implementation
298            // here is already sequential, so no additional logic is needed.
299            Self::Vsoxei {
300                vs3,
301                rs1: _,
302                vs2,
303                vm,
304                eew: index_eew,
305            } => {
306                if !ext_state.vector_instructions_allowed() {
307                    ::core::hint::cold_path();
308                    return Err(ExecutionError::IllegalInstruction {
309                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
310                    });
311                }
312                let Some(vtype) = ext_state.vtype() else {
313                    ::core::hint::cold_path();
314                    return Err(ExecutionError::IllegalInstruction {
315                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
316                    });
317                };
318                let data_eew = vtype.vsew().as_eew();
319                let data_group_regs = vtype.vlmul().register_count();
320                let index_group_regs = vtype
321                    .vlmul()
322                    .index_register_count(index_eew, vtype.vsew())
323                    .ok_or(ExecutionError::IllegalInstruction {
324                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
325                    })?;
326                zvexx_load_helpers::check_register_group_alignment::<Reg, _, _, _>(
327                    program_counter,
328                    vs3,
329                    data_group_regs,
330                )?;
331                zvexx_load_helpers::check_register_group_alignment::<Reg, _, _, _>(
332                    program_counter,
333                    vs2,
334                    index_group_regs,
335                )?;
336                // SAFETY: identical precondition argument to `Vsuxei`
337                unsafe {
338                    zvexx_store_helpers::execute_indexed_store(
339                        ext_state,
340                        memory,
341                        vs3,
342                        vs2,
343                        vm,
344                        rs1_value.as_u64(),
345                        data_eew,
346                        index_eew,
347                        data_group_regs,
348                        Nf::N1,
349                    )?;
350                }
351            }
352            // Unit-stride segment store: `nf` fields per element, stored contiguously
353            Self::Vsseg {
354                vs3,
355                rs1: _,
356                eew,
357                vm_nf,
358            } => {
359                let vm = vm_nf.vm();
360                let nf = vm_nf.nf();
361                if !ext_state.vector_instructions_allowed() {
362                    ::core::hint::cold_path();
363                    return Err(ExecutionError::IllegalInstruction {
364                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
365                    });
366                }
367                let Some(vtype) = ext_state.vtype() else {
368                    ::core::hint::cold_path();
369                    return Err(ExecutionError::IllegalInstruction {
370                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
371                    });
372                };
373                let group_regs = vtype.vlmul().data_register_count(eew, vtype.vsew()).ok_or(
374                    ExecutionError::IllegalInstruction {
375                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
376                    },
377                )?;
378                zvexx_store_helpers::validate_segment_store_registers::<Reg, _, _, _>(
379                    program_counter,
380                    vs3,
381                    group_regs,
382                    nf,
383                )?;
384                // SAFETY:
385                // - `validate_segment_store_registers` guarantees `vs3 % group_regs == 0` and `vs3
386                //   + nf * group_regs <= 32`
387                // - `vl <= group_regs * VLEN.bytes() / eew.bytes()`: same EMUL argument as `Vse`
388                // - vs3/v0 overlap: stores read vs3 as a source; no restriction
389                unsafe {
390                    zvexx_store_helpers::execute_unit_stride_store(
391                        ext_state,
392                        memory,
393                        vs3,
394                        vm,
395                        rs1_value.as_u64(),
396                        eew,
397                        group_regs,
398                        nf,
399                    )?;
400                }
401            }
402            // Strided segment store
403            Self::Vssseg {
404                vs3,
405                rs1: _,
406                rs2: _,
407                eew,
408                vm_nf,
409            } => {
410                let vm = vm_nf.vm();
411                let nf = vm_nf.nf();
412                if !ext_state.vector_instructions_allowed() {
413                    ::core::hint::cold_path();
414                    return Err(ExecutionError::IllegalInstruction {
415                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
416                    });
417                }
418                let Some(vtype) = ext_state.vtype() else {
419                    ::core::hint::cold_path();
420                    return Err(ExecutionError::IllegalInstruction {
421                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
422                    });
423                };
424                let group_regs = vtype.vlmul().data_register_count(eew, vtype.vsew()).ok_or(
425                    ExecutionError::IllegalInstruction {
426                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
427                    },
428                )?;
429                zvexx_store_helpers::validate_segment_store_registers::<Reg, _, _, _>(
430                    program_counter,
431                    vs3,
432                    group_regs,
433                    nf,
434                )?;
435                let stride = rs2_value.as_i64();
436                // SAFETY: same as `Vsseg`.
437                unsafe {
438                    zvexx_store_helpers::execute_strided_store(
439                        ext_state,
440                        memory,
441                        vs3,
442                        vm,
443                        rs1_value.as_u64(),
444                        stride,
445                        eew,
446                        group_regs,
447                        nf,
448                    )?;
449                }
450            }
451            // Indexed-unordered segment store
452            Self::Vsuxseg {
453                vs3,
454                rs1: _,
455                vs2,
456                eew: index_eew,
457                vm_nf,
458            } => {
459                let vm = vm_nf.vm();
460                let nf = vm_nf.nf();
461                if !ext_state.vector_instructions_allowed() {
462                    ::core::hint::cold_path();
463                    return Err(ExecutionError::IllegalInstruction {
464                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
465                    });
466                }
467                let Some(vtype) = ext_state.vtype() else {
468                    ::core::hint::cold_path();
469                    return Err(ExecutionError::IllegalInstruction {
470                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
471                    });
472                };
473                let data_eew = vtype.vsew().as_eew();
474                let data_group_regs = vtype.vlmul().register_count();
475                let index_group_regs = vtype
476                    .vlmul()
477                    .index_register_count(index_eew, vtype.vsew())
478                    .ok_or(ExecutionError::IllegalInstruction {
479                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
480                    })?;
481                zvexx_store_helpers::validate_segment_store_registers::<Reg, _, _, _>(
482                    program_counter,
483                    vs3,
484                    data_group_regs,
485                    nf,
486                )?;
487                zvexx_load_helpers::check_register_group_alignment::<Reg, _, _, _>(
488                    program_counter,
489                    vs2,
490                    index_group_regs,
491                )?;
492                // SAFETY:
493                // - `validate_segment_store_registers` covers `vs3` alignment/bounds
494                // - `check_register_group_alignment` covers `vs2` alignment/bounds
495                // - `vl` bounded by both EMUL groups as in `Vsuxei`
496                // - vs3/v0 overlap: stores read vs3 as a source; no restriction
497                unsafe {
498                    zvexx_store_helpers::execute_indexed_store(
499                        ext_state,
500                        memory,
501                        vs3,
502                        vs2,
503                        vm,
504                        rs1_value.as_u64(),
505                        data_eew,
506                        index_eew,
507                        data_group_regs,
508                        nf,
509                    )?;
510                }
511            }
512            // Indexed-ordered segment store. Sequential iteration satisfies the ordering
513            // requirement.
514            Self::Vsoxseg {
515                vs3,
516                rs1: _,
517                vs2,
518                eew: index_eew,
519                vm_nf,
520            } => {
521                let vm = vm_nf.vm();
522                let nf = vm_nf.nf();
523                if !ext_state.vector_instructions_allowed() {
524                    ::core::hint::cold_path();
525                    return Err(ExecutionError::IllegalInstruction {
526                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
527                    });
528                }
529                let Some(vtype) = ext_state.vtype() else {
530                    ::core::hint::cold_path();
531                    return Err(ExecutionError::IllegalInstruction {
532                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
533                    });
534                };
535                let data_eew = vtype.vsew().as_eew();
536                let data_group_regs = vtype.vlmul().register_count();
537                let index_group_regs = vtype
538                    .vlmul()
539                    .index_register_count(index_eew, vtype.vsew())
540                    .ok_or(ExecutionError::IllegalInstruction {
541                        address: program_counter.old_pc(zvexx_helpers::INSTRUCTION_SIZE),
542                    })?;
543                zvexx_store_helpers::validate_segment_store_registers::<Reg, _, _, _>(
544                    program_counter,
545                    vs3,
546                    data_group_regs,
547                    nf,
548                )?;
549                zvexx_load_helpers::check_register_group_alignment::<Reg, _, _, _>(
550                    program_counter,
551                    vs2,
552                    index_group_regs,
553                )?;
554                // SAFETY: identical precondition argument to `Vsuxseg`
555                unsafe {
556                    zvexx_store_helpers::execute_indexed_store(
557                        ext_state,
558                        memory,
559                        vs3,
560                        vs2,
561                        vm,
562                        rs1_value.as_u64(),
563                        data_eew,
564                        index_eew,
565                        data_group_regs,
566                        nf,
567                    )?;
568                }
569            }
570        }
571
572        Ok(ControlFlow::Continue(Default::default()))
573    }
574}