Skip to main content

ab_riscv_primitives/instructions/
utils.rs

1//! Utility types
2
3#[cfg(test)]
4mod tests;
5
6use crate::registers::general_purpose::RegType;
7use core::fmt;
8use core::hint::assert_unchecked;
9use core::marker::PhantomData;
10use core::ops::{Shl, Shr};
11
12/// New type for unsigned integers that stores 24-bit numbers
13#[derive(Clone, Copy, Hash)]
14#[derive_const(PartialEq, Eq)]
15pub struct U24([u8; 3]);
16
17const impl Default for U24 {
18    #[inline(always)]
19    fn default() -> Self {
20        Self([0; _])
21    }
22}
23
24impl fmt::Debug for U24 {
25    #[inline(always)]
26    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27        fmt::Debug::fmt(&self.to_u32(), f)
28    }
29}
30
31impl fmt::Display for U24 {
32    #[inline(always)]
33    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34        fmt::Display::fmt(&self.to_u32(), f)
35    }
36}
37
38impl fmt::LowerHex for U24 {
39    #[inline(always)]
40    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41        fmt::LowerHex::fmt(&self.to_u32(), f)
42    }
43}
44
45impl fmt::UpperHex for U24 {
46    #[inline(always)]
47    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48        fmt::UpperHex::fmt(&self.to_u32(), f)
49    }
50}
51
52const impl Shl<u8> for U24 {
53    type Output = Self;
54
55    #[inline(always)]
56    fn shl(self, rhs: u8) -> Self::Output {
57        Self::from_u32(self.to_u32().shl(rhs))
58    }
59}
60
61const impl Shr<u8> for U24 {
62    type Output = Self;
63
64    #[inline(always)]
65    fn shr(self, rhs: u8) -> Self::Output {
66        Self::from_u32(self.to_u32().shr(rhs))
67    }
68}
69
70const impl Shl<u8> for &U24 {
71    type Output = U24;
72
73    #[inline(always)]
74    fn shl(self, rhs: u8) -> Self::Output {
75        U24::from_u32(self.to_u32().shl(rhs))
76    }
77}
78
79const impl Shr<u8> for &U24 {
80    type Output = U24;
81
82    #[inline(always)]
83    fn shr(self, rhs: u8) -> Self::Output {
84        U24::from_u32(self.to_u32().shr(rhs))
85    }
86}
87
88const impl From<U24> for u32 {
89    #[inline(always)]
90    fn from(v: U24) -> Self {
91        v.to_u32()
92    }
93}
94
95const impl From<U24> for u64 {
96    #[inline(always)]
97    fn from(v: U24) -> Self {
98        u64::from(v.to_u32())
99    }
100}
101
102impl U24 {
103    /// Create a new `U24` from an unsigned 32-bit integer.
104    ///
105    /// The input value is truncated to 24 bits, providing larger value panics in a debug build.
106    #[inline(always)]
107    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
108    pub const fn from_u32(v: u32) -> Self {
109        let b = v.to_le_bytes();
110        debug_assert!(
111            (v << u8::BITS) >> u8::BITS == v,
112            "Input value exceeds 24 bits"
113        );
114        Self([b[0], b[1], b[2]])
115    }
116
117    /// Convert to an unsigned 32-bit integer
118    #[inline(always)]
119    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
120    pub const fn to_u32(self) -> u32 {
121        let [a, b, c] = self.0;
122        u32::from_le_bytes([a, b, c, 0])
123    }
124}
125
126/// New type for signed integers that stores 24-bit numbers
127#[derive(Clone, Copy, Hash)]
128#[derive_const(PartialEq, Eq)]
129pub struct I24([u8; 3]);
130
131const impl Default for I24 {
132    #[inline(always)]
133    fn default() -> Self {
134        Self([0; _])
135    }
136}
137
138impl fmt::Debug for I24 {
139    #[inline(always)]
140    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141        fmt::Debug::fmt(&self.to_i32(), f)
142    }
143}
144
145impl fmt::Display for I24 {
146    #[inline(always)]
147    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148        fmt::Display::fmt(&self.to_i32(), f)
149    }
150}
151
152impl fmt::LowerHex for I24 {
153    #[inline(always)]
154    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155        fmt::LowerHex::fmt(&self.to_i32(), f)
156    }
157}
158
159impl fmt::UpperHex for I24 {
160    #[inline(always)]
161    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162        fmt::UpperHex::fmt(&self.to_i32(), f)
163    }
164}
165
166const impl Shl<u8> for I24 {
167    type Output = Self;
168
169    #[inline(always)]
170    fn shl(self, rhs: u8) -> Self::Output {
171        Self::from_i32(self.to_i32().shl(rhs))
172    }
173}
174
175const impl Shr<u8> for I24 {
176    type Output = Self;
177
178    #[inline(always)]
179    fn shr(self, rhs: u8) -> Self::Output {
180        Self::from_i32(self.to_i32().shr(rhs))
181    }
182}
183
184const impl Shl<u8> for &I24 {
185    type Output = I24;
186
187    #[inline(always)]
188    fn shl(self, rhs: u8) -> Self::Output {
189        I24::from_i32(self.to_i32().shl(rhs))
190    }
191}
192
193const impl Shr<u8> for &I24 {
194    type Output = I24;
195
196    #[inline(always)]
197    fn shr(self, rhs: u8) -> Self::Output {
198        I24::from_i32(self.to_i32().shr(rhs))
199    }
200}
201
202const impl From<I24> for i32 {
203    #[inline(always)]
204    fn from(v: I24) -> Self {
205        v.to_i32()
206    }
207}
208
209const impl From<I24> for i64 {
210    #[inline(always)]
211    fn from(v: I24) -> Self {
212        i64::from(v.to_i32())
213    }
214}
215
216impl I24 {
217    /// Create a new `I24` from a signed 32-bit integer.
218    ///
219    /// The input value is truncated to 24 bits, providing larger value panics in a debug build.
220    #[inline(always)]
221    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
222    pub const fn from_i32(v: i32) -> Self {
223        let b = v.to_le_bytes();
224        debug_assert!(
225            (v << u8::BITS) >> u8::BITS == v,
226            "Input value exceeds 24 bits"
227        );
228        Self([b[0], b[1], b[2]])
229    }
230
231    /// Convert to a signed 32-bit integer
232    #[inline(always)]
233    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
234    pub const fn to_i32(self) -> i32 {
235        let [a, b, c] = self.0;
236        // Sign-extend
237        i32::from_le_bytes([a, b, c, 0]) << u8::BITS >> u8::BITS
238    }
239}
240
241/// Ensures supported number of low zeroed bits of [`I24WithZeroedBits`]
242const SUPPORTED_LOW_ZEROED_BITS<const LOW_ZEROED_BITS: u8>: usize = {
243    assert!(
244        LOW_ZEROED_BITS >= 8 && LOW_ZEROED_BITS < 32,
245        "Low zeroed bits must be in `8..32` range, so that the remaining bits of a 32-bit number fit \
246        into 24 bits and shifts don't overflow"
247    );
248    0
249};
250
251/// New type for signed integers that stores 32-bit numbers with `LOW_ZEROED_BITS` low bits zeroed
252/// and truncated to 24-bits.
253///
254/// `LOW_ZEROED_BITS` must be in `8..32` range.
255#[derive(Clone, Copy, Hash)]
256#[derive_const(PartialEq, Eq)]
257pub struct I24WithZeroedBits<const LOW_ZEROED_BITS: u8>([u8; 3]);
258
259const impl<const LOW_ZEROED_BITS: u8> Default for I24WithZeroedBits<LOW_ZEROED_BITS>
260where
261    [(); SUPPORTED_LOW_ZEROED_BITS::<LOW_ZEROED_BITS>]:,
262{
263    #[inline(always)]
264    fn default() -> Self {
265        Self([0; _])
266    }
267}
268
269impl<const LOW_ZEROED_BITS: u8> fmt::Debug for I24WithZeroedBits<LOW_ZEROED_BITS>
270where
271    [(); SUPPORTED_LOW_ZEROED_BITS::<LOW_ZEROED_BITS>]:,
272{
273    #[inline(always)]
274    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
275        fmt::Debug::fmt(&self.to_i32(), f)
276    }
277}
278
279impl<const LOW_ZEROED_BITS: u8> fmt::Display for I24WithZeroedBits<LOW_ZEROED_BITS>
280where
281    [(); SUPPORTED_LOW_ZEROED_BITS::<LOW_ZEROED_BITS>]:,
282{
283    #[inline(always)]
284    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
285        fmt::Display::fmt(&self.to_i32(), f)
286    }
287}
288
289impl<const LOW_ZEROED_BITS: u8> fmt::LowerHex for I24WithZeroedBits<LOW_ZEROED_BITS>
290where
291    [(); SUPPORTED_LOW_ZEROED_BITS::<LOW_ZEROED_BITS>]:,
292{
293    #[inline(always)]
294    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
295        fmt::LowerHex::fmt(&self.to_i32(), f)
296    }
297}
298
299impl<const LOW_ZEROED_BITS: u8> fmt::UpperHex for I24WithZeroedBits<LOW_ZEROED_BITS>
300where
301    [(); SUPPORTED_LOW_ZEROED_BITS::<LOW_ZEROED_BITS>]:,
302{
303    #[inline(always)]
304    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
305        fmt::UpperHex::fmt(&self.to_i32(), f)
306    }
307}
308
309const impl<const LOW_ZEROED_BITS: u8> From<I24WithZeroedBits<LOW_ZEROED_BITS>> for i32
310where
311    [(); SUPPORTED_LOW_ZEROED_BITS::<LOW_ZEROED_BITS>]:,
312{
313    #[inline(always)]
314    fn from(v: I24WithZeroedBits<LOW_ZEROED_BITS>) -> Self {
315        v.to_i32()
316    }
317}
318
319const impl<const LOW_ZEROED_BITS: u8> From<I24WithZeroedBits<LOW_ZEROED_BITS>> for i64
320where
321    [(); SUPPORTED_LOW_ZEROED_BITS::<LOW_ZEROED_BITS>]:,
322{
323    #[inline(always)]
324    fn from(v: I24WithZeroedBits<LOW_ZEROED_BITS>) -> Self {
325        i64::from(v.to_i32())
326    }
327}
328
329impl<const LOW_ZEROED_BITS: u8> I24WithZeroedBits<LOW_ZEROED_BITS>
330where
331    [(); SUPPORTED_LOW_ZEROED_BITS::<LOW_ZEROED_BITS>]:,
332{
333    /// Create a new `I24WithZeroedBits` from a signed 32-bit integer.
334    ///
335    /// The input value is shifted right arithmetically by `LOW_ZEROED_BITS` before being stored.
336    /// When converted back with [`Self::to_i32`], the value is shifted back with low bits being
337    /// zero.
338    #[inline(always)]
339    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
340    pub const fn from_i32(v_original: i32) -> Self {
341        let v = v_original >> LOW_ZEROED_BITS;
342        let b = v.to_le_bytes();
343        let return_value = Self([b[0], b[1], b[2]]);
344
345        debug_assert!(
346            return_value.to_i32() == v_original,
347            "Input has non-zero low bits"
348        );
349
350        return_value
351    }
352
353    /// Convert to a signed 32-bit integer
354    #[inline(always)]
355    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
356    pub const fn to_i32(self) -> i32 {
357        let [a, b, c] = self.0;
358        // Sign-extend and shift back
359        (((i32::from_le_bytes([a, b, c, 0]) << u8::BITS >> u8::BITS) << LOW_ZEROED_BITS)
360            .cast_unsigned()
361            & (u32::MAX << LOW_ZEROED_BITS))
362            .cast_signed()
363    }
364}
365
366/// Shift amount of an instruction, which is always below the width of `Type` in bits.
367///
368/// `Type` is the type of the shifted value: the register type for most instructions, and `u32` for
369/// instructions that operate on 32-bit words on RV64, like `slliw`. Shifts by this amount never
370/// overflow, so execution doesn't need to mask it.
371#[derive(Clone, Copy)]
372pub struct Shamt<Type> {
373    shamt: u8,
374    shifted_type: PhantomData<Type>,
375}
376
377const impl<Type> PartialEq for Shamt<Type> {
378    #[inline(always)]
379    fn eq(&self, other: &Self) -> bool {
380        self.shamt == other.shamt
381    }
382}
383
384const impl<Type> Eq for Shamt<Type> {}
385
386impl<Type> fmt::Debug for Shamt<Type> {
387    #[inline(always)]
388    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
389        fmt::Debug::fmt(&self.shamt, f)
390    }
391}
392
393impl<Type> fmt::Display for Shamt<Type> {
394    #[inline(always)]
395    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
396        fmt::Display::fmt(&self.shamt, f)
397    }
398}
399
400impl<Type> Shamt<Type>
401where
402    Type: RegType,
403{
404    /// Create a new shift amount, returns `None` if it is not below the width of `Type` in bits
405    #[inline(always)]
406    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
407    pub const fn new(shamt: u8) -> Option<Self> {
408        if shamt >= Type::BITS {
409            return None;
410        }
411
412        Some(Self {
413            shamt,
414            shifted_type: PhantomData,
415        })
416    }
417
418    /// Get the shift amount, which is below the width of `Type` in bits
419    #[inline(always)]
420    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
421    pub const fn get(self) -> u32 {
422        // SAFETY: The field is private to this module and only set by the constructor, which
423        // rejects values that are not below the width of `Type` in bits
424        unsafe {
425            assert_unchecked(self.shamt < Type::BITS);
426        }
427        u32::from(self.shamt)
428    }
429}