use std::borrow::Cow;
use std::collections::BTreeMap;
use super::super::Core;
use super::packet::{from_hex, hex, to_hex};
use super::threads::Picked;
use crate::emulator::Processor;
use crate::emulator::memory::{from_target, to_target};
use crate::soc::Endian;
const ICOUNT_BYTES: usize = 8;
pub(super) fn description(cpu: &dyn Processor) -> Cow<'static, str> {
with_icount(cpu.target_xml(), family_registers(cpu))
}
fn with_icount(xml: &'static str, number: usize) -> Cow<'static, str> {
let Some((family, after)) = xml.rsplit_once("</target>") else {
return Cow::Borrowed(xml);
};
Cow::Owned(format!(
r#"{family}<feature name="org.rigless.run"><reg name="icount" bitsize="64" type="uint64" regnum="{number}" save-restore="no" group="rigless"/></feature></target>{after}"#
))
}
pub(super) fn value(cpu: &dyn Processor, number: usize, endian: Endian) -> Option<u64> {
match cpu.gdb_register(number) {
Some(bytes) => (bytes.len() <= ICOUNT_BYTES).then(|| from_target(&bytes, endian)),
None => icount(cpu).filter(|_| number == family_registers(cpu)),
}
}
fn family_registers(cpu: &dyn Processor) -> usize {
(0..).map_while(|number| cpu.gdb_register(number)).count()
}
fn icount(cpu: &dyn Processor) -> Option<u64> {
cpu.target_xml()
.contains("</target>")
.then(|| cpu.retired())
}
pub(super) fn named(cpu: &dyn Processor) -> BTreeMap<&'static str, Vec<u8>> {
numbered(cpu.target_xml())
.into_iter()
.filter_map(|(name, number)| Some((name, cpu.gdb_register(number)?)))
.collect()
}
fn values(
core: &Core,
picked: Picked<'_>,
endian: Endian,
) -> Option<Vec<(Option<Vec<u8>>, usize)>> {
let cpu = core
.machine
.cpu
.as_deref()
.expect("the processor between stretches");
let saved = match picked {
Picked::Running => None,
Picked::Switched(saved) => Some(saved),
Picked::Gone => return None,
};
let names: BTreeMap<usize, &str> = numbered(cpu.target_xml())
.into_iter()
.map(|(name, number)| (number, name))
.collect();
let family = (0..)
.map_while(|number| cpu.gdb_register(number))
.enumerate()
.map(|(number, bytes)| {
let width = bytes.len();
match saved {
None => (Some(bytes), width),
Some(saved) => (
names
.get(&number)
.and_then(|name| saved.get(name))
.filter(|kept| kept.len() == width)
.cloned(),
width,
),
}
});
let counted = icount(cpu).map(|count| {
let bytes = to_target(count, ICOUNT_BYTES, endian)[..ICOUNT_BYTES].to_vec();
(Some(bytes), ICOUNT_BYTES)
});
Some(family.chain(counted).collect())
}
fn value_hex((bytes, width): (Option<Vec<u8>>, usize)) -> String {
bytes.map_or_else(|| "xx".repeat(width), |bytes| to_hex(&bytes))
}
pub(super) fn registers(core: &Core, picked: Picked<'_>, endian: Endian) -> String {
values(core, picked, endian).map_or_else(
|| "E01".to_owned(),
|values| values.into_iter().map(value_hex).collect(),
)
}
pub(super) fn set_registers(cpu: &mut dyn Processor, rest: &str) -> &'static str {
let Some(values) = register_values(cpu, rest) else {
return "E01";
};
for (number, bytes) in values.iter().enumerate() {
cpu.set_gdb_register(number, bytes);
}
"OK"
}
pub(super) fn register_values(cpu: &dyn Processor, rest: &str) -> Option<Vec<Vec<u8>>> {
let widths: Vec<usize> = (0..)
.map_while(|number| cpu.gdb_register(number))
.map(|bytes| bytes.len())
.collect();
let counted = icount(cpu).map_or(0, |_| ICOUNT_BYTES);
let bytes =
from_hex(rest).filter(|bytes| bytes.len() == widths.iter().sum::<usize>() + counted)?;
let mut at = 0;
let values = widths.into_iter().map(|width| {
let value = bytes[at..at + width].to_vec();
at += width;
value
});
Some(values.collect())
}
pub(super) fn register(
core: &Core,
picked: Picked<'_>,
rest: &str,
endian: Endian,
) -> Option<String> {
let number = usize::try_from(hex(rest)?).ok()?;
let value = values(core, picked, endian)?.get(number)?.clone();
Some(value_hex(value))
}
pub(super) fn register_write(cpu: &dyn Processor, rest: &str) -> Option<(usize, Vec<u8>)> {
let (number, value) = rest.split_once('=')?;
let number = hex(number).and_then(|number| usize::try_from(number).ok())?;
let width = cpu.gdb_register(number).map(|bytes| bytes.len());
let bytes = from_hex(value).filter(|bytes| Some(bytes.len()) == width)?;
Some((number, bytes))
}
pub(super) fn register_bytes(
cpu: &dyn Processor,
number: usize,
value: u64,
endian: Endian,
) -> Option<Vec<u8>> {
let width = cpu.gdb_register(number)?.len();
(width <= ICOUNT_BYTES).then(|| to_target(value, width, endian)[..width].to_vec())
}
pub(super) fn pc_number(target_xml: &str) -> Option<usize> {
numbered(target_xml)
.into_iter()
.find_map(|(name, number)| (name == "pc").then_some(number))
}
fn numbered(target_xml: &str) -> Vec<(&str, usize)> {
let mut next = 0;
let mut named = Vec::new();
for register in target_xml.split("<reg").skip(1) {
let number = attribute(register, "regnum")
.and_then(|number| number.parse().ok())
.unwrap_or(next);
named.extend(attribute(register, "name").map(|name| (name, number)));
next = number + 1;
}
named
}
fn attribute<'a>(element: &'a str, name: &str) -> Option<&'a str> {
let (element, _) = element.split_once('>')?;
element.split_whitespace().find_map(|each| {
let quoted = each.strip_prefix(name)?.strip_prefix('=')?;
Some(quoted.strip_prefix(QUOTES)?.split_once(QUOTES)?.0)
})
}
const QUOTES: [char; 2] = ['"', '\''];
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_program_counter_is_the_register_the_description_names_pc() {
let numbered = r#"<target><feature name="a"><reg name="r0" bitsize="32"/>
<reg bitsize="32" name="rpc"/><reg name="pc" bitsize="32"/></feature></target>"#;
assert_eq!(pc_number(numbered), Some(2));
let placed = r#"<target><reg name="x" regnum="7"/><reg name="pc" type="code_ptr"/>
<reg name="sp" regnum="40"/></target>"#;
assert_eq!(pc_number(placed), Some(8));
assert_eq!(pc_number("<target><reg name='pc' regnum='15'/>"), Some(15));
assert_eq!(pc_number("<target/>"), None);
}
#[test]
fn icount_follows_the_familys_registers_in_the_description() {
let xml = r#"<target><feature name="a"><reg name="r0" bitsize="32"/></feature></target>"#;
let described = with_icount(xml, 1);
let (family, rest) = described
.split_once(r#"<feature name="org.rigless.run">"#)
.unwrap();
assert_eq!(family, xml.strip_suffix("</target>").unwrap());
assert!(rest.ends_with("</feature></target>"), "{described}");
assert!(
rest.contains(r#"<reg name="icount" bitsize="64" type="uint64" regnum="1""#),
"{described}"
);
assert_eq!(with_icount("<target/>", 40), "<target/>");
}
}