use core::fmt;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[non_exhaustive]
pub enum Size {
Byte,
Word,
Dword,
Qword,
}
impl Size {
#[inline]
#[must_use]
pub const fn bits(self) -> u32 {
match self {
Size::Byte => 8,
Size::Word => 16,
Size::Dword => 32,
Size::Qword => 64,
}
}
#[inline]
#[must_use]
pub const fn bytes(self) -> u32 {
self.bits() / 8
}
pub(crate) const fn keyword(self) -> &'static str {
match self {
Size::Byte => "byte",
Size::Word => "word",
Size::Dword => "dword",
Size::Qword => "qword",
}
}
pub(crate) const fn code(self) -> u8 {
match self {
Size::Byte => 0,
Size::Word => 1,
Size::Dword => 2,
Size::Qword => 3,
}
}
pub(crate) const fn from_code(code: u8) -> Size {
match code & 3 {
0 => Size::Byte,
1 => Size::Word,
2 => Size::Dword,
_ => Size::Qword,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Reg(u8);
const HIGH: u8 = 0x10;
impl Reg {
const fn pack(size: Size, num: u8, high: bool) -> Reg {
Reg((size.code() << 5) | (num & 15) | if high { HIGH } else { 0 })
}
#[inline]
#[must_use]
pub const fn new(size: Size, num: u8) -> Option<Reg> {
if num < 16 {
Some(Reg::pack(size, num, false))
} else {
None
}
}
#[inline]
#[must_use]
pub const fn size(self) -> Size {
Size::from_code(self.0 >> 5)
}
#[inline]
#[must_use]
pub const fn num(self) -> u8 {
self.0 & 15
}
#[inline]
#[must_use]
pub const fn is_high_byte(self) -> bool {
self.0 & HIGH != 0
}
#[must_use]
pub const fn name(self) -> &'static str {
let num = self.num() as usize;
if self.is_high_byte() {
return HIGH_NAMES[num & 3];
}
match self.size() {
Size::Byte => NAMES8[num],
Size::Word => NAMES16[num],
Size::Dword => NAMES32[num],
Size::Qword => NAMES64[num],
}
}
pub(crate) const fn needs_rex(self) -> bool {
let num = self.num();
num >= 8 || (matches!(self.size(), Size::Byte) && !self.is_high_byte() && num >= 4)
}
pub(crate) const fn decode(size: Size, num: u8, rex: bool) -> Reg {
let high = matches!(size, Size::Byte) && !rex && num >= 4 && num < 8;
Reg::pack(size, num, high)
}
pub(crate) const fn bits(self) -> u8 {
self.0
}
pub(crate) const fn from_bits(bits: u8) -> Reg {
Reg(bits & 0x7F)
}
}
const NAMES64: [&str; 16] = [
"rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi", "r8", "r9", "r10", "r11", "r12", "r13",
"r14", "r15",
];
const NAMES32: [&str; 16] = [
"eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi", "r8d", "r9d", "r10d", "r11d", "r12d",
"r13d", "r14d", "r15d",
];
const NAMES16: [&str; 16] = [
"ax", "cx", "dx", "bx", "sp", "bp", "si", "di", "r8w", "r9w", "r10w", "r11w", "r12w", "r13w",
"r14w", "r15w",
];
const NAMES8: [&str; 16] = [
"al", "cl", "dl", "bl", "spl", "bpl", "sil", "dil", "r8b", "r9b", "r10b", "r11b", "r12b",
"r13b", "r14b", "r15b",
];
const HIGH_NAMES: [&str; 4] = ["ah", "ch", "dh", "bh"];
macro_rules! regs {
($size:ident: $($name:ident = $num:expr),* $(,)?) => {
$(
#[doc = concat!("The `", stringify!($name), "` register.")]
pub const $name: Reg = Reg::pack(Size::$size, $num, false);
)*
};
}
#[rustfmt::skip]
impl Reg {
regs!(Qword: RAX = 0, RCX = 1, RDX = 2, RBX = 3, RSP = 4, RBP = 5, RSI = 6, RDI = 7,
R8 = 8, R9 = 9, R10 = 10, R11 = 11, R12 = 12, R13 = 13, R14 = 14, R15 = 15);
regs!(Dword: EAX = 0, ECX = 1, EDX = 2, EBX = 3, ESP = 4, EBP = 5, ESI = 6, EDI = 7,
R8D = 8, R9D = 9, R10D = 10, R11D = 11, R12D = 12, R13D = 13, R14D = 14, R15D = 15);
regs!(Word: AX = 0, CX = 1, DX = 2, BX = 3, SP = 4, BP = 5, SI = 6, DI = 7,
R8W = 8, R9W = 9, R10W = 10, R11W = 11, R12W = 12, R13W = 13, R14W = 14, R15W = 15);
regs!(Byte: AL = 0, CL = 1, DL = 2, BL = 3, SPL = 4, BPL = 5, SIL = 6, DIL = 7,
R8B = 8, R9B = 9, R10B = 10, R11B = 11, R12B = 12, R13B = 13, R14B = 14, R15B = 15);
pub const AH: Reg = Reg::pack(Size::Byte, 4, true);
pub const CH: Reg = Reg::pack(Size::Byte, 5, true);
pub const DH: Reg = Reg::pack(Size::Byte, 6, true);
pub const BH: Reg = Reg::pack(Size::Byte, 7, true);
}
impl fmt::Display for Reg {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
impl fmt::Debug for Reg {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_reg_new_every_number_round_trips() {
for size in [Size::Byte, Size::Word, Size::Dword, Size::Qword] {
for num in 0..16 {
let r = Reg::new(size, num);
assert!(r.is_some_and(|r| r.size() == size && r.num() == num && !r.is_high_byte()));
}
assert_eq!(Reg::new(size, 16), None);
assert_eq!(Reg::new(size, 255), None);
}
}
#[test]
fn test_reg_names_are_distinct() {
let mut names = alloc::vec::Vec::new();
for size in [Size::Byte, Size::Word, Size::Dword, Size::Qword] {
for num in 0..16 {
names.extend(Reg::new(size, num).map(Reg::name));
}
}
names.extend([Reg::AH, Reg::CH, Reg::DH, Reg::BH].map(Reg::name));
let count = names.len();
names.sort_unstable();
names.dedup();
assert_eq!(names.len(), count);
}
#[test]
fn test_reg_high_byte_numbers_and_names() {
assert_eq!((Reg::AH.num(), Reg::AH.name()), (4, "ah"));
assert_eq!((Reg::CH.num(), Reg::CH.name()), (5, "ch"));
assert_eq!((Reg::DH.num(), Reg::DH.name()), (6, "dh"));
assert_eq!((Reg::BH.num(), Reg::BH.name()), (7, "bh"));
assert_ne!(Reg::AH, Reg::SPL);
}
#[test]
fn test_reg_needs_rex() {
assert!(!Reg::RAX.needs_rex());
assert!(Reg::R8.needs_rex());
assert!(Reg::SPL.needs_rex());
assert!(Reg::DIL.needs_rex());
assert!(!Reg::AH.needs_rex());
assert!(!Reg::ESP.needs_rex());
assert!(Reg::R15B.needs_rex());
}
#[test]
fn test_reg_decode_high_byte_depends_on_rex() {
assert_eq!(Reg::decode(Size::Byte, 4, false), Reg::AH);
assert_eq!(Reg::decode(Size::Byte, 4, true), Reg::SPL);
assert_eq!(Reg::decode(Size::Dword, 4, false), Reg::ESP);
assert_eq!(Reg::decode(Size::Byte, 12, false), Reg::R12B);
}
#[test]
fn test_reg_bits_round_trip() {
for r in [Reg::RAX, Reg::AH, Reg::R15B, Reg::SP, Reg::R9D] {
assert_eq!(Reg::from_bits(r.bits()), r);
}
}
}