osom_encoders_x86_64/models/
gpr.rs

1use core::num::NonZero;
2
3use osom_encoders_common::osom_debug_assert;
4
5use super::Size;
6
7/// Represents the kind of a general purpose register.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9#[repr(u8)]
10#[must_use]
11pub enum GPRKind {
12    /// Represents AH, BH, CH and DH registers.
13    Bit8High = 1, // We start from 1 to allow Option<GPRKind> optimization
14
15    /// Represents AL, CL, DL, BL, SPL, BPL, SIL, DIL, R8B, R9B, R10B, R11B, R12B, R13B, R14B and R15B registers.
16    Bit8,
17
18    /// Represents AX, CX, DX, BX, SP, BP, SI, DI, R8W, R9W, R10W, R11W, R12W, R13W, R14W and R15W registers.
19    Bit16,
20
21    /// Represents EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI, R8D, R9D, R10D, R11D, R12D, R13D, R14D and R15D registers.
22    Bit32,
23
24    /// Represents RAX, RCX, RDX, RBX, RSP, RBP, RSI, RDI, R8, R9, R10, R11, R12, R13, R14 and R15 registers.
25    Bit64,
26}
27
28impl GPRKind {
29    #[inline(always)]
30    #[must_use]
31    pub const fn as_u8(self) -> u8 {
32        let result = self as u8;
33        unsafe { core::hint::assert_unchecked(result > 0 && result < 8) };
34        result
35    }
36
37    /// Creates a new `GPRKind` from a `u8` index.
38    ///
39    /// # Safety
40    ///
41    /// The index must be in the range `1..=5`, otherwise the behavior is undefined.
42    #[inline(always)]
43    pub const unsafe fn from_u8_unchecked(value: u8) -> Self {
44        osom_debug_assert!(value > 0 && value < 8);
45        unsafe { core::mem::transmute(value) }
46    }
47
48    /// Creates a new `GPRKind` from a `u8` index.
49    ///
50    /// # Returns
51    ///
52    /// - `Some(GPRKind)` if the `u8` is in the range `1..=5`
53    /// - `None` if the `u8` is 0 or greater than 5
54    #[must_use]
55    pub const fn from_u8(value: u8) -> Option<Self> {
56        if value == 0 || value >= 8 {
57            return None;
58        }
59
60        Some(unsafe { Self::from_u8_unchecked(value) })
61    }
62
63    /// The same as `==` operator but const.
64    #[inline(always)]
65    #[must_use]
66    pub const fn equals(self, other: Self) -> bool {
67        self.as_u8() == other.as_u8()
68    }
69
70    #[inline(always)]
71    pub const fn size(self) -> Size {
72        match self {
73            GPRKind::Bit8 | GPRKind::Bit8High => Size::Bit8,
74            GPRKind::Bit16 => Size::Bit16,
75            GPRKind::Bit32 => Size::Bit32,
76            GPRKind::Bit64 => Size::Bit64,
77        }
78    }
79}
80
81impl core::hash::Hash for GPRKind {
82    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
83        self.as_u8().hash(state);
84    }
85}
86
87/// Represents general purpose registers.
88///
89/// Internally stored as a non-zero `u8` value.
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
91#[repr(transparent)]
92#[must_use]
93pub struct GPR {
94    value: NonZero<u8>,
95}
96
97const GPR_KIND_SHIFT: u8 = 5;
98
99/// Error when creating a new `GPR`.
100#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
101#[repr(u8)]
102pub enum NewGPRError {
103    /// Error when creating a new `GPR` from a [`GPRKind::Bit8High`] and `index` outside of the `4..=7` range.
104    InvalidBit8HighIndex,
105
106    /// Error when creating a new `GPR` from a `kind` and `index` outside of the `0..=31` range.
107    IndexOutOfRange,
108}
109
110macro_rules! gpr {
111    ($name: ident, $kind: ident, $index: literal) => {
112        pub const $name: Self = unsafe { Self::new_unchecked(GPRKind::$kind, $index) };
113    };
114}
115
116impl GPR {
117    gpr!(RAX, Bit64, 0);
118    gpr!(RCX, Bit64, 1);
119    gpr!(RDX, Bit64, 2);
120    gpr!(RBX, Bit64, 3);
121    gpr!(RSP, Bit64, 4);
122    gpr!(RBP, Bit64, 5);
123    gpr!(RSI, Bit64, 6);
124    gpr!(RDI, Bit64, 7);
125    gpr!(R8, Bit64, 8);
126    gpr!(R9, Bit64, 9);
127    gpr!(R10, Bit64, 10);
128    gpr!(R11, Bit64, 11);
129    gpr!(R12, Bit64, 12);
130    gpr!(R13, Bit64, 13);
131    gpr!(R14, Bit64, 14);
132    gpr!(R15, Bit64, 15);
133
134    gpr!(EAX, Bit32, 0);
135    gpr!(ECX, Bit32, 1);
136    gpr!(EDX, Bit32, 2);
137    gpr!(EBX, Bit32, 3);
138    gpr!(ESP, Bit32, 4);
139    gpr!(EBP, Bit32, 5);
140    gpr!(ESI, Bit32, 6);
141    gpr!(EDI, Bit32, 7);
142    gpr!(R8D, Bit32, 8);
143    gpr!(R9D, Bit32, 9);
144    gpr!(R10D, Bit32, 10);
145    gpr!(R11D, Bit32, 11);
146    gpr!(R12D, Bit32, 12);
147    gpr!(R13D, Bit32, 13);
148    gpr!(R14D, Bit32, 14);
149    gpr!(R15D, Bit32, 15);
150
151    gpr!(AX, Bit16, 0);
152    gpr!(CX, Bit16, 1);
153    gpr!(DX, Bit16, 2);
154    gpr!(BX, Bit16, 3);
155    gpr!(SP, Bit16, 4);
156    gpr!(BP, Bit16, 5);
157    gpr!(SI, Bit16, 6);
158    gpr!(DI, Bit16, 7);
159    gpr!(R8W, Bit16, 8);
160    gpr!(R9W, Bit16, 9);
161    gpr!(R10W, Bit16, 10);
162    gpr!(R11W, Bit16, 11);
163    gpr!(R12W, Bit16, 12);
164    gpr!(R13W, Bit16, 13);
165    gpr!(R14W, Bit16, 14);
166    gpr!(R15W, Bit16, 15);
167
168    gpr!(AL, Bit8, 0);
169    gpr!(CL, Bit8, 1);
170    gpr!(DL, Bit8, 2);
171    gpr!(BL, Bit8, 3);
172    gpr!(SPL, Bit8, 4);
173    gpr!(BPL, Bit8, 5);
174    gpr!(SIL, Bit8, 6);
175    gpr!(DIL, Bit8, 7);
176    gpr!(R8B, Bit8, 8);
177    gpr!(R9B, Bit8, 9);
178    gpr!(R10B, Bit8, 10);
179    gpr!(R11B, Bit8, 11);
180    gpr!(R12B, Bit8, 12);
181    gpr!(R13B, Bit8, 13);
182    gpr!(R14B, Bit8, 14);
183    gpr!(R15B, Bit8, 15);
184
185    gpr!(AH, Bit8High, 4);
186    gpr!(CH, Bit8High, 5);
187    gpr!(DH, Bit8High, 6);
188    gpr!(BH, Bit8High, 7);
189
190    /// Creates a new `GPR` from a `GPRKind` and an `index`.
191    ///
192    /// # Safety
193    ///
194    /// If `kind` is `Bit8High`, then `index` must be in the range `4..=7`.
195    /// These represent AH, CH, DH and BH registers. All other combinations
196    /// are valid.
197    #[inline(always)]
198    pub const unsafe fn new_unchecked(kind: GPRKind, index: u8) -> Self {
199        osom_debug_assert!(!kind.equals(GPRKind::Bit8High) || (index >= 4 && index <= 7));
200        osom_debug_assert!(index < 32);
201
202        let kind_u8 = kind.as_u8();
203
204        let value = (kind_u8 << GPR_KIND_SHIFT) | index;
205
206        unsafe {
207            Self {
208                value: NonZero::new_unchecked(value),
209            }
210        }
211    }
212
213    /// Creates a new `GPR` from a `GPRKind` and an `index`.
214    ///
215    /// # Returns
216    ///
217    /// - `Ok(GPR)` if the `GPRKind` and `index` are valid
218    /// - `Err(NewGPRError)` if the `kind` is [`GPRKind::Bit8High`] and
219    ///   `index` is outside the `4..=7` range
220    #[inline(always)]
221    pub const fn new(kind: GPRKind, index: u8) -> Result<Self, NewGPRError> {
222        if kind.equals(GPRKind::Bit8High) && (index < 4 || index > 7) {
223            return Err(NewGPRError::InvalidBit8HighIndex);
224        }
225
226        if index >= 32 {
227            return Err(NewGPRError::IndexOutOfRange);
228        }
229
230        Ok(unsafe { Self::new_unchecked(kind, index) })
231    }
232
233    #[inline(always)]
234    pub const fn kind(self) -> GPRKind {
235        let kind_u8 = self.value.get() >> GPR_KIND_SHIFT;
236        unsafe { GPRKind::from_u8_unchecked(kind_u8) }
237    }
238
239    #[inline(always)]
240    pub const fn size(self) -> Size {
241        self.kind().size()
242    }
243
244    #[inline(always)]
245    #[must_use]
246    pub(crate) const fn index(self) -> u8 {
247        self.value.get() & 0b11111
248    }
249
250    /// Returns true if the GPR is an extended X64 register.
251    #[inline(always)]
252    #[must_use]
253    pub(crate) const fn is_extended(self) -> bool {
254        self.index() > 0b111
255    }
256
257    #[allow(dead_code)] // TODO: will be used once we support APX.
258    /// Returns true if the GPR is an extended register
259    /// from Advanced Performance Extensions. Implies [`Self::is_extended`].
260    #[inline(always)]
261    #[must_use]
262    pub(crate) const fn is_apx(self) -> bool {
263        self.index() > 0b1111
264    }
265
266    #[inline(always)]
267    #[must_use]
268    pub(crate) const fn lower_3_bits_index(self) -> u8 {
269        self.index() & 0b111
270    }
271
272    /// This function verifies that the index of given GPR matches
273    /// the index of AH, CH, DH or BH registers. Meaning it is in
274    /// the `4..=7` range. This is important, since those registers
275    /// share the index with SPL, BPL, SIL and DIL registers (which
276    /// are of the same size). And have to be encoded differently,
277    /// typically by using Operand Size Override prefix.
278    #[inline(always)]
279    #[must_use]
280    pub(crate) const fn index_matches_bit8_high(self) -> bool {
281        let idx = self.index();
282        idx >= 4 && idx <= 7
283    }
284
285    #[inline(always)]
286    #[must_use]
287    pub const fn equals(&self, other: &Self) -> bool {
288        self.value.get() == other.value.get()
289    }
290}
291
292#[cfg(test)]
293mod tests {
294    use rstest::rstest;
295
296    use super::*;
297
298    #[rstest]
299    #[case(GPR::RAX, Size::Bit64)]
300    #[case(GPR::EAX, Size::Bit32)]
301    #[case(GPR::AX, Size::Bit16)]
302    #[case(GPR::AL, Size::Bit8)]
303    #[case(GPR::AH, Size::Bit8)]
304    #[case(GPR::R8, Size::Bit64)]
305    #[case(GPR::R8D, Size::Bit32)]
306    #[case(GPR::R8W, Size::Bit16)]
307    #[case(GPR::R8B, Size::Bit8)]
308    fn test_gpr_size(#[case] gpr: GPR, #[case] size: Size) {
309        assert_eq!(gpr.size(), size);
310    }
311}