Skip to main content

ab_riscv_interpreter/v/zvexx/
zvexx_helpers.rs

1//! Opaque helpers for ZveXx extension
2
3#[cfg(test)]
4mod tests;
5
6use crate::v::vector_config::VectorConfig;
7use crate::v::vector_registers::{VRegGroup, VRegSegmentGroup, VectorRegistersExt};
8use crate::{ExecutionError, PackedAddress, ProgramCounter};
9use ab_riscv_primitives::prelude::*;
10use core::cmp::Ordering;
11use core::hint::cold_path;
12use core::marker::PhantomData;
13
14/// Size of an instruction in bytes.
15///
16/// All instructions here are the same size.
17#[doc(hidden)]
18pub const INSTRUCTION_SIZE: u8 = size_of::<u32>() as u8;
19
20/// Whether a vector instruction other than a load, a store or `vset{i}vl{i}` can execute.
21///
22/// Besides vector instructions being enabled, this requires `vstart` to be zero. These
23/// instructions either complete or trap without modifying any state, so a non-zero `vstart` is a
24/// value this implementation never produces for them, which permits raising an illegal instruction
25/// exception for it (`norm:vstart_vtype_dep`). Reductions, `vcompress.vm` and several mask
26/// instructions require that regardless.
27#[inline(always)]
28#[doc(hidden)]
29#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
30pub fn non_memory_instruction_allowed<Reg, Env>(env: &Env) -> bool
31where
32    Reg: Register,
33    Env: VectorRegistersExt<Hart: HartConfig<Reg = Reg>>,
34{
35    env.vector_instructions_allowed() && env.vstart() == Vstart::ZERO
36}
37
38/// Error of [`vreg_group()`] and [`vreg_segment_group()`], an illegal instruction at `address`.
39///
40/// Unlike a whole [`ExecutionError`], this is fully initialized. A group and an `ExecutionError`
41/// sharing a `Result` makes the compiler carry bytes of groups from earlier instructions around the
42/// interpreter loop into the partially initialized error, which made register allocation of the
43/// loop explode.
44#[derive(Debug, Clone, Copy)]
45struct InvalidRegisterGroup<Address>
46where
47    Address: Copy,
48{
49    address: PackedAddress<Address>,
50}
51
52const impl<Address> From<InvalidRegisterGroup<Address>> for ExecutionError<Address>
53where
54    Address: Copy,
55{
56    #[inline(always)]
57    fn from(error: InvalidRegisterGroup<Address>) -> Self {
58        Self::IllegalInstruction {
59            address: error.address,
60        }
61    }
62}
63
64/// Register group starting at `base` with elements of width `eew` under `config`.
65///
66/// Raises an illegal instruction exception if [`VRegGroup::new()`] rejects it.
67#[inline(always)]
68#[doc(hidden)]
69#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
70pub fn vreg_group<Reg, Env, Memory, PC>(
71    program_counter: &PC,
72    config: VectorConfig<Env::Hart>,
73    base: VReg,
74    eew: Eew,
75) -> Result<VRegGroup<Env::Hart>, impl Into<ExecutionError<Reg::Type>>>
76where
77    Reg: Register,
78    Env: VectorRegistersExt<Hart: HartConfig<Reg = Reg>>,
79    PC: ProgramCounter<Reg::Type, Memory>,
80{
81    let Some(group) = VRegGroup::new(config, base, eew) else {
82        cold_path();
83        return Err(InvalidRegisterGroup {
84            address: PackedAddress::new(program_counter.old_pc(INSTRUCTION_SIZE)),
85        });
86    };
87    Ok(group)
88}
89
90/// Segment group of `nf` fields, the first of which is `first`.
91///
92/// Raises an illegal instruction exception if [`VRegSegmentGroup::new()`] rejects it.
93#[inline(always)]
94#[doc(hidden)]
95#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
96pub fn vreg_segment_group<Reg, Env, Memory, PC>(
97    program_counter: &PC,
98    first: VRegGroup<Env::Hart>,
99    nf: Nf,
100) -> Result<VRegSegmentGroup<Env::Hart>, impl Into<ExecutionError<Reg::Type>>>
101where
102    Reg: Register,
103    Env: VectorRegistersExt<Hart: HartConfig<Reg = Reg>>,
104    PC: ProgramCounter<Reg::Type, Memory>,
105{
106    let Some(group) = VRegSegmentGroup::new(first, nf) else {
107        cold_path();
108        return Err(InvalidRegisterGroup {
109            address: PackedAddress::new(program_counter.old_pc(INSTRUCTION_SIZE)),
110        });
111    };
112    Ok(group)
113}
114
115/// Check that destination `vd` overlaps source `vs` only in a way the spec permits for groups with
116/// different EEWs (spec §5.2):
117///
118/// - the EEWs are equal, then any overlap is fine;
119/// - the destination EEW is smaller and the overlap is in the lowest-numbered part of the source
120///   group, i.e. the destination starts at the source's base register (`vnsrl.wv v2, v2, v6`);
121/// - the destination EEW is larger, the source `EMUL` is at least 1 and the overlap is in the
122///   highest-numbered part of the destination group, i.e. both groups end at the same register
123///   (`vwsubu.wv v2, v14, v3` with `LMUL=1`, where the narrow `v3` aliases the high register of the
124///   wide `{v2, v3}` destination).
125///
126/// Any other overlap is illegal, in particular any overlap at all with a fractional source `EMUL`
127/// when the destination EEW is larger, even though such a source still occupies a whole register.
128/// Instructions that forbid overlaps that this permits check that separately.
129#[inline(always)]
130#[doc(hidden)]
131#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
132pub fn check_destination_overlap<Reg, Env, Memory, PC>(
133    program_counter: &PC,
134    vd: VRegGroup<Env::Hart>,
135    vs: VRegGroup<Env::Hart>,
136) -> Result<(), ExecutionError<Reg::Type>>
137where
138    Reg: Register,
139    Env: VectorRegistersExt<Hart: HartConfig<Reg = Reg>>,
140    PC: ProgramCounter<Reg::Type, Memory>,
141{
142    let allowed = !vd.overlaps(vs)
143        || match vd.eew().bytes_width().cmp(&vs.eew().bytes_width()) {
144            Ordering::Equal => true,
145            Ordering::Less => vd.base() == vs.base(),
146            Ordering::Greater => {
147                !vs.emul().is_fractional()
148                    && vd.base().to_bits() + vd.group_regs().get()
149                        == vs.base().to_bits() + vs.group_regs().get()
150            }
151        };
152    if !allowed {
153        cold_path();
154        return Err(ExecutionError::IllegalInstruction {
155            address: PackedAddress::new(program_counter.old_pc(INSTRUCTION_SIZE)),
156        });
157    }
158    Ok(())
159}
160
161/// Check that register groups `a` and `b` don't overlap
162#[inline(always)]
163#[doc(hidden)]
164#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
165pub fn check_groups_disjoint<Reg, Env, Memory, PC>(
166    program_counter: &PC,
167    a: VRegGroup<Env::Hart>,
168    b: VRegGroup<Env::Hart>,
169) -> Result<(), ExecutionError<Reg::Type>>
170where
171    Reg: Register,
172    Env: VectorRegistersExt<Hart: HartConfig<Reg = Reg>>,
173    PC: ProgramCounter<Reg::Type, Memory>,
174{
175    if a.overlaps(b) {
176        cold_path();
177        return Err(ExecutionError::IllegalInstruction {
178            address: PackedAddress::new(program_counter.old_pc(INSTRUCTION_SIZE)),
179        });
180    }
181    Ok(())
182}
183
184/// Check that single register `reg`, like a mask register, is not one of the registers of `group`
185#[inline(always)]
186#[doc(hidden)]
187#[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
188pub fn check_register_outside_group<Reg, Env, Memory, PC>(
189    program_counter: &PC,
190    group: VRegGroup<Env::Hart>,
191    reg: VReg,
192) -> Result<(), ExecutionError<Reg::Type>>
193where
194    Reg: Register,
195    Env: VectorRegistersExt<Hart: HartConfig<Reg = Reg>>,
196    PC: ProgramCounter<Reg::Type, Memory>,
197{
198    if group.contains(reg) {
199        cold_path();
200        return Err(ExecutionError::IllegalInstruction {
201            address: PackedAddress::new(program_counter.old_pc(INSTRUCTION_SIZE)),
202        });
203    }
204    Ok(())
205}
206
207/// Element widths of a widening operation: `SEW` of the narrow operands and `2*SEW` of the wide
208/// ones, where `2*SEW <= ELEN`.
209///
210/// An EEW greater than `ELEN` is reserved by the RISC-V "V" spec §3.4.2 for every implementation,
211/// so no vector extension makes `SEW == ELEN` legal for a widening instruction.
212#[derive(Debug, Clone, Copy)]
213#[doc(hidden)]
214pub struct WideningSew<Hart>
215where
216    Hart: VectorHartConfig,
217{
218    narrow: Vsew,
219    wide: Vsew,
220    hart: PhantomData<Hart>,
221}
222
223impl<Hart> WideningSew<Hart>
224where
225    Hart: VectorHartConfig,
226{
227    /// Returns `None` when `2*SEW` exceeds `ELEN`.
228    ///
229    /// `ELEN` above 64 is rejected at compile time: widening with `SEW = 64` would be legal then,
230    /// but elements are processed as `u64` here, so a 128-bit wide operand is not supported.
231    #[inline(always)]
232    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
233    pub const fn new(sew: Vsew) -> Option<Self> {
234        const {
235            assert!(
236                Hart::VECTOR_LENGTHS.elen <= Elen::L64,
237                "`ELEN` above 64 is not supported by the interpreter"
238            );
239        }
240
241        let Some(wide) = sew.double_width() else {
242            return None;
243        };
244        if !Hart::VECTOR_LENGTHS.elen.supports(wide.as_eew()) {
245            return None;
246        }
247        Some(Self {
248            narrow: sew,
249            wide,
250            hart: PhantomData,
251        })
252    }
253
254    /// Element width of the narrow operands, `SEW`
255    #[inline(always)]
256    pub const fn narrow(self) -> Vsew {
257        self.narrow
258    }
259
260    /// Element width of the wide operands, `2*SEW`
261    #[inline(always)]
262    pub const fn wide(self) -> Vsew {
263        self.wide
264    }
265}
266
267/// Element widths of an integer extension: `SEW/factor` of the source and `SEW` of the
268/// destination
269#[derive(Debug, Clone, Copy)]
270#[doc(hidden)]
271pub struct ExtensionSew {
272    source: Vsew,
273    dest: Vsew,
274}
275
276impl ExtensionSew {
277    /// Returns `None` when `SEW/factor` is narrower than 8 bits
278    #[inline(always)]
279    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
280    pub const fn new(sew: Vsew, factor: VsewFactor) -> Option<Self> {
281        let Some(source) = sew.divide_by_factor(factor) else {
282            return None;
283        };
284        Some(Self { source, dest: sew })
285    }
286
287    /// Element width of the source operand, `SEW/factor`
288    #[inline(always)]
289    pub const fn source(self) -> Vsew {
290        self.source
291    }
292
293    /// Element width of the destination, `SEW`
294    #[inline(always)]
295    pub const fn dest(self) -> Vsew {
296        self.dest
297    }
298}