use std::collections::HashMap;
use std::sync::OnceLock;
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum RegClass {
Gpr,
GprHigh,
Segment,
Rip,
Xmm,
Mmx,
Control,
Debug,
St,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct Reg {
pub class: RegClass,
pub num: u8,
pub size: u8,
pub rex_required: bool,
}
impl Reg {
pub fn is_gpr(&self) -> bool {
matches!(self.class, RegClass::Gpr | RegClass::GprHigh)
}
pub fn needs_rex_ext(&self) -> bool {
self.num >= 8
}
pub fn valid_index(&self) -> bool {
self.class == RegClass::Gpr && !(self.num == 4 && self.size >= 4)
}
}
struct Entry {
name: &'static str,
class: RegClass,
num: u8,
size: u8,
rex_required: bool,
}
#[rustfmt::skip]
static REGS: &[Entry] = &{
const fn e(name: &'static str, class: RegClass, num: u8, size: u8, rex_required: bool) -> Entry {
Entry { name, class, num, size, rex_required }
}
use RegClass::*;
[
e("rax", Gpr, 0, 8, false), e("rcx", Gpr, 1, 8, false),
e("rdx", Gpr, 2, 8, false), e("rbx", Gpr, 3, 8, false),
e("rsp", Gpr, 4, 8, false), e("rbp", Gpr, 5, 8, false),
e("rsi", Gpr, 6, 8, false), e("rdi", Gpr, 7, 8, false),
e("r8", Gpr, 8, 8, false), e("r9", Gpr, 9, 8, false),
e("r10", Gpr, 10, 8, false), e("r11", Gpr, 11, 8, false),
e("r12", Gpr, 12, 8, false), e("r13", Gpr, 13, 8, false),
e("r14", Gpr, 14, 8, false), e("r15", Gpr, 15, 8, false),
e("eax", Gpr, 0, 4, false), e("ecx", Gpr, 1, 4, false),
e("edx", Gpr, 2, 4, false), e("ebx", Gpr, 3, 4, false),
e("esp", Gpr, 4, 4, false), e("ebp", Gpr, 5, 4, false),
e("esi", Gpr, 6, 4, false), e("edi", Gpr, 7, 4, false),
e("r8d", Gpr, 8, 4, false), e("r9d", Gpr, 9, 4, false),
e("r10d", Gpr, 10, 4, false), e("r11d", Gpr, 11, 4, false),
e("r12d", Gpr, 12, 4, false), e("r13d", Gpr, 13, 4, false),
e("r14d", Gpr, 14, 4, false), e("r15d", Gpr, 15, 4, false),
e("ax", Gpr, 0, 2, false), e("cx", Gpr, 1, 2, false),
e("dx", Gpr, 2, 2, false), e("bx", Gpr, 3, 2, false),
e("sp", Gpr, 4, 2, false), e("bp", Gpr, 5, 2, false),
e("si", Gpr, 6, 2, false), e("di", Gpr, 7, 2, false),
e("r8w", Gpr, 8, 2, false), e("r9w", Gpr, 9, 2, false),
e("r10w", Gpr, 10, 2, false), e("r11w", Gpr, 11, 2, false),
e("r12w", Gpr, 12, 2, false), e("r13w", Gpr, 13, 2, false),
e("r14w", Gpr, 14, 2, false), e("r15w", Gpr, 15, 2, false),
e("al", Gpr, 0, 1, false), e("cl", Gpr, 1, 1, false),
e("dl", Gpr, 2, 1, false), e("bl", Gpr, 3, 1, false),
e("spl", Gpr, 4, 1, true), e("bpl", Gpr, 5, 1, true),
e("sil", Gpr, 6, 1, true), e("dil", Gpr, 7, 1, true),
e("r8b", Gpr, 8, 1, false), e("r9b", Gpr, 9, 1, false),
e("r10b", Gpr, 10, 1, false), e("r11b", Gpr, 11, 1, false),
e("r12b", Gpr, 12, 1, false), e("r13b", Gpr, 13, 1, false),
e("r14b", Gpr, 14, 1, false), e("r15b", Gpr, 15, 1, false),
e("ah", GprHigh, 4, 1, false), e("ch", GprHigh, 5, 1, false),
e("dh", GprHigh, 6, 1, false), e("bh", GprHigh, 7, 1, false),
e("es", Segment, 0, 2, false), e("cs", Segment, 1, 2, false),
e("ss", Segment, 2, 2, false), e("ds", Segment, 3, 2, false),
e("fs", Segment, 4, 2, false), e("gs", Segment, 5, 2, false),
e("rip", Rip, 0, 8, false), e("eip", Rip, 0, 4, false),
e("xmm0", Xmm, 0, 16, false), e("xmm1", Xmm, 1, 16, false),
e("xmm2", Xmm, 2, 16, false), e("xmm3", Xmm, 3, 16, false),
e("xmm4", Xmm, 4, 16, false), e("xmm5", Xmm, 5, 16, false),
e("xmm6", Xmm, 6, 16, false), e("xmm7", Xmm, 7, 16, false),
e("xmm8", Xmm, 8, 16, false), e("xmm9", Xmm, 9, 16, false),
e("xmm10", Xmm, 10, 16, false), e("xmm11", Xmm, 11, 16, false),
e("xmm12", Xmm, 12, 16, false), e("xmm13", Xmm, 13, 16, false),
e("xmm14", Xmm, 14, 16, false), e("xmm15", Xmm, 15, 16, false),
]
};
fn index() -> &'static HashMap<&'static str, Reg> {
static INDEX: OnceLock<HashMap<&'static str, Reg>> = OnceLock::new();
INDEX.get_or_init(|| {
REGS.iter()
.map(|e| {
(
e.name,
Reg {
class: e.class,
num: e.num,
size: e.size,
rex_required: e.rex_required,
},
)
})
.collect()
})
}
pub fn lookup(name: &str) -> Option<Reg> {
index().get(name).copied()
}
pub fn name_of(r: Reg) -> &'static str {
REGS.iter()
.find(|e| e.class == r.class && e.num == r.num && e.size == r.size)
.map(|e| e.name)
.unwrap_or("?")
}
pub fn is_register(name: &str) -> bool {
lookup(name).is_some()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn looks_up_by_size() {
assert_eq!(
lookup("rax").unwrap(),
Reg {
class: RegClass::Gpr,
num: 0,
size: 8,
rex_required: false
}
);
assert_eq!(lookup("eax").unwrap().size, 4);
assert_eq!(lookup("ax").unwrap().size, 2);
assert_eq!(lookup("al").unwrap().size, 1);
assert_eq!(lookup("r15b").unwrap().num, 15);
}
#[test]
fn high_byte_registers_are_distinct() {
let ah = lookup("ah").unwrap();
assert_eq!(ah.class, RegClass::GprHigh);
assert_eq!(ah.num, 4);
let spl = lookup("spl").unwrap();
assert_eq!(spl.class, RegClass::Gpr);
assert_eq!(spl.num, 4);
assert!(spl.rex_required);
}
#[test]
fn rsp_cannot_be_an_index() {
assert!(!lookup("rsp").unwrap().valid_index());
assert!(lookup("rbp").unwrap().valid_index());
assert!(lookup("r12").unwrap().valid_index());
}
#[test]
fn names_round_trip() {
for n in ["rax", "r13d", "sil", "ah", "xmm7", "gs"] {
assert_eq!(name_of(lookup(n).unwrap()), n);
}
}
}