use core::fmt::Write as _;
use crate::core::device::DeviceClass;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RegType {
Int,
CodePtr,
DataPtr,
}
impl RegType {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
RegType::Int => "int",
RegType::CodePtr => "code_ptr",
RegType::DataPtr => "data_ptr",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RegDesc {
pub name: &'static str,
pub bytes: usize,
pub offset: usize,
pub ty: RegType,
}
impl RegDesc {
#[allow(dead_code)]
const fn int(name: &'static str, bytes: usize, offset: usize) -> RegDesc {
RegDesc {
name,
bytes,
offset,
ty: RegType::Int,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RetireCounter {
pub offset: usize,
pub bytes: usize,
}
#[derive(Debug)]
pub struct Arch {
pub class: &'static DeviceClass,
pub verified_version: u32,
pub feature: &'static str,
pub architecture: Option<&'static str>,
pub regs: &'static [RegDesc],
pub pc: usize,
pub retire: Option<RetireCounter>,
}
impl Arch {
#[must_use]
pub fn packet_len(&self) -> usize {
self.regs.iter().map(|r| r.bytes).sum()
}
#[must_use]
pub fn chunk_reach(&self) -> usize {
let regs = self
.regs
.iter()
.map(|r| r.offset + r.bytes)
.max()
.unwrap_or(0);
match self.retire {
Some(c) => regs.max(c.offset + c.bytes),
None => regs,
}
}
#[must_use]
pub fn check(&self) -> bool {
self.class.version == self.verified_version
}
#[must_use]
pub fn target_xml(&self) -> String {
let mut xml = String::with_capacity(256 + self.regs.len() * 80);
xml.push_str("<?xml version=\"1.0\"?>\n");
xml.push_str("<!DOCTYPE target SYSTEM \"gdb-target.dtd\">\n");
xml.push_str("<target version=\"1.0\">\n");
if let Some(arch) = self.architecture {
let _ = writeln!(xml, " <architecture>{arch}</architecture>");
}
let _ = writeln!(xml, " <feature name=\"{}\">", self.feature);
for (i, reg) in self.regs.iter().enumerate() {
let _ = writeln!(
xml,
" <reg name=\"{}\" bitsize=\"{}\" type=\"{}\" regnum=\"{}\"/>",
reg.name,
reg.bytes * 8,
reg.ty.as_str(),
i
);
}
xml.push_str(" </feature>\n");
xml.push_str("</target>\n");
xml
}
}
#[must_use]
pub fn for_class(class: &str) -> Option<&'static Arch> {
ALL.iter().copied().find(|a| a.class.name == class)
}
#[must_use]
pub fn all() -> &'static [&'static Arch] {
ALL
}
static ALL: &[&Arch] = &[
#[cfg(feature = "cpu-mos6502")]
&MOS6502,
#[cfg(feature = "cpu-z80")]
&Z80,
#[cfg(feature = "cpu-arm-aprofile")]
&ARM,
#[cfg(feature = "cpu-riscv")]
&RISCV,
#[cfg(feature = "cpu-x86")]
&I8086,
];
#[cfg(feature = "cpu-mos6502")]
static MOS6502_REGS: &[RegDesc] = &[
RegDesc::int("a", 1, 0),
RegDesc::int("x", 1, 1),
RegDesc::int("y", 1, 2),
RegDesc {
name: "sp",
bytes: 1,
offset: 3,
ty: RegType::DataPtr,
},
RegDesc::int("p", 1, 4),
RegDesc {
name: "pc",
bytes: 2,
offset: 5,
ty: RegType::CodePtr,
},
];
#[cfg(feature = "cpu-mos6502")]
pub static MOS6502: Arch = Arch {
class: &crate::cpu::mos6502::CLASS,
verified_version: 4,
feature: "org.rsemu.mos6502",
architecture: None,
regs: MOS6502_REGS,
pc: 5,
retire: Some(RetireCounter {
offset: 7,
bytes: 8,
}),
};
#[cfg(feature = "cpu-z80")]
static Z80_REGS: &[RegDesc] = &[
RegDesc::int("af", 2, 0),
RegDesc::int("bc", 2, 2),
RegDesc::int("de", 2, 4),
RegDesc::int("hl", 2, 6),
RegDesc {
name: "sp",
bytes: 2,
offset: 12,
ty: RegType::DataPtr,
},
RegDesc {
name: "pc",
bytes: 2,
offset: 14,
ty: RegType::CodePtr,
},
RegDesc::int("ix", 2, 8),
RegDesc::int("iy", 2, 10),
RegDesc::int("af_", 2, 18),
RegDesc::int("bc_", 2, 20),
RegDesc::int("de_", 2, 22),
RegDesc::int("hl_", 2, 24),
RegDesc::int("i", 1, 26),
RegDesc::int("r", 1, 27),
RegDesc::int("wz", 2, 16),
];
#[cfg(feature = "cpu-z80")]
pub static Z80: Arch = Arch {
class: &crate::cpu::z80::CLASS,
verified_version: 2,
feature: "org.rsemu.z80",
architecture: None,
regs: Z80_REGS,
pc: 5,
retire: Some(RetireCounter {
offset: 35,
bytes: 8,
}),
};
#[cfg(feature = "cpu-arm-aprofile")]
static ARM_REGS: [RegDesc; 17] = arm_regs();
#[cfg(feature = "cpu-arm-aprofile")]
const fn arm_regs() -> [RegDesc; 17] {
const NAMES: [&str; 16] = [
"r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "sp",
"lr", "pc",
];
let mut out = [RegDesc::int("cpsr", 4, 64); 17];
let mut i = 0;
while i < 16 {
out[i] = RegDesc {
name: NAMES[i],
bytes: 4,
offset: i * 4,
ty: match i {
13 => RegType::DataPtr,
15 => RegType::CodePtr,
_ => RegType::Int,
},
};
i += 1;
}
out
}
#[cfg(feature = "cpu-arm-aprofile")]
pub static ARM: Arch = Arch {
class: &crate::cpu::arm::aprofile::CLASS,
verified_version: 3,
feature: "org.rsemu.arm",
architecture: Some("arm"),
regs: &ARM_REGS,
pc: 15,
retire: Some(RetireCounter {
offset: 176,
bytes: 8,
}),
};
#[cfg(feature = "cpu-riscv")]
static RISCV_REGS: [RegDesc; 33] = riscv_regs();
#[cfg(feature = "cpu-riscv")]
const fn riscv_regs() -> [RegDesc; 33] {
const NAMES: [&str; 32] = [
"zero", "ra", "sp", "gp", "tp", "t0", "t1", "t2", "fp", "s1", "a0", "a1", "a2", "a3", "a4",
"a5", "a6", "a7", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9", "s10", "s11", "t3", "t4",
"t5", "t6",
];
let mut out = [RegDesc {
name: "pc",
bytes: 8,
offset: 512,
ty: RegType::CodePtr,
}; 33];
let mut i = 0;
while i < 32 {
out[i] = RegDesc {
name: NAMES[i],
bytes: 8,
offset: i * 8,
ty: if i == 2 {
RegType::DataPtr
} else {
RegType::Int
},
};
i += 1;
}
out
}
#[cfg(feature = "cpu-riscv")]
pub static RISCV: Arch = Arch {
class: &crate::cpu::riscv::CLASS,
verified_version: 1,
feature: "org.rsemu.riscv",
architecture: Some("riscv:rv64"),
regs: &RISCV_REGS,
pc: 32,
retire: Some(RetireCounter {
offset: 520,
bytes: 8,
}),
};
#[cfg(feature = "cpu-x86")]
static I8086_REGS: &[RegDesc] = &[
RegDesc::int("eax", 4, 0),
RegDesc::int("ecx", 4, 4),
RegDesc::int("edx", 4, 8),
RegDesc::int("ebx", 4, 12),
RegDesc {
name: "esp",
bytes: 4,
offset: 16,
ty: RegType::DataPtr,
},
RegDesc::int("ebp", 4, 20),
RegDesc::int("esi", 4, 24),
RegDesc::int("edi", 4, 28),
RegDesc {
name: "eip",
bytes: 4,
offset: 32,
ty: RegType::CodePtr,
},
RegDesc::int("eflags", 4, 36),
RegDesc::int("cs", 4, 40),
RegDesc::int("ss", 4, 44),
RegDesc::int("ds", 4, 48),
RegDesc::int("es", 4, 52),
RegDesc::int("fs", 4, 56),
RegDesc::int("gs", 4, 60),
];
#[cfg(feature = "cpu-x86")]
pub static I8086: Arch = Arch {
class: &crate::cpu::x86::CLASS,
verified_version: 6,
feature: "org.rsemu.i386",
architecture: None,
regs: I8086_REGS,
pc: 8,
retire: Some(RetireCounter {
offset: 64,
bytes: 8,
}),
};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_map_still_matches_the_class_it_describes() {
for arch in all() {
assert!(
arch.check(),
"{}: the register map was written against state version {} but the class \
is at version {}. Re-read that core's `save` and move the offsets.",
arch.class.name,
arch.verified_version,
arch.class.version
);
}
}
#[test]
fn no_map_has_a_duplicate_name_or_an_out_of_range_pc() {
for arch in all() {
assert!(arch.pc < arch.regs.len(), "{}", arch.class.name);
assert_eq!(
arch.regs[arch.pc].ty,
RegType::CodePtr,
"{}: the register named as the pc is not typed as one",
arch.class.name
);
for (i, reg) in arch.regs.iter().enumerate() {
assert!(
!arch.regs[..i].iter().any(|other| other.name == reg.name),
"{}: two registers named `{}`",
arch.class.name,
reg.name
);
assert!(
matches!(reg.bytes, 1 | 2 | 4 | 8),
"{}: `{}` is {} bytes wide",
arch.class.name,
reg.name,
reg.bytes
);
}
}
}
#[test]
fn the_generated_description_names_every_register_once() {
for arch in all() {
let xml = arch.target_xml();
assert!(xml.starts_with("<?xml"), "{}", arch.class.name);
assert!(xml.contains(arch.feature), "{}", arch.class.name);
for (i, reg) in arch.regs.iter().enumerate() {
let expect = format!(
"<reg name=\"{}\" bitsize=\"{}\" type=\"{}\" regnum=\"{}\"/>",
reg.name,
reg.bytes * 8,
reg.ty.as_str(),
i
);
assert!(
xml.contains(&expect),
"{}: missing {expect}",
arch.class.name
);
}
}
}
#[cfg(feature = "cpu-mos6502")]
#[test]
fn the_6502_g_packet_is_seven_bytes() {
assert_eq!(MOS6502.packet_len(), 7);
assert_eq!(MOS6502.chunk_reach(), 15);
}
}