Skip to main content

ab_riscv_interpreter/v/
vector_config.rs

1//! Vector configuration
2
3#[cfg(test)]
4mod tests;
5
6use ab_riscv_primitives::prelude::*;
7use core::fmt;
8use core::hint::{assert_unchecked, cold_path};
9use core::marker::PhantomData;
10
11/// Vector length of a [`VectorConfig`], which never exceeds `VLEN`.
12///
13/// `vl <= VLMAX = LMUL * VLEN / SEW <= VLEN`, since `LMUL <= 8` and `SEW >= 8`. Constructors
14/// enforce the bound, so unsafe code can rely on it for accessing a single vector register, like
15/// mask registers with one bit per element.
16#[derive(Debug, Clone, Copy)]
17pub struct BoundedVl<Hart>(Vl, PhantomData<Hart>)
18where
19    Hart: VectorHartConfig;
20
21const impl<Hart> PartialEq for BoundedVl<Hart>
22where
23    Hart: VectorHartConfig,
24{
25    #[inline(always)]
26    fn eq(&self, other: &Self) -> bool {
27        self.0 == other.0
28    }
29}
30
31const impl<Hart> Eq for BoundedVl<Hart> where Hart: VectorHartConfig {}
32
33impl<Hart> fmt::Display for BoundedVl<Hart>
34where
35    Hart: VectorHartConfig,
36{
37    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38        self.0.fmt(f)
39    }
40}
41
42const impl<Hart> From<BoundedVl<Hart>> for Vl
43where
44    Hart: VectorHartConfig,
45{
46    #[inline(always)]
47    fn from(value: BoundedVl<Hart>) -> Self {
48        value.get()
49    }
50}
51
52impl<Hart> BoundedVl<Hart>
53where
54    Hart: VectorHartConfig,
55{
56    /// Create a new instance, returns `None` if `vl` exceeds `VLEN`
57    #[inline(always)]
58    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
59    pub const fn new(vl: Vl) -> Option<Self> {
60        if u32::from(vl) > u32::from(Hart::VECTOR_LENGTHS.vlen) {
61            cold_path();
62            return None;
63        }
64        Some(Self(vl, PhantomData))
65    }
66
67    /// Vector length, which is `<= VLEN`
68    #[inline(always)]
69    pub const fn get(self) -> Vl {
70        // SAFETY: Guaranteed by construction in `VectorConfig`
71        unsafe {
72            assert_unchecked(u32::from(self.0) <= u32::from(Hart::VECTOR_LENGTHS.vlen));
73        }
74        self.0
75    }
76
77    /// Element indices `0..vl`.
78    ///
79    /// Unlike a range of `u16`, which can't represent `vl = 65536` as an exclusive bound, this
80    /// lets the compiler see that every index is below `vl` and `VLEN`.
81    #[inline(always)]
82    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
83    pub fn indices(self) -> impl Iterator<Item = u16> {
84        // `vl <= VLEN <= 65536`, so every index below it fits into `u16`
85        (0..u32::from(self.get())).map(u32::truncate)
86    }
87
88    /// Vector length in bytes, `ceil(vl / 8)`, which is `<= VLEN.bytes()`
89    #[inline(always)]
90    pub const fn bytes(self) -> u16 {
91        let bytes = self.get().bytes();
92        // SAFETY: `vl <= VLEN` and `VLEN` is a multiple of 8
93        unsafe {
94            assert_unchecked(u32::from(bytes) <= Hart::VECTOR_LENGTHS.vlen.bytes());
95        }
96        bytes
97    }
98}
99
100/// Vector configuration: `vtype` together with `vl`, where `vl` never exceeds `VLMAX` of `vtype`.
101///
102/// Neither `vtype` nor `vl` can be written by Zicsr instructions, they only ever change together
103/// through `vset{i}vl{i}` and fault-only-first loads (which only shrink `vl`), so this is the
104/// architectural state as a single value. Constructors enforce the invariant, which unsafe code
105/// relies on to access vector register elements without bounds checks, so there must be no way
106/// to construct an instance that violates it.
107///
108/// `vill` is represented as `None` in `Option<VectorConfig>`, `vl` is zero then.
109#[derive(Debug, Clone, Copy)]
110pub struct VectorConfig<Hart>
111where
112    Hart: VectorHartConfig,
113{
114    vtype: Vtype<Hart>,
115    vl: Vl,
116}
117
118const impl<Hart> PartialEq for VectorConfig<Hart>
119where
120    Hart: VectorHartConfig,
121{
122    #[inline(always)]
123    fn eq(&self, other: &Self) -> bool {
124        self.vtype == other.vtype && self.vl == other.vl
125    }
126}
127
128const impl<Hart> Eq for VectorConfig<Hart> where Hart: VectorHartConfig {}
129
130impl<Hart> VectorConfig<Hart>
131where
132    Hart: VectorHartConfig,
133{
134    /// Create a new configuration.
135    ///
136    /// Returns `None` if `vl` exceeds `VLMAX` of `vtype`.
137    #[inline(always)]
138    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
139    pub const fn new(vtype: Vtype<Hart>, vl: Vl) -> Option<Self> {
140        if u32::from(vl) > u32::from(vtype.vlmax()) {
141            cold_path();
142            return None;
143        }
144
145        Some(Self { vtype, vl })
146    }
147
148    /// Create a new configuration the way `vset{i}vl{i}` does for application vector length
149    /// `avl`, which is `min(AVL, VLMAX)`.
150    ///
151    /// This satisfies every constraint the specification places on `vl`, and with no choice left
152    /// to the implementation, `vl` is the same on every implementation with the same `VLEN`.
153    #[inline(always)]
154    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
155    pub const fn from_avl(vtype: Vtype<Hart>, avl: Vl) -> Self {
156        let vlmax = vtype.vlmax();
157        let vl = if u32::from(avl) > u32::from(vlmax) {
158            vlmax
159        } else {
160            avl
161        };
162
163        Self { vtype, vl }
164    }
165
166    /// Decode from raw `vtype` and `vl` register values.
167    ///
168    /// Returns `None` if `vtype` has `vill` set or is invalid otherwise, or if `vl` exceeds `VLMAX`
169    /// of `vtype`. The latter is not a state the architecture can get into, it is treated as
170    /// `vill` rather than corrected, so that whatever produced it does not go unnoticed.
171    #[inline(always)]
172    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
173    pub const fn from_raw<Reg>(vtype: Reg::Type, vl: Reg::Type) -> Option<Self>
174    where
175        Reg: [const] Register,
176    {
177        let vtype = Vtype::from_raw::<Reg>(vtype)?;
178        let Ok(vl) = u32::try_from(vl.as_u64()) else {
179            cold_path();
180            return None;
181        };
182        let vl = Vl::new(vl)?;
183
184        Self::new(vtype, vl)
185    }
186
187    /// `vtype`
188    #[inline(always)]
189    pub const fn vtype(self) -> Vtype<Hart> {
190        self.vtype
191    }
192
193    /// `vl`, which never exceeds [`Self::vlmax()`] and hence `VLEN`
194    #[inline(always)]
195    pub const fn vl(self) -> BoundedVl<Hart> {
196        BoundedVl(self.vl, PhantomData)
197    }
198
199    /// `VLMAX` of `vtype`
200    #[inline(always)]
201    pub const fn vlmax(self) -> Vl {
202        self.vtype.vlmax()
203    }
204
205    /// The same configuration with `vl` reduced to `vl` if it is lower than the current one, which
206    /// is what fault-only-first loads do
207    #[inline(always)]
208    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
209    pub const fn with_vl_at_most(self, vl: Vl) -> Self {
210        let vl = if u32::from(vl) < u32::from(self.vl) {
211            vl
212        } else {
213            self.vl
214        };
215
216        Self {
217            vtype: self.vtype,
218            vl,
219        }
220    }
221}